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:
- Tokens are burned or locked in pools on the source pool
- Equivalent tokens are minted or released from the destination pool
- 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:
- Open the file
ccip-scripts/svm/router/1_token-transfer.tsin the starter kit - Locate the
CCIP_MESSAGE_CONFIGobject - Edit the value of the
evmReceiverAddressproperty 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 transfersevmReceiverAddress: 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 tokenstokenAmounts: Array of tokens to transfer with their amountstokenMint: 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 transfersextraArgs: Additional configuration for cross-chain executiongasLimit: 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
10000000represents 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:
-
Context Initialization:
- The script initializes the Solana connection
- Loads your keypair
- Sets up CCIP configuration
-
Balance Validation:
- Checks SOL balance (minimum 0.005 SOL required)
- Validates token balances for all tokens being transferred
- Verifies token delegation approvals
-
Fee Calculation:
- Queries the Router program to calculate cross-chain fees
- Validates sufficient balance for fees
-
Message Construction:
- Builds the
SVM2AnyMessagestructure with token amounts, receiver, etc. - Encodes the message according to CCIP standards
- Sets extra arguments for Ethereum compatibility
- Builds the
-
Transaction Building:
- Builds the CCIP send transaction with all necessary accounts
- Includes all token accounts in the correct order
-
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:
- Ensure you've completed all steps in the prerequisites guide
- Verify your token delegations are set up correctly:
yarn svm:token:check - 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:
-
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 -
Solana Explorer: Verify the transaction on Solana:
https://explorer.solana.com/tx/YOUR_TRANSACTION_SIGNATURE?cluster=devnet -
Ethereum Explorer: Once the transfer is successful, verify token receipt on Ethereum:
https://sepolia.etherscan.io/address/YOUR_ETHEREUM_ADDRESSLook for the transferred token in the "Token Transfers" section.