prediction-market-agent-tooling 0.68.0.dev999__py3-none-any.whl → 0.68.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. prediction_market_agent_tooling/chains.py +1 -0
  2. prediction_market_agent_tooling/config.py +37 -2
  3. prediction_market_agent_tooling/deploy/agent.py +13 -19
  4. prediction_market_agent_tooling/deploy/betting_strategy.py +11 -3
  5. prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
  6. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +3 -3
  7. prediction_market_agent_tooling/markets/agent_market.py +16 -9
  8. prediction_market_agent_tooling/markets/blockchain_utils.py +3 -3
  9. prediction_market_agent_tooling/markets/omen/data_models.py +3 -18
  10. prediction_market_agent_tooling/markets/omen/omen.py +26 -11
  11. prediction_market_agent_tooling/markets/omen/omen_contracts.py +2 -196
  12. prediction_market_agent_tooling/markets/omen/omen_resolving.py +2 -2
  13. prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +13 -11
  14. prediction_market_agent_tooling/markets/polymarket/api.py +35 -1
  15. prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
  16. prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
  17. prediction_market_agent_tooling/markets/polymarket/data_models.py +33 -5
  18. prediction_market_agent_tooling/markets/polymarket/polymarket.py +247 -18
  19. prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
  20. prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +2 -1
  21. prediction_market_agent_tooling/markets/seer/data_models.py +1 -1
  22. prediction_market_agent_tooling/markets/seer/price_manager.py +69 -1
  23. prediction_market_agent_tooling/markets/seer/seer.py +35 -20
  24. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +7 -3
  25. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +2 -0
  26. prediction_market_agent_tooling/tools/contract.py +236 -4
  27. prediction_market_agent_tooling/tools/cow/cow_order.py +13 -8
  28. prediction_market_agent_tooling/tools/hexbytes_custom.py +3 -9
  29. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +1 -1
  30. prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
  31. prediction_market_agent_tooling/tools/web3_utils.py +9 -4
  32. {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.68.1.dist-info}/METADATA +8 -7
  33. {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.68.1.dist-info}/RECORD +36 -34
  34. prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -366
  35. {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.68.1.dist-info}/LICENSE +0 -0
  36. {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.68.1.dist-info}/WHEEL +0 -0
  37. {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.68.1.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,19 @@
1
1
  import typing as t
2
2
 
3
+ import cachetools
4
+ from web3 import Web3
5
+
6
+ from prediction_market_agent_tooling.config import APIKeys, RPCConfig
3
7
  from prediction_market_agent_tooling.gtypes import (
4
8
  USD,
9
+ ChecksumAddress,
5
10
  CollateralToken,
6
11
  HexBytes,
7
12
  OutcomeStr,
13
+ OutcomeToken,
8
14
  Probability,
15
+ Wei,
16
+ xDai,
9
17
  )
10
18
  from prediction_market_agent_tooling.loggers import logger
11
19
  from prediction_market_agent_tooling.markets.agent_market import (
@@ -13,27 +21,45 @@ from prediction_market_agent_tooling.markets.agent_market import (
13
21
  ConditionalFilterType,
14
22
  FilterBy,
15
23
  MarketFees,
24
+ ProcessedMarket,
16
25
  QuestionType,
17
26
  SortBy,
18
27
  )
19
- from prediction_market_agent_tooling.markets.data_models import Resolution
28
+ from prediction_market_agent_tooling.markets.data_models import (
29
+ ExistingPosition,
30
+ Resolution,
31
+ )
20
32
  from prediction_market_agent_tooling.markets.polymarket.api import (
21
33
  PolymarketOrderByEnum,
22
34
  get_polymarkets_with_pagination,
35
+ get_user_positions,
36
+ )
37
+ from prediction_market_agent_tooling.markets.polymarket.clob_manager import (
38
+ ClobManager,
39
+ PolymarketPriceSideEnum,
40
+ )
41
+ from prediction_market_agent_tooling.markets.polymarket.constants import (
42
+ POLYMARKET_TINY_BET_AMOUNT,
23
43
  )
24
44
  from prediction_market_agent_tooling.markets.polymarket.data_models import (
45
+ POLYMARKET_BASE_URL,
25
46
  PolymarketGammaResponseDataItem,
26
47
  )
27
- from prediction_market_agent_tooling.markets.polymarket.data_models_web import (
28
- POLYMARKET_BASE_URL,
48
+ from prediction_market_agent_tooling.markets.polymarket.polymarket_contracts import (
49
+ USDCeContract,
29
50
  )
30
51
  from prediction_market_agent_tooling.markets.polymarket.polymarket_subgraph_handler import (
31
52
  ConditionSubgraphModel,
32
53
  PolymarketSubgraphHandler,
33
54
  )
34
55
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
56
+ from prediction_market_agent_tooling.tools.tokens.usd import get_token_in_usd
35
57
  from prediction_market_agent_tooling.tools.utils import check_not_none
36
58
 
59
+ SHARED_CACHE: cachetools.TTLCache[t.Hashable, t.Any] = cachetools.TTLCache(
60
+ maxsize=256, ttl=10 * 60
61
+ )
62
+
37
63
 
38
64
  class PolymarketAgentMarket(AgentMarket):
39
65
  """
@@ -47,6 +73,15 @@ class PolymarketAgentMarket(AgentMarket):
47
73
  # But then in the new subgraph API, they have `fee: BigInt! (Percentage fee of trades taken by market maker. A 2% fee is represented as 2*10^16)`.
48
74
  # TODO: Check out the fees while integrating the subgraph API or if we implement placing of bets on Polymarket.
49
75
  fees: MarketFees = MarketFees.get_zero_fees()
76
+ condition_id: HexBytes
77
+ liquidity_usd: USD
78
+ token_ids: list[int]
79
+ closed_flag_from_polymarket: bool
80
+ active_flag_from_polymarket: bool
81
+
82
+ @staticmethod
83
+ def collateral_token_address() -> ChecksumAddress:
84
+ return USDCeContract().address
50
85
 
51
86
  @staticmethod
52
87
  def build_resolution_from_condition(
@@ -73,7 +108,7 @@ class PolymarketAgentMarket(AgentMarket):
73
108
  if len(payout_numerator_indices_gt_0) != 1:
74
109
  # These cases involve multi-categorical resolution (to be implemented https://github.com/gnosis/prediction-market-agent-tooling/issues/770)
75
110
  logger.warning(
76
- f"Only binary markets are supported. Got payout numerators: {condition_model.payoutNumerators} for condition_id {condition_id.hex()}"
111
+ f"Only binary markets are supported. Got payout numerators: {condition_model.payoutNumerators} for condition_id {condition_id.to_0x_hex()}"
77
112
  )
78
113
  return Resolution(outcome=None, invalid=False)
79
114
 
@@ -81,42 +116,68 @@ class PolymarketAgentMarket(AgentMarket):
81
116
  resolved_outcome = outcomes[payout_numerator_indices_gt_0[0]]
82
117
  return Resolution.from_answer(resolved_outcome)
83
118
 
119
+ def get_token_id_for_outcome(self, outcome: OutcomeStr) -> int:
120
+ outcome_idx = self.outcomes.index(outcome)
121
+ return self.token_ids[outcome_idx]
122
+
84
123
  @staticmethod
85
124
  def from_data_model(
86
125
  model: PolymarketGammaResponseDataItem,
87
126
  condition_model_dict: dict[HexBytes, ConditionSubgraphModel],
88
- ) -> "PolymarketAgentMarket":
127
+ ) -> t.Optional["PolymarketAgentMarket"]:
89
128
  # If len(model.markets) > 0, this denotes a categorical market.
90
129
  markets = check_not_none(model.markets)
91
130
  outcomes = markets[0].outcomes_list
92
131
  outcome_prices = markets[0].outcome_prices
93
132
  if not outcome_prices:
94
- # We give random prices
95
- outcome_prices = [0.5, 0.5]
133
+ logger.info(f"Market has no outcome prices. Skipping. {model=}")
134
+ return None
135
+
96
136
  probabilities = {o: Probability(op) for o, op in zip(outcomes, outcome_prices)}
97
137
 
138
+ condition_id = markets[0].conditionId
98
139
  resolution = PolymarketAgentMarket.build_resolution_from_condition(
99
- condition_id=markets[0].conditionId,
140
+ condition_id=condition_id,
100
141
  condition_model_dict=condition_model_dict,
101
142
  outcomes=outcomes,
102
143
  )
103
144
 
104
145
  return PolymarketAgentMarket(
105
146
  id=model.id,
147
+ condition_id=condition_id,
106
148
  question=model.title,
107
149
  description=model.description,
108
150
  outcomes=outcomes,
109
151
  resolution=resolution,
110
152
  created_time=model.startDate,
111
153
  close_time=model.endDate,
154
+ closed_flag_from_polymarket=model.closed,
155
+ active_flag_from_polymarket=model.active,
112
156
  url=model.url,
113
157
  volume=CollateralToken(model.volume) if model.volume else None,
114
158
  outcome_token_pool=None,
115
159
  probabilities=probabilities,
160
+ liquidity_usd=USD(model.liquidity)
161
+ if model.liquidity is not None
162
+ else USD(0),
163
+ token_ids=markets[0].token_ids,
116
164
  )
117
165
 
118
166
  def get_tiny_bet_amount(self) -> CollateralToken:
119
- raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")
167
+ return CollateralToken(POLYMARKET_TINY_BET_AMOUNT.value)
168
+
169
+ def get_token_in_usd(self, x: CollateralToken) -> USD:
170
+ return get_token_in_usd(x, self.collateral_token_address())
171
+
172
+ @staticmethod
173
+ def get_trade_balance(api_keys: APIKeys, web3: Web3 | None = None) -> USD:
174
+ usdc_balance_wei = USDCeContract().balanceOf(
175
+ for_address=api_keys.public_key, web3=web3
176
+ )
177
+ return USD(usdc_balance_wei.value * 1e-6)
178
+
179
+ def get_liquidity(self, web3: Web3 | None = None) -> CollateralToken:
180
+ return CollateralToken(self.liquidity_usd.value)
120
181
 
121
182
  def place_bet(self, outcome: OutcomeStr, amount: USD) -> str:
122
183
  raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")
@@ -146,6 +207,7 @@ class PolymarketAgentMarket(AgentMarket):
146
207
  match sort_by:
147
208
  case SortBy.NEWEST:
148
209
  order_by = PolymarketOrderByEnum.START_DATE
210
+ ascending = False
149
211
  case SortBy.CLOSING_SOONEST:
150
212
  ascending = True
151
213
  order_by = PolymarketOrderByEnum.END_DATE
@@ -168,15 +230,182 @@ class PolymarketAgentMarket(AgentMarket):
168
230
  )
169
231
 
170
232
  condition_models = PolymarketSubgraphHandler().get_conditions(
171
- condition_ids=[
172
- market.markets[0].conditionId
173
- for market in markets
174
- if market.markets is not None
175
- ]
233
+ condition_ids=list(
234
+ set(
235
+ [
236
+ market.markets[0].conditionId
237
+ for market in markets
238
+ if market.markets is not None
239
+ ]
240
+ )
241
+ )
176
242
  )
177
243
  condition_models_dict = {c.id: c for c in condition_models}
178
244
 
179
- return [
180
- PolymarketAgentMarket.from_data_model(m, condition_models_dict)
181
- for m in markets
182
- ]
245
+ result_markets: list[PolymarketAgentMarket] = []
246
+ for m in markets:
247
+ market = PolymarketAgentMarket.from_data_model(m, condition_models_dict)
248
+ if market is not None:
249
+ result_markets.append(market)
250
+ return result_markets
251
+
252
+ def ensure_min_native_balance(
253
+ self,
254
+ min_required_balance: xDai,
255
+ multiplier: float = 3.0,
256
+ web3: Web3 | None = None,
257
+ ) -> None:
258
+ balance_collateral = USDCeContract().balanceOf(
259
+ for_address=APIKeys().public_key, web3=web3
260
+ )
261
+ # USDC has 6 decimals, xDAI has 18. We convert from Wei into atomic units.
262
+ balance_collateral_atomic = CollateralToken(float(balance_collateral) / 1e6)
263
+ if balance_collateral_atomic < min_required_balance.as_token:
264
+ raise EnvironmentError(
265
+ f"USDC balance {balance_collateral_atomic} < {min_required_balance.as_token=}"
266
+ )
267
+
268
+ @staticmethod
269
+ def redeem_winnings(api_keys: APIKeys) -> None:
270
+ # ToDo - implement me - https://github.com/gnosis/prediction-market-agent-tooling/issues/824
271
+ pass
272
+
273
+ @staticmethod
274
+ def verify_operational_balance(api_keys: APIKeys) -> bool:
275
+ """Method for checking if agent has enough funds to pay for gas fees."""
276
+ web3 = RPCConfig().get_polygon_web3()
277
+ pol_balance: Wei = Wei(web3.eth.get_balance(api_keys.public_key))
278
+ return pol_balance > Wei(int(0.001 * 1e18))
279
+
280
+ def store_prediction(
281
+ self,
282
+ processed_market: ProcessedMarket | None,
283
+ keys: APIKeys,
284
+ agent_name: str,
285
+ ) -> None:
286
+ pass
287
+
288
+ def store_trades(
289
+ self,
290
+ traded_market: ProcessedMarket | None,
291
+ keys: APIKeys,
292
+ agent_name: str,
293
+ web3: Web3 | None = None,
294
+ ) -> None:
295
+ logger.info("Storing trades deactivated for Polymarket.")
296
+ # Understand how market_id can be represented.
297
+ # Condition_id could work but length doesn't seem to match.
298
+
299
+ @classmethod
300
+ def get_user_url(cls, keys: APIKeys) -> str:
301
+ return f"https://polymarket.com/{keys.public_key}"
302
+
303
+ def get_position(
304
+ self, user_id: str, web3: Web3 | None = None
305
+ ) -> ExistingPosition | None:
306
+ """
307
+ Fetches position from the user in a given market.
308
+ """
309
+ positions = get_user_positions(
310
+ user_id=Web3.to_checksum_address(user_id), condition_ids=[self.condition_id]
311
+ )
312
+ if not positions:
313
+ return None
314
+
315
+ amounts_ot = {i: OutcomeToken(0) for i in self.outcomes}
316
+ amounts_potential = {i: USD(0) for i in self.outcomes}
317
+ amounts_current = {i: USD(0) for i in self.outcomes}
318
+
319
+ for p in positions:
320
+ if p.conditionId != self.condition_id.to_0x_hex():
321
+ continue
322
+
323
+ amounts_potential[OutcomeStr(p.outcome)] = USD(p.size)
324
+ amounts_ot[OutcomeStr(p.outcome)] = OutcomeToken(p.size)
325
+ amounts_current[OutcomeStr(p.outcome)] = USD(p.currentValue)
326
+
327
+ return ExistingPosition(
328
+ amounts_potential=amounts_potential,
329
+ amounts_ot=amounts_ot,
330
+ market_id=self.id,
331
+ amounts_current=amounts_current,
332
+ )
333
+
334
+ def can_be_traded(self) -> bool:
335
+ return (
336
+ self.active_flag_from_polymarket
337
+ and not self.closed_flag_from_polymarket
338
+ and self.liquidity_usd
339
+ > USD(5) # we conservatively require some positive liquidity to trade on
340
+ )
341
+
342
+ def get_buy_token_amount(
343
+ self, bet_amount: USD | CollateralToken, outcome_str: OutcomeStr
344
+ ) -> OutcomeToken:
345
+ """Returns number of outcome tokens returned for a given bet expressed in collateral units."""
346
+
347
+ if outcome_str not in self.outcomes:
348
+ raise ValueError(
349
+ f"Outcome {outcome_str} not found in market outcomes {self.outcomes}"
350
+ )
351
+
352
+ token_id = self.get_token_id_for_outcome(outcome_str)
353
+
354
+ price = ClobManager(APIKeys()).get_token_price(
355
+ token_id=token_id, side=PolymarketPriceSideEnum.BUY
356
+ )
357
+ if not price:
358
+ raise ValueError(
359
+ f"Could not get price for outcome {outcome_str} with token_id {token_id}"
360
+ )
361
+
362
+ # we work with floats since USD and Collateral are the same on Polymarket
363
+ buy_token_amount = bet_amount.value / price.value
364
+ logger.info(f"Buy token amount: {buy_token_amount=}")
365
+ return OutcomeToken(buy_token_amount)
366
+
367
+ def buy_tokens(self, outcome: OutcomeStr, amount: USD) -> str:
368
+ clob_manager = ClobManager(APIKeys())
369
+ token_id = self.get_token_id_for_outcome(outcome)
370
+
371
+ created_order = clob_manager.place_buy_market_order(
372
+ token_id=token_id, usdc_amount=amount
373
+ )
374
+ if not created_order.success:
375
+ raise ValueError(f"Error creating order: {created_order}")
376
+
377
+ return created_order.transactionsHashes[0].to_0x_hex()
378
+
379
+ def sell_tokens(
380
+ self,
381
+ outcome: OutcomeStr,
382
+ amount: USD | OutcomeToken,
383
+ api_keys: APIKeys | None = None,
384
+ ) -> str:
385
+ """
386
+ Polymarket's API expect shares to be sold. 1 share == 1 outcome token / 1e6.
387
+ The number of outcome tokens matches the `balanceOf` of the conditionalTokens contract.
388
+ In comparison, the number of shares match the position.size from the user position.
389
+ """
390
+ logger.info(f"Selling {amount=} from {outcome=}")
391
+ clob_manager = ClobManager(api_keys=api_keys or APIKeys())
392
+ token_id = self.get_token_id_for_outcome(outcome)
393
+ token_shares: OutcomeToken
394
+ if isinstance(amount, OutcomeToken):
395
+ token_shares = amount
396
+ elif isinstance(amount, USD):
397
+ token_price = clob_manager.get_token_price(
398
+ token_id=token_id, side=PolymarketPriceSideEnum.SELL
399
+ )
400
+ # We expect that our order sizes don't move the price too much.
401
+ token_shares = OutcomeToken(amount.value / token_price.value)
402
+ else:
403
+ raise ValueError(f"Unsupported amount type {type(amount)}")
404
+
405
+ created_order = clob_manager.place_sell_market_order(
406
+ token_id=token_id, token_shares=token_shares
407
+ )
408
+ if not created_order.success:
409
+ raise ValueError(f"Error creating order: {created_order}")
410
+
411
+ return created_order.transactionsHashes[0].to_0x_hex()
@@ -0,0 +1,35 @@
1
+ from web3 import Web3
2
+
3
+ from prediction_market_agent_tooling.config import APIKeys
4
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress
5
+ from prediction_market_agent_tooling.tools.contract import (
6
+ ConditionalTokenContract,
7
+ ContractERC20BaseClass,
8
+ ContractOnPolygonChain,
9
+ )
10
+
11
+
12
+ class USDCeContract(ContractERC20BaseClass, ContractOnPolygonChain):
13
+ # USDC.e is used by Polymarket.
14
+ address: ChecksumAddress = Web3.to_checksum_address(
15
+ "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
16
+ )
17
+
18
+
19
+ class PolymarketConditionalTokenContract(
20
+ ConditionalTokenContract, ContractOnPolygonChain
21
+ ):
22
+ address: ChecksumAddress = Web3.to_checksum_address(
23
+ "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
24
+ )
25
+
26
+ def approve_if_not_approved(
27
+ self, api_keys: APIKeys, for_address: ChecksumAddress, web3: Web3 | None = None
28
+ ) -> None:
29
+ is_approved = self.isApprovedForAll(
30
+ owner=api_keys.public_key, for_address=for_address, web3=web3
31
+ )
32
+ if not is_approved:
33
+ self.setApprovalForAll(
34
+ api_keys=api_keys, for_address=for_address, approve=True, web3=web3
35
+ )
@@ -30,8 +30,9 @@ class PolymarketSubgraphHandler(BaseSubgraphHandler):
30
30
  def get_conditions(
31
31
  self, condition_ids: list[HexBytes]
32
32
  ) -> list[ConditionSubgraphModel]:
33
- where_stms = {"id_in": [i.hex() for i in condition_ids]}
33
+ where_stms = {"id_in": [i.to_0x_hex() for i in condition_ids]}
34
34
  conditions = self.conditions_subgraph.Query.conditions(
35
+ first=len(condition_ids),
35
36
  where=where_stms,
36
37
  )
37
38
 
@@ -160,7 +160,7 @@ class SeerMarket(BaseModel):
160
160
  @property
161
161
  def url(self) -> str:
162
162
  chain_id = RPCConfig().chain_id
163
- return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.hex()}")
163
+ return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.to_0x_hex()}")
164
164
 
165
165
 
166
166
  class SeerMarketWithQuestions(SeerMarket):
@@ -2,11 +2,15 @@ from cachetools import TTLCache, cached
2
2
  from pydantic import BaseModel
3
3
  from web3 import Web3
4
4
 
5
+ from prediction_market_agent_tooling.deploy.constants import (
6
+ INVALID_OUTCOME_LOWERCASE_IDENTIFIER,
7
+ )
5
8
  from prediction_market_agent_tooling.gtypes import (
6
9
  ChecksumAddress,
7
10
  CollateralToken,
8
11
  HexAddress,
9
12
  OutcomeStr,
13
+ OutcomeToken,
10
14
  Probability,
11
15
  )
12
16
  from prediction_market_agent_tooling.loggers import logger
@@ -46,7 +50,7 @@ class PriceManager:
46
50
  price_diff_pct = abs(old_price - normalized_price) / max(old_price, 0.01)
47
51
  if price_diff_pct > max_price_diff:
48
52
  logger.info(
49
- f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.hex()} "
53
+ f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.to_0x_hex()} "
50
54
  )
51
55
 
52
56
  def get_price_for_token(self, token: ChecksumAddress) -> CollateralToken | None:
@@ -182,3 +186,67 @@ class PriceManager:
182
186
  normalized_prices[outcome] = new_price
183
187
 
184
188
  return normalized_prices
189
+
190
+ def build_initial_probs_from_pool(
191
+ self, model: SeerMarket, wrapped_tokens: list[ChecksumAddress]
192
+ ) -> tuple[dict[OutcomeStr, Probability], dict[OutcomeStr, OutcomeToken]]:
193
+ """
194
+ Builds a map of outcome to probability and outcome token pool.
195
+ """
196
+ probability_map = {}
197
+ outcome_token_pool = {}
198
+ wrapped_tokens_with_supply = [
199
+ (
200
+ token,
201
+ SeerSubgraphHandler().get_pool_by_token(
202
+ token, model.collateral_token_contract_address_checksummed
203
+ ),
204
+ )
205
+ for token in wrapped_tokens
206
+ ]
207
+ wrapped_tokens_with_supply = [
208
+ (token, pool)
209
+ for token, pool in wrapped_tokens_with_supply
210
+ if pool is not None
211
+ ]
212
+
213
+ for token, pool in wrapped_tokens_with_supply:
214
+ if pool is None or pool.token1.id is None or pool.token0.id is None:
215
+ continue
216
+ if HexBytes(token) == HexBytes(pool.token1.id):
217
+ outcome_token_pool[
218
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
219
+ ] = (
220
+ OutcomeToken(pool.totalValueLockedToken0)
221
+ if pool.totalValueLockedToken0 is not None
222
+ else OutcomeToken(0)
223
+ )
224
+ probability_map[
225
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
226
+ ] = Probability(pool.token0Price.value)
227
+ else:
228
+ outcome_token_pool[
229
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
230
+ ] = (
231
+ OutcomeToken(pool.totalValueLockedToken1)
232
+ if pool.totalValueLockedToken1 is not None
233
+ else OutcomeToken(0)
234
+ )
235
+ probability_map[
236
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
237
+ ] = Probability(pool.token1Price.value)
238
+
239
+ for outcome in model.outcomes:
240
+ if outcome not in outcome_token_pool:
241
+ outcome_token_pool[outcome] = OutcomeToken(0)
242
+ logger.warning(
243
+ f"Outcome {outcome} not found in outcome_token_pool for market {self.seer_market.url}."
244
+ )
245
+ if outcome not in probability_map:
246
+ if INVALID_OUTCOME_LOWERCASE_IDENTIFIER not in outcome.lower():
247
+ raise PriceCalculationError(
248
+ f"Couldn't get probability for {outcome} for market {self.seer_market.url}."
249
+ )
250
+ else:
251
+ probability_map[outcome] = Probability(0)
252
+ return probability_map, outcome_token_pool
@@ -28,7 +28,6 @@ from prediction_market_agent_tooling.markets.agent_market import (
28
28
  FilterBy,
29
29
  ParentMarket,
30
30
  ProcessedMarket,
31
- ProcessedTradedMarket,
32
31
  QuestionType,
33
32
  SortBy,
34
33
  )
@@ -38,7 +37,10 @@ from prediction_market_agent_tooling.markets.data_models import (
38
37
  Resolution,
39
38
  )
40
39
  from prediction_market_agent_tooling.markets.market_fees import MarketFees
41
- from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
40
+ from prediction_market_agent_tooling.markets.omen.omen import (
41
+ OmenAgentMarket,
42
+ send_keeping_token_to_eoa_xdai,
43
+ )
42
44
  from prediction_market_agent_tooling.markets.omen.omen_constants import (
43
45
  SDAI_CONTRACT_ADDRESS,
44
46
  )
@@ -136,7 +138,7 @@ class SeerAgentMarket(AgentMarket):
136
138
 
137
139
  def store_trades(
138
140
  self,
139
- traded_market: ProcessedTradedMarket | None,
141
+ traded_market: ProcessedMarket | None,
140
142
  keys: APIKeys,
141
143
  agent_name: str,
142
144
  web3: Web3 | None = None,
@@ -262,10 +264,6 @@ class SeerAgentMarket(AgentMarket):
262
264
  amounts_ot=amounts_ot,
263
265
  )
264
266
 
265
- @staticmethod
266
- def get_user_id(api_keys: APIKeys) -> str:
267
- return OmenAgentMarket.get_user_id(api_keys)
268
-
269
267
  @staticmethod
270
268
  def _filter_markets_contained_in_trades(
271
269
  api_keys: APIKeys,
@@ -325,9 +323,9 @@ class SeerAgentMarket(AgentMarket):
325
323
  amounts=market_balances[market.id],
326
324
  )
327
325
  gnosis_router.redeem_to_base(api_keys, params=params, web3=web3)
328
- logger.info(f"Redeemed market {market.id.hex()}")
326
+ logger.info(f"Redeemed market {market.id.to_0x_hex()}")
329
327
  except Exception as e:
330
- logger.error(f"Failed to redeem market {market.id.hex()}, {e}")
328
+ logger.error(f"Failed to redeem market {market.id.to_0x_hex()}, {e}")
331
329
 
332
330
  # GnosisRouter withdraws sDai into wxDAI/xDai on its own, so no auto-withdraw needed by us.
333
331
 
@@ -345,6 +343,17 @@ class SeerAgentMarket(AgentMarket):
345
343
 
346
344
  return False
347
345
 
346
+ def ensure_min_native_balance(
347
+ self,
348
+ min_required_balance: xDai,
349
+ multiplier: float = 3.0,
350
+ ) -> None:
351
+ send_keeping_token_to_eoa_xdai(
352
+ api_keys=APIKeys(),
353
+ min_required_balance=min_required_balance,
354
+ multiplier=multiplier,
355
+ )
356
+
348
357
  @staticmethod
349
358
  def verify_operational_balance(api_keys: APIKeys) -> bool:
350
359
  return OmenAgentMarket.verify_operational_balance(api_keys=api_keys)
@@ -367,7 +376,7 @@ class SeerAgentMarket(AgentMarket):
367
376
  raise ValueError("Seer categorical markets must have 1 question.")
368
377
 
369
378
  question = model.questions[0]
370
- outcome = model.outcomes[int(question.question.best_answer.hex(), 16)]
379
+ outcome = model.outcomes[int(question.question.best_answer.to_0x_hex(), 16)]
371
380
  return Resolution(outcome=outcome, invalid=False)
372
381
 
373
382
  @staticmethod
@@ -411,13 +420,17 @@ class SeerAgentMarket(AgentMarket):
411
420
  must_have_prices: bool,
412
421
  ) -> t.Optional["SeerAgentMarket"]:
413
422
  price_manager = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
414
-
415
- probability_map = {}
423
+ wrapped_tokens = [Web3.to_checksum_address(i) for i in model.wrapped_tokens]
416
424
  try:
417
- probability_map = price_manager.build_probability_map()
425
+ (
426
+ probability_map,
427
+ outcome_token_pool,
428
+ ) = price_manager.build_initial_probs_from_pool(
429
+ model=model, wrapped_tokens=wrapped_tokens
430
+ )
418
431
  except PriceCalculationError as e:
419
432
  logger.info(
420
- f"Error when calculating probabilities for market {model.id.hex()} - {e}"
433
+ f"Error when calculating probabilities for market {model.id.to_0x_hex()} - {e}"
421
434
  )
422
435
  if must_have_prices:
423
436
  # Price calculation failed, so don't return the market
@@ -428,7 +441,7 @@ class SeerAgentMarket(AgentMarket):
428
441
  parent = SeerAgentMarket.get_parent(model=model, seer_subgraph=seer_subgraph)
429
442
 
430
443
  market = SeerAgentMarket(
431
- id=model.id.hex(),
444
+ id=model.id.to_0x_hex(),
432
445
  question=model.title,
433
446
  creator=model.creator,
434
447
  created_time=model.created_time,
@@ -437,9 +450,9 @@ class SeerAgentMarket(AgentMarket):
437
450
  condition_id=model.condition_id,
438
451
  url=model.url,
439
452
  close_time=model.close_time,
440
- wrapped_tokens=[Web3.to_checksum_address(i) for i in model.wrapped_tokens],
453
+ wrapped_tokens=wrapped_tokens,
441
454
  fees=MarketFees.get_zero_fees(),
442
- outcome_token_pool=None,
455
+ outcome_token_pool=outcome_token_pool,
443
456
  outcomes_supply=model.outcomes_supply,
444
457
  resolution=resolution,
445
458
  volume=None,
@@ -521,7 +534,9 @@ class SeerAgentMarket(AgentMarket):
521
534
  )
522
535
 
523
536
  token_balance = token_contract.balance_of_in_tokens(
524
- for_address=Web3.to_checksum_address(HexAddress(HexStr(pool.id.hex()))),
537
+ for_address=Web3.to_checksum_address(
538
+ HexAddress(HexStr(pool.id.to_0x_hex()))
539
+ ),
525
540
  web3=web3,
526
541
  )
527
542
  collateral_balance = p.get_amount_of_token_in_collateral(
@@ -602,7 +617,7 @@ class SeerAgentMarket(AgentMarket):
602
617
  )
603
618
  cow_tx_hash = trades[0].txHash
604
619
  logger.info(f"TxHash is {cow_tx_hash=} for {order_metadata.uid.root=}.")
605
- return cow_tx_hash.hex()
620
+ return cow_tx_hash.to_0x_hex()
606
621
 
607
622
  except (
608
623
  UnexpectedResponseError,
@@ -635,7 +650,7 @@ class SeerAgentMarket(AgentMarket):
635
650
  )
636
651
  swap_pool_tx_hash = tx_receipt["transactionHash"]
637
652
  logger.info(f"TxHash is {swap_pool_tx_hash=}.")
638
- return swap_pool_tx_hash.hex()
653
+ return swap_pool_tx_hash.to_0x_hex()
639
654
 
640
655
  def place_bet(
641
656
  self,