Skip to main content

Documentation Index

Fetch the complete documentation index at: https://polynode.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.


/v1/resolve now returns the controlling EOA for Polymarket magic-link wallets — accounts created with email or social login. Previously these returned eoa: null because Polymarket’s profile API does not expose the signer for this wallet type.
{
  "safe": "0x16cbe223607a6513ae76d1e3751c78e4eabc2704",
  "eoa": "0xbe5ba588ab7173b34efc0706b881794951014293",
  "username": "MRF",
  "type": "proxy"
}
No code changes required. The response shape is unchanged. The eoa field is now populated for the vast majority of type: "proxy" wallets. A small number may still return eoa: null where the EOA cannot be determined. Repeat queries are sub-100ms.

2026-05-02 — Deposit wallet support

Polymarket is rolling out deposit wallets as a new account type for new users. This release adds full support across the API, all three SDKs, and the copy trading engine. Existing integrations are fully backward compatible — no code changes required unless you want to take advantage of the new features. Read the full Deposit Wallets guide for details, code examples, and FAQ.

What’s new

/resolve endpoint — now returns a type field ("safe", "deposit_wallet", or "proxy") in all responses. Resolves deposit wallets in both directions: wallet address to EOA and EOA to wallet address. Existing safe, eoa, and username fields unchanged.
{
  "safe": "0x8b60bf0f650bf7a0d93f10d72375b37de18f8c40",
  "eoa": "0xa60601a4d903af91855c52bfb3814f6ba342f201",
  "username": null,
  "type": "deposit_wallet"
}
SDK — address derivation and detectionderiveDepositWalletAddress(eoa) computes the deterministic deposit wallet address for any EOA. detectWalletType() and ensureReady() automatically detect whether an EOA has a Safe, proxy, or deposit wallet. Safe is checked first, so existing users keep their existing wallet type. SDK — order signing — when signatureType is POLY_1271 (3), the SDK wraps the EIP-712 signature using ERC-7739 TypedDataSign format automatically. order() / signV2Order() / create_signed_order_v2() detect the signature type from stored credentials and sign correctly. No code changes needed on your side. SDK — onboardingensureReady() handles the full flow for deposit wallet users: derives the wallet, deploys via WALLET-CREATE if needed, and sets V2 approvals via a signed WALLET batch. Same one-call pattern as Safe wallets. Copy trading — the copy trading engine generates the correct typed data for deposit wallet followers (sig_type: 3). Your signing code should use signV2Order() from the SDK.

Install

cargo add polynode@0.13.3        # Rust
npm install polynode-sdk@0.10.6  # TypeScript
pip install polynode==0.10.3     # Python

Do I need to update?

  • If you only read data (positions, trades, resolve): no changes needed. The type field is additive.
  • If you place orders via the SDK: update to the versions above. Your code stays the same.
  • If you build order signatures yourself: you need to implement ERC-7739 wrapping for signatureType: 3. See the guide.

2026-05-01 — Cursor pagination on /v2/onchain/*/trades

The two trade-history endpoints now support cursor pagination in addition to offset:
  • GET /v2/onchain/markets/{token_id}/trades?cursor=<lastTs>:<lastId>
  • GET /v2/onchain/wallets/{address}/trades?cursor=<lastTs>:<lastId>
Why it matters: offset pagination slows down the deeper you page — page 500 means the database walks 50,000 rows of wasted work to find your starting point. Cursor pagination loads page 1000 just as fast as page 1 — typically ~1 second per page, regardless of how deep you go. How to use:
  1. First page: pass ?cursor= (empty value) instead of ?offset=0.
  2. Response includes a pagination block with cursor and has_more.
  3. Subsequent pages: pass that cursor back as ?cursor=<value>.
  4. Stop when has_more is false.
Available on every paid tier — no Growth restriction. Compatibility: purely additive. The existing ?offset= mode is unchanged and remains fully supported. Existing integrations don’t need to change anything — switch to cursor when you next touch the integration. See the updated docs for full examples: Trade History (Market) · Trade History (Wallet).

2026-05-01 — Bulk trade-export endpoints (Growth plan and above)

Two new endpoints return the entire trade history for a market token or wallet in a single response — no client-side pagination required:
  • GET /v2/onchain/markets/{token_id}/trades/all
  • GET /v2/onchain/wallets/{address}/trades/all
The server walks the full history with parallel time-bucketed queries internally. Optional from and to query parameters narrow the window. Limits:
  • Hard cap 250,000 trades per call. Beyond that, response includes "partial": true with "partial_reason": "hard_cap_250000".
  • Wall-clock budget 180 seconds. First cold-cache calls on busy markets/heavy wallets may take 1-3 minutes. Subsequent calls hit Redis cache and return in under 1 second.
  • Typical payload 1-50 MB. Worst case (capped): 100-200 MB JSON.
  • Cache TTL 5 minutes per (scope, from, to) combination. Partial results are not cached.
Tier requirement: Growth plan ($200/mo) or above. Starter and free tier requests receive 403. The bulk endpoints are gated to paid plans because of the heavy server-side compute (one call drives 100s of internal sub-queries) and large payloads. Compatibility: purely additive. The standard paginated /trades endpoints are unchanged and remain available on all paid tiers. Updated upstream timeout to 180 seconds across the gateway path so cold-cache calls complete cleanly.

2026-05-01 — Backtest Copy PnL: new total_realized_pnl_usdc field

Added total_realized_pnl_usdc to /v2/copy-pnl/{wallet} responses. This is the signed sum of WAVG-realized PnL across every position the matcher touched in the window — the closest single number to “did this wallet make money trading?” and the recommended primary “PnL” field for customer-facing UI. Existing actual_pnl_usdc is unchanged but is now documented as a cashflow metric (USDC delta in/out of the wallet over the window), which is intentionally different from realized PnL — see the endpoint docs for the worked example. Also fixed a long-standing bug in the cashflow accounting where neg-risk-conversion events were over-counting settlement_in (treating share counts as USDC). NRC-active wallets see corrected numbers across all four periods (14d, 30d, 60d, 180d) on the next BYOB cycle refresh.

2026-04-30 — Backtest Copy PnL: per-position metrics now match Polymarket byte-for-byte

The new fields added earlier today (avg_entry_prob_weighted, positions_closed on /v2/copy-pnl/*) now produce values that match Polymarket’s own data-api position math. Validated against data-api.polymarket.com/positions realizedPnl across 30 positions on diverse wallets (standard CTF + neg-risk markets):
  • 97 % sub-penny match
  • 100 % sub-$1 match
  • 100 % sub-$10 match
No API changes — same field names, same response shape. Existing integrations automatically benefit. Cached values progressively refresh as background scoring cycles complete.

2026-04-30 — BYOB Snapshot: every wallet × every period in one read

New endpoint GET /v2/copy-pnl/snapshot returns every wallet in your tracked-wallet pool with backtest copy-PnL scores across all six time windows (7d, 14d, 30d, 60d, 90d, 180d) in a single response. Built for stats-card UIs and dashboards that need the whole pool at once instead of issuing one request per period.
  • One request — no per-period round trips, no client-side stitching.
  • Sub-second cached — 100 wallets × 6 periods returns in ~70 ms / ~150 KB. 1000 wallets × 6 periods in ~500 ms / ~1.5 MB.
  • Optional period subset via ?periods=7d,30d to drop payload when you only need one window.
  • Per-wallet error reporting — heavy whales that errored on the most recent refresh surface their failure reason inline, so your UI can render “X wallets retrying” instead of hiding gaps.
curl -H "x-api-key: $YOUR_KEY" \
  "https://api.polynode.dev/v2/copy-pnl/snapshot"
See BYOB — Snapshot for the full response shape and examples.

2026-04-30 — /v2/onchain/markets/{token}/volume faster

Backend optimization for the market volume endpoint. Response shape and values are unchanged — same buys, sells, total_trades, buy_volume_usdc, sell_volume_usdc, volume_usdc, found fields. Light and empty markets see roughly halved latency. No integration changes required.

2026-04-30 — Wallet activity & redemptions: deep pagination unlocked

Heavy wallets returning more than 1000 records on /v2/onchain/wallets/{wallet}/redemptions and /v2/onchain/wallets/{wallet}/activity now return the complete dataset:
  • offset past 1000 now works. Previously, pagination beyond the first 1000 records returned empty results regardless of how many records existed. You can now walk a heavy redeemer’s full history with offset=1000, offset=2000, etc.
  • No more silent truncation. Wallets with >1000 redemptions, splits, merges, or neg-risk conversions previously had results capped at 1000. The full record set is now returned.
No SDK update required. No integration changes. Same URLs, same query parameters, same response shapes — your existing code automatically benefits.
# Walk a heavy redeemer's full history:
curl "https://api.polynode.dev/v2/onchain/wallets/{wallet}/redemptions?limit=1000&offset=0" \
  -H "x-api-key: $YOUR_KEY"

curl "https://api.polynode.dev/v2/onchain/wallets/{wallet}/redemptions?limit=1000&offset=1000" \
  -H "x-api-key: $YOUR_KEY"

2026-04-30 — TypeScript SDK 0.9.7

Pluggable storage for Bun/Deno/edge runtime compatibility:
  • storage config option — pass 'memory' for an in-memory backend (no native dependencies), a file path for SQLite (default), or your own TradingStorage implementation.
  • Auto-fallback — if better-sqlite3 isn’t available (Bun, Deno, edge runtimes), the SDK automatically falls back to in-memory storage instead of crashing.
  • BunSqliteBackend — persistent SQLite storage using Bun’s built-in bun:sqlite. Zero native dependencies. Same schema, same persistence as the Node backend.
  • Auto-detection — if you don’t pass a storage option, the SDK tries better-sqlite3 first (Node), then bun:sqlite (Bun), then falls back to in-memory. Just works in both runtimes.
  • Exported interfaceTradingStorage, InMemoryStorage, and BunSqliteBackend are all exported so you can use them directly or build custom adapters.
// Bun — auto-detected, no config needed:
const trader = new PolyNodeTrader({ /* storage auto-detects bun:sqlite */ });

// Bun — explicit:
import { PolyNodeTrader, BunSqliteBackend } from 'polynode-sdk';
const trader = new PolyNodeTrader({
  storage: new BunSqliteBackend('./trading.db'),
});
Upgrade:
npm install polynode-sdk@0.9.7

2026-04-30 — TypeScript SDK 0.9.5

Bug fix release for the trading module:
  • Fixed signer.getAddress is not a function crashensureReady() and linkWallet() could throw this error when the internal viem WalletClient was passed to downstream Polymarket packages that expect an ethers-style getAddress() method. The SDK now ensures getAddress() is always available on the normalized signer, regardless of which @polymarket/clob-client version you have installed.
  • Fixed signer detection logic — a duplicate guard condition was allowing incomplete signer objects through, causing crashes instead of clean error messages.
  • Hardened relay signer adapter — address extraction now handles additional wallet shapes during Safe deployment and approval flows.
Upgrade:
npm install polynode-sdk@0.9.5

2026-04-30 — BYOB (Bring Your Own Backtest) — precomputed leaderboards

The on-demand backtest endpoints answer “score this wallet” synchronously. BYOB inverts that — you hand us a private wallet pool, we precompute scores in the background across all six period presets, you query the resulting leaderboard with sub-second latency. Four new endpoints under /v2/copy-pnl/:
POST   /v2/copy-pnl/wallets       body: {addresses[]}    add to pool (max 1000)
DELETE /v2/copy-pnl/wallets       body: {addresses[]}    remove from pool
GET    /v2/copy-pnl/wallets                              list pool
GET    /v2/copy-pnl/leaderboard   ?period=&sort_by=&order=&limit=&offset=&min_trade_count=&exclude_toxic=
Newly-added wallets are scored within ~30s of being added. The full pool refreshes daily in the background. Each result row includes computed_at so you can render freshness in your UI. Per-tenant isolation: each API key has its own private pool keyed on the SHA256 of the key. Your tracked wallets are never visible to other customers. References: Backtesting Overview · Add Wallets · Remove Wallets · List Wallets · Leaderboard

