Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Example 07: CCT Lock and Release

This example covers the full LockRelease CCT flow on Sepolia -> Amoy:

  1. Deploy token + lock box + LockRelease pool on both chains.
  2. Configure pools to trust each other.
  3. Fund destination lock box liquidity.
  4. Send a token transfer across the lane.
  5. Verify lock/release behavior.

Scripts used:

  • script/examples/Example07.s.sol:DeployCCTLockReleaseTokenAndPool
  • script/examples/Example07.s.sol:Example07
  • script/examples/Example07.s.sol:FundCCTLockBoxLiquidity
  • script/examples/Example06.s.sol:SendCCTTokenWithExtraArgsV3DefaultFinality (reused for the final transfer step)

Before You Start

Important: Keystore first

Use a local keystore account for script execution:

cast wallet import myAccount --interactive
Enter private key:
Enter password:
`myAccount` keystore was saved successfully. Address: <YOUR_EOA_ADDRESS_SHOULD_APPEAR_HERE>

This chapter assumes --account myAccount.

Script Defaults

DeployCCTLockReleaseTokenAndPool deploys a CrossChainToken (same defaults as Example 06) for the underlying ERC-20, with:

  • name: TestToken
  • symbol: TEST
  • decimals: 18
  • preMint: 1_000_000 * 1e18
  • maxSupply: 100_000_000 * 1e18

Admin registration uses registerAdminViaGetCCIPAdmin on the token (see Example 06).

The deployment also uses:

  • LockRelease pool
  • ERC20LockBox bound to the local token
  • disabled rate limiter config in chain updates

Important: Optimizer is required for CCT pool deployment

This repo includes a dedicated CCT profile in foundry.toml:

[profile.cct]
optimizer = true
optimizer_runs = 200

For this chapter, use FOUNDRY_PROFILE=cct in all commands.

Step 1: Deploy Token + Lock Box + Pool on Sepolia

FOUNDRY_PROFILE=cct forge script script/examples/Example07.s.sol:DeployCCTLockReleaseTokenAndPool \
  --rpc-url ethereumSepolia \
  --account myAccount \
  --broadcast \
  --sig "run(address,address,address,address)" \
  <SEPOLIA_TOKEN_ADMIN_REGISTRY> \
  <SEPOLIA_REGISTRY_MODULE_OWNER_CUSTOM> \
  <SEPOLIA_ARM_PROXY> \
  <SEPOLIA_ROUTER>

Save from logs:

  • <SEPOLIA_TOKEN_ADDRESS>
  • <SEPOLIA_LOCKBOX_ADDRESS>
  • <SEPOLIA_POOL_ADDRESS>

Step 2: Deploy Token + Lock Box + Pool on Amoy

FOUNDRY_PROFILE=cct forge script script/examples/Example07.s.sol:DeployCCTLockReleaseTokenAndPool \
  --rpc-url polygonAmoy \
  --account myAccount \
  --broadcast \
  --sig "run(address,address,address,address)" \
  <AMOY_TOKEN_ADMIN_REGISTRY> \
  <AMOY_REGISTRY_MODULE_OWNER_CUSTOM> \
  <AMOY_ARM_PROXY> \
  <AMOY_ROUTER>

Save from logs:

  • <AMOY_TOKEN_ADDRESS>
  • <AMOY_LOCKBOX_ADDRESS>
  • <AMOY_POOL_ADDRESS>

Step 3: Configure Sepolia Pool With Amoy Remote

FOUNDRY_PROFILE=cct forge script script/examples/Example07.s.sol:Example07 \
  --rpc-url ethereumSepolia \
  --account myAccount \
  --broadcast \
  --sig "run(address,uint64,address,address)" \
  <SEPOLIA_POOL_ADDRESS> \
  <AMOY_CHAIN_SELECTOR> \
  <AMOY_TOKEN_ADDRESS> \
  <AMOY_POOL_ADDRESS>

Step 4: Configure Amoy Pool With Sepolia Remote

