polynode 0.14.1__tar.gz → 0.15.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 (50) hide show
  1. {polynode-0.14.1 → polynode-0.15.0}/PKG-INFO +109 -6
  2. {polynode-0.14.1 → polynode-0.15.0}/README.md +108 -5
  3. {polynode-0.14.1 → polynode-0.15.0}/core-contract-v1.json +55 -1
  4. polynode-0.15.0/polynode/__init__.py +161 -0
  5. polynode-0.15.0/polynode/_version.py +1 -0
  6. polynode-0.15.0/polynode/market_protocol.py +466 -0
  7. {polynode-0.14.1 → polynode-0.15.0}/polynode/redemption_watcher.py +66 -8
  8. {polynode-0.14.1 → polynode-0.15.0}/polynode/short_form.py +35 -2
  9. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/__init__.py +17 -1
  10. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/clob_api.py +12 -0
  11. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/constants.py +16 -0
  12. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/eip712.py +93 -7
  13. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/onboarding.py +186 -1
  14. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/position_management.py +93 -0
  15. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/sqlite_backend.py +37 -1
  16. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/trader.py +487 -7
  17. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/types.py +55 -1
  18. {polynode-0.14.1 → polynode-0.15.0}/polynode/types/__init__.py +1 -0
  19. {polynode-0.14.1 → polynode-0.15.0}/polynode/types/events.py +59 -0
  20. {polynode-0.14.1 → polynode-0.15.0}/polynode/types/perps.py +62 -1
  21. {polynode-0.14.1 → polynode-0.15.0}/polynode/types/short_form.py +3 -0
  22. polynode-0.15.0/polynode/types/v3.py +263 -0
  23. {polynode-0.14.1 → polynode-0.15.0}/polynode/v3.py +469 -3
  24. {polynode-0.14.1 → polynode-0.15.0}/polynode/v3_operations.py +9 -0
  25. {polynode-0.14.1 → polynode-0.15.0}/pyproject.toml +1 -1
  26. polynode-0.14.1/polynode/__init__.py +0 -83
  27. polynode-0.14.1/polynode/_version.py +0 -1
  28. {polynode-0.14.1 → polynode-0.15.0}/.gitignore +0 -0
  29. {polynode-0.14.1 → polynode-0.15.0}/core-fixtures-v1.json +0 -0
  30. {polynode-0.14.1 → polynode-0.15.0}/polynode/cache/__init__.py +0 -0
  31. {polynode-0.14.1 → polynode-0.15.0}/polynode/client.py +0 -0
  32. {polynode-0.14.1 → polynode-0.15.0}/polynode/engine.py +0 -0
  33. {polynode-0.14.1 → polynode-0.15.0}/polynode/errors.py +0 -0
  34. {polynode-0.14.1 → polynode-0.15.0}/polynode/orderbook.py +0 -0
  35. {polynode-0.14.1 → polynode-0.15.0}/polynode/orderbook_integrity.py +0 -0
  36. {polynode-0.14.1 → polynode-0.15.0}/polynode/orderbook_state.py +0 -0
  37. {polynode-0.14.1 → polynode-0.15.0}/polynode/perps.py +0 -0
  38. {polynode-0.14.1 → polynode-0.15.0}/polynode/subscription.py +0 -0
  39. {polynode-0.14.1 → polynode-0.15.0}/polynode/testing.py +0 -0
  40. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/cosigner.py +0 -0
  41. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/escrow.py +0 -0
  42. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/privy.py +0 -0
  43. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/relayer.py +0 -0
  44. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/signer.py +0 -0
  45. {polynode-0.14.1 → polynode-0.15.0}/polynode/trading/user_relayer.py +0 -0
  46. {polynode-0.14.1 → polynode-0.15.0}/polynode/types/enums.py +0 -0
  47. {polynode-0.14.1 → polynode-0.15.0}/polynode/types/orderbook.py +0 -0
  48. {polynode-0.14.1 → polynode-0.15.0}/polynode/types/rest.py +0 -0
  49. {polynode-0.14.1 → polynode-0.15.0}/polynode/types/ws.py +0 -0
  50. {polynode-0.14.1 → polynode-0.15.0}/polynode/ws.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: polynode
3
- Version: 0.14.1
3
+ Version: 0.15.0
4
4
  Summary: Python SDK for the Polynode real-time prediction market data platform
5
5
  Project-URL: Homepage, https://polynode.dev
6
6
  Project-URL: Documentation, https://docs.polynode.dev
