prediction-market-agent-tooling 0.66.5__py3-none-any.whl → 0.67.1__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.
- prediction_market_agent_tooling/deploy/agent.py +23 -5
- prediction_market_agent_tooling/markets/agent_market.py +9 -2
- prediction_market_agent_tooling/markets/manifold/manifold.py +3 -2
- prediction_market_agent_tooling/markets/markets.py +5 -5
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +3 -1
- prediction_market_agent_tooling/markets/omen/omen.py +5 -2
- prediction_market_agent_tooling/markets/polymarket/api.py +87 -104
- prediction_market_agent_tooling/markets/polymarket/data_models.py +60 -14
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -54
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +109 -26
- prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +49 -0
- prediction_market_agent_tooling/markets/polymarket/utils.py +0 -21
- prediction_market_agent_tooling/markets/seer/price_manager.py +65 -46
- prediction_market_agent_tooling/markets/seer/seer.py +70 -90
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +48 -35
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +11 -1
- prediction_market_agent_tooling/tools/cow/cow_order.py +26 -11
- prediction_market_agent_tooling/tools/cow/models.py +4 -2
- prediction_market_agent_tooling/tools/httpx_cached_client.py +13 -6
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +7 -0
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
- prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
- prediction_market_agent_tooling/tools/utils.py +5 -2
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/RECORD +28 -26
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/entry_points.txt +0 -0
@@ -24,6 +24,8 @@ from prediction_market_agent_tooling.loggers import logger
|
|
24
24
|
from prediction_market_agent_tooling.markets.agent_market import (
|
25
25
|
AgentMarket,
|
26
26
|
FilterBy,
|
27
|
+
MarketFees,
|
28
|
+
MarketType,
|
27
29
|
ProcessedMarket,
|
28
30
|
ProcessedTradedMarket,
|
29
31
|
SortBy,
|
@@ -66,8 +68,9 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
66
68
|
)
|
67
69
|
from prediction_market_agent_tooling.tools.cow.cow_order import (
|
68
70
|
NoLiquidityAvailableOnCowException,
|
69
|
-
|
71
|
+
OrderStatusError,
|
70
72
|
get_orders_by_owner,
|
73
|
+
get_trades_by_order_uid,
|
71
74
|
get_trades_by_owner,
|
72
75
|
swap_tokens_waiting,
|
73
76
|
wait_for_order_completion,
|
@@ -76,6 +79,9 @@ from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
|
76
79
|
from prediction_market_agent_tooling.tools.tokens.auto_deposit import (
|
77
80
|
auto_deposit_collateral_token,
|
78
81
|
)
|
82
|
+
from prediction_market_agent_tooling.tools.tokens.slippage import (
|
83
|
+
get_slippage_tolerance_per_token,
|
84
|
+
)
|
79
85
|
from prediction_market_agent_tooling.tools.tokens.usd import (
|
80
86
|
get_token_in_usd,
|
81
87
|
get_usd_in_token,
|
@@ -95,6 +101,7 @@ class SeerAgentMarket(AgentMarket):
|
|
95
101
|
None # Seer markets don't have a description, so just default to None.
|
96
102
|
)
|
97
103
|
outcomes_supply: int
|
104
|
+
minimum_market_liquidity_required: CollateralToken = CollateralToken(1)
|
98
105
|
|
99
106
|
def get_collateral_token_contract(
|
100
107
|
self, web3: Web3 | None = None
|
@@ -131,45 +138,34 @@ class SeerAgentMarket(AgentMarket):
|
|
131
138
|
web3=web3,
|
132
139
|
)
|
133
140
|
|
141
|
+
def get_price_manager(self) -> PriceManager:
|
142
|
+
return PriceManager.build(HexBytes(HexStr(self.id)))
|
143
|
+
|
134
144
|
def get_token_in_usd(self, x: CollateralToken) -> USD:
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
145
|
+
p = self.get_price_manager()
|
146
|
+
sdai_amount = p.get_amount_of_collateral_in_token(
|
147
|
+
# Hard-coded SDAI, because Seer is atm hard-coded it as well, and it's needed in case of fallback to pools. CoW would work with other tokens as well.
|
148
|
+
SDAI_CONTRACT_ADDRESS,
|
149
|
+
x,
|
150
|
+
)
|
151
|
+
if sdai_amount is None:
|
152
|
+
raise RuntimeError(
|
153
|
+
"Both CoW and pool-fallback way of getting price failed."
|
142
154
|
)
|
143
|
-
|
144
|
-
if usd_token_price is None:
|
145
|
-
raise RuntimeError(
|
146
|
-
"Both CoW and pool-fallback way of getting price failed."
|
147
|
-
) from e
|
148
|
-
return USD(x.value * usd_token_price.value)
|
149
|
-
|
150
|
-
def get_collateral_price_from_pools(self) -> USD | None:
|
151
|
-
p = PriceManager.build(HexBytes(HexStr(self.id)))
|
152
|
-
token_price = p.get_token_price_from_pools(token=SDAI_CONTRACT_ADDRESS)
|
153
|
-
if token_price:
|
154
|
-
return get_token_in_usd(token_price, SDAI_CONTRACT_ADDRESS)
|
155
|
-
|
156
|
-
return None
|
155
|
+
return get_token_in_usd(sdai_amount, SDAI_CONTRACT_ADDRESS)
|
157
156
|
|
158
157
|
def get_usd_in_token(self, x: USD) -> CollateralToken:
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
158
|
+
p = self.get_price_manager()
|
159
|
+
token_amount = p.get_amount_of_token_in_collateral(
|
160
|
+
# Hard-coded SDAI, because Seer is atm hard-coded it as well, and it's needed in case of fallback to pools. CoW would work with other tokens as well.
|
161
|
+
SDAI_CONTRACT_ADDRESS,
|
162
|
+
get_usd_in_token(x, SDAI_CONTRACT_ADDRESS),
|
163
|
+
)
|
164
|
+
if token_amount is None:
|
165
|
+
raise RuntimeError(
|
166
|
+
"Both CoW and pool-fallback way of getting price failed."
|
166
167
|
)
|
167
|
-
|
168
|
-
if not usd_token_price:
|
169
|
-
raise RuntimeError(
|
170
|
-
"Both CoW and pool-fallback way of getting price failed."
|
171
|
-
) from e
|
172
|
-
return CollateralToken(x.value / usd_token_price.value)
|
168
|
+
return token_amount
|
173
169
|
|
174
170
|
def get_buy_token_amount(
|
175
171
|
self, bet_amount: USD | CollateralToken, outcome_str: OutcomeStr
|
@@ -185,16 +181,15 @@ class SeerAgentMarket(AgentMarket):
|
|
185
181
|
|
186
182
|
bet_amount_in_tokens = self.get_in_token(bet_amount)
|
187
183
|
|
188
|
-
p =
|
189
|
-
|
184
|
+
p = self.get_price_manager()
|
185
|
+
amount_outcome_tokens = p.get_amount_of_collateral_in_token(
|
190
186
|
token=outcome_token, collateral_exchange_amount=bet_amount_in_tokens
|
191
187
|
)
|
192
|
-
if not
|
188
|
+
if not amount_outcome_tokens:
|
193
189
|
logger.info(f"Could not get price for token {outcome_token}")
|
194
190
|
return None
|
195
191
|
|
196
|
-
amount_outcome_tokens
|
197
|
-
return OutcomeToken(amount_outcome_tokens)
|
192
|
+
return OutcomeToken(amount_outcome_tokens.value)
|
198
193
|
|
199
194
|
def get_sell_value_of_outcome_token(
|
200
195
|
self, outcome: OutcomeStr, amount: OutcomeToken
|
@@ -203,26 +198,18 @@ class SeerAgentMarket(AgentMarket):
|
|
203
198
|
return CollateralToken.zero()
|
204
199
|
|
205
200
|
wrapped_outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
logger.warning(
|
216
|
-
f"No liquidity available on Cow for {wrapped_outcome_token} -> {self.collateral_token_contract_address_checksummed}."
|
201
|
+
|
202
|
+
p = self.get_price_manager()
|
203
|
+
value_outcome_token_in_collateral = p.get_amount_of_token_in_collateral(
|
204
|
+
wrapped_outcome_token, amount.as_token
|
205
|
+
)
|
206
|
+
|
207
|
+
if value_outcome_token_in_collateral is None:
|
208
|
+
raise RuntimeError(
|
209
|
+
f"Could not get price for token from pools for {wrapped_outcome_token}"
|
217
210
|
)
|
218
|
-
|
219
|
-
|
220
|
-
if not price:
|
221
|
-
logger.info(
|
222
|
-
f"Could not get price for token from pools for {wrapped_outcome_token}"
|
223
|
-
)
|
224
|
-
raise e
|
225
|
-
return CollateralToken(price.value * amount.value)
|
211
|
+
|
212
|
+
return value_outcome_token_in_collateral
|
226
213
|
|
227
214
|
@staticmethod
|
228
215
|
def get_trade_balance(api_keys: APIKeys) -> USD:
|
@@ -231,9 +218,7 @@ class SeerAgentMarket(AgentMarket):
|
|
231
218
|
def get_tiny_bet_amount(self) -> CollateralToken:
|
232
219
|
return self.get_in_token(SEER_TINY_BET_AMOUNT)
|
233
220
|
|
234
|
-
def
|
235
|
-
self, user_id: str, web3: Web3 | None = None
|
236
|
-
) -> ExistingPosition:
|
221
|
+
def get_position(self, user_id: str, web3: Web3 | None = None) -> ExistingPosition:
|
237
222
|
"""
|
238
223
|
Fetches position from the user in a given market.
|
239
224
|
We ignore the INVALID balances since we are only interested in binary outcomes.
|
@@ -264,15 +249,6 @@ class SeerAgentMarket(AgentMarket):
|
|
264
249
|
amounts_ot=amounts_ot,
|
265
250
|
)
|
266
251
|
|
267
|
-
def get_position(
|
268
|
-
self, user_id: str, web3: Web3 | None = None
|
269
|
-
) -> ExistingPosition | None:
|
270
|
-
try:
|
271
|
-
return self.get_position_else_raise(user_id=user_id, web3=web3)
|
272
|
-
except Exception as e:
|
273
|
-
logger.warning(f"Could not get position for user {user_id}, exception {e}")
|
274
|
-
return None
|
275
|
-
|
276
252
|
@staticmethod
|
277
253
|
def get_user_id(api_keys: APIKeys) -> str:
|
278
254
|
return OmenAgentMarket.get_user_id(api_keys)
|
@@ -409,16 +385,16 @@ class SeerAgentMarket(AgentMarket):
|
|
409
385
|
filter_by: FilterBy = FilterBy.OPEN,
|
410
386
|
created_after: t.Optional[DatetimeUTC] = None,
|
411
387
|
excluded_questions: set[str] | None = None,
|
412
|
-
|
413
|
-
|
388
|
+
market_type: MarketType = MarketType.ALL,
|
389
|
+
include_conditional_markets: bool = False,
|
414
390
|
) -> t.Sequence["SeerAgentMarket"]:
|
415
391
|
seer_subgraph = SeerSubgraphHandler()
|
392
|
+
|
416
393
|
markets = seer_subgraph.get_markets(
|
417
394
|
limit=limit,
|
418
395
|
sort_by=sort_by,
|
419
396
|
filter_by=filter_by,
|
420
|
-
|
421
|
-
include_only_scalar_markets=fetch_scalar_markets,
|
397
|
+
market_type=market_type,
|
422
398
|
include_conditional_markets=False,
|
423
399
|
)
|
424
400
|
|
@@ -461,7 +437,8 @@ class SeerAgentMarket(AgentMarket):
|
|
461
437
|
f"Could not fetch pool for token {outcome_token}, no liquidity available for outcome."
|
462
438
|
)
|
463
439
|
return CollateralToken(0)
|
464
|
-
|
440
|
+
|
441
|
+
p = self.get_price_manager()
|
465
442
|
total = CollateralToken(0)
|
466
443
|
|
467
444
|
for token_address in [pool.token0.id, pool.token1.id]:
|
@@ -474,19 +451,13 @@ class SeerAgentMarket(AgentMarket):
|
|
474
451
|
for_address=Web3.to_checksum_address(HexAddress(HexStr(pool.id.hex()))),
|
475
452
|
web3=web3,
|
476
453
|
)
|
477
|
-
|
478
|
-
|
479
|
-
token_price_in_sdai = (
|
480
|
-
p.get_token_price_from_pools(token=token_address_checksummed)
|
481
|
-
if token_address_checksummed
|
482
|
-
!= self.collateral_token_contract_address_checksummed
|
483
|
-
else CollateralToken(1.0)
|
454
|
+
collateral_balance = p.get_amount_of_token_in_collateral(
|
455
|
+
token_address_checksummed, token_balance
|
484
456
|
)
|
485
457
|
|
486
458
|
# We ignore the liquidity in outcome tokens if price unknown.
|
487
|
-
if
|
488
|
-
|
489
|
-
total += sdai_balance
|
459
|
+
if collateral_balance:
|
460
|
+
total += collateral_balance
|
490
461
|
|
491
462
|
return total
|
492
463
|
|
@@ -501,7 +472,7 @@ class SeerAgentMarket(AgentMarket):
|
|
501
472
|
|
502
473
|
def has_liquidity_for_outcome(self, outcome: OutcomeStr) -> bool:
|
503
474
|
liquidity = self.get_liquidity_for_outcome(outcome)
|
504
|
-
return liquidity >
|
475
|
+
return liquidity > self.minimum_market_liquidity_required
|
505
476
|
|
506
477
|
def has_liquidity(self) -> bool:
|
507
478
|
# We define a market as having liquidity if it has liquidity for all outcomes except for the invalid (index -1)
|
@@ -534,7 +505,7 @@ class SeerAgentMarket(AgentMarket):
|
|
534
505
|
Returns:
|
535
506
|
Transaction hash of the successful swap
|
536
507
|
"""
|
537
|
-
|
508
|
+
slippage_tolerance = get_slippage_tolerance_per_token(sell_token, buy_token)
|
538
509
|
try:
|
539
510
|
_, order = swap_tokens_waiting(
|
540
511
|
amount_wei=amount_wei,
|
@@ -544,17 +515,26 @@ class SeerAgentMarket(AgentMarket):
|
|
544
515
|
web3=web3,
|
545
516
|
wait_order_complete=False,
|
546
517
|
timeout=timedelta(minutes=2),
|
518
|
+
slippage_tolerance=slippage_tolerance,
|
547
519
|
)
|
548
520
|
order_metadata = asyncio.run(wait_for_order_completion(order=order))
|
549
|
-
logger.
|
521
|
+
logger.info(
|
550
522
|
f"Swapped {sell_token} for {buy_token}. Order details {order_metadata}"
|
551
523
|
)
|
552
|
-
|
524
|
+
trades = get_trades_by_order_uid(HexBytes(order_metadata.uid.root))
|
525
|
+
if len(trades) != 1:
|
526
|
+
raise ValueError(
|
527
|
+
f"Expected exactly 1 trade from {order_metadata=}, but got {len(trades)=}."
|
528
|
+
)
|
529
|
+
cow_tx_hash = trades[0].txHash
|
530
|
+
logger.info(f"TxHash for {order_metadata.uid.root=} is {cow_tx_hash=}.")
|
531
|
+
return cow_tx_hash.hex()
|
553
532
|
|
554
533
|
except (
|
555
534
|
UnexpectedResponseError,
|
556
535
|
TimeoutError,
|
557
536
|
NoLiquidityAvailableOnCowException,
|
537
|
+
OrderStatusError,
|
558
538
|
) as e:
|
559
539
|
# We don't retry if not enough balance.
|
560
540
|
if "InsufficientBalance" in str(e):
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import sys
|
2
2
|
import typing as t
|
3
|
+
from enum import Enum
|
3
4
|
from typing import Any
|
4
5
|
|
5
6
|
from subgrounds import FieldPath
|
@@ -13,7 +14,11 @@ from prediction_market_agent_tooling.deploy.constants import (
|
|
13
14
|
)
|
14
15
|
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei
|
15
16
|
from prediction_market_agent_tooling.loggers import logger
|
16
|
-
from prediction_market_agent_tooling.markets.agent_market import
|
17
|
+
from prediction_market_agent_tooling.markets.agent_market import (
|
18
|
+
FilterBy,
|
19
|
+
MarketType,
|
20
|
+
SortBy,
|
21
|
+
)
|
17
22
|
from prediction_market_agent_tooling.markets.base_subgraph_handler import (
|
18
23
|
BaseSubgraphHandler,
|
19
24
|
)
|
@@ -24,6 +29,14 @@ from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
|
|
24
29
|
from prediction_market_agent_tooling.tools.web3_utils import unwrap_generic_value
|
25
30
|
|
26
31
|
|
32
|
+
class TemplateId(int, Enum):
|
33
|
+
"""Template IDs used in Reality.eth questions."""
|
34
|
+
|
35
|
+
SCALAR = 1
|
36
|
+
CATEGORICAL = 2
|
37
|
+
MULTICATEGORICAL = 3
|
38
|
+
|
39
|
+
|
27
40
|
class SeerSubgraphHandler(BaseSubgraphHandler):
|
28
41
|
"""
|
29
42
|
Class responsible for handling interactions with Seer subgraphs.
|
@@ -70,6 +83,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
70
83
|
markets_field.collateralToken,
|
71
84
|
markets_field.upperBound,
|
72
85
|
markets_field.lowerBound,
|
86
|
+
markets_field.templateId,
|
73
87
|
]
|
74
88
|
return fields
|
75
89
|
|
@@ -100,8 +114,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
100
114
|
filter_by: FilterBy,
|
101
115
|
outcome_supply_gt_if_open: Wei,
|
102
116
|
include_conditional_markets: bool = False,
|
103
|
-
|
104
|
-
include_only_scalar_markets: bool = False,
|
117
|
+
market_type: MarketType = MarketType.ALL,
|
105
118
|
) -> dict[Any, Any]:
|
106
119
|
now = to_int_timestamp(utcnow())
|
107
120
|
|
@@ -123,43 +136,47 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
123
136
|
if not include_conditional_markets:
|
124
137
|
and_stms["parentMarket"] = ADDRESS_ZERO.lower()
|
125
138
|
|
126
|
-
|
127
|
-
exclude_scalar_yes, exclude_scalar_no = {}, {}
|
139
|
+
outcome_filters: list[dict[str, t.Any]] = []
|
128
140
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
141
|
+
if market_type == MarketType.SCALAR:
|
142
|
+
# Template ID "1" + UP/DOWN outcomes for scalar markets
|
143
|
+
and_stms["templateId"] = TemplateId.SCALAR.value
|
144
|
+
up_filter = SeerSubgraphHandler._create_case_variations_condition(
|
133
145
|
UP_OUTCOME_LOWERCASE_IDENTIFIER, "outcomes_contains", "or"
|
134
146
|
)
|
135
|
-
|
147
|
+
down_filter = SeerSubgraphHandler._create_case_variations_condition(
|
136
148
|
DOWN_OUTCOME_LOWERCASE_IDENTIFIER, "outcomes_contains", "or"
|
137
149
|
)
|
138
|
-
|
139
|
-
|
140
|
-
|
150
|
+
outcome_filters.extend([up_filter, down_filter])
|
151
|
+
|
152
|
+
elif market_type == MarketType.BINARY:
|
153
|
+
# Template ID "2" + YES/NO outcomes for binary markets
|
154
|
+
and_stms["templateId"] = TemplateId.CATEGORICAL.value
|
155
|
+
yes_filter = SeerSubgraphHandler._create_case_variations_condition(
|
141
156
|
YES_OUTCOME_LOWERCASE_IDENTIFIER, "outcomes_contains", "or"
|
142
157
|
)
|
143
|
-
|
158
|
+
no_filter = SeerSubgraphHandler._create_case_variations_condition(
|
144
159
|
NO_OUTCOME_LOWERCASE_IDENTIFIER, "outcomes_contains", "or"
|
145
160
|
)
|
161
|
+
outcome_filters.extend([yes_filter, no_filter])
|
146
162
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
163
|
+
elif market_type == MarketType.CATEGORICAL:
|
164
|
+
# Template ID 2 (categorical) OR Template ID 3 (multi-categorical,
|
165
|
+
# we treat them as categorical for now for simplicity)
|
166
|
+
# https://reality.eth.limo/app/docs/html/contracts.html#templates
|
167
|
+
outcome_filters.append(
|
168
|
+
{
|
169
|
+
"or": [
|
170
|
+
{"templateId": TemplateId.CATEGORICAL.value},
|
171
|
+
{"templateId": TemplateId.MULTICATEGORICAL.value},
|
172
|
+
]
|
173
|
+
}
|
158
174
|
)
|
159
175
|
|
160
|
-
|
161
|
-
|
162
|
-
|
176
|
+
# If none specified, don't add any template/outcome filters (returns all types)
|
177
|
+
|
178
|
+
all_filters = [and_stms] + outcome_filters if and_stms else outcome_filters
|
179
|
+
where_stms: dict[str, t.Any] = {"and": all_filters}
|
163
180
|
return where_stms
|
164
181
|
|
165
182
|
def _build_sort_params(
|
@@ -194,20 +211,16 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
194
211
|
sort_by: SortBy = SortBy.NONE,
|
195
212
|
limit: int | None = None,
|
196
213
|
outcome_supply_gt_if_open: Wei = Wei(0),
|
197
|
-
|
198
|
-
|
199
|
-
include_only_scalar_markets: bool = False,
|
214
|
+
market_type: MarketType = MarketType.ALL,
|
215
|
+
include_conditional_markets: bool = False,
|
200
216
|
) -> list[SeerMarket]:
|
201
217
|
sort_direction, sort_by_field = self._build_sort_params(sort_by)
|
202
218
|
|
203
|
-
"""Returns markets that contain 2 categories plus an invalid outcome."""
|
204
|
-
# Binary markets on Seer contain 3 outcomes: OutcomeA, outcomeB and an Invalid option.
|
205
219
|
where_stms = self._build_where_statements(
|
206
220
|
filter_by=filter_by,
|
207
221
|
outcome_supply_gt_if_open=outcome_supply_gt_if_open,
|
208
222
|
include_conditional_markets=include_conditional_markets,
|
209
|
-
|
210
|
-
include_only_scalar_markets=include_only_scalar_markets,
|
223
|
+
market_type=market_type,
|
211
224
|
)
|
212
225
|
|
213
226
|
# These values can not be set to `None`, but they can be omitted.
|
@@ -19,6 +19,7 @@ from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
|
19
19
|
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
20
20
|
SeerSubgraphHandler,
|
21
21
|
)
|
22
|
+
from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
|
22
23
|
|
23
24
|
|
24
25
|
class SwapPoolHandler:
|
@@ -79,7 +80,7 @@ class SwapPoolHandler:
|
|
79
80
|
amount_out_minimum = self._calculate_amount_out_minimum(
|
80
81
|
amount_wei=amount_wei,
|
81
82
|
token_in=token_in,
|
82
|
-
price_outcome_token=price_outcome_token,
|
83
|
+
price_outcome_token=price_outcome_token.priceOfCollateralInAskingToken,
|
83
84
|
)
|
84
85
|
|
85
86
|
p = ExactInputSingleParams(
|
@@ -90,6 +91,15 @@ class SwapPoolHandler:
|
|
90
91
|
amount_out_minimum=amount_out_minimum,
|
91
92
|
)
|
92
93
|
|
94
|
+
# make sure user has enough tokens to sell
|
95
|
+
balance_collateral_token = ContractERC20OnGnosisChain(
|
96
|
+
address=token_in
|
97
|
+
).balanceOf(self.api_keys.bet_from_address, web3=web3)
|
98
|
+
if balance_collateral_token < amount_wei:
|
99
|
+
raise ValueError(
|
100
|
+
f"Balance {balance_collateral_token} of {token_in} insufficient for trade, required {amount_wei}"
|
101
|
+
)
|
102
|
+
|
93
103
|
tx_receipt = SwaprRouterContract().exact_input_single(
|
94
104
|
api_keys=self.api_keys, params=p, web3=web3
|
95
105
|
)
|
@@ -33,12 +33,7 @@ from cowdao_cowpy.order_book.generated.model import (
|
|
33
33
|
from eth_account import Account
|
34
34
|
from eth_account.signers.local import LocalAccount
|
35
35
|
from eth_keys.datatypes import PrivateKey as eth_keys_PrivateKey
|
36
|
-
from tenacity import
|
37
|
-
retry_if_not_exception_type,
|
38
|
-
stop_after_attempt,
|
39
|
-
wait_exponential,
|
40
|
-
wait_fixed,
|
41
|
-
)
|
36
|
+
from tenacity import stop_after_attempt, wait_exponential, wait_fixed
|
42
37
|
from web3 import Web3
|
43
38
|
|
44
39
|
from prediction_market_agent_tooling.config import APIKeys
|
@@ -54,7 +49,7 @@ from prediction_market_agent_tooling.markets.omen.cow_contracts import (
|
|
54
49
|
CowGPv2SettlementContract,
|
55
50
|
)
|
56
51
|
from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
|
57
|
-
from prediction_market_agent_tooling.tools.cow.models import
|
52
|
+
from prediction_market_agent_tooling.tools.cow.models import MinimalisticTrade, Order
|
58
53
|
from prediction_market_agent_tooling.tools.cow.semaphore import postgres_rate_limited
|
59
54
|
from prediction_market_agent_tooling.tools.utils import utcnow
|
60
55
|
|
@@ -108,7 +103,6 @@ def get_sell_token_amount(
|
|
108
103
|
@tenacity.retry(
|
109
104
|
stop=stop_after_attempt(4),
|
110
105
|
wait=wait_exponential(min=4, max=10),
|
111
|
-
retry=retry_if_not_exception_type(NoLiquidityAvailableOnCowException),
|
112
106
|
)
|
113
107
|
def get_quote(
|
114
108
|
amount_wei: Wei,
|
@@ -198,7 +192,7 @@ def handle_allowance(
|
|
198
192
|
reraise=True,
|
199
193
|
stop=stop_after_attempt(3),
|
200
194
|
wait=wait_fixed(1),
|
201
|
-
retry=tenacity.retry_if_not_exception_type((TimeoutError
|
195
|
+
retry=tenacity.retry_if_not_exception_type((TimeoutError)),
|
202
196
|
after=lambda x: logger.debug(f"swap_tokens_waiting failed, {x.attempt_number=}."),
|
203
197
|
)
|
204
198
|
def swap_tokens_waiting(
|
@@ -211,6 +205,7 @@ def swap_tokens_waiting(
|
|
211
205
|
web3: Web3 | None = None,
|
212
206
|
wait_order_complete: bool = True,
|
213
207
|
timeout: timedelta = timedelta(seconds=120),
|
208
|
+
slippage_tolerance: float = 0.01,
|
214
209
|
) -> tuple[OrderMetaData | None, CompletedOrder]:
|
215
210
|
# CoW library uses async, so we need to wrap the call in asyncio.run for us to use it.
|
216
211
|
return asyncio.run(
|
@@ -224,6 +219,7 @@ def swap_tokens_waiting(
|
|
224
219
|
timeout=timeout,
|
225
220
|
web3=web3,
|
226
221
|
wait_order_complete=wait_order_complete,
|
222
|
+
slippage_tolerance=slippage_tolerance,
|
227
223
|
)
|
228
224
|
)
|
229
225
|
|
@@ -353,14 +349,33 @@ async def sign_safe_cow_swap(
|
|
353
349
|
)
|
354
350
|
def get_trades_by_owner(
|
355
351
|
owner: ChecksumAddress,
|
356
|
-
) -> list[
|
352
|
+
) -> list[MinimalisticTrade]:
|
357
353
|
# Using this until cowpy gets fixed (https://github.com/cowdao-grants/cow-py/issues/35)
|
358
354
|
response = httpx.get(
|
359
355
|
f"https://api.cow.fi/xdai/api/v1/trades",
|
360
356
|
params={"owner": owner},
|
361
357
|
)
|
362
358
|
response.raise_for_status()
|
363
|
-
return [
|
359
|
+
return [MinimalisticTrade.model_validate(i) for i in response.json()]
|
360
|
+
|
361
|
+
|
362
|
+
@tenacity.retry(
|
363
|
+
stop=stop_after_attempt(3),
|
364
|
+
wait=wait_fixed(1),
|
365
|
+
after=lambda x: logger.debug(
|
366
|
+
f"get_trades_by_order_uid failed, {x.attempt_number=}."
|
367
|
+
),
|
368
|
+
)
|
369
|
+
def get_trades_by_order_uid(
|
370
|
+
order_uid: HexBytes,
|
371
|
+
) -> list[MinimalisticTrade]:
|
372
|
+
# Using this until cowpy gets fixed (https://github.com/cowdao-grants/cow-py/issues/35)
|
373
|
+
response = httpx.get(
|
374
|
+
f"https://api.cow.fi/xdai/api/v1/trades",
|
375
|
+
params={"orderUid": order_uid.hex()},
|
376
|
+
)
|
377
|
+
response.raise_for_status()
|
378
|
+
return [MinimalisticTrade.model_validate(i) for i in response.json()]
|
364
379
|
|
365
380
|
|
366
381
|
@tenacity.retry(
|
@@ -1,14 +1,16 @@
|
|
1
1
|
from pydantic import BaseModel
|
2
2
|
from sqlmodel import Field, SQLModel
|
3
3
|
|
4
|
-
from prediction_market_agent_tooling.gtypes import ChecksumAddress
|
4
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress, HexBytes
|
5
5
|
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
6
6
|
from prediction_market_agent_tooling.tools.utils import utcnow
|
7
7
|
|
8
8
|
|
9
|
-
class
|
9
|
+
class MinimalisticTrade(BaseModel):
|
10
10
|
sellToken: ChecksumAddress
|
11
11
|
buyToken: ChecksumAddress
|
12
|
+
orderUid: HexBytes
|
13
|
+
txHash: HexBytes
|
12
14
|
|
13
15
|
|
14
16
|
class Order(BaseModel):
|
@@ -1,14 +1,21 @@
|
|
1
1
|
import hishel
|
2
|
+
import httpx
|
2
3
|
|
4
|
+
from prediction_market_agent_tooling.tools.singleton import SingletonMeta
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
+
ONE_DAY_IN_SECONDS = 60 * 60 * 24
|
7
|
+
|
8
|
+
|
9
|
+
class HttpxCachedClient(metaclass=SingletonMeta):
|
10
|
+
def __init__(self, ttl: int = ONE_DAY_IN_SECONDS) -> None:
|
6
11
|
storage = hishel.FileStorage(
|
7
|
-
ttl=
|
8
|
-
check_ttl_every=
|
12
|
+
ttl=ttl,
|
13
|
+
check_ttl_every=60,
|
9
14
|
)
|
10
15
|
controller = hishel.Controller(force_cache=True)
|
11
|
-
self.client = hishel.CacheClient(
|
16
|
+
self.client: httpx.Client = hishel.CacheClient(
|
17
|
+
storage=storage, controller=controller
|
18
|
+
)
|
12
19
|
|
13
|
-
def get_client(self) ->
|
20
|
+
def get_client(self) -> httpx.Client:
|
14
21
|
return self.client
|
@@ -14,6 +14,9 @@ from prediction_market_agent_tooling.tools.cow.cow_order import (
|
|
14
14
|
swap_tokens_waiting,
|
15
15
|
)
|
16
16
|
from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN
|
17
|
+
from prediction_market_agent_tooling.tools.tokens.slippage import (
|
18
|
+
get_slippage_tolerance_per_token,
|
19
|
+
)
|
17
20
|
from prediction_market_agent_tooling.tools.tokens.usd import get_usd_in_token
|
18
21
|
from prediction_market_agent_tooling.tools.utils import should_not_happen
|
19
22
|
|
@@ -156,10 +159,14 @@ def auto_deposit_erc20(
|
|
156
159
|
raise ValueError(
|
157
160
|
"Not enough of the source token to sell to get the desired amount of the collateral token."
|
158
161
|
)
|
162
|
+
slippage_tolerance = get_slippage_tolerance_per_token(
|
163
|
+
KEEPING_ERC20_TOKEN.address, collateral_token_contract.address
|
164
|
+
)
|
159
165
|
swap_tokens_waiting(
|
160
166
|
amount_wei=amount_to_sell_wei,
|
161
167
|
sell_token=KEEPING_ERC20_TOKEN.address,
|
162
168
|
buy_token=collateral_token_contract.address,
|
163
169
|
api_keys=api_keys,
|
164
170
|
web3=web3,
|
171
|
+
slippage_tolerance=slippage_tolerance,
|
165
172
|
)
|
@@ -9,6 +9,9 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
9
9
|
)
|
10
10
|
from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting
|
11
11
|
from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN
|
12
|
+
from prediction_market_agent_tooling.tools.tokens.slippage import (
|
13
|
+
get_slippage_tolerance_per_token,
|
14
|
+
)
|
12
15
|
from prediction_market_agent_tooling.tools.utils import should_not_happen
|
13
16
|
|
14
17
|
|
@@ -49,12 +52,17 @@ def auto_withdraw_collateral_token(
|
|
49
52
|
f"Swapping {amount_wei.as_token} from {collateral_token_contract.symbol_cached(web3)} into {KEEPING_ERC20_TOKEN.symbol_cached(web3)}"
|
50
53
|
)
|
51
54
|
# Otherwise, DEX will handle the rest of token swaps.
|
55
|
+
slippage_tolerance = get_slippage_tolerance_per_token(
|
56
|
+
collateral_token_contract.address,
|
57
|
+
KEEPING_ERC20_TOKEN.address,
|
58
|
+
)
|
52
59
|
swap_tokens_waiting(
|
53
60
|
amount_wei=amount_wei,
|
54
61
|
sell_token=collateral_token_contract.address,
|
55
62
|
buy_token=KEEPING_ERC20_TOKEN.address,
|
56
63
|
api_keys=api_keys,
|
57
64
|
web3=web3,
|
65
|
+
slippage_tolerance=slippage_tolerance,
|
58
66
|
)
|
59
67
|
else:
|
60
68
|
should_not_happen("Unsupported ERC20 contract type.")
|