eth-portfolio-temp 0.2.14__cp313-cp313-win_amd64.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.
Files changed (83) hide show
  1. eth_portfolio/__init__.py +25 -0
  2. eth_portfolio/_argspec.cp313-win_amd64.pyd +0 -0
  3. eth_portfolio/_argspec.py +42 -0
  4. eth_portfolio/_cache.py +121 -0
  5. eth_portfolio/_config.cp313-win_amd64.pyd +0 -0
  6. eth_portfolio/_config.py +4 -0
  7. eth_portfolio/_db/__init__.py +0 -0
  8. eth_portfolio/_db/decorators.py +147 -0
  9. eth_portfolio/_db/entities.py +311 -0
  10. eth_portfolio/_db/utils.py +604 -0
  11. eth_portfolio/_decimal.py +156 -0
  12. eth_portfolio/_decorators.py +84 -0
  13. eth_portfolio/_exceptions.py +67 -0
  14. eth_portfolio/_ledgers/__init__.py +0 -0
  15. eth_portfolio/_ledgers/address.py +938 -0
  16. eth_portfolio/_ledgers/portfolio.py +327 -0
  17. eth_portfolio/_loaders/__init__.py +33 -0
  18. eth_portfolio/_loaders/_nonce.cp313-win_amd64.pyd +0 -0
  19. eth_portfolio/_loaders/_nonce.py +196 -0
  20. eth_portfolio/_loaders/balances.cp313-win_amd64.pyd +0 -0
  21. eth_portfolio/_loaders/balances.py +94 -0
  22. eth_portfolio/_loaders/token_transfer.py +217 -0
  23. eth_portfolio/_loaders/transaction.py +240 -0
  24. eth_portfolio/_loaders/utils.cp313-win_amd64.pyd +0 -0
  25. eth_portfolio/_loaders/utils.py +68 -0
  26. eth_portfolio/_shitcoins.cp313-win_amd64.pyd +0 -0
  27. eth_portfolio/_shitcoins.py +329 -0
  28. eth_portfolio/_stableish.cp313-win_amd64.pyd +0 -0
  29. eth_portfolio/_stableish.py +42 -0
  30. eth_portfolio/_submodules.py +73 -0
  31. eth_portfolio/_utils.py +225 -0
  32. eth_portfolio/_ydb/__init__.py +0 -0
  33. eth_portfolio/_ydb/token_transfers.py +145 -0
  34. eth_portfolio/address.py +397 -0
  35. eth_portfolio/buckets.py +194 -0
  36. eth_portfolio/constants.cp313-win_amd64.pyd +0 -0
  37. eth_portfolio/constants.py +82 -0
  38. eth_portfolio/portfolio.py +661 -0
  39. eth_portfolio/protocols/__init__.py +67 -0
  40. eth_portfolio/protocols/_base.py +108 -0
  41. eth_portfolio/protocols/convex.py +17 -0
  42. eth_portfolio/protocols/dsr.py +51 -0
  43. eth_portfolio/protocols/lending/README.md +6 -0
  44. eth_portfolio/protocols/lending/__init__.py +50 -0
  45. eth_portfolio/protocols/lending/_base.py +57 -0
  46. eth_portfolio/protocols/lending/compound.py +187 -0
  47. eth_portfolio/protocols/lending/liquity.py +110 -0
  48. eth_portfolio/protocols/lending/maker.py +104 -0
  49. eth_portfolio/protocols/lending/unit.py +46 -0
  50. eth_portfolio/protocols/liquity.py +16 -0
  51. eth_portfolio/py.typed +0 -0
  52. eth_portfolio/structs/__init__.py +43 -0
  53. eth_portfolio/structs/modified.py +69 -0
  54. eth_portfolio/structs/structs.py +637 -0
  55. eth_portfolio/typing/__init__.py +1447 -0
  56. eth_portfolio/typing/balance/single.py +176 -0
  57. eth_portfolio__mypyc.cp313-win_amd64.pyd +0 -0
  58. eth_portfolio_scripts/__init__.py +20 -0
  59. eth_portfolio_scripts/_args.py +26 -0
  60. eth_portfolio_scripts/_logging.py +15 -0
  61. eth_portfolio_scripts/_portfolio.py +194 -0
  62. eth_portfolio_scripts/_utils.py +106 -0
  63. eth_portfolio_scripts/balances.cp313-win_amd64.pyd +0 -0
  64. eth_portfolio_scripts/balances.py +52 -0
  65. eth_portfolio_scripts/docker/.grafana/dashboards/Portfolio/Balances.json +1962 -0
  66. eth_portfolio_scripts/docker/.grafana/dashboards/dashboards.yaml +10 -0
  67. eth_portfolio_scripts/docker/.grafana/datasources/datasources.yml +11 -0
  68. eth_portfolio_scripts/docker/__init__.cp313-win_amd64.pyd +0 -0
  69. eth_portfolio_scripts/docker/__init__.py +16 -0
  70. eth_portfolio_scripts/docker/check.cp313-win_amd64.pyd +0 -0
  71. eth_portfolio_scripts/docker/check.py +67 -0
  72. eth_portfolio_scripts/docker/docker-compose.yaml +61 -0
  73. eth_portfolio_scripts/docker/docker_compose.cp313-win_amd64.pyd +0 -0
  74. eth_portfolio_scripts/docker/docker_compose.py +81 -0
  75. eth_portfolio_scripts/main.py +119 -0
  76. eth_portfolio_scripts/py.typed +1 -0
  77. eth_portfolio_scripts/victoria/__init__.py +73 -0
  78. eth_portfolio_scripts/victoria/types.py +38 -0
  79. eth_portfolio_temp-0.2.14.dist-info/METADATA +26 -0
  80. eth_portfolio_temp-0.2.14.dist-info/RECORD +83 -0
  81. eth_portfolio_temp-0.2.14.dist-info/WHEEL +5 -0
  82. eth_portfolio_temp-0.2.14.dist-info/entry_points.txt +2 -0
  83. eth_portfolio_temp-0.2.14.dist-info/top_level.txt +3 -0
