Skip to main content
Version: 0.96.0

Querying Data

The SDK provides methods to query balances, token information, and lane characteristics.

Balance Queries

Native Token Balance

Query ETH, AVAX, SOL, or other native token balances:

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

const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')

const balance = await chain.getBalance({
holder: '0xWalletAddress...',
})

console.log('Balance:', formatEther(balance), 'ETH')

Token Balance

Query ERC20/SPL token balances:

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

const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')

const balance = await chain.getBalance({
holder: '0xWalletAddress...',
token: '0xTokenAddress...',
})

// Get decimals for formatting
const tokenInfo = await chain.getTokenInfo('0xTokenAddress...')
console.log('Balance:', formatUnits(balance, tokenInfo.decimals), tokenInfo.symbol)

Multi-Chain Balance Examples

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

const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')

// Native balance (ETH)
const nativeBalance = await chain.getBalance({
holder: '0xYourAddress...',
})

// ERC-20 token balance
const tokenBalance = await chain.getBalance({
holder: '0xYourAddress...',
token: '0xTokenAddress...',
})

Token Information

Get Token Metadata

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

const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')

const tokenInfo = await chain.getTokenInfo('0xTokenAddress...')

console.log('Name:', tokenInfo.name)
console.log('Symbol:', tokenInfo.symbol)
console.log('Decimals:', tokenInfo.decimals)

Use with Fee Estimation

Always fetch decimals before formatting amounts:

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

const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
const tokenAddress = '0xTokenAddress...'

// Get token decimals
const tokenInfo = await chain.getTokenInfo(tokenAddress)

// Parse user input with correct decimals
const amount = parseUnits('10', tokenInfo.decimals) // 10 tokens

const message = {
receiver: '0xReceiverAddress...',
data: '0x',
tokenAmounts: [{ token: tokenAddress, amount }],
}

const fee = await chain.getFee({
router: chain.network.router,
destChainSelector: networkInfo('avalanche-testnet-fuji').chainSelector,
message,
})

console.log('Fee:', formatUnits(fee, 18), 'ETH')

Lane Latency

Estimate transfer time for a specific lane:

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)

const minutes = Math.round(latency.totalMs / 60000)
console.log(`Estimated transfer time: ~${minutes} minutes`)

// Breakdown
console.log('Source finality:', latency.sourceFinality, 'ms')
console.log('Dest finality:', latency.destFinality, 'ms')

Display to Users

TypeScript
function formatLatency(totalMs: number): string {
const minutes = Math.round(totalMs / 60000)
if (minutes < 1) return 'Less than 1 minute'
if (minutes === 1) return '~1 minute'
return `~${minutes} minutes`
}

const latency = await chain.getLaneLatency(destSelector)
console.log('Transfer time:', formatLatency(latency.totalMs))

Network Information

Get Network by Name, ID, or Selector

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

// By name
const sepolia = networkInfo('ethereum-testnet-sepolia')

// By chain ID
const mainnet = networkInfo(1)

// By chain ID as string
const arbitrum = networkInfo('42161')

// By CCIP chain selector
const fuji = networkInfo(14767482510784806043n)

console.log('Name:', sepolia.name)
console.log('Chain ID:', sepolia.chainId)
console.log('Selector:', sepolia.chainSelector)
console.log('Family:', sepolia.family) // 'EVM', 'SVM', etc.

Chain Selectors vs Chain IDs

CCIP uses chain selectors (not chain IDs) to identify networks:

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

// Wrong: Using chain ID for CCIP operations
const destChain = 84532 // Base Sepolia chain ID - DON'T USE

// Correct: Using CCIP chain selector
const destSelector = networkInfo('ethereum-testnet-sepolia-base-1').chainSelector
// or
const destSelector = 10344971235874465080n // Base Sepolia selector

Chain selectors are unique identifiers assigned by CCIP that remain consistent across the protocol. Always use selectors for:

  • destChainSelector in messages
  • Lane configuration
  • Fee estimation

Supported Tokens

Get Supported Tokens

Query tokens configured in the TokenAdminRegistry:

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

const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')

// Get TokenAdminRegistry address from router
const registry = await chain.getTokenAdminRegistryFor(chain.network.router)

// Get all supported tokens
const tokens = await chain.getSupportedTokens(registry)

console.log('Supported tokens:', tokens)
for (const token of tokens) {
const info = await chain.getTokenInfo(token)
console.log(` ${info.symbol}: ${token}`)
}

Get Fee Tokens

TypeScript
const feeTokens = await chain.getFeeTokens(chain.network.router)

console.log('Fee tokens:', feeTokens)
// Returns Record<string, TokenInfo> with token addresses as keys
for (const [address, info] of Object.entries(feeTokens)) {
console.log(` ${info.symbol}: ${address}`)
}

Complete Example

Build a transfer form with all necessary data:

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 destSelector = networkInfo(destNetworkName).chainSelector

// Get registry first for token queries
const registry = await chain.getTokenAdminRegistryFor(chain.network.router)

// Parallel queries for efficiency
const [
tokenInfo,
balance,
nativeBalance,
latency,
supportedTokens,
] = await Promise.all([
chain.getTokenInfo(tokenAddress),
chain.getBalance({ holder: walletAddress, token: tokenAddress }),
chain.getBalance({ holder: walletAddress }),
chain.getLaneLatency(destSelector),
chain.getSupportedTokens(registry),
])

// 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: chain.network.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),
estimatedTime: `~${Math.round(latency.totalMs / 60000)} minutes`,
isSupported,
}
}

// Usage
const info = await getTransferInfo(
'https://rpc.sepolia.org',
'avalanche-testnet-fuji',
'0xMyWallet...',
'0xTokenAddress...',
'10.5'
)

console.log(`Transfer ${info.amount} ${info.token.symbol}`)
console.log(`Fee: ${info.fee} ETH`)
console.log(`Time: ${info.estimatedTime}`)

CCIP API Client

For standalone API queries without creating a chain instance:

Standalone Usage

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

const api = new CCIPAPIClient()

// Get estimated delivery time
const latency = await api.getLaneLatency(
5009297550715157269n, // Ethereum mainnet selector
4949039107694359620n, // Arbitrum mainnet selector
)

console.log(`Estimated delivery: ${Math.round(latency.totalMs / 60000)} minutes`)

Fetch Message by ID

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

const api = new CCIPAPIClient()

const request = await api.getMessageById(messageId)

console.log('Message ID:', request.message.messageId)
console.log('Sender:', request.message.sender)
console.log('Status:', request.metadata?.status)
console.log('Delivery time:', request.metadata?.deliveryTime, 'ms')

Find Messages in Transaction

TypeScript
const api = new CCIPAPIClient()

const messageIds = await api.getMessageIdsInTx(txHash)
console.log(`Found ${messageIds.length} CCIP message(s)`)

for (const id of messageIds) {
const request = await api.getMessageById(id)
console.log(`Message ${id}: ${request.metadata?.status}`)
}

Custom Configuration

TypeScript
const api = new CCIPAPIClient('https://api.ccip.chain.link', {
timeoutMs: 60000, // Request timeout (default: 30000)
logger: customLogger, // Custom logger
fetch: customFetch, // Custom fetch function
})