polymarket-apis 0.3.0__py3-none-any.whl → 0.3.9__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.

Potentially problematic release.


This version of polymarket-apis might be problematic. Click here for more details.

Files changed (32) hide show
  1. polymarket_apis/__init__.py +42 -0
  2. polymarket_apis/clients/__init__.py +23 -0
  3. polymarket_apis/clients/clob_client.py +224 -117
  4. polymarket_apis/clients/data_client.py +220 -67
  5. polymarket_apis/clients/gamma_client.py +589 -101
  6. polymarket_apis/clients/graphql_client.py +28 -11
  7. polymarket_apis/clients/web3_client.py +538 -131
  8. polymarket_apis/clients/websockets_client.py +24 -7
  9. polymarket_apis/types/__init__.py +167 -0
  10. polymarket_apis/types/clob_types.py +35 -14
  11. polymarket_apis/types/common.py +105 -35
  12. polymarket_apis/types/data_types.py +48 -3
  13. polymarket_apis/types/gamma_types.py +529 -257
  14. polymarket_apis/types/web3_types.py +45 -0
  15. polymarket_apis/types/websockets_types.py +92 -41
  16. polymarket_apis/utilities/config.py +1 -0
  17. polymarket_apis/utilities/constants.py +5 -4
  18. polymarket_apis/utilities/exceptions.py +9 -0
  19. polymarket_apis/utilities/order_builder/builder.py +38 -22
  20. polymarket_apis/utilities/order_builder/helpers.py +0 -1
  21. polymarket_apis/utilities/signing/hmac.py +5 -1
  22. polymarket_apis/utilities/signing/signer.py +2 -2
  23. polymarket_apis/utilities/web3/abis/Safe.json +1138 -0
  24. polymarket_apis/utilities/web3/abis/SafeProxyFactory.json +224 -0
  25. polymarket_apis/utilities/web3/abis/custom_contract_errors.py +1 -1
  26. polymarket_apis/utilities/web3/helpers.py +235 -0
  27. {polymarket_apis-0.3.0.dist-info → polymarket_apis-0.3.9.dist-info}/METADATA +48 -8
  28. polymarket_apis-0.3.9.dist-info/RECORD +44 -0
  29. polymarket_apis/utilities/schemas/activity-subgraph.graphql +0 -86
  30. polymarket_apis/utilities/schemas/open-interest.graphql +0 -30
  31. polymarket_apis-0.3.0.dist-info/RECORD +0 -43
  32. {polymarket_apis-0.3.0.dist-info → polymarket_apis-0.3.9.dist-info}/WHEEL +0 -0
@@ -1,58 +1,151 @@
1
1
  from json import load
2
2
  from pathlib import Path
3
- from typing import Literal, Optional
3
+ from typing import Literal
4
4
 
5
5
  from web3 import Web3
6
+ from web3.constants import MAX_INT
6
7
  from web3.exceptions import ContractCustomError
7
- from web3.middleware import ExtraDataToPOAMiddleware, SignAndSendRawMiddlewareBuilder
8
+ from web3.middleware import (
9
+ ExtraDataToPOAMiddleware,
10
+ SignAndSendRawMiddlewareBuilder,
11
+ )
12
+ from web3.types import ChecksumAddress, TxParams, Wei
8
13
 
9
14
  from ..types.common import EthAddress, Keccak256
15
+ from ..types.web3_types import TransactionReceipt
10
16
  from ..utilities.config import get_contract_config
11
- from ..utilities.constants import HASH_ZERO, POLYGON
17
+ from ..utilities.constants import ADDRESS_ZERO, HASH_ZERO, POLYGON
18
+ from ..utilities.exceptions import SafeAlreadyDeployedError
12
19
  from ..utilities.web3.abis.custom_contract_errors import CUSTOM_ERROR_DICT
13
- from ..utilities.web3.helpers import get_index_set
20
+ from ..utilities.web3.helpers import (
21
+ create_safe_create_signature,
22
+ get_index_set,
23
+ sign_safe_transaction,
24
+ split_signature,
25
+ )
14
26
 
15
27
 
16
28
  def _load_abi(contract_name: str) -> list:
17
- abi_path = Path(__file__).parent.parent/"utilities"/"web3"/"abis"/f"{contract_name}.json"
29
+ abi_path = (
30
+ Path(__file__).parent.parent
31
+ / "utilities"
32
+ / "web3"
33
+ / "abis"
34
+ / f"{contract_name}.json"
35
+ )
18
36
  with Path.open(abi_path) as f:
19
37
  return load(f)
20
38
 
21
- class PolymarketWeb3Client:
22
- def __init__(self, private_key: str , chain_id: Literal[137, 80002] = POLYGON):
23
39
 
