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

@@ -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.4"
13
+ __version__ = "0.3.6"
14
14
  __author__ = "Razvan Gheorghe"
15
15
  __email__ = "razvan@gheorghe.me"
16
16
 
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  import logging
3
- from datetime import UTC, datetime
3
+ from datetime import UTC, datetime, timedelta
4
4
  from typing import Literal, Optional
5
5
  from urllib.parse import urljoin
6
6
 
@@ -93,13 +93,13 @@ class PolymarketClobClient:
93
93
  def __init__(
94
94
  self,
95
95
  private_key: str,
96
- proxy_address: EthAddress,
96
+ address: EthAddress,
97
97
  creds: Optional[ApiCreds] = None,
98
98
  chain_id: Literal[137, 80002] = POLYGON,
99
99
  signature_type: Literal[0, 1, 2] = 1,
100
100
  # 0 - EOA wallet, 1 - Proxy wallet, 2 - Gnosis Safe wallet
101
101
  ):
102
- self.proxy_address = proxy_address
102
+ self.address = address
103
103
  self.client = httpx.Client(http2=True, timeout=30.0)
104
104
  self.async_client = httpx.AsyncClient(http2=True, timeout=30.0)
105
105
  self.base_url: str = "https://clob.polymarket.com"
@@ -107,7 +107,7 @@ class PolymarketClobClient:
107
107
  self.builder = OrderBuilder(
108
108
  signer=self.signer,
109
109
  sig_type=signature_type,
110
- funder=proxy_address,
110
+ funder=address,
111
111
  )
112
112
  self.creds = creds if creds else self.create_or_derive_api_creds()
113
113
 
@@ -349,12 +349,21 @@ class PolymarketClobClient:
349
349
  def get_recent_history(
350
350
  self,
351
351
  token_id: str,
352
- interval: Optional[Literal["1d", "6h", "1h"]] = "1d",
352
+ interval: Literal["1h", "6h", "1d", "1w", "1m", "max"] = "1d",
353
353
  fidelity: int = 1, # resolution in minutes
354
354
  ) -> PriceHistory:
355
- """Get the recent price history of a token (up to now) - 1h, 6h, 1d."""
356
- if fidelity < 1:
357
- msg = f"invalid filters: minimum 'fidelity' for '{interval}' range is 1"
355
+ """Get the recent price history of a token (up to now) - 1h, 6h, 1d, 1w, 1m."""
356
+ min_fidelities: dict[str, int] = {
357
+ "1h": 1,
358
+ "6h": 1,
359
+ "1d": 1,
360
+ "1w": 5,
361
+ "1m": 10,
362
+ "max": 2,
363
+ }
364
+
365
+ if fidelity < min_fidelities.get(interval):
366
+ msg = f"invalid filters: minimum 'fidelity' for '{interval}' range is {min_fidelities.get(interval)}"
358
367
  raise ValueError(msg)
359
368
 
360
369
  params = {
@@ -371,38 +380,41 @@ class PolymarketClobClient:
371
380
  token_id: str,
372
381
  start_time: Optional[datetime] = None,
373
382
  end_time: Optional[datetime] = None,
374
- interval: Literal["max", "1m", "1w"] = "max",
375
383
  fidelity: int = 2, # resolution in minutes
376
384
  ) -> PriceHistory:
377
- """Get the price history of a token between selected dates - 1m, 1w, max."""
378
- min_fidelities: dict[str, int] = {"1m": 10, "1w": 5, "max": 2}
379
-
380
- if fidelity is None or fidelity < min_fidelities[interval]:
381
- msg = f"invalid filters: minimum 'fidelity' for '{interval}' range is {min_fidelities[interval]}"
382
- raise ValueError(msg)
383
-
385
+ """Get the price history of a token between a selected date range of max 15 days or from start_time to now."""
384
386
  if start_time is None and end_time is None:
385
- msg = "At least one of 'start_time' or 'end_time' must be provided."
387
+ msg = "At least 'start_time' or ('start_time' and 'end_time') must be provided"
386
388
  raise ValueError(msg)
387
389
 
388
- # Default values for timestamps if one is not provided
389
-
390
- if start_time is None:
391
- start_time = datetime(2020, 1, 1, tzinfo=UTC) # Default start time
392
- if end_time is None:
393
- end_time = datetime.now(UTC) # Default end time
390
+ if (
391
+ start_time
392
+ and end_time
393
+ and start_time + timedelta(days=15, seconds=1) < end_time
394
+ ):
395
+ msg = "'start_time' - 'end_time' range cannot exceed 15 days. Remove 'end_time' to get prices up to now or set a shorter range."
396
+ raise ValueError(msg)
394
397
 
