polynode
Python SDK for the PolyNode real-time prediction market data platform.
Current in v0.12.2: Python provides the same core capabilities as the TypeScript and Rust SDKs: complete V3 API access, the V3 perps WebSocket, reconnect-aware settlement delivery, and PN1 orderbook integrity. Unknown additive events remain available as raw payloads, decimal values remain precision-safe, and any local queue eviction is reported.
New in v0.11.0: Current-production parity. Trading now defaults to CLOB V2 on clob.polymarket.com, uses PolyNode's public builder attribution unless overridden, omits removed V1 wire fields, and supports V2 GTD expiration. Managed 5-minute, 15-minute, and 4-hour streams select the required 30/60-second Chainlink TWAP lookbacks on a dedicated connection and reconnect/resubscribe at every market rotation. WebSocket models, presets, and filters now cover current redemption, position-conversion, dome/fill, and PM2 combo events. REST position queries now include redeemable/condition filters, multi-wallet batches, and market-holder views; connection and status observability match the current public API.
New in v0.10.8: POLY_1271 V2 order signatures now normalize the ERC-7739 TypedDataSign recovery byte to Ethereum v=27/28 for on-chain ERC-1271 validation.
In v0.10.7: Polymarket V2 deposit-wallet trading fixes. ensure_ready() detects deployed POLY_1271 wallets correctly, V2 type-3 orders use the deposit wallet as both maker and signer, and existing local credentials can be repaired by rerunning ensure_ready().
Install
pip install polynode
For trading support:
pip install 'polynode[trading]'
Quick Start
REST API
from polynode import PolyNode
with PolyNode(api_key="pn_live_...") as pn:
status = pn.status()
connections = pn.connections()
markets = pn.markets(count=10)
settlements = pn.recent_settlements(count=5)
wallet_positions = pn.wallet_positions(
address, redeemable=True, condition_id=condition_id
)
batch_positions = pn.multi_wallet_positions([address, second_address], limit=100)
market_positions = pn.market_positions(
condition_id, sort_by="CURRENT_VALUE", min_size=0.01
)
onchain_positions = pn.wallet_onchain_positions(
address, since=window_start, tag_slug="crypto"
)
Sports and Online Context
from polynode import PolyNode
with PolyNode(api_key="pn_live_...") as pn:
state = pn.sports_game_state(
"nba-cle-nyk-2026-05-31",
price_limit_tokens=20,
)
context = pn.sports_game_context(
"nba-cle-nyk-2026-05-31",
sources=["online"],
query_set="injuries",
max_queries=2,
max_per_query=5,
include_state=True,
)
web = pn.search_online(
"Cavaliers Knicks injury news",
max_results=5,
)
Async REST
import asyncio
from polynode import AsyncPolyNode
async def main():
async with AsyncPolyNode(api_key="pn_live_...") as pn:
status = await pn.status()
markets = await pn.markets(count=10)
asyncio.run(main())
Complete V3 API
V3 includes wallets, combos, rewards, credits, identities, markets, builders, profiles, perps, crypto, sports, backtesting, and other current product families. execute() gives you access to all 120 current V3 operations through one consistent Python interface.
import asyncio
from polynode import AsyncPolyNode
async def read_v3(address: str):
async with AsyncPolyNode(api_key="pn_live_...") as pn:
print(len(pn.v3.operations)) # 120
combo_activity = await pn.v3.execute(
"GET /v3/combos/activity",
query={"limit": 25},
)
wallet_rewards = await pn.v3.execute(
"GET /v3/wallets/{address}/rewards",
path_params={"address": address},
query={"limit": 100},
)
return combo_activity, wallet_rewards
asyncio.run(read_v3("0xabc..."))
The SDK encodes path parameters for you. Read requests that are safe to repeat retry transient failures and honor Retry-After; requests that change data are never retried automatically. JSON decimals decode as Decimal, and ApiError exposes the status, request ID, retry details, and a request URL with credentials removed.
Current presets include dome, fills, combos, redemptions, and deposits. Current filters include since(), combo_condition_ids(), leg_position_ids(), event_ids(), module_ids(), action(), and direction().
dome and fills change settlement delivery into a flat, per-fill wire
format. Use one of those presets on a dedicated PolyNodeWS connection when
also consuming non-fill events; the server deduplicates delivery per
connection and cannot deliver both wire formats for the same settlement.
Reconnect and delivery behavior
Why: a reconnect can overlap the last event or exceed the server's retained history. The SDK resubscribes with the latest accepted timestamp, deduplicates the overlap, preserves unknown additive events, and reports replay state. Replay is best effort, not an unbounded gapless guarantee.
pn.ws.on_replay(
lambda notice: print(
notice.phase, notice.since, notice.guaranteed, notice.warning
)
)
sub.on_overflow(
lambda overflow: print("local iterator queue evicted", overflow.dropped_events)
)
Chainlink TWAP and short-form markets
The TWAP values are lookback windows, not update cadence: 5-minute markets use 30 seconds; 15-minute and 4-hour markets use 60 seconds.
async def stream_short_markets(pn):
prices = await (
pn.ws.subscribe("chainlink")
.feeds(["BTC/USD", "ETH/USD"])
.twap_windows([30])
.send()
)
print(prices.price_source, prices.twap_windows, prices.warnings)
prices.on("price_feed", lambda event: print(event.feed, event.price))
stream = pn.ws.short_form("5m", coins=["btc", "eth"])
stream.on("rotation", lambda rotation: print([m.slug for m in rotation.markets]))
stream.on("price_feed", lambda event: print(event.feed, event.price))
stream.on("settlement", lambda event: print(event.market_slug, event.status))
A Chainlink selection is scoped to its WebSocket connection, so combine feeds and windows into one Chainlink subscription per connection. The resolved subscription exposes the server acknowledgement through price_source, twap_windows, and warnings. short_form() handles rotation safely with its own socket. At each market boundary it closes that socket, discovers the new slugs, reconnects, and subscribes to the exact settlement and TWAP filters again.
V3 perps WebSocket
The V3 perps WebSocket streams tickers, best bid/offer, full books, trades, statistics, and klines. The managed client confirms which channels were accepted, reconnects and resubscribes, and emits an explicit gap notice because the perps stream cannot replay missed messages.
import asyncio
from polynode import AsyncPolyNode, PerpsEvent, perps_channels
async def stream_perps():
async with AsyncPolyNode(api_key="pn_live_...") as pn:
perps = pn.perps
hello = await perps.connect()
ack = await perps.subscribe([
perps_channels.tickers,
perps_channels.book("BTC-USD"),
perps_channels.trades("BTC-USD"),
])
print(hello.max_subscriptions, ack.channels, ack.rejected)
try:
async for message in perps:
if isinstance(message, PerpsEvent) and message.channel == "perps_tickers":
# Prices, quantities, funding, and equity values stay exact strings.
print(message.data["mark_price"], message.data["funding_rate"])
elif message.type in ("lag_warning", "reconnect"):
print(message)
finally:
await perps.disconnect()
asyncio.run(stream_perps())
Use perps_channels.bbo(), .book(), .trades(), and .klines(instrument, "1m" | "1h") for scoped channels. Each perps_book event is a complete replacement snapshot; perps.book("BTC-USD") returns the latest complete book. Authentication (4401) and connection-cap (4429) closes are terminal. Queue eviction is observable through on_overflow().
WebSocket Streaming
import asyncio
from polynode import AsyncPolyNode
async def main():
async with AsyncPolyNode(api_key="pn_live_...") as pn:
sub = await pn.ws.subscribe("settlements").min_size(1000).send()
async for event in sub:
print(event.event_type, event.market_title, event.taker_price)
asyncio.run(main())
Orderbook
import asyncio
from polynode import OrderbookEngine
async def main():
engine = OrderbookEngine(api_key="pn_live_...", integrity=True)
await engine.subscribe(["token_id_1", "token_id_2"])
engine.on("ready", lambda: print(f"Tracking {engine.size} books"))
engine.on("update", lambda u: print(f"{u.asset_id}: {engine.midpoint(u.asset_id)}"))
engine.on("integrity_error", lambda error: print(error.token, error.code))
asyncio.run(main())
With PN1 integrity enabled, the engine validates sequence continuity and deterministic checksums, fails stale or invalid books closed by default, and requests a fresh anchor before making them readable again. Integrity mode requires explicit markets; wildcard subscriptions are unavailable. Set allow_stale_reads=True only when your application explicitly prefers availability over verified state.
Trading
import asyncio
from polynode.trading import PolyNodeTrader, TraderConfig, OrderParams, ExchangeVersion
async def main():
# CLOB V2 (pUSD collateral) is the current production default.
trader = PolyNodeTrader(TraderConfig(
polynode_key="pn_live_...",
# exchange_version=ExchangeVersion.V2,
# builder_code=None, # disables default public PolyNode attribution
))
status = await trader.ensure_ready("0xYourPrivateKey...")
result = await trader.order(OrderParams(
token_id="...",
side="BUY",
price=0.55,
size=100,
builder="0x<your_builder_code_bytes32>", # V2 only; omit for V1
))
print(result)
trader.close()
asyncio.run(main())
For the V2 order flow — required approvals, EIP-712 struct, fee math, and common failure modes — see polynode/trading/V2_ORDER_FLOW.md in the installed package.
V2 fees are determined at match time and are not signed into an order, so V2 payloads omit feeRateBps, nonce, and taker. Explicit legacy V1 mode still signs feeRateBps; for that path the SDK fetches /fee-rate and fails closed if fee, tick-size, or neg-risk metadata is unavailable or malformed.
Documentation
Full docs at docs.polynode.dev
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file polynode-0.12.2.tar.gz.
File metadata
- Download URL: polynode-0.12.2.tar.gz
- Upload date:
- Size: 89.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a16e7d5d46019c3da72b1d89e64eeff4b9a93eeafa0154d06aff83c2c1562b9
|
|
| MD5 |
d2351fcb36cb3210de38c344d41df75d
|
|
| BLAKE2b-256 |
85ed02b55db3e3957b6bd34555790d9007ef5e5a3acd4b306a5f9d0e08385810
|
File details
Details for the file polynode-0.12.2-py3-none-any.whl.
File metadata
- Download URL: polynode-0.12.2-py3-none-any.whl
- Upload date:
- Size: 108.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2b4e510aafdb620c6c3ba162b39659b481eea78fcdcae2e9898437c6524c511
|
|
| MD5 |
7f831d1b0e166d4bb1a5feeffc329e66
|
|
| BLAKE2b-256 |
328dec94d4d62bb5ab55dfbe5e7d26a982a29aea8f673aa42637096d6c1c2395
|