Cross-Chain Token Setup: BurnMint with SPL Token Multisig Tutorial

This educational tutorial demonstrates how to create and configure cross-chain tokens using Chainlink's Cross-Chain Interoperability Protocol (CCIP) between Solana Devnet and Ethereum Sepolia using SPL token multisig concepts. You will learn to implement the SPL token multisig approach within Path A from the CCIP Cross-Chain Token Integration Guide.

Path A Mint Authority Options:

  • Direct Transfer: Transfer mint authority directly to Pool Signer PDA - suitable for development and testing (see tutorial)
  • Multisig Setup (this tutorial): Learn SPL token multisig concepts with Pool Signer PDA as a member - foundation for production systems
  • Production Multisig: Enterprise-grade dual-layer governance with Squads + SPL multisig (see tutorial)

This tutorial focuses on demonstrating multisig architecture concepts, helping you understand governance controls while maintaining autonomous cross-chain token transfers through BurnMint token pools.

What You Will Build

This tutorial implements the SPL token multisig variant of Path A from the CCIP Cross-Chain Token Integration Guide. This approach is designed for learning multisig concepts and provides a foundation for production systems.

Cross-Chain Token Architecture

This tutorial implements the Burn and Mint token handling mechanism between Solana Devnet and Ethereum Sepolia with SPL token multisig governance. You'll deploy two BurnMint pools (one on each chain) that work together to maintain consistent token supply across chains while learning multisig architecture concepts.

How Burn and Mint Works:

  1. Source Chain: Burns tokens from sender's account
  2. CCIP Protocol: Transmits message cross-chain
  3. Destination Chain: Mints equivalent tokens to the receiver

Component Overview

ComponentImplementationAuthority Model
Ethereum SepoliaERC20 token with CCIP BurnMint poolMultiple minters: EOA + Pool
Solana DevnetSPL token with CCIP BurnMint poolSPL Token Multisig: Pool Signer PDA + Admin wallet

SPL Token Multisig Architecture

Key Approach: You will create an SPL token multisig that includes the Pool Signer PDA as a required member, enabling both autonomous CCIP operations and governance-controlled minting.

Educational Focus: This tutorial demonstrates multisig architecture concepts using a simplified 1-of-2 configuration for learning purposes.

Prerequisites

This tutorial uses a two-terminal workflow across two repositories. Install the system tools below, clone both repos, then complete environment setup before starting Phase 1.

System Requirements

  • Node.js v22 or higher: Verify with node -v (nvm recommended)
  • pnpm: Required for the BS58 generator (npm install -g pnpm)
  • Solana CLI: Installation guide (includes spl-token)
  • Git: For cloning repositories
  • CCIP CLI: For cross-chain transfer testing in the final phase

Install the CCIP CLI globally:

npm install -g @chainlink/ccip-cli
ccip-cli --help

See the CCIP CLI documentation for RPC and wallet configuration.

Tutorial Workflow

TerminalRepositoryPurposeCommands
Terminal 1CCIP Solana BS58 GeneratorSolana setup and configurationpnpm bs58
Terminal 2Smart Contract Examples (Hardhat)EVM deploy and configurationnpx hardhat
EitherGlobal @chainlink/ccip-cliCross-chain transfer testing (final)ccip-cli send

Terminal 1: CCIP Solana BS58 Generator

Clone and install (skip git clone if you already have the repo):

git clone https://github.com/smartcontractkit/ccip-solana-bs58-generator.git
cd ccip-solana-bs58-generator
pnpm install

Configure Solana CLI for devnet:

First, check whether your environment is already set up:

solana config get
solana address
solana balance

If the RPC URL is already https://api.devnet.solana.com, your keypair path is correct, and you have sufficient SOL, skip to Terminal 2 setup below.

Otherwise, run only the steps you need:

# Set devnet (skip if config get already shows devnet)
solana config set --url https://api.devnet.solana.com

# Point to your keypair (skip if config get already shows the path you want)
solana config set --keypair ~/.config/solana/id.json

# Create a keypair only if the file does not exist yet
solana-keygen new --outfile ~/.config/solana/id.json