395
398
  params = {
396
399
  "market": token_id,
397
- "startTs": int(start_time.timestamp()),
398
- "endTs": int(end_time.timestamp()),
399
- "interval": interval,
400
400
  "fidelity": fidelity,
401
401
  }
402
+ if start_time:
403
+ params["startTs"] = int(start_time.timestamp())
404
+ if end_time:
405
+ params["endTs"] = int(end_time.timestamp())
406
+
402
407
  response = self.client.get(self._build_url("/prices-history"), params=params)
403
408
  response.raise_for_status()
404
409
  return PriceHistory(**response.json(), token_id=token_id)
405
410
 
411
+ def get_all_history(self, token_id: str) -> PriceHistory:
412
+ """Get the full price history of a token."""
413
+ return self.get_history(
414
+ token_id=token_id,
415
+ start_time=datetime(2020, 1, 1, tzinfo=UTC),
416
+ )
417
+
406
418
  def get_orders(
407
419
  self,
408
420
  order_id: Optional[str] = None,
@@ -732,7 +744,7 @@ class PolymarketClobClient:
732
744
  trade_id: Optional[str] = None,
733
745
  before: Optional[datetime] = None,
734
746
  after: Optional[datetime] = None,
735
- proxy_address: Optional[int] = None,
747
+ address: Optional[EthAddress] = None,
736
748
  next_cursor="MA==",
737
749
  ) -> list[PolygonTrade]:
738
750
  """Fetches the trade history for a user."""
@@ -747,8 +759,8 @@ class PolymarketClobClient:
747
759
  params["before"] = int(before.replace(microsecond=0).timestamp())
748
760
  if after:
749
761
  params["after"] = int(after.replace(microsecond=0).timestamp())
750
- if proxy_address:
751
- params["maker_address"] = proxy_address
762
+ if address:
763
+ params["maker_address"] = address
752
764
 
753
765
  request_args = RequestArgs(method="GET", request_path=TRADES)
754
766
  headers = create_level_2_headers(self.signer, self.creds, request_args)
@@ -788,7 +800,7 @@ class PolymarketClobClient:
788
800
  return DailyEarnedReward(
789
801
  date=date,
790
802
  asset_address="0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
791
- maker_address=self.proxy_address,
803
+ maker_address=self.address,
792
804
  earnings=0.0,
793
805
  asset_rate=0.0,
794
806
  )
@@ -4,10 +4,12 @@ from urllib.parse import urljoin
4
4
 
5
5
  import httpx
6
6
 
7
+ from ..clients.graphql_client import PolymarketGraphQLClient
7
8
  from ..types.common import EthAddress, TimeseriesPoint
