Cross-Chain Token Setup: LockRelease Pool with Squads Governance

This tutorial demonstrates how to implement cross-chain tokens using the Lock and Mint mechanism with production-grade governance.

What You Will Build

You'll create a cross-chain token system with two key components:

  • LockRelease Pool on Solana Devnet: Locks tokens when sending from Solana, releases tokens when receiving to Solana
  • BurnMint Pool on Ethereum Sepolia: Mints tokens when receiving from Solana, burns tokens when sending to Solana

Cross-Chain Transfer Flow:

Solana → Ethereum (Lock and Mint):

  1. Lock: LockRelease pool locks tokens in the token pool on Solana
  2. Mint: BurnMint pool mints tokens on Ethereum

Ethereum → Solana (Burn and Unlock):

  1. Burn: BurnMint pool burns tokens on Ethereum
  2. Unlock: LockRelease pool releases tokens from the token pool on Solana

Governance Architecture

ComponentImplementationGovernance Model
Ethereum SepoliaERC20 token with CCIP BurnMint poolEOA-controlled (tutorial)
Solana DevnetSPL token with CCIP LockRelease poolSquads multisig
Cross-Chain TransferBidirectional CCIP token transfersAutonomous operations

This tutorial demonstrates LockRelease pool governance using Squads multisig for production-grade security:

Key Governance Roles:

  • Pool Owner (Squads): Controls pool configuration, rate limits, and operational parameters
  • Rebalancer (Squads): Manages liquidity provisioning across chains
  • Token Mint Authority: Remains with original token creator (no transfer required)

Production Benefits:

  • No mint authority complexity: LockRelease pools don't need token minting permissions
  • Focused governance: Squads handles pool operations and liquidity management only
  • Production security: Multi-signature approval for all critical pool operations

For complete details on token handling mechanisms, see Token Handling Mechanisms.

Prerequisites

This tutorial uses a two-terminal workflow with Squads multisig governance on Solana. Install the system tools below, create your Squad on devnet, clone both repositories, then complete environment setup before starting Phase 1.

System Requirements

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

Wallets:

Install the CCIP CLI globally:

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

See the CCIP CLI documentation for RPC and wallet configuration.

Repository Setup

