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
@@ -2,3 +2,4 @@ from prediction_market_agent_tooling.gtypes import ChainID
2
2
 
3
3
  ETHEREUM_ID = ChainID(1)
4
4
  GNOSIS_CHAIN_ID = ChainID(100)
5
+ POLYGON_CHAIN_ID = ChainID(137)
@@ -2,6 +2,7 @@ import json
2
2
  import typing as t
3
3
  from copy import deepcopy
4
4
 
5
+ import cachetools
5
6
  from eth_account.signers.local import LocalAccount
6
7
  from eth_typing import URI
7
8
  from pydantic import Field, model_validator
@@ -12,8 +13,13 @@ from safe_eth.eth import EthereumClient
12
13
  from safe_eth.safe.safe import SafeV141
13
14
  from web3 import Account, Web3
14
15
  from web3._utils.http import construct_user_agent
16
+ from web3.middleware import ExtraDataToPOAMiddleware
15
17
 
16
- from prediction_market_agent_tooling.chains import ETHEREUM_ID, GNOSIS_CHAIN_ID
18
+ from prediction_market_agent_tooling.chains import (
19
+ ETHEREUM_ID,
20
+ GNOSIS_CHAIN_ID,
21
+ POLYGON_CHAIN_ID,
22
+ )
17
23
  from prediction_market_agent_tooling.deploy.gcp.utils import gcp_get_secret_value
18
24
  from prediction_market_agent_tooling.gtypes import (
19
25
  ChainID,
@@ -293,6 +299,8 @@ class RPCConfig(BaseSettings):
293
299
  GNOSIS_RPC_URL: URI = Field(default=URI("https://rpc.gnosis.gateway.fm"))
294
300
  GNOSIS_RPC_BEARER: SecretStr | None = None
295
301
  CHAIN_ID: ChainID = Field(default=GNOSIS_CHAIN_ID)
302
+ POLYGON_RPC_URL: URI = Field(default=URI("https://polygon-rpc.com"))
303
+ POLYGON_RPC_BEARER: SecretStr | None = None
296
304
 
297
305
  @property
298
306
  def ethereum_rpc_url(self) -> URI:
@@ -306,26 +314,40 @@ class RPCConfig(BaseSettings):
306
314
  self.GNOSIS_RPC_URL, "GNOSIS_RPC_URL missing in the environment."
307
315
  )
308
316
 
317
+ @property
318
+ def polygon_rpc_url(self) -> URI:
319
+ return check_not_none(
320
+ self.POLYGON_RPC_URL, "POLYGON_RPC_URL missing in the environment."
321
+ )
322
+
309
323
  @property
310
324
  def chain_id(self) -> ChainID:
311
325
  return check_not_none(self.CHAIN_ID, "CHAIN_ID missing in the environment.")
312
326
 
327
+ @property
328
+ def gnosis_chain_id(self) -> ChainID:
329
+ return GNOSIS_CHAIN_ID
330
+
313
331
  def chain_id_to_rpc_url(self, chain_id: ChainID) -> URI:
314
332
  return {
315
333
  ETHEREUM_ID: self.ethereum_rpc_url,
316
334
  GNOSIS_CHAIN_ID: self.gnosis_rpc_url,
335
+ POLYGON_CHAIN_ID: self.polygon_rpc_url,
317
336
  }[chain_id]
318
337
 
319
338
  def chain_id_to_rpc_bearer(self, chain_id: ChainID) -> SecretStr | None:
320
339
  return {
321
340
  ETHEREUM_ID: self.ETHEREUM_RPC_BEARER,
322
341
  GNOSIS_CHAIN_ID: self.GNOSIS_RPC_BEARER,
342
+ POLYGON_CHAIN_ID: self.POLYGON_RPC_BEARER,
323
343
  }[chain_id]
324
344
 
325
345
  def get_web3(self) -> Web3:
326
346
  headers = {
327
347
  "Content-Type": "application/json",
328
- "User-Agent": construct_user_agent(str(type(self))),
348
+ "User-Agent": construct_user_agent(
349
+ str(type(self)), self.__class__.__name__
350
+ ),
329
351
  }
330
352
  if bearer := self.chain_id_to_rpc_bearer(self.chain_id):