2026-04-30 — On-chain wallet/market trades: deep pagination unlocked

/v2/onchain/wallets/{w}/trades and /v2/onchain/markets/{token}/trades now properly paginate across the full trade history of any wallet or market, no matter how active. Previously these endpoints silently truncated heavy traders to ~2000 fills lifetime — deep ?offset= queries returned empty. Customer-visible behavior changes:
  • Heavy wallets (e.g. 1M+ fills) now return correct, complete trade history
  • Deep ?offset= queries return real data instead of empty arrays
  • No SDK or query-shape changes required — same ?limit=&offset= parameters
  • Lower latency on most queries

2026-04-30 — Backtest Copy PnL: batch endpoint

Score up to 100 wallets in one call:
POST /v2/copy-pnl/batch
{ "addresses": ["0x...", ...], "from": "...", "to": "..." }
Same math + response shape as the single-wallet endpoint. Per-wallet errors don’t fail the batch — the bad slot returns {"wallet": "...", "error": "..."} and the rest still come back. Reference: Batch endpoint

2026-04-30 — Backtest Copy PnL endpoint

New paid endpoint for scoring any wallet’s copy-trade quality:
GET /v2/copy-pnl/{wallet}?period=30d
Walks every fill in the requested window, applies a realistic 2% slippage on buys and sells (capped at $1.00 per share for buys), settles redemptions / merges / splits at face value, and returns:
  • actual_pnl_usdc — the wallet’s cashflow PnL over the window
  • backtest_copy_pnl_usdc — what a copier would have earned with friction
  • slippage_amount_usdc — the dollar gap
  • slippage_cost_rate_pct — friction as a percentage of actual PnL
  • toxic_for_copyingtrue when the rate exceeds 15% (wallet’s profit relies on execution speed; copier won’t replicate)
  • trade_count, applied_filters, sources, optional trades[] drill-down
Time window options: ?period=7d|14d|30d|60d|90d|180d (default 30d), or ?from=&to= with YYYY-MM-DD or unix seconds. Note on PnL definition: this returns cashflow PnL (real dollars moved), not Polymarket’s website PnL (which marks open positions at current price). The response includes a pnl_definition: "cashflow" field to make this explicit. For PM-website style PnL use Trader PnL Series. Limits: paid tier required, 1 request per 5s per key, 30s server-side budget. Validated on wallets up to ~1.6M fills in the window. Full reference: Backtest Copy PnL · Backtesting Overview

2026-04-27 — Positions: 100% Polymarket parity, deterministic resolved-market mark, six new fields, real cursor pagination

/v2/onchain/positions now matches Polymarket’s data-api to the cent on every shared position. Verified across six wallets, 627 total shared positions, 100.00% sub-penny match on unrealized_pnl. What’s new on each position row:
  • initial_value — cost basis in USD of currently held shares. The exact number Polymarket uses internally to compute cashPnl.
  • redeemabletrue when the market has resolved and the user can call redeem to claim payout / accept loss. Filter on this with market_status to detect resolved-but-not-yet-redeemed positions.
  • outcome_index — numeric index of this row’s outcome (0 or 1 for binary markets). Stable across the API regardless of UI label.
  • won / winning_outcome_index — present on resolved-win / resolved-loss rows. Explicit booleans / ints so consumers don’t have to parse outcome label strings.
  • opposite_asset — token ID of the binary counterpart outcome on the same market.
What’s fixed on the math:
  • current_price is deterministic on resolved markets. Reads payoutNumerators directly from the CTF contract on Polygon — no third-party indexer dependency, no wait for metadata to catch up. Returns exactly 1.0 for the winning outcome and 0.0 for the loser, the moment the market settles on chain.
  • market_status never lies. Was previously stuck on "live" for fresh resolutions when our metadata was behind. Now uses the chain timestamp as the authoritative resolution signal: "live", "resolved-win", "resolved-loss", "resolved-unknown", or "closed".
  • unrealized_pnl is byte-identical to Polymarket cashPnl for any position covered by both sources.
/v2/onchain/trades cursor pagination now reports truthful has_more. Previously has_more: false was returned even when more rows existed beyond the requested limit. Walk pages by passing pagination.pagination_key back as ?pagination_key=….
# returns has_more: true with pagination_key now
curl "https://api.polynode.dev/v2/onchain/trades?wallet=0x…&limit=2" -H "x-api-key: …"

# walk to the next page
curl "https://api.polynode.dev/v2/onchain/trades?wallet=0x…&limit=2&pagination_key=2" -H "x-api-key: …"
Time filters on /v2/onchain/trades use start_time and end_time (unix seconds), not from / to. Documented and verified.

2026-04-27 — Trades: unified schema, direction & side always present

/v2/onchain/trades, /v2/onchain/wallets/{w}/trades, and /v2/onchain/markets/{tok}/trades now return the same direction and side fields on every row, regardless of which filter combination is passed. Previously these fields appeared only when a ?wallet= anchor was supplied — market-wide queries (?condition_id=…, ?token_id=…, no filter) omitted both, and consumers had to render two different schemas.
# customer's market-wide query — now returns direction+side on every row
curl "https://api.polynode.dev/v2/onchain/trades?condition_id=0xb778…&limit=3" \
  -H "x-api-key: YOUR_KEY"
{
  "side": "maker",
  "direction": "BUY",
  "maker": "0xdf0d2c…",
  "taker": "0xb323fd…"
}
Anchor rule:
  • With ?wallet=direction and side are wallet-relative. direction is BUY when the wallet contributed USDC and received outcome shares, SELL when it contributed outcome shares and received USDC. side is maker when the wallet placed the resting order, taker when it crossed the spread.
  • Without ?wallet=direction is anchored on the maker of each fill (the user who signed the limit order; on Polymarket’s CLOB this is always a user EOA). side is therefore always "maker" in this case.
This means a single fill viewed from a wallet-filtered query and from a market-wide query may report different direction values — both are correct, just anchored on different parties. See the “Direction & side semantics” section on the trades reference page. Strictly additive — calls that previously returned direction/side continue to return the exact same values.

2026-04-26 — New endpoint: GET /v2/onchain/tags for tag discovery

Lists every tag slug in the polynode taxonomy — currently 5,779 tags. Lightweight by default (just slug strings, 73 KB / 43 ms cold for the full list) or ?details=true for per-tag enrichment (markets, events, first/last seen).
# all tags
curl "https://api.polynode.dev/v2/onchain/tags" -H "x-api-key: YOUR_KEY"

# top 700 mainstream tags with stats
curl "https://api.polynode.dev/v2/onchain/tags?min_markets=100&details=true" \
  -H "x-api-key: YOUR_KEY"

# discover NBA-related tags
curl "https://api.polynode.dev/v2/onchain/tags?prefix=nba&details=true" \
  -H "x-api-key: YOUR_KEY"
Values returned here are exactly what to pass to ?tag_slug= on the wallet positions and unified positions endpoints. Use it to populate filter dropdowns or autocomplete inputs. The underlying materialized view refreshes hourly so newly-added Polymarket tags appear within an hour.

2026-04-26 — Wallet Positions: tag_slugs field + ?tag_slug= filter

Every row of GET /v2/wallets/{address}/positions/onchain now includes a tag_slugs array carrying the Polymarket event-level tags that apply to that market — typical values like nba, basketball, sports, politics, crypto, fed, 2025-predictions, plus event-specific slugs like 2026-fifa-world-cup-winner-595. Order is most-specific to most-general. A new optional ?tag_slug=<slug> query parameter filters the response to only positions whose market carries that tag. Composes with since / until, so you can ask for e.g. “all NBA positions traded in the last 30 days” in one request:
SINCE=$(( $(date -u +%s) - 86400 * 30 ))
curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain?tag_slug=nba&since=$SINCE" \
  -H "x-api-key: YOUR_KEY"
When the filter is supplied, aggregates recompute over the filtered set and the response gains filtered: true plus applied_filters: { since, until, tag_slug } so the behavior is self-documenting. Strictly additive — when no filter param is supplied, the response shape is byte-identical to before. Coverage is effectively universal: tags are populated on every event Polymarket indexes (sampled empirically at 100% in our backfill audit), so any non-test market should return a populated tag_slugs array.

2026-04-26 — Unified Trades: additive direction (BUY/SELL) field on /v2/onchain/trades

GET /v2/onchain/trades now also returns direction ("BUY" / "SELL") on every row when filtered by wallet — same semantics as the wallet-trades endpoint, applied to the unified-trades flow. Independent of the existing side field. When no wallet anchor is supplied, direction is omitted (no perspective to derive against). Strictly additive: every other field is byte-identical to prior responses, and queries without wallet are unchanged. Verified live: 1,214 trades cross-checked against Polymarket data-api side — zero drift.

2026-04-26 — Wallet Trades: additive direction (BUY/SELL) field

Every row of GET /v2/onchain/wallets/{address}/trades now carries a direction field with values "BUY" or "SELL", derived from whether the queried wallet contributed USDC or outcome tokens on the fill. This is independent of the existing side field, which remains the exchange role ("maker" / "taker") and is unchanged. The two answer different questions:
  • side — did the wallet provide liquidity or take it?
  • direction — did the wallet enter (BUY) or exit (SELL) outcome shares?
All four combinations (maker+BUY, maker+SELL, taker+BUY, taker+SELL) appear in real responses. Strictly additive — every other field is byte-identical to prior responses. The information direction carries was always derivable from maker_asset_id / taker_asset_id (exactly one is "0" on every fill, that’s the buyer). This change just makes the derivation explicit so analytics integrations don’t have to compute it client-side.

2026-04-26 — Wallet Positions: per-position timestamps, parent event slug, opt-in window filter

Four new fields on every row of GET /v2/wallets/{address}/positions/onchain (and the unified GET /v2/onchain/positions feed). Two new optional query parameters for server-side window filtering. Every change is strictly additive — calls with no new parameters return responses byte-identical to before. New per-position fields:
  • last_trade_at — unix seconds. Latest fill on this token across V1 + V2 exchanges. The right field for “active in the last 30 / 90 days” leaderboard windows.
  • closed_at — unix seconds. Latest moment the wallet redeemed any outcome of the market for collateral. Distinct from resolved_atresolved_at is when the market itself resolved on-chain; closed_at is when this specific wallet actually redeemed.
  • resolved_at — unix seconds. Moment the market became redeemable (oracle reported payouts on-chain). Recent markets are fully covered; markets that resolved before polynode began tracking return null.
  • event_slug — the parent event slug, distinct from the per-row slug (which is the per-market slug). For multi-market events (NBA games with several lines, election markets with multiple candidates, FIFA World Cup with one market per team), event_slug is the parent the markets share. For single-market events, event_slug equals slug. Lets you group positions by event without an extra metadata round-trip.
New optional query parameters — opt-in only:
  • ?since=<unix_seconds> — keep positions whose last_trade_at >= since.
  • ?until=<unix_seconds> — keep positions whose last_trade_at <= until.
