Enable your tokens in CCIP (Lock & Mint): Register from an EOA using Foundry

Guide Versions

This guide is available in multiple versions. Choose the one that matches your needs.

In this tutorial you will:

  1. Deploy a token on two chains using Foundry.
  2. Set up a Lock & Release token pool on the source chain and a Burn Mint token pool on the destination chain.
  3. Configure and activate the pools in CCIP so your token becomes a Cross-Chain Token (CCT).
  4. (Optional) Test the setup by minting and transferring tokens across networks.

Before You Begin

1 Set up your development environment
  1. Install Node.js and npm:

    • Make sure you have Node.js v22.10.0 or above installed. If not, install Node.js v22.10.0 using their documentation.
  2. Install/Update Chainlink CCIP-CLI. You can also find the GitHub repository here.

Terminal
npm install -g @chainlink/ccip-cli

Verify the installation by running the following command:

Terminal
ccip-cli --version
  1. Install Foundry: If you haven't already, follow the instructions in the Foundry documentation to install Foundry.
    Verify the installation by running the following command:
Terminal
forge --version
  1. Clone the repository and navigate to the project directory:
CCIP 2.0 template

Clone the CCIP 2.0 template for a smoother setup.

Terminal
git clone https://github.com/smartcontractkit/docs-cct-foundry.git
cd docs-cct-foundry
  1. Create an encrypted Foundry keystore, if you haven't already:
Terminal
cast wallet import your_keystore_name --interactive
Foundry keystore list command output
  1. Create a .env file by copying the .env.example file, and fill in the required values:
Terminal
cp .env.example .env
.env
# Keystore name (created via `cast wallet import`)
KEYSTORE_NAME=your_keystore_name

# RPC URLs (add the ones you need)
ETHEREUM_SEPOLIA_RPC_URL=your_eth_sepolia_rpc
ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL=your_arbitrum_sepolia_rpc

# Etherscan API key (required only if you pass --verify to deployment scripts)
ETHERSCAN_API_KEY=your_etherscan_api_key
Complete List of Supported Chains

View the complete list of supported chains in the HelperConfig.s.sol file.

  1. To make sure your terminal has access to these variables, run the following command:
Terminal
source .env
  1. Build the project:
Terminal
npm install && forge build
2 How to configure deployment parameters?

Deployment parameters can be configured in two ways:

  1. Edit the .json files in script/input/.
  2. Pass environment variables inline at runtime.

Note: Environment variables take precedence over .json file values.

Token Deployment Configuration

Default values live in script/input/token.json:

script/input/token.json
{
  "name": "BnM Test",
  "symbol": "BnM-T",
  "decimals": 18,
  "maxSupply": 0,
  "preMint": 0,
  "tokenAmountToMint": 1000000000000000000000,
  "tokenAmountToTransfer": 1000000000000000000
}

Deployment fields can be overridden with inline env vars:

Env varOverrides (token.json)OptionalDescription
TOKEN_NAME.nameYesToken name
TOKEN_SYMBOL.symbolYesToken symbol
TOKEN_DECIMALS.decimalsYesDecimal places (usually 18)
TOKEN_MAX_SUPPLY.maxSupplyYesMax supply in smallest unit (0 = unlimited)
TOKEN_PRE_MINT.preMintYesAmount pre-minted to the configured recipient (0 = none)
TOKEN_PRE_MINT_RECIPIENTN/A (env-only)YesAddress receiving pre-minted tokens. If omitted and TOKEN_PRE_MINT > 0, the script defaults to broadcaster
CCIP_ADMIN_ADDRESSN/A (env-only)YesAddress assigned as CCIP admin on the token. If omitted, broadcaster is used

(Optional) Advanced Pool Hooks

Default values live in script/input/advanced-pool-hooks.json:

script/input/advanced-pool-hooks.json
{
  "allowlist": [],
  "thresholdAmount": 0,
  "policyEngine": "0x0000000000000000000000000000000000000000",
  "authorizedCallers": []
}

All fields can be overridden with inline env vars:

Env varOverrides (advanced-pool-hooks.json)OptionalDescription
ALLOWLIST.allowlistYesAddresses allowed to transfer (CSV or JSON array)
THRESHOLD_AMOUNT.thresholdAmountYesThreshold amount after which additional hook checks/policy checks may apply (0 = none)
POLICY_ENGINE.policyEngineYesPolicy engine contract address
AUTHORIZED_CALLERS.authorizedCallersYesAddresses authorized to call the hooks (CSV or JSON array)

Note: A complete tutorial on setting up Advanced Pool Hooks can be found in our docs here: Set advanced pool hooks using Foundry

Tutorial

Prerequisites

Already have deployments? Skip only what you already have:

  • If you already have tokens deployed on both chains, skip Deploy Tokens.
  • If you have deployed tokens but not the corresponding pools and lockbox, you must still run Deploy Token Pools before continuing.
  • If you already have tokens deployed on both chains, V2-compatible token pools deployed on both chains, and the lockbox deployed on the Lock & Release side (Ethereum Sepolia), skip both sections and move straight on to Enablement.

