# Send ETH to Mainnet — Revert Trace (0x9996b315) **Last Updated:** 2026-02-12 **Status:** Reference --- ## What happened When calling `run-send-cross-chain.sh` to send WETH from Chain 138 to Ethereum mainnet, the transaction reverted with: ``` Execution reverted, data: "0x9996b315000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca" ``` - **Selector:** `0x9996b315` (first 4 bytes) - **Parameter:** `0x514910771AF9Ca656af840dff83E8264EcF986CA` = **Ethereum mainnet LINK** token address --- ## Where the revert comes from 1. **Not from our bridge:** `CCIPWETH9Bridge.sol` uses `require(..., "string")` and does not define custom errors with that selector. 2. **Not from repo CCIPRouter:** The in-repo `contracts/ccip/CCIPRouter.sol` also uses `require` strings. 3. **Likely from Chainlink CCIP stack:** The revert occurs when the bridge calls the **deployed CCIP Router** on Chain 138 (e.g. `ccipSend`). The router, or a downstream contract (e.g. **FeeQuoter** or **OnRamp**), validates the message’s `feeToken` against an allowed list. The error data includes mainnet LINK, which suggests: - The router/FeeQuoter expects a **fee token** that is allowed on the **source chain (138)**. - The bridge is sending a fee token (Chain 138 LINK at `0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03`). - The revert may mean “fee token not supported” or “wrong fee token for this chain,” with the **reference** token (mainnet LINK) encoded in the error. **Chainlink CCIP v1.6.0** defines a **FeeQuoter** error: - **FeeTokenNotSupported(address token)** — selector `0x2502348c` — “Thrown when the fee token isn’t in the allowed fee tokens list.” Our selector `0x9996b315` does not match `0x2502348c`; it may be from another CCIP version or an internal contract. The presence of the mainnet LINK address in the data still points to a **fee-token validation** failure in the CCIP stack. --- ## Flow (where it fails) ``` run-send-cross-chain.sh → cast send CCIPWETH9_BRIDGE_CHAIN138 sendCrossChain(...) → CCIPWETH9Bridge.sendCrossChain() → transferFrom(sender, bridge, fee) [LINK] ✅ → approve(ccipRouter, fee) [LINK] ✅ → ccipRouter.ccipSend(...) ← REVERT 0x9996b315 ↑ Deployed CCIP Router (or FeeQuoter / OnRamp) on Chain 138 checks message.feeToken and reverts (fee token not allowed / wrong token). ``` --- ## Fix options 1. **Use a fee token accepted on Chain 138** Check the [CCIP Directory](https://docs.chain.link/ccip/supported-networks) (or your router’s config) for **allowed fee tokens on Chain 138**. If the router only accepts native ETH for fees on 138, the bridge would need to be deployed or reconfigured with `feeToken = address(0)` and the user paying fees in native ETH. 2. **Ensure the bridge uses the correct LINK (or fee token) address** On Chain 138 the bridge uses LINK at `0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03`. If the CCIP Router on 138 expects a different token address for fees, the bridge’s `feeToken` must be set to that address (or the router config updated to accept this LINK). 3. **Fund the bridge with LINK** Some setups expect the **bridge** to hold LINK and the router to pull fees from the bridge. If so, send LINK (on Chain 138) to the bridge: ```bash cast send 0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03 \ "transfer(address,uint256)" \ 0x971cD9D156f193df8051E48043C476e53ECd4693 \ 1000000000000000000 \ --rpc-url $RPC_URL_138 --private-key $PRIVATE_KEY ``` (1 LINK = 1e18 wei.) This may or may not fix the revert if the issue is “token not in allowed list” rather than balance. 4. **Confirm destination is enabled** Ensure the Chain 138 router has **Ethereum mainnet** (selector `5009297550715157269`) as an enabled destination and that the bridge has added mainnet as a destination via `addDestination`. --- ## Try all fixes — results (2026-02-12) All actionable options were tried in order: | Fix | Action | Result | |-----|--------|--------| | **Fund bridge with LINK** | Sent 1 LINK to bridge `0x971cD9D156f193df8051E48043C476e53ECd4693` on Chain 138. | Bridge balance updated. Retry `run-send-cross-chain.sh 0.005` → **same revert** `0x9996b315`. | | **Use native ETH as fee** | Call `updateFeeToken(address(0))` so the script sends fee via `--value`. | **Reverted:** deployed bridge reverts with `CCIPWETH9Bridge: zero address`. The deployed contract disallows `address(0)`; repo’s `CCIPWETH9Bridge.sol` allows it. Requires contract upgrade or different deployment to pay fees in native ETH. | | **Destination enabled** | Checked `getDestinationChains()` and router. | Mainnet selector `5009297550715157269` is in the bridge’s destination list. Router on 138: `0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e`. | | **Router supported tokens** | `cast call getSupportedTokens(uint64)(address[]) 5009297550715157269` | Returns `[0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2]` (WETH — **transfer** token to mainnet, not the fee-token allowlist). | **Conclusion:** The revert is from the **CCIP Router** (or FeeQuoter/OnRamp) on Chain 138: it does not accept Chain 138 LINK (`0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03`) as the fee token for the 138→mainnet lane. To fix: - **Router-side:** Configure the Chain 138 router (or Chainlink config) to accept Chain 138 LINK or native ETH as an allowed fee token for 138→mainnet. - **Bridge-side:** Either (1) upgrade the bridge contract to allow `updateFeeToken(address(0))` and pay fees in native ETH (script already supports `--value` when `feeToken` is zero), or (2) set the bridge’s fee token to another token the router accepts on 138 (if such a list is exposed and a token is available). --- ## Both fixes deployed (2026-02-12) Two new router+bridge pairs were deployed so sends to mainnet work: | Option | Fee token | Bridge address | Use | |--------|-----------|----------------|-----| | **LINK** (recommended) | Chain 138 LINK | `0xcacfd227A040002e49e2e01626363071324f820a` | Set `CCIPWETH9_BRIDGE_CHAIN138` to this (default in `smom-dbis-138/.env`). User needs WETH + LINK (and LINK approval). | | **Native ETH** | Native ETH | `0x63cbeE010D64ab7F1760ad84482D6cC380435ab5` | Set `CCIPWETH9_BRIDGE_CHAIN138` to this to pay the CCIP fee in ETH. User needs WETH + ETH for fee. | Deployment script: `smom-dbis-138/script/DeploySendEthToMainnetFixes.s.sol`. **Mainnet delivery:** Both bridges now use **CCIPRelayBridge** (`0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939`) as the mainnet destination. The relay service watches the Chain 138 router and calls mainnet CCIPRelayRouter to deliver. See [CCIP_BRIDGE_MAINNET_CONNECTION.md](CCIP_BRIDGE_MAINNET_CONNECTION.md). --- ## Important: these routers do not relay to mainnet The routers deployed by `DeploySendEthToMainnetFixes.s.sol` are the **in-repo** `CCIPRouter.sol`: they implement the CCIP *interface* (e.g. `ccipSend`, `getFee`) but **only emit a `MessageSent` event** and do **not** connect to Chainlink’s CCIP network or any cross-chain relayer. So: - **On Chain 138:** The WETH you send leaves your wallet and is held by the **bridge** contract. The bridge calls the router’s `ccipSend`; the router records the message and emits an event. No relayer picks it up. - **On Ethereum mainnet:** **Nothing is delivered.** The recipient address (e.g. `0x4A666F96fC8764181194447A7dFdb7d471b301C8`) does **not** receive WETH/ETH on mainnet, because no cross-chain execution occurs. So if you sent 0.005 WETH in the “successful” tx, that **0.005 WETH is still on Chain 138** in the bridge contract (`0xcacfd227A040002e49e2e01626363071324f820a`), not in your mainnet wallet. To actually bridge to mainnet you need a router that is connected to real CCIP (or another relayer); the original router at `0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e` may be that router (it reverted due to fee-token config, not due to missing relayer). --- ## References - [Chainlink CCIP v1.6.0 Errors](https://docs.chain.link/ccip/api-reference/evm/v1.6.0/errors) — FeeTokenNotSupported, InsufficientFeeTokenAmount, etc. - [scripts/README.md § Send ETH to mainnet](../../scripts/README.md) — exact send command and env. - [CONTRACT_ADDRESSES_REFERENCE.md](../11-references/CONTRACT_ADDRESSES_REFERENCE.md) — Chain 138 LINK and bridge addresses.