Overview
The defining feature ofERC20Confidential is that every holder owns two balances simultaneously inside one contract: a public ERC-20 balance and a confidential (FHE-encrypted) balance. This page explains how those two layers are stored, how they stay backed 1:1, and how the encrypted bookkeeping engine (_confidentialUpdate) drives every confidential mutation.
The Two Layers
Public Layer
Inherited from OpenZeppelin
ERC20Transparent balances tracked in the standard _balances mapping. balanceOf, transfer, approve, transferFrom, and totalSupply behave like any ERC-20. Fully visible on-chain.Confidential Layer
mapping(address => euint64) _confidentialBalancesEncrypted balances stored as euint64 handles. Readable only by the holder (via ACL grant + permit) and the contract. Operated on entirely under FHE.ERC20Confidential inherits the real OpenZeppelin ERC20, the public side is genuinely functional — this is the inversion of FHERC20, where those same functions revert.
On FHERC20,
balanceOfIsIndicator() returns true and balanceOf returns a fake activity number. On ERC20Confidential, both signals declare the public balance real, and confidential-activity display is delegated to a separate sidecar token.The Confidential Pool
CONFIDENTIAL_POOL is a fixed sentinel address (0x1011...0000) that custodies the public tokens backing every confidential balance. It is the linchpin of the dual-balance model.
1
Shielding deposits into the pool
When you
shield, your public tokens are transferred to CONFIDENTIAL_POOL and an equal encrypted amount is credited to your confidential balance.2
The pool holds the backing
At any moment, the public balance sitting at
CONFIDENTIAL_POOL equals the total value of all confidential balances (scaled by the conversion rate).3
Claiming withdraws from the pool
When you unshield and claim, public tokens are transferred out of
CONFIDENTIAL_POOL back to you.confidentialTotalSupply() can be derived directly from the pool’s public balance:
Conversion Rate and Decimals
The public token may use any decimals; the confidential layer is capped at 6 to keep encrypted balances withineuint64.
The rate governs both directions of the bridge:
- Shielding rounds the public amount down to a whole multiple of the rate, then divides by the rate to get the confidential amount.
- Claiming multiplies the decrypted confidential amount by the rate to compute the public payout.
The Confidential Update Engine
_confidentialUpdate is the encrypted analog of ERC-20’s _update. Every confidential mutation — shield mint, confidential transfer, unshield burn — flows through it. Passing address(0) as from means mint; passing address(0) as to means burn.
Zero-replacement on insufficient balance
Zero-replacement on insufficient balance
FHESafeMath.tryDecrease returns an encrypted success flag. If the sender’s balance is too low, FHE.select(success, amount, 0) sets the transferred amount to encrypted zero instead of reverting.This is deliberate: reverting would leak whether the sender had enough balance. The trade-off is that transfers can silently move zero tokens — always work with the returned transferred handle, never the requested amount.Fresh handle on every update
Fresh handle on every update
Each balance change stores a new
euint64 handle (ptr). Any FHE.allow you granted on a previous handle no longer applies to the new one. The engine re-grants allowThis + allow(..., holder) on every update so the contract and holder retain access.Access control is granted inline
Access control is granted inline
After each mutation the function grants:
FHE.allowThis(...)on the new balance and the transferred amount — so the contract can keep operating on them.FHE.allow(balance, holder)— so the holder can decrypt their own balance.FHE.allow(transferred, from/to)— so both parties can see what moved.
The Indicator Sidecar Token
On FHERC20 the token’s ownbalanceOf doubles as a wallet “activity indicator.” ERC20Confidential can’t do that — its balanceOf is a real balance. Instead, the constructor deploys a separate companion token, ERC20ConfidentialIndicator, dedicated to showing confidential activity in wallets and explorers without revealing real amounts.
- Name/symbol:
"1011000 <ParentName>"/"c<PARENTSYMBOL>"(e.g.cMDT), with 4 decimals. - Fake balances:
balanceOfreturns10110000000 + _indicatedBalances[account]— a deliberately non-revealing number that changes with activity but encodes no real amount. - Driven only by the parent:
emitConfidentialTransfer(from, to)isonlyParent. The parent calls it inside_confidentialUpdate, nudging a bounded internal counter and emitting aTransfer(from, to, 10110000001)so explorers register that something happened. - Inert on its own:
transfer,transferFrom,approve, andallowanceall revert withERC20ConfidentialIndicatorNoOp(). You cannot move or approve the indicator token — it exists purely for display.
The indicator’s counter jitters within bounds (increment seeds at
5001, capped at 9999; decrement seeds at 4999, floored at 1). These numbers are intentionally meaningless — they signal activity, not balance. Access the deployed sidecar via the parent’s public indicatorToken variable.Reading Balances
Related Topics
- Bridge value between layers in Shield & Unshield
- Move confidential tokens in Confidential Operations
- Understand the FHERC20 indicator design in FHERC20 Core Features
- Learn about Access Control for FHE permissions