# Chainlink CCIP - Cross-Chain Interoperability Protocol
Source: https://docs.chain.link/ccip
Last Updated: 2025-05-19
Blockchain interoperability protocols are important for the Web3 ecosystem and traditional systems that need to interact with different blockchains. These protocols are the foundation for building blockchain abstraction layers, allowing traditional backends and dApps to interact with any blockchain network through a single middleware solution. Without a blockchain interoperability protocol, Web2 systems and dApps would need to build separate in-house implementations for each cross-chain interaction that they want to use, which is a time-consuming, resource-intensive, and complex process.
Blockchain interoperability protocols provide the following capabilities:
- You can transfer assets and information across multiple blockchains.
- Application developers can leverage the strengths and benefits of different chains.
- Collaboration between developers from diverse blockchain ecosystems enables the building of cross-chain applications to serve more users and provide additional features or products for them.
The *Chainlink Cross-Chain Interoperability Protocol (CCIP)* provides these capabilities and enables a variety of [use cases](#common-use-cases).
## What is Chainlink CCIP?
Chainlink CCIP is a blockchain interoperability protocol that enables developers to build secure applications that can transfer tokens, messages (data), or both tokens and messages across chains.
Given the [inherent risks of cross-chain interoperability](/resources/bridge-risks), CCIP features [defense-in-depth security](https://blog.chain.link/five-levels-cross-chain-security/#level_5__defense-in-depth) and is powered by Chainlink's industry-standard oracle networks which have a proven track record of securing tens of billions of dollars and enabling over $14 trillion in onchain transaction value.
CCIP provides several key security benefits:
- Multiple independent nodes run by independent key holders.
- Three decentralized networks all executing and verifying every cross-chain transaction.
- Separation of responsibilities, with distinct sets of node operators, and with no nodes shared between the transactional DONs and the [Risk Management Network](/ccip/concepts/architecture/key-concepts#risk-management-network).
- Increased decentralization with two separate code bases across two different implementations, written in two different languages to create a previously unseen diversity of software clients in the cross-chain world.
- Novel risk management system with [level-5 security](https://blog.chain.link/five-levels-cross-chain-security/#level_5__defense-in-depth) that can be rapidly adapted to any new risks or attacks that appear in cross-chain messaging.
To understand how Chainlink CCIP works, refer to the [architecture](/ccip/concepts/architecture) section. If you are new to using Chainlink CCIP, read these guides before you deploy any contracts that use CCIP.
## Chainlink CCIP core capabilities
Chainlink CCIP supports three main capabilities:
### Arbitrary Messaging
The ability to send arbitrary data (encoded as bytes) to a receiving smart contract on a different blockchain. The developer is free to encode any data they wish to send.
Typically, developers use arbitrary messaging to trigger an informed action on the receiving smart contract, such as rebalancing an index, minting a specific NFT, or calling an arbitrary function with the sent data as custom parameters. Developers can encode multiple instructions in a single message, enabling them to orchestrate complex, multi-step, multi-chain tasks.
### Token Transfer
The ability to transfer tokens to an account on a different blockchain. This capability enables the seamless movement of assets across chains.
### Programmable Token Transfer
The ability to simultaneously transfer tokens and arbitrary data (encoded as bytes) within a single transaction. This mechanism allows users to transfer tokens and send instructions on what to do with those tokens.
For example, a user could transfer tokens to a lending protocol with instructions to leverage those tokens as collateral for a loan, borrowing another asset to be sent back to the user.
### Receiving account types
With CCIP, you send transactions with data (arbitrary messaging), tokens, or both data and tokens (programmable token transfer). The receiver of a CCIP transaction varies by blockchain family:
| CCIP capability | What is sent | Receiving account types |
| --------------------------- | --------------- | --------------------------------------------------------------------------------------- |
| Arbitrary Messaging | Data | EVM: Smart contracts only SVM: Programs only |
| Token Transfer | Tokens | EVM: Smart contracts and EOAs SVM: User wallets or program-controlled PDAs |
| Programmable Token Transfer | Data and tokens | EVM: Smart contracts only SVM: Data to programs, tokens to program-controlled PDAs |
**Note**: On EVM chains, EOAs cannot receive messages. On Solana (SVM), programs work with Program Derived Addresses (PDAs) to manage token reception.
## Common use cases
Chainlink CCIP enables a variety of use cases:
- **Cross-chain lending:** Chainlink CCIP enables users to lend and borrow a wide range of crypto assets across multiple DeFi platforms running on independent chains.
- **Low-cost transaction computation:** Chainlink CCIP can help offload the computation of transaction data on cost-optimized chains.
- **Optimizing cross-chain yield:** Users can leverage Chainlink CCIP to move collateral to new DeFi protocols to maximize yield across chains.
- **Creating new kinds of dApps:** Chainlink CCIP enables users to take advantage of network effects on certain chains while harnessing compute and storage capabilities of other chains.
Read [What Are Cross-Chain Smart Contracts](https://chain.link/education-hub/cross-chain-smart-contracts) to learn about cross-chain smart contracts and examples of use cases they enable.
## CCIP Directory
See the [CCIP Directory](/ccip/directory) page for a list of supported networks, tokens, and contract addresses.
To learn about tokens, token pools, and the token onboarding process, see the [CCIP Architecture](/ccip/concepts/cross-chain-token/evm/token-pools) page.
---
# Getting Started with Chainlink CCIP
Source: https://docs.chain.link/ccip/getting-started
Last Updated: 2025-05-19
## Available Blockchain Families
CCIP supports multiple blockchain families:
- **EVM**: Send messages and tokens between Ethereum, Avalanche, Polygon, and other EVM-compatible networks
- **SVM**: Connect Solana with other chain families through CCIP
Check the [CCIP Directory](/ccip/directory) for a list of supported blockchains.
## What You Can Build with CCIP
- **Cross-Chain dApps**: Create applications that operate seamlessly across multiple blockchains
- **Token Bridges**: Transfer tokens between different blockchain networks
- **Cross-Chain Data Messaging**: Send arbitrary data between smart contracts on different chains
- **Programmable Token Transfers**: Combine token transfers with messaging to trigger specific actions on destination chains
## Getting Started Guides
Choose your starting point based on your blockchain platform:
- [Getting Started with CCIP on EVM Chains](/ccip/getting-started/evm) - Learn how to deploy sender/receiver contracts and send messages between EVM chains
- [Getting Started with CCIP on Solana (SVM)](/ccip/getting-started/svm)
---
# Getting Started (EVM)
Source: https://docs.chain.link/ccip/getting-started/evm
Last Updated: 2025-05-19
A simple use case for Chainlink CCIP is sending data between smart contracts on different blockchains. This guide shows you how to deploy a CCIP sender contract and a CCIP receiver contract to two different blockchains and send data from the sender contract to the receiver contract. You pay the CCIP fees using LINK.
Fees can also be paid in alternative assets, which currently include the native gas tokens of the source blockchain and their ERC20 wrapped version. For example, you can pay ETH or WETH when you send transactions to the CCIP router on Ethereum and AVAX or WAVAX when you send transactions to the CCIP router on Avalanche.
## Before you begin
- If you are new to smart contract development, learn how to [Deploy Your First Smart Contract](/quickstarts/deploy-your-first-contract) so you are familiar with the tools that are necessary for this guide:
- The [Solidity](https://soliditylang.org/) programming language
- The [MetaMask](https://metamask.io) wallet
- The [Remix](https://remix.ethereum.org/) development environment
- Acquire testnet funds. This guide requires testnet AVAX and LINK on *Avalanche Fuji*. It also requires testnet ETH on *Ethereum Sepolia*. If you need to use different networks, you can find more faucets on the [LINK Token Contracts](/resources/link-token-contracts) page.
- Go to [faucets.chain.link](https://faucets.chain.link/) to get your testnet tokens.
- Learn how to [Fund your contract with LINK](/resources/fund-your-contract).
## Deploy the sender contract
Deploy the `Sender.sol` contract on *Avalanche Fuji*. To see a detailed explanation of this contract, read the [Code Explanation](#sender-code) section.
1. [Open the Sender.sol contract](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Sender.sol) in Remix.
2. Compile the contract.
3. Deploy the sender contract on *Avalanche Fuji*:
1. Open MetaMask and select the *Avalanche Fuji* network.
2. In Remix under the **Deploy & Run Transactions** tab, select *Injected Provider - MetaMask* in the **Environment** list. Remix will use the MetaMask wallet to communicate with *Avalanche Fuji*.
3. Under the **Deploy** section, fill in the router address and the LINK token contract addresses for your specific blockchain. You can find both of these addresses on the [CCIP Directory](/ccip/directory). The LINK token contract address is also listed on the [LINK Token Contracts](/resources/link-token-contracts) page. For *Avalanche Fuji*, the router address is 0xF694E193200268f9a4868e4Aa017A0118C9a8177 and the LINK address is 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846.
4. Click the **transact** button to deploy the contract. MetaMask prompts you to confirm the transaction. Check the transaction details to make sure you are deploying the contract to *Avalanche Fuji*.
5. After you confirm the transaction, the contract address appears in the **Deployed Contracts** list. Copy your contract address.
6. Open MetaMask and send 70 LINK to the contract address that you copied. Your contract will pay CCIP fees in LINK.
**Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this example, you can get additional testnet LINK
from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia.
## Deploy the receiver contract
Deploy the receiver contract on *Ethereum Sepolia*. You will use this contract to receive data from the sender that you deployed on *Avalanche Fuji*. To see a detailed explanation of this contract, read the [Code Explanation](#receiver-code) section.
1. [Open the Receiver.sol](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Receiver.sol) contract in Remix.
2. Compile the contract.
3. Deploy the receiver contract on *Ethereum Sepolia*:
1. Open MetaMask and select the *Ethereum Sepolia* network.
2. In Remix under the **Deploy & Run Transactions** tab, make sure the **Environment** is still set to *Injected Provider - MetaMask*.
3. Under the **Deploy** section, fill in the router address field. For *Ethereum Sepolia*, the Router address is 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59. You can find the addresses for each network on the [CCIP Directory](/ccip/directory).
4. Click the **Deploy** button to deploy the contract. MetaMask prompts you to confirm the transaction. Check the transaction details to make sure you are deploying the contract to *Ethereum Sepolia*.
5. After you confirm the transaction, the contract address appears as the second item in the **Deployed Contracts** list. Copy this contract address.
You now have one *sender* contract on *Avalanche Fuji* and one *receiver* contract on *Ethereum Sepolia*. You sent `70` LINK to the *sender* contract to pay the CCIP fees. Next, send data from the sender contract to the receiver contract.
## Send data
Send a `Hello World!` string from your contract on *Avalanche Fuji* to the contract you deployed on *Ethereum Sepolia*:
1. Open MetaMask and select the *Avalanche Fuji* network.
2. In Remix under the **Deploy & Run Transactions** tab, expand the first contract in the **Deployed Contracts** section.
3. Expand the **sendMessage** function and fill in the following arguments:
| Argument | Description | Value (*Ethereum Sepolia*) |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| destinationChainSelector | CCIP Chain identifier of the target blockchain. You can find each network's chain selector on the [CCIP Directory](/ccip/directory) | 16015286601757825753 |
| receiver | The destination smart contract address | Your deployed contract address |
| text | Any `string` | Hello World! |
4. Click the **transact** button to run the function. MetaMask prompts you to confirm the transaction.
1. After the transaction is successful, note the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0x113933ec9f1b2e795a1e2f564c9d452db92d3e9a150545712687eb546916e633) of a successful transaction on *Avalanche Fuji*.
After the transaction is finalized on the source chain, it will take a few minutes for CCIP to deliver the data to *Ethereum Sepolia* and call the `ccipReceive` function on your receiver contract. You can use the [CCIP explorer](https://ccip.chain.link/) to see the status of your CCIP transaction and then read data stored by your receiver contract.
1. Open the [CCIP explorer](https://ccip.chain.link/) and use the transaction hash that you copied to search for your cross-chain transaction. The explorer provides several details about your request.
2. When the status of the transaction is marked with a "Success" status, the CCIP transaction and the destination transaction are complete.
## Read data
Read data stored by the receiver contract on *Ethereum Sepolia*:
1. Open MetaMask and select the *Ethereum Sepolia* network.
2. In Remix under the **Deploy & Run Transactions** tab, expand the receiver contract deployed on *Ethereum Sepolia*.
3. Click the **getLastReceivedMessageDetails** function button to read the stored data. In this example, it is "Hello World!".
Congratulations! You just sent your first cross-chain data using CCIP. Next, examine the example code to learn how this contract works.
## Examine the example code
### Sender code
The smart contract in this tutorial is designed to interact with CCIP to send data. The contract code includes comments to clarify the various functions, events, and underlying logic. However, this section explains the key elements. You can see the full contract code below.
#### Initializing the contract
When deploying the contract, you define the router address and the LINK contract address of the blockchain where you choose to deploy the contract.
The router address provides functions that are required for this example:
- The `getFee` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee) to estimate the CCIP fees.
- The `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend) to send CCIP messages.
#### Sending data
The `sendMessage` function completes several operations:
1. Construct a CCIP-compatible message using the `EVM2AnyMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage):
- The `receiver` address is encoded in bytes format to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `data` is encoded from a string text to bytes using [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `tokenAmounts` is an array. Each element comprises a [struct](/ccip/api-reference/evm/v1.6.2/client#evmtokenamount) that contains the token address and amount. In this example, the array is empty because no tokens are sent.
- The `extraArgs` specify the `gasLimit` for relaying the CCIP message to the recipient contract on the destination blockchain. In this example, the `gasLimit` is set to `200000`.
- The `feeToken` designates the token address used for CCIP fees. Here, `address(linkToken)` signifies payment in LINK.
2. Compute the fees by invoking the router's `getFee` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee).
3. Ensure that your contract balance in LINK is enough to cover the fees.
4. Grant the router contract permission to deduct the fees from the contract's LINK balance.
5. Dispatch the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend).
### Receiver code
The smart contract in this tutorial is designed to interact with CCIP to receive data. The contract code includes comments to clarify the various functions, events, and underlying logic. However, this section explains the key elements. You can see the full contract code below.
#### Initializing the contract
When you deploy the contract, you define the router address. The receiver contract inherits from the [CCIPReceiver.sol](/ccip/api-reference/evm/v1.6.2/ccip-receiver) contract, which uses the router address.
#### Receiving data
On the destination blockchain:
1. The CCIP Router invokes the `ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive). **Note**: This function is protected by the `onlyRouter` [modifier](/ccip/api-reference/evm/v1.6.2/ccip-receiver#onlyrouter), which ensures that only the router can call the receiver contract.
2. The `ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive) calls an internal function `_ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#_ccipreceive). The receiver contract implements this function.
3. This `_ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#_ccipreceive) expects an `Any2EVMMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage) that contains the following values:
- The CCIP `messageId`.
- The `sourceChainSelector`.
- The `sender` address in bytes format. The sender is a contract deployed on an EVM-compatible blockchain, so the address is decoded from bytes to an Ethereum address using the [ABI specification](https://docs.soliditylang.org/en/v0.8.20/abi-spec.html).
- The `data` is also in bytes format. A `string` is expected, so the data is decoded from bytes to a string using the [ABI specification](https://docs.soliditylang.org/en/v0.8.20/abi-spec.html).
---
# Getting Started with Chainlink CCIP on Solana (SVM)
Source: https://docs.chain.link/ccip/getting-started/svm
Last Updated: 2025-08-22
## Solana (SVM) Support in CCIP
Chainlink CCIP supports Solana through the Solana Virtual Machine (SVM), enabling cross-chain interoperability between Solana and other blockchain families including EVM chains.
## What You Can Build with CCIP on Solana
- **Cross-Chain Token Transfers**: Transfer tokens from/to Solana
- **Cross-Chain Data Messaging**: Send arbitrary data between Solana programs and smart contracts on other chains
- **Programmable Token Transfers**: Combine token transfers with messaging to trigger specific actions on destination chains
- **Cross-Chain Token (CCT) Standard**: Enable your tokens in CCIP to be transferred to/from Solana
## Getting Started with Solana CCIP
SVM tutorials can be found [here](/ccip/tutorials/svm).
---
# CCIP Service Limits
Source: https://docs.chain.link/ccip/service-limits
Last Updated: 2025-09-03
This section outlines the operational limits for Chainlink CCIP across different blockchain architectures.
- **[EVM Service Limits](/ccip/service-limits/evm)**: Service limits for Ethereum and other EVM-compatible blockchains.
- **[Solana Service Limits](/ccip/service-limits/svm)**: Service limits for Solana.
- **[Aptos Service Limits](/ccip/service-limits/aptos)**: Service limits for Aptos.
- **[Network-Specific Limits](/ccip/service-limits/network-specific-limits)**: Documented network-specific limitations (all blockchain families).
Understanding these limits is essential for building reliable cross-chain applications that operate within CCIP's intended parameters.
---
# CCIP Service Limits (EVM)
Source: https://docs.chain.link/ccip/service-limits/evm
Last Updated: 2025-05-19
## Mainnet
| Item | Description | Limit |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| Maximum message `data` length | `data` payload sent within the [CCIP message](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) | 30 kilobytes |
| Message Execution Gas Limit | User specified [gas limit](/ccip/api-reference/evm/v1.6.2/client#genericextraargsv2)
Exception: Lanes originating from CORN have a maximum gas limit of 500,000. | 3,000,000 |
| Maximum number of tokens | Maximum number of distinct tokens a user can transfer in a single transaction. | 1 |
| Token Pool Execution Gas Limit | Maximum gas for executing the combined steps in token pools during cross-chain transfers, including: (1) `balanceOf` check before minting/releasing, (2) `releaseOrMint` function, and (3) `balanceOf` check after minting/releasing. For more details on building custom token pools and handling gas constraints, refer to the [Token Pools documentation](/ccip/concepts/cross-chain-token/evm/token-pools#common-requirements). | 90,000 |
## Testnet
| Item | Description | Limit |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| Maximum message `data` length | `data` payload sent within the [CCIP message](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) | 30 kilobytes |
| Message Execution Gas Limit | User specified [gas limit](/ccip/api-reference/evm/v1.6.2/client#genericextraargsv2)
Exception: Lanes originating from CORN have a maximum gas limit of 500,000. | 3,000,000 |
| Maximum number of tokens | Maximum number of distinct tokens a user can transfer in a single transaction | 1 |
| Token Pool Execution Gas Limit | Maximum gas for executing the combined steps in token pools during cross-chain transfers, including: (1) `balanceOf` check before minting/releasing, (2) `releaseOrMint` function, and (3) `balanceOf` check after minting/releasing. For more details on building custom token pools and handling gas constraints, refer to the [Token Pools documentation](/ccip/concepts/cross-chain-token/evm/token-pools#common-requirements). | 90,000 |
---
# CCIP Service Limits (SVM)
Source: https://docs.chain.link/ccip/service-limits/svm
Last Updated: 2025-05-19
## EVM to SVM
| Item | Description | Limit |
| :------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------- | :----------- |
| Maximum message data length | The total payload for the message (includes the user specified message.data as well as the accounts, Bitmap, token transfer data) | 640 bytes |
| Message execution compute units limit | User specified compute units limit. Mandatory to be set | 400,000 CU |
| Maximum number of tokens | Maximum number of distinct tokens a user can transfer in a single transaction. | 1 |
| Smart Execution time window | Maximum duration CCIP will attempt automatic execution of message | 8 hours |
| Token pool execution gas limit | Default compute units for TokenPool execution at the destination, ie., Solana | 150,000 CU |
| Out of Order execution | Parameter in the extraArgs of a CCIP message | Must be True |
## SVM to EVM
| Item | Description | Limit |
| :----------------------------- | :-------------------------------------------------------------------------------------------------- | :------------ |
| Maximum message data length | data payload sent within the [CCIP message](/ccip/api-reference/svm/v1.6.0/messages#svm2anymessage) | 400 bytes |
| Message execution gas limit | User specified gas limit. Mandatory to be set | 3,000,000 gas |
| Maximum number of tokens | Maximum number of distinct tokens a user can transfer in a single transaction. | 1 |
| Smart Execution time window | Maximum duration for the execution of a CCIP message | 8 hours |
| Token pool execution gas limit | Maximum gas for executing the combined steps in token pools during cross-chain transfers | 90,000 gas |
| Out of Order execution | Parameter in the extraArgs of a CCIP message | Must be True |
---
# CCIP Service Limits (Aptos)
Source: https://docs.chain.link/ccip/service-limits/aptos
Last Updated: 2025-09-03
## EVM to Aptos
These limits apply to messages sent from an EVM-compatible chain to the Aptos blockchain.
| Item | Description | Limit |
| :----------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- | :------------- |
| Maximum message data length | The total payload for the message, including user data and token transfer information. | 30,000 bytes |
| Message execution gas limit | The user-specified `gasLimit` in `extraArgs` for the execution of the `ccip_receive` function on Aptos. | 100,000 gas |
| Maximum number of tokens | The maximum number of distinct tokens that can be transferred in a single transaction. | 1 |
| Smart Execution time window | The maximum duration that CCIP will attempt to automatically execute a message on Aptos before manual execution is required. | 8 hours |
| Token pool execution gas limit | The default gas overhead allocated for a standard token transfer operation on Aptos when a message is received from an EVM chain. | 36 gas |
| Out of Order execution | The `allowOutOfOrderExecution` parameter in the `extraArgs` of a CCIP message from an EVM chain. | Must be `true` |
## Aptos to EVM
These limits apply to messages sent from the Aptos blockchain to an EVM-compatible chain.
| Item | Description | Limit |
| :----------------------------- | :------------------------------------------------------------------------------------------------------------------------- | :------------- |
| Maximum message data length | The `data` payload sent within the [CCIP message](/ccip/api-reference/aptos/v1.6.0/messages#aptos2anymessage). | 30,000 bytes |
| Message execution gas limit | The user-specified `gasLimit` in `extraArgs` for the execution of the `ccipReceive` function on the destination EVM chain. | 100,000 gas |
| Maximum number of tokens | The maximum number of distinct tokens that can be transferred in a single transaction. | 1 |
| Smart Execution time window | The maximum duration for the execution of a CCIP message on the destination EVM chain. | 8 hours |
| Token pool execution gas limit | The default gas overhead for executing token pool logic on the destination EVM chain for a standard token transfer. | 36 gas |
| Out of Order execution | The `allow_out_of_order_execution` parameter in the `extraArgs` of a CCIP message from Aptos. | Must be `true` |
---
# Chainlink CCIP Network Specific Limits
Source: https://docs.chain.link/ccip/service-limits/network-specific-limits
Last Updated: 2025-06-27
This page describes known network-specific limitations that may result from the design of a particular network. End users, application developers, blockchain development teams, token developers and others should read and understand these risks when considering interacting with these networks on CCIP.
## HyperEVM
**Risk**:
[Hyperliquid Data availability](/ccip/service-responsibility#blockchain-development-team-responsibilities): If a HyperEVM RPC is out of sync or offline for 10+ minutes, missing logs from during the downtime are only available through a centralized resource that is hosted and maintained by Hyperliquid. In the event that logs related to specific CCIP messages are irrecoverable, those CCIP messages can become stuck or fail.
**CCIP Mitigation**:
Hyperliquid has provided a workaround to serve logs through a [dedicated resource](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/raw-hyperevm-block-data) in case their network is inaccessible from RPC providers. CCIP is utilizing Hyperliquid's data source to backfill missing logs. In addition, CCIP Node Operators have implemented additional defense, using an multi-RPC approach, allowing for detection of out-of-sync or offline RPCs and an automatic fallback. If all configured RPCs are unavailable for a period of 10+ minutes AND the Hyperliquid data source does not provide them, logs may not be recoverable.
---
# CCIP Service Responsibility - Shared Accountability Model
Source: https://docs.chain.link/ccip/service-responsibility
Last Updated: 2025-06-27
The Chainlink Cross-Chain Interoperability Protocol (CCIP) is a secure, reliable, and easy-to-use interoperability protocol for building cross-chain applications and services. The use of CCIP involves application developers, blockchain development teams, token developers and Chainlink node operators, among others. These participants share responsibility for ensuring that operation and performance match expectations. Please note that CCIP support of a particular blockchain, application, or token does not constitute endorsement of such blockchain, application, or token. Please review the [CCIP Service Limits](/ccip/service-limits) which provides important additional information.
## Application Developer Responsibilities
Application developers are responsible for the correctness, security, and reliability of their application. This includes:
- **Code and application audits:** Developers are responsible for auditing their code and applications before deploying to production. Developers must determine the quality of any audits and ensure that they meet the requirements for their application.
- **CCIP upgrades and best practices:** Developers are responsible for following CCIP documentation regarding implementing CCIP upgrades and best practices for integrating CCIP in their applications.
- **Code dependencies and imports:** Developers are responsible for ensuring the quality, reliability, and security of any dependencies or imported packages that they use with Chainlink CCIP, as well as reviewing and auditing these dependencies and packages.
- **Code quality and testing:** Developers are responsible for ensuring that their application code, onchain and offchain, meets the quality expectations and has undergone rigorous testing.
- **Application monitoring and alerting:** Developers must monitor their applications, inform their users of any abnormal activity, and take appropriate action to restore normal operations.
- **Blockchain risk assessment:** Developers are responsible for the risk assessment of any blockchain network where they choose to deploy their application on or decide to interoperate with, when using Chainlink CCIP. This includes reviewing the time-to-finality formally documented by a blockchain's development team, understanding [how CCIP uses it to determine finality](/ccip/ccip-execution-latency), the nuances in the different types of deterministic finality, and being aware of the risks when CCIP uses block depth to determine chain finality.
- **Token risk assessment:** Developers are responsible for the risk assessment of any tokens they choose to support or list in their application and expose to their users.
- **Risk communication:** Developers must clearly articulate and communicate identified risks to their users.
- **Manual execution:** Developers must monitor their CCIP transactions and take action when transactions require manual execution. For example, informing their users and directing them to the appropriate page on the [CCIP Explorer](https://ccip.chain.link).
- **Risk Management Network coverage:** Developers must check the deployment status of the Risk Management Network on the chains they build on, such as via the [CCIP Directory](/ccip/directory). If the Risk Management Network is not yet active on a chain, developers must validate that its absence conforms to the requirements of their application's specific use case.
## Blockchain Development Team Responsibilities
Blockchain development teams are responsible for the correctness, security, and reliability of their blockchain software. This includes:
- **Block finality:** Blockchain development teams must ensure that blocks with a [commitment level](https://ethereum.org/en/developers/docs/apis/json-rpc/#default-block) of `finalized` are actually final. The properties of the finality mechanism, including underlying assumptions and conditions under which finality violations could occur, must be clearly documented and communicated to application developers in the blockchain ecosystem. The documented time-to-finality informs how long CCIP waits for finality for outbound transactions from that chain; however, an additional buffer may be added.
- **Governance model:** Blockchain development teams are responsible for setting up a clear and effective governance model and communicating its participants and processes clearly to its stakeholders and application developers.
- **Fixes and upgrades:** Blockchain development teams must communicate availability of fixes immediately and announce planned upgrades as much in advance as possible so blockchain validators and application developers can prepare themselves accordingly.
- **Incident management:** Blockchain development teams are responsible for clearly articulating and communicating any security, reliability and availability incidents to their community. This includes root cause analysis, post-mortem details and a clear plan of action to recover and prevent from happening in the future.
- **Blockchain liveness:** Blockchain development teams must take appropriate action to ensure their blockchain maintains a high degree of liveness and aligns with set expectations towards their community members and applications developers.
- **Data availability**: The blockchain network must ensure that complete historical data remains consistently accessible through standard RPC interfaces. This includes block headers, transactions, and emitted logs. This includes ensuring that nodes can serve past blocks and logs even after temporary downtime or restarts. Failure to provide reliable access to historical logs can result in stuck or failed transactions.
## Token Developers Responsibilities
Token Developers may enable token transfers on CCIP for the tokens that they administer. Enabling token transfers on CCIP allows users to transfer tokens between supported blockchains using either [Burn and Mint](/ccip/concepts/cross-chain-token/overview#burn-and-mint), [Lock and Mint](/ccip/concepts/cross-chain-token/overview#lock-and-mint), or [Lock and Unlock](/ccip/concepts/cross-chain-token/overview#lock-and-unlock) processes. Token Developers who choose to enable token transfers on CCIP are responsible for the correctness, security, and reliability of their token pools, token configurations, and token contracts. This includes:
- **Code and application audits**: Token Developers are responsible for auditing their token contract code and token pool contract code. Developers must determine the quality of any audits and ensure that they meet the requirements for their use cases.
- **Configuration of CCIP contracts**: Token Developers are responsible for maintaining the correct token pool and token administrator for their token in all applicable TokenAdminRegistry contracts. Users are responsible for maintaining control of the address which is set as the token administrator. This token administrator is the only role authorized to map a token to the corresponding token pool on the same network.
- **CCIP upgrades and best practices**: Token Developers are responsible for following CCIP documentation regarding implementing CCIP upgrades and best practices for enabling token transfers on CCIP for their token.
- **Code dependencies and imports**: Token Developers are responsible for ensuring the quality, reliability, and security of any dependencies or imported packages that they use with their token contracts, token pools, or configurations including the TokenAdminRegistry. Token Developers are responsible for reviewing and auditing these dependencies and packages.
- **Token Developers must retain access to the token administrator account** after it has accepted this role in the TokenAdminRegistry. Neither Chainlink Labs nor the Chainlink Foundation is responsible for any loss of access to these token pools, loss of funds, or disruption to applications due to loss of access to these required functions.
- **Blockchain risk assessment**: Token Developers are responsible for the risk assessment of any blockchain network where they choose to deploy their tokens, token pools, and tokens enabled for transfer using Chainlink CCIP.
- **Risk communication**: Token Developers must clearly articulate and communicate identified risks to the users of those tokens including any risks specific to the configuration of tokens enabled for transfer using Chainlink CCIP.
- **Authorization**: Token Developers must verify that they are authorized to create token pools for a given token. Although anyone may create a token pool, the token developer must properly register that token with Chainlink CCIP. Token Developers must also properly configure the TokenAdminRegistry.
- **Token pool configurations for [Rebasable Tokens](/ccip/concepts/cross-chain-token/evm/token-pools#tokens-with-rebasing-or-fee-on-transfer-mechanisms)**: Token Developers must properly write the logic in their token pool for burning and minting tokens based on the rebasing mechanism.
- **Token transfer rate limits**: Token DeveloperToken Owners must select and configure appropriate token transfer rate limits for tokens on each lane where they choose to enable their token.
- **Token transfer types**: Token Developers must select appropriate token transfer type for their tokens; either [Burn and Mint](/ccip/concepts/cross-chain-token/overview#burn-and-mint), [Lock and Mint](/ccip/concepts/cross-chain-token/overview#lock-and-mint), or [Lock and Unlock](/ccip/concepts/cross-chain-token/overview#lock-and-unlock). Token Developers are responsible for implementing the burn and mint functions, lock and mint functions or lock and unlock functions in their token contracts correctly on all applicable chains.
- **Migration between CCIP versions**: Token Developers who wish to adopt future versions of CCIP are responsible for all migration tasks required to adopt new features and functionality.
- **Best Practices**: Token Developers are responsible for following the appropriate best practices for creating, managing, and enabling transfers of their tokens on Chainlink CCIP.
- **Risk Management Network coverage**: Token Developers must check the deployment status of the Risk Management Network on the chains they build on, which can be found on the [CCIP Directory](/ccip/directory) page. If the Risk Management Network is not yet active on a chain, Token Developers must validate that its absence conforms to their requirements.
- **Token Developer Attestation**: Token Developers are responsible for ensuring the quality, reliability, and security of their associated attestation endpoint(s). Token Developers are responsible for adhering to Chainlink-defined specifications and maintaining an up-to-date implementation. Neither Chainlink Labs nor the Chainlink Foundation are responsible for the development, maintenance, or operation of Token Developer Attestation endpoints.
- **Following implementation specifications**: Failure to adhere to design specifications for the Chainlink-defined Token Developer Attestation endpoint can result in stuck or failed transactions for users, incorrect accounting of token supply, and/or potential loss of tokens.
- **Maintenance**: Failure to maintain up-to-date compatibility with the Chainlink-defined design specifications may result in downtime or unreliable attestations.
- **Reliability**: Attestation endpoints must be built to handle user demand, both in terms of transactional capacity and uptime. Failure to respond to attestation requests may result in stuck or failed transactions for users and/or potential loss of tokens.
- **Liquidity Management**: Token Developers who choose the **Lock and Mint** or **Lock and Unlock** mechanism must ensure their token pools have sufficient liquidity when releasing tokens. Failure to maintain adequate liquidity can result in stalled or failed cross-chain transfers, causing a degraded user experience. Token Developers are responsible for:
- **Ensuring sufficient liquidity**: Continuously monitor transaction volumes and add liquidity to the pool before it is depleted to avoid having user funds stuck in transit.
- **Avoiding fragmented liquidity**: Where possible, minimize the use of **Lock and Unlock** across multiple blockchains to reduce operational complexity and prevent liquidity from being split across multiple pools.
- **Monitoring liquidity health and automating alerts**: Implement monitoring and alerting systems that notify Token Developers when liquidity drops below certain thresholds, allowing for proactive liquidity management before user transfers fail.
- **Proper use of provideLiquidity and withdrawLiquidity**: Only authorized entities (such as a trusted liquidity manager) should manage liquidity. Ensure all access controls are in place to prevent unauthorized manipulation of the token pool.
Although Token Developers may request that their tokens be added to Transporter, tokens may be added to Transporter at any time even if it has not been explicitly requested.
## Chainlink Node Operator Responsibilities
High-quality Chainlink node operators participate in the decentralized oracle networks (DONs) that power CCIP and the Risk Management Network using a configuration specified in the Chainlink software. As participants in these deployments, Node Operators are responsible for the following components of Chainlink CCIP and the Risk Management Network:
- **Node operations:** Chainlink node operators must ensure the proper configuration, maintenance, and monitoring of their nodes participating in the Chainlink CCIP and Risk Management Network DONs.
- **Transaction execution:** Chainlink node operators must ensure that transactions execute onchain in a timely manner and that they apply gas bumping when necessary.
- **Blockchain client:** Chainlink node operators are responsible for selecting and properly employing blockchain clients, including latest fixes and upgrades, to connect to supported blockchain networks.
- **Consensus participation:** Chainlink node operators must maintain continuous uptime and active participation in OCR consensus.
- **Infrastructure security:** Chainlink node operators must follow infrastructure security best practices. These include access control, configuration management, key management, software version & patch management, and (where applicable) physical security of the underlying hardware.
- **Software version:** Chainlink node operators are responsible for ensuring that Chainlink node deployments are running the latest software versions.
- **Responsiveness:** Chainlink node operators must respond to important communication from Chainlink Labs or from other node operators in a timely manner.
---
# CCIP Execution Latency - Transaction Finality and Timing
Source: https://docs.chain.link/ccip/ccip-execution-latency
Last Updated: 2025-06-04
## CCIP transaction lifecycle
As depicted in the [CCIP detailed architecture](/ccip/concepts/architecture/overview) section, the CCIP transaction lifecycle involves multiple stages from the source blockchain to the destination blockchain:
**Source blockchain:**
1. A sender contract or externally owned account (EOA) sends a CCIP message.
2. The transaction is included in a block and processed by "network participants" (validators in PoS and miners in PoW blockchains).
3. The Committing Decentralized Oracle Network (DON) waits for the block containing the transaction to achieve finality.
**Destination blockchain:**
1. After finality is reached on the source blockchain, the Committing DON relays the Merkle root of a batch of finalized messages to the OffRamp contract on the destination chain.
2. The Risk Management Network verifies and blesses the committed Merkle root on the destination chain, confirming the integrity of the Merkle root and the messages it authenticates.
3. The Executing DON executes the message on the destination chain. If the execution cost on the destination chain is within an acceptable range, the message is executed promptly after being blessed by the Risk Management Network.
The combined latencies of each step in the transaction lifecycle on both the source and destination blockchains impact the total end-to-end execution time for a CCIP transaction. Because different blockchains have unique optimizations for their consensus mechanisms, block sizes, and block times, some blockchains are faster at processing transactions than others. However, various factors can lead to variation in transaction times, including when transferring between the same pair of blockchains:
- **Finality:** Finality is the assurance that past transactions included onchain are extremely difficult or impossible to revert. Finality varies across different blockchains. Some blockchains offer instant finality, while others require multiple block confirmations.
- **Network Congestion:** Network congestion occurs when the volume of transactions exceeds the capacity of the blockchain network, leading to delays in transaction processing. Multiple factors can contribute to network congestion, such as high transaction volumes, increased adoption of blockchain technologies, and events like token launches.
- **Gas Price:** Network participants often prioritize transactions with higher gas prices. If a low gas price is set for a transaction, it can take longer to process than one with a higher gas price, especially during network congestion.
Waiting for finality on the source blockchain is crucial when transacting across multiple networks, as it helps ensure that actions taken on the destination blockchain are based on transactions on the source blockchain that are extremely difficult or impossible to revert. Because the time to achieve finality varies across blockchains and significantly impacts the total CCIP execution latency, the following sections will focus on explaining the different types of finality and how CCIP approaches source chain security on each supported blockchain.
## Finality
Different blockchains employ various consensus mechanisms, leading to different types of finality. This affects users, as even once a transaction is onchain, they must often wait for it to be considered finalized (a time period that varies by blockchain). Finality with blockchains can primarily be categorized into two main types: **probabilistic** finality and **deterministic** finality:
- **Probabilistic finality** is mainly used by Proof-of-Work blockchains and is not the main subject of this article.
- **Deterministic finality** is widely used in most smart contract enabled blockchains that Chainlink CCIP is integrated with today.
### Types of Deterministic Finality
#### Finality on L1 PoA/PoS Chains
Typically, Proof of Authority / Proof of Stake (PoA/PoS) chains use a deterministic model to determine when a block/transaction is considered final. The consensus protocol utilized in such a system is usually designed to be Byzantine Fault Tolerant (BFT). This means that under the assumption that some subset (usually between 51% - 67%) of the participating nodes/stake are honest, and there are no errors in the protocol's implementation, the system works as expected and finality assurances are upheld.
**Examples:**
- **Ethereum's PoS:** Ethereum PoS achieves Byzantine Fault Tolerance (BFT) through economic constraints. Ethereum PoS manages finality using "checkpoint" blocks. The first block in each epoch is designated as a checkpoint. Validators vote on pairs of checkpoints. When two-thirds of the total staked ETH validators agree on the validity of the pair, the earlier of the two checkpoints becomes finalized. To revert a finalized block, an attacker would have to burn at least one-third of the total staked ether, making such an attack extremely costly and difficult to achieve.
- **Comet BFT (Cosmos Hub Network):** Comet BFT is a Byzantine Fault Tolerant (BFT) consensus algorithm designed to provide instant finality. It achieves BFT by ensuring that consensus can be reached as long as more than two-thirds of validators are honest. Once these validators confirm a block, it is immediately considered final and irreversible.
- **Avalanche:** Avalanche uses a random sampling of validators who repeatedly vote on transactions. This process continues until enough validators agree, achieving sub-second finality.
- **BNB Chain:** BNB Chain uses a combination of Proof of Staked Authority (PoSA) and Byzantine Fault Tolerance (BFT) algorithms to finalize transactions. If two-thirds or more of the validators vote as expected, it takes an average of 2.5 blocks to finalize a transaction.
#### Finality on L2s
Layer 2 blockchains, or L2s, are implementations of blockchain systems built on top of existing blockchains (known as Layer 1, or L1) to improve scalability and reduce transaction costs. While they operate independently, L2s are designed to inherit the security of the underlying Layer 1 blockchain: L2s post periodic checkpoints to the underlying blockchain they are built on top of, settling their state and providing stronger finality guarantees than what is provided by their native model.
**Optimistic Rollups:**
Most of the popular optimistic rollups that exist today are run through a centralized sequencer. The sequencer is responsible for ordering incoming transactions, including them in blocks, batching them together and posting them as a bundle to the underlying blockchain they settle on. These bundles serve as commitments and once posted provide more certainty on the finalized state of the rollup. Given that the sequencer is centralized, users are faced with the choice to trust that it won't change the order of the transactions or wait until these commitments are posted to the underlying L1 blockchain.
The optimistic model means that the commitment is valid by default when it is posted to the Layer 1 (L1) blockchain. This is why optimistic rollups typically provide a challenge period, during which a commitment can be challenged if it turns out to be fraudulent. If a challenge is successful, the commitment is replaced and the rollup state is updated to the correct one.
**The typical lifecycle of an optimistic rollup transaction is:**
1. Transaction is included in an L2 block by the sequencer.
2. Transaction is included in a batch that is committed to the L1.
3. Challenge period during which a batch can be challenged if it's invalid - usually lasts a week or more.
4. Transaction is finalized on the L1 - at this point it is considered irreversible.
In the popular optimistic rollup implementations that exist (e.g. OP, Arbitrum, etc.), a commitment can only be challenged if it contains an invalid state root. If the commitment is a valid continuation of the L2, it cannot be challenged. Therefore, seeing a commitment and verifying that it is valid is sufficient certainty for most users to assume finality on this type of L2s, as long as they trust the finality guarantees of the underlying L1. Importantly, this guarantee is supported by waiting for the commitment to the L1 to be finalized according to the L1's finality model.
**ZK Rollups:**
Similarly to optimistic rollups, most popular ZK rollups that exist today are run through a centralized sequencer. The ZK rollup sequencer is also responsible for ordering incoming transactions, including them in blocks, batching them together and posting them as a bundle to the underlying L1 blockchain they settle on. However, in the case of ZK rollups, they also post a validity proof with each batch that is automatically verified onchain on the underlying L1. This validity proof removes the need for a challenge period like on optimistic rollups.
**The typical lifecycle of a ZK rollup transaction is:**
1. Transaction is included in an L2 block by the sequencer.
2. Transaction is included in a batch that is committed to the L1.
3. Validity proof is posted on the L1 that proves the commitment from step 2.
4. Transaction is finalized on the L1.
5. Transaction is considered irreversible. In many cases this happens after a considerable rollup-specific "safety delay" (12-24 hours) from the previous step, which is expected to be reduced as the technology matures.
### How CCIP Determines Finality
The end-to-end transaction times of CCIP messages depend largely on the time-to-finality of the source blockchain. Different blockchains have varying finality types, leading to different times to reach finality. This section explains how CCIP determines finality for different blockchains.
#### Finality Tag
Blockchains with deterministic finality often use a finality tag to indicate when a block is considered final. The finality tag delineates which blocks are finalized, offering a standardized way to determine transaction finality.
- After The Merge, Ethereum shifted to an epoch-based process in PoS, where finality is achieved when two-thirds of validators agree on block finalization over two epochs (64 slots, approximately 12.8 minutes). The Ethereum team introduced the finality tag to provide a default block parameter in specific [JSON-RPC calls](https://ethereum.org/en/developers/docs/apis/json-rpc/), delineating finalized blocks without ambiguity. The finality tag is supported by various Ethereum clients, including Geth.
- Other blockchains have adopted similar finality tags to indicate finalized blocks.
#### Block Depth
In some cases, CCIP relies on block depth to determine when a transaction can be considered final. The block depth refers to the number of successive blocks added after the one containing a given transaction. CCIP uses a sufficient number of blocks to consider the transaction most likely safe from reorgs. There are three cases where CCIP would use block depth:
- Blockchains with probabilistic finality.
- Blockchains with deterministic finality but without a finality tag: In some cases, blockchains have deterministic finality but do not provide a finality tag.
- Blockchains with deterministic finality but slow finality times: In some cases, deterministic finality can take a significant amount of time to reach, leading to a poor user experience.
### Finality by blockchain
This section provides an overview of the finality methods CCIP uses to determine finality for each currently supported blockchain. The table below lists each blockchain and its finality method—whether it uses a finality tag or block depth (with the number of blocks specified for block depth)—and the estimated time required to achieve finality.
| Source Blockchain | Finality Method | Estimated Time for Finality |
| ----------------- | ----------------------------------------- | --------------------------- |
| Apechain | Finality tag | 50 minutes |
| Arbitrum | Finality tag | 17 minutes |
| Astar | Finality tag | 35 seconds |
| Avalanche | Finality tag | \< 1 second |
| Base | Finality tag | 18 minutes |
| Berachain | Finality tag | 7 seconds |
| BitLayer | [Block depth](#block-depth) (21 blocks) | 1 minute |
| Blast | Finality tag | 20 minutes |
| BNB | Finality tag | 5 seconds |
| Bob | Finality tag | 2 hours |
| B² | Finality tag | 20 minutes |
| Celo | Finality tag | \< 1 second |
| Core | [Block depth](#block-depth) (7 blocks) | 1 minute |
| Corn | Finality tag | 12 hours |
| Cronos | Finality tag | 1 second |
| Cronos zkEVM | Finality tag | 31 hours |
| Ethereum | Finality tag | 15 minutes |
| Fraxtal | Finality tag | 30 minutes |
| Gnosis | Finality tag | 3 minutes |
| Hashkey | Finality tag | 1 hour |
| Ink | Finality tag | 2 hours |
| Kroma | Finality tag | 25 minutes |
| Linea | [Block depth](#block-depth) (600 blocks) | 20 minutes |
| Mantle | Finality tag | 28 minutes |
| Metis | Finality tag | 2 hours |
| Mind Network | Finality tag | 1 hour |
| Mode | Finality tag | 37 minutes |
| OP | Finality tag | 20 minutes |
| Polygon | [Block depth](#block-depth) (500 blocks) | 17 minutes |
| Polygon zkEVM | Finality tag | 2 hours |
| Ronin | Finality tag | 10 seconds |
| Scroll | Finality tag | 1 hour |
| Sei | Finality tag | 1 second |
| Solana | Finality tag | \< 1 second |
| Soneium | Finality tag | 27 minutes |
| Sonic | [Block depth](#block-depth) (10 blocks) | 7 seconds |
| Shibarium | Finality tag | 1 minute |
| Treasure | Finality tag | 7 hours |
| Unichain | Finality tag | 24 minutes |
| Wemix | Finality tag | \< 1 second |
| Worldchain | Finality tag | 40 minutes |
| XLayer | Finality tag | 1 hour |
| Zircuit | Finality tag | 21 minutes |
| ZKsync | [Block depth](#block-depth) (1200 blocks) | 20 minutes |
This page provides details on the expected latency for a cross-chain transaction using CCIP, covering the different stages of transaction processing and the factors that influence overall execution times.
For a comprehensive understanding of CCIP's architecture and how messages flow through the system, refer to the [CCIP detailed architecture](/ccip/concepts/architecture/overview) documentation.
---
# CCIP Billing
Source: https://docs.chain.link/ccip/billing
Last Updated: 2025-05-19
The CCIP billing model uses the `feeToken` specified in the [message](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) to pay a single fee on the source blockchain. CCIP uses a gas-locked fee payment mechanism to help ensure the reliable execution of cross-chain transactions regardless of destination blockchain gas spikes. For developers, this means you can simply pay on the source blockchain and CCIP will take care of execution on the destination blockchain.
CCIP supports fee payments in LINK and in alternative assets, including blockchain-native gas tokens and their ERC-20 wrapped versions. The payment model for CCIP is designed to significantly reduce friction for users and quickly scale CCIP to more blockchains by supporting fee payments that originate across a multitude of blockchains over time.
Aside from billing, remember to [carefully estimate the `gasLimit` that you set](/ccip/concepts/best-practices/evm#setting-gaslimit) for your destination contract so CCIP can have enough gas to execute `ccipReceive()`, if applicable. Any unspent gas from this user-set limit is not refunded.
## Billing mechanism
The fee is calculated by the following formula:
```
fee = blockchain fee + network fee
```
Where:
- `fee`: The total fee for processing a [CCIP message](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage). **Note:** Users can call the [getFee](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee) function to estimate the fee.
- `blockchain fee`: This represents an estimation of the gas cost the node operators will pay to deliver the CCIP message to the destination blockchain.
- `network fee`: Fee paid to CCIP service providers, including node operators running the [Decentralized Oracle Network](/ccip/concepts/architecture/key-concepts#decentralized-oracle-network-don) and [Risk Management Network](/ccip/concepts/architecture/key-concepts#risk-management-network).
### Blockchain fee
The blockchain fee is calculated by the following formula:
```
blockchain fee = execution cost + data availability cost
```
#### Execution cost
The execution cost is directly correlated with the estimated gas usage to execute the transaction on the destination blockchain:
```
execution cost = gas price * gas usage * gas multiplier
```
Where:
- `gas price`: The destination gas price. CCIP maintains a cache of destination gas prices on each source blockchain, denominated in each `feeToken`.
- `gas multiplier`: Scaling factor. This multiplier ensures the reliable execution of transactions regardless of destination blockchain gas spikes.
- `gas usage`:
```
gas usage = gas limit + destination gas overhead + destination gas per payload + gas for token transfers`
```
Where:
- `gas limit`: This specifies the maximum amount of gas CCIP can consume to execute [ccipReceive()](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive) on the receiver contract located on the destination blockchain. Users set the gas limit in the [extra argument field](/ccip/api-reference/evm/v1.6.2/client#genericextraargsv2) of the CCIP message. **Note:** Remember to [carefully estimate the `gasLimit` that you set](/ccip/concepts/best-practices/evm#setting-gaslimit) for your destination contract so CCIP can have enough gas to execute `ccipReceive()`. Any unspent gas from this user-set limit is not refunded.
- `destination gas overhead`: This is the fixed gas cost incurred on the destination blockchain by CCIP (Committing DON + Executing DON) and Risk Management Network.
- `destination gas per payload`: This variable gas depends on the length of the data field in the [CCIP message](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage). If there is no payload (CCIP only transfers tokens), the value is `0`.
- `gas for token transfers`: This variable gas cost is for transferring tokens onto the destination blockchain. If there are no token transfers, the value is `0`.
#### Data availability cost
This cost is only relevant if the destination blockchain is a [L2 layer](https://chain.link/education-hub/what-is-layer-2). Some L2s charge fees for [data availability](https://ethereum.org/en/developers/docs/data-availability). For instance, [optimistic rollups](https://ethereum.org/en/developers/docs/scaling/optimistic-rollups/) process the transactions offchain then post the transaction data to Ethereum as calldata, which costs additional gas.
### Network fee
The fee paid to CCIP service providers, including node operators running the [Decentralized Oracle Network](/ccip/concepts/architecture/key-concepts#decentralized-oracle-network-don) and [Risk Management Network](/ccip/concepts/architecture/key-concepts#risk-management-network) is calculated as follows:
#### Token transfers or programmable token transfers
For token transfers or programmable token transfers (token + data), the network fee varies based on the [token handling mechanism](/ccip/concepts/cross-chain-token/overview#token-handling-mechanisms) and the lanes:
- **Lock and Unlock**: The network fee is percentage-based. For each token, it is calculated using the following expression:
```
tokenAmount * price * percentage
```
Where:
- `tokenAmount`: The amount of tokens being transferred.
- `price`: Initially priced in USD and converted into the `feeToken`.
- `percentage`: The values are provided in the [network fee table](#network-fee-table).
- **Lock and Mint**, **Burn and Mint** and **Burn and Unlock**: The network fee is a static amount. See the [network fee table](#network-fee-table).
#### Messaging (only data)
For messaging (only data): The network fee is a static amount, denominated in USD. See the [network fee table](#network-fee-table).
#### Network fee table
The table below provides an overview of the network fees charged for different use cases on different lanes. Percentage-based fees are calculated on the value transferred in a message. USD-denominated fees are applied per message.
You can use the calculator below to learn the network fees for a specific token. Select the environment (mainnet/testnet), the token, the source blockchain, and the destination blockchain to get the network fee:
---
# CCIP Architecture
Source: https://docs.chain.link/ccip/concepts/architecture
Last Updated: 2025-06-09
This section explains the core architecture of the Cross-Chain Interoperability Protocol (CCIP). Learn about the fundamental components and how they interact to enable secure cross-chain communication.
- **[Overview](/ccip/concepts/architecture/overview)**: Get a high-level summary of the CCIP architecture.
- **[Key Concepts](/ccip/concepts/architecture/key-concepts)**: Understand the essential terms and components within the CCIP ecosystem.
- **[Onchain Components](/ccip/concepts/architecture/onchain)**: Explore the on‑chain components, including EVM smart contracts and Solana programs, and Aptos modules, that operate directly on blockchains.
- **[Offchain Components](/ccip/concepts/architecture/offchain)**: Discover the offchain systems, like the Risk Management Network and Decentralized Oracle Network, that support CCIP operations.
---
# CCIP Architecture - Overview
Source: https://docs.chain.link/ccip/concepts/architecture/overview
Last Updated: 2025-08-05
CCIP is a cross-chain messaging protocol built on Chainlink and secured by its decentralized oracle networks (DONs). It provides the following core capabilities:
- **Token Transfers**: Transfer tokens to an externally owned account (EOA) or a receiving contract.
- **Data Transmission**: Send arbitrary data (i.e., bytes) to a receiving contract.
- **Programmable Token Transfers**: Transfer tokens along with instructions for handling them to a receiving contract (referred to as a programmable token transfer or PTT).
CCIP supports several token transfer mechanisms—including Burn-and-Mint, Lock-and-Mint, and Lock-and-Unlock—using a common interface. Token developers can enable permissionless cross-chain transfers of their tokens via the Chainlink CCT (Cross-Chain Token) standard (detailed documentation is available here). Additionally, CCIP includes an independent Risk Management Network (RMN) that provides layered security through a defense-in-depth approach.
## High-level Architecture
CCIP delivers cross-chain messages from a source chain to a destination chain by combining offchain consensus and onchain execution components. The architecture is composed of two primary layers:
1. **Offchain Architecture**
- Decentralized Oracle Network (DON) with nodes that perform distinct roles:
- Commit: Observes and validates source-chain events to build a consensus-based report.
- Execution: Validates pending messages and optimizes them for execution on the destination chain.
- Risk Management Network:
- Operating independently from the main DON, the Risk Management Network "blesses" the committed messages offchain by generating independent attestations. This additional step enhances overall security with a defense-in-depth design.
2. **Onchain Architecture**
- Router:
- Each blockchain has a single immutable Router contract that serves as the unified interface for users and decentralized applications (dApps).
- On the source blockchain, onramp functionality consists of:
- Providing fee estimates.
- Locking or burning tokens.
- Dispatching the message (data and/or tokens).
- On the destination blockchain, offramping functionality consists of:
- Accepting and verifying commit reports from the Committing DON.
- Validating Risk Management Network nodes signatures when receiving messages from the Executing DON.
- Releasing or minting tokens to the receiver.
- Routing the processed message to the designated receiver.
**Note:** These high-level descriptions outline the core principles of CCIP. The detailed architectures for offchain and onchain architecture—and variations across implementations (e.g., on Solana, the Router incorporates OnRamp functionalities)—can be found in the relevant documentation.
---
# CCIP Architecture - Key Concepts
Source: https://docs.chain.link/ccip/concepts/architecture/key-concepts
Last Updated: 2025-05-19
This page explains the high-level, chain-agnostic concepts behind Chainlink CCIP. We recommend reading these concepts before diving deep into the architecture pages.
## Prerequisites
Before diving into Chainlink CCIP, ensure you understand these fundamental blockchain concepts:
- **Accounts / Addresses**: Many blockchains distinguish between user-controlled addresses (called Externally Owned Accounts (EOAs) on EVM blockchains) and onchain program addresses (often called contract accounts in EVM ecosystems). Even if the name or the underlying account model differs, the principle is that a user's private key or authority controls one address (EOA) while the other is controlled by onchain code or logic (contract account).
- **Smart Contracts**: On EVM-based blockchains, these are pieces of logic (often written in languages like Solidity) that get compiled into bytecode and deployed onchain. Other blockchains—such as SVM-based (e.g., Solana), **Aptos**, or **Sui**—may refer to these as "modules," "programs," or simply "onchain code." The basic idea is the same: executable code that lives on the blockchain and enforces rules without centralized control.
- **Decentralized Applications (dApps)**: [dApps](https://ethereum.org/en/developers/docs/dapps/) are applications that use onchain logic to manage data and transactions.
- **Token Standards**: Many chains use standards (like [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) on EVM-based blockchains) or equivalents (e.g., [SPL tokens on Solana](https://spl.solana.com/token), the [Fungible Asset standard on Aptos](https://aptos.dev/en/build/smart-contracts/fungible-asset), or the [Coin standard on Sui](https://docs.sui.io/standards/coin)) to represent fungible tokens.
- **Merkle Trees**: A data structure that summarizes and verifies large information sets with small cryptographic proofs. To learn more, try this [tutorial](https://ethereum.org/en/developers/tutorials/merkle-proofs-for-offline-data-integrity/).
## Blockchain Families
When you see references to "smart contracts" or "onchain programs," they are executed within a virtual machine (VM). Different blockchains implement different VMs with unique design choices:
- **EVM (Ethereum Virtual Machine)**: Used by Ethereum and many other EVM-compatible chains. Contracts are typically written in languages like [Solidity](https://docs.soliditylang.org/) or [Vyper](https://vyper.readthedocs.io/), compiled to EVM bytecode, and executed in a single-threaded environment. Read this [page](https://ethereum.org/en/developers/docs/evm/) to learn more.
- **SVM (Solana Virtual Machine)**: Solana uses the Sealevel parallelization engine to execute onchain programs compiled to Berkeley Packet Filter (BPF) bytecode. This parallel processing approach can handle multiple simultaneous transactions. Read this [page](https://solana.com/news/sealevel---parallel-processing-thousands-of-smart-contracts) to learn more.
- **Aptos**: The Aptos blockchain uses the Move language and its own implementation of the Move Virtual Machine (MoveVM). The MoveVM is designed for safety and verification, and Aptos processes transactions in parallel using its Block-STM execution engine. Read this [page](https://aptos.dev/en/network/blockchain/move) to learn more.
- **Sui**: The Sui blockchain uses a variant of the Move language. Its architecture is object-centric and designed for high-throughput, parallel execution of transactions, enabling horizontal scaling. Read this [page](https://docs.sui.io/concepts/sui-move-concepts) to learn more.
## Cross-Chain dApps
**Cross-chain dApps** are decentralized applications designed to run on—or interact with—multiple blockchains. By leveraging CCIP for cross-chain interoperability, these dApps can:
- Execute transactions, send data, and transfer tokens between different blockchains.
- Provide their users with access to features or liquidity across multiple ecosystems via a unified experience.
- Specialize in the strengths of each underlying chain, such as high throughput, low fees, or specialized contract (or module) functionality.
Because of this multi-chain support, cross-chain dApps can offer broader functionality than dApps confined to a single blockchain.
## Blockchain Finality
**Blockchain finality** is the assurance that past transactions included on a given blockchain are extremely difficult or impossible to revert. Finality varies across different blockchains. Some blockchains offer instant finality, while others require multiple block confirmations.
Finality is crucial for cross-chain transfers, both for arbitrary messaging and non-intents-based token transfers. It ensures that funds are released/minted or that any specified action is executed on the destination chain only after the source chain action (such as a lock or burn) is finalized. This minimizes loss of funds (in the case of token transfers) or unintended actions (in the case of arbitrary messaging) due to source chain reorganizations.
To learn more about finality and how CCIP handles this, read the [CCIP Execution Latency](/ccip/ccip-execution-latency) conceptual guide.
## Lane
A **lane** in CCIP is a conceptual unidirectional path from one specific blockchain (the source) to another (the destination). For example, the lane from Blockchain A to Blockchain B is distinct from the lane from Blockchain B to Blockchain A. This concept helps in configuring settings that apply uniquely to each path, such as rate limits or other parameters. For instance, sending a message from an EVM blockchain to an SVM blockchain may require different parameters compared to sending a message in the reverse direction.
## Node Operators
Organizations with specialized expertise in running Chainlink nodes. Each operator can run multiple nodes across different decentralized oracle networks.
## Decentralized Oracle Network (DON)
A **decentralized oracle network (DON)** consists of multiple independent Chainlink nodes. These nodes monitor and retrieve data from specified sources (e.g., a source chain), reach consensus off-chain, and post results to a blockchain (e.g., a destination chain) in a [trust-minimized manner](https://blog.chain.link/what-is-trust-minimization/). For more details on the DONs used in CCIP, refer to the CCIP Offchain architecture page.
## Risk Management Network
The Risk Management Network is a unique architectural feature of CCIP that reinforces system security through a defense-in-depth approach based on established engineering principles such as N-version programming. On blockchains where RMN is enabled, a distinct set of node operators—separate from those of the core CCIP DONs—manages RMN functions.
The RMN runs a minimal, separate implementation of the Chainlink node software using a different programming language than the primary CCIP system. This client diversity enhances robustness and minimizes external dependencies, thereby reducing the risk of supply chain attacks.
On the blockchains where RMN is enabled, the RMN nodes independently verify and attest to source chain actions. The core CCIP components on the destination chain verify these attestations. For more details, refer to the following section.
---
# Onchain Architecture
Source: https://docs.chain.link/ccip/concepts/architecture/onchain
Last Updated: 2025-09-03
This section details the onchain components of the CCIP architecture, covering both EVM-compatible chains and SVM-based chains such as Solana.
- **[EVM Architecture](/ccip/concepts/architecture/onchain/evm)**: Learn about the onchain components specific to EVM environments.
- **[SVM Architecture](/ccip/concepts/architecture/onchain/svm)**: Explore the onchain programs and components specific for SVM environments.
- **[Aptos Architecture](/ccip/concepts/architecture/onchain/aptos)**: Understand the onchain components and message structures for Aptos environments.
---
# CCIP Onchain Architecture (EVM)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/evm
Last Updated: 2025-05-19
Chainlink CCIP's EVM onchain architecture consists of a set of smart contracts deployed on both source and destination chains. These components work together with CCIP's offchain infrastructure to provide end-to-end cross-chain interoperability.
- **[Overview](/ccip/concepts/architecture/onchain/evm/overview)**: Provides a high-level introduction to CCIP's onchain architecture, including a component diagram, key roles, and a detailed walk-through of a message's lifecycle from source to destination chain.
- **[Components](/ccip/concepts/architecture/onchain/evm/components)**: Describes each architectural component in detail.
- **[Upgradability](/ccip/concepts/architecture/onchain/evm/upgradability)**: Explains CCIP's approach to secure system evolution.
---
# Onchain Architecture - Overview (EVM)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/evm/overview
Last Updated: 2025-05-19
CCIP's onchain architecture includes key smart contracts that receive and process cross-chain messages on the source and destination chains. A sender of a cross-chain message can be either a smart contract or an [externally owned account (EOA)](https://ethereum.org/en/developers/docs/accounts/#types-of-account).
The interface for senders is the Router contract, which interacts with other internal CCIP contracts as described in the sections below. When the source chain CCIP contracts successfully process the cross-chain request, a unique message ID is returned to the sender. The offchain components listening to the events on the source chain process the message and submit it to the destination chain, where further verification and execution happen. The different components are described below.
## Key Components
The following diagram illustrates the key onchain components:
| Component | Ownership | Role |
| ------------------------ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Sender/Receiver | External (User/dApp) | dApp / EOA / smart contract that initiates the cross-chain message on the source chain and/or receives the message on the destination chain. |
| Router | CCIP | The interface on a chain for all CCIP messages, which takes the form of a minimal, immutable contract. On the source chain, the sender calls it to send a cross-chain message. On the destination chain, it routes the message to the receiver contract. |
| OnRamp | CCIP | A source chain contract that validates and processes messages. It interacts with other contracts for specific actions, such as fees, nonce management, or token handling. It also emits events that are listened to by the CCIP offchain components. |
| OffRamp | CCIP | A destination chain contract that receives a committed message from the Chainlink DON and processes the message on the destination chain. It interacts with other contracts for specific actions, such as nonce management or token handling. |
| Fee Quoter | CCIP | A contract that validates and computes fees for cross-chain messages. |
| Nonce Manager | CCIP | A contract that implements message ordering by tracking nonces. |
| Token Admin Registry | CCIP | A contract that contains the mapping of tokens to their token pools. |
| Token Pool | External (Token Developer) | A contract that implements token handling mechanisms, such as Lock/Burn/Release, typically set up by the token Developer. There is one token pool per token on a given chain. |
| Token Contract | External (Token Developer) | An ERC20 token contract owned by the token Developer |
| RMN Contract (RMNRemote) | CCIP | Verifies Risk Management Network (RMN) signatures and handles cursing. |
## Typical Lifecycle of a Message
### Source Blockchain
1. **Preparation**
- The Sender prepares a CCIP Message for a destination blockchain of choice. A CCIP Message includes the following information:
- Receiver (EOA or smart contract)
- Data payload
- Tokens and amounts (if applicable)
- Fee token
- Extra Arguments (e.g., gas limit to use when calling the receiver on the destination chain)
- The Sender calls `Router.getFee()` to receive the total fees to pay for using CCIP. Internally, the Router calls the OnRamp, which calls the Fee Quoter to get the fee.
- The Sender approves the fee if the fee is not the native token.
- The Sender calls the `Router.ccipSend()` function with the CCIP Message and the destination chain selector. For token transfers, the token amount to be transferred must be approved to the Router before this call is made.
2. **Sending**
The Router receives the fee tokens and transfers them to the OnRamp:
- The Router receives the tokens and transfers them to their corresponding Token Pools, which are retrieved from the Token Admin Registry. If the sender has not approved the tokens to the Router, the operation will fail.
- The Router forwards the CCIP Message to the OnRamp for processing, which:
- Validates the message by checking parameters, such as the number of tokens, gas limit, and data length.
- Validates that the destination chain is not cursed.
- For each token included in the Message, instructs the corresponding token pool to lock or burn the tokens, which also verifies the token pool rate limit for that lane.
- A `messageId` is generated and returned to the Sender.
- The OnRamp emits a `CCIPMessageSent` event containing the sequenced message. The Committing DON detects this event and processes the Message.
3. **Initial Offchain Processing**
- The CCIP Commit DON monitors for the `CCIPMessageSent` event to process the Message offchain. More details about the offchain processing are available here.
### Destination Blockchain
1. **Commit Phase**
- The final OCR report from the Committing DON is recorded onchain in the OffRamp contract via the `commit` function. This OCR report may include a mix of blessed and non-blessed roots and price updates from multiple source chains.
- If the OCR report includes **blessed** merkle roots from RMN-enabled source chains, the OffRamp verifies the RMN node signatures onchain via the `RMNRemote` contract.
- The OffRamp also validates that all **unblessed** merkle roots originate from the source chains where RMN is disabled.
- The OffRamp emits a `CommitReportAccepted` event, confirming that a valid commit has been accepted.
2. **Secondary Offchain Processing**
- The CCIP Executing DON monitors for the `CommitReportAccepted` event to identify commit reports with pending executions. All messages associated with these commit reports are collected and a merkle proof is computed for every message ready for execution. The execution plugin considers each message's gas limits and calldata size during the batching process. Note that the message batch executed by the Executing DON may be a subset of a Committing DON batch. The computed merkle proof is then included in the Execute Plugin Report. More details regarding the offchain architecture are available here.
3. **Execution Phase**
- When the execution plugin submits the report, the OffRamp verifies the merkle proofs against the stored merkle roots.
- The OffRamp performs validations, including ensuring that the source chain is not cursed.
- If the CCIP Message includes tokens, the OffRamp retrieves the relevant token pool from the Token Admin Registry and calls the Token Pool's `releaseOrMint` function. This function validates token pool rate limits, releases or mints the tokens, and transfers them to the specified receiver.
- If the CCIP Message contains an arbitrary bytes payload, the OffRamp calls the Router to deliver the CCIP Message to the Receiver.
- The OffRamp emits a final event, `ExecutionStateChanged`, containing the execution state. This state corresponds to one of the following numerical values:
- `0`: `UNTOUCHED`
- `1`: `IN_PROGRESS`
- `2`: `SUCCESS`
- `3`: `FAILURE`
---
# Onchain Architecture - Components (EVM)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/evm/components
Last Updated: 2025-05-19
This section provides more detail on the Onchain components. The [API Reference page](/ccip/api-reference/evm) contains the interface functions and revert reasons.
## Sender/Receiver
**CCIP supports the following as senders and receivers:**
- An externally owned account (EOA)
- A smart contract or smart account
**CCIP messages can be sent in any of the following combinations:**
- EOA → EOA
- EOA → Smart Contract
- Smart Contract → EOA
- Smart Contract → Smart Contract
**Depending on a dApp's architecture, an EOA may interact with the Router using:**
- A frontend or middleware component (e.g., a JavaScript program)
- A sender smart contract
**A CCIP Message can include:**
- An arbitrary bytes payload
- A token transfer
- A programmable token transfer
**Sender Responsibilities:**
- Prepare a structured CCIP Message.
- Retrieve a fee estimate from the Router.
- Call the Router to send the message, as described in the Message Lifecycle section.
**Receiver Considerations:**
- **Data Processing:** If the CCIP Message contains a bytes payload or a programmable token transfer, the receiver must be a smart contract capable of processing the data. Messages sent to an EOA will not deliver the payload.
- **Function Implementation:** The receiver should implement the `ccipReceive()` function using the `IAny2EVMMessageReceiver` interface. The Router is the only contract authorized to call this function.
**Additional Resources:**
- CCIP provides smart contract examples for a Sender/Receiver in the [Applications folder](https://github.com/smartcontractkit/chainlink-ccip/tree/release/contracts-ccip-1.6.2/chains/evm/contracts/applications). For most use cases, consider implementing try-catch mechanisms using a defensive receiver. More details are available [here](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/applications/DefensiveExample.sol).
## Router
The Router serves as the single interface for a sender or interfacing dApp on the source chain for all CCIP messages. As a minimal, immutable contract, there is only one Router contract per chain.
The Router exposes two primary functions for the sender:
- `getFee()`: Retrieves the CCIP fee for a given message.
- `ccipSend()`: Sends a cross-chain message and returns a unique message ID to the sender.
For function signatures and revert reasons, see the [API reference docs](/ccip/api-reference/evm).
On the destination chain, if the CCIP Message contains arbitrary data (or data from a programmable token transfer), the Router routes the message from the OffRamp to the receiver contract. Once the receiving smart contract successfully processes the message, the Router emits a `MessageExecuted` event.
## OnRamp
The OnRamp is an internal CCIP smart contract and is not meant to be user-facing. It operates on the source chain and is the primary contract that the Router calls to process a message. In previous versions of CCIP, there was an OnRamp per destination chain; with the latest release, a single OnRamp on a chain handles messages for any destination chain.
When the Router forwards a `ccipSend()` request to the OnRamp, the contract performs the following actions:
1. **Validations**
- Performs required validations before processing the message, such as verifying that the receiver address is valid.
2. **Token Transfer Processing**
- If the message involves token transfers, it retrieves the correct Token Pool from the Token Admin Registry.
- Initiates calls to lock or burn the token, based on the token handling mechanism.
3. **Nonce Management**
- Uses the Nonce Manager to ensure messages requiring in-order execution are processed in the correct order.
4. **Message ID Generation**
- Returns a unique message ID to the Router.
5. **Event Emission**
- Emits a `CCIPMessageSent` event containing the message ID, source and destination chain information, sender, receiver, and other key message details.
## OffRamp
The OffRamp is an internal CCIP smart contract that operates on the destination chain. It is the primary contract that the CCIP DONs call to process incoming messages.
### Commit Phase
During the **Commit Phase**, the following steps occur:
1. **Commit Report Submission**
- The Committing DON calls the `commit()` function on the OffRamp with a Commit Report that includes messages and/or price reports.
2. **Validation of RMN Blessing**
- If the Commit Report includes blessed merkle roots from RMN-enabled source chains, the OffRamp verifies the RMN node signatures onchain via the `RMNRemote` contract.
- If the Commit Report does not include RMN signatures, the OffRamp validates that all unblessed merkle roots originate from source chains where RMN is disabled.
3. **Price Report Staleness Check**
- The OffRamp validates the staleness of price reports before forwarding the price to the FeeQuoter.
4. **Cursed Source Chain Check**
- The OffRamp verifies the RMN status to ensure that messages from a cursed source chain are blocked.
5. **Event Emission**
- At the end of the Commit Phase, the OffRamp emits a `CommitReportAccepted` event, which the Execution plugin monitors.
### Execution Phase
In the **Execution Phase**, the OffRamp processes the message for final execution:
1. **Merkle Proof Verification**
- The OffRamp verifies the merkle proofs included in the Execution Reports against the committed merkle roots.
2. **Additional Validations**
- The OffRamp performs validations, including ensuring that the source chain is not cursed.
3. **Token Processing (if applicable)**
- If the CCIP Message includes tokens, the OffRamp retrieves the relevant Token Pool from the Token Admin Registry and calls the Token Pool's `unlock/mint` function. This function validates token pool rate limits, unlocks or mints the tokens, and transfers them to the specified receiver.
4. **Message Delivery**
- If the CCIP Message contains arbitrary data, the OffRamp uses the Router to deliver the CCIP Message to the Receiver.
5. **Nonce Management**
- For ordered messages (i.e., messages with a non-zero nonce), the OffRamp interacts with the Nonce Manager to ensure inbound messages are processed sequentially.
6. **Final Execution Status**
- The OffRamp sets the message's final execution status and emits a final ExecutionStateChanged event, indicating either a "Success" or "Failure" state.
**Permissionless Manual Execution (Fallback):** If execution fails—due to insufficient gas limit or a logical error in the receiver smart contract—the message can be manually executed by the user or dApp by directly interacting with the OffRamp. Read the [manual execution](/ccip/concepts/manual-execution) documentation for more details.
## FeeQuoter
The FeeQuoter is an internal CCIP smart contract that calculates and returns CCIP fees on the source chain.
- **Fee Calculation**
- When the Router's `getFee()` function is called, the request is forwarded to the FeeQuoter, which estimates and returns the CCIP fee.
- **Price Management**
- Maintains token and gas prices in USD
- Enforces price staleness rules
- Calculates all cross-chain fees based on current pricing data
- Stores token-specific fee configurations
- **Price Updates**
- When a Commit Report contains price updates, the FeeQuoter on the destination chain is updated accordingly.
For additional details on how CCIP fees are calculated, refer to the [CCIP Billing page](/ccip/billing).
## NonceManager
The NonceManager helps order messages in CCIP by tracking outbound nonces on the source blockchain and inbound nonces on the destination blockchain. It ensures strict ordering when the message's extraArgs parameter requires ordering, thus providing a flexible design.
1. **Ordered Messages**
- **Non-zero Nonces:** When an OnRamp identifies a message that must preserve ordering, it increments and assigns a non-zero outbound nonce.
- **Inbound Validation:** On the destination blockchain, the OffRamp checks that the incoming message's nonce matches the expected inbound nonce. If there is a mismatch, the message is skipped or deferred for later retry.
2. **Out-of-Order Messages**
- **Zero Nonces:** For messages marked "out of order," the OnRamp sets the nonce to **0**.
- **No Sequence Checks:** Because `nonce == 0` indicates no ordering, the OffRamp does not validate or increment inbound nonces. These messages can execute immediately without waiting for earlier messages.
## Token Admin Registry
The Token Admin Registry is a user-facing CCIP smart contract that maintains a one-to-one mapping between token addresses and their corresponding token pool addresses on a given chain. The OnRamp and OffRamp contracts use the Token Admin Registry to retrieve a token's configured token pool address to call the appropriate functions:
- **On the source blockchain:** Lock/Burn tokens
- **On the destination blockchain:** Release/Mint tokens
The `setPool()` method can be invoked by a registered CCIP token administrator via the Registry Module. See the [CCT (Cross-Chain Token)](/ccip/concepts/cross-chain-token/evm) documentation for more details on this interaction.
## Tokens and Token Pools
**Tokens:**
- Tokens are developed by token developers and exist independently of the core CCIP contracts.
- Most ERC20 tokens are compatible with CCIP. For more information on compatibility, see [CCT Compatibility](/ccip/concepts/cross-chain-token/evm/tokens).
**Token Pools:**
- Token Pools are external contracts that interact with token contracts. The OnRamp/OffRamp calls them to perform operations such as burning, minting, locking, or releasing tokens.
- Most token pools follow standard models (Lock/Release and Burn/Mint), with audited code available in the CCIP repository.
- For tokens requiring bespoke logic before burn/mint/lock/release, custom pools can be built on top of the base pools. More details are available in the [CCT Pool Types](/ccip/concepts/cross-chain-token/evm/token-pools#standard-token-pools) and [Custom Pools](/ccip/concepts/cross-chain-token/evm/token-pools#custom-token-pools).
## RMN Contract
Risk management in CCIP is performed by a separate, independent network that continuously monitors and validates cross-chain operations for anomalous activity, thereby providing an additional layer of security.
With the latest CCIP release, the Risk Management Network (RMN) blessing occurs offchain. In this process, the Committing DON interacts offchain with RMN nodes to obtain a blessing, and the resulting RMN node signatures are included in the Commit Report posted on the OffRamp at the destination chain.
The RMN Contract is deployed on every chain where CCIP is integrated, even on chains where RMN is not enabled. Its key functions include:
1. **Blessing Verification**
- **Verify function**: The RMNRemote contract's `verify()` function is used by the OffRamp to verify RMN signatures for messages originating from RMN-enabled source chains.
2. **Cursing Mechanism**
- **Curse Initiation**: When the CCIP Owner manually initiates a curse, the `curse()` function is invoked to mark the appropriate subjects as cursed.
- **Curse Detection**: Onchain components (such as the Router, OnRamp, OffRamp, and TokenPool) call the `isCursed()` function on the RMNRemote contract to detect global curses or curses targeting a remote chain.
---
# Onchain Architecture - Upgradability (EVM)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/evm/upgradability
Last Updated: 2025-05-19
Chainlink Cross‐Chain Interoperability Protocol (CCIP) is designed to evolve in response to new emerging feature requests, security considerations, and the need to support additional blockchains over time. This requires a secure upgrade process that preserves CCIP's robust security while allowing for iterative improvements.
**Note**: The Router contract is intentionally immutable. It remains the primary user entry point on both source and destination blockchains—unmodifiable to preserve reliability, stability, and developer predictability over time.
## What Can Be Upgraded
Upgradability in CCIP primarily refers to two categories of changes:
1. **Onchain Configuration**
- Many CCIP contracts expose public setter functions that can adjust operational parameters.
- Since these parameters can be adjusted without requiring the redeployment of an entire contract, it offers a flexible way to help ensure CCIP's security and reliability over time.
2. **Redeploying Contracts and Redirecting References**
- Once a smart contract is deployed, its code cannot be modified. If a new contract version (e.g., OnRamp, OffRamp, or Token Pool) is required, a new updated contract can be deployed, with all existing references updated to point to the new contract address.
- For example, an OffRamp address might be updated in the Router or a local mapping so that new inbound messages go through the upgraded contract.
- This approach ensures older versions remain stable while new versions can be phased in.
## Implementation Process
All onchain configuration changes must pass through a Role-Based Access Control Timelock (RBACTimelock) contract. This mechanism ensures that:
1. **Proposals**
- All proposals originate from a ManyChainMultiSig (MCMS), which requires multiple independent signers to sign off.
- Signers are selected from multiple high-quality Chainlink node operators with a proven, multi-year track record of securing billions in value within the Chainlink Network, as well as from Chainlink Labs.
- Signers are also spread across multiple different geographic locations globally and may be rotated on a periodic basis to help mitigate potential risks as they arise, such as geographic concentration.
- Two distinct paths exist for a proposal to succeed:
- **Time-locked review:** Node operators securing CCIP can veto a proposal within a defined review period. If no veto occurs, the update proceeds.
- **Explicit approval:** A quorum of independent signers (including node operators) actively endorse the proposal, allowing for urgent or time-sensitive fixes.
2. **Review and Veto Window**
- During the timelock review period, CCIP node operators can inspect the onchain proposal (e.g., adjusting a rate limit) and reject it if it appears incorrect, preventing the proposal from being executed.
3. **Execution**
- Once the timelock finishes (with no veto), the proposal transitions to an "executable" state.
- Any party can call the timelock contract to finalize the proposal (e.g., via a [timelock-worker](https://github.com/smartcontractkit/timelock-worker) script).
- The timelock then calls the target CCIP contracts with the specified changes.
4. **Public Verifiability**
- Information about the MCMS, including the timelock configuration, signer set, and ongoing timelocked proposals are visible onchain.
- For instance, the Ethereum mainnet MCMS can be viewed on [Etherscan](https://etherscan.io/address/0xE53289F32c8E690b7173aA33affE9B6B0CB0012F#readContract).
- Anyone can track in-progress proposals, track any node operators vetos, and monitor the final execution status using onchain data.
## Additional Resources
- **[CCIP Owner Contracts - GitHub](https://github.com/smartcontractkit/ccip-owner-contracts)**: Further documentation and source code for the ManyChainMultiSig, timelock, and other related contracts.
- **[Etherscan: MCMS on Ethereum Mainnet](https://etherscan.io/address/0xE53289F32c8E690b7173aA33affE9B6B0CB0012F#readContract)**: Allows anyone to see the current configuration, pending proposals, and signer addresses on Ethereum.
- **[Timelock Worker Repo](https://github.com/smartcontractkit/timelock-worker)**: Demonstrates how to automate final execution for proposals that have cleared the timelock period.
---
# CCIP Onchain Architecture (SVM)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/svm
Last Updated: 2025-05-19
Chainlink CCIP's SVM onchain architecture consists of specialized Solana programs deployed on both source and destination chains. These components work together with CCIP's offchain infrastructure to provide end-to-end cross-chain interoperability.
- **[Overview](/ccip/concepts/architecture/onchain/svm/overview)**: Provides a high-level introduction to CCIP's SVM-based architecture, including component diagrams, key roles, and a detailed walk-through of a message's lifecycle from source to destination chain.
- **[Components](/ccip/concepts/architecture/onchain/svm/components)**: Describes each architectural component in detail.
- **[Upgradability](/ccip/concepts/architecture/onchain/svm/upgradability)**: Explains CCIP's approach to secure system evolution.
---
# Onchain Architecture - Overview (SVM)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/svm/overview
Last Updated: 2025-05-19
On a Solana Virtual Machine (SVM)-based source blockchain, an onchain program or user wallet interacts with the CCIP Router program, which provides a standard interface for sending messages cross-chain. The Router internally calculates fees, locks or burns tokens via a Token Pool program, and then emits a CCIPMessageSent event. The Committing DON observes that event offchain and relays the messages to a destination blockchain.
On an SVM-based destination blockchain, an onchain program called the OffRamp receives a commit from the Committing DON containing merkle roots of batched messages. The OffRamp verifies the OCR signatures, checks with the Risk Management Network (RMN) Remote program that the source chain is not cursed, and then stores the merkle root. Later, the Executing DON submits individual messages for execution, one per transaction (unlike EVM chains, which can execute in batches). The OffRamp verifies each message's merkle proof against the previously committed root, tracks the execution state, and processes the message. This includes making CPI calls to Token Pools to release or mint tokens and, if applicable, delivering message data to a receiver program that implements the CCIP Receive interface.
## Key Components
**Source Chain**:
**Destination Chain**:
| Component | Ownership | Role |
| ----------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Sender | External (User/Program) | The sender could be a wallet or onchain program. It initiates the cross-chain request by calling the Router program's `ccip_send` instruction. |
| Router | CCIP | The Router program is the entry point for sending cross-chain messages from SVM-based blockchains (outbound only). It validates accounts, calculates fees via the Fee Quoter program, collects fees, manages outbound sequence numbers, and transfers tokens to Token Pools when sending tokens. |
| OffRamp | CCIP | Receives and processes cross-chain messages on the destination blockchain. It commits message batches (via merkle roots), executes individual messages, verifies RMN curses, and initiates token releases/minting through Token Pools. |
| Fee Quoter | CCIP | Computes gas and token fees for cross-chain messages and maintains updated price data for tokens and destination chain gas costs. |
| Token Pools | External (Token Developer) | Specialized programs that handle cross-chain token transfers. Used by the Router for `lock_or_burn_tokens` operations on the source blockchain and by the OffRamp for `release_or_mint_tokens` operations on the destination blockchain. |
| Token | External (Token Developer) | A token program |
| Receiver | External (User/Program) | A wallet or onchain program. If the receiver is a program implementing the `ccip_receive` function, it can handle data and/or tokens, but can only receive tokens to a PDA it has authority over. If it's a wallet, it can only receive tokens through its Associated Token Accounts (ATAs). |
| Risk Management Network (RMN) | CCIP | An onchain program that maintains a registry of "curses" (blocklisted chains). Router, OffRamp, and Token Pools all verify with this program that relevant chains are not cursed before processing messages. |
## Typical Lifecycle of a Message
### Source Blockchain (SVM)
When initiating a CCIP transaction from a SVM-based source chain.
1. **Preparation**
- The Sender prepares a CCIP Message on a SVM-based chain to a destination blockchain of choice. A CCIP message includes the following information:
- **Receiver**: A byte array representing the destination address (e.g., Ethereum account).
- **Data payload**: Arbitrary bytes to be delivered to the receiver.
- **Tokens and amounts** (if applicable)
- **Fee token**: A Solana `Pubkey` for the token used to pay fees or default `PubKey` for the native gas token.
- **Extra Arguments** (e.g., gas limit to use when calling the receiver on a EVM compatible blockchain).
- The Sender calls the Router `get_fee` to receive the total fees for paying CCIP. Internally, the Router calls the Fee Quoter program to determine the fees.
- If the fee token is not the native gas token (e.g., LINK):
- **For wallet senders**: The sender must include their Associated Token Account (ATA) as a writable account in the transaction and sign it, granting the Router one-time permission to transfer the fee tokens to the fee receiver ATA.
- **For program senders**: The sender program must call the SPL token `approve` instruction to authorize the Router's fee billing signer PDA to transfer tokens from the program's token account to the fee receiver ATA.
- **Note**: The fee receiver ATA is a token account owned by the Router's fee billing signer PDA that accumulates CCIP fees. There is a separate fee receiver ATA for each supported fee token.
- The Sender calls the Router `ccip_send`, with:
- The destination chain selector.
- The CCIP message (as detailed above).
- For token transfers: An ordered list of indexes that tell the Router which accounts in the transaction correspond to which tokens being transferred.
- All required accounts in the transaction context, including:
1. Router configuration accounts.
2. RMN accounts that are required for the curse verification.
3. Destination chain state accounts.
4. User nonce account for sequence tracking.
5. Fee token accounts.
6. For each token being transferred: token accounts, pool accounts, and token-specific configuration accounts.
- **Note**: SVM's architecture requires explicitly specifying all accounts a transaction will interact with. Developers should consult the [API reference](/ccip/api-reference/svm) for complete details on required accounts and recommended patterns for constructing `ccip_send` transactions.
2. **Sending**
- Verifications
- The Router verifies with the RMN program that the destination chain is not cursed and that the system-wide emergency (global curse) is not activated. If either check fails, the transaction reverts.
- The Router verifies that all token-related accounts in the transaction are legitimate, correctly structured, and properly connected to each other (e.g., user token accounts are proper ATAs, pool accounts are derived from the correct seeds, etc.).
- Fee Collection
- The Router makes a CPI call to the Fee Quoter program to calculate the exact fee amount based on message size, token transfers, and destination chain gas cost.
- For native gas fee token: The Router transfers native gas tokens from the sender's account to the fee receiver's wrapped native token ATA, then issues a synchronize instruction to update the token account balance.
- For non-native fee tokens: The Router transfers tokens from the sender's Associated Token Account (ATA) to the fee receiver's ATA using the Router's fee billing signer PDA as the authority for the transfer.
- Sequence Management
- The Router increments and records the destination chain's message sequence number.
- It updates the sender's nonce value, which is used to maintain message ordering within a specific lane (source + destination chain pair) for this sender.
- Token Handling (if applicable) for each token being transferred
- The Router transfers tokens from the user's ATA to the corresponding token pool ATA.
- The Router makes a CPI call to the token pool `lock_or_burn_tokens` function to lock or burn tokens, which also verifies token pool rate limits for the specific destination chain.
- A unique message ID is generated.
- The Router emits a `CCIPMessageSent` event with the full message details.
- The message is returned to the Sender.
3. **Initial Offchain Processing**
- The CCIP Commit DON monitors for the `CCIPMessageSent` event to process the message offchain. More details about the offchain processing are available here.
### Destination Blockchain (SVM)
When processing a CCIP transaction on a SVM-based destination chain:
1. **Commit Phase**
- The final OCR report from the Committing DON is recorded onchain in the OffRamp program via the `commit` function. This report contains information from a single source chain and includes a merkle root of messages along with optional token and gas price updates.
- The OffRamp first validates the report's signatures and consensus requirements, ensuring that sufficient CCIP nodes have signed and that sequence numbers are valid and in order.
- The OffRamp makes a CPI call to the RMN program, checking that the source chain is not cursed and that the system-wide emergency (global curse) is not activated.
- The OffRamp verifies and stores the merkle root, which represents a batch of messages from the source chain.
- If the report contains price updates, the OffRamp calls the Fee Quoter program to update token and gas price data.
- Upon successful processing, the OffRamp emits a `CommitReportAccepted` event containing the merkle root and any price updates, confirming that the messages are now committed and available for execution.
2. **Secondary Offchain Processing**
- The CCIP Executing DON monitors for the `CommitReportAccepted` event to identify commit reports with pending executions. Each message associated with these commit reports is processed individually. For each message ready for execution, the Executing DON computes its specific merkle proof against the committed merkle root. Each message is then submitted in a separate transaction to the SVM chain, with the message's merkle proof included in the Execute Plugin Report. The execution plugin implementation for SVM chains executes one message per transaction.
3. **Execution Phase**
- When the execution plugin submits a report, it calls the Offramp program's `execute` function, which verifies the merkle proof against the stored merkle root. **Note**: Unlike EVM implementations, the SVM Offramp verifies a single message execution at a time, with each execution report containing proof specifically for that message.
- The OffRamp performs several validations:
- Verifying the message sequence number is correct.
- Making a CPI call to the RMN program to verify that the source chain is not cursed.
- Checking that the message hasn't already been executed successfully.
- If the message includes token transfers, the OffRamp makes a CPI call to the token pool `release_or_mint_tokens` function to release or mint the corresponding tokens to the intended Receiver's ATA. This call also verifies token pool rate limits for the specific source chain.
- If the message contains arbitrary data, the OffRamp makes a CPI call to the Receiver program to deliver the CCIP message.
- Upon successful execution, the OffRamp emits an `ExecutionStateChanged` event containing the source chain selector, sequence number, message ID, message hash, and execution state (2 for `"SUCCESS"`).
- **Note**: In SVM transaction model, a failed execution transaction will revert all state changes. So if execution fails, the message remains in its original state rather than being marked as `"FAILED"`.
- If automated execution fails, the OffRamp program provides a `manually_execute` function that can be permissionlessly called after a configured time period has passed. This ensures messages can still be executed even if the primary execution path encounters issues. For more information, read the [manual execution](/ccip/concepts/manual-execution) page.
---
# Onchain Architecture - Components (SVM)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/svm/components
Last Updated: 2025-05-19
This section provides more detail on the Onchain components.
## Sender/Receiver
**CCIP supports the following as senders and receivers**:
- A user wallet.
- An onchain program.
**CCIP messages can be sent in any of the following combinations**:
- Wallet → Wallet (or an EOA if the destination is an EVM-based blockchain).
- Wallet → Program (or a smart contract if the destination is an EVM-based blockchain).
- Program → Wallet (or an EOA if the destination is an EVM-based blockchain).
- Program → Program (or a smart contract if the destination is an EVM-based blockchain).
**Depending on a dApp's architecture, a wallet may interact with the Router using**:
- A frontend or middleware component (e.g., a JavaScript program).
- A sender program.
**A CCIP Message can include**:
- An arbitrary bytes payload.
- A token transfer.
- A programmable token transfer (data + tokens).
**Sender Responsibilities**:
- Prepare a structured CCIP Message.
- Include all required accounts in the transaction context, due to the SVM account model. To learn more, read the `ccip_send` [API reference](/ccip/api-reference/svm/v1.6.0/router).
- Retrieve a fee estimate from the Router.
- Call the Router to send the message, as described in the Message Lifecycle section.
- Authorization Mechanisms:
- For fee payments:
- When paying with native gas token (e.g., SOL): No special authorization is needed as the Router will use a system transfer instruction.
- When paying with SPL tokens (e.g., wSOL, LINK):
- Wallet senders: Must include their fee token account (ATA) as a writable account and sign the transaction.
- Program senders: Must call the SPL token `approve` instruction to authorize the Router's fee billing signer PDA to transfer tokens.
- For token transfers (if applicable):
- Wallet senders: Must include their token accounts (ATAs) as writable accounts and sign the transaction.
- Program senders: Must call the SPL token `approve` instruction to authorize the Router's token pools signer PDA to transfer tokens.
**Receiver Considerations**:
- **Data Processing**: If the CCIP Message contains a bytes payload or a programmable token transfer, the receiver must be a program implementing the `ccip_receive` function with a specific discriminator. To learn more, read the `ccip_receive` [API reference](/ccip/api-reference/svm/v1.6.0/messages).
- **Security Validation**: The receiver program should validate that the caller is an authorized OffRamp by checking the `ALLOWED_OFFRAMP` PDA from the Router program.
**Additional Resources**:
- CCIP provides program examples for a Sender/Receiver in the [Programs folder](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs).
## Router
The Router program serves as the entry point for sending cross-chain messages from SVM-based blockchains.
1. **Key Functions**
- `get_fee`: Retrieves the CCIP fees for a given message by calling the Fee Quoter program.
- `ccip_send`: Sends a cross-chain message, emits a CCIPMessageSent event, and returns a unique message ID.
2. **Key Responsibilities**
- **Account Validation**:
- Verifies that all provided accounts are properly owned by expected programs.
- Checks that token accounts are valid Associated Token Accounts (ATAs).
- Ensures token pool accounts are derived from the correct seeds with proper authority.
- Validates that all required configuration accounts are included.
- **Sequence Management**:
- Maintains outbound sequence numbers for message ordering.
- Uses a PDA to store nonce values for each lane (sender-destination chain pair).
- Initializes the nonce PDA if needed (first-time senders pay the rent for this account).
- Updates the nonce value when sequential ordering is required.
- **Fee Collection**:
- Calculates the exact fee amount by making a CPI call to the Fee Quoter program.
- For native gas tokens: Directly transfers tokens from the Sender's account to the fee receiver wrapped native ATA, then issues a synchronize instruction.
- For non-native tokens: Transfers tokens from the sender's ATA to the fee receiver's ATA.
- **Token Handling (if applicable)**:
- Transfers tokens from the sender ATA to the corresponding token pool ATA.
- Calls the token pool `lock_or_burn_tokens` function, which also verifies token pool rate limits for the specified destination chain.
- **Security Verification**:
- Checks with the RMN program that chains are not cursed.
## OffRamp
The OffRamp program operates on the destination SVM chain and processes incoming cross-chain messages. It has two distinct phases: Commit and Execute.
1. **Key Functions**
- `commit`: Receives and verifies commit reports from the Committing DON containing merkle roots of message batches.
- `execute`: Processes individual messages with their merkle proofs against previously committed roots.
- `manually_execute`: Provides a fallback mechanism for permissionlessly executing messages after a waiting period. Read the manual execution page to learn more.
2. **Key Responsibilities**
1. **Commit Phase**
- Validates nodes' signatures from the Committing DON.
- Verifies with the RMN program that the source chain is not cursed.
- Stores the merkle root.
- Processes optional price updates by calling the Fee Quoter program.
- Emits a `CommitReportAccepted` event.
2. **Execute Phase**
- Processes one message per transaction.
- Verifies the merkle proof against the stored Merkle root.
- Sequence Management:
- Tracks and validates message sequence numbers to ensure proper ordering.
- For sequential messages (non-zero nonce), checks that the message's nonce matches the expected inbound nonce.
- If a nonce mismatch is detected, skips the message until the correct sequenced message is received.
- Updates the nonce PDA after successful execution to maintain sequence integrity.
- **Token Handling (if applicable)**, for each token:
- Verifies that token pool addresses are correct by checking with the Router's token admin registry PDA, which maps token mints to an Address Lookup Table (ALT) containing the approved token pool accounts.
- Records initial token balances of receiver ATAs.
- Makes CPI call to the corresponding token pool `release_or_mint_tokens` function to mint or release tokens to the receiver token account (ATA).
- Verifies that post-execution token balances match expected increases.
- **Data Delivery (if applicable)**:
- Verifies that the Receiver program ID matches what was specified in the original message by including the program ID in the merkle proof verification.
- Verifies that all accounts match exactly those specified in the original message on the source chain—any mismatch will cause execution to fail, potentially resulting in blocked funds.
- Creates an instruction with:
- The `ccip_receive` discriminator `[0x0b, 0xf4, 0x09, 0xf9, 0x2c, 0x53, 0x2f, 0xf5]`.
- The serialized `Any2SVMMessage` struct (containing the message ID, source chain selector, sender, data payload and token amounts). Read the API reference to learn more.
- Makes a CPI call to the receiver program using the accounts specified in the original message.
- **Completion**:
- Marks the message as “Success” in the commit report's state tracking.
- Emits an `ExecutionStateChanged` event with details, including the source chain selector, sequence number, message ID, message hash and the new state.
- **Note**: If any part of the execution process fails, the entire transaction will revert. This means no state changes are persisted, no tokens are transferred, and no events are emitted. The message will remain in the previous state, allowing for retry.
3. **Manual Execution (fallback mechanism)**
- Allows any user to permissionlessly execute messages through the `manually_execute` function after a waiting period set by the CCIP admin.
- This mechanism primarily addresses where automated execution fails due to:
- Temporary network congestion.
- Messages requiring more compute units than allocated in the transaction.
- Important points about compute units:
- Manual executors can allocate more computer units to their transactions.
- No modification of the message itself is needed: The same message and execution report are used.
- The `compute_units` field in the message specifies the compute units allocated to the Receiver's program `ccip_receive` execution. During manual execution, users can allocate more compute units to their overall transaction, potentially allowing messages that exceeded compute limits during DON execution to complete successfully.
- Manual execution follows exactly the same verification and execution process as the Execute Phase:
- Validate the source chain is not cursed via a CPI call to the RMN program.
- Validates the merkle proof against the committed root.
- Verifies accounts.
- Processes token transfers and data delivery.
- Updates message state and emits `ExecutionStateChanged` event.
- **Note**: This mechanism cannot fix fundamental issues with the CCIP message payload. If accounts are incorrectly specified in the source chain, even manual execution will fail and funds may remain locked.
## FeeQuoter
The FeeQuoter is a central component in Chainlink CCIP that maintains token and gas prices in USD, enforces rules around price freshness, and calculates all cross-chain fees.
1. **Key Functions**
- `get_fee`: Calculates the total fee required to send a CCIP message.
- `update_prices`: Updates token and gas prices based on data from commit reports.
2. **Key Responsibilities**
- **Fee Calculation**:
- Computes execution costs based on destination chain parameters.
- Converts costs to USD-denimated rates.
- The CCIP Billing page provides further details on how CCIP fees are calculated.
- **Price Management**:
- Maintains token and gas prices in USD.
- Enforces price freshness rules.
- Stores token-specific fee configurations.
## Tokens and Token Pools
### Tokens
- Tokens are developed by token developers and exist independently of the core CCIP programs.
- Most SPL tokens are compatible with CCIP. For more information on compatibility, read the CCT documentation.
### Token Pools
- Token Pools are deployed by token developers and exist independently of the core CCIP programs.
- Token Pools are programs that interact with SPL token programs.
- Token pools follow standard models (Lock/Release and Burn/Mint), with audited code available in the [CCIP repository](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs).
- For tokens requiring bespoke logic before burn/mint/lock/release, custom pools can be built on top of the base pools. More details are available in the CCT documentation.
## Risk Management Network
The Risk Management Network (RMN) adds an additional security layer to CCIP by performing offchain risk validation and maintaining an onchain "cursing" mechanism. The RMN program enables verification of cross-chain messages and can halt message transmission for specific blockchains or globally when security threats are detected.
### Blessing and Verification
- Offchain RMN nodes generate ECDSA signatures over merkle roots representing cross-chain message batches.
- CCIP programs (such as Router or OffRamp) make CPI calls to the `verify_not_cursed` function to check whether the relevant chain (source or destination) is cursed.
- The RMN program checks its `Curses` account to verify the subject isn't cursed before allowing the transaction to proceed.
### Cursing Mechanism
- The RMN program lets an owner curse any blockchain (by chain selector) or declare a global curse through the `curse` instruction that halts all CCIP traffic.
- Once cursed, any CCIP transaction that performs the verification check for that chain selector (or the global curse) will revert, preventing cross-chain message processing.
- The owner calls `uncurse` to lift the curse when the threat is resolved, restoring normal CCIP operations.
---
# Onchain Architecture - Upgradability (SVM)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/svm/upgradability
Last Updated: 2025-05-19
Chainlink's Cross-Chain Interoperability Protocol (CCIP) is designed to evolve in response to new security considerations, emerging feature requests, and the need to onboard additional blockchains. This evolution requires a secure, transparent upgrade process that preserves users' trust in CCIP while allowing for iterative improvements.
## What Can Be Upgraded
On SVM-based blockchains, upgradability primarily happens in two ways:
1. **Onchain Configuration**
Many CCIP programs (e.g., Router, OffRamp) offer public functions that allow for parameter updates without redeploying the entire program. Examples might include:
- Adding or removing supported blockchains.
- Enabling a new fee token.
Because these modifications only change onchain data, operational parameters can be adjusted without redeploying an entire program.
2. **Program Code Upgrades**
In SVM-based blockchains, programs are mutable by default (unless the upgrade authority is removed after deploying them). This allows the same program ID to point to new code if an upgrade is published. Developers typically use this mechanism to:
- Patch vulnerabilities or correct unforeseen implementation errors.
- Introduce new features or improved logic for cross-chain messaging.
Once the program code is upgraded, external references (such as PDAs or user accounts) are not broken because they rely on the original program ID.
**Special case:** Unlike other CCIP components, the OffRamp program follows a different upgrade pattern. Instead of upgrading in place, new OffRamp program instances are deployed when upgrades are needed. This approach ensures backward compatibility with in-flight messages that need to be processed by the correct OffRamp version.
## Implementation Process
All security-critical onchain configuration changes to CCIP on Solana pass through a secure upgrade process using the ManyChainMultiSig (MCMS) and Timelock programs.
Any proposal must follow one of two paths:
1. **Time-locked Review**: The proposal is submitted to the Timelock program and enters a mandatory review period. During this window, node operators securing CCIP can veto the proposal. If no veto occurs, the proposal becomes executable after the delay expires.
2. **Expedited Approval**: The proposal receives explicit approval from a quorum of independent signers, providing an alternative path for time-sensitive circumstances.
Any onchain update that passes the timelock review period without a veto becomes executable and can be implemented by accounts with the Executor role calling the `execute_batch` instruction.
The MCMS program's configuration and all scheduled operations are stored in public accounts that anyone can inspect for transparency and verification.
---
# CCIP Onchain Architecture (Aptos)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/aptos
Last Updated: 2025-09-03
Chainlink CCIP's Aptos onchain architecture consists of specialized Move modules deployed on both source and destination chains. These modules work together with CCIP's offchain infrastructure to provide end-to-end cross-chain interoperability.
- **[Overview](/ccip/concepts/architecture/onchain/aptos/overview)**: Provides a high-level introduction to CCIP's Aptos-based onchain architecture, including a component diagram, key roles, and a detailed walk-through of a message's lifecycle from source to destination chain.
- **[Components](/ccip/concepts/architecture/onchain/aptos/components)**: Describes each architectural component in detail.
- **[Upgradability](/ccip/concepts/architecture/onchain/aptos/upgradability)**: Explains CCIP's approach to secure system evolution.
---
# Onchain Architecture - Overview (Aptos)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/aptos/overview
Last Updated: 2025-09-03
## Aptos as Source Chain
On the Aptos source blockchain, a user or another module initiates a transaction by calling an entry function in the CCIP `Router` module. The `Router` module serves as the primary entry point, performing initial validations before forwarding the call to the appropriate `OnRamp` module. The `OnRamp` module then calculates fees, interacts with a designated Token Pool to lock or burn tokens, and finally emits a `CCIPMessageSent` event. This event is observed offchain by the Committing DON, which then securely relays the message to the destination blockchain.
## Aptos as Destination Chain
On the Aptos destination blockchain, an onchain module called the `OffRamp` receives a `commit` from the Committing DON. This `commit` contains Merkle roots of batched messages. The `OffRamp` module verifies the OCR signatures from the DON, checks the Risk Management Network (RMN) Remote module to ensure the source chain is not cursed, and then stores the verified Merkle root onchain. Subsequently, the Executing DON submits messages for execution. Aptos, like SVM, follows a one-message-per-transaction execution pattern, which is different from the batch execution possible on EVM chains. For each message, the `OffRamp` verifies its Merkle proof against a committed root, tracks the execution state by emitting an `ExecutionStateChanged` event, and processes the payload. This processing includes calling the appropriate Token Pool to release or mint tokens and, if the message contains data, calling the `ccip_receive` entry function on a designated receiver module.
## Key Components
**Source Chain**:
**Destination Chain**:
| Component | Ownership | Role |
| ---------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Sender/Receiver | External (User/Module) | An end-user wallet or a custom Move module that initiates a cross-chain message on the source chain and/or receives the message on the destination chain via the `ccip_receive` function. |
| Router | CCIP | The primary, user-facing entry point for sending outbound messages from Aptos. It validates the destination chain, determines the correct `OnRamp` version to use, and forwards the `ccip_send` call. |
| OnRamp | CCIP | A module that processes outbound messages on the source chain. It validates parameters, calculates fees by calling the FeeQuoter, manages token interactions with `Token Pools` via the TokenAdminDispatcher, increments sequence numbers, and emits a `CCIPMessageSent` event that offchain DONs observe. |
| Nonce Manager | CCIP | A module that tracks outbound nonces for messages on a per-destination-chain and per-sender basis, ensuring ordered message processing where required. |
| OffRamp | CCIP | A destination chain module that receives committed message roots from the offchain DON. It verifies these roots, checks the `RMN Remote` for curses, executes messages by verifying their Merkle proofs, and dispatches token/data payloads to their final destinations. It emits an `ExecutionStateChanged` event upon completion. |
| Fee Quoter | CCIP | A module that calculates the total fee required for a cross-chain message. It uses onchain price data for tokens and destination chain gas to provide an accurate cost estimate in the user's chosen fee_token. |
| Token Admin Dispatcher | CCIP | A dispatcher module that acts as a middleman between the `OnRamp`/`OffRamp` and the TokenAdminRegistry. It ensures that only authorized CCIP modules can initiate token lock, burn, release, or mint operations. |
| Token Admin Registry | CCIP | A module that acts as a central registry and maps a given token's address to its designated Token Pool module. It also manages administrative roles for each token, such as who can update its pool configuration. |
| Receiver Dispatcher | CCIP | A dispatcher module that ensures only the authorized `OffRamp` module can call the `ccip_receive` function on a registered receiver module. It uses the ReceiverRegistry to verify the destination. |
| Receiver Registry | CCIP | A module where custom Move modules can register themselves as valid CCIP message receivers. This registration is necessary for the `OffRamp` to be able to dispatch data and tokens to them. |
| Token Pools | External (Token Developer) | Specialized modules that handle cross-chain token transfers. Used by the `Token Admin Dispatcher` for dispatch_lock_or_burn operations on the source blockchain and dispatch_release_or_mint operations on the destination blockchain. |
| Token | External (Token Developer) | A fungible asset created on Aptos. |
| RMN Remote | CCIP | A module that maintains a list of "cursed" (i.e., blocklisted) chains. Various CCIP modules query this component to ensure they are not interacting with a cursed chain before processing a message. |
## Typical Lifecycle of a Message
### Source Blockchain (Aptos)
This outlines the process when initiating a CCIP transaction from the Aptos blockchain.
1. **Preparation**
- The Sender (a user's wallet or another module) prepares the information for a CCIP Message, including:
- **Receiver**: A byte array representing the destination address (e.g., a 20-byte EVM address).
- **Data payload**: Arbitrary bytes to be delivered to the receiver.
- **Tokens and amounts** (if applicable).
- **Fee token**: The address of the token for paying fees (e.g., native APT or LINK).
- **Extra Arguments**: An encoded byte vector containing destination-specific parameters, like a gas limit for EVM.
- The Sender calls the `ccip_router::router::get_fee` view function to determine the total CCIP fee required for the message. This function internally calls the `Fee Quoter` module for the cost calculation.
- The Sender prepares to call the `ccip_router::router::ccip_send` entry function. Unlike EVM and SVM chains, a separate token approve transaction is not required. The user's signature on the `ccip_send` transaction itself authorizes the CCIP modules to withdraw the necessary tokens and fees from the sender's account.
2. **Sending**
- When the `ccip_send` transaction is executed:
- The `Router` module, as the entry point, performs initial validations and forwards the call to the appropriate `OnRamp` module.
- The `OnRamp` module executes the core logic:
- Verifications: It ensures that the destination chain is not cursed by checking with the `RMN Remote` module.
- Fee Collection: It withdraws the required fee from the sender's fungible asset store. If the fee_token_store address is specified as 0x0, the module automatically resolves to the primary_fungible_store corresponding to the sender's account and the chosen fee_token.
- Sequence Management: It increments the sequence number for the given destination chain. If required, it also interacts with the `Nonce Manager` to handle ordered message nonces.
- Token Handling (if applicable): For each token in the message, the `OnRamp` withdraws the funds from the sender's token store and calls the `Token Admin Dispatcher`. This dispatcher uses the `Token Admin Registry` as a secure state machine:
- It first calls `start_lock_or_burn` on the registry to store the message context (sender, receiver, etc.).
- It then invokes the appropriate Token Pool's `lock_or_burn` function.
- The Token Pool module, in turn, calls back into the registry to securely `get_lock_or_burn_input` and, after processing, `set_lock_or_burn_output`. This secure callback pattern ensures token operations are only performed within a valid CCIP transaction context.
- Event Emission: A unique messageId is generated, and the `OnRamp` emits a `CCIPMessageSent` event containing the full, sequenced message details.
3. **Initial Offchain Processing**
- The CCIP Committing DON monitors the Aptos blockchain for the `CCIPMessageSent` event and begins processing the message offchain to prepare it for commitment on the destination chain.
### Destination Blockchain (Aptos)
This outlines the process when Aptos is the receiving chain for a CCIP message.
1. **Commit Phase**
- The final OCR report from the Committing DON, containing Merkle roots of batched messages and any price updates, is submitted to the `OffRamp` module's `commit` function.
- The `OffRamp` verifies the DON's signatures.
- If the report includes blessed Merkle roots, the `OffRamp` verifies the RMN signatures.
- If the report contains price updates, the `OffRamp` calls the `Fee Quoter` module to update its onchain token and gas price data.
- The `OffRamp` stores the verified Merkle root and emits a `CommitReportAccepted` event, confirming the messages are ready for execution.
2. **Secondary Offchain Processing**
- The CCIP Executing DON monitors for the `CommitReportAccepted` event. For each message in the committed batch, the DON computes its specific Merkle proof. Each message is then submitted in a separate transaction to the Aptos blockchain.
3. **Execution Phase**
- When the Executing DON calls the `OffRamp` module's `execute` function, the `OffRamp` first verifies the message's Merkle proof against a stored Merkle root. It performs several validations, including checking the `RMN Remote` module and ensuring the message has not already been executed.
- If the message includes tokens, the `OffRamp` calls the `Token Admin Dispatcher`. This dispatcher follows a secure callback pattern using the `Token Admin Registry`: it initiates a `release_or_mint` operation, which invokes the correct Token Pool. The pool then calls the registry to get its input data, mints or releases the tokens, and sets the output. The released tokens are then deposited into the receiver's primary fungible store.
- If the message contains arbitrary data, the `OffRamp` calls the `Receiver Dispatcher`. The dispatcher does not pass the message data directly. Instead, it securely stores the payload in the `Receiver Registry` and then triggers the `ccip_receive` entry function of the registered receiver module. The receiver module, in turn, must call receiver_registry::`get_receiver_input` within its own execution to securely fetch the message payload it is meant to process.
- The `OffRamp` emits a final `ExecutionStateChanged` event with the outcome. Due to the atomic nature of Aptos transactions, a successfully processed message will have a state of `SUCCESS` (2). If the transaction fails for any reason, it fully reverts, and the message remains `UNTOUCHED` (0), allowing for a later retry.
- The `OffRamp` module also provides a `manually_execute` function. If automated execution by the DON fails, this function can be permissionlessly called after a configured time delay to ensure the message can still be processed. For more information, read the [manual execution](/ccip/concepts/manual-execution) page.
---
# Onchain Architecture - Components (Aptos)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/aptos/components
Last Updated: 2025-09-03
This section provides more detail on the Onchain components for Aptos.
## Sender/Receiver
**CCIP supports the following as senders and receivers:**
- A user-controlled account – A wallet/user account controlled by a private key (also called EOA on EVM blockchains).
- An onchain program – Smart contract logic (called "programs" on Solana, "modules" on Aptos, "smart contracts" on EVM chains).
**CCIP messages can be sent in any of the following combinations (terminology adapts to the destination blockchain):**
- **User Account → User Account / EOA / Wallet**
- **Supported Message Type**: Token-only transfers.
- **Reason**: The recipient is a simple user account that does not have executable code. While a data payload can be sent in the CCIP message, the recipient account has no way to act upon or process that data.
- **User Account → Module / Smart Contract / Program**
- **Supported Message Type**: Token transfers, arbitrary data, and programmable token transfers (data + tokens).
- **Reason**: The receiving module is designed with a `ccip_receive` function that can be programmed to handle incoming tokens, process arbitrary data, or both simultaneously according to your application's logic.
- **Module / Smart Contract / Program → User Account / EOA / Wallet**
- **Supported Message Type**: Token-only transfers.
- **Reason**: Similar to the first case, the destination is a simple user account that cannot execute logic to process an incoming data payload.
- **Module / Smart Contract / Program → Module / Smart Contract / Program**
- **Supported Message Type**: Token transfers, arbitrary data, and programmable token transfers (data + tokens).
- **Reason**: Both the sender and receiver are programmable entities, allowing for any combination of token and data to be sent and processed according to custom application logic.
**A CCIP Message can include:**
- An arbitrary bytes payload.
- A token transfer.
- A programmable token transfer (data + tokens).
**Sender Responsibilities:**
- Prepare the arguments for the `ccip_send` function.
- Retrieve a fee estimate by calling the `router::get_fee` view function.
- Call the `router::ccip_send` entry function to send the message. The sender's signature on this transaction authorizes the withdrawal of any tokens and fees.
**Receiver Considerations:**
- **Data Processing:** If the CCIP Message contains a data payload or a programmable token transfer, the receiver must be a Move module that implements a `ccip_receive` entry function.
- **Registration:** The receiver module must be registered with the `Receiver Registry` to be able to receive messages from the CCIP OffRamp.
- **Security Validation:** The receiver module should validate that any call to its `ccip_receive` function originates from an authorized CCIP OffRamp address.
## Router
The `ccip_router::router` module serves as the single, user-facing interface for sending all outbound CCIP messages from the Aptos blockchain. As a minimal module, its primary role is to act as a stable entry point that directs traffic to the appropriate `OnRamp`.
The Router exposes two primary functions for the sender:
- `get_fee`: A view function that retrieves the CCIP fee for a given message by forwarding the request to the `OnRamp`.
- `ccip_send`: The entry function that initiates a cross-chain message. It performs initial validations and then forwards the call to the correct `OnRamp` module for processing.
## OnRamp
The `ccip_onramp::onramp` module is an internal CCIP module that handles the core logic for processing outbound messages on the source chain.
When the Router forwards a `ccip_send` request, the OnRamp performs the following actions:
- **Validations**
- Ensures that the destination chain is not cursed.
- Verifies that the sender is on the allowlist if one is enabled for the destination lane.
- **Fee Collection**
- Withdraws the pre-calculated fee amount from the sender's specified `fee_token_store`, defaulting to the user's primary store if `0x0` is provided.
- **Token Handling**
- If the message involves token transfers, it initiates a secure callback pattern by calling the `Token Admin Dispatcher`.
- The dispatcher uses the `Token Admin Registry` to store the message context (sender, receiver, etc.) before invoking the correct `Token Pool` to `lock_or_burn` the assets.
- **Nonce Management**
- For ordered messages, it calls the `Nonce Manager` to retrieve and increment the sender's nonce for that destination.
- **Event Emission**
- Generates a unique `messageId` and emits a `CCIPMessageSent` event containing the complete, sequenced message details.
## Nonce Manager
The `ccip::nonce_manager` module enables optional strict message ordering. It tracks outbound nonces on a per-sender and per-destination-chain basis.
When out-of-order execution is disabled for a message, the `OnRamp` module uses the `Nonce Manager` to assign an incrementing nonce, which is then verified by the `OffRamp` on the destination chain.
## OffRamp
The `ccip_offramp::offramp` module is an internal CCIP module that operates on the destination chain. It is the primary module that the offchain DONs interact with to deliver messages to Aptos.
### Commit Phase
During the **Commit Phase**, the following steps occur:
1. **Commit Report Submission**:\
The Committing DON calls the `commit` function on the `OffRamp` with a report that includes Merkle roots and price updates.
2. **Validation**:
- The `OffRamp` verifies the DON's signatures.
- It verifies any "blessed" Merkle roots.
3. **Price Updates**:\
It calls the `Fee Quoter` to update its onchain token and gas price data.
4. **Event Emission**:\
At the end of the Commit Phase, the `OffRamp` emits a `CommitReportAccepted` event.
### Execution Phase
In the **Execution Phase**, the `OffRamp` processes messages one by one:
1. **Merkle Proof Verification**:\
The `OffRamp` verifies the message's Merkle proof against a previously committed Merkle root.
2. **Additional Validations**:\
The `OffRamp` performs final validations, including ensuring the source chain is not cursed.
3. **Token Processing (if applicable)**:\
It calls the `Token Admin Dispatcher` to initiate the `release_or_mint` process. This follows a secure callback pattern where the dispatcher and `Token Pool` interact with the `Token Admin Registry` to securely process the token transfer before depositing the assets into the receiver's primary fungible store.
4. **Message Delivery (if applicable)**:\
It calls the `Receiver Dispatcher`, which securely stores the message payload in the `Receiver Registry` and then triggers the `ccip_receive` function on the registered receiver module. The receiver module is then responsible for calling the `Receiver Registry` to fetch this payload.
5. **Final Execution Status**:\
The `OffRamp` emits a final `ExecutionStateChanged` event, indicating a `SUCCESS` state. Due to Aptos's transaction atomicity, failed executions revert entirely, leaving the message state as `UNTOUCHED` for a potential retry.
### Permissionless Manual Execution (Fallback)
The `OffRamp` includes a `manually_execute` function. If automated execution fails, this function can be called permissionlessly after a configured time delay to ensure message delivery. For more information, read the [manual execution](/ccip/concepts/manual-execution) page.
## Fee Quoter
The `ccip::fee_quoter` module is responsible for all fee-related calculations.
- **Source Chain**: Called by the `OnRamp` to provide a precise fee estimate for a given message.
- **Destination Chain**: Called by the `OffRamp` to receive and store updated token and gas price data included in commit reports from the CCIP network.
## Token Admin Dispatcher
The `ccip::token_admin_dispatcher` is a security-focused module that acts as a secure entry point for all token operations. Only authorized CCIP modules (specifically, the `OnRamp` and `OffRamp`) are permitted to call this dispatcher. It then looks up the correct `Token Pool` via the `Token Admin Registry` and initiates the `lock_or_burn` or `release_or_mint` operations, preventing direct, unauthorized calls to the token pools.
## Token Admin Registry
The `ccip::token_admin_registry` is a central onchain module that maintains the critical mapping between a token's address and the address of its designated `Token Pool` module. It serves as the single source of truth for the CCIP network to determine how to handle a specific token for cross-chain transfers. It also manages administrative rights for token configurations.
## Receiver Dispatcher
The `ccip::receiver_dispatcher` is a security-focused module that ensures only the authorized `OffRamp` module can deliver a message to a registered receiver module. When a message containing data arrives, the `OffRamp` calls this dispatcher. The dispatcher first verifies that the destination module is registered in the `Receiver Registry` before safely calling the `ccip_receive` function on that end-user's module.
## Receiver Registry
The `ccip::receiver_registry` is a module where developers can register their custom Move modules to make them officially recognizable as valid CCIP message receivers. A module must be registered here before it can receive data or programmatic token transfers from the `OffRamp` via the `Receiver Dispatcher`.
## Tokens and Token Pools
### Tokens
- Tokens are developed by token developers and exist independently of the core CCIP modules.
- Most tokens built on the Aptos [Fungible Asset](https://aptos.dev/en/build/smart-contracts/fungible-asset) standard are compatible with CCIP. For more information on compatibility, refer to the CCT documentation.
### Token Pools
- Token Pools are deployed by token developers and exist independently of the core CCIP modules.
- Token Pools are modules interact with tokens created using the [Fungible Asset](https://aptos.dev/en/build/smart-contracts/fungible-asset) standard.
- Token pools follow standard models:
- **Lock/Release**
- **Burn/Mint**
- Audited code for `lock_release_token_pool` and `burn_mint_token_pool` modules is available in the CCIP repository.
- For tokens requiring custom logic before burn/mint/lock/release, developers may build custom pools on top of these base modules. More details are available in the CCT documentation.
## RMN (Risk Management Network) Remote
The `ccip::rmn_remote` module is a critical security component deployed on every CCIP-enabled chain. Various CCIP modules (like `OnRamp`, `OffRamp`, `Token Pools`) query this component to verify the status of other chains in the network. It maintains an onchain list of "cursed" (i.e., blocklisted) chains. If a source or destination chain is cursed, CCIP transactions involving that chain are halted.
---
# Onchain Architecture - Upgradability (Aptos)
Source: https://docs.chain.link/ccip/concepts/architecture/onchain/aptos/upgradability
Last Updated: 2025-09-03
Chainlink's Cross-Chain Interoperability Protocol (CCIP) is designed to evolve in response to new feature requests, security considerations, and the need to support additional blockchains over time. This requires a secure upgrade process that preserves CCIP's robust security while allowing for iterative improvements.
## What Can Be Upgraded
On the Aptos blockchain, upgradability primarily happens in two ways:
1. **Onchain Configuration**
Many CCIP modules (like `onramp`, `offramp`, `fee_quoter`) expose public entry functions that allow authorized accounts to adjust operational parameters. These functions modify onchain data stored in resources without requiring a new deployment.
Examples include:
- Enabling support for a new destination chain.
- Updating fee parameters.
2. **Module Code Upgrades**
In the CCIP deployment on Aptos, the core modules (`router`, `onramp`, `offramp`, `fee_quoter`, etc.) are grouped into packages and published under a single, unified Object.
Aptos allows for module code to be upgraded in-place. This means a code upgrade for the CCIP protocol involves publishing the new, updated module bytecode to the existing Object address.
Because the Object address remains unchanged:
- External modules and off-chain clients that interact with CCIP do not need to update their stored addresses.
- They seamlessly begin interacting with the new code after the upgrade is published.
This approach ensures that bug fixes and new features can be rolled out atomically and consistently while maintaining a stable on-chain address for the protocol.
## Implementation Process
All critical onchain configuration changes to CCIP on Aptos are governed by a secure, cross-chain process using the **ManyChainMultiSig (MCMS)** system, which functions similarly across all CCIP-supported chains.
The onchain mechanism for this on Aptos is the `mcms_entrypoint` function found within the core CCIP modules. This function is designed to be called by the `mcms_registry`, allowing the multi-chain governance process to execute proposals.
Any proposal must follow one of two paths:
1. **Time-locked Review**: The proposal is submitted onchain and enters a mandatory review period. During this window, node operators securing CCIP can inspect the proposed change and veto it if necessary. If no veto occurs, the proposal becomes executable after the delay expires.
2. **Expedited Approval**: For time-sensitive situations, a proposal can be passed via an expedited path if it receives explicit approval from a quorum of independent signers.
Once a proposal is approved through either path, it can be executed, and the `mcms_entrypoint` on the target Aptos module is called with the specified changes. This entire process is publicly verifiable, ensuring transparency for all onchain upgrades.
---
# Offchain Architecture
Source: https://docs.chain.link/ccip/concepts/architecture/offchain
Last Updated: 2025-05-19
This section describes the offchain components that support the CCIP network, ensuring security, reliability, and efficient cross-chain message processing.
- **[Overview](/ccip/concepts/architecture/offchain/overview)**: A high-level summary of the offchain systems, including Decentralized Oracle Networks (DONs), the Risk Management Network, and node capabilities.
- **[Risk Management Network](/ccip/concepts/architecture/offchain/risk-management-network)**: Understand how this network monitors cross-chain activity for potential risks.
---
# Offchain Architecture - Overview
Source: https://docs.chain.link/ccip/concepts/architecture/offchain/overview
Last Updated: 2025-05-19
CCIP's offchain architecture includes the following:
- **Decentralized Oracle Networks (DONs)**: Running offchain consensus using the Offchain Reporting Protocol (OCR).
- **Risk Management Network (RMN)**: Enhancing security through a defense-in-depth design.
- **Interaction Components**: Managing communication between the CCIP DONs and the RMN.
## Components
### CCIP Decentralized Oracle Networks
With the CCIP v1.6 architecture, there is a single DON called the Role DON that includes all participating nodes. Two OCR plugins run on these nodes:
1. **Commit OCR Plugin**
- Coordinates observations from multiple source chains.
- Requests blessings from the RMN (when applicable) and posts a Commit Report on the destination chain.
2. **Executing OCR Plugin**
- Monitors pending executions on the destination chain.
- Verifies events on the source chain and executes messages accordingly.
**Note**: For simplicity, we sometimes refer to the nodes running the Commit OCR Plugin as the Committing DON and those running the Executing OCR Plugin as the Executing DON. However, these are not separate oracle networks; they are subsets of the same Role DON, distinguished solely by their assigned roles.
### Risk Management Network
The Risk Management Network (RMN) is a unique architectural feature of CCIP that reinforces system security through a defense-in-depth approach. Key aspects include:
- **Separate Implementation**: RMN runs a minimal, separate implementation of the Chainlink node software, which uses a different programming language than the primary CCIP system. This design provides an extra layer of independent attestation, enhancing overall security.
- **Distinct Node Operators**: A dedicated set of node operators—separate from those managing the core CCIP DONs—participates in RMN functions.
- **Blessing Mechanism**: On blockchains where RMN blessing is enabled, RMN nodes "bless" CCIP messages by providing independent attestations of the source chain observation.
### Interaction
- **Commit OCR and RMN Blessings**: The Commit OCR plugin interacts directly with RMN nodes to query for observations and receive independent RMN blessings. These blessings serve as attestations of the validity of source chain observations and are used to form a consolidated set of merkle roots.
- **Configuration and Role Validation**: The roles of nodes are defined and managed on the "Home Chain" (i.e., Ethereum) through specific contracts. Each node software reads from these contracts to verify that nodes are authorized for their roles.
- The **CCIP Home** contract contains the configuration required to bootstrap the CCIP network (Role DON) and includes both chain-level and Role DON-level details.
- The **RMN Home** contract maintains the configuration for RMN nodes.
## Role DON: Diagram and Explanation
Let's take an example from the diagram above to understand the key components. The diagram shows a Commit OCR instance configured for a destination chain (Chain C). Key details include:
- **Commit Role DON**
- Represents all participating nodes.
- Within the Role DON, a specific group of nodes—sometimes called a "sub-committee"—may be assigned chain-specific roles.
- **Chain-Specific Roles** (as illustrated)
- **Green Rectangle**: Nodes observe Chain A (i.e., they read from Chain A).
- **Orange Rectangle**: Nodes observe Chain B.
- **Blue Rectangle**: Nodes write commit reports to Chain C.
- **Overlap**: Note that groups may overlap. For example, the four nodes in the bottom-left corner of the Role DON might not connect to Chain A or Chain C, demonstrating flexibility in node participation.
- **Interaction with RMN**: The Commit OCR interacts with RMN nodes to receive RMN blessings. These blessings act as independent attestations of the validity of source chain observations and are used to form a consolidated set of merkle roots.
- **Configuration and Role Validation**:
- Node roles are configured on the Home Chain (i.e., Ethereum) through specific contracts.
- The node software reads from the **CCIP Home** and **RMN Home** contracts to verify that each node is authorized for its role—whether for making observations or providing RMN blessings.
## High Level Flow
Below are the high-level steps for the Commit OCR process and the Executing OCR process.
### Commit OCR Process
- **Observation Phase**
- Each subcommittee reading from a source chain reaches consensus on the range of messages to build a merkle root.
- A minimum threshold of valid observations is required for consensus.
- **Report Building Phase**
- For source chains where RMN blessing is enabled, the leader of the OCR round queries the RMN nodes for the defined message ranges.
- RMN nodes return independently constructed merkle roots for these ranges.
- After validating the returned merkle roots, the leader sends the consolidated set of merkle roots to the RMN for signatures (a single signature may cover multiple merkle roots).
- **Query Phase**
- The leader shares the Commit Report (which may include RMN signatures) with the rest of the nodes in the Role DON for validation.
- Invalid observations are dropped and the remaining valid ones must meet the threshold to achieve consensus among the nodes.
- **Reporting Phase**
- A subcommittee of nodes writing to the destination chain submits the final Commit Report onchain.
- The report may include merkle roots from multiple sources—some of which have RMN signatures.
- Additionally, the Commit plugin posts price reports for fee tokens, so the Commit Report can contain a combination of merkle roots and price reports.
### Executing OCR Process
- **Pending Execution Check**
- The subcommittee connected to the destination chain checks for pending executions that have been committed via a Commit Report.
- **Validation and Optimization**:
- The DON coordinates to validate the corresponding source chain events for these pending executions.
- Once validated, the Executing DON optimizes the set of messages to be batched for execution. This optimization considers factors such as the gas limit and specific nuances of the destination chain.
- **Execution**:
- The message (or batch of messages) is executed on the destination chain.
---
# Risk Management Network
Source: https://docs.chain.link/ccip/concepts/architecture/offchain/risk-management-network
Last Updated: 2025-05-19
The **Risk Management Network (RMN)** is an independent network that continuously monitors and validates the behavior of CCIP, providing an additional layer of security by independently verifying cross-chain operations for anomalous activity. The RMN independently reconstructs the batches of cross-chain messages from source chains, then blesses valid messages and authorizes them for execution on destination chains. Its core job today is to bless committed cross-chain messages offchain, ensuring they match what was observed on the source blockchain.
The RMN utilizes a separate, minimal implementation of the Chainlink node software, creating a form of client diversity for increased robustness while also minimizing external dependencies to prevent supply chain attacks. Specifically, the RMN was written in a different programming language than the primary CCIP system, developed by a different internal team, and uses a distinct non-overlapping set of node operators compared to the CCIP DONs. The Risk Management Network is a wholly unique concept in cross-chain interoperability that builds upon established engineering principles (N-version programming).
## Process
The verification process of the RMN can be detailed as follows:
1. **Observing Merkle Roots**
- When the Committing DON posts a merkle root of new cross-chain messages on the destination blockchain, each RMN node independently fetches the underlying messages from the source blockchain.
- If the node's locally reconstructed merkle root matches the merkle root posted on the destination chain, it signs an RMN observation stating that the commit is valid.
2. **Assembling Offchain Reports**
- The Committing DON collects identical observations from a sufficient number of RMN nodes, and those nodes produce a final signed RMN report.
- This report consists of cryptographic signatures proving that most RMN nodes have confirmed the commit's integrity.
3. **Verification in Final Onchain Commit**
- The Committing DON attaches these RMN signatures to its final onchain commit.
- On EVM-based blockchains, the OffRamp contract checks the RMN signatures via the RMNRemote contract, ensuring the Executing DON can only finalize merkle roots with sufficient RMN blessings. A similar verification mechanism applies on non-EVM blockchains, tailored to their specific contract or program model.
## RMN Configuration
Although the RMN primarily acts offchain, it does rely on certain onchain configurations:
- **RMNHome**: Deployed on a home chain (e.g., Ethereum), this contract stores global RMN settings, such as node identities, chain selectors, and public keys.
- **RMNRemote**: Deployed on each destination blockchain (including the home chain), this contract verifies RMN node signatures whenever the Committing DON submits its final commit. Only commits carrying valid RMN blessings proceed to complete execution.
## Cursing
While the RMN focuses on blessings, there are ways to address anomalies:
1. **Monitoring and Alerting**: Suspicious activity is monitored, such as replayed messages or potential double-executions.
2. **Propose a Pause**: If needed, a "curse" transaction can be proposed to pause CCIP on a specific blockchain.
3. **Investigation and Resolution**: Once the root cause is addressed, the pause can be lifted.
---
# Cross-Chain Token Standard
Source: https://docs.chain.link/ccip/concepts/cross-chain-token
Last Updated: 2025-06-09
This section explains the Cross-Chain Token (CCT) standard, a Chainlink CCIP feature enabling secure and reliable cross-chain token transfers. Learn how to make your tokens compatible with CCIP.
- **[Overview](/ccip/concepts/cross-chain-token/overview)**: Get a high-level summary of the CCT standard and its benefits.
- **[EVM Tokens](/ccip/concepts/cross-chain-token/evm)**: Guidance for integrating tokens on EVM-compatible blockchains.
- **[SVM Tokens](/ccip/concepts/cross-chain-token/svm)**: Guidance for integrating tokens on SVM-based blockchains like Solana.
---
# Cross-Chain Token Standard - Overview
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/overview
Last Updated: 2025-05-19
The Cross-Chain Token (CCT) standard offers a streamlined and decentralized approach to enabling token transfers across blockchains using Chainlink's Cross-Chain Interoperability Protocol (CCIP). Traditionally, token developers had a friction-laden and manual process to enable their tokens for cross-chain operations. This process required manual deployment of token pools, making it time-consuming. With the introduction of CCTs, token developers now have the power to deploy, configure, and manage their own token pools in CCIP via a simple interface. This self-service model not only accelerates the deployment process but also empowers token developers with greater autonomy and control over their cross-chain token operations. This guide will explore the motivations behind Cross-Chain Token (CCT) and provide an overview of the architectural components in enabling a compatible token in CCIP.
## Motivations
The motivation for Cross-Chain Token (CCT) originates from two primary challenges: liquidity fragmentation in a multi-chain ecosystem and the need for greater autonomy for token developers.
### Liquidity Fragmentation
[Hundreds](https://chainlist.org/) of blockchains exist, each with their own rules, consensus mechanisms, and liquidity. This expansion has created fragmented liquidity, where assets are siloed on individual blockchains, making it difficult for users and developers to access liquidity across different ecosystems. Token developers face the dilemma of choosing which blockchain to deploy on, often deciding between the blockchain with the most liquidity or a new, emerging chain with future potential. Each decision comes with risks, such as competing in a crowded market or dealing with trust assumptions on new blockchains.
### Need for Greater Autonomy
Another critical motivation behind Cross-Chain Token is to empower projects to enable their tokens for cross-chain operations without the support of third-parties. By providing a self-service model, Cross-Chain Token allows projects to take control of their cross-chain applications. This enables rapid expansion to other blockchains supported by CCIP.
Cross-Chain Token (CCT) solves these challenges by allowing token developers to create assets that can move seamlessly across multiple blockchains without fragmenting liquidity. By deploying a token in multiple blockchain environments, token developers can maintain a consistent supply, manage liquidity efficiently, and reach users on different blockchains without introducing complex bridging processes. The CCT standard offers the following benefits:
#### Self-service and Permissionless Deployment
With CCT, token developers can launch a cross-chain token or enable an existing token in a self-service manner within minutes. The fully audited token pool contracts handle the complexities of burning/minting or locking/minting tokens across blockchains, all without requiring liquidity pools. This enables a streamlined and permissionless deployment process that significantly reduces the barriers to cross-chain expansion. Starting from v1.5.1, token pools support zero-downtime upgrades, allowing seamless transitions between pool versions while maintaining support for in-flight messages.
#### Developer Control and Flexibility
Token developers retain complete ownership of their token contracts, token pools, and implementation logic. This includes configuring rate limits across all blockchains, ensuring that token developers have full autonomy to manage their deployments. The CCT standard avoids vendor lock-in, hard-coded functions, and external dependencies, giving projects the flexibility they need to succeed.
#### Defense-in-Depth Security
The CCT standard leverages Chainlink's industry-standard oracle networks, which secure over $16 trillion in Total Value Enabled (TVE). This strong foundation is further enhanced by additional layers of protection, such as the Risk Management Network and configurable transfer rate limits, providing comprehensive security for cross-chain operations.
#### Programmable Token Transfers
Cross-Chain Token (CCT) supports the simultaneous transfer of tokens and messages in a single transaction. This programmability allows for complex use cases, such as executing on-chain actions alongside token transfers, improving the overall efficiency of cross-chain transactions. For instance, a programmable token transfer could execute a function to stake tokens on the destination blockchain.
#### No Liquidity Pools Required
The fully audited token pool contracts provided by CCT eliminate the need for liquidity pools, simplifying cross-chain token management. By locking or burning tokens on the source chain and minting or unlocking them on the destination chain, token developers can maintain consistent liquidity without managing fragmented liquidity pools across multiple blockchains.
#### Reduced Liquidity Fragmentation
The CCT standard addresses liquidity fragmentation by allowing tokens to move seamlessly across multiple blockchains rather than requiring liquidity to be present on both source and destination chains. This unified approach ensures that liquidity remains consistent and accessible across all supported blockchains, reducing inefficiencies and improving the user experience.
#### Zero-Slippage Transfers
Token developers are provided access to pre-audited token pool contracts that enable zero-slippage cross-chain transfers of CCTs. The exact amount of tokens sent to the CCIP OnRamp on the source chain is always the exact amount of tokens received on the CCIP OffRamp on the destination chain.
#### Ease of Cross-Chain Integration
With CCT, existing compatible tokens can be easily extended to support cross-chain functionality. This makes it possible for token developers to extend their token's reach across multiple blockchains with minimal effort, avoiding the complexities associated with traditional bridging solutions.
## Key Concepts
### Token Developer
The entity responsible for issuing and managing the token. Token developers decide which blockchains to deploy the token on, manage its lifecycle, and control its availability across different blockchains.
### Token Owner
The entity that owns the token contract. The token owner has the authority to manage the token contract, including upgrading the contract, changing the token administrator, or transferring ownership to another address.
### Token Administrator
A role assigned to manage cross-chain operations of a token. The token administrator is responsible for mapping a token to a token pool in the TokenAdminRegistry.. The token administrator can be the token owner or another designated entity assigned by the token owner. Technically, the token administrator can be an EOA or a smart account.
### EOA (Externally Owned Account)
A standard blockchain account controlled by a private key, typically used by individuals. EOAs do not have associated code, unlike smart accounts, and are used for directly signing transactions.
### Smart Account
A blockchain account represented by a smart contract, offering advanced functionality like multi-signature authorization or custom transaction logic. Smart accounts provide better security and flexibility compared to EOAs. For example, smart accounts can use multi-signature mechanisms to add an extra layer of security.
### Compatible Token
Cross-Chain tokens need to adhere to specific requirements to ensure interoperability across different blockchain ecosystems. Each blockchain family has its own requirements documentation. For example, you can read about [EVM token requirements](/ccip/concepts/cross-chain-token/evm/tokens) or visit the respective documentation pages for other blockchain families like SVM.
### Token Pool
Each token is associated with its own token pool, which acts as an abstraction layer over compatible tokens designed to facilitate token-related operations for OnRamping and OffRamping. Token pools provide rate limits, a security feature enabling token developers to set a maximum rate at which their token can be transferred per lane. Token pools are configured to `lock` or `burn` tokens on the source blockchain and `unlock` or `mint` tokens on the destination blockchain.
## Token Pool rate limits
Token administrators may set-up token pool rate limits. A token pool rate limit (TPRL) has a maximum capacity and a refill rate, which is the speed at which the maximum capacity is restored after a token transfer has consumed some or all of the available capacity.
You can find the complete list of CCIP supported tokens and their rate limits on the [CCIP Directory page](/ccip/directory).
## Token Handling Mechanisms
The setup of token pools on source and destination blockchain results in four primary token handling mechanisms:
### Burn and Mint
Tokens are burned on the source blockchain, and an equivalent amount of tokens are minted on the destination blockchain. This keeps the total supply of the token constant across blockchains.
### Lock and Mint
This mechanism is suitable for projects with existing tokens on a single blockchain that have already been minted and lack functionality to control the supply via burn and mint functions on the source blockchain. In this approach, tokens are locked on the issuing blockchain, and fully collateralized "wrapped" tokens are minted on the destination blockchain. These wrapped tokens, which must support burn and mint functionality, can subsequently be transferred across other non-issuing blockchains using the Burn and Mint mechanism.
### Burn and Unlock
Tokens are burned on the source blockchain (which is the non-issuing blockchain), and an equivalent amount of tokens are released on the destination blockchain (the issuing blockchain). This mechanism is the inverse of the Lock and Mint mechanism and applies when you send tokens back to their issuing source blockchain.
### Lock and Unlock
Tokens are locked on the source blockchain, and an equivalent amount of tokens are released on the destination blockchain. For additional guidance on Lock and Unlock configuration, please review best practices.
### Token-Specific Transfer Mechanisms
The mechanism for handling tokens varies depending on the characteristics of each token. Below are several examples to illustrate this:
- LINK Token is minted on a single blockchain (Ethereum mainnet) and has a fixed total supply. Consequently, CCIP cannot natively mint it on another blockchain. For LINK, the token pool is configured to lock tokens on Ethereum mainnet (the issuing blockchain) and mint them on the destination blockchain. Conversely, when transferring from a non-issuing blockchain to Ethereum mainnet, the LINK token pool is set to burn the tokens on the source (non-issuing) blockchain and unlock them on Ethereum Mainnet (issuing). For example, transferring 10 LINK from Ethereum mainnet to Base mainnet involves the LINK token pool locking 10 LINK on Ethereum mainnet and minting 10 LINK on Base mainnet. Conversely, transferring 10 LINK from Base mainnet to Ethereum mainnet involves the LINK token pool burning 10 LINK on Base mainnet and unlocking 10 LINK on Ethereum mainnet.
- Wrapped native Assets (e.g., WETH) use a Lock and Unlock mechanism. For instance, when transferring 10 WETH from Ethereum mainnet to Optimism mainnet, the WETH token pool will lock 10 WETH on Ethereum mainnet and unlock 10 WETH on Optimism mainnet. Conversely, transferring from Optimism mainnet back to Ethereum mainnet involves the WETH token pool locking 10 WETH on Optimism mainnet and unlocking 10 WETH on the Ethereum mainnet.
- Stablecoins (e.g., USDC) can be minted natively on multiple blockchains. Their respective token pools employ a Burn and Mint mechanism, burning the token on the source blockchain and then minting it natively on the destination blockchain.
- Tokens with a Proof Of Reserve (PoR) with a PoR feed on a specific blockchain present a challenge for the Burn and Mint mechanism when applied across other blockchains due to conflicts with the PoR feed. For such tokens, the Lock and Mint approach is preferred.
---
# Cross-Chain Token Standard (EVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/evm
Last Updated: 2025-05-19
This section provides detailed guidance for integrating tokens with Chainlink CCIP on EVM-compatible blockchains.
- **[Architecture](/ccip/concepts/cross-chain-token/evm/architecture)**: Understand the specific architecture for CCTs on EVM chains.
- **[Tokens](/ccip/concepts/cross-chain-token/evm/tokens)**: Learn about the requirements and compatibility for your tokens.
- **[Token Pools](/ccip/concepts/cross-chain-token/evm/token-pools)**: Explore the different types of token pools and how to use them.
- **[Registration and Administration](/ccip/concepts/cross-chain-token/evm/registration-administration)**: Details on registering your token with CCIP and managing administrative roles.
- **[Upgradability](/ccip/concepts/cross-chain-token/evm/upgradability)**: Information on upgrading token pool contracts.
---
# Cross-Chain Token Standard - Tokens (EVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/evm/tokens
Last Updated: 2025-05-19
Before enabling an ERC20-compatible token in CCIP, it's important to understand the requirements it must fulfill to integrate with CCIP.
## Registration functions
A token administrator can use any of the following supported function signatures for enabling their tokens in CCIP:
- `owner()`: This function returns the token contract owner's address.
- `getCCIPAdmin()`: This function returns the token administrator's address and is recommended for new tokens, as it allows for abstraction of the CCIP Token Administrator role from other common roles, like `owner()`.
## Transfer functions
The token's smart contract must meet minimum requirements to integrate with CCIP.
### BurnMint Requirements
This section describes the requirements for tokens that are used with the BurnMint token pools.
- The token smart contract must have the following functions:
- `mint(address account, uint256 amount)`: This function is used to mint the `amount` of tokens to a given `account` on the destination blockchain.
- `burn(uint256 amount)`: This function is used to burn the `amount` of tokens on the source blockchain.
- `decimals()`: Returns the token's number of decimals.
- `balanceOf(address account)`: Returns the current token balance of the specified `account`.
- `burnFrom(address account, uint256 amount)`: This function burns a specified number of tokens from the provided account on the source blockchain. Note: This is an optional function. We generally recommend using the `burn` function, but if you use a tokenPool that calls `burnFrom`, your token contract will need to implement this function.
- The token contract must support granting mint and burn permissions. The token developers or another role (such as the token administrator) will grant these permissions to the token pool.
### LockRelease Requirements
This section describes the requirements for tokens that are used with the LockRelease token pools.
- The token smart contract must have the following functions:
- `decimals()`: Returns the token's number of decimals.
- `balanceOf(address account)`: Returns the current token balance of the specified `account`.
- On the destination blockchain, the token contract must support granting mint and burn permissions. The token developers or another role (such as the token administrator) will grant these permissions to the token pool.
## Token Handling Mechanisms
To facilitate cross-chain token transfers, you need to choose the appropriate token handling mechanism and deploy the correct combination of token pools for the source and destination blockchains. The table below summarizes the different token handling mechanisms and the [recommended token pools](https://github.com/smartcontractkit/chainlink-ccip/tree/release/contracts-ccip-1.6.2/chains/evm/contracts/pools) to deploy for each scenario, ensuring a seamless token transfer process.
| Token Handling Mechanism | Source Blockchain Token Pool | Destination Blockchain Token Pool | Notes |
| ------------------------ | ---------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Burn and Mint | BurnMintTokenPool | BurnMintTokenPool | - Standard burn and mint mechanism for cross-chain token transfers. |
| Lock and Mint | LockReleaseTokenPool | BurnMintTokenPool | - The source blockchain is the issuing blockchain. - LockReleaseTokenPool must be deployed on the issuing blockchain. |
| Burn and Unlock | BurnMintTokenPool | LockReleaseTokenPool | - The destination blockchain is the issuing blockchain. - BurnMintTokenPool is used to burn tokens on the source blockchain, and LockReleaseTokenPool is used to unlock tokens on the issuing blockchain. |
| Lock and Unlock | LockReleaseTokenPool | LockReleaseTokenPool | - Tokens are locked on the source blockchain and unlocked on the destination blockchain. - Not recommended due to fragmented liquidity and requires careful management of liquidity across multiple blockchains. |
---
# Cross-Chain Token Standard - Token Pools (EVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/evm/token-pools
Last Updated: 2025-05-19
Learn about standard and custom token pool contracts for facilitating CCT transfers on EVM chains. This page covers common requirements like mandatory functions, gas limit considerations, and token decimal handling.
## Common Requirements
All token pools, whether standard or custom, must adhere to the following guidelines:
### Function Requirements
When CCIP interacts with your token pools, it expects the presence of the following functions:
1. **Sending Tokens (Source)**:
- This must include the following function: `lockOrBurn(Pool.LockOrBurnInV1 calldata lockOrBurnIn) external returns (Pool.LockOrBurnOutV1 memory)`.
- This function locks or burns tokens depending on the implementation of the token pool.
- See implementation details in [`TokenPool.lockOrBurn`](/ccip/api-reference/evm/v1.6.2/token-pool#lockorburn).
2. **Receiving Tokens (Destination)**:
- This must include the following function: `releaseOrMint(Pool.ReleaseOrMintInV1 calldata releaseOrMintIn) external returns (Pool.ReleaseOrMintOutV1 memory)`.
- This function releases or mints tokens depending on the implementation of the token pool.
- See implementation details in [`TokenPool.releaseOrMint`](/ccip/api-reference/evm/v1.6.2/token-pool#releaseormint).
### Gas Requirements
On the destination blockchain, the CCIP OffRamp contract performs three key calls:
1. **`balanceOf` before minting/releasing tokens**: To check the token balance of the receiver before the minting or releasing operation.
2. **`releaseOrMint` to mint or release tokens**: To execute the minting or releasing of tokens on the destination blockchain.
3. **`balanceOf` after minting/releasing tokens**: To verify the token balance of the receiver after the operation is complete.
#### Default Gas Limits
- The combined execution of these three calls should not exceed the default gas limit of 90,000 gas.
- To verify this cost, it is recommended to perform a token transfer on testnet.
#### Handling Execution Failures
- If the default gas limit of **90,000 gas** is exceeded and a custom limit has not been configured, the CCIP execution on the destination blockchain will fail.
- In such cases, manual intervention by the user will be required to execute the transaction. For more information, see the [manual execution page](/ccip/concepts/manual-execution). - The resulting transaction can be inspected for the amount of gas that was used.
#### Requesting Gas Limit Adjustments
- If the combined execution requires consistently more than **90,000 gas** on the destination blockchain, you should [contact Chainlink Labs](https://chain.link/ccip-contact?v=Tokens:%20Gas%20limit%20update) to update an internal CCIP parameter to avoid execution failure.
- It is highly recommended to design your token pool to stay within the 90,000 gas limit whenever possible to ensure you enabled your tokens in CCIP without Chainlink Labs intervention.
### Token Decimal Handling
#### v1.5.0
Token pools on **v1.5.0** do not support tokens with different decimal places across blockchains. When transferring a token between blockchains with differing decimal places, the token loses precision resulting in different amounts of tokens between the source and destination blockchain.
Consider a token developer who deployed their token across two blockchains with different decimal configurations:
- Blockchain A: Token with 12 decimals
- Blockchain B: Token with 6 decimals
| Transfer Path | Example | Explanation | Impact |
| -------------- | --------------------------------------------------------------------- | --------------------- | ------------------------------ |
| A → B (12 → 6) | • Send 1.0 from A (= 10^12 base units) • Receive on B: 1,000,000 | 10^12/10^6= 1,000,000 | Gain of 999,999 (1,000,000 -1) |
| B → A (6 → 12) | • Send 1.0 from B (= 10^6 base units) • Receive on A: 0.000001 | 10^6/10^12= 0.000001 | Loss of 0.999999 (1-0.000001) |
We highly recommend upgrading to v1.5.1 to leverage the native support for token decimal handling. If using v1.5.0, please make sure to configure a custom v1.5.0 token pool.
#### V1.5.1 and Higher
Starting **from v1.5.1**, token pools support tokens with different decimal places across blockchains. This feature can impact the total number of tokens in circulation because tokens locked/burned on the source chain might result in a smaller number of tokens minted/released on the destination chain due to decimal rounding.
**Understanding Token Decimals**
When deploying their token, token developers can configure different decimal places for each blockchain. For example:
- On Ethereum: The developer sets 18 decimals (0.123456789123456789)
- On Polygon: The developer sets 9 decimals (0.123456789)
When transferring tokens between these blockchains, CCIP handles decimal conversion automatically but must round numbers to match the destination's configured precision.
**Impact Scenario**
Consider a token developer who deployed their token across three blockchains with different decimal configurations:
- Blockchain A: High precision (18 decimals)
- Blockchain B: Low precision (9 decimals)
- Blockchain C: High precision (18 decimals)
| Scenario | Transfer Path | Example | Impact |
| --------------------------- | ------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| **High to Low Precision ❌** | A → B | • Send from A: 1.123456789123456789 • Receive on B: 1.123456789 | Lost: 0.000000000123456789 • Burn/mint: Tokens permanently burned on Blockchain A • Lock/release: Tokens locked in pool on Blockchain A |
| **Low to High Precision ✅** | B → A | • Send from B: 1.123456789 • Receive on A: 1.123456789000000000 | • No precision loss |
| **Equal Precision ✅** | A → C | • Send from A: 1.123456789123456789 • Receive on C: 1.123456789123456789 | • No precision loss |
**Best Practices**
- Deploy tokens with the same number of decimals across all blockchains whenever possible
- This prevents any loss of precision during cross-chain transfers
- Different decimals should only be used when required by blockchain limitations (e.g., non-EVM chains with decimal constraints)
- Verify decimal configurations on both source and destination blockchains before transfers
- Consider implementing UI warnings for transfers that might be affected by rounding
- When using high-to-low precision transfers, be aware that:
- In burn/mint pools: Lost precision results in permanently burned tokens
- In lock/release pools: Lost precision results in tokens accumulating in the source pool
## Standard Token Pools
Depending on your use case (token handling mechanism), you need to deploy the appropriate token pool type for each blockchain you want to support. Chainlink provides a set of token pool contracts that you can use to deploy your token pools in minutes. These token pools are fully audited and ready for deployment on your blockchains. You can find the token pool contracts in the [Chainlink GitHub repository](https://github.com/smartcontractkit/chainlink-ccip/tree/release/contracts-ccip-1.6.2/chains/evm/contracts/pools). For most use cases, you should use either:
- **[BurnMintTokenPool](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/pools/BurnMintTokenPool.sol)**: This token pool is used to burn or mint tokens. You can read the API reference here.
- **[BurnFromMintTokenPool](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/pools/BurnFromMintTokenPool.sol)**: This is a variant of the BurnMintTokenPool that uses the `burnFrom(from, amount)` function to burn tokens from a specified account. You can read the API reference here. **Note**: If your token supports the standard `burn` function, you should typically use the BurnMintTokenPool instead of BurnFromMintTokenPool.
- **[LockReleaseTokenPool](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/pools/LockReleaseTokenPool.sol)**: This token pool is used to lock or release tokens. You can read the API reference here.
**Note**: Both token pools inherit from the same base [TokenPool](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/pools/TokenPool.sol) contract, which provides all the common functions necessary for a token pool. For example, it includes the `applyChainUpdates` function, which is used to configure the token pool. You can read the API reference here.
## Custom Token Pools
### Guidelines for Custom Token Pools
If the standard token pools do not meet your requirements, you have the option to build a custom TokenPool. However, it is essential to adhere to the following guidelines:
- Your custom token pool must inherit from the appropriate base token pool contract depending on your token handling mechanism:
- **Burn and Mint**: Your custom token pool should inherit from `BurnMintTokenPoolAbstract`. Use this base contract if your custom pool involves burning tokens on the source chain and minting them on the destination chain.
- **Lock and Release**: Your custom token pool can either inherit from `TokenPool` and implement the `ILiquidityContainer` interface, or directly inherit from `LockReleaseTokenPool` and reimplement `lockOrBurn` and `releaseOrMint` functions as needed. This setup is appropriate when your pool involves locking tokens on the source blockchain and releasing them on the destination blockchain.
- Your custom TokenPool must implement the mandatory functions for both the source and destination blockchains. (Refer to the [Common Requirements](#common-requirements) section for more details.)
### Use Cases for Custom Token Pools
Here are some examples of use cases that may require custom token pools:
#### Tokens with rebasing or fee-on-transfer mechanisms
##### Use Case
Rebasing tokens are a unique type of token that adjusts its supply in response to specific parameters or events (e.g., price or transfer tax). These tokens require custom logic to handle rebasing events during cross-chain transfers.
##### Solution
- **Source Blockchain**: When initiating a cross-chain transfer of rebasing tokens, the TokenPool on the source blockchain should lock or burn the underlying shares rather than a fixed amount of tokens. This ensures that the value is consistently represented, regardless of changes in supply. The number of shares being locked or burned is recorded in the `destPoolData`and passed to the destination blockchain via CCIP.
- **Destination Blockchain**: On the destination blockchain, the TokenPool should accurately convert the number of underlying shares to tokens using the current share-to-token ratio. The calculated token amount should then be minted on the destination blockchain and returned in the `destinationAmount` field of the `ReleaseOrMintOutV1` struct, which is returned by the `releaseOrMint` function.
#### Tokens with different decimals across blockchains
For **v1.5.0** pools:
##### Use Case
Some tokens have different decimal values across various blockchains.
##### Solution
- **Source Blockchain**: During the lock or burn process on the source blockchain, the TokenPool should include a shared denomination in the `destPoolData` field of the `LockOrBurnOutV1` struct (returned by the `lockOrBurn` function) to represent the value in a standard format. This data is then passed to the destination blockchain via CCIP.
- **Destination Blockchain**: On the destination blockchain, the TokenPool should use the information contained in the `sourcePoolData` of the `ReleaseOrMintInV1` struct (used by the `releaseOrMint` function) to convert the value into the local denomination. The correct number of tokens should then be minted based on the destination blockchain's decimal format. The minted amount is returned in the `destinationAmount` field of the `ReleaseOrMintOutV1` struct, which is returned by the `releaseOrMint` function.
---
# Cross-Chain Token Standard - Architecture (EVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/evm/architecture
Last Updated: 2025-05-19
The Cross-Chain Token (CCT) architecture offers a streamlined, self-service approach to enabling cross-chain operations. This system integrates tightly with Chainlink's Cross-Chain Interoperability Protocol (CCIP), allowing token developers to configure, manage, and transfer tokens across multiple blockchains without requiring the support of third parties.
The detailed architecture diagram below provides a comprehensive view of how CCT fits within the CCIP ecosystem, illustrating the interaction between key components such as token contracts, token pools, and registry modules. While you can explore the full details of these components in the CCIP Architecture document, the key takeaway for Cross-Chain Token (CCT) is understanding how the `TokenAdminRegistry` contract is used.
The **TokenAdminRegistry** contract is essential when transferring tokens across blockchains. CCIP's `onRamp` and `offRamp` contracts interact with it to fetch the token pool associated with a given token. For cross-chain transfers to work, token administrators need a way to set or configure the token pool linked to a token.
In the following sections, we will explore how token administrators can register their tokens in the TokenAdminRegistry, link them to the relevant token pools, and configure them for use in CCIP.
In the Cross-Chain Token (CCT) standard, several key contracts work together to facilitate the secure transfer and management of tokens across multiple blockchains. These contracts can be categorized into three main groups:
## Registry
The Registry contracts manage the registration tokens within the CCT system. They ensure that the correct entities have control over cross-chain operations.
- `TokenAdminRegistry`: This contract stores the token administrators and pools for all registered cross-chain tokens. It allows tokens to be registered in a self-service manner and handles administrator role changes via a two-step process (transfer request and acceptance).
- `RegistryModuleOwnerCustom`: This contract facilitates the registration of token administrators. It works with the TokenAdminRegistry to ensure that only authorized administrators are assigned to manage cross-chain operations.
## Token
The Token contract represents the actual token being managed and transferred across blockchains. This contract must be ERC20-compatible and have additional functionalities depending on the cross-chain handling mechanism used. For more details on the requirements for ERC20-compatible tokens, refer to the Tokens page.
## Token Pool
The Token Pool contract is responsible for executing the cross-chain token transfers. It manages how tokens are locked, burned, minted, or unlocked across blockchains.
Each blockchain has its own token pool that interacts with the token contract. Depending on the token handling mechanism (e.g., Burn & Mint or Lock & Mint), different token pool contracts will be deployed. For example:
- `BurnMintTokenPool`: Handles the burning, or minting of tokens depending whether it is the source or destination blockchain.
- `LockReleaseTokenPool`: Handles the locking or releasing of tokens depending on whether it is the source or destination blockchain.
For more information on the token pool contracts and their functionalities, refer to the [Token Pools](/ccip/concepts/cross-chain-token/evm/token-pools) page.
---
# Cross-Chain Token Standard - Registration & Administration (EVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/evm/registration-administration
Last Updated: 2025-05-19
The process of registering your token for **Cross-Chain Token (CCT)** involves a **two-step process** to ensure that the correct administrator is assigned. The token's administrator will initially be placed in a proposed state. The administrator must then explicitly accept the role to complete the registration.
## Self-Service Registration Flow
If the token contract includes any of the necessary functions (`getCCIPAdmin()`, or `owner()`), the registration can be done autonomously by the token administrator:
1. Admin Initiates Registration: The token administrator begins the process by calling one of these functions on the `RegistryModuleOwnerCustom` contract:
- `registerAdminViaGetCCIPAdmin()`
- `registerAdminViaOwner()`
2. Determine Administrator: The RegistryModuleOwnerCustom contract retrieves the administrator from the token contract using the appropriate method:
- Via `getCCIPAdmin()`
- Via `owner()`
3. Propose Administrator: The retrieved administrator is then proposed to the `TokenAdminRegistry` using the `proposeAdministrator()` function, placing the administrator in a pending state.
4. Pending Administrator: At this point, the administrator is marked as "pending" in the `TokenAdminRegistry` and must complete the second step—**accepting the role**—before being officially assigned.
## Non-Self-Service Registration Flow
If the token contract does not have the necessary functions (`getCCIPAdmin()` or `owner()`), the token developer must manually initiate the registration by submitting a request here.
## Interacting with the Registry
### Proposing the Administrator
The following sequence diagram illustrates the process of proposing the administrator for both self-service
registration flow:
### Accepting the Administrator Role
Once the administrator has been proposed and is in a pending state, they must accept the role to complete the registration process. This step finalizes the assignment of the administrator.
1. **Administrator Accepts Role**: The pending administrator must explicitly call the `acceptAdminRole()` function on the `TokenAdminRegistry` to complete the registration.
2. **Finalize Registration**: Once the administrator accepts the role, they are assigned as the token administrator, and the registration process is complete. At this point, the token administrator can set a token pool for the token in the `TokenAdminRegistry`.
Once the administrator has been proposed and is in a pending state, the final step in the registration process is for the pending administrator to accept the role. This sequence diagram illustrates how the pending administrator interacts with the `TokenAdminRegistry` to complete the registration. It also covers the scenario where an incorrect entity attempts to accept the role, leading to an error.
### Transfer Administrator Role
The `transferAdminRole` function allows the current token administrator to initiate the transfer of their role to a new administrator. The transfer process is a secure two-step procedure, requiring the new administrator to explicitly accept the role before the transfer is finalized.
1. **Initiate Role Transfer**: The current administrator calls the `transferAdminRole()` function on the `TokenAdminRegistry`, specifying the token address and the new administrator's address.
2. **Set Pending Administrator**: The `TokenAdminRegistry` contract verifies that the caller is the current administrator of the token and sets the new administrator as pending. The role will remain in this pending state until it is accepted.
3. **Accept the Role**: The new administrator must call the `acceptAdminRole()` function to finalize the transfer and assume the administrator role.
The following sequence diagram illustrates the process of transferring the administrator role and how the new administrator must accept the role to complete the transfer.
### Setting the Token Pool
The `setPool` function allows the token administrator to assign or update the token pool for a specific token in the **TokenAdminRegistry**.
1. **Set Token Pool**: The current administrator calls the `setPool()` function on the `TokenAdminRegistry`, providing the token address and the new pool address.
2. **Validate Pool**: If the new pool address is not `address(0)`, the contract validates that the provided pool supports the token by calling `isSupportedToken()` on the pool contract.
3. **Update or Remove Pool**: If validation succeeds, the token's pool is updated in the registry. Setting the pool to `address(0)` effectively delists the token from cross-chain operations.
The sequence diagram below shows how the token administrator sets or updates the pool for a token. If the pool is set to `address(0)`, the token is delisted from cross-chain operations.
## Configuring the Token Pool
The configuration of token pools includes adding new blockchains, setting remote pool addresses, and applying rate limits for cross-chain transfers. The following functions from the `TokenPool` contract are used for configuring token pools:
### `applyChainUpdates`
- **Purpose**: This function is the primary method for configuring which blockchains the token pool supports and defining rate limits for cross-chain transfers.
- **Details**:
- It allows the token pool owner to add new chains or remove existing ones.
- Configures the pool and token addresses for remote blockchains.
- Sets rate limits for both outbound and inbound transfers.
- **Usage**:
- To add a new blockchain, the pool owner provides the remote chain selector, pool address, token address, and rate limiter configurations.
- To remove a blockchain, the `allowed` flag is set to `false`, and the chain is removed from the list of supported chains.
### `addRemotePool`
- **Purpose**: Adds a new remote pool address for a specific blockchain, enabling support for multiple pools per chain.
- **Details**:
- Allows adding multiple pools for a single chain selector, which is crucial during pool upgrades
- Maintains support for in-flight messages from existing pools while adding new ones
- Validates chain selector support and prevents duplicate pool additions
- Each pool address is hashed and stored for efficient lookup
- **Usage**: This function is particularly useful during pool upgrades, allowing seamless transitions between pool versions while maintaining transaction support.
### `setChainRateLimiterConfig`
- **Purpose**: Configures the rate limits for outbound and inbound token transfers between blockchains.
- **Details**:
- The outbound rate limiter config controls how many tokens can be transferred out of the token pool per unit of time.
- The inbound rate limiter config limits how many tokens can be transferred into the token pool per unit of time.
- Only the pool owner or rate limit admin can call this function.
- **Notes on Rate Limit Admin**: The rate limit admin is a designated address that is authorized to configure the rate limits for a token pool. This admin can be set by the pool owner using the `setRateLimitAdmin` function. If no rate limit admin is set, only the pool owner can modify the rate limits. You can retrieve the current rate limit admin address using the `getRateLimitAdmin` function.
- **Usage**: This function adjusts rate limits to prevent excessive token transfers or overload of the token pool. Note: This function also supports disabling rate limits.
### `applyAllowListUpdates`
- **Purpose**: Manages an allowlist of addresses permitted to interact with the token pool.
- **Details**:
- This function is only relevant if the token pool is access-controlled (i.e., an allowlist is enabled).
- The allowlist ensures that only specific addresses, such as trusted addresses, can transfer tokens.
- **Usage**: The pool owner uses this function to add or remove addresses from the allowlist, controlling who can transfer the tokens through CCIP.
---
# Cross-Chain Token Standard - Upgradability (EVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/evm/upgradability
Last Updated: 2025-05-19
Starting from **CCIP v1.5.1**, token pools support zero-downtime upgrades, allowing seamless transitions between versions while maintaining support for in-flight messages. This section outlines the requirements and considerations for upgrading token pools.
## Upgrade Paths
When upgrading token pools, there are three scenarios to consider:
### Complete Blockchain Upgrade (Recommended)
In this scenario, you upgrade all token pools across your connected blockchains to the latest version simultaneously. For example, if you have pools deployed on Ethereum and Polygon:
- Both pools are upgraded to the latest version
- All cross-chain messages continue processing without interruption
- No manual intervention is required for in-flight transactions
### Partial Network Upgrade (Not Recommended)
In this scenario, not all token pools are upgraded uniformly across blockchains (e.g., upgrading the token pool on Ethereum to version **v1.5.1** while retaining version **v1.5.0** on Polygon). This approach primarily affects v1.5.0 pools due to their strict source blockchain validation requirements.
For example, consider an upgrade between Ethereum and Polygon:
- If you upgrade only the Ethereum pool to **v1.5.1**, any messages that were already sent ("in-flight messages") through the **v1.5.0** pool will fail when they reach Polygon. This happens because **v1.5.0** pools are designed to accept messages only from a single configured remote pool address. When you upgrade the Ethereum pool, messages will come from its new address, which the Polygon pool won't recognize as valid.
- To prevent message delivery failures:
- Always upgrade all connected **v1.5.0** pools to **v1.5.1** simultaneously
- Follow the detailed steps in the [Upgrading from v1.5.0](#upgrading-from-v150) section
### Adding New Blockchains
You can safely deploy the latest pool version when expanding to new blockchains without affecting existing operations. For example, if you have pools on Ethereum and Polygon and want to expand to Avalanche:
- Deploy the latest version on Avalanche
- Existing pools continue operating normally
- New cross-chain routes become available through Avalanche
## Upgrading from v1.5.0
Token pools in **v1.5.0** have a strict source validation mechanism in their `_validateReleaseOrMint` function that only accepts messages from a single configured remote pool address. Unlike **v1.5.1**, which can validate against multiple remote pools, **v1.5.0** pools reject messages if the source pool address doesn't match their configured remote pool. This limitation requires careful handling of upgrades to prevent in-flight messages from failing validation.
### 1. **Deploy New Pools**
- Deploy v1.5.1 pool on Ethereum -> `0xNewPoolEth`
- Deploy v1.5.1 pool on Polygon -> `0xNewPoolPoly`
### 2. **Configure New Pools**
Configure both new pools to accept messages from both the old and new pools. This dual configuration ensures that after the upgrade, the new pools can still process any in-flight messages sent from the old pools before the upgrade.Use `applyChainUpdates` to set up the new pools:
- On Ethereum's new pool:
```solidity
ChainUpdate memory updateForPoly = ChainUpdate({
remoteChainSelector: POLYGON_SELECTOR,
remotePoolAddresses: [abi.encode(0xOldPoolPoly), abi.encode(0xNewPoolPoly)], // Both old and new pools
remoteTokenAddress: TOKEN_POLY_ADDRESS,
outboundRateLimiterConfig: outboundConfig,
inboundRateLimiterConfig: inboundConfig
});
tokenPool.applyChainUpdates([], [updateForPoly]);
```
- On Polygon's new pool:
```solidity
ChainUpdate memory updateForEth = ChainUpdate({
remoteChainSelector: ETH_SELECTOR,
remotePoolAddresses: [abi.encode(0xOldPoolEth), abi.encode(0xNewPoolEth)], // Both old and new pools
remoteTokenAddress: TOKEN_ETH_ADDRESS,
outboundRateLimiterConfig: outboundConfig,
inboundRateLimiterConfig: inboundConfig
});
tokenPool.applyChainUpdates([], [updateForEth]);
```
### 3. **Throttle Existing Pools**
Before upgrading, minimize new token transfers by setting near-zero rate limits on the existing pools. Use `setChainRateLimiterConfig` on both existing pools:
- On Ethereum's old pool:
```solidity
// Set minimal rate limit on old pools
RateLimiter.Config memory minConfig = RateLimiter.Config({
rate: 1, // 1 wei per second
capacity: 1, // 1 wei capacity
isEnabled: true // Keep enabled but effectively paused
});
oldPoolEth.setChainRateLimiterConfig(POLYGON_SELECTOR, minConfig, minConfig);
```
- On Polygon's old pool:
```solidity
oldPoolPoly.setChainRateLimiterConfig(ETH_SELECTOR, minConfig, minConfig);
```
### 4. **Update Token Admin Registry**
Use `setPool` to update the registry on both blockchains. This step switches token transfers to use the new pools.
- On Ethereum:
```solidity
tokenAdminRegistry.setPool(tokenAddress, 0xNewPoolEth);
```
- On Polygon:
```solidity
tokenAdminRegistry.setPool(tokenAddress, 0xNewPoolPoly);
```
### 5. **Handle Manual Executions**
For any failed transactions, follow the manual execution process.
### 6. **Clean Up**
After verifying all transactions are processed successfully:
- Use `removeRemotePool` to remove old pool addresses
- This prevents future messages from using the old pools
- Only proceed with cleanup after confirming:
- No pending transactions in CCIP Explorer
- All manual executions are complete
- New pools are operating correctly
- On Ethereum's new pool:
```solidity
newPoolEth.removeRemotePool(POLYGON_SELECTOR, abi.encode(0xOldPoolPoly));
```
- On Polygon's new pool:
```solidity
newPoolPoly.removeRemotePool(ETH_SELECTOR, abi.encode(0xOldPoolEth));
```
---
# Cross-Chain Token Standard (SVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/svm
Last Updated: 2025-05-19
This section provides detailed guidance for integrating tokens with Chainlink CCIP on SVM-based blockchains like Solana.
- **[Architecture](/ccip/concepts/cross-chain-token/svm/architecture)**: Understand the specific architecture for CCTs on SVM chains.
- **[Tokens](/ccip/concepts/cross-chain-token/svm/tokens)**: Learn about the requirements and compatibility for your SPL tokens.
- **[Token Pools](/ccip/concepts/cross-chain-token/svm/token-pools)**: Explore the different types of token pool programs and how to use them.
- **[Integration Guide](/ccip/concepts/cross-chain-token/svm/integration-guide)**: Determine your integration path based on mint authority control and deployment preferences. Includes decision tree and touch points with Chainlink governance.
- **[Registration and Administration](/ccip/concepts/cross-chain-token/svm/registration-administration)**: Details on registering your token with CCIP and managing administrative roles on SVM.
- **[Upgradability](/ccip/concepts/cross-chain-token/svm/upgradability)**: Information on upgrading token pool programs.
---
# Cross-Chain Token Standard - Architecture (SVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/svm/architecture
Last Updated: 2025-05-19
The Cross-Chain Token (CCT) architecture offers a streamlined, self-service approach to enabling cross-chain operations to any token. The CCT standard is token logic agnostic, meaning token developers can deploy pre-audited token pool contracts to turn any SPL-compatible token into a CCT or deploy their own custom token pool contracts for bespoke token use cases. Importantly, CCTs do not require token developers to inherit any CCIP-specific code within their token's smart contract.
The CCT standard leverages the Chainlink Cross-Chain Interoperability Protocol (CCIP) for cross-chain transfers and allows token developers to configure, manage, and transfer tokens across multiple blockchains.
The architecture diagrams below provide an overview of how CCT fits within the CCIP ecosystem, illustrating the interaction between key components.
**Source Chain**
**Destination Chain**
The CCIP architecture for SVM-based blockchains (e.g., Solana) is described on this page, where we focus specifically on cross-chain token management.
## Architecture
This document explains the four key components of CCIP's Cross-Chain Token architecture on SVM:
1. **Token**: SPL token requirements and registration for CCIP integration
2. **Token Pool**: Self-contained programs that execute cross-chain token operations
3. **Router**: Central coordinator for token administration and outbound transfers
4. **OffRamp**: Handler for inbound cross-chain token reception and release
Each component builds on the previous, forming a secure, integrated system for cross-chain token transfers. The sections below are organized in dependency order - understanding each component provides the foundation for understanding how they work together in the complete architecture.
## Token
In the Cross-Chain Token (CCT) architecture, the token is an SPL Mint managed by either the standard SPL Token program or Token-2022 (Token Extensions). To enable cross-chain transfers, every token requires:
1. **Registration**: Establishes a [token administrator](/ccip/concepts/cross-chain-token/overview#token-administrator) role and creates the TokenAdminRegistry [PDA](https://solana.com/docs/core/pda) that governs CCIP integration
2. **Pool-Specific Requirements**: Token requirements vary by chosen pool type:
- **BurnMint pools**: Must support `mint_to` and `burn` instructions; mint authority must be transferable to the pool
- **LockRelease pools**: Must support `transfer_checked` instruction; no mint authority requirements
3. **Compatibility**: Must support standard token interfaces including [Associated Token Accounts](https://spl.solana.com/associated-token-account) and provide decimal precision
For detailed compatibility requirements and pool type selection guidance, see [Tokens documentation](/ccip/concepts/cross-chain-token/svm/tokens). For the complete registration process and administrator role management, see [Registration & Administration](/ccip/concepts/cross-chain-token/svm/registration-administration).
## Token Pool
A Token Pool is a self-contained program responsible for executing cross-chain token transfers for a specific token. Each token must have exactly one pool per blockchain, deployed using one of three approaches: self-serve mode (recommended), self-deployed standard pools, or custom pools.
**Cross-Chain Token Operations:**
- **Source Chain Operations** (called by Router during `ccip_send`):
- **BurnMint pools**: Transfer tokens from sender's [ATA](https://spl.solana.com/associated-token-account) to pool's ATA, then burn from pool's ATA
- **LockRelease pools**: Lock tokens by transferring from sender's ATA to pool's [ATA](https://spl.solana.com/associated-token-account)
- **Destination Chain Operations** (called by OffRamp during `execute`):
- **BurnMint pools**: Mint tokens directly to receiver's [ATA](https://spl.solana.com/associated-token-account)
- **LockRelease pools**: Release tokens by transferring from pool's [ATA](https://spl.solana.com/associated-token-account) to receiver's ATA
For comprehensive implementation details, deployment approaches, and technical requirements, see [Token Pools documentation](/ccip/concepts/cross-chain-token/svm/token-pools).
## Router
The CCIP Router program is central to cross-chain token management. It has two flows:
1. **Token Administration**
- **TokenAdminRegistry (one [PDA](https://solana.com/docs/core/pda) per mint):** Stores the designated CCIP [token administrator](/ccip/concepts/cross-chain-token/overview#token-administrator) public key for a specific token mint (distinct from the SPL token's own mint authority) and references the token's pool [address lookup table](https://docs.solana.com/developing/lookup-tables) (ALT). The PDA is derived using seeds `[seed::TOKEN_ADMIN_REGISTRY, mint.key().as_ref()]`. This registry is created during token registration (see [Token section](#token)) and governs all subsequent CCIP operations for that token.
- **Token Pool Address Lookup Table:** A Solana [address lookup table](https://docs.solana.com/developing/lookup-tables) that precisely defines the set of account addresses (including pool configuration [PDAs](https://solana.com/docs/core/pda), pool signer PDAs, and other required accounts) that are authorized for cross-chain operations with this token. For the complete list of required accounts and their exact order, see the [`set_pool` ALT requirements](/ccip/api-reference/svm/v1.6.0/router#address-lookup-table-requirements).
- **Administrator Methods:** The token administrator can register or update cross-chain support for their mint through a sequence of instructions:
1. [`owner_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_propose_administrator): Initiated by the mint authority to designate a token administrator.
2. [`accept_admin_role_token_admin_registry`](/ccip/api-reference/svm/v1.6.0/router#accept_admin_role_token_admin_registry): Confirmation by the proposed administrator to accept the role.
3. [`set_pool`](/ccip/api-reference/svm/v1.6.0/router#set_pool): Links the token to a specific token pool configuration via an address lookup table, including writable account indexes for proper permission management.
For detailed step-by-step procedures, sequence diagrams, and complete registration workflows, see [Registration & Administration](/ccip/concepts/cross-chain-token/svm/registration-administration).
- **Pool configuration:** The TokenAdminRegistry maps each mint to exactly one token pool configuration through the ALT. This ensures that during cross-chain transfers, only the authorized token pool implementation can interact with the token.
- **Security Architecture:** The one-to-one mapping between tokens and pools provides critical security guarantees:
- **Single Authorized Pool:** Each token mint can only have one authorized pool configuration, preventing unauthorized token handling
- **Atomic Pool Updates:** Changing pool configurations requires updating the entire ALT reference, ensuring consistent state
- **Access Control:** Only the registry administrator can modify pool assignments, maintaining strict governance
2. **Lock or Burn Flow (when sending tokens cross-chain)**
- [`ccip_send`](/ccip/api-reference/svm/v1.6.0/router#ccip_send) entrypoint: When a CCIP sender initiates a cross-chain token transfer, they invoke the [`ccip_send`](/ccip/api-reference/svm/v1.6.0/router#ccip_send) instruction on the Router, providing the token mint, amount, destination chain, receiver address, and other transaction parameters.
- **Multi-step Validation:** The Router performs comprehensive validation:
- Verifies the destination chain is not cursed (via RMN Remote CPI)
- Checks sender authorization against allow-list if enabled
- Validates token amounts are non-zero
- Ensures proper account relationships and permissions
- **Registry Validation:** The sender must provide the token pool account addresses as part of the transaction. The Router validates these accounts by checking them against the addresses stored in the address lookup table (ALT) referenced by the mint's TokenAdminRegistry PDA, including proper writable permissions.
- **Fee Processing:** Before token interaction, the Router handles fee collection either in native SOL (with wrapping to WSOL) or supported fee SPL tokens through the fee quoter program.
- **[CPI](https://docs.solana.com/developing/programming-model/calling-between-programs) to [`lock_or_burn_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#lock_or_burn_tokens):** After validation, the Router executes a CPI call to the token pool's [`lock_or_burn_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#lock_or_burn_tokens) instruction, passing the appropriate context including nonce information and destination chain details for the token pool to either lock tokens in its [Associated Token Account](https://spl.solana.com/associated-token-account) (ATA) or burn them from circulation, depending on the pool implementation (see [Token Pool section](#token-pool) for operation details).
## OffRamp
The CCIP OffRamp program handles cross-chain token reception and release on the destination chain. This section focuses specifically on the **execute phase**. For the complete OffRamp functionality including commit phase and general message processing, see [OffRamp Components](/ccip/concepts/architecture/onchain/svm/components#offramp).
During the execute phase, when processing messages containing token transfers, the OffRamp performs comprehensive token-specific validations and operations.
1. **Token Pool Authorization Validation**
Before releasing or minting tokens, the OffRamp validates its authorization to interact with token pools:
- **Router Authorization Check**: Verifies a special [PDA](https://solana.com/docs/core/pda) exists, derived using seeds `[seed::ALLOWED_OFFRAMP, source_chain_selector.to_le_bytes().as_ref(), offramp.key().as_ref()]` with the Router as the program ID. This PDA must be owned by the Router, ensuring only authorized OffRamps can trigger token operations.
- **TokenAdminRegistry Validation**: Checks the TokenAdminRegistry [PDA](https://solana.com/docs/core/pda) (owned by the Router) to confirm the provided token pool accounts match the authorized configuration for the token mint.
- **[Address Lookup Table](https://docs.solana.com/developing/lookup-tables) Verification**: Validates that the address lookup table and TokenAdminRegistry PDAs correspond to the mint's registry record, ensuring interaction with the correct token pool program.
2. **Token Pool Operations**
Once authorization is confirmed, the OffRamp executes token operations:
- **[CPI](https://docs.solana.com/developing/programming-model/calling-between-programs) to [`release_or_mint_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#release_or_mint_tokens)**: Makes a cross-program invocation to the token pool's [`release_or_mint_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#release_or_mint_tokens) instruction, passing the validated cross-chain token transfer details.
- **Authorization Signer**: Uses the `external_token_pools_signer` [PDA](https://solana.com/docs/core/pda) as the CPI signer, derived using seeds `[seed::EXTERNAL_TOKEN_POOLS_SIGNER, pool_program.key().as_ref()]` with the OffRamp as the program ID. Token pools recognize this as an authorized caller from the OffRamp.
- **Token Balance Verification**: Validates that the recipient's [Associated Token Account](https://spl.solana.com/associated-token-account) receives the correct amount of tokens as specified by the pool's response.
3. **Cross-Chain Token Operations**
The OffRamp handles different token pool types based on their configuration:
- **BurnMint token pools**: Mint new tokens directly to the recipient's [ATA](https://spl.solana.com/associated-token-account)
- **LockRelease token pools**: Transfer (release) tokens from the pool's [ATA](https://spl.solana.com/associated-token-account) to the recipient's ATA
The OffRamp ensures secure token operations by leveraging the same TokenAdminRegistry and address lookup table system established by the Router (see [Token section](#token)), maintaining the one-to-one mapping between tokens and their authorized pools while providing the necessary authorization for cross-chain token reception.
## Component Integration
The four components work together to enable secure cross-chain transfers:
1. **Token Registration**: Creates the TokenAdminRegistry [PDA](https://solana.com/docs/core/pda) that authorizes all subsequent operations and links to the designated token pool
2. **Token Pool Deployment**: Provides the execution logic for transfers, with the specific pool type (BurnMint or LockRelease) determined by the token's requirements and operational preferences
3. **Router Coordination**: Manages token administration through the TokenAdminRegistry and validates outbound transfers by ensuring proper authorization against the pool's [Address Lookup Table](https://docs.solana.com/developing/lookup-tables)
4. **OffRamp Execution**: Validates authorization against the same TokenAdminRegistry system and executes inbound transfers through [CPI](https://docs.solana.com/developing/programming-model/calling-between-programs) calls to the authorized token pool
This architecture ensures that every cross-chain token operation is governed by the initial registration decision, maintaining security and consistency across the entire transfer lifecycle. The one-to-one mapping between tokens and pools, established during registration and enforced by all components, provides the foundation for secure cross-chain token transfers.
---
# Cross-Chain Token Standard - Tokens (SVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/svm/tokens
Last Updated: 2025-05-19
This document provides essential guidance for integrating tokens with Chainlink CCIP on SVM-based blockchains, such as Solana, using the Cross-Chain Token (CCT) standard. It covers token compatibility requirements, decimal planning considerations, registration processes, and strategic guidance for selecting the right token pool type for your use case.
## Compatibility
Before implementing CCIP support for your token, it's crucial to understand the compatibility requirements. These requirements ensure proper functionality of your token within the CCIP ecosystem.
### Mandatory Compatibility Requirements
All tokens must meet these requirements for CCIP compatibility:
1. **Supported Token Programs**: Must use either the standard SPL Token program or Token-2022 (Token Extensions) program
2. **Interface Requirements**: Must support standard token interfaces:
- **Associated Token Accounts (ATA)**: Required for all CCIP operations
- **Decimals**: Must provide decimal precision via `mint.decimals`
- **Program Identification**: Must be correctly referenced through `mint.to_account_info().owner`
### Decimal Planning
When deploying your token across multiple blockchains, **decimal configuration is a critical strategic decision** that affects cross-chain transfer behavior and token supply management:
**Key Decision:** Deploy your token with the **same number of decimals across all blockchains** whenever possible. This eliminates precision loss during cross-chain transfers.
**Impact on Token Pools:**
- **Different decimals**: Can result in precision loss and permanently locked/burned tokens
- **Same decimals**: Enable perfect 1:1 cross-chain transfers
For comprehensive guidance on decimal compatibility impacts, strategic recommendations, and technical implementation requirements, see [Decimal Compatibility Considerations](/ccip/concepts/cross-chain-token/svm/token-pools#decimal-compatibility-considerations).
### Token Requirements by Pool Type
Your token's requirements depend on which token pool type you'll deploy, determined by your chosen [token handling mechanism](/ccip/concepts/cross-chain-token/svm/token-pools#token-handling-mechanisms). Refer to the token handling mechanisms table to understand which pool type you need for your SVM chain deployment.
#### BurnMint Pool
**Token Requirements:**
- Must support `mint_to` and `burn` instructions
- **Mint authority** must be transferable (directly to pool signer PDA or via SPL Token Multisig). See [BurnMint Pool mint authority configuration](/ccip/concepts/cross-chain-token/svm/token-pools#mint-authority-management) for detailed multisig requirements
#### LockRelease Pool
**Token Requirements:**
- Must support `transfer_checked` instruction
- **No mint authority requirements** (pool operates through transfers only)
For detailed pool implementation, deployment approaches, and configuration requirements, see [Token Pools documentation](/ccip/concepts/cross-chain-token/svm/token-pools).
### Custom Token Pool Implementation
Token developers can alternatively implement their own custom Token Pool Programs to support additional Token-2022 extensions not supported by the standard token pools. Custom implementations must adhere to CCIP's interface specifications while allowing developers to control which instructions are called when handling tokens.
For technical requirements and implementation guidance for custom pools, see [Custom Token Pools](/ccip/concepts/cross-chain-token/svm/token-pools#custom-token-pools).
## Registration
To enable your token for CCIP cross-chain transfers, you must register it and establish a token administrator role. This process ensures secure assignment of administrative control over your token's CCIP integration.
### Registration Requirements
- **Self-Registration**: If you control the token's `mint_authority`, you can complete registration automatically
- **Manual Registration**: If `mint_authority` is not accessible (e.g., set to `None` to cap supply), manual registration is available
### Registration Process
The registration follows a secure two-step process:
1. **Propose Administrator**: Designate who will administer your token's CCIP integration
2. **Accept Role**: The proposed administrator must explicitly accept the role to complete registration
For comprehensive guidance including detailed instructions, sequence diagrams, and administrator role management, see [Registration & Administration](/ccip/concepts/cross-chain-token/svm/registration-administration).
## Next Steps
Once your token meets the compatibility requirements, you need to complete two key steps to enable cross-chain transfers:
### Register Your Token
Complete the token registration process to establish administrative control over your token's CCIP integration.
For detailed instructions, sequence diagrams, and administrator role management, see [Registration & Administration](/ccip/concepts/cross-chain-token/svm/registration-administration).
### Deploy and Configure Token Pool
After registration, deploy and configure a token pool to enable cross-chain transfer operations.
For comprehensive implementation guidance including deployment approaches (self-serve, self-deployed, custom), detailed architecture requirements, and step-by-step configuration instructions, see [Token Pools documentation](/ccip/concepts/cross-chain-token/svm/token-pools).
---
# Cross-Chain Token Standard - Token Pools (SVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/svm/token-pools
Last Updated: 2025-05-19
On SVM-based blockchains (e.g., Solana), a [token pool](/ccip/concepts/cross-chain-token/overview#token-pool) program mediates the cross-chain transfer of [cross-chain tokens](/ccip/concepts/cross-chain-token/overview) by coordinating with the SPL Token Program. This guide describes all the requirements that your token pools must meet.
For architectural context and how token pools fit into the complete CCIP system, see [Architecture Overview](/ccip/concepts/cross-chain-token/svm/architecture). For token-specific requirements and pool type selection guidance, see [Tokens documentation](/ccip/concepts/cross-chain-token/svm/tokens).
## Deployment Approaches
Before diving into the technical details, it's important to understand your three deployment options. Each approach offers different levels of control and responsibility based on your operational preferences and infrastructure requirements.
### Approach 1: Self-Serve Mode (Recommended)
**What it is:** Chainlink Labs deploys and maintains the standard token pool programs (BurnMint and LockRelease) on Solana Devnet and Mainnet. You simply initialize your token pool from these existing deployed programs.
**Key Benefits:**
- No need to build, audit, or deploy pool programs yourself
- Automatic access to upgrades and security fixes
- Fastest time to deployment
- Lower operational overhead
**Who should use this:** [Token developers](/ccip/concepts/cross-chain-token/overview#token-developer) that want to integrate with CCIP quickly without managing infrastructure.
**How it works:**
1. **Program Admin** (Chainlink's upgrade authority) deploys the program and initializes the Global Config PDA with:
- `self_served_allowed: true` (enables user self-service)
- Default router address for the network
- Default RMN remote address for security
2. **Users** can initialize pools for their tokens by calling the [`initialize`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#initialize) instruction, provided they control the token's mint authority
3. New pools automatically inherit the default router and RMN addresses from the Global Config PDA
### Approach 2: Self-Deployed Standard Pools
**What it is:** You build and deploy the standard Chainlink token pool programs yourself, giving you control over the upgrade authority.
**Key Benefits:**
- Full control over when to apply upgrades
- Can customize deployment timing and parameters
- Maintain upgrade authority for your pools
- Use proven, audited standard implementations
**Who should use this:** Projects that need control over upgrade timing or have specific governance requirements.
**How it works:** You compile and deploy the Chainlink-provided [BurnMint](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/burnmint-token-pool) or [LockRelease](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/lockrelease-token-pool) programs to your chosen address, retaining upgrade authority.
### Approach 3: Custom Token Pools
**What it is:** You build your own token pool program with custom logic while ensuring CCIP compatibility.
**Key Benefits:**
- Complete customization for unique token mechanics
- Support for specialized features (rebasing, fee-on-transfer, etc.)
- Full control over program logic
**Who should use this:** Projects with unique token mechanics that standard pools cannot accommodate.
**Requirements:** Must implement all [technical requirements](#technical-requirements) and follow the specifications detailed in the [Custom Token Pools](#custom-token-pools) section. Pay special attention to [decimal compatibility considerations](#decimal-compatibility-considerations) as custom pools require manual decimal conversion implementation.
## Token Handling Mechanisms
Before implementing token pools, you need to choose the appropriate [token handling mechanism](/ccip/concepts/cross-chain-token/overview#token-handling-mechanisms) for your cross-chain token transfers. This strategic decision determines which combination of token pools you'll deploy on source and destination blockchains.
The table below summarizes the different token handling mechanisms and the [recommended token pools](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs) to deploy for each scenario, ensuring a seamless token transfer process.
| Token Handling Mechanism | Source Blockchain Token Pool Type | Destination Blockchain Token Pool Type | How it Works |
| ------------------------ | --------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Burn & Mint | BurnMint | BurnMint | - Standard burn and mint mechanism for CCT transfers. |
| Lock & Mint | LockRelease | BurnMint | - The source blockchain is the issuing blockchain. - The LockRelease token pool must be deployed on the issuing blockchain. |
| Burn & Unlock | BurnMint | LockRelease | - The destination blockchain is the issuing blockchain. - The BurnMint token pool burns tokens on the source blockchain, and the LockRelease token pool unlocks tokens on the issuing blockchain. |
| Lock & Unlock | LockRelease | LockRelease | - Tokens are locked on the source blockchain and unlocked on the destination blockchain. - Can result in fragmented liquidity and requires careful management of liquidity across multiple blockchains to avoid stuck token transfers due to insufficient liquidity locked in the token pool on the destination blockchain. |
## Decimal Compatibility Considerations
Before implementing token pools, it's critical to understand how decimal differences across blockchains can impact your cross-chain token transfers. This section provides strategic guidance for making informed decisions about token deployment and decimal configuration. For initial decimal planning considerations when deploying your token, see [Decimal Planning](/ccip/concepts/cross-chain-token/svm/tokens#decimal-planning).
### The Decimal Challenge
When deploying a token across multiple blockchains, [token developers](/ccip/concepts/cross-chain-token/overview#token-developer) can configure different decimal places for each blockchain. However, this flexibility comes with important trade-offs:
**Example Configuration:**
- **Ethereum**: 18 decimals (0.123456789123456789)
- **Solana**: 9 decimals (0.123456789)
**What Happens During Transfers:**
When transferring between blockchains with different decimal precision, CCIP automatically handles conversion but **must round numbers** to match the destination's configured precision.
### Impact on Token Supply
Different decimal configurations can affect your token's total supply across chains:
| Transfer Direction | Result | Supply Impact |
| ------------------------ | --------------------------------------------------------------- | --------------------------------------------------------------- |
| **High → Low Precision** | Precision loss due to rounding (e.g., 18 decimals → 9 decimals) | **Permanent loss**: Tokens burned/locked exceed tokens released |
| **Low → High Precision** | No precision loss (e.g., 9 decimals → 18 decimals) | **No impact**: Perfect conversion possible |
| **Equal Precision** | No precision loss (e.g., 18 decimals → 18 decimals) | **No impact**: Perfect 1:1 transfers |
**Critical Considerations:**
- **BurnMint Pools**: Lost precision results in **permanently burned tokens** on the source chain
- **LockRelease Pools**: Lost precision results in **tokens permanently locked** in the source pool
- Small amounts are most affected by rounding; large transfers typically see minimal percentage impact
### Strategic Recommendations
**Primary Recommendation:**
Deploy tokens with the **same number of decimals across all blockchains** whenever possible. This completely eliminates precision loss during cross-chain transfers.
**When Different Decimals Are Necessary:**
- Only use different decimals when required by blockchain limitations
- Clearly communicate rounding risks to users in your UI
- Consider implementing transfer warnings for high-to-low precision transfers
- Plan for locked/burned token accumulation in your tokenomics
**Development Considerations:**
- Verify decimal configurations on both source and destination before going live
- Test small-amount transfers to understand rounding behavior
- Consider implementing minimum transfer amounts to minimize relative precision loss
**Standard token pools** (BurnMint, LockRelease) handle decimal conversion automatically - no additional implementation required.
**Custom token pools** must implement decimal conversion manually - see [Decimal Implementation Requirements](#decimal-implementation-requirements) for technical details.
## Standard Token Pools: An Overview
Chainlink provides two standard token pool types that implement the token handling mechanisms described above. Each pool type implements specific logic for how tokens are managed during cross-chain transfers.
### SVM Program Architecture Overview
On SVM-based blockchains like Solana, applications consist of **programs** (executable code) and **accounts** (data storage). Programs are stateless and store their data in separate accounts.
**Program-Derived Addresses (PDAs)** are special accounts with addresses [deterministically derived](https://solana.com/docs/core/pda) from seeds and a program ID. PDAs have no private keys, allowing programs to "sign" for them. This enables secure, predictable account management.
### CCIP Token Pool Architecture
Both BurnMint and LockRelease token pool programs use PDAs to organize data into two categories:
- **Global PDAs**: Shared configuration for all pools deployed from this program
- **Pool-Specific PDAs**: Individual configuration for each token's pool
Each token has its own independent pool (one pool per mint), with pool-specific PDAs derived using the mint address as a seed.
### Program-Derived Addresses (PDAs)
**Global Configuration:**
- **Global Config PDA** (`seeds: ["config"]`): Program-wide settings including `self_served_allowed`, default router, and RMN remote addresses
**Pool-Specific Configuration (per mint):**
- **Pool State PDA** (`seeds: ["ccip_tokenpool_config", mint]`): Token-specific pool configuration, ownership, and operational settings
- **Pool Signer PDA** (`seeds: ["ccip_tokenpool_signer", mint]`): Signing authority for token operations (burns, mints, transfers)
- **Chain Config PDAs** (`seeds: ["ccip_tokenpool_chainconfig", chain_selector, mint]`): Per-destination chain configuration including rate limits and remote pool addresses
The detailed sections below will help you understand the specific requirements and implementation details for each pool type.
### BurnMint Token Pool
The BurnMint token pool provides burn and mint operations for cross-chain token transfers:
#### How It Works
**Outbound token transfer (when the SVM chain is the source chain):**
- User transfers tokens to the pool's Associated Token Account (ATA)
- Pool program burns the tokens using the standard SPL burn instruction
- Tokens are permanently removed from circulation on the source chain
**Inbound token transfer (when the SVM chain is the destination chain):**
- Pool program mints new tokens using the SPL `mint_to` instruction
- New tokens are created in the user's associated token account (ATA)
- Pool program must control the `mint_authority` for the token
#### BurnMint Pool Specifics
The BurnMint token pool specializes in burn and mint operations, requiring specific mint authority management for minting operations.
#### Mint Authority Management
**Mint Authority Control:**
The mint authority follows this lifecycle for BurnMint pools:
**Required First: Pool Initialization**
- You must control the token's `mint_authority` to initialize the pool
- The [`initialize`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#initialize) instruction requires the mint authority as the signer
- **The pool automatically sets the initializer as the pool owner during initialization**
- Pool owner must create the [Associated Token Account (ATA)](https://www.solana-program.com/docs/associated-token-account) for the Pool Signer PDA before pool operations can begin
**Ongoing: Pool Management**
- As the pool owner, you can configure CCIP settings throughout the pool's lifetime:
- Set up chain configurations for new remote chains
- Configure or update rate limits for existing chains
- Manage allowlists and other pool settings
- Add or remove remote pool addresses
- **Transfer pool ownership to any address using [`transfer_ownership`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#transfer_ownership)**
**When Ready: Mint Authority Transfer**
- Transfer the `mint_authority` when you're ready to enable the pool for minting operations
- This enables the pool to mint tokens on inbound flows when the SVM chain is the destination
- Choose one of two transfer mechanisms (multisig approach strongly recommended for production):
**Option 1: Direct Transfer (Simple)**
**Configuration:**
- Transfer the `mint_authority` directly to the Pool Signer PDA
**Best suited for:**
- Development environments
- Simple production deployments where the token exclusively integrates with CCIP
- Scenarios with a single trusted actor interacting with the token
**Limitations:**
- Only the token pool can mint tokens
- Reduced flexibility for future integrations
**Option 2: Multisig Configuration (Recommended for Production)**
**Configuration:**
- Set up an M-of-N [SPL token multisig account](https://spl.solana.com/token#multisig-usage) as the mint authority
- Include the Pool Signer PDA as a required signer **exactly M times** (where M is your threshold)
- Include additional signers under your exclusive control as needed
**SPL Token Multisig Requirements:**
- **Threshold (M)**: Any value from 1 to N (your choice based on security needs)
- **Total Signers (N)**: At least 2 signers total
- **Pool Signer Repetition**: The Pool Signer PDA must appear **at least M times** in the signer list (typically exactly M times for optimal configuration)
- **Additional Signers**: Remaining (N-M) signer slots for your governance control
**Valid Configuration Examples:**
| Threshold (M) | Total Signers (N) | Pool Signer PDA Occurrences | Your Signers |
| ------------- | ----------------- | --------------------------- | ------------ |
| 1 | 3 | 1 | 2 |
| 2 | 4 | 2 | 2 |
| 2 | 5 | 2 | 3 |
| 3 | 6 | 3 | 3 |
**Key benefits:**
- Enables multiple authorized parties to mint tokens
- Maintains governance control through other multisig signers
- Provides extensibility for future integrations
- Enhanced security through distributed control
- Flexible threshold configuration for different security requirements
**Technical Requirements:**
- Must use [SPL token multisig](https://spl.solana.com/token#multisignatures) - other multisig implementations are not supported
- The Pool Signer PDA must be included at least M times as a multisig signer
- Pool must be able to mint tokens for cross-chain transfers without requiring additional manual signatures
**Why Pool Signer Must Appear At Least M Times:**
This design ensures the pool can always mint tokens for cross-chain transfers without requiring your manual intervention, while still maintaining your governance control:
- **Autonomous Operation**: Pool operations always have sufficient signatures (≥M occurrences of Pool Signer)
- **Governance Control**: Your additional signers can still initiate mint operations independent of the pool (≤N-M pool signer occurrences ensures ≥M governance signer slots remain)
- **Flexible Security**: You choose the threshold (M) based on your security requirements, constrained by M ≤ N/2
- **Optimal Configuration**: Most deployments use exactly M pool signer occurrences for optimal balance
**Multisig Configuration Validation:**
The system validates your multisig configuration with these rules:
- `total_signers >= 2` (must have at least 2 signers)
- `threshold >= 1` (must require at least 1 signature)
- `threshold <= total_signers` (can't require more signatures than available signers)
- `pool_signer_occurrences >= threshold` (Pool Signer PDA must appear at least M times for autonomous operation)
- `pool_signer_occurrences <= (total_signers - threshold)` (Pool Signer PDA cannot occupy all governance signer slots)
**Constraint Implications:**
- **Minimum Pool Signer Occurrences**: M times (enables autonomous pool operation)
- **Maximum Pool Signer Occurrences**: N-M times (preserves governance control capability)
- **Threshold Limit**: M ≤ N/2 (ensures both pool autonomy and governance control are possible)
**Common Misconceptions:**
To ensure proper implementation, avoid these common misconceptions about SPL token multisig configurations:
**Incorrect**: "Only 1-of-N multisig is supported"\
**Correct**: "Any M-of-N multisig is supported with proper Pool Signer repetition"
**Incorrect**: "Pool Signer appears once in the multisig"\
**Correct**: "Pool Signer appears at least M times (typically exactly M times) in the multisig"
**Incorrect**: "Pool can operate with any multisig configuration"\
**Correct**: "Pool Signer must appear ≥M and ≤(N-M) times to ensure both autonomous operation and governance control capability"
**Incorrect**: "Any threshold M is valid for any total signers N"\
**Correct**: "Threshold M must satisfy M ≤ N/2 to allow both pool autonomy and governance control"
After this transfer, the pool can mint tokens when the SVM chain is the destination chain for cross-chain transfers.
#### Access Control
Understanding who can call which instructions is critical for secure pool operation and proper integration. This table shows the authorization model for all BurnMint token pool instructions, helping you understand the security boundaries and operational responsibilities.
| Instruction Category | Instruction | Program Upgrade Authority | Pool Owner | Router Authority |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------- | :-----------------------: | :--------: | :--------------: |
| **Global Config** | [`init_global_config`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#init_global_config) | ✅ | ❌ | ❌ |
| | [`update_self_served_allowed`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#update_self_served_allowed) | ✅ | ❌ | ❌ |
| | [`update_default_router`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#update_default_router) | ✅ | ❌ | ❌ |
| | [`update_default_rmn`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#update_default_rmn) | ✅ | ❌ | ❌ |
| **Pool Lifecycle** | [`initialize`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#initialize) | ✅ OR Mint Authority | ❌ | ❌ |
| **Ownership** | [`transfer_ownership`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#transfer_ownership) | ❌ | ✅ | ❌ |
| | [`accept_ownership`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#accept_ownership) | ❌ | ✅\*\* | ❌ |
| **Security** | [`transfer_mint_authority_to_multisig`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#transfer_mint_authority_to_multisig) | ✅ | ❌ | ❌ |
| | [`configure_allow_list`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#configure_allow_list) | ❌ | ✅ | ❌ |
| | [`remove_from_allow_list`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#remove_from_allow_list) | ❌ | ✅ | ❌ |
| **Chain Config** | [`init_chain_remote_config`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#init_chain_remote_config) | ❌ | ✅ | ❌ |
| | [`edit_chain_remote_config`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#edit_chain_remote_config) | ❌ | ✅ | ❌ |
| | [`append_remote_pool_addresses`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#append_remote_pool_addresses) | ❌ | ✅ | ❌ |
| | [`set_chain_rate_limit`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#set_chain_rate_limit) | ❌ | ✅ | ❌ |
| | [`delete_chain_config`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#delete_chain_config) | ❌ | ✅ | ❌ |
| **Cross-Chain** | [`lock_or_burn_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#lock_or_burn_tokens) | ❌ | ❌ | ✅ |
| | [`release_or_mint_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#release_or_mint_tokens) | ❌ | ❌ | ✅ |
**Notes:**
- \* **accept_ownership**: Must be the `proposed_owner` from `transfer_ownership`
- **Critical**: Pool Signer PDA MUST have mint capability (direct or via multisig) for minting operations
- **Critical**: Pool owner must create the [Associated Token Account (ATA)](https://www.solana-program.com/docs/associated-token-account) for the Pool Signer PDA before pool operations can begin
#### When to Use
**Key Characteristics:**
- Requires mint authority control for initialization, then transfer to pool for operations
- Total supply can vary across chains during transfers
- No liquidity management needed
- Suitable for tokens designed to expand/contract supply
**Choose BurnMint When:**
- You control the `mint_authority` for your token (required for initialization)
- You are willing to transfer mint authority to the pool after CCIP registration
- Your token is designed to have variable supply across chains
- You want to avoid liquidity management complexity
- Your token participates in [Burn and Mint](/ccip/concepts/cross-chain-token/overview#burn-and-mint) or [Lock and Mint/Burn and Unlock](/ccip/concepts/cross-chain-token/overview#lock-and-mint) token handling mechanisms
For detailed mint authority configuration options, see [Mint Authority Management](#mint-authority-management).
### LockRelease Token Pool
The LockRelease token pool implements a lock-and-release strategy for cross-chain token transfers:
#### How It Works
**Outbound token transfer (when the SVM chain is the source chain):**
- User transfers tokens to the pool's Associated Token Account (ATA)
- Pool program "locks" tokens by holding them in its account
- Tokens remain in circulation but are held by the pool as escrow
- No tokens are destroyed - total supply remains constant
**Inbound token transfer (when the SVM chain is the destination chain):**
- Pool program transfers tokens from its ATA to the user's ATA
- Pool must have sufficient token balance (liquidity) to fulfill transfers
- No minting occurs - only transfers of existing tokens from the pool's reserves
#### LockRelease Pool Specifics
The LockRelease token pool specializes in lock and release operations, requiring liquidity management rather than mint authority control. Unlike BurnMint pools, LockRelease pools:
- **Do not require mint authority transfer** - the original mint authority remains unchanged
- **Rely on token reserves** - manage liquidity through the pool's token holdings
- **Use rebalancer role** - designated address manages liquidity operations
#### Liquidity Management
**Pool Initialization:**
- Program upgrade authority can always initialize pools
- When self-serve is enabled (`self_served_allowed: true`), the token's mint authority can also initialize pools
- **The pool automatically sets the initializer as the pool owner during initialization**
- Pool is configured with liquidity acceptance settings during initialization
- Pool owner must create the [Associated Token Account (ATA)](https://www.solana-program.com/docs/associated-token-account) for the Pool Signer PDA before pool operations can begin
**Ongoing Liquidity Operations:**
- **Rebalancer Role**: Designated address that can provide or withdraw pool liquidity
- **Liquidity Acceptance**: Configurable setting (`can_accept_liquidity`) controls whether the pool accepts new liquidity
- **Liquidity Operations**:
- `provide_liquidity`: Rebalancer adds tokens to pool reserves
- `withdraw_liquidity`: Rebalancer removes tokens from pool reserves (can transfer to other pools)
**Liquidity Requirements:**
- Pool must maintain sufficient token balance to fulfill cross-chain transfers
- Insufficient liquidity will cause transfer failures
- Rebalancer responsible for maintaining adequate liquidity across all supported chains
#### Access Control
Understanding who can call which instructions is critical for secure pool operation and proper integration. This table shows the authorization model for all LockRelease token pool instructions, helping you understand the security boundaries and operational responsibilities.
| Instruction Category | Instruction | Program Upgrade Authority | Pool Owner | Router Authority | Rebalancer |
| -------------------- | --------------------------------------------------------------------------------------------------------------------- | :-----------------------: | :--------: | :--------------: | :--------: |
| **Global Config** | [`init_global_config`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#init_global_config) | ✅ | ❌ | ❌ | ❌ |
| | [`update_self_served_allowed`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#update_self_served_allowed) | ✅ | ❌ | ❌ | ❌ |
| | [`update_default_router`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#update_default_router) | ✅ | ❌ | ❌ | ❌ |
| | [`update_default_rmn`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#update_default_rmn) | ✅ | ❌ | ❌ | ❌ |
| **Pool Lifecycle** | [`initialize`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#initialize) | ✅ OR Mint Authority | ❌ | ❌ | ❌ |
| **Ownership** | [`transfer_ownership`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#transfer_ownership) | ❌ | ✅ | ❌ | ❌ |
| | [`accept_ownership`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#accept_ownership) | ❌ | ✅\*\* | ❌ | ❌ |
| **Security** | [`configure_allow_list`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#configure_allow_list) | ❌ | ✅ | ❌ | ❌ |
| | [`remove_from_allow_list`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#remove_from_allow_list) | ❌ | ✅ | ❌ | ❌ |
| **Liquidity Mgmt** | [`set_rebalancer`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#set_rebalancer) | ❌ | ✅ | ❌ | ❌ |
| | [`set_can_accept_liquidity`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#set_can_accept_liquidity) | ❌ | ✅ | ❌ | ❌ |
| | [`provide_liquidity`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#provide_liquidity) | ❌ | ❌ | ❌ | ✅ |
| | [`withdraw_liquidity`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#withdraw_liquidity) | ❌ | ❌ | ❌ | ✅ |
| **Chain Config** | [`init_chain_remote_config`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#init_chain_remote_config) | ❌ | ✅ | ❌ | ❌ |
| | [`edit_chain_remote_config`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#edit_chain_remote_config) | ❌ | ✅ | ❌ | ❌ |
| | [`append_remote_pool_addresses`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#append_remote_pool_addresses) | ❌ | ✅ | ❌ | ❌ |
| | [`set_chain_rate_limit`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#set_chain_rate_limit) | ❌ | ✅ | ❌ | ❌ |
| | [`delete_chain_config`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#delete_chain_config) | ❌ | ✅ | ❌ | ❌ |
| **Cross-Chain** | [`lock_or_burn_tokens`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#lock_or_burn_tokens) | ❌ | ❌ | ✅ | ❌ |
| | [`release_or_mint_tokens`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#release_or_mint_tokens) | ❌ | ❌ | ✅ | ❌ |
**Notes:**
- \* **accept_ownership**: Must be the `proposed_owner` from `transfer_ownership`
- **Critical**: Pool owner must create the [Associated Token Account (ATA)](https://www.solana-program.com/docs/associated-token-account) for the Pool Signer PDA before pool operations can begin
#### When to Use
**Key Characteristics:**
- Program upgrade authority can always initialize pools; mint authority can self-initialize when accessible
- No mint authority required for ongoing operations (unlike BurnMint)
- Fixed total supply per chain - tokens are only transferred, never created or destroyed
- Requires active liquidity management and funding
- Suitable for tokens where you want to retain mint authority control OR where mint authority is no longer accessible
**Choose LockRelease When:**
- You want to retain control of the token's mint authority (not transfer it to the pool)
- Your token's mint authority is disabled/revoked (e.g., to cap token supply) or otherwise inaccessible
- You prefer not to delegate minting capabilities to the token pool
- You want to maintain fixed total supply on each blockchain
- You can manage liquidity requirements and rebalancing across chains
- You have operational capacity to monitor and maintain pool liquidity levels
- Your token participates in [Lock and Mint/Burn and Unlock](/ccip/concepts/cross-chain-token/overview#burn-and-unlock) or [Lock and Unlock](/ccip/concepts/cross-chain-token/overview#lock-and-unlock) token handling mechanisms
Both pool types are built on the shared [`base-token-pool`](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/base-token-pool) foundation, which provides common functionality including [rate limiting](/ccip/concepts/cross-chain-token/overview#token-pool-rate-limits), allowlists, cross-chain configuration, and event handling.
## Next Steps: Pool Configuration
After deploying your token pool using any of the approaches above, you'll need to configure it for cross-chain operations. This includes:
- Setting up remote chain configurations
- Configuring rate limits for cross-chain transfers
- Managing allowlists and access controls
- Setting up liquidity management (for LockRelease pools)
For detailed instructions on configuring your token pool parameters, see [Token Pool Configuration](/ccip/concepts/cross-chain-token/svm/registration-administration#token-pool-configuration-pool-side).
## Custom Token Pools
If the standard BurnMint and LockRelease token pool programs don't meet your requirements, you can create a custom token pool program that is compatible with CCIP. This advanced approach gives you complete control over token handling logic while maintaining CCIP compatibility.
### When to Build Custom Token Pools
**Consider building a custom token pool when:**
- **Complex Token Mechanics**: Your token has unique behavior like rebasing, fee-on-transfer, or complex reward mechanisms
- **Specialized Business Logic**: You need custom validation, compliance checks, or integration with other protocols
- **Governance Requirements**: You need custom access control patterns
**Standard pools are sufficient for:**
- Basic SPL tokens with standard mint/burn/transfer functionality
- Tokens that don't require custom logic during cross-chain transfers
- SPL tokens and Token-2022 tokens
### Technical Requirements
All custom token pools must implement the following requirements to be CCIP-compatible:
#### Required Program-Derived Addresses (PDAs)
Your custom token pool **must** use these exact PDA seeds so the CCIP Router can correctly derive and interact with your pool accounts:
**Required PDAs and Their Seeds:**
1. **Global Config PDA**
- Seeds: `["config"]`
- Purpose: Program-wide settings including `self_served_allowed`, default router, and RMN addresses
- Used by: Program admin for deployment configuration
2. **Pool State PDA**
- Seeds: `["ccip_tokenpool_config", mint_address]`
- Purpose: Token-specific pool configuration, ownership, and operational settings
- Used by: Pool management and cross-chain operations
3. **Pool Signer PDA**
- Seeds: `["ccip_tokenpool_signer", mint_address]`
- Purpose: Signing authority for token operations (burns, mints, transfers)
- Used by: Token transfer operations and mint authority control
4. **Chain Config PDAs**
- Seeds: `["ccip_tokenpool_chainconfig", chain_selector, mint_address]`
- Purpose: Per-destination chain configuration including rate limits and remote pool addresses
- Used by: Cross-chain transfer validation and routing
#### Mandatory Instructions
When CCIP interacts with your custom token pools, it expects the presence of the following functions with exact signatures:
1. **For source chain operations (locking or burning tokens):**
- [`lock_or_burn_tokens(ctx: Context, lock_or_burn: LockOrBurnInV1,) -> Result`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#lock_or_burn_tokens)
- This function handles token operations when your chain is the source of a cross-chain transfer
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/base-token-pool#lockorburnoutv1) to learn more about the parameters
2. **For destination chain operations (releasing or minting tokens):**
- [`release_or_mint_tokens(ctx: Context, release_or_mint: ReleaseOrMintInV1,) -> Result`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#release_or_mint_tokens)
- This function handles token operations when your chain is the destination of a cross-chain transfer
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/base-token-pool#releaseormintinv1) to learn more about the parameters
#### Integration with Base Token Pool
All custom token pools should integrate the [`base-token-pool`](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/base-token-pool) library for core functionality:
- **Ownership management**: Pool ownership and proposed ownership transfers
- **Rate limiting**: Inbound and outbound rate limit controls per destination chain
- **Cross-chain configuration**: Remote pool addresses and chain-specific settings
- **Access control**: Allowlists and permission management
- **Event handling**: Standardized event emissions for monitoring
- **Decimal conversion**: Use `to_svm_token_amount` for proper decimal handling between chains
#### Decimal Implementation Requirements
All custom token pools **must** implement proper decimal conversion to handle tokens with different decimal configurations across blockchains. For strategic guidance on decimal compatibility decisions, see [Decimal Compatibility Considerations](#decimal-compatibility-considerations).
**Required Implementation:**
All standard token pools (BurnMint, LockRelease) automatically call [`to_svm_token_amount`](/ccip/api-reference/svm/v1.6.0/base-token-pool#to_svm_token_amount) during the [`release_or_mint_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#release_or_mint_tokens) flow. If you build a custom token pool, you **must** use [`to_svm_token_amount`](/ccip/api-reference/svm/v1.6.0/base-token-pool#to_svm_token_amount) from the base token pool library.
**How [`to_svm_token_amount`](/ccip/api-reference/svm/v1.6.0/base-token-pool#to_svm_token_amount) Works:**
1. Reads the incoming amount from the source chain
2. Converts it from the source token decimals to the local token decimals
3. Returns a u64 amount or an error if the conversion exceeds u64::Max (maximum token supply on SVM-based blockchains)
### Implementation Examples
#### Example 1: Rebasing Token Pool
**Use Case**: Rebasing tokens adjust their supply periodically based on external parameters (e.g., price, yield). These tokens require custom logic to handle rebasing events during cross-chain transfers.
**Implementation Approach**:
- **Source Blockchain**: Instead of burning or locking a specific amount of tokens, track "underlying shares" that represent proportional ownership. In [`lock_or_burn_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#lock_or_burn_tokens), convert the user's tokens into an internal share count and store that in [`LockOrBurnOutV1.dest_pool_data`](/ccip/api-reference/svm/v1.6.0/base-token-pool#lockorburnoutv1) for the destination token pool.
- **Destination Blockchain**: In [`release_or_mint_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#release_or_mint_tokens), parse the share count from [`ReleaseOrMintInV1.source_pool_data`](/ccip/api-reference/svm/v1.6.0/base-token-pool#releaseormintinv1) and convert those shares back into the current token amount based on the latest rebase. If your token supply has been rebased upward/downward since the transfer initiated, recalculate the final amount before minting or transferring to the user's ATA.
#### Example 2: Fee-on-Transfer Token Pool
**Use Case**: Tokens that deduct fees during transfers need custom logic to ensure cross-chain transfer amounts are accurate.
**Implementation Approach**:
- **Source Blockchain**: In [`lock_or_burn_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#lock_or_burn_tokens), calculate the net amount after fees and ensure the correct amount is locked/burned. Store the original intended amount in `dest_pool_data`.
- **Destination Blockchain**: In [`release_or_mint_tokens`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#release_or_mint_tokens), use the original intended amount from `source_pool_data` to mint/release the correct amount, accounting for any destination-side fees.
### Development Resources
**Implementation References:**
- [BurnMint Token Pool](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/burnmint-token-pool) - Standard burn/mint implementation
- [LockRelease Token Pool](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/lockrelease-token-pool) - Standard lock/release implementation
- [Base Token Pool Library](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/base-token-pool) - Shared functionality foundation
**Testing and Validation:**
- Test with CCIP Router integration to ensure proper PDA derivation
- Validate decimal conversion handling for cross-chain transfers
- Ensure rate limiting and access control functions work correctly
- Test both source and destination chain operations thoroughly
---
# Cross-Chain Token Standard - Integration Guide (SVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/svm/integration-guide
This integration guide helps you determine the optimal path for integrating your SPL token with Chainlink CCIP on SVM-based blockchains like Solana. Follow the decision tree below to understand your specific requirements, deployment options, and when you can use self-service versus when you need Chainlink assistance.
## Prerequisites
Before starting integration, ensure you meet these requirements:
- **Token Compatibility**: Your token must meet [compatibility requirements](/ccip/concepts/cross-chain-token/svm/tokens#compatibility)
- **Decimal Planning**: Plan [decimal configuration](/ccip/concepts/cross-chain-token/svm/tokens#decimal-planning) across all target blockchains
- **Pool Type Selection**: Choose the appropriate token pool type for your use case:
- **[BurnMint Pools](/ccip/concepts/cross-chain-token/svm/token-pools#burnmint-token-pool)**: Burn tokens on source, mint on destination. Requires transferring mint authority to pool. Best for tokens designed to have variable supply across chains.
- **[LockRelease Pools](/ccip/concepts/cross-chain-token/svm/token-pools#lockrelease-token-pool)**: Lock tokens on source, release on destination. No mint authority transfer required. Requires liquidity management. Best for retaining mint authority control or capped-supply tokens.
For detailed comparisons and technical requirements, see [Token Handling Mechanisms](/ccip/concepts/cross-chain-token/svm/token-pools#token-handling-mechanisms).
## Integration Overview
CCIP Cross-Chain Token (CCT) integration involves two key phases:
1. **Token Registration**: Establishing a CCIP token administrator role
2. **Token Pool Deployment**: Setting up the pool program that handles cross-chain operations
Your path through these phases depends on your **mint authority control** and chosen **[deployment approach](/ccip/concepts/cross-chain-token/svm/token-pools#deployment-approaches)**.
## Decision Flow Diagram
The following diagram visualizes the complete decision process based on your mint authority control:
## Mint Authority Assessment
The first critical decision point is understanding your mint authority control, as this determines your available options throughout the integration process.
**Why mint authority control matters for token registration:** The Router's [`owner_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_propose_administrator) instruction can only be called by the actual mint authority holder. The Router verifies onchain that the signer account matches the token's `mint_authority` field. If you cannot sign this instruction as the mint authority, the Router cannot verify your authorization onchain, requiring manual governance assistance.
**You have direct control when:**
- You hold the mint authority private key directly
- You control a governance multisig that holds the mint authority (non-SPL token multisig)
- You can execute mint authority operations through your governance process
**You do NOT have direct control when:**
- Mint authority is set to `None` (supply capped)
- Mint authority is held by an SPL token multisig
- Mint authority is controlled by another party you cannot coordinate with
- You have lost access to the mint authority private key
## Integration Paths
Based on your mint authority control, follow the appropriate integration path:
### Path A: Full Self-Service (Mint Authority Controlled)
**When to use:** You have direct control over the mint authority
**Registration Process:**
- **Self-service registration**: Use [`owner_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_propose_administrator) and [`accept_admin_role_token_admin_registry`](/ccip/api-reference/svm/v1.6.0/router#accept_admin_role_token_admin_registry) instructions (propose + accept)
- **Immediate control**: Complete registration without external assistance
- **Administrator flexibility**: Choose any address as the CCIP token administrator
**Pool Deployment Options:**
1. **[Self-Serve Mode](/ccip/concepts/cross-chain-token/svm/token-pools#approach-1-self-serve-mode-recommended) (Recommended)**
- Use Chainlink-deployed standard pool programs
- Initialize your pool using [`initialize`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#initialize) instruction
- Automatic governance-controlled upgrades
- Lowest operational overhead
2. **[Self-Deployed Standard Pools](/ccip/concepts/cross-chain-token/svm/token-pools#approach-2-self-deployed-standard-pools)**
- Deploy standard pool programs yourself
- Control upgrade authority and timing
- Initialize your own pools
3. **[Custom Token Pools](/ccip/concepts/cross-chain-token/svm/token-pools#approach-3-custom-token-pools)**
- Build custom pool logic for specialized requirements
- Full control over implementation and upgrades
### Path B: Assisted Registration + Self-Serve Pools (Mint Authority Not Controlled)
**When to use:** You want to use self-serve mode pools, but you cannot call [`owner_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_propose_administrator) as the mint authority
**Registration Process:**
- **Manual registration required**: Submit [registration request](https://chain.link/ccip-contact?v=Tokens:%20Token%20admin%20registration)
- **CCIP governance assistance**: Router upgrade authority will propose the administrator using [`ccip_admin_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#ccip_admin_propose_administrator)
- **Choose administrator**: You specify who should be the CCIP token administrator
**Pool Deployment:**
- **Assisted pool initialization**: Submit [registration request](https://chain.link/ccip-contact?v=Tokens:%20Token%20admin%20registration) to have the pool program upgrade authority initialize your pool
- **Self-serve mode benefits**: Once initialized, you become the pool owner with full configuration control
- **Governance-controlled upgrades**: Benefit from automatic security updates and features
### Path C: Full Manual Process (Custom or Self-Deployed)
**When to use:** You cannot call [`owner_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_propose_administrator) as the mint authority and want self-deployed or custom pools
**Registration Process:**
- Same manual registration process as Path B
**Pool Deployment:**
- **Deploy your own programs**: Full control over pool program deployment
- **Control upgrade authority**: Manage your own upgrade process
- **Initialize independently**: Use your upgrade authority to initialize pools
## Implementation Steps
Once you've determined your path, follow these implementation steps:
### Step 1: Complete Registration
- **Self-service path**: Follow [self-service registration](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow)
- **Assisted path**: Submit [registration request](https://chain.link/ccip-contact?v=Tokens:%20Token%20admin%20registration)
### Step 2: Deploy and Initialize Token Pool
Choose your deployment approach:
- **Self-serve mode**: Initialize pool from existing Chainlink-deployed programs
- **If you can sign as mint authority**: Use the [`initialize`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#initialize) instruction directly
- **If you cannot sign as mint authority** (mint authority is `None`, held by SPL token multisig, or controlled by another party): Submit [registration request](https://chain.link/ccip-contact?v=Tokens:%20Token%20admin%20registration) for assisted initialization
- **Self-deployed**: Deploy standard pool programs yourself, then initialize pool from your deployed program
- **Custom**: Implement and deploy custom pool following [technical requirements](/ccip/concepts/cross-chain-token/svm/token-pools#custom-token-pools), then initialize pool from your deployed program
### Step 3: Create Associated Token Account
- Create the [Associated Token Account (ATA)](https://www.solana-program.com/docs/associated-token-account) for the Pool Signer PDA
- **Required**: Pool operations cannot begin without this ATA
- The ATA will hold tokens for LockRelease pools or serve as the burn account for BurnMint pools
### Step 4: Transfer Mint Authority (BurnMint Pools Only)
Choose one of two approaches:
- **Direct Transfer**: Transfer `mint_authority` directly to the Pool Signer PDA (simple, pool-only minting)
- **Multisig Setup (Recommended)**: Create an M-of-N [SPL token multisig](https://spl.solana.com/token#multisig-usage) with Pool Signer PDA appearing at least M times as a signer (flexible, multiple authorized minters)
See [Mint Authority Management](/ccip/concepts/cross-chain-token/svm/token-pools#mint-authority-management) for detailed configuration options.
### Step 5: Create Address Lookup Table (ALT)
- Create an ALT containing all required accounts for your pool operations
- Must include required accounts in specific order as detailed in the [`set_pool` API reference](/ccip/api-reference/svm/v1.6.0/router#address-lookup-table-requirements)
- See the complete [ALT requirements table](/ccip/api-reference/svm/v1.6.0/router#address-lookup-table-requirements) for exact account order and derivations
### Step 6: Link Pool to Token Registry
- Call [`set_pool`](/ccip/api-reference/svm/v1.6.0/router#set_pool) instruction on the Router
- Links your pool's ALT to your token in the TokenAdminRegistry
- Enables CCIP to route cross-chain transfers to your pool
### Step 7: Configure Pool Settings
- Complete [remote chain configuration](/ccip/concepts/cross-chain-token/svm/registration-administration#remote-chain-configuration)
- Set up [rate limits and security settings](/ccip/concepts/cross-chain-token/svm/registration-administration#rate-limits-and-security)
- **For LockRelease pools**: Configure [liquidity management](/ccip/concepts/cross-chain-token/svm/registration-administration#liquidity-management-lockrelease-pools-only)
## Next Steps
After determining your integration path:
1. **Review Detailed Documentation**:
- [Architecture](/ccip/concepts/cross-chain-token/svm/architecture): Understand component interactions
- [Tokens](/ccip/concepts/cross-chain-token/svm/tokens): Token requirements and compatibility
- [Token Pools](/ccip/concepts/cross-chain-token/svm/token-pools): Pool implementation details
- [Registration & Administration](/ccip/concepts/cross-chain-token/svm/registration-administration): Complete registration and configuration procedures
2. **For Assisted Paths**: Submit your [registration request](https://chain.link/ccip-contact?v=Tokens:%20Token%20admin%20registration) with detailed information about your token and requirements
3. **For Self-Service Paths**: Begin with token registration following the appropriate workflow for your chosen deployment approach
---
# Cross-Chain Token Standard - Registration & Administration (SVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/svm/registration-administration
Last Updated: 2025-05-19
When token administrators want to register their SPL token for Cross‐Chain Token (CCT) operations, they interact with both the Router program and Token Pool programs. This guide covers both token registration/administration (Router side) and token pool configuration (Pool side).
## Token Registration & Administration (Router Side)
This section covers all operations performed through the Router program to manage token administrator roles and basic token configuration.
### Router Access Control for Token Administration
Understanding who can call which Router instructions is critical for secure token administration. This table shows the authorization model for all token admin registry instructions:
| Instruction | Mint Authority | Router Upgrade Authority | Current Token Admin | Pending Token Admin |
| ---------------------------------------------------------------------------------------------------------------------------- | :------------: | :----------------------: | :-----------------: | :-----------------: |
| [`owner_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_propose_administrator) | ✅ | ❌ | ❌ | ❌ |
| [`owner_override_pending_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_override_pending_administrator) | ✅ | ❌ | ❌ | ❌ |
| [`ccip_admin_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#ccip_admin_propose_administrator) | ❌ | ✅ | ❌ | ❌ |
| [`accept_admin_role_token_admin_registry`](/ccip/api-reference/svm/v1.6.0/router#accept_admin_role_token_admin_registry) | ❌ | ❌ | ❌ | ✅ |
| [`transfer_admin_role_token_admin_registry`](/ccip/api-reference/svm/v1.6.0/router#transfer_admin_role_token_admin_registry) | ❌ | ❌ | ✅ | ❌ |
| [`set_pool`](/ccip/api-reference/svm/v1.6.0/router#set_pool) | ❌ | ❌ | ✅ | ❌ |
**Notes:**
- **Mint Authority**: The SPL token's `mint_authority` holder
- **Router Upgrade Authority**: The Router program's upgrade authority
- **Current Token Admin**: The active CCIP [token administrator](/ccip/concepts/cross-chain-token/overview#token-administrator)
- **Pending Token Admin**: [Token administrator](/ccip/concepts/cross-chain-token/overview#token-administrator) proposed but not yet accepted
**Focus on Self-Service Registration:** The following sections focus on the **self-service registration flow** where you control your token's mint authority. While the Router also supports `ccip_admin_propose_administrator` for cases where mint authority is not accessible, we detail the self-service approach as it's the primary registration method for most token developers.
### Self-Service Registration Flow
If your SPL token supports an automatic way to determine an admin using onchain verifiable data (e.g., you hold the mint authority), you can complete self-registration in a permissionless manner by calling the Router's [`owner_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_propose_administrator) instruction:
1. **Admin Initiates Registration:** The token admin calls the Router's [`owner_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_propose_administrator) instruction, passing in the mint and the proposed administrator's public key. This instruction initializes a `TokenAdminRegistry` PDA for this mint.
2. **Determine Administrator:** The Router ensures that the caller is the SPL token's mint authority, confirming they have the right to set a CCIP admin.
3. **Propose Administrator:** The Router sets the proposed administrator as the pending administrator. The token admin is not fully recognized yet; they remain `pending`. The `TokenAdminRegistry` now stores this user as `pending_administrator`. The next step is to accept the role.
**Self-Service Registration Requirements:**
- **Caller Authorization**: Only the SPL token's `mint_authority` can call [`owner_propose_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_propose_administrator)
- **Registry State**: TokenAdminRegistry PDA must not already exist for this mint
- **Result**: Creates new TokenAdminRegistry PDA with proposed administrator in pending state
### Non-Self-Service Registration Flow
For token programs that do not have a standard way to identify an administrator using onchain verifiable data (e.g., no direct mint authority check), the token developer can manually initiate the registration by submitting a request above.
### Administrator Role Management
#### Proposing the Administrator
The following sequence diagram illustrates the process of proposing the administrator.
#### Overriding Pending Administrator
If you need to change the pending administrator before they accept the role, you can use the [`owner_override_pending_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_override_pending_administrator) instruction. This is useful when you initially proposed the wrong administrator or need to update the pending administrator for other reasons.
1. **Override Pending Administrator:** The mint authority calls the Router's [`owner_override_pending_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_override_pending_administrator) instruction, passing in the mint and the new proposed administrator's public key.
2. **Validate Authority:** The Router ensures that the caller is the SPL token's mint authority and that no administrator has been accepted yet (the `administrator` field must be zero).
3. **Update Pending Administrator:** The Router replaces the existing `pending_administrator` with the new proposed administrator. The previous pending administrator can no longer accept the role.
**Override Requirements:**
- **Caller Authorization**: Only the SPL token's `mint_authority` can call [`owner_override_pending_administrator`](/ccip/api-reference/svm/v1.6.0/router#owner_override_pending_administrator)
- **Registry State**: TokenAdminRegistry PDA must exist but have no accepted administrator yet (`administrator` field is zero)
- **Result**: Updates the `pending_administrator` field with the new proposed administrator
#### Accepting the Administrator Role
Once the administrator has been proposed and is pending, they must accept the role to complete the registration process. This step finalizes the administrator's assignment.
1. **Pending Administrator Calls [`accept_admin_role_token_admin_registry`](/ccip/api-reference/svm/v1.6.0/router#accept_admin_role_token_admin_registry)**
The pending admin invokes [`accept_admin_role_token_admin_registry()`](/ccip/api-reference/svm/v1.6.0/router#accept_admin_role_token_admin_registry) on the Router program, specifying the mint account in the transaction context.
2. **Finalize Registration**
- The Router checks that the caller's public key matches the `pending_administrator` field in the `TokenAdminRegistry` PDA.
- If authorized, the Router sets `administrator = pending_administrator` and clears `pending_administrator`. At this point, the role is fully active.
Below is the sequence diagram illustrating how the pending administrator interacts with the Router program to complete registration.
#### Transfer Administrator Role
The [`transfer_admin_role_token_admin_registry`](/ccip/api-reference/svm/v1.6.0/router#transfer_admin_role_token_admin_registry) instruction allows the current token administrator to transfer their role to a new administrator. This transfer process is secure and involves two steps, requiring the new admin to accept it explicitly before finalization.
1. **Initiate Role Transfer**
- The current admin calls [`transfer_admin_role_token_admin_registry(new_admin)`](/ccip/api-reference/svm/v1.6.0/router#transfer_admin_role_token_admin_registry) on the Router, passing the new administrator's public key.
- The Router checks that the caller is the token's existing administrator. If so, it sets `pending_administrator = new_admin` in the `TokenAdminRegistry` (PDA).
2. **Pending Administrator**
- The registry is now pending. The existing admin will still be active until the new admin accepts.
3. **Accept the Role**
- The new administrator must call [`accept_admin_role_token_admin_registry()`](/ccip/api-reference/svm/v1.6.0/router#accept_admin_role_token_admin_registry) to finalize the transfer.
- If `authority.key()` matches `pending_administrator`, the Router updates `administrator = pending_administrator`. Otherwise, it will fail.
- Once accepted, the new administrator can set or modify the token pool.
Below is a sequence diagram showing how the transfer is requested, followed by how the new admin must accept to complete the handover.
### Setting the Token Pool
On SVM-based blockchains (e.g., Solana), the [`set_pool`](/ccip/api-reference/svm/v1.6.0/router#set_pool) instruction enables a token administrator to map a given mint to the pool lookup table that defines how that token is handled in cross-chain transfers. This instruction modifies the `TokenAdminRegistry` PDA so the Route program knows which token pool accounts can lock or burn tokens on the source chain (and release or mint them on the destination).
1. **Set Token Pool:** The current administrator calls the [`set_pool`](/ccip/api-reference/svm/v1.6.0/router#set_pool) instruction on the Router program, passing in:
- The token mint (for which we are configuring cross-chain transfers).
- The `pool_lookuptable` account (the Address Lookup Table containing the mandatory PDAs and program addresses for that token pool).
- A list of "writable indexes" indicating which lookup table entries should be marked as writable during cross-chain transfers. These indexes specify positions in the ALT that require write permission for successful transaction execution.
2. **Reset Old Pool, Apply New Settings:** Under the hood, the Router updates the `TokenAdminRegistry` PDA:
- Overwrites any previously stored Address Lookup Table reference.
- Resets the old permission bits, then enables the specified "writable indexes."
- This ensures the new token pool has the correct set of PDAs with the correct writable permissions.
3. **Validate or Delist:**
- If the new `pool_lookuptable` is not the zero address, the Router checks that this lookup table has at least the minimal set of addresses required for cross-chain transfers. For the complete list of required accounts and their exact order, see the [`set_pool` ALT requirements](/ccip/api-reference/svm/v1.6.0/router#address-lookup-table-requirements). If valid, the token becomes enabled for cross-chain transfers.
- If the `pool_lookuptable` is the zero address, the token is effectively delisted from CCIP, meaning no new cross-chain transfers can occur.
The sequence diagram below explains how the [`set_pool`](/ccip/api-reference/svm/v1.6.0/router#set_pool) instruction updates the `TokenAdminRegistry` PDA and either enables or delists the token for cross-chain transfers.
## Token Pool Configuration (Pool Side)
Once you have registered your token and set the token pool via the Router, you need to configure the specific pool parameters. These operations are performed directly on the Token Pool programs, not the Router.
### Pool Initialization
Before any other configuration can occur, you must initialize your token pool:
1. **Initialize Token Pool**
- **Instruction:** [`initialize`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#initialize)
- **Use Case:** Create the pool state PDA for your specific mint. This is the first mandatory step that must be completed before any other pool configuration operations.
- **Authorization:** Program upgrade authority can always initialize pools. When self-serve is enabled (`self_served_allowed: true`), the token's mint authority can also initialize pools.
- **Requirements:** Pool owner must create the [Associated Token Account (ATA)](https://www.solana-program.com/docs/associated-token-account) for the Pool Signer PDA before pool operations can begin.
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#initialize) to learn more.
### Remote Chain Configuration
Configure your token pool for specific destination blockchains:
1. **Initialize a Remote Configuration**
- **Instruction:** [`init_chain_remote_config`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#init_chain_remote_config)
- **Use Case:** Create a new onchain account (PDA derived from the chain selector and token pool program ID) holding configuration details for a specific remote blockchain (e.g., remote token address).
- **Authorization:** Pool Owner only
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#init_chain_remote_config) to learn more.
2. **Edit an Existing Remote Configuration**
- **Instruction:** [`edit_chain_remote_config`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#edit_chain_remote_config)
- **Use Case:** Update the entire stored configuration for a remote chain (`RemoteConfig`), including the remote token address and decimals.
- **Authorization:** Pool Owner only
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#edit_chain_remote_config) to learn more.
3. **Add Remote Pool Address**
- **Instruction:** [`append_remote_pool_addresses`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#append_remote_pool_addresses)
- **Use Case:** Register one or more remote pool addresses for the same remote chain. This can happen when you deploy a new pool version on that blockchain but keep the old address functional until all in-flight messages are processed.
- **Authorization:** Pool Owner only
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#append_remote_pool_addresses) to learn more.
4. **Remove Remote Pool Address**
- **Instruction:** [`edit_chain_remote_config`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#edit_chain_remote_config)
- **Use Case:** Remove one or more remote pool addresses from an existing remote chain configuration. Call this instruction with a new `RemoteConfig` that excludes the addresses you want to remove.
- **Authorization:** Pool Owner only
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#edit_chain_remote_config) to learn more.
5. **Remove Chain Config**
- **Instruction:** [`delete_chain_config`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#delete_chain_config)
- **Use Case:** Remove the remote blockchain configuration PDA to stop supporting it permanently.
- **Authorization:** Pool Owner only
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#delete_chain_config) to learn more.
### Rate Limits and Security
Configure transfer limits and access controls for your token pool:
1. **Configure Rate Limits**
- **Instruction:** [`set_chain_rate_limit`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#set_chain_rate_limit)
- **Use Case:** Apply or modify the remote blockchain's inbound/outbound rate-limit configuration. This uses a token bucket algorithm in which you can configure the capacity (maximum tokens) and rate (tokens per second refill rate) for both inbound and outbound transfers.
- **Authorization:** Pool Owner only
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#set_chain_rate_limit) to learn more.
2. **Optional Allowlist**
- **Instructions:** [`configure_allow_list`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#configure_allow_list), [`remove_from_allow_list`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#remove_from_allow_list)
- **Use case:** You can add or remove addresses if the pool has an allowlist. When the allowlist is enabled, only addresses that appear on it can initiate cross‐chain transfers. This provides an additional layer of access control for who can initiate cross-chain token transfers.
- **Authorization:** Pool Owner only
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#configure_allow_list) to learn more.
For detailed authorization requirements, see the access control tables for [BurnMint pools](/ccip/concepts/cross-chain-token/svm/token-pools#access-control) and [LockRelease pools](/ccip/concepts/cross-chain-token/svm/token-pools#access-control-1).
### Liquidity Management (Lock/Release Pools Only)
Configure liquidity providers and settings for Lock/Release token pools:
#### Liquidity Configuration
1. **Set Rebalancer**
- **Instruction:** [`set_rebalancer`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#set_rebalancer)
- **Use Case:** Configure which public key is authorized to add or withdraw liquidity from the pool.
- **Authorization:** Pool Owner only
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#set_rebalancer) to learn more.
2. **Configure Liquidity Acceptance**
- **Instruction:** [`set_can_accept_liquidity`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#set_can_accept_liquidity)
- **Use Case:** Enable or disable whether the pool can accept incoming liquidity via the [`provide_liquidity`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#provide_liquidity) instruction.
- **Authorization:** Pool Owner only
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#set_can_accept_liquidity) to learn more.
#### Liquidity Operations
1. **Provide Liquidity**
- **Instruction:** [`provide_liquidity`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#provide_liquidity)
- **Use Case:** Add tokens to the pool's reserves to enable cross-chain releases. The rebalancer transfers tokens from their account to the pool's Associated Token Account.
- **Authorization:** Rebalancer only
- **Requirements:** Pool must have `can_accept_liquidity` enabled
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#provide_liquidity) to learn more.
2. **Withdraw Liquidity**
- **Instruction:** [`withdraw_liquidity`](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#withdraw_liquidity)
- **Use Case:** Remove tokens from the pool's reserves. Can be used to transfer liquidity between pools by setting the destination as another pool's rebalancer.
- **Authorization:** Rebalancer only
- **Requirements:** Pool must have `can_accept_liquidity` enabled
- Read the [API reference](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool#withdraw_liquidity) to learn more.
**Note:** Lock/Release token pools require active liquidity management to ensure sufficient tokens are available for releases. Insufficient liquidity will cause cross-chain transfer failures. For complete authorization details, see the [LockRelease Access Control table](/ccip/concepts/cross-chain-token/svm/token-pools#access-control-1).
---
# Cross-Chain Token Standard - Upgradability (SVM)
Source: https://docs.chain.link/ccip/concepts/cross-chain-token/svm/upgradability
Last Updated: 2025-05-19
Token pool upgradability on SVM-based blockchains (e.g., Solana) depends entirely on which [deployment approach](/ccip/concepts/cross-chain-token/svm/token-pools#deployment-approaches) you choose. Your upgrade responsibilities and capabilities vary significantly based on whether you use self-serve mode, deploy standard pools yourself, or build custom pools.
## Upgrade Approaches by Deployment Type
Your upgrade experience depends entirely on which deployment approach you chose when setting up your token pool:
### Approach 1: Self-Serve Mode (Recommended - No Action Required)
**How it works:**
This approach follows the same pattern as **SPL Token programs** on Solana: you create and manage your own token pools (like creating your own mints), but the underlying token pool programs are maintained by the program authority through established governance processes.
**What it means for upgrades:**
- **Governance-controlled upgrades**: The standard BurnMint and LockRelease programs are upgraded through CCIP's [governance process](/ccip/concepts/architecture/onchain/svm/upgradability) involving multisig approvals, timelock reviews, and node operator oversight
- **Seamless updates**: Security fixes and feature updates are deployed through the governance process without breaking your pool configuration or mint authority
- **No action required**: You don't manage program upgrades, similar to how you don't upgrade the SPL Token program when using standard Solana tokens
- **Preserved functionality**: Updates happen at the program level while your individual pool state and configuration remain intact
**Your responsibilities:**
- **None** - program upgrades are handled through CCIP governance
- Monitor CCIP announcements for new features or breaking changes
- Test your integration after major protocol updates
**Benefits:**
- Always up-to-date with latest security patches through proven governance
- No operational overhead for program upgrade management
- Professional maintenance through decentralized governance
- Immediate access to new CCIP features
### Approach 2: Self-Deployed Standard Pools (User-Controlled Upgrades)
**What it means for upgrades:**
- **You control the upgrade authority** for your deployed standard pool programs
- **Manual upgrades**: You decide when to apply updates from the Chainlink repository
- **Full control**: You can customize timing, test extensively, and coordinate with your governance
**Your responsibilities:**
- Monitor Chainlink repository for new releases
- Test upgrades in development environments
- Execute upgrade transactions using Solana CLI or Anchor tooling
- Coordinate upgrades with your governance/operational processes
### Approach 3: Custom Token Pools (User-Controlled Upgrades)
**What it means for upgrades:**
- **You control the upgrade authority** for your custom pool program
- **Custom development**: You develop your own upgrades and improvements
- **Complete responsibility**: All upgrade planning, development, and execution is your responsibility
**Your responsibilities:**
- Develop custom upgrade code
- Ensure CCIP compatibility after upgrades
- Test thoroughly with CCIP Router integration
- Execute upgrade transactions
- Monitor for breaking changes in CCIP protocol updates
## In-Place Upgrades (Approaches 2 & 3 Only)
When you control the upgrade authority, in-place upgrades provide significant benefits:
### Benefits of In-Place Upgrades
1. **Retain Mint Authority (Burn and Mint Pools):** SPL tokens have a single `mint_authority`. When you delegate this authority to a Burn and Mint token pool program, that program holds the exclusive rights to mint tokens. As detailed in the [Mint Authority Management](/ccip/concepts/cross-chain-token/svm/token-pools#mint-authority-management) section, in-place upgrades allow you to:
- **Maintain Control:** Upgrade the pool code without reassigning the mint authority, which avoids the complexity of modifying the multisig membership or reconfiguring authorities.
- **Reduce Overhead:** Avoid manual reassignments, such as adding a new pool to the multisig and removing the old one, thereby simplifying maintenance and reducing the risk of errors.
2. **Preserve User Workflow (All Pools):** On SVM-based blockchains, instructions require specifying all involved accounts, including the token pool program ID. If you redeploy your pool to a new Program ID, users and integrators must update their transaction code to reference it. In-place upgrades keep the original ID so that existing references and account parameters remain valid.
## Best Practices for User-Controlled Upgrades
### Governance and Security
1. **Secure Upgrade Authority Management**
- Configure the upgrade authority to a trusted multisig or governance program
- Never use single-key control for production pools
- Implement time delays and approval processes for critical upgrades
2. **Plan Governance Processes**
- Establish clear governance rules for when and how upgrades are approved
- Consider community input for significant changes
- Document upgrade procedures and emergency response plans
### Development and Testing
3. **Comprehensive Testing**
- Develop and validate pool updates in test environments first
- Test integration with CCIP Router and cross-chain operations
- Verify all existing functionality works after upgrade
- Test edge cases and error conditions
4. **Backward Compatibility**
- Design upgrades to maintain compatibility with existing state accounts and PDAs
- Ensure existing user configurations remain valid
- Plan migration strategies for breaking changes
### Execution
5. **Upgrade Execution**
- Use Solana CLI or Anchor tooling to execute upgrade transactions
- Perform upgrades during low-traffic periods when possible
- Have rollback plans ready in case of issues
- Monitor system behavior immediately after upgrades
6. **Prefer Upgrades Over Redeployment**
- In-place upgrades preserve all existing references and authorities
- Redeployment forces re-registration, mint authority reassignment, and user workflow updates
- Only redeploy when absolutely necessary (e.g., fundamental architecture changes)
---
# CCIP Manual Execution
Source: https://docs.chain.link/ccip/concepts/manual-execution
Last Updated: 2025-07-21
In general, messages are successfully delivered and processed via CCIP as described in the [Architecture](/ccip/concepts/architecture) page. However, some exceptional conditions might require users to manually execute the transaction on the destination blockchain:
- The receiver contract on the destination blockchain reverted due to an unhandled exception such as a logical error.
- For token pools, if the combined execution of the required functions (`balanceOf` checks and `releaseOrMint`) exceeds the default gas limit of **90,000 gas** on the destination blockchain, CCIP execution will fail. Read the Token pools [common requirements](/ccip/concepts/cross-chain-token/evm/token-pools#common-requirements) to learn more.
- The receiver contract on the destination blockchain reverted due to the gas limit being insufficient to execute the triggered function (Note: The gas limit value is set in the [extraArgs](/ccip/api-reference/evm/v1.6.2/client#genericextraargsv2) param of the message).
The flowchart below displays the process of a cross-chain transaction, detailing the steps involved in the manual execution:
## CCIP execution
1. A sender contract or EOA initiates a CCIP message on the source blockchain.
2. [CCIP Committing DON](/ccip/concepts/architecture/offchain/overview#commit-ocr-process) awaits [finality](/ccip/concepts/architecture/key-concepts#blockchain-finality) on the source blockchain.
3. Post finality, the [CCIP Committing DON](/ccip/concepts/architecture/offchain/overview#commit-ocr-process) assembles a batch of eligible transactions, computes a Merkle root, and records it to the OffRamp contract on the destination blockchain.
4. After successful verification, the [Risk Management Network](/ccip/concepts/architecture/offchain/risk-management-network) blesses the committed Merkle root.
5. After the committed Merkle root is blessed, the [CCIP Executing DON](/ccip/concepts/architecture/offchain/overview#executing-ocr-process) proceeds with the execution on the destination blockchain.
6. The execution on the destination blockchain works as follows:
1. If the message involves token transfers, the tokens are first transferred to the receiver. **Important:** If the combined execution of the required functions (`balanceOf` checks of the token contract and `releaseOrMint` of the token pool contract) exceeds the default gas limit of **90,000 gas** on the destination blockchain, CCIP execution will fail, and the transaction will become eligible for manual execution. It is highly recommended to design your token pools to stay within this gas limit to avoid execution failure. Read the Token Pools [common requirements](/ccip/concepts/cross-chain-token/evm/token-pools#common-requirements) to learn more.
- If the receiver is an EOA, then this transaction is considered complete with no further processing.
- If the receiver is a smart contract, the [ccipReceive](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive) function is invoked after the token transfer. The ccipReceive function processes the CCIP message and any user-specified logic in the receiver contract. The execution of the CCIP message is atomic (all or none). If the ccipReceive function successfully executes, then all aspects of the transaction are complete, and there is no revert. If, however, there is an issue in the receiver execution due to insufficient gas limit or unhandled exceptions, the attempted token transfer will also revert. The transaction then becomes eligible for manual execution.
2. If the message does not involve token transfers, only arbitrary messaging, and the receiver execution fails due to gas limits or unhandled exceptions, the transaction becomes eligible for manual execution.
## Manual execution
As described above, a CCIP message becomes eligible for manual execution for various reasons. Manual execution means that a user has to manually trigger the execution of the destination transaction after the issue that caused manual execution has been resolved.
When a CCIP message is eligible for manual execution, the [CCIP explorer](https://ccip.chain.link/) shows the following information:
- *Ready for manual execution* status
- The possibility to override the gas limit and a *Trigger Manual Execution* button
Depending on the situation, you can take one of the following steps:
- Insufficient gas limit: The executor can connect their wallet, override the gas limit parameter, increase the gas limit for this particular execution, and trigger a manual execution. If this new gas limit override is sufficient, the transaction will go through successfully. **Note:** This gas limit override applies only to this manually executed transaction.
- Unhandled exception (logical error) in the receiver contract: If the receiver contract is [upgradeable](https://blog.chain.link/upgradable-smart-contracts/), developers must correct the logic, re-deploy the logic contract, and then manually execute the same transaction. If the receiver contract is not upgradeable, developers must deploy a new receiver contract, and then users can send a new CCIP message. Non-upgradable contracts will not benefit from manual execution. **Note:** Always make sure to test your smart contracts thoroughly. As a best practice, implement fallback mechanisms in the CCIP receiver contracts to manage unhandled exceptions gracefully. Read the [Defensive example](/ccip/tutorials/evm/programmable-token-transfers-defensive) to learn more.
When manual execution is initiated, a Merkle proof is generated for the message to be executed. During execution, the CCIP explorer submits the Merkle proof and the new gas limit (if the initial failure was due to a low gas limit). This Merkle proof is verified by the [OffRamp contract](/ccip/concepts/architecture/onchain/evm/components#offramp) against the Merkle root in the OffRamp contract, and that was blessed by the [Risk Management Network](/ccip/concepts/architecture/offchain/risk-management-network). This mirrors the automated execution performed by the [CCIP Executing DON](/ccip/concepts/architecture/offchain/overview#executing-ocr-process), with the addition that the execution is resubmitted using the gas limit you provide.
## Frequently asked questions
1. **Can anyone execute a transaction on the CCIP explorer even if they are not the initiator of the transaction?**
Yes, any EOA can manually execute a CCIP message that is eligible for manual execution. However, the executing account must have sufficient native gas tokens (such as ETH on Ethereum or POL on Polygon) to cover the gas costs associated with the delivery of the CCIP message.
2. **If a user sends multiple messages and the first message isn't successfully delivered and goes into a *manual execution* mode, does that mean all subsequent messages from the user will also be stuck?**
It depends. If a message goes into manual execution mode due to receiver errors (unhandled exceptions or gas limit issues), subsequent messages don't get automatically blocked, unless they would encounter the same error. However, suppose a message goes into manual execution mode after the Smart Execution time window expires (currently 8 hours). In that case, subsequent messages must wait for the first message to be processed to maintain the default sequence.
3. **If the maximum gas limit is 3M (3,000,000) on mainnet, but it turns out that the destination blockchain requires more than that, will an override of > 3M still work?**
Yes, but only for this execution. This works because the gas limit for this execution instance isn't passing through the CCIP validation for the gas limit, for which the CCIP executing DON pays the gas. However, if you consistently need more than 3M for your use case, please reach out to us via this [contact form](https://chain.link/ccip-contact).
4. What should I do if my token pool's gas consumption exceeds the 90,000 gas limit on the destination blockchain?
If your token pool's combined execution—including the `balanceOf` function calls before and after minting/releasing, and the `releaseOrMint` function—consumes more than **90,000 gas** on the destination blockchain, CCIP execution will fail. It's highly recommended to optimize your token and token pool contracts to stay within this limit. However, if you cannot optimize further and consistently require more gas, please [contact](https://chain.link/ccip-contact?v=Tokens:%20Gas%20limit%20update) Chainlink Labs to request assistance.
1. **Will Chainlink Labs reimburse us for manual execution fees?**
Since most manual execution situations are due to insufficient gas limit or an unhandled exception in the receiver contract, Chainlink Labs does not reimburse these fees. If you are a dApp developer, please ensure you test thoroughly to avoid manual executions to the extent possible.
2. **Do I have to manually execute via the CCIP explorer? Are there any other ways to do this?**
The CCIP explorer provides the easiest way to execute manually. It handles all the complexity of submitting the Merkle proof needed for successful transaction execution.
3. **How do I know if my receiver error is due to a gas limit issue or an unhandled exception?**
If you see a *ReceiverError* with a revert reason as empty (0x), this is likely due to a gas limit issue. You can look at the transaction trace for the destination transaction on a tool such as [Tenderly](https://tenderly.co/), and you will likely see an *out of gas* reason mentioned in such cases. Determine the gas limit that would work for your transaction and manually override it. Read the [manual execution](/ccip/tutorials/evm/manual-execution) tutorial to analyze an example of an exception due to a low gas limit.
4. **How can I write contracts that avoid manual execution situations in the first place?**
- Test thoroughly to ensure logical conditions for all paths are gracefully handled in your receiver contract.
- Set a gas limit that works for complex execution paths, not just the simplest ones. Read the [best practices](/ccip/concepts/best-practices/evm#setting-gaslimit) for gas estimation.
- Refer to the [Defensive example](/ccip/tutorials/evm/programmable-token-transfers-defensive) tutorial for an example of how to design a programmable token transfer that handles errors gracefully.
5. **My transaction meets one of the conditions to trigger manual execution, but I do not see this option on the CCIP explorer. How am I supposed to execute this manually?**
This should not happen, but in the unlikely scenario that it does, please submit a support ticket as shown below. Include the CCIP Message ID, your preferred contact details, and a detailed description of the issue you faced. This will help us assist you more effectively.

---
# CCIP Best Practices
Source: https://docs.chain.link/ccip/concepts/best-practices
Last Updated: 2025-05-19
This section outlines recommended practices for using Chainlink CCIP effectively and securely.
- **[EVM Best Practices](/ccip/concepts/best-practices/evm)**: Recommended guidelines for interacting with CCIP on EVM-compatible chains.
- **[SVM Best Practices](/ccip/concepts/best-practices/svm)**: Recommended guidelines for interacting with CCIP on SVM-based chains like Solana.
- **[Aptos Best Practices](/ccip/concepts/best-practices/aptos)**: Recommended guidelines for interacting with CCIP on Aptos chain.
---
# CCIP Best Practices (EVM)
Source: https://docs.chain.link/ccip/concepts/best-practices/evm
Last Updated: 2025-05-19
Before you deploy your cross-chain dApps to mainnet, make sure that your dApps follow the best practices in this document. You are responsible for thoroughly reviewing your code and applying best practices to ensure that your cross-chain dApps are secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, [contact the Chainlink Labs Team](https://chain.link/ccip-contact) before deploying your application to mainnet.
## Verify destination chain
Before calling the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend), ensure that your code allows users to send CCIP messages to trusted destination chains.
**Example**: For an example of how to verify the destination chain, refer to the [Transfer Tokens with Data - Defensive](/ccip/tutorials/evm/programmable-token-transfers-defensive#tutorial) example.
## Verify source chain
When implementing the `ccipReceive` [method](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive) in a contract residing on the destination chain, ensure to verify the source chain of the incoming CCIP message. This verification ensures that CCIP messages can only be received from trusted source chains.
**Example**: For an example of how to verify the source chain, refer to the [Transfer Tokens with Data - Defensive](/ccip/tutorials/evm/programmable-token-transfers-defensive#tutorial) example.
## Verify sender
When implementing the `ccipReceive` [method](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive) in a contract residing on the destination chain, it's important to validate the sender of the incoming CCIP message. This check ensures that CCIP messages are received only from trusted sender addresses.
**Note**: Depending on your use case, this verification might not always be necessary.
**Example**: For an example of how to verify the sender of the incoming CCIP message, refer to the [Transfer Tokens with Data - Defensive](/ccip/tutorials/evm/programmable-token-transfers-defensive#tutorial) example.
## Verify router addresses
When you implement the `ccipReceive` [method](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive) in the contract residing on the destination chain, validate that the `msg.sender` is the correct router address. This verification ensures that only the router contract can call the `ccipReceive` function on the receiver contract and is for developers that want to restrict which accounts are allowed to call `ccipReceive`.
**Example**: For an example of how to verify the router, refer to the [Transfer Tokens with Data - Defensive](/ccip/tutorials/evm/programmable-token-transfers-defensive#tutorial) example.
## Using `extraArgs`
The purpose of [`extraArgs`](/ccip/api-reference/evm/v1.6.2/client#genericextraargsv2) is to allow compatibility with future CCIP upgrades. To get this benefit, make sure that `extraArgs` is mutable in production deployments. This allows you to build it offchain and pass it in a call to a function or store it in a variable that you can update on-demand.
If `extraArgs` are left empty, a default of *200000* `gasLimit` will be set.
### Setting `gasLimit`
The `gasLimit` specifies the maximum amount of gas CCIP can consume to execute `ccipReceive()` on the contract located on the destination blockchain. It is the main factor in determining the fee to send a message. Unspent gas is not refunded.
To transfer tokens directly to an EOA as a *receiver* on the destination blockchain, the `gasLimit` should be set to `0` since there is no `ccipReceive()` implementation to call.
To estimate the accurate gas limit for your destination contract, consider the following options:
- Leveraging Ethereum client RPC by applying `eth_estimateGas` on `receiver.ccipReceive()`. You can find more information on the [Ethereum API Documentation](https://ethereum.github.io/execution-apis/api-documentation/) and [Alchemy documentation](https://docs.alchemy.com/reference/eth-estimategas).
- Conducting [Foundry gas tests](https://book.getfoundry.sh/forge/gas-tracking).
- Using [Hardhat plugin for gas tests](https://github.com/cgewecke/eth-gas-reporter).
- Using a blockchain explorer to look up the gas consumption of a particular internal transaction.
**Example**: For an example of how to estimate the gas limit, refer to the [Optimizing Gas Limit Settings in CCIP Messages](/ccip/tutorials/evm/ccipreceive-gaslimit) guide.
### Setting `allowOutOfOrderExecution`
The `allowOutOfOrderExecution` parameter enables you to control the execution order of your messages on the destination blockchain. This parameter is part of [`GenericExtraArgsV2`](/ccip/api-reference/evm/v1.6.2/client#genericextraargsv2) and is available only on lanes where the **Out of Order Execution** property is set to **Optional** or **Required**. Refer to the [CCIP Directory](/ccip/directory) to determine if your target lane supports this feature.
#### Best Practices
- **When `allowOutOfOrderExecution` is Optional:**
- You can set `allowOutOfOrderExecution` to either `true` or `false`, depending on your application's requirements.
- **`true`:** Messages can be executed in any order relative to other messages from the same sender. If a previous message has not yet been executed on the destination chain, it does not block the execution of subsequent messages.
- **`false`:** Messages are executed in order. CCIP ensures that preceding messages are processed before executing the current message.
- **When `allowOutOfOrderExecution` is Required:**
- You **must** set `allowOutOfOrderExecution` to `true`. This setting acknowledges that messages may be executed out of order. If set to `false`, the message will revert and will not be processed.
- This requirement is enforced on lanes where technical constraints necessitate out-of-order execution, such as mitigating issues related to zero-knowledge proof limitations. For more information, see the [proof overflow problem](https://community.scroll.io/t/the-proof-overflow-problem/841).
## Decoupling CCIP Message Reception and Business Logic
As a best practice, separate the reception of CCIP messages from the core business logic of the contract. Implement 'escape hatches' or fallback mechanisms to gracefully manage situations where the business logic encounters issues. To explore this concept further, refer to the [Defensive Example](/ccip/tutorials/evm/programmable-token-transfers-defensive) guide.
## Evaluate the security and reliability of the networks that you use
Although CCIP has been thoroughly reviewed and audited, inherent risks might still exist based on your use case, the blockchain networks where you deploy your contracts, and the network conditions on those blockchains.
## Review and audit your code
Before securing value with contracts that implement CCIP interfaces and routers, ensure that your code is secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, [contact the Chainlink Labs Team](https://chain.link/ccip-contact) before deploying your application to mainnet.
## Soak test your dApps
Be aware of the [Service Limits and Rate Limits for Supported Networks](/ccip/directory). Before you provide access to end users or secure value, soak test your cross-chain dApps. Ensure that your dApps can operate within these limits and operate correctly during usage spikes or unfavorable network conditions.
## Monitor your dApps
When you build applications that depend on CCIP, include monitoring and safeguards to protect against the negative impact of extreme market events, possible malicious activity on your dApp, potential delays, and outages.
Create your own monitoring alerts based on deviations from normal activity. This will notify you when potential issues occur so you can respond to them.
## Best Practices for Cross-Chain Token (CCT) Administration
When managing your tokens and token pools, it's critical to follow best practices to ensure the security and integrity of your cross-chain operations. This includes proper handling of admin roles and safeguarding against unauthorized access.
### Securely Manage Admin Roles
The **token admin** is responsible for configuring token pools across blockchains and enabling cross-chain operations. This role allows the token admin to set the token pool for a token on each supported CCIP blockchain. To ensure security, follow these guidelines:
- **Understand the Responsibilities of a Token Developer**: Review [Token Developer Responsibilities](/ccip/service-responsibility#token-developers-responsibilities).
- **Assign Admin Roles with Caution**: Only trusted EOAs or smart accounts should be assigned the **token admin** role.
- **Use Multi-Signature Smart Accounts**: For added security, consider assigning the token admin role to a **multi-signature smart account**. This ensures that multiple approvals are required for critical operations.
### Protect Against Unauthorized Admin Actions
- **Monitor Admin Activity**: Implement monitoring systems to track any actions taken by **token admins**, **token pool owners**, and **rate limit admins**. This helps detect unauthorized attempts to modify configurations or execute cross-chain transfers.
- **Limit Admin Privileges When Possible**: For example, instead of giving the full owner access to manage rate limits, consider assigning the **rate limit admin** role, which is specifically responsible for updating rate limits.
- **Leverage Smart Contract Audits**: Ensure your tokens and token pools are audited and follow secure development practices.
### Best Practices for Token Pool Owners and Rate Limit Admins
- **Token Pool Owner Responsibilities**: The **token pool owner** can enable remote chains, set remote pool addresses for a given chain selector, and configure rate limits. Ensure that this role is assigned to a trusted EOA or smart account, and monitor activity regularly.
- **Set Rate Limits Appropriately**: Ensure that you set appropriate rate limits for outbound and inbound token transfers when configuring token pools.
- \*\* Delegate Rate Limit Admin Role \*\*: The rate limit admin is an optional role that the token pool owner can assign to another trusted entity using the `setRateLimitAdmin ()` function. The rate limit admin can only manage rate limits, so this role provides a way to delegate responsibility without giving full access to the token pool configuration.
## Best Practices for Liquidity Management
Effective liquidity management is crucial for ensuring the smooth operation of token pools, especially in [**Lock and Release**](https://github.com/smartcontractkit/ccip/blob/release/contracts-ccip-1.5.1/contracts/src/v0.8/ccip/pools/LockReleaseTokenPool.sol) token pools. The most critical aspect is ensuring that the token pool has enough liquidity available when it is acting in **reception mode** (on the destination blockchain), allowing tokens to be released to the receiver. Failure to manage liquidity will result in a degraded user experience and can result in user funds being 'stuck' in transit.
### Ensure Sufficient Liquidity
When a **LockReleaseTokenPool** operates in **reception mode** (on the destination blockchain), it releases the tokens locked in the token pool. The pool ***must*** have sufficient liquidity to ensure that tokens can be released to the receiver. If the pool lacks liquidity, the release operation will fail and user funds will not be available on the destination blockchain until sufficient liquidity is replenished and manual execution is performed.
- **Best Practice**: Estimate expected volume when preparing to add and manage liquidity to ensure sustainable operations.
- **Best Practice**: Regularly monitor the liquidity available in your token pool and ensure that there is always enough liquidity to support the release of tokens to the receiver when the pool acts as the destination.
### Avoid Fragmented Liquidity with Multiple Issuing Blockchains
Using a **Lock and Unlock** mechanism across multiple issuing blockchains can lead to fragmented liquidity, making it more difficult to maintain sufficient liquidity in each pool.
- **Best Practice**: Where possible, avoid using the **Lock and Unlock** mechanism across multiple issuing blockchains. Fragmented liquidity increases operational overhead and complicates liquidity management.
### Monitor Liquidity Health and Automate Alerts
Monitoring the health of your token pool's liquidity is essential for ensuring the liveness of cross-chain transfers. Automated alerts can notify you if liquidity falls below a certain threshold, allowing you to take action before transfers fail.
- **Best Practice**: Calculate the amount of time required to deplete your token pool to various threshold levels (ie: Low: 50% [Warning], Very Low [Critical] 30%) and utilize automated alerting and pre-defined operational procedures to ensure adequate preparedness to respond by replenishing the pool before users are impacted. For this calculation, be sure to assume users will utilize max capacity transfers paired with token pool's refill rate.
### Use the `provideLiquidity` and `withdrawLiquidity` Functions Properly
In pools like the **LockReleaseTokenPool**, liquidity providers can add and remove liquidity using the [`provideLiquidity`](/ccip/api-reference/evm/v1.6.2/lock-release-token-pool#provideliquidity) and [`withdrawLiquidity`](/ccip/api-reference/evm/v1.6.2/lock-release-token-pool#withdrawliquidity) functions.
- **Best Practice**: Only trusted entities, such as a designated rebalancer, should be allowed to manage liquidity. Make sure to configure liquidity controls securely to prevent unauthorized liquidity manipulation.
### Set and Manage the Rebalancer Role
The **rebalancer** is responsible for managing the liquidity of the pool and ensuring that there is always sufficient liquidity when needed. They can rebalance liquidity between different pools or pool versions if necessary.
- **Best Practice**: Assign the rebalancer role to a trusted entity and ensure they understand the responsibilities, such as maintaining liquidity in the pool to support token releases.
## Multi-Signature Contracts
Multi-signature contracts, such as [Safe Smart Accounts](https://github.com/safe-global/safe-smart-account), enhance security by requiring multiple signatures to authorize transactions.
### Threshold configuration
Set an optimal threshold for signers based on the trust level of participants and the required security.
### Role-based access control
Assign roles with specific permissions to different signers, limiting access to critical operations to trusted individuals.
### Hardware wallet integration
Use hardware-backed keys for signers to safeguard private keys from online vulnerabilities. Ensure that these devices are secure and regularly updated.
### Regular audits and updates
Conduct periodic audits of signer access and contract settings. Update the multisig setup as necessary, especially when personnel changes occur.
### Emergency recovery plans
Implement procedures for recovering from lost keys or compromised accounts, such as a predefined recovery multisig or recovery key holders.
### Transaction review process
Establish a standard process for reviewing and approving transactions, which can include a waiting period for large transfers to mitigate risks and verifying data on a hardware wallet before signing to protect against front-end compromises.
### Security tooling
Tools such as [Tenderly](https://tenderly.co/) or [Hypernative](https://www.hypernative.io/) can provide additional layers of security related to transaction simulation, risk monitoring, and alerting.
### Documentation and training
Maintain thorough documentation of multisig operations and provide training for all signers to ensure familiarity with processes and security protocols.
## Chain-Specific Considerations
### Hedera Fee Decimal Handling
When using Chainlink CCIP with Hedera, you must be aware of a critical difference in decimal handling. Hedera's native HBAR token and wrapped WHBAR both use 8 decimals, while most EVM chains use 18 decimals for their native tokens.
#### Impact on CCIP Fee Calculation
When interacting with Hedera using HBAR or WHBAR as fee tokens:
1. HBAR and WHBAR natively use 8 decimals
2. Hedera's JSON-RPC conversion layer expects a value with 18 decimals for `msg.value` when sending transactions
*Source: [Hedera Documentation on HBAR Decimal Places](https://docs.hedera.com/hedera/sdks-and-apis/sdks/hbars#hbar-decimal-places)*
#### Required Fee Scaling
For off-chain applications (like frontends or scripts) that:
1. Call `getFee()` to determine the fee amount
2. Then use that amount to send CCIP messages
You **must scale the fee** by multiplying it by **10 decimals**:
```
Scaled Fee = getFee() * 10^10
```
When using native HBAR as the fee token, you need to send this scaled value as `msg.value` during the `ccipSend()` call.
When using WHBAR as the fee token, users must approve (ERC20) at least this scaled amount before calling `ccipSend()` with no msg.value.
---
# CCIP Best Practices (SVM)
Source: https://docs.chain.link/ccip/concepts/best-practices/svm
Last Updated: 2025-05-19
Before you deploy your cross-chain dApps to mainnet, make sure that your dApps follow the best practices in this document. You are responsible for thoroughly reviewing your code and applying best practices to ensure that your cross-chain dApps are secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, [contact the Chainlink Labs Team](https://chain.link/ccip-contact) before deploying your application to mainnet.
## Verify destination chain
Before calling the router's `ccip_send` [instruction](/ccip/api-reference/svm/v1.6.0/router#ccip_send), ensure your code verifies that the destination chain is supported by the CCIP Router. Sending messages to unsupported chains will fail and potentially waste transaction fees.
You can programmatically verify destination chain support using Solana PDAs (Program Derived Addresses). Here below is a JavaScript example of how to verify destination chain support:
```javascript
import { Connection, PublicKey } from "@solana/web3.js"
/**
* Verifies if a destination chain is supported by the CCIP Router
*
* @param {Connection} connection - Solana connection object
* @param {string} routerProgramId - The CCIP Router program ID
* @param {BigInt} destinationChainSelector - Chain selector to verify
* @returns {Promise} - Whether the chain is supported
*/
async function isDestinationChainSupported(connection, routerProgramId, destinationChainSelector) {
// Convert chain selector to little-endian buffer (Solana standard)
const chainSelectorBuffer = Buffer.alloc(8)
chainSelectorBuffer.writeBigUInt64LE(BigInt(destinationChainSelector))
// Derive the PDA for this destination chain
// The Router stores chain state in PDAs with seed ["dest_chain_state", chainSelector]
const [destChainPda] = PublicKey.findProgramAddressSync(
[Buffer.from("dest_chain_state"), chainSelectorBuffer],
new PublicKey(routerProgramId)
)
// If the account exists, the chain is supported
const accountInfo = await connection.getAccountInfo(destChainPda)
return accountInfo !== null
}
```
## Verify source chain
When implementing the `ccip_receive` [method](/ccip/api-reference/svm/v1.6.0/receiver#ccip_receive) in a program residing on the destination chain, ensure to verify the source chain of the incoming CCIP message. This verification ensures that CCIP messages can only be received from trusted source chains.
## Verify sender
When implementing the [`ccip_receive`](/ccip/api-reference/svm/v1.6.0/receiver#ccip_receive) instruction in a program residing on the destination chain, it's important to validate the sender of the incoming CCIP message. This check ensures that CCIP messages are received only from trusted sender addresses.
**Note**: Depending on your use case, this verification might not always be necessary.
## Verify authority and allowed offramp
When you implement the [`ccip_receive`](/ccip/api-reference/svm/v1.6.0/receiver#ccip_receive) instruction in the program residing on the destination chain, validate that the `authority` account is the correct Offramp CPI signer PDA and that `allowed_offramp` is the correct PDA owned by the router program. This verification ensures that only the authorized CCIP Offramp program can call the `ccip_receive` function.
**Example in Rust**:
```rust
#[derive(Accounts)]
#[instruction(message: Any2SVMMessage)]
pub struct CcipReceive<'info> {
// Offramp CPI signer PDA must be first
#[account(
seeds = [EXTERNAL_EXECUTION_CONFIG_SEED, crate::ID.as_ref()],
bump,
seeds::program = offramp_program.key(),
)]
pub authority: Signer<'info>,
/// CHECK: Offramp program exists only to derive the allowed offramp PDA
pub offramp_program: UncheckedAccount<'info>,
/// CHECK: PDA owned by the router program verifying this is an allowed offramp
#[account(
owner = state.router @ CcipReceiverError::InvalidCaller,
seeds = [
ALLOWED_OFFRAMP,
message.source_chain_selector.to_le_bytes().as_ref(),
offramp_program.key().as_ref()
],
bump,
seeds::program = state.router,
)]
pub allowed_offramp: UncheckedAccount<'info>,
// Your state account containing the router address
#[account(seeds = [STATE_SEED], bump)]
pub state: Account<'info, ProgramState>,
// Additional accounts as needed
// ...
}
```
## Using `extra_args`
The `extra_args` parameter provides chain-specific configuration for cross-chain messaging. It controls execution parameters on the destination chain, including resource allocation and message ordering guarantees.
### Parameter Selection
When sending a CCIP message, you must select the appropriate `extra_args` structure based on your destination chain:
- `SVMExtraArgsV1`: For Solana and other SVM-based destinations
- `EVMExtraArgsV2`: For Ethereum and other EVM-based destinations
For the full parameter specification, refer to the [CCIP API Reference](/ccip/api-reference/svm/v1.6.0/messages#extra-args).
### Setting `compute_units` (SVM destinations)
The `compute_units` parameter specifies the maximum Solana compute budget (in units) that the CCIP OffRamp can use when executing the `ccip_receive()` instruction on the destination program. This parameter directly affects fee calculation since higher compute budgets require more resources.
**Best Practices:**
- **For Program Receivers**: Set sufficient compute units for your program logic execution; any unused units are not refunded.
- **For Wallet Receivers**: When transferring tokens directly to a wallet with no `ccip_receive()` implementation, set `compute_units` to `0` since no program execution is needed.
- **For Compute Unit Estimation**: Test your receiver program under varying conditions to determine optimal values. Consider:
- Message size and complexity
- Token transfer operations
- Program execution paths
- Additional accounts referenced
### Setting `accountIsWritableBitmap`
When using the `SVMExtraArgsV1` structure, the `accountIsWritableBitmap` field specifies which additional accounts in your message should be marked as writable:
- This is a 64-bit bitmap where each bit position corresponds to an account in the `accounts` array.
- Set the corresponding bit to `1` to mark an account as writable (bit 0 for the first account, bit 1 for the second, etc.).
- Must be provided in little-endian format for Solana compatibility.
### Setting `tokenReceiver`
The `tokenReceiver` parameter in `SVMExtraArgsV1` specifies which Solana account will receive the tokens:
#### When Receiving at Wallet Addresses
When sending tokens to an end-user wallet:
- Set `tokenReceiver` to the user's wallet address (base58 encoded)
- Do NOT use an Associated Token Account (ATA) - use the wallet address directly
- The CCIP infrastructure will automatically derive the proper ATA on the recipient's behalf
```javascript
// Example: Setting tokenReceiver to a user's wallet
tokenReceiver: "EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB" // Recipient wallet
```
#### When Receiving at Program Addresses
When sending tokens to a Solana program:
- Set `tokenReceiver` to a Program Derived Address (PDA) that the program has authority over
- The PDA must be derived using seeds that the program recognizes
- The program must include logic to handle and manage the received tokens
```javascript
// Example: Setting tokenReceiver to a PDA the program controls
tokenReceiver: "57y3NXjkiAzP5Gw9WuUwzJMJbJQAHH6jUYBQfZdTE5zJ" // PDA with program authority
```
In Solana's security model, programs cannot directly control tokens unless they have authority over the token account:
1. **Program Derived Addresses (PDAs)** must be derived from the program's ID using specified seeds
2. Only the program that created the PDA can sign as that PDA
3. Without proper authority, the program cannot transfer, burn, or otherwise manipulate the tokens
#### For Data-Only Messages
When sending only data (no tokens):
- Set `tokenReceiver` to the default Solana PublicKey (`11111111111111111111111111111111`)
- This is required even though no tokens are being transferred
### Setting `allowOutOfOrderExecution`
The `allowOutOfOrderExecution` parameter controls message ordering guarantees:
- `true`: Messages may be processed out of sequence relative to other messages from the same sender
- `false`: Messages are processed in the exact sequence they were sent
## Evaluate the security and reliability of the networks that you use
Although CCIP has been thoroughly reviewed and audited, inherent risks might still exist based on your use case, the blockchain networks where you deploy your programs, and the network conditions on those blockchains.
## Review and audit your code
Before securing value with programs that implement CCIP interfaces and routers, ensure that your code is secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, [contact the Chainlink Labs Team](https://chain.link/ccip-contact) before deploying your application to mainnet.
## Soak test your dApps
Be aware of the [Service Limits and Rate Limits for Supported Networks](/ccip/directory). Before you provide access to end users or secure value, soak test your cross-chain dApps. Ensure that your dApps can operate within these limits and operate correctly during usage spikes or unfavorable network conditions.
## Monitor your dApps
When you build applications that depend on CCIP, include monitoring and safeguards to protect against the negative impact of extreme market events, possible malicious activity on your dApp, potential delays, and outages.
Create your own monitoring alerts based on deviations from normal activity. This will notify you when potential issues occur so you can respond to them.
## Multi-Signature Authorities
Multi-signature authorities enhance security by requiring multiple signatures to authorize transactions.
### Threshold configuration
Set an optimal threshold for signers based on the trust level of participants and the required security.
### Role-based access control
Assign roles with specific permissions to different signers, limiting access to critical operations to trusted individuals.
### Hardware wallet integration
Use hardware wallets for signers to safeguard private keys from online vulnerabilities. Ensure that these devices are secure and regularly updated.
### Regular audits and updates
Conduct periodic audits of signer access and authority settings. Update the multisig setup as necessary, especially when personnel changes occur.
### Emergency recovery plans
Implement procedures for recovering from lost keys or compromised accounts, such as a predefined recovery multisig or recovery key holders.
### Transaction review process
Establish a standard process for reviewing and approving transactions, which can include a waiting period for large transfers to mitigate risks.
### Documentation and training
Maintain thorough documentation of multisig operations and provide training for all signers to ensure familiarity with processes and security protocols.
---
# CCIP Best Practices (Aptos)
Source: https://docs.chain.link/ccip/concepts/best-practices/aptos
Last Updated: 2025-09-03
Before you deploy your cross-chain dApps to mainnet, make sure that your dApps follow the best practices in this document. You are responsible for thoroughly reviewing your code and applying best practices to ensure that your cross-chain dApps are secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, [contact the Chainlink Labs Team](https://chain.link/ccip-contact) before deploying your application to mainnet.
## Verify destination chain
Before calling the `router::ccip_send` entry function, your application should verify that the destination chain is supported. Sending messages to unsupported chains will fail and waste transaction fees.
**Example**: You can programmatically check for support by calling the `onramp::is_chain_supported` view function. Here is a TypeScript example:
```ts
import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"
async function isDestinationChainSupported(
aptos: Aptos,
onRampAddress: string,
destinationChainSelector: string
): Promise {
const result = await aptos.view({
payload: {
function: `${onRampAddress}::onramp::is_chain_supported`,
functionArguments: [destinationChainSelector],
},
})
return result[0] as boolean
}
```
## Verify source chain
When implementing the `ccip_receive` entry function in your custom module, you should verify the `source_chain_selector` from the incoming `Any2AptosMessage`. This ensures your module only accepts messages from blockchains you trust.
```rust
use ccip::client;
fun ccip_receive(
_proof: ProofType,
) {
let message = receiver_registry::get_receiver_input(module_address, _proof);
let source_chain = client::get_source_chain_selector(&message);
// Your allowlist logic
assert!(is_allowed_source_chain(source_chain), E_UNTRUSTED_SOURCE_CHAIN);
// ... rest of your logic
}
```
## Verify sender
Your `ccip_receive` implementation should also validate the sender address in the `Any2AptosMessage` if your application logic depends on messages coming from specific source addresses.
**Note**: This verification may not be necessary for all use cases, such as an application that accepts messages from any sender.
```rust
// Inside your ccip_receive function
let sender_bytes = client::get_sender(&message);
assert!(is_trusted_sender(sender_bytes), E_UNTRUSTED_SENDER);
```
## Using `extraArgs`
The `extra_args` parameter provides chain-specific configuration for cross-chain messaging. It controls execution parameters on the destination chain, including resource allocation and message ordering guarantees.
### Setting `gasLimit`
When sending a message from Aptos to an EVM chain, the `gasLimit` in `extraArgs` specifies the gas for the `ccipReceive` execution on the destination.
- To transfer tokens directly to an EVM wallet (EOA), set the `gasLimit` to `0` because no contract execution is required.
- To call a receiver contract on EVM, you must estimate the required gas and set an appropriate `gasLimit`. Unused gas is not refunded.
- When sending a message from Aptos to another Aptos module, the `gasLimit` can be used to allocate a specific amount of gas for your `ccip_receive` function's execution.
### Message Ordering (`allowOutOfOrderExecution` flag)
- When sending a message **from EVM to Aptos**, the `extraArgs` on the EVM side contains an `allowOutOfOrderExecution: bool` parameter.
- When sending a message **from Aptos**, the `extra_args` contains an `allow_out_of_order_execution: bool` parameter.
## Decoupling CCIP Message Reception and Business Logic
As a best practice, separate the logic for receiving a CCIP message from your core application logic. Your `ccip_receive` function should be lightweight, focusing on:
- Verifying the caller and payload.
- Storing the message data in an onchain resource.
- Emitting an event.
A separate function can then be called by a user or another process to consume the stored data and execute the main business logic. This pattern provides more control and allows for "escape hatches" to manage situations where the business logic encounters issues.
## Key Concepts for Aptos Receivers
When implementing a custom receiver module on Aptos to interact with CCIP, there are several key architectural patterns and constraints to understand.
### Why must `ccip_receive` fetch its own data payload?
The `ccip_receive` function in your module acts as a secure callback, triggered by the CCIP Off-Ramp, but it does not receive the message payload directly in its function arguments. Instead, the receiver module is responsible for actively retrieving the payload.
- **Mechanism**: When a message arrives, the CCIP protocol temporarily stores the payload (the `Any2AptosMessage` struct) within the `ReceiverRegistry` module. Your `ccip_receive` function must then call `receiver_registry::get_receiver_input` to securely fetch this data within the same transaction.
- **Rationale**: This design pattern ensures security. It confirms that only the correctly registered module at the designated receiver address can access the message payload, and only during the context of a valid CCIP execution initiated by the Off-Ramp. This prevents unauthorized access to message data.
### When must a receiver module be deployed under a Resource Account?
The choice between a **resource account** and a **user account** / **code object account** for deploying your receiver module depends entirely on whether the module will ever need to programmatically control assets.
- **Modules That Handle Tokens**: If your module will receive tokens via CCIP and later needs to transfer them, it **must** be deployed under a **resource account**. This is because a resource account allows the module to generate a signer for its own address onchain, which is required to authorize the withdrawal or transfer of those assets. Without this signer capability, any tokens the module receives would be locked.
- **Data-Only Modules**: Conversely, if your receiver module is designed **only to process arbitrary data** — for example, to update its own internal state or trigger an event—and will never hold or transfer assets, it can be deployed under a regular **user account** or **code object account** (the code object account is recommended). In this scenario, the module doesn't need to sign for any transactions on its own behalf, so the signer capability of a resource account is not necessary.
### Why must each `ccip_receive` module be deployed under a unique account?
The CCIP `ReceiverRegistry` is designed to map a single account address to a single, unique `ccip_receive` function.
- **Constraint**: When a CCIP message arrives, it targets a specific receiver address. The protocol requires a deterministic way to find and invoke the correct function. Registering multiple `ccip_receive` functions at the same address would create an ambiguity that the protocol cannot resolve.
- **Recommended Design Pattern**: While you are limited to one registered entry point per account, this does not limit your application's complexity. The recommended approach is to use your single `ccip_receive` function as a **dispatcher**.
- Your application can encode additional routing information inside the data payload of the CCIP message (e.g., using a function name or an action ID).
- Your single `ccip_receive` function then parses this data and calls the appropriate internal functions within your module to handle different logic paths.
This pattern maintains a single, secure entry point for CCIP while allowing for flexible and sophisticated application logic.
## Evaluate the security and reliability of the networks that you use
Although CCIP has been thoroughly reviewed and audited, inherent risks might still exist based on your use case, the blockchain networks where you deploy your programs, and the network conditions on those blockchains.
## Review and audit your code
Before securing value with programs that implement CCIP interfaces and routers, ensure that your code is secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, [contact the Chainlink Labs Team](https://chain.link/ccip-contact) before deploying your application to mainnet.
## Soak test your dApps
Be aware of the [Service Limits and Rate Limits for Supported Networks](/ccip/directory). Before you provide access to end users or secure value, soak test your cross-chain dApps. Ensure that your dApps can operate within these limits and operate correctly during usage spikes or unfavorable network conditions.
## Monitor your dApps
When you build applications that depend on CCIP, include monitoring and safeguards to protect against the negative impact of extreme market events, possible malicious activity on your dApp, potential delays, and outages.
Create your own monitoring alerts based on deviations from normal activity. This will notify you when potential issues occur so you can respond to them.
## Multi-Signature Authorities
Multi-signature authorities enhance security by requiring multiple signatures to authorize transactions.
### Threshold configuration
Set an optimal threshold for signers based on the trust level of participants and the required security.
### Role-based access control
Assign roles with specific permissions to different signers, limiting access to critical operations to trusted individuals.
### Hardware wallet integration
Use hardware wallets for signers to safeguard private keys from online vulnerabilities. Ensure that these devices are secure and regularly updated.
### Regular audits and updates
Conduct periodic audits of signer access and authority settings. Update the multisig setup as necessary, especially when personnel changes occur.
### Emergency recovery plans
Implement procedures for recovering from lost keys or compromised accounts, such as a predefined recovery multisig or recovery key holders.
### Transaction review process
Establish a standard process for reviewing and approving transactions, which can include a waiting period for large transfers to mitigate risks.
### Documentation and training
Maintain thorough documentation of multisig operations and provide training for all signers to ensure familiarity with processes and security protocols.
---
# CCIP Test Tokens - Faucets for EVM and Solana
Source: https://docs.chain.link/ccip/test-tokens
Last Updated: 2025-08-19
import { SVMTestTokensClient } from "@features/ccip/components/faucet"
import CcipCommon from "@features/ccip/CcipCommon.astro"
CCIP provides test tokens that you can mint on testnets for development and testing.
## Quick Start
### EVM Chains
Call the `drip` function directly on token contracts using the interface below or through block explorers:
### Solana Devnet
Use the dedicated faucet interface for CCIP-BnM tokens:
#### Amount and rate limits
- The faucet mints **1 CCIP‑BnM** per request.
- Requests are **rate‑limited to about once every 3 hours per wallet**.
#### Signature requirement
The faucet requires wallet signature verification. When you request tokens, your wallet will prompt you to sign a message.
- Message signing is free (no SOL required) and grants no spending permissions.
- After signature verification, the server executes the token mint transaction.
## About CCIP Test Tokens
CCIP supports specialized test tokens designed for cross-chain testing. These tokens are available on all CCIP-supported testnets.
| Token | Type | Availability | Description |
| ------------ | ----------- | ------------------------------------------------------------------- | -------------------------------------------------------------- |
| **CCIP-BnM** | Burn & Mint | All testnets | Burned on source chain, minted on destination chain |
| **CCIP-LnM** | Lock & Mint | Ethereum Sepolia (native) Other chains (wrapped as clCCIP-LnM) | Locked on Ethereum Sepolia, minted as wrapped tokens elsewhere |
On EVM chains, tokens are minted by calling the `drip` function directly on contracts. On Solana Devnet, CCIP-BnM tokens are available through a dedicated faucet interface.
## LINK Token Faucets
For CCIP operations, you also need LINK tokens to pay transaction fees. Use the official [Chainlink faucets](https://faucets.chain.link/) to obtain LINK tokens on supported testnets by connecting your wallet.
## Block Explorer Method (EVM Chains)
For EVM chains, you call the `drip` function directly on token contracts. You can use block explorers instead of the interface above:
```solidity
function drip(address to) external {
_mint(to, 1e18);
}
```
### General Process
Navigate to the [CCIP Directory](/ccip/directory/testnet) to find token contract addresses, open the contract on the appropriate block explorer, connect your wallet, and call the `drip` function with your wallet address.
### Example: Ethereum Sepolia CCIP-BnM
Locate the CCIP-BnM contract address in the [CCIP Directory](/ccip/directory/testnet) under [Ethereum Sepolia](/ccip/directory/testnet/chain/ethereum-testnet-sepolia). Open the [contract on Etherscan](https://sepolia.etherscan.io/address/0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05), navigate to the Contract tab, select Write Contract, and connect your wallet. Call the `drip` function with your wallet address to mint 1 CCIP-BnM token. Import the token in your wallet using this [MetaMask guide](https://support.metamask.io/hc/en-us/articles/360015489031#h_01FWH492CHY60HWPC28RW0872H).
---
# CCIP Tutorials (EVM)
Source: https://docs.chain.link/ccip/tutorials/evm
Last Updated: 2025-05-19
You can explore several comprehensive guides to learn about cross-chain interoperability using CCIP. These tutorials provide step-by-step instructions to help you understand different patterns that you can incorporate into your blockchain projects.
## Guides
- [Transfer Tokens](/ccip/tutorials/evm/transfer-tokens-from-contract)
- [Transfer Tokens with Data](/ccip/tutorials/evm/programmable-token-transfers)
- [Transfer Tokens with Data - Defensive Example](/ccip/tutorials/evm/programmable-token-transfers-defensive)
- [Cross-Chain Token (CCT)](/ccip/tutorials/evm/cross-chain-tokens)
- [Test CCIP Locally](/ccip/tutorials/evm/test-ccip-locally)
- [Offchain](/ccip/tutorials/evm/offchain)
- [Transfer Tokens between EOAs](/ccip/tutorials/evm/offchain/transfer-tokens-from-eoa)
- [Checking CCIP Message Status](/ccip/tutorials/evm/offchain/get-status-offchain)
- [Using CCIP CLI](/ccip/tutorials/evm/offchain/ccip-tools)
- [Transfer Tokens between EOAs](/ccip/tutorials/evm/offchain/ccip-tools/transfer-tokens-from-eoa)
- [Checking CCIP Message Status](/ccip/tutorials/evm/offchain/ccip-tools/get-status-offchain)
- [Get Supported Tokens](/ccip/tutorials/evm/offchain/ccip-tools/get-supported-tokens)
- [Transfer USDC with Data](/ccip/tutorials/evm/usdc)
- [Send Arbitrary Data](/ccip/tutorials/evm/send-arbitrary-data)
- [Send Arbitrary Data and Receive Transfer Confirmation: A -> B -> A](/ccip/tutorials/evm/send-arbitrary-data-receipt-acknowledgment)
- [Manual Execution](/ccip/tutorials/evm/manual-execution)
- [Optimizing Gas Limit Settings in CCIP Messages](/ccip/tutorials/evm/ccipreceive-gaslimit)
- [Acquire Test Tokens](/ccip/test-tokens)
---
# Transfer Tokens Between Chains from Smart Contracts
Source: https://docs.chain.link/ccip/tutorials/evm/transfer-tokens-from-contract
Last Updated: 2025-05-19
In this tutorial, you will use Chainlink CCIP to transfer tokens from a smart contract to an account on a different blockchain. First, you will pay for the CCIP fees on the source blockchain using LINK. Then, you will use the same contract to pay CCIP fees in native gas tokens. For example, you would use ETH on Ethereum or AVAX on Avalanche.
## Before you begin
1. You should understand how to write, compile, deploy, and fund a smart contract. If you need to brush up on the basics, read this [tutorial](/quickstarts/deploy-your-first-contract), which will guide you through using the [Solidity programming language](https://soliditylang.org/), interacting with the [MetaMask wallet](https://metamask.io) and working within the [Remix Development Environment](https://remix.ethereum.org/).
2. Your account must have some AVAX and LINK tokens on *Avalanche Fuji*. Learn how to [Acquire testnet LINK](/resources/acquire-link).
3. Check the [CCIP Directory](/ccip/directory) to confirm that the tokens you will transfer are supported for your lane. In this example, you will transfer tokens from *Avalanche Fuji* to *Ethereum Sepolia* so check the list of supported tokens [here](/ccip/directory/testnet/chain/avalanche-fuji-testnet).
4. Learn how to [acquire CCIP test tokens](/ccip/test-tokens#evm-chains). Following this guide, you should have CCIP-BnM tokens, and CCIP-BnM should appear in the list of your tokens in MetaMask.
5. Learn how to [fund your contract](/resources/fund-your-contract). This guide shows how to fund your contract in LINK, but you can use the same guide to fund your contract with any ERC20 tokens as long as they appear in the list of tokens in MetaMask.
## Tutorial
In this tutorial, you will transfer [CCIP-BnM](/ccip/test-tokens#about-ccip-test-tokens) tokens from a contract on Avalanche Fuji to an account on Ethereum Sepolia. First, you will pay [CCIP fees in LINK](#transfer-tokens-and-pay-in-link), then you will pay [CCIP fees in native gas](#transfer-tokens-and-pay-in-native). The destination account can be an [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) or a smart contract. Moreover, the example shows how to transfer CCIP-BnM tokens, but you can re-use the same example to transfer other tokens as long as they are supported for your [lane](/ccip/concepts/architecture/key-concepts#lane).
### Deploy your contracts
To use this contract:
1. [Open the contract in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/TokenTransferor.sol).
2. Compile your contract.
3. Deploy and fund your sender contract on *Avalanche Fuji*:
1. Open MetaMask and select the *Avalanche Fuji* network.
2. In Remix IDE, click *Deploy & Run Transactions* and select *Injected Provider - MetaMask* from the environment list. Remix will then interact with your MetaMask wallet to communicate with *Avalanche Fuji*.
3. Fill in your blockchain's router and LINK contract addresses. The router address can be found on the [CCIP Directory](/ccip/directory) and the LINK contract address on the [LINK token contracts page](/resources/link-token-contracts). For *Avalanche Fuji*:
- The router address is 0xF694E193200268f9a4868e4Aa017A0118C9a8177,
- The LINK contract address is 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846.
4. Click the **transact** button. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
5. Open MetaMask and fund your contract with CCIP-BnM tokens. You can transfer 0.002 *CCIP-BnM* to your contract.
4. Enable your contract to transfer tokens to *Ethereum Sepolia*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions for your smart contract deployed on *Avalanche Fuji*.
2. Call the `allowlistDestinationChain` function with 16015286601757825753 as the destination chain selector, and true as allowed. Each chain selector is found on the [CCIP Directory](/ccip/directory).
### Transfer tokens and pay in LINK
You will transfer *0.001 CCIP-BnM*. The CCIP fees for using CCIP will be paid in LINK. Read this [explanation](#transferring-tokens-and-pay-in-link) for a detailed description of the code example.
1. Open MetaMask and connect to *Avalanche Fuji*. Fund your contract with LINK tokens. You can transfer 70 *LINK* to your contract. **Note**: The LINK tokens are used to pay for CCIP fees.
**Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this example, you can get additional testnet LINK
from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia.
2. Transfer CCIP-BnM from *Avalanche Fuji*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions for your smart contract deployed on *Avalanche Fuji*.
3. Fill in the arguments of the ***transferTokensPayLINK*** function:
| Argument | Value and Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| _destinationChainSelector | CCIP Chain identifier of the destination blockchain (*Ethereum Sepolia* in this example). You can find each chain selector on the [CCIP Directory](/ccip/directory). |
| _receiver | Your account address on *Ethereum Sepolia*. The destination account address. It could be a smart contract or an EOA. |
| _token | The *CCIP-BnM* contract address at the source chain (*Avalanche Fuji* in this example). You can find all the addresses for each supported blockchain on the [CCIP Directory](/ccip/directory). |
| _amount | The token amount (*0.001 CCIP-BnM*). |
4. Click the **transact** button and confirm the transaction on MetaMask.
5. Once the transaction is successful, note the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0x62ca604240fc30133646ff94dcedac5375c5e42b109f3339c85e4fa29541d42b) of a transaction on *Avalanche Fuji*.
1. Open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the transaction hash.
2. The CCIP transaction is completed once the status is marked as "Success". The data field is empty because you are only transferring tokens.
3. Check the receiver account on the destination chain:
1. Note the destination transaction hash from the CCIP explorer. `0x083fc1a79ffcfd617426fd71dff87ca16db2e4333e62a28cdd13d4bec0926bcb` in this example.
2. Open the block explorer for your destination chain. For *Ethereum Sepolia*, open [etherscan](https://sepolia.etherscan.io).
3. Search the [transaction hash](https://sepolia.etherscan.io/tx/0x083fc1a79ffcfd617426fd71dff87ca16db2e4333e62a28cdd13d4bec0926bcb).
4. Notice in the *Tokens Transferred* section that CCIP-BnM tokens have been transferred to your account (0.001 CCIP-BnM).
### Transfer tokens and pay in native
You will transfer *0.001 CCIP-BnM*. The CCIP fees for using CCIP will be paid in Avalanche Fuji's native AVAX. Read this [explanation](#transferring-tokens-and-pay-in-native) for a detailed description of the code example.
1. Open MetaMask and connect to *Avalanche Fuji*. Fund your contract with native gas tokens. You can transfer 0.2 *AVAX* to your contract. **Note**: The native gas tokens are used to pay for CCIP fees.
2. Transfer CCIP-BnM from *Avalanche Fuji*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Avalanche Fuji*.
3. Fill in the arguments of the ***transferTokensPayNative*** function:
| Argument | Value and Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| _destinationChainSelector | CCIP Chain identifier of the destination blockchain (*Ethereum Sepolia* in this example). You can find each chain selector on the [CCIP Directory](/ccip/directory). |
| _receiver | Your account address on *Ethereum Sepolia*. The destination account address. It could be a smart contract or an EOA. |
| _token | The *CCIP-BnM* contract address at the source chain (*Avalanche Fuji* in this example). You can find all the addresses for each supported blockchain on the [CCIP Directory](/ccip/directory).. |
| _amount | The token amount (*0.001 CCIP-BnM*). |
4. Click the **transact** button and confirm the transaction on MetaMask.
5. Once the transaction is successful, note the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0x186e5767d65dffe685c24d5ee881201e2b39fd684220a68943b0b861178ddf64) of a transaction on *Avalanche Fuji*.
1. Open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the transaction hash.
2. The CCIP transaction is completed once the status is marked as "Success". The data field is empty because you only transfer tokens. Note that CCIP fees are denominated in LINK. Even if CCIP fees are paid using native gas tokens, node operators will be paid in LINK.
3. Check the receiver account on the destination chain:
1. Note the destination transaction hash from the CCIP explorer. `0xf403d828fa377d657af67f12e99ff435974299c27ba2d57c53494d29bbbfc938` in this example.
2. Open the block explorer for your destination chain. For *Ethereum Sepolia*, open [etherscan](https://sepolia.etherscan.io).
3. Search the [transaction hash](https://sepolia.etherscan.io/tx/0xf403d828fa377d657af67f12e99ff435974299c27ba2d57c53494d29bbbfc938).
4. Notice in the *Tokens Transferred* section that CCIP-BnM tokens have been transferred to your account (0.001 CCIP-BnM).
## Explanation
The smart contract featured in this tutorial is designed to interact with CCIP to transfer a supported token to an account on a destination chain. The contract code contains supporting comments clarifying the functions, events, and underlying logic. This section further explains initializing the contract and transferring tokens.
### Initializing of the contract
When you deploy the contract, you define the router address and LINK contract address of the blockchain where you deploy the contract. The contract uses the router address to interact with the router to estimate the CCIP fees and the transmission of CCIP messages.
### Transferring tokens and pay in LINK
The `transferTokensPayLINK` function undertakes six primary operations:
1. Call the `_buildCCIPMessage` private function to construct a CCIP-compatible message using the `EVM2AnyMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage):
- The `_receiver` address is encoded in bytes to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `data` is empty because you only transfer tokens.
- The `tokenAmounts` is an array, with each element comprising a [`EVMTokenAmount` struct](/ccip/api-reference/evm/v1.6.2/client#evmtokenamount) that contains the token address and amount. The array contains one element where the `_token` (token address) and `_amount` (token amount) are passed by the user when calling the `transferTokensPayLINK` function.
- The `extraArgs` specifies the `gasLimit` for relaying the message to the recipient contract on the destination blockchain. In this example, the `gasLimit` is set to `0` because the contract only transfers tokens and does not expect function calls on the destination blockchain.
- The `_feeTokenAddress` designates the token address used for CCIP fees. Here, `address(linkToken)` signifies payment in LINK.
{" "}
1. Computes the fees by invoking the router's `getFee` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee).
2. Ensures your contract balance in LINK is enough to cover the fees.
3. Grants the router contract permission to deduct the fees from the contract's LINK balance.
4. Grants the router contract permission to deduct the amount from the contract's *CCIP-BnM* balance.
5. Dispatches the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend).
**Note**: As a security measure, the `transferTokensPayLINK` function is protected by the `onlyAllowlistedChain` to ensure the contract owner has allowlisted a destination chain.
### Transferring tokens and pay in native
The `transferTokensPayNative` function undertakes five primary operations:
1. Call the `_buildCCIPMessage` private function to construct a CCIP-compatible message using the `EVM2AnyMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage):
- The `_receiver` address is encoded in bytes to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `data` is empty because you only transfer tokens.
- The `tokenAmounts` is an array, with each element comprising an `EVMTokenAmount` [struct](/ccip/api-reference/evm/v1.6.2/client#evmtokenamount) containing the token address and amount. The array contains one element where the `_token` (token address) and `_amount` (token amount) are passed by the user when calling the `transferTokensPayNative` function.
- The `extraArgs` specifies the `gasLimit` for relaying the message to the recipient contract on the destination blockchain. In this example, the `gasLimit` is set to `0` because the contract only transfers tokens and does not expect function calls on the destination blockchain.
- The `_feeTokenAddress` designates the token address used for CCIP fees. Here, `address(0)` signifies payment in native gas tokens (ETH).
{" "}
1. Computes the fees by invoking the router's `getFee` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee).
2. Ensures your contract balance in native gas is enough to cover the fees.
3. Grants the router contract permission to deduct the amount from the contract's *CCIP-BnM* balance.
4. Dispatches the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend). **Note**: `msg.value` is set because you pay in native gas.
**Note**: As a security measure, the `transferTokensPayNative` function is protected by the `onlyAllowlistedChain`, ensuring the contract owner has allowlisted a destination chain.
---
# Transfer Tokens with Data
Source: https://docs.chain.link/ccip/tutorials/evm/programmable-token-transfers
Last Updated: 2025-05-19
In this tutorial, you will use Chainlink CCIP to transfer tokens and arbitrary data between smart contracts on different blockchains. First, you will pay for the CCIP fees on the source blockchain using LINK. Then, you will use the same contract to pay CCIP fees in native gas tokens. For example, you would use ETH on Ethereum or AVAX on Avalanche.
## Before you begin
1. You should understand how to write, compile, deploy, and fund a smart contract. If you need to brush up on the basics, read this [tutorial](/quickstarts/deploy-your-first-contract), which will guide you through using the [Solidity programming language](https://soliditylang.org/), interacting with the [MetaMask wallet](https://metamask.io) and working within the [Remix Development Environment](https://remix.ethereum.org/).
2. Your account must have some AVAX and LINK tokens on *Avalanche Fuji* and ETH tokens on *Ethereum Sepolia*. Learn how to [Acquire testnet LINK](/resources/acquire-link).
3. Check the [CCIP Directory](/ccip/directory) to confirm that the tokens you will transfer are supported for your lane. In this example, you will transfer tokens from *Avalanche Fuji* to *Ethereum Sepolia* so check the list of supported tokens [here](/ccip/directory/testnet/chain/avalanche-fuji-testnet).
4. Learn how to [acquire CCIP test tokens](/ccip/test-tokens#evm-chains). Following this guide, you should have CCIP-BnM tokens, and CCIP-BnM should appear in the list of your tokens in MetaMask.
5. Learn how to [fund your contract](/resources/fund-your-contract). This guide shows how to fund your contract in LINK, but you can use the same guide for funding your contract with any ERC20 tokens as long as they appear in the list of tokens in MetaMask.
6. Follow the previous tutorial: [*Transfer tokens*](/ccip/tutorials/evm/transfer-tokens-from-contract).
## Tutorial
In this tutorial, you will send a *string* text and CCIP-BnM tokens between smart contracts on *Avalanche Fuji* and *Ethereum Sepolia* using CCIP. First, you will pay [CCIP fees in LINK](#transfer-and-receive-tokens-and-data-and-pay-in-link), then you will pay [CCIP fees in native gas](#transfer-and-receive-tokens-and-data-and-pay-in-native).
### Deploy your contracts
To use this contract:
1. [Open the contract in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/ProgrammableTokenTransfers.sol).
2. Compile your contract.
3. Deploy, fund your sender contract on *Avalanche Fuji* and enable sending messages to *Ethereum Sepolia*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, click on *Deploy & Run Transactions* and select *Injected Provider - MetaMask* from the environment list. Remix will then interact with your MetaMask wallet to communicate with *Avalanche Fuji*.
3. Fill in your blockchain's router and LINK contract addresses. The router address can be found on the [CCIP Directory](/ccip/directory) and the LINK contract address on the [LINK token contracts page](/resources/link-token-contracts). For *Avalanche Fuji*:
- The router address is 0xF694E193200268f9a4868e4Aa017A0118C9a8177,
- The LINK contract address is 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846.
4. Click the **transact** button. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
5. Open MetaMask and fund your contract with CCIP-BnM tokens. You can transfer 0.002 *CCIP-BnM* to your contract.
6. Enable your contract to send CCIP messages to *Ethereum Sepolia*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Avalanche Fuji*.
2. Call the `allowlistDestinationChain`, setting the destination chain selector to 16015286601757825753 and setting `allowed` to true. Each chain selector is found on the [CCIP Directory](/ccip/directory).
4. Deploy your receiver contract on *Ethereum Sepolia* and enable receiving messages from your sender contract:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, make sure the environment is still *Injected Provider - MetaMask*.
3. Fill in your blockchain's router and LINK contract addresses. The router address can be found on the [CCIP Directory](/ccip/directory) and the LINK contract address on the [LINK token contracts page](/resources/link-token-contracts). For *Ethereum Sepolia*, the router address is 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 and the LINK contract address is 0x779877A7B0D9E8603169DdbD7836e478b4624789.
4. Click the **transact** button. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
5. Enable your contract to receive CCIP messages from *Avalanche Fuji*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Ethereum Sepolia*.
2. Call the `allowlistSourceChain` with 14767482510784806043 as the source chain selector, and true as allowed. Each chain selector is found on the [CCIP Directory](/ccip/directory).
6. Enable your contract to receive CCIP messages from the contract that you deployed on *Avalanche Fuji*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Ethereum Sepolia*.
2. Call the `allowlistSender` with the contract address of the contract that you deployed on *Avalanche Fuji*, and true as allowed.
At this point, you have one *sender* contract on *Avalanche Fuji* and one *receiver* contract on *Ethereum Sepolia*. As security measures, you enabled the sender contract to send CCIP messages to *Ethereum Sepolia* and the receiver contract to receive CCIP messages from the sender on *Avalanche Fuji*.
**Note**: Another security measure enforces that only the router can call the `_ccipReceive` function. Read the [explanation](#explanation) section for more details.
### Transfer and Receive tokens and data and pay in LINK
You will transfer *0.001 CCIP-BnM* and a text. The CCIP fees for using CCIP will be paid in LINK. Read this [explanation](#transferring-tokens-and-data-and-pay-in-link) for a detailed description of the code example.
1. Open MetaMask and connect to *Avalanche Fuji*. Fund your contract with LINK tokens. You can transfer 70 *LINK* to your contract. In this example, LINK is used to pay the CCIP fees.
**Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this example, you can get additional testnet LINK
from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia.
2. Send a string data with tokens from *Avalanche Fuji*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Avalanche Fuji*.
3. Fill in the arguments of the ***sendMessagePayLINK*** function:
| Argument | Value and Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| _destinationChainSelector | CCIP Chain identifier of the destination blockchain (*Ethereum Sepolia* in this example). You can find each chain selector on the [CCIP Directory](/ccip/directory). |
| _receiver | Your receiver contract address on *Ethereum Sepolia*. The destination contract address. |
| _text | Any `string` |
| _token | The *CCIP-BnM* contract address at the source chain (*Avalanche Fuji* in this example). You can find all the addresses for each supported blockchain on the [CCIP Directory](/ccip/directory). |
| _amount | The token amount (*0.001 CCIP-BnM*). |
4. Click on `transact` and confirm the transaction on MetaMask.
5. After the transaction is successful, record the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0xd3a0fade0e143fb39964c764bd4803e40062ba8c88e129f44ee795e33ade464b) of a transaction on *Avalanche Fuji*.
1. Open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the transaction hash.
2. The CCIP transaction is completed once the status is marked as "Success". In this example, the CCIP message ID is *0x99a15381125e740c43a60f03c6b011ae05a3541998ca482fb5a4814417627df8*.
3. Check the receiver contract on the destination chain:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Ethereum Sepolia*.
3. Call the `getLastReceivedMessageDetails` function.
4. Notice the received messageId is *0x99a15381125e740c43a60f03c6b011ae05a3541998ca482fb5a4814417627df8*, the received text is *Hello World!*, the token address is *0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05* (CCIP-BnM token address on *Ethereum Sepolia*) and the token amount is 1000000000000000 (0.001 CCIP-BnM).
**Note**: These example contracts are designed to work bi-directionally. As an exercise, you can use them to transfer tokens with data from *Avalanche Fuji* to *Ethereum Sepolia* and from *Ethereum Sepolia* back to *Avalanche Fuji*.
### Transfer and Receive tokens and data and pay in native
You will transfer *0.001 CCIP-BnM* and a text. The CCIP fees for using CCIP will be paid in Avalanche's native AVAX. Read this [explanation](#transferring-tokens-and-data-and-pay-in-native) for a detailed description of the code example.
1. Open MetaMask and connect to *Avalanche Fuji*. Fund your contract with AVAX tokens. You can transfer 0.2 *AVAX* to your contract. The native gas tokens are used to pay the CCIP fees.
2. Send a string data with tokens from *Avalanche Fuji*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Avalanche Fuji*.
3. Fill in the arguments of the ***sendMessagePayNative*** function:
| Argument | Value and Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| _destinationChainSelector | CCIP Chain identifier of the destination blockchain (*Ethereum Sepolia* in this example). You can find each chain selector on the [CCIP Directory](/ccip/directory). |
| _receiver | Your receiver contract address at *Ethereum Sepolia*. The destination contract address. |
| _text | Any `string` |
| _token | The *CCIP-BnM* contract address at the source chain (*Avalanche Fuji* in this example). You can find all the addresses for each supported blockchain on the [CCIP Directory](/ccip/directory). |
| _amount | The token amount (*0.001 CCIP-BnM*). |
4. Click on `transact` and confirm the transaction on MetaMask.
5. Once the transaction is successful, note the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0x8101fef78288981813915e77f8e5746bdba69711bdb7bc1706944a67ac70854b) of a transaction on *Avalanche Fuji*.
1. Open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the transaction hash.
2. The CCIP transaction is completed once the status is marked as "Success". In this example, the CCIP message ID is *0x32bf96ac8b01fe3f04ffa548a3403b3105b4ed479eff407ff763b7539a1d43bd*. Note that CCIP fees are denominated in LINK. Even if CCIP fees are paid using native gas tokens, node operators will be paid in LINK.
3. Check the receiver contract on the destination chain:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Ethereum Sepolia*.
3. Call the `getLastReceivedMessageDetails` function.
4. Notice the received messageId is *0x32bf96ac8b01fe3f04ffa548a3403b3105b4ed479eff407ff763b7539a1d43bd*, the received text is *Hello World!*, the token address is *0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05* (CCIP-BnM token address on *Ethereum Sepolia*) and the token amount is 1000000000000000 (0.001 CCIP-BnM).
**Note**: These example contracts are designed to work bi-directionally. As an exercise, you can use them to transfer tokens with data from *Avalanche Fuji* to *Ethereum Sepolia* and from *Ethereum Sepolia* back to *Avalanche Fuji*.
## Explanation
The smart contract featured in this tutorial is designed to interact with CCIP to transfer and receive tokens and data. The contract code contains supporting comments clarifying the functions, events, and underlying logic. Here we will further explain initializing the contract and sending data with tokens.
### Initializing the contract
When deploying the contract, we define the router address and LINK contract address of the blockchain we deploy the contract on.
Defining the router address is useful for the following:
- Sender part:
- Calls the router's `getFee` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee) to estimate the CCIP fees.
- Calls the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend) to send CCIP messages.
- Receiver part:
- The contract inherits from [CCIPReceiver](/ccip/api-reference/evm/v1.6.2/ccip-receiver), which serves as a base contract for receiver contracts. This contract requires that child contracts implement the `_ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#_ccipreceive). `_ccipReceive` is called by the `ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive), which ensures that only the router can deliver CCIP messages to the receiver contract.
### Transferring tokens and data and pay in LINK
The `sendMessagePayLINK` function undertakes six primary operations:
1. Call the `_buildCCIPMessage` private function to construct a CCIP-compatible message using the `EVM2AnyMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage):
- The `_receiver` address is encoded in bytes to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `data` is encoded from a `string` to `bytes` using [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `tokenAmounts` is an array, with each element comprising an `EVMTokenAmount` [struct](/ccip/api-reference/evm/v1.6.2/client#evmtokenamount) containing the token address and amount. The array contains one element where the `_token` (token address) and `_amount` (token amount) are passed by the user when calling the `sendMessagePayLINK` function.
- The `extraArgs` specifies the `gasLimit` for relaying the message to the recipient contract on the destination blockchain. In this example, the `gasLimit` is set to \`200000.
- The `_feeTokenAddress` designates the token address used for CCIP fees. Here, `address(linkToken)` signifies payment in LINK.
{" "}
1. Computes the fees by invoking the router's `getFee` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee).
2. Ensures your contract balance in LINK is enough to cover the fees.
3. Grants the router contract permission to deduct the fees from the contract's LINK balance.
4. Grants the router contract permission to deduct the amount from the contract's *CCIP-BnM* balance.
5. Dispatches the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend).
**Note**: As a security measure, the `sendMessagePayLINK` function is protected by the `onlyAllowlistedDestinationChain`, ensuring the contract owner has allowlisted a destination chain.
### Transferring tokens and data and pay in native
The `sendMessagePayNative` function undertakes five primary operations:
1. Call the `_buildCCIPMessage` private function to construct a CCIP-compatible message using the `EVM2AnyMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage):
- The `_receiver` address is encoded in bytes to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `data` is encoded from a `string` to `bytes` using [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `tokenAmounts` is an array, with each element comprising an `EVMTokenAmount` [struct](/ccip/api-reference/evm/v1.6.2/client#evmtokenamount) containing the token address and amount. The array contains one element where the `_token` (token address) and `_amount` (token amount) are passed by the user when calling the `sendMessagePayNative` function.
- The `extraArgs` specifies the `gasLimit` for relaying the message to the recipient contract on the destination blockchain. In this example, the `gasLimit` is set to \`200000.
- The `_feeTokenAddress` designates the token address used for CCIP fees. Here, `address(0)` signifies payment in native gas tokens (ETH).
{" "}
1. Computes the fees by invoking the router's `getFee` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee).
2. Ensures your contract balance in native gas is enough to cover the fees.
3. Grants the router contract permission to deduct the amount from the contract's *CCIP-BnM* balance.
4. Dispatches the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend). **Note**: `msg.value` is set because you pay in native gas.
**Note**: As a security measure, the `sendMessagePayNative` function is protected by the `onlyAllowlistedDestinationChain`, ensuring the contract owner has allowlisted a destination chain.
### Receiving messages
On the destination blockchain, the router invokes the `_ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#_ccipreceive) which expects a `Any2EVMMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage) that contains:
- The CCIP `messageId`.
- The `sourceChainSelector`.
- The `sender` address in bytes format. Given that the sender is known to be a contract deployed on an EVM-compatible blockchain, the address is decoded from bytes to an Ethereum address using the [ABI specifications](https://docs.soliditylang.org/en/v0.8.20/abi-spec.html).
- The `tokenAmounts` is an array containing received tokens and their respective amounts. Given that only one token transfer is expected, the first element of the array is extracted.
- The `data`, which is also in bytes format. Given a `string` is expected, the data is decoded from bytes to a string using the [ABI specifications](https://docs.soliditylang.org/en/v0.8.20/abi-spec.html).
**Note**: Three important security measures are applied:
- `_ccipReceive` is called by the `ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive), which ensures that only the router can deliver CCIP messages to the receiver contract. See the `onlyRouter` [modifier](/ccip/api-reference/evm/v1.6.2/ccip-receiver#onlyrouter) for more information.
- The modifier `onlyAllowlisted` ensures that only a call from an allowlisted source chain and sender is accepted.
---
# Transfer Tokens with Data - Defensive Example
Source: https://docs.chain.link/ccip/tutorials/evm/programmable-token-transfers-defensive
Last Updated: 2025-05-19
This tutorial extends the [programmable token transfers example](/ccip/tutorials/evm/programmable-token-transfers). It uses Chainlink CCIP to transfer tokens and arbitrary data between smart contracts on different blockchains, and focuses on defensive coding in the receiver contract. In the event of a specified error during the CCIP message reception, the contract locks the tokens. Locking the tokens allows the owner to recover and redirect them as needed. Defensive coding is crucial as it enables the recovery of locked tokens and ensures the protection of your users' assets.
## Before you begin
1. You should understand how to write, compile, deploy, and fund a smart contract. If you need to brush up on the basics, read this [tutorial](/quickstarts/deploy-your-first-contract), which will guide you through using the [Solidity programming language](https://soliditylang.org/), interacting with the [MetaMask wallet](https://metamask.io) and working within the [Remix Development Environment](https://remix.ethereum.org/).
2. Your account must have some AVAX and LINK tokens on *Avalanche Fuji* and ETH tokens on *Ethereum Sepolia*. Learn how to [Acquire testnet LINK](/resources/acquire-link).
3. Check the [CCIP Directory](/ccip/directory) to confirm that the tokens you will transfer are supported for your lane. In this example, you will transfer tokens from *Avalanche Fuji* to *Ethereum Sepolia* so check the list of supported tokens [here](/ccip/directory/testnet/chain/avalanche-fuji-testnet).
4. Learn how to [acquire CCIP test tokens](/ccip/test-tokens#evm-chains). Following this guide, you should have CCIP-BnM tokens, and CCIP-BnM should appear in the list of your tokens in MetaMask.
5. Learn how to [fund your contract](/resources/fund-your-contract). This guide shows how to fund your contract in LINK, but you can use the same guide for funding your contract with any ERC20 tokens as long as they appear in the list of tokens in MetaMask.
6. Follow the previous tutorial: [*Transfer Tokens with Data*](/ccip/tutorials/evm/programmable-token-transfers) to learn how to make programmable token transfers using CCIP.
## Tutorial
In this guide, you'll initiate a transaction from a smart contract on *Avalanche Fuji*, sending a *string* text and CCIP-BnM tokens to another smart contract on *Ethereum Sepolia* using CCIP. However, a deliberate failure in the processing logic will occur upon reaching the receiver contract. This tutorial will demonstrate a graceful error-handling approach, allowing the contract owner to recover the locked tokens.
### Deploy your contracts
To use this contract:
1. [Open the contract in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/ProgrammableDefensiveTokenTransfers.sol).
2. Compile your contract.
3. Deploy, fund your sender contract on *Avalanche Fuji* and enable sending messages to *Ethereum Sepolia*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, click on *Deploy & Run Transactions* and select *Injected Provider - MetaMask* from the environment list. Remix will then interact with your MetaMask wallet to communicate with *Avalanche Fuji*.
3. Fill in your blockchain's router and LINK contract addresses. The router address can be found on the [CCIP Directory](/ccip/directory) and the LINK contract address on the [LINK token contracts page](/resources/link-token-contracts). For *Avalanche Fuji*:
- The router address is 0xF694E193200268f9a4868e4Aa017A0118C9a8177,
- The LINK contract address is 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846.
4. Click the **transact** button. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
5. Open MetaMask and fund your contract with CCIP-BnM tokens. You can transfer 0.002 *CCIP-BnM* to your contract.
6. Enable your contract to send CCIP messages to *Ethereum Sepolia*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Avalanche Fuji*.
2. Call the `allowlistDestinationChain` with 16015286601757825753 as the destination chain selector, and true as allowed. Each chain selector is found on the [CCIP Directory](/ccip/directory).
4. Deploy your receiver contract on *Ethereum Sepolia* and enable receiving messages from your sender contract:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, make sure the environment is still *Injected Provider - MetaMask*.
3. Fill in your blockchain's router and LINK contract addresses. The router address can be found on the [CCIP Directory](/ccip/directory) and the LINK contract address on the [LINK token contracts page](/resources/link-token-contracts). For *Ethereum Sepolia*:
- The router address is 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59,
- The LINK contract address is 0x779877A7B0D9E8603169DdbD7836e478b4624789.
4. Click the **transact** button. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
5. Enable your contract to receive CCIP messages from *Avalanche Fuji*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Ethereum Sepolia*.
2. Call the `allowlistSourceChain` with 14767482510784806043 as the source chain selector, and true as allowed. Each chain selector is found on the [CCIP Directory](/ccip/directory).
6. Enable your contract to receive CCIP messages from the contract that you deployed on *Avalanche Fuji*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Ethereum Sepolia*.
2. Call the `allowlistSender` with the contract address of the contract that you deployed on *Avalanche Fuji*, and true as allowed.
7. Call the `setSimRevert` function, passing `true` as a parameter, then wait for the transaction to confirm. Setting `s_simRevert` to true simulates a failure when processing the received message. Read the [explanation](#explanation) section for more details.
At this point, you have one *sender* contract on *Avalanche Fuji* and one *receiver* contract on *Ethereum Sepolia*. As security measures, you enabled the sender contract to send CCIP messages to *Ethereum Sepolia* and the receiver contract to receive CCIP messages from the sender on *Avalanche Fuji*. The receiver contract cannot process the message, and therefore, instead of throwing an exception, it will lock the received tokens, enabling the owner to recover them.
**Note**: Another security measure enforces that only the router can call the `_ccipReceive` function. Read the [explanation](#explanation) section for more details.
### Recover the locked tokens
You will transfer *0.001 CCIP-BnM* and a text. The CCIP fees for using CCIP will be paid in LINK.
1. Open MetaMask and connect to *Avalanche Fuji*. Fund your contract with LINK tokens. You can transfer 70 *LINK* to your contract. In this example, LINK is used to pay the CCIP fees.
**Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this example, you can get additional testnet LINK
from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia.
2. Send a string data with tokens from *Avalanche Fuji*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Avalanche Fuji*.
3. Fill in the arguments of the ***sendMessagePayLINK*** function:
| Argument | Value and Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| _destinationChainSelector | CCIP Chain identifier of the destination blockchain (*Ethereum Sepolia* in this example). You can find each chain selector on the [CCIP Directory](/ccip/directory). |
| _receiver | Your receiver contract address at *Ethereum Sepolia*. The destination contract address. |
| _text | Any `string` |
| _token | The *CCIP-BnM* contract address at the source chain (*Avalanche Fuji* in this example). You can find all the addresses for each supported blockchain on the [CCIP Directory](/ccip/directory). |
| _amount | The token amount (*0.001 CCIP-BnM*). |
4. Click on `transact` and confirm the transaction on MetaMask.
5. After the transaction is successful, record the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0x4c7a192fa5636557569d076c06633c4f06140f117a44b49f21628eedd72b8423) of a transaction on *Avalanche Fuji*.
1. Open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the transaction hash.
2. The CCIP transaction is completed once the status is marked as "Success". In this example, the CCIP message ID is *0x120367995ef71f83d64a05bd7793862afda9d04049da4cb32851934490d03ae4*.
3. Check the receiver contract on the destination chain:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of functions of your smart contract deployed on *Ethereum Sepolia*.
3. Call the `getFailedMessages` function with an *offset* of 0 and a *limit* of 1 to retrieve the first failed message.
4. Notice the returned values are: *0x120367995ef71f83d64a05bd7793862afda9d04049da4cb32851934490d03ae4* (the message ID) and *1* (the error code indicating failure).
4. To recover the locked tokens, call the `retryFailedMessage` function:
| Argument | Description |
| --------------- | --------------------------------------------- |
| `messageId` | The unique identifier of the failed message. |
| `tokenReceiver` | The address to which the tokens will be sent. |
5. After confirming the transaction, you can open it in a block explorer. Notice that the locked funds were transferred to the `tokenReceiver` address.
6. Call again the `getFailedMessages` function with an *offset* of 0 and a *limit* of 1 to retrieve the first failed message. Notice that the error code is now *0*, indicating that the message was resolved.
**Note**: These example contracts are designed to work bi-directionally. As an exercise, you can use them to transfer tokens with data from *Avalanche Fuji* to *Ethereum Sepolia* and from *Ethereum Sepolia* back to *Avalanche Fuji*.
## Explanation
The smart contract featured in this tutorial is designed to interact with CCIP to transfer and receive tokens and data. The contract code is similar to the [*Transfer Tokens with Data*](/ccip/tutorials/evm/programmable-token-transfers) tutorial. Hence, you can refer to its [code explanation](/ccip/tutorials/evm/programmable-token-transfers#explanation). We will only explain the main differences.
### Sending messages
The `sendMessagePayLINK` function is similar to the `sendMessagePayLINK` function in the [*Transfer Tokens with Data*](/ccip/tutorials/evm/programmable-token-transfers) tutorial. The main difference is the increased gas limit to account for the additional gas required to process the error-handling logic.
### Receiving and processing messages
Upon receiving a message on the destination blockchain, the `ccipReceive` function is called by the CCIP router. This function serves as the entry point to the contract for processing incoming CCIP messages, enforcing crucial security checks through the `onlyRouter`, and `onlyAllowlisted` modifiers.
Here's the step-by-step breakdown of the process:
1. Entrance through `ccipReceive`:
- The `ccipReceive` function is invoked with an `Any2EVMMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage) containing the message to be processed.
- Security checks ensure the call is from the authorized router, an allowlisted source chain, and an allowlisted sender.
2. Processing Message:
- `ccipReceive` calls the `processMessage` function, which is external to leverage Solidity's try/catch error handling mechanism. **Note**: The `onlySelf` modifier ensures that only the contract can call this function.
- Inside `processMessage`, a check is performed for a simulated revert condition using the `s_simRevert` state variable. This simulation is toggled by the `setSimRevert` function, callable only by the contract owner.
- If `s_simRevert` is false, `processMessage` calls the `_ccipReceive` function for further message processing.
3. Message Processing in `_ccipReceive`:
- `_ccipReceive` extracts and stores various information from the message, such as the `messageId`, decoded `sender` address, token amounts, and data.
- It then emits a `MessageReceived` event, signaling the successful processing of the message.
4. Error Handling:
- If an error occurs during the processing (or a simulated revert is triggered), the catch block within `ccipReceive` is executed.
- The `messageId` of the failed message is added to `s_failedMessages`, and the message content is stored in `s_messageContents`.
- A `MessageFailed` event is emitted, which allows for later identification and reprocessing of failed messages.
### Reprocessing of failed messages
The `retryFailedMessage` function provides a mechanism to recover assets if a CCIP message processing fails. It's specifically designed to handle scenarios where message data issues prevent entire processing yet allow for token recovery:
1. Initiation:
- Only the contract owner can call this function, providing the `messageId` of the failed message and the `tokenReceiver` address for token recovery.
2. Validation:
- It checks if the message has failed using `s_failedMessages.get(messageId)`. If not, it reverts the transaction.
3. Status Update:
- The error code for the message is updated to `RESOLVED` to prevent reentry and multiple retries.
4. Token Recovery:
- Retrieves the failed message content using `s_messageContents[messageId]`.
- Transfers the locked tokens associated with the failed message to the specified `tokenReceiver` as an escape hatch without processing the entire message again.
5. **Event Emission**:
- An event `MessageRecovered` is emitted to signal the successful recovery of the tokens.
This function showcases a graceful asset recovery solution, protecting user values even when message processing encounters issues.
---
# Using the Token Manager
Source: https://docs.chain.link/ccip/tutorials/evm/token-manager
Last Updated: 2025-05-19
The Token Manager allows token developers to deploy, configure, and manage Cross-Chain Tokens (CCTs) in a simplified web interface. The process involves deploying tokens and token pools, registering administrative roles, and configuring token pools to enable secure token transfers using CCIP.
The Token Manager guides you through two workflows:
- **Deploy a new token from scratch**: This is the more beginner-friendly workflow that guides you through the entire process step-by-step, starting by creating a token from scratch.
- **Enable an existing token to go cross-chain**: This is a more advanced workflow for token developers who have already deployed their token and want to add cross-chain capabilities.
If you prefer to manage your deployments and configurations programmatically, refer to the [Cross-Chain Tokens](/ccip/tutorials/cross-chain-tokens) guides available for Remix, Hardhat and Foundry.
You can also use the [CCIP JavaScript SDK](/ccip/ccip-javascript-sdk) to add a fully featured CCIP bridge to your app that can be styled to match your app design.
After enabling your tokens, you can also use Transporter to perform transfers and import tokens with the contract address. Use [test.transporter.io](https://test.transporter.io) for testnet or [transporter.io](https://transporter.io) for mainnet.
## Limitations
Currently, the following advanced features are not yet supported in Token Manager:
- **Token pool replacements and upgrades for existing tokens**. This capability will be added in a subsequent update. To learn more about the process of replacing and upgrading existing token pools, review the [CCIP token pool upgradability](/ccip/concepts/cross-chain-token/evm/upgradability) section.
- **Deployment of token pools that use the [Lock and Unlock mechanism](/ccip/concepts/cross-chain-token/overview#token-handling-mechanisms)**. The Token Manager Wizard automatically configures all tokens with the Burn & Mint mechanism. (Refer to the [Burn & Mint token contract](https://github.com/smartcontractkit/chainlink/blob/contracts-ccip/v1.6.0-beta.0/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol) and [Burn & Mint token pool contract](https://github.com/smartcontractkit/chainlink/blob/contracts-ccip/v1.6.0-beta.0/contracts/src/v0.8/ccip/pools/BurnMintTokenPool.sol) for the Burn & Mint mechanism.)
- Deployment or enablement of custom token pools is not yet supported.
## Getting started
The Token Manager includes both testnet and mainnet functionality. It is highly recommended to test and perform any operations on testnet before mainnet.
Before you can deploy a new token or add an existing token, you need to connect and authorize your wallet.
1. Open the Token Manager using the appropriate link for your use case:
- For testnet, use [https://test.tokenmanager.chain.link](https://test.tokenmanager.chain.link).
- For mainnet, use [https://tokenmanager.chain.link/](https://tokenmanager.chain.link/)
2. Connect your wallet using the **Connect wallet** button in the upper right corner. The following wallets are supported:
- Metamask (EOA)
- Coinbase wallet (EOA)
- WalletConnect (EOA & Safe)
- Rabby wallet (EOA & Safe)
3. After your wallet is connected, authorize the use of your wallet by clicking **Authorize** and complete the subsequent authorization flow for your wallet type. If you are connecting a Safe wallet, authorization is key to enabling permissioned actions on a per user basis; such as initiating transactions, updating off-chain data, and inviting collaborators.
## Deploy a new token
1. Once wallet connection and authorization is complete, click **Add new token** under the **My tokens** section of the Token Manager Dashboard.
2. In the Token Manager Wizard, select **Deploy a new token** and click **Continue** in the lower right corner.
1) On the **Details** page, enter the details for the first network you're configuring for your token deployment:
- Select the network in the **Network** dropdown field.
- Fill in the **Name** and **Symbol** fields to give your token its name and ticker symbol. For example, "Your Token" and "YOURS" respectively.
- Click **Continue**.
2) On the **Settings** page, configure your token's supply:
- Setting a supply cap is optional — toggle the button to enable it and specify an amount. The supply cap sets a maximum limit for the total number of tokens that can ever be minted for the token you're creating.
- Specify an amount of tokens to mint during this initial deployment step.
- Click **Continue**.
3) On the **Networks** page, select the additional blockchain network(s) where you'd like to deploy your new token and click **Continue**.
4) On the **Owners** page, confirm the accounts that will be taking actions on each network. Click **Continue**.
The default account is the currently connected wallet. However, you can have different accounts for each network depending on who you want to be the owner.
5) On the **Summary** page, you can review your upcoming deployments and transactions. Each network you've selected appears along with an expandable list of the transactions the Token Manager will guide you through to deploy your token for each network:
Make sure that your wallet contains gas tokens for each network where you're deploying your token, in order to pay for the deployment transactions.
If you selected more than two networks during the previous step, the *Remove* links are active, allowing you to remove a network before proceeding. If you only have two networks selected, the *Remove* links are intentionally not active. If you need to add more networks, navigate back to the **Networks** page.
6) The **Deploy** page displays the steps that you need to complete for each network:
- Deploy token and pool
- Accept admin role
- Accept token ownership
- Accept pool ownership
After the **Deploy token and pool** step is completed, you can initiate the other steps all at the same time by clicking **Accept** under each step. While these steps run concurrently, each step separately prompts you to confirm the corresponding transactions in your wallet.
Once all of the steps for one network have been initiated, you can switch to the other network and repeat the same process. You do not have to wait for the first network deploy process to be complete before initiating the second network deploy process.
When the deploy process is complete for all the networks you selected, the Token Manager marks them all as **Done**:
Click **Continue**. The Token Manager displays a message showing that your configuration was successful:
When everything is successfully set up for your token, you can view your new Token Page from the Token Manager Dashboard. It displays information about your CCT, enables configuration changes, and allows expansion to additional networks where you can deploy the token.
## Add an existing token
If you have existing token(s) that you've already deployed, you can use the Token Manager to create and configure a token pool for the token, and optionally deploy your token on additional networks. Note that tokens deployed to additional networks are automatically configured to use the Burn and Mint mechanism.
1. On the **Details** page, enter the details for your token on each network where it has already been deployed. As you add each token contract address, the Token Manager displays validation checks for the token and the required admin registration functions in the contract.
Use the *+ Add New Address* link to add additional token deployments. When you've added all the token deployments that you want to add at this time, select the checkbox to confirm *These are all of the tokens I currently want to enable on CCIP*.
2. On the **Pools** page, you're prompted to select the token pool mechanism that's used to transfer value between networks — *Burn / Mint* or *Lock / Release*. Depending on your token contract, you may have more than one option for *Burn / Mint*:
Before selecting a token pool type, be sure to review [CCIP token handling mechanisms](/ccip/concepts/cross-chain-token/overview#token-handling-mechanisms).
3. On the **Networks** page, select the additional blockchain networks where you'd like to deploy your new token. For additional networks, Token Manager Wizard workflow automatically configures all tokens with the Burn & Mint mechanism. (Refer to the [token contract](https://github.com/smartcontractkit/chainlink/blob/contracts-ccip/v1.6.0-beta.0/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol) and [token pool contract](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/ccip/pools/BurnMintTokenPool.sol) for the Burn & Mint mechanism.)
4. On the **Summary** page, each network you've selected appears along with an expandable list of the transactions the Token Manager will guide you through to deploy your token for each network.
If you selected more than two networks during the previous step, the *Remove* links are active, allowing you to remove a network before proceeding. If you only have two networks selected, the *Remove* links are intentionally not active. If you need to add more networks, navigate back to the **Networks** page.
5. The **Deploy** page displays the steps that you need to complete.
For the tokens you're adding, the Token Manager guides you through each of these steps per network:
- Deploy token pool for existing token
- Grant Burn / Mint privileges (**manual step**)
- Register admin
- Accept admin role of your token pool
- Set the token pool address
- Accept ownership of the new token pool
{" "}
For the other tokens in your network, the Token Manager also guides you through updating existing token pools to incorporate the tokens you're adding.
At each step, you are prompted to confirm the corresponding transactions in your wallet. When each step is complete for all the networks you selected, the Token Manager displays a message showing that your configuration was successful.
When everything is successfully set up for your token, you can view your new Token Page from the Token Manager Dashboard. It displays information about your CCT, enables configuration changes, and allows expansion to additional networks.
## Token Manager Dashboard
After you connect your wallet, you can see the Token Manager Dashboard:
- If you click **Add new token**, you enter the Token Manager Wizard which prompts you to deploy a new token or to add an existing token.
- If the wallet you connected to the dashboard is a Token Admin address, the Token Manager automatically populates your tokens in the dashboard.
- If you have saved partial progress in the Token Manager Wizard, your token displays in a draft state so you can return and finish deploying it later.
After you have deployed a new token or added an existing one, each token has its own page:
The token page shows both the configured and unconfigured networks.
- When you select any of the listed unconfigured networks, you can use the Token Manager Wizard to expand your token to those networks, either by deploying a new token or by adding an existing token and deploying a token pool.
- For configured networks, you can view details for each network-specific token, and you can expand a **Token Details** side panel with more information and admin actions. To expand the **Token Details** panel, click the *View* link next to the configured network.
When expanded, the **Token Details** side panel provides more details about the inbound and outbound lanes for your token. If the connected wallet is a token admin or has permissions to update the token pool, the **Token Details** side panel also displays an **Actions** menu:
If you have the appropriate permissions, you can edit your token's inbound and outbound rate limits, edit the router contract address, and propose a new token administrator.
### Managing token settings
1. Connect your wallet and select your token in the Token Manager home page. A detailed page for your token displays, showing both configured and unconfigured networks.
2. To access the **Settings** page, select the gear icon next to your token's name:
### Verifying your token
You can request token verification through the Token Manager; when verification is granted, it allows the token to be listed on the CCIP Directory and ensures information is consistent across other CCIP apps, like CCIP Explorer.
If your token is unverified, an **Unverified** badge displays underneath the token's name at the top of the page. Be sure that all information is correct before your submission, as it requires a manual review process. If you need to make any further changes after submitting your request, you must use the [CCIP Contact Form](https://chain.link/ccip-contact?v=Token%20Manager%20support).
You can send a request for verification from the **Settings** page:
1. The *Token Details* tab allows you to modify your token's name and ticker symbol with an off-chain transaction. You can also specify an avatar for your token. If you have made any changes in this tab, click **Save Changes**.
2. The *Project Details* tab prompts you to fill in your project's name and URL, and a contact email address. This information is kept private. It's optional to fill out, but required if you're requesting token verification.
3. The *Verification* tab has a **Verify my token** button that submits a verification request with the information you provided in the previous two tabs.
When your verification request is granted, the Token Manager will display a **Verified** badge on the token page.
---
# CCIP JavaScript SDK - Integration Guide
Source: https://docs.chain.link/ccip/ccip-javascript-sdk
Last Updated: 2025-05-19
The [CCIP JavaScript SDK](https://github.com/smartcontractkit/ccip-javascript-sdk/tree/main) is a tool that helps you to simplify management of cross-chain token transfers, and to integrate CCIP with the frontend of your own app.
The CCIP JavaScript SDK includes two packages:
- [`ccip-js`](https://github.com/smartcontractkit/ccip-javascript-sdk/blob/main/packages/ccip-js/README.md): A TypeScript library that provides a client for managing cross-chain token transfers that use CCIP routers. This package allows you to manage the steps you need to prepare before sending a CCIP message, as well as checking the transfer status afterward.
- [`ccip-react-components`](https://github.com/smartcontractkit/ccip-javascript-sdk/blob/main/packages/ccip-react-components/README.md): A set of prebuilt ready-to-use UI components built on top of `ccip-js`. This package includes the following features:
- Customize injected wallet providers (MetaMask and Coinbase Wallet)
- Specify preselected chains and tokens that will display as defaults when the component loads
- Customize the UI theme
- Configure allowlists and deny lists for which chains can be used, and which chains can be used as a sources or destinations for cross-chain transfers
Using both packages together, you can add a fully featured CCIP bridge to your app that can be styled to match your app design.
You can also use the `ccip-js` package on its own — for example, to build a backend application. The features of the CCIP-JS package include:
- *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.
## Install and run the SDK
1. [Install `pnpm`](https://pnpm.io/installation).
2. Clone the `ccip-javascript-sdk` repo and navigate to the root directory of the `ccip-javascript-sdk` project:
```sh
git clone https://github.com/smartcontractkit/ccip-javascript-sdk.git && cd ccip-javascript-sdk
```
3. From the project root, run one of the following commands to install the SDK:
- `pnpm dev-example` runs an example NextJS app locally. Navigate to [http://localhost:3000](http://localhost:3000) in your browser.
## Run an example app
The example Next.js app included with the CCIP JavaScript SDK demonstrates the SDK's functionalities within an interactive web application, allowing you to see its features in action.
To get started:
1. Launch the app by using the following commands:
```sh
pnpm build
pnpm dev-example
```
2. In your browser, navigate to [http://localhost:3000/](http://localhost:3000/) to see the interactive app:
## Review a basic UI example
This basic UI example shows a basic token list configuration with CCIP-BnM and CCIP-LnM test tokens, and it lists the testnets you want to use with each one. This example also includes a basic frontend configuration.
Review the reference documentation:
- Listing tokens in [`tokensList`](https://github.com/smartcontractkit/ccip-javascript-sdk/tree/main/packages/ccip-react-components#tokens)
- Configuring the frontend components in [`Config`](https://github.com/smartcontractkit/ccip-javascript-sdk/tree/main/packages/ccip-react-components#config)
- Configuring a [theme for frontend styling](https://github.com/smartcontractkit/ccip-javascript-sdk/tree/main/packages/ccip-react-components#theme)
Review the basic UI example below:
````ts
import 'ccip-react-components/dist/style.css';
import { CCIPWidget, Config, Token } from 'ccip-react-components';
import { sepolia, optimismSepolia } from 'viem/chains';
const tokensList: Token[] = [
{
symbol: 'CCIP-BnM',
address: {
[arbitrumSepolia.id]:'0xA8C0c11bf64AF62CDCA6f93D3769B88BdD7cb93D',
[avalancheFuji.id]: '0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4',
[baseSepolia.id]: '0x88A2d74F47a237a62e7A51cdDa67270CE381555e',
[bscTestnet.id]: '0xbFA2ACd33ED6EEc0ed3Cc06bF1ac38d22b36B9e9',
[optimismSepolia.id]: '0x8aF4204e30565DF93352fE8E1De78925F6664dA7',
[polygonAmoy.id]: '0xcab0EF91Bee323d1A617c0a027eE753aFd6997E4',
[sepolia.id]: '0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05'
},
logoURL: 'https://smartcontract.imgix.net/tokens/ccip-bnm.webp?auto=compress%2Cformat',
tags: ['chainlink', 'default']
},
{
symbol: 'CCIP-LnM',
address: {
[optimismSepolia.id]: '0x044a6B4b561af69D2319A2f4be5Ec327a6975D0a',
[sepolia.id]: '0x466D489b6d36E7E3b824ef491C225F5830E81cC1'
},
logoURL: 'https://smartcontract.imgix.net/tokens/ccip-lnm.webp?auto=compress%2Cformat',
tags: ['chainlink', 'default']
}
];
const config: Config = {
theme: {
pallette: {
background: '#FFFFFF',
border: '#B3B7C0',
text: '#000000',
}
shape: {
radius: 6
},
}
};
;
```typescript
### Theme configuration
You can customize the component's theme to be in line with your app design. These are all the options available for theme configuration:
```ts
import { Config } from 'ccip-react-components';
const config: Config = { theme:
{
/** Define the app colors in HEX format */
palette?: {
/** Titles color and primary button background, default #000000 */
primary?: string;
/** Background color, default '#FFFFFF' */
background?: string;
/** Border color, default '#B3B7C0' */
border?: string;
/** Text color, default '#000000' */
text?: string;
/** Secondary text, inactive and placeholders color, default '#6D7480' */
muted?: string;
/** Input fields background color, default '#FFFFFF' */
input?: string;
/** Popovers, dropdowns and select fields background color, default '#F5F7FA' */
popover?: string;
/** Selected field from a dropdown background color, default '#D7DBE0' */
selected?: string;
/** Warning text color, default '#F7B955' */
warning?: string;
/** Warning text background color, default '#FFF5E0' */
warningBackground?: string;
};
shape?: {
/** Border radius size in px default 6 */
radius?: number;
};
};}
```typescript
## Review a CCIP-JS example
This example uses the `ccip-js` package and 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
Review the [reference documentation](https://github.com/smartcontractkit/ccip-javascript-sdk/tree/main/packages/ccip-js#api-reference) for `ccip-js`.
### Review the code
```ts
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",
})
```typescript
## Build packages
Optionally, if you need to modify the packages and use your modified version, follow these instructions to build the packages:
You can use `pnpm build` to build both packages together. If you're building each package individually, make sure to build the `build-ccip-js` package before you build the `ccip-react-components` package. The React components depend on the JS package.
1. Build the `build-ccip-js` package:
```sh
pnpm i -w
pnpm build-ccip-js
```
1. Build the `ccip-react-components` package:
```sh
pnpm build-components
```
1. Update the `ccip-react-components` package to use the local `ccip-js` version by modifying the `packages/ccip-react-components/package.json` file. Replace the `@chainlink/ccip-js` dependency with the workspace reference:
```
"@chainlink/ccip-js": "workspace:*"
```
1. Update the `examples/nextjs` app to use both local `ccip-js` and `ccip-react-components` versions by modifying the `examples/nextjs/package.json` file. Replace the `@chainlink/ccip-js` and `@chainlink/ccip-react-components` dependencies with these relative paths:
```
"@chainlink/ccip-js": "link:../../packages/ccip-js",
"@chainlink/ccip-react-components": "link:../../packages/ccip-react-components",
```
````
---
# CCIP Offchain tutorials
Source: https://docs.chain.link/ccip/tutorials/evm/offchain
Last Updated: 2025-05-19
These tutorials focus on direct interaction between Externally Owned Accounts (EOAs) and the [CCIP Router](/ccip/concepts/architecture/onchain/evm/components#router).
## Tutorials
- [Transfer Tokens between EOAs](/ccip/tutorials/evm/offchain/transfer-tokens-from-eoa): Learn how to transfer tokens between Externally Owned Accounts (EOAs) across different blockchains, using Chainlink CCIP.
- [Checking CCIP Message Status Off-Chain](/ccip/tutorials/evm/offchain/get-status-offchain): Learn how to verify the status of Chainlink CCIP messages offchain using JavaScript.
---
# Transfer Tokens between EOAs
Source: https://docs.chain.link/ccip/tutorials/evm/offchain/transfer-tokens-from-eoa
Last Updated: 2025-05-19
In this tutorial, you will use Chainlink CCIP to transfer tokens directly from your [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) to an account on a different blockchain. First, you will pay for CCIP fees on the source blockchain using LINK. Then, you will run the same example paying for CCIP fees in native gas, such as ETH on Ethereum or AVAX on Avalanche.
## Before you begin
1. [Install Node.js 18](https://nodejs.org/en/download/). Optionally, you can use the [nvm package](https://www.npmjs.com/package/nvm) to switch between Node.js versions with `nvm use 18`.
```shell
node -v
```
```shell
$ node -v
v18.7.0
```
2. Your [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) must have both AVAX and LINK tokens on *Avalanche Fuji* to pay for the gas fees and CCIP fees.
- [Configure MetaMask to use LINK tokens](/resources/acquire-link#configure-metamask-to-use-link-tokens)
- Acquire testnet AVAX and LINK from [faucets.chain.link/fuji](https://faucets.chain.link/fuji)
3. Check the [CCIP Directory](/ccip/directory) to confirm that the tokens you will transfer are supported for your lane. In this example, you will transfer tokens from *Avalanche Fuji* to *Ethereum Sepolia* so check the list of supported tokens [here](/ccip/directory/testnet/chain/avalanche-fuji-testnet).
4. Learn how to [acquire CCIP test tokens](/ccip/test-tokens#evm-chains). After following this guide, your [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) should have CCIP-BnM tokens, and CCIP-BnM should appear in the list of your tokens in MetaMask.
5. In a terminal, clone the [smart-contract-examples repository](https://github.com/smartcontractkit/smart-contract-examples) and change to the `smart-contract-examples/ccip/offchain/javascript` directory.
```shell
git clone https://github.com/smartcontractkit/smart-contract-examples.git && \
cd smart-contract-examples/ccip/offchain/javascript
```
6. Run `npm install` to install the dependencies.
```shell
npm install
```
7. For higher security, the examples repository imports [@chainlink/env-enc](https://www.npmjs.com/package/@chainlink/env-enc). Use this tool to encrypt your environment variables at rest.
1. Set an encryption password for your environment variables.
```shell
npx env-enc set-pw
```
2. Run `npx env-enc set` to configure a `.env.enc` file with the basic variables that you need to send your requests to *Ethereum Sepolia*.
- `AVALANCHE_FUJI_RPC_URL`: Set a URL for the *Avalanche Fuji* testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service.
- `ETHEREUM_SEPOLIA_RPC_URL`: Set a URL for the *Ethereum Sepolia* testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service.
- `PRIVATE_KEY`: Find the private key for your testnet wallet. If you use MetaMask, follow the instructions to [Export a Private Key](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/).**Note**: The offchain script uses your private key to sign any transactions you make such as transferring tokens.
```shell
npx env-enc set
```
## Tutorial
### Transfer tokens and pay in LINK
In this example, you will transfer CCIP-BnM tokens from your EOA on *Avalanche Fuji* to an account on *Ethereum Sepolia*. The destination account could be an [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) or a smart contract. The example shows how to transfer CCIP-BnM tokens, but you can reuse the same example to transfer other tokens as long as they are supported for your [lane](/ccip/concepts/architecture/key-concepts#lane).
For this example, CCIP fees are paid in LINK tokens. To learn how to pay CCIP fees in native AVAX, read the [Pay in native](#transfer-tokens-and-pay-in-native) section. To see a detailed description of the example code, read the [code explanation](#code-explanation) section.
To transfer tokens and pay in LINK, use the following command:
```
node src/transfer-tokens.js sourceChain destinationChain destinationAccount tokenAddress amount feeTokenAddress
```
The `feeTokenAddress` parameter specifies the token address for paying CCIP fees. The supported tokens for paying fees include LINK, the native gas token of the source blockchain (ETH for Ethereum), and the wrapped native gas token (WETH for Ethereum).
Complete the following steps in your terminal:
1. Send 1,000,000,000,000,000 (0.001 CCIP-BnM ) from your EOA on *Avalanche Fuji* to another account on *Ethereum Sepolia*:
```
node src/transfer-tokens.js avalancheFuji ethereumSepolia YOUR_ACCOUNT 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 1000000000000000 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846
```
Command arguments:
| Argument | Explanation |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| node src/transfer-tokens.js | Node.js will execute the JavaScript code inside the `transfer-tokens.js` file. |
| avalancheFuji | This specifies the source blockchain, in this case, *Avalanche Fuji*. |
| ethereumSepolia | This specifies the destination blockchain, which is *Ethereum Sepolia* in this case. |
| `YOUR_ACCOUNT` | This is the account address on the destination blockchain. You can replace this with your account address. |
| 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 | This is the CCIP-BnM token contract address on Avalanche Fuji. The contract addresses for each network can be found on the [CCIP Directory](/ccip/directory). |
| 1000000000000000 | This is the amount of CCIP-BnM tokens to be transferred. In this example, 0.001 CCIP-BnM are transferred. |
| 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846 | Since you will pay for CCIP fees in LINK, this is the LINK token contract address on *Avalanche Fuji*. The LINK contract address can be found on the [Link Token contracts page](/resources/link-token-contracts?parent=ccip). |
2. Once you execute the command, you should see the following logs:
```
$ node src/transfer-tokens.js avalancheFuji ethereumSepolia 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 1000000000000000 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846
Estimated fees (LINK): 0.020020889739492
Approving router 0xF694E193200268f9a4868e4Aa017A0118C9a8177 to spend 1000000000000000 of token 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4
Approval done. Transaction: 0x103c20b95183380aa7c04edd0cc8b5cd6137f0b36eda931bdd23e66fd0d21251
Approving router 0xF694E193200268f9a4868e4Aa017A0118C9a8177 to spend fees 20020889739492000 of feeToken 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846
Approval done. Transaction: 0x1b6737bbf12f1ba0391ae9ba38c46c72a1118f4d20767c4c67729cf3acc0ae8b
Calling the router to send 1000000000000000 of token 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 to account 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A on destination chain ethereumSepolia using CCIP
✅ 1000000000000000 of Tokens(0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4) Sent to account 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A on destination chain ethereumSepolia using CCIP. Transaction hash 0x96b5b645f4f442131fc9466ff459e2211d408058be4d9a72a8fb057ca0f4723f - Message id is 0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd
Wait for message 0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd to be executed on the destination chain - Check the explorer https://ccip.chain.link/msg/0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd
...
```
3. Analyze the logs:
- The script communicates with the router to calculate the transaction fees required to transfer tokens, which amounts to 20,020,889,739,492,000 Juels (equivalent to 0.02 LINK).
- The script engages with the Link token contract, authorizing the router contract to spend 20,020,889,739,492,000 Juels for the fees and 1000000000000000 (0.001 CCIP-BnM) from your Externally Owned Account (EOA) balance.
- The script initiates a transaction through the router to transfer 1000000000000000 (0.001 CCIP-BnM) to your account on *Ethereum Sepolia*. It also returns the CCIP message ID.
- The script continuously monitors the destination blockchain (*Ethereum Sepolia*) to track the progress and completion of the cross-chain transaction.
4. While the script is waiting for the cross-chain transaction to proceed, open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the message ID. Notice that the status is not finalized yet.
5. After several minutes (the waiting time depends on the [finality of the source blockchain](/ccip/ccip-execution-latency)), the script will complete the polling process, and the following logs will be displayed:
```
Message 0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd has not been processed yet on the destination chain.Try again in 60sec - Check the explorer https://ccip.chain.link/msg/0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd
✅Status of message 0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd is SUCCESS - Check the explorer https://ccip.chain.link/msg/0x64be9f0e67af707d6203184adf30e86b3f0edd024c868ee0f2c57992d69609fd
```
6. Open the [CCIP explorer](https://ccip.chain.link/) and use the message ID to find your cross-chain transaction.
7. The data field is empty because only tokens are transferred. The gas limit is set to 0 because the transaction is directed to an Externally Owned Account (EOA). With an empty data field, no function calls on a smart contract are expected on the destination chain.
### Transfer tokens and pay in native
In this example, you will transfer CCIP-BnM tokens from your EOA on *Avalanche Fuji* to an account on *Ethereum Sepolia*. The destination account could be an [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) or a smart contract. The example shows how to transfer LINK tokens, but you can reuse the same example to transfer other tokens as long as they are supported for your [lane](/ccip/concepts/architecture/key-concepts#lane).
For this example, CCIP fees are paid in Sepolia's native ETH. To learn how to pay CCIP fees in LINK, read the [Pay in LINK](#transfer-tokens-and-pay-in-link) section. To see a detailed description of the example code, read the [code explanation](#code-explanation) section.
To transfer tokens and pay in native, use the following command:
```
node src/transfer-tokens.js sourceChain destinationChain destinationAccount tokenAddress amount
```
Complete the following steps in your terminal:
1. Send 1,000,000,000,000,000 (0.001 CCIP-BnM ) from your EOA on *Avalanche Fuji* to another account on *Ethereum Sepolia*:
```
node src/transfer-tokens.js avalancheFuji ethereumSepolia YOUR_ACCOUNT 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 1000000000000000
```
Command arguments:
| Argument | Explanation |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| node src/transfer-tokens.js | Node.js will execute the JavaScript code inside the `transfer-tokens.js` file. |
| avalancheFuji | This specifies the source blockchain, in this case, *Avalanche Fuji*. |
| ethereumSepolia | This specifies the destination blockchain, which is *Ethereum Sepolia* in this case. |
| `YOUR_ACCOUNT` | This is the account address on the destination blockchain. Replace this with your account address. |
| 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 | This is the CCIP-BnM token contract address on *Avalanche Fuji*. The contract addresses for each network can be found on the [CCIP Directory](/ccip/directory). |
| 1000000000000000 | This is the amount of CCIP-BnM tokens to be transferred. In this example, 0.001 CCIP-BnM are transferred. |
2. After you execute the command, you should see the following logs:
```
$ node src/transfer-tokens.js avalancheFuji ethereumSepolia 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 1000000000000000
Estimated fees (native): 80139264929946666
Approving router 0xF694E193200268f9a4868e4Aa017A0118C9a8177 to spend 1000000000000000 of token 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4
Approval done. Transaction: 0x23fd23e7df77ef6619ed108f507e85108e5e8592bc754a85b1264f8cf15e3221
Calling the router to send 1000000000000000 of token 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 to account 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A on destination chain ethereumSepolia using CCIP
✅ 1000000000000000 of Tokens(0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4) Sent to account 0x83dC44a4C00DFf69d0A0c7c94B20b53a4933BE0A on destination chain ethereumSepolia using CCIP. Transaction hash 0xe4b2226a55a6eb27f5e5ecf497af932578bbdc1009f412a8b7a855a5dbd00ffa - Message id is 0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d
Wait for message 0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d to be executed on the destination chain - Check the explorer https://ccip.chain.link/msg/0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d
...
```
3. Analyze the logs:
- The script communicates with the router to calculate the transaction fees required to transfer tokens, which amounts to 80,139,264,929,946,666 wei (equivalent to 0.08 AVAX).
- The script interacts with the CCIP-BnM token contract, authorizing the router contract to deduct 0.001 CCIP-BnM from your Externally Owned Account (EOA) balance.
- The script initiates a transaction through the router to transfer 0.001 CCIP-BnM tokens to your destination account on *Ethereum Sepolia*. It also returns the CCIP message ID.
- The script continuously monitors the destination blockchain (*Ethereum Sepolia*) to track the progress and completion of the cross-chain transaction.
4. The transaction time depends on the [finality of the source blockchain](/ccip/ccip-execution-latency). After several minutes, the script will complete the polling process and the following logs will be displayed:
```
Message 0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d is not processed yet on destination chain.Try again in 60sec - Check the explorer https://ccip.chain.link/msg/0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d
✅Status of message 0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d is SUCCESS - Check the explorer https://ccip.chain.link/msg/0x137481a149a892f9b555d9f0c934b67fd85354af0292b82eff2f0eafd8686b9d
```
1. Open the [CCIP explorer](https://ccip.chain.link/) and use the message ID to find your cross-chain transaction.
2. The data field is empty because only tokens are transferred. The gas limit is set to 0 because the transaction is directed to an Externally Owned Account (EOA), so no function calls are expected on the destination chain.
## Code explanation
The Javascript featured in this tutorial is designed to interact with CCIP to transfer tokens. The contract code includes several code comments to clarify each step, but this section explains the key elements.
### Imports
The script starts by importing the necessary modules and data. It imports ethers.js and ABIs (Application Binary Interface) from a config file for different contracts and configurations.
### Handling arguments
The `handleArguments` function validates and parses the command line arguments passed to the script.
### Main function: transferTokens
This asynchronous function, `transferTokens` performs the token transfer.
#### Initialization
The script initializes ethers providers to communicate with the blockchains in this section. It parses source and destination router addresses and blockchain selectors. A signer is created to sign transactions.
#### Token validity check
The script fetches a list of supported tokens for the destination chain and checks if the token you want to transfer is supported.
#### Building the CCIP message
A Cross-Chain Interoperability Protocol (CCIP) message is built, which will be sent to the router contract. It includes the destination account, amount, token address, and additional parameters.
#### Fee calculation
The script calls the router to estimate the fees for transferring tokens.
#### Transferring tokens
This section handles the actual transferring of tokens. It covers three cases:
- **Fees paid using the native gas token:** The contract makes one approval for the transfer amount. The fees are included in the `value` transaction field.
- **Fees paid using an asset different from the native gas token and the token being transferred:** The contracts makes two approvals. The first approval is for the transfer amount and the second approval is for the fees.
- **Fees paid using the same asset that is being transferred, but not the native gas token:** The contract makes a single approval for the sum of the transfer amount and fees.
The script waits for the transaction to be validated and stores the transaction receipt.
#### Fetching message ID
The router's `ccipSend` function returns a message ID. The script simulates a call to the blockchain to fetch the message ID that the router returned.
#### Checking the status on the destination chain
The script polls the off-ramp contracts on the destination chain to wait for the message to be executed. If the message is executed, it returns the status. Otherwise, the message times out after 40 minutes.
---
# Checking CCIP Message Status
Source: https://docs.chain.link/ccip/tutorials/evm/offchain/get-status-offchain
Last Updated: 2025-05-19
In this tutorial, you will learn how to verify the status of a Chainlink CCIP transaction offchain using JavaScript. Starting with a CCIP message ID, you'll execute the script to query the current status of a cross-chain message.
## Before you begin
1. Initiate a CCIP transaction and note the CCIP message ID. You can obtain the CCIP message ID by running any of the previous CCIP tutorials.
2. Complete the prerequisite steps of the [Transfer Tokens between EOAs](/ccip/tutorials/evm/offchain/transfer-tokens-from-eoa#before-you-begin) tutorial.
## Tutorial
This tutorial shows you on how to check the status of a Chainlink CCIP transaction using the [`get-status.js`](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/offchain/javascript/src/get-status.js) script. By supplying the script with the source, destination chains, and your CCIP message ID, you can verify the current status of your cross-chain message.
**Execute the script in your command line:**
```bash
node src/get-status.js sourceChain destinationChain messageID
```
**The script requires the following parameters:**
- `sourceChain` is the identifier for the source blockchain. For example, `avalancheFuji`.
- `destinationChain` is the identifier for the destination blockchain. For example, `ethereumSepolia`.
- `messageID` is the unique identifier for the CCIP transaction message that you need to check.
**Example Usage:**
If you initiated a transaction from the Avalanche Fuji testnet to the Ethereum Sepolia testnet and received a message ID, you can check the status of this message with the following command:
```text
$ node src/get-status.js avalancheFuji ethereumSepolia 0x25d18c6adfc1f99514b40f9931a14ca08228cdbabfc5226c1e6a43ce7441595d
Status of message 0x25d18c6adfc1f99514b40f9931a14ca08228cdbabfc5226c1e6a43ce7441595d on offRamp 0x000b26f604eAadC3D874a4404bde6D64a97d95ca is SUCCESS
```
## Code Explanation
The JavaScript `get-status.js` is designed to check the status of a user-provided CCIP message. The contract code includes several code comments to clarify each step, but this section explains the key elements.
### Imports
The script imports the required modules and data:
- **Ethers.js**: [JavaScript library](https://docs.ethers.org/v6/) for interacting with the Ethereum Blockchain and its ecosystem.
- **Router and OffRamp Contract ABIs**: These Application Binary Interfaces (ABIs) enable the script to interact with specific smart contracts on the blockchain.
- **Configuration Functions**: Includes `getProviderRpcUrl` for retrieving the RPC URL of a blockchain, `getRouterConfig` for accessing the router smart contract's configuration, and `getMessageStatus` for translating numeric status codes into readable strings.
#### Understanding the `getMessageStatus` function
Before diving into the script execution, it's crucial to understand how the [`getMessageStatus`](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/offchain/javascript/src/config/offramp.js) function works. This function is designed to translate the numeric status codes returned by Solidity enums into human-readable statuses so they are clear to developers and users. The function uses a mapping defined in [`messageState.json`](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/offchain/config/messageState.json), which correlates to the [`MessageExecutionState`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.1/contracts/src/v0.8/ccip/libraries/Internal.sol#L144) enum used by the [Chainlink CCIP's OffRamp](/ccip/concepts/architecture/onchain/evm/components#offramp) contract.
### Handling Arguments
The `handleArguments` function ensures the script operates with the correct parameters. It validates the presence of three command-line arguments – the source chain identifier, the destination chain identifier, and the message ID.
### Main Function: getStatus
The script's core is encapsulated in the `getStatus` asynchronous function. This function completes initialization, configuration retrieval, and contract instantiation.
#### Initialization
Firstly, it establishes connections to the source and destination blockchain networks using the `JsonRpcProvider`.
#### Configuration Retrieval
The script then retrieves the configuration for router contracts on both the source and destination chains. This includes the router addresses and chain selectors.
#### Contract Instantiation
The script instantiates the source and destination router contracts using ethers and the router contract addresses.
#### Status Query
To query the status of the provided CCIP message ID, the script completes the following steps:
1. Check if the source chain's router supports the destination chain
2. Fetch OffRamp contracts associated with the destination router
3. Filter these contracts to find those that match the source chain
4. Query each matching OffRamp contract for an event related to the message ID
If an event is found, the script reads the status from the arguments. It translates the numeric status into a human-readable status and logs this information.
---
# Using CCIP CLI
Source: https://docs.chain.link/ccip/tutorials/evm/offchain/ccip-tools
Last Updated: 2025-05-19
These tutorials demonstrate how to use [CCIP Tools](https://github.com/smartcontractkit/ccip-tools-ts) to transfer tokens, check message status, and retrieve supported tokens across blockchains.
## Tutorials
- [Transfer Tokens Between EOAs](/ccip/tutorials/evm/offchain/ccip-tools/transfer-tokens-from-eoa): Learn how to transfer tokens between Externally Owned Accounts (EOAs) across different blockchains using Chainlink CCIP.
- [Check CCIP Message Status Off-Chain](/ccip/tutorials/evm/offchain/ccip-tools/get-status-offchain): Learn how to verify the status of Chainlink CCIP messages off-chain.
- [Get Supported Tokens](/ccip/tutorials/evm/offchain/ccip-tools/get-supported-tokens): Learn how to retrieve the list of supported tokens that can be transferred between chains using Chainlink CCIP.
---
# Transfer Tokens between EOAs
Source: https://docs.chain.link/ccip/tutorials/evm/offchain/ccip-tools/transfer-tokens-from-eoa
Last Updated: 2025-05-19
In this tutorial, you will use Chainlink CCIP to transfer tokens directly from your [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) to an account on a different blockchain. First, you will pay for CCIP fees on the source blockchain using LINK. Then, you will run the same example paying for CCIP fees in native gas, such as ETH on Ethereum or AVAX on Avalanche.
## Before you begin
1. [Install Node.js 18](https://nodejs.org/en/download/). Optionally, you can use the [nvm package](https://www.npmjs.com/package/nvm) to switch between Node.js versions with `nvm use 18`.
```shell
node -v
```
```shell
$ node -v
v18.7.0
```
2. Your [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) must have both AVAX and LINK tokens on *Avalanche Fuji* to pay for the gas fees and CCIP fees.
- [Configure MetaMask to use LINK tokens](/resources/acquire-link#configure-metamask-to-use-link-tokens)
- Acquire testnet AVAX and LINK from [faucets.chain.link/fuji](https://faucets.chain.link/fuji)
3. Check the [CCIP Directory](/ccip/directory) to confirm that the tokens you will transfer are supported for your lane. In this example, you will transfer tokens from *Avalanche Fuji* to *Ethereum Sepolia* so check the list of supported tokens [here](/ccip/directory/testnet/chain/avalanche-fuji-testnet). Alternatively, you can use the [Get Supported Tokens](/ccip/tutorials/evm/offchain/ccip-tools/get-supported-tokens) tutorial to retrieve the list of supported tokens programmatically.
4. Learn how to [acquire CCIP test tokens](/ccip/test-tokens#evm-chains). After following this guide, your [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) should have CCIP-BnM tokens, and CCIP-BnM should appear in the list of your tokens in MetaMask.
5. In a terminal, clone the [ccip-tools-ts repository](https://github.com/smartcontractkit/ccip-tools-ts) and change to the `ccip-tools-ts` directory.
```shell
git clone https://github.com/smartcontractkit/ccip-tools-ts && \
cd ccip-tools-ts
```
6. Run `npm install` to install the dependencies.
```shell
npm install
```
7. To make sure that the installation is correct and the `ccip-tools` CLI commands are available, run the following command:
```shell
./dist/ccip-tools-ts --help
```
8. Inside the project's root folder, i.e., `ccip-tools-ts`, create a `.env` file and add two environment variables to store the RPC URLs:
- `AVALANCHE_FUJI_RPC_URL`: Set this to a URL for the *Avalanche Fuji* testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider.
- `ETHEREUM_SEPOLIA_RPC_URL`: Set this to a URL for the *Ethereum Sepolia* testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider.
9. Commands of the [`ccip-tools`](https://github.com/smartcontractkit/ccip-tools-ts) that need to send transactions try to get the private key from a `USER_KEY` environment variable. For simplicity (not a recommended practice), if you are using a testnet wallet that only contains test tokens, you can export the `USER_KEY` environment variable into the current terminal session by running the following command:
```shell
export USER_KEY=
```
## Tutorial
### Transfer tokens and pay in LINK
In this example, you will transfer CCIP-BnM tokens from your EOA on *Avalanche Fuji* to an account on *Ethereum Sepolia*. The destination account could be an [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) or a smart contract. The example shows how to transfer CCIP-BnM tokens, but you can reuse the same example to transfer other tokens as long as they are supported for your [lane](/ccip/concepts/architecture/key-concepts#lane).
For this example, CCIP fees are paid in LINK tokens. To learn how to pay CCIP fees in native AVAX, read the [Pay in native](#transfer-tokens-and-pay-in-native) section. To transfer tokens and pay in LINK, use the following command:
```
./src/index.ts send \
--receiver \
--fee-token \
--transfer-tokens =
--gas-limit 0
```
- `source`: Chain ID or network name.
For example, `43113` for *Avalanche Fuji* or `11155111` for *Ethereum Sepolia*. You can also use the network name, such as `avalanche-testnet-fuji` or `ethereum-testnet-sepolia`.
You can find the supported network names and chain IDs that can be used for `source` in the
[`selectors.ts`](https://github.com/smartcontractkit/ccip-tools-ts/blob/main/src/lib/selectors.ts) file of the
`ccip-tools` repository.
- `router`: Router contract address on the source network.
- `dest`: Chain ID or network name.
For example, `43113` for *Avalanche Fuji* or `11155111` for *Ethereum Sepolia*. You can also use the network name, such as `avalanche-testnet-fuji` or `ethereum-testnet-sepolia`.
You can find the supported network names and chain IDs that can be used for `dest` in the
[`selectors.ts`](https://github.com/smartcontractkit/ccip-tools-ts/blob/main/src/lib/selectors.ts) file of the
`ccip-tools` repository.
- `destinationAccount`: Address of the destination account on the destination network. Skip this argument to use the same address as the source account.
- `feeTokenAddress`: Token address used to pay CCIP fees. Supported tokens for paying fees include LINK, the native gas token of the source blockchain (such as ETH for Ethereum), and the wrapped native gas token (such as WETH for Ethereum).
- `tokenAddress`: Address of the token to be transferred.
- `amount`: Amount of the token to be transferred.
- `gasLimit`: Gas limit for the transaction. This is optional and defaults to 200,000, which is the default value in the ramp config. You can set it to 0 when the transaction is directed to an Externally Owned Account (EOA).
Details such as the router contract address, LINK token address, and wrapped native gas token (like WETH) address can be found in the [CCIP Directory](https://docs.chain.link/ccip/directory) by searching for the relevant network.
Complete the following steps in your terminal:
1. Send 0.001 CCIP-BnM from your EOA on *Avalanche Fuji* to another account on *Ethereum Sepolia*:
```
./src/index.ts send 43113 0xF694E193200268f9a4868e4Aa017A0118C9a8177 11155111 \
--receiver 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf \
--fee-token 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846 \
--transfer-tokens 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4=0.001 \
--gas-limit 0
```
Command arguments:
| Argument | Explanation |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ./src/index.ts send | This executes the `send` command of the `ccip-tools`. |
| 43113 | This specifies the source blockchain, in this case, *Avalanche Fuji*. |
| 0xF694E193200268f9a4868e4Aa017A0118C9a8177 | This specifies the router address on the source blockchain, in this case, *Avalanche Fuji*. |
| 11155111 | This specifies the destination blockchain, which is *Ethereum Sepolia* in this case. |
| --receiver | This specifies the `receiver` flag followed by the account address on the destination blockchain. Skip this argument if you want to use the same address as the source account. |
| `YOUR_ACCOUNT` | This is the account address on the destination blockchain that is supposed to receive the tokens. You can replace this with your account address. |
| --fee-token | This specifies the `fee-token` flag followed by the fee token address on the source blockchain. |
| 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846 | Since you will pay for CCIP fees in LINK, this is the LINK token contract address on *Avalanche Fuji*. The LINK contract address can be found on the [Link Token contracts page](/resources/link-token-contracts?parent=ccip). |
| --transfer-tokens | This specifies the `transfer-tokens` flag, followed by the token address and the amount of tokens to transfer, separated by `=`, as in `--transfer-tokens =`. |
| 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 | This is the CCIP-BnM token contract address on Avalanche Fuji. The contract addresses for each network can be found on the [CCIP Directory](/ccip/directory). |
| 0.001 | This is the amount of CCIP-BnM tokens to be transferred. In this example, 0.001 CCIP-BnM are transferred. The CCIP-BnM token has 18 decimals, so 0.001 would be `1000000000000000` in 18-decimal format. |
| --gas-limit 0 | This specifies the gas limit for the transaction. This is optional and defaults to `200000`, which is the default value in the ramp config. Set it to `0` when the transaction is directed to an Externally Owned Account (EOA). |
2. Once you execute the command, you should see the following logs:
```
$ ./src/index.ts send 43113 0xF694E193200268f9a4868e4Aa017A0118C9a8177 11155111 \
--receiver 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf \
--fee-token 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846 \
--transfer-tokens 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4=0.001 \
--gas-limit 0
Approving 1000000000000000n 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 for 0xF694E193200268f9a4868e4Aa017A0118C9a8177 = 0xa3fd8053a74b71f34c4c280f10fdcba51ea105093998f8349db14485473da912
Approving 23112499163862214n 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846 for 0xF694E193200268f9a4868e4Aa017A0118C9a8177 = 0xb21d2822c7211a6bd39310ae78f4b59ef54ed6271fea21c949d9be78a30f12a7
Sending message to 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf @ ethereum-testnet-sepolia , tx_hash = 0x70858cfeadcfbd1404a65dd4116b549801bde3d184eb324f895529286e15249a
Lane:
┌────────────────┬──────────────────────────────────────────────┬────────────────────────────┐
│ (index) │ source │ dest │
├────────────────┼──────────────────────────────────────────────┼────────────────────────────┤
│ name │ 'avalanche-testnet-fuji' │ 'ethereum-testnet-sepolia' │
│ chainId │ 43113 │ 11155111 │
│ chainSelector │ 14767482510784806043n │ 16015286601757825753n │
│ onRamp/version │ '0x75b9a75Ee1fFef6BE7c4F842a041De7c6153CF4E' │ '1.5.0' │
└────────────────┴──────────────────────────────────────────────┴────────────────────────────┘
Request (source):
┌─────────────────┬──────────────────────────────────────────────────────────────────────┐
│ (index) │ Values │
├─────────────────┼──────────────────────────────────────────────────────────────────────┤
│ messageId │ '0x934f57925b5d8fbc763c2a06dfe2d003676816f8ea67392d3e7888a45469d4c1' │
│ origin │ '0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA' │
│ sender │ '0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA' │
│ receiver │ '0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf' │
│ sequenceNumber │ 3859 │
│ nonce │ 17 │
│ gasLimit │ 0 │
│ transactionHash │ '0x70858cfeadcfbd1404a65dd4116b549801bde3d184eb324f895529286e15249a' │
│ logIndex │ 6 │
│ blockNumber │ 41234897 │
│ timestamp │ '2025-06-02 16:19:51 (10s ago)' │
│ finalized │ true │
│ fee │ '0.023112499163862214 LINK' │
│ tokens │ '0.001 CCIP-BnM' │
│ data │ '0x' │
└─────────────────┴──────────────────────────────────────────────────────────────────────┘
```
3. Analyze the logs:
- The script communicates with the router to calculate the transaction fees required to transfer tokens, which amounts to 40,903,083,926,519,498 Juels (equivalent to 0.04 LINK).
- The script engages with the Link token contract, authorizing the router contract to spend 40,903,083,926,519,498 Juels for the fees and 1000000000000000 (0.001 CCIP-BnM) from your Externally Owned Account (EOA) balance.
- The script initiates a transaction through the router to transfer 1000000000000000 (0.001 CCIP-BnM) to your account on *Ethereum Sepolia*. It also returns the CCIP message ID.
4. While the script is waiting for the cross-chain transaction to proceed, open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the message ID. Notice that the status is not finalized yet.
5. After several minutes (the waiting time depends on the [finality of the source blockchain](/ccip/ccip-execution-latency)), the transaction should be finalized on the source chain. Once finalized, the corresponding transaction is executed on the destination chain by the DON. After execution, the status will be shown as `SUCCESS` in the CCIP explorer.
6. Open the [CCIP explorer](https://ccip.chain.link/) and use the message ID to find your cross-chain transaction.
7. The data field is empty because only tokens are transferred. The gas limit is set to `0` because, although the default value in the ramp config is `200,000`, you can override it by passing the `--gas-limit` flag. Setting it to `0` is appropriate when the transaction is directed to an Externally Owned Account (EOA). With an empty data field, no function calls on a smart contract are expected on the destination chain.
### Transfer tokens and pay in native
In this example, you will transfer CCIP-BnM tokens from your EOA on *Avalanche Fuji* to an account on *Ethereum Sepolia*. The destination account could be an [EOA (Externally Owned Account)](https://ethereum.org/en/developers/docs/accounts/#types-of-account) or a smart contract. The example shows how to transfer CCIP-BnM tokens, but you can reuse the same example to transfer other tokens as long as they are supported for your [lane](/ccip/concepts/architecture/key-concepts#lane).
For this example, CCIP fees are paid in Avalanche Fuji's native AVAX. To learn how to pay CCIP fees in LINK, read the [Pay in LINK](#transfer-tokens-and-pay-in-link) section.
To transfer tokens and pay in native, use the following command:
```
./src/index.ts send \
--receiver \
--transfer-tokens = \
--gas-limit 0
```
Complete the following steps in your terminal:
1. Send 0.001 CCIP-BnM from your EOA on *Avalanche Fuji* to another account on *Ethereum Sepolia*:
```
./src/index.ts send 43113 0xF694E193200268f9a4868e4Aa017A0118C9a8177 11155111 \
--receiver 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf \
--transfer-tokens 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4=0.001 \
--gas-limit 0
```
Command arguments:
| Argument | Explanation |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ./src/index.ts send | This executes the `send` command of the `ccip-tools`. |
| 43113 | This specifies the source blockchain, in this case, *Avalanche Fuji*. |
| 0xF694E193200268f9a4868e4Aa017A0118C9a8177 | This specifies the router address on the source blockchain, in this case, *Avalanche Fuji*. |
| 11155111 | This specifies the destination blockchain, which is *Ethereum Sepolia* in this case. |
| --receiver | This specifies the `receiver` flag followed by the account address on the destination blockchain. Skip this argument if you want to use the same address as the source account. |
| `YOUR_ACCOUNT` | This is the account address on the destination blockchain that is supposed to receive the tokens. You can replace this with your account address. |
| --transfer-tokens | This specifies the `transfer-tokens` flag, followed by the token address and the amount of tokens to transfer, separated by `=`, as in `--transfer-tokens =`. |
| 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 | This is the CCIP-BnM token contract address on Avalanche Fuji. The contract addresses for each network can be found on the [CCIP Directory](/ccip/directory). |
| 0.001 | This is the amount of CCIP-BnM tokens to be transferred. In this example, 0.001 CCIP-BnM are transferred. The CCIP-BnM token has 18 decimals, so 0.001 would be `1000000000000000` in 18-decimal format. |
| --gas-limit 0 | This specifies the gas limit for the transaction. This is optional and defaults to `200000`, which is the default value in the ramp config. Set it to `0` when the transaction is directed to an Externally Owned Account (EOA). |
2. After you execute the command, you should see the following logs:
```
$ ./src/index.ts send 43113 0xF694E193200268f9a4868e4Aa017A0118C9a8177 11155111 \
--receiver 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf \
--transfer-tokens 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4=0.001 \
--gas-limit 0
Approving 1000000000000000n 0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4 for 0xF694E193200268f9a4868e4Aa017A0118C9a8177 = 0x1ea6d165cf627fd4f6856520fc9afa2e08c4e04f79fd78bf7f4ef7da94692503
Sending message to 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf @ ethereum-testnet-sepolia , tx_hash = 0x0a00f9240b6860e34a0664ad0aa8f8e86877d70b97e9787e08e270bea564edce
Lane:
┌────────────────┬──────────────────────────────────────────────┬────────────────────────────┐
│ (index) │ source │ dest │
├────────────────┼──────────────────────────────────────────────┼────────────────────────────┤
│ name │ 'avalanche-testnet-fuji' │ 'ethereum-testnet-sepolia' │
│ chainId │ 43113 │ 11155111 │
│ chainSelector │ 14767482510784806043n │ 16015286601757825753n │
│ onRamp/version │ '0x75b9a75Ee1fFef6BE7c4F842a041De7c6153CF4E' │ '1.5.0' │
└────────────────┴──────────────────────────────────────────────┴────────────────────────────┘
Request (source):
┌─────────────────┬──────────────────────────────────────────────────────────────────────┐
│ (index) │ Values │
├─────────────────┼──────────────────────────────────────────────────────────────────────┤
│ messageId │ '0xd902134a69bff565005c354996386479f9b1204b1810f49e27abc8c413c64312' │
│ origin │ '0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA' │
│ sender │ '0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA' │
│ receiver │ '0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf' │
│ sequenceNumber │ 3861 │
│ nonce │ 18 │
│ gasLimit │ 0 │
│ transactionHash │ '0x0a00f9240b6860e34a0664ad0aa8f8e86877d70b97e9787e08e270bea564edce' │
│ logIndex │ 11 │
│ blockNumber │ 41235059 │
│ timestamp │ '2025-06-02 16:25:12 (7s ago)' │
│ finalized │ true │
│ fee │ '0.019124641265363576 WAVAX' │
│ tokens │ '0.001 CCIP-BnM' │
│ data │ '0x' │
└─────────────────┴──────────────────────────────────────────────────────────────────────┘
```
3. Analyze the logs:
- The script interacts with the CCIP-BnM token contract, authorizing the router contract to deduct 0.001 CCIP-BnM from your Externally Owned Account (EOA) balance.
- The script initiates a transaction through the router to transfer 0.001 CCIP-BnM tokens to your destination account on *Ethereum Sepolia*. It also returns the CCIP message ID.
- The script continuously monitors the destination blockchain (*Ethereum Sepolia*) to track the progress and completion of the cross-chain transaction.
4. While the script is waiting for the cross-chain transaction to proceed, open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the message ID. Notice that the status is not finalized yet.
5. After several minutes (the waiting time depends on the [finality of the source blockchain](/ccip/ccip-execution-latency)), the transaction should be finalized on the source chain. Once finalized, the corresponding transaction is executed on the destination chain by the DON. After execution, the status will be shown as `SUCCESS` in the CCIP explorer.
6. Open the [CCIP explorer](https://ccip.chain.link/) and use the message ID to find your cross-chain transaction.
7. The data field is empty because only tokens are transferred. The gas limit is set to `0` because, although the default value in the ramp config is `200,000`, you can override it by passing the `--gas-limit` flag. Setting it to `0` is appropriate when the transaction is directed to an Externally Owned Account (EOA). With an empty data field, no function calls on a smart contract are expected on the destination chain.
---
# Checking CCIP Message Status
Source: https://docs.chain.link/ccip/tutorials/evm/offchain/ccip-tools/get-status-offchain
Last Updated: 2025-05-19
In this tutorial, you will learn how to verify the status of a Chainlink CCIP transaction offchain using [CCIP Tools](https://github.com/smartcontractkit/ccip-tools-ts). Starting with a CCIP source transaction hash, you'll execute the [`show`](https://github.com/smartcontractkit/ccip-tools-ts/blob/main/README.md#show-default-command) command of the `ccip-tools` to query the current status of a cross-chain message.
## Before you begin
1. Initiate a CCIP transaction and note the CCIP source transaction hash (i.e., the transaction hash on the source blockchain). You can obtain the CCIP source transaction hash by running any of the previous CCIP tutorials.
2. Complete the prerequisite steps of the [Transfer Tokens between EOAs](/ccip/tutorials/evm/offchain/ccip-tools/transfer-tokens-from-eoa#before-you-begin) tutorial.
## Tutorial
This tutorial shows you on how to check the status of a Chainlink CCIP transaction using the [`show`](https://github.com/smartcontractkit/ccip-tools-ts/blob/main/README.md#show-default-command) command of the `ccip-tools`. By supplying the command with the source transaction hash, you can verify the current status of your cross-chain message.
**Execute the script in your command line:**
```bash
./dist/ccip-tools-ts show --page 500
```
**The script requires the following parameters:**
- `sourceTransactionHash`: Transaction hash of the request (source) message. For example, the transaction hash on the *Avalanche Fuji* network in the [Transfer Tokens between EOAs](/ccip/tutorials/evm/offchain/transfer-tokens-from-eoa#before-you-begin) tutorial.
**Example Usage:**
If you initiated a transaction from *Avalanche Fuji* to *Ethereum Sepolia* and received the source transaction hash, you can check the status of your CCIP message with the following command:
```text
$ ./src/index.ts show 0x980dacf245f9c6919678219e97d6ad20e0c1964795ec3801e688315f1f18defd --page 500
Lane:
┌────────────────┬──────────────────────────────────────────────┬────────────────────────────┐
│ (index) │ source │ dest │
├────────────────┼──────────────────────────────────────────────┼────────────────────────────┤
│ name │ 'avalanche-testnet-fuji' │ 'ethereum-testnet-sepolia' │
│ chainId │ 43113 │ 11155111 │
│ chainSelector │ 14767482510784806043n │ 16015286601757825753n │
│ onRamp/version │ '0x75b9a75Ee1fFef6BE7c4F842a041De7c6153CF4E' │ '1.5.0' │
└────────────────┴──────────────────────────────────────────────┴────────────────────────────┘
Request (source):
┌─────────────────┬──────────────────────────────────────────────────────────────────────┐
│ (index) │ Values │
├─────────────────┼──────────────────────────────────────────────────────────────────────┤
│ messageId │ '0x1ce5213bf9880b18be7f44d5ab1065e603ec3a83eb1bebf76af366ed3c0de0b3' │
│ origin │ '0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA' │
│ sender │ '0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA' │
│ receiver │ '0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf' │
│ sequenceNumber │ 3835 │
│ nonce │ 4 │
│ gasLimit │ 200000 │
│ transactionHash │ '0x980dacf245f9c6919678219e97d6ad20e0c1964795ec3801e688315f1f18defd' │
│ logIndex │ 6 │
│ blockNumber │ 40954056 │
│ timestamp │ '2025-05-26 16:34:14 (2h8m33s ago)' │
│ finalized │ true │
│ fee │ '0.040903083926519498 LINK' │
│ tokens │ '0.001 CCIP-BnM' │
│ data │ '0x' │
└─────────────────┴──────────────────────────────────────────────────────────────────────┘
Commit (dest):
┌─────────────────┬──────────────────────────────────────────────────────────────────────┐
│ (index) │ Values │
├─────────────────┼──────────────────────────────────────────────────────────────────────┤
│ merkleRoot │ '0x1ce5213bf9880b18be7f44d5ab1065e603ec3a83eb1bebf76af366ed3c0de0b3' │
│ min │ 3835 │
│ max │ 3835 │
│ origin │ '0x9e587c646d4f4e46B71a02179Fa8951CFB34A382' │
│ contract │ '0x139E06b6dBB1a0C41A1686C091795879c943765A' │
│ transactionHash │ '0xbda1e294e59910e2929e6aec08e52426a9125c1ac20509b9d5b9441789b746b0' │
│ blockNumber │ 8411619 │
│ timestamp │ '2025-05-26 16:35:00 (46s after request)' │
└─────────────────┴──────────────────────────────────────────────────────────────────────┘
Receipts (dest):
┌─────────────────┬──────────────────────────────────────────────────────────────────────┐
│ (index) │ Values │
├─────────────────┼──────────────────────────────────────────────────────────────────────┤
│ state │ '✅ success' │
│ returnData │ '0x' │
│ origin │ '0xdA743Ce0Eb7cC541093F030A3126bF9e3d427E93' │
│ offRamp │ '0x1DEBa99dC8e2A77832461BD386d83D9FCb133137' │
│ transactionHash │ '0xe68ae80ed0b77d6e22f066a08c169c873dd22112ef5f27287bbe85e737c6ec60' │
│ logIndex │ 114 │
│ blockNumber │ 8411627 │
│ timestamp │ '2025-05-26 16:36:36 (2m22s after request)' │
└─────────────────┴──────────────────────────────────────────────────────────────────────┘
```
---
# Get Supported Tokens
Source: https://docs.chain.link/ccip/tutorials/evm/offchain/ccip-tools/get-supported-tokens
Last Updated: 2025-05-19
In this tutorial, you will learn how to retrieve the list of supported tokens that can be transferred between chains using Chainlink CCIP, with [CCIP Tools](https://github.com/smartcontractkit/ccip-tools-ts). You'll use the [`getSupportedTokens`](https://github.com/smartcontractkit/ccip-tools-ts/blob/main/README.md#getsupportedtokens) command of `ccip-tools` to query the list of supported tokens that can be transferred from a specific source chain to a specific destination chain.
## Before you begin
Complete the prerequisite steps of the [Transfer Tokens between EOAs](/ccip/tutorials/evm/offchain/ccip-tools/transfer-tokens-from-eoa#before-you-begin) tutorial.
## Tutorial
This tutorial shows you how to retrieve the list of supported tokens that can be transferred between chains using Chainlink CCIP. By supplying the [`getSupportedTokens`](https://github.com/smartcontractkit/ccip-tools-ts/blob/main/README.md#getsupportedtokens) command of `ccip-tools` with a source chain, source router address, and a destination chain, you can get the list of supported tokens that can be transferred from the source chain to the destination chain.
**Execute the script in your command line:**
```bash
./src/index.ts getSupportedTokens
```
**The script requires the following parameters:**
- `source`: Chain ID or network name.
For example, `43113` for *Avalanche Fuji* or `11155111` for *Ethereum Sepolia*. You can also use the network name, such as `avalanche-testnet-fuji` or `ethereum-testnet-sepolia`.
You can find the supported network names and chain IDs that can be used for `source` in the
[`selectors.ts`](https://github.com/smartcontractkit/ccip-tools-ts/blob/main/src/lib/selectors.ts) file of the
`ccip-tools` repository.
- `router`: Router contract address on the source network.
You can find the router contract address in the [CCIP Directory](https://docs.chain.link/ccip/directory) by searching for the relevant network.
- `dest`: Chain ID or network name.
For example, `43113` for *Avalanche Fuji* or `11155111` for *Ethereum Sepolia*. You can also use the network name, such as `avalanche-testnet-fuji` or `ethereum-testnet-sepolia`.
You can find the supported network names and chain IDs that can be used for `dest` in the
[`selectors.ts`](https://github.com/smartcontractkit/ccip-tools-ts/blob/main/src/lib/selectors.ts) file of the
`ccip-tools` repository.
**Example Usage:**
If you would like to retrieve the list of supported tokens that can be transferred from *Avalanche Fuji* to *Ethereum Sepolia*, you can run the following command:
```text
$ ./src/index.ts getSupportedTokens 43113 0xF694E193200268f9a4868e4Aa017A0118C9a8177 11155111
[INFO] Starting token discovery for cross-chain transfers
[INFO] Using TokenAdminRegistry 1.5.0 at 0xA92053a4a3922084d992fD2835bdBa4caC6877e6 from router 0xF694E193200268f9a4868e4Aa017A0118C9a8177
┌────────────────────────────────────┬──────────────────────────────────────────────┐
│ (index) │ Values │
├────────────────────────────────────┼──────────────────────────────────────────────┤
│ address │ '0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4' │
│ symbol │ 'CCIP-BnM' │
│ name │ 'CCIP-BnM' │
│ decimals │ 18 │
│ pool │ '0x10e3A37ff21c20CD802fdAF0204e2Ff04e5485ee' │
│ pool.typeAndVersion │ 'BurnMintTokenPool 1.5.1' │
│ remoteToken │ '0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05' │
│ remotePools │ '0x4CcbDd6CF18800360161E4D2A519A2047176bDF0' │
│ rateLimiters.outbound.tokens │ '100000.0' │
│ rateLimiters.outbound.capacity │ '100000.0' │
│ rateLimiters.outbound.rate │ '167.0' │
│ rateLimiters.outbound.timeToRefill │ '9m58s' │
│ rateLimiters.inbound.tokens │ '100000.0' │
│ rateLimiters.inbound.capacity │ '100000.0' │
│ rateLimiters.inbound.rate │ '167.0' │
│ rateLimiters.inbound.timeToRefill │ '9m58s' │
└────────────────────────────────────┴──────────────────────────────────────────────┘
┌────────────────────────────────────┬──────────────────────────────────────────────┐
│ (index) │ Values │
├────────────────────────────────────┼──────────────────────────────────────────────┤
│ address │ '0x70F5c5C40b873EA597776DA2C21929A8282A3b35' │
│ symbol │ 'clCCIP-LnM' │
│ name │ 'clCCIP-LnM' │
│ decimals │ 18 │
│ pool │ '0x8e35eB0dfb39Ec5F84254C3f863986a913171E0B' │
│ pool.typeAndVersion │ 'BurnMintTokenPoolAndProxy 1.5.0' │
│ remoteToken │ '0x466D489b6d36E7E3b824ef491C225F5830E81cC1' │
│ remotePools │ '0x658FdaC59a197D5166151640b7a673F7dF1Ba324' │
│ rateLimiters.outbound.tokens │ '100000.0' │
│ rateLimiters.outbound.capacity │ '100000.0' │
│ rateLimiters.outbound.rate │ '167.0' │
│ rateLimiters.outbound.timeToRefill │ '9m58s' │
│ rateLimiters.inbound.tokens │ '100000.0' │
│ rateLimiters.inbound.capacity │ '100000.0' │
│ rateLimiters.inbound.rate │ '167.0' │
│ rateLimiters.inbound.timeToRefill │ '9m58s' │
└────────────────────────────────────┴──────────────────────────────────────────────┘
┌────────────────────────────────────┬──────────────────────────────────────────────┐
│ (index) │ Values │
├────────────────────────────────────┼──────────────────────────────────────────────┤
│ address │ '0x5425890298aed601595a70AB815c96711a31Bc65' │
│ symbol │ 'USDC' │
│ name │ 'USD Coin' │
│ decimals │ 6 │
│ pool │ '0x5931822f394baBC2AACF4588E98FC77a9f5aa8C9' │
│ pool.typeAndVersion │ 'USDCTokenPool 1.5.1' │
│ remoteToken │ '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' │
│ remotePools[0] │ '0xb48EacF882dC4899Ae750AF4a6E2892E11866d8D' │
│ remotePools[1] │ '0xAff3fE524ea94118EF09DaDBE3c77ba6AA0005EC' │
│ rateLimiters.outbound.tokens │ '100000.0' │
│ rateLimiters.outbound.capacity │ '100000.0' │
│ rateLimiters.outbound.rate │ '167.0' │
│ rateLimiters.outbound.timeToRefill │ '9m58s' │
│ rateLimiters.inbound.tokens │ '100000.0' │
│ rateLimiters.inbound.capacity │ '100000.0' │
│ rateLimiters.inbound.rate │ '167.0' │
│ rateLimiters.inbound.timeToRefill │ '9m58s' │
└────────────────────────────────────┴──────────────────────────────────────────────┘
......
......
......
┌───────────────────────┬──────────────────────────────────────────────┐
│ (index) │ Values │
├───────────────────────┼──────────────────────────────────────────────┤
│ address │ '0x838AB8F15867440A41C4149bcbba6ae402EfAd38' │
│ symbol │ 'USDf' │
│ name │ 'Falcon USD' │
│ decimals │ 18 │
│ pool │ '0x8210c0634AB8f273806e4b7866E9Db353773c44B' │
│ pool.typeAndVersion │ 'BurnMintTokenPool 1.5.1' │
│ remoteToken │ '0x3E34bFC2872534C331b6db2E4b3593FA7eaEddFd' │
│ remotePools │ '0x21017CEC5f89fEb2c4B0F6C114E55Fa8EFDB00f2' │
│ rateLimiters.outbound │ 'disabled' │
│ rateLimiters.inbound │ 'disabled' │
└───────────────────────┴──────────────────────────────────────────────┘
┌───────────────────────┬──────────────────────────────────────────────┐
│ (index) │ Values │
├───────────────────────┼──────────────────────────────────────────────┤
│ address │ '0xDA89A6C2c9f6E8d94E4a65d8AEe482908e9D709A' │
│ symbol │ 'CCTWT' │
│ name │ 'CCT Wizard Token' │
│ decimals │ 18 │
│ pool │ '0x3cD3CFAFb82a1Fd711753bb1a51F06a284bbd972' │
│ pool.typeAndVersion │ 'BurnMintTokenPool 1.5.1' │
│ remoteToken │ '0x74Ef0b124f192e0990B5451Ad12A4EC20FCf2B44' │
│ remotePools │ '0x4dC3aA202138B40CC15867f2ab59153cF6FF83c4' │
│ rateLimiters.outbound │ 'disabled' │
│ rateLimiters.inbound │ 'disabled' │
└───────────────────────┴──────────────────────────────────────────────┘
┌───────────────────────┬──────────────────────────────────────────────┐
│ (index) │ Values │
├───────────────────────┼──────────────────────────────────────────────┤
│ address │ '0x9BDdEBA9B0c051Ad5C1819D2F2671Af8B4D360B3' │
│ symbol │ 'TT1' │
│ name │ 'Test Token 1' │
│ decimals │ 18 │
│ pool │ '0x4Fc3534dEB27Bb61AfFFC5c02222594aF3024A61' │
│ pool.typeAndVersion │ 'BurnMintTokenPool 1.5.1' │
│ remoteToken │ '0x0325d145398Eeb977a55B39CC7847C48b84D93f5' │
│ remotePools │ '0x0020267Ef0F96666A8CA236DB592547b14106807' │
│ rateLimiters.outbound │ 'disabled' │
│ rateLimiters.inbound │ 'disabled' │
└───────────────────────┴──────────────────────────────────────────────┘
Summary: totalTokens = 1070 , supportedTokens = 196
```
---
# Cross-Chain Token (CCT) Tutorials
Source: https://docs.chain.link/ccip/tutorials/evm/cross-chain-tokens
Last Updated: 2025-05-19
Before diving into the [tutorials](#tutorials), it's important first to understand the overall procedure for enabling your tokens in CCIP. This procedure involves deploying tokens and token pools, registering administrative roles, and configuring token pools to enable secure token transfers using CCIP. The diagram below outlines the entire process:
### Understanding the Procedure
The steps in the diagram highlight the flow of actions needed to enable a token for cross-chain transfers. These steps will be the foundation of the tutorials. Whether you're working with an Externally Owned Account (EOA) or a **Smart Account** (such as one using a multisig scheme), the overall logic remains the same. You'll follow the same process to enable cross-chain token transfers, configure pools, and register administrative roles.
In the following tutorials, we will walk through each step of the process to give you hands-on experience, from deploying your token to registering and configuring token pools. The process will apply equally whether you use an EOA or a Smart Account (such as with multisig transactions), ensuring flexibility across different account types.
### Key Steps to Keep in Mind:
1. **Token Deployment**: If the token is not yet deployed, you'll deploy an [ERC20-compatible token](/ccip/concepts/cross-chain-token/evm/tokens).
2. **Admin Registration**: The token administrator must be registered in the [`TokenAdminRegistry`](/ccip/api-reference/evm/v1.6.2/token-admin-registry) via self-service.
3. **Pool Deployment and Configuration**: [Token pools](/ccip/concepts/cross-chain-token/evm/token-pools#common-requirements) are deployed, linked to tokens, and configured to manage cross-chain token transfers.
The tutorials will implement the logic of this process, which involves deploying and configuring token pools and registering administrative roles, step-by-step.
## Tutorials
- [Deploy Using Remix IDE](/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-remix): Learn how to deploy and register cross-chain tokens using only your browser and Remix IDE.
- No development environment setup required
- Great for quick testing and learning
- [Register from an EOA (Burn & Mint)](/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-burn-mint-hardhat): Learn how to register a cross-chain token with the **Burn & Mint** mechanism using an EOA.
- [Hardhat version](/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-burn-mint-hardhat)
- [Foundry version](/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-burn-mint-foundry)
- [Register from an EOA (Lock & Mint)](/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-lock-mint-hardhat): Learn how to register a cross-chain token with the **Lock & Mint** mechanism using an EOA.
- [Hardhat version](/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-lock-mint-hardhat)
- [Foundry version](/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-lock-mint-foundry)
- [Set Token Pool Rate Limits](/ccip/tutorials/evm/cross-chain-tokens/update-rate-limiters-hardhat): Learn how to set rate limits for token pools to control cross-chain token transfers.
- [Hardhat version](/ccip/tutorials/evm/cross-chain-tokens/update-rate-limiters-hardhat)
- [Foundry version](/ccip/tutorials/evm/cross-chain-tokens/update-rate-limiters-foundry)
- [Register from a Safe (Burn & Mint)](/ccip/tutorials/evm/cross-chain-tokens/register-from-safe-burn-mint-hardhat): Learn how to register a cross-chain token with the **Burn & Mint** mechanism using a Safe Smart Account.
- [Hardhat version](/ccip/tutorials/evm/cross-chain-tokens/register-from-safe-burn-mint-hardhat)
- [Configure Additional Networks](/ccip/tutorials/evm/cross-chain-tokens/configure-additional-networks-hardhat): Learn how to configure your cross-chain tokens and pools to operate on additional networks beyond the initial setup.
- [Hardhat version](/ccip/tutorials/evm/cross-chain-tokens/configure-additional-networks-hardhat)
- [Foundry version](/ccip/tutorials/evm/cross-chain-tokens/configure-additional-networks-foundry)
---
# Deploy & Register Cross-Chain Tokens with Remix IDE
Source: https://docs.chain.link/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-remix
Last Updated: 2025-05-19
## Overview
This tutorial guides you through the process of enabling two tokens for cross-chain transfers. Upon completion, the system will allow users to **transfer tokens** seamlessly between selected blockchains.
**Expected Outcomes**
- **Two Tokens**: Deploy and configure one token on each blockchain
- **Registration**: Register the tokens in CCIP on both blockchains
- **Two Token Pools**: Deploy and set up the necessary token pools to enable cross-chain transfers
## Tutorial Structure
This interactive tutorial provides a structured learning experience:
### Progress Tracking
- **Visual Dashboard**: A navigation panel tracks progress
- **Clear Checkpoints**: Automatic marking of completed steps
- **Address Management**: System tracking of deployed contracts
- **Guided Flow**: Logically sequenced steps
### Success Guidelines
✓ Complete steps sequentially - each step builds on previous work\
✓ Maintain a record of contract addresses for reference\
✓ Mark your progress using the provided checkboxes\
✓ Input contract addresses as prompted
## Tools Needed
### Web Browser
Any modern web browser
### Remix IDE
[Remix IDE](https://remix.ethereum.org) - Browser-based Ethereum IDE for smart contract development and deployment
### MetaMask
[MetaMask](https://metamask.io/) - Blockchain wallet for connecting to blockchains and signing transactions
## Before You Begin
## Tutorial
### Source Blockchain Setup
### Destination Blockchain Setup
### Configure Cross-Chain Communication
#### Source Chain Configuration
#### Destination Chain Configuration
---
# Enable your tokens in CCIP (Burn & Mint): Register from an EOA using Hardhat
Source: https://docs.chain.link/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-burn-mint-hardhat
Last Updated: 2025-10-22
This tutorial will guide you through the process of enabling your own tokens in CCIP using [Hardhat](https://hardhat.org/). You will learn how to deploy tokens and set up *Burn & Mint* token pools. After that, you will register them in CCIP and configure them without needing manual intervention. Finally, you will test the **Burn & Mint** token handling mechanism, where tokens are burned on the source blockchain and an equivalent amount is minted on the destination blockchain.
We will cover the following key steps:
1. **Deploying Tokens**: You will deploy your [`BurnMintERC20`](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol) tokens on the Avalanche Fuji and Arbitrum Sepolia testnets.
2. **Deploying Token Pools**: Once your tokens are deployed, you will deploy [`BurnMintTokenPool`](/ccip/api-reference/evm/v1.6.2/burn-mint-token-pool) token pools on Avalanche Fuji and Arbitrum Sepolia. These pools are essential for minting and burning tokens during cross-chain transfers. Each token will be linked to a pool, which will manage token transfers and ensure proper handling of assets across chains.
3. **Claiming Mint and Burn Roles**: You will [claim the mint and burn roles](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol#L142) for the token pools, allowing your token pools to control how tokens are minted and burned during cross-chain transfers.
4. **Claiming and Accepting the Admin Role**: This is a two-step process:
1. You will call the [`RegistryModuleOwnerCustom`](/ccip/api-reference/evm/v1.6.2/registry-module-owner-custom) contract's [`registerAdminViaOwner`](/ccip/api-reference/evm/v1.6.2/registry-module-owner-custom#registeradminviaowner) function to register your EOA as the token admin. This role is required to enable your token in CCIP.
2. Once claimed, you will call the [`TokenAdminRegistry`](/ccip/api-reference/evm/v1.6.2/token-admin-registry) contract's [`acceptAdminRole`](/ccip/api-reference/evm/v1.6.2/token-admin-registry#acceptadminrole) function to complete the registration process.
5. **Linking Tokens to Pools**: You will call the [`TokenAdminRegistry`](/ccip/api-reference/evm/v1.6.2/token-admin-registry) contract's [`setPool`](/ccip/api-reference/evm/v1.6.2/token-admin-registry#setpool) function to associate each token with its respective token pool.
6. **Configuring Token Pools**: You will call the [`applyChainUpdates`](/ccip/api-reference/evm/v1.6.2/token-pool#applychainupdates) function on your token pools to configure each pool by setting cross-chain transfer parameters, such as token pool rate limits and enabled destination chains.
7. **Minting Tokens**: You will call the [`mint`](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol#L128) function to mint tokens on Avalanche Fuji for your EOA. These tokens will later be used to test cross-chain transfers to Arbitrum Sepolia.
8. **Transferring Tokens**: Finally, you will transfer tokens from Avalanche Fuji to Arbitrum Sepolia using CCIP. You will have the option to pay CCIP fees in either LINK tokens or native gas tokens.
By the end of this tutorial, you will have successfully deployed, registered, configured, and enabled your tokens and token pools for use in CCIP.
## Before You Begin
1. Make sure you have Node.js v22.10.0 or above installed. If not, **install Node.js v22.10.0**:
[Download Node.js v22.10.0](https://nodejs.org/en/download/) if you don't have it installed. Optionally, you can use the [nvm package](https://www.npmjs.com/package/nvm) to switch between Node.js versions:
```bash
nvm use 22
```
Verify that the correct version of Node.js is installed:
```bash
node -v
```
Example output:
```bash
$ node -v
v22.15.0
```
2. **Clone the repository and navigate to the project directory:**
```bash
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
```
3. **Install dependencies for the project:**
```bash
npm install
```
4. **Compile the project:**
```bash
npm run compile
```
5. **Encrypt your environment variables for higher security:**\
The project uses [@chainlink/env-enc](https://www.npmjs.com/package/@chainlink/env-enc) to encrypt your environment variables at rest. Follow the steps below to configure your environment securely:
1. Set an encryption password for your environment variables:
```bash
npx env-enc set-pw
```
2. Set up a `.env.enc` file with the necessary variables for Avalanche Fuji and Ethereum Sepolia. Use the following command to add the variables:
```bash
npx env-enc set
```
Variables to configure:
- `AVALANCHE_FUJI_RPC_URL`: A URL for the *Avalanche Fuji* testnet. You can get a personal endpoint from services like [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/).
- `ETHEREUM_SEPOLIA_RPC_URL`: A URL for the *Ethereum Sepolia* testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/).
- `PRIVATE_KEY`: The private key for your testnet wallet. If you use MetaMask, you can follow this [guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/) to export your private key. **Note:** This key is required for signing transactions like token transfers.
- `ETHERSCAN_API_KEY`: An API key from Etherscan to verify your contracts. You can obtain one from [Etherscan](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics).
6. **Fund your EOA with LINK and native gas tokens**:\
Make sure your EOA has enough LINK and native gas tokens on Avalanche Fuji and Ethereum Sepolia to cover transaction fees. You can use the [Chainlink faucets](https://faucets.chain.link/) to get testnet tokens.
## Tutorial
### Deploy Tokens
In this step, you will use the `deployToken` task to deploy tokens on two testnets, Avalanche Fuji and Ethereum Sepolia. Below is an explanation of the parameters used during deployment:
| Parameter | Description | Default | Required |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `name` | The name of the token. This is the full name by which the token will be identified. | N/A | Yes |
| `symbol` | The symbol of the token. This is the shorthand (usually 3-5 letters) representing the token. | N/A | Yes |
| `decimals` | The number of decimals the token will use. For instance, `18` decimals means 1 token is represented as `1e18` smallest units. | `18` | No |
| `maxsupply` | The maximum supply of tokens. Use `0` for unlimited supply. | `0` | No |
| `premint` | The amount of tokens to be minted to the owner at the time of deployment. If set to `0`, no tokens will be minted to the owner during deployment. | `0` | No |
| `verifycontract` | Flag to verify the contract on Etherscan or a similar blockchain explorer. Pass this flag to enable verification, omit to skip. | `false` | No |
| `network` | The blockchain on which the token will be deployed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Deploy tokens, use the following commands, substituting the token name and symbol as needed:
1. **Deploy the token on Avalanche Fuji:**
```bash
npx hardhat deployToken --name "BnM sak" --symbol BnMsak --decimals 18 --maxsupply 0 --premint 100000000000000000000 --verifycontract --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T15:48:05.691Z info: 🚀 Deploying BurnMintERC20 to avalancheFuji...
2025-10-21T15:48:05.692Z info: name: BnM sak, symbol: BnMsak
2025-10-21T15:48:43.684Z info: ⏳ Deployment tx: 0x36109e1ff82c5063e231e036ea9d4d0d66421ff5edd61a0d5767e95793f54eb5
2025-10-21T15:48:43.684Z info: Waiting for 2 confirmation(s)...
2025-10-21T15:49:07.361Z info: ✅ Token deployed at: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-21T15:49:09.119Z info: Granting mint and burn roles to 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA...
2025-10-21T15:49:10.392Z info: Waiting for 2 confirmation(s)...
2025-10-21T15:50:20.800Z info: ✅ Mint/Burn roles granted.
2025-10-21T15:50:20.801Z info: Verifying contract...
📤 Submitted source code for verification on SnowTrace:
@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol:BurnMintERC20
Address: 0xb613B55897F07eAF430bF9a509498e55487305Ea
⏳ Waiting for verification result...
✅ Contract verified successfully on SnowTrace!
@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol:BurnMintERC20
Explorer: https://testnet.snowtrace.io/address/0xb613B55897F07eAF430bF9a509498e55487305Ea#code
2025-10-21T15:50:30.197Z info: ✅ Token contract verified successfully
```
2. **Deploy the token on Ethereum Sepolia:**
```bash
npx hardhat deployToken --name "BnM sak" --symbol BnMsak --decimals 18 --maxsupply 0 --premint 100000000000000000000 --verifycontract --network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T15:57:35.979Z info: 🚀 Deploying BurnMintERC20 to ethereumSepolia...
2025-10-21T15:57:35.980Z info: name: BnM sak, symbol: BnMsak
2025-10-21T15:57:47.934Z info: ⏳ Deployment tx: 0xa1559da1474d9a062a63b57f86e4c6047c8918778b4abe5c7f911a85b502094b
2025-10-21T15:57:47.934Z info: Waiting for 3 confirmation(s)...
2025-10-21T15:58:54.199Z info: ✅ Token deployed at: 0x6803e72f0af827577a49FC82d7Ed9291028d687c
2025-10-21T15:58:55.814Z info: Granting mint and burn roles to 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA...
2025-10-21T15:59:00.841Z info: Waiting for 3 confirmation(s)...
2025-10-21T15:59:37.640Z info: ✅ Mint/Burn roles granted.
2025-10-21T15:59:37.640Z info: Verifying contract...
📤 Submitted source code for verification on Etherscan:
@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol:BurnMintERC20
Address: 0x6803e72f0af827577a49FC82d7Ed9291028d687c
⏳ Waiting for verification result...
✅ Contract verified successfully on Etherscan!
@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol:BurnMintERC20
Explorer: https://sepolia.etherscan.io/address/0x6803e72f0af827577a49FC82d7Ed9291028d687c#code
2025-10-21T16:00:05.431Z info: ✅ Token contract verified successfully
```
### Deploy Token Pools
In this step, you will use the `deployTokenPool` task to deploy token pools for the tokens on both testnets, Avalanche Fuji and Ethereum Sepolia. Below is an explanation of the parameters used during deployment:
| Parameter | Description | Default | Required |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------- |
| `tokenaddress` | The address of the token for which the pool is being created. | N/A | Yes |
| `pooltype` | The type of pool to deploy. For this tutorial, we use `"burnMint"` for a pool that supports burning and minting of tokens. | `"burnMint"` | No |
| `localtokendecimals` | The number of decimals for the token on this chain. | `18` | No |
| `verifycontract` | Flag to verify the contract on Etherscan or a similar blockchain explorer. Pass this flag to enable verification, omit to skip. | `false` | No |
| `network` | The blockchain on which the token pool will be deployed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Deploy token pools using the following commands, replacing the token address with the one you deployed in the previous step:
1. **Deploy the burn and mint token pool on Avalanche Fuji:**
```bash
npx hardhat deployTokenPool \
--tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea \
--pooltype burnMint \
--localtokendecimals 18 \
--verifycontract \
--network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T16:03:29.749Z info: 🚀 Deploying burnMint pool on avalancheFuji
2025-10-21T16:03:29.750Z info: Token: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-21T16:03:29.750Z info: Decimals: 18
2025-10-21T16:03:29.750Z info: Allowlist: None
2025-10-21T16:04:31.691Z info: ⏳ Deployment tx: 0xc91a7fb0f1e77531bc49e9ce3c4de5f0bf8797e9e031e23c707b22b50fbb1547
2025-10-21T16:04:31.692Z info: Waiting for 2 confirmation(s)...
2025-10-21T16:04:32.196Z info: ✅ Token pool deployed at: 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A
2025-10-21T16:04:32.196Z info: Granting mint and burn roles to 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A on token 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-21T16:04:33.856Z info: Waiting for 2 confirmation(s)...
2025-10-21T16:05:45.050Z info: ✅ Mint/Burn roles granted
2025-10-21T16:05:45.051Z info: Verifying contract...
📤 Submitted source code for verification on SnowTrace:
@chainlink/contracts-ccip/contracts/pools/BurnMintTokenPool.sol:BurnMintTokenPool
Address: 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A
⏳ Waiting for verification result...
✅ Contract verified successfully on SnowTrace!
@chainlink/contracts-ccip/contracts/pools/BurnMintTokenPool.sol:BurnMintTokenPool
Explorer: https://testnet.snowtrace.io/address/0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A#code
2025-10-21T16:05:59.699Z info: ✅ Token pool contract verified successfully
```
2. **Deploy the burn and mint token pool on Ethereum Sepolia:**
```bash
npx hardhat deployTokenPool \
--tokenaddress 0x6803e72f0af827577a49FC82d7Ed9291028d687c \
--pooltype burnMint \
--localtokendecimals 18 \
--verifycontract \
--network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T16:08:04.592Z info: 🚀 Deploying burnMint pool on ethereumSepolia
2025-10-21T16:08:04.593Z info: Token: 0x6803e72f0af827577a49FC82d7Ed9291028d687c
2025-10-21T16:08:04.593Z info: Decimals: 18
2025-10-21T16:08:04.593Z info: Allowlist: None
2025-10-21T16:08:31.645Z info: ⏳ Deployment tx: 0x10e0b153a0a3e3fe3eccd512403fa01aa8c4a9c017e2f15293da85bf3d0fbc55
2025-10-21T16:08:31.645Z info: Waiting for 3 confirmation(s)...
2025-10-21T16:08:51.518Z info: ✅ Token pool deployed at: 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D
2025-10-21T16:08:51.518Z info: Granting mint and burn roles to 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D on token 0x6803e72f0af827577a49FC82d7Ed9291028d687c
2025-10-21T16:08:57.229Z info: Waiting for 3 confirmation(s)...
2025-10-21T16:09:27.333Z info: ✅ Mint/Burn roles granted
2025-10-21T16:09:27.333Z info: Verifying contract...
📤 Submitted source code for verification on Etherscan:
@chainlink/contracts-ccip/contracts/pools/BurnMintTokenPool.sol:BurnMintTokenPool
Address: 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D
⏳ Waiting for verification result...
✅ Contract verified successfully on Etherscan!
@chainlink/contracts-ccip/contracts/pools/BurnMintTokenPool.sol:BurnMintTokenPool
Explorer: https://sepolia.etherscan.io/address/0xA29878301cA30a4639C33cD7EC8061E4265E1a5D#code
2025-10-21T16:09:53.510Z info: ✅ Token pool contract verified successfully
```
### Claim Admin
In this step, you will use the `claimAdmin` task to register your EOA as the administrator for the deployed tokens on both testnets, Avalanche Fuji and Ethereum Sepolia. This process involves calling the `RegistryModuleOwnerCustom` contract, which will fetch the CCIP admin of the token and set it up as the admin in the registry.
Below is an explanation of the parameters used during the admin claim process:
| Parameter | Description | Default | Required |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | -------- |
| `tokenaddress` | The address of the token for which the admin role is being claimed. | N/A | Yes |
| `network` | The blockchain on which the claim admin process will be executed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Claim the admin role by using the following commands, replacing the token address with the one you deployed in the previous steps:
1. **Claim the admin role on Avalanche Fuji:**
```bash
npx hardhat claimAdmin --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T16:28:08.196Z info: 🎯 Claiming admin for 0xb613B55897F07eAF430bF9a509498e55487305Ea using getCCIPAdmin mode
2025-10-21T16:28:11.832Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is CCIP admin
2025-10-21T16:29:04.511Z info: 📤 TX sent: 0xa15fcb890ac37b81d3ecec9560624e38b7d127177c01af7d9359ab5f6b6ca97c. Waiting for 2 confirmations...
2025-10-21T16:29:13.730Z info: ✅ Admin claimed for 0xb613B55897F07eAF430bF9a509498e55487305Ea on avalancheFuji (2 confirmations)
```
2. **Claim the admin role on Ethereum Sepolia:**
```bash
npx hardhat claimAdmin --tokenaddress 0x6803e72f0af827577a49FC82d7Ed9291028d687c --network ethereumSepolia
```
Example output:
```bash
2025-10-21T16:30:14.261Z info: 🎯 Claiming admin for 0x6803e72f0af827577a49FC82d7Ed9291028d687c using getCCIPAdmin mode
2025-10-21T16:30:23.319Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is CCIP admin
2025-10-21T16:30:30.196Z info: 📤 TX sent: 0x5c7e4644fc88d8df99ce8557665a78037951c748c927b012bb4565285241ae3a. Waiting for 3 confirmations...
2025-10-21T16:31:02.943Z info: ✅ Admin claimed for 0x6803e72f0af827577a49FC82d7Ed9291028d687c on ethereumSepolia (3 confirmations)
```
### Accept Admin Role
In this step, you will use the `acceptAdminRole` task to accept the admin role for the deployed tokens on both testnets, Avalanche Fuji and Ethereum Sepolia. Once you have claimed the role, accepting the role finalizes your control over the token administration.
Below is an explanation of the parameters used during the admin role acceptance process:
| Parameter | Description | Default | Required |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token for which the admin role is being accepted. | N/A | Yes |
| `network` | The blockchain on which the accept admin process will be executed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Accept the admin role by using the following commands, replacing the token address with the one deployed in the previous steps:
1. **Accept the admin role on Avalanche Fuji:**
```bash
npx hardhat acceptAdminRole --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T16:32:22.560Z info: 🔄 Accepting admin role for 0xb613B55897F07eAF430bF9a509498e55487305Ea on avalancheFuji...
2025-10-21T16:32:25.385Z info: Checking pending admin for 0xb613B55897F07eAF430bF9a509498e55487305Ea...
2025-10-21T16:32:25.641Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is the pending admin
2025-10-21T16:32:25.641Z info: Accepting admin role...
2025-10-21T16:33:07.931Z info: 📤 TX sent: 0xa7b57a5d7c29828ddf4941c48877c02d36cbc5ec9a4ba401548fef9163256abb. Waiting for 2 confirmations...
2025-10-21T16:33:24.516Z info: ✅ Admin role accepted for 0xb613B55897F07eAF430bF9a509498e55487305Ea on avalancheFuji (2 confirmations)
```
2. **Accept the admin role on Ethereum Sepolia:**
```bash
npx hardhat acceptAdminRole --tokenaddress 0x6803e72f0af827577a49FC82d7Ed9291028d687c --network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T16:35:18.634Z info: 🔄 Accepting admin role for 0x6803e72f0af827577a49FC82d7Ed9291028d687c on ethereumSepolia...
2025-10-21T16:35:26.364Z info: Checking pending admin for 0x6803e72f0af827577a49FC82d7Ed9291028d687c...
2025-10-21T16:35:26.688Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is the pending admin
2025-10-21T16:35:26.688Z info: Accepting admin role...
2025-10-21T16:35:29.020Z info: 📤 TX sent: 0x5ede40fac823712bfa465f30258dcb5c90a3863f10db4e92d83d5165965221cd. Waiting for 3 confirmations...
2025-10-21T16:36:02.952Z info: ✅ Admin role accepted for 0x6803e72f0af827577a49FC82d7Ed9291028d687c on ethereumSepolia (3 confirmations)
```
### Set Pool
In this step, you will use the `setPool` task to link each token with its respective token pool on both testnets.
Below is an explanation of the parameters used during the pool setting process:
| Parameter | Description | Default | Required |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token to be linked to a pool. | N/A | Yes |
| `pooladdress` | The address of the pool associated with the token. | N/A | Yes |
| `network` | The blockchain on which the pool setting will be executed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Link each token with its respective token pool by using the following commands, replacing the token and pool addresses with the ones you deployed in the previous steps:
1. **Set the pool for Avalanche Fuji:**
```bash
npx hardhat setPool --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --pooladdress 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T16:37:52.758Z info: 🔗 Setting pool for token 0xb613B55897F07eAF430bF9a509498e55487305Ea on avalancheFuji...
2025-10-21T16:37:55.823Z info: 🔹 Using signer: 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca
2025-10-21T16:37:55.823Z info: Using TokenAdminRegistry: 0xA92053a4a3922084d992fD2835bdBa4caC6877e6
2025-10-21T16:37:56.225Z info: Checking token configuration for 0xb613B55897F07eAF430bF9a509498e55487305Ea...
2025-10-21T16:37:56.471Z info: Token 0xb613B55897F07eAF430bF9a509498e55487305Ea current admin: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA
2025-10-21T16:37:56.471Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is the token administrator
2025-10-21T16:37:56.471Z info: Setting pool 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A for token 0xb613B55897F07eAF430bF9a509498e55487305Ea...
2025-10-21T16:38:05.847Z info: 📤 TX sent: 0xc06c155a0a581a83cadec3e6c2d41f1291c255f27fd9a2bfccaa349e8d22527f. Waiting for 2 confirmations...
2025-10-21T16:39:22.658Z info: ✅ Pool successfully set for token 0xb613B55897F07eAF430bF9a509498e55487305Ea → 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A on avalancheFuji (2 confirmations)
```
2. **Set the pool for Ethereum Sepolia:**
```bash
npx hardhat setPool --tokenaddress 0x6803e72f0af827577a49FC82d7Ed9291028d687c --pooladdress 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D --network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T16:42:30.911Z info: 🔗 Setting pool for token 0x6803e72f0af827577a49FC82d7Ed9291028d687c on ethereumSepolia...
2025-10-21T16:42:39.838Z info: 🔹 Using signer: 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca
2025-10-21T16:42:39.838Z info: Using TokenAdminRegistry: 0x95F29FEE11c5C55d26cCcf1DB6772DE953B37B82
2025-10-21T16:42:40.266Z info: Checking token configuration for 0x6803e72f0af827577a49FC82d7Ed9291028d687c...
2025-10-21T16:42:40.702Z info: Token 0x6803e72f0af827577a49FC82d7Ed9291028d687c current admin: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA
2025-10-21T16:42:40.702Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is the token administrator
2025-10-21T16:42:40.702Z info: Setting pool 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D for token 0x6803e72f0af827577a49FC82d7Ed9291028d687c...
2025-10-21T16:42:45.309Z info: 📤 TX sent: 0x994e1e97275ee1ed7b0135afdf8b85071899036bde490602590cda05ab7e5dda. Waiting for 3 confirmations...
2025-10-21T16:43:16.490Z info: ✅ Pool successfully set for token 0x6803e72f0af827577a49FC82d7Ed9291028d687c → 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D on ethereumSepolia (3 confirmations)
```
### Configure Token Pools
In this step, you will use the `applyChainUpdates` task to initialize the token pool configuration on each blockchain to enable cross-chain transfers between Avalanche Fuji and Ethereum Sepolia. Below is an explanation of the parameters used:
| Parameter | Description | Default | Required |
| --------------------------- | --------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `pooladdress` | The address of the pool to be configured. | N/A | Yes |
| `remotechain` | The remote blockchain network (e.g., `ethereumSepolia` for Fuji pool, `avalancheFuji` for Sepolia pool). | N/A | Yes |
| `remotepooladdresses` | Comma-separated list of remote pool addresses. | N/A | Yes |
| `remotetokenaddress` | The address of the token on the remote chain. | N/A | Yes |
| `outboundratelimitenabled` | Flag to enable outbound rate limits for cross-chain transfers. Pass this flag to enable, omit to disable. | `false` | No |
| `outboundratelimitcapacity` | Maximum capacity for the outbound rate limiter (in wei). | `0` | No |
| `outboundratelimitrate` | Refill rate for the outbound rate limiter bucket (tokens per second, in wei). | `0` | No |
| `inboundratelimitenabled` | Flag to enable inbound rate limits for cross-chain transfers. Pass this flag to enable, omit to disable. | `false` | No |
| `inboundratelimitcapacity` | Maximum capacity for the inbound rate limiter (in wei). | `0` | No |
| `inboundratelimitrate` | Refill rate for the inbound rate limiter bucket (tokens per second, in wei). | `0` | No |
Configure the pools using the following commands, replacing the pool, token, and remote pool addresses with those you deployed in the previous steps:
1. **Configure the pool on Avalanche Fuji:**
```bash
npx hardhat applyChainUpdates \
--pooladdress 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A \
--remotechain ethereumSepolia \
--remotepooladdresses 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D \
--remotetokenaddress 0x6803e72f0af827577a49FC82d7Ed9291028d687c \
--network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T16:51:34.417Z info: === Starting Chain Update Configuration ===
2025-10-21T16:51:34.418Z info: 🔹 Local network: avalancheFuji
2025-10-21T16:51:34.418Z info: 🔹 Pool address: 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A
2025-10-21T16:51:34.418Z info: 🔹 Remote chain: ethereumSepolia
2025-10-21T16:51:34.418Z info: 🔹 Remote chain family: evm
2025-10-21T16:51:34.418Z info: 🔹 Remote chain selector: 16015286601757825753
2025-10-21T16:51:36.581Z info: ✅ All addresses validated successfully
2025-10-21T16:51:37.100Z info: ✅ Using signer: 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca
2025-10-21T16:51:37.504Z info: ✅ Connected to TokenPool contract
2025-10-21T16:51:37.505Z info: Remote pool 1: 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D → 0x000000000000000000000000a29878301ca30a4639c33cd7ec8061e4265e1a5d
2025-10-21T16:51:37.506Z info: === Rate Limiter Configuration ===
2025-10-21T16:51:37.506Z info: Outbound enabled: false
2025-10-21T16:51:37.506Z info: Inbound enabled: false
2025-10-21T16:51:37.506Z info: === Executing applyChainUpdates() ===
2025-10-21T16:52:05.003Z info: 📤 TX sent: 0xde6b43d51e34668267eccdb188402b12c3e7497a918f17dd93521c504efc22db
2025-10-21T16:52:05.003Z info: Waiting for 2 confirmations...
2025-10-21T16:52:21.542Z info: ✅ Chain update applied successfully on avalancheFuji (2 confirmations)!
```
2. **Configure the pool on Ethereum Sepolia:**
```bash
npx hardhat applyChainUpdates \
--pooladdress 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D \
--remotechain avalancheFuji \
--remotetokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea \
--remotepooladdresses 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A \
--network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T16:59:10.675Z info: === Starting Chain Update Configuration ===
2025-10-21T16:59:10.676Z info: 🔹 Local network: ethereumSepolia
2025-10-21T16:59:10.676Z info: 🔹 Pool address: 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D
2025-10-21T16:59:10.676Z info: 🔹 Remote chain: avalancheFuji
2025-10-21T16:59:10.676Z info: 🔹 Remote chain family: evm
2025-10-21T16:59:10.676Z info: 🔹 Remote chain selector: 14767482510784806043
2025-10-21T16:59:12.799Z info: ✅ All addresses validated successfully
2025-10-21T16:59:13.366Z info: ✅ Using signer: 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca
2025-10-21T16:59:13.699Z info: ✅ Connected to TokenPool contract
2025-10-21T16:59:13.700Z info: Remote pool 1: 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A → 0x0000000000000000000000005e8b0e5eac57d73e7b93d85b8ea3fcada282868a
2025-10-21T16:59:13.701Z info: === Rate Limiter Configuration ===
2025-10-21T16:59:13.701Z info: Outbound enabled: false
2025-10-21T16:59:13.701Z info: Inbound enabled: false
2025-10-21T16:59:13.701Z info: === Executing applyChainUpdates() ===
2025-10-21T16:59:15.699Z info: 📤 TX sent: 0xddcb3cae44fe84d05b47e1c5ee96e8290104005f4c01501416410c767542aa55
2025-10-21T16:59:15.699Z info: Waiting for 3 confirmations...
2025-10-21T16:59:51.435Z info: ✅ Chain update applied successfully on ethereumSepolia (3 confirmations)!
```
### Mint Tokens
In this step, you will use the `mintTokens` task to mint tokens on Avalanche Fuji for your Externally Owned Account (EOA). Since you assigned mint and burn privileges to your EOA when deploying the tokens in the first step, you can now mint tokens for testing purposes. This is to ensure that you have enough tokens in your EOA to perform cross-chain transfers in the next step.
You will interact with the `BurnMintERC20` token contract, specifically calling the `mint()` function to mint tokens to your EOA.
Below is an explanation of the parameters used during the minting process:
| Parameter | Description | Default | Required |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token for which tokens are being minted. | N/A | Yes |
| `amount` | The amount of tokens to mint (in wei). | N/A | Yes |
| `receiveraddress` | The address of the receiver of the minted tokens. If not provided, defaults to your EOA. | N/A | No |
| `network` | The blockchain on which the minting process will be executed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Mint tokens to your EOA using the following command, replacing the token address with the one you deployed in the previous steps:
1. **Mint tokens on Avalanche Fuji:**
```bash
npx hardhat mintTokens --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --amount 1000000000000000000000 --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T17:00:46.902Z info: 🪙 Minting 1000000000000000000000 BnMsak to 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca...
2025-10-21T17:00:48.879Z info: ⏳ Mint tx: 0xe2759da0ba03aa64bb488acd6518ebd5a927dd99e5df2116c9b6b324506a8a2a
2025-10-21T17:00:48.879Z info: Waiting for 2 confirmation(s)...
2025-10-21T17:00:58.287Z info: ✅ Minted 1000000000000000000000 BnMsak to 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca
2025-10-21T17:00:58.287Z info: Current balance of 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca: 1100000000000000000000 BnMsak
```
### Transfer Tokens
In this step, you will use the `transferTokens` task to transfer tokens from Avalanche Fuji to Ethereum Sepolia using CCIP. You have two options for paying CCIP fees: using LINK tokens or native gas tokens.
You will interact with the `IRouterClient` contract, specifically calling the `ccipSend()` function to initiate the token transfer.
Below is an explanation of the parameters used during the token transfer process:
| Parameter | Description | Default | Required |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token being transferred. | N/A | Yes |
| `amount` | The amount of tokens to transfer. | N/A | Yes |
| `destinationchain` | The blockchain to which the tokens will be transferred. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
| `receiveraddress` | The address of the receiver on the destination blockchain. | N/A | Yes |
| `fee` | The type of fee used for the transfer, either `LINK` or `native`. | `LINK` | No |
| `network` | The blockchain on which the token transfer will be initiated. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
#### Pay fees in LINK
Call the CCIP Router to transfer tokens from Avalanche Fuji to Ethereum Sepolia, paying the CCIP fees in LINK tokens. Replace the token address, amount, receiver address, and blockchain with the appropriate values:
```bash
npx hardhat transferTokens --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --amount 100000000000000000000 --destinationchain ethereumSepolia --receiveraddress 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T17:02:33.667Z info: 🚀 Transferring 100000000000000000000 tokens via CCIP from avalancheFuji to ethereumSepolia...
2025-10-21T17:02:33.669Z info: Token: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-21T17:02:33.669Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-21T17:02:33.669Z info: Fee token: LINK
2025-10-21T17:02:34.619Z info: 💰 Estimated fees: 12147121320763453
2025-10-21T17:02:34.877Z info: Approving 100000000000000000000 tokens for router 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2025-10-21T17:02:36.962Z info: Waiting for 2 confirmation(s)...
2025-10-21T17:02:42.129Z info: Approving 12147121320763453 LINK to router
2025-10-21T17:02:43.439Z info: Waiting for 2 confirmation(s)...
2025-10-21T17:02:52.641Z info: 💰 Estimated CCIP fees: 12147121320763453
2025-10-21T17:02:52.642Z info: Sending CCIP message...
2025-10-21T17:02:53.934Z info: ⏳ CCIP message tx: 0x7b7bccaf0e29b3378a97ca0eb5c812b1fd54e97ab2c7072986abfbaf4844040a
2025-10-21T17:02:53.934Z info: Waiting for 2 confirmation(s)...
2025-10-21T17:03:07.544Z info: ✅ CCIP message sent successfully
2025-10-21T17:03:07.544Z info: Transaction: 0x7b7bccaf0e29b3378a97ca0eb5c812b1fd54e97ab2c7072986abfbaf4844040a
2025-10-21T17:03:07.545Z info: Check status: https://ccip.chain.link/tx/0x7b7bccaf0e29b3378a97ca0eb5c812b1fd54e97ab2c7072986abfbaf4844040a
2025-10-21T17:03:07.545Z info: 📋 Transfer Summary:
2025-10-21T17:03:07.545Z info: Token: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-21T17:03:07.545Z info: Amount: 100000000000000000000
2025-10-21T17:03:07.545Z info: From: avalancheFuji
2025-10-21T17:03:07.545Z info: To: ethereumSepolia
2025-10-21T17:03:07.545Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-21T17:03:07.545Z info: Fee paid in: LINK
```
#### Pay fees in native gas tokens
Call the CCIP Router to transfer tokens from Avalanche Fuji to Ethereum Sepolia, paying the CCIP fees in native gas tokens. Replace the token address, amount, receiver address, and blockchain with the appropriate values:
```bash
npx hardhat transferTokens --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --amount 100000000000000000000 --destinationchain ethereumSepolia --receiveraddress 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf --fee native --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-21T17:04:37.591Z info: 🚀 Transferring 100000000000000000000 tokens via CCIP from avalancheFuji to ethereumSepolia...
2025-10-21T17:04:37.592Z info: Token: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-21T17:04:37.592Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-21T17:04:37.592Z info: Fee token: native
2025-10-21T17:04:38.579Z info: 💰 Estimated fees: 12327130238457656
2025-10-21T17:04:38.837Z info: Approving 100000000000000000000 tokens for router 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2025-10-21T17:04:40.936Z info: Waiting for 2 confirmation(s)...
2025-10-21T17:04:50.174Z info: 💰 Estimated CCIP fees: 12327130238457656
2025-10-21T17:04:50.174Z info: Sending CCIP message...
2025-10-21T17:04:51.485Z info: ⏳ CCIP message tx: 0xcc1e4371129d62e3bff8c2325f236298f64007c7fb6c12bf38b65b88c6791c75
2025-10-21T17:04:51.485Z info: Waiting for 2 confirmation(s)...
2025-10-21T17:05:00.696Z info: ✅ CCIP message sent successfully
2025-10-21T17:05:00.697Z info: Transaction: 0xcc1e4371129d62e3bff8c2325f236298f64007c7fb6c12bf38b65b88c6791c75
2025-10-21T17:05:00.697Z info: Check status: https://ccip.chain.link/tx/0xcc1e4371129d62e3bff8c2325f236298f64007c7fb6c12bf38b65b88c6791c75
2025-10-21T17:05:00.697Z info: 📋 Transfer Summary:
2025-10-21T17:05:00.697Z info: Token: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-21T17:05:00.697Z info: Amount: 100000000000000000000
2025-10-21T17:05:00.697Z info: From: avalancheFuji
2025-10-21T17:05:00.697Z info: To: ethereumSepolia
2025-10-21T17:05:00.697Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-21T17:05:00.697Z info: Fee paid in: native
```
---
# Enable your tokens in CCIP (Lock & Mint): Register from an EOA using Hardhat
Source: https://docs.chain.link/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-lock-mint-hardhat
Last Updated: 2025-10-22
This tutorial will guide you through the process of enabling your own tokens in CCIP using [Hardhat](https://hardhat.org/). You will learn how to deploy tokens, set up a *Lock & Release* token pool on the source blockchain, and a *Burn & Mint* token pool on the destination blockchain. After that, you will register them in CCIP and configure them without needing manual intervention. Finally, you will test the **Lock & Mint** token handling mechanism, where tokens are locked on the source blockchain and an equivalent amount is minted on the destination blockchain.
We will cover the following key steps:
1. **Deploying Tokens**: You will deploy your [`BurnMintERC20`](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol) tokens on the Avalanche Fuji and Arbitrum Sepolia testnets.
2. **Deploying Token Pools**: Once your tokens are deployed, you will deploy [`LockReleaseTokenPool`](/ccip/api-reference/evm/v1.6.2/lock-release-token-pool) on Avalanche Fuji and [`BurnMintTokenPool`](/ccip/api-reference/evm/v1.6.2/burn-mint-token-pool) token pools on Arbitrum Sepolia. These pools are essential for testing the *Lock & Mint* token transfer mechanism: Locking the tokens on the source blockchain and then minting an equivalent amount of tokens on the destination blockchain. Each token will be linked to a pool, which will manage token transfers and ensure proper handling of assets across chains.
3. **Claiming Mint and Burn Roles**: You will [claim the mint and burn roles](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol#L128) for the destination token pool, allowing it to mint and burn tokens during cross-chain transfers.
4. **Claiming and Accepting the Admin Role**: This is a two-step process:
1. You will call the [`RegistryModuleOwnerCustom`](/ccip/api-reference/evm/v1.6.2/registry-module-owner-custom) contract's [`registerAdminViaOwner`](/ccip/api-reference/evm/v1.6.2/registry-module-owner-custom#registeradminviaowner) function to register your EOA as the token admin. This role is required to enable your token in CCIP.
2. Once claimed, you will call the [`TokenAdminRegistry`](/ccip/api-reference/evm/v1.6.2/token-admin-registry) contract's [`acceptAdminRole`](/ccip/api-reference/evm/v1.6.2/token-admin-registry#acceptadminrole) function to complete the registration process.
5. **Linking Tokens to Pools**: You will call the [`TokenAdminRegistry`](/ccip/api-reference/evm/v1.6.2/token-admin-registry) contract's [`setPool`](/ccip/api-reference/evm/v1.6.2/token-admin-registry#setpool) function to associate each token with its respective token pool.
6. **Configuring Token Pools**: You will call the [`applyChainUpdates`](/ccip/api-reference/evm/v1.6.2/token-pool#applychainupdates) function on your token pools to configure each pool by setting cross-chain transfer parameters, such as token pool rate limits and enabled destination chains.
7. **Minting Tokens**: You will call the [`mint`](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol#L128) function to mint tokens on Avalanche Fuji for your EOA. These tokens will later be used to test cross-chain transfers to Arbitrum Sepolia.
8. **Transferring Tokens**: Finally, you will transfer tokens from Avalanche Fuji to Arbitrum Sepolia using CCIP. You will have the option to pay CCIP fees in either LINK tokens or native gas tokens.
By the end of this tutorial, you will have successfully deployed, registered, configured, and enabled your tokens and token pools for use in CCIP.
## Before You Begin
1. Make sure you have Node.js v22.10.0 or above installed. If not, **install Node.js v22.10.0**:
[Download Node.js v22.10.0](https://nodejs.org/en/download/) if you don't have it installed. Optionally, you can use the [nvm package](https://www.npmjs.com/package/nvm) to switch between Node.js versions:
```bash
nvm use 22
```
Verify that the correct version of Node.js is installed:
```bash
node -v
```
Example output:
```bash
$ node -v
v22.15.0
```
2. **Clone the repository and navigate to the project directory:**
```bash
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
```
3. **Install dependencies for the project:**
```bash
npm install
```
4. **Compile the project:**
```bash
npm run compile
```
5. **Encrypt your environment variables for higher security:**\
The project uses [@chainlink/env-enc](https://www.npmjs.com/package/@chainlink/env-enc) to encrypt your environment variables at rest. Follow the steps below to configure your environment securely:
1. Set an encryption password for your environment variables:
```bash
npx env-enc set-pw
```
2. Set up a `.env.enc` file with the necessary variables for Avalanche Fuji and Ethereum Sepolia. Use the following command to add the variables:
```bash
npx env-enc set
```
Variables to configure:
- `AVALANCHE_FUJI_RPC_URL`: A URL for the *Avalanche Fuji* testnet. You can get a personal endpoint from services like [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/).
- `ETHEREUM_SEPOLIA_RPC_URL`: A URL for the *Ethereum Sepolia* testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/).
- `PRIVATE_KEY`: The private key for your testnet wallet. If you use MetaMask, you can follow this [guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/) to export your private key. **Note:** This key is required for signing transactions like token transfers.
- `ETHERSCAN_API_KEY`: An API key from Etherscan to verify your contracts. You can obtain one from [Etherscan](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics).
6. **Fund your EOA with LINK and native gas tokens**:\
Make sure your EOA has enough LINK and native gas tokens on Avalanche Fuji and Ethereum Sepolia to cover transaction fees. You can use the [Chainlink faucets](https://faucets.chain.link/) to get testnet tokens.
## Tutorial
### Deploy Tokens
In this step, you will use the `deployToken` task to deploy tokens on two testnets, Avalanche Fuji and Ethereum Sepolia. Below is an explanation of the parameters used during deployment:
| Parameter | Description | Default | Required |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `name` | The name of the token. This is the full name by which the token will be identified. | N/A | Yes |
| `symbol` | The symbol of the token. This is the shorthand (usually 3-5 letters) representing the token. | N/A | Yes |
| `decimals` | The number of decimals the token will use. For instance, `18` decimals means 1 token is represented as `1e18` smallest units. | `18` | No |
| `maxsupply` | The maximum supply of tokens. Use `0` for unlimited supply. | `0` | No |
| `verifycontract` | Flag to verify the contract on Etherscan or a similar blockchain explorer. Pass this flag to enable verification, omit to skip. | `false` | No |
| `network` | The blockchain on which the token will be deployed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Deploy tokens, use the following commands, substituting the token name and symbol as needed:
1. **Deploy the token on Avalanche Fuji:**
```bash
npx hardhat deployToken --name "BnM sak" --symbol BnMsak --decimals 18 --maxsupply 0 --premint 100000000000000000000 --verifycontract --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T09:49:50.588Z info: 🚀 Deploying BurnMintERC20 to avalancheFuji...
2025-10-22T09:49:50.589Z info: name: BnM sak, symbol: BnMsak
2025-10-22T09:49:55.714Z info: ⏳ Deployment tx: 0x4b764e27bc0e47f8fcec6b59e22a0e56aede0a08a03f7f40737b53fddc4e90d1
2025-10-22T09:49:55.715Z info: Waiting for 2 confirmation(s)...
2025-10-22T09:50:00.519Z info: ✅ Token deployed at: 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5
2025-10-22T09:50:01.179Z info: Granting mint and burn roles to 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA...
2025-10-22T09:50:02.422Z info: Waiting for 2 confirmation(s)...
2025-10-22T09:50:11.467Z info: ✅ Mint/Burn roles granted.
2025-10-22T09:50:11.468Z info: Verifying contract...
The contract at 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 has already been verified on SnowTrace.
If you need to verify a partially verified contract, please use the --force flag.
Explorer: https://testnet.snowtrace.io/address/0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5#code
2025-10-22T09:50:14.859Z info: ✅ Token contract verified successfully
```
2. **Deploy the token on Ethereum Sepolia:**
```bash
npx hardhat deployToken --name "BnM sak" --symbol BnMsak --decimals 18 --maxsupply 0 --premint 100000000000000000000 --verifycontract --network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T09:51:04.733Z info: 🚀 Deploying BurnMintERC20 to ethereumSepolia...
2025-10-22T09:51:04.734Z info: name: BnM sak, symbol: BnMsak
2025-10-22T09:51:17.638Z info: ⏳ Deployment tx: 0x187bac7cfc7ea95c41374baedbf98b6bc1bff121f5f1a49d17457458417eee66
2025-10-22T09:51:17.638Z info: Waiting for 3 confirmation(s)...
2025-10-22T09:52:21.497Z info: ✅ Token deployed at: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-22T09:52:23.669Z info: Granting mint and burn roles to 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA...
2025-10-22T09:52:26.363Z info: Waiting for 3 confirmation(s)...
2025-10-22T09:53:03.003Z info: ✅ Mint/Burn roles granted.
2025-10-22T09:53:03.003Z info: Verifying contract...
The contract at 0xb613B55897F07eAF430bF9a509498e55487305Ea has already been verified on Etherscan.
If you need to verify a partially verified contract, please use the --force flag.
Explorer: https://sepolia.etherscan.io/address/0xb613B55897F07eAF430bF9a509498e55487305Ea#code
2025-10-22T09:53:07.664Z info: ✅ Token contract verified successfully
```
### Deploy Token Pools
In this step, you will use the `deployTokenPool` task to deploy token pools for the tokens on both testnets, Avalanche Fuji and Ethereum Sepolia. Below is an explanation of the parameters used during deployment:
| Parameter | Description | Default | Required |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------- |
| `tokenaddress` | The address of the token for which the pool is being created. | N/A | Yes |
| `pooltype` | The type of pool to deploy. For this tutorial, we use `"lockRelease"` on Avalanche Fuji and `"burnMint"` on Ethereum Sepolia. | `"burnMint"` | No |
| `localtokendecimals` | The number of decimals for the token on this chain. | `18` | No |
| `verifycontract` | Flag to verify the contract on Etherscan or a similar blockchain explorer. Pass this flag to enable verification, omit to skip. | `false` | No |
| `network` | The blockchain on which the token pool will be deployed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Deploy token pools using the following commands, replacing the token address with the one you deployed in the previous step:
1. **Deploy the lock and release token pool on Avalanche Fuji:**
```bash
npx hardhat deployTokenPool \
--tokenaddress 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 \
--pooltype lockRelease \
--localtokendecimals 18 \
--verifycontract \
--network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:08:45.487Z info: 🚀 Deploying lockRelease pool on avalancheFuji
2025-10-22T13:08:45.488Z info: Token: 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5
2025-10-22T13:08:45.488Z info: Decimals: 18
2025-10-22T13:08:45.488Z info: Allowlist: None
2025-10-22T13:08:51.221Z info: ⏳ Deployment tx: 0xb027c026b72626c7d83e8fb2902c982685376a62d1c90655fee6855df3cdf23a
2025-10-22T13:08:51.221Z info: Waiting for 2 confirmation(s)...
2025-10-22T13:09:00.473Z info: ✅ Token pool deployed at: 0x10db2Fd0A5E3e9F68c72ae99CC266f383c1814a2
2025-10-22T13:09:00.474Z info: Verifying contract...
📤 Submitted source code for verification on SnowTrace:
@chainlink/contracts-ccip/contracts/pools/LockReleaseTokenPool.sol:LockReleaseTokenPool
Address: 0x10db2Fd0A5E3e9F68c72ae99CC266f383c1814a2
⏳ Waiting for verification result...
✅ Contract verified successfully on SnowTrace!
@chainlink/contracts-ccip/contracts/pools/LockReleaseTokenPool.sol:LockReleaseTokenPool
Explorer: https://testnet.snowtrace.io/address/0x10db2Fd0A5E3e9F68c72ae99CC266f383c1814a2#code
2025-10-22T13:09:15.173Z info: ✅ Token pool contract verified successfully
```
2. **Deploy the burn and mint token pool on Ethereum Sepolia:**
```bash
npx hardhat deployTokenPool \
--tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea \
--pooltype burnMint \
--localtokendecimals 18 \
--verifycontract \
--network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:10:30.678Z info: 🚀 Deploying burnMint pool on ethereumSepolia
2025-10-22T13:10:30.678Z info: Token: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-22T13:10:30.678Z info: Decimals: 18
2025-10-22T13:10:30.678Z info: Allowlist: None
2025-10-22T13:10:39.505Z info: ⏳ Deployment tx: 0xb1eed241f64f0a5427559b791f576b834885311b916b48c5ee86f711541d54b0
2025-10-22T13:10:39.506Z info: Waiting for 3 confirmation(s)...
2025-10-22T13:11:16.425Z info: ✅ Token pool deployed at: 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A
2025-10-22T13:11:16.425Z info: Granting mint and burn roles to 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A on token 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-22T13:11:19.296Z info: Waiting for 3 confirmation(s)...
2025-10-22T13:11:51.815Z info: ✅ Mint/Burn roles granted
2025-10-22T13:11:51.815Z info: Verifying contract...
The contract at 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A has already been verified on Etherscan.
If you need to verify a partially verified contract, please use the --force flag.
Explorer: https://sepolia.etherscan.io/address/0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A#code
2025-10-22T13:11:55.646Z info: ✅ Token pool contract verified successfully
```
### Claim Admin
In this step, you will use the `claimAdmin` task to register your EOA as the administrator for the deployed tokens on both testnets, Avalanche Fuji and Ethereum Sepolia. This process involves calling the `RegistryModuleOwnerCustom` contract, which will fetch the CCIP admin of the token and set it up as the admin in the registry.
Below is an explanation of the parameters used during the admin claim process:
| Parameter | Description | Default | Required |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | -------- |
| `tokenaddress` | The address of the token for which the admin role is being claimed. | N/A | Yes |
| `network` | The blockchain on which the claim admin process will be executed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Claim the admin role by using the following commands, replacing the token address with the one you deployed in the previous steps:
1. **Claim the admin role on Avalanche Fuji:**
```bash
npx hardhat claimAdmin --tokenaddress 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:17:16.402Z info: 🎯 Claiming admin for 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 using getCCIPAdmin mode
2025-10-22T13:17:20.129Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is CCIP admin
2025-10-22T13:17:22.182Z info: 📤 TX sent: 0xf64560bd40a848ecf26b3d63699cfcbd56154c36851eaa3e5aab4e4d338197a7. Waiting for 2 confirmations...
2025-10-22T13:17:31.204Z info: ✅ Admin claimed for 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 on avalancheFuji (2 confirmations)
```
2. **Claim the admin role on Ethereum Sepolia:**
```bash
npx hardhat claimAdmin --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:18:41.832Z info: 🎯 Claiming admin for 0xb613B55897F07eAF430bF9a509498e55487305Ea using getCCIPAdmin mode
2025-10-22T13:18:48.580Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is CCIP admin
2025-10-22T13:18:51.544Z info: 📤 TX sent: 0xc27012ea91ec4c3f6898c5bd4ad7f7ff390c0030fa87a6692869c6acd96b5417. Waiting for 3 confirmations...
2025-10-22T13:19:27.189Z info: ✅ Admin claimed for 0xb613B55897F07eAF430bF9a509498e55487305Ea on ethereumSepolia (3 confirmations)
```
### Accept Admin Role
In this step, you will use the `acceptAdminRole` task to accept the admin role for the deployed tokens on both testnets, Avalanche Fuji and Ethereum Sepolia. Once you have claimed the role, accepting the role finalizes your control over the token administration.
Below is an explanation of the parameters used during the admin role acceptance process:
| Parameter | Description | Default | Required |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token for which the admin role is being accepted. | N/A | Yes |
| `network` | The blockchain on which the accept admin process will be executed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Accept the admin role by using the following commands, replacing the token address with the one deployed in the previous steps:
1. **Accept the admin role on Avalanche Fuji:**
```bash
npx hardhat acceptAdminRole --tokenaddress 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:21:31.047Z info: 🔄 Accepting admin role for 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 on avalancheFuji...
2025-10-22T13:21:34.256Z info: Checking pending admin for 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5...
2025-10-22T13:21:34.530Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is the pending admin
2025-10-22T13:21:34.530Z info: Accepting admin role...
2025-10-22T13:21:36.591Z info: 📤 TX sent: 0xb4bd7a1fb2803c6e4459e44c014cb450bd7e0149b2d9fc8018eb1a57561600c2. Waiting for 2 confirmations...
2025-10-22T13:21:41.413Z info: ✅ Admin role accepted for 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 on avalancheFuji (2 confirmations)
```
2. **Accept the admin role on Ethereum Sepolia:**
```bash
npx hardhat acceptAdminRole --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:22:24.437Z info: 🔄 Accepting admin role for 0xb613B55897F07eAF430bF9a509498e55487305Ea on ethereumSepolia...
2025-10-22T13:22:29.165Z info: Checking pending admin for 0xb613B55897F07eAF430bF9a509498e55487305Ea...
2025-10-22T13:22:29.467Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is the pending admin
2025-10-22T13:22:29.467Z info: Accepting admin role...
2025-10-22T13:22:31.790Z info: 📤 TX sent: 0x57094dfdb13ab712fb1afb099c6c65734a7726939863626ec6a59d0c24326e89. Waiting for 3 confirmations...
2025-10-22T13:23:26.623Z info: ✅ Admin role accepted for 0xb613B55897F07eAF430bF9a509498e55487305Ea on ethereumSepolia (3 confirmations)
```
### Set Pool
In this step, you will use the `setPool` task to link each token with its respective token pool on both testnets.
Below is an explanation of the parameters used during the pool setting process:
| Parameter | Description | Default | Required |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token to be linked to a pool. | N/A | Yes |
| `pooladdress` | The address of the pool associated with the token. | N/A | Yes |
| `network` | The blockchain on which the pool setting will be executed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Link each token with its respective token pool by using the following commands, replacing the token and pool addresses with the ones you deployed in the previous steps:
1. **Set the pool for Avalanche Fuji:**
```bash
npx hardhat setPool \
--tokenaddress 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 --pooladdress 0x10db2Fd0A5E3e9F68c72ae99CC266f383c1814a2 \
--network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:26:57.003Z info: 🔗 Setting pool for token 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 on avalancheFuji...
2025-10-22T13:27:00.453Z info: 🔹 Using signer: 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca
2025-10-22T13:27:00.453Z info: Using TokenAdminRegistry: 0xA92053a4a3922084d992fD2835bdBa4caC6877e6
2025-10-22T13:27:00.909Z info: Checking token configuration for 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5...
2025-10-22T13:27:01.184Z info: Token 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 current admin: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA
2025-10-22T13:27:01.185Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is the token administrator
2025-10-22T13:27:01.185Z info: Setting pool 0x10db2Fd0A5E3e9F68c72ae99CC266f383c1814a2 for token 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5...
2025-10-22T13:27:03.433Z info: 📤 TX sent: 0xddbf3e5a7d0799ccf11fff3ad816553c75be8cc0d2986d0a49d26c271af040e9. Waiting for 2 confirmations...
2025-10-22T13:27:12.509Z info: ✅ Pool successfully set for token 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 → 0x10db2Fd0A5E3e9F68c72ae99CC266f383c1814a2 on avalancheFuji (2 confirmations)
```
2. **Set the pool for Ethereum Sepolia:**
```bash
npx hardhat setPool \
--tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --pooladdress 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A \
--network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:27:52.517Z info: 🔗 Setting pool for token 0xb613B55897F07eAF430bF9a509498e55487305Ea on ethereumSepolia...
2025-10-22T13:27:59.751Z info: 🔹 Using signer: 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca
2025-10-22T13:27:59.751Z info: Using TokenAdminRegistry: 0x95F29FEE11c5C55d26cCcf1DB6772DE953B37B82
2025-10-22T13:28:00.123Z info: Checking token configuration for 0xb613B55897F07eAF430bF9a509498e55487305Ea...
2025-10-22T13:28:00.533Z info: Token 0xb613B55897F07eAF430bF9a509498e55487305Ea current admin: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA
2025-10-22T13:28:00.533Z info: ✅ Current wallet 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca is the token administrator
2025-10-22T13:28:00.533Z info: Setting pool 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A for token 0xb613B55897F07eAF430bF9a509498e55487305Ea...
2025-10-22T13:28:03.981Z info: 📤 TX sent: 0xdf9eaf86d6250590e2dffcd69f2e0e6a6bcdf41a1e0ab549437e74f0b4f2e02e. Waiting for 3 confirmations...
2025-10-22T13:28:40.100Z info: ✅ Pool successfully set for token 0xb613B55897F07eAF430bF9a509498e55487305Ea → 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A on ethereumSepolia (3 confirmations)
```
### Configure Token Pools
In this step, you will use the `applyChainUpdates` task to initialize the token pool configuration on each blockchain to enable cross-chain transfers between Avalanche Fuji and Ethereum Sepolia. Below is an explanation of the parameters used:
| Parameter | Description | Default | Required |
| --------------------------- | --------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `pooladdress` | The address of the pool to be configured. | N/A | Yes |
| `remotechain` | The remote blockchain network (e.g., `ethereumSepolia` for Fuji pool, `avalancheFuji` for Sepolia pool). | N/A | Yes |
| `remotepooladdresses` | Comma-separated list of remote pool addresses. | N/A | Yes |
| `remotetokenaddress` | The address of the token on the remote chain. | N/A | Yes |
| `outboundratelimitenabled` | Flag to enable outbound rate limits for cross-chain transfers. Pass this flag to enable, omit to disable. | `false` | No |
| `outboundratelimitcapacity` | Maximum capacity for the outbound rate limiter (in wei). | `0` | No |
| `outboundratelimitrate` | Refill rate for the outbound rate limiter bucket (tokens per second, in wei). | `0` | No |
| `inboundratelimitenabled` | Flag to enable inbound rate limits for cross-chain transfers. Pass this flag to enable, omit to disable. | `false` | No |
| `inboundratelimitcapacity` | Maximum capacity for the inbound rate limiter (in wei). | `0` | No |
| `inboundratelimitrate` | Refill rate for the inbound rate limiter bucket (tokens per second, in wei). | `0` | No |
Configure the pools using the following commands, replacing the pool, token, and remote pool addresses with those you deployed in the previous steps:
1. **Configure the pool on Avalanche Fuji:**
```bash
npx hardhat applyChainUpdates \
--pooladdress 0x10db2Fd0A5E3e9F68c72ae99CC266f383c1814a2 \
--remotechain ethereumSepolia \
--remotepooladdresses 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A \
--remotetokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea \
--network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:32:32.607Z info: === Starting Chain Update Configuration ===
2025-10-22T13:32:32.607Z info: 🔹 Local network: avalancheFuji
2025-10-22T13:32:32.608Z info: 🔹 Pool address: 0x10db2Fd0A5E3e9F68c72ae99CC266f383c1814a2
2025-10-22T13:32:32.608Z info: 🔹 Remote chain: ethereumSepolia
2025-10-22T13:32:32.608Z info: 🔹 Remote chain family: evm
2025-10-22T13:32:32.608Z info: 🔹 Remote chain selector: 16015286601757825753
2025-10-22T13:32:34.967Z info: ✅ All addresses validated successfully
2025-10-22T13:32:35.511Z info: ✅ Using signer: 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca
2025-10-22T13:32:35.920Z info: ✅ Connected to TokenPool contract
2025-10-22T13:32:35.921Z info: Remote pool 1: 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A → 0x0000000000000000000000005e8b0e5eac57d73e7b93d85b8ea3fcada282868a
2025-10-22T13:32:35.922Z info: === Rate Limiter Configuration ===
2025-10-22T13:32:35.922Z info: Outbound enabled: false
2025-10-22T13:32:35.922Z info: Inbound enabled: false
2025-10-22T13:32:35.922Z info: === Executing applyChainUpdates() ===
2025-10-22T13:32:38.130Z info: 📤 TX sent: 0x74cbcaf2bbd5fa86f7a72a58f2f289496f3cba10d1d99d3638e7dae1e30d8ded
2025-10-22T13:32:38.130Z info: Waiting for 2 confirmations...
2025-10-22T13:32:47.218Z info: ✅ Chain update applied successfully on avalancheFuji (2 confirmations)!
```
2. **Configure the pool on Ethereum Sepolia:**
```bash
npx hardhat applyChainUpdates \
--pooladdress 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A \
--remotechain avalancheFuji \
--remotepooladdresses 0x10db2Fd0A5E3e9F68c72ae99CC266f383c1814a2 \
--remotetokenaddress 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 \
--network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:33:45.900Z info: === Starting Chain Update Configuration ===
2025-10-22T13:33:45.901Z info: 🔹 Local network: ethereumSepolia
2025-10-22T13:33:45.901Z info: 🔹 Pool address: 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A
2025-10-22T13:33:45.901Z info: 🔹 Remote chain: avalancheFuji
2025-10-22T13:33:45.901Z info: 🔹 Remote chain family: evm
2025-10-22T13:33:45.901Z info: 🔹 Remote chain selector: 14767482510784806043
2025-10-22T13:33:48.062Z info: ✅ All addresses validated successfully
2025-10-22T13:33:48.655Z info: ✅ Using signer: 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca
2025-10-22T13:33:48.977Z info: ✅ Connected to TokenPool contract
2025-10-22T13:33:48.980Z info: Remote pool 1: 0x10db2Fd0A5E3e9F68c72ae99CC266f383c1814a2 → 0x00000000000000000000000010db2fd0a5e3e9f68c72ae99cc266f383c1814a2
2025-10-22T13:33:48.980Z info: === Rate Limiter Configuration ===
2025-10-22T13:33:48.980Z info: Outbound enabled: false
2025-10-22T13:33:48.980Z info: Inbound enabled: false
2025-10-22T13:33:48.981Z info: === Executing applyChainUpdates() ===
2025-10-22T13:33:51.061Z info: 📤 TX sent: 0x16b5122c3687a81b71e0ac479dfae846dc5eb8091d85dc51c137f8aa0e6d0bec
2025-10-22T13:33:51.061Z info: Waiting for 3 confirmations...
2025-10-22T13:34:27.345Z info: ✅ Chain update applied successfully on ethereumSepolia (3 confirmations)!
```
### Mint Tokens
In this step, you will use the `mintTokens` task to mint tokens on Avalanche Fuji for your Externally Owned Account (EOA). Since you assigned mint and burn privileges to your EOA when deploying the tokens in the first step, you can now mint tokens for testing purposes. This is to ensure that you have enough tokens in your EOA to perform cross-chain transfers in the next step.
You will interact with the `BurnMintERC20` token contract, specifically calling the `mint()` function to mint tokens to your EOA.
Below is an explanation of the parameters used during the minting process:
| Parameter | Description | Default | Required |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token for which tokens are being minted. | N/A | Yes |
| `amount` | The amount of tokens to mint (in wei). | N/A | Yes |
| `receiveraddress` | The address of the receiver of the minted tokens. If not provided, defaults to your EOA. | N/A | No |
| `network` | The blockchain on which the minting process will be executed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
Mint tokens to your EOA using the following command, replacing the token address with the one you deployed in the previous steps:
1. **Mint tokens on Avalanche Fuji:**
```bash
npx hardhat mintTokens --tokenaddress 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 --amount 1000000000000000000000 --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:37:15.433Z info: 🪙 Minting 1000000000000000000000 BnMsak to 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca...
2025-10-22T13:37:17.440Z info: ⏳ Mint tx: 0x4e4519f97c773a351132a068f55a0ced2c25bcf7a9587c83b338403b17cdb5bc
2025-10-22T13:37:17.441Z info: Waiting for 2 confirmation(s)...
2025-10-22T13:37:31.076Z info: ✅ Minted 1000000000000000000000 BnMsak to 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca
2025-10-22T13:37:31.076Z info: Current balance of 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca: 1100000000000000000000 BnMsak
```
### Transfer Tokens
In this step, you will use the `transferTokens` task to transfer tokens from Avalanche Fuji to Ethereum Sepolia using CCIP. You have two options for paying CCIP fees: using LINK tokens or native gas tokens.
You will interact with the `IRouterClient` contract, specifically calling the `ccipSend()` function to initiate the token transfer.
Below is an explanation of the parameters used during the token transfer process:
| Parameter | Description | Default | Required |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token being transferred. | N/A | Yes |
| `amount` | The amount of tokens to transfer. | N/A | Yes |
| `destinationchain` | The blockchain to which the tokens will be transferred. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
| `receiveraddress` | The address of the receiver on the destination blockchain. | N/A | Yes |
| `fee` | The type of fee used for the transfer, either `LINK` or `native`. | `LINK` | No |
| `network` | The blockchain on which the token transfer will be initiated. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
#### Pay fees in LINK
Call the CCIP Router to transfer tokens from Avalanche Fuji to Ethereum Sepolia, paying the CCIP fees in LINK tokens. Replace the token address, amount, receiver address, and blockchain with the appropriate values:
```bash
npx hardhat transferTokens --tokenaddress 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 --amount 100000000000000000000 --destinationchain ethereumSepolia --receiveraddress 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:43:39.126Z info: 🚀 Transferring 100000000000000000000 tokens via CCIP from avalancheFuji to ethereumSepolia...
2025-10-22T13:43:39.127Z info: Token: 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5
2025-10-22T13:43:39.127Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-22T13:43:39.127Z info: Fee token: LINK
2025-10-22T13:43:40.097Z info: 💰 Estimated fees: 12762338383426833
2025-10-22T13:43:40.351Z info: Approving 100000000000000000000 tokens for router 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2025-10-22T13:43:42.411Z info: Waiting for 2 confirmation(s)...
2025-10-22T13:43:56.306Z info: Approving 12762338383426833 LINK to router
2025-10-22T13:43:57.544Z info: Waiting for 2 confirmation(s)...
2025-10-22T13:44:02.292Z info: 💰 Estimated CCIP fees: 12762338383426833
2025-10-22T13:44:02.292Z info: Simulating CCIP message...
2025-10-22T13:44:02.549Z info: Sending CCIP message...
2025-10-22T13:44:03.813Z info: ⏳ CCIP message tx: 0x3518d5469eed57cece65221c69d071be9aa883035e7869a24d2f75061f789926
2025-10-22T13:44:03.813Z info: Waiting for 2 confirmation(s)...
2025-10-22T13:44:08.811Z info: ✅ CCIP message sent successfully
2025-10-22T13:44:08.812Z info: Transaction: 0x3518d5469eed57cece65221c69d071be9aa883035e7869a24d2f75061f789926
2025-10-22T13:44:08.812Z info: Check status: https://ccip.chain.link/tx/0x3518d5469eed57cece65221c69d071be9aa883035e7869a24d2f75061f789926
2025-10-22T13:44:08.812Z info: 📋 Transfer Summary:
2025-10-22T13:44:08.813Z info: Token: 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5
2025-10-22T13:44:08.813Z info: Amount: 100000000000000000000
2025-10-22T13:44:08.813Z info: From: avalancheFuji
2025-10-22T13:44:08.813Z info: To: ethereumSepolia
2025-10-22T13:44:08.813Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-22T13:44:08.813Z info: Fee paid in: LINK
```
#### Pay fees in native gas tokens
Call the CCIP Router to transfer tokens from Avalanche Fuji to Ethereum Sepolia, paying the CCIP fees in native gas tokens. Replace the token address, amount, receiver address, and blockchain with the appropriate values:
```bash
npx hardhat transferTokens --tokenaddress 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5 --amount 100000000000000000000 --destinationchain ethereumSepolia --receiveraddress 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf --fee native --network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T13:47:59.281Z info: 🚀 Transferring 100000000000000000000 tokens via CCIP from avalancheFuji to ethereumSepolia...
2025-10-22T13:47:59.283Z info: Token: 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5
2025-10-22T13:47:59.283Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-22T13:47:59.284Z info: Fee token: native
2025-10-22T13:48:00.223Z info: 💰 Estimated fees: 12717027432412391
2025-10-22T13:48:00.489Z info: Approving 100000000000000000000 tokens for router 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2025-10-22T13:48:02.490Z info: Waiting for 2 confirmation(s)...
2025-10-22T13:48:15.961Z info: 💰 Estimated CCIP fees: 12717027432412391
2025-10-22T13:48:15.962Z info: Simulating CCIP message...
2025-10-22T13:48:16.222Z info: Sending CCIP message...
2025-10-22T13:48:17.524Z info: ⏳ CCIP message tx: 0x870904c7d9e758934ab468fd0889f078a7800fdf074242ccbbbac2d9cc0a257e
2025-10-22T13:48:17.524Z info: Waiting for 2 confirmation(s)...
2025-10-22T13:48:22.556Z info: ✅ CCIP message sent successfully
2025-10-22T13:48:22.556Z info: Transaction: 0x870904c7d9e758934ab468fd0889f078a7800fdf074242ccbbbac2d9cc0a257e
2025-10-22T13:48:22.556Z info: Check status: https://ccip.chain.link/tx/0x870904c7d9e758934ab468fd0889f078a7800fdf074242ccbbbac2d9cc0a257e
2025-10-22T13:48:22.556Z info: 📋 Transfer Summary:
2025-10-22T13:48:22.556Z info: Token: 0xa3B8d1648A9f9515b2F2EefC7222c62AEAAC9ce5
2025-10-22T13:48:22.556Z info: Amount: 100000000000000000000
2025-10-22T13:48:22.556Z info: From: avalancheFuji
2025-10-22T13:48:22.556Z info: To: ethereumSepolia
2025-10-22T13:48:22.557Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-22T13:48:22.557Z info: Fee paid in: native
```
Your tokens have been locked on the token pool on Avalanche Fuji, the corresponding tokens have been minted on Ethereum Sepolia and sent to your receiver address.
Note: Since your Lock & Release token pool on Avalanche Fuji has locked some tokens, you can transfer tokens from Ethereum Sepolia to Avalanche Fuji using CCIP as an exercise. Your tokens will be burned on Ethereum Sepolia, and the corresponding tokens will be released on Avalanche Fuji. Make sure not to transfer more tokens than the amount of tokens locked in the token pool on Avalanche Fuji.
---
# Set Token Pool rate limits using Hardhat
Source: https://docs.chain.link/ccip/tutorials/evm/cross-chain-tokens/update-rate-limiters-hardhat
Last Updated: 2025-10-22
This tutorial will guide you through the process of updating the rate limiter settings for outbound and inbound transfers in your deployed token pools using [Hardhat](https://hardhat.org/). You will first review existing rate limiter settings and then update them.
## Prerequisites
- **Tokens and pools deployed**: Ensure that you have tokens and token pools already deployed on both networks you plan to use. If not, refer to one of the following tutorials:
- [Register from an EOA (Burn & Mint)](/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-burn-mint-hardhat)
- [Register from an EOA (Lock & Mint)](/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-lock-mint-hardhat)
- **Admin access**: Ensure you have the necessary privileges to call the [setChainRateLimiterConfig](/ccip/api-reference/evm/v1.6.2/token-pool#setchainratelimiterconfig) function for your token pools.
## Before You Begin
1. Make sure you have Node.js v22.10.0 or above installed. If not, **install Node.js v22.10.0**:
[Download Node.js v22.10.0](https://nodejs.org/en/download/) if you don't have it installed. Optionally, you can use the [nvm package](https://www.npmjs.com/package/nvm) to switch between Node.js versions:
```bash
nvm use 22
```
Verify that the correct version of Node.js is installed:
```bash
node -v
```
Example output:
```bash
$ node -v
v22.15.0
```
2. **Clone the repository and navigate to the project directory:**
```bash
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
```
3. **Install dependencies for the project:**
```bash
npm install
```
4. **Compile the project:**
```bash
npm run compile
```
5. **Encrypt your environment variables for higher security:**\
The project uses [@chainlink/env-enc](https://www.npmjs.com/package/@chainlink/env-enc) to encrypt your environment variables at rest. Follow the steps below to configure your environment securely:
1. Set an encryption password for your environment variables:
```bash
npx env-enc set-pw
```
2. Set up a `.env.enc` file with the necessary variables for Avalanche Fuji and Ethereum Sepolia testnets. Use the following command to add the variables:
```bash
npx env-enc set
```
Variables to configure:
- AVALANCHE_FUJI_RPC_URL: A URL for the *Avalanche Fuji* testnet. You can get a personal
endpoint from services like [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/).
- ETHEREUM_SEPOLIA_RPC_URL: A URL for the *Ethereum Sepolia* testnet. You can get a
personal endpoint from services like [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/).
- PRIVATE_KEY: The private key for your testnet wallet. If you use MetaMask, you can
follow this
[guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/)
to export your private key. **Note:** This key is required for signing transactions like token transfers.
6. **Fund your EOA with native gas tokens**:\
Make sure your EOA has enough native gas tokens on Avalanche Fuji to cover transaction fees. You can use the [Chainlink faucets](https://faucets.chain.link/) to get testnet tokens.
## Tutorial
### Review current rate limiter settings
Use the `getCurrentRateLimits` task to fetch the current rate limiter states for a specific chain from a token pool. This task provides detailed information about both inbound and outbound rate limits, including:
- Whether rate limiting is enabled
- The maximum capacity (bucket size)
- The refill rate (tokens per second)
- Current token amount in the bucket
- Last update timestamp
Below is an explanation of the parameters used:
| Parameter | Description | Default | Required |
| ------------- | ---------------------------------------------------------------------------------------- | ------- | -------- |
| `pooladdress` | The address of the token pool to query. | N/A | Yes |
| `remotechain` | The remote blockchain to check rate limits for (e.g., `ethereumSepolia` for Fuji pool). | N/A | Yes |
| `network` | The blockchain on which to execute the query (e.g., `avalancheFuji`, `ethereumSepolia`). | N/A | Yes |
1. **Get rate limiter settings for the token pool on Avalanche Fuji**:
```bash
npx hardhat getCurrentRateLimits \
--pooladdress 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A \
--remotechain ethereumSepolia \
--network avalancheFuji
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T01:18:02.669Z info: 📊 Fetching rate limiter states for pool 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A on avalancheFuji...
2025-10-22T01:18:02.670Z info: Remote chain: ethereumSepolia
2025-10-22T01:18:02.670Z info: Remote chain selector: 16015286601757825753
2025-10-22T01:18:04.702Z info:
=== Rate Limiter States for ethereumSepolia ===
2025-10-22T01:18:04.702Z info: Pool Address: 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A
2025-10-22T01:18:04.702Z info: Chain Selector: 16015286601757825753
2025-10-22T01:18:04.702Z info:
📤 Outbound Rate Limiter:
2025-10-22T01:18:04.702Z info: Enabled: false
2025-10-22T01:18:04.702Z info: Capacity: 0
2025-10-22T01:18:04.702Z info: Rate: 0
2025-10-22T01:18:04.702Z info: Tokens: 0
2025-10-22T01:18:04.702Z info: Last Updated: 1761095882
2025-10-22T01:18:04.702Z info:
📥 Inbound Rate Limiter:
2025-10-22T01:18:04.702Z info: Enabled: false
2025-10-22T01:18:04.702Z info: Capacity: 0
2025-10-22T01:18:04.703Z info: Rate: 0
2025-10-22T01:18:04.703Z info: Tokens: 0
2025-10-22T01:18:04.703Z info: Last Updated: 1761095882
2025-10-22T01:18:04.703Z info:
✅ Rate limiters fetched successfully
```
2. **Get rate limiter settings for the token pool on Ethereum Sepolia**:
```bash
npx hardhat getCurrentRateLimits \
--pooladdress 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D \
--remotechain avalancheFuji \
--network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T01:21:05.698Z info: 📊 Fetching rate limiter states for pool 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D on ethereumSepolia...
2025-10-22T01:21:05.699Z info: Remote chain: avalancheFuji
2025-10-22T01:21:05.699Z info: Remote chain selector: 14767482510784806043
2025-10-22T01:21:08.160Z info:
=== Rate Limiter States for avalancheFuji ===
2025-10-22T01:21:08.161Z info: Pool Address: 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D
2025-10-22T01:21:08.161Z info: Chain Selector: 14767482510784806043
2025-10-22T01:21:08.161Z info:
📤 Outbound Rate Limiter:
2025-10-22T01:21:08.161Z info: Enabled: false
2025-10-22T01:21:08.161Z info: Capacity: 0
2025-10-22T01:21:08.161Z info: Rate: 0
2025-10-22T01:21:08.161Z info: Tokens: 0
2025-10-22T01:21:08.161Z info: Last Updated: 1761096060
2025-10-22T01:21:08.161Z info:
📥 Inbound Rate Limiter:
2025-10-22T01:21:08.161Z info: Enabled: false
2025-10-22T01:21:08.161Z info: Capacity: 0
2025-10-22T01:21:08.161Z info: Rate: 0
2025-10-22T01:21:08.161Z info: Tokens: 0
2025-10-22T01:21:08.161Z info: Last Updated: 1761096060
2025-10-22T01:21:08.161Z info:
✅ Rate limiters fetched successfully
```
### Update rate limiter settings
Use the `updateRateLimiters` task to update the rate limiter configurations for an existing chain connection in your token pool. This task specifically interacts with the [`setChainRateLimiterConfig`](/ccip/api-reference/evm/v1.6.2/token-pool#setchainratelimiterconfig) function of the `TokenPool` contract, allowing you to adjust the rate limits without altering other configurations like remote pool addresses.
The `updateRateLimiters` task allows you to:
- Enable or disable rate limiting for outbound or inbound transfers or both.
- Set the capacity and rate for the rate limiters, controlling the flow of tokens.
- Target a specific remote chain, updating rate limits for that chain only.
Below is an explanation of the parameters used during the rate limiter update process:
| Parameter | Description | Default | Required |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `pooladdress` | The address of the token pool being configured. | N/A | Yes |
| `remotechain` | The remote blockchain to which this pool is linked. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
| `ratelimiter` | Specifies which rate limiters to update: `inbound`, `outbound`, or `both`. | `both` | No |
| `outboundratelimitenabled` | Flag to enable outbound rate limits for cross-chain transfers. Pass this flag to enable, omit to disable. | `false` | No |
| `outboundratelimitcapacity` | The maximum number of tokens allowed in the bucket for outbound transfers (in wei). **Note:** Applicable if outbound rate limits are enabled. | `0` | No |
| `outboundratelimitrate` | The number of tokens per second that the bucket is refilled for outbound transfers (in wei). **Note:** Applicable if outbound rate limits are enabled. | `0` | No |
| `inboundratelimitenabled` | Flag to enable inbound rate limits for cross-chain transfers. Pass this flag to enable, omit to disable. | `false` | No |
| `inboundratelimitcapacity` | The maximum number of tokens allowed in the bucket for inbound transfers (in wei). **Note:** Applicable if inbound rate limits are enabled. | `0` | No |
| `inboundratelimitrate` | The number of tokens per second that the bucket is refilled for inbound transfers (in wei). **Note:** Applicable if inbound rate limits are enabled. | `0` | No |
| `network` | The blockchain network where the local token pool is deployed. Examples include `avalancheFuji`, `ethereumSepolia`, `baseSepolia`, and `arbitrumSepolia`. | N/A | Yes |
**Command syntax**:
```bash
npx hardhat updateRateLimiters \
--pooladdress \
--remotechain \
--ratelimiter \
--outboundratelimitenabled \
--outboundratelimitcapacity \
--outboundratelimitrate \
--inboundratelimitenabled \
--inboundratelimitcapacity \
--inboundratelimitrate \
--network
```
**Example command**:
Suppose you want to enable inbound and outbound rate limits for your token pool on Avalanche Fuji to control the number of tokens received or sent from/to Ethereum Sepolia. We will use an existing token pool that interacts with an ERC20 token with 18 decimals:
- **Token Pool on Avalanche Fuji**:
- **Outbound Rate Limiter**:
- **Enabled**: `true`
- **Capacity**: `10000000000000000000` wei (equivalent to 10 tokens, based on 18 decimals)
- **Rate**: `100000000000000000` wei (equivalent to 0.1 token per second, based on 18 decimals)
- **Note**:
- `Capacity / Rate = 10 / 0.1 = 100 seconds`
- It takes **100 seconds** to replenish the bucket from 0 to full capacity.
- **Inbound Rate Limiter**:
- **Enabled**: `true`
- **Capacity**: `20000000000000000000` wei (equivalent to 20 tokens, based on 18 decimals)
- **Rate**: `100000000000000000` wei (equivalent to 0.1 tokens per second, based on 18 decimals)
- **Note**:
- `Capacity / Rate = 20 / 0.1 = 200 seconds`
- It takes **200 seconds** to replenish the bucket from 0 to full capacity.
- **Token Pool on Ethereum Sepolia**: Rate limits are the same as the Avalanche Fuji pool, but the inbound and outbound settings are swapped.
- **Outbound Rate Limiter**:
- **Enabled**: `true`
- **Capacity**: `20000000000000000000` wei
- **Rate**: `100000000000000000` wei
- **Inbound Rate Limiter**:
- **Enabled**: `true`
- **Capacity**: `10000000000000000000` wei
- **Rate**: `100000000000000000` wei
1. **Update rate limiter settings for the token pool on Avalanche Fuji**: Replace `` with your token pool address.
```bash
npx hardhat updateRateLimiters \
--pooladdress \
--remotechain ethereumSepolia \
--ratelimiter both \
--outboundratelimitenabled true \
--outboundratelimitcapacity 10000000000000000000 \
--outboundratelimitrate 100000000000000000 \
--inboundratelimitenabled true \
--inboundratelimitcapacity 20000000000000000000 \
--inboundratelimitrate 100000000000000000 \
--network avalancheFuji
```
Expected output:
```bash
$ npx hardhat updateRateLimiters \
--pooladdress 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A \
--remotechain ethereumSepolia \
--ratelimiter both \
--outboundratelimitenabled true \
--outboundratelimitcapacity 10000000000000000000 \
--outboundratelimitrate 100000000000000000 \
--inboundratelimitenabled true \
--inboundratelimitcapacity 20000000000000000000 \
--inboundratelimitrate 100000000000000000 \
--network avalancheFuji
✅ Tasks loaded from /tasks/index.ts
2025-10-22T01:43:31.678Z info: ⚙️ Updating rate limiters for pool 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A on avalancheFuji...
2025-10-22T01:43:31.680Z info: Remote chain: ethereumSepolia
2025-10-22T01:43:31.680Z info: Remote chain selector: 16015286601757825753
2025-10-22T01:43:31.680Z info: Updating: both limiters
2025-10-22T01:43:32.354Z info: ✅ Remote chain is supported by the pool
2025-10-22T01:43:32.630Z info: ✅ Caller is the pool owner
2025-10-22T01:43:32.901Z info:
📊 Current Rate Limiters for pool 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A
2025-10-22T01:43:32.901Z info: Outbound → enabled=false, cap=0, rate=0
2025-10-22T01:43:32.901Z info: Inbound → enabled=false, cap=0, rate=0
2025-10-22T01:43:32.901Z info:
========== Preparing Update ==========
2025-10-22T01:43:32.902Z info: New Outbound → enabled=true, cap=10000000000000000000, rate=100000000000000000
2025-10-22T01:43:32.902Z info: New Inbound → enabled=true, cap=20000000000000000000, rate=100000000000000000
2025-10-22T01:43:32.902Z info:
⚡ Updating both limiters...
2025-10-22T01:43:34.982Z info: ⏳ Rate limiter update tx: 0x2489f68073c72d96a7801bd046a23a23fad40f15d27c9d052a18697f2092336d
2025-10-22T01:43:34.982Z info: Waiting for 2 confirmation(s)...
2025-10-22T01:43:44.017Z info: ✅ Rate limiters updated successfully
2025-10-22T01:43:44.017Z info: Transaction: 0x2489f68073c72d96a7801bd046a23a23fad40f15d27c9d052a18697f2092336d
```
2. **Update rate limiter settings for the token pool on Ethereum Sepolia**: Replace `` with your token pool address.
```bash
npx hardhat updateRateLimiters \
--pooladdress \
--remotechain avalancheFuji \
--ratelimiter both \
--outboundratelimitenabled true \
--outboundratelimitcapacity 20000000000000000000 \
--outboundratelimitrate 100000000000000000 \
--inboundratelimitenabled true \
--inboundratelimitcapacity 10000000000000000000 \
--inboundratelimitrate 100000000000000000 \
--network ethereumSepolia
```
Expected output:
```bash
$ npx hardhat updateRateLimiters \
--pooladdress 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D \
--remotechain avalancheFuji \
--ratelimiter both \
--outboundratelimitenabled true \
--outboundratelimitcapacity 20000000000000000000 \
--outboundratelimitrate 100000000000000000 \
--inboundratelimitenabled true \
--inboundratelimitcapacity 10000000000000000000 \
--inboundratelimitrate 100000000000000000 \
--network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T01:45:28.727Z info: ⚙️ Updating rate limiters for pool 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D on ethereumSepolia...
2025-10-22T01:45:28.730Z info: Remote chain: avalancheFuji
2025-10-22T01:45:28.731Z info: Remote chain selector: 14767482510784806043
2025-10-22T01:45:28.731Z info: Updating: both limiters
2025-10-22T01:45:29.327Z info: ✅ Remote chain is supported by the pool
2025-10-22T01:45:29.624Z info: ✅ Caller is the pool owner
2025-10-22T01:45:29.903Z info:
📊 Current Rate Limiters for pool 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D
2025-10-22T01:45:29.903Z info: Outbound → enabled=false, cap=0, rate=0
2025-10-22T01:45:29.904Z info: Inbound → enabled=false, cap=0, rate=0
2025-10-22T01:45:29.905Z info:
========== Preparing Update ==========
2025-10-22T01:45:29.905Z info: New Outbound → enabled=true, cap=20000000000000000000, rate=100000000000000000
2025-10-22T01:45:29.905Z info: New Inbound → enabled=true, cap=10000000000000000000, rate=100000000000000000
2025-10-22T01:45:29.905Z info:
⚡ Updating both limiters...
2025-10-22T01:45:31.847Z info: ⏳ Rate limiter update tx: 0x1e3862484d4bcb7ca4aba6a8723ec6c90ad9fe223e337363c14ed99822842bb5
2025-10-22T01:45:31.847Z info: Waiting for 3 confirmation(s)...
2025-10-22T01:46:03.375Z info: ✅ Rate limiters updated successfully
2025-10-22T01:46:03.376Z info: Transaction: 0x1e3862484d4bcb7ca4aba6a8723ec6c90ad9fe223e337363c14ed99822842bb5
```
### Verify the new rate limiter settings
After applying the new rate limiter settings, verify that they have been updated correctly.
- Use the `getCurrentRateLimits` task to verify the updated settings:
1. **Verify the updated rate limiter settings for the token pool on Avalanche Fuji**:
```bash
npx hardhat getCurrentRateLimits \
--pooladdress \
--remotechain ethereumSepolia \
--network avalancheFuji
```
Example output:
```bash
$ npx hardhat getCurrentRateLimits \
--pooladdress 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A \
--remotechain ethereumSepolia \
--network avalancheFuji
✅ Tasks loaded from /tasks/index.ts
2025-10-22T02:22:43.555Z info: 📊 Fetching rate limiter states for pool 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A on avalancheFuji...
2025-10-22T02:22:43.556Z info: Remote chain: ethereumSepolia
2025-10-22T02:22:43.556Z info: Remote chain selector: 16015286601757825753
2025-10-22T02:22:45.445Z info:
=== Rate Limiter States for ethereumSepolia ===
2025-10-22T02:22:45.445Z info: Pool Address: 0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A
2025-10-22T02:22:45.445Z info: Chain Selector: 16015286601757825753
2025-10-22T02:22:45.445Z info:
📤 Outbound Rate Limiter:
2025-10-22T02:22:45.445Z info: Enabled: true
2025-10-22T02:22:45.445Z info: Capacity: 10000000000000000000
2025-10-22T02:22:45.446Z info: Rate: 100000000000000000
2025-10-22T02:22:45.446Z info: Tokens: 10000000000000000000
2025-10-22T02:22:45.447Z info: Last Updated: 1761099755
2025-10-22T02:22:45.447Z info:
📥 Inbound Rate Limiter:
2025-10-22T02:22:45.447Z info: Enabled: true
2025-10-22T02:22:45.447Z info: Capacity: 20000000000000000000
2025-10-22T02:22:45.447Z info: Rate: 100000000000000000
2025-10-22T02:22:45.447Z info: Tokens: 20000000000000000000
2025-10-22T02:22:45.447Z info: Last Updated: 1761099755
2025-10-22T02:22:45.447Z info:
✅ Rate limiters fetched successfully
```
2. **Verify the updated rate limiter settings for the token pool on Ethereum Sepolia**:
```bash
npx hardhat getCurrentRateLimits \
--pooladdress \
--remotechain avalancheFuji \
--network ethereumSepolia
```
Example output:
```bash
$ npx hardhat getCurrentRateLimits \
--pooladdress 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D \
--remotechain avalancheFuji \
--network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T02:23:57.094Z info: 📊 Fetching rate limiter states for pool 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D on ethereumSepolia...
2025-10-22T02:23:57.095Z info: Remote chain: avalancheFuji
2025-10-22T02:23:57.095Z info: Remote chain selector: 14767482510784806043
2025-10-22T02:23:59.253Z info:
=== Rate Limiter States for avalancheFuji ===
2025-10-22T02:23:59.253Z info: Pool Address: 0xA29878301cA30a4639C33cD7EC8061E4265E1a5D
2025-10-22T02:23:59.253Z info: Chain Selector: 14767482510784806043
2025-10-22T02:23:59.254Z info:
📤 Outbound Rate Limiter:
2025-10-22T02:23:59.254Z info: Enabled: true
2025-10-22T02:23:59.254Z info: Capacity: 20000000000000000000
2025-10-22T02:23:59.254Z info: Rate: 100000000000000000
2025-10-22T02:23:59.255Z info: Tokens: 20000000000000000000
2025-10-22T02:23:59.255Z info: Last Updated: 1761099828
2025-10-22T02:23:59.255Z info:
📥 Inbound Rate Limiter:
2025-10-22T02:23:59.255Z info: Enabled: true
2025-10-22T02:23:59.255Z info: Capacity: 10000000000000000000
2025-10-22T02:23:59.255Z info: Rate: 100000000000000000
2025-10-22T02:23:59.255Z info: Tokens: 10000000000000000000
2025-10-22T02:23:59.255Z info: Last Updated: 1761099828
2025-10-22T02:23:59.255Z info:
✅ Rate limiters fetched successfully
```
### Test the rate limiter settings
To verify the rate limiter settings, initiate a cross-chain transfer between the token pools on Avalanche Fuji and Ethereum Sepolia. The rate limiter configuration will control the flow of tokens between these pools.
**Note**: Ensure that your externally owned account (EOA) has a sufficient balance of ERC20 tokens on Avalanche Fuji to complete the transfer.
In the example below, we use a token pool at address `0x5e8b0e5EAC57d73E7b93d85b8eA3fcaDa282868A` on Avalanche Fuji, which has burn and mint privileges for the token at address `0xb613B55897F07eAF430bF9a509498e55487305Ea`. We will transfer this token from Avalanche Fuji to Ethereum Sepolia. For your own test, substitute these addresses with the token pool and token addresses that you have deployed.
1. **Test Capacity**: Because the outbound capacity set is `1000000000000000000000` , let's transfer `1000000000000000000001` tokens from Avalanche Fuji to Ethereum Sepolia. This transfer should fail because the capacity is less than the number of tokens being transferred.
Command:
```bash
npx hardhat transferTokens --tokenaddress --amount 1000000000000000000001 --destinationchain ethereumSepolia --receiveraddress --network avalancheFuji
```
Expected output:
```bash
$ npx hardhat transferTokens --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --amount 1000000000000000000001 --destinationchain ethereumSepolia --receiveraddress 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf --network avalancheFuji
✅ Tasks loaded from /tasks/index.ts
2025-10-22T03:53:06.293Z info: 🚀 Transferring 1000000000000000000001 tokens via CCIP from avalancheFuji to ethereumSepolia...
2025-10-22T03:53:06.297Z info: Token: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-22T03:53:06.297Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-22T03:53:06.297Z info: Fee token: LINK
2025-10-22T03:53:07.247Z info: 💰 Estimated fees: 12758132149667117
2025-10-22T03:53:07.502Z info: Approving 1000000000000000000001 tokens for router 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2025-10-22T03:53:09.453Z info: Waiting for 2 confirmation(s)...
2025-10-22T03:53:23.404Z info: Approving 12758132149667117 LINK to router
2025-10-22T03:53:24.672Z info: Waiting for 2 confirmation(s)...
2025-10-22T03:53:33.834Z info: 💰 Estimated CCIP fees: 12758132149667117
2025-10-22T03:53:33.834Z info: Simulating CCIP message...
2025-10-22T03:53:35.890Z error: ❌ Simulation failed
2025-10-22T03:53:35.893Z error: ❌ TokenMaxCapacityExceeded (from RateLimiter)
2025-10-22T03:53:35.893Z error: Args:
2025-10-22T03:53:35.893Z error: [0]: 10000000000000000000
2025-10-22T03:53:35.893Z error: [1]: 1000000000000000000001
2025-10-22T03:53:35.893Z error: [2]: "0xb613B55897F07eAF430bF9a509498e55487305Ea"
```
Notice in the logs that the transfer failed because the capacity was exceeded: `TokenMaxCapacityExceeded`.
2. **Test Rate**: Now, let's transfer `10000000000000000000` tokens from Avalanche Fuji to Ethereum Sepolia, which will empty the bucket. After this transfer, we will attempt to transfer another `10000000000000000000` tokens. This transfer will fail because it takes 100 seconds to replenish the bucket.
1. **First transfer** (Successful):
Command:
```bash
npx hardhat transferTokens --tokenaddress --amount 10000000000000000000 --destinationchain ethereumSepolia --receiveraddress --network avalancheFuji
```
Expected output:
```bash
$ npx hardhat transferTokens --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --amount 10000000000000000000 --destinationchain ethereumSepolia --receiveraddress 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf --network avalancheFuji
✅ Tasks loaded from /tasks/index.ts
2025-10-22T04:01:11.893Z info: 🚀 Transferring 10000000000000000000 tokens via CCIP from avalancheFuji to ethereumSepolia...
2025-10-22T04:01:11.894Z info: Token: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-22T04:01:11.895Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-22T04:01:11.895Z info: Fee token: LINK
2025-10-22T04:01:12.809Z info: 💰 Estimated fees: 12757807740510307
2025-10-22T04:01:13.059Z info: Approving 10000000000000000000 tokens for router 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2025-10-22T04:01:15.068Z info: Waiting for 2 confirmation(s)...
2025-10-22T04:01:20.458Z info: Approving 12757807740510307 LINK to router
2025-10-22T04:01:21.705Z info: Waiting for 2 confirmation(s)...
2025-10-22T04:01:26.687Z info: 💰 Estimated CCIP fees: 12757807740510307
2025-10-22T04:01:26.687Z info: Simulating CCIP message...
2025-10-22T04:01:26.936Z info: Sending CCIP message...
2025-10-22T04:01:28.186Z info: ⏳ CCIP message tx: 0xb52fbc15e2aa90e35bd0d56073ce60c6b92626ac66ba219e1403927dfd31cfab
2025-10-22T04:01:28.186Z info: Waiting for 2 confirmation(s)...
2025-10-22T04:01:32.924Z info: ✅ CCIP message sent successfully
2025-10-22T04:01:32.924Z info: Transaction: 0xb52fbc15e2aa90e35bd0d56073ce60c6b92626ac66ba219e1403927dfd31cfab
2025-10-22T04:01:32.924Z info: Check status: https://ccip.chain.link/tx/0xb52fbc15e2aa90e35bd0d56073ce60c6b92626ac66ba219e1403927dfd31cfab
2025-10-22T04:01:32.924Z info: 📋 Transfer Summary:
2025-10-22T04:01:32.924Z info: Token: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-22T04:01:32.925Z info: Amount: 10000000000000000000
2025-10-22T04:01:32.925Z info: From: avalancheFuji
2025-10-22T04:01:32.925Z info: To: ethereumSepolia
2025-10-22T04:01:32.925Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-22T04:01:32.925Z info: Fee paid in: LINK
```
2. **Second transfer** (Failed):
Command:
```bash
npx hardhat transferTokens --tokenaddress --amount 10000000000000000000 --destinationchain ethereumSepolia --receiveraddress --network avalancheFuji
```
Expected output:
```bash
$ npx hardhat transferTokens --tokenaddress 0xb613B55897F07eAF430bF9a509498e55487305Ea --amount 10000000000000000000 --destinationchain ethereumSepolia --receiveraddress 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf --network avalancheFuji
✅ Tasks loaded from /tasks/index.ts
2025-10-22T04:02:15.242Z info: 🚀 Transferring 10000000000000000000 tokens via CCIP from avalancheFuji to ethereumSepolia...
2025-10-22T04:02:15.244Z info: Token: 0xb613B55897F07eAF430bF9a509498e55487305Ea
2025-10-22T04:02:15.245Z info: Receiver: 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-22T04:02:15.245Z info: Fee token: LINK
2025-10-22T04:02:16.183Z info: 💰 Estimated fees: 12757807740510307
2025-10-22T04:02:16.436Z info: Approving 10000000000000000000 tokens for router 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2025-10-22T04:02:18.432Z info: Waiting for 2 confirmation(s)...
2025-10-22T04:02:32.919Z info: Approving 12757807740510307 LINK to router
2025-10-22T04:02:34.144Z info: Waiting for 2 confirmation(s)...
2025-10-22T04:02:43.114Z info: 💰 Estimated CCIP fees: 12757807740510307
2025-10-22T04:02:43.114Z info: Simulating CCIP message...
2025-10-22T04:02:45.169Z error: ❌ Simulation failed
2025-10-22T04:02:45.175Z error: ❌ TokenRateLimitReached (from RateLimiter)
2025-10-22T04:02:45.175Z error: Args:
2025-10-22T04:02:45.175Z error: [0]: 30
2025-10-22T04:02:45.176Z error: [1]: 7000000000000000000
2025-10-22T04:02:45.176Z error: [2]: "0xb613B55897F07eAF430bF9a509498e55487305Ea"
```
Notice in the logs that the transfer failed because the rate limit was exceeded: `TokenRateLimitReached`.
---
# Enable your tokens in CCIP (Burn & Mint): Register from Safe multisig using Hardhat
Source: https://docs.chain.link/ccip/tutorials/evm/cross-chain-tokens/register-from-safe-burn-mint-hardhat
Last Updated: 2025-10-22
This tutorial will guide you through enabling your tokens in CCIP using [Hardhat](https://hardhat.org/) and Safe Multisig [smart accounts](https://docs.safe.global/home/glossary#smart-account). You will learn how to deploy tokens and set up *Burn & Mint* token pools using a 2-of-3 multi-signature Safe. After that, you will register the tokens in CCIP and configure them using multisig transactions without needing manual intervention. Finally, you will test the **Burn & Mint** token handling mechanism, where tokens are burned on the source blockchain, and an equivalent amount is minted on the destination blockchain.
## Introduction to Smart Accounts and Safe Multisig
### Introduction
A [**smart account**](https://docs.safe.global/home/glossary#smart-account) (also known as a smart contract account) leverages the programmability of smart contracts to extend their functionality and improve their security compared to externally owned accounts (EOAs). Smart accounts are controlled by one or multiple EOAs or other smart accounts, and all transactions must be initiated by one of these controllers.
Some common features of smart accounts include:
- **Multi-signature schemes**: Require multiple approvals for a transaction to be executed, enhancing security.
- **Transaction batching**: Combine multiple actions into a single transaction, reducing costs and improving efficiency.
- **Account recovery**: Allow for recovery mechanisms in case of lost access.
- **Gasless transactions**: Enable transaction fees to be paid by a third party or relayer.
**Safe** is one of the most trusted implementations of a smart account, offering a robust multi-signature mechanism. In this tutorial, we will use a Safe Multisig account, specifically a 2-of-3 multi-signature setup, where **two** out of **three** owners must approve a transaction to execute it. This setup ensures enhanced security and decentralized control over the assets.
The [**Protocol Kit**](https://docs.safe.global/sdk/protocol-kit) from Safe allows developers to interact with Safe, smart accounts through a TypeScript interface. This kit can be used to create new Safe accounts, update configurations, propose transactions, and execute them, making it an ideal choice for blockchain projects.
### How Safe Multisig Works
Before we proceed, let's take a moment to understand how the multisig transaction process works with a Safe smart account.
In this tutorial, we'll use a **2-of-3 multi-signature** setup as an example, where at least two out of the three Smart Account Owners must approve each transaction. Depending on your project's needs, this multisig process is valid for any scheme (e.g., 3-of-5, 4-of-7).
In Safe smart accounts, the **Smart Account Owners** are responsible for approving actions. Transactions are signed off-chain by the required number of owners, which improves efficiency and reduces gas costs. Once the necessary threshold is met, the signed transactions are submitted to the blockchain for execution. The Safe smart account (a smart contract) verifies the signatures and enforces the required number of approvals before executing the transaction.
This process enhances security, particularly for sensitive tasks such as registering a token administrator or configuring a token pool. Multi-signature ensures that no single owner can act alone.
The steps for enabling your tokens in CCIP follow the same flow as the [previous tutorials](/ccip/tutorials/evm/cross-chain-tokens/register-from-eoa-burn-mint-hardhat) that used externally owned accounts (EOA). The key difference here is that for sensitive actions like token administrator registration or token pool configuration, we assume your project uses a Safe multisig. Therefore, multiple signatures are required off-chain; in some cases, a batch of transactions will be submitted to save on gas costs.
The following sequence diagram illustrates the multisig transaction flow in a Safe smart account, from off-chain signature collection to on-chain execution:
In this diagram, the process is as follows:
1. **Signer 1** initiates the batch of transactions (one or multiple transactions) and signs off-chain.
2. **Signer 1** shares the batch with **Signer 2**, who signs it off-chain.
3. Once the required number of signatures is collected (in this case, **two**), the batch is submitted for execution.
4. The **Smart Account** verifies the signatures and checks that the signature threshold has been met.
5. The **Smart Account** then executes each transaction in the batch, interacting with the Target Smart Contract(s).
6. If any transaction fails, the entire batch is reverted. If all transactions succeed, they are confirmed on-chain.
By following this process, we maintain the security of multisig transactions while improving efficiency through the off-chain signature collection and gas savings from transaction batching.
### Steps covered in this tutorial
We will cover the following key steps:
1. **Creating a Safe Account**: You will create a 2-of-3 multi-signature Safe that will serve as the owner of the token and token pool contracts. This Safe will also manage administrative tasks, such as configuring token pools and registering as the token admin in the token admin registry.
2. **Deploying Tokens**: You will deploy your [`BurnMintERC20`](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol) tokens on the Ethereum Sepolia and BASE Sepolia testnets and transfer ownership to the Safe account.
3. **Deploying Token Pools**: Once your tokens are deployed, you will deploy [`BurnMintTokenPool`](/ccip/api-reference/evm/v1.6.2/burn-mint-token-pool) token pools on Ethereum Sepolia and BASE Sepolia. The Safe account will own each token pool.
4. **Claiming and Accepting the Admin Role**: This is a two-step process that will be managed using the Safe multi-signature account. It involves creating and signing multiple meta-transactions off-chain before executing them on-chain to register Safe as the token admin and accept the admin role for managing the tokens and token pools.
1. You will call the [`RegistryModuleOwnerCustom`](/ccip/api-reference/evm/v1.6.2/registry-module-owner-custom) contract's [`registerAdminViaGetCCIPAdmin`](/ccip/api-reference/evm/v1.6.2/registry-module-owner-custom#registeradminviagetccipadmin) function to register the Safe as the token admin. This role is required to enable your token in CCIP.
2. Once claimed, you will call the [`TokenAdminRegistry`](/ccip/api-reference/evm/v1.6.2/token-admin-registry) contract's [`acceptAdminRole`](/ccip/api-reference/evm/v1.6.2/token-admin-registry#acceptadminrole) function to complete the registration process.
Meta-transactions are used here to batch these two actions, allowing both steps to be executed efficiently. The meta-transactions are created off-chain and signed by each of the two required Safe owners. This off-chain signing process reduces gas costs and enhances security, as the transactions are only broadcasted to the blockchain once all required signatures are collected.
5. **Linking Tokens to Pools**: You will use the Safe account to call the [`TokenAdminRegistry`](/ccip/api-reference/evm/v1.6.2/token-admin-registry) contract's [`setPool`](/ccip/api-reference/evm/v1.6.2/token-admin-registry#setpool) function to associate each token with its respective token pool.
6. **Configuring Token Pools**: You will configure each token pool by setting cross-chain transfer parameters, such as token pool rate limits and enabled destination chains, using multisig transactions through the Safe account.
7. **Granting Mint and Burn Roles**: You will grant the mint and burn roles to the token pools on each linked token using the Safe account. These roles are required for the token pools to mint and burn tokens during cross-chain transfers.
8. **Minting Tokens**: You will mint tokens on Ethereum Sepolia. These tokens will later be used to test cross-chain transfers to BASE Sepolia.
9. **Transferring Tokens**: Finally, you will transfer tokens from Ethereum Sepolia to BASE Sepolia using CCIP. You can pay CCIP fees using either LINK tokens or native gas tokens.
By the end of this tutorial, you will have successfully deployed, registered, configured, and enabled your tokens and token pools for use in CCIP. All are managed securely through a multi-signature Safe account.
## Before You Begin
1. Make sure you have Node.js v22.10.0 or above installed. If not, **install Node.js v22.10.0**:
[Download Node.js v22.10.0](https://nodejs.org/en/download/) if you don't have it installed. Optionally, you can use the [nvm package](https://www.npmjs.com/package/nvm) to switch between Node.js versions:
```bash
nvm use 22
```
Verify that the correct version of Node.js is installed:
```bash
node -v
```
Example output:
```bash
$ node -v
v22.15.0
```
2. **Clone the repository and navigate to the project directory:**
```bash
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
```
3. **Install dependencies for the project:**
```bash
npm install
```
4. **Compile the project:**
```bash
npm run compile
```
5. **Encrypt your environment variables for higher security:**\
The project uses [@chainlink/env-enc](https://www.npmjs.com/package/@chainlink/env-enc) to encrypt your environment variables at rest. Follow the steps below to configure your environment securely:
1. Set an encryption password for your environment variables:
```bash
npx env-enc set-pw
```
2. Set up a `.env.enc` file with the necessary variables for Ethereum Sepolia and BASE Sepolia testnets. Use the following command to add the variables:
```bash
npx env-enc set
```
Variables to configure:
- `ETHEREUM_SEPOLIA_RPC_URL`: A URL for the *Ethereum Sepolia* testnet. You can get a personal endpoint from services like [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/).
- `BASE_SEPOLIA_RPC_URL`: A URL for the *BASE Sepolia* testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/).
- `PRIVATE_KEY`: The private key for the first signer of the Safe multisig account. If you use MetaMask, you can follow this [guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/) to export your private key. **Note:** This key is used to create and sign the transaction of the first signer.
- `PRIVATE_KEY_2`: The private key for the second signer of the Safe multisig account. If you use MetaMask, you can follow this [guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/) to export your private key. **Note:** This key is used to create and sign the transaction of the second signer.
- `ETHERSCAN_API_KEY`: An API key from Etherscan to verify your contracts. You can obtain one from [Etherscan](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics).
6. **Fund the EOA linked to the first private key with LINK and native gas tokens**:\
Make sure your EOA has enough LINK and native gas tokens on Ethereum Sepolia and BASE Sepolia to cover transaction fees. You can use the [Chainlink faucets](https://faucets.chain.link/) to get testnet tokens. **Important clarifications**:
- Off-chain signatures are collected for this tutorial. The first EOA is responsible for sending the transactions to the Safe smart account, meaning only the first EOA requires enough native gas tokens for these transactions.
- When transferring the deployed tokens from Ethereum Sepolia to BASE Sepolia, the first EOA will be used to pay the CCIP fees in LINK. Therefore, it is crucial that the first EOA has sufficient LINK tokens to cover these fees. If a different EOA were to initiate the CCIP transfer, that EOA would need to hold enough LINK tokens.
## Tutorial
### Deploy Safe Smart Accounts
In this step, you will deploy a Safe smart account on both Ethereum Sepolia and BASE Sepolia using the `deploySafe` task. The Safe smart account will serve as the multi-signature account, requiring approvals from multiple owners to authorize transactions. You can customize the number of owners and the required threshold for signatures.
Below is an explanation of the parameters used during deployment:
| Parameter | Description | Default | Required |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | -------- |
| `owners` | A comma-separated list of owner addresses. These are the Ethereum addresses that will control the Safe smart account and authorize transactions. | N/A | Yes |
| `threshold` | The number of required signatures to authorize a transaction. This must be at least 1 and cannot exceed the number of owners provided. | `1` | Yes |
| `network` | The blockchain on which the Safe smart account will be deployed. Examples include `ethereumSepolia` for Ethereum Sepolia and `baseSepolia` for BASE Sepolia. | N/A | Yes |
1. **Deploy a Safe on Ethereum Sepolia** (Replace `0xOwnerAddress1`, `0xOwnerAddress2`, and `0xOwnerAddress3` with your Ethereum addresses):
```bash
npx hardhat deploySafe --owners "0xOwnerAddress1,0xOwnerAddress2,0xOwnerAddress3" --threshold 2 --network ethereumSepolia
```
Example output:
```bash
$ npx hardhat deploySafe --owners 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA,0xA028Cedc47485aB2F1230551E4f3a6871B764263,0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf --threshold 2 --network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:02:27.024Z info: ⚙️ Deploying Safe multisig on ethereumSepolia...
2025-10-22T15:02:27.026Z info: Owners (3): 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA, 0xA028Cedc47485aB2F1230551E4f3a6871B764263, 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-22T15:02:27.026Z info: Threshold: 2
2025-10-22T15:02:27.026Z info: Initializing Safe Protocol Kit...
2025-10-22T15:02:27.026Z info: Salt nonce: 0x3bd008a765832c18c35a3f31f49a1d2f3b43c1be0eaaf752f78e7ff84e203912
2025-10-22T15:02:27.027Z info: Deploying Safe contract...
2025-10-22T15:02:33.751Z info: Predicted Safe address: 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:02:36.827Z info: Deploying Safe contract on-chain...
2025-10-22T15:02:44.545Z info: Deployment transaction: 0xa766fe32019a84d6ad5ee8ada4bb45dc676f9ba72b6bf7e671aa3a4ca0202d11
2025-10-22T15:02:44.546Z info: Waiting for confirmation...
2025-10-22T15:03:04.169Z info: ✅ Safe deployed successfully
2025-10-22T15:03:04.170Z info: Safe address: 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:03:04.170Z info: Network: ethereumSepolia
```
2. **Deploy a Safe on BASE Sepolia** (Replace `0xOwnerAddress1`, `0xOwnerAddress2`, and `0xOwnerAddress3` with your Ethereum addresses):
```bash
npx hardhat deploySafe --owners "0xOwnerAddress1,0xOwnerAddress2,0xOwnerAddress3" --threshold 2 --network baseSepolia
```
Example output:
```bash
$ npx hardhat deploySafe --owners 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA,0xA028Cedc47485aB2F1230551E4f3a6871B764263,0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf --threshold 2 --network baseSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:04:29.505Z info: ⚙️ Deploying Safe multisig on baseSepolia...
2025-10-22T15:04:29.507Z info: Owners (3): 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA, 0xA028Cedc47485aB2F1230551E4f3a6871B764263, 0x27d7A69C878F9c8f51f4e53703abCE9bAcd2D9bf
2025-10-22T15:04:29.507Z info: Threshold: 2
2025-10-22T15:04:29.507Z info: Initializing Safe Protocol Kit...
2025-10-22T15:04:29.508Z info: Salt nonce: 0x06b5e0404c2a7c2e7be856725e6666ece610e09f94f22ce5a24366ba6d428e73
2025-10-22T15:04:29.508Z info: Deploying Safe contract...
2025-10-22T15:04:37.618Z info: Predicted Safe address: 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:04:39.082Z info: Deploying Safe contract on-chain...
2025-10-22T15:04:44.074Z info: Deployment transaction: 0xaea379d168d27f30b11112dca71070dcc4758861676752fce932870bf0f76f8e
2025-10-22T15:04:44.075Z info: Waiting for confirmation...
2025-10-22T15:04:44.463Z info: ✅ Safe deployed successfully
2025-10-22T15:04:44.463Z info: Safe address: 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:04:44.463Z info: Network: baseSepolia
```
### Deploy Tokens
In this step, you will deploy a token on both Ethereum Sepolia and BASE Sepolia using the `deployTokenWithSafe` task, then transfer ownership of the token to the Safe multisig account. This ensures that the Safe smart account controls the token, requiring multiple signatures to authorize any future administrative actions.
Below is an explanation of the parameters used during deployment:
| Parameter | Description | Default | Required |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `safeaddress` | The address of the Safe smart account that will own the deployed token. | N/A | Yes |
| `name` | The full name of the token. | N/A | Yes |
| `symbol` | The shorthand symbol representing the token. | N/A | Yes |
| `decimals` | The number of decimal places the token supports (e.g., `18` means 1 token is represented as 1e18 smallest units). | `18` | No |
| `maxsupply` | The maximum supply of tokens. Set to `0` for unlimited supply. | `0` | No |
| `premint` | The amount of tokens to be minted to the owner at the time of deployment. If set to `0`, no tokens will be minted to the owner during deployment. | `0` | No |
| `verifycontract` | Flag to verify the contract on Etherscan or a similar blockchain explorer. Pass this flag to enable verification, omit to skip. | `false` | No |
| `network` | The blockchain on which the token will be deployed. Examples include `ethereumSepolia` for Ethereum Sepolia and `baseSepolia` for BASE Sepolia. | N/A | Yes |
1. **Deploy a token on Ethereum Sepolia** (Replace `0xSafeAddress` with the address of the Safe smart account. You can also adapt the token name and symbol as needed):
```bash
npx hardhat deployTokenWithSafe \
--name "BnM sak" \
--symbol BnMsak \
--decimals 18 \
--maxsupply 0 \
--safeaddress 0xD87c0BCbB9df8C07De0ea9af14296248E082c347 \
--verifycontract \
--network ethereumSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:08:03.909Z info: ⚙️ Deploying BurnMintERC20 on ethereumSepolia...
2025-10-22T15:08:03.911Z info: Token: BnM sak (BnMsak)
2025-10-22T15:08:03.911Z info: Decimals: 18
2025-10-22T15:08:03.911Z info: Max supply: 0
2025-10-22T15:08:03.911Z info: Premint: 0
2025-10-22T15:08:03.911Z info: Safe address: 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:08:03.911Z info: Deploying contract...
2025-10-22T15:08:19.639Z info: ✅ Token deployed at: 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da
2025-10-22T15:08:19.640Z info: Waiting for 3 confirmation(s)...
2025-10-22T15:08:28.643Z info: Verifying contract...
📤 Submitted source code for verification on Etherscan:
@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol:BurnMintERC20
Address: 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da
⏳ Waiting for verification result...
✅ Contract verified successfully on Etherscan!
@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol:BurnMintERC20
Explorer: https://sepolia.etherscan.io/address/0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da#code
2025-10-22T15:08:43.278Z info: ✅ Token contract verified successfully
2025-10-22T15:08:43.278Z info: Transferring ownership of token to Safe at 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:08:44.181Z info: Granting DEFAULT_ADMIN_ROLE to Safe: 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:08:47.077Z info: ⏳ Grant role tx: 0xf6176199bdbc3b638c75cf67bd5674b8aca5216e4aafae057ed638aca8d2ceb1
2025-10-22T15:09:16.482Z info: ✅ Safe granted DEFAULT_ADMIN_ROLE
2025-10-22T15:09:16.482Z info: Setting CCIP admin to Safe...
2025-10-22T15:09:18.450Z info: ⏳ Set CCIP admin tx: 0x205fae446501ebf6f1949992194c8d5364a194cbaf1a6f5a33b4e12c5c02e708
2025-10-22T15:10:04.979Z info: ✅ Safe set as CCIP admin
2025-10-22T15:10:04.979Z info:
✅ Token deployment and configuration complete!
2025-10-22T15:10:04.979Z info: Token address: 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da
2025-10-22T15:10:04.979Z info: Safe address: 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
```
2. **Deploy a token on BASE Sepolia** (Replace `0xSafeAddress` with the address of the Safe smart account. You can also adapt the token name and symbol as needed):
```bash
npx hardhat deployTokenWithSafe \
--name "BnM sak" \
--symbol BnMsak \
--decimals 18 \
--maxsupply 0 \
--safeaddress 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116 \
--verifycontract \
--network baseSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:11:31.865Z info: ⚙️ Deploying BurnMintERC20 on baseSepolia...
2025-10-22T15:11:31.867Z info: Token: BnM sak (BnMsak)
2025-10-22T15:11:31.867Z info: Decimals: 18
2025-10-22T15:11:31.867Z info: Max supply: 0
2025-10-22T15:11:31.867Z info: Premint: 0
2025-10-22T15:11:31.867Z info: Safe address: 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:11:31.867Z info: Deploying contract...
2025-10-22T15:11:39.696Z info: ✅ Token deployed at: 0x7074d32876ed00946d15ea71991eeb86be09666e
2025-10-22T15:11:39.696Z info: Waiting for 2 confirmation(s)...
2025-10-22T15:11:45.698Z info: Verifying contract...
📤 Submitted source code for verification on Basescan:
@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol:BurnMintERC20
Address: 0x7074d32876ed00946d15ea71991eeb86be09666e
⏳ Waiting for verification result...
✅ Contract verified successfully on Basescan!
@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol:BurnMintERC20
Explorer: https://sepolia.basescan.org/address/0x7074d32876ed00946d15ea71991eeb86be09666e#code
2025-10-22T15:12:08.298Z info: ✅ Token contract verified successfully
2025-10-22T15:12:08.299Z info: Transferring ownership of token to Safe at 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:12:08.776Z info: Granting DEFAULT_ADMIN_ROLE to Safe: 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:12:11.801Z info: ⏳ Grant role tx: 0xf1f1c1e57f2299e900ba798e0343d0dade30cddc26dcfa277a96e69731f7d301
2025-10-22T15:12:15.863Z info: ✅ Safe granted DEFAULT_ADMIN_ROLE
2025-10-22T15:12:15.863Z info: Setting CCIP admin to Safe...
2025-10-22T15:12:18.364Z info: ⏳ Set CCIP admin tx: 0x6778f3bb020031fb8c74519a7bb22cb6224291bfd3a5b0c3cc1cef84bef19f53
2025-10-22T15:12:24.474Z info: ✅ Safe set as CCIP admin
2025-10-22T15:12:24.474Z info:
✅ Token deployment and configuration complete!
2025-10-22T15:12:24.474Z info: Token address: 0x7074d32876ed00946d15ea71991eeb86be09666e
2025-10-22T15:12:24.474Z info: Safe address: 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
```
### Deploy Token Pools
In this step, you will deploy a token pool on both Ethereum Sepolia and BASE Sepolia using the `deployTokenPoolWithSafe` task, then transfer ownership of the token pool to the Safe smart account. This ensures that the Safe smart account controls the token pool, providing a secure, multisig setup for managing the token pool operations.
Below is an explanation of the parameters used during deployment:
| Parameter | Description | Default | Required |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token that the pool will manage. | N/A | Yes |
| `safeaddress` | The address of the Safe smart account that will own the token pool. | N/A | Yes |
| `localtokendecimals` | The number of decimals for the token on this chain. | `18` | No |
| `verifycontract` | Flag to verify the contract on Etherscan or a similar blockchain explorer. Pass this flag to enable verification, omit to skip. | `false` | No |
| `network` | The blockchain on which the token pool will be deployed. Examples include `ethereumSepolia` for Ethereum Sepolia and `baseSepolia` for BASE Sepolia. | N/A | Yes |
1. **Deploy a Burn and Mint token pool on Ethereum Sepolia** (Replace `0xTokenAddress` and `0xSafeAddress` with the token address and Safe smart account address, respectively):
```bash
npx hardhat deployTokenPoolWithSafe \
--tokenaddress 0xTokenAddress \
--safeaddress 0xSafeAddress \
--localtokendecimals 18 \
--verifycontract \
--network ethereumSepolia
```
Example output:
```bash
$ npx hardhat deployTokenPoolWithSafe \
--tokenaddress 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da \
--safeaddress 0xD87c0BCbB9df8C07De0ea9af14296248E082c347 \
--localtokendecimals 18 \
--verifycontract \
--network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:20:07.175Z info: ⚙️ Deploying BurnMintTokenPool on ethereumSepolia
2025-10-22T15:20:07.177Z info: Token: 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da
2025-10-22T15:20:07.177Z info: Safe: 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:20:07.177Z info: Decimals: 18
2025-10-22T15:20:11.885Z info: ⏳ Deployment tx: 0x4a6ad26be32f80ad61486122da5e0cbd21cc86ec8a28c4f3b7616d9b646bc236
2025-10-22T15:20:11.886Z info: Waiting for 3 confirmation(s)...
2025-10-22T15:20:52.348Z info: ✅ TokenPool deployed at: 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f
2025-10-22T15:20:52.349Z info: ⚙️ Verifying contract...
The contract at 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f has already been verified on Etherscan.
If you need to verify a partially verified contract, please use the --force flag.
Explorer: https://sepolia.etherscan.io/address/0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f#code
2025-10-22T15:20:56.007Z info: ✅ TokenPool contract verified successfully
2025-10-22T15:20:56.008Z info: ⚙️ Transferring ownership to Safe: 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:21:17.968Z info: ✅ Ownership transferred to Safe at 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
```
2. **Deploy a Burn and Mint token pool on BASE Sepolia** (Replace `0xTokenAddress` and `0xSafeAddress` with the token address and Safe smart account address, respectively):
```bash
npx hardhat deployTokenPoolWithSafe \
--tokenaddress 0xTokenAddress \
--safeaddress 0xSafeAddress \
--localtokendecimals 18 \
--verifycontract \
--network baseSepolia
```
Example output:
```bash
$ npx hardhat deployTokenPoolWithSafe \
--tokenaddress 0x7074d32876ed00946d15ea71991eeb86be09666e \
--safeaddress 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116 \
--localtokendecimals 18 \
--verifycontract \
--network baseSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:22:16.815Z info: ⚙️ Deploying BurnMintTokenPool on baseSepolia
2025-10-22T15:22:16.819Z info: Token: 0x7074d32876ed00946d15ea71991eeb86be09666e
2025-10-22T15:22:16.820Z info: Safe: 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:22:16.820Z info: Decimals: 18
2025-10-22T15:22:20.541Z info: ⏳ Deployment tx: 0x1eb547756ae29576375cefd4a8f43ce8e9726dbbb06639197896a9aa06a660ef
2025-10-22T15:22:20.541Z info: Waiting for 2 confirmation(s)...
2025-10-22T15:22:24.985Z info: ✅ TokenPool deployed at: 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032
2025-10-22T15:22:24.986Z info: ⚙️ Verifying contract...
The contract at 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032 has already been verified on Basescan.
If you need to verify a partially verified contract, please use the --force flag.
Explorer: https://sepolia.basescan.org/address/0x48AF36da7c6cFb23A9C16eAE29766975F33dF032#code
2025-10-22T15:22:28.530Z info: ✅ TokenPool contract verified successfully
2025-10-22T15:22:28.531Z info: ⚙️ Transferring ownership to Safe: 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:22:30.727Z info: ✅ Ownership transferred to Safe at 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
```
### Accept Ownership of Token Pools
After deploying the token pools and transferring ownership to the Safe smart account, the Safe smart account must formally accept ownership of the token pools. This ensures that all administrative actions for the token pools will require multisig approval, ensuring a secure and decentralized management process.
The process will use the Safe smart account to sign the transaction off-chain, collect the required signatures from multiple owners, and then execute it on-chain.
Below is an explanation of the parameters used during this task:
| Parameter | Description | Required |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `contractaddress` | The address of the contract whose ownership the Safe smart account is accepting. | Yes |
| `safeaddress` | The address of the Safe smart account that will accept ownership of the contract. | Yes |
| `network` | The blockchain network where the transaction will be executed. Examples include `ethereumSepolia` for Ethereum Sepolia and `baseSepolia` for BASE Sepolia. | Yes |
1. **Accept ownership of the token pool on Ethereum Sepolia** (Replace `0xContractAddress` and `0xSafeAddress` with the token pool contract address and Safe smart account address, respectively):
```bash
npx hardhat acceptOwnershipFromSafe --contractaddress 0xContractAddress --safeaddress 0xSafeAddress --network ethereumSepolia
```
Example output:
```bash
$ npx hardhat acceptOwnershipFromSafe \
--contractaddress 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f \
--safeaddress 0xD87c0BCbB9df8C07De0ea9af14296248E082c347 \
--network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:24:16.608Z info: ⚙️ Accepting ownership of contract 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f via Safe 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:24:17.379Z info: ✅ Contract exists at 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f
2025-10-22T15:24:17.770Z info: ⚙️ Current owner: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA
2025-10-22T15:24:17.770Z info: ✅ Current owner is 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA - verifying ownership transfer to 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:24:17.770Z info: ⚙️ Simulating acceptOwnership transaction...
2025-10-22T15:24:18.049Z info: ✅ Simulation successful - Safe can accept ownership
2025-10-22T15:24:18.050Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:24:25.938Z info: ✅ Safe transaction created
2025-10-22T15:24:26.973Z info: ✅ Signed by owner 1
2025-10-22T15:24:27.653Z info: ✅ Signed by owner 2
2025-10-22T15:24:27.653Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:24:27.653Z info: 🚀 Executing Safe transaction to accept ownership...
2025-10-22T15:24:34.033Z info: ⏳ Waiting 3 blocks for tx 0x16f729787430afc090f7902ce82ecd942eef841ae5735d667928e6f9cdd5b1c3 to confirm...
2025-10-22T15:24:38.841Z info: ✅ Ownership accepted successfully for 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f
```
2. **Accept ownership of the token pool on BASE Sepolia** (Replace `0xContractAddress` and `0xSafeAddress` with the token pool contract address and Safe smart account address, respectively):
```bash
npx hardhat acceptOwnershipFromSafe \
--contractaddress 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032 \
--safeaddress 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116 \
--network baseSepolia
```
Example output:
```bash
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:25:24.635Z info: ⚙️ Accepting ownership of contract 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032 via Safe 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:25:26.236Z info: ✅ Contract exists at 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032
2025-10-22T15:25:26.510Z info: ⚙️ Current owner: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA
2025-10-22T15:25:26.510Z info: ✅ Current owner is 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA - verifying ownership transfer to 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:25:26.511Z info: ⚙️ Simulating acceptOwnership transaction...
2025-10-22T15:25:26.806Z info: ✅ Simulation successful - Safe can accept ownership
2025-10-22T15:25:26.807Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:25:30.875Z info: ✅ Safe transaction created
2025-10-22T15:25:31.874Z info: ✅ Signed by owner 1
2025-10-22T15:25:33.189Z info: ✅ Signed by owner 2
2025-10-22T15:25:33.189Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:25:33.190Z info: 🚀 Executing Safe transaction to accept ownership...
2025-10-22T15:25:41.053Z info: ⏳ Waiting 2 blocks for tx 0x759d9dd2691ade673c144890e73e9da5e59d7b0b9028426888ade3ab864edccf to confirm...
2025-10-22T15:25:41.455Z info: ✅ Ownership accepted successfully for 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032
```
### Claim and Accept Token Admin Role using Safe
In this step, you will use the `claimAndAcceptAdminRoleFromSafe` task to claim and accept the admin role for the deployed tokens in a single Ethereum transaction. By leveraging Safe's batching feature, we can efficiently combine the two operations—claiming the admin role and accepting the admin role—into one on-chain interaction. This reduces gas costs and improves efficiency.
The process will use the Safe smart account to sign the transaction off-chain, collect the required signatures from multiple owners, and then execute it on-chain.
Below is an explanation of the parameters used during this task:
| Parameter | Description | Default | Required |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token for which the admin role will be claimed and accepted. | N/A | Yes |
| `safeaddress` | The address of the Safe smart account that will execute the transactions and become the token admin. | N/A | Yes |
| `network` | The blockchain on which the transaction will be executed. Examples include `ethereumSepolia` for Ethereum Sepolia and `baseSepolia` for BASE Sepolia. | N/A | Yes |
1. **Claim and accept the admin role for the token on Ethereum Sepolia** (Replace `0xTokenAddress` and `0xSafeAddress` with the token address and Safe smart account address, respectively):
```bash
npx hardhat claimAndAcceptAdminRoleFromSafe --tokenaddress 0xTokenAddress --safeaddress 0xSafeAddress --network ethereumSepolia
```
Example output:
```bash
$ npx hardhat claimAndAcceptAdminRoleFromSafe --tokenaddress 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da --safeaddress 0xD87c0BCbB9df8C07De0ea9af14296248E082c347 --network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:28:38.782Z info: ⚙️ Connecting to token contract at 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da...
2025-10-22T15:28:39.465Z info: ⚙️ Current CCIP admin: 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:28:39.465Z info: ✅ CCIP admin matches Safe address - proceeding with claim and accept
2025-10-22T15:28:40.496Z info: ⚙️ Prepared Safe meta-transactions for 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da
2025-10-22T15:28:40.496Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:28:43.729Z info: ✅ Safe transaction (claim + accept) created
2025-10-22T15:28:44.140Z info: ✅ Signed by owner 1
2025-10-22T15:28:44.545Z info: ✅ Signed by owner 2
2025-10-22T15:28:44.545Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:28:44.546Z info: 🚀 Executing Safe transaction (claim + accept admin role)...
2025-10-22T15:28:49.649Z info: ⏳ Waiting 3 blocks for tx 0x9b91763c65df9c272ca26a532ea5dc29a62aa367d4fdad93d685f86124ae4d99 confirmation...
2025-10-22T15:29:03.911Z info: ✅ Admin role claimed and accepted for 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da
```
2. **Claim and accept the admin role for the token on BASE Sepolia** (Replace `0xTokenAddress` and `0xSafeAddress` with the token address and Safe smart account address, respectively):
```bash
npx hardhat claimAndAcceptAdminRoleFromSafe --tokenaddress 0xTokenAddress --safeaddress 0xSafeAddress --network baseSepolia
```
Example output:
```bash
$ npx hardhat claimAndAcceptAdminRoleFromSafe --tokenaddress 0x7074d32876ed00946d15ea71991eeb86be09666e --safeaddress 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116 --network baseSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:29:46.121Z info: ⚙️ Connecting to token contract at 0x7074d32876ed00946d15ea71991eeb86be09666e...
2025-10-22T15:29:46.732Z info: ⚙️ Current CCIP admin: 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:29:46.733Z info: ✅ CCIP admin matches Safe address - proceeding with claim and accept
2025-10-22T15:29:49.539Z info: ⚙️ Prepared Safe meta-transactions for 0x7074d32876ed00946d15ea71991eeb86be09666e
2025-10-22T15:29:49.540Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:29:55.544Z info: ✅ Safe transaction (claim + accept) created
2025-10-22T15:29:56.133Z info: ✅ Signed by owner 1
2025-10-22T15:29:57.228Z info: ✅ Signed by owner 2
2025-10-22T15:29:57.228Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:29:57.229Z info: 🚀 Executing Safe transaction (claim + accept admin role)...
2025-10-22T15:30:03.859Z info: ⏳ Waiting 2 blocks for tx 0xe5e1cc59d5b734ddbbabb2451a21c79a9c0a8cbd34fb669244c1144f28338e86 confirmation...
2025-10-22T15:30:04.219Z info: ✅ Admin role claimed and accepted for 0x7074d32876ed00946d15ea71991eeb86be09666e
```
### Grant Mint and Burn Roles using Safe
In this step, you will use the `grantMintBurnRoleFromSafe` task to grant mint and burn roles to both the token pool and the Safe smart account on Ethereum Sepolia and BASE Sepolia. The Safe smart account will handle the transaction to securely assign these roles, ensuring that multiple owners sign off on the operation. Granting mint and burn roles is essential to allow the token pool and the Safe account to manage token issuance and burning, and to prepare for future cross-chain transfers.
This process will grant:
- Mint and burn roles to the token pool for handling cross-chain operations.
- Mint and burn roles to the Safe smart account for minting tokens to EOAs for testing purposes.
Below is an explanation of the parameters used during this task:
| Parameter | Description | Required |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| `tokenaddress` | The address of the deployed token contract for which mint and burn roles will be granted. | Yes |
| `burnerminters` | A comma-separated list of addresses (token pools and Safe smart account) to which mint and burn roles will be granted. | Yes |
| `safeaddress` | The address of the Safe smart account that will execute the transaction to grant mint and burn roles. | Yes |
| `network` | The blockchain on which the mint and burn roles will be granted. Examples include `ethereumSepolia` for Ethereum Sepolia and `baseSepolia` for BASE Sepolia. | Yes |
1. **Grant mint and burn roles on Ethereum Sepolia** (Replace `0xTokenAddress`, `0xPoolAddress`, and `0xSafeAddress` with the token address, token pool address, and Safe smart account address, respectively):
```bash
npx hardhat grantMintBurnRoleFromSafe \
--tokenaddress 0xTokenAddress \
--burnerminters 0xPoolAddress,0xSafeAddress \
--safeaddress 0xSafeAddress \
--network ethereumSepolia
```
Example output:
```bash
$ npx hardhat grantMintBurnRoleFromSafe --tokenaddress 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da --burnerminters 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f,0xD87c0BCbB9df8C07De0ea9af14296248E082c347 --safeaddress 0xD87c0BCbB9df8C07De0ea9af14296248E082c347 --network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:37:10.201Z info: ⚙️ Connecting to token contract at 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da...
2025-10-22T15:37:10.612Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:37:13.743Z info: ⚙️ Setting up Safe transaction to grant roles to: 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f, 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:37:14.323Z info: ✅ Safe transaction created
2025-10-22T15:37:16.990Z info: ✅ Signed by owner 1
2025-10-22T15:37:18.027Z info: ✅ Signed by owner 2
2025-10-22T15:37:18.027Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:37:18.027Z info: 🚀 Executing Safe transaction to grant roles...
2025-10-22T15:37:29.642Z info: ⏳ Waiting 3 blocks for tx 0x3c2a143047e7917dfcaa5f2f926000215263e187601999a88b1fab7f1dbfed96 confirmation...
2025-10-22T15:37:39.215Z info: ✅ Mint and burn roles granted successfully
```
2. **Grant mint and burn roles on BASE Sepolia** (Replace `0xTokenAddress`, `0xPoolAddress`, and `0xSafeAddress` with the token address, token pool address, and Safe smart account address, respectively):
```bash
npx hardhat grantMintBurnRoleFromSafe \
--tokenaddress 0xTokenAddress \
--burnerminters 0xPoolAddress,0xSafeAddress \
--safeaddress 0xSafeAddress \
--network baseSepolia
```
Example output:
```bash
$ npx hardhat grantMintBurnRoleFromSafe --tokenaddress 0x7074d32876ed00946d15ea71991eeb86be09666e --burnerminters 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032,0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116 --safeaddress 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116 --network baseSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:38:21.300Z info: ⚙️ Connecting to token contract at 0x7074d32876ed00946d15ea71991eeb86be09666e...
2025-10-22T15:38:21.767Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:38:26.148Z info: ⚙️ Setting up Safe transaction to grant roles to: 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032, 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:38:26.451Z info: ✅ Safe transaction created
2025-10-22T15:38:27.971Z info: ✅ Signed by owner 1
2025-10-22T15:38:29.289Z info: ✅ Signed by owner 2
2025-10-22T15:38:29.289Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:38:29.289Z info: 🚀 Executing Safe transaction to grant roles...
2025-10-22T15:38:40.886Z info: ⏳ Waiting 2 blocks for tx 0xcd1317734142f9b90c49e16830c0cc31626f97387b8bb63b83885051d4d47462 confirmation...
2025-10-22T15:38:42.178Z info: ✅ Mint and burn roles granted successfully
```
### Set Pool using Safe
In this step, you will use the `setPoolFromSafe` task to link a token to a token pool on both Ethereum Sepolia and BASE Sepolia. The Safe smart account will be used to execute the transaction, ensuring that the pool is set securely with multisig approval. Multiple owners will sign the transaction off-chain before it is executed on-chain.
Below is an explanation of the parameters used during this task:
| Parameter | Description | Required |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `tokenaddress` | The address of the token for which the pool will be set. | Yes |
| `pooladdress` | The address of the token pool to be linked to the token. | Yes |
| `safeaddress` | The address of the Safe smart account that will execute the transaction. | Yes |
| `network` | The blockchain on which the transaction will be executed. Examples include `ethereumSepolia` for Ethereum Sepolia and `baseSepolia` for BASE Sepolia. | Yes |
1. **Set the token pool on Ethereum Sepolia** (Replace `0xTokenAddress`, `0xPoolAddress`, and `0xSafeAddress` with the token address, token pool address, and Safe smart account address, respectively):
```bash
npx hardhat setPoolFromSafe --tokenaddress 0xTokenAddress --pooladdress 0xPoolAddress --safeaddress 0xSafeAddress --network ethereumSepolia
```
Example output:
```bash
$ npx hardhat setPoolFromSafe --tokenaddress 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da --pooladdress 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f --safeaddress 0xD87c0BCbB9df8C07De0ea9af14296248E082c347 --network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:40:57.725Z info: ⚙️ Connecting to TokenAdminRegistry at 0x95F29FEE11c5C55d26cCcf1DB6772DE953B37B82 on ethereumSepolia
2025-10-22T15:40:58.327Z info: ⚙️ Preparing to set pool for token 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da → 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f, current admin: 0xD87c0BCbB9df8C07De0ea9af14296248E082c347
2025-10-22T15:40:58.328Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:41:01.865Z info: ✅ Safe transaction created
2025-10-22T15:41:02.576Z info: ✅ Signed by owner 1
2025-10-22T15:41:02.980Z info: ✅ Signed by owner 2
2025-10-22T15:41:02.980Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:41:02.980Z info: 🚀 Executing Safe transaction to set pool for 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da...
2025-10-22T15:41:12.075Z info: ⏳ Waiting 3 blocks for tx 0xd981df37f210b759420277db91db98907aedd1f1ea38885832e96e1a89d8585e confirmation...
2025-10-22T15:41:25.444Z info: ✅ Pool set for token 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da → 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f
```
2. **Set the token pool on BASE Sepolia** (Replace `0xTokenAddress`, `0xPoolAddress`, and `0xSafeAddress` with the token address, token pool address, and Safe smart account address, respectively):
```bash
npx hardhat setPoolFromSafe --tokenaddress 0xTokenAddress --pooladdress 0xPoolAddress --safeaddress 0xSafeAddress --network baseSepolia
```
Example output:
```bash
$ npx hardhat setPoolFromSafe --tokenaddress 0x7074d32876ed00946d15ea71991eeb86be09666e --pooladdress 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032 --safeaddress 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116 --network baseSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:42:57.749Z info: ⚙️ Connecting to TokenAdminRegistry at 0x736D0bBb318c1B27Ff686cd19804094E66250e17 on baseSepolia
2025-10-22T15:42:58.543Z info: ⚙️ Preparing to set pool for token 0x7074d32876ed00946d15ea71991eeb86be09666e → 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032, current admin: 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116
2025-10-22T15:42:58.544Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:43:04.152Z info: ✅ Safe transaction created
2025-10-22T15:43:04.833Z info: ✅ Signed by owner 1
2025-10-22T15:43:05.425Z info: ✅ Signed by owner 2
2025-10-22T15:43:05.425Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:43:05.425Z info: 🚀 Executing Safe transaction to set pool for 0x7074d32876ed00946d15ea71991eeb86be09666e...
2025-10-22T15:43:13.454Z info: ⏳ Waiting 2 blocks for tx 0xa3eed5c5a01581e5e5b981c46e42b9c9bb5f0f24cba04b261fffe18db9125be3 confirmation...
2025-10-22T15:43:13.746Z info: ✅ Pool set for token 0x7074d32876ed00946d15ea71991eeb86be09666e → 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032
```
### Configure Token Pools using Safe
In this step, you will use the `applyChainUpdatesFromSafe` task to configure a token pool for cross-chain interactions. By leveraging the Safe smart account, you can securely update the configuration of the token pool to support remote chains, including setting rate limits and linking it to remote pools and tokens.
The task handles complex cross-chain setups, allowing you to define rate limits for both inbound and outbound token transfers. The transaction is signed by the Safe owners off-chain and then executed on-chain, ensuring secure multi-signature control over the pool configuration.
Below is an explanation of the parameters used during this task:
| Parameter | Description | Default | Required |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `pooladdress` | The address of the token pool to be configured. | N/A | Yes |
| `remotechain` | The identifier of the remote blockchain (e.g., `ethereumSepolia` for Ethereum Sepolia or `baseSepolia` for BASE Sepolia). | N/A | Yes |
| `remotepooladdresses` | Comma-separated list of remote pool addresses. | N/A | Yes |
| `remotetokenaddress` | The address of the token on the remote chain. | N/A | Yes |
| `outboundratelimitenabled` | Flag to enable outbound rate limits for cross-chain transfers. Pass this flag to enable, omit to disable. | `false` | No |
| `outboundratelimitcapacity` | The maximum number of tokens that can be transferred outbound in a single burst (bucket capacity for the outbound rate limiter). | `0` | No |
| `outboundratelimitrate` | The rate at which tokens are refilled in the outbound bucket (tokens per second). | `0` | No |
| `inboundratelimitenabled` | Flag to enable inbound rate limits for cross-chain transfers. Pass this flag to enable, omit to disable. | `false` | No |
| `inboundratelimitcapacity` | The maximum number of tokens that can be transferred inbound in a single burst (bucket capacity for the inbound rate limiter). | `0` | No |
| `inboundratelimitrate` | The rate at which tokens are refilled in the inbound bucket (tokens per second). | `0` | No |
| `safeaddress` | The address of the Safe smart account that will execute the transaction to configure the pool. | N/A | Yes |
| `network` | The blockchain on which the pool is being configured. Examples include `ethereumSepolia` for Ethereum Sepolia and `baseSepolia` for BASE Sepolia. | N/A | Yes |
1. **Configure the token pool on Ethereum Sepolia** (Replace `0xPoolAddress`, `0xRemotePoolAddress`, `0xRemoteTokenAddress`, and `0xSafeAddress` with the token pool address, remote token pool address, remote token address, and Safe smart account address, respectively):
```bash
npx hardhat applyChainUpdatesFromSafe --pooladdress 0xPoolAddress --remotechain baseSepolia --remotepooladdresses 0xRemotePoolAddress --remotetokenaddress 0xRemoteTokenAddress --safeaddress 0xSafeAddress --network ethereumSepolia
```
Example output:
```bash
$ npx hardhat applyChainUpdatesFromSafe --pooladdress 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f --remotechain baseSepolia --remotepooladdresses 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032 --remotetokenaddress 0x7074d32876ed00946d15ea71991eeb86be09666e --safeaddress 0xD87c0BCbB9df8C07De0ea9af14296248E082c347 --network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:51:43.618Z info: ⚙️ Applying chain updates for pool 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f → remote chain baseSepolia
2025-10-22T15:51:44.419Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:51:52.717Z info: ✅ Safe transaction created
2025-10-22T15:51:53.691Z info: ✅ Signed by owner 1
2025-10-22T15:51:54.656Z info: ✅ Signed by owner 2
2025-10-22T15:51:54.656Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:51:54.656Z info: 🚀 Executing Safe transaction for pool 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f...
2025-10-22T15:52:02.815Z info: ⏳ Waiting 3 blocks for tx 0x532b168b2d987a6e510209b95c5504e51a693af7316e20b99c2c58627df188ba confirmation...
2025-10-22T15:52:16.503Z info: ✅ Pool configured successfully for baseSepolia
```
2. **Configure the token pool on BASE Sepolia** (Replace `0xPoolAddress`, `0xRemotePoolAddress`, `0xRemoteTokenAddress`, and `0xSafeAddress` with the token pool address, remote token pool address, remote token address, and Safe smart account address, respectively):
```bash
npx hardhat applyChainUpdatesFromSafe --pooladdress 0xPoolAddress --remotechain ethereumSepolia --remotepooladdresses 0xRemotePoolAddress --remotetokenaddress 0xRemoteTokenAddress --safeaddress 0xSafeAddress --network baseSepolia
```
Example output:
```bash
$ npx hardhat applyChainUpdatesFromSafe --pooladdress 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032 --remotechain ethereumSepolia --remotepooladdresses 0x4133f9c5d62Ac1e155B379695c31752249E2Ad4f --remotetokenaddress 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da --safeaddress 0xA9E127DeFf4f46dCA489349A15F7EB8A059Fb116 --network baseSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:53:02.257Z info: ⚙️ Applying chain updates for pool 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032 → remote chain ethereumSepolia
2025-10-22T15:53:03.055Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:53:09.233Z info: ✅ Safe transaction created
2025-10-22T15:53:10.029Z info: ✅ Signed by owner 1
2025-10-22T15:53:11.255Z info: ✅ Signed by owner 2
2025-10-22T15:53:11.255Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:53:11.255Z info: 🚀 Executing Safe transaction for pool 0x48AF36da7c6cFb23A9C16eAE29766975F33dF032...
2025-10-22T15:53:18.235Z info: ⏳ Waiting 2 blocks for tx 0x54cc1761693cc563629a0c64b4f094081fd7c02d08a979049dfdb3cab756739d confirmation...
2025-10-22T15:53:18.841Z info: ✅ Pool configured successfully for ethereumSepolia
```
### Mint Tokens to an EOA using Safe
In this step, you will use the `mintTokensFromSafe` task to mint tokens to an EOA on Ethereum Sepolia. This process uses a Safe smart account to securely manage the minting process, ensuring that the transaction is signed by multiple owners off-chain before being executed on-chain. These tokens will be used for transfers through CCIP from Ethereum Sepolia to BASE Sepolia.
Below is an explanation of the parameters used during this task:
| Parameter | Description | Required |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------- |
| `tokenaddress` | The address of the token contract from which the tokens will be minted. | Yes |
| `amount` | The amount of tokens to mint for each recipient address. | Yes |
| `receiveraddresses` | A comma-separated list of recipient addresses (EOAs) that will receive the minted tokens. | Yes |
| `safeaddress` | The address of the Safe smart account that will execute the transaction to mint tokens. | Yes |
| `network` | The blockchain on which the minting transaction will be executed. For example, `ethereumSepolia` for Ethereum Sepolia. | Yes |
1. **Mint tokens to an EOA on Ethereum Sepolia** (Replace `0xTokenAddress`, `0xSafeAddress`, and `0xReceiverAddress` with the token address, Safe smart account address, and recipient address, respectively):
```bash
npx hardhat mintTokensFromSafe \
--tokenaddress 0xTokenAddress \
--receiveraddresses 0xReceiverAddress \
--amount 100000000000000000000 \
--safeaddress 0xSafeAddress \
--network ethereumSepolia
```
Example output:
```bash
$ npx hardhat mintTokensFromSafe --tokenaddress 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da --receiveraddresses 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA --amount 100000000000000000000 --safeaddress 0xD87c0BCbB9df8C07De0ea9af14296248E082c347 --network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:54:47.249Z info: ⚙️ Connecting to token contract at 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da...
2025-10-22T15:54:48.393Z info: ⚙️ Checking if Safe has MINTER_ROLE...
2025-10-22T15:54:48.393Z info: ✅ Safe has MINTER_ROLE - proceeding with mint transaction
2025-10-22T15:54:48.393Z info: ⚙️ Initializing Safe Protocol Kit for multisig transaction...
2025-10-22T15:54:54.934Z info: ⚙️ Preparing to mint 100000000000000000000 tokens to receivers: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA
2025-10-22T15:54:55.526Z info: ✅ Safe transaction created
2025-10-22T15:54:56.553Z info: ✅ Signed by owner 1
2025-10-22T15:54:57.606Z info: ✅ Signed by owner 2
2025-10-22T15:54:57.606Z info: ✅ Transaction has 2 signature(s)
2025-10-22T15:54:57.607Z info: 🚀 Executing Safe transaction to mint tokens...
2025-10-22T15:55:05.307Z info: ⏳ Waiting 3 blocks for tx 0x9147027424f893cf7b18f8ca4317c72ef4c45432b5e4aed4da29616da9ff591b confirmation...
2025-10-22T15:55:14.261Z info: ✅ Tokens minted successfully via Safe multisig
2025-10-22T15:55:15.033Z info: ℹ️ 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA → balance: 100000000000000000000 BnMsak
```
### Transfer Tokens
In this step, you will use the `transferTokens` task to transfer tokens from Ethereum Sepolia to BASE Sepolia using CCIP. You have two options for paying CCIP fees: using LINK tokens or native gas tokens.
You will interact with the `IRouterClient` contract, specifically calling the `ccipSend()` function to initiate the token transfer.
Below is an explanation of the parameters used during the token transfer process:
| Parameter | Description | Default | Required |
| ------------------ | -------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| `tokenaddress` | The address of the token being transferred. | N/A | Yes |
| `amount` | The amount of tokens to transfer. | N/A | Yes |
| `destinationchain` | The blockchain to which the tokens will be transferred. Examples include `baseSepolia`, and `ethereumSepolia`. | N/A | Yes |
| `receiveraddress` | The address of the receiver on the destination blockchain. | N/A | Yes |
| `fee` | The type of fee used for the transfer, either `LINK` or `native`. | `LINK` | No |
| `network` | The blockchain on which the token transfer will be initiated. Examples include `baseSepolia`, and `ethereumSepolia`. | N/A | Yes |
#### Pay fees in LINK
Call the CCIP Router to transfer tokens from Ethereum Sepolia to BASE Sepolia, paying the CCIP fees in LINK tokens. Replace the token address, amount, receiver address, and blockchain with the appropriate values:
```bash
npx hardhat transferTokens --tokenaddress 0xTokenAddress --amount 2000000000000000000 --destinationchain baseSepolia --receiveraddress 0xReceiverAddress --fee LINK --network ethereumSepolia
```
Example output:
```bash
$ npx hardhat transferTokens --tokenaddress 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da --amount 2000000000000000000 --destinationchain baseSepolia --receiveraddress 0xA028Cedc47485aB2F1230551E4f3a6871B764263 --fee LINK --network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:56:36.021Z info: 🚀 Transferring 2000000000000000000 tokens via CCIP from ethereumSepolia to baseSepolia...
2025-10-22T15:56:36.022Z info: Token: 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da
2025-10-22T15:56:36.023Z info: Receiver: 0xA028Cedc47485aB2F1230551E4f3a6871B764263
2025-10-22T15:56:36.023Z info: Fee token: LINK
2025-10-22T15:56:37.377Z info: 💰 Estimated fees: 12710869350886886
2025-10-22T15:56:37.702Z info: Approving 2000000000000000000 tokens for router 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
2025-10-22T15:56:40.548Z info: Waiting for 3 confirmation(s)...
2025-10-22T15:57:16.204Z info: Approving 12710869350886886 LINK to router
2025-10-22T15:57:17.625Z info: Waiting for 3 confirmation(s)...
2025-10-22T15:57:48.593Z info: 💰 Estimated CCIP fees: 12710869350886886
2025-10-22T15:57:48.594Z info: Simulating CCIP message...
2025-10-22T15:57:49.549Z info: Sending CCIP message...
2025-10-22T15:57:51.399Z info: ⏳ CCIP message tx: 0xd365f959ff5be2a21a6cd7426845757f2a00ca1ae7a56a3f781f129eb0a0e807
2025-10-22T15:57:51.399Z info: Waiting for 3 confirmation(s)...
2025-10-22T15:58:27.246Z info: ✅ CCIP message sent successfully
2025-10-22T15:58:27.246Z info: Transaction: 0xd365f959ff5be2a21a6cd7426845757f2a00ca1ae7a56a3f781f129eb0a0e807
2025-10-22T15:58:27.247Z info: Check status: https://ccip.chain.link/tx/0xd365f959ff5be2a21a6cd7426845757f2a00ca1ae7a56a3f781f129eb0a0e807
2025-10-22T15:58:27.247Z info: 📋 Transfer Summary:
2025-10-22T15:58:27.247Z info: Token: 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da
2025-10-22T15:58:27.247Z info: Amount: 2000000000000000000
2025-10-22T15:58:27.248Z info: From: ethereumSepolia
2025-10-22T15:58:27.248Z info: To: baseSepolia
2025-10-22T15:58:27.248Z info: Receiver: 0xA028Cedc47485aB2F1230551E4f3a6871B764263
2025-10-22T15:58:27.248Z info: Fee paid in: LINK
```
You can check the status of the message on the [Chainlink CCIP Explorer](https://ccip.chain.link) by visiting the provided URL. In this example, the message ID is `0x706c9057d25c69c9e1191a5a86b07a2156ef78bc7eaff6334c02dad5e905a9fb`.
#### Pay fees in native gas tokens
Call the CCIP Router to transfer tokens from Ethereum Sepolia to BASE Sepolia, paying the CCIP fees in native gas tokens. Replace the token address, amount, receiver address, and blockchain with the appropriate values:
```bash
npx hardhat transferTokens --tokenaddress 0xTokenAddress --amount 2000000000000000000 --destinationchain baseSepolia --receiveraddress 0xReceiverAddress --fee native --network ethereumSepolia
```
Example output:
```bash
$ npx hardhat transferTokens --tokenaddress 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da --amount 2000000000000000000 --destinationchain baseSepolia --receiveraddress 0xA028Cedc47485aB2F1230551E4f3a6871B764263 --fee native --network ethereumSepolia
✅ Tasks loaded from /tasks/index.ts
2025-10-22T15:59:17.942Z info: 🚀 Transferring 2000000000000000000 tokens via CCIP from ethereumSepolia to baseSepolia...
2025-10-22T15:59:17.943Z info: Token: 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da
2025-10-22T15:59:17.944Z info: Receiver: 0xA028Cedc47485aB2F1230551E4f3a6871B764263
2025-10-22T15:59:17.944Z info: Fee token: native
2025-10-22T15:59:19.091Z info: 💰 Estimated fees: 64134211874037
2025-10-22T15:59:19.464Z info: Approving 2000000000000000000 tokens for router 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
2025-10-22T15:59:22.083Z info: Waiting for 3 confirmation(s)...
2025-10-22T15:59:49.077Z info: 💰 Estimated CCIP fees: 64134211874037
2025-10-22T15:59:49.077Z info: Simulating CCIP message...
2025-10-22T15:59:49.354Z info: Sending CCIP message...
2025-10-22T15:59:50.740Z info: ⏳ CCIP message tx: 0x4796f9d7176c8bd90a320e373b0d73aa62948b689a170ac255a16aa4500b093a
2025-10-22T15:59:50.740Z info: Waiting for 3 confirmation(s)...
2025-10-22T16:00:26.196Z info: ✅ CCIP message sent successfully
2025-10-22T16:00:26.196Z info: Transaction: 0x4796f9d7176c8bd90a320e373b0d73aa62948b689a170ac255a16aa4500b093a
2025-10-22T16:00:26.197Z info: Check status: https://ccip.chain.link/tx/0x4796f9d7176c8bd90a320e373b0d73aa62948b689a170ac255a16aa4500b093a
2025-10-22T16:00:26.197Z info: 📋 Transfer Summary:
2025-10-22T16:00:26.197Z info: Token: 0xdca6ab0a735be79fd6b5864a3b1e3f597310b5da
2025-10-22T16:00:26.197Z info: Amount: 2000000000000000000
2025-10-22T16:00:26.197Z info: From: ethereumSepolia
2025-10-22T16:00:26.197Z info: To: baseSepolia
2025-10-22T16:00:26.197Z info: Receiver: 0xA028Cedc47485aB2F1230551E4f3a6871B764263
2025-10-22T16:00:26.197Z info: Fee paid in: native
```
You can check the status of the message on the [Chainlink CCIP Explorer](https://ccip.chain.link) by visiting the provided URL.
---
# Add CCIP Networks for Cross-Chain Token Tutorials (Hardhat)
Source: https://docs.chain.link/ccip/tutorials/evm/cross-chain-tokens/configure-additional-networks-hardhat
Last Updated: 2025-10-30
The [smart-contract-examples](https://github.com/smartcontractkit/smart-contract-examples/tree/main/ccip/cct/hardhat) repository includes default configurations for common CCIP testnet networks. This guide shows how to add support for additional networks.
## Add a Network
Add the network configuration to [`config/networks.ts`](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/cct/hardhat/config/networks.ts):
```typescript
export const configData = {
// ... existing networks
optimismSepolia: {
chainFamily: "evm",
chainId: 11155420,
chainSelector: "5224473277236331295",
router: "0x114A20A10b43D4115e5aeef7345a1A71d2a60C57",
rmnProxy: "0xb40A3109075965cc09E93719e33E748abf680dAe",
tokenAdminRegistry: "0x1d702b1FA12F347f0921C722f9D9166F00DEB67A",
registryModuleOwnerCustom: "0x49c4ba01dc6F5090f9df43Ab8F79449Db91A0CBB",
link: "0xE4aB69C077896252FAFBD49EFD26B5D171A32410",
confirmations: 2,
nativeCurrencySymbol: "ETH",
},
}
```
Set the RPC URL:
```bash
npx env-enc set OPTIMISM_SEPOLIA_RPC_URL
```
The network is now available in all Hardhat tasks using `--network optimismSepolia`.
**Environment variable naming**: The system converts network names from camelCase to SNAKE_CASE and adds `_RPC_URL`. Examples: `optimismSepolia` → `OPTIMISM_SEPOLIA_RPC_URL`, `avalancheFuji` → `AVALANCHE_FUJI_RPC_URL`.
## Configuration Fields
| Field | Required | Description | Source |
| --------------------------- | -------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `chainFamily` | Yes | Blockchain VM type: `"evm"` for Ethereum-compatible chains, `"svm"` for Solana | `config/types.ts` |
| `chainId` | Yes | EVM chain ID | Blockchain's official documentation (preferred) or [ChainList](https://chainlist.org/) |
| `chainSelector` | Yes | CCIP identifier for the network | [CCIP Directory](/ccip/directory) |
| `router` | Yes | CCIP Router contract address | [CCIP Directory](/ccip/directory) |
| `rmnProxy` | Yes | RMN Proxy contract address | [CCIP Directory](/ccip/directory) |
| `tokenAdminRegistry` | Yes | Token Admin Registry address | [CCIP Directory](/ccip/directory) |
| `registryModuleOwnerCustom` | Yes | Registry Module Owner address | [CCIP Directory](/ccip/directory) |
| `link` | Yes | LINK token contract address | [CCIP Directory](/ccip/directory) |
| `confirmations` | No | Number of block confirmations before considering transaction final | Blockchain's finality characteristics and your risk tolerance |
| `nativeCurrencySymbol` | No | Native gas token symbol (e.g., `"ETH"`, `"AVAX"`, `"POL"`) | Blockchain's official documentation |
Find all CCIP addresses in the [CCIP Directory - Testnet](/ccip/directory/testnet) or [CCIP Directory - Mainnet](/ccip/directory/mainnet).
## Test
Deploy a token to verify the configuration:
```bash
npx hardhat deployToken --name "Test Token" --symbol TEST --network optimismSepolia
```
## Contract Verification (Optional)
Most networks are natively supported. Add `--verifycontract` when deploying:
```bash
npx hardhat deployToken --name "Test Token" --symbol TEST --network optimismSepolia --verifycontract
```
For networks not in [Hardhat's chain descriptors](https://github.com/NomicFoundation/hardhat/blob/main/v-next/hardhat/src/internal/builtin-plugins/network-manager/chain-descriptors.ts), add to `hardhat.config.ts`:
```typescript
chainDescriptors: {
12345: {
name: "New Network",
chainType: "generic",
blockExplorers: {
etherscan: {
name: "NewScan",
url: "https://newscan.io",
apiUrl: "https://api.newscan.io/api",
},
},
},
}
```
With [Etherscan API V2](https://docs.etherscan.io/introduction), a single `ETHERSCAN_API_KEY` works across all Etherscan-compatible networks.
---
# Test CCIP Locally
Source: https://docs.chain.link/ccip/tutorials/evm/test-ccip-locally
Last Updated: 2025-05-19
[Chainlink Local](https://github.com/smartcontractkit/chainlink-local) provides a comprehensive set of tools and libraries to test your smart contracts with CCIP locally. By using Chainlink Local, you can quickly set up a local testing environment, simulate Chainlink services, and debug your contracts before deploying them to actual testnets.
## Why Use Chainlink Local?
Testing your smart contracts locally can save you a significant amount of time and effort. With Chainlink Local, you can:
- **Quickly identify and fix issues**: Debug your contracts in a controlled environment before deploying them to testnets.
- **Save time and resources**: Reduce the need for repeated deployments to testnets, speeding up the development process.
## Guides for Different Environments
### Foundry
For Foundry users, the following guides will help you set up and test your CCIP smart contracts locally:
[Foundry Guides for Chainlink Local](/chainlink-local/build/ccip/foundry)
### Hardhat
For Hardhat users, the following guides provide step-by-step instructions to integrate and test CCIP smart contracts locally:
[Hardhat Guides for Chainlink Local](/chainlink-local/build/ccip/hardhat)
### RemixIDE
For users who prefer RemixIDE, these guides will assist you in setting up and testing your CCIP smart contracts locally within the Remix environment:
[RemixIDE Guides for Chainlink Local](/chainlink-local/build/ccip/remix)
---
# Transfer USDC with Data
Source: https://docs.chain.link/ccip/tutorials/evm/usdc
Last Updated: 2025-05-19
USDC is a digital dollar backed 100% and is always redeemable 1:1 for US dollars. The [stablecoin](https://chain.link/education-hub/stablecoins) is issued by [Circle](https://www.circle.com/en/usdc) on multiple blockchain platforms.
This guide will first explain how Chainlink CCIP enables native USDC transfers when both the source and destination blockchains support [Circle's Cross-Chain Transfer Protocol (CCTP)](https://www.circle.com/en/cross-chain-transfer-protocol).
Additionally, it will outline how CCIP also supports transferring Bridged USDC on blockchains that **are not** CCTP-enabled, allowing projects to later migrate to CCTP-enabled transfers if approved by Circle.
The hands-on tutorial at the end demonstrates how to use Chainlink CCIP to transfer USDC and arbitrary data from a smart contract on *Avalanche Fuji* to a smart contract on *Ethereum Sepolia*.
**Note**: In addition to programmable token transfers, you can also use CCIP to transfer USDC tokens without data. Check the [Mainnets](/ccip/directory/mainnet) and [Testnets](/ccip/directory/testnet) configuration pages to learn on which blockchains CCIP supports USDC transfers.
## Architecture
### Native USDC vs. Bridged USDC
New blockchains frequently encounter the **cold start problem**—a scarcity of initial liquidity and limited user adoption that hampers the development and functionality of decentralized applications (dApps). Without sufficient stablecoin liquidity, essential use cases such as borrowing, lending, and trading remain constrained.
To overcome this challenge, Circle introduced the [Bridged USDC Standard](https://www.circle.com/blog/bridged-usdc-standard). This standard facilitates the seamless issuance of Bridged USDC on any EVM-compatible blockchain, enabling third-party teams to deploy USDC without awaiting native support from Circle. By standardizing bridged token issuance, Circle aims to:
- **Reduce Liquidity Fragmentation**: Ensure a unified and efficient USDC supply across multiple blockchains.
- **Enhance User Experience**: Minimize complexities and inconsistencies in managing multiple bridged USDC versions.
- **Facilitate Seamless Upgrades**: Enable projects to transition smoothly to Native USDC once their blockchain is approved for native issuance via the [Cross-Chain Transfer Protocol (CCTP)](https://www.circle.com/en/cross-chain-transfer-protocol).
Projects using Bridged USDC can easily migrate to Native USDC once their blockchain is approved for CCTP by Circle. This migration ensures that user balances, contract addresses, and integrations remain intact, eliminating the need for complex token swaps or liquidity migrations. For more detailed information, refer to Circle's [Bridged USDC Standard](https://www.circle.com/blog/bridged-usdc-standard).
### How CCIP Works with USDC
Chainlink CCIP maintains a consistent [API](/ccip/api-reference/evm/v1.6.2/i-router-client) regardless of whether the transfer involves Native USDC or Bridged USDC. Here's how it operates in both scenarios:
- The sender has to interact with the CCIP router to initiate a cross-chain transaction, similar to the process for any other token transfers. See the [Transfer Tokens](/ccip/tutorials/evm/transfer-tokens-from-contract) guide to learn more.
- The process uses the same onchain components including the Router, OnRamp, Commit Store, OffRamp, and Token Pool.
- The process uses the same offchain components including the Committing DON, Executing DON, and the Risk Management Network.
- USDC transfers also benefit from CCIP additional security provided by the [Risk Management Network](/ccip/concepts/architecture/key-concepts#risk-management-network).
#### Native USDC (CCTP-enabled)
The diagram below shows that the USDC token pools and Executing DON handle the integration with Circle's contracts and offchain CCTP Attestation API. As with any other supported ERC-20 token, USDC has a linked token pool on each supported blockchain to facilitate OnRamp and OffRamp operations. To learn more about these components, read the [architecture page](/ccip/concepts/architecture/onchain/evm/overview).
The following describes the operational process:
1. On the source blockchain:
1. When the sender initiates a transfer of USDC, the USDC token pool interacts with CCTP's contract to burn USDC tokens and specifies the USDC token pool address on the destination blockchain as the authorized caller to mint them.
2. CCTP burns the specified USDC tokens and emits an associated CCTP event.
2. Offchain:
1. The Circle attestation service listens to CCTP events on the source blockchain.
2. The CCIP [Executing DON](/ccip/concepts/architecture/offchain/overview#executing-ocr-process) listens to relevant CCTP events on the source blockchain. When it captures such an event, it calls the Circle Attestation service API to request an attestation. An attestation is a signed authorization to mint the specified amount of USDC on the destination blockchain.
3. On the destination blockchain:
1. The [Executing DON](/ccip/concepts/architecture/offchain/overview#executing-ocr-process) provides the attestation to the [OffRamp contract](/ccip/concepts/architecture/onchain/evm/components#offramp).
2. The OffRamp contract calls the USDC token pool with the USDC amount to be minted, the Receiver address, and the Circle attestation.
3. The USDC token pool calls the CCTP contract. The CCTP contract verifies the attestation signature before minting the specified USDC amount into the Receiver.
4. If there is data in the CCIP message and the Receiver is not an EOA, then the OffRamp contract transmits the CCIP message via the [Router](/ccip/concepts/architecture/onchain/evm/components#router) contract to the Receiver.
#### Bridged USDC (non-CCTP-enabled)
To facilitate USDC transfers between a blockchain that issues native USDC and another that only supports Bridged USDC, Chainlink CCIP employs the [Lock and Mint mechanism](/ccip/concepts/cross-chain-token/overview#token-handling-mechanisms). This approach ensures that each Bridged USDC token on the destination blockchain is fully backed by an equivalent amount of native USDC locked on the source blockchain. Accounting for Non-CCTP-Enabled USDC is isolated on a per-blockchain basis for security and risk mitigation.
1. **Lock native USDC on Source**: The [Lock and Release token pool](https://github.com/smartcontractkit/ccip/blob/ccip-develop/contracts/src/v0.8/ccip/pools/USDC/HybridLockReleaseUSDCTokenPool.sol) locks the specified USDC tokens.
2. **Mint Bridged USDC on Destination**: This Bridged USDC serves as a proxy for the original USDC, enabling liquidity and user adoption without direct integration with CCTP. A [Burn and Mint token pool](/ccip/concepts/cross-chain-token/evm/token-pools#standard-token-pools) is recommended for the Destination Chain; it will mint Bridged USDC for incoming transfers and burn Bridged USDC for outgoing transfers. Other token pools are also supported, depending on the preferences and constraints of the Destination Chain.
## Example
In this tutorial, you will learn how to send USDC tokens from a smart contract on Avalanche Fuji to a smart contract on Ethereum Sepolia using Chainlink CCIP and pay CCIP fees in LINK tokens.
The process uses the following steps:
1. **Transfer USDC and Data:** Initiate a transfer of USDC tokens and associated data from the Sender contract on Avalanche Fuji. The data includes the required arguments and the signature of the `stake` function from the Staker contract.
2. **Receive and Stake:** The Receiver contract on Ethereum Sepolia receives the tokens and data. Then, it uses this data to make a low-level call to the Staker contract, executing the `stake` function to stake USDC on behalf of a beneficiary.
3. **Redeem Staked Tokens:** The beneficiary can redeem the staked tokens for USDC later.
The purpose of including the function signature and arguments in the data is to demonstrate how arbitrary data can support a variety of scenarios and use cases. By sending specific instructions within the data, you can define various interactions between smart contracts across different blockchain networks and make your decentralized application more flexible and powerful.
### Before you begin
1. You should understand how to write, compile, deploy, and fund a smart contract. If you need to brush up on the basics, read this [tutorial](/quickstarts/deploy-your-first-contract), which will guide you through using the [Solidity programming language](https://soliditylang.org/), interacting with the [MetaMask wallet](https://metamask.io) and working within the [Remix Development Environment](https://remix.ethereum.org/).
2. Your account must have some AVAX and LINK tokens on *Avalanche Fuji* and ETH tokens on *Ethereum Sepolia*. You can use the [Chainlink faucet](https://faucets.chain.link/) to acquire testnet tokens.
3. Check the [CCIP Directory](/ccip/directory) to confirm that USDC are supported for your lane. In this example, you will transfer tokens from *Avalanche Fuji* to *Ethereum Sepolia* so check the list of supported tokens [here](/ccip/directory/testnet/chain/avalanche-fuji-testnet).
4. Use the [Circle faucet](https://faucet.circle.com/) to acquire USDC tokens on *Avalanche Fuji*.
5. Learn how to [fund your contract](/resources/fund-your-contract). This guide shows how to fund your contract in LINK, but you can use the same guide for funding your contract with any ERC-20 tokens as long as they appear in the list of tokens in MetaMask.
### Tutorial
#### Deploy your contracts
Deploy the Sender contract on *Avalanche Fuji*:
1. [Open the Sender contract in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/usdc/Sender.sol\&autoCompile=true).
2. Compile your contract.
3. Deploy, fund your sender contract on *Avalanche Fuji* and enable sending messages to *Ethereum Sepolia*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, click on *Deploy & Run Transactions* and select *Injected Provider - MetaMask* from the environment list. Remix will then interact with your MetaMask wallet to communicate with *Avalanche Fuji*.
3. Fill in your blockchain's router, LINK, and USDC contract addresses. The router and USDC addresses can be found on the [CCIP Directory](/ccip/directory) and the LINK contract address on the [LINK token contracts page](/resources/link-token-contracts). For Avalanche Fuji, the addresses are:
- Router address: 0xf694e193200268f9a4868e4aa017a0118c9a8177
- LINK contract address: 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846
- USDC contract address: 0x5425890298aed601595a70AB815c96711a31Bc65
4. Click the **transact** button. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
5. Open MetaMask and fund your contract with USDC tokens. You can transfer 1 *USDC* to your contract.
6. Fund your contract with LINK tokens. You can transfer 70 *LINK* to your contract. In this example, LINK is used to pay the CCIP fees.
**Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this tutorial, you can get additional testnet LINK
from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia.
Deploy the Staker and Receiver contracts on *Ethereum Sepolia*. Configure the Receiver contract to receive CCIP messages from the Sender contract:
1. Deploy the Staker contract:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. [Open the Staker contract in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/usdc/Staker.sol\&autoCompile=true).
3. Compile your contract.
4. In Remix IDE, under *Deploy & Run Transactions*, make sure the environment is still *Injected Provider - MetaMask*.
5. Fill in the usdc contract address. The usdc contract address can be found on the [CCIP Directory](/ccip/directory). For *Ethereum Sepolia*, the usdc contract address is:
- 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238.
6. Click the **transact** button. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
2. Deploy the Receiver contract:
1. [Open the Receiver contract in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/usdc/Receiver.sol\&autoCompile=true).
2. Compile your contract.
3. In Remix IDE, under *Deploy & Run Transactions*, make sure the environment is still *Injected Provider - MetaMask* and that you are still connected to *Ethereum Sepolia*.
4. Fill in your blockchain's router, LINK, and Staker contract addresses. The router and usdc addresses can be found on the [CCIP Directory](/ccip/directory) and the Staker contract address from the previous step. For *Ethereum Sepolia*, the addresses are:
- Router address: 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
- USDC contract address: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238
- Staker address: Copied from the previous step
3. Configure the Receiver contract to receive CCIP messages from the Sender contract:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your Receiver contract deployed on *Ethereum Sepolia*.
2. Fill in the arguments of the ***setSenderForSourceChain*** function:
| Argument | Value and Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| _sourceChainSelector | The chain selector of *Avalanche Fuji*. You can find it on the [CCIP Directory](/ccip/directory). |
| _sender | Your sender contract address at *Avalanche Fuji*. The sender contract address. |
3. Click on `transact` and confirm the transaction on MetaMask.
4. Configure the Sender contract on *Avalanche Fuji*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your Sender contract deployed on *Avalanche Fuji*.
3. Fill in the arguments of the ***setReceiverForDestinationChain*** function:
| Argument | Value and Description |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| _destinationChainSelector | The chain selector of *Ethereum Sepolia*. You can find it on the [CCIP Directory](/ccip/directory). |
| _receiver | Your receiver contract address at *Ethereum Sepolia*. The receiver contract address. |
4. Fill in the arguments of the ***setGasLimitForDestinationChain***: function:
| Argument | Value and Description |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| _destinationChainSelector | The chain selector of *Ethereum Sepolia*. You can find it on the [CCIP Directory](/ccip/directory). |
| _gasLimit | The gas limit for the execution of the CCIP message on the destination chain. |
At this point:
- You have one *sender* contract on *Avalanche Fuji*, one *staker* contract and one *receiver* contract on *Ethereum Sepolia*.
- You enabled the sender contract to send messages to the receiver contract on *Ethereum Sepolia*.
- You set the gas limit for the execution of the CCIP message on *Ethereum Sepolia*.
- You enabled the receiver contract to receive messages from the sender contract on *Avalanche Fuji*.
- You funded the sender contract with USDC and LINK tokens on *Avalanche Fuji*.
#### Transfer and Receive tokens and data and pay in LINK
You will transfer *1 USDC* and arbitrary data, which contains the encoded stake function name and parameters for calling Staker's stake function on the destination chain. The parameters contain the amount of staked tokens and the beneficiary address. The CCIP fees for using CCIP will be paid in LINK.
1. Transfer tokens and data from *Avalanche Fuji*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Avalanche Fuji*.
3. Fill in the arguments of the ***sendMessagePayLINK*** function:
| Argument | Value and Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| _destinationChainSelector | 16015286601757825753 CCIP Chain identifier of the destination blockchain (*Ethereum Sepolia* in this example). You can find each chain selector on the [CCIP Directory](/ccip/directory). |
| _beneficiary | The beneficiary of the Staker tokens on *Ethereum Sepolia*. You can set your own EOA (Externally Owned Account) so you can redeem the Staker tokens in exchange for USDC tokens. |
| _amount | 1000000 The token amount (*1 USDC*). |
4. Click on `transact` and confirm the transaction on MetaMask.
5. After the transaction is successful, record the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0x5e066ec7e94496e1547c368df4199b9f0c4f8f6c82012b2d974aa258a5c9e9fe) of a transaction on *Avalanche Fuji*.
1. Open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the transaction hash.
2. The CCIP transaction is completed once the status is marked as "Success". In this example, the CCIP message ID is *0xcb0fad9eec6664ad959f145cc4eb023924faded08baefc29952205ee37da7f13*.
3. Check the balance of the beneficiary on the destination chain:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your Staker contract deployed on *Ethereum Sepolia*.
3. Call the `balanceOf` function with the beneficiary address.
4. Notice that the balance of the beneficiary is 1,000,000 Staker tokens. The Staker contract has the same number of decimals as the USDC token, which is 6. This means the beneficiary has 1 USDC staked and can redeem it by providing the same amount of Staker tokens.
4. Redeem the staked tokens:
1. Open MetaMask and make sure the network is *Ethereum Sepolia*.
2. Make sure you are connected with the beneficiary account.
3. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your Staker contract deployed on *Ethereum Sepolia*.
4. Call the `redeem` function with the amount of Staker tokens to redeem. In this example, the beneficiary will redeem 1,000,000 Staker tokens. When confirming, MetaMask will confirm that you will transfer the Staker tokens in exchange for USDC tokens.
5. Confirm the transaction on MetaMask. After the transaction is successful, the beneficiary will receive 1 USDC tokens.
## Explanation
The smart contracts featured in this tutorial are designed to interact with CCIP to send and receive USDC tokens and data across different blockchains. The contract code contains supporting comments clarifying the functions, events, and underlying logic. We will explain the Sender, Staker, and Receiver contracts further.
### Sender Contract
The Sender contract is responsible for initiating the transfer of USDC tokens and data. Here's how it works:
1. Initializing the contract:
- When deploying the contract, you define the router address, LINK contract address, and USDC contract address.
- These addresses are essential for interacting with the CCIP router and handling token transfers.
2. `sendMessagePayLINK` function:
- This function sends USDC tokens, the encoded function signature of the `stake` function, and arguments (beneficiary address and amount) to the Receiver contract on the destination chain.
- Constructs a CCIP message using the `EVM2AnyMessage` struct.
- Computes the necessary fees using the router's `getFee` function.
- Ensures the contract has enough LINK to cover the fees and approves the router transfer of LINK on its behalf.
- Dispatches the CCIP message to the destination chain by executing the router's `ccipSend` function.
- Emits a `MessageSent` event.
### Staker Contract
The Staker contract manages the staking and redemption of USDC tokens. Here's how it works:
1. Initializing the contract:
- When deploying the contract, you define the USDC token address.
- This address is essential for interacting with the USDC token contract.
2. `stake` function:
- Allows staking of USDC tokens on behalf of a beneficiary.
- Transfers USDC from the caller (`msg.sender`) to the contract, then mints an equivalent amount of staking tokens to the beneficiary.
3. `redeem` function:
- Allows beneficiaries to redeem their staked tokens for USDC.
- Burns the staked tokens and transfers the equivalent USDC to the beneficiary.
### Receiver Contract
The Receiver contract handles incoming cross-chain messages, processes them, and interacts with the Staker contract to stake USDC on behalf of the beneficiary. Here's how it works:
1. Initializing the Contract:
- When deploying the contract, you define the router address, USDC token address, and staker contract address.
- These addresses are essential for interacting with the CCIP router, USDC token, and Staker contracts.
2. `ccipReceive` function:
- The entry point for the CCIP router to deliver messages to the contract.
- Validates the sender and processes the message, ensuring it comes from the correct sender contract on the source chain.
3. Processing Message:
- Calls the `processMessage` function, which is external to leverage Solidity's try/catch error handling mechanism.
- Inside `processMessage`, it calls the `_ccipReceive` function for further message processing.
4. `_ccipReceive` function:
- Checks if the received token is USDC. If not, it reverts.
- Makes a low-level call to the `stake` function of the Staker contract using the encoded function signature and arguments from the received data.
- Emits a `MessageReceived` event upon successful processing.
5. Error Handling:
- If an error occurs during processing, the catch block within ccipReceive is executed.
- The `messageId` of the failed message is added to `s_failedMessages`, and the message content is stored in `s_messageContents`.
- A `MessageFailed` event is emitted, allowing for later identification and reprocessing of failed messages.
6. `retryFailedMessage` function:
- Allows the contract owner to retry a failed message and recover the associated tokens.
- Updates the error code for the message to `RESOLVED` to prevent multiple retries.
- Transfers the locked tokens associated with the failed message to the specified beneficiary as an escape hatch.
7. `getFailedMessages` function:
- Retrieves a paginated list of failed messages for inspection.
---
# Send Arbitrary Data
Source: https://docs.chain.link/ccip/tutorials/evm/send-arbitrary-data
Last Updated: 2025-05-19
In this tutorial, you will use Chainlink CCIP to send data between smart contracts on different blockchains. First, you will pay for the CCIP fees on the source blockchain using LINK. Then, you will use the same contract to pay CCIP fees in native gas tokens. For example, you would use ETH on Ethereum or AVAX on Avalanche.
## Before you begin
- You should understand how to write, compile, deploy, and fund a smart contract. If you need to brush up on the basics, read this [tutorial](/quickstarts/deploy-your-first-contract), which will guide you through using the [Solidity programming language](https://soliditylang.org/), interacting with the [MetaMask wallet](https://metamask.io) and working within the [Remix Development Environment](https://remix.ethereum.org/).
- Your account must have some AVAX tokens on *Avalanche Fuji* and ETH tokens on *Ethereum Sepolia*.
- Learn how to [Acquire testnet LINK](/resources/acquire-link) and [Fund your contract with LINK](/resources/fund-your-contract).
## Tutorial
In this tutorial, you will send a *string* text between smart contracts on *Avalanche Fuji* and *Ethereum Sepolia* using CCIP. First, you will pay [CCIP fees in LINK](#send-data-and-pay-in-link), then you will pay [CCIP fees in native gas](#send-data-and-pay-in-native).
### Deploy your contracts
To use this contract:
1. [Open the contract in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Messenger.sol).
2. Compile your contract.
3. Deploy your sender contract on *Avalanche Fuji* and enable sending messages to *Ethereum Sepolia*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, click on *Deploy & Run Transactions* and select *Injected Provider - MetaMask* from the environment list. Remix will then interact with your MetaMask wallet to communicate with *Avalanche Fuji*.
3. Fill in the router address and the link address for your network. You can find the router address on the [CCIP Directory](/ccip/directory) and the LINK token address on the [LINK Token contracts page](/resources/link-token-contracts?parent=ccip). For *Avalanche Fuji*:
- The router address is 0xF694E193200268f9a4868e4Aa017A0118C9a8177,
- The LINK contract address is 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846.
4. Click on *transact*. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
5. Enable your contract to send CCIP messages to *Ethereum Sepolia*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Avalanche Fuji*.
2. Call the `allowlistDestinationChain` with 16015286601757825753 as the destination chain selector, and true as allowed. Each chain selector is found on the [CCIP Directory](/ccip/directory).
4. Deploy your receiver contract on *Ethereum Sepolia* and enable receiving messages from your sender contract:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, make sure the environment is still *Injected Provider - MetaMask*.
3. Fill in the router address and the LINK address for your network. You can find the router address on the [CCIP Directory](/ccip/directory) and the LINK contract address on the [LINK token contracts page](/resources/link-token-contracts). For *Ethereum Sepolia*:
- The router address is 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59,
- The LINK contract address is 0x779877A7B0D9E8603169DdbD7836e478b4624789.
4. Click on *transact*. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
5. Enable your contract to receive CCIP messages from *Avalanche Fuji*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Ethereum Sepolia*.
2. Call the `allowlistSourceChain` with 14767482510784806043 as the source chain selector, and true as allowed. Each chain selector is found on the [CCIP Directory](/ccip/directory).
6. Enable your contract to receive CCIP messages from the contract that you deployed on *Avalanche Fuji*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Ethereum Sepolia*.
2. Call the `allowlistSender` with the contract address of the contract that you deployed on *Avalanche Fuji*, and true as allowed.
At this point, you have one *sender* contract on *Avalanche Fuji* and one *receiver* contract on *Ethereum Sepolia*. As security measures, you enabled the sender contract to send CCIP messages to *Ethereum Sepolia* and the receiver contract to receive CCIP messages from the sender and *Avalanche Fuji*. **Note**: Another security measure enforces that only the router can call the `_ccipReceive` function. Read the [explanation](#explanation) section for more details.
### Send data and pay in LINK
You will use CCIP to send a text. The CCIP fees for using CCIP will be paid in LINK. Read this [explanation](#sending-data-and-pay-in-link) for a detailed description of the code example.
1. Open MetaMask and connect to *Avalanche Fuji*. Fund your contract with LINK tokens. You can transfer 70 *LINK* to your contract. In this example, LINK is used to pay the CCIP fees.
**Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this example, you can get additional testnet LINK
from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia.
2. Send "Hello World!" from *Avalanche Fuji*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Avalanche Fuji*.
3. Fill in the arguments of the ***sendMessagePayLINK*** function:
| Argument | Description | Value (*Ethereum Sepolia*) |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| _destinationChainSelector | CCIP Chain identifier of the target blockchain. You can find each network's chain selector on the [CCIP Directory](/ccip/directory) | |
| _receiver | The destination smart contract address | Your deployed receiver contract address |
| _text | any `string` | |
4. Click on `transact` and confirm the transaction on MetaMask.
5. Once the transaction is successful, note the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0x233d2d882e6cfe736c982d58a33021d2f4f6b96e0cfd2c7a874cf2eb63790aa1) of a transaction on *Avalanche Fuji*.
1. Open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the transaction hash.
2. The CCIP transaction is completed once the status is marked as "Success". **Note**: In this example, the CCIP message ID is *0x28a804fa891bde8fb4f6617931187e1033a128c014aa76465911613588bc306f*.
3. Check the receiver contract on the destination chain:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Ethereum Sepolia*.
3. Call the `getLastReceivedMessageDetails`.
4. Notice the received text is the one you sent, "Hello World!" and the message ID is the one you expect *0x28a804fa891bde8fb4f6617931187e1033a128c014aa76465911613588bc306f*.
**Note**: These example contracts are designed to work bi-directionally. As an exercise, you can use them to send data from *Avalanche Fuji* to *Ethereum Sepolia* and from *Ethereum Sepolia* back to *Avalanche Fuji*.
### Send data and pay in native
You will use CCIP to send a text. The CCIP fees for using CCIP will be paid in native gas. Read this [explanation](#sending-data-and-pay-in-native) for a detailed description of the code example.
1. Open MetaMask and connect to *Avalanche Fuji*. Fund your contract with AVAX. You can transfer 1 *AVAX* to your contract. In this example, AVAX is used to pay the CCIP fees.
2. Send "Hello World!" from *Avalanche Fuji*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Avalanche Fuji*.
3. Fill in the arguments of the ***sendMessagePayNative*** function:
| Argument | Description | Value (*Ethereum Sepolia*) |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| _destinationChainSelector | CCIP Chain identifier of the target blockchain. You can find each network's chain selector on the [CCIP Directory](/ccip/directory) | |
| _receiver | The destination smart contract address | Your deployed receiver contract address |
| _text | any `string` | |
4. Click on `transact` and confirm the transaction on MetaMask.
5. Once the transaction is successful, note the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0x5cb5ea9b1631f62148105d67b780b56fce66db398667276ea498104b7896ffee) of a transaction on *Avalanche Fuji*.
1. Open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the transaction hash.
2. The CCIP transaction is completed once the status is marked as "Success". In this example, the CCIP message ID is *0xb8cb414128f440e115dcd5d6ead50e14d250f9a47577c38af4f70deb14191457*. Note that CCIP fees are denominated in LINK. Even if CCIP fees are paid using native gas tokens, node operators will be paid in LINK.
3. Check the receiver contract on the destination chain:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Ethereum Sepolia*.
3. Call the `getLastReceivedMessageDetails`.
4. Notice the received text is the one you sent, "Hello World!" and the message ID is the one you expect *0xb8cb414128f440e115dcd5d6ead50e14d250f9a47577c38af4f70deb14191457*.
**Note**: These example contracts are designed to work bi-directionally. As an exercise, you can use them to send data from *Avalanche Fuji* to *Ethereum Sepolia* and from *Ethereum Sepolia* back to *Avalanche Fuji*.
## Explanation
The smart contract featured in this tutorial is designed to interact with CCIP to send and receive messages. The contract code contains supporting comments clarifying the functions, events, and underlying logic. Here we will further explain initializing the contract and sending and receiving data.
### Initializing of the contract
When deploying the contract, we define the router address and LINK contract address of the blockchain we deploy the contract on.
Defining the router address is useful for the following:
- Sender part:
- Calls the router's `getFee` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee) to estimate the CCIP fees.
- Calls the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend) to send CCIP messages.
- Receiver part:
- The contract inherits from [CCIPReceiver](/ccip/api-reference/evm/v1.6.2/ccip-receiver), which serves as a base contract for receiver contracts. This contract requires that child contracts implement the `_ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#_ccipreceive). `_ccipReceive` is called by the `ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive), which ensures that only the router can deliver CCIP messages to the receiver contract.
### Sending data and pay in LINK
The `sendMessagePayLINK` function undertakes five primary operations:
1. Call the `_buildCCIPMessage` private function to construct a CCIP-compatible message using the `EVM2AnyMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage):
- The `_receiver` address is encoded in bytes to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `data` is encoded from a `string` to `bytes` using [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `tokenAmounts` is an empty `EVMTokenAmount` [struct](/ccip/api-reference/evm/v1.6.2/client#evmtokenamount) array as no tokens are transferred.
- The `extraArgs` specifies the `gasLimit` for relaying the message to the recipient contract on the destination blockchain. In this example, the `gasLimit` is set to `200000`.
- The `_feeTokenAddress` designates the token address used for CCIP fees. Here, `address(linkToken)` signifies payment in LINK.
{" "}
1. Computes the fees by invoking the router's `getFee` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee).
2. Ensures your contract balance in LINK is enough to cover the fees.
3. Grants the router contract permission to deduct the fees from the contract's LINK balance.
4. Dispatches the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend).
**Note**: As a security measure, the `sendMessagePayLINK` function is protected by the `onlyAllowlistedDestinationChain`, ensuring the contract owner has allowlisted a destination chain.
### Sending data and pay in native
The `sendMessagePayNative` function undertakes four primary operations:
1. Call the `_buildCCIPMessage` private function to construct a CCIP-compatible message using the `EVM2AnyMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage):
- The `_receiver` address is encoded in bytes to accommodate non-EVM destination blockchains with distinct address formats. The encoding is achieved through [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `data` is encoded from a `string` to `bytes` using [abi.encode](https://docs.soliditylang.org/en/develop/abi-spec.html).
- The `tokenAmounts` is an empty `EVMTokenAmount` [struct](/ccip/api-reference/evm/v1.6.2/client#evmtokenamount) array as no tokens are transferred.
- The `extraArgs` specifies the `gasLimit` for relaying the message to the recipient contract on the destination blockchain. In this example, the `gasLimit` is set to `200000`.
- The `_feeTokenAddress` designates the token address used for CCIP fees. Here, `address(0)` signifies payment in native gas tokens (ETH).
{" "}
1. Computes the fees by invoking the router's `getFee` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee).
2. Ensures your contract balance in native gas is enough to cover the fees.
3. Dispatches the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend). **Note**: `msg.value` is set because you pay in native gas.
**Note**: As a security measure, the `sendMessagePayNative` function is protected by the `onlyAllowlistedDestinationChain`, ensuring the contract owner has allowlisted a destination chain.
### Receiving data
On the destination blockchain, the router invokes the `ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive) which expects an `Any2EVMMessage` [struct](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage) that contains:
- The CCIP `messageId`.
- The `sourceChainSelector`.
- The `sender` address in bytes format. Given that the sender is known to be a contract deployed on an EVM-compatible blockchain, the address is decoded from bytes to an Ethereum address using the [ABI specifications](https://docs.soliditylang.org/en/v0.8.20/abi-spec.html).
- The `data`, which is also in bytes format. Given a `string` is expected, the data is decoded from bytes to a string using the [ABI specifications](https://docs.soliditylang.org/en/v0.8.20/abi-spec.html).
This example applies three important security measures:
- `_ccipReceive` is called by the `ccipReceive` [function](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive), which ensures that only the router can deliver CCIP messages to the receiver contract. See the `onlyRouter` [modifier](/ccip/api-reference/evm/v1.6.2/ccip-receiver#onlyrouter) for more information.
- The modifier `onlyAllowlisted` ensures that only a call from an allowlisted source chain and sender is accepted.
---
# Send Arbitrary Data and Receive Transfer Confirmation: A -> B -> A
Source: https://docs.chain.link/ccip/tutorials/evm/send-arbitrary-data-receipt-acknowledgment
Last Updated: 2025-05-19
This tutorial will teach you how to use Chainlink CCIP to send arbitrary data between smart contracts on different blockchains and how to track the status of each sent message in the sender contract on the source chain. Tracking the status of sent messages allows your smart contracts to execute actions after the receiver acknowledges it received the message. In this example, the sender contract emits an event after it receives acknowledgment from the receiver.
**Note**: For simplicity, this tutorial demonstrates this pattern for sending arbitrary data. However, you are not limited to this application. You can apply the same pattern to programmable token transfers.
## Before you begin
- This tutorial assumes you have completed the [Send Arbitrary Data](/ccip/tutorials/evm/send-arbitrary-data) tutorial.
- Your account must have some AVAX tokens on *Avalanche Fuji* and ETH tokens on *Ethereum Sepolia*.
- Learn how to [Acquire testnet LINK](/resources/acquire-link) and [Fund your contract with LINK](/resources/fund-your-contract).
## Tutorial
In this tutorial, you will deploy a *message tracker* contract on the source blockchain (Avalanche Fuji) and an *acknowledger* on the destination blockchain (Ethereum Sepolia). Throughout the tutorial, you will pay for CCIP fees using LINK tokens. Here is a step-by-step breakdown:
1. **Sending and building a CCIP message:** Initiate and send a message from the *message tracker* contract on Avalanche Fuji to the *acknowledger* contract on Ethereum Sepolia. The *message tracker* contract constructs a CCIP message that encapsulates a text string and establishes a tracking status for this message before sending it off.
2. **Receiving and acknowledging the message:** After the *acknowledger* contract receives the text on Ethereum Sepolia, it sends back a CCIP message to the *message tracker* contract as an acknowledgment of receipt.
3. **Updating tracking status:** After the *message tracker* receives the acknowledgment, the contract updates the tracking status of the initial CCIP message and emits an event to signal completion.
### Deploy the *message tracker* (sender) contract
Deploy the `MessageTracker.sol` contract on *Avalanche Fuji* and enable it to send and receive CCIP messages to and from *Ethereum Sepolia*. You must also enable your contract to receive CCIP messages from the *acknowledger* contract.
1. [Open the MessageTracker.sol contract](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/MessageTracker.sol) in Remix.
Note: The contract code is also available in the [Examine the code](/ccip/tutorials/evm/send-arbitrary-data-receipt-acknowledgment#messagetrackersol) section.
2. Compile the contract.
3. Deploy the contract on *Avalanche Fuji*:
1. Open MetaMask and select the *Avalanche Fuji* network.
2. On the **Deploy & Run Transactions** tab in Remix, select *Injected Provider - MetaMask* in the **Environment** list. Remix will use the MetaMask wallet to communicate with *Avalanche Fuji*.
3. Under the **Deploy** section, fill in the router address and the LINK token contract address for your specific blockchain. You can find both of these addresses on the [CCIP Directory](/ccip/directory). The LINK token contract address is also listed on the [LINK Token Contracts](/resources/link-token-contracts) page. For *Avalanche Fuji*:
- The router address is 0xF694E193200268f9a4868e4Aa017A0118C9a8177
- The LINK token address is 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846
4. Click **transact** to deploy the contract. MetaMask prompts you to confirm the transaction. Check the transaction details to make sure you are deploying the contract on *Avalanche Fuji*.
5. After you confirm the transaction, the contract address appears in the **Deployed Contracts** list. Copy your contract address.
6. Open MetaMask and send 70 LINK to the contract address you copied. Your contract will pay CCIP fees in LINK.
**Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this example, you can get additional testnet LINK
from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia.
4. Allow the *Ethereum Sepolia* chain selector for both destination and source chains.
1. On the **Deploy & Run Transactions** tab in Remix, expand the *message tracker* contract in the **Deployed Contracts** section.
2. Call the `allowlistDestinationChain` function with 16015286601757825753 as the destination chain selector for *Ethereum Sepolia* and true as allowed.
3. Call the `allowlistSourceChain` function with 16015286601757825753 as the source chain selector for *Ethereum Sepolia* and true as allowed.
You can find each network's chain selector on the [CCIP Directory](/ccip/directory).
### Deploy the acknowledger (receiver) contract
Deploy the `Acknowledger.sol` contract on *Ethereum Sepolia* and enable it to send and receive CCIP messages to and from *Avalanche Fuji*. You must also enable your contract to receive CCIP messages from the *message tracker* contract.
1. [Open the Acknowledger.sol](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Acknowledger.sol) contract in Remix.
Note: The contract code is also available in the [Examine the code](/ccip/tutorials/evm/send-arbitrary-data-receipt-acknowledgment#acknowledgersol) section.
2. Compile the contract.
3. Deploy the contract on *Ethereum Sepolia*:
1. Open MetaMask and select the *Ethereum Sepolia* network.
2. On the **Deploy & Run Transactions** tab in Remix, make sure the **Environment** is still set to *Injected Provider - MetaMask*.
3. Under the **Deploy** section, fill in the router address and the LINK token contract address for your specific blockchain. You can find both of these addresses on the [CCIP Directory](/ccip/directory). The LINK token contract address is also listed on the [LINK Token Contracts](/resources/link-token-contracts) page. For *Ethereum Sepolia*:
- The Router address is 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59.
- The LINK token address is 0x779877A7B0D9E8603169DdbD7836e478b4624789.
4. Click **transact** to deploy the contract. MetaMask prompts you to confirm the transaction. Check the transaction details to make sure you are deploying the contract to *Ethereum Sepolia*.
5. After you confirm the transaction, the contract address appears in the **Deployed Contracts** list. Copy this contract address.
6. Open MetaMask and send 70 LINK to the contract address that you copied. Your contract will pay CCIP fees in LINK.
**Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this example, you can get additional testnet LINK
from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia.
4. Allow the *Avalanche Fuji* chain selector for both destination and source chains. You must also enable your acknowledger contract to receive CCIP messages from the message tracker you deployed on *Avalanche Fuji*.
1. On the **Deploy & Run Transactions** tab in Remix, expand the *acknowledger* contract in the **Deployed Contracts** section. Expand the `allowlistDestinationChain`, `allowlistSender`, and `allowlistSourceChain` functions and fill in the following arguments:
| Function | Description | Value (*Avalanche Fuji*) |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| allowlistDestinationChain | CCIP Chain identifier of the target blockchain. You can find each network's chain selector on the [CCIP Directory](/ccip/directory) | 14767482510784806043, true |
| allowlistSender | The address of the message tracker contract deployed on *Avalanche Fuji* | Your deployed contract address, true |
| allowlistSourceChain | CCIP Chain identifier of the source blockchain. You can find each network's chain selector on the [CCIP Directory](/ccip/directory) | 14767482510784806043, true |
2. Open MetaMask and select the *Ethereum Sepolia* network.
3. For each function you expanded and filled in the arguments for, click the **transact** button to call the function. MetaMask prompts you to confirm the transaction. Wait for each transaction to succeed before calling the following function.
5. Finally, enable your *message tracker* contract to receive CCIP messages from the *acknowledger* contract you deployed on *Ethereum Sepolia*.
1. On the **Deploy & Run Transactions** tab in Remix, expand the *message tracker* contract in the **Deployed Contracts** section. Expand the `allowlistSender` function and fill in your *acknowledger* contract address and true as allowed.
2. Open MetaMask and select the *Avalanche Fuji* network.
3. Click **transact** to call the function. MetaMask prompts you to confirm the transaction.
At this point, you have one *message tracker* (sender) contract on *Avalanche Fuji* and one *acknowledger* (receiver) contract on *Ethereum Sepolia*. You sent `70` LINK to the *message tracker* contract and `70` LINK to the *acknowledger* contract to pay the CCIP fees.
**Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia.
### Send data and track the message status
#### Initial message
1. Send a `Hello World!` string from your *message tracker* contract on *Avalanche Fuji* to your *acknowledger* contract deployed on *Ethereum Sepolia*. You will track the status of this message during this tutorial.
1. Open MetaMask and select the *Avalanche Fuji* network.
2. On the **Deploy & Run Transactions** tab in Remix, expand the *message tracker* contract in the **Deployed Contracts** section.
3. Expand the **sendMessagePayLINK** function and fill in the following arguments:
| Argument | Description | Value (*Ethereum Sepolia*) |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| destinationChainSelector | CCIP Chain identifier of the target blockchain. You can find each network's chain selector on the [CCIP Directory](/ccip/directory) | 16015286601757825753 |
| receiver | The destination smart contract address | Your deployed *acknowledger* contract address |
| text | Any `string` | Hello World! |
4. Click **transact** to call the function. MetaMask prompts you to confirm the transaction.
1. Upon transaction success, expand the last transaction in the Remix log and copy the transaction hash. In this example, it is `0x1f88abc33a4ab426a5466e01d9e5fe8a2b96d6a6e5cedb643a674489c74126b4`.
2. Open the [CCIP Explorer](https://ccip.chain.link/) and use the transaction hash that you copied to search for your cross-chain transaction.
After the transaction is finalized on the source chain, it will take a few minutes for CCIP to deliver the data to *Ethereum Sepolia* and call the `ccipReceive` function on your *acknowledger* contract.
3. Copy the message ID from the CCIP Explorer transaction details. You will use this message ID to track your message status on the *message tracker* contract. In this example, it is `0xdd8be2f5f5d5cf3b8640c62924025b311ae83c6144f0f2ed5c24637436d6aab8`.
4. On the **Deploy & Run Transactions** tab in Remix, expand your *message tracker* contract in the **Deployed Contracts** section.
5. Paste the message ID you copied from the CCIP explorer as the argument in the **messagesInfo** getter function. Click **messagesInfo** to read the message status.
Note the returned `status 1`. This value indicates that the *message tracker* contract has updated your message status to the `Sent` status as defined by the `MessageStatus` `enum` in the *message tracker* contract.
```solidity
// Enum is used to track the status of messages sent via CCIP.
// `NotSent` indicates a message has not yet been sent.
// `Sent` indicates that a message has been sent to the Acknowledger contract but not yet acknowledged.
// `ProcessedOnDestination` indicates that the Acknowledger contract has processed the message and that
// the Message Tracker contract has received the acknowledgment from the Acknowledger contract.
enum MessageStatus {
NotSent, // 0
Sent, // 1
ProcessedOnDestination // 2
}
```
6. When the transaction is marked with a "Success" status on the [CCIP Explorer](https://ccip.chain.link/), the CCIP transaction and the destination transaction are complete. The *acknowledger* contract has received the message from the *message tracker* contract.
#### Acknowledgment message
The *acknowledger* contract processes the message, sends an acknowledgment message containing the initial message ID back to the *message tracker* contract, and emits an `AcknowledgmentSent` event. Read this [explanation](/ccip/tutorials/evm/send-arbitrary-data-receipt-acknowledgment#acknowledger-contract) for further description.
```solidity
// Emitted when an acknowledgment message is successfully sent back to the sender contract.
// This event signifies that the Acknowledger contract has recognized the receipt of an initial message
// and has informed the original sender contract by sending an acknowledgment message,
// including the original message ID.
event AcknowledgmentSent(
bytes32 indexed messageId, // The unique ID of the CCIP message.
uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
address indexed receiver, // The address of the receiver on the destination chain.
bytes32 data, // The data being sent back, usually containing the message ID of the original message to acknowledge its receipt.
address feeToken, // The token address used to pay CCIP fees for sending the acknowledgment.
uint256 fees // The fees paid for sending the acknowledgment message via CCIP.
);
```
1. Copy your *acknowledger* contract address from Remix. Open the [*Ethereum Sepolia* explorer](https://sepolia.etherscan.io/) and search for your deployed *acknowledger* contract. Click the **Events** tab to see the events log.
The first indexed topic (`topic1`) in the `AcknowledgmentSent` event is the acknowledgment message ID sent to the *message tracker* contract on *Avalanche Fuji*. In this example, the message ID is `0xd4d4a5d0db05dc714f8150c1af654ed34eb8c9f7547401fa9bf072a815f56ac1`.
2. Copy your own message ID from the indexed `topic1` and search for it in the [CCIP explorer](https://ccip.chain.link/).
When the transaction is marked with a "Success" status on the CCIP explorer, the CCIP transaction and the destination transaction are complete. The *message tracker* contract has received the message from the *acknowledger* contract.
#### Final status check
When the *message tracker* receives the acknowledgment message, the `ccipReceive` function updates the initial message status to `2`, which corresponds to the `ProcessedOnDestination` status as defined by the `MessageStatus` `enum`. The function emits a `MessageProcessedOnDestination` event.
1. Open MetaMask and select the *Avalanche Fuji* network.
2. On the **Deploy & Run Transactions** tab in Remix, expand your *message tracker* contract in the **Deployed Contracts** section.
3. Copy the **initial message ID** from the CCIP explorer (transaction from *Avalanche Fuji* to *Ethereum Sepolia*) and paste it as the argument in the **messagesInfo** getter function. Click **messagesInfo** to read the message status. It returns `status 2` and the acknowledgment message ID that confirms this status.
4. Copy your *message tracker* contract address from Remix. Open the [*Avalanche Fuji* explorer](https://testnet.snowtrace.io/) and search for your deployed *message tracker* contract. Then, click on the **Events** tab.
The `MessageProcessedOnDestination` event is emitted with the acknowledged message ID `0xdd8be2f5f5d5cf3b8640c62924025b311ae83c6144f0f2ed5c24637436d6aab8` as indexed `topic2`.
```solidity
// Event emitted when the sender contract receives an acknowledgment
// that the receiver contract has successfully received and processed the message.
event MessageProcessedOnDestination(
bytes32 indexed messageId, // The unique ID of the CCIP acknowledgment message.
bytes32 indexed acknowledgedMsgId, // The unique ID of the message acknowledged by the receiver.
uint64 indexed sourceChainSelector, // The chain selector of the source chain.
address sender // The address of the sender from the source chain.
);
```
## Explanation
The smart contracts featured in this tutorial are designed to interact with CCIP to send and receive messages with an acknowledgment of receipt mechanism. The contract code across both contracts contains supporting comments clarifying the functions, events, and underlying logic.
Refer to the [Send Arbitrary Data](/ccip/tutorials/evm/send-arbitrary-data#explanation) tutorial for more explanation about [initializing the contracts](/ccip/tutorials/evm/send-arbitrary-data#initializing-of-the-contract), [sending data, paying in LINK](/ccip/tutorials/evm/send-arbitrary-data#sending-data-and-pay-in-link), and [receiving data](/ccip/tutorials/evm/send-arbitrary-data#receiving-data).
Here, we will further explain the acknowledgment of receipt mechanism.
### Message acknowledgment of receipt mechanism
This mechanism ensures that a message sent by the *message tracker* (sender) contract is received and acknowledged by the *acknowledger* (receiver) contract. The message status is tracked and stored in the *message tracker* contract.
```solidity
// Enum is used to track the status of messages sent via CCIP.
// `NotSent` indicates a message has not yet been sent.
// `Sent` indicates that a message has been sent to the Acknowledger contract but not yet acknowledged.
// `ProcessedOnDestination` indicates that the Acknowledger contract has processed the message and that
// the Message Tracker contract has received the acknowledgment from the Acknowledger contract.
enum MessageStatus {
NotSent, // 0
Sent, // 1
ProcessedOnDestination // 2
}
// Struct to store the status and acknowledger message ID of a message.
struct MessageInfo {
MessageStatus status;
bytes32 acknowledgerMessageId;
}
// Mapping to keep track of message IDs to their info (status & acknowledger message ID).
mapping(bytes32 => MessageInfo) public messagesInfo;
```
#### *Message tracker* contract
The *message tracker* contract acts as the sender, initiating cross-chain communication. It performs the following operations:
- **Message sending**: Constructs and sends messages to the *acknowledger* contract on another blockchain, using [`sendMessagePayLINK`](/ccip/tutorials/evm/send-arbitrary-data#sending-data-and-pay-in-link) function. On top of its [five primary operations](/ccip/tutorials/evm/send-arbitrary-data#sending-data-and-pay-in-link), the [`sendMessagePayLINK`](/ccip/tutorials/evm/send-arbitrary-data#sending-data-and-pay-in-link) function also updates the message status upon sending.
- **Status tracking**:
- Upon sending a message, the *message tracker* updates its internal state to mark the message as `Sent` (status `1`). This status is pivotal for tracking the message lifecycle and awaiting acknowledgment.
```solidity
// Update the message status to `Sent`
messagesInfo[messageId].status = MessageStatus.Sent;
```
- Upon receiving an acknowledgment message from the *acknowledger* contract, the *message tracker* contract updates the message status from `Sent` (status `1`) to `ProcessedOnDestination` (status `2`). This update indicates that the cross-chain communication cycle is complete, and the receiver successfully received and acknowledged the message.
```solidity
// Update the message status to `ProcessedOnDestination`
messagesInfo[messageId].status = MessageStatus.ProcessedOnDestination;
```
#### *Acknowledger* contract
The *acknowledger* contract receives the message, sends back an acknowledgment message, and emits an event. It performs the following operations:
- **Message receipt**: Upon receiving a message via CCIP, the `ccipReceive` function decodes it and calls the `acknowledgePayLINK` function nested within the [`ccipReceive`](/ccip/tutorials/evm/send-arbitrary-data#receiving-data) function.
- **Acknowledgment sending**: The `acknowledgePayLINK` function acts as a custom [`sendMessagePayLINK`](https://docs.chain.link/ccip/tutorials/evm/send-arbitrary-data#sending-data-and-pay-in-link) function nested within the `ccipReceive` function. It sends
an acknowledgment (a CCIP message) to the *message tracker* contract upon the initial message receipt. The data transferred in this acknowledgment message is the initial message ID. It then emits an `AcknowledgmentSent` event.
### Security and integrity
Both contracts use allowlists to process only messages from and to allowed sources.
- **Allowlisting chains and senders**:
- The `sendMessagePayLINK` function is protected by the `onlyAllowlistedDestinationChain` modifier, ensuring the contract owner has allowlisted a destination chain.
- The `ccipReceive` function is protected by the `onlyAllowlisted` modifier, ensuring the contract owner has allowlisted a source chain and a sender.
- **Ensuring the initial message authenticity**: The *message tracker* contract first checks that the message awaiting acknowledgment was sent from the contract itself and is currently marked as `Sent`. Once confirmed, the message status is updated to `ProcessedOnDestination`.
## Examine the code
### MessageTracker.sol
### Acknowledger.sol
## Final note
In this example, the *message tracker* contract emits an event when it receives the acknowledgment message confirming the initial message reception and processing on the counterpart chain. However, you could think of any other logic to execute when the *message tracker* receives the acknowledgment. This tutorial demonstrates the pattern for sending arbitrary data, but you can apply the same pattern to programmable token transfers.
---
# Manual Execution
Source: https://docs.chain.link/ccip/tutorials/evm/manual-execution
Last Updated: 2025-05-19
This tutorial is similar to the [programmable token transfers example](/ccip/tutorials/evm/programmable-token-transfers). It demonstrates the use of Chainlink CCIP for transferring tokens and arbitrary data between smart contracts on different blockchains. A distinctive feature of this tutorial is that we intentionally set a very low gas limit when using CCIP to send our message. This low gas limit is designed to cause the execution on the destination chain to fail, providing an opportunity to demonstrate the manual execution feature. Here's how you will proceed:
1. Initiate a Transfer: You'll transfer tokens and arbitrary data from your source contract on Avalanche Fuji to a receiver contract on Ethereum Sepolia. You will notice that the CCIP message has a very low gas limit, causing the execution on the receiver contract to fail.
2. Failure of CCIP Message Delivery: Once the transaction is finalized on the source chain (Avalanche Fuji), CCIP will deliver your message to the receiver contract on the destination chain (Ethereum Sepolia). You can follow the progress of your transaction using the [CCIP explorer](https://ccip.chain.link/). Here, you'll observe that the execution on the receiver contract failed due to the low gas limit.
3. Manual Execution via CCIP Explorer: Using the [CCIP explorer](https://ccip.chain.link/), you will override the previously set gas limit and retry the execution. This process is referred to as *manual execution*.
4. Confirm Successful Execution: After manually executing the transaction with an adequate gas limit, you'll see that the status of your CCIP message is updated to successful. This indicates that the tokens and data were correctly transferred to the receiver contract.
## Before you begin
1. You should understand how to write, compile, deploy, and fund a smart contract. If you need to brush up on the basics, read this [tutorial](/quickstarts/deploy-your-first-contract), which will guide you through using the [Solidity programming language](https://soliditylang.org/), interacting with the [MetaMask wallet](https://metamask.io) and working within the [Remix Development Environment](https://remix.ethereum.org/).
2. Your account must have some AVAX and LINK tokens on *Avalanche Fuji* and ETH tokens on *Ethereum Sepolia*. Learn how to [Acquire testnet LINK](/resources/acquire-link).
3. Check the [CCIP Directory](/ccip/directory) to confirm that the tokens you will transfer are supported for your lane. In this example, you will transfer tokens from *Avalanche Fuji* to *Ethereum Sepolia* so check the list of supported tokens [here](/ccip/directory/testnet/chain/avalanche-fuji-testnet).
4. Learn how to [acquire CCIP test tokens](/ccip/test-tokens#evm-chains). Following this guide, you should have CCIP-BnM tokens, and CCIP-BnM should appear in the list of your tokens in MetaMask.
5. Learn how to [fund your contract](/resources/fund-your-contract). This guide shows how to fund your contract in LINK, but you can use the same guide for funding your contract with any ERC20 tokens as long as they appear in the list of tokens in MetaMask.
6. Follow the previous tutorial: [*Transfer Tokens with Data*](/ccip/tutorials/evm/programmable-token-transfers) to learn how to make programmable token transfers using CCIP.
7. Create a free account on [Tenderly](https://tenderly.co/). You will use Tenderly to investigate the failed execution of the receiver contract.
## Tutorial
In this tutorial, you'll send a text *string* and CCIP-BnM tokens between smart contracts on *Avalanche Fuji* and *Ethereum Sepolia* using CCIP and pay transaction fees in LINK. The tutorial demonstrates setting a deliberately low gas limit in the CCIP message, causing initial execution failure on the receiver contract. You will then:
1. Use the [CCIP explorer](https://ccip.chain.link/) to increase the gas limit.
2. Manually retry the execution.
3. Observe successful execution after the gas limit adjustment.
### Deploy your contracts
To use this contract:
1. [Open the contract in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/ProgrammableTokenTransfersLowGasLimit.sol).
2. Compile your contract.
3. Deploy, fund your sender contract on *Avalanche Fuji* and enable sending messages to *Ethereum Sepolia*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, click on *Deploy & Run Transactions* and select *Injected Provider - MetaMask* from the environment list. Remix will then interact with your MetaMask wallet to communicate with *Avalanche Fuji*.
3. Fill in your blockchain's router and LINK contract addresses. The router address can be found on the [CCIP Directory](/ccip/directory) and the LINK contract address on the [LINK token contracts page](/resources/link-token-contracts). For *Avalanche Fuji*:
- The router address is 0xF694E193200268f9a4868e4Aa017A0118C9a8177,
- The LINK contract address is 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846.
4. Click the **transact** button. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
5. Open MetaMask and fund your contract with CCIP-BnM tokens. You can transfer 0.002 *CCIP-BnM* to your contract.
6. Open MetaMask and fund your contract with LINK tokens. You can transfer 70 *LINK* to your contract. In this example, LINK is used to pay the CCIP fees.
**Note:** This transaction fee is significantly higher than normal due to gas spikes on Sepolia. To run this example, you can get additional testnet LINK
from [faucets.chain.link](https://faucets.chain.link) or use a supported testnet other than Sepolia.
7. Enable your contract to send CCIP messages to *Ethereum Sepolia*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Avalanche Fuji*.
2. Call the `allowlistDestinationChain` with 16015286601757825753 as the destination chain selector, and true as allowed. Each chain selector is found on the [CCIP Directory](/ccip/directory).
4. Deploy your receiver contract on *Ethereum Sepolia* and enable receiving messages from your sender contract:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, make sure the environment is still *Injected Provider - MetaMask*.
3. Fill in your blockchain's router and LINK contract addresses. The router address can be found on the [CCIP Directory](/ccip/directory) and the LINK contract address on the [LINK token contracts page](/resources/link-token-contracts). For *Ethereum Sepolia*:
- The router address is 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59,
- The LINK contract address is 0x779877A7B0D9E8603169DdbD7836e478b4624789.
4. Click the **transact** button. After you confirm the transaction, the contract address appears on the *Deployed Contracts* list.
Note your contract address.
5. Enable your contract to receive CCIP messages from *Avalanche Fuji*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Ethereum Sepolia*.
2. Call the `allowlistSourceChain` with 14767482510784806043 as the source chain selector, and true as allowed. Each chain selector is found on the [CCIP Directory](/ccip/directory).
6. Enable your contract to receive CCIP messages from the contract that you deployed on *Avalanche Fuji*:
1. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Ethereum Sepolia*.
2. Call the `allowlistSender` with the contract address of the contract that you deployed on *Avalanche Fuji*, and true as allowed.
At this point, you have one *sender* contract on *Avalanche Fuji* and one *receiver* contract on *Ethereum Sepolia*. As security measures, you enabled the sender contract to send CCIP messages to *Ethereum Sepolia* and the receiver contract to receive CCIP messages from the sender and *Avalanche Fuji*.
### Transfer and Receive tokens and data and pay in LINK
You will transfer *0.001 CCIP-BnM* and a text. The CCIP fees for using CCIP will be paid in LINK.
1. Send a string data with tokens from *Avalanche Fuji*:
1. Open MetaMask and select the network *Avalanche Fuji*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Avalanche Fuji*.
3. Fill in the arguments of the ***sendMessagePayLINK*** function:
| Argument | Value and Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| _destinationChainSelector | CCIP Chain identifier of the destination blockchain (*Ethereum Sepolia* in this example). You can find each chain selector on the [CCIP Directory](/ccip/directory). |
| _receiver | Your receiver contract address at *Ethereum Sepolia*. The destination contract address. |
| _text | Any `string` |
| _token | The *CCIP-BnM* contract address at the source chain (*Avalanche Fuji* in this example). You can find all the addresses for each supported blockchain on the [CCIP Directory](/ccip/directory). |
| _amount | The token amount (*0.001 CCIP-BnM*). |
4. Click on `transact` and confirm the transaction on MetaMask.
5. After the transaction is successful, record the transaction hash. Here is an [example](https://testnet.snowtrace.io/tx/0xfb7e1eea5335c018589166b1ac597b618a92899d99ec4d1b1079e147cde81d9b) of a transaction on *Avalanche Fuji*.
1. Open the [CCIP explorer](https://ccip.chain.link/) and search your cross-chain transaction using the transaction hash. Note that the *Gas Limit* is *20000*. In this example, the CCIP message ID is *0xf8dc098c832332ac59ccc73ee00b480975d8f122a2265c90a1ccc2cd52268770*.
2. After a few minutes, the status will be updated to *Ready for manual execution* indicating that CCIP could not successfully deliver the message due to the initial low gas limit. At this stage, you have the option to override the gas limit.
3. You can also confirm that the CCIP message was not delivered to the receiver contract on the destination chain:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Ethereum Sepolia*.
3. Call the `getLastReceivedMessageDetails` function.
4. Observe that the returned data is empty: the received messageId is *0x0000000000000000000000000000000000000000000000000000000000000000*, indicating no message was received. Additionally, the received text field is empty, the token address is the default *0x0000000000000000000000000000000000000000*, and the token amount shows as *0*.
### Manual execution
#### Investigate the root cause of receiver contract execution failure
To determine if a low gas limit is causing the failure in the receiver contract's execution, consider the following methods:
- Error analysis: Examine the error description in the CCIP explorer. An error labeled *ReceiverError. This may be due to an out of gas error on the destination chain. Error code: 0x*, often indicates a low gas issue.
- Advanced Investigation Tool: For a comprehensive analysis, employ a sophisticated tool like [Tenderly](https://tenderly.co/). Tenderly can provide detailed insights into the transaction processes, helping to pinpoint the exact cause of the failure.
To use [Tenderly](https://tenderly.co/):
1. Copy the destination transaction hash from the CCIP explorer. In this example, the destination transaction hash is *0x9f5b50460a1ab551add15dc4b743c81df992e34bc8140bbbdc033de7043140f5*.
2. Open Tenderly and search for your transaction. You should see an interface similar to the following:
3. Enable *Full Trace* then click on *Reverts*.
4. Notice the *out of gas* error in the receiver contract. In this example, the receiver contract is *0x47EAa31C9e2B1B1Ba19824BedcbE0014c15df15e*.
#### Trigger manual execution
You will increase the gas limit and trigger manual execution:
1. In the [CCIP explorer](https://ccip.chain.link/), connect your wallet, set the *Gas limit override* to 200000, and click on *Trigger Manual Execution*.
2. After you confirm the transaction on Metamask, the CCIP explorer shows you a confirmation screen.
3. Click on the *Close* button and observe the status marked as *Success*.
4. Check the receiver contract on the destination chain:
1. Open MetaMask and select the network *Ethereum Sepolia*.
2. In Remix IDE, under *Deploy & Run Transactions*, open the list of transactions of your smart contract deployed on *Ethereum Sepolia*.
3. Call the `getLastReceivedMessageDetails` function.
4. Notice the received messageId is *0xf8dc098c832332ac59ccc73ee00b480975d8f122a2265c90a1ccc2cd52268770*, the received text is *Hello World!*, the token address is *0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05* (CCIP-BnM token address on *Ethereum Sepolia*) and the token amount is 1000000000000000 (0.001 CCIP-BnM).
**Note**: These example contracts are designed to work bi-directionally. As an exercise, you can use them to transfer tokens and data from *Avalanche Fuji* to *Ethereum Sepolia* and from *Ethereum Sepolia* back to *Avalanche Fuji*.
## Explanation
The smart contract used in this tutorial is configured to use CCIP for transferring and receiving tokens with data, similar to the contract in the [*Transfer Tokens with Data*](/ccip/tutorials/evm/programmable-token-transfers#explanation) section of that tutorial.
A key distinction in this tutorial is the intentional setup of a low gas limit of `20,000` for building the CCIP message. This specific gas limit setting is expected to fail the message delivery on the receiver contract in the destination chain:
```solidity
Client.EVMExtraArgsV2({
gasLimit: 20_000
allowOutOfOrderExecution: true
})
```
---
# Optimizing Gas Limit Settings in CCIP Messages
Source: https://docs.chain.link/ccip/tutorials/evm/ccipreceive-gaslimit
Last Updated: 2025-10-21
When constructing a [CCIP message](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage), it's crucial to set the gas limit accurately. The gas limit represents the maximum amount of gas consumed to execute the [`ccipReceive`](/ccip/api-reference/evm/v1.6.2/ccip-receiver#ccipreceive) function on the CCIP Receiver, which influences the transaction fees for sending a CCIP message. Notably, unused gas is not reimbursed, making it essential to estimate the gas limit carefully:
- Setting the gas limit too low will cause the transaction to revert when CCIP calls `ccipReceive` on the CCIP Receiver, which requires a manual re-execution with an increased gas limit. For more details about this scenario, read the [Manual Execution](/ccip/tutorials/evm/manual-execution) guide.
- Conversely, an excessively high gas limit leads to higher fees.
This tutorial shows you how to estimate the gas limit for the `ccipReceive` function using various methods. You will learn how to use a CCIP Receiver where the gas consumption of the `ccipReceive` function varies based on the input data. This example emphasizes the need for testing under diverse conditions. This tutorial includes tasks for the following environments:
1. **Local Environment**: Using [Hardhat](https://hardhat.org/) and [Foundry](https://book.getfoundry.sh/) on a local blockchain provides a swift initial gas estimate. However, different frameworks can yield different results and the local environment will not always be representative of your destination blockchain. Consider these figures to be preliminary estimates. Then, incorporate a buffer and conduct subsequent validations on a testnet.
2. **Testnet**: You can precisely determine the required gas limit by deploying your CCIP Sender and Receiver on a testnet and transmitting several CCIP messages with the previously estimated gas. Although this approach is more time-intensive, especially if testing across multiple blockchains, it offers enhanced accuracy.
3. **Offchain Methods**: Estimating gas with an offchain Web3 provider or tools like [Tenderly](https://docs.tenderly.co/) offers the most accurate and swift way to determine the needed gas limit.
These approaches will give you insights into accurately estimating the gas limit for the `ccipReceive` function, ensuring cost-effective CCIP transactions.
Before you begin, open a terminal, clone the [smart-contract-examples repository](https://github.com/smartcontractkit/smart-contract-examples), and navigate to the `smart-contract-examples/ccip/estimate-gas` directory.
```bash
git clone https://github.com/smartcontractkit/smart-contract-examples.git && \
cd smart-contract-examples/ccip/estimate-gas
```
## Examine the code
The source code for the CCIP Sender and Receiver is located in the `contracts` directory for Hardhat projects and in the `src` directory for Foundry projects. The code includes detailed comments for clarity and is designed to ensure self-explanatory functionality. This section focuses on the `_ccipReceive` function:
```solidity
function _ccipReceive(Client.Any2EVMMessage memory any2EvmMessage) internal override {
uint256 iterations = abi.decode(any2EvmMessage.data, (uint256));
uint256 result = iterations;
uint256 maxIterations = iterations % 100;
for (uint256 i = 0; i < maxIterations; i++) {
result += i;
}
emit MessageReceived(
any2EvmMessage.messageId,
any2EvmMessage.sourceChainSelector,
abi.decode(any2EvmMessage.sender, (address)),
iterations,
maxIterations,
result
);
}
```
The `_ccipReceive` function operates as follows:
1. **Input Processing:** The function accepts a `Client.Any2EVMMessage`. The first step involves decoding the number of iterations from the message's data using ABI decoding.
2. **Logic Execution:** It initializes the `result` variable with the number of iterations. The function calculates `maxIterations` by taking the modulo of iterations with 100, which sets an upper limit for iteration. This step is a safeguard to ensure that the function does not run out of gas.
3. **Iteration:** The function executes a loop from 0 to `maxIterations`, simulating variable computational work based on the input data. This variability directly influences gas consumption.
4. **Event Emission:** Finally, an event `MessageReceived` is emitted.
This code shows how gas consumption for the `_ccipReceive` function can fluctuate in response to the input data, highlighting the necessity for thorough testing under different scenarios to determine the correct `gasLimit`.
## Gas estimation in a local environment
To facilitate testing within a local environment, you will use the [MockCCIPRouter](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/test/mocks/MockRouter.sol) contract. This contract serves as a mock implementation of the CCIP Router contract, enabling the local testing of CCIP Sender and Receiver contracts.
A notable feature of the `MockCCIPRouter` contract is its ability to emit a `MsgExecuted` event:
```solidity
event MsgExecuted(bool success, bytes retData, uint256 gasUsed)
```
This event reports the amount of gas consumed by the `ccipReceive` function.
### Foundry
#### Prerequisites
1. In your terminal, change to the `foundry` directory:
```bash
cd foundry
```
2. Ensure Foundry is [installed](https://book.getfoundry.sh/getting-started/installation).
3. Check the Foundry version:
```bash
forge --version
```
The output should be similar to the following:
```text
forge Version: 1.4.2-stable
Commit SHA: 828441d243f552f82c5a89bdc818e52bdd57b26b
Build Timestamp: 2025-10-18T08:19:39.947006000Z (1760775579)
Build Profile: maxperf
```
You need version 1.4.2 or above. Run `foundryup` to update Foundry if necessary.
4. Build your project:
```bash
forge build
```
The output should be similar to:
```text
[⠊] Compiling...
[⠑] Compiling 59 files with Solc 0.8.24
[⠘] Solc 0.8.24 finished in 1.67s
Compiler run successful!
```
#### Estimate gas
Located in the `test` directory, the `SendReceive.t.sol` test file assesses the gas consumption of the `ccipReceive` function. This file features a test case that sends a CCIP message to the `MockCCIPRouter` contract, which triggers the `MsgExecuted` event. This event provides insights into the gas requirements of the `ccipReceive` function by detailing the amount of gas consumed. The test case explores three scenarios to examine gas usage comprehensively across various operational conditions:
- **Baseline gas consumption:** This scenario runs `0` iteration to determine the baseline gas consumption, representing the least amount of gas required.
- **Average gas consumption:** This scenario runs `50` iterations to estimate the gas consumption under average operational conditions.
- **Peak gas consumption:** This scenario runs `99` iterations to estimate the peak gas consumption, marking the upper limit of gas usage.
To run the test, execute the following command:
```bash
forge test -vv --isolate
```
Output example:
```text
[⠊] Compiling...
No files changed, compilation skipped
Ran 3 tests for test/SendReceive.t.sol:SenderReceiverTest
[PASS] test_SendReceiveAverage() (gas: 137800)
Logs:
Number of iterations 50 - Gas used: 11323
[PASS] test_SendReceiveMax() (gas: 143803)
Logs:
Number of iterations 99 - Gas used: 17350
[PASS] test_SendReceiveMin() (gas: 131614)
Logs:
Number of iterations 0 - Gas used: 5173
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 8.26ms (9.35ms CPU time)
Ran 1 test suite in 123.83ms (8.26ms CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests)
```
This table summarizes the gas usage for different iterations:
| Scenario | Number of iterations | Gas used |
| ------------------------ | -------------------- | -------- |
| Baseline gas consumption | 0 | 5173 |
| Average gas consumption | 50 | 11323 |
| Peak gas consumption | 99 | 17350 |
The output demonstrates that gas consumption increases with the number of iterations, peaking when the iteration count reaches `99`.
In the next section, you will compare these results with those obtained from a local Hardhat environment.
### Hardhat
#### Prerequisites
1. In your terminal, navigate to the `hardhat` directory:
```bash
cd ../hardhat
```
2. Install the dependencies:
```bash
npm install
```
3. Set the password to encrypt your environment variables using the following command:
```bash
npx env-enc set-pw
```
4. Set the following environment variables to deploy contracts on testnets:
- `PRIVATE_KEY`: The private key for your testnet wallet. If you use MetaMask, follow the instructions to [Export a Private Key](https://support.metamask.io/hc/en-us/articles/360015289632-How-to-export-an-account-s-private-key). **Note:** Your private key is needed to sign any transactions you make such as making requests.
- `ETHEREUM_SEPOLIA_RPC_URL`: The RPC URL for Ethereum Sepolia testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service.
- `AVALANCHE_FUJI_RPC_URL`: The RPC URL for Avalanche Fuji testnet. You can sign up for a personal endpoint from [Infura](https://www.infura.io/) or another node provider service.
- `ETHERSCAN_API_KEY`: An Ethereum explorer API key, used to verify your contract. Follow [this guide](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics) to get one from Etherscan.
Input these variables using the following command:
```bash
npx env-enc set
```
5. Compile the contracts:
```bash
npx hardhat compile
```
The output should be similar to:
```text
Compiled 4 Solidity files with solc 0.8.24 (evm target: shanghai)
Nothing to compile
```
#### Estimate gas
Located in the `test` directory, the `Send-Receive.ts` test file is designed to evaluate the gas usage of the `ccipReceive` function. This file employs the same logic as the Foundry test file, featuring three scenarios varying by the number of iterations. The test case transmits a CCIP message to the `MockCCIPRouter` contract, triggering the `MsgExecuted` event. This event provides insights into the gas requirements of the `ccipReceive` function by detailing the amount of gas used.
To run only the TypeScript tests, use the test nodejs task:
```bash
npx hardhat test nodejs
```
Example of the output:
```text
Running node:test tests
Final Gas Usage Report:
Number of iterations 0 - Gas used: 5151
Number of iterations 50 - Gas used: 11301
Number of iterations 99 - Gas used: 17328
Sender and Receiver
✔ should CCIP message from sender to receiver (760ms)
1 passing (1036ms)
```
This table summarizes the gas usage across different iterations:
| Scenario | Number of iterations | Gas used |
| ------------------------ | -------------------- | -------- |
| Baseline gas consumption | 0 | 5151 |
| Average gas consumption | 50 | 11301 |
| Peak gas consumption | 99 | 17328 |
The output demonstrates that gas consumption increases with the number of iterations, peaking when the iteration count reaches `99`.
### Compare the results from Foundry and Hardhat
This table summarizes the gas usage for different iterations from both Foundry and Hardhat:
| Scenario | Number of iterations | Gas used (Foundry) | Gas used (Hardhat) |
| ------------------------ | -------------------- | ------------------ | ------------------ |
| Baseline gas consumption | 0 | 5173 | 5151 |
| Average gas consumption | 50 | 11323 | 11301 |
| Peak gas consumption | 99 | 17350 | 17328 |
Gas usage trends across different iterations are consistent between Foundry and Hardhat and increase with the number of iterations, reaching a peak at 99. However, slight variations in gas usage between the two environments at each iteration level demonstrate the importance of extending gas usage estimation beyond local environment testing. To accurately determine the appropriate gas limit, it is recommended to conduct additional validations on the target blockchain. Setting the gas limit with a buffer is advisable to account for differences between local environment estimations and actual gas usage on the target blockchain.
### Estimate gas usage on your local environment
Now that you've locally estimated the gas usage of the `ccipReceive` function using the provided projects, you can apply the same approach to your own Foundry or Hardhat project. This section will guide you through estimating gas usage in your Foundry or Hardhat project.
#### Estimate `ccipReceive` gas usage locally in your Foundry project
To estimate the gas usage of the `ccipReceive` function within your own Foundry project, follow these steps:
1. Create a testing file in the `test` directory of your project and import the [`MockCCIPRouter`](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/test/mocks/MockRouter.sol) contract:
```solidity
import {MockCCIPRouter} from "@chainlink/contracts-ccip/contracts/test/mocks/MockRouter.sol";
```
***Note*:** The `MockCCIPRouter` receives the CCIP message from your CCIP Sender, calls the `ccipReceive` function on your CCIP Receiver, and emits the `MsgExecuted` event with the gas used.
2. Inside the `setUp` function, deploy the `MockCCIPRouter` contract, and use its address to deploy your CCIP Sender and CCIP Receiver contracts. For more details, check this [example](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/estimate-gas/foundry/test/SendReceive.t.sol#L23).
3. In your test cases:
1. Before transmitting any CCIP messages, use `vm.recordLogs()` to start capturing events. For more details, check this [example](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/estimate-gas/foundry/test/SendReceive.t.sol#L39).
2. After sending the CCIP message, use `vm.getRecordedLogs()` to collect the recorded logs. For more details, check this [example](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/estimate-gas/foundry/test/SendReceive.t.sol#L47-L64).
3. Parse the logs to find the `MsgExecuted(bool,bytes,uint256)` event and extract the gas used. For more details, check this [example](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/estimate-gas/foundry/test/SendReceive.t.sol#L53).
#### Estimate `ccipReceive` gas usage locally in your Hardhat project
To estimate the gas usage of the `ccipReceive` function within your own Hardhat project, follow these steps:
1. Create a Solidity file in the `contracts` directory of your project and import the [`MockCCIPRouter`](https://github.com/smartcontractkit/chainlink-ccip/blob/release/contracts-ccip-1.6.2/chains/evm/contracts/test/mocks/MockRouter.sol) contract:
```solidity
import {MockCCIPRouter} from "@chainlink/contracts-ccip/contracts/test/mocks/MockRouter.sol";
```
***Note*:** The `MockCCIPRouter` receives the CCIP message from your CCIP Sender, calls the `ccipReceive` function on your CCIP Receiver, and emits the `MsgExecuted` event with the gas used.
2. Create a testing file in your project's `test` directory.
3. Inside the `deployFixture` function, deploy the `MockCCIPRouter` contract and use its address to deploy your CCIP Sender and CCIP Receiver contracts. For more details, check this [example](https://github.com/smartcontractkit/smart-contract-examples/blob/ccip/estimate-gas/hardhat/test/Send-Receive.ts#L14).
4. In your test cases:
1. Send the CCIP message to the `MockCCIPRouter` contract. For more details, check this [example](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/estimate-gas/hardhat/test/Send-Receive.ts#L77).
2. Parse the logs to find the `MsgExecuted(bool,bytes,uint256)` event and extract the gas used. For more details, check this [example](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/estimate-gas/hardhat/test/Send-Receive.ts#L59).
## Gas estimation on a testnet
To accurately validate your local environment's gas usage estimations, follow these steps:
1. Deploy and configure the CCIP Sender contract on the Avalanche Fuji testnet and the CCIP Receiver contract on the Ethereum Sepolia testnet.
2. Send several CCIP messages with the same number of iterations used in your local testing. For this purpose, use the `sendCCIPMessage.ts` script in the `scripts/testing` directory. This script includes a 10% buffer over the estimated gas usage to ensure a sufficient gas limit. Refer to the table below for the buffered gas limits for each iteration:
| Scenario | Number of iterations | Estimated gas usage (Hardhat) | Buffered gas limit (+10%) |
| ------------------------ | -------------------- | ----------------------------- | ------------------------- |
| Baseline gas consumption | 0 | 5151 | 5666 |
| Average gas consumption | 50 | 11301 | 12431 |
| Peak gas consumption | 99 | 17328 | 19061 |
3. Use [Tenderly](https://dashboard.tenderly.co) to monitor and confirm that the transactions execute successfully within the buffered gas limits. Subsequently, compare the actual gas usage of the `ccipReceive` function on the Ethereum Sepolia testnet against the buffered limits to fine-tune the final gas limit.
This approach ensures that your gas limit settings are validated against real-world conditions on testnets, providing a more accurate and reliable estimation for deploying on live blockchains.
### Deploy and configure the contracts
To deploy and configure the CCIP Sender contract on the Avalanche Fuji testnet and the CCIP Receiver contract on the Ethereum Sepolia testnet, follow the steps below. **Note**: Your account must have some ETH tokens on Ethereum Sepolia and AVAX tokens on Avalanche Fuji.
1. Deploy the CCIP Sender on the Avalanche Fuji testnet:
```bash
npx hardhat run scripts/deployment/deploySender.ts --network avalancheFuji
```
2. Deploy the CCIP Receiver on the Ethereum Sepolia testnet:
```bash
npx hardhat run scripts/deployment/deployReceiver.ts --network ethereumSepolia
```
3. Authorize the Sender to send messages to Ethereum Sepolia:
```bash
npx hardhat run scripts/configuration/allowlistingForSender.ts --network avalancheFuji
```
4. Authorize the Receiver to receive messages from the Sender:
```bash
npx hardhat run scripts/configuration/allowlistingForReceiver.ts --network ethereumSepolia
```
Upon completion, you will find the CCIP Sender and Receiver contracts deployed and configured on their respective testnets. Contract addresses are available in the `scripts/generatedData.json` file.
### Send CCIP Messages
1. Send three CCIP messages with different numbers of iterations:
```bash
npx hardhat run scripts/testing/sendCCIPMessages.ts --network avalancheFuji
```
Example output:
```text
$ npx hardhat run scripts/testing/sendCCIPMessages.ts --network avalancheFuji
Approving 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846 for 0x6e80C9d77B12470A6158459cED1616F705aA13Ea. Allowance is max uint256. Signer 0x8c244f0b2164e6a3bed74ab429b0ebd661bb14ca...
Number of iterations 0 - Gas limit: 5685 - Message Id: 0x7a51c84fd0734afef641e3578b29e718c208d6ac2e50826f73a3fec24076a72b
Number of iterations 50 - Gas limit: 16190 - Message Id: 0x348ab504289f5a0a0ffed023d8315668dbdd8a8a2ae93349845e837e397aa1ac
Number of iterations 99 - Gas limit: 26485 - Message Id: 0xe4dfdac75ae90f355583b356033179a89a95e5174256a04c371ab10dfe433d96
```
2. Open the [CCIP explorer](https://ccip.chain.link), search each message by its ID, and wait for each message to be successfully transmitted (`Status` in the explorer: `Success`).
For the example above, here are the destination transaction hashes:
| Message id | Ethereum Sepolia transaction hash |
| ------------------------------------------------------------------ | ------------------------------------------------------------------ |
| 0x7a51c84fd0734afef641e3578b29e718c208d6ac2e50826f73a3fec24076a72b | 0x25c26be00c5e72127588a29ef485b817dae0d1bf4cd59fd2446da9af03441413 |
| 0x348ab504289f5a0a0ffed023d8315668dbdd8a8a2ae93349845e837e397aa1ac | 0x25c26be00c5e72127588a29ef485b817dae0d1bf4cd59fd2446da9af03441413 |
| 0xe4dfdac75ae90f355583b356033179a89a95e5174256a04c371ab10dfe433d96 | 0x25c26be00c5e72127588a29ef485b817dae0d1bf4cd59fd2446da9af03441413 |
**Note** that the Ethereum Sepolia transaction hash is the same for all the messages. This is because CCIP batched the messages.
### Check the actual gas usage
1. Open [Tenderly](https://dashboard.tenderly.co) and Search for the [destination transaction hash](https://dashboard.tenderly.co/tx/sepolia/0x25c26be00c5e72127588a29ef485b817dae0d1bf4cd59fd2446da9af03441413).
2. Search for `_callWithExactGasSafeReturnData` that has payload containing your messageId (without `0x`). This is an [example](https://dashboard.tenderly.co/tx/0x25c26be00c5e72127588a29ef485b817dae0d1bf4cd59fd2446da9af03441413?trace=0.0.0.12.0.2.2) for `0x7a51c84fd0734afef641e3578b29e718c208d6ac2e50826f73a3fec24076a72b`.
3. Just below it, you will find the call trace from the Router to your Receiver contract. This is the [trace](https://dashboard.tenderly.co/tx/0x25c26be00c5e72127588a29ef485b817dae0d1bf4cd59fd2446da9af03441413?trace=0.0.0.12.0.2.2).
4. Right below the `_callWithExactGasSafeReturnData`, you will see the `Router` => `Receiver`.`ccipReceive` call. If it shows "No source for this contract", click on "Fetch the contract from public explorer" to load the source code from Etherscan since it's already verified. When you click on the value of the `message` parameter, it will display the exact message ID (as `messageId`) (i.e., `0x7a51c84fd0734afef641e3578b29e718c208d6ac2e50826f73a3fec24076a72b` in this case) as one of its fields.
5. Click on the debugger and you'll get the gas details:
```text
"gas":{
"gas_left":5685
"gas_used":5016
"total_gas_used":7994315
}
```
6. Note the `gas_left` is equal to the limit that is set in the `sendCCIPMessages.ts` script: `5685`. The `gas_used` is the actual gas used by the Receiver contract to process the message.
7. Repeating the same steps for the other two messages, we can summarize the output:
| Scenario | Number of iterations | Estimated gas usage (Hardhat) | Buffered gas limit (+10%) | Gas used on testnet |
| ------------------------ | -------------------- | ----------------------------- | ------------------------- | ------------------- |
| Baseline gas consumption | 0 | 5151 | 5666 | 5016 |
| Average gas consumption | 50 | 11301 | 12431 | 11166 |
| Peak gas consumption | 99 | 17328 | 19061 | 17193 |
Testing on testnets has confirmed that a gas limit of `19061` is adequate for the `ccipReceive` function to execute successfully under various conditions. However, it is important to note that gas usage may differ across testnets. Therefore, it is advisable to conduct similar validation efforts on the blockchain where you intend to deploy. Deploying and validating contracts across multiple testnets can be time-consuming. For efficiency, consider using [offchain methods](#offchain-methods) to estimate gas usage.
## Offchain methods
This section guides you through estimating gas usage using two different offchain methods:
- A Web3 provider using the [ethers.js `estimateGas`](https://docs.ethers.org/v6/api/providers/#Provider-estimateGas) function.
- [Tenderly simulation API](https://docs.tenderly.co/reference/api#tag/Simulations/operation/simulateTransaction). The Tenderly simulation API provides a more accurate result (Read this [blog post](https://blog.tenderly.co/how-tenderly-enables-most-accurate-ethereum-gas-estimation/) to learn more) but you are limited to the blockchains supported by Tenderly.
These methods provide the most accurate and rapid means to determine the necessary gas limit for the `ccipReceive` function. You will use the same CCIP Receiver contract deployed on the Ethereum Sepolia testnet in the previous section.
### Prerequisites
1. In your terminal, navigate to the `offchain` directory:
```bash
cd ../offchain
```
2. Modify the `data.json` file to insert the deployed addresses of your Sender and Receiver contracts.
3. Install the dependencies:
```bash
npm install
```
4. Set the password to encrypt your environment variables:
```bash
npx env-enc set-pw
```
5. Set up the following environment variables:
- `ETHEREUM_SEPOLIA_RPC_URL`: The RPC URL for Ethereum Sepolia testnet. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service.
- `TENDERLY_ACCOUNT_SLUG`: This is one part of your Tenderly API URL. You can [find this value in your Tenderly account](https://docs.tenderly.co/account/projects/account-project-slug).
- `TENDERLY_PROJECT_SLUG`: This is one part of your Tenderly API URL. You can [find this value in your Tenderly account](https://docs.tenderly.co/account/projects/account-project-slug).
- `TENDERLY_ACCESS_KEY`: If you don't already have one, you can [generate a new access token](https://docs.tenderly.co/account/projects/how-to-generate-api-access-token).
Input these variables using the following command:
```bash
npx env-enc set
```
6. Generate [Typechain typings](https://www.npmjs.com/package/typechain) for the Receiver contract:
```bash
npm run generate-types
```
### Introduction of the scripts
The scripts are located in the `src` directory. Each script is self-explanatory and includes comprehensive comments to explain its functionality and usage. There are three scripts:
- `estimateGasProvider.ts`: This script uses the [`eth_estimateGas`](https://docs.alchemy.com/reference/eth-estimategas) Ethereum API to estimate the gas usage of the `ccipReceive` function. It simulates sending three CCIP messages to the Receiver contract with a varying number of iterations and estimates the gas usage using the [ethers.js `estimateGas`](https://docs.ethers.org/v6/api/providers/#Provider-estimateGas) function.
- `estimateGasTenderly.ts`: This script leverages the Tenderly `simulate` API to estimate the gas usage of the `ccipReceive` function. Similar to the previous script, it simulates sending three CCIP messages to the Receiver contract with different numbers of iterations and estimates the gas usage using the [Tenderly `simulate` API](https://docs.tenderly.co/reference/api#tag/Simulations/operation/simulateTransaction).
- `helper.ts`: This script contains helper functions used by the other scripts. The two main functions are:
- `buildTransactionData`: This function constructs a CCIP message for a specified number of iterations and then returns the transaction data.
- `estimateIntrinsicGas`: Exclusively called by the `estimateGasProvider.ts` script, this function estimates the intrinsic gas of a transaction. The intrinsic gas represents the minimum amount of gas required before executing a transaction. It is determined by the transaction data and the type of transaction. Since this gas is paid by the initiator of the transaction, we use this function to estimate the intrinsic gas and then deduct it from the total gas used to isolate the gas consumed by the `ccipReceive` function.
### Estimate gas using a Web3 provider
Ethereum nodes implement the [`eth_estimateGas`](https://docs.alchemy.com/reference/eth-estimategas) Ethereum API to predict the gas required for a transaction's successful execution. To estimate the gas usage of the `ccipReceive` function, you can directly call the `eth_estimateGas` API via a Web3 provider or leverage a library like ethers.js, simplifying this interaction. This guide focuses on the [ethers.js `estimateGas`](https://docs.ethers.org/v6/api/providers/#Provider-estimateGas) function for gas estimation. To estimate the gas usage, execute the following command in your terminal:
```bash
npm run estimate-gas-provider
```
Example output:
```text
$ npm run estimate-gas-provider
> offchain-simulator@1.0.0 estimate-gas-provider
> ts-node src/estimateGasProvider.ts
Final Gas Usage Report:
Number of iterations: 0 - Gas used: 5363
Number of iterations: 50 - Gas used: 11525
Number of iterations: 99 - Gas used: 17564
```
The estimate may exceed the actual gas used by the transaction for various reasons, including differences in node performance and EVM mechanics. For a more precise estimation, consider using Tenderly (see the next section for details).
### Estimate gas using Tenderly
To estimate the gas usage of the `ccipReceive` function using Tenderly, execute the following command:
```bash
npm run estimate-gas-tenderly
```
Example output:
```text
$ npm run estimate-gas-tenderly
> offchain-simulator@1.0.0 estimate-gas-tenderly
> ts-node src/estimateGasTenderly.ts
Final Gas Usage Report:
Number of iterations: 0 - Gas used: 5016
Number of iterations: 50 - Gas used: 11166
Number of iterations: 99 - Gas used: 17193
```
### Comparison
The table below summarizes the gas estimations for different iterations using both Web3 provider and Tenderly:
| Scenario | Number of iterations | Gas estimated (Web3 provider) | Gas estimated (Tenderly) |
| ------------------------ | -------------------- | ----------------------------- | ------------------------ |
| Baseline gas consumption | 0 | 5363 | 5016 |
| Average gas consumption | 50 | 11525 | 11166 |
| Peak gas consumption | 99 | 17564 | 17193 |
The gas estimations from both Web3 provider and Tenderly are consistent across different iterations and align with actual testnet results. This demonstrates the accuracy and reliability of these offchain methods in estimating gas usage. However, you can notice that Tenderly provides more accurate results.
## Conclusion
This tutorial has guided you through estimating the gas limit for the `ccipReceive` function using various methods. You have learned how to estimate gas usage in a local environment using Hardhat and Foundry, validate these estimations on testnets, and use offchain methods to estimate gas usage.
As we have explored various methods for estimating gas for the `ccipReceive` function, it is crucial to apply this knowledge effectively. Here are some targeted recommendations to enhance your approach to gas estimation:
1. **Comprehensive Testing:** Emphasize testing under diverse scenarios to ensure your gas estimations are robust. Different conditions can significantly affect gas usage, so covering as many cases as possible in your tests is crucial.
2. **Preliminary Local Estimates:** Local testing is a critical first step for estimating gas and ensuring your contracts function correctly under various scenarios. While Hardhat and Foundry facilitate development and testing, it's key to remember that these environments may not perfectly mirror your target blockchain's conditions. These initial estimates serve as valuable insights, guiding you toward more accurate gas limit settings when you proceed to testnet validations. Incorporating a buffer based on these preliminary results can help mitigate the risks of underestimating gas requirements due to environmental differences.
3. **Efficiency with Offchain Methods:** Since testing across different blockchains can be resource-intensive, leaning on offchain methods for gas estimation is invaluable. Tools such as Tenderly not only facilitate rapid and accurate gas usage insights on your target blockchains but also enable you to simulate the execution of the `ccipReceive` function on actual contracts deployed on mainnets. If Tenderly doesn't support a particular blockchain, a practical alternative is to use a Web3 provider that does support that chain, as illustrated in the [Estimate gas using a Web3 provider](#estimate-gas-using-a-web3-provider) section. This is particularly helpful when considering the diversity in gas metering rules across blockchains. This approach saves time and enhances the precision of your gas limit estimations, allowing for more cost-effective transactions from your dApp.
---
# CCIP Tutorials (SVM)
Source: https://docs.chain.link/ccip/tutorials/svm
Last Updated: 2025-08-22
Chainlink CCIP enables secure cross-chain communication between Solana and EVM blockchains. These tutorials will help you implement cross-chain functionality in both directions.
## Getting Started
Before diving into specific implementations, ensure you understand the fundamentals:
- [Prerequisites for SVM to EVM Tutorials](/ccip/tutorials/svm/source/prerequisites) - Set up your development environment for Solana-based CCIP development
- [Implementing CCIP Receivers](/ccip/tutorials/svm/receivers) - Learn how to build secure Solana programs that can receive cross-chain messages
## Using Solana as a Source Chain
Send messages and tokens from Solana to EVM chains:
- [SVM to EVM Guide](/ccip/tutorials/svm/source) - Comprehensive guide for building all types of CCIP messages from Solana to EVM chains
- [Token Transfers](/ccip/tutorials/svm/source/token-transfers) - Transfer tokens from Solana to EVM chains without executing code on the destination
## Using Solana as a Destination Chain
Send messages and tokens from EVM chains to Solana:
- [EVM to SVM Guide](/ccip/tutorials/svm/destination) - Comprehensive guide for building all types of CCIP messages from EVM chains to Solana
- [Token Transfers](/ccip/tutorials/svm/destination/token-transfers) - Transfer tokens from EVM chains to Solana wallets
- [Arbitrary Messaging](/ccip/tutorials/svm/destination/arbitrary-messaging) - Send data from EVM chains to execute programs on Solana
## Cross-Chain Token (CCT) Standard
Implement cross-chain tokens that can transfer between Solana and EVM chains using different governance approaches:
- [Cross-Chain Token Tutorials](/ccip/tutorials/svm/cross-chain-tokens) - Complete guide to implementing cross-chain tokens with various governance models
- [Direct Mint Authority Transfer](/ccip/tutorials/svm/cross-chain-tokens/direct-mint-authority) - Simplified approach for development and testing environments
- [SPL Token Multisig Tutorial](/ccip/tutorials/svm/cross-chain-tokens/spl-token-multisig-tutorial) - Educational multisig concepts for learning governance patterns
- [Production Multisig Governance](/ccip/tutorials/svm/cross-chain-tokens/production-multisig-tutorial) - Enterprise-grade dual-layer governance for production deployments
## Architecture Reference
Key Differences from EVM:
- **Account Model:** Solana uses an account-based architecture where programs are stateless and all data is stored in accounts
- **PDAs:** Program Derived Addresses provide deterministic storage for Solana programs
- **Explicit Access:** Programs can only access accounts explicitly provided to them
- **Token Accounts:** Each token requires a separate Associated Token Account (ATA)
Message Types:
- **Token Transfers:** Send tokens across chains without program execution
- **Arbitrary Messaging:** Send data to trigger program execution on the destination chain
- **Programmable Token Transfers:** Send both tokens and data in a single message to trigger program execution with token transfers
---
# Implementing CCIP Receivers
Source: https://docs.chain.link/ccip/tutorials/svm/receivers
Last Updated: 2025-07-25
# Implementing CCIP Receivers for Solana
This reference guide explains the key components and security patterns required for building Solana programs that can receive cross-chain messages via Chainlink's Cross-Chain Interoperability Protocol (CCIP).
## Introduction
A CCIP Receiver is a Solana program that implements the [`ccip_receive`](/ccip/api-reference/svm/v1.6.0/receiver#ccip_receive) instruction, allowing it to process incoming cross-chain messages. It can handle both arbitrary data payloads and/or token transfers, serving as the destination endpoint for CCIP messages.
## Security Architecture
To build a secure CCIP receiver program, you need to understand how it interacts with the core CCIP programs.
Your receiver program interacts with two external CCIP components:
## Core Components of a CCIP Receiver
A complete CCIP Receiver implementation contains several key components, each serving a specific purpose in the cross-chain messaging system.
### Message Structure
CCIP messages follow a standardized structure that your program must be prepared to receive:
```rust
#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
pub struct Any2SVMMessage {
pub message_id: [u8; 32],
pub source_chain_selector: u64,
pub sender: Vec,
pub data: Vec,
pub token_amounts: Vec,
}
#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, Default)]
pub struct SVMTokenAmount {
pub token: Pubkey,
pub amount: u64, // solana local token amount
}
```
These structures contain:
- `message_id`: A unique identifier for the message
- `source_chain_selector`: The chain ID of the source chain
- `sender`: The address of the sender on the source chain
- `data`: The arbitrary data payload
- `token_amounts`: An array of tokens and amounts being transferred
### The CcipReceive Context
The account context for the `ccip_receive` instruction is the most critical security component of your program. It must follow this exact pattern:
```rust
#[derive(Accounts, Debug)]
#[instruction(message: Any2SVMMessage)]
pub struct CcipReceive<'info> {
// Offramp CPI signer PDA must be first.
#[account(
seeds = [EXTERNAL_EXECUTION_CONFIG_SEED, crate::ID.as_ref()],
bump,
seeds::program = offramp_program.key(),
)]
pub authority: Signer<'info>,
/// CHECK offramp program: exists only to derive the allowed offramp PDA
pub offramp_program: UncheckedAccount<'info>,
/// CHECK PDA of the router program verifying the signer is an allowed offramp.
#[account(
owner = state.router @ CcipReceiverError::InvalidCaller,
seeds = [
ALLOWED_OFFRAMP,
message.source_chain_selector.to_le_bytes().as_ref(),
offramp_program.key().as_ref()
],
bump,
seeds::program = state.router,
)]
pub allowed_offramp: UncheckedAccount<'info>,
// Program-specific accounts follow...
#[account(
seeds = [STATE],
bump,
)]
pub state: Account<'info, BaseState>,
// Additional program accounts as needed...
}
```
### Program State
Your program needs state accounts to store configuration information, most importantly the router address:
```rust
#[account]
#[derive(InitSpace, Default, Debug)]
pub struct BaseState {
pub owner: Pubkey,
pub proposed_owner: Pubkey,
pub router: Pubkey,
}
```
This is a basic example of state storage, but your program can have more complex state structures with additional data. The critical requirement is that the state must store the router address for verification of the `allowed_offramp` PDA.
### Extending the CcipReceive Context
While the first three accounts in the CcipReceive context are mandatory and must follow the exact security pattern shown above, **you must extend this structure with additional program-specific accounts** based on your specific use case.
These additional accounts will be provided when your program is called through the CCIP Offramp. In your `ccip_receive` instruction, you should validate these accounts according to your application's security requirements before using them.
### The `ccip_receive` Instruction
The core instruction that implements the CCIP receiver interface:
```rust
/// This instruction is called by the CCIP Offramp to execute the CCIP message.
/// The method name needs to be ccip_receive with Anchor encoding.
/// If not using Anchor, the discriminator needs to be [0x0b, 0xf4, 0x09, 0xf9, 0x2c, 0x53, 0x2f, 0xf5]
pub fn ccip_receive(ctx: Context, message: Any2SVMMessage) -> Result<()> {
// Process message data
if !message.data.is_empty() {
// Custom data processing logic here
}
// Process token transfers
if !message.token_amounts.is_empty() {
// Custom token handling logic here
}
// Emit event for tracking
emit!(MessageReceived {
message_id: message.message_id,
source_chain_selector: message.source_chain_selector,
sender: message.sender.clone(),
});
Ok(())
}
```
## Security Considerations
Building secure CCIP Receivers requires attention to several key areas:
### Caller Validation
The most critical security aspect is validating that the caller is a legitimate CCIP Offramp. This is handled by the account constraints in the `CcipReceive` context:
```rust
// Offramp CPI signer PDA must be first.
#[account(
seeds = [EXTERNAL_EXECUTION_CONFIG_SEED, crate::ID.as_ref()],
bump,
seeds::program = offramp_program.key(),
)]
pub authority: Signer<'info>,
```
This constraint ensures that the transaction is signed by a PDA derived from the offramp program using a specific seed. Only the legitimate CCIP Offramp can produce this signature.
### Router Authorization
The second critical validation is ensuring that the offramp is authorized by the CCIP Router:
```rust
#[account(
owner = state.router @ CcipReceiverError::InvalidCaller,
seeds = [
ALLOWED_OFFRAMP,
message.source_chain_selector.to_le_bytes().as_ref(),
offramp_program.key().as_ref()
],
bump,
seeds::program = state.router,
)]
pub allowed_offramp: UncheckedAccount<'info>,
```
This validates that:
1. The `allowed_offramp` PDA exists and is owned by the router program
2. The PDA is derived using the correct seeds that include the source chain and offramp program ID
3. This proves the router has authorized this specific offramp for this specific source chain
### Optional Sender Validation
For additional security, you can implement sender validation:
```rust
// Optional additional validation in ccip_receive
pub fn ccip_receive(ctx: Context, message: Any2SVMMessage) -> Result<()> {
// Verify sender is approved (if implementing allowlist)
let is_approved = is_sender_approved(
ctx.accounts.state,
message.source_chain_selector,
&message.sender
);
require!(is_approved, CcipReceiverError::InvalidChainAndSender);
// Continue with message processing...
Ok(())
}
```
### Message Deduplication
To prevent replay attacks, consider tracking processed message IDs:
```rust
// In your ccip_receive instruction:
// Check if message has already been processed
let is_duplicate = ctx.accounts.processed_messages
.messages
.contains(&message.message_id);
require!(!is_duplicate, CcipReceiverError::DuplicateMessage);
// Record the message ID to prevent reprocessing
ctx.accounts.processed_messages.messages.push(message.message_id);
if ctx.accounts.processed_messages.messages.len() > MAX_STORED_MESSAGES {
ctx.accounts.processed_messages.messages.remove(0);
}
```
## Token Handling for Receivers
CCIP receivers that handle tokens need to understand how tokens are delivered and how to properly manage them.
### Token Delivery Process
When tokens are sent via CCIP to a Solana program:
1. The tokens are initially delivered to a token account specified as the `tokenReceiver` in the CCIP message
2. For programmable token transfers, this `tokenReceiver` must be a PDA that your program has authority over
3. Your program must implement the logic to handle the received tokens
### Token Admin PDA
Create a dedicated PDA to serve as your program's token administrator:
```rust
#[account(
init,
seeds = [TOKEN_ADMIN_SEED],
bump,
payer = authority,
space = ANCHOR_DISCRIMINATOR,
)]
/// CHECK: CPI signer for tokens
pub token_admin: UncheckedAccount<'info>,
```
This token_admin PDA should:
1. Be initialized during program setup
2. Be used as the authority for token accounts that will receive CCIP tokens
3. Sign token transfer instructions (e.g., for forwarding tokens to their final destination)
### remaining_accounts
For each token being transferred, your `remaining_accounts` typically needs:
1. `token_mint`: The mint account of the token
2. `source_token_account`: The account that received tokens from CCIP
3. `token_admin`: Your program's PDA with authority over the source account
4. `recipient_token_account`: The final destination for the tokens
5. `token_program`: The SPL Token program (Token or Token-2022)
**Note**: The pattern may vary depending on your specific implementation needs.
## Best Practices
When implementing CCIP Receivers, follow these best practices:
1. **Follow the Security Pattern**: Always use the exact account validation pattern shown in the `CcipReceive` context
2. **Store the Router Address**: Store and validate the router address to ensure only allowed offramps can call your program
3. **Handle Token Security**: Use PDAs with proper token authority for receiving and managing tokens
4. **Consider Message Deduplication**: Track message IDs to prevent replaying the same message
5. **Implement Proper Error Handling**: Use specific error codes and messages for better debugging and security
6. **Use Events for Tracking**: Emit events when processing messages to facilitate off-chain tracking and indexing
7. **Test Thoroughly**: Test your receiver with various message types, token amounts, and edge cases
## Example Implementation
For a complete, audited reference implementation of a CCIP Receiver, you can examine the [example-ccip-receiver](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/example-ccip-receiver) in the Chainlink CCIP repository. This example demonstrates all the security patterns and best practices covered in this guide and can serve as a starting point for your own implementation.
---
# CCIP Tutorials: SVM to EVM
Source: https://docs.chain.link/ccip/tutorials/svm/source
Last Updated: 2025-07-25
This section provides comprehensive guides and tutorials for implementing cross‑chain communication from Solana to Ethereum using Chainlink CCIP.
## Getting Started
Before implementing specific use cases, it's important to set up your environment and understand the fundamental concepts:
- [Building CCIP Messages from SVM to EVM](/ccip/tutorials/svm/source/build-messages) - Learn the core message structure, required parameters, and implementation details for all message types
- [Prerequisites for SVM to EVM Tutorials](/ccip/tutorials/svm/source/prerequisites) - Set up your development environment with Solana CLI tools, wallets, token accounts, and delegations
## Tutorials by Use Case
Depending on your specific needs, choose the appropriate tutorial:
- [Token Transfers](/ccip/tutorials/svm/source/token-transfers) - Send tokens from Solana to EVM chains without executing code on the destination
---
# Building CCIP Messages from SVM to EVM
Source: https://docs.chain.link/ccip/tutorials/svm/source/build-messages
Last Updated: 2025-07-25
## Introduction
This guide explains how to construct CCIP Messages from SVM chains (e.g. Solana) to EVM chains (e.g. Ethereum, Arbitrum, Avalanche, etc.). We'll cover the message structure, required parameters, account management, and implementation details for different message types including token transfers, arbitrary data messaging, and programmable token transfers (data and tokens).
## CCIP Message Structure
CCIP messages from SVM are built using the [`SVM2AnyMessage`](/ccip/api-reference/svm/v1.6.0/messages#svm2anymessage) struct from the CCIP Router program. See the [CCIP Router API Reference](/ccip/api-reference/svm/v1.6.0/router) for complete details. The `SVM2AnyMessage` struct is defined as follows:
```rust
pub struct SVM2AnyMessage {
pub receiver: Vec,
pub data: Vec,
pub token_amounts: Vec,
pub fee_token: Pubkey, // pass zero address if native SOL
pub extra_args: Vec,
}
```
### receiver
- For EVM destinations:
- **This is** the address of the contract or wallet that will receive the message
- **Target** either smart contracts that implement `ccipReceive` function or user wallets for token-only transfers
- **Must be properly formatted** as a 32-byte Solana-compatible byte array
### data
- **Definition**: Contains the payload that will be passed to the receiving contract on the destination chain
- **For token-only transfers**: Must be empty
- **For arbitrary messaging** or **programmable token transfers**: Contains the data the receiver contract will process
- **Encoding consideration**: The receiver on the destination chain must be able to correctly decode this data.
### tokenAmounts
- **Definition**: An array of token addresses and amounts to transfer
- **Each token** is represented by a `SVMTokenAmount` struct that contains:
- **token**: The Solana token mint public key
- **amount**: The amount to transfer in the token's native denomination
- **For data-only messages**: Must be an empty array
- **Note**: Check the [CCIP Directory](/ccip/directory) for the list of supported tokens on each lane
### feeToken
- **Definition**: Specifies which token to use for paying CCIP fees
- **Use** `Pubkey::default()` to pay fees with native SOL
- **Alternatively**, specify a token mint address for fee payment with that token
- **Note**: Check the [CCIP Directory](/ccip/directory) for the list of supported fees tokens for the SVM chain you are using
### extraArgs
For EVM-bound messages, the `extraArgs` field must include properly encoded parameters for:
```rust
struct GenericExtraArgsV2 {
gas_limit: u256,
allow_out_of_order_execution: bool,
}
```
- **gas_limit**: Specifies the amount of gas allocated for calling the receiving contract on the destination chain
- **allow_out_of_order_execution**: Flag that determines if messages can be executed out of order
## Implementation by Message Type
### Token Transfer
Use this configuration when sending only tokens from SVM to EVM chains:
```typescript
const message = {
receiver: evmAddressToSolanaBytes(evmReceiverAddress), // 32-byte padded EVM address
data: new Uint8Array(), // Empty data for token-only transfer
tokenAmounts: [{ token: tokenMint, amount: amount }],
feeToken: feeTokenMint, // Or Pubkey.default() for native SOL
extraArgs: encodeExtraArgs({
gasLimit: 0, // Must be 0 for token-only transfers
allowOutOfOrderExecution: true // Must be true for all messages
})
};
```
### Arbitrary Messaging
Use this configuration when sending only data to EVM chains:
```typescript
const message = {
receiver: evmAddressToSolanaBytes(evmReceiverAddress), // 32-byte padded EVM address
data: messageData, // Encoded data to send
tokenAmounts: [], // Empty array for data-only messages
feeToken: feeTokenMint, // Or Pubkey.default() for native SOL
extraArgs: encodeExtraArgs({
gasLimit: 200000, // Appropriate gas limit for the receiving contract
allowOutOfOrderExecution: true // Must be true for all messages
})
};
```
### Programmable Token Transfer (Data and Tokens)
Use this configuration when sending both tokens and data in a single message:
```typescript
const message = {
receiver: evmAddressToSolanaBytes(evmReceiverAddress), // 32-byte padded EVM address
data: messageData, // Encoded data to send
tokenAmounts: [{ token: tokenMint, amount: amount }],
feeToken: feeTokenMint, // Or Pubkey.default() for native SOL
extraArgs: encodeExtraArgs({
gasLimit: 300000, // Higher gas limit for complex operations
allowOutOfOrderExecution: true // Must be true for all messages
})
};
```
## Understanding the `ccip_send` Instruction
The core of sending CCIP messages from SVM is the `ccip_send` instruction in the CCIP Router program. This instruction requires several key components to be prepared correctly:
### Core Components for `ccip_send`
1. **Destination Chain Selector**: A unique identifier for the target blockchain
2. **Message Structure**: The `SVM2AnyMessage` containing receiver, data, tokens, etc.
3. **Required Accounts**: All accounts needed for the instruction
4. **Token Indices**: For token transfers, indices marking where each token's accounts begin. See [detailed explanation in the API Reference](/ccip/api-reference/svm/v1.6.0/router#how-remaining_accounts-and-token_indexes-work)
### Data Encoding for Cross-Chain Compatibility
When sending data from SVM to EVM chains, proper encoding is crucial:
### Account Requirements
Unlike EVM chains which use a simple mapping storage model, SVM account model requires explicit specification of all accounts needed for an operation. When sending CCIP messages, several account types are needed. For complete account specifications, see the [CCIP Router API Reference](/ccip/api-reference/svm/v1.6.0/router#context-accounts):
## Handling Transaction Size Limits
SVM chains have transaction size limitations that become important when sending CCIP messages:
1. **Account Reference Limit:**
- **SVM transactions** have a limit on how many accounts they can reference
- **Each token transfer** adds approximately 11-12 accounts to your transaction
2. **Address Lookup Tables (ALTs):**
- **ALTs allow** transactions to reference accounts without including full public keys
- **The CCIP Router** requires each token to have an ALT in its Token Admin Registry
- **The CCIP Router** relies on these ALTs to locate the correct Pool Program and other token-specific accounts
3. **Transaction Serialized Size:**
- **Even with ALTs**, SVM transactions have a maximum serialized size (1232 bytes)
- **Each token transfer** increases the transaction size
- **If your transaction** exceeds this limit, you'll need to split it into multiple transactions
## Tracking Messages with Transaction Logs
After sending a CCIP message, the CCIP Router emits a `CCIPMessageSent` event in the transaction logs containing key tracking information:
```
Program log: Event: {
"name": "CCIPMessageSent",
"data": {
"dest_chain_selector": [destination chain ID],
"sequence_number": [sequence number],
"message": {
"header": {
"message_id": "0x123...",
...
}
}
}
}
```
The `message_id` in the event header serves as the unique cross-chain identifier that:
- **Links transactions** between source and destination chains
- **Provides confirmation** of successful message execution
Store this identifier in your application for transaction tracking and reconciliation.
## Further Resources
- [CCIP Router API Reference](/ccip/api-reference/svm/v1.6.0/router): Complete technical details about message structure, account requirements, and token handling
- [CCIP Messages API Reference](/ccip/api-reference/svm/v1.6.0/messages): Comprehensive documentation of all message structures
- [How token_indexes and remaining_accounts Work](/ccip/api-reference/svm/v1.6.0/router#how-remaining_accounts-and-token_indexes-work): Step-by-step explanation with examples
---
# Prerequisites for SVM to EVM Tutorials
Source: https://docs.chain.link/ccip/tutorials/svm/source/prerequisites
Last Updated: 2025-05-19
Before starting the SVM to EVM tutorials, ensure you have:
## Development Environment
- **Anchor and Solana CLI Tools**: Install Anchor and Solana CLI Tools following the [installation guide](https://www.anchor-lang.com/docs/installation). This requires Rust to be installed.
- **Node.js v20 or higher**: You can use the [nvm package](https://www.npmjs.com/package/nvm) to install and switch between Node.js versions. Once installed, you can verify the node version with:
```bash
node -v
```
Example output:
```text
$ node -v
v23.11.0
```
- **Yarn**: For installing and managing dependencies.
- **Git**: For cloning the repository.
## Starter Kit Repository
1. Clone the CCIP Solana Starter Kit:
```bash
git clone https://github.com/smartcontractkit/solana-starter-kit.git && cd solana-starter-kit
```
2. Install dependencies:
```bash
yarn install
```
## Wallets
- **Solana Wallet with Private Key**: You'll need a Solana keypair file. If you don't have one, create it with:
```bash
solana-keygen new --outfile ~/.config/solana/id.json
```
- **Ethereum Wallet Address**: You'll need an Ethereum address as the destination for your cross-chain messages. You don't need the private key for these tutorials since you're only sending to, not from, Ethereum.
## Solana RPC URL
- A Solana Devnet RPC URL. You can use the default public endpoint ([https://api.devnet.solana.com](https://api.devnet.solana.com)) or sign up for a personal endpoint from [Helius](https://www.helius.dev/), [Alchemy](https://www.alchemy.com/), or another node provider service.
### Configure Solana CLI for Devnet
Before proceeding, ensure your Solana CLI is configured to use Devnet:
```bash
# Using the default public endpoint
solana config set --url https://api.devnet.solana.com
# OR using a custom RPC endpoint if you have one
# solana config set --url YOUR_CUSTOM_DEVNET_RPC_URL
```
You can verify your current configuration with:
```bash
solana config get
```
You should see your configured Devnet RPC URL in the output. Example output:
```text
$ solana config get
Config File: /Users//.config/solana/cli/config.yml
RPC URL: https://api.devnet.solana.com
WebSocket URL: wss://api.devnet.solana.com/ (computed)
Keypair Path: /Users//.config/solana/id.json
Commitment: confirmed
```
## Native Tokens for Transaction Fees
**SOL** tokens are used for Solana transaction fees. For these tutorials, we will also use SOL to pay for CCIP fees (though LINK and WSOL are alternative payment options).
- Obtain SOL on Devnet using the airdrop command:
```bash
solana airdrop 3
```
- Example output:
```text
Requesting airdrop of 3 SOL
Signature: 2MiFptKYiJQNfzERzNzB4X1tYeBaW1muJ4oNaUpujeaWmgCiwCZ1ftMyYmg9fAitw2Trbsw8yfBNSanLGX4SUAr7
13.25722618 SOL
```
## Associated Token Accounts (ATAs)
Solana's token model requires an Associated Token Account (ATA) for each token you want to hold. These must be created before you can receive or send tokens.
### Creating ATAs
Create an ATA for each token needed in these tutorials:
```bash
# Create ATA for BnM token (used for cross-chain token transfers)
spl-token create-account 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6
# Create ATA for LINK token (used for CCIP fee payments)
spl-token create-account GAn1RmBY76BjdqPAFnqM4jhqSSeeo9Zf7ASqHoWLdwZb
# Create ATA for Wrapped SOL (used for wrapped native fee payments)
spl-token create-account So11111111111111111111111111111111111111112
```
If using a non-default keypair (other than `~/.config/solana/id.json`), specify it explicitly:
```bash
spl-token create-account --owner
```
### Verifying ATAs
Confirm your ATAs were created successfully:
```bash
spl-token accounts
```
Example output:
```text
Token Balance
---------------------------------------------------------------------------------------------------------------
3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6 0.09
GAn1RmBY76BjdqPAFnqM4jhqSSeeo9Zf7ASqHoWLdwZb 0
So11111111111111111111111111111111111111112 4.440009443
```
## Token Delegation
Before sending CCIP messages with tokens from Solana, you must delegate authority to the CCIP Router's Program Derived Addresses (PDAs). This allows the router to transfer tokens on your behalf when executing cross-chain messages.
### Understanding Token Delegations
In Solana, unlike Ethereum where you approve a smart contract to spend your tokens, you need to:
1. **Delegate to CCIP Router's Fee Billing Signer PDA**: For fee tokens (LINK and Wrapped SOL)
2. **Delegate to CCIP Router's Fee Billing Signer PDA**: For tokens you want to send cross-chain (BnM)
### Automating Token Delegation
The `token:delegate` script automates the token delegation process by:
1. Identifying your token accounts for Wrapped SOL, BnM, and LINK
2. Delegating the maximum possible amount to the CCIP Router's fee-billing PDA (allowing it to use tokens for fees)
3. Providing transaction confirmations and explorer links for verification
Run the delegation script:
```bash
yarn svm:token:delegate
```
After running, verify your token delegations:
```bash
yarn svm:token:check
```
The verification shows:
- Your current token balances
- The delegate address for each token
- Delegation amounts
- Status of each delegation (✓ Correct or ✗ Missing/Invalid)
## Obtaining Testnet Tokens
### BnM Tokens on Solana Devnet
To complete the cross-chain token transfer examples, you'll need BnM tokens:
1. Bridge BnM tokens from Ethereum Sepolia to your Solana Devnet wallet
2. Follow the detailed instructions in the [token transfers tutorial](/ccip/tutorials/svm/destination/token-transfers)
#### Phantom setup
- Enable Testnet Mode: Settings → Developer Settings → Testnet Mode.
- After minting, choose Send in Phantom. The BnM token appears in the token
picker. Select it, paste the destination Solana address, and confirm the
transfer.
### Wrapped SOL (wSOL)
Native SOL must be wrapped into wSOL token format before it can be used with token-based interfaces. To wrap your SOL:
```bash
yarn svm:token:wrap
```
This command wraps 0.1 SOL to wSOL by default. To specify a custom amount:
```bash
yarn svm:token:wrap --amount
```
Example:
```text
$ yarn svm:token:wrap --amount 10000000
...
==== Operation Summary ====
[2025-05-01T20:18:03.134Z] Token: Wrapped SOL (wSOL)
[2025-05-01T20:18:03.134Z] Amount Wrapped: 10000000 lamports
[2025-05-01T20:18:03.134Z] New Balance: 4450009443 lamports
[2025-05-01T20:18:03.134Z]
SOL wrapping completed successfully
```
**Note**: All amounts are specified in lamports (1 SOL = 1,000,000,000 lamports).
---
# Token Transfers: SVM to EVM
Source: https://docs.chain.link/ccip/tutorials/svm/source/token-transfers
Last Updated: 2025-05-19
This tutorial demonstrates how to transfer tokens from a Solana Virtual Machine (SVM) to an Ethereum Virtual Machine (EVM) chain using Chainlink CCIP. You will learn how to build a CCIP message on Solana, send it to the CCIP router, and verify the transfer on the destination chain.
## Introduction
This tutorial covers transferring tokens from Solana Devnet to Ethereum Sepolia without any additional data payload or program execution. When you transfer tokens using CCIP:
1. Tokens are burned or locked in pools on the source pool
2. Equivalent tokens are minted or released from the destination pool
3. The process is managed by CCIP
## What You Will Build
In this tutorial, you will:
- Configure a CCIP message for token-only transfers
- Send BnM test tokens from Solana Devnet to an Ethereum Sepolia address
- Pay for CCIP transaction fees using native SOL
- Monitor and verify your cross-chain transfer
## Understanding Token Transfers from SVM to EVM
This tutorial focuses on token-only transfers from your wallet on Solana Devnet to an address on Ethereum Sepolia. Key points specific to token-only transfers:
- **Burn and Mint**: In this tutorial, tokens are burned on Solana Devnet, and equivalent tokens are minted on Ethereum Sepolia
- **Fee Payment**: Transaction fees are paid in native SOL on Solana plus CCIP fees for cross-chain processing. Note that you can pay for CCIP fees using LINK or wrapped SOL
- **Token Delegation**: Requires pre-delegation of token authority to allow the CCIP Router to transfer your tokens
## CCIP Message Configuration
The most important part of implementing a token transfer is configuring the CCIP message correctly. Here's the key configuration from the script:
```typescript
const CCIP_MESSAGE_CONFIG: CCIPMessageConfig = {
destinationChainSelector: CHAIN_SELECTORS[ChainId.ETHEREUM_SEPOLIA].toString(),
evmReceiverAddress: "0x9d087fC03ae39b088326b67fA3C788236645b717", // Change this to your Ethereum address
// Token transfers configuration - supports multiple tokens
tokenAmounts: [
{
tokenMint: config.tokenMint, // BnM token on Solana Devnet
amount: "10000000", // 0.01 tokens with 9 decimals
},
],
// Fee configuration
feeToken: ConfigFeeTokenType.NATIVE, // Use SOL for fees
// Message data (empty for token transfers)
messageData: "", // Empty data for token transfer only
// Extra arguments configuration
extraArgs: {
gasLimit: 0, // No execution on destination for token transfers
allowOutOfOrderExecution: true, // Allow out-of-order execution
},
}
```
### Customizing the Receiver Address
To send tokens to your own Ethereum wallet, you must update the destination address in the script:
1. Open the file `ccip-scripts/svm/router/1_token-transfer.ts` in the starter kit
2. Locate the `CCIP_MESSAGE_CONFIG` object
3. Edit the value of the `evmReceiverAddress` property to your Ethereum wallet address
### Critical Configuration Settings
When setting up your CCIP message for token transfers, these parameters are crucial:
- `gasLimit`: **MUST** be 0 for token-only transfers
- `evmReceiverAddress`: Must be a valid Ethereum address (starting with "0x")
- `messageData`: Empty string for token-only transfers
### Understanding the Configuration Fields
- **`destinationChainSelector`**: CCIP identifier for the destination chain (Ethereum Sepolia)
- **`evmReceiverAddress`**: Ethereum wallet address that will receive the tokens
- **`tokenAmounts`**: Array of tokens to transfer with their amounts
- `tokenMint`: The Solana token mint address (BnM token: `7AC59PVvR64EoMnLX45FHnJAYzPsxdViyYBsaGEQPFvh`)
- `amount`: Token amount in raw format (10000000 = 0.01 BnM with 9 decimals)
- **`feeToken`**: Token used to pay CCIP fees (NATIVE = SOL)
- **`messageData`**: Empty for token-only transfers
- **`extraArgs`**: Additional configuration for cross-chain execution
- `gasLimit`: Set to 0 for token transfers (no execution needed)
- `allowOutOfOrderExecution`: Set to true to allow out of order execution. **Note**: This is mandatory when sending CCIP messages from Solana as the source chain.
## Customizing Your Token Transfer
### Customize Token Amount
The token amount format needs to account for token decimals:
- Solana BnM has 9 decimals
- `10000000` represents 0.01 BnM (10,000,000 / 10^9)
## How the Script Works
The token transfer script handles the interaction with the CCIP Router program without requiring you to write any code. Here's what happens behind the scenes:
1. **Context Initialization**:
- The script initializes the Solana connection
- Loads your keypair
- Sets up CCIP configuration
2. **Balance Validation**:
- Checks SOL balance (minimum 0.005 SOL required)
- Validates token balances for all tokens being transferred
- Verifies token delegation approvals
3. **Fee Calculation**:
- Queries the Router program to calculate cross-chain fees
- Validates sufficient balance for fees
4. **Message Construction**:
- Builds the `SVM2AnyMessage` structure with token amounts, receiver, etc.
- Encodes the message according to CCIP standards
- Sets extra arguments for Ethereum compatibility
5. **Transaction Building**:
- Builds the CCIP send transaction with all necessary accounts
- Includes all token accounts in the correct order
6. **Execution**:
- Signs and sends the transaction to the Solana Devnet network
- Parses the transaction result to extract the message ID
- Provides links to track the cross-chain message
## Source Code Reference
For those interested in implementation details, the script is thoroughly documented with step-by-step comments. You can examine the complete source code at `ccip-scripts/svm/router/1_token-transfer.ts` in the starter kit repository to understand the precise mechanics of building and sending CCIP messages from Solana.
## Running the Token Transfer
### Prerequisites Check
Before running the script:
1. Ensure you've completed all steps in the [prerequisites guide](/ccip/tutorials/svm/source/prerequisites)
2. Verify your token delegations are set up correctly: `yarn svm:token:check`
3. Make sure you have sufficient SOL and BnM token balances
### Execute the Script
Run the script using one of the following options:
```bash
yarn svm:token-transfer
```
### Understanding the Output
When the script executes successfully, you'll see output similar to this:
```
==== Environment Information ====
[2025-05-01T20:46:55.790Z] Wallet public key: EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB
[2025-05-01T20:46:55.791Z]
==== Wallet Balance Information ====
[2025-05-01T20:46:59.331Z] SOL Balance: 13.224536955 SOL
[2025-05-01T20:46:59.332Z] Lamports Balance: 13224536955 lamports
[2025-05-01T20:46:59.332Z]
==== CCIP Router Information ====
[2025-05-01T20:46:59.332Z] CCIP Router Program ID: Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C
[2025-05-01T20:46:59.332Z] Fee Quoter Program ID: FeeQPGkKDeRV1MgoYfMH6L8o3KeuYjwUZrgn4LRKfjHi
[2025-05-01T20:46:59.332Z] RMN Remote Program ID: RmnXLft1mSEwDgMKu2okYuHkiazxntFFcZFrrcXxYg7
[2025-05-01T20:46:59.332Z]
==== CCIP Send Configuration ====
[2025-05-01T20:46:59.332Z] Destination Chain Selector: 16015286601757825753
[2025-05-01T20:46:59.332Z] Receiver Address: 0x9d087fC03ae39b088326b67fA3C788236645b717
[2025-05-01T20:46:59.332Z]
==== Token Transfer Details ====
[2025-05-01T20:46:59.333Z] Getting mint account info for 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6 to determine token program ID...
[2025-05-01T20:46:59.467Z] Detected Token-2022 Program: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
[2025-05-01T20:46:59.467Z] Fetching token decimals for 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6
[2025-05-01T20:46:59.607Z] Token 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6 has 9 decimals
[2025-05-01T20:46:59.639Z] Token 1: 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6
[2025-05-01T20:46:59.639Z] Amount 1: 10000000 raw units (0,01 tokens with 9 decimals)
[2025-05-01T20:46:59.639Z] Fee Token: native
[2025-05-01T20:46:59.639Z]
==== Fee Token Configuration ====
[2025-05-01T20:46:59.639Z] Using native SOL as fee token
[2025-05-01T20:46:59.639Z] No gasLimit provided in extraArgs, using default value: 0
[2025-05-01T20:46:59.641Z]
==== CCIP Message Request Created ====
[2025-05-01T20:46:59.641Z]
==== Compute Budget Configuration ====
[2025-05-01T20:46:59.642Z] Added compute budget instruction with limit: 1400000 units
[2025-05-01T20:46:59.642Z]
==== Fee Calculation ====
[2025-05-01T20:46:59.642Z] Calculating fee for this transaction...
[2025-05-01T20:46:59.642Z] Calculating fee for destination chain 16015286601757825753
[2025-05-01T20:46:59.950Z] Fee calculation complete: 9716055 tokens
[2025-05-01T20:46:59.950Z] Estimated fee: 0.009716055 SOL
[2025-05-01T20:46:59.951Z] Fee in Juels: 97863982000000000
[2025-05-01T20:46:59.951Z]
==== Balance Validation ====
[2025-05-01T20:47:00.085Z] SOL Balance: 13.224536955 SOL
[2025-05-01T20:47:00.085Z]
==== Token Balance Validation ====
[2025-05-01T20:47:00.085Z] Getting mint account info for 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6 to determine token program ID...
[2025-05-01T20:47:00.220Z] Detected Token-2022 Program: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
[2025-05-01T20:47:00.220Z] Fetching token decimals for 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6
[2025-05-01T20:47:00.358Z] Token 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6 has 9 decimals
[2025-05-01T20:47:00.359Z] Validating token 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6: 0,01 tokens (10000000 raw units)
[2025-05-01T20:47:00.509Z] ✅ Balance validation passed for token: 3PjyGzj1jGVgHSKS4VR1Hr1memm63PmN8L9rtPDKwzZ6
[2025-05-01T20:47:00.510Z] ✅ All balance validations passed. Proceeding with transaction.
[2025-05-01T20:47:00.511Z]
==== Sending CCIP Message ====
[2025-05-01T20:47:00.511Z] ⏳ This may take a minute...
[2025-05-01T20:47:00.512Z] Sending CCIP message to destination chain 16015286601757825753
[2025-05-01T20:47:00.512Z] Building accounts for CCIP send to chain 16015286601757825753
[2025-05-01T20:47:02.693Z] CCIP message sent successfully: 51Tpaa5Hxu4dwRNeHr7wqPwkXmroq1bDKSUmVHHPtNbDEhJ6b7Sf9Wkzuau9qDDfh34MKDWPtuMRaq3WGdt9Qz3L
[2025-05-01T20:47:02.694Z] Parsing CCIP message sent event for transaction: 51Tpaa5Hxu4dwRNeHr7wqPwkXmroq1bDKSUmVHHPtNbDEhJ6b7Sf9Wkzuau9qDDfh34MKDWPtuMRaq3WGdt9Qz3L
[2025-05-01T20:47:02.863Z] Successfully extracted messageId: 0xf5a11db94f15b15b77d582e4a0ed3ff709b2fbf2464d1c7d1ed73620000260d9
[2025-05-01T20:47:02.863Z]
==== CCIP Message Sent Successfully ====
[2025-05-01T20:47:02.863Z] Transaction signature: 51Tpaa5Hxu4dwRNeHr7wqPwkXmroq1bDKSUmVHHPtNbDEhJ6b7Sf9Wkzuau9qDDfh34MKDWPtuMRaq3WGdt9Qz3L
[2025-05-01T20:47:02.863Z] Message ID: 0xf5a11db94f15b15b77d582e4a0ed3ff709b2fbf2464d1c7d1ed73620000260d9
[2025-05-01T20:47:02.863Z] Open the CCIP explorer: https://ccip.chain.link/msg/0xf5a11db94f15b15b77d582e4a0ed3ff709b2fbf2464d1c7d1ed73620000260d9
[2025-05-01T20:47:02.863Z]
View transaction on explorer:
[2025-05-01T20:47:02.863Z] https://explorer.solana.com/tx/51Tpaa5Hxu4dwRNeHr7wqPwkXmroq1bDKSUmVHHPtNbDEhJ6b7Sf9Wkzuau9qDDfh34MKDWPtuMRaq3WGdt9Qz3L?cluster=devnet
```
The output provides important information to track your transfer:
- **Environment Information**: Your Solana configuration and wallet details
- **Wallet Balance**: Confirms you have sufficient SOL
- **CCIP Router Information**: The program IDs used for the transaction
- **Token Transfer Details**: The tokens and amounts being transferred
- **Fee Calculation**: The estimated fees of the cross-chain transfer
- **Balance Validation**: Confirmation that all required balances are sufficient
- **Transaction Details**: The signature, message ID, and explorer links
## Verification and Monitoring
After sending your token transfer, you'll need to track it across chains:
1. **Track progress with Message ID**: Use the CCIP Explorer link provided in the output to track your message status:
```
https://ccip.chain.link/msg/YOUR_MESSAGE_ID
```
2. **Solana Explorer**: Verify the transaction on Solana:
```
https://explorer.solana.com/tx/YOUR_TRANSACTION_SIGNATURE?cluster=devnet
```
3. **Ethereum Explorer**: Once the transfer is successful, verify token receipt on Ethereum:
```
https://sepolia.etherscan.io/address/YOUR_ETHEREUM_ADDRESS
```
Look for the transferred token in the "Token Transfers" section.
---
# CCIP Tutorials: EVM to SVM
Source: https://docs.chain.link/ccip/tutorials/svm/destination
Last Updated: 2025-07-25
This section provides comprehensive guides and tutorials for implementing cross‑chain communication from Ethereum (EVM) to Solana using Chainlink CCIP.
## Getting Started
Before implementing specific use cases, it's important to understand the fundamental concepts and message structure for EVM to SVM communication:
- [Building CCIP Messages from EVM to Solana](/ccip/tutorials/svm/destination/build-messages) - Learn the core message structure, required parameters, and implementation details for all message types
## Tutorials by Use Case
Depending on your specific needs, choose the appropriate tutorial:
- [Token Transfers](/ccip/tutorials/svm/destination/token-transfers) - Send tokens from EVM chains to Solana wallets without program execution
- [Arbitrary Messaging](/ccip/tutorials/svm/destination/arbitrary-messaging) - Send data from EVM chains to trigger program execution on Solana
---
# Building CCIP Messages from EVM to SVM
Source: https://docs.chain.link/ccip/tutorials/svm/destination/build-messages
Last Updated: 2025-07-25
## Introduction
This guide explains how to construct CCIP Messages from Ethereum Virtual Machine (EVM) chains (e.g. Ethereum) to SVM chains (e.g. Solana). We'll cover the message structure, required parameters, and implementation details for different message types including token transfers, arbitrary data messaging, and programmable token transfers (data and tokens).
## CCIP Message Structure
CCIP messages are built using the [`EVM2AnyMessage`](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) struct from the [`Client.sol`](/ccip/api-reference/evm/v1.6.2/client) library. The `EVM2AnyMessage` struct is defined as follows:
```solidity
struct EVM2AnyMessage {
bytes receiver;
bytes data;
EVMTokenAmount[] tokenAmounts;
address feeToken;
bytes extraArgs;
}
```
### receiver
- **Definition**: The receiver field specifies which program on the destination chain will process the incoming CCIP message.
- **Token-only transfers**:
- **Use**: `0x0000000000000000000000000000000000000000000000000000000000000000` (32-byte zero address)
- **Why**: No program execution needed for token-only transfers.
- **Arbitrary Messaging** or **Programmable Token Transfers**:
- **Use**: The program ID of the SVM program that implements the `ccip_receive` instruction, converted to a 32-byte hex format.
- **Why**: The program will process the incoming CCIP message and execute the `ccip_receive` instruction.
### data
- **Definition**: Contains the payload that will be passed to the receiving program
- **For token-only transfers**: Empty (`0x`)
- **For arbitrary messaging** or **programmable token transfers**: Contains the data the receiver program will process
- **Encoding requirement**: Must be encoded as a hex string with `0x`· prefix
### tokenAmounts
- **Definition**: An array of token addresses and amounts to transfer
- **For data-only messages**: Must be an empty array
- **For token transfers** or **programmable token transfers**: Each entry specifies a token address and amount. **Note**: Check the [CCIP Directory](/ccip/directory) for the list of supported tokens on each lane
### feeToken
- **Definition**: Specifies which token to use for paying CCIP fees
- **For native gas token**: Use `address(0)` to specify the source chain's native gas token (e.g. ETH on Ethereum)
- **For ERC-20 tokens**: Can specify an ERC-20 token address for fee payment. **Note**: Check the [CCIP Directory](/ccip/directory) for the list of supported fee tokens on your source chain
## extraArgs
The most critical component for SVM-bound messages is the [`SVMExtraArgsV1`
](/ccip/api-reference/evm/v1.6.2/client#svmextraargsv1) structure:
```solidity
struct SVMExtraArgsV1 {
uint32 computeUnits;
uint64 accountIsWritableBitmap;
bool allowOutOfOrderExecution;
bytes32 tokenReceiver;
bytes32[] accounts;
}
bytes4 public constant SVM_EXTRA_ARGS_V1_TAG = 0x1f3b3aba;
```
Let's examine each field in detail:
### computeUnits
Specifies the amount of compute units allowed for calling the `ccip_receive` instruction of the receiver program on SVM (similar to gas limit on EVM chains).
### Understanding SVM Account Model
Unlike EVM chains where smart contracts manage their own storage, SVM blockchains (e.g. Solana) use an account-based architecture where:
- **All data is stored in accounts**: There's no dedicated storage for programs
- **Programs are stateless**: They can only read from and write to accounts passed to them
- **Explicit account access**: Every account a program needs to access must be explicitly provided
- **Access permissions**: Each account must be marked as either *readable* or *writable*
#### accounts
An array of 32-byte Solana public keys representing additional accounts required for execution.
#### accountIsWritableBitmap
A 64-bit bitmap indicating which accounts in the `accounts` array should be marked as writable.
### allowOutOfOrderExecution
**MUST** be set to `true` for SVM as a destination chain.
### tokenReceiver
The Solana account that will initially receive tokens, represented as a 32-byte hex-encoded Solana public key.
## Message Encoding Requirements
When implementing CCIP from EVM to SVM, proper encoding of various elements is crucial for successful message delivery and processing.
## Implementation by Message Type
### Token Transfer
Use this configuration when sending only tokens from EVM to Solana:
### Arbitrary Messaging
Use this configuration when sending only data messages to SVM:
### Programmable Token Transfer (Data and Tokens)
Use this configuration when sending both tokens and data in a single message:
## Related Tutorials
To see these concepts in action with step-by-step implementation guides, check out the following tutorials:
- [Token Transfers: EVM to SVM](/ccip/tutorials/svm/destination/token-transfers) - Learn how to implement token-only transfers from EVM chains to Solana wallets
- [Arbitrary Messaging: EVM to SVM](/ccip/tutorials/svm/destination/arbitrary-messaging) - Learn how to send data messages from EVM chains to Solana programs
These tutorials provide complete, working examples using the concepts covered in this guide.
---
# Token Transfers: EVM to SVM
Source: https://docs.chain.link/ccip/tutorials/svm/destination/token-transfers
Last Updated: 2025-05-19
This tutorial demonstrates how to transfer tokens from an Ethereum Virtual Machine (EVM) chain to a Solana wallet using Chainlink CCIP. You will learn how to build a CCIP message on an EVM chain, send it to the CCIP router, and verify the transfer on the destination chain.
## Introduction
This tutorial covers transferring tokens from Ethereum Sepolia to a Solana wallet without any additional data payload or program execution.
## What You will Build
In this tutorial, you will:
- Configure a CCIP message for token-only transfers
- Send CCIP-BnM test tokens from Ethereum Sepolia to a Solana wallet
- Pay for CCIP transaction fees using LINK tokens
- Monitor and verify your cross-chain transfer
## Prerequisites
Before starting EVM to SVM tutorials, ensure you have:
### Development Environment
- Node.js v20 or higher. You can use the [nvm package](https://www.npmjs.com/package/nvm) to install and switch between Node.js versions. Once installed, you can verify the installation by running `node -v` in your terminal:
```bash
node -v
```
Example output:
```bash
$ node -v
v23.11.0
```
- Git for cloning the repository.
- [Yarn](https://yarnpkg.com/) for installing dependencies.
- **Anchor and Solana CLI Tools**: Install Anchor and Solana CLI Tools following the [installation guide](https://www.anchor-lang.com/docs/installation). This requires Rust to be installed.
### Wallets
- An Ethereum wallet with a private key. If you use MetaMask, you can follow this [guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/) to export your private key.
- A Solana wallet address if you are testing token transfers. If you don't have one, you can generate a new Solana keypair file using the Solana CLI:
```bash
solana-keygen new --outfile ~/.config/solana/id.json
```
Get your wallet address by running:
```bash
solana address
```
Example output:
```bash
$ solana address
EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB
```
### RPC URLs
Get an Ethereum RPC URL. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service.
### Setup Instructions
- Clone the repository:
```bash
git clone https://github.com/smartcontractkit/solana-starter-kit.git && cd solana-starter-kit
```
- Install dependencies:
```bash
yarn install
```
- Create a `.env` file in the project root based on the provided example:
```
EVM_PRIVATE_KEY=your_private_key_here
EVM_RPC_URL=your_rpc_url_here
```
- Replace the `EVM_PRIVATE_KEY` and `EVM_RPC_URL` values with your own Ethereum private key and RPC URL that you obtained in the previous steps.
### Tokens for Testing
You'll need the following tokens on Ethereum Sepolia testnet to complete the tutorials:
#### Required Tokens
| Token | Purpose | How to Obtain |
| -------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ETH | Transaction gas fees | [Chainlink Faucet](https://faucets.chain.link/) or alternative sources such as the [Google Cloud Faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia) |
| LINK | CCIP fees | [Chainlink Faucet](https://faucets.chain.link/) |
| CCIP-BnM | Token transfer examples | Use the command below |
#### Getting CCIP-BnM Tokens
Run the following command in your terminal to get 1 (18 decimals) CCIP-BnM token:
```bash
yarn evm:token:drip
```
You should see output similar to this:
```text
$ yarn evm:token:drip
...
[drip-tokens] [INFO] [1/1] Executing operation...
[drip-tokens] [INFO] Dripping tokens to 0x9d087fC03ae39b088326b67fA3C788236645b717
[drip-tokens] [INFO] ✓ Operation 1/1 successful (Block: 8235790, Gas: 33845)
[drip-tokens] [INFO] Batch operation completed: 1/1 successful
[drip-tokens] [INFO] Final CCIP-BnM balance: 25.129
[drip-tokens] [INFO] Total CCIP-BnM gained: 1.0
[drip-tokens] [INFO]
==== Results ====
[drip-tokens] [INFO] Operations completed: 1/1
[drip-tokens] [INFO] Initial Balance: 24.129 CCIP-BnM
[drip-tokens] [INFO] Final Balance: 25.129 CCIP-BnM
[drip-tokens] [INFO] Gained: 1.0 CCIP-BnM
[drip-tokens] [INFO]
🎉 Drip operations completed!
```
## Understanding Token Transfers to SVM
This tutorial focuses on token-only transfers from EVM chains to Solana wallets. For detailed information about CCIP message structure and parameters, refer to the [guide on building CCIP messages from EVM to SVM](/ccip/tutorials/svm/destination/build-messages).
Key points specific to token-only transfers:
- **No Program Execution**: Tokens are transferred directly to a wallet without program execution
- **Mandatory Settings**:
- `computeUnits` **MUST** be set to 0
- `receiver` field should be set to the default PublicKey (`11111111111111111111111111111111`)
- The actual recipient is specified in the `tokenReceiver` field
- `data` field should be empty (`0x`)
- `allowOutOfOrderExecution` must be `true`
For more details on the CCIP message structure, `extraArgs`, and how the SVM account model works, refer to the [guide on building CCIP messages from EVM to SVM](/ccip/tutorials/svm/destination/build-messages).
## Implementing Token Transfers
In this section, you'll implement a token transfer from Ethereum Sepolia to Solana Devnet using the example script located at `ccip-scripts/evm/router/1_token-transfer.ts` in the starter kit.
### Token Transfer Configuration
The most important part of implementing a token transfer is configuring the CCIP message correctly. Here's the key configuration from the script:
```typescript
const MESSAGE_CONFIG = {
tokenAmounts: [
{
address: config.tokenAddress, // BnM token on Ethereum Sepolia
amount: "1000000000000000", // 0.001 tokens with 18 decimals
},
],
feeToken: FeeTokenType.LINK, // LINK is used for CCIP fees
data: "0x", // Empty for token transfers
extraArgs: {
computeUnits: 0, // No execution on destination - MUST be 0
allowOutOfOrderExecution: true, // MUST be true for SVM destinations
accountIsWritableBitmap: BigInt(0), // No accounts needed
tokenReceiver: "EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB", // Recipient's Solana wallet - Change this to your own wallet address
accounts: [], // No accounts needed for token-only transfers
},
receiver: PublicKey.default.toString(), // Use default PublicKey for token transfers
}
```
### How the Script Works
The token transfer script handles the complete cross-chain transfer process without requiring you to write any code. The script:
1. Configures the CCIP message with the parameters shown above
2. Calculates the CCIP fees required for the transfer
3. Approves the router to spend your CCIP-BnM tokens (the tokens being transferred)
4. Approves the router to spend your LINK tokens (for paying CCIP fees)
5. Calls the CCIP router contract to execute the cross-chain transfer
6. Returns the transaction details and message ID for tracking
For those interested in implementation details, the script is well-commented and follows a straightforward flow. You can review the source code at `ccip-scripts/evm/router/1_token-transfer.ts` in the starter kit repository.
## Running the Token Transfer
### Prerequisites Check
1. Ensure you've completed the setup steps outlined earlier
2. Make sure your `.env` file contains the required values
### Execute the Script
Run the following command after modifying the script directly:
```bash
yarn evm:transfer
```
### Understanding the Output
When the script executes successfully, you'll see output similar to this:
```
==== Environment Information ====
chainId ethereum-sepolia
[token-transfer] [INFO] Router Address: 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
chainId ethereum-sepolia
[ccip-messenger] [INFO] Creating client for chain: Ethereum Sepolia (ethereum-sepolia)
[token-transfer] [INFO] Wallet Address: 0x9d087fC03ae39b088326b67fA3C788236645b717
[token-transfer] [INFO] Native Balance: 600.023652720198652224 ETH
[token-transfer] [INFO] Token: CCIP-BnM (0x316496C5dA67D052235B9952bc42db498d6c520b)
[token-transfer] [INFO] Token Balance: 25.129 CCIP-BnM
[token-transfer] [INFO] Transfer Amount: 0.1 CCIP-BnM
[token-transfer] [INFO] Creating CCIP message request
[token-transfer] [INFO] Using fee token: 0x779877A7B0D9E8603169DdbD7836e478b4624789
[token-transfer] [INFO]
==== Transfer Summary ====
[token-transfer] [INFO] Source Chain: Ethereum Sepolia
[token-transfer] [INFO] Destination Chain: Solana Devnet (16423721717087811551)
[token-transfer] [INFO] Sender: 0x9d087fC03ae39b088326b67fA3C788236645b717
[token-transfer] [INFO] Receiver: 11111111111111111111111111111111
[token-transfer] [INFO] Token Receiver: EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB
[token-transfer] [INFO] Fee Token: 0x779877A7B0D9E8603169DdbD7836e478b4624789
[token-transfer] [INFO]
Token Transfers:
[token-transfer] [INFO] 1. 0.1 CCIP-BnM (0x316496C5dA67D052235B9952bc42db498d6c520b)
[token-transfer] [INFO]
Extra Args: Solana-specific, 458 bytes
[token-transfer] [INFO]
Sending CCIP message...
[ccip-messenger] [INFO] Estimated fee: 27526004358896610
[ccip-messenger] [INFO] Approving 0.033031205230675932 LINK for CCIP Router
[ccip-messenger] [INFO] This token is being used as the fee token with a 20% buffer included
[ccip-messenger] [INFO] Approving 33031205230675932 tokens for 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
[ccip-messenger] [INFO] LINK approved for CCIP Router
[ccip-messenger] [INFO] ✅ Verified on-chain allowance for LINK: 0.033031205230675932 (required: 0.033031205230675932)
[ccip-messenger] [INFO] Approving 0.1 CCIP-BnM for CCIP Router
[ccip-messenger] [INFO] Approving 100000000000000000 tokens for 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
[ccip-messenger] [INFO] CCIP-BnM approved for CCIP Router
[ccip-messenger] [INFO] ✅ Verified on-chain allowance for CCIP-BnM: 0.1 (required: 0.1)
[ccip-messenger] [INFO] Sending CCIP message...
[ccip-messenger] [INFO] Sending CCIP message...
[ccip-messenger] [INFO] Transaction sent: 0x0c90bf80c1f7ff161adbe0846df5026bf78944a3aa46019d97a05bda2b94f014
[ccip-messenger] [INFO] Transaction sent: 0x0c90bf80c1f7ff161adbe0846df5026bf78944a3aa46019d97a05bda2b94f014
[ccip-messenger] [INFO] Message ID: 0x29ec89a867da8ebde045f30bddd663396a4abc0e8374ac2d8083612805fa7fb4
[token-transfer] [INFO]
==== Transfer Results ====
[token-transfer] [INFO] Transaction Hash: 0x0c90bf80c1f7ff161adbe0846df5026bf78944a3aa46019d97a05bda2b94f014
[token-transfer] [INFO] Transaction URL: https://sepolia.etherscan.io/tx/0x0c90bf80c1f7ff161adbe0846df5026bf78944a3aa46019d97a05bda2b94f014
[token-transfer] [INFO] Message ID: 0x29ec89a867da8ebde045f30bddd663396a4abc0e8374ac2d8083612805fa7fb4
[token-transfer] [INFO] CCIP Explorer: https://ccip.chain.link/msg/0x29ec89a867da8ebde045f30bddd663396a4abc0e8374ac2d8083612805fa7fb4
[token-transfer] [INFO] Destination Chain Selector: 16423721717087811551
[token-transfer] [INFO] Sequence Number: 12
[token-transfer] [INFO]
Message tracking for Solana destinations:
[token-transfer] [INFO] Please check the CCIP Explorer link to monitor your message status.
[token-transfer] [INFO]
✅ Transaction completed on the source chain
```
The output provides important information to track your transfer:
- Transaction hash and URL for viewing on Etherscan
- Message ID for tracking in the CCIP Explorer. In this example, the message ID is `0x29ec89a867da8ebde045f30bddd663396a4abc0e8374ac2d8083612805fa7fb4`.
## Behind the Scenes: Message Encoding for Token Transfers
While this tutorial focuses on configuration and execution, it's important to understand that the script uses several utility functions to properly encode CCIP messages. If you're implementing your own token transfer solution outside this starter kit, you will need to handle these encoding steps yourself.
Key utilities used by the script include:
- `createCCIPMessageRequest()`: Creates a properly formatted CCIP message with all required fields
- `validateTokenAmounts()`: Checks token balances and transaction validity
- `getTokenDetails()`: Fetches token metadata for the proper display of amounts
- `setupClientContext()`: Sets up connections to the blockchain with the right configuration
These utilities handle critical tasks such as:
- Converting token amounts between decimal and raw formats
- Properly encoding SVM wallet addresses for token receipt
- Setting up the required ExtraArgs structure for SVM destinations
- Managing token approvals and fee calculations
If you're building your own implementation, we recommend examining the source code in:
- `ccip-scripts/evm/utils/message-utils.ts`
- `ccip-scripts/evm/utils/setup-client.ts`
- `ccip-lib/evm/core/client/CCIPMessenger.ts`
Understanding these utilities will help you implement your own cross-chain token transfers correctly.
## Verification and Monitoring
After sending your token transfer, you'll need to:
1. **Track progress with Message ID**: Use the CCIP Explorer link provided in the output to track your message status across chains. You must wait for the transfer to be successful before proceeding to verification.
2. **Solana Explorer**: Once the transfer is successful, verify token receipt on the Solana Explorer:
- Visit [Solana Explorer](https://explorer.solana.com/?cluster=devnet)
- Search for your Solana wallet address
- Look for the transferred token in the "Tokens" section
---
# Arbitrary Messaging: EVM to SVM
Source: https://docs.chain.link/ccip/tutorials/svm/destination/arbitrary-messaging
Last Updated: 2025-05-19
This tutorial demonstrates how to send arbitrary data from an Ethereum Virtual Machine (EVM) chain to a program on a SVM chain using Chainlink's Cross-Chain Interoperability Protocol (CCIP). You will learn how to configure CCIP messages that trigger a program execution on the destination chain.
## Introduction
This tutorial shows you how to send data-only messages from Ethereum Sepolia to a receiver program on Solana devnet.
## What You will Build
In this tutorial, you will:
- Configure a CCIP message for arbitrary data messaging
- Send data from Ethereum Sepolia to a Solana program
- Pay for CCIP transaction fees using LINK tokens
- Verify the data was received and processed by the program
## Prerequisites
Before starting EVM to SVM tutorials, ensure you have:
### Development Environment
- Node.js v20 or higher. You can use the [nvm package](https://www.npmjs.com/package/nvm) to install and switch between Node.js versions. Once installed, you can verify the installation by running `node -v` in your terminal:
```bash
node -v
```
Example output:
```bash
$ node -v
v23.11.0
```
- Git for cloning the repository.
- [Yarn](https://yarnpkg.com/) for installing dependencies.
- **Anchor and Solana CLI Tools**: Install Anchor and Solana CLI Tools following the [installation guide](https://www.anchor-lang.com/docs/installation). This requires Rust to be installed.
### Wallets
- An Ethereum wallet with a private key. If you use MetaMask, you can follow this [guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/) to export your private key.
### RPC URLs
Get an Ethereum RPC URL. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service.
### Setup Instructions
- Clone the repository:
```bash
git clone https://github.com/smartcontractkit/solana-starter-kit.git && cd solana-starter-kit
```
- Install dependencies:
```bash
yarn install
```
- Create a `.env` file in the project root based on the provided example:
```
EVM_PRIVATE_KEY=your_private_key_here
EVM_RPC_URL=your_rpc_url_here
```
- Replace the `EVM_PRIVATE_KEY` and `EVM_RPC_URL` values with your own Ethereum private key and RPC URL that you obtained in the previous steps.
### Tokens for Testing
You'll need the following tokens on Ethereum Sepolia testnet to complete the tutorials:
| Token | Purpose | How to Obtain |
| ----- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ETH | Transaction gas fees | [Chainlink Faucet](https://faucets.chain.link/) or alternative sources such as the [Google Cloud Faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia) |
| LINK | CCIP fees | [Chainlink Faucet](https://faucets.chain.link/) |
## Understanding Arbitrary Messaging to SVM
This tutorial focuses on arbitrary messaging from EVM chains to SVM programs. For detailed information about CCIP message structure and parameters, refer to the [guide on building CCIP messages from EVM to SVM](/ccip/tutorials/svm/destination/build-messages).
### Key Points Specific to Arbitrary Messaging
- **Program Execution**: Messages trigger program execution on the destination chain
- **Mandatory Settings**:
- `computeUnits` must be sufficient for the execution of `ccip_receive` instruction of the receiver program
- `receiver` field should be set to the Solana program ID
- `tokenReceiver` is typically set to the default PublicKey (`11111111111111111111111111111111`)
- Required accounts must be specified in the `accounts` array
- `accountIsWritableBitmap` must correctly identify which accounts should be writable
- `allowOutOfOrderExecution` must be `true`
For more details on the CCIP message structure, `extraArgs`, and how the SVM account model works, refer to the [guide on building CCIP messages from EVM to SVM](/ccip/tutorials/svm/destination/build-messages).
### Key Differences from Token Transfers
### The CCIP Basic Receiver Program
This tutorial uses the [Basic Receiver program](https://github.com/smartcontractkit/solana-starter-kit/tree/main/programs/ccip-basic-receiver) deployed on [Solana Devnet](https://explorer.solana.com/address/BqmcnLFSbKwyMEgi7VhVeJCis1wW26VySztF34CJrKFq?cluster=devnet) as the destination program.
#### Program Interface Overview
The Basic Receiver program implements the CCIP receiver interface, which requires a `ccip_receive` instruction. This instruction is called by the CCIP offramp when a cross-chain message arrives. Key aspects include:
- The program verifies the caller (must be the trusted CCIP offramp)
- It deserializes and validates the incoming message
- The message data is stored in the program's storage PDA
- The program maintains a message counter to track received messages
- Received messages can be accessed using helper methods provided in the program
#### Understanding Program Derived Addresses (PDAs)
To interact with SVM programs, you need to understand Program Derived Addresses (PDAs):
- PDAs are deterministic addresses derived from seeds and a program ID
- They provide onchain storage for Solana programs
- Each PDA serves a specific purpose
- When sending messages to a program, you must specify all accounts (including PDAs) that the program requires
For the CCIP Basic Receiver program, we need two main PDAs:
1. **State PDA**:
- Contains essential program settings like the router address and owner
- Derived using the seed "state"
- Controlled by the program owner/authority
- The State PDA is critical for validating incoming messages in the `ccip_receive` instruction. It stores the trusted router program ID, which is used to authenticate the caller
- When a message is received, the program checks if the caller is an authorized CCIP router by validating against the router address stored in this PDA
2. **Messages Storage PDA**:
- Contains only the most recent cross-chain message
- Derived using the seed "messages"
- Maintains a message counter and last updated timestamp
- Stores message such as the message ID, type, and data payload
## Implementing Arbitrary Messaging
In this section, we'll examine how arbitrary messaging works in the example script from the starter kit repository. This will help you understand how to send messages to the CCIP Basic Receiver program on Solana Devnet.
### Deriving Program Derived Addresses
The script first derives the PDAs required by the receiver program. This is a critical step because the program can only process messages if the correct PDAs are provided:
```typescript
function deriveReceiverPDAs(receiverProgramIdStr: string) {
const receiverProgramId = new PublicKey(receiverProgramIdStr)
const STATE_SEED = Buffer.from("state")
const MESSAGES_STORAGE_SEED = Buffer.from("messages_storage")
const [statePda] = PublicKey.findProgramAddressSync([STATE_SEED], receiverProgramId)
const [messagesStoragePda] = PublicKey.findProgramAddressSync([MESSAGES_STORAGE_SEED], receiverProgramId)
return {
state: statePda,
messagesStorage: messagesStoragePda,
}
}
// Get the receiver program ID for Solana Devnet
const receiverProgramId = getCCIPSVMConfig(ChainId.SOLANA_DEVNET).receiverProgramId.toString()
// Derive the PDAs for the receiver program
const pdas = deriveReceiverPDAs(receiverProgramId)
```
### Configuring the Message
The script then configures the CCIP message with all necessary parameters for arbitrary messaging:
```typescript
const MESSAGE_CONFIG = {
// Custom message to send - must be properly encoded as hex with 0x prefix
// This example encodes "Hello World" to hex
data: "0x48656c6c6f20576f726c64", // "Hello World" in hex
// Destination program on Solana that will receive the message
receiver: receiverProgramId,
// Fee token to use for CCIP fees
feeToken: FeeTokenType.LINK,
// No tokens are transferred with this message
tokenAmounts: [],
// Extra configuration for Solana
extraArgs: {
// Compute units for Solana execution
// Needed because message processing requires compute units
computeUnits: 200000,
// Allow out-of-order execution - MUST be true for Solana
allowOutOfOrderExecution: true,
// Binary 10 (decimal 2) means only the messages storage account is writable
accountIsWritableBitmap: BigInt(2),
// Token receiver - for arbitrary messages, this is usually the default PublicKey
tokenReceiver: PublicKey.default.toString(),
// Accounts required by the receiver program
accounts: [pdas.state.toString(), pdas.messagesStorage.toString()],
},
}
```
### Understanding AccountIsWritableBitmap
When sending messages to Solana programs, you need to specify which accounts can be written to. The `accountIsWritableBitmap` parameter in the script indicates which accounts in the `accounts` array should be marked as writable.
For our example:
- The script provides 2 accounts: `[statePda, messagesStoragePda]`
- Only the messagesStoragePda needs to be writable for the program to update it
- The state PDA is read-only during message processing
- The bitmap uses binary representation to specify writable permissions:
- Position 0 (statePda): Not writable → Bit value = 0
- Position 1 (messagesStoragePda): Writable → Bit value = 1
- Binary value: 10 (base 2) = 2 (base 10)
Therefore, we set `accountIsWritableBitmap: BigInt(2)` in our message configuration.
### Creating and Sending the CCIP Message
Finally, the script creates and sends the CCIP message using the CCIP SDK:
```typescript
// Setup client context
const { client, wallet } = await setupClientContext()
// Create the CCIP message request
const ccipMessageRequest = await createCCIPMessageRequest(client, config.destinationChainSelector, MESSAGE_CONFIG)
// Send the CCIP message
const result = await client.sendCCIPMessage(ccipMessageRequest)
```
This sends the message to the CCIP router, which then forwards it to the Solana program on the destination chain.
The script includes detailed comments that explain its implementation and follows a straightforward flow. You can review the source code at `ccip-scripts/evm/router/2_arbitrary-messaging.ts` in the starter kit repository.
## Running the Arbitrary Messaging Script
### Run the Script
Now that you understand how the arbitrary messaging script works, let's execute it to send a message:
```bash
yarn evm:arbitrary-messaging
```
### Expected Output
When you run the script, it will display detailed information about the message being sent. You should see output similar to this:
```
==== Environment Information ====
chainId ethereum-sepolia
[arbitrary-messaging] [INFO] Router Address: 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
chainId ethereum-sepolia
[ccip-messenger] [INFO] Creating client for chain: Ethereum Sepolia (ethereum-sepolia)
[arbitrary-messaging] [INFO] Wallet Address: 0x9d087fC03ae39b088326b67fA3C788236645b717
[arbitrary-messaging] [INFO] Native Balance: 600.023603232108193848 ETH
[arbitrary-messaging] [INFO] Creating CCIP message request
[arbitrary-messaging] [INFO] Using fee token: 0x779877A7B0D9E8603169DdbD7836e478b4624789
[arbitrary-messaging] [INFO]
==== Transfer Summary ====
[arbitrary-messaging] [INFO] Source Chain: Ethereum Sepolia
[arbitrary-messaging] [INFO] Destination Chain: Solana Devnet (16423721717087811551)
[arbitrary-messaging] [INFO] Sender: 0x9d087fC03ae39b088326b67fA3C788236645b717
[arbitrary-messaging] [INFO] Receiver: BqmcnLFSbKwyMEgi7VhVeJCis1wW26VySztF34CJrKFq
[arbitrary-messaging] [INFO] Token Receiver: 11111111111111111111111111111111
[arbitrary-messaging] [INFO] Fee Token: 0x779877A7B0D9E8603169DdbD7836e478b4624789
[arbitrary-messaging] [INFO] No tokens being transferred
[arbitrary-messaging] [INFO]
Message Data: 0x48656c6c6f20576f726c64
[arbitrary-messaging] [INFO] Message Data Size: 11 bytes
[arbitrary-messaging] [INFO] Message Data (decoded): Hello World
[arbitrary-messaging] [INFO]
Extra Args: Solana-specific, 292 bytes
[arbitrary-messaging] [INFO] Additional Accounts: 9XDoTJ5mYNnxqdtWV5dA583VCiGUhmL3oEMWirqys3tF, CB7ptrDkY9EgwqHoJwa3TF8u4rhwYmTob2YqzaSpPMtE
[arbitrary-messaging] [INFO] Account Is Writable Bitmap: 2
[arbitrary-messaging] [INFO]
Sending CCIP message...
[ccip-messenger] [INFO] Estimated fee: 12417653034565940
[ccip-messenger] [INFO] Approving 0.014901183641479128 LINK for CCIP Router
[ccip-messenger] [INFO] This token is being used as the fee token with a 20% buffer included
[ccip-messenger] [INFO] Approving 14901183641479128 tokens for 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
[ccip-messenger] [INFO] LINK approved for CCIP Router
[ccip-messenger] [INFO] ✅ Verified on-chain allowance for LINK: 0.014901183641479128 (required: 0.014901183641479128)
[ccip-messenger] [INFO] Sending CCIP message...
[ccip-messenger] [INFO] Sending CCIP message...
[ccip-messenger] [INFO] Transaction sent: 0x6181d2d3d033698149f8dd439df5a5b6753c741e5378761567d013746845213f
[ccip-messenger] [INFO] Transaction sent: 0x6181d2d3d033698149f8dd439df5a5b6753c741e5378761567d013746845213f
[ccip-messenger] [INFO] Message ID: 0x1ebd6f5b23b23613e6ba277c1b4e8ce2a04c6c03e8db20474e97c040a01b3f0b
[arbitrary-messaging] [INFO]
==== Transfer Results ====
[arbitrary-messaging] [INFO] Transaction Hash: 0x6181d2d3d033698149f8dd439df5a5b6753c741e5378761567d013746845213f
[arbitrary-messaging] [INFO] Transaction URL: https://sepolia.etherscan.io/tx/0x6181d2d3d033698149f8dd439df5a5b6753c741e5378761567d013746845213f
[arbitrary-messaging] [INFO] Message ID: 0x1ebd6f5b23b23613e6ba277c1b4e8ce2a04c6c03e8db20474e97c040a01b3f0b
[arbitrary-messaging] [INFO] CCIP Explorer: https://ccip.chain.link/msg/0x1ebd6f5b23b23613e6ba277c1b4e8ce2a04c6c03e8db20474e97c040a01b3f0b
[arbitrary-messaging] [INFO] Destination Chain Selector: 16423721717087811551
[arbitrary-messaging] [INFO] Sequence Number: 13
[arbitrary-messaging] [INFO]
Message tracking for Solana destinations:
[arbitrary-messaging] [INFO] Please check the CCIP Explorer link to monitor your message status.
[arbitrary-messaging] [INFO]
✅ Transaction completed on the source chain
```
### Check the Message Status
The output indicates that the transaction was completed on the source chain. In this example, the message ID is `0x1ebd6f5b23b23613e6ba277c1b4e8ce2a04c6c03e8db20474e97c040a01b3f0b`. You can use the [CCIP Explorer](https://ccip.chain.link/) to monitor the message status.
Wait for the message to be shown as successful on the CCIP Explorer. This may take several minutes to complete.
## Verification: Retrieving the Message
Make sure to wait for the message to be shown as successful on the CCIP Explorer before retrieving the message.
### Query the Receiver Program
After sending your message, you'll want to verify that it was received and processed by the receiver program. The CCIP Basic Receiver program stores messages, allowing you to retrieve them:
```bash
yarn svm:receiver:get-message
```
This script queries the program's message storage PDA, deserializes the data, and displays the content of the most recently received cross-chain message.
### Expected Verification Output
When you run the verification script, you'll see output that confirms your message was received:
```
[2025-05-01T22:20:48.307Z] CCIP Basic Receiver - Get Latest Message
[2025-05-01T22:20:48.310Z] Program ID: BqmcnLFSbKwyMEgi7VhVeJCis1wW26VySztF34CJrKFq
[2025-05-01T22:20:48.337Z] Wallet public key: EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB
[2025-05-01T22:20:48.343Z] Messages Storage PDA: CB7ptrDkY9EgwqHoJwa3TF8u4rhwYmTob2YqzaSpPMtE
[2025-05-01T22:20:52.088Z] Fetching latest message...
[2025-05-01T22:20:52.693Z]
======== LATEST MESSAGE ========
[2025-05-01T22:20:52.693Z] Message ID: 0x1ebd6f5b23b23613e6ba277c1b4e8ce2a04c6c03e8db20474e97c040a01b3f0b
[2025-05-01T22:20:52.695Z] Source Chain Selector: 16015286601757825753
[2025-05-01T22:20:52.695Z] Sender: 0x9d087fc03ae39b088326b67fa3c788236645b717
[2025-05-01T22:20:52.695Z] Message Type: Arbitrary Messaging
[2025-05-01T22:20:52.696Z] Received Timestamp: 2025-05-01T22:20:09.000Z
[2025-05-01T22:20:52.696Z] Data (hex): 0x48656c6c6f20576f726c64
[2025-05-01T22:20:52.696Z] Data (text): Hello World
[2025-05-01T22:20:52.696Z] No tokens transferred in this message
```
## Behind the Scenes: Message and Account Encoding
When sending arbitrary messages to SVM programs, proper encoding of both the message data and account structures is critical. The script handles several complex encoding tasks that would need to be implemented in your own applications:
Key utilities used include:
- `deriveReceiverPDAs()`: Calculates Program Derived Addresses using the same seeds as the SVM program
- `createCCIPMessageRequest()`: Builds the properly formatted CCIP message with all required fields
- Hex encoding utilities: Convert string data to properly formatted hex with `0x` prefix
- `accountIsWritableBitmap` calculation: Determines which accounts can be written to by the program
These utilities handle essential tasks such as:
- Converting between different address formats (Ethereum addresses vs. SVM public keys)
- Properly encoding message data with correct prefixes
- Setting up the required accounts array with properly formatted SVM addresses
- Calculating the correct bitmap values for account permissions
If you're building your own implementation, you should review these key files:
- `ccip-scripts/evm/utils/message-utils.ts`
- `ccip-lib/evm/utils/solana.ts`
- `ccip-lib/evm/core/models.ts`
Understanding these encoding details becomes especially important when working with your own SVM programs, as each program will have its own PDA structure and account requirements.
---
# Cross-Chain Token (CCT) Tutorials
Source: https://docs.chain.link/ccip/tutorials/svm/cross-chain-tokens
Last Updated: 2025-08-13
Learn how to implement cross-chain tokens using Chainlink's Cross-Chain Interoperability Protocol (CCIP) on SVM-based blockchains like Solana.
## [BurnMint with Direct Mint Authority Transfer](/ccip/tutorials/svm/cross-chain-tokens/direct-mint-authority)
This tutorial demonstrates how to create tokens that can seamlessly transfer between Solana Devnet and Ethereum Sepolia using the Cross-Chain Token (CCT) standard with direct mint authority transfer for development environments.
## [BurnMint with SPL Token Multisig Tutorial](/ccip/tutorials/svm/cross-chain-tokens/spl-token-multisig-tutorial)
This educational tutorial demonstrates how to learn SPL token multisig concepts for cross-chain tokens, providing foundational understanding of multisig architecture and distributed control while enabling autonomous CCIP operations. Ideal for understanding multisig concepts before implementing production systems.
## [BurnMint: Production Multisig Governance](/ccip/tutorials/svm/cross-chain-tokens/production-multisig-tutorial)
This production-grade tutorial demonstrates how to implement enterprise-ready cross-chain tokens with dual-layer multisig governance using Squads and SPL token multisig. Learn to create mainnet-suitable governance architecture that separates CCIP administration from mint authority control while maintaining autonomous cross-chain operations.
## [LockRelease: Production Governance](/ccip/tutorials/svm/cross-chain-tokens/lock-release-multisig)
This tutorial demonstrates the Lock and Mint token handling mechanism using LockRelease pools on Solana with Squads multisig governance. Learn to implement cross-chain tokens where the original mint authority is retained while enabling secure liquidity management and pool operations for production environments.
---
# Cross-Chain Token Setup: BurnMint with Direct Mint Authority Transfer
Source: https://docs.chain.link/ccip/tutorials/svm/cross-chain-tokens/direct-mint-authority
Last Updated: 2025-08-13
This comprehensive tutorial demonstrates how to create and configure cross-chain tokens using Chainlink's Cross-Chain Interoperability Protocol (CCIP) between Solana Devnet and Ethereum Sepolia. You will implement the **direct mint authority transfer** approach within **Path A** from the [CCIP Cross-Chain Token Integration Guide](/ccip/concepts/cross-chain-token/svm/integration-guide).
## What You Will Build
This tutorial implements the **direct mint authority transfer** variant of **Path A** from the [CCIP Cross-Chain Token Integration Guide](/ccip/concepts/cross-chain-token/svm/integration-guide). This approach is designed for development and testing environments where you transfer complete mint authority to the Pool Signer PDA for simplified setup.
### Cross-Chain Token Architecture
This tutorial implements the **[Burn and Mint](/ccip/concepts/cross-chain-token/overview#burn-and-mint)** token handling mechanism between Solana Devnet and Ethereum Sepolia. You'll deploy **two BurnMint pools** (one on each chain) that work together to maintain consistent token supply across chains.
**How Burn and Mint Works:**
1. **Source Chain**: Burns tokens from sender's account
2. **CCIP Protocol**: Transmits message cross-chain
3. **Destination Chain**: Mints equivalent tokens to the receiver
### Component Overview
| Component | Implementation | Authority Model |
| -------------------- | ----------------------------------- | -------------------------------------- |
| **Ethereum Sepolia** | ERC20 token with CCIP BurnMint pool | Multiple minters: EOA + Pool |
| **Solana Devnet** | SPL token with CCIP BurnMint pool | Single mint authority: Pool Signer PDA |
### Authority Model Differences
- **Ethereum**: Your EOA + Pool both have mint privileges (multiple minters supported)
- **Solana**: Pool Signer PDA has exclusive mint authority (single authority constraint)
For complete details on token handling mechanisms, see [Token Handling Mechanisms](/ccip/concepts/cross-chain-token/overview#token-handling-mechanisms).
## Prerequisites
This tutorial requires setting up two different repositories in separate terminal windows. Follow the setup instructions for both environments before proceeding.
### Development Environment Requirements
**System Requirements:**
- **Anchor and Solana CLI Tools**: Install following the [installation guide](https://www.anchor-lang.com/docs/installation). Requires Rust to be installed.
- **Node.js v20 or higher**: Use the [nvm package](https://www.npmjs.com/package/nvm) to install and manage versions. Verify with `node -v`
- **Yarn**: For dependency management
- **Git**: For cloning repositories
### Terminal 1: Solana Starter Kit Setup
**Clone and setup the Solana Starter Kit:**
```bash
git clone https://github.com/smartcontractkit/solana-starter-kit.git && cd solana-starter-kit
```
**Install dependencies:**
```bash
yarn install
```
**Configure your Solana environment:**
```bash
# Set Solana CLI to use devnet
solana config set --url https://api.devnet.solana.com
# Set your keypair (create one if needed)
solana config set --keypair ~/.config/solana/id.json
# If you do not have a keypair, create one:
solana-keygen new --outfile ~/.config/solana/id.json
```
**Fund your Solana wallet:**
```bash
# Get your wallet address
solana address
# Request SOL from the devnet faucet
solana airdrop 2
```
**Verify your setup:**
```bash
# Check your SOL balance
solana balance
# Verify you are on devnet
solana config get
```
### Terminal 2: Smart Contract Examples Setup
**Clone the repository and navigate to the Hardhat project:**
```bash
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
```
**Install and compile dependencies:**
```bash
npm install
npm run compile
```
**Set up encrypted environment variables:**
```bash
# Set encryption password
npx env-enc set-pw
# Configure environment variables
npx env-enc set
```
**Required environment variables for Ethereum Sepolia:**
- `ETHEREUM_SEPOLIA_RPC_URL`: RPC endpoint from [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/)
- `PRIVATE_KEY`: Your testnet wallet private key ([MetaMask export guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/))
- `ETHERSCAN_API_KEY`: API key from [Etherscan](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics)
**Fund your wallet:**
- Acquire LINK and ETH on Ethereum Sepolia using [Chainlink faucets](https://faucets.chain.link/)
## Tutorial Approach
This tutorial provides step-by-step instructions with detailed explanations of what each command does and why. You'll work primarily in Terminal 1 (Solana) with occasional switches to Terminal 2 (EVM).
**Environment Variable Management**: This tutorial uses phase-based variable files (e.g., `~/.phase1_vars`, `~/.ccip_complete_vars`) to eliminate manual variable re-entry when switching between terminals. Each phase saves its variables to files that subsequent phases can load automatically.
For deeper technical implementation details, refer to:
- **[Solana Starter Kit README](https://github.com/smartcontractkit/solana-starter-kit/blob/main/README.md)**: SVM command details
- **[Smart Contract Examples README](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/cct/hardhat/README.md)**: EVM implementation guide
## Phase 1: Ethereum Sepolia Token Setup
In this phase, you'll deploy and configure your ERC20 token with CCIP BurnMint pools on Ethereum Sepolia.
### Step 1: Deploy ERC20 Token
Deploy a burnable and mintable ERC20 token that will serve as your cross-chain asset:
Set the token address variable:
### Step 2: Deploy BurnMint Token Pool
Deploy a CCIP token pool that will manage burning and minting operations:
Set the pool address variable:
### Step 3: Mint Initial Token Supply
Mint tokens to your wallet for testing cross-chain transfers:
### Step 4: Register as CCIP Administrator
Register yourself as the CCIP administrator for your token. This two-step process (claim + accept) ensures secure ownership transfer:
#### Claim Admin Role
#### Accept Admin Role
### Step 5: Save Phase 1 Variables
Save your EVM configuration for use in later phases:
## Phase 2: Solana Devnet Token Setup
Now we'll create and configure the Solana side of your cross-chain token system.
### Step 1: Create SPL Token
Create an SPL token with metadata support:
Set the token mint variable:
### Step 2: Initialize CCIP Token Pool
Initialize the token pool configuration on Solana:
Save the Pool Signer PDA and Pool Config PDA from the output:
### Step 3: Create Pool Token Account
In this step, you will create an Associated Token Account (ATA) for the Pool Signer PDA. This ATA is required for the pool to hold and manage tokens during cross-chain transfer operations.
### Step 4: Register as CCIP Administrator
#### Propose Admin
In this step, you will propose yourself as the CCIP administrator for the Solana token.
#### Accept Admin
In this step, you will accept the administrator role for the Solana token. This process establishes your control over the token's CCIP configuration on Solana.
### Step 5: Transfer Mint Authority to Pool Signer PDA
### Step 6: Save Phase 2 Variables
Save your Solana configuration for use in cross-chain setup:
## Phase 3: Cross-Chain Configuration
Configure bidirectional connectivity between your token pools on both chains.
### Step 1: Configure Solana Pool
**Stay in Terminal 1 (Solana Starter Kit)**
Load the Ethereum addresses from Phase 1:
#### Configure Remote Chain
In this step, you will initialize the configuration for Ethereum Sepolia as a remote chain. This creates the basic chain configuration with token information but without pool addresses (those will be added in the next step).
#### Add Remote Pool Address
In this step, you will use update the previously created chain configuration with the Ethereum pool address. This completes the configuration by telling the Solana pool which Ethereum pool it should interact with for cross-chain transfers.
### Step 2: Configure Ethereum Pool
**Switch to Terminal 2 (Smart Contract Examples)**
```bash
pwd
# Should output: ../smart-contract-examples/ccip/cct/hardhat
```
#### Load Variables from Previous Phases
Load all variables needed for EVM cross-chain configuration:
#### Configure Remote Chain
In this step, you will configure the Ethereum pool to recognize the Solana token and pool. This tells the Ethereum pool which Solana pool (via its Pool Config PDA) and token it should interact with for cross-chain transfers.
## Phase 4: Pool Registration
Register your token pools with their respective Token Admin Registries to enable cross-chain operations.
### Step 1: Register Ethereum Pool
**Stay in Terminal 2 (Smart Contract Examples)**
```bash
pwd
# Should output: ../smart-contract-examples/ccip/cct/hardhat
```
Register the BurnMint token pool with your token in the TokenAdminRegistry:
### Step 2: Register Solana Pool
**Switch to Terminal 1 (Solana Starter Kit)**
```bash
pwd
# Should show ../solana-starter-kit
```
#### Create Address Lookup Table
Address Lookup Tables (ALT) optimize Solana transactions by compressing addresses.
Save the ALT address:
#### Register Solana Pool
In this step, you will register the token pool with Solana's Router TokenAdminRegistry. This instruction sets the Address Lookup Table as the pool definition for the token, enabling it for CCIP cross-chain transfers. The `writable_indices` parameter specifies which accounts in the ALT need write access during transactions.
### Step 3: Save Complete Configuration
Save all variables for the testing phase:
## Phase 5: Pre-Transfer Setup
Before testing transfers, complete final setup steps.
### Step 1: Get Pool Signer PDA
Extract the Pool Signer PDA for reference:
Confirm this matches your previously saved PDA:
```bash
echo "Saved Pool Signer PDA: $SOL_POOL_SIGNER_PDA"
echo "Current Pool Signer PDA: BwzTc3R77vf1dS4kj3JX8YGpCvjsg91vBDgyKJfeBkue"
```
### Step 2: Delegate Token Authority
In this step, you will delegate token approval to the fee-billing signer PDA, which is what enables CCIP to transfer tokens on your behalf when sending cross-chain messages.
### Step 3: Verify Delegate
Check the previous step to verify the token is delegated to the Pool Signer PDA:
## Phase 6: Testing Cross-Chain Transfers
In this step, you will test bidirectional token transfers to verify your setup.
Confirm you are in the correct directory (Terminal 1):
```bash
pwd
# Should output: ../solana-starter-kit
```
### Step 1: Load Complete Configuration
Before testing, ensure all variables are available in your current terminal:
### Step 2: Transfer Solana → Ethereum
**In Terminal 1 (Solana Starter Kit)**
### Step 3: Transfer Ethereum → Solana
## Reference: Verification Commands
Use these commands to verify your setup at any point during the tutorial. Each section focuses on a specific component of your cross-chain configuration.
### Solana Pool Verification
**Terminal 1 (Solana Starter Kit)**
```bash
# Verify pool configuration and status
yarn svm:pool:get-info \
--token-mint $SOL_TOKEN_MINT \
--burn-mint-pool-program $CCIP_POOL_PROGRAM
```
**What this shows:**
- Pool configuration details
- Pool signer PDA information
- Token account balances
- Pool operational status
### Solana Chain Configuration
**Terminal 1 (Solana Starter Kit)**
```bash
# Verify cross-chain configuration with Ethereum
yarn svm:pool:get-chain-config \
--token-mint $SOL_TOKEN_MINT \
--burn-mint-pool-program $CCIP_POOL_PROGRAM \
--remote-chain ethereum-sepolia
```
**What this shows:**
- Remote chain configuration
- Token address mappings
- Pool address mappings
- Cross-chain connectivity status
### Solana Token Balance
**Terminal 1 (Solana Starter Kit)**
```bash
# Check your token balance
spl-token balance $SOL_TOKEN_MINT
```
**What this shows:**
- Current token balance in your wallet
- Token account details
- Delegation status
### Ethereum Pool Verification
**Terminal 2 (Smart Contract Examples)**
```bash
# Verify Ethereum pool configuration
npx hardhat getPoolConfig \
--pooladdress $ETH_POOL_ADDRESS \
--network sepolia
```
**What this shows:**
- Pool contract configuration
- Remote chain settings
- Rate limiting parameters
- Pool operational status
### Cross-Chain Transfer Status
**Both Terminals**
```bash
# Monitor CCIP message status (replace with your message ID)
# From the transfer output, look for: "Message ID: 0x..."
# Then visit: https://ccip.chain.link/msg/0x...
```
**What this shows:**
- Transfer execution status
- Cross-chain message progress
- Completion confirmation
- Error details (if any)
---
# Cross-Chain Token Setup: BurnMint with SPL Token Multisig Tutorial
Source: https://docs.chain.link/ccip/tutorials/svm/cross-chain-tokens/spl-token-multisig-tutorial
Last Updated: 2025-08-13
This educational tutorial demonstrates how to create and configure cross-chain tokens using Chainlink's Cross-Chain Interoperability Protocol (CCIP) between Solana Devnet and Ethereum Sepolia using **SPL token multisig concepts**. You will learn to implement the **SPL token multisig** approach within **Path A** from the [CCIP Cross-Chain Token Integration Guide](/ccip/concepts/cross-chain-token/svm/integration-guide#path-a-full-self-service-mint-authority-controlled).
**Path A Mint Authority Options:**
- **Direct Transfer**: Transfer mint authority directly to Pool Signer PDA - suitable for development and testing ([see tutorial](/ccip/tutorials/svm/cross-chain-tokens/direct-mint-authority))
- **Multisig Setup** (this tutorial): Learn SPL token multisig concepts with Pool Signer PDA as a member - foundation for production systems
- **Production Multisig**: Enterprise-grade dual-layer governance with Squads + SPL multisig ([see tutorial](/ccip/tutorials/svm/cross-chain-tokens/production-multisig-tutorial))
This tutorial focuses on demonstrating multisig architecture concepts, helping you understand governance controls while maintaining autonomous cross-chain token transfers through BurnMint token pools.
## What You Will Build
This tutorial implements the **SPL token multisig** variant of **Path A** from the [CCIP Cross-Chain Token Integration Guide](/ccip/concepts/cross-chain-token/svm/integration-guide). This approach is designed for **learning multisig concepts** and provides a foundation for production systems.
### Cross-Chain Token Architecture
This tutorial implements the **[Burn and Mint](/ccip/concepts/cross-chain-token/overview#burn-and-mint)** token handling mechanism between Solana Devnet and Ethereum Sepolia with **SPL token multisig governance**. You'll deploy **two BurnMint pools** (one on each chain) that work together to maintain consistent token supply across chains while learning multisig architecture concepts.
**How Burn and Mint Works:**
1. **Source Chain**: Burns tokens from sender's account
2. **CCIP Protocol**: Transmits message cross-chain
3. **Destination Chain**: Mints equivalent tokens to the receiver
### Component Overview
| Component | Implementation | Authority Model |
| -------------------- | ----------------------------------- | -------------------------------------------------- |
| **Ethereum Sepolia** | ERC20 token with CCIP BurnMint pool | Multiple minters: EOA + Pool |
| **Solana Devnet** | SPL token with CCIP BurnMint pool | SPL Token Multisig: Pool Signer PDA + Admin wallet |
### SPL Token Multisig Architecture
**Key Approach**: You will create an SPL token multisig that includes the Pool Signer PDA as a required member, enabling both autonomous CCIP operations and governance-controlled minting.
**Educational Focus**: This tutorial demonstrates multisig architecture concepts using a simplified 1-of-2 configuration for learning purposes.
## Prerequisites
This tutorial requires setting up two different repositories in separate terminal windows. Follow the setup instructions for both environments before proceeding.
### Development Environment Requirements
**System Requirements:**
- **Anchor and Solana CLI Tools**: Install following the [installation guide](https://www.anchor-lang.com/docs/installation). Requires Rust to be installed.
- **Node.js v20 or higher**: Use the [nvm package](https://www.npmjs.com/package/nvm) to install and manage versions. Verify with `node -v`
- **Yarn**: For dependency management
- **Git**: For cloning repositories
### Terminal 1: Solana Starter Kit Setup
**Clone and setup the Solana Starter Kit:**
```bash
git clone https://github.com/smartcontractkit/solana-starter-kit.git && cd solana-starter-kit
```
**Install dependencies:**
```bash
yarn install
```
**Configure your Solana environment:**
```bash
# Set Solana CLI to use devnet
solana config set --url https://api.devnet.solana.com
# Set your keypair (create one if needed)
solana config set --keypair ~/.config/solana/id.json
# If you do not have a keypair, create one:
solana-keygen new --outfile ~/.config/solana/id.json
```
**Fund your Solana wallet:**
```bash
# Get your wallet address
solana address
# Request SOL from the devnet faucet
solana airdrop 2
```
**Verify your setup:**
```bash
# Check your SOL balance
solana balance
# Verify you are on devnet
solana config get
```
### Terminal 2: Smart Contract Examples Setup
**Clone the repository and navigate to the Hardhat project:**
```bash
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
```
**Install and compile dependencies:**
```bash
npm install
npm run compile
```
**Set up encrypted environment variables:**
```bash
# Set encryption password
npx env-enc set-pw
# Configure environment variables
npx env-enc set
```
**Required environment variables for Ethereum Sepolia:**
- `ETHEREUM_SEPOLIA_RPC_URL`: RPC endpoint from [Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/)
- `PRIVATE_KEY`: Your testnet wallet private key ([MetaMask export guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/))
- `ETHERSCAN_API_KEY`: API key from [Etherscan](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics)
**Fund your wallet:**
- Acquire LINK and ETH on Ethereum Sepolia using [Chainlink faucets](https://faucets.chain.link/)
## Tutorial Approach
This tutorial provides step-by-step instructions with detailed explanations of what each command does and why. You'll work primarily in Terminal 1 (Solana) with occasional switches to Terminal 2 (EVM).
**Environment Variable Management**: This tutorial uses phase-based variable files (e.g., `~/.phase1_vars`, `~/.ccip_complete_vars`) to eliminate manual variable re-entry when switching between terminals. Each phase saves its variables to files that subsequent phases can load automatically.
For detailed implementation code explanations, refer to the comprehensive READMEs in both repositories:
- **[Solana Starter Kit README](https://github.com/smartcontractkit/solana-starter-kit/blob/main/README.md)**: Detailed explanations of all SVM commands and implementation
- **[Smart Contract Examples README](https://github.com/smartcontractkit/smart-contract-examples/blob/main/ccip/cct/hardhat/README.md)**: Comprehensive EVM implementation guide
The READMEs contain detailed technical explanations, troubleshooting guides, and advanced configuration options.
## Phase 1: Ethereum Sepolia Token Setup
In this step, you will use Hardhat tasks to deploy an ERC20 token contract and a corresponding burn and mint token pool on Ethereum Sepolia. The tasks interact with the `BurnMintERC20` contract for token deployment and the `BurnMintTokenPool` contract for pool creation.
**Current Terminal: Terminal 2** (Smart Contract Examples - Hardhat) Verify your location:
```bash
pwd
# Should show: .../smart-contract-examples/ccip/cct/hardhat
```
### Step 1: Deploy ERC20 Token
Using the `deployToken` task, deploy a burnable and mintable ERC20 token on Ethereum Sepolia:
Export your token address for later use:
### Step 2: Deploy Token Pool
In this step, you will use the `deployTokenPool` task to deploy a CCIP BurnMint token pool for the token on Ethereum Sepolia. This task interacts with the `BurnMintTokenPool` contract and grants the necessary mint and burn privileges to the pool.
Export your pool address for later use:
### Step 3: Mint Tokens
In this step, you will use the `mintTokens` task to mint tokens on Ethereum Sepolia for your Externally Owned Account (EOA). Since you assigned mint and burn privileges to your EOA when deploying the tokens, you can now mint tokens for testing purposes. This ensures that you have enough tokens in your EOA to perform cross-chain transfers later.
### Step 4: Claim Admin
In this step, you will use the `claimAdmin` task to register your EOA as the administrator for the deployed token on Ethereum Sepolia. This process involves calling the `RegistryModuleOwnerCustom` contract, which will fetch the CCIP admin of the token and set it up as the admin in the registry.
### Step 5: Accept Admin Role
In this step, you will use the `acceptAdminRole` task to accept the admin role for the deployed token on Ethereum Sepolia. Once you have claimed the role, accepting the role finalizes your control over the token administration.
Save the Phase 1 variables for cross-terminal access:
## Phase 2: Solana Devnet Token Setup
In this phase, you will create an SPL token, initialize the CCIP token pool, and complete CCIP registration **before\*\*** setting up the SPL token multisig. This sequence is critical because the self-service registration requires you to hold the mint authority.
**Switch to Terminal 1** (Solana Starter Kit) Verify your location:
```bash
pwd
# Should show: .../solana-starter-kit
```
Load the Ethereum addresses from Phase 1:
```bash
source ~/.phase1_vars
echo "Loaded Phase 1 variables: ETH_TOKEN_ADDRESS=$ETH_TOKEN_ADDRESS, ETH_POOL_ADDRESS=$ETH_POOL_ADDRESS"
```
### Step 1: Create SPL Token
In this step, you will use the `svm:token:create` script to create an SPL token with Metaplex metadata support on Solana Devnet. This script leverages the TokenManager library to create a comprehensive token with metadata, initial supply, and proper configuration for CCIP integration.
### Set Environment Variables
Set up environment variables for easier reference throughout the tutorial:
### Step 2: Initialize CCIP Token Pool
In this step, you will use the `svm:pool:initialize` script to initialize a CCIP token pool for your SPL token. This process creates the necessary on-chain state for cross-chain operations and establishes the Pool Signer PDA that will manage token operations.
### Step 3: Verify Pool Creation
In this step, you will use the `svm:pool:get-info` script to verify that your token pool was initialized correctly. This command queries the on-chain state and displays comprehensive information about your pool configuration, including the Pool Signer PDA and current ownership.
### Step 4: Create Pool Token Account
In this step, you will use the `svm:pool:create-token-account` script to create an Associated Token Account (ATA) for the Pool Signer PDA. This ATA is required for the pool to hold and manage tokens during cross-chain transfer operations.
### Step 5: Set Pool Environment Variables
Set the Pool Signer PDA and Pool Config PDA from the Step 3 output above:
### Step 6: Claim Admin
In this step, you will use the `svm:admin:propose-administrator` and `svm:admin:accept-admin-role` scripts to register yourself as the CCIP administrator for the Solana token. This process establishes your control over the token's CCIP configuration on Solana.
### Step 7: Accept Admin Role
In this step, you will use the `svm:admin:accept-admin-role` script to accept the proposed administrator role. This process establishes your control over the token's CCIP configuration on Solana.
### Step 8: Create SPL Token Multisig
Now that CCIP registration is complete, create the SPL token multisig that will serve as the mint authority:
**Set the multisig address environment variable:**
### Step 9: Transfer Mint Authority to Multisig
Now transfer the mint authority from your wallet to the multisig:
### Step 10: Verify Multisig Configuration
Verify that the multisig has been properly configured and the mint authority has been transferred:
Save the Phase 2 variables for cross-terminal access:
```bash
# Save all variables from Phases 1 and 2 to complete vars file
cat > ~/.ccip_complete_vars << EOF
export ETH_TOKEN_ADDRESS="$ETH_TOKEN_ADDRESS"
export ETH_POOL_ADDRESS="$ETH_POOL_ADDRESS"
export SOL_TOKEN_MINT="$SOL_TOKEN_MINT"
export SOL_ADMIN_WALLET="$SOL_ADMIN_WALLET"
export CCIP_POOL_PROGRAM="$CCIP_POOL_PROGRAM"
export SOL_POOL_SIGNER_PDA="$SOL_POOL_SIGNER_PDA"
export SOL_POOL_CONFIG_PDA="$SOL_POOL_CONFIG_PDA"
export SOL_MULTISIG_ADDRESS="$SOL_MULTISIG_ADDRESS"
EOF
echo "All variables saved to ~/.ccip_complete_vars for cross-terminal access"
```
## Phase 3: Cross-Chain Configuration
In this step, you will configure bidirectional connectivity between the token pools on both chains. Each chain uses different tools: **Solana** uses Starter Kit scripts to configure its pool to recognize Ethereum tokens and pools, while **Ethereum** uses Hardhat tasks to configure its pool to recognize Solana tokens and pools.
### Step 1: Configure Solana -> Ethereum
#### Initialize Chain Remote Configuration
In this step, you will use the `svm:pool:init-chain-remote-config` script to initialize the configuration for Ethereum Sepolia as a remote chain. This creates the basic chain configuration with token information but without pool addresses (those will be added in the next step).
#### Add Ethereum Pool Address
In this step, you will use the `svm:pool:edit-chain-remote-config` script to update the previously created chain configuration with the Ethereum pool address. This completes the configuration by telling the Solana pool which Ethereum pool it should interact with for cross-chain transfers.
#### Verify Configuration
In this step, you will use the `svm:pool:get-chain-config` script to verify that the Solana pool configuration for Ethereum Sepolia has been set up correctly with both the token address and pool address.
### Step 2: Configure Ethereum → Solana
**Switch to Terminal 2 (Smart Contract Examples)**
```bash
pwd
# Should show: .../smart-contract-examples/ccip/cct/hardhat
```
Load all variables from previous phases:
#### Configure Remote Chain
In this step, you will use the `applyChainUpdates` Hardhat task to configure the Ethereum pool to recognize the Solana token and pool. This tells the Ethereum pool which Solana pool (via its Pool Config PDA) and token it should interact with for cross-chain transfers.
#### Verify Remote Chain Configuration
Verify that the Ethereum pool is correctly configured to recognize the Solana chain:
## Phase 4: Pool Registration
In this step, you will register the token pools with their respective tokens on both chains. This is the final configuration step that enables cross-chain operations by linking tokens to their pools in the CCIP registry.
Pool registration works differently on each platform:
- **Ethereum**: Links the token directly to its pool contract address
- **Solana**: Links the token to an Address Lookup Table containing all necessary pool accounts
### Step 1: Ethereum Sepolia Pool Registration
**Stay in Terminal 2 (Smart Contract Examples)**
```bash
pwd
# Should show: .../smart-contract-examples/ccip/cct/hardhat
```
In this step, you will use the `setPool` Hardhat task to register the BurnMint token pool with the token in Ethereum's TokenAdminRegistry contract. This function sets the pool contract address for the token, enabling it for CCIP cross-chain transfers. Only the token administrator can call this function.
### Step 2: Solana Devnet Pool Registration
**Switch to Terminal 1 (Solana Starter Kit)**
```bash
pwd
# Should show: .../solana-starter-kit
```
#### Create Address Lookup Table
Create an Address Lookup Table (ALT) containing all required accounts for CCIP operations:
**Set ALT environment variable:**
#### Register Pool with Router
Register your token pool with the CCIP Router using the Address Lookup Table:
## Phase 5: Testing Cross-Chain Transfers
Test the complete cross-chain token transfer functionality in both directions.
### Step 1: Transfer Solana → Ethereum
**Stay in Terminal 1 (Solana Starter Kit)**
Confirm you are in the correct directory:
```bash
pwd
# Should show: .../solana-starter-kit
```
#### Prepare for Testing
Your Associated Token Account (ATA) was already created during token creation in Phase 1. Now verify your token balance and prepare for cross-chain transfers:
#### Execute Transfer
#### Monitor and Verify Transaction
Upon successful execution, the system generates critical tracking identifiers for transaction monitoring and verification.
**Transaction Identifiers:**
- **Transaction Signature**: `sb2kHXW3M4P56WNNH74LviWcPmeYXjNA4f6opmt8iPrYF1cu5FC9grLhyu2XTEwkABQUrTdaJoDWc5CHe2gQL2u`
- **CCIP Message ID**: `0x9c486ce15650d0bbff3f9cd8ead0510aadd854f7247bf9b775918a1296408aaa`
**CCIP Explorer** (Primary monitoring interface):
```
https://ccip.chain.link/msg/0x9c486ce15650d0bbff3f9cd8ead0510aadd854f7247bf9b775918a1296408aaa
```
The CCIP Explorer provides comprehensive transaction visibility:
- Source chain (Solana) transaction confirmation
- CCIP message processing and routing
- Destination chain (Ethereum) message delivery
- Token minting completion on Ethereum
**Solana Explorer** (Source chain verification):
```
https://explorer.solana.com/tx/sb2kHXW3M4P56WNNH74LviWcPmeYXjNA4f6opmt8iPrYF1cu5FC9grLhyu2XTEwkABQUrTdaJoDWc5CHe2gQL2u?cluster=devnet
```
### Step 2: Transfer Ethereum → Solana
**In Terminal 1 (Solana Starter Kit)**
#### Execute Transfer
#### Monitor and Verify Transaction
Upon successful execution, the system generates distinct tracking identifiers for comprehensive monitoring across both blockchain networks.
**Transaction Identifiers:**
- **Ethereum Transaction Hash**: `0xb2d48398a4d57dde2aaf0551209abad448b092602ca5087e0d108eadd8a8c319`
- **CCIP Message ID**: `0x567f4cdb2e1c52f6aef6354bd9acbeac6761aa4b1a5e74a356785c2e67197d88`
**CCIP Explorer** (Primary monitoring interface):
```
https://ccip.chain.link/msg/0x567f4cdb2e1c52f6aef6354bd9acbeac6761aa4b1a5e74a356785c2e67197d88
```
The CCIP Explorer provides comprehensive transaction visibility:
- Source chain (Ethereum) transaction confirmation
- CCIP message processing and routing
- Destination chain (Solana) message delivery
- Token minting completion on Solana network
**Ethereum Sepolia Explorer** (Source chain verification):
```
https://sepolia.etherscan.io/tx/0xb2d48398a4d57dde2aaf0551209abad448b092602ca5087e0d108eadd8a8c319
```
## Optional: Verify Mint Authority Control
### Demonstrate Manual Minting Through Multisig
This optional section demonstrates that transferring mint authority to the multisig doesn't mean "losing control" - you can still mint tokens manually through the **Admin Wallet** (the non-PDA signer in your multisig). This proves the multisig setup is working correctly and that you retain administrative capabilities.
---
# Cross-Chain Token Setup: BurnMint with Production Multisig Governance
Source: https://docs.chain.link/ccip/tutorials/svm/cross-chain-tokens/production-multisig-tutorial
Last Updated: 2025-08-13
This tutorial implements **production-grade cross-chain tokens** using Chainlink CCIP with **dual-layer multisig governance** between Solana Devnet and Ethereum Sepolia. You'll build a **production-ready governance architecture** following **Path A** from the [CCIP Cross-Chain Token Integration Guide](/ccip/concepts/cross-chain-token/svm/integration-guide#path-a-full-self-service-mint-authority-controlled).
## What You Will Build
This tutorial implements the **production multisig governance** variant of **Path A** from the [CCIP Cross-Chain Token Integration Guide](/ccip/concepts/cross-chain-token/svm/integration-guide). This approach is designed for **production environments** with enterprise-grade dual-layer governance.
### Cross-Chain Token Architecture
This tutorial implements the **[Burn and Mint](/ccip/concepts/cross-chain-token/overview#burn-and-mint)** token handling mechanism between Solana Devnet and Ethereum Sepolia with **dual-layer multisig governance**. You'll deploy **two BurnMint pools** (one on each chain) that work together to maintain consistent token supply across chains while providing enterprise-grade security controls.
**How Burn and Mint Works:**
1. **Source Chain**: Burns tokens from sender's account
2. **CCIP Protocol**: Transmits message cross-chain
3. **Destination Chain**: Mints equivalent tokens to the receiver
For complete details on token handling mechanisms, see [Token Handling Mechanisms](/ccip/concepts/cross-chain-token/overview#token-handling-mechanisms).
### Component Overview
| Component | Implementation | Authority Model |
| ------------------------ | ------------------------------------ | ------------------------- |
| **Ethereum Sepolia** | ERC20 token with CCIP BurnMint pool | EOA-controlled (tutorial) |
| **Solana Devnet** | SPL token with dual-layer governance | Squads + SPL multisig |
| **Cross-Chain Transfer** | Bidirectional token transfers | Autonomous operations |
### Dual-Layer Governance Architecture
This tutorial implements **dual-layer multisig governance** that separates concerns:
- **Layer 1 (Squads)**: Controls CCIP administration, pool ownership, protocol configuration
- **Layer 2 (SPL Multisig)**: Controls mint authority with Pool Signer PDA for autonomous operations
**Production Benefits:**
- **Comprehensive Security**: Two independent governance layers
- **Operational Autonomy**: Pool Signer PDA enables autonomous cross-chain transfers
- **Administrative Control**: Squads provides enterprise-grade governance
## Prerequisites
### System Requirements
Install these tools before starting:
**Package Managers:**
- **Node.js v20+**: Required for all repositories
- **pnpm**: Required for base58 Generator (`npm install -g pnpm`)
- **yarn**: Required for Solana Starter Kit (`npm install -g yarn`)
**Solana Development:**
- **Solana CLI**: [Installation guide](https://docs.solana.com/cli/install-solana-cli-tools)
**Wallets:**
- **Solana wallet**: [Phantom](https://phantom.app/) or [Backpack](https://backpack.app/) for Devnet operations
- **Ethereum wallet**: [MetaMask](https://metamask.io/) for Sepolia testnet operations
### Repository Setup
Clone and install dependencies for all three repositories:
**Terminal 1: base58 Generator**
```bash
git clone https://github.com/smartcontractkit/ccip-solana-bs58-generator.git
cd ccip-solana-bs58-generator
pnpm install
```
**Terminal 2: EVM Hardhat**
```bash
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
npm install
npm run compile
```
**Terminal 3: Solana Starter Kit**
```bash
git clone https://github.com/smartcontractkit/solana-starter-kit.git
cd solana-starter-kit
yarn install
```
**Create and configure the .env file:**
```bash
# Create .env file in the project root
cat > .env << 'EOF'
EVM_PRIVATE_KEY=your_private_key_here
EVM_RPC_URL=your_rpc_url_here
EOF
```
Replace the placeholder values with:
- `EVM_PRIVATE_KEY`: Your Ethereum wallet private key (for Sepolia testnet operations)
- `EVM_RPC_URL`: Your Ethereum RPC URL (from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another provider)
### Environment Configuration
**Solana Configuration:**
```bash
# Set Solana CLI to devnet
solana config set --url https://api.devnet.solana.com
# Create keypair (ONLY IF NEEDED)
solana-keygen new --outfile ~/.config/solana/id.json
# Check Config
solana config get
# Fund wallet
solana airdrop 3
```
**Ethereum Sepolia Configuration:**
```bash
# In Terminal 2 (Hardhat directory)
npx env-enc set-pw
npx env-enc set
```
Required environment variables:
- `ETHEREUM_SEPOLIA_RPC_URL`: RPC endpoint ([Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/))
- `PRIVATE_KEY`: Testnet wallet private key
- `ETHERSCAN_API_KEY`: API key from [Etherscan](https://etherscan.io/apis)
**Testnet Tokens:**
- **Solana Devnet**: Use `solana airdrop 3` for SOL
- **Ethereum Sepolia**: Use [Chainlink faucets](https://faucets.chain.link/) for LINK and ETH
### Squads Multisig Setup
**Step 1: Prepare Signers**
Create multiple wallet addresses for your multisig signers:
```bash
# Create additional signers using Solana CLI
solana-keygen new --outfile ~/.config/solana/signer2.json
solana-keygen new --outfile ~/.config/solana/signer3.json
# Get signer addresses
solana address --keypair ~/.config/solana/id.json
solana address --keypair ~/.config/solana/signer2.json
solana address --keypair ~/.config/solana/signer3.json
# Fund signers (minimum 0.1 SOL each for transaction fees)
solana transfer 0.1 --allow-unfunded-recipient
solana transfer 0.1 --allow-unfunded-recipient
```
Alternatively, create signers in Phantom wallet and fund them:
```bash
# Transfer SOL to Phantom-created addresses
solana transfer 0.5 --allow-unfunded-recipient
```
**Step 2: Create Your Squad**
1. Visit [devnet.squads.so](https://devnet.squads.so)
2. Connect your Solana wallet (e.g., Phantom/Backpack)
3. Click "Create New Squad"
4. Configure your multisig:
- **Squad Name**: Choose a descriptive name (e.g., "CCIP Token Governance")
- **Members**: Add wallet addresses of all signers
- **Threshold**: Set approval threshold (recommended: 2/3 or 3/5)
**Step 3: Record Critical Addresses**
After Squad creation, navigate to Settings tab and record these addresses.
**In Terminal 1 (base58 Generator), export the vault address:**
```bash
# CRITICAL: Use the VAULT address, NOT the multisig address
export SOL_SQUAD_VAULT_MULTISIG="YOUR_VAULT_ADDRESS_HERE"
```
Verify the export:
```bash
echo "Squads Vault Address: $SOL_SQUAD_VAULT_MULTISIG"
```
**Step 4: Test Your Squad**
Perform a small test transaction to verify setup:
1. Send a small amount of SOL to your Squad vault
2. Create a test transaction in Squads UI (e.g., SOL transfer to your wallet)
3. Confirm all signers can approve transactions
4. Execute the transaction
For detailed setup guidance, see the [Squads Documentation](https://docs.squads.so/).
### Token Creation Option
## Tutorial Approach
This tutorial implements production-grade cross-chain tokens using a three-terminal workflow across specialized repositories:
| Terminal | Repository | Purpose | Commands |
| -------------- | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------------- |
| **Terminal 1** | [CCIP Solana base58 Generator](https://github.com/smartcontractkit/ccip-solana-base58-generator) | Generate governance transactions | `pnpm bs58` |
| **Terminal 2** | [Smart Contract Examples (Hardhat)](https://github.com/smartcontractkit/smart-contract-examples/tree/main/ccip/cct/hardhat) | Deploy EVM components | `npx hardhat` |
| **Terminal 3** | [Solana Starter Kit](https://github.com/smartcontractkit/solana-starter-kit) | Test cross-chain transfers | `yarn` |
**Note**: Each repository contains comprehensive READMEs with detailed technical explanations and troubleshooting guides.
### Key Implementation Notes
- **Terminal 1** generates base58-encoded transactions for Squads multisig governance
- **Terminal 2** uses EOA for tutorial simplicity; production deployments should use multisig wallets (e.g., Safe)
- **Terminal 3** validates end-to-end cross-chain functionality
### base58 Transaction Execution Workflow
**Transaction Generation and Validation:**
1. **CLI Simulation**: Each base58 transaction is automatically simulated during generation
2. **Error Handling**: If simulation fails, error logs appear in terminal - do not upload failed transactions to Squads
3. **Success Indicator**: Successful simulation shows transaction preview and base58 output
**Squads Multisig Execution Process:**
1. **Propose**: A signer imports the base58 transaction into Squads UI → "Add instruction" → "Import base58 encoded tx" → **Initiate Transaction**
2. **Approve**: Required threshold (M) of signers review and approve the transaction
3. **Simulate** (Recommended): Before execution, signers can simulate through Squads interface to preview onchain effects
4. **Execute**: After threshold approval, any signer can execute the transaction
### Environment Variables
Variables use prefixes to prevent confusion across repositories:
| Prefix | Usage | Examples |
| -------- | ------------------ | ------------------------------------------ |
| `ETH_*` | Ethereum addresses | `ETH_TOKEN_ADDRESS`, `ETH_POOL_ADDRESS` |
| `SOL_*` | Solana addresses | `SOL_TOKEN_MINT`, `SOL_POOL_ADDRESS` |
| `CCIP_*` | Protocol constants | `CCIP_POOL_PROGRAM`, `CCIP_ROUTER_PROGRAM` |
## Phase 1: EVM Chain Setup (Ethereum Sepolia)
In this phase, you will deploy ERC20 tokens and configure CCIP BurnMint pools on Ethereum Sepolia. This setup is identical across all Path A variants and provides the foundation for cross-chain operations.
### Step 1: Prepare EVM Environment
First, set up your terminal and verify your environment:
### Step 2: Deploy ERC20 Token
Deploy your cross-chain token on Ethereum Sepolia:
Set the token address variable:
### Step 3: Deploy and Configure CCIP BurnMint Pool
Deploy the BurnMint token pool:
Set the pool address and configure:
### Step 4: Mint Initial Token Supply
Mint tokens for testing:
### Step 5: Claim CCIP Admin Role
In this step, you will use the `claimAdmin` task to register your EOA as the administrator for the deployed token on Ethereum Sepolia. This process involves calling the `RegistryModuleOwnerCustom` contract, which will fetch the CCIP admin of the token and set it up as the admin in the registry.
### Step 6: Accept CCIP Admin Role
In this step, you will use the `acceptAdminRole` task to accept the admin role for the deployed token on Ethereum Sepolia. Once you have claimed the role, accepting the role finalizes your control over the token administration.
### Step 7: Register Pool with Token
In this step, you will use the `setPool` task to register the BurnMint token pool with the token in Ethereum's TokenAdminRegistry contract. This function sets the pool contract address for the token, enabling it for CCIP cross-chain transfers. Only the token administrator can call this function.
**Phase 1 Complete**: Save your variables:
## Phase 2: Solana Setup with Production Dual Multisig Governance
In this phase, you will implement the **production-grade dual-layer multisig governance** architecture on Solana Devnet.
### Step 1: Prepare base58 Environment
### Step 2: Create SPL Token (Layer 2 Foundation)
This command generates a transaction that creates a new SPL token mint with the following features:
- **Mint Authority**: Set to your Squad vault multisig for governance control
- **Metaplex Metadata**: Includes token name, symbol, and URI for cross-platform compatibility
- **Initial Supply**: Automatically mints 5,000 tokens (5,000,000,000,000 smallest units) to your wallet
- **Deterministic Address**: Uses a seed-based approach for predictable mint addresses
Generate the SPL token creation transaction (customize parameters or reuse the example):
After execution, set the token mint:
### Step 3: Initialize BurnMint Token Pool (Before Multisig Setup)
This command creates only one new account and establishes references to existing infrastructure:
**What Gets Created:**
- **Pool State PDA**: Creates the onchain Pool State account that stores your token's CCIP configuration
**What Gets Referenced (Not Created):**
- **Authority Assignment**: Sets your Squad vault as the pool owner with full configuration control
- **Router Integration**: References the existing global CCIP router and RMN (Risk Management Network)
- **Pool Signer PDA**: The Pool Signer PDA is mathematically derived but not created - it will be used on-demand during cross-chain operations
The `initialize-pool` instruction creates only the Pool State account for your SPL token mint, establishing you as the pool owner while referencing existing global infrastructure.
Generate the pool initialization transaction:
**Account Breakdown from the Transaction (Example):**
| Account | Address | Purpose |
| ------- | ---------------------------------------------- | ----------------------------------------------------------- |
| **#1** | `2pGY9WAjanpR3RnY5hQ1a23uDNomzFCAD5HMBgo8nH6M` | **Pool State PDA** - Your main pool configuration |
| **#2** | `3T1wVJporm2JpGojRV831TTQ1nexkBqFWhKnE5Lwedci` | Token Mint (your token) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | Authority (your Squad vault) |
| **#4** | `11111111111111111111111111111111` | System Program |
| **#5** | `41FGToCmdaWa1dgZLKFAjvmx6e6AjVTX7SVRibvsMGVB` | CCIP Pool Program |
| **#6** | `4sVSCJqG9ZKEvnpN38qTzb7Kc8QdHakBgB87HN3FYRaz` | Program Data PDA |
| **#7** | `E4Bsi43kX3iwXAFia2ebm1mS5Xkmmdv3minZDnfo7Zzf` | **Global Config PDA** - Program-wide configuration settings |
**Key Addresses You Need:**
- **Account #1** → `SOL_POOL_ADDRESS` (writable account = pool state)
- **Pool Signer PDA** → Must be derived separately (NOT in transaction accounts)
After execution, set the pool state address from the transaction:
```bash
# Set the pool state address from Account #1 in the transaction above
export SOL_POOL_ADDRESS="2pGY9WAjanpR3RnY5hQ1a23uDNomzFCAD5HMBgo8nH6M"
```
### Step 4: Derive Pool Signer PDA
The Pool Signer PDA is the critical address that will serve as the autonomous signing authority for cross-chain mint and burn operations.
Set the Pool Signer PDA address for use in subsequent steps:
### Step 5: Register CCIP Administrator
This two-step process establishes your Squad vault as the CCIP token administrator, enabling you to enable your token in CCIP. Since your Squad vault currently holds the mint authority, you can complete this registration using the [self-service registration flow](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow) without external assistance.
**Why This Works**: The Router's [owner_propose_administrator](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow) instruction verifies onchain that the caller matches the token's `mint_authority` field. Your Squad vault has this authority, enabling [PATH A self-service registration](/ccip/concepts/cross-chain-token/svm/integration-guide#path-a-full-self-service-mint-authority-controlled).
#### Sub-step 5a: Propose Administrator
The [owner_propose_administrator](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow) instruction creates a TokenAdminRegistry PDA for your token and sets your Squad vault as the [pending administrator](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow):
**Account Breakdown from the Transaction (Example):**
| Account | Address | Description |
| ------- | ---------------------------------------------- | --------------------------------------------------------------------- |
| **#1** | `3Yrg9E4ySAeRezgQY99NNarAmFLtixapga9MZb6y2dt3` | Router Config PDA (read-only) |
| **#2** | `4maPuX2fcDmCkQv1Qa6RmQa2yGpeZLxiqKy9ybwazGft` | **🎯 Token Admin Registry PDA** - Gets created/updated for your token |
| **#3** | `3T1wVJporm2JpGojRV831TTQ1nexkBqFWhKnE5Lwedci` | Token Mint (your token) |
| **#4** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | Authority/Payer (your Squad vault) |
| **#5** | `11111111111111111111111111111111` | System Program |
**What This Transaction Does:**
This transaction proposes your Squad vault as the administrator for your token in the Router's Token Admin Registry. **Account #2** is the Token Admin Registry PDA that stores who has administrative control over your token's cross-chain operations.
#### Sub-step 5b: Accept Administrator Role
The [accept_admin_role](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow) instruction completes the [registration process](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow) by having the pending administrator (your Squad vault) explicitly accept the CCIP token administrator role:
**Account Breakdown from the Transaction (Example):**
| Account | Address | Description |
| ------- | ---------------------------------------------- | ------------------------------------------------------------ |
| **#1** | `3Yrg9E4ySAeRezgQY99NNarAmFLtixapga9MZb6y2dt3` | Router Config PDA (read-only) |
| **#2** | `4maPuX2fcDmCkQv1Qa6RmQa2yGpeZLxiqKy9ybwazGft` | **🎯 Token Admin Registry PDA** - Updates status to "active" |
| **#3** | `3T1wVJporm2JpGojRV831TTQ1nexkBqFWhKnE5Lwedci` | Token Mint (your token) |
| **#4** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | Authority (your Squad vault - must be pending admin) |
**What This Transaction Does:**
This is the **acceptance step** of the two-phase administrator registration. It updates the Token Admin Registry PDA (Account #2) from "pending administrator" to "active administrator" status. Your Squad vault explicitly accepts the administrator role, completing the secure registration process.
**Key Difference**: Only 4 accounts (vs. 5 in the propose step) because we're updating an existing registry entry, not creating a new one.
After both transactions are executed, your Squad vault will be the CCIP token administrator. See [Registration & Administration](/ccip/concepts/cross-chain-token/svm/registration-administration) for more details.
### Step 6: Create SPL Token Multisig (Layer 2 Mint Authority)
This command creates the second layer of your dual-multisig architecture by establishing an SPL token multisig that will control mint authority. The `create-multisig` instruction creates a deterministic multisig account that includes both your Pool Signer PDA and Squad vault as authorized signers.
**What This Accomplishes**:
- **SPL Token Multisig Creation**: Establishes a new multisig account
- **Dual Authority Setup**: Includes both Pool Signer PDA (for autonomous CCIP operations) and Squad vault (for governance control)
- **Threshold Configuration**: Sets threshold to 1, allowing either signer to authorize mint operations
- **Layer 2 Foundation**: Creates the mint authority that will be transferred from your Squad vault in the next step
**Token Program Detection**: The CLI automatically detects whether your mint uses SPL Token v1 or Token-2022 and creates the appropriate multisig type.
**Account Breakdown from the Transaction (Example):**
| Account | Address | Description |
| ------- | ---------------------------------------------- | ----------------------------------------- |
| **#1** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Payer/Authority** (your Squad vault) |
| **#2** | `6c5UGRRQzPvZY4ZJZJiPGRYMw5e32uX8GfFzvm35k3jK` | **🎯 SPL Token Multisig** (newly created) |
**What This Transaction Does:**
This creates an **SPL Token Multisig** account that will control your token's mint authority. The CLI automatically detected SPL Token v1 and created the appropriate multisig type.
**Key Details:**
- **Generated Multisig Address**: `6c5UGRRQzPvZY4ZJZJiPGRYMw5e32uX8GfFzvm35k3jK`
- **Multisig Signers**: Pool Signer PDA + Squad vault
- **Threshold**: 1 (either signer can authorize mint operations)
- **Hybrid Control**: Enables both automated CCIP operations and human governance
**After execution, set the variable for the next step:**
### Step 7: Transfer Mint Authority (Layer 2 Mint Authority)
Transfer mint authority from your Squad vault to the SPL Token Multisig, completing the dual-layer governance architecture. This enables both automated CCIP operations and human governance control.
**What This Command Does:**
- **Authority Transfer**: Moves mint control from Squad vault to SPL Token Multisig
- **SPL Token Operation**: Uses the native `SetAuthority` instruction for mint authority
- **Governance Architecture**: Establishes the final production-ready control structure
- **Dual Control Setup**: Enables both Pool Signer PDA (autonomous) and Squad vault (governance) control
- **Irreversible Change**: Once executed, only the SPL Token Multisig can mint tokens
**Account Breakdown from the Transaction (Example):**
| Account | Address | Description |
| ------- | ---------------------------------------------- | --------------------------------------------- |
| **#1** | `3T1wVJporm2JpGojRV831TTQ1nexkBqFWhKnE5Lwedci` | **Token Mint** (writable - authority updated) |
| **#2** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Current Authority** (signer - Squad vault) |
**Key Details:**
- **Current Authority**: `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` (Squad vault)
- **New Authority**: `6c5UGRRQzPvZY4ZJZJiPGRYMw5e32uX8GfFzvm35k3jK` (SPL Token Multisig)
- **Token Mint**: `3T1wVJporm2JpGojRV831TTQ1nexkBqFWhKnE5Lwedci` (authority field updated)
### Step 8: Test Token Minting (Verify Dual-Layer Control)
Verify that your Squad vault can still mint tokens after the authority transfer. This proves the dual-layer governance is working correctly: Squad vault → SPL Token Multisig → Token minting.
**Purpose of This Test:**
- **Verify Authority Transfer**: Confirm the SPL Token Multisig now controls mint authority
- **Prove Squad Control**: Demonstrate that Squad vault can still mint through the multisig
- **Validate Architecture**: Test the dual-layer governance model works as designed
**Account Breakdown from the Transaction (Example):**
| Account | Address | Description |
| ------- | ---------------------------------------------- | --------------------------------------------------- |
| **#1** | `3T1wVJporm2JpGojRV831TTQ1nexkBqFWhKnE5Lwedci` | **Token Mint** (writable - supply updated) |
| **#2** | `84FvKxAQpHtSaFLguFygoPPEaRorZpTjs8rmMCxuwqUF` | **Recipient's ATA** (writable - receives tokens) |
| **#3** | `6c5UGRRQzPvZY4ZJZJiPGRYMw5e32uX8GfFzvm35k3jK` | **SPL Token Multisig** (read-only - mint authority) |
| **#4** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault** (signer - multisig member) |
**What This Transaction Does:**
This is an **SPL Token Multisig Mint** operation that proves your dual-layer governance is working correctly. The Squad vault acts as a signer for the SPL Token Multisig that now controls mint authority.
**Key Details:**
- **Amount**: 1,000,000,000 smallest units = 1 token (with 9 decimals)
- **Recipient**: Your wallet address (`EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB`)
- **Mint Authority**: SPL Token Multisig (`6c5UGRRQzPvZY4ZJZJiPGRYMw5e32uX8GfFzvm35k3jK`)
- **Multisig Signer**: Squad vault (`59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY`)
- **Token Program**: SPL Token v1 (`TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`)
**Transaction Flow:**
1. **Authority Verification**: SPL Token program verifies the multisig has mint authority
2. **Signature Validation**: Program confirms Squad vault is an authorized multisig signer
3. **Token Creation**: 1 token is minted and added to total supply
4. **Token Transfer**: New tokens are deposited into your wallet's ATA
**Phase 2 Complete**: Save your variables:
## Phase 3: Solana Cross-Chain Setup
In this phase, you will configure the cross-chain connection and complete the CCIP setup.
### Step 1: Load Phase 1 Variables
Load Phase 1 variables and set the Ethereum Sepolia chain selector for cross-chain configuration.
**What This Step Does:**
- **Terminal Verification**: Confirms you're in the correct base58 generator repository
- **Variable Loading**: Imports EVM token and pool addresses from Phase 1
- **Chain Selector Setup**: Establishes Ethereum Sepolia chain selector for cross-chain configuration
### Step 2: Configure Cross-Chain Pool Settings
Configure your token pool for cross-chain transfers to Ethereum Sepolia. This process involves two sequential operations:
1. **Initialize Chain Remote Config**: Create the basic cross-chain configuration using [init_chain_remote_config](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#init_chain_remote_config)
2. **Edit Chain Remote Config**: Add the remote pool address using [edit_chain_remote_config](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#edit_chain_remote_config)
#### Step 2A: Initialize Chain Remote Config
Initialize the basic remote chain configuration for Ethereum Sepolia. Pool addresses must be empty at initialization and rate limits are not configured at this stage.
**Account Breakdown from the Transaction (Example):**
| Account | Address | Description |
| ------- | ---------------------------------------------- | --------------------------------------------------- |
| **#1** | `2pGY9WAjanpR3RnY5hQ1a23uDNomzFCAD5HMBgo8nH6M` | **Pool State PDA** (read-only - validation) |
| **#2** | `8zdWLvsAgLQkRmBDATR3j8xVQzcdwrRNZtPZH5kU37jg` | **Chain Remote Config PDA** (writable - created) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault** (signer, writable - pool authority) |
| **#4** | `11111111111111111111111111111111` | **System Program** (read-only - account creation) |
**What This Command Does:**
This initializes the **cross-chain configuration** for your Solana token to enable CCIP bridging to Ethereum Sepolia. This creates the foundational connection without rate limits or pool addresses.
**Key Details:**
- **Remote Chain**: Ethereum Sepolia (chain selector: `16015286601757825753`)
- **Token Mapping**: Links to ERC20 token `0x7c57A9d966c3E6e344621C512d510f76575640ED`
- **Decimal Precision**: 18 decimals (standard EVM token format)
- **Basic Setup**: No rate limits or pool addresses configured at this stage
- **Account Creation**: Creates a new Chain Remote Config PDA for this cross-chain relationship
**Transaction Flow:**
1. **Authority Verification**: Confirms Squad vault owns the pool state
2. **PDA Derivation**: Calculates Chain Remote Config PDA using pool state + chain selector
3. **Account Creation**: Creates new account with rent-exempt balance
4. **Basic Configuration**: Stores minimal cross-chain parameters (no rate limits, no pool addresses)
#### Step 2B: Edit Chain Remote Config (Add Remote Pool Address)
After initializing the chain remote config, add the remote pool address to enable bidirectional cross-chain transfers. This uses [`edit_chain_remote_config`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#edit_chain_remote_config) to specify the Ethereum pool address.
**Account Breakdown from the Transaction:**
| Account | Address | Description |
| ------- | ---------------------------------------------- | -------------------------------------------------------------- |
| **#1** | `2pGY9WAjanpR3RnY5hQ1a23uDNomzFCAD5HMBgo8nH6M` | **Pool State PDA** (read-only - authority validation) |
| **#2** | `8zdWLvsAgLQkRmBDATR3j8xVQzcdwrRNZtPZH5kU37jg` | **Chain Remote Config PDA** (writable - configuration updated) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault Authority** (signer, writable - fee payer) |
| **#4** | `11111111111111111111111111111111` | **System Program** (read-only - account operations) |
**Transaction Flow:**
1. **Authority Verification**: Confirms Squad vault owns the pool state
2. **PDA Access**: Updates the existing Chain Remote Config PDA
3. **Pool Address Addition**: Adds the remote pool to the the remote pool list for Ethereum Sepolia
### Step 3: Configure Rate Limits (Optional)
This command configures inbound and outbound rate limiting for token transfers between your Solana token and Ethereum Sepolia. Rate limits act as "token buckets" that control the flow of tokens across chains.
**Transaction Accounts (3 total):**
| Account | Address | Type | Purpose |
| ------- | ---------------------------------------------- | -------------------- | ----------------------------------------------------------------- |
| **#1** | `2pGY9WAjanpR3RnY5hQ1a23uDNomzFCAD5HMBgo8nH6M` | **Read-only** | **Pool State PDA** - Main pool configuration account |
| **#2** | `8zdWLvsAgLQkRmBDATR3j8xVQzcdwrRNZtPZH5kU37jg` | **Writable** | **Chain Config PDA** - Chain-specific config (stores rate limits) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Signer, Writable** | **Authority** - Your Squad vault that can modify rate limits |
### Step 4: Create Address Lookup Table
Create an Address Lookup Table (ALT) containing all accounts needed for CCIP router operations.
**What This Command Does:**
This command creates an **Address Lookup Table (ALT)** that stores frequently used accounts for CCIP operations. ALTs are a Solana optimization that reduces transaction size and costs by replacing full 32-byte addresses with small 1-byte indices.
**Account Breakdown from the Transaction:**
| Account | Address | Description |
| ------- | ---------------------------------------------- | --------------------------------------------------------- |
| **#1** | `2s6yx6xCBgEQBrv5h4dF7TQvvKUWTJBEsueqTnZZHLwY` | **ALT Account** (writable - being created) |
| **#2** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **ALT Authority** (signer - will own the table) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Fee Payer** (signer, writable - pays creation costs) |
| **#4** | `11111111111111111111111111111111` | **System Program** (read-only - handles account creation) |
**What This Command Does:**
This command creates the ALT infrastructure needed for efficient CCIP operations by storing frequently used account addresses in a lookup table.
**Transaction Flow:**
1. **ALT Creation**: Creates lookup table account `2s6yx6x...` with Squad Vault as authority
2. **Core Address Addition**: Adds essential CCIP accounts (programs, PDAs) to the table
3. **SPL Multisig Addition**: Adds your SPL multisig `$SOL_SPL_MULTISIG` via `--additional-addresses`
4. **Index Assignment**: Each address gets a unique 1-byte index for future reference
**ALT Contents (What Gets Stored):**
The created lookup table will contain indices for these accounts in this exact order:
| Index | Account Address | Purpose |
| ------ | ---------------------------------------------- | ----------------------------------------------------- |
| **0** | `2s6yx6xCBgEQBrv5h4dF7TQvvKUWTJBEsueqTnZZHLwY` | **ALT Address (Self-Reference)** |
| **1** | `4maPuX2fcDmCkQv1Qa6RmQa2yGpeZLxiqKy9ybwazGft` | **Token Admin Registry PDA** |
| **2** | `41FGToCmdaWa1dgZLKFAjvmx6e6AjVTX7SVRibvsMGVB` | **Pool Program ID** |
| **3** | `2pGY9WAjanpR3RnY5hQ1a23uDNomzFCAD5HMBgo8nH6M` | **Pool State PDA** |
| **4** | `BKkeydRQWKDc5dR7euwhYi47TDLV99o8pD83ncGA2LdZ` | **Pool Signer's Associated Token Account** |
| **5** | `8NTqDH8dFj7aU5FBWeNMJwjsR1gZMR7criaEaDMEE24r` | **Pool Signer PDA** |
| **6** | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | **Token Program ID (SPL Token v1)** |
| **7** | `3T1wVJporm2JpGojRV831TTQ1nexkBqFWhKnE5Lwedci` | **Token Mint** |
| **8** | `GTz54AfhMRkKzFVse8jX8rjAx21fA86ZmVBF1v5VUC3u` | **Fee Quoter Token Config PDA** |
| **9** | `H6ZviaabTYZqUPgiSoMDbeVthcNW9ULcAuUu3zRLFqDR` | **Router External Token Pools Signer PDA** |
| **10** | `6c5UGRRQzPvZY4ZJZJiPGRYMw5e32uX8GfFzvm35k3jK` | **SPL Token Multisig** ← **Your Additional Address!** |
**Transaction Efficiency Impact:**
- **Before ALT**: Each account = 32 bytes (10 accounts = 320 bytes)
- **After ALT**: Each account = 1 byte index (10 accounts = 10 bytes)
- **Savings**: \~310 bytes per future CCIP transaction!
**Set Variables:**
Copy the ALT address from the transaction output and set the environment variable:
### Step 5: Register Pool with Router
Register your token pool with the CCIP Router using [set_pool](/ccip/api-reference/svm/v1.6.0/router#set_pool). This enables the router to route cross-chain transfers through your token pool.
**What This Command Does:**
- **Pool Registration**: Connects your token pool to the CCIP Router for cross-chain operations
- **Lookup Table Integration**: Uses the ALT created in the previous step for efficient account management
- **Router Configuration**: Enables the router to call your pool's token operations
**Account Breakdown from the Transaction:**
| Account | Address | Description |
| ------- | ---------------------------------------------- | ----------------------------------------------------------- |
| **#1** | `3Yrg9E4ySAeRezgQY99NNarAmFLtixapga9MZb6y2dt3` | **Router Config PDA** (read-only - configuration reference) |
| **#2** | `4maPuX2fcDmCkQv1Qa6RmQa2yGpeZLxiqKy9ybwazGft` | **Token Admin Registry PDA** (writable - pool registration) |
| **#3** | `3T1wVJporm2JpGojRV831TTQ1nexkBqFWhKnE5Lwedci` | **Token Mint** (read-only - token identification) |
| **#4** | `2s6yx6xCBgEQBrv5h4dF7TQvvKUWTJBEsueqTnZZHLwY` | **Address Lookup Table** (read-only - ALT registration) |
| **#5** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault Authority** (signer, writable - admin & fees) |
**What This Command Does:**
This command **registers your token pool** with the CCIP Router. It configures the router to use your Address Lookup Table and specifies which accounts need write access during CCIP operations.
**Key Configuration:**
- **ALT Integration**: Uses the lookup table `2s6yx6x...` you just created
- **Writable Indexes**: `[3,4,7]` - Specific accounts from the ALTthat need write access during CCIP operations
**Transaction Flow:**
1. **Authority Verification**: Confirms Squad Vault has CCIP token admin permissions
2. **Pool Registration**: Updates Token Admin Registry with pool configuration
3. **ALT Association**: Links your token with the specific lookup table
4. **Permission Setup**: Defines which accounts can be modified during cross-chain operations
**Writable Indexes Analysis: `[3,4,7]`**
Based on the ALT order established earlier:
| **Index** | **ALT Account** | **Why Writable** |
| --------- | --------------- | ------------------------------------------------ |
| **3** | Pool State PDA | Gets updated during cross-chain operations |
| **4** | Pool Token ATA | Tokens get minted/burned during transfers |
| **7** | Token Mint | Supply gets modified during mint/burn operations |
**Phase 3 Complete**: Save all variables:
## Phase 4: EVM Cross-Chain Setup
Configure the Ethereum pool to recognize the Solana chain and set production rate limits.
### Step 1: Load Phase 3 Variables
Ensure you're in Terminal 2 (Hardhat) and load all the variables from Phase 3.
### Step 2: Configure Cross-Chain Settings
Configure the Ethereum pool to recognize the Solana chain and set production rate limits using [`applyChainUpdates`](/ccip/api-reference/evm/v1.6.2/token-pool#applychainupdates).
## Phase 5: Testing and Validation
### Step 1: Load Phase 4 Variables
Before testing cross-chain transfers, ensure your terminal environment is properly configured:
### Transfer Direction 1: Solana → Ethereum
Test the production multisig setup with a cross-chain transfer from Solana Devnet to Ethereum Sepolia:
#### Prepare for Testing
Before testing cross-chain transfers, you need to create the pool's Associated Token Account (ATA) and prepare your tokens:
##### Check Token Balance
##### Create Pool Associated Token Account
##### Delegate Token Authority
#### Transfer tokens from Solana Devnet to Ethereum Sepolia
#### Monitor and Verify Transaction
Upon successful execution, the system generates critical tracking identifiers for transaction monitoring and verification.
**Transaction Identifiers:**
- **Transaction Signature**: `4T6coWNspJ7TDhsUFVZ7S1huzVxy6dSjbPfrFw4THGRBEt8X9UYfFggrxYLD4Hnm11Hqdnzg9vJTbrNSjp7ycRvf`
- **CCIP Message ID**: `0x21d6ca53d5ab59caa9d5a1d58a5f8040f9695043dcad5ddc0059710dff0113c3`
**CCIP Explorer** (Primary monitoring interface):
```
https://ccip.chain.link/msg/0x21d6ca53d5ab59caa9d5a1d58a5f8040f9695043dcad5ddc0059710dff0113c3
```
The CCIP Explorer provides comprehensive transaction visibility:
- Source chain (Solana) transaction confirmation
- CCIP message processing and routing
- Destination chain (Ethereum) message delivery
- Token minting completion on Ethereum
**Solana Explorer** (Source chain verification):
```
https://explorer.solana.com/tx/4T6coWNspJ7TDhsUFVZ7S1huzVxy6dSjbPfrFw4THGRBEt8X9UYfFggrxYLD4Hnm11Hqdnzg9vJTbrNSjp7ycRvf?cluster=devnet
```
### Transfer Direction 2: Ethereum → Solana
#### Transfer tokens from Ethereum Sepolia to Solana Devnet
#### Monitor and Verify Transaction
Upon successful execution, the system generates distinct tracking identifiers for comprehensive monitoring across both blockchain networks.
**Transaction Identifiers:**
- **Ethereum Transaction Hash**: `0x55e6e9c295c7d32bc0d708f3363e42aad37298f9f30b4db0889e06bfac761157`
- **CCIP Message ID**: `0x11ef3640d8030bae75c2e4ca863a122bbf28fbbcd1dc83be0f77a25091442c72`
**CCIP Explorer** (Primary monitoring interface):
```
https://ccip.chain.link/msg/0x11ef3640d8030bae75c2e4ca863a122bbf28fbbcd1dc83be0f77a25091442c72
```
The CCIP Explorer provides comprehensive transaction visibility:
- Source chain (Ethereum) transaction confirmation
- CCIP message processing and routing
- Destination chain (Solana) message delivery
- Token minting completion on Solana network
**Ethereum Sepolia Explorer** (Source chain verification):
```
https://sepolia.etherscan.io/tx/0x55e6e9c295c7d32bc0d708f3363e42aad37298f9f30b4db0889e06bfac761157
```
### Test Solana Rate Limit Enforcement (Optional)
The transaction correctly failed with `RLMaxCapacityExceeded` error because:
- **Attempted**: 25 tokens (`25000000000` in smallest units)
- **Limit**: 20 tokens maximum capacity (`20000000000`)
- **Result**: Rate limiter blocked the excessive transfer
**Key Error Details:**
- **Error Code**: `RLMaxCapacityExceeded` (Error Number: 6015)
- **Source**: `programs/base-token-pool/src/rate_limiter.rs:48`
- **Meaning**: The token bucket capacity was exceeded
This demonstrates that your rate limits are properly configured and actively protecting against transfers that exceed the configured limits.
### Test EVM Rate Limit Enforcement (Optional)
Test rate limits from the EVM side (Ethereum to Solana) using the same 25-token limit.
The transaction correctly failed because:
- **Attempted**: 25 tokens (`25000000000000000000` wei - 18 decimals)
- **Limit**: 18 tokens maximum capacity
- **Error Selector**: [`0x1a76572a` (TokenMaxCapacityExceeded)](/ccip/api-reference/evm/v1.6.2/errors#rate-limiter-errors)
- **Decoded Values**: Capacity: `18000000000000000000`, Attempted: `25000000000000000000`, Token: `0x9cE471d0a7bE21ee32276dde49104fe02c812906`
This demonstrates that rate limits are enforced on both chains, protecting the cross-chain bridge from excessive transfers. The EVM error selector corresponds to the `TokenMaxCapacityExceeded` error in the [CCIP EVM v1.6.0 errors reference](/ccip/api-reference/evm/v1.6.2/errors).
---
# Cross-Chain Token Setup: LockRelease Pool with Squads Governance
Source: https://docs.chain.link/ccip/tutorials/svm/cross-chain-tokens/lock-release-multisig
Last Updated: 2025-09-17
This tutorial demonstrates how to implement cross-chain tokens using the **[Lock and Mint](/ccip/concepts/cross-chain-token/overview#lock-and-mint)** mechanism with production-grade governance.
## What You Will Build
You'll create a cross-chain token system with two key components:
- **LockRelease Pool on Solana Devnet**: Locks tokens when sending from Solana, releases tokens when receiving to Solana
- **BurnMint Pool on Ethereum Sepolia**: Mints tokens when receiving from Solana, burns tokens when sending to Solana
**Cross-Chain Transfer Flow:**
**Solana → Ethereum (Lock and Mint):**
1. **Lock**: LockRelease pool locks tokens in the token pool on Solana
2. **Mint**: BurnMint pool mints tokens on Ethereum
**Ethereum → Solana (Burn and Unlock):**
1. **Burn**: BurnMint pool burns tokens on Ethereum
2. **Unlock**: LockRelease pool releases tokens from the token pool on Solana
## Governance Architecture
| Component | Implementation | Governance Model |
| ------------------------ | ------------------------------------ | ------------------------- |
| **Ethereum Sepolia** | ERC20 token with CCIP BurnMint pool | EOA-controlled (tutorial) |
| **Solana Devnet** | SPL token with CCIP LockRelease pool | Squads multisig |
| **Cross-Chain Transfer** | Bidirectional CCIP token transfers | Autonomous operations |
This tutorial demonstrates **LockRelease pool governance** using Squads multisig for production-grade security:
**Key Governance Roles:**
- **Pool Owner** (Squads): Controls pool configuration, rate limits, and operational parameters
- **Rebalancer** (Squads): Manages liquidity provisioning across chains
- **Token Mint Authority**: Remains with original token creator (no transfer required)
**Production Benefits:**
- **No mint authority complexity**: LockRelease pools don't need token minting permissions
- **Focused governance**: Squads handles pool operations and liquidity management only
- **Production security**: Multi-signature approval for all critical pool operations
For complete details on token handling mechanisms, see [Token Handling Mechanisms](/ccip/concepts/cross-chain-token/overview#token-handling-mechanisms).
## Prerequisites
### System Requirements
Install these tools before starting:
**Package Managers:**
- **Node.js v20+**: Required for all repositories
- **pnpm**: Required for base58 Generator (`npm install -g pnpm`)
- **yarn**: Required for Solana Starter Kit (`npm install -g yarn`)
**Solana Development:**
- **Solana CLI**: [Installation guide](https://docs.solana.com/cli/install-solana-cli-tools)
**Wallets:**
- **Solana wallet**: [Phantom](https://phantom.app/) or [Backpack](https://backpack.app/) for Devnet operations
- **Ethereum wallet**: [MetaMask](https://metamask.io/) for Sepolia testnet operations
### Repository Setup
Clone and install dependencies for all three repositories:
**Terminal 1: base58 Generator**
```bash
git clone https://github.com/smartcontractkit/ccip-solana-bs58-generator.git
cd ccip-solana-bs58-generator
pnpm install
```
**Terminal 2: EVM Hardhat**
```bash
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
npm install
npm run compile
```
**Terminal 3: Solana Starter Kit**
```bash
git clone https://github.com/smartcontractkit/solana-starter-kit.git
cd solana-starter-kit
yarn install
```
**Create and configure the .env file:**
```bash
# Create .env file in the project root
cat > .env << 'EOF'
EVM_PRIVATE_KEY=your_private_key_here
EVM_RPC_URL=your_rpc_url_here
EOF
```
Replace the placeholder values with:
- `EVM_PRIVATE_KEY`: Your Ethereum wallet private key (for Sepolia testnet operations)
- `EVM_RPC_URL`: Your Ethereum RPC URL (from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another provider)
### Environment Configuration
**Solana Configuration:**
```bash
# Set Solana CLI to devnet
solana config set --url https://api.devnet.solana.com
# Create keypair (ONLY IF NEEDED)
solana-keygen new --outfile ~/.config/solana/id.json
# Check Config
solana config get
# Fund wallet
solana airdrop 3
```
**Ethereum Sepolia Configuration:**
```bash
# In Terminal 2 (Hardhat directory)
npx env-enc set-pw
npx env-enc set
```
Required environment variables:
- `ETHEREUM_SEPOLIA_RPC_URL`: RPC endpoint ([Alchemy](https://www.alchemy.com/) or [Infura](https://www.infura.io/))
- `PRIVATE_KEY`: Testnet wallet private key
- `ETHERSCAN_API_KEY`: API key from [Etherscan](https://etherscan.io/apis)
**Testnet Tokens:**
- **Solana Devnet**: Use `solana airdrop 3` for SOL
- **Ethereum Sepolia**: Use [Chainlink faucets](https://faucets.chain.link/) for LINK and ETH
### Squads Multisig Setup
**Step 1: Prepare Signers**
Create multiple wallet addresses for your multisig signers:
```bash
# Create additional signers using Solana CLI
solana-keygen new --outfile ~/.config/solana/signer2.json
solana-keygen new --outfile ~/.config/solana/signer3.json
# Get signer addresses
solana address --keypair ~/.config/solana/id.json
solana address --keypair ~/.config/solana/signer2.json
solana address --keypair ~/.config/solana/signer3.json
# Fund signers (minimum 0.1 SOL each for transaction fees)
solana transfer 0.1 --allow-unfunded-recipient
solana transfer 0.1 --allow-unfunded-recipient
```
Alternatively, create signers in Phantom wallet and fund them:
```bash
# Transfer SOL to Phantom-created addresses
solana transfer 0.5 --allow-unfunded-recipient
```
**Step 2: Create Your Squad**
1. Visit [devnet.squads.so](https://devnet.squads.so)
2. Connect your Solana wallet (e.g., Phantom/Backpack)
3. Click "Create New Squad"
4. Configure your multisig:
- **Squad Name**: Choose a descriptive name (e.g., "CCIP Token Governance")
- **Members**: Add wallet addresses of all signers
- **Threshold**: Set approval threshold (recommended: 2/3 or 3/5)
**Step 3: Record Critical Addresses**
After Squad creation, navigate to Settings tab and record these addresses.
**In Terminal 1 (base58 Generator), export the vault address:**
```bash
# CRITICAL: Use the VAULT address, NOT the multisig address
export SOL_SQUAD_VAULT_MULTISIG="YOUR_VAULT_ADDRESS_HERE"
```
Verify the export:
```bash
echo "Squads Vault Address: $SOL_SQUAD_VAULT_MULTISIG"
```
**Step 4: Test Your Squad**
Perform a small test transaction to verify setup:
1. Send a small amount of SOL to your Squad vault
2. Create a test transaction in Squads UI (e.g., SOL transfer to your wallet)
3. Confirm all signers can approve transactions
4. Execute the transaction
For detailed setup guidance, see the [Squads Documentation](https://docs.squads.so/).
### Token Creation Option
## Tutorial Approach
This tutorial implements production-grade cross-chain tokens using a three-terminal workflow across specialized repositories:
| Terminal | Repository | Purpose | Commands |
| -------------- | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------------- |
| **Terminal 1** | [CCIP Solana base58 Generator](https://github.com/smartcontractkit/ccip-solana-base58-generator) | Generate governance transactions | `pnpm bs58` |
| **Terminal 2** | [Smart Contract Examples (Hardhat)](https://github.com/smartcontractkit/smart-contract-examples/tree/main/ccip/cct/hardhat) | Deploy EVM components | `npx hardhat` |
| **Terminal 3** | [Solana Starter Kit](https://github.com/smartcontractkit/solana-starter-kit) | Test cross-chain transfers | `yarn` |
**Note**: Each repository contains comprehensive READMEs with detailed technical explanations and troubleshooting guides.
### Key Implementation Notes
- **Terminal 1** generates base58-encoded transactions for Squads multisig governance
- **Terminal 2** uses EOA for tutorial simplicity; production deployments should use multisig wallets (e.g., Safe)
- **Terminal 3** validates end-to-end cross-chain functionality
### base58 Transaction Execution Workflow
**Transaction Generation and Validation:**
1. **CLI Simulation**: Each base58 transaction is automatically simulated during generation
2. **Error Handling**: If simulation fails, error logs appear in terminal - do not upload failed transactions to Squads
3. **Success Indicator**: Successful simulation shows transaction preview and base58 output
**Squads Multisig Execution Process:**
1. **Propose**: A signer imports the base58 transaction into Squads UI → "Add instruction" → "Import base58 encoded tx" → **Initiate Transaction**
2. **Approve**: Required threshold (M) of signers review and approve the transaction
3. **Simulate** (Recommended): Before execution, signers can simulate through Squads interface to preview onchain effects
4. **Execute**: After threshold approval, any signer can execute the transaction
### Environment Variables
Variables use prefixes to prevent confusion across repositories:
| Prefix | Usage | Examples |
| -------- | ------------------ | ------------------------------------------ |
| `ETH_*` | Ethereum addresses | `ETH_TOKEN_ADDRESS`, `ETH_POOL_ADDRESS` |
| `SOL_*` | Solana addresses | `SOL_TOKEN_MINT`, `SOL_POOL_ADDRESS` |
| `CCIP_*` | Protocol constants | `CCIP_POOL_PROGRAM`, `CCIP_ROUTER_PROGRAM` |
## Phase 1: EVM Chain Setup (Ethereum Sepolia)
In this phase, you will deploy ERC20 tokens and configure CCIP BurnMint pools on Ethereum Sepolia. This setup is identical across all Path A variants and provides the foundation for cross-chain operations.
### Step 1: Prepare EVM Environment
First, set up your terminal and verify your environment:
### Step 2: Deploy ERC20 Token
Deploy your cross-chain token on Ethereum Sepolia:
Set the token address variable:
### Step 3: Deploy and Configure CCIP BurnMint Pool
Deploy the BurnMint token pool:
Set the pool address and configure:
### Step 4: Mint Initial Token Supply
Mint tokens for testing:
### Step 5: Claim CCIP Admin Role
In this step, you will use the `claimAdmin` task to register your EOA as the administrator for the deployed token on Ethereum Sepolia. This process involves calling the `RegistryModuleOwnerCustom` contract, which will fetch the CCIP admin of the token and set it up as the admin in the registry.
### Step 6: Accept CCIP Admin Role
In this step, you will use the `acceptAdminRole` task to accept the admin role for the deployed token on Ethereum Sepolia. Once you have claimed the role, accepting the role finalizes your control over the token administration.
### Step 7: Register Pool with Token
In this step, you will use the `setPool` task to register the BurnMint token pool with the token in Ethereum's TokenAdminRegistry contract. This function sets the pool contract address for the token, enabling it for CCIP cross-chain transfers. Only the token administrator can call this function.
**Phase 1 Complete**: Save your variables:
## Phase 2: Solana Setup with Production Dual Multisig Governance
In this phase, you will implement the **production-grade multisig governance** architecture on Solana Devnet.
### Step 1: Prepare base58 Environment
### Step 2: Create SPL Token (Layer 2 Foundation)
This command generates a transaction that creates a new SPL token mint with the following features:
- **Mint Authority**: Set to your Squad vault multisig for governance control
- **Metaplex Metadata**: Includes token name, symbol, and URI for cross-platform compatibility
- **Initial Supply**: Automatically mints 5,000 tokens (5,000,000,000,000 smallest units) to your wallet
- **Deterministic Address**: Uses a seed-based approach for predictable mint addresses
Generate the SPL token creation transaction (customize parameters or reuse the example):
After execution, set the token mint:
### Step 3: Initialize LockRelease Token Pool
This command creates only one new account and establishes references to existing infrastructure:
**What Gets Created:**
- **Pool State PDA**: Creates the onchain Pool State account that stores your token's CCIP configuration
**What Gets Referenced (Not Created):**
- **Authority Assignment**: Sets your Squad vault as the pool owner with full configuration control
- **Router Integration**: References the existing global CCIP router and RMN (Risk Management Network)
- **Pool Signer PDA**: The Pool Signer PDA is programmatically derived - it will be used on-demand during cross-chain operations
The `initialize-pool` instruction creates only the Pool State account for your SPL token mint, establishing your Squad vault as the pool owner while referencing existing global infrastructure.
Generate the pool initialization transaction:
**Account Breakdown from the Transaction (Example):**
| Account | Address | Purpose |
| ------- | ---------------------------------------------- | ----------------------------------------------------------- |
| **#1** | `9VnLxEtwJgnGALzhhXJTtRr4GF83z95aCypPiRFGi9d1` | **Pool State PDA** - Your main pool configuration |
| **#2** | `FVJeMAQSH9dJvVmhe8NHC2DHTEEqT5cgVXTL5CYYmKu1` | Token Mint (your token) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | Authority (your Squad vault) |
| **#4** | `11111111111111111111111111111111` | System Program |
| **#5** | `8eqh8wppT9c5rw4ERqNCffvU6cNFJWff9WmkcYtmGiqC` | CCIP Pool Program |
| **#6** | `AeMEQshfd7w72oGt5cMn8196zod6gQPWskbf5BY83W1B` | Program Data PDA |
| **#7** | `6Nh9CWRGW69VJ7mnD9ELjm6BMkbvoGvVse2HuNuaHPqm` | **Global Config PDA** - Program-wide configuration settings |
**Key Addresses You Need:**
- **Account #1** → `SOL_POOL_ADDRESS` (writable account = pool state)
- **Pool Signer PDA** → Must be derived separately (NOT in transaction accounts)
After execution, set the pool state address from the transaction:
```bash
# Set the pool state address from Account #1 in the transaction above
export SOL_POOL_ADDRESS="9VnLxEtwJgnGALzhhXJTtRr4GF83z95aCypPiRFGi9d1"
```
### Step 4: Derive Pool Signer PDA
The Pool Signer PDA is the critical address that will serve as the autonomous signing authority for cross-chain mint and burn operations.
Set the Pool Signer PDA address for use in subsequent steps:
### Step 5: Register CCIP Administrator
This two-step process establishes your Squad vault as the CCIP token administrator, enabling you to enable your token in CCIP. Since your Squad vault currently holds the mint authority, you can complete this registration using the [self-service registration flow](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow) without external assistance.
**Why This Works**: The Router's [owner_propose_administrator](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow) instruction verifies onchain that the caller matches the token's `mint_authority` field. Your Squad vault has this authority, enabling [PATH A self-service registration](/ccip/concepts/cross-chain-token/svm/integration-guide#path-a-full-self-service-mint-authority-controlled).
#### Sub-step 5a: Propose Administrator
The [owner_propose_administrator](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow) instruction creates a TokenAdminRegistry PDA for your token and sets your Squad vault as the [pending administrator](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow):
**Account Breakdown from the Transaction (Example):**
| Account | Address | Description |
| ------- | ---------------------------------------------- | --------------------------------------------------------------------- |
| **#1** | `3Yrg9E4ySAeRezgQY99NNarAmFLtixapga9MZb6y2dt3` | Router Config PDA (read-only) |
| **#2** | `89Jy2ZEz6LcvBPVQgR2YxPYVoF1sLugNRs3havQP8SvF` | **🎯 Token Admin Registry PDA** - Gets created/updated for your token |
| **#3** | `FVJeMAQSH9dJvVmhe8NHC2DHTEEqT5cgVXTL5CYYmKu1` | Token Mint (your token) |
| **#4** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | Authority/Payer (your Squad vault) |
| **#5** | `11111111111111111111111111111111` | System Program |
**What This Transaction Does:**
This transaction proposes your Squad vault as the administrator for your token in the Router's Token Admin Registry. **Account #2** is the Token Admin Registry PDA that stores who has administrative control over your token's cross-chain operations.
#### Sub-step 5b: Accept Administrator Role
The [accept_admin_role](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow) instruction completes the [registration process](/ccip/concepts/cross-chain-token/svm/registration-administration#self-service-registration-flow) by having the pending administrator (your Squad vault) explicitly accept the CCIP token administrator role:
**Account Breakdown from the Transaction (Example):**
| Account | Address | Description |
| ------- | ---------------------------------------------- | ------------------------------------------------------------ |
| **#1** | `3Yrg9E4ySAeRezgQY99NNarAmFLtixapga9MZb6y2dt3` | Router Config PDA (read-only) |
| **#2** | `89Jy2ZEz6LcvBPVQgR2YxPYVoF1sLugNRs3havQP8SvF` | **🎯 Token Admin Registry PDA** - Updates status to "active" |
| **#3** | `FVJeMAQSH9dJvVmhe8NHC2DHTEEqT5cgVXTL5CYYmKu1` | Token Mint (your token) |
| **#4** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | Authority (your Squad vault - must be pending admin) |
**What This Transaction Does:**
This is the **acceptance step** of the two-phase administrator registration. It updates the Token Admin Registry PDA (Account #2) from "pending administrator" to "active administrator" status. Your Squad vault explicitly accepts the administrator role, completing the secure registration process.
**Key Difference**: Only 4 accounts (vs. 5 in the propose step) because we're updating an existing registry entry, not creating a new one.
After both transactions are executed, your Squad vault will be the CCIP token administrator. See [Registration & Administration](/ccip/concepts/cross-chain-token/svm/registration-administration) for more details.
### Step 6: Create Squad Vault Token Account
Create the Associated Token Account (ATA) for the Squad vault. This account will receive the minted tokens that will later be used for pool liquidity.
**Transaction Details:**
- **Squad Vault ATA**: `G7yVJJKDRBQDdbFttmA3G3oytFKBcsZH8VMdJWBXJKJB` (example)
- **Owner**: Squad vault PDA
### Step 7: Mint Tokens for Pool Liquidity
Mint tokens to your Squad vault to prepare for LockRelease pool liquidity provisioning. Since LockRelease pools require liquidity management, we'll mint tokens that the Squad vault (acting as rebalancer) can later provide to the pool.
**Purpose:**
- **Liquidity Preparation**: Create tokens that will be used for pool liquidity management
- **Rebalancer Setup**: Squad vault will serve as the rebalancer role for the LockRelease pool
- **Pool Operations**: LockRelease pools need liquidity to fulfill cross-chain transfer requests
**Account Breakdown from the Transaction (Example):**
| Account | Address | Description |
| ------- | ---------------------------------------------- | -------------------------------------------------- |
| **#1** | `FVJeMAQSH9dJvVmhe8NHC2DHTEEqT5cgVXTL5CYYmKu1` | **Token Mint** (writable - supply updated) |
| **#2** | `G7yVJJKDRBQDdbFttmA3G3oytFKBcsZH8VMdJWBXJKJB` | **Squad vault's ATA** (writable - receives tokens) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault** (signer) |
**What This Transaction Does:**
This is a standard SPL token mint operation that mints tokens for the Squad vault to use as liquidity for the LockRelease pool. Since the Squad vault retains mint authority, it can directly mint tokens without requiring multisig operations.
**Key Details:**
- **Amount**: 100,000,000,000 smallest units = 100 tokens (with 9 decimals)
- **Recipient**: Squad vault address (will serve as rebalancer for pool liquidity)
- **Mint Authority**: Squad vault (retains original mint authority)
- **Token Program**: SPL Token v1 (`TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`)
**Transaction Flow:**
1. **Authority Verification**: SPL Token program verifies Squad vault has mint authority
2. **Token Creation**: 100 tokens are minted and added to total supply
3. **Token Transfer**: New tokens are deposited into Squad vault's ATA
4. **Liquidity Preparation**: Tokens are now available for pool liquidity operations
### Step 8: Set Rebalancer Role
Configure the Squad vault as the rebalancer for the LockRelease pool. The rebalancer role is required before any liquidity operations can be performed.
**What This Command Does:**
- **Rebalancer Assignment**: Designates the Squad vault as the authorized liquidity manager
- **Pool Configuration**: Updates the pool state to allow liquidity operations
- **Governance Setup**: Establishes who can provide and withdraw pool liquidity
**Account Breakdown:**
| Account | Address | Description |
| ------- | ---------------------------------------------- | -------------------------------------------- |
| **#1** | `9VnLxEtwJgnGALzhhXJTtRr4GF83z95aCypPiRFGi9d1` | **Pool state PDA** (writable) |
| **#2** | `FVJeMAQSH9dJvVmhe8NHC2DHTEEqT5cgVXTL5CYYmKu1` | **Token Mint** (read-only - your token mint) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault** (signer) |
### Step 9: Create Pool Token Account
Create the Associated Token Account (ATA) for the Pool Signer PDA. This account will hold the pool's token reserves for cross-chain operations.
**Transaction Details:**
- **Pool ATA**: `A57bH91QkGuARh37JTN41fd3Vwcmf2bLzVHAVPB8Gaeg` (example)
- **Owner**: Pool Signer PDA
### Step 10: Set Can Accept Liquidity
Configure the LockRelease pool to accept liquidity from the rebalancer. This setting must be enabled before the pool can receive liquidity provisions.
**What This Command Does:**
- **Liquidity Configuration**: Enables the pool to accept liquidity from the authorized rebalancer
- **Pool Setting**: Updates the `can_accept_liquidity` flag to `true`
- **Required Setup**: Must be completed before providing liquidity to the pool
**Account Breakdown:**
| Account | Address | Description |
| ------- | ---------------------------------------------- | ----------------------------------------------------- |
| **#1** | `9VnLxEtwJgnGALzhhXJTtRr4GF83z95aCypPiRFGi9d1` | **Pool State PDA** (writable - configuration updated) |
| **#2** | `FVJeMAQSH9dJvVmhe8NHC2DHTEEqT5cgVXTL5CYYmKu1` | **Token Mint** (read-only) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault** (signer - pool owner authority) |
**Transaction Flow:**
1. **Authority Verification**: Confirms Squad vault is the pool owner
2. **Configuration Update**: Sets `can_accept_liquidity` flag to `true`
3. **Pool Preparation**: Enables the pool to accept liquidity from the rebalancer
### Step 11: Approve Pool Signer as Delegate
Approve the Pool Signer PDA to spend tokens from the Squad vault's ATA. This delegation is required for the pool to transfer tokens during liquidity operations.
**What This Command Does:**
- **Token Delegation**: Authorizes Pool Signer PDA to spend tokens from Squad vault's ATA
- **Liquidity Permission**: Enables the pool to access Squad vault's tokens for liquidity operations
- **Required Setup**: Must be completed before the pool can provide liquidity
**Account Breakdown:**
| Account | Address | Description |
| ------- | ---------------------------------------------- | --------------------------------------------------- |
| **#1** | `G7yVJJKDRBQDdbFttmA3G3oytFKBcsZH8VMdJWBXJKJB` | **Squad Vault ATA** (writable - delegation updated) |
| **#2** | `E8odUv4V4DXy3RWvkNYF7H33X9J56RtsFp4ExVXB86UA` | **Pool Signer PDA** (read-only - delegate) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault** (signer - token account owner) |
**Transaction Flow:**
1. **Authority Verification**: Confirms Squad vault owns the token account
2. **Delegation Setup**: Authorizes Pool Signer PDA to spend up to 50 tokens
### Step 12: Provide Liquidity to Pool
Provide initial liquidity to the LockRelease pool using the tokens minted in Step 7. Liquidity management is essential to ensure that for inbound token transfers (when Solana is the destination chain), the pool has sufficient token balance in its reserves to transfer tokens to receivers.
**What This Command Does:**
- **Liquidity Transfer**: Moves tokens from Squad vault's ATA to the pool's ATA
- **Pool Funding**: Provides the pool with tokens needed for cross-chain operations
- **Rebalancer Operation**: Uses the rebalancer role set in Step 7
**Account Breakdown:**
| Account | Address | Description |
| ------- | ---------------------------------------------- | --------------------------------------------------- |
| **#1** | `9VnLxEtwJgnGALzhhXJTtRr4GF83z95aCypPiRFGi9d1` | **Pool State PDA** (read-only) |
| **#2** | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | **Token Program** (read-only) |
| **#3** | `FVJeMAQSH9dJvVmhe8NHC2DHTEEqT5cgVXTL5CYYmKu1` | **Token Mint** (writable) |
| **#4** | `E8odUv4V4DXy3RWvkNYF7H33X9J56RtsFp4ExVXB86UA` | **Pool Signer PDA** (read-only) |
| **#5** | `A57bH91QkGuARh37JTN41fd3Vwcmf2bLzVHAVPB8Gaeg` | **Pool Token ATA** (writable - receives liquidity) |
| **#6** | `G7yVJJKDRBQDdbFttmA3G3oytFKBcsZH8VMdJWBXJKJB` | **Squad Vault ATA** (writable - provides liquidity) |
| **#7** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault** (signer - rebalancer authority) |
**Transaction Flow:**
1. **Authority Verification**: Confirms Squad vault has rebalancer permissions
2. **Token Transfer**: Moves 50 tokens from Squad vault ATA to pool ATA
3. **Pool Update**: Updates pool's available liquidity balance
4. **Ready for Operations**: Pool can now fulfill cross-chain transfer requests
**Phase 2 Complete**: Save your variables:
## Phase 3: Solana Cross-Chain Setup
In this phase, you will configure the cross-chain connection and complete the CCIP setup.
### Step 1: Load Phase 1 Variables
Load Phase 1 variables and set the Ethereum Sepolia chain selector for cross-chain configuration.
**What This Step Does:**
- **Terminal Verification**: Confirms you're in the correct base58 generator repository
- **Variable Loading**: Imports EVM token and pool addresses from Phase 1
- **Chain Selector Setup**: Establishes Ethereum Sepolia chain selector for cross-chain configuration
### Step 2: Configure Cross-Chain Pool Settings
Configure your token pool for cross-chain transfers to Ethereum Sepolia. This process involves two sequential operations:
1. **Initialize Chain Remote Config**: Create the basic cross-chain configuration using [init_chain_remote_config](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#init_chain_remote_config)
2. **Edit Chain Remote Config**: Add the remote pool address using [edit_chain_remote_config](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#edit_chain_remote_config)
#### Step 2A: Initialize Chain Remote Config
Initialize the basic remote chain configuration for Ethereum Sepolia. Pool addresses must be empty at initialization and rate limits are not configured at this stage.
**Account Breakdown from the Transaction (Example):**
| Account | Address | Description |
| ------- | ---------------------------------------------- | --------------------------------------------------- |
| **#1** | `9VnLxEtwJgnGALzhhXJTtRr4GF83z95aCypPiRFGi9d1` | **Pool State PDA** (read-only - validation) |
| **#2** | `DzPjdCvEgJq6tyTvQtuh6i8WtPn6gWGmhaRM3DnCvwq9` | **Chain Remote Config PDA** (writable - created) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault** (signer, writable - pool authority) |
| **#4** | `11111111111111111111111111111111` | **System Program** (read-only - account creation) |
**What This Command Does:**
This initializes the **cross-chain configuration** for your Solana token to enable CCIP bridging to Ethereum Sepolia. This creates the foundational connection without rate limits or pool addresses.
**Key Details:**
- **Remote Chain**: Ethereum Sepolia (chain selector: `16015286601757825753`)
- **Token Mapping**: Links to ERC20 token `0x563eb47F0D8bE95CAF70ec2b7bB9Cdca6f045715`
- **Decimal Precision**: 18 decimals (standard EVM token format)
- **Basic Setup**: No rate limits or pool addresses configured at this stage
- **Account Creation**: Creates a new Chain Remote Config PDA for this cross-chain relationship
**Transaction Flow:**
1. **Authority Verification**: Confirms Squad vault owns the pool state
2. **PDA Derivation**: Calculates Chain Remote Config PDA using pool state + chain selector
3. **Account Creation**: Creates new account with rent-exempt balance
4. **Basic Configuration**: Stores minimal cross-chain parameters (no rate limits, no pool addresses)
#### Step 2B: Edit Chain Remote Config (Add Remote Pool Address)
After initializing the chain remote config, add the remote pool address to enable bidirectional cross-chain transfers. This uses [`edit_chain_remote_config`](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool#edit_chain_remote_config) to specify the Ethereum pool address.
**Account Breakdown from the Transaction:**
| Account | Address | Description |
| ------- | ---------------------------------------------- | -------------------------------------------------------------- |
| **#1** | `9VnLxEtwJgnGALzhhXJTtRr4GF83z95aCypPiRFGi9d1` | **Pool State PDA** (read-only - authority validation) |
| **#2** | `DzPjdCvEgJq6tyTvQtuh6i8WtPn6gWGmhaRM3DnCvwq9` | **Chain Remote Config PDA** (writable - configuration updated) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault Authority** (signer, writable - fee payer) |
| **#4** | `11111111111111111111111111111111` | **System Program** (read-only - account operations) |
**Transaction Flow:**
1. **Authority Verification**: Confirms Squad vault owns the pool state
2. **PDA Access**: Updates the existing Chain Remote Config PDA
3. **Pool Address Addition**: Adds the remote pool to the the remote pool list for Ethereum Sepolia
### Step 3: Configure Rate Limits (Optional)
This command configures inbound and outbound rate limiting for token transfers between your Solana token and Ethereum Sepolia. Rate limits act as "token buckets" that control the flow of tokens across chains.
**Transaction Accounts (3 total):**
| Account | Address | Type | Purpose |
| ------- | ---------------------------------------------- | -------------------- | ----------------------------------------------------------------- |
| **#1** | `9VnLxEtwJgnGALzhhXJTtRr4GF83z95aCypPiRFGi9d1` | **Read-only** | **Pool State PDA** - Main pool configuration account |
| **#2** | `DzPjdCvEgJq6tyTvQtuh6i8WtPn6gWGmhaRM3DnCvwq9` | **Writable** | **Chain Config PDA** - Chain-specific config (stores rate limits) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Signer, Writable** | **Authority** - Your Squad vault that can modify rate limits |
### Step 4: Create Address Lookup Table
Create an Address Lookup Table (ALT) containing all accounts needed for CCIP router operations.
**What This Command Does:**
This command creates an **Address Lookup Table (ALT)** that stores frequently used accounts for CCIP operations. ALTs are a Solana optimization that reduces transaction size and costs by replacing full 32-byte addresses with small 1-byte indices.
**Account Breakdown from the Transaction:**
| Account | Address | Description |
| ------- | ---------------------------------------------- | --------------------------------------------------------- |
| **#1** | `C3a5U5C4Sn8fywXmV4ZX6JAfYoQrEhAqF5daL39ScKSy` | **ALT Account** (writable - being created) |
| **#2** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **ALT Authority** (signer - will own the table) |
| **#3** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Fee Payer** (signer, writable - pays creation costs) |
| **#4** | `11111111111111111111111111111111` | **System Program** (read-only - handles account creation) |
**What This Command Does:**
This command creates the ALT infrastructure needed for efficient CCIP operations by storing frequently used account addresses in a lookup table.
**Transaction Flow:**
1. **ALT Creation**: Creates lookup table account `C3a5U...` with Squad Vault as authority
2. **Core Address Addition**: Adds essential CCIP accounts (programs, PDAs) to the table
3. **Index Assignment**: Each address gets a unique 1-byte index for future reference
**ALT Contents (What Gets Stored):**
The created lookup table will contain indices for these accounts in this exact order:
| Index | Account Address | Purpose |
| ----- | ---------------------------------------------- | ------------------------------------------ |
| **0** | `C3a5U5C4Sn8fywXmV4ZX6JAfYoQrEhAqF5daL39ScKSy` | **ALT Address (Self-Reference)** |
| **1** | `89Jy2ZEz6LcvBPVQgR2YxPYVoF1sLugNRs3havQP8SvF` | **Token Admin Registry PDA** |
| **2** | `8eqh8wppT9c5rw4ERqNCffvU6cNFJWff9WmkcYtmGiqC` | **Pool Program ID** |
| **3** | `9VnLxEtwJgnGALzhhXJTtRr4GF83z95aCypPiRFGi9d1` | **Pool State PDA** |
| **4** | `A57bH91QkGuARh37JTN41fd3Vwcmf2bLzVHAVPB8Gaeg` | **Pool Signer's Associated Token Account** |
| **5** | `E8odUv4V4DXy3RWvkNYF7H33X9J56RtsFp4ExVXB86UA` | **Pool Signer PDA** |
| **6** | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | **Token Program ID (SPL Token v1)** |
| **7** | `FVJeMAQSH9dJvVmhe8NHC2DHTEEqT5cgVXTL5CYYmKu1` | **Token Mint** |
| **8** | `84grjTssjNyuDsaxJGkpJB8t17Y9sQt1G81R5AQyZ1Rr` | **Fee Quoter Token Config PDA** |
| **9** | `DPyeT76APQQVQD7EJRVw8Y6gLYjGkC5CdbdHQTQkpoN2` | **Router External Token Pools Signer PDA** |
**Transaction Efficiency Impact:**
- **Before ALT**: Each account = 32 bytes (9 accounts = 288 bytes)
- **After ALT**: Each account = 1 byte index (9 accounts = 9 bytes)
- **Savings**: \~279 bytes per future CCIP transaction!
**Set Variables:**
Copy the ALT address from the transaction output and set the environment variable:
### Step 5: Register Pool with Router
Register your token pool with the CCIP Router using [set_pool](/ccip/api-reference/svm/v1.6.0/router#set_pool). This enables the router to route cross-chain transfers through your token pool.
**What This Command Does:**
- **Pool Registration**: Connects your token pool to the CCIP Router for cross-chain operations
- **Lookup Table Integration**: Uses the ALT created in the previous step for efficient account management
- **Router Configuration**: Enables the router to call your pool's token operations
**Account Breakdown from the Transaction:**
| Account | Address | Description |
| ------- | ---------------------------------------------- | ----------------------------------------------------------- |
| **#1** | `3Yrg9E4ySAeRezgQY99NNarAmFLtixapga9MZb6y2dt3` | **Router Config PDA** (read-only - configuration reference) |
| **#2** | `89Jy2ZEz6LcvBPVQgR2YxPYVoF1sLugNRs3havQP8SvF` | **Token Admin Registry PDA** (writable - pool registration) |
| **#3** | `FVJeMAQSH9dJvVmhe8NHC2DHTEEqT5cgVXTL5CYYmKu1` | **Token Mint** (read-only - token identification) |
| **#4** | `C3a5U5C4Sn8fywXmV4ZX6JAfYoQrEhAqF5daL39ScKSy` | **Address Lookup Table** (read-only - ALT registration) |
| **#5** | `59eNrRrxrZMdqJxS7J3WGaV4MLLog2er14kePiWVjXtY` | **Squad Vault Authority** (signer, writable - admin & fees) |
**What This Command Does:**
This command **registers your token pool** with the CCIP Router. It configures the router to use your Address Lookup Table and specifies which accounts need write access during CCIP operations.
**Key Configuration:**
- **ALT Integration**: Uses the lookup table `C3a5U...` you just created
- **Writable Indexes**: `[3,4,7]` - Specific accounts from the ALTthat need write access during CCIP operations
**Transaction Flow:**
1. **Authority Verification**: Confirms Squad Vault has CCIP token admin permissions
2. **Pool Registration**: Updates Token Admin Registry with pool configuration
3. **ALT Association**: Links your token with the specific lookup table
4. **Permission Setup**: Defines which accounts can be modified during cross-chain operations
**Writable Indexes Analysis: `[3,4,7]`**
Based on the ALT order established earlier:
| **Index** | **ALT Account** | **Why Writable** |
| --------- | --------------- | ------------------------------------------------ |
| **3** | Pool State PDA | Gets updated during cross-chain operations |
| **4** | Pool Token ATA | Tokens get minted/burned during transfers |
| **7** | Token Mint | Supply gets modified during mint/burn operations |
**Phase 3 Complete**: Save all variables:
## Phase 4: EVM Cross-Chain Setup
Configure the Ethereum pool to recognize the Solana chain and set production rate limits.
### Step 1: Load Phase 3 Variables
Ensure you're in Terminal 2 (Hardhat) and load all the variables from Phase 3.
### Step 2: Configure Cross-Chain Settings
Configure the Ethereum pool to recognize the Solana chain and set production rate limits using [`applyChainUpdates`](/ccip/api-reference/evm/v1.6.2/token-pool#applychainupdates).
## Phase 5: Testing and Validation
### Step 1: Load Phase 4 Variables
Before testing cross-chain transfers, ensure your terminal environment is properly configured:
### Transfer Direction 1: Solana → Ethereum
Test the production multisig setup with a cross-chain transfer from Solana Devnet to Ethereum Sepolia:
#### Prepare for Testing
Before testing cross-chain transfers, you need to create the pool's Associated Token Account (ATA) and prepare your tokens:
##### Check Token Balance
##### Delegate Token Authority
#### Transfer tokens from Solana Devnet to Ethereum Sepolia
#### Monitor and Verify Transaction
Upon successful execution, the system generates critical tracking identifiers for transaction monitoring and verification.
**Transaction Identifiers:**
- **Transaction Signature**: `4w81mpUozjJsEvHrkukCejbA9byMjkHacwtqM5cJD19XQu8MvXzoJ4YTqqkoEvBfP7HPvdivMA3Q1MePA16PdCXM`
- **CCIP Message ID**: `0xd92433fea1fab2b9b2efa8d6091df0b5156bb159c883b94dada725539e5edace`
**CCIP Explorer** (Primary monitoring interface):
```
https://ccip.chain.link/msg/0xd92433fea1fab2b9b2efa8d6091df0b5156bb159c883b94dada725539e5edace
```
The CCIP Explorer provides comprehensive transaction visibility:
- Source chain (Solana) transaction confirmation
- CCIP message processing and routing
- Destination chain (Ethereum) message delivery
- Token minting completion on Ethereum
**Solana Explorer** (Source chain verification):
```
https://explorer.solana.com/tx/4w81mpUozjJsEvHrkukCejbA9byMjkHacwtqM5cJD19XQu8MvXzoJ4YTqqkoEvBfP7HPvdivMA3Q1MePA16PdCXM?cluster=devnet
```
### Transfer Direction 2: Ethereum → Solana
#### Transfer tokens from Ethereum Sepolia to Solana Devnet
#### Monitor and Verify Transaction
Upon successful execution, the system generates distinct tracking identifiers for comprehensive monitoring across both blockchain networks.
**Transaction Identifiers:**
- **Ethereum Transaction Hash**: `0xcacca1de44800d4e88544597b1b9f8190db93ae2f2144bb3b92aafd73b626c3d`
- **CCIP Message ID**: `0xb8327a54fcf042f6fa9ca236372ea68835204802ca13ebfc34c7f839631ee567`
**CCIP Explorer** (Primary monitoring interface):
```
https://ccip.chain.link/msg/0xb8327a54fcf042f6fa9ca236372ea68835204802ca13ebfc34c7f839631ee567
```
The CCIP Explorer provides comprehensive transaction visibility:
- Source chain (Ethereum) transaction confirmation
- CCIP message processing and routing
- Destination chain (Solana) message delivery
- Token minting completion on Solana network
**Ethereum Sepolia Explorer** (Source chain verification):
```
https://sepolia.etherscan.io/tx/0xcacca1de44800d4e88544597b1b9f8190db93ae2f2144bb3b92aafd73b626c3d
```
---
# CCIP Tutorials (Aptos)
Source: https://docs.chain.link/ccip/tutorials/aptos
Last Updated: 2025-09-03
Chainlink CCIP enables secure cross-chain communication between Aptos and EVM blockchains. These tutorials will help you implement cross-chain functionality in both directions.
## Getting Started
Before diving into specific implementations, ensure you understand the fundamentals:
- [Prerequisites for Aptos to EVM Tutorials](/ccip/tutorials/aptos/source/prerequisites) - Set up your development environment for Aptos -> EVM CCIP development.
- [Prerequisites for EVM to Aptos Tutorials](/ccip/tutorials/aptos/destination/prerequisites) - Set up your development environment for EVM -> Aptos CCIP development.
## Using Aptos as a Source Chain
Send messages and tokens from Aptos to EVM chains:
- [Aptos to EVM Guide](/ccip/tutorials/aptos/source) - Comprehensive guide for building all types of CCIP messages from Aptos to EVM chains.
- [Token Transfers](/ccip/tutorials/aptos/source/token-transfers) - Transfer tokens from Aptos to EVM chains without executing code on the destination.
## Using Aptos as a Destination Chain
Send messages and tokens from EVM chains to Aptos:
- [EVM to Aptos Guide](/ccip/tutorials/aptos/destination) - Comprehensive guide for building all types of CCIP messages from EVM chains to Aptos.
- [Token Transfers](/ccip/tutorials/aptos/destination/token-transfers) - Transfer tokens from EVM chains to Aptos wallets.
{/* - [Arbitrary Messaging](/ccip/tutorials/aptos/destination/arbitrary-messaging) - Send data from EVM chains to execute programs on Aptos */}
## Architecture Reference
Key Differences from EVM:
- **Account Model:** Aptos uses an account-based model where code (modules) and data (resources) are stored directly within accounts.
- **Resource Model:** The Move language on Aptos uses a resource model that provides strong ownership and access control, ensuring resources cannot be duplicated or accidentally destroyed.
- **Explicit Access:** Modules define functions that can explicitly access and modify the resources stored within an account.
- **Fungible Assets:** Tokens are typically handled via the Fungible Asset standard, where assets are stored as resources within an owner's account.
Message Types:
- **Token Transfers:** Send tokens across chains without program execution.
- **Arbitrary Messaging:** Send data to trigger program execution on the destination chain.
- **Programmatic Token Transfers:** Send both tokens and data in a single message to trigger program execution with token transfers.
---
# Implementing CCIP Receivers
Source: https://docs.chain.link/ccip/tutorials/aptos/receivers
Last Updated: 2025-09-03
# Implementing CCIP Receivers for Aptos
This reference guide explains the key components and security patterns required for building Aptos Move modules that can receive cross-chain messages via Chainlink's Cross-Chain Interoperability Protocol (CCIP).
## Introduction
A CCIP Receiver is an Aptos Move module that contains a `ccip_receive` entry function and is registered with the CCIP `ReceiverRegistry`. This allows it to process incoming cross-chain messages, handling both arbitrary data payloads and/or token transfers, serving as the on-chain destination endpoint for CCIP messages.
## Security Architecture
To build a secure CCIP receiver, you need to understand how your module interacts with the core CCIP on-chain components. The security model relies on a chain of trust originating from the authorized CCIP Off-Ramp module.
## Core Components of a CCIP Receiver
A complete CCIP Receiver implementation on Aptos contains several key components.
### Message Structure
Your module must be prepared to handle the `Any2AptosMessage` struct, which it fetches from the `ReceiverRegistry`.
```rust
// From the ccip::client module
struct Any2AptosMessage has store, drop, copy {
message_id: vector,
source_chain_selector: u64,
sender: vector,
data: vector,
dest_token_amounts: vector
}
struct Any2AptosTokenAmount has store, drop, copy {
token: address,
amount: u64
}
```
These structures contain:
- `message_id`: A unique identifier for the message
- `source_chain_selector`: The chain ID of the source chain
- `sender`: The address of the sender on the source chain
- `data`: The arbitrary data payload
- `dest_token_amounts`: An array of tokens and amounts being transferred
### Module State (Resources)
Your module will likely need to store state in on-chain resources. If your module will handle tokens, it's critical to store the `SignerCapability` from its resource account. You also need handles to emit custom events.
```rust
// Example of a state resource from the ccip_message_receiver
struct CCIPReceiverState has key {
signer_cap: account::SignerCapability,
received_message_handle: event::EventHandle,
forwarded_tokens_handle: event::EventHandle,
}
```
### The `ccip_receive` Entry Function
This is the core function that implements the CCIP receiver interface. It acts as a secure callback, triggered by the **Receiver Dispatcher** (which is itself called by the CCIP Off-Ramp), and should be designed as a dispatcher to handle different types of incoming messages.
```rust
// A skeleton ccip_receive function
public entry fun ccip_receive(
_receiver_account: &signer, // The signer of the receiver module's account
_proof: ProofType, // A proof object required for the callback mechanism
) acquires State {
// 1. Fetch the message payload securely from the registry
let message = receiver_registry::get_receiver_input(signer::address_of(_receiver_account), _proof);
// 2. Perform security checks (source chain, sender, etc.)
let source_chain = client::get_source_chain_selector(&message);
assert!(source_chain == 12345, E_UNAUTHORIZED_CHAIN); // Example check
// 3. Process the message data
let received_data = client::get_data(&message);
if (!received_data.is_empty()) {
// Your custom data processing logic here
let state = borrow_global_mut(signer::address_of(_receiver_account));
state.latest_message = received_data;
}
// 4. Process token transfers (if applicable)
// Note: Tokens are automatically deposited into the receiver's primary store.
// This logic would be for forwarding or utilizing them.
let tokens = client::get_dest_token_amounts(&message);
if (!tokens.is_empty()) {
// Your custom token handling logic here
}
// 5. Emit an event for tracking
event::emit(MessageReceived {
message_id: client::get_message_id(&message),
// ...
});
}
```
### Administrative Functions
A robust receiver module should include permissioned entry functions for maintenance and security, such as a function to withdraw tokens that may have been sent to it accidentally or for administrative purposes.
```rust
// Example of a permissioned withdrawal function
public entry fun withdraw_token(
sender: &signer,
recipient: address,
token_address: address,
) acquires CCIPReceiverState {
// Only allow the original deployer to call this function
assert!(signer::address_of(sender) == @deployer, E_UNAUTHORIZED);
let state = borrow_global_mut(@receiver);
let state_signer = account::create_signer_with_capability(&state.signer_cap);
// ... logic to transfer the full balance of a token to the recipient
fungible_asset::transfer(
&state_signer,
// ...
);
}
```
## Publishing Your Receiver Module
How you publish your module is critical and depends on its purpose.
- **For Data-Only Modules**: If your module only processes data and will never hold assets, you can publish it to a regular user account or code object account (the code object account is recommended).
- **For Token-Handling Modules**: If your module will receive tokens, it **must** be deployed to a **Resource Account**. This gives the module an on-chain `signer` capability, which is required to authorize the transfer of tokens *out* of its own account. The `createResourceAccountAndPublishReceiver.ts` script in the starter kit handles this process.
***
## Security Considerations
Building a secure CCIP Receiver on Aptos requires careful attention to several key validation patterns.
### Caller Validation (Protocol Guarantee)
The most critical security check—verifying that the call originates from a legitimate CCIP Off-Ramp—is a **protocol-level guarantee**. This check is performed by the `Receiver Dispatcher` module before your `ccip_receive` function is ever called. It asserts that the caller's signer address is on an allowlist of authorized Off-Ramps, preventing unauthorized modules from sending fake messages to your receiver. Your module's security is built on this foundational guarantee.
### Source Chain and Sender Validation
Your application is responsible for validating the content of the message payload. Inside your `ccip_receive` function, after fetching the `Any2AptosMessage`, you should implement checks against the `source_chain_selector` and `sender` fields if your use case requires it.
```rust
// Inside your ccip_receive function:
let message = receiver_registry::get_receiver_input(...);
// Get the source chain and sender from the message
let source_chain = client::get_source_chain_selector(&message);
let sender_bytes = client::get_sender(&message);
// Verify against your module's allowlists
assert!(is_allowed_source_chain(source_chain), E_UNTRUSTED_SOURCE_CHAIN);
assert!(is_allowed_sender(sender_bytes), E_UNTRUSTED_SENDER);
```
### Message Deduplication
To prevent replay attacks where the same message could be executed multiple times, your receiver should track processed message IDs.
- **Mechanism**: Store recently processed `message_id`s in an on-chain resource (e.g., a `Table` or a `vector`).
- **Implementation**: In your `ccip_receive` function, check if the incoming `message_id` already exists in your store. If it does, abort the transaction. If it doesn't, process the message and then add its ID to the store.
### Asset Management and Account Types
A critical security consideration on Aptos is the account type used for your receiver module.
- To manage (e.g., withdraw or forward) tokens received via CCIP, the receiver module **must** be deployed to a **Resource Account**.
- This provides the necessary on-chain `SignerCapability` for the module to authorize outgoing transactions for the assets it holds.
- Deploying a token-handling module to a standard user or object account will result in **permanently locked funds**, as the module will have no authority to sign for transfers.
***
## Key Implementation Concepts
### Secure Payload Fetching
As shown in the example, your `ccip_receive` function does not get the message payload in its arguments. It **must** call `receiver_registry::get_receiver_input` to securely fetch the `Any2AptosMessage`. This security pattern ensures that only your registered module can access the payload during a valid CCIP execution.
### Token Handling for Receivers
When tokens are sent to your module, the CCIP Off-Ramp automatically deposits them into your module account's primary fungible store *before* your `ccip_receive` function is called. To do anything with these tokens (like forward them to another user), your module must be able to sign for the transfer. This is why deploying to a **Resource Account** and using its `SignerCapability` is mandatory for any module that acts as a custodian of assets.
### The Dispatcher Pattern
The `ReceiverRegistry` maps one account address to one `ccip_receive` function. To handle multiple types of actions, use the **dispatcher pattern** within your `ccip_receive` function. By inspecting the `data` and `token_amounts` fields, you can route the logic to different internal functions, allowing a single, secure entry point to manage many functionalities.
## Example Implementation
For a complete reference implementation of a CCIP Receiver on Aptos, you can examine the `ccip_message_receiver` module within the **[Aptos Starter Kit](https://github.com/smartcontractkit/aptos-starter-kit)**. This example demonstrates many of the security patterns and best practices covered in this guide and serves as an excellent starting point for your own implementation.
---
# CCIP Tutorials: Aptos to EVM
Source: https://docs.chain.link/ccip/tutorials/aptos/source
Last Updated: 2025-09-03
This section provides comprehensive guides and tutorials for implementing cross-chain communication from Aptos chains to Ethereum Virtual Machine (EVM) chains using Chainlink's Cross-Chain Interoperability Protocol (CCIP).
## Getting Started
Before implementing specific use cases, it's important to set up your environment and understand the fundamental concepts:
- [Building CCIP Messages from Aptos to EVM](/ccip/tutorials/aptos/source/build-messages) - Learn the core message structure, required parameters, and implementation details for all message types.
- [Prerequisites for Aptos to EVM Tutorials](/ccip/tutorials/aptos/source/prerequisites) - Set up your development environment with Aptos CLI tools, wallets, and token accounts.
## Tutorials by Use Case
Depending on your specific needs, choose the appropriate tutorial:
- [Token Transfers](/ccip/tutorials/aptos/source/token-transfers) - Send tokens from Aptos to EVM chains without executing code on the destination.
---
# Building CCIP Messages from Aptos to EVM
Source: https://docs.chain.link/ccip/tutorials/aptos/source/build-messages
Last Updated: 2025-09-03
## Introduction
This guide explains how to construct CCIP Messages from the Aptos blockchain to EVM chains (e.g., Ethereum, Arbitrum, Base, etc.). We'll cover the message structure by examining the [`ccip_send`](/ccip/api-reference/aptos/v1.6.0/router#ccip_send) entry function from the [`ccip_router::router` module](/ccip/api-reference/aptos/v1.6.0/router), its required parameters, and the implementation details for different message types including token transfers, arbitrary data messaging, and programmatic token transfers (data and tokens).
## CCIP Message Structure on Aptos
CCIP messages from Aptos are initiated by calling the [`ccip_send`](/ccip/api-reference/aptos/v1.6.0/router#ccip_send) entry function in the CCIP Router Move module. This function serves as the single entry point for all cross-chain messages, and internally routes the request to the correct On-Ramp contract version based on the destination chain. See the [CCIP Router API Reference](/ccip/api-reference/aptos/v1.6.0/router) for complete details.
As defined in the `ccip_router::router` module, the `ccip_send` function has the following signature:
```rust
public entry fun ccip_send(
caller: &signer,
dest_chain_selector: u64,
receiver: vector,
data: vector,
token_addresses: vector,
token_amounts: vector,
token_store_addresses: vector,
fee_token: address,
fee_token_store: address,
extra_args: vector
)
```
### receiver
- **Definition**: The address of the contract or wallet on the destination EVM chain that will receive the message.
- **Formatting**: EVM addresses are 20 bytes long, but Aptos requires this parameter to be a 32-byte array. You must left-pad the 20-byte EVM address with 12 zero-bytes to create a valid 32-byte array.
### data
- **Definition**: The payload that will be executed by the receiving contract on the destination chain.
- **Usage**:
- **For token-only transfers**: This must be an empty `vector`.
- **For arbitrary messaging** or **programmatic token transfers**: This contains the function selectors and arguments for the receiver contract, typically ABI-encoded.
- **Encoding**: The receiver contract on the destination EVM chain must be able to decode this data. Standard EVM ABI-encoding is the recommended approach.
### token_addresses
A vector of Aptos token type addresses to be transferred.
### token_amounts
A vector of amounts to transfer for each corresponding token, in the token's smallest denomination.
### token_store_addresses
A vector of addresses representing the Fungible Asset store from which the tokens will be withdrawn. You could just use `0x0` as the token_store_address, because when using `0x0`, it would retrieve the `primary_store_address` (using `primary_fungible_store::primary_store_address`) of the token corresponding to the sender's account.
For data-only messages, the `token_addresses`, `token_amounts`, and `token_store_addresses` vectors must all be empty.
### fee_token
The address of the token to be used for paying CCIP fees. This can be native APT (`0xa`) or a supported token like LINK.
### fee_token_store
The address of the Fungible Asset store from which the fee will be paid. You can use `0x0` here as well, which will resolve to the primary store for the fee token in the sender's account.
### extra_args
For messages going to an EVM chain, the `extra_args` parameter is a `vector` that must be encoded according to a specific format for compatibility. While there is no literal `GenericExtraArgsV2` struct in the Aptos modules, the byte vector must be encoded to match the format that EVM-family chains expect.
This format consists of a 4-byte tag (`0x181dcf10`) followed by the BCS-encoded parameters:
- **`gas_limit`**: (`u256`) The gas limit for the execution of the transaction on the destination EVM chain.
- **`allow_out_of_order_execution`**: (`bool`) A flag that must always be set to `true` for Aptos-to-EVM messages.
The [`ccip::client`](/ccip/api-reference/aptos/v1.6.0/client) module provides a helper view function, [`encode_generic_extra_args_v2`](/ccip/api-reference/aptos/v1.6.0/client#encode_generic_extra_args_v2), to perform this encoding on-chain. Off-chain scripts replicate this logic to construct the byte vector correctly.
## Estimating Fees
Before sending a transaction, you must calculate the required fee. The [`ccip_router::router`](/ccip/api-reference/aptos/v1.6.0/router) module provides a [`get_fee`](/ccip/api-reference/aptos/v1.6.0/router#get_fee) view function for this purpose. It takes the exact same arguments as [`ccip_send`](/ccip/api-reference/aptos/v1.6.0/router#ccip_send), allowing you to get a precise fee quote for your intended message.
```typescript
import { Aptos } from "@aptos-labs/ts-sdk"
async function getCcipFee(aptos: Aptos, messagePayload: any) {
const fee = await aptos.view({
payload: {
function: `${ccipRouterModuleAddr}::router::get_fee`,
functionArguments: messagePayload.functionArguments, // Reuse the same arguments as ccip_send
},
})
return fee[0]
}
// You would call this before submitting your ccip_send transaction.
const feeAmount = await getCcipFee(aptosClient, transactionPayload)
console.log(`Required CCIP Fee: ${feeAmount}`)
```
## Implementation by Message Type
### Token Transfer
Use this configuration when sending only tokens from Aptos to an EVM chain:
```typescript
import { MoveVector } from "@aptos-labs/ts-sdk"
const transactionPayload = {
function: `${ccipRouterModuleAddr}::router::ccip_send`,
functionArguments: [
destinationChainSelector, // e.g., "16015286601757825753" for Ethereum Sepolia
MoveVector.U8(evmAddressToAptos(receiverAddress)), // Padded 32-byte receiver
MoveVector.U8([]), // Empty data vector
[ccipBnmTokenAddress], // Vector of token addresses
MoveVector.U64([10000000n]), // Vector of token amounts
[tokenStoreAddress], // Vector of token store addresses
feeTokenAddress, // e.g., LINK or native APT token address
feeTokenStore, // Fee token store address
encodeGenericExtraArgsV2(0n, true), // extraArgs with gasLimit = 0
],
}
```
### Arbitrary Messaging
Use this configuration when sending only data to EVM chains.
```typescript
import { MoveVector, Hex } from "@aptos-labs/ts-sdk"
import { ethers } from "ethers"
// ABI-encode the data for the receiver contract
const encodedData = ethers.AbiCoder.defaultAbiCoder().encode(["string"], ["Hello World"])
const dataBytes = Hex.hexInputToUint8Array(encodedData)
const transactionPayload = {
function: `${ccipRouterModuleAddr}::router::ccip_send`,
functionArguments: [
destinationChainSelector,
MoveVector.U8(evmAddressToAptos(receiverContractAddress)),
MoveVector.U8(dataBytes), // The encoded data payload
[], // Empty token addresses vector
MoveVector.U64([]), // Empty token amounts vector
[], // Empty token store addresses vector
feeTokenAddress,
feeTokenStore,
encodeGenericExtraArgsV2(200000n, true), // extraArgs with appropriate gasLimit
],
}
```
### Programmatic Token Transfer (Data and Tokens)
Use this configuration when sending both tokens and data in a single message:
```typescript
import { MoveVector, Hex } from "@aptos-labs/ts-sdk"
import { ethers } from "ethers"
// ABI-encode the data for the receiver contract
const encodedData = ethers.AbiCoder.defaultAbiCoder().encode(["string"], ["Tokens attached"])
const dataBytes = Hex.hexInputToUint8Array(encodedData)
const transactionPayload = {
function: `${ccipRouterModuleAddr}::router::ccip_send`,
functionArguments: [
destinationChainSelector,
MoveVector.U8(evmAddressToAptos(receiverContractAddress)),
MoveVector.U8(dataBytes), // The encoded data payload
[ccipBnmTokenAddress], // Vector of token addresses
MoveVector.U64([10000000n]), // Vector of token amounts
[tokenStoreAddress], // Vector of token store addresses
feeTokenAddress,
feeTokenStore,
encodeGenericExtraArgsV2(200000n, true), // extraArgs with appropriate gasLimit
],
}
```
## Tracking Messages with Transaction Events
After a successful `ccip_send` transaction, the CCIP Router module emits an event containing the unique identifier for the cross-chain message. On Aptos, this event is emitted by the On-Ramp module (e.g., [`CCIPMessageSent`](/ccip/api-reference/aptos/v1.6.0/events#ccip_send)) and can be found in the `Events` tab of the executed transaction on the [Aptos Explorer](https://explorer.aptoslabs.com/?network=testnet).
```json
// Example of a CCIPMessageSent event from an Aptos transaction receipt
{
"Type": "0xe9dbf...ac0dd3::onramp::CCIPMessageSent",
"Data": {
"dest_chain_selector": "16015286601757825753",
"message": {
"header": {
"message_id": "0x06859...2afefea",
"nonce": "0",
"sequence_number": "14",
"source_chain_selector": "743186221051783445"
},
"receiver": "0x00000...bb14ca",
"sender": "0xd0e22...d2fad4",
"token_amounts": [
{
"amount": "10000000",
"dest_token_address": "0x00000...fe82a05"
}
],
"fee_token": "0x8c208...fa3542",
"data": "0x",
"extra_args": "0x181dc...00000..."
},
"sequence_number": "14"
}
}
```
The `message_id` is the critical piece of information that links the source Aptos transaction to the destination EVM transaction.
## Further Resources
- [**CCIP Router API Reference**](/ccip/api-reference/aptos/v1.6.0/router): Complete technical details about the router's functions, parameters, and view functions like [`get_fee`](/ccip/api-reference/aptos/v1.6.0/router#get_fee).
- [**CCIP Messages API Reference**](/ccip/api-reference/aptos/v1.6.0/messages): Comprehensive documentation of all CCIP message and event structures for Aptos.
- **Aptos TS-SDK Docs**: For more information on building transactions and interacting with the Aptos blockchain, refer to the [official Aptos TS-SDK docs](https://aptos.dev/en/build/sdks/ts-sdk).
---
# Prerequisites for Aptos to EVM Tutorials
Source: https://docs.chain.link/ccip/tutorials/aptos/source/prerequisites
Last Updated: 2025-09-03
Before starting the Aptos to EVM tutorials, ensure you have:
## Development Environment
- **Aptos CLI**: Install the Aptos CLI by following the official [installation guide](https://aptos.dev/en/build/cli).
- **Node.js v20 or higher**: You can use the [nvm package](http://nvm.sh/) to install and switch between Node.js versions. Once installed, verify the node version with:
```bash
node -v
```
Example output:
```text
$ node -v
v22.15.0
```
- **Npm**: For installing and managing dependencies.
- **Git**: For cloning the repository.
## Starter Kit Repository
1. Clone the CCIP Aptos Starter Kit:
```bash
git clone https://github.com/smartcontractkit/aptos-starter-kit.git
```
2. Navigate to the directory:
```bash
cd aptos-starter-kit
```
3. Install dependencies:
```bash
npm install
```
## Understanding Named Addresses in the Manifest File (`Move.toml`)
The Move build system uses a manifest file, `Move.toml`, to define a package, its dependencies, and any named addresses it references. Named addresses are human-readable aliases for on-chain hexadecimal addresses, which makes the Move code cleaner, more readable, and easier to manage.
When you compile a Move package, the compiler replaces these aliases (e.g., `@ccip_router`) with their actual address values from the `Move.toml` file.
In the [`aptos-starter-kit`](https://github.com/smartcontractkit/aptos-starter-kit), the example modules (like `ccip_message_sender`, `ccip_message_receiver`) need to interact with the core CCIP modules already deployed on the Aptos Testnet. The `[addresses]` section in their `Move.toml` files provides the necessary references.
**Example `Move.toml` Address Block:**
```toml
[addresses]
ccip = "0xc748085bd02022a9696dfa2058774f92a07401208bbd34cfd0c6d0ac0287ee45"
mcms = "0xbdf1b9aacb4e21bf6f255105831df0172e911d4748e488196fde10d2e2a4e32d"
mcms_register_entrypoints = "0x0"
ccip_onramp = "0xc748085bd02022a9696dfa2058774f92a07401208bbd34cfd0c6d0ac0287ee45"
ccip_router = "0xc748085bd02022a9696dfa2058774f92a07401208bbd34cfd0c6d0ac0287ee45"
```
Here is a breakdown of what each named address represents:
- **`ccip`**: This is the main **Object address** under which the core CCIP packages and their modules (like `fee_quoter`, `rmn_remote`, `token_admin_registry`, etc.) are published.
- **`ccip_router`** & **`ccip_onramp`**: These aliases also point to the main CCIP Object address. This is because the `router` and `onramp` modules are part of the packages published under that single, unified Object. The aliases are defined for clarity and consistency in the Move code's `use` statements.
- **`mcms`**: This is the address of the on-chain **ManyChainMultiSig (MCMS)** module, which is responsible for handling governance and administrative actions for the CCIP protocol through a secure, time-locked [implementation process](/ccip/concepts/architecture/onchain/aptos/upgradability#implementation-process).
- **`mcms_register_entrypoints`**: This is a special value used as a **compile-time flag**. By setting it to `0x0`, the module is compiled in a way that bypasses the logic for registering with the MCMS system. This is useful for certain testing or deployment scenarios where the MCMS module is not relevant.
By defining these addresses in the `Move.toml` file, the example modules can interact with the core CCIP protocol without hardcoding long addresses, making the code portable and easier to update for different networks.
## Wallets
- **Aptos Account**: You'll need an Aptos account. If you don't have one, create it with:
```bash
aptos init --network testnet
```
This command will guide you through creating a new account for Testnet and will save the credentials in a `.aptos/config.yaml` file. This also configures your Aptos CLI to use Testnet.
Verify your current configuration with:
```bash
aptos config show-profiles
```
This should show your `default` profile configured for Testnet, with the `network` set to `Testnet`.
Example output:
```text
$ aptos config show-profiles
{
"Result": {
"default": {
"network": "Testnet",
"has_private_key": true,
"public_key": "ed25519-pub-0x2ecdd2d7bc0cbfe2e44c219ef9a9fddc986b384f4a01fb5d821cf0dab5d2fbae",
"account": "d0e227835c33932721d54ae401cfaae753c295024fe454aa029b5e2782d2fad4",
"rest_url": "https://fullnode.testnet.aptoslabs.com"
}
}
}
```
- **EVM Wallet Address**: You'll need an EVM-compatible wallet address to receive tokens on the destination chain (Ethereum Sepolia). You only need the address itself, not the private key, as you are only sending *to* this address.
## Environment Configuration (`.env` file)
The starter kit uses a `.env` file to manage sensitive information like private keys and RPC URLs. Create a new file named `.env` in the root of the `aptos-starter-kit` directory by copying the example file:
```bash
cp .env.example .env
```
Next, open the `.env` file and fill in the following values:
- `PRIVATE_KEY_HEX`: The private key of your source wallet (EOA) on Aptos Testnet from which you're sending CCIP-BnM tokens. You can find this in the `.aptos/config.yaml` file created by the `aptos init --network testnet` command, as shown in the [Wallets](/ccip/tutorials/aptos/source/prerequisites#wallets) section above.
- `ETHEREUM_SEPOLIA_RPC_URL`: The RPC endpoint for the Ethereum Sepolia testnet. This is required by the verification script (`aptos2evm/checkMsgExecutionStateOnAptos.ts`) to read the `ExecutionStateChanged` event from the CCIP OffRamp on Ethereum Sepolia. You can obtain an RPC URL by signing up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service.
**Example `.env` file:**
```
PRIVATE_KEY_HEX=
ETHEREUM_SEPOLIA_RPC_URL=
```
## Native Tokens for Transaction Fees
**APT** tokens are used for Aptos transaction fees. For these tutorials, we will also use **APT** to pay for CCIP fees.
- Obtain **APT** on Testnet using the faucet:
You can use the official [Aptos Testnet Faucet](https://aptos.dev/en/network/faucet) to get **APT** tokens. Simply enter your Aptos account address and click on **Mint** to request tokens.
- Check your account balance:
```bash
aptos account balance
```
Example output:
```text
$ aptos account balance
{
"Result": [
{
"asset_type": "coin",
"coin_type": "0x1::aptos_coin::AptosCoin",
"balance": 100000000
}
]
}
```
## Obtaining Testnet Tokens
### BnM Tokens on Aptos
To complete the cross-chain token transfer examples, you'll need BnM tokens. To obtain CCIP-BnM tokens on Aptos Testnet, run the `faucets/aptos/dripCCIPBnMToken.ts` script included in the starter kit.
Run the following command in your terminal:
```bash
npx ts-node scripts/faucets/aptos/dripCCIPBnMToken.ts --to
```
The output looks like this:
```text
1 CCIP-BnM token is minted to 0xd0e227835c33932721d54ae401cfaae753c295024fe454aa029b5e2782d2fad4 successfully.
Please check the transaction at https://explorer.aptoslabs.com/txn/0x7e5d5bae918c97cb086c72023012da2a8a230c91155f8ff697eb29001f606e55?network=testnet
```
---
# Token Transfers: Aptos to EVM
Source: https://docs.chain.link/ccip/tutorials/aptos/source/token-transfers
Last Updated: 2025-09-03
This tutorial demonstrates how to transfer tokens from the Aptos blockchain to an Ethereum Virtual Machine (EVM) chain using Chainlink CCIP. You will learn how to build a CCIP message on Aptos, send it using a script, and verify the transfer on the destination chain.
## Introduction
This tutorial covers transferring tokens from Aptos Testnet to Ethereum Sepolia without any additional data payload or program execution. When you transfer tokens using CCIP:
1. Tokens are burned or locked in pools on the source chain (Aptos).
2. Equivalent tokens are minted or released from the destination pool (Ethereum Sepolia).
3. The process is managed by CCIP.
## What You Will Build
In this tutorial, you will:
- Use a script to configure a CCIP message for a token-only transfer.
- Send CCIP-BnM test tokens from Aptos Testnet to an Ethereum Sepolia address.
- Pay for CCIP transaction fees using either native APT or LINK tokens.
- Monitor and verify your cross-chain transfer.
## Understanding Token Transfers from Aptos to EVM
This tutorial focuses on token-only transfers from your wallet on Aptos Testnet to an address on Ethereum Sepolia. Key points specific to token-only transfers:
- **Burn and Mint**: In this tutorial, CCIP-BnM tokens are burned on Aptos Testnet, and equivalent CCIP-BnM tokens are minted on Ethereum Sepolia.
- **Fee Payment**: Transaction fees are paid on Aptos using either native APT or LINK tokens.
- **Message Construction**: A script constructs and sends the transaction payload by calling the [`ccip_send`](/ccip/api-reference/aptos/v1.6.0/router#ccip_send) entry function on the Aptos CCIP Router module.
## CCIP Message Configuration
The most important part of implementing a token transfer is correctly configuring the arguments for the [`ccip_send`](/ccip/api-reference/aptos/v1.6.0/router#ccip_send) function. The `scripts/aptos2evm/ccipSendTokenRouter.ts` script handles this. Here's a look at how the arguments are prepared within the script:
```typescript
// Inside the script, the transaction payload is constructed like this:
const transaction = await aptos.transaction.build.simple({
sender: account.accountAddress,
data: {
function: `${ccipRouterModuleAddr}::router::ccip_send`, // CCIP Router address and function
functionArguments: [
destChainSelector, // Chain selector for Ethereum Sepolia
MoveVector.U8(Hex.hexInputToUint8Array(paddedReceiverArray)), // Your EVM receiver address
MoveVector.U8([]), // Empty data for token-only transfer
[ccipBnMTokenAddr], // The BnM token address on Aptos
MoveVector.U64([TOKEN_AMOUNT_TO_SEND]), // The amount of BnM tokens
[TOKEN_STORE_ADDR], // The token store, e.g., '0x0' for the primary store
feeToken, // The fee token address (APT or LINK)
feeTokenStore, // The fee token store address
extraArgs, // Encoded extra arguments with gas limit
],
},
})
```
### Critical Configuration Settings
When setting up your CCIP message for a token transfer, these parameters are crucial:
#### Required Parameters
- **`data`**: **MUST** be an empty vector (`[]`) for token-only transfers.
- **`extraArgs`**: Must be properly encoded based on your destination chain type.
#### EVM Destination Configuration
For EVM destinations (covered in this tutorial):
- Set `gasLimit` to `0`
- Set `allowOutOfOrderExecution` to `true`
- The script handles encoding by calling `encodeGenericExtraArgsV2(0, true)`
#### SVM Destination Configuration
The `ccipSendTokenRouter.ts` script in this tutorial is designed specifically for EVM destinations and **will not work** for SVM destinations without modifications.
For SVM destinations such as Solana, you will need to adapt the script's `extraArgs` encoding logic.
Refer to the following resources for more information:
- [Building CCIP messages from EVM to SVM](/ccip/tutorials/svm/destination/build-messages)
- [EVM to SVM token transfers tutorial](/ccip/tutorials/svm/destination/token-transfers)
## How the Script Works
The `aptos2evm/ccipSendTokenRouter.ts` script handles the interaction with the CCIP Router module on your behalf. Here's what happens behind the scenes:
1. **Context Initialization**: The script initializes the Aptos client and loads your private key from the `.env` file.
2. **Argument Parsing**: It reads your command-line arguments (`--destChain`, `--feeToken`, `--amount`, `--evmReceiver`) to determine the destination, fee payment method, amount to send, and EVM receiver address.
3. **Parameter Preparation**: It formats the EVM receiver address and token amount correctly for the Aptos blockchain.
4. **Transaction Building**: It constructs the [`ccip_send`](/ccip/api-reference/aptos/v1.6.0/router#ccip_send) transaction with all necessary arguments, including the destination selector, receiver, token details, and encoded extra args.
5. **Simulation and Execution**:
- It simulates the transaction to prevent failures.
- It signs and sends the transaction to the Aptos Testnet.
- It waits for the transaction to be confirmed and prints the transaction hash.
## Running the Token Transfer
### Prerequisites Check
Before running the script:
1. Ensure you've completed the setup steps outlined in the [prerequisites](/ccip/tutorials/aptos/source/prerequisites).
2. Make sure your `.env` file contains your `PRIVATE_KEY_HEX` (of your wallet on Aptos Testnet from which you're sending CCIP-BnM tokens) and `ETHEREUM_SEPOLIA_RPC_URL`.
3. Verify you have sufficient APT and CCIP-BnM token balances in your Aptos wallet.
### Execute the Script
Run the following command from your terminal to transfer CCIP-BnM tokens from Aptos Testnet to Ethereum Sepolia, paying the fee in native APT. You can change the value for the `--amount` flag to send a different number of tokens.
```bash
npx ts-node scripts/aptos2evm/ccipSendTokenRouter.ts --destChain sepolia --feeToken native --amount 0.001 --evmReceiver
```
To pay with LINK instead, change the `feeToken` argument:
```bash
npx ts-node scripts/aptos2evm/ccipSendTokenRouter.ts --destChain sepolia --feeToken link --amount 0.001 --evmReceiver
```
### Understanding the Output
When the script executes successfully, you'll see the logs similar to the following:
```
✅ Transaction successful: https://explorer.aptoslabs.com/txn/0xa5ca115b1f13bb7ace83218f81b60510df29074bfee1f64ce17b00b5737391bc?network=testnet
🆔 CCIP Message ID: 0x657994d25cb3e50d2ae510f0ac9ea7fff845f57c2e901e3d4ceefb2401408daf
🔗 CCIP Explorer URL: https://ccip.chain.link/#/side-drawer/msg/0x657994d25cb3e50d2ae510f0ac9ea7fff845f57c2e901e3d4ceefb2401408daf
```
- The transaction hash is displayed along with the Aptos Explorer URL.
- The output provides the unique `CCIP Message ID`, which is essential for tracking your transfer.
- The CCIP Explorer URL allows you to monitor the message status across chains.
## Verification and Monitoring
After sending your token transfer, you can verify its arrival on Ethereum Sepolia in the following ways:
### Check Message Execution
#### Use the CCIP Explorer to check the message status
Use the CCIP Explorer link provided in the transaction output to track your message status across chains. The explorer gives an overview of the entire cross-chain transaction life cycle.
```text
🔗 CCIP Explorer URL: https://ccip.chain.link/#/side-drawer/msg/
```
#### Programmatically check the message status
After you receive a CCIP Message ID, you can use the `aptos2evm/checkMsgExecutionStateOnEvm.ts` script to see if the message was successfully delivered. This script first fetches the [`CCIPMessageSent`](/ccip/api-reference/aptos/v1.6.0/events#ccip_send) event on Aptos to get the unique Message ID, then polls the destination chain (Ethereum Sepolia) for the corresponding `ExecutionStateChanged` event.
After 1-2 minutes, run the script using the CCIP Message ID you received from the previous step.
**Command**:
```bash
npx ts-node scripts/aptos2evm/checkMsgExecutionStateOnEvm.ts --msgId --destChain sepolia
```
*Replace `` with the actual CCIP Message ID from the log output.*
**Output**:
When the message has been successfully delivered, you will see the following output:
```text
Execution state for CCIP message 0x657994d25cb3e50d2ae510f0ac9ea7fff845f57c2e901e3d4ceefb2401408daf is SUCCESS
```
### Verify Token Balance
Once the script confirms a `SUCCESS` state, you can perform a final verification on a block explorer.
- Visit the [Sepolia Etherscan Explorer](https://sepolia.etherscan.io/).
- Search for your EVM wallet address.
- Under the "ERC20 Token Txns" tab, you should see the incoming transfer of CCIP-BnM tokens.
---
# CCIP Tutorials: EVM to Aptos
Source: https://docs.chain.link/ccip/tutorials/aptos/destination
Last Updated: 2025-09-03
This section provides comprehensive guides and tutorials for implementing cross-chain communication from Ethereum Virtual Machine (EVM) chains to Aptos chains using Chainlink's Cross-Chain Interoperability Protocol (CCIP).
## Getting Started
Before implementing specific use cases, it's important to understand the fundamental concepts and message structure for EVM to Aptos communication:
- [Building CCIP Messages from EVM to Aptos](/ccip/tutorials/aptos/destination/build-messages) - Learn the core message structure, required parameters, and implementation details for all message types.
- [Prerequisites for EVM to Aptos Tutorials](/ccip/tutorials/aptos/destination/prerequisites) - Set up your development environment with Aptos CLI tools, wallets, and token accounts.
## Tutorials by Use Case
Depending on your specific needs, choose the appropriate tutorial:
- [Token Transfers](/ccip/tutorials/aptos/destination/token-transfers) - Send tokens from EVM chains to Aptos wallets without program execution.
- [Arbitrary Messaging](/ccip/tutorials/aptos/destination/arbitrary-messaging) - Send data from EVM chains to Aptos module.
- [Programmatic Token Transfers](/ccip/tutorials/aptos/destination/programmatic-token-transfers) - Send both tokens and data in a single message to trigger module execution with token transfers.
---
# Building CCIP Messages from EVM to Aptos
Source: https://docs.chain.link/ccip/tutorials/aptos/destination/build-messages
Last Updated: 2025-09-03
## Introduction
This guide explains how to construct CCIP Messages from Ethereum Virtual Machine (EVM) chains (e.g., Ethereum, Arbitrum, Base, etc.) to the Aptos blockchain. We'll cover the message structure, required parameters, and implementation details for different message types including token transfers, arbitrary data messaging, and programmatic token transfers (data and tokens).
## CCIP Message Structure
CCIP messages from EVM are built using the [`EVM2AnyMessage`](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) struct from the [`Client.sol`](/ccip/api-reference/evm/v1.6.2/client) library. The `EVM2AnyMessage` struct is defined as follows:
```solidity
struct EVM2AnyMessage {
bytes receiver;
bytes data;
EVMTokenAmount[] tokenAmounts;
address feeToken;
bytes extraArgs;
}
```
### receiver
- **Definition**: The 32-byte account address of the receiver on Aptos.
- **For token-only transfers**: This is the **end user's wallet address** on Aptos.
- **For arbitrary messaging** or **programmatic token transfers**: This must be the account address of your custom Aptos module that is intended to receive and process the message.
### data
- **Definition**: Contains the payload to be delivered to the destination chain.
- **For token-only transfers**: Must be empty (`0x`).
- **For arbitrary messaging** or **programmatic token transfers**: Contains the custom data the receiver module will process.
- **Encoding requirement**: Must be encoded as a hex string with a `0x` prefix.
### tokenAmounts
- **Definition**: An array of token addresses and amounts to transfer.
- **For data-only messages**: Must be an empty array (`[]`).
- **For token transfers** or **programmatic token transfers**: Each entry specifies a token address and amount. **Note**: Check the [CCIP Directory](/ccip/directory) for the list of supported tokens on each lane.
### feeToken
- **Definition**: Specifies which token to use for paying CCIP fees.
- **For native gas token**: Use `address(0)` (`ethers.ZeroAddress`) to specify the source chain's native gas token (e.g., ETH on Ethereum).
- **For ERC-20 tokens**: Specify the ERC-20 token address for fee payment. **Note**: Check the [CCIP Directory](/ccip/directory) for the list of supported fee tokens on your source chain.
## extraArgs
For Aptos-bound messages, the `extraArgs` parameter is a byte string composed of a 4-byte tag (`0x181dcf10`) prepended to the ABI-encoded tuple `(uint256 gasLimit, bool allowOutOfOrderExecution)`. This format is specified in the [`GenericExtraArgsV2`](/ccip/api-reference/evm/v1.6.2/client#genericextraargsv2) reference.
### gasLimit
- **Definition**: Specifies the amount of gas units to allocate for execution on Aptos.
- **Usage**: For simple token and data transfers as shown in the examples, `0` is a sufficient value. For complex programmatic token transfers involving significant computation in your receiving module, this value must be determined through testing.
### allowOutOfOrderExecution
- **Definition**: A boolean flag required for the message.
- **Usage**: The provided example scripts consistently use `true` for this value. It must be set to `true` when Aptos is the destination chain.
## Implementation by Message Type
### Token Transfer
Use this configuration when sending only tokens from an EVM chain to a user's wallet on Aptos.
### Arbitrary Messaging
Use this configuration when sending only a data payload to a custom module on Aptos.
### Programmatic Token Transfer (Data and Tokens)
Use this configuration when sending both tokens and a data payload to a custom module on Aptos.
## Related Tutorials
To see these concepts in action with step-by-step implementation guides, check out the following tutorials:
- [Token Transfers: EVM to Aptos](/ccip/tutorials/aptos/destination/token-transfers) - Learn how to implement token-only transfers from EVM chains to Aptos wallets.
- [Arbitrary Messaging: EVM to Aptos](/ccip/tutorials/aptos/destination/arbitrary-messaging) - Learn how to send data messages from EVM chains to Aptos modules.
- [Programmatic Token Transfers: EVM to Aptos](/ccip/tutorials/aptos/destination/programmatic-token-transfers) - Learn how to send both tokens and data in a single message to trigger module execution with token transfers on Aptos.
These tutorials provide complete, working examples using the concepts covered in this guide.
## Further Resources
- [**CCIP EVM Client API Reference**](/ccip/api-reference/evm/v1.6.2/client): Complete technical details about the `EVM2AnyMessage` struct, helper functions, and message construction on EVM chains.
- **Aptos TS-SDK Docs**: For more information on building the receiving Aptos module, refer to the [official Aptos TS-SDK docs](https://aptos.dev/en/build/sdks/ts-sdk).
---
# Prerequisites for EVM to Aptos Tutorials
Source: https://docs.chain.link/ccip/tutorials/aptos/destination/prerequisites
Last Updated: 2025-09-03
Before starting the EVM to Aptos tutorials, ensure you have:
## Development Environment
- **Aptos CLI**: Install the Aptos CLI by following the official [installation guide](https://aptos.dev/en/build/cli).
- **Node.js v20 or higher**: You can use the [nvm package](http://nvm.sh/) to install and switch between Node.js versions. Once installed, verify the node version with:
```bash
node -v
```
Example output:
```text
$ node -v
v22.15.0
```
- **Npm**: For installing and managing dependencies.
- **Git**: For cloning the repository.
## Starter Kit Repository
1. Clone the CCIP Aptos Starter Kit:
```bash
git clone https://github.com/smartcontractkit/aptos-starter-kit.git
```
2. Navigate to the directory:
```bash
cd aptos-starter-kit
```
3. Install dependencies:
```bash
npm install
```
## Understanding Named Addresses in the Manifest File (`Move.toml`)
The Move build system uses a manifest file, `Move.toml`, to define a package, its dependencies, and any named addresses it references. Named addresses are human-readable aliases for on-chain hexadecimal addresses, which makes the Move code cleaner, more readable, and easier to manage.
When you compile a Move package, the compiler replaces these aliases (e.g., `@ccip_router`) with their actual address values from the `Move.toml` file.
In the [`aptos-starter-kit`](https://github.com/smartcontractkit/aptos-starter-kit), the example modules (like `ccip_message_sender`, `ccip_message_receiver`) need to interact with the core CCIP modules already deployed on the Aptos Testnet. The `[addresses]` section in their `Move.toml` files provides the necessary references.
**Example `Move.toml` Address Block:**
```toml
[addresses]
ccip = "0xc748085bd02022a9696dfa2058774f92a07401208bbd34cfd0c6d0ac0287ee45"
mcms = "0xbdf1b9aacb4e21bf6f255105831df0172e911d4748e488196fde10d2e2a4e32d"
mcms_register_entrypoints = "0x0"
ccip_onramp = "0xc748085bd02022a9696dfa2058774f92a07401208bbd34cfd0c6d0ac0287ee45"
ccip_router = "0xc748085bd02022a9696dfa2058774f92a07401208bbd34cfd0c6d0ac0287ee45"
```
Here is a breakdown of what each named address represents:
- **`ccip`**: This is the main **Object address** under which the core CCIP packages and their modules (like `fee_quoter`, `rmn_remote`, `token_admin_registry`, etc.) are published.
- **`ccip_router`** & **`ccip_onramp`**: These aliases also point to the main CCIP Object address. This is because the `router` and `onramp` modules are part of the packages published under that single, unified Object. The aliases are defined for clarity and consistency in the Move code's `use` statements.
- **`mcms`**: This is the address of the on-chain **ManyChainMultiSig (MCMS)** module, which is responsible for handling governance and administrative actions for the CCIP protocol through a secure, time-locked [implementation process](/ccip/concepts/architecture/onchain/aptos/upgradability#implementation-process).
- **`mcms_register_entrypoints`**: This is a special value used as a **compile-time flag**. By setting it to `0x0`, the module is compiled in a way that bypasses the logic for registering with the MCMS system. This is useful for certain testing or deployment scenarios where the MCMS module is not relevant.
By defining these addresses in the `Move.toml` file, the example modules can interact with the core CCIP protocol without hardcoding long addresses, making the code portable and easier to update for different networks.
## Wallets
- **EVM Wallet and Private Key**: To send transactions from an EVM chain (like Ethereum Sepolia), you need a wallet and its private key.
- Set up a wallet like [MetaMask](https://metamask.io/).
- You will need to export the private key for the account you intend to send *from*. Follow the [official MetaMask guide](https://support.metamask.io/configure/accounts/how-to-export-an-accounts-private-key/) to obtain your private key and add it to the `.env` file as shown below.
- **Aptos Account**: You'll need an Aptos account. If you don't have one, create it with:
```bash
aptos init --network testnet
```
This command will guide you through creating a new account for Testnet and will save the credentials in a `.aptos/config.yaml` file. This also configures your Aptos CLI to use Testnet.
Verify your current configuration with:
```bash
aptos config show-profiles
```
This should show your `default` profile configured for Testnet, with the `network` set to `Testnet`.
Example output:
```text
$ aptos config show-profiles
{
"Result": {
"default": {
"network": "Testnet",
"has_private_key": true,
"public_key": "ed25519-pub-0x2ecdd2d7bc0cbfe2e44c219ef9a9fddc986b384f4a01fb5d821cf0dab5d2fbae",
"account": "d0e227835c33932721d54ae401cfaae753c295024fe454aa029b5e2782d2fad4",
"rest_url": "https://fullnode.testnet.aptoslabs.com"
}
}
}
```
Note down the `account` value from the output and **prepend `0x` to it**. You will need to use this value as one of the environment variables in your `.env` file, as shown below.
## Environment Configuration (`.env` file)
The starter kit uses a `.env` file to manage sensitive information like private keys and RPC URLs. Create a new file named `.env` in the root of the `aptos-starter-kit` directory by copying the example file:
```bash
cp .env.example .env
```
Next, open the `.env` file and fill in the following values:
- `PRIVATE_KEY`: The private key of your source wallet (EOA) on Ethereum Sepolia from which you're sending CCIP-BnM tokens. You can export your private key from your MetaMask Wallet, as shown in the [Wallets](/ccip/tutorials/aptos/destination/prerequisites#wallets) section above.
- `ETHEREUM_SEPOLIA_RPC_URL`: The RPC endpoint for the Ethereum Sepolia testnet. This is required to interact with the Ethereum Sepolia network. You can obtain an RPC URL by signing up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service.
**Example `.env` file:**
```
PRIVATE_KEY=
ETHEREUM_SEPOLIA_RPC_URL=
```
## Native Tokens for Transaction Fees
**ETH** tokens are used for Ethereum Sepolia transaction fees. For these tutorials, we will also use **ETH** to pay for CCIP fees.
- You can use the [Chainlink Faucet](https://faucet.chain.link) to get test ETH.
## Obtaining Testnet Tokens
### LINK Tokens on EVM Chains
When using LINK tokens to pay for CCIP fees, you will need LINK tokens on Ethereum Sepolia. You can use the [Chainlink Faucet](https://faucet.chain.link) to get test LINK tokens.
### BnM Tokens on EVM Chains
To obtain CCIP-BnM tokens on Ethereum Sepolia, you can use the [EVM Chains](/ccip/test-tokens#evm-chains) section to get test BnM tokens.
---
# Token Transfers: EVM to Aptos
Source: https://docs.chain.link/ccip/tutorials/aptos/destination/token-transfers
Last Updated: 2025-09-03
This tutorial demonstrates how to transfer tokens from an Ethereum Virtual Machine (EVM) chain to an Aptos wallet using Chainlink CCIP. You will learn how to build a CCIP message on an EVM chain, send it to the CCIP router, and verify the transfer on the destination chain.
## Introduction
This tutorial covers transferring tokens from Ethereum Sepolia to an Aptos wallet without any additional data payload.
## What You will Build
In this tutorial, you will:
- Use a script to configure a CCIP message for a token-only transfer.
- Send CCIP-BnM test tokens from Ethereum Sepolia to an Aptos wallet.
- Pay for CCIP transaction fees using either native ETH or LINK tokens.
- Monitor and verify your cross-chain transfer.
## Understanding Token Transfers to Aptos
This tutorial focuses on token-only transfers from an EVM chain to an Aptos wallet.
Key points specific to token-only transfers to Aptos:
- **No Program Execution**: Tokens are transferred directly to a wallet without executing a custom module.
- **Mandatory Settings**:
- The `receiver` field in the CCIP message must be the **end user's Aptos wallet address**.
- The `data` field must be empty (`0x`).
- The `extraArgs` field should be encoded with `gasLimit: 0` and `allowOutOfOrderExecution: true`.
## Implementing Token Transfers
In this section, you'll execute a token transfer from Ethereum Sepolia to Aptos Testnet using the example script located at `scripts/evm2aptos/ccipSendTokenRouter.ts` in the starter kit.
### Token Transfer Configuration
The core of the transfer is configuring the [`EVM2AnyMessage`](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) correctly. The script handles this for you by calling a `buildCCIPMessage` function. Here's what the configuration looks like within the script:
```typescript
// Inside the transferTokenPayNative function for example:
const ccipMessage = buildCCIPMessage(
recipient, // Your Aptos wallet address from .env
"0x", // Data is empty for a token-only transfer
tokenAddress, // CCIP-BnM token address on Ethereum Sepolia
tokenAmount, // The amount of tokens to send
ethers.ZeroAddress, // For paying fees in native ETH
encodeExtraArgsV2(0n, true) // gasLimit is 0, allowOutOfOrderExecution is true
)
```
### How the Script Works
The `evm2aptos/ccipSendTokenRouter.ts` script automates the entire cross-chain transfer process. Based on your command-line arguments, it performs the following steps:
1. **Selects Chain Configuration**: Selects the correct RPC URL and contract addresses for the Ethereum Sepolia testnet based on the `--sourceChain sepolia` argument.
2. **Builds the CCIP Message**: Configures the message with the parameters shown above.
3. **Calculates Fees**: Calls the [`getFee`](/ccip/api-reference/evm/v1.6.2/i-router-client#getfee) function on the source chain's Router to determine the required CCIP fee.
4. **Approves Tokens**:
- Approves the Router to spend the CCIP-BnM tokens you are transferring.
- If paying with LINK, it also approves the Router to spend the required amount of LINK tokens for the fee.
5. **Executes Transfer**: Calls the [`ccipSend`](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend) function on the CCIP Router contract to initiate the cross-chain transfer, sending native tokens for the fee if specified.
6. **Returns Message ID**: Parses the transaction receipt to find and display the `CCIPMessageSent` event and its unique `messageId`.
## Running the Token Transfer
### Prerequisites Check
1. Ensure you've completed the setup steps outlined in the [prerequisites](/ccip/tutorials/aptos/destination/prerequisites).
2. Make sure your `.env` file contains your `PRIVATE_KEY` (of your wallet on Ethereum Sepolia from which you're sending CCIP-BnM tokens) and `ETHEREUM_SEPOLIA_RPC_URL`.
3. Verify you have sufficient native ETH and CCIP-BnM token balances in your EVM wallet on Ethereum Sepolia network.
### Execute the Script
Run the following command from your terminal to transfer CCIP-BnM tokens from Ethereum Sepolia to Aptos Testnet, paying the fee in native ETH. You can change the value for the `--amount` flag to send a different number of tokens.
```bash
npx ts-node scripts/evm2aptos/ccipSendTokenRouter.ts --sourceChain sepolia --feeToken native --amount 0.001 --aptosReceiver <0x_YOUR_APTOS_WALLET_ADDRESS>
```
To pay with LINK instead, change the `feeToken` argument:
```bash
npx ts-node scripts/evm2aptos/ccipSendTokenRouter.ts --sourceChain sepolia --feeToken link --amount 0.001 --aptosReceiver <0x_YOUR_APTOS_WALLET_ADDRESS>
```
### Understanding the Output
When the script executes successfully, you'll see output similar to this, showing the steps for a transfer from Ethereum Sepolia:
```
Base Fee (in WEI): 80690622338859
Fee with 20% buffer (in WEI): 96828746806630
Current Allowance of CCIP-BnM token: 0
Approval tx sent: 0x1a1fb6362f2eac4a6a52e4974e15fa071bc9084ad99273ef8aa5b37a9bc0568d
Approval transaction confirmed in block 8775988 after 3 confirmations.
Router contract approved to spend 1000000000000000 of CCIP-BnM token from your account.
Proceeding with the token transfer...
Transaction sent: 0x06d2657c1939f5cba4b29052977355a5cee131a6ffefe80550aeb80c383b9450
Waiting for transaction confirmation...
Transaction confirmed in block 8775991 after 3 confirmations.
✅ Transaction successful: https://sepolia.etherscan.io/tx/0x06d2657c1939f5cba4b29052977355a5cee131a6ffefe80550aeb80c383b9450
🆔 CCIP Message ID: 0x54d2e9fb4b7e852b30fae6617a568e729b714b5d658fa85592def65901b84c56
🔗 CCIP Explorer URL: https://ccip.chain.link/#/side-drawer/msg/0x54d2e9fb4b7e852b30fae6617a568e729b714b5d658fa85592def65901b84c56
```
- The output displays the base fee and the fee with a 20% buffer, both shown in the smallest denomination of the fee token (i.e., WEI for the native token).
- It checks the current allowance of the CCIP-BnM token and approves the Router contract to spend the specified amount if necessary.
- The transaction hash is displayed, which you can use to track the transfer on Ethereum Sepolia.
- The output provides the unique `CCIP Message ID`, which is essential for tracking your transfer.
- The CCIP Explorer URL allows you to monitor the message status across chains.
## Verification and Monitoring
After sending your token transfer, you can verify its arrival on Aptos in the following ways:
### Check Message Execution
#### Use the CCIP Explorer to check the message status
Use the CCIP Explorer link provided in the transaction output to track your message status across chains. The explorer gives an overview of the entire cross-chain transaction life cycle.
```text
🔗 CCIP Explorer URL: https://ccip.chain.link/#/side-drawer/msg/
```
#### Programmatically check the message status
After you receive a CCIP Message ID, you can programmatically check if the CCIP message has been successfully executed on the Aptos network. This is done by querying the [`ExecutionStateChanged`](/ccip/api-reference/aptos/v1.6.0/events#execute_single_report) event emitted by the CCIP OffRamp module. The `evm2aptos/checkMsgExecutionStateOnAptos.ts` script is designed for this purpose.
After 15-20 minutes, run the script using the CCIP Message ID you received from the previous step.
**Command**:
```bash
npx ts-node scripts/evm2aptos/checkMsgExecutionStateOnAptos.ts --msgId
```
*Replace `` with the actual CCIP Message ID from the log output.*
**Output**:
When the message has been successfully delivered, you will see the following output:
```text
Execution state for CCIP message 0x54d2e9fb4b7e852b30fae6617a568e729b714b5d658fa85592def65901b84c56 is SUCCESS
```
### Verify Token Balance
Once the script confirms a `SUCCESS` state, you can perform a final verification on a block explorer.
- Visit the [Aptos Explorer](https://explorer.aptoslabs.com/?network=testnet).
- Search for your Aptos wallet address.
- Under the "Coins" tab, you should see the balance of the CCIP-BnM token you transferred.
---
# Arbitrary Messaging: EVM to Aptos
Source: https://docs.chain.link/ccip/tutorials/aptos/destination/arbitrary-messaging
Last Updated: 2025-09-03
This tutorial demonstrates how to send arbitrary data from an Ethereum Virtual Machine (EVM) chain to a Move module on the Aptos blockchain using Chainlink's Cross-Chain Interoperability Protocol (CCIP). You will learn how to configure a CCIP message that triggers a module execution on the destination chain.
## Introduction
This tutorial shows you how to send a data-only message from the Ethereum Sepolia testnet to a receiver module on the Aptos testnet.
## What You will Build
In this tutorial, you will:
- Publish a CCIP receiver module to the Aptos Testnet.
- Configure a CCIP message for arbitrary data messaging.
- Send data from Ethereum Sepolia to your Aptos module.
- Pay for CCIP transaction fees using LINK tokens.
- Verify that the data was received and processed by the module on Aptos.
## Understanding Arbitrary Messaging to Aptos
This tutorial focuses on arbitrary messaging from EVM chains to Aptos Move modules. For detailed information about CCIP message structure and parameters, refer to the [guide on building CCIP messages from EVM to Aptos](/ccip/tutorials/aptos/destination/build-messages).
### Key Points Specific to Arbitrary Messaging
- **Module Execution**: The message is sent to a specific module on Aptos, triggering the execution of its `ccip_receive` function.
- **Mandatory Settings**:
- The `receiver` field of the CCIP message must be the account address of your destination Aptos module.
- The `tokenAmounts` array must be empty (`[]`).
- The `extraArgs` field should be encoded with a `gasLimit` and a `allowOutOfOrderExecution` flag.
- The `gasLimit` must be a tested value and sufficient for the execution of the `ccip_receive` function of the receiver module (i.e., the destination Aptos module).
- The `allowOutOfOrderExecution` flag must be set to `true` when Aptos is the destination chain.
### Key Differences from Token Transfers
### The `ccip_message_receiver` Module
This tutorial uses the `ccip_message_receiver` module from the `aptos-starter-kit` as the destination.
The `ccip_message_receiver` is a simple module designed to receive arbitrary data. When its `ccip_receive` function is called, it decodes the data as a string and emits a `ReceivedMessage` event, providing a clear on-chain record that the message was processed.
## Implementing Arbitrary Messaging
In this section, you'll first publish the receiver module to Aptos and then use a script to send a message to it.
### Publish the Receiver Module
Before you can send a message, the destination module must exist on the Aptos Testnet. The starter kit provides a script to create a **Resource Account** and publish the `ccip_message_receiver` module to it.
Run the following command:
```bash
npx ts-node scripts/deploy/aptos/createResourceAccountAndPublishReceiver.ts
```
This command will output the address of the newly created resource account. **Copy this address**, as you will need it for the next step.
### Configure and Send the Message
The `evm2aptos/ccipSendMsgRouter.ts` script handles the configuration and sending of the CCIP message. The core of the script builds the message payload:
```typescript
// From scripts/evm2aptos/ccipSendMsgRouter.ts
const ccipMessage = buildCCIPMessage(
recipient, // The address of your deployed Aptos receiver module
hexlify(toUtf8Bytes("Hello Aptos from EVM")), // Your data, hex-encoded
networkConfig.sepolia.linkTokenAddress, // Fee token
encodeExtraArgsV2(0n, true) // gasLimit is 0, allowOutOfOrderExecution is true
)
```
## Running the Arbitrary Messaging Script
### Execute the Script
Run the script from your terminal. You will need to provide the `--aptosReceiver` address you copied from the deployment step. This example sends from **Ethereum Sepolia**.
```bash
npx ts-node scripts/evm2aptos/ccipSendMsgRouter.ts --sourceChain sepolia --feeToken link --aptosReceiver --msgString "Hello Aptos from EVM"
```
### Expected Output
The script will output the progress of the transaction, including approvals and fee calculations, and finish by providing the transaction hash and the CCIP Message ID.
```text
Base Fee (in LINK JUELS): ...
Fee with 20% buffer (in LINK JUELS): ...
Current Allowance of LINK token: 0
Approval tx sent: 0x...
Approval transaction confirmed in block ... after 3 confirmations.
Router contract approved to spend ... of LINK token from your account.
Proceeding with the message transfer...
Transaction sent: 0x...
Waiting for transaction confirmation...
Transaction confirmed in block 8803642 after 3 confirmations.
✅ Transaction successful: https://sepolia.etherscan.io/tx/0x...
🆔 CCIP Message ID: 0x...
🔗 CCIP Explorer URL: https://ccip.chain.link/#/side-drawer/msg/0x...
```
## Verification: Retrieving the Message
After sending the message, you can verify its delivery and processing on Aptos.
### Check Message Execution
#### Use the CCIP Explorer to check the message status
Use the CCIP Explorer link provided in the transaction output to track your message status across chains. The explorer gives an overview of the entire cross-chain transaction life cycle.
```text
🔗 CCIP Explorer URL: https://ccip.chain.link/#/side-drawer/msg/
```
#### Programmatically check the message status
After you receive a CCIP Message ID, you can programmatically check if the CCIP message has been successfully executed on the Aptos network. This is done by querying the [`ExecutionStateChanged`](/ccip/api-reference/aptos/v1.6.0/events#execute_single_report) event emitted by the CCIP OffRamp module. The `evm2aptos/checkMsgExecutionStateOnAptos.ts` script is designed for this purpose.
After 15-20 minutes, run the script using the CCIP Message ID you received from the previous step.
**Command**:
```bash
npx ts-node scripts/evm2aptos/checkMsgExecutionStateOnAptos.ts --msgId
```
*Replace `` with the actual CCIP Message ID from the log output.*
**Output**:
When the message has been successfully delivered, you will see the following output:
```text
Execution state for CCIP message is SUCCESS
```
### Query the Receiver Module
Once the message is successfully executed, you can verify that your receiver module processed it. The `getLatestMessageOnAptos.ts` script queries the `ReceivedMessage` event that your module emitted.
Run the verification script, passing the address of your receiver module:
```bash
npx ts-node scripts/evm2aptos/getLatestMessageOnAptos.ts --aptosReceiver
```
### Expected Verification Output
When you run the verification script, you should see the decoded message that was stored by your module, confirming the end-to-end flow was successful.
```text
Latest message received on Aptos at : Hello Aptos from EVM
```
You can also manually verify this by finding the `offramp::execute` transaction for your module's address in the [Aptos Explorer](https://explorer.aptoslabs.com/?network=testnet) and checking the `Events` tab.
---
# Programmatic Token Transfers: EVM to Aptos
Source: https://docs.chain.link/ccip/tutorials/aptos/destination/programmatic-token-transfers
Last Updated: 2025-09-03
This tutorial demonstrates how to send a programmatic token transfer—a message containing both tokens and arbitrary data—from an Ethereum Virtual Machine (EVM) chain to a Move module on the Aptos blockchain using Chainlink CCIP.
## Introduction
This tutorial shows you how to send CCIP-BnM tokens from the Ethereum Sepolia testnet to a receiver module on the Aptos testnet. The message will also include a data payload containing a final recipient address. The receiver module will then execute logic to forward the received tokens to that final address.
## What You will Build
In this tutorial, you will:
- Publish a CCIP receiver module to the Aptos Testnet.
- Configure a CCIP message containing both a token transfer and a data payload.
- Send the message from Ethereum Sepolia to your Aptos module.
- Pay for CCIP transaction fees using LINK or native ETH.
- Verify that the receiver module executed its logic and forwarded the tokens to the final destination.
## Understanding Programmatic Token Transfers
A programmatic token transfer combines the features of a token transfer and an arbitrary message. It allows you to send assets and instructions in a single, atomic cross-chain transaction.
- **Module Execution**: The message and tokens are sent to a specific module on Aptos, triggering the execution of its `ccip_receive` function.
- **Combined Payload**:
- The `receiver` is the address of your custom Aptos module.
- The `tokenAmounts` array is populated with the tokens to transfer.
- The `data` field contains the instructions for the module.
- The `extraArgs` field should be encoded with a `gasLimit` and a `allowOutOfOrderExecution` flag.
- The `gasLimit` must be a tested value and sufficient for the execution of the `ccip_receive` function of the receiver module (i.e., the destination Aptos module). This includes performing a token transfer in this case, but may also include other logic depending on the module's implementation and the exact use case.
- The `allowOutOfOrderExecution` flag must be set to `true` when Aptos is the destination chain.
### The `ccip_message_receiver` Module
This tutorial uses the `ccip_message_receiver` module from the `aptos-starter-kit`. Its `ccip_receive` function contains dispatcher logic. For this tutorial, we will trigger the part of its logic that handles a message containing both tokens and data.
- **Logic**: When the module receives both tokens and data, it interprets the `data` payload as the 32-byte address of a final recipient. It then uses its on-chain `signer` capability (derived from being deployed on a Resource Account) to transfer the tokens it just received to that final recipient address. Finally, it emits a `ForwardedTokens` event.
## Implementing the Programmatic Transfer
### Publish the Receiver Module
First, the destination module must be deployed on the Aptos Testnet. Because this module will handle and transfer tokens, it must be deployed to a **Resource Account**.
Run the following command from the starter kit:
```bash
npx ts-node scripts/deploy/aptos/createResourceAccountAndPublishReceiver.ts
```
**Copy the address** of the new resource account from the output. This is your receiver module's address.
### Configure and Send the Message
The `evm2aptos/ccipTokenForwarder.ts` script handles the configuration and sending of the message. The core of the script builds the message payload:
```typescript
// From scripts/evm2aptos/ccipTokenForwarder.ts
const ccipMessage = buildCCIPMessage(
recipient, // The address of your deployed Aptos receiver module
aptosAccountAddress, // The final recipient's Aptos address, sent as data
tokenAddress, // The address of the CCIP-BnM token on Sepolia
tokenAmount, // The amount of tokens to send
feeTokenAddress, // The address of the fee token (e.g., LINK)
encodeExtraArgsV2(100000n, true) // A gasLimit for the receiver's logic
)
```
## Running the Script
### Execute the Script
Run the script from your terminal. You will need to provide three key arguments:
- `--aptosReceiver`: The address of the module you just deployed.
- `--aptosAccount`: The final destination address where the tokens should be forwarded.
- `--amount`: The number of tokens to send.
This example sends from **Ethereum Sepolia** and pays fees in LINK:
```bash
npx ts-node scripts/evm2aptos/ccipTokenForwarder.ts --sourceChain sepolia --feeToken link --amount 0.001 --aptosReceiver --aptosAccount
```
### Expected Output
The script will output the progress of the transaction and finish by providing the transaction hash and the CCIP Message ID.
```text
Base Fee (in LINK JUELS): ...
Fee with 20% buffer (in LINK JUELS): ...
Current Allowance of CCIP-BnM token: 0
Approval tx sent: 0x...
Approval transaction confirmed in block 8803748 after 3 confirmations.
Router contract approved to spend 1000000000000000 of CCIP-BnM token from your account.
Current Allowance of LINK token: 0
Approval tx sent: 0x...
Approval transaction confirmed in block ... after 3 confirmations.
Router contract approved to spend ... of LINK token from your account.
Proceeding with the token transfer...
Transaction sent: 0x...
Waiting for transaction confirmation...
Transaction confirmed in block 8803754 after 3 confirmations.
✅ Transaction successful: https://sepolia.etherscan.io/tx/0x...
🆔 CCIP Message ID: 0x...
🔗 CCIP Explorer URL: https://ccip.chain.link/#/side-drawer/msg/0x...
```
## Verification
Verification is a multi-step process: you must confirm the message was executed and then confirm that your module's logic produced the correct outcome.
### Check Message Execution
#### Use the CCIP Explorer to check the message status
Use the CCIP Explorer link provided in the transaction output to track your message status across chains. The explorer gives an overview of the entire cross-chain transaction life cycle.
```text
🔗 CCIP Explorer URL: https://ccip.chain.link/#/side-drawer/msg/
```
#### Programmatically check the message status
After you receive a CCIP Message ID, you can programmatically check if the CCIP message has been successfully executed on the Aptos network. This is done by querying the [`ExecutionStateChanged`](/ccip/api-reference/aptos/v1.6.0/events#execute_single_report) event emitted by the CCIP OffRamp module. The `evm2aptos/checkMsgExecutionStateOnAptos.ts` script is designed for this purpose.
After 15-20 minutes, run the script using the CCIP Message ID you received from the previous step.
**Command**:
```bash
npx ts-node scripts/evm2aptos/checkMsgExecutionStateOnAptos.ts --msgId
```
*Replace `` with the actual CCIP Message ID from the log output.*
**Output**:
When the message has been successfully delivered, you will see the following output:
```text
Execution state for CCIP message is SUCCESS
```
### Verify the Outcome
Once execution is successful, you need to verify that your module correctly forwarded the tokens.
- **Check the Event**: You can manually verify that your module emitted the correct `ForwardedTokens` event by using the [Aptos Explorer](https://explorer.aptoslabs.com/?network=testnet).
1. Search for your receiver module's address (the one you provided with `--aptosReceiver`).
2. In the `Transactions` tab, find the latest transaction that calls the `offramp::execute` function.
3. Click on the transaction and navigate to the `Events` tab.
4. You should find an event with the following structure, confirming that your module's logic was executed:
```text
Account Address:
Creation Number: 3
Sequence Number: 0
Type: ::ccip_message_receiver::ForwardedTokens
Data: {
final_recipient: ""
}
```
- **Check the Final Balance**: The ultimate verification is checking the token balance of the final recipient. Search for the address you provided in the `--aptosAccount` argument on the Aptos Explorer. Under the "Tokens" tab, you should see the new balance of the CCIP-BnM token.
---
# Cross-Chain Token (CCT) Tutorials
Source: https://docs.chain.link/ccip/tutorials/aptos/cross-chain-tokens
Last Updated: 2025-09-03
---
# CCIP Explorer
Source: https://docs.chain.link/ccip/tools-resources/ccip-explorer
Last Updated: 2025-05-19
The [Chainlink CCIP Explorer](https://ccip.chain.link/) is a web interface for tracking the status of cross-chain messages.
## Key Features
- **Transaction Tracking**: View the latest CCIP transactions processed across supported blockchains. You can search for specific transactions using their unique Message ID.
- **Network & Lane Status**: Observe the status of supported blockchains.
Use the CCIP Explorer to gain insights into message processing times, monitor network health, and confirm the successful execution of your cross-chain transactions.
---
# CCIP Token Manager
Source: https://docs.chain.link/ccip/tools-resources/token-manager
Last Updated: 2025-05-19
The CCIP Token Manager is a web interface designed to simplify the deployment, configuration, and management of Cross-Chain Tokens for use with Chainlink CCIP.
It provides guided workflows for token developers, abstracting away much of the complexity involved in setting up tokens for cross-chain transfers.
**Access the Token Manager:**
- **Mainnet:** [https://tokenmanager.chain.link/](https://tokenmanager.chain.link/)
- **Testnet:** [https://test.tokenmanager.chain.link/](https://test.tokenmanager.chain.link/)
## Key Features & Workflows
- **Deploy New Tokens**: Guides users through deploying a new token contract from scratch and configuring it for CCIP, primarily using the Burn & Mint mechanism.
- **Enable Existing Tokens**: Assists in making already-deployed tokens cross-chain capable by deploying and configuring the necessary CCIP token pool contracts.
- **Configuration Management**: Allows token administrators (once connected via wallet) to manage certain token pool settings, such as rate limits.
- **Admin Role Management**: Facilitates the process of registering and accepting administrative roles for token pools.
- **Token Verification**: Provides a process to request token verification for listing in the CCIP Directory.
- **Dashboard View**: Offers a dashboard to view and manage tokens associated with the connected wallet.
---
# CCIP Network Specific Guides
Source: https://docs.chain.link/ccip/tools-resources/network-specific
This section provides guidance on how to integrate with networks that require specific setup in order to successfully utilize CCIP.
## Hyperliquid
- [Hyperliquid Integration Guide](/ccip/tools-resources/network-specific/hyperliquid-integration-guide)
- [HyperEVM Testnet RPC Guide](/ccip/tools-resources/network-specific/hyperevm-testnet-rpc)
---
# Hyperliquid Integration Guide
Source: https://docs.chain.link/ccip/tools-resources/network-specific/hyperliquid-integration-guide
CCIP is fully compatible with Hyperliquid, allowing multichain tokens to be eligible for trading on the Hyperliquid decentralized exchange. This guide will provide comprehensive information about Hyperliquid for CCIP users who want to get enabled on Hyperliquid.
## How Hyperliquid Works
Hyperliquid is an emerging app-chain that powers a popular decentralized exchange with over $4B USDC bridged in. Hyperliquid operates on a dual network system made up of HyperCore and HyperEVM which are fully integrated together to compose the general Hyperliquid State.
- HyperCore is the network where high-speed trading takes place at up to 200k orders per second, secured by HyperBFT Consensus. Trading occurs through a Central Limit Order Book (CLOB) which is different from most decentralized exchange liquidity pool structures.
- HyperEVM is where the supporting infrastructure and contracts are deployed, such as the MintAndBurnERC20 tokens deployed as a part of a CCIP implementation. There is a growing DeFi ecosystem including Money Markets, LSTs and DEXes on HyperEVM.
| | HyperCore | HyperEVM |
| :------------------: | :-----------------------------------------------------------------: | :----------------------------------------------------: |
| **Primary Function** | High-speed decentralized exchange trading via an onchain order book | Supporting infrastructure and external smart contracts |
| **Token Type** | HIP-1 tokens | ERC20 tokens |
| **Deployment** | Requires winning a spot slot auction | Standard ERC20 deployment |
| **Interaction** | Posting “signed actions” through a Rest API Endpoint | Standard RPC Methods |
### HyperCore
HyperCore is a bespoke L1 blockchain designed to deliver low latency, high throughput, and instant finality for the use case of high speed trading of spot assets and perpetual futures. This enables a user experience and market dynamic that feels like a centralized exchange. Users and developers interact with HyperCore by posting signed actions to the [HyperCore API](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api).
#### HIP-1 Token Standard
The native token standard for spot trading on HyperCore is called [HIP-1](https://hyperliquid.gitbook.io/hyperliquid-docs/hyperliquid-improvement-proposals-hips/hip-1-native-token-standard). HIP-1 tokens are fungible and have a fixed supply, which must be declared at the time of initialization.
To deploy a HIP-1 token, the issuer must first win a Dutch auction for a spot slot—a permissioned right to enable a token for trading on Hyperliquid. These auctions are held every 31 hours and typically require a winning bid of tens of thousands of dollars worth of HYPE.
Once a spot slot is secured, the owner can proceed with a defined deployment flow, including initializing the token supply and registering the trading pair—as outlined in the steps below.
### HyperEVM
HyperEVM is largely similar to any other EVM network, allowing standard development tooling to be used and similar smart contract design patterns. The main differences lie in the Dual Block Size architecture and the access to the HyperCore state via read precompiles.
#### Dual Block Architecture
The HyperEVM blockchain operates using a [dual block architecture, consisting of small, fast blocks and large, slow blocks](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/dual-block-architecture):
- **Small blocks (2M gas limit)** are produced every 1 second and handle typical user transactions.
- **Large blocks (30M gas limit)** are produced every 1 minute and are used for more complex operations such as contract deployments.
Users can control which block type their transaction is included in by toggling their block size setting. This is done by submitting a signed action—a type of authenticated message—to a specific endpoint on the HyperCore API.
For example, when deploying a CCIP token on HyperEVM, a developer must first switch to large blocks by submitting the appropriate action to the API. This ensures the transaction has sufficient gas capacity to succeed within a large block.
```json
{ "type": "evmUserModify", "usingBigBlocks": true }
```
#### Read Precompiles
Smart contracts on HyperEVM can access data from HyperCore by calling special system-level addresses known as [precompiles](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/interacting-with-hypercore). These addresses don't correspond to regular smart contracts; instead, they trigger native code execution on the validator node.
For example, when a contract on HyperEVM queries a token balance by calling a designated read precompile, it bypasses standard EVM execution. The call is handled directly by the validator, which fetches the requested data from HyperCore in real time. This mechanism provides secure, gas-efficient, read-only access to the core exchange state — including data such as order book balances, positions, and other protocol-level metrics.
This design enables the development of onchain applications that can respond to the live state of the central limit order book on HyperCore. The same precompile infrastructure also powers cross-chain bridging, allowing seamless integration between ERC-20 tokens on HyperEVM and their linked HIP-1 counterparts on HyperCore.
In the following example snippet, a smart contract on HyperEVM can trustlessly read the balance of an HIP1 token on HyperCore.
```solidity
address constant SPOT_BALANCE_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801;
struct SpotBalance {
uint64 total;
uint64 hold;
uint64 entryNtl;
}
function spotBalance(address user, uint64 token) external view returns (SpotBalance memory) {
bool success;
bytes memory result;
(success, result) = SPOT_BALANCE_PRECOMPILE_ADDRESS.staticcall(abi.encode(user, token));
require(success, "SpotBalance precompile call failed");
return abi.decode(result, (SpotBalance));
}
```
## Integration Steps
ERC-20 tokens deployed on HyperEVM can be linked to HIP-1 tokens on HyperCore by the same EOA that deployed the ERC-20 contract. Once linked, users can seamlessly convert their ERC-20 tokens on HyperEVM into the corresponding HIP-1 tokens on HyperCore using a native Lock-and-Release mechanism enabled by the dual-network architecture.
Each HIP-1 token is assigned a unique token index, which serves two purposes:
- It is used in API calls to identify the token.
- It is used to derive the system address that acts as a bridge for transfers between HyperEVM and HyperCore.
For example, the system address for token index 1385 is:
`0x2000000000000000000000000000000000000569`.
### Deploying CCIP Tokens on Hyperliquid
To make a CCIP token tradable on Hyperliquid, follow these high-level steps:
1. **Secure Spot Slot via Auction**\
Use the Hyperliquid UI to win the spot slot auction and secure the right to deploy a HIP-1 token.
2. **Deploy Token on HyperEVM**
- Deploy your CCIP token to HyperEVM.
- In order to link the HIP-1 token on core to the deployed token on HyperEVM, ensure the token is deployed by an EOA (Externally Owned Account) capable of signing messages.
3. **Initialize the HIP-1 Token**
- Allocate the **maximum intended supply** to the **System Address** derived from the spot slot ID.
- This System Address is consistent across HyperEVM and HyperCore and serves as a bridge between the ERC-20 and HIP-1 tokens.
- Think of this minting step as "funding the bridge" for inbound transfers.
- If the source token has **unbounded supply**, mint a large number (e.g., `2^64 - 1`).
- If the source token has **fixed supply**, mint the full max supply.
- Use the **HyperCore API** for this initialization.
4. **Register Trading Pair**
- Register the trading pair between the HIP-1 token and **USDC** using the **HyperCore API**.
5. **Link HIP-1 Token to ERC-20 Token**
- Make a signed API request to the **HyperCore network** from the EOA that deployed the ERC-20 token.
- This confirms the intent to link the tokens.
6. **Connect the CCIP Token Pool**
- Deploy and configure a BurnAndMint Token Pool on HyperEVM using **CCIP Token Manager**.
### Bridging Between HyperCore and HyperEVM
Once the above steps are completed, a user can bridge their CCIP token from any connected network to HyperEVM as normal. Then they can [convert that ERC20 token into the corresponding HIP-1 token](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/hypercore-less-than-greater-than-hyperevm-transfers#transferring-between-core-and-evm) by simply sending it to the associated System Address. The system address then sends them the same amount of HIP-1 tokens on HyperCore. This works in the other direction in a similar fashion.
## Contact Us
Before proceeding, it’s essential to carefully review your deployment configuration. Even minor misconfigurations can permanently impact your spot slot, potentially requiring you to reacquire one through another auction.
To help ensure a smooth and secure deployment, we currently offer white-glove support for CCIP users. Our team will validate your configuration and, if desired, provide tools and assistance to complete this process.
If you're ready to deploy your CCIP token to Hyperliquid, please [contact us here](https://chain.link/ccip-contact?v=General%20Technical%20Support) to schedule a deployment. Stay tuned—enhanced tooling is on the way to further streamline this process for CCIP users.
---
# HyperEVM Testnet RPC Guide
Source: https://docs.chain.link/ccip/tools-resources/network-specific/hyperevm-testnet-rpc
Last Updated: 2025-10-08
To enhance the HyperEVM developer experience, we provide free public access to our professionally hosted Testnet RPC endpoint. Developers can use this endpoint to build Hyperliquid applications and configure it as a testnet RPC in browser wallets. For mainnet deployments, see available mainnet RPC URLs on the [Hyperliquid docs](https://hyperliquid.gitbook.io/hyperliquid-docs/builder-tools/hyperevm-tools).
## RPC Endpoint Details
- **Network Name**: HyperEVM Testnet
- **RPC URL**: [https://rpcs.chain.link/hyperevm/testnet](https://rpcs.chain.link/hyperevm/testnet)
- **Chain ID**: 998
- **Currency Symbol**: HYPE
- **Block Explorer URL (Optional)**: [https://testnet.purrsec.com/](https://testnet.purrsec.com/)
## For Developers: Building with the RPC
You can use this endpoint in your dApps, scripts, or backend services with any standard Web3 library.
### Ethers.js (JavaScript/TypeScript)
[Ethers.js](https://docs.ethers.org/v6/) is a library for interacting with Ethereum and EVM-compatible chains.
```javascript
import { ethers } from "ethers"
const rpcUrl = "https://rpcs.chain.link/hyperevm/testnet"
const provider = new ethers.JsonRpcProvider(rpcUrl)
async function getBlock() {
const blockNumber = await provider.getBlockNumber()
console.log("Current Block Number:", blockNumber)
}
getBlock()
```
### Viem (JavaScript/TypeScript)
[Viem](https://viem.sh/) is a TypeScript library for interacting with Ethereum and EVM-compatible chains. It provides modular, type-safe APIs as an alternative to ethers.js.
```javascript
import { createPublicClient, http } from "viem"
const rpcUrl = "https://rpcs.chain.link/hyperevm/testnet"
const client = createPublicClient({
transport: http(rpcUrl),
})
async function getBlock() {
const blockNumber = await client.getBlockNumber()
console.log("Current Block Number:", blockNumber)
}
getBlock()
```
### Web3.py (Python)
[Web3.py](https://web3py.readthedocs.io/) is a Python library for interacting with EVM-compatible chains.
```python
from web3 import Web3
rpc_url = "https://rpcs.chain.link/hyperevm/testnet"
w3 = Web3(Web3.HTTPProvider(rpc_url))
if w3.is_connected():
print("Connected to RPC!")
block_number = w3.eth.block_number
print("Current Block Number:", block_number)
else:
print("Failed to connect.")
```
### cURL (Testing)
```shell
curl -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
https://rpcs.chain.link/hyperevm/testnet
```
**Expected Response**:
```json
{ "jsonrpc": "2.0", "id": 1, "result": "0x1f92031" }
```
## For Users: Adding to Your Wallet
You can easily add this network to browser extension wallets like [MetaMask](https://metamask.io/) or [Rabby](https://rabby.io/).
### Using MetaMask
[MetaMask](https://metamask.io/) is a popular browser extension wallet for Ethereum and EVM-compatible chains.
1. Open your MetaMask wallet.
2. Click the network selection dropdown at the top left.
3. Click "Add network".
4. At the bottom, select "Add a network manually".
5. Fill in the form with the network details:
- Network Name: HyperEVM Testnet
- RPC URL: [https://rpcs.chain.link/hyperevm/testnet](https://rpcs.chain.link/hyperevm/testnet)
- Chain ID: 998
- Currency Symbol: HYPE
- Block Explorer URL (Optional): [https://testnet.purrsec.com/](https://testnet.purrsec.com/)
6. Click "Save".
### Using Rabby
[Rabby](https://rabby.io/) is a browser extension wallet designed for multi-chain DeFi users with enhanced security features.
1. Click on the "More" button, find "Add Custom Network".
2. Press "Add Custom Network", or the edit button if editing an existing connection.
3. Fill in the network details:
- Network Name: HyperEVM Testnet
- RPC URL: [https://rpcs.chain.link/hyperevm/testnet](https://rpcs.chain.link/hyperevm/testnet)
- Chain ID: 998
- Currency Symbol: HYPE
- Block Explorer URL (Optional): [https://testnet.purrsec.com/](https://testnet.purrsec.com/)
4. Click "Confirm."
## Supported JSON-RPC Methods
| Method | Description |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| net_version | Returns the current network ID (as a decimal string). |
| web3_clientVersion | Returns the client software version string. |
| eth_blockNumber | Returns the number of the most recent block (hex quantity). |
| eth_call | Executes a read-only call (no state change) against a contract. Only the latest block is supported. |
| eth_chainId | Returns the chain ID used for EIP-155 transaction signing (hex quantity). |
| eth_estimateGas | Estimates the gas required to execute a transaction/call. Only the latest block is supported. |
| eth_feeHistory | Returns historical base fees, priority fee rewards, and gas usage ratios for a range of recent blocks. |
| eth_gasPrice | Returns the node’s suggested gas price. Returns the base fee for the next small block. |
| eth_getBalance | Returns the account balance for an address. Only the latest block is supported. |
| eth_getBlockByHash | Returns block details by block hash; can include full transactions when requested. |
| eth_getBlockByNumber | Returns block details by block number; can include full transactions when requested. |
| eth_getBlockReceipts | Returns all transaction receipts for the specified block. |
| eth_getBlockTransactionCountByHash | Returns the number of transactions in a block, by block hash. |
| eth_getBlockTransactionCountByNumber | Returns the number of transactions in a block, by block number. |
| eth_getCode | Returns the EVM bytecode at an address. Only the latest block is supported. |
| eth_getLogs | Returns log entries that match a filter object. Up to 4 topics and up to 50 blocks in query range. |
| eth_getStorageAt | Returns the value from a storage slot at an address. Only the latest block is supported. |
| eth_getTransactionByBlockHashAndIndex | Returns a transaction from a block, by block hash and transaction index. |
| eth_getTransactionByBlockNumberAndIndex | Returns a transaction from a block, by block number and transaction index. |
| eth_getTransactionByHash | Returns a transaction by its hash. |
| eth_getTransactionCount | Returns the number of transactions sent from an address (the nonce). Only the latest block is supported. |
| eth_getTransactionReceipt | Returns the receipt of a transaction by hash (includes status, gas used, logs). |
| eth_maxPriorityFeePerGas | Returns the current max priority fee per gas (tip). Always returns zero currently. |
| eth_syncing | Reports node sync status. Always returns false. |
The following HyperEVM-specific endpoints are available:
| Method | Description |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| eth_bigBlockGasPrice | Returns the base fee for the next big block. |
| eth_usingBigBlocks | Returns whether the address is using big blocks. |
| eth_getSystemTxsByBlockHash | Similar to the "getTransaction" analogs but returns the system transactions that originate from HyperCore. |
| eth_getSystemTxsByBlockNumber | Similar to the "getTransaction" analogs but returns the system transactions that originate from HyperCore. |
## Rate Limits
The RPC endpoint allows 1,000 requests per IP address within a 5-minute moving window.
---
# CCIP API Reference
Source: https://docs.chain.link/ccip/api-reference
Chainlink Cross-Chain Interoperability Protocol (CCIP) provides secure cross-chain messaging and token transfers between blockchain networks.
- **[EVM-based Blockchains](/ccip/api-reference/evm)**: Complete API reference for CCIP on Ethereum Virtual Machine (EVM) compatible blockchains.
- **[Solana](/ccip/api-reference/svm)**: Complete API reference for CCIP on Solana.
- **[Aptos Blockchain](/ccip/api-reference/aptos)**: Complete API reference for CCIP on the Aptos blockchain.
---
# Chainlink CCIP API Reference Documentation (EVM)
Source: https://docs.chain.link/ccip/api-reference/evm
## Available Versions
### Latest Release
- **[CCIP v1.6.2](/ccip/api-reference/evm/v1.6.2)** (Current Version)
- Added support for USDC Token Pool on Solana
- Added support for FactoryBurnMintERC20 on Hyperliquid
- **[CCIP v1.6.1](/ccip/api-reference/evm/v1.6.1)**
- Pool Library: `ReleaseOrMintInV1.amount` renamed to `sourceDenominatedAmount`
- TokenPool events: `Locked`/`Burned` and `Released`/`Minted` replaced by unified `LockedOrBurned` and `ReleasedOrMinted`
- Rate limiting: added `OutboundRateLimitConsumed` and `InboundRateLimitConsumed` events for monitoring consumption
- LockReleaseTokenPool constructor: removed `acceptLiquidity` parameter; `canAcceptLiquidity` capability removed
- LockReleaseTokenPool: removed `lockOrBurn` and `releaseOrMint` functions
- LockReleaseTokenPool: added `RebalancerSet` event
- Extensibility: standardized pool extension hooks via internal `_lockOrBurn` and `_releaseOrMint` in `TokenPool`
- **[CCIP v1.6.0](/ccip/api-reference/evm/v1.6.0)**
- Added support for SVM (Solana Virtual Machine) chains
- **[CCIP v1.5.1](/ccip/api-reference/evm/v1.5.1)**
- Added support for tokens with different decimals across chains
- Enhanced token pool upgrades to support multiple active pools, ensuring in-flight messages remain deliverable during upgrades
- Added decimal validation and overflow protection
- Upgraded token pool access control from OwnerIsCreator to Ownable2StepMsgSender for better security
- Introduced BurnMintERC20 contract for easy token deployment and cross-chain expansion
- Configurable decimals and max supply
- Built-in CCIP admin support
- Role-based access control for minting and burning
### Previous Versions
- **[CCIP v1.5.0](/ccip/api-reference/evm/v1.5.0)** (Legacy Version)
- Base implementation
- Cross-chain:
- Send arbitrary data
- Transfer tokens
- Programmable Token Transfers
- [Cross-Chain Token (CCT) standard](/ccip/concepts/cross-chain-token/overview)
- ⚠️ We recommend upgrading to v1.5.1 for latest features
## Documentation Structure
Each version includes detailed documentation for:
- Core Contracts (Router, Receiver)
- Token Pool Implementations
- Registry Components
- Interface Definitions
- Library Definitions
- Error Handling
---
# CCIP v1.6.2 API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2
Last Updated: 2025-10-06
## API References
### Core Components
- [CCIPReceiver](/ccip/api-reference/evm/v1.6.2/ccip-receiver) - Base contract for receiving CCIP messages
- [Client](/ccip/api-reference/evm/v1.6.2/client) - Library providing structs and types for building CCIP messages
- [IRouterClient](/ccip/api-reference/evm/v1.6.2/i-router-client) - Interface for sending messages through CCIP
- [Pool](/ccip/api-reference/evm/v1.6.2/pool) - Library providing token pool functions for cross-chain operations
- [RateLimiter](/ccip/api-reference/evm/v1.6.2/rate-limiter) - Contract for managing rate limits on token transfers
- [TypeAndVersion](/ccip/api-reference/evm/v1.6.2/i-type-and-version) - Interface for contract versioning
### Token Pools
- [BurnFromMintTokenPool](/ccip/api-reference/evm/v1.6.2/burn-from-mint-token-pool) - Implementation using `burnFrom(address, amount)` for token burning
- [BurnMintERC20](/ccip/api-reference/evm/v1.6.2/burn-mint-erc20) - Implementation for burning and minting ERC20 tokens
- [BurnMintTokenPool](/ccip/api-reference/evm/v1.6.2/burn-mint-token-pool) - Implementation using `burn(amount)` for token burning
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.6.2/burn-mint-token-pool-abstract) - Abstract contract for burn/mint token handling
- [LockReleaseTokenPool](/ccip/api-reference/evm/v1.6.2/lock-release-token-pool) - Implementation for locking and releasing tokens on their native chain
- [TokenPool](/ccip/api-reference/evm/v1.6.2/token-pool) - Base abstract class defining common functionality for all token pools
### Access Control
- [Ownable2Step](/ccip/api-reference/evm/v1.6.2/ownable-2-step) - Base contract implementing secure two-step ownership transfer
- [Ownable2StepMsgSender](/ccip/api-reference/evm/v1.6.2/ownable-2-step-msg-sender) - Extension of Ownable2Step that sets msg.sender as initial owner
### Registry Components
- [RegistryModuleOwnerCustom](/ccip/api-reference/evm/v1.6.2/registry-module-owner-custom) - Registry module for token admin registration
- [TokenAdminRegistry](/ccip/api-reference/evm/v1.6.2/token-admin-registry) - Contract for storing token pool configurations
### Error Handling
- [Errors](/ccip/api-reference/evm/v1.6.2/errors) - Comprehensive list of CCIP error codes and their descriptions
---
# CCIP v1.6.2 Client Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/client
Last Updated: 2025-09-08
## Client
A library that provides core data structures and utilities for building and handling cross-chain messages in CCIP.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/libraries/Client.sol)
## Structs
### Any2EVMMessage
Structure representing a message received from any chain to an EVM chain.
```solidity
struct Any2EVMMessage {
bytes32 messageId;
uint64 sourceChainSelector;
bytes sender;
bytes data;
EVMTokenAmount[] destTokenAmounts;
}
```
**Properties**
| Name | Type | Description |
| --------------------- | ------------------------------------- | ------------------------------------------------- |
| `messageId` | `bytes32` | Message ID corresponding to ccipSend on source |
| `sourceChainSelector` | `uint64` | Identifier of the source chain |
| `sender` | `bytes` | Sender address (use abi.decode if from EVM chain) |
| `data` | `bytes` | Custom payload from the original message |
| `destTokenAmounts` | [`EVMTokenAmount[]`](#evmtokenamount) | Token amounts in destination chain representation |
### EVM2AnyMessage
Structure for sending a message from an EVM chain to any supported chain.
```solidity
struct EVM2AnyMessage {
bytes receiver;
bytes data;
EVMTokenAmount[] tokenAmounts;
address feeToken;
bytes extraArgs;
}
```
**Properties**
| Name | Type | Description |
| -------------- | ------------------------------------- | --------------------------------------------------- |
| `receiver` | `bytes` | Encoded receiver address for destination EVM chains |
| `data` | `bytes` | Custom payload to send |
| `tokenAmounts` | [`EVMTokenAmount[]`](#evmtokenamount) | Tokens and amounts to transfer |
| `feeToken` | `address` | Token used for fees (address(0) for native tokens) |
| `extraArgs` | `bytes` | Additional arguments encoded with _argsToBytes |
### EVMExtraArgsV1
Structure for V1 extra arguments in cross-chain messages.
```solidity
struct EVMExtraArgsV1 {
uint256 gasLimit;
}
```
**Properties**
| Name | Type | Description |
| ---------- | --------- | -------------------------------------------- |
| `gasLimit` | `uint256` | Gas limit for execution on destination chain |
### GenericExtraArgsV2
Structure for V2 extra arguments in cross-chain messages.
```solidity
struct GenericExtraArgsV2 {
uint256 gasLimit;
bool allowOutOfOrderExecution;
}
```
**Properties**
| Name | Type | Description |
| -------------------------- | --------- | --------------------------------------------- |
| `gasLimit` | `uint256` | Gas limit for execution on destination chain |
| `allowOutOfOrderExecution` | `bool` | Whether messages can be executed in any order |
### SVMExtraArgsV1
Structure for V1 extra arguments specific to Solana VM-based chains.
```solidity
struct SVMExtraArgsV1 {
uint32 computeUnits;
uint64 accountIsWritableBitmap;
bool allowOutOfOrderExecution;
bytes32 tokenReceiver;
bytes32[] accounts;
}
```
**Properties**
| Name | Type | Description |
| -------------------------- | ----------- | ------------------------------------------------------ |
| `computeUnits` | `uint32` | Compute units for execution on Solana |
| `accountIsWritableBitmap` | `uint64` | Bitmap indicating which accounts are writable |
| `allowOutOfOrderExecution` | `bool` | Whether messages can be executed in any order |
| `tokenReceiver` | `bytes32` | Address of the token receiver |
| `accounts` | `bytes32[]` | Additional accounts needed for CCIP receiver execution |
### EVMTokenAmount
Structure representing token amounts in CCIP messages.
```solidity
struct EVMTokenAmount {
address token;
uint256 amount;
}
```
**Properties**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `token` | `address` | Token address on the local chain |
| `amount` | `uint256` | Amount of tokens to transfer |
## State Variables
### EVM_EXTRA_ARGS_V1_TAG
```solidity
bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
```
### GENERIC_EXTRA_ARGS_V2_TAG
```solidity
bytes4 public constant GENERIC_EXTRA_ARGS_V2_TAG = 0x181dcf10;
```
### SVM_EXTRA_ARGS_V1_TAG
```solidity
bytes4 public constant SVM_EXTRA_ARGS_V1_TAG = 0x1f3b3aba;
```
### SVM_EXTRA_ARGS_MAX_ACCOUNTS
```solidity
uint256 public constant SVM_EXTRA_ARGS_MAX_ACCOUNTS = 64;
```
### SVM_TOKEN_TRANSFER_DATA_OVERHEAD
```solidity
uint256 public constant SVM_TOKEN_TRANSFER_DATA_OVERHEAD = (4 + 32) // source_pool
+ 32 // token_address
+ 4 // gas_amount
+ 4 // extra_data overhead
+ 32 // amount
+ 32 // size of the token lookup table account
+ 32 // token-related accounts in the lookup table, over-estimated to 32, typically between 11 - 13
+ 32 // token account belonging to the token receiver, e.g ATA, not included in the token lookup table
+ 32 // per-chain token pool config, not included in the token lookup table
+ 32 // per-chain token billing config, not always included in the token lookup table
+ 32; // OffRamp pool signer PDA, not included in the token lookup table
```
### SVM_MESSAGING_ACCOUNTS_OVERHEAD
```solidity
uint256 public constant SVM_MESSAGING_ACCOUNTS_OVERHEAD = 2;
```
### SVM_ACCOUNT_BYTE_SIZE
```solidity
uint256 public constant SVM_ACCOUNT_BYTE_SIZE = 32;
```
## Functions
### _argsToBytes (V1)
Encodes EVMExtraArgsV1 into bytes for message transmission.
```solidity
function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ----------------------------------- | -------------------------------- |
| `extraArgs` | [`EVMExtraArgsV1`](#evmextraargsv1) | The V1 extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
### _argsToBytes (V2)
Encodes GenericExtraArgsV2 into bytes for message transmission.
```solidity
function _argsToBytes(GenericExtraArgsV2 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ------------------------------------------- | ---------------------------------------- |
| `extraArgs` | [`GenericExtraArgsV2`](#genericextraargsv2) | The V2 generic extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
### _svmArgsToBytes
Encodes SVMExtraArgsV1 into bytes for message transmission.
```solidity
function _svmArgsToBytes(SVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ----------------------------------- | --------------------------------- |
| `extraArgs` | [`SVMExtraArgsV1`](#svmextraargsv1) | The SVM extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
---
# CCIP v1.6.2 ITypeAndVersion Interface API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/i-type-and-version
## ITypeAndVersion
An interface that provides type and version information for contracts.
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/interfaces/ITypeAndVersion.sol)
## Functions
### typeAndVersion
Returns the type and version of the contract.
```solidity
function typeAndVersion() external pure returns (string memory);
```
**Returns**
| Type | Description |
| -------- | ------------------------------------- |
| `string` | The type and version of the contract. |
---
# CCIP v1.6.2 IRouterClient API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/i-router-client
Last Updated: 2025-09-08
## IRouterClient
The IRouterClient interface provides the core functionality for sending cross-chain messages through CCIP (Chainlink Cross-Chain Interoperability Protocol).
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/interfaces/IRouterClient.sol)
## Errors
### InsufficientFeeTokenAmount
Thrown when the provided fee token amount is insufficient for the message delivery.
```solidity
error InsufficientFeeTokenAmount();
```
### InvalidMsgValue
Thrown when the provided msg.value is invalid for the operation.
```solidity
error InvalidMsgValue();
```
### UnsupportedDestinationChain
Thrown when attempting to send a message to an unsupported destination chain.
```solidity
error UnsupportedDestinationChain(uint64 destChainSelector);
```
## Functions
### ccipSend
Sends a message to the destination chain through CCIP.
```solidity
function ccipSend(
uint64 destinationChainSelector,
Client.EVM2AnyMessage calldata message
) external payable returns (bytes32);
```
**Parameters**
| Name | Type | Description |
| -------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `destinationChainSelector` | `uint64` | The destination chain ID |
| `message` | [`Client.EVM2AnyMessage`](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) | The cross-chain CCIP message including data and/or tokens |
**Returns**
| Name | Type | Description |
| ----------- | --------- | -------------- |
| `messageId` | `bytes32` | The message ID |
### getFee
Gets the fee required for sending a CCIP message to the destination chain.
```solidity
function getFee(
uint64 destinationChainSelector,
Client.EVM2AnyMessage memory message
) external view returns (uint256 fee);
```
**Parameters**
| Name | Type | Description |
| -------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `destinationChainSelector` | `uint64` | The destination chainSelector |
| `message` | [`Client.EVM2AnyMessage`](/ccip/api-reference/evm/v1.6.2/client#evm2anymessage) | The cross-chain CCIP message including data and/or tokens |
**Returns**
| Name | Type | Description |
| ----- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `fee` | `uint256` | Returns execution fee for the message delivery to destination chain, denominated in the feeToken specified in the message. |
### isChainSupported
Checks if the given chain ID is supported for sending/receiving.
```solidity
function isChainSupported(uint64 destChainSelector) external view returns (bool supported);
```
**Parameters**
| Name | Type | Description |
| ------------------- | -------- | ------------------- |
| `destChainSelector` | `uint64` | The chain to check. |
**Returns**
| Name | Type | Description |
| ----------- | ------ | ----------------------------------------- |
| `supported` | `bool` | is true if it is supported, false if not. |
---
# CCIP v1.6.2 CCIPReceiver API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/ccip-receiver
Last Updated: 2025-09-08
## CCIPReceiver
An abstract base contract that provides core functionality for CCIP-enabled applications to receive cross-chain messages.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/applications/CCIPReceiver.sol)
## Errors
### InvalidRouter
```solidity
error InvalidRouter(address router);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------- |
| `router` | `address` | The invalid router address |
## State Variables
### i_ccipRouter
```solidity
address internal immutable i_ccipRouter;
```
## Modifiers
### onlyRouter
```solidity
modifier onlyRouter();
```
## Functions
### ccipReceive
Processes incoming CCIP messages from the router.
```solidity
function ccipReceive(Client.Any2EVMMessage calldata message) external virtual override onlyRouter;
```
**Parameters**
| Name | Type | Description |
| --------- | ------------------------------------------------------------------------------- | ---------------- |
| `message` | [`Client.Any2EVMMessage`](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage) | The CCIP message |
### constructor
```solidity
constructor(address router);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `router` | `address` | The CCIP router contract address |
### getRouter
Returns the address of the current CCIP router.
```solidity
function getRouter() public view virtual returns (address);
```
**Returns**
| Type | Description |
| --------- | ------------------------------- |
| `address` | The current CCIP router address |
### supportsInterface
Determines whether the contract implements specific interfaces.
```solidity
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | ---------------------------------- |
| `bool` | True if the interface is supported |
### _ccipReceive
Internal function to be implemented by derived contracts for custom message handling.
```solidity
function _ccipReceive(Client.Any2EVMMessage memory message) internal virtual;
```
**Parameters**
| Name | Type | Description |
| --------- | ------------------------------------------------------------------------------- | --------------------------- |
| `message` | [`Client.Any2EVMMessage`](/ccip/api-reference/evm/v1.6.2/client#any2evmmessage) | The message to be processed |
---
# CCIP v1.6.2 Pool Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/pool
## Pool
A library that provides core data structures and constants for token pool operations in cross-chain transfers.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/libraries/Pool.sol)
## Structs
### LockOrBurnInV1
Input parameters for locking or burning tokens in cross-chain transfers.
```solidity
struct LockOrBurnInV1 {
bytes receiver;
uint64 remoteChainSelector;
address originalSender;
uint256 amount;
address localToken;
}
```
### LockOrBurnOutV1
Output data from a lock or burn operation.
```solidity
struct LockOrBurnOutV1 {
bytes destTokenAddress;
bytes destPoolData;
}
```
### ReleaseOrMintInV1
Input parameters for releasing or minting tokens in cross-chain transfers.
```solidity
struct ReleaseOrMintInV1 {
bytes originalSender;
uint64 remoteChainSelector;
address receiver;
uint256 sourceDenominatedAmount;
address localToken;
bytes sourcePoolAddress;
bytes sourcePoolData;
bytes offchainTokenData;
}
```
### ReleaseOrMintOutV1
Output data from a release or mint operation.
```solidity
struct ReleaseOrMintOutV1 {
uint256 destinationAmount;
}
```
## State Variables
### CCIP_POOL_V1
```solidity
bytes4 public constant CCIP_POOL_V1 = 0xaff2afbf;
```
### CCIP_POOL_V1_RET_BYTES
```solidity
uint16 public constant CCIP_POOL_V1_RET_BYTES = 32;
```
### CCIP_LOCK_OR_BURN_V1_RET_BYTES
```solidity
uint32 public constant CCIP_LOCK_OR_BURN_V1_RET_BYTES = 32;
```
---
# CCIP v1.6.2 TokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/token-pool
## TokenPool
An abstract contract that provides base functionality for managing cross-chain token operations in CCIP. It handles token decimals across different chains, rate limiting, and access control.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/pools/TokenPool.sol)
**Inherits:**
- [Ownable2StepMsgSender](/ccip/api-reference/evm/v1.6.2/ownable-2-step-msg-sender)
## Events
### AllowListAdd
Emitted when an address is added to the allowlist via [`applyAllowListUpdates`](#applyallowlistupdates).
```solidity
event AllowListAdd(address sender);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ------------------------------------------- |
| `sender` | `address` | No | The address that was added to the allowlist |
### AllowListRemove
Emitted when an address is removed from the allowlist via [`applyAllowListUpdates`](#applyallowlistupdates).
```solidity
event AllowListRemove(address sender);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------------------- |
| `sender` | `address` | No | The address that was removed from the allowlist |
### ChainAdded
Emitted when a new chain is configured in the pool via [`applyChainUpdates`](#applychainupdates).
```solidity
event ChainAdded(
uint64 remoteChainSelector,
bytes remoteToken,
RateLimiter.Config outboundRateLimiterConfig,
RateLimiter.Config inboundRateLimiterConfig
);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------------- | -------------------------------------------------------------------------- | ------- | ----------------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the newly added chain |
| `remoteToken` | `bytes` | No | The token address on the remote chain |
| `outboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.2/rate-limiter#config) | No | Rate limit configuration for outbound transfers |
| `inboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.2/rate-limiter#config) | No | Rate limit configuration for inbound transfers |
### ChainConfigured
Emitted when a chain's configuration is updated via [`setChainRateLimiterConfig`](#setchainratelimiterconfig) or [`setChainRateLimiterConfigs`](#setchainratelimiterconfigs).
```solidity
event ChainConfigured(
uint64 remoteChainSelector,
RateLimiter.Config outboundRateLimiterConfig,
RateLimiter.Config inboundRateLimiterConfig
);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------------- | -------------------------------------------------------------------------- | ------- | ------------------------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the chain being configured |
| `outboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.2/rate-limiter#config) | No | Updated rate limit configuration for outbound transfers |
| `inboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.2/rate-limiter#config) | No | Updated rate limit configuration for inbound transfers |
### ChainRemoved
Emitted when a chain is removed from the pool's configuration via [`applyChainUpdates`](#applychainupdates).
```solidity
event ChainRemoved(uint64 remoteChainSelector);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | ----------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the chain being removed |
### LockedOrBurned
Emitted when tokens are locked or burned by the pool as part of an outbound cross-chain transfer.
```solidity
event LockedOrBurned(address indexed sender, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ---------------------------------------------- |
| `sender` | `address` | Yes | The address initiating the lock/burn operation |
| `amount` | `uint256` | No | The amount of tokens locked or burned |
### RemotePoolAdded
Emitted when a new remote pool is added via [`addRemotePool`](#addremotepool) or [`applyChainUpdates`](#applychainupdates).
```solidity
event RemotePoolAdded(uint64 indexed remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | -------------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The identifier of the chain for the new pool |
| `remotePoolAddress` | `bytes` | No | The address of the newly added pool |
### ReleasedOrMinted
Emitted when tokens are released or minted by the pool as part of an inbound cross-chain transfer.
```solidity
event ReleasedOrMinted(address indexed sender, address indexed recipient, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | --------------------------------------------------- |
| `sender` | `address` | Yes | The address initiating the release/mint operation |
| `recipient` | `address` | Yes | The address receiving the released or minted tokens |
| `amount` | `uint256` | No | The amount of tokens released or minted |
### RemotePoolRemoved
Emitted when a remote pool is removed via [`removeRemotePool`](#removeremotepool).
```solidity
event RemotePoolRemoved(uint64 indexed remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | ---------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The identifier of the chain for the pool |
| `remotePoolAddress` | `bytes` | No | The address of the removed pool |
### RouterUpdated
Emitted when the router address is updated via [`setRouter`](#setrouter).
```solidity
event RouterUpdated(address oldRouter, address newRouter);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | ------------------------------------ |
| `oldRouter` | `address` | No | The previous router contract address |
| `newRouter` | `address` | No | The new router contract address |
### RateLimitAdminSet
Emitted when the rate limit administrator is changed via [`setRateLimitAdmin`](#setratelimitadmin).
```solidity
event RateLimitAdminSet(address rateLimitAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| ---------------- | --------- | ------- | ---------------------------------------- |
| `rateLimitAdmin` | `address` | No | The new rate limit administrator address |
### OutboundRateLimitConsumed
Emitted when the outbound rate limit is consumed via `lockOrBurn`.
```solidity
event OutboundRateLimitConsumed(uint64 indexed remoteChainSelector, address token, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | --------- | ------- | --------------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The destination chain selector |
| `token` | `address` | No | The address of the token being sent |
| `amount` | `uint64` | No | The amount of tokens consuming the rate limit |
### InboundRateLimitConsumed
Emitted when the inbound rate limit is consumed via `releaseOrMint`.
```solidity
event InboundRateLimitConsumed(uint64 indexed remoteChainSelector, address token, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | --------- | ------- | --------------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The source chain selector |
| `token` | `address` | No | The address of the token being received |
| `amount` | `uint256` | No | The amount of tokens consuming the rate limit |
## Errors
### AllowListNotEnabled
```solidity
error AllowListNotEnabled();
```
### CallerIsNotARampOnRouter
```solidity
error CallerIsNotARampOnRouter(address caller);
```
### ChainAlreadyExists
```solidity
error ChainAlreadyExists(uint64 chainSelector);
```
**Parameters**
| Name | Type | Description |
| --------------- | -------- | --------------------------------------------- |
| `chainSelector` | `uint64` | The selector of the chain that already exists |
### ChainNotAllowed
```solidity
error ChainNotAllowed(uint64 remoteChainSelector);
```
### CursedByRMN
```solidity
error CursedByRMN();
```
### InvalidDecimalArgs
```solidity
error InvalidDecimalArgs(uint8 expected, uint8 actual);
```
**Parameters**
| Name | Type | Description |
| ---------- | ------- | -------------------------------------- |
| `expected` | `uint8` | The expected number of decimals |
| `actual` | `uint8` | The actual number of decimals provided |
### InvalidRemoteChainDecimals
```solidity
error InvalidRemoteChainDecimals(bytes sourcePoolData);
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------- | -------------------------------------- |
| `sourcePoolData` | `bytes` | The invalid decimal configuration data |
### InvalidRemotePoolForChain
```solidity
error InvalidRemotePoolForChain(uint64 remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | -------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector being queried |
| `remotePoolAddress` | `bytes` | The invalid pool address |
### InvalidSourcePoolAddress
```solidity
error InvalidSourcePoolAddress(bytes sourcePoolAddress);
```
### InvalidToken
```solidity
error InvalidToken(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------------------------- |
| `token` | `address` | The address of the invalid token |
### MismatchedArrayLengths
```solidity
error MismatchedArrayLengths();
```
### NonExistentChain
```solidity
error NonExistentChain(uint64 remoteChainSelector);
```
### OverflowDetected
```solidity
error OverflowDetected(uint8 remoteDecimals, uint8 localDecimals, uint256 remoteAmount);
```
**Parameters**
| Name | Type | Description |
| ---------------- | --------- | ----------------------------------- |
| `remoteDecimals` | `uint8` | The decimals on the remote chain |
| `localDecimals` | `uint8` | The decimals on the local chain |
| `remoteAmount` | `uint256` | The amount that caused the overflow |
### PoolAlreadyAdded
```solidity
error PoolAlreadyAdded(uint64 remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ---------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector where the pool exists |
| `remotePoolAddress` | `bytes` | The address of the already existing pool |
### SenderNotAllowed
```solidity
error SenderNotAllowed(address sender);
```
### Unauthorized
```solidity
error Unauthorized(address caller);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------------- |
| `caller` | `address` | The address that attempted the action |
### ZeroAddressNotAllowed
```solidity
error ZeroAddressNotAllowed();
```
## Structs
### ChainUpdate
Configuration data for adding or updating a chain.
```solidity
struct ChainUpdate {
uint64 remoteChainSelector;
bytes[] remotePoolAddresses;
bytes remoteTokenAddress;
RateLimiter.Config outboundRateLimiterConfig;
RateLimiter.Config inboundRateLimiterConfig;
}
```
### RemoteChainConfig
Internal configuration for a remote chain.
```solidity
struct RemoteChainConfig {
RateLimiter.TokenBucket outboundRateLimiterConfig;
RateLimiter.TokenBucket inboundRateLimiterConfig;
bytes remoteTokenAddress;
EnumerableSet.Bytes32Set remotePools;
}
```
## State Variables
### i_token
The token managed by this pool. Currently supports one token per pool.
```solidity
IERC20 internal immutable i_token;
```
### i_tokenDecimals
The number of decimals for the managed token.
```solidity
uint8 internal immutable i_tokenDecimals;
```
### i_rmnProxy
The Risk Management Network (RMN) proxy address.
```solidity
address internal immutable i_rmnProxy;
```
### i_allowlistEnabled
Flag indicating if the pool uses access control.
```solidity
bool internal immutable i_allowlistEnabled;
```
### s_allowlist
Set of addresses authorized to initiate cross-chain operations.
```solidity
EnumerableSet.AddressSet internal s_allowlist;
```
### s_router
The CCIP Router contract address.
```solidity
IRouter internal s_router;
```
### s_remoteChainSelectors
Set of authorized chain selectors for cross-chain operations.
```solidity
EnumerableSet.UintSet internal s_remoteChainSelectors;
```
### s_remoteChainConfigs
Configuration for each remote chain, including rate limits and token details.
```solidity
mapping(uint64 remoteChainSelector => RemoteChainConfig) internal s_remoteChainConfigs;
```
### s_remotePoolAddresses
Maps hashed pool addresses to their original form for verification.
```solidity
mapping(bytes32 poolAddressHash => bytes poolAddress) internal s_remotePoolAddresses;
```
### s_rateLimitAdmin
The address authorized to manage rate limits.
```solidity
address internal s_rateLimitAdmin;
```
## Functions
### _applyAllowListUpdates
Internal version of applyAllowListUpdates to allow for reuse in the constructor.
```solidity
function _applyAllowListUpdates(address[] memory removes, address[] memory adds) internal;
```
**Parameters**
| Name | Type | Description |
| --------- | ----------- | ----------------------------------------------- |
| `removes` | `address[]` | Array of addresses to remove from the allowlist |
| `adds` | `address[]` | Array of addresses to add to the allowlist |
### _calculateLocalAmount
Calculates the local amount based on the remote amount and decimals.
*This function protects against overflows. If there is a transaction that hits the overflow check, it is
probably incorrect as that means the amount cannot be represented on this chain. If the local decimals have been
wrongly configured, the token developer could redeploy the pool with the correct decimals and manually re-execute the
CCIP tx to fix the issue.*
```solidity
function _calculateLocalAmount(uint256 remoteAmount, uint8 remoteDecimals) internal view virtual returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---------------- | --------- | ---------------------------------------------- |
| `remoteAmount` | `uint256` | The amount on the remote chain. |
| `remoteDecimals` | `uint8` | The decimals of the token on the remote chain. |
**Returns**
| Name | Type | Description |
| -------- | --------- | ----------------- |
| `` | `uint256` | The local amount. |
### _checkAllowList
Internal function to verify if a sender is authorized when allowlist is enabled.
```solidity
function _checkAllowList(address sender) internal view;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `sender` | `address` | The address to check for permission |
### _consumeInboundRateLimit
Internal function to consume rate limiting capacity for incoming transfers.
```solidity
function _consumeInboundRateLimit(uint64 remoteChainSelector, uint256 amount) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | --------- | --------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector for the source chain |
| `amount` | `uint256` | The amount of tokens being transferred |
### _consumeOutboundRateLimit
Internal function to consume rate limiting capacity for outgoing transfers.
```solidity
function _consumeOutboundRateLimit(uint64 remoteChainSelector, uint256 amount) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | --------- | -------------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector for the destination chain |
| `amount` | `uint256` | The amount of tokens being transferred |
### _encodeLocalDecimals
Internal function to encode the local token's decimals for cross-chain communication.
```solidity
function _encodeLocalDecimals() internal view virtual returns (bytes memory);
```
**Returns**
| Type | Description |
| ------- | --------------------------------------------- |
| `bytes` | ABI-encoded decimal places of the local token |
### _lockOrBurn
Abstract internal function designed to be overridden with the specific token lock or burn logic.
```solidity
function _lockOrBurn(uint256 amount) internal virtual;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------------ |
| `amount` | `uint256` | The amount of tokens to lock or burn |
### _onlyOffRamp
Checks whether remote chain selector is configured on this contract, and if the msg.sender
is a permissioned offRamp for the given chain on the Router.
```solidity
function _onlyOffRamp(uint64 remoteChainSelector) internal view;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to validate authorization for |
### _onlyOnRamp
Checks whether remote chain selector is configured on this contract, and if the msg.sender
is a permissioned onRamp for the given chain on the Router.
```solidity
function _onlyOnRamp(uint64 remoteChainSelector) internal view;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to validate authorization for |
### _parseRemoteDecimals
Internal function to decode the decimal configuration received from a remote chain.
```solidity
function _parseRemoteDecimals(bytes memory sourcePoolData) internal view virtual returns (uint8);
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------- | -------------------------------------- |
| `sourcePoolData` | `bytes` | The encoded decimal configuration data |
**Returns**
| Type | Description |
| ------- | ----------------------------------------------- |
| `uint8` | The number of decimals used on the remote chain |
### _releaseOrMint
Abstract internal function designed to be overridden with the specific token release or mint logic.
```solidity
function _releaseOrMint(address receiver, uint256 amount) internal virtual;
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | --------------------------------------- |
| `receiver` | `address` | The address receiving the tokens |
| `amount` | `uint256` | The amount of tokens to release or mint |
### _setRateLimitConfig
Internal function to update rate limit configuration for a chain.
```solidity
function _setRateLimitConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------------------- | ----------------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector to configure |
| `outboundConfig` | `RateLimiter.Config` | Rate limit configuration for outgoing transfers |
| `inboundConfig` | `RateLimiter.Config` | Rate limit configuration for incoming transfers |
### _setRemotePool
Internal function to add a pool address to the allowed remote token pools for a chain. Called during chain configuration and when adding individual remote pools.
```solidity
function _setRemotePool(uint64 remoteChainSelector, bytes memory remotePoolAddress) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to add the pool for |
| `remotePoolAddress` | `bytes` | The address of the remote pool (encoded to support non-EVM chains) |
### _validateLockOrBurn
Internal function to validate lock or burn operations.
```solidity
function _validateLockOrBurn(Pool.LockOrBurnInV1 calldata lockOrBurnIn) internal;
```
### _validateReleaseOrMint
Internal function to validate release or mint operations.
```solidity
function _validateReleaseOrMint(Pool.ReleaseOrMintInV1 calldata releaseOrMintIn) internal;
```
### addRemotePool
Adds a new pool address for a remote chain.
```solidity
function addRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) external onlyOwner;
```
### applyAllowListUpdates
Apply updates to the allow list.
```solidity
function applyAllowListUpdates(address[] calldata removes, address[] calldata adds) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| --------- | ----------- | ---------------------------- |
| `removes` | `address[]` | The addresses to be removed. |
| `adds` | `address[]` | The addresses to be added. |
### applyChainUpdates
Updates chain configurations in bulk.
```solidity
function applyChainUpdates(
uint64[] calldata remoteChainSelectorsToRemove,
ChainUpdate[] calldata chainsToAdd
) external virtual onlyOwner;
```
### constructor
```solidity
constructor(IERC20 token, uint8 localTokenDecimals, address[] memory allowlist, address rmnProxy, address router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ----------- | -------------------------------------------- |
| `token` | `IERC20` | The token to be managed by this pool |
| `localTokenDecimals` | `uint8` | The token's decimal places on this chain |
| `allowlist` | `address[]` | Initial set of authorized addresses (if any) |
| `rmnProxy` | `address` | The Risk Management Network proxy address |
| `router` | `address` | The CCIP Router contract address |
### getAllowList
Gets the allowed addresses.
```solidity
function getAllowList() external view returns (address[] memory);
```
**Returns**
| Name | Type | Description |
| -------- | ----------- | ---------------------- |
| `` | `address[]` | The allowed addresses. |
### getAllowListEnabled
Returns whether allowlist functionality is active.
```solidity
function getAllowListEnabled() external view returns (bool);
```
**Returns**
| Name | Type | Description |
| -------- | ------ | ------------------------------ |
| `` | `bool` | true is enabled, false if not. |
### getCurrentInboundRateLimiterState
Returns the current state of inbound rate limiting for a chain.
```solidity
function getCurrentInboundRateLimiterState(
uint64 remoteChainSelector
) external view returns (RateLimiter.TokenBucket memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to get rate limiter state for |
**Returns**
| Type | Description |
| ------------------------------------------------------------------------------------ | ----------------------------------------- |
| [`RateLimiter.TokenBucket`](/ccip/api-reference/evm/v1.6.2/rate-limiter#tokenbucket) | Current state of the inbound rate limiter |
### getCurrentOutboundRateLimiterState
Returns the current state of outbound rate limiting for a chain.
```solidity
function getCurrentOutboundRateLimiterState(
uint64 remoteChainSelector
) external view returns (RateLimiter.TokenBucket memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to get rate limiter state for |
**Returns**
| Type | Description |
| ------------------------------------------------------------------------------------ | ------------------------------------------ |
| [`RateLimiter.TokenBucket`](/ccip/api-reference/evm/v1.6.2/rate-limiter#tokenbucket) | Current state of the outbound rate limiter |
### getRateLimitAdmin
Returns the current rate limit administrator address.
```solidity
function getRateLimitAdmin() external view returns (address);
```
### getRemotePools
Returns the configured pool addresses for a remote chain.
```solidity
function getRemotePools(uint64 remoteChainSelector) public view returns (bytes[] memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
**Returns**
| Type | Description |
| --------- | ----------------------------------------------- |
| `bytes[]` | Array of encoded pool addresses on remote chain |
### getRemoteToken
Returns the token address on a remote chain.
```solidity
function getRemoteToken(uint64 remoteChainSelector) public view returns (bytes memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
**Returns**
| Type | Description |
| ------- | --------------------------------------------- |
| `bytes` | The encoded token address on the remote chain |
### getRmnProxy
Returns the Risk Management Network proxy address.
```solidity
function getRmnProxy() public view returns (address rmnProxy);
```
**Returns**
| Type | Description |
| --------- | ------------------------------ |
| `address` | The RMN proxy contract address |
### getRouter
Returns the current router address.
```solidity
function getRouter() public view returns (address router);
```
**Returns**
| Type | Description |
| --------- | -------------------------------- |
| `address` | The CCIP router contract address |
### getSupportedChains
Returns all configured chain selectors.
```solidity
function getSupportedChains() public view returns (uint64[] memory);
```
**Returns**
| Type | Description |
| ---------- | ----------------------------------- |
| `uint64[]` | Array of configured chain selectors |
### getToken
Returns the token managed by this pool.
```solidity
function getToken() public view returns (IERC20 token);
```
**Returns**
| Type | Description |
| -------- | -------------------------- |
| `IERC20` | The token contract address |
### getTokenDecimals
Returns the number of decimals for the managed token.
```solidity
function getTokenDecimals() public view virtual returns (uint8 decimals);
```
**Returns**
| Type | Description |
| ------- | ------------------------------------------ |
| `uint8` | The number of decimal places for the token |
### isRemotePool
Verifies if a pool address is configured for a remote chain.
```solidity
function isRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) public view returns (bool);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
| `remotePoolAddress` | `bytes` | The pool address to verify |
**Returns**
| Type | Description |
| ------ | -------------------------------------------- |
| `bool` | True if the pool is configured for the chain |
### isSupportedChain
Checks if a chain is configured in the pool.
```solidity
function isSupportedChain(uint64 remoteChainSelector) public view returns (bool);
```
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the chain is configured in the pool |
### isSupportedToken
Checks if a given token is supported by this pool.
```solidity
function isSupportedToken(address token) public view virtual returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------------------- |
| `token` | `address` | The token address to check |
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the token is supported by this pool |
### lockOrBurn
Locks tokens in the pool for cross-chain transfer.
```solidity
function lockOrBurn(
Pool.LockOrBurnInV1 calldata lockOrBurnIn
) external virtual override returns (Pool.LockOrBurnOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| -------------- | --------------------------------------------------------------------------- | --------------------------------------- |
| `lockOrBurnIn` | [`Pool.LockOrBurnInV1`](/ccip/api-reference/evm/v1.6.2/pool#lockorburninv1) | Input parameters for the lock operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------- | ------------------------------------------------ |
| [`Pool.LockOrBurnOutV1`](/ccip/api-reference/evm/v1.6.2/pool#lockorburnoutv1) | Contains destination token address and pool data |
### releaseOrMint
Releases tokens from the pool to a recipient.
```solidity
function releaseOrMint(
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn
) external virtual override returns (Pool.ReleaseOrMintOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| ----------------- | --------------------------------------------------------------------------------- | ------------------------------------------ |
| `releaseOrMintIn` | [`Pool.ReleaseOrMintInV1`](/ccip/api-reference/evm/v1.6.2/pool#releaseormintinv1) | Input parameters for the release operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------------- | -------------------------------------------------- |
| [`Pool.ReleaseOrMintOutV1`](/ccip/api-reference/evm/v1.6.2/pool#releaseormintoutv1) | Contains the final amount released in local tokens |
### removeRemotePool
Removes a pool address from a remote chain's configuration.
```solidity
function removeRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to remove the pool from |
| `remotePoolAddress` | `bytes` | The address of the pool to remove |
### setChainRateLimiterConfig
Sets the chain rate limiter config.
```solidity
function setChainRateLimiterConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) external;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain selector for which the rate limits apply. |
| `outboundConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.2/rate-limiter#config) | The new outbound rate limiter config, meaning the onRamp rate limits for the given chain. |
| `inboundConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.2/rate-limiter#config) | The new inbound rate limiter config, meaning the offRamp rate limits for the given chain. |
### setChainRateLimiterConfigs
Updates rate limit configurations for multiple chains.
```solidity
function setChainRateLimiterConfigs(
uint64[] calldata remoteChainSelectors,
RateLimiter.Config[] calldata outboundConfigs,
RateLimiter.Config[] calldata inboundConfigs
) external;
```
**Parameters**
| Name | Type | Description |
| ---------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `remoteChainSelectors` | `uint64[]` | The chain selectors to configure |
| `outboundConfigs` | [`RateLimiter.Config[]`](/ccip/api-reference/evm/v1.6.2/rate-limiter#config) | The new outbound rate limiter configs, meaning the onRamp rate limits for the given chains |
| `inboundConfigs` | [`RateLimiter.Config[]`](/ccip/api-reference/evm/v1.6.2/rate-limiter#config) | The new inbound rate limiter configs, meaning the offRamp rate limits for the given chains |
### setRateLimitAdmin
Sets the address authorized to manage rate limits.
```solidity
function setRateLimitAdmin(address rateLimitAdmin) external onlyOwner;
```
### setRouter
Updates the router contract address.
```solidity
function setRouter(address newRouter) public onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ----------- | --------- | ------------------------------- |
| `newRouter` | `address` | The new router contract address |
### supportsInterface
Implements ERC165 interface detection.
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | --------------------------------------------- |
| `bool` | True if the contract implements the interface |
## Rate Limiting
---
# CCIP v1.6.2 BurnMintTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/burn-mint-token-pool
## BurnMintTokenPool
A specialized token pool implementation that handles the minting and burning of third-party tokens using the standard `burn(amount)` function.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/pools/BurnMintTokenPool.sol)
**Inherits:**
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.6.2/burn-mint-token-pool-abstract)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.2/i-type-and-version)
## State Variables
### typeAndVersion
```solidity
string public constant override typeAndVersion = "BurnMintTokenPool 1.6.1";
```
**Returns**
| Type | Description |
| -------- | ------------------------------------------------- |
| `string` | The contract identifier "BurnMintTokenPool 1.6.1" |
## Functions
### _lockOrBurn
Internal function that implements the token burning logic for the `BurnMintTokenPool`.
```solidity
function _lockOrBurn(uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------- |
| `amount` | `uint256` | The number of tokens to burn |
### constructor
```solidity
constructor(
IBurnMintERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ---------------- | ---------------------------------------------------- |
| `token` | `IBurnMintERC20` | The third-party token contract to manage |
| `localTokenDecimals` | `uint8` | The decimal precision for the local token |
| `allowlist` | `address[]` | Initial list of addresses authorized to use the pool |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `router` | `address` | Address of the router contract |
---
# CCIP v1.6.2 BurnMintTokenPoolAbstract Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/burn-mint-token-pool-abstract
## BurnMintTokenPoolAbstract
An abstract contract that implements core token pool functionality for burning and minting operations in cross-chain token transfers.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/pools/BurnMintTokenPoolAbstract.sol)
**Inherits:**
- [TokenPool](/ccip/api-reference/evm/v1.6.2/token-pool)
## Functions
### _releaseOrMint
Internal function that implements the token minting logic for a `BurnMintTokenPool`.
```solidity
function _releaseOrMint(address receiver, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | --------------------------------- |
| `receiver` | `address` | The address to receive the tokens |
| `amount` | `uint256` | The number of tokens to mint |
---
# CCIP v1.6.2 BurnFromMintTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/burn-from-mint-token-pool
## BurnFromMintTokenPool
A specialized token pool contract that manages third-party tokens through minting and burning operations, specifically using the `burnFrom` function.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/pools/BurnFromMintTokenPool.sol)
**Inherits:**
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.6.2/burn-mint-token-pool-abstract)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.2/i-type-and-version)
## State Variables
### typeAndVersion
```solidity
string public constant override typeAndVersion = "BurnFromMintTokenPool 1.6.1";
```
**Returns**
| Type | Description |
| -------- | ----------------------------------------------------- |
| `string` | The contract identifier "BurnFromMintTokenPool 1.6.1" |
## Functions
### _lockOrBurn
Internal function that implements the token burning logic for the `BurnFromMintTokenPool`.
```solidity
function _lockOrBurn(uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------- |
| `amount` | `uint256` | The number of tokens to burn |
### constructor
```solidity
constructor(
IBurnMintERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ---------------- | ------------------------------------------------------ |
| `token` | `IBurnMintERC20` | Address of the token contract to be managed |
| `localTokenDecimals` | `uint8` | Decimal precision of the local token |
| `allowlist` | `address[]` | List of addresses authorized to interact with the pool |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `router` | `address` | Address of the router contract |
---
# CCIP v1.6.2 LockReleaseTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/lock-release-token-pool
## LockReleaseTokenPool
A specialized token pool for managing native tokens through a lock and release mechanism, with support for liquidity management.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/pools/LockReleaseTokenPool.sol)
**Inherits:**
- [TokenPool](/ccip/api-reference/evm/v1.6.2/token-pool)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.2/i-type-and-version)
## Events
### LiquidityTransferred
```solidity
event LiquidityTransferred(address indexed from, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------- |
| `from` | `address` | Yes | The source pool address |
| `amount` | `uint256` | No | The amount of liquidity transferred |
### RebalancerSet
```solidity
event RebalancerSet(address oldRebalancer, address newRebalancer);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------- | --------- | ------- | -------------------------------------- |
| `oldRebalancer` | `address` | No | The address of the previous rebalancer |
| `newRebalancer` | `address` | No | The address of the new rebalancer |
## Errors
### InsufficientLiquidity
```solidity
error InsufficientLiquidity();
```
### LiquidityNotAccepted
```solidity
error LiquidityNotAccepted();
```
## State Variables
### i_acceptLiquidity
```solidity
bool internal immutable i_acceptLiquidity;
```
### s_rebalancer
```solidity
address internal s_rebalancer;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "LockReleaseTokenPool 1.6.1";
```
## Functions
### canAcceptLiquidity
Determines whether the pool can accept external liquidity.
```solidity
function canAcceptLiquidity() external view returns (bool);
```
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the pool accepts external liquidity |
### constructor
```solidity
constructor(
IERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
bool acceptLiquidity,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ----------- | ------------------------------------------- |
| `token` | `IERC20` | The token contract to manage |
| `localTokenDecimals` | `uint8` | The decimal precision for the local token |
| `allowlist` | `address[]` | Initial list of authorized addresses |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `acceptLiquidity` | `bool` | Whether the pool accepts external liquidity |
| `router` | `address` | Address of the router contract |
### getRebalancer
Returns the current rebalancer address.
```solidity
function getRebalancer() external view returns (address);
```
**Returns**
| Type | Description |
| --------- | ------------------------------------- |
| `address` | The current liquidity manager address |
### provideLiquidity
Adds external liquidity to the pool.
```solidity
function provideLiquidity(uint256 amount) external;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------------- |
| `amount` | `uint256` | The amount of liquidity to provide |
### _releaseOrMint
Internal function that implements the token release logic for a `LockReleaseTokenPool`.
```solidity
function _releaseOrMint(address receiver, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | --------------------------------- |
| `receiver` | `address` | The address to receive the tokens |
| `amount` | `uint256` | The number of tokens to release |
### setRebalancer
Updates the rebalancer address.
```solidity
function setRebalancer(address rebalancer) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | --------------------------------- |
| `rebalancer` | `address` | The new rebalancer address to set |
### transferLiquidity
Transfers liquidity from an older pool version.
```solidity
function transferLiquidity(address from, uint256 amount) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `from` | `address` | The address of the source pool |
| `amount` | `uint256` | The amount of liquidity to transfer |
### withdrawLiquidity
Removes liquidity from the pool.
```solidity
function withdrawLiquidity(uint256 amount) external;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `amount` | `uint256` | The amount of liquidity to withdraw |
---
# CCIP v1.6.2 TokenAdminRegistry Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/token-admin-registry
## TokenAdminRegistry
A contract that manages token pool configurations and administrator access for CCIP-enabled tokens.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/tokenAdminRegistry/TokenAdminRegistry.sol)
**Inherits:**
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.2/i-type-and-version)
## Functions
### acceptAdminRole
Accepts the administrator role for a token.
```solidity
function acceptAdminRole(address localToken) external;
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | ------------------------------------------ |
| `localToken` | `address` | The token to accept the administrator role |
### addRegistryModule
Adds a new registry module to the allowed modules list.
```solidity
function addRegistryModule(address module) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------- |
| `module` | `address` | The module to authorize |
### getAllConfiguredTokens
Returns a paginated list of configured tokens.
```solidity
function getAllConfiguredTokens(uint64 startIndex, uint64 maxCount) external view returns (address[] memory tokens);
```
**Parameters**
| Name | Type | Description |
| ------------ | -------- | --------------------------------------------------------- |
| `startIndex` | `uint64` | Starting position in the list (0 for beginning) |
| `maxCount` | `uint64` | Maximum tokens to retrieve (use type(uint64).max for all) |
**Returns**
| Type | Description |
| ----------- | ---------------------------------- |
| `address[]` | List of configured token addresses |
### getPool
Returns the pool address for a specific token.
```solidity
function getPool(address token) external view returns (address);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ------------------ |
| `token` | `address` | The token to query |
**Returns**
| Type | Description |
| --------- | ------------------------ |
| `address` | The token's pool address |
### getPools
Returns pool addresses for multiple tokens.
```solidity
function getPools(address[] calldata tokens) external view returns (address[] memory);
```
**Parameters**
| Name | Type | Description |
| -------- | ----------- | --------------- |
| `tokens` | `address[]` | Tokens to query |
**Returns**
| Type | Description |
| ----------- | ------------------------------------- |
| `address[]` | Array of corresponding pool addresses |
### getTokenConfig
Returns the complete configuration for a token.
```solidity
function getTokenConfig(address token) external view returns (TokenConfig memory);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------- |
| `token` | `address` | Token to query |
**Returns**
| Type | Description |
| ------------- | ---------------------------- |
| `TokenConfig` | Complete token configuration |
### isAdministrator
Checks if an address is the administrator for a token.
```solidity
function isAdministrator(address localToken, address administrator) external view returns (bool);
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | ----------------- |
| `localToken` | `address` | Token to check |
| `administrator` | `address` | Address to verify |
**Returns**
| Type | Description |
| ------ | ---------------------------------------- |
| `bool` | True if address is current administrator |
### isRegistryModule
Checks if an address is an authorized registry module.
```solidity
function isRegistryModule(address module) public view returns (bool);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------- |
| `module` | `address` | Address to verify |
**Returns**
| Type | Description |
| ------ | ------------------------------------ |
| `bool` | True if address is authorized module |
### proposeAdministrator
Proposes an initial administrator for a token.
```solidity
function proposeAdministrator(address localToken, address administrator) external;
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | ------------------------------ |
| `localToken` | `address` | Token to configure |
| `administrator` | `address` | Proposed administrator address |
### removeRegistryModule
Removes a registry module from the allowed modules list.
```solidity
function removeRegistryModule(address module) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------- |
| `module` | `address` | The module to remove |
### setPool
Sets or updates the pool for a token.
```solidity
function setPool(address localToken, address pool) external onlyTokenAdmin(localToken);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | --------------------------------- |
| `localToken` | `address` | Token to configure |
| `pool` | `address` | New pool address (or 0 to delist) |
### transferAdminRole
Initiates transfer of administrator role.
```solidity
function transferAdminRole(address localToken, address newAdmin) external onlyTokenAdmin(localToken);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | -------------------------------------------------------- |
| `localToken` | `address` | The token contract whose admin role is being transferred |
| `newAdmin` | `address` | The proposed new administrator address (or 0 to cancel) |
## Events
### AdministratorTransferRequested
```solidity
event AdministratorTransferRequested(address indexed token, address indexed currentAdmin, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------------- | --------- | ------- | -------------------------------------------------------- |
| `token` | `address` | Yes | The token contract whose admin role is being transferred |
| `currentAdmin` | `address` | Yes | The current administrator address |
| `newAdmin` | `address` | Yes | The proposed new administrator address |
### AdministratorTransferred
```solidity
event AdministratorTransferred(address indexed token, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| ---------- | --------- | ------- | -------------------------------------------------------- |
| `token` | `address` | Yes | The token contract whose admin role has been transferred |
| `newAdmin` | `address` | Yes | The new administrator address |
### PoolSet
```solidity
event PoolSet(address indexed token, address indexed previousPool, address indexed newPool);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------------- | --------- | ------- | ---------------------------------- |
| `token` | `address` | Yes | The token address being configured |
| `previousPool` | `address` | Yes | The previous pool address |
| `newPool` | `address` | Yes | The new pool address |
### RegistryModuleAdded
```solidity
event RegistryModuleAdded(address module);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ------------------------------------------ |
| `module` | `address` | No | The address of the newly authorized module |
### RegistryModuleRemoved
```solidity
event RegistryModuleRemoved(address indexed module);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | --------------------------------- |
| `module` | `address` | Yes | The address of the removed module |
## Errors
### AddressZero
```solidity
error ZeroAddress();
```
### AlreadyRegistered
```solidity
error AlreadyRegistered(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | --------------------------------------------------- |
| `token` | `address` | The token address that already has an administrator |
### InvalidTokenPoolToken
```solidity
error InvalidTokenPoolToken(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | --------------------------------------------------- |
| `token` | `address` | The token address that is not supported by the pool |
### OnlyAdministrator
```solidity
error OnlyAdministrator(address sender, address token);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
| `token` | `address` | The token address being accessed |
### OnlyPendingAdministrator
```solidity
error OnlyPendingAdministrator(address sender, address token);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
| `token` | `address` | The token address being accessed |
### OnlyRegistryModuleOrOwner
```solidity
error OnlyRegistryModuleOrOwner(address sender);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
## Structs
### TokenConfig
Configuration data structure for each token.
```solidity
struct TokenConfig {
address administrator;
address pendingAdministrator;
address tokenPool;
}
```
## State Variables
### s_registryModules
```solidity
EnumerableSet.AddressSet internal s_registryModules;
```
### s_tokenConfig
```solidity
mapping(address token => TokenConfig) internal s_tokenConfig;
```
### s_tokens
```solidity
EnumerableSet.AddressSet internal s_tokens;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "TokenAdminRegistry 1.6.1";
```
---
# CCIP v1.6.2 RegistryModuleOwnerCustom Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/registry-module-owner-custom
## RegistryModuleOwnerCustom
A contract that facilitates token administrator registration through various ownership patterns.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/tokenAdminRegistry/RegistryModuleOwnerCustom.sol)
**Inherits:**
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.2/i-type-and-version)
## Events
### AdministratorRegistered
```solidity
event AdministratorRegistered(address indexed token, address indexed administrator);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------- | --------- | ------- | ------------------------------------ |
| `token` | `address` | Yes | The token contract address |
| `administrator` | `address` | Yes | The registered administrator address |
## Errors
### AddressZero
```solidity
error AddressZero();
```
### CanOnlySelfRegister
```solidity
error CanOnlySelfRegister(address admin, address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------- |
| `admin` | `address` | The expected administrator address |
| `token` | `address` | The token contract address |
### RequiredRoleNotFound
```solidity
error RequiredRoleNotFound(address msgSender, bytes32 role, address token);
```
**Parameters**
| Name | Type | Description |
| ----------- | --------- | ---------------------------- |
| `msgSender` | `address` | The caller's address |
| `role` | `bytes32` | The required role identifier |
| `token` | `address` | The token contract address |
## State Variables
### i_tokenAdminRegistry
```solidity
ITokenAdminRegistry internal immutable i_tokenAdminRegistry;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "RegistryModuleOwnerCustom 1.6.1";
```
## Functions
### constructor
```solidity
constructor(address tokenAdminRegistry);
```
**Parameters**
| Name | Type | Description |
| -------------------- | --------- | ---------------------------------------------- |
| `tokenAdminRegistry` | `address` | The address of the TokenAdminRegistry contract |
### registerAccessControlDefaultAdmin
Registers a token administrator using OpenZeppelin's AccessControl DEFAULT_ADMIN_ROLE.
```solidity
function registerAccessControlDefaultAdmin(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### registerAdminViaGetCCIPAdmin
Registers a token administrator using the `getCCIPAdmin` method.
```solidity
function registerAdminViaGetCCIPAdmin(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### registerAdminViaOwner
Registers a token administrator using the `owner` method.
```solidity
function registerAdminViaOwner(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### _registerAdmin
Internal function to handle administrator registration.
```solidity
function _registerAdmin(address token, address admin) internal;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ------------------------------------------ |
| `token` | `address` | The token contract to register admin for |
| `admin` | `address` | The administrator address being registered |
---
# CCIP v1.6.2 BurnMintERC20 Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/burn-mint-erc20
## BurnMintERC20
An ERC20-compliant token contract that extends the standard functionality with controlled minting and burning capabilities through role-based access control.
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol)
## Events
### CCIPAdminTransferred
```solidity
event CCIPAdminTransferred(address indexed previousAdmin, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------- | --------- | ------- | ------------------------------ |
| `previousAdmin` | `address` | Yes | The address that held the role |
| `newAdmin` | `address` | Yes | The address receiving the role |
## Errors
### InvalidRecipient
```solidity
error InvalidRecipient(address recipient);
```
### MaxSupplyExceeded
```solidity
error MaxSupplyExceeded(uint256 supplyAfterMint);
```
## State Variables
### BURNER_ROLE
```solidity
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
```
### MINTER_ROLE
```solidity
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
```
## Functions
### _approve
Internal function that manages token spending allowances with built-in safety checks.
```solidity
function _approve(address owner, address spender, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | ------------------------------------------------ |
| `owner` | `address` | The address that currently owns the tokens |
| `spender` | `address` | The address that will be allowed to spend tokens |
| `amount` | `uint256` | The number of tokens to approve for spending |
### burn (with amount)
Allows authorized addresses to burn (destroy) tokens from their own account.
```solidity
function burn(uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------- |
| `amount` | `uint256` | The number of tokens to destroy |
### burn (with account)
Alternative burn function that allows burning tokens from a specified account.
```solidity
function burn(address account, uint256 amount) public virtual override;
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | --------------------------------- |
| `account` | `address` | The account to remove tokens from |
| `amount` | `uint256` | The number of tokens to destroy |
### burnFrom
Burns tokens from a specified account, requiring prior approval.
```solidity
function burnFrom(address account, uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE);
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | --------------------------------- |
| `account` | `address` | The account to remove tokens from |
| `amount` | `uint256` | The number of tokens to destroy |
### constructor
```solidity
constructor(
string memory name,
string memory symbol,
uint8 decimals_,
uint256 maxSupply_,
uint256 preMint
) ERC20(name, symbol);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | -------------------------------------------------- |
| `name` | `string` | The display name of the token |
| `symbol` | `string` | The token's ticker symbol |
| `decimals_` | `uint8` | The number of decimal places for token amounts |
| `maxSupply_` | `uint256` | The maximum allowed token supply (0 for unlimited) |
| `preMint` | `uint256` | The amount of tokens to mint to the deployer |
### decimals
Returns the token's decimal precision.
```solidity
function decimals() public view virtual override returns (uint8);
```
**Returns**
| Type | Description |
| ------- | --------------------------------------------------- |
| `uint8` | The number of decimal places used for token amounts |
### getCCIPAdmin
Retrieves the current CCIP administrator's address.
```solidity
function getCCIPAdmin() external view returns (address);
```
**Returns**
| Type | Description |
| --------- | ---------------------------------------- |
| `address` | The current CCIP administrator's address |
### grantMintAndBurnRoles
Assigns both minting and burning permissions to a single address.
```solidity
function grantMintAndBurnRoles(address burnAndMinter) external;
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | -------------------------------------------------------- |
| `burnAndMinter` | `address` | The address that will receive minting and burning rights |
### maxSupply
Returns the token's maximum supply limit.
```solidity
function maxSupply() public view virtual returns (uint256);
```
**Returns**
| Type | Description |
| --------- | ---------------------------------------------------- |
| `uint256` | The maximum allowed token supply (0 means unlimited) |
### mint
Creates new tokens and assigns them to a specified address.
```solidity
function mint(address account, uint256 amount) external override onlyRole(MINTER_ROLE);
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | -------------------------------------------- |
| `account` | `address` | The address that will receive the new tokens |
| `amount` | `uint256` | The number of new tokens to create |
### setCCIPAdmin
Updates the CCIP administrator role.
```solidity
function setCCIPAdmin(address newAdmin) public onlyRole(DEFAULT_ADMIN_ROLE);
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | ------------------------------------------------------- |
| `newAdmin` | `address` | The address that will become the new CCIP administrator |
### supportsInterface
Determines whether the contract implements a specific interface.
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual override(AccessControl, IERC165) returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | --------------------------------------------------------- |
| `bool` | `true` if the contract implements the specified interface |
### _transfer
Internal function that handles token transfers between addresses.
```solidity
function _transfer(address from, address to, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `from` | `address` | The address sending tokens |
| `to` | `address` | The address receiving tokens |
| `amount` | `uint256` | The number of tokens to transfer |
---
# CCIP v1.6.2 RateLimiter Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/rate-limiter
## RateLimiter
A library implementing the Token Bucket algorithm for rate limiting cross-chain operations.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/libraries/RateLimiter.sol)
## Events
### TokensConsumed
```solidity
event TokensConsumed(uint256 tokens);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------- |
| `tokens` | `uint256` | The number of tokens consumed |
### ConfigChanged
```solidity
event ConfigChanged(Config config);
```
**Parameters**
| Name | Type | Description |
| -------- | ------------------- | ----------------------------- |
| `config` | [`Config`](#config) | The new configuration applied |
## Errors
### BucketOverfilled
```solidity
error BucketOverfilled();
```
### OnlyCallableByAdminOrOwner
```solidity
error OnlyCallableByAdminOrOwner();
```
### TokenMaxCapacityExceeded
```solidity
error TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress);
```
### TokenRateLimitReached
```solidity
error TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress);
```
### AggregateValueMaxCapacityExceeded
```solidity
error AggregateValueMaxCapacityExceeded(uint256 capacity, uint256 requested);
```
### AggregateValueRateLimitReached
```solidity
error AggregateValueRateLimitReached(uint256 minWaitInSeconds, uint256 available);
```
### InvalidRateLimitRate
```solidity
error InvalidRateLimitRate(Config rateLimiterConfig);
```
### DisabledNonZeroRateLimit
```solidity
error DisabledNonZeroRateLimit(Config config);
```
### RateLimitMustBeDisabled
```solidity
error RateLimitMustBeDisabled();
```
## Structs
### TokenBucket
Represents the state and configuration of a token bucket rate limiter.
```solidity
struct TokenBucket {
uint128 tokens;
uint32 lastUpdated;
bool isEnabled;
uint128 capacity;
uint128 rate;
}
```
### Config
Configuration parameters for the rate limiter.
```solidity
struct Config {
bool isEnabled;
uint128 capacity;
uint128 rate;
}
```
## Functions
### _consume
Removes tokens from the pool, reducing the available rate capacity for subsequent calls.
```solidity
function _consume(TokenBucket storage s_bucket, uint256 requestTokens, address tokenAddress) internal;
```
**Parameters**
| Name | Type | Description |
| --------------- | ----------------------------- | --------------------------------------------------------------- |
| `s_bucket` | [`TokenBucket`](#tokenbucket) | The token bucket to consume from |
| `requestTokens` | `uint256` | The number of tokens to consume |
| `tokenAddress` | `address` | The token address (use address(0) for aggregate value capacity) |
### _currentTokenBucketState
Retrieves the current state of a token bucket, including automatic refill calculations.
```solidity
function _currentTokenBucketState(TokenBucket memory bucket) internal view returns (TokenBucket memory);
```
**Returns**
| Type | Description |
| ----------------------------- | ------------------------------------- |
| [`TokenBucket`](#tokenbucket) | The current state of the token bucket |
### _setTokenBucketConfig
Updates the rate limiter configuration.
```solidity
function _setTokenBucketConfig(TokenBucket storage s_bucket, Config memory config) internal;
```
**Parameters**
| Name | Type | Description |
| ---------- | ----------------------------- | ------------------------------ |
| `s_bucket` | [`TokenBucket`](#tokenbucket) | The token bucket to configure |
| `config` | [`Config`](#config) | The new configuration to apply |
### _validateTokenBucketConfig
Validates rate limiter configuration parameters.
```solidity
function _validateTokenBucketConfig(Config memory config, bool mustBeDisabled) internal pure;
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------------------- | ------------------------------------------ |
| `config` | [`Config`](#config) | The configuration to validate |
| `mustBeDisabled` | `bool` | Whether the configuration must be disabled |
### _calculateRefill
Calculates the number of tokens to add during a refill operation.
```solidity
function _calculateRefill(
uint256 capacity,
uint256 tokens,
uint256 timeDiff,
uint256 rate
) private pure returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | ------------------------------------------- |
| `capacity` | `uint256` | Maximum token capacity |
| `tokens` | `uint256` | Current token balance |
| `timeDiff` | `uint256` | Time elapsed since last refill (in seconds) |
| `rate` | `uint256` | Tokens per second refill rate |
**Returns**
| Type | Description |
| --------- | ---------------------------------- |
| `uint256` | The new token balance after refill |
### _min
Returns the smaller of two numbers.
```solidity
function _min(uint256 a, uint256 b) internal pure returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---- | --------- | ------------- |
| `a` | `uint256` | First number |
| `b` | `uint256` | Second number |
**Returns**
| Type | Description |
| --------- | ------------------------------ |
| `uint256` | The smaller of the two numbers |
---
# CCIP v1.6.2 Error Codes and Messages API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/errors
When invoking the `ccipSend` [function](/ccip/api-reference/evm/v1.6.2/i-router-client#ccipsend), it is possible to encounter various errors. These might be thrown either by the CCIP router or by one of the downstream contracts called by the CCIP router. Below is a compiled list of potential errors you might encounter. Referencing this list will enable you to capture and handle these exceptions gracefully.
## Router Errors
## OnRamp Errors
## FeeQuoter Errors
## Rate Limiter Errors
## Token (ERC20) Errors
## BurnMintERC20 Errors
## Token Pool Errors
---
# CCIP v1.6.2 Ownable2Step Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/ownable-2-step
## Ownable2Step
A minimal contract that implements 2-step ownership transfer and nothing more. It's made to be minimal to reduce the impact of the bytecode size on any contract that inherits from it.
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/access/Ownable2Step.sol)
## Events
### OwnershipTransferRequested
```solidity
event OwnershipTransferRequested(address indexed from, address indexed to);
```
**Parameters**
| Name | Type | Description |
| ------ | --------- | ------------------------------------- |
| `from` | `address` | Current owner initiating the transfer |
| `to` | `address` | Proposed new owner |
### OwnershipTransferred
```solidity
event OwnershipTransferred(address indexed from, address indexed to);
```
**Parameters**
| Name | Type | Description |
| ------ | --------- | -------------- |
| `from` | `address` | Previous owner |
| `to` | `address` | New owner |
## Errors
### CannotTransferToSelf
```solidity
error CannotTransferToSelf();
```
### MustBeProposedOwner
```solidity
error MustBeProposedOwner();
```
### OnlyCallableByOwner
```solidity
error OnlyCallableByOwner();
```
### OwnerCannotBeZero
```solidity
error OwnerCannotBeZero();
```
## State Variables
### s_owner
The owner is the current owner of the contract.
```solidity
address private s_owner;
```
### s_pendingOwner
The pending owner is the address to which ownership may be transferred.
```solidity
address private s_pendingOwner;
```
## Functions
### acceptOwnership
Allows an ownership transfer to be completed by the recipient.
```solidity
function acceptOwnership() external override;
```
### constructor
Initializes the contract with an owner and optionally a pending owner.
```solidity
constructor(address newOwner, address pendingOwner);
```
**Parameters**
| Name | Type | Description |
| -------------- | --------- | -------------------------------------------------- |
| `newOwner` | `address` | The initial owner of the contract |
| `pendingOwner` | `address` | Optional address to initiate ownership transfer to |
### onlyOwner
Modifier that restricts function access to the contract owner.
```solidity
modifier onlyOwner();
```
### owner
Returns the current owner's address.
```solidity
function owner() public view override returns (address);
```
**Returns**
| Type | Description |
| --------- | -------------------------------- |
| `address` | The address of the current owner |
### transferOwnership
Allows an owner to begin transferring ownership to a new address.
```solidity
function transferOwnership(address to) public override onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ---- | --------- | -------------------------------------------------- |
| `to` | `address` | The address to which ownership will be transferred |
### _validateOwnership
Internal function to validate access control.
```solidity
function _validateOwnership() internal view;
```
---
# CCIP v1.6.2 Ownable2StepMsgSender Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.2/ownable-2-step-msg-sender
## Ownable2StepMsgSender
A contract that facilitates two-step ownership transfer, providing enhanced security for ownership management. This contract extends `Ownable2Step` and automatically sets the deploying address (`msg.sender`) as the initial owner with no pending owner.
**Inherits:**
- [`Ownable2Step`](/ccip/api-reference/evm/v1.6.2/ownable-2-step) - Provides secure two-step ownership transfer functionality
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-solidity/1.5.0/contracts/src/v0.8/shared/access/Ownable2StepMsgSender.sol)
## Functions
### constructor
Initializes the contract with the deploying address as the owner and no pending owner.
```solidity
constructor() Ownable2Step(msg.sender, address(0));
```
---
# CCIP v1.6.1 API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1
Last Updated: 2025-09-08
## API References
### Core Components
- [CCIPReceiver](/ccip/api-reference/evm/v1.6.1/ccip-receiver) - Base contract for receiving CCIP messages
- [Client](/ccip/api-reference/evm/v1.6.1/client) - Library providing structs and types for building CCIP messages
- [IRouterClient](/ccip/api-reference/evm/v1.6.1/i-router-client) - Interface for sending messages through CCIP
- [Pool](/ccip/api-reference/evm/v1.6.1/pool) - Library providing token pool functions for cross-chain operations
- [RateLimiter](/ccip/api-reference/evm/v1.6.1/rate-limiter) - Contract for managing rate limits on token transfers
- [TypeAndVersion](/ccip/api-reference/evm/v1.6.1/i-type-and-version) - Interface for contract versioning
### Token Pools
- [BurnFromMintTokenPool](/ccip/api-reference/evm/v1.6.1/burn-from-mint-token-pool) - Implementation using `burnFrom(address, amount)` for token burning
- [BurnMintERC20](/ccip/api-reference/evm/v1.6.1/burn-mint-erc20) - Implementation for burning and minting ERC20 tokens
- [BurnMintTokenPool](/ccip/api-reference/evm/v1.6.1/burn-mint-token-pool) - Implementation using `burn(amount)` for token burning
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.6.1/burn-mint-token-pool-abstract) - Abstract contract for burn/mint token handling
- [LockReleaseTokenPool](/ccip/api-reference/evm/v1.6.1/lock-release-token-pool) - Implementation for locking and releasing tokens on their native chain
- [TokenPool](/ccip/api-reference/evm/v1.6.1/token-pool) - Base abstract class defining common functionality for all token pools
### Access Control
- [Ownable2Step](/ccip/api-reference/evm/v1.6.1/ownable-2-step) - Base contract implementing secure two-step ownership transfer
- [Ownable2StepMsgSender](/ccip/api-reference/evm/v1.6.1/ownable-2-step-msg-sender) - Extension of Ownable2Step that sets msg.sender as initial owner
### Registry Components
- [RegistryModuleOwnerCustom](/ccip/api-reference/evm/v1.6.1/registry-module-owner-custom) - Registry module for token admin registration
- [TokenAdminRegistry](/ccip/api-reference/evm/v1.6.1/token-admin-registry) - Contract for storing token pool configurations
### Error Handling
- [Errors](/ccip/api-reference/evm/v1.6.1/errors) - Comprehensive list of CCIP error codes and their descriptions
---
# CCIP v1.6.1 Client Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/client
Last Updated: 2025-09-08
## Client
A library that provides core data structures and utilities for building and handling cross-chain messages in CCIP.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/libraries/Client.sol)
## Structs
### Any2EVMMessage
Structure representing a message received from any chain to an EVM chain.
```solidity
struct Any2EVMMessage {
bytes32 messageId;
uint64 sourceChainSelector;
bytes sender;
bytes data;
EVMTokenAmount[] destTokenAmounts;
}
```
**Properties**
| Name | Type | Description |
| --------------------- | ------------------------------------- | ------------------------------------------------- |
| `messageId` | `bytes32` | Message ID corresponding to ccipSend on source |
| `sourceChainSelector` | `uint64` | Identifier of the source chain |
| `sender` | `bytes` | Sender address (use abi.decode if from EVM chain) |
| `data` | `bytes` | Custom payload from the original message |
| `destTokenAmounts` | [`EVMTokenAmount[]`](#evmtokenamount) | Token amounts in destination chain representation |
### EVM2AnyMessage
Structure for sending a message from an EVM chain to any supported chain.
```solidity
struct EVM2AnyMessage {
bytes receiver;
bytes data;
EVMTokenAmount[] tokenAmounts;
address feeToken;
bytes extraArgs;
}
```
**Properties**
| Name | Type | Description |
| -------------- | ------------------------------------- | --------------------------------------------------- |
| `receiver` | `bytes` | Encoded receiver address for destination EVM chains |
| `data` | `bytes` | Custom payload to send |
| `tokenAmounts` | [`EVMTokenAmount[]`](#evmtokenamount) | Tokens and amounts to transfer |
| `feeToken` | `address` | Token used for fees (address(0) for native tokens) |
| `extraArgs` | `bytes` | Additional arguments encoded with _argsToBytes |
### EVMExtraArgsV1
Structure for V1 extra arguments in cross-chain messages.
```solidity
struct EVMExtraArgsV1 {
uint256 gasLimit;
}
```
**Properties**
| Name | Type | Description |
| ---------- | --------- | -------------------------------------------- |
| `gasLimit` | `uint256` | Gas limit for execution on destination chain |
### GenericExtraArgsV2
Structure for V2 extra arguments in cross-chain messages.
```solidity
struct GenericExtraArgsV2 {
uint256 gasLimit;
bool allowOutOfOrderExecution;
}
```
**Properties**
| Name | Type | Description |
| -------------------------- | --------- | --------------------------------------------- |
| `gasLimit` | `uint256` | Gas limit for execution on destination chain |
| `allowOutOfOrderExecution` | `bool` | Whether messages can be executed in any order |
### SVMExtraArgsV1
Structure for V1 extra arguments specific to Solana VM-based chains.
```solidity
struct SVMExtraArgsV1 {
uint32 computeUnits;
uint64 accountIsWritableBitmap;
bool allowOutOfOrderExecution;
bytes32 tokenReceiver;
bytes32[] accounts;
}
```
**Properties**
| Name | Type | Description |
| -------------------------- | ----------- | ------------------------------------------------------ |
| `computeUnits` | `uint32` | Compute units for execution on Solana |
| `accountIsWritableBitmap` | `uint64` | Bitmap indicating which accounts are writable |
| `allowOutOfOrderExecution` | `bool` | Whether messages can be executed in any order |
| `tokenReceiver` | `bytes32` | Address of the token receiver |
| `accounts` | `bytes32[]` | Additional accounts needed for CCIP receiver execution |
### EVMTokenAmount
Structure representing token amounts in CCIP messages.
```solidity
struct EVMTokenAmount {
address token;
uint256 amount;
}
```
**Properties**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `token` | `address` | Token address on the local chain |
| `amount` | `uint256` | Amount of tokens to transfer |
## State Variables
### EVM_EXTRA_ARGS_V1_TAG
```solidity
bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
```
### GENERIC_EXTRA_ARGS_V2_TAG
```solidity
bytes4 public constant GENERIC_EXTRA_ARGS_V2_TAG = 0x181dcf10;
```
### SVM_EXTRA_ARGS_V1_TAG
```solidity
bytes4 public constant SVM_EXTRA_ARGS_V1_TAG = 0x1f3b3aba;
```
### SVM_EXTRA_ARGS_MAX_ACCOUNTS
```solidity
uint256 public constant SVM_EXTRA_ARGS_MAX_ACCOUNTS = 64;
```
### SVM_TOKEN_TRANSFER_DATA_OVERHEAD
```solidity
uint256 public constant SVM_TOKEN_TRANSFER_DATA_OVERHEAD = (4 + 32) // source_pool
+ 32 // token_address
+ 4 // gas_amount
+ 4 // extra_data overhead
+ 32 // amount
+ 32 // size of the token lookup table account
+ 32 // token-related accounts in the lookup table, over-estimated to 32, typically between 11 - 13
+ 32 // token account belonging to the token receiver, e.g ATA, not included in the token lookup table
+ 32 // per-chain token pool config, not included in the token lookup table
+ 32 // per-chain token billing config, not always included in the token lookup table
+ 32; // OffRamp pool signer PDA, not included in the token lookup table
```
### SVM_MESSAGING_ACCOUNTS_OVERHEAD
```solidity
uint256 public constant SVM_MESSAGING_ACCOUNTS_OVERHEAD = 2;
```
### SVM_ACCOUNT_BYTE_SIZE
```solidity
uint256 public constant SVM_ACCOUNT_BYTE_SIZE = 32;
```
## Functions
### _argsToBytes (V1)
Encodes EVMExtraArgsV1 into bytes for message transmission.
```solidity
function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ----------------------------------- | -------------------------------- |
| `extraArgs` | [`EVMExtraArgsV1`](#evmextraargsv1) | The V1 extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
### _argsToBytes (V2)
Encodes GenericExtraArgsV2 into bytes for message transmission.
```solidity
function _argsToBytes(GenericExtraArgsV2 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ------------------------------------------- | ---------------------------------------- |
| `extraArgs` | [`GenericExtraArgsV2`](#genericextraargsv2) | The V2 generic extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
### _svmArgsToBytes
Encodes SVMExtraArgsV1 into bytes for message transmission.
```solidity
function _svmArgsToBytes(SVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ----------------------------------- | --------------------------------- |
| `extraArgs` | [`SVMExtraArgsV1`](#svmextraargsv1) | The SVM extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
---
# CCIP v1.6.1 ITypeAndVersion Interface API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/i-type-and-version
## ITypeAndVersion
An interface that provides type and version information for contracts.
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-release/1.4.0/contracts/src/v0.8/shared/interfaces/ITypeAndVersion.sol)
## Functions
### typeAndVersion
Returns the type and version of the contract.
```solidity
function typeAndVersion() external pure returns (string memory);
```
**Returns**
| Type | Description |
| -------- | ------------------------------------- |
| `string` | The type and version of the contract. |
---
# CCIP v1.6.1 IRouterClient API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/i-router-client
Last Updated: 2025-09-08
## IRouterClient
The IRouterClient interface provides the core functionality for sending cross-chain messages through CCIP (Chainlink Cross-Chain Interoperability Protocol).
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/interfaces/IRouterClient.sol)
## Errors
### InsufficientFeeTokenAmount
Thrown when the provided fee token amount is insufficient for the message delivery.
```solidity
error InsufficientFeeTokenAmount();
```
### InvalidMsgValue
Thrown when the provided msg.value is invalid for the operation.
```solidity
error InvalidMsgValue();
```
### UnsupportedDestinationChain
Thrown when attempting to send a message to an unsupported destination chain.
```solidity
error UnsupportedDestinationChain(uint64 destChainSelector);
```
## Functions
### ccipSend
Sends a message to the destination chain through CCIP.
```solidity
function ccipSend(
uint64 destinationChainSelector,
Client.EVM2AnyMessage calldata message
) external payable returns (bytes32);
```
**Parameters**
| Name | Type | Description |
| -------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `destinationChainSelector` | `uint64` | The destination chain ID |
| `message` | [`Client.EVM2AnyMessage`](/ccip/api-reference/evm/v1.6.1/client#evm2anymessage) | The cross-chain CCIP message including data and/or tokens |
**Returns**
| Name | Type | Description |
| ----------- | --------- | -------------- |
| `messageId` | `bytes32` | The message ID |
### getFee
Gets the fee required for sending a CCIP message to the destination chain.
```solidity
function getFee(
uint64 destinationChainSelector,
Client.EVM2AnyMessage memory message
) external view returns (uint256 fee);
```
**Parameters**
| Name | Type | Description |
| -------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `destinationChainSelector` | `uint64` | The destination chainSelector |
| `message` | [`Client.EVM2AnyMessage`](/ccip/api-reference/evm/v1.6.1/client#evm2anymessage) | The cross-chain CCIP message including data and/or tokens |
**Returns**
| Name | Type | Description |
| ----- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `fee` | `uint256` | Returns execution fee for the message delivery to destination chain, denominated in the feeToken specified in the message. |
### isChainSupported
Checks if the given chain ID is supported for sending/receiving.
```solidity
function isChainSupported(uint64 destChainSelector) external view returns (bool supported);
```
**Parameters**
| Name | Type | Description |
| ------------------- | -------- | ------------------- |
| `destChainSelector` | `uint64` | The chain to check. |
**Returns**
| Name | Type | Description |
| ----------- | ------ | ----------------------------------------- |
| `supported` | `bool` | is true if it is supported, false if not. |
---
# CCIP v1.6.1 CCIPReceiver API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/ccip-receiver
Last Updated: 2025-09-08
## CCIPReceiver
An abstract base contract that provides core functionality for CCIP-enabled applications to receive cross-chain messages.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/applications/CCIPReceiver.sol)
## Errors
### InvalidRouter
```solidity
error InvalidRouter(address router);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------- |
| `router` | `address` | The invalid router address |
## State Variables
### i_ccipRouter
```solidity
address internal immutable i_ccipRouter;
```
## Modifiers
### onlyRouter
```solidity
modifier onlyRouter();
```
## Functions
### ccipReceive
Processes incoming CCIP messages from the router.
```solidity
function ccipReceive(Client.Any2EVMMessage calldata message) external virtual override onlyRouter;
```
**Parameters**
| Name | Type | Description |
| --------- | ------------------------------------------------------------------------------- | ---------------- |
| `message` | [`Client.Any2EVMMessage`](/ccip/api-reference/evm/v1.6.1/client#any2evmmessage) | The CCIP message |
### constructor
```solidity
constructor(address router);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `router` | `address` | The CCIP router contract address |
### getRouter
Returns the address of the current CCIP router.
```solidity
function getRouter() public view virtual returns (address);
```
**Returns**
| Type | Description |
| --------- | ------------------------------- |
| `address` | The current CCIP router address |
### supportsInterface
Determines whether the contract implements specific interfaces.
```solidity
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | ---------------------------------- |
| `bool` | True if the interface is supported |
### _ccipReceive
Internal function to be implemented by derived contracts for custom message handling.
```solidity
function _ccipReceive(Client.Any2EVMMessage memory message) internal virtual;
```
**Parameters**
| Name | Type | Description |
| --------- | ------------------------------------------------------------------------------- | --------------------------- |
| `message` | [`Client.Any2EVMMessage`](/ccip/api-reference/evm/v1.6.1/client#any2evmmessage) | The message to be processed |
---
# CCIP v1.6.1 Pool Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/pool
## Pool
A library that provides core data structures and constants for token pool operations in cross-chain transfers.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/libraries/Pool.sol)
## Structs
### LockOrBurnInV1
Input parameters for locking or burning tokens in cross-chain transfers.
```solidity
struct LockOrBurnInV1 {
bytes receiver;
uint64 remoteChainSelector;
address originalSender;
uint256 amount;
address localToken;
}
```
### LockOrBurnOutV1
Output data from a lock or burn operation.
```solidity
struct LockOrBurnOutV1 {
bytes destTokenAddress;
bytes destPoolData;
}
```
### ReleaseOrMintInV1
Input parameters for releasing or minting tokens in cross-chain transfers.
```solidity
struct ReleaseOrMintInV1 {
bytes originalSender;
uint64 remoteChainSelector;
address receiver;
uint256 sourceDenominatedAmount;
address localToken;
bytes sourcePoolAddress;
bytes sourcePoolData;
bytes offchainTokenData;
}
```
### ReleaseOrMintOutV1
Output data from a release or mint operation.
```solidity
struct ReleaseOrMintOutV1 {
uint256 destinationAmount;
}
```
## State Variables
### CCIP_POOL_V1
```solidity
bytes4 public constant CCIP_POOL_V1 = 0xaff2afbf;
```
### CCIP_POOL_V1_RET_BYTES
```solidity
uint16 public constant CCIP_POOL_V1_RET_BYTES = 32;
```
### CCIP_LOCK_OR_BURN_V1_RET_BYTES
```solidity
uint32 public constant CCIP_LOCK_OR_BURN_V1_RET_BYTES = 32;
```
---
# CCIP v1.6.1 TokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/token-pool
## TokenPool
An abstract contract that provides base functionality for managing cross-chain token operations in CCIP. It handles token decimals across different chains, rate limiting, and access control.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/pools/TokenPool.sol)
**Inherits:**
- [Ownable2StepMsgSender](/ccip/api-reference/evm/v1.6.1/ownable-2-step-msg-sender)
## Events
### AllowListAdd
Emitted when an address is added to the allowlist via [`applyAllowListUpdates`](#applyallowlistupdates).
```solidity
event AllowListAdd(address sender);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ------------------------------------------- |
| `sender` | `address` | No | The address that was added to the allowlist |
### AllowListRemove
Emitted when an address is removed from the allowlist via [`applyAllowListUpdates`](#applyallowlistupdates).
```solidity
event AllowListRemove(address sender);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------------------- |
| `sender` | `address` | No | The address that was removed from the allowlist |
### ChainAdded
Emitted when a new chain is configured in the pool via [`applyChainUpdates`](#applychainupdates).
```solidity
event ChainAdded(
uint64 remoteChainSelector,
bytes remoteToken,
RateLimiter.Config outboundRateLimiterConfig,
RateLimiter.Config inboundRateLimiterConfig
);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------------- | -------------------------------------------------------------------------- | ------- | ----------------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the newly added chain |
| `remoteToken` | `bytes` | No | The token address on the remote chain |
| `outboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.1/rate-limiter#config) | No | Rate limit configuration for outbound transfers |
| `inboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.1/rate-limiter#config) | No | Rate limit configuration for inbound transfers |
### ChainConfigured
Emitted when a chain's configuration is updated via [`setChainRateLimiterConfig`](#setchainratelimiterconfig) or [`setChainRateLimiterConfigs`](#setchainratelimiterconfigs).
```solidity
event ChainConfigured(
uint64 remoteChainSelector,
RateLimiter.Config outboundRateLimiterConfig,
RateLimiter.Config inboundRateLimiterConfig
);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------------- | -------------------------------------------------------------------------- | ------- | ------------------------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the chain being configured |
| `outboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.1/rate-limiter#config) | No | Updated rate limit configuration for outbound transfers |
| `inboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.1/rate-limiter#config) | No | Updated rate limit configuration for inbound transfers |
### ChainRemoved
Emitted when a chain is removed from the pool's configuration via [`applyChainUpdates`](#applychainupdates).
```solidity
event ChainRemoved(uint64 remoteChainSelector);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | ----------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the chain being removed |
### LockedOrBurned
Emitted when tokens are locked or burned by the pool as part of an outbound cross-chain transfer.
```solidity
event LockedOrBurned(address indexed sender, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ---------------------------------------------- |
| `sender` | `address` | Yes | The address initiating the lock/burn operation |
| `amount` | `uint256` | No | The amount of tokens locked or burned |
### RemotePoolAdded
Emitted when a new remote pool is added via [`addRemotePool`](#addremotepool) or [`applyChainUpdates`](#applychainupdates).
```solidity
event RemotePoolAdded(uint64 indexed remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | -------------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The identifier of the chain for the new pool |
| `remotePoolAddress` | `bytes` | No | The address of the newly added pool |
### ReleasedOrMinted
Emitted when tokens are released or minted by the pool as part of an inbound cross-chain transfer.
```solidity
event ReleasedOrMinted(address indexed sender, address indexed recipient, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | --------------------------------------------------- |
| `sender` | `address` | Yes | The address initiating the release/mint operation |
| `recipient` | `address` | Yes | The address receiving the released or minted tokens |
| `amount` | `uint256` | No | The amount of tokens released or minted |
### RemotePoolRemoved
Emitted when a remote pool is removed via [`removeRemotePool`](#removeremotepool).
```solidity
event RemotePoolRemoved(uint64 indexed remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | ---------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The identifier of the chain for the pool |
| `remotePoolAddress` | `bytes` | No | The address of the removed pool |
### RouterUpdated
Emitted when the router address is updated via [`setRouter`](#setrouter).
```solidity
event RouterUpdated(address oldRouter, address newRouter);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | ------------------------------------ |
| `oldRouter` | `address` | No | The previous router contract address |
| `newRouter` | `address` | No | The new router contract address |
### RateLimitAdminSet
Emitted when the rate limit administrator is changed via [`setRateLimitAdmin`](#setratelimitadmin).
```solidity
event RateLimitAdminSet(address rateLimitAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| ---------------- | --------- | ------- | ---------------------------------------- |
| `rateLimitAdmin` | `address` | No | The new rate limit administrator address |
### OutboundRateLimitConsumed
Emitted when the outbound rate limit is consumed via `lockOrBurn`.
```solidity
event OutboundRateLimitConsumed(uint64 indexed remoteChainSelector, address token, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | --------- | ------- | --------------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The destination chain selector |
| `token` | `address` | No | The address of the token being sent |
| `amount` | `uint64` | No | The amount of tokens consuming the rate limit |
### InboundRateLimitConsumed
Emitted when the inbound rate limit is consumed via `releaseOrMint`.
```solidity
event InboundRateLimitConsumed(uint64 indexed remoteChainSelector, address token, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | --------- | ------- | --------------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The source chain selector |
| `token` | `address` | No | The address of the token being received |
| `amount` | `uint256` | No | The amount of tokens consuming the rate limit |
## Errors
### AllowListNotEnabled
```solidity
error AllowListNotEnabled();
```
### CallerIsNotARampOnRouter
```solidity
error CallerIsNotARampOnRouter(address caller);
```
### ChainAlreadyExists
```solidity
error ChainAlreadyExists(uint64 chainSelector);
```
**Parameters**
| Name | Type | Description |
| --------------- | -------- | --------------------------------------------- |
| `chainSelector` | `uint64` | The selector of the chain that already exists |
### ChainNotAllowed
```solidity
error ChainNotAllowed(uint64 remoteChainSelector);
```
### CursedByRMN
```solidity
error CursedByRMN();
```
### InvalidDecimalArgs
```solidity
error InvalidDecimalArgs(uint8 expected, uint8 actual);
```
**Parameters**
| Name | Type | Description |
| ---------- | ------- | -------------------------------------- |
| `expected` | `uint8` | The expected number of decimals |
| `actual` | `uint8` | The actual number of decimals provided |
### InvalidRemoteChainDecimals
```solidity
error InvalidRemoteChainDecimals(bytes sourcePoolData);
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------- | -------------------------------------- |
| `sourcePoolData` | `bytes` | The invalid decimal configuration data |
### InvalidRemotePoolForChain
```solidity
error InvalidRemotePoolForChain(uint64 remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | -------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector being queried |
| `remotePoolAddress` | `bytes` | The invalid pool address |
### InvalidSourcePoolAddress
```solidity
error InvalidSourcePoolAddress(bytes sourcePoolAddress);
```
### InvalidToken
```solidity
error InvalidToken(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------------------------- |
| `token` | `address` | The address of the invalid token |
### MismatchedArrayLengths
```solidity
error MismatchedArrayLengths();
```
### NonExistentChain
```solidity
error NonExistentChain(uint64 remoteChainSelector);
```
### OverflowDetected
```solidity
error OverflowDetected(uint8 remoteDecimals, uint8 localDecimals, uint256 remoteAmount);
```
**Parameters**
| Name | Type | Description |
| ---------------- | --------- | ----------------------------------- |
| `remoteDecimals` | `uint8` | The decimals on the remote chain |
| `localDecimals` | `uint8` | The decimals on the local chain |
| `remoteAmount` | `uint256` | The amount that caused the overflow |
### PoolAlreadyAdded
```solidity
error PoolAlreadyAdded(uint64 remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ---------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector where the pool exists |
| `remotePoolAddress` | `bytes` | The address of the already existing pool |
### SenderNotAllowed
```solidity
error SenderNotAllowed(address sender);
```
### Unauthorized
```solidity
error Unauthorized(address caller);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------------- |
| `caller` | `address` | The address that attempted the action |
### ZeroAddressNotAllowed
```solidity
error ZeroAddressNotAllowed();
```
## Structs
### ChainUpdate
Configuration data for adding or updating a chain.
```solidity
struct ChainUpdate {
uint64 remoteChainSelector;
bytes[] remotePoolAddresses;
bytes remoteTokenAddress;
RateLimiter.Config outboundRateLimiterConfig;
RateLimiter.Config inboundRateLimiterConfig;
}
```
### RemoteChainConfig
Internal configuration for a remote chain.
```solidity
struct RemoteChainConfig {
RateLimiter.TokenBucket outboundRateLimiterConfig;
RateLimiter.TokenBucket inboundRateLimiterConfig;
bytes remoteTokenAddress;
EnumerableSet.Bytes32Set remotePools;
}
```
## State Variables
### i_token
The token managed by this pool. Currently supports one token per pool.
```solidity
IERC20 internal immutable i_token;
```
### i_tokenDecimals
The number of decimals for the managed token.
```solidity
uint8 internal immutable i_tokenDecimals;
```
### i_rmnProxy
The Risk Management Network (RMN) proxy address.
```solidity
address internal immutable i_rmnProxy;
```
### i_allowlistEnabled
Flag indicating if the pool uses access control.
```solidity
bool internal immutable i_allowlistEnabled;
```
### s_allowlist
Set of addresses authorized to initiate cross-chain operations.
```solidity
EnumerableSet.AddressSet internal s_allowlist;
```
### s_router
The CCIP Router contract address.
```solidity
IRouter internal s_router;
```
### s_remoteChainSelectors
Set of authorized chain selectors for cross-chain operations.
```solidity
EnumerableSet.UintSet internal s_remoteChainSelectors;
```
### s_remoteChainConfigs
Configuration for each remote chain, including rate limits and token details.
```solidity
mapping(uint64 remoteChainSelector => RemoteChainConfig) internal s_remoteChainConfigs;
```
### s_remotePoolAddresses
Maps hashed pool addresses to their original form for verification.
```solidity
mapping(bytes32 poolAddressHash => bytes poolAddress) internal s_remotePoolAddresses;
```
### s_rateLimitAdmin
The address authorized to manage rate limits.
```solidity
address internal s_rateLimitAdmin;
```
## Functions
### _applyAllowListUpdates
Internal version of applyAllowListUpdates to allow for reuse in the constructor.
```solidity
function _applyAllowListUpdates(address[] memory removes, address[] memory adds) internal;
```
**Parameters**
| Name | Type | Description |
| --------- | ----------- | ----------------------------------------------- |
| `removes` | `address[]` | Array of addresses to remove from the allowlist |
| `adds` | `address[]` | Array of addresses to add to the allowlist |
### _calculateLocalAmount
Calculates the local amount based on the remote amount and decimals.
*This function protects against overflows. If there is a transaction that hits the overflow check, it is
probably incorrect as that means the amount cannot be represented on this chain. If the local decimals have been
wrongly configured, the token developer could redeploy the pool with the correct decimals and manually re-execute the
CCIP tx to fix the issue.*
```solidity
function _calculateLocalAmount(uint256 remoteAmount, uint8 remoteDecimals) internal view virtual returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---------------- | --------- | ---------------------------------------------- |
| `remoteAmount` | `uint256` | The amount on the remote chain. |
| `remoteDecimals` | `uint8` | The decimals of the token on the remote chain. |
**Returns**
| Name | Type | Description |
| -------- | --------- | ----------------- |
| `` | `uint256` | The local amount. |
### _checkAllowList
Internal function to verify if a sender is authorized when allowlist is enabled.
```solidity
function _checkAllowList(address sender) internal view;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `sender` | `address` | The address to check for permission |
### _consumeInboundRateLimit
Internal function to consume rate limiting capacity for incoming transfers.
```solidity
function _consumeInboundRateLimit(uint64 remoteChainSelector, uint256 amount) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | --------- | --------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector for the source chain |
| `amount` | `uint256` | The amount of tokens being transferred |
### _consumeOutboundRateLimit
Internal function to consume rate limiting capacity for outgoing transfers.
```solidity
function _consumeOutboundRateLimit(uint64 remoteChainSelector, uint256 amount) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | --------- | -------------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector for the destination chain |
| `amount` | `uint256` | The amount of tokens being transferred |
### _encodeLocalDecimals
Internal function to encode the local token's decimals for cross-chain communication.
```solidity
function _encodeLocalDecimals() internal view virtual returns (bytes memory);
```
**Returns**
| Type | Description |
| ------- | --------------------------------------------- |
| `bytes` | ABI-encoded decimal places of the local token |
### _lockOrBurn
Abstract internal function designed to be overridden with the specific token lock or burn logic.
```solidity
function _lockOrBurn(uint256 amount) internal virtual;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------------ |
| `amount` | `uint256` | The amount of tokens to lock or burn |
### _onlyOffRamp
Checks whether remote chain selector is configured on this contract, and if the msg.sender
is a permissioned offRamp for the given chain on the Router.
```solidity
function _onlyOffRamp(uint64 remoteChainSelector) internal view;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to validate authorization for |
### _onlyOnRamp
Checks whether remote chain selector is configured on this contract, and if the msg.sender
is a permissioned onRamp for the given chain on the Router.
```solidity
function _onlyOnRamp(uint64 remoteChainSelector) internal view;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to validate authorization for |
### _parseRemoteDecimals
Internal function to decode the decimal configuration received from a remote chain.
```solidity
function _parseRemoteDecimals(bytes memory sourcePoolData) internal view virtual returns (uint8);
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------- | -------------------------------------- |
| `sourcePoolData` | `bytes` | The encoded decimal configuration data |
**Returns**
| Type | Description |
| ------- | ----------------------------------------------- |
| `uint8` | The number of decimals used on the remote chain |
### _releaseOrMint
Abstract internal function designed to be overridden with the specific token release or mint logic.
```solidity
function _releaseOrMint(address receiver, uint256 amount) internal virtual;
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | --------------------------------------- |
| `receiver` | `address` | The address receiving the tokens |
| `amount` | `uint256` | The amount of tokens to release or mint |
### _setRateLimitConfig
Internal function to update rate limit configuration for a chain.
```solidity
function _setRateLimitConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------------------- | ----------------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector to configure |
| `outboundConfig` | `RateLimiter.Config` | Rate limit configuration for outgoing transfers |
| `inboundConfig` | `RateLimiter.Config` | Rate limit configuration for incoming transfers |
### _setRemotePool
Internal function to add a pool address to the allowed remote token pools for a chain. Called during chain configuration and when adding individual remote pools.
```solidity
function _setRemotePool(uint64 remoteChainSelector, bytes memory remotePoolAddress) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to add the pool for |
| `remotePoolAddress` | `bytes` | The address of the remote pool (encoded to support non-EVM chains) |
### _validateLockOrBurn
Internal function to validate lock or burn operations.
```solidity
function _validateLockOrBurn(Pool.LockOrBurnInV1 calldata lockOrBurnIn) internal;
```
### _validateReleaseOrMint
Internal function to validate release or mint operations.
```solidity
function _validateReleaseOrMint(Pool.ReleaseOrMintInV1 calldata releaseOrMintIn) internal;
```
### addRemotePool
Adds a new pool address for a remote chain.
```solidity
function addRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) external onlyOwner;
```
### applyAllowListUpdates
Apply updates to the allow list.
```solidity
function applyAllowListUpdates(address[] calldata removes, address[] calldata adds) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| --------- | ----------- | ---------------------------- |
| `removes` | `address[]` | The addresses to be removed. |
| `adds` | `address[]` | The addresses to be added. |
### applyChainUpdates
Updates chain configurations in bulk.
```solidity
function applyChainUpdates(
uint64[] calldata remoteChainSelectorsToRemove,
ChainUpdate[] calldata chainsToAdd
) external virtual onlyOwner;
```
### constructor
```solidity
constructor(IERC20 token, uint8 localTokenDecimals, address[] memory allowlist, address rmnProxy, address router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ----------- | -------------------------------------------- |
| `token` | `IERC20` | The token to be managed by this pool |
| `localTokenDecimals` | `uint8` | The token's decimal places on this chain |
| `allowlist` | `address[]` | Initial set of authorized addresses (if any) |
| `rmnProxy` | `address` | The Risk Management Network proxy address |
| `router` | `address` | The CCIP Router contract address |
### getAllowList
Gets the allowed addresses.
```solidity
function getAllowList() external view returns (address[] memory);
```
**Returns**
| Name | Type | Description |
| -------- | ----------- | ---------------------- |
| `` | `address[]` | The allowed addresses. |
### getAllowListEnabled
Returns whether allowlist functionality is active.
```solidity
function getAllowListEnabled() external view returns (bool);
```
**Returns**
| Name | Type | Description |
| -------- | ------ | ------------------------------ |
| `` | `bool` | true is enabled, false if not. |
### getCurrentInboundRateLimiterState
Returns the current state of inbound rate limiting for a chain.
```solidity
function getCurrentInboundRateLimiterState(
uint64 remoteChainSelector
) external view returns (RateLimiter.TokenBucket memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to get rate limiter state for |
**Returns**
| Type | Description |
| ------------------------------------------------------------------------------------ | ----------------------------------------- |
| [`RateLimiter.TokenBucket`](/ccip/api-reference/evm/v1.6.1/rate-limiter#tokenbucket) | Current state of the inbound rate limiter |
### getCurrentOutboundRateLimiterState
Returns the current state of outbound rate limiting for a chain.
```solidity
function getCurrentOutboundRateLimiterState(
uint64 remoteChainSelector
) external view returns (RateLimiter.TokenBucket memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to get rate limiter state for |
**Returns**
| Type | Description |
| ------------------------------------------------------------------------------------ | ------------------------------------------ |
| [`RateLimiter.TokenBucket`](/ccip/api-reference/evm/v1.6.1/rate-limiter#tokenbucket) | Current state of the outbound rate limiter |
### getRateLimitAdmin
Returns the current rate limit administrator address.
```solidity
function getRateLimitAdmin() external view returns (address);
```
### getRemotePools
Returns the configured pool addresses for a remote chain.
```solidity
function getRemotePools(uint64 remoteChainSelector) public view returns (bytes[] memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
**Returns**
| Type | Description |
| --------- | ----------------------------------------------- |
| `bytes[]` | Array of encoded pool addresses on remote chain |
### getRemoteToken
Returns the token address on a remote chain.
```solidity
function getRemoteToken(uint64 remoteChainSelector) public view returns (bytes memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
**Returns**
| Type | Description |
| ------- | --------------------------------------------- |
| `bytes` | The encoded token address on the remote chain |
### getRmnProxy
Returns the Risk Management Network proxy address.
```solidity
function getRmnProxy() public view returns (address rmnProxy);
```
**Returns**
| Type | Description |
| --------- | ------------------------------ |
| `address` | The RMN proxy contract address |
### getRouter
Returns the current router address.
```solidity
function getRouter() public view returns (address router);
```
**Returns**
| Type | Description |
| --------- | -------------------------------- |
| `address` | The CCIP router contract address |
### getSupportedChains
Returns all configured chain selectors.
```solidity
function getSupportedChains() public view returns (uint64[] memory);
```
**Returns**
| Type | Description |
| ---------- | ----------------------------------- |
| `uint64[]` | Array of configured chain selectors |
### getToken
Returns the token managed by this pool.
```solidity
function getToken() public view returns (IERC20 token);
```
**Returns**
| Type | Description |
| -------- | -------------------------- |
| `IERC20` | The token contract address |
### getTokenDecimals
Returns the number of decimals for the managed token.
```solidity
function getTokenDecimals() public view virtual returns (uint8 decimals);
```
**Returns**
| Type | Description |
| ------- | ------------------------------------------ |
| `uint8` | The number of decimal places for the token |
### isRemotePool
Verifies if a pool address is configured for a remote chain.
```solidity
function isRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) public view returns (bool);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
| `remotePoolAddress` | `bytes` | The pool address to verify |
**Returns**
| Type | Description |
| ------ | -------------------------------------------- |
| `bool` | True if the pool is configured for the chain |
### isSupportedChain
Checks if a chain is configured in the pool.
```solidity
function isSupportedChain(uint64 remoteChainSelector) public view returns (bool);
```
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the chain is configured in the pool |
### isSupportedToken
Checks if a given token is supported by this pool.
```solidity
function isSupportedToken(address token) public view virtual returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------------------- |
| `token` | `address` | The token address to check |
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the token is supported by this pool |
### lockOrBurn
Locks tokens in the pool for cross-chain transfer.
```solidity
function lockOrBurn(
Pool.LockOrBurnInV1 calldata lockOrBurnIn
) external virtual override returns (Pool.LockOrBurnOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| -------------- | --------------------------------------------------------------------------- | --------------------------------------- |
| `lockOrBurnIn` | [`Pool.LockOrBurnInV1`](/ccip/api-reference/evm/v1.6.1/pool#lockorburninv1) | Input parameters for the lock operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------- | ------------------------------------------------ |
| [`Pool.LockOrBurnOutV1`](/ccip/api-reference/evm/v1.6.1/pool#lockorburnoutv1) | Contains destination token address and pool data |
### releaseOrMint
Releases tokens from the pool to a recipient.
```solidity
function releaseOrMint(
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn
) external virtual override returns (Pool.ReleaseOrMintOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| ----------------- | --------------------------------------------------------------------------------- | ------------------------------------------ |
| `releaseOrMintIn` | [`Pool.ReleaseOrMintInV1`](/ccip/api-reference/evm/v1.6.1/pool#releaseormintinv1) | Input parameters for the release operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------------- | -------------------------------------------------- |
| [`Pool.ReleaseOrMintOutV1`](/ccip/api-reference/evm/v1.6.1/pool#releaseormintoutv1) | Contains the final amount released in local tokens |
### removeRemotePool
Removes a pool address from a remote chain's configuration.
```solidity
function removeRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to remove the pool from |
| `remotePoolAddress` | `bytes` | The address of the pool to remove |
### setChainRateLimiterConfig
Sets the chain rate limiter config.
```solidity
function setChainRateLimiterConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) external;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain selector for which the rate limits apply. |
| `outboundConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.1/rate-limiter#config) | The new outbound rate limiter config, meaning the onRamp rate limits for the given chain. |
| `inboundConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.1/rate-limiter#config) | The new inbound rate limiter config, meaning the offRamp rate limits for the given chain. |
### setChainRateLimiterConfigs
Updates rate limit configurations for multiple chains.
```solidity
function setChainRateLimiterConfigs(
uint64[] calldata remoteChainSelectors,
RateLimiter.Config[] calldata outboundConfigs,
RateLimiter.Config[] calldata inboundConfigs
) external;
```
**Parameters**
| Name | Type | Description |
| ---------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `remoteChainSelectors` | `uint64[]` | The chain selectors to configure |
| `outboundConfigs` | [`RateLimiter.Config[]`](/ccip/api-reference/evm/v1.6.1/rate-limiter#config) | The new outbound rate limiter configs, meaning the onRamp rate limits for the given chains |
| `inboundConfigs` | [`RateLimiter.Config[]`](/ccip/api-reference/evm/v1.6.1/rate-limiter#config) | The new inbound rate limiter configs, meaning the offRamp rate limits for the given chains |
### setRateLimitAdmin
Sets the address authorized to manage rate limits.
```solidity
function setRateLimitAdmin(address rateLimitAdmin) external onlyOwner;
```
### setRouter
Updates the router contract address.
```solidity
function setRouter(address newRouter) public onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ----------- | --------- | ------------------------------- |
| `newRouter` | `address` | The new router contract address |
### supportsInterface
Implements ERC165 interface detection.
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | --------------------------------------------- |
| `bool` | True if the contract implements the interface |
## Rate Limiting
---
# CCIP v1.6.1 BurnMintTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/burn-mint-token-pool
## BurnMintTokenPool
A specialized token pool implementation that handles the minting and burning of third-party tokens using the standard `burn(amount)` function.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/pools/BurnMintTokenPool.sol)
**Inherits:**
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.6.1/burn-mint-token-pool-abstract)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.1/i-type-and-version)
## State Variables
### typeAndVersion
```solidity
string public constant override typeAndVersion = "BurnMintTokenPool 1.6.1";
```
**Returns**
| Type | Description |
| -------- | ------------------------------------------------- |
| `string` | The contract identifier "BurnMintTokenPool 1.6.1" |
## Functions
### _lockOrBurn
Internal function that implements the token burning logic for the `BurnMintTokenPool`.
```solidity
function _lockOrBurn(uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------- |
| `amount` | `uint256` | The number of tokens to burn |
### constructor
```solidity
constructor(
IBurnMintERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ---------------- | ---------------------------------------------------- |
| `token` | `IBurnMintERC20` | The third-party token contract to manage |
| `localTokenDecimals` | `uint8` | The decimal precision for the local token |
| `allowlist` | `address[]` | Initial list of addresses authorized to use the pool |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `router` | `address` | Address of the router contract |
---
# CCIP v1.6.1 BurnMintTokenPoolAbstract Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/burn-mint-token-pool-abstract
## BurnMintTokenPoolAbstract
An abstract contract that implements core token pool functionality for burning and minting operations in cross-chain token transfers.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/pools/BurnMintTokenPoolAbstract.sol)
**Inherits:**
- [TokenPool](/ccip/api-reference/evm/v1.6.1/token-pool)
## Functions
### _releaseOrMint
Internal function that implements the token minting logic for a `BurnMintTokenPool`.
```solidity
function _releaseOrMint(address receiver, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | --------------------------------- |
| `receiver` | `address` | The address to receive the tokens |
| `amount` | `uint256` | The number of tokens to mint |
---
# CCIP v1.6.1 BurnFromMintTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/burn-from-mint-token-pool
## BurnFromMintTokenPool
A specialized token pool contract that manages third-party tokens through minting and burning operations, specifically using the `burnFrom` function.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/pools/BurnFromMintTokenPool.sol)
**Inherits:**
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.6.1/burn-mint-token-pool-abstract)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.1/i-type-and-version)
## State Variables
### typeAndVersion
```solidity
string public constant override typeAndVersion = "BurnFromMintTokenPool 1.6.1";
```
**Returns**
| Type | Description |
| -------- | ----------------------------------------------------- |
| `string` | The contract identifier "BurnFromMintTokenPool 1.6.1" |
## Functions
### _lockOrBurn
Internal function that implements the token burning logic for the `BurnFromMintTokenPool`.
```solidity
function _lockOrBurn(uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------- |
| `amount` | `uint256` | The number of tokens to burn |
### constructor
```solidity
constructor(
IBurnMintERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ---------------- | ------------------------------------------------------ |
| `token` | `IBurnMintERC20` | Address of the token contract to be managed |
| `localTokenDecimals` | `uint8` | Decimal precision of the local token |
| `allowlist` | `address[]` | List of addresses authorized to interact with the pool |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `router` | `address` | Address of the router contract |
---
# CCIP v1.6.1 LockReleaseTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/lock-release-token-pool
## LockReleaseTokenPool
A specialized token pool for managing native tokens through a lock and release mechanism, with support for liquidity management.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/pools/LockReleaseTokenPool.sol)
**Inherits:**
- [TokenPool](/ccip/api-reference/evm/v1.6.1/token-pool)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.1/i-type-and-version)
## Events
### LiquidityTransferred
```solidity
event LiquidityTransferred(address indexed from, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------- |
| `from` | `address` | Yes | The source pool address |
| `amount` | `uint256` | No | The amount of liquidity transferred |
### RebalancerSet
```solidity
event RebalancerSet(address oldRebalancer, address newRebalancer);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------- | --------- | ------- | -------------------------------------- |
| `oldRebalancer` | `address` | No | The address of the previous rebalancer |
| `newRebalancer` | `address` | No | The address of the new rebalancer |
## Errors
### InsufficientLiquidity
```solidity
error InsufficientLiquidity();
```
### LiquidityNotAccepted
```solidity
error LiquidityNotAccepted();
```
## State Variables
### i_acceptLiquidity
```solidity
bool internal immutable i_acceptLiquidity;
```
### s_rebalancer
```solidity
address internal s_rebalancer;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "LockReleaseTokenPool 1.6.1";
```
## Functions
### canAcceptLiquidity
Determines whether the pool can accept external liquidity.
```solidity
function canAcceptLiquidity() external view returns (bool);
```
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the pool accepts external liquidity |
### constructor
```solidity
constructor(
IERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
bool acceptLiquidity,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ----------- | ------------------------------------------- |
| `token` | `IERC20` | The token contract to manage |
| `localTokenDecimals` | `uint8` | The decimal precision for the local token |
| `allowlist` | `address[]` | Initial list of authorized addresses |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `acceptLiquidity` | `bool` | Whether the pool accepts external liquidity |
| `router` | `address` | Address of the router contract |
### getRebalancer
Returns the current rebalancer address.
```solidity
function getRebalancer() external view returns (address);
```
**Returns**
| Type | Description |
| --------- | ------------------------------------- |
| `address` | The current liquidity manager address |
### provideLiquidity
Adds external liquidity to the pool.
```solidity
function provideLiquidity(uint256 amount) external;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------------- |
| `amount` | `uint256` | The amount of liquidity to provide |
### _releaseOrMint
Internal function that implements the token release logic for a `LockReleaseTokenPool`.
```solidity
function _releaseOrMint(address receiver, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | --------------------------------- |
| `receiver` | `address` | The address to receive the tokens |
| `amount` | `uint256` | The number of tokens to release |
### setRebalancer
Updates the rebalancer address.
```solidity
function setRebalancer(address rebalancer) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | --------------------------------- |
| `rebalancer` | `address` | The new rebalancer address to set |
### transferLiquidity
Transfers liquidity from an older pool version.
```solidity
function transferLiquidity(address from, uint256 amount) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `from` | `address` | The address of the source pool |
| `amount` | `uint256` | The amount of liquidity to transfer |
### withdrawLiquidity
Removes liquidity from the pool.
```solidity
function withdrawLiquidity(uint256 amount) external;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `amount` | `uint256` | The amount of liquidity to withdraw |
---
# CCIP v1.6.1 TokenAdminRegistry Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/token-admin-registry
## TokenAdminRegistry
A contract that manages token pool configurations and administrator access for CCIP-enabled tokens.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/tokenAdminRegistry/TokenAdminRegistry.sol)
**Inherits:**
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.1/i-type-and-version)
## Functions
### acceptAdminRole
Accepts the administrator role for a token.
```solidity
function acceptAdminRole(address localToken) external;
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | ------------------------------------------ |
| `localToken` | `address` | The token to accept the administrator role |
### addRegistryModule
Adds a new registry module to the allowed modules list.
```solidity
function addRegistryModule(address module) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------- |
| `module` | `address` | The module to authorize |
### getAllConfiguredTokens
Returns a paginated list of configured tokens.
```solidity
function getAllConfiguredTokens(uint64 startIndex, uint64 maxCount) external view returns (address[] memory tokens);
```
**Parameters**
| Name | Type | Description |
| ------------ | -------- | --------------------------------------------------------- |
| `startIndex` | `uint64` | Starting position in the list (0 for beginning) |
| `maxCount` | `uint64` | Maximum tokens to retrieve (use type(uint64).max for all) |
**Returns**
| Type | Description |
| ----------- | ---------------------------------- |
| `address[]` | List of configured token addresses |
### getPool
Returns the pool address for a specific token.
```solidity
function getPool(address token) external view returns (address);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ------------------ |
| `token` | `address` | The token to query |
**Returns**
| Type | Description |
| --------- | ------------------------ |
| `address` | The token's pool address |
### getPools
Returns pool addresses for multiple tokens.
```solidity
function getPools(address[] calldata tokens) external view returns (address[] memory);
```
**Parameters**
| Name | Type | Description |
| -------- | ----------- | --------------- |
| `tokens` | `address[]` | Tokens to query |
**Returns**
| Type | Description |
| ----------- | ------------------------------------- |
| `address[]` | Array of corresponding pool addresses |
### getTokenConfig
Returns the complete configuration for a token.
```solidity
function getTokenConfig(address token) external view returns (TokenConfig memory);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------- |
| `token` | `address` | Token to query |
**Returns**
| Type | Description |
| ------------- | ---------------------------- |
| `TokenConfig` | Complete token configuration |
### isAdministrator
Checks if an address is the administrator for a token.
```solidity
function isAdministrator(address localToken, address administrator) external view returns (bool);
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | ----------------- |
| `localToken` | `address` | Token to check |
| `administrator` | `address` | Address to verify |
**Returns**
| Type | Description |
| ------ | ---------------------------------------- |
| `bool` | True if address is current administrator |
### isRegistryModule
Checks if an address is an authorized registry module.
```solidity
function isRegistryModule(address module) public view returns (bool);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------- |
| `module` | `address` | Address to verify |
**Returns**
| Type | Description |
| ------ | ------------------------------------ |
| `bool` | True if address is authorized module |
### proposeAdministrator
Proposes an initial administrator for a token.
```solidity
function proposeAdministrator(address localToken, address administrator) external;
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | ------------------------------ |
| `localToken` | `address` | Token to configure |
| `administrator` | `address` | Proposed administrator address |
### removeRegistryModule
Removes a registry module from the allowed modules list.
```solidity
function removeRegistryModule(address module) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------- |
| `module` | `address` | The module to remove |
### setPool
Sets or updates the pool for a token.
```solidity
function setPool(address localToken, address pool) external onlyTokenAdmin(localToken);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | --------------------------------- |
| `localToken` | `address` | Token to configure |
| `pool` | `address` | New pool address (or 0 to delist) |
### transferAdminRole
Initiates transfer of administrator role.
```solidity
function transferAdminRole(address localToken, address newAdmin) external onlyTokenAdmin(localToken);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | -------------------------------------------------------- |
| `localToken` | `address` | The token contract whose admin role is being transferred |
| `newAdmin` | `address` | The proposed new administrator address (or 0 to cancel) |
## Events
### AdministratorTransferRequested
```solidity
event AdministratorTransferRequested(address indexed token, address indexed currentAdmin, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------------- | --------- | ------- | -------------------------------------------------------- |
| `token` | `address` | Yes | The token contract whose admin role is being transferred |
| `currentAdmin` | `address` | Yes | The current administrator address |
| `newAdmin` | `address` | Yes | The proposed new administrator address |
### AdministratorTransferred
```solidity
event AdministratorTransferred(address indexed token, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| ---------- | --------- | ------- | -------------------------------------------------------- |
| `token` | `address` | Yes | The token contract whose admin role has been transferred |
| `newAdmin` | `address` | Yes | The new administrator address |
### PoolSet
```solidity
event PoolSet(address indexed token, address indexed previousPool, address indexed newPool);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------------- | --------- | ------- | ---------------------------------- |
| `token` | `address` | Yes | The token address being configured |
| `previousPool` | `address` | Yes | The previous pool address |
| `newPool` | `address` | Yes | The new pool address |
### RegistryModuleAdded
```solidity
event RegistryModuleAdded(address module);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ------------------------------------------ |
| `module` | `address` | No | The address of the newly authorized module |
### RegistryModuleRemoved
```solidity
event RegistryModuleRemoved(address indexed module);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | --------------------------------- |
| `module` | `address` | Yes | The address of the removed module |
## Errors
### AddressZero
```solidity
error ZeroAddress();
```
### AlreadyRegistered
```solidity
error AlreadyRegistered(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | --------------------------------------------------- |
| `token` | `address` | The token address that already has an administrator |
### InvalidTokenPoolToken
```solidity
error InvalidTokenPoolToken(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | --------------------------------------------------- |
| `token` | `address` | The token address that is not supported by the pool |
### OnlyAdministrator
```solidity
error OnlyAdministrator(address sender, address token);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
| `token` | `address` | The token address being accessed |
### OnlyPendingAdministrator
```solidity
error OnlyPendingAdministrator(address sender, address token);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
| `token` | `address` | The token address being accessed |
### OnlyRegistryModuleOrOwner
```solidity
error OnlyRegistryModuleOrOwner(address sender);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
## Structs
### TokenConfig
Configuration data structure for each token.
```solidity
struct TokenConfig {
address administrator;
address pendingAdministrator;
address tokenPool;
}
```
## State Variables
### s_registryModules
```solidity
EnumerableSet.AddressSet internal s_registryModules;
```
### s_tokenConfig
```solidity
mapping(address token => TokenConfig) internal s_tokenConfig;
```
### s_tokens
```solidity
EnumerableSet.AddressSet internal s_tokens;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "TokenAdminRegistry 1.6.1";
```
---
# CCIP v1.6.1 RegistryModuleOwnerCustom Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/registry-module-owner-custom
## RegistryModuleOwnerCustom
A contract that facilitates token administrator registration through various ownership patterns.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/tokenAdminRegistry/RegistryModuleOwnerCustom.sol)
**Inherits:**
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.1/i-type-and-version)
## Events
### AdministratorRegistered
```solidity
event AdministratorRegistered(address indexed token, address indexed administrator);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------- | --------- | ------- | ------------------------------------ |
| `token` | `address` | Yes | The token contract address |
| `administrator` | `address` | Yes | The registered administrator address |
## Errors
### AddressZero
```solidity
error AddressZero();
```
### CanOnlySelfRegister
```solidity
error CanOnlySelfRegister(address admin, address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------- |
| `admin` | `address` | The expected administrator address |
| `token` | `address` | The token contract address |
### RequiredRoleNotFound
```solidity
error RequiredRoleNotFound(address msgSender, bytes32 role, address token);
```
**Parameters**
| Name | Type | Description |
| ----------- | --------- | ---------------------------- |
| `msgSender` | `address` | The caller's address |
| `role` | `bytes32` | The required role identifier |
| `token` | `address` | The token contract address |
## State Variables
### i_tokenAdminRegistry
```solidity
ITokenAdminRegistry internal immutable i_tokenAdminRegistry;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "RegistryModuleOwnerCustom 1.6.1";
```
## Functions
### constructor
```solidity
constructor(address tokenAdminRegistry);
```
**Parameters**
| Name | Type | Description |
| -------------------- | --------- | ---------------------------------------------- |
| `tokenAdminRegistry` | `address` | The address of the TokenAdminRegistry contract |
### registerAccessControlDefaultAdmin
Registers a token administrator using OpenZeppelin's AccessControl DEFAULT_ADMIN_ROLE.
```solidity
function registerAccessControlDefaultAdmin(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### registerAdminViaGetCCIPAdmin
Registers a token administrator using the `getCCIPAdmin` method.
```solidity
function registerAdminViaGetCCIPAdmin(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### registerAdminViaOwner
Registers a token administrator using the `owner` method.
```solidity
function registerAdminViaOwner(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### _registerAdmin
Internal function to handle administrator registration.
```solidity
function _registerAdmin(address token, address admin) internal;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ------------------------------------------ |
| `token` | `address` | The token contract to register admin for |
| `admin` | `address` | The administrator address being registered |
---
# CCIP v1.6.1 BurnMintERC20 Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/burn-mint-erc20
## BurnMintERC20
An ERC20-compliant token contract that extends the standard functionality with controlled minting and burning capabilities through role-based access control.
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-release/1.4.0/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol)
## Events
### CCIPAdminTransferred
```solidity
event CCIPAdminTransferred(address indexed previousAdmin, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------- | --------- | ------- | ------------------------------ |
| `previousAdmin` | `address` | Yes | The address that held the role |
| `newAdmin` | `address` | Yes | The address receiving the role |
## Errors
### InvalidRecipient
```solidity
error InvalidRecipient(address recipient);
```
### MaxSupplyExceeded
```solidity
error MaxSupplyExceeded(uint256 supplyAfterMint);
```
## State Variables
### BURNER_ROLE
```solidity
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
```
### MINTER_ROLE
```solidity
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
```
## Functions
### _approve
Internal function that manages token spending allowances with built-in safety checks.
```solidity
function _approve(address owner, address spender, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | ------------------------------------------------ |
| `owner` | `address` | The address that currently owns the tokens |
| `spender` | `address` | The address that will be allowed to spend tokens |
| `amount` | `uint256` | The number of tokens to approve for spending |
### burn (with amount)
Allows authorized addresses to burn (destroy) tokens from their own account.
```solidity
function burn(uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------- |
| `amount` | `uint256` | The number of tokens to destroy |
### burn (with account)
Alternative burn function that allows burning tokens from a specified account.
```solidity
function burn(address account, uint256 amount) public virtual override;
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | --------------------------------- |
| `account` | `address` | The account to remove tokens from |
| `amount` | `uint256` | The number of tokens to destroy |
### burnFrom
Burns tokens from a specified account, requiring prior approval.
```solidity
function burnFrom(address account, uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE);
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | --------------------------------- |
| `account` | `address` | The account to remove tokens from |
| `amount` | `uint256` | The number of tokens to destroy |
### constructor
```solidity
constructor(
string memory name,
string memory symbol,
uint8 decimals_,
uint256 maxSupply_,
uint256 preMint
) ERC20(name, symbol);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | -------------------------------------------------- |
| `name` | `string` | The display name of the token |
| `symbol` | `string` | The token's ticker symbol |
| `decimals_` | `uint8` | The number of decimal places for token amounts |
| `maxSupply_` | `uint256` | The maximum allowed token supply (0 for unlimited) |
| `preMint` | `uint256` | The amount of tokens to mint to the deployer |
### decimals
Returns the token's decimal precision.
```solidity
function decimals() public view virtual override returns (uint8);
```
**Returns**
| Type | Description |
| ------- | --------------------------------------------------- |
| `uint8` | The number of decimal places used for token amounts |
### getCCIPAdmin
Retrieves the current CCIP administrator's address.
```solidity
function getCCIPAdmin() external view returns (address);
```
**Returns**
| Type | Description |
| --------- | ---------------------------------------- |
| `address` | The current CCIP administrator's address |
### grantMintAndBurnRoles
Assigns both minting and burning permissions to a single address.
```solidity
function grantMintAndBurnRoles(address burnAndMinter) external;
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | -------------------------------------------------------- |
| `burnAndMinter` | `address` | The address that will receive minting and burning rights |
### maxSupply
Returns the token's maximum supply limit.
```solidity
function maxSupply() public view virtual returns (uint256);
```
**Returns**
| Type | Description |
| --------- | ---------------------------------------------------- |
| `uint256` | The maximum allowed token supply (0 means unlimited) |
### mint
Creates new tokens and assigns them to a specified address.
```solidity
function mint(address account, uint256 amount) external override onlyRole(MINTER_ROLE);
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | -------------------------------------------- |
| `account` | `address` | The address that will receive the new tokens |
| `amount` | `uint256` | The number of new tokens to create |
### setCCIPAdmin
Updates the CCIP administrator role.
```solidity
function setCCIPAdmin(address newAdmin) public onlyRole(DEFAULT_ADMIN_ROLE);
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | ------------------------------------------------------- |
| `newAdmin` | `address` | The address that will become the new CCIP administrator |
### supportsInterface
Determines whether the contract implements a specific interface.
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual override(AccessControl, IERC165) returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | --------------------------------------------------------- |
| `bool` | `true` if the contract implements the specified interface |
### _transfer
Internal function that handles token transfers between addresses.
```solidity
function _transfer(address from, address to, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `from` | `address` | The address sending tokens |
| `to` | `address` | The address receiving tokens |
| `amount` | `uint256` | The number of tokens to transfer |
---
# CCIP v1.6.1 RateLimiter Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/rate-limiter
## RateLimiter
A library implementing the Token Bucket algorithm for rate limiting cross-chain operations.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/contracts-ccip-v1.6.1/chains/evm/contracts/libraries/RateLimiter.sol)
## Events
### TokensConsumed
```solidity
event TokensConsumed(uint256 tokens);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------- |
| `tokens` | `uint256` | The number of tokens consumed |
### ConfigChanged
```solidity
event ConfigChanged(Config config);
```
**Parameters**
| Name | Type | Description |
| -------- | ------------------- | ----------------------------- |
| `config` | [`Config`](#config) | The new configuration applied |
## Errors
### BucketOverfilled
```solidity
error BucketOverfilled();
```
### OnlyCallableByAdminOrOwner
```solidity
error OnlyCallableByAdminOrOwner();
```
### TokenMaxCapacityExceeded
```solidity
error TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress);
```
### TokenRateLimitReached
```solidity
error TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress);
```
### AggregateValueMaxCapacityExceeded
```solidity
error AggregateValueMaxCapacityExceeded(uint256 capacity, uint256 requested);
```
### AggregateValueRateLimitReached
```solidity
error AggregateValueRateLimitReached(uint256 minWaitInSeconds, uint256 available);
```
### InvalidRateLimitRate
```solidity
error InvalidRateLimitRate(Config rateLimiterConfig);
```
### DisabledNonZeroRateLimit
```solidity
error DisabledNonZeroRateLimit(Config config);
```
### RateLimitMustBeDisabled
```solidity
error RateLimitMustBeDisabled();
```
## Structs
### TokenBucket
Represents the state and configuration of a token bucket rate limiter.
```solidity
struct TokenBucket {
uint128 tokens;
uint32 lastUpdated;
bool isEnabled;
uint128 capacity;
uint128 rate;
}
```
### Config
Configuration parameters for the rate limiter.
```solidity
struct Config {
bool isEnabled;
uint128 capacity;
uint128 rate;
}
```
## Functions
### _consume
Removes tokens from the pool, reducing the available rate capacity for subsequent calls.
```solidity
function _consume(TokenBucket storage s_bucket, uint256 requestTokens, address tokenAddress) internal;
```
**Parameters**
| Name | Type | Description |
| --------------- | ----------------------------- | --------------------------------------------------------------- |
| `s_bucket` | [`TokenBucket`](#tokenbucket) | The token bucket to consume from |
| `requestTokens` | `uint256` | The number of tokens to consume |
| `tokenAddress` | `address` | The token address (use address(0) for aggregate value capacity) |
### _currentTokenBucketState
Retrieves the current state of a token bucket, including automatic refill calculations.
```solidity
function _currentTokenBucketState(TokenBucket memory bucket) internal view returns (TokenBucket memory);
```
**Returns**
| Type | Description |
| ----------------------------- | ------------------------------------- |
| [`TokenBucket`](#tokenbucket) | The current state of the token bucket |
### _setTokenBucketConfig
Updates the rate limiter configuration.
```solidity
function _setTokenBucketConfig(TokenBucket storage s_bucket, Config memory config) internal;
```
**Parameters**
| Name | Type | Description |
| ---------- | ----------------------------- | ------------------------------ |
| `s_bucket` | [`TokenBucket`](#tokenbucket) | The token bucket to configure |
| `config` | [`Config`](#config) | The new configuration to apply |
### _validateTokenBucketConfig
Validates rate limiter configuration parameters.
```solidity
function _validateTokenBucketConfig(Config memory config, bool mustBeDisabled) internal pure;
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------------------- | ------------------------------------------ |
| `config` | [`Config`](#config) | The configuration to validate |
| `mustBeDisabled` | `bool` | Whether the configuration must be disabled |
### _calculateRefill
Calculates the number of tokens to add during a refill operation.
```solidity
function _calculateRefill(
uint256 capacity,
uint256 tokens,
uint256 timeDiff,
uint256 rate
) private pure returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | ------------------------------------------- |
| `capacity` | `uint256` | Maximum token capacity |
| `tokens` | `uint256` | Current token balance |
| `timeDiff` | `uint256` | Time elapsed since last refill (in seconds) |
| `rate` | `uint256` | Tokens per second refill rate |
**Returns**
| Type | Description |
| --------- | ---------------------------------- |
| `uint256` | The new token balance after refill |
### _min
Returns the smaller of two numbers.
```solidity
function _min(uint256 a, uint256 b) internal pure returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---- | --------- | ------------- |
| `a` | `uint256` | First number |
| `b` | `uint256` | Second number |
**Returns**
| Type | Description |
| --------- | ------------------------------ |
| `uint256` | The smaller of the two numbers |
---
# CCIP v1.6.1 Error Codes and Messages API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/errors
When invoking the `ccipSend` [function](/ccip/api-reference/evm/v1.6.1/i-router-client#ccipsend), it is possible to encounter various errors. These might be thrown either by the CCIP router or by one of the downstream contracts called by the CCIP router. Below is a compiled list of potential errors you might encounter. Referencing this list will enable you to capture and handle these exceptions gracefully.
## Router Errors
## OnRamp Errors
## FeeQuoter Errors
## Rate Limiter Errors
## Token (ERC20) Errors
## BurnMintERC20 Errors
## Token Pool Errors
---
# CCIP v1.6.1 Ownable2Step Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/ownable-2-step
## Ownable2Step
A minimal contract that implements 2-step ownership transfer and nothing more. It's made to be minimal to reduce the impact of the bytecode size on any contract that inherits from it.
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-release/1.4.0/contracts/src/v0.8/shared/access/Ownable2Step.sol)
## Events
### OwnershipTransferRequested
```solidity
event OwnershipTransferRequested(address indexed from, address indexed to);
```
**Parameters**
| Name | Type | Description |
| ------ | --------- | ------------------------------------- |
| `from` | `address` | Current owner initiating the transfer |
| `to` | `address` | Proposed new owner |
### OwnershipTransferred
```solidity
event OwnershipTransferred(address indexed from, address indexed to);
```
**Parameters**
| Name | Type | Description |
| ------ | --------- | -------------- |
| `from` | `address` | Previous owner |
| `to` | `address` | New owner |
## Errors
### CannotTransferToSelf
```solidity
error CannotTransferToSelf();
```
### MustBeProposedOwner
```solidity
error MustBeProposedOwner();
```
### OnlyCallableByOwner
```solidity
error OnlyCallableByOwner();
```
### OwnerCannotBeZero
```solidity
error OwnerCannotBeZero();
```
## State Variables
### s_owner
The owner is the current owner of the contract.
```solidity
address private s_owner;
```
### s_pendingOwner
The pending owner is the address to which ownership may be transferred.
```solidity
address private s_pendingOwner;
```
## Functions
### acceptOwnership
Allows an ownership transfer to be completed by the recipient.
```solidity
function acceptOwnership() external override;
```
### constructor
Initializes the contract with an owner and optionally a pending owner.
```solidity
constructor(address newOwner, address pendingOwner);
```
**Parameters**
| Name | Type | Description |
| -------------- | --------- | -------------------------------------------------- |
| `newOwner` | `address` | The initial owner of the contract |
| `pendingOwner` | `address` | Optional address to initiate ownership transfer to |
### onlyOwner
Modifier that restricts function access to the contract owner.
```solidity
modifier onlyOwner();
```
### owner
Returns the current owner's address.
```solidity
function owner() public view override returns (address);
```
**Returns**
| Type | Description |
| --------- | -------------------------------- |
| `address` | The address of the current owner |
### transferOwnership
Allows an owner to begin transferring ownership to a new address.
```solidity
function transferOwnership(address to) public override onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ---- | --------- | -------------------------------------------------- |
| `to` | `address` | The address to which ownership will be transferred |
### _validateOwnership
Internal function to validate access control.
```solidity
function _validateOwnership() internal view;
```
---
# CCIP v1.6.1 Ownable2StepMsgSender Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.1/ownable-2-step-msg-sender
## Ownable2StepMsgSender
A contract that facilitates two-step ownership transfer, providing enhanced security for ownership management. This contract extends `Ownable2Step` and automatically sets the deploying address (`msg.sender`) as the initial owner with no pending owner.
**Inherits:**
- [`Ownable2Step`](/ccip/api-reference/evm/v1.6.1/ownable-2-step) - Provides secure two-step ownership transfer functionality
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-release/1.4.0/contracts/src/v0.8/shared/access/Ownable2StepMsgSender.sol)
## Functions
### constructor
Initializes the contract with the deploying address as the owner and no pending owner.
```solidity
constructor() Ownable2Step(msg.sender, address(0));
```
---
# CCIP v1.6.0 API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0
Last Updated: 2025-05-19
## API References
### Core Components
- [CCIPReceiver](/ccip/api-reference/evm/v1.6.0/ccip-receiver) - Base contract for receiving CCIP messages
- [Client](/ccip/api-reference/evm/v1.6.0/client) - Library providing structs and types for building CCIP messages
- [IRouterClient](/ccip/api-reference/evm/v1.6.0/i-router-client) - Interface for sending messages through CCIP
- [Pool](/ccip/api-reference/evm/v1.6.0/pool) - Library providing token pool functions for cross-chain operations
- [RateLimiter](/ccip/api-reference/evm/v1.6.0/rate-limiter) - Contract for managing rate limits on token transfers
- [TypeAndVersion](/ccip/api-reference/evm/v1.6.0/i-type-and-version) - Interface for contract versioning
### Token Pools
- [BurnFromMintTokenPool](/ccip/api-reference/evm/v1.6.0/burn-from-mint-token-pool) - Implementation using `burnFrom(address, amount)` for token burning
- [BurnMintERC20](/ccip/api-reference/evm/v1.6.0/burn-mint-erc20) - Implementation for burning and minting ERC20 tokens
- [BurnMintTokenPool](/ccip/api-reference/evm/v1.6.0/burn-mint-token-pool) - Implementation using `burn(amount)` for token burning
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.6.0/burn-mint-token-pool-abstract) - Abstract contract for burn/mint token handling
- [LockReleaseTokenPool](/ccip/api-reference/evm/v1.6.0/lock-release-token-pool) - Implementation for locking and releasing tokens on their native chain
- [TokenPool](/ccip/api-reference/evm/v1.6.0/token-pool) - Base abstract class defining common functionality for all token pools
### Access Control
- [Ownable2Step](/ccip/api-reference/evm/v1.6.0/ownable-2-step) - Base contract implementing secure two-step ownership transfer
- [Ownable2StepMsgSender](/ccip/api-reference/evm/v1.6.0/ownable-2-step-msg-sender) - Extension of Ownable2Step that sets msg.sender as initial owner
### Registry Components
- [RegistryModuleOwnerCustom](/ccip/api-reference/evm/v1.6.0/registry-module-owner-custom) - Registry module for token admin registration
- [TokenAdminRegistry](/ccip/api-reference/evm/v1.6.0/token-admin-registry) - Contract for storing token pool configurations
### Error Handling
- [Errors](/ccip/api-reference/evm/v1.6.0/errors) - Comprehensive list of CCIP error codes and their descriptions
---
# CCIP v1.6.0 Client Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/client
Last Updated: 2025-01-17
## Client
A library that provides core data structures and utilities for building and handling cross-chain messages in CCIP.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/libraries/Client.sol)
## Structs
### Any2EVMMessage
Structure representing a message received from any chain to an EVM chain.
```solidity
struct Any2EVMMessage {
bytes32 messageId;
uint64 sourceChainSelector;
bytes sender;
bytes data;
EVMTokenAmount[] destTokenAmounts;
}
```
**Properties**
| Name | Type | Description |
| --------------------- | ------------------------------------- | ------------------------------------------------- |
| `messageId` | `bytes32` | Message ID corresponding to ccipSend on source |
| `sourceChainSelector` | `uint64` | Identifier of the source chain |
| `sender` | `bytes` | Sender address (use abi.decode if from EVM chain) |
| `data` | `bytes` | Custom payload from the original message |
| `destTokenAmounts` | [`EVMTokenAmount[]`](#evmtokenamount) | Token amounts in destination chain representation |
### EVM2AnyMessage
Structure for sending a message from an EVM chain to any supported chain.
```solidity
struct EVM2AnyMessage {
bytes receiver;
bytes data;
EVMTokenAmount[] tokenAmounts;
address feeToken;
bytes extraArgs;
}
```
**Properties**
| Name | Type | Description |
| -------------- | ------------------------------------- | --------------------------------------------------- |
| `receiver` | `bytes` | Encoded receiver address for destination EVM chains |
| `data` | `bytes` | Custom payload to send |
| `tokenAmounts` | [`EVMTokenAmount[]`](#evmtokenamount) | Tokens and amounts to transfer |
| `feeToken` | `address` | Token used for fees (address(0) for native tokens) |
| `extraArgs` | `bytes` | Additional arguments encoded with _argsToBytes |
### EVMExtraArgsV1
Structure for V1 extra arguments in cross-chain messages.
```solidity
struct EVMExtraArgsV1 {
uint256 gasLimit;
}
```
**Properties**
| Name | Type | Description |
| ---------- | --------- | -------------------------------------------- |
| `gasLimit` | `uint256` | Gas limit for execution on destination chain |
### GenericExtraArgsV2
Structure for V2 extra arguments in cross-chain messages.
```solidity
struct GenericExtraArgsV2 {
uint256 gasLimit;
bool allowOutOfOrderExecution;
}
```
**Properties**
| Name | Type | Description |
| -------------------------- | --------- | --------------------------------------------- |
| `gasLimit` | `uint256` | Gas limit for execution on destination chain |
| `allowOutOfOrderExecution` | `bool` | Whether messages can be executed in any order |
### SVMExtraArgsV1
Structure for V1 extra arguments specific to Solana VM-based chains.
```solidity
struct SVMExtraArgsV1 {
uint32 computeUnits;
uint64 accountIsWritableBitmap;
bool allowOutOfOrderExecution;
bytes32 tokenReceiver;
bytes32[] accounts;
}
```
**Properties**
| Name | Type | Description |
| -------------------------- | ----------- | ------------------------------------------------------ |
| `computeUnits` | `uint32` | Compute units for execution on Solana |
| `accountIsWritableBitmap` | `uint64` | Bitmap indicating which accounts are writable |
| `allowOutOfOrderExecution` | `bool` | Whether messages can be executed in any order |
| `tokenReceiver` | `bytes32` | Address of the token receiver |
| `accounts` | `bytes32[]` | Additional accounts needed for CCIP receiver execution |
### EVMTokenAmount
Structure representing token amounts in CCIP messages.
```solidity
struct EVMTokenAmount {
address token;
uint256 amount;
}
```
**Properties**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `token` | `address` | Token address on the local chain |
| `amount` | `uint256` | Amount of tokens to transfer |
## State Variables
### EVM_EXTRA_ARGS_V1_TAG
```solidity
bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
```
### GENERIC_EXTRA_ARGS_V2_TAG
```solidity
bytes4 public constant GENERIC_EXTRA_ARGS_V2_TAG = 0x181dcf10;
```
### SVM_EXTRA_ARGS_V1_TAG
```solidity
bytes4 public constant SVM_EXTRA_ARGS_V1_TAG = 0x1f3b3aba;
```
### SVM_EXTRA_ARGS_MAX_ACCOUNTS
```solidity
uint256 public constant SVM_EXTRA_ARGS_MAX_ACCOUNTS = 64;
```
### SVM_TOKEN_TRANSFER_DATA_OVERHEAD
```solidity
uint256 public constant SVM_TOKEN_TRANSFER_DATA_OVERHEAD = (4 + 32) // source_pool
+ 32 // token_address
+ 4 // gas_amount
+ 4 // extra_data overhead
+ 32 // amount
+ 32 // size of the token lookup table account
+ 32 // token-related accounts in the lookup table, over-estimated to 32, typically between 11 - 13
+ 32 // token account belonging to the token receiver, e.g ATA, not included in the token lookup table
+ 32 // per-chain token pool config, not included in the token lookup table
+ 32 // per-chain token billing config, not always included in the token lookup table
+ 32; // OffRamp pool signer PDA, not included in the token lookup table
```
### SVM_MESSAGING_ACCOUNTS_OVERHEAD
```solidity
uint256 public constant SVM_MESSAGING_ACCOUNTS_OVERHEAD = 2;
```
### SVM_ACCOUNT_BYTE_SIZE
```solidity
uint256 public constant SVM_ACCOUNT_BYTE_SIZE = 32;
```
## Functions
### _argsToBytes (V1)
Encodes EVMExtraArgsV1 into bytes for message transmission.
```solidity
function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ----------------------------------- | -------------------------------- |
| `extraArgs` | [`EVMExtraArgsV1`](#evmextraargsv1) | The V1 extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
### _argsToBytes (V2)
Encodes GenericExtraArgsV2 into bytes for message transmission.
```solidity
function _argsToBytes(GenericExtraArgsV2 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ------------------------------------------- | ---------------------------------------- |
| `extraArgs` | [`GenericExtraArgsV2`](#genericextraargsv2) | The V2 generic extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
### _svmArgsToBytes
Encodes SVMExtraArgsV1 into bytes for message transmission.
```solidity
function _svmArgsToBytes(SVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ----------------------------------- | --------------------------------- |
| `extraArgs` | [`SVMExtraArgsV1`](#svmextraargsv1) | The SVM extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
---
# CCIP v1.6.0 ITypeAndVersion Interface API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/i-type-and-version
## ITypeAndVersion
An interface that provides type and version information for contracts.
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-release/1.4.0/contracts/src/v0.8/shared/interfaces/ITypeAndVersion.sol)
## Functions
### typeAndVersion
Returns the type and version of the contract.
```solidity
function typeAndVersion() external pure returns (string memory);
```
**Returns**
| Type | Description |
| -------- | ------------------------------------- |
| `string` | The type and version of the contract. |
---
# CCIP v1.6.0 IRouterClient API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/i-router-client
Last Updated: 2025-05-19
## IRouterClient
The IRouterClient interface provides the core functionality for sending cross-chain messages through CCIP (Chainlink Cross-Chain Interoperability Protocol).
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/interfaces/IRouterClient.sol)
## Errors
### InsufficientFeeTokenAmount
Thrown when the provided fee token amount is insufficient for the message delivery.
```solidity
error InsufficientFeeTokenAmount();
```
### InvalidMsgValue
Thrown when the provided msg.value is invalid for the operation.
```solidity
error InvalidMsgValue();
```
### UnsupportedDestinationChain
Thrown when attempting to send a message to an unsupported destination chain.
```solidity
error UnsupportedDestinationChain(uint64 destChainSelector);
```
## Functions
### ccipSend
Sends a message to the destination chain through CCIP.
```solidity
function ccipSend(
uint64 destinationChainSelector,
Client.EVM2AnyMessage calldata message
) external payable returns (bytes32);
```
**Parameters**
| Name | Type | Description |
| -------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `destinationChainSelector` | `uint64` | The destination chain ID |
| `message` | [`Client.EVM2AnyMessage`](/ccip/api-reference/evm/v1.6.0/client#evm2anymessage) | The cross-chain CCIP message including data and/or tokens |
**Returns**
| Name | Type | Description |
| ----------- | --------- | -------------- |
| `messageId` | `bytes32` | The message ID |
### getFee
Gets the fee required for sending a CCIP message to the destination chain.
```solidity
function getFee(
uint64 destinationChainSelector,
Client.EVM2AnyMessage memory message
) external view returns (uint256 fee);
```
**Parameters**
| Name | Type | Description |
| -------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `destinationChainSelector` | `uint64` | The destination chainSelector |
| `message` | [`Client.EVM2AnyMessage`](/ccip/api-reference/evm/v1.6.0/client#evm2anymessage) | The cross-chain CCIP message including data and/or tokens |
**Returns**
| Name | Type | Description |
| ----- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `fee` | `uint256` | Returns execution fee for the message delivery to destination chain, denominated in the feeToken specified in the message. |
### isChainSupported
Checks if the given chain ID is supported for sending/receiving.
```solidity
function isChainSupported(uint64 destChainSelector) external view returns (bool supported);
```
**Parameters**
| Name | Type | Description |
| ------------------- | -------- | ------------------- |
| `destChainSelector` | `uint64` | The chain to check. |
**Returns**
| Name | Type | Description |
| ----------- | ------ | ----------------------------------------- |
| `supported` | `bool` | is true if it is supported, false if not. |
---
# CCIP v1.6.0 CCIPReceiver API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/ccip-receiver
Last Updated: 2025-05-19
## CCIPReceiver
An abstract base contract that provides core functionality for CCIP-enabled applications to receive cross-chain messages.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/applications/CCIPReceiver.sol)
## Errors
### InvalidRouter
```solidity
error InvalidRouter(address router);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------- |
| `router` | `address` | The invalid router address |
## State Variables
### i_ccipRouter
```solidity
address internal immutable i_ccipRouter;
```
## Modifiers
### onlyRouter
```solidity
modifier onlyRouter();
```
## Functions
### ccipReceive
Processes incoming CCIP messages from the router.
```solidity
function ccipReceive(Client.Any2EVMMessage calldata message) external virtual override onlyRouter;
```
**Parameters**
| Name | Type | Description |
| --------- | ------------------------------------------------------------------------------- | ---------------- |
| `message` | [`Client.Any2EVMMessage`](/ccip/api-reference/evm/v1.6.0/client#any2evmmessage) | The CCIP message |
### constructor
```solidity
constructor(address router);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `router` | `address` | The CCIP router contract address |
### getRouter
Returns the address of the current CCIP router.
```solidity
function getRouter() public view virtual returns (address);
```
**Returns**
| Type | Description |
| --------- | ------------------------------- |
| `address` | The current CCIP router address |
### supportsInterface
Determines whether the contract implements specific interfaces.
```solidity
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | ---------------------------------- |
| `bool` | True if the interface is supported |
### _ccipReceive
Internal function to be implemented by derived contracts for custom message handling.
```solidity
function _ccipReceive(Client.Any2EVMMessage memory message) internal virtual;
```
**Parameters**
| Name | Type | Description |
| --------- | ------------------------------------------------------------------------------- | --------------------------- |
| `message` | [`Client.Any2EVMMessage`](/ccip/api-reference/evm/v1.6.0/client#any2evmmessage) | The message to be processed |
---
# CCIP v1.6.0 Pool Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/pool
## Pool
A library that provides core data structures and constants for token pool operations in cross-chain transfers.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/libraries/Pool.sol)
## Structs
### LockOrBurnInV1
Input parameters for locking or burning tokens in cross-chain transfers.
```solidity
struct LockOrBurnInV1 {
bytes receiver;
uint64 remoteChainSelector;
address originalSender;
uint256 amount;
address localToken;
}
```
### LockOrBurnOutV1
Output data from a lock or burn operation.
```solidity
struct LockOrBurnOutV1 {
bytes destTokenAddress;
bytes destPoolData;
}
```
### ReleaseOrMintInV1
Input parameters for releasing or minting tokens in cross-chain transfers.
```solidity
struct ReleaseOrMintInV1 {
bytes originalSender;
uint64 remoteChainSelector;
address receiver;
uint256 amount;
address localToken;
bytes sourcePoolAddress;
bytes sourcePoolData;
bytes offchainTokenData;
}
```
### ReleaseOrMintOutV1
Output data from a release or mint operation.
```solidity
struct ReleaseOrMintOutV1 {
uint256 destinationAmount;
}
```
## State Variables
### CCIP_POOL_V1
```solidity
bytes4 public constant CCIP_POOL_V1 = 0xaff2afbf;
```
### CCIP_POOL_V1_RET_BYTES
```solidity
uint16 public constant CCIP_POOL_V1_RET_BYTES = 32;
```
### CCIP_LOCK_OR_BURN_V1_RET_BYTES
```solidity
uint32 public constant CCIP_LOCK_OR_BURN_V1_RET_BYTES = 32;
```
---
# CCIP v1.6.0 TokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/token-pool
## TokenPool
An abstract contract that provides base functionality for managing cross-chain token operations in CCIP. It handles token decimals across different chains, rate limiting, and access control.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/pools/TokenPool.sol)
**Inherits:**
- [Ownable2StepMsgSender](/ccip/api-reference/evm/v1.6.0/ownable-2-step-msg-sender)
## Events
### AllowListAdd
Emitted when an address is added to the allowlist via [`applyAllowListUpdates`](#applyallowlistupdates).
```solidity
event AllowListAdd(address sender);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ------------------------------------------- |
| `sender` | `address` | No | The address that was added to the allowlist |
### AllowListRemove
Emitted when an address is removed from the allowlist via [`applyAllowListUpdates`](#applyallowlistupdates).
```solidity
event AllowListRemove(address sender);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------------------- |
| `sender` | `address` | No | The address that was removed from the allowlist |
### Burned
Emitted when tokens are burned by the pool.
```solidity
event Burned(address indexed sender, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------------- |
| `sender` | `address` | Yes | The address initiating the burn operation |
| `amount` | `uint256` | No | The amount of tokens burned |
### ChainAdded
Emitted when a new chain is configured in the pool via [`applyChainUpdates`](#applychainupdates).
```solidity
event ChainAdded(
uint64 remoteChainSelector,
bytes remoteToken,
RateLimiter.Config outboundRateLimiterConfig,
RateLimiter.Config inboundRateLimiterConfig
);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------------- | -------------------------------------------------------------------------- | ------- | ----------------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the newly added chain |
| `remoteToken` | `bytes` | No | The token address on the remote chain |
| `outboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.0/rate-limiter#config) | No | Rate limit configuration for outbound transfers |
| `inboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.0/rate-limiter#config) | No | Rate limit configuration for inbound transfers |
### ChainConfigured
Emitted when a chain's configuration is updated via [`setChainRateLimiterConfig`](#setchainratelimiterconfig) or [`setChainRateLimiterConfigs`](#setchainratelimiterconfigs).
```solidity
event ChainConfigured(
uint64 remoteChainSelector,
RateLimiter.Config outboundRateLimiterConfig,
RateLimiter.Config inboundRateLimiterConfig
);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------------- | -------------------------------------------------------------------------- | ------- | ------------------------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the chain being configured |
| `outboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.0/rate-limiter#config) | No | Updated rate limit configuration for outbound transfers |
| `inboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.0/rate-limiter#config) | No | Updated rate limit configuration for inbound transfers |
### ChainRemoved
Emitted when a chain is removed from the pool's configuration via [`applyChainUpdates`](#applychainupdates).
```solidity
event ChainRemoved(uint64 remoteChainSelector);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | ----------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the chain being removed |
### Locked
Emitted when tokens are locked by the pool.
```solidity
event Locked(address indexed sender, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------------- |
| `sender` | `address` | Yes | The address initiating the lock operation |
| `amount` | `uint256` | No | The amount of tokens locked |
### Minted
Emitted when tokens are minted by the pool.
```solidity
event Minted(address indexed sender, address indexed recipient, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | ----------------------------------------- |
| `sender` | `address` | Yes | The address initiating the mint operation |
| `recipient` | `address` | Yes | The address receiving the minted tokens |
| `amount` | `uint256` | No | The amount of tokens minted |
### RemotePoolAdded
Emitted when a new remote pool is added via [`addRemotePool`](#addremotepool) or [`applyChainUpdates`](#applychainupdates).
```solidity
event RemotePoolAdded(uint64 indexed remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | -------------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The identifier of the chain for the new pool |
| `remotePoolAddress` | `bytes` | No | The address of the newly added pool |
### Released
Emitted when tokens are released by the pool.
```solidity
event Released(address indexed sender, address indexed recipient, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | -------------------------------------------- |
| `sender` | `address` | Yes | The address initiating the release operation |
| `recipient` | `address` | Yes | The address receiving the released tokens |
| `amount` | `uint256` | No | The amount of tokens released |
### RemotePoolRemoved
Emitted when a remote pool is removed via [`removeRemotePool`](#removeremotepool).
```solidity
event RemotePoolRemoved(uint64 indexed remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | ---------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The identifier of the chain for the pool |
| `remotePoolAddress` | `bytes` | No | The address of the removed pool |
### RouterUpdated
Emitted when the router address is updated via [`setRouter`](#setrouter).
```solidity
event RouterUpdated(address oldRouter, address newRouter);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | ------------------------------------ |
| `oldRouter` | `address` | No | The previous router contract address |
| `newRouter` | `address` | No | The new router contract address |
### RateLimitAdminSet
Emitted when the rate limit administrator is changed via [`setRateLimitAdmin`](#setratelimitadmin).
```solidity
event RateLimitAdminSet(address rateLimitAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| ---------------- | --------- | ------- | ---------------------------------------- |
| `rateLimitAdmin` | `address` | No | The new rate limit administrator address |
## Errors
### AllowListNotEnabled
```solidity
error AllowListNotEnabled();
```
### CallerIsNotARampOnRouter
```solidity
error CallerIsNotARampOnRouter(address caller);
```
### ChainAlreadyExists
```solidity
error ChainAlreadyExists(uint64 chainSelector);
```
**Parameters**
| Name | Type | Description |
| --------------- | -------- | --------------------------------------------- |
| `chainSelector` | `uint64` | The selector of the chain that already exists |
### ChainNotAllowed
```solidity
error ChainNotAllowed(uint64 remoteChainSelector);
```
### CursedByRMN
```solidity
error CursedByRMN();
```
### InvalidDecimalArgs
```solidity
error InvalidDecimalArgs(uint8 expected, uint8 actual);
```
**Parameters**
| Name | Type | Description |
| ---------- | ------- | -------------------------------------- |
| `expected` | `uint8` | The expected number of decimals |
| `actual` | `uint8` | The actual number of decimals provided |
### InvalidRemoteChainDecimals
```solidity
error InvalidRemoteChainDecimals(bytes sourcePoolData);
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------- | -------------------------------------- |
| `sourcePoolData` | `bytes` | The invalid decimal configuration data |
### InvalidRemotePoolForChain
```solidity
error InvalidRemotePoolForChain(uint64 remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | -------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector being queried |
| `remotePoolAddress` | `bytes` | The invalid pool address |
### InvalidSourcePoolAddress
```solidity
error InvalidSourcePoolAddress(bytes sourcePoolAddress);
```
### InvalidToken
```solidity
error InvalidToken(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------------------------- |
| `token` | `address` | The address of the invalid token |
### MismatchedArrayLengths
```solidity
error MismatchedArrayLengths();
```
### NonExistentChain
```solidity
error NonExistentChain(uint64 remoteChainSelector);
```
### OverflowDetected
```solidity
error OverflowDetected(uint8 remoteDecimals, uint8 localDecimals, uint256 remoteAmount);
```
**Parameters**
| Name | Type | Description |
| ---------------- | --------- | ----------------------------------- |
| `remoteDecimals` | `uint8` | The decimals on the remote chain |
| `localDecimals` | `uint8` | The decimals on the local chain |
| `remoteAmount` | `uint256` | The amount that caused the overflow |
### PoolAlreadyAdded
```solidity
error PoolAlreadyAdded(uint64 remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ---------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector where the pool exists |
| `remotePoolAddress` | `bytes` | The address of the already existing pool |
### SenderNotAllowed
```solidity
error SenderNotAllowed(address sender);
```
### Unauthorized
```solidity
error Unauthorized(address caller);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------------- |
| `caller` | `address` | The address that attempted the action |
### ZeroAddressNotAllowed
```solidity
error ZeroAddressNotAllowed();
```
## Structs
### ChainUpdate
Configuration data for adding or updating a chain.
```solidity
struct ChainUpdate {
uint64 remoteChainSelector;
bytes[] remotePoolAddresses;
bytes remoteTokenAddress;
RateLimiter.Config outboundRateLimiterConfig;
RateLimiter.Config inboundRateLimiterConfig;
}
```
### RemoteChainConfig
Internal configuration for a remote chain.
```solidity
struct RemoteChainConfig {
RateLimiter.TokenBucket outboundRateLimiterConfig;
RateLimiter.TokenBucket inboundRateLimiterConfig;
bytes remoteTokenAddress;
EnumerableSet.Bytes32Set remotePools;
}
```
## State Variables
### i_token
The token managed by this pool. Currently supports one token per pool.
```solidity
IERC20 internal immutable i_token;
```
### i_tokenDecimals
The number of decimals for the managed token.
```solidity
uint8 internal immutable i_tokenDecimals;
```
### i_rmnProxy
The Risk Management Network (RMN) proxy address.
```solidity
address internal immutable i_rmnProxy;
```
### i_allowlistEnabled
Flag indicating if the pool uses access control.
```solidity
bool internal immutable i_allowlistEnabled;
```
### s_allowlist
Set of addresses authorized to initiate cross-chain operations.
```solidity
EnumerableSet.AddressSet internal s_allowlist;
```
### s_router
The CCIP Router contract address.
```solidity
IRouter internal s_router;
```
### s_remoteChainSelectors
Set of authorized chain selectors for cross-chain operations.
```solidity
EnumerableSet.UintSet internal s_remoteChainSelectors;
```
### s_remoteChainConfigs
Configuration for each remote chain, including rate limits and token details.
```solidity
mapping(uint64 remoteChainSelector => RemoteChainConfig) internal s_remoteChainConfigs;
```
### s_remotePoolAddresses
Maps hashed pool addresses to their original form for verification.
```solidity
mapping(bytes32 poolAddressHash => bytes poolAddress) internal s_remotePoolAddresses;
```
### s_rateLimitAdmin
The address authorized to manage rate limits.
```solidity
address internal s_rateLimitAdmin;
```
## Functions
### _applyAllowListUpdates
Internal version of applyAllowListUpdates to allow for reuse in the constructor.
```solidity
function _applyAllowListUpdates(address[] memory removes, address[] memory adds) internal;
```
**Parameters**
| Name | Type | Description |
| --------- | ----------- | ----------------------------------------------- |
| `removes` | `address[]` | Array of addresses to remove from the allowlist |
| `adds` | `address[]` | Array of addresses to add to the allowlist |
### _calculateLocalAmount
Calculates the local amount based on the remote amount and decimals.
*This function protects against overflows. If there is a transaction that hits the overflow check, it is
probably incorrect as that means the amount cannot be represented on this chain. If the local decimals have been
wrongly configured, the token developer could redeploy the pool with the correct decimals and manually re-execute the
CCIP tx to fix the issue.*
```solidity
function _calculateLocalAmount(uint256 remoteAmount, uint8 remoteDecimals) internal view virtual returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---------------- | --------- | ---------------------------------------------- |
| `remoteAmount` | `uint256` | The amount on the remote chain. |
| `remoteDecimals` | `uint8` | The decimals of the token on the remote chain. |
**Returns**
| Name | Type | Description |
| -------- | --------- | ----------------- |
| `` | `uint256` | The local amount. |
### _checkAllowList
Internal function to verify if a sender is authorized when allowlist is enabled.
```solidity
function _checkAllowList(address sender) internal view;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `sender` | `address` | The address to check for permission |
### _consumeInboundRateLimit
Internal function to consume rate limiting capacity for incoming transfers.
```solidity
function _consumeInboundRateLimit(uint64 remoteChainSelector, uint256 amount) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | --------- | --------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector for the source chain |
| `amount` | `uint256` | The amount of tokens being transferred |
### _consumeOutboundRateLimit
Internal function to consume rate limiting capacity for outgoing transfers.
```solidity
function _consumeOutboundRateLimit(uint64 remoteChainSelector, uint256 amount) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | --------- | -------------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector for the destination chain |
| `amount` | `uint256` | The amount of tokens being transferred |
### _encodeLocalDecimals
Internal function to encode the local token's decimals for cross-chain communication.
```solidity
function _encodeLocalDecimals() internal view virtual returns (bytes memory);
```
**Returns**
| Type | Description |
| ------- | --------------------------------------------- |
| `bytes` | ABI-encoded decimal places of the local token |
### _onlyOffRamp
Checks whether remote chain selector is configured on this contract, and if the msg.sender
is a permissioned offRamp for the given chain on the Router.
```solidity
function _onlyOffRamp(uint64 remoteChainSelector) internal view;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to validate authorization for |
### _onlyOnRamp
Checks whether remote chain selector is configured on this contract, and if the msg.sender
is a permissioned onRamp for the given chain on the Router.
```solidity
function _onlyOnRamp(uint64 remoteChainSelector) internal view;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to validate authorization for |
### _parseRemoteDecimals
Internal function to decode the decimal configuration received from a remote chain.
```solidity
function _parseRemoteDecimals(bytes memory sourcePoolData) internal view virtual returns (uint8);
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------- | -------------------------------------- |
| `sourcePoolData` | `bytes` | The encoded decimal configuration data |
**Returns**
| Type | Description |
| ------- | ----------------------------------------------- |
| `uint8` | The number of decimals used on the remote chain |
### _setRateLimitConfig
Internal function to update rate limit configuration for a chain.
```solidity
function _setRateLimitConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------------------- | ----------------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector to configure |
| `outboundConfig` | `RateLimiter.Config` | Rate limit configuration for outgoing transfers |
| `inboundConfig` | `RateLimiter.Config` | Rate limit configuration for incoming transfers |
### _setRemotePool
Internal function to add a pool address to the allowed remote token pools for a chain. Called during chain configuration and when adding individual remote pools.
```solidity
function _setRemotePool(uint64 remoteChainSelector, bytes memory remotePoolAddress) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to add the pool for |
| `remotePoolAddress` | `bytes` | The address of the remote pool (encoded to support non-EVM chains) |
### _validateLockOrBurn
Internal function to validate lock or burn operations.
```solidity
function _validateLockOrBurn(Pool.LockOrBurnInV1 calldata lockOrBurnIn) internal;
```
### _validateReleaseOrMint
Internal function to validate release or mint operations.
```solidity
function _validateReleaseOrMint(Pool.ReleaseOrMintInV1 calldata releaseOrMintIn) internal;
```
### addRemotePool
Adds a new pool address for a remote chain.
```solidity
function addRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) external onlyOwner;
```
### applyAllowListUpdates
Apply updates to the allow list.
```solidity
function applyAllowListUpdates(address[] calldata removes, address[] calldata adds) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| --------- | ----------- | ---------------------------- |
| `removes` | `address[]` | The addresses to be removed. |
| `adds` | `address[]` | The addresses to be added. |
### applyChainUpdates
Updates chain configurations in bulk.
```solidity
function applyChainUpdates(
uint64[] calldata remoteChainSelectorsToRemove,
ChainUpdate[] calldata chainsToAdd
) external virtual onlyOwner;
```
### constructor
```solidity
constructor(IERC20 token, uint8 localTokenDecimals, address[] memory allowlist, address rmnProxy, address router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ----------- | -------------------------------------------- |
| `token` | `IERC20` | The token to be managed by this pool |
| `localTokenDecimals` | `uint8` | The token's decimal places on this chain |
| `allowlist` | `address[]` | Initial set of authorized addresses (if any) |
| `rmnProxy` | `address` | The Risk Management Network proxy address |
| `router` | `address` | The CCIP Router contract address |
### getAllowList
Gets the allowed addresses.
```solidity
function getAllowList() external view returns (address[] memory);
```
**Returns**
| Name | Type | Description |
| -------- | ----------- | ---------------------- |
| `` | `address[]` | The allowed addresses. |
### getAllowListEnabled
Returns whether allowlist functionality is active.
```solidity
function getAllowListEnabled() external view returns (bool);
```
**Returns**
| Name | Type | Description |
| -------- | ------ | ------------------------------ |
| `` | `bool` | true is enabled, false if not. |
### getCurrentInboundRateLimiterState
Returns the current state of inbound rate limiting for a chain.
```solidity
function getCurrentInboundRateLimiterState(
uint64 remoteChainSelector
) external view returns (RateLimiter.TokenBucket memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to get rate limiter state for |
**Returns**
| Type | Description |
| ------------------------------------------------------------------------------------ | ----------------------------------------- |
| [`RateLimiter.TokenBucket`](/ccip/api-reference/evm/v1.6.0/rate-limiter#tokenbucket) | Current state of the inbound rate limiter |
### getCurrentOutboundRateLimiterState
Returns the current state of outbound rate limiting for a chain.
```solidity
function getCurrentOutboundRateLimiterState(
uint64 remoteChainSelector
) external view returns (RateLimiter.TokenBucket memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to get rate limiter state for |
**Returns**
| Type | Description |
| ------------------------------------------------------------------------------------ | ------------------------------------------ |
| [`RateLimiter.TokenBucket`](/ccip/api-reference/evm/v1.6.0/rate-limiter#tokenbucket) | Current state of the outbound rate limiter |
### getRateLimitAdmin
Returns the current rate limit administrator address.
```solidity
function getRateLimitAdmin() external view returns (address);
```
### getRemotePools
Returns the configured pool addresses for a remote chain.
```solidity
function getRemotePools(uint64 remoteChainSelector) public view returns (bytes[] memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
**Returns**
| Type | Description |
| --------- | ----------------------------------------------- |
| `bytes[]` | Array of encoded pool addresses on remote chain |
### getRemoteToken
Returns the token address on a remote chain.
```solidity
function getRemoteToken(uint64 remoteChainSelector) public view returns (bytes memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
**Returns**
| Type | Description |
| ------- | --------------------------------------------- |
| `bytes` | The encoded token address on the remote chain |
### getRmnProxy
Returns the Risk Management Network proxy address.
```solidity
function getRmnProxy() public view returns (address rmnProxy);
```
**Returns**
| Type | Description |
| --------- | ------------------------------ |
| `address` | The RMN proxy contract address |
### getRouter
Returns the current router address.
```solidity
function getRouter() public view returns (address router);
```
**Returns**
| Type | Description |
| --------- | -------------------------------- |
| `address` | The CCIP router contract address |
### getSupportedChains
Returns all configured chain selectors.
```solidity
function getSupportedChains() public view returns (uint64[] memory);
```
**Returns**
| Type | Description |
| ---------- | ----------------------------------- |
| `uint64[]` | Array of configured chain selectors |
### getToken
Returns the token managed by this pool.
```solidity
function getToken() public view returns (IERC20 token);
```
**Returns**
| Type | Description |
| -------- | -------------------------- |
| `IERC20` | The token contract address |
### getTokenDecimals
Returns the number of decimals for the managed token.
```solidity
function getTokenDecimals() public view virtual returns (uint8 decimals);
```
**Returns**
| Type | Description |
| ------- | ------------------------------------------ |
| `uint8` | The number of decimal places for the token |
### isRemotePool
Verifies if a pool address is configured for a remote chain.
```solidity
function isRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) public view returns (bool);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
| `remotePoolAddress` | `bytes` | The pool address to verify |
**Returns**
| Type | Description |
| ------ | -------------------------------------------- |
| `bool` | True if the pool is configured for the chain |
### isSupportedChain
Checks if a chain is configured in the pool.
```solidity
function isSupportedChain(uint64 remoteChainSelector) public view returns (bool);
```
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the chain is configured in the pool |
### isSupportedToken
Checks if a given token is supported by this pool.
```solidity
function isSupportedToken(address token) public view virtual returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------------------- |
| `token` | `address` | The token address to check |
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the token is supported by this pool |
### removeRemotePool
Removes a pool address from a remote chain's configuration.
```solidity
function removeRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to remove the pool from |
| `remotePoolAddress` | `bytes` | The address of the pool to remove |
### setChainRateLimiterConfig
Sets the chain rate limiter config.
```solidity
function setChainRateLimiterConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) external;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain selector for which the rate limits apply. |
| `outboundConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.0/rate-limiter#config) | The new outbound rate limiter config, meaning the onRamp rate limits for the given chain. |
| `inboundConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.6.0/rate-limiter#config) | The new inbound rate limiter config, meaning the offRamp rate limits for the given chain. |
### setChainRateLimiterConfigs
Updates rate limit configurations for multiple chains.
```solidity
function setChainRateLimiterConfigs(
uint64[] calldata remoteChainSelectors,
RateLimiter.Config[] calldata outboundConfigs,
RateLimiter.Config[] calldata inboundConfigs
) external;
```
**Parameters**
| Name | Type | Description |
| ---------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `remoteChainSelectors` | `uint64[]` | The chain selectors to configure |
| `outboundConfigs` | [`RateLimiter.Config[]`](/ccip/api-reference/evm/v1.6.0/rate-limiter#config) | The new outbound rate limiter configs, meaning the onRamp rate limits for the given chains |
| `inboundConfigs` | [`RateLimiter.Config[]`](/ccip/api-reference/evm/v1.6.0/rate-limiter#config) | The new inbound rate limiter configs, meaning the offRamp rate limits for the given chains |
### setRateLimitAdmin
Sets the address authorized to manage rate limits.
```solidity
function setRateLimitAdmin(address rateLimitAdmin) external onlyOwner;
```
### setRouter
Updates the router contract address.
```solidity
function setRouter(address newRouter) public onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ----------- | --------- | ------------------------------- |
| `newRouter` | `address` | The new router contract address |
### supportsInterface
Implements ERC165 interface detection.
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | --------------------------------------------- |
| `bool` | True if the contract implements the interface |
## Rate Limiting
---
# CCIP v1.6.0 BurnMintTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/burn-mint-token-pool
## BurnMintTokenPool
A specialized token pool implementation that handles the minting and burning of third-party tokens using the standard `burn(amount)` function.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/pools/BurnMintTokenPool.sol)
**Inherits:**
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.6.0/burn-mint-token-pool-abstract)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.0/i-type-and-version)
## State Variables
### typeAndVersion
```solidity
string public constant override typeAndVersion = "BurnMintTokenPool 1.5.1";
```
**Returns**
| Type | Description |
| -------- | ------------------------------------------------- |
| `string` | The contract identifier "BurnMintTokenPool 1.5.1" |
## Functions
### _burn
Internal function that executes the token burning operation using the standard burn interface.
```solidity
function _burn(uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------- |
| `amount` | `uint256` | The number of tokens to burn |
### constructor
```solidity
constructor(
IBurnMintERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ---------------- | ---------------------------------------------------- |
| `token` | `IBurnMintERC20` | The third-party token contract to manage |
| `localTokenDecimals` | `uint8` | The decimal precision for the local token |
| `allowlist` | `address[]` | Initial list of addresses authorized to use the pool |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `router` | `address` | Address of the router contract |
---
# CCIP v1.6.0 BurnMintTokenPoolAbstract Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/burn-mint-token-pool-abstract
## BurnMintTokenPoolAbstract
An abstract contract that implements core token pool functionality for burning and minting operations in cross-chain token transfers.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/pools/BurnMintTokenPoolAbstract.sol)
**Inherits:**
- [TokenPool](/ccip/api-reference/evm/v1.6.0/token-pool)
## Events
### Burned
```solidity
event Burned(address indexed sender, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------------- |
| `sender` | `address` | Yes | The address initiating the burn operation |
| `amount` | `uint256` | No | The number of tokens burned |
### Minted
```solidity
event Minted(address indexed sender, address indexed recipient, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | ----------------------------------------- |
| `sender` | `address` | Yes | The address initiating the mint operation |
| `recipient` | `address` | Yes | The address receiving the minted tokens |
| `amount` | `uint256` | No | The number of tokens minted |
## Functions
### _burn
Internal function that executes the token burning operation.
```solidity
function _burn(uint256 amount) internal virtual;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------- |
| `amount` | `uint256` | The number of tokens to burn |
### lockOrBurn
Burns tokens in the pool during a cross-chain transfer.
```solidity
function lockOrBurn(
Pool.LockOrBurnInV1 calldata lockOrBurnIn
) external virtual override returns (Pool.LockOrBurnOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| -------------- | --------------------------------------------------------------------------- | --------------------------------------- |
| `lockOrBurnIn` | [`Pool.LockOrBurnInV1`](/ccip/api-reference/evm/v1.6.0/pool#lockorburninv1) | Input parameters for the burn operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------- | ------------------------------------------------ |
| [`Pool.LockOrBurnOutV1`](/ccip/api-reference/evm/v1.6.0/pool#lockorburnoutv1) | Contains destination token address and pool data |
### releaseOrMint
Mints new tokens to a recipient during a cross-chain transfer.
```solidity
function releaseOrMint(
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn
) external virtual override returns (Pool.ReleaseOrMintOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| ----------------- | --------------------------------------------------------------------------------- | --------------------------------------- |
| `releaseOrMintIn` | [`Pool.ReleaseOrMintInV1`](/ccip/api-reference/evm/v1.6.0/pool#releaseormintinv1) | Input parameters for the mint operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------------- | ------------------------------------------------ |
| [`Pool.ReleaseOrMintOutV1`](/ccip/api-reference/evm/v1.6.0/pool#releaseormintoutv1) | Contains the final amount minted in local tokens |
---
# CCIP v1.6.0 BurnFromMintTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/burn-from-mint-token-pool
## BurnFromMintTokenPool
A specialized token pool contract that manages third-party tokens through minting and burning operations, specifically using the `burnFrom` function.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/pools/BurnFromMintTokenPool.sol)
**Inherits:**
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.6.0/burn-mint-token-pool-abstract)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.0/i-type-and-version)
## State Variables
### typeAndVersion
```solidity
string public constant override typeAndVersion = "BurnFromMintTokenPool 1.5.1";
```
**Returns**
| Type | Description |
| -------- | ----------------------------------------------------- |
| `string` | The contract identifier "BurnFromMintTokenPool 1.5.1" |
## Functions
### _burn
Internal function that executes the token burning operation.
```solidity
function _burn(uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------ |
| `amount` | `uint256` | The quantity of tokens to burn |
### constructor
```solidity
constructor(
IBurnMintERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ---------------- | ------------------------------------------------------ |
| `token` | `IBurnMintERC20` | Address of the token contract to be managed |
| `localTokenDecimals` | `uint8` | Decimal precision of the local token |
| `allowlist` | `address[]` | List of addresses authorized to interact with the pool |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `router` | `address` | Address of the router contract |
---
# CCIP v1.6.0 LockReleaseTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/lock-release-token-pool
## LockReleaseTokenPool
A specialized token pool for managing native tokens through a lock and release mechanism, with support for liquidity management.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/pools/LockReleaseTokenPool.sol)
**Inherits:**
- [TokenPool](/ccip/api-reference/evm/v1.6.0/token-pool)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.0/i-type-and-version)
## Events
### LiquidityTransferred
```solidity
event LiquidityTransferred(address indexed from, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------- |
| `from` | `address` | Yes | The source pool address |
| `amount` | `uint256` | No | The amount of liquidity transferred |
## Errors
### InsufficientLiquidity
```solidity
error InsufficientLiquidity();
```
### LiquidityNotAccepted
```solidity
error LiquidityNotAccepted();
```
## State Variables
### i_acceptLiquidity
```solidity
bool internal immutable i_acceptLiquidity;
```
### s_rebalancer
```solidity
address internal s_rebalancer;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "LockReleaseTokenPool 1.5.1";
```
## Functions
### canAcceptLiquidity
Determines whether the pool can accept external liquidity.
```solidity
function canAcceptLiquidity() external view returns (bool);
```
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the pool accepts external liquidity |
### constructor
```solidity
constructor(
IERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
bool acceptLiquidity,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ----------- | ------------------------------------------- |
| `token` | `IERC20` | The token contract to manage |
| `localTokenDecimals` | `uint8` | The decimal precision for the local token |
| `allowlist` | `address[]` | Initial list of authorized addresses |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `acceptLiquidity` | `bool` | Whether the pool accepts external liquidity |
| `router` | `address` | Address of the router contract |
### getRebalancer
Returns the current rebalancer address.
```solidity
function getRebalancer() external view returns (address);
```
**Returns**
| Type | Description |
| --------- | ------------------------------------- |
| `address` | The current liquidity manager address |
### lockOrBurn
Locks tokens in the pool for cross-chain transfer.
```solidity
function lockOrBurn(
Pool.LockOrBurnInV1 calldata lockOrBurnIn
) external virtual override returns (Pool.LockOrBurnOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| -------------- | --------------------------------------------------------------------------- | --------------------------------------- |
| `lockOrBurnIn` | [`Pool.LockOrBurnInV1`](/ccip/api-reference/evm/v1.6.0/pool#lockorburninv1) | Input parameters for the lock operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------- | ------------------------------------------------ |
| [`Pool.LockOrBurnOutV1`](/ccip/api-reference/evm/v1.6.0/pool#lockorburnoutv1) | Contains destination token address and pool data |
### provideLiquidity
Adds external liquidity to the pool.
```solidity
function provideLiquidity(uint256 amount) external;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------------- |
| `amount` | `uint256` | The amount of liquidity to provide |
### releaseOrMint
Releases tokens from the pool to a recipient.
```solidity
function releaseOrMint(
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn
) external virtual override returns (Pool.ReleaseOrMintOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| ----------------- | --------------------------------------------------------------------------------- | ------------------------------------------ |
| `releaseOrMintIn` | [`Pool.ReleaseOrMintInV1`](/ccip/api-reference/evm/v1.6.0/pool#releaseormintinv1) | Input parameters for the release operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------------- | -------------------------------------------------- |
| [`Pool.ReleaseOrMintOutV1`](/ccip/api-reference/evm/v1.6.0/pool#releaseormintoutv1) | Contains the final amount released in local tokens |
### setRebalancer
Updates the rebalancer address.
```solidity
function setRebalancer(address rebalancer) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | --------------------------------- |
| `rebalancer` | `address` | The new rebalancer address to set |
### supportsInterface
Checks interface support using ERC165.
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | ---------------------------------- |
| `bool` | True if the interface is supported |
### transferLiquidity
Transfers liquidity from an older pool version.
```solidity
function transferLiquidity(address from, uint256 amount) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `from` | `address` | The address of the source pool |
| `amount` | `uint256` | The amount of liquidity to transfer |
### withdrawLiquidity
Removes liquidity from the pool.
```solidity
function withdrawLiquidity(uint256 amount) external;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `amount` | `uint256` | The amount of liquidity to withdraw |
---
# CCIP v1.6.0 TokenAdminRegistry Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/token-admin-registry
## TokenAdminRegistry
A contract that manages token pool configurations and administrator access for CCIP-enabled tokens.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/tokenAdminRegistry/TokenAdminRegistry.sol)
**Inherits:**
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.0/i-type-and-version)
## Functions
### acceptAdminRole
Accepts the administrator role for a token.
```solidity
function acceptAdminRole(address localToken) external;
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | ------------------------------------------ |
| `localToken` | `address` | The token to accept the administrator role |
### addRegistryModule
Adds a new registry module to the allowed modules list.
```solidity
function addRegistryModule(address module) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------- |
| `module` | `address` | The module to authorize |
### getAllConfiguredTokens
Returns a paginated list of configured tokens.
```solidity
function getAllConfiguredTokens(uint64 startIndex, uint64 maxCount) external view returns (address[] memory tokens);
```
**Parameters**
| Name | Type | Description |
| ------------ | -------- | --------------------------------------------------------- |
| `startIndex` | `uint64` | Starting position in the list (0 for beginning) |
| `maxCount` | `uint64` | Maximum tokens to retrieve (use type(uint64).max for all) |
**Returns**
| Type | Description |
| ----------- | ---------------------------------- |
| `address[]` | List of configured token addresses |
### getPool
Returns the pool address for a specific token.
```solidity
function getPool(address token) external view returns (address);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ------------------ |
| `token` | `address` | The token to query |
**Returns**
| Type | Description |
| --------- | ------------------------ |
| `address` | The token's pool address |
### getPools
Returns pool addresses for multiple tokens.
```solidity
function getPools(address[] calldata tokens) external view returns (address[] memory);
```
**Parameters**
| Name | Type | Description |
| -------- | ----------- | --------------- |
| `tokens` | `address[]` | Tokens to query |
**Returns**
| Type | Description |
| ----------- | ------------------------------------- |
| `address[]` | Array of corresponding pool addresses |
### getTokenConfig
Returns the complete configuration for a token.
```solidity
function getTokenConfig(address token) external view returns (TokenConfig memory);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------- |
| `token` | `address` | Token to query |
**Returns**
| Type | Description |
| ------------- | ---------------------------- |
| `TokenConfig` | Complete token configuration |
### isAdministrator
Checks if an address is the administrator for a token.
```solidity
function isAdministrator(address localToken, address administrator) external view returns (bool);
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | ----------------- |
| `localToken` | `address` | Token to check |
| `administrator` | `address` | Address to verify |
**Returns**
| Type | Description |
| ------ | ---------------------------------------- |
| `bool` | True if address is current administrator |
### isRegistryModule
Checks if an address is an authorized registry module.
```solidity
function isRegistryModule(address module) public view returns (bool);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------- |
| `module` | `address` | Address to verify |
**Returns**
| Type | Description |
| ------ | ------------------------------------ |
| `bool` | True if address is authorized module |
### proposeAdministrator
Proposes an initial administrator for a token.
```solidity
function proposeAdministrator(address localToken, address administrator) external;
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | ------------------------------ |
| `localToken` | `address` | Token to configure |
| `administrator` | `address` | Proposed administrator address |
### removeRegistryModule
Removes a registry module from the allowed modules list.
```solidity
function removeRegistryModule(address module) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------- |
| `module` | `address` | The module to remove |
### setPool
Sets or updates the pool for a token.
```solidity
function setPool(address localToken, address pool) external onlyTokenAdmin(localToken);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | --------------------------------- |
| `localToken` | `address` | Token to configure |
| `pool` | `address` | New pool address (or 0 to delist) |
### transferAdminRole
Initiates transfer of administrator role.
```solidity
function transferAdminRole(address localToken, address newAdmin) external onlyTokenAdmin(localToken);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | -------------------------------------------------------- |
| `localToken` | `address` | The token contract whose admin role is being transferred |
| `newAdmin` | `address` | The proposed new administrator address (or 0 to cancel) |
## Events
### AdministratorTransferRequested
```solidity
event AdministratorTransferRequested(address indexed token, address indexed currentAdmin, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------------- | --------- | ------- | -------------------------------------------------------- |
| `token` | `address` | Yes | The token contract whose admin role is being transferred |
| `currentAdmin` | `address` | Yes | The current administrator address |
| `newAdmin` | `address` | Yes | The proposed new administrator address |
### AdministratorTransferred
```solidity
event AdministratorTransferred(address indexed token, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| ---------- | --------- | ------- | -------------------------------------------------------- |
| `token` | `address` | Yes | The token contract whose admin role has been transferred |
| `newAdmin` | `address` | Yes | The new administrator address |
### PoolSet
```solidity
event PoolSet(address indexed token, address indexed previousPool, address indexed newPool);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------------- | --------- | ------- | ---------------------------------- |
| `token` | `address` | Yes | The token address being configured |
| `previousPool` | `address` | Yes | The previous pool address |
| `newPool` | `address` | Yes | The new pool address |
### RegistryModuleAdded
```solidity
event RegistryModuleAdded(address module);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ------------------------------------------ |
| `module` | `address` | No | The address of the newly authorized module |
### RegistryModuleRemoved
```solidity
event RegistryModuleRemoved(address indexed module);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | --------------------------------- |
| `module` | `address` | Yes | The address of the removed module |
## Errors
### AddressZero
```solidity
error ZeroAddress();
```
### AlreadyRegistered
```solidity
error AlreadyRegistered(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | --------------------------------------------------- |
| `token` | `address` | The token address that already has an administrator |
### InvalidTokenPoolToken
```solidity
error InvalidTokenPoolToken(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | --------------------------------------------------- |
| `token` | `address` | The token address that is not supported by the pool |
### OnlyAdministrator
```solidity
error OnlyAdministrator(address sender, address token);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
| `token` | `address` | The token address being accessed |
### OnlyPendingAdministrator
```solidity
error OnlyPendingAdministrator(address sender, address token);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
| `token` | `address` | The token address being accessed |
### OnlyRegistryModuleOrOwner
```solidity
error OnlyRegistryModuleOrOwner(address sender);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
## Structs
### TokenConfig
Configuration data structure for each token.
```solidity
struct TokenConfig {
address administrator;
address pendingAdministrator;
address tokenPool;
}
```
## State Variables
### s_registryModules
```solidity
EnumerableSet.AddressSet internal s_registryModules;
```
### s_tokenConfig
```solidity
mapping(address token => TokenConfig) internal s_tokenConfig;
```
### s_tokens
```solidity
EnumerableSet.AddressSet internal s_tokens;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "TokenAdminRegistry 1.5.0";
```
---
# CCIP v1.6.0 RegistryModuleOwnerCustom Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/registry-module-owner-custom
## RegistryModuleOwnerCustom
A contract that facilitates token administrator registration through various ownership patterns.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/tokenAdminRegistry/RegistryModuleOwnerCustom.sol)
**Inherits:**
- [ITypeAndVersion](/ccip/api-reference/evm/v1.6.0/i-type-and-version)
## Events
### AdministratorRegistered
```solidity
event AdministratorRegistered(address indexed token, address indexed administrator);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------- | --------- | ------- | ------------------------------------ |
| `token` | `address` | Yes | The token contract address |
| `administrator` | `address` | Yes | The registered administrator address |
## Errors
### AddressZero
```solidity
error AddressZero();
```
### CanOnlySelfRegister
```solidity
error CanOnlySelfRegister(address admin, address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------- |
| `admin` | `address` | The expected administrator address |
| `token` | `address` | The token contract address |
### RequiredRoleNotFound
```solidity
error RequiredRoleNotFound(address msgSender, bytes32 role, address token);
```
**Parameters**
| Name | Type | Description |
| ----------- | --------- | ---------------------------- |
| `msgSender` | `address` | The caller's address |
| `role` | `bytes32` | The required role identifier |
| `token` | `address` | The token contract address |
## State Variables
### i_tokenAdminRegistry
```solidity
ITokenAdminRegistry internal immutable i_tokenAdminRegistry;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "RegistryModuleOwnerCustom 1.6.0";
```
## Functions
### constructor
```solidity
constructor(address tokenAdminRegistry);
```
**Parameters**
| Name | Type | Description |
| -------------------- | --------- | ---------------------------------------------- |
| `tokenAdminRegistry` | `address` | The address of the TokenAdminRegistry contract |
### registerAccessControlDefaultAdmin
Registers a token administrator using OpenZeppelin's AccessControl DEFAULT_ADMIN_ROLE.
```solidity
function registerAccessControlDefaultAdmin(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### registerAdminViaGetCCIPAdmin
Registers a token administrator using the `getCCIPAdmin` method.
```solidity
function registerAdminViaGetCCIPAdmin(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### registerAdminViaOwner
Registers a token administrator using the `owner` method.
```solidity
function registerAdminViaOwner(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### _registerAdmin
Internal function to handle administrator registration.
```solidity
function _registerAdmin(address token, address admin) internal;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ------------------------------------------ |
| `token` | `address` | The token contract to register admin for |
| `admin` | `address` | The administrator address being registered |
---
# CCIP v1.6.0 BurnMintERC20 Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/burn-mint-erc20
## BurnMintERC20
An ERC20-compliant token contract that extends the standard functionality with controlled minting and burning capabilities through role-based access control.
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-release/1.4.0/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol)
## Events
### CCIPAdminTransferred
```solidity
event CCIPAdminTransferred(address indexed previousAdmin, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------- | --------- | ------- | ------------------------------ |
| `previousAdmin` | `address` | Yes | The address that held the role |
| `newAdmin` | `address` | Yes | The address receiving the role |
## Errors
### InvalidRecipient
```solidity
error InvalidRecipient(address recipient);
```
### MaxSupplyExceeded
```solidity
error MaxSupplyExceeded(uint256 supplyAfterMint);
```
## State Variables
### BURNER_ROLE
```solidity
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
```
### MINTER_ROLE
```solidity
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
```
## Functions
### _approve
Internal function that manages token spending allowances with built-in safety checks.
```solidity
function _approve(address owner, address spender, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | ------------------------------------------------ |
| `owner` | `address` | The address that currently owns the tokens |
| `spender` | `address` | The address that will be allowed to spend tokens |
| `amount` | `uint256` | The number of tokens to approve for spending |
### burn (with amount)
Allows authorized addresses to burn (destroy) tokens from their own account.
```solidity
function burn(uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------- |
| `amount` | `uint256` | The number of tokens to destroy |
### burn (with account)
Alternative burn function that allows burning tokens from a specified account.
```solidity
function burn(address account, uint256 amount) public virtual override;
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | --------------------------------- |
| `account` | `address` | The account to remove tokens from |
| `amount` | `uint256` | The number of tokens to destroy |
### burnFrom
Burns tokens from a specified account, requiring prior approval.
```solidity
function burnFrom(address account, uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE);
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | --------------------------------- |
| `account` | `address` | The account to remove tokens from |
| `amount` | `uint256` | The number of tokens to destroy |
### constructor
```solidity
constructor(
string memory name,
string memory symbol,
uint8 decimals_,
uint256 maxSupply_,
uint256 preMint
) ERC20(name, symbol);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | -------------------------------------------------- |
| `name` | `string` | The display name of the token |
| `symbol` | `string` | The token's ticker symbol |
| `decimals_` | `uint8` | The number of decimal places for token amounts |
| `maxSupply_` | `uint256` | The maximum allowed token supply (0 for unlimited) |
| `preMint` | `uint256` | The amount of tokens to mint to the deployer |
### decimals
Returns the token's decimal precision.
```solidity
function decimals() public view virtual override returns (uint8);
```
**Returns**
| Type | Description |
| ------- | --------------------------------------------------- |
| `uint8` | The number of decimal places used for token amounts |
### getCCIPAdmin
Retrieves the current CCIP administrator's address.
```solidity
function getCCIPAdmin() external view returns (address);
```
**Returns**
| Type | Description |
| --------- | ---------------------------------------- |
| `address` | The current CCIP administrator's address |
### grantMintAndBurnRoles
Assigns both minting and burning permissions to a single address.
```solidity
function grantMintAndBurnRoles(address burnAndMinter) external;
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | -------------------------------------------------------- |
| `burnAndMinter` | `address` | The address that will receive minting and burning rights |
### maxSupply
Returns the token's maximum supply limit.
```solidity
function maxSupply() public view virtual returns (uint256);
```
**Returns**
| Type | Description |
| --------- | ---------------------------------------------------- |
| `uint256` | The maximum allowed token supply (0 means unlimited) |
### mint
Creates new tokens and assigns them to a specified address.
```solidity
function mint(address account, uint256 amount) external override onlyRole(MINTER_ROLE);
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | -------------------------------------------- |
| `account` | `address` | The address that will receive the new tokens |
| `amount` | `uint256` | The number of new tokens to create |
### setCCIPAdmin
Updates the CCIP administrator role.
```solidity
function setCCIPAdmin(address newAdmin) public onlyRole(DEFAULT_ADMIN_ROLE);
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | ------------------------------------------------------- |
| `newAdmin` | `address` | The address that will become the new CCIP administrator |
### supportsInterface
Determines whether the contract implements a specific interface.
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual override(AccessControl, IERC165) returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | --------------------------------------------------------- |
| `bool` | `true` if the contract implements the specified interface |
### _transfer
Internal function that handles token transfers between addresses.
```solidity
function _transfer(address from, address to, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `from` | `address` | The address sending tokens |
| `to` | `address` | The address receiving tokens |
| `amount` | `uint256` | The number of tokens to transfer |
---
# CCIP v1.6.0 RateLimiter Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/rate-limiter
## RateLimiter
A library implementing the Token Bucket algorithm for rate limiting cross-chain operations.
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/blob/contracts-ccip-release/1.6.0/chains/evm/contracts/libraries/RateLimiter.sol)
## Events
### TokensConsumed
```solidity
event TokensConsumed(uint256 tokens);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------- |
| `tokens` | `uint256` | The number of tokens consumed |
### ConfigChanged
```solidity
event ConfigChanged(Config config);
```
**Parameters**
| Name | Type | Description |
| -------- | ------------------- | ----------------------------- |
| `config` | [`Config`](#config) | The new configuration applied |
## Errors
### BucketOverfilled
```solidity
error BucketOverfilled();
```
### OnlyCallableByAdminOrOwner
```solidity
error OnlyCallableByAdminOrOwner();
```
### TokenMaxCapacityExceeded
```solidity
error TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress);
```
### TokenRateLimitReached
```solidity
error TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress);
```
### AggregateValueMaxCapacityExceeded
```solidity
error AggregateValueMaxCapacityExceeded(uint256 capacity, uint256 requested);
```
### AggregateValueRateLimitReached
```solidity
error AggregateValueRateLimitReached(uint256 minWaitInSeconds, uint256 available);
```
### InvalidRateLimitRate
```solidity
error InvalidRateLimitRate(Config rateLimiterConfig);
```
### DisabledNonZeroRateLimit
```solidity
error DisabledNonZeroRateLimit(Config config);
```
### RateLimitMustBeDisabled
```solidity
error RateLimitMustBeDisabled();
```
## Structs
### TokenBucket
Represents the state and configuration of a token bucket rate limiter.
```solidity
struct TokenBucket {
uint128 tokens;
uint32 lastUpdated;
bool isEnabled;
uint128 capacity;
uint128 rate;
}
```
### Config
Configuration parameters for the rate limiter.
```solidity
struct Config {
bool isEnabled;
uint128 capacity;
uint128 rate;
}
```
## Functions
### _consume
Removes tokens from the pool, reducing the available rate capacity for subsequent calls.
```solidity
function _consume(TokenBucket storage s_bucket, uint256 requestTokens, address tokenAddress) internal;
```
**Parameters**
| Name | Type | Description |
| --------------- | ----------------------------- | --------------------------------------------------------------- |
| `s_bucket` | [`TokenBucket`](#tokenbucket) | The token bucket to consume from |
| `requestTokens` | `uint256` | The number of tokens to consume |
| `tokenAddress` | `address` | The token address (use address(0) for aggregate value capacity) |
### _currentTokenBucketState
Retrieves the current state of a token bucket, including automatic refill calculations.
```solidity
function _currentTokenBucketState(TokenBucket memory bucket) internal view returns (TokenBucket memory);
```
**Returns**
| Type | Description |
| ----------------------------- | ------------------------------------- |
| [`TokenBucket`](#tokenbucket) | The current state of the token bucket |
### _setTokenBucketConfig
Updates the rate limiter configuration.
```solidity
function _setTokenBucketConfig(TokenBucket storage s_bucket, Config memory config) internal;
```
**Parameters**
| Name | Type | Description |
| ---------- | ----------------------------- | ------------------------------ |
| `s_bucket` | [`TokenBucket`](#tokenbucket) | The token bucket to configure |
| `config` | [`Config`](#config) | The new configuration to apply |
### _validateTokenBucketConfig
Validates rate limiter configuration parameters.
```solidity
function _validateTokenBucketConfig(Config memory config, bool mustBeDisabled) internal pure;
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------------------- | ------------------------------------------ |
| `config` | [`Config`](#config) | The configuration to validate |
| `mustBeDisabled` | `bool` | Whether the configuration must be disabled |
### _calculateRefill
Calculates the number of tokens to add during a refill operation.
```solidity
function _calculateRefill(
uint256 capacity,
uint256 tokens,
uint256 timeDiff,
uint256 rate
) private pure returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | ------------------------------------------- |
| `capacity` | `uint256` | Maximum token capacity |
| `tokens` | `uint256` | Current token balance |
| `timeDiff` | `uint256` | Time elapsed since last refill (in seconds) |
| `rate` | `uint256` | Tokens per second refill rate |
**Returns**
| Type | Description |
| --------- | ---------------------------------- |
| `uint256` | The new token balance after refill |
### _min
Returns the smaller of two numbers.
```solidity
function _min(uint256 a, uint256 b) internal pure returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---- | --------- | ------------- |
| `a` | `uint256` | First number |
| `b` | `uint256` | Second number |
**Returns**
| Type | Description |
| --------- | ------------------------------ |
| `uint256` | The smaller of the two numbers |
---
# CCIP v1.6.0 Error Codes and Messages API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/errors
When invoking the `ccipSend` [function](/ccip/api-reference/evm/v1.6.0/i-router-client#ccipsend), it is possible to encounter various errors. These might be thrown either by the CCIP router or by one of the downstream contracts called by the CCIP router. Below is a compiled list of potential errors you might encounter. Referencing this list will enable you to capture and handle these exceptions gracefully.
## Router Errors
## OnRamp Errors
## FeeQuoter Errors
## Rate Limiter Errors
## Token (ERC20) Errors
## BurnMintERC20 Errors
## Token Pool Errors
---
# CCIP v1.6.0 Ownable2Step Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/ownable-2-step
## Ownable2Step
A minimal contract that implements 2-step ownership transfer and nothing more. It's made to be minimal to reduce the impact of the bytecode size on any contract that inherits from it.
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-release/1.4.0/contracts/src/v0.8/shared/access/Ownable2Step.sol)
## Events
### OwnershipTransferRequested
```solidity
event OwnershipTransferRequested(address indexed from, address indexed to);
```
**Parameters**
| Name | Type | Description |
| ------ | --------- | ------------------------------------- |
| `from` | `address` | Current owner initiating the transfer |
| `to` | `address` | Proposed new owner |
### OwnershipTransferred
```solidity
event OwnershipTransferred(address indexed from, address indexed to);
```
**Parameters**
| Name | Type | Description |
| ------ | --------- | -------------- |
| `from` | `address` | Previous owner |
| `to` | `address` | New owner |
## Errors
### CannotTransferToSelf
```solidity
error CannotTransferToSelf();
```
### MustBeProposedOwner
```solidity
error MustBeProposedOwner();
```
### OnlyCallableByOwner
```solidity
error OnlyCallableByOwner();
```
### OwnerCannotBeZero
```solidity
error OwnerCannotBeZero();
```
## State Variables
### s_owner
The owner is the current owner of the contract.
```solidity
address private s_owner;
```
### s_pendingOwner
The pending owner is the address to which ownership may be transferred.
```solidity
address private s_pendingOwner;
```
## Functions
### acceptOwnership
Allows an ownership transfer to be completed by the recipient.
```solidity
function acceptOwnership() external override;
```
### constructor
Initializes the contract with an owner and optionally a pending owner.
```solidity
constructor(address newOwner, address pendingOwner);
```
**Parameters**
| Name | Type | Description |
| -------------- | --------- | -------------------------------------------------- |
| `newOwner` | `address` | The initial owner of the contract |
| `pendingOwner` | `address` | Optional address to initiate ownership transfer to |
### onlyOwner
Modifier that restricts function access to the contract owner.
```solidity
modifier onlyOwner();
```
### owner
Returns the current owner's address.
```solidity
function owner() public view override returns (address);
```
**Returns**
| Type | Description |
| --------- | -------------------------------- |
| `address` | The address of the current owner |
### transferOwnership
Allows an owner to begin transferring ownership to a new address.
```solidity
function transferOwnership(address to) public override onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ---- | --------- | -------------------------------------------------- |
| `to` | `address` | The address to which ownership will be transferred |
### _validateOwnership
Internal function to validate access control.
```solidity
function _validateOwnership() internal view;
```
---
# CCIP v1.6.0 Ownable2StepMsgSender Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.6.0/ownable-2-step-msg-sender
## Ownable2StepMsgSender
A contract that facilitates two-step ownership transfer, providing enhanced security for ownership management. This contract extends `Ownable2Step` and automatically sets the deploying address (`msg.sender`) as the initial owner with no pending owner.
**Inherits:**
- [`Ownable2Step`](/ccip/api-reference/evm/v1.6.0/ownable-2-step) - Provides secure two-step ownership transfer functionality
[Git Source](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-release/1.4.0/contracts/src/v0.8/shared/access/Ownable2StepMsgSender.sol)
## Functions
### constructor
Initializes the contract with the deploying address as the owner and no pending owner.
```solidity
constructor() Ownable2Step(msg.sender, address(0));
```
---
# CCIP v1.5.1 API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1
## API References
### Core Components
- [CCIPReceiver](/ccip/api-reference/evm/v1.5.1/ccip-receiver) - Base contract for receiving CCIP messages
- [Client](/ccip/api-reference/evm/v1.5.1/client) - Library providing structs and types for building CCIP messages
- [IRouterClient](/ccip/api-reference/evm/v1.5.1/i-router-client) - Interface for sending messages through CCIP
- [Pool](/ccip/api-reference/evm/v1.5.1/pool) - Library providing token pool functions for cross-chain operations
- [RateLimiter](/ccip/api-reference/evm/v1.5.1/rate-limiter) - Contract for managing rate limits on token transfers
- [TypeAndVersion](/ccip/api-reference/evm/v1.5.1/i-type-and-version) - Interface for contract versioning
### Token Pools
- [BurnFromMintTokenPool](/ccip/api-reference/evm/v1.5.1/burn-from-mint-token-pool) - Implementation using `burnFrom(address, amount)` for token burning
- [BurnMintERC20](/ccip/api-reference/evm/v1.5.1/burn-mint-erc20) - Implementation for burning and minting ERC20 tokens
- [BurnMintTokenPool](/ccip/api-reference/evm/v1.5.1/burn-mint-token-pool) - Implementation using `burn(amount)` for token burning
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.5.1/burn-mint-token-pool-abstract) - Abstract contract for burn/mint token handling
- [LockReleaseTokenPool](/ccip/api-reference/evm/v1.5.1/lock-release-token-pool) - Implementation for locking and releasing tokens on their native chain
- [TokenPool](/ccip/api-reference/evm/v1.5.1/token-pool) - Base abstract class defining common functionality for all token pools
### Access Control
- [Ownable2Step](/ccip/api-reference/evm/v1.5.1/ownable-2-step) - Base contract implementing secure two-step ownership transfer
- [Ownable2StepMsgSender](/ccip/api-reference/evm/v1.5.1/ownable-2-step-msg-sender) - Extension of Ownable2Step that sets msg.sender as initial owner
### Registry Components
- [RegistryModuleOwnerCustom](/ccip/api-reference/evm/v1.5.1/registry-module-owner-custom) - Registry module for token admin registration
- [TokenAdminRegistry](/ccip/api-reference/evm/v1.5.1/token-admin-registry) - Contract for storing token pool configurations
### Error Handling
- [Errors](/ccip/api-reference/evm/v1.5.1/errors) - Comprehensive list of CCIP error codes and their descriptions
---
# CCIP v1.5.1 Client Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/client
## Client
A library that provides core data structures and utilities for building and handling cross-chain messages in CCIP.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/libraries/Client.sol)
## Structs
### Any2EVMMessage
Structure representing a message received from any chain to an EVM chain.
```solidity
struct Any2EVMMessage {
bytes32 messageId;
uint64 sourceChainSelector;
bytes sender;
bytes data;
EVMTokenAmount[] destTokenAmounts;
}
```
**Properties**
| Name | Type | Description |
| --------------------- | ------------------------------------- | ------------------------------------------------- |
| `messageId` | `bytes32` | Message ID corresponding to ccipSend on source |
| `sourceChainSelector` | `uint64` | Identifier of the source chain |
| `sender` | `bytes` | Sender address (use abi.decode if from EVM chain) |
| `data` | `bytes` | Custom payload from the original message |
| `destTokenAmounts` | [`EVMTokenAmount[]`](#evmtokenamount) | Token amounts in destination chain representation |
### EVM2AnyMessage
Structure for sending a message from an EVM chain to any supported chain.
```solidity
struct EVM2AnyMessage {
bytes receiver;
bytes data;
EVMTokenAmount[] tokenAmounts;
address feeToken;
bytes extraArgs;
}
```
**Properties**
| Name | Type | Description |
| -------------- | ------------------------------------- | --------------------------------------------------- |
| `receiver` | `bytes` | Encoded receiver address for destination EVM chains |
| `data` | `bytes` | Custom payload to send |
| `tokenAmounts` | [`EVMTokenAmount[]`](#evmtokenamount) | Tokens and amounts to transfer |
| `feeToken` | `address` | Token used for fees (address(0) for native tokens) |
| `extraArgs` | `bytes` | Additional arguments encoded with _argsToBytes |
### EVMExtraArgsV1
Structure for V1 extra arguments in cross-chain messages.
```solidity
struct EVMExtraArgsV1 {
uint256 gasLimit;
}
```
**Properties**
| Name | Type | Description |
| ---------- | --------- | -------------------------------------------- |
| `gasLimit` | `uint256` | Gas limit for execution on destination chain |
### EVMExtraArgsV2
Structure for V2 extra arguments in cross-chain messages.
```solidity
struct EVMExtraArgsV2 {
uint256 gasLimit;
bool allowOutOfOrderExecution;
}
```
**Properties**
| Name | Type | Description |
| -------------------------- | --------- | --------------------------------------------- |
| `gasLimit` | `uint256` | Gas limit for execution on destination chain |
| `allowOutOfOrderExecution` | `bool` | Whether messages can be executed in any order |
### EVMTokenAmount
Structure representing token amounts in CCIP messages.
```solidity
struct EVMTokenAmount {
address token;
uint256 amount;
}
```
**Properties**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `token` | `address` | Token address on the local chain |
| `amount` | `uint256` | Amount of tokens to transfer |
## State Variables
### EVM_EXTRA_ARGS_V1_TAG
```solidity
bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
```
### EVM_EXTRA_ARGS_V2_TAG
```solidity
bytes4 public constant EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10;
```
## Functions
### _argsToBytes (V1)
Encodes EVMExtraArgsV1 into bytes for message transmission.
```solidity
function _argsToBytes(EVMExtraArgsV1 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ----------------------------------- | -------------------------------- |
| `extraArgs` | [`EVMExtraArgsV1`](#evmextraargsv1) | The V1 extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
### _argsToBytes (V2)
Encodes EVMExtraArgsV2 into bytes for message transmission.
```solidity
function _argsToBytes(EVMExtraArgsV2 memory extraArgs) internal pure returns (bytes memory bts);
```
**Parameters**
| Name | Type | Description |
| ----------- | ----------------------------------- | -------------------------------- |
| `extraArgs` | [`EVMExtraArgsV2`](#evmextraargsv2) | The V2 extra arguments to encode |
**Returns**
| Type | Description |
| ------- | ------------------------------------ |
| `bytes` | The encoded extra arguments with tag |
---
# CCIP v1.5.1 ITypeAndVersion Interface API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/i-type-and-version
## ITypeAndVersion
An interface that provides type and version information for contracts.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/shared/interfaces/ITypeAndVersion.sol)
## Functions
### typeAndVersion
Returns the type and version of the contract.
```solidity
function typeAndVersion() external pure returns (string memory);
```
**Returns**
| Type | Description |
| -------- | ------------------------------------- |
| `string` | The type and version of the contract. |
---
# CCIP v1.5.1 IRouterClient API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/i-router-client
## IRouterClient
The IRouterClient interface provides the core functionality for sending cross-chain messages through CCIP (Chainlink Cross-Chain Interoperability Protocol).
[Git Source](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.3.0/contracts/src/v0.8/ccip/interfaces/IRouterClient.sol)
## Errors
### InsufficientFeeTokenAmount
Thrown when the provided fee token amount is insufficient for the message delivery.
```solidity
error InsufficientFeeTokenAmount();
```
### InvalidMsgValue
Thrown when the provided msg.value is invalid for the operation.
```solidity
error InvalidMsgValue();
```
### UnsupportedDestinationChain
Thrown when attempting to send a message to an unsupported destination chain.
```solidity
error UnsupportedDestinationChain(uint64 destChainSelector);
```
## Functions
### ccipSend
Sends a message to the destination chain through CCIP.
```solidity
function ccipSend(
uint64 destinationChainSelector,
Client.EVM2AnyMessage calldata message
) external payable returns (bytes32);
```
**Parameters**
| Name | Type | Description |
| -------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `destinationChainSelector` | `uint64` | The destination chain ID |
| `message` | [`Client.EVM2AnyMessage`](/ccip/api-reference/evm/v1.5.1/client#evm2anymessage) | The cross-chain CCIP message including data and/or tokens |
**Returns**
| Name | Type | Description |
| ----------- | --------- | -------------- |
| `messageId` | `bytes32` | The message ID |
### getFee
Gets the fee required for sending a CCIP message to the destination chain.
```solidity
function getFee(
uint64 destinationChainSelector,
Client.EVM2AnyMessage memory message
) external view returns (uint256 fee);
```
**Parameters**
| Name | Type | Description |
| -------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `destinationChainSelector` | `uint64` | The destination chainSelector |
| `message` | [`Client.EVM2AnyMessage`](/ccip/api-reference/evm/v1.5.1/client#evm2anymessage) | The cross-chain CCIP message including data and/or tokens |
**Returns**
| Name | Type | Description |
| ----- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `fee` | `uint256` | Returns execution fee for the message delivery to destination chain, denominated in the feeToken specified in the message. |
### isChainSupported
Checks if the given chain ID is supported for sending/receiving.
```solidity
function isChainSupported(uint64 destChainSelector) external view returns (bool supported);
```
**Parameters**
| Name | Type | Description |
| ------------------- | -------- | ------------------- |
| `destChainSelector` | `uint64` | The chain to check. |
**Returns**
| Name | Type | Description |
| ----------- | ------ | ----------------------------------------- |
| `supported` | `bool` | is true if it is supported, false if not. |
---
# CCIP v1.5.1 CCIPReceiver API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/ccip-receiver
## CCIPReceiver
An abstract base contract that provides core functionality for CCIP-enabled applications to receive cross-chain messages.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/applications/CCIPReceiver.sol)
## Errors
### InvalidRouter
```solidity
error InvalidRouter(address router);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------- |
| `router` | `address` | The invalid router address |
## State Variables
### i_ccipRouter
```solidity
address internal immutable i_ccipRouter;
```
## Modifiers
### onlyRouter
```solidity
modifier onlyRouter();
```
## Functions
### ccipReceive
Processes incoming CCIP messages from the router.
```solidity
function ccipReceive(Client.Any2EVMMessage calldata message) external virtual override onlyRouter;
```
**Parameters**
| Name | Type | Description |
| --------- | ------------------------------------------------------------------------------- | ---------------- |
| `message` | [`Client.Any2EVMMessage`](/ccip/api-reference/evm/v1.5.1/client#any2evmmessage) | The CCIP message |
### constructor
```solidity
constructor(address router);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `router` | `address` | The CCIP router contract address |
### getRouter
Returns the address of the current CCIP router.
```solidity
function getRouter() public view virtual returns (address);
```
**Returns**
| Type | Description |
| --------- | ------------------------------- |
| `address` | The current CCIP router address |
### supportsInterface
Determines whether the contract implements specific interfaces.
```solidity
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | ---------------------------------- |
| `bool` | True if the interface is supported |
### _ccipReceive
Internal function to be implemented by derived contracts for custom message handling.
```solidity
function _ccipReceive(Client.Any2EVMMessage memory message) internal virtual;
```
**Parameters**
| Name | Type | Description |
| --------- | ------------------------------------------------------------------------------- | --------------------------- |
| `message` | [`Client.Any2EVMMessage`](/ccip/api-reference/evm/v1.5.1/client#any2evmmessage) | The message to be processed |
---
# CCIP v1.5.1 Pool Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/pool
## Pool
A library that provides core data structures and constants for token pool operations in cross-chain transfers.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/libraries/Pool.sol)
## Structs
### LockOrBurnInV1
Input parameters for locking or burning tokens in cross-chain transfers.
```solidity
struct LockOrBurnInV1 {
bytes receiver;
uint64 remoteChainSelector;
address originalSender;
uint256 amount;
address localToken;
}
```
### LockOrBurnOutV1
Output data from a lock or burn operation.
```solidity
struct LockOrBurnOutV1 {
bytes destTokenAddress;
bytes destPoolData;
}
```
### ReleaseOrMintInV1
Input parameters for releasing or minting tokens in cross-chain transfers.
```solidity
struct ReleaseOrMintInV1 {
bytes originalSender;
uint64 remoteChainSelector;
address receiver;
uint256 amount;
address localToken;
bytes sourcePoolAddress;
bytes sourcePoolData;
bytes offchainTokenData;
}
```
### ReleaseOrMintOutV1
Output data from a release or mint operation.
```solidity
struct ReleaseOrMintOutV1 {
uint256 destinationAmount;
}
```
## State Variables
### CCIP_POOL_V1
```solidity
bytes4 public constant CCIP_POOL_V1 = 0xaff2afbf;
```
### CCIP_POOL_V1_RET_BYTES
```solidity
uint16 public constant CCIP_POOL_V1_RET_BYTES = 32;
```
### CCIP_LOCK_OR_BURN_V1_RET_BYTES
```solidity
uint32 public constant CCIP_LOCK_OR_BURN_V1_RET_BYTES = 32;
```
---
# CCIP v1.5.1 TokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/token-pool
## TokenPool
An abstract contract that provides base functionality for managing cross-chain token operations in CCIP. It handles token decimals across different chains, rate limiting, and access control.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/pools/TokenPool.sol)
**Inherits:**
- [Ownable2StepMsgSender](/ccip/api-reference/evm/v1.5.1/ownable-2-step-msg-sender)
## Events
### AllowListAdd
Emitted when an address is added to the allowlist via [`applyAllowListUpdates`](#applyallowlistupdates).
```solidity
event AllowListAdd(address sender);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ------------------------------------------- |
| `sender` | `address` | No | The address that was added to the allowlist |
### AllowListRemove
Emitted when an address is removed from the allowlist via [`applyAllowListUpdates`](#applyallowlistupdates).
```solidity
event AllowListRemove(address sender);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------------------- |
| `sender` | `address` | No | The address that was removed from the allowlist |
### Burned
Emitted when tokens are burned by the pool.
```solidity
event Burned(address indexed sender, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------------- |
| `sender` | `address` | Yes | The address initiating the burn operation |
| `amount` | `uint256` | No | The amount of tokens burned |
### ChainAdded
Emitted when a new chain is configured in the pool via [`applyChainUpdates`](#applychainupdates).
```solidity
event ChainAdded(
uint64 remoteChainSelector,
bytes remoteToken,
RateLimiter.Config outboundRateLimiterConfig,
RateLimiter.Config inboundRateLimiterConfig
);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------------- | -------------------------------------------------------------------------- | ------- | ----------------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the newly added chain |
| `remoteToken` | `bytes` | No | The token address on the remote chain |
| `outboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.5.1/rate-limiter#config) | No | Rate limit configuration for outbound transfers |
| `inboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.5.1/rate-limiter#config) | No | Rate limit configuration for inbound transfers |
### ChainConfigured
Emitted when a chain's configuration is updated via [`setChainRateLimiterConfig`](#setchainratelimiterconfig) or [`setChainRateLimiterConfigs`](#setchainratelimiterconfigs).
```solidity
event ChainConfigured(
uint64 remoteChainSelector,
RateLimiter.Config outboundRateLimiterConfig,
RateLimiter.Config inboundRateLimiterConfig
);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------------- | -------------------------------------------------------------------------- | ------- | ------------------------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the chain being configured |
| `outboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.5.1/rate-limiter#config) | No | Updated rate limit configuration for outbound transfers |
| `inboundRateLimiterConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.5.1/rate-limiter#config) | No | Updated rate limit configuration for inbound transfers |
### ChainRemoved
Emitted when a chain is removed from the pool's configuration via [`applyChainUpdates`](#applychainupdates).
```solidity
event ChainRemoved(uint64 remoteChainSelector);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | ----------------------------------------- |
| `remoteChainSelector` | `uint64` | No | The identifier of the chain being removed |
### Locked
Emitted when tokens are locked by the pool.
```solidity
event Locked(address indexed sender, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------------- |
| `sender` | `address` | Yes | The address initiating the lock operation |
| `amount` | `uint256` | No | The amount of tokens locked |
### Minted
Emitted when tokens are minted by the pool.
```solidity
event Minted(address indexed sender, address indexed recipient, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | ----------------------------------------- |
| `sender` | `address` | Yes | The address initiating the mint operation |
| `recipient` | `address` | Yes | The address receiving the minted tokens |
| `amount` | `uint256` | No | The amount of tokens minted |
### RemotePoolAdded
Emitted when a new remote pool is added via [`addRemotePool`](#addremotepool) or [`applyChainUpdates`](#applychainupdates).
```solidity
event RemotePoolAdded(uint64 indexed remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | -------------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The identifier of the chain for the new pool |
| `remotePoolAddress` | `bytes` | No | The address of the newly added pool |
### Released
Emitted when tokens are released by the pool.
```solidity
event Released(address indexed sender, address indexed recipient, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | -------------------------------------------- |
| `sender` | `address` | Yes | The address initiating the release operation |
| `recipient` | `address` | Yes | The address receiving the released tokens |
| `amount` | `uint256` | No | The amount of tokens released |
### RemotePoolRemoved
Emitted when a remote pool is removed via [`removeRemotePool`](#removeremotepool).
```solidity
event RemotePoolRemoved(uint64 indexed remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------------- | -------- | ------- | ---------------------------------------- |
| `remoteChainSelector` | `uint64` | Yes | The identifier of the chain for the pool |
| `remotePoolAddress` | `bytes` | No | The address of the removed pool |
### RouterUpdated
Emitted when the router address is updated via [`setRouter`](#setrouter).
```solidity
event RouterUpdated(address oldRouter, address newRouter);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | ------------------------------------ |
| `oldRouter` | `address` | No | The previous router contract address |
| `newRouter` | `address` | No | The new router contract address |
### RateLimitAdminSet
Emitted when the rate limit administrator is changed via [`setRateLimitAdmin`](#setratelimitadmin).
```solidity
event RateLimitAdminSet(address rateLimitAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| ---------------- | --------- | ------- | ---------------------------------------- |
| `rateLimitAdmin` | `address` | No | The new rate limit administrator address |
## Errors
### AllowListNotEnabled
```solidity
error AllowListNotEnabled();
```
### CallerIsNotARampOnRouter
```solidity
error CallerIsNotARampOnRouter(address caller);
```
### ChainAlreadyExists
```solidity
error ChainAlreadyExists(uint64 chainSelector);
```
**Parameters**
| Name | Type | Description |
| --------------- | -------- | --------------------------------------------- |
| `chainSelector` | `uint64` | The selector of the chain that already exists |
### ChainNotAllowed
```solidity
error ChainNotAllowed(uint64 remoteChainSelector);
```
### CursedByRMN
```solidity
error CursedByRMN();
```
### InvalidDecimalArgs
```solidity
error InvalidDecimalArgs(uint8 expected, uint8 actual);
```
**Parameters**
| Name | Type | Description |
| ---------- | ------- | -------------------------------------- |
| `expected` | `uint8` | The expected number of decimals |
| `actual` | `uint8` | The actual number of decimals provided |
### InvalidRemoteChainDecimals
```solidity
error InvalidRemoteChainDecimals(bytes sourcePoolData);
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------- | -------------------------------------- |
| `sourcePoolData` | `bytes` | The invalid decimal configuration data |
### InvalidRemotePoolForChain
```solidity
error InvalidRemotePoolForChain(uint64 remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | -------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector being queried |
| `remotePoolAddress` | `bytes` | The invalid pool address |
### InvalidSourcePoolAddress
```solidity
error InvalidSourcePoolAddress(bytes sourcePoolAddress);
```
### InvalidToken
```solidity
error InvalidToken(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------------------------- |
| `token` | `address` | The address of the invalid token |
### MismatchedArrayLengths
```solidity
error MismatchedArrayLengths();
```
### NonExistentChain
```solidity
error NonExistentChain(uint64 remoteChainSelector);
```
### OverflowDetected
```solidity
error OverflowDetected(uint8 remoteDecimals, uint8 localDecimals, uint256 remoteAmount);
```
**Parameters**
| Name | Type | Description |
| ---------------- | --------- | ----------------------------------- |
| `remoteDecimals` | `uint8` | The decimals on the remote chain |
| `localDecimals` | `uint8` | The decimals on the local chain |
| `remoteAmount` | `uint256` | The amount that caused the overflow |
### PoolAlreadyAdded
```solidity
error PoolAlreadyAdded(uint64 remoteChainSelector, bytes remotePoolAddress);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ---------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector where the pool exists |
| `remotePoolAddress` | `bytes` | The address of the already existing pool |
### SenderNotAllowed
```solidity
error SenderNotAllowed(address sender);
```
### Unauthorized
```solidity
error Unauthorized(address caller);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------------- |
| `caller` | `address` | The address that attempted the action |
### ZeroAddressNotAllowed
```solidity
error ZeroAddressNotAllowed();
```
## Structs
### ChainUpdate
Configuration data for adding or updating a chain.
```solidity
struct ChainUpdate {
uint64 remoteChainSelector;
bytes[] remotePoolAddresses;
bytes remoteTokenAddress;
RateLimiter.Config outboundRateLimiterConfig;
RateLimiter.Config inboundRateLimiterConfig;
}
```
### RemoteChainConfig
Internal configuration for a remote chain.
```solidity
struct RemoteChainConfig {
RateLimiter.TokenBucket outboundRateLimiterConfig;
RateLimiter.TokenBucket inboundRateLimiterConfig;
bytes remoteTokenAddress;
EnumerableSet.Bytes32Set remotePools;
}
```
## State Variables
### i_token
The token managed by this pool. Currently supports one token per pool.
```solidity
IERC20 internal immutable i_token;
```
### i_tokenDecimals
The number of decimals for the managed token.
```solidity
uint8 internal immutable i_tokenDecimals;
```
### i_rmnProxy
The Risk Management Network (RMN) proxy address.
```solidity
address internal immutable i_rmnProxy;
```
### i_allowlistEnabled
Flag indicating if the pool uses access control.
```solidity
bool internal immutable i_allowlistEnabled;
```
### s_allowlist
Set of addresses authorized to initiate cross-chain operations.
```solidity
EnumerableSet.AddressSet internal s_allowlist;
```
### s_router
The CCIP Router contract address.
```solidity
IRouter internal s_router;
```
### s_remoteChainSelectors
Set of authorized chain selectors for cross-chain operations.
```solidity
EnumerableSet.UintSet internal s_remoteChainSelectors;
```
### s_remoteChainConfigs
Configuration for each remote chain, including rate limits and token details.
```solidity
mapping(uint64 remoteChainSelector => RemoteChainConfig) internal s_remoteChainConfigs;
```
### s_remotePoolAddresses
Maps hashed pool addresses to their original form for verification.
```solidity
mapping(bytes32 poolAddressHash => bytes poolAddress) internal s_remotePoolAddresses;
```
### s_rateLimitAdmin
The address authorized to manage rate limits.
```solidity
address internal s_rateLimitAdmin;
```
## Functions
### _applyAllowListUpdates
Internal version of applyAllowListUpdates to allow for reuse in the constructor.
```solidity
function _applyAllowListUpdates(address[] memory removes, address[] memory adds) internal;
```
**Parameters**
| Name | Type | Description |
| --------- | ----------- | ----------------------------------------------- |
| `removes` | `address[]` | Array of addresses to remove from the allowlist |
| `adds` | `address[]` | Array of addresses to add to the allowlist |
### _calculateLocalAmount
Calculates the local amount based on the remote amount and decimals.
*This function protects against overflows. If there is a transaction that hits the overflow check, it is
probably incorrect as that means the amount cannot be represented on this chain. If the local decimals have been
wrongly configured, the token developer could redeploy the pool with the correct decimals and manually re-execute the
CCIP tx to fix the issue.*
```solidity
function _calculateLocalAmount(uint256 remoteAmount, uint8 remoteDecimals) internal view virtual returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---------------- | --------- | ---------------------------------------------- |
| `remoteAmount` | `uint256` | The amount on the remote chain. |
| `remoteDecimals` | `uint8` | The decimals of the token on the remote chain. |
**Returns**
| Name | Type | Description |
| -------- | --------- | ----------------- |
| `` | `uint256` | The local amount. |
### _checkAllowList
Internal function to verify if a sender is authorized when allowlist is enabled.
```solidity
function _checkAllowList(address sender) internal view;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `sender` | `address` | The address to check for permission |
### _consumeInboundRateLimit
Internal function to consume rate limiting capacity for incoming transfers.
```solidity
function _consumeInboundRateLimit(uint64 remoteChainSelector, uint256 amount) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | --------- | --------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector for the source chain |
| `amount` | `uint256` | The amount of tokens being transferred |
### _consumeOutboundRateLimit
Internal function to consume rate limiting capacity for outgoing transfers.
```solidity
function _consumeOutboundRateLimit(uint64 remoteChainSelector, uint256 amount) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | --------- | -------------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector for the destination chain |
| `amount` | `uint256` | The amount of tokens being transferred |
### _encodeLocalDecimals
Internal function to encode the local token's decimals for cross-chain communication.
```solidity
function _encodeLocalDecimals() internal view virtual returns (bytes memory);
```
**Returns**
| Type | Description |
| ------- | --------------------------------------------- |
| `bytes` | ABI-encoded decimal places of the local token |
### _onlyOffRamp
Checks whether remote chain selector is configured on this contract, and if the msg.sender
is a permissioned offRamp for the given chain on the Router.
```solidity
function _onlyOffRamp(uint64 remoteChainSelector) internal view;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to validate authorization for |
### _onlyOnRamp
Checks whether remote chain selector is configured on this contract, and if the msg.sender
is a permissioned onRamp for the given chain on the Router.
```solidity
function _onlyOnRamp(uint64 remoteChainSelector) internal view;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to validate authorization for |
### _parseRemoteDecimals
Internal function to decode the decimal configuration received from a remote chain.
```solidity
function _parseRemoteDecimals(bytes memory sourcePoolData) internal view virtual returns (uint8);
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------- | -------------------------------------- |
| `sourcePoolData` | `bytes` | The encoded decimal configuration data |
**Returns**
| Type | Description |
| ------- | ----------------------------------------------- |
| `uint8` | The number of decimals used on the remote chain |
### _setRateLimitConfig
Internal function to update rate limit configuration for a chain.
```solidity
function _setRateLimitConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------------------- | ----------------------------------------------- |
| `remoteChainSelector` | `uint64` | The chain selector to configure |
| `outboundConfig` | `RateLimiter.Config` | Rate limit configuration for outgoing transfers |
| `inboundConfig` | `RateLimiter.Config` | Rate limit configuration for incoming transfers |
### _setRemotePool
Internal function to add a pool address to the allowed remote token pools for a chain. Called during chain configuration and when adding individual remote pools.
```solidity
function _setRemotePool(uint64 remoteChainSelector, bytes memory remotePoolAddress) internal;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to add the pool for |
| `remotePoolAddress` | `bytes` | The address of the remote pool (encoded to support non-EVM chains) |
### _validateLockOrBurn
Internal function to validate lock or burn operations.
```solidity
function _validateLockOrBurn(Pool.LockOrBurnInV1 calldata lockOrBurnIn) internal;
```
### _validateReleaseOrMint
Internal function to validate release or mint operations.
```solidity
function _validateReleaseOrMint(Pool.ReleaseOrMintInV1 calldata releaseOrMintIn) internal;
```
### addRemotePool
Adds a new pool address for a remote chain.
```solidity
function addRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) external onlyOwner;
```
### applyAllowListUpdates
Apply updates to the allow list.
```solidity
function applyAllowListUpdates(address[] calldata removes, address[] calldata adds) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| --------- | ----------- | ---------------------------- |
| `removes` | `address[]` | The addresses to be removed. |
| `adds` | `address[]` | The addresses to be added. |
### applyChainUpdates
Updates chain configurations in bulk.
```solidity
function applyChainUpdates(
uint64[] calldata remoteChainSelectorsToRemove,
ChainUpdate[] calldata chainsToAdd
) external virtual onlyOwner;
```
### constructor
```solidity
constructor(IERC20 token, uint8 localTokenDecimals, address[] memory allowlist, address rmnProxy, address router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ----------- | -------------------------------------------- |
| `token` | `IERC20` | The token to be managed by this pool |
| `localTokenDecimals` | `uint8` | The token's decimal places on this chain |
| `allowlist` | `address[]` | Initial set of authorized addresses (if any) |
| `rmnProxy` | `address` | The Risk Management Network proxy address |
| `router` | `address` | The CCIP Router contract address |
### getAllowList
Gets the allowed addresses.
```solidity
function getAllowList() external view returns (address[] memory);
```
**Returns**
| Name | Type | Description |
| -------- | ----------- | ---------------------- |
| `` | `address[]` | The allowed addresses. |
### getAllowListEnabled
Returns whether allowlist functionality is active.
```solidity
function getAllowListEnabled() external view returns (bool);
```
**Returns**
| Name | Type | Description |
| -------- | ------ | ------------------------------ |
| `` | `bool` | true is enabled, false if not. |
### getCurrentInboundRateLimiterState
Returns the current state of inbound rate limiting for a chain.
```solidity
function getCurrentInboundRateLimiterState(
uint64 remoteChainSelector
) external view returns (RateLimiter.TokenBucket memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to get rate limiter state for |
**Returns**
| Type | Description |
| ------------------------------------------------------------------------------------ | ----------------------------------------- |
| [`RateLimiter.TokenBucket`](/ccip/api-reference/evm/v1.5.1/rate-limiter#tokenbucket) | Current state of the inbound rate limiter |
### getCurrentOutboundRateLimiterState
Returns the current state of outbound rate limiting for a chain.
```solidity
function getCurrentOutboundRateLimiterState(
uint64 remoteChainSelector
) external view returns (RateLimiter.TokenBucket memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to get rate limiter state for |
**Returns**
| Type | Description |
| ------------------------------------------------------------------------------------ | ------------------------------------------ |
| [`RateLimiter.TokenBucket`](/ccip/api-reference/evm/v1.5.1/rate-limiter#tokenbucket) | Current state of the outbound rate limiter |
### getRateLimitAdmin
Returns the current rate limit administrator address.
```solidity
function getRateLimitAdmin() external view returns (address);
```
### getRemotePools
Returns the configured pool addresses for a remote chain.
```solidity
function getRemotePools(uint64 remoteChainSelector) public view returns (bytes[] memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
**Returns**
| Type | Description |
| --------- | ----------------------------------------------- |
| `bytes[]` | Array of encoded pool addresses on remote chain |
### getRemoteToken
Returns the token address on a remote chain.
```solidity
function getRemoteToken(uint64 remoteChainSelector) public view returns (bytes memory);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
**Returns**
| Type | Description |
| ------- | --------------------------------------------- |
| `bytes` | The encoded token address on the remote chain |
### getRmnProxy
Returns the Risk Management Network proxy address.
```solidity
function getRmnProxy() public view returns (address rmnProxy);
```
**Returns**
| Type | Description |
| --------- | ------------------------------ |
| `address` | The RMN proxy contract address |
### getRouter
Returns the current router address.
```solidity
function getRouter() public view returns (address router);
```
**Returns**
| Type | Description |
| --------- | -------------------------------- |
| `address` | The CCIP router contract address |
### getSupportedChains
Returns all configured chain selectors.
```solidity
function getSupportedChains() public view returns (uint64[] memory);
```
**Returns**
| Type | Description |
| ---------- | ----------------------------------- |
| `uint64[]` | Array of configured chain selectors |
### getToken
Returns the token managed by this pool.
```solidity
function getToken() public view returns (IERC20 token);
```
**Returns**
| Type | Description |
| -------- | -------------------------- |
| `IERC20` | The token contract address |
### getTokenDecimals
Returns the number of decimals for the managed token.
```solidity
function getTokenDecimals() public view virtual returns (uint8 decimals);
```
**Returns**
| Type | Description |
| ------- | ------------------------------------------ |
| `uint8` | The number of decimal places for the token |
### isRemotePool
Verifies if a pool address is configured for a remote chain.
```solidity
function isRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) public view returns (bool);
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | --------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain identifier |
| `remotePoolAddress` | `bytes` | The pool address to verify |
**Returns**
| Type | Description |
| ------ | -------------------------------------------- |
| `bool` | True if the pool is configured for the chain |
### isSupportedChain
Checks if a chain is configured in the pool.
```solidity
function isSupportedChain(uint64 remoteChainSelector) public view returns (bool);
```
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the chain is configured in the pool |
### isSupportedToken
Checks if a given token is supported by this pool.
```solidity
function isSupportedToken(address token) public view virtual returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------------------- |
| `token` | `address` | The token address to check |
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the token is supported by this pool |
### removeRemotePool
Removes a pool address from a remote chain's configuration.
```solidity
function removeRemotePool(uint64 remoteChainSelector, bytes calldata remotePoolAddress) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------ |
| `remoteChainSelector` | `uint64` | The chain selector to remove the pool from |
| `remotePoolAddress` | `bytes` | The address of the pool to remove |
### setChainRateLimiterConfig
Sets the chain rate limiter config.
```solidity
function setChainRateLimiterConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) external;
```
**Parameters**
| Name | Type | Description |
| --------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `remoteChainSelector` | `uint64` | The remote chain selector for which the rate limits apply. |
| `outboundConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.5.1/rate-limiter#config) | The new outbound rate limiter config, meaning the onRamp rate limits for the given chain. |
| `inboundConfig` | [`RateLimiter.Config`](/ccip/api-reference/evm/v1.5.1/rate-limiter#config) | The new inbound rate limiter config, meaning the offRamp rate limits for the given chain. |
### setChainRateLimiterConfigs
Updates rate limit configurations for multiple chains.
```solidity
function setChainRateLimiterConfigs(
uint64[] calldata remoteChainSelectors,
RateLimiter.Config[] calldata outboundConfigs,
RateLimiter.Config[] calldata inboundConfigs
) external;
```
**Parameters**
| Name | Type | Description |
| ---------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `remoteChainSelectors` | `uint64[]` | The chain selectors to configure |
| `outboundConfigs` | [`RateLimiter.Config[]`](/ccip/api-reference/evm/v1.5.1/rate-limiter#config) | The new outbound rate limiter configs, meaning the onRamp rate limits for the given chains |
| `inboundConfigs` | [`RateLimiter.Config[]`](/ccip/api-reference/evm/v1.5.1/rate-limiter#config) | The new inbound rate limiter configs, meaning the offRamp rate limits for the given chains |
### setRateLimitAdmin
Sets the address authorized to manage rate limits.
```solidity
function setRateLimitAdmin(address rateLimitAdmin) external onlyOwner;
```
### setRouter
Updates the router contract address.
```solidity
function setRouter(address newRouter) public onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ----------- | --------- | ------------------------------- |
| `newRouter` | `address` | The new router contract address |
### supportsInterface
Implements ERC165 interface detection.
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | --------------------------------------------- |
| `bool` | True if the contract implements the interface |
## Rate Limiting
---
# CCIP v1.5.1 BurnMintTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/burn-mint-token-pool
## BurnMintTokenPool
A specialized token pool implementation that handles the minting and burning of third-party tokens using the standard `burn(amount)` function.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/pools/BurnMintTokenPool.sol)
**Inherits:**
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.5.1/burn-mint-token-pool-abstract)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.5.1/i-type-and-version)
## State Variables
### typeAndVersion
```solidity
string public constant override typeAndVersion = "BurnMintTokenPool 1.5.1";
```
**Returns**
| Type | Description |
| -------- | ------------------------------------------------- |
| `string` | The contract identifier "BurnMintTokenPool 1.5.1" |
## Functions
### _burn
Internal function that executes the token burning operation using the standard burn interface.
```solidity
function _burn(uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------- |
| `amount` | `uint256` | The number of tokens to burn |
### constructor
```solidity
constructor(
IBurnMintERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ---------------- | ---------------------------------------------------- |
| `token` | `IBurnMintERC20` | The third-party token contract to manage |
| `localTokenDecimals` | `uint8` | The decimal precision for the local token |
| `allowlist` | `address[]` | Initial list of addresses authorized to use the pool |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `router` | `address` | Address of the router contract |
---
# CCIP v1.5.1 BurnMintTokenPoolAbstract Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/burn-mint-token-pool-abstract
## BurnMintTokenPoolAbstract
An abstract contract that implements core token pool functionality for burning and minting operations in cross-chain token transfers.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/pools/BurnMintTokenPoolAbstract.sol)
**Inherits:**
- [TokenPool](/ccip/api-reference/evm/v1.5.1/token-pool)
## Events
### Burned
```solidity
event Burned(address indexed sender, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------------- |
| `sender` | `address` | Yes | The address initiating the burn operation |
| `amount` | `uint256` | No | The number of tokens burned |
### Minted
```solidity
event Minted(address indexed sender, address indexed recipient, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| ----------- | --------- | ------- | ----------------------------------------- |
| `sender` | `address` | Yes | The address initiating the mint operation |
| `recipient` | `address` | Yes | The address receiving the minted tokens |
| `amount` | `uint256` | No | The number of tokens minted |
## Functions
### _burn
Internal function that executes the token burning operation.
```solidity
function _burn(uint256 amount) internal virtual;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------- |
| `amount` | `uint256` | The number of tokens to burn |
### lockOrBurn
Burns tokens in the pool during a cross-chain transfer.
```solidity
function lockOrBurn(
Pool.LockOrBurnInV1 calldata lockOrBurnIn
) external virtual override returns (Pool.LockOrBurnOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| -------------- | --------------------------------------------------------------------------- | --------------------------------------- |
| `lockOrBurnIn` | [`Pool.LockOrBurnInV1`](/ccip/api-reference/evm/v1.5.1/pool#lockorburninv1) | Input parameters for the burn operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------- | ------------------------------------------------ |
| [`Pool.LockOrBurnOutV1`](/ccip/api-reference/evm/v1.5.1/pool#lockorburnoutv1) | Contains destination token address and pool data |
### releaseOrMint
Mints new tokens to a recipient during a cross-chain transfer.
```solidity
function releaseOrMint(
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn
) external virtual override returns (Pool.ReleaseOrMintOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| ----------------- | --------------------------------------------------------------------------------- | --------------------------------------- |
| `releaseOrMintIn` | [`Pool.ReleaseOrMintInV1`](/ccip/api-reference/evm/v1.5.1/pool#releaseormintinv1) | Input parameters for the mint operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------------- | ------------------------------------------------ |
| [`Pool.ReleaseOrMintOutV1`](/ccip/api-reference/evm/v1.5.1/pool#releaseormintoutv1) | Contains the final amount minted in local tokens |
---
# CCIP v1.5.1 BurnFromMintTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/burn-from-mint-token-pool
## BurnFromMintTokenPool
A specialized token pool contract that manages third-party tokens through minting and burning operations, specifically using the `burnFrom` function.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/pools/BurnFromMintTokenPool.sol)
**Inherits:**
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.5.1/burn-mint-token-pool-abstract)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.5.1/i-type-and-version)
## State Variables
### typeAndVersion
```solidity
string public constant override typeAndVersion = "BurnFromMintTokenPool 1.5.1";
```
**Returns**
| Type | Description |
| -------- | ----------------------------------------------------- |
| `string` | The contract identifier "BurnFromMintTokenPool 1.5.1" |
## Functions
### _burn
Internal function that executes the token burning operation.
```solidity
function _burn(uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------ |
| `amount` | `uint256` | The quantity of tokens to burn |
### constructor
```solidity
constructor(
IBurnMintERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ---------------- | ------------------------------------------------------ |
| `token` | `IBurnMintERC20` | Address of the token contract to be managed |
| `localTokenDecimals` | `uint8` | Decimal precision of the local token |
| `allowlist` | `address[]` | List of addresses authorized to interact with the pool |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `router` | `address` | Address of the router contract |
---
# CCIP v1.5.1 LockReleaseTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/lock-release-token-pool
## LockReleaseTokenPool
A specialized token pool for managing native tokens through a lock and release mechanism, with support for liquidity management.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/pools/LockReleaseTokenPool.sol)
**Inherits:**
- [TokenPool](/ccip/api-reference/evm/v1.5.1/token-pool)
- [ITypeAndVersion](/ccip/api-reference/evm/v1.5.1/i-type-and-version)
## Events
### LiquidityTransferred
```solidity
event LiquidityTransferred(address indexed from, uint256 amount);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ----------------------------------- |
| `from` | `address` | Yes | The source pool address |
| `amount` | `uint256` | No | The amount of liquidity transferred |
## Errors
### InsufficientLiquidity
```solidity
error InsufficientLiquidity();
```
### LiquidityNotAccepted
```solidity
error LiquidityNotAccepted();
```
## State Variables
### i_acceptLiquidity
```solidity
bool internal immutable i_acceptLiquidity;
```
### s_rebalancer
```solidity
address internal s_rebalancer;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "LockReleaseTokenPool 1.5.1";
```
## Functions
### canAcceptLiquidity
Determines whether the pool can accept external liquidity.
```solidity
function canAcceptLiquidity() external view returns (bool);
```
**Returns**
| Type | Description |
| ------ | ------------------------------------------- |
| `bool` | True if the pool accepts external liquidity |
### constructor
```solidity
constructor(
IERC20 token,
uint8 localTokenDecimals,
address[] memory allowlist,
address rmnProxy,
bool acceptLiquidity,
address router
) TokenPool(token, localTokenDecimals, allowlist, rmnProxy, router);
```
**Parameters**
| Name | Type | Description |
| -------------------- | ----------- | ------------------------------------------- |
| `token` | `IERC20` | The token contract to manage |
| `localTokenDecimals` | `uint8` | The decimal precision for the local token |
| `allowlist` | `address[]` | Initial list of authorized addresses |
| `rmnProxy` | `address` | Address of the RMN proxy contract |
| `acceptLiquidity` | `bool` | Whether the pool accepts external liquidity |
| `router` | `address` | Address of the router contract |
### getRebalancer
Returns the current rebalancer address.
```solidity
function getRebalancer() external view returns (address);
```
**Returns**
| Type | Description |
| --------- | ------------------------------------- |
| `address` | The current liquidity manager address |
### lockOrBurn
Locks tokens in the pool for cross-chain transfer.
```solidity
function lockOrBurn(
Pool.LockOrBurnInV1 calldata lockOrBurnIn
) external virtual override returns (Pool.LockOrBurnOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| -------------- | --------------------------------------------------------------------------- | --------------------------------------- |
| `lockOrBurnIn` | [`Pool.LockOrBurnInV1`](/ccip/api-reference/evm/v1.5.1/pool#lockorburninv1) | Input parameters for the lock operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------- | ------------------------------------------------ |
| [`Pool.LockOrBurnOutV1`](/ccip/api-reference/evm/v1.5.1/pool#lockorburnoutv1) | Contains destination token address and pool data |
### provideLiquidity
Adds external liquidity to the pool.
```solidity
function provideLiquidity(uint256 amount) external;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ---------------------------------- |
| `amount` | `uint256` | The amount of liquidity to provide |
### releaseOrMint
Releases tokens from the pool to a recipient.
```solidity
function releaseOrMint(
Pool.ReleaseOrMintInV1 calldata releaseOrMintIn
) external virtual override returns (Pool.ReleaseOrMintOutV1 memory);
```
**Parameters**
| Name | Type | Description |
| ----------------- | --------------------------------------------------------------------------------- | ------------------------------------------ |
| `releaseOrMintIn` | [`Pool.ReleaseOrMintInV1`](/ccip/api-reference/evm/v1.5.1/pool#releaseormintinv1) | Input parameters for the release operation |
**Returns**
| Type | Description |
| ----------------------------------------------------------------------------------- | -------------------------------------------------- |
| [`Pool.ReleaseOrMintOutV1`](/ccip/api-reference/evm/v1.5.1/pool#releaseormintoutv1) | Contains the final amount released in local tokens |
### setRebalancer
Updates the rebalancer address.
```solidity
function setRebalancer(address rebalancer) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | --------------------------------- |
| `rebalancer` | `address` | The new rebalancer address to set |
### supportsInterface
Checks interface support using ERC165.
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | ---------------------------------- |
| `bool` | True if the interface is supported |
### transferLiquidity
Transfers liquidity from an older pool version.
```solidity
function transferLiquidity(address from, uint256 amount) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `from` | `address` | The address of the source pool |
| `amount` | `uint256` | The amount of liquidity to transfer |
### withdrawLiquidity
Removes liquidity from the pool.
```solidity
function withdrawLiquidity(uint256 amount) external;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------------- |
| `amount` | `uint256` | The amount of liquidity to withdraw |
---
# CCIP v1.5.1 TokenAdminRegistry Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/token-admin-registry
## TokenAdminRegistry
A contract that manages token pool configurations and administrator access for CCIP-enabled tokens.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenAdminRegistry.sol)
**Inherits:**
- [ITypeAndVersion](/ccip/api-reference/evm/v1.5.1/i-type-and-version)
## Functions
### acceptAdminRole
Accepts the administrator role for a token.
```solidity
function acceptAdminRole(address localToken) external;
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | ------------------------------------------ |
| `localToken` | `address` | The token to accept the administrator role |
### addRegistryModule
Adds a new registry module to the allowed modules list.
```solidity
function addRegistryModule(address module) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------- |
| `module` | `address` | The module to authorize |
### getAllConfiguredTokens
Returns a paginated list of configured tokens.
```solidity
function getAllConfiguredTokens(uint64 startIndex, uint64 maxCount) external view returns (address[] memory tokens);
```
**Parameters**
| Name | Type | Description |
| ------------ | -------- | --------------------------------------------------------- |
| `startIndex` | `uint64` | Starting position in the list (0 for beginning) |
| `maxCount` | `uint64` | Maximum tokens to retrieve (use type(uint64).max for all) |
**Returns**
| Type | Description |
| ----------- | ---------------------------------- |
| `address[]` | List of configured token addresses |
### getPool
Returns the pool address for a specific token.
```solidity
function getPool(address token) external view returns (address);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ------------------ |
| `token` | `address` | The token to query |
**Returns**
| Type | Description |
| --------- | ------------------------ |
| `address` | The token's pool address |
### getPools
Returns pool addresses for multiple tokens.
```solidity
function getPools(address[] calldata tokens) external view returns (address[] memory);
```
**Parameters**
| Name | Type | Description |
| -------- | ----------- | --------------- |
| `tokens` | `address[]` | Tokens to query |
**Returns**
| Type | Description |
| ----------- | ------------------------------------- |
| `address[]` | Array of corresponding pool addresses |
### getTokenConfig
Returns the complete configuration for a token.
```solidity
function getTokenConfig(address token) external view returns (TokenConfig memory);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | -------------- |
| `token` | `address` | Token to query |
**Returns**
| Type | Description |
| ------------- | ---------------------------- |
| `TokenConfig` | Complete token configuration |
### isAdministrator
Checks if an address is the administrator for a token.
```solidity
function isAdministrator(address localToken, address administrator) external view returns (bool);
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | ----------------- |
| `localToken` | `address` | Token to check |
| `administrator` | `address` | Address to verify |
**Returns**
| Type | Description |
| ------ | ---------------------------------------- |
| `bool` | True if address is current administrator |
### isRegistryModule
Checks if an address is an authorized registry module.
```solidity
function isRegistryModule(address module) public view returns (bool);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------- |
| `module` | `address` | Address to verify |
**Returns**
| Type | Description |
| ------ | ------------------------------------ |
| `bool` | True if address is authorized module |
### proposeAdministrator
Proposes an initial administrator for a token.
```solidity
function proposeAdministrator(address localToken, address administrator) external;
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | ------------------------------ |
| `localToken` | `address` | Token to configure |
| `administrator` | `address` | Proposed administrator address |
### removeRegistryModule
Removes a registry module from the allowed modules list.
```solidity
function removeRegistryModule(address module) external onlyOwner;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------- |
| `module` | `address` | The module to remove |
### setPool
Sets or updates the pool for a token.
```solidity
function setPool(address localToken, address pool) external onlyTokenAdmin(localToken);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | --------------------------------- |
| `localToken` | `address` | Token to configure |
| `pool` | `address` | New pool address (or 0 to delist) |
### transferAdminRole
Initiates transfer of administrator role.
```solidity
function transferAdminRole(address localToken, address newAdmin) external onlyTokenAdmin(localToken);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | -------------------------------------------------------- |
| `localToken` | `address` | The token contract whose admin role is being transferred |
| `newAdmin` | `address` | The proposed new administrator address (or 0 to cancel) |
## Events
### AdministratorTransferRequested
```solidity
event AdministratorTransferRequested(address indexed token, address indexed currentAdmin, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------------- | --------- | ------- | -------------------------------------------------------- |
| `token` | `address` | Yes | The token contract whose admin role is being transferred |
| `currentAdmin` | `address` | Yes | The current administrator address |
| `newAdmin` | `address` | Yes | The proposed new administrator address |
### AdministratorTransferred
```solidity
event AdministratorTransferred(address indexed token, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| ---------- | --------- | ------- | -------------------------------------------------------- |
| `token` | `address` | Yes | The token contract whose admin role has been transferred |
| `newAdmin` | `address` | Yes | The new administrator address |
### PoolSet
```solidity
event PoolSet(address indexed token, address indexed previousPool, address indexed newPool);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------------- | --------- | ------- | ---------------------------------- |
| `token` | `address` | Yes | The token address being configured |
| `previousPool` | `address` | Yes | The previous pool address |
| `newPool` | `address` | Yes | The new pool address |
### RegistryModuleAdded
```solidity
event RegistryModuleAdded(address module);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | ------------------------------------------ |
| `module` | `address` | No | The address of the newly authorized module |
### RegistryModuleRemoved
```solidity
event RegistryModuleRemoved(address indexed module);
```
**Parameters**
| Name | Type | Indexed | Description |
| -------- | --------- | ------- | --------------------------------- |
| `module` | `address` | Yes | The address of the removed module |
## Errors
### AddressZero
```solidity
error ZeroAddress();
```
### AlreadyRegistered
```solidity
error AlreadyRegistered(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | --------------------------------------------------- |
| `token` | `address` | The token address that already has an administrator |
### InvalidTokenPoolToken
```solidity
error InvalidTokenPoolToken(address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | --------------------------------------------------- |
| `token` | `address` | The token address that is not supported by the pool |
### OnlyAdministrator
```solidity
error OnlyAdministrator(address sender, address token);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
| `token` | `address` | The token address being accessed |
### OnlyPendingAdministrator
```solidity
error OnlyPendingAdministrator(address sender, address token);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
| `token` | `address` | The token address being accessed |
### OnlyRegistryModuleOrOwner
```solidity
error OnlyRegistryModuleOrOwner(address sender);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | --------------------------------- |
| `sender` | `address` | The unauthorized caller's address |
## Structs
### TokenConfig
Configuration data structure for each token.
```solidity
struct TokenConfig {
address administrator;
address pendingAdministrator;
address tokenPool;
}
```
## State Variables
### s_registryModules
```solidity
EnumerableSet.AddressSet internal s_registryModules;
```
### s_tokenConfig
```solidity
mapping(address token => TokenConfig) internal s_tokenConfig;
```
### s_tokens
```solidity
EnumerableSet.AddressSet internal s_tokens;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "TokenAdminRegistry 1.5.0";
```
---
# CCIP v1.5.1 RegistryModuleOwnerCustom Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/registry-module-owner-custom
## RegistryModuleOwnerCustom
A contract that facilitates token administrator registration through various ownership patterns.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol)
**Inherits:**
- [ITypeAndVersion](/ccip/api-reference/evm/v1.5.1/i-type-and-version)
## Events
### AdministratorRegistered
```solidity
event AdministratorRegistered(address indexed token, address indexed administrator);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------- | --------- | ------- | ------------------------------------ |
| `token` | `address` | Yes | The token contract address |
| `administrator` | `address` | Yes | The registered administrator address |
## Errors
### AddressZero
```solidity
error AddressZero();
```
### CanOnlySelfRegister
```solidity
error CanOnlySelfRegister(address admin, address token);
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------- |
| `admin` | `address` | The expected administrator address |
| `token` | `address` | The token contract address |
### RequiredRoleNotFound
```solidity
error RequiredRoleNotFound(address msgSender, bytes32 role, address token);
```
**Parameters**
| Name | Type | Description |
| ----------- | --------- | ---------------------------- |
| `msgSender` | `address` | The caller's address |
| `role` | `bytes32` | The required role identifier |
| `token` | `address` | The token contract address |
## State Variables
### i_tokenAdminRegistry
```solidity
ITokenAdminRegistry internal immutable i_tokenAdminRegistry;
```
### typeAndVersion
```solidity
string public constant override typeAndVersion = "RegistryModuleOwnerCustom 1.6.0";
```
## Functions
### constructor
```solidity
constructor(address tokenAdminRegistry);
```
**Parameters**
| Name | Type | Description |
| -------------------- | --------- | ---------------------------------------------- |
| `tokenAdminRegistry` | `address` | The address of the TokenAdminRegistry contract |
### registerAccessControlDefaultAdmin
Registers a token administrator using OpenZeppelin's AccessControl DEFAULT_ADMIN_ROLE.
```solidity
function registerAccessControlDefaultAdmin(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### registerAdminViaGetCCIPAdmin
Registers a token administrator using the `getCCIPAdmin` method.
```solidity
function registerAdminViaGetCCIPAdmin(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### registerAdminViaOwner
Registers a token administrator using the `owner` method.
```solidity
function registerAdminViaOwner(address token) external;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ---------------------------------------- |
| `token` | `address` | The token contract to register admin for |
### _registerAdmin
Internal function to handle administrator registration.
```solidity
function _registerAdmin(address token, address admin) internal;
```
**Parameters**
| Name | Type | Description |
| ------- | --------- | ------------------------------------------ |
| `token` | `address` | The token contract to register admin for |
| `admin` | `address` | The administrator address being registered |
---
# CCIP v1.5.1 BurnMintERC20 Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/burn-mint-erc20
## BurnMintERC20
An ERC20-compliant token contract that extends the standard functionality with controlled minting and burning capabilities through role-based access control.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol)
## Events
### CCIPAdminTransferred
```solidity
event CCIPAdminTransferred(address indexed previousAdmin, address indexed newAdmin);
```
**Parameters**
| Name | Type | Indexed | Description |
| --------------- | --------- | ------- | ------------------------------ |
| `previousAdmin` | `address` | Yes | The address that held the role |
| `newAdmin` | `address` | Yes | The address receiving the role |
## Errors
### InvalidRecipient
```solidity
error InvalidRecipient(address recipient);
```
### MaxSupplyExceeded
```solidity
error MaxSupplyExceeded(uint256 supplyAfterMint);
```
## State Variables
### BURNER_ROLE
```solidity
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
```
### MINTER_ROLE
```solidity
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
```
## Functions
### _approve
Internal function that manages token spending allowances with built-in safety checks.
```solidity
function _approve(address owner, address spender, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | ------------------------------------------------ |
| `owner` | `address` | The address that currently owns the tokens |
| `spender` | `address` | The address that will be allowed to spend tokens |
| `amount` | `uint256` | The number of tokens to approve for spending |
### burn (with amount)
Allows authorized addresses to burn (destroy) tokens from their own account.
```solidity
function burn(uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ------------------------------- |
| `amount` | `uint256` | The number of tokens to destroy |
### burn (with account)
Alternative burn function that allows burning tokens from a specified account.
```solidity
function burn(address account, uint256 amount) public virtual override;
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | --------------------------------- |
| `account` | `address` | The account to remove tokens from |
| `amount` | `uint256` | The number of tokens to destroy |
### burnFrom
Burns tokens from a specified account, requiring prior approval.
```solidity
function burnFrom(address account, uint256 amount) public override(IBurnMintERC20, ERC20Burnable) onlyRole(BURNER_ROLE);
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | --------------------------------- |
| `account` | `address` | The account to remove tokens from |
| `amount` | `uint256` | The number of tokens to destroy |
### constructor
```solidity
constructor(
string memory name,
string memory symbol,
uint8 decimals_,
uint256 maxSupply_,
uint256 preMint
) ERC20(name, symbol);
```
**Parameters**
| Name | Type | Description |
| ------------ | --------- | -------------------------------------------------- |
| `name` | `string` | The display name of the token |
| `symbol` | `string` | The token's ticker symbol |
| `decimals_` | `uint8` | The number of decimal places for token amounts |
| `maxSupply_` | `uint256` | The maximum allowed token supply (0 for unlimited) |
| `preMint` | `uint256` | The amount of tokens to mint to the deployer |
### decimals
Returns the token's decimal precision.
```solidity
function decimals() public view virtual override returns (uint8);
```
**Returns**
| Type | Description |
| ------- | --------------------------------------------------- |
| `uint8` | The number of decimal places used for token amounts |
### getCCIPAdmin
Retrieves the current CCIP administrator's address.
```solidity
function getCCIPAdmin() external view returns (address);
```
**Returns**
| Type | Description |
| --------- | ---------------------------------------- |
| `address` | The current CCIP administrator's address |
### grantMintAndBurnRoles
Assigns both minting and burning permissions to a single address.
```solidity
function grantMintAndBurnRoles(address burnAndMinter) external;
```
**Parameters**
| Name | Type | Description |
| --------------- | --------- | -------------------------------------------------------- |
| `burnAndMinter` | `address` | The address that will receive minting and burning rights |
### maxSupply
Returns the token's maximum supply limit.
```solidity
function maxSupply() public view virtual returns (uint256);
```
**Returns**
| Type | Description |
| --------- | ---------------------------------------------------- |
| `uint256` | The maximum allowed token supply (0 means unlimited) |
### mint
Creates new tokens and assigns them to a specified address.
```solidity
function mint(address account, uint256 amount) external override onlyRole(MINTER_ROLE);
```
**Parameters**
| Name | Type | Description |
| --------- | --------- | -------------------------------------------- |
| `account` | `address` | The address that will receive the new tokens |
| `amount` | `uint256` | The number of new tokens to create |
### setCCIPAdmin
Updates the CCIP administrator role.
```solidity
function setCCIPAdmin(address newAdmin) public onlyRole(DEFAULT_ADMIN_ROLE);
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | ------------------------------------------------------- |
| `newAdmin` | `address` | The address that will become the new CCIP administrator |
### supportsInterface
Determines whether the contract implements a specific interface.
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual override(AccessControl, IERC165) returns (bool);
```
**Parameters**
| Name | Type | Description |
| ------------- | -------- | --------------------------------- |
| `interfaceId` | `bytes4` | The interface identifier to check |
**Returns**
| Type | Description |
| ------ | --------------------------------------------------------- |
| `bool` | `true` if the contract implements the specified interface |
### _transfer
Internal function that handles token transfers between addresses.
```solidity
function _transfer(address from, address to, uint256 amount) internal virtual override;
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | -------------------------------- |
| `from` | `address` | The address sending tokens |
| `to` | `address` | The address receiving tokens |
| `amount` | `uint256` | The number of tokens to transfer |
---
# CCIP v1.5.1 RateLimiter Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/rate-limiter
## RateLimiter
A library implementing the Token Bucket algorithm for rate limiting cross-chain operations.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/ccip/libraries/RateLimiter.sol)
## Events
### TokensConsumed
```solidity
event TokensConsumed(uint256 tokens);
```
**Parameters**
| Name | Type | Description |
| -------- | --------- | ----------------------------- |
| `tokens` | `uint256` | The number of tokens consumed |
### ConfigChanged
```solidity
event ConfigChanged(Config config);
```
**Parameters**
| Name | Type | Description |
| -------- | ------------------- | ----------------------------- |
| `config` | [`Config`](#config) | The new configuration applied |
## Errors
### BucketOverfilled
```solidity
error BucketOverfilled();
```
### OnlyCallableByAdminOrOwner
```solidity
error OnlyCallableByAdminOrOwner();
```
### TokenMaxCapacityExceeded
```solidity
error TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress);
```
### TokenRateLimitReached
```solidity
error TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress);
```
### AggregateValueMaxCapacityExceeded
```solidity
error AggregateValueMaxCapacityExceeded(uint256 capacity, uint256 requested);
```
### AggregateValueRateLimitReached
```solidity
error AggregateValueRateLimitReached(uint256 minWaitInSeconds, uint256 available);
```
### InvalidRateLimitRate
```solidity
error InvalidRateLimitRate(Config rateLimiterConfig);
```
### DisabledNonZeroRateLimit
```solidity
error DisabledNonZeroRateLimit(Config config);
```
### RateLimitMustBeDisabled
```solidity
error RateLimitMustBeDisabled();
```
## Structs
### TokenBucket
Represents the state and configuration of a token bucket rate limiter.
```solidity
struct TokenBucket {
uint128 tokens;
uint32 lastUpdated;
bool isEnabled;
uint128 capacity;
uint128 rate;
}
```
### Config
Configuration parameters for the rate limiter.
```solidity
struct Config {
bool isEnabled;
uint128 capacity;
uint128 rate;
}
```
## Functions
### _consume
Removes tokens from the pool, reducing the available rate capacity for subsequent calls.
```solidity
function _consume(TokenBucket storage s_bucket, uint256 requestTokens, address tokenAddress) internal;
```
**Parameters**
| Name | Type | Description |
| --------------- | ----------------------------- | --------------------------------------------------------------- |
| `s_bucket` | [`TokenBucket`](#tokenbucket) | The token bucket to consume from |
| `requestTokens` | `uint256` | The number of tokens to consume |
| `tokenAddress` | `address` | The token address (use address(0) for aggregate value capacity) |
### _currentTokenBucketState
Retrieves the current state of a token bucket, including automatic refill calculations.
```solidity
function _currentTokenBucketState(TokenBucket memory bucket) internal view returns (TokenBucket memory);
```
**Returns**
| Type | Description |
| ----------------------------- | ------------------------------------- |
| [`TokenBucket`](#tokenbucket) | The current state of the token bucket |
### _setTokenBucketConfig
Updates the rate limiter configuration.
```solidity
function _setTokenBucketConfig(TokenBucket storage s_bucket, Config memory config) internal;
```
**Parameters**
| Name | Type | Description |
| ---------- | ----------------------------- | ------------------------------ |
| `s_bucket` | [`TokenBucket`](#tokenbucket) | The token bucket to configure |
| `config` | [`Config`](#config) | The new configuration to apply |
### _validateTokenBucketConfig
Validates rate limiter configuration parameters.
```solidity
function _validateTokenBucketConfig(Config memory config, bool mustBeDisabled) internal pure;
```
**Parameters**
| Name | Type | Description |
| ---------------- | ------------------- | ------------------------------------------ |
| `config` | [`Config`](#config) | The configuration to validate |
| `mustBeDisabled` | `bool` | Whether the configuration must be disabled |
### _calculateRefill
Calculates the number of tokens to add during a refill operation.
```solidity
function _calculateRefill(
uint256 capacity,
uint256 tokens,
uint256 timeDiff,
uint256 rate
) private pure returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---------- | --------- | ------------------------------------------- |
| `capacity` | `uint256` | Maximum token capacity |
| `tokens` | `uint256` | Current token balance |
| `timeDiff` | `uint256` | Time elapsed since last refill (in seconds) |
| `rate` | `uint256` | Tokens per second refill rate |
**Returns**
| Type | Description |
| --------- | ---------------------------------- |
| `uint256` | The new token balance after refill |
### _min
Returns the smaller of two numbers.
```solidity
function _min(uint256 a, uint256 b) internal pure returns (uint256);
```
**Parameters**
| Name | Type | Description |
| ---- | --------- | ------------- |
| `a` | `uint256` | First number |
| `b` | `uint256` | Second number |
**Returns**
| Type | Description |
| --------- | ------------------------------ |
| `uint256` | The smaller of the two numbers |
---
# CCIP v1.5.1 Error Codes and Messages API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/errors
When invoking the `ccipSend` [function](/ccip/api-reference/evm/v1.5.1/i-router-client#ccipsend), it is possible to encounter various errors. These might be thrown either by the CCIP router or by one of the downstream contracts called by the CCIP router. Below is a compiled list of potential errors you might encounter. Referencing this list will enable you to capture and handle these exceptions gracefully.
## Router Errors
## OnRamp Errors
## Rate Limiter Errors
## Token (ERC20) Errors
## BurnMintERC20 Errors
## Token Pool Errors
---
# CCIP v1.5.1 Ownable2Step Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/ownable-2-step
## Ownable2Step
A minimal contract that implements 2-step ownership transfer and nothing more. It's made to be minimal to reduce the impact of the bytecode size on any contract that inherits from it.
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/shared/access/Ownable2Step.sol)
## Events
### OwnershipTransferRequested
```solidity
event OwnershipTransferRequested(address indexed from, address indexed to);
```
**Parameters**
| Name | Type | Description |
| ------ | --------- | ------------------------------------- |
| `from` | `address` | Current owner initiating the transfer |
| `to` | `address` | Proposed new owner |
### OwnershipTransferred
```solidity
event OwnershipTransferred(address indexed from, address indexed to);
```
**Parameters**
| Name | Type | Description |
| ------ | --------- | -------------- |
| `from` | `address` | Previous owner |
| `to` | `address` | New owner |
## Errors
### CannotTransferToSelf
```solidity
error CannotTransferToSelf();
```
### MustBeProposedOwner
```solidity
error MustBeProposedOwner();
```
### OnlyCallableByOwner
```solidity
error OnlyCallableByOwner();
```
### OwnerCannotBeZero
```solidity
error OwnerCannotBeZero();
```
## State Variables
### s_owner
The owner is the current owner of the contract.
```solidity
address private s_owner;
```
### s_pendingOwner
The pending owner is the address to which ownership may be transferred.
```solidity
address private s_pendingOwner;
```
## Functions
### acceptOwnership
Allows an ownership transfer to be completed by the recipient.
```solidity
function acceptOwnership() external override;
```
### constructor
Initializes the contract with an owner and optionally a pending owner.
```solidity
constructor(address newOwner, address pendingOwner);
```
**Parameters**
| Name | Type | Description |
| -------------- | --------- | -------------------------------------------------- |
| `newOwner` | `address` | The initial owner of the contract |
| `pendingOwner` | `address` | Optional address to initiate ownership transfer to |
### onlyOwner
Modifier that restricts function access to the contract owner.
```solidity
modifier onlyOwner();
```
### owner
Returns the current owner's address.
```solidity
function owner() public view override returns (address);
```
**Returns**
| Type | Description |
| --------- | -------------------------------- |
| `address` | The address of the current owner |
### transferOwnership
Allows an owner to begin transferring ownership to a new address.
```solidity
function transferOwnership(address to) public override onlyOwner;
```
**Parameters**
| Name | Type | Description |
| ---- | --------- | -------------------------------------------------- |
| `to` | `address` | The address to which ownership will be transferred |
### _validateOwnership
Internal function to validate access control.
```solidity
function _validateOwnership() internal view;
```
---
# CCIP v1.5.1 Ownable2StepMsgSender Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.1/ownable-2-step-msg-sender
## Ownable2StepMsgSender
A contract that facilitates two-step ownership transfer, providing enhanced security for ownership management. This contract extends `Ownable2Step` and automatically sets the deploying address (`msg.sender`) as the initial owner with no pending owner.
**Inherits:**
- [`Ownable2Step`](/ccip/api-reference/evm/v1.5.1/ownable-2-step) - Provides secure two-step ownership transfer functionality
[Git Source](https://github.com/smartcontractkit/ccip/blob/0df0625eea603ba8572d5382d72979a7f2b12bfb/contracts/src/v0.8/shared/access/Ownable2StepMsgSender.sol)
## Functions
### constructor
Initializes the contract with the deploying address as the owner and no pending owner.
```solidity
constructor() Ownable2Step(msg.sender, address(0));
```
---
# CCIP v1.5.0 API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0
## API References
### Core Components
- [Client](/ccip/api-reference/evm/v1.5.0/client) - Library providing structs and types for building CCIP messages
- [CCIPReceiver](/ccip/api-reference/evm/v1.5.0/ccip-receiver) - Base contract for receiving CCIP messages
- [IRouterClient](/ccip/api-reference/evm/v1.5.0/i-router-client) - Interface for sending messages through CCIP
- [Pool](/ccip/api-reference/evm/v1.5.0/pool) - Library providing token pool functions for cross-chain operations
### Token Pools
- [TokenPool](/ccip/api-reference/evm/v1.5.0/token-pool) - Base abstract class defining common functionality for all token pools
- [BurnMintTokenPoolAbstract](/ccip/api-reference/evm/v1.5.0/burn-mint-token-pool-abstract) - Abstract contract for burn/mint token handling
- [BurnMintTokenPool](/ccip/api-reference/evm/v1.5.0/burn-mint-token-pool) - Implementation using `burn(amount)` for token burning
- [BurnFromMintTokenPool](/ccip/api-reference/evm/v1.5.0/burn-from-mint-token-pool) - Implementation using `burnFrom(address, amount)` for token burning
- [LockReleaseTokenPool](/ccip/api-reference/evm/v1.5.0/lock-release-token-pool) - Implementation for locking and releasing tokens on their native chain
### Registry Components
- [TokenAdminRegistry](/ccip/api-reference/evm/v1.5.0/token-admin-registry) - Contract for storing token pool configurations
- [RegistryModuleOwnerCustom](/ccip/api-reference/evm/v1.5.0/registry-module-owner-custom) - Registry module for token admin registration
### Error Handling
- [Errors](/ccip/api-reference/evm/v1.5.0/errors) - Comprehensive list of CCIP error codes and their descriptions
---
# CCIP v1.5.0 Client Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/client
CCIP senders and receivers use the CCIP [`Client`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/libraries/Client.sol) library to build CCIP messages.
```solidity
import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
```
## Types and Constants
### EVMTokenAmount
Use this solidity struct to specify the token address and amount.
```solidity
struct EVMTokenAmount {
address token;
uint256 amount;
}
```
| Name | Type | Description |
| ------ | ------- | --------------------------------- |
| token | address | token address on the local chain. |
| amount | uint256 | Amount of tokens. |
### Any2EVMMessage
CCIP receivers use this solidity struct to parse the received CCIP message.
```solidity
struct Any2EVMMessage {
bytes32 messageId;
uint64 sourceChainSelector;
bytes sender;
bytes data;
struct Client.EVMTokenAmount[] destTokenAmounts;
}
```
| Name | Type | Description |
| ------------------- | ------------------------ | ------------------------------------------------------------------------------------ |
| messageId | bytes32 | CCIP messageId, generated on the source chain. |
| sourceChainSelector | uint64 | Source chain selector. |
| sender | bytes | Sender address. `abi.decode(sender, (address))` if the source chain is an EVM chain. |
| data | bytes | Payload sent within the CCIP message. |
| destTokenAmounts | Client.EVMTokenAmount[] | Tokens and their amounts in their destination chain representation. |
### EVM2AnyMessage
CCIP senders use this solidity struct to build the CCIP message.
```solidity
struct EVM2AnyMessage {
bytes receiver;
bytes data;
struct Client.EVMTokenAmount[] tokenAmounts;
address feeToken;
bytes extraArgs;
}
```
| Name | Type | Description |
| ------------ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| receiver | bytes | Receiver address. Use `abi.encode(sender)` to encode the address to *bytes*. |
| data | bytes | Payload sent within the CCIP message. |
| tokenAmounts | Client.EVMTokenAmount[] | Tokens and their amounts in the source chain representation. |
| feeToken | address | Address of feeToken. Set `address(0)` to pay in native gas tokens such as ETH on Ethereum or POL on Polygon. |
| extraArgs | bytes | Users fill in the [EVMExtraArgs struct](#evmextraargs) then encode it to bytes using the [_argsToBytes function](#_argstobytes) |
### EVM_EXTRA_ARGS_TAG
#### EVM_EXTRA_ARGS_V1_TAG
```solidity
bytes4 EVM_EXTRA_ARGS_V1_TAG
```
#### EVM_EXTRA_ARGS_V2_TAG
```solidity
bytes4 EVM_EXTRA_ARGS_V2_TAG
```
### EVMExtraArgs
#### EVMExtraArgsV1
```solidity
struct EVMExtraArgsV1 {
uint256 gasLimit;
}
```
| Name | Type | Description |
| -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| gasLimit | uint256 | specifies the maximum amount of gas CCIP can consume to execute `ccipReceive()` on the contract located on the destination blockchain. Read [Setting gasLimit](/ccip/concepts/best-practices/evm#setting-gaslimit) for more details. |
#### EVMExtraArgsV2
```solidity
struct EVMExtraArgsV2 {
uint256 gasLimit;
bool allowOutOfOrderExecution;
}
```
| Name | Type | Description |
| ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| gasLimit | uint256 | specifies the maximum amount of gas CCIP can consume to execute `ccipReceive()` on the contract located on the destination blockchain. Read [Setting gasLimit](/ccip/concepts/best-practices/evm#setting-gaslimit) for more details. |
| allowOutOfOrderExecution | bool | if true, it indicates that the message can be executed in any order relative to other messages from the same sender. This value's default varies by chain. On some chains, a particular value is enforced, meaning if the expected value is not set, the message request will revert. |
## Functions
### _argsToBytes
#### _argsToBytes (v1)
```solidity
function _argsToBytes(struct Client.EVMExtraArgsV1 extraArgs) internal pure returns (bytes bts)
```
It is used to convert the arguments to bytes.
##### Parameters
| Name | Type | Description |
| --------- | ---------------------------------------- | ---------------- |
| extraArgs | [Client.EVMExtraArgsV1](#evmextraargsv1) | Extra arguments. |
##### Return Values
| Name | Type | Description |
| ---- | ----- | ----------------------------------- |
| bts | bytes | Encoded extra arguments in *bytes*. |
#### _argsToBytes (v2)
```solidity
function _argsToBytes(struct Client.EVMExtraArgsV2 extraArgs) internal pure returns (bytes bts)
```
It is used to convert the arguments to bytes.
##### Parameters
| Name | Type | Description |
| --------- | ---------------------------------------- | ---------------- |
| extraArgs | [Client.EVMExtraArgsV2](#evmextraargsv2) | Extra arguments. |
##### Return Values
| Name | Type | Description |
| ---- | ----- | ----------------------------------- |
| bts | bytes | Encoded extra arguments in *bytes*. |
---
# CCIP v1.5.0 ITypeAndVersion Interface API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/i-type-and-version
## ITypeAndVersion
An interface that provides type and version information for contracts.
[Git Source](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/shared/interfaces/ITypeAndVersion.sol)
## Functions
### typeAndVersion
Returns the type and version of the contract.
```solidity
function typeAndVersion() external pure returns (string memory);
```
**Returns**
| Type | Description |
| -------- | ------------------------------------- |
| `string` | The type and version of the contract. |
---
# CCIP v1.5.0 IRouterClient API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/i-router-client
To send messages through CCIP, users must interact with the [`IRouterClient`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/interfaces/IRouterClient.sol) interface.
After you import `IRouterClient.sol`, you can initialize a router client instance:
```solidity
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
...
IRouterClient router;
constructor(address _router) {
router = IRouterClient(_router);
}
```
## Errors
### UnsupportedDestinationChain
```solidity
error UnsupportedDestinationChain(uint64 destChainSelector)
```
### InsufficientFeeTokenAmount
```solidity
error InsufficientFeeTokenAmount()
```
### InvalidMsgValue
```solidity
error InvalidMsgValue()
```
## Functions
### isChainSupported
```solidity
function isChainSupported(uint64 chainSelector) external view returns (bool supported)
```
Checks if the given chain ID is supported for sending/receiving.
#### Parameters
| Name | Type | Description |
| ------------- | ------ | ------------------- |
| chainSelector | uint64 | The chain to check. |
#### Return Values
| Name | Type | Description |
| --------- | ---- | ------------------------------------- |
| supported | bool | is true if supported or false if not. |
### getSupportedTokens
```solidity
function getSupportedTokens(uint64 chainSelector) external view returns (address[] tokens)
```
Gets a list of all supported tokens which can be sent or received
to or from a given chain ID.
#### Parameters
| Name | Type | Description |
| ------------- | ------ | ------------------ |
| chainSelector | uint64 | The chainSelector. |
#### Return Values
| Name | Type | Description |
| ------ | ---------- | -------------------------------------- |
| tokens | address[] | The addresses of all supported tokens. |
### getFee
```solidity
function getFee(uint64 destinationChainSelector, struct Client.EVM2AnyMessage message) external view returns (uint256 fee)
```
*returns 0 fees on invalid message.*
#### Parameters
| Name | Type | Description |
| ------------------------ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
| destinationChainSelector | uint64 | The destination chainSelector |
| message | struct [Client.EVM2AnyMessage](/ccip/api-reference/evm/v1.5.0/client#evm2anymessage) | The cross-chain CCIP message, including data and/or tokens |
#### Return Values
| Name | Type | Description |
| ---- | ------- | -------------------------------------------------------------------------------------------- |
| fee | uint256 | returns guaranteed execution fee for the specified message delivery to the destination chain |
### ccipSend
```solidity
function ccipSend(uint64 destinationChainSelector, struct Client.EVM2AnyMessage message) external payable returns (bytes32)
```
Request a message to be sent to the destination chain.
#### Parameters
| Name | Type | Description |
| ------------------------ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
| destinationChainSelector | uint64 | The destination chain ID |
| message | struct [Client.EVM2AnyMessage](/ccip/api-reference/evm/v1.5.0/client#evm2anymessage) | The cross-chain CCIP message, including data and/or tokens |
#### Return Values
| Name | Type | Description |
| ---- | ------- | ------------------------ |
| [0] | bytes32 | messageId The message ID |
---
# CCIP v1.5.0 CCIPReceiver API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/ccip-receiver
CCIP receiver contracts inherit from [`CCIPReceiver`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/applications/CCIPReceiver.sol).
```solidity
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
...
constructor(address _router) is CCIPReceiver(router) {
}
```
## Functions
### constructor
```solidity
constructor(address router) internal
```
### supportsInterface
```solidity
function supportsInterface(bytes4 interfaceId) public pure returns (bool)
```
IERC165 supports an interfaceId
#### Parameters
| Name | Type | Description |
| ----------- | ------ | ------------------------ |
| interfaceId | bytes4 | The interfaceId to check |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ------------------------------------ |
| [0] | bool | true if the interfaceId is supported |
### ccipReceive
```solidity
function ccipReceive(struct Client.Any2EVMMessage message) external override onlyRouter
```
Only the Router can call this function to deliver a message.
If this reverts, any token transfers also revert. The message
will move to a FAILED state and become available for manual execution.
#### Parameters
| Name | Type | Description |
| ------- | ------------------------------------------------------------------------------------ | ------------ |
| message | struct [Client.Any2EVMMessage](/ccip/api-reference/evm/v1.5.0/client#any2evmmessage) | CCIP Message |
### _ccipReceive
```solidity
function _ccipReceive(struct Client.Any2EVMMessage message) internal virtual
```
Override this function in your implementation.
#### Parameters
| Name | Type | Description |
| ------- | ------------------------------------------------------------------------------------ | -------------- |
| message | struct [Client.Any2EVMMessage](/ccip/api-reference/evm/v1.5.0/client#any2evmmessage) | Any2EVMMessage |
### getRouter
```solidity
function getRouter() public view returns (address)
```
This function returns the current Router address.
#### Return Values
| Name | Type | Description |
| ---- | ------- | ----------------- |
| [0] | address | i_router address |
### InvalidRouter
```solidity
error InvalidRouter(address router)
```
### onlyRouter
```solidity
modifier onlyRouter()
```
*Only calls from the set router are accepted.*
---
# CCIP v1.5.0 Pool Library API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/pool
The [`Pool`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/libraries/Pool.sol) library provides various token pool functions to construct return data for cross-chain operations in Chainlink's CCIP.
```solidity
import { Pool } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Pool.sol";
```
## Types and Constants
### CCIP_POOL_V1
The tag used to signal support for the Pool v1 standard.
```solidity
bytes4 CCIP_POOL_V1
```
### CCIP_POOL_V1_RET_BYTES
The number of bytes in the return data for a pool v1 `releaseOrMint` call.
```solidity
uint16 CCIP_POOL_V1_RET_BYTES
```
### CCIP_LOCK_OR_BURN_V1_RET_BYTES
The default maximum number of bytes in the return data for a pool v1 `lockOrBurn` call.
```solidity
uint32 CCIP_LOCK_OR_BURN_V1_RET_BYTES
```
## Structs
### LockOrBurnInV1
This struct is used when locking or burning tokens for cross-chain operations.
```solidity
struct LockOrBurnInV1 {
bytes receiver;
uint64 remoteChainSelector;
address originalSender;
uint256 amount;
address localToken;
}
```
| Name | Type | Description |
| ------------------- | ------- | --------------------------------------------------------------------------------- |
| receiver | bytes | The recipient of the tokens on the destination chain, ABI encoded. |
| remoteChainSelector | uint64 | The chain ID of the destination chain. |
| originalSender | address | The original sender of the transaction on the source chain. |
| amount | uint256 | The amount of tokens to lock or burn, denominated in the source token's decimals. |
| localToken | address | The address of the token on this chain to lock or burn. |
### LockOrBurnOutV1
This struct represents the output data from a lockOrBurn call.
```solidity
struct LockOrBurnOutV1 {
bytes destTokenAddress;
bytes destPoolData;
}
```
| Name | Type | Description |
| ---------------- | ----- | --------------------------------------------------------------------------------------------------------------------- |
| destTokenAddress | bytes | The address of the destination token, ABI encoded. This value is untrusted as any pool owner can modify it. |
| destPoolData | bytes | Optional pool data to be transferred to the destination chain, capped by default at `CCIP_LOCK_OR_BURN_V1_RET_BYTES`. |
### ReleaseOrMintInV1
This struct is used to release or mint tokens on the destination chain.
```solidity
struct ReleaseOrMintInV1 {
bytes originalSender;
uint64 remoteChainSelector;
address receiver;
uint256 amount;
address localToken;
bytes sourcePoolAddress;
bytes sourcePoolData;
bytes offchainTokenData;
}
```
| Name | Type | Description |
| ------------------- | ------- | ------------------------------------------------------------------------------------------------- |
| originalSender | bytes | The original sender of the transaction on the source chain. |
| remoteChainSelector | uint64 | The chain ID of the source chain. |
| receiver | address | The recipient of the tokens on the destination chain. |
| amount | uint256 | The amount of tokens to release or mint, denominated in the source token's decimals. |
| localToken | address | The address of the token on this chain to release or mint. |
| sourcePoolAddress | bytes | The address of the source pool, ABI encoded. This value must be verified before processing funds. |
| sourcePoolData | bytes | Data received from the source pool to process the release or mint. |
| offchainTokenData | bytes | Untrusted offchain data to process the release or mint. |
### ReleaseOrMintOutV1
This struct represents the output data from a `releaseOrMint` call.
```solidity
struct ReleaseOrMintOutV1 {
uint256 destinationAmount;
}
```
| Name | Type | Description |
| ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| destinationAmount | uint256 | The number of tokens released or minted on the destination chain, denominated in the local token's decimals. Expected to match the input amount. |
---
# CCIP v1.5.0 TokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/token-pool
## TokenPool
The [`TokenPool`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/pools/TokenPool.sol) contract is an abstract base class that defines common functionality for all token pools within the CCIP ecosystem. A token pool acts as an isolated location for holding tokens and executing token-specific logic, especially as tokens move across chains in a cross-chain interoperability setup. The contract implements validation mechanisms to ensure that tokens are securely transferred, burned, or locked when they are transferred between blockchains.
## Errors
### CallerIsNotARampOnRouter
```solidity
error CallerIsNotARampOnRouter(address caller)
```
### ZeroAddressNotAllowed
```solidity
error ZeroAddressNotAllowed()
```
### SenderNotAllowed
```solidity
error SenderNotAllowed(address sender)
```
### AllowListNotEnabled
```solidity
error AllowListNotEnabled()
```
### NonExistentChain
```solidity
error NonExistentChain(uint64 remoteChainSelector)
```
### ChainNotAllowed
```solidity
error ChainNotAllowed(uint64 remoteChainSelector)
```
### CursedByRMN
```solidity
error CursedByRMN()
```
### ChainAlreadyExists
```solidity
error ChainAlreadyExists(uint64 chainSelector)
```
### InvalidSourcePoolAddress
```solidity
error InvalidSourcePoolAddress(bytes sourcePoolAddress)
```
### InvalidToken
```solidity
error InvalidToken(address token)
```
### Unauthorized
```solidity
error Unauthorized(address caller)
```
## Events
### Locked
```solidity
event Locked(address sender, uint256 amount)
```
### Burned
```solidity
event Burned(address sender, uint256 amount)
```
### Released
```solidity
event Released(address sender, address recipient, uint256 amount)
```
### Minted
```solidity
event Minted(address sender, address recipient, uint256 amount)
```
### ChainAdded
```solidity
event ChainAdded(uint64 remoteChainSelector, bytes remoteToken, struct RateLimiter.Config outboundRateLimiterConfig, struct RateLimiter.Config inboundRateLimiterConfig)
```
### ChainConfigured
```solidity
event ChainConfigured(uint64 remoteChainSelector, struct RateLimiter.Config outboundRateLimiterConfig, struct RateLimiter.Config inboundRateLimiterConfig)
```
### ChainRemoved
```solidity
event ChainRemoved(uint64 remoteChainSelector)
```
### RemotePoolSet
```solidity
event RemotePoolSet(uint64 remoteChainSelector, bytes previousPoolAddress, bytes remotePoolAddress)
```
### AllowListAdd
```solidity
event AllowListAdd(address sender)
```
### AllowListRemove
```solidity
event AllowListRemove(address sender)
```
### RouterUpdated
```solidity
event RouterUpdated(address oldRouter, address newRouter)
```
## Structs
### ChainUpdate
```solidity
struct ChainUpdate {
uint64 remoteChainSelector;
bool allowed;
bytes remotePoolAddress;
bytes remoteTokenAddress;
struct RateLimiter.Config outboundRateLimiterConfig;
struct RateLimiter.Config inboundRateLimiterConfig;
}
```
| Name | Type | Description |
| ------------------------- | ------------------ | ------------------------------------------------------------------------------------------------- |
| remoteChainSelector | uint64 | Remote chain selector. |
| allowed | bool | Whether the chain should be enabled. |
| remotePoolAddress | bytes | Address of the remote pool, ABI encoded in the case of a remote EVM chain. |
| remoteTokenAddress | bytes | Address of the remote token, ABI encoded in the case of a remote EVM chain. |
| outboundRateLimiterConfig | RateLimiter.Config | Outbound rate limited config, meaning the rate limits for all of the onRamps for the given chain. |
| inboundRateLimiterConfig | RateLimiter.Config | Inbound rate limited config, meaning the rate limits for all of the offRamps for the given chain. |
### RemoteChainConfig
```solidity
struct RemoteChainConfig {
struct RateLimiter.TokenBucket outboundRateLimiterConfig;
struct RateLimiter.TokenBucket inboundRateLimiterConfig;
bytes remotePoolAddress;
bytes remoteTokenAddress;
}
```
| Name | Type | Description |
| ------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------- |
| outboundRateLimiterConfig | RateLimiter.TokenBucket | Outbound rate limited config, meaning the rate limits for all of the onRamps for the given chain. |
| inboundRateLimiterConfig | RateLimiter.TokenBucket | Inbound rate limited config, meaning the rate limits for all of the offRamps for the given chain. |
| remotePoolAddress | bytes | Address of the remote pool, ABI encoded in the case of a remote EVM chain. |
| remoteTokenAddress | bytes | Address of the remote token, ABI encoded in the case of a remote EVM chain. |
## Variables
### i_token
```solidity
IERC20 i_token
```
*The bridgeable token that is managed by this pool.*
### i_rmnProxy
```solidity
address i_rmnProxy
```
*The address of the RMN proxy.*
### i_allowlistEnabled
```solidity
bool i_allowlistEnabled
```
*The immutable flag that indicates if the pool is access-controlled.*
## Mappings
### s_allowList
```solidity
struct EnumerableSet.AddressSet s_allowList
```
*A set of addresses allowed to trigger lockOrBurn as original senders. Only takes effect if i_allowlistEnabled is true.*
### s_router
```solidity
contract IRouter s_router
```
*The address of the router.*
### s_remoteChainSelectors
```solidity
struct EnumerableSet.UintSet s_remoteChainSelectors
```
*A set of allowed chain selectors.*
### s_remoteChainConfigs
```solidity
mapping(uint64 => struct TokenPool.RemoteChainConfig) s_remoteChainConfigs
```
### s_rateLimitAdmin
```solidity
address s_rateLimitAdmin
```
*The address of the rate limiter admin. Can be address(0) if none is configured.*
## Functions
### constructor
```solidity
constructor(contract IERC20 token, address[] allowlist, address rmnProxy, address router) internal
```
### getRmnProxy
```solidity
function getRmnProxy() public view returns (address rmnProxy)
```
Gets the RMN proxy address.
#### Return Values
| Name | Type | Description |
| -------- | ------- | --------------------- |
| rmnProxy | address | Address of RMN proxy. |
### isSupportedToken
```solidity
function isSupportedToken(address token) public view virtual returns (bool)
```
Returns if the token pool supports the given token.
#### Parameters
| Name | Type | Description |
| ----- | ------- | ------------------------- |
| token | address | The address of the token. |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ------------------------------- |
| [0] | bool | true if the token is supported. |
### getToken
```solidity
function getToken() public view returns (contract IERC20 token)
```
Gets the IERC20 token that this pool can lock or burn.
#### Return Values
| Name | Type | Description |
| ----- | ------ | ----------------- |
| token | IERC20 | The IERC20 token. |
### getRouter
```solidity
function getRouter() public view returns (address router)
```
Gets the pool's Router.
#### Return Values
| Name | Type | Description |
| ------ | ------- | ------------------ |
| router | address | The pool's Router. |
### setRouter
```solidity
function setRouter(address newRouter) public
```
Sets the pool's Router.
#### Parameters
| Name | Type | Description |
| --------- | ------- | ----------------------- |
| newRouter | address | The new Router address. |
### supportsInterface
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool)
```
Signals which version of the pool interface is supported.
### _validateLockOrBurn
```solidity
function _validateLockOrBurn(struct Pool.LockOrBurnInV1 lockOrBurnIn) internal
```
Validates the lock or burn input for correctness.
#### Parameters
| Name | Type | Description |
| ------------ | -------------------------- | ---------------------- |
| lockOrBurnIn | struct Pool.LockOrBurnInV1 | The input to validate. |
### _validateReleaseOrMint
```solidity
function _validateReleaseOrMint(struct Pool.ReleaseOrMintInV1 releaseOrMintIn) internal
```
Validates the release or mint input for correctness.
#### Parameters
| Name | Type | Description |
| --------------- | ----------------------------- | ---------------------- |
| releaseOrMintIn | struct Pool.ReleaseOrMintInV1 | The input to validate. |
### getRemotePool
```solidity
function getRemotePool(uint64 remoteChainSelector) public view returns (bytes)
```
Gets the pool address on the remote chain.
#### Parameters
| Name | Type | Description |
| ------------------- | ------ | ---------------------- |
| remoteChainSelector | uint64 | Remote chain selector. |
### getRemoteToken
```solidity
function getRemoteToken(uint64 remoteChainSelector) public view returns (bytes)
```
Gets the token address on the remote chain.
#### Parameters
| Name | Type | Description |
| ------------------- | ------ | ---------------------- |
| remoteChainSelector | uint64 | Remote chain selector. |
### setRemotePool
```solidity
function setRemotePool(uint64 remoteChainSelector, bytes remotePoolAddress) external
```
Sets the remote pool address for a given chain selector.
#### Parameters
| Name | Type | Description |
| ------------------- | ------ | ------------------------------- |
| remoteChainSelector | uint64 | The remote chain selector. |
| remotePoolAddress | bytes | The address of the remote pool. |
### isSupportedChain
```solidity
function isSupportedChain(uint64 remoteChainSelector) public view returns (bool)
```
Checks whether a remote chain is supported in the token pool.
#### Parameters
| Name | Type | Description |
| ------------------- | ------ | ---------------------- |
| remoteChainSelector | uint64 | Remote chain selector. |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ------------------------------------------- |
| [0] | bool | true if the chain is supported by the pool. |
### getSupportedChains
```solidity
function getSupportedChains() public view returns (uint64[] memory)
```
Gets the list of allowed chains.
#### Return Values
| Name | Type | Description |
| ---- | --------- | ----------------------- |
| [0] | uint64[] | List of allowed chains. |
### applyChainUpdates
```solidity
function applyChainUpdates(ChainUpdate[] calldata chains) external virtual onlyOwner
```
Sets the permissions for a list of chain selectors. The senders for these chains must be allowed on the Router to interact with this pool.
#### Parameters
| Name | Type | Description |
| ------ | ----------------------- | ----------------------------------------------------------- |
| chains | ChainUpdate[] calldata | A list of chains and their permission status & rate limits. |
### setRateLimitAdmin
```solidity
function setRateLimitAdmin(address rateLimitAdmin) external onlyOwner
```
Sets the rate limiter admin address.
#### Parameters
| Name | Type | Description |
| -------------- | ------- | ----------------------------------- |
| rateLimitAdmin | address | The new rate limiter admin address. |
### getRateLimitAdmin
```solidity
function getRateLimitAdmin() external view returns (address)
```
Gets the rate limiter admin address.
#### Return Values
| Name | Type | Description |
| -------------- | ------- | --------------------------------------- |
| rateLimitAdmin | address | The current rate limiter admin address. |
### getCurrentOutboundRateLimiterState
```solidity
function getCurrentOutboundRateLimiterState(uint64 remoteChainSelector) external view returns (RateLimiter.TokenBucket)
```
Gets the token bucket state for outbound rate limits.
#### Parameters
| Name | Type | Description |
| ------------------- | ------ | ---------------------- |
| remoteChainSelector | uint64 | Remote chain selector. |
#### Return Values
| Name | Type | Description |
| ---- | ----------------------- | ----------------------------------------------- |
| [0] | RateLimiter.TokenBucket | The current state of the outbound rate limiter. |
### getCurrentInboundRateLimiterState
```solidity
function getCurrentInboundRateLimiterState(uint64 remoteChainSelector) external view returns (RateLimiter.TokenBucket)
```
Gets the token bucket state for inbound rate limits.
#### Parameters
| Name | Type | Description |
| ------------------- | ------ | ---------------------- |
| remoteChainSelector | uint64 | Remote chain selector. |
#### Return Values
| Name | Type | Description |
| ---- | ----------------------- | ---------------------------------------------- |
| [0] | RateLimiter.TokenBucket | The current state of the inbound rate limiter. |
### setChainRateLimiterConfig
```solidity
function setChainRateLimiterConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) external
```
Sets the chain rate limiter config.
#### Parameters
| Name | Type | Description |
| ------------------- | ------------------ | ------------------------------------ |
| remoteChainSelector | uint64 | Remote chain selector. |
| outboundConfig | RateLimiter.Config | Outbound rate limiter configuration. |
| inboundConfig | RateLimiter.Config | Inbound rate limiter configuration. |
### getAllowListEnabled
```solidity
function getAllowListEnabled() external view returns (bool)
```
Gets whether the allowList functionality is enabled.
#### Return Values
| Name | Type | Description |
| ---- | ---- | --------------------------------- |
| [0] | bool | true if the allowList is enabled. |
### getAllowList
```solidity
function getAllowList() external view returns (address[] memory)
```
Gets the list of allowed addresses in the allowlist.
#### Return Values
| Name | Type | Description |
| ---- | ---------- | -------------------------- |
| [0] | address[] | List of allowed addresses. |
### applyAllowListUpdates
```solidity
function applyAllowListUpdates(address[] calldata removes, address[] calldata adds) external onlyOwner
```
Applies updates to the allow list.
#### Parameters
| Name | Type | Description |
| ------- | ---------- | --------------------------------------- |
| removes | address[] | Addresses to remove from the allowlist. |
| adds | address[] | Addresses to add to the allowlist. |
---
# CCIP v1.5.0 BurnMintTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/burn-mint-token-pool
## BurnMintTokenPool
[`BurnMintTokenPool`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/pools/BurnMintTokenPool.sol) is a concrete implementation that inherits from [`BurnMintTokenPoolAbstract`](/ccip/api-reference/evm/v1.5.0/burn-mint-token-pool-abstract). It is designed to mint and burn a 3rd-party token, using the `burn(amount)` function for burning tokens. Pool whitelisting mode is set during the contract's deployment and cannot be modified later.
## Variables
### typeAndVersion
```solidity
string typeAndVersion
```
## Functions
### constructor
```solidity
constructor(contract IBurnMintERC20 token, address[] allowlist, address rmnProxy, address router) public
```
#### Parameters
| Name | Type | Description |
| --------- | -------------- | --------------------------------------------------------------------- |
| token | IBurnMintERC20 | The token that will be burned and minted. |
| allowlist | address[] | List of addresses allowed to be original senders for token transfers. |
| rmnProxy | address | The RMN proxy address. |
| router | address | The router address. |
### _burn
```solidity
function _burn(uint256 amount) internal virtual
```
Contains the specific burn call for a pool using the token's burn(amount) function.
#### Parameters
| Name | Type | Description |
| ------ | ------- | ----------------------------- |
| amount | uint256 | The amount of tokens to burn. |
---
# CCIP v1.5.0 BurnMintTokenPoolAbstract Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/burn-mint-token-pool-abstract
## BurnMintTokenPoolAbstract
[`BurnMintTokenPoolAbstract`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/pools/BurnMintTokenPoolAbstract.sol) is an abstract contract that extends the [`TokenPool`](/ccip/api-reference/evm/v1.5.0/token-pool) contract. It defines the common logic for burning tokens in the pool. This contract contains validation mechanisms and logic for burning tokens in the context of a cross-chain token transfer. When allowlist is enabled, it ensures only token-developer specified addresses can transfer tokens.
## Functions
### _burn
```solidity
function _burn(uint256 amount) internal virtual
```
Contains the specific burn call for a pool.
#### Parameters
| Name | Type | Description |
| ------ | ------- | ----------------------------- |
| amount | uint256 | The amount of tokens to burn. |
### lockOrBurn
```solidity
function lockOrBurn(struct Pool.LockOrBurnInV1 lockOrBurnIn) external virtual returns (struct Pool.LockOrBurnOutV1)
```
Burn the token in the pool.
#### Parameters
| Name | Type | Description |
| ------------ | ------------------- | ---------------------------------------- |
| lockOrBurnIn | Pool.LockOrBurnInV1 | The input parameters for burning tokens. |
#### Return Values
| Name | Type | Description |
| ---- | -------------------- | ----------------------------------------- |
| [0] | Pool.LockOrBurnOutV1 | The output data after burning the tokens. |
### releaseOrMint
```solidity
function releaseOrMint(struct Pool.ReleaseOrMintInV1 releaseOrMintIn) external virtual returns (struct Pool.ReleaseOrMintOutV1)
```
Mint tokens from the pool to the recipient.
#### Parameters
| Name | Type | Description |
| --------------- | ---------------------- | ---------------------------------------- |
| releaseOrMintIn | Pool.ReleaseOrMintInV1 | The input parameters for minting tokens. |
#### Return Values
| Name | Type | Description |
| ---- | ----------------------- | ----------------------------------------- |
| [0] | Pool.ReleaseOrMintOutV1 | The output data after minting the tokens. |
---
# CCIP v1.5.0 BurnFromMintTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/burn-from-mint-token-pool
## BurnFromMintTokenPool
[`BurnFromMintTokenPool`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/pools/BurnFromMintTokenPool.sol) is a concrete implementation that inherits from [`BurnMintTokenPoolAbstract`](/ccip/api-reference/evm/v1.5.0/burn-mint-token-pool-abstract). It is designed to mint and burn a 3rd-party token, using the `burnFrom(address, amount)` function for burning tokens, where the contract burns tokens from its own balance. When allowlist is enabled, it ensures only token-developer specified addresses can transfer tokens.
## Variables
### typeAndVersion
```solidity
string typeAndVersion
```
## Functions
### constructor
```solidity
constructor(contract IBurnMintERC20 token, address[] allowlist, address rmnProxy, address router) public
```
#### Parameters
| Name | Type | Description |
| --------- | -------------- | --------------------------------------------------------------------- |
| token | IBurnMintERC20 | The token that will be burned and minted. |
| allowlist | address[] | List of addresses allowed to be original senders for token transfers. |
| rmnProxy | address | The RMN proxy address. |
| router | address | The router address. |
### _burn
```solidity
function _burn(uint256 amount) internal virtual
```
Contains the specific burn call for a pool using the token's burnFrom(address, amount) function.
#### Parameters
| Name | Type | Description |
| ------ | ------- | ----------------------------- |
| amount | uint256 | The amount of tokens to burn. |
---
# CCIP v1.5.0 LockReleaseTokenPool Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/lock-release-token-pool
## LockReleaseTokenPool
The [`LockReleaseTokenPool`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/pools/LockReleaseTokenPool.sol) contract is used for handling tokens on their native chain using a lock and release mechanism. It allows tokens to be locked in the pool, facilitating their transfer across blockchains, and then released to the recipient on the destination chain. When allowlist is enabled, it ensures only token-developer specified addresses can transfer tokens.
This contract inherits from the [`TokenPool`](/ccip/api-reference/evm/v1.5.0/token-pool) contract and implements additional functions for liquidity management.
### typeAndVersion
```solidity
string typeAndVersion
```
### constructor
```solidity
constructor(IERC20 token, address[] allowlist, address rmnProxy, bool acceptLiquidity, address router)
```
Initializes the pool with a token, allowlist, RMN proxy, and router. The constructor also determines whether the pool can accept liquidity.
#### Parameters
| Name | Type | Description |
| --------------- | ---------- | --------------------------------------------------------------------- |
| token | IERC20 | The token managed by the pool. |
| allowlist | address[] | List of addresses allowed to be original senders for token transfers. |
| rmnProxy | address | The RMN proxy address. |
| acceptLiquidity | bool | Whether the pool accepts liquidity. |
| router | address | The router address for cross-chain communication. |
### lockOrBurn
```solidity
function lockOrBurn(Pool.LockOrBurnInV1 calldata lockOrBurnIn) external virtual returns (Pool.LockOrBurnOutV1 memory)
```
Locks the token in the pool for transfer across chains.
#### Parameters
| Name | Type | Description |
| ------------ | ---------------------------- | --------------------------------------- |
| lockOrBurnIn | Pool.LockOrBurnInV1 calldata | Encoded data fields for token transfer. |
#### Return Values
| Name | Type | Description |
| ---------------- | ----- | --------------------------------------------------- |
| destTokenAddress | bytes | The destination token address on the remote chain. |
| destPoolData | bytes | Optional data to be passed to the destination pool. |
### releaseOrMint
```solidity
function releaseOrMint(Pool.ReleaseOrMintInV1 calldata releaseOrMintIn) external virtual returns (Pool.ReleaseOrMintOutV1 memory)
```
Releases tokens from the pool to the recipient.
#### Parameters
| Name | Type | Description |
| --------------- | ------------------------------- | ---------------------------------------------------------- |
| releaseOrMintIn | Pool.ReleaseOrMintInV1 calldata | Encoded data fields for releasing tokens to the recipient. |
#### Return Values
| Name | Type | Description |
| ----------------- | ------- | ----------------------------------------------------------------- |
| destinationAmount | uint256 | The amount of tokens released or minted on the destination chain. |
### getRebalancer
```solidity
function getRebalancer() external view returns (address)
```
Gets the address of the rebalancer.
#### Return Values
| Name | Type | Description |
| ---------- | ------- | ----------------------- |
| rebalancer | address | The current rebalancer. |
### setRebalancer
```solidity
function setRebalancer(address rebalancer) external
```
Sets the LiquidityManager address. Only callable by the contract owner.
#### Parameters
| Name | Type | Description |
| ---------- | ------- | --------------------------------- |
| rebalancer | address | The new LiquidityManager address. |
### canAcceptLiquidity
```solidity
function canAcceptLiquidity() external view returns (bool)
```
Checks if the pool can accept liquidity.
#### Return Values
| Name | Type | Description |
| ---- | ---- | ---------------------------------------------------- |
| [0] | bool | true if the pool accepts liquidity, false otherwise. |
### provideLiquidity
```solidity
function provideLiquidity(uint256 amount) external
```
Adds liquidity to the pool.
#### Parameters
| Name | Type | Description |
| ------ | ------- | ----------------------------------- |
| amount | uint256 | Amount of liquidity to be provided. |
### withdrawLiquidity
```solidity
function withdrawLiquidity(uint256 amount) external
```
Removes liquidity from the pool and sends the tokens to the sender.
#### Parameters
| Name | Type | Description |
| ------ | ------- | ------------------------------------ |
| amount | uint256 | Amount of liquidity to be withdrawn. |
### transferLiquidity
```solidity
function transferLiquidity(address from, uint256 amount) external
```
Transfers liquidity from an older version of the pool to this pool.
#### Parameters
| Name | Type | Description |
| ------ | ------- | --------------------------------------------------------- |
| from | address | The address of the older pool to transfer liquidity from. |
| amount | uint256 | The amount of liquidity to transfer. |
### supportsInterface
```solidity
function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool)
```
Checks which interface the contract supports.
#### Parameters
| Name | Type | Description |
| ----------- | ------ | ------------------------- |
| interfaceId | bytes4 | The interface identifier. |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------------------------------- |
| [0] | bool | true if the interface is supported. |
---
# CCIP v1.5.0 TokenAdminRegistry Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/token-admin-registry
The [`TokenAdminRegistry`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/tokenAdminRegistry/TokenAdminRegistry.sol) contract stores the token pool configuration for all cross-chain tokens. It operates on a self-serve basis, where tokens can be registered without intervention from the CCIP owner.
*This contract is not considered upgradable, as it is a customer-facing contract that will store significant amounts of data.*
## Errors
### OnlyRegistryModuleOrOwner
```solidity
error OnlyRegistryModuleOrOwner(address sender)
```
### OnlyAdministrator
```solidity
error OnlyAdministrator(address sender, address token)
```
### OnlyPendingAdministrator
```solidity
error OnlyPendingAdministrator(address sender, address token)
```
### AlreadyRegistered
```solidity
error AlreadyRegistered(address token)
```
### ZeroAddress
```solidity
error ZeroAddress()
```
### InvalidTokenPoolToken
```solidity
error InvalidTokenPoolToken(address token)
```
## Events
### PoolSet
```solidity
event PoolSet(address token, address previousPool, address newPool)
```
### AdministratorTransferRequested
```solidity
event AdministratorTransferRequested(address token, address currentAdmin, address newAdmin)
```
### AdministratorTransferred
```solidity
event AdministratorTransferred(address token, address newAdmin)
```
### RegistryModuleAdded
```solidity
event RegistryModuleAdded(address module)
```
### RegistryModuleRemoved
```solidity
event RegistryModuleRemoved(address module)
```
## Structs
### TokenConfig
```solidity
struct TokenConfig {
address administrator;
address pendingAdministrator;
address tokenPool;
}
```
| Name | Type | Description |
| -------------------- | ------- | ------------------------------------- |
| administrator | address | Current administrator of the token. |
| pendingAdministrator | address | Pending administrator for transfer. |
| tokenPool | address | Token pool associated with the token. |
## Constants
### typeAndVersion
```solidity
string typeAndVersion
```
*The version of the TokenAdminRegistry contract.*
## Mappings
### s_tokenConfig
```solidity
mapping(address => struct TokenAdminRegistry.TokenConfig) s_tokenConfig
```
*Stores the configuration for each token.*
### s_tokens
```solidity
struct EnumerableSet.AddressSet s_tokens
```
*List of all tokens that have been configured.*
### s_registryModules
```solidity
struct EnumerableSet.AddressSet s_registryModules
```
*List of all registry modules allowed to interact with the contract.*
## Functions
### getPools
```solidity
function getPools(address[] tokens) external view returns (address[])
```
Returns the pools for the given tokens.
*Will return `address(0)` for tokens that do not have an associated pool.*
#### Parameters
| Name | Type | Description |
| ------ | ---------- | ---------------------------------- |
| tokens | address[] | Array of token addresses to query. |
#### Return Values
| Name | Type | Description |
| ---- | ---------- | --------------------------------------- |
| [0] | address[] | Array of pool addresses for each token. |
### getPool
```solidity
function getPool(address token) external view returns (address)
```
Returns the pool for the given token.
#### Parameters
| Name | Type | Description |
| ----- | ------- | ------------------------------ |
| token | address | The token to get the pool for. |
#### Return Values
| Name | Type | Description |
| ---- | ------- | --------------------------- |
| [0] | address | Pool address for the token. |
### getTokenConfig
```solidity
function getTokenConfig(address token) external view returns (struct TokenAdminRegistry.TokenConfig)
```
Returns the configuration for a token.
#### Parameters
| Name | Type | Description |
| ----- | ------- | --------------------------------------- |
| token | address | The token to get the configuration for. |
#### Return Values
| Name | Type | Description |
| ---- | ------------------------------------- | ---------------------------- |
| [0] | struct TokenAdminRegistry.TokenConfig | Configuration for the token. |
### getAllConfiguredTokens
```solidity
function getAllConfiguredTokens(uint64 startIndex, uint64 maxCount) external view returns (address[] tokens)
```
Returns a list of tokens that are configured in the TokenAdminRegistry.
*The function is paginated to avoid RPC timeouts.*
#### Parameters
| Name | Type | Description |
| ---------- | ------ | ---------------------------------------------------------------------------------------------------- |
| startIndex | uint64 | Starting index in the list (use 0 to start from the beginning). |
| maxCount | uint64 | Maximum number of tokens to retrieve (use `type(uint64).max` to retrieve all tokens in large lists). |
#### Return Values
| Name | Type | Description |
| ------ | ---------- | ---------------------------------------- |
| tokens | address[] | Array of addresses of configured tokens. |
### setPool
```solidity
function setPool(address localToken, address pool) external
```
Sets the pool for a token. Setting the pool to `address(0)` will delist the token from CCIP.
#### Parameters
| Name | Type | Description |
| ---------- | ------- | ------------------------------------- |
| localToken | address | The token to associate with the pool. |
| pool | address | The pool to associate with the token. |
### transferAdminRole
```solidity
function transferAdminRole(address localToken, address newAdmin) external
```
Transfers the administrator role for a token. The new admin must call `acceptAdminRole` to finalize the transfer.
#### Parameters
| Name | Type | Description |
| ---------- | ------- | ----------------------------------------- |
| localToken | address | The token to transfer the admin role for. |
| newAdmin | address | The new administrator's address. |
### acceptAdminRole
```solidity
function acceptAdminRole(address localToken) external
```
Accepts the administrator role for a token.
*Only the pending administrator can call this function.*
#### Parameters
| Name | Type | Description |
| ---------- | ------- | ----------------------------------------------------- |
| localToken | address | The token for which the admin role is being accepted. |
### isAdministrator
```solidity
function isAdministrator(address localToken, address administrator) external view returns (bool)
```
Returns whether an address is the administrator of the given token.
#### Parameters
| Name | Type | Description |
| ------------- | ------- | ----------------------------------------- |
| localToken | address | The token to check the administrator for. |
| administrator | address | The administrator address to check. |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ---------------------------------------- |
| [0] | bool | True if the address is an administrator. |
### proposeAdministrator
```solidity
function proposeAdministrator(address localToken, address administrator) external
```
Proposes a new administrator for the token.
*Can only be called by a registry module.*
#### Parameters
| Name | Type | Description |
| ------------- | ------- | ------------------------------------- |
| localToken | address | The token to propose a new admin for. |
| administrator | address | The new administrator address. |
### isRegistryModule
```solidity
function isRegistryModule(address module) public view returns (bool)
```
Returns whether an address is a valid registry module.
#### Parameters
| Name | Type | Description |
| ------ | ------- | ---------------------------- |
| module | address | The module address to check. |
#### Return Values
| Name | Type | Description |
| ---- | ---- | ----------------------------------------- |
| [0] | bool | True if the address is a registry module. |
### addRegistryModule
```solidity
function addRegistryModule(address module) external
```
Adds a new registry module to the list of allowed modules.
#### Parameters
| Name | Type | Description |
| ------ | ------- | --------------------------------- |
| module | address | The address of the module to add. |
### removeRegistryModule
```solidity
function removeRegistryModule(address module) external
```
Removes a registry module from the list of allowed modules.
#### Parameters
| Name | Type | Description |
| ------ | ------- | ------------------------------------ |
| module | address | The address of the module to remove. |
### onlyTokenAdmin
```solidity
modifier onlyTokenAdmin(address token)
```
Modifier that checks if an address is the administrator of the given token.
---
# CCIP v1.5.0 RegistryModuleOwnerCustom Contract API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/registry-module-owner-custom
The [`RegistryModuleOwnerCustom`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.0/contracts/src/v0.8/ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol) contract is responsible for registering the administrator of a token in the [`TokenAdminRegistry`](/ccip/api-reference/evm/v1.5.0/token-admin-registry). This contract allows for the registration of token administrators through either the `getCCIPAdmin()` method (for tokens with a CCIP admin) or the `owner()` method (for standard tokens with an owner). The contract enforces that only the rightful administrator of the token can register themselves, ensuring secure and accurate registration within the `TokenAdminRegistry`. The contract also emits an event, `AdministratorRegistered`, whenever a token administrator is successfully registered.
## Errors
### CanOnlySelfRegister
```solidity
error CanOnlySelfRegister(address admin, address token)
```
### AddressZero
```solidity
error AddressZero()
```
## Events
### AdministratorRegistered
```solidity
event AdministratorRegistered(address token, address administrator)
```
## Constants
### typeAndVersion
```solidity
string typeAndVersion
```
## Variables
### i_tokenAdminRegistry
```solidity
ITokenAdminRegistry i_tokenAdminRegistry
```
## Functions
### constructor
```solidity
constructor(address tokenAdminRegistry) public
```
### registerAdminViaGetCCIPAdmin
```solidity
function registerAdminViaGetCCIPAdmin(address token) external
```
Registers the admin of the token using the `getCCIPAdmin` method.
*The caller must be the admin returned by the `getCCIPAdmin` method.*
#### Parameters
| Name | Type | Description |
| ----- | ------- | ------------------------------------ |
| token | address | The token to register the admin for. |
### registerAdminViaOwner
```solidity
function registerAdminViaOwner(address token) external
```
Registers the admin of the token using the `owner` method.
*The caller must be the admin returned by the `owner` method.*
#### Parameters
| Name | Type | Description |
| ----- | ------- | ------------------------------------ |
| token | address | The token to register the admin for. |
### _registerAdmin
```solidity
function _registerAdmin(address token, address admin) internal
```
Registers the admin of the token to msg.sender given that the admin is equal to msg.sender.
#### Parameters
| Name | Type | Description |
| ----- | ------- | ------------------------------------ |
| token | address | The token to register the admin for. |
| admin | address | The caller must be the admin. |
---
# CCIP v1.5.0 Error Codes and Messages API Reference
Source: https://docs.chain.link/ccip/api-reference/evm/v1.5.0/errors
If you get errors when invoking the `ccipSend` [function](/ccip/api-reference/evm/v1.5.0/i-router-client#ccipsend), the errors might be thrown either by the CCIP router or by one of the downstream contracts called by the CCIP router. Below is a compiled list of potential errors you might encounter. Referencing this list will enable you to capture and handle these exceptions gracefully.
## Router Errors
## OnRamp Errors
## Rate Limiter Errors
## Token (ERC20) Errors
## Price Registry Errors
---
# Chainlink CCIP API Reference Documentation (SVM)
Source: https://docs.chain.link/ccip/api-reference/svm
## Available Versions
### Latest Release
- **[CCIP v1.6.0](/ccip/api-reference/svm/v1.6.0)** (Current Version)
- Initial release for Solana
- Cross-chain messaging from Solana to EVM and SVM chains
- Token transfers using SPL tokens
- Support for native SOL or SPL fee payments
- Configurable gas limits and out-of-order execution
## Documentation Structure
Each version includes detailed documentation for:
- Message Structures and Extra Args
- Router Instructions
- Receiver Instructions
- Events
- Error Handling
---
# CCIP v1.6.0 SVM API Reference
Source: https://docs.chain.link/ccip/api-reference/svm/v1.6.0
## API References
### Core Components
- [Messages](/ccip/api-reference/svm/v1.6.0/messages) - Message structures and extra args for cross-chain messaging
- [Router](/ccip/api-reference/svm/v1.6.0/router) - Instructions for sending messages and managing CCIP routing on Solana
- [Receiver](/ccip/api-reference/svm/v1.6.0/receiver) - Implementation guide for CCIP message receivers on SVM blockchains
### Token Pool Components
- [Base Token Pool Library](/ccip/api-reference/svm/v1.6.0/base-token-pool) - Shared library providing common functionality for all token pool implementations
- [BurnMint Token Pool](/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool) - Token pool implementation using burn and mint strategy for cross-chain transfers
- [Lock-Release Token Pool](/ccip/api-reference/svm/v1.6.0/lock-release-token-pool) - Token pool implementation using lock and release strategy with liquidity management
### Reference Materials
- [Events](/ccip/api-reference/svm/v1.6.0/events) - Event emissions for tracking cross-chain messages and token operations
- [Errors](/ccip/api-reference/svm/v1.6.0/errors) - Comprehensive list of CCIP error codes and troubleshooting guidance
---
# CCIP v1.6.0 SVM Messages API Reference
Source: https://docs.chain.link/ccip/api-reference/svm/v1.6.0/messages
## Messages
### SVMTokenAmount
The `SVMTokenAmount` struct captures essential information about a specific token and the transfer amount.
```rust
pub struct SVMTokenAmount {
pub token: Pubkey,
pub amount: u64,
}
```
**Parameters**
| Field | Type | Description |
| -------- | -------- | ----------------------------------------------------------------------------------------------- |
| `token` | `Pubkey` | The **mint address** of the token on Solana. For example, the Mint `Pubkey` for an SPL token. |
| `amount` | `u64` | The number of tokens to transfer is in the token's smallest unit (often nine decimals for SPL). |
**Where it's used**:
- CCIP Sender: As part of `SVM2AnyMessage.token_amounts`
- CCIP Receiver: As part of `Any2SVMMessage.token_amounts`
## Extra Args
Below is a developer-focused API reference for the extra arguments used when sending SVM → EVM or SVM → SVM messages. These extra arguments get serialized into the `extra_args` field inside the `SVM2AnyMessage` struct. We will show:
1. The **constants** (tags), identifying each extra-args type.
2. The **struct layouts** (`EVMExtraArgsV2` and `SVMExtraArgsV1`).
3. How to **serialize** (and deserialize) these extra-args so they can be appended to the extra_args in a CCIP message.
### Tags
You **prepend** a 4-byte "tag" inside your message, identifying which extra args to use. These tags are just constants, computed as 32-bit from a known hash:
| Constant | Value (Hex) | Purpose |
| ----------------------- | ------------ | ----------------------------------------------- |
| `EVM_EXTRA_ARGS_V2_TAG` | `0x181dcf10` | Denotes `EVMExtraArgsV2` (SVM → EVM extra args) |
| `SVM_EXTRA_ARGS_V1_TAG` | `0x1f3b3aba` | Denotes `SVMExtraArgsV1` (SVM → SVM extra args) |
### `EVMExtraArgsV2`
When sending a message **from SVM-based blockchains (e.g., Solana) to EVM-based blockchains (e.g., Ethereum)**, you can supply extra parameters such as a per-message `gas_limit`. This is particularly relevant for an EVM-based destination that requires enough gas to call the receiver contract.
```rust
/// `bytes4(keccak256("CCIP EVMExtraArgsV2"))` = `0x181dcf10`
pub const EVM_EXTRA_ARGS_V2_TAG: u32 = 0x181dcf10;
#[derive(Clone, AnchorSerialize, AnchorDeserialize, Default)]
pub struct EVMExtraArgsV2 {
pub gas_limit: u128, // message gas limit for EVM execution
pub allow_out_of_order_execution: bool, // user-configurable OOO execution
}
```
**Parameters**
| Field | Type | Description |
| ------------------------------ | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gas_limit` | `u128` | The exact **gas limit** to call the `ccipReceive` function of the Receiver contract. If your Receiver contract needs 50,000 gas to run, set `gas_limit = 50000`. |
| `allow_out_of_order_execution` | `bool` | If `true`, you let CCIP process the message in a potentially **out-of-order** manner. |
Below is a Rust function showing how to serialize an `EVMExtraArgsV2`:
```rust
fn create_evm_extra_args(gas_limit: u128, ooo: bool) -> Vec {
use anchor_lang::AnchorSerialize;
let data = EVMExtraArgsV2 {
gas_limit,
allow_out_of_order_execution: ooo,
};
// 1) Start with the 4-byte tag, big-endian
let mut buffer = EVM_EXTRA_ARGS_V2_TAG.to_be_bytes().to_vec();
// 2) Serialize the struct via Borsh
let mut serialized = data.try_to_vec().unwrap();
// 3) Append the serialized payload
buffer.append(&mut serialized);
buffer
}
```
### `SVMExtraArgsV1`
If your destination is another SVM-based blockchain, the extra args can define `compute_units`, an accounts list, and whether you allow out-of-order execution, among other fields.
```rust
/// `bytes4(keccak256("CCIP SVMExtraArgsV1"))` = `0x1f3b3aba`
pub const SVM_EXTRA_ARGS_V1_TAG: u32 = 0x1f3b3aba;
#[derive(Clone, AnchorSerialize, AnchorDeserialize, Default)]
pub struct SVMExtraArgsV1 {
pub compute_units: u32, // Offchain usage for compute budget
pub account_is_writable_bitmap: u64, // Bitmask describing which accounts are writable
pub allow_out_of_order_execution: bool, // Must match the receiving chain config if OOO
pub token_receiver: [u8; 32], // If tokens are sent, cannot be the 0-address
pub accounts: Vec<[u8; 32]>, // Additional accounts for SVM cross-program calls
}
```
**Parameters**
| Field | Type | Description |
| ------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `compute_units` | `u32` | An optional override determines how many compute units the destination OffRamp should set. |
| `account_is_writable_bitmap` | `u64` | A bitmask marking which `accounts` entries are writable (bit = 1) vs. read-only (bit = 0). For example, if the 2nd bit is set, the underlying on-chain program treats `accounts[1]` as writable. |
| `allow_out_of_order_execution` | `bool` | Whether this cross-chain message may be executed out of order (OOO). If `true`, the normal strict sequence requirement can be bypassed, allowing the chain to process it earlier/later. |
| `token_receiver` | `[u8; 32]` | A Solana Pubkey (32 bytes) that receives tokens if the cross-chain message includes token transfers. If so, this field must be nonzero. Typically, the Associated Token Account (ATA) of the receiver. Set this to [0; 32] if no tokens are sent. |
| `accounts` | `Vec<[u8; 32]>` | A list of additional accounts (each a 32-byte Solana `Pubkey`) that the transaction needs to reference on the destination chain. These might be program IDs to invoke, PDAs storing state, or other required accounts. |
Below is a Rust function showing how to serialize an `SVMExtraArgsV1`:
```rust
fn create_svm_extra_args(
compute_units: u32,
account_is_writable_bitmap: u64,
allow_ooo: bool,
token_receiver: [u8; 32],
accounts: Vec<[u8; 32]>,
) -> Vec {
use anchor_lang::AnchorSerialize;
let data = SVMExtraArgsV1 {
compute_units,
account_is_writable_bitmap,
allow_out_of_order_execution: allow_ooo,
token_receiver,
accounts,
};
// 1) Start with the 4-byte tag, big-endian
let mut buffer = SVM_EXTRA_ARGS_V1_TAG.to_be_bytes().to_vec();
// 2) Serialize the struct via Borsh (AnchorSerialize)
let mut serialized = data.try_to_vec().unwrap();
// 3) Append the serialized payload
buffer.append(&mut serialized);
buffer
}
```
## Message Structures
### SVM2AnyMessage
When you send a cross-chain message from SVM-based blockchains to another blockchain, the `SVM2AnyMessage` struct packages all necessary data: destination address, payload, tokens, fees, and any extra arguments.
```rust
pub struct SVM2AnyMessage {
pub receiver: Vec,
pub data: Vec,
pub token_amounts: Vec,
pub fee_token: Pubkey,
pub extra_args: Vec,
}
```
**Parameters**
| Field | Type | Description |
| --------------- | --------------------- | -------------------------------------------------------------------------------------------------------- |
| `receiver` | `Vec` | The destination address in raw byte format (since different blockchains have different address lengths). |
| `data` | `Vec` | Arbitrary payload to be parsed by the receiver on the destination blockchain. |
| `token_amounts` | `Vec` | List of tokens to transfer. |
| `fee_token` | `Pubkey` | Mint of the fee token used to pay cross-chain fees. Use `Pubkey::default()` if paying with native SOL. |
| `extra_args` | `Vec` | Additional arguments or metadata (Read Extra Args for more details). |
### Any2SVMMessage
When the OffRamp executes a cross-chain message into an SVM-based blockchain (e.g., Solana), it provides an `Any2SVMMessage` to the receiving program. This struct lets you verify the source, handle tokens, and parse the payload.
```rust
pub struct Any2SVMMessage {
pub message_id: [u8; 32],
pub source_chain_selector: u64,
pub sender: Vec,
pub data: Vec,
pub token_amounts: Vec,
}
```
**Parameters**
| Field | Type | Description |
| ----------------------- | --------------------- | ----------------------------------------------------------------------------- |
| `message_id` | `[u8; 32]` | Unique 32-byte ID for this cross-chain message. |
| `source_chain_selector` | `u64` | Indicates which blockchain the message originated from. |
| `sender` | `Vec` | The sender's address on the source blockchain, in raw byte format. |
| `data` | `Vec` | Arbitrary payload to be parsed by the receiver on the destination blockchain. |
| `token_amounts` | `Vec` | If tokens are bridged, they appear here, along with how many. |
### SVM2AnyRampMessage
When sending a cross-chain message from a Solana-compatible (SVM) blockchain to a destination chain (EVM, SVM, etc.), the `SVM2AnyRampMessage` struct describes all necessary data and token information. It’s typically constructed inside the ccip_send call.
```rust
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct SVM2AnyRampMessage {
pub header: RampMessageHeader,
pub sender: Pubkey,
pub data: Vec,
pub receiver: Vec,
pub extra_args: Vec,
pub fee_token: Pubkey,
pub token_amounts: Vec,
pub fee_token_amount: CrossChainAmount,
pub fee_value_juels: CrossChainAmount,
}
```
**Parameters**
| Field | Type | Description |
| ------------------ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `header` | `RampMessageHeader` | Metadata about the message: `message_id`, `source_chain_selector`, `dest_chain_selector`, `sequence_number`, `nonce`. |
| `sender` | `Pubkey` | The sender's Solana Pubkey (32 bytes) on the source chain, representing the wallet or program that invoked `ccip_send`. |
| `data` | `Vec` | Arbitrary payload data. |
| `receiver` | `Vec` | The destination address in raw bytes. For example, if it's an EVM chain, this might be 20 bytes (the EVM contract address). If it's an SVM chain, it might be 32 bytes (a Solana Pubkey). |
| `extra_args` | `Vec` | Serialized "extra arguments" relevant to the destination. |
| `fee_token` | `Pubkey` | Mint of the token used to pay the cross-chain fees. |
| `token_amounts` | `Vec` | List of tokens locked or burned as part of this cross-chain message. Each `SVM2AnyTokenTransfer` in the array has: `source_pool_address` (the source pool address that locked/burned these tokens), `dest_token_address` (the byte address for the destination token contract), `extra_data` (optional data attached to this token transfer), `amount` (how many tokens are locked on Solana, denominated in the source token's decimals), and `dest_exec_data` (destination-chain execution parameters). |
| `fee_token_amount` | `CrossChainAmount` | The billing token amount (in local chain decimals) was ultimately charged as a fee. |
| `fee_value_juels` | `CrossChainAmount` | The fees are converted to Juels. |
---
# CCIP v1.6.0 SVM Router API Reference
Source: https://docs.chain.link/ccip/api-reference/svm/v1.6.0/router
## Router
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/ccip-router)
Below is a complete API reference for the CCIP Router program instructions.
### Message Sending
#### `ccip_send`
This instruction is the entry point for sending a cross-chain message from an SVM-based blockchain to a specified destination blockchain.
```rust
fn ccip_send(
ctx: Context,
dest_chain_selector: u64,
message: SVM2AnyMessage,
token_indexes: Vec
) -> Result<[u8; 32]>;
```
##### Parameters
| Name | Type | Description |
| ---------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `dest_chain_selector` | `u64` | The unique CCIP blockchain identifier of the destination blockchain. |
| `message` | `SVM2AnyMessage` | Read [Messages](/ccip/api-reference/svm/v1.6.0/messages#svm2anymessage) for more details. |
| `token_indexes` | `Vec` | Index offsets slicing the remaining accounts so each token's subset can be grouped (see [Context](#context-accounts)). |
##### Context (Accounts)
These are the required accounts passed alongside the instructions. For relevant PDAs, the instructions on how to derive seeds are given below.
| Field | Type | Writable? | Description |
| ------------------------------------------------ | ---------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `config` | `Account` | No | Router config PDA. **Derivation**: `["config"]` under the `ccip_router` program. |
| `dest_chain_state` | `Account` | Yes | Per-destination blockchain PDA for `sequence_number`, chain config, etc. **Derivation**: `["dest_chain_state", dest_chain_selector]` under the `ccip_router` program. |
| `nonce` | `Account` | Yes | Current nonce PDA for `(authority, dest_chain_selector)`. **Derivation**: `["nonce", dest_chain_selector, authority_pubkey]` under the `ccip_router` program. |
| `authority` | `Signer<'info>` | Yes | The user/wallet paying for the `ccip_send` transaction. Also, it must match the seeds in `nonce`. |
| `system_program` | `Program<'info, System>` | No | Standard System Program. |
| `fee_token_program` | `Interface<'info, TokenInterface>` | No | The token program used for fee payment (e.g., SPL Token). If paying with native SOL, this is just the `SystemProgramID`. |
| `fee_token_mint` | `InterfaceAccount<'info, Mint>` | No | Fee token mint. If paying in SPL, pass your chosen token mint. If paying in native SOL, a special "zero" mint (`Pubkey::default()`) or "[native mint](https://docs.rs/spl-token/latest/spl_token/native_mint/constant.ID.html)" (`native_mint::ID`) is used. |
| `fee_token_user_associated_account` | `UncheckedAccount<'info>` | Yes | If fees are paid in SPL, this is the user's ATA. **Derivation**: It is derived via the Associated Token Program seeds: `[authority_pubkey, fee_token_program.key(), fee_token_mint.key() ]` under the relevant Token Program (Make sure you use the correct token program ID—**Token-2022** vs.SPL Token). If paying with native SOL, pass the zero address (`Pubkey::default())` and do not mark it writable. |
| `fee_token_receiver` | `InterfaceAccount<'info, TokenAccount>` | Yes | The ATA where all the fees are collected. **Derivation**: from `[fee_billing_signer,fee_token_program.key(),fee_token_mint.key()]`. |
| `fee_billing_signer` | `UncheckedAccount<'info>` | No | PDA is the router's billing authority for transferring fees (native SOL or SPL tokens). from fee_token_user_associated_account to fee_token_receiver. **Derivation**: `["fee_billing_signer"]` under the `ccip_router` program. |
| `fee_quoter` | `UncheckedAccount<'info>` | No | The **Fee Quoter** program ID. |
| `fee_quoter_config` | `UncheckedAccount<'info>` | No | The global Fee Quoter config PDA. **Derivation**: `["config"]` under the `fee_quoter` program. |
| `fee_quoter_dest_chain` | `UncheckedAccount<'info>` | No | Per-destination blockchain PDA in the Fee Quoter program. It stores chain-specific configuration (gas price data, limits, etc.) for SVM2Any messages. **Derivation**: `["dest_chain",dest_chain_selector]` under the `fee_quoter` program. |
| `fee_quoter_billing_token_config` | `UncheckedAccount<'info>` | No | A per-fee-token PDA in the Fee Quoter program stores token-specific parameters (price data, billing premiums, etc.) used to calculate fees. **Derivation**: If the message pays fees in native SOL, the seed uses the `native_mint::ID`; otherwise, it uses the SPL token's `mint` public key. `["fee_billing_token_config", seed]` under the `fee_quoter` program. |
| `fee_quoter_link_token_config` | `UncheckedAccount<'info>` | No | PDA containing the Fee Quoter's LINK token billing configuration (LINK price data, premium multipliers, etc.). The fee token amount is converted into "juels" using LINK's valuation from this account during fee calculation. **Derivation**: `["fee_billing_token_config", link_token_mint]` under the `fee_quoter` program. |
| `rmn_remote` | `UncheckedAccount<'info>` | No | The RMN program ID used to verify if a given chain is cursed. |
| `rmn_remote_curses` | `UncheckedAccount<'info>` | No | PDA containing list of curses chain selectors and global curses. **Derivation**: `["curses"]` under the `rmn_remote` program. |
| `rmn_remote_config` | `UncheckedAccount<'info>` | No | RMN config PDA, containing configuration that control how curse verification works. **Derivation**: `["config"]` under the `rmn_remote` program. |
| `token_pools_signer` | `UncheckedAccount<'info>` | Yes | PDA with the authority to **CPI** into token pool logic (mint/burn, lock/release). **Derivation**: `["external_token_pools_signer"]` under the `ccip_router` program. |
| `remaining_accounts` | `&[AccountInfo]` (slice) | Yes | You pass extra accounts for each token you wish to transfer (does not include fee tokens). Typically includes the sender ATA, the token pool config, token admin registry PDAs… etc. |
##### How `remaining_accounts` and `token_indexes` Work
When you call the Router's `ccip_send` instruction, you pass:
1. A list of `token_amounts` you want to transfer cross-chain.
2. A slice of `remaining_accounts` containing the per-token PDAs (e.g., user token ATA, pool config, token admin registry PDA, etc.).
3. A `token_indexes` array tells the Router where in `remaining_accounts` each token's sub-slice begins.
###### Reason for `remaining_accounts`
On Solana, each Anchor instruction has a fixed set of named accounts. However, CCIP must handle any number of tokens, each requiring many accounts. Rather than define a massive static context, the Router uses Anchor's dynamic `ctx.remaining_accounts`: All token-specific accounts are packed into one slice.
###### Reason for `token_indexes`
The Router must figure out which segment of that slice corresponds to token #0, token #1, etc. So you provide an integer offset in `token_indexes[i]` indicating where the `i`th token's accounts begin inside `remaining_accounts`.
The Router:
- Slices out `[start..end)` for the `i`th token's accounts. The subslice is from start up to but **not** including end. This is how you indicate that the token i's accounts occupy positions start, start+1, …, end-1.
- Validates each account.
- Calls the appropriate token pool to the lock-or-burn operation on them.
###### Structure of Each Token's Sub-slice
Inside each token's sub-slice, the Router expects:
1. The user's token account (ATA).
2. The token's chain PDAs.
3. Lookup table PDAs, token admin registry, pool program, pool config, pool signer, token program, the mint, etc.
In total, it is typically **12 or more** accounts per token. Repeat that "per-token chunk" of \~12 accounts for each token if you have multiple tokens. These accounts are extracted in this order:
| Index | Account | Description |
| ----- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 | `user_token_account` | ATA for `(authority, mint, token_program)`. |
| 1 | `token_billing_config` | Per-destination blockchain-specific fee overrides for a given token. **Note**: In most cases, tokens do not have a custom billing fee structure. In these cases, CCIP uses the fallback default fee configuration. PDA `["per_chain_per_token_config", dest_chain_selector, mint]` under the fee_quoter program. |
| 2 | `pool_chain_config` | PDA `["ccip_tokenpool_chainconfig", dest_chain_selector, mint]` under fee_quoter program. |
| 3 | `lookup_table` | Address Lookup Table that the token's admin registry claims. Must match the Token Admin Registry's `lookup_table` field. |
| 4 | `token_admin_registry` | PDA `["token_admin_registry", mint]` under the ccip_router program. |
| 5 | `pool_program` | The Token Pool program ID (for CPI calls). |
| 6 | `pool_config` | PDA `[ "ccip_tokenpool_config", mint ]` under the pool_program. |
| 7 | `pool_token_account` | ATA for (`pool_signer`, `mint`, `token_program`). |
| 8 | `pool_signer` | PDA `[ "ccip_tokenpool_signer", mint ]` under the pool_program. |
| 9 | `token_program` | Token program ID (e.g. `spl_token` or 2022). Also, it must match the mint's `owner`. |
| 10 | `mint` | The SPL Mint (public key) for this token. |
| 11 | `fee_token_config` | A token billing configuration account under the Fee Quoter program. It contains settings such as whether there is a specific pricing for the token, its pricing in USD, and any premium multipliers. **Note**: In most cases, tokens do not have a custom billing fee structure. In these cases, CCIP uses the fallback default fee configuration. PDA `["fee_billing_token_config", mint]` under the fee_quoter program. |
| 12 | `…` | Additional accounts are passed if required by the token pool. |
###### Examples
**One Token Transfer**
Suppose you want to send **one token** (`myMint`) cross-chain:
1. `token_amounts`: length = 1, e.g. `[{ token: myMint_pubkey, amount: 1000000 }]`.
2. `token_indexes`: `[1]`. Meaning:
- The 0th token's remaining_accounts sub-slice will be `[token_indexes[0] .. endOfArray)`, i.e. `[1..]`.
- The user's Associated Token Account (ATA) for that token is found at `remaining_accounts[0]`.
3. Your `remaining_accounts` must have:
- **1** user's ATA (the sender ATA for the single token).
- **12** pool-related accounts (pool config, chain config, token program, etc.).
That is **13** total.
**Two Token Transfers**
Suppose you want to send **two tokens** (`mintA` and `mintB`) cross-chain:
1. `token_amounts`: length = 2, e.g. `[{ token: mintA_pubkey, amount: 1000000 },{ token: mintB_pubkey, amount: 2000000 } ]`.
2. `token_indexes` must be length=2 since there are two tokens, and token_indexes = [2, 14]. Explanation:
- After we skip the user ATAs at indices [0..2), we want the next 12 accounts for the first token to lie in `[2..14)`, and then the next 12 for the second token to lie in `[14..end)`.
- The Router program will use token_indexes:
1. For the first token: The sub slice is [2..14).
2. For the second token: The sub slice is [14…endOfArray).
3. Your `remaining_accounts` must have:
- **2** user ATAs (one for `mintA`, one for `mintB`).
- **12** pool accounts for `mintA`.
- **12** pool accounts for `mintB`.
Thus `2 + 12 + 12 = 26` accounts in `remaining_accounts`.
#### `get_fee`
Queries the Router for the fee required to send a cross-chain message. This is a permissionless call that calculates fees without verifying curse status to avoid RMN CPI overhead.
```rust
fn get_fee(
ctx: Context,
dest_chain_selector: u64,
message: SVM2AnyMessage,
) -> Result;
```
##### Parameters
| Name | Type | Description |
| ---------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `dest_chain_selector` | `u64` | The unique CCIP blockchain identifier of the destination blockchain. |
| `message` | `SVM2AnyMessage` | The message for which to calculate fees. Read [Messages](/ccip/api-reference/svm/v1.6.0/messages#svm2anymessage) for more details. |
##### Return Value
| Type | Description |
| --------------------------- | -------------------------------------------------------------------- |
| `GetFeeResult` | Contains fee amount, fee in juels (LINK), and the fee token address. |
```rust
struct GetFeeResult {
pub amount: u64, // Fee amount in the specified fee token
pub juels: u128, // Fee value converted to LINK juels
pub token: Pubkey, // The fee token address
}
```
##### Context (Accounts)
| Field | Type | Writable? | Description |
| --------------------------------- | -------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `config` | `Account` | No | Router config PDA. **Derivation**: `["config"]` under the `ccip_router` program. |
| `dest_chain_state` | `Account` | No | Per-destination blockchain PDA for retrieving lane version. **Derivation**: `["dest_chain_state", dest_chain_selector]` under the `ccip_router` program. |
| `fee_quoter` | `UncheckedAccount<'info>` | No | The **Fee Quoter** program ID. |
| `fee_quoter_config` | `UncheckedAccount<'info>` | No | The global Fee Quoter config PDA. **Derivation**: `["config"]` under the `fee_quoter` program. |
| `fee_quoter_dest_chain` | `UncheckedAccount<'info>` | No | Per-destination blockchain PDA in the Fee Quoter program. **Derivation**: `["dest_chain", dest_chain_selector]` under the `fee_quoter` program. |
| `fee_quoter_billing_token_config` | `UncheckedAccount<'info>` | No | Fee token billing configuration PDA. **Derivation**: `["fee_billing_token_config", fee_token_mint]` under the `fee_quoter` program. Uses `native_mint::ID` if paying with native SOL. |
| `fee_quoter_link_token_config` | `UncheckedAccount<'info>` | No | LINK token billing configuration PDA for fee conversion. **Derivation**: `["fee_billing_token_config", link_token_mint]` under the `fee_quoter` program. |
| `remaining_accounts` | `&[AccountInfo]` (slice) | No | Per-token billing configurations. See [Remaining Accounts Structure](#remaining-accounts-structure-for-get_fee) below. |
##### Remaining Accounts Structure for `get_fee`
The `remaining_accounts` must be provided in this specific order for each token being transferred:
1. **Billing Token Config**: Token-specific billing configuration under the Fee Quoter program
- **Derivation**: `["fee_billing_token_config", token_mint]` under the `fee_quoter` program
2. **Per-Chain Per-Token Config**: Chain and token specific fee overrides under the Fee Quoter program
- **Derivation**: `["per_chain_per_token_config", dest_chain_selector, token_mint]` under the `fee_quoter` program
**Example for 2 tokens:**
```
remaining_accounts = [
billing_config_token_A, // Token A billing config
per_chain_config_token_A, // Token A per-chain config
billing_config_token_B, // Token B billing config
per_chain_config_token_B, // Token B per-chain config
]
```
##### Usage Notes
- **Permissionless**: Anyone can call this instruction to query fees
- **No Curse Check**: Does not verify RMN curse status for performance
- **Fee Calculation**: Returns both the fee amount in the specified token and the equivalent value in LINK juels
- **Token Support**: Supports both native SOL and SPL token fee payments
### Token Administration
These instructions manage token administrator roles and token registration with CCIP.
#### `owner_propose_administrator`
Proposes a token administrator for a given SPL token. This is used for self-service registration when you control the token's mint authority.
```rust
fn owner_propose_administrator(
ctx: Context,
token_admin_registry_admin: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ----------------------------------------- | --------------------- | --------------------------------------------------- |
| `token_admin_registry_admin` | `Pubkey` | The public key of the proposed token administrator. |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------------- | -------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `config` | `Account` | No | Router config PDA. **Derivation**: `["config"]` under the `ccip_router` program. |
| `token_admin_registry` | `Account` | Yes | Token admin registry PDA to initialize. **Derivation**: `["token_admin_registry", mint]` under the `ccip_router` program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint for which to propose an administrator. |
| `authority` | `Signer<'info>` | Yes | Must be the mint authority of the SPL token. |
| `system_program` | `Program<'info, System>` | No | Standard System Program. |
##### Authorization
- **Caller**: Must be the SPL token's `mint_authority`
- **Registry State**: TokenAdminRegistry PDA must not already exist for this mint
#### `owner_override_pending_administrator`
Overrides the pending administrator before they accept the role. Can only be called by the token's mint authority.
```rust
fn owner_override_pending_administrator(
ctx: Context,
token_admin_registry_admin: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ----------------------------------------- | --------------------- | ------------------------------------------------- |
| `token_admin_registry_admin` | `Pubkey` | The public key of the new proposed administrator. |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------------- | -------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `config` | `Account` | No | Router config PDA. **Derivation**: `["config"]` under the `ccip_router` program. |
| `token_admin_registry` | `Account` | Yes | Existing token admin registry PDA. **Derivation**: `["token_admin_registry", mint]` under the `ccip_router` program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint. |
| `authority` | `Signer<'info>` | Yes | Must be the mint authority of the SPL token. |
| `system_program` | `Program<'info, System>` | No | Standard System Program. |
##### Authorization
- **Caller**: Must be the SPL token's `mint_authority`
- **Registry State**: TokenAdminRegistry PDA must exist but have no accepted administrator yet
#### `ccip_admin_propose_administrator`
Proposes a token administrator via CCIP governance. Used when the caller cannot access the mint authority.
```rust
fn ccip_admin_propose_administrator(
ctx: Context,
token_admin_registry_admin: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ----------------------------------------- | --------------------- | --------------------------------------------------- |
| `token_admin_registry_admin` | `Pubkey` | The public key of the proposed token administrator. |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------------- | -------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `config` | `Account` | No | Router config PDA. **Derivation**: `["config"]` under the `ccip_router` program. |
| `token_admin_registry` | `Account` | Yes | Token admin registry PDA to initialize. **Derivation**: `["token_admin_registry", mint]` under the `ccip_router` program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint for which to propose an administrator. |
| `authority` | `Signer<'info>` | Yes | Must be the Router program's upgrade authority. |
| `system_program` | `Program<'info, System>` | No | Standard System Program. |
##### Authorization
- **Caller**: Must be the Router program's upgrade authority
- **Registry State**: TokenAdminRegistry PDA must not already exist for this mint
#### `accept_admin_role_token_admin_registry`
Accepts the administrator role for a token. Must be called by the pending administrator to finalize registration.
```rust
fn accept_admin_role_token_admin_registry(
ctx: Context,
) -> Result<()>;
```
##### Parameters
This instruction takes no additional parameters.
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------------- | -------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `config` | `Account` | No | Router config PDA. **Derivation**: `["config"]` under the `ccip_router` program. |
| `token_admin_registry` | `Account` | Yes | Existing token admin registry PDA. **Derivation**: `["token_admin_registry", mint]` under the `ccip_router` program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint. |
| `authority` | `Signer<'info>` | Yes | Must be the pending administrator listed in the token admin registry. |
##### Authorization
- **Caller**: Must match the `pending_administrator` field in the TokenAdminRegistry PDA
- **Registry State**: TokenAdminRegistry PDA must exist with a pending administrator
#### `transfer_admin_role_token_admin_registry`
Transfers the administrator role to a new administrator. This is a two-step process requiring the new admin to accept.
```rust
fn transfer_admin_role_token_admin_registry(
ctx: Context,
new_admin: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ------------------------ | --------------------- | ---------------------------------------- |
| `new_admin` | `Pubkey` | The public key of the new administrator. |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------------- | -------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `config` | `Account` | No | Router config PDA. **Derivation**: `["config"]` under the `ccip_router` program. |
| `token_admin_registry` | `Account` | Yes | Existing token admin registry PDA. **Derivation**: `["token_admin_registry", mint]` under the `ccip_router` program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint. |
| `authority` | `Signer<'info>` | Yes | Must be the current administrator of the token admin registry. |
##### Authorization
- **Caller**: Must be the current `administrator` listed in the TokenAdminRegistry PDA
- **Registry State**: TokenAdminRegistry PDA must exist with an active administrator
#### `set_pool`
Sets the Address Lookup Table that defines the token pool for cross-chain transfers. This enables or disables a token for CCIP.
```rust
fn set_pool(
ctx: Context,
writable_indexes: Vec,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------- |
| `writable_indexes` | `Vec` | Bitmap of indexes in the lookup table that should be marked as writable during transactions. |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------------- | -------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `config` | `Account` | No | Router config PDA. **Derivation**: `["config"]` under the `ccip_router` program. |
| `token_admin_registry` | `Account` | Yes | Existing token admin registry PDA. **Derivation**: `["token_admin_registry", mint]` under the `ccip_router` program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint. |
| `pool_lookuptable` | `UncheckedAccount<'info>` | No | Address Lookup Table containing the token pool accounts. Pass zero address to delist token from CCIP. |
| `authority` | `Signer<'info>` | Yes | Must be the current administrator of the token admin registry. |
##### Authorization
- **Caller**: Must be the current `administrator` listed in the TokenAdminRegistry PDA
- **Pool Validation**: If not zero address, the lookup table must contain at least the minimum required accounts
##### Pool Status
- **Enable Token**: Set `pool_lookuptable` to a valid Address Lookup Table with required pool accounts
- **Disable Token**: Set `pool_lookuptable` to zero address (`Pubkey::default()`) to delist from CCIP
##### Address Lookup Table Requirements
The `pool_lookuptable` must contain exactly these accounts in the specified order for proper CCIP token pool operations:
| Index | Account | Derivation | Description |
| ----- | --------------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| 0 | **Lookup Table Address** | N/A | The Address Lookup Table itself |
| 1 | **Token Admin Registry** | `["token_admin_registry", mint]` under Router program | Registry PDA for this token |
| 2 | **Pool Program** | N/A | The token pool program ID (e.g., BurnMint or LockRelease Token Pool program) |
| 3 | **Pool Config** | `["ccip_tokenpool_config", mint]` under pool program | Token-specific pool configuration and settings |
| 4 | **Pool Token Account** | ATA of (pool_signer, mint, token_program) | Associated Token Account holding pool's tokens |
| 5 | **Pool Signer** | `["ccip_tokenpool_signer", mint]` under pool program | PDA with authority for token operations |
| 6 | **Token Program** | N/A | SPL Token program (`TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`) or Token-2022 program (`TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`) |
| 7 | **Token Mint** | N/A | The SPL token mint address |
| 8 | **Fee Token Config** | `["fee_billing_token_config", mint]` under Fee Quoter | Fee token billing configuration PDA |
| 9 | **CCIP Router Pool Signer** | `["external_token_pools_signer", pool_program]` under Router | Router's signer PDA for the pool |
**Notes:**
- The ALT contains exactly 10 accounts as shown above
- Custom token pools may require additional accounts beyond these core 10
- All PDA derivations must use the correct program IDs
- Pool Token Account must be the proper Associated Token Account for the Pool Signer
- Writable permissions are specified via the `writable_indexes` parameter
**Creating the ALT:**
1. Create a new Address Lookup Table
2. Add accounts in the exact order specified above
3. Ensure all PDA derivations are correct for your specific token and pool program
4. Call `set_pool` with the ALT address and appropriate `writable_indexes`
For PDA derivation examples and account validation details, see the [Token Pool documentation](/ccip/concepts/cross-chain-token/svm/token-pools).
---
# CCIP v1.6.0 SVM Receiver API Reference
Source: https://docs.chain.link/ccip/api-reference/svm/v1.6.0/receiver
## Receiver
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/example-ccip-receiver)
Below is a complete API reference for the `ccip_receive` instruction that must be implemented by any Solana program wishing to receive CCIP messages.
### `ccip_receive`
This instruction is the entry point for receiving a cross-chain message on an SVM-based blockchain from any source blockchain.
```rust
pub fn ccip_receive(
ctx: Context,
message: Any2SVMMessage
) -> Result<()>;
```
#### Parameters
| Name | Type | Description |
| --------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `message` | `Any2SVMMessage` | The cross-chain message being delivered. See [Message Structure](/ccip/api-reference/svm/v1.6.0/messages#any2svmmessage) for details. |
#### Context (Accounts)
These are the required accounts that must be passed to implement a secure CCIP Receiver. The first three accounts form the critical security validation chain and must be implemented exactly as shown.
| Field | Type | Writable? | Description |
| ------------------------------ | -------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `authority` | `Signer<'info>` | No | The Offramp CPI signer PDA. This must be the first account. **Derivation**: `[EXTERNAL_EXECUTION_CONFIG_SEED, receiver_program_id]` under the `offramp_program`. |
| `offramp_program` | `UncheckedAccount<'info>` | No | The Offramp program ID. This exists only to derive the allowed offramp PDA and must be the second account. |
| `allowed_offramp` | `UncheckedAccount<'info>` | No | PDA owned by the Router program that verifies this Offramp is allowed. **Derivation**: `[ALLOWED_OFFRAMP, source_chain_selector, offramp_program_key]` under the Router program. Must be the third account. |
| *Additional accounts* | *Various* | *Varies* | The receiver program can define additional accounts as needed for its specific logic (state accounts, token accounts, etc.). These are application-specific. |
#### Implementation Requirements
1. **Instruction Name and Discriminator**:
- If using Anchor, the instruction name must be exactly `ccip_receive`.
- If not using Anchor, the instruction discriminator must be `[0x0b, 0xf4, 0x09, 0xf9, 0x2c, 0x53, 0x2f, 0xf5]`.
2. **Security Pattern**:
- The first three accounts in the `CcipReceive` context must follow the exact pattern shown above.
- Your program must store the Router address (typically in a state account) to verify the `allowed_offramp` PDA.
3. **Account Validation**:
- The `authority` must be validated as a PDA derived from the offramp program.
- The `allowed_offramp` must be validated as a PDA owned by the router program with the correct seeds.
4. **State Management**:
- The receiver program should maintain state that includes at minimum the router address.
- Optionally track processed message IDs to prevent replay attacks.
## Example
Below is a minimal example of a secure `CcipReceive` context implementation:
```rust
#[derive(Accounts, Debug)]
#[instruction(message: Any2SVMMessage)]
pub struct CcipReceive<'info> {
// Offramp CPI signer PDA must be first.
#[account(
seeds = [EXTERNAL_EXECUTION_CONFIG_SEED, crate::ID.as_ref()],
bump,
seeds::program = offramp_program.key(),
)]
pub authority: Signer<'info>,
/// CHECK offramp program: exists only to derive the allowed offramp PDA
pub offramp_program: UncheckedAccount<'info>,
/// CHECK PDA of the router program verifying the signer is an allowed offramp.
#[account(
owner = state.router @ CcipReceiverError::InvalidCaller,
seeds = [
ALLOWED_OFFRAMP,
message.source_chain_selector.to_le_bytes().as_ref(),
offramp_program.key().as_ref()
],
bump,
seeds::program = state.router,
)]
pub allowed_offramp: UncheckedAccount<'info>,
// Program-specific state account - must contain router address
#[account(
seeds = [STATE],
bump,
)]
pub state: Account<'info, BaseState>,
// Additional program-specific accounts...
}
```
And a minimal implementation of the `ccip_receive` instruction:
```rust
pub fn ccip_receive(ctx: Context, message: Any2SVMMessage) -> Result<()> {
// Process message data
if !message.data.is_empty() {
// Process arbitrary data payload
}
// Process token transfers
if !message.token_amounts.is_empty() {
// Handle received tokens
}
// Emit event for tracking
emit!(MessageReceived {
message_id: message.message_id
});
Ok(())
}
```
## Token Handling
When implementing a CCIP Receiver that needs to handle token transfers, you must create a PDA that will serve as the token administrator. This PDA will have the authority to sign token transfer instructions.
### Token Admin PDA
Create a dedicated PDA to manage tokens within your program:
```rust
// During program initialization
#[account(
init,
seeds = [TOKEN_ADMIN_SEED],
bump,
payer = authority,
space = ANCHOR_DISCRIMINATOR,
)]
/// CHECK: CPI signer for tokens
pub token_admin: UncheckedAccount<'info>,
```
### Using remaining_accounts for Token Transfers
When handling token transfers, the number of accounts passed depends on the specific token being handled. The `ccip_receive` handler should use `remaining_accounts` to access these token accounts.
Below is an example of a typical token transfer implementation:
```rust
// Example of token-related accounts in remaining_accounts
// For each token transfer:
// 1. token_mint: Account
// 2. source_token_account: Account (owned by program with token_admin authority)
// 3. token_admin: UncheckedAccount (the PDA with authority)
// 4. recipient_token_account: Account
// 5. token_program: Program
// Example token transfer logic
pub fn handle_token_transfer(ctx: Context, message: Any2SVMMessage) -> Result<()> {
// Check if we have sufficient remaining accounts for token handling
if ctx.remaining_accounts.len() < 5 {
return Err(ErrorCode::InvalidRemainingAccounts.into());
}
// Extract account references from the remaining_accounts
let token_mint_info = &ctx.remaining_accounts[0];
let source_token_account = &ctx.remaining_accounts[1];
let token_admin_info = &ctx.remaining_accounts[2];
let recipient_account_info = &ctx.remaining_accounts[3];
let token_program_info = &ctx.remaining_accounts[4];
// Verify the token_admin is the expected PDA
let (expected_token_admin, admin_bump) =
Pubkey::find_program_address(&[TOKEN_ADMIN_SEED], &crate::ID);
if token_admin_info.key() != expected_token_admin {
return Err(ErrorCode::InvalidTokenAdmin.into());
}
// Create and execute the token transfer instruction
let seeds = &[TOKEN_ADMIN_SEED, &[admin_bump]];
let signer_seeds = &[&seeds[..]];
// Transfer tokens using CPI with the PDA as signer
// ... token transfer code ...
Ok(())
}
```
---
# CCIP v1.6.0 SVM Base Token Pool Library Reference
Source: https://docs.chain.link/ccip/api-reference/svm/v1.6.0/base-token-pool
## Base Token Pool Library
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/base-token-pool)
The Base Token Pool library provides foundational components and shared functionality for CCIP token pool implementations on SVM-based blockchains. This library is not deployable as a standalone program but serves as a dependency for concrete token pool implementations like BurnMint and Lock-Release pools.
### Core Components
The Base Token Pool library provides several key modules:
#### Common Module (`common.rs`)
Contains shared data structures, constants, validation functions, and events used across all token pool implementations.
#### Rate Limiter Module (`rate_limiter.rs`)
Implements token bucket rate limiting functionality to control cross-chain transfer rates.
### Data Structures
#### Core Configuration Structures
##### `BaseConfig`
The fundamental configuration structure used by all token pools.
```rust
pub struct BaseConfig {
// Token configuration
pub token_program: Pubkey, // SPL Token or Token-2022 program ID
pub mint: Pubkey, // Token mint address
pub decimals: u8, // Token decimals
pub pool_signer: Pubkey, // Pool signer PDA
pub pool_token_account: Pubkey, // Pool's associated token account
// Ownership and administration
pub owner: Pubkey, // Current pool owner
pub proposed_owner: Pubkey, // Proposed new owner (for ownership transfer)
pub rate_limit_admin: Pubkey, // Rate limit administrator (currently unused - rate limits managed by pool owner)
// CCIP integration
pub router_onramp_authority: Pubkey, // Router's onramp authority PDA
pub router: Pubkey, // CCIP Router program address
pub rmn_remote: Pubkey, // RMN Remote program address
// Lock-Release specific (unused in BurnMint pools)
pub rebalancer: Pubkey, // Rebalancer address for liquidity management
pub can_accept_liquidity: bool, // Whether pool accepts liquidity operations
// Access control
pub list_enabled: bool, // Whether allowlist is enabled
pub allow_list: Vec, // Allowlisted addresses for pool operations
}
```
**Key Methods:**
- `init()`: Initializes BaseConfig with default values
- `transfer_ownership()`: Proposes ownership transfer
- `accept_ownership()`: Accepts proposed ownership transfer
- `set_router()`: Updates router address and derives new onramp authority
- `set_rmn()`: Updates RMN remote address
- `set_rebalancer()`: Sets rebalancer address (Lock-Release pools only)
- `set_can_accept_liquidity()`: Enables/disables liquidity operations
##### `BaseChain`
Configuration for a specific remote chain destination.
```rust
pub struct BaseChain {
pub remote: RemoteConfig, // Remote chain token and pool configuration
pub inbound_rate_limit: RateLimitTokenBucket, // Rate limiting for incoming transfers
pub outbound_rate_limit: RateLimitTokenBucket, // Rate limiting for outgoing transfers
}
```
**Key Methods:**
- `set()`: Updates remote chain configuration
- `append_remote_pool_addresses()`: Adds remote pool addresses
- `set_chain_rate_limit()`: Configures rate limits for the chain
##### `RemoteConfig`
Configuration for tokens and pools on remote chains.
```rust
pub struct RemoteConfig {
pub pool_addresses: Vec, // Remote pool addresses (supports multiple versions)
pub token_address: RemoteAddress, // Remote token address
pub decimals: u8, // Remote token decimals
}
```
##### `RemoteAddress`
Represents an address on a remote chain (supports various address formats).
```rust
pub struct RemoteAddress {
pub address: Vec, // Address bytes (max 64 bytes)
}
```
#### Cross-Chain Transfer Structures
##### `LockOrBurnInV1`
Input parameters for locking or burning tokens (source chain operation).
```rust
pub struct LockOrBurnInV1 {
pub receiver: Vec, // Recipient address on destination chain
pub remote_chain_selector: u64, // Destination chain ID
pub original_sender: Pubkey, // Original transaction sender
pub amount: u64, // Amount to lock/burn (local decimals)
pub local_token: Pubkey, // Local token mint address
pub msg_total_nonce: u64, // Message nonce from onramp
}
```
##### `LockOrBurnOutV1`
Output from lock or burn operations.
```rust
pub struct LockOrBurnOutV1 {
pub dest_token_address: RemoteAddress, // Remote token address
pub dest_pool_data: Vec, // ABI-encoded local token decimals
}
```
##### `ReleaseOrMintInV1`
Input parameters for releasing or minting tokens (destination chain operation).
```rust
pub struct ReleaseOrMintInV1 {
pub original_sender: RemoteAddress, // Original sender on source chain
pub remote_chain_selector: u64, // Source chain ID
pub receiver: Pubkey, // Token recipient on this chain
pub amount: [u8; 32], // Amount in source decimals (u256)
pub local_token: Pubkey, // Local token mint address
pub source_pool_address: RemoteAddress, // Source pool address (must be validated)
pub source_pool_data: Vec, // Data from source pool
pub offchain_token_data: Vec, // Untrusted offchain data
}
```
##### `ReleaseOrMintOutV1`
Output from release or mint operations.
```rust
pub struct ReleaseOrMintOutV1 {
pub destination_amount: u64, // Amount released/minted (local decimals)
}
```
### Rate Limiting
The library provides comprehensive rate limiting functionality using a token bucket algorithm.
#### `RateLimitTokenBucket`
Implements rate limiting for cross-chain transfers.
```rust
pub struct RateLimitTokenBucket {
pub tokens: u64, // Current tokens in bucket
pub last_updated: u64, // Last refill timestamp
cfg: RateLimitConfig, // Rate limit configuration
}
```
**Key Methods:**
- `consume()`: Attempts to consume tokens from bucket
- `set_token_bucket_config()`: Updates rate limit configuration
#### `RateLimitConfig`
Configuration for rate limiting behavior.
```rust
pub struct RateLimitConfig {
pub enabled: bool, // Whether rate limiting is enabled
pub capacity: u64, // Maximum tokens in bucket
pub rate: u64, // Tokens per second refill rate
}
```
**Validation Rules:**
- When enabled: `rate < capacity` and `rate != 0`
- When disabled: `rate == 0` and `capacity == 0`
### Constants and Seeds
The library defines critical constants used for PDA derivation across all pool implementations:
```rust
// PDA Seeds
pub const POOL_CHAINCONFIG_SEED: &[u8] = b"ccip_tokenpool_chainconfig";
pub const POOL_STATE_SEED: &[u8] = b"ccip_tokenpool_config";
pub const POOL_SIGNER_SEED: &[u8] = b"ccip_tokenpool_signer";
pub const CONFIG_SEED: &[u8] = b"config";
// Router Integration
pub const EXTERNAL_TOKEN_POOLS_SIGNER: &[u8] = b"external_token_pools_signer";
pub const ALLOWED_OFFRAMP: &[u8] = b"allowed_offramp";
// Other Constants
pub const ANCHOR_DISCRIMINATOR: usize = 8;
```
### Validation Functions
The library provides critical validation functions that ensure security and correctness of cross-chain operations.
#### `validate_lock_or_burn`
Validates parameters for locking or burning tokens on the source chain.
```rust
pub fn validate_lock_or_burn<'info>(
lock_or_burn_in: &LockOrBurnInV1,
config_mint: Pubkey,
outbound_rate_limit: &mut RateLimitTokenBucket,
allow_list_enabled: bool,
allow_list: &[Pubkey],
rmn_remote: AccountInfo<'info>,
rmn_remote_curses: AccountInfo<'info>,
rmn_remote_config: AccountInfo<'info>,
) -> Result<()>
```
**Validation Checks:**
1. **Token Validation**: Ensures `local_token` matches the pool's configured mint
2. **Allowlist Check**: Validates `original_sender` is allowlisted (if enabled)
3. **Curse Check**: Verifies destination chain is not cursed via RMN Remote CPI
4. **Rate Limiting**: Consumes tokens from outbound rate limit bucket
#### `validate_release_or_mint`
Validates parameters for releasing or minting tokens on the destination chain.
```rust
pub fn validate_release_or_mint<'info>(
release_or_mint_in: &ReleaseOrMintInV1,
parsed_amount: u64,
config_mint: Pubkey,
pool_addresses: &[RemoteAddress],
inbound_rate_limit: &mut RateLimitTokenBucket,
rmn_remote: AccountInfo<'info>,
rmn_remote_curses: AccountInfo<'info>,
rmn_remote_config: AccountInfo<'info>,
) -> Result<()>
```
**Validation Checks:**
1. **Token Validation**: Ensures `local_token` matches the pool's configured mint
2. **Source Pool Validation**: Verifies `source_pool_address` is in the configured pool addresses list
3. **Curse Check**: Verifies source chain is not cursed via RMN Remote CPI
4. **Rate Limiting**: Consumes tokens from inbound rate limit bucket
#### `verify_uncursed_cpi`
Performs CPI to RMN Remote to verify a chain is not cursed.
```rust
pub fn verify_uncursed_cpi<'info>(
rmn_remote: AccountInfo<'info>,
rmn_remote_config: AccountInfo<'info>,
rmn_remote_curses: AccountInfo<'info>,
chain_selector: u64,
) -> Result<()>
```
### Utility Functions
#### `to_svm_token_amount`
Converts cross-chain token amounts to local SVM amounts, handling decimal differences.
```rust
pub fn to_svm_token_amount(
incoming_amount_bytes: [u8; 32], // LE encoded u256 from source chain
incoming_decimal: u8, // Source token decimals
local_decimal: u8, // Local token decimals
) -> Result
```
**Conversion Logic:**
- **More incoming decimals**: Divides by `10^(incoming_decimal - local_decimal)`
- **Fewer incoming decimals**: Multiplies by `10^(local_decimal - incoming_decimal)`
- **Equal decimals**: No conversion needed
- **Overflow protection**: Validates result fits in u64
### Account Meta Utilities
#### `CcipAccountMeta`
Serializable version of Solana's `AccountMeta` for cross-program communication.
```rust
pub struct CcipAccountMeta {
pub pubkey: Pubkey,
pub is_signer: bool,
pub is_writable: bool,
}
```
#### `ToMeta` Trait
Provides convenient methods for creating `CcipAccountMeta` instances.
```rust
pub trait ToMeta {
fn readonly(self) -> CcipAccountMeta;
fn writable(self) -> CcipAccountMeta;
fn signer(self) -> CcipAccountMeta;
}
```
### Error Types
The library defines comprehensive error types used across all pool implementations:
```rust
pub enum CcipTokenPoolError {
// Authorization errors
InvalidInitPoolPermissions,
Unauthorized,
InvalidPoolCaller,
// Configuration errors
InvalidInputs,
InvalidVersion,
InvalidRMNRemoteAddress,
// Token operation errors
InvalidSender,
InvalidSourcePoolAddress,
InvalidToken,
InvalidTokenAmountConversion,
// Allowlist errors
AllowlistKeyAlreadyExisted,
AllowlistKeyDidNotExist,
// Remote pool errors
RemotePoolAddressAlreadyExisted,
NonemptyPoolAddressesInit,
// Rate limiting errors (RL prefix)
RLBucketOverfilled,
RLMaxCapacityExceeded,
RLRateLimitReached,
RLInvalidRateLimitRate,
RLDisabledNonZeroRateLimit,
// Lock-Release specific errors
LiquidityNotAccepted,
TransferZeroTokensNotAllowed,
// Other
InvalidDerivationStage,
}
```
### Events
The library defines events that are emitted by pool implementations. See the [Events API Reference](/ccip/api-reference/svm/v1.6.0/events) for detailed documentation of all events including:
- Configuration events (`GlobalConfigUpdated`, `RemoteChainConfigured`, etc.)
- Token operation events (`Burned`, `Minted`, `Locked`, `Released`)
- Administrative events (`OwnershipTransferred`, `RouterUpdated`, etc.)
- Rate limiting events (`TokensConsumed`, `ConfigChanged`)
### Usage by Pool Implementations
The Base Token Pool library is used by concrete pool implementations as follows:
#### Import Pattern
```rust
use base_token_pool::{
common::*,
rate_limiter::*,
};
```
#### Structure Embedding
Pool implementations embed base structures:
```rust
#[account]
#[derive(InitSpace)]
pub struct State {
pub version: u8,
pub config: BaseConfig, // Embedded base configuration
}
#[account]
#[derive(InitSpace)]
pub struct ChainConfig {
pub base: BaseChain, // Embedded base chain configuration
}
```
#### Validation Integration
Pool operations call base validation functions:
```rust
// In lock_or_burn_tokens instruction
validate_lock_or_burn(
&lock_or_burn,
ctx.accounts.state.config.mint,
&mut ctx.accounts.chain_config.base.outbound_rate_limit,
ctx.accounts.state.config.list_enabled,
&ctx.accounts.state.config.allow_list,
ctx.accounts.rmn_remote.to_account_info(),
ctx.accounts.rmn_remote_curses.to_account_info(),
ctx.accounts.rmn_remote_config.to_account_info(),
)?;
```
This shared foundation ensures consistency, security, and maintainability across all CCIP token pool implementations on SVM-based blockchains.
---
# CCIP v1.6.0 SVM BurnMint Token Pool API Reference
Source: https://docs.chain.link/ccip/api-reference/svm/v1.6.0/burn-mint-token-pool
## BurnMint Token Pool
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/burnmint-token-pool)
Below is a complete API reference for the CCIP BurnMint Token Pool program instructions. This pool implementation burns tokens on the source chain and mints them on the destination chain.
### Global Configuration
These instructions manage the global configuration of the BurnMint Token Pool program.
#### `init_global_config`
Initializes the global configuration for the BurnMint Token Pool program. This must be called once during initial setup.
```rust
fn init_global_config(
ctx: Context,
router_address: Pubkey,
rmn_address: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ---------------- | -------- | --------------------------------------- |
| `router_address` | `Pubkey` | The default CCIP Router program address |
| `rmn_address` | `Pubkey` | The default RMN Remote program address |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------- | ----------------------------------- | --------- | -------------------------------------------------------------------------------------------- |
| `config` | `Account` | Yes | Global pool config PDA to initialize. **Derivation**: `["config"]` under this program. |
| `authority` | `Signer<'info>` | Yes | Must be the program upgrade authority. |
| `system_program` | `Program<'info, System>` | No | Standard System Program. |
| `program` | `Program<'info, BurnmintTokenPool>` | No | The BurnMint Token Pool program. |
| `program_data` | `Account<'info, ProgramData>` | No | Program data account for upgrade authority verification. |
##### Authorization
- **Caller**: Must be the program upgrade authority
#### `update_self_served_allowed`
Updates whether self-service pool initialization is allowed.
```rust
fn update_self_served_allowed(
ctx: Context,
self_served_allowed: bool,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| --------------------- | ------ | ------------------------------------------------------- |
| `self_served_allowed` | `bool` | Whether mint authorities can initialize their own pools |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| -------------- | ----------------------------------- | --------- | ------------------------------------------------------------------------------ |
| `config` | `Account` | Yes | Global pool config PDA. **Derivation**: `["config"]` under this program. |
| `authority` | `Signer<'info>` | No | Must be the program upgrade authority. |
| `program` | `Program<'info, BurnmintTokenPool>` | No | The BurnMint Token Pool program. |
| `program_data` | `Account<'info, ProgramData>` | No | Program data account for upgrade authority verification. |
##### Authorization
- **Caller**: Must be the program upgrade authority
#### `update_default_router`
Updates the default router address used for new token pools.
```rust
fn update_default_router(
ctx: Context,
router_address: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ---------------- | -------- | ------------------------------------------- |
| `router_address` | `Pubkey` | The new default CCIP Router program address |
##### Context (Accounts)
Same as `update_self_served_allowed`.
##### Authorization
- **Caller**: Must be the program upgrade authority
#### `update_default_rmn`
Updates the default RMN Remote address used for new token pools.
```rust
fn update_default_rmn(
ctx: Context,
rmn_address: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ------------- | -------- | ------------------------------------------ |
| `rmn_address` | `Pubkey` | The new default RMN Remote program address |
##### Context (Accounts)
Same as `update_self_served_allowed`.
##### Authorization
- **Caller**: Must be the program upgrade authority
### Pool Initialization & Management
These instructions handle individual token pool lifecycle management.
#### `initialize`
Initializes a new token pool for a specific SPL token mint.
```rust
fn initialize(ctx: Context) -> Result<()>;
```
##### Parameters
This instruction takes no additional parameters.
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------- | ----------------------------------- | --------- | --------------------------------------------------------------------------------------------------------- |
| `state` | `Account` | Yes | Pool state PDA to initialize. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint to create a pool for. |
| `authority` | `Signer<'info>` | Yes | Pool initializer (see Authorization below). |
| `system_program` | `Program<'info, System>` | No | Standard System Program. |
| `program` | `Program<'info, BurnmintTokenPool>` | No | The BurnMint Token Pool program. |
| `program_data` | `Account<'info, ProgramData>` | No | Program data account for upgrade authority verification. |
| `config` | `Account` | No | Global pool config PDA. **Derivation**: `["config"]` under this program. |
##### Authorization
- **Program Upgrade Authority**: Can always initialize pools
- **Mint Authority**: Can initialize pools when `self_served_allowed` is true
#### `transfer_mint_authority_to_multisig`
Transfers the mint authority of the token to a multisig account that includes the pool signer as a required signer.
```rust
fn transfer_mint_authority_to_multisig(
ctx: Context,
) -> Result<()>;
```
##### Parameters
This instruction takes no additional parameters.
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ----------------------------- | ----------------------------------- | --------- | -------------------------------------------------------------------------------------------- |
| `state` | `Account` | Yes | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `mint` | `InterfaceAccount<'info, Mint>` | Yes | The SPL token mint. |
| `token_program` | `Interface<'info, TokenInterface>` | No | Token program (SPL Token or Token-2022). |
| `pool_signer` | `UncheckedAccount<'info>` | No | Pool signer PDA. **Derivation**: `["ccip_tokenpool_signer", mint]` under this program. |
| `authority` | `Signer<'info>` | No | Must be the program upgrade authority. |
| `new_multisig_mint_authority` | `UncheckedAccount<'info>` | No | The new multisig account that will become the mint authority. |
| `program` | `Program<'info, BurnmintTokenPool>` | No | The BurnMint Token Pool program. |
| `program_data` | `Account<'info, ProgramData>` | No | Program data account for upgrade authority verification. |
##### Authorization
- **Caller**: Must be the program upgrade authority
##### Multisig Requirements
Must use SPL Token Multisig with Pool Signer PDA configured as a required signer. For detailed configuration requirements, validation rules, and examples, see [Mint Authority Management](/ccip/concepts/cross-chain-token/svm/token-pools#mint-authority-management).
#### `transfer_ownership`
Initiates transfer of pool ownership to a new owner.
```rust
fn transfer_ownership(
ctx: Context,
proposed_owner: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ---------------- | -------- | ---------------------------------------- |
| `proposed_owner` | `Pubkey` | The public key of the proposed new owner |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ----------- | ------------------------------- | --------- | ------------------------------------------------------------------------------------------- |
| `state` | `Account` | Yes | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint. |
| `authority` | `Signer<'info>` | No | Must be the current pool owner. |
##### Authorization
- **Caller**: Must be the current pool owner
#### `accept_ownership`
Accepts pool ownership by the proposed owner.
```rust
fn accept_ownership(ctx: Context) -> Result<()>;
```
##### Parameters
This instruction takes no additional parameters.
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ----------- | ------------------------------- | --------- | ------------------------------------------------------------------------------------------- |
| `state` | `Account` | Yes | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint. |
| `authority` | `Signer<'info>` | No | Must be the proposed owner. |
##### Authorization
- **Caller**: Must be the proposed owner stored in the pool state
#### `set_router`
Updates the router address for an existing pool.
```rust
fn set_router(
ctx: Context,
new_router: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ------------ | -------- | ----------------------------------- |
| `new_router` | `Pubkey` | The new CCIP Router program address |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| -------------- | ----------------------------------- | --------- | ------------------------------------------------------------------------------------------- |
| `state` | `Account` | No | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint. |
| `authority` | `Signer<'info>` | Yes | Must be program upgrade authority and pool owner. |
| `program` | `Program<'info, BurnmintTokenPool>` | No | The BurnMint Token Pool program. |
| `program_data` | `Account<'info, ProgramData>` | No | Program data account for upgrade authority verification. |
##### Authorization
- **Caller**: Must be the program upgrade authority AND the pool owner
#### `set_rmn`
Updates the RMN Remote address for an existing pool.
```rust
fn set_rmn(
ctx: Context,
rmn_address: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ------------- | -------- | ---------------------------------- |
| `rmn_address` | `Pubkey` | The new RMN Remote program address |
##### Context (Accounts)
Same as `set_router`.
##### Authorization
- **Caller**: Must be the program upgrade authority AND the pool owner
### Chain Configuration
These instructions manage per-destination-chain configuration for token pools.
#### `init_chain_remote_config`
Initializes configuration for a specific remote destination chain.
```rust
fn init_chain_remote_config(
ctx: Context,
remote_chain_selector: u64,
mint: Pubkey,
cfg: RemoteConfig,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ----------------------- | -------------- | ----------------------------------------------------------- |
| `remote_chain_selector` | `u64` | The destination chain selector |
| `mint` | `Pubkey` | The SPL token mint |
| `cfg` | `RemoteConfig` | Remote chain configuration (must have empty pool addresses) |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------- | ------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `state` | `Account` | No | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `chain_config` | `Account` | Yes | Chain config PDA to initialize. **Derivation**: `["ccip_tokenpool_chainconfig", remote_chain_selector, mint]` under this program. |
| `authority` | `Signer<'info>` | Yes | Must be the pool owner. |
| `system_program` | `Program<'info, System>` | No | Standard System Program. |
##### Authorization
- **Caller**: Must be the pool owner
#### `edit_chain_remote_config`
Updates configuration for an existing remote destination chain.
```rust
fn edit_chain_remote_config(
ctx: Context,
remote_chain_selector: u64,
mint: Pubkey,
cfg: RemoteConfig,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ----------------------- | -------------- | ---------------------------------- |
| `remote_chain_selector` | `u64` | The destination chain selector |
| `mint` | `Pubkey` | The SPL token mint |
| `cfg` | `RemoteConfig` | Updated remote chain configuration |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------- | ------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `state` | `Account` | No | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `chain_config` | `Account` | Yes | Existing chain config PDA. **Derivation**: `["ccip_tokenpool_chainconfig", remote_chain_selector, mint]` under this program. |
| `authority` | `Signer<'info>` | Yes | Must be the pool owner. |
| `system_program` | `Program<'info, System>` | No | Standard System Program. |
##### Authorization
- **Caller**: Must be the pool owner
#### `append_remote_pool_addresses`
Adds additional remote pool addresses to an existing chain configuration.
```rust
fn append_remote_pool_addresses(
ctx: Context,
remote_chain_selector: u64,
mint: Pubkey,
addresses: Vec,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ----------------------- | -------------------- | ------------------------------ |
| `remote_chain_selector` | `u64` | The destination chain selector |
| `mint` | `Pubkey` | The SPL token mint |
| `addresses` | `Vec` | Remote pool addresses to add |
##### Context (Accounts)
Same as `edit_chain_remote_config`.
##### Authorization
- **Caller**: Must be the pool owner
#### `set_chain_rate_limit`
Configures rate limits for inbound and outbound transfers to/from a specific chain.
```rust
fn set_chain_rate_limit(
ctx: Context,
remote_chain_selector: u64,
mint: Pubkey,
inbound: RateLimitConfig,
outbound: RateLimitConfig,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ----------------------- | ----------------- | ----------------------------------------------- |
| `remote_chain_selector` | `u64` | The destination chain selector |
| `mint` | `Pubkey` | The SPL token mint |
| `inbound` | `RateLimitConfig` | Rate limit configuration for inbound transfers |
| `outbound` | `RateLimitConfig` | Rate limit configuration for outbound transfers |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| -------------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------- |
| `state` | `Account` | No | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `chain_config` | `Account` | Yes | Chain config PDA. **Derivation**: `["ccip_tokenpool_chainconfig", remote_chain_selector, mint]` under this program. |
| `authority` | `Signer<'info>` | Yes | Must be the pool owner. |
##### Authorization
- **Caller**: Must be the pool owner
#### `delete_chain_config`
Removes configuration for a specific remote destination chain.
```rust
fn delete_chain_config(
ctx: Context,
remote_chain_selector: u64,
mint: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ----------------------- | -------- | ------------------------------ |
| `remote_chain_selector` | `u64` | The destination chain selector |
| `mint` | `Pubkey` | The SPL token mint |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| -------------- | ---------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `state` | `Account` | No | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `chain_config` | `Account` | Yes | Chain config PDA to delete. **Derivation**: `["ccip_tokenpool_chainconfig", remote_chain_selector, mint]` under this program. |
| `authority` | `Signer<'info>` | Yes | Must be the pool owner. |
##### Authorization
- **Caller**: Must be the pool owner
### Access Control
These instructions manage allowlists for token transfers.
#### `configure_allow_list`
Configures the allowlist for the token pool, adding addresses and enabling/disabling the allowlist.
```rust
fn configure_allow_list(
ctx: Context,
add: Vec,
enabled: bool,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| --------- | ------------- | --------------------------------- |
| `add` | `Vec` | Addresses to add to the allowlist |
| `enabled` | `bool` | Whether to enable the allowlist |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ---------------- | ------------------------------- | --------- | ------------------------------------------------------------------------------------------- |
| `state` | `Account` | Yes | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `mint` | `InterfaceAccount<'info, Mint>` | No | The SPL token mint. |
| `authority` | `Signer<'info>` | Yes | Must be the pool owner. |
| `system_program` | `Program<'info, System>` | No | Standard System Program. |
##### Authorization
- **Caller**: Must be the pool owner
#### `remove_from_allow_list`
Removes addresses from the allowlist.
```rust
fn remove_from_allow_list(
ctx: Context,
remove: Vec,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| -------- | ------------- | -------------------------------------- |
| `remove` | `Vec` | Addresses to remove from the allowlist |
##### Context (Accounts)
Same as `configure_allow_list`.
##### Authorization
- **Caller**: Must be the pool owner
### Cross-Chain Operations
These instructions handle the core cross-chain token transfer operations.
#### `lock_or_burn_tokens`
Burns tokens on the source chain as part of a cross-chain transfer (OnRamp operation).
```rust
fn lock_or_burn_tokens(
ctx: Context,
lock_or_burn: LockOrBurnInV1,
) -> Result;
```
##### Parameters
| Name | Type | Description |
| -------------- | ---------------- | --------------------------------- |
| `lock_or_burn` | `LockOrBurnInV1` | Cross-chain transfer request data |
##### Return Value
| Type | Description |
| ----------------- | ------------------------------------------------ |
| `LockOrBurnOutV1` | Contains destination token address and pool data |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| -------------------- | --------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------- |
| `authority` | `Signer<'info>` | No | Must be the Router's onramp authority. |
| `state` | `Account` | No | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `token_program` | `Interface<'info, TokenInterface>` | No | Token program (SPL Token or Token-2022). |
| `mint` | `InterfaceAccount<'info, Mint>` | Yes | The SPL token mint. |
| `pool_signer` | `UncheckedAccount<'info>` | No | Pool signer PDA. **Derivation**: `["ccip_tokenpool_signer", mint]` under this program. |
| `pool_token_account` | `InterfaceAccount<'info, TokenAccount>` | Yes | Pool's token account (ATA for pool_signer). |
| `rmn_remote` | `UncheckedAccount<'info>` | No | RMN Remote program address. |
| `rmn_remote_curses` | `UncheckedAccount<'info>` | No | RMN curses PDA. |
| `rmn_remote_config` | `UncheckedAccount<'info>` | No | RMN config PDA. |
| `chain_config` | `Account` | Yes | Chain config PDA. **Derivation**: `["ccip_tokenpool_chainconfig", remote_chain_selector, mint]` under this program. |
##### Authorization
- **Caller**: Must be the Router's designated onramp authority
#### `release_or_mint_tokens`
Mints tokens on the destination chain as part of a cross-chain transfer (OffRamp operation).
```rust
fn release_or_mint_tokens(
ctx: Context,
release_or_mint: ReleaseOrMintInV1,
) -> Result;
```
##### Parameters
| Name | Type | Description |
| ----------------- | ------------------- | ------------------------------------ |
| `release_or_mint` | `ReleaseOrMintInV1` | Cross-chain transfer completion data |
##### Return Value
| Type | Description |
| -------------------- | -------------------------------------- |
| `ReleaseOrMintOutV1` | Contains the destination amount minted |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ------------------------ | --------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------- |
| `authority` | `Signer<'info>` | No | Offramp authority derived from external token pools signer. |
| `offramp_program` | `UncheckedAccount<'info>` | No | The offramp program for PDA derivation. |
| `allowed_offramp` | `UncheckedAccount<'info>` | No | Router's allowed offramp PDA. |
| `state` | `Account` | No | Pool state PDA. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
| `token_program` | `Interface<'info, TokenInterface>` | No | Token program (SPL Token or Token-2022). |
| `mint` | `InterfaceAccount<'info, Mint>` | Yes | The SPL token mint. |
| `pool_signer` | `UncheckedAccount<'info>` | No | Pool signer PDA. **Derivation**: `["ccip_tokenpool_signer", mint]` under this program. |
| `pool_token_account` | `InterfaceAccount<'info, TokenAccount>` | Yes | Pool's token account (ATA for pool_signer). |
| `chain_config` | `Account` | Yes | Chain config PDA. **Derivation**: `["ccip_tokenpool_chainconfig", remote_chain_selector, mint]` under this program. |
| `rmn_remote` | `UncheckedAccount<'info>` | No | RMN Remote program address. |
| `rmn_remote_curses` | `UncheckedAccount<'info>` | No | RMN curses PDA. |
| `rmn_remote_config` | `UncheckedAccount<'info>` | No | RMN config PDA. |
| `receiver_token_account` | `InterfaceAccount<'info, TokenAccount>` | Yes | Receiver's token account (ATA for the recipient). |
##### Authorization
- **Caller**: Must be an authorized offramp with valid Router approval
### Utility Instructions
#### `type_version`
Returns the program type and version information.
```rust
fn type_version(ctx: Context) -> Result;
```
##### Parameters
This instruction takes no additional parameters.
##### Return Value
| Type | Description |
| -------- | ------------------------------------ |
| `String` | Program type and version information |
#### `initialize_state_version`
Permissionless instruction to initialize the state version for pools that were created before versioning was implemented.
```rust
fn initialize_state_version(
ctx: Context,
mint: Pubkey,
) -> Result<()>;
```
##### Parameters
| Name | Type | Description |
| ------ | -------- | ------------------ |
| `mint` | `Pubkey` | The SPL token mint |
##### Context (Accounts)
| Field | Type | Writable? | Description |
| ------- | ---------------- | --------- | ---------------------------------------------------------------------------------------------------------------------- |
| `state` | `Account` | Yes | Pool state PDA with uninitialized version. **Derivation**: `["ccip_tokenpool_config", mint]` under this program. |
##### Authorization
- **Permissionless**: Anyone can call this instruction
---
# CCIP v1.6.0 SVM Lock-Release Token Pool API Reference
Source: https://docs.chain.link/ccip/api-reference/svm/v1.6.0/lock-release-token-pool
## Lock-Release Token Pool
[Git Source](https://github.com/smartcontractkit/chainlink-ccip/tree/v1.6.0-solana/chains/solana/contracts/programs/lockrelease-token-pool)
The Lock-Release Token Pool program implements a token pool that uses a lock/release strategy for cross-chain transfers. When tokens are sent cross-chain, they are locked in the pool on the source chain and released from the pool on the destination chain.
**Program ID**: `8eqh8wppT9c5rw4ERqNCffvU6cNFJWff9WmkcYtmGiqC`
### Instructions
The Lock-Release Token Pool program provides 24 instructions organized into 6 categories:
#### Global Configuration
These instructions manage program-wide settings that apply to all pools deployed from this program.
##### `init_global_config`
Initializes the global configuration for the Lock-Release Token Pool program. Only callable by the program's upgrade authority.
```rust
pub fn init_global_config(
ctx: Context,
router_address: Pubkey,
rmn_address: Pubkey,
) -> Result<()>
```
**Parameters:**
- `router_address`: Default CCIP Router program address for new pools
- `rmn_address`: Default RMN Remote program address for security validation
**Context Accounts:**
- `config`: Global Config PDA (`seeds: ["config"]`) - initialized by this instruction
- `authority`: Program upgrade authority (signer, pays for account creation)
- `system_program`: Solana System Program
- `program`: Lock-Release Token Pool program account
- `program_data`: Program data account for upgrade authority validation
**Authorization:** Program upgrade authority only
**PDA Derivation:**
```
Global Config PDA = find_program_address(["config"], program_id)
```
##### `update_self_served_allowed`
Updates whether self-service pool initialization is allowed.
```rust
pub fn update_self_served_allowed(
ctx: Context,
self_served_allowed: bool,
) -> Result<()>
```
**Parameters:**
- `self_served_allowed`: Whether to allow users to initialize pools for tokens they control
**Context Accounts:**
- `config`: Global Config PDA (mutable)
- `authority`: Program upgrade authority (signer)
- `program`: Lock-Release Token Pool program account
- `program_data`: Program data account for upgrade authority validation
**Authorization:** Program upgrade authority only
##### `update_default_router`
Updates the default router address for new pools.
```rust
pub fn update_default_router(
ctx: Context,
router_address: Pubkey,
) -> Result<()>
```
**Parameters:**
- `router_address`: New default router program address
**Context Accounts:** Same as `update_self_served_allowed`
**Authorization:** Program upgrade authority only
##### `update_default_rmn`
Updates the default RMN remote address for new pools.
```rust
pub fn update_default_rmn(
ctx: Context,
rmn_address: Pubkey,
) -> Result<()>
```
**Parameters:**
- `rmn_address`: New default RMN remote program address
**Context Accounts:** Same as `update_self_served_allowed`
**Authorization:** Program upgrade authority only
#### Pool Initialization & Management
These instructions handle individual pool lifecycle management and configuration.
##### `initialize`
Initializes a new Lock-Release Token Pool for a specific token.
```rust
pub fn initialize(ctx: Context) -> Result<()>
```
**Context Accounts:**
- `state`: Pool State PDA (`seeds: ["ccip_tokenpool_config", mint]`) - initialized by this instruction
- `mint`: Token mint account that this pool will manage
- `authority`: Pool initializer (signer, pays for account creation)
- `system_program`: Solana System Program
- `program`: Lock-Release Token Pool program account
- `program_data`: Program data account for authorization validation
- `config`: Global Config PDA for default settings
**Authorization:**
- Program upgrade authority, OR
- Token mint authority (when `self_served_allowed` is true in global config)
**PDA Derivation:**
```
Pool State PDA = find_program_address(["ccip_tokenpool_config", mint], program_id)
Pool Signer PDA = find_program_address(["ccip_tokenpool_signer", mint], program_id)
```
##### `type_version`
Returns the program type and version information.
```rust
pub fn type_version(_ctx: Context) -> Result
```
**Returns:** Program type and version string (e.g., "LockReleaseTokenPool 1.6.0")
**Context Accounts:**
- `clock`: Clock sysvar (unused but required by Anchor)
**Authorization:** None (permissionless)
##### `transfer_ownership`
Proposes a new owner for the pool. The proposed owner must call `accept_ownership` to complete the transfer.
```rust
pub fn transfer_ownership(
ctx: Context,
proposed_owner: Pubkey,
) -> Result<()>
```
**Parameters:**
- `proposed_owner`: Address of the proposed new pool owner
**Context Accounts:**
- `state`: Pool State PDA (mutable)
- `mint`: Token mint account
- `authority`: Current pool owner (signer)
**Authorization:** Current pool owner only
##### `accept_ownership`
Accepts ownership of a pool that was proposed via `transfer_ownership`.
```rust
pub fn accept_ownership(ctx: Context) -> Result<()>
```
**Context Accounts:**
- `state`: Pool State PDA (mutable)
- `mint`: Token mint account
- `authority`: Proposed owner (signer)
**Authorization:** Proposed owner only
##### `set_router`
Updates the router address for this pool.
```rust
pub fn set_router(
ctx: Context,
new_router: Pubkey,
) -> Result<()>
```
**Parameters:**
- `new_router`: New router program address
**Context Accounts:**
- `state`: Pool State PDA (mutable)
- `mint`: Token mint account
- `authority`: Program upgrade authority (signer)
- `program`: Lock-Release Token Pool program account
- `program_data`: Program data account for authorization validation
**Authorization:** Program upgrade authority only (and pool must be owned by upgrade authority)
##### `set_rmn`
Updates the RMN remote address for this pool.
```rust
pub fn set_rmn(
ctx: Context,
rmn_address: Pubkey,
) -> Result<()>
```
**Parameters:**
- `rmn_address`: New RMN remote program address
**Context Accounts:** Same as `set_router`
**Authorization:** Program upgrade authority only (and pool must be owned by upgrade authority)
##### `initialize_state_version`
Sets the pool's version field if it was not set during initialization. Permissionless utility instruction.
```rust
pub fn initialize_state_version(
ctx: Context,
_mint: Pubkey,
) -> Result<()>
```
**Parameters:**
- `_mint`: Token mint address (used for PDA derivation)
**Context Accounts:**
- `state`: Pool State PDA (mutable) - must have uninitialized version
**Authorization:** None (permissionless)
#### Chain Configuration
These instructions manage per-destination chain settings including rate limits and remote pool addresses.
##### `init_chain_remote_config`
Initializes configuration for a remote destination chain.
```rust
pub fn init_chain_remote_config(
ctx: Context,
remote_chain_selector: u64,
mint: Pubkey,
cfg: RemoteConfig,
) -> Result<()>
```
**Parameters:**
- `remote_chain_selector`: Destination chain identifier
- `mint`: Token mint address
- `cfg`: Remote chain configuration (must have empty `pool_addresses`)
**Context Accounts:**
- `state`: Pool State PDA
- `chain_config`: Chain Config PDA (`seeds: ["ccip_tokenpool_chainconfig", chain_selector, mint]`) - initialized by this instruction
- `authority`: Pool owner (signer, pays for account creation)
- `system_program`: Solana System Program
**Authorization:** Pool owner only
**PDA Derivation:**
```
Chain Config PDA = find_program_address(
["ccip_tokenpool_chainconfig", remote_chain_selector.to_le_bytes(), mint],
program_id
)
```
##### `edit_chain_remote_config`
Updates the remote configuration for an existing chain.
```rust
pub fn edit_chain_remote_config(
ctx: Context,
remote_chain_selector: u64,
mint: Pubkey,
cfg: RemoteConfig,
) -> Result<()>
```
**Parameters:**
- `remote_chain_selector`: Destination chain identifier
- `mint`: Token mint address
- `cfg`: Updated remote chain configuration
**Context Accounts:**
- `state`: Pool State PDA
- `chain_config`: Chain Config PDA (mutable, resized as needed)
- `authority`: Pool owner (signer, pays for resize)
- `system_program`: Solana System Program
**Authorization:** Pool owner only
##### `append_remote_pool_addresses`
Adds remote pool addresses to an existing chain configuration.
```rust
pub fn append_remote_pool_addresses(
ctx: Context,
remote_chain_selector: u64,
_mint: Pubkey,
addresses: Vec,
) -> Result<()>
```
**Parameters:**
- `remote_chain_selector`: Destination chain identifier
- `_mint`: Token mint address (used for PDA derivation)
- `addresses`: List of remote pool addresses to add
**Context Accounts:**
- `state`: Pool State PDA
- `chain_config`: Chain Config PDA (mutable, resized as needed)
- `authority`: Pool owner (signer, pays for resize)
- `system_program`: Solana System Program
**Authorization:** Pool owner only
##### `set_chain_rate_limit`
Configures rate limits for inbound and outbound transfers for a specific chain.
```rust
pub fn set_chain_rate_limit(
ctx: Context,
remote_chain_selector: u64,
mint: Pubkey,
inbound: RateLimitConfig,
outbound: RateLimitConfig,
) -> Result<()>
```
**Parameters:**
- `remote_chain_selector`: Destination chain identifier
- `mint`: Token mint address
- `inbound`: Rate limiting configuration for incoming transfers
- `outbound`: Rate limiting configuration for outgoing transfers
**Context Accounts:**
- `state`: Pool State PDA
- `chain_config`: Chain Config PDA (mutable)
- `authority`: Pool owner (signer)
**Authorization:** Pool owner only
##### `delete_chain_config`
Deletes the configuration for a remote chain and closes the account.
```rust
pub fn delete_chain_config(
_ctx: Context,
remote_chain_selector: u64,
mint: Pubkey,
) -> Result<()>
```
**Parameters:**
- `remote_chain_selector`: Destination chain identifier
- `mint`: Token mint address
**Context Accounts:**
- `state`: Pool State PDA
- `chain_config`: Chain Config PDA (closed, rent returned to authority)
- `authority`: Pool owner (signer, receives rent)
**Authorization:** Pool owner only
#### Access Control
These instructions manage the allowlist for senders authorized to use the pool.
##### `configure_allow_list`
Adds addresses to the allowlist and enables/disables allowlist enforcement.
```rust
pub fn configure_allow_list(
ctx: Context,
add: Vec,
enabled: bool,
) -> Result<()>
```
**Parameters:**
- `add`: List of addresses to add to allowlist
- `enabled`: Whether to enable allowlist enforcement
**Context Accounts:**
- `state`: Pool State PDA (mutable, resized as needed)
- `mint`: Token mint account
- `authority`: Pool owner (signer, pays for resize)
- `system_program`: Solana System Program
**Authorization:** Pool owner only
##### `remove_from_allow_list`
Removes addresses from the allowlist.
```rust
pub fn remove_from_allow_list(
ctx: Context,
remove: Vec,
) -> Result<()>
```
**Parameters:**
- `remove`: List of addresses to remove from allowlist
**Context Accounts:**
- `state`: Pool State PDA (mutable, resized as needed)
- `mint`: Token mint account
- `authority`: Pool owner (signer, pays for resize)
- `system_program`: Solana System Program
**Authorization:** Pool owner only
#### Cross-Chain Operations
These instructions handle the core lock/release operations for cross-chain transfers.
##### `release_or_mint_tokens`
Releases tokens from the pool to a receiver (destination chain operation).
```rust
pub fn release_or_mint_tokens(
ctx: Context,
release_or_mint: ReleaseOrMintInV1,
) -> Result
```
**Parameters:**
- `release_or_mint`: Release parameters including receiver, amount, and source chain info
**Context Accounts:**
- `authority`: OffRamp authority PDA (signer) - derived from OffRamp program
- `offramp_program`: OffRamp program account
- `allowed_offramp`: Router's allowed OffRamp PDA for validation
- `state`: Pool State PDA
- `token_program`: SPL Token or Token-2022 program
- `mint`: Token mint account (mutable)
- `pool_signer`: Pool Signer PDA
- `pool_token_account`: Pool's ATA holding locked tokens (mutable)
- `chain_config`: Chain Config PDA for source chain (mutable for rate limiting)
- `rmn_remote`: RMN Remote program
- `rmn_remote_curses`: RMN Remote curses PDA
- `rmn_remote_config`: RMN Remote config PDA
- `receiver_token_account`: Receiver's ATA for tokens (mutable)
**Authorization:** Valid OffRamp program (validated via Router's allowed OffRamp PDA)
**Returns:**
```rust
ReleaseOrMintOutV1 {
destination_amount: u64, // Amount released in destination token decimals
}
```
##### `lock_or_burn_tokens`
Locks tokens in the pool (source chain operation).
```rust
pub fn lock_or_burn_tokens(
ctx: Context