prediction-market-agent-tooling 0.67.1__py3-none-any.whl → 0.67.3__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 (24) hide show
  1. prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
  2. prediction_market_agent_tooling/deploy/agent.py +109 -47
  3. prediction_market_agent_tooling/deploy/betting_strategy.py +1 -1
  4. prediction_market_agent_tooling/markets/agent_market.py +16 -3
  5. prediction_market_agent_tooling/markets/manifold/manifold.py +4 -3
  6. prediction_market_agent_tooling/markets/markets.py +6 -5
  7. prediction_market_agent_tooling/markets/metaculus/metaculus.py +4 -3
  8. prediction_market_agent_tooling/markets/omen/omen.py +5 -4
  9. prediction_market_agent_tooling/markets/polymarket/polymarket.py +5 -4
  10. prediction_market_agent_tooling/markets/seer/data_models.py +30 -8
  11. prediction_market_agent_tooling/markets/seer/seer.py +79 -20
  12. prediction_market_agent_tooling/markets/seer/seer_contracts.py +18 -0
  13. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +149 -20
  14. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +0 -4
  15. prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +0 -10
  16. prediction_market_agent_tooling/tools/contract.py +59 -0
  17. prediction_market_agent_tooling/tools/cow/cow_order.py +4 -1
  18. prediction_market_agent_tooling/tools/rephrase.py +71 -0
  19. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +57 -0
  20. {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/METADATA +2 -1
  21. {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/RECORD +24 -22
  22. {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/LICENSE +0 -0
  23. {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/WHEEL +0 -0
  24. {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  import sys
2
2
  import typing as t
3
+ from collections import defaultdict
3
4
  from enum import Enum
4
5
  from typing import Any
5
6
 
@@ -15,16 +16,22 @@ from prediction_market_agent_tooling.deploy.constants import (
15
16
  from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei
16
17
  from prediction_market_agent_tooling.loggers import logger
17
18
  from prediction_market_agent_tooling.markets.agent_market import (
19
+ ConditionalFilterType,
18
20
  FilterBy,
19
- MarketType,
21
+ QuestionType,
20
22
  SortBy,
21
23
  )
22
24
  from prediction_market_agent_tooling.markets.base_subgraph_handler import (
23
25
  BaseSubgraphHandler,
24
26
  )
25
- from prediction_market_agent_tooling.markets.seer.data_models import SeerMarket
27
+ from prediction_market_agent_tooling.markets.seer.data_models import (
28
+ SeerMarket,
29
+ SeerMarketQuestions,
30
+ SeerMarketWithQuestions,
31
+ )
26
32
  from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
27
33
  from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
34
+ from prediction_market_agent_tooling.tools.singleton import SingletonMeta
28
35
  from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
29
36
  from prediction_market_agent_tooling.tools.web3_utils import unwrap_generic_value
30
37
 
@@ -62,7 +69,9 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
62
69
  )
63
70
  )
64
71
 
65
- def _get_fields_for_markets(self, markets_field: FieldPath) -> list[FieldPath]:
72
+ def _get_fields_for_markets(
73
+ self, markets_field: FieldPath, current_level: int = 0, max_level: int = 1
74
+ ) -> list[FieldPath]:
66
75
  fields = [
67
76
  markets_field.id,
68
77
  markets_field.factory,
@@ -76,7 +85,6 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
76
85
  markets_field.payoutNumerators,
77
86
  markets_field.hasAnswers,
78
87
  markets_field.blockTimestamp,
79
- markets_field.parentMarket.id,
80
88
  markets_field.openingTs,
81
89
  markets_field.finalizeTs,
82
90
  markets_field.wrappedTokens,
@@ -85,6 +93,18 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
85
93
  markets_field.lowerBound,
86
94
  markets_field.templateId,
87
95
  ]
96
+ if current_level < max_level:
97
+ fields.extend(
98
+ self._get_fields_for_markets(
99
+ markets_field.parentMarket, current_level + 1, max_level
100
+ )
101
+ )
102
+ # TODO: Same situation as with `questions` field above.
103
+ # fields.extend(
104
+ # self._get_fields_for_markets(
105
+ # markets_field.childMarkets, current_level + 1, max_level
106
+ # )
107
+ # )
88
108
  return fields
89
109
 
90
110
  @staticmethod
@@ -113,8 +133,9 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
113
133
  def _build_where_statements(
114
134
  filter_by: FilterBy,
115
135
  outcome_supply_gt_if_open: Wei,
116
- include_conditional_markets: bool = False,
117
- market_type: MarketType = MarketType.ALL,
136
+ question_type: QuestionType = QuestionType.ALL,
137
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
138
+ parent_market_id: HexBytes | None = None,
118
139
  ) -> dict[Any, Any]:
119
140
  now = to_int_timestamp(utcnow())
120
141
 
@@ -133,12 +154,12 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
133
154
  case _:
134
155
  raise ValueError(f"Unknown filter {filter_by}")
135
156
 
136
- if not include_conditional_markets:
137
- and_stms["parentMarket"] = ADDRESS_ZERO.lower()
157
+ if parent_market_id:
158
+ and_stms["parentMarket"] = parent_market_id.hex().lower()
138
159
 
139
160
  outcome_filters: list[dict[str, t.Any]] = []
140
161
 
141
- if market_type == MarketType.SCALAR:
162
+ if question_type == QuestionType.SCALAR:
142
163
  # Template ID "1" + UP/DOWN outcomes for scalar markets
143
164
  and_stms["templateId"] = TemplateId.SCALAR.value
144
165
  up_filter = SeerSubgraphHandler._create_case_variations_condition(
@@ -149,7 +170,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
149
170
  )
150
171
  outcome_filters.extend([up_filter, down_filter])
151
172
 
152
- elif market_type == MarketType.BINARY:
173
+ elif question_type == QuestionType.BINARY:
153
174
  # Template ID "2" + YES/NO outcomes for binary markets
154
175
  and_stms["templateId"] = TemplateId.CATEGORICAL.value
155
176
  yes_filter = SeerSubgraphHandler._create_case_variations_condition(
@@ -160,7 +181,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
160
181
  )
161
182
  outcome_filters.extend([yes_filter, no_filter])
162
183
 
163
- elif market_type == MarketType.CATEGORICAL:
184
+ elif question_type == QuestionType.CATEGORICAL:
164
185
  # Template ID 2 (categorical) OR Template ID 3 (multi-categorical,
165
186
  # we treat them as categorical for now for simplicity)
166
187
  # https://reality.eth.limo/app/docs/html/contracts.html#templates
@@ -173,9 +194,21 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
173
194
  }
174
195
  )
175
196
 
176
- # If none specified, don't add any template/outcome filters (returns all types)
197
+ # Build filters for conditional_filter type
198
+ conditional_filter = {}
199
+ match conditional_filter_type:
200
+ case ConditionalFilterType.ONLY_CONDITIONAL:
201
+ conditional_filter["parentMarket_not"] = ADDRESS_ZERO.lower()
202
+ case ConditionalFilterType.ONLY_NOT_CONDITIONAL:
203
+ conditional_filter["parentMarket"] = ADDRESS_ZERO.lower()
204
+ case ConditionalFilterType.ALL:
205
+ pass
206
+ case _:
207
+ raise ValueError(
208
+ f"Unknown conditional filter {conditional_filter_type}"
209
+ )
177
210
 
178
- all_filters = [and_stms] + outcome_filters if and_stms else outcome_filters
211
+ all_filters = outcome_filters + [and_stms, conditional_filter]
179
212
  where_stms: dict[str, t.Any] = {"and": all_filters}
180
213
  return where_stms
181
214
 
@@ -211,16 +244,18 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
211
244
  sort_by: SortBy = SortBy.NONE,
212
245
  limit: int | None = None,
213
246
  outcome_supply_gt_if_open: Wei = Wei(0),
214
- market_type: MarketType = MarketType.ALL,
215
- include_conditional_markets: bool = False,
216
- ) -> list[SeerMarket]:
247
+ question_type: QuestionType = QuestionType.ALL,
248
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
249
+ parent_market_id: HexBytes | None = None,
250
+ ) -> list[SeerMarketWithQuestions]:
217
251
  sort_direction, sort_by_field = self._build_sort_params(sort_by)
