Skip to main content

Overview

ERC20Confidential bridges its two balances with three operations. Shielding (public to confidential) is synchronous. Unshielding (confidential to public) is a two-step, asynchronous flow because FHE decryption happens offchain.

shield()

Move public tokens into the confidential pool and mint yourself an equal encrypted balance, in a single transaction.

unshield() to claimUnshielded()

Burn confidential tokens, decrypt the burned amount offchain, then claim the equivalent public tokens back out of the pool.
Both directions are governed by the conversion rate: _rate() public base units equal one confidential unit.

Shielding (Public to Confidential)

What happens:
1

Round down to precision

amount is rounded down to the nearest whole multiple of _rate(). If that leaves zero, the call reverts with AmountTooSmallForConfidentialPrecision.
2

Move public tokens to the pool

The rounded amount is transferred from the caller’s public balance into CONFIDENTIAL_POOL via the standard ERC-20 _transfer.
3

Mint the confidential balance

An equal (rate-scaled) encrypted amount is credited to the caller’s confidential balance through _confidentialUpdate(address(0), msg.sender, ...).
4

Emit TokensShielded

TokensShielded(msg.sender, amountToShield) records the public amount that was shielded.
Shielding requires no approval, it moves the caller’s own public balance. The caller must already hold at least amountToShield public tokens.

Example


Unshielding (Confidential to Public)

Unshielding is asynchronous. unshield burns the confidential tokens and marks the burned ciphertext publicly decryptable; the network decrypts it offchain; claimUnshielded then verifies the decryption proof and releases the public tokens.

Step 1: Burn and create a claim

There are two overloads: one takes a plaintext uint64, the other an already-encrypted euint64 handle.
Because of zero-replacement, unshielding more than your confidential balance burns zero, it does not revert. You’ll end up with a claim for zero tokens. Ensure sufficient balance before unshielding.

Step 2: Decrypt offchain

unshield returns the burned euint64 handle and calls FHE.allowPublic(burned), so anyone can decrypt it, no permit needed. Retrieve the claim’s ctHash and request decryption:

Step 3: Claim the public tokens

Submit the plaintext and proof. The contract verifies the proof (via FHE.verifyDecryptResult), then transfers the rate-scaled public amount out of the pool.
The public payout goes to the claim’s stored to (the address that called unshield), regardless of who submits the claimUnshielded transaction. The proof, not the sender, authorizes the release.

Batch Claiming

Claim several pending unshields in one transaction:
The three arrays must be the same length, or the call reverts with LengthMismatch. Each entry is processed by the same _handleClaim used by the single-claim path.

Claim Lifecycle

Claims are tracked by the inherited FHERC20WrapperClaimHelper.

Querying claims

getUserClaims returns only pending claims, a claim is removed from the user’s set once it is successfully claimed. Use it to drive a “claimable balance” view in your UI.

Claim errors


Full Round-Trip Example


Events