polynode 0.9.1__py3-none-any.whl → 0.9.2__py3-none-any.whl

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.
@@ -54,7 +54,10 @@ COLLATERAL_ONRAMP = "0x93070a847efef7f70739046a929d47a521f5b8ee"
54
54
  COLLATERAL_OFFRAMP = "0x2957922eb93258b93368531d39facca3b4dc5854"
55
55
 
56
56
  # Fee Escrow
57
+ # V1 (USDC.e collateral, paired with the V1 CLOB)
57
58
  FEE_ESCROW_ADDRESS = "0xa11D28433B79D0A88F3119b16A090075752258EA"
59
+ # V2 (pUSD collateral, paired with the V2 CLOB). Deployed 2026-04-24.
60
+ FEE_ESCROW_ADDRESS_V2 = "0x3A43D88ef8Aae4dF5a50B3abf67122CAAeEF7c9F"
58
61
 
59
62
  # Metadata cache TTL (5 minutes)
60
63
  META_TTL_SECONDS = 300
@@ -14,10 +14,15 @@ from typing import Any
14
14
 
15
15
  import httpx
16
16
 
17
- from .constants import CHAIN_ID, FEE_ESCROW_ADDRESS
18
- from .types import Eip712Payload
17
+ from .constants import CHAIN_ID, FEE_ESCROW_ADDRESS, FEE_ESCROW_ADDRESS_V2
18
+ from .types import Eip712Payload, ExchangeVersion
19
19
 
20
20
  # ── EIP-712 Types ──
21
+ #
22
+ # Both V1 and V2 FeeEscrow share the same FeeAuth struct typehash. They differ only in
23
+ # the EIP-712 domain: V1 is ("PolyNodeFeeEscrow", "1", V1 contract); V2 is
24
+ # ("PolyNodeFeeEscrowV2", "2", V2 contract). Signing against the wrong domain produces
25
+ # a digest the contract won't accept — `pullFee` reverts with `InvalidSignature`.
21
26
 