218
252
 
219
253
  where_stms = self._build_where_statements(
220
254
  filter_by=filter_by,
221
255
  outcome_supply_gt_if_open=outcome_supply_gt_if_open,
222
- include_conditional_markets=include_conditional_markets,
223
- market_type=market_type,
256
+ parent_market_id=parent_market_id,
257
+ question_type=question_type,
258
+ conditional_filter_type=conditional_filter_type,
224
259
  )
225
260
 
226
261
  # These values can not be set to `None`, but they can be omitted.
@@ -239,12 +274,65 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
239
274
  )
240
275
  fields = self._get_fields_for_markets(markets_field)
241
276
  markets = self.do_query(fields=fields, pydantic_model=SeerMarket)
242
- return markets
277
+ market_ids = [m.id for m in markets]
278
+ # We fetch questions from all markets and all parents in one go
279
+ parent_market_ids = [
280
+ m.parent_market.id for m in markets if m.parent_market is not None
281
+ ]
282
+ q = SeerQuestionsCache(seer_subgraph_handler=self)
283
+ q.fetch_questions(list(set(market_ids + parent_market_ids)))
284
+
285
+ # Create SeerMarketWithQuestions for each market
286
+ return [
287
+ SeerMarketWithQuestions(
288
+ **m.model_dump(), questions=q.market_id_to_questions[m.id]
289
+ )
290
+ for m in markets
291
+ ]
292
+
293
+ def get_questions_for_markets(
294
+ self, market_ids: list[HexBytes]
295
+ ) -> list[SeerMarketQuestions]:
296
+ where = unwrap_generic_value(
297
+ {"market_in": [market_id.hex().lower() for market_id in market_ids]}
298
+ )
299
+ markets_field = self.seer_subgraph.Query.marketQuestions(where=where)
300
+ fields = self._get_fields_for_questions(markets_field)
301
+ questions = self.do_query(fields=fields, pydantic_model=SeerMarketQuestions)
302
+ return questions
243
303
 