Terminal 1: CCIP Solana BS58 Generator (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

Terminal 2: Smart Contract Examples (Hardhat) (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

Environment Configuration

Solana (Terminal 1)

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 Ethereum Sepolia 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 3
solana balance

Ethereum Sepolia (Terminal 2)

# 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:

  • ETHEREUM_SEPOLIA_RPC_URL: RPC endpoint (Alchemy or Infura)
  • PRIVATE_KEY: Testnet wallet private key
  • ETHERSCAN_API_KEY: API key from Etherscan

Testnet tokens:

  • Solana Devnet: solana airdrop 3 for SOL
  • Ethereum Sepolia: ETH for gas and CCIP fees (Chainlink faucet or Google Cloud Faucet). LINK is optional for --fee-token LINK on EVM sends.

Squads Multisig Setup

Step 1: Prepare signers

# Create additional signers using Solana CLI
solana-keygen new --outfile ~/.config/solana/signer2.json
solana-keygen new --outfile ~/.config/solana/signer3.json

# Get signer addresses
solana address --keypair ~/.config/solana/id.json
solana address --keypair ~/.config/solana/signer2.json
solana address --keypair ~/.config/solana/signer3.json

# Fund signers (minimum 0.1 SOL each for transaction fees)
solana transfer <SIGNER2_ADDRESS> 0.1 --allow-unfunded-recipient
solana transfer <SIGNER3_ADDRESS> 0.1 --allow-unfunded-recipient

Alternatively, create signers in Phantom and fund them:

solana transfer <PHANTOM_SIGNER_ADDRESS> 0.5 --allow-unfunded-recipient

Step 2: Create your Squad

  1. Visit devnet.squads.so
  2. Connect your Solana wallet (Phantom or Backpack)
  3. Click Create New Squad
  4. Configure members and threshold (recommended: 2/3 or 3/5)

Step 3: Record the vault address

After Squad creation, open the Settings tab and copy the vault address (not the multisig address).

In Terminal 1:

# CRITICAL: Use the VAULT address, NOT the multisig address
export SOL_SQUAD_VAULT_MULTISIG="YOUR_VAULT_ADDRESS_HERE"
echo "Squads Vault Address: $SOL_SQUAD_VAULT_MULTISIG"

Step 4: Test your Squad

  1. Send a small amount of SOL to your Squad vault
  2. Create a test transaction in Squads UI (e.g., SOL transfer to your wallet)
  3. Confirm signers can approve and execute

See Squads Documentation for detailed setup guidance.

Token Creation Option

Tutorial Approach

TerminalRepositoryPurposeCommands
Terminal 1CCIP Solana BS58 GeneratorGovernance txs and pre-transfer setuppnpm bs58
Terminal 2Smart Contract Examples (Hardhat)Deploy and configure EVM componentsnpx hardhat
EitherGlobal @chainlink/ccip-cliCross-chain transfer testing (final phase)ccip-cli send

The CCIP Solana BS58 Generator README contains command syntax, simulation behavior, and troubleshooting.

Key Implementation Notes

  • Terminal 1 generates base58 transactions for Squads governance (no --execute). Pre-transfer delegation steps use --execute with your local wallet.
  • Terminal 2 uses an EOA for tutorial simplicity; production EVM deployments should use multisig wallets (e.g., Safe).
  • Cross-chain transfers use @chainlink/ccip-cli: Terminal 1 for Solana → EVM, Terminal 2 for EVM → Solana. For EVM sends, prefer --wallet hardhat:<name> (Hardhat keystore) from the Hardhat directory; alternatively export PRIVATE_KEY manually after npx env-enc view (Hardhat tasks load env-enc automatically, but ccip-cli does not). Provide RPCs via --rpc/--rpcs, RPC_* environment variables, or a .env file — see CCIP CLI configuration.

base58 Transaction Execution Workflow

Transaction generation:

  1. CLI simulation: Each base58 transaction is simulated during generation
  2. Error handling: If simulation fails, do not upload the transaction to Squads
  3. Success: Terminal shows a transaction preview and base58 output

Squads execution:

  1. Propose: Import base58 output in Squads UI → Developers (left sidebar) → TX BuilderImport base58 encoded txInitiate Transaction
  2. Approve: Required threshold of signers approves
  3. Simulate (recommended): Preview onchain effects in Squads before execution
  4. Execute: Any approved signer executes after threshold is met

Environment Variables

Variables use prefixes to prevent confusion across repositories:

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

Phase 1: EVM Chain Setup (Ethereum Sepolia)

In this phase, you will deploy ERC20 tokens and configure CCIP BurnMint pools on Ethereum Sepolia. This setup is identical across all Path A variants and provides the foundation for cross-chain operations.

Step 1: Prepare EVM Environment

First, set up your terminal and verify your environment:

# Terminal 2: Navigate to EVM repository

# Verify location and project structure

# Should output: smart-contract-examples/ccip/cct/hardhat

pwd

# Verify private key variables are set:

npx env-enc view

Step 2: Deploy ERC20 Token

Deploy your cross-chain token on Ethereum Sepolia:

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

# Copy the token address from the output above

Set the token address variable:

# REPLACE with actual address from deployment output
export ETH_TOKEN_ADDRESS="<INSERT_YOUR_ACTUAL_TOKEN_ADDRESS>"

Verify the address is set correctly:

echo "✅ Ethereum Token Address: $ETH_TOKEN_ADDRESS"

Step 3: Deploy and Configure CCIP BurnMint Pool

Deploy the BurnMint token pool:

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

Set the pool address and configure:

# REPLACE with actual address from deployment output
export ETH_POOL_ADDRESS="<INSERT_YOUR_ACTUAL_POOL_ADDRESS>"

Verify configuration

echo "✅ Ethereum Pool Address: $ETH_POOL_ADDRESS"

Step 4: Mint Initial Token Supply

Mint tokens for testing:

# Mint 1000 tokens for testing
npx hardhat mintTokens \
  --tokenaddress $ETH_TOKEN_ADDRESS \
  --amount 1000000000000000000000 \
  --network ethereumSepolia

# Note: Balance can be checked in your wallet or through block explorer

echo "✅ Tokens minted successfully"

Step 5: Claim CCIP Admin Role

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

# Claim admin role for CCIP token registry
npx hardhat claimAdmin \
  --tokenaddress $ETH_TOKEN_ADDRESS \
  --network ethereumSepolia

Step 6: Accept CCIP Admin Role

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

# Accept admin role to complete CCIP registration
npx hardhat acceptAdminRole \
--tokenaddress $ETH_TOKEN_ADDRESS \
--network ethereumSepolia

Step 7: Register Pool with Token

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

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

Phase 1 Complete: Save your variables:

# Save Phase 1 variables for later use
echo "export ETH_TOKEN_ADDRESS=\"$ETH_TOKEN_ADDRESS\"" > ~/.phase1_vars
echo "export ETH_POOL_ADDRESS=\"$ETH_POOL_ADDRESS\"" >> ~/.phase1_vars

echo "=== Phase 1 Complete - EVM Setup ==="
echo "✅ ETH Token: $ETH_TOKEN_ADDRESS"
echo "✅ ETH Pool: $ETH_POOL_ADDRESS"

Phase 2: Solana LockRelease Setup with Squads Governance

In this phase, you will implement the production-grade LockRelease pool with Squads multisig governance on Solana Devnet.

Step 1: Prepare base58 Environment

# Terminal 1: Verify location
# Should output: ccip-solana-bs58-generator
pwd

Set up CCIP constants on Solana Devnet (DO NOT CHANGE THESE)

export SOL_CCIP_POOL_PROGRAM="8eqh8wppT9c5rw4ERqNCffvU6cNFJWff9WmkcYtmGiqC"
export SOL_CCIP_ROUTER="Ccip842gzYHhvdDkSyi2YVCoAWPbYJoApMFzSxQroE9C"
export SOL_CCIP_FEE_QUOTER_PROGRAM="FeeQPGkKDeRV1MgoYfMH6L8o3KeuYjwUZrgn4LRKfjHi"

Note: You can find the Router, Fee Quoter, and self-serve pool programs addresses in the CCIP Directory.

Get your Solana wallet address for token recipient:

export SOL_WALLET_ADDRESS=$(solana address)

Verify all required variables are set:

echo "✅ Pool Program: $SOL_CCIP_POOL_PROGRAM"
echo "✅ Router Program: $SOL_CCIP_ROUTER"
echo "✅ Squad Vault (from Prerequisites): $SOL_SQUAD_VAULT_MULTISIG"
echo "✅ Recipient Wallet: $SOL_WALLET_ADDRESS"

Step 2: Create SPL Token

This command generates a transaction that creates a new SPL token mint with the following features:

  • Mint Authority: Set to your Squad vault multisig for governance control
  • Metaplex Metadata: Includes token name, symbol, and URI for cross-platform compatibility
  • Initial Supply: Automatically mints 5,000 tokens (5,000,000,000,000 smallest units) to your wallet
  • Deterministic Address: Uses a seed-based approach for predictable mint addresses

Generate the SPL token creation transaction (customize parameters or reuse the example):

# Generate SPL token creation transaction with initial supply
pnpm bs58 spl-token \
  --env devnet \
  --instruction create-mint \
  --authority $SOL_SQUAD_VAULT_MULTISIG \
  --decimals 9 \
  --with-metaplex true \
  --name "AEM" \
  --symbol "CCIP-AEM" \
  --uri "https://cyan-pleasant-anteater-613.mypinata.cloud/ipfs/bafkreieirlwjqbtzniqsgcjebzexlcspcmvd4woh3ajvf2p4fuivkenw6i" \
  --initial-supply 5000000000000 \
  --recipient $SOL_WALLET_ADDRESS

After execution, set the token mint:

# Copy the mint address from the base58 generator output above
export SOL_TOKEN_MINT="<YOUR_ACTUAL_MINT_ADDRESS>"

Verify the token mint:

echo "✅ Token Mint: $SOL_TOKEN_MINT"

Step 3: Initialize LockRelease Token Pool

This command creates only one new account and establishes references to existing infrastructure:

What Gets Created:

  • Pool State PDA: Creates the onchain Pool State account that stores your token's CCIP configuration

What Gets Referenced (Not Created):

  • Authority Assignment: Sets your Squad vault as the pool owner with full configuration control
  • Router Integration: References the existing global CCIP router and RMN (Risk Management Network)
  • Pool Signer PDA: The Pool Signer PDA is programmatically derived - it will be used on-demand during cross-chain operations

The initialize-pool instruction creates only the Pool State account for your SPL token mint, establishing your Squad vault as the pool owner while referencing existing global infrastructure.

Generate the pool initialization transaction:

# Generate pool initialization transaction
pnpm bs58 lockrelease-token-pool --env devnet \
  --instruction initialize-pool \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG

Account Breakdown from the Transaction (Example):

AccountAddressPurpose
#19n9UgFppJCanCjy6cd3tEudEemUf4bQRR8PgiXN7Rp4RPool State PDA - Your main pool configuration
#23nLW4zA3xdZmKY5SyhP2RRr7BDMKLuh4SQ9kFTDsx1LiToken Mint (your token)
#3AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBAuthority (your Squad vault)
#411111111111111111111111111111111System Program
#58eqh8wppT9c5rw4ERqNCffvU6cNFJWff9WmkcYtmGiqCCCIP Pool Program
#6AeMEQshfd7w72oGt5cMn8196zod6gQPWskbf5BY83W1BProgram Data PDA
#76Nh9CWRGW69VJ7mnD9ELjm6BMkbvoGvVse2HuNuaHPqmGlobal Config PDA - Program-wide configuration settings

Key Addresses You Need:

  • Account #1SOL_POOL_ADDRESS (writable account = pool state)
  • Pool Signer PDA → Must be derived separately (NOT in transaction accounts)

After execution, set the pool state address from the transaction:

# Set the pool state address from Account #1 in the transaction above
export SOL_POOL_ADDRESS="<YOUR_ACTUAL_POOL_STATE_ADDRESS>"

Verify the pool address:

echo "✅ Pool Address: $SOL_POOL_ADDRESS"

Step 4: Derive Pool Signer PDA

The Pool Signer PDA is the critical address that will serve as the autonomous signing authority for cross-chain mint and burn operations.

# Derive all pool-related accounts including the Pool Signer PDA
pnpm bs58 utils \
  --env devnet \
  --instruction derive-accounts \
  --program-type lockrelease-token-pool \
  --program-id "$SOL_CCIP_POOL_PROGRAM" \
  --mint "$SOL_TOKEN_MINT"

Set the Pool Signer PDA address for use in subsequent steps:

# Set the Pool Signer PDA from the derivation output above
export SOL_POOL_SIGNER_PDA="<YOUR_ACTUAL_POOL_SIGNER_PDA>"

Verify both addresses are set correctly:

echo "Pool Address: $SOL_POOL_ADDRESS"
echo "Pool Signer PDA: $SOL_POOL_SIGNER_PDA"

Step 5: Register CCIP Administrator

This two-step process establishes your Squad vault as the CCIP token administrator, enabling you to enable your token in CCIP. Since your Squad vault currently holds the mint authority, you can complete this registration using the self-service registration flow without external assistance.

Why This Works: The Router's owner_propose_administrator instruction verifies onchain that the caller matches the token's mint_authority field. Your Squad vault has this authority, enabling PATH A self-service registration.

Sub-step 5a: Propose Administrator

The owner_propose_administrator instruction creates a TokenAdminRegistry PDA for your token and sets your Squad vault as the pending administrator:

# Generate administrator registration transaction
pnpm bs58 router --env devnet --instruction owner-propose-administrator \
  --program-id $SOL_CCIP_ROUTER \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG \
  --token-admin-registry-admin $SOL_SQUAD_VAULT_MULTISIG

Account Breakdown from the Transaction (Example):

AccountAddressDescription
#13Yrg9E4ySAeRezgQY99NNarAmFLtixapga9MZb6y2dt3Router Config PDA (read-only)
#26gmKNyGtrSgB4gAH9yQyzXSPnDw1P5We9abZLEcuqdLY🎯 Token Admin Registry PDA - Gets created/updated for your token
#33nLW4zA3xdZmKY5SyhP2RRr7BDMKLuh4SQ9kFTDsx1LiToken Mint (your token)
#4AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBAuthority/Payer (your Squad vault)
#511111111111111111111111111111111System Program

What This Transaction Does: This transaction proposes your Squad vault as the administrator for your token in the Router's Token Admin Registry. Account #2 is the Token Admin Registry PDA that stores who has administrative control over your token's cross-chain operations.

Sub-step 5b: Accept Administrator Role

The accept_admin_role instruction completes the registration process by having the pending administrator (your Squad vault) explicitly accept the CCIP token administrator role:

# Generate administrator role acceptance transaction
pnpm bs58 router --env devnet --instruction accept-admin-role \
  --program-id $SOL_CCIP_ROUTER \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG

Account Breakdown from the Transaction (Example):

AccountAddressDescription
#13Yrg9E4ySAeRezgQY99NNarAmFLtixapga9MZb6y2dt3Router Config PDA (read-only)
#26gmKNyGtrSgB4gAH9yQyzXSPnDw1P5We9abZLEcuqdLY🎯 Token Admin Registry PDA - Updates status to "active"
#33nLW4zA3xdZmKY5SyhP2RRr7BDMKLuh4SQ9kFTDsx1LiToken Mint (your token)
#4AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBAuthority (your Squad vault - must be pending admin)

What This Transaction Does: This is the acceptance step of the two-phase administrator registration. It updates the Token Admin Registry PDA (Account #2) from "pending administrator" to "active administrator" status. Your Squad vault explicitly accepts the administrator role, completing the secure registration process.

Key Difference: Only 4 accounts (vs. 5 in the propose step) because we're updating an existing registry entry, not creating a new one.

After both transactions are executed, your Squad vault will be the CCIP token administrator. See Registration & Administration for more details.

Step 6: Create Squad Vault Token Account

Create the Associated Token Account (ATA) for the Squad vault. This account will receive the minted tokens that will later be used for pool liquidity.

# Create Associated Token Account for Squad vault
spl-token create-account $SOL_TOKEN_MINT \
  --owner $SOL_SQUAD_VAULT_MULTISIG \
  --fee-payer $HOME/.config/solana/id.json

Note: You can use a different fee payer if needed by changing the --fee-payer parameter.

Transaction Details:

  • Squad Vault ATA: ApwnQFrEvC2Zo9PD6YigMNDzQQPQvFm4bNE1EUtFZzMV (example)
  • Owner: Squad vault PDA

Step 7: Mint Tokens for Pool Liquidity

Mint tokens to your Squad vault to prepare for LockRelease pool liquidity provisioning. Since LockRelease pools require liquidity management, we'll mint tokens that the Squad vault (acting as rebalancer) can later provide to the pool.

Purpose:

  • Liquidity Preparation: Create tokens that will be used for pool liquidity management
  • Rebalancer Setup: Squad vault will serve as the rebalancer role for the LockRelease pool
  • Pool Operations: LockRelease pools need liquidity to fulfill cross-chain transfer requests
# Generate token minting transaction using SPL multisig

pnpm bs58 spl-token --env devnet --instruction mint \
  --authority "$SOL_SQUAD_VAULT_MULTISIG" \
  --mint "$SOL_TOKEN_MINT" \
  --amount 100000000000 \
  --recipient "$SOL_SQUAD_VAULT_MULTISIG"

Account Breakdown from the Transaction (Example):

AccountAddressDescription
#13nLW4zA3xdZmKY5SyhP2RRr7BDMKLuh4SQ9kFTDsx1LiToken Mint (writable - supply updated)
#2ApwnQFrEvC2Zo9PD6YigMNDzQQPQvFm4bNE1EUtFZzMVSquad vault's ATA (writable - receives tokens)
#3AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBSquad Vault (signer)

What This Transaction Does: This is a standard SPL token mint operation that mints tokens for the Squad vault to use as liquidity for the LockRelease pool. Since the Squad vault retains mint authority, it can directly mint tokens without requiring multisig operations.

Key Details:

  • Amount: 100,000,000,000 smallest units = 100 tokens (with 9 decimals)
  • Recipient: Squad vault address (will serve as rebalancer for pool liquidity)
  • Mint Authority: Squad vault (retains original mint authority)
  • Token Program: SPL Token v1 (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)

Transaction Flow:

  1. Authority Verification: SPL Token program verifies Squad vault has mint authority
  2. Token Creation: 100 tokens are minted and added to total supply
  3. Token Transfer: New tokens are deposited into Squad vault's ATA
  4. Liquidity Preparation: Tokens are now available for pool liquidity operations

Step 8: Set Rebalancer Role

Configure the Squad vault as the rebalancer for the LockRelease pool. The rebalancer role is required before any liquidity operations can be performed.

What This Command Does:

  • Rebalancer Assignment: Designates the Squad vault as the authorized liquidity manager
  • Pool Configuration: Updates the pool state to allow liquidity operations
  • Governance Setup: Establishes who can provide and withdraw pool liquidity
# Generate set rebalancer transaction
pnpm bs58 lockrelease-token-pool \
  --env devnet \
  --instruction set-rebalancer \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG \
  --rebalancer $SOL_SQUAD_VAULT_MULTISIG

Account Breakdown:

AccountAddressDescription
#19n9UgFppJCanCjy6cd3tEudEemUf4bQRR8PgiXN7Rp4RPool state PDA (writable)
#23nLW4zA3xdZmKY5SyhP2RRr7BDMKLuh4SQ9kFTDsx1LiToken Mint (read-only - your token mint)
#3AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBSquad Vault (signer)

Step 9: Create Pool Token Account

Create the Associated Token Account (ATA) for the Pool Signer PDA. This account will hold the pool's token reserves for cross-chain operations.

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

Note: You can use a different fee payer if needed by changing the --fee-payer parameter.

Transaction Details:

  • Pool ATA: GQ4md7wUKmU35m4munp8giYg9osXCTaGVCPvNYzz8Wnu (example)
  • Owner: Pool Signer PDA

Step 10: Set Can Accept Liquidity

Configure the LockRelease pool to accept liquidity from the rebalancer. This setting must be enabled before the pool can receive liquidity provisions.

What This Command Does:

  • Liquidity Configuration: Enables the pool to accept liquidity from the authorized rebalancer
  • Pool Setting: Updates the can_accept_liquidity flag to true
  • Required Setup: Must be completed before providing liquidity to the pool
# Generate set can accept liquidity transaction
pnpm bs58 lockrelease-token-pool \
  --env devnet \
  --instruction set-can-accept-liquidity \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG \
  --allow true

Account Breakdown:

AccountAddressDescription
#19n9UgFppJCanCjy6cd3tEudEemUf4bQRR8PgiXN7Rp4RPool State PDA (writable - configuration updated)
#23nLW4zA3xdZmKY5SyhP2RRr7BDMKLuh4SQ9kFTDsx1LiToken Mint (read-only)
#3AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBSquad Vault (signer - pool owner authority)

Transaction Flow:

  1. Authority Verification: Confirms Squad vault is the pool owner
  2. Configuration Update: Sets can_accept_liquidity flag to true
  3. Pool Preparation: Enables the pool to accept liquidity from the rebalancer

Step 11: Approve Pool Signer as Delegate

Approve the Pool Signer PDA to spend tokens from the Squad vault's ATA. This delegation is required for the pool to transfer tokens during liquidity operations.

What This Command Does:

  • Token Delegation: Authorizes Pool Signer PDA to spend tokens from Squad vault's ATA
  • Liquidity Permission: Enables the pool to access Squad vault's tokens for liquidity operations
  • Required Setup: Must be completed before the pool can provide liquidity
# Generate approve delegation transaction
pnpm bs58 spl-token \
  --env devnet \
  --instruction approve \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG \
  --delegate $SOL_POOL_SIGNER_PDA \
  --amount "50000000000"

Account Breakdown:

AccountAddressDescription
#1ApwnQFrEvC2Zo9PD6YigMNDzQQPQvFm4bNE1EUtFZzMVSquad Vault ATA (writable - delegation updated)
#25FBAzopzmZ1oo2aadR1PKfTZqK7viBYipjvDVMyEQxzkPool Signer PDA (read-only - delegate)
#3AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBSquad Vault (signer - token account owner)

Transaction Flow:

  1. Authority Verification: Confirms Squad vault owns the token account
  2. Delegation Setup: Authorizes Pool Signer PDA to spend up to 50 tokens

Step 12: Provide Liquidity to Pool

Provide initial liquidity to the LockRelease pool using the tokens minted in Step 7. Liquidity management is essential to ensure that for inbound token transfers (when Solana is the destination chain), the pool has sufficient token balance in its reserves to transfer tokens to receivers.

What This Command Does:

  • Liquidity Transfer: Moves tokens from Squad vault's ATA to the pool's ATA
  • Pool Funding: Provides the pool with tokens needed for cross-chain operations
  • Rebalancer Operation: Uses the rebalancer role set in Step 7
# Generate provide liquidity transaction
pnpm bs58 lockrelease-token-pool \
  --env devnet \
  --instruction provide-liquidity \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG \
  --amount "50000000000"

Note: This provides 50 tokens (50,000,000,000 smallest units) as initial pool liquidity.

Account Breakdown:

AccountAddressDescription
#19n9UgFppJCanCjy6cd3tEudEemUf4bQRR8PgiXN7Rp4RPool State PDA (read-only)
#2TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DAToken Program (read-only)
#33nLW4zA3xdZmKY5SyhP2RRr7BDMKLuh4SQ9kFTDsx1LiToken Mint (writable)
#45FBAzopzmZ1oo2aadR1PKfTZqK7viBYipjvDVMyEQxzkPool Signer PDA (read-only)
#5GQ4md7wUKmU35m4munp8giYg9osXCTaGVCPvNYzz8WnuPool Token ATA (writable - receives liquidity)
#6ApwnQFrEvC2Zo9PD6YigMNDzQQPQvFm4bNE1EUtFZzMVSquad Vault ATA (writable - provides liquidity)
#7AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBSquad Vault (signer - rebalancer authority)

Transaction Flow:

  1. Authority Verification: Confirms Squad vault has rebalancer permissions
  2. Token Transfer: Moves 50 tokens from Squad vault ATA to pool ATA
  3. Pool Update: Updates pool's available liquidity balance
  4. Ready for Operations: Pool can now fulfill cross-chain transfer requests

Phase 2 Complete: Save your variables:

# Save all Solana variables
cat > ~/.phase2_vars << EOF
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 SOL_SQUAD_VAULT_MULTISIG="$SOL_SQUAD_VAULT_MULTISIG"
export SOL_TOKEN_MINT="$SOL_TOKEN_MINT"
export SOL_POOL_ADDRESS="$SOL_POOL_ADDRESS"
export SOL_POOL_SIGNER_PDA="$SOL_POOL_SIGNER_PDA"
export SOL_WALLET_ADDRESS="$SOL_WALLET_ADDRESS"
EOF

echo "=== Phase 2 Complete - LockRelease Setup ==="
echo "✅ Token Mint: $SOL_TOKEN_MINT"
echo "✅ Pool Address: $SOL_POOL_ADDRESS"
echo "✅ Pool Signer PDA: $SOL_POOL_SIGNER_PDA"
echo "✅ Squads Multisig: $SOL_SQUAD_VAULT_MULTISIG"
echo "✅ Wallet Address: $SOL_WALLET_ADDRESS"

Phase 3: Solana Cross-Chain Setup

In this phase, you will configure the cross-chain connection and complete the CCIP setup.

Step 1: Load Phase 1 Variables

Load Phase 1 variables and set the Ethereum Sepolia chain selector for cross-chain configuration.

What This Step Does:

  • Terminal Verification: Confirms you're in the correct base58 generator repository
  • Variable Loading: Imports EVM token and pool addresses from Phase 1
  • Chain Selector Setup: Establishes Ethereum Sepolia chain selector for cross-chain configuration
# Verify you are in the correct terminal (terminal 1)
# Should output: /Users/.../ccip-solana-bs58-generator
pwd

# Load Phase 1 EVM variables

source ~/.phase1_vars

# Set chain selector for Ethereum Sepolia

export ETHEREUM_SEPOLIA_CHAIN_SELECTOR="16015286601757825753"

# Verify variables are loaded

echo "✅ ETH Token: $ETH_TOKEN_ADDRESS"
echo "✅ ETH Pool: $ETH_POOL_ADDRESS"
echo "✅ Chain Selector: $ETHEREUM_SEPOLIA_CHAIN_SELECTOR"

Step 2: Configure Cross-Chain Pool Settings

Configure your token pool for cross-chain transfers to Ethereum Sepolia. This process involves two sequential operations:

  1. Initialize Chain Remote Config: Create the basic cross-chain configuration using init_chain_remote_config
  2. Edit Chain Remote Config: Add the remote pool address using edit_chain_remote_config

Step 2A: Initialize Chain Remote Config

Initialize the basic remote chain configuration for Ethereum Sepolia. Pool addresses must be empty at initialization and rate limits are not configured at this stage.

# Initialize basic cross-chain configuration (no rate limits, no pool addresses)
pnpm bs58 lockrelease-token-pool --env devnet --instruction init-chain-remote-config \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG \
  --remote-chain-selector $ETHEREUM_SEPOLIA_CHAIN_SELECTOR \
  --token-address $ETH_TOKEN_ADDRESS \
  --decimals "18"

Account Breakdown from the Transaction (Example):

AccountAddressDescription
#19n9UgFppJCanCjy6cd3tEudEemUf4bQRR8PgiXN7Rp4RPool State PDA (read-only - validation)
#25ZctnHiRZyrtDirqfXxpo4aH6eNT7YTJZ3HZh5WJWMiXChain Remote Config PDA (writable - created)
#3AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBSquad Vault (signer, writable - pool authority)
#411111111111111111111111111111111System Program (read-only - account creation)

What This Command Does: This initializes the cross-chain configuration for your Solana token to enable CCIP bridging to Ethereum Sepolia. This creates the foundational connection without rate limits or pool addresses.

Key Details:

  • Remote Chain: Ethereum Sepolia (chain selector: 16015286601757825753)
  • Token Mapping: Links to ERC20 token 0x44490fee906a4cfd5e71a46c3ca2147287b018e7
  • Decimal Precision: 18 decimals (standard EVM token format)
  • Basic Setup: No rate limits or pool addresses configured at this stage
  • Account Creation: Creates a new Chain Remote Config PDA for this cross-chain relationship

Transaction Flow:

  1. Authority Verification: Confirms Squad vault owns the pool state
  2. PDA Derivation: Calculates Chain Remote Config PDA using pool state + chain selector
  3. Account Creation: Creates new account with rent-exempt balance
  4. Basic Configuration: Stores minimal cross-chain parameters (no rate limits, no pool addresses)

Step 2B: Edit Chain Remote Config (Add Remote Pool Address)

After initializing the chain remote config, add the remote pool address to enable bidirectional cross-chain transfers. This uses edit_chain_remote_config to specify the Ethereum pool address.

# Configure chain connection and remote pool address
pnpm bs58 lockrelease-token-pool --env devnet --instruction edit-chain-remote-config \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG \
  --remote-chain-selector $ETHEREUM_SEPOLIA_CHAIN_SELECTOR \
  --pool-addresses "[\"$ETH_POOL_ADDRESS\"]" \
  --token-address $ETH_TOKEN_ADDRESS \
  --decimals "18"

Account Breakdown from the Transaction:

AccountAddressDescription
#19n9UgFppJCanCjy6cd3tEudEemUf4bQRR8PgiXN7Rp4RPool State PDA (read-only - authority validation)
#25ZctnHiRZyrtDirqfXxpo4aH6eNT7YTJZ3HZh5WJWMiXChain Remote Config PDA (writable - configuration updated)
#3AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBSquad Vault Authority (signer, writable - fee payer)
#411111111111111111111111111111111System Program (read-only - account operations)

Transaction Flow:

  1. Authority Verification: Confirms Squad vault owns the pool state
  2. PDA Access: Updates the existing Chain Remote Config PDA
  3. Pool Address Addition: Adds the remote pool to the the remote pool list for Ethereum Sepolia

Step 3: Configure Rate Limits (Optional)

This command configures inbound and outbound rate limiting for token transfers between your Solana token and Ethereum Sepolia. Rate limits act as "token buckets" that control the flow of tokens across chains.

# Configure rate limits for cross-chain token transfers
pnpm bs58 lockrelease-token-pool \
  --env devnet \
  --instruction set-chain-rate-limit \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG \
  --remote-chain-selector $ETHEREUM_SEPOLIA_CHAIN_SELECTOR \
  --inbound-enabled "true" \
  --inbound-capacity "20000000000" \
  --inbound-rate "100000000" \
  --outbound-enabled "true" \
  --outbound-capacity "18000000000" \
  --outbound-rate "100000000"

Transaction Accounts (3 total):

AccountAddressTypePurpose
#19n9UgFppJCanCjy6cd3tEudEemUf4bQRR8PgiXN7Rp4RRead-onlyPool State PDA - Main pool configuration account
#25ZctnHiRZyrtDirqfXxpo4aH6eNT7YTJZ3HZh5WJWMiXWritableChain Config PDA - Chain-specific config (stores rate limits)
#3AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBSigner, WritableAuthority - Your Squad vault that can modify rate limits

Step 4: Create Address Lookup Table

Create an Address Lookup Table (ALT) containing all accounts needed for CCIP router operations.

What This Command Does:

This command creates an Address Lookup Table (ALT) that stores frequently used accounts for CCIP operations. ALTs are a Solana optimization that reduces transaction size and costs by replacing full 32-byte addresses with small 1-byte indices.

# Generate address lookup table creation transaction
pnpm bs58 router --env devnet --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_SQUAD_VAULT_MULTISIG

Account Breakdown from the Transaction:

AccountAddressDescription
#12xNjGFuymdCu2KMqDtNTTVuhyFUYvXhbWz29Q1kAnPVKALT Account (writable - being created)
#2AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBALT Authority (signer - will own the table)
#3AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBFee Payer (signer, writable - pays creation costs)
#411111111111111111111111111111111System Program (read-only - handles account creation)

What This Command Does:

This command creates the ALT infrastructure needed for efficient CCIP operations by storing frequently used account addresses in a lookup table.

Transaction Flow:

  1. ALT Creation: Creates lookup table account 2xNjGFuymdCu2KMqDtNTTVuhyFUYvXhbWz29Q1kAnPVK with Squad Vault as authority
  2. Core Address Addition: Adds essential CCIP accounts (programs, PDAs) to the table
  3. Index Assignment: Each address gets a unique 1-byte index for future reference

ALT Contents (What Gets Stored): The created lookup table will contain indices for these accounts in this exact order:

IndexAccount AddressPurpose
02xNjGFuymdCu2KMqDtNTTVuhyFUYvXhbWz29Q1kAnPVKALT Address (Self-Reference)
16gmKNyGtrSgB4gAH9yQyzXSPnDw1P5We9abZLEcuqdLYToken Admin Registry PDA
28eqh8wppT9c5rw4ERqNCffvU6cNFJWff9WmkcYtmGiqCPool Program ID
39n9UgFppJCanCjy6cd3tEudEemUf4bQRR8PgiXN7Rp4RPool State PDA
4GQ4md7wUKmU35m4munp8giYg9osXCTaGVCPvNYzz8WnuPool Signer's Associated Token Account
55FBAzopzmZ1oo2aadR1PKfTZqK7viBYipjvDVMyEQxzkPool Signer PDA
6TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DAToken Program ID (SPL Token v1)
73nLW4zA3xdZmKY5SyhP2RRr7BDMKLuh4SQ9kFTDsx1LiToken Mint
884grjTssjNyuDsaxJGkpJB8t17Y9sQt1G81R5AQyZ1RrFee Quoter Token Config PDA
9DPyeT76APQQVQD7EJRVw8Y6gLYjGkC5CdbdHQTQkpoN2Router External Token Pools Signer PDA

Transaction Efficiency Impact:

  • Before ALT: Each account = 32 bytes (9 accounts = 288 bytes)
  • After ALT: Each account = 1 byte index (9 accounts = 9 bytes)
  • Savings: ~279 bytes per future CCIP transaction!

Set Variables:

Copy the ALT address from the transaction output and set the environment variable:

export SOL_ADDRESS_LOOKUP_TABLE="<YOUR_ALT_ADDRESS>"

Verify the variable is set:

echo "✅ Address Lookup Table: $SOL_ADDRESS_LOOKUP_TABLE"

Step 5: Register Pool with Router

Register your token pool with the CCIP Router using set_pool. This enables the router to route cross-chain transfers through your token pool.

What This Command Does:

  • Pool Registration: Connects your token pool to the CCIP Router for cross-chain operations
  • Lookup Table Integration: Uses the ALT created in the previous step for efficient account management
  • Router Configuration: Enables the router to call your pool's token operations
# Generate pool registration transaction
pnpm bs58 router --env devnet --instruction set-pool \
  --program-id $SOL_CCIP_ROUTER \
  --mint $SOL_TOKEN_MINT \
  --authority $SOL_SQUAD_VAULT_MULTISIG \
  --pool-lookup-table $SOL_ADDRESS_LOOKUP_TABLE \
  --writable-indexes "[3,4,7]"

Account Breakdown from the Transaction:

AccountAddressDescription
#13Yrg9E4ySAeRezgQY99NNarAmFLtixapga9MZb6y2dt3Router Config PDA (read-only - configuration reference)
#26gmKNyGtrSgB4gAH9yQyzXSPnDw1P5We9abZLEcuqdLYToken Admin Registry PDA (writable - pool registration)
#33nLW4zA3xdZmKY5SyhP2RRr7BDMKLuh4SQ9kFTDsx1LiToken Mint (read-only - token identification)
#42xNjGFuymdCu2KMqDtNTTVuhyFUYvXhbWz29Q1kAnPVKAddress Lookup Table (read-only - ALT registration)
#5AgPSS5TVRimXPx2bv7Cs2CnuD6pVGPZkrPxjnZ8tLtgBSquad Vault Authority (signer, writable - admin & fees)

What This Command Does:

This command registers your token pool with the CCIP Router. It configures the router to use your Address Lookup Table and specifies which accounts need write access during CCIP operations.

Key Configuration:

  • ALT Integration: Uses the lookup table C3a5U... you just created
  • Writable Indexes: [3,4,7] - Specific accounts from the ALTthat need write access during CCIP operations

Transaction Flow:

  1. Authority Verification: Confirms Squad Vault has CCIP token admin permissions
  2. Pool Registration: Updates Token Admin Registry with pool configuration
  3. ALT Association: Links your token with the specific lookup table
  4. Permission Setup: Defines which accounts can be modified during cross-chain operations

Writable Indexes Analysis: [3,4,7]

Based on the ALT order established earlier:

IndexALT AccountWhy Writable
3Pool State PDAGets updated during cross-chain operations
4Pool Token ATATokens get minted/burned during transfers
7Token MintSupply gets modified during mint/burn operations

Phase 3 Complete: Save all variables:

# Save all variables for testing phase
cat > ~/.all_vars << EOF
# Phase 1 - EVM
export ETH_TOKEN_ADDRESS="$ETH_TOKEN_ADDRESS"
export ETH_POOL_ADDRESS="$ETH_POOL_ADDRESS"

# Phase 2 - Solana

export SOL_CCIP_POOL_PROGRAM="$SOL_CCIP_POOL_PROGRAM"
export SOL_CCIP_ROUTER="$SOL_CCIP_ROUTER"
export SOL_SQUAD_VAULT_MULTISIG="$SOL_SQUAD_VAULT_MULTISIG"
export SOL_WALLET_ADDRESS="$SOL_WALLET_ADDRESS"
export SOL_TOKEN_MINT="$SOL_TOKEN_MINT"
export SOL_POOL_ADDRESS="$SOL_POOL_ADDRESS"
export SOL_POOL_SIGNER_PDA="$SOL_POOL_SIGNER_PDA"
export SOL_ADDRESS_LOOKUP_TABLE="$SOL_ADDRESS_LOOKUP_TABLE"

# Chain Selectors

export ETHEREUM_SEPOLIA_CHAIN_SELECTOR="$ETHEREUM_SEPOLIA_CHAIN_SELECTOR"
EOF

echo "=== Phase 3 Complete - Cross-Chain Configuration ==="
echo "✅ Address Lookup Table: $SOL_ADDRESS_LOOKUP_TABLE"
echo "✅ Cross-chain connection configured"
echo "✅ All variables saved to ~/.all_vars"

Phase 4: EVM Cross-Chain Setup

Configure the Ethereum pool to recognize the Solana chain and set production rate limits.

Step 1: Load Phase 3 Variables

Ensure you're in Terminal 2 (Hardhat) and load all the variables from Phase 3.

# Verify you are in the Hardhat directory (Terminal 2)
# Should be: /.../smart-contract-examples/ccip/cct/hardhat
pwd

# Load all environment variables from Phase 3
source ~/.all_vars

# Verify key variables are loaded
echo "✅ ETH Token: $ETH_TOKEN_ADDRESS"
echo "✅ ETH Pool: $ETH_POOL_ADDRESS"
echo "✅ SOL Token: $SOL_TOKEN_MINT"
echo "✅ SOL Pool: $SOL_POOL_ADDRESS"
echo "✅ SOL ALT: $SOL_ADDRESS_LOOKUP_TABLE"

Step 2: Configure Cross-Chain Settings

Configure the Ethereum pool to recognize the Solana chain and set production rate limits using applyChainUpdates.

# Configure cross-chain settings WITH rate limits
npx hardhat applyChainUpdates \
 --pooladdress $ETH_POOL_ADDRESS \
 --remotechain solanaDevnet \
 --remotepooladdresses $SOL_POOL_ADDRESS \
  --remotetokenaddress $SOL_TOKEN_MINT \
  --outboundratelimitenabled true \
  --outboundratelimitcapacity 18000000000000000000 \
  --outboundratelimitrate 100000000000000000 \
  --inboundratelimitenabled true \
  --inboundratelimitcapacity 20000000000000000000 \
  --inboundratelimitrate 100000000000000000 \
 --network ethereumSepolia

Phase 5: Pre-Transfer Setup, Validation, and Testing

Step 1: Load Configuration

# Check current directory (Terminal 1)
pwd
# Should show: .../ccip-solana-bs58-generator

# Load all environment variables
source ~/.all_vars

# Verify critical variables
echo "SOL_TOKEN_MINT: $SOL_TOKEN_MINT"
echo "ETH_TOKEN_ADDRESS: $ETH_TOKEN_ADDRESS"
echo "SOL_POOL_SIGNER_PDA: $SOL_POOL_SIGNER_PDA"
echo "SOL_ADDRESS_LOOKUP_TABLE: $SOL_ADDRESS_LOOKUP_TABLE"
echo "SOL_WALLET_ADDRESS: $SOL_WALLET_ADDRESS"

Step 2: Pre-Transfer Setup

Before cross-chain transfers (when available), complete token delegation so CCIP can move tokens from your wallet.

Check Token Balance

spl-token balance $SOL_TOKEN_MINT

Create Pool Associated Token Account

If not created during pool setup, create the pool's ATA:

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

Delegate Token Authority

# 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_WALLET_ADDRESS \
  --mint $SOL_TOKEN_MINT \
  --delegate $SOL_CCIP_FEE_BILLING_SIGNER \
  --amount 18446744073709551615

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_WALLET_ADDRESS --addresses-only)

echo "SOL_TOKEN_ACCOUNT=$SOL_TOKEN_ACCOUNT"

Display token account and delegation status:

spl-token display $SOL_TOKEN_ACCOUNT

Step 3: Verify Solana Pool and Chain Config

# Verify pool state
pnpm bs58 --env devnet lockrelease-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 lockrelease-token-pool \
  --instruction get-chain-config \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --remote-chain-selector $ETHEREUM_SEPOLIA_CHAIN_SELECTOR

# Verify token configuration
spl-token display $SOL_TOKEN_MINT

Step 4: 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_ADDRESS 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 ~/.all_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 ~/.all_vars

ccip-cli send \
  -s ethereum-testnet-sepolia \
  -r $ETH_CCIP_ROUTER \
  -d solana-devnet \
  --to $SOL_WALLET_ADDRESS \
  -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

Optional: Test Rate Limit Enforcement

If you configured rate limits in Phase 3, attempt a transfer that exceeds capacity (25 tokens vs 20-token limit):

# Terminal 1

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

Expect RLMaxCapacityExceeded (Error 6015) when limits are enabled.

Optional: Verify Rate Limit Configuration

If you configured rate limits in Phase 3, confirm they appear in your chain configuration:

# Terminal 1 — inspect Solana outbound/inbound rate limits
pnpm bs58 --env devnet lockrelease-token-pool \
  --instruction get-chain-config \
  --program-id $SOL_CCIP_POOL_PROGRAM \
  --mint $SOL_TOKEN_MINT \
  --remote-chain-selector $ETHEREUM_SEPOLIA_CHAIN_SELECTOR
# Terminal 2 — inspect EVM rate limit settings
npx hardhat getPoolConfig \
  --pooladdress $ETH_POOL_ADDRESS \
  --network ethereumSepolia

Congratulations! Your production Lock/Release cross-chain token infrastructure with Squads governance is fully configured on both chains.

Get the latest Chainlink content straight to your inbox.