prediction-market-agent-tooling 0.61.1.dev462__py3-none-any.whl → 0.61.1.dev477__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.
Files changed (48) hide show
  1. prediction_market_agent_tooling/deploy/agent.py +5 -4
  2. prediction_market_agent_tooling/deploy/betting_strategy.py +69 -53
  3. prediction_market_agent_tooling/gtypes.py +27 -105
  4. prediction_market_agent_tooling/jobs/jobs_models.py +7 -5
  5. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +17 -13
  6. prediction_market_agent_tooling/markets/agent_market.py +52 -96
  7. prediction_market_agent_tooling/markets/blockchain_utils.py +27 -1
  8. prediction_market_agent_tooling/markets/data_models.py +44 -40
  9. prediction_market_agent_tooling/markets/manifold/api.py +6 -2
  10. prediction_market_agent_tooling/markets/manifold/data_models.py +25 -33
  11. prediction_market_agent_tooling/markets/manifold/manifold.py +11 -8
  12. prediction_market_agent_tooling/markets/market_fees.py +2 -4
  13. prediction_market_agent_tooling/markets/omen/data_models.py +57 -66
  14. prediction_market_agent_tooling/markets/omen/omen.py +249 -214
  15. prediction_market_agent_tooling/markets/omen/omen_contracts.py +29 -31
  16. prediction_market_agent_tooling/markets/omen/omen_resolving.py +14 -7
  17. prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +14 -20
  18. prediction_market_agent_tooling/markets/polymarket/data_models.py +3 -3
  19. prediction_market_agent_tooling/markets/polymarket/data_models_web.py +4 -4
  20. prediction_market_agent_tooling/markets/polymarket/polymarket.py +5 -3
  21. prediction_market_agent_tooling/markets/seer/data_models.py +11 -8
  22. prediction_market_agent_tooling/markets/seer/seer.py +71 -85
  23. prediction_market_agent_tooling/markets/seer/seer_contracts.py +5 -10
  24. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +7 -7
  25. prediction_market_agent_tooling/monitor/monitor.py +2 -2
  26. prediction_market_agent_tooling/tools/balances.py +11 -9
  27. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +10 -12
  28. prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +24 -27
  29. prediction_market_agent_tooling/tools/betting_strategies/utils.py +1 -3
  30. prediction_market_agent_tooling/tools/contract.py +10 -14
  31. prediction_market_agent_tooling/tools/cow/cow_manager.py +4 -3
  32. prediction_market_agent_tooling/tools/cow/cow_order.py +4 -3
  33. prediction_market_agent_tooling/tools/langfuse_client_utils.py +1 -13
  34. prediction_market_agent_tooling/tools/omen/sell_positions.py +3 -6
  35. prediction_market_agent_tooling/tools/safe.py +6 -5
  36. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +30 -32
  37. prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +22 -5
  38. prediction_market_agent_tooling/tools/tokens/main_token.py +2 -2
  39. prediction_market_agent_tooling/tools/utils.py +8 -14
  40. prediction_market_agent_tooling/tools/web3_utils.py +41 -24
  41. {prediction_market_agent_tooling-0.61.1.dev462.dist-info → prediction_market_agent_tooling-0.61.1.dev477.dist-info}/METADATA +1 -2
  42. {prediction_market_agent_tooling-0.61.1.dev462.dist-info → prediction_market_agent_tooling-0.61.1.dev477.dist-info}/RECORD +45 -48
  43. prediction_market_agent_tooling/tools/_generic_value.py +0 -248
  44. prediction_market_agent_tooling/tools/tokens/token_utils.py +0 -46
  45. prediction_market_agent_tooling/tools/tokens/usd.py +0 -63
  46. {prediction_market_agent_tooling-0.61.1.dev462.dist-info → prediction_market_agent_tooling-0.61.1.dev477.dist-info}/LICENSE +0 -0
  47. {prediction_market_agent_tooling-0.61.1.dev462.dist-info → prediction_market_agent_tooling-0.61.1.dev477.dist-info}/WHEEL +0 -0
  48. {prediction_market_agent_tooling-0.61.1.dev462.dist-info → prediction_market_agent_tooling-0.61.1.dev477.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.value, **(tx_params or {})},
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 = Wei(self.call("balanceOf", [for_address], web3=web3))
246
+ balance: Wei = self.call("balanceOf", [for_address], web3=web3)
248
247
  return balance
249
248
 
250
- def balance_of_in_tokens(
251
- self, for_address: ChecksumAddress, web3: Web3 | None = None
252
- ) -> Token:
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 = Wei(self.call("convertToShares", [assets], web3=web3))
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 = Wei(self.call("convertToAssets", [shares], web3=web3))
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) -> Wei:
440
- balance = Wei(self.call("balanceOf", [owner], web3=web3))
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 | None
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, Token, Wei
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: Token,
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.as_wei,
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 Wei(order_quote.quote.buyAmount.root)
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.value,
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.value, bet.amount.value)
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.as_token <= agent_market.get_tiny_bet_amount():
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 = Wei(ethereum_client.get_balance(account.address))
66
- account_balance_xdai = account_balance.as_token
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.value < 0.01:
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).value})["data"]
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 USD, Wei
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 swap_tokens_waiting
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
- collateral_amount_wei_or_usd: Wei | USD,
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, collateral_amount_wei, api_keys, web3
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.as_token} {collateral_token_contract.symbol()}."
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
- collateral_amount_wei: Wei,
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
- asset_amount_wei = collateral_token_contract.convertToAssets(
88
- collateral_amount_wei, web3
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 >= collateral_amount_wei:
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
- collateral_amount_wei: Wei,
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
- Wei(0), collateral_amount_wei - collateral_balance_wei
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 = remaining_to_get_in_collateral_wei * 1.01
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 swap_tokens_waiting
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 = amount_wei.without_fraction(slippage)
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.as_token} from {collateral_token_contract.symbol_cached(web3)} into {KEEPING_ERC20_TOKEN.symbol_cached(web3)}"
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.as_token} from {collateral_token_contract.symbol_cached(web3)} into {KEEPING_ERC20_TOKEN.symbol_cached(web3)}"
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=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 xDai
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 = xDai(0.1)
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: OutcomeToken,
192
- holdings: OutcomeToken,
193
- other_holdings: OutcomeToken,
185
+ shares_to_sell: float,
186
+ holdings: float,
187
+ other_holdings: float,
194
188
  fees: MarketFees,
195
- ) -> Token:
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 = OutcomeToken((r + fees.absolute) / (1 - fees.bet_proportion))
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 ((first_term * second_term) - third_term).value
206
+ return (first_term * second_term) - third_term
213
207
 
214
208
  amount_to_sell = newton(f, 0)
215
- return Token(float(amount_to_sell) * 0.999999) # Avoid rounding errors
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
- xDaiWei,
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 = xDai(1)
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, 6)]),
159
- stop=tenacity.stop_after_attempt(5),
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, 6)]),
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: xDaiWei,
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.value, "to": to_address}
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, 6)]),
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.dev462
3
+ Version: 0.61.1.dev477
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)