331
353
  headers["Authorization"] = f"Bearer {bearer.get_secret_value()}"
@@ -339,6 +361,19 @@ class RPCConfig(BaseSettings):
339
361
  )
340
362
  )
341
363
 
364
+ @cachetools.cached(
365
+ cachetools.TTLCache(maxsize=100, ttl=5 * 60),
366
+ key=lambda self: f"{self.model_dump_json()}",
367
+ )
368
+ def get_polygon_web3(self) -> Web3:
369
+ web3 = self.get_web3()
370
+ if self.chain_id != POLYGON_CHAIN_ID:
371
+ raise ValueError(f"Chain ID {self.chain_id} is not Polygon Mainnet")
372
+
373
+ # We need to inject middleware into the Polygon web3 instance (https://web3py.readthedocs.io/en/stable/middleware.html#proof-of-authority)
374
+ web3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
375
+ return web3
376
+
342
377
 
343
378
  class CloudCredentials(BaseSettings):
344
379
  model_config = SettingsConfigDict(
@@ -25,7 +25,6 @@ from prediction_market_agent_tooling.markets.agent_market import (
25
25
  ConditionalFilterType,
26
26
  FilterBy,
27
27
  ProcessedMarket,
28
- ProcessedTradedMarket,
29
28
  QuestionType,
30
29
  SortBy,
31
30
  )
@@ -38,9 +37,6 @@ from prediction_market_agent_tooling.markets.data_models import (
38
37
  Trade,
39
38
  )
40
39
  from prediction_market_agent_tooling.markets.markets import MarketType
41
- from prediction_market_agent_tooling.markets.omen.omen import (
42
- send_keeping_token_to_eoa_xdai,
43
- )
44
40
  from prediction_market_agent_tooling.tools.custom_exceptions import (
45
41
  CantPayForGasError,
46
42
  OutOfFundsError,
@@ -414,15 +410,13 @@ class DeployablePredictionAgent(DeployableAgent):
414
410
  """
415
411
  Executed before processing of each market.
416
412
  """
417
- api_keys = APIKeys()
418
413
 
419
414
  if market_type.is_blockchain_market:
420
- # Exchange wxdai back to xdai if the balance is getting low, so we can keep paying for fees.
415
+ # Ensure we have enough native token balance for transaction fees
421
416
  if self.min_balance_to_keep_in_native_currency is not None:
422
- send_keeping_token_to_eoa_xdai(
423
- api_keys,
417
+ market.ensure_min_native_balance(
424
418
  min_required_balance=self.min_balance_to_keep_in_native_currency,
425
- multiplier=3,
419
+ multiplier=3.0,
426
420
  )
427
421
 
428
422
  def build_answer(
@@ -515,7 +509,7 @@ class DeployablePredictionAgent(DeployableAgent):
515
509
  self.verify_answer_outcomes(market=market, answer=answer)
516
510
 
517
511
  processed_market = (
518
- ProcessedMarket(answer=answer) if answer is not None else None
512
+ ProcessedMarket(answer=answer, trades=[]) if answer is not None else None
519
513
  )
520
514
 
521
515
  self.update_langfuse_trace_by_processed_market(market_type, processed_market)
@@ -742,7 +736,7 @@ class DeployableTraderAgent(DeployablePredictionAgent):
742
736
  market_type: MarketType,
743
737
  market: AgentMarket,
744
738
  verify_market: bool = True,
745
- ) -> ProcessedTradedMarket | None:
739
+ ) -> ProcessedMarket | None:
746
740
  processed_market = super().process_market(market_type, market, verify_market)
747
741
  if processed_market is None:
748
742
  return None
@@ -762,7 +756,7 @@ class DeployableTraderAgent(DeployablePredictionAgent):
762
756
  )
763
757
  placed_trades = self.execute_trades(market, trades)
764
758
 
765
- traded_market = ProcessedTradedMarket(
759
+ traded_market = ProcessedMarket(
766
760
  answer=processed_market.answer, trades=placed_trades
767
761
  )
768
762
  logger.info(f"Traded market {market.question=} from {market.url=}.")
@@ -780,10 +774,10 @@ class DeployableTraderAgent(DeployablePredictionAgent):
780
774
  market,
781
775
  processed_market,
782
776
  )
783
- if isinstance(processed_market, ProcessedTradedMarket):
784
- if self.store_trades:
785
- market.store_trades(processed_market, api_keys, self.agent_name)
786
- else:
787
- logger.info(
788
- f"Trades {processed_market.trades} not stored because {self.store_trades=}."
789
- )
777
+
778
+ if self.store_trades and processed_market is not None:
779
+ market.store_trades(processed_market, api_keys, self.agent_name)
780
+ else:
781
+ logger.info(
782
+ f"Trades {processed_market=} not stored because {self.store_trades=}."
783
+ )
@@ -460,7 +460,9 @@ class BinaryKellyBettingStrategy(BettingStrategy):
460
460
  amounts = {
461
461
  bet_outcome: BettingStrategy.cap_to_profitable_bet_amount(
462
462
  market, market.get_token_in_usd(kelly_bet_size), bet_outcome
463
- ),
463
+ )
464
+ if kelly_bet_size > 0
465
+ else USD(0),
464
466
  }