@@ -36,7 +36,9 @@ Description-Content-Type: text/markdown
36
36
 
37
37
  Python SDK for the [Polynode](https://polynode.dev) real-time prediction market data platform.
38
38
 
39
- **New in v0.14.1:** Every prepared user-owned order exposes its canonical V2 exchange `order_hash` before submission. This is the exact CLOB/open-order ID and fill `order_hash`; deposit-wallet orders hash the standard inner `Order`, not the POLY_1271 wallet-signing wrapper. Record it before the network attempt so timeouts can be reconciled without risking a duplicate.
39
+ **New in v0.15.0:** Polymarket Protocol V2 markets (October 2026). Upgrade before 2026-11-02 if you trade: `order()` looks up each market's protocol and signs Protocol V2 orders for the right exchange automatically, and split, merge and the new redeem work on them too. Nothing changes for current markets or for streaming and REST users. This is not the April 2026 "V2" (CTF Exchange V2 and pUSD) described elsewhere in this README; see [Polymarket Protocol V2 markets](#polymarket-protocol-v2-markets).
40
+
41
+ **In v0.14.1:** Every prepared user-owned order exposes its canonical V2 exchange `order_hash` before submission. This is the exact CLOB/open-order ID and fill `order_hash`; deposit-wallet orders hash the standard inner `Order`, not the POLY_1271 wallet-signing wrapper. Record it before the network attempt so timeouts can be reconciled without risking a duplicate.
40
42
 
41
43
  **In v0.14.0:** Web platforms can take a user from wallet authorization through an exact browser-signed user-owned order without exposing backend credentials. The SDK imports the shared versioned browser bundle into memory, produces a credential-free signing request with a complete order preview, supports one-time multi-worker state, validates canonical signatures and wallet identity, checks BUY collateral before prompting, and submits with exact zero builder attribution.
42
44
 
@@ -72,7 +74,6 @@ with PolyNode(api_key="pn_live_...") as pn:
72
74
  status = pn.status()
73
75
  connections = pn.connections()
74
76
  markets = pn.markets(count=10)
75
- settlements = pn.recent_settlements(count=5)
76
77
  wallet_positions = pn.wallet_positions(
77
78
  address, redeemable=True, condition_id=condition_id
78
79
  )
@@ -127,7 +128,7 @@ asyncio.run(main())
127
128
 
128
129
  ### Complete V3 API
129
130
 
130
- 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.
131
+ V3 includes wallets, combos, rewards, credits, identities, markets, events, builders, profiles, perps, crypto, sports, backtesting, and other current product families. `execute()` gives you access to all 129 current V3 operations through one consistent Python interface.
131
132
 
132
133
  ```python
133
134
  import asyncio
@@ -135,7 +136,7 @@ from polynode import AsyncPolyNode
135
136
 
136
137
  async def read_v3(address: str):
137
138
  async with AsyncPolyNode(api_key="pn_live_...") as pn:
138
- print(len(pn.v3.operations)) # 120
139
+ print(len(pn.v3.operations)) # 128
139
140
 
140
141
  combo_activity = await pn.v3.execute(
141
142
  "GET /v3/combos/activity",
@@ -146,11 +147,76 @@ async def read_v3(address: str):
146
147
  path_params={"address": address},
147
148
  query={"limit": 100},
148
149
  )
149
- return combo_activity, wallet_rewards
150
+ scopes = await pn.v3.execute(
151
+ "GET /v3/leaderboard/scopes",
152
+ query={"period": "7d", "scope": "category"},
153
+ )
154
+ markets = await pn.v3.execute(
155
+ "GET /v3/markets",
156
+ query={"status": "open", "limit": 100},
157
+ )
158
+ return combo_activity, wallet_rewards, scopes, markets
150
159
 
151
160
  asyncio.run(read_v3("0xabc..."))
152
161
  ```
153
162
 
163
+ Use the typed Combo P&L helper for All Time realized, unrealized, and total
164
+ P&L, or realized P&L over a fixed or custom half-open window:
165
+
166
+ ```python
167
+ from polynode import PolyNodeV3
168
+
169
+ with PolyNodeV3("pn_live_...") as v3:
170
+ all_time = v3.combo_pnl_leaderboard(
171
+ period="all",
172
+ sort_by="total_pnl",
173
+ )
174
+ custom = v3.combo_pnl_leaderboard(
175
+ period="custom",
176
+ after=1_788_134_400,
177
+ before=1_788_220_800,
178
+ sort_by="realized_pnl",
179
+ )
180
+ ```
181
+
182
+ Wallets with incomplete arbitrary transfer basis or without a current mark are
183
+ omitted rather than returned with zero P&L. Inspect `coverage` on every response
184
+ before presenting the ranking.
185
+
186
+ Market and event catalogs use opaque cursor pagination. Reward-market list
187
+ rows include nullable title, slug, image, event, category, and tag fields.
188
+
189
+ Named perps helpers accept the returned opaque cursor:
190
+
191
+ ```python
192
+ from polynode import PolyNodeV3
193
+
194
+ with PolyNodeV3(api_key="pn_live_...") as v3:
195
+ first = v3.perps_trades(
196
+ "BTC-USD",
197
+ after=1_787_875_200,
198
+ before=1_787_961_600,
199
+ limit=100,
200
+ )
201
+ if first.get("next_cursor"):
202
+ second = v3.perps_trades(
203
+ "BTC-USD",
204
+ after=1_787_875_200,
205
+ before=1_787_961_600,
206
+ limit=100,
207
+ cursor=first["next_cursor"],
208
+ )
209
+
210
+ events = v3.perps_events(address=address, limit=100)
211
+ liquidations = v3.perps_liquidations(min_notional=10_000)
212
+ flows = v3.perps_wallet_flows(address, limit=100)
213
+ ```
214
+
215
+ Reuse the same route and filters on every cursor page. Wallet-flow totals and
216
+ counts remain fixed during one traversal. Cumulative volume delta remains a
217
+ bounded single-page series and has no cursor helper. Async clients expose the
218
+ same methods with `await`.
219
+
154
220
  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.
155
221
 
156
222
  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()`.
@@ -301,6 +367,43 @@ For the V2 order flow, required approvals, and common failure modes, see [docs.p
301
367
 
302
368
  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.
303
369
 
370
+ #### Polymarket Protocol V2 markets
371
+
372
+ > **Two different "V2"s.** Polymarket Protocol V2 (October 2026) is Polymarket's new position system: positions live in a new ledger (PositionManager), every market type trades on one new exchange (ExchangeV3, order signing domain version `"3"`), and split, merge and redeem go through a Router. It is **not** the April 2026 upgrade (CTF Exchange V2 with pUSD collateral) that `ExchangeVersion.V2` and the rest of this README call "V2". Current markets keep using the April exchanges.
373
+
374
+ **When.** Polymarket's test markets for Protocol V2 trade from 2026-10-05 to 2026-10-30. New markets move to Protocol V2 from around 2026-11-02 (tentative, per Polymarket). Existing markets stay where they are.
375
+
376
+ **What you need to do.**
377
+
378
+ - Streaming and REST only: nothing. Same methods, same response shapes. Rows and events for Protocol V2 markets carry a few optional fields: `protocol_version: "v2"`, `module` (`"binary"` or `"neg_risk"`), `exchange_version: "v3"` on fills, and `payouts_ppm` / `derived` on oracle events. A row without them is a current market.
379
+ - Trading: upgrade to `polynode` 0.15.0 before 2026-11-02 and place orders exactly as before. Older versions keep working on current markets, but Polymarket rejects their orders on Protocol V2 markets (no funds move).
380
+ - Storing ids yourself: keep outcome ids as strings (up to 78 digits). Condition ids are returned in the 66-character form; the 64-character form is accepted as input.
381
+
382
+ The first order on a Protocol V2 market grants the two approvals it needs (pUSD and PositionManager, both for ExchangeV3): one gasless batch for Safe and deposit wallets, or two transactions with a little POL gas for an EOA. To do it ahead of time, or for browser-signed user-owned orders, call `ensure_ready` once with `protocol_v2=True`. Plain `ensure_ready()` behaves exactly as before.
383
+
384
+ ```python
385
+ from polynode.trading import MergeParams, RedeemParams, SplitParams
386
+
387
+ await trader.ensure_ready("0xYourPrivateKey...", protocol_v2=True) # optional
388
+ version = await trader.get_market_protocol(token_id) # "v1" (current) or "v2"
389
+
390
+ # Same call for every market; the SDK picks the exchange and signing domain.
391
+ await trader.order(OrderParams(token_id=token_id, side="BUY", price=0.5, size=10))
392
+
393
+ # Position operations (user-owned execution) route through the Router on V2 markets.
394
+ await trader.execute_split(SplitParams(condition_id=condition_id, amount=10))
395
+ await trader.execute_merge(MergeParams(condition_id=condition_id, amount=10))
396
+ await trader.execute_redeem(RedeemParams(condition_id=condition_id, outcome_index=0)) # 0 = YES, 1 = NO
397
+ payout = await trader.preview_payout(position_id, 10) # None until resolved
398
+
399
+ status = await trader.check_approvals(protocol_v2=True)
400
+ print(status.protocol_v2_ready, status.position_ops_v2_ready, status.auto_redeem_enabled)
401
+ ```
402
+
403
+ The SDK learns a market's protocol from Polymarket's market data and caches it for 24 hours in a new local table (existing tables are unchanged). It never guesses from the shape of an id: if the protocol cannot be learned, the order is refused with a retryable `MarketProtocolError` instead of being signed for the wrong exchange. Pass `protocol_version="v2"` (or call `trader.hint_market_protocol(...)`) to skip the lookup; a value that contradicts what the SDK already knows is refused. Short-form streams pick `positionIds` for Protocol V2 markets and expose `market.protocol_version`, so orders on discovered markets skip the lookup.
404
+
405
+ Results of Protocol V2 markets are shares of 1,000,000 (`[1000000, 0]` = YES won, `[500000, 500000]` = 50/50); `payouts` carries the same result in smallest whole numbers, like current markets. `RedemptionWatcher` handles both. Automatic redemption by Polymarket's AutoRedeemer is opt-in only (`enable_auto_redeem()` / `disable_auto_redeem()`); proceeds always return to your wallet. Converting neg-risk positions is not supported for Protocol V2 markets yet.
406
+
304
407
  #### Optional user-owned execution
305
408
 
306
409
  Set `execution_mode=ExecutionMode.USER_OWNED` when the signing wallet should trade with zero builder attribution and use its own gasless authorization. Builder mode remains the default and existing integrations are unchanged.
@@ -2,7 +2,9 @@
2
2
 
3
3
  Python SDK for the [Polynode](https://polynode.dev) real-time prediction market data platform.
4
4
 
5
- **New in v0.14.1:** Every prepared user-owned order exposes its canonical V2 exchange `order_hash` before submission. This is the exact CLOB/open-order ID and fill `order_hash`; deposit-wallet orders hash the standard inner `Order`, not the POLY_1271 wallet-signing wrapper. Record it before the network attempt so timeouts can be reconciled without risking a duplicate.
5
+ **New in v0.15.0:** Polymarket Protocol V2 markets (October 2026). Upgrade before 2026-11-02 if you trade: `order()` looks up each market's protocol and signs Protocol V2 orders for the right exchange automatically, and split, merge and the new redeem work on them too. Nothing changes for current markets or for streaming and REST users. This is not the April 2026 "V2" (CTF Exchange V2 and pUSD) described elsewhere in this README; see [Polymarket Protocol V2 markets](#polymarket-protocol-v2-markets).
6
+
7
+ **In v0.14.1:** Every prepared user-owned order exposes its canonical V2 exchange `order_hash` before submission. This is the exact CLOB/open-order ID and fill `order_hash`; deposit-wallet orders hash the standard inner `Order`, not the POLY_1271 wallet-signing wrapper. Record it before the network attempt so timeouts can be reconciled without risking a duplicate.
6
8
 
7
9
  **In v0.14.0:** Web platforms can take a user from wallet authorization through an exact browser-signed user-owned order without exposing backend credentials. The SDK imports the shared versioned browser bundle into memory, produces a credential-free signing request with a complete order preview, supports one-time multi-worker state, validates canonical signatures and wallet identity, checks BUY collateral before prompting, and submits with exact zero builder attribution.
8
10
 
@@ -38,7 +40,6 @@ with PolyNode(api_key="pn_live_...") as pn:
38
40
  status = pn.status()
39
41
  connections = pn.connections()
40
42
  markets = pn.markets(count=10)
41
- settlements = pn.recent_settlements(count=5)
42
43
  wallet_positions = pn.wallet_positions(
43
44
  address, redeemable=True, condition_id=condition_id
44
45
  )
@@ -93,7 +94,7 @@ asyncio.run(main())
93
94
 
94
95
  ### Complete V3 API
95
96
 
96
- 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.
97
+ V3 includes wallets, combos, rewards, credits, identities, markets, events, builders, profiles, perps, crypto, sports, backtesting, and other current product families. `execute()` gives you access to all 129 current V3 operations through one consistent Python interface.
97
98
 
98
99
  ```python
99
100
  import asyncio
@@ -101,7 +102,7 @@ from polynode import AsyncPolyNode
101
102
 
102
103
  async def read_v3(address: str):
103
104
  async with AsyncPolyNode(api_key="pn_live_...") as pn:
104
- print(len(pn.v3.operations)) # 120
105
+ print(len(pn.v3.operations)) # 128
105
106
 
106
107
  combo_activity = await pn.v3.execute(
107
108
  "GET /v3/combos/activity",
@@ -112,11 +113,76 @@ async def read_v3(address: str):
112
113
  path_params={"address": address},
113
114
  query={"limit": 100},
114
115
  )
115
- return combo_activity, wallet_rewards
116
+ scopes = await pn.v3.execute(
117
+ "GET /v3/leaderboard/scopes",
118
+ query={"period": "7d", "scope": "category"},
119
+ )
120
+ markets = await pn.v3.execute(
121
+ "GET /v3/markets",
122
+ query={"status": "open", "limit": 100},
123
+ )
124
+ return combo_activity, wallet_rewards, scopes, markets
116
125
 
117
126
  asyncio.run(read_v3("0xabc..."))
118
127
  ```
119
128
 
129
+ Use the typed Combo P&L helper for All Time realized, unrealized, and total
130
+ P&L, or realized P&L over a fixed or custom half-open window:
131
+
132
+ ```python
133
+ from polynode import PolyNodeV3
134
+
135
+ with PolyNodeV3("pn_live_...") as v3:
136
+ all_time = v3.combo_pnl_leaderboard(
137
+ period="all",
138
+ sort_by="total_pnl",
139
+ )
140
+ custom = v3.combo_pnl_leaderboard(
141
+ period="custom",
142
+ after=1_788_134_400,
143
+ before=1_788_220_800,
144
+ sort_by="realized_pnl",
145
+ )
146
+ ```
147
+
148
+ Wallets with incomplete arbitrary transfer basis or without a current mark are
149
+ omitted rather than returned with zero P&L. Inspect `coverage` on every response
150
+ before presenting the ranking.
151
+
152
+ Market and event catalogs use opaque cursor pagination. Reward-market list
153
+ rows include nullable title, slug, image, event, category, and tag fields.
154
+
155
+ Named perps helpers accept the returned opaque cursor:
156
+
157
+ ```python
158
+ from polynode import PolyNodeV3
159
+
160
+ with PolyNodeV3(api_key="pn_live_...") as v3:
161
+ first = v3.perps_trades(
162
+ "BTC-USD",
163
+ after=1_787_875_200,
164
+ before=1_787_961_600,
165
+ limit=100,
166
+ )
167
+ if first.get("next_cursor"):
168
+ second = v3.perps_trades(
169
+ "BTC-USD",
170
+ after=1_787_875_200,
171
+ before=1_787_961_600,
172
+ limit=100,
173
+ cursor=first["next_cursor"],
174
+ )
175
+
176
+ events = v3.perps_events(address=address, limit=100)
177
+ liquidations = v3.perps_liquidations(min_notional=10_000)
178
+ flows = v3.perps_wallet_flows(address, limit=100)
179
+ ```
180
+
181
+ Reuse the same route and filters on every cursor page. Wallet-flow totals and
182
+ counts remain fixed during one traversal. Cumulative volume delta remains a
183
+ bounded single-page series and has no cursor helper. Async clients expose the
184
+ same methods with `await`.
185
+
120
186
  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.
121
187
 
122
188
  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()`.
@@ -267,6 +333,43 @@ For the V2 order flow, required approvals, and common failure modes, see [docs.p
267
333
 
268
334
  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.
269
335
 
336
+ #### Polymarket Protocol V2 markets
337
+
338
+ > **Two different "V2"s.** Polymarket Protocol V2 (October 2026) is Polymarket's new position system: positions live in a new ledger (PositionManager), every market type trades on one new exchange (ExchangeV3, order signing domain version `"3"`), and split, merge and redeem go through a Router. It is **not** the April 2026 upgrade (CTF Exchange V2 with pUSD collateral) that `ExchangeVersion.V2` and the rest of this README call "V2". Current markets keep using the April exchanges.
339
+
340
+ **When.** Polymarket's test markets for Protocol V2 trade from 2026-10-05 to 2026-10-30. New markets move to Protocol V2 from around 2026-11-02 (tentative, per Polymarket). Existing markets stay where they are.
341
+
342
+ **What you need to do.**
343
+
344
+ - Streaming and REST only: nothing. Same methods, same response shapes. Rows and events for Protocol V2 markets carry a few optional fields: `protocol_version: "v2"`, `module` (`"binary"` or `"neg_risk"`), `exchange_version: "v3"` on fills, and `payouts_ppm` / `derived` on oracle events. A row without them is a current market.
345
+ - Trading: upgrade to `polynode` 0.15.0 before 2026-11-02 and place orders exactly as before. Older versions keep working on current markets, but Polymarket rejects their orders on Protocol V2 markets (no funds move).
346
+ - Storing ids yourself: keep outcome ids as strings (up to 78 digits). Condition ids are returned in the 66-character form; the 64-character form is accepted as input.
347
+
348
+ The first order on a Protocol V2 market grants the two approvals it needs (pUSD and PositionManager, both for ExchangeV3): one gasless batch for Safe and deposit wallets, or two transactions with a little POL gas for an EOA. To do it ahead of time, or for browser-signed user-owned orders, call `ensure_ready` once with `protocol_v2=True`. Plain `ensure_ready()` behaves exactly as before.
349
+
350
+ ```python
351
+ from polynode.trading import MergeParams, RedeemParams, SplitParams
352
+
353
+ await trader.ensure_ready("0xYourPrivateKey...", protocol_v2=True) # optional
354
+ version = await trader.get_market_protocol(token_id) # "v1" (current) or "v2"
355
+
356
+ # Same call for every market; the SDK picks the exchange and signing domain.
357
+ await trader.order(OrderParams(token_id=token_id, side="BUY", price=0.5, size=10))
358
+
359
+ # Position operations (user-owned execution) route through the Router on V2 markets.
360
+ await trader.execute_split(SplitParams(condition_id=condition_id, amount=10))
361
+ await trader.execute_merge(MergeParams(condition_id=condition_id, amount=10))
362
+ await trader.execute_redeem(RedeemParams(condition_id=condition_id, outcome_index=0)) # 0 = YES, 1 = NO
363
+ payout = await trader.preview_payout(position_id, 10) # None until resolved
364
+
365
+ status = await trader.check_approvals(protocol_v2=True)
366
+ print(status.protocol_v2_ready, status.position_ops_v2_ready, status.auto_redeem_enabled)
367
+ ```
368
+
369
+ The SDK learns a market's protocol from Polymarket's market data and caches it for 24 hours in a new local table (existing tables are unchanged). It never guesses from the shape of an id: if the protocol cannot be learned, the order is refused with a retryable `MarketProtocolError` instead of being signed for the wrong exchange. Pass `protocol_version="v2"` (or call `trader.hint_market_protocol(...)`) to skip the lookup; a value that contradicts what the SDK already knows is refused. Short-form streams pick `positionIds` for Protocol V2 markets and expose `market.protocol_version`, so orders on discovered markets skip the lookup.
370
+
371
+ Results of Protocol V2 markets are shares of 1,000,000 (`[1000000, 0]` = YES won, `[500000, 500000]` = 50/50); `payouts` carries the same result in smallest whole numbers, like current markets. `RedemptionWatcher` handles both. Automatic redemption by Polymarket's AutoRedeemer is opt-in only (`enable_auto_redeem()` / `disable_auto_redeem()`); proceeds always return to your wallet. Converting neg-risk positions is not supported for Protocol V2 markets yet.
372
+
270
373
  #### Optional user-owned execution
271
374
 
272
375
  Set `execution_mode=ExecutionMode.USER_OWNED` when the signing wallet should trade with zero builder attribution and use its own gasless authorization. Builder mode remains the default and existing integrations are unchanged.
@@ -237,7 +237,7 @@
237
237
  "never_drop_silently": true
238
238
  }
239
239
  },
