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

@@ -48,6 +48,7 @@ from ..utilities.endpoints import (
48
48
  DELETE_API_KEY,
49
49
  DERIVE_API_KEY,
50
50
  GET_API_KEYS,
51
+ GET_FEE_RATE,
51
52
  GET_LAST_TRADE_PRICE,
52
53
  GET_LAST_TRADES_PRICES,
53
54
  GET_MARKET,
@@ -70,6 +71,7 @@ from ..utilities.endpoints import (
70
71
  TRADES,
71
72
  )
72
73
  from ..utilities.exceptions import (
74
+ InvalidFeeRateError,
73
75
  InvalidPriceError,
74
76
  InvalidTickSizeError,
75
77
  LiquidityError,
@@ -111,6 +113,7 @@ class PolymarketClobClient:
111
113
  # local cache
112
114
  self.__tick_sizes = {}
113
115
  self.__neg_risk = {}
116
+ self.__fee_rates = {}
114
117
 
115
118
  def _build_url(self, endpoint: str) -> str:
116
119
  return urljoin(self.base_url, endpoint)
@@ -183,6 +186,18 @@ class PolymarketClobClient:
183
186
 
184
187
  return self.__neg_risk[token_id]
185
188
 
189
+ def get_fee_rate_bps(self, token_id: str) -> int:
190
+ if token_id in self.__fee_rates:
191
+ return self.__fee_rates[token_id]
192
+
193
+ params = {"token_id": token_id}
194
+ response = self.client.get(self._build_url(GET_FEE_RATE), params=params)
195
+ response.raise_for_status()
196
+ fee_rate = response.json().get("base_fee") or 0
197
+ self.__fee_rates[token_id] = fee_rate
198
+
199
+ return fee_rate
200
+
186
201
  def __resolve_tick_size(
187
202
  self, token_id: str, tick_size: TickSize = None,
188
203
  ) -> TickSize:
@@ -195,6 +210,17 @@ class PolymarketClobClient:
195
210
  tick_size = min_tick_size
196
211
  return tick_size
197
212
 
213
+ def __resolve_fee_rate(
214
+ self, token_id: str, user_fee_rate: Optional[int] = None,
215
+ ) -> int:
216
+ market_fee_rate_bps = self.get_fee_rate_bps(token_id)
217
+ # If both fee rate on the market and the user supplied fee rate are non-zero, validate that they match
218
+ # else return the market fee rate
219
+ if market_fee_rate_bps > 0 and user_fee_rate is not None and user_fee_rate > 0 and user_fee_rate != market_fee_rate_bps:
220
+ msg = f"invalid user provided fee rate: ({user_fee_rate}), fee rate for the market must be {market_fee_rate_bps}"
221
+ raise InvalidFeeRateError(msg)
222
+ return market_fee_rate_bps
223
+
198
224
  def get_midpoint(self, token_id: str) -> Midpoint:
199
225
  """Get the mid-market price for the given token."""
200
226
  params = {"token_id": token_id}
@@ -406,6 +432,10 @@ class PolymarketClobClient:
406
432
  else self.get_neg_risk(order_args.token_id)
407
433
  )
408
434
 
435
+ # fee rate
436
+ fee_rate_bps = self.__resolve_fee_rate(order_args.token_id, order_args.fee_rate_bps)
437
+ order_args.fee_rate_bps = fee_rate_bps
438
+
409
439
  return self.builder.create_order(
410
440
  order_args,
411
441
  CreateOrderOptions(
@@ -416,7 +446,7 @@ class PolymarketClobClient:
416
446
 
417
447
  def post_order(self, order: SignedOrder, order_type: OrderType = OrderType.GTC) -> Optional[OrderPostResponse]:
418
448
  """Posts a SignedOrder."""
419
- body = order_to_json(order, self.creds.api_key, order_type)
449
+ body = order_to_json(order, self.creds.key, order_type)
420
450
  headers = create_level_2_headers(
421
451
  self.signer,
422
452
  self.creds,
@@ -444,7 +474,7 @@ class PolymarketClobClient:
444
474
 
445
475
  def post_orders(self, args: list[PostOrdersArgs]):
446
476
  """Posts multiple SignedOrders at once."""
447
- body = [order_to_json(arg.order, self.creds.api_key, arg.order_type) for arg in args]
477
+ body = [order_to_json(arg.order, self.creds.key, arg.order_type) for arg in args]
448
478
  headers = create_level_2_headers(
449
479
  self.signer,
450
480
  self.creds,
@@ -526,6 +556,10 @@ class PolymarketClobClient:
526
556
  else self.get_neg_risk(order_args.token_id)
527
557
  )
528
558
 
559
+ # fee rate
560
+ fee_rate_bps = self.__resolve_fee_rate(order_args.token_id, order_args.fee_rate_bps)
561
+ order_args.fee_rate_bps = fee_rate_bps
562
+
529
563
  return self.builder.create_market_order(
530
564
  order_args,
531
565
  CreateOrderOptions(
@@ -614,7 +648,7 @@ class PolymarketClobClient:
614
648
 
615
649
  def get_trades(
616
650
  self,
617
- condition_id: Keccak256 | None = None,
651
+ condition_id: Optional[Keccak256] = None,
618
652
  token_id: Optional[str] = None,
619
653
  trade_id: Optional[str] = None,
620
654
  before: Optional[datetime] = None,
@@ -1,11 +1,19 @@
1
1
  import json
2
+ import random
3
+ import string
2
4
  from datetime import datetime
3
- from typing import Optional, Union
5
+ from typing import Literal, Optional, Union
4
6
  from urllib.parse import urljoin
5
7
 
6
8
  import httpx
7
9
 
8
- from ..types.gamma_types import Event, GammaMarket, QueryEvent
10
+ from ..types.gamma_types import Event, EventList, GammaMarket
11
+
12
+
13
+ def generate_random_id(length=16):
14
+ characters = string.ascii_letters + string.digits
15
+ random_id = "".join(random.choices(characters, k=length))
16
+ return random_id
9
17
 
10
18
 
11
19
  class PolymarketGammaClient:
@@ -227,82 +235,94 @@ class PolymarketGammaClient:
227
235
 
228
236
  return events
229
237
 
230
- def search_events(self, query: str, active: bool = True) -> list[QueryEvent]:
231
- """Search for events by query."""
232
- params = {"q": query, "events_status": "active" if active else "resolved"}
233
- response = self.client.get("https://polymarket.com/api/events/global", params=params)
238
+ def search_events(
239
+ self,
240
+ query: str,
241
+ active: bool = True,
242
+ status: Optional[Literal["active", "resolved"]] = "active",
243
+ sort: Literal["volume", "volume_24hr", "liquidity", "start_date", "end_date", "competitive"] = "volume_24hr",
244
+ page: int = 1,
245
+ limit_per_type: int = 50, # max is 50
246
+ ) -> EventList:
247
+ """Search for events by query. Should emulate the website search function."""
248
+ params = {"q": query,"page": page, "limit_per_type": limit_per_type, "events_status": status, "active": active, "presets": "EventsTitle"}
249
+ if sort:
250
+ params["sort"] = sort
251
+ if sort == "end_date":
252
+ params["ascending"] = "true"
253
+ response = self.client.get(self._build_url("/public-search"), params=params)
234
254
  response.raise_for_status()
235
- return [QueryEvent(**event) for event in response.json()["events"]]
255
+ return EventList(**response.json())
236
256
 
237
- def grok_market_summary(self, condition_id: str):
238
- market = self.get_markets(condition_ids=[condition_id])[0]
257
+ def grok_event_summary(self, event_slug: str):
258
+ json_payload = {
259
+ "id": generate_random_id(),
260
+ "messages": [{"role": "user", "content": "", "parts": []}],
261
+ }
239
262
 
240
263
  params = {
241
- "marketName": market.group_item_title,
242
- "eventTitle": market.question,
243
- "odds": market.outcome_prices[0],
244
- "marketDescription": market.description,
245
- "isNegRisk": market.neg_risk,
264
+ "prompt": event_slug,
246
265
  }
247
266
 
248
- with self.client.stream(method="GET", url="https://polymarket.com/api/grok/market-explanation", params=params) as stream:
267
+ with self.client.stream(
268
+ method="POST",
269
+ url="https://polymarket.com/api/grok/event-summary",
270
+ params=params,
271
+ json=json_payload,
272
+ ) as stream:
249
273
  messages = []
250
274
  citations = []
251
- for line in stream.iter_lines():
252
- if line:
253
- line_str = line
254
- if line_str.startswith("data: "):
255
- json_part = line_str[len("data: "):]
256
- try:
257
- data = json.loads(json_part)
258
- # Extract content if present
259
- content = data.get("choices", [{}])[0].get("delta", {}).get("content", "")
260
- if content:
261
- messages.append(content)
262
- print(content, end="") # Stream content
263
- # Extract citations if present
264
- if "citations" in data:
265
- citations.extend(data["citations"])
266
- except json.JSONDecodeError:
267
- pass
275
+ seen_urls = set()
276
+
277
+ for line_bytes in stream.iter_lines():
278
+ line = (
279
+ line_bytes.decode() if isinstance(line_bytes, bytes) else line_bytes
280
+ )
281
+ if line.startswith("__SOURCES__:"):
282
+ sources_json_str = line[len("__SOURCES__:") :]
283
+ try:
284
+ sources_obj = json.loads(sources_json_str)
285
+ for source in sources_obj.get("sources", []):
286
+ url = source.get("url")
287
+ if url and url not in seen_urls:
288
+ citations.append(source)
289
+ seen_urls.add(url)
290
+ except json.JSONDecodeError:
291
+ pass
292
+ else:
293
+ messages.append(line)
294
+ print(line, end="") # or handle message text as needed
268
295
 
269
- # After streaming, print citations if any
296
+ # After reading streamed lines:
270
297
  if citations:
271
- print("\n\nCitations:")
272
- for cite in citations:
273
- print(f"- {cite}")
298
+ print("\n\nSources:")
299
+ for source in citations:
300
+ print(f"- {source.get('url', 'Unknown URL')}")
274
301
 
275
- def grok_event_summary(self, event_slug: str):
276
- params = {
277
- "prompt": event_slug,
302
+ def grok_election_market_explanation(self, candidate_name: str, election_title: str):
303
+ text = f"Provide candidate information for {candidate_name} in the {election_title} on Polymarket."
304
+ json_payload = {
305
+ "id": generate_random_id(),
306
+ "messages": [
307
+ {
308
+ "role": "user",
309
+ "content": text,
310
+ "parts": [{"type": "text", "text": text}],
311
+ },
312
+ ],
278
313
  }
279
314
 
280
- with self.client.stream(method="GET", url="https://polymarket.com/api/grok/event-summary", params=params) as stream:
281
- messages = []
282
- citations = []
283
- for line in stream.iter_lines():
284
- if line:
285
- line_str = line
286
- if line_str.startswith("data: "):
287
- json_part = line_str[len("data: "):]
288
- try:
289
- data = json.loads(json_part)
290
- # Extract content if present
291
- content = data.get("choices", [{}])[0].get("delta", {}).get("content", "")
292
- if content:
293
- messages.append(content)
294
- print(content, end="") # Stream content
295
- # Extract citations if present
296
- if "citations" in data:
297
- citations.extend(data["citations"])
298
- except json.JSONDecodeError:
299
- pass
315
+ response = self.client.post(
316
+ url="https://polymarket.com/api/grok/election-market-explanation",
317
+ json=json_payload,
318
+ )
319
+ response.raise_for_status()
300
320
 
301
- # After streaming, print citations if any
302
- if citations:
303
- print("\n\nCitations:")
304
- for cite in citations:
305
- print(f"- {cite}")
321
+ parts = [p.strip() for p in response.text.split("**") if p.strip()]
322
+ for i, part in enumerate(parts):
323
+ if ":" in part and i != 0:
324
+ print()
325
+ print(part)
306
326
 
307
327
  def __enter__(self):
308
328
  return self
@@ -0,0 +1,182 @@
1
+ from typing import Literal, Optional
2
+
3
+ from gql import Client, gql
4
+ from gql.transport.httpx import HTTPXAsyncTransport
5
+ from graphql import GraphQLInputObjectType, GraphQLObjectType
6
+
7
+ from ..utilities.config import GRAPHQL_ENDPOINTS
8
+
9
+
10
+ class PolymarketGraphQLClient:
11
+ def __init__(self,
12
+ endpoint_name: Literal[
13
+ "activity_subgraph",
14
+ "fpmm_subgraph",
15
+ "open_interest_subgraph",
16
+ "orderbook_subgraph",
17
+ "pnl_subgraph",
18
+ "positions_subgraph",
19
+ "sports_oracle_subgraph",
20
+ "wallet_subgraph",
21
+ ]):
22
+ if endpoint_name not in GRAPHQL_ENDPOINTS:
23
+ msg = f"Invalid endpoint name: {endpoint_name}. Must be one of {list(GRAPHQL_ENDPOINTS.keys())}"
24
+ raise ValueError(msg)
25
+ endpoint_url = GRAPHQL_ENDPOINTS[endpoint_name]
26
+ self.transport = HTTPXAsyncTransport(url=endpoint_url)
27
+ self.client = Client(transport=self.transport, fetch_schema_from_transport=True)
28
+ self.schema = None
29
+ self.object_types = []
30
+ self.query_fields = []
31
+ self.subscription_fields = []
32
+ self.filter_input_types = []
33
+
34
+ async def _init_schema(self):
35
+ async with self.client as session:
36
+ self.schema = session.schema
37
+
38
+ async def _get_query_fields(self):
39
+ if self.query_fields:
40
+ return self.query_fields
41
+ if not self.schema:
42
+ await self._init_schema()
43
+ self.query_fields = [field for field in self.schema.type_map["Query"].fields if not field.startswith("_")]
44
+
45
+ return self.query_fields
46
+
47
+ async def _get_subscription_fields(self):
48
+ if self.subscription_fields:
49
+ return self.subscription_fields
50
+ if not self.schema:
51
+ await self._init_schema()
52
+ if "Subscription" in self.schema.type_map:
53
+ self.subscription_fields = [field for field in self.schema.type_map["Subscription"].fields if not field.startswith("_")]
54
+ else:
55
+ self.subscription_fields = []
56
+ return self.subscription_fields
57
+
58
+ async def _get_object_types(self):
59
+ if self.object_types:
60
+ return self.object_types
61
+ if not self.schema:
62
+ await self._init_schema()
63
+ for object_name, object in self.schema.type_map.items():
64
+ if type(object) is GraphQLObjectType and not object_name.startswith("_"):
65
+ self.object_types.append(object_name)
66
+ return self.object_types
67
+
68
+ async def _get_filter_input_types(self):
69
+ if self.filter_input_types:
70
+ return self.filter_input_types
71
+ if not self.schema:
72
+ await self._init_schema()
73
+ for object_name, object in self.schema.type_map.items():
74
+ if isinstance(object, GraphQLInputObjectType) and not object_name.startswith("_"):
75
+ self.filter_input_types.append(object_name)
76
+ return self.filter_input_types
77
+
78
+ async def list_queries(self):
79
+ if self.query_fields:
80
+ return self.query_fields
81
+ return await self._get_query_fields()
82
+
83
+ async def list_subscriptions(self):
84
+ if self.subscription_fields:
85
+ return self.subscription_fields
86
+ return await self._get_subscription_fields()
87
+
88
+ async def list_object_types(self):
89
+ if self.object_types:
90
+ return self.object_types
91
+ return await self._get_object_types()
92
+
93
+ async def list_filter_input_types(self):
94
+ if self.filter_input_types:
95
+ return self.filter_input_types
96
+ return await self._get_filter_input_types()
97
+
98
+ async def get_fields(self, object_name: str):
99
+ if self.schema is None:
100
+ await self._init_schema()
101
+ if object_name not in self.schema.type_map:
102
+ msg = "Invalid object name"
103
+ raise ValueError(msg)
104
+ return list(self.schema.type_map[object_name].fields.keys())
105
+
106
+ async def query(
107
+ self,
108
+ endpoint: str,
109
+ fields: list[str],
110
+ filters: Optional[dict] = None,
111
+ relationships: Optional[dict] = None,
112
+ ):
113
+ if not self.schema:
114
+ await self._init_schema()
115
+ if not self.query_fields:
116
+ await self._get_query_fields()
117
+ if not self.object_types:
118
+ await self._get_object_types()
119
+
120
+ if endpoint not in self.query_fields:
121
+ msg = f"Invalid endpoint: {endpoint}"
122
+ raise ValueError(msg)
123
+
124
+ endpoint_field = self.schema.type_map["Query"].fields[endpoint]
125
+ required_args = [
126
+ arg_name for arg_name, arg in endpoint_field.args.items()
127
+ if arg.type.to_kwargs().get("required", False)
128
+ ]
129
+ missing_args = [arg for arg in required_args if not (filters and arg in filters)]
130
+ if missing_args:
131
+ msg = f"Missing required argument(s) for '{endpoint}': {', '.join(missing_args)}"
132
+ raise ValueError(msg)
133
+
134
+ def build_selection(fields, relationships):
135
+ selections = []
136
+ for field in fields:
137
+ if relationships and field in relationships:
138
+ subfields = relationships[field]
139
+ selections.append(f"{field} {{ {' '.join(subfields)} }}")
140
+ else:
141
+ selections.append(field)
142
+ return " ".join(selections)
143
+
144
+ def build_args(filters):
145
+ """Build GraphQL arguments, handling both simple and complex where clauses."""
146
+ if not filters:
147
+ return ""
148
+
149
+ arg_strs = []
150
+ for k, v in filters.items():
151
+ if k == "where" and isinstance(v, dict):
152
+ # Handle complex where clause
153
+ where_conditions = []
154
+ for where_key, where_value in v.items():
155
+ if isinstance(where_value, str):
156
+ where_conditions.append(f"{where_key}: {where_value}")
157
+ else:
158
+ where_conditions.append(f"{where_key}: {where_value}")
159
+ where_str = "{" + ", ".join(where_conditions) + "}"
160
+ arg_strs.append(f"{k}: {where_str}")
161
+ # Handle simple key-value filters
162
+ elif isinstance(v, str):
163
+ arg_strs.append(f'{k}: "{v}"')
164
+ else:
165
+ arg_strs.append(f"{k}: {v}")
166
+
167
+ return "(" + ", ".join(arg_strs) + ")"
168
+
169
+ selection_set = build_selection(fields, relationships)
170
+ args = build_args(filters)
171
+
172
+ query_str = f"""
173
+ query {{
174
+ {endpoint}{args} {{
175
+ {selection_set}
176
+ }}
177
+ }}
178
+ """
179
+ print(query_str)
180
+ async with self.client as session:
181
+ result = await session.execute(gql(query_str))
182
+ return result
@@ -77,7 +77,7 @@ class PolymarketWeb3Client:
77
77
  abi_element_identifier="redeemPositions",
78
78
  args=[condition_id, amounts],
79
79
  )
80
- def _encode_convert(self, neg_risk_market_id: Keccak256, index_set: int, amount: int):
80
+ def _encode_convert(self, neg_risk_market_id: Keccak256, index_set: int, amount: int) -> str:
81
81
  return self.neg_risk_adapter.encode_abi(
82
82
  abi_element_identifier="convertPositions",
83
83
  args=[neg_risk_market_id, index_set, amount],