# Wallet & exchange integration

This guide is for wallet developers, dApp developers, and exchanges who want to support confidential tokens on the Zama Protocol. It covers ERC-7984 wallet flows (showing decrypted balances, sending transfers with encrypted inputs), the Confidential Token Wrappers Registry, and wrapping/unwrapping between ERC-20 and ERC-7984.

By the end of this guide, you will be able to:

* Initialize the Zama SDK in a wallet, browser app, or backend.
* Display ERC-7984 confidential balances by user-decrypting on the user's behalf.
* Build ERC-7984 transfers using encrypted inputs.
* Discover wrapped token pairs via the Wrappers Registry.
* Implement wrap and unwrap flows between ERC-20 and ERC-7984.

## Core concepts

While building support for [ERC-7984 confidential tokens](https://eips.ethereum.org/EIPS/eip-7984) you will encounter the following terminology. For a deeper architectural overview, see [Architecture](https://github.com/zama-ai/sdk/blob/main/concepts/architecture/README.md).

* **FHEVM** — Zama's library for computations on encrypted values. Encrypted values are represented on-chain as **ciphertext handles** (`bytes32`).
* **Host chain** — the EVM network your users connect to (e.g. Ethereum mainnet, Sepolia).
* **Gateway chain** — Zama's L3 chain that coordinates encryptions and decryptions.
* **Relayer** — off-chain service that registers encrypted inputs, coordinates decryptions, and returns results. Wallets and dApps talk to the Relayer via the Zama SDK.
* **ACL** — access control for ciphertext handles. Contracts grant per-address permissions so a user can read data they should have access to.
* **Native confidential token** — an ERC-7984 token where balances and transfer amounts are encrypted by default. Not derived from an underlying ERC-20.
* **Wrapped confidential token** — a standard ERC-20 wrapped into ERC-7984 form via a wrapper contract. The underlying ERC-20 is unchanged.
* **Confidential Token Wrappers Registry** — on-chain registry mapping ERC-20s to their ERC-7984 wrappers.

## Integration at a glance

You do **not** need to run FHE infrastructure to integrate. Wallets and exchanges interact with the protocol entirely through the Zama SDK:

1. Install and configure `@zama-fhe/sdk` (or `@zama-fhe/react-sdk` for React apps). See [Quick start](https://github.com/zama-ai/sdk/blob/main/tutorials/quick-start/README.md) for stack-by-stack setup.
2. Initialize a `ZamaSDK` instance with a relayer, signer, and storage. See the [`ZamaSDK` reference](https://github.com/zama-ai/sdk/blob/main/reference/sdk/ZamaSDK/README.md).
3. For each confidential token contract, build a `Token` handle via `sdk.createToken(address)`.
4. Read encrypted balances, build transfers, and manage operators using the `Token` API or React hooks.

## What wallets and exchanges should support

* **Transfers**: Support the ERC-7984 transfer variants documented by OpenZeppelin, including forms that use an input proof and optional receiver callbacks. The SDK's [`Token.confidentialTransfer`](https://github.com/zama-ai/sdk/blob/main/reference/sdk/Token/README.md) and [`useConfidentialTransfer`](https://github.com/zama-ai/sdk/blob/main/reference/react/useConfidentialTransfer/README.md) handle the encrypted input pipeline for you. See [Transfer privately](https://github.com/zama-ai/sdk/blob/main/guides/transfer-privately/README.md).
* **Operators**: Operators can move any amount during an active window. UX must capture an expiry, show risk clearly, and make revoke easy. See [Operator approvals](https://github.com/zama-ai/sdk/blob/main/guides/operator-approvals/README.md).
* **Events and metadata**: Names and symbols behave like conventional ERC-20s, but on-chain amounts remain encrypted. Render user-specific amounts only after user-decrypting them.

## Display confidential balances

Balances are stored as ciphertext handles. To display one, the user authorizes the wallet's session via an EIP-712 signature, after which the SDK performs **user decryption** to obtain the cleartext value. The session signature is cached, so subsequent decryptions for authorized contracts complete without prompting.

{% hint style="warning" %}
**Don't trigger the first signature automatically.** Gate the initial EIP-712 prompt behind an explicit user action — a "View balance" or "Authorize" button — so users opt into the wallet popup instead of being surprised by it. Once the session is cached, balance reads in other components decrypt silently.
{% endhint %}

{% tabs %}
{% tab title="SDK" %}

```ts
import { ZamaSDK } from "@zama-fhe/sdk";

const sdk = new ZamaSDK({ relayer, signer, storage });
const token = sdk.createToken("0xConfidentialToken");

// First call prompts the wallet for an EIP-712 session signature;
// invoke it from a user action, not on app start.
const balance = await token.balanceOf(); // connected wallet
const peer = await token.balanceOf("0xUserAddr"); // explicit holder
```

{% endtab %}

{% tab title="React" %}

```tsx
import { useAllow, useIsAllowed, useConfidentialBalance } from "@zama-fhe/react-sdk";

const TOKEN = "0xConfidentialToken" as const;

function Balance() {
  const { mutate: allow, isPending: isAllowing } = useAllow();
  const { data: isAllowed } = useIsAllowed({ contractAddresses: [TOKEN] });

  const { data, isPending, error } = useConfidentialBalance(
    { tokenAddress: TOKEN },
    { enabled: !!isAllowed },
  );

  if (!isAllowed) {
    return (
      <button onClick={() => allow([TOKEN])} disabled={isAllowing}>
        {isAllowing ? "Signing…" : "View balance"}
      </button>
    );
  }
  if (isPending) return <span>Decrypting…</span>;
  if (error) return <span>{error.message}</span>;
  return <span>{data?.toString()}</span>;
}
```

{% endtab %}
{% endtabs %}

A common pattern is to call `useAllow` once when the user first connects (covering every confidential contract you'll touch), then read balances anywhere in the app without further prompts. Credentials persist in IndexedDB and survive page reloads. See [Encrypt & decrypt](https://github.com/zama-ai/sdk/blob/main/guides/encrypt-decrypt/README.md) for the full pre-authorization pattern, and [Check balances](https://github.com/zama-ai/sdk/blob/main/guides/check-balances/README.md) for batch decryption across multiple tokens.

## Send a confidential transfer

Amounts are encrypted client-side before submission. The SDK builds the input proof, registers it with the relayer, and submits the transaction.

{% tabs %}
{% tab title="SDK" %}

```ts
const { txHash, receipt } = await token.confidentialTransfer("0xRecipient", 500n);
```

{% endtab %}

{% tab title="React" %}

```tsx
import { useConfidentialTransfer } from "@zama-fhe/react-sdk";

const { mutate, isPending } = useConfidentialTransfer({ token: tokenAddress });
mutate({ to: "0xRecipient", amount: 500n });
```

{% endtab %}
{% endtabs %}

For operator transfers (`transferFrom`-style with delegated authority), see [`useConfidentialTransferFrom`](https://github.com/zama-ai/sdk/blob/main/reference/react/useConfidentialTransferFrom/README.md) and [Operator approvals](https://github.com/zama-ai/sdk/blob/main/guides/operator-approvals/README.md).

## Wrapping and unwrapping

Wrapped confidential tokens let users convert standard ERC-20s into ERC-7984 form. Once wrapped, balances and transfer amounts are encrypted on-chain. The underlying ERC-20 is unchanged and recoverable by unwrapping.

### Fungibility framing for exchanges

A wrapped confidential token should be treated as **fungible with its underlying ERC-20** from the user's perspective. A user who deposits USDT and a user who deposits cUSDT are depositing the same underlying asset; the exchange handles wrap/unwrap internally.

Common flows:

* **User deposits ERC-20** (e.g. USDT): exchange wraps to confidential form (cUSDT) if needed for on-chain operations.
* **User deposits confidential token** (e.g. cUSDT): no wrapping needed; credit the same underlying balance.
* **User withdraws as ERC-20**: exchange unwraps and sends standard ERC-20.
* **User withdraws as confidential token**: exchange sends the confidential token directly.

In all cases, the user sees a single unified balance for the underlying asset.

### Shield (wrap)

`Token.shield` wraps a standard ERC-20 into its ERC-7984 form. The SDK handles the underlying ERC-20 approval, the wrapper deposit, and decimal conversion. The encrypted balance lands in the recipient's address (defaulting to the connected wallet).

{% tabs %}
{% tab title="SDK" %}

```ts
// Exact-amount approval (default)
await token.shield(1000n);

// Custom recipient (e.g. exchange's hot wallet)
await token.shield(1000n, { to: "0xExchangeWallet" });

// Skip approval if you've already approved the wrapper
await token.shield(1000n, { approvalStrategy: "skip" });
```

{% endtab %}

{% tab title="React" %}

```tsx
import { useShield } from "@zama-fhe/react-sdk";

const { mutate, isPending } = useShield({ token: confidentialTokenAddress });
mutate({ amount: 1000n });
```

{% endtab %}
{% endtabs %}

See [Shield tokens](https://github.com/zama-ai/sdk/blob/main/guides/shield-tokens/README.md) for the full options surface, including custom approval strategies and progress callbacks.

### Unshield (unwrap)

Unwrapping is a **two-step asynchronous process** at the contract level: an unwrap request burns the encrypted amount, then a finalize call sends the cleartext amount of underlying ERC-20 once the gateway has publicly decrypted it. `Token.unshield` does both steps in one SDK call, including waiting for the decryption proof.

{% tabs %}
{% tab title="SDK" %}

```ts
const { txHash, receipt } = await token.unshield(500n);

// Track each phase for UI updates
await token.unshield(500n, {
  onUnwrapSubmitted: (h) => updateUI("Unwrap submitted…"),
  onFinalizing: () => updateUI("Waiting for decryption proof…"),
  onFinalizeSubmitted: (h) => updateUI("Unshield complete!"),
});

// Drain the entire balance
await token.unshieldAll();
```

{% endtab %}

{% tab title="React" %}

```tsx
import { useUnshield, useUnshieldAll } from "@zama-fhe/react-sdk";

const { mutate } = useUnshield({ token: confidentialTokenAddress });
mutate({ amount: 500n });
```

{% endtab %}
{% endtabs %}

If the user closes the page between unwrap and finalize, resume with `Token.resumeUnshield` / [`useResumeUnshield`](https://github.com/zama-ai/sdk/blob/main/reference/react/useResumeUnshield/README.md). See [Unshield tokens](https://github.com/zama-ai/sdk/blob/main/guides/unshield-tokens/README.md) for the full flow.

### Decimal conversion in your UI

Wrappers enforce a maximum of **6 decimals** on the confidential side. When wrapping a higher-precision underlying (e.g. 18-decimal tokens), amounts are rounded down and excess underlying is refunded to the caller.

| Underlying decimals | Wrapper decimals | Conversion rate | Effect                                  |
| ------------------- | ---------------- | --------------- | --------------------------------------- |
| 18                  | 6                | 10^12           | 1 wrapper unit = 10^12 underlying units |
| 6                   | 6                | 1               | 1:1                                     |
| 2                   | 2                | 1               | 1:1                                     |

Display balances in the underlying asset's decimals when possible — your users think in USDT, not cUSDT-with-6-decimals. The wrapper's `decimals()` and `rate()` views are exposed via the underlying contract for UI conversions.

## Discover wrapped tokens via the Registry

The Confidential Token Wrappers Registry is an on-chain contract that maps ERC-20s to their ERC-7984 wrappers. It's the canonical directory for wallets and exchanges to discover which underlying tokens have official confidential wrappers.

The SDK exposes it via `sdk.registry`. See the [`WrappersRegistry` reference](https://github.com/zama-ai/sdk/blob/main/reference/sdk/WrappersRegistry/README.md) for the full surface.

{% hint style="warning" %}
**Always check validity.** A non-zero wrapper address may have been revoked. Treat `isValid: false` as no wrapper for that token.
{% endhint %}

### Look up a wrapper for an ERC-20

{% tabs %}
{% tab title="SDK" %}

```ts
const result = await sdk.registry.getConfidentialToken("0xUSDC");
if (result?.isValid) {
  const cUsdc = sdk.createToken(result.confidentialTokenAddress);
}
```

{% endtab %}

{% tab title="React" %}

```tsx
import { useConfidentialTokenAddress } from "@zama-fhe/react-sdk";

const { data } = useConfidentialTokenAddress({ token: "0xUSDC" });
// data: { confidentialTokenAddress, isValid } | null
```

{% endtab %}
{% endtabs %}

### Reverse lookup (confidential → underlying)

{% tabs %}
{% tab title="SDK" %}

```ts
const result = await sdk.registry.getUnderlyingToken("0xConfidentialToken");
```

{% endtab %}

{% tab title="React" %}

```tsx
import { useTokenAddress } from "@zama-fhe/react-sdk";

const { data } = useTokenAddress({ token: confidentialTokenAddress });
```

{% endtab %}
{% endtabs %}

### List all registered pairs (paginated)

{% tabs %}
{% tab title="SDK" %}

```ts
const page = await sdk.registry.listPairs({
  page: 1,
  pageSize: 20,
  metadata: true, // include name/symbol/decimals/totalSupply
});
for (const pair of page.items) {
  console.log(pair.underlying.symbol, "→", pair.confidential.symbol);
}
```

{% endtab %}

{% tab title="React" %}

```tsx
import { useListPairs } from "@zama-fhe/react-sdk";

const { data } = useListPairs({ page: 1, pageSize: 20, metadata: true });
```

{% endtab %}
{% endtabs %}

### Currently registered tokens

The following wrapped confidential tokens are registered on Ethereum mainnet:

| Confidential token | Address                                                                                                                 |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| cUSDC              | [`0xe978F22157048E5DB8E5d07971376e86671672B2`](https://etherscan.io/address/0xe978F22157048E5DB8E5d07971376e86671672B2) |
| cUSDT              | [`0xAe0207C757Aa2B4019Ad96edD0092ddc63EF0c50`](https://etherscan.io/address/0xAe0207C757Aa2B4019Ad96edD0092ddc63EF0c50) |
| cWETH              | [`0xda9396b82634Ea99243cE51258B6A5Ae512D4893`](https://etherscan.io/address/0xda9396b82634Ea99243cE51258B6A5Ae512D4893) |
| cBRON              | [`0x85dE671c3bec1aDeD752c3Cea943521181C826bc`](https://etherscan.io/address/0x85dE671c3bec1aDeD752c3Cea943521181C826bc) |
| cZAMA              | [`0x80CB147Fd86dC6dEe3Eee7e4Cee33d1397d98071`](https://etherscan.io/address/0x80CB147Fd86dC6dEe3Eee7e4Cee33d1397d98071) |
| cTGBP              | [`0xa873750ccbafd5ec7dd13bfd5237d7129832edd9`](https://etherscan.io/address/0xa873750ccbafd5ec7dd13bfd5237d7129832edd9) |

Look up the underlying ERC-20 for each via `sdk.registry.getUnderlyingToken(address)`.

## End-to-end example

For a runnable React dApp using these APIs end-to-end, follow [Build your first confidential dApp](https://github.com/zama-ai/sdk/blob/main/tutorials/first-confidential-dapp/README.md).

## UI and UX recommendations

* **Caching**: Decrypted values are cached client-side for the session lifetime. Offer a refresh action that repeats the decrypt flow.
* **Permissions**: Treat user decryption as a permission grant with scope and duration. Show which contracts are included and when access expires. The SDK's session model is described in [Session model](https://github.com/zama-ai/sdk/blob/main/concepts/session-model/README.md).
* **Indicators**: Use distinct icons or badges for encrypted amounts. Avoid showing zero when a value is simply undisclosed.
* **Operator visibility**: Always show current operator approvals with expiry and a one-tap revoke. See [`useConfidentialIsApproved`](https://github.com/zama-ai/sdk/blob/main/reference/react/useConfidentialIsApproved/README.md) and [`useRevoke`](https://github.com/zama-ai/sdk/blob/main/reference/react/useRevoke/README.md).
* **Wrapping/unwrapping**: Clearly indicate which token a user is converting between. Show the underlying ERC-20's name and symbol alongside the confidential token.
* **Failure modes**: Differentiate between decryption denied, missing ACL grant, and expired session. Offer guided recovery actions. See [Handle errors](https://github.com/zama-ai/sdk/blob/main/guides/handle-errors/README.md).

## Testing and environments

* For local development against a Hardhat chain with no relayer, use [`RelayerCleartext`](https://github.com/zama-ai/sdk/blob/main/reference/sdk/RelayerCleartext/README.md). See [Local development](https://github.com/zama-ai/sdk/blob/main/guides/local-development/README.md).
* For testnet, use the SDK's built-in Sepolia config or any other supported network — see [Network presets](https://github.com/zama-ai/sdk/blob/main/reference/sdk/network-presets/README.md).
* Keep chain selection in a single source of truth in your app.

## Further reading

* [OpenZeppelin Confidential Contracts documentation](https://docs.openzeppelin.com/confidential-contracts) — ERC-7984 transfer variants, receiver callbacks, and operator semantics.
* [`Token` reference](https://github.com/zama-ai/sdk/blob/main/reference/sdk/Token/README.md) — full method surface for shield, unshield, transfer, approve, and balance operations.
* [`WrappersRegistry` reference](https://github.com/zama-ai/sdk/blob/main/reference/sdk/WrappersRegistry/README.md) — registry construction, caching, and pagination.
* [Architecture](https://github.com/zama-ai/sdk/blob/main/concepts/architecture/README.md) — how the SDK, relayer, and gateway fit together.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.zama.org/protocol/sdk/getting-started/wallet-exchange-integration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
