Building an ERC-20 Compliance Token

The ComplianceTokenERC20 is a ready-to-deploy, policy-protected ERC-20 token provided as an ACE reference implementation. It inherits PolicyProtectedUpgradeable, routes every state-changing function through a PolicyEngine, and is designed for deployment behind a proxy.

For a comparison with the ERC-3643 variant and guidance on which to choose, see Building a New Contract.

What makes it ACE-compatible

The token satisfies all the requirements described in Making Your Contract ACE-Compatible:

  1. Inherits PolicyProtectedUpgradeable — The contract calls __PolicyProtected_init during initialization, which sets the contract owner and connects it to a PolicyEngine.
  2. All state-changing functions are policy-protected — Every function that modifies balances, allowances, or frozen state carries the runPolicy or runPolicyWithContext modifier. The PolicyEngine evaluates all attached policies before the function body executes.
  3. ERC-7201 namespaced storage — All token state lives in a dedicated ComplianceTokenStoreERC20 storage struct, following the ERC-7201 pattern for safe upgradeable storage.

Protected functions

Every state-changing function on the token is policy-protected. The runPolicy modifier intercepts each call and routes it through the PolicyEngine, which evaluates all attached policies before the function body executes. Functions that need to pass additional context (such as offchain signatures or metadata) use runPolicyWithContext instead, which forwards a bytes context parameter to every policy in the chain.

ERC-20 standard

Function
Modifier
Description
transfer(to, amount)runPolicyTransfer tokens from the caller to another address.
transferFrom(from, to, amount)runPolicyTransfer tokens on behalf of another address using an allowance.
approve(spender, amount)runPolicySet an allowance for a spender.

Minting and burning

FunctionModifierDescription
mint(to, amount)runPolicyCreate new tokens and assign them to an address.
burn(amount)runPolicyDestroy tokens from the caller's balance.
burnFrom(from, amount)runPolicyDestroy tokens from another address.

Administrative and compliance

Function
Modifier
Description
freeze(account, amount, context)runPolicyWithContextFreeze a specific amount of tokens on an account. Frozen tokens cannot be transferred or burned.
unfreeze(account, amount, context)runPolicyWithContextUnfreeze a previously frozen amount on an account.
forceTransfer(from, to, amount, context)runPolicyWithContextAdministratively move tokens between accounts, subject to frozen balance checks.

Frozen token behavior

ComplianceTokenERC20 uses a strict preservation model for frozen tokens:

  • Available balance = total balance - frozen balance. Every transfer, burn, and force transfer checks that the sender has sufficient unfrozen balance and reverts if not.
  • No automatic unfreezing — Frozen tokens remain frozen during all operations. An administrator must explicitly call unfreeze before those tokens can be moved or burned.
  • Pre-freezing — Tokens can be frozen on an account before they are received. The frozen amount is tracked independently from the balance, so an admin can set a frozen amount in advance and the restriction takes effect as soon as tokens arrive.

This model provides maximum compliance control: every change to frozen status is an explicit, auditable administrative action.

Storage layout

All token state is stored in ComplianceTokenStoreERC20, which uses ERC-7201 namespaced storage at a deterministic slot:

FieldTypeDescription
namestringToken name.
symbolstringToken symbol.
decimalsuint8Decimal precision for display.
totalSupplyuint256Total supply of tokens.
balancesmapping(address => uint256)Per-account token balances.
allowancesmapping(address => mapping(address => uint256))Per-account spender allowances.
frozenBalancesmapping(address => uint256)Per-account frozen token amounts.
datamapping(bytes32 => bytes)Generic storage for extensions.

CCIP compatibility

The contract exposes getCCIPAdmin(), which returns the contract owner. This enables integration with Chainlink CCIP for cross-chain token transfers by identifying the admin authorized to configure the token's CCIP settings.

Reference implementation

The full source code for the ERC-20 compliance token:

Get the latest Chainlink content straight to your inbox.