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
@@ -4,10 +4,14 @@ 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,
11
+ EventLiveVolume,
12
+ GQLPosition,
10
13
  HolderResponse,
14
+ MarketValue,
11
15
  Position,
12
16
  Trade,
13
17
  UserMetric,
@@ -20,33 +24,76 @@ class PolymarketDataClient:
20
24
  def __init__(self, base_url: str = "https://data-api.polymarket.com"):
21
25
  self.base_url = base_url
22
26
  self.client = httpx.Client(http2=True, timeout=30.0)
27
+ self.gql_positions_client = PolymarketGraphQLClient(
28
+ endpoint_name="positions_subgraph"
29
+ )
23
30
 
24
31
  def _build_url(self, endpoint: str) -> str:
25
32
  return urljoin(self.base_url, endpoint)
26
33
 
34
+ def get_ok(self) -> str:
35
+ response = self.client.get(self.base_url)
36
+ response.raise_for_status()
37
+ return response.json()["data"]
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
+
27
68
  def get_positions(
28
- self,
29
- user: str,
30
- condition_id: Optional[Union[str, list[str]]] = None,
31
- size_threshold: float = 1.0,
32
- redeemable: Optional[bool] = None,
33
- mergeable: Optional[bool] = None,
34
- title: Optional[str] = None,
35
- limit: int = 100,
36
- offset: int = 0,
37
- sort_by: Literal[
38
- "TOKENS",
39
- "CURRENT",
40
- "INITIAL",
41
- "CASHPNL",
42
- "PERCENTPNL",
43
- "TITLE",
44
- "RESOLVING",
45
- "PRICE",
46
- ] = "TOKENS",
47
- sort_direction: Literal["ASC", "DESC"] = "DESC",
69
+ self,
70
+ user: EthAddress,
71
+ condition_id: Optional[
72
+ Union[str, list[str]]
73
+ ] = None, # mutually exclusive with event_id
74
+ event_id: Optional[
75
+ Union[int, list[int]]
76
+ ] = None, # mutually exclusive with condition_id
77
+ size_threshold: float = 1.0,
78
+ redeemable: bool = False,
79
+ mergeable: bool = False,
80
+ title: Optional[str] = None,
81
+ limit: int = 100,
82
+ offset: int = 0,
83
+ sort_by: Literal[
84
+ "TOKENS",
85
+ "CURRENT",
86
+ "INITIAL",
87
+ "CASHPNL",
88
+ "PERCENTPNL",
89
+ "TITLE",
90
+ "RESOLVING",
91
+ "PRICE",
92
+ "AVGPRICE",
93
+ ] = "TOKENS",
94
+ sort_direction: Literal["ASC", "DESC"] = "DESC",
48
95
  ) -> list[Position]:
