Skip to main content
The BurnMessage library defines the format for the message body used in CCTP token transfers. This message is embedded within the main Message’s messageBody field.

BurnMessage Structure

version
uint32
required
Message body version (4 bytes)
burnToken
bytes32
required
Address of the burned token on the source domain (32 bytes, starting at index 4)
mintRecipient
bytes32
required
Address that will receive minted tokens on destination domain (32 bytes, starting at index 36)
amount
uint256
required
Amount of tokens burned (32 bytes, starting at index 68)
messageSender
bytes32
required
Address that initiated the burn (32 bytes, starting at index 100)

Memory Layout (V1)

Field                 Bytes      Type       Index
-----------------------------------------------
version               4          uint32     0
burnToken             32         bytes32    4
mintRecipient         32         bytes32    36
amount                32         uint256    68
messageSender         32         bytes32    100

Total: 132 bytes (fixed size)
Source: ~/workspace/source/src/messages/BurnMessage.sol:23-30

BurnMessage V2 Format

Version 2 adds fee handling and hook support:
Field                 Bytes      Type       Index
-----------------------------------------------
version               4          uint32     0
burnToken             32         bytes32    4
mintRecipient         32         bytes32    36
amount                32         uint256    68
messageSender         32         bytes32    100
maxFee                32         uint256    132
feeExecuted           32         uint256    164
expirationBlock       32         uint256    196
hookData              dynamic    bytes      228
Source: ~/workspace/source/src/messages/v2/BurnMessageV2.sol:26-37

V2 Additions

maxFee
uint256
Maximum fee the sender is willing to pay on the destination domain (32 bytes, starting at index 132)
feeExecuted
uint256
Actual fee charged during execution (set to 0 during message creation, populated on destination)
expirationBlock
uint256
Block number after which the message expires (set to 0 during message creation)
hookData
bytes
Optional dynamic data passed to hooks for custom processing on destination domain

Encoding Functions

Format Burn Message (V1)

Source: ~/workspace/source/src/messages/BurnMessage.sol:57-72
function _formatMessage(
    uint32 _version,
    bytes32 _burnToken,
    bytes32 _mintRecipient,
    uint256 _amount,
    bytes32 _messageSender
) internal pure returns (bytes memory)
Creates a V1 burn message with exactly 132 bytes. Parameters:
  • _version: Message body version
  • _burnToken: Burned token address on source domain (as bytes32)
  • _mintRecipient: Recipient address on destination domain (as bytes32)
  • _amount: Amount burned
  • _messageSender: Address that initiated the burn (as bytes32)
Returns: Formatted 132-byte burn message

Format Burn Message for Relay (V2)

Source: ~/workspace/source/src/messages/v2/BurnMessageV2.sol:68-89
function _formatMessageForRelay(
    uint32 _version,
    bytes32 _burnToken,
    bytes32 _mintRecipient,
    uint256 _amount,
    bytes32 _messageSender,
    uint256 _maxFee,
    bytes calldata _hookData
) internal pure returns (bytes memory)
Creates a V2 burn message with fee and hook support. Parameters:
  • _version: Message body version
  • _burnToken: Burned token address (as bytes32)
  • _mintRecipient: Recipient address (as bytes32)
  • _amount: Amount burned
  • _messageSender: Initiator address (as bytes32)
  • _maxFee: Maximum fee for destination processing
  • _hookData: Optional hook data for custom logic
Returns: Formatted burn message (minimum 228 bytes + hookData length)
feeExecuted and expirationBlock are set to 0 during message creation. These fields are populated during attestation and execution.

Decoding Functions

V1 Getters

Source: ~/workspace/source/src/messages/BurnMessage.sol
// Get message version
function _getVersion(bytes29 _message) internal pure returns (uint32)

// Get burned token address
function _getBurnToken(bytes29 _message) internal pure returns (bytes32)

// Get mint recipient address
function _getMintRecipient(bytes29 _message) internal pure returns (bytes32)

// Get burned amount
function _getAmount(bytes29 _message) internal pure returns (uint256)

// Get message sender address
function _getMessageSender(bytes29 _message) internal pure returns (bytes32)

V2 Getters

V2 inherits V1 getters and adds: Source: ~/workspace/source/src/messages/v2/BurnMessageV2.sol
// Inherited V1 getters
function _getVersion(bytes29 _message) internal pure returns (uint32)
function _getBurnToken(bytes29 _message) internal pure returns (bytes32)
function _getMintRecipient(bytes29 _message) internal pure returns (bytes32)
function _getAmount(bytes29 _message) internal pure returns (uint256)
function _getMessageSender(bytes29 _message) internal pure returns (bytes32)

// V2-specific getters
function _getMaxFee(bytes29 _message) internal pure returns (uint256)
function _getFeeExecuted(bytes29 _message) internal pure returns (uint256)
function _getExpirationBlock(bytes29 _message) internal pure returns (uint256)
function _getHookData(bytes29 _message) internal pure returns (bytes29)
V2 uses BurnMessage as a library dependency, calling V1 functions internally (e.g., _message._getVersion()).

