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,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
@@ -0,0 +1,170 @@
1
+ import asyncio
2
+ from decimal import Decimal
3
+ from typing import ClassVar, Optional
4
+
5
+ from eth_protocols.logger import logger
6
+ from eth_protocols.uniswap_v2.pair import V2Pair
7
+ from eth_protocols.uniswap_v3.pool import V3Pool
8
+ from eth_rpc import EventData, EventSubscriber, get_current_network
9
+ from eth_rpc.types import Network
10
+ from eth_typeshed.chainlink.eth_usd_feed import ChainlinkPriceOracle, ETHUSDPriceFeed
11
+ from eth_typeshed.constants import Tokens
12
+ from eth_typeshed.uniswap_v2 import V2SyncEvent, V2SyncEventType
13
+ from eth_typeshed.uniswap_v3 import V3SwapEvent, V3SwapEventType
14
+ from eth_typing import HexAddress, HexStr
15
+ from eth_utils import to_checksum_address
16
+ from pydantic import BaseModel, Field, PrivateAttr
17
+
18
+
19
+ class EthPriceProvider(BaseModel):
20
+ feed: ClassVar[ETHUSDPriceFeed] = ETHUSDPriceFeed(
21
+ address=ChainlinkPriceOracle.for_network().ETH
22
+ )
23
+ price: float | None = None
24
+ decimals: int = Field(default=8)
25
+
26
+ def __init__(self, block_number: int | None = None, **kwargs):
27
+ super().__init__(**kwargs)
28
+ if block_number:
29
+ asyncio.run(self.refresh_price(block_number=block_number))
30
+
31
+ async def refresh_price(self, block_number: int | None = None):
32
+ if block_number:
33
+ latest_round_data = (
34
+ await self.feed.latest_round_data().get(block_number=block_number)
35
+ )[1]
36
+ else:
37
+ latest_round_data = (await self.feed.latest_round_data().get())[1]
38
+
39
+ self.price = latest_round_data / 10**self.decimals
40
+
41
+ def get_eth_price(self):
42
+ return Decimal(self.price)
43
+
44
+
45
+ class PriceTracker(BaseModel):
46
+ token_address_to_pairs: dict[HexAddress, list[V2Pair | V3Pool]] = Field(
47
+ default_factory=dict
48
+ )
49
+ pair_address_to_pairs: dict[HexAddress, V2Pair | V3Pool] = Field(
50
+ default_factory=dict
51
+ )
52
+ _eth_price_provider: EthPriceProvider = PrivateAttr(
53
+ default_factory=EthPriceProvider
54
+ )
55
+ network: Network = Field(default_factory=get_current_network)
56
+
57
+ @property
58
+ def stables(self):
59
+ return Tokens.for_network(self.network).stables
60
+
61
+ @property
62
+ def WETH(self):
63
+ return Tokens.for_network(self.network).WETH
64
+
65
+ @property
66
+ def pairs(self):
67
+ return [
68
+ pair for sublist in self.token_address_to_pairs.values() for pair in sublist
69
+ ]
70
+
71
+ async def get_pair_reserves(self, pair_address: HexAddress):
72
+ pair_address = to_checksum_address(pair_address)
73
+ pair = self.pair_address_to_pairs[pair_address]
74
+ if not pair:
75
+ return 0, 0
76
+ return await pair.reserves()
77
+
78
+ def update_pairs(self, pairs: dict[HexAddress, list[V2Pair | V3Pool]]):
79
+ if not pairs:
80
+ return
81
+ for k, pairs_list in pairs.items():
82
+ k = to_checksum_address(k)
83
+ tmp_pairs = []
84
+ for pair in pairs_list:
85
+ address = to_checksum_address(pair.pair_address)
86
+ tmp_pairs.append(pair)
87
+ self.pair_address_to_pairs[address] = pair
88
+ self.token_address_to_pairs[k] = tmp_pairs
89
+
90
+ def is_stable(self, token: HexAddress):
91
+ for s in self.stables:
92
+ if s.lower() == token.lower():
93
+ return True
94
+ return False
95
+
96
+ @staticmethod
97
+ def find_highest_reserve_pair(
98
+ token_address: HexAddress, pairs: list[V2Pair | V3Pool]
99
+ ):
100
+ highest_liq = Decimal(0)
101
+ ret = None
102
+ for pair in pairs:
103
+ reserve = pair.get_reserve(token_address)
104
+ if reserve > highest_liq:
105
+ ret = pair
106
+ highest_liq = reserve
107
+ return ret
108
+
109
+ def get_highest_liq_pair(self, token_address: HexAddress):
110
+ if not self.token_address_to_pairs:
111
+ raise ValueError("token address_to_pairs not set")
112
+ pairs_list = self.token_address_to_pairs.get(
113
+ to_checksum_address(token_address), []
114
+ )
115
+ return self.find_highest_reserve_pair(token_address, pairs_list)
116
+
117
+ def get_tvl_in_usd(self, pair_address):
118
+ if to_checksum_address(pair_address) not in self.pair_address_to_pairs:
119
+ logger.warning(f"not found {pair_address=}")
120
+ return 0
121
+ pair = self.pair_address_to_pairs[to_checksum_address(pair_address)]
122
+ return pair.get_reserve(pair.token0.address) * (
123
+ self.get_price_in_usd(pair.token0.address)
124
+ ) + pair.get_reserve(pair.token1.address) * (
125
+ self.get_price_in_usd(pair.token1.address)
126
+ )
127
+
128
+ def get_price_in_usd(
129
+ self, token_address: HexAddress, block_number: Optional[int] = None
130
+ ):
131
+ """
132
+ Recurse the token address following its deepest liquidity until we reach a token with a known price.
133
+ """
134
+ if to_checksum_address(token_address) == to_checksum_address(self.WETH):
135
+ return self._eth_price_provider.get_eth_price()
136
+ if not token_address:
137
+ return 0
138
+ if self.is_stable(token_address):
139
+ return 1
140
+ pair = self.get_highest_liq_pair(token_address)
141
+ if not pair:
142
+ # FIXME: check if any data is missing for specific pairs
143
+ return 0
144
+ price_a_to_b = pair.get_price(token_address, block_number)
145
+ tokenb = pair.get_other_token(token_address)
146
+ if self.is_stable(tokenb): # in self.stables:
147
+ return price_a_to_b
148
+ # if not found, recurse
149
+ return price_a_to_b * self.get_price_in_usd(tokenb, block_number=block_number)
150
+
151
+ async def subscribe(self):
152
+ """
153
+ subscribe to events from all pairs that you're tracking price
154
+ if new pair added, add to pairs list and restart tracking from last hear block number
155
+ """
156
+ pair_addresses = [pair.pair_address for pair in self.pairs]
157
+ subscriber = EventSubscriber()
158
+ subscriber.add_receiver(self, [V3SwapEvent, V2SyncEvent])
159
+ await subscriber.listen(addresses=pair_addresses)
160
+
161
+ async def put(self, event: EventData[V3SwapEventType | V2SyncEventType]):
162
+ match event.event:
163
+ case V2SyncEventType(reserve0=reserve0, reserve1=reserve1):
164
+ pair = self.pair_address_to_pairs[event.tx.address]
165
+ pair.set_reserves(reserve0, reserve1)
166
+ self.pair_address_to_pairs[event.tx.address]
167
+ case V3SwapEventType():
168
+ # TODO: handle event ingest
169
+ # maybe simulate reserves for V3
170
+ pass
@@ -0,0 +1,3 @@
1
+ import logging
2
+
3
+ logger = logging.getLogger("eth_protocols")
@@ -0,0 +1,5 @@
1
+ from .erc20 import ERC20
2
+
3
+ __all__ = [
4
+ "ERC20",
5
+ ]