# Table of Contents
- [Chainlink CCIP | Chainlink Documentation](#chainlink-ccip-chainlink-documentation)
- [Getting Started | Chainlink Documentation](#getting-started-chainlink-documentation)
- [Chainlink Documentation | Chainlink Documentation](#chainlink-documentation-chainlink-documentation)
- [CCIP Service Limits | Chainlink Documentation](#ccip-service-limits-chainlink-documentation)
- [Chainlink CCIP | Chainlink Documentation](#chainlink-ccip-chainlink-documentation)
- [Chainlink CCIP | Chainlink Documentation](#chainlink-ccip-chainlink-documentation)
- [CCIP Billing | Chainlink Documentation](#ccip-billing-chainlink-documentation)
- [Chainlink CCIP Service Responsibility | Chainlink Documentation](#chainlink-ccip-service-responsibility-chainlink-documentation)
- [CCIP Execution Latency | Chainlink Documentation](#ccip-execution-latency-chainlink-documentation)
- [Chainlink CCIP Release Notes | Chainlink Documentation](#chainlink-ccip-release-notes-chainlink-documentation)
- [Using the CCIP JavaScript SDK | Chainlink Documentation](#using-the-ccip-javascript-sdk-chainlink-documentation)
- [Transfer Tokens with Data | Chainlink Documentation](#transfer-tokens-with-data-chainlink-documentation)
- [Chainlink Data Feeds | Chainlink Documentation](#chainlink-data-feeds-chainlink-documentation)
- [Enable your tokens in CCIP (Burn & Mint): Register from an EOA using Hardhat | Chainlink Documentation](#enable-your-tokens-in-ccip-burn-mint-register-from-an-eoa-using-hardhat-chainlink-documentation)
- [Transfer Tokens | Chainlink Documentation](#transfer-tokens-chainlink-documentation)
- [Cross-Chain Token (CCT) Tutorials | Chainlink Documentation](#cross-chain-token-cct-tutorials-chainlink-documentation)
- [Deploy & Register Cross-Chain Tokens with Remix IDE | Chainlink Documentation](#deploy-register-cross-chain-tokens-with-remix-ide-chainlink-documentation)
- [Using the Token Manager | Chainlink Documentation](#using-the-token-manager-chainlink-documentation)
- [Enable your tokens in CCIP (Lock & Mint): Register from an EOA using Hardhat | Chainlink Documentation](#enable-your-tokens-in-ccip-lock-mint-register-from-an-eoa-using-hardhat-chainlink-documentation)
- [Transfer Tokens with Data - Defensive Example | Chainlink Documentation](#transfer-tokens-with-data-defensive-example-chainlink-documentation)
- [Set Token Pool rate limits using Hardhat | Chainlink Documentation](#set-token-pool-rate-limits-using-hardhat-chainlink-documentation)
- [Chainlink Data Streams | Chainlink Documentation](#chainlink-data-streams-chainlink-documentation)
---
# Chainlink CCIP | Chainlink Documentation
On this page
Chainlink CCIP
==============
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?](#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#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 [concepts](/ccip/concepts)
, [architecture](/ccip/architecture)
, and [best practices](/ccip/best-practices)
. 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-core-capabilities)
----------------------------------------------------------------------
Chainlink CCIP supports three main capabilities:
* **Arbitrary Messaging**: is 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:** You can transfer tokens to a smart contract or directly to an [Externally Owned Account(EOA)](https://ethereum.org/en/developers/docs/accounts/#types-of-account)
on a different blockchain.
* **Programmable Token Transfer**: is 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.
It is recommended that token developers use CCIP's native Token Transfer capabilities to enable the transfer of their tokens across blockchains. CCIP Token Transfers offer rigorously audited Token Pool contracts and provide configurable rate-limiting functionalities for an enhanced developer experience and risk management. In addition, Token Transfers also improve the composability of tokens with other dApps and token bridges that are integrated with the standardized CCIP interface.
### [Receiving account types](#receiving-account-types)
With CCIP, you send transactions with data, tokens, or both data and tokens. The receiver of a CCIP transaction is either a smart contract or an [externally owned account (EOA)](https://ethereum.org/en/developers/docs/accounts/#types-of-account)
. Smart contracts can receive both data and tokens, while EOAs can only receive tokens:
| CCIP capability | What is sent | Type of receiving account supported |
| --- | --- | --- |
| Arbitrary Messaging | Data | Smart contracts only. EOAs on EVM blockchains cannot receive messages. |
| Token Transfer | Tokens | Smart contracts and EOAs |
| Programmable Token Transfer | Data and tokens | Smart contracts only. If you send data and transfer tokens to an EOA, only tokens will be transferred. |
[Common use cases](#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](#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/architecture#token-pools)
page.
What's next
-----------
* [\> Complete the Getting Started guide to learn the basics](/ccip/getting-started)
* [\> CCIP Directory](/ccip/directory)
* [\> Learn how to transfer tokens](/ccip/tutorials/transfer-tokens-from-contract)
* [\> Learn CCIP core concepts](/ccip/concepts)
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Getting Started | Chainlink Documentation
On this page
Getting Started
===============
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](#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-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.
[Open in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Sender.sol&autoCompile=true)
[What is Remix?](/getting-started/conceptual-overview#what-is-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)
--------------------------------------------------------------
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.
[Open in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Receiver.sol&autoCompile=true)
[What is Remix?](/getting-started/conceptual-overview#what-is-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-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.
5. 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)
------------------------
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](#examine-the-example-code)
------------------------------------------------------
### [Sender 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.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
/// @title - A simple contract for sending string data across chains.
contract Sender is OwnerIsCreator {
// Custom errors to provide more descriptive revert messages.
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance.
// Event emitted when a message is sent to another chain.
event MessageSent(
bytes32 indexed messageId, // The unique ID of the CCIP message.
uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
address receiver, // The address of the receiver on the destination chain.
string text, // The text being sent.
address feeToken, // the token address used to pay CCIP fees.
uint256 fees // The fees paid for sending the CCIP message.
);
IRouterClient private s_router;
LinkTokenInterface private s_linkToken;
/// @notice Constructor initializes the contract with the router address.
/// @param _router The address of the router contract.
/// @param _link The address of the link contract.
constructor(address _router, address _link) {
s_router = IRouterClient(_router);
s_linkToken = LinkTokenInterface(_link);
}
/// @notice Sends data to receiver on the destination chain.
/// @dev Assumes your contract has sufficient LINK.
/// @param destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param receiver The address of the recipient on the destination blockchain.
/// @param text The string text to be sent.
/// @return messageId The ID of the message that was sent.
function sendMessage(
uint64 destinationChainSelector,
address receiver,
string calldata text
) external onlyOwner returns (bytes32 messageId) {
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({
receiver: abi.encode(receiver), // ABI-encoded receiver address
data: abi.encode(text), // ABI-encoded string
tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array indicating no tokens are being sent
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit and allowing out-of-order execution.
// Best Practice: For simplicity, the values are hardcoded. It is advisable to use a more dynamic approach
// where you set the extra arguments off-chain. This allows adaptation depending on the lanes, messages,
// and ensures compatibility with future CCIP upgrades. Read more about it here: https://docs.chain.link/ccip/best-practices#using-extraargs
Client.EVMExtraArgsV2({
gasLimit: 200_000, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken address, indicating LINK will be used for fees
feeToken: address(s_linkToken)
});
// Get the fee required to send the message
uint256 fees = s_router.getFee(
destinationChainSelector,
evm2AnyMessage
);
if (fees > s_linkToken.balanceOf(address(this)))
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
s_linkToken.approve(address(s_router), fees);
// Send the message through the router and store the returned message ID
messageId = s_router.ccipSend(destinationChainSelector, evm2AnyMessage);
// Emit an event with message details
emit MessageSent(
messageId,
destinationChainSelector,
receiver,
text,
address(s_linkToken),
fees
);
// Return the message ID
return messageId;
}
}
[Open in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Sender.sol&autoCompile=true)
[What is Remix?](/getting-started/conceptual-overview#what-is-remix)
#### [Initializing the contract](#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/v1.5.1/i-router-client#getfee)
to estimate the CCIP fees.
* The `ccipSend` [function](/ccip/api-reference/v1.5.1/i-router-client#ccipsend)
to send CCIP messages.
#### [Sending data](#sending-data)
The `sendMessage` function completes several operations:
1. Construct a CCIP-compatible message using the `EVM2AnyMessage` [struct](/ccip/api-reference/v1.5.1/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/v1.5.1/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/v1.5.1/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/v1.5.1/i-router-client#ccipsend)
.
### [Receiver code](#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.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
/// @title - A simple contract for receiving string data across chains.
contract Receiver is CCIPReceiver {
// Event emitted when a message is received from another chain.
event MessageReceived(
bytes32 indexed messageId, // The unique ID of the message.
uint64 indexed sourceChainSelector, // The chain selector of the source chain.
address sender, // The address of the sender from the source chain.
string text // The text that was received.
);
bytes32 private s_lastReceivedMessageId; // Store the last received messageId.
string private s_lastReceivedText; // Store the last received text.
/// @notice Constructor initializes the contract with the router address.
/// @param router The address of the router contract.
constructor(address router) CCIPReceiver(router) {}
/// handle a received message
function _ccipReceive(
Client.Any2EVMMessage memory any2EvmMessage
) internal override {
s_lastReceivedMessageId = any2EvmMessage.messageId; // fetch the messageId
s_lastReceivedText = abi.decode(any2EvmMessage.data, (string)); // abi-decoding of the sent text
emit MessageReceived(
any2EvmMessage.messageId,
any2EvmMessage.sourceChainSelector, // fetch the source chain identifier (aka selector)
abi.decode(any2EvmMessage.sender, (address)), // abi-decoding of the sender address,
abi.decode(any2EvmMessage.data, (string))
);
}
/// @notice Fetches the details of the last received message.
/// @return messageId The ID of the last received message.
/// @return text The last received text.
function getLastReceivedMessageDetails()
external
view
returns (bytes32 messageId, string memory text)
{
return (s_lastReceivedMessageId, s_lastReceivedText);
}
}
[Open in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/Receiver.sol&autoCompile=true)
[What is Remix?](/getting-started/conceptual-overview#what-is-remix)
#### [Initializing the contract](#initializing-the-contract-1)
When you deploy the contract, you define the router address. The receiver contract inherits from the [CCIPReceiver.sol](/ccip/api-reference/v1.5.1/ccip-receiver)
contract, which uses the router address.
#### [Receiving data](#receiving-data)
On the destination blockchain:
1. The CCIP Router invokes the `ccipReceive` [function](/ccip/api-reference/v1.5.1/ccip-receiver#ccipreceive)
. **Note**: This function is protected by the `onlyRouter` [modifier](/ccip/api-reference/v1.5.1/ccip-receiver#onlyrouter)
, which ensures that only the router can call the receiver contract.
2. The `ccipReceive` [function](/ccip/api-reference/v1.5.1/ccip-receiver#ccipreceive)
calls an internal function `_ccipReceive` [function](/ccip/api-reference/v1.5.1/ccip-receiver#_ccipreceive)
. The receiver contract implements this function.
3. This `_ccipReceive` [function](/ccip/api-reference/v1.5.1/ccip-receiver#_ccipreceive)
expects an `Any2EVMMessage` [struct](/ccip/api-reference/v1.5.1/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)
.
What's next
-----------
* [\> Learn how to transfer tokens](/ccip/tutorials/transfer-tokens-from-contract)
* [\> Learn how to transfer tokens and send data in a single CCIP transaction](/ccip/tutorials/programmable-token-transfers)
* [\> Transfer Tokens Between EOAs](/ccip/tutorials/transfer-tokens-from-eoa)
* [\> Learn how to send arbitrary data over CCIP](/ccip/tutorials/send-arbitrary-data)
* [\> CCIP Directory](/ccip/directory)
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Chainlink Documentation | Chainlink Documentation
Chainlink Developer Docs
========================
What are you building?
----------------------
[\
\
### CCIP\
\
Global standard for building secure cross-chain applications.](/ccip)
[\
\
### Data Feeds\
\
Decentralized and high-quality data for DeFi, sports, weather, and more.](/data-feeds)
[\
\
### Data Streams\
\
Secure and reliable high-frequency market data for ultra-fast derivatives products.](/data-streams)
[\
\
### Functions\
\
Serverless platform that fetches data from any API & runs custom compute.](/chainlink-functions)
[\
\
### Automation\
\
Reliable, high-performance, decentralized automation for smart contracts.](/chainlink-automation)
[\
\
### VRF\
\
Verifiable, tamper-proof random number generator for blockchain gaming and NFTs.](/vrf)
#### Recommended reading
We think you'd love to explore
------------------------------
[General](/getting-started/conceptual-overview)
[Link Token Contracts](/resources/link-token-contracts)
[Getting Started with CCIP](/ccip/getting-started)
[CCIP Directory](/ccip/directory)
[Data Feed Addresses](/data-feeds/price-feeds/addresses)
[SmartData Feed Addresses](/data-feeds/smartdata/addresses)
[Getting Started with Data Streams](/data-streams/getting-started-hardhat)
[Data Streams Addresses](/data-streams/crypto-streams)
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# CCIP Service Limits | Chainlink Documentation
On this page
CCIP Service Limits
===================
[Mainnet](#mainnet)
--------------------
| Item | Description | Limit |
| --- | --- | --- |
| Maximum message `data` length | `data` payload sent within the [CCIP message](/ccip/api-reference/v1.5.1/client#evm2anymessage) | 30 kilobytes |
| Message Execution Gas Limit | User specified [gas limit](/ccip/api-reference/v1.5.1/client#evmextraargsv1)
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 |
| Smart Execution time window | Maximum duration for the execution of a [CCIP message](/ccip/api-reference/v1.5.1/client#evm2anymessage) | 8 hours |
| 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-tokens#requirements-for-token-pools)
. | 90,000 |
[Testnet](#testnet)
--------------------
| Item | Description | Limit |
| --- | --- | --- |
| Maximum message `data` length | `data` payload sent within the [CCIP message](/ccip/api-reference/v1.5.1/client#evm2anymessage) | 30 kilobytes |
| Message Execution Gas Limit | User specified [gas limit](/ccip/api-reference/v1.5.1/client#evmextraargsv1)
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 |
| Smart Execution timeframe | Maximum duration for the execution of a [CCIP message](/ccip/api-reference/v1.5.1/client#evm2anymessage) | 8 hours |
| 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-tokens#requirements-for-token-pools)
. | 90,000 |
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Chainlink CCIP | Chainlink Documentation
On this page
CCIP Directory
==============

Networks testnet (50)
---------------------
[\
\
### Apechain Curtis\
\
1 lane | 1 token](/ccip/directory/testnet/chain/apechain-testnet-curtis)
[\
\
### Arbitrum Sepolia\
\
10 lanes | 3 tokens](/ccip/directory/testnet/chain/ethereum-testnet-sepolia-arbitrum-1)
[\
\
### Astar Shibuya\
\
2 lanes | 2 tokens](/ccip/directory/testnet/chain/polkadot-testnet-astar-shibuya)
[\
\
### Avalanche Fuji\
\
10 lanes | 3 tokens](/ccip/directory/testnet/chain/avalanche-fuji-testnet)
[\
\
### B² Testnet\
\
1 lane | 1 token](/ccip/directory/testnet/chain/bitcoin-testnet-bsquared-1)
[\
\
### Base Sepolia\
\
11 lanes | 3 tokens](/ccip/directory/testnet/chain/ethereum-testnet-sepolia-base-1)
[\
\
### Berachain Bartio\
\
1 lane | 0 token](/ccip/directory/testnet/chain/berachain-testnet-bartio)
[\
\
### Bitlayer Testnet\
\
1 lane | 0 token](/ccip/directory/testnet/chain/bitcoin-testnet-bitlayer-1)
[\
\
### Blast Sepolia\
\
2 lanes | 2 tokens](/ccip/directory/testnet/chain/ethereum-testnet-sepolia-blast-1)
[\
\
### BNB Chain Testnet\
\
8 lanes | 2 tokens](/ccip/directory/testnet/chain/bsc-testnet)
[\
\
### BOB Sepolia\
\
1 lane | 1 token](/ccip/directory/testnet/chain/bitcoin-testnet-sepolia-bob-1)
[\
\
### Botanix Testnet\
\
1 lane | 1 token](/ccip/directory/testnet/chain/bitcoin-testnet-botanix)
[\
\
### Celo Alfajores\
\
1 lane | 2 tokens](/ccip/directory/testnet/chain/celo-testnet-alfajores)
[\
\
### Core Testnet\
\
1 lane | 0 token](/ccip/directory/testnet/chain/core-testnet)
See more
Tokens (3)
----------
[Add my token](/ccip/tutorials/token-manager#verifying-your-token)
[\
\
### CCIP-BnM](/ccip/directory/testnet/token/CCIP-BnM)
[\
\
### CCIP-LnM](/ccip/directory/testnet/token/CCIP-LnM)
[\
\
### USDC](/ccip/directory/testnet/token/USDC)
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Chainlink CCIP | Chainlink Documentation
On this page
CCIP Directory
==============

Networks mainnet (45)
---------------------
[\
\
### Apechain\
\
1 lane | 0 token](/ccip/directory/mainnet/chain/apechain-mainnet)
[\
\
### Arbitrum One\
\
20 lanes | 58 tokens](/ccip/directory/mainnet/chain/ethereum-mainnet-arbitrum-1)
[\
\
### Astar\
\
2 lanes | 4 tokens](/ccip/directory/mainnet/chain/polkadot-mainnet-astar)
[\
\
### Avalanche\
\
10 lanes | 11 tokens](/ccip/directory/mainnet/chain/avalanche-mainnet)
[\
\
### B²\
\
2 lanes | 1 token](/ccip/directory/mainnet/chain/bitcoin-mainnet-bsquared-1)
[\
\
### Base\
\
16 lanes | 41 tokens](/ccip/directory/mainnet/chain/ethereum-mainnet-base-1)
[\
\
### Berachain\
\
1 lane | 1 token](/ccip/directory/mainnet/chain/berachain-mainnet)
[\
\
### Bitlayer\
\
1 lane | 1 token](/ccip/directory/mainnet/chain/bitcoin-mainnet-bitlayer-1)
[\
\
### Blast\
\
4 lanes | 5 tokens](/ccip/directory/mainnet/chain/ethereum-mainnet-blast-1)
[\
\
### BNB Chain\
\
13 lanes | 29 tokens](/ccip/directory/mainnet/chain/bsc-mainnet)
[\
\
### BOB\
\
1 lane | 1 token](/ccip/directory/mainnet/chain/bitcoin-mainnet-bob-1)
[\
\
### Celo\
\
11 lanes | 4 tokens](/ccip/directory/mainnet/chain/celo-mainnet)
[\
\
### Core\
\
1 lane | 0 token](/ccip/directory/mainnet/chain/core-mainnet)
[\
\
### Corn\
\
1 lane | 1 token](/ccip/directory/mainnet/chain/corn-mainnet)
See more
Tokens (80)
-----------
[Add my token](/ccip/tutorials/token-manager#verifying-your-token)
[\
\
### $PAAL](/ccip/directory/mainnet/token/$PAAL)
[\
\
### ALU](/ccip/directory/mainnet/token/ALU)
[\
\
### APU](/ccip/directory/mainnet/token/APU)
[\
\
### BETS](/ccip/directory/mainnet/token/BETS)
[\
\
### BMX](/ccip/directory/mainnet/token/BMX)
[\
\
### BONE](/ccip/directory/mainnet/token/BONE)
[\
\
### BYTES](/ccip/directory/mainnet/token/BYTES)
[\
\
### CKP](/ccip/directory/mainnet/token/CKP)
[\
\
### DEXTF](/ccip/directory/mainnet/token/DEXTF)
[\
\
### DFX](/ccip/directory/mainnet/token/DFX)
[\
\
### DIP](/ccip/directory/mainnet/token/DIP)
[\
\
### DPI](/ccip/directory/mainnet/token/DPI)
[\
\
### dsETH](/ccip/directory/mainnet/token/dsETH)
[\
\
### EARNM](/ccip/directory/mainnet/token/EARNM)
[\
\
### egETH](/ccip/directory/mainnet/token/egETH)
[\
\
### ETHx](/ccip/directory/mainnet/token/ETHx)
[\
\
### GHO](/ccip/directory/mainnet/token/GHO)
[\
\
### hyETH](/ccip/directory/mainnet/token/hyETH)
[\
\
### IBTC](/ccip/directory/mainnet/token/IBTC)
[\
\
### LAND](/ccip/directory/mainnet/token/LAND)
[\
\
### LBTC](/ccip/directory/mainnet/token/LBTC)
[\
\
### LDY](/ccip/directory/mainnet/token/LDY)
[\
\
### LEASH](/ccip/directory/mainnet/token/LEASH)
[\
\
### LINK](/ccip/directory/mainnet/token/LINK)
See more
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# CCIP Billing | Chainlink Documentation
On this page
CCIP Billing
============
The CCIP billing model uses the `feeToken` specified in the [message](/ccip/api-reference/v1.5.1/client#evm2anymessage)
to pay a single fee on the source blockchain. CCIP uses a gas-locked fee payment mechanism, referred to as _Smart Execution_, 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/best-practices#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](#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/v1.5.1/client#evm2anymessage)
. **Note:** Users can call the [getFee](/ccip/api-reference/v1.5.1/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#decentralized-oracle-network-don)
and [Risk Management Network](/ccip/concepts#risk-management-network)
.
### [Blockchain fee](#blockchain-fee)
The blockchain fee is calculated by the following formula:
blockchain fee = execution cost + data availability cost
#### [Execution 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 for _Smart Execution_. 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/v1.5.1/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/v1.5.1/client#evmextraargsv1)
of the CCIP message. **Note:** Remember to [carefully estimate the `gasLimit` that you set](/ccip/best-practices#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/v1.5.1/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](#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](#network-fee)
The fee paid to CCIP service providers, including node operators running the [Decentralized Oracle Network](/ccip/concepts#decentralized-oracle-network-don)
and [Risk Management Network](/ccip/concepts#risk-management-network)
is calculated as follows:
#### [Token transfers or programmable token transfers](#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/architecture#token-pools)
and the [lanes](/ccip/concepts#lane)
:
* **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)](#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](#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.
| Use case | Token Pool Mechanism | Lanes | Fee Token | |
| --- | --- | --- | --- | --- |
| LINK | Others |
| --- | --- | --- | --- | --- |
| * Token Transfers
* Programmable Token Transfers | Lock and Unlock | All Lanes | 0.063 % | 0.07 % |
| Lock and Mint
Burn and Mint
Burn and Unlock | Non-Ethereum | 0.225 USD | 0.25 USD |
| From: Ethereum | 0.45 USD | 0.50 USD |
| To: Ethereum | 1.35 USD | 1.50 USD |
| Messaging | N/A | Non-Ethereum | 0.09 USD | 0.10 USD |
| From/To: Ethereum | 0.45 USD | 0.50 USD |
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:
Calculate
| Token | Mechanism | Fee Token | |
| --- | --- | --- | --- |
| LINK | Others |
| --- | --- | --- | --- |
| | | | |
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Chainlink CCIP Service Responsibility | Chainlink Documentation
On this page
Chainlink CCIP Service Responsibility
=====================================
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.
[Application Developer Responsibilities](#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/concepts/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-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.
[Token Developers Responsibilities](#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-tokens#token-handling-mechanisms-and-token-pool-deployment)
, [Lock and Mint](/ccip/concepts/cross-chain-tokens#token-handling-mechanisms-and-token-pool-deployment)
, or [Lock and Unlock](/ccip/concepts/cross-chain-tokens#token-handling-mechanisms-and-token-pool-deployment)
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-tokens#custom-token-pools)
**: 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-tokens#token-handling-mechanisms-and-token-pool-deployment)
, [Lock and Mint](/ccip/concepts/cross-chain-tokens#token-handling-mechanisms-and-token-pool-deployment)
, or [Lock and Unlock](/ccip/concepts/cross-chain-tokens#token-handling-mechanisms-and-token-pool-deployment)
. 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](#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.
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# CCIP Execution Latency | Chainlink Documentation
On this page
CCIP Execution Latency
======================
[CCIP transaction lifecycle](#ccip-transaction-lifecycle)
----------------------------------------------------------
As depicted in the [CCIP detailed architecture](/ccip/architecture#detailed-architecture)
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 CommitStore 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. If the execution cost increases, such as due to a gas price spike, the Executing DON adjusts the gas price incrementally to achieve eventual execution, potentially introducing additional latency (Smart Execution).
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](#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](#types-of-deterministic-finality)
#### [Finality on L1 PoA/PoS Chains](#finality-on-l1-poapos-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](#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](#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](#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](#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](#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 |
| --- | --- | --- |
| 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 |
| 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 |
| Mode | Finality tag | 37 minutes |
| OP | Finality tag | 20 minutes |
| Polygon | Finality tag | 2 minutes |
| Polygon zkEVM | Finality tag | 2 hours |
| Ronin | Finality tag | 10 seconds |
| Scroll | Finality tag | 1 hour |
| Sei | 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 |
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Chainlink CCIP Release Notes | Chainlink Documentation
On this page
Chainlink CCIP Release Notes
============================
[2025-03-27 - CCIP Expands to New Blockchains](#2025-03-27-ccip-expands-to-new-blockchains)
--------------------------------------------------------------------------------------------
Chainlink CCIP expands support to new blockchains:
* Apechain Mainnet
* Cronos Mainnet
* Cronos zkEVM Mainnet
* Hedera Mainnet
Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet)
for contract addresses and network details.
[2025-03-21 - CCIP Expands to New Blockchains](#2025-03-21-ccip-expands-to-new-blockchains)
--------------------------------------------------------------------------------------------
Chainlink CCIP expands support to new blockchains:
* Mind Network Mainnet
* Mind Network Testnet
Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
for contract addresses and network details.
[2025-03-17 - CCIP on Hedera Testnet](#2025-03-17-ccip-on-hedera-testnet)
--------------------------------------------------------------------------
Chainlink CCIP expands support to Hedera Testnet. Check the [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
for contract addresses and network details.
[2025-03-05 - CCIP Expands to New Blockchains](#2025-03-05-ccip-expands-to-new-blockchains)
--------------------------------------------------------------------------------------------
Chainlink CCIP expands support to new blockchains:
* Apechain Curtis
* Cronos Testnet
* Cronos zkEVM Testnet
* Hemi Sepolia
Check the [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
for contract addresses and network details.
[2025-02-18 - CCIP Expands to New Blockchains](#2025-02-18-ccip-expands-to-new-blockchains)
--------------------------------------------------------------------------------------------
Chainlink CCIP expands support to new blockchains:
* Merlin Mainnet
* Fraxtal Mainnet
* Unichain Mainnet
* Core Mainnet
* Berachain Mainnet
Check the [CCIP Directory](https://docs.chain.link/ccip/directory/mainnet)
for more information.
[2025-02-12 - CCIP Expands to New Blockchains](#2025-02-12-ccip-expands-to-new-blockchains)
--------------------------------------------------------------------------------------------
Chainlink CCIP expands support to new blockchains:
* Merlin Testnet
* Fraxtal Testnet
* Lens Sepolia
* Unichain Sepolia
* Berachain Bartio
Check the [CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
for more information.
[2025-02-10 - CCIP Expands to Treasure](#2025-02-10-ccip-expands-to-treasure)
------------------------------------------------------------------------------
Chainlink CCIP expands support to Treasure:
* Treasure Mainnet
* Treasure Topaz testnet
Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
for contract addresses and network details.
[2025-02-07 - CCIP Expands to New Blockchains](#2025-02-07-ccip-expands-to-new-blockchains)
--------------------------------------------------------------------------------------------
Chainlink CCIP expands support to new blockchains:
* Bitlayer
* Botanix
* Corn Network
* Hashkey Chain
* Ink
* Monad Testnet
* Polygon zkEVM
* Sei Network
* Soneium
* XLayer
Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
for contract addresses and network details.
[2025-01-30 - CCIP on Bitlayer](#2025-01-30-ccip-on-bitlayer)
--------------------------------------------------------------
Chainlink CCIP is publicly available on Bitlayer. Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/bitcoin-mainnet-bitlayer-1)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/bitcoin-testnet-bitlayer-1)
for more information.
[2025-01-23 - CCIP on World Chain](#2025-01-23-ccip-on-world-chain)
--------------------------------------------------------------------
Chainlink CCIP is publicly available on World Chain. Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/ethereum-mainnet-worldchain-1)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/ethereum-testnet-sepolia-worldchain-1)
for more information.
[2025-01-22 - CCIP on Sonic](#2025-01-22-ccip-on-sonic)
--------------------------------------------------------
Chainlink CCIP is publicly available on Sonic mainnet and testnet. Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/sonic-mainnet)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/sonic-testnet-blaze)
for more information.
[2025-01-22 - CCIP on Bob](#2025-01-22-ccip-on-bob)
----------------------------------------------------
Chainlink CCIP is publicly available on Bob mainnet and Sepolia testnet. Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/bitcoin-mainnet-bob-1)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/bitcoin-testnet-sepolia-bob-1)
for more information.
[2025-01-14 - Bridged USDC Support in CCIP](#2025-01-14-bridged-usdc-support-in-ccip)
--------------------------------------------------------------------------------------
Chainlink CCIP now supports Bridged USDC, addressing the critical challenge of liquidity fragmentation in the multi-chain ecosystem. This enhancement enables:
* **Seamless Liquidity**: Projects can now leverage Bridged USDC to bootstrap initial liquidity on new chains without waiting for native USDC support
* **Standardized Implementation**: Follows Circle's Bridged USDC Standard, ensuring consistency and reliability across different blockchains
* **Future-Proof Integration**: Projects can easily transition to native USDC when their blockchain receives CCTP approval, with no disruption to existing integrations
* **Enhanced DeFi Accessibility**: Accelerates DeFi adoption by providing immediate access to stable, reliable USDC liquidity across emerging blockchain networks
This implementation maintains CCIP's robust security features while expanding the possibilities for cross-chain token transfers and DeFi applications.
For more information, see the [USDC tutorial](https://docs.chain.link/ccip/tutorials/usdc)
.
[2025-01-14 - Token Manager](#2025-01-14-token-manager)
--------------------------------------------------------
The Token Manager is now available to help token developers to deploy, configure, and manage Cross-Chain Tokens (CCTs) in a simplified web interface. Refer to the [Token Manager](https://docs.chain.link/ccip/tutorials/token-manager)
guide for more information.
[2024-12-17 - CCIP on Shibarium](#2024-12-17-ccip-on-shibarium)
----------------------------------------------------------------
Chainlink CCIP is publicly available on Shibarium mainnet and Shibarium Puppynet. Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/shibarium-mainnet)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/shibarium-testnet-puppynet)
for more information.
[2024-12-17 - CCIP on Bsquared](#2024-12-17-ccip-on-bsquared)
--------------------------------------------------------------
Chainlink CCIP is publicly available on Bsquared mainnet and Bsquared testnet. Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/bitcoin-mainnet-bsquared-1)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/bitcoin-testnet-bsquared-1)
for more information.
[2024-12-11 - CCIP on Ronin](#2024-12-11-ccip-on-ronin)
--------------------------------------------------------
Chainlink CCIP is publicly available on Ronin mainnet and Ronin Saigon testnet. Check the mainnet [CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/ronin-mainnet)
and testnet [CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/ronin-testnet-saigon)
for more information.
[2024-12-12 - CCIP JavaScript SDK](#2024-12-12-ccip-javascript-sdk)
--------------------------------------------------------------------
The [CCIP JavaScript SDK](https://github.com/smartcontractkit/ccip-javascript-sdk)
is now available, introducing two packages to simplify management of cross-chain token transfers, and to integrate CCIP with the frontend of your own app.
The [CCIP JavaScript SDK guide](https://docs.chain.link/ccip/ccip-javascript-sdk)
introduces the features of the SDK and shows how to run an example app so you can explore the SDK's capabilities.
[2024-12-04 - Chainlink CCIP 1.5.1](#2024-12-04-chainlink-ccip-1-5-1)
----------------------------------------------------------------------
Chainlink CCIP 1.5.1 is now available, introducing several significant enhancements for cross-chain token pool management.
**Enhanced Token Support:**
* Added support for tokens with different decimals across chains
* New BurnMintERC20 contract for easy token deployment and cross-chain expansion with configurable decimals and max supply
**Improved Token Pool Management:**
* Enhanced token pool upgrades to support multiple active pools simultaneously
* Ensures in-flight messages remain deliverable during pool upgrades
* Upgraded token pool access control from OwnerIsCreator to Ownable2StepMsgSender for better security
For detailed implementation guides and examples, visit our [Cross-Chain Token (CCT) documentation](https://docs.chain.link/ccip/concepts/cross-chain-tokens)
. For technical details and interfaces, see the [CCIP v1.5.1 API Reference](https://docs.chain.link/ccip/api-reference/v1.5.1)
.
[2024-12-03 - CCIP on Mantle](#2024-12-03-ccip-on-mantle)
----------------------------------------------------------
Chainlink CCIP is publicly available on Mantle mainnet and testnet. Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/ethereum-mainnet-mantle-1)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/ethereum-testnet-sepolia-mantle-1)
for more information.
[2024-11-25 - CCIP on Zircuit](#2024-11-25-ccip-on-zircuit)
------------------------------------------------------------
Chainlink CCIP is publicly available on Zircuit mainnet and testnet. Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/ethereum-mainnet-zircuit-1)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/ethereum-testnet-sepolia-zircuit-1)
for more information.
[2024-11-19 - CCIP on Astar](#2024-11-19-ccip-on-astar)
--------------------------------------------------------
Chainlink CCIP is publicly available on Astar Mainnet and Astar Shibuya. Check the [mainnet CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/polkadot-mainnet-astar)
and [testnet CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/polkadot-testnet-astar-shibuya)
for more information.
[2024-11-06 - CCIP on Ethereum Holesky](#2024-11-06-ccip-on-ethereum-holesky)
------------------------------------------------------------------------------
Chainlink CCIP is publicly available on Ethereum Holesky. Check the [CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/ethereum-testnet-holesky)
for more information.
[2024-10-29 - CCIP Directory and CCIP Explorer](#2024-10-29-ccip-directory-and-ccip-explorer)
----------------------------------------------------------------------------------------------
The new [CCIP Directory](https://docs.chain.link/ccip/directory)
provides access to network, token, and lane configuration details, replacing the former CCIP Supported Networks pages.
The CCIP Explorer has been refreshed with a new home page, and the Transaction Details now load in a side panel. The CCIP Explorer also supports the option to display CCIP Private Transactions.
[2024-10-29 - Cross-Chain Token (CCT) standard - Self-Service Availability](#2024-10-29-cross-chain-token-cct-standard-self-service-availability)
--------------------------------------------------------------------------------------------------------------------------------------------------
Chainlink's Cross-Chain Token (CCT) standard is now available, introducing a self-service model that allows token developers to enable their tokens in CCIP independently.
* Token developers can deploy, configure, and manage their tokens and token pools in Chainlink's Cross-Chain Interoperability Protocol (CCIP) without requiring manual deployment.
* Chainlink provides fully audited token pools, supporting either Burn & Mint or Lock & Mint mechanisms, which can be combined to provide different token handling mechanisms.
* Token developers maintain complete ownership of their token contracts, pools, and implementation logic, including configuration of rate limits for token transfers across multiple blockchains.
* The CCT standard avoids vendor lock-in and hard-coded functions, ensuring complete autonomy for projects managing cross-chain assets.
* CCT leverages Chainlink's Decentralized Oracle Networks (DONs) to provide secure cross-chain token operations, supported by the Risk Management Network and configurable rate limits.
Read the CCT [conceptual](https://docs.chain.link/ccip/concepts/cross-chain-tokens)
documentation for more information. To learn how to enable your tokens in CCIP in minutes, follow the CCT [tutorials](https://docs.chain.link/ccip/tutorials/cross-chain-tokens)
.
[2024-10-22 - CCIP on Soneium Minato](#2024-10-22-ccip-on-soneium-minato)
--------------------------------------------------------------------------
Chainlink CCIP is available on Soneium Minato testnet. Visit the [CCIP Directory](https://docs.chain.link/ccip/directory/testnet/chain/ethereum-testnet-sepolia-soneium-1)
page for more information.
[2024-10-10 - CCIP on Scroll](#2024-10-10-ccip-on-scroll)
----------------------------------------------------------
Chainlink CCIP is publicly available on Scroll mainnet and testnet.
* Visit the [CCIP Directory](https://docs.chain.link/ccip/directory)
page for more information.
[2024-10-08 - CCIP on Linea](#2024-10-08-ccip-on-linea)
--------------------------------------------------------
Chainlink CCIP is publicly available on Linea mainnet and testnet.
* Visit the [CCIP Directory](https://docs.chain.link/ccip/directory)
for more information.
[2024-10-04 - Chainlink CCIP 1.5 - Testnet](#2024-10-04-chainlink-ccip-1-5-testnet)
------------------------------------------------------------------------------------
Chainlink CCIP 1.5 is now available on testnet, introducing several new features and enhancements.
**Risk Management Network Coverage:** Certain CCIP integrations may not initially include the Risk Management Network (RMN). Blockchains can be integrated with CCIP in a phased approach, starting with the deployment of the Committing and Executing Decentralized Oracle Networks (DONs), followed by the addition of the Risk Management Network in a subsequent update. During a phased deployment, the relevant Commit Stores are configured in the Risk Management contract to always be considered blessed until the Risk Management Network has been deployed for that blockchain. Please refer to the [CCIP Directory](https://docs.chain.link/ccip/directory)
to identify which integrations utilize a phased approach, and review the [CCIP Service Responsibility](https://docs.chain.link/ccip/service-responsibility)
for more information.
**New Version of `EVMExtraArgs`:** Chainlink CCIP 1.5 introduces a new version of `EVMExtraArgs`, allowing users to set the `allowOutOfOrderExecution` parameter. This feature enables developers to control the execution order of their messages on the destination blockchain. The `allowOutOfOrderExecution` parameter is part of [`EVMExtraArgsV2`](https://docs.chain.link/ccip/api-reference/client#evmextraargsv2)
and is available only on lanes where the **Out of Order Execution** property is set to **Optional** or **Required**. Please consult the [CCIP Directory](https://docs.chain.link/ccip/directory)
to determine if your target lane supports this feature.
[2024-09-16 - CCIP on ZKsync](#2024-09-16-ccip-on-zksync)
----------------------------------------------------------
Chainlink CCIP is publicly available on ZKsync mainnet and testnet.
* [Mainnet lane details](https://docs.chain.link/ccip/directory/mainnet/chain/ethereum-mainnet-zksync-1)
* [Testnet lane details](https://docs.chain.link/ccip/directory/testnet/chain/ethereum-testnet-sepolia-zksync-1)
[2024-08-08 - CCIP on Metis](#2024-08-08-ccip-on-metis)
--------------------------------------------------------
Chainlink CCIP is publicly available on Metis mainnet and testnet.
* [Mainnet lane details](https://docs.chain.link/ccip/directory/mainnet/chain/ethereum-mainnet-andromeda-1)
* [Testnet lane details](https://docs.chain.link/ccip/directory/testnet/chain/ethereum-testnet-sepolia-andromeda-1)
[2024-07-09 - CCIP on Blast](#2024-07-09-ccip-on-blast)
--------------------------------------------------------
Chainlink CCIP is publicly available on Blast mainnet and testnet.
* [Mainnet lane details](https://docs.chain.link/ccip/directory/mainnet/chain/ethereum-mainnet-blast-1)
* [Testnet lane details](https://docs.chain.link/ccip/directory/testnet/chain/ethereum-testnet-sepolia-blast-1)
[2024-06-19 - CCIP on Mode](#2024-06-19-ccip-on-mode)
------------------------------------------------------
Chainlink CCIP is publicly available on Mode mainnet and testnet.
* [Mainnet lane details](https://docs.chain.link/ccip/directory/mainnet/chain/ethereum-mainnet-mode-1)
* [Testnet lane details](https://docs.chain.link/ccip/directory/testnet/chain/ethereum-testnet-sepolia-mode-1)
[2024-06-05 - CCIP on Gnosis](#2024-06-05-ccip-on-gnosis)
----------------------------------------------------------
Chainlink CCIP is publicly available on Gnosis mainnet.
* [Mainnet lane details](https://docs.chain.link/ccip/directory/mainnet/chain/xdai-mainnet)
[2024-05-29 - CCIP on Celo](#2024-05-29-ccip-on-celo)
------------------------------------------------------
Chainlink CCIP is publicly available on Celo mainnet and Alfajores testnet.
* [Mainnet lane details](https://docs.chain.link/ccip/directory/mainnet/chain/celo-mainnet)
* [Testnet lane details](https://docs.chain.link/ccip/directory/testnet/chain/celo-testnet-alfajores)
[2024-05-08 - CCIP on Polygon Amoy](#2024-05-08-ccip-on-polygon-amoy)
----------------------------------------------------------------------
Chainlink CCIP is publicly available on the Polygon Amoy testnet.
* [Testnet lane details](https://docs.chain.link/ccip/directory/testnet/chain/polygon-testnet-amoy)
[2024-04-24 - Chainlink CCIP GA](#2024-04-24-chainlink-ccip-ga)
----------------------------------------------------------------
Chainlink CCIP is now Generally Available (GA) on mainnet and testnet.
To support your development and implementation needs, we encourage you to reach out to our team of experts for guidance and support. For expert advice, visit the [Chainlink CCIP Contact form](https://chain.link/ccip-contact)
.
Additionally, the [Chainlink CCIP local simulator](https://github.com/smartcontractkit/chainlink-local)
is available to enhance your development workflow with CCIP. This tool allows you to simulate Chainlink CCIP functionality locally within your Hardhat and Foundry projects. The simulator is designed so you can test your contracts locally and transition smoothly to test networks without any modifications.
[2024-04-11 - WETH and support of Lock and Unlock mechanism](#2024-04-11-weth-and-support-of-lock-and-unlock-mechanism)
------------------------------------------------------------------------------------------------------------------------
Chainlink's CCIP now supports WETH (Wrapped Ether) transfers through the Lock and Unlock token mechanism.
This feature allows CCIP to securely lock tokens on the source blockchain and subsequently release an equivalent amount of tokens on the destination blockchain, facilitating seamless cross-chain transfers of WETH. The introduction of this mechanism enables WETH transfers across several key lanes.
For a specific lane configuration, see the [CCIP Directory](https://docs.chain.link/ccip/directory/mainnet)
. For more detailed information about the Lock and Unlock mechanism and its applications, read the [Token Pools](https://docs.chain.link/ccip/architecture#token-pools)
page.
[2024-04-11 - CCIP gas limit increase on Mainnet](#2024-04-11-ccip-gas-limit-increase-on-mainnet)
--------------------------------------------------------------------------------------------------
The maximum [gasLimit](https://docs.chain.link/ccip/api-reference/client#evmextraargsv1)
that you can set for CCIP messages on mainnet has been increased to 3,000,000 gas units. The change has been documented in the [Service Limits](https://docs.chain.link/ccip/service-limits)
page.
[2024-04-01 - CCIP v1.0.0 deprecated on mainnet](#2024-04-01-ccip-v1-0-0-deprecated-on-mainnet)
------------------------------------------------------------------------------------------------
CCIP v1.0.0 is no longer supported on mainnet. You must use the new router addresses listed in the [CCIP Directory](https://docs.chain.link/ccip/directory/mainnet)
page.
[2024-03-11 - CCIP on Wemix and Kroma](#2024-03-11-ccip-on-wemix-and-kroma)
----------------------------------------------------------------------------
Chainlink CCIP is publicly available on Wemix and Kroma for both mainnet and testnet. See the [CCIP Directory](https://docs.chain.link/ccip/directory)
for network details.
[2024-02-07 - v1.0.0 deprecated on testnet](#2024-02-07-v1-0-0-deprecated-on-testnet)
--------------------------------------------------------------------------------------
CCIP v1.0.0 is no longer supported on **testnet**. You must use the new router addresses listed in the [CCIP Directory](https://docs.chain.link/ccip/directory)
.
[2024-01-15 - v1.2.0 release on mainnet](#2024-01-15-v1-2-0-release-on-mainnet)
--------------------------------------------------------------------------------
CCIP v1.0.0 has been deprecated on mainnet. You must use the new router addresses listed in this page **before March 31st, 2024**. Please note that there is no change to the router interface. The CCIP v1.0.0 mainnet routers will continue to function in parallel **until March 31st, 2024**, but we highly recommend switching to the v1.2.0 routers as soon as possible. If you currently use CCIP v1.0.0, use the [@chainlink/contracts-ccip npm package version 0.7.6](https://www.npmjs.com/package/@chainlink/contracts-ccip/v/0.7.6)
. To migrate to v1.2.0, use [version 1.2.1 of the npm package](https://www.npmjs.com/package/@chainlink/contracts-ccip/v/1.2.1)
or later. Please refer to the [release notes](https://docs.chain.link/ccip/release-notes)
for a comprehensive overview of the enhancements and new features in v1.2.0.
* There is no change to the router interface, but you must use the new router addresses listed in the [CCIP Directory](https://docs.chain.link/ccip/directory)
.
* USDC transfers are currently supported on several lanes. See the the [CCIP Directory](https://docs.chain.link/ccip/directory/mainnet)
page to get a specific lane's token addresses and rate limits.
* The message sequencing process is simplified in CCIP message handling by removing the `strict` sequencing flag from the [extraArgs](https://docs.chain.link/ccip/api-reference/client#evmextraargsv1)
field in [CCIP messages](https://docs.chain.link/ccip/api-reference/client#evm2anymessage)
.
* The gas limit and maximum message data length for CCIP messages have been adjusted on mainnets. These changes are detailed in the [Service Limits](https://docs.chain.link/ccip/service-limits)
documentation.
* To interact with CCIP v1.2.0, use the [@chainlink/contract-ccip](https://www.npmjs.com/package/@chainlink/contracts-ccip)
npm package.
[2023-12-15 - Arbitrum Sepolia](#2023-12-15-arbitrum-sepolia)
--------------------------------------------------------------
Chainlink CCIP is publicly available on Arbitrum Sepolia. See the [CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
for more information.
[2023-12-08 - v1.2.0 release on testnet](#2023-12-08-v1-2-0-release-on-testnet)
--------------------------------------------------------------------------------
CCIP v1.0.0 has been deprecated on testnet. You must use the new router addresses listed in the [CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
**before January 31st, 2024**. Please note that there is no change to the router interface. The CCIP v1.0.0 testnet routers will continue to function in parallel **until January 31st, 2024**, but we highly recommend switching to the v1.2.0 routers as soon as possible. If you currently use CCIP v1.0.0, use the [@chainlink/contracts-ccip npm package version 0.7.6](https://www.npmjs.com/package/@chainlink/contracts-ccip/v/0.7.6)
. To migrate to v1.2.0, use [version 1.2.1 of the npm package](https://www.npmjs.com/package/@chainlink/contracts-ccip/v/1.2.1)
or later.
* There is no change to the router interface, but you must use the new router addresses listed in the [CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
. USDC transfers are currently supported on several lanes. See the the [CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
page to get a specific lane's token addresses and rate limits.
* We've simplified the message sequencing process in our CCIP message handling by removing the `strict` sequencing flag from the [extraArgs](https://docs.chain.link/ccip/api-reference/client#evmextraargsv1)
field in [CCIP messages](https://docs.chain.link/ccip/api-reference/client#evm2anymessage)
.
* The gas limit and maximum message data length for CCIP messages have been adjusted on testnets. These changes are detailed in the [Service Limits](https://docs.chain.link/ccip/service-limits)
documentation.
* To interact with CCIP v1.2.0 , use the [@chainlink/contract-ccip](https://www.npmjs.com/package/@chainlink/contracts-ccip)
npm package.
[2023-11-17 - CCIP deprecation on Arbitrum Goerli](#2023-11-17-ccip-deprecation-on-arbitrum-goerli)
----------------------------------------------------------------------------------------------------
Arbitrum Goerli is no longer supported. Arbitrum Sepolia support will be added at a later date.
[2023-09-27 - CCIP on BNB Chain and Base mainnets](#2023-09-27-ccip-on-bnb-chain-and-base-mainnets)
----------------------------------------------------------------------------------------------------
Chainlink CCIP is publicly available on the BNB Chain and Base mainnets. See the [CCIP Directory](https://docs.chain.link/ccip/directory/mainnet)
page for more information.
[2023-09-21 - CCIP on Arbitrum Mainnet](#2023-09-21-ccip-on-arbitrum-mainnet)
------------------------------------------------------------------------------
Chainlink CCIP is publicly available on Arbitrum Mainnet. See the [CCIP Directory](https://docs.chain.link/ccip/directory/mainnet)
for more information.
[2023-08-25 - CCIP on BNB Chain and Base testnets](#2023-08-25-ccip-on-bnb-chain-and-base-testnets)
----------------------------------------------------------------------------------------------------
Chainlink CCIP is publicly available on the BNB Chain and Base mainnets. See the [CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
page for more information.
[2023-07-20 - CCIP Testnet GA release](#2023-07-20-ccip-testnet-ga-release)
----------------------------------------------------------------------------
Chainlink CCIP is publicly available on the following testnet chains:
* Ethereum Sepolia
* Optimism Goerli
* Avalanche Fuji
* Arbitrum Goerli
* Polygon Mumbai
See the [CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
page for more information.
[2023-07-17 - CCIP Testnet Early Access](#2023-07-17-ccip-testnet-early-access)
--------------------------------------------------------------------------------
Chainlink CCIP is available in early access on the following mainnet chains:
* Ethereum Sepolia
* Optimism Goerli
* Avalanche Fuji
* Arbitrum Goerli
* Polygon Mumbai
See the [CCIP Directory](https://docs.chain.link/ccip/directory/testnet)
page for more information.
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Using the CCIP JavaScript SDK | Chainlink Documentation
On this page
Using the CCIP JavaScript SDK
=============================
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](#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:
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:
Install SDK and run example appInstall SDK only
pnpm install
pnpm build
pnpm dev-example
pnpm install
* `pnpm dev-example` runs an example NextJS app locally. Navigate to [http://localhost:3000](http://localhost:3000)
in your browser.
[Run an example app](#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:
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](#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:
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
},
}
};
;
### [Theme configuration](#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:
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;
};
};}
[Review a CCIP-JS example](#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](#review-the-code)
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",
})
[Build packages](#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:
pnpm i -w
pnpm build-ccip-js
2. Build the `ccip-react-components` package:
pnpm build-components
3. 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:*"
4. 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",
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Transfer Tokens with Data | Chainlink Documentation
On this page
Transfer Tokens with Data
=========================
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](#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#mint-test-tokens)
. 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/transfer-tokens-from-contract)
.
[Tutorial](#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)
.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
/// @title - A simple messenger contract for transferring/receiving tokens and data across chains.
contract ProgrammableTokenTransfers is CCIPReceiver, OwnerIsCreator {
using SafeERC20 for IERC20;
// Custom errors to provide more descriptive revert messages.
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees.
error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails.
error DestinationChainNotAllowed(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner.
error SourceChainNotAllowed(uint64 sourceChainSelector); // Used when the source chain has not been allowlisted by the contract owner.
error SenderNotAllowed(address sender); // Used when the sender has not been allowlisted by the contract owner.
error InvalidReceiverAddress(); // Used when the receiver address is 0.
// Event emitted when a message is sent to another chain.
event MessageSent(
bytes32 indexed messageId, // The unique ID of the CCIP message.
uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
address receiver, // The address of the receiver on the destination chain.
string text, // The text being sent.
address token, // The token address that was transferred.
uint256 tokenAmount, // The token amount that was transferred.
address feeToken, // the token address used to pay CCIP fees.
uint256 fees // The fees paid for sending the message.
);
// Event emitted when a message is received from another chain.
event MessageReceived(
bytes32 indexed messageId, // The unique ID of the CCIP message.
uint64 indexed sourceChainSelector, // The chain selector of the source chain.
address sender, // The address of the sender from the source chain.
string text, // The text that was received.
address token, // The token address that was transferred.
uint256 tokenAmount // The token amount that was transferred.
);
bytes32 private s_lastReceivedMessageId; // Store the last received messageId.
address private s_lastReceivedTokenAddress; // Store the last received token address.
uint256 private s_lastReceivedTokenAmount; // Store the last received amount.
string private s_lastReceivedText; // Store the last received text.
// Mapping to keep track of allowlisted destination chains.
mapping(uint64 => bool) public allowlistedDestinationChains;
// Mapping to keep track of allowlisted source chains.
mapping(uint64 => bool) public allowlistedSourceChains;
// Mapping to keep track of allowlisted senders.
mapping(address => bool) public allowlistedSenders;
IERC20 private s_linkToken;
/// @notice Constructor initializes the contract with the router address.
/// @param _router The address of the router contract.
/// @param _link The address of the link contract.
constructor(address _router, address _link) CCIPReceiver(_router) {
s_linkToken = IERC20(_link);
}
/// @dev Modifier that checks if the chain with the given destinationChainSelector is allowlisted.
/// @param _destinationChainSelector The selector of the destination chain.
modifier onlyAllowlistedDestinationChain(uint64 _destinationChainSelector) {
if (!allowlistedDestinationChains[_destinationChainSelector])
revert DestinationChainNotAllowed(_destinationChainSelector);
_;
}
/// @dev Modifier that checks the receiver address is not 0.
/// @param _receiver The receiver address.
modifier validateReceiver(address _receiver) {
if (_receiver == address(0)) revert InvalidReceiverAddress();
_;
}
/// @dev Modifier that checks if the chain with the given sourceChainSelector is allowlisted and if the sender is allowlisted.
/// @param _sourceChainSelector The selector of the destination chain.
/// @param _sender The address of the sender.
modifier onlyAllowlisted(uint64 _sourceChainSelector, address _sender) {
if (!allowlistedSourceChains[_sourceChainSelector])
revert SourceChainNotAllowed(_sourceChainSelector);
if (!allowlistedSenders[_sender]) revert SenderNotAllowed(_sender);
_;
}
/// @dev Updates the allowlist status of a destination chain for transactions.
/// @notice This function can only be called by the owner.
/// @param _destinationChainSelector The selector of the destination chain to be updated.
/// @param allowed The allowlist status to be set for the destination chain.
function allowlistDestinationChain(
uint64 _destinationChainSelector,
bool allowed
) external onlyOwner {
allowlistedDestinationChains[_destinationChainSelector] = allowed;
}
/// @dev Updates the allowlist status of a source chain
/// @notice This function can only be called by the owner.
/// @param _sourceChainSelector The selector of the source chain to be updated.
/// @param allowed The allowlist status to be set for the source chain.
function allowlistSourceChain(
uint64 _sourceChainSelector,
bool allowed
) external onlyOwner {
allowlistedSourceChains[_sourceChainSelector] = allowed;
}
/// @dev Updates the allowlist status of a sender for transactions.
/// @notice This function can only be called by the owner.
/// @param _sender The address of the sender to be updated.
/// @param allowed The allowlist status to be set for the sender.
function allowlistSender(address _sender, bool allowed) external onlyOwner {
allowlistedSenders[_sender] = allowed;
}
/// @notice Sends data and transfer tokens to receiver on the destination chain.
/// @notice Pay for fees in LINK.
/// @dev Assumes your contract has sufficient LINK to pay for CCIP fees.
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param _receiver The address of the recipient on the destination blockchain.
/// @param _text The string data to be sent.
/// @param _token token address.
/// @param _amount token amount.
/// @return messageId The ID of the CCIP message that was sent.
function sendMessagePayLINK(
uint64 _destinationChainSelector,
address _receiver,
string calldata _text,
address _token,
uint256 _amount
)
external
onlyOwner
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
// address(linkToken) means fees are paid in LINK
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
_receiver,
_text,
_token,
_amount,
address(s_linkToken)
);
// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());
// Get the fee required to send the CCIP message
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > s_linkToken.balanceOf(address(this)))
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
s_linkToken.approve(address(router), fees);
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
IERC20(_token).approve(address(router), _amount);
// Send the message through the router and store the returned message ID
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);
// Emit an event with message details
emit MessageSent(
messageId,
_destinationChainSelector,
_receiver,
_text,
_token,
_amount,
address(s_linkToken),
fees
);
// Return the message ID
return messageId;
}
/// @notice Sends data and transfer tokens to receiver on the destination chain.
/// @notice Pay for fees in native gas.
/// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or POL on Polygon.
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param _receiver The address of the recipient on the destination blockchain.
/// @param _text The string data to be sent.
/// @param _token token address.
/// @param _amount token amount.
/// @return messageId The ID of the CCIP message that was sent.
function sendMessagePayNative(
uint64 _destinationChainSelector,
address _receiver,
string calldata _text,
address _token,
uint256 _amount
)
external
onlyOwner
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
// address(0) means fees are paid in native gas
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
_receiver,
_text,
_token,
_amount,
address(0)
);
// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());
// Get the fee required to send the CCIP message
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > address(this).balance)
revert NotEnoughBalance(address(this).balance, fees);
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
IERC20(_token).approve(address(router), _amount);
// Send the message through the router and store the returned message ID
messageId = router.ccipSend{value: fees}(
_destinationChainSelector,
evm2AnyMessage
);
// Emit an event with message details
emit MessageSent(
messageId,
_destinationChainSelector,
_receiver,
_text,
_token,
_amount,
address(0),
fees
);
// Return the message ID
return messageId;
}
/**
* @notice Returns the details of the last CCIP received message.
* @dev This function retrieves the ID, text, token address, and token amount of the last received CCIP message.
* @return messageId The ID of the last received CCIP message.
* @return text The text of the last received CCIP message.
* @return tokenAddress The address of the token in the last CCIP received message.
* @return tokenAmount The amount of the token in the last CCIP received message.
*/
function getLastReceivedMessageDetails()
public
view
returns (
bytes32 messageId,
string memory text,
address tokenAddress,
uint256 tokenAmount
)
{
return (
s_lastReceivedMessageId,
s_lastReceivedText,
s_lastReceivedTokenAddress,
s_lastReceivedTokenAmount
);
}
/// handle a received message
function _ccipReceive(
Client.Any2EVMMessage memory any2EvmMessage
)
internal
override
onlyAllowlisted(
any2EvmMessage.sourceChainSelector,
abi.decode(any2EvmMessage.sender, (address))
) // Make sure source chain and sender are allowlisted
{
s_lastReceivedMessageId = any2EvmMessage.messageId; // fetch the messageId
s_lastReceivedText = abi.decode(any2EvmMessage.data, (string)); // abi-decoding of the sent text
// Expect one token to be transferred at once, but you can transfer several tokens.
s_lastReceivedTokenAddress = any2EvmMessage.destTokenAmounts[0].token;
s_lastReceivedTokenAmount = any2EvmMessage.destTokenAmounts[0].amount;
emit MessageReceived(
any2EvmMessage.messageId,
any2EvmMessage.sourceChainSelector, // fetch the source chain identifier (aka selector)
abi.decode(any2EvmMessage.sender, (address)), // abi-decoding of the sender address,
abi.decode(any2EvmMessage.data, (string)),
any2EvmMessage.destTokenAmounts[0].token,
any2EvmMessage.destTokenAmounts[0].amount
);
}
/// @notice Construct a CCIP message.
/// @dev This function will create an EVM2AnyMessage struct with all the necessary information for programmable tokens transfer.
/// @param _receiver The address of the receiver.
/// @param _text The string data to be sent.
/// @param _token The token to be transferred.
/// @param _amount The amount of the token to be transferred.
/// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas.
/// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message.
function _buildCCIPMessage(
address _receiver,
string calldata _text,
address _token,
uint256 _amount,
address _feeTokenAddress
) private pure returns (Client.EVM2AnyMessage memory) {
// Set the token amounts
Client.EVMTokenAmount[]
memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount({
token: _token,
amount: _amount
});
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
return
Client.EVM2AnyMessage({
receiver: abi.encode(_receiver), // ABI-encoded receiver address
data: abi.encode(_text), // ABI-encoded string
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit and allowing out-of-order execution.
// Best Practice: For simplicity, the values are hardcoded. It is advisable to use a more dynamic approach
// where you set the extra arguments off-chain. This allows adaptation depending on the lanes, messages,
// and ensures compatibility with future CCIP upgrades. Read more about it here: https://docs.chain.link/ccip/best-practices#using-extraargs
Client.EVMExtraArgsV2({
gasLimit: 200_000, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
});
}
/// @notice Fallback function to allow the contract to receive Ether.
/// @dev This function has no function body, making it a default function for receiving Ether.
/// It is automatically called when Ether is sent to the contract without any data.
receive() external payable {}
/// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract.
/// @dev This function reverts if there are no funds to withdraw or if the transfer fails.
/// It should only be callable by the owner of the contract.
/// @param _beneficiary The address to which the Ether should be sent.
function withdraw(address _beneficiary) public onlyOwner {
// Retrieve the balance of this contract
uint256 amount = address(this).balance;
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
// Attempt to send the funds, capturing the success status and discarding any return data
(bool sent, ) = _beneficiary.call{value: amount}("");
// Revert if the send failed, with information about the attempted transfer
if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount);
}
/// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token.
/// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw.
/// @param _beneficiary The address to which the tokens will be sent.
/// @param _token The contract address of the ERC20 token to be withdrawn.
function withdrawToken(
address _beneficiary,
address _token
) public onlyOwner {
// Retrieve the balance of this contract
uint256 amount = IERC20(_token).balanceOf(address(this));
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
IERC20(_token).safeTransfer(_beneficiary, amount);
}
}
[Open in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/ProgrammableTokenTransfers.sol&autoCompile=true)
[What is Remix?](/getting-started/conceptual-overview#what-is-remix)
### [Deploy your contracts](#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](#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 | `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)
. |
| \_receiver | Your receiver contract address on _Ethereum Sepolia_.
The destination contract address. |
| \_text | `Hello World!`
Any `string` |
| \_token | `0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4`
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 | `1000000000000000`
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_.
3. Open the [CCIP explorer](https://ccip.chain.link/)
and search your cross-chain transaction using the transaction hash.

4. The CCIP transaction is completed once the status is marked as "Success". In this example, the CCIP message ID is _0x99a15381125e740c43a60f03c6b011ae05a3541998ca482fb5a4814417627df8_.

5. 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](#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 | `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)
. |
| \_receiver | Your receiver contract address at _Ethereum Sepolia_.
The destination contract address. |
| \_text | `Hello World!`
Any `string` |
| \_token | `0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4`
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 | `1000000000000000`
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_.
3. Open the [CCIP explorer](https://ccip.chain.link/)
and search your cross-chain transaction using the transaction hash.

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

5. 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](#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](#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/v1.5.1/i-router-client#getfee)
to estimate the CCIP fees.
* Calls the router's `ccipSend` [function](/ccip/api-reference/v1.5.1/i-router-client#ccipsend)
to send CCIP messages.
* Receiver part:
* The contract inherits from [CCIPReceiver](/ccip/api-reference/v1.5.1/ccip-receiver)
, which serves as a base contract for receiver contracts. This contract requires that child contracts implement the `_ccipReceive` [function](/ccip/api-reference/v1.5.1/ccip-receiver#_ccipreceive)
. `_ccipReceive` is called by the `ccipReceive` [function](/ccip/api-reference/v1.5.1/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](#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/v1.5.1/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/v1.5.1/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.
2. Computes the fees by invoking the router's `getFee` [function](/ccip/api-reference/v1.5.1/i-router-client#getfee)
.
3. Ensures your contract balance in LINK is enough to cover the fees.
4. Grants the router contract permission to deduct the fees from the contract's LINK balance.
5. Grants the router contract permission to deduct the amount from the contract's _CCIP-BnM_ balance.
6. Dispatches the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/v1.5.1/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](#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/v1.5.1/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/v1.5.1/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).
2. Computes the fees by invoking the router's `getFee` [function](/ccip/api-reference/v1.5.1/i-router-client#getfee)
.
3. Ensures your contract balance in native gas is enough to cover the fees.
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/v1.5.1/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](#receiving-messages)
On the destination blockchain, the router invokes the `_ccipReceive` [function](/ccip/api-reference/v1.5.1/ccip-receiver#_ccipreceive)
which expects a `Any2EVMMessage` [struct](/ccip/api-reference/v1.5.1/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/v1.5.1/ccip-receiver#ccipreceive)
, which ensures that only the router can deliver CCIP messages to the receiver contract. See the `onlyRouter` [modifier](/ccip/api-reference/v1.5.1/ccip-receiver#onlyrouter)
for more information.
* The modifier `onlyAllowlisted` ensures that only a call from an allowlisted source chain and sender is accepted.
What's next
-----------
* [\> Learn how to manually execute a failed CCIP transaction](/ccip/tutorials/manual-execution)
* [\> Learn how to handle errors gracefully when making CCIP transactions](/ccip/tutorials/programmable-token-transfers-defensive)
* [\> Transfer Tokens Between EOAs](/ccip/tutorials/transfer-tokens-from-eoa)
* [\> See example cross-chain dApps and tools](/ccip/examples)
* [\> CCIP Directory](/ccip/directory)
* [\> Learn CCIP best practices](/ccip/best-practices)
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Chainlink Data Feeds | Chainlink Documentation
On this page
Chainlink Data Feeds
====================
Chainlink Data Feeds are the quickest way to connect your smart contracts to real-world data such as asset prices, reserve balances, and L2 sequencer health.
If you already started a project and need to integrate Chainlink, you can [add Chainlink to your existing project](/resources/create-a-chainlinked-project?parent=dataFeeds#installing-into-existing-projects)
with the [`@chainlink/contracts` NPM package](https://www.npmjs.com/package/@chainlink/contracts)
.
[Types of data feeds](#types-of-data-feeds)
--------------------------------------------
Data feeds provide many different types of data for your applications.
* [Price Feeds](#price-feeds)
* [SmartData Feeds](#smartdata-feeds)
* [Rate and Volatility Feeds](#rate-and-volatility-feeds)
* [L2 sequencer uptime feeds](#l2-sequencer-uptime-feeds)
### [Price Feeds](#price-feeds)
Smart contracts often act in real-time on data such as prices of assets. This is especially true in [DeFi](https://chain.link/use-cases/defi)
.
For example, [Synthetix](https://www.synthetix.io/)
uses Data Feeds to determine prices on their derivatives platform. Lending and borrowing platforms like [AAVE](https://aave.com/)
use Data Feeds to ensure the total value of the collateral.
Data Feeds aggregate many data sources and publish them onchain using a combination of the [Decentralized Data Model](/architecture-overview/architecture-decentralized-model?parent=dataFeeds)
and [Offchain Reporting](/architecture-overview/off-chain-reporting?parent=dataFeeds)
.
To learn how to use Price Feeds, see the [Price Feeds](/data-feeds/price-feeds)
documentation.
See the [Data Feeds Contract Addresses](/data-feeds/price-feeds/addresses)
page for a list of available networks and addresses.
### [SmartData Feeds](#smartdata-feeds)
Chainlink SmartData is a suite of onchain data offerings designed to unlock the utility, accessibility, and reliability of tokenized real-world assets (RWAs). By providing secure minting assurances alongside essential real-world data such as reserves, Net Asset Value (NAV), and Assets Under Management (AUM) data, the SmartData suite embeds security and enriches data into tokenized RWA offerings.
To learn more about SmartData Feeds, see the [SmartData](/data-feeds/smartdata)
documentation.
See the [SmartData Contract Addresses](/data-feeds/smartdata/addresses)
page for a list of available networks and addresses.
### [Rate and Volatility Feeds](#rate-and-volatility-feeds)
Several feeds provide interest rate curve data, APY data, and realized asset price volatility.
To learn more, see the [Rate and Volatility Feeds](/data-feeds/rates-feeds)
documentation.
See the [Rate and Volatility Contract Addresses](/data-feeds/rates-feeds/addresses)
page for a list of available networks and addresses.
### [L2 sequencer uptime feeds](#l2-sequencer-uptime-feeds)
L2 sequencer feeds track the last known status of the sequencer on an L2 network at a given point in time. This helps you prevent mass liquidations by providing a grace period to allow customers to react to these events.
To learn how to use L2 sequencer uptime feeds, see the [L2 Sequencer Uptime Feeds](/data-feeds/l2-sequencer-feeds)
documentation.
[Components of a data feed](#components-of-a-data-feed)
--------------------------------------------------------
Data Feeds are an example of a decentralized oracle network and include the following components:
* **Consumer**: A consumer is an onchain or offchain application that uses Data Feeds. Consumer contracts use the [`AggregatorV3Interface`](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol)
to call functions on the proxy contract and retrieve information from the aggregator contract. For a complete list of functions available in the `AggregatorV3Interface`, see the [Data Feeds API Reference](/data-feeds/api-reference/#aggregatorv3interface)
.
* **Proxy contract**: Proxy contracts are onchain proxies that point to the aggregator for a particular data feed. Using proxies enables the underlying aggregator to be upgraded without any service interruption to consuming contracts. Proxy contracts can vary from one data feed to another, but the [`EACAggregatorProxy.sol` contract](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.0.0/contracts/src/v0.6/EACAggregatorProxy.sol)
on Github is a common example.
* **Aggregator contract**: An aggregator is a contract that receives periodic data updates from the oracle network. Aggregators store aggregated data onchain so that consumers can retrieve it and act upon it within the same transaction. For a complete list of functions and variables available on most aggregator contracts, see the [Data Feeds API Reference](/data-feeds/api-reference/#accesscontrolledoffchainaggregator)
.
To learn how to create a consumer contract that uses an existing data feed, read the [Using Data Feeds](/data-feeds/price-feeds)
documentation.
[Reading proxy and aggregator configurations](#reading-proxy-and-aggregator-configurations)
--------------------------------------------------------------------------------------------
Because the proxy and aggregator contracts are all onchain, you can see the current configuration by reading the variables through an [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html)
or using a blockchain explorer for your network. For example, you can see the [BTC/USD proxy configuration](https://etherscan.io/address/0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c#readContract)
on the Ethereum network using Etherscan.
If you read the BTC/USD proxy configuration, you can query all of the functions and variables that are publicly accessible for that contract including the `aggregator` address, `latestRoundData()` function, `latestAnswer` variable, `owner` address, `latestTimestamp` variable, and several others. To see descriptions for the proxy contract variables and functions, see the source code for your specific data feed on [Etherscan](https://etherscan.io/address/0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c#code#L568)
.
The proxy contract points to an aggregator. This allows you to retrieve data through the proxy even if the aggregator is upgraded. If you view the `aggregator` address defined in the proxy configuration, you can see the aggregator and its configuration. For example, see the [BTC/USD aggregator contract](https://etherscan.io/address/0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c#code)
in Etherscan. This contract includes several variables and functions, including another `latestRoundData()`. To see descriptions for the aggregator variables and functions, see the source code on [GitHub](https://github.com/smartcontractkit/libocr/blob/master/contract/AccessControlledOffchainAggregator.sol)
or [Etherscan](https://etherscan.io/address/0xAe74faA92cB67A95ebCAB07358bC222e33A34dA7#code#F1#L1)
.
You can call the `latestRoundData()` function directly on the aggregator, but it is a best practice to use the proxy instead so that changes to the aggregator do not affect your application. Similar to the proxy contract, the aggregator contract has a `latestAnswer` variable, `owner` address, `latestTimestamp` variable, and several others.
[Components of an aggregator](#components-of-an-aggregator)
------------------------------------------------------------
The aggregator contract has several variables and functions that might be useful for your application. Although aggregator contracts are similar for each data feed, some aggregators have different variables. Use the `typeAndVersion()` function on the aggregator to identify what type of aggregator it is and what version it is running.
Always check the contract source code and configuration to understand how specific data feeds operate. For example, the [aggregator contract for BTC/USD on Arbitrum](https://arbiscan.io/address/0x942d00008D658dbB40745BBEc89A93c253f9B882#code)
is different from the aggregators on other networks.
For examples of the contracts that are typically used in aggregator deployments, see the [libocr repository](https://github.com/smartcontractkit/libocr/blob/master/contract/)
on GitHub.
For a complete list of functions and variables available on most aggregator contracts, see the [Data Feeds API Reference](/data-feeds/api-reference/#accesscontrolledoffchainaggregator)
.
[Updates to proxy and aggregator contracts](#updates-to-proxy-and-aggregator-contracts)
----------------------------------------------------------------------------------------
To accommodate the dynamic nature of offchain environments, Chainlink Data Feeds are updated from time to time to add new features and capabilities as well as respond to externalities such as token migrations, protocol rebrands, extreme market events, and upstream issues with data or node operations.
These updates include changes to the aggregator configuration or a complete replacement of the aggregator that the proxy uses. If you consume data feeds through the proxy, your applications can continue to operate during these changes.
Proxy and aggregator contracts all have an `owner` address that has permission to change variables and functions. For example, if you read the [BTC/USD proxy contract](https://etherscan.io/address/0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c#readContract)
in Etherscan, you can see the `owner` address. This address is a [multi-signature safe](https://docs.safe.global/getting-started/readme)
(multisig) that you can also inspect.
If you [view the multisig contract](https://etherscan.io/address/0x21f73D42Eb58Ba49dDB685dc29D3bF5c0f0373CA#readProxyContract)
in Etherscan using the _Read as Proxy_ feature, you can see the full details of the multisig including the list of addresses that can sign and the number of signers required for the multisig to approve actions on any contracts that it owns.
The multisig-coordinated upgradability of Chainlink Data Feeds involves time-tested processes that balance collusion-resistance with the flexibility required to implement improvements and swiftly react to external conditions. The approach taken to upgradability will continue to evolve over time to meet user requirements.
[Monitoring data feeds](#monitoring-data-feeds)
------------------------------------------------
When you build applications and protocols that depend on data feeds, include monitoring and safeguards to protect against the negative impact of extreme market events, possible malicious activity on third-party venues or contracts, potential delays, and outages.
Create your own monitoring alerts based on deviations in the answers that data feeds provide. This will notify you when potential issues occur so you can respond to them.
### [Check the latest answer against reasonable limits](#check-the-latest-answer-against-reasonable-limits)
The data feed aggregator includes both [`minAnswer` and `maxAnswer` values](https://github.com/smartcontractkit/libocr/blob/9e4afd8896f365b964bdf769ca28f373a3fb0300/contract/AccessControlledOffchainAggregator.sol#L33)
. On most data feeds, these values are no longer used and they do not stop your application from reading the most recent answer. For monitoring purposes, you must decide what limits are acceptable for your application.
Configure your application to detect when the reported answer is close to reaching reasonable minimum and maximum limits so it can alert you to potential market events. Separately, configure your application to detect and respond to extreme price volatility or prices that are outside of your acceptable limits.
### [Check the timestamp of the latest answer](#check-the-timestamp-of-the-latest-answer)
Chainlink Data Feeds do not provide streaming data. Rather, the aggregator updates its `latestAnswer` when the value deviates beyond a specified threshold or when the heartbeat idle time has passed. You can find the heartbeat and deviation values for each data feed at [data.chain.link](https://data.chain.link/)
or in the [Contract Addresses](/data-feeds/price-feeds/addresses)
lists.
Your application should track the `latestTimestamp` variable or use the `updatedAt` value from the `latestRoundData()` function to make sure that the latest answer is recent enough for your application to use it. If your application detects that the reported answer is not updated within the heartbeat or within time limits that you determine are acceptable for your application, pause operation or switch to an alternate operation mode while identifying the cause of the delay.
When the node detects that the heartbeat is reached, it initiates the latest round. Depending on congestion and network conditions, there may be a slight delay for the latest round to get onchain.
During periods of low volatility, the heartbeat triggers updates to the latest answer. Some heartbeats are configured to last several hours, so your application should check the timestamp and verify that the latest answer is recent enough for your application.
Users should build applications with the understanding that data feeds for wrapped or liquid staking assets might have different heartbeat and deviation thresholds than that of the underlying asset. Heartbeat and deviation thresholds can also differ for the same asset across different blockchains. Combining data from multiple feeds, even those with a common denominator, might result in a margin of error that users must account for in their risk mitigation practices.
To learn more about the heartbeat and deviation threshold, read the [Decentralized Data Model](/architecture-overview/architecture-decentralized-model?parent=dataFeeds#aggregator)
page.
What's next
-----------
* [\> Follow the Data Feeds Getting Started guide to learn the basics](/data-feeds/getting-started)
* [\> Read the API reference for using Data Feeds](/data-feeds/api-reference)
* [\> Find Price Feed Addresses](/data-feeds/price-feeds/addresses)
* [\> Find SmartData Addresses](/data-feeds/smartdata/addresses)
* [\> Find Rate and Volatility Feed Addresses](/data-feeds/rates-feeds/addresses)
* [\> Learn how to use Data Feeds on L2 networks](/data-feeds/l2-sequencer-feeds)
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Enable your tokens in CCIP (Burn & Mint): Register from an EOA using Hardhat | Chainlink Documentation
On this page
Enable your tokens in CCIP (Burn & Mint): Register from an EOA using Hardhat
============================================================================
Guide Versions
--------------
This guide is available in multiple versions. Choose the one that matches your needs.
Hardhat (Burn & Mint)
[Hardhat (Burn & Mint)](/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-hardhat)
[Foundry (Burn & Mint)](/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-foundry)
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 [`BurnMintERC677`](https://github.com/smartcontractkit/ccip/tree/release/contracts-ccip-1.5.1/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.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/v1.5.1/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/ccip/blob/f8d2f981e54aa7eb739d21ab925a954e043da9b6/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.sol#L141)
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/v1.5.1/registry-module-owner-custom)
contract's [`registerAdminViaOwner`](/ccip/api-reference/v1.5.1/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/v1.5.1/token-admin-registry)
contract's [`acceptAdminRole`](/ccip/api-reference/v1.5.1/token-admin-registry#acceptadminrole)
function to complete the registration process.
5. **Linking Tokens to Pools**: You will call the [`TokenAdminRegistry`](/ccip/api-reference/v1.5.1/token-admin-registry)
contract's [`setPool`](/ccip/api-reference/v1.5.1/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/v1.5.1/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/ccip/blob/f8d2f981e54aa7eb739d21ab925a954e043da9b6/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.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](#before-you-begin)
--------------------------------------
1. Make sure you have Node.js v18 or above installed. If not, **install Node.js v18**:
[Download Node.js 18](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:
nvm use 18
Verify that the correct version of Node.js is installed:
node -v
Example output:
$ node -v
v18.7.0
2. **Clone the repository and navigate to the project directory:**
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
3. **Install dependencies for the project:**
npm install
4. **Compile the project:**
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:
npx env-enc set-pw
2. Set up a `.env.enc` file with the necessary variables for Avalanche Fuji and Arbitrum Sepolia testnets. Use the following command to add the variables:
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/)
.
* `ARBITRUM_SEPOLIA_RPC_URL`: A URL for the _Arbitrum 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)
.
* `ARBISCAN_API_KEY`: An Arbitrum explorer API key, used to verify your contract. Follow [this guide](https://docs.arbiscan.io/getting-started/viewing-api-usage-statistics)
to get one from Arbiscan.
6. **Fund your EOA with LINK and native gas tokens**:
Make sure your EOA has enough LINK and 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](#tutorial)
----------------------
### [Deploy Tokens](#deploy-tokens)
In this step, you will use the `deployToken.ts` task to deploy tokens on two testnets, Avalanche Fuji and Arbitrum 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 |
| `withgetccipadmin` | A flag to determine whether the token contract has a `getCCIPAdmin()` function. If set to `true`, a CCIP admin is required. When `false`, token admin registration will use the token `owner()` function. | `false` | No |
| `ccipadminaddress` | The address of the CCIP admin, only applicable if `withgetccipadmin` is set to `true`. | N/A | No |
| `verifycontract` | Whether to verify the contract on Etherscan or a similar blockchain explorer. | `false` | No |
| `network` | The blockchain on which the token will be deployed. Examples include `avalancheFuji`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | N/A | Yes |
Deploy tokens, use the following commands, substituting the token name and symbol as needed:
1. **Deploy the token on Avalanche Fuji:**
npx hardhat deployToken --name "BnM aem" --symbol BnMaem --decimals 18 --maxsupply 0 --withgetccipadmin false --verifycontract true --network avalancheFuji
Example output:
2024-12-02T20:20:30.258Z info: Deploying BurnMintERC677 contract to avalancheFuji
2024-12-02T20:20:30.259Z info: Waiting 2 blocks for transaction 0x6839605601ff18d81fd8a2ec29d86b212eb0879913e6e59e1b839f0ef1be5c06 to be confirmed...
2024-12-02T20:20:33.120Z info: Token deployed to: 0x16F6b0f41b9217857551e29F38F99975a2fc9add
2024-12-02T20:20:33.226Z info: Granting mint and burn roles to 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T20:20:49.308Z info: Verifying contract...
The contract 0x16F6b0f41b9217857551e29F38F99975a2fc9add has already been verified on the block explorer. If you're trying to verify a partially verified contract, please use the --force flag.
https://testnet.snowtrace.io/address/0x16F6b0f41b9217857551e29F38F99975a2fc9add#code
2024-12-02T20:20:50.247Z info: Token contract deployed and verified
2. **Deploy the token on Arbitrum Sepolia:**
npx hardhat deployToken --name "BnM aem" --symbol BnMaem --decimals 18 --maxsupply 0 --withgetccipadmin false --verifycontract true --network arbitrumSepolia
Example output:
2024-12-02T20:22:17.498Z info: Deploying BurnMintERC677 contract to arbitrumSepolia
2024-12-02T20:22:17.498Z info: Waiting 2 blocks for transaction 0x15787a9f99ff1b3d36f601564630e49b4cf4c9d102d7e4343465fa99ae684645 to be confirmed...
2024-12-02T20:22:18.155Z info: Token deployed to: 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30
2024-12-02T20:22:18.198Z info: Granting mint and burn roles to 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T20:22:19.398Z info: Verifying contract...
The contract 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30 has already been verified on the block explorer. If you're trying to verify a partially verified contract, please use the --force flag.
https://sepolia.arbiscan.io/address/0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30#code
2024-12-02T20:22:19.952Z info: Token contract deployed and verified
### [Deploy Token Pools](#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 Arbitrum 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 |
| `network` | The blockchain on which the token pool will be deployed. Examples include `avalancheFuji`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | N/A | Yes |
| `verifycontract` | Whether to verify the contract on Etherscan or a similar blockchain explorer. | `false` | No |
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:**
npx hardhat deployTokenPool \
--tokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add \
--pooltype burnMint \
--localtokendecimals 18 \
--verifycontract true \
--network avalancheFuji
Example output:
2024-12-02T20:26:17.039Z info: Waiting 2 blocks for transaction 0x081466b6ef4a32095f726939ecb0f0e7e8bbb7ae88b85f9f0f3d3fdb3aa3a9ad to be confirmed...
2024-12-02T20:26:20.917Z info: Token pool deployed to: 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9
2024-12-02T20:26:20.918Z info: Granting mint and burn roles to 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9 on token 0x16F6b0f41b9217857551e29F38F99975a2fc9add
2024-12-02T20:26:40.032Z info: Verifying contract...
Successfully submitted source code for contract
@chainlink/contracts-ccip/src/v0.8/ccip/pools/BurnMintTokenPool.sol:BurnMintTokenPool at 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9
for verification on the block explorer. Waiting for verification result...
Successfully verified contract BurnMintTokenPool on the block explorer.
https://testnet.snowtrace.io/address/0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9#code
2024-12-02T20:26:58.195Z info: Token pool contract deployed and verified
2. **Deploy the burn and mint token pool on Arbitrum Sepolia:**
npx hardhat deployTokenPool \
--tokenaddress 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30 \
--pooltype burnMint \
--localtokendecimals 18 \
--verifycontract true \
--network arbitrumSepolia
Example output:
2024-12-02T20:27:22.065Z info: Waiting 2 blocks for transaction 0x0f65d531ca93a2a678923c14feaf6c36cb7023353e54f28152c01f93d5fd3031 to be confirmed...
2024-12-02T20:27:22.734Z info: Token pool deployed to: 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65
2024-12-02T20:27:22.734Z info: Granting mint and burn roles to 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65 on token 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30
2024-12-02T20:27:25.269Z info: Verifying contract...
Successfully submitted source code for contract
@chainlink/contracts-ccip/src/v0.8/ccip/pools/BurnMintTokenPool.sol:BurnMintTokenPool at 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65
for verification on the block explorer. Waiting for verification result...
Successfully verified contract BurnMintTokenPool on the block explorer.
https://sepolia.arbiscan.io/address/0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65#code
2024-12-02T20:27:32.830Z info: Token pool contract deployed and verified
### [Claim Admin](#claim-admin)
In this step, you will use the `claimAdmin.ts` task to register your EOA as the administrator for the deployed tokens on both testnets, Avalanche Fuji and Arbitrum Sepolia. This process involves calling the `RegistryModuleOwnerCustom` contract, which will fetch the token owner and set it up as the admin.
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 |
| `withccipadmin` | A flag indicating whether the token contract has a CCIP admin. If `false`, the token admin is registered using the `owner()` function. | `false` | No |
| `network` | The blockchain on which the claim admin process will be executed. Examples include `avalancheFuji`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | 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:**
npx hardhat claimAdmin --tokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add --withccipadmin false --network avalancheFuji
Example output:
2024-12-02T20:41:40.306Z info: Accepted admin role for token 0x16F6b0f41b9217857551e29F38F99975a2fc9add tx: 0xb771c2cdcd87c69a50571cf4b78dce0f3200b59f3252fb5e46146ff2e39c7a69
2. **Claim the admin role on Arbitrum Sepolia:**
npx hardhat claimAdmin --tokenaddress 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30 --withccipadmin false --network arbitrumSepolia
Example output:
2024-12-02T20:42:00.700Z info: Accepted admin role for token 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30 tx: 0xf584810d48cdf79544ed5f80644b35a4ab2357925f2f040f802e1a3447a0b272
### [Accept Admin Role](#accept-admin-role)
In this step, you will use the `acceptAdminRole.ts` task to accept the admin role for the deployed tokens on both testnets, Avalanche Fuji and Arbitrum 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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | 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:**
npx hardhat acceptAdminRole --tokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add --network avalancheFuji
Example output:
2024-12-02T20:41:40.306Z info: Accepted admin role for token 0x16F6b0f41b9217857551e29F38F99975a2fc9add tx: 0xb771c2cdcd87c69a50571cf4b78dce0f3200b59f3252fb5e46146ff2e39c7a69
2. **Accept the admin role on Arbitrum Sepolia:**
npx hardhat acceptAdminRole --tokenaddress 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30 --network arbitrumSepolia
Example output:
2024-12-02T20:42:00.700Z info: Accepted admin role for token 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30 tx: 0xf584810d48cdf79544ed5f80644b35a4ab2357925f2f040f802e1a3447a0b272
### [Set Pool](#set-pool)
In this step, you will use the `setPool.ts` 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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | 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:**
npx hardhat setPool --tokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add --pooladdress 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9 --network avalancheFuji
Example output:
2024-12-02T20:45:49.006Z info: Setting pool for token 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9 by 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T20:45:54.093Z info: Pool set for token 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9
2. **Set the pool for Arbitrum Sepolia:**
npx hardhat setPool --tokenaddress 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30 --pooladdress 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65 --network arbitrumSepolia
Example output:
2024-12-02T20:46:12.139Z info: Setting pool for token 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30 to 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65 by 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T20:46:12.785Z info: Pool set for token 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30 to 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65
### [Configure Token Pools](#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 Arbitrum 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., `arbitrumSepolia` 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` | Enables or disables the outbound rate limiter. | `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` | Enables or disables the inbound rate limiter. | `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:**
npx hardhat applyChainUpdates \
--pooladdress 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9 \
--remotechain arbitrumSepolia \
--remotepooladdresses 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65 \
--remotetokenaddress 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30 \
--network avalancheFuji
Example output:
2024-12-02T20:48:33.304Z info: Applying chain update to pool at address: 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9
2024-12-02T20:48:33.304Z info: Remote chain: arbitrumSepolia (3478487238524512106)
2024-12-02T20:48:33.304Z info: Remote pool addresses: 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65
2024-12-02T20:48:33.304Z info: Remote token address: 0x79b6C325D7f952ddC61Cc51D554cBf4099ca8b30
2024-12-02T20:48:40.430Z info: Chain update applied successfully
2. **Configure the pool on Arbitrum Sepolia:**
npx hardhat applyChainUpdates \
--pooladdress 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65 \
--remotechain avalancheFuji \
--remotetokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add \
--remotepooladdresses 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9 \
--network arbitrumSepolia
Example output:
2024-12-02T20:50:22.850Z info: Applying chain update to pool at address: 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65
2024-12-02T20:50:22.850Z info: Remote chain: avalancheFuji (14767482510784806043)
2024-12-02T20:50:22.850Z info: Remote pool addresses: 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9
2024-12-02T20:50:22.850Z info: Remote token address: 0x16F6b0f41b9217857551e29F38F99975a2fc9add
2024-12-02T20:50:25.237Z info: Chain update applied successfully
### [Mint Tokens](#mint-tokens)
In this step, you will use the `mintTokens.ts` 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 `BurnMintERC677` 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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | 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:**
npx hardhat mintTokens --tokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add --amount 1000000000000000000000 --network avalancheFuji
Example output:
2024-12-02T20:52:58.723Z info: Minting 1000000000000000000000 of BnMaem tokens to 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T20:53:03.388Z info: Minted 1000000000000000000000 of BnMaem tokens to 0x9d087fC03ae39b088326b67fA3C788236645b717 - transaction hash: 0x72803df594f0ff01cf22437722601fc3a41afc4463d05598ad56fd85eef26671
2024-12-02T20:53:03.601Z info: Current balance of 0x9d087fC03ae39b088326b67fA3C788236645b717 is 1000000000000000000000 BnMaem
### [Transfer Tokens](#transfer-tokens)
In this step, you will use the `transferTokens` task to transfer tokens from Avalanche Fuji to Arbitrum 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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | 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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | N/A | Yes |
#### [Pay fees in LINK](#pay-fees-in-link)
Call the CCIP Router to transfer tokens from Avalanche Fuji to Arbitrum Sepolia, paying the CCIP fees in LINK tokens. Replace the token address, amount, receiver address, and blockchain with the appropriate values:
npx hardhat transferTokens --tokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add --amount 100 --destinationchain arbitrumSepolia --receiveraddress 0x9d087fC03ae39b088326b67fA3C788236645b717 --network avalancheFuji
Example output:
2024-12-02T20:54:39.129Z info: Estimated fees: 19264384915635927
2024-12-02T20:54:39.132Z info: Approving 100 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-02T20:54:50.192Z info: Approving 19264384915635927 LINK to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-02T20:55:06.526Z info: Transferring 100 of 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia with 19264384915635927 of LINK as fees
2024-12-02T20:55:12.517Z info: Transaction hash: 0x5edd3a0144d0cae8c63559285fa5a1985d44c037c72434176aa600cc08cf727e
2024-12-02T20:55:12.535Z info: Message dispatched. Message id: 0xbafe82be04659a3064e1a1e08a2bc9d156968769ed224e100a49d0a6bc99f3f1
2024-12-02T20:55:12.535Z info: ✅ Transferred 100 of 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia. Transaction hash: 0x5edd3a0144d0cae8c63559285fa5a1985d44c037c72434176aa600cc08cf727e - CCIP message id: 0xbafe82be04659a3064e1a1e08a2bc9d156968769ed224e100a49d0a6bc99f3f1
2024-12-02T20:55:12.535Z info: Check status of message on https://ccip.chain.link/msg/0xbafe82be04659a3064e1a1e08a2bc9d156968769ed224e100a49d0a6bc99f3f1
#### [Pay fees in native gas tokens](#pay-fees-in-native-gas-tokens)
Call the CCIP Router to transfer tokens from Avalanche Fuji to Arbitrum Sepolia, paying the CCIP fees in native gas tokens. Replace the token address, amount, receiver address, and blockchain with the appropriate values:
npx hardhat transferTokens --tokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add --amount 100 --destinationchain arbitrumSepolia --receiveraddress 0x9d087fC03ae39b088326b67fA3C788236645b717 --fee native --network avalancheFuji
Example output:
2024-12-02T20:57:28.842Z info: Estimated fees: 10345327607939136
2024-12-02T20:57:28.844Z info: Approving 100 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-02T20:57:34.157Z info: Transferring 100 of 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia with 10345327607939136 of native token as fees
2024-12-02T20:57:45.269Z info: Transaction hash: 0xa68375fc560f8910b1c6dda8d6765d0bebd02e2de91f763188e529b5eb007ab4
2024-12-02T20:57:45.288Z info: Message dispatched. Message id: 0xdbf8a93ed33c726d866478ef9c492b5dbba65eb62192a42b63743ce3af86e443
2024-12-02T20:57:45.288Z info: ✅ Transferred 100 of 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia. Transaction hash: 0xa68375fc560f8910b1c6dda8d6765d0bebd02e2de91f763188e529b5eb007ab4 - CCIP message id: 0xdbf8a93ed33c726d866478ef9c492b5dbba65eb62192a42b63743ce3af86e443
2024-12-02T20:57:45.288Z info: Check status of message on https://ccip.chain.link/msg/0xdbf8a93ed33c726d866478ef9c492b5dbba65eb62192a42b63743ce3af86e443
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Transfer Tokens | Chainlink Documentation
On this page
Transfer Tokens
===============
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](#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#mint-test-tokens)
. 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](#tutorial)
----------------------
In this tutorial, you will transfer [CCIP-BnM](/ccip/test-tokens#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#lane)
.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
/// @title - A simple contract for transferring tokens across chains.
contract TokenTransferor is OwnerIsCreator {
using SafeERC20 for IERC20;
// Custom errors to provide more descriptive revert messages.
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees.
error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails.
error DestinationChainNotAllowlisted(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner.
error InvalidReceiverAddress(); // Used when the receiver address is 0.
// Event emitted when the tokens are transferred to an account on another chain.
event TokensTransferred(
bytes32 indexed messageId, // The unique ID of the message.
uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
address receiver, // The address of the receiver on the destination chain.
address token, // The token address that was transferred.
uint256 tokenAmount, // The token amount that was transferred.
address feeToken, // the token address used to pay CCIP fees.
uint256 fees // The fees paid for sending the message.
);
// Mapping to keep track of allowlisted destination chains.
mapping(uint64 => bool) public allowlistedChains;
IRouterClient private s_router;
IERC20 private s_linkToken;
/// @notice Constructor initializes the contract with the router address.
/// @param _router The address of the router contract.
/// @param _link The address of the link contract.
constructor(address _router, address _link) {
s_router = IRouterClient(_router);
s_linkToken = IERC20(_link);
}
/// @dev Modifier that checks if the chain with the given destinationChainSelector is allowlisted.
/// @param _destinationChainSelector The selector of the destination chain.
modifier onlyAllowlistedChain(uint64 _destinationChainSelector) {
if (!allowlistedChains[_destinationChainSelector])
revert DestinationChainNotAllowlisted(_destinationChainSelector);
_;
}
/// @dev Modifier that checks the receiver address is not 0.
/// @param _receiver The receiver address.
modifier validateReceiver(address _receiver) {
if (_receiver == address(0)) revert InvalidReceiverAddress();
_;
}
/// @dev Updates the allowlist status of a destination chain for transactions.
/// @notice This function can only be called by the owner.
/// @param _destinationChainSelector The selector of the destination chain to be updated.
/// @param allowed The allowlist status to be set for the destination chain.
function allowlistDestinationChain(
uint64 _destinationChainSelector,
bool allowed
) external onlyOwner {
allowlistedChains[_destinationChainSelector] = allowed;
}
/// @notice Transfer tokens to receiver on the destination chain.
/// @notice pay in LINK.
/// @notice the token must be in the list of supported tokens.
/// @notice This function can only be called by the owner.
/// @dev Assumes your contract has sufficient LINK tokens to pay for the fees.
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param _receiver The address of the recipient on the destination blockchain.
/// @param _token token address.
/// @param _amount token amount.
/// @return messageId The ID of the message that was sent.
function transferTokensPayLINK(
uint64 _destinationChainSelector,
address _receiver,
address _token,
uint256 _amount
)
external
onlyOwner
onlyAllowlistedChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
// address(linkToken) means fees are paid in LINK
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
_receiver,
_token,
_amount,
address(s_linkToken)
);
// Get the fee required to send the message
uint256 fees = s_router.getFee(
_destinationChainSelector,
evm2AnyMessage
);
if (fees > s_linkToken.balanceOf(address(this)))
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
s_linkToken.approve(address(s_router), fees);
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
IERC20(_token).approve(address(s_router), _amount);
// Send the message through the router and store the returned message ID
messageId = s_router.ccipSend(
_destinationChainSelector,
evm2AnyMessage
);
// Emit an event with message details
emit TokensTransferred(
messageId,
_destinationChainSelector,
_receiver,
_token,
_amount,
address(s_linkToken),
fees
);
// Return the message ID
return messageId;
}
/// @notice Transfer tokens to receiver on the destination chain.
/// @notice Pay in native gas such as ETH on Ethereum or POL on Polygon.
/// @notice the token must be in the list of supported tokens.
/// @notice This function can only be called by the owner.
/// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or POL on Polygon.
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param _receiver The address of the recipient on the destination blockchain.
/// @param _token token address.
/// @param _amount token amount.
/// @return messageId The ID of the message that was sent.
function transferTokensPayNative(
uint64 _destinationChainSelector,
address _receiver,
address _token,
uint256 _amount
)
external
onlyOwner
onlyAllowlistedChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
// address(0) means fees are paid in native gas
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
_receiver,
_token,
_amount,
address(0)
);
// Get the fee required to send the message
uint256 fees = s_router.getFee(
_destinationChainSelector,
evm2AnyMessage
);
if (fees > address(this).balance)
revert NotEnoughBalance(address(this).balance, fees);
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
IERC20(_token).approve(address(s_router), _amount);
// Send the message through the router and store the returned message ID
messageId = s_router.ccipSend{value: fees}(
_destinationChainSelector,
evm2AnyMessage
);
// Emit an event with message details
emit TokensTransferred(
messageId,
_destinationChainSelector,
_receiver,
_token,
_amount,
address(0),
fees
);
// Return the message ID
return messageId;
}
/// @notice Construct a CCIP message.
/// @dev This function will create an EVM2AnyMessage struct with all the necessary information for tokens transfer.
/// @param _receiver The address of the receiver.
/// @param _token The token to be transferred.
/// @param _amount The amount of the token to be transferred.
/// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas.
/// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message.
function _buildCCIPMessage(
address _receiver,
address _token,
uint256 _amount,
address _feeTokenAddress
) private pure returns (Client.EVM2AnyMessage memory) {
// Set the token amounts
Client.EVMTokenAmount[]
memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount({
token: _token,
amount: _amount
});
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
return
Client.EVM2AnyMessage({
receiver: abi.encode(_receiver), // ABI-encoded receiver address
data: "", // No data
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit and allowing out-of-order execution.
// Best Practice: For simplicity, the values are hardcoded. It is advisable to use a more dynamic approach
// where you set the extra arguments off-chain. This allows adaptation depending on the lanes, messages,
// and ensures compatibility with future CCIP upgrades. Read more about it here: https://docs.chain.link/ccip/best-practices#using-extraargs
Client.EVMExtraArgsV2({
gasLimit: 0, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
});
}
/// @notice Fallback function to allow the contract to receive Ether.
/// @dev This function has no function body, making it a default function for receiving Ether.
/// It is automatically called when Ether is transferred to the contract without any data.
receive() external payable {}
/// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract.
/// @dev This function reverts if there are no funds to withdraw or if the transfer fails.
/// It should only be callable by the owner of the contract.
/// @param _beneficiary The address to which the Ether should be transferred.
function withdraw(address _beneficiary) public onlyOwner {
// Retrieve the balance of this contract
uint256 amount = address(this).balance;
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
// Attempt to send the funds, capturing the success status and discarding any return data
(bool sent, ) = _beneficiary.call{value: amount}("");
// Revert if the send failed, with information about the attempted transfer
if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount);
}
/// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token.
/// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw.
/// @param _beneficiary The address to which the tokens will be sent.
/// @param _token The contract address of the ERC20 token to be withdrawn.
function withdrawToken(
address _beneficiary,
address _token
) public onlyOwner {
// Retrieve the balance of this contract
uint256 amount = IERC20(_token).balanceOf(address(this));
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
IERC20(_token).safeTransfer(_beneficiary, amount);
}
}
[Open in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/TokenTransferor.sol&autoCompile=true)
[What is Remix?](/getting-started/conceptual-overview#what-is-remix)
### [Deploy your contracts](#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](#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 | `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)
. |
| \_receiver | Your account address on _Ethereum Sepolia_.
The destination account address. It could be a smart contract or an EOA. |
| \_token | `0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4`
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 | `1000000000000000`
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_.
3. Open the [CCIP explorer](https://ccip.chain.link/)
and search your cross-chain transaction using the transaction hash.

4. The CCIP transaction is completed once the status is marked as "Success". The data field is empty because you are only transferring tokens.

5. 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](#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 | `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)
. |
| \_receiver | Your account address on _Ethereum Sepolia_.
The destination account address. It could be a smart contract or an EOA. |
| \_token | `0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4`
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 | `1000000000000000`
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_.
3. Open the [CCIP explorer](https://ccip.chain.link/)
and search your cross-chain transaction using the transaction hash.

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

5. 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](#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](#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](#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/v1.5.1/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/v1.5.1/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.
2. Computes the fees by invoking the router's `getFee` [function](/ccip/api-reference/v1.5.1/i-router-client#getfee)
.
3. Ensures your contract balance in LINK is enough to cover the fees.
4. Grants the router contract permission to deduct the fees from the contract's LINK balance.
5. Grants the router contract permission to deduct the amount from the contract's _CCIP-BnM_ balance.
6. Dispatches the CCIP message to the destination chain by executing the router's `ccipSend` [function](/ccip/api-reference/v1.5.1/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](#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/v1.5.1/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/v1.5.1/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).
2. Computes the fees by invoking the router's `getFee` [function](/ccip/api-reference/v1.5.1/i-router-client#getfee)
.
3. Ensures your contract balance in native gas is enough to cover the fees.
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/v1.5.1/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.
What's next
-----------
* [\> Learn how to transfer tokens and send data in a single CCIP transaction](/ccip/tutorials/programmable-token-transfers)
* [\> See example cross-chain dApps and tools](/ccip/examples)
* [\> CCIP Directory](/ccip/directory)
* [\> Learn CCIP best practices](/ccip/best-practices)
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Cross-Chain Token (CCT) Tutorials | Chainlink Documentation
On this page
Cross-Chain Token (CCT) Tutorials
=================================
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](#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:](#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-tokens#requirements-for-cross-chain-tokens)
.
2. **Admin Registration**: The token administrator must be registered in the [`TokenAdminRegistry`](/ccip/api-reference/v1.5.1/token-admin-registry)
via self-service.
3. **Pool Deployment and Configuration**: [Token pools](/ccip/concepts/cross-chain-tokens#requirements-for-token-pools)
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](#tutorials)
------------------------
* [Deploy Using Remix IDE](/ccip/tutorials/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/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/cross-chain-tokens/register-from-eoa-burn-mint-hardhat)
* [Foundry version](/ccip/tutorials/cross-chain-tokens/register-from-eoa-burn-mint-foundry)
* [Register from an EOA (Lock & Mint)](/ccip/tutorials/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/cross-chain-tokens/register-from-eoa-lock-mint-hardhat)
* [Foundry version](/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-foundry)
* [Set Token Pool Rate Limits](/ccip/tutorials/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/cross-chain-tokens/update-rate-limiters-hardhat)
* [Foundry version](/ccip/tutorials/cross-chain-tokens/update-rate-limiters-foundry)
* [Register from a Safe (Burn & Mint)](/ccip/tutorials/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/cross-chain-tokens/register-from-safe-burn-mint-hardhat)
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Deploy & Register Cross-Chain Tokens with Remix IDE | Chainlink Documentation
On this page
Deploy & Register Cross-Chain Tokens with Remix IDE
===================================================
[Overview](#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](#tutorial-structure)
------------------------------------------
This interactive tutorial provides a structured learning experience:
### [Progress Tracking](#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](#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](#tools-needed)
------------------------------
### [Web Browser](#web-browser)
Any modern web browser
### [Remix IDE](#remix-ide)
[Remix IDE](https://remix.ethereum.org)
- Browser-based Ethereum IDE for smart contract development and deployment
### [MetaMask](#metamask)
[MetaMask](https://metamask.io/)
- Blockchain wallet for connecting to blockchains and signing transactions
[Before You Begin](#before-you-begin)
--------------------------------------
Prerequisites
Complete these steps before starting the tutorial
#### 1\. Web Browser Setup
Configure your browser with the required extensions and networks
Web Browser Setup
▼
#### Using Chainlist (Recommended)
* Visit Chainlist
* Search for your desired blockchains
* Click "Add to MetaMask" for each blockchain
[Open Chainlist→](https://chainlist.org)
#### Manual Configuration
* Open MetaMask Settings
* Select Networks
* Add Network manually
[View Guide→](https://support.metamask.io/networks-and-sidechains/managing-networks/how-to-add-a-custom-network-rpc/)
#### 2\. Native Gas Tokens
Acquire tokens for transaction fees
Native Gas Tokens Ready
▶
Blockchain Setup
Choose the source and destination blockchains for your cross-chain token
#### Select Your Blockchains
Choose the source and destination blockchains for your cross-chain token
🔧Testnet🌐Mainnet
Select Source▼
Select Destination▼
Contract Setup
Import and compile the required smart contracts
#### Import Required Contracts
Import and compile the token contracts in Remix IDE
Contracts Imported
1. Open the pre-configured token contract in Remix:
2. Wait a few seconds for Remix to automatically compile all contracts.
[Tutorial](#tutorial)
----------------------
### [Source Blockchain Setup](#source-blockchain-setup)
Deploy Token Contract
Configure and deploy your token using Remix IDE
Ensure MetaMask is connected to **loading...**

Already Have a Token?
If you have an existing token that meets the [CCT requirements](/ccip/concepts/cross-chain-tokens#requirements-for-cross-chain-tokens)
:
* Skip the "Deploy Token" section
* Enter your existing token address in the address field below
* Continue with "Claim and Accept Admin Role"
The tutorial will use your provided token address for subsequent steps.
1. Configure Remix
* Open the "Deploy & Run Transactions" tab
* Set Environment to "Injected Provider - MetaMask"
* Select **BurnMintERC677** contract
2. Set Parameters
Configure your token by setting these required parameters in Remix:

About the Parameters
* The name and symbol help identify your token in wallets and applications.
* Using 18 decimals is standard for most ERC20 tokens (1 token = 1000000000000000000 wei or 1018).
* If maxSupply is set to 0, it allows unlimited minting. For a limited supply, you must scale the amount according to the number of decimals. For example, if you want a max supply of 1,000 tokens with 18 decimals, the maxSupply would be`1000 * 1018` \= `1000000000000000000000` (that's 1 followed by 21 zeros).
`name``string`
The full name of your token that users will see
`"My Cross Chain Token"`
`symbol``string`
A short ticker symbol for your token (usually 3-4 letters)
`"MCCT"`
`decimals``uint8`
Number of decimal places your token will support (18 is standard)
`18`
`maxSupply``uint256`
The maximum amount of tokens that can ever exist (0 means unlimited)
`0`
3. Deploy Contract
* Click "Deploy" and confirm in MetaMask
* Copy your token address from "Deployed Contracts"
4. Verify Contract (Optional)

Why Verify Your Contract?
Contract verification makes your token contract's source code public on the blockchain explorer. This:
* Builds trust by allowing anyone to audit your code
* Enables direct interaction through the blockchain explorer
* Helps other developers understand and integrate with your contract
1. Access the Blockchain Explorer
Blockchain explorer information will be available once you select a network.
2. Verify Using Remix IDE
Remix IDE Guide
Official guide for verifying contracts using the Remix IDE verification plugin
[View Guide ↗](https://remix-ide.readthedocs.io/en/latest/contract_verification.html)
Chainlink Tutorial
Step-by-step tutorial for contract verification on blockchain explorers
[View Tutorial ↗](https://chain.link/tutorials/how-to-verify-a-smart-contract-on-etherscan)
3. Confirm Verification
1. Return to your contract on the blockchain explorer
2. Look for a green checkmark ✓ or "Verified" status
3. You should now see your contract's source code in the "Code" tab
Contract verification link will be available once you select a network.
Claim and Accept Admin Role
Configure your EOA as CCIP administrator of your token
Ensure MetaMask is connected to **loading...**
1. Register as Admin
Admin Role Claimed

Admin Registration Options
The Cross-Chain Token (CCT) standard supports multiple methods for registering as a token administrator. We use `registerAdminViaOwner()` in this tutorial because our deployed BurnMintERC677 token implements the `owner()` function. For other token implementations, you might use different registration methods. See the [self-service registration documentation](/ccip/concepts/cross-chain-tokens#self-service-registration-flow)
for all available options.
1. In the "Deploy & Run Transactions" tab, select the **RegistryModuleOwnerCustom** contract
2. Click "At Address" with:
**Contract:** RegistryModuleOwnerCustom`[Select source blockchain first]`
3. The RegistryModuleOwnerCustom will be displayed in the "Deployed Contracts" section
4. Click on the RegistryModuleOwnerCustom contract address to open the contract details
5. Call `registerAdminViaOwner`:
`registerAdminViaOwner`
Register yourself as the CCIP administrator for your token
⚠️ You must be the token owner to call this function
Parameters:
`token``address`
The token contract you want to administer
`Your deployed token address`
6. Confirm the transaction in MetaMask
2. Accept Admin Role
Admin Role Accepted
1. In the "Deploy & Run Transactions" tab, select **TokenAdminRegistry** contract
2. Click "At Address" with:
**Contract:** TokenAdminRegistry`[Select source blockchain first]`
3. The TokenAdminRegistry will be displayed in the "Deployed Contracts" section
4. Click on the TokenAdminRegistry contract address to open the contract details
5. Call `acceptAdminRole`:
`acceptAdminRole`
Accept your role as CCIP administrator for your token
⚠️ Must be called after registerAdminViaOwner is confirmed
Parameters:
`token``address`
The token contract to accept administrator role for
`Your deployed token address`
6. Confirm the transaction in MetaMask
Deploy Token Pool
Choose your pool type and deploy using Remix IDE
Ensure MetaMask is connected to **loading...**
1. Choose Pool Type

Understanding Pool Types
Each pool type serves different use cases and has specific requirements. Learn more about pool types and their characteristics in the [token pools documentation](/ccip/concepts/cross-chain-tokens#standard-token-pools)
.
Select the appropriate pool type based on your token's characteristics and requirements
Burn & Mint PoolBurnMintTokenPool
Standard mechanism for cross-chain transfers. Tokens are burned on one chain and minted on another, maintaining constant total supply.
ℹ️ Use this for new tokens or tokens with burn/mint capability
Lock & Release PoolLockReleaseTokenPool
Suitable for tokens that already exist on this blockchain and can't be burned. Tokens are locked here and "wrapped" versions are minted on other chains.
ℹ️ Deploy this on the blockchain where your token originally exists
2. Configure Remix
* Open the "Deploy & Run Transactions" tab
* Set Environment to "Injected Provider - MetaMask"
* Select **BurnMintTokenPool** contract
3. Set Parameters
Configure your pool by setting these required parameters in Remix:
`token``address`
Address of the token to be minted/burned

`localTokenDecimals``uint8`
Number of decimals for your token
`18`
`allowlist``address[]`
Addresses allowed to transfer tokens (empty array for no restrictions)
`[]`
`rmnProxy``address`
Address of the RMN contract
`[Select source blockchain first]`
`router``address`
Address of the CCIP Router contract
`[Select source blockchain first]`
4. Deploy Contract
* Click "Deploy" and confirm in MetaMask
* Copy your pool address from "Deployed Contracts"
5. Verify Contract (Optional)

Why Verify Your Contract?
Contract verification makes your pool contract's source code public on the blockchain explorer. This:
* Builds trust by allowing anyone to audit your code
* Enables direct interaction through the blockchain explorer
* Helps other developers understand and integrate with your contract
1. Access the Blockchain Explorer
Blockchain explorer information will be available once you select a network.
2. Verify Using Remix IDE
Remix IDE Guide
Official guide for verifying contracts using the Remix IDE verification plugin
[View Guide ↗](https://remix-ide.readthedocs.io/en/latest/contract_verification.html)
Chainlink Tutorial
Step-by-step tutorial for contract verification on blockchain explorers
[View Tutorial ↗](https://chain.link/tutorials/how-to-verify-a-smart-contract-on-etherscan)
3. Confirm Verification
1. Return to your contract on the blockchain explorer
2. Look for a green checkmark ✓ or "Verified" status
3. You should now see your contract's source code in the "Code" tab
Contract verification link will be available once you select a network.
Configure Token Registry
Register your token pool in the CCIP registry
Ensure MetaMask is connected to **loading...**
1. Configure Registry
Pool Registered
1. In the "Deploy & Run Transactions" tab, select the **TokenAdminRegistry** contract in the _Contracts_ drop-down list.
2. Next to the **At Address** button, fill in the following contract address, and then click the **At Address** button.
**Contract:** TokenAdminRegistry`[Select source blockchain first]`
3. Select the TokenAdminRegistry contract to expand its details
4. Call `setPool`:
`setPool`
Enable your token for CCIP by registering its token pool
⚠️ You must be the token admin to call this function
Parameters:
`localToken``address`
The token to enable for CCIP

`pool``address`
The token pool that will handle cross-chain transfers

5. Confirm the transaction in MetaMask
### [Destination Blockchain Setup](#destination-blockchain-setup)
Deploy Token Contract
Configure and deploy your token using Remix IDE
Ensure MetaMask is connected to **loading...**

Already Have a Token?
If you have an existing token that meets the [CCT requirements](/ccip/concepts/cross-chain-tokens#requirements-for-cross-chain-tokens)
:
* Skip the "Deploy Token" section
* Enter your existing token address in the address field below
* Continue with "Claim and Accept Admin Role"
The tutorial will use your provided token address for subsequent steps.
1. Configure Remix
* Open the "Deploy & Run Transactions" tab
* Set Environment to "Injected Provider - MetaMask"
* Select **BurnMintERC677** contract
2. Set Parameters
Configure your token by setting these required parameters in Remix:

About the Parameters
* The name and symbol help identify your token in wallets and applications.
* Using 18 decimals is standard for most ERC20 tokens (1 token = 1000000000000000000 wei or 1018).
* If maxSupply is set to 0, it allows unlimited minting. For a limited supply, you must scale the amount according to the number of decimals. For example, if you want a max supply of 1,000 tokens with 18 decimals, the maxSupply would be`1000 * 1018` \= `1000000000000000000000` (that's 1 followed by 21 zeros).
`name``string`
The full name of your token that users will see
`"My Cross Chain Token"`
`symbol``string`
A short ticker symbol for your token (usually 3-4 letters)
`"MCCT"`
`decimals``uint8`
Number of decimal places your token will support (18 is standard)
`18`
`maxSupply``uint256`
The maximum amount of tokens that can ever exist (0 means unlimited)
`0`
3. Deploy Contract
* Click "Deploy" and confirm in MetaMask
* Copy your token address from "Deployed Contracts"
4. Verify Contract (Optional)

Why Verify Your Contract?
Contract verification makes your token contract's source code public on the blockchain explorer. This:
* Builds trust by allowing anyone to audit your code
* Enables direct interaction through the blockchain explorer
* Helps other developers understand and integrate with your contract
1. Access the Blockchain Explorer
Blockchain explorer information will be available once you select a network.
2. Verify Using Remix IDE
Remix IDE Guide
Official guide for verifying contracts using the Remix IDE verification plugin
[View Guide ↗](https://remix-ide.readthedocs.io/en/latest/contract_verification.html)
Chainlink Tutorial
Step-by-step tutorial for contract verification on blockchain explorers
[View Tutorial ↗](https://chain.link/tutorials/how-to-verify-a-smart-contract-on-etherscan)
3. Confirm Verification
1. Return to your contract on the blockchain explorer
2. Look for a green checkmark ✓ or "Verified" status
3. You should now see your contract's source code in the "Code" tab
Contract verification link will be available once you select a network.
Claim and Accept Admin Role
Configure your EOA as CCIP administrator of your token
Ensure MetaMask is connected to **loading...**
1. Register as Admin
Admin Role Claimed

Admin Registration Options
The Cross-Chain Token (CCT) standard supports multiple methods for registering as a token administrator. We use `registerAdminViaOwner()` in this tutorial because our deployed BurnMintERC677 token implements the `owner()` function. For other token implementations, you might use different registration methods. See the [self-service registration documentation](/ccip/concepts/cross-chain-tokens#self-service-registration-flow)
for all available options.
1. In the "Deploy & Run Transactions" tab, select the **RegistryModuleOwnerCustom** contract
2. Click "At Address" with:
**Contract:** RegistryModuleOwnerCustom`[Select destination blockchain first]`
3. The RegistryModuleOwnerCustom will be displayed in the "Deployed Contracts" section
4. Click on the RegistryModuleOwnerCustom contract address to open the contract details
5. Call `registerAdminViaOwner`:
`registerAdminViaOwner`
Register yourself as the CCIP administrator for your token
⚠️ You must be the token owner to call this function
Parameters:
`token``address`
The token contract you want to administer
`Your deployed token address`
6. Confirm the transaction in MetaMask
2. Accept Admin Role
Admin Role Accepted
1. In the "Deploy & Run Transactions" tab, select **TokenAdminRegistry** contract
2. Click "At Address" with:
**Contract:** TokenAdminRegistry`[Select destination blockchain first]`
3. The TokenAdminRegistry will be displayed in the "Deployed Contracts" section
4. Click on the TokenAdminRegistry contract address to open the contract details
5. Call `acceptAdminRole`:
`acceptAdminRole`
Accept your role as CCIP administrator for your token
⚠️ Must be called after registerAdminViaOwner is confirmed
Parameters:
`token``address`
The token contract to accept administrator role for
`Your deployed token address`
6. Confirm the transaction in MetaMask
Deploy Token Pool
Choose your pool type and deploy using Remix IDE
Ensure MetaMask is connected to **loading...**
1. Choose Pool Type

Understanding Pool Types
Each pool type serves different use cases and has specific requirements. Learn more about pool types and their characteristics in the [token pools documentation](/ccip/concepts/cross-chain-tokens#standard-token-pools)
.
Select the appropriate pool type based on your token's characteristics and requirements
Burn & Mint PoolBurnMintTokenPool
Standard mechanism for cross-chain transfers. Tokens are burned on one chain and minted on another, maintaining constant total supply.
ℹ️ Use this for new tokens or tokens with burn/mint capability
Lock & Release PoolLockReleaseTokenPool
Suitable for tokens that already exist on this blockchain and can't be burned. Tokens are locked here and "wrapped" versions are minted on other chains.
ℹ️ Deploy this on the blockchain where your token originally exists
2. Configure Remix
* Open the "Deploy & Run Transactions" tab
* Set Environment to "Injected Provider - MetaMask"
* Select **BurnMintTokenPool** contract
3. Set Parameters
Configure your pool by setting these required parameters in Remix:
`token``address`
Address of the token to be minted/burned

`localTokenDecimals``uint8`
Number of decimals for your token
`18`
`allowlist``address[]`
Addresses allowed to transfer tokens (empty array for no restrictions)
`[]`
`rmnProxy``address`
Address of the RMN contract
`[Select destination blockchain first]`
`router``address`
Address of the CCIP Router contract
`[Select destination blockchain first]`
4. Deploy Contract
* Click "Deploy" and confirm in MetaMask
* Copy your pool address from "Deployed Contracts"
5. Verify Contract (Optional)

Why Verify Your Contract?
Contract verification makes your pool contract's source code public on the blockchain explorer. This:
* Builds trust by allowing anyone to audit your code
* Enables direct interaction through the blockchain explorer
* Helps other developers understand and integrate with your contract
1. Access the Blockchain Explorer
Blockchain explorer information will be available once you select a network.
2. Verify Using Remix IDE
Remix IDE Guide
Official guide for verifying contracts using the Remix IDE verification plugin
[View Guide ↗](https://remix-ide.readthedocs.io/en/latest/contract_verification.html)
Chainlink Tutorial
Step-by-step tutorial for contract verification on blockchain explorers
[View Tutorial ↗](https://chain.link/tutorials/how-to-verify-a-smart-contract-on-etherscan)
3. Confirm Verification
1. Return to your contract on the blockchain explorer
2. Look for a green checkmark ✓ or "Verified" status
3. You should now see your contract's source code in the "Code" tab
Contract verification link will be available once you select a network.
Configure Token Registry
Register your token pool in the CCIP registry
Ensure MetaMask is connected to **loading...**
1. Configure Registry
Pool Registered
1. In the "Deploy & Run Transactions" tab, select the **TokenAdminRegistry** contract in the _Contracts_ drop-down list.
2. Next to the **At Address** button, fill in the following contract address, and then click the **At Address** button.
**Contract:** TokenAdminRegistry`[Select destination blockchain first]`
3. Select the TokenAdminRegistry contract to expand its details
4. Call `setPool`:
`setPool`
Enable your token for CCIP by registering its token pool
⚠️ You must be the token admin to call this function
Parameters:
`localToken``address`
The token to enable for CCIP

`pool``address`
The token pool that will handle cross-chain transfers

5. Confirm the transaction in MetaMask
### [Configure Cross-Chain Communication](#configure-cross-chain-communication)
#### [Source Chain Configuration](#source-chain-configuration)
Grant Burn and Mint Privileges
Grant required privileges to your token pool
Ensure MetaMask is connected to **loading...**
1. Grant Burn and Mint Privileges
Grant Burn and Mint Privileges

Optional Step
Skip this section if you deployed a **LockReleaseTokenPool**
1. In the list of deployed contracts, select the **BurnMintERC677** at 
2. Click to open the contract details
3. Call `grantMintAndBurnRoles`:
`grantMintAndBurnRoles`
Grant mint and burn privileges to your token pool for cross-chain transfers
⚠️ You must be the token contract owner to call this function
Parameters:
`burnAndMinter``address`
Address to grant mint and burn roles to (your token pool)

4. Confirm the transaction in MetaMask
Configure Pool
Set up cross-chain communication parameters
Ensure MetaMask is connected to **loading...**
1. Configure Pool
Configure Pool
⚠️ Please select valid blockchains first
⚠️ Please deploy your token pool before proceeding
⚡️
#### Current Chain Prerequisites
* Select valid blockchains for the transfer
* Deploy your token pool on the current chain
Verify Configuration
Confirm your cross-chain setup is correct
Ensure MetaMask is connected to **loading...**
1. Verify Configuration
Verify Configuration
1. Open the "Deploy & Run Transactions" tab in Remix
2. Select your token pool contract:
**LockReleaseTokenPool**
3. Click the contract to view its functions
2. Verify Remote Token
1. Call `getRemoteToken`:
`getRemoteToken`
Retrieves the ABI-encoded address of your token on the remote chain
Parameters:
`remoteChainSelector``uint64`
Chain selector for undefined
Returns:
bytes
ABI-encoded address of the token on the remote chain
Expected Result:
Waiting for remote token address...
3. Verify Remote Pools
1. Call `getRemotePools`:
`getRemotePools`
Returns all registered token pools on the remote chain
Parameters:
`remoteChainSelector``uint64`
Chain selector for undefined
Returns:
bytes\[\]
Array of ABI-encoded addresses of all registered pools on the remote chain
Expected Result:
Waiting for remote pool address...
4. Verify Rate Limiters
1. Call `getCurrentInboundRateLimiterState`:
`getCurrentInboundRateLimiterState`
Verifies your inbound transfer rate limits
Parameters:
`remoteChainSelector``uint64`
Chain selector for undefined
Returns:
TokenBucket
Current state of the inbound rate limiter
Expected Result:
Tokens`0`
Last Updated`Current block timestamp`
Status`false`
Capacity`0`
Rate`0`

NOTE
The `tokens` field starts at 0 and the `lastUpdated` field will show the current block timestamp.
2. Call `getCurrentOutboundRateLimiterState`:
`getCurrentOutboundRateLimiterState`
Verifies your outbound transfer rate limits
Parameters:
`remoteChainSelector``uint64`
Chain selector for undefined
Returns:
TokenBucket
Current state of the outbound rate limiter
Expected Result:
Tokens`0`
Last Updated`Current block timestamp`
Status`false`
Capacity`0`
Rate`0`

NOTE
The `tokens` field starts at 0 and the `lastUpdated` field will show the current block timestamp.
#### [Destination Chain Configuration](#destination-chain-configuration)
Grant Burn and Mint Privileges
Grant required privileges to your token pool
Ensure MetaMask is connected to **loading...**
1. Grant Burn and Mint Privileges
Grant Burn and Mint Privileges

Optional Step
Skip this section if you deployed a **LockReleaseTokenPool**
1. In the list of deployed contracts, select the **BurnMintERC677** at 
2. Click to open the contract details
3. Call `grantMintAndBurnRoles`:
`grantMintAndBurnRoles`
Grant mint and burn privileges to your token pool for cross-chain transfers
⚠️ You must be the token contract owner to call this function
Parameters:
`burnAndMinter``address`
Address to grant mint and burn roles to (your token pool)

4. Confirm the transaction in MetaMask
Configure Pool
Set up cross-chain communication parameters
Ensure MetaMask is connected to **loading...**
1. Configure Pool
Configure Pool
⚠️ Please select valid blockchains first
⚠️ Please deploy your token pool before proceeding
⚡️
#### Current Chain Prerequisites
* Select valid blockchains for the transfer
* Deploy your token pool on the current chain
Verify Configuration
Confirm your cross-chain setup is correct
Ensure MetaMask is connected to **loading...**
1. Verify Configuration
Verify Configuration
1. Open the "Deploy & Run Transactions" tab in Remix
2. Select your token pool contract:
**LockReleaseTokenPool**
3. Click the contract to view its functions
2. Verify Remote Token
1. Call `getRemoteToken`:
`getRemoteToken`
Retrieves the ABI-encoded address of your token on the remote chain
Parameters:
`remoteChainSelector``uint64`
Chain selector for undefined
Returns:
bytes
ABI-encoded address of the token on the remote chain
Expected Result:
Waiting for remote token address...
3. Verify Remote Pools
1. Call `getRemotePools`:
`getRemotePools`
Returns all registered token pools on the remote chain
Parameters:
`remoteChainSelector``uint64`
Chain selector for undefined
Returns:
bytes\[\]
Array of ABI-encoded addresses of all registered pools on the remote chain
Expected Result:
Waiting for remote pool address...
4. Verify Rate Limiters
1. Call `getCurrentInboundRateLimiterState`:
`getCurrentInboundRateLimiterState`
Verifies your inbound transfer rate limits
Parameters:
`remoteChainSelector``uint64`
Chain selector for undefined
Returns:
TokenBucket
Current state of the inbound rate limiter
Expected Result:
Tokens`0`
Last Updated`Current block timestamp`
Status`false`
Capacity`0`
Rate`0`

NOTE
The `tokens` field starts at 0 and the `lastUpdated` field will show the current block timestamp.
2. Call `getCurrentOutboundRateLimiterState`:
`getCurrentOutboundRateLimiterState`
Verifies your outbound transfer rate limits
Parameters:
`remoteChainSelector``uint64`
Chain selector for undefined
Returns:
TokenBucket
Current state of the outbound rate limiter
Expected Result:
Tokens`0`
Last Updated`Current block timestamp`
Status`false`
Capacity`0`
Rate`0`

NOTE
The `tokens` field starts at 0 and the `lastUpdated` field will show the current block timestamp.
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Using the Token Manager | Chainlink Documentation
On this page
Using the Token Manager
=======================
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.
[Limitations](#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-tokens#token-pool-upgradability)
section.
* **Deployment of token pools that use the [Lock and Unlock mechanism](/ccip/concepts/cross-chain-tokens#token-handling-mechanisms-and-token-pool-deployment)
**. In this use case, tokens are locked on the source blockchain, and an equivalent amount of tokens are released on the destination blockchain.
* Deployment or enablement of custom token pools is not yet supported.
* Adding a new network is currently supported if the Token Admin address is the same for each network. This capability will be expanded in a subsequent update to enable multiple Token Admin addresses across different networks.
[Getting started](#getting-started)
------------------------------------
1. Open the Token Manager, which has separate links for testnet and mainnet. It is highly recommended to test and perform any operations on testnet before mainnet:
* 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.
After your wallet is connected, you'll see the Token Manager homepage.
[Deploy a new token](#deploy-a-new-token)
------------------------------------------
If you haven't deployed your token to any networks yet, you can start here. This Token Manager Wizard automatically configures all tokens with the Burn & Mint mechanism. (Refer to the [token contract](https://github.com/smartcontractkit/chainlink/blob/develop/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.)
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:

4. 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.
5. The **Deploy** page displays the steps that you need to complete. For each network, the Token Manager guides you through each of these steps in order:
* Deploy token and pool
* Accept admin role
* Accept token ownership
* Accept pool ownership

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 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.
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.
[Add an existing 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-tokens#token-handling-mechanisms-and-token-pool-deployment)
.
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/develop/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](#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](#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](#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.
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Enable your tokens in CCIP (Lock & Mint): Register from an EOA using Hardhat | Chainlink Documentation
On this page
Enable your tokens in CCIP (Lock & Mint): Register from an EOA using Hardhat
============================================================================
Guide Versions
--------------
This guide is available in multiple versions. Choose the one that matches your needs.
Hardhat (Lock & Mint)
[Hardhat (Lock & Mint)](/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat)
[Foundry (Lock & Mint)](/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-foundry)
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 [`BurnMintERC677`](https://github.com/smartcontractkit/ccip/blob/release/contracts-ccip-1.5.1/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.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/v1.5.1/lock-release-token-pool)
on Avalanche Fuji and [`BurnMintTokenPool`](/ccip/api-reference/v1.5.1/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/ccip/blob/release/contracts-ccip-1.5.1/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.sol#L141)
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/v1.5.1/registry-module-owner-custom)
contract's [`registerAdminViaOwner`](/ccip/api-reference/v1.5.1/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/v1.5.1/token-admin-registry)
contract's [`acceptAdminRole`](/ccip/api-reference/v1.5.1/token-admin-registry#acceptadminrole)
function to complete the registration process.
5. **Linking Tokens to Pools**: You will call the [`TokenAdminRegistry`](/ccip/api-reference/v1.5.1/token-admin-registry)
contract's [`setPool`](/ccip/api-reference/v1.5.1/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/v1.5.1/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/ccip/blob/f8d2f981e54aa7eb739d21ab925a954e043da9b6/contracts/src/v0.8/shared/token/ERC677/BurnMintERC677.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](#before-you-begin)
--------------------------------------
1. Make sure you have Node.js v18 or above installed. If not, **install Node.js v18**:
[Download Node.js 18](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:
nvm use 18
Verify that the correct version of Node.js is installed:
node -v
Example output:
$ node -v
v18.7.0
2. **Clone the repository and navigate to the project directory:**
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
3. **Install dependencies for the project:**
npm install
4. **Compile the project:**
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:
npx env-enc set-pw
2. Set up a `.env.enc` file with the necessary variables for Avalanche Fuji and Arbitrum Sepolia testnets. Use the following command to add the variables:
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/)
.
* `ARBITRUM_SEPOLIA_RPC_URL`: A URL for the _Arbitrum 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)
.
* `ARBISCAN_API_KEY`: An API key from Arbiscan to verify your contracts on Arbitrum. See [this guide](https://docs.arbiscan.io/getting-started/viewing-api-usage-statistics)
to get one from Arbiscan.
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 Arbitrum Sepolia to cover transaction fees. You can use the [Chainlink faucets](https://faucets.chain.link/)
to get testnet tokens.
[Tutorial](#tutorial)
----------------------
### [Deploy Tokens](#deploy-tokens)
In this step, you will use the `deployToken.ts` task to deploy tokens on two testnets, Avalanche Fuji and Arbitrum 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 |
| `withgetccipadmin` | A flag to determine whether the token contract has a `getCCIPAdmin()` function. If set to `true`, a CCIP admin is required. When `false`, token admin registration will use the token `owner()` function. | `false` | No |
| `ccipadminaddress` | The address of the CCIP admin, only applicable if `withgetccipadmin` is set to `true`. | N/A | No |
| `verifycontract` | Whether to verify the contract on Etherscan or a similar blockchain explorer. | `false` | No |
| `network` | The blockchain on which the token will be deployed. Examples include `avalancheFuji`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | N/A | Yes |
Deploy tokens, use the following commands, substituting the token name and symbol as needed:
1. **Deploy the token on Avalanche Fuji:**
npx hardhat deployToken --name "BnM aem" --symbol BnMaem --decimals 18 --maxsupply 0 --withgetccipadmin false --verifycontract true --network avalancheFuji
Example output:
2024-12-02T21:02:09.465Z info: Deploying BurnMintERC677 contract to avalancheFuji
2024-12-02T21:02:09.466Z info: Waiting 2 blocks for transaction 0x68139536a78ecdb33c49a12050374cea62493ecd63d8fac4c0619e6c337eec79 to be confirmed...
2024-12-02T21:02:11.748Z info: Token deployed to: 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6
2024-12-02T21:02:11.856Z info: Granting mint and burn roles to 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T21:02:17.673Z info: Verifying contract...
The contract 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 has already been verified on the block explorer. If you're trying to verify a partially verified contract, please use the --force flag.
https://testnet.snowtrace.io/address/0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6#code
2024-12-02T21:02:18.526Z info: Token contract deployed and verified
2. **Deploy the token on Arbitrum Sepolia:**
npx hardhat deployToken --name "BnM aem" --symbol BnMaem --decimals 18 --maxsupply 0 --withgetccipadmin false --verifycontract true --network arbitrumSepolia
Example output:
2024-12-02T21:03:46.264Z info: Deploying BurnMintERC677 contract to arbitrumSepolia
2024-12-02T21:03:46.266Z info: Waiting 2 blocks for transaction 0x25cad62e9caa6f434172a40e08dacabbf510e7668250396309aeae11610b43a2 to be confirmed...
2024-12-02T21:03:47.017Z info: Token deployed to: 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF
2024-12-02T21:03:47.142Z info: Granting mint and burn roles to 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T21:03:48.212Z info: Verifying contract...
The contract 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF has already been verified on the block explorer. If you're trying to verify a partially verified contract, please use the --force flag.
https://sepolia.arbiscan.io/address/0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF#code
2024-12-02T21:03:48.816Z info: Token contract deployed and verified
### [Deploy Token Pools](#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 Arbitrum 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 Fuji and `"burnMint"` on Arbitrum Sepolia. | `"burnMint"` | No |
| `localtokendecimals` | The number of decimals for the token on this chain. | `18` | No |
| `acceptliquidity` | Whether the pool should accept liquidity. Only applicable for `lockRelease` pools. | `false` | No |
| `network` | The blockchain on which the token pool will be deployed. Examples include `avalancheFuji`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | N/A | Yes |
| `verifycontract` | Whether to verify the contract on Etherscan or a similar blockchain explorer. | `false` | No |
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:**
npx hardhat deployTokenPool \
--tokenaddress 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 \
--pooltype lockRelease \
--localtokendecimals 18 \
--acceptliquidity false \
--verifycontract true \
--network avalancheFuji
Example output:
2024-12-02T21:07:26.472Z info: Waiting 2 blocks for transaction 0xd786d5916c122d92cc2ac8015261030b49d96c4faca558f394776463ddac8a82 to be confirmed...
2024-12-02T21:07:39.641Z info: Token pool deployed to: 0x5E0244414148af58fBE112570BA32b4f22765f7d
2024-12-02T21:07:39.642Z info: Verifying contract...
Successfully submitted source code for contract
@chainlink/contracts-ccip/src/v0.8/ccip/pools/LockReleaseTokenPool.sol:LockReleaseTokenPool at 0x5E0244414148af58fBE112570BA32b4f22765f7d
for verification on the block explorer. Waiting for verification result...
Successfully verified contract LockReleaseTokenPool on the block explorer.
https://testnet.snowtrace.io/address/0x5E0244414148af58fBE112570BA32b4f22765f7d#code
2024-12-02T21:07:57.727Z info: Token pool contract deployed and verified
2. **Deploy the burn and mint token pool on Arbitrum Sepolia:**
npx hardhat deployTokenPool \
--tokenaddress 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF \
--pooltype burnMint \
--localtokendecimals 18 \
--verifycontract true \
--network arbitrumSepolia
Example output:
2024-12-02T21:08:53.290Z info: Waiting 2 blocks for transaction 0x18da27fb545fd32cbf1b9e847e0184260167dd764aa1beb97899bead10a26dae to be confirmed...
2024-12-02T21:08:53.980Z info: Token pool deployed to: 0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9
2024-12-02T21:08:53.980Z info: Granting mint and burn roles to 0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9 on token 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF
2024-12-02T21:08:55.732Z info: Verifying contract...
The contract 0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9 has already been verified on the block explorer. If you're trying to verify a partially verified contract, please use the --force flag.
https://sepolia.arbiscan.io/address/0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9#code
2024-12-02T21:08:56.271Z info: Token pool contract deployed and verified
### [Claim Admin](#claim-admin)
In this step, you will use the `claimAdmin.ts` task to register your EOA as the administrator for the deployed tokens on both testnets, Avalanche Fuji and Arbitrum Sepolia. This process involves calling the `RegistryModuleOwnerCustom` contract, which will fetch the token owner and set it up as the admin.
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 |
| `withccipadmin` | A flag indicating whether the token contract has a CCIP admin. If `false`, the token admin is registered using the `owner()` function. | `false` | No |
| `network` | The blockchain on which the claim admin process will be executed. Examples include `avalancheFuji`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | 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:**
npx hardhat claimAdmin --tokenaddress 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 --withccipadmin false --network avalancheFuji
Example output:
2024-12-02T21:11:42.208Z info: Current token owner: 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T21:11:42.209Z info: Claiming admin of 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 via owner() for signer 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T21:11:50.405Z info: Claimed admin of 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 tx: 0xf89b2116f964f33b8d444d6a8e8540f580465d7d2b5a1565501ede18ca82c5a1
2. **Claim the admin role on Arbitrum Sepolia:**
npx hardhat claimAdmin --tokenaddress 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF --withccipadmin false --network arbitrumSepolia
Example output:
2024-12-02T21:12:15.438Z info: Current token owner: 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T21:12:15.439Z info: Claiming admin of 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF via owner() for signer 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T21:12:16.920Z info: Claimed admin of 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF tx: 0x9943b494f46e9eda693e414fd0c9ed1319716f6b2fecc73653e265f1d5bcf6fc
### [Accept Admin Role](#accept-admin-role)
In this step, you will use the `acceptAdminRole.ts` task to accept the admin role for the deployed tokens on both testnets, Avalanche Fuji and Arbitrum 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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | 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:**
npx hardhat acceptAdminRole --tokenaddress 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 --network avalancheFuji
Example output:
2024-12-02T21:13:46.513Z info: Accepted admin role for token 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 tx: 0x2965d5194436a233b3888414c80f60119ff45baee21c6e3db4ba72b4b5e3d1ed
2. **Accept the admin role on Arbitrum Sepolia:**
npx hardhat acceptAdminRole --tokenaddress 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF --network arbitrumSepolia
Example output:
2024-12-02T21:14:31.333Z info: Accepted admin role for token 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF tx: 0xe442fcdd9ee4d4c6ccc2fd6629ac6d500d86e47b741ca4a07831fcfaf46c554c
### [Set Pool](#set-pool)
In this step, you will use the `setPool.ts` 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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | 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:**
npx hardhat setPool \
--tokenaddress 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 --pooladdress 0x5E0244414148af58fBE112570BA32b4f22765f7d \
--network avalancheFuji
Example output:
2024-10-09T00:37:26.568Z info: Setting pool for token 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 to 0x5E0244414148af58fBE112570BA32b4f22765f7d by 0x45C90FBb5acC1a5c156a401B56Fea55e69E7669d
2024-10-09T00:37:36.984Z info: Pool set for token 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 to 0x5E0244414148af58fBE112570BA32b4f22765f7d
2. **Set the pool for Arbitrum Sepolia:**
npx hardhat setPool \
--tokenaddress 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF --pooladdress 0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9 \
--network arbitrumSepolia
Example output:
2024-12-02T21:16:53.163Z info: Setting pool for token 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF to 0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9 by 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T21:16:55.750Z info: Pool set for token 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF to 0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9
### [Configure Token Pools](#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 Arbitrum 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., `arbitrumSepolia` 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` | Enables or disables the outbound rate limiter. | `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` | Enables or disables the inbound rate limiter. | `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:**
npx hardhat applyChainUpdates \
--pooladdress 0x5E0244414148af58fBE112570BA32b4f22765f7d \
--remotechain arbitrumSepolia \
--remotepooladdresses 0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9 \
--remotetokenaddress 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF \
--network avalancheFuji
Example output:
2024-12-02T21:19:41.293Z info: Applying chain update to pool at address: 0x5E0244414148af58fBE112570BA32b4f22765f7d
2024-12-02T21:19:41.294Z info: Remote chain: arbitrumSepolia (3478487238524512106)
2024-12-02T21:19:41.294Z info: Remote pool addresses: 0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9
2024-12-02T21:19:41.294Z info: Remote token address: 0xf63818E364407933f61F12Cf90D4f2Ed0261EEbF
2024-12-02T21:19:50.302Z info: Chain update applied successfully
2. **Configure the pool on Arbitrum Sepolia:**
npx hardhat applyChainUpdates \
--pooladdress 0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9 \
--remotechain avalancheFuji \
--remotepooladdresses 0x5E0244414148af58fBE112570BA32b4f22765f7d \
--remotetokenaddress 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 \
--network arbitrumSepolia
Example output:
2024-12-02T21:20:26.160Z info: Applying chain update to pool at address: 0xCAF6825F4ceFB71CA414A2f242e56b5e08C471D9
2024-12-02T21:20:26.160Z info: Remote chain: avalancheFuji (14767482510784806043)
2024-12-02T21:20:26.160Z info: Remote pool addresses: 0x5E0244414148af58fBE112570BA32b4f22765f7d
2024-12-02T21:20:26.160Z info: Remote token address: 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6
2024-12-02T21:20:28.026Z info: Chain update applied successfully
### [Mint Tokens](#mint-tokens)
In this step, you will use the `mintTokens.ts` 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 `BurnMintERC677` 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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | 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:**
npx hardhat mintTokens --tokenaddress 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 --amount 1000000000000000000000 --network avalancheFuji
Example output:
2024-12-02T21:21:36.303Z info: Minting 1000000000000000000000 of BnMaem tokens to 0x9d087fC03ae39b088326b67fA3C788236645b717
2024-12-02T21:21:40.227Z info: Minted 1000000000000000000000 of BnMaem tokens to 0x9d087fC03ae39b088326b67fA3C788236645b717 - transaction hash: 0xb869b45474dff73d0726579cb0838221b76a201d36a45d9f7a7df05fca16c519
2024-12-02T21:21:40.434Z info: Current balance of 0x9d087fC03ae39b088326b67fA3C788236645b717 is 1000000000000000000000 BnMaem
### [Transfer Tokens](#transfer-tokens)
In this step, you will use the `transferTokens` task to transfer tokens from Avalanche Fuji to Arbitrum 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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | 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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | N/A | Yes |
#### [Pay fees in LINK](#pay-fees-in-link)
Call the CCIP Router to transfer tokens from Avalanche Fuji to Arbitrum Sepolia, paying the CCIP fees in LINK tokens. Replace the token address, amount, receiver address, and blockchain with the appropriate values:
npx hardhat transferTokens --tokenaddress 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 --amount 100 --destinationchain arbitrumSepolia --receiveraddress 0x9d087fC03ae39b088326b67fA3C788236645b717 --network avalancheFuji
Example output:
2024-12-02T21:23:03.979Z info: Estimated fees: 19182637092835296
2024-12-02T21:23:03.982Z info: Approving 100 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-02T21:23:11.473Z info: Approving 19182637092835296 LINK to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-02T21:23:20.524Z info: Transferring 100 of 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia with 19182637092835296 of LINK as fees
2024-12-02T21:23:43.738Z info: Transaction hash: 0xd52e3384c11e1dfc14c1545cd0294e0ca6078130c847f9d2bd21db308e04897a
2024-12-02T21:23:43.764Z info: Message dispatched. Message id: 0x0bcff51bd548e8bd3504dfe28046627f902441dd039f30fce4700b152ec88891
2024-12-02T21:23:43.764Z info: ✅ Transferred 100 of 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia. Transaction hash: 0xd52e3384c11e1dfc14c1545cd0294e0ca6078130c847f9d2bd21db308e04897a - CCIP message id: 0x0bcff51bd548e8bd3504dfe28046627f902441dd039f30fce4700b152ec88891
2024-12-02T21:23:43.764Z info: Check status of message on https://ccip.chain.link/msg/0x0bcff51bd548e8bd3504dfe28046627f902441dd039f30fce4700b152ec88891
#### [Pay fees in native gas tokens](#pay-fees-in-native-gas-tokens)
Call the CCIP Router to transfer tokens from Avalanche Fuji to Arbitrum Sepolia, paying the CCIP fees in native gas tokens. Replace the token address, amount, receiver address, and blockchain with the appropriate values:
npx hardhat transferTokens --tokenaddress 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 --amount 100 --destinationchain arbitrumSepolia --receiveraddress 0x9d087fC03ae39b088326b67fA3C788236645b717 --fee native --network avalancheFuji
Example output:
2024-12-02T21:25:09.240Z info: Estimated fees: 10303786857889968
2024-12-02T21:25:09.241Z info: Approving 100 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-02T21:25:14.164Z info: Transferring 100 of 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia with 10303786857889968 of native token as fees
2024-12-02T21:25:18.442Z info: Transaction hash: 0x328acf3a83301d1373e567b5688fe5931bf99d031885686f86c9328df358b43c
2024-12-02T21:25:18.457Z info: Message dispatched. Message id: 0xf7ec3800b441d0958daaa21324f291b5f3be681c18947d5ffcd81a637d770423
2024-12-02T21:25:18.457Z info: ✅ Transferred 100 of 0x32a7ACf39A4f3864e70529d6453c1fd24fB06cF6 to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia. Transaction hash: 0x328acf3a83301d1373e567b5688fe5931bf99d031885686f86c9328df358b43c - CCIP message id: 0xf7ec3800b441d0958daaa21324f291b5f3be681c18947d5ffcd81a637d770423
2024-12-02T21:25:18.457Z info: Check status of message on https://ccip.chain.link/msg/0xf7ec3800b441d0958daaa21324f291b5f3be681c18947d5ffcd81a637d770423
Your tokens have been locked on the token pool on Avalanche Fuji, the corresponding tokens have been minted on Arbitrum 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 Arbitrum Sepolia to Avalanche Fuji using CCIP as an exercise. Your tokens will be burned on Arbitrum 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.
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Transfer Tokens with Data - Defensive Example | Chainlink Documentation
On this page
Transfer Tokens with Data - Defensive Example
=============================================
This tutorial extends the [programmable token transfers example](/ccip/tutorials/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](#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#mint-test-tokens)
. 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/programmable-token-transfers)
to learn how to make programmable token transfers using CCIP.
[Tutorial](#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.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
import {EnumerableMap} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableMap.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
/// @title - A simple messenger contract for transferring/receiving tokens and data across chains.
/// @dev - This example shows how to recover tokens in case of revert
contract ProgrammableDefensiveTokenTransfers is CCIPReceiver, OwnerIsCreator {
using EnumerableMap for EnumerableMap.Bytes32ToUintMap;
using SafeERC20 for IERC20;
// Custom errors to provide more descriptive revert messages.
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees.
error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails.
error DestinationChainNotAllowlisted(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner.
error SourceChainNotAllowed(uint64 sourceChainSelector); // Used when the source chain has not been allowlisted by the contract owner.
error SenderNotAllowed(address sender); // Used when the sender has not been allowlisted by the contract owner.
error InvalidReceiverAddress(); // Used when the receiver address is 0.
error OnlySelf(); // Used when a function is called outside of the contract itself.
error ErrorCase(); // Used when simulating a revert during message processing.
error MessageNotFailed(bytes32 messageId);
// Example error code, could have many different error codes.
enum ErrorCode {
// RESOLVED is first so that the default value is resolved.
RESOLVED,
// Could have any number of error codes here.
FAILED
}
struct FailedMessage {
bytes32 messageId;
ErrorCode errorCode;
}
// Event emitted when a message is sent to another chain.
event MessageSent(
bytes32 indexed messageId, // The unique ID of the CCIP message.
uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
address receiver, // The address of the receiver on the destination chain.
string text, // The text being sent.
address token, // The token address that was transferred.
uint256 tokenAmount, // The token amount that was transferred.
address feeToken, // the token address used to pay CCIP fees.
uint256 fees // The fees paid for sending the message.
);
// Event emitted when a message is received from another chain.
event MessageReceived(
bytes32 indexed messageId, // The unique ID of the CCIP message.
uint64 indexed sourceChainSelector, // The chain selector of the source chain.
address sender, // The address of the sender from the source chain.
string text, // The text that was received.
address token, // The token address that was transferred.
uint256 tokenAmount // The token amount that was transferred.
);
event MessageFailed(bytes32 indexed messageId, bytes reason);
event MessageRecovered(bytes32 indexed messageId);
bytes32 private s_lastReceivedMessageId; // Store the last received messageId.
address private s_lastReceivedTokenAddress; // Store the last received token address.
uint256 private s_lastReceivedTokenAmount; // Store the last received amount.
string private s_lastReceivedText; // Store the last received text.
// Mapping to keep track of allowlisted destination chains.
mapping(uint64 => bool) public allowlistedDestinationChains;
// Mapping to keep track of allowlisted source chains.
mapping(uint64 => bool) public allowlistedSourceChains;
// Mapping to keep track of allowlisted senders.
mapping(address => bool) public allowlistedSenders;
IERC20 private s_linkToken;
// The message contents of failed messages are stored here.
mapping(bytes32 messageId => Client.Any2EVMMessage contents)
public s_messageContents;
// Contains failed messages and their state.
EnumerableMap.Bytes32ToUintMap internal s_failedMessages;
// This is used to simulate a revert in the processMessage function.
bool internal s_simRevert = false;
/// @notice Constructor initializes the contract with the router address.
/// @param _router The address of the router contract.
/// @param _link The address of the link contract.
constructor(address _router, address _link) CCIPReceiver(_router) {
s_linkToken = IERC20(_link);
}
/// @dev Modifier that checks if the chain with the given destinationChainSelector is allowlisted.
/// @param _destinationChainSelector The selector of the destination chain.
modifier onlyAllowlistedDestinationChain(uint64 _destinationChainSelector) {
if (!allowlistedDestinationChains[_destinationChainSelector])
revert DestinationChainNotAllowlisted(_destinationChainSelector);
_;
}
/// @dev Modifier that checks if the chain with the given sourceChainSelector is allowlisted and if the sender is allowlisted.
/// @param _sourceChainSelector The selector of the destination chain.
/// @param _sender The address of the sender.
modifier onlyAllowlisted(uint64 _sourceChainSelector, address _sender) {
if (!allowlistedSourceChains[_sourceChainSelector])
revert SourceChainNotAllowed(_sourceChainSelector);
if (!allowlistedSenders[_sender]) revert SenderNotAllowed(_sender);
_;
}
/// @dev Modifier that checks the receiver address is not 0.
/// @param _receiver The receiver address.
modifier validateReceiver(address _receiver) {
if (_receiver == address(0)) revert InvalidReceiverAddress();
_;
}
/// @dev Modifier to allow only the contract itself to execute a function.
/// Throws an exception if called by any account other than the contract itself.
modifier onlySelf() {
if (msg.sender != address(this)) revert OnlySelf();
_;
}
/// @dev Updates the allowlist status of a destination chain for transactions.
/// @notice This function can only be called by the owner.
/// @param _destinationChainSelector The selector of the destination chain to be updated.
/// @param allowed The allowlist status to be set for the destination chain.
function allowlistDestinationChain(
uint64 _destinationChainSelector,
bool allowed
) external onlyOwner {
allowlistedDestinationChains[_destinationChainSelector] = allowed;
}
/// @dev Updates the allowlist status of a source chain
/// @notice This function can only be called by the owner.
/// @param _sourceChainSelector The selector of the source chain to be updated.
/// @param allowed The allowlist status to be set for the source chain.
function allowlistSourceChain(
uint64 _sourceChainSelector,
bool allowed
) external onlyOwner {
allowlistedSourceChains[_sourceChainSelector] = allowed;
}
/// @dev Updates the allowlist status of a sender for transactions.
/// @notice This function can only be called by the owner.
/// @param _sender The address of the sender to be updated.
/// @param allowed The allowlist status to be set for the sender.
function allowlistSender(address _sender, bool allowed) external onlyOwner {
allowlistedSenders[_sender] = allowed;
}
/// @notice Sends data and transfer tokens to receiver on the destination chain.
/// @notice Pay for fees in LINK.
/// @dev Assumes your contract has sufficient LINK to pay for CCIP fees.
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param _receiver The address of the recipient on the destination blockchain.
/// @param _text The string data to be sent.
/// @param _token token address.
/// @param _amount token amount.
/// @return messageId The ID of the CCIP message that was sent.
function sendMessagePayLINK(
uint64 _destinationChainSelector,
address _receiver,
string calldata _text,
address _token,
uint256 _amount
)
external
onlyOwner
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
// address(linkToken) means fees are paid in LINK
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
_receiver,
_text,
_token,
_amount,
address(s_linkToken)
);
// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());
// Get the fee required to send the CCIP message
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > s_linkToken.balanceOf(address(this)))
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
s_linkToken.approve(address(router), fees);
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
IERC20(_token).approve(address(router), _amount);
// Send the message through the router and store the returned message ID
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);
// Emit an event with message details
emit MessageSent(
messageId,
_destinationChainSelector,
_receiver,
_text,
_token,
_amount,
address(s_linkToken),
fees
);
// Return the message ID
return messageId;
}
/// @notice Sends data and transfer tokens to receiver on the destination chain.
/// @notice Pay for fees in native gas.
/// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or POL on Polygon.
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param _receiver The address of the recipient on the destination blockchain.
/// @param _text The string data to be sent.
/// @param _token token address.
/// @param _amount token amount.
/// @return messageId The ID of the CCIP message that was sent.
function sendMessagePayNative(
uint64 _destinationChainSelector,
address _receiver,
string calldata _text,
address _token,
uint256 _amount
)
external
onlyOwner
onlyAllowlistedDestinationChain(_destinationChainSelector)
validateReceiver(_receiver)
returns (bytes32 messageId)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
// address(0) means fees are paid in native gas
Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
_receiver,
_text,
_token,
_amount,
address(0)
);
// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());
// Get the fee required to send the CCIP message
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
if (fees > address(this).balance)
revert NotEnoughBalance(address(this).balance, fees);
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
IERC20(_token).approve(address(router), _amount);
// Send the message through the router and store the returned message ID
messageId = router.ccipSend{value: fees}(
_destinationChainSelector,
evm2AnyMessage
);
// Emit an event with message details
emit MessageSent(
messageId,
_destinationChainSelector,
_receiver,
_text,
_token,
_amount,
address(0),
fees
);
// Return the message ID
return messageId;
}
/**
* @notice Returns the details of the last CCIP received message.
* @dev This function retrieves the ID, text, token address, and token amount of the last received CCIP message.
* @return messageId The ID of the last received CCIP message.
* @return text The text of the last received CCIP message.
* @return tokenAddress The address of the token in the last CCIP received message.
* @return tokenAmount The amount of the token in the last CCIP received message.
*/
function getLastReceivedMessageDetails()
public
view
returns (
bytes32 messageId,
string memory text,
address tokenAddress,
uint256 tokenAmount
)
{
return (
s_lastReceivedMessageId,
s_lastReceivedText,
s_lastReceivedTokenAddress,
s_lastReceivedTokenAmount
);
}
/**
* @notice Retrieves a paginated list of failed messages.
* @dev This function returns a subset of failed messages defined by `offset` and `limit` parameters. It ensures that the pagination parameters are within the bounds of the available data set.
* @param offset The index of the first failed message to return, enabling pagination by skipping a specified number of messages from the start of the dataset.
* @param limit The maximum number of failed messages to return, restricting the size of the returned array.
* @return failedMessages An array of `FailedMessage` struct, each containing a `messageId` and an `errorCode` (RESOLVED or FAILED), representing the requested subset of failed messages. The length of the returned array is determined by the `limit` and the total number of failed messages.
*/
function getFailedMessages(
uint256 offset,
uint256 limit
) external view returns (FailedMessage[] memory) {
uint256 length = s_failedMessages.length();
// Calculate the actual number of items to return (can't exceed total length or requested limit)
uint256 returnLength = (offset + limit > length)
? length - offset
: limit;
FailedMessage[] memory failedMessages = new FailedMessage[](
returnLength
);
// Adjust loop to respect pagination (start at offset, end at offset + limit or total length)
for (uint256 i = 0; i < returnLength; i++) {
(bytes32 messageId, uint256 errorCode) = s_failedMessages.at(
offset + i
);
failedMessages[i] = FailedMessage(messageId, ErrorCode(errorCode));
}
return failedMessages;
}
/// @notice The entrypoint for the CCIP router to call. This function should
/// never revert, all errors should be handled internally in this contract.
/// @param any2EvmMessage The message to process.
/// @dev Extremely important to ensure only router calls this.
function ccipReceive(
Client.Any2EVMMessage calldata any2EvmMessage
)
external
override
onlyRouter
onlyAllowlisted(
any2EvmMessage.sourceChainSelector,
abi.decode(any2EvmMessage.sender, (address))
) // Make sure the source chain and sender are allowlisted
{
/* solhint-disable no-empty-blocks */
try this.processMessage(any2EvmMessage) {
// Intentionally empty in this example; no action needed if processMessage succeeds
} catch (bytes memory err) {
// Could set different error codes based on the caught error. Each could be
// handled differently.
s_failedMessages.set(
any2EvmMessage.messageId,
uint256(ErrorCode.FAILED)
);
s_messageContents[any2EvmMessage.messageId] = any2EvmMessage;
// Don't revert so CCIP doesn't revert. Emit event instead.
// The message can be retried later without having to do manual execution of CCIP.
emit MessageFailed(any2EvmMessage.messageId, err);
return;
}
}
/// @notice Serves as the entry point for this contract to process incoming messages.
/// @param any2EvmMessage Received CCIP message.
/// @dev Transfers specified token amounts to the owner of this contract. This function
/// must be external because of the try/catch for error handling.
/// It uses the `onlySelf`: can only be called from the contract.
function processMessage(
Client.Any2EVMMessage calldata any2EvmMessage
)
external
onlySelf
onlyAllowlisted(
any2EvmMessage.sourceChainSelector,
abi.decode(any2EvmMessage.sender, (address))
) // Make sure the source chain and sender are allowlisted
{
// Simulate a revert for testing purposes
if (s_simRevert) revert ErrorCase();
_ccipReceive(any2EvmMessage); // process the message - may revert as well
}
/// @notice Allows the owner to retry a failed message in order to unblock the associated tokens.
/// @param messageId The unique identifier of the failed message.
/// @param tokenReceiver The address to which the tokens will be sent.
/// @dev This function is only callable by the contract owner. It changes the status of the message
/// from 'failed' to 'resolved' to prevent reentry and multiple retries of the same message.
function retryFailedMessage(
bytes32 messageId,
address tokenReceiver
) external onlyOwner {
// Check if the message has failed; if not, revert the transaction.
if (s_failedMessages.get(messageId) != uint256(ErrorCode.FAILED))
revert MessageNotFailed(messageId);
// Set the error code to RESOLVED to disallow reentry and multiple retries of the same failed message.
s_failedMessages.set(messageId, uint256(ErrorCode.RESOLVED));
// Retrieve the content of the failed message.
Client.Any2EVMMessage memory message = s_messageContents[messageId];
// This example expects one token to have been sent, but you can handle multiple tokens.
// Transfer the associated tokens to the specified receiver as an escape hatch.
IERC20(message.destTokenAmounts[0].token).safeTransfer(
tokenReceiver,
message.destTokenAmounts[0].amount
);
// Emit an event indicating that the message has been recovered.
emit MessageRecovered(messageId);
}
/// @notice Allows the owner to toggle simulation of reversion for testing purposes.
/// @param simRevert If `true`, simulates a revert condition; if `false`, disables the simulation.
/// @dev This function is only callable by the contract owner.
function setSimRevert(bool simRevert) external onlyOwner {
s_simRevert = simRevert;
}
function _ccipReceive(
Client.Any2EVMMessage memory any2EvmMessage
) internal override {
s_lastReceivedMessageId = any2EvmMessage.messageId; // fetch the messageId
s_lastReceivedText = abi.decode(any2EvmMessage.data, (string)); // abi-decoding of the sent text
// Expect one token to be transferred at once, but you can transfer several tokens.
s_lastReceivedTokenAddress = any2EvmMessage.destTokenAmounts[0].token;
s_lastReceivedTokenAmount = any2EvmMessage.destTokenAmounts[0].amount;
emit MessageReceived(
any2EvmMessage.messageId,
any2EvmMessage.sourceChainSelector, // fetch the source chain identifier (aka selector)
abi.decode(any2EvmMessage.sender, (address)), // abi-decoding of the sender address,
abi.decode(any2EvmMessage.data, (string)),
any2EvmMessage.destTokenAmounts[0].token,
any2EvmMessage.destTokenAmounts[0].amount
);
}
/// @notice Construct a CCIP message.
/// @dev This function will create an EVM2AnyMessage struct with all the necessary information for programmable tokens transfer.
/// @param _receiver The address of the receiver.
/// @param _text The string data to be sent.
/// @param _token The token to be transferred.
/// @param _amount The amount of the token to be transferred.
/// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas.
/// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message.
function _buildCCIPMessage(
address _receiver,
string calldata _text,
address _token,
uint256 _amount,
address _feeTokenAddress
) private pure returns (Client.EVM2AnyMessage memory) {
// Set the token amounts
Client.EVMTokenAmount[]
memory tokenAmounts = new Client.EVMTokenAmount[](1);
Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
token: _token,
amount: _amount
});
tokenAmounts[0] = tokenAmount;
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({
receiver: abi.encode(_receiver), // ABI-encoded receiver address
data: abi.encode(_text), // ABI-encoded string
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit and allowing out-of-order execution.
// Best Practice: For simplicity, the values are hardcoded. It is advisable to use a more dynamic approach
// where you set the extra arguments off-chain. This allows adaptation depending on the lanes, messages,
// and ensures compatibility with future CCIP upgrades. Read more about it here: https://docs.chain.link/ccip/best-practices#using-extraargs
Client.EVMExtraArgsV2({
gasLimit: 400_000, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
});
return evm2AnyMessage;
}
/// @notice Fallback function to allow the contract to receive Ether.
/// @dev This function has no function body, making it a default function for receiving Ether.
/// It is automatically called when Ether is sent to the contract without any data.
receive() external payable {}
/// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract.
/// @dev This function reverts if there are no funds to withdraw or if the transfer fails.
/// It should only be callable by the owner of the contract.
/// @param _beneficiary The address to which the Ether should be sent.
function withdraw(address _beneficiary) public onlyOwner {
// Retrieve the balance of this contract
uint256 amount = address(this).balance;
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
// Attempt to send the funds, capturing the success status and discarding any return data
(bool sent, ) = _beneficiary.call{value: amount}("");
// Revert if the send failed, with information about the attempted transfer
if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount);
}
/// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token.
/// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw.
/// @param _beneficiary The address to which the tokens will be sent.
/// @param _token The contract address of the ERC20 token to be withdrawn.
function withdrawToken(
address _beneficiary,
address _token
) public onlyOwner {
// Retrieve the balance of this contract
uint256 amount = IERC20(_token).balanceOf(address(this));
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
IERC20(_token).safeTransfer(_beneficiary, amount);
}
}
[Open in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/CCIP/ProgrammableDefensiveTokenTransfers.sol&autoCompile=true)
[What is Remix?](/getting-started/conceptual-overview#what-is-remix)
### [Deploy your contracts](#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](#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 | `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)
. |
| \_receiver | Your receiver contract address at _Ethereum Sepolia_.
The destination contract address. |
| \_text | `Hello World!`
Any `string` |
| \_token | `0xD21341536c5cF5EB1bcb58f6723cE26e8D8E90e4`
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 | `1000000000000000`
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_.
3. Open the [CCIP explorer](https://ccip.chain.link/)
and search your cross-chain transaction using the transaction hash.

4. The CCIP transaction is completed once the status is marked as "Success". In this example, the CCIP message ID is _0x120367995ef71f83d64a05bd7793862afda9d04049da4cb32851934490d03ae4_.

5. 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).
6. 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. |

7. After confirming the transaction, you can open it in a block explorer. Notice that the locked funds were transferred to the `tokenReceiver` address.

8. 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](#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/programmable-token-transfers)
tutorial. Hence, you can refer to its [code explanation](/ccip/tutorials/programmable-token-transfers#explanation)
. We will only explain the main differences.
### [Sending messages](#sending-messages)
The `sendMessagePayLINK` function is similar to the `sendMessagePayLINK` function in the [_Transfer Tokens with Data_](/ccip/tutorials/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](#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/v1.5.1/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](#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.
What's next
-----------
* [\> Transfer Tokens Between EOAs](/ccip/tutorials/transfer-tokens-from-eoa)
* [\> See example cross-chain dApps and tools](/ccip/examples)
* [\> CCIP Directory](/ccip/directory)
* [\> Learn CCIP best practices](/ccip/best-practices)
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Set Token Pool rate limits using Hardhat | Chainlink Documentation
On this page
Set Token Pool rate limits using Hardhat
========================================
Guide Versions
--------------
This guide is available in multiple versions. Choose the one that matches your needs.
Hardhat
[Hardhat](/ccip/tutorials/cross-chain-tokens/update-rate-limiters-hardhat)
[Foundry](/ccip/tutorials/cross-chain-tokens/update-rate-limiters-foundry)
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](#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/cross-chain-tokens/register-from-eoa-burn-mint-hardhat)
* [Register from an EOA (Lock & Mint)](/ccip/tutorials/cross-chain-tokens/register-from-eoa-lock-mint-hardhat)
* **Admin access**: Ensure you have the necessary privileges to call the [setChainRateLimiterConfig](/ccip/api-reference/v1.5.1/token-pool#setchainratelimiterconfig)
function for your token pools.
[Before You Begin](#before-you-begin)
--------------------------------------
1. Make sure you have Node.js v18 or above installed. If not, **install Node.js v18**:
[Download Node.js 18](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:
nvm use 18
Verify that the correct version of Node.js is installed:
node -v
Example output:
$ node -v
v18.7.0
2. **Clone the repository and navigate to the project directory:**
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
3. **Install dependencies for the project:**
npm install
4. **Compile the project:**
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:
npx env-enc set-pw
2. Set up a `.env.enc` file with the necessary variables for Avalanche Fuji and Arbitrum Sepolia testnets. Use the following command to add the variables:
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/)
.
* `ARBITRUM_SEPOLIA_RPC_URL`: A URL for the _Arbitrum 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](#tutorial)
----------------------
### [Review current rate limiter settings](#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., `arbitrumSepolia` for Fuji pool). | N/A | Yes |
| `network` | The blockchain on which to execute the query (e.g., `avalancheFuji`, `arbitrumSepolia`). | N/A | Yes |
1. **Get rate limiter settings for the token pool on Avalanche Fuji**:
npx hardhat getCurrentRateLimits \
--pooladdress 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9 \
--remotechain arbitrumSepolia \
--network avalancheFuji
Example output:
2024-12-02T22:20:25.611Z info:
Rate Limiter States for Chain: arbitrumSepolia
2024-12-02T22:20:25.613Z info: Pool Address: 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9
2024-12-02T22:20:25.613Z info: Chain Selector: 3478487238524512106
2024-12-02T22:20:25.613Z info:
Outbound Rate Limiter:
2024-12-02T22:20:25.613Z info: Enabled: false
2024-12-02T22:20:25.613Z info: Capacity: 0
2024-12-02T22:20:25.613Z info: Rate: 0
2024-12-02T22:20:25.613Z info: Tokens: 0
2024-12-02T22:20:25.614Z info: Last Updated: 1733178019
2024-12-02T22:20:25.614Z info:
Inbound Rate Limiter:
2024-12-02T22:20:25.614Z info: Enabled: false
2024-12-02T22:20:25.614Z info: Capacity: 0
2024-12-02T22:20:25.614Z info: Rate: 0
2024-12-02T22:20:25.614Z info: Tokens: 0
2024-12-02T22:20:25.614Z info: Last Updated: 1733178019
2. **Get rate limiter settings for the token pool on Arbitrum Sepolia**:
npx hardhat getCurrentRateLimits \
--pooladdress 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65 \
--remotechain avalancheFuji \
--network arbitrumSepolia
Example output:
2024-12-02T22:23:31.366Z info:
Rate Limiter States for Chain: avalancheFuji
2024-12-02T22:23:31.367Z info: Pool Address: 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65
2024-12-02T22:23:31.367Z info: Chain Selector: 14767482510784806043
2024-12-02T22:23:31.367Z info:
Outbound Rate Limiter:
2024-12-02T22:23:31.367Z info: Enabled: false
2024-12-02T22:23:31.367Z info: Capacity: 0
2024-12-02T22:23:31.367Z info: Rate: 0
2024-12-02T22:23:31.367Z info: Tokens: 0
2024-12-02T22:23:31.367Z info: Last Updated: 1733178210
2024-12-02T22:23:31.367Z info:
Inbound Rate Limiter:
2024-12-02T22:23:31.367Z info: Enabled: true
2024-12-02T22:23:31.367Z info: Capacity: 0
2024-12-02T22:23:31.367Z info: Rate: 0
2024-12-02T22:23:31.368Z info: Tokens: 0
2024-12-02T22:23:31.368Z info: Last Updated: 1733178210
### [Update rate limiter settings](#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/v1.5.1/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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | N/A | Yes |
| `ratelimiter` | Specifies which rate limiters to update: `inbound`, `outbound`, or `both`. | `both` | No |
| `outboundratelimitenabled` | A flag indicating whether to enable outbound rate limits for cross-chain transfers (`true` or `false`). | `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` | A flag indicating whether to enable inbound rate limits for cross-chain transfers (`true` or `false`). | `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`, `arbitrumSepolia`, `baseSepolia`, and `sepolia`. | N/A | Yes |
**Command syntax**:
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 Arbitrum 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 Arbitrum 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.
npx hardhat updateRateLimiters \
--pooladdress \
--remotechain arbitrumSepolia \
--ratelimiter both \
--outboundratelimitenabled true \
--outboundratelimitcapacity 10000000000000000000 \
--outboundratelimitrate 100000000000000000 \
--inboundratelimitenabled true \
--inboundratelimitcapacity 20000000000000000000 \
--inboundratelimitrate 100000000000000000 \
--network avalancheFuji
Expected output:
$ npx hardhat updateRateLimiters \
--pooladdress 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9 \
--remotechain arbitrumSepolia \
--ratelimiter both \
--outboundratelimitenabled true \
--outboundratelimitcapacity 10000000000000000000 \
--outboundratelimitrate 100000000000000000 \
--inboundratelimitenabled true \
--inboundratelimitcapacity 20000000000000000000 \
--inboundratelimitrate 100000000000000000 \
--network avalancheFuji
2024-12-03T02:03:29.692Z info: Current Rate Limiters for token pool: 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9
2024-12-03T02:03:29.693Z info: Outbound Rate Limiter:
2024-12-03T02:03:29.693Z info: Enabled: true
2024-12-03T02:03:29.693Z info: Capacity: 1000000000000000000000
2024-12-03T02:03:29.693Z info: Rate: 100000000000000000000
2024-12-03T02:03:29.693Z info: Inbound Rate Limiter:
2024-12-03T02:03:29.693Z info: Enabled: true
2024-12-03T02:03:29.694Z info: Capacity: 1000000000000000000000
2024-12-03T02:03:29.694Z info: Rate: 100000000000000000000
2024-12-03T02:03:29.694Z info:
2024-12-03T02:03:29.694Z info: ========== Updating Rate Limiters ==========
2024-12-03T02:03:29.694Z info: New Outbound Rate Limiter:
2024-12-03T02:03:29.694Z info: Enabled: true
2024-12-03T02:03:29.694Z info: Capacity: 10000000000000000000
2024-12-03T02:03:29.694Z info: Rate: 100000000000000000
2024-12-03T02:03:29.694Z info: New Inbound Rate Limiter:
2024-12-03T02:03:29.694Z info: Enabled: true
2024-12-03T02:03:29.694Z info: Capacity: 20000000000000000000
2024-12-03T02:03:29.694Z info: Rate: 100000000000000000
2024-12-03T02:03:29.694Z info: Updating both rate limiters...
2024-12-03T02:03:38.949Z info: Transaction hash: 0xf1e3548461bb6005d0105994ddb6d324d2276852575d51edba7a3a8239a88523
2024-12-03T02:03:38.950Z info: Rate limiters updated successfully
2. **Update rate limiter settings for the token pool on Arbitrum Sepolia**: Replace `` with your token pool address.
npx hardhat updateRateLimiters \
--pooladdress \
--remotechain avalancheFuji \
--ratelimiter both \
--outboundratelimitenabled true \
--outboundratelimitcapacity 20000000000000000000 \
--outboundratelimitrate 100000000000000000 \
--inboundratelimitenabled true \
--inboundratelimitcapacity 10000000000000000000 \
--inboundratelimitrate 100000000000000000 \
--network arbitrumSepolia
Expected output:
$ npx hardhat updateRateLimiters \
--pooladdress 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65 \
--remotechain avalancheFuji \
--ratelimiter both \
--outboundratelimitenabled true \
--outboundratelimitcapacity 20000000000000000000 \
--outboundratelimitrate 100000000000000000 \
--inboundratelimitenabled true \
--inboundratelimitcapacity 10000000000000000000 \
--inboundratelimitrate 100000000000000000 \
--network arbitrumSepolia
2024-12-03T02:04:23.683Z info: Current Rate Limiters for token pool: 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65
2024-12-03T02:04:23.684Z info: Outbound Rate Limiter:
2024-12-03T02:04:23.684Z info: Enabled: true
2024-12-03T02:04:23.684Z info: Capacity: 1000000000000000000000
2024-12-03T02:04:23.684Z info: Rate: 100000000000000000000
2024-12-03T02:04:23.684Z info: Inbound Rate Limiter:
2024-12-03T02:04:23.684Z info: Enabled: true
2024-12-03T02:04:23.684Z info: Capacity: 1000000000000000000000
2024-12-03T02:04:23.685Z info: Rate: 100000000000000000000
2024-12-03T02:04:23.685Z info:
2024-12-03T02:04:23.685Z info: ========== Updating Rate Limiters ==========
2024-12-03T02:04:23.685Z info: New Outbound Rate Limiter:
2024-12-03T02:04:23.685Z info: Enabled: true
2024-12-03T02:04:23.685Z info: Capacity: 20000000000000000000
2024-12-03T02:04:23.685Z info: Rate: 100000000000000000
2024-12-03T02:04:23.685Z info: New Inbound Rate Limiter:
2024-12-03T02:04:23.685Z info: Enabled: true
2024-12-03T02:04:23.685Z info: Capacity: 10000000000000000000
2024-12-03T02:04:23.685Z info: Rate: 100000000000000000
2024-12-03T02:04:23.685Z info: Updating both rate limiters...
2024-12-03T02:04:24.913Z info: Transaction hash: 0x23aed05dee94911eb16dcf0123698d64521ec19856416a952ce0b2fb31973c32
2024-12-03T02:04:24.914Z info: Rate limiters updated successfully
### [Verify the new rate limiter settings](#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**:
npx hardhat getCurrentRateLimits \
--pooladdress \
--remotechain arbitrumSepolia \
--network avalancheFuji
Example output:
$ npx hardhat getCurrentRateLimits \
--pooladdress 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9 \
--remotechain arbitrumSepolia \
--network avalancheFuji
2024-12-03T02:09:39.584Z info:
Rate Limiter States for Chain: arbitrumSepolia
2024-12-03T02:09:39.586Z info: Pool Address: 0x1aFafBAd2528747cf464F62e9e57238FDE8f2BA9
2024-12-03T02:09:39.586Z info: Chain Selector: 3478487238524512106
2024-12-03T02:09:39.587Z info:
Outbound Rate Limiter:
2024-12-03T02:09:39.587Z info: Enabled: true
2024-12-03T02:09:39.587Z info: Capacity: 10000000000000000000
2024-12-03T02:09:39.587Z info: Rate: 100000000000000000
2024-12-03T02:09:39.587Z info: Tokens: 10000000000000000000
2024-12-03T02:09:39.587Z info: Last Updated: 1733191776
2024-12-03T02:09:39.588Z info:
Inbound Rate Limiter:
2024-12-03T02:09:39.588Z info: Enabled: true
2024-12-03T02:09:39.588Z info: Capacity: 20000000000000000000
2024-12-03T02:09:39.588Z info: Rate: 100000000000000000
2024-12-03T02:09:39.588Z info: Tokens: 20000000000000000000
2024-12-03T02:09:39.588Z info: Last Updated: 1733191776
2. **Verify the updated rate limiter settings for the token pool on Arbitrum Sepolia**:
npx hardhat getCurrentRateLimits \
--pooladdress \
--remotechain avalancheFuji \
--network arbitrumSepolia
Example output:
$ npx hardhat getCurrentRateLimits \
--pooladdress 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65 \
--remotechain avalancheFuji \
--network arbitrumSepolia
2024-12-03T02:10:43.993Z info:
Rate Limiter States for Chain: avalancheFuji
2024-12-03T02:10:43.993Z info: Pool Address: 0x86F2521dB26AAEdDE5fe11cFA69EE5EfBeBc4E65
2024-12-03T02:10:43.993Z info: Chain Selector: 14767482510784806043
2024-12-03T02:10:43.993Z info:
Outbound Rate Limiter:
2024-12-03T02:10:43.993Z info: Enabled: true
2024-12-03T02:10:43.994Z info: Capacity: 20000000000000000000
2024-12-03T02:10:43.994Z info: Rate: 100000000000000000
2024-12-03T02:10:43.994Z info: Tokens: 20000000000000000000
2024-12-03T02:10:43.994Z info: Last Updated: 1733191842
2024-12-03T02:10:43.994Z info:
Inbound Rate Limiter:
2024-12-03T02:10:43.994Z info: Enabled: true
2024-12-03T02:10:43.994Z info: Capacity: 10000000000000000000
2024-12-03T02:10:43.994Z info: Rate: 100000000000000000
2024-12-03T02:10:43.994Z info: Tokens: 10000000000000000000
2024-12-03T02:10:43.994Z info: Last Updated: 1733191842
### [Test the rate limiter settings](#test-the-rate-limiter-settings)
To verify the rate limiter settings, initiate a cross-chain transfer between the token pools on Avalanche Fuji and Arbitrum 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 `0xa6aca9013b111228433da2aea186cb267d74fc0f` on Avalanche Fuji, which has burn and mint privileges for the token at address `0xcb19276d94d82bd0aceb9fd960df6c69e42ee1c6`. We will transfer this token from Avalanche Fuji to Arbitrum 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 Arbitrum Sepolia. This transfer should fail because the capacity is less than the number of tokens being transferred.
Command:
npx hardhat transferTokens --tokenaddress --amount 1000000000000000000001 --destinationchain arbitrumSepolia --receiveraddress --network avalancheFuji
Expected output:
$ npx hardhat transferTokens --tokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add --amount 1000000000000000000001 --destinationchain arbitrumSepolia --receiveraddress 0x9d087fC03ae39b088326b67fA3C788236645b717 --network avalancheFuji
2024-12-03T02:12:30.674Z info: Estimated fees: 17678069377595357
2024-12-03T02:12:30.678Z info: Approving 1000000000000000000001 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-03T02:12:34.556Z info: Approving 17678069377595357 LINK to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-03T02:12:39.409Z info: Transferring 1000000000000000000001 of 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia with 17678069377595357 of LINK as fees
Simulation failed
Decoded error from factory EVM2EVMOnRamp__factory: TokenMaxCapacityExceeded Result(3) [\
10000000000000000000n,\
1000000000000000000001n,\
'0x16F6b0f41b9217857551e29F38F99975a2fc9add'\
]
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 Arbitrum 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:
npx hardhat transferTokens --tokenaddress --amount 10000000000000000000 --destinationchain arbitrumSepolia --receiveraddress --network avalancheFuji
Expected output:
$ npx hardhat transferTokens --tokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add --amount 10000000000000000000 --destinationchain arbitrumSepolia --receiveraddress 0x9d087fC03ae39b088326b67fA3C788236645b717 --network avalancheFuji
2024-12-03T02:15:03.303Z info: Estimated fees: 17678069377595357
2024-12-03T02:15:03.307Z info: Approving 10000000000000000000 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-03T02:15:10.205Z info: Approving 17678069377595357 LINK to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-03T02:15:16.795Z info: Transferring 10000000000000000000 of 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia with 17678069377595357 of LINK as fees
2024-12-03T02:15:20.917Z info: Transaction hash: 0xcbceddfb41c7cf34a012856d486a7f135f76d9c9081fbcd6fa81e2735f595231
2024-12-03T02:15:20.944Z info: Message dispatched. Message id: 0x28f907e8a21196932a74e4abc8270de6f0ef08004e25227ee8345d81e86518a5
2024-12-03T02:15:20.945Z info: ✅ Transferred 10000000000000000000 of 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia. Transaction hash: 0xcbceddfb41c7cf34a012856d486a7f135f76d9c9081fbcd6fa81e2735f595231 - CCIP message id: 0x28f907e8a21196932a74e4abc8270de6f0ef08004e25227ee8345d81e86518a5
2024-12-03T02:15:20.945Z info: Check status of message on https://ccip.chain.link/msg/0x28f907e8a21196932a74e4abc8270de6f0ef08004e25227ee8345d81e86518a5
2. **Second transfer** (Failed):
Command:
npx hardhat transferTokens --tokenaddress --amount 10000000000000000000 --destinationchain arbitrumSepolia --receiveraddress --network avalancheFuji
Expected output:
$ npx hardhat transferTokens --tokenaddress 0x16F6b0f41b9217857551e29F38F99975a2fc9add --amount 10000000000000000000 --destinationchain arbitrumSepolia --receiveraddress 0x9d087fC03ae39b088326b67fA3C788236645b717 --network avalancheFuji
2024-12-03T02:15:27.313Z info: Estimated fees: 17678069377595357
2024-12-03T02:15:27.315Z info: Approving 10000000000000000000 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-03T02:15:31.487Z info: Approving 17678069377595357 LINK to 0xF694E193200268f9a4868e4Aa017A0118C9a8177
2024-12-03T02:15:33.625Z info: Transferring 10000000000000000000 of 0x16F6b0f41b9217857551e29F38F99975a2fc9add to 0x9d087fC03ae39b088326b67fA3C788236645b717 on chain arbitrumSepolia with 17678069377595357 of LINK as fees
Simulation failed
Decoded error from factory EVM2EVMOnRamp__factory: TokenRateLimitReached Result(3) [\
84n,\
1600000000000000000n,\
'0x16F6b0f41b9217857551e29F38F99975a2fc9add'\
]
Notice in the logs that the transfer failed because the rate limit was exceeded: `TokenRateLimitReached`.
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---
# Chainlink Data Streams | Chainlink Documentation
On this page
Chainlink Data Streams
======================
Chainlink Data Streams delivers low-latency market data offchain, which you can verify onchain. This approach provides decentralized applications (dApps) with on-demand access to high-frequency market data backed by decentralized, fault-tolerant, and transparent infrastructure.
Traditional push-based oracles update onchain data at set intervals or when certain price thresholds are met. In contrast, Chainlink Data Streams uses a pull-based design that preserves trust-minimization with onchain verification.
[Sub-Second Data and Commit-and-Reveal](#sub-second-data-and-commit-and-reveal)
--------------------------------------------------------------------------------
Chainlink Data Streams supports sub-second data resolution for latency-sensitive use cases by retrieving data only when needed. You can combine the data with any transaction in near real time. A “commit-and-reveal” approach mitigates frontrunning by making trade data and stream data visible atomically on-chain.
[Comparison to push-based oracles](#comparison-to-push-based-oracles)
----------------------------------------------------------------------
Chainlink's push-based oracles regularly publish price data onchain. By contrast, Chainlink Data Streams relies on a pull-based design, letting you retrieve a report and verify it onchain whenever you need it. Verification confirms that the decentralized oracle network (DON) agreed on and signed the data. Some applications only need onchain data at fixed intervals, which suits push-based oracles. However, others require higher-frequency updates and lower latency. Pull-based oracles meet these needs and still provide cryptographic guarantees about data accuracy.

Pull-based oracles also operate more efficiently by retrieving data only when necessary. For example, a decentralized exchange might fetch a Data Streams report and verify it onchain only when a user executes a trade, rather than continuously pushing updates that might not be immediately used.
[Comprehensive market insights](#comprehensive-market-insights)
----------------------------------------------------------------
Chainlink Data Streams offers price points such as mid prices and [Liquidity-Weighted Bid and Ask](/data-streams/concepts/liquidity-weighted-prices)
(LWBA) for Crypto Streams. LWBA prices reflect current order book conditions, providing deeper insight into market liquidity and depth. With additional parameters, such as volatility and liquidity metrics, Data Streams helps protocols enhance trading accuracy, improve onchain risk management, and dynamically adjust margins or settlement conditions in response to real-time market shifts.
[High availability and resilient infrastructure](#high-availability-and-resilient-infrastructure)
--------------------------------------------------------------------------------------------------
Data Streams API services use an [active-active multi-site deployment](/data-streams/architecture#active-active-multi-site-deployment)
model across multiple distributed and isolated origins. This architecture ensures continuous operations even if one origin fails, delivering robust fault tolerance and high availability.
[Use cases](#use-cases)
------------------------
Access to low-latency, high-frequency data enables a variety of onchain applications:
* **Perpetual Futures:** Sub-second data and frontrunning mitigation allow onchain perpetual futures protocols to compete with centralized exchanges on performance while retaining transparency and decentralization.
* **Options:** Pull-based oracles provide timely settlement of options contracts with the added benefit of market liquidity data to support dynamic onchain risk management.
* **Prediction Markets:** High-frequency updates let participants act on real-time data, ensuring quick reactions to events and accurate settlement.
[Data Streams implementations](#data-streams-implementations)
--------------------------------------------------------------
### [Streams Trade](#streams-trade)
Streams Trade combines Chainlink Data Streams with Chainlink Automation to deliver automated trade execution with frontrunning mitigation. This approach suits dApps that require automated, trust-minimized trade execution and high-frequency market data. Chainlink Automation ensures near-instant onchain access to data while keeping the execution of user transactions fair and reliable.
[Learn more about Streams Trade](/data-streams/streams-trade)
### [Streams Direct](#streams-direct)
Streams Direct provides direct access to Data Streams through SDKs and APIs. This solution suits applications that need programmatic data retrieval and verification, whether for offchain display or onchain settlement. It delivers high-frequency, low-latency data for any custom use case.
[Learn more about Streams Direct](/data-streams/streams-direct)
What's next
-----------
* [\> Learn how to retrieve Data Streams reports with the Streams Trade implementation](/data-streams/getting-started)
* [\> Learn how to fetch and decode Data Streams reports with the Streams Direct API](/data-streams/tutorials/streams-direct/streams-direct-api)
* [\> Find the list of available Stream IDs](/data-streams/crypto-streams)
* [\> Find the schema of data to expect from Data Streams reports](/data-streams/reference/report-schema)
Get the latest Chainlink content straight to your inbox.
--------------------------------------------------------
Email Address
---