40
+ class PolymarketWeb3Client:
41
+ def __init__(
42
+ self,
43
+ private_key: str,
44
+ signature_type: Literal[0, 1, 2] = 1,
45
+ chain_id: Literal[137, 80002] = POLYGON,
46
+ ):
24
47
  self.w3 = Web3(Web3.HTTPProvider("https://polygon-rpc.com"))
25
- self.w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
26
- self.w3.middleware_onion.inject(SignAndSendRawMiddlewareBuilder.build(private_key), layer=0)
48
+ self.w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0) # type: ignore[arg-type]
49
+ self.w3.middleware_onion.inject(
50
+ SignAndSendRawMiddlewareBuilder.build(private_key), # type: ignore[arg-type]
51
+ layer=0,
52
+ )
27
53
 
28
54
  self.account = self.w3.eth.account.from_key(private_key)
55
+ self.signature_type = signature_type
29
56
 
30
57
  self.config = get_contract_config(chain_id, neg_risk=False)
31
58
  self.neg_risk_config = get_contract_config(chain_id, neg_risk=True)
32
59
 
33
60
  self.usdc_address = Web3.to_checksum_address(self.config.collateral)
34
61
  self.usdc_abi = _load_abi("UChildERC20Proxy")
35
- self.usdc = self.contract(self.usdc_address, self.usdc_abi)
62
+ self.usdc = self._contract(self.usdc_address, self.usdc_abi)
36
63
 
37
- self.conditional_tokens_address = Web3.to_checksum_address(self.config.conditional_tokens)
64
+ self.conditional_tokens_address = Web3.to_checksum_address(
65
+ self.config.conditional_tokens
66
+ )
38
67
  self.conditional_tokens_abi = _load_abi("ConditionalTokens")
39
- self.conditional_tokens = self.contract(self.conditional_tokens_address, self.conditional_tokens_abi)
68
+ self.conditional_tokens = self._contract(
69
+ self.conditional_tokens_address, self.conditional_tokens_abi
70
+ )
40
71
 
41
72
  self.exchange_address = Web3.to_checksum_address(self.config.exchange)
42
73
  self.exchange_abi = _load_abi("CTFExchange")
43
- self.exchange = self.contract(self.exchange_address, self.exchange_abi)
74
+ self.exchange = self._contract(self.exchange_address, self.exchange_abi)
44
75
 
45
- self.neg_risk_exchange_address = Web3.to_checksum_address(self.neg_risk_config.exchange)
76
+ self.neg_risk_exchange_address = Web3.to_checksum_address(
77
+ self.neg_risk_config.exchange
78
+ )
46
79
  self.neg_risk_exchange_abi = _load_abi("NegRiskCtfExchange")
47
- self.neg_risk_exchange = self.contract(self.neg_risk_exchange_address, self.neg_risk_exchange_abi)
80
+ self.neg_risk_exchange = self._contract(
81
+ self.neg_risk_exchange_address, self.neg_risk_exchange_abi
82
+ )
48
83
 
49
- self.neg_risk_adapter_address = "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296"
84
+ self.neg_risk_adapter_address = Web3.to_checksum_address(
85
+ "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296"
86
+ )
50
87
  self.neg_risk_adapter_abi = _load_abi("NegRiskAdapter")
51
- self.neg_risk_adapter = self.contract(self.neg_risk_adapter_address, self.neg_risk_adapter_abi)
88
+ self.neg_risk_adapter = self._contract(
89
+ self.neg_risk_adapter_address, self.neg_risk_adapter_abi
90
+ )
52
91
 
53
- self.proxy_factory_address = "0xaB45c5A4B0c941a2F231C04C3f49182e1A254052"
92
+ self.proxy_factory_address = Web3.to_checksum_address(
93
+ "0xaB45c5A4B0c941a2F231C04C3f49182e1A254052"
94
+ )
54
95
  self.proxy_factory_abi = _load_abi("ProxyWalletFactory")
