Why $1.94M turned into $14k of LIT

On July 5, 2026, in block 25467373, 0xff89...981e swapped 1,126.44 ETH to LIT through 0x. But they ended up with only 5,775.66 LIT (about $14.2k at the time), losing ~$1.93M. How?

Red: What the trader got. Blue: Best single route and best split route out (~9 and ~18x more). Grey: Sum of all LIT on Ethereum.

There was only ~126k LIT onchain

There were 125,739 LIT in five pools onchain at the time (~$307k):

Pool Protocol Max input Max LIT out LIT out in USD
USDC/LIT primary Uniswap v4, no hook 160,963.80 USDC 51,591.29 ~$126.5k
USDC/LIT secondary Uniswap v4, no hook 156,115.61 USDC 48,129.05 ~$118.0k
USDC/LIT third Uniswap v3 6,892.80 USDC 2,475.79 ~$6.1k
WETH/LIT Uniswap v3 44.99 WETH 19,965.88 ~$49.0k
ETH/LIT Uniswap v4 4.87 ETH 3,016.86 ~$7.4k

So the theoretical max you could get on Ethereum at the time was 125,179 LIT (~$307k). The trade started with ~$1.94M worth of ETH, so even the best possible trade was going to lose ~$1.6M in value.

The 0x trade went through the deepest LIT pool in the list that had 51,591 LIT (~$126.5k). But the trader got only 5,775 LIT (~$14.2k).

Why didn't the router give the trader at least the 51,591 LIT from this pool?

All the best routes to USDC fail the final leg

The trade started from WETH. There are many liquid paths from WETH to USDC. The best route from WETH to USDC ends up with 1,942,305 USDC.

But 0x's route took a detour through AVAIL: it swapped all 1,126.44 WETH into 6,678,869 AVAIL through a shallow WETH/AVAIL pool (23 ETH TVL), then pushed that AVAIL into the even more overwhelmed AVAIL/USDC pool, turning ~$1.94M into 14,508 USDC.

The final hop only put 14,508 USDC through the USDC->LIT pool, even though it could have taken 160,963.80 USDC.

Why did 0x go through AVAIL, losing 99.3% of the USDC?

0x's route: $1.94M of WETH in, $14.2k LIT out. Almost all the value dies as price impact in two shallow AVAIL pools. 801,484 AVAIL strand in the Settler and is swept by a bot.

How "Ticks exceeded" stumped the router

The USDC->LIT pool is a concentrated liquidity pool. If you try to swap more USDC than the LIT side can support, the pool runs out of initialized ticks and fills only partially, leaving the rest of the input unswapped and throwing a "Ticks exceeded" error.

The 0x Settler contract can handle that case, but 0x's offchain router probably had a safeguard against this case and discarded all routes that arrive at the USDC/LIT pool with more USDC than it could handle (160,963.798334 USDC).

There were 40 direct (no-split) routes that arrived with more than 160,963 USDC at the USDC-LIT pool. Offchain routers that avoid overflow would discard all of those candidate routes.

All 129 simulated WETH → USDC candidate routes at full size, ranked by USDC out (log scale; the interactive chart has a log/linear toggle, log preselected). Red routes arrive above the pool's 160,963.80 USDC limit and fail the final leg; green routes would fit. 0x's AVAIL route would sit at position 88 here.

The first green route is WETH -> wTAO -> USDC -> LIT. It arrives with 157,040 USDC, just 2.4% shy of the max the pool can take. And returns 50,609 LIT.

Successful Tenderly simulation of this route in the same block and position of the trade: tdly.co/shared/simulation/f794613a.

So the trader could have easily gotten 50,609 LIT (~$124.1k), 8.8 times the settled amount, going through wTAO.

Why didn't he get this route?

Intermediate Token Whitelists

Most routers restrict the tokens they allow into their routes to cut down on the search space and speed up their algorithm. So long-tail tokens, strange tokens (e.g. fee-on-transfer tokens) and long-tail protocols often don't make it into the router.

But wTAO and its pools were vanilla: wTAO is a standard ERC20 token. The WETH/wTAO and wTAO/USDC pools are standard Uniswap v3 pools with a 1% fee.

