prediction-market-agent-tooling 0.67.2__py3-none-any.whl → 0.67.4__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 (30) hide show
  1. prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
  2. prediction_market_agent_tooling/deploy/agent.py +57 -51
  3. prediction_market_agent_tooling/deploy/betting_strategy.py +8 -5
  4. prediction_market_agent_tooling/markets/agent_market.py +24 -1
  5. prediction_market_agent_tooling/markets/blockchain_utils.py +5 -3
  6. prediction_market_agent_tooling/markets/data_models.py +23 -3
  7. prediction_market_agent_tooling/markets/manifold/manifold.py +2 -1
  8. prediction_market_agent_tooling/markets/metaculus/metaculus.py +2 -1
  9. prediction_market_agent_tooling/markets/omen/omen.py +2 -1
  10. prediction_market_agent_tooling/markets/polymarket/api.py +9 -3
  11. prediction_market_agent_tooling/markets/polymarket/data_models.py +5 -3
  12. prediction_market_agent_tooling/markets/polymarket/polymarket.py +17 -8
  13. prediction_market_agent_tooling/markets/seer/data_models.py +25 -1
  14. prediction_market_agent_tooling/markets/seer/seer.py +85 -26
  15. prediction_market_agent_tooling/markets/seer/seer_contracts.py +18 -0
  16. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +122 -18
  17. prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +4 -1
  18. prediction_market_agent_tooling/tools/contract.py +59 -0
  19. prediction_market_agent_tooling/tools/cow/cow_order.py +4 -1
  20. prediction_market_agent_tooling/tools/hexbytes_custom.py +9 -0
  21. prediction_market_agent_tooling/tools/httpx_cached_client.py +5 -3
  22. prediction_market_agent_tooling/tools/langfuse_client_utils.py +4 -0
  23. prediction_market_agent_tooling/tools/rephrase.py +1 -1
  24. prediction_market_agent_tooling/tools/singleton.py +11 -6
  25. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +57 -0
  26. {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.4.dist-info}/METADATA +1 -1
  27. {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.4.dist-info}/RECORD +30 -29
  28. {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.4.dist-info}/LICENSE +0 -0
  29. {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.4.dist-info}/WHEEL +0 -0
  30. {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.4.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,6 +16,7 @@ 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
21
  QuestionType,
20
22
  SortBy,
@@ -22,9 +24,14 @@ from prediction_market_agent_tooling.markets.agent_market import (
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
 
@@ -85,12 +92,6 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
85
92
  markets_field.upperBound,
86
93
  markets_field.lowerBound,
87
94
  markets_field.templateId,
88
- # TODO: On the Subgraph, `questions` field is a kind of sub-query, instead of a classic list of values.
89
- # See how it is shown on their UI: https://thegraph.com/explorer/subgraphs/B4vyRqJaSHD8dRDb3BFRoAzuBK18c1QQcXq94JbxDxWH?view=Query&chain=arbitrum-one.
90
- # And that doesn't work with subgrounds.
91
- # markets_field.questions.question.id,
92
- # markets_field.questions.question.finalize_ts,
93
- # markets_field.questions.question.best_answer,
94
95
  ]
95
96
  if current_level < max_level:
96
97
  fields.extend(
@@ -132,8 +133,8 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
132
133
  def _build_where_statements(
133
134
  filter_by: FilterBy,
134
135
  outcome_supply_gt_if_open: Wei,
135
- include_conditional_markets: bool = False,
136
136
  question_type: QuestionType = QuestionType.ALL,
137
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
137
138
  parent_market_id: HexBytes | None = None,
138
139
  ) -> dict[Any, Any]:
139
140
  now = to_int_timestamp(utcnow())
@@ -153,9 +154,6 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
153
154
  case _:
154
155
  raise ValueError(f"Unknown filter {filter_by}")
155
156
 
156
- if not include_conditional_markets:
157
- and_stms["parentMarket"] = ADDRESS_ZERO.lower()
158
-
159
157
  if parent_market_id:
160
158
  and_stms["parentMarket"] = parent_market_id.hex().lower()
161
159
 
@@ -196,9 +194,21 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
196
194
  }
197
195
  )
198
196
 
199
- # 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
+ )
200
210
 
201
- all_filters = [and_stms] + outcome_filters if and_stms else outcome_filters
211
+ all_filters = outcome_filters + [and_stms, conditional_filter]
202
212
  where_stms: dict[str, t.Any] = {"and": all_filters}
203
213
  return where_stms
204
214
 
@@ -235,9 +245,9 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
235
245
  limit: int | None = None,
236
246
  outcome_supply_gt_if_open: Wei = Wei(0),
237
247
  question_type: QuestionType = QuestionType.ALL,
238
- include_conditional_markets: bool = False,
248
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
239
249
  parent_market_id: HexBytes | None = None,
240
- ) -> list[SeerMarket]:
250
+ ) -> list[SeerMarketWithQuestions]:
241
251
  sort_direction, sort_by_field = self._build_sort_params(sort_by)
242
252
 
243
253
  where_stms = self._build_where_statements(
@@ -245,7 +255,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
245
255
  outcome_supply_gt_if_open=outcome_supply_gt_if_open,
246
256
  parent_market_id=parent_market_id,
247
257
  question_type=question_type,
248
- include_conditional_markets=include_conditional_markets,
258
+ conditional_filter_type=conditional_filter_type,
249
259
  )
