FHERC20ERC20Wrapper wraps any standard ERC-20. FHERC20NativeWrapper wraps ETH, taken directly or as WETH. Use this page to deploy a wrapper, shield into it, and get the underlying asset back out.
Deploy a wrapper
Both wrappers are abstract. You inherit one and pass the FHERC20 constructor arguments next to the wrapper’s own:ConfidentialUSDC.sol
ConfidentialETH.sol
shieldNative, because it reads its decimals from WETH.
The 6 passed to FHERC20 does not set the wrapper’s decimals. The wrapper overrides decimals() with a value derived from the underlying token. The constructor argument only sets the indicator tick, the unit that balanceOf reports in. Pass the wrapper’s real decimals, which is 6 for any underlying with 6 or more.
Both wrappers call into ERC20ConfidentialLib, so you must deploy that library and link it into the wrapper. See link the shared library for the steps.
How decimals and the rate work
Encrypted amounts areeuint64, so a wrapper caps its own decimals at 6. When the underlying has more, the wrapper converts at a fixed rate():
One confidential unit is backed by
rate() units of the underlying. Shielding rounds down to a multiple of rate(), and a claim pays amount * rate(). If the underlying has no decimals() function, the ERC-20 wrapper assumes 18.
Shield an ERC-20
shield(to, amount) pulls the underlying from the caller and mints confidential tokens to to. Approve the wrapper first:
- The wrapper pulls
amount - (amount % rate())and mintsamount / rate(). The remainder never leaves your account. - An
amountbelowrate()mints zero and pulls nothing. It does not revert. tocannot beaddress(0). The mint reverts withFHERC20InvalidReceiver.
Shield in one transaction with ERC-1363
If the underlying implements ERC-1363, you can skip the approval. CalltransferAndCall on the underlying with the wrapper as the recipient. The wrapper’s onTransferReceived mints to the address in the first 20 bytes of data, or to the sender when data is shorter than 20 bytes:
amount arrives first, so the wrapper refunds the part below a multiple of rate() to the sender. onTransferReceived reverts with FHERC20UnauthorizedCaller unless the underlying token calls it.
Shield the native token
FHERC20NativeWrapper has two entry points:
shieldNative(to)takesmsg.value. It refunds the part below a multiple ofrate()to the caller.shieldWrappedNative(to, value)pulls WETH, which you approve first, and unwraps it. The part below a multiple ofrate()is never pulled.
to == address(0) as the caller, and both revert with AmountTooSmallForConfidentialPrecision when the value is below rate(). With 18-decimal ETH the rate is 10^12, so 1 ETH mints 1,000,000 units.
Unshield back to the underlying
Getting the underlying back takes two transactions, because the amount is encrypted and the wrapper can only pay out a plaintext:1
Unshield
Call
unshield(from, to, amount). The wrapper burns the amount from from, makes the burned amount publicly decryptable, and records a claim for to.2
Decrypt
Read the claim and decrypt its handle with
decryptForTx. No ACP (Access Control Permission) is needed, because the burned amount is public.3
Claim
Call
claimUnshielded(id, value, signature). The wrapper verifies the decryption and sends value * rate() of the underlying to the claim’s recipient.unshield overloads:
The caller must be
from or an operator for from, or the call reverts with FHERC20UnauthorizedSpender. to cannot be address(0). If the amount exceeds the balance, the wrapper burns zero, and the claim decrypts to 0.
Find and settle the claim
A claim is keyed by a claim ID, not by the ciphertext handle. Two unshields can burn values with the same handle, so the handle cannot identify a claim. Read pending claims withgetUserClaims(to), then decrypt ctHash and claim by id:
to, so a relayer can settle claims for your users. claimUnshieldedBatch(ids, values, signatures) settles several in one transaction and reverts as a whole if any element is invalid. See decrypt to transaction for the SDK side.
getUserClaims returns only pending claims. getClaim(id) returns any claim as this struct:
Total supply checks
Because the wrapper’s holdings are public, it can bound its own supply.inferredTotalSupply() returns the underlying balance divided by rate(), and every mint reverts with FHERC20TotalSupplyOverflow if that exceeds maxTotalSupply(), which is type(uint64).max. Anyone can raise inferredTotalSupply() by sending the underlying to the wrapper directly. It also lags confidentialTotalSupply() between an unshield and its claim.
Errors
InvalidSigner comes from the TaskManager, which verifies the decryption. See common errors for the other TaskManager errors.
Events
Shielding also emits the FHERC20
Transfer and ConfidentialTransfer events for the mint. The ERC-20 wrapper has no shield event of its own.