What happened
Our AgentSwap solver checked a swap and got a green light. The real transaction failed with InsufficientOutput, after spending roughly one million gas. Five of six sampled failures had the same cause: the pool charged more than the simulation showed. (Replay evidence)
The trick was simple. A Uniswap V4 hook can set a pool's fee and read the transaction's gas price. In our zero-gas-price calls, these hooks returned a low fee. With a real transaction's gas price, they returned a higher one. Replaying the same transaction in both settings reproduced the mismatch. (Fee probes)
| Hook | Simulation fee | Real transaction fee |
|---|---|---|
H1 · 0x0d83…e880 | 0–0.20% | 14% |
H2 · 0x62d4…4080 | 0% | 1.8947–4.90%, depending on the hop |
H3 · 0x349c…80c4 · control | 0% | 0% |
A router that trusts the cheap quote may choose the pool. The higher fee then reduces the output. If the user's minimum is no longer met, the swap reverts; if it still passes, the swap completes at the worse price.

source: fee_step.csv
How we caught it
Run the same swap twice, changing only the context. Pin the block, calldata, balances, and caller. Compare a zero-gas-price call with the real transaction's fee settings. Then compare debug_traceCall results, following the first changed return value down to beforeSwap. Its third return word contains the LP fee override.
For the wider scan, we first select dynamic-fee pools with beforeSwap enabled. We probe each selected hook in both fee settings; when it needs a complete swap, we use the V4 Quoter. An error or empty pool gets no safety verdict. This finds candidates, but does not test every way a hook can recognize a simulator.
The September 5 follow-up exposed that limit. Our actual simulation wrapper omitted from, making tx.origin zero. Adding realistic fees alone still produced the inflated quote. Only the intended wallet plus realistic fees reproduced the mined transaction's short output. We tested all four zero/real sender-and-fee combinations at the same historical block. (Four-way replay, method and limits)
How the hook spots a simulation
The follow-up traced another Robinhood hook, 0xd752…0080, through a fee controller to implementation 0x1181…f46dd. The detector lived in that implementation. Looking only at the hook's code would have missed it.
It recognizes the simulator's defaults. At block 54,676,220, the implementation combined these checks with OR: zero gas price, zero transaction origin, Foundry's default test address, zero base fee, a zero value from opcode 0x44 (difficulty/PREVRANDAO), and a stored gas threshold. On the observed path, any match cleared the calculated fee. (Bytecode, disassembly, and executed trace)
PoolManager → hook → fee controller → implementation
Recognized simulation fingerprint → LP fee becomes 0%
No fingerprint matched → calculated LP fee applies
Foundry's default address is hard-coded in the detector. With realistic gas price, direct controller probes returned:
| Transaction origin | LP fee |
|---|---|
| Zero address | 0% |
| Foundry default | 0% |
| Broadcasting wallet | 2.4391% |
| Independent nonzero control | 2.4391% |
The result held at four gas limits. The returned fee word changed from 0x400000 to 0x405f47: the same override flag, plus 24,391 fee units. This is how a simulated swap gets a real—but misleading—low-fee result. No quote-response tampering is needed. (Probe results)
This is reconstructed bytecode behavior; we did not obtain the Solidity source. A fixed-fee branch also exists, so the detector is not necessarily active in every configuration. And fixing the context is not a future-price guarantee: advancing only the timestamp by one second changed the fee from 2.4391% to 4.8953%. (Context probes)
How much it cost
In the September 4 snapshot, three confirmed bait pools processed 1,003 swaps with about 238,334 USDG in USDG-side volume. Their estimated fees totalled 6,419 USDG. These are historical measurements, not live totals. (Pool totals)
The estimate multiplies each swap's USDG-side amount by its recorded fee rate. It is not a measurement of fees withdrawn. The 17 distinct callers are contract addresses, often routers—not a count of individual users.
We found the pools in a census of 21,661 dynamic-fee pools and 2,203 hooks. Only four incident-related hooks were tested on Robinhood Chain; three showed the mismatch. Untested hooks have no verdict. (Browse the census)

source: cumulative_bait_fees.csv
Who collected the fees
The extra charge went to liquidity providers through normal Uniswap LP fee accounting. We found no hook claim transfers in the 993 swap transactions checked. (Accounting checks, mechanism evidence)
Each pool's observed liquidity traced to one owner. Across the three pools there were two owners: 0x22d5…2527 and 0x9924…9699. For two hooks, deployment and pool creation traced to those same owners. The third hook's deployment could not be established. (Ownership evidence)
USDG withdrawals are visible, but they mix deposited capital with fees. We cannot label the entire withdrawal as profit. (Withdrawal records)
It also appeared on other chains
Ethereum had one matching hook, with roughly 136 USDC in observed volume. Arbitrum had a cluster of six hooks that quoted 0.3% at zero gas price; higher-gas-price probes returned 50–53.3%. Some results depended on the probe context. (Cross-chain results, hook details)
We found no mismatch among the hooks we could test on Unichain, Optimism, or Avalanche. Some calls failed or pools lacked liquidity, so those hooks remain undecided. Base and Polygon scans were incomplete in this snapshot; BNB was not scanned.
This technique has been reported before. Our contribution is tracing this cluster from the quote mismatch to the fee records and liquidity owners. Prior reports and sources give the background.
What this means for users
AgentSwap uses intents. Users sign an order with a minimum output and an expiry; our solver submits the fill. In these failed fills, settlement reverted because it could not deliver the signed minimum. Users lost no principal from those failures; our solver paid the gas. The signed minimum protects an agreed floor, not the best available price.
A direct aggregator swap can still succeed at a worse price. Excessive slippage tolerance sets a low minimum output. A hook can quote a low fee, charge more onchain, and leave enough output to pass that minimum. The swap completes and the user receives fewer tokens. If output falls below the minimum, the swap reverts and the sender pays gas.
As a meta-aggregator, AgentSwap also checks routes supplied by upstream aggregators. In this investigation, we performed dedicated detection and marked malicious hooks that upstream aggregators had not identified and filtered out.
For solvers, use the intended wallet, realistic transaction fees, and faithful block fields through the actual simulation wrapper. Quarantine routes with confirmed simulation-dependent fees; a second simulation with the same defaults adds no protection. Keep minimum-output checks, and avoid repeating an identical failed route. (Detection method)
Check the evidence
Open the pool explorer to compare quoted and real-context fees, inspect individual pools, or run a fresh probe. The article and census use a fixed snapshot; a live probe may differ.
The research notes contain the method and limits. Download the fee data, swap records, or cross-chain summary to check the numbers.