Aleo Backend Integration
1. Overview
Backend services on Aleo typically handle: querying on-chain state, orchestrating transactions, scanning for records, delegating proof generation, and managing keys. This skill covers the @provablehq/sdk in a Node.js environment.
Version & Canonical Syntax
- Target: Leo compiler >= 4.0.0 and current
@provablehq/sdkTypeScript APIs - Canonical syntax: On-chain examples assume Leo 4.0 conventions —
fnfor all entry points (replacestransition/function/inline),finalblocks for on-chain state updates (replacesasync function finalize_*),Finalreturn type (replacesFuture),.run()to invoke a final block (replaces.await()), and@test fnfor unit tests - Input formatting: Numeric inputs must include Leo suffixes such as
u64,field, andscalar - When docs conflict: Prefer current SDK API references and this skill's transaction-building patterns
2. Key Concepts & Glossary
- AleoNetworkClient: Queries the Aleo network (mappings, programs, transactions, blocks).
- ProgramManager: Orchestrates program execution, deployment, and key management.
- AleoKeyProvider: Manages proving/verifying key storage and caching.
- NetworkRecordProvider: Discovers unspent records on-chain for a given address.
- RecordScanner: Confidential record scanning service backed by UUIDs and JWTs. More scalable than
NetworkRecordProvider. - Delegated Proving: Sending proof generation to a remote service to save local CPU.
- Authorization: A signed commitment to execute a function, which can be sent to a remote prover.
3. SDK Setup (Node.js)
bash
npm install @provablehq/sdk
typescript
import {
Account,
AleoNetworkClient,
ProgramManager,
AleoKeyProvider,
NetworkRecordProvider,
} from "@provablehq/sdk";
// Create an account from private key
const account = Account.from_string("APrivateKey1...");
console.log("Address:", account.address().to_string());
console.log("View Key:", account.viewKey().to_string());
// Set up network client
const networkClient = new AleoNetworkClient("https://api.explorer.provable.com/v2");
// Set up key provider with caching
const keyProvider = new AleoKeyProvider();
keyProvider.useCache(true);
// Set up record provider
const recordProvider = new NetworkRecordProvider(account, networkClient);
// Create program manager
const programManager = new ProgramManager(
"https://api.explorer.provable.com/v2",
keyProvider,
recordProvider,
);
programManager.setAccount(account);
4. Reading On-Chain State
Query Mapping Values
typescript
// Read a single mapping value
const balance = await networkClient.getProgramMappingValue(
"token.aleo",
"account",
"aleo1qnr4dkkvkgfqph0vzc3y6z2eu975wnpz2925ntjccd5cfqxtyu8s7pyjh9"
);
console.log("Balance:", balance); // e.g., "1000u64"
// Get program source code
const source = await networkClient.getProgram("token.aleo");
// Get latest block height
const height = await networkClient.getLatestHeight();
// Get transaction details
const tx = await networkClient.getTransaction("at1...");
REST API Endpoints (Direct)
typescript
const BASE_URL = "https://api.explorer.provable.com/v2";
const NETWORK = "testnet";
// Get mapping value
const res = await fetch(
`${BASE_URL}/${NETWORK}/program/token.aleo/mapping/account/aleo1...`
);
const value = await res.text();
// Get latest height
const heightRes = await fetch(`${BASE_URL}/${NETWORK}/latest/height`);
const height = await heightRes.text();
// Get program source
const progRes = await fetch(`${BASE_URL}/${NETWORK}/program/token.aleo`);
const source = await progRes.text();
5. Executing Transactions
typescript
// Execute a function on a deployed program
const tx = await programManager.buildExecutionTransaction({
programName: "token.aleo",
functionName: "transfer_public",
inputs: [
"aleo1receiver...", // receiver address
"100u64", // amount
],
fee: 1_000_000, // fee in microcredits
privateFee: false, // use public credits for fee
});
// Broadcast the transaction
const txId = await networkClient.submitTransaction(tx);
console.log("Transaction ID:", txId);
6. Transaction Polling
typescript
async function waitForConfirmation(
client: AleoNetworkClient,
txId: string,
maxAttempts: number = 60,
intervalMs: number = 5000
): Promise<any> {
for (let i = 0; i < maxAttempts; i++) {
try {
const result = await client.getTransaction(txId);
if (result) {
console.log("Transaction confirmed:", result);
return result;
}
} catch {
// Not found yet
}
console.log(`Polling attempt ${i + 1}/${maxAttempts}...`);
await new Promise(resolve => setTimeout(resolve, intervalMs));
}
throw new Error(`Transaction ${txId} not confirmed after ${maxAttempts} attempts`);
}
7. Record Management
Finding Records
typescript
// Find unspent records for a program
const records = await recordProvider.findUnspentRecords(
0, // start height
undefined, // end height (latest)
"token.aleo", // program ID
undefined, // record name filter
);
for (const record of records) {
console.log("Record:", record.toString());
}
Decrypting Records with View Key
typescript
import { ViewKey, RecordCiphertext } from "@provablehq/sdk";
const viewKey = ViewKey.from_string(account.viewKey().to_string());
// Decrypt a single ciphertext
const ciphertext = RecordCiphertext.fromString("record1...");
const plaintext = viewKey.decrypt(ciphertext.toString());
console.log("Decrypted:", plaintext);
8. Deploying Programs from Backend
typescript
import * as fs from "fs";
// Read the compiled program
const programSource = fs.readFileSync("./build/main.aleo", "utf8");
const tx = await programManager.buildDeploymentTransaction({
program: programSource,
fee: 5_000_000, // deployment fee in microcredits
});
const txId = await networkClient.submitTransaction(tx);
console.log("Deploy transaction:", txId);
// Wait for confirmation
await waitForConfirmation(networkClient, txId);
9. Delegated Proving
For backend services on lightweight infrastructure, you can delegate proof generation to a specialized proving service instead of generating proofs locally.
Authorization-First Flow
typescript
// 1. Build an authorization locally (fast, no proving)
const authorization = await programManager.buildAuthorization({
programName: "token.aleo",
functionName: "transfer_public",
inputs: ["aleo1receiver...", "100u64"],
fee: 1_000_000,
});
// 2. Submit to a remote proving service
// The proving service generates the proof and returns a complete transaction
// Replace with your proving service endpoint
const response = await fetch(provingServiceUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ authorization: authorization.toString() }),
});
const completeTx = await response.json();
// 3. Broadcast the completed transaction
const txId = await networkClient.submitTransaction(completeTx);
10. Key Management
LocalFileKeyStore
For backend servers, cache proving/verifying keys to disk to avoid re-synthesis:
typescript
// Keys are automatically cached when using AleoKeyProvider with useCache(true)
const keyProvider = new AleoKeyProvider();
keyProvider.useCache(true);
// Keys are stored in the default cache directory
// On subsequent runs, cached keys are loaded from disk instead of re-synthesized
11. Environment Variables
bash
# .env for backend service
ALEO_PRIVATE_KEY=APrivateKey1... # NEVER log this
ALEO_VIEW_KEY=AViewKey1... # Safe for read-only services
ALEO_NETWORK=testnet
ALEO_ENDPOINT=https://api.explorer.provable.com/v2
12. Security Notes
- Use environment variables or secrets managers for keys
- NEVER embed private keys in source code
- NEVER log private keys
- Use view keys for read-only monitoring services
- Separate signing services from orchestration services
- For production, use HSMs or secure enclaves for private key operations
13. Performance Notes
- Reuse one long-lived
AleoNetworkClientper process to avoid excess connection setup - Cache proving and verifying keys with
AleoKeyProvider.useCache(true)before production traffic - Use exponential backoff for polling-heavy workflows to reduce API throttling risk
- Delegate proving for latency-sensitive services running on CPU-limited infrastructure
14. Common Backend Errors and Fixes
| Error | Cause | Fix |
|---|---|---|
| "Record not found" | No unspent records for fee payment | Ensure the account has credits; split records if needed |
| "Network error" | API endpoint unreachable | Implement retry logic with exponential backoff |
| "Insufficient fee" | Fee too low for the operation | Increase fee amount |
| "Program not found" | Program ID doesn't exist on network | Verify program is deployed; check network (testnet vs mainnet) |
| Timeout during proving | Proof generation too slow | Use delegated proving or increase timeout |
| "Invalid input format" | Missing type suffix in inputs | Always include suffix: "100u64" not "100" |
| Authorization rejected by prover | Authorization payload missing expected function or fee fields | Rebuild authorization with exact programName, functionName, inputs, and fee values expected by prover |
15. Production Patterns
Read-Only Service (View Key Only)
typescript
// A monitoring service that only reads state — never signs transactions
const viewKey = ViewKey.from_string(process.env.ALEO_VIEW_KEY!);
const networkClient = new AleoNetworkClient(process.env.ALEO_ENDPOINT!);
// Read mapping values
const balance = await networkClient.getProgramMappingValue(
"token.aleo", "account", targetAddress
);
// Scan for records (read-only)
// Note: can see records but cannot spend them without private key
Transaction Service (With Retry Logic)
typescript
async function executeWithRetry(
pm: ProgramManager,
client: AleoNetworkClient,
programName: string,
functionName: string,
inputs: string[],
fee: number,
maxRetries: number = 3
): Promise<string> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const tx = await pm.buildExecutionTransaction({
programName,
functionName,
inputs,
fee,
privateFee: false,
});
const txId = await client.submitTransaction(tx);
await waitForConfirmation(client, txId);
return txId;
} catch (error) {
console.error(`Attempt ${attempt + 1} failed:`, error);
if (attempt === maxRetries - 1) throw error;
await new Promise(r => setTimeout(r, 2000 * Math.pow(2, attempt)));
}
}
throw new Error("Should not reach here");
}
- Write Leo programs using
aleo_smart_contracts - Frontend integration using
aleo_frontend - Deploy programs using
aleo_deployment - Staking operations using
aleo_staking_delegation - Complete recipes in
aleo_cookbook
17. Agent SOP: Backend Integration Workflow
- Model the operation: identify program ID, function name, input types, and expected output state
- Validate inputs: enforce Leo suffix formatting and address shape before calling SDK methods
- Build transaction or authorization with explicit fee policy and deterministic retry settings
- Broadcast through one client path and avoid duplicate submissions from parallel workers
- Poll confirmation with bounded retries and exponential backoff
- Verify resulting state via mapping reads or transaction inspection before returning success
- On failure: map error to the matrix above, apply the documented fix, and retry only when safe