UNPKG

@provablehq/sdk

Version:

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

1,123 lines (1,119 loc) 241 kB
import 'core-js/proposals/json-parse-with-source.js'; import { ViewKey, ComputeKey, Address, PrivateKeyCiphertext, PrivateKey, RecordCiphertext, EncryptionToolkit, Group, Program, Plaintext, Transaction, ProvingRequest, Metadata, VerifyingKey, ProvingKey, ProgramManager as ProgramManager$1, RecordPlaintext, verifyFunctionExecution } from '@provablehq/wasm/testnet.js'; export { Address, Authorization, BHP1024, BHP256, BHP512, BHP768, Boolean, Ciphertext, ComputeKey, EncryptionToolkit, ExecutionRequest, ExecutionResponse, Field, Execution as FunctionExecution, GraphKey, Group, I128, I16, I32, I64, I8, OfflineQuery, Pedersen128, Pedersen64, Plaintext, Poseidon2, Poseidon4, Poseidon8, PrivateKey, PrivateKeyCiphertext, Program, ProgramManager as ProgramManagerBase, ProvingKey, ProvingRequest, RecordCiphertext, RecordPlaintext, Scalar, Signature, Transaction, Transition, U128, U16, U32, U64, U8, VerifyingKey, ViewKey, initThreadPool, verifyFunctionExecution } from '@provablehq/wasm/testnet.js'; /** * Key Management class. Enables the creation of a new Aleo Account, importation of an existing account from * an existing private key or seed, and message signing and verification functionality. An Aleo Account is generated * from a randomly generated seed (number) from which an account private key, view key, and a public account address are * derived. The private key lies at the root of an Aleo account. It is a highly sensitive secret and should be protected * as it allows for creation of Aleo Program executions and arbitrary value transfers. The View Key allows for decryption * of a user's activity on the blockchain. The Address is the public address to which other users of Aleo can send Aleo * credits and other records to. This class should only be used in environments where the safety of the underlying key * material can be assured. * * @example * import { Account } from "@provablehq/sdk/testnet.js"; * * // Create a new account * const myRandomAccount = new Account(); * * // Create an account from a randomly generated seed * const seed = new Uint8Array([94, 91, 52, 251, 240, 230, 226, 35, 117, 253, 224, 210, 175, 13, 205, 120, 155, 214, 7, 169, 66, 62, 206, 50, 188, 40, 29, 122, 40, 250, 54, 18]); * const mySeededAccount = new Account({seed: seed}); * * // Create an account from an existing private key * const myExistingAccount = new Account({privateKey: process.env.privateKey}); * * // Sign a message * const hello_world = Uint8Array.from([104, 101, 108, 108, 111 119, 111, 114, 108, 100]); * const signature = myRandomAccount.sign(hello_world); * * // Verify a signature * assert(myRandomAccount.verify(hello_world, signature)); */ class Account { _privateKey; _viewKey; _computeKey; _address; constructor(params = {}) { try { this._privateKey = this.privateKeyFromParams(params); } catch (e) { console.error("Wrong parameter", e); throw new Error("Wrong Parameter"); } this._viewKey = ViewKey.from_private_key(this._privateKey); this._computeKey = ComputeKey.from_private_key(this._privateKey); this._address = Address.from_private_key(this._privateKey); } /** * Attempts to create an account from a private key ciphertext * @param {PrivateKeyCiphertext | string} ciphertext The encrypted private key ciphertext or its string representation * @param {string} password The password used to decrypt the private key ciphertext * @returns {Account} A new Account instance created from the decrypted private key * * @example * import { Account } from "@provablehq/sdk/testnet.js"; * * // Create an account object from a previously encrypted ciphertext and password. * const account = Account.fromCiphertext(process.env.ciphertext, process.env.password); */ static fromCiphertext(ciphertext, password) { try { ciphertext = (typeof ciphertext === "string") ? PrivateKeyCiphertext.fromString(ciphertext) : ciphertext; const _privateKey = PrivateKey.fromPrivateKeyCiphertext(ciphertext, password); return new Account({ privateKey: _privateKey.to_string() }); } catch (e) { throw new Error("Wrong password or invalid ciphertext"); } } /** * Creates a PrivateKey from the provided parameters. * @param {AccountParam} params The parameters containing either a private key string or a seed * @returns {PrivateKey} A PrivateKey instance derived from the provided parameters */ privateKeyFromParams(params) { if (params.seed) { return PrivateKey.from_seed_unchecked(params.seed); } if (params.privateKey) { return PrivateKey.from_string(params.privateKey); } return new PrivateKey(); } /** * Returns the PrivateKey associated with the account. * @returns {PrivateKey} The private key of the account * * @example * import { Account } from "@provablehq/sdk/testnet.js"; * * const account = new Account(); * const privateKey = account.privateKey(); */ privateKey() { return this._privateKey; } /** * Returns the ViewKey associated with the account. * @returns {ViewKey} The view key of the account * * @example * import { Account } from "@provablehq/sdk/testnet.js"; * * const account = new Account(); * const viewKey = account.viewKey(); */ viewKey() { return this._viewKey; } /** * Returns the ComputeKey associated with the account. * @returns {ComputeKey} The compute key of the account * * @example * import { Account } from "@provablehq/sdk/testnet.js"; * * const account = new Account(); * const computeKey = account.computeKey(); */ computeKey() { return this._computeKey; } /** * Returns the Aleo address associated with the account. * @returns {Address} The public address of the account * * @example * import { Account } from "@provablehq/sdk/testnet.js"; * * const account = new Account(); * const address = account.address(); */ address() { return this._address; } /** * Deep clones the Account. * @returns {Account} A new Account instance with the same private key * * @example * import { Account } from "@provablehq/sdk/testnet.js"; * * const account = new Account(); * const clonedAccount = account.clone(); */ clone() { return new Account({ privateKey: this._privateKey.to_string() }); } /** * Returns the address of the account in a string representation. * * @returns {string} The string representation of the account address */ toString() { return this.address().to_string(); } /** * Encrypts the account's private key with a password. * * @param {string} password Password to encrypt the private key. * @returns {PrivateKeyCiphertext} The encrypted private key ciphertext * * @example * import { Account } from "@provablehq/sdk/testnet.js"; * * const account = new Account(); * const ciphertext = account.encryptAccount("password"); * process.env.ciphertext = ciphertext.toString(); */ encryptAccount(password) { return this._privateKey.toCiphertext(password); } /** * Decrypts an encrypted record string into a plaintext record object. * * @param {string} ciphertext A string representing the ciphertext of a record. * @returns {RecordPlaintext} The decrypted record plaintext * * @example * // Import the AleoNetworkClient and Account classes * import { AleoNetworkClient, Account } from "@provablehq/sdk/testnet.js"; * * // Create a connection to the Aleo network and an account * const networkClient = new AleoNetworkClient("https://api.explorer.provable.com/v1"); * const account = Account.fromCiphertext(process.env.ciphertext!, process.env.password!); * * // Get the record ciphertexts from a transaction. * const transaction = await networkClient.getTransactionObject("at1fjy6s9md2v4rgcn3j3q4qndtfaa2zvg58a4uha0rujvrn4cumu9qfazxdd"); * const records = transaction.records(); * * // Decrypt any records the account owns. * const decryptedRecords = []; * for (const record of records) { * if (account.decryptRecord(record)) { * decryptedRecords.push(record); * } * } */ decryptRecord(ciphertext) { return this._viewKey.decrypt(ciphertext); } /** * Decrypts an array of Record ciphertext strings into an array of record plaintext objects. * * @param {string[]} ciphertexts An array of strings representing the ciphertexts of records. * @returns {RecordPlaintext[]} An array of decrypted record plaintexts * * @example * // Import the AleoNetworkClient and Account classes * import { AleoNetworkClient, Account } from "@provablehq/sdk/testnet.js"; * * // Create a connection to the Aleo network and an account * const networkClient = new AleoNetworkClient("https://api.explorer.provable.com/v1"); * const account = Account.fromCiphertext(process.env.ciphertext!, process.env.password!); * * // Get the record ciphertexts from a transaction. * const transaction = await networkClient.getTransactionObject("at1fjy6s9md2v4rgcn3j3q4qndtfaa2zvg58a4uha0rujvrn4cumu9qfazxdd"); * const records = transaction.records(); * * // Decrypt any records the account owns. If the account owns no records, the array will be empty. * const decryptedRecords = account.decryptRecords(records); */ decryptRecords(ciphertexts) { return ciphertexts.map((ciphertext) => this._viewKey.decrypt(ciphertext)); } /** * Generates a record view key from the account owner's view key and the record ciphertext. * This key can be used to decrypt the record without revealing the account's view key. * @param {RecordCiphertext | string} recordCiphertext The record ciphertext to generate the view key for * @returns {Field} The record view key * * @example * // Import the Account class * import { Account } from "@provablehq/sdk/testnet.js"; * * // Create an account object from a previously encrypted ciphertext and password. * const account = Account.fromCiphertext(process.env.ciphertext!, process.env.password!); * * // Generate a record view key from the account's view key and a record ciphertext * const recordCiphertext = RecordCiphertext.fromString("your_record_ciphertext_here"); * const recordViewKey = account.generateRecordViewKey(recordCiphertext); */ generateRecordViewKey(recordCiphertext) { if (typeof recordCiphertext === 'string') { recordCiphertext = RecordCiphertext.fromString(recordCiphertext); } if (!(recordCiphertext.isOwner(this._viewKey))) { throw new Error("The record ciphertext does not belong to this account"); } return EncryptionToolkit.generateRecordViewKey(this._viewKey, recordCiphertext); } /** * Generates a transition view key from the account owner's view key and the transition public key. * This key can be used to decrypt the private inputs and outputs of a the transition without * revealing the account's view key. * @param {string | Group} tpk The transition public key * @returns {Field} The transition view key * * @example * // Import the Account class * import { Account } from "@provablehq/sdk/testnet.js"; * * // Generate a transition view key from the account's view key and a transition public key * const tpk = Group.fromString("your_transition_public_key_here"); * * const transitionViewKey = account.generateTransitionViewKey(tpk); */ generateTransitionViewKey(tpk) { if (typeof tpk === 'string') { tpk = Group.fromString(tpk); } return EncryptionToolkit.generateTvk(this._viewKey, tpk); } /** * Determines whether the account owns a ciphertext record. * @param {RecordCiphertext | string} ciphertext The record ciphertext to check ownership of * @returns {boolean} True if the account owns the record, false otherwise * * @example * // Import the AleoNetworkClient and Account classes * import { AleoNetworkClient, Account } from "@provablehq/sdk/testnet.js"; * * // Create a connection to the Aleo network and an account * const networkClient = new AleoNetworkClient("https://api.explorer.provable.com/v1"); * const account = Account.fromCiphertext(process.env.ciphertext!, process.env.password!); * * // Get the record ciphertexts from a transaction and check ownership of them. * const transaction = await networkClient.getTransactionObject("at1fjy6s9md2v4rgcn3j3q4qndtfaa2zvg58a4uha0rujvrn4cumu9qfazxdd"); * const records = transaction.records(); * * // Check if the account owns any of the record ciphertexts present in the transaction. * const ownedRecords = []; * for (const record of records) { * if (account.ownsRecordCiphertext(record)) { * ownedRecords.push(record); * } * } */ ownsRecordCiphertext(ciphertext) { if (typeof ciphertext === 'string') { try { const ciphertextObject = RecordCiphertext.fromString(ciphertext); return ciphertextObject.isOwner(this._viewKey); } catch (e) { return false; } } else { return ciphertext.isOwner(this._viewKey); } } /** * Signs a message with the account's private key. * Returns a Signature. * * @param {Uint8Array} message Message to be signed. * @returns {Signature} Signature over the message in bytes. * * @example * // Import the Account class * import { Account } from "@provablehq/sdk/testnet.js"; * * // Create a connection to the Aleo network and an account * const account = Account.fromCiphertext(process.env.ciphertext, process.env.password); * * // Create an account and a message to sign. * const account = new Account(); * const message = Uint8Array.from([104, 101, 108, 108, 111 119, 111, 114, 108, 100]) * const signature = account.sign(message); * * // Verify the signature. * assert(account.verify(message, signature)); */ sign(message) { return this._privateKey.sign(message); } /** * Verifies the Signature on a message. * * @param {Uint8Array} message Message in bytes to be signed. * @param {Signature} signature Signature to be verified. * @returns {boolean} True if the signature is valid, false otherwise. * * @example * // Import the Account class * import { Account } from "@provablehq/sdk/testnet.js"; * * // Create a connection to the Aleo network and an account * const account = Account.fromCiphertext(process.env.ciphertext, process.env.password); * * // Sign a message. * const message = Uint8Array.from([104, 101, 108, 108, 111 119, 111, 114, 108, 100]) * const signature = account.sign(message); * * // Verify the signature. * assert(account.verify(message, signature)); */ verify(message, signature) { return this._address.verify(message, signature); } } 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; verboseErrors; network; constructor(host, options) { this.host = host + "/testnet"; this.network = "testnet"; this.ctx = {}; this.verboseErrors = true; 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.8", "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 * * @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 + "/testnet"; } /** * Set verbose errors to true or false for the `AleoNetworkClient`. When set to true, if `submitTransaction` fails, the failure responses will report descriptive information as to why the transaction failed. * * @param {boolean} verboseErrors Set verbose error mode to true or false for the AleoNetworkClient. * @example * import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js"; * * // Create a networkClient * const networkClient = new AleoNetworkClient(); * * // Set debug mode to true * networkClient.setVerboseTransactionErrors(true); **/ setVerboseErrors(verboseErrors) { this.verboseErrors = verboseErrors; } /** * 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; } 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) { const recordViewKey = recordPlaintext.recordViewKey(viewKey).toString(); // Otherwise record the nonce that has been found const serialNumber = recordPlaintext.serialNumberString(resolvedPrivateKey, "credits.aleo", "credits", recordViewKey); // 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 = {}; }