# Fund your wallet if balance is low
solana airdrop 2
solana balance

Terminal 2: Smart Contract Examples (Hardhat)

Clone, install, and compile (skip git clone if you already have the repo):

git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/ccip/cct/hardhat
npm install
npm run compile

Set up encrypted environment variables:

# Required at the start of each session
npx env-enc set-pw

# Verify existing variables (skip npx env-enc set if all required vars are already configured)
npx env-enc view

# Add or update variables only if missing
npx env-enc set

Required variables for Ethereum Sepolia:

Fund your EVM wallet:

  • Acquire ETH on Ethereum Sepolia for transaction gas and CCIP fees (Chainlink faucet or Google Cloud Faucet)
  • LINK is optional — use --fee-token LINK on EVM → Solana sends if you prefer paying CCIP fees in LINK

Cross-Chain Transfers (CCIP CLI)

The final tutorial phase uses globally installed ccip-cli (not the BS58 generator):

  • Solana → EVM: Run from Terminal 1 with --wallet ~/.config/solana/id.json
  • EVM → Solana: Run from Terminal 2 (Hardhat directory). Prefer --wallet hardhat:<name> (Hardhat keystore) so the signing key stays encrypted. Hardhat tasks load env-enc automatically; ccip-cli does not. As an alternative, view the same key with npx env-enc view and export PRIVATE_KEY manually for the send command.

RPC endpoints (required for source and destination chains). You can provide them any of these ways — see CCIP CLI configuration:

  • Command line: pass --rpc on each command (repeat for multiple networks), or --rpcs with comma-separated URLs
  • Environment variables: export RPC_* variables (e.g., RPC_SEPOLIA, RPC_SOLANA_DEVNET) or the tutorial's SOLANA_DEVNET_RPC and ETHEREUM_SEPOLIA_RPC_URL exports
  • File: create a .env file in the directory where you run ccip-cli (default --rpcs-file, one URL per line)

See the tutorial's Configure CCIP CLI section for the recommended --rpc examples.

Environment Variables

Variables use prefixes to prevent confusion across repositories and tools:

PrefixUsageExamples
ETH_*Ethereum addressesETH_TOKEN_ADDRESS, ETH_POOL_ADDRESS
SOL_*Solana addressesSOL_TOKEN_MINT, SOL_POOL_ADDRESS, SOL_WALLET_ADDRESS
SOL_CCIP_*Solana CCIP program IDsSOL_CCIP_POOL_PROGRAM, SOL_CCIP_ROUTER, SOL_CCIP_FEE_QUOTER_PROGRAM

Tutorial Approach

This tutorial provides step-by-step instructions with detailed explanations of what each command does and why. You'll work primarily in Terminal 1 (CCIP Solana BS58 Generator) with occasional switches to Terminal 2 (EVM).

Environment Variable Management: This tutorial uses phase-based variable files (e.g., ~/.phase1_vars, ~/.ccip_complete_vars) to eliminate manual variable re-entry when switching between terminals. Each phase saves its variables to files that subsequent phases can load automatically.

For detailed implementation code explanations, refer to:

EOA execution: In Terminal 1, append --execute to each pnpm bs58 command so your local Solana wallet signs and sends transactions directly. Set --authority to your wallet address ($SOL_ADMIN_WALLET). Read-only commands (get-state, get-chain-config, derive-accounts) do not use --execute.

Phase 1: Ethereum Sepolia Token Setup

In this step, you will use Hardhat tasks to deploy an ERC20 token contract and a corresponding burn and mint token pool on Ethereum Sepolia. The tasks interact with the BurnMintERC20 contract for token deployment and the BurnMintTokenPool contract for pool creation.

Current Terminal: Terminal 2 (Smart Contract Examples - Hardhat) Verify your location:

pwd
# Should show: .../smart-contract-examples/ccip/cct/hardhat

Step 1: Deploy ERC20 Token

Using the deployToken task, deploy a burnable and mintable ERC20 token on Ethereum Sepolia:

# Deploy BurnMint ERC20 token
npx hardhat deployToken \
 --name "BnM AEM Token" \
 --symbol BnMAEM \
 --decimals 18 \
 --verifycontract \
 --network ethereumSepolia

