skill·2026-03-26·8 min read

Aleo Backend Integration

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/sdk TypeScript APIs
  • Canonical syntax: On-chain examples assume Leo 4.0 conventions — fn for all entry points (replaces transition / function / inline), final blocks for on-chain state updates (replaces async function finalize_*), Final return type (replaces Future), .run() to invoke a final block (replaces .await()), and @test fn for unit tests
  • Input formatting: Numeric inputs must include Leo suffixes such as u64, field, and scalar
  • 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 AleoNetworkClient per 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

ErrorCauseFix
"Record not found"No unspent records for fee paymentEnsure the account has credits; split records if needed
"Network error"API endpoint unreachableImplement retry logic with exponential backoff
"Insufficient fee"Fee too low for the operationIncrease fee amount
"Program not found"Program ID doesn't exist on networkVerify program is deployed; check network (testnet vs mainnet)
Timeout during provingProof generation too slowUse delegated proving or increase timeout
"Invalid input format"Missing type suffix in inputsAlways include suffix: "100u64" not "100"
Authorization rejected by proverAuthorization payload missing expected function or fee fieldsRebuild 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

  1. Model the operation: identify program ID, function name, input types, and expected output state
  2. Validate inputs: enforce Leo suffix formatting and address shape before calling SDK methods
  3. Build transaction or authorization with explicit fee policy and deterministic retry settings
  4. Broadcast through one client path and avoid duplicate submissions from parallel workers
  5. Poll confirmation with bounded retries and exponential backoff
  6. Verify resulting state via mapping reads or transaction inspection before returning success
  7. On failure: map error to the matrix above, apply the documented fix, and retry only when safe

Sources