Example 07: CCT Lock and Release
This example covers the full LockRelease CCT flow on Sepolia -> Amoy:
- Deploy token + lock box + LockRelease pool on both chains.
- Configure pools to trust each other.
- Fund destination lock box liquidity.
- Send a token transfer across the lane.
- Verify lock/release behavior.
Scripts used:
script/examples/Example07.s.sol:DeployCCTLockReleaseTokenAndPoolscript/examples/Example07.s.sol:Example07script/examples/Example07.s.sol:FundCCTLockBoxLiquidityscript/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:TestTokensymbol:TESTdecimals:18preMint:1_000_000 * 1e18maxSupply:100_000_000 * 1e18
Admin registration uses registerAdminViaGetCCIPAdmin on the token (see Example 06).
The deployment also uses:
- LockRelease pool
ERC20LockBoxbound 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 = 200For this chapter, use
FOUNDRY_PROFILE=cctin 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 (1e18is 1 token for 18-decimal token).gasLimitis set to0in this command because this is token-only transfer to an EOA receiver.- This sender uses
ExtraArgsV3withblockConfirmations = 0(default finality). <FEE_TOKEN_ADDRESS>:- native fee:
0x0000000000000000000000000000000000000000 - LINK fee: LINK token address on Sepolia
- native fee:
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