prediction-market-agent-tooling 0.66.6__py3-none-any.whl → 0.67.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. prediction_market_agent_tooling/deploy/agent.py +70 -14
  2. prediction_market_agent_tooling/markets/agent_market.py +9 -2
  3. prediction_market_agent_tooling/markets/manifold/manifold.py +2 -2
  4. prediction_market_agent_tooling/markets/markets.py +6 -5
  5. prediction_market_agent_tooling/markets/metaculus/metaculus.py +4 -4
  6. prediction_market_agent_tooling/markets/omen/omen.py +3 -3
  7. prediction_market_agent_tooling/markets/polymarket/polymarket.py +3 -3
  8. prediction_market_agent_tooling/markets/seer/data_models.py +5 -7
  9. prediction_market_agent_tooling/markets/seer/price_manager.py +65 -46
  10. prediction_market_agent_tooling/markets/seer/seer.py +78 -85
  11. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +34 -9
  12. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +0 -4
  13. prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +1 -11
  14. prediction_market_agent_tooling/tools/cow/cow_order.py +24 -11
  15. prediction_market_agent_tooling/tools/cow/models.py +4 -2
  16. prediction_market_agent_tooling/tools/rephrase.py +71 -0
  17. {prediction_market_agent_tooling-0.66.6.dist-info → prediction_market_agent_tooling-0.67.2.dist-info}/METADATA +2 -1
  18. {prediction_market_agent_tooling-0.66.6.dist-info → prediction_market_agent_tooling-0.67.2.dist-info}/RECORD +21 -20
  19. {prediction_market_agent_tooling-0.66.6.dist-info → prediction_market_agent_tooling-0.67.2.dist-info}/LICENSE +0 -0
  20. {prediction_market_agent_tooling-0.66.6.dist-info → prediction_market_agent_tooling-0.67.2.dist-info}/WHEEL +0 -0
  21. {prediction_market_agent_tooling-0.66.6.dist-info → prediction_market_agent_tooling-0.67.2.dist-info}/entry_points.txt +0 -0
@@ -6,6 +6,7 @@ from enum import Enum
6
6
  from functools import cached_property
7
7
 
8
8
  from pydantic import computed_field
9
+ from pydantic_ai.exceptions import UnexpectedModelBehavior
9
10
 
10
11
  from prediction_market_agent_tooling.config import APIKeys
