From 7f4a13ad9a32e5f1edfb79f9ebde593fa8bd27f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduard=20Bardaj=C3=AD=20Puig?= Date: Fri, 11 Oct 2024 14:10:40 +0100 Subject: [PATCH] Add faucet, map entry, members --- bun.lockb | Bin 75627 -> 75627 bytes src/index.ts | 24 ++---- src/stacks-api/accounts/index.ts | 9 ++- src/stacks-api/blocks/index.ts | 6 +- src/stacks-api/faucets/index.ts | 6 ++ src/stacks-api/faucets/stx.ts | 47 ++++++++++++ src/stacks-api/index.ts | 47 +++++------- src/stacks-api/info/index.ts | 9 ++- src/stacks-api/proof-of-transfer/index.ts | 18 +++-- src/stacks-api/smart-contracts/index.ts | 6 +- src/stacks-api/stacking-pool/index.ts | 6 +- src/stacks-api/stacking-pool/members.ts | 30 ++++---- src/stacks-api/transactions/index.ts | 9 ++- src/stacks-api/types.ts | 8 ++ src/stacks-rpc-api/index.ts | 6 ++ src/stacks-rpc-api/smart-contracts/index.ts | 6 ++ .../smart-contracts/map-entry.ts | 72 ++++++++++++++++++ 17 files changed, 234 insertions(+), 75 deletions(-) create mode 100644 src/stacks-api/faucets/index.ts create mode 100644 src/stacks-api/faucets/stx.ts create mode 100644 src/stacks-rpc-api/index.ts create mode 100644 src/stacks-rpc-api/smart-contracts/index.ts create mode 100644 src/stacks-rpc-api/smart-contracts/map-entry.ts diff --git a/bun.lockb b/bun.lockb index 83aa2d095ef2283242568c1e992b1e606f3286c0..541c8a01b88934ea5cc11c122cb8f087b749887e 100755 GIT binary patch delta 22 ecmaETj^*_^mJQbD*%{+3^o;aOHanfyi~s<0T?pC$ delta 22 acmaETj^*_^mJQbD*_jx?V6)SC%?JQzhX;cI diff --git a/src/index.ts b/src/index.ts index a1ff133..bc5c869 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,21 +1,9 @@ -import { - accounts, - blocks, - info, - proofOfTransfer, - smartContracts, - stackingPool, - transactions, -} from "./stacks-api/index.js"; -export const stacksApi = { - accounts, - blocks, - info, - proofOfTransfer, - smartContracts, - stackingPool, - transactions, -}; +export { stacksApi } from "./stacks-api/index.js"; export type * as StacksApi from "./stacks-api/index.js"; +export { stacksRpcApi } from "./stacks-rpc-api/index.js"; +export type * as StacksRpcApi from "./stacks-rpc-api/index.js"; + +export { queries } from "./queries/index.js"; + export * from "./utils/index.js"; diff --git a/src/stacks-api/accounts/index.ts b/src/stacks-api/accounts/index.ts index 008c5f8..e157670 100644 --- a/src/stacks-api/accounts/index.ts +++ b/src/stacks-api/accounts/index.ts @@ -1,5 +1,10 @@ -export { balances } from "./balances.js"; +import { balances } from "./balances.js"; export type * as Balances from "./balances.js"; -export { latestNonce } from "./latest-nonce.js"; +import { latestNonce } from "./latest-nonce.js"; export type * as LatestNonce from "./latest-nonce.js"; + +export const accounts = { + balances, + latestNonce, +}; diff --git a/src/stacks-api/blocks/index.ts b/src/stacks-api/blocks/index.ts index d8f8fd7..712f5d9 100644 --- a/src/stacks-api/blocks/index.ts +++ b/src/stacks-api/blocks/index.ts @@ -1,2 +1,6 @@ -export { getBlock } from "./get-block.js"; +import { getBlock } from "./get-block.js"; export type * as GetBlock from "./get-block.js"; + +export const blocks = { + getBlock, +}; diff --git a/src/stacks-api/faucets/index.ts b/src/stacks-api/faucets/index.ts new file mode 100644 index 0000000..3de79c6 --- /dev/null +++ b/src/stacks-api/faucets/index.ts @@ -0,0 +1,6 @@ +import { stx } from "./stx.js"; +export type * as Stx from "./stx.js"; + +export const faucets = { + stx, +}; diff --git a/src/stacks-api/faucets/stx.ts b/src/stacks-api/faucets/stx.ts new file mode 100644 index 0000000..474e2f6 --- /dev/null +++ b/src/stacks-api/faucets/stx.ts @@ -0,0 +1,47 @@ +import { error, safePromise, success, type Result } from "../../utils/safe.js"; +import type { ApiRequestOptions } from "../types.js"; + +export type Args = { + address: string; + stacking?: boolean; +} & ApiRequestOptions; + +export async function stx(opts: Args): Promise> { + const search = new URLSearchParams(); + search.append("address", opts.address); + if (opts.stacking) search.append("stacking", "true"); + + const init: RequestInit = {}; + if (opts.apiKeyConfig) { + init.headers = { + [opts.apiKeyConfig.header]: opts.apiKeyConfig.key, + }; + } + init.method = "POST"; + + const endpoint = `${opts.baseUrl}/extended/v1/faucets/stx?${search}`; + const res = await fetch(endpoint, init); + + if (!res.ok) { + return error({ + name: "FetchStxError", + message: "Failed to fetch STX.", + data: { + status: res.status, + statusText: res.statusText, + bodyParseResult: await safePromise(res.json()), + }, + }); + } + + const [jsonError, data] = await safePromise(res.json()); + if (jsonError) { + return error({ + name: "ParseBodyError", + message: "Failed to parse response body as JSON.", + data: jsonError, + }); + } + + return success(data); +} diff --git a/src/stacks-api/index.ts b/src/stacks-api/index.ts index 1117f84..1876ab6 100644 --- a/src/stacks-api/index.ts +++ b/src/stacks-api/index.ts @@ -1,39 +1,34 @@ -import { balances, latestNonce } from "./accounts/index.js"; -export const accounts = { balances, latestNonce }; +import { accounts } from "./accounts/index.js"; export type * as Accounts from "./accounts/index.js"; -import { getBlock } from "./blocks/index.js"; -export const blocks = { getBlock }; +import { blocks } from "./blocks/index.js"; export type * as Blocks from "./blocks/index.js"; -import { coreApi, poxDetails } from "./info/index.js"; -export const info = { coreApi, poxDetails }; +import { faucets } from "./faucets/index.js"; +export type * as Faucets from "./faucets/index.js"; + +import { info } from "./info/index.js"; export type * as Info from "./info/index.js"; -import { - cycle, - cycles, - signerInCycle, - signersInCycle, - stackersForSignerInCycle, -} from "./proof-of-transfer/index.js"; -export const proofOfTransfer = { - cycle, - cycles, - signerInCycle, - signersInCycle, - stackersForSignerInCycle, -}; +import { proofOfTransfer } from "./proof-of-transfer/index.js"; export type * as ProofOfTransfer from "./proof-of-transfer/index.js"; -import { readOnly } from "./smart-contracts/index.js"; -export const smartContracts = { readOnly }; +import { smartContracts } from "./smart-contracts/index.js"; export type * as SmartContracts from "./smart-contracts/index.js"; -import { members } from "./stacking-pool/index.js"; -export const stackingPool = { members }; +import { stackingPool } from "./stacking-pool/index.js"; export type * as StackingPool from "./stacking-pool/index.js"; -import { addressTransactions, getTransaction } from "./transactions/index.js"; -export const transactions = { addressTransactions, getTransaction }; +import { transactions } from "./transactions/index.js"; export type * as Transactions from "./transactions/index.js"; + +export const stacksApi = { + accounts, + blocks, + faucets, + info, + proofOfTransfer, + smartContracts, + stackingPool, + transactions, +}; diff --git a/src/stacks-api/info/index.ts b/src/stacks-api/info/index.ts index b2d1fdd..ae26d5e 100644 --- a/src/stacks-api/info/index.ts +++ b/src/stacks-api/info/index.ts @@ -1,5 +1,10 @@ -export { coreApi } from "./core-api.js"; +import { coreApi } from "./core-api.js"; export type * as CoreApi from "./core-api.js"; -export { poxDetails } from "./pox-details.js"; +import { poxDetails } from "./pox-details.js"; export type * as PoxDetails from "./pox-details.js"; + +export const info = { + coreApi, + poxDetails, +}; diff --git a/src/stacks-api/proof-of-transfer/index.ts b/src/stacks-api/proof-of-transfer/index.ts index 113bc13..bb8221f 100644 --- a/src/stacks-api/proof-of-transfer/index.ts +++ b/src/stacks-api/proof-of-transfer/index.ts @@ -1,14 +1,22 @@ -export { cycle } from "./cycle.js"; +import { cycle } from "./cycle.js"; export type * as Cycle from "./cycle.js"; -export { cycles } from "./cycles.js"; +import { cycles } from "./cycles.js"; export type * as Cycles from "./cycles.js"; -export { signerInCycle } from "./signer-in-cycle.js"; +import { signerInCycle } from "./signer-in-cycle.js"; export type * as SignerInCycle from "./signer-in-cycle.js"; -export { signersInCycle } from "./signers-in-cycle.js"; +import { signersInCycle } from "./signers-in-cycle.js"; export type * as SignersInCycle from "./signers-in-cycle.js"; -export { stackersForSignerInCycle } from "./stackers-for-signer-in-cycle.js"; +import { stackersForSignerInCycle } from "./stackers-for-signer-in-cycle.js"; export type * as StackersForSignerInCycle from "./stackers-for-signer-in-cycle.js"; + +export const proofOfTransfer = { + cycle, + cycles, + signerInCycle, + signersInCycle, + stackersForSignerInCycle, +}; diff --git a/src/stacks-api/smart-contracts/index.ts b/src/stacks-api/smart-contracts/index.ts index 0e3ea6a..c0f37ff 100644 --- a/src/stacks-api/smart-contracts/index.ts +++ b/src/stacks-api/smart-contracts/index.ts @@ -1,2 +1,6 @@ -export { readOnly } from "./read-only.js"; +import { readOnly } from "./read-only.js"; export type * as ReadOnly from "./read-only.js"; + +export const smartContracts = { + readOnly, +}; diff --git a/src/stacks-api/stacking-pool/index.ts b/src/stacks-api/stacking-pool/index.ts index 3b9e3f6..733344e 100644 --- a/src/stacks-api/stacking-pool/index.ts +++ b/src/stacks-api/stacking-pool/index.ts @@ -1,2 +1,6 @@ -export { members } from "./members.js"; +import { members } from "./members.js"; export type * as Members from "./members.js"; + +export const stackingPool = { + members, +}; diff --git a/src/stacks-api/stacking-pool/members.ts b/src/stacks-api/stacking-pool/members.ts index c571bce..ed0f84f 100644 --- a/src/stacks-api/stacking-pool/members.ts +++ b/src/stacks-api/stacking-pool/members.ts @@ -1,14 +1,15 @@ import { error, safePromise, success, type Result } from "../../utils/safe.js"; -import { type ApiRequestOptions } from "../types.js"; +import { type ApiPaginationOptions, type ApiRequestOptions } from "../types.js"; import * as v from "valibot"; -export type Options = { +export type Args = { poolPrincipal: string; afterBlock?: number; unanchored?: boolean; limit?: number; offset?: number; -}; +} & ApiRequestOptions & + ApiPaginationOptions; export const memberSchema = v.object({ stacker: v.string(), @@ -28,27 +29,22 @@ export const membersResponseSchema = v.object({ }); export type MembersResponse = v.InferOutput; -export async function members( - opts: Options, - apiOpts: ApiRequestOptions, -): Promise> { +export async function members(args: Args): Promise> { const search = new URLSearchParams(); - if (opts.afterBlock) search.append("after_block", opts.afterBlock.toString()); - if (opts.unanchored) search.append("unanchored", "true"); - if (opts.limit) search.append("limit", opts.limit.toString()); - if (opts.offset) search.append("offset", opts.offset.toString()); + if (args.afterBlock) search.append("after_block", args.afterBlock.toString()); + if (args.unanchored) search.append("unanchored", "true"); + if (args.limit) search.append("limit", args.limit.toString()); + if (args.offset) search.append("offset", args.offset.toString()); const init: RequestInit = {}; - if (apiOpts.apiKeyConfig) { + if (args.apiKeyConfig) { init.headers = { - [apiOpts.apiKeyConfig.header]: apiOpts.apiKeyConfig.key, + [args.apiKeyConfig.header]: args.apiKeyConfig.key, }; } - const res = await fetch( - `${apiOpts.baseUrl}/extended/beta/stacking/${opts.poolPrincipal}/delegations?${search}`, - init, - ); + const endpoint = `${args.baseUrl}/extended/v1/pox4/${args.poolPrincipal}/delegations?${search}`; + const res = await fetch(endpoint, init); if (!res.ok) { return error({ diff --git a/src/stacks-api/transactions/index.ts b/src/stacks-api/transactions/index.ts index 339694d..39d3afb 100644 --- a/src/stacks-api/transactions/index.ts +++ b/src/stacks-api/transactions/index.ts @@ -1,7 +1,12 @@ -export { addressTransactions } from "./address-transactions.js"; +import { addressTransactions } from "./address-transactions.js"; export type * as AddressTransactions from "./address-transactions.js"; -export { getTransaction } from "./get-transaction.js"; +import { getTransaction } from "./get-transaction.js"; export type * as GetTransaction from "./get-transaction.js"; export type * as Common from "./schemas.js"; + +export const transactions = { + addressTransactions, + getTransaction, +}; diff --git a/src/stacks-api/types.ts b/src/stacks-api/types.ts index e466608..f5369aa 100644 --- a/src/stacks-api/types.ts +++ b/src/stacks-api/types.ts @@ -14,6 +14,14 @@ export type ApiRequestOptions = { apiKeyConfig?: ApiKeyConfig; }; +export type ProofAndTip = { + /** + * Returns object without the proof field when set to 0. + */ + proof?: number; + tip?: "latest" | string; +}; + export type ApiPaginationOptions = { /** * The number of items to return. Each endpoint has its own maximum allowed diff --git a/src/stacks-rpc-api/index.ts b/src/stacks-rpc-api/index.ts new file mode 100644 index 0000000..b57fe33 --- /dev/null +++ b/src/stacks-rpc-api/index.ts @@ -0,0 +1,6 @@ +import { smartContracts } from "./smart-contracts/index.js"; +export type * as SmartContracts from "./smart-contracts/index.js"; + +export const stacksRpcApi = { + smartContracts, +}; diff --git a/src/stacks-rpc-api/smart-contracts/index.ts b/src/stacks-rpc-api/smart-contracts/index.ts new file mode 100644 index 0000000..fa2dd3b --- /dev/null +++ b/src/stacks-rpc-api/smart-contracts/index.ts @@ -0,0 +1,6 @@ +import { mapEntry } from "./map-entry.js"; +export type * as MapEntry from "./map-entry.js"; + +export const smartContracts = { + mapEntry, +}; diff --git a/src/stacks-rpc-api/smart-contracts/map-entry.ts b/src/stacks-rpc-api/smart-contracts/map-entry.ts new file mode 100644 index 0000000..d71fc7c --- /dev/null +++ b/src/stacks-rpc-api/smart-contracts/map-entry.ts @@ -0,0 +1,72 @@ +import type { ApiRequestOptions, ProofAndTip } from "../../stacks-api/types.js"; +import { error, safePromise, success, type Result } from "../../utils/safe.js"; +import * as v from "valibot"; + +export type Args = { + contractAddress: string; + contractName: string; + mapName: string; + mapKey: string; +} & ApiRequestOptions & + ProofAndTip; + +const mapEntryResponseSchema = v.object({ + /** + * Hex-encoded string of clarity value. It is always an optional tuple. + */ + data: v.string(), + /** + * Hex-encoded string of the MARF proof for the data + */ + proof: v.optional(v.string()), +}); +export type MapEntryResponse = v.InferOutput; + +export async function mapEntry(args: Args): Promise> { + const search = new URLSearchParams(); + if (args.proof === 0) search.append("proof", "0"); + if (args.tip) search.append("tip", args.tip); + + const init: RequestInit = {}; + if (args.apiKeyConfig) { + init.headers = { + [args.apiKeyConfig.header]: args.apiKeyConfig.key, + }; + } + init.method = "POST"; + init.body = args.mapKey; + + const endpoint = `${args.baseUrl}/v2/map_entry/${args.contractAddress}/${args.contractName}/${args.mapName}?${search}`; + const res = await fetch(endpoint, init); + if (!res.ok) { + return error({ + name: "FetchMapEntryError", + message: "Failed to fetch map entry.", + data: { + status: res.status, + statusText: res.statusText, + bodyParseResult: await safePromise(res.json()), + }, + }); + } + + const [jsonError, data] = await safePromise(res.json()); + if (jsonError) { + return error({ + name: "ParseBodyError", + message: "Failed to parse response body as JSON.", + data: jsonError, + }); + } + + const validationResult = v.safeParse(mapEntryResponseSchema, data); + if (!validationResult.success) { + return error({ + name: "ValidateDataError", + message: "Failed to validate response data.", + data: validationResult, + }); + } + + return success(validationResult.output); +}