Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a430afe
Change SnarkVM dep to latest rev on the corresponding SnarkVM:feat/dy…
iamalwaysuncomfortable Feb 17, 2026
42ec63a
[Fix] add WASM support for dynamic dispatch variant types (#1199)
marshacb Feb 24, 2026
367a7f2
fix: use dot-delimited type strings for dynamic.record and dynamic.fu…
marshacb Mar 2, 2026
14946ce
Handle Consensus V14 in deployments (#1230)
iamalwaysuncomfortable Mar 10, 2026
25423eb
Update SnarkVM rev for dynamic dispatch
iamalwaysuncomfortable Mar 11, 2026
02d19b4
[Feature] Create stringToField utility (#1237)
iamalwaysuncomfortable Mar 12, 2026
a2d2ad7
[Feature] Re-export dynamic record from SnarkVM (#1236)
iamalwaysuncomfortable Mar 12, 2026
78a3362
[Feature] Updates to make AMM tests work. (#1241)
d0cd Mar 24, 2026
30249f0
feat: Add dynamic dispatch import resolution across all WASM entry po…
marshacb Feb 26, 2026
180498b
refactor: Unify resolve_imports to handle both static and dynamic dis…
marshacb Mar 3, 2026
fa5d4c6
Add ProgramImportsBuilder for zero-cost proving/verifying key passing…
marshacb Mar 5, 2026
3ba2b7a
Fix review feedback: guard phase-2 key insertion, remove debug log, f…
marshacb Mar 6, 2026
f8762c6
Refactor ProgramEntry to store native types, add edition support, fix…
marshacb Mar 10, 2026
2173787
Add KeyStore integration with auto-persist for top-level and import k…
marshacb Mar 11, 2026
be5e5b7
Add key byte methods and toObject to ProgramImportsBuilder, fix test …
marshacb Mar 11, 2026
953a731
Fix review feedback: restore type: module, add programNames/getProgra…
marshacb Mar 11, 2026
3774d9d
Add transitive import resolution for dynamic dispatch targets with te…
marshacb Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion create-leo-app/template-devnode-js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ constructor:
async function main() {
// Initialize multi-threading to allow WASM execution.
await initThreadPool();
const heights = getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12");
const heights = getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13");
console.log(`Set development consensus heights to ${heights}`);

const privateKey = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH";
const account = new Account({privateKey});
Expand Down
133 changes: 72 additions & 61 deletions create-leo-app/template-node-ts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
Account,
AleoKeyProvider,
AleoKeyProviderParams,
ConfirmedTransactionJSON,
initThreadPool,
LocalFileKeyStore,
ProgramManager,
Transaction,
} from "@provablehq/sdk/testnet.js";
Expand Down Expand Up @@ -33,106 +33,115 @@ const programManager = new ProgramManager();
const account = new Account();
programManager.setAccount(account);

// Create a key provider in order to re-use the same key for each execution
// Create a key provider with in-memory caching for the current session
const keyProvider = new AleoKeyProvider();
keyProvider.useCache(true);
programManager.setKeyProvider(keyProvider);

async function localProgramExecution(program: string, programName: string, aleoFunction: string, inputs: string[]) {
// Pre-synthesize the program keys and then cache them in memory using the key provider.
try {
const keyPair = await programManager.synthesizeKeys(program, aleoFunction, inputs);
// Set up a persistent KeyStore to cache proving and verifying keys on disk.
// Keys are stored in the .aleo/ directory and survive across sessions.
const keyStore = new LocalFileKeyStore();
programManager.setKeyStore(keyStore);

programManager.keyProvider.cacheKeys(`${programName}:${aleoFunction}`, keyPair);

} catch (e) {
throw new Error(`Failed to synthesize keys: ${e.message}`);
}
// Run offline execution, deployment, and online execution of hello_hello.aleo.
async function run(online: boolean = false) {
const programName = "hello_hello.aleo";
const hello_hello_program = generateHelloHelloSource(programName);
const functionName = "hello";
const inputs = ["5u32", "5u32"];

// Specify parameters for the key provider to use search for program keys. In particular specify the cache key
// that was used to cache the keys in the previous step.
const keyProviderParams = new AleoKeyProviderParams({cacheKey: `${programName}:${aleoFunction}`});
// --- STEP 1: Execute offline (cold — keys are synthesized and auto-persisted) ---
console.log("");
console.log("// --- STEP 1: Execute offline (cold run — keys synthesized and auto-persisted). --- //");
let start = Date.now();

// Execute once using the key provider params defined above. This will use the cached proving keys and make
// execution significantly faster.
// The first run() synthesizes keys inside WASM and automatically persists them
// to the KeyStore. No explicit synthesizeKeys() call is needed.
let executionResponse = await programManager.run(
program,
aleoFunction,
hello_hello_program,
functionName,
inputs,
true,
undefined,
keyProviderParams,
);
console.log("hello_hello/hello executed - result:", executionResponse.getOutputs());
const coldTime = Date.now() - start;
console.log(`Cold execution completed in ${coldTime / 1000}s — result: ${executionResponse.getOutputs()}`);

// Verify the execution using the verifying key that was generated earlier.
// Verify the execution.
if (programManager.verifyExecution(executionResponse, 9_000_000)) {
console.log("hello_hello/hello execution verified!");
console.log("Execution verified!");
} else {
throw("Execution failed verification!");
throw new Error("Execution failed verification!");
}
}

// Run a deployment and both online and offline executions.
async function run(online: boolean = false) {
// Generate the hello_hello.aleo program source code and inputs.
let programName = `hello_hello.aleo`;
let hello_hello_program = generateHelloHelloSource(programName);
const functionName = "hello";
const inputs = ["5u32", "5u32"];
// Confirm keys were auto-persisted to the KeyStore.
const proverStored = await keyStore.has(`${programName}.${functionName}.prover`);
const verifierStored = await keyStore.has(`${programName}.${functionName}.verifier`);
console.log(`Keys auto-persisted to KeyStore: prover=${proverStored}, verifier=${verifierStored}`);

// --- STEP 2: Execute again offline (warm — keys are loaded from KeyStore, no synthesis) ---
console.log("");
console.log("// --- STEP 1: Execute the program offline to test it gives the expected results. --- //");
// Execute the program locally.
console.log(`Executing ${programName}/hello offline`);
let start = Date.now();
const result = await localProgramExecution(hello_hello_program, programName, functionName, inputs);
console.log(`✅ Local execute finished in ${(Date.now() - start)/1000}s`);
console.log("// --- STEP 2: Execute offline again (warm run — keys loaded from KeyStore). --- //");
start = Date.now();

// No synthesizeKeys call needed — the ProgramManager automatically checks the KeyStore
// for cached proving and verifying keys before execution.
executionResponse = await programManager.run(
hello_hello_program,
functionName,
inputs,
true,
);
const warmTime = Date.now() - start;
console.log(`Warm execution completed in ${warmTime / 1000}s — result: ${executionResponse.getOutputs()}`);

if (programManager.verifyExecution(executionResponse, 9_000_000)) {
console.log("Execution verified!");
} else {
throw new Error("Execution failed verification!");
}

if (warmTime < coldTime) {
console.log(`KeyStore speedup: ${(coldTime / warmTime).toFixed(1)}x faster on warm run`);
}

// --- STEP 3: Build a deployment transaction. ---
console.log("");
console.log("// --- STEP 2: Build the deployment transaction. --- //");
console.log("// --- STEP 3: Build the deployment transaction. --- //");
start = Date.now();
programName = `hello_hello_${Math.floor(Math.random() * 65536)}.aleo`;
hello_hello_program = generateHelloHelloSource(programName);
const deployProgramName = `hello_hello_${Math.floor(Math.random() * 65536)}.aleo`;
const deployProgram = generateHelloHelloSource(deployProgramName);
const deploymentTx: Transaction = await programManager.buildDeploymentTransaction(
hello_hello_program,
deployProgram,
0,
false,
)
console.log(`Deployment transaction built in ${(Date.now() - start)/1000}s`);
);
console.log(`Deployment transaction built in ${(Date.now() - start) / 1000}s`);

// If the deployment flag is set to true, deploy the program on testnet (requires aleo credits).
if (online) {
const txId: string = await programManager.networkClient.submitTransaction(deploymentTx);
const confirmedTx: ConfirmedTransactionJSON = await programManager.networkClient.waitForTransactionConfirmation(txId);
if (txId === confirmedTx.transaction.id) {
console.log(`Program ${programName} deployed to Aleo Testnet successfully!`);
console.log(`Program ${deployProgramName} deployed to Aleo Testnet successfully!`);
}
} else {
programName = `hello_hello.aleo`;
hello_hello_program = generateHelloHelloSource(programName);
}

// --- STEP 4: Build an online execution transaction. ---
// Keys from Step 1 are automatically loaded from KeyStore — no re-synthesis.
console.log("");
console.log("// --- STEP 3: Execute the program ONLINE. --- //");

// If the program was actually deployed, execute it online. Otherwise, execute an equivalent
// program with the same logic.
console.log(`Executing ${programName}/hello online on the aleo network`);
console.log("// --- STEP 4: Build an online execution transaction (keys from KeyStore). --- //");
start = Date.now();
const keySearchParams = new AleoKeyProviderParams({cacheKey: `${programName}:${functionName}`});
const executionTx: Transaction = await programManager.buildExecutionTransaction(
{
programName,
functionName,
priorityFee: 0,
privateFee: false,
inputs: inputs,
keySearchParams,
program: hello_hello_program
}
)
console.log(`✅ Online execution of ${programName} built in ${(Date.now() - start)/1000}s`);
inputs,
program: hello_hello_program,
},
);
console.log(`Online execution transaction built in ${(Date.now() - start) / 1000}s`);

// If the online option is specified, submit the transaction to the network.
if (online) {
Expand All @@ -142,7 +151,9 @@ async function run(online: boolean = false) {
console.log(`Program ${programName}/hello executed successfully!`);
}
}

// Clean up persisted keys
await keyStore.clear();
}

// Run the offline execution, deployment, and online execution of hello_hello.aleo.
await run(false);
1 change: 1 addition & 0 deletions create-leo-app/template-node-ts/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

/* Module Resolution Options */
"moduleResolution": "bundler",
"customConditions": ["node"],
"esModuleInterop": true,

/* Advanced Options */
Expand Down
123 changes: 123 additions & 0 deletions create-leo-app/template-react-ts/src/IndexedDBKeyStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type {
KeyStore,
KeyLocator,
} from "@provablehq/sdk";
import {
ProvingKey,
VerifyingKey,
} from "@provablehq/sdk";
import type { FunctionKeyPair } from "@provablehq/sdk";

const DB_NAME = "aleo-keystore";
const DB_VERSION = 1;
const KEYS_STORE = "keys";
const META_STORE = "metadata";

/**
* Browser-native KeyStore backed by IndexedDB.
*
* Implements the SDK's KeyStore interface so that proving and verifying keys
* are persisted across page reloads. Drop-in replacement for LocalFileKeyStore
* in browser/React environments.
*/
export class IndexedDBKeyStore implements KeyStore {
private dbPromise: Promise<IDBDatabase>;

constructor() {
this.dbPromise = this.open();
}

private open(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const req = indexedDB.open(DB_NAME, DB_VERSION);
req.onupgradeneeded = () => {
const db = req.result;
if (!db.objectStoreNames.contains(KEYS_STORE)) {
db.createObjectStore(KEYS_STORE);
}
if (!db.objectStoreNames.contains(META_STORE)) {
db.createObjectStore(META_STORE);
}
};
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}

private async tx(
store: string,
mode: IDBTransactionMode,
): Promise<IDBObjectStore> {
const db = await this.dbPromise;
return db.transaction(store, mode).objectStore(store);
}

private request<T>(req: IDBRequest<T>): Promise<T> {
return new Promise((resolve, reject) => {
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}

async getKeyBytes(locator: KeyLocator): Promise<Uint8Array | null> {
const store = await this.tx(KEYS_STORE, "readonly");
const result = await this.request(store.get(locator.locator));
return result instanceof Uint8Array ? result : null;
}

async getProvingKey(locator: KeyLocator): Promise<ProvingKey | null> {
const bytes = await this.getKeyBytes(locator);
if (!bytes) return null;
return ProvingKey.fromBytes(bytes);
}

async getVerifyingKey(locator: KeyLocator): Promise<VerifyingKey | null> {
const bytes = await this.getKeyBytes(locator);
if (!bytes) return null;
return VerifyingKey.fromBytes(bytes);
}

async setKeys(
proverLocator: KeyLocator,
verifierLocator: KeyLocator,
keys: FunctionKeyPair,
): Promise<void> {
const [pk, vk] = keys;
const store = await this.tx(KEYS_STORE, "readwrite");
await this.request(store.put(pk.toBytes(), proverLocator.locator));
// Reopen transaction (IndexedDB auto-commits after await).
const store2 = await this.tx(KEYS_STORE, "readwrite");
await this.request(store2.put(vk.toBytes(), verifierLocator.locator));
}

async setKeyBytes(keyBytes: Uint8Array, locator: KeyLocator): Promise<void> {
const store = await this.tx(KEYS_STORE, "readwrite");
await this.request(store.put(keyBytes, locator.locator));
}

async getKeyMetadata(locator: string): Promise<any | null> {
const store = await this.tx(META_STORE, "readonly");
const result = await this.request(store.get(locator));
return result ?? null;
}

async has(locator: string): Promise<boolean> {
const store = await this.tx(KEYS_STORE, "readonly");
const count = await this.request(store.count(locator));
return count > 0;
}

async delete(locator: string): Promise<void> {
const keyStore = await this.tx(KEYS_STORE, "readwrite");
await this.request(keyStore.delete(locator));
const metaStore = await this.tx(META_STORE, "readwrite");
await this.request(metaStore.delete(locator));
}

async clear(): Promise<void> {
const keyStore = await this.tx(KEYS_STORE, "readwrite");
await this.request(keyStore.clear());
const metaStore = await this.tx(META_STORE, "readwrite");
await this.request(metaStore.clear());
}
}
13 changes: 13 additions & 0 deletions create-leo-app/template-react-ts/src/workers/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,26 @@ import {
NetworkRecordProvider,
} from "@provablehq/sdk";
import { expose, proxy } from "comlink";
import { IndexedDBKeyStore } from "../IndexedDBKeyStore";

await initThreadPool();

// Shared KeyStore instance persists keys in IndexedDB across page reloads.
const keyStore = new IndexedDBKeyStore();

async function localProgramExecution(program, aleoFunction, inputs) {
const programManager = new ProgramManager();

// Create a temporary account for the execution of the program
const account = new Account();
programManager.setAccount(account);

// Set up key caching: in-memory for the session, IndexedDB across sessions.
const keyProvider = new AleoKeyProvider();
keyProvider.useCache(true);
programManager.setKeyProvider(keyProvider);
programManager.setKeyStore(keyStore);

const executionResponse = await programManager.run(
program,
aleoFunction,
Expand Down Expand Up @@ -56,6 +66,9 @@ async function deployProgram(program) {

programManager.setAccount(account);

// Persist keys across sessions with IndexedDB.
programManager.setKeyStore(keyStore);

// Define a fee to pay to deploy the program
const fee = 1.9; // 1.9 Aleo credits

Expand Down
Loading
Loading