Validation

V1 Validation

Source: ~/workspace/source/src/messages/BurnMessage.sol:131-134
function _validateBurnMessageFormat(bytes29 _message) internal pure {
    require(_message.isValid(), "Malformed message");
    require(_message.len() == BURN_MESSAGE_LEN, "Invalid message length");
}
Enforces exactly 132 bytes for V1 messages.

V2 Validation

Source: ~/workspace/source/src/messages/v2/BurnMessageV2.sol:151-157
function _validateBurnMessageFormat(bytes29 _message) internal pure {
    require(_message.isValid(), "Malformed message");
    require(
        _message.len() >= HOOK_DATA_INDEX,
        "Invalid burn message: too short"
    );
}
Enforces minimum 228 bytes (allows variable-length hookData).

TypedMemView Usage

BurnMessage uses TypedMemView for efficient parsing:
using TypedMemView for bytes;
using TypedMemView for bytes29;
  • indexUint(offset, length): Reads uint256/uint32 at byte offset
  • index(offset, length): Reads bytes32 at byte offset
  • slice(offset, length, type): Extracts subview for hookData
See TypedMemView documentation for details.

Example Usage

Encoding a V1 BurnMessage

import {BurnMessage} from "./messages/BurnMessage.sol";
import {AddressUtils} from "./messages/v2/AddressUtils.sol";

// Convert addresses
bytes32 tokenBytes = AddressUtils.toBytes32(usdcAddress);
bytes32 recipientBytes = AddressUtils.toBytes32(destinationAddress);
bytes32 senderBytes = AddressUtils.toBytes32(msg.sender);

// Format burn message
bytes memory burnMsg = BurnMessage._formatMessage(
    0,                // version
    tokenBytes,       // burn token
    recipientBytes,   // mint recipient
    1000000,          // amount (1 USDC with 6 decimals)
    senderBytes       // message sender
);

// burnMsg is exactly 132 bytes

Encoding a V2 BurnMessage with Hooks

import {BurnMessageV2} from "./messages/v2/BurnMessageV2.sol";
import {AddressUtils} from "./messages/v2/AddressUtils.sol";

// Prepare hook data
bytes memory hookData = abi.encode(
    recipientContract,
    "customFunction(uint256)",
    additionalParams
);

// Format V2 burn message
bytes memory burnMsg = BurnMessageV2._formatMessageForRelay(
    1,                // version 1
    tokenBytes,       // burn token
    recipientBytes,   // mint recipient
    1000000,          // amount
    senderBytes,      // message sender
    5000,             // max fee (0.005 USDC)
    hookData          // hook data
);

// burnMsg is 228 + hookData.length bytes

Decoding a BurnMessage

import {TypedMemView} from "@memview-sol/contracts/TypedMemView.sol";
import {BurnMessage} from "./messages/BurnMessage.sol";

using TypedMemView for bytes;
using BurnMessage for bytes29;

// Parse message body from main message
bytes29 messageBody = message._messageBody();

// Validate format
messageBody._validateBurnMessageFormat();

// Extract fields
uint32 version = messageBody._getVersion();
bytes32 burnToken = messageBody._getBurnToken();
bytes32 mintRecipient = messageBody._getMintRecipient();
uint256 amount = messageBody._getAmount();
bytes32 sender = messageBody._getMessageSender();

// Convert to addresses
address tokenAddress = AddressUtils.toAddress(burnToken);
address recipientAddress = AddressUtils.toAddress(mintRecipient);

Decoding V2 with Fee and Hook Data

import {BurnMessageV2} from "./messages/v2/BurnMessageV2.sol";

using TypedMemView for bytes;
using BurnMessageV2 for bytes29;

bytes29 burnMessageView = messageBody.ref(0);

// Validate V2 format
burnMessageView._validateBurnMessageFormat();

// Extract V2-specific fields
uint256 maxFee = burnMessageView._getMaxFee();
uint256 feeExecuted = burnMessageView._getFeeExecuted();
uint256 expirationBlock = burnMessageView._getExpirationBlock();
bytes29 hookData = burnMessageView._getHookData();

// Process hook data if present
if (hookData.len() > 0) {
    // Decode and execute hook logic
    bytes memory hookBytes = hookData.clone();
    (address target, bytes memory callData) = abi.decode(hookBytes, (address, bytes));
    // Execute hook...
}

Integration with Message

BurnMessage is embedded as the messageBody field in Message:
import {Message} from "./messages/Message.sol";
import {BurnMessage} from "./messages/BurnMessage.sol";

// Create burn message
bytes memory burnMsg = BurnMessage._formatMessage(...);

// Embed in main message
bytes memory fullMessage = Message._formatMessage(
    0,                  // message version
    sourceDomain,
    destinationDomain,
    nonce,
    sender,
    recipient,
    destinationCaller,
    burnMsg            // burn message as body
);

See Also

Build docs developers (and LLMs) love