prediction-market-agent-tooling 0.43.4__py3-none-any.whl → 0.45.0__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.
@@ -44,7 +44,6 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
44
44
  from prediction_market_agent_tooling.markets.omen.omen_contracts import (
45
45
  OMEN_DEFAULT_MARKET_FEE,
46
46
  Arbitrator,
47
- ContractDepositableWrapperERC20OnGnosisChain,
48
47
  OmenConditionalTokenContract,
49
48
  OmenFixedProductMarketMakerContract,
50
49
  OmenFixedProductMarketMakerFactoryContract,
@@ -57,9 +56,10 @@ from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
57
56
  )
58
57
  from prediction_market_agent_tooling.tools.balances import get_balances
59
58
  from prediction_market_agent_tooling.tools.contract import (
60
- asset_or_shares,
59
+ ContractDepositableWrapperERC20BaseClass,
60
+ ContractERC4626BaseClass,
61
61
  auto_deposit_collateral_token,
62
- init_erc4626_or_wrappererc20_or_erc20_contract,
62
+ init_collateral_token_contract,
63
63
  to_gnosis_chain_contract,
64
64
  )
65
65
  from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
@@ -549,13 +549,9 @@ def omen_buy_outcome_tx(
549
549
  Bets the given amount of xDai for the given outcome in the given market.
550
550
  """
551
551
  amount_wei = xdai_to_wei(amount)
552
- from_address_checksummed = api_keys.bet_from_address
553
552
 
554
553
  market_contract: OmenFixedProductMarketMakerContract = market.get_contract()
555
554
  collateral_token_contract = market_contract.get_collateral_token_contract()
556
- assert isinstance(
557
- collateral_token_contract, ContractDepositableWrapperERC20OnGnosisChain
558
- ), "TODO: Implement for the ERC-20 and ERC-4626 case."
559
555
 
560
556
  # Get the index of the outcome we want to buy.
561
557
  outcome_index: int = market.get_outcome_index(outcome)
@@ -573,16 +569,12 @@ def omen_buy_outcome_tx(
573
569
  amount_wei=amount_wei,
574
570
  web3=web3,
575
571
  )
576
- # Deposit xDai to the collateral token,
577
- # this can be skipped, if we know we already have enough collateral tokens.
578
- collateral_token_balance = collateral_token_contract.balanceOf(
579
- for_address=from_address_checksummed, web3=web3
580
- )
581
- if auto_deposit and collateral_token_balance < amount_wei:
582
- deposit_amount_wei = Wei(amount_wei - collateral_token_balance)
583
- collateral_token_contract.deposit(
584
- api_keys=api_keys, amount_wei=deposit_amount_wei, web3=web3
572
+
573
+ if auto_deposit:
574
+ auto_deposit_collateral_token(
575
+ collateral_token_contract, amount_wei, api_keys, web3
585
576
  )
577
+
586
578
  # Buy shares using the deposited xDai in the collateral token.
587
579
  market_contract.buy(
588
580
  api_keys=api_keys,
@@ -631,9 +623,6 @@ def omen_sell_outcome_tx(
631
623
  market_contract: OmenFixedProductMarketMakerContract = market.get_contract()
632
624
  conditional_token_contract = OmenConditionalTokenContract()
633
625
  collateral_token_contract = market_contract.get_collateral_token_contract()
634
- assert isinstance(
635
- collateral_token_contract, ContractDepositableWrapperERC20OnGnosisChain
636
- ), "TODO: Implement for the ERC-20 and ERC-4626 case."
637
626
 
638
627
  # Verify, that markets uses conditional tokens that we expect.
639
628
  if (
@@ -669,10 +658,19 @@ def omen_sell_outcome_tx(
669
658
  max_outcome_tokens_to_sell,
670
659
  web3=web3,
671
660
  )
672
- if auto_withdraw:
673
- # Optionally, withdraw from the collateral token back to the `from_address` wallet.
661
+ if auto_withdraw and (
662
+ isinstance(collateral_token_contract, ContractERC4626BaseClass)
663
+ or isinstance(
664
+ collateral_token_contract, ContractDepositableWrapperERC20BaseClass
665
+ )
666
+ ):
674
667
  collateral_token_contract.withdraw(
675
- api_keys=api_keys, amount_wei=amount_wei, web3=web3
668
+ api_keys,
669
+ remove_fraction(
670
+ amount_wei,
671
+ 0.001, # Allow 0.1% slippage.
672
+ ),
673
+ web3,
676
674
  )
677
675
 
678
676
 
@@ -718,7 +716,7 @@ def omen_create_market_tx(
718
716
  realitio_contract = OmenRealitioContract()
719
717
  conditional_token_contract = OmenConditionalTokenContract()
720
718
  collateral_token_contract = to_gnosis_chain_contract(
721
- init_erc4626_or_wrappererc20_or_erc20_contract(collateral_token_address, web3)
719
+ init_collateral_token_contract(collateral_token_address, web3)
722
720
  )
723
721
  factory_contract = OmenFixedProductMarketMakerFactoryContract()
724
722
  oracle_contract = OmenOracleContract()
@@ -735,7 +733,6 @@ def omen_create_market_tx(
735
733
  "The oracle's conditional tokens address is not the same as we are using."
736
734
  )
737
735
 
738
- # If auto deposit is enabled.
739
736
  if auto_deposit:
740
737
  auto_deposit_collateral_token(
741
738
  collateral_token_contract=collateral_token_contract,
@@ -772,19 +769,15 @@ def omen_create_market_tx(
772
769
  web3=web3,
773
770
  )
774
771
 
775
- # Use shares as the initial funds, if the collateral is erc-4626.
776
- # We need to do this, because for example for 1 xDai in erc-20 token, we could receive <1 shares in the vault,
777
- # and then, providing liquidity would fail, because we would not have enough shares.
778
- initial_funds_in_asset_or_shares = wei_type(
779
- asset_or_shares(collateral_token_contract, initial_funds_wei)
780
- * 0.999 # Allow some slippage.
772
+ initial_funds_in_shares = collateral_token_contract.get_in_shares(
773
+ amount=initial_funds_wei, web3=web3
781
774
  )
782
775
 
783
776
  # Approve the market maker to withdraw our collateral token.
784
777
  collateral_token_contract.approve(
785
778
  api_keys=api_keys,
786
779
  for_address=factory_contract.address,
787
- amount_wei=initial_funds_in_asset_or_shares,
780
+ amount_wei=initial_funds_in_shares,
788
781
  web3=web3,
789
782
  )
790
783
 
@@ -793,7 +786,7 @@ def omen_create_market_tx(
793
786
  api_keys=api_keys,
794
787
  condition_id=condition_id,
795
788
  fee=fee,
796
- initial_funds_wei=initial_funds_in_asset_or_shares,
789
+ initial_funds_wei=initial_funds_in_shares,
797
790
  collateral_token_address=collateral_token_contract.address,
798
791
  web3=web3,
799
792
  )
@@ -816,21 +809,11 @@ def omen_fund_market_tx(
816
809
  auto_deposit: bool,
817
810
  web3: Web3 | None = None,
818
811
  ) -> None:
819
- from_address = api_keys.bet_from_address
820
812
  market_contract = market.get_contract()
821
813
  collateral_token_contract = market_contract.get_collateral_token_contract()
822
- assert isinstance(
823
- collateral_token_contract, ContractDepositableWrapperERC20OnGnosisChain
824
- ), "TODO: Implement for the ERC-20 and ERC-4626 case."
825
814
 
826
- # Deposit xDai to the collateral token,
827
- # this can be skipped, if we know we already have enough collateral tokens.
828
- if (
829
- auto_deposit
830
- and collateral_token_contract.balanceOf(for_address=from_address, web3=web3)
831
- < funds
832
- ):
833
- collateral_token_contract.deposit(api_keys, funds, web3=web3)
815
+ if auto_deposit:
816
+ auto_deposit_collateral_token(collateral_token_contract, funds, api_keys, web3)
834
817
 
835
818
  collateral_token_contract.approve(
836
819
  api_keys=api_keys,
@@ -942,7 +925,8 @@ def omen_remove_fund_market_tx(
942
925
  """
943
926
  from_address = api_keys.bet_from_address
944
927
  market_contract = market.get_contract()
945
- original_balances = get_balances(from_address, web3=web3)
928
+ market_collateral_token_contract = market_contract.get_collateral_token_contract()
929
+ original_balance = market_collateral_token_contract.balanceOf(from_address)
946
930
 
947
931
  total_shares = market_contract.balanceOf(from_address, web3=web3)
948
932
  if total_shares == 0:
@@ -977,11 +961,11 @@ def omen_remove_fund_market_tx(
977
961
  web3=web3,
978
962
  )
979
963
 
980
- new_balances = get_balances(from_address, web3)
964
+ new_balance = market_collateral_token_contract.balanceOf(from_address)
981
965
 
982
966
  logger.debug(f"Result from merge positions {result}")
983
967
  logger.info(
984
- f"Withdrawn {new_balances.wxdai - original_balances.wxdai} wxDai from liquidity at {market.url=}."
968
+ f"Withdrawn {new_balance - original_balance} {market_collateral_token_contract.symbol_cached()} from liquidity at {market.url=}."
985
969
  )
986
970
 
987
971
 
@@ -28,7 +28,7 @@ from prediction_market_agent_tooling.tools.contract import (
28
28
  ContractERC4626OnGnosisChain,
29
29
  ContractOnGnosisChain,
30
30
  abi_field_validator,
31
- init_erc4626_or_wrappererc20_or_erc20_contract,
31
+ init_collateral_token_contract,
32
32
  to_gnosis_chain_contract,
33
33
  )
34
34
  from prediction_market_agent_tooling.tools.web3_utils import (
@@ -390,16 +390,10 @@ class OmenFixedProductMarketMakerContract(ContractOnGnosisChain):
390
390
 
391
391
  def get_collateral_token_contract(
392
392
  self, web3: Web3 | None = None
393
- ) -> (
394
- ContractERC20OnGnosisChain
395
- | ContractERC4626OnGnosisChain
396
- | ContractDepositableWrapperERC20OnGnosisChain
397
- ):
393
+ ) -> ContractERC20OnGnosisChain:
398
394
  web3 = web3 or self.get_web3()
399
395
  return to_gnosis_chain_contract(
400
- init_erc4626_or_wrappererc20_or_erc20_contract(
401
- self.collateralToken(web3=web3), web3
402
- )
396
+ init_collateral_token_contract(self.collateralToken(web3=web3), web3)
403
397
  )
404
398
 
405
399
 
@@ -26,6 +26,7 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
26
26
  from prediction_market_agent_tooling.markets.omen.omen_contracts import (
27
27
  OmenThumbnailMapping,
28
28
  WrappedxDaiContract,
29
+ sDaiContract,
29
30
  )
30
31
  from prediction_market_agent_tooling.tools.singleton import SingletonMeta
31
32
  from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
@@ -185,7 +186,7 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
185
186
  condition_id_in: list[HexBytes] | None = None,
186
187
  id_in: list[str] | None = None,
187
188
  excluded_questions: set[str] | None = None,
188
- collateral_token_address: ChecksumAddress | None = None,
189
+ collateral_token_address_in: tuple[ChecksumAddress, ...] | None = None,
189
190
  ) -> dict[str, t.Any]:
