UNPKG

@fastnear/api

Version:

Interact with NEAR Protocol blockchain including transaction signing, utilities, and more.

1 lines 84.5 kB
{"version":3,"sources":["../../src/near.ts"],"sourcesContent":["import {\n lsSet,\n lsGet,\n tryParseJson,\n fromBase64,\n toBase64,\n canSignWithLAK,\n toBase58,\n parseJsonFromBytes,\n signHash,\n serializeTransaction,\n serializeSignedTransaction, bytesToBase64, PlainTransaction,\n} from \"@fastnear/utils\";\n\nimport type { NEP413Message } from \"@fastnear/utils\";\n\nimport {\n _state,\n DEFAULT_NETWORK_ID,\n NETWORKS,\n getAccountState,\n getActiveNetwork,\n getWalletProvider,\n setActiveNetwork,\n setWalletProvider,\n getTxHistory,\n update,\n updateAccountState,\n updateTxHistory,\n} from \"./state.js\";\n\nimport type { WalletProvider, FastNearNetworkId } from \"./state.js\";\nimport type { NetworkConfig } from \"./state.js\";\nimport type {\n AccessKeyWithError,\n BlockView,\n ExplainedAction,\n ExplainedError,\n ExplainedTransaction,\n FastNearApiV1AccountFtResponse,\n FastNearApiV1AccountFullResponse,\n FastNearApiV1AccountNftResponse,\n FastNearApiV1AccountStakingResponse,\n FastNearApiV1FtTopResponse,\n FastNearApiV1PublicKeyAllResponse,\n FastNearApiV1PublicKeyResponse,\n FastNearRecipeDiscoveryEntry,\n FastNearRecipeViewAccountResult,\n FastNearKvAllByPredecessorResponse,\n FastNearKvGetHistoryKeyResponse,\n FastNearKvGetLatestKeyResponse,\n FastNearKvHistoryByAccountResponse,\n FastNearKvHistoryByPredecessorResponse,\n FastNearKvLatestByAccountResponse,\n FastNearKvLatestByPredecessorResponse,\n FastNearKvMultiResponse,\n FastNearNeardataBlockChunkResponse,\n FastNearNeardataBlockHeadersResponse,\n FastNearNeardataBlockOptimisticResponse,\n FastNearNeardataBlockResponse,\n FastNearNeardataBlockShardResponse,\n FastNearNeardataFirstBlockResponse,\n FastNearNeardataHealthResponse,\n FastNearNeardataLastBlockFinalResponse,\n FastNearNeardataLastBlockOptimisticResponse,\n FastNearTransfersQueryResponse,\n FastNearRpcQueryAccountResponse,\n FastNearTxAccountResponse,\n FastNearTxBlockResponse,\n FastNearTxBlocksResponse,\n FastNearTxReceiptResponse,\n FastNearTxTransactionRow,\n FastNearTxTransactionsResponse,\n LastKnownBlock,\n RecipeConnectParams,\n RecipeFunctionCallParams,\n RecipeInspectTransactionInput,\n RecipeInspectTransactionParams,\n RecipeViewAccountInput,\n RecipeTransferParams,\n RecipeViewAccountParams,\n RecipeViewContractParams,\n} from \"./types.js\";\n\nimport {\n getConfig,\n setConfig,\n resetTxHistory,\n resolveConfig,\n} from \"./state.js\";\n\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport * as reExportAllUtils from \"@fastnear/utils\";\nimport * as stateExports from \"./state.js\";\n\nexport const MaxBlockDelayMs = 1000 * 60 * 60 * 6; // 6 hours\n\nfunction normalizeActionParams(action: any): Record<string, any> {\n if (!action || typeof action !== \"object\") {\n return {};\n }\n if (action.params && typeof action.params === \"object\") {\n return { ...action.params };\n }\n const { type, ...rest } = action;\n return { ...rest };\n}\n\nfunction looksRetryable(message: string, code: string | number | null, kind: ExplainedError[\"kind\"]): boolean {\n if (kind === \"transport_error\") {\n return true;\n }\n if (typeof code === \"number\" && [408, 429, 500, 502, 503, 504, -32000].includes(code)) {\n return true;\n }\n return /timeout|temporar|temporarily|rate limit|unavailable|network|gateway/i.test(message);\n}\n\nfunction parseErrorPayload(error: unknown): {\n code: string | number | null;\n name: string | null;\n message: string;\n data: any;\n kind: ExplainedError[\"kind\"];\n} {\n const fallbackMessage = error instanceof Error\n ? error.message\n : typeof error === \"string\"\n ? error\n : \"Unknown error\";\n\n const parsedMessage = tryParseJson(fallbackMessage);\n const parsedObject = parsedMessage && typeof parsedMessage === \"object\"\n ? parsedMessage\n : error && typeof error === \"object\"\n ? error\n : null;\n\n const code = parsedObject && \"code\" in parsedObject ? (parsedObject as any).code : null;\n const data = parsedObject && \"data\" in parsedObject\n ? (parsedObject as any).data\n : parsedMessage && parsedMessage !== parsedObject\n ? parsedMessage\n : null;\n const name = error instanceof Error\n ? error.name\n : parsedObject && \"name\" in parsedObject\n ? String((parsedObject as any).name)\n : \"Error\";\n const message = parsedObject && \"message\" in parsedObject\n ? String((parsedObject as any).message)\n : fallbackMessage;\n\n let kind: ExplainedError[\"kind\"] = \"error\";\n if (parsedObject && (\"code\" in parsedObject || \"data\" in parsedObject)) {\n kind = \"rpc_error\";\n } else if (/wallet/i.test(message)) {\n kind = \"wallet_error\";\n } else if (/network|timeout|fetch|gateway|unavailable/i.test(message)) {\n kind = \"transport_error\";\n }\n\n return {\n code,\n name,\n message,\n data,\n kind,\n };\n}\n\nexport function withBlockId(params: Record<string, any>, blockId?: string) {\n if (blockId === \"final\" || blockId === \"optimistic\") {\n return { ...params, finality: blockId };\n }\n return blockId ? { ...params, block_id: blockId } : { ...params, finality: \"optimistic\" };\n}\n\ntype ServiceFamily = \"rpc\" | \"api\" | \"tx\" | \"transfers\" | \"neardata\" | \"fastdata.kv\";\ntype ServiceAuthStyle = \"none\" | \"bearer\" | \"query\";\n\ninterface ServiceRequestOptions {\n family: Exclude<ServiceFamily, \"rpc\">;\n path: string;\n method?: \"GET\" | \"POST\";\n query?: Record<string, any>;\n body?: any;\n headers?: Record<string, string>;\n // When set, routes the request through the override network's defaults\n // (URLs from `NETWORKS[network].services.*`) instead of the active\n // config. The active config's `apiKey` still flows through. Without\n // it, the active config is used end-to-end.\n network?: FastNearNetworkId;\n}\n\nconst SERVICE_AUTH_STYLES: Record<ServiceFamily, ServiceAuthStyle> = {\n rpc: \"query\",\n api: \"bearer\",\n tx: \"bearer\",\n transfers: \"bearer\",\n neardata: \"query\",\n \"fastdata.kv\": \"bearer\",\n};\n\nfunction omitUndefinedEntries<T extends Record<string, any>>(value?: T): T | undefined {\n if (!value) {\n return undefined;\n }\n\n return Object.fromEntries(\n Object.entries(value).filter(([, entry]) => entry !== undefined)\n ) as T;\n}\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/+$/, \"\");\n}\n\nfunction trimLeadingSlash(value: string): string {\n return value.replace(/^\\/+/, \"\");\n}\n\nfunction appendQueryParams(url: URL, query?: Record<string, any>): URL {\n if (!query) {\n return url;\n }\n\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) {\n continue;\n }\n if (Array.isArray(value)) {\n value.forEach((item) => {\n if (item !== undefined && item !== null) {\n url.searchParams.append(key, String(item));\n }\n });\n continue;\n }\n url.searchParams.set(key, String(value));\n }\n\n return url;\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: Record<string, any>): string {\n const url = new URL(trimLeadingSlash(path), `${trimTrailingSlash(baseUrl)}/`);\n return appendQueryParams(url, query).toString();\n}\n\nasync function parseResponsePayload(response: Response) {\n const text = await response.text();\n if (!text) {\n return null;\n }\n\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction buildHttpError(service: ServiceFamily, response: Response, payload: any): Error {\n return new Error(\n JSON.stringify({\n code: response.status,\n name: `${service}.http_error`,\n message: `${service} request failed with ${response.status} ${response.statusText}`,\n data: payload,\n })\n );\n}\n\n// Resolve the config to use for a per-call override. Without an\n// override, returns the active config. With one, returns a config built\n// from `NETWORKS[network].services` (so URLs hit the right hosts) but\n// inheriting the active `apiKey` (which is account-bound, not\n// network-bound). User-applied per-network URL overrides via\n// `near.config({ services: …, networkId: \"testnet\" })` are not currently\n// preserved across cross-network calls — callers who need that should\n// switch via `near.config({ networkId })` and back.\nfunction resolveConfigForCall(network?: FastNearNetworkId): NetworkConfig {\n const active = getConfig();\n if (!network || network === active.networkId) {\n return active;\n }\n return resolveConfig({ apiKey: active.apiKey ?? null }, NETWORKS[network]);\n}\n\nfunction resolveServiceBaseUrl(family: Exclude<ServiceFamily, \"rpc\">, config: NetworkConfig): string {\n let baseUrl: string | null | undefined;\n\n switch (family) {\n case \"api\":\n baseUrl = config.services?.api?.baseUrl;\n break;\n case \"tx\":\n baseUrl = config.services?.tx?.baseUrl;\n break;\n case \"transfers\":\n baseUrl = config.services?.transfers?.baseUrl;\n break;\n case \"neardata\":\n baseUrl = config.services?.neardata?.baseUrl;\n break;\n case \"fastdata.kv\":\n baseUrl = config.services?.fastdata?.kvBaseUrl;\n break;\n }\n\n if (!baseUrl) {\n if (family === \"transfers\" && config.networkId === \"testnet\") {\n throw new Error(\n \"fastnear: transfers service is not configured for testnet. Provide near.config({ services: { transfers: { baseUrl: \\\"https://...\\\" } } }) to override.\"\n );\n }\n throw new Error(`fastnear: ${family} service is not configured for ${config.networkId}.`);\n }\n\n return baseUrl;\n}\n\nfunction resolveRpcUrl(config: NetworkConfig): string {\n const rpcUrl = config.nodeUrl || config.services?.rpc?.baseUrl;\n if (!rpcUrl) {\n throw new Error(\"fastnear: getConfig() returned invalid config: missing nodeUrl.\");\n }\n return rpcUrl;\n}\n\n// Pick the archival RPC if configured; fall back to the regular RPC so\n// callers that opt into `useArchival` against a network without archival\n// configured still get an answer (just from the non-archival node).\nfunction resolveArchivalUrl(config: NetworkConfig): string {\n return config.services?.archival?.baseUrl || resolveRpcUrl(config);\n}\n\nfunction buildAuthedUrl(\n service: ServiceFamily,\n baseUrl: string,\n path = \"\",\n query?: Record<string, any>,\n config: NetworkConfig = getConfig(),\n): string {\n const authStyle = SERVICE_AUTH_STYLES[service];\n const authQuery =\n authStyle === \"query\" && config.apiKey\n ? { ...(query || {}), apiKey: config.apiKey }\n : query;\n\n return buildUrl(baseUrl, path, authQuery);\n}\n\nasync function sendServiceRequest<T = any>({\n family,\n path,\n method = \"GET\",\n query,\n body,\n headers = {},\n network,\n}: ServiceRequestOptions): Promise<T> {\n const config = resolveConfigForCall(network);\n const authStyle = SERVICE_AUTH_STYLES[family];\n const url = buildAuthedUrl(family, resolveServiceBaseUrl(family, config), path, query, config);\n const requestHeaders: Record<string, string> = { ...headers };\n\n if (authStyle === \"bearer\" && config.apiKey) {\n requestHeaders.Authorization = `Bearer ${config.apiKey}`;\n }\n\n let requestBody: string | undefined;\n if (body !== undefined) {\n requestHeaders[\"Content-Type\"] = requestHeaders[\"Content-Type\"] || \"application/json\";\n requestBody = JSON.stringify(body);\n }\n\n const response = await fetch(url, {\n method,\n headers: requestHeaders,\n body: requestBody,\n });\n const payload = await parseResponsePayload(response);\n\n if (!response.ok) {\n throw buildHttpError(family, response, payload);\n }\n\n return payload as T;\n}\n\nexport interface RpcRouteOptions {\n /**\n * Route this call to the archival RPC (`services.archival.baseUrl`)\n * instead of the default. Useful for queries with a historical\n * `blockId`. Falls back to the regular RPC if archival isn't configured.\n */\n useArchival?: boolean;\n /**\n * Route this call to the override network's RPC instead of the active\n * `near.config().networkId`. Useful when a page holds parallel\n * mainnet+testnet sessions and a single read or write needs to target\n * the non-active network without flipping config back and forth.\n */\n network?: FastNearNetworkId;\n}\n\nexport async function sendRpc<T = any>(\n method: string,\n params: Record<string, any> | any[],\n options?: RpcRouteOptions,\n): Promise<T> {\n const config = resolveConfigForCall(options?.network);\n const baseUrl = options?.useArchival ? resolveArchivalUrl(config) : resolveRpcUrl(config);\n const response = await fetch(buildAuthedUrl(\"rpc\", baseUrl, \"\", undefined, config), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: `fastnear-${Date.now()}`,\n method,\n params,\n }),\n });\n const result = await parseResponsePayload(response);\n if (!response.ok) {\n throw buildHttpError(\"rpc\", response, result);\n }\n if (result && typeof result === \"object\" && \"error\" in result && (result as any).error) {\n throw new Error(JSON.stringify(result.error));\n }\n return result as T;\n}\n\nexport function afterTxSent(txId: string, network?: FastNearNetworkId) {\n const txHistory = getTxHistory();\n sendRpc(\"tx\", {\n tx_hash: txHistory[txId]?.txHash,\n sender_account_id: txHistory[txId]?.tx?.signerId,\n wait_until: \"EXECUTED_OPTIMISTIC\",\n }, { network })\n .then( result => {\n const successValue = result?.result?.status?.SuccessValue;\n updateTxHistory({\n txId,\n status: \"Executed\",\n result,\n successValue: successValue ? tryParseJson(fromBase64(successValue)) : undefined,\n finalState: true,\n });\n })\n .catch((error) => {\n updateTxHistory({\n txId,\n status: \"ErrorAfterIncluded\",\n error: tryParseJson(error.message) ?? error.message,\n finalState: true,\n });\n });\n}\n\nexport async function sendTxToRpc(\n signedTxBase64: string,\n waitUntil: string | undefined,\n txId: string,\n network?: FastNearNetworkId,\n) {\n // default to \"INCLUDED\"\n // see options: https://docs.near.org/api/rpc/transactions#tx-status-result\n waitUntil = waitUntil || \"INCLUDED\";\n\n try {\n const sendTxRes = await sendRpc(\"send_tx\", {\n signed_tx_base64: signedTxBase64,\n wait_until: waitUntil,\n }, { network });\n\n updateTxHistory({ txId, status: \"Included\", finalState: false });\n afterTxSent(txId, network);\n\n return sendTxRes;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson(errorMessage) ?? errorMessage,\n finalState: false,\n });\n throw new Error(errorMessage);\n }\n}\n\n/**\n * Generates a mock transaction ID.\n */\nexport function generateTxId(): string {\n const randomPart = crypto.getRandomValues(new Uint32Array(2)).join(\"\");\n return `tx-${Date.now()}-${parseInt(randomPart, 10).toString(36)}`;\n}\n\nexport const accountId = (options: { network?: FastNearNetworkId } = {}) =>\n getAccountState(options.network).accountId;\n\nexport const publicKey = (options: { network?: FastNearNetworkId } = {}) =>\n getAccountState(options.network).publicKey;\n\nexport const config = (newConfig?: Partial<NetworkConfig>) => {\n const current = getConfig();\n if (newConfig) {\n const networkChanging =\n !!newConfig.networkId && current.networkId !== newConfig.networkId;\n setConfig(newConfig);\n if (networkChanging) {\n // Per-network state and per-network block cache are preserved\n // across config switches — each network's slot survives. tx\n // history is still cleared because it's keyed by local txId, not\n // network, and a switch is the natural boundary for \"I'm done\n // with the previous network's recent activity.\"\n resetTxHistory();\n }\n // Whenever a `networkId` is explicitly specified, pin the active\n // cursor to it — even when config.networkId was already that value.\n // Callers use `config({ networkId })` as the canonical \"switch\n // default network\" idiom, so it should also flip active.\n if (newConfig.networkId) {\n setActiveNetwork(getConfig().networkId);\n }\n }\n return getConfig();\n};\n\nexport const authStatus = (\n options: { network?: FastNearNetworkId } = {},\n): string | Record<string, any> => {\n return getAccountState(options.network).accountId ? \"SignedIn\" : \"SignedOut\";\n};\n\nexport const getPublicKeyForContract = (options: { network?: FastNearNetworkId } = {}) => {\n return publicKey(options);\n}\n\nexport const selected = (options: { network?: FastNearNetworkId } = {}) => {\n const network = options.network ?? getConfig().networkId;\n const slot = getAccountState(network);\n\n return {\n network,\n nodeUrl: getConfig().nodeUrl,\n walletUrl: getConfig().walletUrl,\n helperUrl: getConfig().helperUrl,\n explorerUrl: getConfig().explorerUrl,\n account: slot.accountId,\n contract: slot.accessKeyContractId,\n publicKey: slot.publicKey,\n }\n}\n\nexport const requestSignIn = async ({\n contractId,\n excludedWallets,\n features,\n network,\n}: {\n contractId?: string;\n excludedWallets?: string[];\n features?: Record<string, boolean>;\n network?: FastNearNetworkId;\n} = {}) => {\n const provider = getWalletProvider();\n if (!provider) {\n throw new Error(\"No wallet provider set. Call useWallet() first or load the @fastnear/wallet IIFE bundle.\");\n }\n\n const targetNetwork = network ?? getConfig().networkId;\n\n // Drop any prior session on the *target* network only — leaving sessions\n // on other networks intact. With @fastnear/wallet 1.1.0+ the optional\n // `{ network }` argument scopes both checks; older providers that ignore\n // the option fall back to \"active network\", which matches pre-1.1.2\n // behavior since `targetNetwork` defaulted to the active config.\n if (provider.isConnected({ network: targetNetwork })) {\n await provider.disconnect({ network: targetNetwork });\n }\n\n const result = await provider.connect({\n contractId,\n network: targetNetwork,\n excludedWallets,\n features,\n });\n\n if (!result) {\n // User rejected\n return undefined;\n }\n\n // Write the connected account into the *target network*'s slot rather\n // than the legacy global, and promote that network to active so that\n // back-compat callers (`near.accountId()` without args, etc.) resolve\n // to the just-connected session.\n updateAccountState({ accountId: result.accountId }, targetNetwork);\n setActiveNetwork(targetNetwork);\n return result;\n};\n\nexport const view = async ({\n contractId,\n methodName,\n args,\n argsBase64,\n blockId,\n useArchival,\n network,\n }: {\n contractId: string;\n methodName: string;\n args?: any;\n argsBase64?: string;\n blockId?: string;\n useArchival?: boolean;\n network?: FastNearNetworkId;\n}) => {\n const encodedArgs = argsBase64 || (args ? toBase64(JSON.stringify(args)) : \"\");\n const queryResult = await sendRpc(\n \"query\",\n withBlockId(\n {\n request_type: \"call_function\",\n account_id: contractId,\n method_name: methodName,\n args_base64: encodedArgs,\n },\n blockId\n ),\n { useArchival, network },\n );\n\n return parseJsonFromBytes(queryResult.result.result);\n};\n\nexport const queryAccount = async ({\n accountId,\n blockId,\n useArchival,\n network,\n }: {\n accountId: string;\n blockId?: string;\n useArchival?: boolean;\n network?: FastNearNetworkId;\n}): Promise<FastNearRpcQueryAccountResponse> => {\n return sendRpc(\n \"query\",\n withBlockId({ request_type: \"view_account\", account_id: accountId }, blockId),\n { useArchival, network },\n );\n};\n\nexport const queryBlock = async ({ blockId, useArchival, network }: { blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }): Promise<BlockView> => {\n return sendRpc(\"block\", withBlockId({}, blockId), { useArchival, network });\n};\n\nexport const queryAccessKey = async ({\n accountId,\n publicKey,\n blockId,\n useArchival,\n network,\n }: {\n accountId: string;\n publicKey: string;\n blockId?: string;\n useArchival?: boolean;\n network?: FastNearNetworkId;\n}): Promise<AccessKeyWithError> => {\n return sendRpc(\n \"query\",\n withBlockId(\n { request_type: \"view_access_key\", account_id: accountId, public_key: publicKey },\n blockId\n ),\n { useArchival, network },\n );\n};\n\nexport const queryTx = async ({ txHash, accountId, useArchival, network }: { txHash: string; accountId: string; useArchival?: boolean; network?: FastNearNetworkId }) => {\n return sendRpc(\"tx\", [txHash, accountId], { useArchival, network });\n};\n\nfunction shouldPrintInteractiveSeparator(): boolean {\n if (typeof process === \"undefined\") {\n return false;\n }\n\n return Boolean(process.stdout?.isTTY);\n}\n\nexport function print(value: unknown): void {\n if (shouldPrintInteractiveSeparator()) {\n console.log(\"\");\n }\n\n if (\n value === null ||\n value === undefined ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n typeof value === \"bigint\"\n ) {\n console.log(value);\n return;\n }\n\n try {\n console.log(JSON.stringify(value, null, 2));\n } catch {\n console.log(value);\n }\n}\n\nfunction requiredParam<T>(value: T | undefined | null, name: string): T {\n if (value === undefined || value === null || value === \"\") {\n throw new Error(`fastnear: missing required parameter \"${name}\"`);\n }\n return value;\n}\n\nfunction encodePathParam(value: string | number, name: string): string {\n return encodeURIComponent(String(requiredParam(value, name)));\n}\n\nfunction withAliases(\n params: Record<string, any>,\n aliases: Record<string, string>\n): Record<string, any> {\n const next = { ...params };\n for (const [from, to] of Object.entries(aliases)) {\n if (next[from] !== undefined && next[to] === undefined) {\n next[to] = next[from];\n }\n delete next[from];\n }\n return next;\n}\n\nexport const tx = {\n transactions: ({ txHashes, network, ...filters }: { txHashes: string[]; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearTxTransactionsResponse>({\n family: \"tx\",\n path: \"/v0/transactions\",\n method: \"POST\",\n network,\n body: omitUndefinedEntries({\n ...filters,\n tx_hashes: requiredParam(txHashes, \"txHashes\"),\n }),\n }),\n\n receipt: ({ receiptId, network, ...filters }: { receiptId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearTxReceiptResponse>({\n family: \"tx\",\n path: \"/v0/receipt\",\n method: \"POST\",\n network,\n body: omitUndefinedEntries({\n ...filters,\n receipt_id: requiredParam(receiptId, \"receiptId\"),\n }),\n }),\n\n account: ({ accountId, network, ...filters }: { accountId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearTxAccountResponse>({\n family: \"tx\",\n path: \"/v0/account\",\n method: \"POST\",\n network,\n body: omitUndefinedEntries({\n ...filters,\n account_id: requiredParam(accountId, \"accountId\"),\n }),\n }),\n\n block: ({ network, ...params }: { network?: FastNearNetworkId; [key: string]: any } = {}) =>\n sendServiceRequest<FastNearTxBlockResponse>({\n family: \"tx\",\n path: \"/v0/block\",\n method: \"POST\",\n network,\n body: omitUndefinedEntries(params),\n }),\n\n blocks: ({ network, ...params }: { network?: FastNearNetworkId; [key: string]: any } = {}) =>\n sendServiceRequest<FastNearTxBlocksResponse>({\n family: \"tx\",\n path: \"/v0/blocks\",\n method: \"POST\",\n network,\n body: omitUndefinedEntries(params),\n }),\n};\n\nexport const api = {\n v1: {\n accountFull: ({ accountId, network, ...query }: { accountId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearApiV1AccountFullResponse>({\n family: \"api\",\n path: `/v1/account/${encodePathParam(accountId, \"accountId\")}/full`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n accountFt: ({ accountId, network, ...query }: { accountId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearApiV1AccountFtResponse>({\n family: \"api\",\n path: `/v1/account/${encodePathParam(accountId, \"accountId\")}/ft`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n accountNft: ({ accountId, network, ...query }: { accountId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearApiV1AccountNftResponse>({\n family: \"api\",\n path: `/v1/account/${encodePathParam(accountId, \"accountId\")}/nft`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n accountStaking: ({ accountId, network, ...query }: { accountId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearApiV1AccountStakingResponse>({\n family: \"api\",\n path: `/v1/account/${encodePathParam(accountId, \"accountId\")}/staking`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n publicKey: ({ publicKey, network, ...query }: { publicKey: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearApiV1PublicKeyResponse>({\n family: \"api\",\n path: `/v1/public_key/${encodePathParam(publicKey, \"publicKey\")}`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n publicKeyAll: ({ publicKey, network, ...query }: { publicKey: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearApiV1PublicKeyAllResponse>({\n family: \"api\",\n path: `/v1/public_key/${encodePathParam(publicKey, \"publicKey\")}/all`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n ftTop: ({ tokenId, network, ...query }: { tokenId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearApiV1FtTopResponse>({\n family: \"api\",\n path: `/v1/ft/${encodePathParam(tokenId, \"tokenId\")}/top`,\n network,\n query: omitUndefinedEntries(query),\n }),\n },\n};\n\nexport const transfers = {\n query: ({ network, ...params }: { network?: FastNearNetworkId; [key: string]: any } = {}) =>\n sendServiceRequest<FastNearTransfersQueryResponse>({\n family: \"transfers\",\n path: \"/v0/transfers\",\n method: \"POST\",\n network,\n body: omitUndefinedEntries(withAliases(params, { accountId: \"account_id\", resumeToken: \"resume_token\" })),\n }),\n};\n\n// NEP-141 (fungible token) view-call shorthand.\n// Each helper is a one-line wrapper around `view` with the standard\n// method name pre-filled — agents prompt with the spec's verb instead\n// of pasting the underlying methodName recipe each time.\nexport const ft = {\n balance: ({ contractId, accountId, blockId, useArchival, network }: { contractId: string; accountId: string; blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }) =>\n view({ contractId, methodName: \"ft_balance_of\", args: { account_id: accountId }, blockId, useArchival, network }),\n\n metadata: ({ contractId, blockId, useArchival, network }: { contractId: string; blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }) =>\n view({ contractId, methodName: \"ft_metadata\", args: {}, blockId, useArchival, network }),\n\n totalSupply: ({ contractId, blockId, useArchival, network }: { contractId: string; blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }) =>\n view({ contractId, methodName: \"ft_total_supply\", args: {}, blockId, useArchival, network }),\n\n // NEP-145 storage-management read; many wallet/dApp flows need to know\n // whether an account is registered with the FT contract before transferring.\n storageBalance: ({ contractId, accountId, blockId, useArchival, network }: { contractId: string; accountId: string; blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }) =>\n view({ contractId, methodName: \"storage_balance_of\", args: { account_id: accountId }, blockId, useArchival, network }),\n\n // Cross-contract: list every FT contract this account holds, served by\n // the FastNear indexer. Layer-mixed but the natural name from the\n // agent's perspective.\n inventory: ({ accountId, network, ...query }: { accountId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearApiV1AccountFtResponse>({\n family: \"api\",\n path: `/v1/account/${encodePathParam(accountId, \"accountId\")}/ft`,\n network,\n query: omitUndefinedEntries(query),\n }),\n};\n\n// NEP-171 (non-fungible token) view-call shorthand. Same pattern as `ft`.\nexport const nft = {\n metadata: ({ contractId, blockId, useArchival, network }: { contractId: string; blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }) =>\n view({ contractId, methodName: \"nft_metadata\", args: {}, blockId, useArchival, network }),\n\n token: ({ contractId, tokenId, blockId, useArchival, network }: { contractId: string; tokenId: string; blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }) =>\n view({ contractId, methodName: \"nft_token\", args: { token_id: tokenId }, blockId, useArchival, network }),\n\n forOwner: ({ contractId, accountId, fromIndex, limit, blockId, useArchival, network }: { contractId: string; accountId: string; fromIndex?: string; limit?: number; blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }) =>\n view({\n contractId,\n methodName: \"nft_tokens_for_owner\",\n args: omitUndefinedEntries({ account_id: accountId, from_index: fromIndex, limit }),\n blockId,\n useArchival,\n network,\n }),\n\n supplyForOwner: ({ contractId, accountId, blockId, useArchival, network }: { contractId: string; accountId: string; blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }) =>\n view({ contractId, methodName: \"nft_supply_for_owner\", args: { account_id: accountId }, blockId, useArchival, network }),\n\n totalSupply: ({ contractId, blockId, useArchival, network }: { contractId: string; blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }) =>\n view({ contractId, methodName: \"nft_total_supply\", args: {}, blockId, useArchival, network }),\n\n tokens: ({ contractId, fromIndex, limit, blockId, useArchival, network }: { contractId: string; fromIndex?: string; limit?: number; blockId?: string; useArchival?: boolean; network?: FastNearNetworkId }) =>\n view({\n contractId,\n methodName: \"nft_tokens\",\n args: omitUndefinedEntries({ from_index: fromIndex, limit }),\n blockId,\n useArchival,\n network,\n }),\n\n // List every NFT contract this account holds, served by the FastNear indexer.\n inventory: ({ accountId, network, ...query }: { accountId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearApiV1AccountNftResponse>({\n family: \"api\",\n path: `/v1/account/${encodePathParam(accountId, \"accountId\")}/nft`,\n network,\n query: omitUndefinedEntries(query),\n }),\n};\n\nexport const neardata = {\n lastBlockFinal: ({ network, ...query }: { network?: FastNearNetworkId; [key: string]: any } = {}) =>\n sendServiceRequest<FastNearNeardataLastBlockFinalResponse>({\n family: \"neardata\",\n path: \"/v0/last_block/final\",\n network,\n query: omitUndefinedEntries(query),\n }),\n\n lastBlockOptimistic: ({ network, ...query }: { network?: FastNearNetworkId; [key: string]: any } = {}) =>\n sendServiceRequest<FastNearNeardataLastBlockOptimisticResponse>({\n family: \"neardata\",\n path: \"/v0/last_block/optimistic\",\n network,\n query: omitUndefinedEntries(query),\n }),\n\n block: ({ blockHeight, network, ...query }: { blockHeight: string | number; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearNeardataBlockResponse>({\n family: \"neardata\",\n path: `/v0/block/${encodePathParam(blockHeight, \"blockHeight\")}`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n blockHeaders: ({ blockHeight, network, ...query }: { blockHeight: string | number; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearNeardataBlockHeadersResponse>({\n family: \"neardata\",\n path: `/v0/block/${encodePathParam(blockHeight, \"blockHeight\")}/headers`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n blockShard: ({ blockHeight, shardId, network, ...query }: { blockHeight: string | number; shardId: string | number; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearNeardataBlockShardResponse>({\n family: \"neardata\",\n path: `/v0/block/${encodePathParam(blockHeight, \"blockHeight\")}/shard/${encodePathParam(shardId, \"shardId\")}`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n blockChunk: ({ blockHeight, shardId, network, ...query }: { blockHeight: string | number; shardId: string | number; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearNeardataBlockChunkResponse>({\n family: \"neardata\",\n path: `/v0/block/${encodePathParam(blockHeight, \"blockHeight\")}/chunk/${encodePathParam(shardId, \"shardId\")}`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n blockOptimistic: ({ blockHeight, network, ...query }: { blockHeight: string | number; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearNeardataBlockOptimisticResponse>({\n family: \"neardata\",\n path: `/v0/block_opt/${encodePathParam(blockHeight, \"blockHeight\")}`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n firstBlock: ({ network, ...query }: { network?: FastNearNetworkId; [key: string]: any } = {}) =>\n sendServiceRequest<FastNearNeardataFirstBlockResponse>({\n family: \"neardata\",\n path: \"/v0/first_block\",\n network,\n query: omitUndefinedEntries(query),\n }),\n\n health: ({ network, ...query }: { network?: FastNearNetworkId; [key: string]: any } = {}) =>\n sendServiceRequest<FastNearNeardataHealthResponse>({\n family: \"neardata\",\n path: \"/health\",\n network,\n query: omitUndefinedEntries(query),\n }),\n};\n\nexport const fastdata = {\n kv: {\n getLatestKey: ({ currentAccountId, predecessorId, key, network, ...query }: { currentAccountId: string; predecessorId: string; key: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearKvGetLatestKeyResponse>({\n family: \"fastdata.kv\",\n path: `/v0/latest/${encodePathParam(currentAccountId, \"currentAccountId\")}/${encodePathParam(predecessorId, \"predecessorId\")}/${encodePathParam(key, \"key\")}`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n getHistoryKey: ({ currentAccountId, predecessorId, key, network, ...query }: { currentAccountId: string; predecessorId: string; key: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearKvGetHistoryKeyResponse>({\n family: \"fastdata.kv\",\n path: `/v0/history/${encodePathParam(currentAccountId, \"currentAccountId\")}/${encodePathParam(predecessorId, \"predecessorId\")}/${encodePathParam(key, \"key\")}`,\n network,\n query: omitUndefinedEntries(query),\n }),\n\n latestByAccount: ({ accountId, network, ...body }: { accountId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearKvLatestByAccountResponse>({\n family: \"fastdata.kv\",\n path: `/v0/latest/${encodePathParam(accountId, \"accountId\")}`,\n method: \"POST\",\n network,\n body: omitUndefinedEntries(body),\n }),\n\n historyByAccount: ({ accountId, network, ...body }: { accountId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearKvHistoryByAccountResponse>({\n family: \"fastdata.kv\",\n path: `/v0/history/${encodePathParam(accountId, \"accountId\")}`,\n method: \"POST\",\n network,\n body: omitUndefinedEntries(body),\n }),\n\n latestByPredecessor: ({ currentAccountId, predecessorId, network, ...body }: { currentAccountId: string; predecessorId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearKvLatestByPredecessorResponse>({\n family: \"fastdata.kv\",\n path: `/v0/latest/${encodePathParam(currentAccountId, \"currentAccountId\")}/${encodePathParam(predecessorId, \"predecessorId\")}`,\n method: \"POST\",\n network,\n body: omitUndefinedEntries(body),\n }),\n\n historyByPredecessor: ({ currentAccountId, predecessorId, network, ...body }: { currentAccountId: string; predecessorId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearKvHistoryByPredecessorResponse>({\n family: \"fastdata.kv\",\n path: `/v0/history/${encodePathParam(currentAccountId, \"currentAccountId\")}/${encodePathParam(predecessorId, \"predecessorId\")}`,\n method: \"POST\",\n network,\n body: omitUndefinedEntries(body),\n }),\n\n allByPredecessor: ({ predecessorId, network, ...body }: { predecessorId: string; network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearKvAllByPredecessorResponse>({\n family: \"fastdata.kv\",\n path: `/v0/all/${encodePathParam(predecessorId, \"predecessorId\")}`,\n method: \"POST\",\n network,\n body: omitUndefinedEntries(body),\n }),\n\n multi: ({ network, ...body }: { network?: FastNearNetworkId; [key: string]: any }) =>\n sendServiceRequest<FastNearKvMultiResponse>({\n family: \"fastdata.kv\",\n path: \"/v0/multi\",\n method: \"POST\",\n network,\n body: omitUndefinedEntries(body),\n }),\n },\n};\n\nexport const localTxHistory = () => {\n return getTxHistory();\n};\n\nexport const signOut = async ({\n network,\n}: { network?: FastNearNetworkId } = {}) => {\n const provider = getWalletProvider();\n const targetNetwork = network ?? getConfig().networkId;\n\n if (provider?.isConnected({ network: targetNetwork })) {\n await provider.disconnect({ network: targetNetwork });\n }\n\n // Per-network state means we always clear the target network's slot\n // — parallel sessions on other networks survive untouched. (Pre-1.1.1\n // state was global; this used to be guarded so a non-active sign-out\n // wouldn't clobber the active session. The per-network split moots\n // that guard.)\n updateAccountState(\n {\n accountId: null,\n privateKey: null,\n accessKeyContractId: null,\n lastWalletId: null,\n },\n targetNetwork,\n );\n\n // Implicit `signOut()` (no arg) preserves the legacy reset-to-default\n // shape: callers using single-session signOut expect the active config\n // to flip back to mainnet defaults afterwards.\n if (network === undefined) {\n setConfig(NETWORKS[DEFAULT_NETWORK_ID]);\n setActiveNetwork(DEFAULT_NETWORK_ID);\n }\n};\n\nexport const sendTx = async ({\n receiverId,\n actions,\n waitUntil,\n network,\n }: {\n receiverId: string;\n actions: any[];\n waitUntil?: string;\n network?: FastNearNetworkId;\n}) => {\n const targetNetwork = network ?? getConfig().networkId;\n const slot = getAccountState(targetNetwork);\n const signerId = slot.accountId;\n if (!signerId) throw new Error(\"Must sign in\");\n\n const pubKey = slot.publicKey ?? \"\";\n const privKey = slot.privateKey;\n const txId = generateTxId();\n\n // If no local private key, or the receiver doesn't match the access key contract,\n // or the actions aren't signable with a limited access key, delegate to the wallet\n if (!privKey || receiverId !== slot.accessKeyContractId || !canSignWithLAK(actions)) {\n const jsonTx = { signerId, receiverId, actions };\n updateTxHistory({ status: \"Pending\", txId, tx: jsonTx, finalState: false });\n\n try {\n const provider = getWalletProvider();\n if (!provider?.isConnected({ network: targetNetwork })) {\n throw new Error(\"Must sign in\");\n }\n\n const result = await provider.sendTransaction({ ...jsonTx, network: targetNetwork });\n\n if (!result) {\n // User rejected\n updateTxHistory({ txId, status: \"RejectedByUser\", finalState: true });\n return { rejected: true };\n }\n\n if (result.outcomes?.length) {\n result.outcomes.forEach((r: any) =>\n updateTxHistory({\n txId,\n status: \"Executed\",\n result: r,\n txHash: r.transaction?.hash,\n finalState: true,\n })\n );\n }\n\n return result;\n } catch (err) {\n console.error('fastnear: error sending tx using wallet provider:', err)\n updateTxHistory({\n txId,\n status: \"Error\",\n error: tryParseJson((err as Error).message),\n finalState: true,\n });\n\n return Promise.reject(err);\n }\n }\n\n // Local signing path (limited access key). The RPC helpers and the\n // `nonce`/`block` caches are now per-network too, so a sendTx on a\n // non-active network signs against that network's RPC and caches the\n // nonce/block under the right key.\n const nonceKey = `nonce.${targetNetwork}`;\n const blockKey = `block.${targetNetwork}`;\n\n let nonce = lsGet(nonceKey) as number | null;\n if (nonce == null) {\n const accessKey = await queryAccessKey({ accountId: signerId, publicKey: pubKey, network: targetNetwork });\n if (accessKey.result.error) {\n throw new Error(`Access key error: ${accessKey.result.error} when attempting to get nonce for ${signerId} for public key ${pubKey}`);\n }\n nonce = accessKey.result.nonce;\n lsSet(nonceKey, nonce);\n }\n\n let lastKnownBlock = lsGet(blockKey) as LastKnownBlock | null;\n if (\n !lastKnownBlock ||\n parseFloat(lastKnownBlock.header.timestamp_nanosec) / 1e6 + MaxBlockDelayMs < Date.now()\n ) {\n const latestBlock = await queryBlock({ blockId: \"final\", network: targetNetwork });\n lastKnownBlock = {\n header: {\n hash: latestBlock.result.header.hash,\n timestamp_nanosec: latestBlock.result.header.timestamp_nanosec,\n },\n };\n lsSet(blockKey, lastKnownBlock);\n }\n\n nonce += 1;\n lsSet(nonceKey, nonce);\n\n const blockHash = lastKnownBlock.header.hash;\n\n const plainTransactionObj: PlainTransaction = {\n signerId,\n publicKey: pubKey,\n nonce,\n receiverId,\n blockHash,\n actions,\n };\n\n const txBytes = serializeTransaction(plainTransactionObj);\n const txHashBytes = sha256(txBytes);\n const txHash58 = toBase58(txHashBytes);\n\n const signatureBase58 = signHash(txHashBytes, privKey, { returnBase58: true }) as string;\n const signedTransactionBytes = serializeSignedTransaction(plainTransactionObj, signatureBase58);\n const signedTxBase64 = bytesToBase64(signedTransactionBytes);\n\n updateTxHistory({\n status: \"Pending\",\n txId,\n tx: plainTransactionObj,\n signature: signatureBase58,\n signedTxBase64,\n txHash: txHash58,\n finalState: false,\n });\n\n return await sendTxToRpc(signedTxBase64, waitUntil, txId, targetNetwork);\n};\n\n/**\n * Signs a NEP-413 message using the connected wallet. Pass an explicit\n * `{ network }` to route the signature through that network's wallet\n * session — useful when a page holds parallel mainnet+testnet sessions.\n * Without it, the active network's session is used.\n */\nexport const signMessage = async (\n message: NEP413Message,\n options: { network?: FastNearNetworkId } = {},\n) => {\n const provider = getWalletProvider();\n const targetNetwork = options.network ?? getConfig().networkId;\n if (!provider?.isConnected({ network: targetNetwork })) {\n throw new Error(\"Must sign in\");\n }\n if (!provider.signMessage) {\n throw new Error(\"Connected wallet does not support signMessage\");\n }\n return provider.signMessage({ ...message, network: targetNetwork });\n};\n\n/**\n * Set the wallet provider used by the API for signing and sending transactions.\n * Automatically called in IIFE builds when globalThis.nearWallet is present.\n */\nexport const useWallet = (provider: WalletProvider): void => {\n setWalletProvider(provider);\n};\n\n// exports\nexport const utils = reExportAllUtils;\n\nexport const event = stateExports.events;\n\n// `_state` is a live binding in state.ts (reassigned on\n// setActiveNetwork / updateAccountState). The literal-object form of\n// this namespace would freeze a value-copy at module load and go stale\n// as soon as the active network changed, so we expose `_state` as a\n// getter that always resolves to the current active slot.\nexport const state = (() => {\n const ns: Record<string, any> = {\n DEFAULT_NETWORK_ID: stateExports.DEFAULT_NETWORK_ID,\n NETWORKS: stateExports.NETWORKS,\n _config: stateExports._config,\n _txHistory: stateExports._txHistory,\n _unbroadcastedEvents: stateExports._unbroadcastedEvents,\n setWalletProvider: stateExports.setWalletProvider,\n getWalletProvider: stateExports.getWalletProvider,\n update: stateExports.update,\n updateAccountState: stateExports.updateAccountState,\n getAccountState: stateExports.getAccountState,\n getActiveNetwork: stateExports.getActiveNetwork,\n setActiveNetwork: stateExports.setActiveNetwork,\n updateTxHistory: stateExports.updateTxHistory,\n getConfig: stateExports.getConfig,\n getTxHistory: stateExports.getTxHistory,\n setConfig: stateExports.setConfig,\n resetTxHistory: stateExports.resetTxHistory,\n };\n Object.defineProperty(ns, \"_state\", {\n get: () => stateExports._state,\n enumerable: true,\n });\n return ns;\n})();\n\nexport const exp = {\n utils,\n borsh: reExportAllUtils.exp.borsh,\n borshSchema: reExportAllUtils.exp.borshSchema.getBorshSchema(),\n};\n\n// action helpers\nexport const actions = {\n functionCall: ({\n methodName,\n gas,\n deposit,\n args,\n argsBase64,\n }: {\n methodName: string;\n gas?: string;\n deposit?: string;\n args?: Record<string, any>;\n argsBase64?: string;\n }) => ({\n type: \"FunctionCall\",\n methodName,\n args,\n argsBase64,\n gas,\n deposit,\n }),\n\n transfer: (yoctoAmount: string) => ({\n type: \"Transfer\",\n deposit: yoctoAmount,\n }),\n\n stakeNEAR: ({amount, publicKey}: { amount: string; publicKey: string }) => ({\n type: \"Stake\",\n stake: amount,\n publicKey,\n }),\n\n addFullAccessKey: ({publicKey}: { publicKey: string }) => ({\n type: \"AddKey\",\n publicKey: publicKey,\n accessKey: {permission: \"FullAccess\"},\n }),\n\n addLimitedAccessKey: ({\n publicKey,\n allowance,\n accountId,\n methodNames,\n }: {\n publicKey: string;\n allowance: string;\n accountId: string;\n methodNames: string[];\n }) => ({\n type: \"AddKey\",\n publicKey: publicKey,\n accessKey: {\n permission: \"FunctionCall\",\n allowance,\n receiverId: accountId,\n methodNames,\n },\n }),\n\n deleteKey: ({publicKey}: { publicKey: string }) => ({\n type: \"DeleteKey\",\n publicKey,\n }),\n\n deleteAccount: ({beneficiaryId}: { beneficiaryId: string }) => ({\n type: \"DeleteAccount\",\n beneficiaryId,\n }),\n\n createAccount: () => ({\n type: \"CreateAccount\",\n }),\n\n deployContract: ({codeBase64}: { codeBase64: string }) => ({\n type: \"DeployContract\",\n codeBase64,\n }),\n};\n\nexport const explain = {\n action: (action: any): ExplainedAction => {\n const t