eth-portfolio-temp 0.2.12__cp313-cp313-win32.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-temp might be problematic. Click here for more details.
- eth_portfolio/__init__.py +25 -0
- eth_portfolio/_argspec.cp313-win32.pyd +0 -0
- eth_portfolio/_argspec.py +42 -0
- eth_portfolio/_cache.py +121 -0
- eth_portfolio/_config.cp313-win32.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 +604 -0
- eth_portfolio/_decimal.py +156 -0
- eth_portfolio/_decorators.py +84 -0
- eth_portfolio/_exceptions.py +67 -0
- eth_portfolio/_ledgers/__init__.py +0 -0
- eth_portfolio/_ledgers/address.py +938 -0
- eth_portfolio/_ledgers/portfolio.py +327 -0
- eth_portfolio/_loaders/__init__.py +33 -0
- eth_portfolio/_loaders/_nonce.cp313-win32.pyd +0 -0
- eth_portfolio/_loaders/_nonce.py +196 -0
- eth_portfolio/_loaders/balances.cp313-win32.pyd +0 -0
- eth_portfolio/_loaders/balances.py +94 -0
- eth_portfolio/_loaders/token_transfer.py +217 -0
- eth_portfolio/_loaders/transaction.py +240 -0
- eth_portfolio/_loaders/utils.cp313-win32.pyd +0 -0
- eth_portfolio/_loaders/utils.py +68 -0
- eth_portfolio/_shitcoins.cp313-win32.pyd +0 -0
- eth_portfolio/_shitcoins.py +329 -0
- eth_portfolio/_stableish.cp313-win32.pyd +0 -0
- eth_portfolio/_stableish.py +42 -0
- eth_portfolio/_submodules.py +73 -0
- eth_portfolio/_utils.py +225 -0
- eth_portfolio/_ydb/__init__.py +0 -0
- eth_portfolio/_ydb/token_transfers.py +145 -0
- eth_portfolio/address.py +397 -0
- eth_portfolio/buckets.py +194 -0
- eth_portfolio/constants.cp313-win32.pyd +0 -0
- eth_portfolio/constants.py +82 -0
- eth_portfolio/portfolio.py +661 -0
- eth_portfolio/protocols/__init__.py +67 -0
- eth_portfolio/protocols/_base.py +108 -0
- eth_portfolio/protocols/convex.py +17 -0
- eth_portfolio/protocols/dsr.py +51 -0
- eth_portfolio/protocols/lending/README.md +6 -0
- eth_portfolio/protocols/lending/__init__.py +50 -0
- eth_portfolio/protocols/lending/_base.py +57 -0
- eth_portfolio/protocols/lending/compound.py +187 -0
- eth_portfolio/protocols/lending/liquity.py +110 -0
- eth_portfolio/protocols/lending/maker.py +104 -0
- eth_portfolio/protocols/lending/unit.py +46 -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/__init__.py +1447 -0
- eth_portfolio/typing/balance/single.py +176 -0
- eth_portfolio__mypyc.cp313-win32.pyd +0 -0
- eth_portfolio_scripts/__init__.py +20 -0
- eth_portfolio_scripts/_args.py +26 -0
- eth_portfolio_scripts/_logging.py +15 -0
- eth_portfolio_scripts/_portfolio.py +194 -0
- eth_portfolio_scripts/_utils.py +106 -0
- eth_portfolio_scripts/balances.cp313-win32.pyd +0 -0
- eth_portfolio_scripts/balances.py +52 -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__.cp313-win32.pyd +0 -0
- eth_portfolio_scripts/docker/__init__.py +16 -0
- eth_portfolio_scripts/docker/check.cp313-win32.pyd +0 -0
- eth_portfolio_scripts/docker/check.py +56 -0
- eth_portfolio_scripts/docker/docker-compose.yaml +61 -0
- eth_portfolio_scripts/docker/docker_compose.cp313-win32.pyd +0 -0
- eth_portfolio_scripts/docker/docker_compose.py +78 -0
- eth_portfolio_scripts/main.py +119 -0
- eth_portfolio_scripts/py.typed +1 -0
- eth_portfolio_scripts/victoria/__init__.py +73 -0
- eth_portfolio_scripts/victoria/types.py +38 -0
- eth_portfolio_temp-0.2.12.dist-info/METADATA +25 -0
- eth_portfolio_temp-0.2.12.dist-info/RECORD +83 -0
- eth_portfolio_temp-0.2.12.dist-info/WHEEL +5 -0
- eth_portfolio_temp-0.2.12.dist-info/entry_points.txt +2 -0
- eth_portfolio_temp-0.2.12.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the :class:`~eth_portfolio.Portfolio` and :class:`~eth_portfolio.PortfolioWallets` classes, which are used to manage and interact with a collection of :class:`~eth_portfolio.address.PortfolioAddress` objects.
|
|
3
|
+
It also includes the :class:`~eth_portfolio.PortfolioLedger` class for handling transactions, internal transfers, and token transfers associated with the portfolio.
|
|
4
|
+
The :class:`~eth_portfolio.Portfolio` class leverages the `a_sync` library to support both synchronous and asynchronous operations. This allows it to efficiently gather data, perform portfolio-related tasks, and handle network calls without blocking, thus improving the overall responsiveness and performance of portfolio operations.
|
|
5
|
+
|
|
6
|
+
This file is part of a larger system that includes modules for handling portfolio addresses, ledger entries, and other related tasks.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from functools import wraps
|
|
11
|
+
from typing import Any, AsyncIterator, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
|
12
|
+
|
|
13
|
+
import a_sync
|
|
14
|
+
from a_sync import igather
|
|
15
|
+
from a_sync.a_sync import ASyncFunction
|
|
16
|
+
from brownie import web3
|
|
17
|
+
from checksum_dict import ChecksumAddressDict
|
|
18
|
+
from pandas import DataFrame, concat # type: ignore
|
|
19
|
+
from web3 import Web3
|
|
20
|
+
from y.datatypes import Address, Block
|
|
21
|
+
|
|
22
|
+
from eth_portfolio import _argspec
|
|
23
|
+
from eth_portfolio._decorators import set_end_block_if_none
|
|
24
|
+
from eth_portfolio._ledgers.address import PandableLedgerEntryList
|
|
25
|
+
from eth_portfolio._ledgers.portfolio import (
|
|
26
|
+
PortfolioInternalTransfersLedger,
|
|
27
|
+
PortfolioLedgerBase,
|
|
28
|
+
PortfolioTokenTransfersLedger,
|
|
29
|
+
PortfolioTransactionsLedger,
|
|
30
|
+
)
|
|
31
|
+
from eth_portfolio._utils import _LedgeredBase
|
|
32
|
+
from eth_portfolio.address import PortfolioAddress
|
|
33
|
+
from eth_portfolio.constants import ADDRESSES
|
|
34
|
+
from eth_portfolio.structs import LedgerEntry
|
|
35
|
+
from eth_portfolio.typing import Addresses, PortfolioBalances
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PortfolioWallets(Iterable[PortfolioAddress], Dict[Address, PortfolioAddress]): # type: ignore [metaclass]
|
|
41
|
+
"""
|
|
42
|
+
A container that holds all :class:`~eth_portfolio.address.PortfolioAddress` objects for a specific :class:`~eth_portfolio.Portfolio`.
|
|
43
|
+
|
|
44
|
+
Works like a ``Dict[Address, PortfolioAddress]`` except when you iterate you get the values instead of the keys.
|
|
45
|
+
You should not initialize these. They are created automatically during :class:`~eth_portfolio.Portfolio` initialization.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
_wallets: A checksummed dictionary of :class:`~eth_portfolio.address.PortfolioAddress` objects.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
_wallets: ChecksumAddressDict[PortfolioAddress]
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
addresses: Iterable[Address],
|
|
56
|
+
start_block: Block,
|
|
57
|
+
load_prices: bool,
|
|
58
|
+
num_workers_transactions: int,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Initialize a PortfolioWallets instance.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
portfolio: The :class:`~eth_portfolio.Portfolio` instance to which this wallet belongs.
|
|
65
|
+
addresses: An iterable of addresses to be included in the portfolio.
|
|
66
|
+
"""
|
|
67
|
+
self._wallets: ChecksumAddressDict[PortfolioAddress] = ChecksumAddressDict()
|
|
68
|
+
"""
|
|
69
|
+
A checksummed dictionary of :class:`~eth_portfolio.address.PortfolioAddress` objects.
|
|
70
|
+
|
|
71
|
+
Type:
|
|
72
|
+
ChecksumAddressDict[PortfolioAddress]
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
for address in addresses:
|
|
76
|
+
self._wallets[address] = PortfolioAddress(
|
|
77
|
+
address,
|
|
78
|
+
start_block,
|
|
79
|
+
load_prices,
|
|
80
|
+
num_workers_transactions=num_workers_transactions,
|
|
81
|
+
asynchronous=portfolio.asynchronous,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def __repr__(self) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Return a string representation of the PortfolioWallets instance.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
String representation of the instance.
|
|
90
|
+
"""
|
|
91
|
+
return f"<{type(self).__name__} wallets={list(self._wallets.values())}>"
|
|
92
|
+
|
|
93
|
+
def __contains__(self, address: Union[Address, PortfolioAddress]) -> bool:
|
|
94
|
+
"""
|
|
95
|
+
Check if an address is in the portfolio wallets.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
address: The address to check.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
True if the address is in the wallets, False otherwise.
|
|
102
|
+
"""
|
|
103
|
+
return address in self._wallets
|
|
104
|
+
|
|
105
|
+
def __getitem__(self, address: Address) -> PortfolioAddress:
|
|
106
|
+
"""
|
|
107
|
+
Get the :class:`~eth_portfolio.address.PortfolioAddress` object for a given address.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
address: The address to look up.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
PortfolioAddress: The :class:`~eth_portfolio.address.PortfolioAddress` object.
|
|
114
|
+
"""
|
|
115
|
+
return self._wallets[address]
|
|
116
|
+
|
|
117
|
+
def __iter__(self) -> Iterator[PortfolioAddress]:
|
|
118
|
+
"""
|
|
119
|
+
Iterate over the :class:`~eth_portfolio.address.PortfolioAddress` objects in the wallets.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Iterator[PortfolioAddress]: An iterator over :class:`~eth_portfolio.address.PortfolioAddress` objects.
|
|
123
|
+
"""
|
|
124
|
+
yield from self._wallets.values()
|
|
125
|
+
|
|
126
|
+
def __len__(self) -> int:
|
|
127
|
+
"""
|
|
128
|
+
Get the number of :class:`~eth_portfolio.address.PortfolioAddress` objects in the wallets.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The number of :class:`~eth_portfolio.address.PortfolioAddress` objects.
|
|
132
|
+
"""
|
|
133
|
+
return len(self._wallets)
|
|
134
|
+
|
|
135
|
+
def __bool__(self) -> bool:
|
|
136
|
+
"""
|
|
137
|
+
Check if the wallets contain any addresses.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
True if there are addresses in the wallets, False otherwise.
|
|
141
|
+
"""
|
|
142
|
+
return bool(self._wallets)
|
|
143
|
+
|
|
144
|
+
def keys(self) -> Iterable[Address]:
|
|
145
|
+
"""
|
|
146
|
+
Get the keys (addresses) of the wallets.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Iterable[Address]: An iterable of addresses.
|
|
150
|
+
"""
|
|
151
|
+
return self._wallets.keys()
|
|
152
|
+
|
|
153
|
+
def values(self) -> Iterable[PortfolioAddress]:
|
|
154
|
+
"""
|
|
155
|
+
Get the values (:class:`~eth_portfolio.address.PortfolioAddress` objects) of the wallets.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Iterable[PortfolioAddress]: An iterable of :class:`~eth_portfolio.address.PortfolioAddress` objects.
|
|
159
|
+
"""
|
|
160
|
+
return self._wallets.values()
|
|
161
|
+
|
|
162
|
+
def items(self) -> Iterable[Tuple[Address, PortfolioAddress]]:
|
|
163
|
+
"""
|
|
164
|
+
Get the items (address, :class:`~eth_portfolio.address.PortfolioAddress` pairs) of the wallets.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Iterable[Tuple[Address, PortfolioAddress]]: An iterable of (address, :class:`~eth_portfolio.address.PortfolioAddress`) tuples.
|
|
168
|
+
"""
|
|
169
|
+
return self._wallets.items()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
_DEFAULT_LABEL = "your portfolio"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class Portfolio(a_sync.ASyncGenericBase):
|
|
176
|
+
"""
|
|
177
|
+
Used to export information about a group of :class:`~eth_portfolio.address.PortfolioAddress` objects.
|
|
178
|
+
|
|
179
|
+
- Has all attributes of a :class:`~eth_portfolio.address.PortfolioAddress`.
|
|
180
|
+
- All calls to `function(*args, **kwargs)` will return `{address: PortfolioAddress(Address).function(*args, **kwargs)}`
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
label: str = _DEFAULT_LABEL
|
|
184
|
+
"""
|
|
185
|
+
A label for the portfolio. Defaults to "your portfolio"
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
load_prices: bool = True
|
|
189
|
+
"""
|
|
190
|
+
Whether to load prices. Defaults to True.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
asynchronous: bool = False
|
|
194
|
+
"""
|
|
195
|
+
Whether to use asynchronous operations. Defaults to False.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
_start_block = 0
|
|
199
|
+
"""
|
|
200
|
+
The starting block number. Defaults to 0.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
def __init__(
|
|
204
|
+
self,
|
|
205
|
+
addresses: Addresses,
|
|
206
|
+
start_block: int = 0,
|
|
207
|
+
label: str = _DEFAULT_LABEL,
|
|
208
|
+
load_prices: bool = True,
|
|
209
|
+
num_workers_transactions: int = 1000,
|
|
210
|
+
asynchronous: bool = False,
|
|
211
|
+
) -> None:
|
|
212
|
+
"""
|
|
213
|
+
Initialize a Portfolio instance.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
addresses: The addresses to include in the portfolio.
|
|
217
|
+
Examples:
|
|
218
|
+
>>> portfolio = Portfolio(addresses=["0xAddress1", "0xAddress2"])
|
|
219
|
+
>>> print(portfolio)
|
|
220
|
+
"""
|
|
221
|
+
if not isinstance(start_block, int):
|
|
222
|
+
raise TypeError(f"`start_block` must be an integer, not {type(start_block)}")
|
|
223
|
+
if start_block < 0:
|
|
224
|
+
raise ValueError("`start_block` must be >= 0")
|
|
225
|
+
super().__init__()
|
|
226
|
+
|
|
227
|
+
if start_block:
|
|
228
|
+
self._start_block = start_block
|
|
229
|
+
|
|
230
|
+
assert isinstance(label, str), f"`label` must be a string, you passed {type(label)}"
|
|
231
|
+
if label != _DEFAULT_LABEL:
|
|
232
|
+
self.label = label
|
|
233
|
+
|
|
234
|
+
if load_prices is False:
|
|
235
|
+
self.load_prices = False
|
|
236
|
+
elif load_prices is not True:
|
|
237
|
+
raise TypeError(f"`load_prices` must be a boolean, you passed {type(load_prices)}")
|
|
238
|
+
|
|
239
|
+
if asynchronous is True:
|
|
240
|
+
self.asynchronous = True
|
|
241
|
+
elif asynchronous is not False:
|
|
242
|
+
raise TypeError(f"`asynchronous` must be a boolean, you passed {type(asynchronous)}")
|
|
243
|
+
|
|
244
|
+
if isinstance(addresses, str):
|
|
245
|
+
addresses = [addresses]
|
|
246
|
+
elif not isinstance(addresses, Iterable):
|
|
247
|
+
raise TypeError(f"`addresses` must be an iterable, not {type(addresses)}")
|
|
248
|
+
|
|
249
|
+
self.addresses = PortfolioWallets(
|
|
250
|
+
addresses, start_block, load_prices, num_workers_transactions
|
|
251
|
+
)
|
|
252
|
+
"""
|
|
253
|
+
A container for the :class:`~eth_portfolio.Portfolio`'s :class:`~eth_portfolio.address.PortfolioAddress` objects.
|
|
254
|
+
|
|
255
|
+
Type:
|
|
256
|
+
:class:`~eth_portfolio.PortfolioWallets`
|
|
257
|
+
|
|
258
|
+
Works like a ``Dict[Address, PortfolioAddress]`` except you get the values when you iterate instead of the keys.
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
self.ledger = PortfolioLedger(self)
|
|
262
|
+
"""
|
|
263
|
+
A container for all of your fun taxable events.
|
|
264
|
+
|
|
265
|
+
Type:
|
|
266
|
+
:class:`~eth_portfolio.PortfolioLedger`
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
self.w3: Web3 = web3
|
|
270
|
+
"""
|
|
271
|
+
The `Web3` object which will be used to call your rpc for all read operations.
|
|
272
|
+
|
|
273
|
+
Type:
|
|
274
|
+
:class:`~web3.Web3`
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
def __repr__(self) -> str:
|
|
278
|
+
# NOTE: should we show addresses? for now we won't
|
|
279
|
+
# string = f"{type(self).__name__}(addresses={list(map(str, self.addresses))}"
|
|
280
|
+
string = f"{type(self).__name__}(label={self.label}"
|
|
281
|
+
if self._start_block:
|
|
282
|
+
string += f", start_block={self._start_block}"
|
|
283
|
+
return string + f", load_prices={self.load_prices}, asynchronous={self.asynchronous})"
|
|
284
|
+
|
|
285
|
+
def __repr__(self) -> str:
|
|
286
|
+
# NOTE: should we show addresses? for now we won't
|
|
287
|
+
# string = f"{type(self).__name__}(addresses={list(map(str, self.addresses))}"
|
|
288
|
+
string = f"{type(self).__name__}(label={self.label}"
|
|
289
|
+
if self._start_block:
|
|
290
|
+
string += f", start_block={self._start_block}"
|
|
291
|
+
return string + f", load_prices={self.load_prices}, asynchronous={self.asynchronous})"
|
|
292
|
+
|
|
293
|
+
def __getitem__(self, key: Address) -> PortfolioAddress:
|
|
294
|
+
"""
|
|
295
|
+
Get a :class:`~eth_portfolio.address.PortfolioAddress` by its key.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
key: The address key.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
PortfolioAddress: The :class:`~eth_portfolio.address.PortfolioAddress` object.
|
|
302
|
+
|
|
303
|
+
Example:
|
|
304
|
+
>>> portfolio = Portfolio(addresses=["0xAddress1", "0xAddress2"])
|
|
305
|
+
>>> address = portfolio["0xAddress1"]
|
|
306
|
+
>>> print(address)
|
|
307
|
+
"""
|
|
308
|
+
return self.addresses[key]
|
|
309
|
+
|
|
310
|
+
def __iter__(self) -> Iterator[PortfolioAddress]:
|
|
311
|
+
"""
|
|
312
|
+
Iterate over the :class:`~eth_portfolio.address.PortfolioAddress` objects.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Iterator[PortfolioAddress]: An iterator over :class:`~eth_portfolio.address.PortfolioAddress` objects.
|
|
316
|
+
|
|
317
|
+
Example:
|
|
318
|
+
>>> portfolio = Portfolio(addresses=["0xAddress1", "0xAddress2"])
|
|
319
|
+
>>> for address in portfolio:
|
|
320
|
+
... print(address)
|
|
321
|
+
"""
|
|
322
|
+
yield from self.addresses
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def transactions(self) -> PortfolioTransactionsLedger:
|
|
326
|
+
"""
|
|
327
|
+
A container for all transactions to or from any of your :class:`~eth_portfolio.address.PortfolioAddress`.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
PortfolioTransactionsLedger: The :class:`~eth_portfolio._ledgers.portfolio.PortfolioTransactionsLedger` object.
|
|
331
|
+
|
|
332
|
+
Example:
|
|
333
|
+
>>> portfolio = Portfolio(addresses=["0xAddress1", "0xAddress2"])
|
|
334
|
+
>>> transactions = portfolio.transactions
|
|
335
|
+
>>> print(transactions)
|
|
336
|
+
"""
|
|
337
|
+
return self.ledger.transactions
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def internal_transfers(self) -> PortfolioInternalTransfersLedger:
|
|
341
|
+
"""
|
|
342
|
+
A container for all internal transfers to or from any of your :class:`~eth_portfolio.address.PortfolioAddress`.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
PortfolioInternalTransfersLedger: The :class:`~eth_portfolio._ledgers.portfolio.PortfolioInternalTransfersLedger` object.
|
|
346
|
+
|
|
347
|
+
Example:
|
|
348
|
+
>>> portfolio = Portfolio(addresses=["0xAddress1", "0xAddress2"])
|
|
349
|
+
>>> internal_transfers = portfolio.internal_transfers
|
|
350
|
+
>>> print(internal_transfers)
|
|
351
|
+
"""
|
|
352
|
+
return self.ledger.internal_transfers
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def token_transfers(self) -> PortfolioTokenTransfersLedger:
|
|
356
|
+
"""
|
|
357
|
+
A container for all token transfers to or from any of your :class:`~eth_portfolio.address.PortfolioAddress`.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
PortfolioTokenTransfersLedger: The :class:`~eth_portfolio._ledgers.portfolio.PortfolioTokenTransfersLedger` object.
|
|
361
|
+
|
|
362
|
+
Example:
|
|
363
|
+
>>> portfolio = Portfolio(addresses=["0xAddress1", "0xAddress2"])
|
|
364
|
+
>>> token_transfers = portfolio.token_transfers
|
|
365
|
+
>>> print(token_transfers)
|
|
366
|
+
"""
|
|
367
|
+
return self.ledger.token_transfers
|
|
368
|
+
|
|
369
|
+
async def describe(self, block: int) -> PortfolioBalances:
|
|
370
|
+
"""
|
|
371
|
+
Returns a full snapshot of your portfolio at a given block.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
block: The block number.
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
PortfolioBalances: A snapshot of the portfolio balances.
|
|
378
|
+
|
|
379
|
+
Example:
|
|
380
|
+
>>> portfolio = Portfolio(addresses=["0xAddress1", "0xAddress2"])
|
|
381
|
+
>>> balances = await portfolio.describe(block=1200000)
|
|
382
|
+
>>> print(balances)
|
|
383
|
+
"""
|
|
384
|
+
assert block
|
|
385
|
+
return PortfolioBalances(
|
|
386
|
+
await a_sync.gather(
|
|
387
|
+
{address.address: address.describe(block, sync=False) for address in self}
|
|
388
|
+
),
|
|
389
|
+
block=block,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
async def sent(
|
|
393
|
+
self,
|
|
394
|
+
start_block: Optional[Block] = None,
|
|
395
|
+
end_block: Optional[Block] = None,
|
|
396
|
+
*,
|
|
397
|
+
mem_cache: bool = True,
|
|
398
|
+
) -> AsyncIterator[LedgerEntry]:
|
|
399
|
+
async for obj in self.ledger.sent(start_block, end_block, mem_cache=mem_cache):
|
|
400
|
+
yield obj
|
|
401
|
+
|
|
402
|
+
async def received(
|
|
403
|
+
self,
|
|
404
|
+
start_block: Optional[Block] = None,
|
|
405
|
+
end_block: Optional[Block] = None,
|
|
406
|
+
*,
|
|
407
|
+
mem_cache: bool = True,
|
|
408
|
+
) -> AsyncIterator[LedgerEntry]:
|
|
409
|
+
async for obj in self.ledger.received(start_block, end_block, mem_cache=mem_cache):
|
|
410
|
+
yield obj
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
async_functions = {
|
|
414
|
+
name: obj for name, obj in PortfolioAddress.__dict__.items() if isinstance(obj, ASyncFunction)
|
|
415
|
+
}
|
|
416
|
+
for func_name, func in async_functions.items():
|
|
417
|
+
if not callable(getattr(PortfolioAddress, func_name)):
|
|
418
|
+
raise RuntimeError(
|
|
419
|
+
f"A PortfolioAddress object should not have a non-callable attribute suffixed with '_async'"
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
@a_sync.a_sync(default=func.default)
|
|
423
|
+
@wraps(func)
|
|
424
|
+
async def imported_func(self: Portfolio, *args: Any, **kwargs: Any) -> _argspec.get_return_type(getattr(PortfolioAddress, func_name)): # type: ignore
|
|
425
|
+
"""
|
|
426
|
+
Import an asynchronous function from :class:`~eth_portfolio.address.PortfolioAddress` to :class:`~eth_portfolio.Portfolio`.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
self: The :class:`~eth_portfolio.Portfolio` instance.
|
|
430
|
+
args: Positional arguments for the function.
|
|
431
|
+
kwargs: Keyword arguments for the function.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
Any: The return type of the function.
|
|
435
|
+
|
|
436
|
+
Example:
|
|
437
|
+
>>> portfolio = Portfolio(addresses=["0xAddress1", "0xAddress2"])
|
|
438
|
+
>>> result = await portfolio.some_async_function(*args, **kwargs)
|
|
439
|
+
>>> print(result)
|
|
440
|
+
"""
|
|
441
|
+
return await a_sync.gather(
|
|
442
|
+
{address: func(address, *args, **kwargs, sync=False) for address in self}
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
setattr(Portfolio, func_name, imported_func)
|
|
446
|
+
logger.debug("Ported %s from PortfolioAddress to Portfolio", func_name)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _get_missing_cols_from_KeyError(e: KeyError) -> List[str]:
|
|
450
|
+
"""
|
|
451
|
+
Extract missing column names from a KeyError.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
e: The KeyError exception.
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
A list of missing column names.
|
|
458
|
+
"""
|
|
459
|
+
split = str(e).split("'")
|
|
460
|
+
return [split[i * 2 + 1] for i in range(len(split) // 2)]
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class PortfolioLedger(_LedgeredBase[PortfolioLedgerBase]):
|
|
464
|
+
"""
|
|
465
|
+
A container for all transactions, internal transfers, and token transfers to or from any of the wallets in your :class:`~eth_portfolio.Portfolio`.
|
|
466
|
+
"""
|
|
467
|
+
|
|
468
|
+
def __init__(self, portfolio: "Portfolio") -> None:
|
|
469
|
+
"""
|
|
470
|
+
Initialize a PortfolioLedger instance.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
portfolio: The :class:`~eth_portfolio.Portfolio` instance to which this ledger belongs.
|
|
474
|
+
"""
|
|
475
|
+
super().__init__(portfolio._start_block)
|
|
476
|
+
self.portfolio = portfolio
|
|
477
|
+
"""
|
|
478
|
+
The :class:`~eth_portfolio.Portfolio` containing the wallets this ledger will pertain to.
|
|
479
|
+
"""
|
|
480
|
+
self.transactions = PortfolioTransactionsLedger(portfolio)
|
|
481
|
+
"""
|
|
482
|
+
A container for all transactions to or from any of your :class:`~eth_portfolio.address.PortfolioAddress`.
|
|
483
|
+
"""
|
|
484
|
+
self.internal_transfers = PortfolioInternalTransfersLedger(portfolio)
|
|
485
|
+
"""
|
|
486
|
+
A container for all internal transfers to or from any of your :class:`~eth_portfolio.address.PortfolioAddress`.
|
|
487
|
+
"""
|
|
488
|
+
self.token_transfers = PortfolioTokenTransfersLedger(portfolio)
|
|
489
|
+
"""
|
|
490
|
+
A container for all token transfers to or from any of your :class:`~eth_portfolio.address.PortfolioAddress`.
|
|
491
|
+
"""
|
|
492
|
+
self.asynchronous = portfolio.asynchronous
|
|
493
|
+
"""
|
|
494
|
+
True if default mode is async, False if sync.
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
async def all_entries(
|
|
498
|
+
self, start_block: Block, end_block: Block
|
|
499
|
+
) -> Dict[PortfolioAddress, Dict[str, PandableLedgerEntryList]]:
|
|
500
|
+
"""
|
|
501
|
+
Returns a mapping containing all transactions, internal transfers, and token transfers to or from each wallet in your portfolio.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
start_block: The starting block number.
|
|
505
|
+
end_block: The ending block number.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
Dict[PortfolioAddress, Dict[str, PandableLedgerEntryList]]: A dictionary mapping :class:`~eth_portfolio.address.PortfolioAddress` to their ledger entries.
|
|
509
|
+
|
|
510
|
+
Example:
|
|
511
|
+
>>> portfolio = Portfolio(addresses=["0xAddress1", "0xAddress2"])
|
|
512
|
+
>>> entries = await portfolio.ledger.all_entries(start_block=1000000, end_block=1100000)
|
|
513
|
+
>>> print(entries)
|
|
514
|
+
"""
|
|
515
|
+
return await a_sync.gather(
|
|
516
|
+
{address: address.all(start_block, end_block, sync=False) for address in self.portfolio}
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
@set_end_block_if_none
|
|
520
|
+
async def df(self, start_block: Block, end_block: Block, full: bool = False) -> DataFrame:
|
|
521
|
+
"""
|
|
522
|
+
Returns a DataFrame containing all transactions, internal transfers, and token transfers to or from any wallet in your portfolio.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
start_block: The starting block number.
|
|
526
|
+
end_block: The ending block number.
|
|
527
|
+
full (optional): Whether to include all columns or a subset. Defaults to False.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
DataFrame: A DataFrame with the ledger entries.
|
|
531
|
+
|
|
532
|
+
Example:
|
|
533
|
+
>>> portfolio = Portfolio(addresses=["0xAddress1", "0xAddress2"])
|
|
534
|
+
>>> df = await portfolio.ledger.df(start_block=1000000, end_block=1100000)
|
|
535
|
+
>>> print(df)
|
|
536
|
+
"""
|
|
537
|
+
df = concat(
|
|
538
|
+
await igather(ledger.df(start_block, end_block, sync=False) for ledger in self._ledgers)
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
# Reorder columns
|
|
542
|
+
while True:
|
|
543
|
+
try:
|
|
544
|
+
if full:
|
|
545
|
+
df = df[
|
|
546
|
+
[
|
|
547
|
+
"chainId",
|
|
548
|
+
"blockNumber",
|
|
549
|
+
"blockHash",
|
|
550
|
+
"transactionIndex",
|
|
551
|
+
"hash",
|
|
552
|
+
"log_index",
|
|
553
|
+
"nonce",
|
|
554
|
+
"from",
|
|
555
|
+
"to",
|
|
556
|
+
"token",
|
|
557
|
+
"token_address",
|
|
558
|
+
"value",
|
|
559
|
+
"price",
|
|
560
|
+
"value_usd",
|
|
561
|
+
"gas",
|
|
562
|
+
"gasPrice",
|
|
563
|
+
"gasUsed",
|
|
564
|
+
"maxFeePerGas",
|
|
565
|
+
"maxPriorityFeePerGas",
|
|
566
|
+
"type",
|
|
567
|
+
"callType",
|
|
568
|
+
"traceAddress",
|
|
569
|
+
"subtraces",
|
|
570
|
+
"output",
|
|
571
|
+
"error",
|
|
572
|
+
"result",
|
|
573
|
+
"address",
|
|
574
|
+
"code",
|
|
575
|
+
"init",
|
|
576
|
+
"r",
|
|
577
|
+
"s",
|
|
578
|
+
"v",
|
|
579
|
+
"input",
|
|
580
|
+
]
|
|
581
|
+
]
|
|
582
|
+
else:
|
|
583
|
+
df = df[
|
|
584
|
+
[
|
|
585
|
+
"chainId",
|
|
586
|
+
"blockNumber",
|
|
587
|
+
"hash",
|
|
588
|
+
"from",
|
|
589
|
+
"to",
|
|
590
|
+
"token",
|
|
591
|
+
"token_address",
|
|
592
|
+
"value",
|
|
593
|
+
"price",
|
|
594
|
+
"value_usd",
|
|
595
|
+
"gas",
|
|
596
|
+
"gasPrice",
|
|
597
|
+
"gasUsed",
|
|
598
|
+
"maxFeePerGas",
|
|
599
|
+
"maxPriorityFeePerGas",
|
|
600
|
+
"type",
|
|
601
|
+
"callType",
|
|
602
|
+
"traceAddress",
|
|
603
|
+
"subtraces",
|
|
604
|
+
"output",
|
|
605
|
+
"error",
|
|
606
|
+
"result",
|
|
607
|
+
"address",
|
|
608
|
+
]
|
|
609
|
+
]
|
|
610
|
+
df = df[df["value"] != 0]
|
|
611
|
+
break
|
|
612
|
+
except KeyError as e:
|
|
613
|
+
for column in _get_missing_cols_from_KeyError(e):
|
|
614
|
+
df[column] = None
|
|
615
|
+
|
|
616
|
+
sort_cols = (
|
|
617
|
+
["blockNumber", "transactionIndex", "log_index"] if full else ["blockNumber", "hash"]
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
try:
|
|
621
|
+
return df.sort_values(sort_cols).reset_index(drop=True)
|
|
622
|
+
except KeyError:
|
|
623
|
+
logger.error(df)
|
|
624
|
+
logger.error(df.columns)
|
|
625
|
+
raise
|
|
626
|
+
|
|
627
|
+
async def sent(
|
|
628
|
+
self,
|
|
629
|
+
start_block: Optional[Block] = None,
|
|
630
|
+
end_block: Optional[Block] = None,
|
|
631
|
+
*,
|
|
632
|
+
mem_cache: bool = True,
|
|
633
|
+
) -> AsyncIterator[LedgerEntry]:
|
|
634
|
+
portfolio_addresses = set(self.portfolio.addresses.keys())
|
|
635
|
+
async for obj in self._get_and_yield(start_block or 0, end_block, mem_cache):
|
|
636
|
+
if (
|
|
637
|
+
obj.value
|
|
638
|
+
and obj.from_address in portfolio_addresses
|
|
639
|
+
and obj.to_address not in portfolio_addresses
|
|
640
|
+
):
|
|
641
|
+
yield obj
|
|
642
|
+
|
|
643
|
+
async def received(
|
|
644
|
+
self,
|
|
645
|
+
start_block: Optional[Block] = None,
|
|
646
|
+
end_block: Optional[Block] = None,
|
|
647
|
+
*,
|
|
648
|
+
mem_cache: bool = True,
|
|
649
|
+
) -> AsyncIterator[LedgerEntry]:
|
|
650
|
+
portfolio_addresses = set(self.portfolio.addresses.keys())
|
|
651
|
+
async for obj in self._get_and_yield(start_block or 0, end_block, mem_cache):
|
|
652
|
+
if (
|
|
653
|
+
obj.value
|
|
654
|
+
and obj.to_address in portfolio_addresses
|
|
655
|
+
and obj.from_address not in portfolio_addresses
|
|
656
|
+
):
|
|
657
|
+
yield obj
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
# Use this var for a convenient way to set up your portfolio using env vars.
|
|
661
|
+
portfolio = Portfolio(ADDRESSES)
|