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 herearrow-up-right.

  • ACL: The Access Control List (ACL) contract that manages the permissions for encrypted amounts. More information in the FHEVM library documentationarrow-up-right.

  • Input proof: A proof that the encrypted amount is valid. More information in the relayer-sdk documentationarrow-up-right.

  • Public decryption: A request to publicly decrypt an encrypted amount. More information in the relayer-sdk documentationarrow-up-right.

Quick Start

circle-exclamation

Decimal conversion

circle-exclamation

Unsupported tokens

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 herearrow-up-right.

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:

  • amount must be a value using the same decimal precision as the underlying token.

  • to must not be the zero address.

circle-info

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

circle-exclamation

Unsupported from

With input proof

circle-info

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 documentationarrow-up-right.

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.sender must be from or an approved operator for from.

  • from mut not be the zero address.

  • encryptedAmount will 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:

2) Finalize unwrap

circle-info

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 documentationarrow-up-right.

This finalizes the unwrap request by sending the corresponding amount of underlying tokens to the to defined in the unwrap request.

Transfer confidential tokens

circle-info

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.

circle-exclamation

Unsupported from

Direct transfer

Operator-based transfer

Considerations:

  • msg.sender must be from or an approved operator for from.

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.sender must be from or an approved operator for from.

Check the conversion rate and decimals

Examples:

Underlying Decimals
Wrapper Decimals
Rate
Effect

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.

circle-info

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 documentationarrow-up-right.

Architecture

Events

Event
Description

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

Error
Cause

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

Underlying Decimals
Wrapper Decimals
Example

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