Skip to main content
Version: 1.1.1

Multi-Chain

The SDK provides a unified Chain interface across all supported blockchain families. This guide covers connecting to each chain, cross-family messaging, and chain-specific details.

For EVM-only usage, see Sending Messages and Tracking Messages — they cover the full workflow without multi-chain considerations.

Supported Chains

Chain ClassFamilyWallet TypeSendExecuteToken Pools
EVMChainEVMethers Signer or viemWallet(client)YesYesYes
SolanaChainSolanaAnchor-compatible WalletYesYesYes
AptosChainAptosAptosAsyncAccountYesYesYes
SuiChainSuiKeypairNoYesNo
TONChainTONTONWalletYesYesNo
note

Sui is execution-only — it can execute incoming messages but cannot send cross-chain messages. Token pool queries are not implemented on Sui or TON.

Connecting

TypeScript
import { EVMChain } from '@chainlink/ccip-sdk'

const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
console.log('Connected to:', chain.network.name)
console.log('Chain selector:', chain.network.chainSelector)

All chains accept the same ChainContext options:

TypeScript
const chain = await EVMChain.fromUrl('https://rpc.sepolia.org', {
logger: console, // Custom logger
apiClient: null, // Disable CCIP API (RPC-only mode)
})

Unified Interface

All chain classes implement the Chain interface, so you can write chain-agnostic code:

TypeScript
import {
type Chain,
EVMChain,
SolanaChain,
AptosChain,
networkInfo,
ChainFamily,
} from '@chainlink/ccip-sdk'

function createChain(rpcUrl: string, family: ChainFamily): Promise<Chain> {
switch (family) {
case ChainFamily.EVM:
return EVMChain.fromUrl(rpcUrl)
case ChainFamily.Solana:
return SolanaChain.fromUrl(rpcUrl)
case ChainFamily.Aptos:
return AptosChain.fromUrl(rpcUrl)
default:
throw new Error(`Unsupported family: ${family}`)
}
}

// Works with any chain
async function getMessages(chain: Chain, txHash: string) {
const requests = await chain.getMessagesInTx(txHash)
for (const req of requests) {
console.log('Message ID:', req.message.messageId)
}
return requests
}

Cross-Chain Token Transfers

For token-only transfers (no data), the SDK auto-populates extraArgs with sensible defaults for the destination chain. You only need receiver and tokenAmounts:

TypeScript
import { EVMChain, networkInfo } from '@chainlink/ccip-sdk'
import { parseUnits } from 'viem'

const source = await EVMChain.fromUrl('https://rpc.sepolia.org')
const router = '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59'

// Same code works regardless of destination chain family
const destSelector = networkInfo('solana-devnet').chainSelector // or 'avalanche-testnet-fuji', 'aptos-testnet'

const tokenInfo = await source.getTokenInfo(tokenAddress)
const amount = parseUnits('10', tokenInfo.decimals)

const message = {
receiver: 'RecipientAddressOnDestChain...',
tokenAmounts: [{ token: tokenAddress, amount }],
}

const fee = await source.getFee({ router, destChainSelector: destSelector, message })
await source.sendMessage({
router,
destChainSelector: destSelector,
message,
wallet,
})

The SDK handles computeUnits (Solana), gasLimit (EVM/Aptos), tokenReceiver, allowOutOfOrderExecution, and other chain-specific fields automatically.

Cross-Family Messaging

For messages with data (arbitrary messaging or programmable token transfers), you must provide chain-specific extraArgs. The SDK encodes them automatically — just pass the right fields.

For full details on receiver contract setup and account configuration, see the dedicated tutorials:

TypeScript
import { EVMChain, networkInfo } from '@chainlink/ccip-sdk'

const source = await EVMChain.fromUrl('https://rpc.sepolia.org')
const destSelector = networkInfo('solana-devnet').chainSelector

const message = {
receiver: 'SolanaProgramAddress...',
data: '0x1234...',
tokenAmounts: [],
extraArgs: {
computeUnits: 200000n,
accountIsWritableBitmap: 0n,
allowOutOfOrderExecution: true,
tokenReceiver: '', // Required when sending data + tokens
accounts: [], // Additional accounts the program needs
},
}

const fee = await source.getFee({ router, destChainSelector: destSelector, message })

When sending data + tokens, tokenReceiver must be set to the Solana wallet or PDA that will receive the tokens.

Extra Args by Destination

The extraArgs fields depend on the destination chain family:

DestinationExtra Args TypeKey Fields
EVMEVMExtraArgsV1/V2/V3gasLimit, allowOutOfOrderExecution
SolanaSVMExtraArgsV1computeUnits, accounts, accountIsWritableBitmap, tokenReceiver, allowOutOfOrderExecution
AptosEVMExtraArgsV2gasLimit, allowOutOfOrderExecution
SuiSuiExtraArgsV1gasLimit, allowOutOfOrderExecution, tokenReceiver, receiverObjectIds
TONEVMExtraArgsV2gasLimit, allowOutOfOrderExecution

For token-only transfers (no data), you can omit extraArgs entirely — the SDK fills in defaults.

Solana Notes

Transaction Flow

Solana uses transaction signatures (not hashes). Use getTransaction() to fetch details:

TypeScript
const chain = await SolanaChain.fromUrl('https://api.devnet.solana.com')

const tx = await chain.getTransaction(signature)
const messages = await chain.getMessagesInTx(tx)

Unsigned Transactions

For browser wallets (Phantom, Solflare), generate unsigned transactions and let the wallet handle signing, compute budget, and priority fees:

TypeScript
import { SolanaChain } from '@chainlink/ccip-sdk'
import { TransactionMessage, VersionedTransaction } from '@solana/web3.js'

const chain = await SolanaChain.fromUrl('https://api.devnet.solana.com')

const unsignedTx = await chain.generateUnsignedSendMessage({
sender: walletPublicKey.toBase58(),
router: routerAddress,
destChainSelector,
message,
})

// Build versioned transaction
const { blockhash } = await connection.getLatestBlockhash()
const messageV0 = new TransactionMessage({
payerKey: walletPublicKey,
recentBlockhash: blockhash,
instructions: unsignedTx.instructions,
}).compileToV0Message(unsignedTx.lookupTables)

const transaction = new VersionedTransaction(messageV0)
const signature = await sendTransaction(transaction, connection)