You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Real-time orderbook analytics for Polymarket prediction markets on Polygon
Overview
High-performance Substreams modules for extracting, processing, and persisting orderbook events from Polymarket's CTF Exchange and Neg Risk Exchange contracts on Polygon — across both CLOB v1 and CLOB v2. Built with foundational stores for efficient parallel execution and ready-to-use SQL and Clickhouse sinks.
CLOB v2 ready (since v0.4.0)
Polymarket migrated to CLOB v2 at the 2026-04-28 ~11:00 UTC cutover. v2 ships fresh Exchange contracts at new addresses, a redesigned OrderFilled event, a different fee model, and a new collateral wrapper (pUSD). This package indexes both contract generations side-by-side so a single output stream spans the migration without breaking existing consumers.
Protocol-determined at match time, taker only, dynamic per market via getClobMarketInfo()
Collateral (wallet)
USDC.e directly
pUSD — a 1:1-backed ERC-20 wrapper. USDC.e converts via CollateralOnramp.wrap()
Collateral (CTF level)
USDC.e
USDC.e (unchanged — pUSD is purely wallet-facing)
Builder attribution
HMAC headers on API orders
Single builderCode (bytes32) on the order, surfaced as builder on the event
EIP-712 domain version
"1"
"2" for exchange signing (L1 API auth still "1")
Open orders at cutover
—
All wiped during the maintenance window
How this package handles it
Parallel module set. Four new map modules at initialBlock: 84,902,353 (v2 deploy block) extract OrderFilled and OrdersMatched events from the two v2 Exchange contracts. The four legacy v1 modules continue at initialBlock: 57,000,000 for full historical backfill.
Unified output.map_all_order_fills merges all four fill streams (v1 CTF + v1 NegRisk + v2 CTF + v2 NegRisk) and sorts by ordinal, so downstream stores and analytics see one continuous order flow that spans the cutover with no gap.
exchange_version column. Every OrderFilledEvent and OrdersMatchedEvent carries an exchange_version field ("v1" or "v2") so you can filter, partition, or audit by generation.
v2-only fields surfaced.token_id, side_raw, builder, and metadata are first-class columns on order_fills for v2 rows (empty / 0 for v1).
Backward-compat shape. Legacy maker_asset_id / taker_asset_id fields are populated for v2 fills using the (side, tokenId) mapping (BUY: maker="0", taker=tokenId; SELL: maker=tokenId, taker="0"). Existing queries that key on these fields — including the foundational stores and Clickhouse materialized views shipped here — keep working unchanged.
Authoritative side. v2 ships the trade direction directly in the event (side enum). v0.4.0 uses this for v2 rows; v1 rows continue to use the legacy parity-based heuristic.
Fee column semantics. v2 fees are taker-only and protocol-determined at match time; the same fee field surfaces a single realized taker fee.
What stays the same
Same chain (Polygon), same Firehose source.
Same db_out SQL/Clickhouse sink path.
Same start block for v1 history (57,000,000); v2 fills appear automatically at deploy block 84,902,353.
Same module names and proto field numbers — v0.4.0 is a strict superset of v0.3.1.
Key Features
Feature
Description
CLOB v1 + v2
Indexes both legacy and new Exchange contracts in a single unified stream
Dual Exchange Support
Tracks CTF Exchange and Neg Risk Exchange on each generation
Order Fill Events
Trade execution data with price calculations and authoritative v2 side
Builder Attribution
v2 builder and metadata (bytes32) surfaced as columns
Market Analytics
Volume, trades, buy/sell ratios, average trade sizes
-- Top markets by volumeSELECT*FROM top_markets_by_volume;
-- Top traders by volumeSELECT*FROM top_traders_by_volume;
-- Recent large trades (> 1000 USDC)SELECT*FROM recent_large_trades;
-- Market activity over timeSELECTDATE(created_at) asdate,
COUNT(*) as trades,
SUM(taker_amount_filled::numeric/ 1e18) as volume
FROM order_fills
GROUP BYDATE(created_at)
ORDER BYdateDESC;
-- Top markets by 24h volumeSELECT
market_id,
sum(total_volume) as volume_24h,
sum(trades_count) as trades_24h
FROM hourly_volume
WHERE hour >= now() - INTERVAL 24 HOUR
GROUP BY market_id
ORDER BY volume_24h DESCLIMIT10;
-- Trader leaderboardSELECT
id,
total_volume,
trades_quantity,
total_fees
FROM trader_analytics FINAL
WHERE is_active =1ORDER BY total_volume DESCLIMIT100;
-- Hourly volume trendSELECT
hour,
sum(total_volume) as volume,
sum(trades_count) as trades
FROM hourly_volume
WHERE hour >= now() - INTERVAL 7 DAY
GROUP BY hour
ORDER BY hour;
Data Schema
OrderFilledEvent
Field
Type
Description
id
string
Unique event identifier
transaction_hash
string
Transaction hash
order_hash
string
Order hash
maker
string
Maker address
taker
string
Taker address
maker_asset_id
string
Maker's asset token ID (v2: derived from side+token_id for backward compat)
taker_asset_id
string
Taker's asset token ID (v2: derived from side+token_id)
maker_amount_filled
string
Amount filled for maker
taker_amount_filled
string
Amount filled for taker
fee
string
Realized taker fee (v2 fees are protocol-determined at match time)
side
string
Trade side string (buy / sell)
price
string
Calculated execution price
block_number
uint64
Block number
exchange_version
string
"v1" or "v2" — identifies which Exchange generation emitted the fill
token_id
string
Conditional token ID (v1: derived non-zero asset; v2: emitted directly)
side_raw
uint32
v2 side enum: 0=BUY, 1=SELL (0 for v1)
builder
string
bytes32 builder attribution code, hex-encoded (v2 only; empty for v1)
metadata
string
bytes32 order metadata, hex-encoded (v2 only; empty for v1)
MarketOrderbook
Field
Type
Description
id
string
Market identifier
trades_quantity
uint64
Total trade count
buys_quantity
uint64
Buy trade count
sells_quantity
uint64
Sell trade count
collateral_volume
string
Total volume
average_trade_size
string
Average trade size
total_fees
string
Total fees collected
mid_price
string
Current mid price
Contract Addresses
CLOB v1 (legacy — historical fills only after the 2026-04-28 cutover)
Contract
Address
CTF Exchange v1
0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e
Neg Risk Exchange v1
0xC5d563A36AE78145C45a50134d48A1215220f80a
CLOB v2 (deployed 2026-03-31 by Polymarket Deployer 1)
Contract
Address
CTF Exchange V2
0xE111180000d2663C0091e4f400237545B87B996B
Neg Risk CTF Exchange V2
0xe2222d279d744050d28e00520010520000310F59
V2 deploy block: 84,902,353 · Cutover: 2026-04-28 ~11:00 UTC
Using as a Dependency
Import this package to build higher-level analytics:
Re-point the sink at v0.4.0 — no resync required; v2 modules pick up at block 84,902,353 and v1 modules continue from your existing cursor.
Optional: rebuild Clickhouse materialized views to aggregate by token_id instead of maker_asset_id (the v1 schema lumped all collateral=0 buys under one key).