Export your token address for later use:

# Set token address from deployment
export ETH_TOKEN_ADDRESS="<YOUR_TOKEN_ADDRESS>"

Verify your token address:

echo "Token Address: $ETH_TOKEN_ADDRESS"

Step 2: Deploy Token Pool

In this step, you will use the deployTokenPool task to deploy a CCIP BurnMint token pool for the token on Ethereum Sepolia. This task interacts with the BurnMintTokenPool contract and grants the necessary mint and burn privileges to the pool.

# Deploy BurnMint pool
npx hardhat deployTokenPool \
 --tokenaddress $ETH_TOKEN_ADDRESS \
 --localtokendecimals 18 \
 --pooltype burnMint \
 --verifycontract \
 --network ethereumSepolia

Export your pool address for later use:

# Set pool address from deployment
export ETH_POOL_ADDRESS="<YOUR_POOL_ADDRESS>"

Verify your pool address:

echo "Pool Address: $ETH_POOL_ADDRESS"

Step 3: Mint Tokens

In this step, you will use the mintTokens task to mint tokens on Ethereum Sepolia for your Externally Owned Account (EOA). Since you assigned mint and burn privileges to your EOA when deploying the tokens, you can now mint tokens for testing purposes. This ensures that you have enough tokens in your EOA to perform cross-chain transfers later.

# Mint initial token supply for testing
npx hardhat mintTokens \
  --tokenaddress $ETH_TOKEN_ADDRESS \
  --amount 1000000000000000000000 \
  --network ethereumSepolia

Step 4: Claim Admin

In this step, you will use the claimAdmin task to register your EOA as the administrator for the deployed token on Ethereum Sepolia. This process involves calling the RegistryModuleOwnerCustom contract, which will fetch the CCIP admin of the token and set it up as the admin in the registry.

# Claim admin role
npx hardhat claimAdmin \
  --tokenaddress $ETH_TOKEN_ADDRESS \
  --network ethereumSepolia

Step 5: Accept Admin Role

In this step, you will use the acceptAdminRole task to accept the admin role for the deployed token on Ethereum Sepolia. Once you have claimed the role, accepting the role finalizes your control over the token administration.

# Accept admin role
npx hardhat acceptAdminRole \
 --tokenaddress $ETH_TOKEN_ADDRESS \
 --network ethereumSepolia

Save the Phase 1 variables for cross-terminal access:

# Save Phase 1 variables for cross-terminal use
cat > ~/.phase1_vars << EOF
export ETH_TOKEN_ADDRESS="$ETH_TOKEN_ADDRESS"
export ETH_POOL_ADDRESS="$ETH_POOL_ADDRESS"
EOF

echo "=== Phase 1 Complete - EVM Setup ==="
echo "✅ ETH Token: $ETH_TOKEN_ADDRESS"
echo "✅ ETH Pool: $ETH_POOL_ADDRESS"
echo "✅ Variables saved to ~/.phase1_vars"

Phase 2: Solana Devnet Token Setup

In this phase, you will create an SPL token, initialize the CCIP token pool, and complete CCIP registration before** setting up the SPL token multisig. This sequence is critical because the self-service registration requires you to hold the mint authority.

Switch to Terminal 1 (CCIP Solana BS58 Generator) Verify your location:

pwd
# Should show: .../ccip-solana-bs58-generator

Load the Ethereum addresses from Phase 1 and set CCIP constants:

source ~/.phase1_vars

export SOL_ADMIN_WALLET=$(solana address)
export SOL_CCIP_POOL_PROGRAM="41FGToCmdaWa1dgZLKFAjvmx6e6AjVTX7SVRibvsMGVB"
export SOL_CCIP_ROUTER="Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C"
export SOL_CCIP_FEE_QUOTER_PROGRAM="FeeQPGkKDeRV1MgoYfMH6L8o3KeuYjwUZrgn4LRKfjHi"
export ETH_SEPOLIA_CHAIN_SELECTOR="16015286601757825753"

