prediction-market-agent-tooling 0.57.16.dev311__py3-none-any.whl → 0.57.18__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/seer_market_factory.abi.json +609 -0
- prediction_market_agent_tooling/config.py +8 -0
- prediction_market_agent_tooling/loggers.py +9 -1
- prediction_market_agent_tooling/markets/omen/omen.py +12 -16
- prediction_market_agent_tooling/markets/omen/omen_constants.py +8 -0
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +16 -4
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +3 -3
- prediction_market_agent_tooling/markets/seer/data_models.py +41 -2
- prediction_market_agent_tooling/markets/seer/seer.py +94 -0
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +76 -0
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +2 -1
- prediction_market_agent_tooling/monitor/monitor.py +1 -1
- prediction_market_agent_tooling/monitor/monitor_app.py +4 -8
- prediction_market_agent_tooling/tools/contract.py +1 -98
- prediction_market_agent_tooling/tools/cow/cow_order.py +133 -0
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +156 -0
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +62 -0
- prediction_market_agent_tooling/tools/tokens/main_token.py +13 -0
- {prediction_market_agent_tooling-0.57.16.dev311.dist-info → prediction_market_agent_tooling-0.57.18.dist-info}/METADATA +3 -1
- {prediction_market_agent_tooling-0.57.16.dev311.dist-info → prediction_market_agent_tooling-0.57.18.dist-info}/RECORD +23 -15
- {prediction_market_agent_tooling-0.57.16.dev311.dist-info → prediction_market_agent_tooling-0.57.18.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.57.16.dev311.dist-info → prediction_market_agent_tooling-0.57.18.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.57.16.dev311.dist-info → prediction_market_agent_tooling-0.57.18.dist-info}/entry_points.txt +0 -0
@@ -31,6 +31,10 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
|
|
31
31
|
RealitioLogNewQuestionEvent,
|
32
32
|
format_realitio_question,
|
33
33
|
)
|
34
|
+
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
35
|
+
SDAI_CONTRACT_ADDRESS,
|
36
|
+
WRAPPED_XDAI_CONTRACT_ADDRESS,
|
37
|
+
)
|
34
38
|
from prediction_market_agent_tooling.tools.contract import (
|
35
39
|
ContractDepositableWrapperERC20OnGnosisChain,
|
36
40
|
ContractERC20OnGnosisChain,
|
@@ -419,18 +423,26 @@ class OmenFixedProductMarketMakerContract(ContractOnGnosisChain):
|
|
419
423
|
)
|
420
424
|
|
421
425
|
|
422
|
-
class
|
426
|
+
class GNOContract(ContractERC20OnGnosisChain):
|
423
427
|
address: ChecksumAddress = Web3.to_checksum_address(
|
424
|
-
"
|
428
|
+
"0x9c58bacc331c9aa871afd802db6379a98e80cedb"
|
425
429
|
)
|
426
430
|
|
427
431
|
|
428
|
-
class
|
432
|
+
class WETHContract(ContractERC20OnGnosisChain):
|
429
433
|
address: ChecksumAddress = Web3.to_checksum_address(
|
430
|
-
"
|
434
|
+
"0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1"
|
431
435
|
)
|
432
436
|
|
433
437
|
|
438
|
+
class WrappedxDaiContract(ContractDepositableWrapperERC20OnGnosisChain):
|
439
|
+
address: ChecksumAddress = WRAPPED_XDAI_CONTRACT_ADDRESS
|
440
|
+
|
441
|
+
|
442
|
+
class sDaiContract(ContractERC4626OnGnosisChain):
|
443
|
+
address: ChecksumAddress = SDAI_CONTRACT_ADDRESS
|
444
|
+
|
445
|
+
|
434
446
|
OMEN_DEFAULT_MARKET_FEE_PERC = 0.02 # 2% fee from the buying shares amount.
|
435
447
|
REALITY_DEFAULT_FINALIZATION_TIMEOUT = timedelta(days=3)
|
436
448
|
|
@@ -50,7 +50,7 @@ def claim_bonds_on_realitio_questions(
|
|
50
50
|
except Exception as e:
|
51
51
|
if not skip_failed:
|
52
52
|
raise e
|
53
|
-
logger.
|
53
|
+
logger.warning(
|
54
54
|
f"Failed to claim bond for {question.url=}, {question.questionId=}: {e}"
|
55
55
|
)
|
56
56
|
|
@@ -203,7 +203,7 @@ def omen_submit_answer_market_tx(
|
|
203
203
|
web3: Web3 | None = None,
|
204
204
|
) -> None:
|
205
205
|
"""
|
206
|
-
After the answer is submitted, there is
|
206
|
+
After the answer is submitted, there is waiting period where the answer can be challenged by others.
|
207
207
|
And after the period is over, you need to resolve the market using `omen_resolve_market_tx`.
|
208
208
|
"""
|
209
209
|
realitio_contract = OmenRealitioContract()
|
@@ -224,7 +224,7 @@ def omen_submit_invalid_answer_market_tx(
|
|
224
224
|
web3: Web3 | None = None,
|
225
225
|
) -> None:
|
226
226
|
"""
|
227
|
-
After the answer is submitted, there is
|
227
|
+
After the answer is submitted, there is waiting period where the answer can be challenged by others.
|
228
228
|
And after the period is over, you need to resolve the market using `omen_resolve_market_tx`.
|
229
229
|
"""
|
230
230
|
realitio_contract = OmenRealitioContract()
|
@@ -1,6 +1,33 @@
|
|
1
|
+
import typing as t
|
2
|
+
|
3
|
+
from eth_typing import HexAddress
|
1
4
|
from pydantic import BaseModel, ConfigDict, Field
|
5
|
+
from web3.constants import ADDRESS_ZERO
|
6
|
+
|
7
|
+
from prediction_market_agent_tooling.gtypes import HexBytes, Wei
|
8
|
+
|
9
|
+
|
10
|
+
class CreateCategoricalMarketsParams(BaseModel):
|
11
|
+
model_config = ConfigDict(populate_by_name=True)
|
2
12
|
|
3
|
-
|
13
|
+
market_name: str = Field(..., alias="marketName")
|
14
|
+
outcomes: list[str]
|
15
|
+
# Only relevant for scalar markets
|
16
|
+
question_start: str = Field(alias="questionStart", default="")
|
17
|
+
question_end: str = Field(alias="questionEnd", default="")
|
18
|
+
outcome_type: str = Field(alias="outcomeType", default="")
|
19
|
+
|
20
|
+
# Not needed for non-conditional markets.
|
21
|
+
parent_outcome: int = Field(alias="parentOutcome", default=0)
|
22
|
+
parent_market: HexAddress = Field(alias="parentMarket", default=ADDRESS_ZERO)
|
23
|
+
|
24
|
+
category: str
|
25
|
+
lang: str
|
26
|
+
lower_bound: int = Field(alias="lowerBound", default=0)
|
27
|
+
upper_bound: int = Field(alias="upperBound", default=0)
|
28
|
+
min_bond: Wei = Field(..., alias="minBond")
|
29
|
+
opening_time: int = Field(..., alias="openingTime")
|
30
|
+
token_names: list[str] = Field(..., alias="tokenNames")
|
4
31
|
|
5
32
|
|
6
33
|
class SeerParentMarket(BaseModel):
|
@@ -13,8 +40,11 @@ class SeerMarket(BaseModel):
|
|
13
40
|
id: HexBytes
|
14
41
|
title: str = Field(alias="marketName")
|
15
42
|
outcomes: list[str]
|
16
|
-
parent_market: SeerParentMarket | None = Field(alias="parentMarket")
|
17
43
|
wrapped_tokens: list[HexBytes] = Field(alias="wrappedTokens")
|
44
|
+
parent_outcome: int = Field(alias="parentOutcome")
|
45
|
+
parent_market: t.Optional[SeerParentMarket] = Field(
|
46
|
+
alias="parentMarket", default=None
|
47
|
+
)
|
18
48
|
|
19
49
|
|
20
50
|
class SeerToken(BaseModel):
|
@@ -29,3 +59,12 @@ class SeerPool(BaseModel):
|
|
29
59
|
liquidity: int
|
30
60
|
token0: SeerToken
|
31
61
|
token1: SeerToken
|
62
|
+
|
63
|
+
|
64
|
+
class NewMarketEvent(BaseModel):
|
65
|
+
market: HexAddress
|
66
|
+
marketName: str
|
67
|
+
parentMarket: HexAddress
|
68
|
+
conditionId: HexBytes
|
69
|
+
questionId: HexBytes
|
70
|
+
questionsIds: list[HexBytes]
|
@@ -0,0 +1,94 @@
|
|
1
|
+
from eth_typing import ChecksumAddress
|
2
|
+
from web3 import Web3
|
3
|
+
from web3.types import TxReceipt
|
4
|
+
|
5
|
+
from prediction_market_agent_tooling.config import APIKeys
|
6
|
+
from prediction_market_agent_tooling.gtypes import xDai
|
7
|
+
from prediction_market_agent_tooling.markets.seer.data_models import NewMarketEvent
|
8
|
+
from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
9
|
+
SeerMarketFactory,
|
10
|
+
)
|
11
|
+
from prediction_market_agent_tooling.tools.contract import (
|
12
|
+
init_collateral_token_contract,
|
13
|
+
to_gnosis_chain_contract,
|
14
|
+
)
|
15
|
+
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
16
|
+
from prediction_market_agent_tooling.tools.tokens.auto_deposit import (
|
17
|
+
auto_deposit_collateral_token,
|
18
|
+
)
|
19
|
+
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
|
20
|
+
|
21
|
+
|
22
|
+
def seer_create_market_tx(
|
23
|
+
api_keys: APIKeys,
|
24
|
+
initial_funds: xDai,
|
25
|
+
question: str,
|
26
|
+
opening_time: DatetimeUTC,
|
27
|
+
language: str,
|
28
|
+
outcomes: list[str],
|
29
|
+
auto_deposit: bool,
|
30
|
+
category: str,
|
31
|
+
min_bond_xdai: xDai,
|
32
|
+
web3: Web3 | None = None,
|
33
|
+
) -> ChecksumAddress:
|
34
|
+
web3 = web3 or SeerMarketFactory.get_web3() # Default to Gnosis web3.
|
35
|
+
initial_funds_wei = xdai_to_wei(initial_funds)
|
36
|
+
|
37
|
+
factory_contract = SeerMarketFactory()
|
38
|
+
collateral_token_address = factory_contract.collateral_token(web3=web3)
|
39
|
+
collateral_token_contract = to_gnosis_chain_contract(
|
40
|
+
init_collateral_token_contract(collateral_token_address, web3)
|
41
|
+
)
|
42
|
+
|
43
|
+
if auto_deposit:
|
44
|
+
auto_deposit_collateral_token(
|
45
|
+
collateral_token_contract=collateral_token_contract,
|
46
|
+
api_keys=api_keys,
|
47
|
+
amount_wei=initial_funds_wei,
|
48
|
+
web3=web3,
|
49
|
+
)
|
50
|
+
|
51
|
+
# In case of ERC4626, obtained (for example) sDai out of xDai could be lower than the `amount_wei`, so we need to handle it.
|
52
|
+
initial_funds_in_shares = collateral_token_contract.get_in_shares(
|
53
|
+
amount=initial_funds_wei, web3=web3
|
54
|
+
)
|
55
|
+
|
56
|
+
# Approve the market maker to withdraw our collateral token.
|
57
|
+
collateral_token_contract.approve(
|
58
|
+
api_keys=api_keys,
|
59
|
+
for_address=factory_contract.address,
|
60
|
+
amount_wei=initial_funds_in_shares,
|
61
|
+
web3=web3,
|
62
|
+
)
|
63
|
+
|
64
|
+
# Create the market.
|
65
|
+
params = factory_contract.build_market_params(
|
66
|
+
market_question=question,
|
67
|
+
outcomes=outcomes,
|
68
|
+
opening_time=opening_time,
|
69
|
+
language=language,
|
70
|
+
category=category,
|
71
|
+
min_bond_xdai=min_bond_xdai,
|
72
|
+
)
|
73
|
+
tx_receipt = factory_contract.create_categorical_market(
|
74
|
+
api_keys=api_keys, params=params, web3=web3
|
75
|
+
)
|
76
|
+
|
77
|
+
# ToDo - Add liquidity to market on Swapr (https://github.com/gnosis/prediction-market-agent-tooling/issues/497)
|
78
|
+
market_address = extract_market_address_from_tx(
|
79
|
+
factory_contract=factory_contract, tx_receipt=tx_receipt, web3=web3
|
80
|
+
)
|
81
|
+
return market_address
|
82
|
+
|
83
|
+
|
84
|
+
def extract_market_address_from_tx(
|
85
|
+
factory_contract: SeerMarketFactory, tx_receipt: TxReceipt, web3: Web3
|
86
|
+
) -> ChecksumAddress:
|
87
|
+
"""We extract the newly created market from the NewMarket event emitted in the transaction."""
|
88
|
+
event_logs = (
|
89
|
+
factory_contract.get_web3_contract(web3=web3)
|
90
|
+
.events.NewMarket()
|
91
|
+
.process_receipt(tx_receipt)
|
92
|
+
)
|
93
|
+
new_market_event = NewMarketEvent(**event_logs[0]["args"])
|
94
|
+
return Web3.to_checksum_address(new_market_event.market)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from web3 import Web3
|
4
|
+
from web3.types import TxReceipt
|
5
|
+
|
6
|
+
from prediction_market_agent_tooling.config import APIKeys
|
7
|
+
from prediction_market_agent_tooling.gtypes import ABI, ChecksumAddress, xDai
|
8
|
+
from prediction_market_agent_tooling.markets.seer.data_models import (
|
9
|
+
CreateCategoricalMarketsParams,
|
10
|
+
)
|
11
|
+
from prediction_market_agent_tooling.tools.contract import (
|
12
|
+
ContractOnGnosisChain,
|
13
|
+
abi_field_validator,
|
14
|
+
)
|
15
|
+
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
16
|
+
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
|
17
|
+
|
18
|
+
|
19
|
+
class SeerMarketFactory(ContractOnGnosisChain):
|
20
|
+
# https://gnosisscan.io/address/0x83183da839ce8228e31ae41222ead9edbb5cdcf1#code.
|
21
|
+
abi: ABI = abi_field_validator(
|
22
|
+
os.path.join(
|
23
|
+
os.path.dirname(os.path.realpath(__file__)),
|
24
|
+
"../../abis/seer_market_factory.abi.json",
|
25
|
+
)
|
26
|
+
)
|
27
|
+
address: ChecksumAddress = Web3.to_checksum_address(
|
28
|
+
"0x83183da839ce8228e31ae41222ead9edbb5cdcf1"
|
29
|
+
)
|
30
|
+
|
31
|
+
@staticmethod
|
32
|
+
def build_market_params(
|
33
|
+
market_question: str,
|
34
|
+
outcomes: list[str],
|
35
|
+
opening_time: DatetimeUTC,
|
36
|
+
min_bond_xdai: xDai,
|
37
|
+
language: str = "en_US",
|
38
|
+
category: str = "misc",
|
39
|
+
) -> CreateCategoricalMarketsParams:
|
40
|
+
return CreateCategoricalMarketsParams(
|
41
|
+
market_name=market_question,
|
42
|
+
token_names=[
|
43
|
+
o.upper() for o in outcomes
|
44
|
+
], # Following usual token names on Seer (YES,NO).
|
45
|
+
min_bond=xdai_to_wei(min_bond_xdai),
|
46
|
+
opening_time=int(opening_time.timestamp()),
|
47
|
+
outcomes=outcomes,
|
48
|
+
lang=language,
|
49
|
+
category=category,
|
50
|
+
)
|
51
|
+
|
52
|
+
def market_count(self, web3: Web3 | None = None) -> int:
|
53
|
+
count: int = self.call("marketCount", web3=web3)
|
54
|
+
return count
|
55
|
+
|
56
|
+
def market_at_index(self, index: int, web3: Web3 | None = None) -> ChecksumAddress:
|
57
|
+
market_address: str = self.call("markets", function_params=[index], web3=web3)
|
58
|
+
return Web3.to_checksum_address(market_address)
|
59
|
+
|
60
|
+
def collateral_token(self, web3: Web3 | None = None) -> ChecksumAddress:
|
61
|
+
collateral_token_address: str = self.call("collateralToken", web3=web3)
|
62
|
+
return Web3.to_checksum_address(collateral_token_address)
|
63
|
+
|
64
|
+
def create_categorical_market(
|
65
|
+
self,
|
66
|
+
api_keys: APIKeys,
|
67
|
+
params: CreateCategoricalMarketsParams,
|
68
|
+
web3: Web3 | None = None,
|
69
|
+
) -> TxReceipt:
|
70
|
+
receipt_tx = self.send(
|
71
|
+
api_keys=api_keys,
|
72
|
+
function_name="createCategoricalMarket",
|
73
|
+
function_params=[params.model_dump(by_alias=True)],
|
74
|
+
web3=web3,
|
75
|
+
)
|
76
|
+
return receipt_tx
|
@@ -46,6 +46,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
46
46
|
markets_field.factory,
|
47
47
|
markets_field.creator,
|
48
48
|
markets_field.marketName,
|
49
|
+
markets_field.parentOutcome,
|
49
50
|
markets_field.outcomes,
|
50
51
|
markets_field.parentMarket.id,
|
51
52
|
markets_field.finalizeTs,
|
@@ -126,7 +127,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
126
127
|
]
|
127
128
|
return fields
|
128
129
|
|
129
|
-
def
|
130
|
+
def get_swapr_pools_for_market(self, market: SeerMarket) -> list[SeerPool]:
|
130
131
|
# We iterate through the wrapped tokens and put them in a where clause so that we hit the subgraph endpoint just once.
|
131
132
|
wheres = []
|
132
133
|
for wrapped_token in market.wrapped_tokens:
|
@@ -378,7 +378,7 @@ def monitor_market_outcome_bias(
|
|
378
378
|
|
379
379
|
if len(df) > 0:
|
380
380
|
st.altair_chart(
|
381
|
-
alt.layer(open_chart, resolved_chart).interactive(),
|
381
|
+
alt.layer(open_chart, resolved_chart).interactive(),
|
382
382
|
use_container_width=True,
|
383
383
|
)
|
384
384
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import typing as t
|
2
|
-
from datetime import
|
2
|
+
from datetime import datetime, timedelta
|
3
3
|
|
4
4
|
import streamlit as st
|
5
5
|
|
@@ -105,13 +105,9 @@ def monitor_app(
|
|
105
105
|
start_time: DatetimeUTC | None = (
|
106
106
|
DatetimeUTC.from_datetime(
|
107
107
|
datetime.combine(
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
st.date_input(
|
112
|
-
"Start time",
|
113
|
-
value=utcnow() - timedelta(weeks=settings.PAST_N_WEEKS),
|
114
|
-
),
|
108
|
+
st.date_input(
|
109
|
+
"Start time",
|
110
|
+
value=utcnow() - timedelta(weeks=settings.PAST_N_WEEKS),
|
115
111
|
),
|
116
112
|
datetime.min.time(),
|
117
113
|
)
|
@@ -22,11 +22,7 @@ from prediction_market_agent_tooling.gtypes import (
|
|
22
22
|
)
|
23
23
|
from prediction_market_agent_tooling.tools.data_models import MessageContainer
|
24
24
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
25
|
-
from prediction_market_agent_tooling.tools.utils import
|
26
|
-
BPS_CONSTANT,
|
27
|
-
DatetimeUTC,
|
28
|
-
should_not_happen,
|
29
|
-
)
|
25
|
+
from prediction_market_agent_tooling.tools.utils import BPS_CONSTANT, DatetimeUTC
|
30
26
|
from prediction_market_agent_tooling.tools.web3_utils import (
|
31
27
|
call_function_on_contract,
|
32
28
|
send_function_on_contract_tx,
|
@@ -798,99 +794,6 @@ def init_collateral_token_contract(
|
|
798
794
|
)
|
799
795
|
|
800
796
|
|
801
|
-
def auto_deposit_collateral_token(
|
802
|
-
collateral_token_contract: ContractERC20BaseClass,
|
803
|
-
amount_wei: Wei,
|
804
|
-
api_keys: APIKeys,
|
805
|
-
web3: Web3 | None,
|
806
|
-
) -> None:
|
807
|
-
if isinstance(collateral_token_contract, ContractERC4626BaseClass):
|
808
|
-
auto_deposit_erc4626(collateral_token_contract, amount_wei, api_keys, web3)
|
809
|
-
|
810
|
-
elif isinstance(
|
811
|
-
collateral_token_contract, ContractDepositableWrapperERC20BaseClass
|
812
|
-
):
|
813
|
-
auto_deposit_depositable_wrapper_erc20(
|
814
|
-
collateral_token_contract, amount_wei, api_keys, web3
|
815
|
-
)
|
816
|
-
|
817
|
-
elif isinstance(collateral_token_contract, ContractERC20BaseClass):
|
818
|
-
if (
|
819
|
-
collateral_token_contract.balanceOf(
|
820
|
-
for_address=api_keys.bet_from_address, web3=web3
|
821
|
-
)
|
822
|
-
< amount_wei
|
823
|
-
):
|
824
|
-
raise ValueError(
|
825
|
-
f"Not enough of the collateral token, but it's not a wrapper token that we can deposit automatically."
|
826
|
-
)
|
827
|
-
|
828
|
-
else:
|
829
|
-
should_not_happen("Unsupported ERC20 contract type.")
|
830
|
-
|
831
|
-
|
832
|
-
def auto_deposit_depositable_wrapper_erc20(
|
833
|
-
collateral_token_contract: ContractDepositableWrapperERC20BaseClass,
|
834
|
-
amount_wei: Wei,
|
835
|
-
api_keys: APIKeys,
|
836
|
-
web3: Web3 | None,
|
837
|
-
) -> None:
|
838
|
-
collateral_token_balance = collateral_token_contract.balanceOf(
|
839
|
-
for_address=api_keys.bet_from_address, web3=web3
|
840
|
-
)
|
841
|
-
|
842
|
-
# If we have enough of the collateral token, we don't need to deposit.
|
843
|
-
if collateral_token_balance >= amount_wei:
|
844
|
-
return
|
845
|
-
|
846
|
-
# If we don't have enough, we need to deposit the difference.
|
847
|
-
left_to_deposit = Wei(amount_wei - collateral_token_balance)
|
848
|
-
collateral_token_contract.deposit(api_keys, left_to_deposit, web3=web3)
|
849
|
-
|
850
|
-
|
851
|
-
def auto_deposit_erc4626(
|
852
|
-
collateral_token_contract: ContractERC4626BaseClass,
|
853
|
-
asset_amount_wei: Wei,
|
854
|
-
api_keys: APIKeys,
|
855
|
-
web3: Web3 | None,
|
856
|
-
) -> None:
|
857
|
-
for_address = api_keys.bet_from_address
|
858
|
-
collateral_token_balance_in_shares = collateral_token_contract.balanceOf(
|
859
|
-
for_address=for_address, web3=web3
|
860
|
-
)
|
861
|
-
asset_amount_wei_in_shares = collateral_token_contract.convertToShares(
|
862
|
-
asset_amount_wei, web3
|
863
|
-
)
|
864
|
-
|
865
|
-
# If we have enough shares, we don't need to deposit.
|
866
|
-
if collateral_token_balance_in_shares >= asset_amount_wei_in_shares:
|
867
|
-
return
|
868
|
-
|
869
|
-
# If we need to deposit into erc4626, we first need to have enough of the asset token.
|
870
|
-
asset_token_contract = collateral_token_contract.get_asset_token_contract(web3=web3)
|
871
|
-
|
872
|
-
# If the asset token is Depositable Wrapper ERC-20, we can deposit it, in case we don't have enough.
|
873
|
-
if (
|
874
|
-
collateral_token_contract.get_asset_token_balance(for_address, web3)
|
875
|
-
< asset_amount_wei
|
876
|
-
):
|
877
|
-
if isinstance(asset_token_contract, ContractDepositableWrapperERC20BaseClass):
|
878
|
-
auto_deposit_depositable_wrapper_erc20(
|
879
|
-
asset_token_contract, asset_amount_wei, api_keys, web3
|
880
|
-
)
|
881
|
-
else:
|
882
|
-
raise ValueError(
|
883
|
-
"Not enough of the asset token, but it's not a depositable wrapper token that we can deposit automatically."
|
884
|
-
)
|
885
|
-
|
886
|
-
# Finally, we can deposit the asset token into the erc4626 vault.
|
887
|
-
collateral_token_balance_in_assets = collateral_token_contract.convertToAssets(
|
888
|
-
collateral_token_balance_in_shares, web3
|
889
|
-
)
|
890
|
-
left_to_deposit = Wei(asset_amount_wei - collateral_token_balance_in_assets)
|
891
|
-
collateral_token_contract.deposit_asset_token(left_to_deposit, api_keys, web3)
|
892
|
-
|
893
|
-
|
894
797
|
def to_gnosis_chain_contract(
|
895
798
|
contract: ContractERC20BaseClass,
|
896
799
|
) -> ContractERC20OnGnosisChain:
|
@@ -0,0 +1,133 @@
|
|
1
|
+
import asyncio
|
2
|
+
from datetime import timedelta
|
3
|
+
|
4
|
+
import httpx
|
5
|
+
from cowdao_cowpy import swap_tokens
|
6
|
+
from cowdao_cowpy.common.chains import Chain
|
7
|
+
from cowdao_cowpy.common.config import SupportedChainId
|
8
|
+
from cowdao_cowpy.common.constants import CowContractAddress
|
9
|
+
from cowdao_cowpy.order_book.api import OrderBookApi
|
10
|
+
from cowdao_cowpy.order_book.config import Envs, OrderBookAPIConfigFactory
|
11
|
+
from cowdao_cowpy.order_book.generated.model import (
|
12
|
+
Address,
|
13
|
+
OrderMetaData,
|
14
|
+
OrderQuoteRequest,
|
15
|
+
OrderQuoteSide1,
|
16
|
+
OrderQuoteSideKindSell,
|
17
|
+
OrderStatus,
|
18
|
+
TokenAmount,
|
19
|
+
)
|
20
|
+
from eth_account.signers.local import LocalAccount
|
21
|
+
from eth_typing.evm import ChecksumAddress
|
22
|
+
from web3 import Web3
|
23
|
+
from web3.types import Wei
|
24
|
+
|
25
|
+
from prediction_market_agent_tooling.config import APIKeys
|
26
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei, wei_type
|
27
|
+
from prediction_market_agent_tooling.loggers import logger
|
28
|
+
from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
|
29
|
+
from prediction_market_agent_tooling.tools.utils import utcnow
|
30
|
+
|
31
|
+
|
32
|
+
def get_order_book_api(env: Envs, chain: Chain) -> OrderBookApi:
|
33
|
+
chain_id = SupportedChainId(chain.value[0])
|
34
|
+
return OrderBookApi(OrderBookAPIConfigFactory.get_config(env, chain_id))
|
35
|
+
|
36
|
+
|
37
|
+
def get_buy_token_amount(
|
38
|
+
amount_wei: Wei,
|
39
|
+
sell_token: ChecksumAddress,
|
40
|
+
buy_token: ChecksumAddress,
|
41
|
+
chain: Chain = Chain.GNOSIS,
|
42
|
+
env: Envs = "prod",
|
43
|
+
) -> Wei:
|
44
|
+
order_book_api = get_order_book_api(env, chain)
|
45
|
+
order_quote_request = OrderQuoteRequest(
|
46
|
+
sellToken=Address(sell_token),
|
47
|
+
buyToken=Address(buy_token),
|
48
|
+
from_=Address(
|
49
|
+
"0x1234567890abcdef1234567890abcdef12345678"
|
50
|
+
), # Just random address, doesn't matter.
|
51
|
+
)
|
52
|
+
order_side = OrderQuoteSide1(
|
53
|
+
kind=OrderQuoteSideKindSell.sell,
|
54
|
+
sellAmountBeforeFee=TokenAmount(str(amount_wei)),
|
55
|
+
)
|
56
|
+
order_quote = asyncio.run(
|
57
|
+
order_book_api.post_quote(order_quote_request, order_side)
|
58
|
+
)
|
59
|
+
return wei_type(order_quote.quote.buyAmount.root)
|
60
|
+
|
61
|
+
|
62
|
+
def swap_tokens_waiting(
|
63
|
+
amount_wei: Wei,
|
64
|
+
sell_token: ChecksumAddress,
|
65
|
+
buy_token: ChecksumAddress,
|
66
|
+
api_keys: APIKeys,
|
67
|
+
chain: Chain = Chain.GNOSIS,
|
68
|
+
env: Envs = "prod",
|
69
|
+
web3: Web3 | None = None,
|
70
|
+
) -> OrderMetaData:
|
71
|
+
account = api_keys.get_account()
|
72
|
+
|
73
|
+
# Approve the CoW Swap Vault Relayer to get the sell token.
|
74
|
+
ContractERC20OnGnosisChain(address=sell_token).approve(
|
75
|
+
api_keys,
|
76
|
+
Web3.to_checksum_address(CowContractAddress.VAULT_RELAYER.value),
|
77
|
+
amount_wei=amount_wei,
|
78
|
+
web3=web3,
|
79
|
+
)
|
80
|
+
|
81
|
+
# CoW library uses async, so we need to wrap the call in asyncio.run for us to use it.
|
82
|
+
return asyncio.run(
|
83
|
+
swap_tokens_waiting_async(
|
84
|
+
amount_wei, sell_token, buy_token, account, chain, env
|
85
|
+
)
|
86
|
+
)
|
87
|
+
|
88
|
+
|
89
|
+
async def swap_tokens_waiting_async(
|
90
|
+
amount_wei: Wei,
|
91
|
+
sell_token: ChecksumAddress,
|
92
|
+
buy_token: ChecksumAddress,
|
93
|
+
account: LocalAccount,
|
94
|
+
chain: Chain,
|
95
|
+
env: Envs,
|
96
|
+
timeout: timedelta = timedelta(seconds=60),
|
97
|
+
) -> OrderMetaData:
|
98
|
+
order = await swap_tokens(
|
99
|
+
amount=amount_wei,
|
100
|
+
sell_token=sell_token,
|
101
|
+
buy_token=buy_token,
|
102
|
+
account=account,
|
103
|
+
chain=chain,
|
104
|
+
env=env,
|
105
|
+
)
|
106
|
+
logger.info(f"Order created: {order}")
|
107
|
+
start_time = utcnow()
|
108
|
+
|
109
|
+
while True:
|
110
|
+
async with httpx.AsyncClient() as client:
|
111
|
+
response = await client.get(order.url)
|
112
|
+
order_metadata = OrderMetaData.model_validate(response.json())
|
113
|
+
|
114
|
+
if order_metadata.status == OrderStatus.fulfilled:
|
115
|
+
logger.info(f"Order {order.uid} ({order.url}) completed.")
|
116
|
+
return order_metadata
|
117
|
+
|
118
|
+
elif order_metadata.status in (
|
119
|
+
OrderStatus.cancelled,
|
120
|
+
OrderStatus.expired,
|
121
|
+
):
|
122
|
+
raise ValueError(f"Order {order.uid} failed. {order.url}")
|
123
|
+
|
124
|
+
if utcnow() - start_time > timeout:
|
125
|
+
raise TimeoutError(
|
126
|
+
f"Timeout waiting for order {order.uid} to be completed. {order.url}"
|
127
|
+
)
|
128
|
+
|
129
|
+
logger.info(
|
130
|
+
f"Order status of {order.uid} ({order.url}): {order_metadata.status}, waiting..."
|
131
|
+
)
|
132
|
+
|
133
|
+
await asyncio.sleep(3.14)
|