8
9
  from ..types.data_types import (
9
10
  Activity,
10
11
  EventLiveVolume,
12
+ GQLPosition,
11
13
  HolderResponse,
12
14
  MarketValue,
13
15
  Position,
@@ -22,6 +24,9 @@ class PolymarketDataClient:
22
24
  def __init__(self, base_url: str = "https://data-api.polymarket.com"):
23
25
  self.base_url = base_url
24
26
  self.client = httpx.Client(http2=True, timeout=30.0)
27
+ self.gql_positions_client = PolymarketGraphQLClient(
28
+ endpoint_name="positions_subgraph"
29
+ )
25
30
 
26
31
  def _build_url(self, endpoint: str) -> str:
27
32
  return urljoin(self.base_url, endpoint)
@@ -31,6 +36,35 @@ class PolymarketDataClient:
31
36
  response.raise_for_status()
32
37
  return response.json()["data"]
33
38
 
39
+ def get_all_positions(
40
+ self,
41
+ user: EthAddress,
42
+ size_threshold: float = 0.0,
43
+ ):
44
+ # data-api /positions endpoint does not support fetching all positions without filters
45
+ # a workaround is to use the GraphQL positions subgraph directly
46
+ query = f"""query {{
47
+ userBalances(where: {{
48
+ user: "{user.lower()}",
49
+ balance_gt: "{int(size_threshold * 10**6)}"
50
+ }}) {{
51
+ user
52
+ asset {{
53
+ id
54
+ condition {{
55
+ id
56
+ }}
57
+ complement
58
+ outcomeIndex
59
+ }}
60
+ balance
61
+ }}
62
+ }}
63
+ """
64
+
65
+ response = self.gql_positions_client.query(query)
66
+ return [GQLPosition(**pos) for pos in response["userBalances"]]
67
+
34
68
  def get_positions(
35
69
  self,
36
70
  user: EthAddress,
@@ -32,7 +32,7 @@ class PolymarketWeb3Client:
32
32
  def __init__(
33
33
  self,
34
34
  private_key: str,
35
- signature_type: Literal[1, 2] = 1,
35
+ signature_type: Literal[0, 1, 2] = 1,
36
36
  chain_id: Literal[137, 80002] = POLYGON,
37
37
  ):
38
38
  self.w3 = Web3(Web3.HTTPProvider("https://polygon-rpc.com"))
@@ -90,15 +90,14 @@ class PolymarketWeb3Client:
90
90
  )
91
91
 
92
92
  match self.signature_type:
93
+ case 0:
94
+ self.address = self.account.address
93
95
  case 1:
94
- self.proxy_address = self.get_poly_proxy_address()
96
+ self.address = self.get_poly_proxy_address()
95
97
  case 2:
96
- self.proxy_address = self.get_safe_proxy_address()
98
+ self.address = self.get_safe_proxy_address()
97
99
  self.safe_abi = _load_abi("Safe")
98
- self.safe = self.contract(self.proxy_address, self.safe_abi)
99
- case _:
100
- msg = "Invalid signature_type: choose 1 for Magic/Email wallet or 2 for Safe/Gnosis wallet"
101
- raise ValueError(msg)
100
+ self.safe = self.contract(self.address, self.safe_abi)
102
101
 
103
102
  def _encode_split(self, condition_id: Keccak256, amount: int) -> str:
104
103
  return self.conditional_tokens.encode_abi(
@@ -159,23 +158,16 @@ class PolymarketWeb3Client:
159
158
  Explicitly passing the proxy address is faster due to only one contract function call.
160
159
  """
161
160
  if address is None:
162
- address = self.get_poly_proxy_address()
161
+ address = self.address
163
162
  balance_res = self.usdc.functions.balanceOf(address).call()
164
163
  return float(balance_res / 1e6)
165
164
 
166
165
  def get_token_balance(
167
- self, token_id: str, address: EthAddress | None = None
166
+ self, token_id: str, address: Optional[EthAddress] = None
168
167
  ) -> float:
169
168
  """Get the token balance of the given address."""
170
169
  if not address:
171
- match self.signature_type:
172
- case 1:
173
- address = self.get_poly_proxy_address(self.account.address)
174
- case 2:
175
- address = self.get_safe_proxy_address(self.account.address)
176
- case _:
177
- msg = "Invalid signature_type: choose 1 for Magic/Email wallet or 2 for Safe/Gnosis wallet"
178
- raise ValueError(msg)
170
+ address = self.address
179
171
  balance_res = self.conditional_tokens.functions.balanceOf(
180
172
  address, int(token_id)
181
173
  ).call()
@@ -214,7 +206,7 @@ class PolymarketWeb3Client:
214
206
  )
215
207
 
216
208
  def split_position(
217
- self, condition_id: Keccak256, amount: int, neg_risk: bool = True
209
+ self, condition_id: Keccak256, amount: float, neg_risk: bool = True
218
210
  ):
219
211
  """Splits usdc into two complementary positions of equal size."""
220
212
  amount = int(amount * 1e6)
@@ -224,40 +216,47 @@ class PolymarketWeb3Client:
224
216
  if neg_risk
225
217
  else self.conditional_tokens_address
226
218
  )
219
+ nonce = self.w3.eth.get_transaction_count(self.account.address)
220
+ transaction = {
221
+ "nonce": nonce,
222
+ "gasPrice": int(1.05 * self.w3.eth.gas_price),
223
+ "gas": 1000000,
224
+ "from": self.account.address,
225
+ }
227
226
 
228
227
  match self.signature_type:
228
+ case 0:
229
+ contract = (
230
+ self.neg_risk_adapter if neg_risk else self.conditional_tokens
231
+ )
232
+ txn_data = contract.functions.splitPosition(
233
+ self.usdc_address,
234
+ HASH_ZERO,
235
+ condition_id,
236
+ [1, 2],
237
+ amount,
238
+ ).build_transaction(transaction=transaction)
229
239
  case 1:
230
- nonce = self.w3.eth.get_transaction_count(self.account.address)
231
240
  proxy_txn = {
232
241
  "typeCode": 1,
233
242
  "to": to,
234
243
  "value": 0,
235
244
  "data": data,
236
245
  }
237
-
238
246
  txn_data = self.proxy_factory.functions.proxy(
239
247
  [proxy_txn]
240
- ).build_transaction(
241
- {
242
- "nonce": nonce,
243
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
244
- "gas": 1000000,
245
- "from": self.account.address,
246
- }
247
- )
248
+ ).build_transaction(transaction=transaction)
248
249
  case 2:
249
- nonce = self.safe.functions.nonce().call()
250
+ safe_nonce = self.safe.functions.nonce().call()
250
251
  safe_txn = {
251
252
  "to": to,
252
253
  "data": data,
253
254
  "operation": 0, # 1 for delegatecall, 0 for call
254
255
  "value": 0,
255
256
  }
256
-
257
257
  packed_sig = sign_safe_transaction(
258
- self.account, self.safe, safe_txn, nonce
258
+ self.account, self.safe, safe_txn, safe_nonce
259
259
  )
260
-
261
260
  txn_data = self.safe.functions.execTransaction(
262
261
  safe_txn["to"],
263
262
  safe_txn["value"],
@@ -269,16 +268,7 @@ class PolymarketWeb3Client:
269
268
  ADDRESS_ZERO, # gasToken
270
269
  ADDRESS_ZERO, # refundReceiver
271
270
  packed_sig,
272
- ).build_transaction(
273
- {
274
- "nonce": self.w3.eth.get_transaction_count(
275
- self.account.address
276
- ),
277
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
278
- "gas": 1000000,
279
- "from": self.account.address,
280
- }
281
- )
271
+ ).build_transaction(transaction=transaction)
282
272
 
283
273
  # Sign and send transaction
284
274
  signed_txn = self.account.sign_transaction(txn_data)
@@ -293,7 +283,7 @@ class PolymarketWeb3Client:
293
283
  print("Done!")
294
284
 
295
285
  def merge_position(
296
- self, condition_id: Keccak256, amount: int, neg_risk: bool = True
286
+ self, condition_id: Keccak256, amount: float, neg_risk: bool = True
297
287
  ):
298
288
  """Merges two complementary positions into usdc."""
299
289
  amount = int(amount * 1e6)
@@ -303,10 +293,27 @@ class PolymarketWeb3Client:
303
293
  if neg_risk
304
294
  else self.conditional_tokens_address
305
295
  )
296
+ nonce = self.w3.eth.get_transaction_count(self.account.address)
297
+ transaction = {
298
+ "nonce": nonce,
299
+ "gasPrice": int(1.05 * self.w3.eth.gas_price),
300
+ "gas": 1000000,
301
+ "from": self.account.address,
302
+ }
306
303
 
307
304
  match self.signature_type:
305
+ case 0:
306
+ contract = (
307
+ self.neg_risk_adapter if neg_risk else self.conditional_tokens
308
+ )
309
+ txn_data = contract.functions.mergePositions(
310
+ self.usdc_address,
311
+ HASH_ZERO,
312
+ condition_id,
313
+ [1, 2],
314
+ amount,
315
+ ).build_transaction(transaction=transaction)
308
316
  case 1:
309
- nonce = self.w3.eth.get_transaction_count(self.account.address)
310
317
  proxy_txn = {
311
318
  "typeCode": 1,
312
319
  "to": to,
@@ -316,25 +323,17 @@ class PolymarketWeb3Client:
316
323
 
317
324
  txn_data = self.proxy_factory.functions.proxy(
318
325
  [proxy_txn]
319
- ).build_transaction(
320
- {
321
- "nonce": nonce,
322
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
323
- "gas": 1000000,
324
- "from": self.account.address,
325
- }
326
- )
326
+ ).build_transaction(transaction=transaction)
327
327
  case 2:
328
- nonce = self.safe.functions.nonce().call()
328
+ safe_nonce = self.safe.functions.nonce().call()
329
329
  safe_txn = {
330
330
  "to": to,
331
331
  "data": data,
332
332
  "operation": 0, # 1 for delegatecall, 0 for call
333
333
  "value": 0,
334
334
  }
335
-
336
335
  packed_sig = sign_safe_transaction(
337
- self.account, self.safe, safe_txn, nonce
336
+ self.account, self.safe, safe_txn, safe_nonce
338
337
  )
339
338
  txn_data = self.safe.functions.execTransaction(
340
339
  safe_txn["to"],
@@ -347,16 +346,7 @@ class PolymarketWeb3Client:
347
346
  ADDRESS_ZERO, # gasToken
348
347
  ADDRESS_ZERO, # refundReceiver
349
348
  packed_sig,
350
- ).build_transaction(
351
- {
352
- "nonce": self.w3.eth.get_transaction_count(
353
- self.account.address
354
- ),
355
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
356
- "gas": 1000000,
357
- "from": self.account.address,
358
- }
359
- )
349
+ ).build_transaction(transaction=transaction)
360
350
 
361
351
  # Sign and send transaction
362
352
  signed_txn = self.account.sign_transaction(txn_data)
@@ -380,9 +370,9 @@ class PolymarketWeb3Client:
380
370
  where x is the number of shares of the first outcome
381
371
  y is the number of shares of the second outcome.
382
372
  """
383
- amounts = [int(amount * 1e6) for amount in amounts]
373
+ int_amounts = [int(amount * 1e6) for amount in amounts]
384
374
  data = (
385
- self._encode_redeem_neg_risk(condition_id, amounts)
375
+ self._encode_redeem_neg_risk(condition_id, int_amounts)
386
376
  if neg_risk
387
377
  else self._encode_redeem(condition_id)
388
378
  )
@@ -391,38 +381,50 @@ class PolymarketWeb3Client:
391
381
  if neg_risk
392
382
  else self.conditional_tokens_address
393
383
  )
384
+ nonce = self.w3.eth.get_transaction_count(self.account.address)
385
+ transaction = {
386
+ "nonce": nonce,
387
+ "gasPrice": int(1.05 * self.w3.eth.gas_price),
388
+ "gas": 1000000,
389
+ "from": self.account.address,
390
+ }
394
391
 
395
392
  match self.signature_type:
393
+ case 0:
394
+ contract = (
395
+ self.neg_risk_adapter if neg_risk else self.conditional_tokens
396
+ )
397
+ if neg_risk:
398
+ txn_data = contract.functions.redeemPositions(
399
+ condition_id, int_amounts
400
+ ).build_transaction(transaction=transaction)
401
+ else:
402
+ txn_data = contract.functions.redeemPositions(
403
+ self.usdc_address,
404
+ HASH_ZERO,
405
+ condition_id,
406
+ [1, 2],
407
+ ).build_transaction(transaction=transaction)
396
408
  case 1:
397
- nonce = self.w3.eth.get_transaction_count(self.account.address)
398
409
  proxy_txn = {
399
410
  "typeCode": 1,
400
411
  "to": to,
401
412
  "value": 0,
402
413
  "data": data,
403
414
  }
404
-
405
415
  txn_data = self.proxy_factory.functions.proxy(
406
416
  [proxy_txn]
407
- ).build_transaction(
408
- {
409
- "nonce": nonce,
410
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
411
- "gas": 1000000,
412
- "from": self.account.address,
413
- }
414
- )
417
+ ).build_transaction(transaction=transaction)
415
418
  case 2:
416
- nonce = self.safe.functions.nonce().call()
419
+ safe_nonce = self.safe.functions.nonce().call()
417
420
  safe_txn = {
418
421
  "to": to,
419
422
  "data": data,
420
423
  "operation": 0, # 1 for delegatecall, 0 for call
421
424
  "value": 0,
422
425
  }
423
-
424
426
  packed_sig = sign_safe_transaction(
425
- self.account, self.safe, safe_txn, nonce
427
+ self.account, self.safe, safe_txn, safe_nonce
426
428
  )
427
429
  txn_data = self.safe.functions.execTransaction(
428
430
  safe_txn["to"],
@@ -435,16 +437,7 @@ class PolymarketWeb3Client:
435
437
  ADDRESS_ZERO, # gasToken
436
438
  ADDRESS_ZERO, # refundReceiver
437
439
  packed_sig,
438
- ).build_transaction(
439
- {
440
- "nonce": self.w3.eth.get_transaction_count(
441
- self.account.address
442
- ),
443
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
444
- "gas": 1000000,
445
- "from": self.account.address,
446
- }
447
- )
440
+ ).build_transaction(transaction=transaction)
448
441
 
449
442
  # Sign and send transaction
450
443
  signed_txn = self.account.sign_transaction(txn_data)
@@ -459,18 +452,32 @@ class PolymarketWeb3Client:
459
452
  print("Done!")