echo "Loaded Phase 1: ETH_TOKEN_ADDRESS=$ETH_TOKEN_ADDRESS"
echo "Admin Wallet: $SOL_ADMIN_WALLET"

Step 1: Create SPL Token

Create an SPL token with Metaplex metadata and initial supply using direct EOA execution:

# Create SPL token with Metaplex metadata and initial supply
pnpm bs58 --env devnet --execute spl-token \
  --instruction create-mint \
  --authority $SOL_ADMIN_WALLET \
  --decimals 9 \
  --with-metaplex true \
  --name "AEM" \
  --symbol "CCIP-AEM" \
  --uri "https://cyan-pleasant-anteater-613.mypinata.cloud/ipfs/bafkreieirlwjqbtzniqsgcjebzexlcspcmvd4woh3ajvf2p4fuivkenw6i" \
  --initial-supply 1000000000000 \
  --recipient $SOL_ADMIN_WALLET

Set the token mint variable:

# Copy the mint address from the command output above
export SOL_TOKEN_MINT="<REPLACE_WITH_YOUR_TOKEN_MINT_ADDRESS>"

Verify the variable:

echo "Token Mint: $SOL_TOKEN_MINT"

Step 2: Initialize CCIP Token Pool

Initialize a CCIP token pool for your SPL token. This creates the on-chain state for cross-chain operations and establishes the Pool Signer PDA that will become a multisig member.

# Initialize pool for your token
pnpm bs58 --env devnet --execute burnmint-token-pool \
  --instruction initialize-pool \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_ADMIN_WALLET

Step 3: Verify Pool Creation

Derive pool accounts using the read-only derive-accounts command:

# Derive Pool Signer PDA and Pool State PDA
pnpm bs58 --env devnet utils \
  --instruction derive-accounts \
  --program-type burnmint-token-pool \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT

Copy the Pool Signer PDA and Pool State PDA from the output, then export them:

export SOL_POOL_SIGNER_PDA="<REPLACE_WITH_YOUR_POOL_SIGNER_PDA>"
export SOL_POOL_CONFIG_PDA="<REPLACE_WITH_YOUR_POOL_STATE_PDA>"

Verify the variables:

echo "Pool Signer PDA: $SOL_POOL_SIGNER_PDA"
echo "Pool Config PDA: $SOL_POOL_CONFIG_PDA"

Step 4: Create Pool Token Account

Create an Associated Token Account (ATA) for the Pool Signer PDA. This ATA is required for the pool to hold and manage tokens during cross-chain transfer operations.

# Create ATA for Pool Signer PDA
spl-token create-account $SOL_TOKEN_MINT \
  --owner $SOL_POOL_SIGNER_PDA \
  --fee-payer $HOME/.config/solana/id.json

Step 5: Claim Admin

Register yourself as the CCIP administrator for the Solana token. This must be completed while you still hold the mint authority.

# Propose yourself as CCIP administrator (requires mint authority)
pnpm bs58 --env devnet --execute router \
  --instruction owner-propose-administrator \
  --program-id $SOL_CCIP_ROUTER \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_ADMIN_WALLET \
  --token-admin-registry-admin $SOL_ADMIN_WALLET

Step 6: Accept Admin Role

Accept the proposed administrator role to establish CCIP admin control:

# Accept the proposed administrator role
pnpm bs58 --env devnet --execute router \
  --instruction accept-admin-role \
  --program-id $SOL_CCIP_ROUTER \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_ADMIN_WALLET

Step 7: Create SPL Token Multisig

Now that CCIP registration is complete, create the SPL token multisig that will serve as the mint authority:

# Create 1-of-2 multisig with Pool Signer PDA and admin wallet (tutorial setup)
pnpm bs58 --env devnet --execute spl-token \
  --instruction create-multisig \
  --authority $SOL_ADMIN_WALLET \
  --seed "ccip-multisig-001" \
  --mint $SOL_TOKEN_MINT \
  --signers "[\"$SOL_POOL_SIGNER_PDA\",\"$SOL_ADMIN_WALLET\"]" \
  --threshold 1

Set the multisig address environment variable:

# Set multisig address from the output above
export SOL_MULTISIG_ADDRESS="<REPLACE_WITH_YOUR_MULTISIG_ADDRESS>"

Verify output:

echo "Multisig Address: $SOL_MULTISIG_ADDRESS"

Step 8: Transfer Mint Authority to Multisig

Now transfer the mint authority from your wallet to the multisig:

# Transfer mint authority to multisig
pnpm bs58 --env devnet --execute spl-token \
  --instruction transfer-mint-authority \
  --authority $SOL_ADMIN_WALLET \
  --mint $SOL_TOKEN_MINT \
  --new-mint-authority $SOL_MULTISIG_ADDRESS

Step 9: Verify Multisig Configuration

Verify that the multisig has been properly configured and the mint authority has been transferred:

# Check token mint authority
spl-token display $SOL_TOKEN_MINT

# Check multisig details
spl-token display $SOL_MULTISIG_ADDRESS

Save the Phase 2 variables for cross-terminal access:

# Save all variables from Phases 1 and 2 to complete vars file
cat > ~/.ccip_complete_vars << EOF
export ETH_TOKEN_ADDRESS="$ETH_TOKEN_ADDRESS"
export ETH_POOL_ADDRESS="$ETH_POOL_ADDRESS"
export SOL_TOKEN_MINT="$SOL_TOKEN_MINT"
export SOL_ADMIN_WALLET="$SOL_ADMIN_WALLET"
export SOL_CCIP_POOL_PROGRAM="$SOL_CCIP_POOL_PROGRAM"
export SOL_CCIP_ROUTER="$SOL_CCIP_ROUTER"
export SOL_CCIP_FEE_QUOTER_PROGRAM="$SOL_CCIP_FEE_QUOTER_PROGRAM"
export ETH_SEPOLIA_CHAIN_SELECTOR="$ETH_SEPOLIA_CHAIN_SELECTOR"
export SOL_POOL_SIGNER_PDA="$SOL_POOL_SIGNER_PDA"
export SOL_POOL_CONFIG_PDA="$SOL_POOL_CONFIG_PDA"
export SOL_MULTISIG_ADDRESS="$SOL_MULTISIG_ADDRESS"
EOF

echo "=== Phase 2 Complete - Solana Setup ==="
echo "✅ SOL Token: $SOL_TOKEN_MINT"
echo "✅ Pool Signer PDA: $SOL_POOL_SIGNER_PDA"
echo "✅ Multisig Address: $SOL_MULTISIG_ADDRESS"
echo "✅ Variables saved to ~/.ccip_complete_vars"

Phase 3: Cross-Chain Configuration

In this step, you will configure bidirectional connectivity between the token pools on both chains. Each chain uses different tools: Solana uses CCIP Solana BS58 Generator commands to configure its pool to recognize Ethereum tokens and pools, while Ethereum uses Hardhat tasks to configure its pool to recognize Solana tokens and pools.

Step 1: Configure Solana -> Ethereum

Initialize Chain Remote Configuration

In this step, you will initialize the configuration for Ethereum Sepolia as a remote chain. This creates the basic chain configuration with token information but without pool addresses (those will be added in the next step).

# Initialize remote chain configuration for Ethereum Sepolia
pnpm bs58 --env devnet --execute burnmint-token-pool \
  --instruction init-chain-remote-config \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_ADMIN_WALLET \
  --remote-chain-selector $ETH_SEPOLIA_CHAIN_SELECTOR \
  --pool-addresses '[]' \
  --token-address $ETH_TOKEN_ADDRESS \
  --decimals 9

Add Ethereum Pool Address

In this step, you will update the previously created chain configuration with the Ethereum pool address.

# Add Ethereum pool address to the configuration
pnpm bs58 --env devnet --execute burnmint-token-pool \
  --instruction edit-chain-remote-config \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_ADMIN_WALLET \
  --remote-chain-selector $ETH_SEPOLIA_CHAIN_SELECTOR \
  --pool-addresses "[\"$ETH_POOL_ADDRESS\"]" \
  --token-address $ETH_TOKEN_ADDRESS \
  --decimals 9

Verify Configuration

In this step, you will verify that the Solana pool configuration for Ethereum Sepolia has been set up correctly.

