Files
proxmox/docs/07-ccip/SEND_ETH_TO_MAINNET_REVERT_TRACE.md
defiQUG fbda1b4beb
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
docs: Ledger Live integration, contract deploy learnings, NEXT_STEPS updates
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands
- CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround
- CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check
- NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere
- MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates
- LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 15:46:57 -08:00

8.2 KiB
Raw Permalink Blame History

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 messages 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 isnt 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 (or your routers 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 bridges 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:

    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.005same 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); repos 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 bridges destination list. Router on 138: 0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e.
Router supported tokens cast call <ROUTER> 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 bridges 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.


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 Chainlinks 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 routers 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