For judges
Everything you need in one page. No setup, no key, no account. Built on the CoinMarketCap DEX API for the Build with CMC: API Hackathon — AI Agents & Automation track.
The claim
Every “LP removed” alert stops at the row that fired it. Forwarding Address follows the wallet across every pool of the token — on this chain and on the asset’s other EVM chains — and rewrites the alert: Rebalance, Migration, Partial, or a real Exit. A plain API call shows the removal; only the maker-keyed cross-pool join and the adjudication turn “liquidity gone” into “liquidity moved to Ring Exchange (Ethereum), 99.8% recovered”.
The 30-second path
The judged capability is one stdlib Python file and the endpoint it calls is keyless:
git clone https://github.com/edycutjong/forwarding.git && cd forwarding
python3 scripts/forwarding.py investigate --platform ethereum --address 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984
Expected: the trace of every call as it happens (endpoint, status, ms), then a red ● LP REMOVED line, then the verdict — for UNI, as of 2026-09-18 23:16:31 UTC:
◆ MIGRATION · severity amber
99.8% recovered — $21,287,255 of $21,330,275
+$21,287,255 into Ring Exchange (Ethereum) · UNI/WETH (ethereum) 2 min 12 s later · pool now holds $102,239,395
Then the agent surface, in one line:
claude mcp add forwarding -- python3 $PWD/scripts/mcp_server.py
and ask Claude Code “where did the UNI liquidity go?” — it calls where_did_liquidity_go and the tool answers with the verdict and the raw rows. Hand it a just-in-time transaction and it refuses. Real session transcript: docs/proof/mcp_session.md.
- Nothing else is required. No
pip install, no.env, no API key, no signup. One thing is not a bug: the endpoint is CoinMarketCap’s shared anonymous tier, rate-limited per IP. A clean run is 27.0 s (p50 of 5 live runs, 11 calls at 2 s spacing). If the tier is throttling, the script backs off (15 s, 30 s, 60 s) and says so; if your IP’s quota is exhausted it exits 75 with the way through — wait a minute, or export a free key asCMC_API_KEY(an escape hatch, never a requirement; every receipt here was taken with no key set). - Watch the trace. Every call is printed as it happens — that is the event’s “visible evidence of a real API call” on the judged surface, and the same calls are committed with their verbatim responses in
docs/proof/. - Read the verdict. The two rows it was computed from are printed underneath, with the division written out. Change the token, or hand it
--txnand--maker, and it follows that wallet instead.
Full annotated transcript with the receipt: DEMO.md.
Receipt — live run, 2026-09-18T23:16:31Z
| The removal | −$21,330,275 out of Ring Exchange (Ethereum) · UNI/WBTC, 2026-09-02 03:59:11 UTC, an unknown share of the pool, maker 0x4f0aa5900b8292273b2f9a178d5468f8048bb9a9 |
| The verdict | MIGRATION · 99.8% recovered — +$21,287,255 into Ring Exchange (Ethereum) · UNI/WETH, 2 min 12 s later, the same 1,962,475.54 UNI to ten decimals |
| Re-derive it | 21,287,254.93 ÷ 21,330,274.56 = 0.9980 — two tu fields from /v1/dex/liquidity-change/list?maker= |
| API calls | 11, all HTTP 200, 73.0 s wall clock |
| Credits used | 0 — every endpoint is on the keyless /public-api surface |
| Credentials | none; run with every CMC env var unset |
| Base rate | 335 removals ≥ $100,000 followed one wallet at a time: rebalance 194 · migration 19 · partial 10 · exit 112 — 67% were the same wallet putting liquidity back within 6 h |
| Tests | 126 offline tests + 10 live · 2,000 property cases, 0 failing |
| Latency | adjudicate replay p50 0.004 ms (n=1000) · live investigation p50 27.0 s, p95 28.0 s (n=5) |
| Raw receipts | hero.json · exit.json · rebalance.json · jit.json · base_rate.json · live_run.json · bench_live.json · bench_replay.json · spike_maker.json |
The other three outcomes, captured by the same published rule: EXIT (LINK, 0% recovered), REBALANCE (LINK, 105% recovered), refused (DAI JIT pair, $229,834).
Reproduce
python3 scripts/forwarding.py investigate --platform ethereum --address 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 # the hero, live
python3 scripts/forwarding.py investigate --platform ethereum --address 0x514910771af9ca656af840dff83e8264ecf986ca # LINK
python3 scripts/forwarding.py watch --cycles 1 # the autonomous loop, one pass over 11 tokens
make test # 126 offline tests
make test-live # 10 tests against the real CMC contract
make verify # replay every receipt, assert I1–I6, check the page
pytest tests/test_property.py --hypothesis-show-statistics # the 2,000
CI / deterministic replay — this is not the product: make bench replays adjudicate() over the committed receipts. Nothing on the judged path reads them; investigate always runs live.
Why only CoinMarketCap
/v1/dex/liquidity-change/list returns, per liquidity event and with no key, the maker address (m), the side (tp), the USD value (tu), the venue (en/f) and the pair — and it accepts maker= as a server-side filter. That one parameter is the product: the same wallet’s adds in every other pool of the token come back in a single call. Remove CoinMarketCap and reproducing the join needs a multi-chain liquidity-event indexer, a per-DEX pool registry, a cross-chain contract registry and a pool-depth oracle — four systems to replace 6 keyless endpoints.
Honest limitations
- Pool identity is venue + pair, not pool address. Rows carry no pool contract; two fee tiers of one pair on one venue collapse into one identity.
- The window is ±6 h, walked with the cursor because
startTimeis plan-gated keyless. A wallet that comes back a day later reads as an Exit for that window — stated on the card. - A wallet is not an entity. Liquidity moved through a second wallet is not followed.
- A wallet that cycles more than once in the window shows a share over 100% — the adds are attributed to the whole window, and the card discloses the other removals.
- The anonymous tier throttles per IP. The CLI backs off and says so; the hosted page’s live box shares one IP across every visitor, so the page leads with the dated receipt.
- The base rate follows wallets on the same chain only — its EXIT count is an upper bound.
Links
| Run it | DEMO.md — annotated transcript + receipt |
| How it works | ARCHITECTURE.md — derived from the code |
| The rule | docs/SPEC.md — state machine, thresholds, invariants I1–I6 |
| API feedback for CMC | FEEDBACK.md — where the API got in the way |
| The product | scripts/forwarding.py · scripts/mcp_server.py |
| Landing page | forwarding.edycu.dev — the receipt, the flip, and a live box |