eth-portfolio 0.5.8__cp310-cp310-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.
Potentially problematic release.
This version of eth-portfolio might be problematic. Click here for more details.
- eth_portfolio/__init__.py +24 -0
- eth_portfolio/_argspec.cp310-win_amd64.pyd +0 -0
- eth_portfolio/_argspec.py +43 -0
- eth_portfolio/_cache.py +119 -0
- eth_portfolio/_config.cp310-win_amd64.pyd +0 -0
- eth_portfolio/_config.py +4 -0
- eth_portfolio/_db/__init__.py +0 -0
- eth_portfolio/_db/decorators.py +147 -0
- eth_portfolio/_db/entities.py +311 -0
- eth_portfolio/_db/utils.py +619 -0
- eth_portfolio/_decimal.py +154 -0
- eth_portfolio/_decorators.py +84 -0
- eth_portfolio/_exceptions.py +65 -0
- eth_portfolio/_ledgers/__init__.py +0 -0
- eth_portfolio/_ledgers/address.py +917 -0
- eth_portfolio/_ledgers/portfolio.py +328 -0
- eth_portfolio/_loaders/__init__.py +33 -0
- eth_portfolio/_loaders/_nonce.cp310-win_amd64.pyd +0 -0
- eth_portfolio/_loaders/_nonce.py +193 -0
- eth_portfolio/_loaders/balances.cp310-win_amd64.pyd +0 -0
- eth_portfolio/_loaders/balances.py +95 -0
- eth_portfolio/_loaders/token_transfer.py +215 -0
- eth_portfolio/_loaders/transaction.py +240 -0
- eth_portfolio/_loaders/utils.cp310-win_amd64.pyd +0 -0
- eth_portfolio/_loaders/utils.py +67 -0
- eth_portfolio/_shitcoins.cp310-win_amd64.pyd +0 -0
- eth_portfolio/_shitcoins.py +342 -0
- eth_portfolio/_stableish.cp310-win_amd64.pyd +0 -0
- eth_portfolio/_stableish.py +42 -0
- eth_portfolio/_submodules.py +72 -0
- eth_portfolio/_utils.py +229 -0
- eth_portfolio/_ydb/__init__.py +0 -0
- eth_portfolio/_ydb/token_transfers.py +144 -0
- eth_portfolio/address.py +396 -0
- eth_portfolio/buckets.py +212 -0
- eth_portfolio/constants.cp310-win_amd64.pyd +0 -0
- eth_portfolio/constants.py +87 -0
- eth_portfolio/portfolio.py +669 -0
- eth_portfolio/protocols/__init__.py +64 -0
- eth_portfolio/protocols/_base.py +107 -0
- eth_portfolio/protocols/convex.py +17 -0
- eth_portfolio/protocols/dsr.py +50 -0
- eth_portfolio/protocols/lending/README.md +6 -0
- eth_portfolio/protocols/lending/__init__.py +50 -0
- eth_portfolio/protocols/lending/_base.py +56 -0
- eth_portfolio/protocols/lending/compound.py +186 -0
- eth_portfolio/protocols/lending/liquity.py +108 -0
- eth_portfolio/protocols/lending/maker.py +110 -0
- eth_portfolio/protocols/lending/unit.py +44 -0
- eth_portfolio/protocols/liquity.py +17 -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 +628 -0
- eth_portfolio/typing/__init__.py +1418 -0
- eth_portfolio/typing/balance/single.py +176 -0
- eth_portfolio-0.5.8.dist-info/METADATA +28 -0
- eth_portfolio-0.5.8.dist-info/RECORD +83 -0
- eth_portfolio-0.5.8.dist-info/WHEEL +5 -0
- eth_portfolio-0.5.8.dist-info/entry_points.txt +2 -0
- eth_portfolio-0.5.8.dist-info/top_level.txt +3 -0
- eth_portfolio__mypyc.cp310-win_amd64.pyd +0 -0
- eth_portfolio_scripts/__init__.py +17 -0
- eth_portfolio_scripts/_args.py +26 -0
- eth_portfolio_scripts/_logging.py +14 -0
- eth_portfolio_scripts/_portfolio.py +209 -0
- eth_portfolio_scripts/_utils.py +106 -0
- eth_portfolio_scripts/balances.cp310-win_amd64.pyd +0 -0
- eth_portfolio_scripts/balances.py +56 -0
- eth_portfolio_scripts/docker/.grafana/dashboards/Portfolio/Balances.json +1962 -0
- eth_portfolio_scripts/docker/.grafana/dashboards/dashboards.yaml +10 -0
- eth_portfolio_scripts/docker/.grafana/datasources/datasources.yml +11 -0
- eth_portfolio_scripts/docker/__init__.cp310-win_amd64.pyd +0 -0
- eth_portfolio_scripts/docker/__init__.py +16 -0
- eth_portfolio_scripts/docker/check.cp310-win_amd64.pyd +0 -0
- eth_portfolio_scripts/docker/check.py +66 -0
- eth_portfolio_scripts/docker/docker-compose.yaml +61 -0
- eth_portfolio_scripts/docker/docker_compose.cp310-win_amd64.pyd +0 -0
- eth_portfolio_scripts/docker/docker_compose.py +97 -0
- eth_portfolio_scripts/main.py +118 -0
- eth_portfolio_scripts/py.typed +1 -0
- eth_portfolio_scripts/victoria/__init__.py +72 -0
- eth_portfolio_scripts/victoria/types.py +38 -0
eth_portfolio/_utils.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sqlite3
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from collections.abc import AsyncGenerator, AsyncIterator
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from typing import TYPE_CHECKING, Final, Generic, TypeVar
|
|
8
|
+
|
|
9
|
+
import dank_mids
|
|
10
|
+
from a_sync import ASyncGenericBase, ASyncIterable, ASyncIterator, as_yielded
|
|
11
|
+
from a_sync.asyncio import sleep0 as yield_to_loop
|
|
12
|
+
from brownie import chain
|
|
13
|
+
from brownie.exceptions import ContractNotFound
|
|
14
|
+
from eth_typing import ChecksumAddress
|
|
15
|
+
from faster_async_lru import alru_cache
|
|
16
|
+
from faster_eth_abi.exceptions import InsufficientDataBytes
|
|
17
|
+
from pandas import DataFrame
|
|
18
|
+
from y import ERC20, Contract, Network
|
|
19
|
+
from y.constants import CHAINID, NETWORK_NAME
|
|
20
|
+
from y.datatypes import AddressOrContract, Block
|
|
21
|
+
from y.exceptions import (
|
|
22
|
+
CantFetchParam,
|
|
23
|
+
ContractNotVerified,
|
|
24
|
+
NodeNotSynced,
|
|
25
|
+
NonStandardERC20,
|
|
26
|
+
PriceError,
|
|
27
|
+
reraise_excs_with_extra_context,
|
|
28
|
+
yPriceMagicError,
|
|
29
|
+
)
|
|
30
|
+
from y.prices.magic import get_price
|
|
31
|
+
|
|
32
|
+
from eth_portfolio import _config, _decimal
|
|
33
|
+
from eth_portfolio.typing import _T
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from eth_portfolio.structs import LedgerEntry
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
logger: Final = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
NON_STANDARD_ERC721: Final[list[ChecksumAddress]] = {
|
|
42
|
+
Network.Mainnet: ["0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB"], # CryptoPunks
|
|
43
|
+
}.get(CHAINID, [])
|
|
44
|
+
|
|
45
|
+
SUPPRESS_ERROR_LOGS: Final[list[ChecksumAddress]] = {
|
|
46
|
+
# put tokens here when you don't expect them to price successfully and do not want to see the associated error logs
|
|
47
|
+
}.get(CHAINID, [])
|
|
48
|
+
"""Put tokens here when you don't expect them to price successfully and do not want to see the associated error logs."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def get_buffered_chain_height() -> int:
|
|
52
|
+
"""Returns an int equal to the current height of the chain minus `_config.REORG_BUFFER`."""
|
|
53
|
+
return await dank_mids.eth.get_block_number() - _config.REORG_BUFFER
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class PandableList(list[_T]):
|
|
57
|
+
@cached_property
|
|
58
|
+
def df(self) -> DataFrame:
|
|
59
|
+
return self._df()
|
|
60
|
+
|
|
61
|
+
def _df(self) -> DataFrame:
|
|
62
|
+
"""Override this method if you need to manipulate your dataframe before returning it."""
|
|
63
|
+
return DataFrame(self)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Decimal(_decimal.Decimal):
|
|
67
|
+
"""
|
|
68
|
+
I'm in the process of moving from floats to decimals, this will help be as I buidl.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
# TODO i forget why I had this lol, should I delete?
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def _describe_err(token: AddressOrContract, block: Block | None) -> str:
|
|
75
|
+
"""
|
|
76
|
+
Assembles a string used to provide as much useful information as possible in PriceError messages
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
symbol = await ERC20(token, asynchronous=True).symbol
|
|
80
|
+
except NonStandardERC20:
|
|
81
|
+
symbol = None
|
|
82
|
+
|
|
83
|
+
if block is None:
|
|
84
|
+
if symbol:
|
|
85
|
+
return f"{symbol} {token} on {NETWORK_NAME}"
|
|
86
|
+
|
|
87
|
+
return f"malformed token {token} on {NETWORK_NAME}"
|
|
88
|
+
|
|
89
|
+
if symbol:
|
|
90
|
+
return f"{symbol} {token} on {NETWORK_NAME} at {block}"
|
|
91
|
+
|
|
92
|
+
return f"malformed token {token} on {NETWORK_NAME} at {block}"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
_to_raise: Final = (
|
|
96
|
+
OSError,
|
|
97
|
+
FileNotFoundError,
|
|
98
|
+
NodeNotSynced,
|
|
99
|
+
NotImplementedError,
|
|
100
|
+
sqlite3.OperationalError,
|
|
101
|
+
InsufficientDataBytes,
|
|
102
|
+
UnboundLocalError,
|
|
103
|
+
RuntimeError,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def _get_price(token: AddressOrContract, block: int | None = None) -> _decimal.Decimal:
|
|
108
|
+
token = str(token)
|
|
109
|
+
with reraise_excs_with_extra_context(token, block):
|
|
110
|
+
try:
|
|
111
|
+
if await is_erc721(token):
|
|
112
|
+
return _decimal.Decimal(0)
|
|
113
|
+
maybe_float = await get_price(token, block, silent=True, sync=False)
|
|
114
|
+
dprice = _decimal.Decimal(maybe_float)
|
|
115
|
+
return round(dprice, 18)
|
|
116
|
+
except CantFetchParam as e:
|
|
117
|
+
logger.warning("CantFetchParam %s", e)
|
|
118
|
+
except yPriceMagicError as e:
|
|
119
|
+
# Raise these exceptions
|
|
120
|
+
if isinstance(e.exception, _to_raise) and not isinstance(e.exception, RecursionError):
|
|
121
|
+
raise e.exception
|
|
122
|
+
# The exceptions below are acceptable enough
|
|
123
|
+
elif isinstance(e.exception, NonStandardERC20):
|
|
124
|
+
# Can't get symbol for handling like other excs
|
|
125
|
+
logger.warning(f"NonStandardERC20 while fetching price for {token}")
|
|
126
|
+
elif isinstance(e.exception, PriceError):
|
|
127
|
+
if token not in SUPPRESS_ERROR_LOGS:
|
|
128
|
+
logger.warning(
|
|
129
|
+
f"PriceError while fetching price for {await _describe_err(token, block)}"
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
logger.warning(f"{e} while fetching price for {await _describe_err(token, block)}")
|
|
133
|
+
logger.warning(e, exc_info=True)
|
|
134
|
+
return _decimal.Decimal(0)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@alru_cache(maxsize=None)
|
|
138
|
+
async def is_erc721(token: ChecksumAddress) -> bool:
|
|
139
|
+
# This can probably be improved
|
|
140
|
+
try:
|
|
141
|
+
contract = await Contract.coroutine(token)
|
|
142
|
+
except (ContractNotFound, ContractNotVerified):
|
|
143
|
+
return False
|
|
144
|
+
attrs = "setApprovalForAll", "getApproved", "isApprovedForAll"
|
|
145
|
+
if all(hasattr(contract, attr) for attr in attrs):
|
|
146
|
+
return True
|
|
147
|
+
elif contract.address in NON_STANDARD_ERC721:
|
|
148
|
+
return True
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _unpack_indicies(indicies: Block | tuple[Block, Block]) -> tuple[Block, Block]:
|
|
153
|
+
"""Unpacks indicies and returns a tuple (start_block, end_block)."""
|
|
154
|
+
if isinstance(indicies, tuple):
|
|
155
|
+
start_block, end_block = indicies
|
|
156
|
+
else:
|
|
157
|
+
start_block = 0
|
|
158
|
+
end_block = indicies
|
|
159
|
+
return start_block, end_block
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class _YieldEvery:
|
|
163
|
+
__slots__ = ("_every", "_count")
|
|
164
|
+
|
|
165
|
+
def __init__(self, every: int) -> None:
|
|
166
|
+
self._every: Final = every
|
|
167
|
+
self._count = 0
|
|
168
|
+
|
|
169
|
+
async def tick(self) -> None:
|
|
170
|
+
self._count += 1
|
|
171
|
+
if self._count % self._every == 0:
|
|
172
|
+
await yield_to_loop()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class _AiterMixin(ASyncIterable[_T]):
|
|
176
|
+
def __aiter__(self) -> AsyncIterator[_T]:
|
|
177
|
+
return self[self._start_block : chain.height].__aiter__()
|
|
178
|
+
|
|
179
|
+
def __getitem__(self, slice: slice) -> ASyncIterator[_T]:
|
|
180
|
+
if slice.start is not None and not isinstance(slice.start, (int, datetime)):
|
|
181
|
+
raise TypeError(f"start must be int or datetime. you passed {slice.start}")
|
|
182
|
+
if slice.stop and not isinstance(slice.stop, (int, datetime)):
|
|
183
|
+
raise TypeError(f"start must be int or datetime. you passed {slice.start}")
|
|
184
|
+
if slice.step is not None:
|
|
185
|
+
raise ValueError("You cannot use a step here.")
|
|
186
|
+
return ASyncIterator(self._get_and_yield(slice.start or 0, slice.stop, True)) # type: ignore [truthy-function]
|
|
187
|
+
|
|
188
|
+
def yield_forever(self, mem_cache: bool = True) -> ASyncIterator[_T]:
|
|
189
|
+
return ASyncIterator(self._get_and_yield(self._start_block or 0, None, mem_cache))
|
|
190
|
+
|
|
191
|
+
@abstractmethod
|
|
192
|
+
async def _get_and_yield(
|
|
193
|
+
self, start_block: Block, end_block: Block, mem_cache: bool
|
|
194
|
+
) -> AsyncGenerator[_T, None]:
|
|
195
|
+
yield
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
@abstractmethod
|
|
199
|
+
def _start_block(self) -> int: ...
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
_LT = TypeVar("_LT")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class _LedgeredBase(ASyncGenericBase, _AiterMixin["LedgerEntry"], Generic[_LT]):
|
|
206
|
+
"""A mixin class for things with ledgers"""
|
|
207
|
+
|
|
208
|
+
transactions: _LT
|
|
209
|
+
internal_transfers: _LT
|
|
210
|
+
token_transfers: _LT
|
|
211
|
+
|
|
212
|
+
def __init__(self, start_block: Block) -> None:
|
|
213
|
+
self.start_block = start_block
|
|
214
|
+
super().__init__()
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def _start_block(self) -> Block:
|
|
218
|
+
# in the middle of refactoring this
|
|
219
|
+
return self.start_block
|
|
220
|
+
|
|
221
|
+
@cached_property
|
|
222
|
+
def _ledgers(self) -> tuple[_LT, _LT, _LT]:
|
|
223
|
+
"""A tuple with the 3 ledgers (transactions, internal transfers, token transfers)"""
|
|
224
|
+
return self.transactions, self.internal_transfers, self.token_transfers
|
|
225
|
+
|
|
226
|
+
def _get_and_yield(
|
|
227
|
+
self, start_block: Block, end_block: Block, mem_cache: bool
|
|
228
|
+
) -> AsyncGenerator["LedgerEntry", None]:
|
|
229
|
+
return as_yielded(*(ledger._get_and_yield(start_block, end_block, mem_cache) for ledger in self._ledgers)) # type: ignore [return-value,index]
|
|
File without changes
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from asyncio import Task, create_task
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from logging import DEBUG, getLogger
|
|
5
|
+
from typing import Any, Final, cast
|
|
6
|
+
|
|
7
|
+
import dank_mids
|
|
8
|
+
import evmspec
|
|
9
|
+
import y._db.log
|
|
10
|
+
from a_sync import ASyncIterable, ASyncIterator, as_yielded
|
|
11
|
+
from brownie import chain
|
|
12
|
+
from eth_typing import BlockNumber, ChecksumAddress
|
|
13
|
+
from faster_eth_abi import encode
|
|
14
|
+
from faster_eth_utils import encode_hex
|
|
15
|
+
from y.utils.events import ProcessedEvents
|
|
16
|
+
|
|
17
|
+
from eth_portfolio._loaders import load_token_transfer
|
|
18
|
+
from eth_portfolio._shitcoins import SHITCOINS
|
|
19
|
+
from eth_portfolio._utils import _YieldEvery
|
|
20
|
+
from eth_portfolio.constants import TRANSFER_SIGS
|
|
21
|
+
from eth_portfolio.structs import TokenTransfer
|
|
22
|
+
|
|
23
|
+
logger: Final = getLogger(__name__)
|
|
24
|
+
_logger_is_enabled_for: Final = logger.isEnabledFor
|
|
25
|
+
_logger_log: Final = logger._log
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def encode_address(address: Any) -> bytes:
|
|
29
|
+
return encode_hex(encode(["address"], [str(address)]))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _TokenTransfers(ProcessedEvents["Task[TokenTransfer]"]):
|
|
33
|
+
"""A helper mixin that contains all logic for fetching token transfers for a particular wallet address"""
|
|
34
|
+
|
|
35
|
+
__slots__ = "address", "_load_prices"
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
address: ChecksumAddress,
|
|
40
|
+
from_block: BlockNumber,
|
|
41
|
+
load_prices: bool = False,
|
|
42
|
+
) -> None:
|
|
43
|
+
self.address: Final = address
|
|
44
|
+
self._load_prices: Final = load_prices
|
|
45
|
+
super().__init__(topics=self._topics, from_block=from_block)
|
|
46
|
+
|
|
47
|
+
def __repr__(self) -> str:
|
|
48
|
+
return f"{self.__class__.__name__}(address={self.address})"
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def _topics(self) -> list: ...
|
|
53
|
+
|
|
54
|
+
@ASyncIterator.wrap # type: ignore [call-overload]
|
|
55
|
+
async def yield_thru_block(self, block: BlockNumber) -> AsyncIterator["Task[TokenTransfer]"]:
|
|
56
|
+
if not _logger_is_enabled_for(DEBUG):
|
|
57
|
+
async for task in self._objects_thru(block=block):
|
|
58
|
+
yield task
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
_logger_log(DEBUG, "%s yielding all objects thru block %s", (self, block))
|
|
62
|
+
async for task in self._objects_thru(block=block):
|
|
63
|
+
_logger_log(
|
|
64
|
+
DEBUG,
|
|
65
|
+
"yielding %s at block %s [thru: %s, lock: %s]",
|
|
66
|
+
(task, task.block, block, self._lock.value),
|
|
67
|
+
)
|
|
68
|
+
yield task
|
|
69
|
+
_logger_log(DEBUG, "%s yield thru %s complete", (self, block))
|
|
70
|
+
|
|
71
|
+
async def _extend(self, objs: list[evmspec.Log]) -> None:
|
|
72
|
+
shitcoins = SHITCOINS.get(chain.id, set())
|
|
73
|
+
append_loader_task = self._objects.append
|
|
74
|
+
yielder = _YieldEvery(100)
|
|
75
|
+
for log in objs:
|
|
76
|
+
if cast(ChecksumAddress, log.address) in shitcoins:
|
|
77
|
+
continue
|
|
78
|
+
# save i/o
|
|
79
|
+
array_encodable_log = y._db.log.Log(**log)
|
|
80
|
+
task = create_task(load_token_transfer(array_encodable_log, self._load_prices))
|
|
81
|
+
task.block = log.block # type: ignore [attr-defined]
|
|
82
|
+
append_loader_task(task)
|
|
83
|
+
# Make sure the event loop doesn't get blocked
|
|
84
|
+
await yielder.tick()
|
|
85
|
+
|
|
86
|
+
def _get_block_for_obj(self, task: "Task[TokenTransfer]") -> int:
|
|
87
|
+
return task.block # type: ignore [attr-defined]
|
|
88
|
+
|
|
89
|
+
def _process_event(self, task: "Task[TokenTransfer]") -> "Task[TokenTransfer]":
|
|
90
|
+
return task
|
|
91
|
+
|
|
92
|
+
def _done_callback(self, task: Task) -> None:
|
|
93
|
+
if e := task.exception():
|
|
94
|
+
self._exc = e
|
|
95
|
+
logger.exception(e)
|
|
96
|
+
raise e
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class InboundTokenTransfers(_TokenTransfers):
|
|
100
|
+
"""A container that fetches and iterates over all inbound token transfers for a particular wallet address"""
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def _topics(self) -> list:
|
|
104
|
+
return [TRANSFER_SIGS, None, encode_address(self.address)]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class OutboundTokenTransfers(_TokenTransfers):
|
|
108
|
+
"""A container that fetches and iterates over all outbound token transfers for a particular wallet address"""
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def _topics(self) -> list:
|
|
112
|
+
return [TRANSFER_SIGS, encode_address(self.address)]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class TokenTransfers(ASyncIterable[TokenTransfer]):
|
|
116
|
+
"""
|
|
117
|
+
A container that fetches and iterates over all token transfers for a particular wallet address.
|
|
118
|
+
NOTE: These do not come back in chronologcal order.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(
|
|
122
|
+
self,
|
|
123
|
+
address: ChecksumAddress,
|
|
124
|
+
from_block: BlockNumber,
|
|
125
|
+
load_prices: bool = False,
|
|
126
|
+
) -> None:
|
|
127
|
+
self.transfers_in: Final = InboundTokenTransfers(
|
|
128
|
+
address, from_block, load_prices=load_prices
|
|
129
|
+
)
|
|
130
|
+
self.transfers_out: Final = OutboundTokenTransfers(
|
|
131
|
+
address, from_block, load_prices=load_prices
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def __aiter__(self) -> AsyncIterator["Task[TokenTransfer]"]:
|
|
135
|
+
async for transfer in self.__yield_thru_block(await dank_mids.eth.block_number):
|
|
136
|
+
yield transfer
|
|
137
|
+
|
|
138
|
+
def yield_thru_block(self, block: BlockNumber) -> ASyncIterator["Task[TokenTransfer]"]:
|
|
139
|
+
return ASyncIterator(self.__yield_thru_block(block))
|
|
140
|
+
|
|
141
|
+
def __yield_thru_block(self, block: BlockNumber) -> AsyncIterator["Task[TokenTransfer]"]:
|
|
142
|
+
return as_yielded(
|
|
143
|
+
self.transfers_in.yield_thru_block(block), self.transfers_out.yield_thru_block(block)
|
|
144
|
+
)
|