eth-portfolio 1.1.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.

Potentially problematic release.


This version of eth-portfolio might be problematic. Click here for more details.

Files changed (47) hide show
  1. eth_portfolio/__init__.py +16 -0
  2. eth_portfolio/_argspec.py +42 -0
  3. eth_portfolio/_cache.py +116 -0
  4. eth_portfolio/_config.py +3 -0
  5. eth_portfolio/_db/__init__.py +0 -0
  6. eth_portfolio/_db/decorators.py +147 -0
  7. eth_portfolio/_db/entities.py +204 -0
  8. eth_portfolio/_db/utils.py +595 -0
  9. eth_portfolio/_decimal.py +122 -0
  10. eth_portfolio/_decorators.py +71 -0
  11. eth_portfolio/_exceptions.py +67 -0
  12. eth_portfolio/_ledgers/__init__.py +0 -0
  13. eth_portfolio/_ledgers/address.py +892 -0
  14. eth_portfolio/_ledgers/portfolio.py +327 -0
  15. eth_portfolio/_loaders/__init__.py +33 -0
  16. eth_portfolio/_loaders/balances.py +78 -0
  17. eth_portfolio/_loaders/token_transfer.py +214 -0
  18. eth_portfolio/_loaders/transaction.py +379 -0
  19. eth_portfolio/_loaders/utils.py +59 -0
  20. eth_portfolio/_shitcoins.py +212 -0
  21. eth_portfolio/_utils.py +286 -0
  22. eth_portfolio/_ydb/__init__.py +0 -0
  23. eth_portfolio/_ydb/token_transfers.py +136 -0
  24. eth_portfolio/address.py +382 -0
  25. eth_portfolio/buckets.py +181 -0
  26. eth_portfolio/constants.py +58 -0
  27. eth_portfolio/portfolio.py +629 -0
  28. eth_portfolio/protocols/__init__.py +66 -0
  29. eth_portfolio/protocols/_base.py +107 -0
  30. eth_portfolio/protocols/convex.py +17 -0
  31. eth_portfolio/protocols/dsr.py +31 -0
  32. eth_portfolio/protocols/lending/__init__.py +49 -0
  33. eth_portfolio/protocols/lending/_base.py +57 -0
  34. eth_portfolio/protocols/lending/compound.py +185 -0
  35. eth_portfolio/protocols/lending/liquity.py +110 -0
  36. eth_portfolio/protocols/lending/maker.py +105 -0
  37. eth_portfolio/protocols/lending/unit.py +47 -0
  38. eth_portfolio/protocols/liquity.py +16 -0
  39. eth_portfolio/py.typed +0 -0
  40. eth_portfolio/structs/__init__.py +43 -0
  41. eth_portfolio/structs/modified.py +69 -0
  42. eth_portfolio/structs/structs.py +637 -0
  43. eth_portfolio/typing.py +1460 -0
  44. eth_portfolio-1.1.0.dist-info/METADATA +174 -0
  45. eth_portfolio-1.1.0.dist-info/RECORD +47 -0
  46. eth_portfolio-1.1.0.dist-info/WHEEL +5 -0
  47. eth_portfolio-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,379 @@
