> ## Documentation Index
> Fetch the complete documentation index at: https://cofhe-docs.fhenix.zone/llms.txt
> Use this file to discover all available pages before exploring further.

# Extend ERC20Confidential

> Freeze accounts, add a compliance observer, mint confidentially, auto-shield mints, or add the confidential layer to your own ERC-20

`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`](/fhe-library/confidential-contracts/overview#link-the-shared-library) 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`:

```solidity FreezableToken.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ERC20Confidential } from "fhenix-confidential-contracts/contracts/ERC20Confidential/ERC20Confidential.sol";

contract FreezableToken is ERC20Confidential, Ownable {
    mapping(address => bool) public frozen;

    error AccountFrozen(address account);

    constructor() ERC20Confidential("Freezable Token", "FRZ", 18) Ownable(msg.sender) {
        _mint(msg.sender, 1_000_000 ether);
    }

    function setFrozen(address account, bool isFrozen) external onlyOwner {
        frozen[account] = isFrozen;
    }

    function _checkNotFrozen(address account) private view {
        if (frozen[account]) revert AccountFrozen(account);
    }

    // Confidential transfers and unshield.
    function _beforeConfidentialMove(address from, address to) internal view override {
        _checkNotFrozen(from);
        _checkNotFrozen(to);
    }

    // Public transfers, shield, and claim payouts.
    function _update(address from, address to, uint256 value) internal override {
        _checkNotFrozen(from);
        _checkNotFrozen(to);
        super._update(from, to, value);
    }
}
```

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:

```solidity ObservedToken.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ERC20Confidential } from "fhenix-confidential-contracts/contracts/ERC20Confidential/ERC20Confidential.sol";
import { ERC20ConfidentialLib } from "fhenix-confidential-contracts/contracts/ERC20Confidential/ERC20ConfidentialLib.sol";

contract ObservedToken is ERC20Confidential, Ownable {
    constructor() ERC20Confidential("Observed Token", "OBS", 18) Ownable(msg.sender) {
        _mint(msg.sender, 1_000_000 ether);
    }

    function setObserver(address observer_) external onlyOwner {
        ERC20ConfidentialLib.setObserver(observer_);
    }

    function grantObserverPast(uint256[] calldata handles) external onlyOwner {
        ERC20ConfidentialLib.grantObserverPast(handles);
    }
}
```

`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`](/client-sdk/guides/decrypt-to-view).

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](/fhe-library/confidential-contracts/dual-mode/dual-balance-model#where-the-backing-tokens-sit) 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.

```solidity theme={null}
function mintConfidential(address to, uint64 amount) external onlyOwner {
    _confidentialMint(to, amount);
}
```

`_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`:

```solidity AutoShieldToken.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import { ERC20Confidential } from "fhenix-confidential-contracts/contracts/ERC20Confidential/ERC20Confidential.sol";
import { ERC20ConfidentialLib, IMintModePolicy, MintMode } from "fhenix-confidential-contracts/contracts/ERC20Confidential/ERC20ConfidentialLib.sol";

contract MintModeRegistry is IMintModePolicy {
    mapping(address => MintMode) private _modes;

    function setMintMode(MintMode mode) external {
        _modes[msg.sender] = mode;
    }

    function mintModeFor(address account) external view returns (MintMode) {
        MintMode mode = _modes[account];
        return mode == MintMode.UNSET ? MintMode.PUBLIC : mode;
    }
}

contract AutoShieldToken is ERC20Confidential {
    address public immutable mintModes;

    constructor(address mintModes_) ERC20Confidential("Auto Shield Token", "AST", 18) {
        mintModes = mintModes_;
    }

    function mint(address to, uint256 amount) external {
        _mint(to, amount);
        ERC20ConfidentialLib.autoShield(mintModes, to, amount);
    }
}
```

`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:

| Hook | Must |
| - | - |
| <code style={{ whiteSpace: "nowrap" }}>\_ledgerMint(to, amount)</code> | Mint `amount` public tokens to `to` |
| <code style={{ whiteSpace: "nowrap" }}>\_ledgerTransfer(from, to, amount)</code> | Move `amount` public tokens without an allowance check |
| <code style={{ whiteSpace: "nowrap" }}>\_ledgerBalanceOf(account)</code> | Return the public balance of `account` |

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`:

```solidity LedgerToken.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import { ERC20ConfidentialCoreUpgradeable } from "fhenix-confidential-contracts/contracts/ERC20Confidential/ERC20ConfidentialCoreUpgradeable.sol";

contract LedgerToken is ERC20, ERC165, ERC20ConfidentialCoreUpgradeable {
    constructor() ERC20("Ledger Token", "LDG") {
        __ERC20ConfidentialCore_init(decimals(), 6);
        _mint(msg.sender, 1_000_000 ether);
    }

    function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
        return _confidentialSupportsInterface(interfaceId) || super.supportsInterface(interfaceId);
    }

    function _ledgerMint(address to, uint256 amount) internal override {
        _mint(to, amount);
    }

    function _ledgerTransfer(address from, address to, uint256 amount) internal override {
        _transfer(from, to, amount);
    }

    function _ledgerBalanceOf(address account) internal view override returns (uint256) {
        return balanceOf(account);
    }
}
```

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`](https://github.com/FhenixProtocol/fhenix-confidential-contracts/blob/v0.4.0/contracts/ERC20Confidential/ERC20Confidential.sol) is the reference host to compare against.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.