polynode 0.10.7__tar.gz → 0.11.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 (52) hide show
  1. polynode-0.11.0/.gitignore +8 -0
  2. {polynode-0.10.7 → polynode-0.11.0}/PKG-INFO +52 -5
  3. polynode-0.11.0/README.md +183 -0
  4. {polynode-0.10.7 → polynode-0.11.0}/polynode/__init__.py +3 -1
  5. polynode-0.11.0/polynode/_version.py +1 -0
  6. {polynode-0.10.7 → polynode-0.11.0}/polynode/client.py +144 -8
  7. {polynode-0.10.7 → polynode-0.11.0}/polynode/short_form.py +121 -19
  8. {polynode-0.10.7 → polynode-0.11.0}/polynode/subscription.py +63 -2
  9. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/V2_ORDER_FLOW.md +7 -30
  10. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/clob_api.py +69 -10
  11. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/constants.py +3 -0
  12. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/eip712.py +19 -8
  13. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/escrow.py +3 -2
  14. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/onboarding.py +4 -4
  15. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/trader.py +22 -6
  16. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/types.py +6 -3
  17. {polynode-0.10.7 → polynode-0.11.0}/polynode/types/enums.py +22 -0
  18. polynode-0.11.0/polynode/types/events.py +571 -0
  19. {polynode-0.10.7 → polynode-0.11.0}/polynode/types/rest.py +18 -1
  20. {polynode-0.10.7 → polynode-0.11.0}/polynode/types/short_form.py +5 -1
  21. {polynode-0.10.7 → polynode-0.11.0}/polynode/types/ws.py +12 -0
  22. polynode-0.11.0/polynode/ws.py +761 -0
  23. {polynode-0.10.7 → polynode-0.11.0}/pyproject.toml +11 -1
  24. polynode-0.10.7/.gitignore +0 -43
  25. polynode-0.10.7/README.md +0 -136
  26. polynode-0.10.7/polynode/_version.py +0 -1
  27. polynode-0.10.7/polynode/types/events.py +0 -270
  28. polynode-0.10.7/polynode/ws.py +0 -328
  29. polynode-0.10.7/tests/__init__.py +0 -0
  30. polynode-0.10.7/tests/conftest.py +0 -33
  31. polynode-0.10.7/tests/test_client.py +0 -141
  32. polynode-0.10.7/tests/test_orderbook.py +0 -73
  33. polynode-0.10.7/tests/test_orderbook_protocol.py +0 -131
  34. polynode-0.10.7/tests/test_trading.py +0 -464
  35. polynode-0.10.7/tests/test_types.py +0 -80
  36. polynode-0.10.7/tests/test_ws_protocol.py +0 -107
  37. {polynode-0.10.7 → polynode-0.11.0}/polynode/cache/__init__.py +0 -0
  38. {polynode-0.10.7 → polynode-0.11.0}/polynode/engine.py +0 -0
  39. {polynode-0.10.7 → polynode-0.11.0}/polynode/errors.py +0 -0
  40. {polynode-0.10.7 → polynode-0.11.0}/polynode/orderbook.py +0 -0
  41. {polynode-0.10.7 → polynode-0.11.0}/polynode/orderbook_state.py +0 -0
  42. {polynode-0.10.7 → polynode-0.11.0}/polynode/redemption_watcher.py +0 -0
  43. {polynode-0.10.7 → polynode-0.11.0}/polynode/testing.py +0 -0
  44. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/__init__.py +0 -0
  45. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/cosigner.py +0 -0
  46. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/position_management.py +0 -0
  47. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/privy.py +0 -0
  48. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/relayer.py +0 -0
  49. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/signer.py +0 -0
  50. {polynode-0.10.7 → polynode-0.11.0}/polynode/trading/sqlite_backend.py +0 -0
  51. {polynode-0.10.7 → polynode-0.11.0}/polynode/types/__init__.py +0 -0
  52. {polynode-0.10.7 → polynode-0.11.0}/polynode/types/orderbook.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/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polynode
3
- Version: 0.10.7
3
+ Version: 0.11.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
@@ -35,7 +35,11 @@ Description-Content-Type: text/markdown
35
35
 
