Skip to main content

Overview

The IMessageHandler interface defines the contract for receiving and processing cross-chain messages on the destination domain. Contracts implementing this interface can handle messages forwarded from the MessageReceiver after validation.

Interface Definition

IMessageHandler (V1)

interface IMessageHandler {
    function handleReceiveMessage(
        uint32 sourceDomain,
        bytes32 sender,
        bytes calldata messageBody
    ) external returns (bool);
}
Source: src/interfaces/IMessageHandler.sol:31

IMessageHandlerV2

V2 introduces finality awareness with separate handlers for finalized and unfinalized messages:
interface IMessageHandlerV2 {
    function handleReceiveFinalizedMessage(
        uint32 sourceDomain,
        bytes32 sender,
        uint32 finalityThresholdExecuted,
        bytes calldata messageBody
    ) external returns (bool);

    function handleReceiveUnfinalizedMessage(
        uint32 sourceDomain,
        bytes32 sender,
        uint32 finalityThresholdExecuted,
        bytes calldata messageBody
    ) external returns (bool);
}
Source: src/interfaces/v2/IMessageHandlerV2.sol:35

Functions

handleReceiveMessage

function handleReceiveMessage(
    uint32 sourceDomain,
    bytes32 sender,
    bytes calldata messageBody
) external returns (bool);
Handles an incoming message from a Receiver on the destination domain. Parameters:
  • sourceDomain - The source domain ID where the message originated
  • sender - The address of the message sender on the source domain (as bytes32)
  • messageBody - The raw bytes of the message payload
Returns:
  • success - True if the message was processed successfully, false otherwise

handleReceiveFinalizedMessage (V2)

function handleReceiveFinalizedMessage(
    uint32 sourceDomain,
    bytes32 sender,
    uint32 finalityThresholdExecuted,
    bytes calldata messageBody
) external returns (bool);
Handles incoming finalized messages (finality threshold >= 2000). Parameters:
  • sourceDomain - The source domain ID where the message originated
  • sender - The address of the message sender on the source domain (as bytes32)
  • finalityThresholdExecuted - The finality threshold at which the message was attested (>= 2000)
  • messageBody - The raw bytes of the message payload
Returns:
  • success - True if successful, false otherwise

handleReceiveUnfinalizedMessage (V2)

function handleReceiveUnfinalizedMessage(
    uint32 sourceDomain,
    bytes32 sender,
    uint32 finalityThresholdExecuted,
    bytes calldata messageBody
) external returns (bool);
Handles incoming unfinalized messages (finality threshold < 2000). Parameters:
  • sourceDomain - The source domain ID where the message originated
  • sender - The address of the message sender on the source domain (as bytes32)
  • finalityThresholdExecuted - The finality threshold at which the message was attested (< 2000)
  • messageBody - The raw bytes of the message payload
Returns:
  • success - True if successful, false otherwise

When to Implement

Implement IMessageHandler when your contract needs to:
  • Receive and process cross-chain messages from CCTP
  • Handle token transfers with custom logic on the destination chain
  • Act as the recipient endpoint in cross-chain communication
  • Process burn messages for token minting operations

Implementation Example

The TokenMessenger contract provides a reference implementation:
contract TokenMessenger is IMessageHandler, Rescuable {
    IMessageTransmitter public immutable localMessageTransmitter;
    ITokenMinter public localMinter;
    uint32 public immutable messageBodyVersion;
    mapping(uint32 => bytes32) public remoteTokenMessengers;

    function handleReceiveMessage(
        uint32 remoteDomain,
        bytes32 sender,
        bytes calldata messageBody
    )
        external
        override
        onlyLocalMessageTransmitter
        onlyRemoteTokenMessenger(remoteDomain, sender)
        returns (bool)
    {
        // Parse the burn message
        bytes29 _msg = messageBody.ref(0);
        _msg._validateBurnMessageFormat();
        require(
            _msg._getVersion() == messageBodyVersion,
            "Invalid message body version"
        );

        // Extract message fields
        bytes32 _mintRecipient = _msg._getMintRecipient();
        bytes32 _burnToken = _msg._getBurnToken();
        uint256 _amount = _msg._getAmount();

        // Mint tokens to recipient
        ITokenMinter _localMinter = _getLocalMinter();
        _mintAndWithdraw(
            address(_localMinter),
            remoteDomain,
            _burnToken,
            Message.bytes32ToAddress(_mintRecipient),
            _amount
        );

        return true;
    }
}
Source: src/TokenMessenger.sol:313

Key Implementation Requirements

Security Validations

  1. Caller Verification - Only accept calls from the registered MessageTransmitter:
    modifier onlyLocalMessageTransmitter() {
        require(
            msg.sender == address(localMessageTransmitter),
            "Invalid message transmitter"
        );
        _;
    }
    
  2. Sender Verification - Validate the remote sender is authorized:
    modifier onlyRemoteTokenMessenger(uint32 domain, bytes32 tokenMessenger) {
        require(
            _isRemoteTokenMessenger(domain, tokenMessenger),
            "Remote TokenMessenger unsupported"
        );
        _;
    }
    
  3. Message Format Validation - Validate message structure and version before processing

Message Processing

  1. Parse the message body according to the expected format
  2. Extract relevant parameters (recipient, amount, token, etc.)
  3. Execute the intended action (mint tokens, trigger hooks, etc.)
  4. Return true on success, false on failure

V2 Enhancements

Version 2 introduces finality awareness:
  • Finalized Messages (threshold >= 2000): Messages with strong finality guarantees
  • Unfinalized Messages (threshold < 2000): Messages with probabilistic finality
Implementations can apply different handling logic based on finality:
function handleReceiveFinalizedMessage(
    uint32 sourceDomain,
    bytes32 sender,
    uint32 finalityThresholdExecuted,
    bytes calldata messageBody
) external returns (bool) {
    // Process with full confidence - finality guaranteed
    _processWithFullConfidence(messageBody);
    return true;
}

function handleReceiveUnfinalizedMessage(
    uint32 sourceDomain,
    bytes32 sender,
    uint32 finalityThresholdExecuted,
    bytes calldata messageBody
) external returns (bool) {
    // Process with caution - may require additional confirmations
    _processWithCaution(messageBody, finalityThresholdExecuted);
    return true;
}
  • IReceiver - Receives and validates messages before forwarding to handlers
  • ITokenMinter - Mints and burns tokens in response to cross-chain transfers
  • IRelayer - Sends messages from source to destination domains

Build docs developers (and LLMs) love