# Malicious Relayer can Replay Execute Calldata on Different Chains Causing Double-Spend Issue

Connext BridgeFacet.execute never checks CallParams.destinationDomain against s.domain, so a relayer can replay signed execute calldata on other chains and drain router liquidity.

- Payout: Audit contest
- Severity: high
- Program: Connext
- Found by: xiaoming90
- Disclosed: 2022-10-17
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2022-06-connext-findings/issues/144
- Library page: https://bugbountylibrary.com/r/c4%3A2022-06-connext%3AH-06
- Tags: area:web3 domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:business-logic vuln:signature-replay

**Target:** BridgeFacet.execute / _executeSanityChecks cross-chain bridge
**Root cause:** transferId is derived from canonical token domain/id and CallParams, not the local chain, and _executeSanityChecks never validates s.domain against CallParams.destinationDomain, so identical calldata is accepted on any chain where the local token maps to the same canonical token.
**Steps:**
1. Attacker initiates a fast-transfer of 1000 USDC from Ethereum to Optimism via Connext; routers sign the transferId.
2. Attacker (as relayer) submits the execute calldata to Optimism BridgeFacet.execute; router signature validates and 1000 oUSDC is paid to attacker.
3. Attacker edits ExecuteArgs.local to Polygon's local USDC representation and resubmits the same calldata to Polygon BridgeFacet.execute.
4. Polygon execute rebuilds the same transferId via tokenRegistry.getTokenId(local) (canonical Ethereum/USDC) so router signature still validates; 1000 POS-USDC paid to attacker.
5. Router can reclaim liquidity on Optimism when the Nomad message arrives but cannot reclaim on Polygon, draining router funds.
**How found:** Code review of _getTransferId and _executeSanityChecks: noticed transferId is computed from tokenRegistry.getTokenId(_args.local) (canonical domain/id) and CallParams, with no check that s.domain equals CallParams.destinationDomain, implying calldata is domain-agnostic.
**Exploitation:** A relayer tasked with delivering execute calldata replays it on a second chain after swapping ExecuteArgs.local to that chain's local token representation; since transferId depends only on canonical token id/domain and CallParams, router signatures remain valid and the bridge pays out liquidity on both chains.
**Escalation:** Replay across every chain where the same routers have liquidity and the local token maps to the same canonical token, multiplying the drain.
**Insight:** When a signed message's replay-protection ID is derived from canonical/global identifiers rather than the receiving chain, check whether the destination domain is validated on-chain; any field excluded from the signed digest (like local token address) is attacker-mutable.

