Vectorblock 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.vectorblock.io/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": {
"vectorblock-mempool": {
"url": "https://mcp.vectorblock.io/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.vectorblock.io/sse currently returns 502 (gateway down), so the remote SSE connection is unverified live. The tool output above was captured by invoking the server code directly against the live Redis feed. As a fallback you can run the server locally over stdio via Docker:{
"mcpServers": {
"vectorblock-mempool": {
"command": "docker",
"args": ["exec", "-i", "mev-mcp", "python", "mempool_mcp_server.py"]
}
}
}Base URLs & rate limits
| param | type / default | description |
|---|---|---|
| REST API | https://api.vectorblock.io | FastAPI api-server (topology). CORS open. |
| MCP (SSE) | https://mcp.vectorblock.io/sse | FastMCP server — 8 tools. Currently 502 (see note). |
| Web app | https://vectorblock.io | 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. Vectorblock 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.vectorblock.io/sse. Connect with the config in Quickstart. Eight tools read the live Redis alert buffer + Neo4j wallet graph. All responses below are live captures.
502; examples were captured by running the server code against the live feed. 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.vectorblock.io) 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.vectorblock.io/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.vectorblock.io/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.vectorblock.io/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.vectorblock.io/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://vectorblock.io/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://vectorblock.io/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.