@provablehq/sdk
Version:
A Software Development Kit (SDK) for Zero-Knowledge Transactions
1,121 lines (1,119 loc) • 168 kB
JavaScript
import { PrivateKey, RecordCiphertext, Program, Plaintext, Address, Transaction, Metadata, VerifyingKey, ProvingKey, ProgramManager as ProgramManager$1, RecordPlaintext, verifyFunctionExecution } from '@provablehq/wasm/testnet.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 + "/testnet";
this.network = "testnet";
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 + "/testnet";
}
/**
* 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