Overview
CCTP V2 contracts use a CREATE2-based deployment strategy to ensure deterministic addresses across chains. The deployment process involves:
Create2Factory : Deploy factory for deterministic deployments
Implementation Contracts : Deploy logic contracts
Proxy Contracts : Deploy proxies pointing to implementations
Setup Remote Resources : Configure cross-chain connections
Key Rotation (Mainnet only): Rotate administrative keys
AddressUtilsExternal : Deploy helper library
All deployment scripts use Forge Scripts and are located in scripts/v2/ directory.
Prerequisites
Foundry CLI installed (forge 0.2.0)
Git submodules initialized: git submodule update --init --recursive
Node.js and Yarn installed: yarn install
Sufficient funds in deployer accounts for gas
Deployment Steps
Step 1: Deploy Create2Factory
The Create2Factory enables deterministic address deployment across chains.
Configure Environment
Add to your .env file: CREATE2_FACTORY_OWNER =< address >
Parameters :
CREATE2_FACTORY_OWNER: Address that will own the Create2Factory
Simulate Deployment
Perform a dry run: make simulate-deploy-create2-factory \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R > \
CREATE2_FACTORY_OWNER_KEY= < PRIVATE_KE Y >
SENDER should match the address derived from CREATE2_FACTORY_OWNER_KEY.
Deploy Factory
Deploy the Create2Factory: make deploy-create2-factory \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R > \
CREATE2_FACTORY_OWNER_KEY= < PRIVATE_KE Y >
Record the deployed factory address for subsequent steps.
Reference : scripts/v2/DeployCreate2Factory.s.sol
Step 2: Deploy V2 Implementation Contracts
Deploy the logic contracts that proxies will delegate to.
Configure Environment
Add to your .env file: CREATE2_FACTORY_CONTRACT_ADDRESS =< address >
TOKEN_MINTER_V2_OWNER_KEY =< private_key >
TOKEN_CONTROLLER_ADDRESS =< address >
DOMAIN =< uint32 >
MESSAGE_BODY_VERSION =< uint32 >
VERSION =< uint32 >
Parameters :
CREATE2_FACTORY_CONTRACT_ADDRESS: Address from Step 1
TOKEN_MINTER_V2_OWNER_KEY: Private key for TokenMinter owner
TOKEN_CONTROLLER_ADDRESS: Token controller address
DOMAIN: Local domain ID (e.g., 0 for Ethereum, 1 for Avalanche)
MESSAGE_BODY_VERSION: Message format version (typically 1)
VERSION: Protocol version (typically 2)
Simulate Deployment
make simulate-deploy-implementations-v2 \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R > \
CREATE2_FACTORY_OWNER_KEY= < CREATE2_FACTORY_OWNER_KE Y >
Deploy Implementations
make deploy-implementations-v2 \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R > \
CREATE2_FACTORY_OWNER_KEY= < CREATE2_FACTORY_OWNER_KE Y >
This deploys:
MessageTransmitterV2 implementation
TokenMessengerV2 implementation
TokenMinterV2 implementation
Record all implementation addresses.
Reference : scripts/v2/DeployImplementationsV2.s.sol
Step 3: Deploy V2 Proxies
Deploy proxy contracts with full initialization.
Configure Environment
Replace environment variables in .env: # Token Configuration
USDC_CONTRACT_ADDRESS =< address >
TOKEN_CONTROLLER_ADDRESS =< address >
# Remote Configuration (1:1:1 correspondence)
REMOTE_DOMAINS =< uint32[ ] > # e.g., "1,2,3"
REMOTE_USDC_CONTRACT_ADDRESSES =< address[ ] > # e.g., "0x123...,0x456...,0x789..."
REMOTE_TOKEN_MESSENGER_V2_ADDRESSES =< bytes32[ ] >
# Factory
CREATE2_FACTORY_CONTRACT_ADDRESS =< address >
# MessageTransmitterV2 Roles
MESSAGE_TRANSMITTER_V2_OWNER_ADDRESS =< address >
MESSAGE_TRANSMITTER_V2_PAUSER_ADDRESS =< address >
MESSAGE_TRANSMITTER_V2_RESCUER_ADDRESS =< address >
MESSAGE_TRANSMITTER_V2_ATTESTER_MANAGER_ADDRESS =< address >
MESSAGE_TRANSMITTER_V2_ATTESTER_1_ADDRESS =< address >
MESSAGE_TRANSMITTER_V2_ATTESTER_2_ADDRESS =< address >
MESSAGE_TRANSMITTER_V2_PROXY_ADMIN_ADDRESS =< address >
# TokenMinterV2 Roles
TOKEN_MINTER_V2_PAUSER_ADDRESS =< address >
TOKEN_MINTER_V2_RESCUER_ADDRESS =< address >
# TokenMessengerV2 Roles (NEW in V2)
TOKEN_MESSENGER_V2_OWNER_ADDRESS =< address >
TOKEN_MESSENGER_V2_RESCUER_ADDRESS =< address >
TOKEN_MESSENGER_V2_FEE_RECIPIENT_ADDRESS =< address >
TOKEN_MESSENGER_V2_DENYLISTER_ADDRESS =< address >
TOKEN_MESSENGER_V2_PROXY_ADMIN_ADDRESS =< address >
TOKEN_MESSENGER_V2_MIN_FEE_CONTROLLER_ADDRESS =< address >
TOKEN_MESSENGER_V2_MIN_FEE =< uint256 > # e.g., 5000 for 0.05%
# Domain Configuration
DOMAIN =< uint32 >
BURN_LIMIT_PER_MESSAGE =< uint256 >
# Deployer Keys
TOKEN_CONTROLLER_KEY =< private_key >
TOKEN_MINTER_V2_OWNER_KEY =< private_key >
Important : REMOTE_DOMAINS, REMOTE_USDC_CONTRACT_ADDRESSES, and REMOTE_TOKEN_MESSENGER_V2_ADDRESSES must correspond 1:1:1 in order.
New V2 Role Addresses :
TOKEN_MESSENGER_V2_FEE_RECIPIENT_ADDRESS: Receives collected fees
TOKEN_MESSENGER_V2_DENYLISTER_ADDRESS: Manages protocol denylist
TOKEN_MESSENGER_V2_MIN_FEE_CONTROLLER_ADDRESS: Sets minimum fees
TOKEN_MESSENGER_V2_MIN_FEE: Initial minimum fee in 1/1000 basis points
Simulate Deployment
make simulate-deploy-proxies-v2 \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R > \
CREATE2_FACTORY_OWNER_KEY= < CREATE2_FACTORY_OWNER_KE Y >
Deploy Proxies
make deploy-proxies-v2 \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R > \
CREATE2_FACTORY_OWNER_KEY= < CREATE2_FACTORY_OWNER_KE Y >
This deploys and initializes:
MessageTransmitterV2 proxy
TokenMessengerV2 proxy
TokenMinterV2 proxy
All contracts are fully initialized with roles and remote configurations.
Reference : scripts/v2/DeployProxiesV2.s.sol
Step 4: Setup Additional Remote Resources
Only perform steps 4-7 for additional remote resources NOT already configured in Step 3.
If you need to add more remote chains after initial deployment:
Configure Environment
Update .env with additional remote configuration: TOKEN_MESSENGER_V2_OWNER_KEY =< private_key >
TOKEN_CONTROLLER_KEY =< private_key >
TOKEN_MESSENGER_V2_CONTRACT_ADDRESS =< address >
TOKEN_MINTER_V2_CONTRACT_ADDRESS =< address >
USDC_CONTRACT_ADDRESS =< address >
REMOTE_USDC_CONTRACT_ADDRESS =< address >
REMOTE_DOMAIN =< uint32 >
Add one remote resource at a time , repeating these steps for each remote chain.
Simulate Setup
make simulate-setup-remote-resources-v2 \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R >
Setup Remote Resources
make setup-remote-resources-v2 \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R >
This:
Links remote TokenMessenger
Configures remote USDC token pair
Repeat for Each Remote Chain
Repeat steps 1-3 for each additional remote chain you want to support.
Reference : scripts/v2/SetupRemoteResourcesV2.s.sol
Step 5: Key Rotation (Mainnet Only)
Mainnet Only : This step should only be performed on mainnet deployments. Skip for testnets.
Configure Environment
Add to your .env file: # Current Contract Addresses
MESSAGE_TRANSMITTER_V2_CONTRACT_ADDRESS =< address >
TOKEN_MESSENGER_V2_CONTRACT_ADDRESS =< address >
TOKEN_MINTER_V2_CONTRACT_ADDRESS =< address >
# Current Owner Keys
MESSAGE_TRANSMITTER_V2_OWNER_KEY =< private_key >
TOKEN_MESSENGER_V2_OWNER_KEY =< private_key >
TOKEN_MINTER_V2_OWNER_KEY =< private_key >
# New Owner Addresses
MESSAGE_TRANSMITTER_V2_NEW_OWNER_ADDRESS =< address >
TOKEN_MESSENGER_V2_NEW_OWNER_ADDRESS =< address >
TOKEN_MINTER_V2_NEW_OWNER_ADDRESS =< address >
NEW_TOKEN_CONTROLLER_ADDRESS =< address >
Simulate Key Rotation
make simulate-rotate-keys-v2 \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R >
Rotate Keys
make rotate-keys-v2 \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R >
This transfers ownership of all V2 contracts to new owner addresses.
Reference : scripts/v2/RotateKeysV2.s.sol
Step 6: Deploy AddressUtilsExternal
Deploy the helper library for easy integration.
Configure Environment
Ensure CREATE2_FACTORY_CONTRACT_ADDRESS is set in .env: CREATE2_FACTORY_CONTRACT_ADDRESS =< address >
Simulate Deployment
make simulate-deploy-address-utils-external \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R > \
CREATE2_FACTORY_OWNER_KEY= < CREATE2_FACTORY_OWNER_KE Y >
Deploy AddressUtilsExternal
make deploy-address-utils-external \
RPC_URL= < RPC_UR L > \
SENDER= < SENDE R > \
CREATE2_FACTORY_OWNER_KEY= < CREATE2_FACTORY_OWNER_KE Y >
This deploys AddressUtilsExternal to a deterministic address for easy integration.
Reference : scripts/v2/DeployAddressUtilsExternal.s.sol
Predicting CREATE2 Addresses
You can predict deployment addresses before deploying:
# Predict MessageTransmitterV2 Implementation
forge script scripts/v2/PredictCreate2Deployments.s.sol \
--sig "messageTransmitterV2Impl(address,uint32,uint32)" \
< create2FactoryAddres s > \
< domai n > \
< messageVersio n >
# Predict TokenMessengerV2 Implementation
forge script scripts/v2/PredictCreate2Deployments.s.sol \
--sig "tokenMessengerV2Impl(address,uint32)" \
< create2FactoryAddres s > \
< messageBodyVersio n >
# Predict MessageTransmitterV2 Proxy
forge script scripts/v2/PredictCreate2Deployments.s.sol \
--sig "messageTransmitterV2Proxy(address)" \
< create2FactoryAddres s >
# Predict TokenMessengerV2 Proxy
forge script scripts/v2/PredictCreate2Deployments.s.sol \
--sig "tokenMessengerV2Proxy(address)" \
< create2FactoryAddres s >
# Predict TokenMinterV2
forge script scripts/v2/PredictCreate2Deployments.s.sol \
--sig "tokenMinterV2(address)" \
< create2FactoryAddres s >
# Predict AddressUtilsExternal
forge script scripts/v2/PredictCreate2Deployments.s.sol \
--sig "addressUtilsExternal(address)" \
< create2FactoryAddres s >
Reference : scripts/v2/PredictCreate2Deployments.s.sol
Role Configuration Summary
MessageTransmitterV2 Roles
Role Responsibilities owner General administration, role updates pauser Emergency pause functionality rescuer Emergency token recovery attesterManager Manages attester set attester Signs attestations (multiple attesters) proxyAdmin Proxy upgrade authority
TokenMessengerV2 Roles
Role Responsibilities New in V2 owner General administration rescuer Emergency token recovery feeRecipient Receives collected fees ✅ denylister Manages protocol denylist ✅ minFeeController Sets minimum fee requirements ✅ proxyAdmin Proxy upgrade authority
TokenMinterV2 Roles
Role Responsibilities owner General administration pauser Emergency pause functionality rescuer Emergency token recovery tokenController Manages token pairs and burn limits
Environment Variables Checklist
Step 2: Implementation Contracts
✓ CREATE2_FACTORY_CONTRACT_ADDRESS
✓ TOKEN_MINTER_V2_OWNER_KEY
✓ TOKEN_CONTROLLER_ADDRESS
✓ DOMAIN
✓ MESSAGE_BODY_VERSION
✓ VERSION
# Token & Factory
✓ USDC_CONTRACT_ADDRESS
✓ TOKEN_CONTROLLER_ADDRESS
✓ CREATE2_FACTORY_CONTRACT_ADDRESS
# Remote Configuration
✓ REMOTE_DOMAINS
✓ REMOTE_USDC_CONTRACT_ADDRESSES
✓ REMOTE_TOKEN_MESSENGER_V2_ADDRESSES
# MessageTransmitter Roles
✓ MESSAGE_TRANSMITTER_V2_OWNER_ADDRESS
✓ MESSAGE_TRANSMITTER_V2_PAUSER_ADDRESS
✓ MESSAGE_TRANSMITTER_V2_RESCUER_ADDRESS
✓ MESSAGE_TRANSMITTER_V2_ATTESTER_MANAGER_ADDRESS
✓ MESSAGE_TRANSMITTER_V2_ATTESTER_1_ADDRESS
✓ MESSAGE_TRANSMITTER_V2_ATTESTER_2_ADDRESS
✓ MESSAGE_TRANSMITTER_V2_PROXY_ADMIN_ADDRESS
# TokenMinter Roles
✓ TOKEN_MINTER_V2_PAUSER_ADDRESS
✓ TOKEN_MINTER_V2_RESCUER_ADDRESS
# TokenMessenger Roles (NEW)
✓ TOKEN_MESSENGER_V2_OWNER_ADDRESS
✓ TOKEN_MESSENGER_V2_RESCUER_ADDRESS
✓ TOKEN_MESSENGER_V2_FEE_RECIPIENT_ADDRESS
✓ TOKEN_MESSENGER_V2_DENYLISTER_ADDRESS
✓ TOKEN_MESSENGER_V2_PROXY_ADMIN_ADDRESS
✓ TOKEN_MESSENGER_V2_MIN_FEE_CONTROLLER_ADDRESS
✓ TOKEN_MESSENGER_V2_MIN_FEE
# Domain
✓ DOMAIN
✓ BURN_LIMIT_PER_MESSAGE
# Keys
✓ TOKEN_CONTROLLER_KEY
✓ TOKEN_MINTER_V2_OWNER_KEY
Step 4: Additional Remote Resources
✓ TOKEN_MESSENGER_V2_OWNER_KEY
✓ TOKEN_CONTROLLER_KEY
✓ TOKEN_MESSENGER_V2_CONTRACT_ADDRESS
✓ TOKEN_MINTER_V2_CONTRACT_ADDRESS
✓ USDC_CONTRACT_ADDRESS
✓ REMOTE_USDC_CONTRACT_ADDRESS
✓ REMOTE_DOMAIN
Step 5: Key Rotation (Mainnet)
# Current
✓ MESSAGE_TRANSMITTER_V2_CONTRACT_ADDRESS
✓ TOKEN_MESSENGER_V2_CONTRACT_ADDRESS
✓ TOKEN_MINTER_V2_CONTRACT_ADDRESS
✓ MESSAGE_TRANSMITTER_V2_OWNER_KEY
✓ TOKEN_MESSENGER_V2_OWNER_KEY
✓ TOKEN_MINTER_V2_OWNER_KEY
# New
✓ MESSAGE_TRANSMITTER_V2_NEW_OWNER_ADDRESS
✓ TOKEN_MESSENGER_V2_NEW_OWNER_ADDRESS
✓ TOKEN_MINTER_V2_NEW_OWNER_ADDRESS
✓ NEW_TOKEN_CONTROLLER_ADDRESS
Step 6: AddressUtilsExternal
✓ CREATE2_FACTORY_CONTRACT_ADDRESS
Post-Deployment Verification
After deployment, verify:
Contract Addresses
Record all deployed addresses:
Create2Factory
MessageTransmitterV2 (implementation & proxy)
TokenMessengerV2 (implementation & proxy)
TokenMinterV2 (implementation & proxy)
AddressUtilsExternal
Role Configuration
Verify all roles are set correctly: # Check owner
cast call $TOKEN_MESSENGER_V2 "owner()" --rpc-url $RPC_URL
# Check fee recipient
cast call $TOKEN_MESSENGER_V2 "feeRecipient()" --rpc-url $RPC_URL
# Check denylister
cast call $TOKEN_MESSENGER_V2 "denylister()" --rpc-url $RPC_URL
Remote Configuration
Verify remote token messengers are linked: # Check remote token messenger
cast call $TOKEN_MESSENGER_V2 \
"remoteTokenMessengers(uint32)" \
$REMOTE_DOMAIN \
--rpc-url $RPC_URL
Fee Configuration
Verify fee settings: # Check minimum fee
cast call $TOKEN_MESSENGER_V2 "minFee()" --rpc-url $RPC_URL
# Check min fee controller
cast call $TOKEN_MESSENGER_V2 "minFeeController()" --rpc-url $RPC_URL
Troubleshooting
Simulation Fails
Verify all environment variables are set correctly
Check that SENDER has sufficient funds for gas
Ensure RPC_URL is accessible and correct
Verify private keys match expected addresses
CREATE2 Address Mismatch
Ensure Create2Factory address is consistent across chains
Verify constructor parameters match exactly
Check that salt values are identical
Initialization Fails
Verify all role addresses are non-zero
Check that remote arrays have equal length
Ensure owner address is set correctly
Remote Resources Not Linking
Verify remote domain IDs are correct
Check that remote TokenMessenger addresses are bytes32 format
Ensure TokenMinter has correct token pairs configured
Next Steps
Test Deployment Test your V2 deployment
Integration Guide Integrate V2 into your application
Migration Guide Migrate from V1 to V2
API Reference Complete V2 API documentation