Skip to content

Latest commit

 

History

History
614 lines (499 loc) · 18.8 KB

README.md

File metadata and controls

614 lines (499 loc) · 18.8 KB

CCIP-JS

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.

Table of Contents

Why 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.

Features

  • 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.

Installation

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

Usage

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',
})

API Reference

Client

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

createClient(): Client

Creates a Client object.

RateLimiterState

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
}

DynamicConfig

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
}

OffRamp

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
}

TransferStatus

Represents the transaction status of a cross-chain transfer.

enum TransferStatus {
  Untouched = 0,
  InProgress = 1,
  Success = 2,
  Failure = 3,
}

Client Methods

approveRouter

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 }>

getAllowance

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>

getOnRampAddress

Retrieves the onRamp contract address from a router contract.

getOnRampAddress(options: {
  client: Viem.Client
  routerAddress: Viem.Address
  destinationChainSelector: string
}): Promise<Viem.Address>

getSupportedFeeTokens

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[]>

getLaneRateRefillLimits

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>

getTokenRateLimitByLane

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>

getFee

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.

getTokenAdminRegistry

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>

isTokenSupported

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>

transferTokens

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.

sendCCIPMessage

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 }>

getTransferStatus

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>

getTransactionReceipt

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>

Development

Build

pnpm i -w
pnpm build-ccip-js

Running tests

pnpm i -w
anvil
pnpm test

Contributing

Contributions are welcome! Please open an issue or submit a pull request on GitHub.

  1. Fork the repository.
  2. Create your feature branch (git checkout -b feature/my-feature).
  3. Commit your changes (git commit -m 'Add some feature').
  4. Push to the branch (git push origin feature/my-feature).
  5. Open a pull request.

License

CCIP-JS is available under the MIT license.