250
260
 
251
261
  # These values can not be set to `None`, but they can be omitted.
@@ -264,12 +274,65 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
264
274
  )
265
275
  fields = self._get_fields_for_markets(markets_field)
266
276
  markets = self.do_query(fields=fields, pydantic_model=SeerMarket)
267
- 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
+ ]
268
292
 
269
- def get_market_by_id(self, market_id: HexBytes) -> SeerMarket:
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
303
+
304
+ def get_market_by_id(self, market_id: HexBytes) -> SeerMarketWithQuestions:
270
305
  markets_field = self.seer_subgraph.Query.market(id=market_id.hex().lower())
271
306
  fields = self._get_fields_for_markets(markets_field)
272
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)
273
336
  if len(markets) != 1:
274
337
  raise ValueError(
275
338
  f"Fetched wrong number of markets. Expected 1 but got {len(markets)}"
@@ -328,3 +391,44 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
328
391
  # We select the first one
329
392
  return pools[0]
330
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
@@ -71,7 +71,10 @@ class SwapPoolHandler:
71
71
  price_outcome_token = PriceManager.build(
72
72
  HexBytes(HexStr(self.market_id))
73
73
  ).get_token_price_from_pools(token=outcome_token)
74
- if not price_outcome_token:
74
+ if (
75
+ not price_outcome_token
76
+ or not price_outcome_token.priceOfCollateralInAskingToken
77
+ ):
75
78
  raise ValueError(
76
79
  f"Could not find price for {outcome_token=} and {self.collateral_token_address}"
77
80
  )
@@ -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,
@@ -60,6 +60,15 @@ 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
71
+
63
72
  @classmethod
64
73
  def __eth_pydantic_validate__(
65
74
  cls, value: t.Any, info: ValidationInfo | None = None
@@ -1,15 +1,17 @@
1
+ from datetime import timedelta
2
+
1
3
  import hishel
2
4
  import httpx
3
5
 
4
6
  from prediction_market_agent_tooling.tools.singleton import SingletonMeta
5
7
 
6
- ONE_DAY_IN_SECONDS = 60 * 60 * 24
8
+ ONE_DAY = timedelta(days=1)
7
9
 
8
10
 
9
11
  class HttpxCachedClient(metaclass=SingletonMeta):
10
- def __init__(self, ttl: int = ONE_DAY_IN_SECONDS) -> None:
12
+ def __init__(self, ttl: timedelta = ONE_DAY) -> None:
11
13
  storage = hishel.FileStorage(
12
- ttl=ttl,
14
+ ttl=ttl.total_seconds(),
13
15
  check_ttl_every=60,
14
16
  )
15
17
  controller = hishel.Controller(force_cache=True)
@@ -68,6 +68,7 @@ def get_traces_for_agent(
68
68
  client: Langfuse,
69
69
  to_timestamp: DatetimeUTC | None = None,
70
70
  tags: str | list[str] | None = None,
71
+ limit: int | None = None,
71
72
  ) -> list[TraceWithDetails]:
72
73
  """
73
74
  Fetch agent traces using pagination
@@ -98,6 +99,9 @@ def get_traces_for_agent(
98
99
  if has_output:
99
100
  agent_traces = [t for t in agent_traces if t.output is not None]
100
101
  all_agent_traces.extend(agent_traces)
102
+ if limit is not None and len(all_agent_traces) >= limit:
103
+ all_agent_traces = all_agent_traces[:limit]
104
+ break
101
105
  return all_agent_traces
102
106
 
103
107
 
@@ -33,7 +33,7 @@ Output only the rephrased question.
33
33
  @tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_fixed(1))
34
34
  @observe()
35
35
  @db_cache
36
- def rephrase_question_to_unconditioned(
36
+ def rephrase_question_to_unconditional(
37
37
  question: str,
38
38
  parent_question: str,
39
39
  needed_parent_outcome: str,
@@ -8,16 +8,21 @@ class SingletonMeta(type, t.Generic[_T]):
8
8
  The Singleton class can be implemented in different ways in Python. Some
9
9
  possible methods include: base class, decorator, metaclass. We will use the
10
10
  metaclass because it is best suited for this purpose.
11
+
12
+ This version creates a unique instance for each unique set of __init__ arguments.
11
13
  """
12
14
 
13
- _instances: dict[t.Any, _T] = {}
15
+ _instances: dict[
16
+ tuple[t.Any, tuple[t.Any, ...], tuple[tuple[str, t.Any], ...]], _T
17
+ ] = {}
14
18
 
15
19
  def __call__(self, *args: t.Any, **kwargs: t.Any) -> _T:
16
20
  """
17
- Possible changes to the value of the `__init__` argument do not affect
18
- the returned instance.
21
+ Different __init__ arguments will result in different instances.
19
22
  """
20
- if self not in self._instances:
23
+ # Create a key based on the class, args, and kwargs (sorted for consistency)
24
+ key = (self, args, tuple(sorted(kwargs.items())))
25
+ if key not in self._instances:
21
26
  instance = super().__call__(*args, **kwargs)
22
- self._instances[self] = instance
23
- return self._instances[self]
27
+ self._instances[key] = instance
28
+ return self._instances[key]
@@ -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.2
3
+ Version: 0.67.4
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.13