polymarket-apis 0.3.7__py3-none-any.whl → 0.3.8__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.
@@ -10,7 +10,7 @@ This package provides a comprehensive interface to Polymarket's APIs including:
10
10
  - GraphQL API for flexible data queries
11
11
  """
12
12
 
13
- __version__ = "0.3.7"
13
+ __version__ = "0.3.8"
14
14
  __author__ = "Razvan Gheorghe"
15
15
  __email__ = "razvan@gheorghe.me"
16
16
 
@@ -14,10 +14,13 @@ from web3.types import ChecksumAddress, TxParams, Wei
14
14
  from ..types.common import EthAddress, Keccak256
15
15
  from ..utilities.config import get_contract_config
16
16
  from ..utilities.constants import ADDRESS_ZERO, HASH_ZERO, POLYGON
17
+ from ..utilities.exceptions import SafeAlreadyDeployedError
17
18
  from ..utilities.web3.abis.custom_contract_errors import CUSTOM_ERROR_DICT
18
19
  from ..utilities.web3.helpers import (
20
+ create_safe_create_signature,
19
21
  get_index_set,
20
22
  sign_safe_transaction,
23
+ split_signature,
21
24
  )
22
25
 
23
26
 
@@ -123,6 +126,20 @@ class PolymarketWeb3Client:
123
126
  args=[address, True],
124
127
  )
125
128
 
129
+ def _encode_transfer_usdc(self, address: ChecksumAddress, amount: int) -> str:
130
+ return self.usdc.encode_abi(
131
+ abi_element_identifier="transfer",
132
+ args=[address, amount],
133
+ )
134
+
135
+ def _encode_transfer_token(
136
+ self, token_id: str, address: ChecksumAddress, amount: int
137
+ ) -> str:
138
+ return self.conditional_tokens.encode_abi(
139
+ abi_element_identifier="safeTransferFrom",
140
+ args=[self.address, address, int(token_id), amount, HASH_ZERO],
141
+ )
142
+
126
143
  def _encode_split(self, condition_id: Keccak256, amount: int) -> str:
127
144
  return self.conditional_tokens.encode_abi(
128
145
  abi_element_identifier="splitPosition",
@@ -364,6 +381,48 @@ class PolymarketWeb3Client:
364
381
  + self.neg_risk_adapter.functions.getConditionId(question_id).call().hex()
365
382
  )
366
383
 
384
+ def deploy_safe(self) -> str:
385
+ """Deploy a Safe wallet using the SafeProxyFactory contract."""
386
+ safe_address = self.get_safe_proxy_address()
387
+ if self.w3.eth.get_code(self.w3.to_checksum_address(safe_address)) != b"":
388
+ msg = f"Safe already deployed at {safe_address}"
389
+ raise SafeAlreadyDeployedError(msg)
390
+
391
+ # Create the EIP-712 signature for Safe creation
392
+ sig = create_safe_create_signature(account=self.account, chain_id=POLYGON)
393
+
394
+ # Split the signature into r, s, v components
395
+ split_sig = split_signature(sig)
396
+
397
+ # Build the transaction
398
+ transaction = self._build_transaction()
399
+
400
+ # Execute the createProxy function
401
+ txn_data = self.safe_proxy_factory.functions.createProxy(
402
+ ADDRESS_ZERO, # paymentToken
403
+ 0, # payment
404
+ ADDRESS_ZERO, # paymentReceiver
405
+ (
406
+ split_sig["v"],
407
+ split_sig["r"],
408
+ split_sig["s"],
409
+ ), # createSig tuple (uint8, bytes32, bytes32)
410
+ ).build_transaction(transaction=transaction)
411
+
412
+ # Sign and send transaction
413
+ signed_txn = self.account.sign_transaction(txn_data)
414
+ tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
415
+ tx_hash_hex = tx_hash.hex()
416
+
417
+ print(f"txn hash: 0x{tx_hash_hex}")
418
+
419
+ # Wait for transaction to be mined
420
+ self.w3.eth.wait_for_transaction_receipt(tx_hash)
421
+
422
+ print("Done!")
423
+
424
+ return f"0x{tx_hash_hex}"
425
+
367
426
  def set_all_approvals(self) -> None:
368
427
  """Sets both collateral and conditional tokens approvals."""
369
428
  print("Approving ConditionalTokens as spender on USDC")
@@ -396,6 +455,143 @@ class PolymarketWeb3Client:
396
455
  )
397
456
  print("All approvals set!")
398
457
 
458
+ def transfer_usdc(self, recipient: EthAddress, amount: float) -> str:
459
+ """Transfers usdc.e from the account to the proxy address."""
460
+ balance = self.get_usdc_balance(address=self.address)
461
+ if balance < amount:
462
+ msg = f"Insufficient USDC.e balance: {balance} < {amount}"
463
+ raise ValueError(msg)
464
+ amount = int(balance * 1e6)
465
+ transaction = self._build_transaction()
466
+ data = self._encode_transfer_usdc(
467
+ self.w3.to_checksum_address(recipient), amount
468
+ )
469
+ txn_data: TxParams | None = None
470
+ match self.signature_type:
471
+ case 0:
472
+ txn_data = self.usdc.functions.transfer(
473
+ recipient,
474
+ amount,
475
+ ).build_transaction(transaction=transaction)
476
+ case 1:
477
+ proxy_txn = {
478
+ "typeCode": 1,
479
+ "to": self.usdc_address,
480
+ "value": 0,
481
+ "data": data,
482
+ }
483
+ txn_data = self.proxy_factory.functions.proxy(
484
+ [proxy_txn]
485
+ ).build_transaction(transaction=transaction)
486
+ case 2:
487
+ safe_nonce = self.safe.functions.nonce().call()
488
+ safe_txn = {
489
+ "to": self.usdc_address,
490
+ "data": data,
491
+ "operation": 0, # 1 for delegatecall, 0 for call
492
+ "value": 0,
493
+ }
494
+ packed_sig = sign_safe_transaction(
495
+ self.account, self.safe, safe_txn, safe_nonce
496
+ )
497
+ txn_data = self.safe.functions.execTransaction(
498
+ safe_txn["to"],
499
+ safe_txn["value"],
500
+ safe_txn["data"],
501
+ safe_txn.get("operation", 0),
502
+ 0, # safeTxGas
503
+ 0, # baseGas
504
+ 0, # gasPrice
505
+ ADDRESS_ZERO, # gasToken
506
+ ADDRESS_ZERO, # refundReceiver
507
+ packed_sig,
508
+ ).build_transaction(transaction=transaction)
509
+
510
+ # Sign and send transaction
511
+ signed_txn = self.account.sign_transaction(txn_data)
512
+ tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
513
+ tx_hash_hex = tx_hash.hex()
514
+
515
+ print(f"Txn hash: 0x{tx_hash_hex}")
516
+
517
+ # Wait for transaction to be mined
518
+ self.w3.eth.wait_for_transaction_receipt(tx_hash)
519
+
520
+ print("Done!")
521
+
522
+ return f"0x{tx_hash_hex}"
523
+
524
+ def transfer_token(
525
+ self, token_id: str, recipient: EthAddress, amount: float
526
+ ) -> str:
527
+ """Transfers conditional tokens from the account to the recipient address."""
528
+ balance = self.get_token_balance(token_id=token_id, address=self.address)
529
+ if balance < amount:
530
+ msg = f"Insufficient token balance: {balance} < {amount}"
531
+ raise ValueError(msg)
532
+ amount = int(balance * 1e6)
533
+ transaction = self._build_transaction()
534
+ data = self._encode_transfer_token(
535
+ token_id, self.w3.to_checksum_address(recipient), amount
536
+ )
537
+ txn_data: TxParams | None = None
538
+ match self.signature_type:
539
+ case 0:
540
+ txn_data = self.conditional_tokens.functions.safeTransferFrom(
541
+ self.address,
542
+ recipient,
543
+ int(token_id),
544
+ amount,
545
+ b"",
546
+ ).build_transaction(transaction=transaction)
547
+ case 1:
548
+ proxy_txn = {
549
+ "typeCode": 1,
550
+ "to": self.conditional_tokens_address,
551
+ "value": 0,
552
+ "data": data,
553
+ }
554
+ txn_data = self.proxy_factory.functions.proxy(
555
+ [proxy_txn]
556
+ ).build_transaction(transaction=transaction)
557
+ case 2:
558
+ safe_nonce = self.safe.functions.nonce().call()
559
+ safe_txn = {
560
+ "to": self.conditional_tokens_address,
561
+ "data": data,
562
+ "operation": 0, # 1 for delegatecall, 0 for call
563
+ "value": 0,
564
+ }
565
+ packed_sig = sign_safe_transaction(
566
+ self.account, self.safe, safe_txn, safe_nonce
567
+ )
568
+ txn_data = self.safe.functions.execTransaction(
569
+ safe_txn["to"],
570
+ safe_txn["value"],
571
+ safe_txn["data"],
572
+ safe_txn.get("operation", 0),
573
+ 0, # safeTxGas
574
+ 0, # baseGas
575
+ 0, # gasPrice
576
+ ADDRESS_ZERO, # gasToken
577
+ ADDRESS_ZERO, # refundReceiver
578
+ packed_sig,
579
+ ).build_transaction(transaction=transaction)
580
+
581
+ # Sign and send transaction
582
+ signed_txn = self.account.sign_transaction(txn_data)
583
+ tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
584
+ tx_hash_hex = tx_hash.hex()
585
+
586
+ print(f"Txn hash: 0x{tx_hash_hex}")
587
+
588
+ # Wait for transaction to be mined
589
+ self.w3.eth.wait_for_transaction_receipt(tx_hash)
590
+
591
+ print("Done!")
592
+
593
+ return f"0x{tx_hash_hex}"
594
+
399
595
  def split_position(
400
596
  self, condition_id: Keccak256, amount: float, neg_risk: bool = True
401
597
  ) -> str:
@@ -20,3 +20,7 @@ class MissingOrderbookError(Exception):
20
20
 
21
21
  class AuthenticationRequiredError(ValueError):
22
22
  """Raised when authentication credentials are required but not provided."""
23
+
24
+
25
+ class SafeAlreadyDeployedError(Exception):
26
+ """Raised when attempting to deploy a Safe that has already been deployed."""
@@ -1,6 +1,6 @@
1
1
  import re
2
2
  from collections.abc import Iterable
3
- from typing import Any
3
+ from typing import Any, Literal
4
4
 
5
5
  from eth_account import Account
6
6
  from eth_account.messages import encode_defunct
@@ -114,6 +114,88 @@ def abi_encode_packed(*params: dict) -> bytes:
114
114
  return b"".join(parts)
115
115
 
116
116
 
117
+ def split_signature(signature_hex: str) -> dict[str, Any]:
118
+ """
119
+ Split a signature into r, s, v components compatible with Safe factory.
120
+
121
+ Args:
122
+ signature_hex: Signature as hex string
123
+
124
+ Returns:
125
+ dict: Dictionary with r, s, v components properly formatted for the contract
126
+
127
+ """
128
+ # Remove 0x prefix if present
129
+ signature_hex = signature_hex.removeprefix("0x")
130
+
131
+ # Convert to bytes
132
+ signature_bytes = bytes.fromhex(signature_hex)
133
+
134
+ if len(signature_bytes) != 65:
135
+ msg = "Invalid signature length"
136
+ raise ValueError(msg)
137
+
138
+ # Extract r, s, v
139
+ r = signature_bytes[:32]
140
+ s = signature_bytes[32:64]
141
+ v = signature_bytes[64]
142
+
143
+ if v < 27:
144
+ if v in {0, 1}:
145
+ v += 27
146
+ else:
147
+ msg = "Invalid signature v value"
148
+ raise ValueError(msg)
149
+
150
+ # Return properly formatted components for the contract:
151
+ # - r and s as bytes32 (keep as bytes, web3.py will handle conversion)
152
+ # - v as uint8 (integer)
153
+ return {
154
+ "r": r, # bytes32
155
+ "s": s, # bytes32
156
+ "v": v, # uint8
157
+ }
158
+
159
+
160
+ def create_safe_create_signature(
161
+ account: Account, chain_id: Literal[137, 80002]
162
+ ) -> str:
163
+ """
164
+ Create EIP-712 signature for Safe creation.
165
+
166
+ Returns:
167
+ str: The signature as hex string
168
+
169
+ """
170
+ # EIP-712 domain
171
+ domain = {
172
+ "name": "Polymarket Contract Proxy Factory",
173
+ "chainId": chain_id,
174
+ "verifyingContract": "0xaacFeEa03eb1561C4e67d661e40682Bd20E3541b",
175
+ }
176
+
177
+ # EIP-712 types
178
+ types = {
179
+ "CreateProxy": [
180
+ {"name": "paymentToken", "type": "address"},
181
+ {"name": "payment", "type": "uint256"},
182
+ {"name": "paymentReceiver", "type": "address"},
183
+ ]
184
+ }
185
+
186
+ # Values to sign
187
+ values = {
188
+ "paymentToken": ADDRESS_ZERO,
189
+ "payment": 0,
190
+ "paymentReceiver": ADDRESS_ZERO,
191
+ }
192
+
193
+ # Create the signature using eth_account
194
+ signature = account.sign_typed_data(domain, types, values)
195
+
196
+ return signature.signature.hex()
197
+
198
+
117
199
  def sign_safe_transaction(
118
200
  account: Account, safe: Contract, safe_txn: dict, nonce: int
119
201
  ) -> bytes:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polymarket-apis
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
4
  Summary: Unified Polymarket APIs with Pydantic data validation - Clob, Gamma, Data, Web3, Websockets, GraphQL clients.
5
5
  Project-URL: repository, https://github.com/qualiaenjoyer/polymarket-apis
6
6
  Author-email: Razvan Gheorghe <razvan@gheorghe.me>
@@ -158,10 +158,13 @@ flowchart LR
158
158
  - #### Supporting EOA(signature_type=0), Email/Magic wallets (signature_type=1) and Safe/Gnosis wallets (signature_type=2)
159
159
  - #### Approvals
160
160
  - set approvals for all needed usdc and conditional token spenders (needed for full trading functionality)
161
- - Safe/Gnosis wallets need to be deployed beforehand, either via "Enable Trading" pop-up on the web UI or by calling createProxy on the SafeWalletFactory contract (wip)
161
+ - Safe/Gnosis wallet holders need to run deploy_safe before setting approvals
162
162
  - #### Balance
163
163
  - get usdc balance by user address
164
164
  - get token balance by `token_id` and user address
165
+ - #### Transfers
166
+ - transfer usdc to another address - needs recipient address, amount
167
+ - transfer token to another address - needs `token_id`, recipient address, amount
165
168
  - #### Token/USDC conversions
166
169
  - split USDC into complementary tokens - needs `condition_id`, amount, neg_risk bool
167
170
  - merge complementary tokens into USDC - needs `condition_id`, amount, neg_risk bool
@@ -1,10 +1,10 @@
1
- polymarket_apis/__init__.py,sha256=OzhWc-PqnHoGyrcRwclQG2mLGSu0cz7QLuhx-jenzgk,1126
1
+ polymarket_apis/__init__.py,sha256=6-aPCzc4EkyKJoDVu-LrkhOPUBHyog8_pKBYKbrdklI,1126
2
2
  polymarket_apis/clients/__init__.py,sha256=ruMvFEA4HNkbWEYqbnrCuYXR4PUwkV1XWG0w63we-LA,759
3
3
  polymarket_apis/clients/clob_client.py,sha256=Eo5zXYtI_Ct0EByzjJnH6h7FLHOqheXDujBA57CtINw,32485
4
4
  polymarket_apis/clients/data_client.py,sha256=0--2W6_DZtC7cRud8OYS7bH2heWzn2NwvcUVK3PyEwU,13242
5
5
  polymarket_apis/clients/gamma_client.py,sha256=iDfuaClhRK2Y5v8ZA03Qbne3CnIM4JJolHo-qUrQV78,27633
6
6
  polymarket_apis/clients/graphql_client.py,sha256=KgjxbXNWEXp82ZEz464in5mCn1PydnZqWq-g11xu9YU,1839
7
- polymarket_apis/clients/web3_client.py,sha256=Xsk1APsnMDRu9daLKZnf6LW41LdocuRV9rqjKnZjYmw,26400
7
+ polymarket_apis/clients/web3_client.py,sha256=xyanN9Imfv-O_dPAirtkyKaMHll6HhWlEIzqE19kOzA,33930
8
8
  polymarket_apis/clients/websockets_client.py,sha256=gaGcsjqp1ZRyxrL6EWvZ9XaTv7koTPDzlcShj0u0B2A,7737
9
9
  polymarket_apis/types/__init__.py,sha256=cyrAPNX0ty0wkMwZqB0lNA7n3JoOJCSeTpclmamMh9k,3258
10
10
  polymarket_apis/types/clob_types.py,sha256=WZs370ni2B2UsLW-yIPGA2C-z8dfbywX26vADuu2YFc,11725
@@ -16,7 +16,7 @@ polymarket_apis/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
16
16
  polymarket_apis/utilities/config.py,sha256=kQymQRy9fVg5jt8CcQJxsSgIZFbfjPx2q_gNnZI5b24,2449
17
17
  polymarket_apis/utilities/constants.py,sha256=TzFVtN8zNgvEa4yjLWjpvPYsAi1LLV5oPEnHlbwxSDQ,637
18
18
  polymarket_apis/utilities/endpoints.py,sha256=bxZyrJBPbVauWc-eR0RMh6KDqU-SmO_3LfQwVMNJ6vE,1235
19
- polymarket_apis/utilities/exceptions.py,sha256=nLVkwGNkX8mBhOi3L3lLEJ5UCPd5OBjl2f7kcct3K3A,368
19
+ polymarket_apis/utilities/exceptions.py,sha256=lpZPF2-rw64qrnhCQWIspAv-rgm7aKvP8UUoVHcrPUA,495
20
20
  polymarket_apis/utilities/headers.py,sha256=Cc5WEnIBLYAgfwvmCXRBwA2zUYME8fDy4PbwlwlB6Oo,1510
21
21
  polymarket_apis/utilities/order_builder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  polymarket_apis/utilities/order_builder/builder.py,sha256=dyxKhMeNjGyHXEIFBBZhWwI8TaQQcE_ErQ4sF6BcMX0,8476
@@ -27,7 +27,7 @@ polymarket_apis/utilities/signing/hmac.py,sha256=1VPfO2yT8nyStk6U4AQeyTzQTt5-69P
27
27
  polymarket_apis/utilities/signing/model.py,sha256=kVduuJGth7WSCUDCVVydCgPd4yEVI85gEmMxohXsvp0,191
28
28
  polymarket_apis/utilities/signing/signer.py,sha256=cxjcYRn1a0ApVcir1AvamsFcLZ0rYNlEEX4sPUz_Nrw,776
29
29
  polymarket_apis/utilities/web3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- polymarket_apis/utilities/web3/helpers.py,sha256=qzQA-n9rHbuFk57olvBa8P_T6IzrUsaQ2Vv0887EX4o,4694
30
+ polymarket_apis/utilities/web3/helpers.py,sha256=aczuvPXqnFGcKKfT5GhpnJL2U6UBEcpjbzhQUfw0MDk,6765
31
31
  polymarket_apis/utilities/web3/abis/CTFExchange.json,sha256=zt8fZnUaOrD8Vh5njM0EEUpeITWhuu0SZrIZigWxgV8,38499
32
32
  polymarket_apis/utilities/web3/abis/ConditionalTokens.json,sha256=3TUcX7He74VMkoL1kxbDbtULZ70VY_EBe01pfByprsk,12584
33
33
  polymarket_apis/utilities/web3/abis/NegRiskAdapter.json,sha256=HABIoRF1s1NgctpRTdaaNDqzODzgdZLE-s2E6ef4nAY,18867
@@ -38,6 +38,6 @@ polymarket_apis/utilities/web3/abis/SafeProxyFactory.json,sha256=bdr2WdYCRClXLTT
38
38
  polymarket_apis/utilities/web3/abis/UChildERC20Proxy.json,sha256=ZyQC38U0uxInlmnW2VXDVD3TJfTIRmSNMkTxQsaG7oA,27396
39
39
  polymarket_apis/utilities/web3/abis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  polymarket_apis/utilities/web3/abis/custom_contract_errors.py,sha256=GjCVn2b6iRheT7s-kc8Po9uwH9LfaHA1yRpJyjXRcxs,1172
41
- polymarket_apis-0.3.7.dist-info/METADATA,sha256=Z3jOxzSY7thon83qiZQbeZ_8sCebx2BKCVRKR-acFjA,12802
42
- polymarket_apis-0.3.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
43
- polymarket_apis-0.3.7.dist-info/RECORD,,
41
+ polymarket_apis-0.3.8.dist-info/METADATA,sha256=jgUzQQ3iAHns_9zGXKZ-Xt2Pe90v5rExs8qiInrEikg,12889
42
+ polymarket_apis-0.3.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
43
+ polymarket_apis-0.3.8.dist-info/RECORD,,