---
name: leo-scaffold
description: Full-stack Aleo blockchain development skill for the leo-scaffold toolkit. Use this skill whenever a user is building on Aleo, writing Leo programs, deploying to Aleo testnet/mainnet, integrating Leo Wallet, working with ZK proofs, records, transitions, or building dApps with the leo-scaffold project structure (Next.js 14 + Leo + TypeScript). Trigger for any Leo program design, onchainbio.aleo work, Aleo record model questions, privacy-preserving smart contract architecture, circuit constraints, proving/verification flows, or frontend integration with Aleo programs. Also trigger for deployment scripts, debug console work, and wallet adapter integration.
---
# Leo Scaffold — Aleo Full-Stack Development Skill
You are a senior full-stack Aleo blockchain developer working inside the **leo-scaffold** toolkit. You think in ZK-native terms: record model, private state, circuit constraints, Leo transitions, and proof generation. All code you write is production-grade.
---
## Project Context
**Live Program**: `onchainbio.aleo` on Aleo Testnet
**Stack**: Next.js 14 (App Router) · TypeScript · Tailwind CSS · Leo · Leo Wallet Adapter
**Privacy model**: All user data private by default via ZK proofs. Records are encrypted to owner.
### Directory Layout
```
my-app/
├── app/
│ ├── page.tsx
│ ├── bio/page.tsx
│ ├── debug/page.tsx
│ ├── docs/page.tsx
│ ├── components/
│ └── wallet/
├── program/
│ ├── src/main.leo
│ ├── build/
│ └── deploy.sh
└── docs/
```
---
## Core Leo Program: onchainbio.aleo
### Record Structure
```leo
record BioProfile:
owner as address.private;
name as field.private;
bio as field.private;
created_at_block as u64.private;
updated_at_block as u64.private;
```
### Transitions
| Function | Description |
|---|---|
| `register_bio` | Mint a new BioProfile record for caller |
| `update_bio` | Consume old record, output updated record (owner only) |
| `get_bio` | Return profile fields (owner only, private) |
| `view_bio` | Public mapping lookup — check if bio exists for address |
### Privacy Model
- Records are **encrypted to owner** — only the owner can read or spend them
- `register_bio` / `update_bio` execute as ZK transitions — inputs never revealed on-chain
- `view_bio` uses a **public mapping** (`address → bool`) for existence checks
- Block height fields are private — not visible to observers
---
## Leo Program Patterns
### Standard Transition
```leo
transition register_bio(
name: field,
bio: field,
block_height: u64
) -> BioProfile {
return BioProfile {
owner: self.caller,
name: name,
bio: bio,
created_at_block: block_height,
updated_at_block: block_height,
};
}
```
### Record Consumption Pattern (update)
```leo
transition update_bio(
old_profile: BioProfile,
new_name: field,
new_bio: field,
block_height: u64
) -> BioProfile {
return BioProfile {
owner: old_profile.owner,
name: new_name,
bio: new_bio,
created_at_block: old_profile.created_at_block,
updated_at_block: block_height,
};
}
```
### Public Mapping Pattern
```leo
mapping bio_exists: address => bool;
async transition view_bio(public user: address) -> Future {
return finalize_view_bio(user);
}
async function finalize_view_bio(user: address) {
let exists: bool = Mapping::get_or_use(bio_exists, user, false);
assert_eq(exists, true);
}
```
---
## Frontend Integration
### Wallet Provider
```typescript
import { WalletProvider } from "@demox-labs/aleo-wallet-adapter-react";
import { LeoWalletAdapter } from "@demox-labs/aleo-wallet-adapter-leo";
import { WalletModalProvider } from "@demox-labs/aleo-wallet-adapter-reactui";
const wallets = [new LeoWalletAdapter({ appName: "leo-scaffold" })];
export function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
### Executing a Transition
```typescript
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import { Transaction, WalletAdapterNetwork } from "@demox-labs/aleo-wallet-adapter-base";
const PROGRAM_ID = "onchainbio.aleo";
const NETWORK = WalletAdapterNetwork.TestnetBeta;
export function useRegisterBio() {
const { requestTransaction, publicKey } = useWallet();
const register = async (name: string, bio: string, blockHeight: number) => {
const tx = Transaction.createTransaction(
publicKey,
NETWORK,
PROGRAM_ID,
"register_bio",
[stringToField(name), stringToField(bio), `${blockHeight}u64`],
150000
);
return await requestTransaction(tx);
};
return { register };
}
```
### Field Encoding Helpers
```typescript
export function stringToField(str: string): string {
const encoder = new TextEncoder();
const bytes = encoder.encode(str.slice(0, 30));
let value = BigInt(0);
for (let i = 0; i < bytes.length; i++) {
value += BigInt(bytes[i]) * (BigInt(256) ** BigInt(i));
}
return `${value}field`;
}
export function fieldToString(field: string): string {
let value = BigInt(field.replace("field", ""));
const bytes: number[] = [];
while (value > 0n) {
bytes.push(Number(value % 256n));
value /= 256n;
}
return new TextDecoder().decode(new Uint8Array(bytes)).replace(/\0/g, "");
}
```
---
## Deployment
```bash
#!/bin/bash
set -e
echo "Building program..."
leo build
echo "Deploying to Aleo Testnet..."
leo deploy \
--broadcast \
--yes \
--network testnet \
--private-key "${ALEO_PRIVATE_KEY}" \
--query "https://api.explorer.aleo.org/v1"
echo "Done. View: https://explorer.aleo.org/programs/${PROGRAM_NAME}.aleo"
```
---
## Debug Console — Key APIs
```typescript
// Query a public mapping
const res = await fetch(
`https://api.explorer.aleo.org/v1/testnet/program/${PROGRAM_ID}/mapping/bio_exists/${address}`
);
const exists = await res.json();
// Poll transaction status
const tx = await fetch(
`https://api.explorer.aleo.org/v1/testnet/transaction/${txId}`
);
const data = await tx.json(); // check data.status === "accepted"
```
---
## ZK Execution Flow
1. User calls transition in browser via Leo Wallet
2. Wallet generates ZK proof locally (WASM prover)
3. Proof + encrypted outputs broadcast to Aleo network
4. Validators verify proof — never see private inputs
5. Output records encrypted to owner's address, stored on-chain
6. Owner's wallet scans chain with view key to decrypt owned records
---
## Leo Types → JS
| Leo Type | JS Format | Example |
|---|---|---|
| `field` | `"Nfield"` | `"7field"` |
| `u64` | `"Nu64"` | `"1000u64"` |
| `address` | bech32 string | `"aleo1..."` |
| `bool` | `"true"`/`"false"` | `"true"` |
| `u8` | `"Nu8"` | `"255u8"` |
---
## Guidelines
- Minimize circuit constraints — avoid dynamic loops, use fixed-size unrolled logic
- Always set `self.caller` as record owner on mint transitions
- Use mappings for public state, records for private state
- Fee budget: 150,000–300,000 microcredits per transition
- Test locally with `leo run ` before deploying
- Testnet faucet: https://faucet.aleo.org
## Adding a New Program to leo-scaffold
1. Edit `program/src/main.leo`
2. `leo build` — fix constraint errors
3. Run `deploy.sh`
4. Add route `app/[feature]/page.tsx`
5. Create hook `app/wallet/use[Feature].ts`
6. Expose inputs in `app/debug/page.tsx`
7. Document transitions in `docs/`