Skip to main content
ERC20Confidential exposes hooks and library functions for the policies a regulated or product-specific token needs. This page shows each one with a contract that compiles against fhenix-confidential-contracts@0.4.0. None of them is active unless your contract wires it in. Every contract here inherits ERC20Confidential, so it must be linked to ERC20ConfidentialLib when you deploy it.

Freeze accounts

_beforeConfidentialMove(from, to) runs before every account-initiated confidential move: the eight transfer functions and both unshield overloads. It is empty by default. Override it and revert to block a move. It does not run on shield, on public ERC-20 transfers, on claimUnshielded, or on _confidentialMint. A freeze that only overrides this hook leaves the public balance, shielding, and pending claims open. To close those too, also override OpenZeppelin’s _update, which every public balance change goes through, including the pool transfers inside shield and claimUnshielded:
FreezableToken.sol
With both overrides, a frozen account cannot send or receive in either balance, cannot shield, and cannot be paid out by a claim.

Add a compliance observer

A compliance observer is an address that can decrypt confidential balances and transfer amounts, for example an auditor. The library stores one observer per token. Once set, every confidential move grants the observer access to the new balances of both parties and to the moved amount. The library exposes the setter and the backfill as functions your token calls. Put them behind your own access control:
ObservedToken.sol
observer() returns the current observer, or the zero address for none. Setting it emits ObserverSet(observer). Grants only cover handles created after the observer is set. A balance that has not moved since then is still unreadable to the observer. To grant access to older handles, collect them, for example from confidentialBalanceOf and past ConfidentialTransfer events, and pass them to grantObserverPast. It emits ObserverPastGranted(observer, count) and reverts with NoObserverSet if no observer is set. Pass only handles this token created; on CoFHE a handle the token is not allowed on makes the whole call revert. The observer reads values the same way a holder does, with decryptForView. Changing the observer does not revoke the old observer’s access to handles it was already granted.

Mint straight into confidential balances

_confidentialMint(address to, uint64 amount) mints amount * rate public tokens into the pool and credits amount confidential units to to. The amount is in confidential units and is public in your call data. The recipient never holds the tokens publicly.
_confidentialMint raises totalSupply() and updates confidentialTotalSupply. It does not call _beforeConfidentialMove, so it mints to frozen accounts unless your _update override blocks the recipient. In FreezableToken above it does not, because the public leg goes to the pool, not to to.

Auto-shield minted tokens

ERC20ConfidentialLib.autoShield(policy, to, amount) shields amount for to right after a mint, if a policy contract says to wants private mints. The policy implements IMintModePolicy:
AutoShieldToken.sol
MintMode has three values: UNSET, PUBLIC, and PRIVATE. autoShield shields only when the policy returns PRIVATE. It never reverts, so it cannot break the mint around it. It leaves the tokens public when:
  • the policy address is zero,
  • the policy call reverts,
  • the amount is smaller than the rate,
  • or the amount exceeds what a uint64 holds in confidential units.
As with shield, the remainder below the rate stays public. Minting 1 ether + 5 to an account set to PRIVATE shields 1 ether and leaves 5 base units public.

Add the confidential layer to your own ERC-20

If your token already has its own ERC-20 implementation, inherit ERC20ConfidentialCoreUpgradeable instead of ERC20Confidential. It carries the whole confidential layer but no ERC-20 and no OpenZeppelin base contracts, so it does not clash with the ones your token brings. It reaches your ledger through three hooks: Your token must also expose the standard public balanceOf(address) over the same ledger, because the library reads the pool’s balance through it. Call __ERC20ConfidentialCore_init(publicDecimals, confidentialDecimals) once from your constructor or initializer, and add _confidentialSupportsInterface(interfaceId) to your supportsInterface:
LedgerToken.sol
The confidential decimals you pass are clamped to the public decimals. This host has no indicator token, which is optional: confidential transfers work without one. To add one, deploy an ERC20ConfidentialIndicator with your token as its parent and register it with _setIndicatorToken. ERC20Confidential.sol is the reference host to compare against.