22
27
  FEE_AUTH_DOMAIN = {
23
28
  "name": "PolyNodeFeeEscrow",
@@ -26,6 +31,13 @@ FEE_AUTH_DOMAIN = {
26
31
  "verifyingContract": FEE_ESCROW_ADDRESS,
27
32
  }
28
33
 
34
+ FEE_AUTH_DOMAIN_V2 = {
35
+ "name": "PolyNodeFeeEscrowV2",
36
+ "version": "2",
37
+ "chainId": CHAIN_ID,
38
+ "verifyingContract": FEE_ESCROW_ADDRESS_V2,
39
+ }
40
+
29
41
  FEE_AUTH_TYPES = {
30
42
  "FeeAuth": [
31
43
  {"name": "orderId", "type": "bytes32"},
@@ -40,6 +52,16 @@ FEE_AUTH_TYPES = {
40
52
  ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
41
53
 
42
54
 
55
+ def fee_auth_domain_for(version: ExchangeVersion) -> dict:
56
+ """Return the EIP-712 FeeAuth domain for a given exchange version."""
57
+ return FEE_AUTH_DOMAIN_V2 if version == ExchangeVersion.V2 else FEE_AUTH_DOMAIN
58
+
59
+
60
+ def fee_escrow_address_for(version: ExchangeVersion) -> str:
61
+ """Return the FeeEscrow contract address for a given exchange version."""
62
+ return FEE_ESCROW_ADDRESS_V2 if version == ExchangeVersion.V2 else FEE_ESCROW_ADDRESS
63
+
64
+
43
65
  # ── Fee Calculation ──
44
66
 
45
67
  def calculate_fee(price: float, size: float, fee_bps: int) -> int:
@@ -67,8 +89,17 @@ def generate_escrow_order_id() -> str:
67
89
 
68
90
  # ── Nonce Fetching ──
69
91
 
70
- async def fetch_escrow_nonce(rpc_url: str, signer_address: str) -> int:
71
- """Fetch the current nonce for a signer from the FeeEscrow contract via eth_call."""
92
+ async def fetch_escrow_nonce(
93
+ rpc_url: str,
94
+ signer_address: str,
95
+ escrow_address: str = FEE_ESCROW_ADDRESS,
96
+ ) -> int:
97
+ """Fetch the current nonce for a signer from a FeeEscrow contract via eth_call.
98
+
99
+ Pass `fee_escrow_address_for(exchange_version)` for the `escrow_address` argument
100
+ when the caller's exchange version is known (V2 customers must read nonces from
101
+ the V2 contract — nonces on V1 and V2 are tracked independently).
102
+ """
72
103
  # getNonce(address) selector = 0x2d0335ab
73
104
  addr = signer_address.lower().replace("0x", "").zfill(64)
74
105
  data = "0x2d0335ab" + addr
@@ -80,7 +111,7 @@ async def fetch_escrow_nonce(rpc_url: str, signer_address: str) -> int:
80
111
  "jsonrpc": "2.0",
81
112
  "id": 1,
82
113
  "method": "eth_call",
83
- "params": [{"to": FEE_ESCROW_ADDRESS, "data": data}, "latest"],
114
+ "params": [{"to": escrow_address, "data": data}, "latest"],
84
115
  },
85
116
  )
86
117
  result = resp.json()
@@ -103,6 +134,9 @@ class FeeAuthRequest:
103
134
  signature: str
104
135
  affiliate: str
105
136
  affiliate_share_bps: int
137
+ # V2-routing hint for the cosigner. Set when exchange_version == V2 so the
138
+ # cosigner picks the V2 operator; absent for V1 (preserves backwards compat).
139
+ escrow_contract: str | None = None
106
140
 
107
141
 
108
142
  async def sign_fee_auth(
@@ -116,19 +150,22 @@ async def sign_fee_auth(
116
150
  nonce: int,
117
151
  affiliate: str | None = None,
118
152
  affiliate_share_bps: int | None = None,
153
+ exchange_version: ExchangeVersion = ExchangeVersion.V1,
119
154
  ) -> FeeAuthRequest:
120
155
  """Sign a FeeAuth EIP-712 message.
121
156
 
122
157
  Args:
123
158
  sign_typed_data: Callable that signs an Eip712Payload and returns a hex signature.
124
159
  escrow_order_id: Random bytes32 hex string.
125
- payer: Safe wallet address (where USDC is pulled from).
160
+ payer: Safe wallet address (where collateral is pulled from — USDC.e on V1, pUSD on V2).
126
161
  signer_address: EOA address (signs the message).
127
- fee_amount: Fee in raw USDC (6 decimals).
162
+ fee_amount: Fee in raw collateral units (6 decimals).
128
163
  deadline: Unix timestamp — authorization expires after this.
129
164
  nonce: Signer's current nonce from the escrow contract.
130
165
  affiliate: Partner wallet address.
131
166
  affiliate_share_bps: Partner's share of fee in bps.
167
+ exchange_version: V1 (default, USDC.e) or V2 (pUSD). Selects the EIP-712 domain
168
+ and the escrow contract address the cosigner should route to.
132
169
 
133
170
  Returns:
134
171
  FeeAuthRequest ready for the cosigner.
@@ -143,7 +180,7 @@ async def sign_fee_auth(
143
180
  }
144
181
 
145
182
  payload = Eip712Payload(
146
- domain=FEE_AUTH_DOMAIN,
183
+ domain=fee_auth_domain_for(exchange_version),
147
184
  types=FEE_AUTH_TYPES,
148
185
  primary_type="FeeAuth",
149
186
  message=message,
@@ -153,6 +190,10 @@ async def sign_fee_auth(
153
190
  if not signature.startswith("0x"):
154
191
  signature = f"0x{signature}"
155
192
 
193
+ escrow_contract = (
194
+ FEE_ESCROW_ADDRESS_V2 if exchange_version == ExchangeVersion.V2 else None
195
+ )
196
+
156
197
  return FeeAuthRequest(
157
198
  escrow_order_id=escrow_order_id,
158
199
  payer=payer,
@@ -163,4 +204,5 @@ async def sign_fee_auth(
163
204
  signature=signature,
164
205
  affiliate=affiliate or ZERO_ADDRESS,
165
206
  affiliate_share_bps=affiliate_share_bps if affiliate_share_bps is not None else 10000,
207
+ escrow_contract=escrow_contract,
166
208
  )
@@ -29,6 +29,7 @@ from .constants import (
29
29
  USDC,
30
30
  V2_SPENDERS,
31
31
  FEE_ESCROW_ADDRESS,
32
+ FEE_ESCROW_ADDRESS_V2,
32
33
  )
33
34
  from .types import ApprovalStatus, BalanceInfo, ExchangeVersion, SignatureType
34
35
 
@@ -189,9 +190,16 @@ async def check_approvals(
189
190
  approved = ctf_contract.functions.isApprovedForAll(funder, spender_addr).call()
190
191
  ctf_status[name] = approved
191
192
 
192
- # Fee Escrow USDC approval (always USDC.e regardless of exchange version)
193
- usdc_contract = w3.eth.contract(address=Web3.to_checksum_address(USDC), abi=erc20_abi)
194
- escrow_allowance = usdc_contract.functions.allowance(funder, Web3.to_checksum_address(FEE_ESCROW_ADDRESS)).call()
193
+ # Fee Escrow approval. V1 approves USDC.e for the V1 contract; V2 approves pUSD
194
+ # for the V2 contract. pullFee fails with `TransferFailed` if the approval is
195
+ # missing on the version the caller's orders are routed to.
196
+ if version == ExchangeVersion.V2:
197
+ escrow_token_contract = w3.eth.contract(address=Web3.to_checksum_address(POLY_USD), abi=erc20_abi)
198
+ escrow_addr = Web3.to_checksum_address(FEE_ESCROW_ADDRESS_V2)
199
+ else:
200
+ escrow_token_contract = w3.eth.contract(address=Web3.to_checksum_address(USDC), abi=erc20_abi)
201
+ escrow_addr = Web3.to_checksum_address(FEE_ESCROW_ADDRESS)
202
+ escrow_allowance = escrow_token_contract.functions.allowance(funder, escrow_addr).call()
195
203
  usdc_status["fee_escrow"] = escrow_allowance >= threshold
196
204
 
197
205
  all_approved = all(usdc_status.values()) and all(ctf_status.values())
@@ -273,9 +281,16 @@ async def set_approvals(
273
281
  tx_hashes.append(tx_hash.hex())
274
282
  nonce += 1
275
283
 
276
- # Fee Escrow USDC approval (always USDC.e regardless of exchange version)
277
- escrow_contract = w3.eth.contract(address=Web3.to_checksum_address(USDC), abi=erc20_abi)
278
- tx = escrow_contract.functions.approve(Web3.to_checksum_address(FEE_ESCROW_ADDRESS), max_uint).build_transaction({
284
+ # Fee Escrow collateral approval V1 approves USDC.e for V1 FeeEscrow;
285
+ # V2 approves pUSD for V2 FeeEscrow.
286
+ if version == ExchangeVersion.V2:
287
+ escrow_token = POLY_USD
288
+ escrow_target = FEE_ESCROW_ADDRESS_V2
289
+ else:
290
+ escrow_token = USDC
291
+ escrow_target = FEE_ESCROW_ADDRESS
292
+ escrow_token_contract = w3.eth.contract(address=Web3.to_checksum_address(escrow_token), abi=erc20_abi)
293
+ tx = escrow_token_contract.functions.approve(Web3.to_checksum_address(escrow_target), max_uint).build_transaction({
279
294
  "from": account.address,
280
295
  "nonce": nonce,
281
296
  "chainId": CHAIN_ID,
@@ -26,7 +26,7 @@ from .constants import (
26
26
  )
27
27
  from .cosigner import build_l2_headers, send_via_cosigner
28
28
  from .eip712 import create_signed_order, create_signed_order_v2
29
- from .escrow import calculate_fee, generate_escrow_order_id, fetch_escrow_nonce, sign_fee_auth
29
+ from .escrow import calculate_fee, fee_escrow_address_for, fetch_escrow_nonce, generate_escrow_order_id, sign_fee_auth
30
30
  from .onboarding import (
31
31
  check_approvals as check_approvals_onchain,
32
32
  check_balance as check_balance_onchain,
@@ -699,7 +699,8 @@ class PolyNodeTrader:
699
699
  fee_amount_raw = calculate_fee(params.price, params.size, fee_config.fee_bps)
700
700
  if fee_amount_raw > 0:
701
701
  funder = creds.funder_address or creds.wallet_address
702
- nonce = await fetch_escrow_nonce(self._rpc_url, signer.address)
702
+ escrow_address = fee_escrow_address_for(self._exchange_version)
703
+ nonce = await fetch_escrow_nonce(self._rpc_url, signer.address, escrow_address)
703
704
  deadline = int(time.time()) + 300
704
705
  escrow_oid = generate_escrow_order_id()
705
706
  fee_auth = await sign_fee_auth(
@@ -712,6 +713,7 @@ class PolyNodeTrader:
712
713
  nonce=nonce,
713
714
  affiliate=fee_config.affiliate,
714
715
  affiliate_share_bps=fee_config.affiliate_share_bps,
716
+ exchange_version=self._exchange_version,
715
717
  )
716
718
  fee_auth_dict = {
717
719
  "escrow_order_id": fee_auth.escrow_order_id,
@@ -724,6 +726,8 @@ class PolyNodeTrader:
724
726
  "affiliate": fee_auth.affiliate,
725
727
  "affiliate_share_bps": fee_auth.affiliate_share_bps,
726
728
  }
729
+ if fee_auth.escrow_contract:
730
+ fee_auth_dict["escrow_contract"] = fee_auth.escrow_contract
727
731
 
728
732
  request_data: dict[str, Any] = {"method": "POST", "path": "/order", "body": body_str, "headers": headers}
729
733
  if fee_auth_dict:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polynode
3
- Version: 0.9.1
3
+ Version: 0.9.2
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
@@ -14,17 +14,17 @@ polynode/cache/__init__.py,sha256=A9lXcIE3ISKKo1r0o3o-CkmQpBhLdKwdQfCR0eW5i9o,51
14
14
  polynode/trading/V2_ORDER_FLOW.md,sha256=Ppd7iwZjtmVwok-fP4Ofo4UBQ9pIAg0t57RJ-OzqRHs,10926
15
15
  polynode/trading/__init__.py,sha256=sPv30j9eyvqjnUBxGAYaKpAPQZ2jTw3UwA4DZXYZ21s,923
16
16
  polynode/trading/clob_api.py,sha256=Sfb8XLN9MMzVE6jARNqEQxiD608OC_6Xjc7uiJbDQEc,7274
17
- polynode/trading/constants.py,sha256=aW8F9Q2W0TRNVX0JNh39k0qxbb03ijUAGbWjrJFjf_o,2513
17
+ polynode/trading/constants.py,sha256=IEdrwC4bPjo4MoVlDKYqXphlmIKsoxO4f_s_pyZ3x08,2702
18
18
  polynode/trading/cosigner.py,sha256=Fww9OgaKdHQAHd8Kom1AJBtmMGC8FbNZxdFaFALNBY8,3058
19
19
  polynode/trading/eip712.py,sha256=PKLfMt58Diy419jiQXysnX1kSlU_QcWDRofGEb3u_JI,9744
20
- polynode/trading/escrow.py,sha256=bU_ohyOlx2r6KjBYxUNdjROvrmunWV1Ey99AuSOt9Z8,4590
21
- polynode/trading/onboarding.py,sha256=oXtFz1HF1vGvMnb5o44SwuADd2mdU5ws1v7n30rk6fg,15066
20
+ polynode/trading/escrow.py,sha256=T2v5bv3wIyQTcFoxcHXlDBlzyeWDYbxRIqQamqlhaog,6513
21
+ polynode/trading/onboarding.py,sha256=GdQh1aM9F0LuCL3uqNTguXX5Sxe1_0hSVdHR7kksKZ0,15777
22
22
  polynode/trading/position_management.py,sha256=z2pYls4ErhyRE6thQdOs45BF4llKvKNNruQgcMEFT_U,3827
23
23
  polynode/trading/privy.py,sha256=iiUQZsBqjj29C7oUiFFktoT2muZY06bcMzABKstFKqE,4523
24
24
  polynode/trading/relayer.py,sha256=adTo-aclWnaqrFtfWEOQ2Ye859CZMc9q_fsd3DePDGA,10542
25
25
  polynode/trading/signer.py,sha256=piifRquwnwFwrKLL24jsRQYMh_cdkz8wNz99Vx8_KTY,3413
26
26
  polynode/trading/sqlite_backend.py,sha256=zfo4WevuunWV-7i6Z3gQi_e7Tonw-7m1TsEd88O5I4w,9486
27
- polynode/trading/trader.py,sha256=MUc8DFyFpSoogVJHpSEe2AhVxqxHYZ6FiSw-mQ2I1hI,36882
27
+ polynode/trading/trader.py,sha256=EEwF_U2DXVPRnENY5azk8FgzSx_zXns27apQQj9isus,37188
28
28
  polynode/trading/types.py,sha256=mo5zJRlfQLWuXwrkS9AaGqKWrt7FMf6bPGRE-0T62J0,5698
29
29
  polynode/types/__init__.py,sha256=0v_CByqG-zT1xucbT2So_77YDmsPj9u956at94nyrLM,280
30
30
  polynode/types/enums.py,sha256=2cx9l10A-LUeDNBJZn7l0XBTka2YfxRQcXW0s7s3_4k,1082
@@ -33,6 +33,6 @@ polynode/types/orderbook.py,sha256=MwD6P3poo3F70bONUZ5i3ajk1sgw39hHTOw6D0fP5_8,1
33
33
  polynode/types/rest.py,sha256=ftJKQZH0wBrR_l199rDMqnVS8cyt8YnCvo9cKIsTcCw,7012
34
34
  polynode/types/short_form.py,sha256=xfGklDi587u6K2aXaRWiwbxqkipW8BiEPeNIDT45v2Y,799
35
35
  polynode/types/ws.py,sha256=EzE9Vzrb6cILemfssmnqM8fbHt4USn3latpUHKatwk4,994
36
- polynode-0.9.1.dist-info/METADATA,sha256=t6KrXQDkV0FSLqZ522PZK__qczPvfuUvn2CKwgOHxx4,3777
37
- polynode-0.9.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
38
- polynode-0.9.1.dist-info/RECORD,,
36
+ polynode-0.9.2.dist-info/METADATA,sha256=-FqTxtI_g04JQ7AxM9kzp9RxfnF6dGNADA3CmfXORK8,3777
37
+ polynode-0.9.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
38
+ polynode-0.9.2.dist-info/RECORD,,