Build with the official Zama registry
Every snippet on this page uses real, verified Sepolia addresses from src/config/zamaSepolia.ts — the single source of truth in CipherWrap. Addresses interpolated at build time; they can't go stale.
Why use the official registry?
The Zama Wrappers Registry is the canonical on-chain index of all production-grade ERC-7984 confidential wrappers for Sepolia. Integrating it — rather than deploying your own throwaway wrapper — gives you:
- Shared liquidity. Your users share depth with every other app that wraps the same underlying token.
- Revocation visibility. The
isValidflag lets you disable wrap/unwrap instantly if a wrapper is deprecated — no redeployment needed. - One place to check for new pairs. When Zama deploys new wrappers, your app discovers them automatically at runtime.
1. Read all pairs from the registry
One call returns every pair, including revoked ones. Filter by isValid before enabling actions.
import { useReadContract } from 'wagmi'
// Always read pairs live — never substitute a static list.
const { data: pairs } = useReadContract({
address: '0x2f0750Bbb0A246059d80e94c454586a7F27a128e', // official Zama Wrappers Registry (Sepolia)
abi: registryAbi, // from src/abis/registry.ts in CipherWrap
functionName: 'getTokenConfidentialTokenPairs',
})
// pairs: Array<{ confidentialTokenAddress, tokenAddress, isValid }>
// Always check isValid. Revoked pairs are still returned.
const valid = pairs?.filter((p) => p.isValid) ?? []2. Token-specific snippets
Select any known wrapper — all snippets update to show that pair's real addresses.
a. Config object
// Drop this into your project — all values from the official Zama registry.
const TOKEN = {
symbol: 'cUSDCMock',
name: 'Confidential USDC (Mock)',
wrapperAddress: '0x7c5BF43B851c1dff1a4feE8dB225b87f2C223639',
underlyingAddress: '0x9b5Cd13b8eFbB58Dc25A05CF411D8056058aDFfF',
registry: '0x2f0750Bbb0A246059d80e94c454586a7F27a128e', // official Zama Wrappers Registry
chainId: 11155111, // Sepolia
decimals: 6,
} as constb. Approve + Wrap (shield)
import { parseUnits } from 'viem'
import { useApproveUnderlying, useShield } from '@zama-fhe/react-sdk'
// Step 1 — approve the cUSDCMock wrapper to pull USDCMock
const { mutate: approve } = useApproveUnderlying(
'0x7c5BF43B851c1dff1a4feE8dB225b87f2C223639',
)
approve({ amount: parseUnits('100', 6) })
// Step 2 — FHE-encrypt locally, then deposit (shield)
const { mutate: shield } = useShield({
address: '0x7c5BF43B851c1dff1a4feE8dB225b87f2C223639',
})
shield({
amount: parseUnits('100', 6),
approvalStrategy: 'skip', // pre-approved in step 1
onShieldSubmitted: (txHash) => console.log('wrap tx:', txHash),
})
// On-chain your USDCMock balance becomes an FHE ciphertext handle.
// No observer — including the contract owner — can read your balance.c. Decrypt confidential balance
import { formatUnits } from 'viem'
import {
useHasPermit,
useGrantPermit,
useConfidentialBalance,
} from '@zama-fhe/react-sdk'
const WRAPPER = '0x7c5BF43B851c1dff1a4feE8dB225b87f2C223639' // cUSDCMock
// Step 1 — grant a user-specific permit (off-chain EIP-712 — zero gas)
const { data: hasPermit } = useHasPermit({ contractAddresses: [WRAPPER] })
const { mutate: grantPermit } = useGrantPermit()
// call once: grantPermit([WRAPPER])
// Step 2 — read and auto-decrypt the confidential balance
const { data: balance } = useConfidentialBalance(
{ address: WRAPPER, account: userAddress },
{ enabled: hasPermit === true },
)
// balance is a bigint — decrypted only for the permit holder.
// Nobody else can produce this value.
// formatUnits(balance, 6) → e.g. "100.0"d. Unwrap (two-phase unshield)
import { parseUnits } from 'viem'
import { useUnshield } from '@zama-fhe/react-sdk'
// Two on-chain phases — the SDK manages both automatically.
// Phase 1: FHE-encrypt the amount → submit unwrap() on-chain
// Phase 2: await Zama FHE network proof → submit finalizeUnwrap()
const { mutate: unshield } = useUnshield(
'0x7c5BF43B851c1dff1a4feE8dB225b87f2C223639', // cUSDCMock
)
unshield({
amount: parseUnits('100', 6),
onUnwrapSubmitted: (txHash) => console.log('Phase 1 tx:', txHash),
onFinalizing: () => console.log('Waiting for FHE proof…'),
onFinalizeSubmitted: (txHash) => console.log('Phase 2 tx:', txHash),
})
// When onFinalizeSubmitted fires, your USDCMock is back
// in your ERC-20 balance at 0x9b5Cd13b8eFbB58Dc25A05CF411D8056058aDFfFIf finalization is interrupted, use useResumeUnshield(wrapperAddress) with the original phase-1 tx hash to continue from phase 2.
3. Verified Sepolia contract addresses
All addresses verified against the official Zama protocol-apps repository. Do not use unverified addresses.
Known wrapper pairs · Sepolia (chainId 11155111)
0x7c5BF43B851c1dff1a4feE8dB225b87f2C2236390x9b5Cd13b8eFbB58Dc25A05CF411D8056058aDFfF0x4E7B06D78965594eB5EF5414c357ca21E15544910xa7dA08FafDC9097Cc0E7D4f113A61e31d7e8e9b00x46208622DA27d91db4f0393733C8BA082ed831580xff54739b16576FA5402F211D0b938469Ab9A5f3F0xaa5612FA27c927a0c7961f5AEFEE5ba3A0F9C8910xFf021fB13cA64e5354c62c954b949a88cfDEb25E0xf2D628d2598aF4eAF94CB76a437Ff86CA78FfbFB0x75355a85c6FB9df5f0C80FF54e8747EEe9a0BF57Underlying is a TEST token, not the real Sepolia ZAMA token.
0xfCE5c7069c5525eF6c8C2b2E35A745bA20a2F7CC0x93c931278A2aad1916783F952f94276eA5111442Mock tGBP for developer testing. Distinct from the non-mock ctGBP.
0xe4FcF848739845BC81Dee1d5352cf3844F0a60C70x24377AE4AA0C45ecEe71225007f17c5D423dd9400x167DC962808B32CFFFc7e14B5018c0bE06A3A2080xf6Ef9ADB61A48E29E36bc873070A46A3D2667ff3Restricted mint — wraps the real testnet tGBP. No public faucet.
4. Resources
Official Zama documentation and standards.
Zama FHE React SDK docs ↗
Hook API reference, EIP-712 permit model, FHE fundamentals
ERC-7984 standard ↗
The confidential token wrapper interface CipherWrap implements
protocol-apps — Sepolia addresses ↗
Authoritative address list (source of truth for this page)
CipherWrap /demo ↗
Live walkthrough: connect → faucet → wrap → decrypt → unwrap