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.
- prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
- prediction_market_agent_tooling/deploy/agent.py +109 -47
- prediction_market_agent_tooling/deploy/betting_strategy.py +1 -1
- prediction_market_agent_tooling/markets/agent_market.py +16 -3
- prediction_market_agent_tooling/markets/manifold/manifold.py +4 -3
- prediction_market_agent_tooling/markets/markets.py +6 -5
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +4 -3
- prediction_market_agent_tooling/markets/omen/omen.py +5 -4
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +5 -4
- prediction_market_agent_tooling/markets/seer/data_models.py +30 -8
- prediction_market_agent_tooling/markets/seer/seer.py +79 -20
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +18 -0
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +149 -20
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +0 -4
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +0 -10
- prediction_market_agent_tooling/tools/contract.py +59 -0
- prediction_market_agent_tooling/tools/cow/cow_order.py +4 -1
- prediction_market_agent_tooling/tools/rephrase.py +71 -0
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +57 -0
- {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/METADATA +2 -1
- {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/RECORD +24 -22
- {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/WHEEL +0 -0
- {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
|
-
|
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
|
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(
|
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
|
-
|
117
|
-
|
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
|
137
|
-
and_stms["parentMarket"] =
|
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
|
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
|
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
|
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
|
-
#
|
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 =
|
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
|
-
|
215
|
-
|
216
|
-
|
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
|
-
|
223
|
-
|
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
|
-
|
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) ->
|
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(
|
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.
|
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)
|