polymarket-apis 0.2.2__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 (40) hide show
  1. polymarket_apis/__init__.py +2 -0
  2. polymarket_apis/clients/__init__.py +0 -0
  3. polymarket_apis/clients/clob_client.py +730 -0
  4. polymarket_apis/clients/data_client.py +234 -0
  5. polymarket_apis/clients/gamma_client.py +311 -0
  6. polymarket_apis/clients/web3_client.py +261 -0
  7. polymarket_apis/clients/websockets_client.py +131 -0
  8. polymarket_apis/types/__init__.py +0 -0
  9. polymarket_apis/types/clob_types.py +494 -0
  10. polymarket_apis/types/common.py +49 -0
  11. polymarket_apis/types/data_types.py +161 -0
  12. polymarket_apis/types/gamma_types.py +313 -0
  13. polymarket_apis/types/websockets_types.py +191 -0
  14. polymarket_apis/utilities/__init__.py +0 -0
  15. polymarket_apis/utilities/config.py +36 -0
  16. polymarket_apis/utilities/constants.py +26 -0
  17. polymarket_apis/utilities/endpoints.py +37 -0
  18. polymarket_apis/utilities/exceptions.py +11 -0
  19. polymarket_apis/utilities/headers.py +54 -0
  20. polymarket_apis/utilities/order_builder/__init__.py +0 -0
  21. polymarket_apis/utilities/order_builder/builder.py +240 -0
  22. polymarket_apis/utilities/order_builder/helpers.py +61 -0
  23. polymarket_apis/utilities/signing/__init__.py +0 -0
  24. polymarket_apis/utilities/signing/eip712.py +28 -0
  25. polymarket_apis/utilities/signing/hmac.py +20 -0
  26. polymarket_apis/utilities/signing/model.py +8 -0
  27. polymarket_apis/utilities/signing/signer.py +25 -0
  28. polymarket_apis/utilities/web3/__init__.py +0 -0
  29. polymarket_apis/utilities/web3/abis/CTFExchange.json +1851 -0
  30. polymarket_apis/utilities/web3/abis/ConditionalTokens.json +705 -0
  31. polymarket_apis/utilities/web3/abis/NegRiskAdapter.json +999 -0
  32. polymarket_apis/utilities/web3/abis/NegRiskCtfExchange.json +1856 -0
  33. polymarket_apis/utilities/web3/abis/ProxyWalletFactory.json +319 -0
  34. polymarket_apis/utilities/web3/abis/UChildERC20Proxy.json +1438 -0
  35. polymarket_apis/utilities/web3/abis/__init__.py +0 -0
  36. polymarket_apis/utilities/web3/abis/custom_contract_errors.py +31 -0
  37. polymarket_apis/utilities/web3/helpers.py +8 -0
  38. polymarket_apis-0.2.2.dist-info/METADATA +18 -0
  39. polymarket_apis-0.2.2.dist-info/RECORD +40 -0
  40. polymarket_apis-0.2.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,240 @@