When either is supplied, all aggregates (count, open_count, closed_count, total_realized_pnl, total_unrealized_pnl, total_pnl, positions_with_pnl) recompute over the filtered set, and the response gains two top-level keys to make the shift self-documenting: filtered: true and applied_filters: { since, until }. These keys are absent from default responses, so existing integrations see zero behavior change.
# 30-day window — leaderboard-style request
SINCE=$(( $(date -u +%s) - 86400 * 30 ))
curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain?since=$SINCE" \
  -H "x-api-key: YOUR_KEY"
See the Positions & P&L (Wallet) reference for the full field catalog and filtering rules.

2026-04-26 — X Search API beta

New paid endpoints for live X (Twitter) search and account-timeline data. Aimed at teams adding social context next to their prediction-market data — sentiment around a market, replies on a leader’s post, what’s being said about an event.
  • GET /v2/x/search?q=…&max=… — search by query, with full operator support (from:, since:, min_faves:, hashtags, lang:).
  • GET /v2/x/user/{handle}/tweets?max=… — most-recent tweets from any public X account.
Available on starter, growth, pro, and enterprise tiers with monthly quotas of 500 / 1,000 / 1,500 / 5,000. Hard rate cap of 1 request per second per key. Track usage live via X-Quota-Used / X-Quota-Limit response headers. See the X Search API guide for the full schema and examples.

2026-04-25 — Pricing section retired: candles deduplicated, “Market Card” introduced

The “Pricing” sidebar section has been removed. It contained two endpoints:
  • GET /v1/stats/{token_id} — kept, renamed to Market Card, moved further down the sidebar (just above “System”). This one is genuinely useful: a single call returns the condition_id, outcomes, neg-risk flag, end date, current orderbook liquidity, 24h OHLCV summary, and last price. Saves you from chaining 3–4 calls when you just need a market overview tile.
  • GET /v1/candles/{token_id} — removed from documentation. It was an in-memory rolling buffer with no historical depth, no pagination, no VWAP, and no buy/sell split. Use GET /v2/onchain/candles/{token_id} instead — same OHLCV plus VWAP, volume in shares, trade count, full history, and pagination. The endpoint itself still responds on the API for backward compatibility.

2026-04-25 — Wallet endpoints reorganized: V1 wallet endpoints removed from documentation

The “Wallets” and “Onchain” sidebar sections have been merged into a single section called Wallets / Positions / Onchain. All wallet, position, and trade endpoints now live in one place — the V2 onchain endpoints, which are backed by our own indexer rather than upstream Polymarket data-api. What changed in the docs: The seven legacy V1 wallet/market endpoints have been removed from the documentation sidebar. They were inferior duplicates of the V2 onchain equivalents — three of them returned empty or error responses, and the rest lacked PnL, average-price, current-price, and market-status fields that the V2 versions provide directly from chain data.
Removed from docsUse instead
GET /v1/wallets/{addr}/positionsGET /v2/onchain/positions?wallet=…
GET /v1/wallets/{addr}/closed-positionsGET /v2/onchain/positions?wallet=…&status=closed
GET /v1/wallets/positions (multi-wallet)Loop GET /v2/onchain/positions?wallet=… per wallet
GET /v1/wallets/{addr}/tradesGET /v2/onchain/wallets/{address}/trades
GET /v1/markets/{id}/positionsGET /v2/onchain/positions?token_id=…
GET /v1/markets/{id}/tradesGET /v2/onchain/markets/{token_id}/trades
GET /v1/resolve/{query}GET /v2/resolve/{query}
Important — backward compatibility preserved: The V1 endpoints still respond on the API. We have not turned them off. If you have integrations hitting the legacy paths, they continue to work exactly as before — we just stopped surfacing them in the documentation because the V2 onchain replacements are strictly better. You should migrate, but you don’t have to migrate today. Why V2 onchain is better: V1 wallet endpoints were thin proxies over Polymarket’s data-api.polymarket.com and returned mostly social-flavored fields (bio, profile picture, pseudonym). V2 onchain endpoints are powered by our own indexer over Polymarket’s CTF Exchange logs and return rich trading-relevant fields: avg_price, realized_pnl, unrealized_pnl, current_price, total_bought, market_status, order_hash, fee, maker_amount, taker_amount, condition_id, and more. Same data your equity curve, leaderboard, and PnL endpoints already use.

2026-04-25 — Equity Curve endpoint: faster default + four new query parameters

The /v2/trader/{wallet}/equity endpoint is faster, more flexible, and more accurate. Default is now realized-only. The curve is built from completed P&L by default — the locked-in result of every closed position, partial sell, and resolution. Same query, same numbers, every time. Whales return in 1–2 seconds (was 8+). If you want mark-to-market on currently-open positions (the old behavior), pass include_unrealized=1. That path is still supported, just opt-in now because it adds latency and makes responses non-deterministic as live prices move. Four new optional query parameters:
ParamDescription
from=YYYY-MM-DDStart date (also accepts unix seconds).
to=YYYY-MM-DDEnd date (same format).
max_markets=NKeep only the most recent N markets the wallet has touched.
include_unrealized=1Mark-to-market open positions (was the old default).
All four compose. Example — fast realized-only curve over the wallet’s last 50 markets in early April:
curl "https://api.polynode.dev/v2/trader/{wallet}/equity?from=2026-04-01&to=2026-04-15&max_markets=50&normalize=1" \
  -H "Authorization: Bearer YOUR_API_KEY"
New response fields: markets_count (distinct outcome groups in the curve), applied_filters (echoes what was applied, useful for debugging), and partial (true if the request hit the 10-second server-side cap). Coverage fix. Wallets with more than 500 lifetime trades previously had positions whose first-trade timestamp wasn’t found, causing those positions to be incorrectly stamped at “now” on the curve. The endpoint now walks all relevant trade history and falls back to sibling-token and redemption timestamps for positions acquired through USDC splits. Curves are dramatically more accurate for active traders. Full reference: /api-reference/enriched/equity-curve.

2026-04-24 — Fee Escrow V2 live on Polygon (pUSD collateral, V2 CLOB)

We’ve deployed a V2 variant of the Fee Escrow contract so platforms building on the V2 CLOB can charge fees in pUSD (V2’s native collateral) instead of USDC.e. Both V1 and V2 are live and fully supported — there is no migration. If you’re already running V1, nothing changes. FeeEscrow V2 (new):
FieldValue
Address0x3A43D88ef8Aae4dF5a50B3abf67122CAAeEF7c9F
CollateralpUSD 0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB (6 decimals)
EIP-712 domainname="PolyNodeFeeEscrowV2", version="2", chainId 137
FeeEscrow V1 (unchanged):
FieldValue
Address0xa11D28433B79D0A88F3119b16A090075752258EA
CollateralUSDC.e 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
EIP-712 domainname="PolyNodeFeeEscrow", version="1", chainId 137
The contracts have byte-identical calldata, identical function signatures, and identical semantics. The only functional differences are the collateral token and the domain name / version. The FeeAuth typed-data struct is unchanged. Opting into V2 today: The cosigner accepts an escrow_contract field in fee_auth. Absent or set to the V1 address = V1 (default). Set to the V2 address = V2. Sign the FeeAuth against the V2 domain when using V2.
"fee_auth": {
  "escrow_contract": "0x3A43D88ef8Aae4dF5a50B3abf67122CAAeEF7c9F",
  "escrow_order_id": "0x...",
  "payer":  "0xSafe...",
  "signer": "0xEOA...",
  "fee_amount": "27500",
  "deadline": 1777000000,
  "nonce": 0,
  "signature": "0x...",                   // signed against the V2 domain
  "affiliate": "0xPartnerWallet...",
  "affiliate_share_bps": 10000
}
SDKs: V2 fee escrow is wired up in all three SDKs. Set exchangeVersion to V2 in your TraderConfig and the SDK automatically signs FeeAuth against the V2 domain, reads nonces from the V2 contract, approves pUSD against the V2 FeeEscrow during ensureReady(), and tells the cosigner to route to the V2 operator. Install:
npm install polynode-sdk@0.9.2
pip install "polynode==0.9.2"
cargo add polynode@0.12.2
V1 customers: no changes, V1 stays the default. Omit exchangeVersion (or set it to V1) and everything keeps working exactly as before. Validation: 8/8 end-to-end fork tests pass against the deployed V2 contract, exercising pullFee + distribute (30/70 treasury/affiliate split) + refund + 72-hour claimRefund + every revert path (expired deadline, wrong nonce, tampered signature). The test impersonates a real on-chain pUSD whale to fund the payer. Contract bytecode is a strict 2-change diff from V1 (collateral address + domain strings). Full guide: /guides/fee-escrow.

2026-04-21 (late) — Unrealized P&L and market status on positions

The /v2/onchain/positions and /v2/wallets/{address}/positions/onchain endpoints now return unrealized P&L, the current market price, and a status flag per position — so you don’t have to fetch prices separately or special-case resolved markets in client code. New per-position fields:
  • unrealized_pnl — paper P&L on the held size at the current price. For resolved markets, uses the settlement price (1.0 for winners, 0.0 for losers). For live markets, uses the current market price. Returns 0 when size = 0.
  • current_price — the price used to compute unrealized_pnl. null when size = 0 or no price is available.
  • market_status — one of live, resolved-win, resolved-loss, or closed. Lets you tell apart a live-tradable holding from a resolved-but-never-redeemed position without looking up the market separately.
New portfolio totals on /v2/wallets/{address}/positions/onchain:
  • total_unrealized_pnl
  • total_pnltotal_realized_pnl + total_unrealized_pnl. This is the number that matches the “Profit” value shown on a Polymarket profile page.
Drop-in — every existing field keeps its name and type.

2026-04-21 (late) — Gasless Safe wrap/unwrap via the Polymarket relayer

Patch release. wrapToPolyUsd / unwrapFromPolyUsd (and their Python/Rust equivalents) now route through the Polymarket relayer automatically when called on a Safe or Proxy wallet — the flow ensureReady already uses. Previously these helpers sent the transaction directly from the EOA, which failed for Safe wallets because the funds live in the Safe, not on the EOA, and the EOA has no MATIC for gas. Install:
npm install polynode-sdk@0.9.1       # TypeScript
pip install "polynode>=0.9.1"        # Python
cargo add polynode@0.12.1            # Rust
Verified end-to-end with real Safe-via-relayer transactions:
  • TS: wrap 0xde6c02c3..., unwrap 0x006cd3dd...
  • Python: wrap 0x7de53811..., unwrap 0xccf9cf32...
  • Rust: wrap 0xd80af0f4..., unwrap 0xd21d6ad6...
All six txs mined on Polygon with status: 0x1, funded by third-party relayer EOAs — your EOA only signs the Safe execTransaction EIP-712 payload. Requirements for Safe wrap/unwrap: pass builderCredentials in TraderConfig. The SDK uses your Polymarket builder HMAC key to authenticate the relayer submit. Mint one at polymarket.com/settings?tab=builder. Tip — avoid stale-cache order rejection after wrap: after wrapToPolyUsd(), the V2 CLOB’s cached balance view is stale for a few seconds. If you place an order immediately, the CLOB can return a cryptic "error parsing fee rate bps" instead of the actual “balance not yet visible”. Call await trader.refreshBalanceAllowance() before the order, or wait ~3 seconds. This is why we don’t auto-refresh in wrap — not every wrap is followed by an immediate order. Also in this patch:
  • Rust: fixed encode_unwrap selector (was 0x39f47693, correct value is 0x8cc7104f). All Rust V2 unwraps prior to 0.12.1 hit the wrong contract function and reverted on-chain.
  • Rust: get_polyusd_balance() / get_usdce_balance() now read the funder address (Safe) instead of the EOA. Previously they always returned 0 for Safe-wallet users.
  • Python: relayer.py module added with a minimal Python port of @polymarket/builder-relayer-client. Implements EIP-712 Safe tx hashing, EIP-191 personal_sign via eth_account, builder HMAC headers, and /submit + /transaction polling.