190
191
  where_stms: dict[str, t.Any] = {
191
192
  "isPendingArbitration": False,
@@ -195,8 +196,10 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
195
196
  "condition_": {},
196
197
  }
197
198
 
198
- if collateral_token_address:
199
- where_stms["collateralToken"] = collateral_token_address.lower()
199
+ if collateral_token_address_in:
200
+ where_stms["collateralToken_in"] = [
201
+ x.lower() for x in collateral_token_address_in
202
+ ]
200
203
 
201
204
  if creator:
202
205
  where_stms["creator"] = creator
@@ -338,9 +341,12 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
338
341
  sort_by_field: FieldPath | None = None,
339
342
  sort_direction: str | None = None,
340
343
  outcomes: list[str] = [OMEN_TRUE_OUTCOME, OMEN_FALSE_OUTCOME],
341
- collateral_token_address: (
342
- ChecksumAddress | None
343
- ) = WrappedxDaiContract().address, # TODO: Remove this default limitation once we fully support other than wxDai markets.
344
+ # TODO: Agents don't know how to convert value between other tokens, we assume 1 unit = 1xDai = $1 (for example if market would be in wETH, betting 1 unit of wETH would be crazy :D)
345
+ collateral_token_address_in: tuple[ChecksumAddress, ...]
346
+ | None = (
347
+ WrappedxDaiContract().address,
348
+ sDaiContract().address,
349
+ ),
344
350
  ) -> t.List[OmenMarket]:
345
351
  """
346
352
  Complete method to fetch Omen binary markets with various filters, use `get_omen_binary_markets_simple` for simplified version that uses FilterBy and SortBy enums.
@@ -358,7 +364,7 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
358
364
  id_in=id_in,
359
365
  excluded_questions=excluded_questions,
360
366
  liquidity_bigger_than=liquidity_bigger_than,
361
- collateral_token_address=collateral_token_address,
367
+ collateral_token_address_in=collateral_token_address_in,
362
368
  )
363
369
 
364
370
  # These values can not be set to `None`, but they can be omitted.
@@ -1,110 +1,67 @@
1
- import typing as t
1
+ from enum import Enum
2
2
 
3
- from prediction_market_agent_tooling.gtypes import Probability, wei_type, xDai
4
- from prediction_market_agent_tooling.markets.omen.data_models import OmenMarket
5
- from prediction_market_agent_tooling.tools.utils import check_not_none
6
- from prediction_market_agent_tooling.tools.web3_utils import (
7
- ONE_XDAI,
8
- wei_to_xdai,
9
- xdai_to_wei,
10
- )
3
+ from pydantic import BaseModel
11
4
 
12
- OutcomeIndex = t.Literal[0, 1]
13
5
 
6
+ class BetDirection(str, Enum):
7
+ YES = "Yes"
8
+ NO = "No"
14
9
 
15
- def _get_kelly_criterion_bet(
16
- x: int, y: int, p: float, c: float, b: int, f: float
17
- ) -> int:
10
+
11
+ class KellyBet(BaseModel):
12
+ direction: BetDirection
13
+ size: float
14
+
15
+
16
+ def check_is_valid_probability(probability: float) -> None:
17
+ if not 0 <= probability <= 1:
18
+ raise ValueError("Probability must be between 0 and 1")
19
+
20
+
21
+ def get_kelly_bet(
22
+ max_bet: float,
23
+ market_p_yes: float,
24
+ estimated_p_yes: float,
25
+ confidence: float,
26
+ ) -> KellyBet:
18
27
  """