1
+ from py_order_utils.builders import OrderBuilder as UtilsOrderBuilder
2
+ from py_order_utils.model import (
3
+ BUY as UTILS_BUY,
4
+ )
5
+ from py_order_utils.model import (
6
+ EOA,
7
+ OrderData,
8
+ SignedOrder,
9
+ )
10
+ from py_order_utils.model import (
11
+ SELL as UTILS_SELL,
12
+ )
13
+ from py_order_utils.signer import Signer as UtilsSigner
14
+
15
+ from ...types.clob_types import (
16
+ CreateOrderOptions,
17
+ MarketOrderArgs,
18
+ OrderArgs,
19
+ OrderSummary,
20
+ OrderType,
21
+ RoundConfig,
22
+ TickSize,
23
+ )
24
+ from ..config import get_contract_config
25
+ from ..constants import BUY, SELL
26
+ from ..exceptions import LiquidityError
27
+ from ..signing.signer import Signer
28
+ from .helpers import (
29
+ decimal_places,
30
+ round_down,
31
+ round_normal,
32
+ round_up,
33
+ to_token_decimals,
34
+ )
35
+
36
+ ROUNDING_CONFIG: dict[TickSize, RoundConfig] = {
37
+ "0.1": RoundConfig(price=1, size=2, amount=3),
38
+ "0.01": RoundConfig(price=2, size=2, amount=4),
39
+ "0.001": RoundConfig(price=3, size=2, amount=5),
40
+ "0.0001": RoundConfig(price=4, size=2, amount=6),
41
+ }
42
+
43
+
44
+ class OrderBuilder:
45
+ def __init__(self, signer: Signer, sig_type=None, funder=None):
46
+ self.signer = signer
47
+
48
+ # Signature type used sign orders, defaults to EOA type
49
+ self.sig_type = sig_type if sig_type is not None else EOA
50
+
51
+ # Address which holds funds to be used.
52
+ # Used for Polymarket proxy wallets and other smart contract wallets
53
+ # Defaults to the address of the signer
54
+ self.funder = funder if funder is not None else self.signer.address()
55
+
56
+ def get_order_amounts(
57
+ self, side: str, size: float, price: float, round_config: RoundConfig,
58
+ ):
59
+ raw_price = round_normal(price, round_config.price)
60
+
61
+ if side == BUY:
62
+ raw_taker_amt = round_down(size, round_config.size)
63
+
64
+ raw_maker_amt = raw_taker_amt * raw_price
65
+ if decimal_places(raw_maker_amt) > round_config.amount:
66
+ raw_maker_amt = round_up(raw_maker_amt, round_config.amount + 4)
67
+ if decimal_places(raw_maker_amt) > round_config.amount:
68
+ raw_maker_amt = round_down(raw_maker_amt, round_config.amount)
69
+
70
+ maker_amount = to_token_decimals(raw_maker_amt)
71
+ taker_amount = to_token_decimals(raw_taker_amt)
72
+
73
+ return UTILS_BUY, maker_amount, taker_amount
74
+ if side == SELL:
75
+ raw_maker_amt = round_down(size, round_config.size)
76
+
77
+ raw_taker_amt = raw_maker_amt * raw_price
78
+ if decimal_places(raw_taker_amt) > round_config.amount:
79
+ raw_taker_amt = round_up(raw_taker_amt, round_config.amount + 4)
80
+ if decimal_places(raw_taker_amt) > round_config.amount:
81
+ raw_taker_amt = round_down(raw_taker_amt, round_config.amount)
82
+
83
+ maker_amount = to_token_decimals(raw_maker_amt)
84
+ taker_amount = to_token_decimals(raw_taker_amt)
85
+
86
+ return UTILS_SELL, maker_amount, taker_amount
87
+ msg = f"order_args.side must be '{BUY}' or '{SELL}'"
88
+ raise ValueError(msg)
89
+
90
+ def get_market_order_amounts(
91
+ self, side: str, amount: float, price: float, round_config: RoundConfig,
92
+ ):
93
+ raw_price = round_normal(price, round_config.price)
94
+
95
+ if side == BUY:
96
+ raw_maker_amt = round_down(amount, round_config.size)
97
+ raw_taker_amt = raw_maker_amt / raw_price
98
+ if decimal_places(raw_taker_amt) > round_config.amount:
99
+ raw_taker_amt = round_up(raw_taker_amt, round_config.amount + 4)
100
+ if decimal_places(raw_taker_amt) > round_config.amount:
101
+ raw_taker_amt = round_down(raw_taker_amt, round_config.amount)
102
+
103
+ maker_amount = to_token_decimals(raw_maker_amt)
104
+ taker_amount = to_token_decimals(raw_taker_amt)
105
+
106
+ return UTILS_BUY, maker_amount, taker_amount
107
+
108
+ if side == SELL:
109
+ raw_maker_amt = round_down(amount, round_config.size)
110
+
111
+ raw_taker_amt = raw_maker_amt * raw_price
112
+ if decimal_places(raw_taker_amt) > round_config.amount:
113
+ raw_taker_amt = round_up(raw_taker_amt, round_config.amount + 4)
114
+ if decimal_places(raw_taker_amt) > round_config.amount:
115
+ raw_taker_amt = round_down(raw_taker_amt, round_config.amount)
116
+
117
+ maker_amount = to_token_decimals(raw_maker_amt)
118
+ taker_amount = to_token_decimals(raw_taker_amt)
119
+
120
+ return UTILS_SELL, maker_amount, taker_amount
121
+ msg = f"order_args.side must be '{BUY}' or '{SELL}'"
122
+ raise ValueError(msg)
123
+
124
+ def create_order(
125
+ self, order_args: OrderArgs, options: CreateOrderOptions,
126
+ ) -> SignedOrder:
127
+ """Creates and signs an order."""
128
+ side, maker_amount, taker_amount = self.get_order_amounts(
129
+ order_args.side,
130
+ order_args.size,
131
+ order_args.price,
132
+ ROUNDING_CONFIG[options.tick_size],
133
+ )
134
+
135
+ data = OrderData(
136
+ maker=self.funder,
137
+ taker=order_args.taker,
138
+ tokenId=order_args.token_id,
139
+ makerAmount=str(maker_amount),
140
+ takerAmount=str(taker_amount),
141
+ side=side,
142
+ feeRateBps=str(order_args.fee_rate_bps),
143
+ nonce=str(order_args.nonce),
144
+ signer=self.signer.address(),
145
+ expiration=str(order_args.expiration),
146
+ signatureType=self.sig_type,
147
+ )
148
+
149
+ contract_config = get_contract_config(
150
+ self.signer.get_chain_id(), options.neg_risk,
151
+ )
152
+
153
+ order_builder = UtilsOrderBuilder(
154
+ contract_config.exchange,
155
+ self.signer.get_chain_id(),
156
+ UtilsSigner(key=self.signer.private_key),
157
+ )
158
+
159
+ return order_builder.build_signed_order(data)
160
+
161
+ def create_market_order(
162
+ self, order_args: MarketOrderArgs, options: CreateOrderOptions,
163
+ ) -> SignedOrder:
164
+ """Creates and signs a market order."""
165
+ side, maker_amount, taker_amount = self.get_market_order_amounts(
166
+ order_args.side,
167
+ order_args.amount,
168
+ order_args.price,
169
+ ROUNDING_CONFIG[options.tick_size],
170
+ )
171
+
172
+ data = OrderData(
173
+ maker=self.funder,
174
+ taker=order_args.taker,
175
+ tokenId=order_args.token_id,
176
+ makerAmount=str(maker_amount),
177
+ takerAmount=str(taker_amount),
178
+ side=side,
179
+ feeRateBps=str(order_args.fee_rate_bps),
180
+ nonce=str(order_args.nonce),
181
+ signer=self.signer.address(),
182
+ expiration="0",
183
+ signatureType=self.sig_type,
184
+ )
185
+
186
+ contract_config = get_contract_config(
187
+ self.signer.get_chain_id(), options.neg_risk,
188
+ )
189
+
190
+ order_builder = UtilsOrderBuilder(
191
+ contract_config.exchange,
192
+ self.signer.get_chain_id(),
193
+ UtilsSigner(key=self.signer.private_key),
194
+ )
195
+
196
+ return order_builder.build_signed_order(data)
197
+
198
+ def calculate_buy_market_price(
199
+ self,
200
+ asks: list[OrderSummary], # expected to be sorted from worst to best price (high to low)
201
+ amount_to_match: float, # in usdc
202
+ order_type: OrderType,
203
+ ) -> float:
204
+ if not asks:
205
+ msg = "No ask orders available"
206
+ raise LiquidityError(msg)
207
+
208
+ sum = 0
209
+ for p in reversed(asks):
210
+ sum += float(p.size) * float(p.price)
211
+ if sum >= amount_to_match:
212
+ return float(p.price)
213
+
214
+ if order_type == OrderType.FOK:
215
+ msg = "no match"
216
+ raise ValueError(msg)
217
+
218
+ return float(asks[0].price)
219
+
220
+ def calculate_sell_market_price(
221
+ self,
222
+ bids: list[OrderSummary], # expected to be sorted from worst to best price (low to high)
223
+ amount_to_match: float, # in usdc
224
+ order_type: OrderType,
225
+ ) -> float:
226
+ if not bids:
227
+ msg = "No bid orders available"
228
+ raise LiquidityError(msg)
229
+
230
+ sum = 0
231
+ for p in reversed(bids):
232
+ sum += float(p.size)
233
+ if sum >= amount_to_match:
234
+ return float(p.price)
235
+
236
+ if order_type == OrderType.FOK:
237
+ msg = "no match"
238
+ raise ValueError(msg)
239
+
240
+ return float(bids[0].price)
@@ -0,0 +1,61 @@
1
+ import hashlib
2
+ from decimal import Decimal
3
+ from math import ceil, floor
4
+
5
+ from ...types.clob_types import OrderBookSummary, TickSize
6
+
7
+
8
+ def round_down(x: float, sig_digits: int) -> float:
9
+ return floor(x * (10**sig_digits)) / (10**sig_digits)
10
+
11
+
12
+ def round_normal(x: float, sig_digits: int) -> float:
13
+ return round(x * (10**sig_digits)) / (10**sig_digits)
14
+
15
+
16
+ def round_up(x: float, sig_digits: int) -> float:
17
+ return ceil(x * (10**sig_digits)) / (10**sig_digits)
18
+
19
+
20
+ def to_token_decimals(x: float) -> int:
21
+ f = (10**6) * x
22
+ if decimal_places(f) > 0:
23
+ f = round_normal(f, 0)
24
+ return int(f)
25
+
26
+
27
+ def decimal_places(x: float) -> int:
28
+ """
29
+ Returns the number of decimal places in a float.
30
+
31
+ Assumes x is always a finite, non-special value (not NaN or Infinity).
32
+ """
33
+ exponent = Decimal(str(x)).as_tuple().exponent
34
+ if not isinstance(exponent, int):
35
+ msg = "Input must be a finite float."
36
+ raise TypeError(msg)
37
+ return abs(exponent)
38
+
39
+
40
+
41
+ def generate_orderbook_summary_hash(orderbook: OrderBookSummary) -> str:
42
+ """Compute hash while forcing empty string for hash field."""
43
+ server_hash = orderbook.hash
44
+ orderbook.hash = ""
45
+ computed_hash = hashlib.sha1(
46
+ str(orderbook.model_dump_json(by_alias=True)).encode("utf-8"),
47
+ ).hexdigest()
48
+ orderbook.hash = server_hash
49
+ return computed_hash
50
+
51
+
52
+ def order_to_json(order, owner, order_type) -> dict:
53
+ return {"order": order.dict(), "owner": owner, "orderType": order_type.value}
54
+
55
+
56
+ def is_tick_size_smaller(a: TickSize, b: TickSize) -> bool:
57
+ return float(a) < float(b)
58
+
59
+
60
+ def price_valid(price: float, tick_size: TickSize) -> bool:
61
+ return float(tick_size) <= price <= 1 - float(tick_size)
File without changes
@@ -0,0 +1,28 @@
1
+ from eth_utils import keccak
2
+ from poly_eip712_structs import make_domain
3
+ from py_order_utils.utils import prepend_zx
4
+
5
+ from ..signing.model import ClobAuth
6
+ from ..signing.signer import Signer
7
+
8
+ CLOB_DOMAIN_NAME = "ClobAuthDomain"
9
+ CLOB_VERSION = "1"
10
+ MSG_TO_SIGN = "This message attests that I control the given wallet"
11
+
12
+
13
+ def get_clob_auth_domain(chain_id: int):
14
+ return make_domain(name=CLOB_DOMAIN_NAME, version=CLOB_VERSION, chainId=chain_id)
15
+
16
+
17
+ def sign_clob_auth_message(signer: Signer, timestamp: int, nonce: int) -> str:
18
+ clob_auth_msg = ClobAuth(
19
+ address=signer.address(),
20
+ timestamp=str(timestamp),
21
+ nonce=nonce,
22
+ message=MSG_TO_SIGN,
23
+ )
24
+ chain_id = signer.get_chain_id()
25
+ auth_struct_hash = prepend_zx(
26
+ keccak(clob_auth_msg.signable_bytes(get_clob_auth_domain(chain_id))).hex(),
27
+ )
28
+ return prepend_zx(signer.sign(auth_struct_hash))
@@ -0,0 +1,20 @@
1
+ import base64
2
+ import hashlib
3
+ import hmac
4
+
5
+
6
+ def build_hmac_signature(
7
+ secret: str, timestamp: str, method: str, request_path: str, body=None,
8
+ ):
9
+ """Creates an HMAC signature by signing a payload with the secret."""
10
+ base64_secret = base64.urlsafe_b64decode(secret)
11
+ message = str(timestamp) + str(method) + str(request_path)
12
+ if body:
13
+ # NOTE: Necessary to replace single quotes with double quotes
14
+ # to generate the same hmac message as go and typescript
15
+ message += str(body).replace("'", '"')
16
+
17
+ h = hmac.new(base64_secret, bytes(message, "utf-8"), hashlib.sha256)
18
+
19
+ # ensure base64 encoded
20
+ return (base64.urlsafe_b64encode(h.digest())).decode("utf-8")
@@ -0,0 +1,8 @@
1
+ from poly_eip712_structs import Address, EIP712Struct, String, Uint
2
+
3
+
4
+ class ClobAuth(EIP712Struct):
5
+ address = Address()
6
+ timestamp = String()
7
+ nonce = Uint()
8
+ message = String()
@@ -0,0 +1,25 @@
1
+ from eth_account import Account
2
+
3
+
4
+ class Signer:
5
+ def __init__(self, private_key: str, chain_id: int):
6
+ if private_key is None:
7
+ msg = "private_key must not be None"
8
+ raise ValueError(msg)
9
+ if chain_id is None:
10
+ msg = "chain_id must not be None"
11
+ raise ValueError(msg)
12
+
13
+ self.private_key = private_key
14
+ self.account = Account.from_key(private_key)
15
+ self.chain_id = chain_id
16
+
17
+ def address(self):
18
+ return self.account.address
19
+
20
+ def get_chain_id(self):
21
+ return self.chain_id
22
+
23
+ def sign(self, message_hash):
24
+ """Signs a message hash."""
25
+ return Account.unsafe_sign_hash(message_hash, self.private_key).signature.hex()
File without changes