240
- "operation_count": 120,
240
+ "operation_count": 129,
241
241
  "operations": [
242
242
  {
243
243
  "method": "POST",
@@ -371,12 +371,48 @@
371
371
  "domain": "combos",
372
372
  "retry": "safe_read"
373
373
  },
374
+ {
375
+ "method": "GET",
376
+ "path": "/v3/combos/activity/daily",
377
+ "domain": "combos",
378
+ "retry": "safe_read"
379
+ },
380
+ {
381
+ "method": "GET",
382
+ "path": "/v3/combos/leaderboard",
383
+ "domain": "combos",
384
+ "retry": "safe_read"
385
+ },
374
386
  {
375
387
  "method": "GET",
376
388
  "path": "/v3/combos/redemptions",
377
389
  "domain": "combos",
378
390
  "retry": "safe_read"
379
391
  },
392
+ {
393
+ "method": "GET",
394
+ "path": "/v3/markets",
395
+ "domain": "markets",
396
+ "retry": "safe_read"
397
+ },
398
+ {
399
+ "method": "GET",
400
+ "path": "/v3/markets/stats",
401
+ "domain": "markets",
402
+ "retry": "safe_read"
403
+ },
404
+ {
405
+ "method": "GET",
406
+ "path": "/v3/events",
407
+ "domain": "events",
408
+ "retry": "safe_read"
409
+ },
410
+ {
411
+ "method": "GET",
412
+ "path": "/v3/events/stats",
413
+ "domain": "events",
414
+ "retry": "safe_read"
415
+ },
380
416
  {
381
417
  "method": "GET",
382
418
  "path": "/v3/markets/search",
@@ -485,6 +521,12 @@
485
521
  "domain": "global",
486
522
  "retry": "safe_read"
487
523
  },
524
+ {
525
+ "method": "GET",
526
+ "path": "/v3/leaderboard/scopes",
527
+ "domain": "global",
528
+ "retry": "safe_read"
529
+ },
488
530
  {
489
531
  "method": "GET",
490
532
  "path": "/v3/builders",
@@ -497,6 +539,12 @@
497
539
  "domain": "builders",
498
540
  "retry": "safe_read"
499
541
  },
542
+ {
543
+ "method": "GET",
544
+ "path": "/v3/builders/{code}/leaderboard",
545
+ "domain": "builders",
546
+ "retry": "safe_read"
547
+ },
500
548
  {
501
549
  "method": "GET",
502
550
  "path": "/v3/builders/{code}/trades",
@@ -887,6 +935,12 @@
887
935
  "domain": "perps",
888
936
  "retry": "safe_read"
889
937
  },
938
+ {
939
+ "method": "GET",
940
+ "path": "/v3/perps/activity/daily",
941
+ "domain": "perps",
942
+ "retry": "safe_read"
943
+ },
890
944
  {
891
945
  "method": "GET",
892
946
  "path": "/v3/perps/funding-pnl/{instrument}",
@@ -0,0 +1,161 @@
1
+ """PolyNode Python SDK — real-time prediction market data and trading."""
2
+
3
+ from ._version import __version__
4
+ from .client import AsyncPolyNode, PolyNode
5
+ from .engine import EngineView, OrderbookEngine
6
+ from .errors import ApiError, PolyNodeError, WsError
7
+ from .market_protocol import (
8
+ MarketProtocolError,
9
+ MarketProtocolInfo,
10
+ MarketProtocolResolver,
11
+ ProtocolVersion,
12
+ get_market_protocol,
13
+ normalize_condition_id,
14
+ protocol_v2_position_ids,
15
+ )
16
+ from .orderbook import OrderbookWS
17
+ from .orderbook_state import LocalOrderbook
18
+ from .perps import PerpsChannels, PerpsWS, perps_channels, perps_websocket_url
19
+ from .types.perps import (
20
+ PerpsConnected,
21
+ PerpsDecodeError,
22
+ PerpsEvent,
23
+ PerpsLagWarning,
24
+ PerpsMessage,
25
+ PerpsPositionEvent,
26
+ PerpsPositionEventsResponse,
27
+ PerpsPositionEventType,
28
+ PerpsPong,
29
+ PerpsQueueOverflow,
30
+ PerpsReconnectNotice,
31
+ PerpsRejectedChannel,
32
+ PerpsServerError,
33
+ PerpsSubscribed,
34
+ PerpsRestTrade,
35
+ PerpsTradesResponse,
36
+ PerpsUnknownMessage,
37
+ PerpsUnsubscribed,
38
+ PerpsWalletFlowEvent,
39
+ PerpsWalletFlowsResponse,
40
+ )
41
+ from .types.v3 import (
42
+ BuilderTraderLeaderboardCoverage,
43
+ BuilderTraderLeaderboardPeriod,
44
+ BuilderTraderLeaderboardResponse,
45
+ BuilderTraderLeaderboardRow,
46
+ BuilderTraderLeaderboardSort,
47
+ BuilderTraderLeaderboardWindow,
48
+ ComboActivityCountSemantics,
49
+ ComboActivityDailyResponse,
50
+ ComboActivityDay,
51
+ ComboPnlLeaderboardCoverage,
52
+ ComboPnlLeaderboardPeriod,
53
+ ComboPnlLeaderboardResponse,
54
+ ComboPnlLeaderboardRow,
55
+ ComboPnlLeaderboardSort,
56
+ ComboPnlLeaderboardWindow,
57
+ ComboPnlScope,
58
+ DailyActivityPeriod,
59
+ PerpsActivityDailyResponse,
60
+ PerpsActivityDay,
61
+ PerpsActivityTotal,
62
+ PerpsInstrumentActivityDay,
63
+ PerpsInstrumentActivitySeries,
64
+ ProtocolV2RowFields,
65
+ )
66
+ from .redemption_watcher import RedeemableAlert, RedemptionWatcher, TrackedPosition
67
+ from .short_form import ShortFormStream, short_form_chainlink_feed, short_form_twap_window
68
+ from .subscription import Subscription, SubscriptionBuilder
69
+ from .testing import get_active_test_wallet, get_active_test_wallets
70
+ from .v3 import AsyncPolyNodeV3, PolyNodeV3
71
+ from .v3_operations import V3_OPERATIONS, V3_OPERATION_COUNT, V3Operation
72
+ from .ws import PolyNodeWS
73
+
74
+ __all__ = [
75
+ "MarketProtocolError",
76
+ "MarketProtocolInfo",
77
+ "MarketProtocolResolver",
78
+ "ProtocolVersion",
79
+ "ProtocolV2RowFields",
80
+ "get_market_protocol",
81
+ "normalize_condition_id",
82
+ "protocol_v2_position_ids",
83
+ "__version__",
84
+ # Clients
85
+ "PolyNode",
86
+ "AsyncPolyNode",
87
+ "PolyNodeV3",
88
+ "AsyncPolyNodeV3",
89
+ "PerpsWS",
90
+ "PerpsChannels",
91
+ "perps_channels",
92
+ "perps_websocket_url",
93
+ "PerpsConnected",
94
+ "PerpsRejectedChannel",
95
+ "PerpsSubscribed",
96
+ "PerpsUnsubscribed",
97
+ "PerpsPong",
98
+ "PerpsEvent",
99
+ "PerpsLagWarning",
100
+ "PerpsServerError",
101
+ "PerpsReconnectNotice",
102
+ "PerpsDecodeError",
103
+ "PerpsUnknownMessage",
104
+ "PerpsQueueOverflow",
105
+ "PerpsMessage",
106
+ "PerpsPositionEventType",
107
+ "PerpsRestTrade",
108
+ "PerpsTradesResponse",
109
+ "PerpsPositionEvent",
110
+ "PerpsPositionEventsResponse",
111
+ "PerpsWalletFlowEvent",
112
+ "PerpsWalletFlowsResponse",
113
+ "BuilderTraderLeaderboardPeriod",
114
+ "BuilderTraderLeaderboardSort",
115
+ "BuilderTraderLeaderboardRow",
116
+ "BuilderTraderLeaderboardWindow",
117
+ "BuilderTraderLeaderboardCoverage",
118
+ "BuilderTraderLeaderboardResponse",
119
+ "ComboPnlLeaderboardPeriod",
120
+ "ComboPnlLeaderboardSort",
121
+ "ComboPnlScope",
122
+ "ComboPnlLeaderboardWindow",
123
+ "ComboPnlLeaderboardCoverage",
124
+ "ComboPnlLeaderboardRow",
125
+ "ComboPnlLeaderboardResponse",
126
+ "DailyActivityPeriod",
127
+ "ComboActivityDay",
128
+ "ComboActivityCountSemantics",
129
+ "ComboActivityDailyResponse",
130
+ "PerpsActivityDay",
131
+ "PerpsInstrumentActivityDay",
132
+ "PerpsInstrumentActivitySeries",
133
+ "PerpsActivityTotal",
134
+ "PerpsActivityDailyResponse",
135
+ "V3Operation",
136
+ "V3_OPERATIONS",
137
+ "V3_OPERATION_COUNT",
138
+ # WebSocket
139
+ "PolyNodeWS",
140
+ "SubscriptionBuilder",
141
+ "Subscription",
142
+ # Orderbook
143
+ "OrderbookWS",
144
+ "LocalOrderbook",
145
+ "OrderbookEngine",
146
+ "EngineView",
147
+ # Streams
148
+ "ShortFormStream",
149
+ "short_form_chainlink_feed",
150
+ "short_form_twap_window",
151
+ "RedemptionWatcher",
152
+ "RedeemableAlert",
153
+ "TrackedPosition",
154
+ # Testing
155
+ "get_active_test_wallet",
156
+ "get_active_test_wallets",
157
+ # Errors
158
+ "PolyNodeError",
159
+ "ApiError",
160
+ "WsError",
161
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.15.0"