prediction-market-agent-tooling 0.58.3.dev385__py3-none-any.whl → 0.58.4__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.
@@ -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 = 20
67
+ MAX_AVAILABLE_MARKETS = 1000
68
68
 
69
69
 
70
70
  def initialize_langfuse(enable_langfuse: bool) -> None:
@@ -430,6 +430,7 @@ class DeployablePredictionAgent(DeployableAgent):
430
430
  logger.info(f"Market '{market.question}' doesn't meet the criteria.")
431
431
  answer = None
432
432
  else:
433
+ logger.info(f"Answering market '{market.question}'.")
433
434
  answer = self.answer_binary_market(market)
434
435
 
435
436
  processed_market = (
@@ -522,6 +523,7 @@ class DeployableTraderAgent(DeployablePredictionAgent):
522
523
  MarketType.OMEN,
523
524
  MarketType.MANIFOLD,
524
525
  MarketType.POLYMARKET,
526
+ MarketType.SEER,
525
527
  ]
526
528
 
527
529
  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 outcome in [
98
- market.get_outcome_str_from_bool(True),
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
- @staticmethod
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
+ )
@@ -9,6 +9,7 @@ from prediction_market_agent_tooling.tools.utils import DatetimeUTC
9
9
 
10
10
  class Currency(str, Enum):
11
11
  xDai = "xDai"
12
+ sDai = "sDai"
12
13
  Mana = "Mana"
13
14
  USDC = "USDC"
14
15
 
@@ -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
- omen_auto_deposit: bool = True,
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=omen_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
- if traded_market is None:
438
- logger.warning(f"No prediction for market {self.id}, not storing anything.")
439
- return
440
-
441
- reasoning = (
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
- @staticmethod
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.gtypes import HexBytes, Wei
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[HexBytes] = Field(alias="wrappedTokens")
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 xDai
7
- from prediction_market_agent_tooling.markets.seer.data_models import NewMarketEvent
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 build_filter_for_conditional_markets(
74
- include_conditional_markets: bool = True,
80
+ def _build_where_statements(
81
+ filter_by: FilterBy,
82
+ include_conditional_markets: bool = False,
75
83
  ) -> dict[Any, Any]:
76
- return (
77
- {}
78
- if include_conditional_markets
79
- else {"parentMarket": ADDRESS_ZERO.lower()}
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, include_conditional_markets: bool = True
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
- query_filter = self.build_filter_for_conditional_markets(
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, include_conditional_markets: bool = True
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
- include_conditional_markets=include_conditional_markets
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.hex().lower()},
137
- {"token1": wrapped_token.hex().lower()},
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.58.3.dev385
3
+ Version: 0.58.4
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.13
@@ -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=3VzmTIekbICWrpoo0VjIbR7s_AKjhYaAaUP5RITDGek,23554
26
+ prediction_market_agent_tooling/deploy/agent.py,sha256=wLqnDakm1jThn_T1UnmsRvEly_cDaZ-7LYuj6xLauMA,23647
27
27
  prediction_market_agent_tooling/deploy/agent_example.py,sha256=dIIdZashExWk9tOdyDjw87AuUcGyM7jYxNChYrVK2dM,1001
28
- prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=kMrIE3wMv_IB6nJd_1DmDXDkEZhsXFOgyTd7JZ0gqHI,13068
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=W2ME57-CSAhrt8qm8-b5r7yLq-Sk7R_BZMaApvjhrUE,12901
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=oMoHxxiNekGgXX1FFExS_MNuHjte2pV1OEynVDIKVc8,4368
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=5x_bX_Mr-696RGXL97qZWnkqq6uV3q2stI-v2-dcaHs,3894
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
52
  prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=Suxa7xELdYuFNKqvGvFh8qyfVtAg79E-vaQ6dqNZOtA,3261
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=xNHYn5diKjKlkeKoYNr8DrE0gSMsU8poGhxBjw2PZow,51783
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=DmxkAfusRbupqBDjohSFhsYyW7WjKZ1sQKpm8xeAMi4,2017
66
- prediction_market_agent_tooling/markets/seer/seer.py,sha256=l87L82M-RHMBJjfdtNXWgGLc979n2Fub9Z702zE8I9c,3351
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=KraQe4GgbdMcYnvpEJqcSHXQqgyDiC7IjkIOHAfma9s,5528
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.3.dev385.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
122
- prediction_market_agent_tooling-0.58.3.dev385.dist-info/METADATA,sha256=BUNlEFZXKyo10bb9d49r_eyTZgC8tRHExC6OukNMZFU,8636
123
- prediction_market_agent_tooling-0.58.3.dev385.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
124
- prediction_market_agent_tooling-0.58.3.dev385.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
125
- prediction_market_agent_tooling-0.58.3.dev385.dist-info/RECORD,,
123
+ prediction_market_agent_tooling-0.58.4.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
124
+ prediction_market_agent_tooling-0.58.4.dist-info/METADATA,sha256=CnWmMbCA3ZjjwouIrK4FffaGiYL7hcp78JVkaJpmexE,8629
125
+ prediction_market_agent_tooling-0.58.4.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
126
+ prediction_market_agent_tooling-0.58.4.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
127
+ prediction_market_agent_tooling-0.58.4.dist-info/RECORD,,