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.
- eth_protocols/__init__.py +13 -0
- eth_protocols/camelot_v3/__init__.py +5 -0
- eth_protocols/camelot_v3/pool.py +163 -0
- eth_protocols/helpers/__init__.py +9 -0
- eth_protocols/helpers/dex_pairs.py +254 -0
- eth_protocols/helpers/multicall.py +35 -0
- eth_protocols/helpers/price_tracker.py +170 -0
- eth_protocols/logger.py +3 -0
- eth_protocols/tokens/__init__.py +5 -0
- eth_protocols/tokens/erc20/__init__.py +198 -0
- eth_protocols/tokens/erc20/events.py +37 -0
- eth_protocols/types/__init__.py +5 -0
- eth_protocols/types/dex_pair.py +123 -0
- eth_protocols/uniswap_v2/__init__.py +8 -0
- eth_protocols/uniswap_v2/factory.py +46 -0
- eth_protocols/uniswap_v2/pair.py +146 -0
- eth_protocols/uniswap_v2/price.py +104 -0
- eth_protocols/uniswap_v3/__init__.py +5 -0
- eth_protocols/uniswap_v3/liquidity.py +3 -0
- eth_protocols/uniswap_v3/pool.py +168 -0
- eth_protocols/utils/lookup_dict.py +29 -0
- eth_protocols_py-0.1.4.dist-info/METADATA +23 -0
- eth_protocols_py-0.1.4.dist-info/RECORD +25 -0
- eth_protocols_py-0.1.4.dist-info/WHEEL +5 -0
- eth_protocols_py-0.1.4.dist-info/top_level.txt +1 -0
|
@@ -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 @@
|
|
|
1
|
+
eth_protocols
|