Skip to main content

Overview

The ITokenMinter interface defines the contract for managing tokens that are mintable, burnable, and transferable across domains. This interface is central to CCTP’s token transfer mechanism, handling the burn on source chains and mint on destination chains.

Interface Definition

ITokenMinter (V1)

interface ITokenMinter {
    function mint(
        uint32 sourceDomain,
        bytes32 burnToken,
        address to,
        uint256 amount
    ) external returns (address mintToken);

    function burn(address burnToken, uint256 amount) external;

    function getLocalToken(uint32 remoteDomain, bytes32 remoteToken)
        external
        view
        returns (address);

    function setTokenController(address newTokenController) external;
}
Source: src/interfaces/ITokenMinter.sol:23

ITokenMinterV2

V2 extends the base interface with multi-recipient minting:
interface ITokenMinterV2 is ITokenMinter {
    function mint(
        uint32 sourceDomain,
        bytes32 burnToken,
        address recipientOne,
        address recipientTwo,
        uint256 amountOne,
        uint256 amountTwo
    ) external returns (address);
}
Source: src/interfaces/v2/ITokenMinterV2.sol:27

Functions

mint

function mint(
    uint32 sourceDomain,
    bytes32 burnToken,
    address to,
    uint256 amount
) external returns (address mintToken);
Mints local tokens corresponding to tokens burned on the source domain. Parameters:
  • sourceDomain - The domain ID where the tokens were burned
  • burnToken - The address of the burned token on the source domain (as bytes32)
  • to - The address to receive the minted tokens on this domain
  • amount - The amount of tokens to mint
Returns:
  • mintToken - The address of the token contract that was minted
Reverts if:
  • The (sourceDomain, burnToken) pair does not map to a valid local token
  • The amount exceeds the minter’s allowance for the token
  • The mint operation fails

burn

function burn(address burnToken, uint256 amount) external;
Burns tokens owned by the TokenMinter contract. Parameters:
  • burnToken - The address of the token contract to burn from
  • amount - The amount of tokens to burn
Reverts if:
  • The amount exceeds the TokenMinter’s balance of the token
  • The burn operation fails

getLocalToken

function getLocalToken(uint32 remoteDomain, bytes32 remoteToken)
    external
    view
    returns (address);
Queries the local token address corresponding to a remote domain and token. Parameters:
  • remoteDomain - The remote domain ID
  • remoteToken - The token address on the remote domain (as bytes32)
Returns:
  • The local token address, or address(0) if no mapping exists

setTokenController

function setTokenController(address newTokenController) external;
Sets the token controller responsible for managing token mappings and limits. Parameters:
  • newTokenController - The address of the new token controller

Token Management

Token Mappings

The TokenMinter maintains bidirectional mappings between local and remote tokens:
// Query local token for a remote token
address localUSDC = tokenMinter.getLocalToken(
    0,  // Ethereum domain
    0x000000000000000000000000A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48  // USDC on Ethereum
);

Minter Allowances

Each token has a per-minter allowance that limits the amount that can be minted:
  • Prevents unlimited minting even if the minter is compromised
  • Set by the token controller based on liquidity and risk parameters
  • Must be sufficient to cover the mint amount

Token Controller

The token controller is responsible for:
  • Mapping local tokens to remote tokens across domains
  • Setting and managing per-minter allowances
  • Configuring token-specific transfer limits
  • Managing token-related permissions

V2 Enhancements

Multi-Recipient Minting

V2 introduces the ability to mint to multiple recipients in a single operation:
function mint(
    uint32 sourceDomain,
    bytes32 burnToken,
    address recipientOne,
    address recipientTwo,
    uint256 amountOne,
    uint256 amountTwo
) external returns (address);
Parameters:
  • sourceDomain - The domain where tokens were burned
  • burnToken - The burned token address (as bytes32)
  • recipientOne - First recipient address
  • recipientTwo - Second recipient address
  • amountOne - Amount to mint to first recipient
  • amountTwo - Amount to mint to second recipient
Returns:
  • The address of the minted token contract
Use Cases:
  • Fee splitting between recipient and relayer
  • Atomic distribution to multiple parties
  • Gas-efficient multi-party settlements
Example:
// Mint 95 USDC to user and 5 USDC to relayer as fee
address mintedToken = tokenMinterV2.mint(
    0,              // Source domain (Ethereum)
    usdcBytes32,    // Burned USDC address
    userAddress,    // 95% recipient
    relayerAddress, // 5% fee recipient
    95e6,          // 95 USDC (6 decimals)
    5e6            // 5 USDC relayer fee
);

Usage in CCTP Flow

Burn Flow (Source Chain)

  1. User approves TokenMessenger to spend tokens
  2. TokenMessenger transfers tokens to TokenMinter
  3. TokenMinter burns the tokens:
    // In TokenMessenger.depositForBurn
    _mintBurnToken.transferFrom(msg.sender, address(_localMinter), amount);
    _localMinter.burn(_burnToken, amount);
    
Source: src/TokenMessenger.sol:444

Mint Flow (Destination Chain)

  1. MessageReceiver validates and forwards message to TokenMessenger
  2. TokenMessenger calls TokenMinter to mint tokens:
    // In TokenMessenger.handleReceiveMessage
    ITokenMinter _localMinter = _getLocalMinter();
    address _mintToken = _minter.mint(
        remoteDomain,
        _burnToken,
        _mintRecipient,
        _amount
    );
    
Source: src/TokenMessenger.sol:534

Implementation Requirements

Security Considerations

  1. Access Control - Only authorized contracts (e.g., TokenMessenger) should call mint/burn
  2. Token Validation - Verify token mappings exist before minting
  3. Allowance Checks - Ensure mint amounts don’t exceed minter allowances
  4. Balance Verification - Confirm sufficient balance before burning

Example Implementation Pattern

contract TokenMinter is ITokenMinter, Ownable {
    // Token mappings: domain => remote token => local token
    mapping(uint32 => mapping(bytes32 => address)) public remoteTokensToLocalTokens;
    
    // Minter allowances: token => minter => allowance
    mapping(address => mapping(address => uint256)) public minterAllowance;
    
    address public tokenController;
    
    function mint(
        uint32 sourceDomain,
        bytes32 burnToken,
        address to,
        uint256 amount
    ) external returns (address mintToken) {
        // Get local token for the remote token
        mintToken = getLocalToken(sourceDomain, burnToken);
        require(mintToken != address(0), "Invalid token pair");
        
        // Check minter allowance
        require(
            minterAllowance[mintToken][msg.sender] >= amount,
            "Exceeds minter allowance"
        );
        
        // Decrease allowance
        minterAllowance[mintToken][msg.sender] -= amount;
        
        // Mint tokens to recipient
        IMintBurnToken(mintToken).mint(to, amount);
        
        return mintToken;
    }
    
    function burn(address burnToken, uint256 amount) external {
        require(amount > 0, "Amount must be nonzero");
        
        // Burn tokens from this contract's balance
        IMintBurnToken(burnToken).burn(amount);
    }
    
    function getLocalToken(uint32 remoteDomain, bytes32 remoteToken)
        external
        view
        returns (address)
    {
        return remoteTokensToLocalTokens[remoteDomain][remoteToken];
    }
    
    function setTokenController(address newTokenController) external onlyOwner {
        require(newTokenController != address(0), "Invalid controller");
        tokenController = newTokenController;
    }
}

Build docs developers (and LLMs) love