AdaBoost MEV Shield turns the live Ethereum mempool into a derived MEV-risk feed: per-transaction cliff_score, wallet-cluster attribution, and an MCP server for agents. All examples below were run against the live endpoints; secrets are redacted.
Getting Started
Access model
The public API is an open research preview. The api-server sets permissive CORS (allow_origins=["*"]) and has no auth middleware, no API-key check, and no micropayment gating in code — so read endpoints require no key or signup. (A paid / metered access tier is not wired up yet; when it lands it will be documented here with the exact header.)
ANTHROPIC_API_KEY that powers the /api/v1/chat RAG endpoint. It is never sent by clients and never required to read data.Quickstart (3 steps)
1. Access. None needed — go straight to a call.
2. First REST call. Fetch the live mempool overview:
curl -s https://api.adaboost.ai/api/v1/mempool/stats{
"alerts_last_60s": 18,
"alerts_last_5m": 18,
"buffer_size": 18,
"bot_breakdown": { "unknown": 18 },
"timestamp": 1784177067.77
}3. Connect the MCP server. Add the remote SSE endpoint to any MCP-capable client:
{
"mcpServers": {
"adaboost-mempool": {
"url": "https://mcp.adaboost.ai/sse"
}
}
}Then call a tool — e.g. get_mempool_stats. Live output from the tool:
{
"status": "LIVE",
"last_event_sec_ago": 0,
"buffer_size": 11,
"alerts_last_60s": 11,
"alerts_last_5m": 11,
"avg_gas_gwei": 22.5,
"high_risk_pool_count": 0,
"high_risk_pools": [],
"active_addresses": 4,
"bot_breakdown": { "unknown": 11 },
"wallets_tracked": 15769,
"data_source": "Ethereum mempool — PublicNode + Alchemy (~75% coverage)"
}https://mcp.adaboost.ai/sse is live (SSE transport). If you're on the same host as the server, you can also run it over stdio via Docker:{
"mcpServers": {
"adaboost-mempool": {
"command": "docker",
"args": ["exec", "-i", "-e", "MCP_TRANSPORT=stdio", "mev-mcp", "python", "mempool_mcp_server.py"]
}
}
}Base URLs & rate limits
| param | type / default | description |
|---|---|---|
| REST API | https://api.adaboost.ai | FastAPI api-server (topology). CORS open. |
| MCP (SSE) | https://mcp.adaboost.ai/sse | FastMCP server — 8 tools. |
| Web app | https://adaboost.ai | Next.js dashboard + same-origin /api/* proxy routes. |
Concepts
Source of truth: eth-mempool-capture/CLAUDE.md and README.md.
cliff_score
A 0–1 MEV-risk score per pending transaction. It is a fixed-weight heuristic (probabilistic calibration is deferred), combining a reduced, defensible core:
cliff_score = 0.40 × lob_cliff (liquidity cliff from LOB bands)
+ 0.30 × order_imbalance (OFI — order-flow imbalance)
+ 0.20 × gas_z_norm (EIP-1559 gas Z-score)
+ 0.10 × nonce_signal (RBF / gap / spam)Example: a routine V2 swap scores 0.30; the busiest pool in the last window peaked at cliff_peak 0.55 (from /mempool/pools/ranked). An alert is published when cliff_score ≥ CLIFF_THRESHOLD.
lob_cliff
The dominant term (0.40). A liquidity cliff is a tick range where Uniswap V3 concentrated liquidity drops off sharply — crossing it causes outsized slippage. It is read from materialized uniswap.lob_bands (per-pool, per-block band severities) via a Vertica join, keyed on the swap's resolved pool.
Order Imbalance (OFI)
The 0.30 term. This is order-flow imbalance — |buy_frac − 0.5| × 2 over a rolling window (0 = balanced, 1 = extreme one-sided). It is NOT VPIN: the earlier "VPIN" label was misleading and has been removed everywhere. In alerts the field is ofiImbalance (0 = all sell, 1 = all buy).
gas z-score
The 0.20 term. A rolling Z-score of effective_gas_price_gwei (EIP-1559), normalized 0–1 and capped at 3σ — a spike indicates a searcher bidding aggressively for inclusion.
nonce / RBF / spam signals
The 0.10 term, from a per-sender nonce tracker: rbf (same sender+nonce re-sent with higher gas), nonceGap (missing pending nonce — arb staging), and nonceSpam (many pending nonces — eviction flooding). Ethereum has no native replace-by-fee flag like Bitcoin; its RBF-equivalent is same-nonce replacement, which is exactly what these signals detect.
MEV categories & feed hygiene
Each tx is classified by its 4-byte selector into a mevCategory. The MEV feed carries only real MEV categories:
| param | type / default | description |
|---|---|---|
| V3_SWAP / V2_SWAP / V3_MULTIHOP | in feed ✅ | Uniswap V2/V3 swaps |
| UNIV_ROUTER / V3_ROUTER2 / 1INCH / 1INCH_V5 | in feed ✅ | Router / aggregator swaps |
| V3_MINT / V3_BURN / V3_COLLECT | in feed ✅ | LP add / remove / collect (JIT) |
| ERC20 | filtered ❌ | token transfer / approve — no swap |
| neutral | filtered ❌ | selector not in the DEX map — often a plain ETH transfer |
| unknown | filtered ❌ | unrecognized call |
neutral/ERC20/unknown are non-MEV noise and are dropped before the feed and the wallet graph (one filter cleans both). Extreme-spam floods go to a separate mev-spam channel, never the MEV feed.
Bot families / wallet clusters
Wallets that co-appear on the same pools within a short window are linked in a Neo4j graph; Louvain community detection groups them into operator clusters. Each cluster gets a heuristic bot_family: arb_searcher, jit_liquidity, sandwich, spam_bot, or unknown. Live example: 20 active clusters, families {arb_searcher, jit_liquidity, unknown}.
Coverage caveat
Capture currently runs on public nodes only (PublicNode + Alchemy), which sees roughly 70–85% of the public mempool. Premium sources (bloXroute, Infura) are not configured, so private order flow (Flashbots & other private relays) is invisible — those bundles never hit the public mempool. Treat scores as a lower bound on activity.
Shelved signals
Two signals ship in the code but are experimental — currently disabled behind flags and hidden from the UI, because they are non-functional without data they don't yet have:
| param | type / default | description |
|---|---|---|
| Kyle's λ | ENABLE_KYLE_LAMBDA=false | Needs signed ERC20 trade sizes we don't decode yet → reads ~0 everywhere. Re-enable when ERC20 amount decoding lands. |
| Hawkes intensity | ENABLE_HAWKES=false | Needs 48h of per-pool history it only thinly has. Re-enable after real history + a passing backtest. |
When off, the worker emits kyleLambda, estimatedImpact, hawkesIntensity, hawkesPrediction as null (see the live alert in the API reference). The classes are retained, not deleted.
Contrast with mempool.space
mempool.space is descriptive telemetry read straight from a Bitcoin node (getmempoolinfo / getrawmempool + light tx-type/RBF heuristics) — it reports what the node already knows. AdaBoost is derived Ethereum MEV-risk scoring (cliff_score = OFI × LOB-depth × gas-z + nonce) computed from observations the node does not hand you. That is why feed hygiene and coverage matter here in a way they don't for a fee explorer.
MCP Tools
Source: eth-mempool-capture/app/mempool_mcp_server.py (FastMCP). Endpoint: https://mcp.adaboost.ai/sse. Connect with the config in Quickstart. Eight tools read the live Redis alert buffer + Neo4j wallet graph. All responses below are live captures.
get_hawkes_intensity is experimental — currently disabled in spirit (Hawkes is shelved); it still computes a crude self-contained intensity from buffer timestamps and should not be relied on.get_wallet_reputation
Is this address a known MEV bot? Queries Neo4j + the live buffer.
| param | type / default | description |
|---|---|---|
| address | string | Ethereum wallet address (0x…, 42 chars) |
{
"address": "0xbcde93bf363ad3f650289862539fcd325878a949",
"bot_type": "unknown",
"confidence": 0.0,
"risk_level": "LOW",
"cluster_id": null,
"cluster_size": 74,
"tx_count": 56,
"last_seen_sec": null,
"summary": "No bot signals for 0xbcde93bf…"
}get_pool_risk
Aggregate MEV risk for a pool: cliff, OFI, active bots, recommended route.
| param | type / default | description |
|---|---|---|
| pool_address | string | Uniswap pool/router address (0x…) |
{
"pool": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"alert_count": 12,
"cliff_peak": 0.55,
"cliff_avg": 0.34,
"risk_level": "LOW",
"ofi_imbalance": 1.0,
"order_flow": "BUY",
"hawkes_intensity": 0.0, // shelved → 0
"next_attack_sec": null, // shelved
"active_bots": 0,
"recommended_route": "public",
"reason": "public submission safe"
}get_active_bot_clusters
Operator clusters from the Neo4j graph (Louvain over CO_APPEARED edges).
| param | type / default | description |
|---|---|---|
| minutes | int = 60 | Look-back window |
{
"window_minutes": 60,
"total_clusters": 20,
"total_wallets": 3021,
"by_family": { "arb_searcher": 860, "unknown": 1954, "jit_liquidity": 207 },
"clusters": [
{ "cluster_id": "3939", "bot_family": "arb_searcher", "size": 650, "active_members": 171 },
{ "cluster_id": "1830", "bot_family": "unknown", "size": 1130, "active_members": 184 }
],
"summary": "20 clusters, 3021 wallets — 860 arb searcher wallets, 207 jit liquidity wallets"
}get_recent_alerts
Last N scored alerts from the live buffer, with filters.
| param | type / default | description |
|---|---|---|
| limit | int = 10 | Max alerts (capped at 50) |
| min_cliff | float = 0.3 | Minimum cliff score |
| pool_address | string? = null | Filter to a pool |
| bot_type | string? = null | Filter by bot type |
{
"count": 1,
"buffer": 11,
"alerts": [{
"tx": "0x1a1c713d053a2e4ccf…",
"pool": "0x7a250d56…",
"category": "V2_SWAP",
"cliff": 0.3,
"risk": "low",
"bot_type": "unknown",
"confidence": 0.0,
"cluster": null,
"hawkes": null, // shelved
"impact_pct": null, // shelved (Kyle)
"gas_gwei": 0.001,
"value_usd": 10.5,
"age_sec": 0
}],
"stats": { "avg_cliff": 0.3, "high_risk": 0, "bots_detected": 0 }
}get_pool_ofi
Current Order-Flow Imbalance for a pool (renamed from the old get_pool_vpin).
| param | type / default | description |
|---|---|---|
| pool_address | string | Pool address (0x…) |
| window_seconds | int = 30 | Rolling window |
{
"pool": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"window_sec": 30,
"ofi_imbalance": 1.0,
"buy_pct": "100.0%",
"sell_pct": "0.0%",
"trade_count": 11,
"volume_eth": 0.4068,
"signal": "STRONG BUY — sandwich bots likely"
}get_pool_vpin (returning vpin_imbalance) until the OFI rename ships. Source and this doc use get_pool_ofi / ofi_imbalance.get_hawkes_intensity experimental — disabled
Attack-cluster intensity + time-to-next estimate. Hawkes is shelved; do not rely on this.
| param | type / default | description |
|---|---|---|
| pool_address | string | Pool address (0x…) |
{
"pool": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"intensity": 1.0,
"level": "CRITICAL",
"prediction_sec": 0.2,
"obs_count": 11,
"narrative": "CRITICAL — cluster active, next attack ~0s"
}check_transaction_safety
Private-vs-public relay recommendation for a swap. Use before swaps > $1,000.
| param | type / default | description |
|---|---|---|
| pool_address | string | Target pool (0x…) |
| value_eth | float | Swap size in ETH |
| from_address | string? = null | Sender — checks if known bot |
{
"pool": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"value_eth": 5.0,
"route": "public",
"confidence": 0.07,
"recommendation": "✅ PUBLIC SAFE (7% conf) — risk=0.42 — public submission safe",
"cliff_score": 0.55,
"hawkes_intensity": 0,
"ofi_imbalance": 1.0,
"active_bots": 0,
"from_is_known_bot": false,
"estimated_impact_pct": 0.89,
"estimated_mev_loss_usd": 110.62,
"private_relay": "https://protect.flashbots.net"
}get_mempool_stats
Overall mempool activity — alert rates, gas, active bots, coverage. No params.
{
"status": "LIVE",
"last_event_sec_ago": 0,
"buffer_size": 11,
"alerts_last_60s": 11,
"alerts_last_5m": 11,
"avg_gas_gwei": 22.5,
"high_risk_pool_count": 0,
"high_risk_pools": [],
"active_addresses": 4,
"bot_breakdown": { "unknown": 11 },
"wallets_tracked": 15769,
"data_source": "Ethereum mempool — PublicNode + Alchemy (~75% coverage)"
}API Reference
REST + SSE from the api-server (api.adaboost.ai) and the web app's own same-origin routes. Every example below was run live; secrets redacted (there are none in these payloads).
Mempool endpoints
| param | type / default | description |
|---|---|---|
| limit | int = 50 (1–200) | Max alerts returned |
| min_cliff | float = 0.3 | Minimum cliff score |
| pool | string? = null | Filter to a pool address |
curl -s "https://api.adaboost.ai/api/v1/mempool/recent?limit=1&min_cliff=0.3"{
"count": 1,
"buffer_size": 33,
"alerts": [{
"id": "0x518397835e1c048603b1ecc7710ce62306f9013badf969615b5bebaf0fd5d0c9",
"type": "swap",
"pool": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"value": 12.5,
"gasPrice": 0.01,
"size": 0.005,
"mevRisk": "low",
"cliffScore": 0.3,
"mevCategory": "V2_SWAP",
"timestamp": 1784177127876,
"from": "0xbcde93bf363ad3f650289862539fcd325878a949",
"source": "txpool_poll",
"nonceSignal": 0.0, "rbf": false, "nonceGap": false, "nonceSpam": false,
"is_buy": true, "decode_method": "selector_name",
"ofiImbalance": 1.0, "ofi_imbalance": 1.0,
"hawkesIntensity": null, "hawkesPrediction": null,
"kyleLambda": null, "estimatedImpact": null,
"fpBotType": "unknown", "fpConfidence": 0.0, "fpClusterId": null
}]
}Server-Sent Events stream of scored alerts. Empty data: {} lines are keep-alive heartbeats (ignored by EventSource); real events carry the same object shape as /recent.
| param | type / default | description |
|---|---|---|
| min_cliff | float = 0.3 | Minimum cliff score to stream |
curl -sN "https://api.adaboost.ai/api/v1/mempool/alerts?min_cliff=0.3"data: {}
data: {"id": "0x63d5c7…", "type": "swap", "pool": "0x7a250d56…",
"cliffScore": 0.3, "mevCategory": "V2_SWAP",
"ofiImbalance": 1.0, "hawkesIntensity": null, "kyleLambda": null,
"fpBotType": "unknown", "timestamp": 1784177138493}$ curl -s https://api.adaboost.ai/api/v1/mempool/stats
{
"alerts_last_60s": 18,
"alerts_last_5m": 18,
"buffer_size": 18,
"bot_breakdown": { "unknown": 18 },
"timestamp": 1784177067.77
}| param | type / default | description |
|---|---|---|
| limit | int = 10 | Number of pools by cliff peak |
{
"pools": [
{ "pool": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", "pair": "", "alert_count": 26, "cliff_peak": 0.55 },
{ "pool": "0xe592427a0aece92de3edee1f18e0157c05861564", "pair": "", "alert_count": 4, "cliff_peak": 0.3 }
]
}Community / reputation
| param | type / default | description |
|---|---|---|
| minutes | int = 60 | Look-back window for active wallets |
{
"window_minutes": 60,
"active_communities": 20,
"communities": [
{ "cluster_id": "5443", "bot_family": "arb_searcher", "size": 352,
"active_members": 158, "last_activity": 1784176246.554 }
]
}$ curl -s https://api.adaboost.ai/api/v1/community/5443
{
"cluster_id": "5443",
"bot_family": "arb_searcher",
"size": 352,
"members": [
{ "address": "0xe7708e0562622f168ecb8b9c652018e276893236",
"bot_type": "unknown", "confidence": 0.0, "tx_count": 54 }
],
"active_pools": [],
"avg_confidence": 0.0
}{"detail":"Community <id> not found"} if the cluster has no members in the graph.{
"address": "0xe5a665b3c6ca4a3ae020792fd9631dccfb51ba6b",
"bot_type": "unknown", "confidence": 0.0,
"basis": "mempool_signals", "confirmed_onchain": false,
"signals": [], "tx_count": 0, "unique_pools": 0,
"active_pools": [], "cluster_id": null, "cluster_size": null,
"data_window_hours": 168
}unknown. For real reputation use the MCP get_wallet_reputation tool (queries Neo4j + buffer).Pools & LOB
{
"pools": [
{ "pool_address": "0x109830a1aaad605bbf02a9dfa7b0b92ec2fb7daa",
"token0": "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0",
"token1": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"fee": 100, "tick_spacing": 1 }
]
}{
"pool_address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
"block_number": 25542926,
"current_tick": 200731,
"active_liquidity": "14138374530141853205",
"sqrt_price_x96": "1809240231296221848372330688219320",
"ask_bands": [ { "band_no": 1, "band_start_tick": 200... } ],
"bid_bands": [ ... ]
}Web app routes (same-origin proxy)
The Next.js app exposes convenience routes under https://adaboost.ai/api/* that wrap the backend and add a { success, data, timestamp } envelope.
{
"success": true,
"data": [
{ "pool_address": "0x11b815efb8f581194ae79006d24e0d814b7697f6",
"token0_symbol": "WETH", "token1_symbol": "USDT", "fee_tier": 500,
"current_tick": -195840, "current_price": 2512.45,
"total_value_locked_usd": 125000000 }
],
"timestamp": 1784177187000
}{
"success": true,
"data": { "pool": { "pool_address": "0x11b815efb8f581194ae79006d24e0d814b7697f6",
"token0_symbol": "WETH", "token1_symbol": "USDT",
"fee_tier": 500, "current_tick": -195840,
"current_price": 2513.80, "total_value_locked_usd": 129856750 } },
"timestamp": 1784177187000
}{
"success": false,
"error": "Trade impact calculation is unavailable (no verified slippage engine wired up).",
"timestamp": 1784177187429
}/api/impact returns 503 by design: there is no verified slippage engine wired up, so the route refuses rather than surface wrong numbers.Proxies POST /api/v1/chat (RAG over the datasets). Body: { "query": "..." }. Availability depends on a server-side ANTHROPIC_API_KEY; check GET /api/v1/chat/status.
curl -s -X POST https://adaboost.ai/api/chat \
-H 'content-type: application/json' \
-d '{"query":"which pools have the steepest liquidity cliffs?"}'{ answer, sources?, ... } — labeled unverified rather than fabricated.Same-origin SSE proxy the dashboard uses; forwards /api/v1/mempool/alerts from the backend. Same event shape as the backend stream above.
Datasets & Schema
Column-level schema and file layout for the downloadable datasets (LOB Bands, Cliff Events, Trade Impact, LLM Task Set). Sample downloads are on the Datasets page; pricing is on the Pricing page.
Column schema
Every column below is read directly out of the sample file you can download from the home page or Samples — names, types and counts are generated from the shipped Parquet, not written by hand. Big integers exceed int64 and ship as exact decimal strings; cast them to decimal/BigInt, not float.
| # | Column | Type | Description |
|---|---|---|---|
| 1 | pool_address | string | Uniswap V3 pool address |
| 2 | pool_pair | string | Token pair, token0/token1 order (WETH/USDT) |
| 3 | fee_tier_bps | int64 | Pool fee tier in basis points (30 = 0.30%) |
| 4 | tick_spacing | int64 | Pool tick spacing |
| 5 | block_number | int64 | Ethereum block number — the chain reconciliation key |
| 6 | materialized_at_utc | timestamp | When our pipeline wrote the row. NOT the block timestamp — use block_number for chain time |
| 7 | side | string | ASK (price moves up, buy token0) or BID (price moves down, sell token0) |
| 8 | band_no | int64 | Band index, 1 = nearest the current price, increasing outward |
| 9 | band_start_tick | int64 | Band lower bound (half-open [start, end); ticks descend on BID) |
| 10 | band_end_tick | int64 | Band upper bound — also where this row’s cliff sits |
| 11 | band_start_price_usdt_per_weth | float64 | 1.0001^band_start_tick × 10^(dec0-dec1) |
| 12 | band_end_price_usdt_per_weth | float64 | 1.0001^band_end_tick × 10^(dec0-dec1) |
| 13 | band_active_liquidity | string (big integer) | Uniswap virtual liquidity L in the band. Exceeds int64 — exact decimal string |
| 14 | band_size_weth | float64 | WETH traded to cross this band end to end |
| 15 | band_size_usdt | float64 | USDT traded to cross this band end to end |
| 16 | cumulative_weth | float64 | Running WETH total from the current price through this band — the depth curve |
| 17 | cumulative_usdt | float64 | Running USDT total from the current price through this band — the depth curve |
| 18 | band_size_token0_raw | string (big integer) | band_size_weth in wei. Exact decimal string |
| 19 | band_size_token1_raw | string | band_size_usdt in 6dp base units. Exact decimal string |
| 20 | cumulative_size_token0_raw | string (big integer) | cumulative_weth in wei. Exact decimal string |
| 21 | cumulative_size_token1_raw | string | cumulative_usdt in 6dp base units. Exact decimal string |
| 22 | is_liquidity_cliff | bool | True when cliff_severity ≥ 0.20 at this band’s END tick. A trade crosses this cliff only by filling PAST band_end_tick |
| 23 | cliff_severity | float64 | 1 − active_liq_after / active_liq_before. 0.35 = 35% of active liquidity vanishes at band_end_tick |
| 24 | active_liq_before | string (big integer) | This band’s own active liquidity. Exact decimal string |
| 25 | active_liq_after | string (big integer) | The NEXT band’s active liquidity. Exact decimal string |
| 26 | drop_ratio | float64 | active_liq_after / active_liq_before — complement of cliff_severity |
| 27 | current_tick | int64 | Pool tick at this block |
| 28 | mid_price_usdt_per_weth | float64 | Spot price from the chain-true sqrtPriceX96, not from the tick |
| 29 | distance_from_mid_ticks | int64 | |band_start_tick − current_tick| |
| 30 | distance_from_mid_pct | float64 | Signed price distance from mid: positive on ASK, negative on BID |
| 31 | side_depth_bands | int64 | Total bands on this side at this block (166–300 in this sample) |
| 32 | side_truncated_at_ingest | bool | True when this side hit the 300-band ingestion cap, so its far tail is cut off |
All 32 columns of lob_bands_sample.parquet are listed — nothing omitted. spread_bps is deliberately excluded from the datasets (known 100× units bug) and is not shipped in any file.
Files & checksums
The free sample bundles, exactly as shipped. Sizes and checksums are computed from the real files at build time — not illustrative figures.
These are the real SHA-256 hashes of the files served from this site. Download the samples into one directory together with SHA256SUMS and the command above will verify them — it is keyed by the filenames your browser saves. (SHA256SUMS covers every sample file; it cannot list its own hash.)