460
453
 
461
454
  def convert_positions(
462
- self, question_ids: list[Keccak256], neg_risk_market_id: Keccak256, amount: int
455
+ self,
456
+ question_ids: list[Keccak256],
457
+ amount: float,
463
458
  ):
464
- nonce = self.w3.eth.get_transaction_count(self.account.address)
465
459
  amount = int(amount * 1e6)
460
+ neg_risk_market_id = question_ids[0][:-2] + "00"
466
461
  data = self._encode_convert(
467
462
  neg_risk_market_id, get_index_set(question_ids), amount
468
463
  )
469
464
  to = self.neg_risk_adapter_address
465
+ nonce = self.w3.eth.get_transaction_count(self.account.address)
466
+ transaction = {
467
+ "nonce": nonce,
468
+ "gasPrice": int(1.05 * self.w3.eth.gas_price),
469
+ "gas": 1000000,
470
+ "from": self.account.address,
471
+ }
470
472
 
471
473
  match self.signature_type:
474
+ case 0:
475
+ txn_data = self.neg_risk_adapter.functions.convertPositions(
476
+ neg_risk_market_id,
477
+ get_index_set(question_ids),
478
+ amount,
479
+ ).build_transaction(transaction=transaction)
472
480
  case 1:
473
- nonce = self.w3.eth.get_transaction_count(self.account.address)
474
481
  proxy_txn = {
475
482
  "typeCode": 1,
476
483
  "to": to,
@@ -480,16 +487,9 @@ class PolymarketWeb3Client:
480
487
 
481
488
  txn_data = self.proxy_factory.functions.proxy(
482
489
  [proxy_txn]
483
- ).build_transaction(
484
- {
485
- "nonce": nonce,
486
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
487
- "gas": 1000000,
488
- "from": self.account.address,
489
- }
490
- )
490
+ ).build_transaction(transaction=transaction)
491
491
  case 2:
492
- nonce = self.safe.functions.nonce().call()
492
+ safe_nonce = self.safe.functions.nonce().call()
493
493
  safe_txn = {
494
494
  "to": to,
495
495
  "data": data,
@@ -498,7 +498,7 @@ class PolymarketWeb3Client:
498
498
  }
499
499
 
500
500
  packed_sig = sign_safe_transaction(
501
- self.account, self.safe, safe_txn, nonce
501
+ self.account, self.safe, safe_txn, safe_nonce
502
502
  )
503
503
  txn_data = self.safe.functions.execTransaction(
504
504
  safe_txn["to"],
@@ -511,16 +511,7 @@ class PolymarketWeb3Client:
511
511
  ADDRESS_ZERO, # gasToken
512
512
  ADDRESS_ZERO, # refundReceiver
513
513
  packed_sig,
514
- ).build_transaction(
515
- {
516
- "nonce": self.w3.eth.get_transaction_count(
517
- self.account.address
518
- ),
519
- "gasPrice": int(1.05 * self.w3.eth.gas_price),
520
- "gas": 1000000,
521
- "from": self.account.address,
522
- }
523
- )
514
+ ).build_transaction(transaction=transaction)
524
515
 
525
516
  # Sign and send transaction
526
517
  signed_txn = self.account.sign_transaction(txn_data)
@@ -3,7 +3,7 @@ from datetime import UTC, datetime
3
3
  from typing import Annotated
4
4
 
5
5
  from dateutil import parser
6
- from pydantic import AfterValidator, BaseModel, BeforeValidator, Field
6
+ from pydantic import AfterValidator, BaseModel, BeforeValidator, ConfigDict, Field
7
7
 
8
8
 
9
9
  def validate_keccak256(v: str) -> str:
@@ -30,5 +30,7 @@ EmptyString = Annotated[str, Field(pattern=r"^$", description="An empty string")
30
30
 
31
31
 
32
32
  class TimeseriesPoint(BaseModel):
33
- value: float
34
- timestamp: datetime
33
+ model_config = ConfigDict(populate_by_name=True)
34
+
35
+ value: float = Field(alias="p")
36
+ timestamp: datetime = Field(alias="t")
@@ -1,11 +1,43 @@
1
1
  from datetime import UTC, datetime
2
2
  from typing import Literal, Optional
3
3
 
4
- from pydantic import BaseModel, Field, field_validator
4
+ from pydantic import BaseModel, Field, field_validator, model_validator
5
5
 
6
6
  from .common import EmptyString, EthAddress, Keccak256
7
7
 
8
8
 
9
+ class GQLPosition(BaseModel):
10
+ user: EthAddress
11
+ token_id: str
12
+ complementary_token_id: str
13
+ condition_id: Keccak256
14
+ outcome_index: int
15
+ balance: float
16
+
17
+ @model_validator(mode="before")
18
+ def _flatten(cls, values):
19
+ asset = values.get("asset")
20
+ if isinstance(asset, dict):
21
+ if "id" in asset:
22
+ values.setdefault("token_id", asset["id"])
23
+ if "complement" in asset:
24
+ values.setdefault("complementary_token_id", asset["complement"])
25
+ condition = asset.get("condition")
26
+ if isinstance(condition, dict) and "id" in condition:
27
+ values.setdefault("condition_id", condition["id"])
28
+ if "outcomeIndex" in asset:
29
+ values.setdefault("outcome_index", asset["outcomeIndex"])
30
+ values.pop("asset", None)
31
+ return values
32
+
33
+ @field_validator("balance", mode="before")
34
+ @classmethod
35
+ def _parse_balance(cls, value):
36
+ if isinstance(value, str):
37
+ value = int(value)
38
+ return value / 10**6
39
+
40
+
9
41
  class Position(BaseModel):
10
42
  # User identification
11
43
  proxy_wallet: EthAddress = Field(alias="proxyWallet")
@@ -28,7 +28,7 @@ def _pack_primitive(typ: str, val: Any) -> bytes:
28
28
  raw = val
29
29
 
30
30
  if typ == "string":
31
- if not isinstance(raw, (bytes, str)):
31
+ if not isinstance(raw, bytes | str):
32
32
  msg = "string value must be str or bytes"
33
33
  raise TypeError(msg)
34
34
  return raw.encode() if isinstance(raw, str) else raw
@@ -47,7 +47,7 @@ def _pack_primitive(typ: str, val: Any) -> bytes:
47
47
  b = raw.to_bytes(n, "big")
48
48
  elif isinstance(raw, str) and raw.startswith("0x"):
49
49
  b = bytes.fromhex(raw[2:])
50
- elif isinstance(raw, (bytes, bytearray)):
50
+ elif isinstance(raw, bytes | bytearray):
51
51
  b = bytes(raw)
52
52
  else:
53
53
  msg = f"unsupported value for {typ}"
@@ -63,7 +63,7 @@ def _pack_primitive(typ: str, val: Any) -> bytes:
63
63
  addr = raw[2:]
64
64
  elif isinstance(raw, str):
65
65
  addr = raw
66
- elif isinstance(raw, (bytes, bytearray)):
66
+ elif isinstance(raw, bytes | bytearray):
67
67
  return bytes(raw[-20:])
68
68
  else:
69
69
  msg = "address must be hex string or bytes"
@@ -75,7 +75,7 @@ def _pack_primitive(typ: str, val: Any) -> bytes:
75
75
  if m:
76
76
  bits = int(m.group(1)) if m.group(1) else 256
77
77
  size = bits // 8
78
- if isinstance(raw, (bytes, bytearray)):
78
+ if isinstance(raw, bytes | bytearray):
79
79
  intval = int.from_bytes(raw, "big")
80
80
  elif isinstance(raw, str) and raw.startswith("0x"):
81
81
  intval = int(raw, 16)
@@ -1,7 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polymarket-apis
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: Unified Polymarket APIs with Pydantic data validation - Clob, Gamma, Data, Web3, Websockets, GraphQL clients.
5
+ Project-URL: repository, https://github.com/qualiaenjoyer/polymarket-apis
5
6
  Author-email: Razvan Gheorghe <razvan@gheorghe.me>
6
7
  Requires-Python: >=3.12
7
8
  Requires-Dist: gql[httpx]>=4.0.0
@@ -88,7 +89,9 @@ flowchart LR
88
89
  - check if a **Market** offers rewards by `condition_id` - **get_market_rewards()**
89
90
  - get all active markets that offer rewards sorted by different metrics and ordered, filtered by a query, show your favourites from the web app - **get_reward_markets()** (*naming would do with some work*)
90
91
  - #### Miscellaneous
91
- - get price history
92
+ - get recent price history by `token_id` in the last 1h, 6h, 1d, 1w, 1m
93
+ - get price history by `token_id` in start/end interval
94
+ - get all price history by `token_id` in 2 min increments
92
95
  - get **ClobMarket** by `condition_id`
93
96
  - get all **ClobMarkets**
94
97
 
@@ -1,15 +1,15 @@
1
- polymarket_apis/__init__.py,sha256=LR_GjibN6oQ92cZYbqhff0vVUtXhrVUfL5f8m2lWCMo,1126
1
+ polymarket_apis/__init__.py,sha256=qovYOBPNsQVc792R8UIVlGIrU-p-2DKclTosLQZbhT4,1126
2
2
  polymarket_apis/clients/__init__.py,sha256=ruMvFEA4HNkbWEYqbnrCuYXR4PUwkV1XWG0w63we-LA,759
3
- polymarket_apis/clients/clob_client.py,sha256=dDXkEf31tQnLHC7KsN7UIMfnLDJJFHrJ4VD_WwaTFbI,32102
4
- polymarket_apis/clients/data_client.py,sha256=SAPxgfWwPK_YuNBOvDxKgUIzdh-hMo2YlnTZ0rFDMXE,12000
3
+ polymarket_apis/clients/clob_client.py,sha256=gt3EqqMYOYB0_hI8HByp0flfKRsUkzL9gpQZJxX_r5k,32359
4
+ polymarket_apis/clients/data_client.py,sha256=xHZcjZMKpzPD8kipPc1qN08q7jWPqN3Py1hD28MQ2EQ,13151
5
5
  polymarket_apis/clients/gamma_client.py,sha256=tBR_z03c3wh1_sxFbjNx54DcrFEjFB0TmJiOYNUc_pk,27632
6
6
  polymarket_apis/clients/graphql_client.py,sha256=KgjxbXNWEXp82ZEz464in5mCn1PydnZqWq-g11xu9YU,1839
7
- polymarket_apis/clients/web3_client.py,sha256=J3j7T39cgZVb4PrYs7FoPoasO3PBP_7SbSuQzA--m64,20162
7
+ polymarket_apis/clients/web3_client.py,sha256=KR48E9rVKfwqo6BCtGKQZ6VYyeRTiKPKMlI3sv0gjpk,19843
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=PmD_afx7zowX0cFJ39PQQ4jywGtwNjLfoke1H7iZZnI,11744
11
- polymarket_apis/types/common.py,sha256=RYu6Dfh0HUIogpK_kZ1AQQQEiqQD3hHACU5fMKSUoMU,1003
12
- polymarket_apis/types/data_types.py,sha256=F6GDhWNtIoQ8kvLvUdlRt596aUfnuc1aHg8m8UweT_M,4641
11
+ polymarket_apis/types/common.py,sha256=zFo10eBj4_3alOhYSilCm6Dh5ZkzhrdR9nuRSDQ_od8,1107
12
+ polymarket_apis/types/data_types.py,sha256=vBElvARw8LkH2xXwJkVErpQo0FkXJA3b44hKD5clSOI,5729
13
13
  polymarket_apis/types/gamma_types.py,sha256=MoIUI6csqEp_vij7mkFi2IlIA99c5UNzI3zODSONWzQ,30641
14
14
  polymarket_apis/types/websockets_types.py,sha256=XGTpfix2rFFQg-97ZkPagS_tHJJpSnB5mCMf_sT2MOI,11180
15
15
  polymarket_apis/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -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=7-nFALFrz0qg4F4lZRyHI41S7IWxr7t0WMLOHqHzJcg,732
29
29
  polymarket_apis/utilities/web3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- polymarket_apis/utilities/web3/helpers.py,sha256=QEpBaA_g7DRzFUZAfiKr6kg5T5_u5FaVZ0mGGNzo6mo,4698
30
+ polymarket_apis/utilities/web3/helpers.py,sha256=qzQA-n9rHbuFk57olvBa8P_T6IzrUsaQ2Vv0887EX4o,4694
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.4.dist-info/METADATA,sha256=36D_8S5Nbt5dNd-oAZDEvndcd-bLoR5RlfP8ypVj5GA,12231
42
- polymarket_apis-0.3.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
43
- polymarket_apis-0.3.4.dist-info/RECORD,,
41
+ polymarket_apis-0.3.6.dist-info/METADATA,sha256=AToVPj7UlKers6vyyeIFBjgNvdridWvI7Y82_T8fHzg,12479
42
+ polymarket_apis-0.3.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
43
+ polymarket_apis-0.3.6.dist-info/RECORD,,