UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

765 lines 34.4 kB
import { Field } from '../../provable/wrapped.js'; import { UInt32, UInt64 } from '../../provable/int.js'; import { Actions, TokenId } from './account-update.js'; import { PublicKey, PrivateKey } from '../../provable/crypto/signature.js'; import { LedgerHash, EpochSeed, StateHash } from './base58-encodings.js'; import { fillPartialAccount, parseFetchedAccount } from './account.js'; import { sendZkappQuery, lastBlockQuery, lastBlockQueryFailureCheck, transactionStatusQuery, getEventsQuery, getActionsQuery, genesisConstantsQuery, accountQuery, currentSlotQuery, } from './graphql.js'; export { fetchAccount, fetchLastBlock, fetchGenesisConstants, fetchCurrentSlot, checkZkappTransaction, parseFetchedAccount, markAccountToBeFetched, markNetworkToBeFetched, markActionsToBeFetched, fetchMissingData, fetchTransactionStatus, getCachedAccount, getCachedNetwork, getCachedActions, getCachedGenesisConstants, addCachedAccount, networkConfig, setMinaDefaultHeaders, setArchiveDefaultHeaders, setGraphqlEndpoint, setGraphqlEndpoints, setMinaGraphqlFallbackEndpoints, setArchiveGraphqlEndpoint, setArchiveGraphqlFallbackEndpoints, setLightnetAccountManagerEndpoint, sendZkapp, fetchEvents, fetchActions, makeGraphqlRequest, Lightnet, }; let networkConfig = { minaEndpoint: '', minaFallbackEndpoints: [], archiveEndpoint: '', archiveFallbackEndpoints: [], lightnetAccountManagerEndpoint: '', minaDefaultHeaders: {}, archiveDefaultHeaders: {}, }; function checkForValidUrl(url) { try { new URL(url); return true; } catch { return false; } } /** * Sets up the default headers to be used for all Mina node GraphQL requests, example usage: * ```typescript * setMinaDefaultHeaders({ Authorization: 'Bearer example-token' }); * ``` * * It can be overridden by passing headers to the individual fetch functions, example usage: * ```typescript * setMinaDefaultHeaders({ Authorization: 'Bearer default-token' }); * await fetchAccount({publicKey}, minaEndpoint, { headers: { Authorization: 'Bearer override-token' } }); * ``` * @param headers Arbitrary sized headers to be used for all Mina node GraphQL requests. */ function setMinaDefaultHeaders(headers) { networkConfig.minaDefaultHeaders = headers; } /** * Sets up the default headers to be used for all Archive node GraphQL requests, example usage: * ```typescript * setArchiveDefaultHeaders({ Authorization: 'Bearer example-token' }); * ``` * * It can be overridden by passing headers to the individual fetch functions, example usage: * ```typescript * setArchiveDefaultHeaders({ Authorization: 'Bearer default-token' }); * await fetchEvents({publicKey}, archiveEndpoint, { headers: { Authorization: 'Bearer override-token' } }); * ``` * @param headers Arbitrary sized headers to be used for all Mina Archive node GraphQL requests. */ function setArchiveDefaultHeaders(headers) { networkConfig.archiveDefaultHeaders = headers; } function setGraphqlEndpoints([graphqlEndpoint, ...fallbackEndpoints]) { setGraphqlEndpoint(graphqlEndpoint); setMinaGraphqlFallbackEndpoints(fallbackEndpoints); } function setGraphqlEndpoint(graphqlEndpoint, minaDefaultHeaders) { if (!checkForValidUrl(graphqlEndpoint)) { throw new Error(`Invalid GraphQL endpoint: ${graphqlEndpoint}. Please specify a valid URL.`); } networkConfig.minaEndpoint = graphqlEndpoint; if (minaDefaultHeaders) setMinaDefaultHeaders(minaDefaultHeaders); } function setMinaGraphqlFallbackEndpoints(graphqlEndpoints) { if (graphqlEndpoints.some((endpoint) => !checkForValidUrl(endpoint))) { throw new Error(`Invalid GraphQL endpoint: ${graphqlEndpoints}. Please specify a valid URL.`); } networkConfig.minaFallbackEndpoints = graphqlEndpoints; } /** * Sets up a GraphQL endpoint to be used for fetching information from an Archive Node. * */ function setArchiveGraphqlEndpoint(graphqlEndpoint, archiveDefaultHeaders) { if (!checkForValidUrl(graphqlEndpoint)) { throw new Error(`Invalid GraphQL endpoint: ${graphqlEndpoint}. Please specify a valid URL.`); } networkConfig.archiveEndpoint = graphqlEndpoint; if (archiveDefaultHeaders) setArchiveDefaultHeaders(archiveDefaultHeaders); } function setArchiveGraphqlFallbackEndpoints(graphqlEndpoints) { if (graphqlEndpoints.some((endpoint) => !checkForValidUrl(endpoint))) { throw new Error(`Invalid GraphQL endpoint: ${graphqlEndpoints}. Please specify a valid URL.`); } networkConfig.archiveFallbackEndpoints = graphqlEndpoints; } /** * Sets up the lightnet account manager endpoint to be used for accounts acquisition and releasing. * * @param endpoint Account manager endpoint. */ function setLightnetAccountManagerEndpoint(endpoint) { if (!checkForValidUrl(endpoint)) { throw new Error(`Invalid account manager endpoint: ${endpoint}. Please specify a valid URL.`); } networkConfig.lightnetAccountManagerEndpoint = endpoint; } /** * Gets account information on the specified publicKey by performing a GraphQL query * to the specified endpoint. This will call the 'GetAccountInfo' query which fetches * zkapp related account information. * * If an error is returned by the specified endpoint, an error is thrown. Otherwise, * the data is returned. * * @param accountInfo The public key and token id of the account to fetch * @param accountInfo.publicKey The specified publicKey to get account information on * @param accountInfo.tokenId The specified tokenId to get account information on * @param graphqlEndpoint The graphql endpoint to fetch from * @param config An object that exposes an additional timeout and header options * @returns zkapp information on the specified account or an error is thrown */ async function fetchAccount(accountInfo, graphqlEndpoint = networkConfig.minaEndpoint, { timeout = defaultTimeout, headers } = {}) { let publicKeyBase58 = accountInfo.publicKey instanceof PublicKey ? accountInfo.publicKey.toBase58() : accountInfo.publicKey; let tokenIdBase58 = typeof accountInfo.tokenId === 'string' || !accountInfo.tokenId ? accountInfo.tokenId : TokenId.toBase58(accountInfo.tokenId); return await fetchAccountInternal({ publicKey: publicKeyBase58, tokenId: tokenIdBase58 }, graphqlEndpoint, { timeout, headers: { ...networkConfig.minaDefaultHeaders, ...headers }, }); } // internal version of fetchAccount which does the same, but returns the original JSON version // of the account, to save some back-and-forth conversions when caching accounts async function fetchAccountInternal(accountInfo, graphqlEndpoint = networkConfig.minaEndpoint, config) { const { publicKey, tokenId } = accountInfo; let [response, error] = await makeGraphqlRequest(accountQuery(publicKey, tokenId ?? TokenId.toBase58(TokenId.default)), graphqlEndpoint, networkConfig.minaFallbackEndpoints, config); if (error !== undefined) return { account: undefined, error }; let fetchedAccount = response?.data?.account; if (!fetchedAccount) { return { account: undefined, error: { statusCode: 404, statusText: `fetchAccount: Account with public key ${publicKey} does not exist.`, }, }; } let account = parseFetchedAccount(fetchedAccount); // account successfully fetched - add to cache before returning addCachedAccountInternal(account, graphqlEndpoint); return { account, error: undefined, }; } // Specify 5min as the default timeout const defaultTimeout = 5 * 60 * 1000; let accountCache = {}; let networkCache = {}; let actionsCache = {}; let accountsToFetch = {}; let networksToFetch = {}; let actionsToFetch = {}; let genesisConstantsCache = {}; const emptyKey = PublicKey.empty().toBase58(); function markAccountToBeFetched(publicKey, tokenId, graphqlEndpoint) { let publicKeyBase58 = publicKey.toBase58(); if (publicKeyBase58 === emptyKey) return; let tokenBase58 = TokenId.toBase58(tokenId); accountsToFetch[`${publicKeyBase58};${tokenBase58};${graphqlEndpoint}`] = { publicKey: publicKeyBase58, tokenId: tokenBase58, graphqlEndpoint, }; } function markNetworkToBeFetched(graphqlEndpoint) { networksToFetch[graphqlEndpoint] = { graphqlEndpoint }; } function markActionsToBeFetched(publicKey, tokenId, graphqlEndpoint, actionStates = {}) { let publicKeyBase58 = publicKey.toBase58(); let tokenBase58 = TokenId.toBase58(tokenId); let { fromActionState, endActionState } = actionStates; let fromActionStateBase58 = fromActionState ? fromActionState.toString() : undefined; let endActionStateBase58 = endActionState ? endActionState.toString() : undefined; actionsToFetch[`${publicKeyBase58};${tokenBase58};${graphqlEndpoint}`] = { publicKey: publicKeyBase58, tokenId: tokenBase58, actionStates: { fromActionState: fromActionStateBase58, endActionState: endActionStateBase58, }, graphqlEndpoint, }; } async function fetchMissingData(graphqlEndpoint, archiveEndpoint) { let promises = Object.entries(accountsToFetch).map(async ([key, { publicKey, tokenId }]) => { let response = await fetchAccountInternal({ publicKey, tokenId }, graphqlEndpoint); if (response.error === undefined) delete accountsToFetch[key]; }); let actionPromises = Object.entries(actionsToFetch).map(async ([key, { publicKey, actionStates, tokenId }]) => { let response = await fetchActions({ publicKey, actionStates, tokenId }, archiveEndpoint); if (!('error' in response) || response.error === undefined) delete actionsToFetch[key]; }); promises.push(...actionPromises); let network = Object.entries(networksToFetch).find(([, network]) => { return network.graphqlEndpoint === graphqlEndpoint; }); if (network !== undefined) { promises.push((async () => { const [lastBlockOk, constantsOk] = await Promise.all([ fetchLastBlock(graphqlEndpoint) .then(() => true) .catch(() => false), fetchGenesisConstants(graphqlEndpoint) .then(() => true) .catch(() => false), ]); if (lastBlockOk && constantsOk) { delete networksToFetch[network[0]]; } })()); } await Promise.all(promises); } function getCachedAccount(publicKey, tokenId, graphqlEndpoint = networkConfig.minaEndpoint) { return accountCache[accountCacheKey(publicKey, tokenId, graphqlEndpoint)]?.account; } function getCachedNetwork(graphqlEndpoint = networkConfig.minaEndpoint) { return networkCache[graphqlEndpoint]?.network; } function getCachedActions(publicKey, tokenId, graphqlEndpoint = networkConfig.archiveEndpoint) { return actionsCache[accountCacheKey(publicKey, tokenId, graphqlEndpoint)]?.actions; } function getCachedGenesisConstants(graphqlEndpoint = networkConfig.minaEndpoint) { return genesisConstantsCache[graphqlEndpoint]; } /** * Adds an account to the local cache, indexed by a GraphQL endpoint. */ function addCachedAccount(partialAccount, graphqlEndpoint = networkConfig.minaEndpoint) { let account = fillPartialAccount(partialAccount); addCachedAccountInternal(account, graphqlEndpoint); } function addCachedAccountInternal(account, graphqlEndpoint) { accountCache[accountCacheKey(account.publicKey, account.tokenId, graphqlEndpoint)] = { account, graphqlEndpoint, timestamp: Date.now(), }; } function addCachedActions({ publicKey, tokenId }, actions, graphqlEndpoint) { actionsCache[`${publicKey};${tokenId};${graphqlEndpoint}`] = { actions, graphqlEndpoint, timestamp: Date.now(), }; } function accountCacheKey(publicKey, tokenId, graphqlEndpoint) { return `${publicKey.toBase58()};${TokenId.toBase58(tokenId)};${graphqlEndpoint}`; } /** * Fetches the last block on the Mina network. */ async function fetchLastBlock(graphqlEndpoint = networkConfig.minaEndpoint, headers) { let [resp, error] = await makeGraphqlRequest(lastBlockQuery, graphqlEndpoint, networkConfig.minaFallbackEndpoints, { headers: { ...networkConfig.minaDefaultHeaders, ...headers } }); if (error) throw Error(error.statusText); let lastBlock = resp?.data?.bestChain?.[0]; if (lastBlock === undefined) { throw Error('Failed to fetch latest network state.'); } let network = parseFetchedBlock(lastBlock); networkCache[graphqlEndpoint] = { network, graphqlEndpoint, timestamp: Date.now(), }; return network; } /** * Fetches the current slot number of the Mina network. * @param graphqlEndpoint GraphQL endpoint to fetch from * @param headers optional headers to pass to the fetch request * @returns The current slot number */ async function fetchCurrentSlot(graphqlEndpoint = networkConfig.minaEndpoint, headers) { let [resp, error] = await makeGraphqlRequest(currentSlotQuery, graphqlEndpoint, networkConfig.minaFallbackEndpoints, { headers: { ...networkConfig.minaDefaultHeaders, ...headers } }); if (error) throw Error(`Error making GraphQL request: ${error.statusText}`); let bestChain = resp?.data?.bestChain; if (!bestChain || bestChain.length === 0) { throw Error('Failed to fetch the current slot. The response data is undefined.'); } return bestChain[0].protocolState.consensusState.slot; } async function fetchLatestBlockZkappStatus(blockLength, graphqlEndpoint = networkConfig.minaEndpoint) { let [resp, error] = await makeGraphqlRequest(lastBlockQueryFailureCheck(blockLength), graphqlEndpoint, networkConfig.minaFallbackEndpoints, { headers: networkConfig.minaDefaultHeaders }); if (error) throw Error(`Error making GraphQL request: ${error.statusText}`); let bestChain = resp?.data; if (bestChain === undefined) { throw Error('Failed to fetch the latest zkApp transaction status. The response data is undefined.'); } return bestChain; } async function checkZkappTransaction(transactionHash, blockLength = 20) { let bestChainBlocks = await fetchLatestBlockZkappStatus(blockLength); for (let block of bestChainBlocks.bestChain) { for (let zkappCommand of block.transactions.zkappCommands) { if (zkappCommand.hash === transactionHash) { if (zkappCommand.failureReason !== null) { let failureReason = zkappCommand.failureReason.reverse().map((failure) => { return [failure.failures.map((failureItem) => failureItem)]; }); return { success: false, failureReason, }; } else { return { success: true, failureReason: null, }; } } } } return { success: false, failureReason: null, }; } function parseFetchedBlock({ protocolState: { blockchainState: { snarkedLedgerHash }, consensusState: { blockHeight, minWindowDensity, totalCurrency, slotSinceGenesis, nextEpochData, stakingEpochData, }, }, }) { return { snarkedLedgerHash: LedgerHash.fromBase58(snarkedLedgerHash), // TODO: use date or utcDate? blockchainLength: UInt32.from(blockHeight), minWindowDensity: UInt32.from(minWindowDensity), totalCurrency: UInt64.from(totalCurrency), globalSlotSinceGenesis: UInt32.from(slotSinceGenesis), nextEpochData: parseEpochData(nextEpochData), stakingEpochData: parseEpochData(stakingEpochData), }; } function parseEpochData({ ledger: { hash, totalCurrency }, seed, startCheckpoint, lockCheckpoint, epochLength, }) { return { ledger: { hash: LedgerHash.fromBase58(hash), totalCurrency: UInt64.from(totalCurrency), }, seed: EpochSeed.fromBase58(seed), startCheckpoint: StateHash.fromBase58(startCheckpoint), lockCheckpoint: StateHash.fromBase58(lockCheckpoint), epochLength: UInt32.from(epochLength), }; } /** * Fetches the status of a transaction. */ async function fetchTransactionStatus(txId, graphqlEndpoint = networkConfig.minaEndpoint, headers) { let [resp, error] = await makeGraphqlRequest(transactionStatusQuery(txId), graphqlEndpoint, networkConfig.minaFallbackEndpoints, { headers: { ...networkConfig.minaDefaultHeaders, ...headers } }); if (error) throw Error(error.statusText); let txStatus = resp?.data?.transactionStatus; if (txStatus === undefined || txStatus === null) { throw Error(`Failed to fetch transaction status. TransactionId: ${txId}`); } return txStatus; } /** * Sends a zkApp command (transaction) to the specified GraphQL endpoint. */ function sendZkapp(json, graphqlEndpoint = networkConfig.minaEndpoint, { timeout = defaultTimeout, headers } = {}) { return makeGraphqlRequest(sendZkappQuery(json), graphqlEndpoint, networkConfig.minaFallbackEndpoints, { timeout, headers: { ...networkConfig.minaDefaultHeaders, ...headers }, }); } /** * Asynchronously fetches event data for an account from the Mina Archive Node GraphQL API. * @param accountInfo - The account information object. * @param accountInfo.publicKey - The account public key. * @param [accountInfo.tokenId] - The optional token ID for the account. * @param [graphqlEndpoint=networkConfig.archiveEndpoint] - The GraphQL endpoint to query. Defaults to the Archive Node GraphQL API. * @param [filterOptions={}] - The optional filter options object. * @param headers - Optional headers to pass to the fetch request * @returns A promise that resolves to an array of objects containing event data, block information and transaction information for the account. * @throws If the GraphQL request fails or the response is invalid. * @example * ```ts * const accountInfo = { publicKey: 'B62qiwmXrWn7Cok5VhhB3KvCwyZ7NHHstFGbiU5n7m8s2RqqNW1p1wF' }; * const events = await fetchEvents(accountInfo); * console.log(events); * ``` */ async function fetchEvents(queryInputs, graphqlEndpoint = networkConfig.archiveEndpoint, headers) { if (!graphqlEndpoint) throw Error('fetchEvents: Specified GraphQL endpoint is undefined. When using events, you must set the archive node endpoint in Mina.Network(). Please ensure your Mina.Network() configuration includes an archive node endpoint.'); let [response, error] = await makeGraphqlRequest(getEventsQuery(queryInputs), graphqlEndpoint, networkConfig.archiveFallbackEndpoints, { headers: { ...networkConfig.archiveDefaultHeaders, ...headers } }); if (error) throw Error(error.statusText); let fetchedEvents = response?.data.events; if (fetchedEvents === undefined) { throw Error(`Failed to fetch events data. Account: ${queryInputs.publicKey} Token: ${queryInputs.tokenId}`); } return fetchedEvents.map((event) => { let events = event.eventData.map(({ data, transactionInfo }) => { return { data, transactionInfo, }; }); return { events, blockHeight: UInt32.from(event.blockInfo.height), blockHash: event.blockInfo.stateHash, parentBlockHash: event.blockInfo.parentHash, globalSlot: UInt32.from(event.blockInfo.globalSlotSinceGenesis), chainStatus: event.blockInfo.chainStatus, }; }); } /** * Fetches account actions for a specified public key and token ID by performing a GraphQL query. * * @param accountInfo - An {@link ActionsQueryInputs} containing the public key, and optional query parameters for the actions query * @param graphqlEndpoint - The GraphQL endpoint to fetch from. Defaults to the configured Mina endpoint. * @param headers - Optional headers to pass to the fetch request * * @returns A promise that resolves to an object containing the final actions hash for the account, and a list of actions * @throws Will throw an error if the GraphQL endpoint is invalid or if the fetch request fails. * * @example * ```ts * const accountInfo = { publicKey: 'B62qiwmXrWn7Cok5VhhB3KvCwyZ7NHHstFGbiU5n7m8s2RqqNW1p1wF' }; * const actionsList = await fetchAccount(accountInfo); * console.log(actionsList); * ``` */ async function fetchActions(queryInputs, graphqlEndpoint = networkConfig.archiveEndpoint, headers) { if (!graphqlEndpoint) throw Error('fetchActions: Specified GraphQL endpoint is undefined. When using actions, you must set the archive node endpoint in Mina.Network(). Please ensure your Mina.Network() configuration includes an archive node endpoint.'); queryInputs.tokenId = queryInputs.tokenId ?? TokenId.toBase58(TokenId.default); let [response, _error] = await makeGraphqlRequest(getActionsQuery(queryInputs), graphqlEndpoint, networkConfig.archiveFallbackEndpoints, { headers: { ...networkConfig.archiveDefaultHeaders, ...headers } }); let fetchedActions = response?.data.actions; if (fetchedActions === undefined) { return { error: { statusCode: 404, statusText: `fetchActions: Account with public key ${queryInputs.publicKey} with tokenId ${queryInputs.tokenId} does not exist.`, }, }; } const actionsList = createActionsList({ publicKey: queryInputs.publicKey, actionStates: { fromActionState: queryInputs.actionStates?.fromActionState, endActionState: queryInputs.actionStates?.endActionState, }, }, fetchedActions); addCachedActions({ publicKey: queryInputs.publicKey, tokenId: queryInputs.tokenId }, actionsList, graphqlEndpoint); return actionsList; } /** * Given a graphQL response from #getActionsQuery, process the actions into a canonical actions list */ export function createActionsList(accountInfo, fetchedActions) { const { publicKey, actionStates } = accountInfo; let actionsList = []; // correct for archive node sending one block too many if (fetchedActions.length !== 0 && fetchedActions[0].actionState.actionStateOne === actionStates.fromActionState) { fetchedActions = fetchedActions.slice(1); } fetchedActions.forEach((actionBlock) => { let { actionData } = actionBlock; let latestActionState = Field(actionBlock.actionState.actionStateTwo); let actionState = actionBlock.actionState.actionStateOne; if (actionData.length === 0) throw Error(`No action data was found for the account ${publicKey} with the latest action state ${actionState}`); // DEPRECATED: In case the archive node is running an out-of-date version, best guess is to sort by the account update id // As of 2025-01-07, minascan is running a version of the node which supports `sequenceNumber` and `zkappAccountUpdateIds` fields // We could consider removing this fallback since no other nodes are widely used if (!actionData[0].transactionInfo) { actionData = actionData.sort((a1, a2) => { return Number(a1.accountUpdateId) - Number(a2.accountUpdateId); }); } else { // sort actions within one block by transaction sequence number and account update sequence actionData = actionData.sort((a1, a2) => { const a1TxSequence = a1.transactionInfo.sequenceNumber; const a2TxSequence = a2.transactionInfo.sequenceNumber; if (a1TxSequence === a2TxSequence) { const a1AuSequence = a1.transactionInfo.zkappAccountUpdateIds.indexOf(Number(a1.accountUpdateId)); const a2AuSequence = a2.transactionInfo.zkappAccountUpdateIds.indexOf(Number(a2.accountUpdateId)); return a1AuSequence - a2AuSequence; } else { return a1TxSequence - a2TxSequence; } }); } // split actions by account update let actionsByAccountUpdate = []; let currentAccountUpdateId = 'none'; let currentActions; actionData.forEach(({ accountUpdateId, data }) => { if (accountUpdateId === currentAccountUpdateId) { currentActions.push(data); } else { currentAccountUpdateId = accountUpdateId; currentActions = [data]; actionsByAccountUpdate.push(currentActions); } }); // re-hash actions for (let actions of actionsByAccountUpdate) { latestActionState = updateActionState(actions, latestActionState); actionsList.push({ actions, hash: latestActionState.toString() }); } const finalActionState = latestActionState.toString(); const expectedActionState = actionState; if (finalActionState !== expectedActionState) { throw new Error(`Failed to derive correct actions hash for ${publicKey}. Derived hash: ${finalActionState}, expected hash: ${expectedActionState}). All action hashes derived: ${JSON.stringify(actionsList, null, 2)} Please try a different Archive Node API endpoint. `); } }); return actionsList; } /** * Fetches genesis constants. */ async function fetchGenesisConstants(graphqlEndpoint = networkConfig.minaEndpoint, headers) { let [resp, error] = await makeGraphqlRequest(genesisConstantsQuery, graphqlEndpoint, networkConfig.minaFallbackEndpoints, { headers: { ...networkConfig.minaDefaultHeaders, ...headers } }); if (error) throw Error(error.statusText); const genesisConstants = resp?.data?.genesisConstants; const consensusConfiguration = resp?.data?.daemonStatus?.consensusConfiguration; if (genesisConstants === undefined || consensusConfiguration === undefined) { throw Error('Failed to fetch genesis constants.'); } const data = { genesisTimestamp: genesisConstants.genesisTimestamp, coinbase: Number(genesisConstants.coinbase), accountCreationFee: Number(genesisConstants.accountCreationFee), epochDuration: Number(consensusConfiguration.epochDuration), k: Number(consensusConfiguration.k), slotDuration: Number(consensusConfiguration.slotDuration), slotsPerEpoch: Number(consensusConfiguration.slotsPerEpoch), }; genesisConstantsCache[graphqlEndpoint] = data; return data; } var Lightnet; (function (Lightnet) { /** * Gets random key pair (public and private keys) from account manager * that operates with accounts configured in target network Genesis Ledger. * * If an error is returned by the specified endpoint, an error is thrown. Otherwise, * the data is returned. * * @param options.isRegularAccount Whether to acquire key pair of regular or zkApp account (one with already configured verification key) * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from * @returns Key pair */ async function acquireKeyPair(options = {}) { const { isRegularAccount = true, lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, } = options; const response = await fetch(`${lightnetAccountManagerEndpoint}/acquire-account?isRegularAccount=${isRegularAccount}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (response.ok) { const data = await response.json(); if (data) { return { publicKey: PublicKey.fromBase58(data.pk), privateKey: PrivateKey.fromBase58(data.sk), }; } } throw new Error('Failed to acquire the key pair'); } Lightnet.acquireKeyPair = acquireKeyPair; /** * Releases previously acquired key pair by public key. * * @param options.publicKey Public key of previously acquired key pair to release * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from * @returns Response message from the account manager as string or null if the request failed */ async function releaseKeyPair(options) { const { publicKey, lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint, } = options; const response = await fetch(`${lightnetAccountManagerEndpoint}/release-account`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ pk: publicKey, }), }); if (response.ok) { const data = await response.json(); if (data) { return data.message; } } return null; } Lightnet.releaseKeyPair = releaseKeyPair; /** * Gets previously acquired key pairs list. * * @param options.lightnetAccountManagerEndpoint Account manager endpoint to fetch from * @returns Key pairs list or null if the request failed */ async function listAcquiredKeyPairs(options) { const { lightnetAccountManagerEndpoint = networkConfig.lightnetAccountManagerEndpoint } = options; const response = await fetch(`${lightnetAccountManagerEndpoint}/list-acquired-accounts`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (response.ok) { const data = await response.json(); if (data) { return data.map((account) => ({ publicKey: PublicKey.fromBase58(account.pk), privateKey: PrivateKey.fromBase58(account.sk), })); } } return null; } Lightnet.listAcquiredKeyPairs = listAcquiredKeyPairs; })(Lightnet || (Lightnet = {})); function updateActionState(actions, actionState) { let actionHash = Actions.fromJSON(actions).hash; return Actions.updateSequenceState(actionState, actionHash); } // TODO it seems we're not actually catching most errors here async function makeGraphqlRequest(query, graphqlEndpoint = networkConfig.minaEndpoint, fallbackEndpoints, { timeout = defaultTimeout, headers } = {}) { if (graphqlEndpoint === 'none') throw Error("Should have made a graphql request, but don't know to which endpoint. Try calling `setGraphqlEndpoint` first."); let timeouts = []; const clearTimeouts = () => { timeouts.forEach((t) => clearTimeout(t)); timeouts = []; }; const makeRequest = async (url) => { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); timeouts.push(timer); let body = JSON.stringify({ operationName: null, query, variables: {} }); try { let response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', ...headers, }, body, signal: controller.signal, }); return checkResponseStatus(response); } finally { clearTimeouts(); } }; // try to fetch from endpoints in pairs let timeoutErrors = []; let urls = [graphqlEndpoint, ...fallbackEndpoints]; for (let i = 0; i < urls.length; i += 2) { let url1 = urls[i]; let url2 = urls[i + 1]; if (url2 === undefined) { try { return await makeRequest(url1); } catch (error) { return [undefined, inferError(error)]; } } try { return await Promise.race([makeRequest(url1), makeRequest(url2)]); } catch (unknownError) { let error = inferError(unknownError); if (error.statusCode === 408) { // If the request timed out, try the next 2 endpoints timeoutErrors.push({ url1, url2, error }); } else { // If the request failed for some other reason (e.g. o1js error), return the error return [undefined, error]; } } } const statusText = timeoutErrors .map(({ url1, url2, error }) => `Request to ${url1} and ${url2} timed out. Error: ${error}`) .join('\n'); return [undefined, { statusCode: 408, statusText }]; } async function checkResponseStatus(response) { if (response.ok) { let jsonResponse = await response.json(); if (jsonResponse.errors && jsonResponse.errors.length > 0) { return [ undefined, { statusCode: response.status, statusText: jsonResponse.errors.map((error) => error.message).join('\n'), }, ]; } else if (jsonResponse.data === undefined) { return [ undefined, { statusCode: response.status, statusText: `GraphQL response data is undefined`, }, ]; } return [jsonResponse, undefined]; } else { return [ undefined, { statusCode: response.status, statusText: response.statusText, }, ]; } } function inferError(error) { let errorMessage = JSON.stringify(error); if (error instanceof AbortSignal) { return { statusCode: 408, statusText: `Request Timeout: ${errorMessage}` }; } else { return { statusCode: 500, statusText: `Unknown Error: ${errorMessage}`, }; } } //# sourceMappingURL=fetch.js.map