> ## Documentation Index
> Fetch the complete documentation index at: https://www.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](/docs/javascript/reference/client/create-dynamic-client), [Initializing the Dynamic Client](/docs/javascript/reference/client/initialize-dynamic-client)).
</Note>

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 via MPC. The private key never leaves Dynamic's wallet service.
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

<Info>
  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.
</Info>

### Install dependencies

<CodeGroup>
  ```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
  ```
</CodeGroup>

`@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.

<Tabs>
  <Tab title="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>

  <Tab title="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 };
    }
    ```
  </Tab>
</Tabs>

## 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.

<Tabs>
  <Tab title="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>

  <Tab title="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>;
    }
    ```
  </Tab>
</Tabs>

## 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.
</Warning>

## 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...`).

<Tabs>
  <Tab title="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>

  <Tab title="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>}
        </>
      );
    }
    ```
  </Tab>
</Tabs>

## 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

<Info>
  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.
</Info>

## 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](/docs/javascript/building-ui/token-balances-display) - reading balances across chains
* [React Quickstart (JS SDK)](/docs/javascript/reference/react-quickstart) - 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