11
12
  from prediction_market_agent_tooling.deploy.betting_strategy import (
@@ -19,13 +20,12 @@ from prediction_market_agent_tooling.deploy.trade_interval import (
19
20
  )
20
21
  from prediction_market_agent_tooling.gtypes import USD, OutcomeToken, xDai
21
22
  from prediction_market_agent_tooling.loggers import logger
22
- from prediction_market_agent_tooling.markets.agent_market import AgentMarket, FilterBy
23
- from prediction_market_agent_tooling.markets.agent_market import (
24
- MarketType as AgentMarketType,
25
- )
26
23
  from prediction_market_agent_tooling.markets.agent_market import (
24
+ AgentMarket,
25
+ FilterBy,
27
26
  ProcessedMarket,
28
27
  ProcessedTradedMarket,
28
+ QuestionType,
29
29
  SortBy,
30
30
  )
31
31
  from prediction_market_agent_tooling.markets.data_models import (
@@ -47,6 +47,9 @@ from prediction_market_agent_tooling.tools.custom_exceptions import (
47
47
  from prediction_market_agent_tooling.tools.is_invalid import is_invalid
48
48
  from prediction_market_agent_tooling.tools.is_predictable import is_predictable_binary
49
49
  from prediction_market_agent_tooling.tools.langfuse_ import langfuse_context, observe
50
+ from prediction_market_agent_tooling.tools.rephrase import (
51
+ rephrase_question_to_unconditioned,
52
+ )
50
53
  from prediction_market_agent_tooling.tools.tokens.main_token import (
51
54
  MINIMUM_NATIVE_TOKEN_IN_EOA_FOR_FEES,
52
55
  )
@@ -196,6 +199,7 @@ class DeployablePredictionAgent(DeployableAgent):
196
199
  trade_on_markets_created_after: DatetimeUTC | None = None
197
200
  get_markets_sort_by: SortBy = SortBy.CLOSING_SOONEST
198
201
  get_markets_filter_by: FilterBy = FilterBy.OPEN
202
+ rephrase_conditioned_markets: bool = True
199
203
 
200
204
  # Agent behaviour when filtering fetched markets
201
205
  allow_invalid_questions: bool = False
@@ -205,6 +209,8 @@ class DeployablePredictionAgent(DeployableAgent):
205
209
  MINIMUM_NATIVE_TOKEN_IN_EOA_FOR_FEES
206
210
  )
207
211
 
212
+ just_warn_on_unexpected_model_behavior: bool = False
213
+
208
214
  # Only Metaculus allows to post predictions without trading (buying/selling of outcome tokens).
209
215
  supported_markets: t.Sequence[MarketType] = [MarketType.METACULUS]
210
216
 
@@ -224,6 +230,7 @@ class DeployablePredictionAgent(DeployableAgent):
224
230
  self.answer_categorical_market = observe()(self.answer_categorical_market) # type: ignore[method-assign]
225
231
  self.answer_scalar_market = observe()(self.answer_scalar_market) # type: ignore[method-assign]
226
232
  self.process_market = observe()(self.process_market) # type: ignore[method-assign]
233
+ self.rephrase_market_to_unconditioned = observe()(self.rephrase_market_to_unconditioned) # type: ignore[method-assign]
227
234
 
228
235
  def update_langfuse_trace_by_market(
229
236
  self, market_type: MarketType, market: AgentMarket
@@ -298,6 +305,34 @@ class DeployablePredictionAgent(DeployableAgent):
298
305
 
299
306
  return True
300
307
 
308
+ def rephrase_market_to_unconditioned(
309
+ self,
310
+ market_: AgentMarket,
311
+ ) -> AgentMarket:
312
+ """
313
+ If `rephrase_conditioned_markets` is set to True,
314
+ this method will be used to rephrase the question to account for the parent's market probability in the agent's decision process.
315
+ """
316
+ new = market_.model_copy(deep=True)
317
+
318
+ if new.parent is not None and new.parent.market.parent is not None:
319
+ new.parent.market = self.rephrase_market_to_unconditioned(new.parent.market)
320
+
321
+ rephrased_question = (
322
+ rephrase_question_to_unconditioned(
323
+ new.question,
324
+ new.parent.market.question,
325
+ new.parent.market.outcomes[new.parent.parent_outcome],
326
+ )
327
+ if new.parent is not None
328
+ else new.question
329
+ )
330
+
331
+ new.question = rephrased_question
332
+ new.parent = None
333
+
334
+ return new
335
+
301
336
  def answer_categorical_market(
302
337
  self, market: AgentMarket
303
338
  ) -> CategoricalProbabilisticAnswer | None:
@@ -338,13 +373,23 @@ class DeployablePredictionAgent(DeployableAgent):
338
373
  return False
339
374
 
340
375
  @property
341
- def agent_market_type(self) -> AgentMarketType:
376
+ def include_conditional_markets(self) -> bool:
377
+ # TODO: All should work in our code, except that currently CoW most of the time completely fails to swap xDai into outcome tokens of conditioned market.
378
+ # Enable after https://github.com/gnosis/prediction-market-agent-tooling/issues/748 and/or https://github.com/gnosis/prediction-market-agent-tooling/issues/759 is resolved.
379
+ return False
380
+ # `include_conditional_markets` if `rephrase_conditioned_markets` is enabled.
381
+ # We can expand this method in teh future, when we implement also more complex logic about conditional markets.
382
+ # Note that conditional market isn't a type of the market like Binary or Categorical, it means that it uses outcome tokens from parent market as a collateral token in this market.
383
+ return self.rephrase_conditioned_markets
384
+
385
+ @property
386
+ def agent_question_type(self) -> QuestionType:
342
387
  if self.fetch_scalar_markets:
343
- return AgentMarketType.SCALAR
388
+ return QuestionType.SCALAR
344
389
  elif self.fetch_categorical_markets:
345
- return AgentMarketType.CATEGORICAL
390
+ return QuestionType.CATEGORICAL
346
391
  else:
347
- return AgentMarketType.BINARY
392
+ return QuestionType.BINARY
348
393
 
349
394
  def get_markets(
350
395
  self,
@@ -355,15 +400,14 @@ class DeployablePredictionAgent(DeployableAgent):
355
400
  """
356
401
  cls = market_type.market_class
357
402
 
358
- agent_market_type = self.agent_market_type
359
-
360
403
  # Fetch the soonest closing markets to choose from
361
404
  available_markets = cls.get_markets(
362
405
  limit=self.n_markets_to_fetch,
363
406
  sort_by=self.get_markets_sort_by,
364
407
  filter_by=self.get_markets_filter_by,
365
408
  created_after=self.trade_on_markets_created_after,
366
- market_type=agent_market_type,
409
+ question_type=self.agent_question_type,
410
+ include_conditional_markets=self.include_conditional_markets,
367
411
  )
368
412
  return available_markets
369
413
 
@@ -396,6 +440,9 @@ class DeployablePredictionAgent(DeployableAgent):
396
440
 
397
441
  logger.info(f"Answering market '{market.question}'.")
398
442
 
443
+ if self.rephrase_conditioned_markets and market.parent is not None:
444
+ market = self.rephrase_market_to_unconditioned(market)
445
+
399
446
  if market.is_binary:
400
447
  try:
401
448
  binary_answer = self.answer_binary_market(market)
@@ -455,9 +502,18 @@ class DeployablePredictionAgent(DeployableAgent):
455
502
  f"Processing market {market.question=} from {market.url=} with liquidity {market.get_liquidity()}."
456
503
  )
457
504
 
458
- answer = self.build_answer(
459
- market=market, market_type=market_type, verify_market=verify_market
460
- )
505
+ try:
506
+ answer = self.build_answer(
507
+ market=market, market_type=market_type, verify_market=verify_market
508
+ )
509
+ except UnexpectedModelBehavior:
510
+ (
511
+ logger.warning
512
+ if self.just_warn_on_unexpected_model_behavior
513
+ else logger.exception
514
+ )(f"Unexpected model behaviour in {self.__class__.__name__}.")
515
+ answer = None
516
+
461
517
  if answer is not None:
462
518
  self.verify_answer_outcomes(market=market, answer=answer)
463
519
 
@@ -64,7 +64,12 @@ class FilterBy(str, Enum):
64
64
  NONE = "none"
65
65
 
66
66
 
67
- class MarketType(str, Enum):
67
+ class ParentMarket(BaseModel):
68
+ market: "AgentMarket"
69
+ parent_outcome: int
70
+
71
+
72
+ class QuestionType(str, Enum):
68
73
  ALL = "all"
69
74
  CATEGORICAL = "categorical"
70
75
  SCALAR = "scalar"
@@ -96,6 +101,8 @@ class AgentMarket(BaseModel):
96
101
  upper_bound: Wei | None = None
97
102
  lower_bound: Wei | None = None
98
103
 
104
+ parent: ParentMarket | None = None
105
+
99
106
  @field_validator("probabilities")
100
107
  def validate_probabilities(
101
108
  cls,
@@ -376,7 +383,7 @@ class AgentMarket(BaseModel):
376
383
  filter_by: FilterBy = FilterBy.OPEN,
377
384
  created_after: t.Optional[DatetimeUTC] = None,
378
385
  excluded_questions: set[str] | None = None,
379
- market_type: MarketType = MarketType.ALL,
386
+ question_type: QuestionType = QuestionType.ALL,
380
387
  include_conditional_markets: bool = False,
381
388
  ) -> t.Sequence["AgentMarket"]:
382
389
  raise NotImplementedError("Subclasses must implement this method")
@@ -12,7 +12,7 @@ from prediction_market_agent_tooling.markets.agent_market import (
12
12
  AgentMarket,
13
13
  FilterBy,
14
14
  MarketFees,
15
- MarketType,
15
+ QuestionType,
16
16
  SortBy,
17
17
  )
18
18
  from prediction_market_agent_tooling.markets.manifold.api import (
@@ -111,7 +111,7 @@ class ManifoldAgentMarket(AgentMarket):
111
111
  filter_by: FilterBy = FilterBy.OPEN,
112
112
  created_after: t.Optional[DatetimeUTC] = None,
113
113
  excluded_questions: set[str] | None = None,
114
- market_type: MarketType = MarketType.ALL,
114
+ question_type: QuestionType = QuestionType.ALL,
115
115
  include_conditional_markets: bool = False,
116
116
  ) -> t.Sequence["ManifoldAgentMarket"]:
117
117
  sort: t.Literal["newest", "close-date"] | None
@@ -3,11 +3,12 @@ from enum import Enum
3
3
 
4
4
  from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
5
5
  from prediction_market_agent_tooling.jobs.omen.omen_jobs import OmenJobAgentMarket
6
- from prediction_market_agent_tooling.markets.agent_market import AgentMarket, FilterBy
7
6
  from prediction_market_agent_tooling.markets.agent_market import (
8
- MarketType as AgentMarketType,
7
+ AgentMarket,
8
+ FilterBy,
9
+ QuestionType,
10
+ SortBy,
9
11
  )
10
- from prediction_market_agent_tooling.markets.agent_market import SortBy
11
12
  from prediction_market_agent_tooling.markets.manifold.manifold import (
12
13
  ManifoldAgentMarket,
13
14
  )
@@ -68,7 +69,7 @@ def get_binary_markets(
68
69
  sort_by: SortBy = SortBy.NONE,
69
70
  excluded_questions: set[str] | None = None,
70
71
  created_after: DatetimeUTC | None = None,
71
- agent_market_type: AgentMarketType = AgentMarketType.BINARY,
72
+ question_type: QuestionType = QuestionType.BINARY,
72
73
  ) -> t.Sequence[AgentMarket]:
73
74
  agent_market_class = MARKET_TYPE_TO_AGENT_MARKET[market_type]
74
75
  markets = agent_market_class.get_markets(
@@ -77,6 +78,6 @@ def get_binary_markets(
77
78
  filter_by=filter_by,
78
79
  created_after=created_after,
79
80
  excluded_questions=excluded_questions,
80
- market_type=agent_market_type,
81
+ question_type=question_type,
81
82
  )
82
83
  return markets
@@ -9,8 +9,8 @@ from prediction_market_agent_tooling.markets.agent_market import (
9
9
  AgentMarket,
10
10
  FilterBy,
11
11
  MarketFees,
12
- MarketType,
13
12
  ProcessedMarket,
13
+ QuestionType,
14
14
  SortBy,
15
15
  )
16
16
  from prediction_market_agent_tooling.markets.metaculus.api import (
@@ -67,15 +67,15 @@ class MetaculusAgentMarket(AgentMarket):
67
67
  )
68
68
 
69
69
  @staticmethod
70
- def get_markets( # type: ignore[override]
70
+ def get_markets(
71
71
  limit: int,
72
72
  sort_by: SortBy = SortBy.NONE,
73
73
  filter_by: FilterBy = FilterBy.OPEN,
74
74
  created_after: t.Optional[DatetimeUTC] = None,
75
75
  excluded_questions: set[str] | None = None,
76
- tournament_id: int | None = None,
77
- market_type: MarketType = MarketType.ALL,
76
+ question_type: QuestionType = QuestionType.ALL,
78
77
  include_conditional_markets: bool = False,
78
+ tournament_id: int | None = None,
79
79
  ) -> t.Sequence["MetaculusAgentMarket"]:
80
80
  order_by: str | None
81
81
  if sort_by == SortBy.NONE:
@@ -25,9 +25,9 @@ from prediction_market_agent_tooling.markets.agent_market import (
25
25
  AgentMarket,
26
26
  FilterBy,
27
27
  MarketFees,
28
- MarketType,
29
28
  ProcessedMarket,
30
29
  ProcessedTradedMarket,
30
+ QuestionType,
31
31
  SortBy,
32
32
  )
33
33
  from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
@@ -380,10 +380,10 @@ class OmenAgentMarket(AgentMarket):
380
380
  filter_by: FilterBy = FilterBy.OPEN,
381
381
  created_after: t.Optional[DatetimeUTC] = None,
382
382
  excluded_questions: set[str] | None = None,
383
- market_type: MarketType = MarketType.ALL,
383
+ question_type: QuestionType = QuestionType.ALL,
384
384
  include_conditional_markets: bool = False,
385
385
  ) -> t.Sequence["OmenAgentMarket"]:
386
- fetch_categorical_markets = market_type == MarketType.CATEGORICAL
386
+ fetch_categorical_markets = question_type == QuestionType.CATEGORICAL
387
387
 
388
388
  return [
389
389
  OmenAgentMarket.from_data_model(m)
@@ -11,7 +11,7 @@ from prediction_market_agent_tooling.markets.agent_market import (
11
11
  AgentMarket,
12
12
  FilterBy,
13
13
  MarketFees,
14
- MarketType,
14
+ QuestionType,
15
15
  SortBy,
16
16
  )
17
17
  from prediction_market_agent_tooling.markets.data_models import Resolution
@@ -123,7 +123,7 @@ class PolymarketAgentMarket(AgentMarket):
123
123
  filter_by: FilterBy = FilterBy.OPEN,
124
124
  created_after: t.Optional[DatetimeUTC] = None,
125
125
  excluded_questions: set[str] | None = None,
126
- market_type: MarketType = MarketType.ALL,
126
+ question_type: QuestionType = QuestionType.ALL,
127
127
  include_conditional_markets: bool = False,
128
128
  ) -> t.Sequence["PolymarketAgentMarket"]:
129
129
  closed: bool | None
@@ -159,7 +159,7 @@ class PolymarketAgentMarket(AgentMarket):
159
159
  ascending=ascending,
160
160
  created_after=created_after,
161
161
  excluded_questions=excluded_questions,
162
- only_binary=market_type is not MarketType.CATEGORICAL,
162
+ only_binary=question_type is not QuestionType.CATEGORICAL,
163
163
  )
164
164
 
165
165
  condition_models = PolymarketSubgraphHandler().get_conditions(
@@ -3,7 +3,7 @@ from datetime import timedelta
3
3
  from typing import Annotated
4
4
  from urllib.parse import urljoin
5
5
 
6
- from pydantic import BaseModel, BeforeValidator, ConfigDict, Field
6
+ from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, computed_field
7
7
  from web3 import Web3
8
8
  from web3.constants import ADDRESS_ZERO
9
9
 
@@ -17,9 +17,6 @@ from prediction_market_agent_tooling.gtypes import (
17
17
  Web3Wei,
18
18
  Wei,
19
19
  )
20
- from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
21
- SeerParentMarket,
22
- )
23
20
  from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
24
21
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
25
22
  from prediction_market_agent_tooling.tools.utils import utcnow
@@ -70,10 +67,10 @@ class SeerMarket(BaseModel):
70
67
  title: str = Field(alias="marketName")
71
68
  outcomes: t.Sequence[OutcomeStr]
72
69
  wrapped_tokens: list[HexAddress] = Field(alias="wrappedTokens")
73
- parent_outcome: int = Field(alias="parentOutcome")
74
- parent_market: t.Optional[SeerParentMarket] = Field(
75
- alias="parentMarket", default=None
70
+ parent_outcome: int = Field(
71
+ alias="parentOutcome", description="It comes as 0 from non-conditioned markets."
76
72
  )
73
+ parent_market: t.Optional["SeerMarket"] = Field(alias="parentMarket", default=None)
77
74
  collateral_token: HexAddress = Field(alias="collateralToken")
78
75
  condition_id: HexBytes = Field(alias="conditionId")
79
76
  opening_ts: int = Field(alias="openingTs")
@@ -139,6 +136,7 @@ class SeerMarket(BaseModel):
139
136
  def created_time(self) -> DatetimeUTC:
140
137
  return DatetimeUTC.to_datetime_utc(self.block_timestamp)
141
138
 
139
+ @computed_field # type: ignore[prop-decorator]
142
140
  @property
143
141
  def url(self) -> str:
144
142
  chain_id = RPCConfig().chain_id
@@ -1,6 +1,5 @@
1
- import typing as t
2
-
3
1
  from cachetools import TTLCache, cached
2
+ from pydantic import BaseModel
4
3
  from web3 import Web3
5
4
 
6
5
  from prediction_market_agent_tooling.gtypes import (
@@ -18,32 +17,15 @@ from prediction_market_agent_tooling.markets.seer.exceptions import (
18
17
  from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
19
18
  SeerSubgraphHandler,
20
19
  )
21
- from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
22
20
  from prediction_market_agent_tooling.tools.cow.cow_order import (
23
21
  get_buy_token_amount_else_raise,
24
22
  )
25
23
  from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
26
24
 
27
25
 
28
- def _make_cache_key(
29
- *args: t.Any,
30
- token: ChecksumAddress,
31
- collateral_exchange_amount: CollateralToken | None = None,
32
- ) -> str:
33
- """
34
- Generate a unique cache key based on a token address and optional collateral token.
35
- """
36
-
37
- if collateral_exchange_amount is None:
38
- return f"{token}-no_collateral"
39
-
40
- return "-".join(
41
- [
42
- token,
43
- collateral_exchange_amount.symbol,
44
- str(collateral_exchange_amount.value),
45
- ]
46
- )
26
+ class Prices(BaseModel):
27
+ priceOfCollateralInAskingToken: CollateralToken
28
+ priceOfAskingTokenInCollateral: CollateralToken
47
29
 
48
30
 
49
31
  class PriceManager:
@@ -67,17 +49,17 @@ class PriceManager:
67
49
  f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.hex()} "
68
50
  )
69
51
 
70
- @cached(TTLCache(maxsize=100, ttl=5 * 60), key=_make_cache_key)
71
- def get_price_for_token(
52
+ def get_price_for_token(self, token: ChecksumAddress) -> CollateralToken | None:
53
+ return self.get_amount_of_token_in_collateral(token, CollateralToken(1))
54
+
55
+ @cached(TTLCache(maxsize=100, ttl=5 * 60))
56
+ def get_amount_of_collateral_in_token(
72
57
  self,
73
58
  token: ChecksumAddress,
74
- collateral_exchange_amount: CollateralToken | None = None,
59
+ collateral_exchange_amount: CollateralToken,
75
60
  ) -> CollateralToken | None:
76
- collateral_exchange_amount = (
77
- collateral_exchange_amount
78
- if collateral_exchange_amount is not None
79
- else CollateralToken(1)
80
- )
61
+ if token == self.seer_market.collateral_token_contract_address_checksummed:
62
+ return collateral_exchange_amount
81
63
 
82
64
  try:
83
65
  buy_token_amount = get_buy_token_amount_else_raise(
@@ -85,23 +67,51 @@ class PriceManager:
85
67
  sell_token=self.seer_market.collateral_token_contract_address_checksummed,
86
68
  buy_token=token,
87
69
  )
88
- price = collateral_exchange_amount.as_wei / buy_token_amount
89
- return CollateralToken(price)
70
+ return buy_token_amount.as_token
90
71
 
91
72
  except Exception as e:
92
73
  logger.warning(
93
74
  f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
94
75
  )
95
- return self.get_token_price_from_pools(token=token)
76
+ prices = self.get_token_price_from_pools(token=token)
77
+ return (
78
+ prices.priceOfCollateralInAskingToken * collateral_exchange_amount
79
+ if prices
80
+ else None
81
+ )
96
82
 
97
- @staticmethod
98
- def pool_token0_matches_token(token: ChecksumAddress, pool: SeerPool) -> bool:
99
- return pool.token0.id.hex().lower() == token.lower()
83
+ @cached(TTLCache(maxsize=100, ttl=5 * 60))
84
+ def get_amount_of_token_in_collateral(
85
+ self,
86
+ token: ChecksumAddress,
87
+ token_exchange_amount: CollateralToken,
88
+ ) -> CollateralToken | None:
89
+ if token == self.seer_market.collateral_token_contract_address_checksummed:
90
+ return token_exchange_amount
91
+
92
+ try:
93
+ buy_collateral_amount = get_buy_token_amount_else_raise(
94
+ sell_amount=token_exchange_amount.as_wei,
95
+ sell_token=token,
96
+ buy_token=self.seer_market.collateral_token_contract_address_checksummed,
97
+ )
98
+ return buy_collateral_amount.as_token
99
+
100
+ except Exception as e:
101
+ logger.warning(
102
+ f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
103
+ )
104
+ prices = self.get_token_price_from_pools(token=token)
105
+ return (
106
+ prices.priceOfAskingTokenInCollateral * token_exchange_amount
107
+ if prices
108
+ else None
109
+ )
100
110
 
101
111
  def get_token_price_from_pools(
102
112
  self,
103
113
  token: ChecksumAddress,
104
- ) -> CollateralToken | None:
114
+ ) -> Prices | None:
105
115
  pool = SeerSubgraphHandler().get_pool_by_token(
106
116
  token_address=token,
107
117
  collateral_address=self.seer_market.collateral_token_contract_address_checksummed,
@@ -111,15 +121,24 @@ class PriceManager:
111
121
  logger.warning(f"Could not find a pool for {token=}")
112
122
  return None
113
123
 
114
- # The mapping below is odd but surprisingly the Algebra subgraph delivers the token1Price
115
- # for the token0 and the token0Price for the token1 pool.
116
- # For example, in a outcomeYES (token0)/sDAI pool (token1), token1Price is the price of outcomeYES in units of sDAI.
117
- price = (
118
- pool.token1Price
119
- if self.pool_token0_matches_token(token=token, pool=pool)
120
- else pool.token0Price
124
+ if (
125
+ Web3.to_checksum_address(pool.token0.id)
126
+ == self.seer_market.collateral_token_contract_address_checksummed
127
+ ):
128
+ price_coll_in_asking = (
129
+ pool.token1Price
130
+ ) # how many outcome tokens per 1 collateral
131
+ price_asking_in_coll = (
132
+ pool.token0Price
133
+ ) # how many collateral tokens per 1 outcome
134
+ else:
135
+ price_coll_in_asking = pool.token0Price
136
+ price_asking_in_coll = pool.token1Price
137
+
138
+ return Prices(
139
+ priceOfCollateralInAskingToken=price_coll_in_asking,
140
+ priceOfAskingTokenInCollateral=price_asking_in_coll,
121
141
  )
122
- return price
123
142
 
124
143
  def build_probability_map(self) -> dict[OutcomeStr, Probability]:
125
144
  # Inspired by https://github.com/seer-pm/demo/blob/ca682153a6b4d4dd3dcc4ad8bdcbe32202fc8fe7/web/src/hooks/useMarketOdds.ts#L15
@@ -160,6 +179,6 @@ class PriceManager:
160
179
  outcome = self.seer_market.outcomes[
161
180
  self.seer_market.wrapped_tokens.index(outcome_token)
162
181
  ]
163
- normalized_prices[OutcomeStr(outcome)] = new_price
182
+ normalized_prices[outcome] = new_price
164
183
 
165
184
  return normalized_prices
@@ -25,9 +25,10 @@ from prediction_market_agent_tooling.markets.agent_market import (
25
25
  AgentMarket,
26
26
  FilterBy,
27
27
  MarketFees,
28
- MarketType,
28
+ ParentMarket,
29
29
  ProcessedMarket,
30
30
  ProcessedTradedMarket,
31
+ QuestionType,
31
32
  SortBy,
32
33
  )
33
34
  from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
@@ -68,8 +69,9 @@ from prediction_market_agent_tooling.tools.contract import (
68
69
  )
69
70
  from prediction_market_agent_tooling.tools.cow.cow_order import (
70
71
  NoLiquidityAvailableOnCowException,
71
- get_buy_token_amount_else_raise,
72
+ OrderStatusError,
72
73
  get_orders_by_owner,
74
+ get_trades_by_order_uid,
73
75
  get_trades_by_owner,
74
76
  swap_tokens_waiting,
75
77
  wait_for_order_completion,
@@ -85,7 +87,7 @@ from prediction_market_agent_tooling.tools.tokens.usd import (
85
87
  get_token_in_usd,
86
88
  get_usd_in_token,
87
89
  )
88
- from prediction_market_agent_tooling.tools.utils import utcnow
90
+ from prediction_market_agent_tooling.tools.utils import check_not_none, utcnow
89
91
 
90
92
  # We place a larger bet amount by default than Omen so that cow presents valid quotes.
91
93
  SEER_TINY_BET_AMOUNT = USD(0.1)
@@ -137,45 +139,34 @@ class SeerAgentMarket(AgentMarket):
137
139
  web3=web3,
138
140
  )
139
141
 
142
+ def get_price_manager(self) -> PriceManager:
143
+ return PriceManager.build(HexBytes(HexStr(self.id)))
144
+
140
145
  def get_token_in_usd(self, x: CollateralToken) -> USD:
141
- try:
142
- return get_token_in_usd(
143
- x, self.collateral_token_contract_address_checksummed
144
- )
145
- except NoLiquidityAvailableOnCowException as e:
146
- logger.warning(
147
- f"Could not get quote for {self.collateral_token_contract_address_checksummed} from Cow, exception {e=}. Falling back to pools. "
146
+ p = self.get_price_manager()
147
+ sdai_amount = p.get_amount_of_collateral_in_token(
148
+ # Hard-coded SDAI, because Seer is atm hard-coded it as well, and it's needed in case of fallback to pools. CoW would work with other tokens as well.
149
+ SDAI_CONTRACT_ADDRESS,
150
+ x,
151
+ )
152
+ if sdai_amount is None:
153
+ raise RuntimeError(
154
+ "Both CoW and pool-fallback way of getting price failed."
148
155
  )
149
- usd_token_price = self.get_collateral_price_from_pools()
150
- if usd_token_price is None:
151
- raise RuntimeError(
152
- "Both CoW and pool-fallback way of getting price failed."
153
- ) from e
154
- return USD(x.value * usd_token_price.value)
155
-
156
- def get_collateral_price_from_pools(self) -> USD | None:
157
- p = PriceManager.build(HexBytes(HexStr(self.id)))
158
- token_price = p.get_token_price_from_pools(token=SDAI_CONTRACT_ADDRESS)
159
- if token_price:
160
- return get_token_in_usd(token_price, SDAI_CONTRACT_ADDRESS)
161
-
162
- return None
156
+ return get_token_in_usd(sdai_amount, SDAI_CONTRACT_ADDRESS)
163
157
 
164
158
  def get_usd_in_token(self, x: USD) -> CollateralToken:
165
- try:
166
- return get_usd_in_token(
167
- x, self.collateral_token_contract_address_checksummed
168
- )
169
- except NoLiquidityAvailableOnCowException as e:
170
- logger.warning(
171
- f"Could not get quote for {self.collateral_token_contract_address_checksummed} from Cow, exception {e=}. Falling back to pools. "
159
+ p = self.get_price_manager()
160
+ token_amount = p.get_amount_of_token_in_collateral(
161
+ # Hard-coded SDAI, because Seer is atm hard-coded it as well, and it's needed in case of fallback to pools. CoW would work with other tokens as well.
162
+ SDAI_CONTRACT_ADDRESS,
163
+ get_usd_in_token(x, SDAI_CONTRACT_ADDRESS),
164
+ )
165
+ if token_amount is None:
166
+ raise RuntimeError(
167
+ "Both CoW and pool-fallback way of getting price failed."
172
168
  )
173
- usd_token_price = self.get_collateral_price_from_pools()
174
- if not usd_token_price:
175
- raise RuntimeError(
176
- "Both CoW and pool-fallback way of getting price failed."
177
- ) from e
178
- return CollateralToken(x.value / usd_token_price.value)
169
+ return token_amount
179
170
 
180
171
  def get_buy_token_amount(
181
172
  self, bet_amount: USD | CollateralToken, outcome_str: OutcomeStr
@@ -191,16 +182,15 @@ class SeerAgentMarket(AgentMarket):
191
182
 
192
183
  bet_amount_in_tokens = self.get_in_token(bet_amount)
193
184
 
194
- p = PriceManager.build(market_id=HexBytes(HexStr(self.id)))
195
- price = p.get_price_for_token(
185
+ p = self.get_price_manager()
186
+ amount_outcome_tokens = p.get_amount_of_collateral_in_token(
196
187
  token=outcome_token, collateral_exchange_amount=bet_amount_in_tokens
197
188
  )
198
- if not price:
189
+ if not amount_outcome_tokens:
199
190
  logger.info(f"Could not get price for token {outcome_token}")
200
191
  return None
201
192
 
202
- amount_outcome_tokens = bet_amount_in_tokens / price
203
- return OutcomeToken(amount_outcome_tokens)
193
+ return OutcomeToken(amount_outcome_tokens.value)
204
194
 
205
195
  def get_sell_value_of_outcome_token(
206
196
  self, outcome: OutcomeStr, amount: OutcomeToken
@@ -209,26 +199,18 @@ class SeerAgentMarket(AgentMarket):
209
199
  return CollateralToken.zero()
210
200
 
211
201
  wrapped_outcome_token = self.get_wrapped_token_for_outcome(outcome)
212
- try:
213
- # We calculate how much collateral we would get back if we sold `amount` of outcome token.
214
- value_outcome_token_in_collateral = get_buy_token_amount_else_raise(
215
- sell_amount=amount.as_outcome_wei.as_wei,
216
- sell_token=wrapped_outcome_token,
217
- buy_token=self.collateral_token_contract_address_checksummed,
218
- )
219
- return value_outcome_token_in_collateral.as_token
220
- except NoLiquidityAvailableOnCowException as e:
221
- logger.warning(
222
- f"No liquidity available on Cow for {wrapped_outcome_token} -> {self.collateral_token_contract_address_checksummed}."
202
+
203
+ p = self.get_price_manager()
204
+ value_outcome_token_in_collateral = p.get_amount_of_token_in_collateral(
205
+ wrapped_outcome_token, amount.as_token
206
+ )
207
+
208
+ if value_outcome_token_in_collateral is None:
209
+ raise RuntimeError(
210
+ f"Could not get price for token from pools for {wrapped_outcome_token}"
223
211
  )
224
- p = PriceManager.build(market_id=HexBytes(HexStr(self.id)))
225
- price = p.get_token_price_from_pools(token=wrapped_outcome_token)
226
- if not price:
227
- logger.info(
228
- f"Could not get price for token from pools for {wrapped_outcome_token}"
229
- )
230
- raise e
231
- return CollateralToken(price.value * amount.value)
212
+
213
+ return value_outcome_token_in_collateral
232
214
 
233
215
  @staticmethod
234
216
  def get_trade_balance(api_keys: APIKeys) -> USD:
@@ -393,6 +375,22 @@ class SeerAgentMarket(AgentMarket):
393
375
  probabilities=probability_map,
394
376
  upper_bound=model.upper_bound,
395
377
  lower_bound=model.lower_bound,
378
+ parent=(
379
+ ParentMarket(
380
+ market=(
381
+ check_not_none(
382
+ SeerAgentMarket.from_data_model_with_subgraph(
383
+ model.parent_market,
384
+ seer_subgraph,
385
+ False,
386
+ )
387
+ )
388
+ ),
389
+ parent_outcome=model.parent_outcome,
390
+ )
391
+ if model.parent_market
392
+ else None
393
+ ),
396
394
  )
397
395
 
398
396
  return market
@@ -404,7 +402,7 @@ class SeerAgentMarket(AgentMarket):
404
402
  filter_by: FilterBy = FilterBy.OPEN,
405
403
  created_after: t.Optional[DatetimeUTC] = None,
406
404
  excluded_questions: set[str] | None = None,
407
- market_type: MarketType = MarketType.ALL,
405
+ question_type: QuestionType = QuestionType.ALL,
408
406
  include_conditional_markets: bool = False,
409
407
  ) -> t.Sequence["SeerAgentMarket"]:
410
408
  seer_subgraph = SeerSubgraphHandler()
@@ -413,8 +411,8 @@ class SeerAgentMarket(AgentMarket):
413
411
  limit=limit,
414
412
  sort_by=sort_by,
415
413
  filter_by=filter_by,
416
- market_type=market_type,
417
- include_conditional_markets=False,
414
+ question_type=question_type,
415
+ include_conditional_markets=include_conditional_markets,
418
416
  )
419
417
 
420
418
  # We exclude the None values below because `from_data_model_with_subgraph` can return None, which
@@ -456,7 +454,8 @@ class SeerAgentMarket(AgentMarket):
456
454
  f"Could not fetch pool for token {outcome_token}, no liquidity available for outcome."
457
455
  )
458
456
  return CollateralToken(0)
459
- p = PriceManager.build(HexBytes(HexStr(self.id)))
457
+
458
+ p = self.get_price_manager()
460
459
  total = CollateralToken(0)
461
460
 
462
461
  for token_address in [pool.token0.id, pool.token1.id]:
@@ -469,19 +468,13 @@ class SeerAgentMarket(AgentMarket):
469
468
  for_address=Web3.to_checksum_address(HexAddress(HexStr(pool.id.hex()))),
470
469
  web3=web3,
471
470
  )
472
-
473
- # get price
474
- token_price_in_sdai = (
475
- p.get_token_price_from_pools(token=token_address_checksummed)
476
- if token_address_checksummed
477
- != self.collateral_token_contract_address_checksummed
478
- else CollateralToken(1.0)
471
+ collateral_balance = p.get_amount_of_token_in_collateral(
472
+ token_address_checksummed, token_balance
479
473
  )
480
474
 
481
475
  # We ignore the liquidity in outcome tokens if price unknown.
482
- if token_price_in_sdai:
483
- sdai_balance = token_balance * token_price_in_sdai
484
- total += sdai_balance
476
+ if collateral_balance:
477
+ total += collateral_balance
485
478
 
486
479
  return total
487
480
 
@@ -542,15 +535,23 @@ class SeerAgentMarket(AgentMarket):
542
535
  slippage_tolerance=slippage_tolerance,
543
536
  )
544
537
  order_metadata = asyncio.run(wait_for_order_completion(order=order))
545
- logger.debug(
538
+ logger.info(
546
539
  f"Swapped {sell_token} for {buy_token}. Order details {order_metadata}"
547
540
  )
548
- return order_metadata.uid.root
541
+ trades = get_trades_by_order_uid(HexBytes(order_metadata.uid.root))
542
+ if len(trades) != 1:
543
+ raise ValueError(
544
+ f"Expected exactly 1 trade from {order_metadata=}, but got {len(trades)=}."
545
+ )
546
+ cow_tx_hash = trades[0].txHash
547
+ logger.info(f"TxHash for {order_metadata.uid.root=} is {cow_tx_hash=}.")
548
+ return cow_tx_hash.hex()
549
549
 
550
550
  except (
551
551
  UnexpectedResponseError,
552
552
  TimeoutError,
553
553
  NoLiquidityAvailableOnCowException,
554
+ OrderStatusError,
554
555
  ) as e:
555
556
  # We don't retry if not enough balance.
556
557
  if "InsufficientBalance" in str(e):
@@ -601,14 +602,6 @@ class SeerAgentMarket(AgentMarket):
601
602
  collateral_contract, amount_wei, api_keys, web3
602
603
  )
603
604
 
604
- collateral_balance = collateral_contract.balanceOf(
605
- api_keys.bet_from_address, web3=web3
606
- )
607
- if collateral_balance < amount_wei:
608
- raise ValueError(
609
- f"Balance {collateral_balance} not enough for bet size {amount}"
610
- )
611
-
612
605
  return self._swap_tokens_with_fallback(
613
606
  sell_token=collateral_contract.address,
614
607
  buy_token=outcome_token,
@@ -16,7 +16,7 @@ from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei
16
16
  from prediction_market_agent_tooling.loggers import logger
17
17
  from prediction_market_agent_tooling.markets.agent_market import (
18
18
  FilterBy,
19
- MarketType,
19
+ QuestionType,
20
20
  SortBy,
21
21
  )
22
22
  from prediction_market_agent_tooling.markets.base_subgraph_handler import (
@@ -62,7 +62,9 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
62
62
  )
63
63
  )
64
64
 
65
- def _get_fields_for_markets(self, markets_field: FieldPath) -> list[FieldPath]:
65
+ def _get_fields_for_markets(
66
+ self, markets_field: FieldPath, current_level: int = 0, max_level: int = 1
67
+ ) -> list[FieldPath]:
66
68
  fields = [
67
69
  markets_field.id,
68
70
  markets_field.factory,
@@ -76,7 +78,6 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
76
78
  markets_field.payoutNumerators,
77
79
  markets_field.hasAnswers,
78
80
  markets_field.blockTimestamp,
79
- markets_field.parentMarket.id,
80
81
  markets_field.openingTs,
81
82
  markets_field.finalizeTs,
82
83
  markets_field.wrappedTokens,
@@ -84,7 +85,25 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
84
85
  markets_field.upperBound,
85
86
  markets_field.lowerBound,
86
87
  markets_field.templateId,
88
+ # TODO: On the Subgraph, `questions` field is a kind of sub-query, instead of a classic list of values.
89
+ # See how it is shown on their UI: https://thegraph.com/explorer/subgraphs/B4vyRqJaSHD8dRDb3BFRoAzuBK18c1QQcXq94JbxDxWH?view=Query&chain=arbitrum-one.
90
+ # And that doesn't work with subgrounds.
91
+ # markets_field.questions.question.id,
92
+ # markets_field.questions.question.finalize_ts,
93
+ # markets_field.questions.question.best_answer,
87
94
  ]
95
+ if current_level < max_level:
96
+ fields.extend(
97
+ self._get_fields_for_markets(
98
+ markets_field.parentMarket, current_level + 1, max_level
99
+ )
100
+ )
101
+ # TODO: Same situation as with `questions` field above.
102
+ # fields.extend(
103
+ # self._get_fields_for_markets(
104
+ # markets_field.childMarkets, current_level + 1, max_level
105
+ # )
106
+ # )
88
107
  return fields
89
108
 
90
109
  @staticmethod
@@ -114,7 +133,8 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
114
133
  filter_by: FilterBy,
115
134
  outcome_supply_gt_if_open: Wei,
116
135
  include_conditional_markets: bool = False,
117
- market_type: MarketType = MarketType.ALL,
136
+ question_type: QuestionType = QuestionType.ALL,
137
+ parent_market_id: HexBytes | None = None,
118
138
  ) -> dict[Any, Any]:
119
139
  now = to_int_timestamp(utcnow())
120
140
 
@@ -136,9 +156,12 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
136
156
  if not include_conditional_markets:
137
157
  and_stms["parentMarket"] = ADDRESS_ZERO.lower()
138
158
 
159
+ if parent_market_id:
160
+ and_stms["parentMarket"] = parent_market_id.hex().lower()
161
+
139
162
  outcome_filters: list[dict[str, t.Any]] = []
140
163
 
141
- if market_type == MarketType.SCALAR:
164
+ if question_type == QuestionType.SCALAR:
142
165
  # Template ID "1" + UP/DOWN outcomes for scalar markets
143
166
  and_stms["templateId"] = TemplateId.SCALAR.value
144
167
  up_filter = SeerSubgraphHandler._create_case_variations_condition(
@@ -149,7 +172,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
149
172
  )
150
173
  outcome_filters.extend([up_filter, down_filter])
151
174
 
152
- elif market_type == MarketType.BINARY:
175
+ elif question_type == QuestionType.BINARY:
153
176
  # Template ID "2" + YES/NO outcomes for binary markets
154
177
  and_stms["templateId"] = TemplateId.CATEGORICAL.value
155
178
  yes_filter = SeerSubgraphHandler._create_case_variations_condition(
@@ -160,7 +183,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
160
183
  )
161
184
  outcome_filters.extend([yes_filter, no_filter])
162
185
 
163
- elif market_type == MarketType.CATEGORICAL:
186
+ elif question_type == QuestionType.CATEGORICAL:
164
187
  # Template ID 2 (categorical) OR Template ID 3 (multi-categorical,
165
188
  # we treat them as categorical for now for simplicity)
166
189
  # https://reality.eth.limo/app/docs/html/contracts.html#templates
@@ -211,16 +234,18 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
211
234
  sort_by: SortBy = SortBy.NONE,
212
235
  limit: int | None = None,
213
236
  outcome_supply_gt_if_open: Wei = Wei(0),
214
- market_type: MarketType = MarketType.ALL,
237
+ question_type: QuestionType = QuestionType.ALL,
215
238
  include_conditional_markets: bool = False,
239
+ parent_market_id: HexBytes | None = None,
216
240
  ) -> list[SeerMarket]:
217
241
  sort_direction, sort_by_field = self._build_sort_params(sort_by)
218
242
 
219
243
  where_stms = self._build_where_statements(
220
244
  filter_by=filter_by,
221
245
  outcome_supply_gt_if_open=outcome_supply_gt_if_open,
246
+ parent_market_id=parent_market_id,
247
+ question_type=question_type,
222
248
  include_conditional_markets=include_conditional_markets,
223
- market_type=market_type,
224
249
  )
225
250
 
226
251
  # These values can not be set to `None`, but they can be omitted.
@@ -58,7 +58,3 @@ class CreateCategoricalMarketsParams(BaseModel):
58
58
  ) # typed as int for later .model_dump() usage (if using Wei, other keys also exported)
59
59
  opening_time: int = Field(..., alias="openingTime")
60
60
  token_names: list[str] = Field(..., alias="tokenNames")
61
-
62
-
63
- class SeerParentMarket(BaseModel):
64
- id: HexBytes
@@ -19,7 +19,6 @@ from prediction_market_agent_tooling.markets.seer.seer_contracts import (
19
19
  from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
20
20
  SeerSubgraphHandler,
21
21
  )
22
- from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
23
22
 
24
23
 
25
24
  class SwapPoolHandler:
@@ -80,7 +79,7 @@ class SwapPoolHandler:
80
79
  amount_out_minimum = self._calculate_amount_out_minimum(
81
80
  amount_wei=amount_wei,
82
81
  token_in=token_in,
83
- price_outcome_token=price_outcome_token,
82
+ price_outcome_token=price_outcome_token.priceOfCollateralInAskingToken,
84
83
  )
85
84
 
86
85
  p = ExactInputSingleParams(
@@ -91,15 +90,6 @@ class SwapPoolHandler:
91
90
  amount_out_minimum=amount_out_minimum,
92
91
  )
93
92
 
94
- # make sure user has enough tokens to sell
95
- balance_collateral_token = ContractERC20OnGnosisChain(
96
- address=token_in
97
- ).balanceOf(self.api_keys.bet_from_address, web3=web3)
98
- if balance_collateral_token < amount_wei:
99
- raise ValueError(
100
- f"Balance {balance_collateral_token} of {token_in} insufficient for trade, required {amount_wei}"
101
- )
102
-
103
93
  tx_receipt = SwaprRouterContract().exact_input_single(
104
94
  api_keys=self.api_keys, params=p, web3=web3
105
95
  )
@@ -33,12 +33,7 @@ from cowdao_cowpy.order_book.generated.model import (
33
33
  from eth_account import Account
34
34
  from eth_account.signers.local import LocalAccount
35
35
  from eth_keys.datatypes import PrivateKey as eth_keys_PrivateKey
36
- from tenacity import (
37
- retry_if_not_exception_type,
38
- stop_after_attempt,
39
- wait_exponential,
40
- wait_fixed,
41
- )
36
+ from tenacity import stop_after_attempt, wait_exponential, wait_fixed
42
37
  from web3 import Web3
43
38
 
44
39
  from prediction_market_agent_tooling.config import APIKeys
@@ -54,7 +49,7 @@ from prediction_market_agent_tooling.markets.omen.cow_contracts import (
54
49
  CowGPv2SettlementContract,
55
50
  )
56
51
  from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
57
- from prediction_market_agent_tooling.tools.cow.models import MinimalisticToken, Order
52
+ from prediction_market_agent_tooling.tools.cow.models import MinimalisticTrade, Order
58
53
  from prediction_market_agent_tooling.tools.cow.semaphore import postgres_rate_limited
59
54
  from prediction_market_agent_tooling.tools.utils import utcnow
60
55
 
@@ -108,7 +103,6 @@ def get_sell_token_amount(
108
103
  @tenacity.retry(
109
104
  stop=stop_after_attempt(4),
110
105
  wait=wait_exponential(min=4, max=10),
111
- retry=retry_if_not_exception_type(NoLiquidityAvailableOnCowException),
112
106
  )
113
107
  def get_quote(
114
108
  amount_wei: Wei,
@@ -198,7 +192,7 @@ def handle_allowance(
198
192
  reraise=True,
199
193
  stop=stop_after_attempt(3),
200
194
  wait=wait_fixed(1),
201
- retry=tenacity.retry_if_not_exception_type((TimeoutError, OrderStatusError)),
195
+ retry=tenacity.retry_if_not_exception_type((TimeoutError)),
202
196
  after=lambda x: logger.debug(f"swap_tokens_waiting failed, {x.attempt_number=}."),
203
197
  )
204
198
  def swap_tokens_waiting(
@@ -355,14 +349,33 @@ async def sign_safe_cow_swap(
355
349
  )
356
350
  def get_trades_by_owner(
357
351
  owner: ChecksumAddress,
358
- ) -> list[MinimalisticToken]:
352
+ ) -> list[MinimalisticTrade]:
359
353
  # Using this until cowpy gets fixed (https://github.com/cowdao-grants/cow-py/issues/35)
360
354
  response = httpx.get(
361
355
  f"https://api.cow.fi/xdai/api/v1/trades",
362
356
  params={"owner": owner},
363
357
  )
364
358
  response.raise_for_status()
365
- return [MinimalisticToken.model_validate(i) for i in response.json()]
359
+ return [MinimalisticTrade.model_validate(i) for i in response.json()]
360
+
361
+
362
+ @tenacity.retry(
363
+ stop=stop_after_attempt(3),
364
+ wait=wait_fixed(1),
365
+ after=lambda x: logger.debug(
366
+ f"get_trades_by_order_uid failed, {x.attempt_number=}."
367
+ ),
368
+ )
369
+ def get_trades_by_order_uid(
370
+ order_uid: HexBytes,
371
+ ) -> list[MinimalisticTrade]:
372
+ # Using this until cowpy gets fixed (https://github.com/cowdao-grants/cow-py/issues/35)
373
+ response = httpx.get(
374
+ f"https://api.cow.fi/xdai/api/v1/trades",
375
+ params={"orderUid": order_uid.hex()},
376
+ )
377
+ response.raise_for_status()
378
+ return [MinimalisticTrade.model_validate(i) for i in response.json()]
366
379
 
367
380
 
368
381
  @tenacity.retry(
@@ -1,14 +1,16 @@
1
1
  from pydantic import BaseModel
2
2
  from sqlmodel import Field, SQLModel
3
3
 
4
- from prediction_market_agent_tooling.gtypes import ChecksumAddress
4
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress, HexBytes
5
5
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
6
6
  from prediction_market_agent_tooling.tools.utils import utcnow
7
7
 
8
8
 
9
- class MinimalisticToken(BaseModel):
9
+ class MinimalisticTrade(BaseModel):
10
10
  sellToken: ChecksumAddress
11
11
  buyToken: ChecksumAddress
12
+ orderUid: HexBytes
13
+ txHash: HexBytes
12
14
 
13
15
 
14
16
  class Order(BaseModel):
@@ -0,0 +1,71 @@
1
+ import tenacity
2
+
3
+ from prediction_market_agent_tooling.config import APIKeys
4
+ from prediction_market_agent_tooling.tools.caches.db_cache import db_cache
5
+ from prediction_market_agent_tooling.tools.langfuse_ import (
6
+ get_langfuse_langchain_config,
7
+ observe,
8
+ )
9
+ from prediction_market_agent_tooling.tools.utils import (
10
+ LLM_SEED,
11
+ LLM_SUPER_LOW_TEMPERATURE,
12
+ )
13
+
14
+ REPHRASE_QUESTION_PROMPT = """Given the following question of main interest: {question}
15
+
16
+ But it's conditioned on `{parent_question}` resolving to `{needed_parent_outcome}`.
17
+
18
+ Rewrite the main question to contain the parent question in the correct form.
19
+
20
+ The main question will be used as a prediction market, so it does need to be rephrased using the parent question properly. Such that the probability of the main question also accounts for the conditioned outcome.
21
+
22
+ For example:
23
+ ```
24
+ Main question: What is the probability of <X> happening before <date>?
25
+ Conditioned on: Will <Y> happen before <another-date>?
26
+ Rephrased: What is the joint probability of Y happening before <another-date> and then X happening before <date>?
27
+ ```
28
+
29
+ Output only the rephrased question.
30
+ """
31
+
32
+
33
+ @tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_fixed(1))
34
+ @observe()
35
+ @db_cache
36
+ def rephrase_question_to_unconditioned(
37
+ question: str,
38
+ parent_question: str,
39
+ needed_parent_outcome: str,
40
+ engine: str = "gpt-4.1",
41
+ temperature: float = LLM_SUPER_LOW_TEMPERATURE,
42
+ seed: int = LLM_SEED,
43
+ prompt_template: str = REPHRASE_QUESTION_PROMPT,
44
+ max_tokens: int = 1024,
45
+ ) -> str:
46
+ try:
47
+ from langchain.prompts import ChatPromptTemplate
48
+ from langchain_openai import ChatOpenAI
49
+ except ImportError:
50
+ raise ImportError("langchain not installed")
51
+
52
+ llm = ChatOpenAI(
53
+ model_name=engine,
54
+ temperature=temperature,
55
+ seed=seed,
56
+ openai_api_key=APIKeys().openai_api_key,
57
+ )
58
+
59
+ prompt = ChatPromptTemplate.from_template(template=prompt_template)
60
+ messages = prompt.format_messages(
61
+ question=question,
62
+ parent_question=parent_question,
63
+ needed_parent_outcome=needed_parent_outcome,
64
+ )
65
+ completion = str(
66
+ llm.invoke(
67
+ messages, max_tokens=max_tokens, config=get_langfuse_langchain_config()
68
+ ).content
69
+ )
70
+
71
+ return completion
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.66.6
3
+ Version: 0.67.2
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.13
@@ -42,6 +42,7 @@ Requires-Dist: proto-plus (>=1.0.0,<2.0.0)
42
42
  Requires-Dist: protobuf (>=5.0.0,<6.0.0)
43
43
  Requires-Dist: psycopg2-binary (>=2.9.9,<3.0.0)
44
44
  Requires-Dist: pydantic (>=2.6.1,<3.0.0)
45
+ Requires-Dist: pydantic-ai (>=0.1.9,<1.0.0)
45
46
  Requires-Dist: pydantic-settings (>=2.4.0,<3.0.0)
46
47
  Requires-Dist: pymongo (>=4.8.0,<5.0.0)
47
48
  Requires-Dist: pytest-postgresql (>=6.1.1,<7.0.0)
@@ -26,7 +26,7 @@ prediction_market_agent_tooling/benchmark/utils.py,sha256=vmVTQLER8I7MM_bHFiavrN
26
26
  prediction_market_agent_tooling/chains.py,sha256=1qQstoqXMwqwM7k-KH7MjMz8Ei-D83KZByvDbCZpAxs,116
27
27
  prediction_market_agent_tooling/config.py,sha256=-kJfdDr-m0R-tGZ1KRI-hJJk0mXDt142CAlvwaJ2N2I,11778
28
28
  prediction_market_agent_tooling/data_download/langfuse_data_downloader.py,sha256=VY23h324VKIVkevj1B1O-zL1eEp9AElmcfn6SwYDUSc,14246
29
- prediction_market_agent_tooling/deploy/agent.py,sha256=KoL3gu1ofpqDSctCtR5e3g5fAPoiik4173TH9ozDunM,28076
29
+ prediction_market_agent_tooling/deploy/agent.py,sha256=-kLE5US4F3tFOhpztO4PHgopz5xTTGU-iHdgrRBIOLI,30623
30
30
  prediction_market_agent_tooling/deploy/agent_example.py,sha256=yS1fWkHynr9MYGNOM2WsCnRWLPaffY4bOc6bIudrdd4,1377
31
31
  prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=wiUV_zyB9ASLH-VUETIme0H_IMih8jZwiwEmcRa3W5Q,21125
32
32
  prediction_market_agent_tooling/deploy/constants.py,sha256=iobTlZpQD6_2UL9TfoElAnBEbqzIIAKZSsAoMCGhwmA,331
@@ -40,7 +40,7 @@ prediction_market_agent_tooling/jobs/jobs_models.py,sha256=DoZ9dlvVhpNrnINiR1uy6
40
40
  prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=lCymxn0iH4xDmqouTP2LMORoGCiTzlK1_yqYtx1Njj4,5132
41
41
  prediction_market_agent_tooling/loggers.py,sha256=o1HyvwtK1DbuC0YWQwJNqzXLLbSC41gNBkEUxiAziEg,5796
42
42
  prediction_market_agent_tooling/logprobs_parser.py,sha256=DBlBQtWX8_URXhzTU3YWIPa76Zx3QDHlx1ARqbgJsVI,5008
43
- prediction_market_agent_tooling/markets/agent_market.py,sha256=AMLkuhJwxNpMNBa4BnI72zRzbs-oGpdFQmWFovwtxBY,20280
43
+ prediction_market_agent_tooling/markets/agent_market.py,sha256=4ktJolOR4z4M7o3qnj2HBnBIA6WgXtYNe_Mbzgp5gTc,20411
44
44
  prediction_market_agent_tooling/markets/base_subgraph_handler.py,sha256=7RaYO_4qAmQ6ZGM8oPK2-CkiJfKmV9MxM-rJlduaecU,1971
45
45
  prediction_market_agent_tooling/markets/blockchain_utils.py,sha256=6REOt70v3vnzmtCbuRcUTdwt6htXy9nAfNkLOH3Bv1U,2987
46
46
  prediction_market_agent_tooling/markets/categorize.py,sha256=orLZlPaHgeREU66m1amxfWikeV77idV4sZDPB8NgSD0,1300
@@ -48,17 +48,17 @@ prediction_market_agent_tooling/markets/data_models.py,sha256=x4dBbdPb7dd2JiaD15
48
48
  prediction_market_agent_tooling/markets/manifold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  prediction_market_agent_tooling/markets/manifold/api.py,sha256=tWnjuqvU8pcCuja2B_ynHeds1iiEFc6QWHjeSO_GSxY,7676
50
50
  prediction_market_agent_tooling/markets/manifold/data_models.py,sha256=3z1gFbPMEgCDGqeH-IK8wcvqmIHgLdZX8C2M1UQ7iDw,6740
51
- prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=UfFoLrnweImQ-_9AoH2pOoVshu003Wegpnk78Ki2ffM,5369
51
+ prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=wbv2dMWFinxCiNJoGJQuzC9o9XiwmWPJ4fI2dJAx_IQ,5377
52
52
  prediction_market_agent_tooling/markets/manifold/utils.py,sha256=DCigEbpGWkXR-RZT_oJrvJhilmxKFAxcWMjUFi0nBBI,530
53
53
  prediction_market_agent_tooling/markets/market_fees.py,sha256=YeK3ynjYIguB0xf6sO5iyg9lOdW_HD4C6nbJfiGyRCU,1351
54
- prediction_market_agent_tooling/markets/markets.py,sha256=WAM2Ic8TS5hS1MOgH7zjznBdmCSfO9GaSpypM3a82HE,2922
54
+ prediction_market_agent_tooling/markets/markets.py,sha256=jEyYZVubrxnMvPHVO3ofxVXgzwmQ5sPlPx4QAVLy9qc,2777
55
55
  prediction_market_agent_tooling/markets/metaculus/api.py,sha256=4TRPGytQQbSdf42DCg2M_JWYPAuNjqZ3eBqaQBLkNks,2736
56
56
  prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=WjPt0MKeJNtoY-8oLQTLC8vQYYQ-dBj8UZoPq-UBYsQ,3288
57
- prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=ubJoMjDOnz3f7Er5aRJKb-G8_8Kr5DZz-UZ2rgLRgE8,5253
57
+ prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=JO7hO5RdoW0qFZ3HzSMNeAivT6ddqyVTpbq6M-WmonY,5235
58
58
  prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
59
  prediction_market_agent_tooling/markets/omen/cow_contracts.py,sha256=sFaW82u_haL4nze8fjTmnQsOuV0OecunQlAhh1OAw0w,1091
60
60
  prediction_market_agent_tooling/markets/omen/data_models.py,sha256=RsBYSbM4deA6Os4kQ3egH3HvwT80tQho6T1yyoATCMs,31103
61
- prediction_market_agent_tooling/markets/omen/omen.py,sha256=H25maQbdW0NDPZkFI_vWQgyOWEF07yHv7zYixSYQZhI,50611
61
+ prediction_market_agent_tooling/markets/omen/omen.py,sha256=UUeAxVRN0eSuRH2_T2qmYO7yEp3xf2_wv2aLtCtdDk0,50623
62
62
  prediction_market_agent_tooling/markets/omen/omen_constants.py,sha256=XtRk4vpxwUYkTndfjlcmghA-NOIneV8zdHFdyI7tHhM,487
63
63
  prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=27-HRngTqfk_wgvttB3GeVHhy_O2YZcz9izo9OufOI0,29991
64
64
  prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=D-ubf_LumHs_c5rBAAntQ8wGKprtO2V1JZeedmChNIE,11035
@@ -66,17 +66,17 @@ prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=2fL
66
66
  prediction_market_agent_tooling/markets/polymarket/api.py,sha256=-WW49xTYxzb6wBWZ8t6oOpf9puD6Vph8y2AYBocLGcM,4211
67
67
  prediction_market_agent_tooling/markets/polymarket/data_models.py,sha256=iRorGwuC9lXcWiVeOqykPip7FSx41RCAo6FOhyKlokU,4971
68
68
  prediction_market_agent_tooling/markets/polymarket/data_models_web.py,sha256=QQoFZp1hSllB9r3yaiglMbEtP3YNCCMm3VmqZ2RDnlo,10576
69
- prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=sPWd1jMcpcp1YbmzRIHUoDzF3GyzhigGsdj2FLchYW8,6677
69
+ prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=sNlhjTmmsmEXpjj7vGdLrZaZOJYXTTGrXjaZBgu8P0M,6689
70
70
  prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py,sha256=bjgTQXf7HhN0Pc_oy5fFNE6v12F8rK05nq6bozgOYIg,1587
71
71
  prediction_market_agent_tooling/markets/polymarket/utils.py,sha256=A_diygWKYyp4WHbxAlAVAuC0S0ZqbEKE80wxL6mxHKQ,1275
72
- prediction_market_agent_tooling/markets/seer/data_models.py,sha256=Fkn9jG8f0W8XlfM4EFfMSw0V0c3IGSqnKPt1UnG34Ag,6084
72
+ prediction_market_agent_tooling/markets/seer/data_models.py,sha256=CPWJptYHHCINgKIsfOO8b7anqQ_FMfyYL9tzESnnxOw,6103
73
73
  prediction_market_agent_tooling/markets/seer/exceptions.py,sha256=cEObdjluivD94tgOLzmimR7wgQEOt6SRakrYdhsRQtk,112
74
- prediction_market_agent_tooling/markets/seer/price_manager.py,sha256=MClY2NGwOV70nZYIcmzXFy6Ogd8NBIq7telQcQ3VcU4,6243
75
- prediction_market_agent_tooling/markets/seer/seer.py,sha256=SZ-01FntWjmhaMQpMe3td9tQ6LYFKdL2r-IVBlWNgNo,27361
74
+ prediction_market_agent_tooling/markets/seer/price_manager.py,sha256=PZf6-6zc6DvH1u65wHWyeD55lgG-UGnN_xzBMvrb3ug,7120
75
+ prediction_market_agent_tooling/markets/seer/seer.py,sha256=WX8xFdxnDiXCj4AkXnnjNvb3DJ4d8pQQdNK9bb397RQ,26670
76
76
  prediction_market_agent_tooling/markets/seer/seer_contracts.py,sha256=uMzpHpI6_tgfhWxPzupLdUJlZ1P2wr0rRiYjAGClKgU,4984
77
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=kNmWzCsKsMn_NUP3eiELl2Ldwx-bg2HojOgKtDXMZ0U,11704
78
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py,sha256=0izxS8Mtzonfdl9UqvFVXrdj0hVzieroekXhogfZKCw,1817
79
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py,sha256=q5AtlcYcMrD45bfo_ffPSdpmW1h1TU4xbZ4tdj1KMAk,3904
77
+ prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=Thg9GWnA8wnLR_fdVHDo5k1X0Olk5BIiOcksRFDcvR8,12979
78
+ prediction_market_agent_tooling/markets/seer/subgraph_data_models.py,sha256=7szvK5we3LF38UthWHg5V3LD6C137O5_WMEcP9Dwl8w,1763
79
+ prediction_market_agent_tooling/markets/seer/swap_pool_handler.py,sha256=kyU65phG7eHUyFMuwQ2HQUIayjBe6PCjBJ_pS7p164s,3434
80
80
  prediction_market_agent_tooling/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
81
  prediction_market_agent_tooling/tools/_generic_value.py,sha256=pD_PI13lpPp1gFoljHwa_Lzlp-u2pu0m-Z7LcxwDM2U,10618
82
82
  prediction_market_agent_tooling/tools/balances.py,sha256=Osab21btfJDw2Y-jT_TV-KHGrseCRxcsYeW6WcOMB8E,1050
@@ -88,8 +88,8 @@ prediction_market_agent_tooling/tools/caches/inmemory_cache.py,sha256=ZW5iI5rmjq
88
88
  prediction_market_agent_tooling/tools/caches/serializers.py,sha256=vFDx4fsPxclXp2q0sv27j4al_M_Tj9aR2JJP-xNHQXA,2151
89
89
  prediction_market_agent_tooling/tools/contract.py,sha256=pdr9ZYmj4QVUfgVKdvOU6ucYdBpJGdha_FMR_LgtcEs,22912
90
90
  prediction_market_agent_tooling/tools/costs.py,sha256=EaAJ7v9laD4VEV3d8B44M4u3_oEO_H16jRVCdoZ93Uw,954
91
- prediction_market_agent_tooling/tools/cow/cow_order.py,sha256=ZM6W0_LprH7iWEgTVKFgcBVbDrBDBuTEM-Kkg2R-DqY,13361
92
- prediction_market_agent_tooling/tools/cow/models.py,sha256=jAITIcg8GXEGtRQpezVvlqaQ5M8OEiiHqLwoyUeX3JM,655
91
+ prediction_market_agent_tooling/tools/cow/cow_order.py,sha256=CtCPnBcHSrWmcCuNxqqPHQwyBTL6pLqtSvNJt49nHno,13810
92
+ prediction_market_agent_tooling/tools/cow/models.py,sha256=Z_XUAMj5AGHZE-A92oRG0DuPeXTSyWnZRIwKQ49LaXU,709
93
93
  prediction_market_agent_tooling/tools/cow/semaphore.py,sha256=IJGKRgvXnRSkEt_z1i5-eFIoVMcyhr7HK0JfIz1MXdQ,3738
94
94
  prediction_market_agent_tooling/tools/custom_exceptions.py,sha256=Fh8z1fbwONvP4-j7AmV_PuEcoqb6-QXa9PJ9m7guMcM,93
95
95
  prediction_market_agent_tooling/tools/datetime_utc.py,sha256=8_WackjtjC8zHXrhQFTGQ6e6Fz_6llWoKR4CSFvIv9I,2766
@@ -113,6 +113,7 @@ prediction_market_agent_tooling/tools/perplexity/perplexity_search.py,sha256=Ii-
113
113
  prediction_market_agent_tooling/tools/relevant_news_analysis/data_models.py,sha256=95l84aztFaxcRLLcRQ46yKJbIlOEuDAbIGLouyliDzA,1316
114
114
  prediction_market_agent_tooling/tools/relevant_news_analysis/relevant_news_analysis.py,sha256=r4MdP5uEMlaCwoa2XQZzGq3oZEqnoo9S4dg8uzXfSOY,5473
115
115
  prediction_market_agent_tooling/tools/relevant_news_analysis/relevant_news_cache.py,sha256=kNWq92T11Knb9mYBZlMiZUzOpKgCd-5adanylQUMRJA,3085
116
+ prediction_market_agent_tooling/tools/rephrase.py,sha256=mvprYrZST2LaN3F6n5Ew3IpO_xUMnKpxSMggFcZ3IgM,2319
116
117
  prediction_market_agent_tooling/tools/safe.py,sha256=o477HGPQv7X_eDoOeYoELCHryiq1_102y_JVhGEPDXw,5165
117
118
  prediction_market_agent_tooling/tools/singleton.py,sha256=CiIELUiI-OeS7U7eeHEt0rnVhtQGzwoUdAgn_7u_GBM,729
118
119
  prediction_market_agent_tooling/tools/streamlit_user_login.py,sha256=NXEqfjT9Lc9QtliwSGRASIz1opjQ7Btme43H4qJbzgE,3010
@@ -127,8 +128,8 @@ prediction_market_agent_tooling/tools/tokens/usd.py,sha256=yuW8iPPtcpP4eLH2nORMD
127
128
  prediction_market_agent_tooling/tools/transaction_cache.py,sha256=K5YKNL2_tR10Iw2TD9fuP-CTGpBbZtNdgbd0B_R7pjg,1814
128
129
  prediction_market_agent_tooling/tools/utils.py,sha256=mbOGoWKalNIm7M2K51TEPGwU9oVp1Z6SPsFaBgbn6ws,7397
129
130
  prediction_market_agent_tooling/tools/web3_utils.py,sha256=0r26snqCXGdLKCWA8jpe7DV8x2NPYWZwOy4oyKyDCYk,12615
130
- prediction_market_agent_tooling-0.66.6.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
131
- prediction_market_agent_tooling-0.66.6.dist-info/METADATA,sha256=zs-VgqXzeMZUqr0GWFQG4mrZ6W91ca3vySAs8c5Zuvo,8726
132
- prediction_market_agent_tooling-0.66.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
133
- prediction_market_agent_tooling-0.66.6.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
134
- prediction_market_agent_tooling-0.66.6.dist-info/RECORD,,
131
+ prediction_market_agent_tooling-0.67.2.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
132
+ prediction_market_agent_tooling-0.67.2.dist-info/METADATA,sha256=dgNwYA_B2Ko36dTkAxhrAxKAfcrsvpIF26gN3sOiM-A,8770
133
+ prediction_market_agent_tooling-0.67.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
134
+ prediction_market_agent_tooling-0.67.2.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
135
+ prediction_market_agent_tooling-0.67.2.dist-info/RECORD,,