19
- Implments https://en.wikipedia.org/wiki/Kelly_criterion
20
-
21
- Taken from https://github.com/valory-xyz/trader/blob/main/strategies/kelly_criterion/kelly_criterion.py
22
-
23
- ```
24
- Licensed under the Apache License, Version 2.0 (the "License");
25
- you may not use this file except in compliance with the License.
26
- You may obtain a copy of the License at
27
-
28
- http://www.apache.org/licenses/LICENSE-2.0
29
-
30
- Unless required by applicable law or agreed to in writing, software
31
- distributed under the License is distributed on an "AS IS" BASIS,
32
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33
- See the License for the specific language governing permissions and
34
- limitations under the License.
35
- ```
36
-
37
- x: Number of tokens in the selected outcome pool
38
- y: Number of tokens in the other outcome pool
39
- p: Probability of winning
40
- c: Confidence
41
- b: Bankroll
42
- f: Fee fraction
28
+ Calculate the optimal bet amount using the Kelly Criterion for a binary outcome market.
29
+
30
+ From https://en.wikipedia.org/wiki/Kelly_criterion:
31
+
32
+ f* = p - q / b
33
+
34
+ where:
35
+ - f* is the fraction of the current bankroll to wager
36
+ - p is the probability of a win
37
+ - q = 1-p is the probability of a loss
38
+ - b is the proportion of the bet gained with a win
39
+
40
+ Note: this calculation does not factor in that the bet changes the market
41
+ odds. This means the calculation is only accurate if the bet size is small
42
+ compared to the market volume. See discussion here for more detail:
43
+ https://github.com/gnosis/prediction-market-agent-tooling/pull/330#discussion_r1698269328
43
44
  """
44
- if b == 0:
45
- return 0
46
- numerator = (
47
- -4 * x**2 * y
48
- + b * y**2 * p * c * f
49
- + 2 * b * x * y * p * c * f
50
- + b * x**2 * p * c * f
51
- - 2 * b * y**2 * f
52
- - 2 * b * x * y * f
53
- + (
54
- (
55
- 4 * x**2 * y
56
- - b * y**2 * p * c * f
57
- - 2 * b * x * y * p * c * f
58
- - b * x**2 * p * c * f
59
- + 2 * b * y**2 * f
60
- + 2 * b * x * y * f
61
- )
62
- ** 2
63
- - (
64
- 4
65
- * (x**2 * f - y**2 * f)
66
- * (
67
- -4 * b * x * y**2 * p * c
68
- - 4 * b * x**2 * y * p * c
69
- + 4 * b * x * y**2
70
- )
71
- )
72
- )
73
- ** (1 / 2)
74
- )
75
- denominator = 2 * (x**2 * f - y**2 * f)
76
- if denominator == 0:
77
- return 0
78
- kelly_bet_amount = numerator / denominator
79
- return int(kelly_bet_amount)
80
-
81
-
82
- def get_kelly_criterion_bet(
83
- market: OmenMarket,
84
- estimated_p_yes: Probability,
85
- max_bet: xDai,
86
- ) -> t.Tuple[xDai, OutcomeIndex]:
87
- if len(market.outcomeTokenAmounts) != 2:
88
- raise ValueError("Only binary markets are supported.")
89
-
90
- current_p_yes = check_not_none(
91
- market.outcomeTokenProbabilities, "No probabilities, is marked closed?"
92
- )[0]
93
- outcome_index: OutcomeIndex = 0 if estimated_p_yes > current_p_yes else 1
94
- estimated_p_win = estimated_p_yes if outcome_index == 0 else 1 - estimated_p_yes
95
-
96
- kelly_bet_wei = wei_type(
97
- _get_kelly_criterion_bet(
98
- x=market.outcomeTokenAmounts[outcome_index],
99
- y=market.outcomeTokenAmounts[1 - outcome_index],
100
- p=estimated_p_win,
101
- c=1, # confidence
102
- b=xdai_to_wei(max_bet), # bankroll, or max bet, in Wei
103
- f=(
104
- xdai_to_wei(ONE_XDAI)
105
- - check_not_none(market.fee, "No fee for the market.")
106
- )
107
- / xdai_to_wei(ONE_XDAI), # fee fraction
108
- )
109
- )
110
- return wei_to_xdai(kelly_bet_wei), outcome_index
45
+ check_is_valid_probability(market_p_yes)
46
+ check_is_valid_probability(estimated_p_yes)
47
+ check_is_valid_probability(confidence)
48
+
49
+ if estimated_p_yes > market_p_yes:
50
+ bet_direction = BetDirection.YES
51
+ market_prob = market_p_yes
52
+ else:
53
+ bet_direction = BetDirection.NO
54
+ market_prob = 1 - market_p_yes
55
+
56
+ # Handle the case where market_prob is 0
57
+ if market_prob == 0:
58
+ market_prob = 1e-10
59
+
60
+ edge = abs(estimated_p_yes - market_p_yes) * confidence
61
+ odds = (1 / market_prob) - 1
62
+ kelly_fraction = edge / odds
63
+
64
+ # Ensure bet size is non-negative does not exceed the wallet balance
65
+ bet_size = min(kelly_fraction * max_bet, max_bet)
66
+
67
+ return KellyBet(direction=bet_direction, size=bet_size)
@@ -21,6 +21,7 @@ from prediction_market_agent_tooling.tools.gnosis_rpc import (
21
21
  GNOSIS_NETWORK_ID,
22
22
  GNOSIS_RPC_URL,
23
23
  )
24
+ from prediction_market_agent_tooling.tools.utils import should_not_happen
24
25
  from prediction_market_agent_tooling.tools.web3_utils import (
25
26
  call_function_on_contract,
26
27
  send_function_on_contract_tx,
@@ -66,6 +67,11 @@ class ContractBaseClass(BaseModel):
66
67
  address: ChecksumAddress
67
68
 
68
69
  _abi_field_validator = field_validator("abi", mode="before")(abi_field_validator)
70
+ _cache: dict[
71
+ str, t.Any
72
+ ] = (
73
+ {}
74
+ ) # Can be used to hold values that aren't going to change after getting them for the first time, as for example `symbol` of an ERC-20 token.
69
75
 
70
76
  def call(
71
77
  self,
@@ -179,6 +185,18 @@ class ContractERC20BaseClass(ContractBaseClass):
179
185
  )
180
186
  )
181
187
 
188
+ def symbol(self, web3: Web3 | None = None) -> str:
189
+ symbol: str = self.call("symbol", web3=web3)
190
+ return symbol
191
+
192
+ def symbol_cached(self, web3: Web3 | None = None) -> str:
193
+ web3 = web3 or self.get_web3()
194
+ cache_key = create_contract_method_cache_key(self.symbol, web3)
195
+ if cache_key not in self._cache:
196
+ self._cache[cache_key] = self.symbol(web3=web3)
197
+ value: str = self._cache[cache_key]
198
+ return value
199
+
182
200
  def approve(
183
201
  self,
184
202
  api_keys: APIKeys,
@@ -219,6 +237,10 @@ class ContractERC20BaseClass(ContractBaseClass):
219
237
  balance: Wei = self.call("balanceOf", [for_address], web3=web3)
220
238
  return balance
221
239
 
240
+ def get_in_shares(self, amount: Wei, web3: Web3 | None = None) -> Wei:
241
+ # ERC-20 just holds the token, so the exact amount we send there, is the amount of shares we have there.
242
+ return amount
243
+
222
244
 
223
245
  class ContractDepositableWrapperERC20BaseClass(ContractERC20BaseClass):
224
246
  """
