polynode 0.10.8__tar.gz → 0.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. polynode-0.12.0/.gitignore +8 -0
  2. polynode-0.12.0/PKG-INFO +299 -0
  3. polynode-0.12.0/README.md +266 -0
  4. polynode-0.12.0/core-contract-v1.json +1026 -0
  5. polynode-0.12.0/core-fixtures-v1.json +442 -0
  6. polynode-0.12.0/polynode/__init__.py +83 -0
  7. polynode-0.12.0/polynode/_version.py +1 -0
  8. {polynode-0.10.8 → polynode-0.12.0}/polynode/client.py +194 -8
  9. {polynode-0.10.8 → polynode-0.12.0}/polynode/engine.py +89 -11
  10. {polynode-0.10.8 → polynode-0.12.0}/polynode/errors.py +24 -3
  11. {polynode-0.10.8 → polynode-0.12.0}/polynode/orderbook.py +62 -3
  12. polynode-0.12.0/polynode/orderbook_integrity.py +77 -0
  13. polynode-0.12.0/polynode/orderbook_state.py +334 -0
  14. polynode-0.12.0/polynode/perps.py +537 -0
  15. {polynode-0.10.8 → polynode-0.12.0}/polynode/short_form.py +121 -19
  16. polynode-0.12.0/polynode/subscription.py +364 -0
  17. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/V2_ORDER_FLOW.md +7 -30
  18. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/clob_api.py +69 -10
  19. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/constants.py +3 -0
  20. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/eip712.py +4 -3
  21. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/escrow.py +3 -2
  22. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/onboarding.py +4 -4
  23. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/trader.py +22 -6
  24. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/types.py +6 -3
  25. {polynode-0.10.8 → polynode-0.12.0}/polynode/types/__init__.py +1 -0
  26. {polynode-0.10.8 → polynode-0.12.0}/polynode/types/enums.py +22 -0
  27. polynode-0.12.0/polynode/types/events.py +583 -0
  28. {polynode-0.10.8 → polynode-0.12.0}/polynode/types/orderbook.py +47 -1
  29. polynode-0.12.0/polynode/types/perps.py +106 -0
  30. {polynode-0.10.8 → polynode-0.12.0}/polynode/types/rest.py +18 -1
  31. {polynode-0.10.8 → polynode-0.12.0}/polynode/types/short_form.py +5 -1
  32. polynode-0.12.0/polynode/types/ws.py +83 -0
  33. polynode-0.12.0/polynode/v3.py +499 -0
  34. polynode-0.12.0/polynode/v3_operations.py +149 -0
  35. polynode-0.12.0/polynode/ws.py +944 -0
  36. {polynode-0.10.8 → polynode-0.12.0}/pyproject.toml +15 -1
  37. polynode-0.10.8/.gitignore +0 -45
  38. polynode-0.10.8/PKG-INFO +0 -171
  39. polynode-0.10.8/README.md +0 -138
  40. polynode-0.10.8/polynode/__init__.py +0 -41
  41. polynode-0.10.8/polynode/_version.py +0 -1
  42. polynode-0.10.8/polynode/orderbook_state.py +0 -83
  43. polynode-0.10.8/polynode/subscription.py +0 -150
  44. polynode-0.10.8/polynode/types/events.py +0 -270
  45. polynode-0.10.8/polynode/types/ws.py +0 -39
  46. polynode-0.10.8/polynode/ws.py +0 -328
  47. polynode-0.10.8/tests/__init__.py +0 -0
  48. polynode-0.10.8/tests/conftest.py +0 -33
  49. polynode-0.10.8/tests/test_client.py +0 -141
  50. polynode-0.10.8/tests/test_orderbook.py +0 -73
  51. polynode-0.10.8/tests/test_orderbook_protocol.py +0 -131
  52. polynode-0.10.8/tests/test_trading.py +0 -486
  53. polynode-0.10.8/tests/test_types.py +0 -80
  54. polynode-0.10.8/tests/test_ws_protocol.py +0 -107
  55. {polynode-0.10.8 → polynode-0.12.0}/polynode/cache/__init__.py +0 -0
  56. {polynode-0.10.8 → polynode-0.12.0}/polynode/redemption_watcher.py +0 -0
  57. {polynode-0.10.8 → polynode-0.12.0}/polynode/testing.py +0 -0
  58. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/__init__.py +0 -0
  59. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/cosigner.py +0 -0
  60. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/position_management.py +0 -0
  61. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/privy.py +0 -0
  62. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/relayer.py +0 -0
  63. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/signer.py +0 -0
  64. {polynode-0.10.8 → polynode-0.12.0}/polynode/trading/sqlite_backend.py +0 -0
