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.
- polymarket_apis/__init__.py +2 -0
- polymarket_apis/clients/__init__.py +0 -0
- polymarket_apis/clients/clob_client.py +730 -0
- polymarket_apis/clients/data_client.py +234 -0
- polymarket_apis/clients/gamma_client.py +311 -0
- polymarket_apis/clients/web3_client.py +261 -0
- polymarket_apis/clients/websockets_client.py +131 -0
- polymarket_apis/types/__init__.py +0 -0
- polymarket_apis/types/clob_types.py +494 -0
- polymarket_apis/types/common.py +49 -0
- polymarket_apis/types/data_types.py +161 -0
- polymarket_apis/types/gamma_types.py +313 -0
- polymarket_apis/types/websockets_types.py +191 -0
- polymarket_apis/utilities/__init__.py +0 -0
- polymarket_apis/utilities/config.py +36 -0
- polymarket_apis/utilities/constants.py +26 -0
- polymarket_apis/utilities/endpoints.py +37 -0
- polymarket_apis/utilities/exceptions.py +11 -0
- polymarket_apis/utilities/headers.py +54 -0
- polymarket_apis/utilities/order_builder/__init__.py +0 -0
- polymarket_apis/utilities/order_builder/builder.py +240 -0
- polymarket_apis/utilities/order_builder/helpers.py +61 -0
- polymarket_apis/utilities/signing/__init__.py +0 -0
- polymarket_apis/utilities/signing/eip712.py +28 -0
- polymarket_apis/utilities/signing/hmac.py +20 -0
- polymarket_apis/utilities/signing/model.py +8 -0
- polymarket_apis/utilities/signing/signer.py +25 -0
- polymarket_apis/utilities/web3/__init__.py +0 -0
- polymarket_apis/utilities/web3/abis/CTFExchange.json +1851 -0
- polymarket_apis/utilities/web3/abis/ConditionalTokens.json +705 -0
- polymarket_apis/utilities/web3/abis/NegRiskAdapter.json +999 -0
- polymarket_apis/utilities/web3/abis/NegRiskCtfExchange.json +1856 -0
- polymarket_apis/utilities/web3/abis/ProxyWalletFactory.json +319 -0
- polymarket_apis/utilities/web3/abis/UChildERC20Proxy.json +1438 -0
- polymarket_apis/utilities/web3/abis/__init__.py +0 -0
- polymarket_apis/utilities/web3/abis/custom_contract_errors.py +31 -0
- polymarket_apis/utilities/web3/helpers.py +8 -0
- polymarket_apis-0.2.2.dist-info/METADATA +18 -0
- polymarket_apis-0.2.2.dist-info/RECORD +40 -0
- 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,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
|