Skip to main content
Version: 1.6.2

Faster-Than-Finality (FTF)

FTF allows CCIP messages to be relayed before full source-chain finality by specifying a lower number of block confirmations. It is available on v2.0+ lanes where the token pool has FTF enabled.

Check FTF Availability​

Call getLaneFeatures with a token to get pool-specific FTF data:

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

const source = await EVMChain.fromUrl('https://rpc.sepolia.org')
const router = '0xRouterAddress...'
const destSelector = networkInfo('avalanche-testnet-fuji').chainSelector

const features = await source.getLaneFeatures({
router,
destChainSelector: destSelector,
token: '0xTokenAddress...',
})

const minConfirmations = features.FINALITY_FAST

Interpret FINALITY_FAST:

ValueLane versionMeaning
undefinedPre-v2.0FTF not supported. Use V2 extraArgs only.
0v2.0+V3 extraArgs supported, but FTF not enabled for this token. finality has no effect.
> 0v2.0+ with FTFFTF enabled. Use finality >= minValue in V3 extraArgs.
note

Without a token, the result reflects lane version only: undefined for pre-v2.0, or a hardcoded 1 for v2.0+ (confirming V3 support without pool-specific FTF data). Pass a token to get the actual pool-specific value.

note

getLaneFeatures is implemented for EVM chains only.

Send with FTF​

Set finality in extraArgs. The SDK auto-detects V3 encoding when any V3-only field is present:

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

const source = await EVMChain.fromUrl('https://rpc.sepolia.org')
const router = '0xRouterAddress...'
const destSelector = networkInfo('avalanche-testnet-fuji').chainSelector

const features = await source.getLaneFeatures({
router,
destChainSelector: destSelector,
token: '0xTokenAddress...',
})

const minConfirmations = features.FINALITY_FAST

let extraArgs
if (minConfirmations != null && minConfirmations > 0) {
// FTF enabled — use V3 with finality
extraArgs = {
gasLimit: 200_000n,
finality: minConfirmations,
}
} else if (minConfirmations != null) {
// v2.0+ but FTF not enabled — V2-style args work
extraArgs = {
gasLimit: 200_000n,
allowOutOfOrderExecution: true,
}
} else {
// Pre-v2.0 — V2 encoding only
extraArgs = {
gasLimit: 200_000n,
allowOutOfOrderExecution: true,
}
}

await source.sendMessage({
router,
destChainSelector: destSelector,
message: {
receiver: '0xReceiverAddress...',
tokenAmounts: [{ token: '0xTokenAddress...', amount: 1_000_000n }],
extraArgs,
},
wallet,
})

The SDK does not validate extraArgs against the lane's on-chain version. Passing V3 fields (e.g., finality) to a pre-v2.0 lane will revert on-chain.

Estimate FTF Latency​

Pass numberOfBlocks to getLaneLatency to estimate delivery time with FTF:

TypeScript
// Standard finality (default)
const standard = await source.getLaneLatency(destSelector)
console.log('Standard delivery:', Math.round(standard.totalMs / 60000), 'minutes')

// FTF with specific block confirmations
const ftf = await source.getLaneLatency(destSelector, minConfirmations)
console.log('FTF delivery:', Math.round(ftf.totalMs / 60000), 'minutes')

When numberOfBlocks is omitted or 0, the API returns latency for the lane's default finality.

Estimate FTF Fees​

Token transfer fees differ between standard and FTF. Call getTotalFeesEstimate with finality in extraArgs:

TypeScript
const estimate = await source.getTotalFeesEstimate({
router,
destChainSelector: destSelector,
message: {
receiver: '0xReceiverAddress...',
tokenAmounts: [{ token: '0xTokenAddress...', amount: 1_000_000n }],
extraArgs: { finality: minConfirmations },
},
})

console.log('CCIP fee:', estimate.ccipFee)

if (estimate.tokenTransferFee) {
const sendAmount = 1_000_000n
const received = sendAmount - estimate.tokenTransferFee.feeDeducted
console.log('BPS rate:', estimate.tokenTransferFee.bps)
console.log('Fee deducted from token:', estimate.tokenTransferFee.feeDeducted)
console.log('Recipient receives:', received)
}