1
+ """
2
+ This module contains loader functions that facilitate the retrieval and loading of blockchain transaction data.
3
+
4
+ The functions defined here use various async operations to retrieve transaction data from Ethereum-like blockchains, and process them, to then store them in a local database. The module leverages utilities like retry mechanisms, caching, and efficient async processing to handle potentially large amounts of blockchain data.
5
+
6
+ The primary focus of this module is to support eth-portfolio's internal operations such as loading transactions by address and nonce, retrieving transaction details from specific blocks, and managing transaction-related data.
7
+ """
8
+
9
+ import asyncio
10
+ import itertools
11
+ from collections import defaultdict
12
+ from logging import DEBUG, getLogger
13
+ from typing import DefaultDict, Dict, List, Optional, Tuple
14
+
15
+ import a_sync
16
+ import dank_mids
17
+ import eth_retry
18
+ import evmspec
19
+ import msgspec
20
+ from async_lru import alru_cache
21
+ from evmspec import data
22
+ from pony.orm import TransactionIntegrityError
23
+ from y import get_price
24
+ from y._decorators import stuck_coro_debugger
25
+ from y.constants import EEE_ADDRESS
26
+ from y.datatypes import Address, Block
27
+ from y.exceptions import reraise_excs_with_extra_context
28
+ from y.utils.events import decode_logs
29
+
30
+ from eth_portfolio.structs import structs
31
+ from eth_portfolio._cache import cache_to_disk
32
+ from eth_portfolio._db import utils as db
33
+ from eth_portfolio._decimal import Decimal
34
+ from eth_portfolio._loaders.utils import get_transaction_receipt
35
+
36
+ logger = getLogger(__name__)
37
+
38
+ Nonce = int
39
+ Nonces = DefaultDict[Nonce, Block]
40
+
41
+ nonces: DefaultDict[Address, Nonces] = defaultdict(lambda: defaultdict(int))
42
+
43
+
44
+ @eth_retry.auto_retry
45
+ @stuck_coro_debugger
46
+ async def load_transaction(
47
+ address: Address, nonce: Nonce, load_prices: bool
48
+ ) -> Tuple[Nonce, Optional[structs.Transaction]]:
49
+ """
50
+ Loads a transaction by address and nonce.
51
+
52
+ This function attempts to load a transaction from the local database based on the given address and nonce.
53
+
54
+ If the transaction is not found in the local database or if price data is missing but requested by the user, it retrieves the transaction data from the blockchain using a binary search approach, processes it, and stores it back in the local database.
55
+
56
+ Args:
57
+ address: The address of the account involved in the transaction.
58
+ nonce: The nonce associated with the transaction.
59
+ load_prices: Whether to load and calculate price-related information for the transaction.
60
+
61
+ Returns:
62
+ A tuple containing the nonce and the retrieved or newly created :class:`~eth_portfolio.structs.Transaction` object, or None if the transaction could not be found.
63
+
64
+ Example:
65
+ >>> print(await load_transaction(address="0x1234567890abcdef1234567890abcdef12345678", nonce=5, load_prices=True))
66
+ (5, Transaction(...))
67
+ """
68
+ if transaction := await db.get_transaction(address, nonce):
69
+ if load_prices and transaction.price is None:
70
+ await db.delete_transaction(transaction)
71
+ else:
72
+ return nonce, transaction
73
+
74
+ block = await get_block_for_nonce(address, nonce)
75
+ tx = await get_transaction_by_nonce_and_block(address, nonce, block)
76
+ if tx is None:
77
+ return nonce, None
78
+
79
+ if load_prices:
80
+ # TODO: debug why `tx.value` isnt already a Wei obj
81
+ scaled = data.Wei(tx.value).scaled
82
+ price = Decimal(await get_price(EEE_ADDRESS, block=tx.blockNumber, sync=False))
83
+ transaction = structs.Transaction.from_rpc_response(
84
+ tx, price=round(price, 18), value_usd=round(scaled * price, 18)
85
+ )
86
+ else:
87
+ transaction = structs.Transaction.from_rpc_response(tx)
88
+
89
+ a_sync.create_task(
90
+ coro=_insert_to_db(transaction, load_prices),
91
+ skip_gc_until_done=True,
92
+ )
93
+
94
+ return nonce, transaction
95
+
96
+
97
+ @alru_cache(maxsize=None, ttl=5)
98
+ async def _get_block_number():
99
+ return await dank_mids.eth.block_number
100
+
101
+
102
+ _nonce_cache_locks: DefaultDict[Address, asyncio.Lock] = defaultdict(asyncio.Lock)
103
+
104
+
105
+ async def get_block_for_nonce(address: Address, nonce: Nonce) -> int:
106
+ async with _nonce_cache_locks[address]:
107
+ highest_known_nonce_lower_than_query = None
108
+ lowest_known_nonce_greater_than_query = None
109
+
110
+ # it is impossible for n to == nonce
111
+ filtered = filter(lambda n: n != nonce, nonces[address])
112
+ for less_than, ns in itertools.groupby(filtered, lambda n: n < nonce):
113
+ if less_than:
114
+ max_value = max(ns)
115
+ if (
116
+ highest_known_nonce_lower_than_query is None
117
+ or max_value > highest_known_nonce_lower_than_query
118
+ ):
119
+ highest_known_nonce_lower_than_query = max_value
120
+
121
+ else:
122
+ min_value = min(ns)
123
+ if (
124
+ lowest_known_nonce_greater_than_query is None
125
+ or min_value < lowest_known_nonce_greater_than_query
126
+ ):
127
+ lowest_known_nonce_greater_than_query = min_value
128
+
129
+ if highest_known_nonce_lower_than_query is not None:
130
+ lo = nonces[address][highest_known_nonce_lower_than_query]
131
+ else:
132
+ lo = 0
133
+
134
+ if lowest_known_nonce_greater_than_query is not None:
135
+ hi = nonces[address][lowest_known_nonce_greater_than_query]
136
+ else:
137
+ hi = await _get_block_number()
138
+
139
+ del highest_known_nonce_lower_than_query, lowest_known_nonce_greater_than_query
140
+
141
+ # lets find the general area first before we proceed with our binary search
142
+ range_size = hi - lo + 1
143
+ if range_size > 4:
144
+ num_chunks = _get_num_chunks(range_size)
145
+ chunk_size = range_size // num_chunks
146
+ points: Dict[int, Nonce] = await a_sync.gather(
147
+ {
148
+ point: get_nonce_at_block(address, point)
149
+ for point in (lo + i * chunk_size for i in range(num_chunks))
150
+ }
151
+ )
152
+
153
+ for block, _nonce in points.items():
154
+ if _nonce >= nonce:
155
+ hi = block
156
+ break
157
+ lo = block
158
+
159
+ del num_chunks, chunk_size, points, block
160
+
161
+ del range_size
162
+
163
+ debug_logs_enabled = logger.isEnabledFor(DEBUG)
164
+ while True:
165
+ _nonce = await get_nonce_at_block(address, lo)
166
+
167
+ if _nonce < nonce:
168
+ old_lo = lo
169
+ lo += int((hi - lo) / 2) or 1
170
+ if debug_logs_enabled:
171
+ logger._log(
172
+ DEBUG,
173
+ "Nonce for %s at %s is %s, checking higher block %s",
174
+ (address, old_lo, _nonce, lo),
175
+ )
176
+ continue
177
+
178
+ prev_block_nonce: int = await get_nonce_at_block(address, lo - 1)
179
+ if prev_block_nonce >= nonce:
180
+ hi = lo
181
+ lo = int(lo / 2)
182
+ if debug_logs_enabled:
183
+ logger._log(
184
+ DEBUG,
185
+ "Nonce for %s at %s is %s, checking lower block %s",
186
+ (address, hi, _nonce, lo),
187
+ )
188
+ continue
189
+
190
+ if debug_logs_enabled:
191
+ logger._log(DEBUG, "Found nonce %s for %s at block %s", (nonce, address, lo))
192
+
193
+ return lo
194
+
195
+
196
+ async def _insert_to_db(transaction: structs.Transaction, load_prices: bool) -> None:
197
+ with reraise_excs_with_extra_context(transaction):
198
+ try:
199
+ await db.insert_transaction(transaction)
200
+ except TransactionIntegrityError:
201
+ if load_prices:
202
+ await db.delete_transaction(transaction)
203
+ await db.insert_transaction(transaction)
204
+
205
+
206
+ @eth_retry.auto_retry
207
+ @stuck_coro_debugger
208
+ @cache_to_disk
209
+ async def get_transaction_by_nonce_and_block(
210
+ address: Address, nonce: int, block: Block
211
+ ) -> Optional[evmspec.Transaction]:
212
+ """
213
+ This function retrieves a transaction for a specifified address by its nonce and block, if any match.
214
+
215
+ It also handles special cases:
216
+
217
+ 1. Contract creation transactions where the transaction's 'to' field is None.
218
+ 2. Gnosis Safe deployments where the transaction's 'to' field is a specific address.
219
+
220
+ Args:
221
+ address: The addresses of the accounts that sent the transaction.
222
+ nonce: The nonce associated with the transaction creator.
223
+ block: The block number from which to retrieve the transactions.
224
+
225
+ Returns:
226
+ The transaction data if found, otherwise None.
227
+
228
+ Example:
229
+ >>> transaction = await get_transaction_by_nonce_and_block(address="0x1234567890abcdef1234567890abcdef12345678", nonce=5, block=1234567)
230
+ >>> if transaction:
231
+ ... print(transaction.hash)
232
+ """
233
+ for tx in await get_block_transactions(block):
234
+ if tx.sender == address and tx.nonce == nonce:
235
+ return tx
236
+
237
+ receipt_bytes: msgspec.Raw
238
+ # Special handler for contract creation transactions
239
+ if tx.to is None:
240
+ receipt_bytes = await get_transaction_receipt(tx.hash.hex())
241
+ receipt_0 = msgspec.json.decode(
242
+ receipt_bytes,
243
+ type=ReceiptContractAddress,
244
+ dec_hook=data.Address._decode_hook,
245
+ )
246
+ if receipt_0.contractAddress == address:
247
+ return tx
248
+ # Special handler for Gnosis Safe deployments
249
+ elif tx.to == "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2":
250
+ receipt_bytes = await get_transaction_receipt(tx.hash.hex())
251
+ receipt_1 = msgspec.json.decode(
252
+ receipt_bytes,
253
+ type=ReceiptLogs,
254
+ dec_hook=data._decode_hook,
255
+ )
256
+ events = decode_logs(receipt_1.logs)
257
+ if (
258
+ "SafeSetup" in events
259
+ and "ProxyCreation" in events
260
+ and any(event["proxy"] == address for event in events["ProxyCreation"])
261
+ ):
262
+ return tx
263
+ return None
264
+
265
+
266
+ class ReceiptContractAddress(msgspec.Struct):
267
+ """We only decode what we need and immediately discard the rest of the receipt."""
268
+
269
+ contractAddress: data.Address
270
+
271
+
272
+ class ReceiptLogs(msgspec.Struct):
273
+ logs: List[evmspec.Log]
274
+
275
+
276
+ @alru_cache(maxsize=None)
277
+ @eth_retry.auto_retry
278
+ @stuck_coro_debugger
279
+ @cache_to_disk
280
+ async def get_nonce_at_block(address: Address, block: Block) -> int:
281
+ """
282
+ Retrieves the nonce of an address at a specific block.
283
+
284
+ This function gets the transaction count (nonce) for the given address at the specified block. It also includes a special case to handle known issues on certain networks like Arbitrum.
285
+
286
+ Args:
287
+ address: The address of the account.
288
+ block: The block number at which to retrieve the nonce.
289
+
290
+ Returns:
291
+ The nonce of the address at the given block.
292
+
293
+ Example:
294
+ >>> block = 12345678
295
+ >>> nonce = await get_nonce_at_block("0x1234567890abcdef1234567890abcdef12345678", block)
296
+ >>> print(f"The nonce at block {block} is {nonce}.")
297
+
298
+ """
299
+ nonce = None
300
+ try:
301
+ # This is a temp fix for a bug in dank mids
302
+ # TODO move this into dank
303
+ while nonce is None:
304
+ nonce = await dank_mids.eth.get_transaction_count(address, block_identifier=block) - 1
305
+ _update_nonces(address, nonce, block)
306
+ return nonce
307
+ except ValueError as e:
308
+ # NOTE this is known to occur on Arbitrum
309
+ if "error creating execution cursor" in str(e) and block == 0:
310
+ return -1
311
+ raise ValueError(f"For {address} at {block}: {e}") from e
312
+
313
+
314
+ def _update_nonces(address: Address, nonce: Nonce, block: Block):
315
+ # if you are searching for `nonce` and you verified it occurs AT or ABOVE `block` call this fn.
316
+ if block > nonces[address][nonce]:
317
+ nonces[address][nonce] = block
318
+
319
+
320
+ @alru_cache(ttl=60 * 60)
321
+ @eth_retry.auto_retry
322
+ @stuck_coro_debugger
323
+ async def _get_block_transactions(block: Block) -> List[evmspec.Transaction]:
324
+ """
325
+ Retrieves all transactions from a specific block.
326
+
327
+ This function fetches the full transaction data for a block using async caching to optimize repeated requests.
328
+
329
+ The cache has a time-to-live (TTL) of 1 hour to avoid memory leaks for services or long-running scripts.
330
+
331
+ Args:
332
+ block: The block number from which to retrieve the transactions.
333
+
334
+ Returns:
335
+ A list of transaction data objects from the block.
336
+
337
+ Example:
338
+ >>> transactions = await _get_block_transactions(block=12345678)
339
+ >>> [print(tx.hash) for tx in transactions]
340
+ """
341
+ return await dank_mids.eth.get_transactions(block)
342
+
343
+
344
+ get_block_transactions = a_sync.SmartProcessingQueue(_get_block_transactions, 1_000)
345
+ """
346
+ A smart processing queue that retrieves transactions from blocks with managable concurrency.
347
+
348
+ This queue processes the retrieval of block transactions using a limited number of workers to handle potentially overwhelming volume of transactions.
349
+ It wraps the _get_block_transactions function to provide efficient concurrent processing.
350
+
351
+ Example:
352
+ >>> transactions = await get_block_transactions.process(block=12345678)
353
+ >>> [print(tx['hash']) for tx in transactions]
354
+ """
355
+
356
+
357
+ def _get_num_chunks(range_size: int) -> int:
358
+ if range_size >= 4096:
359
+ return 100
360
+ elif range_size >= 2048:
361
+ return 80
362
+ elif range_size >= 1024:
363
+ return 40
364
+ elif range_size >= 512:
365
+ return 20
366
+ elif range_size >= 256:
367
+ return 10
368
+ elif range_size >= 128:
369
+ return 8
370
+ elif range_size >= 64:
371
+ return 6
372
+ elif range_size >= 32:
373
+ return 5
374
+ elif range_size >= 16:
375
+ return 4
376
+ elif range_size >= 8:
377
+ return 3
378
+ else:
379
+ return 2
@@ -0,0 +1,59 @@
1
+ import dank_mids
2
+ import eth_retry
3
+ import msgspec
4
+ from a_sync import SmartProcessingQueue
5
+ from async_lru import alru_cache
6
+ from eth_typing import HexStr
7
+ from y._decorators import stuck_coro_debugger
8
+
9
+
10
+ @eth_retry.auto_retry
11
+ @alru_cache(maxsize=None, ttl=60 * 60)
12
+ @stuck_coro_debugger
13
+ async def _get_transaction_receipt(txhash: HexStr) -> msgspec.Raw:
14
+ """
15
+ Fetches the transaction receipt for a given transaction hash.
16
+
17
+ This function retrieves the transaction receipt from the Ethereum network
18
+ using the provided transaction hash. It utilizes caching to store results
19
+ for a specified time-to-live (TTL) to improve performance and reduce
20
+ network calls. The function is also decorated to automatically retry
21
+ in case of failures and to debug if the coroutine gets stuck.
22
+
23
+ Args:
24
+ txhash: The transaction hash for which to retrieve the receipt.
25
+
26
+ Returns:
27
+ msgspec.Raw: The raw transaction receipt data.
28
+
29
+ Examples:
30
+ >>> txhash = "0x1234567890abcdef..."
31
+ >>> receipt = await _get_transaction_receipt(txhash)
32
+ >>> print(receipt)
33
+
34
+ See Also:
35
+ - :func:`eth_retry.auto_retry`: For automatic retry logic.
36
+ - :func:`async_lru.alru_cache`: For caching the results.
37
+ - :func:`y._decorators.stuck_coro_debugger`: For debugging stuck coroutines.
38
+ """
39
+ return await dank_mids.eth.get_transaction_receipt(
40
+ txhash, decode_to=msgspec.Raw, decode_hook=None
41
+ )
42
+
43
+
44
+ get_transaction_receipt = SmartProcessingQueue(_get_transaction_receipt, 5000)
45
+ """
46
+ A queue for processing transaction receipt requests.
47
+
48
+ This queue manages the processing of transaction receipt requests, allowing
49
+ up to 5000 concurrent requests. It uses the `_get_transaction_receipt` function
50
+ to fetch the receipts.
51
+
52
+ Examples:
53
+ >>> txhash = "0x1234567890abcdef..."
54
+ >>> receipt = await get_transaction_receipt(txhash)
55
+ >>> print(receipt)
56
+
57
+ See Also:
58
+ - :class:`a_sync.SmartProcessingQueue`: For managing asynchronous processing queues.
59
+ """
@@ -0,0 +1,212 @@
1
+ from collections import defaultdict
2
+ from typing import DefaultDict, Set
3
+
4
+ from brownie.convert import EthAddress
5
+ from y import Network, convert
6
+
7
+ shitcoins = {
8
+ Network.Mainnet: [
9
+ # Tagged as fake on Etherscan
10
+ "0xb8ac53AFCCEBc44AaC33D0C30E2Ae0783E7fde5f",
11
+ "0xe397ef3E332256F38983ffaE987158da3e18c5Ec",
12
+ "0x7452E3fC2fE611C6B7761c6c393BECe059881aC7",
13
+ "0x4ab16CDc82a4eA4727Ab40caee1bb46622C13641",
14
+ "0x471c3A7f132bc94938516CB2Bf6f02C7521D2797",
15
+ "0x106EebF11F34ECCcaD59c1CA9398d828765f64f8",
16
+ "0x634a31d5DB29F2a646bADAd27bb7b1E5A78FD316",
17
+ "0xF673623e8507551BDe72290E909c7E184A4799a3",
18
+ "0x269641A320F8465eF4e710F51Dc6e6862D7E8A77",
19
+ "0x956F824B5a37673c6fC4a6904186cB3BA499349B",
20
+ "0xBFA9180729f1c549334080005Ca37093593fB7Aa",
21
+ "0x643695D282f6BA237afe27FFE0Acd89a86b50d3e",
22
+ # Tagged as spam on Etherscan
23
+ "0xCfB0Ef3e179427316FBD7F0D0296F173762BeEE0",
24
+ "0xfAFe8a7c0A9b3807CC1df0DEB0BA0B5a5fb7A872",
25
+ "0xdf66B9727553fB9Bfa417699CB8F60425d62d1E3",
26
+ "0x68Ca006dB91312Cd60a2238Ce775bE5F9f738bBa",
27
+ "0xBCCBeDAb3Bf6FA1cfC3e2d07Aa5ce4A282864D6E",
28
+ "0x26004d228fC8A32c5bd1a106108c8647A455B04a",
29
+ "0x635701CC5fE41FE8AbD02aa74Beb02e3540E9BB2",
30
+ "0xf8358bd95dcA48187e3F4BE05847F3593776C086",
31
+ "0x459176FDC68C945B6bb23eB946eee62457041567",
32
+ "0xf55F8aA8F3d777f1aB3A4ae9E269C8D7B73078De",
33
+ "0x434d5875534D19fC7105281Cf61dbfd1C93c8cb1",
34
+ "0x908599FDf490b73D171B57731bd4Ca95b7F0DE6a",
35
+ "0x4709099BE25D156578405132d66aeBfC2e12937A",
36
+ "0x82315517d61ecf47f9A78705127934F3d431cB21",
37
+ "0x2B000332CD291eF558aF76298A4d6F6001E4e015",
38
+ "0x163F4D81d86ac282A0F0F94D9FE3E0321ACcfd37",
39
+ "0x660235331d2FA5FeB7a49F31556B0Be31f02560D",
40
+ "0x696F33F4dd9BE5538483f63308453D2D67D07331",
41
+ "0xcbbadd74b3bb09836d97d91050e13E06089ba485",
42
+ "0xE0c31b3e341E7EF3C04eB14E58802b88a80A45A1",
43
+ "0xA6D74802a2222d5cCe5eA0531159ed878943b54c",
44
+ "0xcAB80A2bf07BECaF8d48168081352ea873B8Db91",
45
+ "0x72B12aec69dA93357f2B69aCf33d5B75cF17575B",
46
+ "0xA4C8A13FeE3b19718E45d678C071bDE3e33A7302",
47
+ "0x579e4ca5888eD1420492988BF75E26D9e7B4C535",
48
+ "0xB80216D5b4eec2BEc74eF10e5d3814Fec6Fd8af0",
49
+ "0x6A007E207E50B4C6B2ADCFc6a873F6e698645fE3",
50
+ "0x84d12988D71244a8937a9816037BeB3e61E17FdD",
51
+ "0x0734E85525Ca6838fe48EC6EB29b9d457F254F73",
52
+ "0xa10c97bF5629340A35c41a8AA308af0804750605",
53
+ "0x69D732F50e248D4B825d524fEDEB0D7Ce3d76352",
54
+ "0xAF0b2fBeDd5d1Fda457580FB3DAbAD1F5C8bBC36",
55
+ "0xbadb4170Cd3309680477Fc81D527101cbB4000EE",
56
+ "0x7841479c5976b8184DBcde9a7a5113901b233EfB",
57
+ "0x1e4A0ADFC2e9bf2Dfd041Bc89405538d0D5A077a",
58
+ "0xb07de4b2989E180F8907B8C7e617637C26cE2776",
59
+ "0xa9517B2E61a57350D6555665292dBC632C76adFe",
60
+ "0x38715Ab4b9d4e00890773D7338d94778b0dFc0a8",
61
+ "0x6171136E82a2f1bAA2494c69528f599467EfeA20",
62
+ "0x9Ee582c42F88CAA5e7BCDE2e86e69Af3Cf5FCe40",
63
+ "0xFD1860C9d602236214652a21753731F9acD2C362",
64
+ "0x0Fd23DaB8723b1Fd5e7c3d74Baa624B443423b6B",
65
+ "0xe0736F3F455F1DBD29Bf6F8346EAd22f5CF78d08",
66
+ "0x7F57A01fc540ca3A26345C4903B9a220aD048196",
67
+ "0x635701CC5fE41FE8AbD02aa74Beb02e3540E9BB2",
68
+ "0xF709642622729feafC8bf0934679706df49d8A30",
69
+ # Tagged as phishing on Etherscan
70
+ "0x0bF377fb3b5F1dD601e693B8fAF6b0bD249f37D3",
71
+ "0xBf5fB1563ef58ba41325454ca61Cc3D62bd40744",
72
+ "0x54fd62228C6e1234fd5Fded28555CA963Dcf6d26",
73
+ "0xA36Ceec605d81aE74268Fda28A5c0Bd10b1D1f7C",
74
+ "0xF9d25EB4C75ed744596392cf89074aFaA43614a8",
75
+ "0x1412ECa9dc7daEf60451e3155bB8Dbf9DA349933",
76
+ "0x1fc4DB3e7f9124bAAFB7B34346dB7931Dad621C2",
77
+ "0x120aA018634F555484c088c8da80F75Aa07E004F",
78
+ "0xeDe11D3d5dd7D5454844f6f121cc106bF1144a45",
79
+ "0x875bf9be244970B8572DD042053508bF758371Ee",
80
+ "0x070C0147884D7CF984aFBC2Eb6F3428A39b5E229",
81
+ "0xb0B1d4732eFE32AEA466ED6BC3c79181eD4810c4",
82
+ "0xCf39B7793512F03f2893C16459fd72E65D2Ed00c",
83
+ "0x89532890c2959e836C1Bca52309356841238b8B7",
84
+ "0xD1B0c350f73E6f7Cd5c9978b82B46a59aE9aD524",
85
+ "0x17a10104CBC1eD155D083eaD9FCF5C3440bb50e8",
86
+ "0x8eB3FecAAA963C86d9b49004d9f28092f1Db3D6c",
87
+ "0x52bbca3B24c1c1ac050fF4546F37AA88282aaE22",
88
+ "0xECF0dE4C8498Cfd686E4702D955426b22d812d6B",
89
+ "0xF01f7A348681776c1FC9A066c6973882B693cdC6",
90
+ "0x0e1CD6d2715432e4DBedFE969b0Eb2867FF61d5b",
91
+ # Generally looks like shit
92
+ "0xd5b0Bd07864C0aaCC3b6Cd5e63C68c62C0F263d2",
93
+ "0x2b23BF339F5cc3393a558373E0A73a576F0838D3",
94
+ "0x7B2e238FB48ee7322664B9C26bb3ACedBfCC1f70",
95
+ "0x08918171758171A13050cdE6Cc6eB90172Af5737",
96
+ "0x9398c448728738039b91687919262194899D3f27",
97
+ "0x0A527683c3154F5F9E1b4203Ef4d05962b2411bf",
98
+ "0xe3C6F9D0d731C2Eb6B6D3eBFb6732fCd26a365d0",
99
+ "0x6cF0B5A20B2d4b55e6b752d7016275B892035652",
100
+ "0x98d1c76831c0aBEa30e3dac8628BD72d6B0E3dd2",
101
+ "0x4C6fDc0476B2FE0daFf0B5824c3A918673d6014E",
102
+ "0xC6d3D201530a6D4aD9dFbAAd39C5f68A9A470a69",
103
+ "0x525fC44CBE181C1108c209091B5EEc5a5028190d",
104
+ "0x7d1a6a4f806A4a64AD32e7F2350E176eA6B9a1F6",
105
+ "0x75E34A4A04d5f5F7Fc01801d2d287d64D882529B",
106
+ "0x8F49cB69ee13974D6396FC26B0c0D78044FCb3A7",
107
+ "0xB688d06d858E092EBB145394a1BA08C7a10E1F56",
108
+ "0x154C5875b1B0DB1794f88D003730DaD160E6b38e",
109
+ "0x11068577AE36897fFaB0024F010247B9129459E6",
110
+ "0xBA89375bAE9b3DE92442e9C037d4303A6e4FB086",
111
+ "0xcDbd4089C2F98DA715e52127680f87aFdB183A2e",
112
+ "0x3a3a4d2d9755283D9e25105B042C5f45BC0Edf05",
113
+ "0x70c18F2fDcb00d27494f767503874788e35c9940",
114
+ "0xF511123fdf2F13811abf4edDb493860101471729",
115
+ "0x830Cbe766EE470B67F77ea62a56246863F75f376",
116
+ "0x76988Eb5d8a81F44919d87328c36179FdCca044a",
117
+ "0xa6DE609807c7258A0D34F5307c1808F062A59794",
118
+ "0x698068C6a369b1BF04D516f5fE48424797973DCf",
119
+ "0xbEb3c5F7f4F8dB708BcfaC4D0fDcDb0bEd285741",
120
+ "0xCdC94877E4164D2e915fC5E8310155D661A995F1",
121
+ "0x5D80A8D8CB80696073e82407968600A37e1dd780",
122
+ "0x19383F024BA4c06e44D11a8B8BB7ebF87faB184C",
123
+ "0xF5b2C59F6DB42FFCdFC1625999C81fDF17953384",
124
+ "0xdb83eC9EEAC2b3CF8Eb282f91c73C38159578697",
125
+ "0x2F30E0F6B484eF6Be57b6435e34687018ff8Cb4D",
126
+ "0x242a705E8AF8A24B7EB30f3DbAF899eB25E3D76A",
127
+ "0x2f848B4A2B5dfC3b9e4Eb229551c0887E6348653",
128
+ "0x0795619E02716a81ac9EF6E55363D538DA104e57",
129
+ "0x6D9541ba0f1039d0f8636b4f39D20A8a7464f357",
130
+ "0x1C3d9Db84e0EEE4744893A7FAeE6187F31E39539",
131
+ "0x3654746Ce159BA2FCDF926133D51ecBb85f19288",
132
+ "0x67542502245eb5DF64eF7Ea776199CeB79401058",
133
+ "0x0951490Cec0261F60Ff0C42DE7F62488Cc8313D8",
134
+ "0x154C5875b1B0DB1794f88D003730DaD160E6b38e",
135
+ "0xcdBb37f84bf94492b44e26d1F990285401e5423e",
136
+ "0x53d345839E7dF5a6c8Cf590C5c703AE255E44816",
137
+ "0xCCCCee7d9B0f18ab16b217A794D2671549F1A895",
138
+ "0xc6a76f7ad66d0e6Ccd1AaAd6e7568c9bd55Dce62",
139
+ "0xB4d4334eB5a251cbE5cC7Afb0Ba26119aCE62281",
140
+ "0xE5c5AE39B98eFd9D3c9E0F2a5457D98fFA4b0b46",
141
+ # Tagged as well known address scam on Etherscan
142
+ "0x2Ec109a0ceFEC70661a242a8B54cae8f45630397",
143
+ "0x11d666B9C8fa057774324a9bFe6B53eB8d23079f",
144
+ "0x0598BEf845934d83463cEaDd8C86F0801e2Ee058",
145
+ "0xAcDfe439652F78750AFCe1BbA2cB9068EDb4e69c",
146
+ "0xd3cc310DD7b94D6Ef06615f73A3029A67d935c5E",
147
+ "0x0C6D7f5a8c122aCB941aCb95cb99C2cdE893b7a7",
148
+ "0xCBd85ffF5106a42d622be7F9e528DB0bfF07Da8A",
149
+ "0x22e810C6aed30FA93C2D2B62F456E5f78e8918e1",
150
+ "0xc750d0c91DD5f2D3964931FF5dC42B2bB3e008C1",
151
+ "0x6284C089D6944df1ba61bd56618AEba07D1d6136",
152
+ "0x4E654E3A532a386329bde5f01D0AC7995Ab94c07",
153
+ "0x570EC272F07c563860477DCAfB04a04FFd2979a1",
154
+ "0xEcd8eDb13Cd68707579D5EC891A24C03B8d5E831",
155
+ "0x5D6434D533922d60F1eeec913f680C5daF31cB9A",
156
+ "0x15B8adFa95e841a55867495F07B8102329546987",
157
+ "0xFe0bb2ae94BF11de9773DECb413b4a6a241cBCd0",
158
+ "0x4d3D07f4E7a154c32D36bBcBE2Ea64E0B6F98440",
159
+ "0xEea2fEf22353282fb760d27EA7A1E2f06B3F442d",
160
+ "0x218Ae209BEc57eeFa9149789aE09388459bC91d1",
161
+ "0x4639FFC90b0fD6Dffb57Af712109bfa419afaEB5",
162
+ "0x39dDC0a04F0E1F2830f3f1FEc414cD6E23168beE",
163
+ "0x4a41775Da459B38e641141e4C696DF10EC1f4983",
164
+ "0x72C07D9151DcE2ea862595487ef7e21cC312e564",
165
+ "0xE00cD9B8ebb503E4bE266983EfC6158fCFfe0004",
166
+ "0x46DDf95a62b0F2eF6dc4e70717f36172a595517F",
167
+ "0x7cfa05320D83A20980Ac76B91a3A11981877Ef3A",
168
+ "0xbbaC5B019876534c3C399F56E150684c5015F670",
169
+ "0x2840a9fC9ad15738c762e404300761eC828aFFCb",
170
+ "0xC1c8c49b0405f6CFfBA5351179bEFB2d8a2c776c",
171
+ "0xcB56B36Ef3d4E5C3b7d2fbC4f4Ee5d525893a555",
172
+ "0x5807daC5CC59F02dFA88154dE7F9f2174C263814",
173
+ "0x6545B409acdD7e1BE14C835B5c3B826C5d312D02",
174
+ "0xdF781bBA6F9EefB1A74bb39f6DF5e282c5976636",
175
+ "0x85D385244D41ac914484FD6fbBaB177c10A86e79",
176
+ "0xa51a8578052EdEB4Ced5333A5e058860d9E7a35b",
177
+ "0x7a6b87D7a874Fce4c2d923b09C0E09e4936bcF57",
178
+ "0xFabb91ae9318082505c2c6d7acD9d521d4eDDEFf",
179
+ "0x5807daC5CC59F02dFA88154dE7F9f2174C263814",
180
+ ],
181
+ Network.Fantom: [
182
+ # generally looks like shit
183
+ "0x630277E37fd2Ddf81e4683f3692dD817aa6225Cb",
184
+ ],
185
+ Network.Optimism: [
186
+ "0x168fbA6072EE467931484a418EDeb5FcC1B9fb79",
187
+ "0x04788E66B9D884969538e4e35a0D57e9e022c799",
188
+ "0x6Cad3dE073FD85567c0Df84063c8990855710677",
189
+ "0x109390594d0ef9B40B4B745Cac6ceCf37a179417",
190
+ "0x87f4b57E2d3c8C9dCD85aD9dA80129f92754b19f",
191
+ "0x1828Bff08BD244F7990edDCd9B19cc654b33cDB4",
192
+ "0xd8dAE0aD69F4aDB507dB0C3e56Ec94942D470450",
193
+ ],
194
+ Network.Arbitrum: [
195
+ "0xA57D85481B9ecD864F542Ea37bAA79A101a4f599",
196
+ "0x73fcaFc42ABB43A408bdE4b37eAfCCDadCB2b554",
197
+ "0x13577D823114697289cf85d3f563a21417022e7C",
198
+ "0xb8f2A48362371F7B4E88460BD4B3B5dba48A344f",
199
+ "0x184deAc2B1052AcB020Fa5159683733be3447699",
200
+ "0xBE9460b50f515169E14545aaddF60B3C9dB7e5Fb",
201
+ "0x8fb95D642C19a9C5C9138C5b444455A69196F2EC",
202
+ "0x1d7edf990Cd21eaB14aA55f8d06a386c73D24B5B",
203
+ "0x96C708e060CdC5B8C6Ac6a015db0b10833F74157",
204
+ "0x4dB2207C2cf35aA5ecFE61c5d098Bb3e3Ed1a7B0",
205
+ "0x17c074e90372aC53FF98cA24c3745E6E38717C14",
206
+ ],
207
+ }
208
+
209
+ SHITCOINS: DefaultDict[Network, Set[EthAddress]] = defaultdict(set)
210
+ """A dictionary that contains, for each chain, a set of shitcoins that will be fully ignored by eth_portfolio. Add your own shitcoins to this object to boost eth_portfolio's performance."""
211
+ for chain, tokens in shitcoins.items():
212
+ SHITCOINS[chain].update(convert.to_address(token) for token in tokens)