# Verify the chain configuration is complete
pnpm bs58 --env devnet burnmint-token-pool \
  --instruction get-chain-config \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --remote-chain-selector $ETH_SEPOLIA_CHAIN_SELECTOR

Step 2: Configure Ethereum → Solana

Switch to Terminal 2 (Smart Contract Examples)

pwd
# Should show: .../smart-contract-examples/ccip/cct/hardhat

Load all variables from previous phases:

source ~/.ccip_complete_vars
echo "Loaded variables:"
echo "ETH_TOKEN_ADDRESS=$ETH_TOKEN_ADDRESS"
echo "ETH_POOL_ADDRESS=$ETH_POOL_ADDRESS"
echo "SOL_TOKEN_MINT=$SOL_TOKEN_MINT"
echo "Pool Config PDA: $SOL_POOL_CONFIG_PDA"
echo "Pool Signer PDA: $SOL_POOL_SIGNER_PDA"
echo "Multisig Address: $SOL_MULTISIG_ADDRESS"
echo "Admin Wallet: $SOL_ADMIN_WALLET"
echo "Pool Program: $SOL_CCIP_POOL_PROGRAM"

Configure Remote Chain

In this step, you will use the applyChainUpdates Hardhat task to configure the Ethereum pool to recognize the Solana token and pool. This tells the Ethereum pool which Solana pool (via its Pool Config PDA) and token it should interact with for cross-chain transfers.

# Configure Ethereum pool to recognize Solana chain
npx hardhat applyChainUpdates \
  --pooladdress $ETH_POOL_ADDRESS \
  --remotechain solanaDevnet \
  --remotepooladdresses $SOL_POOL_CONFIG_PDA \
  --remotetokenaddress $SOL_TOKEN_MINT \
  --network ethereumSepolia

Verify Remote Chain Configuration

Verify that the Ethereum pool is correctly configured to recognize the Solana chain:

# Verify Ethereum pool configuration
npx hardhat getPoolConfig \
  --pooladdress $ETH_POOL_ADDRESS \
  --network ethereumSepolia

Phase 4: Pool Registration

In this step, you will register the token pools with their respective tokens on both chains. This is the final configuration step that enables cross-chain operations by linking tokens to their pools in the CCIP registry.

Pool registration works differently on each platform:

  • Ethereum: Links the token directly to its pool contract address
  • Solana: Links the token to an Address Lookup Table containing all necessary pool accounts

Step 1: Ethereum Sepolia Pool Registration

Stay in Terminal 2 (Smart Contract Examples)

pwd
# Should show: .../smart-contract-examples/ccip/cct/hardhat

In this step, you will use the setPool Hardhat task to register the BurnMint token pool with the token in Ethereum's TokenAdminRegistry contract. This function sets the pool contract address for the token, enabling it for CCIP cross-chain transfers. Only the token administrator can call this function.

# Register token pool with TokenAdminRegistry contract
npx hardhat setPool \
 --tokenaddress $ETH_TOKEN_ADDRESS \
 --pooladdress $ETH_POOL_ADDRESS \
 --network ethereumSepolia

Step 2: Solana Devnet Pool Registration

Switch to Terminal 1 (CCIP Solana BS58 Generator)

pwd
# Should show: .../ccip-solana-bs58-generator

Create Address Lookup Table

Create an Address Lookup Table (ALT) containing all required CCIP accounts plus your multisig address:

# Create ALT with 10 base CCIP addresses + multisig address
pnpm bs58 --env devnet --execute router \
  --instruction create-lookup-table \
  --program-id $SOL_CCIP_ROUTER \
  --fee-quoter-program-id $SOL_CCIP_FEE_QUOTER_PROGRAM \
  --pool-program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_ADMIN_WALLET \
  --additional-addresses "[\"$SOL_MULTISIG_ADDRESS\"]"

Set ALT environment variable:

export SOL_ALT_ADDRESS="<REPLACE_WITH_YOUR_ALT_ADDRESS>"

Verify output:

echo "ALT Address: $SOL_ALT_ADDRESS"

Register Pool with Router

