
Understanding the four Legion Score pillars
What each score represents, how it is calculated, and what it takes to reach the top

Concrete Vaults: the most accessible path to real yield in DeFi
A beginner-friendly introduction to automated DeFi strategies powered by Concrete.

Deploying your first Solidity Contract on Arc Testnet
Deploying your first Solidity Contract on Arc Testnet

Subscribe to Colliseum

Understanding the four Legion Score pillars
What each score represents, how it is calculated, and what it takes to reach the top

Concrete Vaults: the most accessible path to real yield in DeFi
A beginner-friendly introduction to automated DeFi strategies powered by Concrete.

Deploying your first Solidity Contract on Arc Testnet
Deploying your first Solidity Contract on Arc Testnet
<100 subscribers
<100 subscribers


Hi! My name is Heorhii. If you are building on Aleo, you already know the magic is privacy by default. The next step is giving users a smooth way to connect a wallet, sign messages, decrypt records, and send private transactions. In this guide I’ll show you how to wire up modular TypeScript wallet adapters in a React app, then level up to a universal adapter that supports multiple wallets out of the box. We will finish with a hands-on example using the token_registry.aleo program.
Everything here is developer focused. Copy the snippets, tweak the values, ship your app.
What we are integrating:
Demox Labs Wallet Adapters for React and React UI
Leo Wallet adapter and a Universal Wallet Adapter that adds Puzzle, Fox, and Soter
Hooks to sign messages, decrypt, request records, send transactions, deploy programs, and subscribe to events
Why Wallet Adapters matter. Before diving into the setup, it’s important to understand why wallet adapters are essential for Aleo developers and how they simplify building privacy-focused applications.
In simple terms, wallet adapters act as a compatibility layer between your dApp and multiple Aleo wallets. They provide a single, consistent API for connecting, signing, decrypting, and sending transactions without requiring separate integrations for each wallet.
Here’s why they are so valuable:
Unified interface for all wallets. You get one standard set of methods such as connect, signMessage, requestTransaction, and decrypt that works across Leo, Puzzle, Fox, and Soter wallets.
Better user experience. Users can choose their preferred wallet while your app logic stays the same. There is no need for extra conditional code or wallet-specific flows.
No vendor lock-in. Wallets can be added or removed through configuration without rewriting core logic.
Improved security and clarity. Adapters keep responsibilities clear: your dApp prepares the intent and the wallet authorizes it. This structure supports stronger security practices.
Consistent permission handling. Permissions such as Decrypt or OnChainHistory are handled the same way in every wallet, keeping behavior predictable.
Stable and maintainable. When a wallet SDK updates, the adapter absorbs most of the changes so your app continues to work.
Reusable and scalable. The same wallet provider instance can serve multiple React components, which keeps large applications clean and efficient.
Faster development. Ready-made UI components like WalletMultiButton and unified TypeScript types help you move quickly from setup to production.
Easier testing. Adapters can be mocked for testing, making it possible to run integration tests without using a real wallet.
Centralized error handling. A single onError hook and consistent transaction status tracking simplify debugging and monitoring.
Flexible privacy and fee settings. Adapters support both public and private fee modes, allowing you to control transaction behavior for different scenarios.
Seamless ecosystem integration. They work naturally with tools like create-leo-app, React frameworks, and Aleo UI kits so you can focus on building your app instead of managing wallet integrations.
https://developer.aleo.org/category/wallets
Quick start with React UI:
1) Install:
npm install --save \
@demox-labs/aleo-wallet-adapter-base \
@demox-labs/aleo-wallet-adapter-react \
@demox-labs/aleo-wallet-adapter-reactui \
@demox-labs/aleo-wallet-adapter-leo \
react
2) Wrap your app:
import React, { FC, useMemo } from "react";
import { WalletProvider } from "@demox-labs/aleo-wallet-adapter-react";
import { WalletModalProvider } from "@demox-labs/aleo-wallet-adapter-reactui";
import { LeoWalletAdapter } from "@demox-labs/aleo-wallet-adapter-leo";
import {
DecryptPermission,
WalletAdapterNetwork,
} from "@demox-labs/aleo-wallet-adapter-base";
import "@demox-labs/aleo-wallet-adapter-reactui/styles.css";
export const Wallet: FC = () => {
const wallets = useMemo(
() => [new LeoWalletAdapter({ appName: "Leo Demo App" })],
[]
);
return (
<WalletProvider
wallets={wallets}
decryptPermission={DecryptPermission.UponRequest}
network={WalletAdapterNetwork.Localnet}
autoConnect
>
<WalletModalProvider>
{/* Your app here */}
</WalletModalProvider>
</WalletProvider>
);
};
Common flows you will need:
1) Sign a message:
import { WalletNotConnectedError } from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import { LeoWalletAdapter } from "@demox-labs/aleo-wallet-adapter-leo";
import React, { FC, useCallback } from "react";
export const SignMessage: FC = () => {
const { wallet, publicKey } = useWallet();
const onClick = useCallback(async () => {
if (!publicKey) throw new WalletNotConnectedError();
const message = "a message to sign";
const bytes = new TextEncoder().encode(message);
const signatureBytes = await (wallet?.adapter as LeoWalletAdapter).signMessage(bytes);
const signature = new TextDecoder().decode(signatureBytes);
alert("Signed message: " + signature);
}, [wallet, publicKey]);
return <button onClick={onClick} disabled={!publicKey}>Sign message</button>;
};
2) Decrypt a record payload:
import { WalletNotConnectedError } from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const DecryptMessage: FC = () => {
const { publicKey, decrypt } = useWallet();
const onClick = async () => {
const cipherText = "record....";
if (!publicKey) throw new WalletNotConnectedError();
if (decrypt) {
const decryptedPayload = await decrypt(cipherText);
alert("Decrypted payload: " + decryptedPayload);
}
};
return <button onClick={onClick} disabled={!publicKey}>Decrypt message</button>;
};
3) Request records for a program:
import { WalletNotConnectedError } from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const RequestRecords: FC = () => {
const { publicKey, requestRecords } = useWallet();
const onClick = async () => {
const program = "credits.aleo";
if (!publicKey) throw new WalletNotConnectedError();
if (requestRecords) {
const records = await requestRecords(program);
console.log("Records:", records);
}
};
return <button onClick={onClick} disabled={!publicKey}>Request Records</button>;
};
4) Build and send a transaction:
import {
Transaction,
WalletAdapterNetwork,
WalletNotConnectedError
} from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const RequestTransaction: FC = () => {
const { publicKey, requestTransaction, transactionStatus, transitionViewKeys } = useWallet();
const onClick = async () => {
if (!publicKey) throw new WalletNotConnectedError();
// Use one of your unspent records from RequestRecords
const record = `'{"id":"0f27d86a-1026-4980-9816-bcdce7569aa4","program_id":"credits.aleo","microcredits":"200000","spent":false,"data":{}}'`;
const amount = 1; // example
const inputs = [JSON.parse(record), "aleo1kf3dgrz9...", `${amount}u64`];
const fee = 35_000;
const aleoTransaction = Transaction.createTransaction(
publicKey,
WalletAdapterNetwork.Testnet,
"credits.aleo",
"transfer",
inputs,
fee
);
if (requestTransaction) {
const txId = await requestTransaction(aleoTransaction);
const status = await transactionStatus(txId);
const tvks = await transitionViewKeys(txId);
console.log({ txId, status, tvks });
}
};
return <button onClick={onClick} disabled={!publicKey}>Request Transaction</button>;
};
5) Deploy a program:
import {
Deployment,
WalletAdapterNetwork,
WalletNotConnectedError
} from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const DeployProgram: FC = () => {
const { publicKey, requestDeploy } = useWallet();
const onClick = async () => {
if (!publicKey) throw new WalletNotConnectedError();
const program = `
program hello.aleo;
function main:
input r0 as u32.public;
input r1 as u32.private;
add r0 r1 into r2;
output r2 as u32.private;
`;
const fee = 4_835_000;
const aleoDeployment = new Deployment(
publicKey,
WalletAdapterNetwork.Testnet,
program,
fee
);
if (requestDeploy) {
await requestDeploy(aleoDeployment);
}
};
return <button onClick={onClick} disabled={!publicKey}>Deploy Program</button>;
};
6) Request record plaintexts:
Requires OnChainHistory permission.
import { WalletNotConnectedError } from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const RequestRecordPlaintexts: FC = () => {
const { publicKey, requestRecordPlaintexts } = useWallet();
const onClick = async () => {
const program = "credits.aleo";
if (!publicKey) throw new WalletNotConnectedError();
if (requestRecordPlaintexts) {
const records = await requestRecordPlaintexts(program);
console.log("Record plaintexts:", records);
}
};
return <button onClick={onClick} disabled={!publicKey}>Request Record Plaintexts</button>;
};
7) Request on-chain transaction history:
Also requires OnChainHistory permission.
import { WalletNotConnectedError } from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const RequestTxHistory: FC = () => {
const { publicKey, requestTransactionHistory } = useWallet();
const onClick = async () => {
const program = "credits.aleo";
if (!publicKey) throw new WalletNotConnectedError();
if (requestTransactionHistory) {
const txs = await requestTransactionHistory(program);
console.log("Transactions:", txs);
}
};
return <button onClick={onClick} disabled={!publicKey}>Request Tx History</button>;
};
8) Subscribe to wallet events:
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import { LeoWalletAdapter } from "@demox-labs/aleo-wallet-adapter-leo";
import React, { FC, useEffect, useCallback } from "react";
export const SubscribeToEvent: FC = () => {
const { wallet } = useWallet();
const handleAccountChange = useCallback(() => {
// reconnect or refresh state
}, [wallet]);
useEffect(() => {
(wallet?.adapter as LeoWalletAdapter)?.on("accountChange", handleAccountChange);
return () => {
(wallet?.adapter as LeoWalletAdapter)?.off("accountChange", handleAccountChange);
};
}, [wallet, handleAccountChange]);
return null;
};
https://docs.leo.app/aleo-wallet-adapter#signing
Universal Wallet Adapter: support many wallets with one setup
Most apps want to support multiple wallets. The community Universal Wallet Adapter builds on Demox Labs’ work and adds Puzzle, Fox, and Soter in one place.
1) Install:
npm install --save \
@demox-labs/aleo-wallet-adapter-base \
@demox-labs/aleo-wallet-adapter-react \
@demox-labs/aleo-wallet-adapter-reactui \
aleo-adapters
2) Configure providers:
import { WalletModalProvider } from "@demox-labs/aleo-wallet-adapter-reactui";
import { WalletProvider } from "@demox-labs/aleo-wallet-adapter-react";
import { DecryptPermission, WalletAdapterNetwork } from "@demox-labs/aleo-wallet-adapter-base";
import { useMemo } from "react";
import {
PuzzleWalletAdapter,
LeoWalletAdapter,
FoxWalletAdapter,
SoterWalletAdapter
} from "aleo-adapters";
export default function Providers({ children }: { children: React.ReactNode }) {
const wallets = useMemo(
() => [
new LeoWalletAdapter({ appName: "Aleo app" }),
new PuzzleWalletAdapter({
programIdPermissions: {
[WalletAdapterNetwork.MainnetBeta]: ["dApp_1.aleo", "dApp_1_import.aleo", "dApp_1_import_2.aleo"],
[WalletAdapterNetwork.TestnetBeta]: ["dApp_1_test.aleo", "dApp_1_test_import.aleo", "dApp_1_test_import_2.aleo"]
},
appName: "Aleo app",
appDescription: "A privacy-focused DeFi app",
appIconUrl: ""
}),
new FoxWalletAdapter({ appName: "Aleo app" }),
new SoterWalletAdapter({ appName: "Aleo app" })
],
[]
);
return (
<WalletProvider
wallets={wallets}
decryptPermission={DecryptPermission.UponRequest}
network={WalletAdapterNetwork.MainnetBeta}
autoConnect
>
<WalletModalProvider>{children}</WalletModalProvider>
</WalletProvider>
);
}
3) Add a ready-made connect button:
import { WalletMultiButton } from "@demox-labs/aleo-wallet-adapter-reactui";
import "@demox-labs/aleo-wallet-adapter-reactui/dist/styles.css";
export default function WalletButton() {
return <WalletMultiButton />;
}
4) Use the hook:
export interface WalletContextState {
autoConnect: boolean;
wallets: Wallet[];
wallet: Wallet | null;
publicKey: string | null;
connecting: boolean;
connected: boolean;
disconnecting: boolean;
select(walletName: WalletName): void;
connect(decryptPermission: DecryptPermission, network: WalletAdapterNetwork, programs?: string[]): Promise<void>;
disconnect(): Promise<void>;
signMessage(message: Uint8Array): Promise<Uint8Array>;
decrypt(cipherText: string, tpk?: string, programId?: string, functionName?: string, index?: number): Promise<string>;
requestRecords(program: string): Promise<any[]>;
requestTransaction(transaction: AleoTransaction): Promise<string>;
requestExecution(transaction: AleoTransaction): Promise<string>;
requestBulkTransactions(transactions: AleoTransaction[]): Promise<string[]>;
requestDeploy(deployment: AleoDeployment): Promise<string>;
transactionStatus(transactionId: string): Promise<string>;
getExecution(transactionId: string): Promise<string>;
requestRecordPlaintexts(program: string): Promise<any[]>;
}
https://developer.aleo.org/guides/wallets/universal_wallet_adapter
Example: use the Universal Adapter with token_registry.aleo
1) Create a new app:
npm create leo-app@latest
2) Install adapters:
npm install --save \
@demox-labs/aleo-wallet-adapter-base \
@demox-labs/aleo-wallet-adapter-react \
@demox-labs/aleo-wallet-adapter-reactui \
aleo-adapters
3) Wrap providers in src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { WalletModalProvider } from "@demox-labs/aleo-wallet-adapter-reactui";
import { WalletProvider } from "@demox-labs/aleo-wallet-adapter-react";
import { DecryptPermission, WalletAdapterNetwork } from "@demox-labs/aleo-wallet-adapter-base";
import { useMemo } from "react";
import {
PuzzleWalletAdapter,
LeoWalletAdapter,
FoxWalletAdapter,
SoterWalletAdapter
} from "aleo-adapters";
const Root = () => {
const wallets = useMemo(
() => [
new LeoWalletAdapter({ appName: "Aleo app" }),
new PuzzleWalletAdapter({
programIdPermissions: {
[WalletAdapterNetwork.TestnetBeta]: ["token_registry.aleo"]
},
appName: "Aleo app",
appDescription: "A privacy-focused DeFi app",
appIconUrl: ""
}),
new FoxWalletAdapter({ appName: "Aleo app" }),
new SoterWalletAdapter({ appName: "Aleo app" })
],
[]
);
return (
<React.StrictMode>
<WalletProvider
wallets={wallets}
network={WalletAdapterNetwork.TestnetBeta}
decryptPermission={DecryptPermission.UponRequest}
autoConnect
>
<WalletModalProvider>
<App />
</WalletModalProvider>
</WalletProvider>
</React.StrictMode>
);
};
ReactDOM.createRoot(document.getElementById("root")!).render(<Root />);
4) Add the wallet button in src/App.tsx:
import { WalletMultiButton } from "@demox-labs/aleo-wallet-adapter-reactui";
import "@demox-labs/aleo-wallet-adapter-reactui/dist/styles.css";
return (
<>
<div style={{ position: "absolute", top: 20, right: 20 }}>
<WalletMultiButton />
</div>
{/* your UI */}
</>
);
5) Request a transaction to register a token:
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
const { publicKey, requestTransaction } = useWallet();
const result = await requestTransaction({
address: publicKey || "",
chainId: "testnetbeta",
transitions: [
{
program: "token_registry.aleo",
functionName: "register_token",
inputs: [
"12736872field", // token_name
"1273687u128", // token_symbol
"1273687u128", // token_decimals
"6u8", // token_type
"1000000000u128", // token_supply
"false", // external_authorization_required
"aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc" // external party
]
}
],
fee: 100000,
feePrivate: false
});
console.log(result);
6) Mint a private token record:
const { requestTransaction } = useWallet();
const result = await requestTransaction({
address: publicKey || "",
chainId: "testnetbeta",
transitions: [
{
program: "token_registry.aleo",
functionName: "mint_private",
inputs: [
"12736872field", // token_name
receivingAddress, // receiving_address
"1000000u128", // token_amount
"false", // external_authorization_required
"0u32" // authorized_until if external auth is true
]
}
],
fee: 100000,
feePrivate: false
});
console.log(result);
7) Fetch and display unspent records:
const { requestRecordPlaintexts } = useWallet();
async function requestRecord() {
if (!requestRecordPlaintexts) {
alert("No wallet connected");
return;
}
const records = await requestRecordPlaintexts("token_registry.aleo");
const unspent = records.filter(r => !r.spent);
if (unspent.length) {
unspent.forEach((r, i) => console.log(`Record ${i + 1}:`, r.plaintext));
} else {
console.log("No unspent records found");
}
}
A full working App.tsx example with buttons for register, mint, and fetch is included in your source. Use it as a scaffold for your UI.
More info you can find here:
https://developer.aleo.org/guides/wallets/usage_example/
Production tips:
Set permissions explicitly. Decrypt and OnChainHistory require user permission. Ask only when needed.
Fee strategy. Expose a setting for public or private fees and dynamic fee ceilings for reliability.
Error handling. Surface wallet errors with user friendly messages. Retry transient failures.
Program IDs and networks. Keep them in config files per environment to avoid hardcoding.
Records UX. Show both mappings and record plaintexts. Make “unspent vs spent” obvious.
Conclusion. Aleo gives you privacy by design. With these adapters, you can give users a clean wallet experience without fighting boilerplate. Start simple with Leo Wallet. Add the Universal Wallet Adapter when you want broader coverage. From signing and decrypting to sending private transactions and deploying programs, the APIs are straightforward and production ready.
To know more about Aleo, join now!
Aleo Twitter
Aleo Discord
Aleo Website
List of Aleo and Leo code and resourses
Prepared by Colliseum
Hi! My name is Heorhii. If you are building on Aleo, you already know the magic is privacy by default. The next step is giving users a smooth way to connect a wallet, sign messages, decrypt records, and send private transactions. In this guide I’ll show you how to wire up modular TypeScript wallet adapters in a React app, then level up to a universal adapter that supports multiple wallets out of the box. We will finish with a hands-on example using the token_registry.aleo program.
Everything here is developer focused. Copy the snippets, tweak the values, ship your app.
What we are integrating:
Demox Labs Wallet Adapters for React and React UI
Leo Wallet adapter and a Universal Wallet Adapter that adds Puzzle, Fox, and Soter
Hooks to sign messages, decrypt, request records, send transactions, deploy programs, and subscribe to events
Why Wallet Adapters matter. Before diving into the setup, it’s important to understand why wallet adapters are essential for Aleo developers and how they simplify building privacy-focused applications.
In simple terms, wallet adapters act as a compatibility layer between your dApp and multiple Aleo wallets. They provide a single, consistent API for connecting, signing, decrypting, and sending transactions without requiring separate integrations for each wallet.
Here’s why they are so valuable:
Unified interface for all wallets. You get one standard set of methods such as connect, signMessage, requestTransaction, and decrypt that works across Leo, Puzzle, Fox, and Soter wallets.
Better user experience. Users can choose their preferred wallet while your app logic stays the same. There is no need for extra conditional code or wallet-specific flows.
No vendor lock-in. Wallets can be added or removed through configuration without rewriting core logic.
Improved security and clarity. Adapters keep responsibilities clear: your dApp prepares the intent and the wallet authorizes it. This structure supports stronger security practices.
Consistent permission handling. Permissions such as Decrypt or OnChainHistory are handled the same way in every wallet, keeping behavior predictable.
Stable and maintainable. When a wallet SDK updates, the adapter absorbs most of the changes so your app continues to work.
Reusable and scalable. The same wallet provider instance can serve multiple React components, which keeps large applications clean and efficient.
Faster development. Ready-made UI components like WalletMultiButton and unified TypeScript types help you move quickly from setup to production.
Easier testing. Adapters can be mocked for testing, making it possible to run integration tests without using a real wallet.
Centralized error handling. A single onError hook and consistent transaction status tracking simplify debugging and monitoring.
Flexible privacy and fee settings. Adapters support both public and private fee modes, allowing you to control transaction behavior for different scenarios.
Seamless ecosystem integration. They work naturally with tools like create-leo-app, React frameworks, and Aleo UI kits so you can focus on building your app instead of managing wallet integrations.
https://developer.aleo.org/category/wallets
Quick start with React UI:
1) Install:
npm install --save \
@demox-labs/aleo-wallet-adapter-base \
@demox-labs/aleo-wallet-adapter-react \
@demox-labs/aleo-wallet-adapter-reactui \
@demox-labs/aleo-wallet-adapter-leo \
react
2) Wrap your app:
import React, { FC, useMemo } from "react";
import { WalletProvider } from "@demox-labs/aleo-wallet-adapter-react";
import { WalletModalProvider } from "@demox-labs/aleo-wallet-adapter-reactui";
import { LeoWalletAdapter } from "@demox-labs/aleo-wallet-adapter-leo";
import {
DecryptPermission,
WalletAdapterNetwork,
} from "@demox-labs/aleo-wallet-adapter-base";
import "@demox-labs/aleo-wallet-adapter-reactui/styles.css";
export const Wallet: FC = () => {
const wallets = useMemo(
() => [new LeoWalletAdapter({ appName: "Leo Demo App" })],
[]
);
return (
<WalletProvider
wallets={wallets}
decryptPermission={DecryptPermission.UponRequest}
network={WalletAdapterNetwork.Localnet}
autoConnect
>
<WalletModalProvider>
{/* Your app here */}
</WalletModalProvider>
</WalletProvider>
);
};
Common flows you will need:
1) Sign a message:
import { WalletNotConnectedError } from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import { LeoWalletAdapter } from "@demox-labs/aleo-wallet-adapter-leo";
import React, { FC, useCallback } from "react";
export const SignMessage: FC = () => {
const { wallet, publicKey } = useWallet();
const onClick = useCallback(async () => {
if (!publicKey) throw new WalletNotConnectedError();
const message = "a message to sign";
const bytes = new TextEncoder().encode(message);
const signatureBytes = await (wallet?.adapter as LeoWalletAdapter).signMessage(bytes);
const signature = new TextDecoder().decode(signatureBytes);
alert("Signed message: " + signature);
}, [wallet, publicKey]);
return <button onClick={onClick} disabled={!publicKey}>Sign message</button>;
};
2) Decrypt a record payload:
import { WalletNotConnectedError } from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const DecryptMessage: FC = () => {
const { publicKey, decrypt } = useWallet();
const onClick = async () => {
const cipherText = "record....";
if (!publicKey) throw new WalletNotConnectedError();
if (decrypt) {
const decryptedPayload = await decrypt(cipherText);
alert("Decrypted payload: " + decryptedPayload);
}
};
return <button onClick={onClick} disabled={!publicKey}>Decrypt message</button>;
};
3) Request records for a program:
import { WalletNotConnectedError } from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const RequestRecords: FC = () => {
const { publicKey, requestRecords } = useWallet();
const onClick = async () => {
const program = "credits.aleo";
if (!publicKey) throw new WalletNotConnectedError();
if (requestRecords) {
const records = await requestRecords(program);
console.log("Records:", records);
}
};
return <button onClick={onClick} disabled={!publicKey}>Request Records</button>;
};
4) Build and send a transaction:
import {
Transaction,
WalletAdapterNetwork,
WalletNotConnectedError
} from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const RequestTransaction: FC = () => {
const { publicKey, requestTransaction, transactionStatus, transitionViewKeys } = useWallet();
const onClick = async () => {
if (!publicKey) throw new WalletNotConnectedError();
// Use one of your unspent records from RequestRecords
const record = `'{"id":"0f27d86a-1026-4980-9816-bcdce7569aa4","program_id":"credits.aleo","microcredits":"200000","spent":false,"data":{}}'`;
const amount = 1; // example
const inputs = [JSON.parse(record), "aleo1kf3dgrz9...", `${amount}u64`];
const fee = 35_000;
const aleoTransaction = Transaction.createTransaction(
publicKey,
WalletAdapterNetwork.Testnet,
"credits.aleo",
"transfer",
inputs,
fee
);
if (requestTransaction) {
const txId = await requestTransaction(aleoTransaction);
const status = await transactionStatus(txId);
const tvks = await transitionViewKeys(txId);
console.log({ txId, status, tvks });
}
};
return <button onClick={onClick} disabled={!publicKey}>Request Transaction</button>;
};
5) Deploy a program:
import {
Deployment,
WalletAdapterNetwork,
WalletNotConnectedError
} from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const DeployProgram: FC = () => {
const { publicKey, requestDeploy } = useWallet();
const onClick = async () => {
if (!publicKey) throw new WalletNotConnectedError();
const program = `
program hello.aleo;
function main:
input r0 as u32.public;
input r1 as u32.private;
add r0 r1 into r2;
output r2 as u32.private;
`;
const fee = 4_835_000;
const aleoDeployment = new Deployment(
publicKey,
WalletAdapterNetwork.Testnet,
program,
fee
);
if (requestDeploy) {
await requestDeploy(aleoDeployment);
}
};
return <button onClick={onClick} disabled={!publicKey}>Deploy Program</button>;
};
6) Request record plaintexts:
Requires OnChainHistory permission.
import { WalletNotConnectedError } from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const RequestRecordPlaintexts: FC = () => {
const { publicKey, requestRecordPlaintexts } = useWallet();
const onClick = async () => {
const program = "credits.aleo";
if (!publicKey) throw new WalletNotConnectedError();
if (requestRecordPlaintexts) {
const records = await requestRecordPlaintexts(program);
console.log("Record plaintexts:", records);
}
};
return <button onClick={onClick} disabled={!publicKey}>Request Record Plaintexts</button>;
};
7) Request on-chain transaction history:
Also requires OnChainHistory permission.
import { WalletNotConnectedError } from "@demox-labs/aleo-wallet-adapter-base";
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import React, { FC } from "react";
export const RequestTxHistory: FC = () => {
const { publicKey, requestTransactionHistory } = useWallet();
const onClick = async () => {
const program = "credits.aleo";
if (!publicKey) throw new WalletNotConnectedError();
if (requestTransactionHistory) {
const txs = await requestTransactionHistory(program);
console.log("Transactions:", txs);
}
};
return <button onClick={onClick} disabled={!publicKey}>Request Tx History</button>;
};
8) Subscribe to wallet events:
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
import { LeoWalletAdapter } from "@demox-labs/aleo-wallet-adapter-leo";
import React, { FC, useEffect, useCallback } from "react";
export const SubscribeToEvent: FC = () => {
const { wallet } = useWallet();
const handleAccountChange = useCallback(() => {
// reconnect or refresh state
}, [wallet]);
useEffect(() => {
(wallet?.adapter as LeoWalletAdapter)?.on("accountChange", handleAccountChange);
return () => {
(wallet?.adapter as LeoWalletAdapter)?.off("accountChange", handleAccountChange);
};
}, [wallet, handleAccountChange]);
return null;
};
https://docs.leo.app/aleo-wallet-adapter#signing
Universal Wallet Adapter: support many wallets with one setup
Most apps want to support multiple wallets. The community Universal Wallet Adapter builds on Demox Labs’ work and adds Puzzle, Fox, and Soter in one place.
1) Install:
npm install --save \
@demox-labs/aleo-wallet-adapter-base \
@demox-labs/aleo-wallet-adapter-react \
@demox-labs/aleo-wallet-adapter-reactui \
aleo-adapters
2) Configure providers:
import { WalletModalProvider } from "@demox-labs/aleo-wallet-adapter-reactui";
import { WalletProvider } from "@demox-labs/aleo-wallet-adapter-react";
import { DecryptPermission, WalletAdapterNetwork } from "@demox-labs/aleo-wallet-adapter-base";
import { useMemo } from "react";
import {
PuzzleWalletAdapter,
LeoWalletAdapter,
FoxWalletAdapter,
SoterWalletAdapter
} from "aleo-adapters";
export default function Providers({ children }: { children: React.ReactNode }) {
const wallets = useMemo(
() => [
new LeoWalletAdapter({ appName: "Aleo app" }),
new PuzzleWalletAdapter({
programIdPermissions: {
[WalletAdapterNetwork.MainnetBeta]: ["dApp_1.aleo", "dApp_1_import.aleo", "dApp_1_import_2.aleo"],
[WalletAdapterNetwork.TestnetBeta]: ["dApp_1_test.aleo", "dApp_1_test_import.aleo", "dApp_1_test_import_2.aleo"]
},
appName: "Aleo app",
appDescription: "A privacy-focused DeFi app",
appIconUrl: ""
}),
new FoxWalletAdapter({ appName: "Aleo app" }),
new SoterWalletAdapter({ appName: "Aleo app" })
],
[]
);
return (
<WalletProvider
wallets={wallets}
decryptPermission={DecryptPermission.UponRequest}
network={WalletAdapterNetwork.MainnetBeta}
autoConnect
>
<WalletModalProvider>{children}</WalletModalProvider>
</WalletProvider>
);
}
3) Add a ready-made connect button:
import { WalletMultiButton } from "@demox-labs/aleo-wallet-adapter-reactui";
import "@demox-labs/aleo-wallet-adapter-reactui/dist/styles.css";
export default function WalletButton() {
return <WalletMultiButton />;
}
4) Use the hook:
export interface WalletContextState {
autoConnect: boolean;
wallets: Wallet[];
wallet: Wallet | null;
publicKey: string | null;
connecting: boolean;
connected: boolean;
disconnecting: boolean;
select(walletName: WalletName): void;
connect(decryptPermission: DecryptPermission, network: WalletAdapterNetwork, programs?: string[]): Promise<void>;
disconnect(): Promise<void>;
signMessage(message: Uint8Array): Promise<Uint8Array>;
decrypt(cipherText: string, tpk?: string, programId?: string, functionName?: string, index?: number): Promise<string>;
requestRecords(program: string): Promise<any[]>;
requestTransaction(transaction: AleoTransaction): Promise<string>;
requestExecution(transaction: AleoTransaction): Promise<string>;
requestBulkTransactions(transactions: AleoTransaction[]): Promise<string[]>;
requestDeploy(deployment: AleoDeployment): Promise<string>;
transactionStatus(transactionId: string): Promise<string>;
getExecution(transactionId: string): Promise<string>;
requestRecordPlaintexts(program: string): Promise<any[]>;
}
https://developer.aleo.org/guides/wallets/universal_wallet_adapter
Example: use the Universal Adapter with token_registry.aleo
1) Create a new app:
npm create leo-app@latest
2) Install adapters:
npm install --save \
@demox-labs/aleo-wallet-adapter-base \
@demox-labs/aleo-wallet-adapter-react \
@demox-labs/aleo-wallet-adapter-reactui \
aleo-adapters
3) Wrap providers in src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { WalletModalProvider } from "@demox-labs/aleo-wallet-adapter-reactui";
import { WalletProvider } from "@demox-labs/aleo-wallet-adapter-react";
import { DecryptPermission, WalletAdapterNetwork } from "@demox-labs/aleo-wallet-adapter-base";
import { useMemo } from "react";
import {
PuzzleWalletAdapter,
LeoWalletAdapter,
FoxWalletAdapter,
SoterWalletAdapter
} from "aleo-adapters";
const Root = () => {
const wallets = useMemo(
() => [
new LeoWalletAdapter({ appName: "Aleo app" }),
new PuzzleWalletAdapter({
programIdPermissions: {
[WalletAdapterNetwork.TestnetBeta]: ["token_registry.aleo"]
},
appName: "Aleo app",
appDescription: "A privacy-focused DeFi app",
appIconUrl: ""
}),
new FoxWalletAdapter({ appName: "Aleo app" }),
new SoterWalletAdapter({ appName: "Aleo app" })
],
[]
);
return (
<React.StrictMode>
<WalletProvider
wallets={wallets}
network={WalletAdapterNetwork.TestnetBeta}
decryptPermission={DecryptPermission.UponRequest}
autoConnect
>
<WalletModalProvider>
<App />
</WalletModalProvider>
</WalletProvider>
</React.StrictMode>
);
};
ReactDOM.createRoot(document.getElementById("root")!).render(<Root />);
4) Add the wallet button in src/App.tsx:
import { WalletMultiButton } from "@demox-labs/aleo-wallet-adapter-reactui";
import "@demox-labs/aleo-wallet-adapter-reactui/dist/styles.css";
return (
<>
<div style={{ position: "absolute", top: 20, right: 20 }}>
<WalletMultiButton />
</div>
{/* your UI */}
</>
);
5) Request a transaction to register a token:
import { useWallet } from "@demox-labs/aleo-wallet-adapter-react";
const { publicKey, requestTransaction } = useWallet();
const result = await requestTransaction({
address: publicKey || "",
chainId: "testnetbeta",
transitions: [
{
program: "token_registry.aleo",
functionName: "register_token",
inputs: [
"12736872field", // token_name
"1273687u128", // token_symbol
"1273687u128", // token_decimals
"6u8", // token_type
"1000000000u128", // token_supply
"false", // external_authorization_required
"aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3ljyzc" // external party
]
}
],
fee: 100000,
feePrivate: false
});
console.log(result);
6) Mint a private token record:
const { requestTransaction } = useWallet();
const result = await requestTransaction({
address: publicKey || "",
chainId: "testnetbeta",
transitions: [
{
program: "token_registry.aleo",
functionName: "mint_private",
inputs: [
"12736872field", // token_name
receivingAddress, // receiving_address
"1000000u128", // token_amount
"false", // external_authorization_required
"0u32" // authorized_until if external auth is true
]
}
],
fee: 100000,
feePrivate: false
});
console.log(result);
7) Fetch and display unspent records:
const { requestRecordPlaintexts } = useWallet();
async function requestRecord() {
if (!requestRecordPlaintexts) {
alert("No wallet connected");
return;
}
const records = await requestRecordPlaintexts("token_registry.aleo");
const unspent = records.filter(r => !r.spent);
if (unspent.length) {
unspent.forEach((r, i) => console.log(`Record ${i + 1}:`, r.plaintext));
} else {
console.log("No unspent records found");
}
}
A full working App.tsx example with buttons for register, mint, and fetch is included in your source. Use it as a scaffold for your UI.
More info you can find here:
https://developer.aleo.org/guides/wallets/usage_example/
Production tips:
Set permissions explicitly. Decrypt and OnChainHistory require user permission. Ask only when needed.
Fee strategy. Expose a setting for public or private fees and dynamic fee ceilings for reliability.
Error handling. Surface wallet errors with user friendly messages. Retry transient failures.
Program IDs and networks. Keep them in config files per environment to avoid hardcoding.
Records UX. Show both mappings and record plaintexts. Make “unspent vs spent” obvious.
Conclusion. Aleo gives you privacy by design. With these adapters, you can give users a clean wallet experience without fighting boilerplate. Start simple with Leo Wallet. Add the Universal Wallet Adapter when you want broader coverage. From signing and decrypting to sending private transactions and deploying programs, the APIs are straightforward and production ready.
To know more about Aleo, join now!
Aleo Twitter
Aleo Discord
Aleo Website
List of Aleo and Leo code and resourses
Prepared by Colliseum
Share Dialog
Share Dialog
No activity yet