465
467
  target_position = Position(market_id=market.id, amounts_current=amounts)
466
468
  trades = self._build_rebalance_trades_from_positions(
@@ -515,9 +517,15 @@ class BinaryKellyBettingStrategy(BettingStrategy):
515
517
  )
516
518
  return kelly_bet_size
517
519
 
518
- pool_balances = [i.as_outcome_wei for i in market.outcome_token_pool.values()]
520
+ filtered_pool = {
521
+ outcome: pool_value
522
+ for outcome, pool_value in market.outcome_token_pool.items()
523
+ if INVALID_OUTCOME_LOWERCASE_IDENTIFIER not in outcome.lower()
524
+ }
525
+
526
+ pool_balances = [i.as_outcome_wei for i in filtered_pool.values()]
519
527
  # stay float for compatibility with `minimize_scalar`
520
- total_pool_balance = sum([i.value for i in market.outcome_token_pool.values()])
528
+ total_pool_balance = sum([i.value for i in filtered_pool.values()])
521
529
 
522
530
  # The bounds below have been found to work heuristically.
523
531
  optimized_bet_amount = minimize_scalar(
@@ -9,7 +9,7 @@ from prediction_market_agent_tooling.deploy.betting_strategy import (
9
9
  from prediction_market_agent_tooling.gtypes import USD, Probability
10
10
  from prediction_market_agent_tooling.markets.agent_market import (
11
11
  AgentMarket,
12
- ProcessedTradedMarket,
12
+ ProcessedMarket,
13
13
  )
14
14
  from prediction_market_agent_tooling.markets.omen.data_models import (
15
15
  OMEN_FALSE_OUTCOME,
@@ -64,7 +64,7 @@ class JobAgentMarket(AgentMarket, ABC):
64
64
  @abstractmethod
65
65
  def submit_job_result(
66
66
  self, agent_name: str, max_bond: USD, result: str
67
- ) -> ProcessedTradedMarket:
67
+ ) -> ProcessedMarket:
68
68
  """Submit the completed result for this job."""
69
69
 
70
70
  def to_simple_job(self, max_bond: USD) -> SimpleJob:
@@ -7,7 +7,7 @@ from prediction_market_agent_tooling.deploy.betting_strategy import (
7
7
  )
8
8
  from prediction_market_agent_tooling.gtypes import USD
9
9
  from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
10
- from prediction_market_agent_tooling.markets.agent_market import ProcessedTradedMarket
10
+ from prediction_market_agent_tooling.markets.agent_market import ProcessedMarket
11
11
  from prediction_market_agent_tooling.markets.data_models import PlacedTrade, Trade
12
12
  from prediction_market_agent_tooling.markets.omen.omen import (
13
13
  OmenAgentMarket,
@@ -72,7 +72,7 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
72
72
 
73
73
  def submit_job_result(
74
74
  self, agent_name: str, max_bond: USD, result: str
75
- ) -> ProcessedTradedMarket:
75
+ ) -> ProcessedMarket:
76
76
  if not APIKeys().enable_ipfs_upload:
77
77
  raise RuntimeError(
78
78
  f"ENABLE_IPFS_UPLOAD must be set to True to upload job results."
@@ -81,7 +81,7 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
81
81
  trade = self.get_job_trade(max_bond, result)
82
82
  buy_id = self.buy_tokens(outcome=trade.outcome, amount=trade.amount)
83
83
 
84
- processed_traded_market = ProcessedTradedMarket(
84
+ processed_traded_market = ProcessedMarket(
85
85
  answer=self.get_job_answer(result),
86
86
  trades=[PlacedTrade.from_trade(trade, id=buy_id)],
87
87
  )
@@ -23,6 +23,7 @@ from prediction_market_agent_tooling.gtypes import (
23
23
  OutcomeWei,
24
24
  Probability,
25
25
  Wei,
26
+ xDai,
26
27
  )
27
28
  from prediction_market_agent_tooling.markets.data_models import (
28
29
  USD,
@@ -44,9 +45,6 @@ from prediction_market_agent_tooling.tools.utils import (
44
45
 
45
46
  class ProcessedMarket(BaseModel):
46
47
  answer: CategoricalProbabilisticAnswer
47
-
48
-
49
- class ProcessedTradedMarket(ProcessedMarket):
50
48
  trades: list[PlacedTrade]
51
49
 
52
50
 
@@ -450,7 +448,7 @@ class AgentMarket(BaseModel):
450
448
 
451
449
  def store_trades(
452
450
  self,
453
- traded_market: ProcessedTradedMarket | None,
451
+ traded_market: ProcessedMarket | None,
454
452
  keys: APIKeys,
455
453
  agent_name: str,
456
454
  web3: Web3 | None = None,
@@ -509,11 +507,20 @@ class AgentMarket(BaseModel):
509
507
  )
510
508
 
511
509
  def get_outcome_index(self, outcome: OutcomeStr) -> int:
512
- outcomes_lowercase = [o.lower() for o in self.outcomes]
510
+ """Get the index of the given outcome in the market's outcomes."""
513
511
  try:
514
- return outcomes_lowercase.index(outcome.lower())
515
- except ValueError:
516
- raise ValueError(f"Outcome `{outcome}` not found in `{self.outcomes}`.")
512
+ return [o.lower() for o in self.outcomes].index(outcome.lower())
513
+ except ValueError as e:
514
+ raise ValueError(
515
+ f"Outcome '{outcome}' not found in market outcomes: {self.outcomes}"
516
+ ) from e
517
+
518
+ def ensure_min_native_balance(
519
+ self,
520
+ min_required_balance: xDai,
521
+ multiplier: float = 3.0,
522
+ ) -> None:
523
+ raise NotImplementedError("Subclass must implement this method")
517
524
 
518
525
  def get_token_balance(self, user_id: str, outcome: OutcomeStr) -> OutcomeToken:
519
526
  raise NotImplementedError("Subclasses must implement this method")
@@ -562,7 +569,7 @@ class AgentMarket(BaseModel):
562
569
 
563
570
  @staticmethod
564
571
  def get_user_id(api_keys: APIKeys) -> str:
565
- raise NotImplementedError("Subclasses must implement this method")
572
+ return api_keys.bet_from_address
566
573
 
567
574
  def get_most_recent_trade_datetime(self, user_id: str) -> DatetimeUTC | None:
568
575
  raise NotImplementedError("Subclasses must implement this method")
@@ -11,7 +11,7 @@ from prediction_market_agent_tooling.gtypes import (
11
11
  OutcomeStr,
12
12
  )
13
13
  from prediction_market_agent_tooling.loggers import logger
14
- from prediction_market_agent_tooling.markets.agent_market import ProcessedTradedMarket
14
+ from prediction_market_agent_tooling.markets.agent_market import ProcessedMarket
15
15
  from prediction_market_agent_tooling.markets.omen.data_models import (
16
16
  ContractPrediction,
17
17
  IPFSAgentResult,
@@ -28,7 +28,7 @@ def store_trades(
28
28
  contract: _AgentResultMappingContract,
29
29
  market_id: ChecksumAddress,
30
30
  outcomes: Sequence[OutcomeStr],
31
- traded_market: ProcessedTradedMarket | None,
31
+ traded_market: ProcessedMarket | None,
32
32
  keys: APIKeys,
33
33
  agent_name: str,
34
34
  web3: Web3 | None = None,
@@ -83,5 +83,5 @@ def store_trades(
83
83
  web3=web3,
84
84
  )
85
85
  logger.info(
86
- f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].hex()}."
86
+ f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].to_0x_hex()}."
87
87
  )
@@ -28,6 +28,7 @@ from prediction_market_agent_tooling.markets.omen.omen_constants import (
28
28
  OMEN_TRUE_OUTCOME,
29
29
  )
30
30
  from prediction_market_agent_tooling.tools.contract import (
31
+ ConditionPreparationEvent,
31
32
  ContractERC20OnGnosisChain,
32
33
  init_collateral_token_contract,
33
34
  to_gnosis_chain_contract,
@@ -47,7 +48,7 @@ OMEN_BINARY_MARKET_OUTCOMES: t.Sequence[OutcomeStr] = [
47
48
  ]
48
49
  INVALID_ANSWER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
49
50
  INVALID_ANSWER_HEX_BYTES = HexBytes(INVALID_ANSWER)
50
- INVALID_ANSWER_STR = HexStr(INVALID_ANSWER_HEX_BYTES.hex())
51
+ INVALID_ANSWER_STR = HexStr(INVALID_ANSWER_HEX_BYTES.to_0x_hex())
51
52
  OMEN_BASE_URL = "https://aiomen.eth.limo"
52
53
  PRESAGIO_BASE_URL = "https://presagio.pages.dev"
53
54
  TEST_CATEGORY = "test" # This category is hidden on Presagio for testing purposes.
@@ -594,7 +595,7 @@ class OmenBet(BaseModel):
594
595
  )
595
596
 
596
597
  return ResolvedBet(
597
- id=self.transactionHash.hex(),
598
+ id=self.transactionHash.to_0x_hex(),
598
599
  # Use the transaction hash instead of the bet id - both are valid, but we return the transaction hash from the trade functions, so be consistent here.
599
600
  amount=self.collateral_amount_token,
600
601
  outcome=self.fpmm.outcomes[self.outcomeIndex],
@@ -795,13 +796,6 @@ class OmenFixedProductMarketMakerCreationEvent(BaseModel):
795
796
  return Web3.to_checksum_address(self.collateralToken)
796
797
 
797
798
 
798
- class ConditionPreparationEvent(BaseModel):
799
- conditionId: HexBytes
800
- oracle: HexAddress
801
- questionId: HexBytes
802
- outcomeSlotCount: int
803
-
804
-
805
799
  class FPMMFundingAddedEvent(BaseModel):
806
800
  funder: HexAddress
807
801
  amountsAdded: list[Wei]
@@ -895,12 +889,3 @@ class IPFSAgentResult(BaseModel):
895
889
  model_config = ConfigDict(
896
890
  extra="forbid",
897
891
  )
898
-
899
-
900
- class PayoutRedemptionEvent(BaseModel):
901
- redeemer: HexAddress
902
- collateralToken: HexAddress
903
- parentCollectionId: HexBytes
904
- conditionId: HexBytes
905
- indexSets: list[int]
906
- payout: Wei
@@ -6,6 +6,7 @@ import tenacity
6
6
  from pydantic import BaseModel
7
7
  from tqdm import tqdm
8
8
  from web3 import Web3
9
+ from web3.constants import HASH_ZERO
9
10
 
10
11
  from prediction_market_agent_tooling.config import APIKeys
11
12
  from prediction_market_agent_tooling.gtypes import (
@@ -28,7 +29,6 @@ from prediction_market_agent_tooling.markets.agent_market import (
28
29
  FilterBy,
29
30
  MarketFees,
30
31
  ProcessedMarket,
31
- ProcessedTradedMarket,
32
32
  QuestionType,
33
33
  SortBy,
34
34
  )
@@ -43,7 +43,6 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
43
43
  OMEN_TRUE_OUTCOME,
44
44
  PRESAGIO_BASE_URL,
45
45
  Condition,
46
- ConditionPreparationEvent,
47
46
  CreatedMarket,
48
47
  OmenBet,
49
48
  OmenMarket,
@@ -60,13 +59,13 @@ from prediction_market_agent_tooling.markets.omen.omen_contracts import (
60
59
  OmenOracleContract,
61
60
  OmenRealitioContract,
62
61
  WrappedxDaiContract,
63
- build_parent_collection_id,
64
62
  )
65
63
  from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
66
64
  OmenSubgraphHandler,
67
65
  )
68
66
  from prediction_market_agent_tooling.tools.balances import get_balances
69
67
  from prediction_market_agent_tooling.tools.contract import (
68
+ ConditionPreparationEvent,
70
69
  init_collateral_token_contract,
71
70
  to_gnosis_chain_contract,
72
71
  )
@@ -411,6 +410,26 @@ class OmenAgentMarket(AgentMarket):
411
410
  def redeem_winnings(api_keys: APIKeys) -> None:
412
411
  redeem_from_all_user_positions(api_keys)
413
412
 
413
+ def ensure_min_native_balance(
414
+ self,
415
+ min_required_balance: xDai,
416
+ multiplier: float = 3.0,
417
+ ) -> None:
418
+ """
419
+ Ensure the EOA has at least the minimum required native token balance.
420
+ If not, transfer wrapped native tokens from the betting address and unwrap them.
421
+
422
+ Args:
423
+ min_required_balance: Minimum required native token balance to maintain
424
+ multiplier: Multiplier to apply to the required balance to keep as a buffer
425
+ """
426
+
427
+ send_keeping_token_to_eoa_xdai(
428
+ api_keys=APIKeys(),
429
+ min_required_balance=min_required_balance,
430
+ multiplier=multiplier,
431
+ )
432
+
414
433
  @staticmethod
415
434
  def get_trade_balance(api_keys: APIKeys, web3: Web3 | None = None) -> USD:
416
435
  native_usd = get_xdai_in_usd(
@@ -438,7 +457,7 @@ class OmenAgentMarket(AgentMarket):
438
457
 
439
458
  def store_trades(
440
459
  self,
441
- traded_market: ProcessedTradedMarket | None,
460
+ traded_market: ProcessedMarket | None,
442
461
  keys: APIKeys,
443
462
  agent_name: str,
444
463
  web3: Web3 | None = None,
@@ -653,10 +672,6 @@ class OmenAgentMarket(AgentMarket):
653
672
  def get_user_balance(user_id: str) -> float:
654
673
  return float(get_balances(Web3.to_checksum_address(user_id)).total)
655
674
 
656
- @staticmethod
657
- def get_user_id(api_keys: APIKeys) -> str:
658
- return api_keys.bet_from_address
659
-
660
675
  def get_most_recent_trade_datetime(self, user_id: str) -> DatetimeUTC | None:
661
676
  sgh = OmenSubgraphHandler()
662
677
  trades = sgh.get_trades(
@@ -734,7 +749,7 @@ def omen_buy_outcome_tx(
734
749
  web3=web3,
735
750
  )
736
751
 
737
- return tx_receipt["transactionHash"].hex()
752
+ return tx_receipt["transactionHash"].to_0x_hex()
738
753
 
739
754
 
740
755
  def binary_omen_buy_outcome_tx(
@@ -828,7 +843,7 @@ def omen_sell_outcome_tx(
828
843
  web3=web3,
829
844
  )
830
845
 
831
- return tx_receipt["transactionHash"].hex()
846
+ return tx_receipt["transactionHash"].to_0x_hex()
832
847
 
833
848
 
834
849
  def binary_omen_sell_outcome_tx(
@@ -1076,7 +1091,7 @@ def get_conditional_tokens_balance_for_market(
1076
1091
  """
1077
1092
  balance_per_index_set: dict[int, OutcomeWei] = {}
1078
1093
  conditional_token_contract = OmenConditionalTokenContract()
1079
- parent_collection_id = build_parent_collection_id()
1094
+ parent_collection_id = HASH_ZERO
1080
1095
 
1081
1096
  for index_set in market.condition.index_sets:
1082
1097
  collection_id = conditional_token_contract.getCollectionId(