For the complete documentation index, see llms.txt. This page is also available as Markdown.

Wallet & exchange integration

How wallets and exchanges support ERC-7984 confidential tokens with the Zama SDK — display balances, build transfers, manage operators, and wrap/unwrap between ERC-20 and confidential form.

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 you will encounter the following terminology. For a deeper architectural overview, see Architecture.

  • FHEVM — Zama's library for computations on encrypted values. Each encrypted value is represented on-chain as a bytes32 reference (also called a "handle" in Solidity / FHE.sol).

  • 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 encrypted values. 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 for stack-by-stack setup.

  2. Initialize a ZamaSDK instance with a relayer, signer, and storage. See the ZamaSDK reference.

  3. For each confidential token contract, create a Token instance 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 and useConfidentialTransfer handle the encrypted input pipeline for you. See Transfer privately.

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

  • 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 on-chain as encrypted values. 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.

A common pattern is to call useGrantPermit 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 for the full pre-authorization pattern, and Check balances 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.

For operator transfers (transferFrom-style with delegated authority), see useConfidentialTransferFrom and Operator approvals.

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)

WrappedToken.shield wraps a standard ERC-20 into its ERC-7984 form. The SDK handles the wrapping flow internally — using transferAndCall for ERC-1363 underlyings (one transaction) or approve + wrap for everything else (two transactions). The encrypted balance lands in the recipient's address (defaulting to the connected wallet). See Shielding paths for which currently-wrapped tokens use which path.

See Shield tokens 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. WrappedToken.unshield does both steps in one SDK call, including waiting for the decryption proof.

If the user closes the page between unwrap and finalize, resume with WrappedToken.resumeUnshield / useResumeUnshield. See Unshield tokens 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 contract itself exposes decimals() and rate() views (read them from the confidential token address, not the underlying ERC-20) for these 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 for the full surface.

Look up a wrapper for an ERC-20

Reverse lookup (confidential → underlying)

List all registered pairs (paginated)

Currently registered tokens

The following wrapped confidential tokens are registered on Ethereum mainnet:

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.

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 permit model is described in Permit model.

  • 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 (call setOperator with a past timestamp to revoke). See useConfidentialIsOperator and useConfidentialSetOperator.

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

Testing and environments

  • For local development against a Hardhat chain with no relayer, use RelayerCleartext. See Local development.

  • For testnet, use the SDK's built-in Sepolia config or any other supported network — see Network presets.

  • Keep chain selection in a single source of truth in your app.

Further reading

Last updated