Who stands between your quote and your fill, per pool.
A DEX trader sees a quote and a fill and cannot tell what stood between them β a wallet printing on both sides of the block, or the pool itself. Middleman orders a token's real prints by block and log index, joins them on the maker address, names the middleman, and says where to route and what slippage cap to set.
Live, keyless, 2026-09-18T22:36Z: CoinMarketCap's #1 Uniswap v2 pair on Ethereum by transactions was busy with itself β 66.2 % of its volume was 3 wallets buying back what they just sold, inside one transaction, 29 times in 3.6 hours. Take the legs out and an organic fill there pays 15.5 bps, not the 55.3 the raw tape implies. 800 prints in 8.6 s for 0 credits. Receipt β
No key. No signup. No install. One command:
python3 scripts/middleman.pymiddleman 0.1.0 β keyless, live
hero rule: the base token of the #1 Ethereum Uniswap v2 pair by 24h transactions at capture time
β MOTO/WETH 0xbd965230588eaa536de6aa45e8ebbc01638535e0
MOTO Β· ethereum Β· 800 prints Β· 3.86 h Β· blocks 26006381β26007537 Β· captured 2026-09-18T23:00:20Z Β· keyless
27.5% of Uniswap v2 / WETH volume is 1 wallet(s) buying back what they just sold (8 round-trips, same transaction)
pool prints organic round-trips sandw qβfill p50 / p90 tax liq
----------------------------------------------------------------------------------------------------------------
Uniswap v2 / WETH 771 755 8 Β· 1 wallet(s) Β· 27.5% 0 39.2 / 60.8 bps 0/0 $954k β route
Uniswap v2 / USDC 16 16 β 0 70.4 / 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.8 bps, 1 candidate pool(s))
rule: lowest organic p90 quote-to-fill among pools with β₯ 50 organic prints
coverage: this window is 16% of the routed pool's 24h transactions (4969: 2696 buys / 2273 sells)
block 26006381 β raw rows (round-trip)
lgid 633 0x9c97c0a6a0β¦ sell a0 505,623.1418 a1 0.625752 a1/a0 1.2376e-06 β leg
lgid 642 0x9c97c0a6a0β¦ buy a0 497,596.3088 a1 0.619495 a1/a0 1.2450e-06 β leg
lgid 721 0xdbde0f745f⦠buy a0 240.4182 a1 0.000300 a1/a0 1.2494e-06
leg 1 lgid 633 0x9c97c0a6a00b1a9f74dbe09a4c55ec9f09f8af7d sell a1/a0 = 0.6257523552405421 / 505623.14176740864 = 1.2375864622280164e-06
leg 2 lgid 642 0x9c97c0a6a00b1a9f74dbe09a4c55ec9f09f8af7d buy a1/a0 = 0.6194948316881366 / 497596.3088161665 = 1.2449747329556753e-06
same wallet Β· same block 26006381 Β· same tx Β· |Ξa0| / a0 = 0.0159 β€ 0.05 β round-trip
wrote docs/proof/live_run.json (11.3s wall clock, 0 credits β keyless)
That is a live call to CoinMarketCap's keyless
/public-apisurface. Nothing here is a fixture. This transcript is from 2026-09-18T23:00:18Z (run with--jsonso the receipt was kept:docs/proof/live_run.json; the bare command prints the same table). The page β and the numbers at the top of this file β come from the capturescripts/seed.pymade 24 minutes earlier with the same engine and the same rule, when the same wallets were 66.2 % of the pool's volume across 29 round-trips (docs/proof/moto.json, the 800 raw prints indata/tape_moto.json). Between the two runs they went quiet; a third run the next morning found the window clean β zero round-trips, and the tool said so (docs/proof/live_run_quiet.json). Run it yourself and the number will differ again, because it comes from the market rather than from this file β andpython3 scripts/verify_tape.pyre-derives every committed receipt from its tape with the network unplugged. All four transcripts β the fourth, on 2026-09-20, found a different hero and 17 sandwiches β are in DEMO.md. Each transcript's first line stamps the version the engine declared when it was captured (0.1.0); the first tagged release is v1.0.0 (2026-09-20), and every receipt still re-derives from its tape under it.
Read the two orange rows. Same maker, same block, same transaction hash: sell 505,623 MOTO,
buy 497,596 MOTO back, 1.6 % apart. Divide a1 by a0 and you have the price each leg paid.
In the capture behind the page that wallet and two others did it 29 times β $127,254 of the
pool's $192,275 β and the pair sits at the top of CoinMarketCap's activity ranking because of them.
The same table, in a browser: middleman.edycu.dev renders the capture with the raw rows one click away, every request behind it on /evidence, and a paste box that runs the same engine live on any token. One page for judges, no key and no setup: /judge.
![]() The table. 66.2 % Β· three pools Β· the route line |
![]() UNI, live. 19 pools, the route, and the one sandwich |
![]() /evidence. 108 requests, hashed, credits 0 |
![]() Paste a token. SHIB, live, through the keyless proxy |
Nine screenshots of live execution, including the raw-rows panel and the 390 px mobile view,
are in docs/screenshots/.
Every DEX tool shows a trader two numbers before a swap β a quote and a slippage tolerance β and one after: the fill. The gap between them has three possible authors: a sandwicher printing around her inside the block, a wash bot whose round-trips make a pool look deep, or the pool's own thinness. Nothing tells her which. So she picks the pool with the most transactions (the one most likely to be washed) and sets a loose cap "to be safe" (the one thing a sandwicher needs). Every flow tool prints volume and transaction counts β exactly the numbers a round-tripping wallet inflates.
CoinMarketCap's /v1/dex/tokens/transactions returns, per swap and with no key, the maker
address (ma), the block (h), the log index (lgid), the side and both amounts.
Order the prints by (h, lgid), group them into pools by (venue, base, quote), and join on
the maker:
| Shape | Definition | What it is |
|---|---|---|
| Round-trip | the same wallet's next print in the block is the other side, size within 5 % | buying back what it just sold β usually inside one transaction |
| Sandwich | a round-trip whose legs enclose 1β4 other makers printing in the first leg's direction, and the wallet came out ahead | the prints between were front-run |
| Organic | everything else β victims included; their fills are real | what a trader actually pays |
Then, for every organic print, the basis points between the price it paid (a1 / a0) and the
print before it in the same pool β the price you saw versus the price you got β p50 and p90
per pool. And one line: route via the pool with the lowest organic p90, cap slippage at that
p90 rounded up to 0.05 %.
The hero is a rule, not a pick. The page shows whichever token is the base of the pair CoinMarketCap itself ranks #1 by 24 h transactions on Ethereum Uniswap v2 at capture time. Whatever that pair is on capture day is what the page shows.
| Token Β· chain | Prints | Round-trips | Sandwiches | Route Β· cap |
|---|---|---|---|---|
| MOTO Β· ethereum β hero by rule | 800 | 29 Β· 3 wallets Β· 66.2 % of volume | 0 | Uniswap v2 / WETH Β· 0.65 % |
| SHIB Β· ethereum | 800 | 0 | 0 | ShibaSwap / WETH Β· 0.65 % |
| PEPE Β· ethereum | 800 | 0 | 0 | Uniswap v2 / WETH Β· 0.65 % |
| UNI Β· ethereum | 800 | 0 | 1 | Uniswap v3 / WETH Β· 0.20 % |
| LINK Β· ethereum | 800 | 0 | 1 | Uniswap v4 / ETH Β· 0.05 % |
| AAVE Β· ethereum | 800 | 0 | 0 | Uniswap v3 / WETH Β· 0.15 % |
| Mog Β· ethereum | 800 | 0 | 0 | Uniswap v2 / WETH Β· 0.65 % |
| SPX Β· ethereum | 800 | 0 | 0 | Uniswap v2 / WETH Β· 0.65 % |
| USDT Β· bsc β hero by rule | 800 | 13 | 0 | PancakeSwap v4 / KII Β· 0.05 % |
| USDC Β· solana β hero by rule | 800 | 3 | 0 | Tessera V / SOL Β· 0.05 % |
8,000 prints Β· 2 sandwiches Β· 45 round-trips by 14 wallets. The two sandwiches are the same
wallet, 0xae2fc483β¦, front-running a seller on UNI and on LINK in three separate transactions
inside one block each time β for $0.95 and $2.19. The widest spread between one token's pools:
LINK, 22.9Γ (0.6 bps median on Uniswap v4 / ETH against 14.2 on Uniswap v3 / USDC).
Every row's receipt is in docs/proof/ and every receipt re-derives from its tape.
2026-09-18. This project was decided as the sandwich rate per pool, with a prediction that it might be near zero off Ethereum mainnet. The day-1 spike ran the join on 1,200 real prints and found zero same-block sandwiches; the ten-token census found two in 8,000. The pre-authorised fallback β victim overpay on same-block front-runs β measured a median of 0.0 bps. So the headline changed before the build, not the caveat after: the engine is the same feed, the same ordering, the same maker join; the sandwich is one named case of a middleman; and the number the page leads with is the one the join actually found β a round-trip share that the sponsor's own activity ranking is built on.
2026-09-19. The first cut of the join called four things on USDT/DGAI (PancakeSwap v3, BSC)
sandwiches. They were two wallets washing around each other β each selling and buying back
for exactly the quote it received, take zero. A sandwich now requires take > 0; a same-wallet
return with nothing extracted is a round-trip whether or not other makers printed between the
legs. The regression test is named for it.
2026-09-20. Two days after the census put the sandwich rate at 0.025 %, the bare command found 17 sandwiches in one 800-print window on the day's #1 pair (wildebeest/WETH, Receipt 4), led by the same wallet the census had caught twice. The rate is small on average, not small everywhere β so the census number stands as a census number, the headline stays the price an organic fill pays, and the sandwich is the named case the join was built to catch.
pull the prints β order by (block, log index) β recover pools β join on the maker β price every organic fill β route + cap
The same diagram as a page, light and dark, with the derivation beside it: middleman.edycu.dev/architecture
Architecture diagram as Mermaid (click to expand)
flowchart LR
TX["/v1/dex/tokens/transactions<br/>ma Β· h Β· lgid Β· tp Β· a0 Β· a1 Β· tx Β· en Β· t0a Β· t1a"] --> PULL["tape.pull()<br/>keyless Β· lastId cursor Β· (tx, lgid) identity Β· receipts"]
PULL --> ORDER["detect.order() Β· detect.group()<br/>(int(h), int(lgid)) Β· (en, t0a, t1a)"]
ORDER --> JOIN["detect.middlemen()<br/>round-trips Β· sandwiches Β· organic"]
JOIN --> COST["cost.pool_row()<br/>a1/a0 Β· quote-to-fill p50/p90"]
COST --> ROUTE["recommend.route()<br/>argmin organic p90 Β· cap = p90 β 0.05 %"]
POOLS["/v1/dex/token/pools Β· /v1/dex/security/detail Β· /v4/dex/pairs/quotes/latest"] --> LABEL["enrich.*<br/>pool address Β· liquidity Β· taxes Β· 24 h coverage"] --> COST
SP["/v4/dex/spot-pairs/latest"] -->|"the hero rule"| CLI["scripts/middleman.py<br/>live Β· keyless Β· zero flags"] --> PULL
SEED["scripts/seed.py"] --> TAPE[("data/tape_*.json")] -->|"verify_tape.py"| PROOF[("docs/proof/*.json")] --> RENDER["render_site.py"] --> SITE["site/ β /, /evidence, /judge"]
SITE -->|"paste a token"| JS["site/middleman.js<br/>the engine, ported"] --> FN["/api/swaps<br/>keyless proxy + CORS + 60 s cache"] --> TX
No server, no database, no model. The product is one join applied to data only CoinMarketCap publishes, so everything that is not the fetch, the join, or the arithmetic was removed.
| Stage | Function | What it does |
|---|---|---|
| Fetch | tape.pull() |
Keyless GET with backoff (15 / 30 / 60 s), cursor from data.lastId on the envelope, prints keyed by (tx, lgid), a receipt per call: URL, status, body hash, timing. |
| Order | detect.order() Β· detect.group() |
Sort by (int(h), int(lgid)) β both arrive as strings; recover pools from (en, t0a, t1a) β the feed carries no pool address. |
| Join | detect.middlemen() |
The product. Same maker, same block, next print on the other side, size within 5 % β round-trip; 1β4 other makers enclosed and take > 0 β sandwich; the rest organic. |
| Price | cost.pool_row() |
a1 / a0 per print, never the feed's rounded q; quote-to-fill against the previous print, p50 / p90 per pool; the example block with its arithmetic. |
| Route | recommend.route() |
Lowest organic p90 among pools with β₯ 50 organic prints; the cap is that p90 rounded up to the next 0.05 %. States the rule, never widens it. |
| Label | enrich.* |
Pool address and liquidity, transfer taxes (never counted as a middleman), the window's share of the pool's 24 h count, the hero rule, explorer templates. |
| Layer | Technology |
|---|---|
| Language | Python 3.11, stdlib only on the judged path β urllib, json, hashlib, statistics |
| Data | CoinMarketCap DEX API, keyless /public-api surface β six endpoints, four load-bearing |
| Browser | one static page + a 338-line port of the engine (site/middleman.js), parity-tested against Python on every tape |
| Deployment | Vercel: static site/, two functions under api/ (a keyless CORS proxy and a health check) |
| Tests | pytest Β· hypothesis (property-based) Β· live contract tests Β· node-driven boundary tests |
| Quality | ruff Β· mypy Β· pytest-cov Β· pip-audit Β· gitleaks Β· CodeQL Β· Dependabot |
Full derivation, every failure mode, and the deliberate non-architecture: ARCHITECTURE.md Β· the definitions: docs/METHOD.md. Rendered page, light and dark: middleman.edycu.dev/architecture.
| # | Endpoint | Role | Key? | Called from |
|---|---|---|---|---|
| 1 | /public-api/v1/dex/tokens/transactions |
the engine β ma, h, lgid, tp, a0, a1, tx, en, t0a, t1a per swap; lastId cursor |
π none | middleman/tape.py Β· api/swaps.js |
| 2 | /public-api/v1/dex/token/pools |
pool address, venue, liqUsd β labels each (en, t0a, t1a) group, counts merged fee tiers |
π none | middleman/enrich.py |
| 3 | /public-api/v1/dex/security/detail |
extra.buyTax / extra.sellTax β a transfer tax is never counted as a middleman |
π none | middleman/enrich.py |
| 4 | /public-api/v4/dex/pairs/quotes/latest |
24h_no_of_buys + 24h_no_of_sells β how much of a day the window covers |
π none | middleman/enrich.py |
| 5 | /public-api/v4/dex/spot-pairs/latest |
the hero rule β #1 pair by no_of_transactions_24h, per chain |
π none | middleman/enrich.py |
| 6 | /public-api/v1/dex/platform/list |
explorer URL templates for the transaction links | π none | middleman/enrich.py |
Every call is on /evidence with its URL, status,
timestamp, body hash and the envelope's credit_count β CMC reports 1 per keyless call
against an account that does not exist (FEEDBACK.md Β§2); no key was sent, so
0 credits were charged on every receipt β and tests/test_published_counts.py fails if the
code ever calls a path this table does not name.
The join needs the maker address, the block, the log index, the side and both amounts, per
swap, for every pool of a token across a chain, keyless, with a cursor. That row shape is what
/v1/dex/tokens/transactions returns, and nowhere else is it published without an indexer.
The maker address is what lets "the same wallet on both sides" be counted rather than
suspected; the log index is what makes "between" exact inside a block instead of a timestamp
heuristic. CMC has no mempool view and no MEV labels β and the product does not need them,
because a middleman has to print. /v1/dex/token/pools is what turns a (venue, base, quote)
group back into an address with liquidity; /v1/dex/security/detail is what keeps a
fee-on-transfer token from being scored as extraction; /v4/dex/pairs/quotes/latest is what
tells a reader how much of the day 800 prints covered; /v4/dex/spot-pairs/latest is what
makes the hero a rule instead of a pick.
Remove CoinMarketCap and you would need a multi-chain swap indexer with maker attribution, a per-DEX pool registry, a fee-on-transfer simulator, a second aggregator for pair counts and an explorer map β five systems β to recompute what eight keyless pages return.
Where the API got in the way is in FEEDBACK.md: eight dated, evidenced
findings for the CMC product team, from the missing CORS header to q being rounded on
Uniswap v4 rows β and what is genuinely excellent and worth protecting.
No wallet needed anywhere β every page is a read-only call. Nothing signs, nothing is submitted on chain; the product reads prints that already landed.
| Route | Serves |
|---|---|
| middleman.edycu.dev | the table, the route line, the raw rows, the census strip, the receipt, the paste box β rendered from the committed capture, JavaScript-off safe |
| /judge | one page for one reader: the claim, the 30-second path, the receipt block, the real reproduce command, the limitations β no key, no cookie, no session |
| /evidence | every request behind every receipt: URL, HTTP status, UTC, sha256 of the body, credits |
| /pitch | the pitch deck β 12 slides, arrow keys, P for speaker notes, Cmd+P for a PDF; every number a slot from the same receipts |
/api/swaps?platform=&address= |
the identical keyless CMC URL with the one header CMC omits (Access-Control-Allow-Origin) and a 60 s CDN cache β api/swaps.js, 58 lines, holds no secret and can reach no other host |
| /api/health | the server clock, the receipts' capture time, the census totals; no upstream call |
The four pages are generated, never hand-edited: scripts/render_site.py renders them
from docs/proof/*.json through {{slot}} templates, aborts if any slot is unfilled, and
make check fails if the committed HTML is not what the receipts render. node scripts/serve.js
serves the same routes from a fresh clone on port 8101.
middleman.edycu.dev is the canonical host β a custom domain on
the same Vercel project, HTTPS enforced (http:// answers 308); middleman-cmc.vercel.app is
the project's own alias and serves the identical build. On either host the paste box calls
/api/swaps same-origin; served from anywhere else (a file server, a mirror) it reaches the
canonical host by absolute URL (API_BASE in site/middleman.js).
| Measurement | Value |
|---|---|
| Live run wall clock | 8.6 s β 800 prints of the hero token, 11 calls, clean path Β· 11.0 s and 11.6 s on the third and fourth runs |
| Credits used | 0 β keyless, with every CMC variable explicitly unset, on all four receipts |
| Tests | 257 β 251 offline, 6 live; each regression named for the defect it pins |
| Property-based verification of the join | 1,000 generated blocks, 0 violations of middlemen()'s own definition |
| Parity | the browser engine (site/middleman.js) vs the Python engine, every pool row of every committed tape |
| Permission boundary | the one deployed function proven to reach exactly one keyless URL and never forward a caller's credential β tests/test_proxy_boundary.py |
| Re-derivation | scripts/verify_tape.py β every published number from its tape, offline; make check fails on drift |
| Drift gates | render_site.py --check (pages = receipts) Β· check_submission_readiness.py (no placeholder, no stale count on any judge-facing surface) |
| Live contract | tests/test_live.py asserts the real row shape: fields present, h/lgid parse as ints, a1/a0 computable, t0a is the queried token |
| Branch coverage of the engine | 97.6 % of middleman/, gated at 95 % β make test-coverage |
| Benchmarks | engine p50 3.3 ms / 800 prints (p95 3.7 ms, n=200) Β· one keyless page p50 1.5 s, p95 16.4 s (n=8) β one iteration sat through the throttle backoff |
The 1,000 is the number worth reading. Coverage says we ran the lines we wrote. The property
test says that across 1,000 generated blocks middlemen() never violated its own definition:
every print is a leg or organic and never both; every round-trip is the same wallet's next
print on the other side at a matched size; every sandwich has one to four victims who all
printed in the first leg's direction and are still in the organic sample; and the attacker
came out ahead.
pytest tests/test_property.py --hypothesis-show-statistics # β 1000 passing, 0 failing| Control | Why it is load-bearing | Test |
|---|---|---|
| A same-wallet return that extracted nothing is a round-trip, not a sandwich | the first cut scored two wash bots on BSC as four sandwiches β the retraction above | tests/test_detect.py:185 |
| The same wallet on both sides in two different blocks is not a round-trip | "between" is a block-level fact; a timestamp heuristic would invent middlemen | tests/test_detect.py:90 |
| A front-run that came out behind is a round-trip, not a sandwich | an extraction with a negative take is not extraction | tests/test_detect.py:242 |
| Block and log index sort as integers, not as the strings they arrive as | "9" > "10" as strings β the join order would be wrong on every page boundary |
tests/test_detect.py:15 |
The price is a1 / a0, never the feed's rounded q |
q is rounded to two significant figures, or zero, on Uniswap v4 rows (FEEDBACK.md Β§4) |
tests/test_cost.py:13 |
The cursor is read from the envelope's lastId, not from the last swap |
a cursor off the last row re-fetched the same 100 prints forever | tests/test_tape.py:78 |
| A 500 is the same throttle wearing a different status | the anonymous tier reports its limit as 500 as often as 429 (FEEDBACK.md Β§2) | tests/test_tape.py:141 |
| With every key variable unset, no credential header is sent | the keyless claim is the whole reproducibility story | tests/test_tape.py:228 |
seed.py refuses to record a keyed receipt |
a keyed run can never pass as one of the published keyless receipts | tests/test_scripts.py:217 |
| A tampered receipt is named by the offline re-derivation | the tapes are the raw material; a number edited by hand fails make check |
tests/test_scripts.py:227 |
| The proxy can only ever reach one keyless CoinMarketCap URL | a public function on a shared IP must not be an open proxy | tests/test_proxy_boundary.py:69 |
| A caller's key, token, cookie or forwarded-host is never forwarded upstream | the deployment holds no secret and will not carry yours | tests/test_proxy_boundary.py:94 |
security/detail taxes are read from the list-wrapped, camel-cased block they actually arrive in |
a misread tax would score a fee-on-transfer token as extraction | tests/test_enrich.py:146 |
| When no pool qualifies, the rule is stated, not widened | a routing rule that loosens itself to produce an answer is not a rule | tests/test_recommend.py:51 |
| An unfilled template slot stops the render rather than shipping the braces | no placeholder can reach a page a judge reads | tests/test_render_site.py:23 |
| Every surface publishes the test count the suite has | "251 tests" on a README that has 257 is the cheapest way to look careless | tests/test_published_counts.py:16 |
/judge answers 200 with the claim to a client carrying no credentials |
a judge page that breaks on submission day is worse than none | tests/test_judge_surface.py:89 |
| The join never violates its own definition, across 1,000 generated blocks | the property test, not the examples, is what makes the shapes above a definition | tests/test_property.py:31 |
- The window is the last 800 prints, not 24 hours β 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.
- 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). Filed as FEEDBACK.md Β§6.
- Quote-to-fill is a realised, fee-inclusive print-to-print move β p90 sits near 60 bps on every busy 0.30 % pool because consecutive opposite-side prints straddle the fee twice. It is used comparatively, across pools of one token in one window, where the bias is shared.
- 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.
- The anonymous tier throttles per IP, reports it as 500 as often as 429, and sends no
Retry-After. The tool backs off 15 / 30 / 60 s and exits 75 when the quota is spent. Filed as FEEDBACK.md Β§2. - The deployed paste box shares one IP across every visitor. The page renders from committed receipts, the proxy caches 60 s, and the CLI is the primary live path.
- Python 3.11 or newer. That is the entire list.
- No API key, no account, no
pip install. The engine and the CLI are stdlib-only.
git clone https://github.com/edycutjong/middleman.git
cd middleman
python3 scripts/middleman.pyFor judges: there is no account to create and no credential to configure β the judged path is keyless by design. Start at JUDGE.md or /judge.
If CoinMarketCap's anonymous tier is throttling your IP, the script backs off (15 s, 30 s, 60 s) and, if the quota is exhausted, exits 75 with a message that says so rather than printing a number. Optional escape hatch: a free key from coinmarketcap.com/api exported as
CMC_API_KEYmoves the identical calls to the keyed host. Never required β the default path is keyless, a keyed run announces itself on its first line, and every receipt here was taken with no key set.
python3 scripts/middleman.py # the hero by rule, live, keyless
python3 scripts/middleman.py --address 0xbd965230588eaa536de6aa45e8ebbc01638535e0 --symbol MOTO --pages 8 --json moto.json
python3 scripts/middleman.py --address 0x⦠--symbol X --platform bsc # any token; ethereum | bsc | solana
python3 scripts/middleman.py --watchlist # SHIB Β· PEPE Β· UNIThe library is importable β six pure functions, stdlib only:
from middleman import tape, detect, cost, recommend
prints, meta = tape.pull("ethereum", "0xbd965230588eaa536de6aa45e8ebbc01638535e0", pages=8)
for key, rows in detect.group(prints).items():
rt, sw, organic = detect.middlemen(rows)
row = cost.pool_row(key, rows, rt, sw, organic)make setup # dev deps only: pytest, pytest-cov, ruff, mypy, hypothesis, pip-audit
make lint # ruff check + format check
make typecheck # mypy over middleman/, scripts/ and tests/
make test # 251 offline tests, no internet
make test-coverage # the same, branch coverage of the engine gated at 95%
make test-live # 6 tests against the real CoinMarketCap contract, keyless
make demo # the judged capability, live, no key
make verify # every committed receipt re-derived from its tape, offline
make bench # deterministic p50/p95 over the committed tape β a replay, not the product
make bench-live # p50/p95 over the real keyless fetch
make site # re-render /, /evidence, /judge and /pitch from docs/proof/*.json
make serve # site/ + api/ on http://localhost:8101, the way Vercel routes them
make audit # pip-audit + gitleaks over the full history
make check # refuse to ship a placeholder, a stale count, or a page that drifted
make ci # lint + typecheck + test-coverage + audit + check| Layer | Tool | Status |
|---|---|---|
| Code quality | ruff (check + format) Β· mypy | β |
| Unit testing | pytest, 251 offline tests Β· engine + scripts branch coverage 100 %, gated at 100 % | β |
| Property testing | hypothesis, 1,000 generated blocks | β |
| Live contract testing | pytest -m live against real CMC, keyless |
β |
| Permission boundary | api/swaps.js driven under node with fetch stubbed |
β |
| Judge surface | /judge served and probed with no credentials β locally in the suite, in CI over HTTP |
β |
| Security (SAST) | CodeQL β Python and JavaScript | β |
| Security (SCA) | Dependabot + pip-audit | β |
| Secret scanning | gitleaks, full history, on every push | β |
| Release automation | semver derived from Angular-convention commits | β |
Six stages on every push: lint + mypy and the tests on Python 3.11 / 3.12 / 3.13 β pip-audit
and the three receipt gates β the deterministic benchmark (a replay, labelled as such) β the
judged capability, live and keyless (exit 75 means CMC throttled the shared runner, not that
the product failed) β /judge, /, /evidence and /api/health probed over HTTP with no
credentials β the deploy gate on main, then the gated production deploy to Vercel (vercel build +
vercel deploy --prebuilt --prod, only after every stage above is green). It is keyless, so it runs on forks and PRs too; if
CMC changes the contract it breaks in CI rather than in front of a judge.
middleman/
βββ middleman/ the engine β six stdlib modules
β βββ tape.py the fetch: keyless GET with backoff, receipts, the lastId cursor, (tx, lgid) identity
β βββ detect.py order Β· group Β· middlemen β the join
β βββ cost.py a1/a0 Β· quote-to-fill Β· percentiles Β· pool_row Β· the example block
β βββ recommend.py route Β· cap_pct
β βββ enrich.py hero_pair Β· token_pools Β· label_pools Β· security Β· pair_quotes Β· platforms
β βββ cli.py analyse Β· render Β· main β the table and the receipt
βββ scripts/
β βββ middleman.py the door a judge walks through
β βββ seed.py the hero rule per chain + the watchlist β data/ + docs/proof/ + census.json
β βββ verify_tape.py every receipt re-derived from its tape, offline; exit 1 on drift
β βββ bench.py p50/p95 of the fetch (live) and the engine (replay)
β βββ render_site.py docs/proof/*.json β site/ through slot templates; --check gates drift
β βββ check_submission_readiness.py placeholders and stale test counts on every judge-facing surface
β βββ serve.js site/ + api/ locally, the way Vercel routes them
β βββ site_templates/ index.html Β· evidence.html Β· judge.html Β· deck.html β {{slot}} templates
βββ site/ generated: / Β· /evidence Β· /judge Β· /pitch Β· middleman.js (the engine, ported)
βββ api/ swaps.js (keyless proxy) Β· health.js β the two Vercel functions
βββ tests/ 257 tests: the join, the fetch contract, the CLI, parity, the boundary, the surfaces
βββ data/ tape_<sym>.json Γ10 β rows verbatim with page hashes; nothing judged reads them
βββ docs/proof/ <sym>.json Γ10 Β· census.json Β· spike.json Β· live_run*.json Β· bench_*.json
βββ docs/METHOD.md the definitions, the invariant, the exclusions
βββ JUDGE.md Β· DEMO.md Β· ARCHITECTURE.md Β· FEEDBACK.md
βββ README.md you are here
- Per-swap join on the keyless
/v1/dex/tokens/transactionsβ round-trips, sandwiches, organic - Prices from
a1 / a0, never the feed's roundedq; order from(int(h), int(lgid)) - Pools recovered from
(en, t0a, t1a)and labelled with address, liquidity and taxes - The routing rule and the slippage cap, stated on screen with the rule and the coverage
- The hero chosen by a published rule from CMC's own ranking, on three chains
- Ten-token census with every receipt committed and re-derivable offline
- Backoff across both forms of the anonymous throttle; an exhausted quota exits 75 and explains itself
- The page, the evidence page and the judge page, generated from the receipts and gated on drift
- The engine ported to the browser, parity-tested, behind a keyless proxy whose boundary is tested
- Per-fee-tier rows on Uniswap v3 β not possible from the feed: it carries no pool address (FEEDBACK.md Β§6). Merged tiers are marked Γn instead of guessed.
- A 24 h window by default β eight keyless pages is 3β20 h depending on the token; a full day is
--pages 24on a busy pair, against a per-IP quota. The window's share of the day is printed instead of assumed. - Naming wallets β deliberately not built. A round-trip is a shape; the rows are printed and the reader decides.
| Demo video | https://youtu.be/BwikjIf19hQ β 2 min 57 s: the committed page, a real keyless run from a fresh clone at real speed, LINK pasted live, the evidence page and the census; subtitles in the upload |
| For judges | JUDGE.md Β· /judge β the claim, the 30-second path, the receipt, the real reproduce command. |
| Live page | middleman.edycu.dev β the capture beside its raw rows, a dated snapshot that says so on every number, and a paste box that runs the engine live. |
| Evidence | /evidence β every request, hashed. |
| Pitch deck | /pitch β 12 slides, arrow keys, P for notes; the frozen slide is two raw rows and one division. |
| The receipts | DEMO.md β four real transcripts, with docs/proof/ behind them. |
| Social card | docs/assets/og-image.png β the mark: two blue prints, one orange hairpin. |
| Screenshots | docs/screenshots/ β nine, of live execution: the table, the raw rows, the UNI sandwich, the paste box mid-fetch, /evidence, mobile. |
| How it differs | docs/COMPARISON.md β the six nearest entries in this hackathon's gallery, by name, and the exact boundary with each. |
The four transcripts disagree β 66.2 %, 27.5 %, 0 %, and then a different pair with 17 sandwiches β because the 800-print window and the rule's pick moved between the runs. Same rule, same engine. A number that moves with the market is the evidence it is live.
MIT β see LICENSE. Β© 2026 Edy Cu Β· @edycutjong
Built for the Build with CMC: API Hackathon, Markets and Trading Tools track. Thank you to the CoinMarketCap team for publishing the maker address and the log index on every swap, keyless β those two fields are what make "who stood between" a count instead of a guess β and our feedback on the rest of the API is in FEEDBACK.md.



