> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Private Payments on Aleo

> Send private ALEO credits between embedded wallets using the JavaScript SDK.

> [!NOTE]
>
> Setup examples use React with `@dynamic-labs-sdk/react-hooks`, but the Aleo calls (`requestRecords`, `requestTransaction`) live on the wallet provider from `@dynamic-labs-sdk/aleo` and work in any framework. Before this: create and initialize a Dynamic client (see [Creating a Dynamic Client](https://docs.dynamic.xyz/docs/javascript/reference/client/create-dynamic-client.md), [Initializing the Dynamic Client](https://docs.dynamic.xyz/docs/javascript/reference/client/initialize-dynamic-client.md)).

Aleo is a privacy-first L1 where balances live inside encrypted **records** rather than public account state. A private transfer consumes one record and produces two new ones (one for the recipient, one returning the change to the sender), without revealing the sender, recipient, or amount on-chain.

On Aleo there is no one-call transfer helper. You pick one of the wallet's own records, then submit a `credits.aleo/transfer_private` transition through `requestTransaction`. The embedded wallet proves and broadcasts it for you.

## How it works

1. User logs in with email OTP (or any auth method you configure in the dashboard).
2. An embedded Aleo wallet is created automatically. It's [MPC-backed](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/overview.md), not a single private key held by one party.
3. Your app lists the records the wallet owns and picks one that covers the amount.
4. `requestTransaction()` sends the transition to the wallet service, which generates the zero-knowledge proof and broadcasts the transaction.

Proving is slower than a normal signed transaction, so the call takes several seconds. Surface a loading state so users know it's working. The network fee is sponsored, so you do not pass a fee.

## Setup

### Dashboard configuration

> [!NOTE]
>
> In the Dynamic dashboard, enable **Aleo** under **Chains & Networks**, enable **Embedded wallets** under **Wallets**, and turn on automatic wallet creation so a wallet is provisioned the moment a user logs in. Skipping this step is the most common reason `useGetWalletAccounts()` returns no Aleo account. It's a dashboard setting, not a code issue.

### Install dependencies

```bash npm theme={"system"}
npm i @dynamic-labs-sdk/client @dynamic-labs-sdk/aleo @dynamic-labs-sdk/react-hooks @tanstack/react-query
```

```bash yarn theme={"system"}
yarn add @dynamic-labs-sdk/client @dynamic-labs-sdk/aleo @dynamic-labs-sdk/react-hooks @tanstack/react-query
```

```bash pnpm theme={"system"}
pnpm add @dynamic-labs-sdk/client @dynamic-labs-sdk/aleo @dynamic-labs-sdk/react-hooks @tanstack/react-query
```

`@tanstack/react-query` is a required peer dependency of `@dynamic-labs-sdk/react-hooks`. Every hook is built on TanStack Query.

### Environment variables

```env .env.local theme={"system"}
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=your-environment-id-here
```

Your environment ID is in the Dynamic dashboard under **Developer Settings → SDK & API Keys**.

### Initialize Dynamic

Create `src/lib/dynamicClient.ts`:

```typescript src/lib/dynamicClient.ts theme={"system"}
import { createDynamicClient } from "@dynamic-labs-sdk/client";
import { addWaasAleoExtension } from "@dynamic-labs-sdk/aleo/waas";

export const dynamicClient = createDynamicClient({
  environmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID!,
});

// Register the Aleo embedded wallet extension.
// For the Shield browser extension, use addAleoWalletStandardExtension()
// from '@dynamic-labs-sdk/aleo/walletStandard' instead. Private transfers
// on this page assume an embedded wallet.
addWaasAleoExtension();
```

### Wire the provider

```tsx src/app/providers.tsx theme={"system"}
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { DynamicProvider } from "@dynamic-labs-sdk/react-hooks";
import { dynamicClient } from "@/lib/dynamicClient";

const queryClient = new QueryClient();

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      <DynamicProvider client={dynamicClient}>
        {children}
      </DynamicProvider>
    </QueryClientProvider>
  );
}
```

### Auto-create wallets on login

Place this component inside `<DynamicProvider>` so an Aleo wallet is provisioned as soon as the user authenticates:

```tsx src/lib/WaasBootstrap.tsx theme={"system"}
import { useOnEvent } from "@dynamic-labs-sdk/react-hooks";
import {
  createWaasWalletAccounts,
  getChainsMissingWaasWalletAccounts,
} from "@dynamic-labs-sdk/client/waas";

export function WaasBootstrap() {
  useOnEvent({
    event: "userChanged",
    listener: async ({ user }) => {
      if (!user) return;
      const missing = getChainsMissingWaasWalletAccounts();
      if (missing.length > 0) {
        await createWaasWalletAccounts({ chains: missing });
      }
    },
  });
  return null;
}
```

## Step 1: Get the Aleo wallet and its provider

The Aleo methods live on the wallet provider, so you need both the account and the provider. `isAleoWalletAccount` picks the Aleo account out of a mixed-chain list, and `isAleoWalletProvider` narrows the provider to the Aleo surface.

> [tab] JavaScript
>
> ```typescript theme={"system"}
> import { getWalletAccounts } from "@dynamic-labs-sdk/client";
> import { getWalletProviderFromWalletAccount } from "@dynamic-labs-sdk/client/core";
> import { isAleoWalletAccount, isAleoWalletProvider } from "@dynamic-labs-sdk/aleo";
> import { dynamicClient } from "./lib/dynamicClient";
>
> export function getAleoWallet() {
>   const walletAccount = getWalletAccounts().find(isAleoWalletAccount);
>   if (!walletAccount) return undefined;
>
>   const walletProvider = getWalletProviderFromWalletAccount(
>     { walletAccount },
>     dynamicClient
>   );
>   if (!isAleoWalletProvider(walletProvider)) return undefined;
>
>   return { walletAccount, walletProvider };
> }
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import { useGetWalletAccounts, useDynamicClient } from "@dynamic-labs-sdk/react-hooks";
> import { getWalletProviderFromWalletAccount } from "@dynamic-labs-sdk/client/core";
> import { isAleoWalletAccount, isAleoWalletProvider } from "@dynamic-labs-sdk/aleo";
>
> export function useAleoWallet() {
>   const client = useDynamicClient();
>   const { data: accounts = [] } = useGetWalletAccounts();
>
>   const walletAccount = accounts.find(isAleoWalletAccount);
>   if (!walletAccount) return undefined;
>
>   const walletProvider = getWalletProviderFromWalletAccount(
>     { walletAccount },
>     client
>   );
>   if (!isAleoWalletProvider(walletProvider)) return undefined;
>
>   return { walletAccount, walletProvider };
> }
> ```

## Step 2: Read the public balance

`getNativeBalance` returns the wallet's **public** credits balance as a human-readable string (credits, not microcredits). Private records are not included: they are only visible to the wallet that owns them, which is what Step 3 reads.

> [tab] JavaScript
>
> ```typescript theme={"system"}
> import { getNativeBalance } from "@dynamic-labs-sdk/client";
> import type { AleoWalletAccount } from "@dynamic-labs-sdk/aleo";
>
> export async function getPublicAleoBalance(walletAccount: AleoWalletAccount) {
>   const { balance } = await getNativeBalance({ walletAccount });
>   return balance ?? "0";
> }
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import { useGetNativeBalance } from "@dynamic-labs-sdk/react-hooks";
> import type { AleoWalletAccount } from "@dynamic-labs-sdk/aleo";
>
> export function PublicAleoBalance({ walletAccount }: { walletAccount: AleoWalletAccount }) {
>   const { data, isLoading } = useGetNativeBalance({ walletAccount });
>
>   if (isLoading) return <span>Loading...</span>;
>   return <span>{data?.balance ?? "0"} ALEO public</span>;
> }
> ```

## Step 3: Find a record that covers the amount

`requestRecords` returns the `credits.aleo` records the wallet owns. Pass `plaintext: true` to get the decrypted record strings, which carry the `microcredits` amount you need to compare against.

A private transfer spends exactly one record, so you need a single record worth at least the amount you are sending.

```typescript src/lib/findAleoRecord.ts theme={"system"}
import type { AleoWalletAccount, AleoWalletProvider } from "@dynamic-labs-sdk/aleo";
import { MICROCREDITS_PER_CREDIT } from "@dynamic-labs-sdk/aleo";

const MICROCREDITS_PATTERN = /microcredits:\s*(\d+)u64/;

// Credits are decimal strings ("1.5"); the chain works in microcredits.
export function creditsToMicrocredits(credits: string): bigint {
  const [whole = "0", fraction = ""] = credits.split(".");
  const padded = fraction.padEnd(6, "0").slice(0, 6);
  return BigInt(whole) * BigInt(MICROCREDITS_PER_CREDIT) + BigInt(padded || "0");
}

export async function findRecordForAmount({
  amount,
  walletAccount,
  walletProvider,
}: {
  amount: string;
  walletAccount: AleoWalletAccount;
  walletProvider: AleoWalletProvider;
}): Promise<string | undefined> {
  const required = creditsToMicrocredits(amount);

  const { records } = (await walletProvider.requestRecords?.({
    options: { plaintext: true },
    program: "credits.aleo",
    walletAccount,
  })) ?? { records: [] };

  return records
    .filter((record): record is string => typeof record === "string")
    .find((record) => {
      const microcredits = MICROCREDITS_PATTERN.exec(record)?.[1];
      return microcredits !== undefined && BigInt(microcredits) >= required;
    });
}
```

> [!WARNING]
>
> `requestRecords` is optional on the Aleo wallet provider type, so call it with `?.` as above. It is implemented for embedded wallets; external wallets implement it only if the extension supports record enumeration.

## Step 4: Send the private transfer

Build a single `transfer_private` transition and pass it to `requestTransaction`. Supply `inputTypes` explicitly: the record and the private inputs cannot be inferred from their string values, and the wallet service needs the types to build the proving circuit.

`requestTransaction` returns the on-chain transaction id (`at1...`).

> [tab] JavaScript
>
> ```typescript theme={"system"}
> import type { AleoWalletAccount, AleoWalletProvider } from "@dynamic-labs-sdk/aleo";
>
> import { creditsToMicrocredits, findRecordForAmount } from "./findAleoRecord";
>
> export async function sendPrivateAleo({
>   amount,
>   recipient,
>   walletAccount,
>   walletProvider,
> }: {
>   amount: string;
>   recipient: string;
>   walletAccount: AleoWalletAccount;
>   walletProvider: AleoWalletProvider;
> }) {
>   const record = await findRecordForAmount({
>     amount,
>     walletAccount,
>     walletProvider,
>   });
>   if (!record) {
>     throw new Error("No single record covers this amount");
>   }
>
>   const { networkId: chainId } = await walletProvider.getActiveNetworkId();
>
>   const { transactionId } = await walletProvider.requestTransaction({
>     transaction: {
>       address: walletAccount.address,
>       chainId,
>       // Embedded wallet transactions are fee-sponsored, so these are ignored.
>       fee: 0,
>       feePrivate: false,
>       transitions: [
>         {
>           functionName: "transfer_private",
>           inputs: [
>             record,
>             recipient,
>             `${creditsToMicrocredits(amount)}u64`,
>           ],
>           inputTypes: ["credits.record", "address.private", "u64.private"],
>           program: "credits.aleo",
>         },
>       ],
>     },
>     walletAccount,
>   });
>
>   return transactionId;
> }
> ```

> [tab] React
>
> ```tsx src/components/SendAleoButton.tsx theme={"system"}
> import { useState } from "react";
> import { getAleoExplorerTxUrl } from "@dynamic-labs-sdk/aleo";
>
> import { useAleoWallet } from "@/lib/useAleoWallet";
> import { sendPrivateAleo } from "@/lib/sendPrivateAleo";
>
> export function SendAleoButton({
>   recipient,
>   amount,
> }: {
>   recipient: string;
>   amount: string;
> }) {
>   const aleoWallet = useAleoWallet();
>   const [explorerUrl, setExplorerUrl] = useState<string | null>(null);
>   const [pending, setPending] = useState(false);
>   const [error, setError] = useState<string | null>(null);
>
>   const handleSend = async () => {
>     if (!aleoWallet) return;
>     setPending(true);
>     setError(null);
>     try {
>       const transactionId = await sendPrivateAleo({
>         amount,
>         recipient,
>         ...aleoWallet,
>       });
>       const { networkId } = await aleoWallet.walletProvider.getActiveNetworkId();
>       setExplorerUrl(getAleoExplorerTxUrl({ networkId, txId: transactionId }));
>     } catch (e) {
>       setError(e instanceof Error ? e.message : "Transfer failed");
>     } finally {
>       setPending(false);
>     }
>   };
>
>   return (
>     <>
>       <button onClick={handleSend} disabled={!aleoWallet || pending}>
>         {pending ? "Proving..." : `Send ${amount} ALEO`}
>       </button>
>       {explorerUrl && (
>         <p>
>           Sent!{" "}
>           <a href={explorerUrl} target="_blank" rel="noopener noreferrer">
>             View on explorer
>           </a>
>         </p>
>       )}
>       {error && <p style={{ color: "red" }}>{error}</p>}
>     </>
>   );
> }
> ```

## Step 5: Link to the explorer

`getAleoExplorerTxUrl` builds the Provable explorer URL and validates the transaction id. Pass the active `networkId` so testnet transactions link to the testnet explorer (`0` is mainnet, `1` is testnet).

```typescript src/lib/getAleoTxLink.ts theme={"system"}
import { getAleoExplorerTxUrl } from "@dynamic-labs-sdk/aleo";
import type { AleoWalletProvider } from "@dynamic-labs-sdk/aleo";

export async function getAleoTxLink({
  transactionId,
  walletProvider,
}: {
  transactionId: string;
  walletProvider: AleoWalletProvider;
}) {
  const { networkId } = await walletProvider.getActiveNetworkId();
  return getAleoExplorerTxUrl({ networkId, txId: transactionId });
}
```

## Current limits

> [!NOTE]
>
> On embedded Aleo wallets today:
>
> * One transition per transaction. Batching several transitions atomically is not exposed yet.
> * `signMessage` and `decrypt` throw `AleoFeatureUnsupportedError`. Aleo's MPC signer has no arbitrary-message primitive, and the view key stays inside the wallet service.
> * There is no record merging in the JavaScript SDK, so a payment is capped by the largest single record the wallet owns.
> * The chain-agnostic `transferAmount` helper is not implemented for Aleo yet, so it throws for an Aleo wallet account. Use `requestTransaction` as shown above. When Aleo support lands, `transferAmount` becomes the one-call path for public transfers; private transfers still need a record, so Steps 3 and 4 stay relevant.

## Common issues

| Symptom | Cause |
| - | - |
| No Aleo account in `useGetWalletAccounts()` | Aleo or embedded wallets are off in the dashboard, or automatic wallet creation is not enabled under Chains & Networks. |
| `requestRecords` returns an empty list | The wallet holds no private records yet. Fund it and move a public balance into a record first. |
| "No single record covers this amount" | The balance is split across small records. Send a smaller amount, or receive a larger single record. |
| `transferAmount` throws for an Aleo account | Not supported on Aleo. Use `requestTransaction` with a `credits.aleo` transition. |
| The transfer takes several seconds | Expected. Proof generation is compute-intensive. Show a loading indicator while the proof builds. |

## See also

* [Token Balances & Display](https://docs.dynamic.xyz/docs/javascript/building-ui/token-balances-display.md) - reading balances across chains
* [React Quickstart (JS SDK)](https://docs.dynamic.xyz/docs/javascript/reference/react-quickstart.md) - general JS SDK setup
* [Aleo developer documentation](https://developer.aleo.org) - the `credits.aleo` program and record model
* [Provable explorer](https://explorer.provable.com) - inspect broadcast transactions


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.