Package Logo
polymarket-pnl
polymarket-pnl@v1.2.1
Total Downloads
76087
Published
13 hours ago
Network
polygon polygon
Publisher
User Avatar PaulieB14

Readme

Polymarket P&L Substreams


Overview

Comprehensive Substreams package for tracking Polymarket P&L with SQL sink support for persistent state accumulation. Tracks all trading activity from CTF Exchange and Neg Risk Exchange contracts across both CLOB v1 and CLOB v2.


CLOB v2 ready (since v1.2.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 P&L pipeline spans the migration.

What changed in CLOB v2

Concept CLOB v1 CLOB v2
Exchange contracts 0x4bfb…982e (CTF) / 0xC5d5…f80a (NegRisk) 0xE111…996B (CTF) / 0xe222…0F59 (NegRisk) — fresh deploys
Order uniqueness nonce per maker timestamp (ms) — nonces removed
Order side Inferred from makerAssetId == 0 Explicit side enum on the order and event (BUY=0, SELL=1)
OrderFilled event 8 fields, including makerAssetId + takerAssetId 10 fields: single tokenId + side + new builder (bytes32) + metadata (bytes32)
Fees Embedded in order (feeRateBps), maker + taker 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

  1. Contract dispatch. map_order_fills watches all four Exchange addresses (v1 + v2). When a log lands on a v2 contract, it routes to the v2 decoder; v1 contracts still use the original v1 decoder. Both produce the same internal OrderFilledEvent shape, so the rest of the pipeline (stores, P&L math, SQL sink) sees one homogeneous stream.
  2. v2 → v1 field mapping. The v2 event emits one tokenId with an explicit side. We synthesize the legacy maker_asset_id / taker_asset_id pair from (side, tokenId) using the same convention as v1 (collateral-side gets "0", the conditional-token side gets the token id). This keeps your existing WHERE side = 'buy' queries and the cost-basis store working with zero changes.
  3. New columns surfaced. v2's builder and metadata are decoded but currently land only in the in-memory event (not yet in the SQL trades table — see Migration to richer v2 schema below for an opt-in extension).
  4. Source-of-truth tagging. The exchange column tags every row with which contract emitted it: ctf / neg_risk (v1) or ctf_v2 / neg_risk_v2. So you can filter, partition, or audit by generation.
  5. Fee column semantics. v2 fees are taker-only and protocol-determined; they appear in the same fee field but represent a single realized taker fee rather than maker+taker fees.
  6. No schema migration required. Existing trades, user_pnl, user_positions, markets, daily_stats tables work unchanged — v2 fills slot in as additional rows with the new exchange values.

What stays the same

  • Same chain (Polygon), same Firehose source, same RPC.
  • Same map_order_fills → stores → analytics → db_out DAG.
  • Same SQL sink configuration, same leaderboard / whale views.
  • Same start block for historical backfill (33,605,403); v2 fills activate naturally at the v2 deploy block (84,902,353).

Migration to richer v2 schema

If you want to query v2-specific fields like builder (for builder attribution analytics) or metadata, you can add columns and surface them in db_out:

ALTER TABLE trades
  ADD COLUMN exchange_version VARCHAR(4) DEFAULT 'v1',
  ADD COLUMN builder VARCHAR(66),
  ADD COLUMN metadata VARCHAR(66);
CREATE INDEX idx_trades_builder ON trades(builder) WHERE builder IS NOT NULL;

Then add the matching .set("builder", ...) / .set("metadata", ...) calls in db_out and republish. This is opt-in — the package as shipped doesn't require it.

Key Features

Feature Description
CLOB v1 + v2 Decodes both Exchange generations into the same P&L pipeline
Real P&L Tracking Realized & unrealized P&L with cost basis
SQL Sink PostgreSQL/Clickhouse for persistent state
Trader Analytics Volume, win rate, max drawdown
Market Stats Price, volume, trade counts per market
Whale Detection Large trade tracking with trader context

Quick Start

Stream Data (No Database)

# Install CLI
brew install streamingfast/tap/substreams

# Authenticate
substreams auth

# Stream order fills
substreams run https://spkg.io/PaulieB14/polymarket-pnl-v1.2.0.spkg \
  map_order_fills \
  -e polygon.substreams.pinax.network:443 \
  -s 65000000 -t +1000

# Stream user P&L
substreams run https://spkg.io/PaulieB14/polymarket-pnl-v1.2.0.spkg \
  map_user_pnl \
  -e polygon.substreams.pinax.network:443 \
  -s 65000000 -t +1000

Sink to PostgreSQL (Required for P&L)

Important: P&L requires accumulated state. Use the SQL sink for accurate calculations.

# Install sink
brew install streamingfast/tap/substreams-sink-sql

# Create database
createdb polymarket_pnl

# Setup schema
substreams-sink-sql setup \
  "psql://localhost:5432/polymarket_pnl?sslmode=disable" \
  https://spkg.io/PaulieB14/polymarket-pnl-v1.2.0.spkg

# Run sink (start from beginning for full history)
substreams-sink-sql run \
  "psql://localhost:5432/polymarket_pnl?sslmode=disable" \
  https://spkg.io/PaulieB14/polymarket-pnl-v1.2.0.spkg \
  -e polygon.substreams.pinax.network:443

Query Your Data

-- Top traders by P&L
SELECT * FROM leaderboard_pnl LIMIT 20;

-- Whale trades with trader stats
SELECT * FROM whale_trades;

-- User positions
SELECT * FROM user_positions
WHERE user_address = '0x...' AND quantity > 0;

-- Daily stats
SELECT date, total_volume, total_trades
FROM daily_stats ORDER BY date DESC;

Architecture

                    Polygon Blockchain
                           │
                           ▼
              ┌─────────────────────────┐
              │     Firehose Blocks     │
              └─────────────────────────┘
                           │
         ┌─────────────────┼─────────────────┐
         ▼                 ▼                 ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ map_order_fills │ │map_token_transf │ │map_usdc_transf  │
│  (CTF + NegRisk)│ │    (ERC1155)    │ │    (USDC)       │
└─────────────────┘ └─────────────────┘ └─────────────────┘
         │                 │                   │
         └─────────────────┼───────────────────┘
                           ▼
              ┌─────────────────────────┐
              │    STORES (State)       │
              │  • user_positions       │
              │  • user_cost_basis      │
              │  • user_realized_pnl    │
              │  • latest_prices        │
              └─────────────────────────┘
                           │
                           ▼
              ┌─────────────────────────┐
              │      map_user_pnl       │
              │   (Computed Analytics)  │
              └─────────────────────────┘
                           │
                           ▼
              ┌─────────────────────────┐
              │         db_out          │
              │      (SQL Sink)         │
              └─────────────────────────┘
                           │
                           ▼
              ┌─────────────────────────┐
              │       PostgreSQL        │
              └─────────────────────────┘

Modules

Layer 1: Event Extraction

Module Description
map_order_fills OrderFilled events from CTF & NegRisk exchanges
map_token_transfers ERC1155 TransferSingle events
map_usdc_transfers USDC transfer events

Layer 2: State Stores

Store Key Description
store_user_positions {user}:{token} Position quantities
store_user_cost_basis {user}:{token} Total cost basis
store_user_realized_pnl {user} Realized P&L
store_user_volume {user} Trading volume
store_user_trade_count {user} Trade count
store_market_volume {token} Market volume
store_latest_prices {token} Latest prices

Layer 3: Analytics

Module Description
map_user_pnl Real-time P&L calculations
map_market_stats Market-level statistics

Layer 4: Sink

Module Description
db_out Database changes for SQL sink

Database Schema

Tables

Table Description
trades All order fills with price, amount, side
user_pnl Aggregated P&L per user
user_positions Current positions with cost basis
markets Market statistics
daily_stats Daily aggregates

Views

View Description
leaderboard_pnl Top 1000 by P&L
leaderboard_volume Top 1000 by volume
whale_trades Trades >$10K

Contract Addresses

CLOB v1 (legacy — historical fills only after the 2026-04-28 cutover)

Contract Address Start Block
CTF Exchange v1 0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e 33,605,403
NegRisk Exchange v1 0xC5d563A36AE78145C45a50134d48A1215220f80a 50,505,492

CLOB v2 (deployed 2026-03-31 by Polymarket Deployer 1, cutover 2026-04-28)

Contract Address
CTF Exchange V2 0xE111180000d2663C0091e4f400237545B87B996B
NegRisk CTF Exchange V2 0xe2222d279d744050d28e00520010520000310F59

Other (unchanged)

Contract Address Start Block
Conditional Tokens 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 4,023,686
USDC 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 4,023,686

Build from Source

# Clone
git clone https://github.com/PaulieB14/Polymarket-P-L-Substreams
cd Polymarket-P-L-Substreams

# Build
substreams build

# Test
substreams run substreams.yaml map_order_fills \
  -e polygon.substreams.pinax.network:443 \
  -s 65000000 -t +100

# Package & publish
substreams pack substreams.yaml -o polymarket-pnl-v1.2.0.spkg
substreams publish polymarket-pnl-v1.2.0.spkg

Why SQL Sink?

P&L calculation requires state accumulation over time:

Without Sink With SQL Sink
No history Full history persisted
P&L = $0 Accurate P&L
Stateless Tracks cost basis
Demo only Production ready

Related


License

MIT

Documentation

Modules

Maps icon
Maps

map
map_order_fills

7676655d38d21a0f55f0223f898de8a3f51685a0
map map_order_fills (
blocksf.ethereum.type.v2.Block
)  -> pnl.v1.OrderFills

Extracts OrderFilled events from CTF Exchange and NegRisk Exchange across both CLOB v1 (legacy) and v2 (deployed 2026-03-31, cutover 2026-04-28). This is the primary source of trading activity.

substreams gui polymarket-pnl@v1.2.1 map_order_fills

map
map_token_transfers

c1ae3194e43205c4bbada5cab790f2c11fe1b4a0
map map_token_transfers (
)  -> pnl.v1.TokenTransfers

Extracts ERC1155 TransferSingle events for position tracking.

substreams gui polymarket-pnl@v1.2.1 map_token_transfers

map
map_usdc_transfers

512ad2b86c70f555c9c6d0191c1ca8ea76ab8828
map map_usdc_transfers (
)  -> pnl.v1.UsdcTransfers

Extracts USDC (ERC20) transfer events for collateral tracking.

substreams gui polymarket-pnl@v1.2.1 map_usdc_transfers

map
map_user_pnl

ccee2b9a38681b50e5be83eb2507035b71de6544

Computes real-time P&L for users based on their positions and trades. Outputs user P&L updates when positions change.

substreams gui polymarket-pnl@v1.2.1 map_user_pnl

map
map_market_stats

d3300a7e1c166fdac36a392a595ed9d162f38407
map map_market_stats (
store_market_volumeDELTAS<add,bigint>
)  -> pnl.v1.MarketStats

Computes market-level statistics (volume, trades, prices).

substreams gui polymarket-pnl@v1.2.1 map_market_stats

map
db_out

6ac2c56ffdf47116d3cb8d59bf3620b9c877512c
Default param : min_trade_size=1000000

Outputs database changes for SQL sink (PostgreSQL/Clickhouse). Creates/updates tables: trades, user_pnl, user_positions, markets

substreams gui polymarket-pnl@v1.2.1 db_out

map
ethcommon:all_events

963652a247fd23d0823dde62d21ae54c783b6073
map ethcommon:all_events (
)  -> sf.substreams.ethereum.v1.Events

all_events gives you all the events in a block (from successful transactions), with basic block hash/number/timestamp and transaction hash

substreams gui polymarket-pnl@v1.2.1 ethcommon:all_events
Stores icon
Stores

store
store_user_positions

563f3330f718fcd1920c1c921546449152a80851
store <add,bigint> store_user_positions (
)

Accumulates user positions per token. Tracks quantity held. Key: {user_address}:{token_id}

substreams gui polymarket-pnl@v1.2.1 store_user_positions

store
store_user_cost_basis

14a3a5af3967d4e367e4c3fa90ec39636e38d035
store <add,bigint> store_user_cost_basis (
)

Tracks total cost basis per user per token for average price calculation. Key: {user_address}:{token_id}

substreams gui polymarket-pnl@v1.2.1 store_user_cost_basis

store
store_user_realized_pnl

d53593f5e09ec977ab043506bf06c0d8ea40f1c3
store <add,bigint> store_user_realized_pnl (
)

Accumulates realized P&L when users sell positions. Key: {user_address}

substreams gui polymarket-pnl@v1.2.1 store_user_realized_pnl

store
store_user_volume

fc14158c9d989c1be155fa4cc0202fba6d4632d4
store <add,bigint> store_user_volume (
)

Tracks total trading volume per user. Key: {user_address}

substreams gui polymarket-pnl@v1.2.1 store_user_volume

store
store_user_trade_count

ebc1aa75145ca8eceb0755ba9c1db27ad4125c56
store <add,int64> store_user_trade_count (
)

Tracks number of trades per user. Key: {user_address}

substreams gui polymarket-pnl@v1.2.1 store_user_trade_count

store
store_market_volume

520acf45dfa71c57e3b191e9fa9f00874919633e
store <add,bigint> store_market_volume (
)

Tracks total volume per market (token_id). Key: {token_id}

substreams gui polymarket-pnl@v1.2.1 store_market_volume

store
store_latest_prices

62114f9eef70a840a4f7790dbec852947b57cb95
store <set,pnl.v1.TokenPrice> store_latest_prices (
)

Stores latest price per token for unrealized P&L calculation. Key: {token_id}

substreams gui polymarket-pnl@v1.2.1 store_latest_prices
Block Indexes icon
Block Indexes

blockIndex
ethcommon:index_events

87255243f80f5d4755cd826ec57bf70696a4d7b6

index_events sets the following keys on the block:

  • Event signatures evt_sig:0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef (signature in hex, prefixed by 0x)
  • Event address evt_addr:0x0123456789abcdef0123456789abcdef01234567 (address in hex, prefixed by 0x)
substreams gui polymarket-pnl@v1.2.1 ethcommon:index_events
Protobuf

Protobuf Docs Explorer

sf.ethereum.type.v2
sf.ethereum.substreams.v1
pnl.v1