# CrossCurve (ex-EYWA) bridge exploit

## 1. What happened

On 1 February 2026 the CrossCurve "Consensus Bridge" was exploited on Ethereum
mainnet, then repeated against PortalV2 on Arbitrum
(https://cantina.xyz/blog/crosscurve-bridge-hack-axelar-expressexecute,
https://x.com/MixBytes/status/2018304206763892766). Dates disagree: KuCoin/Defimon
place the first transactions on 31 January
(https://www.kucoin.com/news/articles/crosscurve-bridge-exploit-3m-loss-due-to-fabricated-message-vulnerability),
The Block/Crypto.news use 1 February, BlockSec 2026/02/02
(https://blocksec.com/blog/weekly-web3-security-incident-roundup-feb-2-feb-8-2026).

Amounts disagree too. The project put market value at $1,441,892.31
(https://x.com/crosscurvefi/status/2018366973936087512,
https://x.com/MixBytes/status/2018304206763892766). BlockSec estimated ~$2.8M;
Defimon put it near $3M across Ethereum, Arbitrum, Optimism, Base, Mantle, Frax,
Celo and Blast
(https://finance.yahoo.com/news/crosscurve-contains-exploit-crypto-thefts-115656873.html).
QuillAudits reports $1.4M (https://www.quillaudits.com/blog/hack-analysis/cross-curve-exploit).

What was taken: mostly the protocol's own EYWA token. 999,787,453.03 EYWA (~999.8M,
the entire PortalV2 EYWA balance) was unlocked to the attacker, plus USDT 815,361,
CRV 239,889.64, WETH 123.59, USDC 34,820.73, USDB 10,288.43 and smaller amounts of
3CRV, 2CRV, WBTC and frxUSD
(https://x.com/crosscurvefi/status/2018366973936087512). The Ethereum-side EYWA could
not be liquidated (no EYWA DEX pools there; sole CEX route frozen).

## 2. Root cause

Not a key theft or tricked signer: a smart-contract access-control/configuration
flaw, an integration blunder. CrossCurve's ReceiverAxelar inherited Axelar GMP SDK
v5.10's AxelarExpressExecutable, whose `expressExecute()` is `external`, callable by
anyone, guarded only by a fresh-commandId check
(https://x.com/MixBytes/status/2018304206763892766,
https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/releases/5.10.x/contracts/express/AxelarExpressExecutable.sol#L92-L108).
CrossCurve never overrode or guarded it. The normal `execute()` path is protected by
`gateway.validateContractCall()`; `expressExecute()` skips it. Inside `_execute()`,
ReceiverAxelar only verified that the caller-supplied sourceChain/sourceAddress
matched a stored `peers` entry, values that are attacker-chosen and public, so it
proved nothing (https://blocksec.com/blog/weekly-web3-security-incident-roundup-feb-2-feb-8-2026).
Impact was amplified because the confirmation threshold for that peer (chain id
80,094 = Berachain, address 0xF3792BAE7F35DCDE2916C6E6A72CCD3A5330D565) was 1, so
one bridge path sufficed and no real consensus was required
(https://cantina.xyz/blog/crosscurve-bridge-hack-axelar-expressexecute).

## 3. Pattern

Class: cross-chain message validation / missing access control on a public
execution path.

```
function expressExecute(commandId, sourceChain, sourceAddress, payload) external {
    require(!gateway.isCommandExecuted(commandId));   # only real check
    _execute(sourceChain, sourceAddress, payload);     # no gateway.validateContractCall()
}
# _execute(): require(peers[sourceChain] == sourceAddress)  # both attacker-supplied
# then decode(payload) -> BURN_UNLOCK -> PortalV2.unlock(token, amount, from, attacker)
```

Attacker: fresh commandId; sourceChain="berachain"; a registered peer address; a
payload whose source chain id/sender pass the receiver checks and whose op is
BURN_UNLOCK to the attacker's "to" address; then a direct `expressExecute()` call.
`expressExecuteWithToken()` and the v6.x siblings share this shape.

## 4. The check that would have caught it

- Unit test: from a non-Gateway EOA, call `ReceiverAxelar.expressExecute()` with a
  fresh commandId, whitelisted sourceChain/sourceAddress and crafted payload, and
  assert it reverts; the fast path must be callable only by the Axelar Gateway or a
  trusted executor.
- Invariant/fuzz test: assert no code path can move value out of PortalV2 (`unlock`,
  `_unlock`) unless `gateway.validateContractCall()` approved the
  (commandId, sourceChain, sourceAddress, payloadHash) tuple in that tx; fuzz
  arbitrary `(commandId, sourceChain, sourceAddress, payload)` into
  `expressExecute()` and check it holds.
- Review rule: enumerate every inherited public entrypoint (e.g. Slither's callable
  entrypoint list); require symmetric validation between fast and normal paths, and
  a config check that any consensus threshold is > 1.

## 5. Sources

- https://x.com/MixBytes/status/2018304206763892766 (MixBytes post-mortem; definitive technical account)
- https://x.com/crosscurvefi/status/2018366973936087512 (project token/amount breakdown)
- https://x.com/crosscurvefi/article/2019475142594744611 (project recovery memorandum)
- https://cantina.xyz/blog/crosscurve-bridge-hack-axelar-expressexecute (via web.archive.org/web/20260521195744/)
- https://blocksec.com/blog/weekly-web3-security-incident-roundup-feb-2-feb-8-2026
- https://www.quillaudits.com/blog/hack-analysis/cross-curve-exploit
- https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/releases/5.10.x/contracts/express/AxelarExpressExecutable.sol#L92-L108
- https://crypto.news/crosscurve-exploited-for-3-million-in-multi-network-bridge-attack/
- https://finance.yahoo.com/news/crosscurve-contains-exploit-crypto-thefts-115656873.html
- https://www.kucoin.com/news/articles/crosscurve-bridge-exploit-3m-loss-due-to-fabricated-message-vulnerability
- https://www.theblock.co/post/387939/crosscurve-bridge-exploited-for-approximately-3-million-across-multiple-chains-via-spoofed-messages

Not found: no separate project-authored technical post-mortem exists; the
memorandum states the auditor's report is the definitive account and no further
report is planned (https://x.com/crosscurvefi/article/2019475142594744611).