55
- self.proxy_factory = self.contract(self.proxy_factory_address, self.proxy_factory_abi)
96
+ self.proxy_factory = self._contract(
97
+ self.proxy_factory_address, self.proxy_factory_abi
98
+ )
99
+
100
+ self.safe_proxy_factory_address = Web3.to_checksum_address(
101
+ "0xaacFeEa03eb1561C4e67d661e40682Bd20E3541b"
102
+ )
103
+ self.safe_proxy_factory_abi = _load_abi("SafeProxyFactory")
104
+ self.safe_proxy_factory = self._contract(
105
+ self.safe_proxy_factory_address, self.safe_proxy_factory_abi
106
+ )
107
+
108
+ match self.signature_type:
109
+ case 0:
110
+ self.address = self.account.address
111
+ case 1:
112
+ self.address = self.get_poly_proxy_address()
113
+ case 2:
114
+ self.address = self.get_safe_proxy_address()
115
+ self.safe_abi = _load_abi("Safe")
116
+ self.safe = self._contract(self.address, self.safe_abi)
117
+
118
+ def _contract(self, address, abi):
119
+ return self.w3.eth.contract(
120
+ address=Web3.to_checksum_address(address),
121
+ abi=abi,
122
+ )
123
+
124
+ def _encode_usdc_approve(self, address: ChecksumAddress) -> str:
125
+ return self.usdc.encode_abi(
126
+ abi_element_identifier="approve",
127
+ args=[address, int(MAX_INT, base=16)],
128
+ )
129
+
130
+ def _encode_condition_tokens_approve(self, address: ChecksumAddress) -> str:
131
+ return self.conditional_tokens.encode_abi(
132
+ abi_element_identifier="setApprovalForAll",
133
+ args=[address, True],
134
+ )
135
+
136
+ def _encode_transfer_usdc(self, address: ChecksumAddress, amount: int) -> str:
137
+ return self.usdc.encode_abi(
138
+ abi_element_identifier="transfer",
139
+ args=[address, amount],
140
+ )
141
+
142
+ def _encode_transfer_token(
143
+ self, token_id: str, address: ChecksumAddress, amount: int
144
+ ) -> str:
145
+ return self.conditional_tokens.encode_abi(
146
+ abi_element_identifier="safeTransferFrom",
147
+ args=[self.address, address, int(token_id), amount, HASH_ZERO],
148
+ )
56
149
 
57
150
  def _encode_split(self, condition_id: Keccak256, amount: int) -> str:
58
151
  return self.conditional_tokens.encode_abi(
@@ -72,23 +165,117 @@ class PolymarketWeb3Client:
72
165
  args=[self.usdc_address, HASH_ZERO, condition_id, [1, 2]],
73
166
  )
74
167
 
75
- def _encode_redeem_neg_risk(self, condition_id: Keccak256, amounts: list[int]) -> str:
168
+ def _encode_redeem_neg_risk(
169
+ self, condition_id: Keccak256, amounts: list[int]
170
+ ) -> str:
76
171
  return self.neg_risk_adapter.encode_abi(
77
172
  abi_element_identifier="redeemPositions",
78
173
  args=[condition_id, amounts],
79
174
  )
80
- def _encode_convert(self, neg_risk_market_id: Keccak256, index_set: int, amount: int) -> str:
175
+
176
+ def _encode_convert(
177
+ self, neg_risk_market_id: Keccak256, index_set: int, amount: int
178
+ ) -> str:
81
179
  return self.neg_risk_adapter.encode_abi(
82
180
  abi_element_identifier="convertPositions",
83
181
  args=[neg_risk_market_id, index_set, amount],
84
182
  )
85
183
 