36
36
  Python SDK for the [PolyNode](https://polynode.dev) real-time prediction market data platform.
37
37
 
38
- **New 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()`.
38
+ **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.
39
+
40
+ **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.
41
+
42
+ **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()`.
39
43
 
40
44
  ## Install
41
45
 
@@ -57,8 +61,19 @@ from polynode import PolyNode
57
61
 
58
62
  with PolyNode(api_key="pn_live_...") as pn:
59
63
  status = pn.status()
64
+ connections = pn.connections()
60
65
  markets = pn.markets(count=10)
61
66
  settlements = pn.recent_settlements(count=5)
67
+ wallet_positions = pn.wallet_positions(
68
+ address, redeemable=True, condition_id=condition_id
69
+ )
70
+ batch_positions = pn.multi_wallet_positions([address, second_address], limit=100)
71
+ market_positions = pn.market_positions(
72
+ condition_id, sort_by="CURRENT_VALUE", min_size=0.01
73
+ )
74
+ onchain_positions = pn.wallet_onchain_positions(
75
+ address, since=window_start, tag_slug="crypto"
76
+ )
62
77
  ```
63
78
 
64
79
  ### Sports and Online Context
@@ -101,6 +116,36 @@ async def main():
101
116
  asyncio.run(main())
102
117
  ```
103
118
 
119
+ 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()`.
120
+
121
+ `dome` and `fills` change settlement delivery into a flat, per-fill wire
122
+ format. Use one of those presets on a dedicated `PolyNodeWS` connection when
123
+ also consuming non-fill events; the server deduplicates delivery per
124
+ connection and cannot deliver both wire formats for the same settlement.
125
+
126
+ ### Chainlink TWAP and short-form markets
127
+
128
+ The TWAP values are lookback windows, not update cadence: 5-minute markets use 30 seconds; 15-minute and 4-hour markets use 60 seconds.
129
+
130
+ ```python
131
+ async def stream_short_markets(pn):
132
+ prices = await (
133
+ pn.ws.subscribe("chainlink")
134
+ .feeds(["BTC/USD", "ETH/USD"])
135
+ .twap_windows([30])
136
+ .send()
137
+ )
138
+ print(prices.price_source, prices.twap_windows, prices.warnings)
139
+ prices.on("price_feed", lambda event: print(event.feed, event.price))
140
+
141
+ stream = pn.ws.short_form("5m", coins=["btc", "eth"])
142
+ stream.on("rotation", lambda rotation: print([m.slug for m in rotation.markets]))
143
+ stream.on("price_feed", lambda event: print(event.feed, event.price))
144
+ stream.on("settlement", lambda event: print(event.market_slug, event.status))
145
+ ```
146
+
147
+ 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.
148
+
104
149
  ### WebSocket Streaming
105
150
 
106
151
  ```python
@@ -140,11 +185,11 @@ import asyncio
140
185
  from polynode.trading import PolyNodeTrader, TraderConfig, OrderParams, ExchangeVersion
141
186
 
142
187
  async def main():
143
- # Default is V1 (USDC.e). Pass exchange_version=ExchangeVersion.V2 for the V2 CLOB
144
- # (pUSD collateral, clob-v2.polymarket.com, builder attribution).
188
+ # CLOB V2 (pUSD collateral) is the current production default.
145
189
  trader = PolyNodeTrader(TraderConfig(
146
190
  polynode_key="pn_live_...",
147
- exchange_version=ExchangeVersion.V2,
191
+ # exchange_version=ExchangeVersion.V2,
192
+ # builder_code=None, # disables default public PolyNode attribution
148
193
  ))
149
194
  status = await trader.ensure_ready("0xYourPrivateKey...")
150
195
 
@@ -164,6 +209,8 @@ asyncio.run(main())
164
209
 
165
210
  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.
166
211
 
212
+ 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.
213
+
167
214
  ## Documentation
168
215
 
169
216
  Full docs at [docs.polynode.dev](https://docs.polynode.dev)
@@ -0,0 +1,183 @@
1
+ # polynode
2
+
3
+ Python SDK for the [PolyNode](https://polynode.dev) real-time prediction market data platform.
4
+
5
+ **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.
6
+
7
+ **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.
8
+
9
+ **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()`.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install polynode
15
+ ```
16
+
17
+ For trading support:
18
+ ```bash
19
+ pip install polynode[trading]
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### REST API
25
+
26
+ ```python
27
+ from polynode import PolyNode
28
+
29
+ with PolyNode(api_key="pn_live_...") as pn:
30
+ status = pn.status()
31
+ connections = pn.connections()
32
+ markets = pn.markets(count=10)
33
+ settlements = pn.recent_settlements(count=5)
34
+ wallet_positions = pn.wallet_positions(
35
+ address, redeemable=True, condition_id=condition_id
36
+ )
37
+ batch_positions = pn.multi_wallet_positions([address, second_address], limit=100)
38
+ market_positions = pn.market_positions(
39
+ condition_id, sort_by="CURRENT_VALUE", min_size=0.01
40
+ )
41
+ onchain_positions = pn.wallet_onchain_positions(
42
+ address, since=window_start, tag_slug="crypto"
43
+ )
44
+ ```
45
+
46
+ ### Sports and Online Context
47
+
48
+ ```python
49
+ from polynode import PolyNode
50
+
51
+ with PolyNode(api_key="pn_live_...") as pn:
52
+ state = pn.sports_game_state(
53
+ "nba-cle-nyk-2026-05-31",
54
+ price_limit_tokens=20,
55
+ )
56
+
57
+ context = pn.sports_game_context(
58
+ "nba-cle-nyk-2026-05-31",
59
+ sources=["online"],
60
+ query_set="injuries",
61
+ max_queries=2,
62
+ max_per_query=5,
63
+ include_state=True,
64
+ )
65
+
66
+ web = pn.search_online(
67
+ "Cavaliers Knicks injury news",
68
+ max_results=5,
69
+ )
70
+ ```
71
+
72
+ ### Async REST
73
+
74
+ ```python
75
+ import asyncio
76
+ from polynode import AsyncPolyNode
77
+
78
+ async def main():
79
+ async with AsyncPolyNode(api_key="pn_live_...") as pn:
80
+ status = await pn.status()
81
+ markets = await pn.markets(count=10)
82
+
83
+ asyncio.run(main())
84
+ ```
85
+
86
+ 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()`.
87
+
88
+ `dome` and `fills` change settlement delivery into a flat, per-fill wire
89
+ format. Use one of those presets on a dedicated `PolyNodeWS` connection when
90
+ also consuming non-fill events; the server deduplicates delivery per
91
+ connection and cannot deliver both wire formats for the same settlement.
92
+
93
+ ### Chainlink TWAP and short-form markets
94
+
95
+ The TWAP values are lookback windows, not update cadence: 5-minute markets use 30 seconds; 15-minute and 4-hour markets use 60 seconds.
96
+
97
+ ```python
98
+ async def stream_short_markets(pn):
99
+ prices = await (
100
+ pn.ws.subscribe("chainlink")
101
+ .feeds(["BTC/USD", "ETH/USD"])
102
+ .twap_windows([30])
103
+ .send()
104
+ )
105
+ print(prices.price_source, prices.twap_windows, prices.warnings)
106
+ prices.on("price_feed", lambda event: print(event.feed, event.price))
107
+
108
+ stream = pn.ws.short_form("5m", coins=["btc", "eth"])
109
+ stream.on("rotation", lambda rotation: print([m.slug for m in rotation.markets]))
110
+ stream.on("price_feed", lambda event: print(event.feed, event.price))
111
+ stream.on("settlement", lambda event: print(event.market_slug, event.status))
112
+ ```
113
+
114
+ 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.
115
+
116
+ ### WebSocket Streaming
117
+
118
+ ```python
119
+ import asyncio
120
+ from polynode import AsyncPolyNode
121
+
122
+ async def main():
123
+ async with AsyncPolyNode(api_key="pn_live_...") as pn:
124
+ sub = await pn.ws.subscribe("settlements").min_size(1000).send()
125
+
126
+ async for event in sub:
127
+ print(event.event_type, event.market_title, event.taker_price)
128
+
129
+ asyncio.run(main())
130
+ ```
131
+
132
+ ### Orderbook
133
+
134
+ ```python
135
+ import asyncio
136
+ from polynode import OrderbookEngine
137
+
138
+ async def main():
139
+ engine = OrderbookEngine(api_key="pn_live_...")
140
+ await engine.subscribe(["token_id_1", "token_id_2"])
141
+
142
+ engine.on("ready", lambda: print(f"Tracking {engine.size} books"))
143
+ engine.on("update", lambda u: print(f"{u.asset_id}: {engine.midpoint(u.asset_id)}"))
144
+
145
+ asyncio.run(main())
146
+ ```
147
+
148
+ ### Trading
149
+
150
+ ```python
151
+ import asyncio
152
+ from polynode.trading import PolyNodeTrader, TraderConfig, OrderParams, ExchangeVersion
153
+
154
+ async def main():
155
+ # CLOB V2 (pUSD collateral) is the current production default.
156
+ trader = PolyNodeTrader(TraderConfig(
157
+ polynode_key="pn_live_...",
158
+ # exchange_version=ExchangeVersion.V2,
159
+ # builder_code=None, # disables default public PolyNode attribution
160
+ ))
161
+ status = await trader.ensure_ready("0xYourPrivateKey...")
162
+
163
+ result = await trader.order(OrderParams(
164
+ token_id="...",
165
+ side="BUY",
166
+ price=0.55,
167
+ size=100,
168
+ builder="0x<your_builder_code_bytes32>", # V2 only; omit for V1
169
+ ))
170
+ print(result)
171
+
172
+ trader.close()
173
+
174
+ asyncio.run(main())
175
+ ```
176
+
177
+ 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.
178
+
179
+ 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.
180
+
181
+ ## Documentation
182
+
183
+ Full docs at [docs.polynode.dev](https://docs.polynode.dev)
@@ -7,7 +7,7 @@ from .errors import ApiError, PolyNodeError, WsError
7
7
  from .orderbook import OrderbookWS
8
8
  from .orderbook_state import LocalOrderbook
9
9
  from .redemption_watcher import RedeemableAlert, RedemptionWatcher, TrackedPosition
10
- from .short_form import ShortFormStream
10
+ from .short_form import ShortFormStream, short_form_chainlink_feed, short_form_twap_window
11
11
  from .subscription import Subscription, SubscriptionBuilder
12
12
  from .testing import get_active_test_wallet, get_active_test_wallets
13
13
  from .ws import PolyNodeWS
@@ -28,6 +28,8 @@ __all__ = [
28
28
  "EngineView",
29
29
  # Streams
30
30
  "ShortFormStream",
31
+ "short_form_chainlink_feed",
32
+ "short_form_twap_window",
31
33
  "RedemptionWatcher",
32
34
  "RedeemableAlert",
33
35
  "TrackedPosition",
@@ -0,0 +1 @@
1
+ __version__ = "0.11.0"
@@ -143,6 +143,10 @@ class PolyNode:
143
143
  def status(self) -> StatusResponse:
144
144
  return StatusResponse.model_validate(self._fetch("/v1/status"))
145
145
 
146
+ def connections(self) -> dict:
147
+ """Return connection and subscription usage for this API key or account."""
148
+ return self._fetch("/v1/connections")
149
+
146
150
  def create_key(self, name: str = "unnamed") -> ApiKeyResponse:
147
151
  return ApiKeyResponse.model_validate(
148
152
  self._fetch("/v1/keys", method="POST", body={"name": name}, auth=False)
@@ -404,14 +408,78 @@ class PolyNode:
404
408
  query={"limit": limit, "offset": offset, "side": side, "user": user},
405
409
  )
406
410
 
407
- def wallet_positions(self, address: str, *, limit: int | None = None, offset: int | None = None) -> dict:
411
+ def wallet_positions(
412
+ self,
413
+ address: str,
414
+ *,
415
+ limit: int | None = None,
416
+ offset: int | None = None,
417
+ redeemable: bool | None = None,
418
+ condition_id: str | None = None,
419
+ ) -> dict:
408
420
  return self._fetch(
409
421
  f"/v1/wallets/{quote(address, safe='')}/positions",
410
- query={"limit": limit, "offset": offset},
422
+ query={
423
+ "limit": limit,
424
+ "offset": offset,
425
+ "redeemable": redeemable,
426
+ "condition_id": condition_id,
427
+ },
428
+ )
429
+
430
+ def multi_wallet_positions(
431
+ self,
432
+ wallets: list[str] | tuple[str, ...],
433
+ *,
434
+ limit: int | None = None,
435
+ offset: int | None = None,
436
+ ) -> dict:
437
+ """Return positions for up to 20 wallets in one request."""
438
+ return self._fetch(
439
+ "/v1/wallets/positions",
440
+ query={"wallets": _csv_param(wallets), "limit": limit, "offset": offset},
411
441
  )
412
442
 
413
- def wallet_onchain_positions(self, address: str) -> dict:
414
- return self._fetch(f"/v2/wallets/{quote(address, safe='')}/positions/onchain")
443
+ def market_positions(
444
+ self,
445
+ id: str,
446
+ *,
447
+ limit: int | None = None,
448
+ offset: int | None = None,
449
+ sort_by: str | None = None,
450
+ sort_direction: str | None = None,
451
+ status: str | None = None,
452
+ include_trades: bool | None = None,
453
+ user: str | list[str] | tuple[str, ...] | None = None,
454
+ min_size: float | None = None,
455
+ ) -> dict:
456
+ """Return holder positions for a condition ID or market slug."""
457
+ return self._fetch(
458
+ f"/v1/markets/{quote(id, safe='')}/positions",
459
+ query={
460
+ "limit": limit,
461
+ "offset": offset,
462
+ "sortBy": sort_by,
463
+ "sortDirection": sort_direction,
464
+ "status": status,
465
+ "includeTrades": include_trades,
466
+ "user": _csv_param(user),
467
+ "minSize": min_size,
468
+ },
469
+ )
470
+
471
+ def wallet_onchain_positions(
472
+ self,
473
+ address: str,
474
+ *,
475
+ since: int | None = None,
476
+ until: int | None = None,
477
+ tag_slug: str | None = None,
478
+ ) -> dict:
479
+ return self._fetch(
480
+ f"/v2/wallets/{quote(address, safe='')}/positions/onchain",
481
+ query={"since": since, "until": until, "tag_slug": tag_slug},
482
+ )
415
483
 
416
484
  # ── Orderbook (REST) ──
417
485
 
@@ -592,6 +660,10 @@ class AsyncPolyNode:
592
660
  async def status(self) -> StatusResponse:
593
661
  return StatusResponse.model_validate(await self._fetch("/v1/status"))
594
662
 
663
+ async def connections(self) -> dict:
664
+ """Return connection and subscription usage for this API key or account."""
665
+ return await self._fetch("/v1/connections")
666
+
595
667
  async def create_key(self, name: str = "unnamed") -> ApiKeyResponse:
596
668
  return ApiKeyResponse.model_validate(
597
669
  await self._fetch("/v1/keys", method="POST", body={"name": name}, auth=False)
@@ -853,14 +925,78 @@ class AsyncPolyNode:
853
925
  query={"limit": limit, "offset": offset, "side": side, "user": user},
854
926
  )
855
927
 
856
- async def wallet_positions(self, address: str, *, limit: int | None = None, offset: int | None = None) -> dict:
928
+ async def wallet_positions(
929
+ self,
930
+ address: str,
931
+ *,
932
+ limit: int | None = None,
933
+ offset: int | None = None,
934
+ redeemable: bool | None = None,
935
+ condition_id: str | None = None,
936
+ ) -> dict:
857
937
  return await self._fetch(
858
938
  f"/v1/wallets/{quote(address, safe='')}/positions",
859
- query={"limit": limit, "offset": offset},
939
+ query={
940
+ "limit": limit,
941
+ "offset": offset,
942
+ "redeemable": redeemable,
943
+ "condition_id": condition_id,
944
+ },
945
+ )
946
+
947
+ async def multi_wallet_positions(
948
+ self,
949
+ wallets: list[str] | tuple[str, ...],
950
+ *,
951
+ limit: int | None = None,
952
+ offset: int | None = None,
953
+ ) -> dict:
954
+ """Return positions for up to 20 wallets in one request."""
955
+ return await self._fetch(
956
+ "/v1/wallets/positions",
957
+ query={"wallets": _csv_param(wallets), "limit": limit, "offset": offset},
860
958
  )
861
959
 
862
- async def wallet_onchain_positions(self, address: str) -> dict:
863
- return await self._fetch(f"/v2/wallets/{quote(address, safe='')}/positions/onchain")
960
+ async def market_positions(
961
+ self,
962
+ id: str,
963
+ *,
964
+ limit: int | None = None,
965
+ offset: int | None = None,
966
+ sort_by: str | None = None,
967
+ sort_direction: str | None = None,
968
+ status: str | None = None,
969
+ include_trades: bool | None = None,
970
+ user: str | list[str] | tuple[str, ...] | None = None,
971
+ min_size: float | None = None,
972
+ ) -> dict:
973
+ """Return holder positions for a condition ID or market slug."""
974
+ return await self._fetch(
975
+ f"/v1/markets/{quote(id, safe='')}/positions",
976
+ query={
977
+ "limit": limit,
978
+ "offset": offset,
979
+ "sortBy": sort_by,
980
+ "sortDirection": sort_direction,
981
+ "status": status,
982
+ "includeTrades": include_trades,
983
+ "user": _csv_param(user),
984
+ "minSize": min_size,
985
+ },
986
+ )
987
+
988
+ async def wallet_onchain_positions(
989
+ self,
990
+ address: str,
991
+ *,
992
+ since: int | None = None,
993
+ until: int | None = None,
994
+ tag_slug: str | None = None,
995
+ ) -> dict:
996
+ return await self._fetch(
997
+ f"/v2/wallets/{quote(address, safe='')}/positions/onchain",
998
+ query={"since": since, "until": until, "tag_slug": tag_slug},
999
+ )
864
1000
 
865
1001
  # ── Orderbook (REST) ──
866
1002