Building CCIP Messages from SVM to EVM

Introduction

This guide explains how to construct CCIP Messages from SVM chains (e.g. Solana) to EVM chains (e.g. Ethereum, Arbitrum, Avalanche, etc.). We'll cover the message structure, required parameters, account management, and implementation details for different message types including token transfers, arbitrary data messaging, and programmable token transfers (data and tokens).

CCIP Message Structure

CCIP messages from SVM are built using the SVM2AnyMessage struct from the CCIP Router program. See the CCIP Router API Reference for complete details. The SVM2AnyMessage struct is defined as follows:

pub struct SVM2AnyMessage {
    pub receiver: Vec<u8>,
    pub data: Vec<u8>,
    pub token_amounts: Vec<SVMTokenAmount>,
    pub fee_token: Pubkey, // pass zero address if native SOL
    pub extra_args: Vec<u8>,
}

receiver

  • For EVM destinations:
    • This is the address of the contract or wallet that will receive the message
    • Target either smart contracts that implement ccipReceive function or user wallets for token-only transfers
    • Must be properly formatted as a 32-byte Solana-compatible byte array

data

  • Definition: Contains the payload that will be passed to the receiving contract on the destination chain
  • For token-only transfers: Must be empty
  • For arbitrary messaging or programmable token transfers: Contains the data the receiver contract will process
  • Encoding consideration: The receiver on the destination chain must be able to correctly decode this data.

tokenAmounts

  • Definition: An array of token addresses and amounts to transfer
  • Each token is represented by a SVMTokenAmount struct that contains:
    • token: The Solana token mint public key
    • amount: The amount to transfer in the token's native denomination
  • For data-only messages: Must be an empty array
  • Note: Check the CCIP Directory for the list of supported tokens on each lane

feeToken

  • Definition: Specifies which token to use for paying CCIP fees
  • Use Pubkey::default() to pay fees with native SOL
  • Alternatively, specify a token mint address for fee payment with that token
  • Note: Check the CCIP Directory for the list of supported fees tokens for the SVM chain you are using

extraArgs

For EVM-bound messages, the extraArgs field must include properly encoded parameters for:

struct GenericExtraArgsV2 {
    gas_limit: u256,
    allow_out_of_order_execution: bool,
}
  • gas_limit: Specifies the amount of gas allocated for calling the receiving contract on the destination chain
  • allow_out_of_order_execution: Flag that determines if messages can be executed out of order

Implementation by Message Type

Token Transfer

Use this configuration when sending only tokens from SVM to EVM chains:

const message = {
  receiver: evmAddressToSolanaBytes(evmReceiverAddress), // 32-byte padded EVM address
  data: new Uint8Array(), // Empty data for token-only transfer
  tokenAmounts: [{ token: tokenMint, amount: amount }],
  feeToken: feeTokenMint, // Or Pubkey.default() for native SOL
  extraArgs: encodeExtraArgs({
    gasLimit: 0, // Must be 0 for token-only transfers
    allowOutOfOrderExecution: true // Must be true for all messages
  })
};

Arbitrary Messaging

Use this configuration when sending only data to EVM chains:

const message = {
  receiver: evmAddressToSolanaBytes(evmReceiverAddress), // 32-byte padded EVM address
  data: messageData, // Encoded data to send
  tokenAmounts: [], // Empty array for data-only messages
  feeToken: feeTokenMint, // Or Pubkey.default() for native SOL
  extraArgs: encodeExtraArgs({
    gasLimit: 200000, // Appropriate gas limit for the receiving contract
    allowOutOfOrderExecution: true // Must be true for all messages
  })
};

Programmable Token Transfer (Data and Tokens)

Use this configuration when sending both tokens and data in a single message:

const message = {
  receiver: evmAddressToSolanaBytes(evmReceiverAddress), // 32-byte padded EVM address
  data: messageData, // Encoded data to send
  tokenAmounts: [{ token: tokenMint, amount: amount }],
  feeToken: feeTokenMint, // Or Pubkey.default() for native SOL
  extraArgs: encodeExtraArgs({
    gasLimit: 300000, // Higher gas limit for complex operations
    allowOutOfOrderExecution: true // Must be true for all messages
  })
};

Understanding the ccip_send Instruction

The core of sending CCIP messages from SVM is the ccip_send instruction in the CCIP Router program. This instruction requires several key components to be prepared correctly:

Core Components for ccip_send

  1. Destination Chain Selector: A unique identifier for the target blockchain
  2. Message Structure: The SVM2AnyMessage containing receiver, data, tokens, etc.
  3. Required Accounts: All accounts needed for the instruction
  4. Token Indices: For token transfers, indices marking where each token's accounts begin. See detailed explanation in the API Reference

