ParaMEV API
Live MEV data on Monad mainnet over plain JSON — the same data behind the dashboard: confirmed sandwich attacks with USD profit, atomic-arb candidates, extractor rankings and venue-level swap flow. CORS-open, so it works straight from a browser, a notebook, or an agent.
Overview
Base URL: https://mev.parascan.dev. All endpoints are GET, return
application/json, and send Access-Control-Allow-Origin: *.
No signup is needed for casual use.
curl -s https://mev.parascan.dev/api/summary | jq .totals
Detection methodology (how a sandwich gets confirmed, how profit becomes USD) is documented in the
public repo.
Counts are floors, never extrapolations; unpriceable sandwiches carry an explicit
reason instead of a made-up number.
Building an agent? There's a zero-dependency
MCP server
over this API — it hands Claude & co. three tools (get_pool_risk,
list_sandwiches, get_mev_summary), so an execution agent can check a
pool's toxicity before routing a swap.
Authentication
Anonymous access works for light use. For anything sustained — agents, bots, integrations — pass an API key with each request, either as a header or a query parameter:
curl -s -H "X-API-Key: pmk_your_key_here" \
"https://mev.parascan.dev/api/sandwiches?limit=200"
# or, where headers are awkward:
curl -s "https://mev.parascan.dev/api/summary?key=pmk_your_key_here"
Keys are free — open a GitHub issue with one line about what you're building and you'll get one.
Rate limits
| Access | Limit | Scope |
|---|---|---|
| anonymous | 30 requests / minute | per IP |
| with API key | 600 requests / minute | per key (independent of IP) |
Limits use a 60-second sliding window. Over the limit you get a
429 with a Retry-After: 30 header — back off and retry.
/healthz is not rate-limited.
The whole observatory in one call: cumulative totals, top extractors, most contended pools, the 50 most recent sandwiches, 30 most recent arb candidates, and 48 hourly buckets.
{
"lastBlock": 87173695,
"lastError": null,
"totals": {
"blocks": 5365131, // blocks analyzed, history + live
"swaps": 2047479, // decoded swap events
"perKind": { "v2": 61474, "v3": 513399, "v4": 714812, "kuru": 757794, "pcs3": 88012, "lfj": 31240 },
"arbCandidates": 87442,
"sandwiches": 493, // confirmed, all venues
"kuruSandwiches": 43,
"victims": 521,
"extractedUsd": 312.44, // gross, marked to victim price
"attackerGasUsd": 6.21,
"pricedSandwiches": 455
},
"attackerCount": 83,
"backfill": { "from": 82168465, "to": 87168464, "blocks": 5000000, "at": 1783820000000 },
"topAttackers": [ { "key": "0x22c6a8b7…", "sandwiches": 41, "kuru": 9, "amm": 32, "extractedUsd": 38.12, "lastBlock": 87168001 } ],
"topPools": [ /* most-traded pools */ ],
"topSandwichedPools": [ /* pools ranked by sandwich count */ ],
"recentSandwiches": [ /* 50 newest, see object shape below */ ],
"recentArbs": [ /* 30 newest */ ],
"hourly": { "2026-07-12T04": { "swaps": 14210, "sandwiches": 2, "arbs": 311 } }
}
The full sandwich history (not just the dashboard's recent window), filterable. Each row is a confirmed sandwich — see the object shape below.
| Param | Description |
|---|---|
| limit | number of rows, default 50, max 1000 (newest first) |
| pool | pool / orderbook address; for v4 either the manager address or the poolId matches |
| attacker | attacker EOA or executor bot contract address |
| venue | v2 | v3 | v4 | kuru | pcs3 | lfj |
| token | token address, native, or a symbol (e.g. WMON) — matches sandwiches on any pool containing it |
| sinceBlock | only sandwiches closing strictly after this block — the polling cursor (see below) |
| sinceTs | only sandwiches at/after this time (ms epoch or ISO date) |
| order | desc (default) or asc by closing block |
curl -s "https://mev.parascan.dev/api/sandwiches?pool=0x4538053f…&limit=20" -H "X-API-Key: pmk_…" | jq '.[0]'
Incremental polling. Pass sinceBlock (or envelope=1)
and the response becomes an envelope carrying the scan head. Feed lastBlock back as
the next sinceBlock and you never miss or re-download anything:
# first call: everything after block N (empty items is normal)
curl -s "https://mev.parascan.dev/api/sandwiches?sinceBlock=87173000"
{
"items": [ /* sandwiches with blocks[1] > 87173000 */ ],
"count": 2,
"lastBlock": 87173695, // ← pass this as sinceBlock next poll
"totalOnRecord": 493
}
Toxicity of a pool or token right now — built for execution agents deciding
slippage before a trade. Query by pool (address) or token
(address, native, or symbol); one of the two is required.
Protocol teams: the dashboard's “Watch your pool” box calls this endpoint — paste a pool or token address there for a no-code view of the same data.
curl -s "https://mev.parascan.dev/api/risk?token=WMON" | jq
{
"query": { "token": "WMON" },
"risk": "elevated", // none | low | elevated | high
"hint": "sandwiched within the last 24h — set a tight slippage limit before trading",
"lastBlock": 87173695,
"allTime": { "sandwiches": 61, "victims": 64, "extractedUsd": 48.02, "attackers": 12 },
"last24h": { "sandwiches": 2, "victims": 2, "extractedUsd": 1.71, "attackers": 1 },
"lastSandwich": { "block": 87171502, "agoBlocks": 2193, "at": 1783819771000, "venue": "v3", "pool": "0x4538…", "attacker": "0x22c6…" },
"activeBots7d": [ { "attacker": "0x22c6…", "sandwiches": 9, "lastBlock": 87171502 } ],
"pools": [ /* pools where the token was sandwiched (token queries) */ ],
"trackedPools": 17
}
Risk levels: high — a sandwich in the last hour, or ≥3 in 24h;
elevated — at least one in 24h; low — sandwiched historically,
quiet for 24h; none — never recorded. Pool queries also return
poolStats (kind, lifetime swaps and sandwiches).
| Param | Description |
|---|---|
| limit | number of rows, default 30, max 100 (newest first) |
Atomic-arb candidates: single transactions that swapped through ≥2 pools. Unlike sandwiches, these are not confirmed for profit.
[
{ "tx": "0x1bd82533…", "pools": 3, "block": 87173098, "at": 1783820646088 }
]
Watcher liveness. status is ok, starting, or
degraded (RPC trouble; lastError says what). Not rate-limited.
{ "status": "ok", "lastBlock": 87173695, "lastError": null }
Object shapes
Sandwich — one confirmed attack:
| Field | Description |
|---|---|
| venue | v2 | v3 | v4 | kuru | pcs3 (PancakeSwap v3) | lfj (LFJ Liquidity Book) — PancakeSwap v2 pools emit the canonical v2 event and land in v2 |
| via | confirmation rule: sender (same EOA front+back), bot-contract (same executor contract), txOrigin (Kuru) |
| pool | pool address; v4 uses manager:poolId; Kuru is the orderbook address |
| attacker | attacking origin address |
| bot | executor contract, when confirmed via bot-contract |
| victim / victims | first victim address / count of victim fills bracketed |
| blocks | [open, close] block numbers |
| front, victimTx, back | the three transaction hashes |
| at | timestamp (ms) — live detection time, or estimated block time for backfill |
| backfill | true when found by historical replay rather than live |
| profit | see below |
profit — pool-leg accounting, marked to the victim's own execution price:
| Field | Description |
|---|---|
| usd | gross extracted USD (can be negative — bots misfire; shown as-is), or null when unpriceable |
| gasUsd | attacker gas for front+back (exact, from receipts) |
| netUsd | usd − gasUsd |
| token, amount | the leftover leg the figure was priced from |
| priced | false ⇒ reason explains: unpriced-pair, kuru-unverifiable-book, price-sanity, implausible-usd, rpc-failed, no-swaps |
Errors
HTTP 429 — over the rate limit (Retry-After: 30)
{
"error": "rate limit exceeded",
"limit": "30/min",
"hint": "pass an API key via the X-API-Key header for a higher limit — request one at github.com/krimdev/paramev/issues"
}
Anything else non-200: 404 for unknown paths, 405 for non-GET methods.
The API never returns partial data silently — if the watcher is degraded you'll see it in
lastError on /api/summary and /healthz.