polynode 0.15.0__tar.gz → 0.15.1__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.15.1/CHANGELOG.md +10 -0
  2. {polynode-0.15.0 → polynode-0.15.1}/PKG-INFO +4 -2
  3. {polynode-0.15.0 → polynode-0.15.1}/README.md +3 -1
  4. polynode-0.15.1/polynode/_version.py +1 -0
  5. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/onboarding.py +6 -0
  6. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/trader.py +84 -61
  7. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/types.py +3 -0
  8. {polynode-0.15.0 → polynode-0.15.1}/pyproject.toml +1 -1
  9. polynode-0.15.0/polynode/_version.py +0 -1
  10. {polynode-0.15.0 → polynode-0.15.1}/.gitignore +0 -0
  11. {polynode-0.15.0 → polynode-0.15.1}/core-contract-v1.json +0 -0
  12. {polynode-0.15.0 → polynode-0.15.1}/core-fixtures-v1.json +0 -0
  13. {polynode-0.15.0 → polynode-0.15.1}/polynode/__init__.py +0 -0
  14. {polynode-0.15.0 → polynode-0.15.1}/polynode/cache/__init__.py +0 -0
  15. {polynode-0.15.0 → polynode-0.15.1}/polynode/client.py +0 -0
  16. {polynode-0.15.0 → polynode-0.15.1}/polynode/engine.py +0 -0
  17. {polynode-0.15.0 → polynode-0.15.1}/polynode/errors.py +0 -0
  18. {polynode-0.15.0 → polynode-0.15.1}/polynode/market_protocol.py +0 -0
  19. {polynode-0.15.0 → polynode-0.15.1}/polynode/orderbook.py +0 -0
  20. {polynode-0.15.0 → polynode-0.15.1}/polynode/orderbook_integrity.py +0 -0
  21. {polynode-0.15.0 → polynode-0.15.1}/polynode/orderbook_state.py +0 -0
  22. {polynode-0.15.0 → polynode-0.15.1}/polynode/perps.py +0 -0
  23. {polynode-0.15.0 → polynode-0.15.1}/polynode/redemption_watcher.py +0 -0
  24. {polynode-0.15.0 → polynode-0.15.1}/polynode/short_form.py +0 -0
  25. {polynode-0.15.0 → polynode-0.15.1}/polynode/subscription.py +0 -0
  26. {polynode-0.15.0 → polynode-0.15.1}/polynode/testing.py +0 -0
  27. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/__init__.py +0 -0
  28. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/clob_api.py +0 -0
  29. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/constants.py +0 -0
  30. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/cosigner.py +0 -0
  31. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/eip712.py +0 -0
  32. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/escrow.py +0 -0
  33. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/position_management.py +0 -0
  34. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/privy.py +0 -0
  35. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/relayer.py +0 -0
  36. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/signer.py +0 -0
  37. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/sqlite_backend.py +0 -0
  38. {polynode-0.15.0 → polynode-0.15.1}/polynode/trading/user_relayer.py +0 -0
  39. {polynode-0.15.0 → polynode-0.15.1}/polynode/types/__init__.py +0 -0
  40. {polynode-0.15.0 → polynode-0.15.1}/polynode/types/enums.py +0 -0
  41. {polynode-0.15.0 → polynode-0.15.1}/polynode/types/events.py +0 -0
  42. {polynode-0.15.0 → polynode-0.15.1}/polynode/types/orderbook.py +0 -0
  43. {polynode-0.15.0 → polynode-0.15.1}/polynode/types/perps.py +0 -0
  44. {polynode-0.15.0 → polynode-0.15.1}/polynode/types/rest.py +0 -0
  45. {polynode-0.15.0 → polynode-0.15.1}/polynode/types/short_form.py +0 -0
  46. {polynode-0.15.0 → polynode-0.15.1}/polynode/types/v3.py +0 -0
  47. {polynode-0.15.0 → polynode-0.15.1}/polynode/types/ws.py +0 -0
  48. {polynode-0.15.0 → polynode-0.15.1}/polynode/v3.py +0 -0
  49. {polynode-0.15.0 → polynode-0.15.1}/polynode/v3_operations.py +0 -0
  50. {polynode-0.15.0 → polynode-0.15.1}/polynode/ws.py +0 -0
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.15.1 (2026-10-11)
4
+
5
+ - Fixed `wrap_to_polyusd()` and `unwrap_from_polyusd()` for EOA wallets on Polygon. With web3 7 or 8 they failed before sending anything, with an `ExtraDataLengthError` about a POA chain. They now set gas and gas price themselves.
6
+ - `wrap_to_polyusd()` and `unwrap_from_polyusd()` now wait for the approval transaction to confirm before sending the wrap or unwrap, and return the transaction hash with a `0x` prefix.
7
+ - Fixed `set_approvals()` on Polygon in the same way.
8
+ - `ensure_ready()` results have a new `approval_tx_hashes` field with the hashes of any approval transactions that call sent. It is an empty list when nothing was sent. Existing fields are unchanged.
9
+
10
+ Earlier releases are described at the top of the README.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: polynode
3
- Version: 0.15.0
3
+ Version: 0.15.1
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.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).
39
+ **New in v0.15.1:** Fixes `wrap_to_polyusd()` and `unwrap_from_polyusd()` for EOA wallets on Polygon. With web3 7 or 8 they failed before sending anything, with an `ExtraDataLengthError` about a POA chain. They now set gas and gas price themselves, wait for the approval to confirm before sending the wrap or unwrap, and return the transaction hash with a `0x` prefix. `set_approvals()` gets the same gas price fix. `ensure_ready()` results now include `approval_tx_hashes`: the hashes of any approval transactions that call sent, or an empty list when none were needed.
40
+
41
+ **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
42
 