86
- def contract(self, address, abi):
87
- return self.w3.eth.contract(
88
- address=Web3.to_checksum_address(address),
89
- abi=abi,
184
+ def _build_base_transaction(self) -> TxParams:
185
+ """Build base transaction parameters."""
186
+ nonce = self.w3.eth.get_transaction_count(self.account.address)
187
+
188
+ current_gas_price: int = self.w3.eth.gas_price
189
+ adjusted_gas_price = Wei(int(current_gas_price * 1.05))
190
+
191
+ base_transaction: TxParams = {
192
+ "nonce": nonce,
193
+ "gasPrice": adjusted_gas_price,
194
+ "gas": 1000000,
195
+ "from": self.account.address,
196
+ }
197
+
198
+ return base_transaction
199
+
200
+ def _build_proxy_transaction(self, to, data, base_transaction) -> TxParams:
201
+ proxy_txn = {
202
+ "typeCode": 1,
203
+ "to": to,
204
+ "value": 0,
205
+ "data": data,
206
+ }
207
+ txn_data = self.proxy_factory.functions.proxy([proxy_txn]).build_transaction(
208
+ transaction=base_transaction
90
209
  )
91
210
 
211
+ return txn_data
212
+
213
+ def _build_safe_transaction(self, to, data, base_transaction) -> TxParams:
214
+ safe_nonce = self.safe.functions.nonce().call()
215
+ safe_txn = {
216
+ "to": to,
217
+ "data": data,
218
+ "operation": 0, # 1 for delegatecall, 0 for call
219
+ "value": 0,
220
+ }
221
+ packed_sig = sign_safe_transaction(
222
+ self.account,
223
+ self.safe,
224
+ safe_txn,
225
+ safe_nonce,
226
+ )
227
+ txn_data = self.safe.functions.execTransaction(
228
+ safe_txn["to"],
229
+ safe_txn["value"],
230
+ safe_txn["data"],
231
+ safe_txn.get("operation", 0),
232
+ 0, # safeTxGas
233
+ 0, # baseGas
234
+ 0, # gasPrice
235
+ ADDRESS_ZERO, # gasToken
236
+ ADDRESS_ZERO, # refundReceiver
237
+ packed_sig,
238
+ ).build_transaction(transaction=base_transaction)
239
+
240
+ return txn_data
241
+
242
+ def _execute_transaction(
243
+ self, txn_data: TxParams, operation_name: str = "Transaction"
244
+ ) -> TransactionReceipt:
245
+ """
246
+ Execute a transaction, wait for receipt, and print status.
247
+
248
+ Args:
249
+ txn_data: Built transaction data
250
+ operation_name: Name of operation for logging
251
+
252
+ """
253
+ signed_txn = self.account.sign_transaction(txn_data)
254
+ tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
255
+ tx_hash_hex = tx_hash.hex()
256
+
257
+ print(f"Txn hash: 0x{tx_hash_hex}")
258
+
259
+ # Wait for transaction to be mined and get receipt
260
+ receipt_dict = self.w3.eth.wait_for_transaction_receipt(tx_hash)
261
+ receipt = TransactionReceipt.model_validate(receipt_dict)
262
+
263
+ print(f"{operation_name} succeeded") if receipt.status == 1 else print(
264
+ f"{operation_name} failed"
265
+ )
266
+
267
+ return receipt
268
+
269
+ def get_poly_proxy_address(self, address: EthAddress | None = None) -> EthAddress:
270
+ """Get the polymarket proxy address for the current account."""
271
+ address = address if address else self.account.address
272
+ return self.exchange.functions.getPolyProxyWalletAddress(address).call()
273
+
274
+ def get_safe_proxy_address(self, address: EthAddress | None = None) -> EthAddress:
275
+ """Get the safe proxy address for the current account."""
276
+ address = address if address else self.account.address
277
+ return self.safe_proxy_factory.functions.computeProxyAddress(address).call()
278
+
92
279
  def get_usdc_balance(self, address: EthAddress | None = None) -> float:
93
280
  """
94
281
  Get the usdc balance of the given address.
@@ -98,31 +285,41 @@ class PolymarketWeb3Client:
98
285
  Explicitly passing the proxy address is faster due to only one contract function call.
99
286
  """
100
287
  if address is None:
101
- address = self.exchange.functions.getPolyProxyWalletAddress(self.account.address).call()
288
+ address = self.address
102
289
  balance_res = self.usdc.functions.balanceOf(address).call()
103
290
  return float(balance_res / 1e6)
104
291
 
105
- def get_token_balance(self, token_id: str, address: EthAddress | None = None) -> float:
292
+ def get_token_balance(
293
+ self, token_id: str, address: EthAddress | None = None
294
+ ) -> float:
106
295
  """Get the token balance of the given address."""
107
- if address is None:
108
- address = self.exchange.functions.getPolyProxyWalletAddress(self.account.address).call()
109
- balance_res = self.conditional_tokens.functions.balanceOf(address, int(token_id)).call()
296
+ if not address:
297
+ address = self.address
298
+ balance_res = self.conditional_tokens.functions.balanceOf(
299
+ address, int(token_id)
300
+ ).call()
110
301
  return float(balance_res / 1e6)
111
302
 
112
- def get_token_complement(self, token_id: str) -> Optional[str]:
303
+ def get_token_complement(self, token_id: str) -> str | None:
113
304
  """Get the complement of the given token."""
114
305
  try:
115
- return str(self.neg_risk_exchange.functions.getComplement(int(token_id)).call())
306
+ return str(
307
+ self.neg_risk_exchange.functions.getComplement(int(token_id)).call()
308
+ )
116
309
  except ContractCustomError as e:
117
310
  if e.args[0] in CUSTOM_ERROR_DICT:
118
311
  try:
119
- return str(self.exchange.functions.getComplement(int(token_id)).call())
312
+ return str(
313
+ self.exchange.functions.getComplement(int(token_id)).call()
314
+ )
120
315
  except ContractCustomError as e2:
121
316
  if e2.args[0] in CUSTOM_ERROR_DICT:
122
317
  msg = f"{CUSTOM_ERROR_DICT[e2.args[0]]}"
123
318
  raise ContractCustomError(
124
319
  msg,
125
320
  ) from e2
321
+ return None
322
+ return None
126
323
 
127
324
  def get_condition_id_neg_risk(self, question_id: Keccak256) -> Keccak256:
128
325
  """
@@ -132,71 +329,269 @@ class PolymarketWeb3Client:
132
329
  outcomeSlotCount is represented by the last two digits of question id). Returns a keccak256 hash of
133
330
  the oracle and question id.
134
331
  """
135
- return "0x" + self.neg_risk_adapter.functions.getConditionId(question_id).call().hex()
332
+ return (
333
+ "0x"
334
+ + self.neg_risk_adapter.functions.getConditionId(question_id).call().hex()
335
+ )
136
336
 
137
- def split_position(self, condition_id: Keccak256, amount: int, neg_risk: bool = True):
138
- """Splits usdc into two complementary positions of equal size."""
139
- nonce = self.w3.eth.get_transaction_count(self.account.address)
140
- amount = int(amount * 1e6)
337
+ def deploy_safe(self) -> TransactionReceipt:
338
+ """Deploy a Safe wallet using the SafeProxyFactory contract."""
339
+ safe_address = self.get_safe_proxy_address()
340
+ if self.w3.eth.get_code(self.w3.to_checksum_address(safe_address)) != b"":
341
+ msg = f"Safe already deployed at {safe_address}"
342
+ raise SafeAlreadyDeployedError(msg)
343
+
344
+ # Create the EIP-712 signature for Safe creation
345
+ sig = create_safe_create_signature(account=self.account, chain_id=POLYGON)
346
+
347
+ # Split the signature into r, s, v components
348
+ split_sig = split_signature(sig)
349
+
350
+ # Build the transaction
351
+ base_transaction = self._build_base_transaction()
352
+
353
+ # Execute the createProxy function
354
+ txn_data = self.safe_proxy_factory.functions.createProxy(
355
+ ADDRESS_ZERO, # paymentToken
356
+ 0, # payment
357
+ ADDRESS_ZERO, # paymentReceiver
358
+ (
359
+ split_sig["v"],
360
+ split_sig["r"],
361
+ split_sig["s"],
362
+ ), # createSig tuple (uint8, bytes32, bytes32)
363
+ ).build_transaction(transaction=base_transaction)
141
364
 
142
- proxy_txn = {
143
- "typeCode": 1,
144
- "to": self.neg_risk_adapter_address if neg_risk else self.conditional_tokens_address,
145
- "value": 0,
146
- "data": self._encode_split(condition_id, amount),
147
- }
365
+ # Sign and send transaction
366
+ return self._execute_transaction(
367
+ txn_data, operation_name="Gnosis Safe Deployment"
368
+ )
148
369
 
149
- # Send transaction through proxy factory
150
- txn_data = self.proxy_factory.functions.proxy([proxy_txn]).build_transaction({
151
- "nonce": nonce,
152
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
153
- "gas": 1000000,
154
- "from": self.account.address,
155
- })
370
+ def set_collateral_approval(self, spender: ChecksumAddress) -> TransactionReceipt:
371
+ to = self.usdc_address
372
+ data = self._encode_usdc_approve(address=spender)
373
+ base_transaction = self._build_base_transaction()
374
+ txn_data: TxParams = {}
375
+
376
+ match self.signature_type:
377
+ case 0:
378
+ txn_data = self.usdc.functions.approve(
379
+ spender, int(MAX_INT, base=16)
380
+ ).build_transaction(transaction=base_transaction)
381
+ case 1:
382
+ txn_data = self._build_proxy_transaction(to, data, base_transaction)
383
+ case 2:
384
+ txn_data = self._build_safe_transaction(to, data, base_transaction)
385
+
386
+ return self._execute_transaction(txn_data, operation_name="Collateral Approval")
387
+
388
+ def set_conditional_tokens_approval(
389
+ self, spender: ChecksumAddress
390
+ ) -> TransactionReceipt:
391
+ to = self.conditional_tokens_address
392
+ data = self._encode_condition_tokens_approve(address=spender)
393
+ base_transaction = self._build_base_transaction()
394
+ txn_data: TxParams = {}
395
+
396
+ match self.signature_type:
397
+ case 0:
398
+ txn_data = self.conditional_tokens.functions.setApprovalForAll(
399
+ spender, True
400
+ ).build_transaction(transaction=base_transaction)
401
+ case 1:
402
+ txn_data = self._build_proxy_transaction(to, data, base_transaction)
403
+ case 2:
404
+ txn_data = self._build_safe_transaction(to, data, base_transaction)
405
+
406
+ return self._execute_transaction(
407
+ txn_data, operation_name="Conditional Tokens Approval"
408
+ )
156
409
 
157
- # Sign and send transaction
158
- signed_txn = self.account.sign_transaction(txn_data)
159
- tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction).hex()
410
+ def set_all_approvals(self) -> list[TransactionReceipt]:
411
+ """Sets both collateral and conditional tokens approvals."""
412
+ receipts = []
413
+ print("Approving ConditionalTokens as spender on USDC")
414
+ receipts.append(
415
+ self.set_collateral_approval(
416
+ spender=self.conditional_tokens_address,
417
+ )
418
+ )
419
+ print("Approving CTFExchange as spender on USDC")
420
+ receipts.append(
421
+ self.set_collateral_approval(
422
+ spender=self.exchange_address,
423
+ )
424
+ )
425
+ print("Approving NegRiskCtfExchange as spender on USDC")
426
+ receipts.append(
427
+ self.set_collateral_approval(
428
+ spender=self.neg_risk_exchange_address,
429
+ )
430
+ )
431
+ print("Approving NegRiskAdapter as spender on USDC")
432
+ receipts.append(
433
+ self.set_collateral_approval(
434
+ spender=self.neg_risk_adapter_address,
435
+ )
436
+ )
437
+ print("Approving CTFExchange as spender on ConditionalTokens")
438
+ receipts.append(
439
+ self.set_conditional_tokens_approval(
440
+ spender=self.exchange_address,
441
+ )
442
+ )
443
+ print("Approving NegRiskCtfExchange as spender on ConditionalTokens")
444
+ receipts.append(
445
+ self.set_conditional_tokens_approval(
446
+ spender=self.neg_risk_exchange_address,
447
+ )
448
+ )
449
+ print("Approving NegRiskAdapter as spender on ConditionalTokens")
450
+ receipts.append(
451
+ self.set_conditional_tokens_approval(
452
+ spender=self.neg_risk_adapter_address,
453
+ )
454
+ )
455
+ print("All approvals set!")
160
456
 
