prediction-market-agent-tooling 0.68.0.dev999__py3-none-any.whl → 0.69.0__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/chains.py +1 -0
- prediction_market_agent_tooling/config.py +37 -2
- prediction_market_agent_tooling/deploy/agent.py +26 -21
- prediction_market_agent_tooling/deploy/betting_strategy.py +133 -22
- prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +17 -20
- prediction_market_agent_tooling/markets/agent_market.py +27 -9
- prediction_market_agent_tooling/markets/blockchain_utils.py +3 -3
- prediction_market_agent_tooling/markets/markets.py +16 -0
- prediction_market_agent_tooling/markets/omen/data_models.py +3 -18
- prediction_market_agent_tooling/markets/omen/omen.py +26 -11
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +2 -196
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +2 -2
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +13 -11
- prediction_market_agent_tooling/markets/polymarket/api.py +35 -1
- prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
- prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
- prediction_market_agent_tooling/markets/polymarket/data_models.py +33 -5
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +247 -18
- prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
- prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +2 -1
- prediction_market_agent_tooling/markets/seer/data_models.py +41 -6
- prediction_market_agent_tooling/markets/seer/price_manager.py +69 -1
- prediction_market_agent_tooling/markets/seer/seer.py +77 -26
- prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +71 -20
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +67 -0
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +17 -22
- prediction_market_agent_tooling/tools/contract.py +236 -4
- prediction_market_agent_tooling/tools/cow/cow_order.py +13 -8
- prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
- prediction_market_agent_tooling/tools/hexbytes_custom.py +3 -9
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +17 -5
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +2 -2
- prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
- prediction_market_agent_tooling/tools/web3_utils.py +9 -4
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/METADATA +8 -7
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/RECORD +41 -38
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -366
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/entry_points.txt +0 -0
@@ -30,8 +30,9 @@ class PolymarketSubgraphHandler(BaseSubgraphHandler):
|
|
30
30
|
def get_conditions(
|
31
31
|
self, condition_ids: list[HexBytes]
|
32
32
|
) -> list[ConditionSubgraphModel]:
|
33
|
-
where_stms = {"id_in": [i.
|
33
|
+
where_stms = {"id_in": [i.to_0x_hex() for i in condition_ids]}
|
34
34
|
conditions = self.conditions_subgraph.Query.conditions(
|
35
|
+
first=len(condition_ids),
|
35
36
|
where=where_stms,
|
36
37
|
)
|
37
38
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import typing as t
|
2
2
|
from datetime import timedelta
|
3
|
+
from enum import Enum
|
3
4
|
from typing import Annotated
|
4
5
|
from urllib.parse import urljoin
|
5
6
|
|
@@ -139,11 +140,6 @@ class SeerMarket(BaseModel):
|
|
139
140
|
for token in self.wrapped_tokens
|
140
141
|
]
|
141
142
|
|
142
|
-
@property
|
143
|
-
def is_binary(self) -> bool:
|
144
|
-
# 3 because Seer has also third, `Invalid` outcome.
|
145
|
-
return len(self.outcomes) == 3
|
146
|
-
|
147
143
|
@property
|
148
144
|
def collateral_token_contract_address_checksummed(self) -> ChecksumAddress:
|
149
145
|
return Web3.to_checksum_address(self.collateral_token)
|
@@ -160,7 +156,7 @@ class SeerMarket(BaseModel):
|
|
160
156
|
@property
|
161
157
|
def url(self) -> str:
|
162
158
|
chain_id = RPCConfig().chain_id
|
163
|
-
return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.
|
159
|
+
return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.to_0x_hex()}")
|
164
160
|
|
165
161
|
|
166
162
|
class SeerMarketWithQuestions(SeerMarket):
|
@@ -188,3 +184,42 @@ class ExactInputSingleParams(BaseModel):
|
|
188
184
|
limit_sqrt_price: Wei = Field(
|
189
185
|
alias="limitSqrtPrice", default_factory=lambda: Wei(0)
|
190
186
|
) # 0 for convenience, we also don't expect major price shifts
|
187
|
+
|
188
|
+
|
189
|
+
class SeerTransactionType(str, Enum):
|
190
|
+
SWAP = "swap"
|
191
|
+
SPLIT = "split"
|
192
|
+
|
193
|
+
|
194
|
+
class SeerTransaction(BaseModel):
|
195
|
+
model_config = ConfigDict(populate_by_name=True)
|
196
|
+
|
197
|
+
market_name: str = Field(alias="marketName")
|
198
|
+
market_id: HexBytes = Field(alias="marketId")
|
199
|
+
type: SeerTransactionType
|
200
|
+
block_number: int = Field(alias="blockNumber")
|
201
|
+
transaction_hash: HexBytes = Field(alias="transactionHash")
|
202
|
+
collateral: HexAddress
|
203
|
+
collateral_symbol: str = Field(alias="collateralSymbol")
|
204
|
+
|
205
|
+
token_in: HexAddress = Field(alias="tokenIn")
|
206
|
+
token_out: HexAddress = Field(alias="tokenOut")
|
207
|
+
amount_in: Wei = Field(alias="amountIn")
|
208
|
+
amount_out: Wei = Field(alias="amountOut")
|
209
|
+
token_in_symbol: str = Field(alias="tokenInSymbol")
|
210
|
+
token_out_symbol: str = Field(alias="tokenOutSymbol")
|
211
|
+
timestamp: int | None = None
|
212
|
+
|
213
|
+
amount: Wei | None = None
|
214
|
+
|
215
|
+
@property
|
216
|
+
def timestamp_dt(self) -> DatetimeUTC | None:
|
217
|
+
return DatetimeUTC.to_datetime_utc(self.timestamp) if self.timestamp else None
|
218
|
+
|
219
|
+
@property
|
220
|
+
def token_in_checksum(self) -> ChecksumAddress:
|
221
|
+
return Web3.to_checksum_address(self.token_in)
|
222
|
+
|
223
|
+
@property
|
224
|
+
def token_out_checksum(self) -> ChecksumAddress:
|
225
|
+
return Web3.to_checksum_address(self.token_out)
|
@@ -2,11 +2,15 @@ from cachetools import TTLCache, cached
|
|
2
2
|
from pydantic import BaseModel
|
3
3
|
from web3 import Web3
|
4
4
|
|
5
|
+
from prediction_market_agent_tooling.deploy.constants import (
|
6
|
+
INVALID_OUTCOME_LOWERCASE_IDENTIFIER,
|
7
|
+
)
|
5
8
|
from prediction_market_agent_tooling.gtypes import (
|
6
9
|
ChecksumAddress,
|
7
10
|
CollateralToken,
|
8
11
|
HexAddress,
|
9
12
|
OutcomeStr,
|
13
|
+
OutcomeToken,
|
10
14
|
Probability,
|
11
15
|
)
|
12
16
|
from prediction_market_agent_tooling.loggers import logger
|
@@ -46,7 +50,7 @@ class PriceManager:
|
|
46
50
|
price_diff_pct = abs(old_price - normalized_price) / max(old_price, 0.01)
|
47
51
|
if price_diff_pct > max_price_diff:
|
48
52
|
logger.info(
|
49
|
-
f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.
|
53
|
+
f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.to_0x_hex()} "
|
50
54
|
)
|
51
55
|
|
52
56
|
def get_price_for_token(self, token: ChecksumAddress) -> CollateralToken | None:
|
@@ -182,3 +186,67 @@ class PriceManager:
|
|
182
186
|
normalized_prices[outcome] = new_price
|
183
187
|
|
184
188
|
return normalized_prices
|
189
|
+
|
190
|
+
def build_initial_probs_from_pool(
|
191
|
+
self, model: SeerMarket, wrapped_tokens: list[ChecksumAddress]
|
192
|
+
) -> tuple[dict[OutcomeStr, Probability], dict[OutcomeStr, OutcomeToken]]:
|
193
|
+
"""
|
194
|
+
Builds a map of outcome to probability and outcome token pool.
|
195
|
+
"""
|
196
|
+
probability_map = {}
|
197
|
+
outcome_token_pool = {}
|
198
|
+
wrapped_tokens_with_supply = [
|
199
|
+
(
|
200
|
+
token,
|
201
|
+
SeerSubgraphHandler().get_pool_by_token(
|
202
|
+
token, model.collateral_token_contract_address_checksummed
|
203
|
+
),
|
204
|
+
)
|
205
|
+
for token in wrapped_tokens
|
206
|
+
]
|
207
|
+
wrapped_tokens_with_supply = [
|
208
|
+
(token, pool)
|
209
|
+
for token, pool in wrapped_tokens_with_supply
|
210
|
+
if pool is not None
|
211
|
+
]
|
212
|
+
|
213
|
+
for token, pool in wrapped_tokens_with_supply:
|
214
|
+
if pool is None or pool.token1.id is None or pool.token0.id is None:
|
215
|
+
continue
|
216
|
+
if HexBytes(token) == HexBytes(pool.token1.id):
|
217
|
+
outcome_token_pool[
|
218
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
219
|
+
] = (
|
220
|
+
OutcomeToken(pool.totalValueLockedToken0)
|
221
|
+
if pool.totalValueLockedToken0 is not None
|
222
|
+
else OutcomeToken(0)
|
223
|
+
)
|
224
|
+
probability_map[
|
225
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
226
|
+
] = Probability(pool.token0Price.value)
|
227
|
+
else:
|
228
|
+
outcome_token_pool[
|
229
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
230
|
+
] = (
|
231
|
+
OutcomeToken(pool.totalValueLockedToken1)
|
232
|
+
if pool.totalValueLockedToken1 is not None
|
233
|
+
else OutcomeToken(0)
|
234
|
+
)
|
235
|
+
probability_map[
|
236
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
237
|
+
] = Probability(pool.token1Price.value)
|
238
|
+
|
239
|
+
for outcome in model.outcomes:
|
240
|
+
if outcome not in outcome_token_pool:
|
241
|
+
outcome_token_pool[outcome] = OutcomeToken(0)
|
242
|
+
logger.warning(
|
243
|
+
f"Outcome {outcome} not found in outcome_token_pool for market {self.seer_market.url}."
|
244
|
+
)
|
245
|
+
if outcome not in probability_map:
|
246
|
+
if INVALID_OUTCOME_LOWERCASE_IDENTIFIER not in outcome.lower():
|
247
|
+
raise PriceCalculationError(
|
248
|
+
f"Couldn't get probability for {outcome} for market {self.seer_market.url}."
|
249
|
+
)
|
250
|
+
else:
|
251
|
+
probability_map[outcome] = Probability(0)
|
252
|
+
return probability_map, outcome_token_pool
|
@@ -28,7 +28,6 @@ from prediction_market_agent_tooling.markets.agent_market import (
|
|
28
28
|
FilterBy,
|
29
29
|
ParentMarket,
|
30
30
|
ProcessedMarket,
|
31
|
-
ProcessedTradedMarket,
|
32
31
|
QuestionType,
|
33
32
|
SortBy,
|
34
33
|
)
|
@@ -36,9 +35,13 @@ from prediction_market_agent_tooling.markets.blockchain_utils import store_trade
|
|
36
35
|
from prediction_market_agent_tooling.markets.data_models import (
|
37
36
|
ExistingPosition,
|
38
37
|
Resolution,
|
38
|
+
ResolvedBet,
|
39
39
|
)
|
40
40
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
41
|
-
from prediction_market_agent_tooling.markets.omen.omen import
|
41
|
+
from prediction_market_agent_tooling.markets.omen.omen import (
|
42
|
+
OmenAgentMarket,
|
43
|
+
send_keeping_token_to_eoa_xdai,
|
44
|
+
)
|
42
45
|
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
43
46
|
SDAI_CONTRACT_ADDRESS,
|
44
47
|
)
|
@@ -54,6 +57,7 @@ from prediction_market_agent_tooling.markets.seer.exceptions import (
|
|
54
57
|
PriceCalculationError,
|
55
58
|
)
|
56
59
|
from prediction_market_agent_tooling.markets.seer.price_manager import PriceManager
|
60
|
+
from prediction_market_agent_tooling.markets.seer.seer_api import get_seer_transactions
|
57
61
|
from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
58
62
|
GnosisRouter,
|
59
63
|
SeerMarketFactory,
|
@@ -79,7 +83,6 @@ from prediction_market_agent_tooling.tools.cow.cow_order import (
|
|
79
83
|
OrderStatusError,
|
80
84
|
get_orders_by_owner,
|
81
85
|
get_trades_by_order_uid,
|
82
|
-
get_trades_by_owner,
|
83
86
|
swap_tokens_waiting,
|
84
87
|
wait_for_order_completion,
|
85
88
|
)
|
@@ -136,7 +139,7 @@ class SeerAgentMarket(AgentMarket):
|
|
136
139
|
|
137
140
|
def store_trades(
|
138
141
|
self,
|
139
|
-
traded_market:
|
142
|
+
traded_market: ProcessedMarket | None,
|
140
143
|
keys: APIKeys,
|
141
144
|
agent_name: str,
|
142
145
|
web3: Web3 | None = None,
|
@@ -262,10 +265,6 @@ class SeerAgentMarket(AgentMarket):
|
|
262
265
|
amounts_ot=amounts_ot,
|
263
266
|
)
|
264
267
|
|
265
|
-
@staticmethod
|
266
|
-
def get_user_id(api_keys: APIKeys) -> str:
|
267
|
-
return OmenAgentMarket.get_user_id(api_keys)
|
268
|
-
|
269
268
|
@staticmethod
|
270
269
|
def _filter_markets_contained_in_trades(
|
271
270
|
api_keys: APIKeys,
|
@@ -274,10 +273,12 @@ class SeerAgentMarket(AgentMarket):
|
|
274
273
|
"""
|
275
274
|
We filter the markets using previous trades by the user so that we don't have to process all Seer markets.
|
276
275
|
"""
|
277
|
-
trades_by_user =
|
276
|
+
trades_by_user = get_seer_transactions(
|
277
|
+
api_keys.bet_from_address, RPCConfig().CHAIN_ID
|
278
|
+
)
|
278
279
|
|
279
|
-
traded_tokens = {t.
|
280
|
-
[t.
|
280
|
+
traded_tokens = {t.token_in_checksum for t in trades_by_user}.union(
|
281
|
+
[t.token_out_checksum for t in trades_by_user]
|
281
282
|
)
|
282
283
|
filtered_markets: list[SeerMarket] = []
|
283
284
|
for market in markets:
|
@@ -315,19 +316,43 @@ class SeerAgentMarket(AgentMarket):
|
|
315
316
|
for market in filtered_markets
|
316
317
|
if market.is_redeemable(owner=api_keys.bet_from_address, web3=web3)
|
317
318
|
]
|
319
|
+
logger.info(f"Got {len(markets_to_redeem)} markets to redeem on Seer.")
|
318
320
|
|
319
321
|
gnosis_router = GnosisRouter()
|
320
322
|
for market in markets_to_redeem:
|
321
323
|
try:
|
324
|
+
# GnosisRouter needs approval to use our outcome tokens
|
325
|
+
for i, token in enumerate(market.wrapped_tokens):
|
326
|
+
ContractERC20OnGnosisChain(
|
327
|
+
address=Web3.to_checksum_address(token)
|
328
|
+
).approve(
|
329
|
+
api_keys,
|
330
|
+
for_address=gnosis_router.address,
|
331
|
+
amount_wei=market_balances[market.id][i].as_wei,
|
332
|
+
web3=web3,
|
333
|
+
)
|
334
|
+
|
335
|
+
# We can only ask for redeem of outcome tokens on correct outcomes
|
336
|
+
# TODO: Implement more complex use-cases: https://github.com/gnosis/prediction-market-agent-tooling/issues/850
|
337
|
+
amounts_to_redeem = [
|
338
|
+
(amount if numerator > 0 else OutcomeWei(0))
|
339
|
+
for amount, numerator in zip(
|
340
|
+
market_balances[market.id], market.payout_numerators
|
341
|
+
)
|
342
|
+
]
|
343
|
+
|
344
|
+
# Redeem!
|
322
345
|
params = RedeemParams(
|
323
346
|
market=Web3.to_checksum_address(market.id),
|
324
347
|
outcome_indices=list(range(len(market.payout_numerators))),
|
325
|
-
amounts=
|
348
|
+
amounts=amounts_to_redeem,
|
326
349
|
)
|
327
350
|
gnosis_router.redeem_to_base(api_keys, params=params, web3=web3)
|
328
|
-
logger.info(f"Redeemed market {market.
|
329
|
-
except Exception
|
330
|
-
logger.
|
351
|
+
logger.info(f"Redeemed market {market.url}.")
|
352
|
+
except Exception:
|
353
|
+
logger.exception(
|
354
|
+
f"Failed to redeem market {market.url}, {market.outcomes}, with amounts {market_balances[market.id]} and payout numerators {market.payout_numerators}, and wrapped tokens {market.wrapped_tokens}."
|
355
|
+
)
|
331
356
|
|
332
357
|
# GnosisRouter withdraws sDai into wxDAI/xDai on its own, so no auto-withdraw needed by us.
|
333
358
|
|
@@ -345,6 +370,17 @@ class SeerAgentMarket(AgentMarket):
|
|
345
370
|
|
346
371
|
return False
|
347
372
|
|
373
|
+
def ensure_min_native_balance(
|
374
|
+
self,
|
375
|
+
min_required_balance: xDai,
|
376
|
+
multiplier: float = 3.0,
|
377
|
+
) -> None:
|
378
|
+
send_keeping_token_to_eoa_xdai(
|
379
|
+
api_keys=APIKeys(),
|
380
|
+
min_required_balance=min_required_balance,
|
381
|
+
multiplier=multiplier,
|
382
|
+
)
|
383
|
+
|
348
384
|
@staticmethod
|
349
385
|
def verify_operational_balance(api_keys: APIKeys) -> bool:
|
350
386
|
return OmenAgentMarket.verify_operational_balance(api_keys=api_keys)
|
@@ -367,7 +403,7 @@ class SeerAgentMarket(AgentMarket):
|
|
367
403
|
raise ValueError("Seer categorical markets must have 1 question.")
|
368
404
|
|
369
405
|
question = model.questions[0]
|
370
|
-
outcome = model.outcomes[int(question.question.best_answer.
|
406
|
+
outcome = model.outcomes[int(question.question.best_answer.to_0x_hex(), 16)]
|
371
407
|
return Resolution(outcome=outcome, invalid=False)
|
372
408
|
|
373
409
|
@staticmethod
|
@@ -411,13 +447,17 @@ class SeerAgentMarket(AgentMarket):
|
|
411
447
|
must_have_prices: bool,
|
412
448
|
) -> t.Optional["SeerAgentMarket"]:
|
413
449
|
price_manager = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
|
414
|
-
|
415
|
-
probability_map = {}
|
450
|
+
wrapped_tokens = [Web3.to_checksum_address(i) for i in model.wrapped_tokens]
|
416
451
|
try:
|
417
|
-
|
452
|
+
(
|
453
|
+
probability_map,
|
454
|
+
outcome_token_pool,
|
455
|
+
) = price_manager.build_initial_probs_from_pool(
|
456
|
+
model=model, wrapped_tokens=wrapped_tokens
|
457
|
+
)
|
418
458
|
except PriceCalculationError as e:
|
419
459
|
logger.info(
|
420
|
-
f"Error when calculating probabilities for market {model.id.
|
460
|
+
f"Error when calculating probabilities for market {model.id.to_0x_hex()} - {e}"
|
421
461
|
)
|
422
462
|
if must_have_prices:
|
423
463
|
# Price calculation failed, so don't return the market
|
@@ -428,7 +468,7 @@ class SeerAgentMarket(AgentMarket):
|
|
428
468
|
parent = SeerAgentMarket.get_parent(model=model, seer_subgraph=seer_subgraph)
|
429
469
|
|
430
470
|
market = SeerAgentMarket(
|
431
|
-
id=model.id.
|
471
|
+
id=model.id.to_0x_hex(),
|
432
472
|
question=model.title,
|
433
473
|
creator=model.creator,
|
434
474
|
created_time=model.created_time,
|
@@ -437,9 +477,9 @@ class SeerAgentMarket(AgentMarket):
|
|
437
477
|
condition_id=model.condition_id,
|
438
478
|
url=model.url,
|
439
479
|
close_time=model.close_time,
|
440
|
-
wrapped_tokens=
|
480
|
+
wrapped_tokens=wrapped_tokens,
|
441
481
|
fees=MarketFees.get_zero_fees(),
|
442
|
-
outcome_token_pool=
|
482
|
+
outcome_token_pool=outcome_token_pool,
|
443
483
|
outcomes_supply=model.outcomes_supply,
|
444
484
|
resolution=resolution,
|
445
485
|
volume=None,
|
@@ -451,6 +491,15 @@ class SeerAgentMarket(AgentMarket):
|
|
451
491
|
|
452
492
|
return market
|
453
493
|
|
494
|
+
@staticmethod
|
495
|
+
def get_resolved_bets_made_since(
|
496
|
+
better_address: ChecksumAddress,
|
497
|
+
start_time: DatetimeUTC,
|
498
|
+
end_time: DatetimeUTC | None,
|
499
|
+
) -> list[ResolvedBet]:
|
500
|
+
# TODO: https://github.com/gnosis/prediction-market-agent-tooling/issues/841
|
501
|
+
raise NotImplementedError()
|
502
|
+
|
454
503
|
@staticmethod
|
455
504
|
def get_markets(
|
456
505
|
limit: int,
|
@@ -521,7 +570,9 @@ class SeerAgentMarket(AgentMarket):
|
|
521
570
|
)
|
522
571
|
|
523
572
|
token_balance = token_contract.balance_of_in_tokens(
|
524
|
-
for_address=Web3.to_checksum_address(
|
573
|
+
for_address=Web3.to_checksum_address(
|
574
|
+
HexAddress(HexStr(pool.id.to_0x_hex()))
|
575
|
+
),
|
525
576
|
web3=web3,
|
526
577
|
)
|
527
578
|
collateral_balance = p.get_amount_of_token_in_collateral(
|
@@ -602,7 +653,7 @@ class SeerAgentMarket(AgentMarket):
|
|
602
653
|
)
|
603
654
|
cow_tx_hash = trades[0].txHash
|
604
655
|
logger.info(f"TxHash is {cow_tx_hash=} for {order_metadata.uid.root=}.")
|
605
|
-
return cow_tx_hash.
|
656
|
+
return cow_tx_hash.to_0x_hex()
|
606
657
|
|
607
658
|
except (
|
608
659
|
UnexpectedResponseError,
|
@@ -635,7 +686,7 @@ class SeerAgentMarket(AgentMarket):
|
|
635
686
|
)
|
636
687
|
swap_pool_tx_hash = tx_receipt["transactionHash"]
|
637
688
|
logger.info(f"TxHash is {swap_pool_tx_hash=}.")
|
638
|
-
return swap_pool_tx_hash.
|
689
|
+
return swap_pool_tx_hash.to_0x_hex()
|
639
690
|
|
640
691
|
def place_bet(
|
641
692
|
self,
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import httpx
|
2
|
+
|
3
|
+
from prediction_market_agent_tooling.gtypes import ChainID, ChecksumAddress
|
4
|
+
from prediction_market_agent_tooling.markets.seer.data_models import SeerTransaction
|
5
|
+
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
6
|
+
from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
|
7
|
+
|
8
|
+
|
9
|
+
def get_seer_transactions(
|
10
|
+
account: ChecksumAddress,
|
11
|
+
chain_id: ChainID,
|
12
|
+
start_time: DatetimeUTC | None = None,
|
13
|
+
end_time: DatetimeUTC | None = None,
|
14
|
+
timeout: int = 60, # The endpoint is pretty slow to respond atm.
|
15
|
+
) -> list[SeerTransaction]:
|
16
|
+
url = "https://app.seer.pm/.netlify/functions/get-transactions"
|
17
|
+
params: dict[str, str | int] = {
|
18
|
+
"account": account,
|
19
|
+
"chainId": chain_id,
|
20
|
+
"startTime": to_int_timestamp(start_time) if start_time else 0,
|
21
|
+
"endTime": to_int_timestamp(end_time if end_time else utcnow()),
|
22
|
+
}
|
23
|
+
response = httpx.get(url, params=params, timeout=timeout)
|
24
|
+
response.raise_for_status()
|
25
|
+
response_json = response.json()
|
26
|
+
|
27
|
+
transactions = [SeerTransaction.model_validate(tx) for tx in response_json]
|
28
|
+
return transactions
|
@@ -29,10 +29,17 @@ from prediction_market_agent_tooling.markets.seer.data_models import (
|
|
29
29
|
SeerMarketQuestions,
|
30
30
|
SeerMarketWithQuestions,
|
31
31
|
)
|
32
|
-
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import
|
32
|
+
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
|
33
|
+
SeerPool,
|
34
|
+
SwaprSwap,
|
35
|
+
)
|
33
36
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
34
37
|
from prediction_market_agent_tooling.tools.singleton import SingletonMeta
|
35
|
-
from prediction_market_agent_tooling.tools.utils import
|
38
|
+
from prediction_market_agent_tooling.tools.utils import (
|
39
|
+
DatetimeUTC,
|
40
|
+
to_int_timestamp,
|
41
|
+
utcnow,
|
42
|
+
)
|
36
43
|
from prediction_market_agent_tooling.tools.web3_utils import unwrap_generic_value
|
37
44
|
|
38
45
|
|
@@ -155,7 +162,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
155
162
|
raise ValueError(f"Unknown filter {filter_by}")
|
156
163
|
|
157
164
|
if parent_market_id:
|
158
|
-
and_stms["parentMarket"] = parent_market_id.
|
165
|
+
and_stms["parentMarket"] = parent_market_id.to_0x_hex().lower()
|
159
166
|
|
160
167
|
outcome_filters: list[dict[str, t.Any]] = []
|
161
168
|
|
@@ -294,7 +301,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
294
301
|
self, market_ids: list[HexBytes]
|
295
302
|
) -> list[SeerMarketQuestions]:
|
296
303
|
where = unwrap_generic_value(
|
297
|
-
{"market_in": [market_id.
|
304
|
+
{"market_in": [market_id.to_0x_hex().lower() for market_id in market_ids]}
|
298
305
|
)
|
299
306
|
markets_field = self.seer_subgraph.Query.marketQuestions(where=where)
|
300
307
|
fields = self._get_fields_for_questions(markets_field)
|
@@ -302,7 +309,9 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
302
309
|
return questions
|
303
310
|
|
304
311
|
def get_market_by_id(self, market_id: HexBytes) -> SeerMarketWithQuestions:
|
305
|
-
markets_field = self.seer_subgraph.Query.market(
|
312
|
+
markets_field = self.seer_subgraph.Query.market(
|
313
|
+
id=market_id.to_0x_hex().lower()
|
314
|
+
)
|
306
315
|
fields = self._get_fields_for_markets(markets_field)
|
307
316
|
markets = self.do_query(fields=fields, pydantic_model=SeerMarket)
|
308
317
|
if len(markets) != 1:
|
@@ -326,8 +335,8 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
326
335
|
]
|
327
336
|
return fields
|
328
337
|
|
329
|
-
def get_market_by_wrapped_token(self,
|
330
|
-
where_stms = {"wrappedTokens_contains":
|
338
|
+
def get_market_by_wrapped_token(self, tokens: list[ChecksumAddress]) -> SeerMarket:
|
339
|
+
where_stms = {"wrappedTokens_contains": tokens}
|
331
340
|
markets_field = self.seer_subgraph.Query.markets(
|
332
341
|
where=unwrap_generic_value(where_stms)
|
333
342
|
)
|
@@ -339,20 +348,27 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
339
348
|
)
|
340
349
|
return markets[0]
|
341
350
|
|
342
|
-
def
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
pools_field.token0Price,
|
348
|
-
pools_field.token1Price,
|
349
|
-
pools_field.token0.id,
|
350
|
-
pools_field.token0.name,
|
351
|
-
pools_field.token0.symbol,
|
352
|
-
pools_field.token1.id,
|
353
|
-
pools_field.token1.name,
|
354
|
-
pools_field.token1.symbol,
|
351
|
+
def _get_fields_for_seer_token(self, fields: FieldPath) -> list[FieldPath]:
|
352
|
+
return [
|
353
|
+
fields.id,
|
354
|
+
fields.name,
|
355
|
+
fields.symbol,
|
355
356
|
]
|
357
|
+
|
358
|
+
def _get_fields_for_pools(self, pools_field: FieldPath) -> list[FieldPath]:
|
359
|
+
fields = (
|
360
|
+
[
|
361
|
+
pools_field.id,
|
362
|
+
pools_field.liquidity,
|
363
|
+
pools_field.sqrtPrice,
|
364
|
+
pools_field.token0Price,
|
365
|
+
pools_field.token1Price,
|
366
|
+
pools_field.totalValueLockedToken0,
|
367
|
+
pools_field.totalValueLockedToken1,
|
368
|
+
]
|
369
|
+
+ self._get_fields_for_seer_token(pools_field.token0)
|
370
|
+
+ self._get_fields_for_seer_token(pools_field.token1)
|
371
|
+
)
|
356
372
|
return fields
|
357
373
|
|
358
374
|
def get_pool_by_token(
|
@@ -392,6 +408,41 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
392
408
|
return pools[0]
|
393
409
|
return None
|
394
410
|
|
411
|
+
def _get_fields_for_swaps(self, swaps_field: FieldPath) -> list[FieldPath]:
|
412
|
+
fields = (
|
413
|
+
[
|
414
|
+
swaps_field.id,
|
415
|
+
swaps_field.pool.id,
|
416
|
+
swaps_field.sender,
|
417
|
+
swaps_field.recipient,
|
418
|
+
swaps_field.price,
|
419
|
+
swaps_field.amount0,
|
420
|
+
swaps_field.amount1,
|
421
|
+
swaps_field.timestamp,
|
422
|
+
]
|
423
|
+
+ self._get_fields_for_seer_token(swaps_field.token0)
|
424
|
+
+ self._get_fields_for_seer_token(swaps_field.token1)
|
425
|
+
)
|
426
|
+
return fields
|
427
|
+
|
428
|
+
def get_swaps(
|
429
|
+
self,
|
430
|
+
recipient: ChecksumAddress,
|
431
|
+
timestamp_gt: DatetimeUTC | None = None,
|
432
|
+
timestamp_lt: DatetimeUTC | None = None,
|
433
|
+
) -> list[SwaprSwap]:
|
434
|
+
where_argument: dict[str, Any] = {"recipient": recipient.lower()}
|
435
|
+
if timestamp_gt is not None:
|
436
|
+
where_argument["timestamp_gt"] = to_int_timestamp(timestamp_gt)
|
437
|
+
if timestamp_lt is not None:
|
438
|
+
where_argument["timestamp_lt"] = to_int_timestamp(timestamp_lt)
|
439
|
+
|
440
|
+
swaps_field = self.swapr_algebra_subgraph.Query.swaps(where=where_argument)
|
441
|
+
fields = self._get_fields_for_swaps(swaps_field)
|
442
|
+
swaps = self.do_query(fields=fields, pydantic_model=SwaprSwap)
|
443
|
+
|
444
|
+
return swaps
|
445
|
+
|
395
446
|
|
396
447
|
class SeerQuestionsCache(metaclass=SingletonMeta):
|
397
448
|
"""A singleton cache for storing and retrieving Seer market questions.
|
@@ -1,12 +1,17 @@
|
|
1
1
|
from pydantic import BaseModel, ConfigDict, Field
|
2
|
+
from web3 import Web3
|
2
3
|
from web3.constants import ADDRESS_ZERO
|
3
4
|
|
4
5
|
from prediction_market_agent_tooling.gtypes import (
|
6
|
+
ChecksumAddress,
|
5
7
|
CollateralToken,
|
6
8
|
HexAddress,
|
7
9
|
HexBytes,
|
8
10
|
OutcomeStr,
|
11
|
+
OutcomeToken,
|
12
|
+
Wei,
|
9
13
|
)
|
14
|
+
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
10
15
|
|
11
16
|
|
12
17
|
class SeerToken(BaseModel):
|
@@ -14,6 +19,10 @@ class SeerToken(BaseModel):
|
|
14
19
|
name: str
|
15
20
|
symbol: str
|
16
21
|
|
22
|
+
@property
|
23
|
+
def address(self) -> ChecksumAddress:
|
24
|
+
return Web3.to_checksum_address(self.id.hex())
|
25
|
+
|
17
26
|
|
18
27
|
class SeerPool(BaseModel):
|
19
28
|
model_config = ConfigDict(populate_by_name=True)
|
@@ -24,6 +33,64 @@ class SeerPool(BaseModel):
|
|
24
33
|
token0Price: CollateralToken
|
25
34
|
token1Price: CollateralToken
|
26
35
|
sqrtPrice: int
|
36
|
+
totalValueLockedToken0: float
|
37
|
+
totalValueLockedToken1: float
|
38
|
+
|
39
|
+
|
40
|
+
class SwaprSwap(BaseModel):
|
41
|
+
id: str # It's like "0x73afd8f096096552d72a0b40ea66d2076be136c6a531e2f6b190d151a750271e#32" (note the #32) # web3-private-key-ok
|
42
|
+
recipient: HexAddress
|
43
|
+
sender: HexAddress
|
44
|
+
price: Wei
|
45
|
+
amount0: CollateralToken
|
46
|
+
amount1: CollateralToken
|
47
|
+
token0: SeerToken
|
48
|
+
token1: SeerToken
|
49
|
+
timestamp: int
|
50
|
+
|
51
|
+
@property
|
52
|
+
def timestamp_utc(self) -> DatetimeUTC:
|
53
|
+
return DatetimeUTC.to_datetime_utc(self.timestamp)
|
54
|
+
|
55
|
+
@property
|
56
|
+
def added_to_pool(self) -> CollateralToken:
|
57
|
+
return self.amount0 if self.amount0 > 0 else self.amount1
|
58
|
+
|
59
|
+
@property
|
60
|
+
def withdrawn_from_pool(self) -> OutcomeToken:
|
61
|
+
return (
|
62
|
+
OutcomeToken(abs(self.amount0).value)
|
63
|
+
if self.amount0 < 0
|
64
|
+
else OutcomeToken(abs(self.amount1).value)
|
65
|
+
)
|
66
|
+
|
67
|
+
|
68
|
+
class SeerSwap(BaseModel):
|
69
|
+
id: str # It's like "0x73afd8f096096552d72a0b40ea66d2076be136c6a531e2f6b190d151a750271e#32" (note the #32) # web3-private-key-ok
|
70
|
+
recipient: HexAddress
|
71
|
+
sender: HexAddress
|
72
|
+
price: Wei
|
73
|
+
amount0: CollateralToken
|
74
|
+
amount1: CollateralToken
|
75
|
+
token0: SeerToken
|
76
|
+
token1: SeerToken
|
77
|
+
timestamp: int
|
78
|
+
|
79
|
+
@property
|
80
|
+
def timestamp_utc(self) -> DatetimeUTC:
|
81
|
+
return DatetimeUTC.to_datetime_utc(self.timestamp)
|
82
|
+
|
83
|
+
@property
|
84
|
+
def buying_collateral_amount(self) -> CollateralToken:
|
85
|
+
return self.amount0 if self.amount0 > 0 else self.amount1
|
86
|
+
|
87
|
+
@property
|
88
|
+
def received_shares_amount(self) -> OutcomeToken:
|
89
|
+
return (
|
90
|
+
OutcomeToken(abs(self.amount0).value)
|
91
|
+
if self.amount0 < 0
|
92
|
+
else OutcomeToken(abs(self.amount1).value)
|
93
|
+
)
|
27
94
|
|
28
95
|
|
29
96
|
class NewMarketEvent(BaseModel):
|