polynode 0.14.0__tar.gz → 0.14.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 (47) hide show
  1. {polynode-0.14.0 → polynode-0.14.1}/PKG-INFO +69 -14
  2. {polynode-0.14.0 → polynode-0.14.1}/README.md +68 -13
  3. polynode-0.14.1/polynode/_version.py +1 -0
  4. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/eip712.py +29 -7
  5. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/types.py +66 -8
  6. {polynode-0.14.0 → polynode-0.14.1}/pyproject.toml +1 -1
  7. polynode-0.14.0/polynode/_version.py +0 -1
  8. {polynode-0.14.0 → polynode-0.14.1}/.gitignore +0 -0
  9. {polynode-0.14.0 → polynode-0.14.1}/core-contract-v1.json +0 -0
  10. {polynode-0.14.0 → polynode-0.14.1}/core-fixtures-v1.json +0 -0
  11. {polynode-0.14.0 → polynode-0.14.1}/polynode/__init__.py +0 -0
  12. {polynode-0.14.0 → polynode-0.14.1}/polynode/cache/__init__.py +0 -0
  13. {polynode-0.14.0 → polynode-0.14.1}/polynode/client.py +0 -0
  14. {polynode-0.14.0 → polynode-0.14.1}/polynode/engine.py +0 -0
  15. {polynode-0.14.0 → polynode-0.14.1}/polynode/errors.py +0 -0
  16. {polynode-0.14.0 → polynode-0.14.1}/polynode/orderbook.py +0 -0
  17. {polynode-0.14.0 → polynode-0.14.1}/polynode/orderbook_integrity.py +0 -0
  18. {polynode-0.14.0 → polynode-0.14.1}/polynode/orderbook_state.py +0 -0
  19. {polynode-0.14.0 → polynode-0.14.1}/polynode/perps.py +0 -0
  20. {polynode-0.14.0 → polynode-0.14.1}/polynode/redemption_watcher.py +0 -0
  21. {polynode-0.14.0 → polynode-0.14.1}/polynode/short_form.py +0 -0
  22. {polynode-0.14.0 → polynode-0.14.1}/polynode/subscription.py +0 -0
  23. {polynode-0.14.0 → polynode-0.14.1}/polynode/testing.py +0 -0
  24. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/__init__.py +0 -0
  25. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/clob_api.py +0 -0
  26. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/constants.py +0 -0
  27. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/cosigner.py +0 -0
  28. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/escrow.py +0 -0
  29. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/onboarding.py +0 -0
  30. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/position_management.py +0 -0
  31. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/privy.py +0 -0
  32. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/relayer.py +0 -0
  33. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/signer.py +0 -0
  34. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/sqlite_backend.py +0 -0
  35. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/trader.py +0 -0
  36. {polynode-0.14.0 → polynode-0.14.1}/polynode/trading/user_relayer.py +0 -0
  37. {polynode-0.14.0 → polynode-0.14.1}/polynode/types/__init__.py +0 -0
  38. {polynode-0.14.0 → polynode-0.14.1}/polynode/types/enums.py +0 -0
  39. {polynode-0.14.0 → polynode-0.14.1}/polynode/types/events.py +0 -0
  40. {polynode-0.14.0 → polynode-0.14.1}/polynode/types/orderbook.py +0 -0
  41. {polynode-0.14.0 → polynode-0.14.1}/polynode/types/perps.py +0 -0
  42. {polynode-0.14.0 → polynode-0.14.1}/polynode/types/rest.py +0 -0
  43. {polynode-0.14.0 → polynode-0.14.1}/polynode/types/short_form.py +0 -0
  44. {polynode-0.14.0 → polynode-0.14.1}/polynode/types/ws.py +0 -0
  45. {polynode-0.14.0 → polynode-0.14.1}/polynode/v3.py +0 -0
  46. {polynode-0.14.0 → polynode-0.14.1}/polynode/v3_operations.py +0 -0
  47. {polynode-0.14.0 → polynode-0.14.1}/polynode/ws.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: polynode
