How $1.94M turned into $14k but didn't need to. A story of routing on Ethereum.
Someone swapped 1,126.44 ETH (~$1.94M) to LIT on Ethereum and only got 5,775 LIT (~$14.2k). I simulated the possible routes and found one that would give 102,107 LIT (~$250k), 17.7 times as much. Why didn't 0x find it? A story of the shortcuts and limitations of routers and how to fix them with a local backup router.
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?
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?
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.
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.
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.
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.
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?