prediction-market-agent-tooling 0.68.0.dev999__py3-none-any.whl → 0.69.0__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 (42) 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 +26 -21
  4. prediction_market_agent_tooling/deploy/betting_strategy.py +133 -22
  5. prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
  6. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +17 -20
  7. prediction_market_agent_tooling/markets/agent_market.py +27 -9
  8. prediction_market_agent_tooling/markets/blockchain_utils.py +3 -3
  9. prediction_market_agent_tooling/markets/markets.py +16 -0
  10. prediction_market_agent_tooling/markets/omen/data_models.py +3 -18
  11. prediction_market_agent_tooling/markets/omen/omen.py +26 -11
  12. prediction_market_agent_tooling/markets/omen/omen_contracts.py +2 -196
  13. prediction_market_agent_tooling/markets/omen/omen_resolving.py +2 -2
  14. prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +13 -11
  15. prediction_market_agent_tooling/markets/polymarket/api.py +35 -1
  16. prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
  17. prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
  18. prediction_market_agent_tooling/markets/polymarket/data_models.py +33 -5
  19. prediction_market_agent_tooling/markets/polymarket/polymarket.py +247 -18
  20. prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
  21. prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +2 -1
  22. prediction_market_agent_tooling/markets/seer/data_models.py +41 -6
  23. prediction_market_agent_tooling/markets/seer/price_manager.py +69 -1
  24. prediction_market_agent_tooling/markets/seer/seer.py +77 -26
  25. prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
  26. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +71 -20
  27. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +67 -0
  28. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +17 -22
  29. prediction_market_agent_tooling/tools/contract.py +236 -4
  30. prediction_market_agent_tooling/tools/cow/cow_order.py +13 -8
  31. prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
  32. prediction_market_agent_tooling/tools/hexbytes_custom.py +3 -9
  33. prediction_market_agent_tooling/tools/langfuse_client_utils.py +17 -5
  34. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +2 -2
  35. prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
  36. prediction_market_agent_tooling/tools/web3_utils.py +9 -4
  37. {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/METADATA +8 -7
  38. {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/RECORD +41 -38
  39. prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -366
  40. {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/LICENSE +0 -0
  41. {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/WHEEL +0 -0
  42. {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,6 @@
1
+ from enum import Enum
1
2
  from itertools import chain
3
+ from typing import Callable
2
4
 
3
5
  import numpy as np
4
6
  from scipy.optimize import minimize
@@ -9,11 +11,7 @@ from prediction_market_agent_tooling.gtypes import (
9
11
  Probability,
10
12
  )
11
13
  from prediction_market_agent_tooling.loggers import logger
12
- from prediction_market_agent_tooling.markets.agent_market import AgentMarket
13
14
  from prediction_market_agent_tooling.markets.market_fees import MarketFees
14
- from prediction_market_agent_tooling.markets.omen.omen import (
15
- calculate_buy_outcome_token,
16
- )
17
15
  from prediction_market_agent_tooling.tools.betting_strategies.utils import (
18
16
  BinaryKellyBet,
19
17
  CategoricalKellyBet,
@@ -21,6 +19,11 @@ from prediction_market_agent_tooling.tools.betting_strategies.utils import (
21
19
  from prediction_market_agent_tooling.tools.utils import check_not_none
22
20
 
23
21
 
22
+ class KellyType(str, Enum):
23
+ SIMPLE = "simple"
24
+ FULL = "full"
25
+
26
+
24
27
  def check_is_valid_probability(probability: float) -> None:
25
28
  if not 0 <= probability <= 1:
26
29
  raise ValueError("Probability must be between 0 and 1")
@@ -237,7 +240,7 @@ def get_kelly_bets_categorical_simplified(
237
240
 
238
241
 
239
242
  def get_kelly_bets_categorical_full(
240
- outcome_pool_sizes: list[OutcomeToken],
243
+ market_probabilities: list[Probability],
241
244
  estimated_probabilities: list[Probability],
242
245
  confidence: float,
243
246
  max_bet: CollateralToken,
@@ -245,6 +248,8 @@ def get_kelly_bets_categorical_full(
245
248
  allow_multiple_bets: bool,
246
249
  allow_shorting: bool,
247
250
  multicategorical: bool,
251
+ # investment amount, outcome index --> received outcome tokens
252
+ get_buy_token_amount: Callable[[CollateralToken, int], OutcomeToken],
248
253
  bet_precision: int = 6,
249
254
  ) -> list[CategoricalKellyBet]:
250
255
  """
@@ -255,18 +260,13 @@ def get_kelly_bets_categorical_full(
255
260
  If the agent's probabilities are very close to the market's, returns all-zero bets.
256
261
  multicategorical means that multiple outcomes could be selected as correct ones.
257
262
  """
258
- assert len(outcome_pool_sizes) == len(
263
+ assert len(market_probabilities) == len(
259
264
  estimated_probabilities
260
265
  ), "Mismatch in number of outcomes"
261
-
262
- market_probabilities = AgentMarket.compute_fpmm_probabilities(
263
- [x.as_outcome_wei for x in outcome_pool_sizes]
264
- )
265
-
266
266
  for p in chain(market_probabilities, estimated_probabilities, [confidence]):
267
267
  check_is_valid_probability(p)
268
268
 
269
- n = len(outcome_pool_sizes)
269
+ n = len(market_probabilities)
270
270
  max_bet_value = max_bet.value
271
271
 
272
272
  if all(
@@ -283,22 +283,17 @@ def get_kelly_bets_categorical_full(
283
283
  payout = 0.0
284
284
  if bets[i] >= 0:
285
285
  # If bet on i is positive, we buy outcome i
286
- buy_result = calculate_buy_outcome_token(
287
- CollateralToken(bets[i]), i, outcome_pool_sizes, fees
288
- )
289
- payout += buy_result.outcome_tokens_received.value
286
+ buy_result = get_buy_token_amount(CollateralToken(bets[i]), i)
287
+ payout += buy_result.value
290
288
  else:
291
289
  # If bet is negative, we "short" outcome i by buying all other outcomes
292
290
  for j in range(n):
293
291
  if j == i:
294
292
  continue
295
- buy_result = calculate_buy_outcome_token(
296
- CollateralToken(abs(bets[i]) / (n - 1)),
297
- j,
298
- outcome_pool_sizes,
299
- fees,
293
+ buy_result = get_buy_token_amount(
294
+ CollateralToken(abs(bets[i]) / (n - 1)), j
300
295
  )
301
- payout += buy_result.outcome_tokens_received.value
296
+ payout += buy_result.value
302
297
  payouts.append(payout)
303
298
  return payouts
304
299
 
@@ -8,16 +8,21 @@ import eth_abi
8
8
  from eth_abi.exceptions import DecodingError
9
9
  from pydantic import BaseModel, field_validator
10
10
  from web3 import Web3
11
- from web3.constants import CHECKSUM_ADDRESSS_ZERO
11
+ from web3.constants import CHECKSUM_ADDRESSS_ZERO, HASH_ZERO
12
12
  from web3.contract.contract import Contract as Web3Contract
13
13
 
14
+ from prediction_market_agent_tooling.chains import POLYGON_CHAIN_ID
14
15
  from prediction_market_agent_tooling.config import APIKeys, RPCConfig
15
16
  from prediction_market_agent_tooling.gtypes import (
16
17
  ABI,
17
18
  ChainID,
18
19
  ChecksumAddress,
19
20
  CollateralToken,
21
+ HexAddress,
22
+ HexBytes,
23
+ HexStr,
20
24
  Nonce,
25
+ OutcomeWei,
21
26
  TxParams,
22
27
  TxReceipt,
23
28
  Wei,
@@ -516,9 +521,19 @@ class ContractOnGnosisChain(ContractBaseClass):
516
521
  Contract base class with Gnosis Chain configuration.
517
522
  """
518
523
 
524
+ # This is defined like so because other chains (like Ethereum) rely on contracts that inherit
525
+ # from ContractOnGnosisChain. To be re-evaluated on https://github.com/gnosis/prediction-market-agent-tooling/issues/845
519
526
  CHAIN_ID = RPCConfig().chain_id
520
527
 
521
528
 
529
+ class ContractOnPolygonChain(ContractBaseClass):
530
+ """
531
+ Contract base class with Gnosis Chain configuration.
532
+ """
533
+
534
+ CHAIN_ID = POLYGON_CHAIN_ID
535
+
536
+
522
537
  class ContractProxyOnGnosisChain(ContractProxyBaseClass, ContractOnGnosisChain):
523
538
  """
524
539
  Proxy contract base class with Gnosis Chain configuration.
@@ -566,6 +581,223 @@ class ContractERC4626OnGnosisChain(
566
581
  return to_gnosis_chain_contract(super().get_asset_token_contract(web3=web3))
567
582
 
568
583
 
584
+ class PayoutRedemptionEvent(BaseModel):
585
+ redeemer: HexAddress
586
+ collateralToken: HexAddress
587
+ parentCollectionId: HexBytes
588
+ conditionId: HexBytes
589
+ indexSets: list[int]
590
+ payout: Wei
591
+
592
+
593
+ class ConditionPreparationEvent(BaseModel):
594
+ conditionId: HexBytes
595
+ oracle: HexAddress
596
+ questionId: HexBytes
597
+ outcomeSlotCount: int
598
+
599
+
600
+ class ConditionalTokenContract(ContractBaseClass):
601
+ # Contract ABI taken from https://gnosisscan.io/address/0xCeAfDD6bc0bEF976fdCd1112955828E00543c0Ce#code.
602
+ abi: ABI = abi_field_validator(
603
+ os.path.join(
604
+ os.path.dirname(os.path.realpath(__file__)),
605
+ "../abis/omen_fpmm_conditionaltokens.abi.json",
606
+ )
607
+ )
608
+
609
+ def getConditionId(
610
+ self,
611
+ question_id: HexBytes,
612
+ oracle_address: ChecksumAddress,
613
+ outcomes_slot_count: int,
614
+ web3: Web3 | None = None,
615
+ ) -> HexBytes:
616
+ id_ = HexBytes(
617
+ self.call(
618
+ "getConditionId",
619
+ [oracle_address, question_id, outcomes_slot_count],
620
+ web3=web3,
621
+ )
622
+ )
623
+ return id_
624
+
625
+ def balanceOf(
626
+ self, from_address: ChecksumAddress, position_id: int, web3: Web3 | None = None
627
+ ) -> OutcomeWei:
628
+ balance = OutcomeWei(
629
+ self.call("balanceOf", [from_address, position_id], web3=web3)
630
+ )
631
+ return balance
632
+
633
+ def isApprovedForAll(
634
+ self,
635
+ owner: ChecksumAddress,
636
+ for_address: ChecksumAddress,
637
+ web3: Web3 | None = None,
638
+ ) -> bool:
639
+ is_approved: bool = self.call(
640
+ "isApprovedForAll", [owner, for_address], web3=web3
641
+ )
642
+ return is_approved
643
+
644
+ def getCollectionId(
645
+ self,
646
+ parent_collection_id: HexStr,
647
+ condition_id: HexBytes,
648
+ index_set: int,
649
+ web3: Web3 | None = None,
650
+ ) -> HexBytes:
651
+ collection_id = HexBytes(
652
+ self.call(
653
+ "getCollectionId",
654
+ [parent_collection_id, condition_id, index_set],
655
+ web3=web3,
656
+ )
657
+ )
658
+ return collection_id
659
+
660
+ def getPositionId(
661
+ self,
662
+ collateral_token_address: ChecksumAddress,
663
+ collection_id: HexBytes,
664
+ web3: Web3 | None = None,
665
+ ) -> int:
666
+ position_id: int = self.call(
667
+ "getPositionId",
668
+ [collateral_token_address, collection_id],
669
+ web3=web3,
670
+ )
671
+ return position_id
672
+
673
+ def mergePositions(
674
+ self,
675
+ api_keys: APIKeys,
676
+ collateral_token_address: ChecksumAddress,
677
+ conditionId: HexBytes,
678
+ index_sets: t.List[int],
679
+ amount: OutcomeWei,
680
+ parent_collection_id: HexStr = HASH_ZERO,
681
+ web3: Web3 | None = None,
682
+ ) -> TxReceipt:
683
+ return self.send(
684
+ api_keys=api_keys,
685
+ function_name="mergePositions",
686
+ function_params=[
687
+ collateral_token_address,
688
+ parent_collection_id,
689
+ conditionId,
690
+ index_sets,
691
+ amount,
692
+ ],
693
+ web3=web3,
694
+ )
695
+
696
+ def redeemPositions(
697
+ self,
698
+ api_keys: APIKeys,
699
+ collateral_token_address: HexAddress,
700
+ condition_id: HexBytes,
701
+ index_sets: t.List[int],
702
+ parent_collection_id: HexStr = HASH_ZERO,
703
+ web3: Web3 | None = None,
704
+ ) -> PayoutRedemptionEvent:
705
+ receipt_tx = self.send(
706
+ api_keys=api_keys,
707
+ function_name="redeemPositions",
708
+ function_params=[
709
+ collateral_token_address,
710
+ parent_collection_id,
711
+ condition_id,
712
+ index_sets,
713
+ ],
714
+ web3=web3,
715
+ )
716
+ redeem_event_logs = (
717
+ self.get_web3_contract(web3=web3)
718
+ .events.PayoutRedemption()
719
+ .process_receipt(receipt_tx)
720
+ )
721
+ redeem_event = PayoutRedemptionEvent(**redeem_event_logs[0]["args"])
722
+ return redeem_event
723
+
724
+ def getOutcomeSlotCount(
725
+ self, condition_id: HexBytes, web3: Web3 | None = None
726
+ ) -> int:
727
+ count: int = self.call("getOutcomeSlotCount", [condition_id], web3=web3)
728
+ return count
729
+
730
+ def does_condition_exists(
731
+ self, condition_id: HexBytes, web3: Web3 | None = None
732
+ ) -> bool:
733
+ return self.getOutcomeSlotCount(condition_id, web3=web3) > 0
734
+
735
+ def is_condition_resolved(
736
+ self, condition_id: HexBytes, web3: Web3 | None = None
737
+ ) -> bool:
738
+ # from ConditionalTokens.redeemPositions:
739
+ # uint den = payoutDenominator[conditionId]; require(den > 0, "result for condition not received yet");
740
+ payout_for_condition = self.payoutDenominator(condition_id, web3=web3)
741
+ return payout_for_condition > 0
742
+
743
+ def payoutDenominator(
744
+ self, condition_id: HexBytes, web3: Web3 | None = None
745
+ ) -> int:
746
+ payoutForCondition: int = self.call(
747
+ "payoutDenominator", [condition_id], web3=web3
748
+ )
749
+ return payoutForCondition
750
+
751
+ def setApprovalForAll(
752
+ self,
753
+ api_keys: APIKeys,
754
+ for_address: ChecksumAddress,
755
+ approve: bool,
756
+ tx_params: t.Optional[TxParams] = None,
757
+ web3: Web3 | None = None,
758
+ ) -> TxReceipt:
759
+ return self.send(
760
+ api_keys=api_keys,
761
+ function_name="setApprovalForAll",
762
+ function_params=[
763
+ for_address,
764
+ approve,
765
+ ],
766
+ tx_params=tx_params,
767
+ web3=web3,
768
+ )
769
+
770
+ def prepareCondition(
771
+ self,
772
+ api_keys: APIKeys,
773
+ oracle_address: ChecksumAddress,
774
+ question_id: HexBytes,
775
+ outcomes_slot_count: int,
776
+ tx_params: t.Optional[TxParams] = None,
777
+ web3: Web3 | None = None,
778
+ ) -> ConditionPreparationEvent:
779
+ receipt_tx = self.send(
780
+ api_keys=api_keys,
781
+ function_name="prepareCondition",
782
+ function_params=[
783
+ oracle_address,
784
+ question_id,
785
+ outcomes_slot_count,
786
+ ],
787
+ tx_params=tx_params,
788
+ web3=web3,
789
+ )
790
+
791
+ event_logs = (
792
+ self.get_web3_contract(web3=web3)
793
+ .events.ConditionPreparation()
794
+ .process_receipt(receipt_tx)
795
+ )
796
+ cond_event = ConditionPreparationEvent(**event_logs[0]["args"])
797
+
798
+ return cond_event
799
+
800
+
569
801
  class DebuggingContract(ContractOnGnosisChain):
570
802
  # Contract ABI taken from https://gnosisscan.io/address/0x5Aa82E068aE6a6a1C26c42E5a59520a74Cdb8998#code.
571
803
  abi: ABI = abi_field_validator(
@@ -614,12 +846,12 @@ def contract_implements_function(
614
846
  look_for_proxy_contract: bool = True,
615
847
  ) -> bool:
616
848
  function_signature = f"{function_name}({','.join(function_arg_types or [])})"
617
- function_selector = web3.keccak(text=function_signature)[0:4].hex()[2:]
849
+ function_selector = web3.keccak(text=function_signature)[0:4].to_0x_hex()[2:]
618
850
  # 1. Check directly in bytecode
619
- bytecode = web3.eth.get_code(contract_address).hex()
851
+ bytecode = web3.eth.get_code(contract_address).to_0x_hex()
620
852
  if function_selector in bytecode:
621
853
  return True
622
- contract_code = web3.eth.get_code(contract_address).hex()
854
+ contract_code = web3.eth.get_code(contract_address).to_0x_hex()
623
855
  implements = function_selector in contract_code
624
856
 
625
857
  # If not found directly and we should check proxies
@@ -48,7 +48,7 @@ from prediction_market_agent_tooling.loggers import logger
48
48
  from prediction_market_agent_tooling.markets.omen.cow_contracts import (
49
49
  CowGPv2SettlementContract,
50
50
  )
51
- from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
51
+ from prediction_market_agent_tooling.tools.contract import ContractERC20BaseClass
52
52
  from prediction_market_agent_tooling.tools.cow.models import MinimalisticTrade, Order
53
53
  from prediction_market_agent_tooling.tools.cow.semaphore import postgres_rate_limited
54
54
  from prediction_market_agent_tooling.tools.utils import utcnow
@@ -166,7 +166,8 @@ def get_buy_token_amount_else_raise(
166
166
  def handle_allowance(
167
167
  api_keys: APIKeys,
168
168
  sell_token: ChecksumAddress,
169
- amount_wei: Wei,
169
+ amount_to_check_wei: Wei,
170
+ amount_to_set_wei: Wei | None = None,
170
171
  for_address: ChecksumAddress | None = None,
171
172
  web3: Web3 | None = None,
172
173
  ) -> None:
@@ -174,16 +175,17 @@ def handle_allowance(
174
175
  for_address = for_address or Web3.to_checksum_address(
175
176
  CowContractAddress.VAULT_RELAYER.value
176
177
  )
177
- current_allowance = ContractERC20OnGnosisChain(address=sell_token).allowance(
178
+ current_allowance = ContractERC20BaseClass(address=sell_token).allowance(
178
179
  owner=api_keys.bet_from_address,
179
180
  for_address=for_address,
180
181
  web3=web3,
181
182
  )
182
- if current_allowance < amount_wei:
183
- ContractERC20OnGnosisChain(address=sell_token).approve(
183
+ if current_allowance < amount_to_check_wei:
184
+ amount_to_set_wei = amount_to_set_wei or amount_to_check_wei
185
+ ContractERC20BaseClass(address=sell_token).approve(
184
186
  api_keys,
185
187
  for_address=for_address,
186
- amount_wei=amount_wei,
188
+ amount_wei=amount_to_set_wei,
187
189
  web3=web3,
188
190
  )
189
191
 
@@ -305,7 +307,10 @@ async def swap_tokens_waiting_async(
305
307
  wait_order_complete: bool = True,
306
308
  ) -> tuple[OrderMetaData | None, CompletedOrder]:
307
309
  handle_allowance(
308
- api_keys=api_keys, sell_token=sell_token, amount_wei=amount_wei, web3=web3
310
+ api_keys=api_keys,
311
+ sell_token=sell_token,
312
+ amount_to_check_wei=amount_wei,
313
+ web3=web3,
309
314
  )
310
315
  valid_to = (utcnow() + timeout).timestamp()
311
316
  order = await place_swap_order(
@@ -375,7 +380,7 @@ def get_trades_by_order_uid(
375
380
  # Using this until cowpy gets fixed (https://github.com/cowdao-grants/cow-py/issues/35)
376
381
  response = httpx.get(
377
382
  f"https://api.cow.fi/xdai/api/v1/trades",
378
- params={"orderUid": order_uid.hex()},
383
+ params={"orderUid": order_uid.to_0x_hex()},
379
384
  )
380
385
  response.raise_for_status()
381
386
  return [MinimalisticTrade.model_validate(i) for i in response.json()]
@@ -34,8 +34,20 @@ class DatetimeUTC(datetime):
34
34
  def __get_pydantic_core_schema__(
35
35
  cls, source_type: t.Any, handler: GetCoreSchemaHandler
36
36
  ) -> CoreSchema:
37
- dt_schema = handler(datetime)
38
- return core_schema.no_info_after_validator_function(cls._validate, dt_schema)
37
+ # Use union schema to handle int, str, and datetime inputs directly
38
+ return core_schema.union_schema(
39
+ [
40
+ core_schema.no_info_after_validator_function(
41
+ cls._validate, core_schema.int_schema()
42
+ ),
43
+ core_schema.no_info_after_validator_function(
44
+ cls._validate, core_schema.str_schema()
45
+ ),
46
+ core_schema.no_info_after_validator_function(
47
+ cls._validate, handler(datetime)
48
+ ),
49
+ ]
50
+ )
39
51
 
40
52
  @staticmethod
41
53
  def from_datetime(dt: datetime) -> "DatetimeUTC":
@@ -11,7 +11,7 @@ from pydantic_core.core_schema import (
11
11
  with_info_before_validator_function,
12
12
  )
13
13
 
14
- hex_serializer = plain_serializer_function_ser_schema(function=lambda x: x.hex())
14
+ hex_serializer = plain_serializer_function_ser_schema(function=lambda x: x.to_0x_hex())
15
15
 
16
16
 
17
17
  class BaseHex:
@@ -60,14 +60,8 @@ class HexBytes(HexBytesBase, BaseHex):
60
60
  value = hex_str[2:] if hex_str.startswith("0x") else hex_str
61
61
  return super().fromhex(value)
62
62
 
63
- def hex(
64
- self,
65
- sep: t.Union[str, bytes] | None = None,
66
- bytes_per_sep: t.SupportsIndex = 1,
67
- ) -> str:
68
- """We enforce a 0x prefix."""
69
- x = super().hex(sep, bytes_per_sep) # type: ignore[arg-type]
70
- return x if x.startswith("0x") else "0x" + x
63
+ def __repr__(self) -> str:
64
+ return f'HexBytes("{self.to_0x_hex()}")'
71
65
 
72
66
  @classmethod
73
67
  def __eth_pydantic_validate__(
@@ -5,7 +5,9 @@ from langfuse import Langfuse
5
5
  from langfuse.client import TraceWithDetails
6
6
  from pydantic import BaseModel
7
7
 
8
+ from prediction_market_agent_tooling.deploy.agent import MarketType
8
9
  from prediction_market_agent_tooling.loggers import logger
10
+ from prediction_market_agent_tooling.markets.agent_market import AgentMarket
9
11
  from prediction_market_agent_tooling.markets.data_models import (
10
12
  CategoricalProbabilisticAnswer,
11
13
  PlacedTrade,
@@ -16,12 +18,13 @@ from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
16
18
  from prediction_market_agent_tooling.markets.omen.omen_constants import (
17
19
  WRAPPED_XDAI_CONTRACT_ADDRESS,
18
20
  )
21
+ from prediction_market_agent_tooling.markets.seer.seer import SeerAgentMarket
19
22
  from prediction_market_agent_tooling.tools.utils import DatetimeUTC
20
23
 
21
24
 
22
25
  class ProcessMarketTrace(BaseModel):
23
26
  timestamp: int
24
- market: OmenAgentMarket
27
+ market: SeerAgentMarket | OmenAgentMarket
25
28
  answer: CategoricalProbabilisticAnswer
26
29
  trades: list[PlacedTrade]
27
30
 
@@ -40,13 +43,18 @@ class ProcessMarketTrace(BaseModel):
40
43
  def from_langfuse_trace(
41
44
  trace: TraceWithDetails,
42
45
  ) -> t.Optional["ProcessMarketTrace"]:
43
- market = trace_to_omen_agent_market(trace)
46
+ market = trace_to_agent_market(trace)
44
47
  answer = trace_to_answer(trace)
45
48
  trades = trace_to_trades(trace)
46
49
 
47
50
  if not market or not answer or not trades:
48
51
  return None
49
52
 
53
+ if not isinstance(market, (SeerAgentMarket, OmenAgentMarket)):
54
+ raise ValueError(
55
+ f"Market type {type(market)} is not supported for ProcessMarketTrace"
56
+ )
57
+
50
58
  return ProcessMarketTrace(
51
59
  market=market,
52
60
  answer=answer,
@@ -105,17 +113,21 @@ def get_traces_for_agent(
105
113
  return all_agent_traces
106
114
 
107
115
 
108
- def trace_to_omen_agent_market(trace: TraceWithDetails) -> OmenAgentMarket | None:
116
+ def trace_to_agent_market(trace: TraceWithDetails) -> AgentMarket | None:
109
117
  if not trace.input:
110
118
  logger.warning(f"No input in the trace: {trace}")
111
119
  return None
112
120
  if not trace.input["args"]:
113
121
  logger.warning(f"No args in the trace: {trace}")
114
122
  return None
115
- assert len(trace.input["args"]) == 2 and trace.input["args"][0] == "omen"
123
+ assert len(trace.input["args"]) == 2
124
+
125
+ market_type = MarketType(trace.input["args"][0])
126
+ market_class = market_type.market_class
127
+
116
128
  try:
117
129
  # If the market model is invalid (e.g. outdated), it will raise an exception
118
- market = OmenAgentMarket.model_validate(trace.input["args"][1])
130
+ market = market_class.model_validate(trace.input["args"][1])
119
131
  return market
120
132
  except Exception as e:
121
133
  logger.warning(f"Market not parsed from langfuse because: {e}")
@@ -194,7 +194,7 @@ def mint_full_set(
194
194
  # of the child market.
195
195
  seer_subgraph_handler = SeerSubgraphHandler()
196
196
  market = seer_subgraph_handler.get_market_by_wrapped_token(
197
- token=collateral_token_contract.address
197
+ tokens=[collateral_token_contract.address]
198
198
  )
199
199
  market_collateral_token = Web3.to_checksum_address(market.collateral_token)
200
200
 
@@ -215,7 +215,7 @@ def mint_full_set(
215
215
  handle_allowance(
216
216
  api_keys=api_keys,
217
217
  sell_token=market_collateral_token,
218
- amount_wei=collateral_amount_wei,
218
+ amount_to_check_wei=collateral_amount_wei,
219
219
  for_address=router.address,
220
220
  web3=web3,
221
221
  )
@@ -10,6 +10,9 @@ from prediction_market_agent_tooling.markets.omen.omen_constants import (
10
10
  SDAI_CONTRACT_ADDRESS,
11
11
  WRAPPED_XDAI_CONTRACT_ADDRESS,
12
12
  )
13
+ from prediction_market_agent_tooling.markets.polymarket.polymarket_contracts import (
14
+ USDCeContract,
15
+ )
13
16
  from prediction_market_agent_tooling.tools.contract import ContractERC4626OnGnosisChain
14
17
  from prediction_market_agent_tooling.tools.cow.cow_order import (
15
18
  get_buy_token_amount_else_raise,
@@ -39,8 +42,8 @@ def get_token_in_usd(amount: CollateralToken, token_address: ChecksumAddress) ->
39
42
  # A short cache to not spam CoW and prevent timeouts, but still have relatively fresh data.
40
43
  @cached(TTLCache(maxsize=100, ttl=5 * 60))
41
44
  def get_single_token_to_usd_rate(token_address: ChecksumAddress) -> USD:
42
- # (w)xDai is a stable coin against USD, so use it to estimate USD worth.
43
- if WRAPPED_XDAI_CONTRACT_ADDRESS == token_address:
45
+ # (w)xDai and USDC are stablecoins pegged to USD, so use it to estimate USD worth.
46
+ if token_address in [WRAPPED_XDAI_CONTRACT_ADDRESS, USDCeContract().address]:
44
47
  return USD(1.0)
45
48
  # sDai is ERC4626 with wxDai as asset, we can take the rate directly from there instead of calling CoW.
46
49
  if SDAI_CONTRACT_ADDRESS == token_address:
@@ -9,6 +9,7 @@ from eth_typing import URI
9
9
  from eth_utils.currency import MAX_WEI, MIN_WEI
10
10
  from pydantic.types import SecretStr
11
11
  from safe_eth.eth import EthereumClient
12
+ from safe_eth.eth.ethereum_client import TxSpeed
12
13
  from safe_eth.safe.safe import SafeV141
13
14
  from web3 import Web3
14
15
  from web3.constants import HASH_ZERO
@@ -104,7 +105,9 @@ def call_function_on_contract(
104
105
  function_params: Optional[list[Any] | dict[str, Any]] = None,
105
106
  ) -> Any:
106
107
  contract = web3.eth.contract(address=contract_address, abi=contract_abi)
107
- output = contract.functions[function_name](*parse_function_params(function_params)).call() # type: ignore # TODO: Fix Mypy, as this works just OK.
108
+ output = contract.functions[function_name](
109
+ *parse_function_params(function_params)
110
+ ).call()
108
111
  return output
109
112
 
110
113
 
@@ -120,9 +123,10 @@ def prepare_tx(
120
123
  ) -> TxParams:
121
124
  tx_params_new = _prepare_tx_params(web3, from_address, access_list, tx_params)
122
125
  contract = web3.eth.contract(address=contract_address, abi=contract_abi)
123
-
124
126
  # Build the transaction.
125
- function_call = contract.functions[function_name](*parse_function_params(function_params)) # type: ignore # TODO: Fix Mypy, as this works just OK.
127
+ function_call = contract.functions[function_name](
128
+ *parse_function_params(function_params)
129
+ )
126
130
  built_tx_params: TxParams = function_call.build_transaction(tx_params_new)
127
131
  return built_tx_params
128
132
 
@@ -269,6 +273,7 @@ def send_function_on_contract_tx_using_safe(
269
273
  tx_hash, tx = safe_tx.execute(
270
274
  from_private_key.get_secret_value(),
271
275
  tx_nonce=eoa_nonce,
276
+ eip1559_speed=TxSpeed.FAST,
272
277
  )
273
278
  receipt_tx = web3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout)
274
279
  check_tx_receipt(receipt_tx)
@@ -286,7 +291,7 @@ def sign_send_and_get_receipt_tx(
286
291
  tx_params_new, private_key=from_private_key.get_secret_value()
287
292
  )
288
293
  # Send the signed transaction.
289
- send_tx = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
294
+ send_tx = web3.eth.send_raw_transaction(signed_tx.raw_transaction)
290
295
  # And wait for the receipt.
291
296
  receipt_tx = web3.eth.wait_for_transaction_receipt(send_tx, timeout=timeout)
292
297
  # Verify it didn't fail.