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.
- prediction_market_agent_tooling/markets/omen/omen.py +31 -47
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +3 -9
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +13 -7
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +61 -104
- prediction_market_agent_tooling/tools/contract.py +134 -76
- {prediction_market_agent_tooling-0.43.4.dist-info → prediction_market_agent_tooling-0.45.0.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.43.4.dist-info → prediction_market_agent_tooling-0.45.0.dist-info}/RECORD +10 -10
- {prediction_market_agent_tooling-0.43.4.dist-info → prediction_market_agent_tooling-0.45.0.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.43.4.dist-info → prediction_market_agent_tooling-0.45.0.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.43.4.dist-info → prediction_market_agent_tooling-0.45.0.dist-info}/entry_points.txt +0 -0
@@ -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
|
-
|
59
|
+
ContractDepositableWrapperERC20BaseClass,
|
60
|
+
ContractERC4626BaseClass,
|
61
61
|
auto_deposit_collateral_token,
|
62
|
-
|
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
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
776
|
-
|
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=
|
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=
|
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
|
-
|
827
|
-
|
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
|
-
|
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
|
-
|
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 {
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
199
|
-
where_stms["
|
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
|
-
|
342
|
-
|
343
|
-
|
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
|
-
|
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
|
-
|
1
|
+
from enum import Enum
|
2
2
|
|
3
|
-
from
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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 =
|
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,
|
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(
|
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
|
407
|
-
address: ChecksumAddress,
|
408
|
-
|
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}
|
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
|
-
|
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
|
-
|
488
|
-
|
489
|
-
|
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
|
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
|
-
|
511
|
+
should_not_happen("Unsupported ERC20 contract type.")
|
499
512
|
|
500
513
|
|
501
|
-
def
|
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
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
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
|
-
|
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)}"
|
@@ -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=
|
41
|
-
prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=
|
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=
|
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=
|
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=
|
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.
|
79
|
-
prediction_market_agent_tooling-0.
|
80
|
-
prediction_market_agent_tooling-0.
|
81
|
-
prediction_market_agent_tooling-0.
|
82
|
-
prediction_market_agent_tooling-0.
|
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,,
|
File without changes
|
File without changes
|