ConfidentialWrapper locks a standard ERC-20 and issues confidential wrapped tokens (cTokens) with encrypted balances. The wrapper handles decimal normalization — all cTokens use 6 decimals regardless of the underlying token’s decimals.
Deployed instances on mainnet:
New wrappers are created via WrapperFactory.
Key properties
| Property | Value |
|---|---|
| cToken decimals | 6 (always) |
| Unwrap timeout | 1 day (UNWRAP_TIMEOUT) |
| Underlying token | Immutable, set at deploy |
wrap
approve the wrapper contract before calling this.
Amount in the underlying token’s native decimals (e.g.
1e18 for 1 WETH).1e18 WETH deposit becomes 1_000_000 encrypted cWETH units (6 decimals).
unwrap
Amount of cTokens to unwrap (6 decimals). For example,
1_000_000 = 1 token.requestId you use in finalizeUnwrap. The contract marks your account as restricted (isRestricted = true) until finalized or cancelled.
Internally, FHE.le(amount, balance) creates an encrypted boolean (canUnwrap) and calls FHE.makePubliclyDecryptable(canUnwrap) to request KMS threshold decryption.
finalizeUnwrap
The request ID returned by
unwrap().The FHE ciphertext handles that were decrypted (must match the
canUnwrap handle).ABI-encoded decrypted values. Decoded as
bool canUnwrap.KMS threshold signatures verified via
FHE.checkSignatures().canUnwrap == true, the cToken balance is reduced and the underlying ERC-20 is transferred to the requester. The account restriction is cleared regardless of outcome.
cancelUnwrap
UNWRAP_TIMEOUT (1 day) has elapsed. Clears the account restriction without transferring any underlying tokens.
transferPlaintext
FHE.asEuint64. Used by ConfidentialPaymentRouterV2 internally.
balanceOf
UnwrapRequest struct
Address that requested the unwrap.
Amount requested to unwrap (6 decimals).
The
ebool ciphertext handle for the canUnwrap boolean.Block timestamp when the unwrap was requested. Used to enforce the 1-day timeout.
Events
| Event | Parameters | Notes |
|---|---|---|
Wrapped | account, amount | cToken amount (6 decimals) |
UnwrapRequested | requestId, account, amount, handle | |
UnwrapFinalized | requestId, account, amount, success | |
UnwrapCancelled | requestId, account | |
Transfer | from, to, amount | amount always 0 |
Custom errors
| Error | When thrown |
|---|---|
ZeroAddress | Zero address passed |
ZeroAmount | amount is 0 |
AmountTooHigh | Adjusted amount exceeds uint64 max |
AccountRestricted | Caller has a pending unwrap request |
RequestNotFound | requestId does not exist |
RequestNotReady | Timeout not yet elapsed (for cancelUnwrap) |
NotRequestOwner | Caller is not the request owner |
RequestExpired | (reserved) |
WrapperFactory
How to deploy a new wrapper for any ERC-20.
Private Portfolio
Using wrap/unwrap from the frontend.