prediction-market-agent-tooling 0.65.5__py3-none-any.whl → 0.69.17.dev1149__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/agentresultmapping.abi.json +192 -0
- prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
- prediction_market_agent_tooling/abis/processor.abi.json +16 -0
- prediction_market_agent_tooling/abis/swapr_quoter.abi.json +221 -0
- prediction_market_agent_tooling/abis/swapr_router.abi.json +634 -0
- prediction_market_agent_tooling/benchmark/benchmark.py +1 -1
- prediction_market_agent_tooling/benchmark/utils.py +13 -0
- prediction_market_agent_tooling/chains.py +1 -0
- prediction_market_agent_tooling/config.py +61 -2
- prediction_market_agent_tooling/data_download/langfuse_data_downloader.py +405 -0
- prediction_market_agent_tooling/deploy/agent.py +199 -67
- prediction_market_agent_tooling/deploy/agent_example.py +1 -1
- prediction_market_agent_tooling/deploy/betting_strategy.py +412 -68
- prediction_market_agent_tooling/deploy/constants.py +6 -0
- prediction_market_agent_tooling/gtypes.py +11 -1
- prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +19 -20
- prediction_market_agent_tooling/loggers.py +9 -1
- prediction_market_agent_tooling/logprobs_parser.py +2 -1
- prediction_market_agent_tooling/markets/agent_market.py +106 -18
- prediction_market_agent_tooling/markets/blockchain_utils.py +37 -19
- prediction_market_agent_tooling/markets/data_models.py +120 -7
- prediction_market_agent_tooling/markets/manifold/data_models.py +5 -3
- prediction_market_agent_tooling/markets/manifold/manifold.py +21 -2
- prediction_market_agent_tooling/markets/manifold/utils.py +8 -2
- prediction_market_agent_tooling/markets/market_type.py +74 -0
- prediction_market_agent_tooling/markets/markets.py +7 -99
- prediction_market_agent_tooling/markets/metaculus/data_models.py +3 -3
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +5 -8
- prediction_market_agent_tooling/markets/omen/cow_contracts.py +5 -1
- prediction_market_agent_tooling/markets/omen/data_models.py +63 -32
- prediction_market_agent_tooling/markets/omen/omen.py +112 -23
- prediction_market_agent_tooling/markets/omen/omen_constants.py +8 -0
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +18 -203
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +33 -13
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +23 -18
- prediction_market_agent_tooling/markets/polymarket/api.py +123 -100
- prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
- prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
- prediction_market_agent_tooling/markets/polymarket/data_models.py +95 -19
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +373 -29
- prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
- prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +91 -0
- prediction_market_agent_tooling/markets/polymarket/utils.py +1 -22
- prediction_market_agent_tooling/markets/seer/data_models.py +111 -17
- prediction_market_agent_tooling/markets/seer/exceptions.py +2 -0
- prediction_market_agent_tooling/markets/seer/price_manager.py +165 -50
- prediction_market_agent_tooling/markets/seer/seer.py +393 -106
- prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +115 -5
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +297 -66
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +43 -8
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +80 -0
- prediction_market_agent_tooling/tools/_generic_value.py +8 -2
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +271 -8
- prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -1
- prediction_market_agent_tooling/tools/caches/db_cache.py +219 -117
- prediction_market_agent_tooling/tools/caches/serializers.py +11 -2
- prediction_market_agent_tooling/tools/contract.py +480 -38
- prediction_market_agent_tooling/tools/contract_utils.py +61 -0
- prediction_market_agent_tooling/tools/cow/cow_order.py +218 -45
- prediction_market_agent_tooling/tools/cow/models.py +122 -0
- prediction_market_agent_tooling/tools/cow/semaphore.py +104 -0
- prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
- prediction_market_agent_tooling/tools/db/db_manager.py +59 -0
- prediction_market_agent_tooling/tools/hexbytes_custom.py +4 -1
- prediction_market_agent_tooling/tools/httpx_cached_client.py +15 -6
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +21 -8
- prediction_market_agent_tooling/tools/openai_utils.py +31 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_client.py +86 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_models.py +26 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_search.py +73 -0
- prediction_market_agent_tooling/tools/rephrase.py +71 -0
- prediction_market_agent_tooling/tools/singleton.py +11 -6
- prediction_market_agent_tooling/tools/streamlit_utils.py +188 -0
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +64 -0
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
- prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
- prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
- prediction_market_agent_tooling/tools/utils.py +61 -3
- prediction_market_agent_tooling/tools/web3_utils.py +63 -9
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/METADATA +13 -9
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/RECORD +86 -64
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/WHEEL +1 -1
- prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +0 -171
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -420
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/entry_points.txt +0 -0
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import typing as t
|
|
3
|
+
from datetime import timedelta
|
|
2
4
|
|
|
5
|
+
import cachetools
|
|
6
|
+
from cowdao_cowpy.common.api.errors import UnexpectedResponseError
|
|
3
7
|
from eth_typing import ChecksumAddress
|
|
4
8
|
from web3 import Web3
|
|
5
9
|
from web3.types import TxReceipt
|
|
@@ -14,57 +18,96 @@ from prediction_market_agent_tooling.gtypes import (
|
|
|
14
18
|
OutcomeStr,
|
|
15
19
|
OutcomeToken,
|
|
16
20
|
OutcomeWei,
|
|
21
|
+
Wei,
|
|
17
22
|
xDai,
|
|
18
23
|
)
|
|
19
24
|
from prediction_market_agent_tooling.loggers import logger
|
|
20
25
|
from prediction_market_agent_tooling.markets.agent_market import (
|
|
21
26
|
AgentMarket,
|
|
27
|
+
ConditionalFilterType,
|
|
22
28
|
FilterBy,
|
|
29
|
+
ParentMarket,
|
|
23
30
|
ProcessedMarket,
|
|
24
|
-
|
|
31
|
+
QuestionType,
|
|
25
32
|
SortBy,
|
|
26
33
|
)
|
|
27
|
-
from prediction_market_agent_tooling.markets.
|
|
34
|
+
from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
|
|
35
|
+
from prediction_market_agent_tooling.markets.data_models import (
|
|
36
|
+
ExistingPosition,
|
|
37
|
+
Resolution,
|
|
38
|
+
ResolvedBet,
|
|
39
|
+
)
|
|
28
40
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
|
29
|
-
from prediction_market_agent_tooling.markets.omen.omen import
|
|
41
|
+
from prediction_market_agent_tooling.markets.omen.omen import (
|
|
42
|
+
OmenAgentMarket,
|
|
43
|
+
send_keeping_token_to_eoa_xdai,
|
|
44
|
+
)
|
|
45
|
+
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
|
46
|
+
SDAI_CONTRACT_ADDRESS,
|
|
47
|
+
)
|
|
48
|
+
from prediction_market_agent_tooling.markets.omen.omen_contracts import (
|
|
49
|
+
SeerAgentResultMappingContract,
|
|
50
|
+
)
|
|
30
51
|
from prediction_market_agent_tooling.markets.seer.data_models import (
|
|
31
|
-
RedeemParams,
|
|
32
52
|
SeerMarket,
|
|
53
|
+
SeerMarketWithQuestions,
|
|
54
|
+
)
|
|
55
|
+
from prediction_market_agent_tooling.markets.seer.exceptions import (
|
|
56
|
+
PriceCalculationError,
|
|
33
57
|
)
|
|
34
58
|
from prediction_market_agent_tooling.markets.seer.price_manager import PriceManager
|
|
59
|
+
from prediction_market_agent_tooling.markets.seer.seer_api import get_seer_transactions
|
|
35
60
|
from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
|
36
61
|
GnosisRouter,
|
|
37
62
|
SeerMarketFactory,
|
|
38
63
|
)
|
|
39
64
|
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
|
65
|
+
SeerQuestionsCache,
|
|
40
66
|
SeerSubgraphHandler,
|
|
67
|
+
TemplateId,
|
|
41
68
|
)
|
|
42
69
|
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
|
|
43
70
|
NewMarketEvent,
|
|
44
71
|
)
|
|
72
|
+
from prediction_market_agent_tooling.markets.seer.swap_pool_handler import (
|
|
73
|
+
SwapPoolHandler,
|
|
74
|
+
)
|
|
45
75
|
from prediction_market_agent_tooling.tools.contract import (
|
|
46
76
|
ContractERC20OnGnosisChain,
|
|
47
77
|
init_collateral_token_contract,
|
|
48
78
|
to_gnosis_chain_contract,
|
|
49
79
|
)
|
|
50
80
|
from prediction_market_agent_tooling.tools.cow.cow_order import (
|
|
51
|
-
|
|
52
|
-
|
|
81
|
+
NoLiquidityAvailableOnCowException,
|
|
82
|
+
OrderStatusError,
|
|
83
|
+
get_orders_by_owner,
|
|
84
|
+
get_trades_by_order_uid,
|
|
85
|
+
handle_allowance,
|
|
53
86
|
swap_tokens_waiting,
|
|
87
|
+
wait_for_order_completion,
|
|
54
88
|
)
|
|
55
89
|
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
|
56
90
|
from prediction_market_agent_tooling.tools.tokens.auto_deposit import (
|
|
57
91
|
auto_deposit_collateral_token,
|
|
58
92
|
)
|
|
93
|
+
from prediction_market_agent_tooling.tools.tokens.slippage import (
|
|
94
|
+
get_slippage_tolerance_per_token,
|
|
95
|
+
)
|
|
59
96
|
from prediction_market_agent_tooling.tools.tokens.usd import (
|
|
60
97
|
get_token_in_usd,
|
|
61
98
|
get_usd_in_token,
|
|
62
99
|
)
|
|
100
|
+
from prediction_market_agent_tooling.tools.utils import check_not_none, utcnow
|
|
63
101
|
|
|
64
102
|
# We place a larger bet amount by default than Omen so that cow presents valid quotes.
|
|
65
103
|
SEER_TINY_BET_AMOUNT = USD(0.1)
|
|
66
104
|
|
|
67
105
|
|
|
106
|
+
SHARED_CACHE: cachetools.TTLCache[t.Hashable, t.Any] = cachetools.TTLCache(
|
|
107
|
+
maxsize=256, ttl=10 * 60
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
68
111
|
class SeerAgentMarket(AgentMarket):
|
|
69
112
|
wrapped_tokens: list[ChecksumAddress]
|
|
70
113
|
creator: HexAddress
|
|
@@ -74,6 +117,7 @@ class SeerAgentMarket(AgentMarket):
|
|
|
74
117
|
None # Seer markets don't have a description, so just default to None.
|
|
75
118
|
)
|
|
76
119
|
outcomes_supply: int
|
|
120
|
+
minimum_market_liquidity_required: CollateralToken = CollateralToken(1)
|
|
77
121
|
|
|
78
122
|
def get_collateral_token_contract(
|
|
79
123
|
self, web3: Web3 | None = None
|
|
@@ -95,18 +139,53 @@ class SeerAgentMarket(AgentMarket):
|
|
|
95
139
|
|
|
96
140
|
def store_trades(
|
|
97
141
|
self,
|
|
98
|
-
traded_market:
|
|
142
|
+
traded_market: ProcessedMarket | None,
|
|
99
143
|
keys: APIKeys,
|
|
100
144
|
agent_name: str,
|
|
101
145
|
web3: Web3 | None = None,
|
|
102
146
|
) -> None:
|
|
103
|
-
|
|
147
|
+
return store_trades(
|
|
148
|
+
contract=SeerAgentResultMappingContract(),
|
|
149
|
+
market_id=Web3.to_checksum_address(self.id),
|
|
150
|
+
outcomes=self.outcomes,
|
|
151
|
+
traded_market=traded_market,
|
|
152
|
+
keys=keys,
|
|
153
|
+
agent_name=agent_name,
|
|
154
|
+
web3=web3,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def get_price_manager(self) -> PriceManager:
|
|
158
|
+
return PriceManager.build(HexBytes(HexStr(self.id)))
|
|
104
159
|
|
|
105
160
|
def get_token_in_usd(self, x: CollateralToken) -> USD:
|
|
106
|
-
|
|
161
|
+
p = self.get_price_manager()
|
|
162
|
+
# This function is meant to convert market's collateral token into usd value, however, on Seer, market's collateral can be another's market outcome token.
|
|
163
|
+
# That's why we need this middle step.
|
|
164
|
+
sdai_amount = p.get_amount_of_collateral_in_token(
|
|
165
|
+
# Hard-coded SDAI, because Seer is atm hard-coded it as well, and it's needed in case of fallback to pools. CoW would work with other tokens as well.
|
|
166
|
+
SDAI_CONTRACT_ADDRESS,
|
|
167
|
+
x,
|
|
168
|
+
)
|
|
169
|
+
if sdai_amount is None:
|
|
170
|
+
raise RuntimeError(
|
|
171
|
+
"Both CoW and pool-fallback way of getting price failed."
|
|
172
|
+
)
|
|
173
|
+
return get_token_in_usd(sdai_amount.as_token, SDAI_CONTRACT_ADDRESS)
|
|
107
174
|
|
|
108
175
|
def get_usd_in_token(self, x: USD) -> CollateralToken:
|
|
109
|
-
|
|
176
|
+
p = self.get_price_manager()
|
|
177
|
+
# This function is meant to convert market's collateral token into usd value, however, on Seer, market's collateral can be another's market outcome token.
|
|
178
|
+
# That's why we need this middle step.
|
|
179
|
+
token_amount = p.get_amount_of_token_in_collateral(
|
|
180
|
+
# Hard-coded SDAI, because Seer is atm hard-coded it as well, and it's needed in case of fallback to pools. CoW would work with other tokens as well.
|
|
181
|
+
SDAI_CONTRACT_ADDRESS,
|
|
182
|
+
OutcomeToken.from_token(get_usd_in_token(x, SDAI_CONTRACT_ADDRESS)),
|
|
183
|
+
)
|
|
184
|
+
if token_amount is None:
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
"Both CoW and pool-fallback way of getting price failed."
|
|
187
|
+
)
|
|
188
|
+
return token_amount
|
|
110
189
|
|
|
111
190
|
def get_buy_token_amount(
|
|
112
191
|
self, bet_amount: USD | CollateralToken, outcome_str: OutcomeStr
|
|
@@ -122,16 +201,15 @@ class SeerAgentMarket(AgentMarket):
|
|
|
122
201
|
|
|
123
202
|
bet_amount_in_tokens = self.get_in_token(bet_amount)
|
|
124
203
|
|
|
125
|
-
p =
|
|
126
|
-
|
|
204
|
+
p = self.get_price_manager()
|
|
205
|
+
amount_outcome_tokens = p.get_amount_of_collateral_in_token(
|
|
127
206
|
token=outcome_token, collateral_exchange_amount=bet_amount_in_tokens
|
|
128
207
|
)
|
|
129
|
-
if not
|
|
208
|
+
if not amount_outcome_tokens:
|
|
130
209
|
logger.info(f"Could not get price for token {outcome_token}")
|
|
131
210
|
return None
|
|
132
211
|
|
|
133
|
-
amount_outcome_tokens
|
|
134
|
-
return OutcomeToken(amount_outcome_tokens)
|
|
212
|
+
return amount_outcome_tokens
|
|
135
213
|
|
|
136
214
|
def get_sell_value_of_outcome_token(
|
|
137
215
|
self, outcome: OutcomeStr, amount: OutcomeToken
|
|
@@ -141,13 +219,17 @@ class SeerAgentMarket(AgentMarket):
|
|
|
141
219
|
|
|
142
220
|
wrapped_outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
|
143
221
|
|
|
144
|
-
|
|
145
|
-
value_outcome_token_in_collateral =
|
|
146
|
-
|
|
147
|
-
sell_token=wrapped_outcome_token,
|
|
148
|
-
buy_token=self.collateral_token_contract_address_checksummed,
|
|
222
|
+
p = self.get_price_manager()
|
|
223
|
+
value_outcome_token_in_collateral = p.get_amount_of_token_in_collateral(
|
|
224
|
+
wrapped_outcome_token, amount
|
|
149
225
|
)
|
|
150
|
-
|
|
226
|
+
|
|
227
|
+
if value_outcome_token_in_collateral is None:
|
|
228
|
+
raise RuntimeError(
|
|
229
|
+
f"Could not get price for token from pools for {wrapped_outcome_token}"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return value_outcome_token_in_collateral
|
|
151
233
|
|
|
152
234
|
@staticmethod
|
|
153
235
|
def get_trade_balance(api_keys: APIKeys) -> USD:
|
|
@@ -156,9 +238,9 @@ class SeerAgentMarket(AgentMarket):
|
|
|
156
238
|
def get_tiny_bet_amount(self) -> CollateralToken:
|
|
157
239
|
return self.get_in_token(SEER_TINY_BET_AMOUNT)
|
|
158
240
|
|
|
159
|
-
def
|
|
241
|
+
def get_position(
|
|
160
242
|
self, user_id: str, web3: Web3 | None = None
|
|
161
|
-
) -> ExistingPosition:
|
|
243
|
+
) -> ExistingPosition | None:
|
|
162
244
|
"""
|
|
163
245
|
Fetches position from the user in a given market.
|
|
164
246
|
We ignore the INVALID balances since we are only interested in binary outcomes.
|
|
@@ -173,9 +255,11 @@ class SeerAgentMarket(AgentMarket):
|
|
|
173
255
|
)
|
|
174
256
|
)
|
|
175
257
|
|
|
176
|
-
amounts_ot[
|
|
177
|
-
|
|
178
|
-
|
|
258
|
+
amounts_ot[outcome_str] = outcome_token_balance_wei.as_outcome_token
|
|
259
|
+
|
|
260
|
+
# Adhere to convenience from other markets, where we return None if user doesn't have any position.
|
|
261
|
+
if all(v == 0 for v in amounts_ot.values()):
|
|
262
|
+
return None
|
|
179
263
|
|
|
180
264
|
amounts_current = {
|
|
181
265
|
k: self.get_token_in_usd(self.get_sell_value_of_outcome_token(k, v))
|
|
@@ -191,33 +275,23 @@ class SeerAgentMarket(AgentMarket):
|
|
|
191
275
|
amounts_ot=amounts_ot,
|
|
192
276
|
)
|
|
193
277
|
|
|
194
|
-
def get_position(
|
|
195
|
-
self, user_id: str, web3: Web3 | None = None
|
|
196
|
-
) -> ExistingPosition | None:
|
|
197
|
-
try:
|
|
198
|
-
return self.get_position_else_raise(user_id=user_id, web3=web3)
|
|
199
|
-
except Exception as e:
|
|
200
|
-
logger.warning(f"Could not get position for user {user_id}, exception {e}")
|
|
201
|
-
return None
|
|
202
|
-
|
|
203
|
-
@staticmethod
|
|
204
|
-
def get_user_id(api_keys: APIKeys) -> str:
|
|
205
|
-
return OmenAgentMarket.get_user_id(api_keys)
|
|
206
|
-
|
|
207
278
|
@staticmethod
|
|
208
279
|
def _filter_markets_contained_in_trades(
|
|
209
280
|
api_keys: APIKeys,
|
|
210
|
-
markets:
|
|
281
|
+
markets: t.Sequence[SeerMarket],
|
|
211
282
|
) -> list[SeerMarket]:
|
|
212
283
|
"""
|
|
213
284
|
We filter the markets using previous trades by the user so that we don't have to process all Seer markets.
|
|
214
285
|
"""
|
|
215
|
-
trades_by_user = get_trades_by_owner(api_keys.bet_from_address)
|
|
216
286
|
|
|
217
|
-
|
|
218
|
-
|
|
287
|
+
trades_by_user = get_seer_transactions(
|
|
288
|
+
api_keys.bet_from_address, RPCConfig().CHAIN_ID
|
|
219
289
|
)
|
|
220
|
-
|
|
290
|
+
|
|
291
|
+
traded_tokens = {t.token_in_checksum for t in trades_by_user}.union(
|
|
292
|
+
[t.token_out_checksum for t in trades_by_user]
|
|
293
|
+
)
|
|
294
|
+
filtered_markets: list[SeerMarket] = []
|
|
221
295
|
for market in markets:
|
|
222
296
|
if any(
|
|
223
297
|
[
|
|
@@ -230,10 +304,12 @@ class SeerAgentMarket(AgentMarket):
|
|
|
230
304
|
return filtered_markets
|
|
231
305
|
|
|
232
306
|
@staticmethod
|
|
233
|
-
def redeem_winnings(api_keys: APIKeys) -> None:
|
|
234
|
-
web3 = RPCConfig().get_web3()
|
|
307
|
+
def redeem_winnings(api_keys: APIKeys, web3: Web3 | None = None) -> None:
|
|
308
|
+
web3 = web3 or RPCConfig().get_web3()
|
|
235
309
|
subgraph = SeerSubgraphHandler()
|
|
236
310
|
|
|
311
|
+
# ToDo - Find open positions by user directly
|
|
312
|
+
|
|
237
313
|
closed_markets = subgraph.get_markets(
|
|
238
314
|
filter_by=FilterBy.RESOLVED, sort_by=SortBy.NEWEST
|
|
239
315
|
)
|
|
@@ -242,8 +318,8 @@ class SeerAgentMarket(AgentMarket):
|
|
|
242
318
|
)
|
|
243
319
|
|
|
244
320
|
market_balances = {
|
|
245
|
-
market.id:
|
|
246
|
-
api_keys.bet_from_address, web3
|
|
321
|
+
market.id: list(
|
|
322
|
+
market.get_outcome_token_balances(api_keys.bet_from_address, web3)
|
|
247
323
|
)
|
|
248
324
|
for market in filtered_markets
|
|
249
325
|
}
|
|
@@ -253,41 +329,157 @@ class SeerAgentMarket(AgentMarket):
|
|
|
253
329
|
for market in filtered_markets
|
|
254
330
|
if market.is_redeemable(owner=api_keys.bet_from_address, web3=web3)
|
|
255
331
|
]
|
|
332
|
+
logger.info(f"Got {len(markets_to_redeem)} markets to redeem on Seer.")
|
|
256
333
|
|
|
257
334
|
gnosis_router = GnosisRouter()
|
|
258
335
|
for market in markets_to_redeem:
|
|
259
336
|
try:
|
|
260
|
-
|
|
337
|
+
# GnosisRouter needs approval to use our outcome tokens
|
|
338
|
+
for i, token in enumerate(market.wrapped_tokens):
|
|
339
|
+
handle_allowance(
|
|
340
|
+
api_keys=api_keys,
|
|
341
|
+
sell_token=Web3.to_checksum_address(token),
|
|
342
|
+
amount_to_check_wei=market_balances[market.id][i].as_wei,
|
|
343
|
+
for_address=gnosis_router.address,
|
|
344
|
+
web3=web3,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
# We can only ask for redeem of outcome tokens on correct outcomes
|
|
348
|
+
amounts_to_redeem = [
|
|
349
|
+
(amount if numerator > 0 else OutcomeWei(0))
|
|
350
|
+
for amount, numerator in zip(
|
|
351
|
+
market_balances[market.id], market.payout_numerators
|
|
352
|
+
)
|
|
353
|
+
]
|
|
354
|
+
|
|
355
|
+
gnosis_router.redeem_to_base(
|
|
356
|
+
api_keys,
|
|
261
357
|
market=Web3.to_checksum_address(market.id),
|
|
262
|
-
|
|
263
|
-
amounts=
|
|
358
|
+
outcome_indexes=list(range(len(market.payout_numerators))),
|
|
359
|
+
amounts=amounts_to_redeem,
|
|
360
|
+
web3=web3,
|
|
361
|
+
)
|
|
362
|
+
logger.info(f"Redeemed market {market.url}.")
|
|
363
|
+
except Exception:
|
|
364
|
+
logger.exception(
|
|
365
|
+
f"Failed to redeem market {market.url}, {market.outcomes}, with amounts {market_balances[market.id]} and payout numerators {market.payout_numerators}, and wrapped tokens {market.wrapped_tokens}."
|
|
264
366
|
)
|
|
265
|
-
gnosis_router.redeem_to_base(api_keys, params=params, web3=web3)
|
|
266
|
-
logger.info(f"Redeemed market {market.id.hex()}")
|
|
267
|
-
except Exception as e:
|
|
268
|
-
logger.error(f"Failed to redeem market {market.id.hex()}, {e}")
|
|
269
367
|
|
|
270
368
|
# GnosisRouter withdraws sDai into wxDAI/xDai on its own, so no auto-withdraw needed by us.
|
|
271
369
|
|
|
370
|
+
def have_bet_on_market_since(self, keys: APIKeys, since: timedelta) -> bool:
|
|
371
|
+
"""Check if the user has placed a bet on this market since a specific time using Cow API."""
|
|
372
|
+
# Cow endpoint doesn't allow us to filter by time.
|
|
373
|
+
start_time = utcnow() - since
|
|
374
|
+
prev_orders = get_orders_by_owner(owner=keys.bet_from_address)
|
|
375
|
+
for order in prev_orders:
|
|
376
|
+
if order.creationDate >= start_time and {
|
|
377
|
+
Web3.to_checksum_address(order.sellToken),
|
|
378
|
+
Web3.to_checksum_address(order.buyToken),
|
|
379
|
+
}.intersection(set(self.wrapped_tokens)):
|
|
380
|
+
return True
|
|
381
|
+
|
|
382
|
+
return False
|
|
383
|
+
|
|
384
|
+
def ensure_min_native_balance(
|
|
385
|
+
self,
|
|
386
|
+
min_required_balance: xDai,
|
|
387
|
+
multiplier: float = 3.0,
|
|
388
|
+
) -> None:
|
|
389
|
+
send_keeping_token_to_eoa_xdai(
|
|
390
|
+
api_keys=APIKeys(),
|
|
391
|
+
min_required_balance=min_required_balance,
|
|
392
|
+
multiplier=multiplier,
|
|
393
|
+
)
|
|
394
|
+
|
|
272
395
|
@staticmethod
|
|
273
396
|
def verify_operational_balance(api_keys: APIKeys) -> bool:
|
|
274
397
|
return OmenAgentMarket.verify_operational_balance(api_keys=api_keys)
|
|
275
398
|
|
|
399
|
+
@staticmethod
|
|
400
|
+
def build_resolution(
|
|
401
|
+
model: SeerMarketWithQuestions,
|
|
402
|
+
) -> Resolution | None:
|
|
403
|
+
if model.questions[0].question.finalize_ts == 0:
|
|
404
|
+
# resolution not yet finalized
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
if model.template_id != TemplateId.CATEGORICAL:
|
|
408
|
+
logger.warning("Resolution can only be built for categorical markets.")
|
|
409
|
+
# Future note - for scalar markets, simply fetch best_answer and convert
|
|
410
|
+
# from hex into int and divide by 1e18 (because Wei).
|
|
411
|
+
return None
|
|
412
|
+
|
|
413
|
+
if len(model.questions) != 1:
|
|
414
|
+
raise ValueError("Seer categorical markets must have 1 question.")
|
|
415
|
+
|
|
416
|
+
question = model.questions[0]
|
|
417
|
+
outcome = model.outcomes[int(question.question.best_answer.to_0x_hex(), 16)]
|
|
418
|
+
return Resolution(outcome=outcome, invalid=False)
|
|
419
|
+
|
|
420
|
+
@staticmethod
|
|
421
|
+
def convert_seer_market_into_market_with_questions(
|
|
422
|
+
seer_market: SeerMarket, seer_subgraph: SeerSubgraphHandler
|
|
423
|
+
) -> "SeerMarketWithQuestions":
|
|
424
|
+
q = SeerQuestionsCache(seer_subgraph_handler=seer_subgraph)
|
|
425
|
+
q.fetch_questions([seer_market.id])
|
|
426
|
+
questions = q.market_id_to_questions[seer_market.id]
|
|
427
|
+
return SeerMarketWithQuestions(**seer_market.model_dump(), questions=questions)
|
|
428
|
+
|
|
429
|
+
@staticmethod
|
|
430
|
+
def get_parent(
|
|
431
|
+
model: SeerMarket,
|
|
432
|
+
seer_subgraph: SeerSubgraphHandler,
|
|
433
|
+
) -> t.Optional["ParentMarket"]:
|
|
434
|
+
if not model.parent_market:
|
|
435
|
+
return None
|
|
436
|
+
|
|
437
|
+
# turn into a market with questions
|
|
438
|
+
parent_market_with_questions = (
|
|
439
|
+
SeerAgentMarket.convert_seer_market_into_market_with_questions(
|
|
440
|
+
model.parent_market, seer_subgraph=seer_subgraph
|
|
441
|
+
)
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
market_with_questions = check_not_none(
|
|
445
|
+
SeerAgentMarket.from_data_model_with_subgraph(
|
|
446
|
+
parent_market_with_questions, seer_subgraph, False
|
|
447
|
+
)
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
return ParentMarket(
|
|
451
|
+
market=market_with_questions, parent_outcome=model.parent_outcome
|
|
452
|
+
)
|
|
453
|
+
|
|
276
454
|
@staticmethod
|
|
277
455
|
def from_data_model_with_subgraph(
|
|
278
|
-
model:
|
|
456
|
+
model: SeerMarketWithQuestions,
|
|
457
|
+
seer_subgraph: SeerSubgraphHandler,
|
|
458
|
+
must_have_prices: bool,
|
|
279
459
|
) -> t.Optional["SeerAgentMarket"]:
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
460
|
+
price_manager = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
|
|
461
|
+
wrapped_tokens = [Web3.to_checksum_address(i) for i in model.wrapped_tokens]
|
|
462
|
+
try:
|
|
463
|
+
(
|
|
464
|
+
probability_map,
|
|
465
|
+
outcome_token_pool,
|
|
466
|
+
) = price_manager.build_initial_probs_from_pool(
|
|
467
|
+
model=model, wrapped_tokens=wrapped_tokens
|
|
468
|
+
)
|
|
469
|
+
except PriceCalculationError as e:
|
|
284
470
|
logger.info(
|
|
285
|
-
f"
|
|
471
|
+
f"Error when calculating probabilities for market {model.id.to_0x_hex()} - {e}"
|
|
286
472
|
)
|
|
287
|
-
|
|
473
|
+
if must_have_prices:
|
|
474
|
+
# Price calculation failed, so don't return the market
|
|
475
|
+
return None
|
|
288
476
|
|
|
289
|
-
|
|
290
|
-
|
|
477
|
+
resolution = SeerAgentMarket.build_resolution(model=model)
|
|
478
|
+
|
|
479
|
+
parent = SeerAgentMarket.get_parent(model=model, seer_subgraph=seer_subgraph)
|
|
480
|
+
|
|
481
|
+
market = SeerAgentMarket(
|
|
482
|
+
id=model.id.to_0x_hex(),
|
|
291
483
|
question=model.title,
|
|
292
484
|
creator=model.creator,
|
|
293
485
|
created_time=model.created_time,
|
|
@@ -296,15 +488,29 @@ class SeerAgentMarket(AgentMarket):
|
|
|
296
488
|
condition_id=model.condition_id,
|
|
297
489
|
url=model.url,
|
|
298
490
|
close_time=model.close_time,
|
|
299
|
-
wrapped_tokens=
|
|
491
|
+
wrapped_tokens=wrapped_tokens,
|
|
300
492
|
fees=MarketFees.get_zero_fees(),
|
|
301
|
-
outcome_token_pool=
|
|
493
|
+
outcome_token_pool=outcome_token_pool,
|
|
302
494
|
outcomes_supply=model.outcomes_supply,
|
|
303
|
-
resolution=
|
|
495
|
+
resolution=resolution,
|
|
304
496
|
volume=None,
|
|
305
497
|
probabilities=probability_map,
|
|
498
|
+
upper_bound=model.upper_bound,
|
|
499
|
+
lower_bound=model.lower_bound,
|
|
500
|
+
parent=parent,
|
|
306
501
|
)
|
|
307
502
|
|
|
503
|
+
return market
|
|
504
|
+
|
|
505
|
+
@staticmethod
|
|
506
|
+
def get_resolved_bets_made_since(
|
|
507
|
+
better_address: ChecksumAddress,
|
|
508
|
+
start_time: DatetimeUTC,
|
|
509
|
+
end_time: DatetimeUTC | None,
|
|
510
|
+
) -> list[ResolvedBet]:
|
|
511
|
+
# TODO: https://github.com/gnosis/prediction-market-agent-tooling/issues/841
|
|
512
|
+
raise NotImplementedError()
|
|
513
|
+
|
|
308
514
|
@staticmethod
|
|
309
515
|
def get_markets(
|
|
310
516
|
limit: int,
|
|
@@ -312,29 +518,40 @@ class SeerAgentMarket(AgentMarket):
|
|
|
312
518
|
filter_by: FilterBy = FilterBy.OPEN,
|
|
313
519
|
created_after: t.Optional[DatetimeUTC] = None,
|
|
314
520
|
excluded_questions: set[str] | None = None,
|
|
315
|
-
|
|
521
|
+
question_type: QuestionType = QuestionType.ALL,
|
|
522
|
+
conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
|
|
316
523
|
) -> t.Sequence["SeerAgentMarket"]:
|
|
317
524
|
seer_subgraph = SeerSubgraphHandler()
|
|
525
|
+
|
|
318
526
|
markets = seer_subgraph.get_markets(
|
|
319
527
|
limit=limit,
|
|
320
528
|
sort_by=sort_by,
|
|
321
529
|
filter_by=filter_by,
|
|
322
|
-
|
|
530
|
+
question_type=question_type,
|
|
531
|
+
conditional_filter_type=conditional_filter_type,
|
|
323
532
|
)
|
|
324
533
|
|
|
325
534
|
# We exclude the None values below because `from_data_model_with_subgraph` can return None, which
|
|
326
535
|
# represents an invalid market.
|
|
327
|
-
|
|
536
|
+
seer_agent_markets = [
|
|
328
537
|
market
|
|
329
538
|
for m in markets
|
|
330
539
|
if (
|
|
331
540
|
market := SeerAgentMarket.from_data_model_with_subgraph(
|
|
332
|
-
model=m,
|
|
541
|
+
model=m,
|
|
542
|
+
seer_subgraph=seer_subgraph,
|
|
543
|
+
must_have_prices=filter_by == FilterBy.OPEN,
|
|
333
544
|
)
|
|
334
545
|
)
|
|
335
546
|
is not None
|
|
336
547
|
]
|
|
337
548
|
|
|
549
|
+
if filter_by == FilterBy.OPEN:
|
|
550
|
+
# Extra manual filter for liquidity, as subgraph is sometimes unreliable.
|
|
551
|
+
seer_agent_markets = [m for m in seer_agent_markets if m.has_liquidity()]
|
|
552
|
+
|
|
553
|
+
return seer_agent_markets
|
|
554
|
+
|
|
338
555
|
def get_outcome_str_from_idx(self, outcome_index: int) -> OutcomeStr:
|
|
339
556
|
return self.outcomes[outcome_index]
|
|
340
557
|
|
|
@@ -353,7 +570,8 @@ class SeerAgentMarket(AgentMarket):
|
|
|
353
570
|
f"Could not fetch pool for token {outcome_token}, no liquidity available for outcome."
|
|
354
571
|
)
|
|
355
572
|
return CollateralToken(0)
|
|
356
|
-
|
|
573
|
+
|
|
574
|
+
p = self.get_price_manager()
|
|
357
575
|
total = CollateralToken(0)
|
|
358
576
|
|
|
359
577
|
for token_address in [pool.token0.id, pool.token1.id]:
|
|
@@ -363,22 +581,18 @@ class SeerAgentMarket(AgentMarket):
|
|
|
363
581
|
)
|
|
364
582
|
|
|
365
583
|
token_balance = token_contract.balance_of_in_tokens(
|
|
366
|
-
for_address=Web3.to_checksum_address(
|
|
584
|
+
for_address=Web3.to_checksum_address(
|
|
585
|
+
HexAddress(HexStr(pool.id.to_0x_hex()))
|
|
586
|
+
),
|
|
367
587
|
web3=web3,
|
|
368
588
|
)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
token_price_in_sdai = (
|
|
372
|
-
p.get_token_price_from_pools(token=token_address_checksummed)
|
|
373
|
-
if token_address_checksummed
|
|
374
|
-
!= self.collateral_token_contract_address_checksummed
|
|
375
|
-
else CollateralToken(1.0)
|
|
589
|
+
collateral_balance = p.get_amount_of_token_in_collateral(
|
|
590
|
+
token_address_checksummed, OutcomeToken.from_token(token_balance)
|
|
376
591
|
)
|
|
377
592
|
|
|
378
593
|
# We ignore the liquidity in outcome tokens if price unknown.
|
|
379
|
-
if
|
|
380
|
-
|
|
381
|
-
total += sdai_balance
|
|
594
|
+
if collateral_balance:
|
|
595
|
+
total += collateral_balance
|
|
382
596
|
|
|
383
597
|
return total
|
|
384
598
|
|
|
@@ -393,8 +607,9 @@ class SeerAgentMarket(AgentMarket):
|
|
|
393
607
|
|
|
394
608
|
def has_liquidity_for_outcome(self, outcome: OutcomeStr) -> bool:
|
|
395
609
|
liquidity = self.get_liquidity_for_outcome(outcome)
|
|
396
|
-
return liquidity >
|
|
610
|
+
return liquidity > self.minimum_market_liquidity_required
|
|
397
611
|
|
|
612
|
+
@cachetools.cached(cache=SHARED_CACHE, key=lambda self: f"has_liquidity_{self.id}")
|
|
398
613
|
def has_liquidity(self) -> bool:
|
|
399
614
|
# We define a market as having liquidity if it has liquidity for all outcomes except for the invalid (index -1)
|
|
400
615
|
return all(
|
|
@@ -405,6 +620,85 @@ class SeerAgentMarket(AgentMarket):
|
|
|
405
620
|
outcome_idx = self.outcomes.index(outcome)
|
|
406
621
|
return self.wrapped_tokens[outcome_idx]
|
|
407
622
|
|
|
623
|
+
def _swap_tokens_with_fallback(
|
|
624
|
+
self,
|
|
625
|
+
sell_token: ChecksumAddress,
|
|
626
|
+
buy_token: ChecksumAddress,
|
|
627
|
+
amount_wei: Wei,
|
|
628
|
+
api_keys: APIKeys,
|
|
629
|
+
web3: Web3 | None,
|
|
630
|
+
) -> str:
|
|
631
|
+
"""
|
|
632
|
+
Helper method to swap tokens with a fallback to direct pool swapping if the order times out.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
sell_token: Address of the token to sell
|
|
636
|
+
buy_token: Address of the token to buy
|
|
637
|
+
amount_wei: Amount to swap in wei
|
|
638
|
+
api_keys: API keys for the transaction
|
|
639
|
+
web3: Web3 instance
|
|
640
|
+
|
|
641
|
+
Returns:
|
|
642
|
+
Transaction hash of the successful swap
|
|
643
|
+
"""
|
|
644
|
+
slippage_tolerance = get_slippage_tolerance_per_token(sell_token, buy_token)
|
|
645
|
+
try:
|
|
646
|
+
_, order = swap_tokens_waiting(
|
|
647
|
+
amount_wei=amount_wei,
|
|
648
|
+
sell_token=sell_token,
|
|
649
|
+
buy_token=buy_token,
|
|
650
|
+
api_keys=api_keys,
|
|
651
|
+
web3=web3,
|
|
652
|
+
wait_order_complete=False,
|
|
653
|
+
timeout=timedelta(minutes=2),
|
|
654
|
+
slippage_tolerance=slippage_tolerance,
|
|
655
|
+
)
|
|
656
|
+
order_metadata = asyncio.run(wait_for_order_completion(order=order))
|
|
657
|
+
logger.info(
|
|
658
|
+
f"Swapped {sell_token} for {buy_token}. Order details {order_metadata}"
|
|
659
|
+
)
|
|
660
|
+
trades = get_trades_by_order_uid(HexBytes(order_metadata.uid.root))
|
|
661
|
+
if len(trades) != 1:
|
|
662
|
+
raise ValueError(
|
|
663
|
+
f"Expected exactly 1 trade from {order_metadata=}, but got {len(trades)=}."
|
|
664
|
+
)
|
|
665
|
+
cow_tx_hash = trades[0].txHash
|
|
666
|
+
logger.info(f"TxHash is {cow_tx_hash=} for {order_metadata.uid.root=}.")
|
|
667
|
+
return cow_tx_hash.to_0x_hex()
|
|
668
|
+
|
|
669
|
+
except (
|
|
670
|
+
UnexpectedResponseError,
|
|
671
|
+
TimeoutError,
|
|
672
|
+
NoLiquidityAvailableOnCowException,
|
|
673
|
+
OrderStatusError,
|
|
674
|
+
) as e:
|
|
675
|
+
# We don't retry if not enough balance.
|
|
676
|
+
if "InsufficientBalance" in str(e):
|
|
677
|
+
raise e
|
|
678
|
+
# Note that we don't need to cancel the order because we are setting
|
|
679
|
+
# timeout and valid_to in the order, thus the order simply expires.
|
|
680
|
+
logger.info(
|
|
681
|
+
f"Exception occured when swapping tokens via Cowswap, doing swap via pools. {e}"
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
if not self.has_liquidity():
|
|
685
|
+
logger.error(f"Market {self.id} has no liquidity. Cannot place bet.")
|
|
686
|
+
raise e
|
|
687
|
+
|
|
688
|
+
tx_receipt = SwapPoolHandler(
|
|
689
|
+
api_keys=api_keys,
|
|
690
|
+
market_id=self.id,
|
|
691
|
+
collateral_token_address=self.collateral_token_contract_address_checksummed,
|
|
692
|
+
).buy_or_sell_outcome_token(
|
|
693
|
+
token_in=sell_token,
|
|
694
|
+
token_out=buy_token,
|
|
695
|
+
amount_in=amount_wei,
|
|
696
|
+
web3=web3,
|
|
697
|
+
)
|
|
698
|
+
swap_pool_tx_hash = tx_receipt["transactionHash"]
|
|
699
|
+
logger.info(f"TxHash is {swap_pool_tx_hash=}.")
|
|
700
|
+
return swap_pool_tx_hash.to_0x_hex()
|
|
701
|
+
|
|
408
702
|
def place_bet(
|
|
409
703
|
self,
|
|
410
704
|
outcome: OutcomeStr,
|
|
@@ -413,6 +707,7 @@ class SeerAgentMarket(AgentMarket):
|
|
|
413
707
|
web3: Web3 | None = None,
|
|
414
708
|
api_keys: APIKeys | None = None,
|
|
415
709
|
) -> str:
|
|
710
|
+
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
|
416
711
|
api_keys = api_keys if api_keys is not None else APIKeys()
|
|
417
712
|
if not self.can_be_traded():
|
|
418
713
|
raise ValueError(
|
|
@@ -428,27 +723,13 @@ class SeerAgentMarket(AgentMarket):
|
|
|
428
723
|
collateral_contract, amount_wei, api_keys, web3
|
|
429
724
|
)
|
|
430
725
|
|
|
431
|
-
|
|
432
|
-
if collateral_balance < amount_wei:
|
|
433
|
-
raise ValueError(
|
|
434
|
-
f"Balance {collateral_balance} not enough for bet size {amount}"
|
|
435
|
-
)
|
|
436
|
-
|
|
437
|
-
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
|
438
|
-
|
|
439
|
-
# Sell sDAI using token address
|
|
440
|
-
order_metadata = swap_tokens_waiting(
|
|
441
|
-
amount_wei=amount_wei,
|
|
726
|
+
return self._swap_tokens_with_fallback(
|
|
442
727
|
sell_token=collateral_contract.address,
|
|
443
728
|
buy_token=outcome_token,
|
|
729
|
+
amount_wei=amount_wei,
|
|
444
730
|
api_keys=api_keys,
|
|
445
731
|
web3=web3,
|
|
446
732
|
)
|
|
447
|
-
logger.debug(
|
|
448
|
-
f"Purchased {outcome_token} in exchange for {collateral_contract.address}. Order details {order_metadata}"
|
|
449
|
-
)
|
|
450
|
-
|
|
451
|
-
return order_metadata.uid.root
|
|
452
733
|
|
|
453
734
|
def sell_tokens(
|
|
454
735
|
self,
|
|
@@ -470,21 +751,27 @@ class SeerAgentMarket(AgentMarket):
|
|
|
470
751
|
else self.get_in_token(amount).as_wei
|
|
471
752
|
)
|
|
472
753
|
|
|
473
|
-
|
|
474
|
-
amount_wei=token_amount,
|
|
754
|
+
return self._swap_tokens_with_fallback(
|
|
475
755
|
sell_token=outcome_token,
|
|
476
756
|
buy_token=Web3.to_checksum_address(
|
|
477
757
|
self.collateral_token_contract_address_checksummed
|
|
478
758
|
),
|
|
759
|
+
amount_wei=token_amount,
|
|
479
760
|
api_keys=api_keys,
|
|
480
761
|
web3=web3,
|
|
481
762
|
)
|
|
482
763
|
|
|
483
|
-
|
|
484
|
-
|
|
764
|
+
def get_token_balance(
|
|
765
|
+
self, user_id: str, outcome: OutcomeStr, web3: Web3 | None = None
|
|
766
|
+
) -> OutcomeToken:
|
|
767
|
+
erc20_token = ContractERC20OnGnosisChain(
|
|
768
|
+
address=self.get_wrapped_token_for_outcome(outcome)
|
|
769
|
+
)
|
|
770
|
+
return OutcomeToken.from_token(
|
|
771
|
+
erc20_token.balance_of_in_tokens(
|
|
772
|
+
for_address=Web3.to_checksum_address(user_id), web3=web3
|
|
773
|
+
)
|
|
485
774
|
)
|
|
486
|
-
|
|
487
|
-
return order_metadata.uid.root
|
|
488
775
|
|
|
489
776
|
|
|
490
777
|
def seer_create_market_tx(
|