One reason could be that wTAO was too long-tail a token.

But AVAIL, the token 0x went through, is a small token too. It's even smaller ($11M mcap with ~$0.3M / day volume) than wTAO ($22M mcap with ~$0.5-0.8M / day volume). So a very restrictive in-between token set doesn't explain it.

Even without wTAO there were 46 chances to return more LIT

It's not only wTAO. There were 88 other routes that fit through the final pool, 46 of which returned more LIT than the 0x route. Many went through large, obvious intermediate tokens (like 1INCH, UNI, or WBTC). There was even a route with no intermediate token at all: a plain WETH -> USDC route. Rank 44 in the chart, it passed 146,454 USDC and returned 47,907 LIT.

So even the most limited token set (no intermediate token) and router setup (only 2-hop routes) would have returned 47,907 LIT (8.3x what the trader got).

Pool Cut

Another heuristic routers use is to not consider pools that are too small, or too rarely traded on. Here are the pools from the routes that beat the settled trade without overflowing the final LIT pool:

Pool Used by TVL (ETH) Route LIT out (× settled)
WETH/UNI Fynd split route 7,848 102,107 (17.7×)
WETH/wTAO Fynd wTAO route 1,458 50,610 (8.8×)
wTAO/USDC Fynd wTAO route 549 50,610 (8.8×)
WETH/USDC direct route 392 47,907 (8.3×)
UNI/USDC Fynd split route 197 102,107 (17.7×)
1INCH/USDC 1INCH route 157 44,784 (7.8×)
WETH/1INCH 1INCH route 146 44,784 (7.8×)
USDC/LIT secondary Fynd split route, final LIT pools 105 102,107 (17.7×)
AVAIL/USDC 0x's route 76 5,776 (1×)
USDC/LIT primary 0x's route, final LIT pools 50 50,610 (8.8×)
WETH/AVAIL 0x's route 23 5,776 (1×)
USDC/LIT third Fynd split route 13 102,107 (17.7×)

Most of these pools are larger than the pools in the settled route. So a simple TVL cut-off doesn't explain why 0x skipped them.

It gets weirder: the 0x route was willing to break the AVAIL/USDC pool limit

Strangely, the route that 0x settled did break a pool limit: the limit of the intermediate AVAIL/USDC hop (a Uniswap v3 pool, not v4).

  • The WETH/AVAIL hop sent 6,678,869 AVAIL into the 0x Settler.
  • The AVAIL/USDC pool could only absorb 5,877,385 AVAIL before hitting its limit.
  • The swap filled partially and returned 14,508 USDC.
  • The unconsumed 801,484 AVAIL never left the Settler.
The overflow hop: 6,678,869 AVAIL arrive at the AVAIL/USDC pool, which can only absorb 5,877,385. The swap part-fills for 14,508 USDC; the 801,484 leftover AVAIL strand in the Settler until an MEV bot sweeps them for 1.24 WETH.

0x's v3 swap callback pays the pool only the amount the pool actually requested, so the leftover simply stayed behind as Settler balance.

0x's Settler handles a v3 overflow differently than a v4 overflow. On v3 the overflow is silent: the callback pays only what the pool asks for, and the remainder strands in the Settler. On v4, the Settler's note accounting tracks the remaining sell amount and flags a nonzero remainder as a partial fill, so a final-hop overflow is visible.

Had 0x been willing to overflow the final v4 pool the way its route overflowed the v3 AVAIL pool, even the naive direct route would have partial-filled the LIT pool for its maximum 51,591 LIT, with the ~$1.78M USDC overhang stranded in the Settler.

There's probably a clue here why the offchain router refused to overflow the v4 pools but not the AVAIL v3 pool.

The leftover went to an MEV bot: Leftover funds are not safe in the Settler, and can be transferred out by anyone. An MEV bot did exactly that, in the same block: it swept the 801,484 AVAIL out of the Settler (sweep transaction) and immediately swapped it back through the WETH/AVAIL pool for 1.24 WETH.

Going up from 50,609 LIT to 102,107 LIT with split routes

