Introduction
Hey developers. We build apps. Building private web apps is hard. I know that because I spend half my life debugging WASM panics. But the tooling is getting better. Last week I talked about local key management in Deep Dive: Why persistent key storage matters for Shield-era Aleo apps. Today we write some actual code.
We are going to scaffold a complete browser app for private transfers using create-leo-app.
What we are building
You need a reliable way to transfer tokens without exposing balances. Our goal is a minimalist frontend that executes token transfers locally. We will wire up a small Leo program and connect it to the Provable SDK. Multithreaded WASM initialization is the last piece.
Prerequisites
Make sure Node.js is installed. Install the Leo CLI. Grab a fresh cup of coffee.
Step 1: Scaffold the project
Run the generator.
npm create leo-app@latest private-transfer
cd private-transfer
npm install
npm run install-leo
The generator gives you a React frontend plus a separate Leo program folder. Your React code lives under src, and the Leo program lives in its own project directory.
Step 2: Write the token program
Open the generated Leo program directory and edit src/main.leo. In current Leo syntax, externally callable entry points use transition. You do not add a plain constructor() block here, and you only need async finalize logic when you are updating public on-chain state.
program private_transfer_app.aleo {
record Token {
owner: address,
amount: u64,
}
transition transfer_private(sender_token: Token, receiver: address, amount: u64) -> (Token, Token) {
let difference: u64 = sender_token.amount - amount;
let remaining: Token = Token {
owner: sender_token.owner,
amount: difference,
};
let transferred: Token = Token {
owner: receiver,
amount: amount,
};
return (remaining, transferred);
}
}
Your program.json manifest should look like this.
{
"program": "private_transfer_app.aleo",
"version": "0.1.0",
"description": "A browser-first private transfer app.",
"license": "MIT"
}
Step 3: Build and run
Switch into the Leo program directory before you test locally.
cd private_transfer_app
leo build
leo run transfer_private "{ owner: aleo1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef, amount: 100u64, _nonce: 0group }" aleo10987654321fedcba0987654321fedcba0987654321fedcba0987654321fedcba 20u64
The command above runs the transition locally. It consumes the input record. It returns two new records. If you want to generate a proof instead of a local run, use leo execute.
Step 4: The frontend wiring
Open your React application. You need to initialize the Provable SDK before you do anything expensive with WASM.
Proof generation on the main thread is a bad time. Browsers behave much better if you initialize the thread pool early, and real apps should move heavy proving work into a Web Worker.
import {
initThreadPool,
ProgramManager,
AleoKeyProvider,
NetworkRecordProvider,
AleoNetworkClient,
} from '@provablehq/sdk/mainnet.js';
await initThreadPool();
const endpoint = 'https://api.explorer.provable.com/v2';
const networkClient = new AleoNetworkClient(endpoint);
const keyProvider = new AleoKeyProvider();
const recordProvider = new NetworkRecordProvider('aleo1sender...', networkClient);
const programManager = new ProgramManager(endpoint, keyProvider, recordProvider);
const inputs = [
'{ owner: aleo1sender..., amount: 100u64, _nonce: 0group }',
'aleo1receiver...',
'20u64',
];
const tx = await programManager.buildExecutionTransaction(
'private_transfer_app.aleo',
'transfer_private',
inputs,
0.2,
false,
);
const txId = await networkClient.submitTransaction(tx);
The SDK handles local proof generation and transaction construction. The network only sees the submitted transaction artifacts, not your plaintext notes in the frontend.
What is next
Play around with the code. Break it. Fix it again. Aleo Developer Office Hours are a good place to ask questions. Keep building.