@@ -0,0 +1,217 @@
1
+ """
2
+ This module orchestrates the process of loading and processing token transfers within the eth_portfolio ecosystem.
3
+ """
4
+
5
+ import decimal
6
+ from logging import getLogger
7
+ from typing import Final, Optional, Set
8
+
9
+ from a_sync import create_task, gather
10
+ from dank_mids import BlockSemaphore
11
+ from eth_typing import ChecksumAddress
12
+ from evmspec.data import TransactionIndex
13
+ from faster_async_lru import alru_cache
14
+ from msgspec import Struct, ValidationError
15
+ from msgspec.json import decode
16
+ from pony.orm import TransactionIntegrityError, UnexpectedError
17
+ from y import ERC20
18
+ from y._db.log import Log
19
+ from y._decorators import stuck_coro_debugger
20
+ from y.exceptions import NonStandardERC20, reraise_excs_with_extra_context
21
+
22
+ from eth_portfolio._cache import cache_to_disk
23
+ from eth_portfolio._db import utils as db
24
+ from eth_portfolio._decimal import Decimal
25
+ from eth_portfolio._loaders.utils import get_transaction_receipt
26
+ from eth_portfolio._utils import _get_price
27
+ from eth_portfolio.structs import TokenTransfer
28
+
29
+
30
+ logger = getLogger(__name__)
31
+
32
+ token_transfer_semaphore: Final = BlockSemaphore(
33
+ 20_000, # Some arbitrary number
34
+ name="eth_portfolio.token_transfers",
35
+ )
36
+ """A semaphore that regulates the concurrent processing of token transfers by processing lower blocks first."""
37
+
38
+
39
+ @stuck_coro_debugger
40
+ async def load_token_transfer(
41
+ transfer_log: "Log", load_prices: bool
42
+ ) -> Optional[TokenTransfer]: # sourcery skip: simplify-boolean-comparison
43
+ """
44
+ Processes and loads a token transfer from a log entry, with comprehensive error handling and optional price fetching.
45
+
46
+ This function employs a multi-step process:
47
+ 1. Validates the transfer against a known set of 'shitcoins', skipping processing if matched.
48
+ 2. Checks for existing database entries, potentially deleting and reprocessing if price data is requested but missing.
49
+ 3. Decodes the transfer log and retrieves associated metadata (e.g., token scale, symbol, transaction index).
50
+ 4. Optionally fetches the token price at the time of the transaction.
51
+ 5. Constructs and persists a :class:`~eth_portfolio.structs.TokenTransfer` object in the database.
52
+
53
+ The function handles various exceptions, including :class:`~y.exceptions.NonStandardERC20` for non-compliant tokens and :class:`decimal.InvalidOperation` for extreme :class:`~Decimal` values.
54
+
55
+ Args:
56
+ transfer_log: A dictionary containing the raw log entry of the token transfer.
57
+ load_prices: A flag indicating whether to fetch and include price data for the token at the time of transfer.
58
+
59
+ Returns:
60
+ A processed TokenTransfer object if successful, or None if the transfer cannot be processed due to various constraints or errors.
61
+
62
+ Note:
63
+ This function employs caching mechanisms and database operations to optimize performance.
64
+ """
65
+ if transfer_log.removed:
66
+ if transfer := await db.get_token_transfer(transfer_log):
67
+ await db.delete_token_transfer(transfer)
68
+ return None
69
+
70
+ if transfer := await db.get_token_transfer(transfer_log):
71
+ if load_prices is False or transfer.price:
72
+ return transfer
73
+ await db.delete_token_transfer(transfer)
74
+
75
+ token_address: ChecksumAddress = transfer_log.address
76
+ if token_address in _non_standard_erc20:
77
+ logger.debug("%s is not a standard ERC20 token, skipping.", token_address)
78
+ return None
79
+
80
+ async with token_transfer_semaphore[transfer_log.block]:
81
+ token = ERC20(token_address, asynchronous=True)
82
+ try:
83
+ try:
84
+ # This will be mem cached so no need to gather and add a bunch of overhead
85
+ scale = await token.scale
86
+ except NonStandardERC20 as e:
87
+ # NOTE: if we cant fetch scale, this is probably either a shitcoin or an NFT (which we don't support at this time)
88
+ logger.debug("%s for %s, skipping.", e, transfer_log)
89
+ _non_standard_erc20.add(token_address)
90
+ return None
91
+
92
+ # This will be mem cached so no need to include it in the gather and add a bunch of overhead
93
+ symbol = await get_symbol(token)
94
+
95
+ tx_index_coro = get_transaction_index(transfer_log.transactionHash.hex())
96
+ coro_results = {"token": symbol}
97
+
98
+ if load_prices:
99
+ coro_results.update(
100
+ await gather(
101
+ {
102
+ "transaction_index": tx_index_coro,
103
+ "price": _get_price(token.address, transfer_log.blockNumber),
104
+ }
105
+ )
106
+ )
107
+ else:
108
+ coro_results["transaction_index"] = await tx_index_coro
109
+
110
+ except Exception as e:
111
+ logger.error(
112
+ f"%s %s for %s %s at block %s:",
113
+ e.__class__.__name__,
114
+ e,
115
+ await get_symbol(token) or token.address,
116
+ token_address,
117
+ transfer_log.blockNumber,
118
+ )
119
+ logger.exception(e)
120
+ return None
121
+
122
+ value = Decimal(transfer_log.data.as_uint256) / scale
123
+
124
+ if price := coro_results.get("price"):
125
+ coro_results["value_usd"] = round(value * price, 18)
126
+
127
+ transfer = TokenTransfer(log=transfer_log, value=value, **coro_results)
128
+
129
+ create_task(
130
+ _insert_to_db(transfer, load_prices),
131
+ skip_gc_until_done=True,
132
+ )
133
+
134
+ return transfer
135
+
136
+
137
+ async def _insert_to_db(transfer: TokenTransfer, load_prices: bool) -> None:
138
+ with reraise_excs_with_extra_context(transfer):
139
+ try:
140
+ await db.insert_token_transfer(transfer)
141
+ except TransactionIntegrityError:
142
+ if load_prices:
143
+ await db.delete_token_transfer(transfer)
144
+ await db.insert_token_transfer(transfer)
145
+ except UnexpectedError:
146
+ digits_before_decimal = str(transfer.value).split(".")[0]
147
+ if len(digits_before_decimal) <= 20:
148
+ raise
149
+ except decimal.InvalidOperation as e:
150
+ # TODO: debug why this happens sometimes
151
+ logger.exception("%s %s", e, transfer)
152
+ pass
153
+
154
+
155
+ _non_standard_erc20: Final[Set[ChecksumAddress]] = set()
156
+
157
+
158
+ @stuck_coro_debugger
159
+ async def get_symbol(token: ERC20) -> Optional[str]:
160
+ """
161
+ Retrieves the symbol of a given ERC20 token, with error handling for non-standard implementations.
162
+
163
+ This function attempts to access the token's symbol through the standard ERC20 symbol method. If the token contract
164
+ does not adhere to the standard ERC20 interface, indicated by a :class:`~y.exceptions.NonStandardERC20` exception, the function
165
+ returns `None` instead of propagating the error.
166
+
167
+ Args:
168
+ token: An ERC20 token object representing the token whose symbol is to be retrieved.
169
+
170
+ Returns:
171
+ The token's symbol as a string if successfully retrieved, or None if the token does not implement a standard symbol method.
172
+ """
173
+ if token.address in _non_standard_erc20:
174
+ return None
175
+ try:
176
+ return await token.__symbol__
177
+ except NonStandardERC20:
178
+ _non_standard_erc20.add(token.address)
179
+ return None
180
+
181
+
182
+ @alru_cache(ttl=60 * 60)
183
+ @stuck_coro_debugger
184
+ @cache_to_disk
185
+ async def get_transaction_index(hash: str) -> int:
186
+ """
187
+ Retrieves the transaction index for a given transaction hash, with results cached to disk.
188
+
189
+ This function fetches the transaction receipt corresponding to the provided hash and extracts the transaction index,
190
+ which represents the position of the transaction within its containing block. The result is cached to disk to
191
+ optimize performance for future runs.
192
+
193
+ Args:
194
+ hash: The hexadecimal string representation of the transaction hash.
195
+
196
+ Returns:
197
+ The zero-based index of the transaction within its block.
198
+ """
199
+ while True:
200
+ receipt_bytes = await get_transaction_receipt(hash)
201
+ if receipt_bytes is not None:
202
+ # TODO: debug why this happens, its something inside of dank_mids
203
+ break
204
+ logger.info("get_transaction_index failed, retrying...")
205
+
206
+ try:
207
+ return decode(
208
+ receipt_bytes, type=HasTxIndex, dec_hook=TransactionIndex._decode_hook
209
+ ).transactionIndex
210
+ except ValidationError as e:
211
+ new = TypeError(e, receipt_bytes, decode(receipt_bytes))
212
+ logger.exception(new)
213
+ raise new from e
214
+
215
+
216
+ class HasTxIndex(Struct):
217
+ transactionIndex: TransactionIndex
@@ -0,0 +1,240 @@
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
+ from logging import getLogger
10
+ from typing import Awaitable, Callable, Final, List, Optional, Tuple
11
+
12
+ import a_sync
13
+ import dank_mids
14
+ import eth_retry
15
+ import evmspec
16
+ import msgspec
17
+ from a_sync import SmartProcessingQueue
18
+ from eth_typing import ChecksumAddress
19
+ from evmspec import data
20
+ from faster_async_lru import alru_cache
21
+ from pony.orm import TransactionIntegrityError
22
+ from y import get_price
23
+ from y._decorators import stuck_coro_debugger
24
+ from y.constants import EEE_ADDRESS
25
+ from y.datatypes import Block
26
+ from y.exceptions import reraise_excs_with_extra_context
27
+ from y.utils.events import decode_logs
28
+
29
+ from eth_portfolio.structs import structs
30
+ from eth_portfolio._cache import cache_to_disk
31
+ from eth_portfolio._db import utils as db
32
+ from eth_portfolio._decimal import Decimal
33
+ from eth_portfolio._loaders._nonce import Nonce, get_block_for_nonce
34
+ from eth_portfolio._loaders._nonce import get_nonce_at_block as _get_nonce_at_block
35
+ from eth_portfolio._loaders.utils import get_transaction_receipt
36
+
37
+
38
+ Transactions = List[evmspec.Transaction]
39
+
40
+
41
+ logger: Final = getLogger(__name__)
42
+
43
+
44
+ @eth_retry.auto_retry
45
+ @stuck_coro_debugger
46
+ async def load_transaction(
47
+ address: ChecksumAddress, 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
+ async def _insert_to_db(transaction: structs.Transaction, load_prices: bool) -> None:
98
+ with reraise_excs_with_extra_context(transaction):
99
+ try:
100
+ await db.insert_transaction(transaction)
101
+ except TransactionIntegrityError:
102
+ if load_prices:
103
+ await db.delete_transaction(transaction)
104
+ await db.insert_transaction(transaction)
105
+
106
+
107
+ @eth_retry.auto_retry
108
+ @stuck_coro_debugger
109
+ @cache_to_disk
110
+ async def get_transaction_by_nonce_and_block(
111
+ address: ChecksumAddress, nonce: int, block: Block
112
+ ) -> Optional[evmspec.Transaction]:
113
+ """
114
+ This function retrieves a transaction for a specifified address by its nonce and block, if any match.
115
+
116
+ It also handles special cases:
117
+
118
+ 1. Contract creation transactions where the transaction's 'to' field is None.
119
+ 2. Gnosis Safe deployments where the transaction's 'to' field is a specific address.
120
+
121
+ Args:
122
+ address: The addresses of the accounts that sent the transaction.
123
+ nonce: The nonce associated with the transaction creator.
124
+ block: The block number from which to retrieve the transactions.
125
+
126
+ Returns:
127
+ The transaction data if found, otherwise None.
128
+
129
+ Example:
130
+ >>> transaction = await get_transaction_by_nonce_and_block(address="0x1234567890abcdef1234567890abcdef12345678", nonce=5, block=1234567)
131
+ >>> if transaction:
132
+ ... print(transaction.hash)
133
+ """
134
+ for tx in await get_block_transactions(block):
135
+ if tx.sender == address and tx.nonce == nonce:
136
+ return tx
137
+
138
+ receipt_bytes: msgspec.Raw
139
+ # Special handler for contract creation transactions
140
+ if tx.to is None:
141
+ receipt_bytes = await get_transaction_receipt(tx.hash.hex())
142
+ receipt_0 = msgspec.json.decode(
143
+ receipt_bytes,
144
+ type=ReceiptContractAddress,
145
+ dec_hook=data.Address._decode_hook,
146
+ )
147
+ if receipt_0.contractAddress == address:
148
+ return tx
149
+ # Special handler for Gnosis Safe deployments
150
+ elif tx.to == "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2":
151
+ receipt_bytes = await get_transaction_receipt(tx.hash.hex())
152
+ receipt_1 = msgspec.json.decode(
153
+ receipt_bytes,
154
+ type=ReceiptLogs,
155
+ dec_hook=data._decode_hook,
156
+ )
157
+ events = decode_logs(receipt_1.logs)
158
+ if (
159
+ "SafeSetup" in events
160
+ and "ProxyCreation" in events
161
+ and any(event["proxy"] == address for event in events["ProxyCreation"])
162
+ ):
163
+ return tx
164
+ return None
165
+
166
+
167
+ class ReceiptContractAddress(msgspec.Struct):
168
+ """We only decode what we need and immediately discard the rest of the receipt."""
169
+
170
+ contractAddress: data.Address
171
+
172
+
173
+ class ReceiptLogs(msgspec.Struct):
174
+ logs: List[evmspec.Log]
175
+
176
+
177
+ get_transaction_count: Final = dank_mids.eth.get_transaction_count
178
+
179
+
180
+ @alru_cache(maxsize=None)
181
+ @eth_retry.auto_retry
182
+ @stuck_coro_debugger
183
+ @cache_to_disk
184
+ async def get_nonce_at_block(address: ChecksumAddress, block: Block) -> int:
185
+ """
186
+ Retrieves the nonce of an address at a specific block.
187
+
188
+ 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.
189
+
190
+ Args:
191
+ address: The address of the account.
192
+ block: The block number at which to retrieve the nonce.
193
+
194
+ Returns:
195
+ The nonce of the address at the given block.
196
+
197
+ Example:
198
+ >>> block = 12345678
199
+ >>> nonce = await get_nonce_at_block("0x1234567890abcdef1234567890abcdef12345678", block)
200
+ >>> print(f"The nonce at block {block} is {nonce}.")
201
+
202
+ """
203
+ return await _get_nonce_at_block(address, block)
204
+
205
+
206
+ _get_block_transactions: Final[Callable[[Block], Awaitable[Transactions]]] = alru_cache(
207
+ ttl=60 * 60
208
+ )(eth_retry.auto_retry(stuck_coro_debugger(dank_mids.eth.get_transactions)))
209
+ """
210
+ Retrieves all transactions from a specific block.
211
+
212
+ This function fetches the full transaction data for a block using async caching to optimize repeated requests.
213
+
214
+ The cache has a time-to-live (TTL) of 1 hour to avoid memory leaks for services or long-running scripts.
215
+
216
+ Args:
217
+ block: The block number from which to retrieve the transactions.
218
+
219
+ Returns:
220
+ A list of transaction data objects from the block.
221
+
222
+ Example:
223
+ >>> transactions = await _get_block_transactions(block=12345678)
224
+ >>> [print(tx.hash) for tx in transactions]
225
+ """
226
+
227
+
228
+ get_block_transactions: Final[SmartProcessingQueue[Block, [], Transactions]] = SmartProcessingQueue(
229
+ _get_block_transactions, 1_000
230
+ )
231
+ """
232
+ A smart processing queue that retrieves transactions from blocks with managable concurrency.
233
+
234
+ This queue processes the retrieval of block transactions using a limited number of workers to handle potentially overwhelming volume of transactions.
235
+ It wraps the _get_block_transactions function to provide efficient concurrent processing.
236
+
237
+ Example:
238
+ >>> transactions = await get_block_transactions.process(block=12345678)
239
+ >>> [print(tx['hash']) for tx in transactions]
240
+ """
@@ -0,0 +1,68 @@
1
+ from typing import Final
2
+
3
+ import dank_mids
4
+ import eth_retry
5
+ import msgspec
6
+ from a_sync import SmartProcessingQueue
7
+ from eth_typing import HexStr
8
+ from faster_async_lru import alru_cache
9
+ from y._decorators import stuck_coro_debugger
10
+
11
+
12
+ Raw: Final = msgspec.Raw
13
+ TxReceiptQueue = SmartProcessingQueue[HexStr, [], msgspec.Raw]
14
+
15
+
16
+ @eth_retry.auto_retry(min_sleep_time=1, max_sleep_time=3, max_retries=20, suppress_logs=1)
17
+ @alru_cache(maxsize=None, ttl=60 * 60)
18
+ @stuck_coro_debugger
19
+ async def _get_transaction_receipt(txhash: HexStr) -> msgspec.Raw:
20
+ """
21
+ Fetches the transaction receipt for a given transaction hash.
22
+
23
+ This function retrieves the transaction receipt from the Ethereum network
24
+ using the provided transaction hash. It utilizes caching to store results
25
+ for a specified time-to-live (TTL) to improve performance and reduce
26
+ network calls. The function is also decorated to automatically retry
27
+ in case of failures and to debug if the coroutine gets stuck.
28
+
29
+ Args:
30
+ txhash: The transaction hash for which to retrieve the receipt.
31
+
32
+ Returns:
33
+ msgspec.Raw: The raw transaction receipt data.
34
+
35
+ Examples:
36
+ >>> txhash = "0x1234567890abcdef..."
37
+ >>> receipt = await _get_transaction_receipt(txhash)
38
+ >>> print(receipt)
39
+
40
+ See Also:
41
+ - :func:`eth_retry.auto_retry`: For automatic retry logic.
42
+ - :func:`async_lru.alru_cache`: For caching the results.
43
+ - :func:`y._decorators.stuck_coro_debugger`: For debugging stuck coroutines.
44
+ """
45
+ return await __get_transaction_receipt(txhash, decode_to=Raw, decode_hook=None)
46
+
47
+
48
+ get_transaction_receipt: Final[TxReceiptQueue] = SmartProcessingQueue(
49
+ _get_transaction_receipt, 5000
50
+ )
51
+ """
52
+ A queue for processing transaction receipt requests.
53
+
54
+ This queue manages the processing of transaction receipt requests, allowing
55
+ up to 5000 concurrent requests. It uses the `_get_transaction_receipt` function
56
+ to fetch the receipts.
57
+
58
+ Examples:
59
+ >>> txhash = "0x1234567890abcdef..."
60
+ >>> receipt = await get_transaction_receipt(txhash)
61
+ >>> print(receipt)
62
+
63
+ See Also:
64
+ - :class:`a_sync.SmartProcessingQueue`: For managing asynchronous processing queues.
65
+ """
66
+
67
+
68
+ __get_transaction_receipt: Final = dank_mids.eth.get_transaction_receipt