Dangling Approvals: The $7.5M JaredFromSubway Counter-MEV Honeypot
June 24, 2026 · 10 min read
The predator becomes the prey
For most of two years, jaredfromsubway.eth was the apex predator of Ethereum's mempool — by some counts responsible for around 70% of all sandwich attacks on the network between late 2024 and late 2025. On June 20, 2026, it was drained for roughly $7.5 million in WETH, USDC, and USDT (its operator claimed closer to $15M).
What makes this one worth a post-mortem isn't the size. It's the cause. There was no leaked private key, no compromised dependency, no bug in a shared DeFi protocol. On-chain security firm Blockaid classified it as a counter-MEV honeypot: a trap engineered to turn the bot's own profit-seeking logic into the attack. The bot was drained because it did exactly what it was built to do — fast, profitably, and without ever questioning who it was trading with.
How the bot was supposed to work — and why that's the attack surface
A sandwich/arbitrage bot lives or dies on speed and math. It continuously scans for profitable routes, simulates a candidate transaction against forked state (an eth_call or a local fork), and if the simulation returns a net profit, it executes immediately to beat competitors. To actually move tokens through a route, it issues an ERC-20 approve() so the router/helper on that route can pull its tokens.
Every one of those properties is a foothold. The decision to trade is fully automated and gated on a single question — “does this simulate profitable?” — and the act of trading grants spending power over real tokens to whatever contract the route points at. The attacker's entire campaign is built on those two facts.
Phase 1 — Build a fake world
Over several weeks, the attacker deployed 66 counterfeit token contracts that mimicked the names and interfaces of WETH, USDC, and USDT, and paired them with fake liquidity pools on Uniswap-style forks. Then they sent harmless test transactions through those pools to reverse-engineer the bot's routing, gas, and opportunity-detection behavior. The goal of this phase wasn't profit — it was to learn exactly what the bot considered a lucrative, safe trade.
Phase 2 — Earn the bot's trust
This is the part the headlines miss, and it's the cleverest piece. In small “unarmed” batches, the malicious contracts behaved perfectly normally: they consumed the bot's approvals as a real swap would and handed it small, genuine profits. To the bot's heuristics and to its simulator, these pools were now proven — real value, real trading activity, repeatable profit. The con worked precisely because the early trades were honest.
Phase 3 — The trap: dangling approvals
Once the routes were trusted, the attacker flipped the behavior. In large “armed” batches, the same child contracts stopped behaving like a real swap. The bot computed a fat, profitable route, simulated it (still net-positive), and executed — granting an exact, trade-sized allowance to the helper contract on the route. But this time the helper did not consume the approval. The trade resolved, the simulation had been satisfied, and a live spending allowance was left standing on a real token, pointed at a contract the attacker controlled.
Nothing reset it. The bot had no post-trade step that drove the allowance back to zero or checked that it had been spent. Repeat across many armed trades and many helper contracts, and the attacker quietly accumulated dozens of standing allowances — each an open door into the bot's real holdings.
Phase 4 — The sweep
With the allowances in place, the attacker broke the link between the profitable bait and the theft. In a separate, coordinated transaction, they called transferFrom(bot, attacker, amount) against the real WETH, USDC, and USDT — once per standing allowance — sweeping the bot's inventory out in a single shot.
transferFrom pulls mean dozens of separate helper contracts, each left holding a standing allowance. Counts observed in the drain transaction; confirm exact figures before relying on them.The transfer log is the tell. The drain is not one largetransferFrom; it's many identical, fixed-size ones — repeated pulls of exactly 92.161407687812186112 WETH, of 143,528.656384 USDC, of 149,488.051712 USDT. That pattern is only possible one way. An ERC-20 allowance is a single value per (owner, spender) pair, and transferFrom decrements it — so one spender with a 92.16 WETH allowance can pull 92.16 WETH once. Sixteen identical pulls means sixteen separate spender contracts, each holding its own standing allowance. The “66 fake contracts” weren't set dressing; each was a bucket for one more open approval.
The assumptions that got it drained
Strip away the MEV specifics and this is a chain of ordinary, reasonable- sounding assumptions — each of which the attacker turned into a lever:
- “If it simulates profitable, it's safe to execute.” Simulation validated the trade's P&L. But
approve()is persistent state that outlives the simulated transaction — the sim proved the trade made money; it could never prove the trade left nothing exploitable behind. - “Approvals get consumed by the swap.” No post-trade check that the allowance was actually spent, and no reset to zero. Dangling allowances survived across blocks.
- “Anything shaped like an ERC-20/router is benign.” The bot granted spending power over real tokens to contracts it had never validated, at execution speed.
- “A contract that behaved well in small trades behaves the same at scale.” The armed/unarmed switch existed to break exactly this assumption.
- “Atomicity is my security boundary.” The attacker decoupled the profitable bait transaction from the later drain transaction — so the thing the bot validated and the thing that robbed it were never the same transaction.
Why “approve only the exact amount” would not have saved it
Most of the coverage lands on the same lesson: always approve the exact amount needed, not infinity. Look again at the number the bot approved — 92.161407687812186112, an 18-decimal, precisely-computed trade figure. The bot was already approving exact, trade-sized amounts. It still got drained.
“Approve exact amounts” addresses the blast radius of a single bad approval. It does nothing about the real failures here:
- Approving spenders it didn't control. The single most important fix: an allowlist of trusted routers. An exact-amount approval to a hostile contract is still a hostile approval.
- Not verifying consumption. After a trade, assert the allowance you granted is back to zero. An approval that wasn't spent is a red flag, not a no-op.
- Treating approvals as fire-and-forget. Allowances are persistent state. An automated system has to reconcile the state it leaves between transactions, not just the P&L of each one.
You can't simulate your way out of a dangling approval.
The deeper lesson
Simulation and atomicity validate a transaction: its inputs, its execution, its profit. They say nothing about the persistent state the transaction leaves behind. The bot proved every trade profitable and was still drained, because the vulnerability lived in the gap between “this transaction is safe” and “the state after this transaction is safe.” It's the same shape as a theme we keep coming back to — you can't test your way to soundness (more on that here). For anyone running automated on-chain systems, the corollary is blunt: the residual state your bot leaves between transactions is part of your attack surface, and almost nothing in a fast execution loop is looking at it.
What would have caught it
- Spender allowlist. Only ever
approve()routers on a vetted list; treat an approval to an unknown contract as a bug, not a routing detail. - Allowance hygiene as an invariant. approve → use → assert-zero, every trade. A post-execution check that no non-zero allowances remain to non-allowlisted spenders would have made the dangling approvals impossible to accumulate.
- Monitor standing allowances. Continuous off-chain monitoring for open approvals on the bot's holdings would have surfaced the trap during the weeks it was being set, not after the sweep.
- Distrust your own simulator. A counterparty that's honest in small simulated batches and hostile at scale is a known con; profitability in simulation is not a safety proof.
Aftermath
The operator offered an on-chain white-hat deal — roughly a 50% bounty for the safe return of funds — and threatened legal action. The attacker largely declined: on-chain monitors tracked the proceeds being consolidated (reportedly into the low thousands of ETH) and partially routed through Tornado Cash, with the rest swapped toward DAI to dodge stablecoin freezes. (Exact exfiltration figures and the bounty terms varied across early reports — treat the specifics as preliminary.)
The most profitable bot in the mempool was undone not by a flaw in its code so much as by a flaw in its worldview: that a trade which simulates profitable is a trade that's safe to make. Auditing an automated on-chain system means auditing the assumptions and the residual state it leaves behind — not just the transactions it sends.
