CCIP-JS is a TypeScript library that provides a client for managing cross-chain token transfers that use Chainlink's Cross-Chain Interoperability Protocol (CCIP) routers. The library utilizes types and helper functions from Viem.
To learn more about CCIP, refer to the CCIP documentation.
- CCIP-JS
CCIP-JS provides ready-to-use typesafe methods for every step of the token transfer process.
Although you can do a CCIP token transfer simply by calling the ccipSend
function, there are multiple steps that need to be done beforehand, such as:
- checking allowances
- approving transfers
- retrieving supported lanes
- retrieving lane limits
- retrieving fee amounts and fee tokens
Additionally, after the transfer, you may need to check the transfer status.
- Token Approvals: Approve tokens for cross-chain transfers.
- Allowance Checks: Retrieve the allowance for token transfers.
- Rate Limits: Get rate refill limits for lanes.
- Fee Calculation: Calculate the fee required for transfers.
- Token Transfers: Transfer tokens across chains.
- Transfer Status: Retrieve the status of a transfer by transaction hash.
To install the package, use the following command:
npm install @chainlink/ccip-js viem
Or with Yarn:
yarn add @chainlink/ccip-js viem
Or with PNPM:
pnpm add @chainlink/ccip-js viem
This example code covers the following steps:
- Initialize CCIP-JS Client for mainnet
- Approve tokens for transfer
- Get fee for the transfer
- Send the transfer through CCIP using one of the following options for fee payment:
- Using the native token fee
- Using the provided supported token for fee payment
import * as CCIP from '@chainlink/ccip-js'
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'
// Initialize CCIP-JS Client for mainnet
const ccipClient = CCIP.createClient()
const publicClient = createPublicClient({
chain: mainnet,
transport: http(),
})
const walletClient = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum!),
})
// Approve Router to transfer tokens on user's behalf
const { txHash, txReceipt } = await ccipClient.approveRouter({
client: walletClient,
routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef',
tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef',
amount: 1000000000000000000n,
waitForReceipt: true,
})
console.log(`Transfer approved. Transaction hash: ${txHash}. Transaction receipt: ${txReceipt}`)
// Get fee for the transfer
const fee = await ccipClient.getFee({
client: publicClient,
routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef',
tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef',
amount: 1000000000000000000n,
destinationAccount: '0x1234567890abcdef1234567890abcdef12345678',
destinationChainSelector: '1234',
})
console.log(`Fee: ${fee.toLocaleString()}`)
// Variant 1: Transfer via CCIP using native token fee
const { txHash, messageId } = await client.transferTokens({
client: walletClient,
routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef',
tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef',
amount: 1000000000000000000n,
destinationAccount: '0x1234567890abcdef1234567890abcdef12345678',
destinationChainSelector: '1234',
})
console.log(`Transfer success. Transaction hash: ${txHash}. Message ID: ${messageId}`)
// Variant 2: Transfer via CCIP using the provided supported token for fee payment
const { txHash, messageId } = await client.transferTokens({
client: walletClient,
routerAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef',
tokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef',
amount: 1000000000000000000n,
destinationAccount: '0x1234567890abcdef1234567890abcdef12345678',
destinationChainSelector: '1234',
feeTokenAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef',
})
An object containing methods for cross-chain transfer management. Refer to Client methods for more information about each method.
export interface Client {
approveRouter(options: {
client: Viem.WalletClient
routerAddress: Viem.Address
tokenAddress: Viem.Address
amount: bigint
waitForReceipt?: boolean
writeContractParameters?: Partial<{
gas: bigint
gasPrice: bigint
nonce: number
}>
waitForTransactionReceiptParameters?: Partial<{
confirmations: number
pollingInterval: number
}>
}): Promise<{ txHash: Viem.Hash; txReceipt?: Viem.TransactionReceipt }>
getAllowance(options: {
client: Viem.Client
routerAddress: Viem.Address
tokenAddress: Viem.Address
account: Viem.Address
}): Promise<bigint>
getOnRampAddress(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationChainSelector: string
}): Promise<Viem.Address>
getSupportedFeeTokens(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationChainSelector: string
}): Promise<Viem.Address[]>
getLaneRateRefillLimits(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationChainSelector: string
}): Promise<RateLimiterState>
getTokenRateLimitByLane(options: {
client: Viem.Client
routerAddress: Viem.Address
supportedTokenAddress: Viem.Address
destinationChainSelector: string
}): Promise<RateLimiterState>
getFee(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationAccount: Viem.Address
destinationChainSelector: string
amount: bigint
tokenAddress: Viem.Address
feeTokenAddress?: Viem.Address
message?: string
}): Promise<bigint>
getTokenAdminRegistry(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationChainSelector: string
tokenAddress: Viem.Address
}): Promise<Viem.Address>
isTokenSupported(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationChainSelector: string
tokenAddress: Viem.Address
}): Promise<boolean>
transferTokens(options: {
client: Viem.WalletClient
routerAddress: Viem.Address
destinationChainSelector: string
amount: bigint
destinationAccount: Viem.Address
tokenAddress: Viem.Address
feeTokenAddress?: Viem.Address
message?: string
writeContractParameters?: Partial<{
gas: bigint
gasPrice: bigint
nonce: number
}>
waitForTransactionReceiptParameters?: Partial<{
confirmations: number
pollingInterval: number
}>
}): Promise<{ txHash: Viem.Hash; messageId: Viem.Hash; txReceipt: Viem.TransactionReceipt }>
sendCCIPMessage(options: {
client: Viem.WalletClient
routerAddress: Viem.Address
destinationChainSelector: string
destinationAccount: Viem.Address
feeTokenAddress?: Viem.Address
message: string
writeContractParameters?: Partial<{
gas: bigint
gasPrice: bigint
nonce: number
}>
waitForTransactionReceiptParameters?: Partial<{
confirmations: number
pollingInterval: number
}>
}): Promise<{ txHash: Viem.Hash; messageId: Viem.Hash; txReceipt: Viem.TransactionReceipt }>
getTransferStatus(options: {
client: Viem.Client
destinationRouterAddress: Viem.Address
sourceChainSelector: string
messageId: Viem.Hash
fromBlockNumber?: bigint
}): Promise<TransferStatus | null>
getTransactionReceipt(options: { client: Viem.Client; hash: Viem.Hash }): Promise<Viem.TransactionReceipt>
}
createClient(): Client
Creates a Client object.
Represents the state of a rate limiter using a token bucket algorithm.
interface RateLimiterState {
// Current number of tokens that are in the bucket. This represents the available capacity for requests.
tokens: bigint
// Timestamp in seconds of the last token refill, allows tracking token consumption over time. This is designed to be accurate for over 100 years.
lastUpdated: number
// Indicates whether the rate limiting feature is enabled or disabled.
isEnabled: boolean
// Maximum number of tokens that can be in the bucket, representing the total capacity of the limiter.
capacity: bigint
// The rate at which tokens are refilled in the bucket, measuer in tokes per second.
rate: bigint
}
Configuration settings for dynamic aspects of cross-chain transfers.
The DynamicConfig
type defines the structure of an object that holds various dynamic configuration parameters for cross-chain transactions. These settings are used to control the behavior and limits of transfers, such as gas calculations, data availability, and message size constraints.
type DynamicConfig = {
// The address of the router responsible for handling the cross-chain transfers. This address is used to route the transaction through the correct path.
router: Viem.Address
// The maximum number of tokens that can be included in a single message. This parameter limits the token batch size in cross-chain transfers to prevent overly large transactions.
maxNumberOfTokensPerMsg: number
// The amount of gas required per byte of payload on the destination chain. This parameter is used to calculate the total gas needed based on the size of the payload being transferred.
destGasPerPayloadByte: number
// The additional gas required on the destination chain to account for data availability overhead. This value is used to ensure that enough gas is allocated for the availability of the data being transferred.
destDataAvailabilityOverheadGas: number
// The overhead in gas that is added to the destination chain to account for base transaction costs. This value helps ensure that the transaction has enough gas to cover additional overhead on the destination chain.
destGasOverhead: number
// The gas cost per byte of data availability on the destination chain. This parameter contributes to the overall gas calculation for data availability during the transfer.
destGasPerDataAvailabilityByte: number
// The multiplier in basis points (bps) applied to the data availability gas cost. This value is used to adjust the cost of data availability by applying a scaling factor.
destDataAvailabilityMultiplierBps: number
// The address of the price registry used to obtain pricing information for gas and other costs during the transfer. This registry helps ensure that the correct prices are applied to the transaction.
priceRegistry: Viem.Address
// The maximum number of data bytes that can be included in a single message. This parameter limits the size of the data payload to prevent excessive data in one transfer.
maxDataBytes: number
// The maximum gas limit that can be applied to a single message. This parameter ensures that the transaction does not exceed a certain gas threshold, preventing overly costly operations.
maxPerMsgGasLimit: number
}
Represents the off-ramp configuration for a cross-chain transfer.
type OffRamp = {
// The address of the off-ramp contract on the destination blockchain.
offRamp: Viem.Address
// The selector for the source chain.
sourceChainSelector: bigint
}
Represents the transaction status of a cross-chain transfer.
enum TransferStatus {
Untouched = 0,
InProgress = 1,
Success = 2,
Failure = 3,
}
Approve the CCIP router to spend tokens for transfers and fees on behalf of the user. Returns the transaction hash and optionally the transaction receipt.
Q: Why an approval is needed? A: For a cross-chain transfer the CCIP router contract needs to withdraw the tokens amount and fee from the sender's wallet. The user should review and approve the parameters of the transfer before executing it.
approveRouter(options: {
client: Viem.WalletClient
routerAddress: Viem.Address
tokenAddress: Viem.Address
amount: bigint
waitForReceipt?: boolean
writeContractParameters?: Partial<{
gas: bigint
gasPrice: bigint
nonce: number
}>
waitForTransactionReceiptParameters?: Partial<{
confirmations: number
pollingInterval: number
}>
}): Promise<{ txHash: Viem.Hash; txReceipt?: Viem.TransactionReceipt }>
Retrieves the allowance of a specified account for a cross-chain transfer.
getAllowance(options: {
client: Viem.Client
routerAddress: Viem.Address
tokenAddress: Viem.Address
account: Viem.Address
}): Promise<bigint>
Retrieves the onRamp contract address from a router contract.
getOnRampAddress(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationChainSelector: string
}): Promise<Viem.Address>
Gets a list of supported tokens which can be used to pay the fees for the cross-chain transfer.
getSupportedFeeTokens(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationChainSelector: string
}): Promise<Viem.Address[]>
Retrieves the aggregated rate refill limits for the specified lane. Returns a promise that resolves to RateLimiterState object.
getLaneRateRefillLimits(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationChainSelector: string
}): Promise<RateLimiterState>
Retrieves the rate refill limits for the specified token. Returns a promise that resolves to RateLimiterState object.
getTokenRateLimitByLane(options: {
client: Viem.Client
routerAddress: Viem.Address
supportedTokenAddress: Viem.Address
destinationChainSelector: string
}): Promise<RateLimiterState>
Gets the fee required for the cross-chain transfer.
getFee(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationAccount: Viem.Address
destinationChainSelector: string
amount: bigint
tokenAddress: Viem.Address
feeTokenAddress?: Viem.Address
message?: string
}): Promise<bigint>
Get the fee required for the cross-chain transfer and/or sending cross-chain message.
Retrieve the token admin registry contract address from an onRamp contract
getTokenAdminRegistry(options: {
client: Viem.Client
routerAddress: Viem.Address
destinationChainSelector: string
tokenAddress: Viem.Address
}): Promise<Viem.Address>
Check if the token is supported on the destination chain. The call, and all configs are made on the source chain.
isTokenSupported(options: {
client: Viem.Client
routerAddress: Viem.Address // router address on source chain.
destinationChainSelector: string
tokenAddress: Viem.Address // token address on source chain.
}): Promise<boolean>
Initiates the token transfer and returns the transaction hash, cross-chain transfer message ID and transaction receipt.
transferTokens(options: {
client: Viem.WalletClient
routerAddress: Viem.Address
destinationChainSelector: string
amount: bigint
destinationAccount: Viem.Address
tokenAddress: Viem.Address
feeTokenAddress?: Viem.Address
message?: string
writeContractParameters?: Partial<{
gas: bigint
gasPrice: bigint
nonce: number
}>
waitForTransactionReceiptParameters?: Partial<{
confirmations: number
pollingInterval: number
}>
}): Promise<{ txHash: Viem.Hash; messageId: Viem.Hash; txReceipt: Viem.TransactionReceipt }>
Initiates the token transfer and returns the transaction hash, cross-chain transfer message ID and transaction receipt.
Send arbitrary message through CCIP
sendCCIPMessage(options: {
client: Viem.WalletClient
routerAddress: Viem.Address
destinationChainSelector: string
destinationAccount: Viem.Address
feeTokenAddress?: Viem.Address
message: string
writeContractParameters?: Partial<{
gas: bigint
gasPrice: bigint
nonce: number
}>
waitForTransactionReceiptParameters?: Partial<{
confirmations: number
pollingInterval: number
}>
}): Promise<{ txHash: Viem.Hash; messageId: Viem.Hash; txReceipt: Viem.TransactionReceipt }>
Retrieves the status of a cross-chain transfer based on the message ID. Returns a promise that resolves to TransferStatus object or null
getTransferStatus(options: {
client: Viem.Client
destinationRouterAddress: Viem.Address
sourceChainSelector: string
messageId: Viem.Hash
fromBlockNumber?: bigint
}): Promise<TransferStatus | null>
Retrieves the transaction receipt based on the transaction hash. Returns a promise that resolves to TransactionReceipt
object.
getTransactionReceipt(options: { client: Viem.Client; hash: Viem.Hash }): Promise<Viem.TransactionReceipt>
pnpm i -w
pnpm build-ccip-js
pnpm i -w
anvil
pnpm test
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
- Fork the repository.
- Create your feature branch (git checkout -b feature/my-feature).
- Commit your changes (git commit -m 'Add some feature').
- Push to the branch (git push origin feature/my-feature).
- Open a pull request.
CCIP-JS is available under the MIT license.