If you already have a V1 token pool deployed and want to use newer features or prepare for lane migration:

  • You will need to deploy a V2-compatible token pool
  • Your existing V1 pool can continue to support current transfers
  • Follow the migration guide to upgrade your setup without disrupting your existing deployment

Read: Migrate from V1 to V2 token pools

Before continuing, export the relevant addresses so subsequent scripts can find them:

Terminal
export ETHEREUM_SEPOLIA_TOKEN=<your_token_address_on_sepolia>
export ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_TOKEN=<your_token_address_on_arbitrum_sepolia>

export ETHEREUM_SEPOLIA_TOKEN_POOL=<your_token_pool_address_on_sepolia>
export ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_TOKEN_POOL=<your_token_pool_address_on_arbitrum_sepolia>
1 Deploy Tokens

Use the DeployToken.s.sol script to deploy your tokens on two testnets, Ethereum Sepolia and Arbitrum Sepolia. The destination chain token (Arbitrum Sepolia) implements the IBurnMintERC20 interface, which exposes the mint and burn functions that the Burn & Mint token pool later calls during cross-chain transfers. The source chain token (Ethereum Sepolia) does not need burn/mint support; it is locked in the lockbox by the Lock & Release pool.

Note: The script reads the script/input/token.json file to get the token name, symbol, decimals, maximum supply, and pre-mint amount.
You can also override these values by providing inline environment variables at runtime.
See how to configure deployment parameters for more details.

DeployToken.s.sol

View the deployment script on GitHub.

Optional: If you want to grant mint and burn roles to an address other than the deployer, export ROLES_RECIPIENT once:

Terminal
export ROLES_RECIPIENT=<your_roles_recipient_address>
  1. Deploy token on Ethereum Sepolia:
Terminal
forge script \
  script/deploy/DeployToken.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast \
  --verify

Your output should look something like this:

Terminal
========================================
🪙 Deploy Token
========================================
Chain:        Ethereum Sepolia
Action:       Deploy token
========================================

Token Parameters:
Name:                         BnM Test
Symbol:                       BnM-T
Decimals:                     18
Max Supply:                   0
Pre-mint:                     0

Pre-mint Recipient:           0xYourDesignatedPreMintRecipient
CCIP Admin:                   0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994

[Step 1] Deploying BnM Test (BnM-T) on Ethereum Sepolia
Token deployed at: 0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
https://sepolia.etherscan.io/address/0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D

[Step 2] Granting mint and burn roles to: 0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
✅ Roles granted successfully!

========================================
✅ Deployment Complete on Ethereum Sepolia!
========================================
Token Address: 0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
https://sepolia.etherscan.io/address/0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
========================================

After deployment, the token address is automatically saved to:

script/deployments/tokens/{CHAIN_NAME_IDENTIFIER}/{timestamp}-{SYMBOL}-Token.json
  1. The file uses the env var name as the key (for example, ETHEREUM_SEPOLIA_TOKEN).

  2. You can copy the key and value directly into an export command.

  3. The script/deployments/ directory is ignored by .gitignore, so these files stay local to each user.

  4. Deploy token on Arbitrum Sepolia:

Terminal
forge script \
  script/deploy/DeployToken.s.sol \
  --rpc-url $ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast \
  --verify

Your output should look something like this:

Terminal
========================================
🪙 Deploy Token
========================================
Chain:        Arbitrum Sepolia
Action:       Deploy token
========================================

Token Parameters:
Name:                         BnM Test
Symbol:                       BnM-T
Decimals:                     18
Max Supply:                   0
Pre-mint:                     0

Pre-mint Recipient:           0xYourDesignatedPreMintRecipient
CCIP Admin:                   0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994

[Step 1] Deploying BnM Test (BnM-T) on Arbitrum Sepolia
Token deployed at: 0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
https://sepolia.arbiscan.io/address/0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c

[Step 2] Granting mint and burn roles to: 0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
✅ Roles granted successfully!

========================================
✅ Deployment Complete on Arbitrum Sepolia!
========================================
Token Address: 0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
https://sepolia.arbiscan.io/address/0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
========================================

After deployment, the token address is automatically saved to:

script/deployments/tokens/{CHAIN_NAME_IDENTIFIER}/{timestamp}-{SYMBOL}-Token.json
  1. Set the environment variables for the deployed token addresses:
Terminal
export ETHEREUM_SEPOLIA_TOKEN=0x...
export ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_TOKEN=0x...
2 Deploy Token Pools

This tutorial deploys:

  • A Lock & Release token pool on the source chain (Ethereum Sepolia)
  • A Burn & Mint token pool on the destination chain (Arbitrum Sepolia)
  • An ERC20LockBox to hold token liquidity on behalf of the Lock & Release token pool on the source chain.
  • On the destination chain, the tokens are simply burned or minted as required by the Burn & Mint token pool.
Deploy ERC20LockBox (Ethereum Sepolia)

The ERC20LockBox holds token liquidity on behalf of the LockReleaseTokenPool. It must be deployed before the pool.

DeployERC20LockBox.s.sol

View the lockbox deployment script on GitHub.

  1. Deploy the ERC20LockBox on Ethereum Sepolia:
Terminal
forge script \
  script/deploy/DeployERC20LockBox.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast \
  --verify

Your output should look something like this:

Terminal
========================================
📦 Deploy ERC20 LockBox
========================================
Chain:        Ethereum Sepolia
Action:       Deploy ERC20 lockbox
========================================

ERC20LockBox Parameters:
Token:                        0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Authorized Callers:           None (add after deploying the token pool)

[Step 1] Deploying ERC20LockBox on Ethereum Sepolia
ERC20LockBox deployed at: 0x1c0b9A8f7E6d5C4b3A2f1E0d9C8b7A6f5E4d3C2b
https://sepolia.etherscan.io/address/0x1c0b9A8f7E6d5C4b3A2f1E0d9C8b7A6f5E4d3C2b
✅ ERC20LockBox deployed successfully!

========================================
✅ Deployment Complete on Ethereum Sepolia!
========================================
ERC20LockBox Address: 0x1c0b9A8f7E6d5C4b3A2f1E0d9C8b7A6f5E4d3C2b
https://sepolia.etherscan.io/address/0x1c0b9A8f7E6d5C4b3A2f1E0d9C8b7A6f5E4d3C2b

Copy this address to use in the next commands:
LOCK_BOX=0x1c0b9A8f7E6d5C4b3A2f1E0d9C8b7A6f5E4d3C2b
========================================

After deployment, the lockbox address is automatically saved to:

script/deployments/lock-boxes/{CHAIN_NAME_IDENTIFIER}/{timestamp}-{SYMBOL}-LockBox.json
  1. Set the environment variable for the deployed lockbox address:
Terminal
export LOCK_BOX=0x...
Deploy Lock & Release Token Pool (Ethereum Sepolia)

Use this script to deploy a Lock & Release token pool on the source chain. A LockReleaseTokenPool locks tokens in the ERC20LockBox on the source chain during outbound transfers and releases escrowed tokens during inbound transfers.

DeployLockReleaseTokenPool.s.sol

View the Lock & Release token pool deployment script on GitHub.

  1. Deploy a Lock & Release token pool for the deployed token on Ethereum Sepolia:
Terminal
LOCK_BOX=$LOCK_BOX \
  forge script \
  script/deploy/DeployLockReleaseTokenPool.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast \
  --verify

Your output should look something like this:

Terminal
========================================
🔐 Deploy Lock & Release Token Pool
========================================
Chain:        Ethereum Sepolia
Action:       Deploy lock & release token pool
========================================

Token Pool Parameters:
Token:                        0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Decimals:                     18
Router:                       0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
RMN Proxy:                    0xba3f6251de62dED61Ff98590cB2fDf6871FbB991
LockBox:                      0x1c0b9A8f7E6d5C4b3A2f1E0d9C8b7A6f5E4d3C2b
AdvancedPoolHooks:            None (0x0)

[Step 1] Deploying LockReleaseTokenPool on Ethereum Sepolia
Token Pool deployed at: 0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
https://sepolia.etherscan.io/address/0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
✅ LockReleaseTokenPool deployed successfully!

========================================
✅ Deployment Complete on Ethereum Sepolia!
========================================
Token Pool Address: 0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
https://sepolia.etherscan.io/address/0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
========================================

After deployment, the pool address is automatically saved to:

script/deployments/token-pools/{CHAIN_NAME_IDENTIFIER}/{timestamp}-{SYMBOL}-LockReleaseTokenPool.json
  1. Set the environment variable for the deployed pool address:
Terminal
export ETHEREUM_SEPOLIA_TOKEN_POOL=0x...
Authorize Pool on LockBox (Ethereum Sepolia)

The pool must be authorized as a caller on the lockbox before it can deposit and withdraw tokens during cross-chain transfers.

UpdateAuthorizedCallers.s.sol

View the authorized callers script on GitHub.

  1. Authorize the token pool on the lockbox:
Terminal
LOCK_BOX=$LOCK_BOX \
  ADD_ADDRESSES=$ETHEREUM_SEPOLIA_TOKEN_POOL \
  forge script \
  script/configure/authorized-callers/UpdateAuthorizedCallers.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
📝 Update Authorized Callers
========================================
Chain:        Ethereum Sepolia
LockBox:      0x1c0b9A8f7E6d5C4b3A2f1E0d9C8b7A6f5E4d3C2b
Action:       Update authorized callers
========================================

Adding 1 caller(s):
[0] 0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567

========================================
✅ Authorized callers updated on Ethereum Sepolia!
========================================
LockBox:      0x1c0b9A8f7E6d5C4b3A2f1E0d9C8b7A6f5E4d3C2b
LockBox:      https://sepolia.etherscan.io/address/0x1c0b9A8f7E6d5C4b3A2f1E0d9C8b7A6f5E4d3C2b
========================================
Deploy Burn & Mint Token Pool (Arbitrum Sepolia)

Use this script to deploy a Burn & Mint token pool on the destination chain. A BurnMintTokenPool burns tokens on the source chain and mints the equivalent amount on the destination chain, so the pool must hold the destination token's mint and burn roles, which the script grants automatically after deployment.