161
- print(f"Txn hash: {tx_hash}")
457
+ return receipts
162
458
 
163
- # Wait for transaction to be mined
164
- self.w3.eth.wait_for_transaction_receipt(tx_hash)
459
+ def transfer_usdc(self, recipient: EthAddress, amount: float) -> TransactionReceipt:
460
+ """Transfers usdc.e from the account to the proxy address."""
461
+ balance = self.get_usdc_balance(address=self.address)
462
+ if balance < amount:
463
+ msg = f"Insufficient USDC.e balance: {balance} < {amount}"
464
+ raise ValueError(msg)
465
+ amount = int(balance * 1e6)
165
466
 
166
- print("Done!")
467
+ to = self.usdc_address
468
+ data = self._encode_transfer_usdc(
469
+ self.w3.to_checksum_address(recipient), amount
470
+ )
471
+ base_transaction = self._build_base_transaction()
472
+ txn_data: TxParams = {}
473
+
474
+ match self.signature_type:
475
+ case 0:
476
+ txn_data = self.usdc.functions.transfer(
477
+ recipient,
478
+ amount,
479
+ ).build_transaction(transaction=base_transaction)
480
+ case 1:
481
+ txn_data = self._build_proxy_transaction(to, data, base_transaction)
482
+ case 2:
483
+ txn_data = self._build_safe_transaction(to, data, base_transaction)
167
484
 
