Jaredfromsubway Mev Bot 2

JaredFromSubway MEV Bot 2 Incident Analysis

Summary#

The attacker exploited jaredfromsubway: MEV Bot 2 0x1f2f... and drained approximately $7.5M in USDC, USDT, and WETH in a single transaction. The jaredfromsubway bot is widely known as a predatory sandwich-attack bot. In this incident, however, the bot itself was exploited.

Sandwich Basics#

You do not need to understand sandwich attacks to understand this incident. This section only explains how the bot normally works.

Take an AMM (automated market maker) like Uniswap v2 as a simple example. For simplicity, this ignores Uniswap fees and gas costs.

Say a Uniswap v2 ETH/USDC pool holds:

  • 100 ETH in reserve
  • 200,000 USDC in reserve In this pool, 1 ETH = 2,000 USDC (200,000 USDC / 100 ETH = 2,000 USDC per ETH).
ETH reserveUSDC reserveETH price in this pool
100 ETH200,000 USDC2,000 USDC/ETH
90 ETH220,000 USDC~2,444 USDC/ETH
110 ETH180,000 USDC~1,636 USDC/ETH

Thus, as the amount of ETH decreases in the pool, ETH becomes scarcer and its price goes up, and vice versa.

A sandwich attack exploits this mechanism.

Normal Swap#

First, here is the normal swap flow without a sandwich attack.

Alice wants to buy ETH by paying 20,000 USDC.

Alice puts 20,000 USDC into the ETH/USDC pool.

The Uniswap v2-style AMM keeps ETH reserve * USDC reserve = k (x * y = k).1

Here, k is: 100 * 200,000 = 20,000,000 and the post-swap ETH reserve would be: 20,000,000 / 220,000 = 90.909

So, Alice receives 9.091 ETH (100 - 90.909) if no one intervenes.

Normal AMM swap diagram

Sandwiched Swap#

Now Bob, an MEV bot, enters the market.

Alice broadcasts the swap transaction, and it stays in the mempool for a while until it is included in a block. At this point, Bob can observe this transaction and might think:

The ETH price in this pool will go up because Alice's transaction reduces the ETH reserve.
So I should buy ETH first, then sell it after Alice's transaction is executed and the ETH price rises.

Transactions in Ethereum's public mempool can be observed by anyone. Validators and builders decide which transactions are included and in what order. In a simplified model, Bob can pay a higher priority fee or submit a bundle so that his transaction is placed before Alice's transaction.

Bob now broadcasts a transaction to buy ETH with 10,000 USDC, using a higher priority fee or MEV bundle.

After Bob's transaction, the pool has 210,000 USDC, and the ETH reserve is 20,000,000 / 210,000 = 95.238 ETH. So Bob receives 4.762 ETH (100 - 95.238).

PoolETH reserveUSDC reserve
After Bob's TX95.238210,000

The ETH price in this pool goes up: 210,000 / 95.238 = ~2,205 USDC per ETH Bob's transaction pushed up the swap rate.

Now Alice's transaction (paying 20,000 USDC for ETH) is included and executed. The pool's USDC reserve becomes 230,000, and the ETH reserve becomes 20,000,000 / 230,000 = 86.957. Alice receives 8.281 ETH (95.238 - 86.957) at the end.

Without the sandwich attack, Alice would have received 9.091 ETH, but with the attack she receives 8.281 ETH.

Sandwiched AMM swap diagram

After Alice's transaction is finalized, the ETH price in the pool is pushed up further. Bob then sells his 4.762 ETH back to the pool, receiving about 11,941 USDC.

BobValue
Paid first10,000 USDC
Received afterabout 11,941 USDC
Netabout 1,941 USDC

In reality, Uniswap fees, gas costs, and builder/validator payments reduce Bob's profit. However, the attack remains rational as long as the expected profit is positive.

Incident: Bot Exploit#

The JaredFromSubway bot can also follow routes, such as sandwich attacks or arbitrage routes, as long as they are profitable. The route does not have to be a sandwich attack.

