prediction-market-agent-tooling 0.67.2__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 +57 -51
- prediction_market_agent_tooling/deploy/betting_strategy.py +1 -1
- prediction_market_agent_tooling/markets/agent_market.py +7 -1
- prediction_market_agent_tooling/markets/manifold/manifold.py +2 -1
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +2 -1
- prediction_market_agent_tooling/markets/omen/omen.py +2 -1
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +2 -1
- prediction_market_agent_tooling/markets/seer/data_models.py +25 -1
- prediction_market_agent_tooling/markets/seer/seer.py +74 -24
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +18 -0
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +122 -18
- 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 +1 -1
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +57 -0
- {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/RECORD +21 -20
- {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/entry_points.txt +0 -0
@@ -48,10 +48,14 @@ class CreateCategoricalMarketsParams(BaseModel):
|
|
48
48
|
SEER_BASE_URL = "https://app.seer.pm"
|
49
49
|
|
50
50
|
|
51
|
-
def seer_normalize_wei(value: int | None) -> int | None:
|
51
|
+
def seer_normalize_wei(value: int | dict[str, t.Any] | None) -> int | None:
|
52
52
|
# See https://github.com/seer-pm/demo/blob/main/web/netlify/edge-functions/utils/common.ts#L22
|
53
53
|
if value is None:
|
54
54
|
return value
|
55
|
+
elif isinstance(value, dict):
|
56
|
+
if value.get("value") is None:
|
57
|
+
raise ValueError(f"Expected a dictionary with a value key, but got {value}")
|
58
|
+
value = int(value["value"])
|
55
59
|
is_in_wei = value > 1e10
|
56
60
|
return value if is_in_wei else value * 10**18
|
57
61
|
|
@@ -59,6 +63,21 @@ def seer_normalize_wei(value: int | None) -> int | None:
|
|
59
63
|
SeerNormalizedWei = Annotated[Wei | None, BeforeValidator(seer_normalize_wei)]
|
60
64
|
|
61
65
|
|
66
|
+
class MarketId(BaseModel):
|
67
|
+
id: HexBytes
|
68
|
+
|
69
|
+
|
70
|
+
class SeerQuestion(BaseModel):
|
71
|
+
id: str
|
72
|
+
best_answer: HexBytes
|
73
|
+
finalize_ts: int
|
74
|
+
|
75
|
+
|
76
|
+
class SeerMarketQuestions(BaseModel):
|
77
|
+
question: SeerQuestion
|
78
|
+
market: MarketId
|
79
|
+
|
80
|
+
|
62
81
|
class SeerMarket(BaseModel):
|
63
82
|
model_config = ConfigDict(populate_by_name=True)
|
64
83
|
|
@@ -71,6 +90,7 @@ class SeerMarket(BaseModel):
|
|
71
90
|
alias="parentOutcome", description="It comes as 0 from non-conditioned markets."
|
72
91
|
)
|
73
92
|
parent_market: t.Optional["SeerMarket"] = Field(alias="parentMarket", default=None)
|
93
|
+
template_id: int = Field(alias="templateId")
|
74
94
|
collateral_token: HexAddress = Field(alias="collateralToken")
|
75
95
|
condition_id: HexBytes = Field(alias="conditionId")
|
76
96
|
opening_ts: int = Field(alias="openingTs")
|
@@ -143,6 +163,10 @@ class SeerMarket(BaseModel):
|
|
143
163
|
return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.hex()}")
|
144
164
|
|
145
165
|
|
166
|
+
class SeerMarketWithQuestions(SeerMarket):
|
167
|
+
questions: list[SeerMarketQuestions]
|
168
|
+
|
169
|
+
|
146
170
|
class RedeemParams(BaseModel):
|
147
171
|
model_config = ConfigDict(populate_by_name=True)
|
148
172
|
market: ChecksumAddress
|
@@ -23,8 +23,8 @@ from prediction_market_agent_tooling.gtypes import (
|
|
23
23
|
from prediction_market_agent_tooling.loggers import logger
|
24
24
|
from prediction_market_agent_tooling.markets.agent_market import (
|
25
25
|
AgentMarket,
|
26
|
+
ConditionalFilterType,
|
26
27
|
FilterBy,
|
27
|
-
MarketFees,
|
28
28
|
ParentMarket,
|
29
29
|
ProcessedMarket,
|
30
30
|
ProcessedTradedMarket,
|
@@ -32,7 +32,10 @@ from prediction_market_agent_tooling.markets.agent_market import (
|
|
32
32
|
SortBy,
|
33
33
|
)
|
34
34
|
from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
|
35
|
-
from prediction_market_agent_tooling.markets.data_models import
|
35
|
+
from prediction_market_agent_tooling.markets.data_models import (
|
36
|
+
ExistingPosition,
|
37
|
+
Resolution,
|
38
|
+
)
|
36
39
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
37
40
|
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
38
41
|
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
@@ -44,6 +47,7 @@ from prediction_market_agent_tooling.markets.omen.omen_contracts import (
|
|
44
47
|
from prediction_market_agent_tooling.markets.seer.data_models import (
|
45
48
|
RedeemParams,
|
46
49
|
SeerMarket,
|
50
|
+
SeerMarketWithQuestions,
|
47
51
|
)
|
48
52
|
from prediction_market_agent_tooling.markets.seer.exceptions import (
|
49
53
|
PriceCalculationError,
|
@@ -54,7 +58,9 @@ from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
|
54
58
|
SeerMarketFactory,
|
55
59
|
)
|
56
60
|
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
61
|
+
SeerQuestionsCache,
|
57
62
|
SeerSubgraphHandler,
|
63
|
+
TemplateId,
|
58
64
|
)
|
59
65
|
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
|
60
66
|
NewMarketEvent,
|
@@ -257,7 +263,7 @@ class SeerAgentMarket(AgentMarket):
|
|
257
263
|
@staticmethod
|
258
264
|
def _filter_markets_contained_in_trades(
|
259
265
|
api_keys: APIKeys,
|
260
|
-
markets:
|
266
|
+
markets: t.Sequence[SeerMarket],
|
261
267
|
) -> list[SeerMarket]:
|
262
268
|
"""
|
263
269
|
We filter the markets using previous trades by the user so that we don't have to process all Seer markets.
|
@@ -267,7 +273,7 @@ class SeerAgentMarket(AgentMarket):
|
|
267
273
|
traded_tokens = {t.buyToken for t in trades_by_user}.union(
|
268
274
|
[t.sellToken for t in trades_by_user]
|
269
275
|
)
|
270
|
-
filtered_markets = []
|
276
|
+
filtered_markets: list[SeerMarket] = []
|
271
277
|
for market in markets:
|
272
278
|
if any(
|
273
279
|
[
|
@@ -338,9 +344,64 @@ class SeerAgentMarket(AgentMarket):
|
|
338
344
|
return OmenAgentMarket.verify_operational_balance(api_keys=api_keys)
|
339
345
|
|
340
346
|
@staticmethod
|
341
|
-
def
|
347
|
+
def build_resolution(
|
348
|
+
model: SeerMarketWithQuestions,
|
349
|
+
) -> Resolution | None:
|
350
|
+
if model.questions[0].question.finalize_ts == 0:
|
351
|
+
# resolution not yet finalized
|
352
|
+
return None
|
353
|
+
|
354
|
+
if model.template_id != TemplateId.CATEGORICAL:
|
355
|
+
logger.warning("Resolution can only be built for categorical markets.")
|
356
|
+
# Future note - for scalar markets, simply fetch best_answer and convert
|
357
|
+
# from hex into int and divide by 1e18 (because Wei).
|
358
|
+
return None
|
359
|
+
|
360
|
+
if len(model.questions) != 1:
|
361
|
+
raise ValueError("Seer categorical markets must have 1 question.")
|
362
|
+
|
363
|
+
question = model.questions[0]
|
364
|
+
outcome = model.outcomes[int(question.question.best_answer.hex(), 16)]
|
365
|
+
return Resolution(outcome=outcome, invalid=False)
|
366
|
+
|
367
|
+
@staticmethod
|
368
|
+
def convert_seer_market_into_market_with_questions(
|
369
|
+
seer_market: SeerMarket, seer_subgraph: SeerSubgraphHandler
|
370
|
+
) -> "SeerMarketWithQuestions":
|
371
|
+
q = SeerQuestionsCache(seer_subgraph_handler=seer_subgraph)
|
372
|
+
q.fetch_questions([seer_market.id])
|
373
|
+
questions = q.market_id_to_questions[seer_market.id]
|
374
|
+
return SeerMarketWithQuestions(**seer_market.model_dump(), questions=questions)
|
375
|
+
|
376
|
+
@staticmethod
|
377
|
+
def get_parent(
|
342
378
|
model: SeerMarket,
|
343
379
|
seer_subgraph: SeerSubgraphHandler,
|
380
|
+
) -> t.Optional["ParentMarket"]:
|
381
|
+
if not model.parent_market:
|
382
|
+
return None
|
383
|
+
|
384
|
+
# turn into a market with questions
|
385
|
+
parent_market_with_questions = (
|
386
|
+
SeerAgentMarket.convert_seer_market_into_market_with_questions(
|
387
|
+
model.parent_market, seer_subgraph=seer_subgraph
|
388
|
+
)
|
389
|
+
)
|
390
|
+
|
391
|
+
market_with_questions = check_not_none(
|
392
|
+
SeerAgentMarket.from_data_model_with_subgraph(
|
393
|
+
parent_market_with_questions, seer_subgraph, False
|
394
|
+
)
|
395
|
+
)
|
396
|
+
|
397
|
+
return ParentMarket(
|
398
|
+
market=market_with_questions, parent_outcome=model.parent_outcome
|
399
|
+
)
|
400
|
+
|
401
|
+
@staticmethod
|
402
|
+
def from_data_model_with_subgraph(
|
403
|
+
model: SeerMarketWithQuestions,
|
404
|
+
seer_subgraph: SeerSubgraphHandler,
|
344
405
|
must_have_prices: bool,
|
345
406
|
) -> t.Optional["SeerAgentMarket"]:
|
346
407
|
price_manager = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
|
@@ -356,6 +417,10 @@ class SeerAgentMarket(AgentMarket):
|
|
356
417
|
# Price calculation failed, so don't return the market
|
357
418
|
return None
|
358
419
|
|
420
|
+
resolution = SeerAgentMarket.build_resolution(model=model)
|
421
|
+
|
422
|
+
parent = SeerAgentMarket.get_parent(model=model, seer_subgraph=seer_subgraph)
|
423
|
+
|
359
424
|
market = SeerAgentMarket(
|
360
425
|
id=model.id.hex(),
|
361
426
|
question=model.title,
|
@@ -370,27 +435,12 @@ class SeerAgentMarket(AgentMarket):
|
|
370
435
|
fees=MarketFees.get_zero_fees(),
|
371
436
|
outcome_token_pool=None,
|
372
437
|
outcomes_supply=model.outcomes_supply,
|
373
|
-
resolution=
|
438
|
+
resolution=resolution,
|
374
439
|
volume=None,
|
375
440
|
probabilities=probability_map,
|
376
441
|
upper_bound=model.upper_bound,
|
377
442
|
lower_bound=model.lower_bound,
|
378
|
-
parent=
|
379
|
-
ParentMarket(
|
380
|
-
market=(
|
381
|
-
check_not_none(
|
382
|
-
SeerAgentMarket.from_data_model_with_subgraph(
|
383
|
-
model.parent_market,
|
384
|
-
seer_subgraph,
|
385
|
-
False,
|
386
|
-
)
|
387
|
-
)
|
388
|
-
),
|
389
|
-
parent_outcome=model.parent_outcome,
|
390
|
-
)
|
391
|
-
if model.parent_market
|
392
|
-
else None
|
393
|
-
),
|
443
|
+
parent=parent,
|
394
444
|
)
|
395
445
|
|
396
446
|
return market
|
@@ -403,7 +453,7 @@ class SeerAgentMarket(AgentMarket):
|
|
403
453
|
created_after: t.Optional[DatetimeUTC] = None,
|
404
454
|
excluded_questions: set[str] | None = None,
|
405
455
|
question_type: QuestionType = QuestionType.ALL,
|
406
|
-
|
456
|
+
conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
|
407
457
|
) -> t.Sequence["SeerAgentMarket"]:
|
408
458
|
seer_subgraph = SeerSubgraphHandler()
|
409
459
|
|
@@ -412,7 +462,7 @@ class SeerAgentMarket(AgentMarket):
|
|
412
462
|
sort_by=sort_by,
|
413
463
|
filter_by=filter_by,
|
414
464
|
question_type=question_type,
|
415
|
-
|
465
|
+
conditional_filter_type=conditional_filter_type,
|
416
466
|
)
|
417
467
|
|
418
468
|
# We exclude the None values below because `from_data_model_with_subgraph` can return None, which
|
@@ -9,6 +9,7 @@ from prediction_market_agent_tooling.gtypes import (
|
|
9
9
|
ChecksumAddress,
|
10
10
|
OutcomeStr,
|
11
11
|
TxReceipt,
|
12
|
+
Wei,
|
12
13
|
xDai,
|
13
14
|
)
|
14
15
|
from prediction_market_agent_tooling.markets.seer.data_models import (
|
@@ -115,6 +116,23 @@ class GnosisRouter(ContractOnGnosisChain):
|
|
115
116
|
)
|
116
117
|
return receipt_tx
|
117
118
|
|
119
|
+
def split_position(
|
120
|
+
self,
|
121
|
+
api_keys: APIKeys,
|
122
|
+
collateral_token: ChecksumAddress,
|
123
|
+
market_id: ChecksumAddress,
|
124
|
+
amount: Wei,
|
125
|
+
web3: Web3 | None = None,
|
126
|
+
) -> TxReceipt:
|
127
|
+
"""Splits collateral token into full set of outcome tokens."""
|
128
|
+
receipt_tx = self.send(
|
129
|
+
api_keys=api_keys,
|
130
|
+
function_name="splitPosition",
|
131
|
+
function_params=[collateral_token, market_id, amount],
|
132
|
+
web3=web3,
|
133
|
+
)
|
134
|
+
return receipt_tx
|
135
|
+
|
118
136
|
|
119
137
|
class SwaprRouterContract(ContractOnGnosisChain):
|
120
138
|
# File content taken from https://github.com/protofire/omen-exchange/blob/master/app/src/abi/marketMaker.json.
|
@@ -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
|
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
|
-
#
|
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 =
|
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
|
-
|
248
|
+
conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
|
239
249
|
parent_market_id: HexBytes | None = None,
|
240
|
-
) -> list[
|
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
|
-
|
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
|
-
|
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
|
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
|
@@ -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,
|
@@ -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
|
36
|
+
def rephrase_question_to_unconditional(
|
37
37
|
question: str,
|
38
38
|
parent_question: str,
|
39
39
|
needed_parent_outcome: str,
|