next · esc overview · p notes
Middleman v0.1.0

Who stands between
your quote and your fill.

Order the prints. Join on the maker. Name the middleman, per pool.

Build with CMC · API Hackathon Built on the CoinMarketCap API Markets and Trading Tools MIT · keyless
Middleman — two blue prints in one block; an orange hairpin between them, one wallet's sell turning back into its own buy
MOTO/WETH on Ethereum · #1 Uniswap v2 pair by 24 h transactions, CoinMarketCap's own ranking · 2026-09-18 22:36:19 UTC
66.2%
of the pool's volume in the last 800 prints

was 3 wallets buying back what they just sold, inside one transaction — 29 times in 3.6 hours. The pair is #1 because of them.

The problem

Three suspects.
No witness.

Rina routes a mid-cap through the pool her wallet picked and is filled under the quote. She spends the evening convinced a bot ate her.

Every tool shows her volume and 24 h transactions — exactly the two numbers a wallet trading with itself inflates. So she picks the busiest pool and sets a loose cap "to be safe".

quote the price she saw fill the price she got ? a sandwicher prints around her, same block a wash bot round-trips inflate the count the pool itself thin, and honest about it
The solution

One table names it.

The last 800 real prints of a token from CoinMarketCap's keyless feed, ordered by block and log index, joined on the maker address. One row per pool. One line under it.

round-tripthe same wallet's next print in the block is the other side, size within 5 % — removed and counted
sandwicha round-trip whose legs enclose someone else's print, and the wallet came out ahead — named
organiceverything else — what a fill really pays, p50 / p90 per pool
routethe pool with the lowest organic p90; that p90, rounded up, is the cap
middleman.edycu.dev
The page: 66.2 % headline in orange, the three-pool table with the round-trip row in orange, and the green route line — route via Uniswap v2 / WETH, cap slippage at 0.65 %
How it works · six stdlib modules, no server, no model

Order. Group. Join on the maker.

/v1/dex/tokens/transactions tape.pull() keyless · lastId cursor ma h lgid tp a0 a1 tx en detect.order() (int(h), int(lgid)) strings → ints, else "9" > "10" misorders detect.group() (en, t0a, t1a) no pool address on a row — recover it detect.middlemen() same ma · next print |Δa0|/a0 ≤ 5 % → round-trip enclosed, take > 0 → sandwich cost.pool_row() a1 / a0 vs the print before p50 / p90 per pool recommend.route() argmin organic p90 ≥ 50 organic prints cap = p90 ↑ 0.05 % /v1/dex/token/pools pool address · venue · liqUsd merged v3 fee tiers marked ×n /v1/dex/security/detail buyTax · sellTax — a tax is never a middleman /v4/dex/pairs/quotes/latest 24h_no_of_buys + 24h_no_of_sells how much of a day 800 prints cover enrich.* — labels, never scores /v4/dex/spot-pairs/latest the hero rule: #1 pair by no_of_transactions_24h, per chain
block 26006339 · Uniswap v2 / WETH · rows verbatim from the API · docs/proof/moto.json (opens in a new tab)

Two rows. One division.

lgid   ma             side  a0 (MOTO)        a1 (WETH)   a1/a0
202    0x9c97c0a6a0…  buy    561,202.9606     0.699926    1.2472e-06
209    0xc9160fdab1…  sell   842,991.8538     1.043025    1.2373e-06  ◀ leg
218    0xc9160fdab1…  buy    829,640.6520     1.032594    1.2446e-06  ◀ leg
225    0xdd64c9d73e…  sell   774,494.6589     0.958588    1.2377e-06
same ma · same block 26006339 · same tx
|Δa0| / a0 = |829,640 − 842,991| / 842,991 = 0.0158 ≤ 0.05 → round-trip

That is the whole definition. Sell 842,991 MOTO, buy 829,640 back, 1.6 % apart, one transaction. Divide a1 by a0 and you have the price each leg paid.

This wallet and two others did it 29 times in 3.6 h — $127,254 of the pool's $192,275. Take the legs out and an organic fill pays 15.5 bps, not the 55.3 the raw tape implies.

A trader reading the raw tape sizes a cap three and a half times too loose.

Live · the same engine ported to the browser, parity-tested, behind a keyless proxy

Paste any token. Read the rows.

middleman.edycu.dev · UNI, live
UNI, live: 19 pools ranked by organic quote-to-fill, the green route line — route via Uniswap v3 / WETH, cap slippage at 0.20 % — and the raw-rows panel for the block that holds the one sandwich
119 pools of one token, ranked by organic p90
2the decision: venue + cap, with the rule stated
3the block that holds the sandwich — legs orange, victim red
middleman.edycu.dev · SHIB, live
The paste box after a live SHIB fetch: 800 prints in 8 pages, 6.6 s, 0 credits; the headline reads 1.7× more per fill in Uniswap v2 / WETH than in ShibaSwap / WETH
4800 prints · 8 pages · 0 credits, through /api/swaps
middleman.edycu.dev/evidence
/evidence: 108 requests to CoinMarketCap's keyless /public-api with HTTP status, UTC, milliseconds and the sha256 of each body; the engine's rules verbatim; the re-derive commands
5every call: status · UTC · sha256 of the body
Ten tokens · three chains · the same rule, whatever it said · docs/proof/census.json (opens in a new tab)

Ten tokens. Whatever they said.

prints scanned
8,000

