Files
smom-dbis-138/services/relay

CCIP Relay Service

Custom relay mechanism for delivering CCIP messages from Chain 138 to Ethereum Mainnet.

Architecture

The relay system consists of:

  1. CCIPRelayRouter (on Ethereum Mainnet): Receives relayed messages and forwards them to bridge contracts
  2. CCIPRelayBridge (on Ethereum Mainnet): Receives messages from relay router and transfers tokens to recipients
  3. 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

  1. Event Monitoring: The relay service monitors MessageSent events from the CCIP Router on Chain 138
  2. Message Queue: Detected messages are added to a queue for processing
  3. Token Address Mapping: Source chain token addresses are mapped to destination chain addresses
  4. Message Relay: For each message:
    • Constructs the Any2EVMMessage format with mapped token addresses
    • Calls relayMessage on the Relay Router contract on Ethereum Mainnet
    • Relay Router forwards to Relay Bridge
    • Relay Bridge calls ccipReceive and transfers tokens to recipient

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 reads ETHEREUM_MAINNET_RPC from smom-dbis-138/.env). Prefer Infura https://mainnet.infura.io/v3/<PROJECT_ID> or Alchemy to avoid public RPC rate limits (429). Default fallback: https://ethereum.publicnode.com.
  • PRIVATE_KEY or RELAYER_PRIVATE_KEY: Private key for relayer (needs ETH on mainnet for gas)
  • START_BLOCK: Block number to start monitoring from (default: latest or 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

  1. Check all environment variables are set correctly
  2. Verify RPC endpoints are accessible
  3. Ensure private key is valid and properly expanded (use .env.local if needed)
  4. Check Node.js and npm are installed

Messages not being relayed

  1. Check relayer has ETH for gas on mainnet
  2. Verify relay router and bridge addresses are correct
  3. Ensure relayer address has RELAYER_ROLE
  4. Check bridge is authorized in router
  5. Verify bridge has sufficient WETH9 balance

Relay transactions failing

  1. Most common: Bridge has insufficient WETH9 tokens - fund the bridge
  2. Check gas limit is sufficient (default: 1,000,000)
  3. Verify message format is correct
  4. Review error logs for specific revert reasons
  5. Check transaction receipt for revert reason

High gas costs

  • Adjust gas limit in RelayService.js if 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