@@ -283,10 +305,11 @@ class ContractERC4626BaseClass(ContractERC20BaseClass):
283
305
  self,
284
306
  api_keys: APIKeys,
285
307
  amount_wei: Wei,
286
- receiver: ChecksumAddress,
308
+ receiver: ChecksumAddress | None = None,
287
309
  tx_params: t.Optional[TxParams] = None,
288
310
  web3: Web3 | None = None,
289
311
  ) -> TxReceipt:
312
+ receiver = receiver or api_keys.bet_from_address
290
313
  return self.send(
291
314
  api_keys=api_keys,
292
315
  function_name="deposit",
@@ -295,6 +318,25 @@ class ContractERC4626BaseClass(ContractERC20BaseClass):
295
318
  web3=web3,
296
319
  )
297
320
 
321
+ def withdraw(
322
+ self,
323
+ api_keys: APIKeys,
324
+ assets_wei: Wei,
325
+ receiver: ChecksumAddress | None = None,
326
+ owner: ChecksumAddress | None = None,
327
+ tx_params: t.Optional[TxParams] = None,
328
+ web3: Web3 | None = None,
329
+ ) -> TxReceipt:
330
+ receiver = receiver or api_keys.bet_from_address
331
+ owner = owner or api_keys.bet_from_address
332
+ return self.send(
333
+ api_keys=api_keys,
334
+ function_name="withdraw",
335
+ function_params=[assets_wei, receiver, owner],
336
+ tx_params=tx_params,
337
+ web3=web3,
338
+ )
339
+
298
340
  def convertToShares(self, assets: Wei, web3: Web3 | None = None) -> Wei:
299
341
  shares: Wei = self.call("convertToShares", [assets], web3=web3)
300
342
  return shares
@@ -307,9 +349,7 @@ class ContractERC4626BaseClass(ContractERC20BaseClass):
307
349
  self, web3: Web3 | None = None
308
350
  ) -> ContractERC20BaseClass | ContractDepositableWrapperERC20BaseClass:
309
351
  web3 = web3 or self.get_web3()
310
- contract = init_erc4626_or_wrappererc20_or_erc20_contract(
311
- self.asset(), web3=web3
312
- )
352
+ contract = init_collateral_token_contract(self.asset(), web3=web3)
313
353
  assert not isinstance(
314
354
  contract, ContractERC4626OnGnosisChain
315
355
  ), "Asset token should be either Depositable Wrapper ERC-20 or ERC-20." # Shrinking down possible types.
@@ -336,6 +376,10 @@ class ContractERC4626BaseClass(ContractERC20BaseClass):
336
376
 
337
377
  return receipt
338
378
 
379
+ def get_in_shares(self, amount: Wei, web3: Web3 | None = None) -> Wei:
380
+ # We send erc20 to the vault and receive shares in return, which can have a different value.
381
+ return self.convertToShares(amount, web3=web3)
382
+
339
383
 
340
384
  class ContractOnGnosisChain(ContractBaseClass):
341
385
  """
@@ -359,14 +403,16 @@ class ContractERC20OnGnosisChain(ContractERC20BaseClass, ContractOnGnosisChain):
359
403
 
360
404
 
361
405
  class ContractDepositableWrapperERC20OnGnosisChain(
362
- ContractDepositableWrapperERC20BaseClass, ContractOnGnosisChain
406
+ ContractDepositableWrapperERC20BaseClass, ContractERC20OnGnosisChain
363
407
  ):
364
408
  """
365
409
  Depositable Wrapper ERC-20 standard base class with Gnosis Chain configuration.