The pool-level BPS rate applied depends on finality mode:

Finality modefinalityBPS field used
Standard0 or omittedfinalityTransferFeeBps
FTF> 0 (block depth)fastFinalityTransferFeeBps

See Fee Estimation for the full TotalFeesEstimate type and USDC/CCTP fee handling.

FTF Rate Limits​

FTF transfers have separate rate limiters from standard transfers. Query them via getLaneFeatures:

TypeScript
const features = await source.getLaneFeatures({
router,
destChainSelector: destSelector,
token: '0xTokenAddress...',
})

// Standard rate limits
if (features.RATE_LIMITS) {
console.log('Standard — available:', features.RATE_LIMITS.tokens, '/', features.RATE_LIMITS.capacity)
}

// FTF rate limits (only when FTF is enabled)
if (features.FAST_RATE_LIMITS) {
const ftf = features.FAST_RATE_LIMITS
console.log('FTF — available:', ftf.tokens, '/', ftf.capacity)
}

FTF rate limits are also available per remote chain via getTokenPoolRemotes:

TypeScript
const remotes = await source.getTokenPoolRemotes(poolAddress, destSelector)
const remote = Object.values(remotes)[0]

if (remote && 'fastOutboundRateLimiterState' in remote) {
const ftfOutbound = remote.fastOutboundRateLimiterState
if (ftfOutbound) {
console.log('FTF outbound:', ftfOutbound.tokens, '/', ftfOutbound.capacity)
}
}

Rate limiter values are null when rate limiting is disabled for that direction.

Complete Example​

Check FTF availability, estimate latency and fees, then send:

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

async function sendWithFTF(
sourceRpc: string,
routerAddress: string,
destName: string,
tokenAddress: string,
amount: bigint,
receiver: string,
wallet: unknown,
) {
const source = await EVMChain.fromUrl(sourceRpc)
const destSelector = networkInfo(destName).chainSelector

// 1. Check FTF availability
const features = await source.getLaneFeatures({
router: routerAddress,
destChainSelector: destSelector,
token: tokenAddress,
})

const minConfirmations = features.FINALITY_FAST
if (minConfirmations == null || minConfirmations === 0) {
console.log('FTF not available on this lane/token')
return
}

// 2. Check FTF rate limits
if (features.FAST_RATE_LIMITS) {
const available = features.FAST_RATE_LIMITS.tokens
console.log('FTF rate limit available:', available)
}

// 3. Estimate FTF latency
const latency = await source.getLaneLatency(destSelector, minConfirmations)
console.log('FTF delivery estimate:', Math.round(latency.totalMs / 60000), 'minutes')

// 4. Estimate fees with FTF
const message = {
receiver,
tokenAmounts: [{ token: tokenAddress, amount }],
extraArgs: { finality: minConfirmations },
}

const estimate = await source.getTotalFeesEstimate({
router: routerAddress,
destChainSelector: destSelector,
message,
})

console.log('CCIP fee:', estimate.ccipFee)
if (estimate.tokenTransferFee) {
console.log('Token BPS:', estimate.tokenTransferFee.bps)
console.log('Recipient receives:', amount - estimate.tokenTransferFee.feeDeducted)
}

// 5. Send
await source.sendMessage({
router: routerAddress,
destChainSelector: destSelector,
message,
wallet,
})
}

Error Handling​

ErrorWhen
CCIPNotImplementedErrorgetLaneFeatures called on a non-EVM chain
CCIPLaneNotFoundErrorgetLaneLatency — lane does not exist
CCIPApiClientNotAvailableErrorgetLaneLatency — API client disabled

Fee estimation and send errors are documented in Fee Estimation and Sending Messages.

Method Reference​

MethodReturnsUsed for
chain.getLaneFeatures(opts)Partial<LaneFeatures>Check FTF availability and rate limits
chain.getLaneLatency(destSelector, numberOfBlocks?)LaneLatencyResponseEstimate FTF delivery time
chain.getTotalFeesEstimate(opts)TotalFeesEstimateEstimate fees with FTF BPS rates