DeployBurnMintTokenPool.s.sol

View the Burn & Mint token pool deployment script on GitHub.

  1. Deploy a Burn & Mint token pool for the deployed token on Arbitrum Sepolia:
Terminal
forge script \
  script/deploy/DeployBurnMintTokenPool.s.sol \
  --rpc-url $ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast \
  --verify

Your output should look something like this:

Terminal
========================================
🔥⚒️ Deploy Burn & Mint Token Pool
========================================
Chain:        Arbitrum Sepolia
Action:       Deploy burn & mint token pool
========================================

Token Pool Parameters:
Token:                        0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
Decimals:                     18
Router:                       0x2a9C5afB0d0e4BAb2BCdaE109EC4b0c4Be15a165
RMN Proxy:                    0x9527E2d01A3064ef6b50c1Da1C0cC523803BCFF2
AdvancedPoolHooks:            None (0x0)

[Step 1] Deploying BurnMintTokenPool on Arbitrum Sepolia
Token Pool deployed at: 0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
https://sepolia.arbiscan.io/address/0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d

[Step 2] Granting mint and burn roles to token pool: 0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
✅ Roles granted successfully!

========================================
✅ Deployment Complete on Arbitrum Sepolia!
========================================
Token Pool Address: 0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
https://sepolia.arbiscan.io/address/0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
========================================

After deployment, the pool address is automatically saved to:

script/deployments/token-pools/{CHAIN_NAME_IDENTIFIER}/{timestamp}-{SYMBOL}-BurnMintTokenPool.json
Set environment variables

Set the environment variables for the deployed token pool addresses:

Terminal
export ETHEREUM_SEPOLIA_TOKEN_POOL=0x...
export ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_TOKEN_POOL=0x...

Enablement

These steps configure your token pools and activate them under the CCIP protocol so your token becomes a fully registered Cross-Chain Token (CCT).

1 Claim Admin Role

Use the ClaimAdmin.s.sol script to register your EOA as the administrator for the deployed tokens on both chains.
This process involves interacting with the RegistryModuleOwnerCustom contract to set up your EOA as the admin.

ClaimAdmin.s.sol

View the admin claim script on GitHub.

  1. Claim the Admin role for the token on Ethereum Sepolia:
Terminal
CCIP_ADMIN_ADDRESS=<your_ccip_admin_address> \
  forge script \
  script/setup/ClaimAdmin.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
👑 Claim Token Admin
========================================
Chain:        Ethereum Sepolia
Action:       Claim token admin
========================================

Claim Admin Parameters:
Token:                        0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Current Admin:                0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
Expected Admin:               0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
Registry Module:              0xa3c796d480638d7476792230da1E2ADa86e031b0
Admin Method:                 getCCIPAdmin()

[Step 1] Claiming admin for token via getCCIPAdmin() on Ethereum Sepolia
✅ Admin claimed successfully!

========================================
✅ Admin Claim Complete on Ethereum Sepolia!
========================================
Token Address: 0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Token Address: https://sepolia.etherscan.io/address/0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Admin Address: 0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
========================================
  1. Claim the Admin role for the token on Arbitrum Sepolia:
Terminal
CCIP_ADMIN_ADDRESS=<your_ccip_admin_address> \
  forge script \
  script/setup/ClaimAdmin.s.sol \
  --rpc-url $ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
👑 Claim Token Admin
========================================
Chain:        Arbitrum Sepolia
Action:       Claim token admin
========================================

Claim Admin Parameters:
Token:                        0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
Current Admin:                0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
Expected Admin:               0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
Registry Module:              0xaD417c0611dBD225471D31F056b8B6beC1CBC153
Admin Method:                 getCCIPAdmin()

[Step 1] Claiming admin for token via getCCIPAdmin() on Arbitrum Sepolia
✅ Admin claimed successfully!

========================================
✅ Admin Claim Complete on Arbitrum Sepolia!
========================================
Token Address: 0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
Token Address: https://sepolia.arbiscan.io/address/0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
Admin Address: 0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
========================================
2 Accept Admin Role

Use the AcceptAdminRole.s.sol script to accept the admin role for the deployed tokens on both chains.
Once you have claimed the role, accepting it finalizes your control over the token administration via the TokenAdminRegistry.

AcceptAdminRole.s.sol

View the admin acceptance script on GitHub.

  1. Accept the admin role for the token on Ethereum Sepolia:
Terminal
forge script \
  script/setup/AcceptAdminRole.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
👑 Accept Admin Role
========================================
Chain:        Ethereum Sepolia
Action:       Accept admin role
========================================

Accept Admin Role Parameters:
Token:                        0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Token Admin Registry:         0x95F29FEE11c5C55d26cCcf1DB6772DE953B37B82
Pending Administrator:        0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
Signer:                       0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994

[Step 1] Accepting admin role for token on Ethereum Sepolia
✅ Admin role accepted successfully!

========================================
✅ Admin Role Accepted on Ethereum Sepolia!
========================================
Token Address: 0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Token Address: https://sepolia.etherscan.io/address/0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
New Administrator: 0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
========================================
  1. Accept the admin role for the token on Arbitrum Sepolia:
