Skip to main content
Version: 1.1.1

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:

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

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

TypeScript
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

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

TypeScript
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

TypeScript
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

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

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

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:

TypeScript
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

MethodCalled onPurpose
getTokenAdminRegistryFor(address)ChainResolve TokenAdminRegistry from Router/OnRamp/OffRamp
getRegistryTokenConfig(registry, token)ChainGet token's registry entry (pool address, admin)
getTokenPoolConfig(pool)ChainPool config: token, router, typeAndVersion, minBlockConfirmations
getTokenPoolRemotes(pool, chainSelector?)ChainRemote chain config: remote token, pools, rate limits
getSupportedTokens(registry)ChainList all tokens in the registry
getTokenInfo(token)ChainToken metadata (symbol, decimals, name)