So far we found a trade that throws no errors and returns 50,609 LIT. There were 125,739 LIT on Ethereum. To get the rest we need to run a routing algorithm that can route through multiple pools in parallel: a split router.

I ran Fynd's new Split router on the block and got 102,107 LIT, about $250k.

0x's split router probably checked all the obvious routes to USDC (arriving with ~$1.94M), never found a split where no pool breaks its limit, and gave up.

This is understandable; hosted routers must economize compute. Fynd split search took about 2.7s to find this route (while normal trades take < 20ms). It went through 792 candidate paths to find the pieces to assemble this split route.

All candidate paths Fynd explored. You can see that the 48 best-scored candidates all fail because they return too much USDC; green bars show paths that land without error. Fynd then built its final route out of the marked (①②③④) candidate routes. The wTAO branch, the best single route of the whole trade, sits at rank #556 of 792.

Fynd found the following route:

Branch Allocation Route LIT out
A 10% WETH -> wTAO -> USDC -> LIT (primary v4) 50,610
B 15% WETH -> USDC -> LIT (secondary v4) 46,148
C 70% WETH -> ABT / ArcBlock -> ETH -> LIT (native v4) 2,998
D 5% WETH -> UNI -> USDC -> LIT (third v3) 2,351

Total: 102,107 LIT, about $250k. Tenderly simulation

That is 89 LIT short of the 102,196 LIT theoretical max of the three USDC/LIT pools.

One order, split four ways: 10% via wTAO, 15% direct, 70% via ABT, 5% via UNI, four LIT exits, 102,107 LIT total. The red band is where we sacrifice USDC: we intentionally burn 70% of the input in a shallow pool for 3% of the output, to stay within what the LIT pools can absorb.

This is still not all 125,739 LIT on Ethereum, which would need the LIT from the WETH/LIT and ETH/LIT pools too. But we will stop here for now.

How to keep this from happening again

To protect yourself from this happening to you or your users:

Display price impact: Most UIs do, and the user likely saw a price impact warning and ignored it (I didn't check what UI they used).

Run your own algorithm: If you run a UI that routes large or many swaps, run your own router. Routing algorithms behind APIs make tradeoffs you don't know about.

Each has its own blindspots. If you run your own router you can choose them consciously: what tokens and pools to include, how many algorithms to run, and how long you wait for a solution (the longer you wait, the better the answer).

Running a router used to be impossibly hard.

It's simple now: go to https://docs.fynd.xyz/, follow the quickstart guide, and you have your own local router up in 10 min.

Choose exactly the setup you want (route on Curve or not, add PEPE pools or not, run a fast or a slow algorithm). You can easily find the above routes yourself. And you can scale to any load (Fynd parallelizes, is all in Rust, and can handle 1,000 requests per second on a MacBook).

Use a MEV Protected RPC: The backrun was trivial. If you went through a MEV protected RPC with rebates, you could have gotten most of the lost value back.

Note: As far as I can tell there was no negligence or intentional malpractice in what 0x did here. The failings of this trade are within expected limitations of hosted routing algorithms. If you try to swap way outside the limits of the chain's liquidity you can expect that routers do not optimize for this case yet.

All trade simulations

Simulation Result Link
Single route: WETH -> wTAO -> USDC -> LIT 50,609.47 LIT (~$124.1k) Tenderly
Split route, uncapped candidates: 4 branches over 4 LIT pools 102,107.45 LIT (~$250.4k) Tenderly

All simulations run at block 25467373, transaction index 0 (where the original trade sat). We used a placeholder sender (0x1111...1111) with WETH balance and allowance state overrides; the swaps themselves execute against unmodified historical pool state. Gas cost at the block's gas price (0.0786 gwei) was about $0.44 for the single route (gas 3,241,466) and $0.69 for the split route (gas 5,119,782); it had no real effect on which route wins.

Open Mysteries

There are still some open mysteries:

  • What token/pool filter removed those pools from the 0x router? A TVL filter doesn't explain it.
  • Why did the offchain router accept a v3 overflow but not a v4 overflow?
  • Why didn't the frontend or user go through a MEV protected endpoint?
  • What warnings did or didn't the user see and ignore?