Terminal
forge script \
  script/setup/AcceptAdminRole.s.sol \
  --rpc-url $ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
👑 Accept Admin Role
========================================
Chain:        Arbitrum Sepolia
Action:       Accept admin role
========================================

Accept Admin Role Parameters:
Token:                        0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
Token Admin Registry:         0x8126bE56454B628a88C17849B9ED99dd5a11Bd2f
Pending Administrator:        0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
Signer:                       0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994

[Step 1] Accepting admin role for token on Arbitrum Sepolia
✅ Admin role accepted successfully!

========================================
✅ Admin Role Accepted on Arbitrum Sepolia!
========================================
Token Address: 0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
Token Address: https://sepolia.arbiscan.io/address/0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
New Administrator: 0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
========================================
3 Configure Token Pools

Use the ApplyChainUpdates.s.sol script to link each token pool to its counterpart on the other chain and configure rate limits.
You will interact with the TokenPool contract by calling the applyChainUpdates() function to enable cross-chain transfers.

applyChainUpdates() registers the destination chain and the destination pool on the local pool by recording:

  • The remote chain selector (to identify the exact chain),
  • The remote pool address (to identify the exact pool on that exact chain),
  • The remote token address (to identify the exact token on the remote pool),
  • And per-direction rate limit buckets (outbound and inbound).

You must run it on both pools so each side knows its counterpart.

ApplyChainUpdates.s.sol

View the pool configuration script on GitHub.

Env varRequiredDescription
OUTBOUND_RATE_LIMIT_CAPACITYNoToken bucket capacity for outbound transfers
OUTBOUND_RATE_LIMIT_RATENoToken bucket refill rate (tokens/second) for outbound transfers
OUTBOUND_RATE_LIMIT_ENABLEDNoOverride isEnabled explicitly (true/false; defaults to true when CAPACITY or RATE are set)
INBOUND_RATE_LIMIT_CAPACITYNoToken bucket capacity for inbound transfers
INBOUND_RATE_LIMIT_RATENoToken bucket refill rate (tokens/second) for inbound transfers
INBOUND_RATE_LIMIT_ENABLEDNoOverride isEnabled explicitly (true/false; defaults to true when CAPACITY or RATE are set)
  1. Configure the token pool on Ethereum Sepolia:
Terminal
DEST_CHAIN=ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1 \
  OUTBOUND_RATE_LIMIT_CAPACITY=1000000000000000000000 \
  OUTBOUND_RATE_LIMIT_RATE=100000000000000000 \
  INBOUND_RATE_LIMIT_CAPACITY=1000000000000000000000 \
  INBOUND_RATE_LIMIT_RATE=100000000000000000 \
  forge script \
  script/setup/ApplyChainUpdates.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
🔗 Apply Chain Updates
========================================
Chain:        Ethereum Sepolia
Remote Chain: Arbitrum Sepolia
Token Pool:   0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
Action:       Configure cross-chain lane
========================================

Chain Update Parameters:
Source Pool:                  0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
Destination Chain Selector:   3478487238524512106
Destination Chain Family:     evm
Destination Pool:             0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Destination Token:            0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
Outbound Rate Limit Enabled:  true
Outbound Rate Limit Capacity: 1000000000000000000000
Outbound Rate Limit Rate:     100000000000000000
Inbound Rate Limit Enabled:   true
Inbound Rate Limit Capacity:  1000000000000000000000
Inbound Rate Limit Rate:      100000000000000000

[Step 1] Applying chain updates on Ethereum Sepolia
✅ Chain updates applied successfully!

========================================
✅ Chain Updates Complete on Ethereum Sepolia!
========================================
Token Pool:   0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
Token Pool:   https://sepolia.etherscan.io/address/0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
Remote Chain: Arbitrum Sepolia (Selector: 3478487238524512106)
Remote Pool:  0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
========================================
  1. Configure the token pool on Arbitrum Sepolia:
Terminal
DEST_CHAIN=ETHEREUM_SEPOLIA \
  OUTBOUND_RATE_LIMIT_CAPACITY=1000000000000000000000 \
  OUTBOUND_RATE_LIMIT_RATE=100000000000000000 \
  INBOUND_RATE_LIMIT_CAPACITY=1000000000000000000000 \
  INBOUND_RATE_LIMIT_RATE=100000000000000000 \
  forge script \
  script/setup/ApplyChainUpdates.s.sol \
  --rpc-url $ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
🔗 Apply Chain Updates
========================================
Chain:        Arbitrum Sepolia
Remote Chain: Ethereum Sepolia
Token Pool:   0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Action:       Configure cross-chain lane
========================================

Chain Update Parameters:
Source Pool:                  0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Destination Chain Selector:   16015286601757825753
Destination Chain Family:     evm
Destination Pool:             0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
Destination Token:            0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Outbound Rate Limit Enabled:  true
Outbound Rate Limit Capacity: 1000000000000000000000
Outbound Rate Limit Rate:     100000000000000000
Inbound Rate Limit Enabled:   true
Inbound Rate Limit Capacity:  1000000000000000000000
Inbound Rate Limit Rate:      100000000000000000

