Token Transfers: SVM to EVM

This tutorial demonstrates how to transfer tokens from a Solana Virtual Machine (SVM) to an Ethereum Virtual Machine (EVM) chain using Chainlink CCIP. You will learn how to build a CCIP message on Solana, send it to the CCIP router, and verify the transfer on the destination chain.

Introduction

This tutorial covers transferring tokens from Solana Devnet to Ethereum Sepolia without any additional data payload or program execution. When you transfer tokens using CCIP:

  1. Tokens are burned or locked in pools on the source pool
  2. Equivalent tokens are minted or released from the destination pool
  3. The process is managed by CCIP

What You Will Build

In this tutorial, you will:

  • Configure a CCIP message for token-only transfers
  • Send BnM test tokens from Solana Devnet to an Ethereum Sepolia address
  • Pay for CCIP transaction fees using native SOL
  • Monitor and verify your cross-chain transfer

Understanding Token Transfers from SVM to EVM

This tutorial focuses on token-only transfers from your wallet on Solana Devnet to an address on Ethereum Sepolia. Key points specific to token-only transfers:

  • Burn and Mint: In this tutorial, tokens are burned on Solana Devnet, and equivalent tokens are minted on Ethereum Sepolia
  • Fee Payment: Transaction fees are paid in native SOL on Solana plus CCIP fees for cross-chain processing. Note that you can pay for CCIP fees using LINK or wrapped SOL
  • Token Delegation: Requires pre-delegation of token authority to allow the CCIP Router to transfer your tokens

CCIP Message Configuration

The most important part of implementing a token transfer is configuring the CCIP message correctly. Here's the key configuration from the script:

const CCIP_MESSAGE_CONFIG: CCIPMessageConfig = {
  destinationChainSelector: CHAIN_SELECTORS[ChainId.ETHEREUM_SEPOLIA].toString(),
  evmReceiverAddress: "0x9d087fC03ae39b088326b67fA3C788236645b717", // Change this to your Ethereum address

  // Token transfers configuration - supports multiple tokens
  tokenAmounts: [
    {
      tokenMint: config.tokenMint, // BnM token on Solana Devnet
      amount: "10000000", // 0.01 tokens with 9 decimals
    },
  ],

  // Fee configuration
  feeToken: ConfigFeeTokenType.NATIVE, // Use SOL for fees

  // Message data (empty for token transfers)
  messageData: "", // Empty data for token transfer only

  // Extra arguments configuration
  extraArgs: {
    gasLimit: 0, // No execution on destination for token transfers
    allowOutOfOrderExecution: true, // Allow out-of-order execution
  },
}

Customizing the Receiver Address

To send tokens to your own Ethereum wallet, you must update the destination address in the script:

  1. Open the file ccip-scripts/svm/router/1_token-transfer.ts in the starter kit
  2. Locate the CCIP_MESSAGE_CONFIG object
  3. Edit the value of the evmReceiverAddress property to your Ethereum wallet address

Critical Configuration Settings

When setting up your CCIP message for token transfers, these parameters are crucial:

  • gasLimit: MUST be 0 for token-only transfers
  • evmReceiverAddress: Must be a valid Ethereum address (starting with "0x")
  • messageData: Empty string for token-only transfers

Understanding the Configuration Fields

  • destinationChainSelector: CCIP identifier for the destination chain (Ethereum Sepolia)
  • evmReceiverAddress: Ethereum wallet address that will receive the tokens
  • tokenAmounts: Array of tokens to transfer with their amounts
    • tokenMint: The Solana token mint address (BnM token: 7AC59PVvR64EoMnLX45FHnJAYzPsxdViyYBsaGEQPFvh)
    • amount: Token amount in raw format (10000000 = 0.01 BnM with 9 decimals)
  • feeToken: Token used to pay CCIP fees (NATIVE = SOL)
  • messageData: Empty for token-only transfers
  • extraArgs: Additional configuration for cross-chain execution
    • gasLimit: Set to 0 for token transfers (no execution needed)
    • allowOutOfOrderExecution: Set to true to allow out of order execution. Note: This is mandatory when sending CCIP messages from Solana as the source chain.

Customizing Your Token Transfer

Customize Token Amount

The token amount format needs to account for token decimals:

  • Solana BnM has 9 decimals
  • 10000000 represents 0.01 BnM (10,000,000 / 10^9)

How the Script Works