This article is based on publicly available on-chain transaction data, public incident reports, and third-party technical reconstruction. I did not independently reproduce the full archive-node trace environment like Reth2. Therefore, low-level claims about contract storage, recovered selectors, and exact internal call behavior are treated as reconstructed findings from external analysis.

In short, unarmed mode was the lure: the contracts made the route look profitable. Armed mode was the exploit path: the contracts left the bot's approvals in place and later used them to drain funds.

Unarmed Mode#

  1. The bot finds a profitable, arbitrage-like route. A route here means a proposed sequence of contract calls and token hops, such as: USDC -> child/wrapper contract -> pair1 -> fCAP -> pair2 -> hub token -> Hub -> USDC

    The route may be identified using pending transactions, same-block state changes, pool reserves, and simulation results.

  2. The bot approves the Child contract to spend a specified amount of real USDC. For example, the bot calls USDC.approve(child, 1,000 USDC) This does not transfer any USDC. It only grants the Child contract an allowance to spend up to 1,000 USDC from the bot contract.
  3. The Child consumes that allowance. The bot expects the Child to call: USDC.transferFrom(botContract, child, 1,000 USDC) After this, the allowance should be consumed, and the route continues through fake-token assets and Uniswap v2-like pairs.
  4. The route then appears to swap through DEX pairs: USDC principal -> fUSDC / child wrapper -> fCAP -> HubToken -> Hub settlement -> real USDC back to the bot
  5. The bot expects the final real-token balance to be higher than before: before: 10,000 USDC after: 10,100 USDC profit: +100 USDC

If the bot mainly checks final balance or PnL, this looks like a successful arbitrage.

Unarmed mode route diagram

Armed Mode#

  1. The attacker armed the Coordinator for the current block. The Coordinator recorded the current block as the armed block. From this point, Child contracts can check: Coordinator.isArmed() == true
  2. The attacker used the Trigger contract to prepare fake pair state. The fake pairs were updated so that the route appeared profitable to the bot.
  3. The bot approved the Child to spend real USDC. USDC.approve(child, 1,000 USDC)
  4. The bot called the Child setup function. The Child checked the Coordinator and detected armed mode.
  5. Because the Coordinator was armed, the Child did not pull real USDC. It did not call: USDC.transferFrom(botContract, child, 1,000 USDC)
  6. Instead, the Child credited fake token balance to the first fake pair. The bot's real USDC stayed in the bot contract. The fake pair received fake input, such as fUSDC.
  7. The fake route continued through attacker-controlled fake pairs. fUSDC -> fCAP -> HubToken
  8. The Hub paid the bot a small amount of real USDC. Hub -> bot: 100 USDC
  9. The bot saw positive PnL: before: 10,000 USDC after: 10,100 USDC So the route looked profitable.
  10. But the dangerous allowance remained: allowance(botContract, child) = 1,000 USDC
  11. Later, the attacker called the Coordinator's drain function. The Coordinator called withdraw(botContract) on many Child contracts.
  12. Each Child used the remaining allowance to drain the bot: USDC.transferFrom(botContract, attackerRecipient, 1,000 USDC)
Armed mode route diagram

Root Causes and Defense Opportunities#

Root cause 1: Missing post-state safety invariant The bot checked profit, but not whether dangerous approvals remained after execution. Defense opportunity: check that risky allowances are cleared or bounded after each route.

Root cause 2: Fake-market route admission The bot accepted attacker-controlled markets as tradable. Defense opportunity: restrict routes to trusted or well-understood markets.

Root cause 3: State-dependent contract behavior The bot accepted contracts whose behavior could change between simulation and execution. Defense opportunity: reject routes that depend on hidden mode switches or block-dependent behavior.

The common lesson from Drift and this incident is that DeFi systems must not treat market-shaped artifacts as markets. A token, pool, price history, or route should be admitted only after checking its provenance, liquidity independence, behavioral stability, and post-execution safety invariants.

Footnotes#

  1. https://developers.uniswap.org/docs/get-started/concepts/how-uniswap-works#swaps

  2. https://ethereum.org/developers/docs/nodes-and-clients