[Step 1] Applying chain updates on Arbitrum Sepolia
✅ Chain updates applied successfully!

========================================
✅ Chain Updates Complete on Arbitrum Sepolia!
========================================
Token Pool:   0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Token Pool:   https://sepolia.arbiscan.io/address/0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Remote Chain: Ethereum Sepolia (Selector: 16015286601757825753)
Remote Pool:  0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
========================================
  1. (Optional) Read the list of supported chains and currently configured remote pools:
Terminal
forge script \
  script/setup/GetSupportedChains.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL
4 (Optional) Configure Faster Than Finality for the Token Pool
  1. CCIP 2.0 supports faster than finality as an opt-in feature, letting token issuers control the speed vs. security tradeoff for cross-chain transfers of their token.

  2. By default, a newly deployed token pool only allows transfers at default finality (equivalent to omitting finality or using finality=finalized).

  3. To enable faster than finality, the pool owner must configure which faster than finality modes are allowed. Senders then request faster than finality per transfer using a numeric block depth via finality=<N> (minimum recommended depth: 32).

This section applies only to token pools with versions ≥ 2.0. Existing v1 pools do not support fast finality.

In this tutorial, configure faster than finality on both token pools: the Lock & Release pool on Ethereum Sepolia and the Burn Mint pool on Arbitrum Sepolia.

SetFinalityConfig.s.sol

View the pool configuration script on GitHub.

  1. Set the allowed finality config for the token pool on Ethereum Sepolia:
Terminal
BLOCK_DEPTH=32 \
  DEST_CHAIN=ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1 \
  OUTBOUND_RATE_LIMIT_CAPACITY=1000000000000000000000 \
  OUTBOUND_RATE_LIMIT_RATE=100000000000000000 \
  INBOUND_RATE_LIMIT_CAPACITY=1000000000000000000000 \
  INBOUND_RATE_LIMIT_RATE=100000000000000000 \
  forge script \
  script/configure/finality-config/SetFinalityConfig.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
⏱️  Set Finality Config
========================================
Chain:        Ethereum Sepolia
Remote Chain: Arbitrum Sepolia
Token Pool:   0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
Action:       Set finality config
========================================

Current Finality Config:      0x00000000
New Finality Config:          0x00000020
Mode:                         BLOCK_DEPTH (32 blocks)

----------------------------------------
📊 Current Rate Limits (faster than finality where enabled, standard otherwise):
----------------------------------------
Outbound [standard fallback]:
Enabled:  true
Capacity: 1000000000000000000000
Rate:     100000000000000000
Tokens:   1000000000000000000000
Inbound [standard fallback]:
Enabled:  true
Capacity: 1000000000000000000000
Rate:     100000000000000000
Tokens:   1000000000000000000000

[Step 1] Setting finality config on Ethereum Sepolia
✅ Finality config set successfully!

[Step 2] Updating rate limits (faster than finality bucket) on Ethereum Sepolia -> Arbitrum Sepolia
✅ Rate limits updated successfully!

========================================
✅ Configuration Complete on Ethereum Sepolia!
========================================
Token Pool:      0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
Finality Config: 0x00000020
Mode:            BLOCK_DEPTH (32 blocks)
Token Pool:      https://sepolia.etherscan.io/address/0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
========================================
  1. Set the allowed finality config for the token pool on Arbitrum Sepolia:
Terminal
BLOCK_DEPTH=32 \
  DEST_CHAIN=ETHEREUM_SEPOLIA \
  OUTBOUND_RATE_LIMIT_CAPACITY=1000000000000000000000 \
  OUTBOUND_RATE_LIMIT_RATE=100000000000000000 \
  INBOUND_RATE_LIMIT_CAPACITY=1000000000000000000000 \
  INBOUND_RATE_LIMIT_RATE=100000000000000000 \
  forge script \
  script/configure/finality-config/SetFinalityConfig.s.sol \
  --rpc-url $ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
⏱️  Set Finality Config
========================================
Chain:        Arbitrum Sepolia
Remote Chain: Ethereum Sepolia
Token Pool:   0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Action:       Set finality config
========================================

Current Finality Config:      0x00000000
New Finality Config:          0x00000020
Mode:                         BLOCK_DEPTH (32 blocks)

----------------------------------------
📊 Current Rate Limits (faster than finality where enabled, standard otherwise):
----------------------------------------
Outbound [standard fallback]:
Enabled:  true
Capacity: 1000000000000000000000
Rate:     100000000000000000
Tokens:   1000000000000000000000
Inbound [standard fallback]:
Enabled:  true
Capacity: 1000000000000000000000
Rate:     100000000000000000
Tokens:   1000000000000000000000

[Step 1] Setting finality config on Arbitrum Sepolia
✅ Finality config set successfully!

[Step 2] Updating rate limits (faster than finality bucket) on Arbitrum Sepolia -> Ethereum Sepolia
✅ Rate limits updated successfully!