The token transfer script handles the interaction with the CCIP Router program without requiring you to write any code. Here's what happens behind the scenes:

  1. Context Initialization:

    • The script initializes the Solana connection
    • Loads your keypair
    • Sets up CCIP configuration
  2. Balance Validation:

    • Checks SOL balance (minimum 0.005 SOL required)
    • Validates token balances for all tokens being transferred
    • Verifies token delegation approvals
  3. Fee Calculation:

    • Queries the Router program to calculate cross-chain fees
    • Validates sufficient balance for fees
  4. Message Construction:

    • Builds the SVM2AnyMessage structure with token amounts, receiver, etc.
    • Encodes the message according to CCIP standards
    • Sets extra arguments for Ethereum compatibility
  5. Transaction Building:

    • Builds the CCIP send transaction with all necessary accounts
    • Includes all token accounts in the correct order
  6. Execution:

    • Signs and sends the transaction to the Solana Devnet network
    • Parses the transaction result to extract the message ID
    • Provides links to track the cross-chain message

Source Code Reference

For those interested in implementation details, the script is thoroughly documented with step-by-step comments. You can examine the complete source code at ccip-scripts/svm/router/1_token-transfer.ts in the starter kit repository to understand the precise mechanics of building and sending CCIP messages from Solana.

Running the Token Transfer

Prerequisites Check

Before running the script:

  1. Ensure you've completed all steps in the prerequisites guide
  2. Verify your token delegations are set up correctly: yarn svm:token:check
  3. Make sure you have sufficient SOL and BnM token balances

Execute the Script

Run the script using one of the following options:

yarn svm:token-transfer

Understanding the Output

When the script executes successfully, you'll see output similar to this:

==== Environment Information ====