Register your token pool with the CCIP Router using the Address Lookup Table:

# Register pool with CCIP Router using the ALT
pnpm bs58 --env devnet --execute router \
  --instruction set-pool \
  --program-id $SOL_CCIP_ROUTER \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_ADMIN_WALLET \
  --pool-lookup-table $SOL_ALT_ADDRESS \
  --writable-indexes "[3,4,7]"

Save the complete configuration including the ALT address:

cat >> ~/.ccip_complete_vars << EOF
export SOL_ALT_ADDRESS="$SOL_ALT_ADDRESS"
EOF

echo "=== Complete Configuration Saved ==="
echo "✅ ALT Address: $SOL_ALT_ADDRESS"
echo "✅ All variables saved to ~/.ccip_complete_vars"
echo "✅ Ready for cross-chain testing"

Phase 5: Pre-Transfer Setup

Before validating your configuration, complete final setup steps.

Stay in Terminal 1 (CCIP Solana BS58 Generator)

pwd
# Should show: .../ccip-solana-bs58-generator

Step 1: Delegate Token Authority

Delegate token approval to the CCIP fee-billing signer PDA, which enables CCIP to transfer tokens on your behalf when sending cross-chain messages.

# Derive the CCIP fee-billing signer PDA
export SOL_CCIP_FEE_BILLING_SIGNER=$(solana find-program-derived-address $SOL_CCIP_ROUTER string:fee_billing_signer | head -1)

echo "SOL_CCIP_FEE_BILLING_SIGNER=$SOL_CCIP_FEE_BILLING_SIGNER"

Approve the fee-billing signer to transfer tokens from your ATA:

# --amount is u64 max (2^64 - 1): max delegation for CCIP fee billing
pnpm bs58 --env devnet --execute spl-token \
  --instruction approve \
  --authority $SOL_ADMIN_WALLET \
  --mint $SOL_TOKEN_MINT \
  --delegate $SOL_CCIP_FEE_BILLING_SIGNER \
  --amount 18446744073709551615

Step 2: Verify Delegation

Check that your token account is delegated to the fee-billing signer:

# Find your Associated Token Account (ATA) address
export SOL_TOKEN_ACCOUNT=$(spl-token accounts $SOL_TOKEN_MINT --owner $SOL_ADMIN_WALLET --addresses-only)

echo "SOL_TOKEN_ACCOUNT=$SOL_TOKEN_ACCOUNT"

Display token account and delegation status:

spl-token display $SOL_TOKEN_ACCOUNT

Phase 6: Test Cross-Chain Transfers

Validate your configuration, then execute bidirectional token transfers using the CCIP CLI.

Step 1: Load Complete Configuration

source ~/.ccip_complete_vars

echo "=== Configuration Validation ==="
echo "✅ ETH Token: $ETH_TOKEN_ADDRESS"
echo "✅ ETH Pool: $ETH_POOL_ADDRESS"
echo "✅ SOL Token: $SOL_TOKEN_MINT"
echo "✅ Pool Signer PDA: $SOL_POOL_SIGNER_PDA"
echo "✅ Multisig Address: $SOL_MULTISIG_ADDRESS"
echo "✅ ALT Address: $SOL_ALT_ADDRESS"
echo "✅ Admin Wallet: $SOL_ADMIN_WALLET"

Step 2: Verify Solana Pool and Chain Config

# Verify pool state
pnpm bs58 --env devnet burnmint-token-pool \
  --instruction get-state \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT

# Verify Ethereum Sepolia remote chain config
pnpm bs58 --env devnet burnmint-token-pool \
  --instruction get-chain-config \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --remote-chain-selector $ETH_SEPOLIA_CHAIN_SELECTOR

# Verify mint authority is the multisig
spl-token display $SOL_TOKEN_MINT

# Verify token balance and delegation
spl-token balance $SOL_TOKEN_MINT

Step 3: Verify Ethereum Pool (Terminal 2)

Switch to Terminal 2 (Smart Contract Examples - Hardhat):

npx hardhat getPoolConfig \
  --pooladdress $ETH_POOL_ADDRESS \
  --network ethereumSepolia

