> ## 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. It's [MPC-backed](/docs/overview/wallets/embedded-wallets/mpc/overview), 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

<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
