eth-protocols-py 0.1.4__tar.gz

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 (31) hide show
  1. eth_protocols_py-0.1.4/PKG-INFO +22 -0
  2. eth_protocols_py-0.1.4/pyproject.toml +38 -0
  3. eth_protocols_py-0.1.4/setup.cfg +4 -0
  4. eth_protocols_py-0.1.4/setup.py +13 -0
  5. eth_protocols_py-0.1.4/src/eth_protocols/__init__.py +13 -0
  6. eth_protocols_py-0.1.4/src/eth_protocols/camelot_v3/__init__.py +5 -0
  7. eth_protocols_py-0.1.4/src/eth_protocols/camelot_v3/pool.py +163 -0
  8. eth_protocols_py-0.1.4/src/eth_protocols/helpers/__init__.py +9 -0
  9. eth_protocols_py-0.1.4/src/eth_protocols/helpers/dex_pairs.py +254 -0
  10. eth_protocols_py-0.1.4/src/eth_protocols/helpers/multicall.py +35 -0
  11. eth_protocols_py-0.1.4/src/eth_protocols/helpers/price_tracker.py +170 -0
  12. eth_protocols_py-0.1.4/src/eth_protocols/logger.py +3 -0
  13. eth_protocols_py-0.1.4/src/eth_protocols/tokens/__init__.py +5 -0
  14. eth_protocols_py-0.1.4/src/eth_protocols/tokens/erc20/__init__.py +198 -0
  15. eth_protocols_py-0.1.4/src/eth_protocols/tokens/erc20/events.py +37 -0
  16. eth_protocols_py-0.1.4/src/eth_protocols/types/__init__.py +5 -0
  17. eth_protocols_py-0.1.4/src/eth_protocols/types/dex_pair.py +123 -0
  18. eth_protocols_py-0.1.4/src/eth_protocols/uniswap_v2/__init__.py +8 -0
  19. eth_protocols_py-0.1.4/src/eth_protocols/uniswap_v2/factory.py +46 -0
  20. eth_protocols_py-0.1.4/src/eth_protocols/uniswap_v2/pair.py +146 -0
  21. eth_protocols_py-0.1.4/src/eth_protocols/uniswap_v2/price.py +104 -0
  22. eth_protocols_py-0.1.4/src/eth_protocols/uniswap_v3/__init__.py +5 -0
  23. eth_protocols_py-0.1.4/src/eth_protocols/uniswap_v3/liquidity.py +3 -0
  24. eth_protocols_py-0.1.4/src/eth_protocols/uniswap_v3/pool.py +168 -0
  25. eth_protocols_py-0.1.4/src/eth_protocols/utils/lookup_dict.py +29 -0
  26. eth_protocols_py-0.1.4/src/eth_protocols_py.egg-info/PKG-INFO +22 -0
  27. eth_protocols_py-0.1.4/src/eth_protocols_py.egg-info/SOURCES.txt +29 -0
  28. eth_protocols_py-0.1.4/src/eth_protocols_py.egg-info/dependency_links.txt +1 -0
  29. eth_protocols_py-0.1.4/src/eth_protocols_py.egg-info/requires.txt +22 -0
  30. eth_protocols_py-0.1.4/src/eth_protocols_py.egg-info/top_level.txt +1 -0
  31. eth_protocols_py-0.1.4/tests/test_create2_address.py +12 -0
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.1
2
+ Name: eth-protocols-py
3
+ Version: 0.1.4
4
+ Requires-Python: >=3.10
5
+ Requires-Dist: eth_rpc_py
6
+ Requires-Dist: eth_typeshed_py
7
+ Requires-Dist: sortedcontainers
8
+ Requires-Dist: eth_hash
9
+ Provides-Extra: lint
10
+ Requires-Dist: mypy; extra == "lint"
11
+ Requires-Dist: ruff; extra == "lint"
12
+ Provides-Extra: test
13
+ Requires-Dist: pytest==7.4.1; extra == "test"
14
+ Requires-Dist: pytest-cov==4.1.0; extra == "test"
15
+ Requires-Dist: coverage[toml]==7.3.1; extra == "test"
16
+ Provides-Extra: build
17
+ Requires-Dist: build[virtualenv]==1.0.3; extra == "build"
18
+ Provides-Extra: dev
19
+ Requires-Dist: tox; extra == "dev"
20
+ Requires-Dist: eth-protocols-py[lint]; extra == "dev"
21
+ Requires-Dist: eth-protocols-py[test]; extra == "dev"
22
+ Requires-Dist: eth-protocols-py[build]; extra == "dev"
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "setuptools_scm[toml]>=8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "eth-protocols-py"
7
+ requires-python = ">=3.10"
8
+ dynamic = ["version"]
9
+ dependencies = [
10
+ "eth_rpc_py",
11
+ "eth_typeshed_py",
12
+ "sortedcontainers",
13
+ "eth_hash"
14
+ ]
15
+
16
+ # Enables the usage of setuptools_scm
17
+ [tool.setuptools_scm]
18
+ root = "../../"
19
+
20
+ [project.optional-dependencies]
21
+ lint = [
22
+ "mypy",
23
+ "ruff",
24
+ ]
25
+ test = [
26
+ "pytest==7.4.1",
27
+ "pytest-cov==4.1.0",
28
+ "coverage[toml]==7.3.1",
29
+ ]
30
+ build = [
31
+ "build[virtualenv]==1.0.3",
32
+ ]
33
+ dev = [
34
+ "tox",
35
+ "eth-protocols-py[lint]",
36
+ "eth-protocols-py[test]",
37
+ "eth-protocols-py[build]",
38
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ import os
2
+
3
+ from setuptools import find_packages, setup
4
+
5
+
6
+ def get_version():
7
+ return os.getenv("PACKAGE_VERSION", "0.0.0")
8
+
9
+
10
+ setup(
11
+ name="eth-protocols-py",
12
+ version=get_version(),
13
+ )
@@ -0,0 +1,13 @@
1
+ from .helpers import DexPairHelper, PriceTracker
2
+ from .tokens import ERC20
3
+ from .uniswap_v2 import V2Factory, V2Pair
4
+ from .uniswap_v3 import V3Pool
5
+
6
+ __all__ = [
7
+ "DexPairHelper",
8
+ "PriceTracker",
9
+ "ERC20",
10
+ "V2Factory",
11
+ "V2Pair",
12
+ "V3Pool",
13
+ ]
@@ -0,0 +1,5 @@
1
+ from .pool import CamelotV3Pool
2
+
3
+ __all__ = [
4
+ "CamelotV3Pool",
5
+ ]
@@ -0,0 +1,163 @@
1
+ from decimal import Decimal
2
+ from typing import cast
3
+
4
+ from eth_protocols.tokens import ERC20
5
+ from eth_protocols.types import DexPair
6
+ from eth_rpc.types import BLOCK_STRINGS, MaybeAwaitable
7
+ from eth_typeshed.camelot_v3 import CamelotV3Pool, GlobalState
8
+ from eth_typeshed.erc20 import OwnerRequest
9
+ from eth_typeshed.multicall import multicall
10
+ from eth_typing import HexAddress
11
+ from eth_utils import to_checksum_address
12
+ from pydantic import PrivateAttr
13
+
14
+ Q192 = Decimal(2**192)
15
+
16
+
17
+ class CamelotV3Pool(DexPair):
18
+ _contract: CamelotV3Pool = PrivateAttr()
19
+
20
+ @classmethod
21
+ def load_static(
22
+ cls,
23
+ pair_address: HexAddress,
24
+ tokena: HexAddress,
25
+ tokenb: HexAddress,
26
+ ):
27
+ token0, token1 = (
28
+ (tokena, tokenb) if tokena.lower() < tokenb.lower() else (tokenb, tokena)
29
+ )
30
+ return cls(
31
+ token0=ERC20(address=token0), # type: ignore
32
+ token1=ERC20(address=token1), # type: ignore
33
+ pair_address=pair_address,
34
+ )
35
+
36
+ @classmethod
37
+ async def load_pair(
38
+ cls,
39
+ pair_address: HexAddress,
40
+ ):
41
+ # TODO: type hints don't work with pydantic validators
42
+ _contract = CamelotV3Pool(address=pair_address)
43
+ token0, token1, fee = await multicall.execute(
44
+ _contract.token0(),
45
+ _contract.token1(),
46
+ )
47
+
48
+ return cls(
49
+ token0=ERC20(address=token0), # type: ignore
50
+ token1=ERC20(address=token1), # type: ignore
51
+ pair_address=pair_address, # type: ignore
52
+ )
53
+
54
+ def set_global_state(self, global_stage: GlobalState):
55
+ self._global_state = global_stage
56
+
57
+ def set_reserve0(self, reserve: int):
58
+ self._reserve0 = reserve
59
+
60
+ def set_reserve1(self, reserve: int):
61
+ self._reserve1 = reserve
62
+
63
+ def get_price(
64
+ self,
65
+ token: HexAddress,
66
+ block_number: int | BLOCK_STRINGS = "latest",
67
+ ) -> Decimal:
68
+ token = to_checksum_address(token)
69
+ assert token == self.token0.address or token == self.token1.address
70
+
71
+ if block_number:
72
+ global_state = self._contract.global_state().get(block_number=block_number)
73
+ else:
74
+ global_state = self._global_state
75
+
76
+ sqrt_price = global_state.price
77
+
78
+ return self.sqrt_price_x96_to_token_prices(
79
+ sqrt_price,
80
+ self.token0.sync.decimals(),
81
+ self.token1.sync.decimals(),
82
+ token == self.token0.address,
83
+ )
84
+
85
+ def get_other_token(self, token: HexAddress) -> HexAddress:
86
+ token = to_checksum_address(token)
87
+ if token == self.token0.address:
88
+ return self.token1.address
89
+ elif token == self.token1.address:
90
+ return self.token0.address
91
+ else:
92
+ raise ValueError(
93
+ f"{token=} cannot be found {self.token0.address=} {self.token1.address=}"
94
+ )
95
+
96
+ def get_reserves(
97
+ self, block_number: int | BLOCK_STRINGS = "latest", sync: bool = False
98
+ ) -> MaybeAwaitable[tuple[int, int]]:
99
+ if sync:
100
+ token0_reserves, token1_reserves = multicall.execute(
101
+ *self._construct_pair_balance_request(), sync=True
102
+ )
103
+ self.set_reserves(token0_reserves, token1_reserves)
104
+ return token0_reserves, token1_reserves
105
+
106
+ async def get_reserves() -> tuple[int, int]:
107
+ token0_reserves = await self.token0.balance_of(
108
+ self.pair_address, block_number=block_number
109
+ )
110
+ token1_reserves = await self.token1.balance_of(
111
+ self.pair_address, block_number=block_number
112
+ )
113
+ self.set_reserves(token0_reserves, token1_reserves)
114
+ return token0_reserves, token1_reserves
115
+
116
+ return get_reserves
117
+
118
+ @staticmethod
119
+ def sqrt_price_x96_to_token_prices(
120
+ sqrt_price_x96: int,
121
+ token0_decimals: int,
122
+ token1_decimals: int,
123
+ token0: bool = True,
124
+ ) -> Decimal:
125
+ num = Decimal(sqrt_price_x96 * sqrt_price_x96)
126
+ price1: Decimal = (
127
+ (num / Q192)
128
+ * (Decimal(10) ** token0_decimals)
129
+ / (Decimal(10) ** token1_decimals)
130
+ )
131
+ if token0:
132
+ return Decimal(price1)
133
+ return Decimal(1) / price1
134
+
135
+ def _construct_pair_balance_request(self):
136
+ return [
137
+ self.token0.raw.balance_of(
138
+ OwnerRequest(
139
+ self.pair_address,
140
+ )
141
+ ),
142
+ self.token1.raw.balance_of(
143
+ OwnerRequest(
144
+ self.pair_address,
145
+ )
146
+ ),
147
+ ]
148
+
149
+ @classmethod
150
+ async def get_all_reserves(
151
+ self, pools: list["V3Pool"], block_number: int | BLOCK_STRINGS = "latest"
152
+ ) -> list[tuple[int, int]]:
153
+ pool_calls = []
154
+ for pool in pools:
155
+ pool_calls.extend(pool._construct_pair_balance_request())
156
+ reserves = cast(
157
+ list[int],
158
+ await multicall.execute(
159
+ *pool_calls,
160
+ block_number=block_number,
161
+ ),
162
+ )
163
+ return [(reserves[i], reserves[i + 1]) for i in range(0, len(reserves), 2)]
@@ -0,0 +1,9 @@
1
+ from .dex_pairs import DexPairHelper
2
+ from .multicall import MultiCallRequestHelper
3
+ from .price_tracker import PriceTracker
4
+
5
+ __all__ = [
6
+ "DexPairHelper",
7
+ "PriceTracker",
8
+ "MultiCallRequestHelper",
9
+ ]
@@ -0,0 +1,254 @@
1
+ from itertools import combinations
2
+
3
+ from eth_protocols.camelot_v3 import CamelotV3Pool
4
+ from eth_protocols.uniswap_v2 import V2Pair
5
+ from eth_protocols.uniswap_v3 import V3Pool
6
+ from eth_rpc.types.primitives import uint24
7
+ from eth_typeshed.camelot_v3 import CamelotV3Factory
8
+ from eth_typeshed.camelot_v3 import GetPoolRequest as CamelotGetPoolRequest
9
+ from eth_typeshed.constants import Factories, Tokens
10
+ from eth_typeshed.erc20 import OwnerRequest
11
+ from eth_typeshed.uniswap_v2 import GetPairRequest, UniswapV2Factory, UniswapV2Pair
12
+ from eth_typeshed.uniswap_v3 import GetPoolRequest, UniswapV3Factory, UniswapV3Pool
13
+ from eth_typeshed.utils import try_execute_with_setters
14
+ from eth_typing import HexAddress
15
+ from eth_utils import to_checksum_address
16
+ from pydantic import BaseModel
17
+
18
+
19
+ class DexPairHelper(BaseModel):
20
+
21
+ @staticmethod
22
+ def find_pair(
23
+ data: dict[HexAddress, list[V2Pair | V3Pool]],
24
+ addr: HexAddress,
25
+ paired_with: HexAddress,
26
+ fee_tiers: list[int],
27
+ uniswap_v2_factory_address: HexAddress,
28
+ uniswap_v3_factory_address: HexAddress,
29
+ ):
30
+ addr = to_checksum_address(addr)
31
+ paired_with = to_checksum_address(paired_with)
32
+ calls_list = [
33
+ (
34
+ UniswapV2Factory(address=uniswap_v2_factory_address).get_pair(
35
+ GetPairRequest(token_a=addr, token_b=paired_with)
36
+ ),
37
+ lambda result, addr=addr, paired_with=paired_with: (
38
+ data.setdefault(addr, []).append(
39
+ V2Pair.load_static(
40
+ pair_address=result,
41
+ tokena=addr,
42
+ tokenb=paired_with,
43
+ )
44
+ )
45
+ ),
46
+ ),
47
+ ]
48
+
49
+ for fee in fee_tiers:
50
+ calls_list.extend(
51
+ [
52
+ (
53
+ UniswapV3Factory(address=uniswap_v3_factory_address).get_pool(
54
+ GetPoolRequest(
55
+ token_a=addr,
56
+ token_b=paired_with,
57
+ fee=uint24(fee),
58
+ )
59
+ ),
60
+ lambda result, addr=addr, paired_with=paired_with, fee=fee: ( # type: ignore
61
+ data.setdefault(addr, []).append(
62
+ V3Pool.load_static(
63
+ tokena=addr,
64
+ tokenb=paired_with,
65
+ pair_address=result,
66
+ fee=fee,
67
+ )
68
+ )
69
+ ),
70
+ ),
71
+ ]
72
+ )
73
+ if uniswap_v3_factory_address == Factories.Arbitrum.Camelot_V3:
74
+ calls_list.extend(
75
+ [
76
+ (
77
+ CamelotV3Factory(
78
+ address=uniswap_v3_factory_address
79
+ ).pool_by_pair(
80
+ CamelotGetPoolRequest(token_a=addr, token_b=paired_with)
81
+ ),
82
+ lambda result, addr=addr, paired_with=paired_with: ( # type: ignore
83
+ data.setdefault(addr, []).append(
84
+ CamelotV3Pool.load_static(
85
+ tokena=addr, tokenb=paired_with, pair_address=result
86
+ )
87
+ )
88
+ ),
89
+ ),
90
+ ]
91
+ )
92
+ return calls_list
93
+
94
+ @staticmethod
95
+ async def find_all_pairs(
96
+ addresses: list[HexAddress],
97
+ find_pairs: list[HexAddress] = Tokens.for_network().main,
98
+ uniswap_v2_factory_address: HexAddress = Factories.for_network().UniswapV2,
99
+ uniswap_v3_factory_address: HexAddress = Factories.for_network().UniswapV3,
100
+ fee_tiers: list[int] = [500, 3000, 10000],
101
+ block_number: int | None = None,
102
+ ) -> dict[HexAddress, list[V2Pair | V3Pool]]:
103
+ data: dict[HexAddress, list[V2Pair | V3Pool]] = {}
104
+ calls_with_setters = []
105
+ for addr in addresses:
106
+ if addr not in data:
107
+ data[addr] = []
108
+ for paired_with in find_pairs:
109
+ calls_with_setters.extend(
110
+ DexPairHelper.find_pair(
111
+ data,
112
+ addr,
113
+ paired_with,
114
+ fee_tiers,
115
+ uniswap_v2_factory_address,
116
+ uniswap_v3_factory_address,
117
+ )
118
+ )
119
+ await try_execute_with_setters(
120
+ calls_with_setters, block_number=block_number or "latest"
121
+ )
122
+ await DexPairHelper.add_reserves_to_pairs(data, block_number=block_number)
123
+ return data
124
+
125
+ @staticmethod
126
+ async def find_all_stables_pairs(
127
+ find_pairs: list[HexAddress] = Tokens.for_network().main,
128
+ uniswap_v2_factory_address: HexAddress = Factories.for_network().UniswapV2,
129
+ uniswap_v3_factory_address: HexAddress = Factories.for_network().UniswapV3,
130
+ fee_tiers: list[int] = [500, 3000, 10000],
131
+ block_number: int | None = None,
132
+ ) -> dict[HexAddress, list[V2Pair | V3Pool]]:
133
+ data: dict[HexAddress, list[V2Pair | V3Pool]] = {}
134
+ calls_with_setters = []
135
+
136
+ unique_tuples = list(combinations(find_pairs, 2))
137
+
138
+ for t in unique_tuples:
139
+ calls_with_setters.extend(
140
+ DexPairHelper.find_pair(
141
+ data,
142
+ t[0],
143
+ t[1],
144
+ fee_tiers,
145
+ uniswap_v2_factory_address,
146
+ uniswap_v3_factory_address,
147
+ )
148
+ )
149
+ await try_execute_with_setters(
150
+ calls_with_setters, block_number=block_number or "latest"
151
+ )
152
+ await DexPairHelper.add_reserves_to_pairs(data, block_number=block_number)
153
+ return data
154
+
155
+ @staticmethod
156
+ async def add_reserves_to_pairs(
157
+ data: dict[HexAddress, list[V2Pair | V3Pool]],
158
+ block_number: int | None = None,
159
+ ):
160
+ calls_with_setters = []
161
+
162
+ for pairs in data.values():
163
+ for pair in pairs:
164
+ if isinstance(pair, V2Pair):
165
+ calls_with_setters.append(
166
+ (
167
+ UniswapV2Pair(address=pair.pair_address).get_reserves(),
168
+ lambda result, pair=pair: (
169
+ pair.set_reserves(result[0], result[1])
170
+ ),
171
+ )
172
+ )
173
+ calls_with_setters.append(
174
+ (
175
+ pair.token0.raw.decimals(),
176
+ lambda result, pair=pair: (
177
+ pair.token0.set_decimals(result)
178
+ ),
179
+ )
180
+ )
181
+ calls_with_setters.append(
182
+ (
183
+ pair.token0.raw.symbol(),
184
+ lambda result, pair=pair: (pair.token0.set_symbol(result)),
185
+ )
186
+ )
187
+ calls_with_setters.append(
188
+ (
189
+ pair.token1.raw.decimals(),
190
+ lambda result, pair=pair: (
191
+ pair.token1.set_decimals(result)
192
+ ),
193
+ )
194
+ )
195
+ calls_with_setters.append(
196
+ (
197
+ pair.token1.raw.symbol(),
198
+ lambda result, pair=pair: (pair.token1.set_symbol(result)),
199
+ )
200
+ )
201
+ elif isinstance(pair, V3Pool):
202
+ calls_with_setters.append(
203
+ (
204
+ UniswapV3Pool(address=pair.pair_address).slot0(),
205
+ lambda result, pair=pair: (pair.set_slot0(result)),
206
+ )
207
+ )
208
+ calls_with_setters.append(
209
+ (
210
+ pair.token0.raw.balance_of(
211
+ OwnerRequest(owner=pair.pair_address)
212
+ ),
213
+ lambda result, pair=pair: (pair.set_reserve0(result)),
214
+ )
215
+ )
216
+ calls_with_setters.append(
217
+ (
218
+ pair.token1.raw.balance_of(
219
+ OwnerRequest(owner=pair.pair_address)
220
+ ),
221
+ lambda result, pair=pair: (pair.set_reserve1(result)),
222
+ )
223
+ )
224
+ calls_with_setters.append(
225
+ (
226
+ pair.token0.raw.decimals(),
227
+ lambda result, pair=pair: (
228
+ pair.token0.set_decimals(result)
229
+ ),
230
+ )
231
+ )
232
+ calls_with_setters.append(
233
+ (
234
+ pair.token0.raw.symbol(),
235
+ lambda result, pair=pair: (pair.token0.set_symbol(result)),
236
+ )
237
+ )
238
+ calls_with_setters.append(
239
+ (
240
+ pair.token1.raw.decimals(),
241
+ lambda result, pair=pair: (
242
+ pair.token1.set_decimals(result)
243
+ ),
244
+ )
245
+ )
246
+ calls_with_setters.append(
247
+ (
248
+ pair.token1.raw.symbol(),
249
+ lambda result, pair=pair: (pair.token1.set_symbol(result)),
250
+ )
251
+ )
252
+ await try_execute_with_setters(
253
+ calls_with_setters, block_number=block_number or "latest"
254
+ )
@@ -0,0 +1,35 @@
1
+ from eth_rpc.types import BLOCK_STRINGS
2
+ from eth_typeshed.utils import try_execute_with_setters
3
+
4
+
5
+ class MultiCallRequestHelper:
6
+ calls_with_setters: list
7
+ data: dict
8
+
9
+ def __init__(self):
10
+ self.calls_with_setters = []
11
+ self.data = {}
12
+
13
+ def prepare(self, address, protocol_call_func, result_handler_func):
14
+ self.prepare_raw(
15
+ address,
16
+ protocol_call_func,
17
+ lambda result: result_handler_func(self.data[address], result),
18
+ )
19
+
20
+ def prepare_data(self, address, init_key, init_value):
21
+ if address not in self.data:
22
+ self.data[address] = {}
23
+ if init_key not in self.data[address]:
24
+ self.data[address][init_key] = init_value
25
+
26
+ def prepare_raw(self, address, protocol_call_func, lambda_handler_func):
27
+ if address not in self.data:
28
+ self.data[address] = {}
29
+ self.calls_with_setters.append((protocol_call_func, lambda_handler_func))
30
+
31
+ async def call(self, block_number: int | BLOCK_STRINGS = "latest"):
32
+ await try_execute_with_setters(
33
+ self.calls_with_setters, block_number=block_number
34
+ )
35
+ return self.data