[2025-05-01T20:46:55.790Z] Wallet public key: EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB
[2025-05-01T20:46:55.791Z]
==== Wallet Balance Information ====
[2025-05-01T20:46:59.331Z] SOL Balance: 13.224536955 SOL
[2025-05-01T20:46:59.332Z] Lamports Balance: 13224536955 lamports
[2025-05-01T20:46:59.332Z]
==== CCIP Router Information ====
[2025-05-01T20:46:59.332Z] CCIP Router Program ID: Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C
[2025-05-01T20:46:59.332Z] Fee Quoter Program ID: FeeQPGkKDeRV1MgoYfMH6L8o3KeuYjwUZrgn4LRKfjHi
[2025-05-01T20:46:59.332Z] RMN Remote Program ID: RmnXLft1mSEwDgMKu2okYuHkiazxntFFcZFrrcXxYg7
[2025-05-01T20:46:59.332Z]
==== CCIP Send Configuration ====
[2025-05-01T20:46:59.332Z] Destination Chain Selector: 16015286601757825753
[2025-05-01T20:46:59.332Z] Receiver Address: 0x9d087fC03ae39b088326b67fA3C788236645b717
[2025-05-01T20:46:59.332Z]
==== Token Transfer Details ====
[2025-05-01T20:46:59.333Z] Getting mint account info for 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6 to determine token program ID...
[2025-05-01T20:46:59.467Z] Detected Token-2022 Program: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
[2025-05-01T20:46:59.467Z] Fetching token decimals for 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6
[2025-05-01T20:46:59.607Z] Token 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6 has 9 decimals
[2025-05-01T20:46:59.639Z] Token 1: 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6
[2025-05-01T20:46:59.639Z] Amount 1: 10000000 raw units (0,01 tokens with 9 decimals)
[2025-05-01T20:46:59.639Z] Fee Token: native
[2025-05-01T20:46:59.639Z]
==== Fee Token Configuration ====
[2025-05-01T20:46:59.639Z] Using native SOL as fee token
[2025-05-01T20:46:59.639Z] No gasLimit provided in extraArgs, using default value: 0
[2025-05-01T20:46:59.641Z]
==== CCIP Message Request Created ====
[2025-05-01T20:46:59.641Z]
==== Compute Budget Configuration ====
[2025-05-01T20:46:59.642Z] Added compute budget instruction with limit: 1400000 units
[2025-05-01T20:46:59.642Z]
==== Fee Calculation ====
[2025-05-01T20:46:59.642Z] Calculating fee for this transaction...
[2025-05-01T20:46:59.642Z] Calculating fee for destination chain 16015286601757825753
[2025-05-01T20:46:59.950Z] Fee calculation complete: 9716055 tokens
[2025-05-01T20:46:59.950Z] Estimated fee: 0.009716055 SOL
[2025-05-01T20:46:59.951Z] Fee in Juels: 97863982000000000
[2025-05-01T20:46:59.951Z]
==== Balance Validation ====
[2025-05-01T20:47:00.085Z] SOL Balance: 13.224536955 SOL
[2025-05-01T20:47:00.085Z]
==== Token Balance Validation ====
[2025-05-01T20:47:00.085Z] Getting mint account info for 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6 to determine token program ID...
[2025-05-01T20:47:00.220Z] Detected Token-2022 Program: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
[2025-05-01T20:47:00.220Z] Fetching token decimals for 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6
[2025-05-01T20:47:00.358Z] Token 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6 has 9 decimals
[2025-05-01T20:47:00.359Z] Validating token 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6: 0,01 tokens (10000000 raw units)
[2025-05-01T20:47:00.509Z] ✅ Balance validation passed for token: 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6
[2025-05-01T20:47:00.510Z] ✅ All balance validations passed. Proceeding with transaction.
[2025-05-01T20:47:00.511Z]
==== Sending CCIP Message ====
[2025-05-01T20:47:00.511Z] ⏳ This may take a minute...
[2025-05-01T20:47:00.512Z] Sending CCIP message to destination chain 16015286601757825753
[2025-05-01T20:47:00.512Z] Building accounts for CCIP send to chain 16015286601757825753
[2025-05-01T20:47:02.693Z] CCIP message sent successfully: 51Tpaa5Hxu4dwRNeHr7wqPwkXmroq1bDKSUmVHHPtNbDEhJ6b7Sf9Wkzuau9qDDfh34MKDWPtuMRaq3WGdt9Qz3L
[2025-05-01T20:47:02.694Z] Parsing CCIP message sent event for transaction: 51Tpaa5Hxu4dwRNeHr7wqPwkXmroq1bDKSUmVHHPtNbDEhJ6b7Sf9Wkzuau9qDDfh34MKDWPtuMRaq3WGdt9Qz3L
[2025-05-01T20:47:02.863Z] Successfully extracted messageId: 0xf5a11db94f15b15b77d582e4a0ed3ff709b2fbf2464d1c7d1ed73620000260d9
[2025-05-01T20:47:02.863Z]
==== CCIP Message Sent Successfully ====
[2025-05-01T20:47:02.863Z] Transaction signature: 51Tpaa5Hxu4dwRNeHr7wqPwkXmroq1bDKSUmVHHPtNbDEhJ6b7Sf9Wkzuau9qDDfh34MKDWPtuMRaq3WGdt9Qz3L
[2025-05-01T20:47:02.863Z] Message ID: 0xf5a11db94f15b15b77d582e4a0ed3ff709b2fbf2464d1c7d1ed73620000260d9
[2025-05-01T20:47:02.863Z] Open the CCIP explorer: https://ccip.chain.link/msg/0xf5a11db94f15b15b77d582e4a0ed3ff709b2fbf2464d1c7d1ed73620000260d9
[2025-05-01T20:47:02.863Z]
View transaction on explorer:
[2025-05-01T20:47:02.863Z] https://explorer.solana.com/tx/51Tpaa5Hxu4dwRNeHr7wqPwkXmroq1bDKSUmVHHPtNbDEhJ6b7Sf9Wkzuau9qDDfh34MKDWPtuMRaq3WGdt9Qz3L?cluster=devnet

The output provides important information to track your transfer:

  • Environment Information: Your Solana configuration and wallet details
  • Wallet Balance: Confirms you have sufficient SOL
  • CCIP Router Information: The program IDs used for the transaction
  • Token Transfer Details: The tokens and amounts being transferred
  • Fee Calculation: The estimated fees of the cross-chain transfer
  • Balance Validation: Confirmation that all required balances are sufficient
  • Transaction Details: The signature, message ID, and explorer links

Verification and Monitoring

After sending your token transfer, you'll need to track it across chains:

  1. Track progress with Message ID: Use the CCIP Explorer link provided in the output to track your message status:

    https://ccip.chain.link/msg/YOUR_MESSAGE_ID
    
  2. Solana Explorer: Verify the transaction on Solana:

    https://explorer.solana.com/tx/YOUR_TRANSACTION_SIGNATURE?cluster=devnet
    
  3. Ethereum Explorer: Once the transfer is successful, verify token receipt on Ethereum:

    https://sepolia.etherscan.io/address/YOUR_ETHEREUM_ADDRESS
    

    Look for the transferred token in the "Token Transfers" section.

Get the latest Chainlink content straight to your inbox.