10 tokens on Ethereum, BSC and Solana, 445 pools; every receipt committed and re-derivable from its tape offline.

round-trips
45

by 14 wallets. The widest gap between one token's pools: LINK, 22.9× — 0.6 bps median on Uniswap v4 (Ethereum) / ETH against 14.2 on Uniswap v3 (Ethereum) / USDC.

sandwiches
2

both the same wallet, front-running a seller on UNI and on LINK — for $0.95 and $2.19. A rate of 0.025 %.

RETRACTED · 2026-09-18

This project was decided as the sandwich rate per pool. The day-1 spike found zero same-block sandwiches in 1,200 prints; the census found two in 8,000. So the headline changed before the build, not the caveat after — same feed, same ordering, same join; the sandwich is one named case of a middleman, and the number the page leads with is the one the join actually found. On 2026-09-20 the same command found 17 sandwiches in one window on a different pair — small on average is not small everywhere.

CoinMarketCap DEX API · keyless /public-api surface · every call on /evidence with its hash

Six keyless endpoints. One engine.

endpointwhat the product takes from it
/v1/dex/tokens/transactionsthe enginema h lgid tp a0 a1 tx en t0a t1a per swap, lastId cursor
/v1/dex/token/poolspool address, venue, liqUsd — labels each (en, t0a, t1a) group
/v1/dex/security/detailbuyTax / sellTax — a transfer tax is never scored as a middleman
/v4/dex/pairs/quotes/latest24 h buys + sells — 15.7 % of the pair's 4,899 transactions is what 800 prints covered
/v4/dex/spot-pairs/latestthe hero rule — #1 pair by no_of_transactions_24h, per chain
/v1/dex/platform/listexplorer URL templates for the transaction links
0
credits, every receipt

No key exists to charge. It survives the Oct 1 Basic reversion at zero credits.

5
systems to replace it

A multi-chain swap indexer with maker attribution, a pool registry, a fee-on-transfer simulator, a pair-count aggregator, an explorer map.

8
findings in FEEDBACK.md

From the missing CORS header to q rounded to two figures on Uniswap v4 rows — dated, with evidence.

Four real runs, 145 tests, one command · DEMO.md (opens in a new tab)

Run it. The number moves.

2026-09-18 22:36:19 UTC · scripts/seed.py, the capture behind the page · no key · DEMO.md receipt 1
$ python3 scripts/middleman.py   hero rule → MOTO/WETH, the #1 Ethereum Uniswap v2 pair by 24h transactions
MOTO · ethereum · 800 prints · 3.6 h · blocks 26006339–26007416 · captured 2026-09-18 22:36:19 UTC · keyless

  66.2% of Uniswap v2 / WETH volume is 3 wallet(s) buying back what they just sold (29 round-trips, same transaction)

  pool                         prints organic  round-trips                sandw  q→fill p50 / p90     tax     liq
  Uniswap v2 / WETH               770     712  29 · 3 wallet(s) · 66.2%       0  15.5 / 60.9 bps      0/0   $956k  ◀ route
  Uniswap v2 / USDC                17      17  —                              0  66.5 / 137.8 bps     0/0    $39k
  Uniswap v2 / USDT                13      13  —                              0  71.1 / 135.2 bps     0/0    $34k

  ▶ route via Uniswap v2 / WETH · cap slippage at 0.65 %   (organic p90 60.9 bps, 1 candidate pool(s))
wrote docs/proof/moto.json  (8.6 s wall clock · 11 calls, every one HTTP 200 · 0 credits — keyless)
145
tests · 139 offline, 6 live
1,000
generated blocks, 0 violations
3.3 ms
engine p50 / 800 prints
8.6 s
live · 800 prints · 11 calls
2026-09-18 22:36:19 UTC
66.2 %
29 round-trips · 3 wallets — the capture behind the page
2026-09-18 23:00:18 UTC
27.5 %
8 round-trips · 1 wallet — 24 minutes later, 11.3 s
2026-09-19 00:28:45 UTC
0.0 %
0 round-trips, a quiet window — and the tool said so, 11.0 s
What this is not · the full list of six is on the README

Where it stops.

The window is 800 prints, not 24 h

3.6 h on the hero, 20 h on SHIB. The span is on every row; the receipt carries the window's share of the pool's 24 h count — 15.7 % here.

Uniswap v3 fee tiers of one pair merge

The feed carries no pool address. A row marked ×n is n pools sharing (venue, base, quote) — marked, never guessed. Filed as FEEDBACK.md §6.

Quote-to-fill is a realised, fee-inclusive move

Consecutive opposite-side prints straddle the fee twice, so p90 sits near 60 bps on every busy 0.30 % pool. It is used comparatively, across pools of one token in one window.

A round-trip is a shape, not a verdict

Same wallet, same block, opposite sides, size-matched. The rows and the definition are printed; nobody is labelled a bot. The anonymous tier throttles per IP — the tool backs off and exits 75 if the quota is spent.

The ask

Run it before
you score it.

about ten seconds from a fresh clone · no key · no install
$ git clone https://github.com/edycutjong/middleman.git
$ cd middleman
$ python3 scripts/middleman.py
$ python3 scripts/verify_tape.py   # every receipt vs its tape, offline

Between your quote and your fill:
two rows and one division.

Middlemanv0.1.0captured 2026-09-18 22:36:19 UTC0 credits
01 / 12
speaker notes