Data Encoding for Cross-Chain Compatibility

When sending data from SVM to EVM chains, proper encoding is crucial:

When sending messages across chains:

  • Ensure the data payload uses an encoding format that's compatible with the receiver contract on the destination chain.
  • For EVM-based destinations, implement Ethereum ABI encoding to guarantee proper data interpretation upon receipt.

Account Requirements

Unlike EVM chains which use a simple mapping storage model, SVM account model requires explicit specification of all accounts needed for an operation. When sending CCIP messages, several account types are needed. For complete account specifications, see the CCIP Router API Reference:

  1. Core Accounts

    1. Config PDA (index 0): The CCIP Router configuration account - Stores router configuration settings.
    2. Destination Chain (index 1): State account for the target chain - Stores information about the destination chain and is writable because it's updated during message sending.
    3. Nonce (index 2): Per-user sequence tracking - Keeps track of message sequence numbers for each user and destination chain pair, and is writable because it's incremented with each message.
    4. Authority (signer) (index 3): Transaction signer/fee payer - The account that signs the transaction and pays for fees, must be a signer and is writable.
    5. System Program (index 4): For account creation if needed - Required for account creation operations.
  2. Fee Payment Accounts

    1. Fee Token Program (index 5): Token program for the fee token - Either the standard SPL Token or Token-2022 program.
    2. Fee Token Mint (index 6): The mint of the token used for fees - Defines which token is used to pay for CCIP fees.
    3. User's Fee Token (index 7): Token account of fee payer - The user's token account which is debited for fees, is writable.
    4. Fee Receiver (index 8): Destination for fees - The account where fees are sent, is writable.
    5. Fee Billing Signer (index 9): PDA with authority over fee receiver - Has authority to manage fee-related operations.
  3. Fee Quoter Accounts

    1. Fee Quoter (index 10): The Fee Quoter program account - Program responsible for calculating fees.
    2. Fee Quoter Config (index 11): Configuration account for the Fee Quoter - Stores global configuration for the Fee Quoter.
    3. Fee Quoter Dest Chain (index 12): Destination chain configuration for the Fee Quoter - Specific fee configuration for the target chain.
    4. Fee Quoter Billing Token Config (index 13): Fee token configuration for billing - Specific configuration for the token used to pay fees.
    5. Fee Quoter Link Token Config (index 14): LINK token configuration for the Fee Quoter - Configuration for LINK token as a reference.
  4. RMN Remote Accounts

    1. RMN Remote (index 15): The Remote Monitoring Network program - Responsible for cross-chain monitoring and validation.
    2. RMN Remote Curses (index 16): Curse records for the Remote Monitoring Network - Stores validation information.
    3. RMN Remote Config (index 17): Configuration for the Remote Monitoring Network - Stores settings for the RMN program.

These accounts must be provided in exactly this order in the remaining_accounts parameter. See structure of token sub-slices in the API reference.

Note: The tokenIndexes parameter must be provided in the ccip_send instruction parameter, containing the starting indices in the remaining accounts array where each token's accounts begin. See examples of single and multi-token transfers in the API reference.

Handling Transaction Size Limits

SVM chains have transaction size limitations that become important when sending CCIP messages:

  1. Account Reference Limit:

    • SVM transactions have a limit on how many accounts they can reference
    • Each token transfer adds approximately 11-12 accounts to your transaction
  2. Address Lookup Tables (ALTs):

    • ALTs allow transactions to reference accounts without including full public keys
    • The CCIP Router requires each token to have an ALT in its Token Admin Registry
    • The CCIP Router relies on these ALTs to locate the correct Pool Program and other token-specific accounts
  3. Transaction Serialized Size:

    • Even with ALTs, SVM transactions have a maximum serialized size (1232 bytes)
    • Each token transfer increases the transaction size
    • If your transaction exceeds this limit, you'll need to split it into multiple transactions

Tracking Messages with Transaction Logs

After sending a CCIP message, the CCIP Router emits a CCIPMessageSent event in the transaction logs containing key tracking information:

Program log: Event: {
  "name": "CCIPMessageSent",
  "data": {
    "dest_chain_selector": [destination chain ID],
    "sequence_number": [sequence number],
    "message": {
      "header": {
        "message_id": "0x123...",
        ...
      }
    }
  }
}

The message_id in the event header serves as the unique cross-chain identifier that:

  • Links transactions between source and destination chains
  • Provides confirmation of successful message execution

Store this identifier in your application for transaction tracking and reconciliation.

Further Resources

Get the latest Chainlink content straight to your inbox.