168
- def merge_position(self, condition_id: Keccak256, amount: int, neg_risk: bool = True):
169
- """Merges two complementary positions into usdc."""
170
- nonce = self.w3.eth.get_transaction_count(self.account.address)
171
- amount = int(amount * 1e6)
485
+ # Sign and send transaction
486
+ return self._execute_transaction(txn_data, operation_name="USDC Transfer")
487
+
488
+ def transfer_token(
489
+ self, token_id: str, recipient: EthAddress, amount: float
490
+ ) -> TransactionReceipt:
491
+ """Transfers conditional tokens from the account to the recipient address."""
492
+ balance = self.get_token_balance(token_id=token_id, address=self.address)
493
+ if balance < amount:
494
+ msg = f"Insufficient token balance: {balance} < {amount}"
495
+ raise ValueError(msg)
496
+ amount = int(balance * 1e6)
497
+
498
+ to = self.conditional_tokens_address
499
+ data = self._encode_transfer_token(
500
+ token_id, self.w3.to_checksum_address(recipient), amount
501
+ )
502
+ base_transaction = self._build_base_transaction()
503
+ txn_data: TxParams = {}
504
+
505
+ match self.signature_type:
506
+ case 0:
507
+ txn_data = self.conditional_tokens.functions.safeTransferFrom(
508
+ self.address,
509
+ recipient,
510
+ int(token_id),
511
+ amount,
512
+ b"",
513
+ ).build_transaction(transaction=base_transaction)
514
+ case 1:
515
+ txn_data = self._build_proxy_transaction(to, data, base_transaction)
516
+ case 2:
517
+ txn_data = self._build_safe_transaction(to, data, base_transaction)
172
518
 
