CCIP Relay Service
Custom relay mechanism for delivering CCIP messages from Chain 138 to Ethereum Mainnet.
Architecture
The relay system consists of:
- CCIPRelayRouter (on Ethereum Mainnet): Receives relayed messages and forwards them to bridge contracts
- CCIPRelayBridge (on Ethereum Mainnet): Receives messages from relay router and transfers tokens to recipients
- Relay Service (off-chain): Monitors MessageSent events on Chain 138 and relays messages to Ethereum Mainnet
Current Deployment
Deployed Contracts (Ethereum Mainnet)
- Relay Router:
0xAd9A228CcEB4cbB612cD165FFB72fE090ff10Afb - Relay Bridge:
0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939 - WETH9:
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Source Chain (Chain 138)
- CCIP Router (emits MessageSent):
0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817 - WETH9 Bridge (LINK fee):
0xcacfd227A040002e49e2e01626363071324f820a - WETH9 Bridge (native ETH fee):
0x63cbeE010D64ab7F1760ad84482D6cC380435ab5 - WETH9:
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Both bridges have mainnet destination set to CCIPRelayBridge (0xF9A32F37...) so the relay service delivers to mainnet.
Deployment
1. Deploy Relay Contracts on Ethereum Mainnet
cd /home/intlc/projects/proxmox/smom-dbis-138
# Set environment variables
export PRIVATE_KEY=0x...
export RPC_URL_MAINNET=https://mainnet.infura.io/v3/YOUR_PROJECT_ID # or Alchemy; avoid public RPC 429
export WETH9_MAINNET=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
export RELAYER_ADDRESS=0x... # Address that will run relay service
# Deploy
forge script script/DeployCCIPRelay.s.sol:DeployCCIPRelay \
--rpc-url $RPC_URL_MAINNET \
--broadcast \
--legacy \
--via-ir
2. Configure Environment
The service uses environment variables. Create .env file in services/relay/:
# Source Chain (Chain 138)
RPC_URL_138=https://rpc-core.d-bis.org
CCIP_ROUTER_CHAIN138=0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817
CCIPWETH9_BRIDGE_CHAIN138=0xcacfd227A040002e49e2e01626363071324f820a
# Destination Chain (Ethereum Mainnet) — use Infura/Alchemy to avoid 429 rate limits
RPC_URL_MAINNET=https://mainnet.infura.io/v3/YOUR_PROJECT_ID
# Or set ETHEREUM_MAINNET_RPC in smom-dbis-138/.env; relay uses RPC_URL_MAINNET first, then ETHEREUM_MAINNET_RPC
CCIP_RELAY_ROUTER_MAINNET=0xAd9A228CcEB4cbB612cD165FFB72fE090ff10Afb
CCIP_RELAY_BRIDGE_MAINNET=0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939
# Relayer Configuration
PRIVATE_KEY=0x... # Private key for relayer (needs ETH on mainnet for gas)
RELAYER_PRIVATE_KEY=${PRIVATE_KEY} # Alternative name
# Monitoring Configuration
START_BLOCK=latest # or specific block number (e.g., 242500)
POLL_INTERVAL=5000 # milliseconds
CONFIRMATION_BLOCKS=1
# Retry Configuration
MAX_RETRIES=3
RETRY_DELAY=5000 # milliseconds
# Chain Selectors
SOURCE_CHAIN_ID=138
DESTINATION_CHAIN_SELECTOR=5009297550715157269
Important: If PRIVATE_KEY contains variable expansion (e.g., ${PRIVATE_KEY}), create a .env.local file with the expanded value to avoid issues with dotenv.
3. Install Dependencies
cd services/relay
npm install
4. Start Relay Service
# Start service
npm start
# Or use the wrapper script (recommended)
./start-relay.sh
How It Works
- Event Monitoring: The relay service monitors
MessageSentevents from the CCIP Router on Chain 138 - Message Queue: Detected messages are added to a queue for processing
- Token Address Mapping: Source chain token addresses are mapped to destination chain addresses
- Message Relay: For each message:
- Constructs the
Any2EVMMessageformat with mapped token addresses - Calls
relayMessageon the Relay Router contract on Ethereum Mainnet - Relay Router forwards to Relay Bridge
- Relay Bridge calls
ccipReceiveand transfers tokens to recipient
- Constructs the
Configuration
Key configuration options in .env:
RPC_URL_138: RPC endpoint for Chain 138 (default:http://192.168.11.250:8545)RPC_URL_MAINNET: RPC endpoint for Ethereum Mainnet (relay also readsETHEREUM_MAINNET_RPCfrom smom-dbis-138/.env). Prefer Infurahttps://mainnet.infura.io/v3/<PROJECT_ID>or Alchemy to avoid public RPC rate limits (429). Default fallback:https://ethereum.publicnode.com.PRIVATE_KEYorRELAYER_PRIVATE_KEY: Private key for relayer (needs ETH on mainnet for gas)START_BLOCK: Block number to start monitoring from (default:latestor specific block number)POLL_INTERVAL: How often to poll for new events in milliseconds (default:5000)CONFIRMATION_BLOCKS: Number of confirmations to wait before processing (default:1)MAX_RETRIES: Maximum retry attempts for failed relays (default:3)RETRY_DELAY: Delay between retries in milliseconds (default:5000)
Critical Requirements
Bridge Funding
The relay bridge must be funded with WETH9 tokens before it can complete transfers.
- Bridge Address:
0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939 - WETH9 Address:
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 - Current Status: Bridge must have sufficient WETH9 to cover all transfers
To check bridge balance:
cast call 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 \
"balanceOf(address)" \
0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939 \
--rpc-url $RPC_URL_MAINNET
To fund the bridge (if you have WETH9):
cast send 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 \
"transfer(address,uint256)" \
0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939 \
20000000000000000000000 \
--rpc-url $RPC_URL_MAINNET \
--private-key $PRIVATE_KEY
Security Considerations
- Relayer Role: Only addresses with relayer role can call
relayMessage - Bridge Authorization: Only authorized bridges can receive messages
- Replay Protection: Messages are tracked by messageId to prevent duplicate processing
- Access Control: Admin can add/remove bridges and relayers
- Token Address Mapping: Source chain token addresses are mapped to destination chain addresses
Monitoring
The service logs all activities. Check logs for:
relay-service.log: Combined log output- Console output: Real-time status
Monitor key metrics:
- Messages detected per hour
- Messages relayed successfully
- Failed relay attempts
- Bridge WETH9 balance
Troubleshooting
Service won't start
- Check all environment variables are set correctly
- Verify RPC endpoints are accessible
- Ensure private key is valid and properly expanded (use
.env.localif needed) - Check Node.js and npm are installed
Messages not being relayed
- Check relayer has ETH for gas on mainnet
- Verify relay router and bridge addresses are correct
- Ensure relayer address has RELAYER_ROLE
- Check bridge is authorized in router
- Verify bridge has sufficient WETH9 balance
Relay transactions failing
- Most common: Bridge has insufficient WETH9 tokens - fund the bridge
- Check gas limit is sufficient (default: 1,000,000)
- Verify message format is correct
- Review error logs for specific revert reasons
- Check transaction receipt for revert reason
High gas costs
- Adjust gas limit in
RelayService.jsif needed - Monitor gas prices and adjust timing if possible
- Consider message batching for future improvements
Development
# Run in development mode with auto-reload
npm run dev
# Run tests (if available)
npm test