@@ -0,0 +1,8 @@
1
+ node_modules/
2
+ dist/
3
+ build/
4
+ .env
5
+ .env.*
6
+ __pycache__/
7
+ *.egg-info/
8
+ .venv/
@@ -0,0 +1,299 @@
1
+ Metadata-Version: 2.4
2
+ Name: polynode
3
+ Version: 0.12.0
4
+ Summary: Python SDK for the PolyNode real-time prediction market data platform
5
+ Project-URL: Homepage, https://polynode.dev
6
+ Project-URL: Documentation, https://docs.polynode.dev
7
+ Author-email: PolyNode <josh@quantish.live>
8
+ License: MIT
9
+ Keywords: polymarket,polynode,prediction-markets,trading,websocket
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Office/Business :: Financial
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: httpx>=0.27
21
+ Requires-Dist: pydantic>=2.0
22
+ Requires-Dist: websockets>=12.0
23
+ Provides-Extra: all
24
+ Requires-Dist: aiosqlite>=0.20; extra == 'all'
25
+ Requires-Dist: eth-account>=0.13; extra == 'all'
26
+ Requires-Dist: web3>=7.0; extra == 'all'
27
+ Provides-Extra: cache
28
+ Requires-Dist: aiosqlite>=0.20; extra == 'cache'
29
+ Provides-Extra: trading
30
+ Requires-Dist: eth-account>=0.13; extra == 'trading'
31
+ Requires-Dist: web3>=7.0; extra == 'trading'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # polynode
35
+
36
+ Python SDK for the [PolyNode](https://polynode.dev) real-time prediction market data platform.
37
+
38
+ **New in v0.12.0:** Python now 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.
39
+
40
+ **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.
41
+
42
+ **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.
43
+
44
+ **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()`.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install polynode
50
+ ```
51
+
52
+ For trading support:
53
+ ```bash
54
+ pip install 'polynode[trading]'
55
+ ```
56
+
57
+ ## Quick Start
58
+
59
+ ### REST API
60
+
61
+ ```python
62
+ from polynode import PolyNode
63
+
64
+ with PolyNode(api_key="pn_live_...") as pn:
65
+ status = pn.status()
66
+ connections = pn.connections()
67
+ markets = pn.markets(count=10)
68
+ settlements = pn.recent_settlements(count=5)
69
+ wallet_positions = pn.wallet_positions(
70
+ address, redeemable=True, condition_id=condition_id
71
+ )
72
+ batch_positions = pn.multi_wallet_positions([address, second_address], limit=100)
73
+ market_positions = pn.market_positions(
74
+ condition_id, sort_by="CURRENT_VALUE", min_size=0.01
75
+ )
76
+ onchain_positions = pn.wallet_onchain_positions(
77
+ address, since=window_start, tag_slug="crypto"
78
+ )
79
+ ```
80
+
81
+ ### Sports and Online Context
82
+
83
+ ```python
84
+ from polynode import PolyNode
85
+
86
+ with PolyNode(api_key="pn_live_...") as pn:
87
+ state = pn.sports_game_state(
88
+ "nba-cle-nyk-2026-05-31",
89
+ price_limit_tokens=20,
90
+ )
91
+
92
+ context = pn.sports_game_context(
93
+ "nba-cle-nyk-2026-05-31",
94
+ sources=["online"],
95
+ query_set="injuries",
96
+ max_queries=2,
97
+ max_per_query=5,
98
+ include_state=True,
99
+ )
100
+
101
+ web = pn.search_online(
102
+ "Cavaliers Knicks injury news",
103
+ max_results=5,
104
+ )
105
+ ```
106
+
107
+ ### Async REST
108
+
109
+ ```python
110
+ import asyncio
111
+ from polynode import AsyncPolyNode
112
+
113
+ async def main():
114
+ async with AsyncPolyNode(api_key="pn_live_...") as pn:
115
+ status = await pn.status()
116
+ markets = await pn.markets(count=10)
117
+
118
+ asyncio.run(main())
119
+ ```
120
+
121
+ ### Complete V3 API
122
+
123
+ 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.
124
+
125
+ ```python
126
+ import asyncio
127
+ from polynode import AsyncPolyNode
128
+
129
+ async def read_v3(address: str):
130
+ async with AsyncPolyNode(api_key="pn_live_...") as pn:
131
+ print(len(pn.v3.operations)) # 120
132
+
133
+ combo_activity = await pn.v3.execute(
134
+ "GET /v3/combos/activity",
135
+ query={"limit": 25},
136
+ )
137
+ wallet_rewards = await pn.v3.execute(
138
+ "GET /v3/wallets/{address}/rewards",
139
+ path_params={"address": address},
140
+ query={"limit": 100},
141
+ )
142
+ return combo_activity, wallet_rewards
143
+
144
+ asyncio.run(read_v3("0xabc..."))
145
+ ```
146
+
147
+ 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.
148
+
149
+ 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()`.
150
+
151
+ `dome` and `fills` change settlement delivery into a flat, per-fill wire
152
+ format. Use one of those presets on a dedicated `PolyNodeWS` connection when
153
+ also consuming non-fill events; the server deduplicates delivery per
154
+ connection and cannot deliver both wire formats for the same settlement.
155
+
156
+ ### Reconnect and delivery behavior
157
+
158
+ 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.
159
+
160
+ ```python
161
+ pn.ws.on_replay(
162
+ lambda notice: print(
163
+ notice.phase, notice.since, notice.guaranteed, notice.warning
164
+ )
165
+ )
166
+
167
+ sub.on_overflow(
168
+ lambda overflow: print("local iterator queue evicted", overflow.dropped_events)
169
+ )
170
+ ```
171
+
172
+ ### Chainlink TWAP and short-form markets
173
+
174
+ The TWAP values are lookback windows, not update cadence: 5-minute markets use 30 seconds; 15-minute and 4-hour markets use 60 seconds.
175
+
176
+ ```python
177
+ async def stream_short_markets(pn):
178
+ prices = await (
179
+ pn.ws.subscribe("chainlink")
180
+ .feeds(["BTC/USD", "ETH/USD"])
181
+ .twap_windows([30])
182
+ .send()
183
+ )
184
+ print(prices.price_source, prices.twap_windows, prices.warnings)
185
+ prices.on("price_feed", lambda event: print(event.feed, event.price))
186
+
187
+ stream = pn.ws.short_form("5m", coins=["btc", "eth"])
188
+ stream.on("rotation", lambda rotation: print([m.slug for m in rotation.markets]))
189
+ stream.on("price_feed", lambda event: print(event.feed, event.price))
190
+ stream.on("settlement", lambda event: print(event.market_slug, event.status))
191
+ ```
192
+
193
+ 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.
194
+
195
+ ### V3 perps WebSocket
196
+
197
+ 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.
198
+
199
+ ```python
200
+ import asyncio
201
+ from polynode import AsyncPolyNode, PerpsEvent, perps_channels
202
+
203
+ async def stream_perps():
204
+ async with AsyncPolyNode(api_key="pn_live_...") as pn:
205
+ perps = pn.perps
206
+ hello = await perps.connect()
207
+ ack = await perps.subscribe([
208
+ perps_channels.tickers,
209
+ perps_channels.book("BTC-USD"),
210
+ perps_channels.trades("BTC-USD"),
211
+ ])
212
+ print(hello.max_subscriptions, ack.channels, ack.rejected)
213
+
214
+ try:
215
+ async for message in perps:
216
+ if isinstance(message, PerpsEvent) and message.channel == "perps_tickers":
217
+ # Prices, quantities, funding, and equity values stay exact strings.
218
+ print(message.data["mark_price"], message.data["funding_rate"])
219
+ elif message.type in ("lag_warning", "reconnect"):
220
+ print(message)
221
+ finally:
222
+ await perps.disconnect()
223
+
224
+ asyncio.run(stream_perps())
225
+ ```
226
+
227
+ 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()`.
228
+
229
+ ### WebSocket Streaming
230
+
231
+ ```python
232
+ import asyncio
233
+ from polynode import AsyncPolyNode
234
+
235
+ async def main():
236
+ async with AsyncPolyNode(api_key="pn_live_...") as pn:
237
+ sub = await pn.ws.subscribe("settlements").min_size(1000).send()
238
+
239
+ async for event in sub:
240
+ print(event.event_type, event.market_title, event.taker_price)
241
+
242
+ asyncio.run(main())
243
+ ```
244
+
245
+ ### Orderbook
246
+
247
+ ```python
248
+ import asyncio
249
+ from polynode import OrderbookEngine
250
+
251
+ async def main():
252
+ engine = OrderbookEngine(api_key="pn_live_...", integrity=True)
253
+ await engine.subscribe(["token_id_1", "token_id_2"])
254
+
255
+ engine.on("ready", lambda: print(f"Tracking {engine.size} books"))
256
+ engine.on("update", lambda u: print(f"{u.asset_id}: {engine.midpoint(u.asset_id)}"))
257
+ engine.on("integrity_error", lambda error: print(error.token, error.code))
258
+
259
+ asyncio.run(main())
260
+ ```
261
+
262
+ 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.
263
+
264
+ ### Trading
265
+
266
+ ```python
267
+ import asyncio
268
+ from polynode.trading import PolyNodeTrader, TraderConfig, OrderParams, ExchangeVersion
269
+
270
+ async def main():
271
+ # CLOB V2 (pUSD collateral) is the current production default.
272
+ trader = PolyNodeTrader(TraderConfig(
273
+ polynode_key="pn_live_...",
274
+ # exchange_version=ExchangeVersion.V2,
275
+ # builder_code=None, # disables default public PolyNode attribution
276
+ ))
277
+ status = await trader.ensure_ready("0xYourPrivateKey...")
278
+
279
+ result = await trader.order(OrderParams(
280
+ token_id="...",
281
+ side="BUY",
282
+ price=0.55,
283
+ size=100,
284
+ builder="0x<your_builder_code_bytes32>", # V2 only; omit for V1
285
+ ))
286
+ print(result)
287
+
288
+ trader.close()
289
+
290
+ asyncio.run(main())
291
+ ```
292
+
293
+ 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.
294
+
295
+ 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.
296
+
297
+ ## Documentation
298
+
299
+ Full docs at [docs.polynode.dev](https://docs.polynode.dev)
@@ -0,0 +1,266 @@
1
+ # polynode
2
+
3
+ Python SDK for the [PolyNode](https://polynode.dev) real-time prediction market data platform.
4
+
5
+ **New in v0.12.0:** Python now 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.
6
+
7
+ **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.
8
+
9
+ **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.
10
+
11
+ **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()`.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install polynode
17
+ ```
18
+
19
+ For trading support:
20
+ ```bash
21
+ pip install 'polynode[trading]'
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### REST API
27
+
28
+ ```python
29
+ from polynode import PolyNode
30
+
31
+ with PolyNode(api_key="pn_live_...") as pn:
32
+ status = pn.status()
33
+ connections = pn.connections()
34
+ markets = pn.markets(count=10)
35
+ settlements = pn.recent_settlements(count=5)
36
+ wallet_positions = pn.wallet_positions(
37
+ address, redeemable=True, condition_id=condition_id
38
+ )
39
+ batch_positions = pn.multi_wallet_positions([address, second_address], limit=100)
40
+ market_positions = pn.market_positions(
41
+ condition_id, sort_by="CURRENT_VALUE", min_size=0.01
42
+ )
43
+ onchain_positions = pn.wallet_onchain_positions(
44
+ address, since=window_start, tag_slug="crypto"
45
+ )
46
+ ```
47
+
48
+ ### Sports and Online Context
49
+
50
+ ```python
51
+ from polynode import PolyNode
52
+
53
+ with PolyNode(api_key="pn_live_...") as pn:
54
+ state = pn.sports_game_state(
55
+ "nba-cle-nyk-2026-05-31",
56
+ price_limit_tokens=20,
57
+ )
58
+
59
+ context = pn.sports_game_context(
60
+ "nba-cle-nyk-2026-05-31",
61
+ sources=["online"],
62
+ query_set="injuries",
63
+ max_queries=2,
64
+ max_per_query=5,
65
+ include_state=True,
66
+ )
67
+
68
+ web = pn.search_online(
69
+ "Cavaliers Knicks injury news",
70
+ max_results=5,
71
+ )
72
+ ```
73
+
74
+ ### Async REST
75
+
76
+ ```python
77
+ import asyncio
78
+ from polynode import AsyncPolyNode
79
+
80
+ async def main():
81
+ async with AsyncPolyNode(api_key="pn_live_...") as pn:
82
+ status = await pn.status()
83
+ markets = await pn.markets(count=10)
84
+
85
+ asyncio.run(main())
86
+ ```
87
+
88
+ ### Complete V3 API
89
+
90
+ 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.
91
+
92
+ ```python
93
+ import asyncio
94
+ from polynode import AsyncPolyNode
95
+
96
+ async def read_v3(address: str):
97
+ async with AsyncPolyNode(api_key="pn_live_...") as pn:
98
+ print(len(pn.v3.operations)) # 120
99
+
100
+ combo_activity = await pn.v3.execute(
101
+ "GET /v3/combos/activity",
102
+ query={"limit": 25},
103
+ )
104
+ wallet_rewards = await pn.v3.execute(
105
+ "GET /v3/wallets/{address}/rewards",
106
+ path_params={"address": address},
107
+ query={"limit": 100},
108
+ )
109
+ return combo_activity, wallet_rewards
110
+
111
+ asyncio.run(read_v3("0xabc..."))
112
+ ```
113
+
114
+ 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.
115
+
116
+ 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()`.
117
+
118
+ `dome` and `fills` change settlement delivery into a flat, per-fill wire
119
+ format. Use one of those presets on a dedicated `PolyNodeWS` connection when
120
+ also consuming non-fill events; the server deduplicates delivery per
121
+ connection and cannot deliver both wire formats for the same settlement.
122
+
123
+ ### Reconnect and delivery behavior
124
+
125
+ 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.
126
+
127
+ ```python
128
+ pn.ws.on_replay(
129
+ lambda notice: print(
130
+ notice.phase, notice.since, notice.guaranteed, notice.warning
131
+ )
132
+ )
133
+
134
+ sub.on_overflow(
135
+ lambda overflow: print("local iterator queue evicted", overflow.dropped_events)
136
+ )
137
+ ```
138
+
139
+ ### Chainlink TWAP and short-form markets
140
+
141
+ The TWAP values are lookback windows, not update cadence: 5-minute markets use 30 seconds; 15-minute and 4-hour markets use 60 seconds.
142
+
143
+ ```python
144
+ async def stream_short_markets(pn):
145
+ prices = await (
146
+ pn.ws.subscribe("chainlink")
147
+ .feeds(["BTC/USD", "ETH/USD"])
148
+ .twap_windows([30])
149
+ .send()
150
+ )
151
+ print(prices.price_source, prices.twap_windows, prices.warnings)
152
+ prices.on("price_feed", lambda event: print(event.feed, event.price))
153
+
154
+ stream = pn.ws.short_form("5m", coins=["btc", "eth"])
155
+ stream.on("rotation", lambda rotation: print([m.slug for m in rotation.markets]))
156
+ stream.on("price_feed", lambda event: print(event.feed, event.price))
157
+ stream.on("settlement", lambda event: print(event.market_slug, event.status))
158
+ ```
159
+
160
+ 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.
161
+
162
+ ### V3 perps WebSocket
163
+
164
+ 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.
165
+
166
+ ```python
167
+ import asyncio
168
+ from polynode import AsyncPolyNode, PerpsEvent, perps_channels
169
+
170
+ async def stream_perps():
171
+ async with AsyncPolyNode(api_key="pn_live_...") as pn:
172
+ perps = pn.perps
173
+ hello = await perps.connect()
174
+ ack = await perps.subscribe([
175
+ perps_channels.tickers,
176
+ perps_channels.book("BTC-USD"),
177
+ perps_channels.trades("BTC-USD"),
178
+ ])
179
+ print(hello.max_subscriptions, ack.channels, ack.rejected)
180
+
181
+ try:
182
+ async for message in perps:
183
+ if isinstance(message, PerpsEvent) and message.channel == "perps_tickers":
184
+ # Prices, quantities, funding, and equity values stay exact strings.
185
+ print(message.data["mark_price"], message.data["funding_rate"])
186
+ elif message.type in ("lag_warning", "reconnect"):
187
+ print(message)
188
+ finally:
189
+ await perps.disconnect()
190
+
191
+ asyncio.run(stream_perps())
192
+ ```
193
+
194
+ 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()`.
195
+
196
+ ### WebSocket Streaming
197
+
198
+ ```python
199
+ import asyncio
200
+ from polynode import AsyncPolyNode
201
+
202
+ async def main():
203
+ async with AsyncPolyNode(api_key="pn_live_...") as pn:
204
+ sub = await pn.ws.subscribe("settlements").min_size(1000).send()
205
+
206
+ async for event in sub:
207
+ print(event.event_type, event.market_title, event.taker_price)
208
+
209
+ asyncio.run(main())
210
+ ```
211
+
212
+ ### Orderbook
213
+
214
+ ```python
215
+ import asyncio
216
+ from polynode import OrderbookEngine
217
+
218
+ async def main():
219
+ engine = OrderbookEngine(api_key="pn_live_...", integrity=True)
220
+ await engine.subscribe(["token_id_1", "token_id_2"])
221
+
222
+ engine.on("ready", lambda: print(f"Tracking {engine.size} books"))
223
+ engine.on("update", lambda u: print(f"{u.asset_id}: {engine.midpoint(u.asset_id)}"))
224
+ engine.on("integrity_error", lambda error: print(error.token, error.code))
225
+
226
+ asyncio.run(main())
227
+ ```
228
+
229
+ 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.
230
+
231
+ ### Trading
232
+
233
+ ```python
234
+ import asyncio
235
+ from polynode.trading import PolyNodeTrader, TraderConfig, OrderParams, ExchangeVersion
236
+
237
+ async def main():
238
+ # CLOB V2 (pUSD collateral) is the current production default.
239
+ trader = PolyNodeTrader(TraderConfig(
240
+ polynode_key="pn_live_...",
241
+ # exchange_version=ExchangeVersion.V2,
242
+ # builder_code=None, # disables default public PolyNode attribution
243
+ ))
244
+ status = await trader.ensure_ready("0xYourPrivateKey...")
245
+
246
+ result = await trader.order(OrderParams(
247
+ token_id="...",
248
+ side="BUY",
249
+ price=0.55,
250
+ size=100,
251
+ builder="0x<your_builder_code_bytes32>", # V2 only; omit for V1
252
+ ))
253
+ print(result)
254
+
255
+ trader.close()
256
+
257
+ asyncio.run(main())
258
+ ```
259
+
260
+ 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.
261
+
262
+ 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.
263
+
264
+ ## Documentation
265
+
266
+ Full docs at [docs.polynode.dev](https://docs.polynode.dev)