prediction-market-agent-tooling 0.61.1.dev489__py3-none-any.whl → 0.62.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.
Files changed (50) hide show
  1. prediction_market_agent_tooling/deploy/agent.py +4 -5
  2. prediction_market_agent_tooling/deploy/betting_strategy.py +53 -69
  3. prediction_market_agent_tooling/gtypes.py +105 -27
  4. prediction_market_agent_tooling/jobs/jobs_models.py +5 -7
  5. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +13 -17
  6. prediction_market_agent_tooling/markets/agent_market.py +96 -55
  7. prediction_market_agent_tooling/markets/blockchain_utils.py +2 -32
  8. prediction_market_agent_tooling/markets/data_models.py +40 -44
  9. prediction_market_agent_tooling/markets/manifold/api.py +2 -6
  10. prediction_market_agent_tooling/markets/manifold/data_models.py +33 -25
  11. prediction_market_agent_tooling/markets/manifold/manifold.py +13 -11
  12. prediction_market_agent_tooling/markets/market_fees.py +6 -2
  13. prediction_market_agent_tooling/markets/omen/data_models.py +66 -57
  14. prediction_market_agent_tooling/markets/omen/omen.py +222 -250
  15. prediction_market_agent_tooling/markets/omen/omen_contracts.py +32 -33
  16. prediction_market_agent_tooling/markets/omen/omen_resolving.py +7 -14
  17. prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +20 -14
  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 +3 -5
  21. prediction_market_agent_tooling/markets/seer/data_models.py +92 -5
  22. prediction_market_agent_tooling/markets/seer/seer.py +93 -115
  23. prediction_market_agent_tooling/markets/seer/seer_contracts.py +11 -6
  24. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +18 -26
  25. prediction_market_agent_tooling/monitor/monitor.py +2 -2
  26. prediction_market_agent_tooling/tools/_generic_value.py +261 -0
  27. prediction_market_agent_tooling/tools/balances.py +14 -11
  28. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +12 -10
  29. prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +31 -24
  30. prediction_market_agent_tooling/tools/betting_strategies/utils.py +3 -1
  31. prediction_market_agent_tooling/tools/contract.py +14 -10
  32. prediction_market_agent_tooling/tools/cow/cow_manager.py +3 -4
  33. prediction_market_agent_tooling/tools/cow/cow_order.py +51 -7
  34. prediction_market_agent_tooling/tools/langfuse_client_utils.py +13 -1
  35. prediction_market_agent_tooling/tools/omen/sell_positions.py +6 -3
  36. prediction_market_agent_tooling/tools/safe.py +5 -6
  37. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +36 -27
  38. prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +4 -25
  39. prediction_market_agent_tooling/tools/tokens/main_token.py +2 -2
  40. prediction_market_agent_tooling/tools/tokens/token_utils.py +46 -0
  41. prediction_market_agent_tooling/tools/tokens/usd.py +79 -0
  42. prediction_market_agent_tooling/tools/utils.py +14 -8
  43. prediction_market_agent_tooling/tools/web3_utils.py +24 -41
  44. {prediction_market_agent_tooling-0.61.1.dev489.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/METADATA +2 -1
  45. {prediction_market_agent_tooling-0.61.1.dev489.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/RECORD +48 -47
  46. prediction_market_agent_tooling/markets/seer/price_manager.py +0 -111
  47. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +0 -57
  48. {prediction_market_agent_tooling-0.61.1.dev489.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/LICENSE +0 -0
  49. {prediction_market_agent_tooling-0.61.1.dev489.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/WHEEL +0 -0
  50. {prediction_market_agent_tooling-0.61.1.dev489.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,261 @@
1
+ import typing as t
2
+ from decimal import Decimal
3
+ from typing import TypeVar, overload
4
+
5
+ from pydantic import GetCoreSchemaHandler
6
+ from pydantic_core import CoreSchema, core_schema
7
+ from web3.types import Wei as WeiWeb3
8
+
9
+ InputValueType = TypeVar(
10
+ "InputValueType", bound=t.Union[str, int, float, WeiWeb3, Decimal]
11
+ )
12
+ InternalValueType = TypeVar("InternalValueType", bound=t.Union[int, float, WeiWeb3])
13
+
14
+
15
+ class _GenericValue(
16
+ t.Generic[InputValueType, InternalValueType],
17
+ # Not great, but it allows to serialize this object with plain json.
18
+ dict[t.Literal["value"] | t.Literal["type"], InternalValueType | str],
19
+ ):
20
+ """
21
+ A helper class intended for inheritance. Do not instantiate this class directly.
22
+
23
+ Example:
24
+
25
+ ```python
26
+ a = _GenericValue(10)
27
+ b = Token(100) # Token is a subclass of _GenericValue
28
+ c = xDai(100) # xDai is a subclass of _GenericValue
29
+ d = Mana(100) # Mana is a subclass of _GenericValue
30
+ e = xDai(50)
31
+
32
+ # Mypy will complain if we try to work with different currencies (types)
33
+ b - c # mypy will report incompatible types
34
+ c - d # mypy will report incompatible types
35
+ c - e # mypy will be ok
36
+ a - b # mypy won't report issues, as others are subclasses of _GenericValue, and that's a problem, so don't use _GenericValue directly
37
+
38
+ # Resulting types after arithmetic operations are as expected, so we don't need to wrap them as before (e.g. xdai_type(c + c))
39
+ x = c - e # x is of type xDai
40
+ x = c * e # x if of type xDai
41
+ x = c / e # x is of type float (pure value after division with same types)
42
+ x = c / 2 # x is of type xDai
43
+ x = c // 2 # x is of type xDai
44
+ x * x * 2 # x is of type xDai
45
+ ```
46
+
47
+ TODO: There are some type ignores which isn't cool, but it works and type-wise values are also correct. Idk how to explain it to mypy though.
48
+ """
49
+
50
+ GenericValueType = TypeVar(
51
+ "GenericValueType", bound="_GenericValue[InputValueType, InternalValueType]"
52
+ )
53
+
54
+ parser: t.Callable[[InputValueType], InternalValueType]
55
+
56
+ def __init_subclass__(
57
+ cls, parser: t.Callable[[InputValueType], InternalValueType]
58
+ ) -> None:
59
+ super().__init_subclass__()
60
+ cls.parser = parser
61
+
62
+ def __init__(self, value: InputValueType) -> None:
63
+ self.value: InternalValueType = self.parser(value)
64
+ super().__init__({"value": self.value, "type": self.__class__.__name__})
65
+
66
+ def __str__(self) -> str:
67
+ return f"{self.value}"
68
+
69
+ def __neg__(self: GenericValueType) -> GenericValueType:
70
+ return type(self)(-self.value) # type: ignore[arg-type]
71
+
72
+ def __abs__(self: GenericValueType) -> GenericValueType:
73
+ return type(self)(abs(self.value)) # type: ignore[arg-type]
74
+
75
+ def __sub__(
76
+ self: GenericValueType, other: GenericValueType | t.Literal[0]
77
+ ) -> GenericValueType:
78
+ if other == 0:
79
+ other = self.zero()
80
+ if not isinstance(other, _GenericValue):
81
+ raise TypeError("Cannot subtract different types")
82
+ if type(self) is not type(other):
83
+ raise TypeError("Cannot subtract different types")
84
+ return type(self)(self.value - other.value)
85
+
86
+ def __add__(
87
+ self: GenericValueType, other: GenericValueType | t.Literal[0]
88
+ ) -> GenericValueType:
89
+ if other == 0:
90
+ other = self.zero()
91
+ if not isinstance(other, _GenericValue):
92
+ raise TypeError("Cannot add different types")
93
+ if type(self) is not type(other):
94
+ raise TypeError("Cannot add different types")
95
+ return type(self)(self.value + other.value)
96
+
97
+ def __mul__(
98
+ self: GenericValueType, other: GenericValueType | int | float
99
+ ) -> GenericValueType:
100
+ if not isinstance(other, (_GenericValue, int, float)):
101
+ raise TypeError("Cannot multiply different types")
102
+ if not isinstance(other, (int, float)) and type(self) is not type(other):
103
+ raise TypeError("Cannot multiply different types")
104
+ return type(self)(self.value * (other if isinstance(other, (int, float)) else other.value)) # type: ignore
105
+
106
+ @overload
107
+ def __truediv__(self: GenericValueType, other: int | float) -> GenericValueType:
108
+ ...
109
+
110
+ @overload
111
+ def __truediv__(
112
+ self: GenericValueType, other: GenericValueType
113
+ ) -> InternalValueType:
114
+ ...
115
+
116
+ def __truediv__(
117
+ self: GenericValueType, other: GenericValueType | int | float
118
+ ) -> GenericValueType | InternalValueType:
119
+ if not isinstance(other, (_GenericValue, int, float)):
120
+ raise TypeError("Cannot multiply different types")
121
+ if not isinstance(other, (int, float)) and type(self) is not type(other):
122
+ raise TypeError("Cannot multiply different types")
123
+ if other == 0:
124
+ raise ZeroDivisionError("Cannot divide by zero")
125
+ if isinstance(other, (int, float)):
126
+ return type(self)(self.value / other) # type: ignore
127
+ else:
128
+ return self.value / other.value # type: ignore
129
+
130
+ @overload
131
+ def __floordiv__(self: GenericValueType, other: int | float) -> GenericValueType:
132
+ ...
133
+
134
+ @overload
135
+ def __floordiv__(
136
+ self: GenericValueType, other: GenericValueType
137
+ ) -> InternalValueType:
138
+ ...
139
+
140
+ def __floordiv__(
141
+ self: GenericValueType, other: GenericValueType | int | float
142
+ ) -> GenericValueType | InternalValueType:
143
+ if not isinstance(other, (_GenericValue, int, float)):
144
+ raise TypeError("Cannot multiply different types")
145
+ if not isinstance(other, (int, float)) and type(self) is not type(other):
146
+ raise TypeError("Cannot multiply different types")
147
+ if other == 0:
148
+ raise ZeroDivisionError("Cannot divide by zero")
149
+ if isinstance(other, (int, float)):
150
+ return type(self)(self.value // other) # type: ignore
151
+ else:
152
+ return self.value // other.value # type: ignore
153
+
154
+ def __lt__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
155
+ if other == 0:
156
+ other = self.zero()
157
+ if not isinstance(other, _GenericValue):
158
+ raise TypeError("Cannot compare different types")
159
+ if type(self) is not type(other):
160
+ raise TypeError("Cannot compare different types")
161
+ return bool(self.value < other.value)
162
+
163
+ def __le__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
164
+ if other == 0:
165
+ other = self.zero()
166
+ if not isinstance(other, _GenericValue):
167
+ raise TypeError("Cannot compare different types")
168
+ if type(self) is not type(other):
169
+ raise TypeError("Cannot compare different types")
170
+ return bool(self.value <= other.value)
171
+
172
+ def __gt__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
173
+ if other == 0:
174
+ other = self.zero()
175
+ if not isinstance(other, _GenericValue):
176
+ raise TypeError("Cannot compare different types")
177
+ if type(self) is not type(other):
178
+ raise TypeError("Cannot compare different types")
179
+ return bool(self.value > other.value)
180
+
181
+ def __ge__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
182
+ if other == 0:
183
+ other = self.zero()
184
+ if not isinstance(other, _GenericValue):
185
+ raise TypeError("Cannot compare different types")
186
+ if type(self) is not type(other):
187
+ raise TypeError("Cannot compare different types")
188
+ return bool(self.value >= other.value)
189
+
190
+ def __eq__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool: # type: ignore
191
+ if other == 0:
192
+ other = self.zero()
193
+ if not isinstance(other, _GenericValue):
194
+ raise TypeError("Cannot compare different types")
195
+ if type(self) is not type(other):
196
+ raise TypeError("Cannot compare different types")
197
+ return bool(self.value == other.value)
198
+
199
+ def __ne__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool: # type: ignore
200
+ if other == 0:
201
+ other = self.zero()
202
+ if not isinstance(other, _GenericValue):
203
+ raise TypeError("Cannot compare different types")
204
+ if type(self) is not type(other):
205
+ raise TypeError("Cannot compare different types")
206
+ return bool(self.value != other.value)
207
+
208
+ def __repr__(self) -> str:
209
+ return f"{type(self).__name__}({self.value})"
210
+
211
+ def __float__(self) -> float:
212
+ return float(self.value)
213
+
214
+ def __radd__(
215
+ self: GenericValueType, other: GenericValueType | t.Literal[0] | int | float
216
+ ) -> GenericValueType:
217
+ if isinstance(other, (_GenericValue, int, float)):
218
+ return self.__add__(other) # type: ignore[operator]
219
+
220
+ elif isinstance(other, (int, float)) and other == 0:
221
+ return self
222
+
223
+ else:
224
+ raise TypeError("Cannot add different types")
225
+
226
+ def __round__(self: GenericValueType, ndigits: int = 0) -> GenericValueType:
227
+ if not isinstance(self.value, (int, float)):
228
+ raise TypeError("Cannot round non-numeric types")
229
+ return type(self)(round(self.value, ndigits)) # type: ignore[arg-type]
230
+
231
+ def __bool__(self) -> bool:
232
+ return bool(self.value)
233
+
234
+ @classmethod
235
+ def __get_pydantic_core_schema__(
236
+ cls, source_type: t.Any, handler: GetCoreSchemaHandler
237
+ ) -> CoreSchema:
238
+ # Support for Pydantic usage.
239
+ dt_schema = handler(str | int | float | dict)
240
+ return core_schema.no_info_after_validator_function(
241
+ lambda x: cls(x["value"] if isinstance(x, dict) else x),
242
+ dt_schema,
243
+ )
244
+
245
+ def with_fraction(self: GenericValueType, fraction: float) -> GenericValueType:
246
+ if not 0 <= fraction <= 1:
247
+ raise ValueError(f"Given fraction {fraction} is not in the range [0,1].")
248
+ return self.__class__(self.value * (1 + fraction)) # type: ignore[arg-type]
249
+
250
+ def without_fraction(self: GenericValueType, fraction: float) -> GenericValueType:
251
+ if not 0 <= fraction <= 1:
252
+ raise ValueError(f"Given fraction {fraction} is not in the range [0,1].")
253
+ return self.__class__(self.value * (1 - fraction)) # type: ignore[arg-type]
254
+
255
+ @classmethod
256
+ def zero(cls: type[GenericValueType]) -> GenericValueType:
257
+ return cls(0) # type: ignore[arg-type]
258
+
259
+ @property
260
+ def symbol(self) -> str:
261
+ return self.__class__.__name__
@@ -1,32 +1,35 @@
1
1
  from pydantic import BaseModel
2
2
  from tenacity import retry, stop_after_attempt, wait_fixed
3
3
  from web3 import Web3
4
- from web3.types import Wei
5
4
 
6
- from prediction_market_agent_tooling.gtypes import ChecksumAddress, xDai
5
+ from prediction_market_agent_tooling.gtypes import (
6
+ ChecksumAddress,
7
+ CollateralToken,
8
+ xDai,
9
+ xDaiWei,
10
+ )
7
11
  from prediction_market_agent_tooling.markets.omen.omen_contracts import (
8
12
  WrappedxDaiContract,
9
13
  sDaiContract,
10
14
  )
11
- from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai
12
15
 
13
16
 
14
17
  class Balances(BaseModel):
15
18
  xdai: xDai
16
- wxdai: xDai
17
- sdai: xDai
19
+ wxdai: CollateralToken
20
+ sdai: CollateralToken
18
21
 
19
22
  @property
20
- def total(self) -> xDai:
21
- return xDai(self.xdai + self.wxdai + self.sdai)
23
+ def total(self) -> CollateralToken:
24
+ return self.xdai.as_token + self.wxdai + self.sdai
22
25
 
23
26
 
24
27
  @retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
25
28
  def get_balances(address: ChecksumAddress, web3: Web3 | None = None) -> Balances:
26
29
  if not web3:
27
30
  web3 = WrappedxDaiContract().get_web3()
28
- xdai_balance = Wei(web3.eth.get_balance(address))
29
- xdai = wei_to_xdai(xdai_balance)
30
- wxdai = wei_to_xdai(WrappedxDaiContract().balanceOf(address, web3=web3))
31
- sdai = wei_to_xdai(sDaiContract().balanceOf(address, web3=web3))
31
+ xdai_balance = xDaiWei(web3.eth.get_balance(address))
32
+ xdai = xdai_balance.as_xdai
33
+ wxdai = WrappedxDaiContract().balanceOf(address, web3=web3).as_token
34
+ sdai = sDaiContract().balanceOf(address, web3=web3).as_token
32
35
  return Balances(xdai=xdai, wxdai=wxdai, sdai=sdai)
@@ -1,3 +1,4 @@
1
+ from prediction_market_agent_tooling.gtypes import CollateralToken, OutcomeToken
1
2
  from prediction_market_agent_tooling.markets.market_fees import MarketFees
2
3
  from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
3
4
 
@@ -8,7 +9,7 @@ def check_is_valid_probability(probability: float) -> None:
8
9
 
9
10
 
10
11
  def get_kelly_bet_simplified(
11
- max_bet: float,
12
+ max_bet: CollateralToken,
12
13
  market_p_yes: float,
13
14
  estimated_p_yes: float,
14
15
  confidence: float,
@@ -51,17 +52,17 @@ def get_kelly_bet_simplified(
51
52
  kelly_fraction = edge / odds
52
53
 
53
54
  # Ensure bet size is non-negative does not exceed the wallet balance
54
- bet_size = min(kelly_fraction * max_bet, max_bet)
55
+ bet_size = CollateralToken(min(kelly_fraction * max_bet.value, max_bet.value))
55
56
 
56
57
  return SimpleBet(direction=bet_direction, size=bet_size)
57
58
 
58
59
 
59
60
  def get_kelly_bet_full(
60
- yes_outcome_pool_size: float,
61
- no_outcome_pool_size: float,
61
+ yes_outcome_pool_size: OutcomeToken,
62
+ no_outcome_pool_size: OutcomeToken,
62
63
  estimated_p_yes: float,
63
64
  confidence: float,
64
- max_bet: float,
65
+ max_bet: CollateralToken,
65
66
  fees: MarketFees,
66
67
  ) -> SimpleBet:
67
68
  """
@@ -97,13 +98,13 @@ def get_kelly_bet_full(
97
98
  check_is_valid_probability(confidence)
98
99
 
99
100
  if max_bet == 0:
100
- return SimpleBet(direction=True, size=0)
101
+ return SimpleBet(direction=True, size=CollateralToken(0))
101
102
 
102
- x = yes_outcome_pool_size
103
- y = no_outcome_pool_size
103
+ x = yes_outcome_pool_size.value
104
+ y = no_outcome_pool_size.value
104
105
  p = estimated_p_yes
105
106
  c = confidence
106
- b = max_bet
107
+ b = max_bet.value
107
108
  f = 1 - fee
108
109
 
109
110
  if x == y:
@@ -144,5 +145,6 @@ def get_kelly_bet_full(
144
145
 
145
146
  # Clip the bet size to max_bet to account for rounding errors.
146
147
  return SimpleBet(
147
- direction=kelly_bet_amount > 0, size=min(max_bet, abs(kelly_bet_amount))
148
+ direction=kelly_bet_amount > 0,
149
+ size=CollateralToken(min(max_bet.value, abs(kelly_bet_amount))),
148
150
  )
@@ -2,19 +2,22 @@ from functools import reduce
2
2
 
3
3
  import numpy as np
4
4
 
5
- from prediction_market_agent_tooling.gtypes import Probability, Wei, xDai
5
+ from prediction_market_agent_tooling.gtypes import (
6
+ CollateralToken,
7
+ OutcomeToken,
8
+ Probability,
9
+ )
6
10
  from prediction_market_agent_tooling.markets.omen.omen import (
7
11
  MarketFees,
8
12
  OmenAgentMarket,
9
13
  )
10
14
  from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
11
15
  from prediction_market_agent_tooling.tools.utils import check_not_none
12
- from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai, xdai_to_wei
13
16
 
14
17
 
15
18
  def get_market_moving_bet(
16
- yes_outcome_pool_size: float,
17
- no_outcome_pool_size: float,
19
+ yes_outcome_pool_size: OutcomeToken,
20
+ no_outcome_pool_size: OutcomeToken,
18
21
  market_p_yes: float,
19
22
  target_p_yes: float,
20
23
  fees: MarketFees,
@@ -42,20 +45,21 @@ def get_market_moving_bet(
42
45
  fixed_product = yes_outcome_pool_size * no_outcome_pool_size
43
46
  bet_direction: bool = target_p_yes > market_p_yes
44
47
 
45
- min_bet_amount = 0.0
46
- max_bet_amount = 100 * (
48
+ min_bet_amount = CollateralToken(0.0)
49
+ max_bet_amount = (
47
50
  yes_outcome_pool_size + no_outcome_pool_size
48
- ) # TODO set a better upper bound
51
+ ).as_token * 100 # TODO set a better upper bound
49
52
 
50
53
  # Binary search for the optimal bet amount
51
54
  for _ in range(max_iters):
52
55
  bet_amount = (min_bet_amount + max_bet_amount) / 2
53
- amounts_diff = fees.get_bet_size_after_fees(bet_amount)
56
+ amounts_diff = fees.get_after_fees(bet_amount)
57
+ amounts_diff_as_ot = OutcomeToken.from_token(amounts_diff)
54
58
 
55
59
  # Initial new amounts are old amounts + equal new amounts for each outcome
56
- yes_outcome_new_pool_size = yes_outcome_pool_size + amounts_diff
57
- no_outcome_new_pool_size = no_outcome_pool_size + amounts_diff
58
- new_amounts = {
60
+ yes_outcome_new_pool_size = yes_outcome_pool_size + amounts_diff_as_ot
61
+ no_outcome_new_pool_size = no_outcome_pool_size + amounts_diff_as_ot
62
+ new_amounts: dict[bool, OutcomeToken] = {
59
63
  True: yes_outcome_new_pool_size,
60
64
  False: no_outcome_new_pool_size,
61
65
  }
@@ -63,15 +67,20 @@ def get_market_moving_bet(
63
67
  # Now give away tokens at `bet_outcome_index` to restore invariant
64
68
  new_product = yes_outcome_new_pool_size * no_outcome_new_pool_size
65
69
  dx = (new_product - fixed_product) / new_amounts[not bet_direction]
66
- new_amounts[bet_direction] -= dx
70
+ new_amounts[bet_direction] -= OutcomeToken(dx)
67
71
 
68
72
  # Check that the invariant is restored
69
73
  assert np.isclose(
70
- reduce(lambda x, y: x * y, list(new_amounts.values()), 1.0),
74
+ reduce(lambda x, y: x * y.value, list(new_amounts.values()), 1.0),
71
75
  float(fixed_product),
72
76
  )
73
77
 
74
- new_p_yes = Probability(new_amounts[False] / sum(list(new_amounts.values())))
78
+ new_p_yes = Probability(
79
+ (
80
+ new_amounts[False]
81
+ / sum(list(new_amounts.values()), start=OutcomeToken(0))
82
+ )
83
+ )
75
84
  if abs(target_p_yes - new_p_yes) < 1e-6:
76
85
  break
77
86
  elif new_p_yes > target_p_yes:
@@ -97,33 +106,31 @@ def _sanity_check_omen_market_moving_bet(
97
106
  using the adjusted outcome pool sizes to calculate the new p_yes.
98
107
  """
99
108
  buy_amount_ = market.get_contract().calcBuyAmount(
100
- investment_amount=xdai_to_wei(xDai(bet_to_check.size)),
109
+ investment_amount=bet_to_check.size.as_wei,
101
110
  outcome_index=market.get_outcome_index(
102
111
  market.get_outcome_str_from_bool(bet_to_check.direction)
103
112
  ),
104
113
  )
105
- buy_amount = float(wei_to_xdai(Wei(buy_amount_)))
114
+ buy_amount = buy_amount_.as_outcome_token
106
115
 
107
116
  outcome_token_pool = check_not_none(market.outcome_token_pool)
108
117
  yes_outcome_pool_size = outcome_token_pool[market.get_outcome_str_from_bool(True)]
109
118
  no_outcome_pool_size = outcome_token_pool[market.get_outcome_str_from_bool(False)]
110
- market_const = yes_outcome_pool_size * no_outcome_pool_size
119
+ market_const = yes_outcome_pool_size.value * no_outcome_pool_size.value
111
120
 
112
- bet_to_check_size_after_fees = market.fees.get_bet_size_after_fees(
113
- bet_to_check.size
114
- )
121
+ bet_to_check_size_after_fees = market.fees.get_after_fees(bet_to_check.size).value
115
122
 
116
123
  # When you buy 'yes' tokens, you add your bet size to the both pools, then
117
124
  # subtract `buy_amount` from the 'yes' pool. And vice versa for 'no' tokens.
118
125
  new_yes_outcome_pool_size = (
119
- yes_outcome_pool_size
126
+ yes_outcome_pool_size.value
120
127
  + bet_to_check_size_after_fees
121
- - float(bet_to_check.direction) * buy_amount
128
+ - float(bet_to_check.direction) * buy_amount.value
122
129
  )
123
130
  new_no_outcome_pool_size = (
124
- no_outcome_pool_size
131
+ no_outcome_pool_size.value
125
132
  + bet_to_check_size_after_fees
126
- - float(not bet_to_check.direction) * buy_amount
133
+ - float(not bet_to_check.direction) * buy_amount.value
127
134
  )
128
135
  new_market_const = new_yes_outcome_pool_size * new_no_outcome_pool_size
129
136
  # Check the invariant is restored
@@ -1,6 +1,8 @@
1
1
  from pydantic import BaseModel
2
2
 
3
+ from prediction_market_agent_tooling.gtypes import CollateralToken
4
+
3
5
 
4
6
  class SimpleBet(BaseModel):
5
7
  direction: bool
6
- size: float
8
+ size: CollateralToken
@@ -14,6 +14,7 @@ from prediction_market_agent_tooling.gtypes import (
14
14
  ABI,
15
15
  ChainID,
16
16
  ChecksumAddress,
17
+ CollateralToken,
17
18
  Nonce,
18
19
  TxParams,
19
20
  TxReceipt,
@@ -153,7 +154,7 @@ class ContractBaseClass(BaseModel):
153
154
  api_keys=api_keys,
154
155
  function_name=function_name,
155
156
  function_params=function_params,
156
- tx_params={"value": amount_wei, **(tx_params or {})},
157
+ tx_params={"value": amount_wei.value, **(tx_params or {})},
157
158
  timeout=timeout,
158
159
  web3=web3,
159
160
  )
@@ -243,12 +244,13 @@ class ContractERC20BaseClass(ContractBaseClass):
243
244
  )
244
245
 
245
246
  def balanceOf(self, for_address: ChecksumAddress, web3: Web3 | None = None) -> Wei:
246
- balance: Wei = self.call("balanceOf", [for_address], web3=web3)
247
+ balance = Wei(self.call("balanceOf", [for_address], web3=web3))
247
248
  return balance
248
249
 
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
250
+ def balance_of_in_tokens(
251
+ self, for_address: ChecksumAddress, web3: Web3 | None = None
252
+ ) -> CollateralToken:
253
+ return self.balanceOf(for_address, web3=web3).as_token
252
254
 
253
255
 
254
256
  class ContractDepositableWrapperERC20BaseClass(ContractERC20BaseClass):
@@ -355,11 +357,11 @@ class ContractERC4626BaseClass(ContractERC20BaseClass):
355
357
  return self.withdraw(api_keys=api_keys, assets_wei=assets, web3=web3)
356
358
 
357
359
  def convertToShares(self, assets: Wei, web3: Web3 | None = None) -> Wei:
358
- shares: Wei = self.call("convertToShares", [assets], web3=web3)
360
+ shares = Wei(self.call("convertToShares", [assets], web3=web3))
359
361
  return shares
360
362
 
361
363
  def convertToAssets(self, shares: Wei, web3: Web3 | None = None) -> Wei:
362
- assets: Wei = self.call("convertToAssets", [shares], web3=web3)
364
+ assets = Wei(self.call("convertToAssets", [shares], web3=web3))
363
365
  return assets
364
366
 
365
367
  def get_asset_token_contract(
@@ -434,8 +436,8 @@ class ContractERC721BaseClass(ContractBaseClass):
434
436
  web3=web3,
435
437
  )
436
438
 
437
- def balanceOf(self, owner: ChecksumAddress, web3: Web3 | None = None) -> int:
438
- balance: int = self.call("balanceOf", [owner], web3=web3)
439
+ def balanceOf(self, owner: ChecksumAddress, web3: Web3 | None = None) -> Wei:
440
+ balance = Wei(self.call("balanceOf", [owner], web3=web3))
439
441
  return balance
440
442
 
441
443
  def owner_of(self, token_id: int, web3: Web3 | None = None) -> ChecksumAddress:
@@ -604,12 +606,14 @@ def contract_implements_function(
604
606
 
605
607
 
606
608
  def init_collateral_token_contract(
607
- address: ChecksumAddress, web3: Web3
609
+ address: ChecksumAddress, web3: Web3 | None
608
610
  ) -> ContractERC20BaseClass:
609
611
  """
610
612
  Checks if the given contract is Depositable ERC-20, ERC-20 or ERC-4626 and returns the appropriate class instance.
611
613
  Throws an error if the contract is neither of them.
612
614
  """
615
+ web3 = web3 or RPCConfig().get_web3()
616
+
613
617
  if contract_implements_function(address, "asset", web3=web3):
614
618
  return ContractERC4626BaseClass(address=address)
615
619
 
@@ -19,10 +19,9 @@ 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, Wei, xDai
22
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress, CollateralToken, Wei
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
26
25
 
27
26
  COW_ENV: Envs = "prod"
28
27
 
@@ -94,14 +93,14 @@ class CowManager:
94
93
 
95
94
  @staticmethod
96
95
  def swap(
97
- amount: xDai,
96
+ amount: CollateralToken,
98
97
  sell_token: ChecksumAddress,
99
98
  buy_token: ChecksumAddress,
100
99
  api_keys: APIKeys,
101
100
  web3: Web3 | None = None,
102
101
  ) -> OrderMetaData:
103
102
  order_metadata = swap_tokens_waiting(
104
- amount_wei=xdai_to_wei(amount),
103
+ amount_wei=amount.as_wei,
105
104
  sell_token=sell_token,
106
105
  buy_token=buy_token,
107
106
  api_keys=api_keys,