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
bytes32reference (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:
Install and configure
@zama-fhe/sdk(or@zama-fhe/react-sdkfor React apps). See Quick start for stack-by-stack setup.Initialize a
ZamaSDKinstance with a relayer, signer, and storage. See theZamaSDKreference.For each confidential token contract, create a
Tokeninstance viasdk.createToken(address).Read encrypted balances, build transfers, and manage operators using the
TokenAPI 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.confidentialTransferanduseConfidentialTransferhandle 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.
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.
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.
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.
Always check validity. A non-zero wrapper address may have been revoked. Treat isValid: false as no wrapper for that token.
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
setOperatorwith a past timestamp to revoke). SeeuseConfidentialIsOperatoranduseConfidentialSetOperator.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
OpenZeppelin Confidential Contracts documentation — ERC-7984 transfer variants, receiver callbacks, and operator semantics.
Tokenreference — full method surface for shield, unshield, transfer, approve, and balance operations.WrappersRegistryreference — registry construction, caching, and pagination.Architecture — how the SDK, relayer, and gateway fit together.
Last updated