173
- proxy_txn = {
174
- "typeCode": 1,
175
- "to": self.neg_risk_adapter_address if neg_risk else self.conditional_tokens_address,
176
- "value": 0,
177
- "data": self._encode_merge(condition_id, amount),
178
- }
519
+ # Sign and send transaction
520
+ return self._execute_transaction(txn_data, operation_name="Token Transfer")
179
521
 
180
- # Send transaction through proxy factory
181
- txn_data = self.proxy_factory.functions.proxy([proxy_txn]).build_transaction({
182
- "nonce": nonce,
183
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
184
- "gas": 1000000,
185
- "from": self.account.address,
186
- })
522
+ def split_position(
523
+ self, condition_id: Keccak256, amount: float, neg_risk: bool = True
524
+ ) -> TransactionReceipt:
525
+ """Splits usdc into two complementary positions of equal size."""
526
+ amount = int(amount * 1e6)
527
+
528
+ to = (
529
+ self.neg_risk_adapter_address
530
+ if neg_risk
531
+ else self.conditional_tokens_address
532
+ )
533
+ data = self._encode_split(condition_id, amount)
534
+ base_transaction = self._build_base_transaction()
535
+ txn_data: TxParams = {}
536
+
537
+ match self.signature_type:
538
+ case 0:
539
+ _contract = (
540
+ self.neg_risk_adapter if neg_risk else self.conditional_tokens
541
+ )
542
+ txn_data = _contract.functions.splitPosition(
543
+ self.usdc_address,
544
+ HASH_ZERO,
545
+ condition_id,
546
+ [1, 2],
547
+ amount,
548
+ ).build_transaction(transaction=base_transaction)
549
+ case 1:
550
+ txn_data = self._build_proxy_transaction(to, data, base_transaction)
551
+ case 2:
552
+ txn_data = self._build_safe_transaction(to, data, base_transaction)
187
553
 
188
554
  # Sign and send transaction
189
- signed_txn = self.account.sign_transaction(txn_data)
190
- tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction).hex()
555
+ return self._execute_transaction(txn_data, operation_name="Split Position")
191
556
 
192
- print(f"Txn hash: {tx_hash}")
557
+ def merge_position(
558
+ self, condition_id: Keccak256, amount: float, neg_risk: bool = True
559
+ ) -> TransactionReceipt:
560
+ """Merges two complementary positions into usdc."""
561
+ amount = int(amount * 1e6)
193
562
 
194
- # Wait for transaction to be mined
195
- self.w3.eth.wait_for_transaction_receipt(tx_hash)
563
+ to = (
564
+ self.neg_risk_adapter_address
565
+ if neg_risk
566
+ else self.conditional_tokens_address
567
+ )
568
+ data = self._encode_merge(condition_id, amount)
569
+ base_transaction = self._build_base_transaction()
570
+ txn_data: TxParams = {}
571
+
572
+ match self.signature_type:
573
+ case 0:
574
+ _contract = (
575
+ self.neg_risk_adapter if neg_risk else self.conditional_tokens
576
+ )
577
+ txn_data = _contract.functions.mergePositions(
578
+ self.usdc_address,
579
+ HASH_ZERO,
580
+ condition_id,
581
+ [1, 2],
582
+ amount,
583
+ ).build_transaction(transaction=base_transaction)
584
+ case 1:
585
+ txn_data = self._build_proxy_transaction(to, data, base_transaction)
586
+ case 2:
587
+ txn_data = self._build_safe_transaction(to, data, base_transaction)
196
588
 
197
- print("Done!")
589
+ # Sign and send transaction
590
+ return self._execute_transaction(txn_data, operation_name="Merge Position")
198
591
 
199
- def redeem_position(self, condition_id: Keccak256, amounts: list[float], neg_risk: bool = True):
592
+ def redeem_position(
593
+ self, condition_id: Keccak256, amounts: list[float], neg_risk: bool = True
594
+ ) -> TransactionReceipt:
200
595
  """
201
596
  Redeem a position into usdc.
202
597
 
@@ -204,59 +599,71 @@ class PolymarketWeb3Client:
204
599
  where x is the number of shares of the first outcome
205
600
  y is the number of shares of the second outcome.
206
601
  """