41
43
  **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.
42
44
 
@@ -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.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).
5
+ **New in v0.15.1:** Fixes `wrap_to_polyusd()` and `unwrap_from_polyusd()` for EOA wallets on Polygon. With web3 7 or 8 they failed before sending anything, with an `ExtraDataLengthError` about a POA chain. They now set gas and gas price themselves, wait for the approval to confirm before sending the wrap or unwrap, and return the transaction hash with a `0x` prefix. `set_approvals()` gets the same gas price fix. `ensure_ready()` results now include `approval_tx_hashes`: the hashes of any approval transactions that call sent, or an empty list when none were needed.
6
+
7
+ **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
8
 
7
9
  **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.
8
10
 
@@ -0,0 +1 @@
1
+ __version__ = "0.15.1"
@@ -773,6 +773,9 @@ async def set_approvals(
773
773
 
774
774
  tx_hashes: list[str] = []
775
775
  nonce = w3.eth.get_transaction_count(account.address)
776
+ # An explicit gas price keeps web3 from decoding the latest block, which
777
+ # fails on Polygon without the POA middleware.
778
+ gas_price = w3.eth.gas_price
776
779
 
777
780
  for spender in spender_list:
778
781
  spender_addr = Web3.to_checksum_address(spender)
@@ -783,6 +786,7 @@ async def set_approvals(
783
786
  "from": account.address,
784
787
  "nonce": nonce,
785
788
  "chainId": CHAIN_ID,
789
+ "gasPrice": gas_price,
786
790
  }
787
791
  )
788
792
  signed = account.sign_transaction(tx)
@@ -798,6 +802,7 @@ async def set_approvals(
798
802
  "from": account.address,
799
803
  "nonce": nonce,
800
804
  "chainId": CHAIN_ID,
805
+ "gasPrice": gas_price,
801
806
  }
802
807
  )
803
808
  signed = account.sign_transaction(tx)
@@ -823,6 +828,7 @@ async def set_approvals(
823
828
  "from": account.address,
824
829
  "nonce": nonce,
825
830
  "chainId": CHAIN_ID,
831
+ "gasPrice": gas_price,
826
832
  }
827
833
  )
