Skip to main content

Overview

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

shield()

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

unshield() → claimUnshielded()

Burn confidential tokens, decrypt the burned amount off-chain, 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 → 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 → Public)

Unshielding is asynchronous. unshield burns the confidential tokens and marks the burned ciphertext publicly decryptable; the network decrypts it off-chain; 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 off-chain

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