207
- nonce = self.w3.eth.get_transaction_count(self.account.address)
208
- amounts = [int(amount * 1e6) for amount in amounts]
209
-
210
- proxy_txn = {
211
- "typeCode": 1,
212
- "to": self.neg_risk_adapter_address if neg_risk else self.conditional_tokens_address,
213
- "value": 0,
214
- "data": self._encode_redeem_neg_risk(condition_id, amounts) if neg_risk else self._encode_redeem(condition_id),
215
- }
602
+ int_amounts = [int(amount * 1e6) for amount in amounts]
216
603
 
217
- # Send transaction through proxy factory
218
- txn_data = self.proxy_factory.functions.proxy([proxy_txn]).build_transaction({
219
- "nonce": nonce,
220
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
221
- "gas": 1000000,
222
- "from": self.account.address,
223
- })
604
+ to = (
605
+ self.neg_risk_adapter_address
606
+ if neg_risk
607
+ else self.conditional_tokens_address
608
+ )
609
+ data = (
610
+ self._encode_redeem_neg_risk(condition_id, int_amounts)
611
+ if neg_risk
612
+ else self._encode_redeem(condition_id)
613
+ )
614
+ base_transaction = self._build_base_transaction()
615
+ txn_data: TxParams = {}
616
+
617
+ match self.signature_type:
618
+ case 0:
619
+ _contract = (
620
+ self.neg_risk_adapter if neg_risk else self.conditional_tokens
621
+ )
622
+ if neg_risk:
623
+ txn_data = _contract.functions.redeemPositions(
624
+ condition_id, int_amounts
625
+ ).build_transaction(transaction=base_transaction)
626
+ else:
627
+ txn_data = _contract.functions.redeemPositions(
628
+ self.usdc_address,
629
+ HASH_ZERO,
630
+ condition_id,
631
+ [1, 2],
632
+ ).build_transaction(transaction=base_transaction)
633
+ case 1:
634
+ txn_data = self._build_proxy_transaction(to, data, base_transaction)
635
+ case 2:
636
+ txn_data = self._build_safe_transaction(to, data, base_transaction)
224
637
 
225
638
  # Sign and send transaction
226
- signed_txn = self.account.sign_transaction(txn_data)
227
- tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction).hex()
228
-
229
- print(f"Txn hash: {tx_hash}")
639
+ return self._execute_transaction(txn_data, operation_name="Redeem Position")
230
640
 
231
- # Wait for transaction to be mined
232
- self.w3.eth.wait_for_transaction_receipt(tx_hash)
233
-
234
- print("Done!")
235
-
236
- def convert_positions(self, question_ids: list[Keccak256], neg_risk_market_id: Keccak256, amount: int):
237
- nonce = self.w3.eth.get_transaction_count(self.account.address)
641
+ def convert_positions(
642
+ self,
643
+ question_ids: list[Keccak256],
644
+ amount: float,
645
+ ) -> TransactionReceipt:
238
646
  amount = int(amount * 1e6)
647
+ neg_risk_market_id = question_ids[0][:-2] + "00"
239
648
 
240
- proxy_txn = {
241
- "typeCode": 1,
242
- "to": self.neg_risk_adapter_address,
243
- "value": 0,
244
- "data": self._encode_convert(neg_risk_market_id, get_index_set(question_ids), amount),
245
- }
246
-
247
- txn_data = self.proxy_factory.functions.proxy([proxy_txn]).build_transaction({
248
- "nonce": nonce,
249
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
250
- "gas": 1000000,
251
- "from": self.account.address,
252
- })
253
-
254
- signed_txn = self.account.sign_transaction(txn_data)
255
- tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction).hex()
256
-
257
- print(f"Txn hash: {tx_hash}")
258
-
259
- # Wait for transaction to be mined
260
- self.w3.eth.wait_for_transaction_receipt(tx_hash)
649
+ to = self.neg_risk_adapter_address
650
+ data = self._encode_convert(
651
+ neg_risk_market_id, get_index_set(question_ids), amount
652
+ )
653
+ base_transaction = self._build_base_transaction()
654
+ txn_data: TxParams = {}
655
+
656
+ match self.signature_type:
657
+ case 0:
658
+ txn_data = self.neg_risk_adapter.functions.convertPositions(
659
+ neg_risk_market_id,
660
+ get_index_set(question_ids),
661
+ amount,
662
+ ).build_transaction(transaction=base_transaction)
663
+ case 1:
664
+ txn_data = self._build_proxy_transaction(to, data, base_transaction)
665
+ case 2:
666
+ txn_data = self._build_safe_transaction(to, data, base_transaction)
261
667
 
262
- print("Done!")
668
+ # Sign and send transaction
669
+ return self._execute_transaction(txn_data, operation_name="Convert Positions")