Forwarding AddressWhere the pulled liquidity went.

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.

  1. 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 as CMC_API_KEY (an escape hatch, never a requirement; every receipt here was taken with no key set).
  2. 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/.
  3. Read the verdict. The two rows it was computed from are printed underneath, with the division written out. Change the token, or hand it --txn and --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 verdictMIGRATION · 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 it21,287,254.93 ÷ 21,330,274.56 = 0.9980 — two tu fields from /v1/dex/liquidity-change/list?maker=
API calls11, all HTTP 200, 73.0 s wall clock
Credits used0 — every endpoint is on the keyless /public-api surface
Credentialsnone; run with every CMC env var unset
Base rate335 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
Tests126 offline tests + 10 live · 2,000 property cases, 0 failing
Latencyadjudicate replay p50 0.004 ms (n=1000) · live investigation p50 27.0 s, p95 28.0 s (n=5)
Raw receiptshero.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

Links

Run itDEMO.md — annotated transcript + receipt
How it worksARCHITECTURE.md — derived from the code
The ruledocs/SPEC.md — state machine, thresholds, invariants I1–I6
API feedback for CMCFEEDBACK.md — where the API got in the way
The productscripts/forwarding.py · scripts/mcp_server.py
Landing pageforwarding-cmc.vercel.app — the receipt, the flip, and a live box