🚨 Technical Forensic Analysis — $24.15M USDC Bridge Theft on Arbitrum
This incident was not caused by a smart contract logic bug. On-chain and cryptographic evidence points to a compromise of the hot-validator signing path.
Asset: Native USDC
Token: 0xaf88d065e77c8cc2239327c5edb3a432268e5831
Amount: 24,150,000 USDC
Date: July 22, 2026
1️⃣ Withdrawal proposal
At 21:26:55 UTC, the attacker submitted a malicious withdrawal through:
batchedCreateWithdrawals
Selector: 0x6cc76ee8
Transaction:
0x217c45c1272550e0439e53243f2987b7fb3f58b1d33c222597bbb71851b93f74
The transaction contained five validator signatures and the complete seven-validator set, whose voting power totaled 10,000.
Withdrawal parameters included:
user/destination:
0x2f2974fabc54dba33442261211c06bd20e0feefc
usd:
24,150,000
nonce:
0xa5fb88f9
The attacker wallet is an EIP-7702 delegated EOA—not a plain EOA.
Delegate/sweeper:
0x63c0c19a282a1b52b07dd5a65b58948a07dae32b
The proposal was relayed by a one-off address outside the normal relayer rotation:
0x32e3200d6e944cd9bd1c8c9865293b07206e7a01
2️⃣ Validator signatures
The five recovered signers were registered hot validators:
• 0x00bb84af06dac03bfe744da13df9d2d6fd8e77e5 — 1,428
• 0x27259f90d6ae500262ace6e8428434e0c1f308f5 — 1,429
• 0x2e26de22a92e41704b3ea00cc65a6cda47b12c9e — 1,428
• 0x52d4d9ad78a53a69bd089ee8f282ce0cd0506da7 — 1,428
• 0xbb472bc3962ad02ac660429fdbb319b5bc66da7b — 1,429
Total signing power:
7,142 / 10,000
Required quorum:
2/3 = 6,667
Therefore, the malicious withdrawal carried enough valid signing power to pass the contract’s verification.
3️⃣ EIP-712 cryptographic verification
I reconstructed the signing message using the verified contract’s own scheme:
message = keccak256(
abi.encode(
AGENT_TYPEHASH,
keccak256("a"),
keccak256(
abi.encode(
bridge,
keccak256(
abi.encode(
"createWithdrawal",
user,
destination,
usd,
nonce
)
)
)
)
)
)
The final EIP-712 digest was calculated as:
digest = keccak256(
0x1901 ||
domainSeparator ||
message
)
Domain:
name: "Exchange"
version: "1"
chainId: 42161
verifyingContract:
0x0100000000000000000000000000000000000001
Recomputed message:
0x558a989f405d706935fa15efee8eac6e32fbba7ea3f70dfd930c66436e7ed8c2
This exactly matches the message ID used in the on-chain finalize transaction.
Signing digest:
0x417d4213ff618235c568f6932e8d389ca663d004c947ebe64de8f144ff957faa
Recovering the five ECDSA signatures from this digest produced the five registered hot-validator addresses above, cryptographically confirming their combined 7,142 voting power.
4️⃣ Withdrawal finalization
At 21:30:25 UTC, the withdrawal was finalized through:
batchedFinalizeWithdrawals(bytes32[])
Selector: 0xc5bdf3ca
Transaction:
0x50d0b3ec6c3f5fce0f10abf81540bbb508f421494aa2b3480c4a264b0436547b
The finalize call contained only the 32-byte message ID—no validator signatures. The signatures were included in the earlier proposal transaction.
The gap between proposal and finalization was 210 seconds, approximately matching the contract’s 200-second dispute window.
The contract correctly validated the signatures, observed the dispute period, and released the funds exactly as designed.
The validator signing path—not the contract logic—was the failure point.