prediction-market-agent-tooling 0.61.1.dev463__py3-none-any.whl → 0.61.1.dev482__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/deploy/agent.py +5 -4
- prediction_market_agent_tooling/deploy/betting_strategy.py +69 -53
- prediction_market_agent_tooling/gtypes.py +27 -105
- prediction_market_agent_tooling/jobs/jobs_models.py +7 -5
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +17 -13
- prediction_market_agent_tooling/markets/agent_market.py +52 -96
- prediction_market_agent_tooling/markets/blockchain_utils.py +27 -1
- prediction_market_agent_tooling/markets/data_models.py +44 -40
- prediction_market_agent_tooling/markets/manifold/api.py +6 -2
- prediction_market_agent_tooling/markets/manifold/data_models.py +25 -33
- prediction_market_agent_tooling/markets/manifold/manifold.py +11 -8
- prediction_market_agent_tooling/markets/market_fees.py +2 -4
- prediction_market_agent_tooling/markets/omen/data_models.py +57 -66
- prediction_market_agent_tooling/markets/omen/omen.py +249 -214
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +29 -31
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +14 -7
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +14 -20
- prediction_market_agent_tooling/markets/polymarket/data_models.py +3 -3
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +4 -4
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +5 -3
- prediction_market_agent_tooling/markets/seer/data_models.py +12 -8
- prediction_market_agent_tooling/markets/seer/seer.py +71 -85
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +5 -10
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +14 -9
- prediction_market_agent_tooling/monitor/monitor.py +2 -2
- prediction_market_agent_tooling/tools/balances.py +11 -9
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +10 -12
- prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +24 -27
- prediction_market_agent_tooling/tools/betting_strategies/utils.py +1 -3
- prediction_market_agent_tooling/tools/contract.py +10 -14
- prediction_market_agent_tooling/tools/cow/cow_manager.py +4 -3
- prediction_market_agent_tooling/tools/cow/cow_order.py +4 -3
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +1 -13
- prediction_market_agent_tooling/tools/omen/sell_positions.py +3 -6
- prediction_market_agent_tooling/tools/safe.py +6 -5
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +30 -32
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +22 -5
- prediction_market_agent_tooling/tools/tokens/main_token.py +2 -2
- prediction_market_agent_tooling/tools/utils.py +8 -14
- prediction_market_agent_tooling/tools/web3_utils.py +41 -24
- {prediction_market_agent_tooling-0.61.1.dev463.dist-info → prediction_market_agent_tooling-0.61.1.dev482.dist-info}/METADATA +1 -2
- {prediction_market_agent_tooling-0.61.1.dev463.dist-info → prediction_market_agent_tooling-0.61.1.dev482.dist-info}/RECORD +45 -48
- prediction_market_agent_tooling/tools/_generic_value.py +0 -255
- prediction_market_agent_tooling/tools/tokens/token_utils.py +0 -46
- prediction_market_agent_tooling/tools/tokens/usd.py +0 -63
- {prediction_market_agent_tooling-0.61.1.dev463.dist-info → prediction_market_agent_tooling-0.61.1.dev482.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.61.1.dev463.dist-info → prediction_market_agent_tooling-0.61.1.dev482.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.61.1.dev463.dist-info → prediction_market_agent_tooling-0.61.1.dev482.dist-info}/entry_points.txt +0 -0
@@ -15,7 +15,6 @@ from prediction_market_agent_tooling.gtypes import (
|
|
15
15
|
ChainID,
|
16
16
|
ChecksumAddress,
|
17
17
|
Nonce,
|
18
|
-
Token,
|
19
18
|
TxParams,
|
20
19
|
TxReceipt,
|
21
20
|
Wei,
|
@@ -154,7 +153,7 @@ class ContractBaseClass(BaseModel):
|
|
154
153
|
api_keys=api_keys,
|
155
154
|
function_name=function_name,
|
156
155
|
function_params=function_params,
|
157
|
-
tx_params={"value": amount_wei
|
156
|
+
tx_params={"value": amount_wei, **(tx_params or {})},
|
158
157
|
timeout=timeout,
|
159
158
|
web3=web3,
|
160
159
|
)
|
@@ -244,13 +243,12 @@ class ContractERC20BaseClass(ContractBaseClass):
|
|
244
243
|
)
|
245
244
|
|
246
245
|
def balanceOf(self, for_address: ChecksumAddress, web3: Web3 | None = None) -> Wei:
|
247
|
-
balance =
|
246
|
+
balance: Wei = self.call("balanceOf", [for_address], web3=web3)
|
248
247
|
return balance
|
249
248
|
|
250
|
-
def
|
251
|
-
|
252
|
-
|
253
|
-
return self.balanceOf(for_address, web3=web3).as_token
|
249
|
+
def get_in_shares(self, amount: Wei, web3: Web3 | None = None) -> Wei:
|
250
|
+
# ERC-20 just holds the token, so the exact amount we send there, is the amount of shares we have there.
|
251
|
+
return amount
|
254
252
|
|
255
253
|
|
256
254
|
class ContractDepositableWrapperERC20BaseClass(ContractERC20BaseClass):
|
@@ -357,11 +355,11 @@ class ContractERC4626BaseClass(ContractERC20BaseClass):
|
|
357
355
|
return self.withdraw(api_keys=api_keys, assets_wei=assets, web3=web3)
|
358
356
|
|
359
357
|
def convertToShares(self, assets: Wei, web3: Web3 | None = None) -> Wei:
|
360
|
-
shares =
|
358
|
+
shares: Wei = self.call("convertToShares", [assets], web3=web3)
|
361
359
|
return shares
|
362
360
|
|
363
361
|
def convertToAssets(self, shares: Wei, web3: Web3 | None = None) -> Wei:
|
364
|
-
assets =
|
362
|
+
assets: Wei = self.call("convertToAssets", [shares], web3=web3)
|
365
363
|
return assets
|
366
364
|
|
367
365
|
def get_asset_token_contract(
|
@@ -436,8 +434,8 @@ class ContractERC721BaseClass(ContractBaseClass):
|
|
436
434
|
web3=web3,
|
437
435
|
)
|
438
436
|
|
439
|
-
def balanceOf(self, owner: ChecksumAddress, web3: Web3 | None = None) ->
|
440
|
-
balance =
|
437
|
+
def balanceOf(self, owner: ChecksumAddress, web3: Web3 | None = None) -> int:
|
438
|
+
balance: int = self.call("balanceOf", [owner], web3=web3)
|
441
439
|
return balance
|
442
440
|
|
443
441
|
def owner_of(self, token_id: int, web3: Web3 | None = None) -> ChecksumAddress:
|
@@ -606,14 +604,12 @@ def contract_implements_function(
|
|
606
604
|
|
607
605
|
|
608
606
|
def init_collateral_token_contract(
|
609
|
-
address: ChecksumAddress, web3: Web3
|
607
|
+
address: ChecksumAddress, web3: Web3
|
610
608
|
) -> ContractERC20BaseClass:
|
611
609
|
"""
|
612
610
|
Checks if the given contract is Depositable ERC-20, ERC-20 or ERC-4626 and returns the appropriate class instance.
|
613
611
|
Throws an error if the contract is neither of them.
|
614
612
|
"""
|
615
|
-
web3 = web3 or RPCConfig().get_web3()
|
616
|
-
|
617
613
|
if contract_implements_function(address, "asset", web3=web3):
|
618
614
|
return ContractERC4626BaseClass(address=address)
|
619
615
|
|
@@ -19,9 +19,10 @@ from web3 import Web3
|
|
19
19
|
from web3.constants import ADDRESS_ZERO
|
20
20
|
|
21
21
|
from prediction_market_agent_tooling.config import APIKeys
|
22
|
-
from prediction_market_agent_tooling.gtypes import ChecksumAddress,
|
22
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei, xDai
|
23
23
|
from prediction_market_agent_tooling.loggers import logger
|
24
24
|
from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting
|
25
|
+
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
|
25
26
|
|
26
27
|
COW_ENV: Envs = "prod"
|
27
28
|
|
@@ -93,14 +94,14 @@ class CowManager:
|
|
93
94
|
|
94
95
|
@staticmethod
|
95
96
|
def swap(
|
96
|
-
amount:
|
97
|
+
amount: xDai,
|
97
98
|
sell_token: ChecksumAddress,
|
98
99
|
buy_token: ChecksumAddress,
|
99
100
|
api_keys: APIKeys,
|
100
101
|
web3: Web3 | None = None,
|
101
102
|
) -> OrderMetaData:
|
102
103
|
order_metadata = swap_tokens_waiting(
|
103
|
-
amount_wei=amount
|
104
|
+
amount_wei=xdai_to_wei(amount),
|
104
105
|
sell_token=sell_token,
|
105
106
|
buy_token=buy_token,
|
106
107
|
api_keys=api_keys,
|
@@ -21,9 +21,10 @@ from cowdao_cowpy.order_book.generated.model import (
|
|
21
21
|
from eth_account.signers.local import LocalAccount
|
22
22
|
from eth_typing.evm import ChecksumAddress
|
23
23
|
from web3 import Web3
|
24
|
+
from web3.types import Wei
|
24
25
|
|
25
26
|
from prediction_market_agent_tooling.config import APIKeys
|
26
|
-
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei
|
27
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei, wei_type
|
27
28
|
from prediction_market_agent_tooling.loggers import logger
|
28
29
|
from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
|
29
30
|
from prediction_market_agent_tooling.tools.utils import utcnow
|
@@ -61,7 +62,7 @@ def get_buy_token_amount(
|
|
61
62
|
order_quote = asyncio.run(
|
62
63
|
order_book_api.post_quote(order_quote_request, order_side)
|
63
64
|
)
|
64
|
-
return
|
65
|
+
return wei_type(order_quote.quote.buyAmount.root)
|
65
66
|
|
66
67
|
|
67
68
|
def swap_tokens_waiting(
|
@@ -101,7 +102,7 @@ async def swap_tokens_waiting_async(
|
|
101
102
|
timeout: timedelta = timedelta(seconds=60),
|
102
103
|
) -> OrderMetaData:
|
103
104
|
order = await swap_tokens(
|
104
|
-
amount=amount_wei
|
105
|
+
amount=amount_wei,
|
105
106
|
sell_token=sell_token,
|
106
107
|
buy_token=buy_token,
|
107
108
|
account=account,
|
@@ -13,9 +13,6 @@ from prediction_market_agent_tooling.markets.data_models import (
|
|
13
13
|
TradeType,
|
14
14
|
)
|
15
15
|
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
16
|
-
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
17
|
-
WRAPPED_XDAI_CONTRACT_ADDRESS,
|
18
|
-
)
|
19
16
|
from prediction_market_agent_tooling.tools.utils import DatetimeUTC
|
20
17
|
|
21
18
|
|
@@ -150,20 +147,11 @@ def get_trace_for_bet(
|
|
150
147
|
# Filter for traces with the same bet outcome and amount
|
151
148
|
traces_for_bet: list[ProcessMarketTrace] = []
|
152
149
|
for t in traces:
|
153
|
-
if (
|
154
|
-
t.market.collateral_token_contract_address_checksummed
|
155
|
-
not in WRAPPED_XDAI_CONTRACT_ADDRESS
|
156
|
-
):
|
157
|
-
# TODO: We need to compute bet amount token in USD here, but at the time of bet placement!
|
158
|
-
logger.warning(
|
159
|
-
"This currently works only for WXDAI markets, because we need to compare against USD value."
|
160
|
-
)
|
161
|
-
continue
|
162
150
|
# Cannot use exact comparison due to gas fees
|
163
151
|
if (
|
164
152
|
t.buy_trade
|
165
153
|
and t.buy_trade.outcome == bet.outcome
|
166
|
-
and np.isclose(t.buy_trade.amount.
|
154
|
+
and np.isclose(t.buy_trade.amount.amount, bet.amount.amount)
|
167
155
|
):
|
168
156
|
traces_for_bet.append(t)
|
169
157
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from datetime import timedelta
|
2
2
|
|
3
3
|
from prediction_market_agent_tooling.config import APIKeys
|
4
|
-
from prediction_market_agent_tooling.gtypes import USD
|
5
4
|
from prediction_market_agent_tooling.loggers import logger
|
6
5
|
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
7
6
|
from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
|
@@ -24,9 +23,7 @@ def sell_all(
|
|
24
23
|
better_address=better_address,
|
25
24
|
market_opening_after=utcnow() + timedelta(days=closing_later_than_days),
|
26
25
|
)
|
27
|
-
bets_total_usd = sum(
|
28
|
-
(b.get_collateral_amount_usd() for b in bets), start=USD.zero()
|
29
|
-
)
|
26
|
+
bets_total_usd = sum(b.collateral_amount_usd for b in bets)
|
30
27
|
unique_market_urls = set(b.fpmm.url for b in bets)
|
31
28
|
starting_balance = get_balances(better_address)
|
32
29
|
new_balance = starting_balance
|
@@ -40,9 +37,9 @@ def sell_all(
|
|
40
37
|
outcome = agent_market.outcomes[bet.outcomeIndex]
|
41
38
|
current_token_balance = agent_market.get_token_balance(better_address, outcome)
|
42
39
|
|
43
|
-
if current_token_balance.
|
40
|
+
if current_token_balance.amount <= OmenAgentMarket.get_tiny_bet_amount().amount:
|
44
41
|
logger.info(
|
45
|
-
f"Skipping bet on {bet.fpmm.url} because the actual balance is unreasonably low {current_token_balance}."
|
42
|
+
f"Skipping bet on {bet.fpmm.url} because the actual balance is unreasonably low {current_token_balance.amount}."
|
46
43
|
)
|
47
44
|
continue
|
48
45
|
|
@@ -11,10 +11,11 @@ from safe_eth.eth.constants import NULL_ADDRESS
|
|
11
11
|
from safe_eth.eth.contracts import get_safe_V1_4_1_contract
|
12
12
|
from safe_eth.safe.proxy_factory import ProxyFactoryV141
|
13
13
|
from safe_eth.safe.safe import SafeV141
|
14
|
+
from web3.types import Wei
|
14
15
|
|
15
|
-
from prediction_market_agent_tooling.gtypes import Wei
|
16
16
|
from prediction_market_agent_tooling.loggers import logger
|
17
17
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
18
|
+
from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai
|
18
19
|
|
19
20
|
|
20
21
|
def create_safe(
|
@@ -62,10 +63,10 @@ def create_safe(
|
|
62
63
|
f"does not exist on network {ethereum_network.name}"
|
63
64
|
)
|
64
65
|
|
65
|
-
account_balance =
|
66
|
-
account_balance_xdai = account_balance
|
66
|
+
account_balance = ethereum_client.get_balance(account.address)
|
67
|
+
account_balance_xdai = wei_to_xdai(account_balance)
|
67
68
|
# We set a reasonable expected balance below for Safe deployment not to fail.
|
68
|
-
if account_balance_xdai
|
69
|
+
if account_balance_xdai < 0.01:
|
69
70
|
raise ValueError(
|
70
71
|
f"Client's balance is {account_balance_xdai} xDAI, too low for deploying a Safe."
|
71
72
|
)
|
@@ -107,7 +108,7 @@ def create_safe(
|
|
107
108
|
payment_token,
|
108
109
|
payment,
|
109
110
|
payment_receiver,
|
110
|
-
).build_transaction({"gas": 1, "gasPrice": Wei(1)
|
111
|
+
).build_transaction({"gas": 1, "gasPrice": Wei(1)})["data"]
|
111
112
|
)
|
112
113
|
|
113
114
|
proxy_factory = ProxyFactoryV141(proxy_factory_address, ethereum_client)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from web3 import Web3
|
2
2
|
|
3
3
|
from prediction_market_agent_tooling.config import APIKeys
|
4
|
-
from prediction_market_agent_tooling.gtypes import
|
4
|
+
from prediction_market_agent_tooling.gtypes import Wei, wei_type
|
5
5
|
from prediction_market_agent_tooling.loggers import logger
|
6
6
|
from prediction_market_agent_tooling.tools.contract import (
|
7
7
|
ContractDepositableWrapperERC20BaseClass,
|
@@ -9,44 +9,32 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
9
9
|
ContractERC20OnGnosisChain,
|
10
10
|
ContractERC4626BaseClass,
|
11
11
|
)
|
12
|
-
from prediction_market_agent_tooling.tools.cow.cow_order import
|
12
|
+
from prediction_market_agent_tooling.tools.cow.cow_order import (
|
13
|
+
get_buy_token_amount,
|
14
|
+
swap_tokens_waiting,
|
15
|
+
)
|
13
16
|
from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN
|
14
|
-
from prediction_market_agent_tooling.tools.tokens.usd import get_usd_in_token
|
15
17
|
from prediction_market_agent_tooling.tools.utils import should_not_happen
|
18
|
+
from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai
|
16
19
|
|
17
20
|
|
18
21
|
def auto_deposit_collateral_token(
|
19
22
|
collateral_token_contract: ContractERC20BaseClass,
|
20
|
-
|
23
|
+
amount_wei: Wei,
|
21
24
|
api_keys: APIKeys,
|
22
25
|
web3: Web3 | None = None,
|
23
|
-
slippage: float = 0.01,
|
24
26
|
) -> None:
|
25
|
-
collateral_amount_wei = (
|
26
|
-
collateral_amount_wei_or_usd
|
27
|
-
if isinstance(collateral_amount_wei_or_usd, Wei)
|
28
|
-
else get_usd_in_token(
|
29
|
-
collateral_amount_wei_or_usd, collateral_token_contract.address
|
30
|
-
).as_wei
|
31
|
-
)
|
32
|
-
# Deposit a bit more, to cover any later changes in the price.
|
33
|
-
collateral_amount_wei = collateral_amount_wei.with_fraction(slippage)
|
34
|
-
|
35
27
|
if isinstance(collateral_token_contract, ContractDepositableWrapperERC20BaseClass):
|
36
28
|
# In this case, we can use deposit function directly, no need to go through DEX.
|
37
29
|
auto_deposit_depositable_wrapper_erc20(
|
38
|
-
collateral_token_contract,
|
30
|
+
collateral_token_contract, amount_wei, api_keys, web3
|
39
31
|
)
|
40
32
|
|
41
33
|
elif isinstance(collateral_token_contract, ContractERC4626BaseClass):
|
42
|
-
auto_deposit_erc4626(
|
43
|
-
collateral_token_contract, collateral_amount_wei, api_keys, web3
|
44
|
-
)
|
34
|
+
auto_deposit_erc4626(collateral_token_contract, amount_wei, api_keys, web3)
|
45
35
|
|
46
36
|
elif isinstance(collateral_token_contract, ContractERC20BaseClass):
|
47
|
-
auto_deposit_erc20(
|
48
|
-
collateral_token_contract, collateral_amount_wei, api_keys, web3
|
49
|
-
)
|
37
|
+
auto_deposit_erc20(collateral_token_contract, amount_wei, api_keys, web3)
|
50
38
|
|
51
39
|
else:
|
52
40
|
should_not_happen("Unsupported ERC20 contract type.")
|
@@ -67,16 +55,16 @@ def auto_deposit_depositable_wrapper_erc20(
|
|
67
55
|
return
|
68
56
|
|
69
57
|
# If we don't have enough, we need to deposit the difference.
|
70
|
-
left_to_deposit = amount_wei - collateral_token_balance
|
58
|
+
left_to_deposit = Wei(amount_wei - collateral_token_balance)
|
71
59
|
logger.info(
|
72
|
-
f"Depositing {left_to_deposit
|
60
|
+
f"Depositing {wei_to_xdai(left_to_deposit)} {collateral_token_contract.symbol()}."
|
73
61
|
)
|
74
62
|
collateral_token_contract.deposit(api_keys, left_to_deposit, web3=web3)
|
75
63
|
|
76
64
|
|
77
65
|
def auto_deposit_erc4626(
|
78
66
|
collateral_token_contract: ContractERC4626BaseClass,
|
79
|
-
|
67
|
+
asset_amount_wei: Wei,
|
80
68
|
api_keys: APIKeys,
|
81
69
|
web3: Web3 | None,
|
82
70
|
) -> None:
|
@@ -84,12 +72,12 @@ def auto_deposit_erc4626(
|
|
84
72
|
collateral_token_balance_in_shares = collateral_token_contract.balanceOf(
|
85
73
|
for_address=for_address, web3=web3
|
86
74
|
)
|
87
|
-
|
88
|
-
|
75
|
+
asset_amount_wei_in_shares = collateral_token_contract.convertToShares(
|
76
|
+
asset_amount_wei, web3
|
89
77
|
)
|
90
78
|
|
91
79
|
# If we have enough shares, we don't need to deposit.
|
92
|
-
if collateral_token_balance_in_shares >=
|
80
|
+
if collateral_token_balance_in_shares >= asset_amount_wei_in_shares:
|
93
81
|
return
|
94
82
|
|
95
83
|
# If we need to deposit into erc4626, we first need to have enough of the asset token.
|
@@ -101,7 +89,7 @@ def auto_deposit_erc4626(
|
|
101
89
|
collateral_token_balance_in_assets = collateral_token_contract.convertToAssets(
|
102
90
|
collateral_token_balance_in_shares, web3
|
103
91
|
)
|
104
|
-
left_to_deposit = asset_amount_wei - collateral_token_balance_in_assets
|
92
|
+
left_to_deposit = Wei(asset_amount_wei - collateral_token_balance_in_assets)
|
105
93
|
if (
|
106
94
|
collateral_token_contract.get_asset_token_balance(for_address, web3)
|
107
95
|
< left_to_deposit
|
@@ -120,22 +108,32 @@ def auto_deposit_erc4626(
|
|
120
108
|
|
121
109
|
def auto_deposit_erc20(
|
122
110
|
collateral_token_contract: ContractERC20BaseClass,
|
123
|
-
|
111
|
+
amount_xdai_wei: Wei,
|
124
112
|
api_keys: APIKeys,
|
125
113
|
web3: Web3 | None,
|
126
114
|
) -> None:
|
115
|
+
# How much it is in the other token (collateral token).
|
116
|
+
collateral_amount_wei = get_buy_token_amount(
|
117
|
+
amount_xdai_wei,
|
118
|
+
KEEPING_ERC20_TOKEN.address,
|
119
|
+
collateral_token_contract.address,
|
120
|
+
)
|
127
121
|
# How much do we have already in the other token (collateral token).
|
128
122
|
collateral_balance_wei = collateral_token_contract.balanceOf(
|
129
123
|
api_keys.bet_from_address
|
130
124
|
)
|
131
125
|
# Amount of collateral token remaining to get.
|
132
126
|
remaining_to_get_in_collateral_wei = max(
|
133
|
-
|
127
|
+
0, collateral_amount_wei - collateral_balance_wei
|
134
128
|
)
|
135
129
|
if not remaining_to_get_in_collateral_wei:
|
136
130
|
return
|
137
131
|
# Estimate of how much of the source token we need to sell in order to fill the remaining collateral amount, with 1% slippage to be sure.
|
138
|
-
amount_to_sell_wei =
|
132
|
+
amount_to_sell_wei = wei_type(
|
133
|
+
(remaining_to_get_in_collateral_wei * amount_xdai_wei)
|
134
|
+
/ collateral_amount_wei
|
135
|
+
* 1.01
|
136
|
+
)
|
139
137
|
# If we don't have enough of the source token.
|
140
138
|
if amount_to_sell_wei > ContractERC20OnGnosisChain(
|
141
139
|
address=KEEPING_ERC20_TOKEN.address
|
@@ -7,9 +7,16 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
7
7
|
ContractERC20BaseClass,
|
8
8
|
ContractERC4626BaseClass,
|
9
9
|
)
|
10
|
-
from prediction_market_agent_tooling.tools.cow.cow_order import
|
10
|
+
from prediction_market_agent_tooling.tools.cow.cow_order import (
|
11
|
+
get_buy_token_amount,
|
12
|
+
swap_tokens_waiting,
|
13
|
+
)
|
11
14
|
from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN
|
12
15
|
from prediction_market_agent_tooling.tools.utils import should_not_happen
|
16
|
+
from prediction_market_agent_tooling.tools.web3_utils import (
|
17
|
+
remove_fraction,
|
18
|
+
wei_to_xdai,
|
19
|
+
)
|
13
20
|
|
14
21
|
|
15
22
|
def auto_withdraw_collateral_token(
|
@@ -20,7 +27,10 @@ def auto_withdraw_collateral_token(
|
|
20
27
|
slippage: float = 0.001,
|
21
28
|
) -> None:
|
22
29
|
# Small slippage as exact exchange rate constantly changes and we don't care about small differences.
|
23
|
-
amount_wei =
|
30
|
+
amount_wei = remove_fraction(
|
31
|
+
amount_wei,
|
32
|
+
slippage,
|
33
|
+
)
|
24
34
|
|
25
35
|
if not amount_wei:
|
26
36
|
logger.warning(
|
@@ -41,7 +51,7 @@ def auto_withdraw_collateral_token(
|
|
41
51
|
):
|
42
52
|
# If the ERC4626 is backed by KEEPING_ERC20_TOKEN, we can withdraw it directly, no need to go through DEX.
|
43
53
|
logger.info(
|
44
|
-
f"Withdrawing {amount_wei
|
54
|
+
f"Withdrawing {wei_to_xdai(amount_wei)} from {collateral_token_contract.symbol_cached(web3)} into {KEEPING_ERC20_TOKEN.symbol_cached(web3)}"
|
45
55
|
)
|
46
56
|
collateral_token_contract.withdraw_in_shares(
|
47
57
|
api_keys,
|
@@ -50,11 +60,18 @@ def auto_withdraw_collateral_token(
|
|
50
60
|
)
|
51
61
|
elif isinstance(collateral_token_contract, ContractERC20BaseClass):
|
52
62
|
logger.info(
|
53
|
-
f"Swapping {amount_wei
|
63
|
+
f"Swapping {wei_to_xdai(amount_wei)} {collateral_token_contract.symbol_cached(web3)} into {KEEPING_ERC20_TOKEN.symbol_cached(web3)}"
|
54
64
|
)
|
55
65
|
# Otherwise, DEX will handle the rest of token swaps.
|
66
|
+
# First, convert `amount_wei` from xDai-based value into the collateral token-based value.
|
67
|
+
collateral_amount_wei = get_buy_token_amount(
|
68
|
+
amount_wei,
|
69
|
+
KEEPING_ERC20_TOKEN.address,
|
70
|
+
collateral_token_contract.address,
|
71
|
+
)
|
72
|
+
# And then sell it.
|
56
73
|
swap_tokens_waiting(
|
57
|
-
amount_wei=
|
74
|
+
amount_wei=collateral_amount_wei,
|
58
75
|
sell_token=collateral_token_contract.address,
|
59
76
|
buy_token=KEEPING_ERC20_TOKEN.address,
|
60
77
|
api_keys=api_keys,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from prediction_market_agent_tooling.gtypes import
|
1
|
+
from prediction_market_agent_tooling.gtypes import xdai_type
|
2
2
|
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
3
3
|
WRAPPED_XDAI_CONTRACT_ADDRESS,
|
4
4
|
)
|
@@ -15,4 +15,4 @@ KEEPING_ERC20_TOKEN = ContractDepositableWrapperERC20OnGnosisChain(
|
|
15
15
|
address=WRAPPED_XDAI_CONTRACT_ADDRESS
|
16
16
|
)
|
17
17
|
|
18
|
-
MINIMUM_NATIVE_TOKEN_IN_EOA_FOR_FEES =
|
18
|
+
MINIMUM_NATIVE_TOKEN_IN_EOA_FOR_FEES = xdai_type(0.1)
|
@@ -9,13 +9,7 @@ from pydantic import BaseModel, ValidationError
|
|
9
9
|
from scipy.optimize import newton
|
10
10
|
from scipy.stats import entropy
|
11
11
|
|
12
|
-
from prediction_market_agent_tooling.gtypes import
|
13
|
-
DatetimeUTC,
|
14
|
-
OutcomeToken,
|
15
|
-
Probability,
|
16
|
-
SecretStr,
|
17
|
-
Token,
|
18
|
-
)
|
12
|
+
from prediction_market_agent_tooling.gtypes import DatetimeUTC, Probability, SecretStr
|
19
13
|
from prediction_market_agent_tooling.loggers import logger
|
20
14
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
21
15
|
|
@@ -188,11 +182,11 @@ def prob_uncertainty(prob: Probability) -> float:
|
|
188
182
|
|
189
183
|
|
190
184
|
def calculate_sell_amount_in_collateral(
|
191
|
-
shares_to_sell:
|
192
|
-
holdings:
|
193
|
-
other_holdings:
|
185
|
+
shares_to_sell: float,
|
186
|
+
holdings: float,
|
187
|
+
other_holdings: float,
|
194
188
|
fees: MarketFees,
|
195
|
-
) ->
|
189
|
+
) -> float:
|
196
190
|
"""
|
197
191
|
Computes the amount of collateral that needs to be sold to get `shares`
|
198
192
|
amount of shares. Returns None if the amount can't be computed.
|
@@ -205,11 +199,11 @@ def calculate_sell_amount_in_collateral(
|
|
205
199
|
raise ValueError("All share args must be greater than 0")
|
206
200
|
|
207
201
|
def f(r: float) -> float:
|
208
|
-
R =
|
202
|
+
R = (r + fees.absolute) / (1 - fees.bet_proportion)
|
209
203
|
first_term = other_holdings - R
|
210
204
|
second_term = holdings + shares_to_sell - R
|
211
205
|
third_term = holdings * other_holdings
|
212
|
-
return (
|
206
|
+
return (first_term * second_term) - third_term
|
213
207
|
|
214
208
|
amount_to_sell = newton(f, 0)
|
215
|
-
return
|
209
|
+
return float(amount_to_sell) * 0.999999 # Avoid rounding errors
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import binascii
|
2
2
|
import secrets
|
3
|
-
from typing import Any, Optional
|
3
|
+
from typing import Any, Optional, TypeVar
|
4
4
|
|
5
5
|
import base58
|
6
6
|
import tenacity
|
@@ -11,7 +11,7 @@ from safe_eth.eth import EthereumClient
|
|
11
11
|
from safe_eth.safe.safe import SafeV141
|
12
12
|
from web3 import Web3
|
13
13
|
from web3.constants import HASH_ZERO
|
14
|
-
from web3.types import AccessList, AccessListEntry, Nonce, TxParams, TxReceipt
|
14
|
+
from web3.types import AccessList, AccessListEntry, Nonce, TxParams, TxReceipt, Wei
|
15
15
|
|
16
16
|
from prediction_market_agent_tooling.gtypes import (
|
17
17
|
ABI,
|
@@ -23,13 +23,12 @@ from prediction_market_agent_tooling.gtypes import (
|
|
23
23
|
PrivateKey,
|
24
24
|
private_key_type,
|
25
25
|
xDai,
|
26
|
-
|
26
|
+
xdai_type,
|
27
27
|
)
|
28
28
|
from prediction_market_agent_tooling.loggers import logger
|
29
|
-
from prediction_market_agent_tooling.tools._generic_value import _GenericValue
|
30
29
|
|
31
30
|
ONE_NONCE = Nonce(1)
|
32
|
-
ONE_XDAI =
|
31
|
+
ONE_XDAI = xdai_type(1)
|
33
32
|
ZERO_BYTES = HexBytes(HASH_ZERO)
|
34
33
|
NOT_REVERTED_ICASE_REGEX_PATTERN = "(?i)(?!.*reverted.*)"
|
35
34
|
|
@@ -43,6 +42,17 @@ def private_key_to_public_key(private_key: SecretStr) -> ChecksumAddress:
|
|
43
42
|
return verify_address(account.address)
|
44
43
|
|
45
44
|
|
45
|
+
def wei_to_xdai(wei: Wei) -> xDai:
|
46
|
+
return xDai(float(Web3.from_wei(wei, "ether")))
|
47
|
+
|
48
|
+
|
49
|
+
def xdai_to_wei(native: xDai) -> Wei:
|
50
|
+
return Web3.to_wei(native, "ether")
|
51
|
+
|
52
|
+
|
53
|
+
RemoveOrAddFractionAmountType = TypeVar("RemoveOrAddFractionAmountType", bound=int)
|
54
|
+
|
55
|
+
|
46
56
|
def verify_address(address: str) -> ChecksumAddress:
|
47
57
|
if not Web3.is_checksum_address(address):
|
48
58
|
raise ValueError(
|
@@ -51,6 +61,26 @@ def verify_address(address: str) -> ChecksumAddress:
|
|
51
61
|
return ChecksumAddress(HexAddress(HexStr(address)))
|
52
62
|
|
53
63
|
|
64
|
+
def remove_fraction(
|
65
|
+
amount: RemoveOrAddFractionAmountType, fraction: float
|
66
|
+
) -> RemoveOrAddFractionAmountType:
|
67
|
+
"""Removes the given fraction from the given integer-bounded amount and returns the value as an original type."""
|
68
|
+
if 0 <= fraction <= 1:
|
69
|
+
keep_percentage = 1 - fraction
|
70
|
+
return type(amount)(int(amount * keep_percentage))
|
71
|
+
raise ValueError(f"The given fraction {fraction!r} is not in the range [0, 1].")
|
72
|
+
|
73
|
+
|
74
|
+
def add_fraction(
|
75
|
+
amount: RemoveOrAddFractionAmountType, fraction: float
|
76
|
+
) -> RemoveOrAddFractionAmountType:
|
77
|
+
"""Adds the given fraction to the given integer-bounded amount and returns the value as an original type."""
|
78
|
+
if 0 <= fraction <= 1:
|
79
|
+
keep_percentage = 1 + fraction
|
80
|
+
return type(amount)(int(amount * keep_percentage))
|
81
|
+
raise ValueError(f"The given fraction {fraction!r} is not in the range [0, 1].")
|
82
|
+
|
83
|
+
|
54
84
|
def check_tx_receipt(receipt: TxReceipt) -> None:
|
55
85
|
if receipt["status"] != 1:
|
56
86
|
raise ValueError(
|
@@ -58,20 +88,7 @@ def check_tx_receipt(receipt: TxReceipt) -> None:
|
|
58
88
|
)
|
59
89
|
|
60
90
|
|
61
|
-
def unwrap_generic_value(value: Any) -> Any:
|
62
|
-
if value is None:
|
63
|
-
return None
|
64
|
-
if isinstance(value, _GenericValue):
|
65
|
-
return value.value
|
66
|
-
elif isinstance(value, list):
|
67
|
-
return [unwrap_generic_value(v) for v in value]
|
68
|
-
elif isinstance(value, dict):
|
69
|
-
return {k: unwrap_generic_value(v) for k, v in value.items()}
|
70
|
-
return value
|
71
|
-
|
72
|
-
|
73
91
|
def parse_function_params(params: Optional[list[Any] | dict[str, Any]]) -> list[Any]:
|
74
|
-
params = unwrap_generic_value(params)
|
75
92
|
if params is None:
|
76
93
|
return []
|
77
94
|
if isinstance(params, list):
|
@@ -155,8 +172,8 @@ def _prepare_tx_params(
|
|
155
172
|
# Don't retry on `reverted` messages, as they would always fail again.
|
156
173
|
# TODO: Check this, see https://github.com/gnosis/prediction-market-agent-tooling/issues/625.
|
157
174
|
# retry=tenacity.retry_if_exception_message(match=NOT_REVERTED_ICASE_REGEX_PATTERN),
|
158
|
-
wait=tenacity.wait_chain(*[tenacity.wait_fixed(n) for n in range(1,
|
159
|
-
stop=tenacity.stop_after_attempt(
|
175
|
+
wait=tenacity.wait_chain(*[tenacity.wait_fixed(n) for n in range(1, 10)]),
|
176
|
+
stop=tenacity.stop_after_attempt(9),
|
160
177
|
after=lambda x: logger.debug(
|
161
178
|
f"send_function_on_contract_tx failed, {x.attempt_number=}."
|
162
179
|
),
|
@@ -193,7 +210,7 @@ def send_function_on_contract_tx(
|
|
193
210
|
# Don't retry on `reverted` messages, as they would always fail again.
|
194
211
|
# TODO: Check this, see https://github.com/gnosis/prediction-market-agent-tooling/issues/625.
|
195
212
|
# retry=tenacity.retry_if_exception_message(match=NOT_REVERTED_ICASE_REGEX_PATTERN),
|
196
|
-
wait=tenacity.wait_chain(*[tenacity.wait_fixed(n) for n in range(1,
|
213
|
+
wait=tenacity.wait_chain(*[tenacity.wait_fixed(n) for n in range(1, 10)]),
|
197
214
|
stop=tenacity.stop_after_attempt(5),
|
198
215
|
after=lambda x: logger.debug(
|
199
216
|
f"send_function_on_contract_tx_using_safe failed, {x.attempt_number=}."
|
@@ -290,14 +307,14 @@ def send_xdai_to(
|
|
290
307
|
web3: Web3,
|
291
308
|
from_private_key: PrivateKey,
|
292
309
|
to_address: ChecksumAddress,
|
293
|
-
value:
|
310
|
+
value: Wei,
|
294
311
|
data_text: Optional[str | bytes] = None,
|
295
312
|
tx_params: Optional[TxParams] = None,
|
296
313
|
timeout: int = 180,
|
297
314
|
) -> TxReceipt:
|
298
315
|
from_address = private_key_to_public_key(from_private_key)
|
299
316
|
|
300
|
-
tx_params_new: TxParams = {"value": value
|
317
|
+
tx_params_new: TxParams = {"value": value, "to": to_address}
|
301
318
|
if data_text is not None:
|
302
319
|
tx_params_new["data"] = (
|
303
320
|
Web3.to_bytes(text=data_text)
|
@@ -343,7 +360,7 @@ def byte32_to_ipfscidv0(hex: HexBytes) -> IPFSCIDVersion0:
|
|
343
360
|
|
344
361
|
|
345
362
|
@tenacity.retry(
|
346
|
-
wait=tenacity.wait_chain(*[tenacity.wait_fixed(n) for n in range(1,
|
363
|
+
wait=tenacity.wait_chain(*[tenacity.wait_fixed(n) for n in range(1, 10)]),
|
347
364
|
stop=tenacity.stop_after_attempt(5),
|
348
365
|
after=lambda x: logger.debug(
|
349
366
|
f"get_receipt_block_timestamp failed, {x.attempt_number=}."
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: prediction-market-agent-tooling
|
3
|
-
Version: 0.61.1.
|
3
|
+
Version: 0.61.1.dev482
|
4
4
|
Summary: Tools to benchmark, deploy and monitor prediction market agents.
|
5
5
|
Author: Gnosis
|
6
6
|
Requires-Python: >=3.10,<3.13
|
@@ -56,7 +56,6 @@ Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
|
56
56
|
Requires-Dist: tavily-python (>=0.5.0,<0.6.0)
|
57
57
|
Requires-Dist: tqdm (>=4.66.2,<5.0.0)
|
58
58
|
Requires-Dist: typer (>=0.9.0,<1.0.0)
|
59
|
-
Requires-Dist: types-cachetools (>=5.5.0.20240820,<6.0.0.0)
|
60
59
|
Requires-Dist: types-python-dateutil (>=2.9.0.20240906,<3.0.0.0)
|
61
60
|
Requires-Dist: types-pytz (>=2024.1.0.20240203,<2025.0.0.0)
|
62
61
|
Requires-Dist: types-requests (>=2.31.0.0,<3.0.0.0)
|