Skip to main content
These are the mistakes that are easiest to make with ERC20Confidential. Each one links to the page that explains the behavior behind it.

Treat shield and unshield amounts as public

Privacy covers balances and transfers inside the confidential layer, not the boundary. The amount passed to shield is in the call data and in the TokensShielded event. The amount passed to unshield(uint64) is in the call data, and every claim reveals its amount when it settles. Tell users that what they move in and out is visible. A user who shields 1,000 tokens and later unshields 1,000 links the two. See shield and unshield.

Check the amount that actually moved

A confidential transfer or unshield of more than the balance does not revert. It moves zero. Every transfer returns the amount that moved as a sharedEuint64, so a contract that credits a user should credit that returned amount, never the amount it asked for. See call a token from your contract.

Work in confidential units

Transfers, unshield, and _confidentialMint take confidential units, while shield and every ERC-20 function take public base units. For an 18-decimal token one confidential unit is 10^12 base units, and shield leaves anything below that in the public balance. Read confidentialDecimals() instead of assuming 6. See decimals and units.

Use the plaintext supply, or sync before reading the handle

confidentialTotalSupplyPlaintext() is always current. The confidentialTotalSupply() handle goes stale when someone transfers public tokens straight to the pool, and stays stale until anyone calls syncConfidentialTotalSupply(). It is also the zero handle until the first shield. See what total supply counts.

Gate both balances if you freeze accounts

_beforeConfidentialMove covers confidential transfers and unshield only. A freeze built on it alone still lets the account use its public balance, shield, and receive claim payouts. Override _update as well. See freeze accounts.

Backfill the observer when you set it

A compliance observer only gets access to handles created after it is set. Call grantObserverPast with the existing balance handles, or the observer cannot read balances that have not moved since. See add a compliance observer.

Keep operators short-lived

A confidential operator can move any amount of the holder’s confidential balance until its deadline. It is separate from the public allowance: approve grants nothing on the confidential side, and setOperator grants nothing on the public side. Set a deadline that covers the action you need, and set it to 0 when you are done. See operators. Deploy ERC20ConfidentialLib from fhenix-confidential-contracts@0.4.0 and link every token to it; the address cannot change after deployment. Compile with the optimizer on, so added functions do not push the token past the 24 KB contract size limit. See link the shared library.