49
- params = {
96
+ params: dict[str, str | list[str] | int | float] = {
50
97
  "user": user,
51
98
  "sizeThreshold": size_threshold,
52
99
  "limit": min(limit, 500),
@@ -56,6 +103,10 @@ class PolymarketDataClient:
56
103
  params["market"] = condition_id
57
104
  if isinstance(condition_id, list):
58
105
  params["market"] = ",".join(condition_id)
106
+ if isinstance(event_id, str):
107
+ params["eventId"] = event_id
108
+ if isinstance(event_id, list):
109
+ params["eventId"] = [str(i) for i in event_id]
59
110
  if redeemable is not None:
60
111
  params["redeemable"] = redeemable
61
112
  if mergeable is not None:
@@ -72,17 +123,20 @@ class PolymarketDataClient:
72
123
  return [Position(**pos) for pos in response.json()]
73
124
 
74
125
  def get_trades(
75
- self,
76
- limit: int = 100,
77
- offset: int = 0,
78
- taker_only: bool = True,
79
- filter_type: Optional[Literal["CASH", "TOKENS"]] = None,
80
- filter_amount: Optional[float] = None,
81
- condition_id: Optional[str] = None,
82
- user: Optional[str] = None,
83
- side: Optional[Literal["BUY", "SELL"]] = None,
126
+ self,
127
+ limit: int = 100,
128
+ offset: int = 0,
129
+ taker_only: bool = True,
130
+ filter_type: Optional[Literal["CASH", "TOKENS"]] = None,
131
+ filter_amount: Optional[
132
+ float
133
+ ] = None, # must be provided together with filter_type
134
+ condition_id: Optional[str | list[str]] = None,
135
+ event_id: Optional[int | list[int]] = None,
136
+ user: Optional[str] = None,
137
+ side: Optional[Literal["BUY", "SELL"]] = None,
84
138
  ) -> list[Trade]:
85
- params = {
139
+ params: dict[str, int | bool | float | str | list[str]] = {
86
140
  "limit": min(limit, 500),
87
141
  "offset": offset,
88
142
  "takerOnly": taker_only,
@@ -91,8 +145,14 @@ class PolymarketDataClient:
91
145
  params["filterType"] = filter_type
92
146
  if filter_amount:
93
147
  params["filterAmount"] = filter_amount
94
- if condition_id:
148
+ if isinstance(condition_id, str):
95
149
  params["market"] = condition_id
150
+ if isinstance(condition_id, list):
151
+ params["market"] = ",".join(condition_id)
152
+ if isinstance(event_id, str):
153
+ params["eventId"] = event_id
154
+ if isinstance(event_id, list):
155
+ params["eventId"] = [str(i) for i in event_id]
96
156
  if user:
97
157
  params["user"] = user
98
158
  if side:
@@ -103,23 +163,39 @@ class PolymarketDataClient:
103
163
  return [Trade(**trade) for trade in response.json()]
104
164
 
105
165
  def get_activity(
106
- self,
107
- user: str,
108
- limit: int = 100,
109
- offset: int = 0,
110
- condition_id: Optional[Union[str, list[str]]] = None,
111
- type: Optional[Union[Literal["TRADE", "SPLIT", "MERGE", "REDEEM", "REWARD", "CONVERSION"], list[Literal["TRADE", "SPLIT", "MERGE", "REDEEM", "REWARD", "CONVERSION"]]]] = None,
112
- start: Optional[datetime] = None,
113
- end: Optional[datetime] = None,
114
- side: Optional[Literal["BUY", "SELL"]] = None,
115
- sort_by: Literal["TIMESTAMP", "TOKENS", "CASH"] = "TIMESTAMP",
116
- sort_direction: Literal["ASC", "DESC"] = "DESC",
166
+ self,
167
+ user: EthAddress,
168
+ limit: int = 100,
169
+ offset: int = 0,
170
+ condition_id: Optional[Union[str, list[str]]] = None,
171
+ event_id: Optional[Union[int, list[int]]] = None,
172
+ type: Optional[
173
+ Union[
174
+ Literal["TRADE", "SPLIT", "MERGE", "REDEEM", "REWARD", "CONVERSION"],
175
+ list[
176
+ Literal["TRADE", "SPLIT", "MERGE", "REDEEM", "REWARD", "CONVERSION"]
177
+ ],
178
+ ]
179
+ ] = None,
180
+ start: Optional[datetime] = None,
181
+ end: Optional[datetime] = None,
182
+ side: Optional[Literal["BUY", "SELL"]] = None,
183
+ sort_by: Literal["TIMESTAMP", "TOKENS", "CASH"] = "TIMESTAMP",
184
+ sort_direction: Literal["ASC", "DESC"] = "DESC",
117
185
  ) -> list[Activity]:
118
- params = {"user": user, "limit": min(limit, 500), "offset": offset}
186
+ params: dict[str, str | list[str] | int] = {
187
+ "user": user,
188
+ "limit": min(limit, 500),
189
+ "offset": offset,
190
+ }
119
191
  if isinstance(condition_id, str):
120
192
  params["market"] = condition_id
121
193
  if isinstance(condition_id, list):
122
194
  params["market"] = ",".join(condition_id)
195
+ if isinstance(event_id, str):
196
+ params["eventId"] = event_id
197
+ if isinstance(event_id, list):
198
+ params["eventId"] = [str(i) for i in event_id]
123
199
  if isinstance(type, str):
124
200
  params["type"] = type
125
201
  if isinstance(type, list):
@@ -140,22 +216,25 @@ class PolymarketDataClient:
140
216
  return [Activity(**activity) for activity in response.json()]
141
217
 
142
218
  def get_holders(
143
- self,
144
- condition_id: str,
145
- limit: int = 20,
219
+ self,
220
+ condition_id: str,
221
+ limit: int = 500,
222
+ min_balance: int = 1,
146
223
  ) -> list[HolderResponse]:
147
- """Takes in a condition_id and returns a list of at most 20 top holders for each corresponding token_id."""
148
- params = {"market": condition_id, "limit": limit}
224
+ """Takes in a condition_id and returns top holders for each corresponding token_id."""
225
+ params: dict[str, int | str] = {
226
+ "market": condition_id,
227
+ "limit": limit,
228
+ "min_balance": min_balance,
229
+ }
149
230
  response = self.client.get(self._build_url("/holders"), params=params)
150
231
  response.raise_for_status()
151
- return [
152
- HolderResponse(**holder_data) for holder_data in response.json()
153
- ]
232
+ return [HolderResponse(**holder_data) for holder_data in response.json()]
154
233
 
155
234
  def get_value(
156
- self,
157
- user: str,
158
- condition_ids: Optional[Union[str, list[str]]] = None,
235
+ self,
236
+ user: EthAddress,
237
+ condition_ids: Optional[Union[str, list[str]]] = None,
159
238
  ) -> ValueResponse:
160
239
  """
161
240
  Get the current value of a user's position in a set of markets.
@@ -175,13 +254,67 @@ class PolymarketDataClient:
175
254
  response.raise_for_status()
176
255
  return ValueResponse(**response.json()[0])
177
256
 
257
+ def get_closed_positions(
258
+ self,
259
+ user: EthAddress,
260
+ condition_ids: Optional[Union[str, list[str]]] = None,
261
+ ) -> list[Position]:
262
+ """Get all closed positions."""
263
+ params = {"user": user}
264
+ if isinstance(condition_ids, str):
265
+ params["market"] = condition_ids
266
+ if isinstance(condition_ids, list):
267
+ params["market"] = ",".join(condition_ids)
268
+
269
+ response = self.client.get(self._build_url("/closed-positions"), params=params)
270
+ response.raise_for_status()
271
+ return [Position(**pos) for pos in response.json()]
272
+
273
+ def get_total_markets_traded(
274
+ self,
275
+ user: EthAddress,
276
+ ) -> int:
277
+ """Get the total number of markets a user has traded in."""
278
+ params = {"user": user}
279
+
280
+ response = self.client.get(self._build_url("/traded"), params=params)
281
+ response.raise_for_status()
282
+ return response.json()["traded"]
283
+
284
+ def get_open_interest(
285
+ self,
286
+ condition_ids: Optional[Union[str, list[str]]] = None,
287
+ ) -> list[MarketValue]:
288
+ """Get open interest."""
289
+ params = {}
290
+
291
+ if isinstance(condition_ids, str):
292
+ params["market"] = condition_ids
293
+ if isinstance(condition_ids, list):
294
+ params["market"] = ",".join(condition_ids)
295
+
296
+ response = self.client.get(self._build_url("/oi"), params=params)
297
+ response.raise_for_status()
298
+ return [MarketValue(**oi) for oi in response.json()]
299
+
300
+ def get_live_volume(
301
+ self,
302
+ event_id: int,
303
+ ) -> EventLiveVolume:
304
+ """Get live volume for a given event."""
305
+ params = {"id": str(event_id)}
306
+
307
+ response = self.client.get(self._build_url("/live-volume"), params=params)
308
+ response.raise_for_status()
309
+ return EventLiveVolume(**response.json()[0])
310
+
178
311
  # website endpoints
179
312
 
180
313
  def get_pnl(
181
- self,
182
- user: EthAddress,
183
- period: Literal["all", "1m", "1w", "1d"] = "all",
184
- frequency: Literal["1h", "3h", "12h", "1d"] = "1h",
314
+ self,
315
+ user: EthAddress,
316
+ period: Literal["all", "1m", "1w", "1d"] = "all",
317
+ frequency: Literal["1h", "3h", "12h", "1d"] = "1h",
185
318
  ) -> list[TimeseriesPoint]:
186
319
  """Get a user's PnL timeseries in the last day, week, month or all with a given frequency."""
187
320
  params = {
@@ -190,22 +323,36 @@ class PolymarketDataClient:
190
323
  "fidelity": frequency,
191
324
  }
192
325
 
193
- response = self.client.get("https://user-pnl-api.polymarket.com/user-pnl", params=params)
326
+ response = self.client.get(
327
+ "https://user-pnl-api.polymarket.com/user-pnl", params=params
328
+ )
194
329
  response.raise_for_status()
195
330
  return [TimeseriesPoint(**point) for point in response.json()]
196
331
 
197
- def get_user_metric(self, user: EthAddress, metric: Literal["profit", "volume"] = "profit", window: Literal["1d", "7d", "30d", "all"] = "all"):
332
+ def get_user_metric(
333
+ self,
334
+ user: EthAddress,
335
+ metric: Literal["profit", "volume"] = "profit",
336
+ window: Literal["1d", "7d", "30d", "all"] = "all",
337
+ ):
198
338
  """Get a user's overall profit or volume in the last day, week, month or all."""
199
- params = {
339
+ params: dict[str, int | str] = {
200
340
  "address": user,
201
341
  "window": window,
202
342
  "limit": 1,
203
343
  }
204
- response = self.client.get("https://lb-api.polymarket.com/" + metric, params=params)
344
+ response = self.client.get(
345
+ "https://lb-api.polymarket.com/" + metric, params=params
346
+ )
205
347
  response.raise_for_status()
206
348
  return UserMetric(**response.json()[0])
207
349
 
208
- def get_leaderboard_user_rank(self, user: EthAddress, metric: Literal["profit", "volume"] = "profit", window: Literal["1d", "7d", "30d", "all"] = "all"):
350
+ def get_leaderboard_user_rank(
351
+ self,
352
+ user: EthAddress,
353
+ metric: Literal["profit", "volume"] = "profit",
354
+ window: Literal["1d", "7d", "30d", "all"] = "all",
355
+ ):
209
356
  """Get a user's rank on the leaderboard by profit or volume."""
210
357
  params = {
211
358
  "address": user,
@@ -216,17 +363,23 @@ class PolymarketDataClient:
216
363
  response.raise_for_status()
217
364
  return UserRank(**response.json()[0])
218
365
 
219
- def get_leaderboard_top_users(self, metric: Literal["profit", "volume"] = "profit", window: Literal["1d", "7d", "30d", "all"] = "all", limit: int = 100):
366
+ def get_leaderboard_top_users(
367
+ self,
368
+ metric: Literal["profit", "volume"] = "profit",
369
+ window: Literal["1d", "7d", "30d", "all"] = "all",
370
+ limit: int = 100,
371
+ ):
220
372
  """Get the leaderboard of the top at most 100 users by profit or volume."""
221
373
  params = {
222
374
  "window": window,
223
375
  "limit": limit,
224
376
  }
225
- response = self.client.get("https://lb-api.polymarket.com/" + metric, params=params)
377
+ response = self.client.get(
378
+ "https://lb-api.polymarket.com/" + metric, params=params
379
+ )
226
380
  response.raise_for_status()
227
381
  return [UserMetric(**user) for user in response.json()]
228
382
 
229
-
230
383
  def __enter__(self):
231
384
  return self
232
385