Confirm the Ethereum pool recognizes Solana Devnet with your $SOL_POOL_CONFIG_PDA and $SOL_TOKEN_MINT.

Configure CCIP CLI

Export these RPC URLs once before your first transfer:

export SOLANA_DEVNET_RPC="https://api.devnet.solana.com"
export ETHEREUM_SEPOLIA_RPC_URL="<YOUR_ETHEREUM_SEPOLIA_RPC_URL>"

export ETH_CCIP_ROUTER="0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59"

Use the same ETHEREUM_SEPOLIA_RPC_URL from your env-enc setup in Terminal 2 (npx env-enc view). On ccip-cli send, the source chain RPC quotes fees and submits the transaction; for token-only transfers in this tutorial, that is the only RPC strictly required to send. Pass both RPCs on cross-chain commands anyway: the destination RPC is required for ccip-cli show --wait and for optional send features such as --estimate-gas-limit or send --wait.

Transfer Solana → Ethereum

Terminal 1 (CCIP Solana BS58 Generator):

source ~/.ccip_complete_vars

export ETH_RECEIVER_ADDRESS="<YOUR_ETHEREUM_RECEIVER_ADDRESS>"


ccip-cli send \
  -s solana-devnet \
  -r $SOL_CCIP_ROUTER \
  -d ethereum-testnet-sepolia \
  --to $ETH_RECEIVER_ADDRESS \
  -t $SOL_TOKEN_MINT=0.001 \
  --wallet ~/.config/solana/id.json \
  --rpc $SOLANA_DEVNET_RPC \
  --rpc $ETHEREUM_SEPOLIA_RPC_URL

The -t flag uses human-readable amounts (0.001 tokens with 9 decimals = 1,000,000 smallest units).

# Replace with the transaction hash or message ID from the send output
ccip-cli show <TX_HASH_OR_MESSAGE_ID> --wait \
  --rpc $SOLANA_DEVNET_RPC \
  --rpc $ETHEREUM_SEPOLIA_RPC_URL

Transfer Ethereum → Solana

Switch to Terminal 2 (Smart Contract Examples - Hardhat):

ccip-cli does not load Hardhat env-enc automatically. Run these commands from the Hardhat project directory. Prefer Hardhat keystore over exporting a private key in plain text.

source ~/.ccip_complete_vars

ccip-cli send \
  -s ethereum-testnet-sepolia \
  -r $ETH_CCIP_ROUTER \
  -d solana-devnet \
  --to $SOL_ADMIN_WALLET \
  -t $ETH_TOKEN_ADDRESS=1.0 \
  --wallet hardhat:<YOUR_KEYSTORE_ACCOUNT_NAME> \
  --rpc $ETHEREUM_SEPOLIA_RPC_URL \
  --rpc $SOLANA_DEVNET_RPC

Use the keystore account name from your Hardhat project (see Hardhat keystore). This keeps the signing key encrypted instead of in shell history.

For token-only transfers to Solana, --to is the destination wallet that receives minted tokens. CCIP fees are paid in native Sepolia ETH by default — ensure your wallet has sufficient ETH for gas and fees.

ccip-cli show <TX_HASH_OR_MESSAGE_ID> --wait \
  --rpc $ETHEREUM_SEPOLIA_RPC_URL \
  --rpc $SOLANA_DEVNET_RPC

Congratulations! Your cross-chain token infrastructure with SPL token multisig governance is fully configured on both chains.

Optional: Verify Mint Authority Control

Demonstrate Manual Minting Through Multisig

This optional section demonstrates that transferring mint authority to the multisig doesn't mean "losing control" - you can still mint tokens manually through the Admin Wallet (the non-PDA signer in your multisig). This proves the multisig setup is working correctly and that you retain administrative capabilities.

# Demonstrate manual minting through the multisig
# This proves you haven't "lost" mint authority - it's just managed through the multisig
spl-token mint $SOL_TOKEN_MINT 1 \
  --owner $SOL_MULTISIG_ADDRESS \
  --multisig-signer $HOME/.config/solana/id.json

What's next

Get the latest Chainlink content straight to your inbox.