828
834
  signed = account.sign_transaction(tx)
@@ -38,6 +38,7 @@ from .clob_api import (
38
38
  refresh_balance_allowance as clob_refresh_balance_allowance,
39
39
  )
40
40
  from .constants import (
41
+ CHAIN_ID,
41
42
  CLOB_HOST_V2,
42
43
  COLLATERAL_OFFRAMP,
43
44
  COLLATERAL_ONRAMP,
@@ -438,6 +439,7 @@ class PolyNodeTrader:
438
439
 
439
440
  temp = await normalize_signer(signer, type or SignatureType.POLY_GNOSIS_SAFE)
440
441
  actions: list[str] = []
442
+ approval_tx_hashes: list[str] = []
441
443
 
442
444
  if type is not None:
443
445
  sig_type = type
@@ -571,13 +573,14 @@ class PolyNodeTrader:
571
573
  execution_mode=self._execution_mode,
572
574
  )
573
575
  if sig_type == SignatureType.POLY_GNOSIS_SAFE:
574
- await relay.execute_safe_with_signer(
576
+ approval_hash = await relay.execute_safe_with_signer(
575
577
  normalized, funder, approval_txns
576
578
  )
577
579
  else:
578
- await relay.execute_deposit_wallet_calls(
580
+ approval_hash = await relay.execute_deposit_wallet_calls(
579
581
  normalized, funder, approval_txns
580
582
  )
583
+ _record_tx_hashes(approval_tx_hashes, approval_hash)
581
584
  verified = await check_approvals_onchain(
582
585
  funder,
583
586
  self._rpc_url,
@@ -690,7 +693,7 @@ class PolyNodeTrader:
690
693
  protocol_v2_ready: bool | None = None
691
694
  if protocol_v2:
692
695
  granted = await self._ensure_protocol_v2_order_ready(
693
- stored, normalized, strict=True
696
+ stored, normalized, strict=True, tx_hashes=approval_tx_hashes
694
697
  )
695
698
  actions.append(
696
699
  "protocol_v2_approvals_set"
@@ -719,6 +722,7 @@ class PolyNodeTrader:
719
722
  and self._user_relayer_credentials is not None
720
723
  ),
721
724
  protocol_v2_ready=protocol_v2_ready,
725
+ approval_tx_hashes=approval_tx_hashes,
722
726
  )
723
727
 
724
728
  async def link_wallet(
@@ -1192,35 +1196,14 @@ class PolyNodeTrader:
1192
1196
  "wrap_to_polyusd requires a private key signer for EOA wallets"
1193
1197
  )
1194
1198
 
1195
- nonce = w3.eth.get_transaction_count(funder_addr)
1196
- if allowance < amount:
1197
- tx = usdc_contract.functions.approve(
1198
- onramp_addr, 2**256 - 1
1199
- ).build_transaction(
1200
- {
1201
- "from": funder_addr,
1202
- "nonce": nonce,
1203
- "chainId": 137,
1204
- }
1205
- )
1206
- signed = w3.eth.account.sign_transaction(tx, signer._private_key)
1207
- w3.eth.send_raw_transaction(signed.raw_transaction)
1208
- nonce += 1
1209
-
1210
- tx = onramp_contract.functions.wrap(
1211
- Web3.to_checksum_address(USDC),
1199
+ return await asyncio.to_thread(
1200
+ _send_eoa_with_approval,
1201
+ w3,
1202
+ signer._private_key,
1212
1203
  funder_addr,
1213
- int(amount),
1214
- ).build_transaction(
1215
- {
1216
- "from": funder_addr,
1217
- "nonce": nonce,
1218
- "chainId": 137,
1219
- }
1204
+ (USDC, approve_data) if allowance < amount else None,
1205
+ (COLLATERAL_ONRAMP, wrap_data),
1220
1206
  )
1221
- signed = w3.eth.account.sign_transaction(tx, signer._private_key)
1222
- tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
1223
- return tx_hash.hex()
1224
1207
 
1225
1208
  async def unwrap_from_polyusd(self, amount: int) -> str:
1226
1209
  """Unwrap PolyUSD back into USDC.e via the Collateral Offramp.
@@ -1334,35 +1317,14 @@ class PolyNodeTrader:
1334
1317
  "unwrap_from_polyusd requires a private key signer for EOA wallets"
1335
1318
  )
1336
1319
 
1337
- nonce = w3.eth.get_transaction_count(funder_addr)
1338
- if allowance < amount:
1339
- tx = polyusd_contract.functions.approve(
1340
- offramp_addr, 2**256 - 1
1341
- ).build_transaction(
1342
- {
1343
- "from": funder_addr,
1344
- "nonce": nonce,
1345
- "chainId": 137,
1346
- }
1347
- )
1348
- signed = w3.eth.account.sign_transaction(tx, signer._private_key)
1349
- w3.eth.send_raw_transaction(signed.raw_transaction)
1350
- nonce += 1
1351
-
1352
- tx = offramp_contract.functions.unwrap(
1353
- Web3.to_checksum_address(USDC),
1320
+ return await asyncio.to_thread(
1321
+ _send_eoa_with_approval,
1322
+ w3,
1323
+ signer._private_key,
1354
1324
  funder_addr,
1355
- int(amount),
1356
- ).build_transaction(
1357
- {
1358
- "from": funder_addr,
1359
- "nonce": nonce,
1360
- "chainId": 137,
1361
- }
1325
+ (POLY_USD, approve_data) if allowance < amount else None,
1326
+ (COLLATERAL_OFFRAMP, unwrap_data),
1362
1327
  )
1363
- signed = w3.eth.account.sign_transaction(tx, signer._private_key)
1364
- tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
1365
- return tx_hash.hex()
1366
1328
 
1367
1329
  def get_polyusd_balance(self) -> int:
1368
1330
  """Get PolyUSD balance for the active funder address.
@@ -2618,11 +2580,13 @@ class PolyNodeTrader:
2618
2580
  signer: NormalizedSigner,
2619
2581
  *,
2620
2582
  strict: bool = False,
2583
+ tx_hashes: list[str] | None = None,
2621
2584
  ) -> bool:
2622
2585
  """Grant the ExchangeV3 approvals if missing. Returns True if it granted.
2623
2586
 
2624
2587
  ``strict`` (ensure_ready) surfaces an unreadable chain state instead of
2625
- letting the order proceed.
2588
+ letting the order proceed. Sent transaction hashes are appended to
2589
+ ``tx_hashes`` when given.
2626
2590
  """
2627
2591
  funder = creds.funder_address or creds.wallet_address
2628
2592
  key = funder.lower()
@@ -2645,16 +2609,21 @@ class PolyNodeTrader:
2645
2609
 
2646
2610
  txns = build_protocol_v2_order_approval_txns()
2647
2611
  sig_type = creds.signature_type
2612
+ sent: Any
2648
2613
  if sig_type == SignatureType.POLY_GNOSIS_SAFE:
2649
- await self._relay_client().execute_safe_with_signer(signer, funder, txns)
2614
+ sent = await self._relay_client().execute_safe_with_signer(
2615
+ signer, funder, txns
2616
+ )
2650
2617
  elif sig_type == SignatureType.POLY_1271:
2651
- await self._relay_client().execute_deposit_wallet_calls(signer, funder, txns)
2618
+ sent = await self._relay_client().execute_deposit_wallet_calls(
2619
+ signer, funder, txns
2620
+ )
2652
2621
  elif (
2653
2622
  sig_type == SignatureType.EOA
2654
2623
  and signer._private_key
2655
2624
  and funder.lower() == signer.address.lower()
2656
2625
  ):
2657
- await asyncio.to_thread(
2626
+ sent = await asyncio.to_thread(
2658
2627
  send_protocol_v2_order_approvals_eoa,
2659
2628
  signer._private_key,
2660
2629
  self._rpc_url,
@@ -2665,6 +2634,8 @@ class PolyNodeTrader:
2665
2634
  "cannot be approved automatically; approve pUSD and PositionManager "
2666
2635
  "for ExchangeV3 from the funding wallet, then retry"
2667
2636
  )
2637
+ if tx_hashes is not None:
2638
+ _record_tx_hashes(tx_hashes, sent)
2668
2639
  if not await asyncio.to_thread(
2669
2640
  read_protocol_v2_order_approvals, funder, self._rpc_url
2670
2641
  ):
@@ -2743,6 +2714,58 @@ class PolyNodeTrader:
2743
2714
  return self._active_signer
2744
2715
 
2745
2716
 
2717
+ def _record_tx_hashes(out: list[str], sent: Any) -> None:
2718
+ """Append transaction hash(es) returned by a send as 0x-prefixed strings."""
2719
+ for h in sent if isinstance(sent, (list, tuple)) else [sent]:
2720
+ if isinstance(h, (bytes, bytearray)):
2721
+ h = h.hex()
2722
+ if isinstance(h, str) and h:
2723
+ out.append(h if h.startswith("0x") else "0x" + h)
2724
+
2725
+
2726
+ def _send_eoa_with_approval(
2727
+ w3: Any,
2728
+ private_key: str,
2729
+ sender: str,
2730
+ approval: tuple[str, str] | None,
2731
+ call: tuple[str, str],
2732
+ *,
2733
+ receipt_timeout: float = 120.0,
2734
+ ) -> str:
2735
+ """Send an optional ERC-20 approval, wait for it, then send ``call``.
2736
+
2737
+ Gas and gas price are set explicitly so web3 never has to decode the
2738
+ latest block (Polygon blocks need the POA middleware for that).
2739
+ Returns the 0x-prefixed hash of ``call``.
2740
+ """
2741
+ from web3 import Web3
2742
+
2743
+ def send(to: str, data: str, nonce: int) -> str:
2744
+ tx: dict[str, Any] = {
2745
+ "from": sender,
2746
+ "to": Web3.to_checksum_address(to),
2747
+ "data": data,
2748
+ "value": 0,
2749
+ "nonce": nonce,
2750
+ "chainId": CHAIN_ID,
2751
+ }
2752
+ tx["gas"] = w3.eth.estimate_gas(tx)
2753
+ tx["gasPrice"] = w3.eth.gas_price
2754
+ signed = w3.eth.account.sign_transaction(tx, private_key)
2755
+ return Web3.to_hex(w3.eth.send_raw_transaction(signed.raw_transaction))
2756
+
2757
+ nonce = w3.eth.get_transaction_count(sender)
2758
+ if approval is not None:
2759
+ approve_hash = send(*approval, nonce)
2760
+ receipt = w3.eth.wait_for_transaction_receipt(
2761
+ approve_hash, timeout=receipt_timeout
2762
+ )
2763
+ if receipt.get("status") != 1:
2764
+ raise RuntimeError("approval transaction reverted")
2765
+ nonce += 1
2766
+ return send(*call, nonce)
2767
+
2768
+
2746
2769
  def _position_hint(value: Any) -> ProtocolVersion | None:
2747
2770
  if value is None or value in ("v1", "v2"):
2748
2771
  return value
@@ -405,6 +405,9 @@ class ReadyStatus:
405
405
  # Set only by ensure_ready(protocol_v2=True): True once the Polymarket
406
406
  # Protocol V2 order approvals (ExchangeV3) are in place.
407
407
  protocol_v2_ready: bool | None = None
408
+ # Hashes of the approval transactions this call sent (relayer or direct),
409
+ # in send order. Empty when nothing was sent.
410
+ approval_tx_hashes: list[str] = field(default_factory=list)
408
411
 
409
412
 
410
413
  @dataclass
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "polynode"
7
- version = "0.15.0"
7
+ version = "0.15.1"
8
8
  description = "Python SDK for the Polynode real-time prediction market data platform"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1 +0,0 @@
1
- __version__ = "0.15.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes