prediction-market-agent-tooling 0.65.5__py3-none-any.whl → 0.69.17.dev1149__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 (88) hide show
  1. prediction_market_agent_tooling/abis/agentresultmapping.abi.json +192 -0
  2. prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
  3. prediction_market_agent_tooling/abis/processor.abi.json +16 -0
  4. prediction_market_agent_tooling/abis/swapr_quoter.abi.json +221 -0
  5. prediction_market_agent_tooling/abis/swapr_router.abi.json +634 -0
  6. prediction_market_agent_tooling/benchmark/benchmark.py +1 -1
  7. prediction_market_agent_tooling/benchmark/utils.py +13 -0
  8. prediction_market_agent_tooling/chains.py +1 -0
  9. prediction_market_agent_tooling/config.py +61 -2
  10. prediction_market_agent_tooling/data_download/langfuse_data_downloader.py +405 -0
  11. prediction_market_agent_tooling/deploy/agent.py +199 -67
  12. prediction_market_agent_tooling/deploy/agent_example.py +1 -1
  13. prediction_market_agent_tooling/deploy/betting_strategy.py +412 -68
  14. prediction_market_agent_tooling/deploy/constants.py +6 -0
  15. prediction_market_agent_tooling/gtypes.py +11 -1
  16. prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
  17. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +19 -20
  18. prediction_market_agent_tooling/loggers.py +9 -1
  19. prediction_market_agent_tooling/logprobs_parser.py +2 -1
  20. prediction_market_agent_tooling/markets/agent_market.py +106 -18
  21. prediction_market_agent_tooling/markets/blockchain_utils.py +37 -19
  22. prediction_market_agent_tooling/markets/data_models.py +120 -7
  23. prediction_market_agent_tooling/markets/manifold/data_models.py +5 -3
  24. prediction_market_agent_tooling/markets/manifold/manifold.py +21 -2
  25. prediction_market_agent_tooling/markets/manifold/utils.py +8 -2
  26. prediction_market_agent_tooling/markets/market_type.py +74 -0
  27. prediction_market_agent_tooling/markets/markets.py +7 -99
  28. prediction_market_agent_tooling/markets/metaculus/data_models.py +3 -3
  29. prediction_market_agent_tooling/markets/metaculus/metaculus.py +5 -8
  30. prediction_market_agent_tooling/markets/omen/cow_contracts.py +5 -1
  31. prediction_market_agent_tooling/markets/omen/data_models.py +63 -32
  32. prediction_market_agent_tooling/markets/omen/omen.py +112 -23
  33. prediction_market_agent_tooling/markets/omen/omen_constants.py +8 -0
  34. prediction_market_agent_tooling/markets/omen/omen_contracts.py +18 -203
  35. prediction_market_agent_tooling/markets/omen/omen_resolving.py +33 -13
  36. prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +23 -18
  37. prediction_market_agent_tooling/markets/polymarket/api.py +123 -100
  38. prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
  39. prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
  40. prediction_market_agent_tooling/markets/polymarket/data_models.py +95 -19
  41. prediction_market_agent_tooling/markets/polymarket/polymarket.py +373 -29
  42. prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
  43. prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +91 -0
  44. prediction_market_agent_tooling/markets/polymarket/utils.py +1 -22
  45. prediction_market_agent_tooling/markets/seer/data_models.py +111 -17
  46. prediction_market_agent_tooling/markets/seer/exceptions.py +2 -0
  47. prediction_market_agent_tooling/markets/seer/price_manager.py +165 -50
  48. prediction_market_agent_tooling/markets/seer/seer.py +393 -106
  49. prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
  50. prediction_market_agent_tooling/markets/seer/seer_contracts.py +115 -5
  51. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +297 -66
  52. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +43 -8
  53. prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +80 -0
  54. prediction_market_agent_tooling/tools/_generic_value.py +8 -2
  55. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +271 -8
  56. prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -1
  57. prediction_market_agent_tooling/tools/caches/db_cache.py +219 -117
  58. prediction_market_agent_tooling/tools/caches/serializers.py +11 -2
  59. prediction_market_agent_tooling/tools/contract.py +480 -38
  60. prediction_market_agent_tooling/tools/contract_utils.py +61 -0
  61. prediction_market_agent_tooling/tools/cow/cow_order.py +218 -45
  62. prediction_market_agent_tooling/tools/cow/models.py +122 -0
  63. prediction_market_agent_tooling/tools/cow/semaphore.py +104 -0
  64. prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
  65. prediction_market_agent_tooling/tools/db/db_manager.py +59 -0
  66. prediction_market_agent_tooling/tools/hexbytes_custom.py +4 -1
  67. prediction_market_agent_tooling/tools/httpx_cached_client.py +15 -6
  68. prediction_market_agent_tooling/tools/langfuse_client_utils.py +21 -8
  69. prediction_market_agent_tooling/tools/openai_utils.py +31 -0
  70. prediction_market_agent_tooling/tools/perplexity/perplexity_client.py +86 -0
  71. prediction_market_agent_tooling/tools/perplexity/perplexity_models.py +26 -0
  72. prediction_market_agent_tooling/tools/perplexity/perplexity_search.py +73 -0
  73. prediction_market_agent_tooling/tools/rephrase.py +71 -0
  74. prediction_market_agent_tooling/tools/singleton.py +11 -6
  75. prediction_market_agent_tooling/tools/streamlit_utils.py +188 -0
  76. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +64 -0
  77. prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
  78. prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
  79. prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
  80. prediction_market_agent_tooling/tools/utils.py +61 -3
  81. prediction_market_agent_tooling/tools/web3_utils.py +63 -9
  82. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/METADATA +13 -9
  83. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/RECORD +86 -64
  84. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/WHEEL +1 -1
  85. prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +0 -171
  86. prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -420
  87. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/entry_points.txt +0 -0
  88. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info/licenses}/LICENSE +0 -0
@@ -1,4 +1,5 @@
1
1
  import typing as t
2
+ from datetime import timedelta
2
3
 
3
4
  from pydantic import BaseModel, ConfigDict, Field, computed_field, model_validator
4
5
  from web3 import Web3
@@ -23,7 +24,12 @@ from prediction_market_agent_tooling.markets.data_models import (
23
24
  Resolution,
24
25
  ResolvedBet,
25
26
  )
27
+ from prediction_market_agent_tooling.markets.omen.omen_constants import (
28
+ OMEN_FALSE_OUTCOME,
29
+ OMEN_TRUE_OUTCOME,
30
+ )
26
31
  from prediction_market_agent_tooling.tools.contract import (
32
+ ConditionPreparationEvent,
27
33
  ContractERC20OnGnosisChain,
28
34
  init_collateral_token_contract,
29
35
  to_gnosis_chain_contract,
@@ -37,15 +43,13 @@ from prediction_market_agent_tooling.tools.utils import (
37
43
  utcnow,
38
44
  )
39
45
 
40
- OMEN_TRUE_OUTCOME = OutcomeStr("Yes")
41
- OMEN_FALSE_OUTCOME = OutcomeStr("No")
42
46
  OMEN_BINARY_MARKET_OUTCOMES: t.Sequence[OutcomeStr] = [
43
47
  OMEN_TRUE_OUTCOME,
44
48
  OMEN_FALSE_OUTCOME,
45
49
  ]
46
50
  INVALID_ANSWER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
47
51
  INVALID_ANSWER_HEX_BYTES = HexBytes(INVALID_ANSWER)
48
- INVALID_ANSWER_STR = HexStr(INVALID_ANSWER_HEX_BYTES.hex())
52
+ INVALID_ANSWER_STR = HexStr(INVALID_ANSWER_HEX_BYTES.to_0x_hex())
49
53
  OMEN_BASE_URL = "https://aiomen.eth.limo"
50
54
  PRESAGIO_BASE_URL = "https://presagio.pages.dev"
51
55
  TEST_CATEGORY = "test" # This category is hidden on Presagio for testing purposes.
@@ -83,6 +87,7 @@ class Question(BaseModel):
83
87
  templateId: int
84
88
  outcomes: t.Sequence[OutcomeStr]
85
89
  isPendingArbitration: bool
90
+ timeout: int # Finalization time in seconds
86
91
  openingTimestamp: int
87
92
  answerFinalizedTimestamp: t.Optional[DatetimeUTC] = None
88
93
  currentAnswer: t.Optional[str] = None
@@ -96,6 +101,10 @@ class Question(BaseModel):
96
101
  # Based on https://github.com/protofire/omen-exchange/blob/2cfdf6bfe37afa8b169731d51fea69d42321d66c/app/src/hooks/graph/useGraphMarketMakerData.tsx#L217.
97
102
  return self.data
98
103
 
104
+ @property
105
+ def timeout_timedelta(self) -> timedelta:
106
+ return timedelta(seconds=self.timeout)
107
+
99
108
  @property
100
109
  def n_outcomes(self) -> int:
101
110
  return len(self.outcomes)
@@ -253,6 +262,10 @@ class OmenMarket(BaseModel):
253
262
 
254
263
  return self
255
264
 
265
+ @property
266
+ def creator_checksum(self) -> ChecksumAddress:
267
+ return Web3.to_checksum_address(self.creator)
268
+
256
269
  @property
257
270
  def openingTimestamp(self) -> int:
258
271
  # This field is also available on this model itself, but for some reason it's typed to be optional,
@@ -468,6 +481,7 @@ class OmenMarket(BaseModel):
468
481
  outcomes=model.question_event.parsed_question.outcomes,
469
482
  isPendingArbitration=False, # Can not be, it's a fresh market.
470
483
  openingTimestamp=model.question_event.opening_ts,
484
+ timeout=model.question_event.timeout,
471
485
  answerFinalizedTimestamp=None, # It's a new one, can not be.
472
486
  currentAnswer=None, # It's a new one, no answer yet.
473
487
  ),
@@ -592,14 +606,19 @@ class OmenBet(BaseModel):
592
606
  )
593
607
 
594
608
  return ResolvedBet(
595
- id=self.transactionHash.hex(),
609
+ id=self.transactionHash.to_0x_hex(),
596
610
  # 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.
597
611
  amount=self.collateral_amount_token,
598
612
  outcome=self.fpmm.outcomes[self.outcomeIndex],
599
613
  created_time=self.creation_datetime,
600
614
  market_question=self.title,
601
615
  market_id=self.fpmm.id,
602
- market_outcome=self.fpmm.outcomes[self.outcomeIndex],
616
+ market_outcome=self.fpmm.outcomes[
617
+ check_not_none(
618
+ self.fpmm.answer_index,
619
+ "Should not be None if `is_resolved_with_valid_answer`.",
620
+ )
621
+ ],
603
622
  resolved_time=check_not_none(self.fpmm.finalized_datetime),
604
623
  profit=self.get_profit(),
605
624
  )
@@ -788,13 +807,6 @@ class OmenFixedProductMarketMakerCreationEvent(BaseModel):
788
807
  return Web3.to_checksum_address(self.collateralToken)
789
808
 
790
809
 
791
- class ConditionPreparationEvent(BaseModel):
792
- conditionId: HexBytes
793
- oracle: HexAddress
794
- questionId: HexBytes
795
- outcomeSlotCount: int
796
-
797
-
798
810
  class FPMMFundingAddedEvent(BaseModel):
799
811
  funder: HexAddress
800
812
  amountsAdded: list[Wei]
@@ -826,18 +838,44 @@ class CreatedMarket(BaseModel):
826
838
 
827
839
  class ContractPrediction(BaseModel):
828
840
  model_config = ConfigDict(populate_by_name=True)
841
+
842
+ market: str | None = Field(
843
+ None,
844
+ alias="marketAddress",
845
+ description="Market's address. Will be None on older records.",
846
+ )
829
847
  publisher: str = Field(..., alias="publisherAddress")
830
848
  ipfs_hash: HexBytes = Field(..., alias="ipfsHash")
831
849
  tx_hashes: list[HexBytes] = Field(..., alias="txHashes")
832
- estimated_probability_bps: int = Field(..., alias="estimatedProbabilityBps")
850
+ outcomes: list[OutcomeStr] = Field(...)
851
+ estimated_probabilities_bps: list[int] = Field(
852
+ ..., alias="estimatedProbabilitiesBps"
853
+ )
833
854
 
834
- @property
835
- def estimated_probability(self) -> Probability:
836
- return Probability(self.estimated_probability_bps / BPS_CONSTANT)
855
+ @model_validator(mode="before")
856
+ @classmethod
857
+ def handle_legacy_estimated_probability_bps(
858
+ cls, values: dict[str, t.Any]
859
+ ) -> dict[str, t.Any]:
860
+ # If 'estimatedProbabilityBps' is present and 'outcomes'/'estimatedProbabilitiesBps' are not,
861
+ # convert to the new format using "Yes" and "No" outcomes.
862
+ # This allows for backward compatibility with old contract events.
863
+ if (
864
+ "estimatedProbabilityBps" in values
865
+ and "outcomes" not in values
866
+ and "estimatedProbabilitiesBps" not in values
867
+ ):
868
+ prob_bps = values["estimatedProbabilityBps"]
869
+ values["outcomes"] = [
870
+ OMEN_TRUE_OUTCOME,
871
+ OMEN_FALSE_OUTCOME,
872
+ ]
873
+ values["estimatedProbabilitiesBps"] = [prob_bps, BPS_CONSTANT - prob_bps]
874
+ return values
837
875
 
838
- @property
839
- def boolean_outcome(self) -> bool:
840
- return self.estimated_probability > 0.5
876
+ def estimated_probability_of_outcome(self, outcome: OutcomeStr) -> Probability:
877
+ index = self.outcomes.index(outcome)
878
+ return Probability(self.estimated_probabilities_bps[index] / BPS_CONSTANT)
841
879
 
842
880
  @computed_field # type: ignore[prop-decorator] # Mypy issue: https://github.com/python/mypy/issues/14461
843
881
  @property
@@ -847,10 +885,12 @@ class ContractPrediction(BaseModel):
847
885
  @staticmethod
848
886
  def from_tuple(values: tuple[t.Any, ...]) -> "ContractPrediction":
849
887
  return ContractPrediction(
850
- publisher=values[0],
851
- ipfs_hash=values[1],
852
- tx_hashes=values[2],
853
- estimated_probability_bps=values[3],
888
+ market=values[0],
889
+ publisher=values[1],
890
+ ipfs_hash=values[2],
891
+ tx_hashes=values[3],
892
+ outcomes=values[4],
893
+ estimated_probabilities_bps=values[5],
854
894
  )
855
895
 
856
896
 
@@ -860,12 +900,3 @@ class IPFSAgentResult(BaseModel):
860
900
  model_config = ConfigDict(
861
901
  extra="forbid",
862
902
  )
863
-
864
-
865
- class PayoutRedemptionEvent(BaseModel):
866
- redeemer: HexAddress
867
- collateralToken: HexAddress
868
- parentCollectionId: HexBytes
869
- conditionId: HexBytes
870
- indexSets: list[int]
871
- payout: Wei
@@ -3,8 +3,10 @@ from collections import defaultdict
3
3
  from datetime import timedelta
4
4
 
5
5
  import tenacity