2026-04-21 — SDK V2 order flow hardening (all three SDKs)

End-to-end V2 order placement verified live on clob-v2.polymarket.com across all three SDKs. Matched fills tagged with your builder code now appear in the V2 CLOB’s /builder/trades attribution feed. Install:
npm install polynode-sdk@0.9.0       # TypeScript
pip install "polynode>=0.9.0"        # Python
cargo add polynode@0.12.0            # Rust
What changed:
  • V2 approvals now include the V1 NegRiskAdapter in the spender list. The V2 CLOB’s balance-allowance check monitors this address alongside the two V2 exchanges — orders without this approval were rejected with "not enough balance / allowance".
  • New methods: refreshBalanceAllowance() / getBalanceAllowance(). The V2 CLOB maintains a cached balance-allowance view per API key; ensureReady() now auto-refreshes it after setting V2 approvals so the first order after onboarding goes through.
  • Amount math hardened against float-precision edge cases. Raw-units now derived via string-based decimal conversion instead of Math.trunc(x * 1e6) (TS) / (x * 1_000_000.0) as u64 (Rust), which could truncate to N-1 for prices that round-trip badly through float.
  • V2 order body now matches @polymarket/clob-client-v2 exactly — adds taker, postOnly, deferExec fields to the POST payload.
  • checkBalance() is V2-aware — reads pUSD on V2, USDC.e on V1. Previously always read USDC.e.
  • Market neg-risk detection fix (TS). The old check treated every market as neg-risk (truthiness of the response object rather than its neg_risk field), which signed orders against the wrong exchange on standard markets.
Migration: drop-in. trader.order(...) signature is unchanged; if you’re on V2 and your orders were going through before, they still will. If you had a custom ensureReady-equivalent flow, you may now drop the manual /balance-allowance/update call — ensureReady() handles it on fresh V2 approvals. The full V2 wire format, required approvals, fee math, and common failure modes are documented in each SDK at src/trading/V2_ORDER_FLOW.md (TS, Rust) / polynode/trading/V2_ORDER_FLOW.md (Python). End-to-end verification (from each public registry, fresh sandbox, real V2 orders against clob-v2.polymarket.com):
SDKInstalled fromV2 order placedCancelled
polynode-sdk@0.9.0npm install polynode-sdk0xb088a702... LIVE
polynode 0.9.0pip install polynode[trading]0x4906757c... LIVE
polynode 0.12.0cargo add polynode@0.120x6d3623c6... LIVE
Each sandbox pulled from the public registry, imported the SDK, placed a real V2 GTC BUY with builder attribution, confirmed status: "live" from the V2 CLOB, then cancelled. No local source overrides, no pre-compiled artifacts — same install path a new user follows. Docs fix also shipped today: the V2 section of /sdks/trading now inlines ensureReady / ensure_ready so a V2-first reader who pastes only that block hits a copy-paste runnable flow. Previously the snippet silently assumed the reader had already gone through the Quick Start section.

2026-04-20 — CLOB v2 API launched

