Confidential wrapper
This document gives an ovrview of the Confidential Wrapper, a smart contract that wraps standard ERC-20 tokens into confidential ERC-7984 tokens. Built on Zama's FHEVM, it enables privacy-preserving token transfers where balances and transfer amounts remain encrypted.
Terminology
Confidential Token: The ERC-7984 confidential token wrapper.
Underlying Token: The standard ERC-20 token wrapped by the confidential wrapper.
Wrapping: Converting ERC-20 tokens into confidential tokens.
Unwrapping: Converting confidential tokens back into ERC-20 tokens.
Rate: The conversion ratio between underlying token units and confidential token units (due to decimal differences).
Operator: An address authorized to transfer confidential tokens on behalf of another address.
Owner: The owner of the wrapper contract. In the FHEVM protocol, this is initially set to a DAO governance contract handled by Zama. Ownership will then be transferred to the underlying token's owner.
Registry: The registry contract that maps ERC-20 tokens to their corresponding confidential wrappers. More information here.
ACL: The Access Control List (ACL) contract that manages the permissions for encrypted amounts. More information in the FHEVM library documentation.
Input proof: A proof that the encrypted amount is valid. More information in the
relayer-sdkdocumentation.Public decryption: A request to publicly decrypt an encrypted amount. More information in the
relayer-sdkdocumentation.
Quick Start
Decimal conversion
The wrapper enforces a maximum number of decimals for the confidential token. When wrapping, amounts are rounded down and excess tokens are refunded. Currently, this maximum is set to 6 decimals only. See Maximum number of decimals for more information.
Unsupported tokens
Non-standard tokens such as fee-on-transfer or any deflationary-type tokens are NOT supported.
Get the confidential wrapper address of an ERC-20 token
Zama provides a registry contract that maps ERC-20 tokens to their corresponding verified confidential wrappers. Make sure to check the registry contract to ensure the confidential wrapper is valid before wrapping. More information here.
Wrap ERC-20 → Confidential token
Important: Prior to wrapping, the confidential wrapper contract must be approved by the msg.sender on the underlying token.
The wrapper will mint the corresponding confidential token to the to address and refund the excess tokens to the msg.sender (due to decimal conversion). Considerations:
amountmust be a value using the same decimal precision as the underlying token.tomust not be the zero address.
Low amount handling
If the amount is less than the rate, the wrapping will succeed but the recipient will receive 0 confidential tokens and the excess tokens will be refunded to the msg.sender.
Unwrap confidential token → ERC-20
Unwrapping is a two-step asynchronous process: an unwrap must be first made and then finalized with finalizeUnwrap. The unwrap function can be called with or without an input proof.
1) Unwrap request
Unsupported from
fromAccounts with a zero balance that have never held tokens cannot be the from address in unwrap requests.
With input proof
Input proof
To unwrap any amount of confidential tokens, the from address must first create an encrypted input to generate an encryptedAmount (externalEuint64) along its inputProof. The amount to be encrypted must use the same decimal precision as the confidential wrapper. More information in the relayer-sdk documentation.
Alternatively, an unwrap request can be made without an input proof if the encrypted amount (euint64) is known to from. For example, this can be the confidential balance of from.
This requests an unwrap request of encryptedAmount confidential tokens from from. Considerations:
msg.sendermust befromor an approved operator forfrom.frommut not be the zero address.encryptedAmountwill be burned in the request.NO transfer of underlying tokens is made in this request.
It emits an UnwrapRequested event:
Without input proof
Alternatively, an unwrap request can be made without an input proof if the encrypted amount (euint64) is known to from. For example, this can be the confidential balance of from.
On top of the above unwrap request considerations:
msg.sendermust be approved by ACL for the givenencryptedAmount⚠️ (see ACL documentation).
2) Finalize unwrap
Public decryption
The encrypted burned amount burntAmount emitted by the UnwrapRequested event must be publicly decrypted to get the cleartextAmount along its decryptionProof. More information in the relayer-sdk documentation.
This finalizes the unwrap request by sending the corresponding amount of underlying tokens to the to defined in the unwrap request.
Transfer confidential tokens
Transfer with input proof
Similarly to the unwrap process, transfers can be made with or without an input proof and the encrypted amount must be approved by the ACL for the msg.sender.
Unsupported from
fromAccounts with a zero balance that have never held tokens cannot be the from address in confidential transfers.
Direct transfer
Operator-based transfer
Considerations:
msg.sendermust befromor an approved operator forfrom.
Transfer with callback
The callback can be used along an ERC-7984 receiver contract.
Operator-based transfer with callback
The callback can be used along an ERC-7984 receiver contract.
Considerations:
msg.sendermust befromor an approved operator forfrom.
Check the conversion rate and decimals
Examples:
18
6
10^12
1 wrapped = 10^12 underlying
6
6
1
1:1 mapping
2
2
1
1:1 mapping
Check supplies
Non-confidential total supply
The wrapper exposes a non-confidential view of the total supply, computed from the underlying ERC20 balance held by the wrapper contract. This value may be higher than confidentialTotalSupply() if tokens are sent directly to the wrapper outside of the wrapping process.
Total Value Shielded (TVS)
This view function is useful for getting a good approximation of the wrapper's Total Value Shielded (TVS).
Encrypted (confidential) total supply
The actual supply tracked by the confidential token contract, represented as an encrypted value. To determine the cleartext value, you need to request decryption and appropriate ACL authorization.
Maximum total supply
The maximum number of wrapped tokens supported by the encrypted datatype (uint64 limit). If this maximum is exceeded, wrapping new tokens will revert.
Integration patterns
Operator system
Delegate transfer capabilities with time-based expiration:
Amount disclosure
Optionally reveal encrypted amounts publicly:
Check ACL permissions
Before using encrypted amounts in transactions, callers must be authorized:
Transfer functions with euint64 (not externalEuint64) require the caller to already have ACL permission for that ciphertext. More information in the FHEVM library documentation.
Architecture
Events
ConfidentialTransfer(from, to, encryptedAmount)
Emitted on every transfer (including mint/burn)
OperatorSet(holder, operator, until)
Emitted when operator permissions change
UnwrapRequested(receiver, encryptedAmount)
Emitted when unwrap is initiated
UnwrapFinalized(receiver, encryptedAmount, cleartextAmount)
Emitted when unwrap completes
AmountDiscloseRequested(encryptedAmount, requester)
Emitted when disclosure is requested
AmountDisclosed(encryptedAmount, cleartextAmount)
Emitted when amount is publicly disclosed
Errors
ERC7984InvalidReceiver(address)
Transfer to zero address
ERC7984InvalidSender(address)
Transfer from zero address
ERC7984UnauthorizedSpender(holder, spender)
Caller not authorized as operator
ERC7984ZeroBalance(holder)
Sender has never held tokens
ERC7984UnauthorizedUseOfEncryptedAmount(amount, user)
Caller lacks ACL permission for ciphertext
ERC7984UnauthorizedCaller(caller)
Invalid caller for operation
InvalidUnwrapRequest(amount)
Finalizing non-existent unwrap request
ERC7984TotalSupplyOverflow()
Minting would exceed uint64 max
Important Considerations
Ciphertext uniqueness assumption
The unwrap mechanism stores requests in a mapping keyed by ciphertext and the current implementation assumes these ciphertexts are unique. This holds in this very specific case but be aware of this architectural decision as it is NOT true in the general case.
Maximum number of decimals
The maximum number of decimals _maxDecimals() for the confidential token is currently set to 6 decimals only. This is due to FHE limitations as confidential balances must be represented by the euint64 encrypted datatype.
It is possible that future implementations of the wrapper set a higher _maxDecimals() value to better suit the needs of the underlying token. For example, cWBTC might require 8 decimals since using only 6 would make the smallest unit impractically expensive.
At deployment, the confidential wrapper sets its number of decimals as:
the number of decimals of the underlying token if it is less than
_maxDecimals()_maxDecimals()otherwise
Example with _maxDecimals() set to 6
18
6
ZAMA/cZAMA
6
6
USDT/cUSDT
2
2
GUSD/cGUSD
Once a confidential wrapper contract is deployed, this number cannot be updated. It can be viewed with the following view function:
Maximum total supply
The maximum total supply for the confidential token is currently set to type(uint64).max (2^64 - 1) due to FHE limitations.
Interface Support (ERC-165)
Upgradeability
The contract uses UUPS (Universal Upgradeable Proxy Standard) with 2-step ownership transfer. Only the owner can upgrade the contract. Initially, the owner is set to a DAO governance contract handled by Zama. Ownership will then be transferred to the underlying token's owner.
Last updated