3
- Version: 0.14.0
3
+ Version: 0.14.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.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.
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.
40
+
41
+ **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.
40
42
 
41
43
  **In v0.13.0:** Trading added explicit `user_owned` execution. Existing builder mode remains the default; opted-in wallets use zero builder attribution, one wallet-ownership authorization, and strict wallet-bound gasless credentials. Builder credentials and nonzero builder codes fail closed in this mode. EOA-controlled Safe and deposit wallets are supported; legacy Magic/proxy wallets are intentionally excluded from the first release.
42
44
 
@@ -536,29 +538,82 @@ const result = await fetch("/api/orders/submit", {
536
538
  }).then((response) => response.json());
537
539
  ```
538
540
 
539
- On the backend, atomically take the prepared object from session state, then validate and submit it:
541
+ On the backend, atomically take the prepared object from session state, durably record the deterministic order identity, then validate and submit it:
540
542
 
541
543
  ```python
542
- async def submit_order(trader, browser_result, prepared_orders):
544
+ async def submit_order(trader, browser_result, prepared_orders, attempts):
543
545
  # `take_once` must delete atomically so two workers cannot submit the same request.
544
546
  prepared = await prepared_orders.take_once(browser_result.request_id)
545
547
  if prepared is None:
546
548
  raise ValueError("Unknown or already-used signing request")
547
549
 