New paid REST namespace /clobv2/* exposing every Polymarket CLOB v2 fill, position, builder, and neg-risk event. Backed by polynode’s own indexer on s4 — no external dependencies. 11 endpoints, all enriched with market metadata (question, slug, outcome, image, condition_id) inline:
  • GET /clobv2/trades — global fills, filterable by wallet/builder/market/time/size
  • GET /clobv2/positions — every open/closed v2 position
  • GET /clobv2/candles/{token_id} — OHLCV at 1m/5m/15m/1h/4h/1d with VWAP + buy/sell split
  • GET /clobv2/wallets/{address}/trades — per-wallet fill history with side
  • GET /clobv2/markets/{token_id}/{trades,volume,orderbook} — per-outcome aggregates
  • GET /clobv2/builders — v2 builder leaderboard (v2-exclusive)
  • GET /clobv2/neg-risk/events{,/{parent_id}/children} — multi-outcome event drilldown
  • GET /clobv2/volume/hourly — platform-wide hourly volume buckets
Every list response includes count, pagination.{limit,offset,has_more}, and rate-limit headers. Paid tier required; free-tier keys receive 402. See the overview for auth + numeric-precision conventions. /v2/onchain/* enrichment restored Trades / wallet-trades / market-trades / market-volume / candles on the legacy /v2/onchain/* endpoints now carry market, market_slug, outcome, image, and condition_id inline again. These fields had been quietly lost during a backend refactor.

2026-04-19 — Expanded orderbook wire format + Rust SDK 0.11.0

The orderbook WebSocket now delivers PM’s full per-level payload so you can maintain a tick-accurate local book without REST polling. Verified at 100% best-bid / 100% best-ask across 50 concurrent markets. What’s new in the price_change message Every entry in the assets array now includes four extra fields:
  • size — the absolute size at that level after the change. "0" means the level was removed.
  • side"BUY" (affects bids) or "SELL" (affects asks)
  • best_bid, best_ask — best bid/ask on the asset after the change
Old clients parsing only {asset_id, price} are unaffected — the new fields are additive. Multiple distinct levels on the same asset may now appear in one batch; repeat hits on the same (asset_id, price, side) within the 250ms coalesce window still merge to last-write-wins. See the orderbook message reference for the full field list. Rust SDK 0.11.0
  • PriceChangeAsset now carries size, side, best_bid, best_ask
  • New LocalOrderbook::apply_price_change(&PriceChange) — apply incremental updates directly to your local book
  • OrderbookEngine automatically applies price_change events to its shared state so every EngineView stays tick-accurate
  • OrderbookUpdate::LastTradePrice(BookTrade) — executed-trade events now deserialize cleanly (previously dropped)
Install:
cargo add polynode@0.11.0
Breaking: none. Enum variants added are additive under existing serde routing. Explicit exhaustive matches on OrderbookUpdate will need to handle the new LastTradePrice variant — add a _ => {} arm or match it explicitly.

2026-04-19 — V2-ready SDKs released (Rust 0.9.0, TypeScript 0.8.0, Python 0.8.0)

All three polynode trading SDKs are now wire-compatible with the Polymarket V2 exchange ahead of the April 28, 2026 cutover. Upgrade before then. V1 orders submitted after cutover will be rejected with order_version_mismatch. What changed (all three SDKs):
  • V2 CLOB POST body now includes "expiration": "0" to match @polymarket/clob-client-v2’s wire format
  • OrderParams now exposes a builder field (bytes32). Pass your builder code on each order to attribute trades on-chain. Defaults to zero (no attribution). Mint your V2 builder code at polymarket.com/settings?tab=builder
  • All V2 order placement paths live-verified against clob-v2.polymarket.com
Install:
cargo add polynode@0.9.0          # Rust
npm install polynode-sdk@^0.8.0   # TypeScript
pip install "polynode>=0.8.0"     # Python
Breaking (Rust only): OrderParams added a required struct field. If you construct OrderParams { ... } explicitly without ..Default::default(), add builder: None to your literal. No code change required if you use struct update syntax. See the V2 Migration Guide for the full cutover checklist, the single-line switch, and a troubleshooting reference for common V2 errors.

2026-04-18 — Rust SDK 0.8.1: batch orderbook API

New: batch + all-tracked queries

Every per-token orderbook method on OrderbookEngine and EngineView now has a batch and an all-tracked variant. One read lock, one round-trip, results returned as a HashMap<String, T> keyed by token ID.
let mids   = engine.midpoints(&ids).await;       // requested tokens
let mids   = engine.midpoints_all().await;        // every tracked token

// Same shape for: spreads / best_bids / best_asks / books
let books  = engine.books_all().await;            // full L2 for everything
Tokens not in local state are silently skipped, so callers can pass mixed lists without pre-filtering.

New: detect inactive markets

Each tracked token now records when its local copy was last touched. Use it to find markets that have stopped moving.
use std::time::Duration;

if let Some(ts) = engine.last_change("token_a").await {
    println!("last update {:?} ago", ts.elapsed());
}

let stale = engine.inactive_since(Duration::from_secs(60)).await;

New: direct state access

engine.state() returns the underlying Arc<RwLock<LocalOrderbook>> so callers can hold the lock and walk the full state themselves — useful for snapshotting all tokens at one consistent moment or building custom views.
let state = engine.state();
let guard = state.read().await;
let mids  = guard.midpoints_all();
let books = guard.books_all();

Install

cargo add polynode@0.8.1
All existing per-token methods (engine.midpoint(id), engine.book(id), etc.) are unchanged. This release is purely additive.

2026-04-17 — Unrealized P&L + redemption fix

New: unrealized_pnl field on positions

Every position returned by /v2/onchain/positions now includes an unrealized_pnl field showing the paper profit or loss on remaining open shares.
  • Resolved markets: uses the final settlement price (1forwinners,1 for winners, 0 for losers)
  • Active markets: uses the current market price, refreshed every 15 minutes

Fixed: realized P&L for redeemed positions

Positions that were closed via onchain payout redemption (rather than selling on the orderbook) now correctly reflect their realized P&L. Previously, these showed realized_pnl: 0 even when the position had a gain or loss.

2026-04-15 — Trade-indexed candles endpoint

New: GET /v2/onchain/candles/{token_id}

Server-built OHLCV candles from real onchain fills. Each candle includes open, high, low, close, total volume in USD and shares, separate buy and sell volume, trade count, and VWAP. Pagination is anchor-based: each request returns up to 1000 trades worth of candles, anchored at a timestamp, block number, or transaction hash. Walk older history by passing the cursor from the previous response. Same model used by major exchange APIs.
curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN_ID?resolution=1h" \
  -H "x-api-key: YOUR_KEY"
Resolutions: 1m, 5m, 15m, 1h, 4h, 1d. Optional gap_fill=true for charting libraries that expect a continuous time axis. Full reference at /api-reference/onchain/candles.

2026-04-14 — Positions sort fix + last_activity field

Fixed: /v2/onchain/positions wallet queries now sort by recency

When querying positions for a specific wallet, results are now ordered by the most recent on-chain activity for each position. Previously the order=desc parameter did not reflect recency, causing newer positions to appear below older ones. The most recently traded positions now appear at the top. Positions with no fill history (rare — acquired purely via split, merge, or redemption) are placed at the end.

New: last_activity response field

Wallet queries now include a last_activity field on each position, containing the unix timestamp of the most recent fill for that position. Use it to display “last traded” times in your UI.
{
  "wallet": "0x4aefd329896464da0ffb16c4ebcd083a4360c181",
  "token_id": "99969794444523158222638792918531145371262906249733117976401662588024805483979",
  "size": 0,
  "status": "closed",
  "market": "Suns vs. Lakers",
  "market_slug": "nba-phx-lal-2026-04-10",
  "outcome": "Lakers",
  "last_activity": 1776232830
}

Pagination behavior

Wallet queries return every position for the wallet in a single response (up to 500), ordered by recency. has_more is always false and no cursor is returned — request limit=500 once and read all results. Wallets with more than 500 lifetime positions are capped. Non-wallet queries (filtering by market_slug, condition_id, token_id, or min_size alone) continue to use cursor-based pagination.

2026-04-12 — SDK v0.7.0: Fee Escrow + Order History

New: Per-Order Fee Collection

The trading SDK now supports optional per-order fee collection with on-chain escrow. Platforms can charge fees on trades with a single config option.
npm install polynode-sdk@0.7.0
const trader = new PolyNodeTrader({
  polynodeKey: 'pn_live_...',
  feeConfig: { feeBps: 50 },  // 0.5% fee
});

const result = await trader.order({
  tokenId: '...',
  side: 'BUY',
  price: 0.55,
  size: 100,
});

console.log(result.feeEscrowTxHash); // on-chain TX hash
console.log(result.feeAmount);       // "0.0275" USDC
  • Automatic fee handling — fees are pulled into escrow before the order, distributed on fill, refunded on cancel
  • Cancel auto-refund — cancelling an order automatically returns the fee to the user’s wallet, inline in the same request
  • Affiliate revenue sharing — split fees with partners on a per-order basis via affiliate and affiliateShareBps
  • 72-hour safety net — if the operator doesn’t settle, users can self-refund on-chain
  • Zero overhead when disabledfeeBps: 0 (default) skips the escrow entirely with no behavior change
  • 7th approval — added to ensureReady() batch, gasless for Safe wallets

Improved: Order History

Local order history now persists fee escrow data (feeAmountRaw, escrowOrderId, feeEscrowTxHash). Existing databases auto-migrate on first open. No action required.

SDK Releases

  • TypeScript polynode-sdk@0.7.1 on npm — adds FeeConfig export
  • Rust polynode@0.7.1 on crates.io — fixes compile error in 0.7.0
  • Python polynode@0.7.2 on PyPI — fixes __version__ reporting
See the Fee Escrow Guide for the full architecture, security model, and code examples.

2026-04-11 — polynode-charts v0.1.5

New: createShortFormOverlay

One-liner to add price-to-beat overlays to any live chart. Renders interval buttons (5m/15m/1h), auto-discovers Polymarket short-form markets, draws a dashed price line, and shows live odds with a countdown timer.
import { createChart, createShortFormOverlay } from 'polynode-charts'

const chart = createChart('#btc')
const series = chart.addLineSeries({ color: '#f7931a' })
series.setLive(true)
chart.timeScale().goLive()

createShortFormOverlay(chart, series, { coin: 'btc' })

Improvements

  • Multi-outcome event discovery now prioritizes genuine multi-outcome markets (neg_risk: true) over resolved binary matches
  • MarketInfo.outcome field identifies which side of a binary market each token represents (“Yes” or “No”)
  • Price-to-beat badge renders correctly on the right axis for all price ranges
  • Orderbook tooltip no longer stays stuck after data refresh
  • Tighter chart padding for a denser, more professional look

2026-04-11 — polynode-charts v0.1.0

New npm package for interactive charting in the browser. Zero dependencies, Canvas 2D rendering at 60fps, purpose-built for prediction market data and live crypto price streams.

Install

npm install polynode-charts

What’s included

  • Candlestick, line, area, and volume series types with smooth animation
  • Live streaming mode with lerp animation, pulsing dots, and auto-scroll
  • Orderbook visualization with depth chart and spread display
  • Short-form market overlays — price-to-beat lines with live odds rotation for 5m/15m/1h crypto markets
  • Multi-outcome support for prediction markets (up to 20+ outcomes with auto-assigned colors)
  • Built-in data providersPolynodeProvider (REST), PolynodeOBProvider (WebSocket orderbook), ShortFormProvider (market discovery + rotation)
  • Interactive pan, zoom, scroll, pinch-to-zoom on mobile
  • Crosshair with snap-to-candle and OHLC tooltip

Quick start

import { createChart, PolynodeProvider } from 'polynode-charts'

const provider = new PolynodeProvider({ apiKey: 'pn_...' })
const chart = createChart('#chart', {
  rightPriceScale: { mode: 'probability' },
})

const series = chart.addCandleSeries({
  upColor: '#22c55e',
  downColor: '#ef4444',
})

const data = await provider.candles(tokenId, '1h')
series.setData(data)

Documentation

Full docs at Charts SDK Overview, including API reference, data providers, series types, examples, and configuration.
polynode-charts is the browser visualization companion to polynode-sdk (the Node.js data SDK). They are separate npm packages. Building a frontend? Install both.

2026-04-09 — Confirmed Fills on Status Updates

status_update events now include a confirmed_fills array with exact execution data from on-chain OrderFilled receipt logs. This is the same data source Polymarket’s own APIs read.

What changed

  • status_update events now carry a confirmed_fills field — an array of per-fill objects with exact price, size, fee, order hash, maker, taker, and token ID
  • Each fill is decoded directly from OrderFilled receipt logs on the CTF Exchange and NegRisk CTF Exchange contracts
  • Prices match Polymarket’s activity data exactly (verified across 1,300+ fills with zero price discrepancies)

Why this matters

polynode detects settlements 3-5 seconds before on-chain confirmation by decoding transaction calldata from the mempool. For single-maker fills, calldata prices are exact. For multi-maker fills (~5% of trades), the calldata only has aggregate amounts, so per-maker prices are estimated within 0.01-0.04. With confirmed_fills, you now get both: the fast pre-confirmation signal AND the exact on-chain execution data in a single subscription. Use whichever fits your use case.

Use cases

  • Copy trading: Use the pending settlement event (speed matters, price difference is negligible)
  • Analytics and bookkeeping: Use confirmed_fills from the status_update (exact prices, fees, and sizes)
  • Full lifecycle: Track both to compare pre-confirmation estimates against final execution

Size difference vs Polymarket

confirmed_fills reports gross token amounts from the OrderFilled event. Polymarket’s activity API reports sizes net of fees. The fee field on each fill lets you compute the net amount if needed.

Subscribe

No subscription changes needed. If you already subscribe to settlements, you’re already receiving status_update events with confirmed_fills.
{"action": "subscribe", "type": "settlements"}

Documentation


2026-04-08 — Redemption Event Streaming

New redemption event type in the WebSocket stream. Every time a user redeems their outcome tokens after a market resolves, you get a real-time event with the redeemer address, payout amount, condition ID, and full market metadata enrichment.

WebSocket Stream

  • New event type: redemption — available in the firehose and via event_types filter
  • Decoded from PayoutRedemption logs on the Conditional Tokens contract
  • Includes payout amount in USDC, redeemer wallet, outcome slots redeemed
  • Enriched with market title, slug, image, tokens map, neg_risk flag
  • Filter with min_size to see only winning redemptions, or track specific wallets

Subscribe

{"action": "subscribe", "type": "settlements", "filters": {"event_types": ["redemption"]}}

Documentation


2026-04-07 — Polymarket V2 Exchange Support

polynode now supports the Polymarket V2 exchange system alongside V1. All V2 contracts have been identified, decoded, and verified against live mainnet data.

Settlement Stream

  • V2 settlements are detected and streamed automatically — no subscription changes needed
  • Same event types: settlement, status_update, trade, deposit
  • PolyUSD wrapping/unwrapping events now included in deposit stream
  • V2 detection is automatic alongside V1 (both supported simultaneously)

Trading SDK (Rust v0.6.0, TypeScript v0.6.0, Python v0.7.0)

  • New ExchangeVersion::V2 / exchangeVersion: "v2" config option
  • V2 order signing with updated EIP-712 domain (version “2”)
  • wrapToPolyUsd() and unwrapFromPolyUsd() helper methods
  • getPolyUsdBalance() and getUsdceBalance() balance checking
  • V2 approval management (PolyUSD to V2 exchange contracts)
  • One config change to switch from V1 to V2 — everything else stays the same

Documentation

  • V2 Migration Guide — what changed, what stayed the same, SDK examples
  • PolyUSD Guide — how to wrap/unwrap PolyUSD with code examples
  • V2 Technical Details — contract addresses, order struct, event signatures (subscribers only)
  • Deposit and settlement event pages updated with V2 notes

What We Verified

  • V2 order placement tested live on the V2 CLOB
  • EIP-712 order hash matches the live V2 exchange contract on Polygon mainnet
  • PolyUSD wrapping detected through our event pipeline in real time
  • All existing market data, token IDs, and enrichment identical between V1 and V2

2026-04-05 — Short-Form Docs: Full Field Reference + Orderbook Guide

Updated the Short-Form Markets documentation with the complete ShortFormMarket field reference. All 14 fields are now documented, including conditionId, clobTokenIds, windowStart, windowEnd, outcomes, and outcomePrices which were previously undocumented. Added a new “Connect the Orderbook to Crypto Markets” section showing how to pass clobTokenIds from a short-form rotation event directly to OrderbookEngine for real-time depth data on crypto prediction markets. Includes TypeScript and Rust examples. No SDK changes. All fields were already available in the SDK — this is a docs-only update.

2026-04-04 — Crypto Price Fix (All SDKs)

Fixed stale price-to-beat values in short-form crypto markets. Polymarket changed the variant parameter on their crypto-price endpoint for 5-minute and hourly markets. The SDK now sends the correct values, matching what Polymarket’s own frontend uses. Affected intervals: 5-minute and hourly. 15-minute was unaffected.
npm install polynode-sdk@0.5.9    # TypeScript
cargo add polynode@0.5.8          # Rust
pip install polynode==0.6.2       # Python

2026-04-04 — TypeScript SDK v0.5.8

Fixed a bug where price_feed events from Chainlink subscriptions were silently dropped in the TypeScript SDK. The .on('price_feed', ...) handler now fires correctly.
const sub = await pn.ws
  .subscribe('chainlink')
  .feeds(['BTC/USD'])
  .send();

sub.on('price_feed', (event) => {
  console.log(`${event.feed}: $${event.price}`);
});
Update with npm install polynode-sdk@0.5.8.

2026-04-02 — Position Management (All SDKs)

Split, merge, and convert positions directly from the SDK. No need to interact with smart contracts manually. TypeScript — gasless execution via the Polymarket relayer:
await trader.split({ conditionId: '0x...', amount: 100 });
await trader.merge({ conditionId: '0x...', amount: 100 });
await trader.convert({ marketId: '0x...', outcomeIndices: [0, 1], amount: 100 });
Rust & Python — transaction builders that return ready-to-submit calldata:
tx = build_split_txn("0x...", 100.0, neg_risk=True)
tx = build_convert_txn("0x...", [0, 1], 100.0)
  • Position Management guide — full walkthrough with examples
  • Convert is unique to neg-risk multi-outcome markets (e.g. “Who will win the World Cup?”)
  • TypeScript SDK handles neg-risk vs standard market routing automatically

2026-04-02 — Positions Converted Event (WebSocket API)

New positions_converted event type for tracking position conversions on neg-risk multi-outcome markets. When a trader converts NO positions into USDC + YES positions on complementary outcomes (via the NegRiskAdapter convertPositions function), this event fires with the full decoded details.
{
  "data": {
    "event_type": "positions_converted",
    "event_title": "Hungary Parliamentary Election Winner",
    "stakeholder": "0x30cecdf29f069563ea21b8ae94492e41e53a6b2b",
    "converted_outcomes": ["Fidesz-KDNP", "TISZA"],
    "amount": 98,
    "market_id": "0x355e7310dd6e18ef5fa456de7ce1331bd8c7540c...",
    "index_set": "0x...0021",
    "neg_risk": true
  }
}
  • converted_outcomes decodes the bitmask into human-readable outcome names
  • event_title shows the parent multi-outcome event
  • Included by default in wallets and markets subscription types
  • Add explicitly via event_types: ["positions_converted"] for other subscription types
  • Fires ~13 times per minute across all active neg-risk markets

2026-04-01 — Builder Credentials (All SDKs)

TypeScript SDK v0.5.7 / Rust SDK v0.5.7 / Python SDK v0.6.1

Platforms can now pass their own Polymarket builder credentials for order attribution. All volume gets credited to your builder profile on the Builder Leaderboard.
const trader = new PolyNodeTrader({
  polynodeKey: 'pn_live_...',
  builderCredentials: {
    key: process.env.POLY_BUILDER_API_KEY,
    secret: process.env.POLY_BUILDER_SECRET,
    passphrase: process.env.POLY_BUILDER_PASSPHRASE,
  },
});
  • Get your builder credentials at polymarket.com/settings?tab=builder
  • polynode never stores your builder credentials. They’re used per-request for HMAC signing only.
  • When omitted, polynode’s default builder attribution is used (orders still go through).
Install:
npm install polynode-sdk@0.5.7    # TypeScript
cargo add polynode@0.5.7          # Rust
pip install polynode==0.6.1       # Python

2026-03-31 — TypeScript SDK v0.5.6 (Cache Crash Recovery)

TypeScript SDK v0.5.6

Fixes a crash that could occur when stopping the cache mid-backfill, and adds proper crash recovery for interrupted backfills. Graceful shutdown: cache.stop() now waits for any in-flight backfill operation to complete before closing the database. Previously, stopping during an active backfill could crash with a Cannot read properties of null error on the SQLite handle. Crash recovery: on startup, any backfills that were interrupted by a previous crash or kill are automatically detected and retried. The cache logs exactly what’s happening:
# Restart after crash — resumes incomplete backfills only
[PolyNodeCache] Reset 1 interrupted backfill(s) from previous session.
[PolyNodeCache] Backfilling 2 entities (1 page of 500 each) — ETA: ~1s

# Clean restart — all data persisted, no network calls needed
[PolyNodeCache] All 4 entities already backfilled, skipping.
Accurate logging: the startup log now only reports entities that actually need backfilling, instead of the total watchlist count.
npm install polynode-sdk@0.5.6

2026-03-31 — Equity Curve Endpoint

New: Trader Equity Curve

Full equity curve for any Polymarket wallet. Returns a time-ordered series of cumulative profit and loss across every position the wallet has ever taken. Endpoint: GET /v2/trader/{wallet}/equity?period=all&normalize=1 Features:
  • Raw P&L curve with realized gains/losses and mark-to-market on open positions
  • $1-normalized curve for copy-trade analysis: each position scaled to $1 of risk, showing expected returns per dollar deployed
  • Period filtering: 7d, 30d, 90d, 1y, all
  • Accuracy within 2-3% of Polymarket’s own P&L calculations (variance from real-time price differences on open positions only)
$1 normalization answers the question: “If I copy every trade this wallet makes but only risk $1 per position, what are my returns?” Useful for evaluating trader skill independent of position sizing.
curl "https://api.polynode.dev/v2/trader/0x.../equity?period=all&normalize=1" \
  -H "x-api-key: YOUR_KEY"
{
  "positions_count": 278,
  "open_count": 83,
  "raw": {
    "final_pnl": -395.90,
    "curve": [{"t": 1774555427, "pnl": 10.56}, ...]
  },
  "normalized": {
    "bet_size": 1,
    "final_pnl": -3.19,
    "curve": [{"t": 1774555427, "pnl": 0.07}, ...]
  }
}
See the full documentation for methodology details and response field reference.

Fix: Market Price Fetching

Fixed an issue where current market prices were not being fetched correctly for open positions across multiple endpoints. Price accuracy is now significantly improved for any endpoint that reports unrealized P&L.

2026-03-29 — Rust SDK v0.5.6 (Order Placement Fix)

Rust SDK v0.5.6

Critical fixes for order placement. The trading module now passes full end-to-end testing: wallet onboarding, order placement, cancellation, and order history. Order signing fixes:
  • Fixed EIP-712 struct hashing (domain type filtering)
  • Fixed signature v normalization for CLOB compatibility
  • Fixed amount rounding to match tick-size-based precision rules
  • Fixed salt generation range for CLOB compatibility
Authentication fixes:
  • Fixed HMAC base64 decoding for URL-safe encoded secrets
  • Fixed wallet address checksumming (EIP-55) in all auth headers and order payloads
Privy integration fixes:
  • Fixed address formatting in Privy wallet API calls
  • Fixed signature normalization in Privy response extraction
[dependencies]
polynode = { version = "0.5.6", features = ["trading"] }

2026-03-29 — Python SDK v0.6.0

Python SDK v0.6.0

Trading module fixes and Privy server-side wallet support. Trading fixes:
  • Fixed CLOB credential creation (endpoint, auth headers, EIP-712 message structure)
  • Fixed order signing (domain name, signature format, payload serialization)
  • Fixed HMAC authentication for URL-safe base64 secrets
  • Fixed fetch_tick_size and fetch_neg_risk response parsing
  • Full end-to-end verified: wallet generation, onboarding, order placement, cancellation, gasless flow
New: Privy signer
  • PrivySigner for server-side trading with Privy-managed wallets
  • No private key needed, signing via Privy’s wallet API
  • Works with all trading methods (ensure_ready, order, cancel_all, etc.)
Other fixes:
  • markets_by_category() now correctly filters via query parameter
  • Fixed subscription filters code example in docs
pip install polynode==0.6.0
# With trading support:
pip install "polynode[trading]==0.6.0"

2026-03-29 — Rust SDK v0.5.0 (Feature Parity)

Rust SDK v0.5.0

Major release bringing the Rust SDK to feature parity with the TypeScript SDK. New REST endpoints (12 methods):
  • Orderbook REST: orderbook_rest(), midpoint(), spread()
  • Enriched data: leaderboard(), trending(), activity(), movers()
  • Trader analytics: trader_profile(), trader_pnl()
  • Events: event(), search_events(), markets_by_category()
Trading module (--features trading):
  • PolyNodeTrader for full order lifecycle on Polymarket
  • Wallet generation, onboarding (auto-detect Safe/Proxy), order placement, cancellation
  • Native EIP-712 signing via TradingSigner trait + PrivateKeySigner
  • CREATE2 address derivation for Safe and Proxy wallets
  • CLOB authentication with L2 HMAC headers
  • Builder attribution via co-signer proxy
  • Local SQLite storage for credentials and order history
  • On-chain approval and balance checks
Privy integration (--features privy):
  • PrivySigner for server-side trading with Privy-managed wallets
  • Pure HTTP-based, no native SDK dependency
Redemption watcher:
  • Monitor wallets for redeemable positions after oracle resolution
  • Fires alerts with winner detection and payout estimation
[dependencies]
polynode = "0.5"

# With trading:
# polynode = { version = "0.5", features = ["trading"] }
Full documentation: Rust SDK

2026-03-29 — Trading Module v0.5.5 (TypeScript SDK)

TypeScript SDK v0.5.5

  • EOA auto-approvals: ensureReady() now automatically sends approval transactions for EOA wallets with proper nonce management. Requires 0.05 MATIC in the wallet ($0.01).
  • Fix: createPrivySigner, createPrivyClient, and other Privy helpers are now correctly exported from the package root. In v0.5.2 these were only reachable via internal module paths.
  • Privy server-auth integration: createPrivySigner() and createPrivyClient() for server-side trading with Privy-managed wallets.
  • Unlimited gasless operations: Paid tiers get unlimited gasless on-chain operations. Free tier gets basic gasless onboarding.
  • ethers v5/v6 compatibility: Safe transaction signing works with both ethers v5 and v6.
npm install polynode-sdk@0.5.5 viem better-sqlite3 @polymarket/clob-client @polymarket/builder-relayer-client @polymarket/builder-signing-sdk
Full documentation: Trading

2026-03-29 — Trading Module (TypeScript SDK v0.5.0)

TypeScript SDK — Order Placement on Polymarket

Place and manage orders on Polymarket through the polynode SDK. One function call handles wallet setup, credential creation, approvals, and order placement.
npm install polynode-sdk@0.5.0 viem better-sqlite3 @polymarket/clob-client @polymarket/builder-relayer-client @polymarket/builder-signing-sdk
One-call onboarding:
import { PolyNodeTrader } from 'polynode-sdk';

const trader = new PolyNodeTrader({ polynodeKey: 'pn_live_...' });

// Auto-detects your wallet type. Deploys Safe, sets approvals, creates credentials.
const status = await trader.ensureReady('0xYOUR_PRIVATE_KEY');

const result = await trader.order({
  tokenId: '...',
  side: 'BUY',
  price: 0.55,
  size: 100,
});

await trader.cancelOrder(result.orderId);
Key features:
  • Auto-detection: Pass your private key. The SDK checks if you have a Safe, Proxy, or EOA on-chain and uses the right one. No wallet type guessing.
  • Wallet generation: PolyNodeTrader.generateWallet() for users starting from scratch.
  • Gasless onboarding: Safe deployment + 6 token approvals in one call, ~10 seconds, zero gas.
  • Local credential custody: CLOB credentials stored in local SQLite. Export, import, and back up freely.
  • Builder attribution: Orders route through polynode’s relay for affiliate tracking. If the relay is down, orders fall back to direct CLOB submission.
  • All three wallet types: EOA (type 0), Proxy (type 1, legacy Magic Link), Safe (type 2, browser wallets).
  • Dome drop-in: Same @polymarket/clob-client signing under the hood. Import existing Dome credentials with linkCredentials().
Full documentation: Trading | Dome Migration

2026-03-28 — Onchain Data Endpoints + Market Metadata Enrichment

Enrichment: All Onchain Endpoints Now Include Market Metadata

Every onchain endpoint now returns enriched data with human-readable market context. Responses include market (question text), slug, outcome label, image, and condition_id alongside the raw onchain data. Powered by a full backfill of all 706K+ Polymarket markets into a local metadata store that refreshes every 5 minutes.

New: Onchain Data Section

Seven new endpoints sourced directly from blockchain settlement data. These provide complete, accurate data that never times out or drops records, unlike endpoints that rely on upstream APIs. All onchain endpoints are under /v2/onchain/ and documented in the new Onchain section of the API reference.

Wallet endpoints

  • GET /v2/onchain/wallets/{addr}/trades — Complete trade fill history for any wallet. Every fill, every counterparty, every fee. Replaces the unreliable upstream trade API that drops records for heavy wallets.
  • GET /v2/onchain/wallets/{addr}/redemptions — All redemptions with payout amounts. See who cashed out, when, and how much. Not available anywhere else.
  • GET /v2/onchain/wallets/{addr}/activity — Splits, merges, and multi-outcome conversions. Shows how wallets interact with the CTF contract beyond simple trading.

Market endpoints

  • GET /v2/onchain/markets/{tokenId}/trades — Complete trade tape for any market token.
  • GET /v2/onchain/markets/{tokenId}/volume — Lifetime volume stats: total trades, buys, sells, USDC volume with buy/sell breakdown.
  • GET /v2/onchain/markets/{conditionId}/oi — Per-market open interest in USDC.
  • GET /v2/onchain/oi — Global platform open interest (currently ~$469M).
All endpoints support limit and offset pagination where applicable. Responses are cached for 1-5 minutes depending on the endpoint.

Wallet P&L: Complete Position History + Accurate Realized P&L

What changed

The existing GET /v1/wallets/{addr}/positions endpoint only returns open positions. Once a market resolves or a position is fully sold, it disappears from the response. The existing GET /v1/wallets/{addr}/trades endpoint silently drops trades for high-volume wallets (we verified one wallet showing 403 trades through the API vs 4,405 actual onchain trades). This made it impossible to compute accurate P&L through our API. That’s fixed now. Two new v2 endpoints return complete position history with accurate P&L sourced from onchain settlement data. Per-position realized_pnl values come from the same onchain settlement data Polymarket uses and match individual position P&L to the penny. The total_realized_pnl aggregate measures total realized gains, which is different from Polymarket’s profile PnL (see docs for a full breakdown).

GET /v2/wallets/{address}/positions/onchain

One call. No pagination. All positions (open + closed). Unlike the v1 positions endpoint which requires the SDK to paginate through trades page by page, this endpoint returns the complete picture in a single request. The server handles all internal pagination (up to 20,000 positions).
curl "https://api.polynode.dev/v2/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/positions/onchain" \
  -H "x-api-key: YOUR_KEY"
{
  "wallet": "0xbddf61af533ff524d27154e589d2d7a81510c684",
  "source": "onchain",
  "count": 873,
  "open_count": 334,
  "closed_count": 288,
  "total_realized_pnl": 17183579.48,
  "positions_with_pnl": 309,
  "positions": [
    {
      "token_id": "3415885798119615...",
      "size": 0,
      "avg_price": 0.316481,
      "realized_pnl": 447182.95,
      "total_bought": 654236.31
    }
  ]
}
FieldDescription
countTotal positions (open + closed)
open_countPositions still held (size > 0)
closed_countFully exited positions with nonzero P&L
positions_with_pnlNumber of positions with nonzero realized P&L
total_realized_pnlSum of all realized_pnl values
positions[].realized_pnlProfit or loss for this position in USDC
positions[].avg_priceVolume-weighted average entry price
positions[].total_boughtTotal tokens acquired
Responses are cached for 5 minutes. First request takes 200ms-3s depending on position count. Cached responses return in under 50ms.

GET /v2/wallets/{address}/closed-positions

Closed positions with full metadata (title, outcome, slug). Paginated.
curl "https://api.polynode.dev/v2/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/closed-positions?limit=3&sortBy=REALIZEDPNL&sortDirection=DESC" \
  -H "x-api-key: YOUR_KEY"
Parameters: limit (max 50), offset, sortBy (REALIZEDPNL, AVGPRICE, PRICE, TITLE, TIMESTAMP), sortDirection (ASC/DESC).

SDK: Automatic onchain P&L backfill

The TypeScript (polynode-sdk@0.4.10) and Rust (polynode@0.4.3) SDKs now fetch onchain position data automatically during backfill. No code changes needed if you’re already using cache.start(). The backfill makes one additional API call per wallet (the onchain positions endpoint above) and stores the results in your local SQLite database.
npm install polynode-sdk@0.4.10
Before: The SDK backfilled trades page by page (up to 6 requests per wallet) and computed P&L from incomplete trade history. Wallets with dropped trades got wrong numbers. After: One call per wallet returns all positions with precomputed P&L. The SDK stores this in SQLite and uses it as the source of truth for all P&L queries.
const cache = new PolyNodeCache(client);
await cache.start();

// P&L from local cache — no API calls
const pnl = cache.computeRealizedPnl("0xbddf61af533ff524d27154e589d2d7a81510c684");
{
  "wallet": "0xbddf61af533ff524d27154e589d2d7a81510c684",
  "total_realized_pnl": 17183579.48,
  "total_unrealized_pnl": 3030.53,
  "total_pnl": 17186610.01,
  "confidence": "full",
  "trades_analyzed": 403,
  "tokens": [
    {
      "token_id": "3415885798119615...",
      "market_title": "Nuggets vs. Warriors",
      "outcome": "Warriors",
      "realized_pnl": 447182.95,
      "avg_cost": 0.3170,
      "remaining_size": 0
    }
  ]
}
trades_analyzed reflects cached trades from the REST API (403 in this case). The total_realized_pnl and per-token realized_pnl values come from onchain settlement data and are accurate regardless of cached trade count. walletDashboard() now includes realized_pnl, pnl_confidence, and token_pnl fields:
const dash = cache.walletDashboard("0xbddf61af533ff524d27154e589d2d7a81510c684");
// dash.realized_pnl     → 17183579.48
// dash.pnl_confidence   → "full"
// dash.token_pnl        → per-token P&L breakdown
// dash.total_pnl        → realized + unrealized combined

Crypto Price Streaming (WebSocket)

Real-time cryptocurrency prices are now available on the same WebSocket connection you already use for settlements and oracle events. Seven feeds, each updating roughly once per second: BTC/USD, ETH/USD, SOL/USD, BNB/USD, XRP/USD, DOGE/USD, HYPE/USD Subscribe with {"action": "subscribe", "type": "chainlink"}. Optionally filter to specific feeds:
{
  "action": "subscribe",
  "type": "chainlink",
  "filters": { "feeds": ["BTC/USD", "ETH/USD"] }
}
Each price update includes bid, ask, and mid price:
{
  "type": "price_feed",
  "feed": "BTC/USD",
  "timestamp": 1774675338,
  "data": {
    "feed": "BTC/USD",
    "price": 66240.86,
    "bid": 66234.85,
    "ask": 66243.30,
    "timestamp": 1774675338
  }
}
BTC/USD includes actual bid/ask spread. Other feeds currently report bid and ask equal to the mid price. See the full documentation for connection examples, feed details, and event format reference.

Crypto REST Endpoints

Five new REST endpoints for crypto prediction market data:
  • GET /v1/crypto/markets — All crypto prediction markets with volume, liquidity, and open interest (3 min cache)
  • GET /v1/crypto/candles?symbol=BTC — ~30 five-minute OHLC candles (30s cache)
  • GET /v1/crypto/price?symbol=BTC&window={epoch} — Open/close price for a specific market window (10s cache)
  • GET /v1/crypto/active — Currently active 5-minute up-or-down markets across all 7 coins with live odds (30s cache)
  • GET /v1/crypto/series — Recurring crypto market series across timeframes (5 min cache)
curl "https://api.polynode.dev/v1/crypto/candles?symbol=ETH" \
  -H "x-api-key: YOUR_KEY"
Supported symbols: BTC, ETH, SOL, BNB, XRP, DOGE, HYPE. See the REST API reference for full parameter details and response samples.

2026-03-26

API — Wallet Resolver

New endpoint that instantly resolves any Polymarket wallet identity. Pass a proxy wallet address, EOA address, or username and get back all three.
curl "https://api.polynode.dev/v1/resolve/DTCahill" \
  -H "x-api-key: YOUR_KEY"
{
  "safe": "0x0ecdd241ec1bc84a40b8142bebe65787aee97514",
  "eoa": "0xddd57cce99ca962fe23aa2da95b139f86a241459",
  "username": "DTCahill"
}
Backed by a pre-indexed table of ~3 million Polymarket wallets, updated daily. Lookups are sub-millisecond. Accepts any of the three identifiers as input:
  • GET /v1/resolve/{safe_address} — proxy wallet to EOA + username
  • GET /v1/resolve/{eoa_address} — EOA to proxy wallet + username
  • GET /v1/resolve/{username} — username to both addresses (case-insensitive)
See the full documentation for details.

API — order_hash on Settlements and Trades

Every settlement trade and confirmed trade event now includes an order_hash field — the EIP-712 hash that uniquely identifies a limit order on Polymarket’s order book. Why this matters: A single limit order can be partially filled across multiple transactions. The order_hash stays the same across all of them, letting you track order lifecycle, group partial fills, and build order-level analytics.
  • Settlement events: order_hash appears on each trade inside the trades[] array. Available on pending (pre-chain) settlements, computed from transaction calldata before block confirmation.
  • Trade events: order_hash appears as a top-level field, extracted directly from the on-chain OrderFilled event log.
{
  "type": "trade",
  "data": {
    "order_hash": "0x916fa5c2728c93e19ea5d7c254b07234815ad01e59597573c09d852fe53ee564",
    "maker": "0x4b8cf80092c60b9b23d22133eb63eb4508fe4d31",
    "price": 0.71,
    "size": 3.0
  }
}
No SDK update required — the field is automatically included in the WebSocket stream.

API — order_hashes on Status Updates

The status_update event now includes an order_hashes field containing every EIP-712 order hash from the original pending settlement. This lets you correlate confirmed settlements back to specific limit orders without needing to store state from the initial pending event.
{
  "type": "status_update",
  "data": {
    "tx_hash": "0xbb26c8cd...",
    "order_hashes": [
      "0xafc3488df3505c4dbba8797b8b6c645630c8ee9decf811a0acc4d63fdecfdd36",
      "0x9b5a06de7196bde87b4d8baa6cb80681151338d263fac0e643a2d53c4497e27f"
    ],
    "maker_wallets": ["0xd408...", "0x9443..."],
    "latency_ms": 3959
  }
}
Purely additive. Existing clients are unaffected.

TypeScript SDK — RedemptionWatcher Memory Management

The RedemptionWatcher now automatically evicts resolved conditions and zero-size positions from its internal index. This keeps memory bounded to only active, non-zero positions regardless of how long the watcher runs or how many wallets it tracks. Previously, resolved markets and fully-sold positions accumulated in memory indefinitely. For long-running services tracking thousands of wallets, this could grow to hundreds of megabytes over weeks. What changed:
  • Resolved conditions are removed from the index immediately after alerts fire
  • Positions that drop to zero size (full sell) are removed automatically
  • refreshInterval default changed from 0 (disabled) to 300_000 (5 minutes). This periodic REST refresh acts as a safety net, re-populating any positions missed during brief WebSocket disconnections.
No breaking changes. Alert behavior is identical. If you were explicitly setting refreshInterval: 0, that still works.
npm install polynode-sdk@latest
See RedemptionWatcher — Memory Management for details.

Trader Profile — EOA Wallet Resolution

The /v1/trader/{wallet} endpoint now returns an eoaWallet field that resolves the underlying EOA (externally owned account) for Polymarket proxy wallets. This is derived onchain from the Gnosis Safe contract.
{
  "wallet": "0xc2e7800b5af46e6093872b177b7a5e7f0563be51",
  "eoaWallet": "0xb49e5499562a4bc3345c1a1f2db13a5360dfddac",
  "name": "beachboy4",
  ...
}
For older lightweight proxy wallets, eoaWallet returns null.

Tier-Aware Rate Limits on Data-Heavy Endpoints

A handful of data-heavy endpoints now have their own per-key rate limits that scale with your plan:
TierLimit
Free1 req/sec
Starter30 req/sec
Growth50 req/sec
Enterprise100 req/sec
Affected endpoints: /v1/trader/{wallet}, /v1/trader/{wallet}/pnl, /v1/leaderboard, /v1/trending, /v1/activity, /v1/event/{slug}, /v1/movers, /v2/markets/{category}. All other REST endpoints use your plan’s standard rate limit. See Rate Limits for full details.

2026-03-25

TypeScript SDK — RedemptionWatcher

New high-level class that monitors wallets and fires alerts the instant any position becomes redeemable on-chain.
npm install polynode-sdk@latest
import { RedemptionWatcher } from 'polynode-sdk';

const watcher = new RedemptionWatcher({ apiKey: 'pn_live_...' });

watcher.on('alert', (alert) => {
  if (alert.isWinner) {
    console.log(`${alert.wallet} can redeem "${alert.marketTitle}" — ~$${alert.estimatedPayoutUsd}`);
  }
});

await watcher.start(['0xabc...', '0xdef...']);
What it does: Fetches wallet positions via REST, indexes by condition_id, subscribes to the oracle stream for condition_resolution events, and cross-references in real-time. Optional live position tracking via the wallets stream keeps sizes accurate as users trade. Key features:
  • Runtime addWallets() / removeWallets() with automatic re-subscription
  • Callback (.on('alert', ...)) and async iterator (for await) consumption
  • Winner detection with payout estimates
  • Full market metadata on every alert (title, slug, image, outcomes)
See the full documentation for lifecycle, configuration, and a production example.

Oracle Stream — condition_resolution Event

The oracle WebSocket stream now includes condition_resolution events. This is the on-chain signal that positions are redeemable on the Conditional Tokens contract. Previously, the stream included resolution events (outcome decided on the UMA adapter) but not the separate on-chain step where reportPayouts() is called on the CTF contract. For neg-risk markets (the majority on Polymarket), there is a ~2-3 minute gap between these two events. condition_resolution closes that gap. Subscribe and filter:
{
  "action": "subscribe",
  "type": "oracle",
  "event_types": ["condition_resolution"]
}
Payload includes: resolved_price, payouts, condition_id, question_id, and full market metadata (title, outcomes, token IDs, image). Use case: Trigger redemption workflows the instant positions become redeemable, instead of polling or waiting for the Polymarket UI to update. SDK support: Available in Rust SDK polynode 0.4 via OracleEventType::ConditionResolution. TypeScript SDK update coming soon.

TypeScript SDK v0.4.8 — Composable Leaderboard Builder

The local cache now supports composable leaderboards with multi-metric ranking, market filtering, slug pattern matching, wallet scoping, and time windows. New: Call cache.leaderboard() with no arguments to get a LeaderboardBuilder:
const rows = cache.leaderboard()
  .metrics(['total_pnl', 'volume', 'win_rate'])
  .slugs(['*election*'])
  .since(weekAgo)
  .limit(10)
  .build();

// Each row: { wallet, label, rank, metrics: { total_pnl, volume, win_rate } }
7 new metrics (11 total): roi, realized_pnl, volume, avg_trade_size, largest_win, largest_loss, market_count. Builder methods:
  • .metric() / .metrics() — single or multi-metric per row
  • .sortBy() / .sort('ASC' | 'DESC') — control ranking
  • .markets([conditionIds]) — filter by market
  • .slugs([patterns]) — glob match on market slugs (*election*, btc-*)
  • .category({ name, slugs }) — reusable named slug groups
  • .wallets([addrs]) — rank a wallet subset instead of full watchlist
  • .since(ts) / .until(ts) — time window for trade metrics
  • .limit(n) — top N
Backward compatible. Existing cache.leaderboard('total_pnl') still returns the same LeaderboardEntry[] format.
npm install polynode-sdk@0.4.8
Full documentation: Local Cache — Leaderboard Builder

WebSocket — Application-Level Keepalive

Cloud platform reverse proxies (Railway, Render, Heroku, AWS ALB, fly.io) can intercept WebSocket Ping/Pong control frames, preventing the server from detecting that your client is still alive. This caused connections to drop after 1-2 minutes with an empty error and no close frame. What changed:
  • The server now accepts any incoming message (not just Pong frames) as proof the client is alive
  • New {"action": "ping"} message returns {"type": "pong", "ts": ...} as an application-level keepalive
  • Stale connection timeout extended from 90 seconds to 5 minutes
  • Stale disconnects now include a close frame with a reason, instead of a silent drop
If you’re running on a cloud platform, add a periodic ping to your connection:
async def keepalive():
    while True:
        await asyncio.sleep(30)
        await ws.send(json.dumps({"action": "ping"}))

ping_task = asyncio.create_task(keepalive())
If you’re running on bare metal or a VM with no reverse proxy in front of your client, this doesn’t affect you. Standard WS Ping/Pong still works as before. See WebSocket Overview for full connection examples.

WebSocket — Reconnect Gap-Fill with since

Subscribe messages now accept an optional since filter (UNIX milliseconds). When set, the initial snapshot returns all events after that timestamp instead of just the most recent N, letting you fill gaps after a disconnect or cold start without missing data.
{
  "action": "subscribe",
  "type": "settlements",
  "filters": { "since": 1774412600000 }
}
The response is the same snapshot message you already handle. Existing clients are unaffected — since is fully optional. Lookback windows by tier:
TierMax lookback
Free30 seconds
Starter2 minutes
Growth5 minutes
Enterprise5 minutes
If since is older than your tier’s window, it’s automatically clamped. Within the window, there is no event count limit — you get everything that matches your filters.

2026-03-24

API — Onchain Redemption State on Wallet Positions

The /v1/wallets/{addr}/positions and /v1/wallets/positions endpoints now include onchain redemption data for resolved markets. Three new fields are returned on positions where redeemable is true:
  • redeemedtrue if the wallet has burned their tokens onchain (claimed USDC payout), false if tokens are still held.
  • unredeemed_balance — Number of tokens still held onchain. Tells you exactly how much hasn’t been claimed.
  • unredeemed_usd — USD value of unclaimed tokens ($1 per token on winning positions).
This data is not available from Polymarket’s API. polynode checks the Conditional Token Framework contract onchain in a single batched call, adding ~130ms only when redeemable positions are present. Active (unresolved) positions are unaffected.
curl "https://api.polynode.dev/v1/wallets/0x62d2.../positions" \
  -H "x-api-key: pn_live_..."
{
  "title": "Lana Del Rey divorce in 2025?",
  "redeemable": true,
  "redeemed": false,
  "unredeemed_balance": 418.73,
  "unredeemed_usd": 418.73,
  "size": 418.73
}

2026-03-23

API — Event Search & Token IDs

Two changes to event endpoints:
  • New: GET /v1/events/search?q=...&limit=N — search events by text query. Returns events with all sub-markets, each including tokenId (YES token for CLOB price history via /v1/candles) and current price. Multi-outcome events like “How many Fed rate cuts in 2026?” return as a single result with all outcomes.
  • Updated: GET /v1/event/{slug} — now includes tokenId on every market in the response. Previously, token IDs were not available on this endpoint.
curl "https://api.polynode.dev/v1/events/search?q=recession&limit=5" \
  -H "x-api-key: pn_live_..."
Rate limit: 1 request per second per API key (shared with other enriched data endpoints).

TypeScript SDK v0.4.6

  • New: searchEvents(query, { limit }) — typed method for event search, returns EventSearchResponse
  • New types: EventSearchResponse, EventSearchResult, EventSearchMarket
  • Updated: EventMarket now includes optional tokenId field
npm install polynode-sdk@0.4.6
const results = await pn.searchEvents('Fed rate', { limit: 5 });
for (const event of results.events) {
  console.log(event.title, `(${event.markets.length} outcomes)`);
  for (const m of event.markets) {
    // Use tokenId to fetch price history
    const candles = await pn.candles(m.tokenId, { resolution: '1h' });
  }
}

2026-03-21

TypeScript SDK v0.4.4 — Enriched Data Methods

Eight new typed methods on the PolyNode client for all enriched data endpoints:
  • leaderboard() — top 20 traders by profit or volume, with period filtering
  • trending() — carousel, breaking markets, hot topics, featured events, and biggest movers
  • activity() — platform-wide trade feed (50 most recent)
  • movers() — markets with largest 24h price swings
  • traderProfile(wallet) — full trader stats: PnL, volume, trades, largest win
  • traderPnl(wallet, { period }) — cumulative PnL time series
  • event(slug) — full event detail with all sub-markets
  • marketsByCategory(category) — browse markets by category
All methods are fully typed with dedicated response interfaces. Install:
npm install polynode-sdk@0.4.4

TypeScript SDK v0.4.3 — Cache UI Primitives

Four new features for building dashboards on top of the local cache.
  • View methodswatchlistSummary(), walletDashboard(), leaderboard(), marketOverview(). Pre-shaped data for common dashboard patterns. No SQL, no aggregation.
  • Reactive subscriptionsonChange() and onWalletChange() fire callbacks when new trades or settlements land from the live WebSocket stream. Returns an unsub() function for cleanup.
  • Export helpersexportCSV(), exportJSON(), exportRows() dump filtered data for charting libraries, spreadsheets, or custom analysis.
  • Query builder — Chainable fluent API: .wallet(), .side(), .since(), .market(), .minPnl(), .limit(), .run(). Complex filters without writing SQL.
npm install polynode-sdk@0.4.3
Full documentation: Local Cache

2026-03-21

API — Enriched Data Endpoints

Eight new REST endpoints for trader analytics, market discovery, and platform trends.
  • GET /v1/leaderboard — Top 20 traders ranked by profit or volume (daily, weekly, monthly, all-time)
  • GET /v1/trader/{wallet} — Full trader profile: PnL, volume, trade count, largest win, portfolio value
  • GET /v1/trader/{wallet}/pnl — PnL time series at 4 resolutions (1D, 1W, 1M, ALL)
  • GET /v1/trending — Carousel highlights, breaking markets, hot topics, featured events, biggest movers
  • GET /v1/activity — Platform-wide live trade feed (last 50 trades with tx hashes)
  • GET /v1/event/{slug} — Full event data with all sub-markets, outcome prices, condition IDs
  • GET /v1/movers — Markets with the largest 24h price changes
  • GET /v2/markets/{category} — Category market listings with counts (crypto, politics, sports, etc.)
Rate limit: 1 request per second per API key. Responses cached 1-3 minutes.

TypeScript SDK v0.4.1

  • Fix: ESM imports now work correctly. v0.4.0 crashed on cache.start() when using import syntax.
  • Fix: Eliminated MODULE_TYPELESS_PACKAGE_JSON Node.js warning.
  • New: getActiveTestWallet() / getActiveTestWallets() — returns known-active wallet addresses for testing and examples.
npm install polynode-sdk@0.4.1

API — Wallet positions fix

  • Fixed firstTradeAt / lastTradeAt returning null for certain wallets on GET /v1/markets/{id}/positions?includeTrades=true.
  • Added fallback query path for wallets where the primary trade lookup returns empty.

API — Per-endpoint rate limiting

  • includeTrades=true on market positions now has its own 20 req/min rate limit per key.
  • Separate bucket from standard rate limit — doesn’t consume your normal quota.

2026-03-21

TypeScript SDK v0.4.0

  • New: Local Cache — SQLite-backed local storage for instant offline queries.
  • Backfill wallet history in seconds (1 request per wallet, up to 500 trades).
  • Live WebSocket stream keeps the cache up to date automatically.
  • Query trades, positions, and settlements locally with zero API calls.
  • Watchlist file with hot-reload and runtime add/remove API.
  • Full documentation at Local Cache.

Rust SDK v0.4.0

  • New: Local Cache ported from TypeScript. Same architecture, same SQL schema, same query methods.
  • Builder pattern: PolyNodeCache::builder(client).db_path(...).build()?
  • rusqlite (bundled) + notify as optional dependencies behind cache feature flag.

2026-03-20

Web Frontend

  • Speed comparison test redesigned with 3-second warm-up period, win % scoreboard, and visual win ratio bar.
  • Updated “Run this test yourself” code snippet to match new logic.

2026-03-19

TypeScript SDK v0.3.0

  • New: OrderbookEngine — higher-level orderbook client with shared state and filtered views per component.
  • New: LocalOrderbook — local orderbook state management.
  • New: ShortFormStream — condensed event stream for bandwidth-constrained environments.

API

  • Orderbook REST endpoints: /v1/orderbook/{token}, /v1/midpoint/{token}, /v1/spread/{token}.
  • Wallet trades limit parameter now allows up to 1,000 (was 500).

2026-03-15

API v1 — Initial Public Release

  • WebSocket streaming: settlements, trades, prices, blocks, wallets, markets, large trades, oracle, chainlink
  • REST API: markets, search, candles, stats, settlements, wallets
  • Orderbook streaming via ob.polynode.dev
  • RPC endpoint via rpc.polynode.dev — JSON-RPC with TX #1 delivery
  • CLI (pn) — stream events, query markets, manage API keys from the terminal