Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/circlefin/evm-cctp-contracts/llms.txt

Use this file to discover all available pages before exploring further.

The TokenController contract provides token configuration management for CCTP, including mapping local tokens to remote tokens across domains and setting per-message burn limits. Contract: src/roles/TokenController.sol

Key Concepts

  • Token Pairs: Links between local tokens and their corresponding remote domain tokens
  • Burn Limits: Maximum amount of each token that can be burned per message
  • Token Controller Role: Authorized address that manages token configuration

State Variables

burnLimitsPerMessage

mapping(address => uint256) public burnLimitsPerMessage
Maps local token addresses to their maximum burn amount per message. Key: Local token address
Value: Maximum burn amount per message (in token’s smallest unit)
Usage: A burn limit of 0 means the token is not supported for burning.

remoteTokensToLocalTokens

mapping(bytes32 => address) public remoteTokensToLocalTokens
Maps remote domain and token pairs to local token addresses. Key: keccak256(abi.encodePacked(remoteDomain, remoteToken))
Value: Local token address
Usage: Enables validation and minting when receiving messages from remote domains.

_tokenController

address private _tokenController
Private state variable storing the address of the token controller.

Functions

linkTokenPair

function linkTokenPair(
    address localToken,
    uint32 remoteDomain,
    bytes32 remoteToken
) external onlyTokenController
Links a pair of local and remote tokens to be supported by the TokenMinter. Parameters:
  • localToken: Address of the local token
  • remoteDomain: Domain ID of the remote chain
  • remoteToken: Address of the token on the remote domain (as bytes32)
Requirements:
  • Caller must be the token controller
  • Remote token must not already be linked to a local token
Emits: TokenPairLinked(address localToken, uint32 remoteDomain, bytes32 remoteToken) Source: TokenController.sol:126 Note:
  • A remote token can only map to one local token
  • Multiple remote tokens can map to the same local token
  • Linking does not enable deposits (must also call setMaxBurnAmountPerMessage)

unlinkTokenPair

function unlinkTokenPair(
    address localToken,
    uint32 remoteDomain,
    bytes32 remoteToken
) external onlyTokenController
Removes the link between a local token and a remote token. Parameters:
  • localToken: Address of the local token
  • remoteDomain: Domain ID of the remote chain
  • remoteToken: Address of the token on the remote domain (as bytes32)
Requirements:
  • Caller must be the token controller
  • Token pair must currently be linked
Emits: TokenPairUnlinked(address localToken, uint32 remoteDomain, bytes32 remoteToken) Source: TokenController.sol:157 Note: Unlinking does not disable burning the local token (must separately call setMaxBurnAmountPerMessage with 0)

setMaxBurnAmountPerMessage

function setMaxBurnAmountPerMessage(
    address localToken,
    uint256 burnLimitPerMessage
) external onlyTokenController
Sets the maximum burn amount per message for a given local token. Parameters:
  • localToken: Address of the local token
  • burnLimitPerMessage: Maximum burn amount per message (0 to disable burning)
Requirements:
  • Caller must be the token controller
Emits: SetBurnLimitPerMessage(address indexed token, uint256 burnLimitPerMessage) Source: TokenController.sol:186 Important:
  • Burns exceeding this limit will revert
  • Mints do not respect this limit
  • Reducing the limit does not affect previously burned tokens (they can still be minted)
  • Setting to 0 disables burning for that token

tokenController

function tokenController() external view returns (address)
Returns the address of the token controller. Returns: Address of the current token controller Source: TokenController.sol:113

Internal Functions

_setTokenController

function _setTokenController(address newTokenController) internal
Internal function to set the token controller address. Parameters:
  • newTokenController: Address of the new token controller
Requirements:
  • New token controller must be non-zero address
Emits: SetTokenController(address tokenController) Source: TokenController.sol:202

_getLocalToken

function _getLocalToken(
    uint32 remoteDomain,
    bytes32 remoteToken
) internal view returns (address)
Gets the local token associated with a remote domain and token. Parameters:
  • remoteDomain: Domain ID of the remote chain
  • remoteToken: Address of the token on the remote domain (as bytes32)
Returns: Local token address, or zero address if not linked Source: TokenController.sol:217

_hashRemoteDomainAndToken

function _hashRemoteDomainAndToken(
    uint32 remoteDomain,
    bytes32 remoteToken
) internal pure returns (bytes32)
Hashes a remote domain and token address together. Parameters:
  • remoteDomain: Domain ID of the remote chain
  • remoteToken: Address of the token on the remote domain (as bytes32)
Returns: Keccak256 hash of packed remote domain and token Source: TokenController.sol:235

Modifiers

onlyTokenController

modifier onlyTokenController()
Restricts function access to the token controller only. Reverts: “Caller is not tokenController” if caller is not the token controller Source: TokenController.sol:82

onlyWithinBurnLimit

modifier onlyWithinBurnLimit(address token, uint256 amount)
Ensures that a burn operation does not exceed the per-message burn limit. Parameters:
  • token: Address of the token to burn
  • amount: Amount to burn
Reverts:
  • “Burn token not supported” if burn limit is 0
  • “Burn amount exceeds per tx limit” if amount exceeds limit
Source: TokenController.sol:98

Events

TokenPairLinked

event TokenPairLinked(
    address localToken,
    uint32 remoteDomain,
    bytes32 remoteToken
)
Emitted when a token pair is linked. Parameters:
  • localToken: Local token address
  • remoteDomain: Remote domain ID
  • remoteToken: Remote token address as bytes32

TokenPairUnlinked

event TokenPairUnlinked(
    address localToken,
    uint32 remoteDomain,
    bytes32 remoteToken
)
Emitted when a token pair is unlinked.

SetBurnLimitPerMessage

event SetBurnLimitPerMessage(
    address indexed token,
    uint256 burnLimitPerMessage
)
Emitted when a burn limit is set for a token. Parameters:
  • token: Local token address
  • burnLimitPerMessage: New burn limit per message

SetTokenController

event SetTokenController(address tokenController)
Emitted when the token controller address is set. Parameters:
  • tokenController: New token controller address

Usage Examples

Setting Up a New Token

// Step 1: Link token pair (as token controller)
// Link local USDC to Ethereum USDC
tokenController.linkTokenPair(
    0xLOCAL_USDC_ADDRESS,
    0, // Ethereum domain ID
    0x000000000000000000000000ETHEREUM_USDC_ADDRESS
);

// Step 2: Set burn limit to enable deposits
// Allow burning up to 1 million USDC per message
tokenController.setMaxBurnAmountPerMessage(
    0xLOCAL_USDC_ADDRESS,
    1_000_000 * 10**6 // 1M USDC (6 decimals)
);

Updating Configuration

// Reduce burn limit
tokenController.setMaxBurnAmountPerMessage(
    0xLOCAL_USDC_ADDRESS,
    100_000 * 10**6 // 100K USDC
);

// Temporarily disable burning (set to 0)
tokenController.setMaxBurnAmountPerMessage(
    0xLOCAL_USDC_ADDRESS,
    0
);

// Unlink token pair
tokenController.unlinkTokenPair(
    0xLOCAL_USDC_ADDRESS,
    0, // Ethereum domain ID
    0x000000000000000000000000ETHEREUM_USDC_ADDRESS
);

Checking Configuration

// Check burn limit
uint256 limit = tokenMinter.burnLimitsPerMessage(0xLOCAL_USDC_ADDRESS);

// Check if remote token is linked
bytes32 key = keccak256(abi.encodePacked(
    uint32(0), // Ethereum domain
    bytes32(uint256(uint160(0xETHEREUM_USDC_ADDRESS)))
));
address localToken = tokenMinter.remoteTokensToLocalTokens(key);

Security Considerations

  • Token controller should be a trusted address (e.g., multisig or DAO)
  • Burn limits should be set based on liquidity and risk assessment
  • A token pair can only be linked once (prevents conflicting mappings)
  • Minting is not limited by burn limits (asymmetric by design)
  • Setting burn limit to 0 effectively disables deposits for that token
  • Unlinking a token pair does not automatically disable burning
  • Remote tokens are stored as bytes32 to support non-EVM chains
  • Multiple remote tokens can map to the same local token (useful for wrapped tokens)

Integration with TokenMinter

The TokenMinter contract inherits from TokenController and uses these functions in:
  • depositForBurn(): Checks burn limit via onlyWithinBurnLimit modifier
  • handleReceiveMessage(): Uses _getLocalToken() to determine which token to mint
  • Administrative setup: Owner uses inherited functions to configure supported tokens

Remote Token Address Format

Remote tokens are represented as bytes32 to support both EVM and non-EVM chains:
// Convert EVM address to bytes32
bytes32 remoteToken = bytes32(uint256(uint160(evmAddress)));

// Extract EVM address from bytes32
address evmAddress = address(uint160(uint256(remoteToken)));
For non-EVM chains, the bytes32 format is used directly without conversion.

Build docs developers (and LLMs) love