| **CCIPRelayBridge** | `0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939` | Holds WETH; releases to recipient when relay router calls `ccipReceive`. **Must be funded with WETH** for payouts. **WETH9-only** — no other tokens accepted. |
**Token mapping:** Chain 138 → Mainnet address mapping and which tokens the relay bridge supports are in [TOKEN_MAPPING_AND_MAINNET_ADDRESSES.md](TOKEN_MAPPING_AND_MAINNET_ADDRESSES.md). Source of truth: `config/token-mapping.json`.
| **Bridge** (LINK fee) | `0xcacfd227A040002e49e2e01626363071324f820a` | Pay fee in Chain 138 LINK. Default in `CCIPWETH9_BRIDGE_CHAIN138`. |
| **Bridge** (native ETH fee) | `0x63cbeE010D64ab7F1760ad84482D6cC380435ab5` | Pay fee in native ETH. |
Both bridges have **mainnet destination** set to **CCIPRelayBridge** (`0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939`), so all 138→mainnet sends are delivered via the relay.
---
## End-to-End Flow
1. User on Chain 138 calls bridge `sendCrossChain(mainnetSelector, recipient, amount)` (e.g. via `scripts/bridge/run-send-cross-chain.sh`).
2. Bridge pulls WETH from user, calls router `ccipSend(...)` with `receiver = abi.encode(CCIPRelayBridge)`.
3. Router emits `MessageSent` (no Chainlink relayer).
4.**Relay service** (Node) watches the Chain 138 router for `MessageSent`, builds `Any2EVMMessage`, and calls mainnet **CCIPRelayRouter.relayMessage(CCIPRelayBridge, message)**.
5. Relay router calls **CCIPRelayBridge.ccipReceive(message)**; bridge transfers WETH to `recipient` on mainnet.
---
## Running the Relay Service
1.**Fund mainnet CCIPRelayBridge** with WETH so it can pay recipients:
```bash
# Option A: Script (transfers deployer's full WETH balance by default)
If the default RPC rate-limits (429), set `ETHEREUM_MAINNET_RPC` to Infura or Alchemy in `smom-dbis-138/.env`.
2.**Grant relayer role** (if not already): The relay tx will revert with "transaction execution reverted" (no revert data) until the relayer address has `RELAYER_ROLE` on the mainnet router. As the router's admin (deployer), run:
# .env: RPC_URL_138, RPC_URL_MAINNET or ETHEREUM_MAINNET_RPC (Infura/Alchemy recommended to avoid 429), PRIVATE_KEY (relayer), CCIP_RELAY_*
npm start
```
For mainnet RPC, set `RPC_URL_MAINNET` in `services/relay/.env` or `ETHEREUM_MAINNET_RPC` in `smom-dbis-138/.env`. Prefer Infura (`https://mainnet.infura.io/v3/<PROJECT_ID>`) or Alchemy; see [RPC_ENDPOINTS_MASTER.md](../04-configuration/RPC_ENDPOINTS_MASTER.md).
Config defaults in `services/relay/src/config.js` point to the router and bridges above; override with env vars if needed.
### If relay tx reverts with "transaction execution reverted"
1.**Relayer role:** Ensure the relayer has `RELAYER_ROLE`: run `./scripts/bridge/grant-relayer-role-mainnet.sh` (use `RPC_URL_MAINNET=https://ethereum.publicnode.com` if Infura returns 403).
2.**Bridge WETH:** The mainnet CCIPRelayBridge must hold at least the amount being relayed. If the bridge balance is lower than the transfer amount, fund it:
- Ethereum receiver bridge has live code and reports `weth9() = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2`.
-`processedTransfers(0x19656fe758fc0e36ce5ce16ad9101e76c9eae19e5ed6bea08335dfb664215edc)` remained `false` across repeated checks at:
-`2026-03-30T02:44:48Z`
-`2026-03-30T02:45:48Z`
-`2026-03-30T02:46:50Z`
-`2026-03-30T02:47:52Z`
-`2026-03-30T02:48:53Z`
- No `CrossChainTransferCompleted` logs were found on the destination bridge over Ethereum block range `24765000..latest` during the initial verification window.
**Relay repair and replay execution**
- Host repaired: `r630-01`
- Local repo relay implementation was newer than the deployed host version:
-`LiquidityPoolETH` (`0x603e078eb5Cca4F5c817A2F76D073f924D7272d3`) showed accounting drift on Ethereum mainnet:
- actual native ETH balance: `0`
- actual WETH balance: `500125031257814` wei (`0.000500125031257814 WETH`)
- contract accounting still reported:
- ETH available liquidity: `15000000000000000` wei (`0.015 ETH`)
- WETH available liquidity: `1000000000000000` wei (`0.001 WETH`)
- The relayer address `0x4A666F96fC8764181194447A7dFdb7d471b301C8` is recorded as the LP for those pool balances, but direct withdrawals proved the accounting drift is real:
- ETH-side withdrawal reverted with `LiquidityPoolETH: ETH transfer failed`
- WETH-side withdrawal reverted with `FailedInnerCall`
- Operational consequence:
- neither `SwapRouter` nor `EnhancedSwapRouter` is a funding source for this payout
- the trustless pool path cannot currently self-fund the missing relay-bridge WETH because the live balances do not match the pool’s own accounting
**Verified blockers**
1.**Insufficient relay-bridge liquidity on Ethereum mainnet**
- Mainnet relay bridge WETH balance at verification time:
- Deployer liquidity on mainnet at verification time:
- WETH balance: `0`
- ETH balance: `3345428710812742` wei (`0.003345428710812742 ETH`)
- Result: the message cannot be paid out on Ethereum mainnet until the relay bridge is funded by another mainnet wallet.
2.**Relay service source polling instability**
- Host: `r630-01`
- Service: `ccip-relay.service`
- Status during verification: `active (running)`
- Journal showed repeated source filter errors:
-`eth_getFilterChanges ... Filter not found`
- This was repaired on `2026-03-29`: the deployed relay code was updated, the stale host `.env.local` override was disabled, and the message was successfully replayed into a destination tx.
- The source-side relay issue is no longer the active blocker for this message.
3.**Destination-side execution is still blocked after relay repair**
- The repaired relay successfully replayed the historical message and submitted destination tx `0x87e3d401c498781fabb1289be283af2244add3ab768dfca00027e9d4e270318d`.
- That tx mined and reverted in block `24767607` with router-level revert `CCIPRelayRouter: relay failed`.
- Because the relay bridge balance stayed unchanged and `processedTransfers(messageId)` remained `false`, the live failure is still downstream of source detection and upstream of payout completion.
**Operational conclusion**
- The Chain 138 send is verified.
- The original Ethereum receive was blocked by **destination execution + liquidity/accounting**, not by the source send.
### 2026-03-29 — Recovery path and successful completion
**Bootstrap funding path that was actually used**
1.`138 -> Gnosis` was tested first but is not a live lane because Chain 138 emits through the custom router `0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817` while the native Gnosis bridge expects the public Chainlink router.
2.`138 -> Avalanche` and `138 -> BSC` native-bridge onward paths were evaluated next; Avalanche and BSC native CCIP continuation were not usable for direct onward mainnet forwarding because fee quoting on the official bridge path reverted.
3. The working recovery path was:
- send WETH from Chain 138 to the **BSC relay-backed receiver**
- unwrap received WETH into native BNB
- bridge BNB to Ethereum mainnet using LiFi / Across
- wrap enough ETH into WETH on mainnet
- fund the mainnet relay bridge
- manually replay the original historical message into `CCIPRelayRouter`
- Mainnet recipient ETH balance remained positive for gas after completion.
**Operational conclusion**
- The original Chain 138 `0.01 WETH` send to Ethereum mainnet is now **fully completed**.
- The failure mode was recoverable by sourcing missing liquidity from a relay-supported public-chain lane and manually replaying the original message after funding.
- For future incidents of this specific class, the fastest working runbook is:
1. verify the original message ID is still unprocessed on mainnet
2. source missing WETH through a relay-supported public-chain route
3. fund the mainnet relay bridge with the exact shortfall
4. replay the historical message directly into `CCIPRelayRouter.relayMessage(...)`