244
- def get_market_by_id(self, market_id: HexBytes) -> SeerMarket:
304
+ def get_market_by_id(self, market_id: HexBytes) -> SeerMarketWithQuestions:
245
305
  markets_field = self.seer_subgraph.Query.market(id=market_id.hex().lower())
246
306
  fields = self._get_fields_for_markets(markets_field)
247
307
  markets = self.do_query(fields=fields, pydantic_model=SeerMarket)
308
+ if len(markets) != 1:
309
+ raise ValueError(
310
+ f"Fetched wrong number of markets. Expected 1 but got {len(markets)}"
311
+ )
312
+ q = SeerQuestionsCache(self)
313
+ q.fetch_questions([market_id])
314
+ questions = q.market_id_to_questions[market_id]
315
+ s = SeerMarketWithQuestions.model_validate(
316
+ markets[0].model_dump() | {"questions": questions}
317
+ )
318
+ return s
319
+
320
+ def _get_fields_for_questions(self, questions_field: FieldPath) -> list[FieldPath]:
321
+ fields = [
322
+ questions_field.question.id,
323
+ questions_field.question.best_answer,
324
+ questions_field.question.finalize_ts,
325
+ questions_field.market.id,
326
+ ]
327
+ return fields
328
+
329
+ def get_market_by_wrapped_token(self, token: ChecksumAddress) -> SeerMarket:
330
+ where_stms = {"wrappedTokens_contains": [token]}
331
+ markets_field = self.seer_subgraph.Query.markets(
332
+ where=unwrap_generic_value(where_stms)
333
+ )
334
+ fields = self._get_fields_for_markets(markets_field)
335
+ markets = self.do_query(fields=fields, pydantic_model=SeerMarket)
248
336
  if len(markets) != 1:
249
337
  raise ValueError(
250
338
  f"Fetched wrong number of markets. Expected 1 but got {len(markets)}"
@@ -303,3 +391,44 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
303
391
  # We select the first one
304
392
  return pools[0]
305
393
  return None
394
+
395
+
396
+ class SeerQuestionsCache(metaclass=SingletonMeta):
397
+ """A singleton cache for storing and retrieving Seer market questions.
398
+
399
+ This class provides an in-memory cache for Seer market questions, preventing
400
+ redundant subgraph queries by maintaining a mapping of market IDs to their
401
+ associated questions. It implements the singleton pattern to ensure a single
402
+ cache instance is used throughout the agent run.
403
+
404
+ Attributes:
405
+ market_id_to_questions: A dictionary mapping market IDs to lists of SeerMarketQuestions
406
+ seer_subgraph_handler: Handler for interacting with the Seer subgraph
407
+ """
408
+
409
+ def __init__(self, seer_subgraph_handler: SeerSubgraphHandler | None = None):
410
+ self.market_id_to_questions: dict[
411
+ HexBytes, list[SeerMarketQuestions]
412
+ ] = defaultdict(list)
413
+ self.seer_subgraph_handler = seer_subgraph_handler or SeerSubgraphHandler()
414
+
415
+ def fetch_questions(self, market_ids: list[HexBytes]) -> None:
416
+ filtered_list = [
417
+ market_id
418
+ for market_id in market_ids
419
+ if market_id not in self.market_id_to_questions
420
+ ]
421
+ if not filtered_list:
422
+ return
423
+
424
+ questions = self.seer_subgraph_handler.get_questions_for_markets(filtered_list)
425
+ # Group questions by market_id
426
+ questions_by_market: dict[HexBytes, list[SeerMarketQuestions]] = defaultdict(
427
+ list
428
+ )
429
+ for q in questions:
430
+ questions_by_market[q.market.id].append(q)
431
+
432
+ # Update the cache with the new questions for each market
433
+ for market_id, market_questions in questions_by_market.items():
434
+ self.market_id_to_questions[market_id] = market_questions
@@ -58,7 +58,3 @@ class CreateCategoricalMarketsParams(BaseModel):
58
58
  ) # typed as int for later .model_dump() usage (if using Wei, other keys also exported)
59
59
  opening_time: int = Field(..., alias="openingTime")
60
60
  token_names: list[str] = Field(..., alias="tokenNames")
61
-
62
-
63
- class SeerParentMarket(BaseModel):
64
- id: HexBytes
@@ -19,7 +19,6 @@ from prediction_market_agent_tooling.markets.seer.seer_contracts import (
19
19
  from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
20
20
  SeerSubgraphHandler,
21
21
  )
22
- from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
23
22
 
24
23
 
25
24
  class SwapPoolHandler:
@@ -91,15 +90,6 @@ class SwapPoolHandler:
91
90
  amount_out_minimum=amount_out_minimum,
92
91
  )
93
92
 
94
- # make sure user has enough tokens to sell
95
- balance_collateral_token = ContractERC20OnGnosisChain(
96
- address=token_in
97
- ).balanceOf(self.api_keys.bet_from_address, web3=web3)
98
- if balance_collateral_token < amount_wei:
99
- raise ValueError(
100
- f"Balance {balance_collateral_token} of {token_in} insufficient for trade, required {amount_wei}"
101
- )
102
-
103
93
  tx_receipt = SwaprRouterContract().exact_input_single(
104
94
  api_keys=self.api_keys, params=p, web3=web3
105
95
  )
@@ -413,6 +413,20 @@ class ContractERC4626BaseClass(ContractERC20BaseClass):
413
413
  return self.convertToShares(amount, web3=web3)
414
414
 
415
415
 
416
+ class ContractWrapped1155BaseClass(ContractERC20BaseClass):
417
+ """Wrapped 1155 contract from Seer (https://gnosisscan.io/address/0x2f9c49974ad8b9b31424d9dc812667b16310ca50#readContract)
418
+ Source code - https://github.com/seer-pm/demo/blob/main/contracts/src/interaction/1155-to-20/Wrapped1155Factory.sol#L224
419
+ This contract inherits from ERC20 and contains additional properties, such as multiToken (conditional Token contract implementation)
420
+ and tokenId (token identifier). Goal is to wrap individual tokens into a standalone ERC20 token.
421
+ """
422
+
423
+ abi: ABI = abi_field_validator(
424
+ os.path.join(
425
+ os.path.dirname(os.path.realpath(__file__)), "../abis/erc1155.abi.json"
426
+ )
427
+ )
428
+
429
+
416
430
  class OwnableContract(ContractBaseClass):