366
410
  """
367
411
 
368
412
 
369
- class ContractERC4626OnGnosisChain(ContractERC4626BaseClass, ContractOnGnosisChain):
413
+ class ContractERC4626OnGnosisChain(
414
+ ContractERC4626BaseClass, ContractERC20OnGnosisChain
415
+ ):
370
416
  """
371
417
  ERC-4626 standard base class with Gnosis Chain configuration.
372
418
  """
@@ -403,14 +449,9 @@ def contract_implements_function(
403
449
  return implements
404
450
 
405
451
 
406
- def init_erc4626_or_wrappererc20_or_erc20_contract(
407
- address: ChecksumAddress,
408
- web3: Web3,
409
- ) -> (
410
- ContractERC20BaseClass
411
- | ContractERC4626BaseClass
412
- | ContractDepositableWrapperERC20BaseClass
413
- ):
452
+ def init_collateral_token_contract(
453
+ address: ChecksumAddress, web3: Web3
454
+ ) -> ContractERC20BaseClass:
414
455
  """
415
456
  Checks if the given contract is Depositable ERC-20, ERC-20 or ERC-4626 and returns the appropriate class instance.
416
457
  Throws an error if the contract is neither of them.
@@ -435,95 +476,106 @@ def init_erc4626_or_wrappererc20_or_erc20_contract(
435
476
 
436
477
  else:
437
478
  raise ValueError(
438
- f"Contract at {address} on Gnosis Chain is neither WrapperERC-20, ERC-20 nor ERC-4626."
479
+ f"Contract at {address} is neither Depositable ERC-20, ERC-20 nor ERC-4626."
439
480
  )
440
481
 
441
482
 
442
483
  def auto_deposit_collateral_token(
443
- collateral_token_contract: (
444
- ContractERC20BaseClass
445
- | ContractERC4626BaseClass
446
- | ContractDepositableWrapperERC20BaseClass
447
- ),
484
+ collateral_token_contract: ContractERC20BaseClass,
448
485
  amount_wei: Wei,
449
486
  api_keys: APIKeys,
450
487
  web3: Web3 | None,
451
488
  ) -> None:
452
- for_address = api_keys.bet_from_address
453
- # This might be in shares, if it's an erc-4626 token.
454
- collateral_token_balance = collateral_token_contract.balanceOf(
455
- for_address=for_address, web3=web3
456
- )
457
-
458
489
  if isinstance(collateral_token_contract, ContractERC4626BaseClass):
459
- # In the more complex case, we need to deposit into the saving token, out of the erc-20 token.
460
- # We need to compare with shares, because if erc-4626 is used, the liquidity in market will be in shares as well.
461
- if collateral_token_balance < collateral_token_contract.convertToShares(
462
- amount_wei
463
- ):
464
- asset_token_contract = collateral_token_contract.get_asset_token_contract(
465
- web3=web3
466
- )
467
-
468
- # If the asset token is Depositable Wrapper ERC-20, we can deposit it, in case we don't have enough.
469
- if (
470
- collateral_token_contract.get_asset_token_balance(for_address, web3)
471
- < amount_wei
472
- ):
473
- if isinstance(
474
- asset_token_contract, ContractDepositableWrapperERC20BaseClass
475
- ):
476
- asset_token_contract.deposit(api_keys, amount_wei, web3=web3)
477
- else:
478
- raise ValueError(
479
- f"Not enough of the asset token, but it's not a depositable wrapper token that we can deposit automatically."
480
- )
481
-
482
- collateral_token_contract.deposit_asset_token(amount_wei, api_keys, web3)
490
+ auto_deposit_erc4626(collateral_token_contract, amount_wei, api_keys, web3)
483
491
 
484
492
  elif isinstance(
485
493
  collateral_token_contract, ContractDepositableWrapperERC20BaseClass
486
494
  ):
487
- # If the collateral token is Depositable Wrapper ERC-20, it's a simple case where we can just deposit it, if needed.
488
- if collateral_token_balance < amount_wei:
489
- collateral_token_contract.deposit(api_keys, amount_wei, web3=web3)
495
+ auto_deposit_depositable_wrapper_erc20(
496
+ collateral_token_contract, amount_wei, api_keys, web3
497
+ )
490
498
 
491
499
  elif isinstance(collateral_token_contract, ContractERC20BaseClass):
492
- if collateral_token_balance < amount_wei:
500
+ if (
501
+ collateral_token_contract.balanceOf(
502
+ for_address=api_keys.bet_from_address, web3=web3
503
+ )
504
+ < amount_wei
505
+ ):
493
506
  raise ValueError(
494
507
  f"Not enough of the collateral token, but it's not a wrapper token that we can deposit automatically."
495
508
  )
496
509
 
497
510
  else:
498
- raise RuntimeError("Bug in our logic! :(")
511
+ should_not_happen("Unsupported ERC20 contract type.")
499
512
 
500
513
 
501
- def asset_or_shares(
502
- collateral_token_contract: (
503
- ContractERC20BaseClass
504
- | ContractERC4626BaseClass
505
- | ContractDepositableWrapperERC20BaseClass
506
- ),
514
+ def auto_deposit_depositable_wrapper_erc20(
515
+ collateral_token_contract: ContractDepositableWrapperERC20BaseClass,
507
516
  amount_wei: Wei,
508
- ) -> Wei:
509
- return (
510
- collateral_token_contract.convertToShares(amount_wei)
511
- if isinstance(collateral_token_contract, ContractERC4626BaseClass)
512
- else amount_wei
517
+ api_keys: APIKeys,
518
+ web3: Web3 | None,
519
+ ) -> None:
520
+ collateral_token_balance = collateral_token_contract.balanceOf(
521
+ for_address=api_keys.bet_from_address, web3=web3
522
+ )
523
+
524
+ # If we have enough of the collateral token, we don't need to deposit.
525
+ if collateral_token_balance >= amount_wei:
526
+ return
527
+
528
+ # If we don't have enough, we need to deposit the difference.
529
+ left_to_deposit = Wei(amount_wei - collateral_token_balance)
530
+ collateral_token_contract.deposit(api_keys, left_to_deposit, web3=web3)
531
+
532
+
533
+ def auto_deposit_erc4626(
534
+ collateral_token_contract: ContractERC4626BaseClass,
535
+ asset_amount_wei: Wei,
536
+ api_keys: APIKeys,
537
+ web3: Web3 | None,
538
+ ) -> None:
539
+ for_address = api_keys.bet_from_address
540
+ collateral_token_balance_in_shares = collateral_token_contract.balanceOf(
541
+ for_address=for_address, web3=web3
542
+ )
543
+ asset_amount_wei_in_shares = collateral_token_contract.convertToShares(
544
+ asset_amount_wei, web3
513
545
  )
514
546
 
547
+ # If we have enough shares, we don't need to deposit.
548
+ if collateral_token_balance_in_shares >= asset_amount_wei_in_shares:
549
+ return
550
+
551
+ # If we need to deposit into erc4626, we first need to have enough of the asset token.
552
+ asset_token_contract = collateral_token_contract.get_asset_token_contract(web3=web3)
553
+
554
+ # If the asset token is Depositable Wrapper ERC-20, we can deposit it, in case we don't have enough.
555
+ if (
556
+ collateral_token_contract.get_asset_token_balance(for_address, web3)
557
+ < asset_amount_wei
558
+ ):
559
+ if isinstance(asset_token_contract, ContractDepositableWrapperERC20BaseClass):
560
+ auto_deposit_depositable_wrapper_erc20(
561
+ asset_token_contract, asset_amount_wei, api_keys, web3
562
+ )
563
+ else:
564
+ raise ValueError(
565
+ "Not enough of the asset token, but it's not a depositable wrapper token that we can deposit automatically."
566
+ )
567
+
568
+ # Finally, we can deposit the asset token into the erc4626 vault.
569
+ collateral_token_balance_in_assets = collateral_token_contract.convertToAssets(
570
+ collateral_token_balance_in_shares, web3
571
+ )
572
+ left_to_deposit = Wei(asset_amount_wei - collateral_token_balance_in_assets)
573
+ collateral_token_contract.deposit_asset_token(left_to_deposit, api_keys, web3)
574
+
515
575
 
516
576
  def to_gnosis_chain_contract(
517
- contract: (
518
- ContractDepositableWrapperERC20BaseClass
519
- | ContractERC4626BaseClass
520
- | ContractERC20BaseClass
521
- ),
522
- ) -> (
523
- ContractDepositableWrapperERC20OnGnosisChain
524
- | ContractERC4626OnGnosisChain
525
- | ContractERC20OnGnosisChain
526
- ):
577
+ contract: ContractERC20BaseClass,
578
+ ) -> ContractERC20OnGnosisChain:
527
579
  if isinstance(contract, ContractERC4626BaseClass):
528
580
  return ContractERC4626OnGnosisChain(address=contract.address)
529
581
  elif isinstance(contract, ContractDepositableWrapperERC20BaseClass):
@@ -532,3 +584,9 @@ def to_gnosis_chain_contract(
532
584
  return ContractERC20OnGnosisChain(address=contract.address)
533
585
  else:
534
586
  raise ValueError("Unsupported contract type")
587
+
588
+
589
+ def create_contract_method_cache_key(
590
+ method: t.Callable[[t.Any], t.Any], web3: Web3
591
+ ) -> str:
592
+ return f"{method.__name__}-{str(web3.provider)}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.43.4
3
+ Version: 0.45.0
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.12
@@ -37,10 +37,10 @@ prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=6TBy17xn
37
37
  prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=uNF7LP4evvubk818g2zbX1VlnFxeUQOkNgx_e_LwaJA,3416
38
38
  prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  prediction_market_agent_tooling/markets/omen/data_models.py,sha256=fpYxaslKq48lpNDsFUHcggY5geZIAKWDfC9FwUgIstE,14539
40
- prediction_market_agent_tooling/markets/omen/omen.py,sha256=_H_1huaCXTpmDUmHaHqIQoCbIrrMATHeXXdkw5BHu1s,40713
41
- prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=BmIWFvQ-CM355eHmRrKipS_FcYc6TEISv2l2r45qHAU,22790
40
+ prediction_market_agent_tooling/markets/omen/omen.py,sha256=7n-UwtXVKVqbZoJhRGDPlsYPyHcdMGuiJpJCaQMsG3A,39488
41
+ prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=FZCbzTuVUI-8NvbB2BDRP0xtkDY5pdCxpVuQ0nnQLdI,22618
42
42
  prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=tXTJM_HNefODAJSGU_w1OklZ457ZMAjL6dC0EvkUYQ8,9450
43
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=s-CHD1DuWyGv1_KfxNZv8fmBft60528b51nZ5XANzpU,25692
43
+ prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=4F4QR8MDwyEw52rolEJNxtbCcIipX6ccNHANfWyCBjg,25945
44
44
  prediction_market_agent_tooling/markets/polymarket/api.py,sha256=HXmA1akA0qDj0m3e-GEvWG8x75pm6BX4H7YJPQcST7I,4767
45
45
  prediction_market_agent_tooling/markets/polymarket/data_models.py,sha256=9CJzakyEcsn6DQBK2nOXjOMzTZBLAmK_KqevXvW17DI,4292
46
46
  prediction_market_agent_tooling/markets/polymarket/data_models_web.py,sha256=yK0uxLQrwImVAXbvwscdmxTjSxMpAjCcN760EWEK_8M,11914
@@ -56,12 +56,12 @@ prediction_market_agent_tooling/monitor/monitor_app.py,sha256=THyZ67baByakoOm3hI
56
56
  prediction_market_agent_tooling/monitor/monitor_settings.py,sha256=Xiozs3AsufuJ04JOe1vjUri-IAMWHjjmc2ugGGiHNH4,947
57
57
  prediction_market_agent_tooling/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
58
  prediction_market_agent_tooling/tools/balances.py,sha256=nR8_dSfbm3yTOOmMAwhGlurftEiNo1w1WIVzbskjdmM,837
59
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=IbhQPoKsQnjnag_n_wAL12n8QdCe7tAMRNV2QS8yYxY,3520
59
+ prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=d3QEg1Bia8D7SPHGr-SgP0OHSLIzm_hf4sN4iz06qiM,1951
60
60
  prediction_market_agent_tooling/tools/betting_strategies/market_moving.py,sha256=wtrHVQRuA0uDx06z0OxQLYbswuOpHQ1UyCWwLCrD_oM,4400
61
61
  prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py,sha256=-FUSuQQgjcWSSnoFxnlAyTeilY6raJABJVM2QKkFqAY,438
62
62
  prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py,sha256=THMXwFlskvzbjnX_OiYtDSzI8XVFyULWfP2525_9UGc,429
63
63
  prediction_market_agent_tooling/tools/cache.py,sha256=tGHHd9HCiE_hCCtPtloHZQdDfBuiow9YsqJNYi2Tx_0,499
64
- prediction_market_agent_tooling/tools/contract.py,sha256=vjkPeZszvco_IAITp1GUfCZ3X8HrSol4GCDDa8TFFvw,17100
64
+ prediction_market_agent_tooling/tools/contract.py,sha256=d6eR1CSSKRxyM-zgUtp5jmj3z8nAfwTdtiTGWjzlvYU,19388
65
65
  prediction_market_agent_tooling/tools/costs.py,sha256=EaAJ7v9laD4VEV3d8B44M4u3_oEO_H16jRVCdoZ93Uw,954
66
66
  prediction_market_agent_tooling/tools/gnosis_rpc.py,sha256=_MYSoyOR2MgAJkop1ERf8RhLum-M8S6OjaAsaqUW41w,203
67
67
  prediction_market_agent_tooling/tools/google.py,sha256=SfVDxb3oEOUK8mpd0l3mTX9ybrdrTPNM6HjfJ7kfNjA,1794
@@ -75,8 +75,8 @@ prediction_market_agent_tooling/tools/singleton.py,sha256=CiIELUiI-OeS7U7eeHEt0r
75
75
  prediction_market_agent_tooling/tools/streamlit_user_login.py,sha256=NXEqfjT9Lc9QtliwSGRASIz1opjQ7Btme43H4qJbzgE,3010
76
76
  prediction_market_agent_tooling/tools/utils.py,sha256=JE9YWtPPhnTgLiOyGAZDNG5K8nCwUY9IZEuAlm9UcxA,6611
77
77
  prediction_market_agent_tooling/tools/web3_utils.py,sha256=nKRHmdLnWSKd3wpo-cysXGvhhrJ2Yf69sN2FFQfSt6s,10578
78
- prediction_market_agent_tooling-0.43.4.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
79
- prediction_market_agent_tooling-0.43.4.dist-info/METADATA,sha256=3-3qr88s5tHIqW1_h8tK8HCP5ayx-GANQyPFGCNkS_k,7634
80
- prediction_market_agent_tooling-0.43.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
81
- prediction_market_agent_tooling-0.43.4.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
82
- prediction_market_agent_tooling-0.43.4.dist-info/RECORD,,
78
+ prediction_market_agent_tooling-0.45.0.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
79
+ prediction_market_agent_tooling-0.45.0.dist-info/METADATA,sha256=3KvBa071FaBwX1gQtAtG5rg9LMVEhIfSkZC5VrzYRcc,7634
80
+ prediction_market_agent_tooling-0.45.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
81
+ prediction_market_agent_tooling-0.45.0.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
82
+ prediction_market_agent_tooling-0.45.0.dist-info/RECORD,,