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.
- eth_portfolio/__init__.py +16 -0
- eth_portfolio/_argspec.py +42 -0
- eth_portfolio/_cache.py +116 -0
- eth_portfolio/_config.py +3 -0
- eth_portfolio/_db/__init__.py +0 -0
- eth_portfolio/_db/decorators.py +147 -0
- eth_portfolio/_db/entities.py +204 -0
- eth_portfolio/_db/utils.py +595 -0
- eth_portfolio/_decimal.py +122 -0
- eth_portfolio/_decorators.py +71 -0
- eth_portfolio/_exceptions.py +67 -0
- eth_portfolio/_ledgers/__init__.py +0 -0
- eth_portfolio/_ledgers/address.py +892 -0
- eth_portfolio/_ledgers/portfolio.py +327 -0
- eth_portfolio/_loaders/__init__.py +33 -0
- eth_portfolio/_loaders/balances.py +78 -0
- eth_portfolio/_loaders/token_transfer.py +214 -0
- eth_portfolio/_loaders/transaction.py +379 -0
- eth_portfolio/_loaders/utils.py +59 -0
- eth_portfolio/_shitcoins.py +212 -0
- eth_portfolio/_utils.py +286 -0
- eth_portfolio/_ydb/__init__.py +0 -0
- eth_portfolio/_ydb/token_transfers.py +136 -0
- eth_portfolio/address.py +382 -0
- eth_portfolio/buckets.py +181 -0
- eth_portfolio/constants.py +58 -0
- eth_portfolio/portfolio.py +629 -0
- eth_portfolio/protocols/__init__.py +66 -0
- eth_portfolio/protocols/_base.py +107 -0
- eth_portfolio/protocols/convex.py +17 -0
- eth_portfolio/protocols/dsr.py +31 -0
- eth_portfolio/protocols/lending/__init__.py +49 -0
- eth_portfolio/protocols/lending/_base.py +57 -0
- eth_portfolio/protocols/lending/compound.py +185 -0
- eth_portfolio/protocols/lending/liquity.py +110 -0
- eth_portfolio/protocols/lending/maker.py +105 -0
- eth_portfolio/protocols/lending/unit.py +47 -0
- eth_portfolio/protocols/liquity.py +16 -0
- eth_portfolio/py.typed +0 -0
- eth_portfolio/structs/__init__.py +43 -0
- eth_portfolio/structs/modified.py +69 -0
- eth_portfolio/structs/structs.py +637 -0
- eth_portfolio/typing.py +1460 -0
- eth_portfolio-1.1.0.dist-info/METADATA +174 -0
- eth_portfolio-1.1.0.dist-info/RECORD +47 -0
- eth_portfolio-1.1.0.dist-info/WHEEL +5 -0
- 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)
|