prediction-market-agent-tooling 0.58.3.dev385__py3-none-any.whl → 0.58.5__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 +16 -3
- prediction_market_agent_tooling/deploy/betting_strategy.py +2 -6
- prediction_market_agent_tooling/markets/agent_market.py +1 -2
- prediction_market_agent_tooling/markets/blockchain_utils.py +82 -0
- prediction_market_agent_tooling/markets/data_models.py +1 -0
- prediction_market_agent_tooling/markets/markets.py +4 -1
- prediction_market_agent_tooling/markets/metaculus/data_models.py +1 -1
- prediction_market_agent_tooling/markets/omen/omen.py +12 -62
- prediction_market_agent_tooling/markets/seer/data_models.py +182 -3
- prediction_market_agent_tooling/markets/seer/seer.py +271 -3
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +103 -21
- prediction_market_agent_tooling/tools/cow/cow_manager.py +113 -0
- {prediction_market_agent_tooling-0.58.3.dev385.dist-info → prediction_market_agent_tooling-0.58.5.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.58.3.dev385.dist-info → prediction_market_agent_tooling-0.58.5.dist-info}/RECORD +17 -15
- {prediction_market_agent_tooling-0.58.3.dev385.dist-info → prediction_market_agent_tooling-0.58.5.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.58.3.dev385.dist-info → prediction_market_agent_tooling-0.58.5.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.58.3.dev385.dist-info → prediction_market_agent_tooling-0.58.5.dist-info}/entry_points.txt +0 -0
@@ -64,7 +64,7 @@ from prediction_market_agent_tooling.tools.is_predictable import is_predictable_
|
|
64
64
|
from prediction_market_agent_tooling.tools.langfuse_ import langfuse_context, observe
|
65
65
|
from prediction_market_agent_tooling.tools.utils import DatetimeUTC, utcnow
|
66
66
|
|
67
|
-
MAX_AVAILABLE_MARKETS =
|
67
|
+
MAX_AVAILABLE_MARKETS = 1000
|
68
68
|
|
69
69
|
|
70
70
|
def initialize_langfuse(enable_langfuse: bool) -> None:
|
@@ -361,17 +361,23 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
361
361
|
if self.have_bet_on_market_since(
|
362
362
|
market, since=self.same_market_trade_interval.get(market=market)
|
363
363
|
):
|
364
|
+
logger.info(
|
365
|
+
f"Market already bet on within {self.same_market_trade_interval}."
|
366
|
+
)
|
364
367
|
return False
|
365
368
|
|
366
369
|
# Manifold allows to bet only on markets with probability between 1 and 99.
|
367
370
|
if market_type == MarketType.MANIFOLD and not (1 < market.current_p_yes < 99):
|
371
|
+
logger.info("Manifold's market probability not in the range 1-99.")
|
368
372
|
return False
|
369
373
|
|
370
374
|
# Do as a last check, as it uses paid OpenAI API.
|
371
375
|
if not is_predictable_binary(market.question):
|
376
|
+
logger.info("Market question is not predictable.")
|
372
377
|
return False
|
373
378
|
|
374
379
|
if not self.allow_invalid_questions and is_invalid(market.question):
|
380
|
+
logger.info("Market question is invalid.")
|
375
381
|
return False
|
376
382
|
|
377
383
|
return True
|
@@ -430,6 +436,7 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
430
436
|
logger.info(f"Market '{market.question}' doesn't meet the criteria.")
|
431
437
|
answer = None
|
432
438
|
else:
|
439
|
+
logger.info(f"Answering market '{market.question}'.")
|
433
440
|
answer = self.answer_binary_market(market)
|
434
441
|
|
435
442
|
processed_market = (
|
@@ -480,7 +487,10 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
480
487
|
)
|
481
488
|
processed = 0
|
482
489
|
|
483
|
-
for market in available_markets:
|
490
|
+
for market_idx, market in enumerate(available_markets):
|
491
|
+
logger.info(
|
492
|
+
f"Going to process market {market_idx+1} / {len(available_markets)}."
|
493
|
+
)
|
484
494
|
self.before_process_market(market_type, market)
|
485
495
|
processed_market = self.process_market(market_type, market)
|
486
496
|
self.after_process_market(market_type, market, processed_market)
|
@@ -491,7 +501,9 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
491
501
|
if processed == self.bet_on_n_markets_per_run:
|
492
502
|
break
|
493
503
|
|
494
|
-
logger.info(
|
504
|
+
logger.info(
|
505
|
+
f"All markets processed. Successfully processed {processed}/{len(available_markets)}."
|
506
|
+
)
|
495
507
|
|
496
508
|
def after_process_markets(self, market_type: MarketType) -> None:
|
497
509
|
"""
|
@@ -522,6 +534,7 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
522
534
|
MarketType.OMEN,
|
523
535
|
MarketType.MANIFOLD,
|
524
536
|
MarketType.POLYMARKET,
|
537
|
+
MarketType.SEER,
|
525
538
|
]
|
526
539
|
|
527
540
|
def __init__(
|
@@ -13,7 +13,6 @@ from prediction_market_agent_tooling.markets.data_models import (
|
|
13
13
|
Trade,
|
14
14
|
TradeType,
|
15
15
|
)
|
16
|
-
from prediction_market_agent_tooling.markets.omen.data_models import get_boolean_outcome
|
17
16
|
from prediction_market_agent_tooling.markets.omen.omen import (
|
18
17
|
get_buy_outcome_token_amount,
|
19
18
|
)
|
@@ -94,11 +93,8 @@ class BettingStrategy(ABC):
|
|
94
93
|
sell price is higher.
|
95
94
|
"""
|
96
95
|
trades = []
|
97
|
-
for
|
98
|
-
market.get_outcome_str_from_bool(
|
99
|
-
market.get_outcome_str_from_bool(False),
|
100
|
-
]:
|
101
|
-
outcome_bool = get_boolean_outcome(outcome)
|
96
|
+
for outcome_bool in [True, False]:
|
97
|
+
outcome = market.get_outcome_str_from_bool(outcome_bool)
|
102
98
|
prev_amount: TokenAmount = (
|
103
99
|
existing_position.amounts[outcome]
|
104
100
|
if existing_position and outcome in existing_position.amounts
|
@@ -284,8 +284,7 @@ class AgentMarket(BaseModel):
|
|
284
284
|
def has_unsuccessful_resolution(self) -> bool:
|
285
285
|
return self.resolution in [Resolution.CANCEL, Resolution.MKT]
|
286
286
|
|
287
|
-
|
288
|
-
def get_outcome_str_from_bool(outcome: bool) -> OutcomeStr:
|
287
|
+
def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
|
289
288
|
raise NotImplementedError("Subclasses must implement this method")
|
290
289
|
|
291
290
|
def get_outcome_str(self, outcome_index: int) -> str:
|
@@ -0,0 +1,82 @@
|
|
1
|
+
from web3 import Web3
|
2
|
+
from web3.constants import HASH_ZERO
|
3
|
+
|
4
|
+
from prediction_market_agent_tooling.config import APIKeys
|
5
|
+
from prediction_market_agent_tooling.gtypes import (
|
6
|
+
ChecksumAddress,
|
7
|
+
HexBytes,
|
8
|
+
HexStr,
|
9
|
+
xDai,
|
10
|
+
xdai_type,
|
11
|
+
)
|
12
|
+
from prediction_market_agent_tooling.loggers import logger
|
13
|
+
from prediction_market_agent_tooling.markets.agent_market import ProcessedTradedMarket
|
14
|
+
from prediction_market_agent_tooling.markets.omen.data_models import (
|
15
|
+
ContractPrediction,
|
16
|
+
IPFSAgentResult,
|
17
|
+
)
|
18
|
+
from prediction_market_agent_tooling.markets.omen.omen_contracts import (
|
19
|
+
OmenAgentResultMappingContract,
|
20
|
+
)
|
21
|
+
from prediction_market_agent_tooling.tools.balances import get_balances
|
22
|
+
from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
|
23
|
+
from prediction_market_agent_tooling.tools.utils import BPS_CONSTANT
|
24
|
+
from prediction_market_agent_tooling.tools.web3_utils import ipfscidv0_to_byte32
|
25
|
+
|
26
|
+
|
27
|
+
def get_total_balance(
|
28
|
+
address: ChecksumAddress,
|
29
|
+
web3: Web3 | None = None,
|
30
|
+
sum_xdai: bool = True,
|
31
|
+
sum_wxdai: bool = True,
|
32
|
+
) -> xDai:
|
33
|
+
"""
|
34
|
+
Checks if the total balance of xDai and wxDai in the wallet is above the minimum required balance.
|
35
|
+
"""
|
36
|
+
current_balances = get_balances(address, web3)
|
37
|
+
# xDai and wxDai have equal value and can be exchanged for almost no cost, so we can sum them up.
|
38
|
+
total_balance = 0.0
|
39
|
+
if sum_xdai:
|
40
|
+
total_balance += current_balances.xdai
|
41
|
+
if sum_wxdai:
|
42
|
+
total_balance += current_balances.wxdai
|
43
|
+
return xdai_type(total_balance)
|
44
|
+
|
45
|
+
|
46
|
+
def store_trades(
|
47
|
+
market_id: str,
|
48
|
+
traded_market: ProcessedTradedMarket | None,
|
49
|
+
keys: APIKeys,
|
50
|
+
agent_name: str,
|
51
|
+
) -> None:
|
52
|
+
if traded_market is None:
|
53
|
+
logger.warning(f"No prediction for market {market_id}, not storing anything.")
|
54
|
+
return
|
55
|
+
|
56
|
+
reasoning = traded_market.answer.reasoning if traded_market.answer.reasoning else ""
|
57
|
+
|
58
|
+
ipfs_hash_decoded = HexBytes(HASH_ZERO)
|
59
|
+
if keys.enable_ipfs_upload:
|
60
|
+
logger.info("Storing prediction on IPFS.")
|
61
|
+
ipfs_hash = IPFSHandler(keys).store_agent_result(
|
62
|
+
IPFSAgentResult(reasoning=reasoning, agent_name=agent_name)
|
63
|
+
)
|
64
|
+
ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
|
65
|
+
|
66
|
+
tx_hashes = [
|
67
|
+
HexBytes(HexStr(i.id)) for i in traded_market.trades if i.id is not None
|
68
|
+
]
|
69
|
+
prediction = ContractPrediction(
|
70
|
+
publisher=keys.bet_from_address,
|
71
|
+
ipfs_hash=ipfs_hash_decoded,
|
72
|
+
tx_hashes=tx_hashes,
|
73
|
+
estimated_probability_bps=int(traded_market.answer.p_yes * BPS_CONSTANT),
|
74
|
+
)
|
75
|
+
tx_receipt = OmenAgentResultMappingContract().add_prediction(
|
76
|
+
api_keys=keys,
|
77
|
+
market_address=Web3.to_checksum_address(market_id),
|
78
|
+
prediction=prediction,
|
79
|
+
)
|
80
|
+
logger.info(
|
81
|
+
f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].hex()}."
|
82
|
+
)
|
@@ -28,6 +28,7 @@ from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
|
|
28
28
|
from prediction_market_agent_tooling.markets.polymarket.polymarket import (
|
29
29
|
PolymarketAgentMarket,
|
30
30
|
)
|
31
|
+
from prediction_market_agent_tooling.markets.seer.seer import SeerAgentMarket
|
31
32
|
from prediction_market_agent_tooling.tools.utils import (
|
32
33
|
DatetimeUTC,
|
33
34
|
should_not_happen,
|
@@ -41,6 +42,7 @@ class MarketType(str, Enum):
|
|
41
42
|
MANIFOLD = "manifold"
|
42
43
|
POLYMARKET = "polymarket"
|
43
44
|
METACULUS = "metaculus"
|
45
|
+
SEER = "seer"
|
44
46
|
|
45
47
|
@property
|
46
48
|
def market_class(self) -> type[AgentMarket]:
|
@@ -56,7 +58,7 @@ class MarketType(str, Enum):
|
|
56
58
|
|
57
59
|
@property
|
58
60
|
def is_blockchain_market(self) -> bool:
|
59
|
-
return self in [MarketType.OMEN, MarketType.POLYMARKET]
|
61
|
+
return self in [MarketType.OMEN, MarketType.POLYMARKET, MarketType.SEER]
|
60
62
|
|
61
63
|
|
62
64
|
MARKET_TYPE_TO_AGENT_MARKET: dict[MarketType, type[AgentMarket]] = {
|
@@ -64,6 +66,7 @@ MARKET_TYPE_TO_AGENT_MARKET: dict[MarketType, type[AgentMarket]] = {
|
|
64
66
|
MarketType.OMEN: OmenAgentMarket,
|
65
67
|
MarketType.POLYMARKET: PolymarketAgentMarket,
|
66
68
|
MarketType.METACULUS: MetaculusAgentMarket,
|
69
|
+
MarketType.SEER: SeerAgentMarket,
|
67
70
|
}
|
68
71
|
|
69
72
|
JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET: dict[MarketType, type[JobAgentMarket]] = {
|
@@ -4,7 +4,6 @@ from datetime import timedelta
|
|
4
4
|
|
5
5
|
import tenacity
|
6
6
|
from web3 import Web3
|
7
|
-
from web3.constants import HASH_ZERO
|
8
7
|
|
9
8
|
from prediction_market_agent_tooling.config import APIKeys
|
10
9
|
from prediction_market_agent_tooling.gtypes import (
|
@@ -28,6 +27,10 @@ from prediction_market_agent_tooling.markets.agent_market import (
|
|
28
27
|
ProcessedTradedMarket,
|
29
28
|
SortBy,
|
30
29
|
)
|
30
|
+
from prediction_market_agent_tooling.markets.blockchain_utils import (
|
31
|
+
get_total_balance,
|
32
|
+
store_trades,
|
33
|
+
)
|
31
34
|
from prediction_market_agent_tooling.markets.data_models import (
|
32
35
|
Bet,
|
33
36
|
BetAmount,
|
@@ -42,9 +45,7 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
|
|
42
45
|
PRESAGIO_BASE_URL,
|
43
46
|
Condition,
|
44
47
|
ConditionPreparationEvent,
|
45
|
-
ContractPrediction,
|
46
48
|
CreatedMarket,
|
47
|
-
IPFSAgentResult,
|
48
49
|
OmenBet,
|
49
50
|
OmenMarket,
|
50
51
|
OmenUserPosition,
|
@@ -55,7 +56,6 @@ from prediction_market_agent_tooling.markets.omen.omen_contracts import (
|
|
55
56
|
OMEN_DEFAULT_MARKET_FEE_PERC,
|
56
57
|
REALITY_DEFAULT_FINALIZATION_TIMEOUT,
|
57
58
|
Arbitrator,
|
58
|
-
OmenAgentResultMappingContract,
|
59
59
|
OmenConditionalTokenContract,
|
60
60
|
OmenFixedProductMarketMakerContract,
|
61
61
|
OmenFixedProductMarketMakerFactoryContract,
|
@@ -74,7 +74,6 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
74
74
|
)
|
75
75
|
from prediction_market_agent_tooling.tools.custom_exceptions import OutOfFundsError
|
76
76
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
77
|
-
from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
|
78
77
|
from prediction_market_agent_tooling.tools.tokens.auto_deposit import (
|
79
78
|
auto_deposit_collateral_token,
|
80
79
|
)
|
@@ -82,7 +81,6 @@ from prediction_market_agent_tooling.tools.tokens.auto_withdraw import (
|
|
82
81
|
auto_withdraw_collateral_token,
|
83
82
|
)
|
84
83
|
from prediction_market_agent_tooling.tools.utils import (
|
85
|
-
BPS_CONSTANT,
|
86
84
|
DatetimeUTC,
|
87
85
|
calculate_sell_amount_in_collateral,
|
88
86
|
check_not_none,
|
@@ -90,7 +88,6 @@ from prediction_market_agent_tooling.tools.utils import (
|
|
90
88
|
from prediction_market_agent_tooling.tools.web3_utils import (
|
91
89
|
add_fraction,
|
92
90
|
get_receipt_block_timestamp,
|
93
|
-
ipfscidv0_to_byte32,
|
94
91
|
remove_fraction,
|
95
92
|
wei_to_xdai,
|
96
93
|
xdai_to_wei,
|
@@ -206,7 +203,7 @@ class OmenAgentMarket(AgentMarket):
|
|
206
203
|
self,
|
207
204
|
outcome: bool,
|
208
205
|
amount: BetAmount,
|
209
|
-
|
206
|
+
auto_deposit: bool = True,
|
210
207
|
web3: Web3 | None = None,
|
211
208
|
api_keys: APIKeys | None = None,
|
212
209
|
) -> str:
|
@@ -222,7 +219,7 @@ class OmenAgentMarket(AgentMarket):
|
|
222
219
|
amount=amount_xdai,
|
223
220
|
market=self,
|
224
221
|
binary_outcome=outcome,
|
225
|
-
auto_deposit=
|
222
|
+
auto_deposit=auto_deposit,
|
226
223
|
web3=web3,
|
227
224
|
)
|
228
225
|
|
@@ -434,38 +431,11 @@ class OmenAgentMarket(AgentMarket):
|
|
434
431
|
keys: APIKeys,
|
435
432
|
agent_name: str,
|
436
433
|
) -> None:
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
traded_market.answer.reasoning if traded_market.answer.reasoning else ""
|
443
|
-
)
|
444
|
-
|
445
|
-
ipfs_hash_decoded = HexBytes(HASH_ZERO)
|
446
|
-
if keys.enable_ipfs_upload:
|
447
|
-
logger.info("Storing prediction on IPFS.")
|
448
|
-
ipfs_hash = IPFSHandler(keys).store_agent_result(
|
449
|
-
IPFSAgentResult(reasoning=reasoning, agent_name=agent_name)
|
450
|
-
)
|
451
|
-
ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
|
452
|
-
|
453
|
-
tx_hashes = [
|
454
|
-
HexBytes(HexStr(i.id)) for i in traded_market.trades if i.id is not None
|
455
|
-
]
|
456
|
-
prediction = ContractPrediction(
|
457
|
-
publisher=keys.bet_from_address,
|
458
|
-
ipfs_hash=ipfs_hash_decoded,
|
459
|
-
tx_hashes=tx_hashes,
|
460
|
-
estimated_probability_bps=int(traded_market.answer.p_yes * BPS_CONSTANT),
|
461
|
-
)
|
462
|
-
tx_receipt = OmenAgentResultMappingContract().add_prediction(
|
463
|
-
api_keys=keys,
|
464
|
-
market_address=Web3.to_checksum_address(self.id),
|
465
|
-
prediction=prediction,
|
466
|
-
)
|
467
|
-
logger.info(
|
468
|
-
f"Added prediction to market {self.id}. - receipt {tx_receipt['transactionHash'].hex()}."
|
434
|
+
return store_trades(
|
435
|
+
market_id=self.id,
|
436
|
+
traded_market=traded_market,
|
437
|
+
keys=keys,
|
438
|
+
agent_name=agent_name,
|
469
439
|
)
|
470
440
|
|
471
441
|
@staticmethod
|
@@ -516,8 +486,7 @@ class OmenAgentMarket(AgentMarket):
|
|
516
486
|
cls.get_outcome_str(cls.index_set_to_outcome_index(index_set))
|
517
487
|
)
|
518
488
|
|
519
|
-
|
520
|
-
def get_outcome_str_from_bool(outcome: bool) -> OutcomeStr:
|
489
|
+
def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
|
521
490
|
return (
|
522
491
|
OutcomeStr(OMEN_TRUE_OUTCOME) if outcome else OutcomeStr(OMEN_FALSE_OUTCOME)
|
523
492
|
)
|
@@ -1302,25 +1271,6 @@ def get_binary_market_p_yes_history(market: OmenAgentMarket) -> list[Probability
|
|
1302
1271
|
return history
|
1303
1272
|
|
1304
1273
|
|
1305
|
-
def get_total_balance(
|
1306
|
-
address: ChecksumAddress,
|
1307
|
-
web3: Web3 | None = None,
|
1308
|
-
sum_xdai: bool = True,
|
1309
|
-
sum_wxdai: bool = True,
|
1310
|
-
) -> xDai:
|
1311
|
-
"""
|
1312
|
-
Checks if the total balance of xDai and wxDai in the wallet is above the minimum required balance.
|
1313
|
-
"""
|
1314
|
-
current_balances = get_balances(address, web3)
|
1315
|
-
# xDai and wxDai have equal value and can be exchanged for almost no cost, so we can sum them up.
|
1316
|
-
total_balance = 0.0
|
1317
|
-
if sum_xdai:
|
1318
|
-
total_balance += current_balances.xdai
|
1319
|
-
if sum_wxdai:
|
1320
|
-
total_balance += current_balances.wxdai
|
1321
|
-
return xdai_type(total_balance)
|
1322
|
-
|
1323
|
-
|
1324
1274
|
def withdraw_wxdai_to_xdai_to_keep_balance(
|
1325
1275
|
api_keys: APIKeys,
|
1326
1276
|
min_required_balance: xDai,
|
@@ -1,10 +1,26 @@
|
|
1
|
+
import re
|
1
2
|
import typing as t
|
3
|
+
from enum import Enum
|
4
|
+
from urllib.parse import urljoin
|
2
5
|
|
3
|
-
from eth_typing import HexAddress
|
4
6
|
from pydantic import BaseModel, ConfigDict, Field
|
7
|
+
from web3 import Web3
|
5
8
|
from web3.constants import ADDRESS_ZERO
|
6
9
|
|
7
|
-
from prediction_market_agent_tooling.
|
10
|
+
from prediction_market_agent_tooling.config import RPCConfig
|
11
|
+
from prediction_market_agent_tooling.gtypes import (
|
12
|
+
ChecksumAddress,
|
13
|
+
HexAddress,
|
14
|
+
HexBytes,
|
15
|
+
Probability,
|
16
|
+
Wei,
|
17
|
+
xdai_type,
|
18
|
+
)
|
19
|
+
from prediction_market_agent_tooling.loggers import logger
|
20
|
+
from prediction_market_agent_tooling.markets.data_models import Resolution
|
21
|
+
from prediction_market_agent_tooling.tools.cow.cow_manager import CowManager
|
22
|
+
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
23
|
+
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
|
8
24
|
|
9
25
|
|
10
26
|
class CreateCategoricalMarketsParams(BaseModel):
|
@@ -30,21 +46,184 @@ class CreateCategoricalMarketsParams(BaseModel):
|
|
30
46
|
token_names: list[str] = Field(..., alias="tokenNames")
|
31
47
|
|
32
48
|
|
49
|
+
class SeerOutcomeEnum(str, Enum):
|
50
|
+
YES = "yes"
|
51
|
+
NO = "no"
|
52
|
+
INVALID = "invalid"
|
53
|
+
|
54
|
+
@classmethod
|
55
|
+
def from_bool(cls, value: bool) -> "SeerOutcomeEnum":
|
56
|
+
return cls.YES if value else cls.NO
|
57
|
+
|
58
|
+
@classmethod
|
59
|
+
def from_string(cls, value: str) -> "SeerOutcomeEnum":
|
60
|
+
"""Convert a string (case-insensitive) to an Outcome enum."""
|
61
|
+
normalized = value.strip().lower()
|
62
|
+
patterns = {
|
63
|
+
r"^yes$": cls.YES,
|
64
|
+
r"^no$": cls.NO,
|
65
|
+
r"^(invalid|invalid result)$": cls.INVALID,
|
66
|
+
}
|
67
|
+
|
68
|
+
# Search through patterns and return the first match
|
69
|
+
for pattern, outcome in patterns.items():
|
70
|
+
if re.search(pattern, normalized):
|
71
|
+
return outcome
|
72
|
+
|
73
|
+
raise ValueError(f"Could not map {value=} to an outcome.")
|
74
|
+
|
75
|
+
def to_bool(self) -> bool:
|
76
|
+
"""Convert a SeerOutcomeEnum to a boolean value."""
|
77
|
+
if self == self.YES:
|
78
|
+
return True
|
79
|
+
elif self == self.NO:
|
80
|
+
return False
|
81
|
+
elif self == self.INVALID:
|
82
|
+
raise ValueError("Cannot convert INVALID outcome to boolean.")
|
83
|
+
else:
|
84
|
+
raise ValueError(f"Unknown outcome: {self}")
|
85
|
+
|
86
|
+
|
33
87
|
class SeerParentMarket(BaseModel):
|
34
88
|
id: HexBytes
|
35
89
|
|
36
90
|
|
91
|
+
SEER_BASE_URL = "https://app.seer.pm"
|
92
|
+
|
93
|
+
|
37
94
|
class SeerMarket(BaseModel):
|
38
95
|
model_config = ConfigDict(populate_by_name=True)
|
39
96
|
|
40
97
|
id: HexBytes
|
98
|
+
creator: HexAddress
|
41
99
|
title: str = Field(alias="marketName")
|
42
100
|
outcomes: list[str]
|
43
|
-
wrapped_tokens: list[
|
101
|
+
wrapped_tokens: list[HexAddress] = Field(alias="wrappedTokens")
|
44
102
|
parent_outcome: int = Field(alias="parentOutcome")
|
45
103
|
parent_market: t.Optional[SeerParentMarket] = Field(
|
46
104
|
alias="parentMarket", default=None
|
47
105
|
)
|
106
|
+
collateral_token: HexAddress = Field(alias="collateralToken")
|
107
|
+
condition_id: HexBytes = Field(alias="conditionId")
|
108
|
+
opening_ts: int = Field(alias="openingTs")
|
109
|
+
block_timestamp: int = Field(alias="blockTimestamp")
|
110
|
+
has_answers: bool | None = Field(alias="hasAnswers")
|
111
|
+
payout_reported: bool = Field(alias="payoutReported")
|
112
|
+
payout_numerators: list[int] = Field(alias="payoutNumerators")
|
113
|
+
|
114
|
+
@property
|
115
|
+
def has_valid_answer(self) -> bool:
|
116
|
+
# We assume that, for the market to be resolved as invalid, it must have both:
|
117
|
+
# 1. An invalid outcome AND
|
118
|
+
# 2. Invalid payoutNumerator is 1.
|
119
|
+
|
120
|
+
try:
|
121
|
+
self.outcome_as_enums[SeerOutcomeEnum.INVALID]
|
122
|
+
except KeyError:
|
123
|
+
raise ValueError(
|
124
|
+
f"Market {self.id.hex()} has no invalid outcome. {self.outcomes}"
|
125
|
+
)
|
126
|
+
|
127
|
+
return self.payout_reported and self.payout_numerators[-1] != 1
|
128
|
+
|
129
|
+
@property
|
130
|
+
def outcome_as_enums(self) -> dict[SeerOutcomeEnum, int]:
|
131
|
+
return {
|
132
|
+
SeerOutcomeEnum.from_string(outcome): idx
|
133
|
+
for idx, outcome in enumerate(self.outcomes)
|
134
|
+
}
|
135
|
+
|
136
|
+
@property
|
137
|
+
def is_resolved(self) -> bool:
|
138
|
+
return self.payout_reported
|
139
|
+
|
140
|
+
@property
|
141
|
+
def is_resolved_with_valid_answer(self) -> bool:
|
142
|
+
return self.is_resolved and self.has_valid_answer
|
143
|
+
|
144
|
+
def get_resolution_enum(self) -> t.Optional[Resolution]:
|
145
|
+
if not self.is_resolved_with_valid_answer:
|
146
|
+
return None
|
147
|
+
|
148
|
+
max_idx = self.payout_numerators.index(1)
|
149
|
+
|
150
|
+
outcome: str = self.outcomes[max_idx]
|
151
|
+
outcome_enum = SeerOutcomeEnum.from_string(outcome)
|
152
|
+
if outcome_enum.to_bool():
|
153
|
+
return Resolution.YES
|
154
|
+
return Resolution.NO
|
155
|
+
|
156
|
+
@property
|
157
|
+
def is_binary(self) -> bool:
|
158
|
+
# 3 because Seer has also third, `Invalid` outcome.
|
159
|
+
return len(self.outcomes) == 3
|
160
|
+
|
161
|
+
def boolean_outcome_from_answer(self, answer: HexBytes) -> bool:
|
162
|
+
if not self.is_binary:
|
163
|
+
raise ValueError(
|
164
|
+
f"Market with title {self.title} is not binary, it has {len(self.outcomes)} outcomes."
|
165
|
+
)
|
166
|
+
|
167
|
+
outcome: str = self.outcomes[answer.as_int()]
|
168
|
+
outcome_enum = SeerOutcomeEnum.from_string(outcome)
|
169
|
+
return outcome_enum.to_bool()
|
170
|
+
|
171
|
+
def get_resolution_enum_from_answer(self, answer: HexBytes) -> Resolution:
|
172
|
+
if self.boolean_outcome_from_answer(answer):
|
173
|
+
return Resolution.YES
|
174
|
+
else:
|
175
|
+
return Resolution.NO
|
176
|
+
|
177
|
+
@property
|
178
|
+
def collateral_token_contract_address_checksummed(self) -> ChecksumAddress:
|
179
|
+
return Web3.to_checksum_address(self.collateral_token)
|
180
|
+
|
181
|
+
@property
|
182
|
+
def close_time(self) -> DatetimeUTC:
|
183
|
+
return DatetimeUTC.to_datetime_utc(self.opening_ts)
|
184
|
+
|
185
|
+
@property
|
186
|
+
def created_time(self) -> DatetimeUTC:
|
187
|
+
return DatetimeUTC.to_datetime_utc(self.block_timestamp)
|
188
|
+
|
189
|
+
@property
|
190
|
+
def current_p_yes(self) -> Probability:
|
191
|
+
price_data = {}
|
192
|
+
for idx in range(len(self.outcomes)):
|
193
|
+
wrapped_token = self.wrapped_tokens[idx]
|
194
|
+
price = self._get_price_for_token(
|
195
|
+
token=Web3.to_checksum_address(wrapped_token)
|
196
|
+
)
|
197
|
+
price_data[idx] = price
|
198
|
+
|
199
|
+
if sum(price_data.values()) == 0:
|
200
|
+
logger.warning(
|
201
|
+
f"Could not get p_yes for market {self.id.hex()}, all price quotes are 0."
|
202
|
+
)
|
203
|
+
return Probability(0)
|
204
|
+
|
205
|
+
yes_idx = self.outcome_as_enums[SeerOutcomeEnum.YES]
|
206
|
+
price_yes = price_data[yes_idx] / sum(price_data.values())
|
207
|
+
return Probability(price_yes)
|
208
|
+
|
209
|
+
def _get_price_for_token(self, token: ChecksumAddress) -> float:
|
210
|
+
collateral_exchange_amount = xdai_to_wei(xdai_type(1))
|
211
|
+
try:
|
212
|
+
quote = CowManager().get_quote(
|
213
|
+
collateral_token=self.collateral_token_contract_address_checksummed,
|
214
|
+
buy_token=token,
|
215
|
+
sell_amount=collateral_exchange_amount,
|
216
|
+
)
|
217
|
+
except Exception as e:
|
218
|
+
logger.warning(f"Could not get quote for {token=}, returning price 0. {e=}")
|
219
|
+
return 0
|
220
|
+
|
221
|
+
return collateral_exchange_amount / float(quote.quote.buyAmount.root)
|
222
|
+
|
223
|
+
@property
|
224
|
+
def url(self) -> str:
|
225
|
+
chain_id = RPCConfig().chain_id
|
226
|
+
return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.hex()}")
|
48
227
|
|
49
228
|
|
50
229
|
class SeerToken(BaseModel):
|
@@ -1,22 +1,290 @@
|
|
1
|
+
import typing as t
|
2
|
+
|
1
3
|
from eth_typing import ChecksumAddress
|
2
4
|
from web3 import Web3
|
3
5
|
from web3.types import TxReceipt
|
4
6
|
|
5
7
|
from prediction_market_agent_tooling.config import APIKeys
|
6
|
-
from prediction_market_agent_tooling.gtypes import
|
7
|
-
|
8
|
+
from prediction_market_agent_tooling.gtypes import (
|
9
|
+
HexAddress,
|
10
|
+
HexBytes,
|
11
|
+
OutcomeStr,
|
12
|
+
Wei,
|
13
|
+
wei_type,
|
14
|
+
xDai,
|
15
|
+
xdai_type,
|
16
|
+
)
|
17
|
+
from prediction_market_agent_tooling.loggers import logger
|
18
|
+
from prediction_market_agent_tooling.markets.agent_market import (
|
19
|
+
AgentMarket,
|
20
|
+
FilterBy,
|
21
|
+
ProcessedMarket,
|
22
|
+
ProcessedTradedMarket,
|
23
|
+
SortBy,
|
24
|
+
)
|
25
|
+
from prediction_market_agent_tooling.markets.blockchain_utils import (
|
26
|
+
get_total_balance,
|
27
|
+
store_trades,
|
28
|
+
)
|
29
|
+
from prediction_market_agent_tooling.markets.data_models import (
|
30
|
+
BetAmount,
|
31
|
+
Currency,
|
32
|
+
Position,
|
33
|
+
TokenAmount,
|
34
|
+
)
|
35
|
+
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
36
|
+
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
37
|
+
from prediction_market_agent_tooling.markets.omen.omen_contracts import sDaiContract
|
38
|
+
from prediction_market_agent_tooling.markets.seer.data_models import (
|
39
|
+
NewMarketEvent,
|
40
|
+
SeerMarket,
|
41
|
+
SeerOutcomeEnum,
|
42
|
+
)
|
8
43
|
from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
9
44
|
SeerMarketFactory,
|
10
45
|
)
|
46
|
+
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
47
|
+
SeerSubgraphHandler,
|
48
|
+
)
|
49
|
+
from prediction_market_agent_tooling.tools.balances import get_balances
|
11
50
|
from prediction_market_agent_tooling.tools.contract import (
|
51
|
+
ContractERC20OnGnosisChain,
|
12
52
|
init_collateral_token_contract,
|
13
53
|
to_gnosis_chain_contract,
|
14
54
|
)
|
55
|
+
from prediction_market_agent_tooling.tools.cow.cow_manager import (
|
56
|
+
CowManager,
|
57
|
+
NoLiquidityAvailableOnCowException,
|
58
|
+
)
|
15
59
|
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
16
60
|
from prediction_market_agent_tooling.tools.tokens.auto_deposit import (
|
17
61
|
auto_deposit_collateral_token,
|
18
62
|
)
|
19
|
-
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
|
63
|
+
from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai, xdai_to_wei
|
64
|
+
|
65
|
+
# We place a larger bet amount by default than Omen so that cow presents valid quotes.
|
66
|
+
SEER_TINY_BET_AMOUNT = xdai_type(0.1)
|
67
|
+
|
68
|
+
|
69
|
+
class SeerAgentMarket(AgentMarket):
|
70
|
+
currency = Currency.sDai
|
71
|
+
wrapped_tokens: list[ChecksumAddress]
|
72
|
+
creator: HexAddress
|
73
|
+
collateral_token_contract_address_checksummed: ChecksumAddress
|
74
|
+
condition_id: HexBytes
|
75
|
+
seer_outcomes: dict[SeerOutcomeEnum, int]
|
76
|
+
description: str | None = (
|
77
|
+
None # Seer markets don't have a description, so just default to None.
|
78
|
+
)
|
79
|
+
|
80
|
+
def store_prediction(
|
81
|
+
self,
|
82
|
+
processed_market: ProcessedMarket | None,
|
83
|
+
keys: APIKeys,
|
84
|
+
agent_name: str,
|
85
|
+
) -> None:
|
86
|
+
"""On Seer, we have to store predictions along with trades, see `store_trades`."""
|
87
|
+
|
88
|
+
def store_trades(
|
89
|
+
self,
|
90
|
+
traded_market: ProcessedTradedMarket | None,
|
91
|
+
keys: APIKeys,
|
92
|
+
agent_name: str,
|
93
|
+
) -> None:
|
94
|
+
return store_trades(
|
95
|
+
market_id=self.id,
|
96
|
+
traded_market=traded_market,
|
97
|
+
keys=keys,
|
98
|
+
agent_name=agent_name,
|
99
|
+
)
|
100
|
+
|
101
|
+
def _convert_bet_amount_into_wei(self, bet_amount: BetAmount) -> Wei:
|
102
|
+
if bet_amount.currency == self.currency:
|
103
|
+
return xdai_to_wei(xdai_type(bet_amount.amount))
|
104
|
+
raise ValueError(
|
105
|
+
f"Currencies don't match. Currency bet amount {bet_amount.currency} currency market: {self.currency}"
|
106
|
+
)
|
107
|
+
|
108
|
+
def get_buy_token_amount(
|
109
|
+
self, bet_amount: BetAmount, direction: bool
|
110
|
+
) -> TokenAmount:
|
111
|
+
"""Returns number of outcome tokens returned for a given bet expressed in collateral units."""
|
112
|
+
|
113
|
+
outcome_token = self.get_wrapped_token_for_outcome(direction)
|
114
|
+
|
115
|
+
bet_amount_in_wei = self._convert_bet_amount_into_wei(bet_amount=bet_amount)
|
116
|
+
|
117
|
+
quote = CowManager().get_quote(
|
118
|
+
buy_token=outcome_token,
|
119
|
+
sell_amount=bet_amount_in_wei,
|
120
|
+
collateral_token=self.collateral_token_contract_address_checksummed,
|
121
|
+
)
|
122
|
+
sell_amount = wei_to_xdai(wei_type(quote.quote.buyAmount.root))
|
123
|
+
return TokenAmount(amount=sell_amount, currency=bet_amount.currency)
|
124
|
+
|
125
|
+
def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
|
126
|
+
outcome_translated = SeerOutcomeEnum.from_bool(outcome)
|
127
|
+
idx = self.seer_outcomes[outcome_translated]
|
128
|
+
return OutcomeStr(self.outcomes[idx])
|
129
|
+
|
130
|
+
@staticmethod
|
131
|
+
def get_trade_balance(api_keys: APIKeys) -> float:
|
132
|
+
return OmenAgentMarket.get_trade_balance(api_keys=api_keys)
|
133
|
+
|
134
|
+
@classmethod
|
135
|
+
def get_tiny_bet_amount(cls) -> BetAmount:
|
136
|
+
return BetAmount(amount=SEER_TINY_BET_AMOUNT, currency=cls.currency)
|
137
|
+
|
138
|
+
def get_position(self, user_id: str, web3: Web3 | None = None) -> Position | None:
|
139
|
+
"""
|
140
|
+
Fetches position from the user in a given market.
|
141
|
+
We ignore the INVALID balances since we are only interested in binary outcomes.
|
142
|
+
"""
|
143
|
+
|
144
|
+
amounts = {}
|
145
|
+
|
146
|
+
for outcome in [True, False]:
|
147
|
+
wrapped_token = self.get_wrapped_token_for_outcome(outcome)
|
148
|
+
|
149
|
+
outcome_token_balance = ContractERC20OnGnosisChain(
|
150
|
+
address=wrapped_token
|
151
|
+
).balanceOf(for_address=Web3.to_checksum_address(user_id), web3=web3)
|
152
|
+
outcome_str = self.get_outcome_str_from_bool(outcome=outcome)
|
153
|
+
amounts[outcome_str] = TokenAmount(
|
154
|
+
amount=wei_to_xdai(outcome_token_balance), currency=self.currency
|
155
|
+
)
|
156
|
+
|
157
|
+
return Position(market_id=self.id, amounts=amounts)
|
158
|
+
|
159
|
+
@staticmethod
|
160
|
+
def get_user_id(api_keys: APIKeys) -> str:
|
161
|
+
return OmenAgentMarket.get_user_id(api_keys)
|
162
|
+
|
163
|
+
@staticmethod
|
164
|
+
def redeem_winnings(api_keys: APIKeys) -> None:
|
165
|
+
# ToDo - implement me (https://github.com/gnosis/prediction-market-agent-tooling/issues/499)
|
166
|
+
pass
|
167
|
+
|
168
|
+
@staticmethod
|
169
|
+
def verify_operational_balance(api_keys: APIKeys) -> bool:
|
170
|
+
return get_total_balance(
|
171
|
+
api_keys.public_key,
|
172
|
+
# Use `public_key`, not `bet_from_address` because transaction costs are paid from the EOA wallet.
|
173
|
+
sum_wxdai=False,
|
174
|
+
) > xdai_type(0.001)
|
175
|
+
|
176
|
+
@staticmethod
|
177
|
+
def from_data_model(model: SeerMarket) -> "SeerAgentMarket":
|
178
|
+
return SeerAgentMarket(
|
179
|
+
id=model.id.hex(),
|
180
|
+
question=model.title,
|
181
|
+
creator=model.creator,
|
182
|
+
created_time=model.created_time,
|
183
|
+
outcomes=model.outcomes,
|
184
|
+
collateral_token_contract_address_checksummed=model.collateral_token_contract_address_checksummed,
|
185
|
+
condition_id=model.condition_id,
|
186
|
+
url=model.url,
|
187
|
+
close_time=model.close_time,
|
188
|
+
wrapped_tokens=[Web3.to_checksum_address(i) for i in model.wrapped_tokens],
|
189
|
+
fees=MarketFees.get_zero_fees(),
|
190
|
+
outcome_token_pool=None,
|
191
|
+
resolution=model.get_resolution_enum(),
|
192
|
+
volume=None,
|
193
|
+
current_p_yes=model.current_p_yes,
|
194
|
+
seer_outcomes=model.outcome_as_enums,
|
195
|
+
)
|
196
|
+
|
197
|
+
@staticmethod
|
198
|
+
def get_binary_markets(
|
199
|
+
limit: int,
|
200
|
+
sort_by: SortBy,
|
201
|
+
filter_by: FilterBy = FilterBy.OPEN,
|
202
|
+
created_after: t.Optional[DatetimeUTC] = None,
|
203
|
+
excluded_questions: set[str] | None = None,
|
204
|
+
) -> t.Sequence["SeerAgentMarket"]:
|
205
|
+
return [
|
206
|
+
SeerAgentMarket.from_data_model(m)
|
207
|
+
for m in SeerSubgraphHandler().get_binary_markets(
|
208
|
+
limit=limit,
|
209
|
+
sort_by=sort_by,
|
210
|
+
filter_by=filter_by,
|
211
|
+
)
|
212
|
+
]
|
213
|
+
|
214
|
+
def has_liquidity_for_outcome(self, outcome: bool) -> bool:
|
215
|
+
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
216
|
+
try:
|
217
|
+
CowManager().get_quote(
|
218
|
+
collateral_token=self.collateral_token_contract_address_checksummed,
|
219
|
+
buy_token=outcome_token,
|
220
|
+
sell_amount=xdai_to_wei(
|
221
|
+
xdai_type(1)
|
222
|
+
), # we take 1 xDai as a baseline value for common trades the agents take.
|
223
|
+
)
|
224
|
+
return True
|
225
|
+
except NoLiquidityAvailableOnCowException:
|
226
|
+
logger.info(
|
227
|
+
f"Could not get a quote for {outcome_token=} {outcome=}, returning no liquidity"
|
228
|
+
)
|
229
|
+
return False
|
230
|
+
|
231
|
+
def has_liquidity(self) -> bool:
|
232
|
+
# We conservatively define a market as having liquidity if it has liquidity for the `True` outcome token AND the `False` outcome token.
|
233
|
+
return self.has_liquidity_for_outcome(True) and self.has_liquidity_for_outcome(
|
234
|
+
False
|
235
|
+
)
|
236
|
+
|
237
|
+
def get_wrapped_token_for_outcome(self, outcome: bool) -> ChecksumAddress:
|
238
|
+
outcome_from_enum = SeerOutcomeEnum.from_bool(outcome)
|
239
|
+
outcome_idx = self.seer_outcomes[outcome_from_enum]
|
240
|
+
outcome_token = self.wrapped_tokens[outcome_idx]
|
241
|
+
return outcome_token
|
242
|
+
|
243
|
+
def place_bet(
|
244
|
+
self,
|
245
|
+
outcome: bool,
|
246
|
+
amount: BetAmount,
|
247
|
+
auto_deposit: bool = True,
|
248
|
+
web3: Web3 | None = None,
|
249
|
+
api_keys: APIKeys | None = None,
|
250
|
+
) -> str:
|
251
|
+
api_keys = api_keys if api_keys is not None else APIKeys()
|
252
|
+
if not self.can_be_traded():
|
253
|
+
raise ValueError(
|
254
|
+
f"Market {self.id} is not open for trading. Cannot place bet."
|
255
|
+
)
|
256
|
+
|
257
|
+
if amount.currency != self.currency:
|
258
|
+
raise ValueError(f"Seer bets are made in xDai. Got {amount.currency}.")
|
259
|
+
|
260
|
+
collateral_contract = sDaiContract()
|
261
|
+
if auto_deposit:
|
262
|
+
# We convert the deposit amount (in sDai) to assets in order to convert.
|
263
|
+
asset_amount = collateral_contract.convertToAssets(
|
264
|
+
xdai_to_wei(xdai_type(amount.amount))
|
265
|
+
)
|
266
|
+
auto_deposit_collateral_token(
|
267
|
+
collateral_contract, asset_amount, api_keys, web3
|
268
|
+
)
|
269
|
+
|
270
|
+
# We require that amount is given in sDAI.
|
271
|
+
collateral_balance = get_balances(address=api_keys.bet_from_address, web3=web3)
|
272
|
+
if collateral_balance.sdai < amount.amount:
|
273
|
+
raise ValueError(
|
274
|
+
f"Balance {collateral_balance.sdai} not enough for bet size {amount.amount}"
|
275
|
+
)
|
276
|
+
|
277
|
+
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
278
|
+
# Sell sDAI using token address
|
279
|
+
order_metadata = CowManager().swap(
|
280
|
+
amount=xdai_type(amount.amount),
|
281
|
+
sell_token=collateral_contract.address,
|
282
|
+
buy_token=Web3.to_checksum_address(outcome_token),
|
283
|
+
api_keys=api_keys,
|
284
|
+
web3=web3,
|
285
|
+
)
|
286
|
+
|
287
|
+
return order_metadata.uid.root
|
20
288
|
|
21
289
|
|
22
290
|
def seer_create_market_tx(
|
@@ -1,8 +1,11 @@
|
|
1
|
+
import sys
|
2
|
+
import typing as t
|
1
3
|
from typing import Any
|
2
4
|
|
3
5
|
from subgrounds import FieldPath
|
4
6
|
from web3.constants import ADDRESS_ZERO
|
5
7
|
|
8
|
+
from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
|
6
9
|
from prediction_market_agent_tooling.markets.base_subgraph_handler import (
|
7
10
|
BaseSubgraphHandler,
|
8
11
|
)
|
@@ -11,8 +14,7 @@ from prediction_market_agent_tooling.markets.seer.data_models import (
|
|
11
14
|
SeerPool,
|
12
15
|
)
|
13
16
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
14
|
-
|
15
|
-
INVALID_OUTCOME = "Invalid result"
|
17
|
+
from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
|
16
18
|
|
17
19
|
|
18
20
|
class SeerSubgraphHandler(BaseSubgraphHandler):
|
@@ -45,21 +47,26 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
45
47
|
markets_field.id,
|
46
48
|
markets_field.factory,
|
47
49
|
markets_field.creator,
|
50
|
+
markets_field.conditionId,
|
48
51
|
markets_field.marketName,
|
49
52
|
markets_field.parentOutcome,
|
50
53
|
markets_field.outcomes,
|
54
|
+
markets_field.payoutReported,
|
55
|
+
markets_field.payoutNumerators,
|
56
|
+
markets_field.hasAnswers,
|
57
|
+
markets_field.blockTimestamp,
|
51
58
|
markets_field.parentMarket.id,
|
59
|
+
markets_field.openingTs,
|
52
60
|
markets_field.finalizeTs,
|
53
61
|
markets_field.wrappedTokens,
|
62
|
+
markets_field.collateralToken,
|
54
63
|
]
|
55
64
|
return fields
|
56
65
|
|
57
66
|
@staticmethod
|
58
67
|
def filter_bicategorical_markets(markets: list[SeerMarket]) -> list[SeerMarket]:
|
59
68
|
# We do an extra check for the invalid outcome for safety.
|
60
|
-
return [
|
61
|
-
m for m in markets if len(m.outcomes) == 3 and INVALID_OUTCOME in m.outcomes
|
62
|
-
]
|
69
|
+
return [m for m in markets if len(m.outcomes) == 3]
|
63
70
|
|
64
71
|
@staticmethod
|
65
72
|
def filter_binary_markets(markets: list[SeerMarket]) -> list[SeerMarket]:
|
@@ -70,35 +77,110 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
70
77
|
]
|
71
78
|
|
72
79
|
@staticmethod
|
73
|
-
def
|
74
|
-
|
80
|
+
def _build_where_statements(
|
81
|
+
filter_by: FilterBy,
|
82
|
+
include_conditional_markets: bool = False,
|
75
83
|
) -> dict[Any, Any]:
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
84
|
+
now = to_int_timestamp(utcnow())
|
85
|
+
|
86
|
+
and_stms: dict[str, t.Any] = {}
|
87
|
+
|
88
|
+
match filter_by:
|
89
|
+
case FilterBy.OPEN:
|
90
|
+
and_stms["openingTs_gt"] = now
|
91
|
+
and_stms["hasAnswers"] = False
|
92
|
+
case FilterBy.RESOLVED:
|
93
|
+
# We consider RESOLVED == CLOSED (on Seer UI)
|
94
|
+
and_stms["payoutReported"] = True
|
95
|
+
case FilterBy.NONE:
|
96
|
+
pass
|
97
|
+
case _:
|
98
|
+
raise ValueError(f"Unknown filter {filter_by}")
|
99
|
+
|
100
|
+
if not include_conditional_markets:
|
101
|
+
and_stms["parentMarket"] = ADDRESS_ZERO.lower()
|
102
|
+
|
103
|
+
# We are only interested in binary markets of type YES/NO/Invalid.
|
104
|
+
or_stms = {}
|
105
|
+
or_stms["or"] = [
|
106
|
+
{"outcomes_contains": ["YES"]},
|
107
|
+
{"outcomes_contains": ["Yes"]},
|
108
|
+
{"outcomes_contains": ["yes"]},
|
109
|
+
]
|
110
|
+
|
111
|
+
where_stms: dict[str, t.Any] = {"and": [and_stms, or_stms]}
|
112
|
+
return where_stms
|
113
|
+
|
114
|
+
def _build_sort_params(
|
115
|
+
self, sort_by: SortBy
|
116
|
+
) -> tuple[str | None, FieldPath | None]:
|
117
|
+
sort_direction: str | None
|
118
|
+
sort_by_field: FieldPath | None
|
119
|
+
|
120
|
+
match sort_by:
|
121
|
+
case SortBy.NEWEST:
|
122
|
+
sort_direction = "desc"
|
123
|
+
sort_by_field = self.seer_subgraph.Market.block_timestamp
|
124
|
+
case SortBy.CLOSING_SOONEST:
|
125
|
+
sort_direction = "asc"
|
126
|
+
sort_by_field = self.seer_subgraph.Market.opening_ts
|
127
|
+
# ToDo - Implement liquidity conditions by looking up Swapr subgraph.
|
128
|
+
case SortBy.NONE | SortBy.HIGHEST_LIQUIDITY | SortBy.LOWEST_LIQUIDITY:
|
129
|
+
sort_direction = None
|
130
|
+
sort_by_field = None
|
131
|
+
case _:
|
132
|
+
raise ValueError(f"Unknown sort_by: {sort_by}")
|
133
|
+
|
134
|
+
return sort_direction, sort_by_field
|
81
135
|
|
82
136
|
def get_bicategorical_markets(
|
83
|
-
self,
|
137
|
+
self,
|
138
|
+
filter_by: FilterBy,
|
139
|
+
limit: int | None = None,
|
140
|
+
sort_by_field: FieldPath | None = None,
|
141
|
+
sort_direction: str | None = None,
|
142
|
+
include_conditional_markets: bool = True,
|
84
143
|
) -> list[SeerMarket]:
|
85
144
|
"""Returns markets that contain 2 categories plus an invalid outcome."""
|
86
145
|
# Binary markets on Seer contain 3 outcomes: OutcomeA, outcomeB and an Invalid option.
|
87
|
-
|
88
|
-
include_conditional_markets
|
146
|
+
where_stms = self._build_where_statements(
|
147
|
+
filter_by=filter_by, include_conditional_markets=include_conditional_markets
|
148
|
+
)
|
149
|
+
|
150
|
+
# These values can not be set to `None`, but they can be omitted.
|
151
|
+
optional_params = {}
|
152
|
+
if sort_by_field is not None:
|
153
|
+
optional_params["orderBy"] = sort_by_field
|
154
|
+
if sort_direction is not None:
|
155
|
+
optional_params["orderDirection"] = sort_direction
|
156
|
+
|
157
|
+
markets_field = self.seer_subgraph.Query.markets(
|
158
|
+
first=(
|
159
|
+
limit if limit else sys.maxsize
|
160
|
+
), # if not limit, we fetch all possible markets,
|
161
|
+
where=where_stms,
|
162
|
+
**optional_params,
|
89
163
|
)
|
90
|
-
query_filter["outcomes_contains"] = [INVALID_OUTCOME]
|
91
|
-
markets_field = self.seer_subgraph.Query.markets(where=query_filter)
|
92
164
|
fields = self._get_fields_for_markets(markets_field)
|
93
165
|
markets = self.do_query(fields=fields, pydantic_model=SeerMarket)
|
94
166
|
two_category_markets = self.filter_bicategorical_markets(markets)
|
95
167
|
return two_category_markets
|
96
168
|
|
97
169
|
def get_binary_markets(
|
98
|
-
self,
|
170
|
+
self,
|
171
|
+
filter_by: FilterBy,
|
172
|
+
sort_by: SortBy = SortBy.NONE,
|
173
|
+
limit: int | None = None,
|
174
|
+
include_conditional_markets: bool = True,
|
99
175
|
) -> list[SeerMarket]:
|
176
|
+
sort_direction, sort_by_field = self._build_sort_params(sort_by)
|
177
|
+
|
100
178
|
two_category_markets = self.get_bicategorical_markets(
|
101
|
-
|
179
|
+
limit=limit,
|
180
|
+
include_conditional_markets=include_conditional_markets,
|
181
|
+
sort_direction=sort_direction,
|
182
|
+
sort_by_field=sort_by_field,
|
183
|
+
filter_by=filter_by,
|
102
184
|
)
|
103
185
|
# Now we additionally filter markets based on YES/NO being the only outcomes.
|
104
186
|
binary_markets = self.filter_binary_markets(two_category_markets)
|
@@ -133,8 +215,8 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
133
215
|
for wrapped_token in market.wrapped_tokens:
|
134
216
|
wheres.extend(
|
135
217
|
[
|
136
|
-
{"token0": wrapped_token.
|
137
|
-
{"token1": wrapped_token.
|
218
|
+
{"token0": wrapped_token.lower()},
|
219
|
+
{"token1": wrapped_token.lower()},
|
138
220
|
]
|
139
221
|
)
|
140
222
|
pools_field = self.swapr_algebra_subgraph.Query.pools(where={"or": wheres})
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
from cowdao_cowpy.common.api.errors import UnexpectedResponseError
|
4
|
+
from cowdao_cowpy.common.config import SupportedChainId
|
5
|
+
from cowdao_cowpy.cow.swap import get_order_quote
|
6
|
+
from cowdao_cowpy.order_book.api import OrderBookApi
|
7
|
+
from cowdao_cowpy.order_book.config import Envs, OrderBookAPIConfigFactory
|
8
|
+
from cowdao_cowpy.order_book.generated.model import (
|
9
|
+
Address,
|
10
|
+
OrderMetaData,
|
11
|
+
OrderQuoteRequest,
|
12
|
+
OrderQuoteResponse,
|
13
|
+
OrderQuoteSide1,
|
14
|
+
OrderQuoteSideKindSell,
|
15
|
+
)
|
16
|
+
from cowdao_cowpy.order_book.generated.model import TokenAmount as TokenAmountCow
|
17
|
+
from tenacity import retry, retry_if_not_exception_type, stop_after_attempt, wait_fixed
|
18
|
+
from web3 import Web3
|
19
|
+
from web3.constants import ADDRESS_ZERO
|
20
|
+
|
21
|
+
from prediction_market_agent_tooling.config import APIKeys
|
22
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei, xDai
|
23
|
+
from prediction_market_agent_tooling.loggers import logger
|
24
|
+
from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting
|
25
|
+
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
|
26
|
+
|
27
|
+
COW_ENV: Envs = "prod"
|
28
|
+
|
29
|
+
|
30
|
+
class NoLiquidityAvailableOnCowException(Exception):
|
31
|
+
"""Custom exception for handling case where no liquidity available."""
|
32
|
+
|
33
|
+
|
34
|
+
class CowManager:
|
35
|
+
def __init__(self) -> None:
|
36
|
+
self.order_book_api = OrderBookApi(
|
37
|
+
OrderBookAPIConfigFactory.get_config(COW_ENV, SupportedChainId.GNOSIS_CHAIN)
|
38
|
+
)
|
39
|
+
self.precision = 18 # number of token decimals from ERC1155 wrapped tokens.
|
40
|
+
|
41
|
+
@retry(
|
42
|
+
stop=stop_after_attempt(2),
|
43
|
+
wait=wait_fixed(2),
|
44
|
+
retry=retry_if_not_exception_type(NoLiquidityAvailableOnCowException),
|
45
|
+
)
|
46
|
+
def get_quote(
|
47
|
+
self,
|
48
|
+
collateral_token: ChecksumAddress,
|
49
|
+
buy_token: ChecksumAddress,
|
50
|
+
sell_amount: Wei,
|
51
|
+
) -> OrderQuoteResponse:
|
52
|
+
"""
|
53
|
+
Quote the price for a sell order.
|
54
|
+
|
55
|
+
Parameters:
|
56
|
+
- collateral_token: The token being sold.
|
57
|
+
- buy_token: The token being bought.
|
58
|
+
- sell_amount: The amount of collateral to sell in atoms.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
- An OrderQuoteResponse containing the quote information.
|
62
|
+
|
63
|
+
Raises:
|
64
|
+
- NoLiquidityAvailableOnCowException if no liquidity is available on CoW.
|
65
|
+
"""
|
66
|
+
|
67
|
+
order_quote_request = OrderQuoteRequest(
|
68
|
+
buyToken=Address(buy_token),
|
69
|
+
sellToken=Address(collateral_token),
|
70
|
+
from_=Address(ADDRESS_ZERO),
|
71
|
+
)
|
72
|
+
|
73
|
+
order_side = OrderQuoteSide1(
|
74
|
+
kind=OrderQuoteSideKindSell.sell,
|
75
|
+
sellAmountBeforeFee=TokenAmountCow(str(sell_amount)),
|
76
|
+
)
|
77
|
+
try:
|
78
|
+
return asyncio.run(
|
79
|
+
get_order_quote(
|
80
|
+
order_quote_request=order_quote_request,
|
81
|
+
order_side=order_side,
|
82
|
+
order_book_api=self.order_book_api,
|
83
|
+
)
|
84
|
+
)
|
85
|
+
|
86
|
+
except UnexpectedResponseError as e1:
|
87
|
+
if "NoLiquidity" in e1.message:
|
88
|
+
raise NoLiquidityAvailableOnCowException(e1.message)
|
89
|
+
logger.warning(f"Found unexpected Cow response error: {e1}")
|
90
|
+
raise
|
91
|
+
except Exception as e:
|
92
|
+
logger.warning(f"Found unhandled Cow response error: {e}")
|
93
|
+
raise
|
94
|
+
|
95
|
+
@staticmethod
|
96
|
+
def swap(
|
97
|
+
amount: xDai,
|
98
|
+
sell_token: ChecksumAddress,
|
99
|
+
buy_token: ChecksumAddress,
|
100
|
+
api_keys: APIKeys,
|
101
|
+
web3: Web3 | None = None,
|
102
|
+
) -> OrderMetaData:
|
103
|
+
order_metadata = swap_tokens_waiting(
|
104
|
+
amount_wei=xdai_to_wei(amount),
|
105
|
+
sell_token=sell_token,
|
106
|
+
buy_token=buy_token,
|
107
|
+
api_keys=api_keys,
|
108
|
+
web3=web3,
|
109
|
+
)
|
110
|
+
logger.debug(
|
111
|
+
f"Purchased {buy_token} in exchange for {sell_token}. Order details {order_metadata}"
|
112
|
+
)
|
113
|
+
return order_metadata
|
@@ -23,9 +23,9 @@ prediction_market_agent_tooling/benchmark/agents.py,sha256=B1-uWdyeN4GGKMWGK_-Cc
|
|
23
23
|
prediction_market_agent_tooling/benchmark/benchmark.py,sha256=MqTiaaJ3cYiOLUVR7OyImLWxcEya3Rl5JyFYW-K0lwM,17097
|
24
24
|
prediction_market_agent_tooling/benchmark/utils.py,sha256=D0MfUkVZllmvcU0VOurk9tcKT7JTtwwOp-63zuCBVuc,2880
|
25
25
|
prediction_market_agent_tooling/config.py,sha256=owJ3goDbH1aeX8PzeJCeGK5pitYJqqk7yFOkaTbDarY,9209
|
26
|
-
prediction_market_agent_tooling/deploy/agent.py,sha256=
|
26
|
+
prediction_market_agent_tooling/deploy/agent.py,sha256=fFUxATQjSTRz70oDxBrgCk93NzF1i506cklJ8WtS_S0,24199
|
27
27
|
prediction_market_agent_tooling/deploy/agent_example.py,sha256=dIIdZashExWk9tOdyDjw87AuUcGyM7jYxNChYrVK2dM,1001
|
28
|
-
prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=
|
28
|
+
prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=Y6Pb8OfSb6galRbfdNBvvNTgO-4dR2ybJ4o5GKJcMoM,12894
|
29
29
|
prediction_market_agent_tooling/deploy/constants.py,sha256=M5ty8URipYMGe_G-RzxRydK3AFL6CyvmqCraJUrLBnE,82
|
30
30
|
prediction_market_agent_tooling/deploy/gcp/deploy.py,sha256=CYUgnfy-9XVk04kkxA_5yp0GE9Mw5caYqlFUZQ2j3ks,3739
|
31
31
|
prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py,sha256=OsPboCFGiZKsvGyntGZHwdqPlLTthITkNF5rJFvGgU8,2582
|
@@ -36,23 +36,24 @@ prediction_market_agent_tooling/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
|
|
36
36
|
prediction_market_agent_tooling/jobs/jobs_models.py,sha256=GOtsNm7URhzZM5fPY64r8m8Gz-sSsUhG1qmDoC7wGL8,2231
|
37
37
|
prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=N0_jGDyXQeVXXlYg4oA_pOfqIjscHsLQbr0pBwFGoRo,5178
|
38
38
|
prediction_market_agent_tooling/loggers.py,sha256=MvCkQSJL2_0yErNatqr81sJlc4aOgPzDp9VNrIhKUcc,4140
|
39
|
-
prediction_market_agent_tooling/markets/agent_market.py,sha256=
|
39
|
+
prediction_market_agent_tooling/markets/agent_market.py,sha256=IoJ7EVj2kHtL4ht8Dq02ghj8OC0mEj5jKOI0jpcU1n0,12889
|
40
40
|
prediction_market_agent_tooling/markets/base_subgraph_handler.py,sha256=7RaYO_4qAmQ6ZGM8oPK2-CkiJfKmV9MxM-rJlduaecU,1971
|
41
|
+
prediction_market_agent_tooling/markets/blockchain_utils.py,sha256=gZtQwF5UrOd_yOkNPLRbpMzUd55-Nsluy0858YYdPn8,2873
|
41
42
|
prediction_market_agent_tooling/markets/categorize.py,sha256=jsoHWvZk9pU6n17oWSCcCxNNYVwlb_NXsZxKRI7vmsk,1301
|
42
|
-
prediction_market_agent_tooling/markets/data_models.py,sha256=
|
43
|
+
prediction_market_agent_tooling/markets/data_models.py,sha256=uUuCMoo-Q4ws-03e6iOJJbQtZmQ1JZLapXOeb97t95o,4386
|
43
44
|
prediction_market_agent_tooling/markets/manifold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
45
|
prediction_market_agent_tooling/markets/manifold/api.py,sha256=Fd0HYnstvvHO6AZkp1xiRlvCwQUc8kLR8DAj6PAZu0s,7297
|
45
46
|
prediction_market_agent_tooling/markets/manifold/data_models.py,sha256=eiGS4rEkxseZNpEb2BICKnjF0qqgkQTMuUPbSe7_04I,6059
|
46
47
|
prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=qemQIwuFg4yf6egGWFp9lWpz1lXr02QiBeZ2akcT6II,5026
|
47
48
|
prediction_market_agent_tooling/markets/manifold/utils.py,sha256=_gGlWid0sPF127Omx5qQ1fq17frLInv0wdyXJBMGVzM,670
|
48
49
|
prediction_market_agent_tooling/markets/market_fees.py,sha256=Q64T9uaJx0Vllt0BkrPmpMEz53ra-hMVY8Czi7CEP7s,1227
|
49
|
-
prediction_market_agent_tooling/markets/markets.py,sha256=
|
50
|
+
prediction_market_agent_tooling/markets/markets.py,sha256=OMADWd1C5wD7sVdcY_GVdxAFDndkU9kn6Ble4GXCw0c,4045
|
50
51
|
prediction_market_agent_tooling/markets/metaculus/api.py,sha256=4TRPGytQQbSdf42DCg2M_JWYPAuNjqZ3eBqaQBLkNks,2736
|
51
|
-
prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=
|
52
|
+
prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=FaBCTPPezXbBwZ9p791CiVgQ4vB696xnMbz9XVXmiVI,3267
|
52
53
|
prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=86TIx6cavEWc8Cv4KpZxSvwiSw9oFybXE3YB49pg-CA,4377
|
53
54
|
prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
54
55
|
prediction_market_agent_tooling/markets/omen/data_models.py,sha256=uT8ILKrg2g4jGodPxtolPErk25buNzMYndb01ZL2dYE,28421
|
55
|
-
prediction_market_agent_tooling/markets/omen/omen.py,sha256=
|
56
|
+
prediction_market_agent_tooling/markets/omen/omen.py,sha256=mtkLJR1w3QRfyW6aY_Ey-3ITL2BPHrisvPY0tmB4wGU,49864
|
56
57
|
prediction_market_agent_tooling/markets/omen/omen_constants.py,sha256=D9oflYKafLQiHYtB5sScMHqmXyzM8JP8J0yATmc4SQQ,233
|
57
58
|
prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=EXqBlVivbmW8aBQ65O09X2xkyesHAop49GUl1tUffWA,28648
|
58
59
|
prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=E1BVDvQd1qYtCxmfC94kJtGkmQqpGPHL3zTkcs5wW6M,9697
|
@@ -62,10 +63,10 @@ prediction_market_agent_tooling/markets/polymarket/data_models.py,sha256=Fd5PI5y
|
|
62
63
|
prediction_market_agent_tooling/markets/polymarket/data_models_web.py,sha256=VZhVccTApygSKMmy6Au2G02JCJOKJnR_oVeKlaesuSg,12548
|
63
64
|
prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=NRoZK71PtH8kkangMqme7twcAXhRJSSabbmOir-UnAI,3418
|
64
65
|
prediction_market_agent_tooling/markets/polymarket/utils.py,sha256=DImFxeMg8lTfsEDZ8FavndW38TfUsCkawcVGnucsuGo,2029
|
65
|
-
prediction_market_agent_tooling/markets/seer/data_models.py,sha256=
|
66
|
-
prediction_market_agent_tooling/markets/seer/seer.py,sha256=
|
66
|
+
prediction_market_agent_tooling/markets/seer/data_models.py,sha256=HGJv4XSvCxXLLC5VwxZTZ5E4w_bWGKv50fM_6ssloxI,8203
|
67
|
+
prediction_market_agent_tooling/markets/seer/seer.py,sha256=M3RN3ZlLduFJvhk_0KTZDkX94z_HKhFOIK7BwLSGauM,13145
|
67
68
|
prediction_market_agent_tooling/markets/seer/seer_contracts.py,sha256=E7CYAKZiK6cg3dyj1kJuIPKSYYUft98F64shF5S0g4s,2730
|
68
|
-
prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=
|
69
|
+
prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=aycOvJ1_f5m7xzd_Hlx98_-VeM869IY9mTzJ2zn_VEM,8577
|
69
70
|
prediction_market_agent_tooling/monitor/financial_metrics/financial_metrics.py,sha256=fjIgjDIx5MhH5mwf7S0cspLOOSU3elYLhGYoIiM26mU,2746
|
70
71
|
prediction_market_agent_tooling/monitor/markets/manifold.py,sha256=TS4ERwTfQnot8dhekNyVNhJYf5ysYsjF-9v5_kM3aVI,3334
|
71
72
|
prediction_market_agent_tooling/monitor/markets/metaculus.py,sha256=LOnyWWBFdg10-cTWdb76nOsNjDloO8OfMT85GBzRCFI,1455
|
@@ -86,6 +87,7 @@ prediction_market_agent_tooling/tools/caches/inmemory_cache.py,sha256=ZW5iI5rmjq
|
|
86
87
|
prediction_market_agent_tooling/tools/caches/serializers.py,sha256=vFDx4fsPxclXp2q0sv27j4al_M_Tj9aR2JJP-xNHQXA,2151
|
87
88
|
prediction_market_agent_tooling/tools/contract.py,sha256=JkkrXm75_XUZ33jVlXF7v-Ge9RLSfXQeY523H_kuThQ,26069
|
88
89
|
prediction_market_agent_tooling/tools/costs.py,sha256=EaAJ7v9laD4VEV3d8B44M4u3_oEO_H16jRVCdoZ93Uw,954
|
90
|
+
prediction_market_agent_tooling/tools/cow/cow_manager.py,sha256=WK6Uk722VotjLHtxDPHxvwBrWVb3rvTegg_3w58ehwU,3869
|
89
91
|
prediction_market_agent_tooling/tools/cow/cow_order.py,sha256=ohriN_65BZ69f_BV6bNg_AwZjidip4yi4d2G6Ddy5Qg,4238
|
90
92
|
prediction_market_agent_tooling/tools/custom_exceptions.py,sha256=Fh8z1fbwONvP4-j7AmV_PuEcoqb6-QXa9PJ9m7guMcM,93
|
91
93
|
prediction_market_agent_tooling/tools/data_models.py,sha256=jDQ7FU0QQhXlcgJh5VZZGwDTYP2OPAqKPHZFewCPAUY,732
|
@@ -118,8 +120,8 @@ prediction_market_agent_tooling/tools/tokens/main_token.py,sha256=5iHO7-iehSlXuu
|
|
118
120
|
prediction_market_agent_tooling/tools/transaction_cache.py,sha256=K5YKNL2_tR10Iw2TD9fuP-CTGpBbZtNdgbd0B_R7pjg,1814
|
119
121
|
prediction_market_agent_tooling/tools/utils.py,sha256=jLG4nbEoIzzJiZ4RgMx4Q969Zdl0p0s63p8uET_0Fuw,6440
|
120
122
|
prediction_market_agent_tooling/tools/web3_utils.py,sha256=e7lqqQddVZaa905rhBb6L1fC3o39Yr-PDJsJjHFBeRE,12523
|
121
|
-
prediction_market_agent_tooling-0.58.
|
122
|
-
prediction_market_agent_tooling-0.58.
|
123
|
-
prediction_market_agent_tooling-0.58.
|
124
|
-
prediction_market_agent_tooling-0.58.
|
125
|
-
prediction_market_agent_tooling-0.58.
|
123
|
+
prediction_market_agent_tooling-0.58.5.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
|
124
|
+
prediction_market_agent_tooling-0.58.5.dist-info/METADATA,sha256=rPCTjBMlKpKZHpIpANRKiO-FIZRUqMA_P0DUcps9pcI,8629
|
125
|
+
prediction_market_agent_tooling-0.58.5.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
126
|
+
prediction_market_agent_tooling-0.58.5.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
|
127
|
+
prediction_market_agent_tooling-0.58.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|