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, CCIPError } from '@chainlink/ccip-sdk'
import { formatEther } from 'viem'
const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
try {
const balance = await chain.getBalance({
holder: '0xWalletAddress...',
})
console.log('Balance:', formatEther(balance), 'ETH')
} catch (error) {
if (CCIPError.isCCIPError(error)) {
console.error('Failed to get balance:', error.message)
} else {
throw error
}
}
Token Balance
Query ERC20/SPL token balances:
TypeScript
import { EVMChain, CCIPError } from '@chainlink/ccip-sdk'
import { formatUnits } from 'viem'
const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
try {
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)
} catch (error) {
if (CCIPError.isCCIPError(error)) {
console.error('Failed to get token balance:', error.message)
} else {
throw error
}
}
Multi-Chain Balance Examples
- EVM
- Solana
- Aptos
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...',
})
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',
})
TypeScript
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',
})
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:
destChainSelectorin 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
})
Related
- Sending Messages - Send transfers
- Token Pools - Query pool configuration
- Tracking Messages - Track transfer status