FOUNDRY_PROFILE=cct forge script script/examples/Example07.s.sol:Example07 \
  --rpc-url polygonAmoy \
  --account myAccount \
  --broadcast \
  --sig "run(address,uint64,address,address)" \
  <AMOY_POOL_ADDRESS> \
  <SEPOLIA_CHAIN_SELECTOR> \
  <SEPOLIA_TOKEN_ADDRESS> \
  <SEPOLIA_POOL_ADDRESS>

Step 5: Fund Destination Lock Box Liquidity

For Sepolia -> Amoy transfers, fund the Amoy lock box first:

FOUNDRY_PROFILE=cct forge script script/examples/Example07.s.sol:FundCCTLockBoxLiquidity \
  --rpc-url polygonAmoy \
  --account myAccount \
  --broadcast \
  --sig "run(address,address,uint256)" \
  <AMOY_TOKEN_ADDRESS> \
  <AMOY_LOCKBOX_ADDRESS> \
  <LIQUIDITY_AMOUNT>

If you also want Amoy -> Sepolia transfers, fund Sepolia lock box too with the same script.

Step 6: Send LockRelease Transfer (Reuse Example06 CCT Sender)

FOUNDRY_PROFILE=cct forge script script/examples/Example06.s.sol:SendCCTTokenWithExtraArgsV3DefaultFinality \
  --rpc-url ethereumSepolia \
  --account myAccount \
  --broadcast \
  --sig "run(address,uint64,address,address,uint256,uint32,address)" \
  <SEPOLIA_ROUTER> \
  <AMOY_CHAIN_SELECTOR> \
  <RECEIVER_ON_AMOY> \
  <SEPOLIA_TOKEN_ADDRESS> \
  <AMOUNT> \
  0 \
  <FEE_TOKEN_ADDRESS>

Parameter notes:

  • <AMOUNT> uses token decimals (1e18 is 1 token for 18-decimal token).
  • gasLimit is set to 0 in this command because this is token-only transfer to an EOA receiver.
  • This sender uses ExtraArgsV3 with blockConfirmations = 0 (default finality).
  • <FEE_TOKEN_ADDRESS>:
    • native fee: 0x0000000000000000000000000000000000000000
    • LINK fee: LINK token address on Sepolia

Step 7: Verify LockRelease Behavior

Verify token->pool registration:

cast call <SEPOLIA_TOKEN_ADMIN_REGISTRY> "getPool(address)(address)" <SEPOLIA_TOKEN_ADDRESS> --rpc-url ethereumSepolia
cast call <AMOY_TOKEN_ADMIN_REGISTRY> "getPool(address)(address)" <AMOY_TOKEN_ADDRESS> --rpc-url polygonAmoy

Verify remote lane mapping:

cast call <SEPOLIA_POOL_ADDRESS> "getRemoteToken(uint64)(bytes)" <AMOY_CHAIN_SELECTOR> --rpc-url ethereumSepolia
cast call <SEPOLIA_POOL_ADDRESS> "getRemotePools(uint64)(bytes[])" <AMOY_CHAIN_SELECTOR> --rpc-url ethereumSepolia
cast call <AMOY_POOL_ADDRESS> "getRemoteToken(uint64)(bytes)" <SEPOLIA_CHAIN_SELECTOR> --rpc-url polygonAmoy
cast call <AMOY_POOL_ADDRESS> "getRemotePools(uint64)(bytes[])" <SEPOLIA_CHAIN_SELECTOR> --rpc-url polygonAmoy

Verify lock box balances and receiver balance after transfer finalizes:

cast call <SEPOLIA_TOKEN_ADDRESS> "balanceOf(address)(uint256)" <SEPOLIA_LOCKBOX_ADDRESS> --rpc-url ethereumSepolia
cast call <AMOY_TOKEN_ADDRESS> "balanceOf(address)(uint256)" <AMOY_LOCKBOX_ADDRESS> --rpc-url polygonAmoy
cast call <AMOY_TOKEN_ADDRESS> "balanceOf(address)(uint256)" <RECEIVER_ON_AMOY> --rpc-url polygonAmoy

Monitor message status with the message ID on:

  • https://ccip.chain.link