Querying Data
The SDK provides methods to query network information, token metadata, lane capabilities, balances, and fees. Most methods are RPC-based and work without the CCIP API.
Network Information
Look Up Chains
networkInfo resolves chain selectors, chain IDs, and chain names to a NetworkInfo object. The result is memoized.
import { networkInfo } from '@chainlink/ccip-sdk'
// By name
const sepolia = networkInfo('ethereum-testnet-sepolia')
// By chain ID (number)
const mainnet = networkInfo(1)
// By CCIP chain selector (bigint)
const fuji = networkInfo(14767482510784806043n)
console.log('Name:', sepolia.name) // 'ethereum-testnet-sepolia'
console.log('Chain ID:', sepolia.chainId) // 11155111
console.log('Selector:', sepolia.chainSelector) // 16015286601757825753n
console.log('Family:', sepolia.family) // 'EVM'
Throws CCIPChainNotFoundError if the chain is not supported by CCIP.
Chain Selectors vs Chain IDs
CCIP uses chain selectors (not chain IDs) to identify networks. Always use selectors for destChainSelector, lane configuration, and fee estimation:
import { networkInfo } from '@chainlink/ccip-sdk'
// Wrong: chain ID
const destChain = 84532 // Base Sepolia chain ID — DON'T USE
// Correct: CCIP chain selector
const destSelector = networkInfo('ethereum-testnet-sepolia-base-1').chainSelector
// => 10344971235874465080n
Token Queries
Token Metadata
getTokenInfo returns the symbol, decimals, and optional name for any token. RPC-only.
import { EVMChain } from '@chainlink/ccip-sdk'
const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
const tokenInfo = await chain.getTokenInfo('0xTokenAddress...')
console.log('Symbol:', tokenInfo.symbol) // e.g. 'LINK'
console.log('Decimals:', tokenInfo.decimals) // e.g. 18
console.log('Name:', tokenInfo.name) // e.g. 'ChainLink Token' (optional)
Use getTokenInfo whenever you need to parse or format token amounts:
import { parseUnits, formatUnits } from 'viem'
const tokenInfo = await chain.getTokenInfo(tokenAddress)
// Parse user input with correct decimals
const amount = parseUnits('10.5', tokenInfo.decimals)
// Format on-chain amounts for display
const display = formatUnits(balance, tokenInfo.decimals)
console.log(`${display} ${tokenInfo.symbol}`)
Supported Tokens
Query which tokens are configured for cross-chain transfers on a given router:
import { EVMChain } from '@chainlink/ccip-sdk'
const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
const router = '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59'
// Step 1: Get the TokenAdminRegistry address
const registry = await chain.getTokenAdminRegistryFor(router)
// Step 2: Get all configured tokens
const tokens = await chain.getSupportedTokens(registry)
for (const token of tokens) {
const info = await chain.getTokenInfo(token)
console.log(`${info.symbol} (${info.decimals} decimals): ${token}`)
}
getTokenAdminRegistryFor accepts a Router, OnRamp, or OffRamp address — it auto-detects the contract type and resolves the registry.
Fee Tokens
getFeeTokens returns the tokens accepted for paying CCIP fees on a given router, along with their metadata:
const router = '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59'
const feeTokens = await chain.getFeeTokens(router)
// Record<string, TokenInfo> — token address → { symbol, decimals, name? }
for (const [address, info] of Object.entries(feeTokens)) {
console.log(`${info.symbol}: ${address}`)
}
Lane Queries
Lane Capabilities
getLaneFeatures checks what features are available on a specific lane. Use this to adapt your message parameters — for example, to set blockConfirmations in extra args when Faster-Than-Finality (FTF) is enabled.
import { EVMChain, networkInfo } from '@chainlink/ccip-sdk'
const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
const router = '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59'
const destSelector = networkInfo('avalanche-testnet-fuji').chainSelector
// Without token — lane-level features
const laneFeatures = await chain.getLaneFeatures({ router, destChainSelector: destSelector })
// With token — token-specific features (rate limits, FTF for this token)
const tokenFeatures = await chain.getLaneFeatures({
router,
destChainSelector: destSelector,
token: '0xTokenAddress...',
})
Returns Partial<LaneFeatures> — not all fields are guaranteed:
| Field | Type | Meaning |
|---|---|---|
MIN_BLOCK_CONFIRMATIONS | number | undefined | undefined = pre-v2.0 lane (no FTF), 0 = FTF supported but not enabled for this token, >0 = FTF enabled with this many block confirmations |
RATE_LIMITS | RateLimiterState | null | Standard rate limiter state (null if disabled) |
CUSTOM_BLOCK_CONFIRMATIONS_RATE_LIMITS | RateLimiterState | null | FTF-specific rate limiter (only when MIN_BLOCK_CONFIRMATIONS > 0) |
const features = await chain.getLaneFeatures({
router,
destChainSelector: destSelector,
token: tokenAddress,
})
if (features.MIN_BLOCK_CONFIRMATIONS != null && features.MIN_BLOCK_CONFIRMATIONS > 0) {
console.log('FTF enabled — block confirmations:', features.MIN_BLOCK_CONFIRMATIONS)
// Can set blockConfirmations in extraArgs for faster delivery
}
if (features.RATE_LIMITS) {
console.log('Rate limit capacity:', features.RATE_LIMITS.capacity)
console.log('Available tokens:', features.RATE_LIMITS.tokens)
}
getLaneFeatures is currently implemented for EVM chains only. Requires token to query token-specific features (rate limits, FTF).
Lane Latency
getLaneLatency estimates the delivery time for a lane. Requires the CCIP API (enabled by default).
import { EVMChain, networkInfo } from '@chainlink/ccip-sdk'
const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
const destSelector = networkInfo('avalanche-testnet-fuji').chainSelector
const latency = await chain.getLaneLatency(destSelector)
console.log(`Estimated delivery: ~${Math.round(latency.totalMs / 60000)} minutes`)
Throws CCIPApiClientNotAvailableError if you created the chain with apiClient: null. Throws CCIPLaneNotFoundError if the lane doesn't exist.
Fee Estimation
getFee returns the fee amount in the fee token's smallest units. RPC-only — calls the Router contract directly.
import { EVMChain, networkInfo } from '@chainlink/ccip-sdk'
import { parseUnits, formatUnits } from 'viem'
const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
const router = '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59'
const destSelector = networkInfo('avalanche-testnet-fuji').chainSelector
const tokenAddress = '0xTokenAddress...'
const tokenInfo = await chain.getTokenInfo(tokenAddress)
const amount = parseUnits('10', tokenInfo.decimals)
const fee = await chain.getFee({
router,
destChainSelector: destSelector,
message: {
receiver: '0xReceiverAddress...',
data: '0x',
tokenAmounts: [{ token: tokenAddress, amount }],
},
})
// Fee is in native token units (wei) when paying with native, or fee token units
console.log('Fee:', formatUnits(fee, 18), 'ETH')
To pay with a specific fee token (e.g., LINK), set feeToken in the message:
const fee = await chain.getFee({
router,
destChainSelector: destSelector,
message: {
receiver: '0xReceiverAddress...',
data: '0x',
tokenAmounts: [],
feeToken: '0xLINKAddress...', // Pay fees in LINK
},
})
Balance Queries
getBalance returns balances in the token's smallest units. RPC-only.
import { EVMChain } from '@chainlink/ccip-sdk'
import { formatEther, formatUnits } from 'viem'
const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
const holder = '0xWalletAddress...'
// Native token balance (ETH)
const nativeBalance = await chain.getBalance({ holder })
console.log('ETH:', formatEther(nativeBalance))
// ERC-20 token balance
const tokenAddress = '0xTokenAddress...'
const tokenBalance = await chain.getBalance({ holder, token: tokenAddress })
const tokenInfo = await chain.getTokenInfo(tokenAddress)
console.log(`${tokenInfo.symbol}:`, formatUnits(tokenBalance, tokenInfo.decimals))
Works across all chain families:
- Solana
- Aptos
import { SolanaChain } from '@chainlink/ccip-sdk'
const chain = await SolanaChain.fromUrl('https://api.devnet.solana.com')
// SOL balance
const solBalance = await chain.getBalance({ holder: 'YourSolanaAddress' })
// SPL Token balance (auto-detects Token-2022)
const splBalance = await chain.getBalance({
holder: 'YourSolanaAddress',
token: 'TokenMintAddress',
})
import { AptosChain } from '@chainlink/ccip-sdk'
const chain = await AptosChain.fromUrl('https://api.testnet.aptoslabs.com/v1')
// APT balance
const aptBalance = await chain.getBalance({ holder: '0xYourAptosAddress' })
// Fungible Asset balance
const faBalance = await chain.getBalance({
holder: '0xYourAptosAddress',
token: '0xFungibleAssetAddress',
})
CCIP API Client
For standalone API queries without creating a chain instance, use CCIPAPIClient directly. This is useful for backend services that don't need RPC connections.
import { CCIPAPIClient } from '@chainlink/ccip-sdk'
const api = CCIPAPIClient.fromUrl() // defaults to https://api.ccip.chain.link
fromUrl is memoized — calling it with the same URL returns the same instance.
Get Message by ID
const request = await api.getMessageById('0xMessageId...')
console.log('Status:', request.metadata.status)
console.log('Source:', request.metadata.sourceNetworkInfo.name)
console.log('Dest:', request.metadata.destNetworkInfo.name)
if (request.metadata.deliveryTime) {
console.log('Delivery time:', request.metadata.deliveryTime, 'ms')
}
Find Messages in Transaction
const messageIds = await api.getMessageIdsInTx('0xTxHash...')
for (const id of messageIds) {
const request = await api.getMessageById(id)
console.log(`${id}: ${request.metadata.status}`)
}
Get Execution Input
Fetch execution inputs (proof, off-chain token data) for manual execution:
const execInput = await api.getExecutionInput('0xMessageId...')
console.log('OffRamp:', execInput.offRamp)
console.log('Version:', execInput.version)
// Use with dest.execute()
const { offRamp, ...input } = execInput
await dest.execute({ offRamp, input, wallet })
Lane Latency (Standalone)
const latency = await api.getLaneLatency(
5009297550715157269n, // Ethereum mainnet selector
4949039107694359620n, // Arbitrum mainnet selector
)
console.log(`Estimated delivery: ~${Math.round(latency.totalMs / 60000)} minutes`)
Unlike chain.getLaneLatency(destSelector), the standalone version requires both source and destination selectors.
Custom Configuration
const api = CCIPAPIClient.fromUrl('https://custom-api.example.com', {
timeoutMs: 60000, // Request timeout (default: 30000)
logger: customLogger, // Custom logger
fetch: customFetch, // Custom fetch function
})
Explorer Links
Generate links to the CCIP Explorer — no API or RPC needed:
import { getCCIPExplorerUrl } from '@chainlink/ccip-sdk'
const messageUrl = getCCIPExplorerUrl('msg', messageId)
// => 'https://ccip.chain.link/msg/0x...'
const txUrl = getCCIPExplorerUrl('tx', txHash)
// => 'https://ccip.chain.link/tx/0x...'
const addressUrl = getCCIPExplorerUrl('address', senderAddress)
// => 'https://ccip.chain.link/address/0x...'
Complete Example
Build a transfer form that queries all necessary data before sending:
import { EVMChain, networkInfo } from '@chainlink/ccip-sdk'
import { formatUnits, parseUnits } from 'viem'
async function getTransferInfo(
sourceRpc: string,
destNetworkName: string,
walletAddress: string,
tokenAddress: string,
amountString: string
) {
const chain = await EVMChain.fromUrl(sourceRpc)
const router = '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59' // Sepolia Router
const destSelector = networkInfo(destNetworkName).chainSelector
// Get registry for token queries
const registry = await chain.getTokenAdminRegistryFor(router)
// Parallel queries — all RPC-based except latency
const [tokenInfo, balance, nativeBalance, supportedTokens, laneFeatures] =
await Promise.all([
chain.getTokenInfo(tokenAddress),
chain.getBalance({ holder: walletAddress, token: tokenAddress }),
chain.getBalance({ holder: walletAddress }),
chain.getSupportedTokens(registry),
chain.getLaneFeatures({ router, destChainSelector: destSelector, token: tokenAddress }),
])
// Check if token is supported
const isSupported = supportedTokens.some(
(t) => t.toLowerCase() === tokenAddress.toLowerCase()
)
if (!isSupported) {
throw new Error(`Token ${tokenInfo.symbol} not supported on this lane`)
}
// Parse amount and check balance
const amount = parseUnits(amountString, tokenInfo.decimals)
if (amount > balance) {
throw new Error('Insufficient token balance')
}
// Estimate fee
const fee = await chain.getFee({
router,
destChainSelector: destSelector,
message: {
receiver: walletAddress,
data: '0x',
tokenAmounts: [{ token: tokenAddress, amount }],
},
})
if (fee > nativeBalance) {
throw new Error('Insufficient native balance for fee')
}
return {
token: tokenInfo,
balance: formatUnits(balance, tokenInfo.decimals),
amount: amountString,
fee: formatUnits(fee, 18),
ftfEnabled:
laneFeatures.MIN_BLOCK_CONFIRMATIONS != null &&
laneFeatures.MIN_BLOCK_CONFIRMATIONS > 0,
rateLimited: laneFeatures.RATE_LIMITS != null,
}
}
Method Reference
| Method | Called on | Data source | Purpose |
|---|---|---|---|
networkInfo(selectorOrIdOrName) | — (standalone) | Built-in registry | Resolve chain names, IDs, and selectors |
getTokenInfo(token) | Chain | RPC | Get token symbol, decimals, and name |
getSupportedTokens(registry) | Chain | RPC | List tokens configured in TokenAdminRegistry |
getTokenAdminRegistryFor(address) | Chain | RPC | Resolve TokenAdminRegistry from Router/OnRamp/OffRamp |
getFeeTokens(router) | Chain | RPC | List accepted fee tokens with metadata |
getLaneFeatures({ router, destChainSelector, token? }) | Chain | RPC | Query lane capabilities (FTF, rate limits) |
getLaneLatency(destSelector) | Chain | CCIP API | Estimated delivery time |
getFee({ router, destChainSelector, message }) | Chain | RPC | Fee estimation in fee token units |
getBalance({ holder, token? }) | Chain | RPC | Native or token balance |
getCCIPExplorerUrl(type, value) | — (standalone) | None | Generate CCIP Explorer URLs |
CCIPAPIClient.fromUrl(url?) | — (static) | — | Create standalone API client |
api.getMessageById(id) | CCIPAPIClient | CCIP API | Full message with lifecycle metadata |
api.getMessageIdsInTx(txHash) | CCIPAPIClient | CCIP API | Message IDs in a transaction |
api.getExecutionInput(messageId) | CCIPAPIClient | CCIP API | Proof and off-chain data for manual execution |
api.getLaneLatency(source, dest) | CCIPAPIClient | CCIP API | Estimated delivery time (standalone) |
Related
- Sending Messages — Send cross-chain messages
- Token Pools — Query pool configuration and rate limits
- Tracking Messages — Track message lifecycle