eth-protocols-py 0.1.4__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.
@@ -0,0 +1,168 @@
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.erc20 import OwnerRequest
8
+ from eth_typeshed.multicall import multicall
9
+ from eth_typeshed.uniswap_v3 import Slot0, UniswapV3Pool
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 V3Pool(DexPair):
18
+ _contract: UniswapV3Pool = PrivateAttr()
19
+ fee: int
20
+
21
+ @classmethod
22
+ def load_static(
23
+ cls,
24
+ pair_address: HexAddress,
25
+ tokena: HexAddress,
26
+ tokenb: HexAddress,
27
+ fee: int,
28
+ ):
29
+ token0, token1 = (
30
+ (tokena, tokenb) if tokena.lower() < tokenb.lower() else (tokenb, tokena)
31
+ )
32
+ return cls(
33
+ token0=ERC20(address=token0), # type: ignore
34
+ token1=ERC20(address=token1), # type: ignore
35
+ pair_address=pair_address, # type: ignore
36
+ fee=fee,
37
+ )
38
+
39
+ @classmethod
40
+ async def load_pair(
41
+ cls,
42
+ pair_address: HexAddress,
43
+ ):
44
+ # TODO: type hints don't work with pydantic validators
45
+ _contract = UniswapV3Pool(address=pair_address)
46
+ token0, token1, fee = await multicall.execute(
47
+ _contract.token0(),
48
+ _contract.token1(),
49
+ _contract.fee(),
50
+ )
51
+
52
+ return cls(
53
+ token0=ERC20(address=token0), # type: ignore
54
+ token1=ERC20(address=token1), # type: ignore
55
+ pair_address=pair_address, # type: ignore
56
+ fee=fee,
57
+ )
58
+
59
+ def set_slot0(self, slot0: Slot0):
60
+ self._slot0 = slot0
61
+
62
+ def set_reserve0(self, reserve: int):
63
+ self._reserve0 = reserve
64
+
65
+ def set_reserve1(self, reserve: int):
66
+ self._reserve1 = reserve
67
+
68
+ def get_price(
69
+ self,
70
+ token: HexAddress,
71
+ block_number: int | BLOCK_STRINGS = "latest",
72
+ ) -> Decimal:
73
+ token = to_checksum_address(token)
74
+ assert token == self.token0.address or token == self.token1.address
75
+
76
+ if block_number:
77
+ slot0 = self._contract.slot0().get(block_number=block_number)
78
+ else:
79
+ slot0 = self._slot0
80
+
81
+ sqrt_price = slot0.sqrt_price
82
+
83
+ return self.sqrt_price_x96_to_token_prices(
84
+ sqrt_price,
85
+ self.token0.sync.decimals(),
86
+ self.token1.sync.decimals(),
87
+ token == self.token0.address,
88
+ )
89
+
90
+ def get_other_token(self, token: HexAddress) -> HexAddress:
91
+ token = to_checksum_address(token)
92
+ if token == self.token0.address:
93
+ return self.token1.address
94
+ elif token == self.token1.address:
95
+ return self.token0.address
96
+ else:
97
+ raise ValueError(
98
+ f"{token=} cannot be found {self.token0.address=} {self.token1.address=}"
99
+ )
100
+
101
+ def get_reserves(
102
+ self, block_number: int | BLOCK_STRINGS = "latest", sync: bool = False
103
+ ) -> MaybeAwaitable[tuple[int, int]]:
104
+ if sync:
105
+ token0_reserves, token1_reserves = multicall.execute(
106
+ *self._construct_pair_balance_request(), sync=True
107
+ )
108
+ self.set_reserves(token0_reserves, token1_reserves)
109
+ return token0_reserves, token1_reserves
110
+
111
+ async def get_reserves() -> tuple[int, int]:
112
+ token0_reserves = await self.token0.balance_of(
113
+ self.pair_address, block_number=block_number
114
+ )
115
+ token1_reserves = await self.token1.balance_of(
116
+ self.pair_address, block_number=block_number
117
+ )
118
+ self.set_reserves(token0_reserves, token1_reserves)
119
+ return token0_reserves, token1_reserves
120
+
121
+ return get_reserves
122
+
123
+ @staticmethod
124
+ def sqrt_price_x96_to_token_prices(
125
+ sqrt_price_x96: int,
126
+ token0_decimals: int,
127
+ token1_decimals: int,
128
+ token0: bool = True,
129
+ ) -> Decimal:
130
+ num = Decimal(sqrt_price_x96 * sqrt_price_x96)
131
+ price1: Decimal = (
132
+ (num / Q192)
133
+ * (Decimal(10) ** token0_decimals)
134
+ / (Decimal(10) ** token1_decimals)
135
+ )
136
+ if token0:
137
+ return Decimal(price1)
138
+ return Decimal(1) / price1
139
+
140
+ def _construct_pair_balance_request(self):
141
+ return [
142
+ self.token0.raw.balance_of(
143
+ OwnerRequest(
144
+ self.pair_address,
145
+ )
146
+ ),
147
+ self.token1.raw.balance_of(
148
+ OwnerRequest(
149
+ self.pair_address,
150
+ )
151
+ ),
152
+ ]
153
+
154
+ @classmethod
155
+ async def get_all_reserves(
156
+ self, pools: list["V3Pool"], block_number: int | BLOCK_STRINGS = "latest"
157
+ ) -> list[tuple[int, int]]:
158
+ pool_calls = []
159
+ for pool in pools:
160
+ pool_calls.extend(pool._construct_pair_balance_request())
161
+ reserves = cast(
162
+ list[int],
163
+ await multicall.execute(
164
+ *pool_calls,
165
+ block_number=block_number,
166
+ ),
167
+ )
168
+ return [(reserves[i], reserves[i + 1]) for i in range(0, len(reserves), 2)]
@@ -0,0 +1,29 @@
1
+ from sortedcontainers import SortedDict
2
+
3
+
4
+ class LookupDict:
5
+ def __init__(self):
6
+ self.data = SortedDict()
7
+
8
+ def add(self, key, value):
9
+ self.data[key] = value
10
+
11
+ def find_lte(self, key):
12
+ # Find the closest key that is less than or equal to the given key
13
+ idx = self.data.bisect_right(key) - 1
14
+ if idx == -1:
15
+ return None, None # If all keys in the dict are greater than the given key
16
+ closest_key = self.data.iloc[idx]
17
+ return closest_key, self.data[closest_key]
18
+
19
+ def find_gte(self, key):
20
+ # Find the closest key that is equal to or larger than the given key
21
+ idx = self.data.bisect_left(key)
22
+ if idx == len(self.data):
23
+ return None, None # If all keys in the dict are smaller than the given key
24
+ closest_key = self.data.iloc[idx]
25
+ return closest_key, self.data[closest_key]
26
+
27
+ def range_iterator(self, low_key, high_key):
28
+ for key in self.data.irange(low_key, high_key, inclusive=(True, True)):
29
+ yield key, self.data[key]
@@ -0,0 +1,23 @@
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: build
10
+ Requires-Dist: build[virtualenv] ==1.0.3 ; extra == 'build'
11
+ Provides-Extra: dev
12
+ Requires-Dist: tox ; extra == 'dev'
13
+ Requires-Dist: eth-protocols-py[lint] ; extra == 'dev'
14
+ Requires-Dist: eth-protocols-py[test] ; extra == 'dev'
15
+ Requires-Dist: eth-protocols-py[build] ; extra == 'dev'
16
+ Provides-Extra: lint
17
+ Requires-Dist: mypy ; extra == 'lint'
18
+ Requires-Dist: ruff ; extra == 'lint'
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest ==7.4.1 ; extra == 'test'
21
+ Requires-Dist: pytest-cov ==4.1.0 ; extra == 'test'
22
+ Requires-Dist: coverage[toml] ==7.3.1 ; extra == 'test'
23
+
@@ -0,0 +1,25 @@
1
+ eth_protocols/__init__.py,sha256=XjIrz_SGXdHWar-Cl1ks44L8XTGtcdmPNIdl0x42rWI,262
2
+ eth_protocols/logger.py,sha256=RVwRQqopRpd4B_I1O2XePvbnG1Q6XfQIMLcCfAivJxI,60
3
+ eth_protocols/camelot_v3/__init__.py,sha256=Ef5CsopBI7COWMItbCpf8LBlF-48w7QYrreJ2TF9P6c,68
4
+ eth_protocols/camelot_v3/pool.py,sha256=HR3YJC2gE9KIpBeXk5RJP7Tjo3flvjzTsD2lib7y4pw,5203
5
+ eth_protocols/helpers/__init__.py,sha256=suGBSUplGYyutI5NLDW5d5oCrKoCJlDimUqL8UdP_G0,209
6
+ eth_protocols/helpers/dex_pairs.py,sha256=gO3xjtN-CLe5JB5JAsr_dx2cv19VHUCYGGYK-MC6Cog,10315
7
+ eth_protocols/helpers/multicall.py,sha256=rDwZ9IuQhzgVzchPkTLjAdtYvDqbnhs9xaoWFWKkI60,1174
8
+ eth_protocols/helpers/price_tracker.py,sha256=s9wH9B2ruTGD-B3xVEMMvcV1-z3CWLNfTo5QARw0lmM,6455
9
+ eth_protocols/tokens/__init__.py,sha256=P5SCiQYJ2j3Egea6Ui6aoIcUHAGzTt18GGn2MvXfVyo,53
10
+ eth_protocols/tokens/erc20/__init__.py,sha256=yBiWw_m7JuQY22_Yq5tEXWYPuAjwWW5fPUC3E6OW_3M,6453
11
+ eth_protocols/tokens/erc20/events.py,sha256=8pKC5ON-F7K4rlsDknFkvA0F_8ItqY-WVidwAbT6ZI0,1198
12
+ eth_protocols/types/__init__.py,sha256=UZCacpP8BfU7VBwyLH8Q_XqamK1WZHcWdJunwJ0w27c,60
13
+ eth_protocols/types/dex_pair.py,sha256=3OMXyBYmqpWWqJKVpVE3OeqiamCvK5F0jIlFpVMxUPQ,3957
14
+ eth_protocols/uniswap_v2/__init__.py,sha256=Yc2TCrIM1Oof5-f-Z4JBdCTw88ZEQcCFBA410Il79Pg,160
15
+ eth_protocols/uniswap_v2/factory.py,sha256=A_rUGKKnJv82oE14gunSnxNCxUsvaxXXdttab1peN6g,1478
16
+ eth_protocols/uniswap_v2/pair.py,sha256=byaB6vx5r6zK_CdLLdiiAWkU5We3QFtGsfj9F-X1dZ0,5245
17
+ eth_protocols/uniswap_v2/price.py,sha256=yhiEy-Esg2GJCbkW5WGF7vnDBNMR7o70HC2sFDDbWiM,3420
18
+ eth_protocols/uniswap_v3/__init__.py,sha256=dSWB0Re-BIgndmTubDxkq7C2yeW_aHxzKt_1z4LqmbM,54
19
+ eth_protocols/uniswap_v3/liquidity.py,sha256=e0meO-7Ct4RUL_uY4O7OgSAywWUnERvgwOUW8WHF0SI,63
20
+ eth_protocols/uniswap_v3/pool.py,sha256=K2JglPT7R69x90zHYyDy0b7N9qULpQemharkgF73ZUI,5244
21
+ eth_protocols/utils/lookup_dict.py,sha256=KpbBbbsVNvIH2xvqccyWSk2B017943exqsvgqC5gYEs,1065
22
+ eth_protocols_py-0.1.4.dist-info/METADATA,sha256=O_AFlQCNw0LzJbhSU5yeO5QhUtXpTelvCS27Y6oax6A,776
23
+ eth_protocols_py-0.1.4.dist-info/WHEEL,sha256=-oYQCr74JF3a37z2nRlQays_SX2MqOANoqVjBBAP2yE,91
24
+ eth_protocols_py-0.1.4.dist-info/top_level.txt,sha256=d2sh5VgSNiq8Mx-TP_-tDYa6w5QVLghgvO8StcjUQl8,14
25
+ eth_protocols_py-0.1.4.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (71.0.3)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ eth_protocols