6
+ from pydantic import BaseModel
6
7
  from tqdm import tqdm
7
8
  from web3 import Web3
9
+ from web3.constants import HASH_ZERO
8
10
 
9
11
  from prediction_market_agent_tooling.config import APIKeys
10
12
  from prediction_market_agent_tooling.gtypes import (
@@ -23,10 +25,11 @@ from prediction_market_agent_tooling.gtypes import (
23
25
  from prediction_market_agent_tooling.loggers import logger
24
26
  from prediction_market_agent_tooling.markets.agent_market import (
25
27
  AgentMarket,
28
+ ConditionalFilterType,
26
29
  FilterBy,
27
30
  MarketFees,
28
31
  ProcessedMarket,
29
- ProcessedTradedMarket,
32
+ QuestionType,
30
33
  SortBy,
31
34
  )
32
35
  from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
@@ -40,7 +43,6 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
40
43
  OMEN_TRUE_OUTCOME,
41
44
  PRESAGIO_BASE_URL,
42
45
  Condition,
43
- ConditionPreparationEvent,
44
46
  CreatedMarket,
45
47
  OmenBet,
46
48
  OmenMarket,
@@ -50,19 +52,20 @@ from prediction_market_agent_tooling.markets.omen.omen_contracts import (
50
52
  OMEN_DEFAULT_MARKET_FEE_PERC,
51
53
  REALITY_DEFAULT_FINALIZATION_TIMEOUT,
52
54
  Arbitrator,
55
+ OmenAgentResultMappingContract,
53
56
  OmenConditionalTokenContract,
54
57
  OmenFixedProductMarketMakerContract,
55
58
  OmenFixedProductMarketMakerFactoryContract,
56
59
  OmenOracleContract,
57
60
  OmenRealitioContract,
58
61
  WrappedxDaiContract,
59
- build_parent_collection_id,
60
62
  )
61
63
  from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
62
64
  OmenSubgraphHandler,
63
65
  )
64
66
  from prediction_market_agent_tooling.tools.balances import get_balances
65
67
  from prediction_market_agent_tooling.tools.contract import (
68
+ ConditionPreparationEvent,
66
69
  init_collateral_token_contract,
67
70
  to_gnosis_chain_contract,
68
71
  )
@@ -84,10 +87,10 @@ from prediction_market_agent_tooling.tools.utils import (
84
87
  DatetimeUTC,
85
88
  calculate_sell_amount_in_collateral,
86
89
  check_not_none,
90
+ utcnow,
87
91
  )
88
92
  from prediction_market_agent_tooling.tools.web3_utils import get_receipt_block_timestamp
89
93
 
90
- OMEN_DEFAULT_REALITIO_BOND_VALUE = xDai(0.01)
91
94
  # Too low value would work with the Omen contract, but causes CoW orders (when buying the specific market's tokens) to fail.
92
95
  OMEN_TINY_BET_AMOUNT = USD(0.01)
93
96
 
@@ -102,6 +105,7 @@ class OmenAgentMarket(AgentMarket):
102
105
 
103
106
  collateral_token_contract_address_checksummed: ChecksumAddress
104
107
  market_maker_contract_address_checksummed: ChecksumAddress
108
+ outcome_token_pool: dict[OutcomeStr, OutcomeToken]
105
109
  condition: Condition
106
110
  finalized_time: DatetimeUTC | None
107
111
  created_time: DatetimeUTC
@@ -157,6 +161,14 @@ class OmenAgentMarket(AgentMarket):
157
161
  def get_usd_in_token(self, x: USD) -> CollateralToken:
158
162
  return get_usd_in_token(x, self.collateral_token_contract_address_checksummed)
159
163
 
164
+ def have_bet_on_market_since(self, keys: APIKeys, since: timedelta) -> bool:
165
+ start_time = utcnow() - since
166
+ prev_bets = self.get_bets_made_since(
167
+ better_address=keys.bet_from_address, start_time=start_time
168
+ )
169
+
170
+ return self.id in [b.market_id for b in prev_bets]
171
+
160
172
  def liquidate_existing_positions(
161
173
  self,
162
174
  bet_outcome: OutcomeStr,
@@ -368,8 +380,11 @@ class OmenAgentMarket(AgentMarket):
368
380
  filter_by: FilterBy = FilterBy.OPEN,
369
381
  created_after: t.Optional[DatetimeUTC] = None,
370
382
  excluded_questions: set[str] | None = None,
371
- fetch_categorical_markets: bool = False,
383
+ question_type: QuestionType = QuestionType.ALL,
384
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
372
385
  ) -> t.Sequence["OmenAgentMarket"]:
386
+ fetch_categorical_markets = question_type == QuestionType.CATEGORICAL
387
+
373
388
  return [
374
389
  OmenAgentMarket.from_data_model(m)
375
390
  for m in OmenSubgraphHandler().get_omen_markets_simple(
@@ -394,6 +409,26 @@ class OmenAgentMarket(AgentMarket):
394
409
  def redeem_winnings(api_keys: APIKeys) -> None:
395
410
  redeem_from_all_user_positions(api_keys)
396
411
 
412
+ def ensure_min_native_balance(
413
+ self,
414
+ min_required_balance: xDai,
415
+ multiplier: float = 3.0,
416
+ ) -> None:
417
+ """
418
+ Ensure the EOA has at least the minimum required native token balance.
419
+ If not, transfer wrapped native tokens from the betting address and unwrap them.
420
+
421
+ Args:
422
+ min_required_balance: Minimum required native token balance to maintain
423
+ multiplier: Multiplier to apply to the required balance to keep as a buffer
424
+ """
425
+
426
+ send_keeping_token_to_eoa_xdai(
427
+ api_keys=APIKeys(),
428
+ min_required_balance=min_required_balance,
429
+ multiplier=multiplier,
430
+ )
431
+
397
432
  @staticmethod
398
433
  def get_trade_balance(api_keys: APIKeys, web3: Web3 | None = None) -> USD:
399
434
  native_usd = get_xdai_in_usd(
@@ -421,16 +456,19 @@ class OmenAgentMarket(AgentMarket):
421
456
 
422
457
  def store_trades(
423
458
  self,
424
- traded_market: ProcessedTradedMarket | None,
459
+ traded_market: ProcessedMarket | None,
425
460
  keys: APIKeys,
426
461
  agent_name: str,
427
462
  web3: Web3 | None = None,
428
463
  ) -> None:
429
464
  return store_trades(
430
- market_id=self.id,
465
+ contract=OmenAgentResultMappingContract(),
466
+ market_id=Web3.to_checksum_address(self.id),
467
+ outcomes=self.outcomes,
431
468
  traded_market=traded_market,
432
469
  keys=keys,
433
470
  agent_name=agent_name,
471
+ web3=web3,
434
472
  )
435
473
 
436
474
  @staticmethod
@@ -607,11 +645,10 @@ class OmenAgentMarket(AgentMarket):
607
645
  Note: this is only valid if the market instance's token pool is
608
646
  up-to-date with the smart contract.
609
647
  """
610
- outcome_token_pool = check_not_none(self.outcome_token_pool)
611
648
  amount = get_buy_outcome_token_amount(
612
649
  investment_amount=self.get_in_token(bet_amount),
613
650
  outcome_index=self.get_outcome_index(outcome),
614
- pool_balances=[outcome_token_pool[x] for x in self.outcomes],
651
+ pool_balances=[self.outcome_token_pool[x] for x in self.outcomes],
615
652
  fees=self.fees,
616
653
  )
617
654
  return amount
@@ -634,10 +671,6 @@ class OmenAgentMarket(AgentMarket):
634
671
  def get_user_balance(user_id: str) -> float:
635
672
  return float(get_balances(Web3.to_checksum_address(user_id)).total)
636
673
 
637
- @staticmethod
638
- def get_user_id(api_keys: APIKeys) -> str:
639
- return api_keys.bet_from_address
640
-
641
674
  def get_most_recent_trade_datetime(self, user_id: str) -> DatetimeUTC | None:
642
675
  sgh = OmenSubgraphHandler()
643
676
  trades = sgh.get_trades(
@@ -670,6 +703,26 @@ def omen_buy_outcome_tx(
670
703
  auto_deposit: bool,
671
704
  web3: Web3 | None = None,
672
705
  slippage: float = 0.01,
706
+ ) -> str:
707
+ return omen_buy_outcome_tx_no_retry(
708
+ api_keys=api_keys,
709
+ amount=amount,
710
+ market=market,
711
+ outcome=outcome,
712
+ auto_deposit=auto_deposit,
713
+ web3=web3,
714
+ slippage=slippage,
715
+ )
716
+
717
+
718
+ def omen_buy_outcome_tx_no_retry(
719
+ api_keys: APIKeys,
720
+ amount: USD | CollateralToken,
721
+ market: OmenAgentMarket,
722
+ outcome: OutcomeStr,
723
+ auto_deposit: bool,
724
+ web3: Web3 | None = None,
725
+ slippage: float = 0.01,
673
726
  ) -> str:
674
727
  """
675
728
  Bets the given amount for the given outcome in the given market.
@@ -715,7 +768,7 @@ def omen_buy_outcome_tx(
715
768
  web3=web3,
716
769
  )
717
770
 
718
- return tx_receipt["transactionHash"].hex()
771
+ return tx_receipt["transactionHash"].to_0x_hex()
719
772
 
720
773
 
721
774
  def binary_omen_buy_outcome_tx(
@@ -809,7 +862,7 @@ def omen_sell_outcome_tx(
809
862
  web3=web3,
810
863
  )
811
864
 
812
- return tx_receipt["transactionHash"].hex()
865
+ return tx_receipt["transactionHash"].to_0x_hex()
813
866
 
814
867
 
815
868
  def binary_omen_sell_outcome_tx(
@@ -1057,11 +1110,11 @@ def get_conditional_tokens_balance_for_market(
1057
1110
  """
1058
1111
  balance_per_index_set: dict[int, OutcomeWei] = {}
1059
1112
  conditional_token_contract = OmenConditionalTokenContract()
1060
- parent_collection_id = build_parent_collection_id()
1113
+ parent_collection_id = HASH_ZERO
1061
1114
 
1062
1115
  for index_set in market.condition.index_sets:
1063
1116
  collection_id = conditional_token_contract.getCollectionId(
1064
- parent_collection_id, market.condition.id, index_set, web3=web3
1117
+ HexBytes(parent_collection_id), market.condition.id, index_set, web3=web3
1065
1118
  )
1066
1119
  # Note that collection_id is returned as bytes, which is accepted by the contract calls downstream.
1067
1120
  position_id: int = conditional_token_contract.getPositionId(
@@ -1305,20 +1358,34 @@ def send_keeping_token_to_eoa_xdai(
1305
1358
  )
1306
1359
 
1307
1360
 
1308
- def get_buy_outcome_token_amount(
1361
+ class BuyOutcomeResult(BaseModel):
1362
+ outcome_tokens_received: OutcomeToken
1363
+ new_pool_balances: list[OutcomeToken]
1364
+
1365
+
1366
+ def calculate_buy_outcome_token(
1309
1367
  investment_amount: CollateralToken,
1310
1368
  outcome_index: int,
1311
1369
  pool_balances: list[OutcomeToken],
1312
1370
  fees: MarketFees,
1313
- ) -> OutcomeToken:
1371
+ ) -> BuyOutcomeResult:
1314
1372
  """
1315
1373
  Calculates the amount of outcome tokens received for a given investment
1374
+ and returns the new pool balances after the purchase.
1316
1375
 
1317
1376
  Taken from https://github.com/gnosis/conditional-tokens-market-makers/blob/6814c0247c745680bb13298d4f0dd7f5b574d0db/contracts/FixedProductMarketMaker.sol#L264
1318
1377
  """
1319
1378
  if outcome_index >= len(pool_balances):
1320
1379
  raise ValueError("invalid outcome index")
1321
1380
 
1381
+ new_pool_balances = pool_balances.copy()
1382
+
1383
+ if investment_amount == 0:
1384
+ return BuyOutcomeResult(
1385
+ outcome_tokens_received=OutcomeToken(0),
1386
+ new_pool_balances=new_pool_balances,
1387
+ )
1388
+
1322
1389
  investment_amount_minus_fees = fees.get_after_fees(investment_amount)
1323
1390
  investment_amount_minus_fees_as_ot = OutcomeToken(
1324
1391
  investment_amount_minus_fees.value
@@ -1330,17 +1397,39 @@ def get_buy_outcome_token_amount(
1330
1397
  # Calculate the ending balance considering all other outcomes
1331
1398
  for i, pool_balance in enumerate(pool_balances):
1332
1399
  if i != outcome_index:
1333
- denominator = pool_balance + investment_amount_minus_fees_as_ot
1400
+ new_pool_balances[i] = pool_balance + investment_amount_minus_fees_as_ot
1334
1401
  ending_outcome_balance = OutcomeToken(
1335
- (ending_outcome_balance * pool_balance / denominator)
1402
+ (ending_outcome_balance * pool_balance / new_pool_balances[i])
1336
1403
  )
1337
1404
 
1405
+ # Update the bought outcome's pool balance
1406
+ new_pool_balances[outcome_index] = ending_outcome_balance
1407
+
1338
1408
  if ending_outcome_balance <= 0:
1339
1409
  raise ValueError("must have non-zero balances")
1340
1410
 
1341
- result = (
1411
+ outcome_tokens_received = (
1342
1412
  buy_token_pool_balance
1343
1413
  + investment_amount_minus_fees_as_ot
1344
1414
  - ending_outcome_balance
1345
1415
  )
1346
- return result
1416
+
1417
+ return BuyOutcomeResult(
1418
+ outcome_tokens_received=outcome_tokens_received,
1419
+ new_pool_balances=new_pool_balances,
1420
+ )
1421
+
1422
+
1423
+ def get_buy_outcome_token_amount(
1424
+ investment_amount: CollateralToken,
1425
+ outcome_index: int,
1426
+ pool_balances: list[OutcomeToken],
1427
+ fees: MarketFees,
1428
+ ) -> OutcomeToken:
1429
+ result = calculate_buy_outcome_token(
1430
+ investment_amount=investment_amount,
1431
+ outcome_index=outcome_index,
1432
+ pool_balances=pool_balances,
1433
+ fees=fees,
1434
+ )
1435
+ return result.outcome_tokens_received
@@ -1,8 +1,16 @@
1
1
  from web3 import Web3
2
2
 
3
+ from prediction_market_agent_tooling.gtypes import OutcomeStr
4
+
5
+ OMEN_TRUE_OUTCOME = OutcomeStr("Yes")
6
+ OMEN_FALSE_OUTCOME = OutcomeStr("No")
7
+
3
8
  WRAPPED_XDAI_CONTRACT_ADDRESS = Web3.to_checksum_address(
4
9
  "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d"
5
10
  )
6
11
  SDAI_CONTRACT_ADDRESS = Web3.to_checksum_address(
7
12
  "0xaf204776c7245bF4147c2612BF6e5972Ee483701"
8
13
  )
14
+ METRI_SUPER_GROUP_CONTRACT_ADDRESS = Web3.to_checksum_address(
15
+ "0x7147A7405fCFe5CFa30c6d5363f9f357a317d082"
16
+ )