Token Pools
Token pools are contracts that manage cross-chain token transfers. Each supported token has a pool that handles lock/release or burn/mint mechanics, rate limiting, and cross-chain routing. The SDK provides methods to query pool configuration and rate limit status — all RPC-based.
Find the Token Pool Address
Tokens are registered in a TokenAdminRegistry contract. To find the pool address for a token, query the registry:
import { EVMChain } from '@chainlink/ccip-sdk'
const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
const router = '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59'
// Step 1: Get the TokenAdminRegistry for this router
const registry = await chain.getTokenAdminRegistryFor(router)
// Step 2: Get the token's registry entry
const tokenConfig = await chain.getRegistryTokenConfig(registry, tokenAddress)
if (!tokenConfig.tokenPool) {
console.log('No pool configured for this token')
} else {
console.log('Token pool:', tokenConfig.tokenPool)
}
getTokenAdminRegistryFor accepts a Router, OnRamp, or OffRamp address — it auto-detects the contract type and resolves the registry.
Pool Configuration
getTokenPoolConfig returns the pool's token, router, and version string:
const poolConfig = await chain.getTokenPoolConfig(poolAddress)
console.log('Token:', poolConfig.token)
console.log('Router:', poolConfig.router)
console.log('Type and version:', poolConfig.typeAndVersion)
// e.g. "BurnMintTokenPool 1.5.0", "LockReleaseTokenPool 2.0.0"
The typeAndVersion string identifies the pool type (BurnMint, LockRelease, USDC, etc.) and version. The SDK does not expose pool types as an enum — parse the string if you need the pool type.
Faster-Than-Finality (FTF) Configuration
On v2.0+ pools, getTokenPoolConfig also returns minBlockConfirmations:
const poolConfig = await chain.getTokenPoolConfig(poolAddress)
if (poolConfig.minBlockConfirmations != null) {
if (poolConfig.minBlockConfirmations === 0) {
console.log('FTF supported but not enabled for this pool')
} else {
console.log('FTF enabled — min block confirmations:', poolConfig.minBlockConfirmations)
}
}
// undefined means pre-v2.0 pool (FTF not supported)
Remote Chain Configuration
getTokenPoolRemotes returns how the pool connects to other chains — remote token addresses, remote pool addresses, and rate limiter state.
Query All Remote Chains
const remotes = await chain.getTokenPoolRemotes(poolAddress)
// Record<string, TokenPoolRemote> — keys are chain selectors as strings
for (const [chainSelector, remote] of Object.entries(remotes)) {
console.log(`Chain ${chainSelector}:`)
console.log(' Remote token:', remote.remoteToken)
console.log(' Remote pools:', remote.remotePools)
}
Query a Specific Remote Chain
Pass a chain selector to filter to a single destination:
import { networkInfo } from '@chainlink/ccip-sdk'
const destSelector = networkInfo('avalanche-testnet-fuji').chainSelector
const remotes = await chain.getTokenPoolRemotes(poolAddress, destSelector)
const remote = Object.values(remotes)[0]
if (remote) {
console.log('Remote token:', remote.remoteToken)
console.log('Remote pools:', remote.remotePools)
}
Rate Limits
Token pools use token-bucket rate limiters to cap throughput in each direction:
- Outbound — limits tokens leaving the chain (checked on source)
- Inbound — limits tokens entering the chain (checked on destination)
Each rate limiter state is null when rate limiting is disabled for that direction.
Check Rate Limit Status
const remotes = await chain.getTokenPoolRemotes(poolAddress, destSelector)
const remote = Object.values(remotes)[0]
if (remote) {
// Outbound rate limit (null if disabled)
if (remote.outboundRateLimiterState) {
const { tokens, capacity, rate } = remote.outboundRateLimiterState
console.log('Outbound rate limit:')
console.log(` Available: ${tokens} / ${capacity} tokens`)
console.log(` Refill rate: ${rate} tokens/second`)
} else {
console.log('Outbound rate limiting: disabled')
}
// Inbound rate limit (null if disabled)
if (remote.inboundRateLimiterState) {
const { tokens, capacity, rate } = remote.inboundRateLimiterState
console.log('Inbound rate limit:')
console.log(` Available: ${tokens} / ${capacity} tokens`)
console.log(` Refill rate: ${rate} tokens/second`)
} else {
console.log('Inbound rate limiting: disabled')
}
}
RateLimiterState Type
type RateLimiterState = {
tokens: bigint // Current tokens available in the bucket
capacity: bigint // Maximum bucket capacity
rate: bigint // Refill rate (tokens per second)
} | null // null = rate limiting disabled
FTF Rate Limits
TokenPool v2.0+ contracts may include separate rate limiters for Faster-Than-Finality transfers. These apply when custom (lower) block confirmations are used instead of waiting for full finality:
const remotes = await chain.getTokenPoolRemotes(poolAddress, destSelector)
const remote = Object.values(remotes)[0]
if (remote && 'customBlockConfirmationsOutboundRateLimiterState' in remote) {
const ftfOutbound = remote.customBlockConfirmationsOutboundRateLimiterState
if (ftfOutbound) {
console.log('FTF outbound:', ftfOutbound.tokens, '/', ftfOutbound.capacity)
}
const ftfInbound = remote.customBlockConfirmationsInboundRateLimiterState
if (ftfInbound) {
console.log('FTF inbound:', ftfInbound.tokens, '/', ftfInbound.capacity)
}
}
FTF rate limiter fields are only present on v2.0+ pools. Use the in operator to check before accessing them.
Complete Example
Query full token pool information for a lane:
import { EVMChain, networkInfo } from '@chainlink/ccip-sdk'
async function inspectTokenPool(
rpcUrl: string,
routerAddress: string,
tokenAddress: string,
destNetworkName: string
) {
const chain = await EVMChain.fromUrl(rpcUrl)
const destSelector = networkInfo(destNetworkName).chainSelector
// 1. Find the pool
const registry = await chain.getTokenAdminRegistryFor(routerAddress)
const tokenConfig = await chain.getRegistryTokenConfig(registry, tokenAddress)
if (!tokenConfig.tokenPool) {
console.log('No pool configured for this token')
return
}
const poolAddress = tokenConfig.tokenPool
console.log('Pool:', poolAddress)
// 2. Get pool config
const poolConfig = await chain.getTokenPoolConfig(poolAddress)
console.log('Type:', poolConfig.typeAndVersion)
if (poolConfig.minBlockConfirmations != null && poolConfig.minBlockConfirmations > 0) {
console.log('FTF enabled — min confirmations:', poolConfig.minBlockConfirmations)
}
// 3. Get remote configuration for the destination
const remotes = await chain.getTokenPoolRemotes(poolAddress, destSelector)
const remote = Object.values(remotes)[0]
if (!remote) {
console.log('No remote configuration for', destNetworkName)
return
}
console.log('\nRemote token:', remote.remoteToken)
console.log('Remote pools:', remote.remotePools)
// 4. Rate limits
if (remote.outboundRateLimiterState) {
const { tokens, capacity, rate } = remote.outboundRateLimiterState
const pct = Number((tokens * 100n) / capacity)
console.log(`\nOutbound: ${pct}% available (${tokens}/${capacity})`)
console.log(`Refill: ${rate} tokens/sec`)
}
if (remote.inboundRateLimiterState) {
const { tokens, capacity, rate } = remote.inboundRateLimiterState
const pct = Number((tokens * 100n) / capacity)
console.log(`\nInbound: ${pct}% available (${tokens}/${capacity})`)
console.log(`Refill: ${rate} tokens/sec`)
}
// 5. FTF rate limits (v2.0+ only)
if ('customBlockConfirmationsOutboundRateLimiterState' in remote) {
const ftfOut = remote.customBlockConfirmationsOutboundRateLimiterState
const ftfIn = remote.customBlockConfirmationsInboundRateLimiterState
if (ftfOut) {
console.log(`\nFTF outbound: ${ftfOut.tokens}/${ftfOut.capacity}`)
}
if (ftfIn) {
console.log(`FTF inbound: ${ftfIn.tokens}/${ftfIn.capacity}`)
}
}
}
// Usage
await inspectTokenPool(
'https://rpc.sepolia.org',
'0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59',
'0xTokenAddress...',
'avalanche-testnet-fuji'
)
Method Reference
| Method | Called on | Purpose |
|---|---|---|
getTokenAdminRegistryFor(address) | Chain | Resolve TokenAdminRegistry from Router/OnRamp/OffRamp |
getRegistryTokenConfig(registry, token) | Chain | Get token's registry entry (pool address, admin) |
getTokenPoolConfig(pool) | Chain | Pool config: token, router, typeAndVersion, minBlockConfirmations |
getTokenPoolRemotes(pool, chainSelector?) | Chain | Remote chain config: remote token, pools, rate limits |
getSupportedTokens(registry) | Chain | List all tokens in the registry |
getTokenInfo(token) | Chain | Token metadata (symbol, decimals, name) |
Related
- Querying Data — Token queries, lane features, and balances
- Sending Messages — Send token transfers
- Error Handling — Handle errors and retries