417
431
  abi: ABI = abi_field_validator(
418
432
  os.path.join(
@@ -533,6 +547,12 @@ class ContractDepositableWrapperERC20OnGnosisChain(
533
547
  """
534
548
 
535
549
 
550
+ class ContractWrapped1155OnGnosisChain(
551
+ ContractWrapped1155BaseClass, ContractERC20OnGnosisChain
552
+ ):
553
+ pass
554
+
555
+
536
556
  class ContractERC4626OnGnosisChain(
537
557
  ContractERC4626BaseClass, ContractERC20OnGnosisChain
538
558
  ):
@@ -626,6 +646,11 @@ def contract_implements_function(
626
646
  function_name=function_name,
627
647
  web3=web3,
628
648
  function_arg_types=function_arg_types,
649
+ ) or seer_minimal_proxy_implements_function(
650
+ contract_address=contract_address,
651
+ function_name=function_name,
652
+ web3=web3,
653
+ function_arg_types=function_arg_types,
629
654
  )
630
655
 
631
656
  return implements
@@ -654,6 +679,31 @@ def minimal_proxy_implements_function(
654
679
  return False
655
680
 
656
681
 
682
+ def seer_minimal_proxy_implements_function(
683
+ contract_address: ChecksumAddress,
684
+ function_name: str,
685
+ web3: Web3,
686
+ function_arg_types: list[str] | None = None,
687
+ ) -> bool:
688
+ try:
689
+ # Read address between specific indices to find logic contract
690
+ bytecode = web3.eth.get_code(contract_address)
691
+ logic_contract_address = bytecode[11:31]
692
+ if not Web3.is_address(logic_contract_address):
693
+ return False
694
+
695
+ return contract_implements_function(
696
+ Web3.to_checksum_address(logic_contract_address),
697
+ function_name=function_name,
698
+ web3=web3,
699
+ function_arg_types=function_arg_types,
700
+ look_for_proxy_contract=False,
701
+ )
702
+ except DecodingError:
703
+ logger.info("Error decoding contract address on seer minimal proxy")
704
+ return False
705
+
706
+
657
707
  def init_collateral_token_contract(
658
708
  address: ChecksumAddress, web3: Web3 | None
659
709
  ) -> ContractERC20BaseClass:
@@ -673,6 +723,13 @@ def init_collateral_token_contract(
673
723
  ):
674
724
  return ContractDepositableWrapperERC20BaseClass(address=address)
675
725
 
726
+ elif contract_implements_function(
727
+ address,
728
+ "multiToken",
729
+ web3=web3,
730
+ ):
731
+ return ContractWrapped1155BaseClass(address=address)
732
+
676
733
  elif contract_implements_function(
677
734
  address,
678
735
  "balanceOf",
@@ -694,6 +751,8 @@ def to_gnosis_chain_contract(
694
751
  return ContractERC4626OnGnosisChain(address=contract.address)
695
752
  elif isinstance(contract, ContractDepositableWrapperERC20BaseClass):
696
753
  return ContractDepositableWrapperERC20OnGnosisChain(address=contract.address)
754
+ elif isinstance(contract, ContractWrapped1155BaseClass):
755
+ return ContractWrapped1155OnGnosisChain(address=contract.address)
697
756
  elif isinstance(contract, ContractERC20BaseClass):
698
757
  return ContractERC20OnGnosisChain(address=contract.address)
699
758
  else:
@@ -167,10 +167,13 @@ def handle_allowance(
167
167
  api_keys: APIKeys,
168
168
  sell_token: ChecksumAddress,
169
169
  amount_wei: Wei,
170
+ for_address: ChecksumAddress | None = None,
170
171
  web3: Web3 | None = None,
171
172
  ) -> None:
172
173
  # Approve the CoW Swap Vault Relayer to get the sell token only if allowance not sufficient.
173
- for_address = Web3.to_checksum_address(CowContractAddress.VAULT_RELAYER.value)
174
+ for_address = for_address or Web3.to_checksum_address(
175
+ CowContractAddress.VAULT_RELAYER.value
176
+ )
174
177
  current_allowance = ContractERC20OnGnosisChain(address=sell_token).allowance(
175
178
  owner=api_keys.bet_from_address,
176
179
  for_address=for_address,
@@ -0,0 +1,71 @@
1
+ import tenacity
2
+
3
+ from prediction_market_agent_tooling.config import APIKeys
4
+ from prediction_market_agent_tooling.tools.caches.db_cache import db_cache
5
+ from prediction_market_agent_tooling.tools.langfuse_ import (
6
+ get_langfuse_langchain_config,
7
+ observe,
8
+ )
9
+ from prediction_market_agent_tooling.tools.utils import (
10
+ LLM_SEED,
11
+ LLM_SUPER_LOW_TEMPERATURE,
12
+ )
13
+
14
+ REPHRASE_QUESTION_PROMPT = """Given the following question of main interest: {question}
15
+
16
+ But it's conditioned on `{parent_question}` resolving to `{needed_parent_outcome}`.
17
+
18
+ Rewrite the main question to contain the parent question in the correct form.
19
+
20
+ The main question will be used as a prediction market, so it does need to be rephrased using the parent question properly. Such that the probability of the main question also accounts for the conditioned outcome.
21
+
22
+ For example:
23
+ ```
24
+ Main question: What is the probability of <X> happening before <date>?
25
+ Conditioned on: Will <Y> happen before <another-date>?
26
+ Rephrased: What is the joint probability of Y happening before <another-date> and then X happening before <date>?
27
+ ```
28
+
29
+ Output only the rephrased question.
30
+ """
31
+
32
+
33
+ @tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_fixed(1))
34
+ @observe()
35
+ @db_cache
36
+ def rephrase_question_to_unconditional(
37
+ question: str,
38
+ parent_question: str,
39
+ needed_parent_outcome: str,
40
+ engine: str = "gpt-4.1",
41
+ temperature: float = LLM_SUPER_LOW_TEMPERATURE,
42
+ seed: int = LLM_SEED,
43
+ prompt_template: str = REPHRASE_QUESTION_PROMPT,
44
+ max_tokens: int = 1024,
45
+ ) -> str:
46
+ try:
47
+ from langchain.prompts import ChatPromptTemplate
48
+ from langchain_openai import ChatOpenAI
49
+ except ImportError:
50
+ raise ImportError("langchain not installed")
51
+
52
+ llm = ChatOpenAI(
53
+ model_name=engine,
54
+ temperature=temperature,
55
+ seed=seed,
56
+ openai_api_key=APIKeys().openai_api_key,
57
+ )
58
+
59
+ prompt = ChatPromptTemplate.from_template(template=prompt_template)
60
+ messages = prompt.format_messages(
61
+ question=question,
62
+ parent_question=parent_question,
63
+ needed_parent_outcome=needed_parent_outcome,
64
+ )
65
+ completion = str(
66
+ llm.invoke(
67
+ messages, max_tokens=max_tokens, config=get_langfuse_langchain_config()
68
+ ).content
69
+ )
70
+
71
+ return completion
@@ -3,14 +3,22 @@ from web3 import Web3
3
3
  from prediction_market_agent_tooling.config import APIKeys
4
4
  from prediction_market_agent_tooling.gtypes import USD, Wei
5
5
  from prediction_market_agent_tooling.loggers import logger
6
+ from prediction_market_agent_tooling.markets.seer.seer_contracts import GnosisRouter
7
+ from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
8
+ SeerSubgraphHandler,
9
+ )
6
10
  from prediction_market_agent_tooling.tools.contract import (
7
11
  ContractDepositableWrapperERC20BaseClass,
8
12
  ContractERC20BaseClass,
9
13
  ContractERC20OnGnosisChain,
10
14
  ContractERC4626BaseClass,
15
+ ContractWrapped1155BaseClass,
16
+ init_collateral_token_contract,
17
+ to_gnosis_chain_contract,
11
18
  )
12
19
  from prediction_market_agent_tooling.tools.cow.cow_order import (
13
20
  get_sell_token_amount,
21
+ handle_allowance,
14
22
  swap_tokens_waiting,
15
23
  )
16
24
  from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN
@@ -49,6 +57,9 @@ def auto_deposit_collateral_token(
49
57
  collateral_token_contract, collateral_amount_wei, api_keys, web3
50
58
  )
51
59
 
60
+ elif isinstance(collateral_token_contract, ContractWrapped1155BaseClass):
61
+ mint_full_set(collateral_token_contract, collateral_amount_wei, api_keys, web3)
62
+
52
63
  elif isinstance(collateral_token_contract, ContractERC20BaseClass):
53
64
  auto_deposit_erc20(
54
65
  collateral_token_contract, collateral_amount_wei, api_keys, web3
@@ -170,3 +181,49 @@ def auto_deposit_erc20(
170
181
  web3=web3,
171
182
  slippage_tolerance=slippage_tolerance,
172
183
  )
184
+
185
+
186
+ def mint_full_set(
187
+ collateral_token_contract: ContractERC20BaseClass,
188
+ collateral_amount_wei: Wei,
189
+ api_keys: APIKeys,
190
+ web3: Web3 | None,
191
+ ) -> None:
192
+ router = GnosisRouter()
193
+ # We need to fetch the parent's market collateral token, to split it and get the collateral token
194
+ # of the child market.
195
+ seer_subgraph_handler = SeerSubgraphHandler()
196
+ market = seer_subgraph_handler.get_market_by_wrapped_token(
197
+ token=collateral_token_contract.address
198
+ )
199
+ market_collateral_token = Web3.to_checksum_address(market.collateral_token)
200
+
201
+ balance_market_collateral = ContractERC20OnGnosisChain(
202
+ address=market_collateral_token
203
+ ).balanceOf(for_address=api_keys.bet_from_address, web3=web3)
204
+ if balance_market_collateral < collateral_amount_wei:
205
+ logger.debug(
206
+ f"Not enough collateral token in the market. Expected {collateral_amount_wei} but got {balance_market_collateral}. Auto-depositing market collateral."
207
+ )
208
+ market_collateral_token_contract = to_gnosis_chain_contract(
209
+ init_collateral_token_contract(market_collateral_token, web3=web3)
210
+ )
211
+ auto_deposit_collateral_token(
212
+ market_collateral_token_contract, collateral_amount_wei, api_keys, web3
213
+ )
214
+
215
+ handle_allowance(
216
+ api_keys=api_keys,
217
+ sell_token=market_collateral_token,
218
+ amount_wei=collateral_amount_wei,
219
+ for_address=router.address,
220
+ web3=web3,
221
+ )
222
+
223
+ router.split_position(
224
+ api_keys=api_keys,
225
+ collateral_token=market_collateral_token,
226
+ market_id=Web3.to_checksum_address(market.id),
227
+ amount=collateral_amount_wei,
228
+ web3=web3,
229
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.67.1
3
+ Version: 0.67.3
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.13
@@ -42,6 +42,7 @@ Requires-Dist: proto-plus (>=1.0.0,<2.0.0)
42
42
  Requires-Dist: protobuf (>=5.0.0,<6.0.0)
43
43
  Requires-Dist: psycopg2-binary (>=2.9.9,<3.0.0)
44
44
  Requires-Dist: pydantic (>=2.6.1,<3.0.0)
45
+ Requires-Dist: pydantic-ai (>=0.1.9,<1.0.0)
45
46
  Requires-Dist: pydantic-settings (>=2.4.0,<3.0.0)
46
47
  Requires-Dist: pymongo (>=4.8.0,<5.0.0)
47
48
  Requires-Dist: pytest-postgresql (>=6.1.1,<7.0.0)