========================================
✅ Configuration Complete on Arbitrum Sepolia!
========================================
Token Pool:      0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Finality Config: 0x00000020
Mode:            BLOCK_DEPTH (32 blocks)
Token Pool:      https://sepolia.arbiscan.io/address/0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
========================================
5 Activate the Token Pool

Use the SetPool.s.sol script to register each token pool in the CCIP TokenAdminRegistry, making it the official pool for your token. This is the step that activates the pool in the CCIP protocol.

SetPool.s.sol

View the pool linking script on GitHub.

  1. Link the token to its respective token pool on Ethereum Sepolia:
Terminal
forge script \
  script/setup/SetPool.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
🏊‍♂️ Set Token Pool
========================================
Chain:        Ethereum Sepolia
Token Pool:   0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
Action:       Set token pool
========================================

Set Pool Parameters:
Token:                        0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Pool:                         0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
Token Admin Registry:         0x95F29FEE11c5C55d26cCcf1DB6772DE953B37B82
Token Administrator:          0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994

[Step 1] Setting pool for token on Ethereum Sepolia
✅ Pool set successfully!

========================================
✅ Pool Set Complete on Ethereum Sepolia!
========================================
Token Address: 0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Token Address: https://sepolia.etherscan.io/address/0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Pool Address:  0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
Pool Address:  https://sepolia.etherscan.io/address/0x8a1b2C3d4E5F60718293a4B5c6D7E8F901234567
========================================
  1. Link the token to its respective token pool on Arbitrum Sepolia:
Terminal
forge script \
  script/setup/SetPool.s.sol \
  --rpc-url $ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
🏊‍♂️ Set Token Pool
========================================
Chain:        Arbitrum Sepolia
Token Pool:   0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Action:       Set token pool
========================================

Set Pool Parameters:
Token:                        0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
Pool:                         0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Token Admin Registry:         0x8126bE56454B628a88C17849B9ED99dd5a11Bd2f
Token Administrator:          0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994

[Step 1] Setting pool for token on Arbitrum Sepolia
✅ Pool set successfully!

========================================
✅ Pool Set Complete on Arbitrum Sepolia!
========================================
Token Address: 0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
Token Address: https://sepolia.arbiscan.io/address/0x5B3c8F2a9D4e7A1c6F0b3E8d5C9a2F4b7D1e6A3c
Pool Address:  0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
Pool Address:  https://sepolia.arbiscan.io/address/0x7D2f4A6c8E1b3D5a9F0c2E4b6A8d1C3e5F7a9B2d
========================================

Cross-chain Token Operations

If all the previous steps were successful, your CCT token is now fully registered and activated under the CCIP protocol. Use these next steps to mint and transfer your tokens across networks.

1 Mint Tokens

Use the MintTokens.s.sol script to mint tokens to your Externally Owned Account (EOA) on Ethereum Sepolia.
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.

MintTokens.s.sol

View the minting script on GitHub.

  1. Mint tokens for your EOA on Ethereum Sepolia:
Terminal
AMOUNT=1000000000000000000000 \
  forge script \
  script/operations/MintTokens.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
💰 Mint Tokens
========================================
Chain:        Ethereum Sepolia
Action:       Mint tokens
========================================

Mint Parameters:
Token:                        0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Token Symbol:                 BnM-T
Amount:                       1000000000000000000000
Receiver:                     0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994

[Step 1] Minting 1000000000000000000000 BnM-T to 0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
✅ Tokens minted successfully!

========================================
✅ Minting Complete on Ethereum Sepolia!
========================================
Receiver Address: 0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
Receiver Address: https://sepolia.etherscan.io/address/0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
New Balance: 1000000000000000000000 BnM-T
========================================
2 Transfer Tokens Across Networks

Use the ccip-cli command to securely transfer tokens across networks from your terminal. Tokens will be:

  1. locked on the source chain (Ethereum Sepolia), and,
  2. minted on the destination chain (Arbitrum Sepolia). \

You can choose to pay CCIP fees using LINK tokens or the native gas token.

Note:

  1. Add --fee-token LINK to pay CCIP fees in LINK. If you omit this flag, fees are paid in the native gas token.
  2. Use --receiver <address> for a custom recipient. If omitted on EVM-to-EVM, the receiver defaults to the sender.

The CLI command expects the router address to be set in the environment variables. You can set it using the following command:

Terminal
export ETHEREUM_SEPOLIA_ROUTER=0x...
  1. Transfer tokens from Ethereum Sepolia to Arbitrum Sepolia on default finality:
Terminal
ccip-cli send \
  --source ethereum-testnet-sepolia \
  --router $ETHEREUM_SEPOLIA_ROUTER \
  --dest ethereum-testnet-sepolia-arbitrum-1 \
  --transfer-tokens $ETHEREUM_SEPOLIA_TOKEN=1.23 \
  --receiver 0xYourReceiverAddress \
  --wallet foundry:$KEYSTORE_NAME \
  --rpc "$ETHEREUM_SEPOLIA_RPC_URL" \
  --rpc "$ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL"

Your output should look something like this:

Terminal
Fee: 130129888907619n = 0.000130129888907619 ETH
✔ Enter password for Foundry keystore 'PRIVATE_KEY'
🚀 Sending message to 0xE23Fc63F47F08F58B9d7448d4CCE0bCDcc96d7F3 @ ethereum-testnet-sepolia-arbitrum-1 , tx => 0x3dc40bea29f3e3fc93ff8fce0dda45fd7f55a07ace5089646e018874c8b6745e , messageId => 0x4a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293
CCIP Explorer: https://ccip.chain.link/msg/0x4a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293
  1. Transfer tokens from Ethereum Sepolia to Arbitrum Sepolia using faster than finality (block depth):

This assumes you enabled faster than finality on the pool in the optional finality configuration accordion above. When you pass --extra finality=..., ccip-cli encodes it into the CCIP message extra args.

Faster Than Finality using numeric block depth (finality=<N>). Use a value of at least 32:

Terminal
ccip-cli send \
  --source ethereum-testnet-sepolia \
  --router $ETHEREUM_SEPOLIA_ROUTER \
  --dest ethereum-testnet-sepolia-arbitrum-1 \
  --transfer-tokens $ETHEREUM_SEPOLIA_TOKEN=1.23 \
  --receiver 0xYourReceiverAddress \
  --extra finality=32 \
  --wallet foundry:$KEYSTORE_NAME \
  --rpc "$ETHEREUM_SEPOLIA_RPC_URL" \
  --rpc "$ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL"

Your output should look something like this:

Terminal
Fee: 130129888907619n = 0.000130129888907619 ETH
✔ Enter password for Foundry keystore 'PRIVATE_KEY'
🚀 Sending message to 0xE23Fc63F47F08F58B9d7448d4CCE0bCDcc96d7F3 @ ethereum-testnet-sepolia-arbitrum-1 , tx => 0xeee045c912703291e6d6166e4ef8bfb0a7fb9035af1a41ac00b65c58ad868a94 , messageId => 0x5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4
CCIP Explorer: https://ccip.chain.link/msg/0x5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4
3 (Optional) Hand Off Token Administration After Enablement
  1. The process to set up your tokens for cross-chain transfers is complete. At this stage, you might want to hand off token administration to a different address, for example, transferring control to a multisig or a dedicated operations wallet. This new admin will be able to mint and transfer tokens as needed.
  2. Use the TransferTokenAdminRole.s.sol script to achieve this. This is a two-step process: the current admin initiates the transfer, and the new admin must run AcceptAdminRole.s.sol to complete it. Until the new admin accepts, the current admin retains full control.
TransferTokenAdminRole.s.sol

View the transfer admin script on GitHub.

  1. Initiate the transfer on Ethereum Sepolia:
Terminal
NEW_ADMIN=0x... \
  forge script \
  script/setup/TransferTokenAdminRole.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast

Your output should look something like this:

Terminal
========================================
🔄 Transfer Token Admin Role
========================================
Chain:        Ethereum Sepolia
Action:       Transfer admin role
========================================

Transfer Admin Role Parameters:
Token:                        0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
Token Admin Registry:         0x95F29FEE11c5C55d26cCcf1DB6772DE953B37B82
Current Administrator:        0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994
Pending Administrator:        0x0000000000000000000000000000000000000000
New Admin:                    0xE23Fc63F47F08F58B9d7448d4CCE0bCDcc96d7F3
Signer:                       0x3A34637a41aB08519d30Fdb65344aBa8E9b2e994

[Step 1] Transferring admin role for token on Ethereum Sepolia
✅ Admin role transfer initiated successfully!

========================================
✅ Admin Role Transfer Initiated on Ethereum Sepolia!
========================================
Token:       https://sepolia.etherscan.io/address/0x9602399103Ff5F87587Ac5A28E1551A0bA0c6C0D
New Admin:   0xE23Fc63F47F08F58B9d7448d4CCE0bCDcc96d7F3
========================================

ℹ️  The new admin (0xE23Fc63F47F08F58B9d7448d4CCE0bCDcc96d7F3) must run AcceptAdminRole to complete the transfer.
  1. Repeat the same command on Arbitrum Sepolia if you also want to transfer admin rights on the destination chain:
Terminal
NEW_ADMIN=0x... \
  forge script \
  script/setup/TransferTokenAdminRole.s.sol \
  --rpc-url $ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL \
  --account $KEYSTORE_NAME \
  --broadcast
  1. The new admin completes the transfer by running the AcceptAdminRole script for both chains. Set NEW_ADMIN_KEYSTORE_NAME to the Foundry keystore name that the new admin created via cast wallet import:
Terminal
export NEW_ADMIN_KEYSTORE_NAME=new_admin_keystore_name

forge script \
  script/setup/AcceptAdminRole.s.sol \
  --rpc-url $ETHEREUM_SEPOLIA_RPC_URL \
  --account $NEW_ADMIN_KEYSTORE_NAME \
  --broadcast

forge script \
  script/setup/AcceptAdminRole.s.sol \
  --rpc-url $ETHEREUM_TESTNET_SEPOLIA_ARBITRUM_1_RPC_URL \
  --account $NEW_ADMIN_KEYSTORE_NAME \
  --broadcast

What's next

Get the latest Chainlink content straight to your inbox.