UNPKG

@provablehq/sdk

Version:

A Software Development Kit (SDK) for Zero-Knowledge Transactions

1,121 lines (1,119 loc) 168 kB
import { PrivateKey, RecordCiphertext, Program, Plaintext, Address, Transaction, Metadata, VerifyingKey, ProvingKey, ProgramManager as ProgramManager$1, RecordPlaintext, verifyFunctionExecution } from '@provablehq/wasm/mainnet.js'; function detectBrowser() { const userAgent = navigator.userAgent; if (/chrome|crios|crmo/i.test(userAgent) && !/edge|edg|opr/i.test(userAgent)) { return "chrome"; } else if (/firefox|fxios/i.test(userAgent)) { return "firefox"; } else if (/safari/i.test(userAgent) && !/chrome|crios|crmo|android/i.test(userAgent)) { return "safari"; } else if (/edg/i.test(userAgent)) { return "edge"; } else if (/opr\//i.test(userAgent)) { return "opera"; } else { return "browser"; } } function environment() { if ((typeof process !== 'undefined') && (process.release?.name === 'node')) { return 'node'; } else if (typeof window !== 'undefined') { return detectBrowser(); } else { return 'unknown'; } } function logAndThrow(message) { console.error(message); throw new Error(message); } function parseJSON(json) { function revive(key, value, context) { if (Number.isInteger(value)) { return BigInt(context.source); } else { return value; } } return JSON.parse(json, revive); } async function get(url, options) { const response = await fetch(url, options); if (!response.ok) { throw new Error(response.status + " could not get URL " + url); } return response; } async function post(url, options) { options.method = "POST"; const response = await fetch(url, options); if (!response.ok) { throw new Error(response.status + " could not post URL " + url); } return response; } async function retryWithBackoff(fn, { maxAttempts = 5, baseDelay = 100, jitter, retryOnStatus = [], shouldRetry, } = {}) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (err) { const isLast = attempt === maxAttempts; const error = err; let retryable = false; if (typeof error.status === "number") { if (error.status >= 500) { retryable = true; } else if (error.status >= 400 && shouldRetry) { retryable = shouldRetry(error); } } else if (shouldRetry) { retryable = shouldRetry(error); } if (!retryable || isLast) throw error; const jitterAmount = jitter ?? baseDelay; const actualJitter = Math.floor(Math.random() * jitterAmount); const delay = baseDelay * 2 ** (attempt - 1) + actualJitter; console.warn(`Retry ${attempt}/${maxAttempts} failed. Retrying in ${delay}ms...`); await new Promise((res) => setTimeout(res, delay)); } } throw new Error("retryWithBackoff: unreachable"); } /** * Client library that encapsulates REST calls to publicly exposed endpoints of Aleo nodes. The methods provided in this * allow users to query public information from the Aleo blockchain and submit transactions to the network. * * @param {string} host * @example * // Connection to a local node. * const localNetworkClient = new AleoNetworkClient("http://0.0.0.0:3030", undefined, account); * * // Connection to a public beacon node * const account = Account.fromCiphertext(process.env.ciphertext, process.env.password); * const publicNetworkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined, account); */ class AleoNetworkClient { host; headers; account; ctx; network; constructor(host, options) { this.host = host + "/mainnet"; this.network = "mainnet"; this.ctx = {}; if (options && options.headers) { this.headers = options.headers; } else { this.headers = { // This is replaced by the actual version by a Rollup plugin "X-Aleo-SDK-Version": "0.9.2", "X-Aleo-environment": environment(), }; } } /** * Set an account to use in networkClient calls * * @param {Account} account Set an account to use for record scanning functions. * @example * import { Account, AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1"); * const account = new Account(); * networkClient.setAccount(account); */ setAccount(account) { this.account = account; } /** * Return the Aleo account used in the networkClient * * @example * const account = networkClient.getAccount(); */ getAccount() { return this.account; } /** * Set a new host for the networkClient * * @param {string} host The address of a node hosting the Aleo API * @param host * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a networkClient that connects to a local node. * const networkClient = new AleoNetworkClient("http://0.0.0.0:3030", undefined); * * // Set the host to a public node. * networkClient.setHost("http://api.explorer.provable.com/v1"); */ setHost(host) { this.host = host + "/mainnet"; } /** * Set a header in the `AleoNetworkClient`s header map * * @param {string} headerName The name of the header to set * @param {string} value The header value * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a networkClient * const networkClient = new AleoNetworkClient(); * * // Set the value of the `Accept-Language` header to `en-US` * networkClient.setHeader('Accept-Language', 'en-US'); */ setHeader(headerName, value) { this.headers[headerName] = value; } /** * Remove a header from the `AleoNetworkClient`s header map * * @param {string} headerName The name of the header to be removed * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a networkClient * const networkClient = new AleoNetworkClient(); * * // Remove the default `X-Aleo-SDK-Version` header * networkClient.removeHeader('X-Aleo-SDK-Version'); */ removeHeader(headerName) { delete this.headers[headerName]; } /** * Fetches data from the Aleo network and returns it as a JSON object. * * @param url The URL to fetch data from. */ async fetchData(url = "/") { try { const raw = await this.fetchRaw(url); return parseJSON(raw); } catch (error) { throw new Error(`Error fetching data: ${error}`); } } /** * Fetches data from the Aleo network and returns it as an unparsed string. * * This method should be used when it is desired to reconstitute data returned * from the network into a WASM object. * * @param url */ async fetchRaw(url = "/") { try { const ctx = { ...this.ctx }; return await retryWithBackoff(async () => { const response = await get(this.host + url, { headers: { ...this.headers, ...ctx, }, }); return await response.text(); }); } catch (error) { throw new Error(`Error fetching data: ${error}`); } } /** * Wrapper around the POST helper to allow mocking in tests. Not meant for use in production. * * @param url The URL to POST to. * @param options The RequestInit options for the POST request. * @returns The Response object from the POST request. */ async _sendPost(url, options) { return post(url, options); } /** * Attempt to find records in the Aleo blockchain. * * @param {number} startHeight - The height at which to start searching for unspent records * @param {number} endHeight - The height at which to stop searching for unspent records * @param {boolean} unspent - Whether to search for unspent records only * @param {string[]} programs - The program(s) to search for unspent records in * @param {number[]} amounts - The amounts (in microcredits) to search for (eg. [100, 200, 3000]) * @param {number} maxMicrocredits - The maximum number of microcredits to search for * @param {string[]} nonces - The nonces of already found records to exclude from the search * @param {string | PrivateKey} privateKey - An optional private key to use to find unspent records. * @returns {Promise<Array<RecordPlaintext>>} An array of records belonging to the account configured in the network client. * * @example * import { Account, AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Import an account from a ciphertext and password. * const account = Account.fromCiphertext(process.env.ciphertext, process.env.password); * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * networkClient.setAccount(account); * * // Find specific amounts * const startHeight = 500000; * const amounts = [600000, 1000000]; * const records = networkClient.findRecords(startHeight, undefined, true, ["credits.aleo"] amounts); * * // Find specific amounts with a maximum number of cumulative microcredits * const maxMicrocredits = 100000; * const records = networkClient.findRecords(startHeight, undefined, true, ["credits.aleo"] undefined, maxMicrocredits); */ async findRecords(startHeight, endHeight, unspent = false, programs, amounts, maxMicrocredits, nonces, privateKey) { nonces = nonces || []; // Ensure start height is not negative if (startHeight < 0) { throw new Error("Start height must be greater than or equal to 0"); } // Initialize search parameters const records = new Array(); let start; let end; let resolvedPrivateKey; let failures = 0; let totalRecordValue = BigInt(0); let latestHeight; // Ensure a private key is present to find owned records if (typeof privateKey === "undefined") { if (typeof this.account === "undefined") { throw new Error("Private key must be specified in an argument to findOwnedRecords or set in the AleoNetworkClient"); } else { resolvedPrivateKey = this.account._privateKey; } } else { try { resolvedPrivateKey = privateKey instanceof PrivateKey ? privateKey : PrivateKey.from_string(privateKey); } catch (error) { throw new Error("Error parsing private key provided."); } } const viewKey = resolvedPrivateKey.to_view_key(); // Get the latest height to ensure the range being searched is valid try { const blockHeight = await this.getLatestHeight(); if (typeof blockHeight === "number") { latestHeight = blockHeight; } else { throw new Error(`Error fetching latest block height: Expected type 'number' got '${typeof blockHeight}'`); } } catch (error) { throw new Error(`Error fetching latest block height: ${error}`); } // If no end height is specified or is greater than the latest height, set the end height to the latest height if (typeof endHeight === "number" && endHeight <= latestHeight) { end = endHeight; } else { end = latestHeight; } // If the starting is greater than the ending height, return an error if (startHeight > end) { throw new Error("Start height must be less than or equal to end height."); } // Iterate through blocks in reverse order in chunks of 50 while (end > startHeight) { start = end - 50; if (start < startHeight) { start = startHeight; } try { // Get 50 blocks (or the difference between the start and end if less than 50) const blocks = await this.getBlockRange(start, end); end = start; // Iterate through blocks to find unspent records for (let i = 0; i < blocks.length; i++) { const block = blocks[i]; const transactions = block.transactions; if (!(typeof transactions === "undefined")) { for (let j = 0; j < transactions.length; j++) { const confirmedTransaction = transactions[j]; // Search for unspent records in execute transactions of credits.aleo if (confirmedTransaction.type == "execute") { const transaction = confirmedTransaction.transaction; if (transaction.execution && !(typeof transaction.execution .transitions == "undefined")) { for (let k = 0; k < transaction.execution.transitions .length; k++) { const transition = transaction.execution.transitions[k]; // Only search for unspent records in the specified programs. if (!(typeof programs === "undefined")) { if (!programs.includes(transition.program)) { continue; } } if (!(typeof transition.outputs == "undefined")) { for (let l = 0; l < transition.outputs.length; l++) { const output = transition.outputs[l]; if (output.type === "record") { try { // Create a wasm record ciphertext object from the found output const record = RecordCiphertext.fromString(output.value); // Determine if the record is owned by the specified view key if (record.isOwner(viewKey)) { // Decrypt the record and get the serial number const recordPlaintext = record.decrypt(viewKey); // If the record has already been found, skip it const nonce = recordPlaintext.nonce(); if (nonces.includes(nonce)) { continue; } if (unspent) { // Otherwise record the nonce that has been found const serialNumber = recordPlaintext.serialNumberString(resolvedPrivateKey, "credits.aleo", "credits"); // Attempt to see if the serial number is spent try { await retryWithBackoff(() => this.getTransitionId(serialNumber)); continue; } catch (error) { console.log("Found unspent record!"); } } // Add the record to the list of records if the user did not specify amounts. if (!amounts) { records.push(recordPlaintext); // If the user specified a maximum number of microcredits, check if the search has found enough if (typeof maxMicrocredits === "number") { totalRecordValue += recordPlaintext.microcredits(); // Exit if the search has found the amount specified if (totalRecordValue >= BigInt(maxMicrocredits)) { return records; } } } // If the user specified a list of amounts, check if the search has found them if (!(typeof amounts === "undefined") && amounts.length > 0) { let amounts_found = 0; if (recordPlaintext.microcredits() > amounts[amounts_found]) { amounts_found += 1; records.push(recordPlaintext); // If the user specified a maximum number of microcredits, check if the search has found enough if (typeof maxMicrocredits === "number") { totalRecordValue += recordPlaintext.microcredits(); // Exit if the search has found the amount specified if (totalRecordValue >= BigInt(maxMicrocredits)) { return records; } } if (records.length >= amounts.length) { return records; } } } } } catch (error) { } } } } } } } } } } } catch (error) { // If there is an error fetching blocks, log it and keep searching console.warn("Error fetching blocks in range: " + start.toString() + "-" + end.toString()); console.warn("Error: ", error); failures += 1; if (failures > 10) { console.warn("10 failures fetching records reached. Returning records fetched so far"); return records; } } } return records; } /** * Attempts to find unspent records in the Aleo blockchain. * * @param {number} startHeight - The height at which to start searching for unspent records * @param {number} endHeight - The height at which to stop searching for unspent records * @param {string[]} programs - The program(s) to search for unspent records in * @param {number[]} amounts - The amounts (in microcredits) to search for (eg. [100, 200, 3000]) * @param {number} maxMicrocredits - The maximum number of microcredits to search for * @param {string[]} nonces - The nonces of already found records to exclude from the search * @param {string | PrivateKey} privateKey - An optional private key to use to find unspent records. * @returns {Promise<Array<RecordPlaintext>>} An array of unspent records belonging to the account configured in the network client. * * @example * import { Account, AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * const account = Account.fromCiphertext(process.env.ciphertext, process.env.password); * * // Create a network client and set an account to search for records with. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * networkClient.setAccount(account); * * // Find specific amounts * const startHeight = 500000; * const endHeight = 550000; * const amounts = [600000, 1000000]; * const records = networkClient.findUnspentRecords(startHeight, endHeight, ["credits.aleo"], amounts); * * // Find specific amounts with a maximum number of cumulative microcredits * const maxMicrocredits = 100000; * const records = networkClient.findUnspentRecords(startHeight, undefined, ["credits.aleo"], undefined, maxMicrocredits); */ async findUnspentRecords(startHeight, endHeight, programs, amounts, maxMicrocredits, nonces, privateKey) { try { this.ctx = { "X-ALEO-METHOD": "findUnspentRecords" }; return await this.findRecords(startHeight, endHeight, true, programs, amounts, maxMicrocredits, nonces, privateKey); } catch (error) { throw new Error("Error finding unspent records: " + error); } finally { this.ctx = {}; } } /** * Returns the contents of the block at the specified block height. * * @param {number} blockHeight - The height of the block to fetch * @returns {Promise<BlockJSON>} A javascript object containing the block at the specified height * * @example * const block = networkClient.getBlock(1234); */ async getBlock(blockHeight) { try { this.ctx = { "X-ALEO-METHOD": "getBlock" }; const block = await this.fetchData("/block/" + blockHeight); return block; } catch (error) { throw new Error(`Error fetching block ${blockHeight}: ${error}`); } finally { this.ctx = {}; } } /** * Returns the contents of the block with the specified hash. * * @param {string} blockHash The hash of the block to fetch. * @returns {Promise<BlockJSON>} A javascript object representation of the block matching the hash. * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * const block = networkClient.getBlockByHash("ab19dklwl9vp63zu3hwg57wyhvmqf92fx5g8x0t6dr72py8r87pxupqfne5t9"); */ async getBlockByHash(blockHash) { try { this.ctx = { "X-ALEO-METHOD": "getBlockByHash" }; const block = await this.fetchData(`/block/${blockHash}`); return block; } catch (error) { throw new Error(`Error fetching block ${blockHash}: ${error}`); } finally { this.ctx = {}; } } /** * Returns a range of blocks between the specified block heights. A maximum of 50 blocks can be fetched at a time. * * @param {number} start Starting block to fetch. * @param {number} end Ending block to fetch. This cannot be more than 50 blocks ahead of the start block. * @returns {Promise<Array<BlockJSON>>} An array of block objects * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Fetch 50 blocks. * const (start, end) = (2050, 2100); * const blockRange = networkClient.getBlockRange(start, end); * * let cursor = start; * blockRange.forEach((block) => { * assert(block.height == cursor); * cursor += 1; * } */ async getBlockRange(start, end) { try { this.ctx = { "X-ALEO-METHOD": "getBlockRange" }; return await this.fetchData("/blocks?start=" + start + "&end=" + end); } catch (error) { throw new Error(`Error fetching blocks between ${start} and ${end}: ${error}`); } finally { this.ctx = {}; } } /** * Returns the deployment transaction id associated with the specified program. * * @param {Program | string} program The name of the deployed program OR a wasm Program object. * @returns {Promise<string>} The transaction ID of the deployment transaction. * * @example * import { AleoNetworkClient } from "@provablehq/sdk/testnet.js"; * * // Get the transaction ID of the deployment transaction for a program. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * const transactionId = networkClient.getDeploymentTransactionIDForProgram("hello_hello.aleo"); * * // Get the transaction data for the deployment transaction. * const transaction = networkClient.getTransactionObject(transactionId); * * // Get the verifying keys for the functions in the deployed program. * const verifyingKeys = transaction.verifyingKeys(); */ async getDeploymentTransactionIDForProgram(program) { this.ctx = { "X-ALEO-METHOD": "getDeploymentTransactionIDForProgram" }; if (program instanceof Program) { program = program.id(); } try { const id = await this.fetchData("/find/transactionID/deployment/" + program); return id.replace('"', ""); } catch (error) { throw new Error(`Error fetching deployment transaction for program ${program}: ${error}`); } finally { this.ctx = {}; } } /** * Returns the deployment transaction associated with a specified program as a JSON object. * * @param {Program | string} program The name of the deployed program OR a wasm Program object. * @returns {Promise<Transaction>} JSON representation of the deployment transaction. * * @example * import { AleoNetworkClient, DeploymentJSON } from "@provablehq/sdk/testnet.js"; * * // Get the transaction ID of the deployment transaction for a program. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * const transaction = networkClient.getDeploymentTransactionForProgram("hello_hello.aleo"); * * // Get the verifying keys for each function in the deployment. * const deployment = <DeploymentJSON>transaction.deployment; * const verifyingKeys = deployment.verifying_keys; */ async getDeploymentTransactionForProgram(program) { if (program instanceof Program) { program = program.id(); } try { this.ctx = { "X-ALEO-METHOD": "getDeploymentTransactionForProgram" }; const transaction_id = (await this.getDeploymentTransactionIDForProgram(program)); return await this.getTransaction(transaction_id); } catch (error) { throw new Error(`Error fetching deployment transaction for program ${program}: ${error}`); } finally { this.ctx = {}; } } /** * Returns the deployment transaction associated with a specified program as a wasm object. * * @param {Program | string} program The name of the deployed program OR a wasm Program object. * @returns {Promise<Transaction>} Wasm object representation of the deployment transaction. * * @example * import { AleoNetworkClient } from "@provablehq/sdk/testnet.js"; * * // Get the transaction ID of the deployment transaction for a program. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * const transactionId = networkClient.getDeploymentTransactionIDForProgram("hello_hello.aleo"); * * // Get the transaction data for the deployment transaction. * const transaction = networkClient.getDeploymentTransactionObjectForProgram(transactionId); * * // Get the verifying keys for the functions in the deployed program. * const verifyingKeys = transaction.verifyingKeys(); */ async getDeploymentTransactionObjectForProgram(program) { try { this.ctx = { "X-ALEO-METHOD": "getDeploymentTransactionObjectForProgram" }; const transaction_id = (await this.getDeploymentTransactionIDForProgram(program)); return await this.getTransactionObject(transaction_id); } catch (error) { throw new Error(`Error fetching deployment transaction for program ${program}: ${error}`); } finally { this.ctx = {}; } } /** * Returns the contents of the latest block as JSON. * * @returns {Promise<BlockJSON>} A javascript object containing the latest block * * @example * import { AleoNetworkClient } from "@provablehq/sdk/testnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * const latestHeight = networkClient.getLatestBlock(); */ async getLatestBlock() { try { this.ctx = { "X-ALEO-METHOD": "getLatestBlock" }; return (await this.fetchData("/block/latest")); } catch (error) { throw new Error(`Error fetching latest block: ${error}`); } finally { this.ctx = {}; } } /** * Returns the latest committee. * * @returns {Promise<object>} A javascript object containing the latest committee * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * // Create a network client and get the latest committee. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * const latestCommittee = await networkClient.getLatestCommittee(); */ async getLatestCommittee() { try { this.ctx = { "X-ALEO-METHOD": "getLatestCommittee" }; return await this.fetchData("/committee/latest"); } catch (error) { throw new Error(`Error fetching latest committee: ${error}`); } finally { this.ctx = {}; } } /** * Returns the committee at the specified block height. * * @param {number} blockHeight - The height of the block to fetch the committee for * @returns {Promise<object>} A javascript object containing the committee * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * // Create a network client and get the committee for a specific block. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * const committee = await networkClient.getCommitteeByBlockHeight(1234); */ async getCommitteeByBlockHeight(blockHeight) { try { this.ctx = { "X-ALEO-METHOD": "getCommitteeByBlockHeight" }; return await this.fetchData(`/committee/${blockHeight}`); } catch (error) { throw new Error(`Error fetching committee at height ${blockHeight}: ${error}`); } finally { this.ctx = {}; } } /** * Returns the latest block height. * * @returns {Promise<number>} The latest block height. * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * const latestHeight = networkClient.getLatestHeight(); */ async getLatestHeight() { try { this.ctx = { "X-ALEO-METHOD": "getLatestHeight" }; return Number(await this.fetchData("/block/height/latest")); } catch (error) { throw new Error(`Error fetching latest height: ${error}`); } finally { this.ctx = {}; } } /** * Returns the latest block hash. * * @returns {Promise<string>} The latest block hash. * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * // Get the latest block hash. * const latestHash = networkClient.getLatestBlockHash(); */ async getLatestBlockHash() { try { this.ctx = { "X-ALEO-METHOD": "getLatestBlockHash" }; return String(await this.fetchData("/block/hash/latest")); } catch (error) { throw new Error(`Error fetching latest hash: ${error}`); } finally { this.ctx = {}; } } /** * Returns the source code of a program given a program ID. * * @param {string} programId The program ID of a program deployed to the Aleo Network * @returns {Promise<string>} Source code of the program * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * const program = networkClient.getProgram("hello_hello.aleo"); * const expectedSource = "program hello_hello.aleo;\n\nfunction hello:\n input r0 as u32.public;\n input r1 as u32.private;\n add r0 r1 into r2;\n output r2 as u32.private;\n" * assert.equal(program, expectedSource); */ async getProgram(programId) { try { this.ctx = { "X-ALEO-METHOD": "getProgram" }; return await this.fetchData("/program/" + programId); } catch (error) { throw new Error(`Error fetching program ${programId}: ${error}`); } finally { this.ctx = {}; } } /** * Returns a program object from a program ID or program source code. * * @param {string} inputProgram The program ID or program source code of a program deployed to the Aleo Network * @returns {Promise<Program>} Source code of the program * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * const programID = "hello_hello.aleo"; * const programSource = "program hello_hello.aleo;\n\nfunction hello:\n input r0 as u32.public;\n input r1 as u32.private;\n add r0 r1 into r2;\n output r2 as u32.private;\n" * * // Get program object from program ID or program source code * const programObjectFromID = await networkClient.getProgramObject(programID); * const programObjectFromSource = await networkClient.getProgramObject(programSource); * * // Both program objects should be equal * assert(programObjectFromID.to_string() === programObjectFromSource.to_string()); */ async getProgramObject(inputProgram) { try { this.ctx = { "X-ALEO-METHOD": "getProgramObject" }; return Program.fromString(inputProgram); } catch (error) { try { return Program.fromString(await this.getProgram(inputProgram)); } catch (error) { throw new Error(`${inputProgram} is neither a program name or a valid program: ${error}`); } } finally { this.ctx = {}; } } /** * Returns an object containing the source code of a program and the source code of all programs it imports * * @param {Program | string} inputProgram The program ID or program source code of a program deployed to the Aleo Network * @returns {Promise<ProgramImports>} Object of the form { "program_id": "program_source", .. } containing program id & source code for all program imports * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * const double_test_source = "import multiply_test.aleo;\n\nprogram double_test.aleo;\n\nfunction double_it:\n input r0 as u32.private;\n call multiply_test.aleo/multiply 2u32 r0 into r1;\n output r1 as u32.private;\n" * const double_test = Program.fromString(double_test_source); * const expectedImports = { * "multiply_test.aleo": "program multiply_test.aleo;\n\nfunction multiply:\n input r0 as u32.public;\n input r1 as u32.private;\n mul r0 r1 into r2;\n output r2 as u32.private;\n" * } * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * // Imports can be fetched using the program ID, source code, or program object * let programImports = await networkClient.getProgramImports("double_test.aleo"); * assert.deepStrictEqual(programImports, expectedImports); * * // Using the program source code * programImports = await networkClient.getProgramImports(double_test_source); * assert.deepStrictEqual(programImports, expectedImports); * * // Using the program object * programImports = await networkClient.getProgramImports(double_test); * assert.deepStrictEqual(programImports, expectedImports); */ async getProgramImports(inputProgram) { try { this.ctx = { "X-ALEO-METHOD": "getProgramImports" }; const imports = {}; // Get the program object or fail if the program is not valid or does not exist const program = inputProgram instanceof Program ? inputProgram : await this.getProgramObject(inputProgram); // Get the list of programs that the program imports const importList = program.getImports(); // Recursively get any imports that the imported programs have in a depth first search order for (let i = 0; i < importList.length; i++) { const import_id = importList[i]; if (!imports.hasOwnProperty(import_id)) { const programSource = (await this.getProgram(import_id)); const nestedImports = (await this.getProgramImports(import_id)); for (const key in nestedImports) { if (!imports.hasOwnProperty(key)) { imports[key] = nestedImports[key]; } } imports[import_id] = programSource; } } return imports; } catch (error) { logAndThrow("Error fetching program imports: " + error.message); } finally { this.ctx = {}; } } /** * Get a list of the program names that a program imports. * * @param {Program | string} inputProgram - The program id or program source code to get the imports of * @returns {string[]} - The list of program names that the program imports * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * const programImportsNames = networkClient.getProgramImports("wrapped_credits.aleo"); * const expectedImportsNames = ["credits.aleo"]; * assert.deepStrictEqual(programImportsNames, expectedImportsNames); */ async getProgramImportNames(inputProgram) { try { this.ctx = { "X-ALEO-METHOD": "getProgramImportNames" }; const program = inputProgram instanceof Program ? inputProgram : await this.getProgramObject(inputProgram); return program.getImports(); } catch (error) { throw new Error(`Error fetching imports for program ${inputProgram instanceof Program ? inputProgram.id() : inputProgram}: ${error.message}`); } finally { this.ctx = {}; } } /** * Returns the names of the mappings of a program. * * @param {string} programId - The program ID to get the mappings of (e.g. "credits.aleo") * @returns {Promise<Array<string>>} - The names of the mappings of the program. * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * const mappings = networkClient.getProgramMappingNames("credits.aleo"); * const expectedMappings = [ * "committee", * "delegated", * "metadata", * "bonded", * "unbonding", * "account", * "withdraw" * ]; * assert.deepStrictEqual(mappings, expectedMappings); */ async getProgramMappingNames(programId) { try { this.ctx = { "X-ALEO-METHOD": "getProgramMappingNames" }; return await this.fetchData(`/program/${programId}/mappings`); } catch (error) { throw new Error(`Error fetching mappings for program ${programId} - ensure the program exists on chain before trying again`); } finally { this.ctx = {}; } } /** * Returns the value of a program's mapping for a specific key. * * @param {string} programId - The program ID to get the mapping value of (e.g. "credits.aleo") * @param {string} mappingName - The name of the mapping to get the value of (e.g. "account") * @param {string | Plaintext} key - The key to look up in the mapping (e.g. an address for the "account" mapping) * @returns {Promise<string>} String representation of the value of the mapping * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * // Get public balance of an account * const mappingValue = networkClient.getMappingValue("credits.aleo", "account", "aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px"); * const expectedValue = "0u64"; * assert(mappingValue === expectedValue); */ async getProgramMappingValue(programId, mappingName, key) { try { this.ctx = { "X-ALEO-METHOD": "getProgramMappingValue" }; const keyString = key instanceof Plaintext ? key.toString() : key; return await this.fetchData(`/program/${programId}/mapping/${mappingName}/${keyString}`); } catch (error) { throw new Error(`Error fetching value for key '${key}' in mapping '${mappingName}' in program '${programId}' - ensure the mapping exists and the key is correct`); } finally { this.ctx = {}; } } /** * Returns the value of a mapping as a wasm Plaintext object. Returning an object in this format allows it to be converted to a Js type and for its internal members to be inspected if it's a struct or array. * * @param {string} programId - The program ID to get the mapping value of (e.g. "credits.aleo") * @param {string} mappingName - The name of the mapping to get the value of (e.g. "bonded") * @param {string | Plaintext} key - The key to look up in the mapping (e.g. an address for the "bonded" mapping) * @returns {Promise<Plaintext>} String representation of the value of the mapping * * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined); * * // Get the bond state as an account. * const unbondedState = networkClient.getMappingPlaintext("credits.aleo", "bonded", "aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px"); * * // Get the two members of the object individually. * const validator = unbondedState.getMember("validator"); * const microcredits = unbondedState.getMember("microcredits"); * * // Ensure the expected values are correct. * assert.equal(validator, "aleo1u6940v5m0fzud859xx2c9tj2gjg6m5qrd28n636e6fdd2akvfcgqs34mfd"); * assert.equal(microcredits, BigInt("9007199254740991")); * * // Get a JS object representation of the unbonded state. * const unbondedStateObject = unbondedState.toObject(); * * const expectedState = { * validator: "aleo1u6940v5m0fzud859xx2c9tj2gjg6m5qrd28n636e6fdd2akvfcgqs34mfd", * microcredits: BigInt(9007199254740991) * }; * assert.equal(unbondedState, expectedState); */ async getProgramMappingPlaintext(programId, mappingName, key) { try { this.ctx = { "X-ALEO-METHOD": "getProgramMappingPlaintext" }; const keyString = key instanceof Plaintext ? key.toString() : key; const value = await this.fetchRaw(`/program/${programId}/mapping/${mappingName}/${keyString}`); return Plaintext.fromString(JSON.parse(value)); } catch (error) { throw new Error("Failed to fetch mapping value." + error); } finally { this.ctx = {}; } } /** * Returns the public balance of an address from the account mapping in credits.aleo * * @param {Address | string} address A string or wasm object representing an address. * @returns {Promise<number>} The public balance of the address in microcredits. * * @example * import { AleoNetworkClient, Account } from "@provablehq/sdk/mainnet.js"; * * // Create a network client. * const networkC