Enable your tokens in CCIP (Burn & Mint): Register from an EOA using Foundry
Guide Versions
This guide is available in multiple versions. Choose the one that matches your needs.
This tutorial will guide you through the process of enabling your own tokens in CCIP using Foundry. 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:
-
Deploying Tokens: You will deploy your
BurnMintERC20tokens on the Avalanche Fuji and Arbitrum Sepolia testnets. -
Deploying Token Pools: Once your tokens are deployed, you will deploy
BurnMintTokenPooltoken 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. -
Claiming Mint and Burn Roles: You will claim the mint and burn roles for the token pools, allowing your token pools to control how tokens are minted and burned during cross-chain transfers.
-
Claiming and Accepting the Admin Role: This is a two-step process:
-
You will call the
RegistryModuleOwnerCustomcontract'sregisterAdminViaOwnerfunction to register your EOA as the token admin. This role is required to enable your token in CCIP. -
Once claimed, you will call the
TokenAdminRegistrycontract'sacceptAdminRolefunction to complete the registration process.
-
-
Linking Tokens to Pools: You will call the
TokenAdminRegistrycontract'ssetPoolfunction to associate each token with its respective token pool. -
Configuring Token Pools: You will call the
applyChainUpdatesfunction on your token pools to configure each pool by setting cross-chain transfer parameters, such as token pool rate limits and enabled destination chains. -
Minting Tokens: You will call the
mintfunction to mint tokens on Avalanche Fuji for your EOA. These tokens will later be used to test cross-chain transfers to Arbitrum Sepolia. -
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
-
Make sure you have Node.js v22.10.0 or above installed. If not, install Node.js v22.10.0:
Download Node.js v22.10.0 if you don't have it installed. Optionally, you can use the nvm package to switch between Node.js versions:
nvm use 22Verify that the correct version of Node.js is installed:
node -vExample output:
$ node -v v22.15.0 -
Install Foundry: If you haven't already, follow the instructions in the Foundry documentation to install Foundry.
-
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/foundry -
Set up your environment: Create a
.envfile by copying the.env.examplefile, and fill in the required values:cp .env.example .envExample
.envfile:PRIVATE_KEY=<your_private_key> RPC_URL_FUJI=<your_rpc_url_fuji> RPC_URL_ARBITRUM_SEPOLIA=<your_rpc_url_arbitrum_sepolia> ETHERSCAN_API_KEY=<your_etherscan_api_key>Variables to configure:
PRIVATE_KEY: The private key for your testnet wallet. If you use MetaMask, you can follow this guide to export your private key. Note: This key is required for signing transactions like token transfers.RPC_URL_FUJI: The RPC URL for the Fuji testnet. You can get this from the Alchemy or Infura website.RPC_URL_ARBITRUM_SEPOLIA: The RPC URL for the Arbitrum Sepolia testnet. You can get this from the Alchemy or Infura website.ETHERSCAN_API_KEY: An API key from Etherscan to verify your contracts. You can obtain one from Etherscan.
-
Load the environment variables into the terminal session where you will run the commands:
source .env -
Install dependencies:
forge install && npm install -
Compile the contracts:
forge compile
Optional: Configure your config.json file
Before running the scripts, you can customize the config.json within the script folder to your needs.
-
By default, this guide and the accompanying scripts are configured to work with Avalanche Fuji (chain ID
43113) and Arbitrum Sepolia (chain ID421614). If you do not make any changes to theconfig.jsonfile, you will mint your tokens on Avalanche Fuji and transfer them to Arbitrum Sepolia. -
If you wish to customize the token parameters such as the token name, symbol, decimals, amount to mint, or amount to transfer, you can modify these fields in the
config.jsonfile accordingly.Note: If you decide to change the chain IDs in the
remoteChainssection to use different networks, you will also need to update your.envfile with the appropriate RPC URLs for those networks.
Example config.json file:
{
"BnMToken": {
"name": "BnM KH",
"symbol": "BnMkh",
"decimals": 18,
"maxSupply": 0,
"preMint": 100000000000000000000,
"ccipAdminAddress": "0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA"
},
"tokenAmountToMint": 1000000000000000000000,
"tokenAmountToTransfer": 100000000000000000000,
"feeType": "link",
"remoteChains": {
"43113": 421614,
"421614": 43113
}
}
The config.json file contains the following parameters:
| Field | Description |
|---|---|
name | The name of the token you are going to deploy. Replace "BnM KH" with your desired token name. |
symbol | The symbol of the token. Replace "BnMkh" with your desired token symbol. |
decimals | The number of decimals for the token (usually 18 for standard ERC tokens). |
maxSupply | The maximum supply of tokens (in the smallest unit, based on decimals). When maxSupply is 0, the supply is unlimited. |
preMint | The amount of tokens to be minted to the owner at the time of deployment, specified (in the smallest unit, based on decimals). When preMint is 0, no tokens will be minted to the owner during deployment. |
ccipAdminAddress | The address of the CCIP admin. Replace the address value 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA with your token deployer account address. |
| --- | ----- |
tokenAmountToMint | The amount of tokens to mint when running the minting script. This value should be specified in wei (1 token with 18 decimals = 1000000000000000000 wei). |
| --- | ----- |
tokenAmountToTransfer | The amount of tokens to transfer when running the token transfer script. Specify the number of tokens you want to transfer across chains. |
| --- | ----- |
feeType | Defines the fee type for transferring tokens across chains. Options are "link" (for paying fees in LINK tokens) or "native" (for paying fees in native tokens). |
| --- | ----- |
remoteChains | Defines the relationship between source and remote (destination) chain IDs. The keys in this object are the current chain IDs, and the values represent the corresponding remote chain. Example: "43113": 421614 means that if you're running a script on Avalanche Fuji (chain ID 43113), the remote chain is Arbitrum Sepolia (chain ID 421614). |
Tutorial
Deploy Tokens
In this step, you will use the DeployToken.s.sol script to deploy your tokens on two testnets, Avalanche Fuji and Arbitrum Sepolia.
The script reads the config.json file to get the token name, symbol, decimals, maximum supply, and pre-mint amount. Based on these configurations, it deploys the appropriate token contract and grants mint and burn roles to your EOA.
-
Deploy tokens on Avalanche Fuji:
forge script script/DeployToken.s.sol --rpc-url $RPC_URL_FUJI --private-key $PRIVATE_KEY --broadcast --verifyExample output:
Deployed BurnMintERC20 at: 0x56e4CFf5009908D8d8C5Aac5479574f9aC66e145 Granted mint and burn roles to: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA Writing deployed token address to file: ./script/output/deployedToken_avalancheFuji.json -
Deploy tokens on Arbitrum Sepolia:
forge script script/DeployToken.s.sol --rpc-url $RPC_URL_ARBITRUM_SEPOLIA --private-key $PRIVATE_KEY --broadcast --verifyExample output:
Deployed BurnMintERC20 at: 0x8E9A370Ff35E9A5C081818B5C9EcC3E3EA89396b Granted mint and burn roles to: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA Writing deployed token address to file: ./script/output/deployedToken_arbitrumSepolia.json
Deploy Token Pools
In this step, you will use the DeployBurnMintTokenPool.s.sol script to deploy Burn & Mint token pools for the tokens on both testnets, Avalanche Fuji and Arbitrum Sepolia and grant mint and burn roles to the token pools.
-
Deploy a Burn & Mint token pool for the deployed token on Avalanche Fuji:
forge script script/DeployBurnMintTokenPool.s.sol --rpc-url $RPC_URL_FUJI --private-key $PRIVATE_KEY --broadcast --verifyExample output:
Burn & Mint token pool deployed to: 0xeB841acbc25549D4deB6Ed9401f3CbB529F3DC3e Granted mint and burn roles to token pool: 0xeB841acbc25549D4deB6Ed9401f3CbB529F3DC3e Writing deployed token pool address to file: ./script/output/deployedTokenPool_avalancheFuji.json -
Deploy a Burn & Mint token pool for the deployed token on Arbitrum Sepolia:
forge script script/DeployBurnMintTokenPool.s.sol --rpc-url $RPC_URL_ARBITRUM_SEPOLIA --private-key $PRIVATE_KEY --broadcast --verifyExample output:
Burn & Mint token pool deployed to: 0x346e11C1a869b88A8033e40B18Efdc4eCD9FFd02 Granted mint and burn roles to token pool: 0x346e11C1a869b88A8033e40B18Efdc4eCD9FFd02 Writing deployed token pool address to file: ./script/output/deployedTokenPool_arbitrumSepolia.json
Claim Admin Role
In this step, you will use the ClaimAdmin.s.sol script to register your EOA as the administrator for the deployed tokens on both Avalanche Fuji and Arbitrum Sepolia testnets. This process involves interacting with the RegistryModuleOwnerCustom contract to set up your EOA as the admin.
The script will claim the admin role using getCCIPAdmin() function and validate by comparing the returned value with the value of the ccipAdminAddress field of your config.json file. So, make sure to replace the value of the ccipAdminAddress field with your EOA or token deployer account address.
-
Claim the Admin role for the token on Avalanche Fuji:
forge script script/ClaimAdmin.s.sol --rpc-url $RPC_URL_FUJI --private-key $PRIVATE_KEY --broadcastExample output:
Current token admin: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA Claiming admin of the token via getCCIPAdmin() for CCIP admin: 0x45C90FBb5acC1a5c156a401B56Fea55e69E7669d Admin claimed successfully for token: 0x56e4CFf5009908D8d8C5Aac5479574f9aC66e145 -
Claim the Admin role for the token on Arbitrum Sepolia:
forge script script/ClaimAdmin.s.sol --rpc-url $RPC_URL_ARBITRUM_SEPOLIA --private-key $PRIVATE_KEY --broadcastExample output:
Current token admin: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA Claiming admin of the token via getCCIPAdmin() for CCIP admin: 0x45C90FBb5acC1a5c156a401B56Fea55e69E7669d Admin claimed successfully for token: 0x8E9A370Ff35E9A5C081818B5C9EcC3E3EA89396b
Accept Admin Role
In this step, you will use the AcceptAdminRole.s.sol script to accept the admin role for the deployed tokens on both Avalanche Fuji and Arbitrum Sepolia testnets. Once you have claimed the role, accepting the role finalizes your control over the token administration.
-
Accept the admin role for the token on Avalanche Fuji:
forge script script/AcceptAdminRole.s.sol --rpc-url $RPC_URL_FUJI --private-key $PRIVATE_KEY --broadcastExample output:
Accepted admin role for token: 0x56e4CFf5009908D8d8C5Aac5479574f9aC66e145 -
Accept the admin role for the token on Arbitrum Sepolia:
forge script script/AcceptAdminRole.s.sol --rpc-url $RPC_URL_ARBITRUM_SEPOLIA --private-key $PRIVATE_KEY --broadcastExample output:
Accepted admin role for token: 0x8E9A370Ff35E9A5C081818B5C9EcC3E3EA89396b
Set Token Pool
In this step, you will use the SetPool.s.sol script to link each token with its respective token pool on both testnets.
-
Link the token to its respective token pool on Avalanche Fuji:
forge script script/SetPool.s.sol --rpc-url $RPC_URL_FUJI --private-key $PRIVATE_KEY --broadcastExample output:
Setting pool for token: 0x56e4CFf5009908D8d8C5Aac5479574f9aC66e145 New pool address: 0xeB841acbc25549D4deB6Ed9401f3CbB529F3DC3e Action performed by admin: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA Pool set for token 0x56e4CFf5009908D8d8C5Aac5479574f9aC66e145 to 0xeB841acbc25549D4deB6Ed9401f3CbB529F3DC3e -
Link the token to its respective token pool on Arbitrum Sepolia:
forge script script/SetPool.s.sol --rpc-url $RPC_URL_ARBITRUM_SEPOLIA --private-key $PRIVATE_KEY --broadcastExample output:
Setting pool for token: 0x8E9A370Ff35E9A5C081818B5C9EcC3E3EA89396b New pool address: 0x346e11C1a869b88A8033e40B18Efdc4eCD9FFd02 Action performed by admin: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA Pool set for token 0x8E9A370Ff35E9A5C081818B5C9EcC3E3EA89396b to 0x346e11C1a869b88A8033e40B18Efdc4eCD9FFd02
Configure Token Pools
In this step, you will use the ApplyChainUpdates.s.sol script to initialize the token pool configuration on each blockchain to enable cross-chain transfers between Avalanche Fuji and Arbitrum Sepolia. This involves linking the token pool on one blockchain to a corresponding pool on another blockchain, allowing cross-chain token transfers. You will interact with the TokenPool contract by calling the applyChainUpdates() function to set up cross-chain parameters and rate limits.
Parameters used in pool configuration:
- Main Parameters:
| Parameter | Description | Default | Required |
|---|---|---|---|
poolAddress | The address of the token pool being configured on the current chain. | N/A | Yes |
chainSelectorRemovals | An array of chain selectors (identifiers) for which configurations should be removed. | Empty array | No |
chainUpdates | An array of ChainUpdate structs, each containing configuration updates for a specific remote chain. | N/A | Yes |
ChainUpdateStruct Parameters:
Each ChainUpdate struct in the chainUpdates array includes the following fields:
| Field | Description | Required |
|---|---|---|
remoteChainSelector | The chain selector (identifier) of the remote blockchain to which this pool will be linked. | Yes |
remotePoolAddresses | An array of addresses of the token pools on the remote blockchain, each encoded as bytes (abi.encode(remotePoolAddress)). | Yes |
remoteTokenAddress | The address of the token on the remote blockchain, encoded as bytes (abi.encode(remoteTokenAddress)). | Yes |
outboundRateLimiterConfig | Configuration for outbound rate limiting (transfers from current chain to remote chain). This is a struct with fields: isEnabled, capacity, rate. | No |
inboundRateLimiterConfig | Configuration for inbound rate limiting (transfers from remote chain to current chain). This is a struct with fields: isEnabled, capacity, rate. | No |
- Fields within
outboundRateLimiterConfigandinboundRateLimiterConfig:
| Sub-Parameter | Description | Default | Required |
|---|---|---|---|
isEnabled | A flag indicating whether rate limiting is enabled (true or false). | false | No |
capacity | The maximum number of tokens allowed in the bucket for rate limiting. Applicable only if rate limiting is enabled. | 0 | No |
rate | The rate at which tokens are refilled into the bucket per second. Applicable only if rate limiting is enabled. | 0 | No |
-
Configure the token pool on Avalanche Fuji:
forge script script/ApplyChainUpdates.s.sol --rpc-url $RPC_URL_FUJI --private-key $PRIVATE_KEY --broadcastExample output:
Chain update applied to pool at address: 0xeB841acbc25549D4deB6Ed9401f3CbB529F3DC3e -
Configure the token pool on Arbitrum Sepolia:
forge script script/ApplyChainUpdates.s.sol --rpc-url $RPC_URL_ARBITRUM_SEPOLIA --private-key $PRIVATE_KEY --broadcastExample output:
Chain update applied to pool at address: 0x346e11C1a869b88A8033e40B18Efdc4eCD9FFd02
Mint Tokens
In this step, you will use the MintTokens.s.sol script to mint tokens to your Externally Owned Account (EOA) on Avalanche Fuji. Since you granted mint and burn privileges to your EOA during the token deployment in the first step, you are authorized to mint tokens for testing purposes. This ensures that your EOA has sufficient tokens to perform cross-chain transfers in the next step.
The script will read the amount to mint from the tokenAmountToMint field in your config.json file and call the mint() function of the BurnMintERC20 token contract to mint the tokens to your EOA.
Mint tokens for your EOA on Avalanche Fuji:
forge script script/MintTokens.s.sol --rpc-url $RPC_URL_FUJI --private-key $PRIVATE_KEY --broadcast
Example output:
Minting 1000000000000000000000 tokens to 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA
Waiting for confirmations...
Minted 1000000000000000000000 tokens to 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA
Current balance of receiver is 1000000000000000000000 BnMkh
Transfer Tokens Across Networks
In this step, you will use the TransferTokens.s.sol script to transfer tokens from Avalanche Fuji to Arbitrum Sepolia using CCIP. You can choose to pay CCIP fees using LINK tokens or the native gas token.
The scripts reads the tokenAmountToTransfer, the feeType, and the destinationChainId from your config.json file. It prepares the transfer by approving the token transfer and estimating the CCIP fees based on your feeType. The script then calls the ccipSend() function on the IRouterClient contract to initiate the cross-chain transfer.
Transfer tokens from Avalanche Fuji to Arbitrum Sepolia:
forge script script/TransferTokens.s.sol --rpc-url $RPC_URL_FUJI --private-key $PRIVATE_KEY --broadcast
Example output:
Estimated fees: 22153771891999418
Message ID:
0x7097afbbf87e803653f0b4d116369f91a6118153d183f466f86f1080ee7dd578
Check status of the message at https://ccip.chain.link/msg/7097afbbf87e803653f0b4d116369f91a6118153d183f466f86f1080ee7dd578
You can check the status of the message on the Chainlink CCIP Explorer using the message ID provided in the output.