548
- result = await trader.submit_prepared_user_owned_order(
549
- prepared,
550
+ order_hash = prepared.order_hash
551
+ # This durable, idempotent write must commit before submission starts.
552
+ await attempts.create_once(
553
+ order_hash=order_hash,
550
554
  request_id=browser_result.request_id,
551
- address=browser_result.address,
552
- signature=browser_result.signature,
555
+ wallet=prepared.signing_request.address,
556
+ token_id=prepared.signing_request.order["tokenId"],
557
+ side=prepared.signing_request.order["side"],
558
+ status="submitting",
553
559
  )
554
- return {
555
- "success": result.success,
556
- "orderId": result.order_id,
557
- "error": result.error,
558
- }
560
+
561
+ try:
562
+ result = await trader.submit_prepared_user_owned_order(
563
+ prepared,
564
+ request_id=browser_result.request_id,
565
+ address=browser_result.address,
566
+ signature=browser_result.signature,
567
+ )
568
+ except BaseException:
569
+ # Once consumed, a local/network exception has an ambiguous outcome.
570
+ status = (
571
+ "outcome_unknown"
572
+ if prepared.consumed
573
+ else "rejected_before_submission"
574
+ )
575
+ await attempts.set_status(order_hash, status)
576
+ raise
577
+ else:
578
+ if result.order_id and result.order_id.lower() != order_hash.lower():
579
+ await attempts.set_status(order_hash, "identity_mismatch")
580
+ raise RuntimeError("CLOB returned an unexpected order ID")
581
+ await attempts.record_response(order_hash, result)
582
+ return {
583
+ "success": result.success,
584
+ "orderId": result.order_id or order_hash,
585
+ "error": result.error,
586
+ }
559
587
  ```
560
588
 
561
- `prepare_user_owned_order()` supports V2 user-owned execution only, expires after five minutes by default, forces zero builder attribution, rejects positive fee authentication, and performs no submission. GTC, FOK, and FAK orders must omit expiration (zero is accepted and canonicalized to no expiration); GTD requires a fresh Unix-seconds expiration with the 60-second safety buffer, and the signing request is clamped to that window. `submit_prepared_user_owned_order()` verifies the request ID, expiry, connected wallet, stored wallet/funder identity, exact typed data, and recovered signer before consuming the request. A consumed request cannot be replayed. If submission has an ambiguous network result, reconcile its status instead of preparing an automatic duplicate.
589
+ `prepared.order_hash` is the lowercase `0x`-prefixed EIP-712 digest of the canonical standard exchange `Order`. For EOA and Safe orders it also matches the browser-signing digest. POLY_1271 browsers sign an outer `TypedDataSign` wrapper, whose digest is intentionally different; `order_hash` always identifies the inner order that the CLOB and fills report. The property is computed on demand, is unchanged by trusted-state export/import, and is not added to the serialized schema.
590
+
591
+ `prepare_user_owned_order()` supports V2 user-owned execution only, expires after five minutes by default, forces zero builder attribution, rejects positive fee authentication, and performs no submission. GTC, FOK, and FAK orders must omit expiration (zero is accepted and canonicalized to no expiration); GTD requires a fresh Unix-seconds expiration with the 60-second safety buffer, and the signing request is clamped to that window. `submit_prepared_user_owned_order()` verifies the request ID, expiry, connected wallet, stored wallet/funder identity, exact typed data, and recovered signer before consuming the request. A consumed request cannot be replayed.
592
+
593
+ Treat any exception after `prepared.consumed` becomes true—including an HTTP timeout, disconnect, cancellation, or local failure around the network call—as `outcome_unknown`. Never submit that hash again and never automatically prepare a replacement. Reconcile by exact hash against both `await trader.get_open_orders(asset_id=token_id)` (`OpenOrder.id`) and your durable authenticated V2 fill source (`fill.order_hash`):
594
+
595
+ ```python
596
+ async def reconcile_unknown(trader, attempt, fills):
597
+ open_orders = await trader.get_open_orders(asset_id=attempt.token_id)
598
+ open_order = next(
599
+ (order for order in open_orders if order.id.lower() == attempt.order_hash.lower()),
600
+ None,
601
+ )
602
+ matching_fills = await fills.by_order_hash(attempt.order_hash)
603
+ return {"openOrder": open_order, "fills": matching_fills}
604
+ ```
605
+
606
+ An open order may already be partially filled (`size_matched` is nonzero), while a fully filled or canceled order will not remain open. Group all partial fills for the exact hash. Finding neither an open order nor a fill is not proof that submission failed: keep the attempt unknown and retry reconciliation with bounded backoff. The application-owned attempt store and fill source must be durable, wallet-scoped, and idempotent by `order_hash`; do not infer identity from token, side, amounts, timestamps, balances, or request ID.
607
+
608
+ If a request-scoped trader was populated from a browser bundle or decrypted vault record, clear its credentials even when validation, signing, submission, or reconciliation fails:
609
+
610
+ ```python
611
+ trader = await trader_from_browser_bundle(request_json)
612
+ try:
613
+ result = await submit_order(trader, browser_result, prepared_orders, attempts)
614
+ finally:
615
+ trader.close()
616
+ ```
562
617
 
563
618
  For a single-process application, a backend-only in-memory dictionary is sufficient. For multiple workers, serialize only with the SDK's trusted server-state methods and use a server-side store that can atomically take/delete by `requestId`:
564
619
 
@@ -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.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.
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.
6
+
7
+ **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.
6
8
 
7
9
  **In v0.13.0:** Trading added explicit `user_owned` execution. Existing builder mode remains the default; opted-in wallets use zero builder attribution, one wallet-ownership authorization, and strict wallet-bound gasless credentials. Builder credentials and nonzero builder codes fail closed in this mode. EOA-controlled Safe and deposit wallets are supported; legacy Magic/proxy wallets are intentionally excluded from the first release.
8
10
 
@@ -502,29 +504,82 @@ const result = await fetch("/api/orders/submit", {
502
504
  }).then((response) => response.json());
503
505
  ```
504
506
 
505
- On the backend, atomically take the prepared object from session state, then validate and submit it:
507
+ On the backend, atomically take the prepared object from session state, durably record the deterministic order identity, then validate and submit it:
506
508
 
507
509
  ```python
508
- async def submit_order(trader, browser_result, prepared_orders):
510
+ async def submit_order(trader, browser_result, prepared_orders, attempts):
509
511
  # `take_once` must delete atomically so two workers cannot submit the same request.
510
512
  prepared = await prepared_orders.take_once(browser_result.request_id)
511
513
  if prepared is None:
512
514
  raise ValueError("Unknown or already-used signing request")
513
515
 
514
- result = await trader.submit_prepared_user_owned_order(
515
- prepared,
516
+ order_hash = prepared.order_hash
517
+ # This durable, idempotent write must commit before submission starts.
518
+ await attempts.create_once(
519
+ order_hash=order_hash,
516
520
  request_id=browser_result.request_id,
517
- address=browser_result.address,
518
- signature=browser_result.signature,
521
+ wallet=prepared.signing_request.address,
522
+ token_id=prepared.signing_request.order["tokenId"],
523
+ side=prepared.signing_request.order["side"],
524
+ status="submitting",
519
525
  )
520
- return {
521
- "success": result.success,
522
- "orderId": result.order_id,
523
- "error": result.error,
524
- }
526
+
527
+ try:
528
+ result = await trader.submit_prepared_user_owned_order(
529
+ prepared,
530
+ request_id=browser_result.request_id,
531
+ address=browser_result.address,
532
+ signature=browser_result.signature,
533
+ )
534
+ except BaseException:
535
+ # Once consumed, a local/network exception has an ambiguous outcome.
536
+ status = (
537
+ "outcome_unknown"
538
+ if prepared.consumed
539
+ else "rejected_before_submission"
540
+ )
541
+ await attempts.set_status(order_hash, status)
542
+ raise
543
+ else:
544
+ if result.order_id and result.order_id.lower() != order_hash.lower():
545
+ await attempts.set_status(order_hash, "identity_mismatch")
546
+ raise RuntimeError("CLOB returned an unexpected order ID")
547
+ await attempts.record_response(order_hash, result)
548
+ return {
549
+ "success": result.success,
550
+ "orderId": result.order_id or order_hash,
551
+ "error": result.error,
552
+ }
525
553
  ```
526
554
 
527
- `prepare_user_owned_order()` supports V2 user-owned execution only, expires after five minutes by default, forces zero builder attribution, rejects positive fee authentication, and performs no submission. GTC, FOK, and FAK orders must omit expiration (zero is accepted and canonicalized to no expiration); GTD requires a fresh Unix-seconds expiration with the 60-second safety buffer, and the signing request is clamped to that window. `submit_prepared_user_owned_order()` verifies the request ID, expiry, connected wallet, stored wallet/funder identity, exact typed data, and recovered signer before consuming the request. A consumed request cannot be replayed. If submission has an ambiguous network result, reconcile its status instead of preparing an automatic duplicate.
555
+ `prepared.order_hash` is the lowercase `0x`-prefixed EIP-712 digest of the canonical standard exchange `Order`. For EOA and Safe orders it also matches the browser-signing digest. POLY_1271 browsers sign an outer `TypedDataSign` wrapper, whose digest is intentionally different; `order_hash` always identifies the inner order that the CLOB and fills report. The property is computed on demand, is unchanged by trusted-state export/import, and is not added to the serialized schema.
556
+
557
+ `prepare_user_owned_order()` supports V2 user-owned execution only, expires after five minutes by default, forces zero builder attribution, rejects positive fee authentication, and performs no submission. GTC, FOK, and FAK orders must omit expiration (zero is accepted and canonicalized to no expiration); GTD requires a fresh Unix-seconds expiration with the 60-second safety buffer, and the signing request is clamped to that window. `submit_prepared_user_owned_order()` verifies the request ID, expiry, connected wallet, stored wallet/funder identity, exact typed data, and recovered signer before consuming the request. A consumed request cannot be replayed.
558
+
559
+ Treat any exception after `prepared.consumed` becomes true—including an HTTP timeout, disconnect, cancellation, or local failure around the network call—as `outcome_unknown`. Never submit that hash again and never automatically prepare a replacement. Reconcile by exact hash against both `await trader.get_open_orders(asset_id=token_id)` (`OpenOrder.id`) and your durable authenticated V2 fill source (`fill.order_hash`):
560
+
561
+ ```python
562
+ async def reconcile_unknown(trader, attempt, fills):
563
+ open_orders = await trader.get_open_orders(asset_id=attempt.token_id)
564
+ open_order = next(
565
+ (order for order in open_orders if order.id.lower() == attempt.order_hash.lower()),
566
+ None,
567
+ )
568
+ matching_fills = await fills.by_order_hash(attempt.order_hash)
569
+ return {"openOrder": open_order, "fills": matching_fills}
570
+ ```
571
+
572
+ An open order may already be partially filled (`size_matched` is nonzero), while a fully filled or canceled order will not remain open. Group all partial fills for the exact hash. Finding neither an open order nor a fill is not proof that submission failed: keep the attempt unknown and retry reconciliation with bounded backoff. The application-owned attempt store and fill source must be durable, wallet-scoped, and idempotent by `order_hash`; do not infer identity from token, side, amounts, timestamps, balances, or request ID.
573
+
574
+ If a request-scoped trader was populated from a browser bundle or decrypted vault record, clear its credentials even when validation, signing, submission, or reconciliation fails:
575
+
576
+ ```python
577
+ trader = await trader_from_browser_bundle(request_json)
578
+ try:
579
+ result = await submit_order(trader, browser_result, prepared_orders, attempts)
580
+ finally:
581
+ trader.close()
582
+ ```
528
583
 
529
584
  For a single-process application, a backend-only in-memory dictionary is sufficient. For multiple workers, serialize only with the SDK's trusted server-state methods and use a server-side store that can atomically take/delete by `requestId`:
530
585
 
@@ -0,0 +1 @@
1
+ __version__ = "0.14.1"
@@ -477,6 +477,18 @@ def validate_order_payload_pair_v2(
477
477
  funder_address: str,
478
478
  ) -> None:
479
479
  """Fail if prepared V2 order and wallet-signing payloads diverge."""
480
+ _validate_standard_order_payload_v2(order_payload)
481
+ expected_signing_payload = signing_payload_for_order_v2(
482
+ order_payload,
483
+ signature_type=signature_type,
484
+ funder_address=funder_address,
485
+ )
486
+ if signing_payload.to_dict() != expected_signing_payload.to_dict():
487
+ raise ValueError("prepared user-owned order and signing payload do not match")
488
+
489
+
490
+ def _validate_standard_order_payload_v2(order_payload: Eip712Payload) -> None:
491
+ """Fail unless a payload is an exact standard V2 exchange Order."""
480
492
  if order_payload.primary_type != "Order":
481
493
  raise ValueError("prepared user-owned order has an invalid primary type")
482
494
  if order_payload.types != ORDER_TYPES_V2:
@@ -486,13 +498,23 @@ def validate_order_payload_pair_v2(
486
498
  expected_fields = {field["name"] for field in ORDER_TYPES_V2["Order"]}
487
499
  if set(order_payload.message) != expected_fields:
488
500
  raise ValueError("prepared user-owned order has invalid EIP-712 fields")
489
- expected_signing_payload = signing_payload_for_order_v2(
490
- order_payload,
491
- signature_type=signature_type,
492
- funder_address=funder_address,
493
- )
494
- if signing_payload.to_dict() != expected_signing_payload.to_dict():
495
- raise ValueError("prepared user-owned order and signing payload do not match")
501
+
502
+
503
+ def _standard_order_hash_v2(order_payload: Eip712Payload) -> str:
504
+ """Return the canonical exchange digest for one standard V2 Order."""
505
+ _validate_standard_order_payload_v2(order_payload)
506
+ try:
507
+ from eth_account.messages import encode_typed_data
508
+ from eth_utils import keccak
509
+ except ImportError:
510
+ raise ImportError(
511
+ "eth-account is required for trading. Install with: "
512
+ "pip install polynode[trading]"
513
+ )
514
+
515
+ signable = encode_typed_data(full_message=order_payload.to_dict())
516
+ digest = keccak(b"\x19\x01" + signable.header + signable.body)
517
+ return f"0x{digest.hex()}"
496
518
 
497
519
 
498
520
  def validate_canonical_user_owned_order_v2(
@@ -157,14 +157,15 @@ class Eip712Payload:
157
157
  "verifyingContract": "address",
158
158
  "salt": "bytes32",
159
159
  }
160
- try:
161
- domain_type = [
162
- {"name": name, "type": domain_field_types[name]} for name in self.domain
163
- ]
164
- except KeyError as exc:
165
- raise ValueError(
166
- f"unsupported EIP-712 domain field: {exc.args[0]}"
167
- ) from exc
160
+ unsupported_fields = set(self.domain) - set(domain_field_types)
161
+ if unsupported_fields:
162
+ unsupported_field = sorted(unsupported_fields)[0]
163
+ raise ValueError(f"unsupported EIP-712 domain field: {unsupported_field}")
164
+ domain_type = [
165
+ {"name": name, "type": type_name}
166
+ for name, type_name in domain_field_types.items()
167
+ if name in self.domain
168
+ ]
168
169
  return deepcopy(
169
170
  {
170
171
  "domain": self.domain,
@@ -515,6 +516,63 @@ class PreparedUserOwnedOrder:
515
516
  """Whether this prepared order was discarded or used for submission."""
516
517
  return self._consumed
517
518
 
519
+ @property
520
+ def order_hash(self) -> str:
521
+ """Return the canonical V2 exchange Order EIP-712 digest.
522
+
523
+ This is the CLOB/open-order ID and fill ``order_hash``. For POLY_1271,
524
+ it hashes the inner standard Order, never the wallet-signing
525
+ ``TypedDataSign`` wrapper.
526
+ """
527
+ from .eip712 import (
528
+ _standard_order_hash_v2,
529
+ canonical_order_values_v2,
530
+ user_owned_order_preview_v2,
531
+ validate_canonical_user_owned_order_v2,
532
+ )
533
+
534
+ canonical_price, canonical_size = canonical_order_values_v2(
535
+ self._price,
536
+ self._size,
537
+ self._tick_size,
538
+ )
539
+ if self._price != canonical_price or self._size != canonical_size:
540
+ raise ValueError("prepared user-owned order price or size is not canonical")
541
+ validate_canonical_user_owned_order_v2(
542
+ self._order_payload,
543
+ self._signing_payload,
544
+ wallet_address=self._wallet_address,
545
+ funder_address=self._funder_address,
546
+ signature_type=self._signature_type,
547
+ token_id=self._token_id,
548
+ price=self._price,
549
+ size=self._size,
550
+ side=self._side,
551
+ tick_size=self._tick_size,
552
+ neg_risk=self._neg_risk,
553
+ )
554
+ if (
555
+ self.signing_request.version != "1"
556
+ or not isinstance(self.signing_request.address, str)
557
+ or self.signing_request.address.lower() != self._wallet_address.lower()
558
+ ):
559
+ raise ValueError("prepared user-owned order wallet binding is invalid")
560
+ if self.signing_request.typed_data != self._signing_payload.to_dict():
561
+ raise ValueError("prepared user-owned order signing request was modified")
562
+ expected_preview = user_owned_order_preview_v2(
563
+ self._order_payload,
564
+ token_id=self._token_id,
565
+ side=self._side,
566
+ price=self._price,
567
+ size=self._size,
568
+ order_type=self._order_type,
569
+ post_only=self._post_only,
570
+ expiration=self._expiration,
571
+ )
572
+ if self.signing_request.order != expected_preview:
573
+ raise ValueError("prepared user-owned order preview was modified")
574
+ return _standard_order_hash_v2(self._order_payload)
575
+
518
576
  def _unsigned_server_dict(self) -> dict[str, Any]:
519
577
  return {
520
578
  "version": "1",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "polynode"
7
- version = "0.14.0"
7
+ version = "0.14.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.14.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