Skip to main content
Version: 1.1.1

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.

TypeScript
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:

TypeScript
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.

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
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.

TypeScript
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:

FieldTypeMeaning
MIN_BLOCK_CONFIRMATIONSnumber | undefinedundefined = pre-v2.0 lane (no FTF), 0 = FTF supported but not enabled for this token, >0 = FTF enabled with this many block confirmations
RATE_LIMITSRateLimiterState | nullStandard rate limiter state (null if disabled)
CUSTOM_BLOCK_CONFIRMATIONS_RATE_LIMITSRateLimiterState | nullFTF-specific rate limiter (only when MIN_BLOCK_CONFIRMATIONS > 0)
TypeScript
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)
}
note

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).

TypeScript
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.

TypeScript
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:

TypeScript
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.

TypeScript
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:

TypeScript
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',
})

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.

TypeScript
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

TypeScript
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

TypeScript
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:

TypeScript
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)

TypeScript
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

TypeScript
const api = CCIPAPIClient.fromUrl('https://custom-api.example.com', {
timeoutMs: 60000, // Request timeout (default: 30000)
logger: customLogger, // Custom logger
fetch: customFetch, // Custom fetch function
})

Generate links to the CCIP Explorer — no API or RPC needed:

TypeScript
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:

TypeScript
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

MethodCalled onData sourcePurpose
networkInfo(selectorOrIdOrName)— (standalone)Built-in registryResolve chain names, IDs, and selectors
getTokenInfo(token)ChainRPCGet token symbol, decimals, and name
getSupportedTokens(registry)ChainRPCList tokens configured in TokenAdminRegistry
getTokenAdminRegistryFor(address)ChainRPCResolve TokenAdminRegistry from Router/OnRamp/OffRamp
getFeeTokens(router)ChainRPCList accepted fee tokens with metadata
getLaneFeatures({ router, destChainSelector, token? })ChainRPCQuery lane capabilities (FTF, rate limits)
getLaneLatency(destSelector)ChainCCIP APIEstimated delivery time
getFee({ router, destChainSelector, message })ChainRPCFee estimation in fee token units
getBalance({ holder, token? })ChainRPCNative or token balance
getCCIPExplorerUrl(type, value)— (standalone)NoneGenerate CCIP Explorer URLs
CCIPAPIClient.fromUrl(url?)— (static)Create standalone API client
api.getMessageById(id)CCIPAPIClientCCIP APIFull message with lifecycle metadata
api.getMessageIdsInTx(txHash)CCIPAPIClientCCIP APIMessage IDs in a transaction
api.getExecutionInput(messageId)CCIPAPIClientCCIP APIProof and off-chain data for manual execution
api.getLaneLatency(source, dest)CCIPAPIClientCCIP APIEstimated delivery time (standalone)