dao-treasury 0.0.29__cp311-cp311-win32.whl → 0.0.31__cp311-cp311-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.
- 17ebe61b88bd37338d0a__mypyc.cp311-win32.pyd +0 -0
- dao_treasury/.grafana/provisioning/dashboards/dashboards.yaml +8 -0
- dao_treasury/.grafana/provisioning/dashboards/streams/LlamaPay.json +116 -0
- dao_treasury/.grafana/provisioning/dashboards/treasury/Cashflow.json +22 -22
- dao_treasury/_docker.cp311-win32.pyd +0 -0
- dao_treasury/_docker.py +23 -18
- dao_treasury/_nicknames.cp311-win32.pyd +0 -0
- dao_treasury/_wallet.cp311-win32.pyd +0 -0
- dao_treasury/constants.cp311-win32.pyd +0 -0
- dao_treasury/db.py +172 -16
- dao_treasury/docker-compose.yaml +1 -0
- dao_treasury/main.py +27 -3
- dao_treasury/sorting/__init__.cp311-win32.pyd +0 -0
- dao_treasury/sorting/__init__.py +1 -0
- dao_treasury/sorting/_matchers.cp311-win32.pyd +0 -0
- dao_treasury/sorting/_rules.cp311-win32.pyd +0 -0
- dao_treasury/sorting/factory.cp311-win32.pyd +0 -0
- dao_treasury/sorting/rule.cp311-win32.pyd +0 -0
- dao_treasury/sorting/rule.py +5 -0
- dao_treasury/sorting/rules/__init__.cp311-win32.pyd +0 -0
- dao_treasury/sorting/rules/__init__.py +1 -0
- dao_treasury/sorting/rules/ignore/__init__.cp311-win32.pyd +0 -0
- dao_treasury/sorting/rules/ignore/__init__.py +1 -0
- dao_treasury/sorting/rules/ignore/llamapay.cp311-win32.pyd +0 -0
- dao_treasury/sorting/rules/ignore/llamapay.py +20 -0
- dao_treasury/streams/__init__.cp311-win32.pyd +0 -0
- dao_treasury/streams/__init__.py +0 -0
- dao_treasury/streams/llamapay.cp311-win32.pyd +0 -0
- dao_treasury/streams/llamapay.py +363 -0
- dao_treasury/treasury.py +25 -4
- dao_treasury/types.cp311-win32.pyd +0 -0
- {dao_treasury-0.0.29.dist-info → dao_treasury-0.0.31.dist-info}/METADATA +37 -5
- dao_treasury-0.0.31.dist-info/RECORD +49 -0
- dao_treasury-0.0.31.dist-info/top_level.txt +2 -0
- 8d7637a0eb7617042369__mypyc.cp311-win32.pyd +0 -0
- dao_treasury-0.0.29.dist-info/RECORD +0 -37
- dao_treasury-0.0.29.dist-info/top_level.txt +0 -2
- {dao_treasury-0.0.29.dist-info → dao_treasury-0.0.31.dist-info}/WHEEL +0 -0
@@ -0,0 +1,363 @@
|
|
1
|
+
import asyncio
|
2
|
+
from datetime import datetime, timedelta, timezone
|
3
|
+
from decimal import Decimal
|
4
|
+
from logging import getLogger
|
5
|
+
from typing import (
|
6
|
+
Awaitable,
|
7
|
+
Callable,
|
8
|
+
Dict,
|
9
|
+
Final,
|
10
|
+
Iterator,
|
11
|
+
List,
|
12
|
+
Optional,
|
13
|
+
Set,
|
14
|
+
final,
|
15
|
+
)
|
16
|
+
|
17
|
+
import dank_mids
|
18
|
+
import pony.orm
|
19
|
+
from a_sync import AsyncThreadPoolExecutor, igather
|
20
|
+
from brownie.network.event import _EventItem
|
21
|
+
from eth_typing import BlockNumber, ChecksumAddress, HexAddress, HexStr
|
22
|
+
from tqdm.asyncio import tqdm_asyncio
|
23
|
+
|
24
|
+
from y import Contract, Network, get_block_at_timestamp, get_price
|
25
|
+
from y.time import UnixTimestamp
|
26
|
+
from y.utils.events import decode_logs, get_logs_asap
|
27
|
+
|
28
|
+
from dao_treasury import constants
|
29
|
+
from dao_treasury.db import (
|
30
|
+
Stream,
|
31
|
+
StreamedFunds,
|
32
|
+
Address,
|
33
|
+
Token,
|
34
|
+
must_sort_outbound_txgroup_dbid,
|
35
|
+
)
|
36
|
+
from dao_treasury._wallet import TreasuryWallet
|
37
|
+
|
38
|
+
|
39
|
+
logger: Final = getLogger(__name__)
|
40
|
+
|
41
|
+
_UTC: Final = timezone.utc
|
42
|
+
|
43
|
+
_ONE_DAY: Final = 60 * 60 * 24
|
44
|
+
|
45
|
+
_STREAMS_THREAD: Final = AsyncThreadPoolExecutor(1)
|
46
|
+
|
47
|
+
create_task: Final = asyncio.create_task
|
48
|
+
|
49
|
+
ObjectNotFound: Final = pony.orm.ObjectNotFound
|
50
|
+
commit: Final = pony.orm.commit
|
51
|
+
db_session: Final = pony.orm.db_session
|
52
|
+
|
53
|
+
|
54
|
+
networks: Final = [Network.Mainnet]
|
55
|
+
|
56
|
+
factories: List[HexAddress] = []
|
57
|
+
|
58
|
+
if dai_stream_factory := {
|
59
|
+
Network.Mainnet: "0x60c7B0c5B3a4Dc8C690b074727a17fF7aA287Ff2",
|
60
|
+
}.get(constants.CHAINID):
|
61
|
+
factories.append(dai_stream_factory)
|
62
|
+
|
63
|
+
if yfi_stream_factory := {
|
64
|
+
Network.Mainnet: "0xf3764eC89B1ad20A31ed633b1466363FAc1741c4",
|
65
|
+
}.get(constants.CHAINID):
|
66
|
+
factories.append(yfi_stream_factory)
|
67
|
+
|
68
|
+
|
69
|
+
def _generate_dates(
|
70
|
+
start: datetime, end: datetime, stop_at_today: bool = True
|
71
|
+
) -> Iterator[datetime]:
|
72
|
+
current = start
|
73
|
+
while current < end:
|
74
|
+
yield current
|
75
|
+
current += timedelta(days=1)
|
76
|
+
if stop_at_today and current.date() > datetime.now(_UTC).date():
|
77
|
+
break
|
78
|
+
|
79
|
+
|
80
|
+
_StreamToStart = Callable[[HexStr, Optional[BlockNumber]], Awaitable[int]]
|
81
|
+
|
82
|
+
_streamToStart_cache: Final[Dict[HexStr, _StreamToStart]] = {}
|
83
|
+
|
84
|
+
|
85
|
+
def _get_streamToStart(stream_id: HexStr) -> _StreamToStart:
|
86
|
+
if streamToStart := _streamToStart_cache.get(stream_id):
|
87
|
+
return streamToStart
|
88
|
+
with db_session:
|
89
|
+
contract: Contract = Stream[stream_id].contract.contract # type: ignore [misc]
|
90
|
+
streamToStart = contract.streamToStart.coroutine
|
91
|
+
_streamToStart_cache[stream_id] = streamToStart
|
92
|
+
return streamToStart
|
93
|
+
|
94
|
+
|
95
|
+
async def _get_start_timestamp(
|
96
|
+
stream_id: HexStr, block: Optional[BlockNumber] = None
|
97
|
+
) -> int:
|
98
|
+
streamToStart = _streamToStart_cache.get(stream_id)
|
99
|
+
if streamToStart is None:
|
100
|
+
streamToStart = await _STREAMS_THREAD.run(_get_streamToStart, stream_id)
|
101
|
+
# try:
|
102
|
+
return int(await streamToStart(f"0x{stream_id}", block_identifier=block)) # type: ignore [call-arg]
|
103
|
+
# except Exception:
|
104
|
+
# return 0
|
105
|
+
|
106
|
+
|
107
|
+
def _pause_stream(stream_id: HexStr) -> None:
|
108
|
+
with db_session:
|
109
|
+
Stream[stream_id].pause() # type: ignore [misc]
|
110
|
+
|
111
|
+
|
112
|
+
def _stop_stream(stream_id: str, block: BlockNumber) -> None:
|
113
|
+
with db_session:
|
114
|
+
Stream[stream_id].stop_stream(block) # type: ignore [misc]
|
115
|
+
|
116
|
+
|
117
|
+
_block_timestamps: Final[Dict[BlockNumber, UnixTimestamp]] = {}
|
118
|
+
|
119
|
+
|
120
|
+
async def _get_block_timestamp(block: BlockNumber) -> UnixTimestamp:
|
121
|
+
if timestamp := _block_timestamps.get(block):
|
122
|
+
return timestamp
|
123
|
+
timestamp = await dank_mids.eth.get_block_timestamp(block)
|
124
|
+
_block_timestamps[block] = timestamp
|
125
|
+
return timestamp
|
126
|
+
|
127
|
+
|
128
|
+
"""
|
129
|
+
class _StreamProcessor(ABC):
|
130
|
+
@abstractmethod
|
131
|
+
async def _load_streams(self) -> None:
|
132
|
+
...
|
133
|
+
"""
|
134
|
+
|
135
|
+
|
136
|
+
@final
|
137
|
+
class LlamaPayProcessor:
|
138
|
+
"""
|
139
|
+
Generalized async processor for DAO stream contracts.
|
140
|
+
Args are passed in at construction time.
|
141
|
+
Supports time-bounded admin periods for filtering.
|
142
|
+
"""
|
143
|
+
|
144
|
+
handled_events: Final = (
|
145
|
+
"StreamCreated",
|
146
|
+
"StreamCreatedWithReason",
|
147
|
+
"StreamModified",
|
148
|
+
"StreamPaused",
|
149
|
+
"StreamCancelled",
|
150
|
+
)
|
151
|
+
skipped_events: Final = (
|
152
|
+
"PayerDeposit",
|
153
|
+
"PayerWithdraw",
|
154
|
+
"Withdraw",
|
155
|
+
)
|
156
|
+
|
157
|
+
def __init__(self) -> None:
|
158
|
+
self.stream_contracts: Final = {Contract(addr) for addr in factories}
|
159
|
+
|
160
|
+
async def _get_streams(self) -> None:
|
161
|
+
await igather(
|
162
|
+
self._load_contract_events(stream_contract)
|
163
|
+
for stream_contract in self.stream_contracts
|
164
|
+
)
|
165
|
+
|
166
|
+
async def _load_contract_events(self, stream_contract: Contract) -> None:
|
167
|
+
events = decode_logs(
|
168
|
+
await get_logs_asap(stream_contract.address, None, sync=False)
|
169
|
+
)
|
170
|
+
keys: Set[str] = set(events.keys())
|
171
|
+
for k in keys:
|
172
|
+
if k not in self.handled_events and k not in self.skipped_events:
|
173
|
+
raise NotImplementedError(f"Need to handle event: {k}")
|
174
|
+
|
175
|
+
if "StreamCreated" in keys:
|
176
|
+
for event in events["StreamCreated"]:
|
177
|
+
from_address, *_ = event.values()
|
178
|
+
from_address = Address.get_or_insert(from_address).address
|
179
|
+
if not TreasuryWallet.check_membership(
|
180
|
+
from_address, event.block_number
|
181
|
+
):
|
182
|
+
continue
|
183
|
+
await _STREAMS_THREAD.run(self._get_stream, event)
|
184
|
+
|
185
|
+
if "StreamCreatedWithReason" in keys:
|
186
|
+
for event in events["StreamCreatedWithReason"]:
|
187
|
+
from_address, *_ = event.values()
|
188
|
+
from_address = Address.get_or_insert(from_address).address
|
189
|
+
if not TreasuryWallet.check_membership(
|
190
|
+
from_address, event.block_number
|
191
|
+
):
|
192
|
+
continue
|
193
|
+
await _STREAMS_THREAD.run(self._get_stream, event)
|
194
|
+
|
195
|
+
if "StreamModified" in keys:
|
196
|
+
for event in events["StreamModified"]:
|
197
|
+
from_address, _, _, old_stream_id, *_ = event.values()
|
198
|
+
if not TreasuryWallet.check_membership(
|
199
|
+
from_address, event.block_number
|
200
|
+
):
|
201
|
+
continue
|
202
|
+
await _STREAMS_THREAD.run(
|
203
|
+
_stop_stream, old_stream_id.hex(), event.block_number
|
204
|
+
)
|
205
|
+
await _STREAMS_THREAD.run(self._get_stream, event)
|
206
|
+
|
207
|
+
if "StreamPaused" in keys:
|
208
|
+
for event in events["StreamPaused"]:
|
209
|
+
from_address, *_, stream_id = event.values()
|
210
|
+
if not TreasuryWallet.check_membership(
|
211
|
+
from_address, event.block_number
|
212
|
+
):
|
213
|
+
continue
|
214
|
+
await _STREAMS_THREAD.run(_pause_stream, stream_id.hex())
|
215
|
+
|
216
|
+
if "StreamCancelled" in keys:
|
217
|
+
for event in events["StreamCancelled"]:
|
218
|
+
from_address, *_, stream_id = event.values()
|
219
|
+
if not TreasuryWallet.check_membership(
|
220
|
+
from_address, event.block_number
|
221
|
+
):
|
222
|
+
continue
|
223
|
+
await _STREAMS_THREAD.run(
|
224
|
+
_stop_stream, stream_id.hex(), event.block_number
|
225
|
+
)
|
226
|
+
|
227
|
+
def _get_stream(self, log: _EventItem) -> Stream:
|
228
|
+
with db_session:
|
229
|
+
if log.name == "StreamCreated":
|
230
|
+
from_address, to_address, amount_per_second, stream_id = log.values()
|
231
|
+
reason = None
|
232
|
+
elif log.name == "StreamCreatedWithReason":
|
233
|
+
from_address, to_address, amount_per_second, stream_id, reason = (
|
234
|
+
log.values()
|
235
|
+
)
|
236
|
+
elif log.name == "StreamModified":
|
237
|
+
(
|
238
|
+
from_address,
|
239
|
+
_,
|
240
|
+
_,
|
241
|
+
old_stream_id,
|
242
|
+
to_address,
|
243
|
+
amount_per_second,
|
244
|
+
stream_id,
|
245
|
+
) = log.values()
|
246
|
+
reason = Stream[old_stream_id.hex()].reason # type: ignore [misc]
|
247
|
+
else:
|
248
|
+
raise NotImplementedError("This is not an appropriate event log.")
|
249
|
+
|
250
|
+
stream_id_hex = stream_id.hex()
|
251
|
+
try:
|
252
|
+
return Stream[stream_id_hex] # type: ignore [misc]
|
253
|
+
except ObjectNotFound:
|
254
|
+
entity = Stream(
|
255
|
+
stream_id=stream_id_hex,
|
256
|
+
contract=Address.get_dbid(log.address),
|
257
|
+
start_block=log.block_number,
|
258
|
+
token=Token.get_dbid(Contract(log.address).token()),
|
259
|
+
from_address=Address.get_dbid(from_address),
|
260
|
+
to_address=Address.get_dbid(to_address),
|
261
|
+
amount_per_second=amount_per_second,
|
262
|
+
txgroup=must_sort_outbound_txgroup_dbid,
|
263
|
+
)
|
264
|
+
if reason is not None:
|
265
|
+
entity.reason = reason
|
266
|
+
commit()
|
267
|
+
return entity
|
268
|
+
|
269
|
+
def streams_for_recipient(
|
270
|
+
self, recipient: ChecksumAddress, at_block: Optional[BlockNumber] = None
|
271
|
+
) -> List[Stream]:
|
272
|
+
with db_session:
|
273
|
+
streams = Stream.select(lambda s: s.to_address.address == recipient)
|
274
|
+
if at_block is None:
|
275
|
+
return list(streams)
|
276
|
+
return [
|
277
|
+
s for s in streams if (s.end_block is None or at_block <= s.end_block)
|
278
|
+
]
|
279
|
+
|
280
|
+
def streams_for_token(
|
281
|
+
self, token: ChecksumAddress, include_inactive: bool = False
|
282
|
+
) -> List[Stream]:
|
283
|
+
with db_session:
|
284
|
+
streams = Stream.select(lambda s: s.token.address.address == token)
|
285
|
+
return (
|
286
|
+
list(streams)
|
287
|
+
if include_inactive
|
288
|
+
else [s for s in streams if s.is_alive]
|
289
|
+
)
|
290
|
+
|
291
|
+
async def process_streams(self, run_forever: bool = False) -> None:
|
292
|
+
logger.info("Processing stream events and streamed funds...")
|
293
|
+
# Always sync events before processing
|
294
|
+
await self._get_streams()
|
295
|
+
with db_session:
|
296
|
+
streams = [s.stream_id for s in Stream.select()]
|
297
|
+
await tqdm_asyncio.gather(
|
298
|
+
*(
|
299
|
+
self.process_stream(stream_id, run_forever=run_forever)
|
300
|
+
for stream_id in streams
|
301
|
+
),
|
302
|
+
desc="LlamaPay Streams",
|
303
|
+
)
|
304
|
+
|
305
|
+
async def process_stream(
|
306
|
+
self, stream_id: HexStr, run_forever: bool = False
|
307
|
+
) -> None:
|
308
|
+
start, end = await _STREAMS_THREAD.run(Stream._get_start_and_end, stream_id)
|
309
|
+
for date_obj in _generate_dates(start, end, stop_at_today=not run_forever):
|
310
|
+
if await self.process_stream_for_date(stream_id, date_obj) is None:
|
311
|
+
return
|
312
|
+
|
313
|
+
async def process_stream_for_date(
|
314
|
+
self, stream_id: HexStr, date_obj: datetime
|
315
|
+
) -> Optional[StreamedFunds]:
|
316
|
+
entity = await _STREAMS_THREAD.run(
|
317
|
+
StreamedFunds.get_entity, stream_id, date_obj
|
318
|
+
)
|
319
|
+
if entity:
|
320
|
+
return entity
|
321
|
+
|
322
|
+
stream_token, start_date = await _STREAMS_THREAD.run(
|
323
|
+
Stream._get_token_and_start_date, stream_id
|
324
|
+
)
|
325
|
+
check_at = date_obj + timedelta(days=1) - timedelta(seconds=1)
|
326
|
+
block = await get_block_at_timestamp(check_at, sync=False)
|
327
|
+
price_fut = asyncio.create_task(get_price(stream_token, block, sync=False))
|
328
|
+
start_timestamp = await _get_start_timestamp(stream_id, block)
|
329
|
+
if start_timestamp == 0:
|
330
|
+
if await _STREAMS_THREAD.run(Stream.check_closed, stream_id):
|
331
|
+
price_fut.cancel()
|
332
|
+
return None
|
333
|
+
|
334
|
+
while start_timestamp == 0:
|
335
|
+
block -= 1
|
336
|
+
start_timestamp = await _get_start_timestamp(stream_id, block)
|
337
|
+
|
338
|
+
block_datetime = datetime.fromtimestamp(
|
339
|
+
await _get_block_timestamp(block), tz=_UTC
|
340
|
+
)
|
341
|
+
assert block_datetime.date() == date_obj.date()
|
342
|
+
seconds_active = (check_at - block_datetime).seconds
|
343
|
+
is_last_day = True
|
344
|
+
else:
|
345
|
+
seconds_active = int(check_at.timestamp()) - start_timestamp
|
346
|
+
is_last_day = False
|
347
|
+
|
348
|
+
seconds_active_today = min(seconds_active, _ONE_DAY)
|
349
|
+
if seconds_active_today < _ONE_DAY and not is_last_day:
|
350
|
+
if date_obj.date() != start_date:
|
351
|
+
seconds_active_today = _ONE_DAY
|
352
|
+
|
353
|
+
with db_session:
|
354
|
+
price = Decimal(await price_fut)
|
355
|
+
entity = await _STREAMS_THREAD.run(
|
356
|
+
StreamedFunds.create_entity,
|
357
|
+
stream_id,
|
358
|
+
date_obj,
|
359
|
+
price,
|
360
|
+
seconds_active_today,
|
361
|
+
is_last_day,
|
362
|
+
)
|
363
|
+
return entity
|
dao_treasury/treasury.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from asyncio import create_task
|
1
|
+
from asyncio import create_task, gather
|
2
2
|
from logging import getLogger
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import Final, Iterable, List, Optional, Union
|
@@ -16,6 +16,7 @@ from dao_treasury._wallet import TreasuryWallet
|
|
16
16
|
from dao_treasury.constants import CHAINID
|
17
17
|
from dao_treasury.db import TreasuryTx
|
18
18
|
from dao_treasury.sorting._rules import Rules
|
19
|
+
from dao_treasury.streams import llamapay
|
19
20
|
|
20
21
|
|
21
22
|
Wallet = Union[TreasuryWallet, str]
|
@@ -108,6 +109,10 @@ class Treasury(a_sync.ASyncGenericBase): # type: ignore [misc]
|
|
108
109
|
)
|
109
110
|
"""An eth_portfolio.Portfolio object used for exporting tx and balance history"""
|
110
111
|
|
112
|
+
self._llamapay: Final = (
|
113
|
+
llamapay.LlamaPayProcessor() if CHAINID in llamapay.networks else None
|
114
|
+
)
|
115
|
+
|
111
116
|
self.asynchronous: Final = asynchronous
|
112
117
|
"""A boolean flag indicating whether the API for this `Treasury` object is sync or async by default"""
|
113
118
|
|
@@ -120,7 +125,7 @@ class Treasury(a_sync.ASyncGenericBase): # type: ignore [misc]
|
|
120
125
|
def txs(self) -> a_sync.ASyncIterator[LedgerEntry]:
|
121
126
|
return self.portfolio.ledger.all_entries
|
122
127
|
|
123
|
-
async def
|
128
|
+
async def _insert_txs(
|
124
129
|
self, start_block: BlockNumber, end_block: BlockNumber
|
125
130
|
) -> None:
|
126
131
|
"""Populate the database with treasury transactions in a block range.
|
@@ -136,7 +141,7 @@ class Treasury(a_sync.ASyncGenericBase): # type: ignore [misc]
|
|
136
141
|
|
137
142
|
Examples:
|
138
143
|
>>> # Insert transactions from block 0 to 10000
|
139
|
-
>>> await treasury.
|
144
|
+
>>> await treasury._insert_txs(0, 10000)
|
140
145
|
"""
|
141
146
|
with db_session:
|
142
147
|
futs = []
|
@@ -146,6 +151,22 @@ class Treasury(a_sync.ASyncGenericBase): # type: ignore [misc]
|
|
146
151
|
logger.debug("zero value transfer, skipping %s", entry)
|
147
152
|
continue
|
148
153
|
futs.append(create_task(TreasuryTx.insert(entry)))
|
149
|
-
|
150
154
|
if futs:
|
151
155
|
await tqdm_asyncio.gather(*futs, desc="Insert Txs to Postgres")
|
156
|
+
logger.info(f"{len(futs)} transfers exported")
|
157
|
+
|
158
|
+
async def _process_streams(self) -> None:
|
159
|
+
if self._llamapay is not None:
|
160
|
+
await self._llamapay.process_streams(run_forever=True)
|
161
|
+
|
162
|
+
async def populate_db(
|
163
|
+
self, start_block: BlockNumber, end_block: BlockNumber
|
164
|
+
) -> None:
|
165
|
+
"""
|
166
|
+
Populate the database with treasury transactions and streams in parallel.
|
167
|
+
"""
|
168
|
+
tasks = [self._insert_txs(start_block, end_block)]
|
169
|
+
if self._llamapay:
|
170
|
+
tasks.append(self._process_streams())
|
171
|
+
await gather(*tasks)
|
172
|
+
logger.info("db connection closed")
|
Binary file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dao_treasury
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.31
|
4
4
|
Summary: Produce comprehensive financial reports for your on-chain org
|
5
5
|
Classifier: Development Status :: 3 - Alpha
|
6
6
|
Classifier: Intended Audience :: Developers
|
@@ -14,7 +14,7 @@ Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Topic :: Software Development :: Libraries
|
15
15
|
Requires-Python: >=3.10,<3.13
|
16
16
|
Description-Content-Type: text/markdown
|
17
|
-
Requires-Dist: eth-portfolio-temp<0.1,>=0.0.
|
17
|
+
Requires-Dist: eth-portfolio-temp<0.1,>=0.0.29.dev0
|
18
18
|
Dynamic: classifier
|
19
19
|
Dynamic: description
|
20
20
|
Dynamic: description-content-type
|
@@ -38,7 +38,7 @@ DAO Treasury is a comprehensive financial reporting and treasury management solu
|
|
38
38
|
|
39
39
|
## Prerequisites
|
40
40
|
|
41
|
-
- First, you will need to bring your own archive node. This can be one you run yourself, or one from one of the common providers (Tenderly, Alchemy, QuickNode, etc.)
|
41
|
+
- First, you will need to bring your own archive node. This can be one you run yourself, or one from one of the common providers (Tenderly, Alchemy, QuickNode, etc.). Your archive node must have tracing enabled (free-tier Alchemy nodes do not support this option).
|
42
42
|
- You must configure a [brownie network](https://eth-brownie.readthedocs.io/en/stable/network-management.html) to use your RPC.
|
43
43
|
- You will need an auth token for [Etherscan](https://etherscan.io/)'s API. Follow their [guide](https://docs.etherscan.io/etherscan-v2/getting-an-api-key) to get your key, and set env var `ETHERSCAN_TOKEN` with its value.
|
44
44
|
- You'll also need [Docker](https://www.docker.com/get-started/) installed on your system. If on MacOS, you will need to leave Docker Desktop open while Yearn Treasury is running.
|
@@ -68,13 +68,43 @@ poetry run dao-treasury run --wallet 0x123 --network mainnet --interval 12h
|
|
68
68
|
- `--interval`: The time interval between each data snapshot (default: 12h)
|
69
69
|
- `--daemon`: Run the export process in the background (default: False) (NOTE: currently unsupported)
|
70
70
|
- `--grafana-port`: Set the port for the Grafana dashboard where you can view data (default: 3004)
|
71
|
-
- `--renderer-port`: Set the port for the report rendering service (default:
|
71
|
+
- `--renderer-port`: Set the port for the report rendering service (default: 8091)
|
72
72
|
- `--victoria-port`: Set the port for the Victoria metrics reporting endpoint (default: 8430)
|
73
|
+
- `--start-renderer`: If set, both the Grafana and renderer containers will be started for dashboard image export. By default, only the grafana container is started.
|
73
74
|
|
74
75
|
After running the command, the export script will run continuously until you close your terminal.
|
75
76
|
To view the dashboards, just open your browser and navigate to [http://localhost:3004](http://localhost:3004)!
|
76
77
|
|
77
|
-
|
78
|
+
## Docker
|
79
|
+
|
80
|
+
When you run DAO Treasury, [eth-portfolio](https://github.com/BobTheBuidler/eth-portfolio) will build and start 4 [required Docker containers](https://bobthebuidler.github.io/eth-portfolio/exporter.html#docker-containers) on your system. Additionally, DAO Treasury will build and start 2 more required containers:
|
81
|
+
|
82
|
+
- **grafana**
|
83
|
+
- Provides a web-based dashboard for visualizing your treasury data.
|
84
|
+
- Pre-configured with dashboards and plugins for real-time monitoring.
|
85
|
+
- Uses persistent storage to retain dashboard settings and data.
|
86
|
+
- Accessible locally (default port `3004`, configurable via `--grafana-port`).
|
87
|
+
- Supports anonymous access for convenience.
|
88
|
+
- Integrates with the renderer container for dashboard image export.
|
89
|
+
- Loads dashboards and data sources automatically via provisioning files.
|
90
|
+
|
91
|
+
- **renderer**
|
92
|
+
- Runs the official Grafana image renderer service.
|
93
|
+
- Enables Grafana to export dashboards as images for reporting or sharing.
|
94
|
+
- Operates on port `8091` by default (configurable via `--renderer-port`).
|
95
|
+
- Tightly integrated with the Grafana container for seamless image rendering.
|
96
|
+
- **Note:** The renderer container is only started if you pass the `--start-renderer` CLI flag.
|
97
|
+
|
98
|
+
**How it works:**
|
99
|
+
1. DAO Treasury collects and exports treasury data.
|
100
|
+
2. Grafana displays this data in pre-built dashboards for analysis and reporting.
|
101
|
+
3. The renderer container allows dashboards to be exported as images directly from Grafana (if enabled).
|
102
|
+
|
103
|
+
**Additional Information:**
|
104
|
+
- All containers are orchestrated via Docker Compose and started automatically as needed.
|
105
|
+
- Grafana provisioning ensures dashboards and data sources are set up out-of-the-box.
|
106
|
+
- All dashboard data and settings are persisted for durability.
|
107
|
+
- Dashboard images can be generated for reporting via the renderer (if enabled).
|
78
108
|
|
79
109
|
## Screenshots
|
80
110
|
|
@@ -85,3 +115,5 @@ Enjoy!
|
|
85
115
|
## Contributing
|
86
116
|
|
87
117
|
We welcome contributions to DAO Treasury! For detailed guidelines on how to contribute, please see the [Contributing Guidelines](https://github.com/BobTheBuidler/dao-treasury/blob/master/CONTRIBUTING.md).
|
118
|
+
|
119
|
+
Enjoy!
|
@@ -0,0 +1,49 @@
|
|
1
|
+
17ebe61b88bd37338d0a__mypyc.cp311-win32.pyd,sha256=zFyroqiKF9KL9Ya3Dl7MRiFlC5muBebBLBalE526QE0,401408
|
2
|
+
dao_treasury/ENVIRONMENT_VARIABLES.py,sha256=ci7djcsXc9uRi6_vBQv-avGQrrIacyftXmcwKuitWWU,203
|
3
|
+
dao_treasury/__init__.py,sha256=U8BsakN_w15wVE_7MjVbHD9LBal48LfJ6a1Icf5oHdY,1052
|
4
|
+
dao_treasury/_docker.cp311-win32.pyd,sha256=kvsjF_yLFhH1ZNXbs6rZg1abu1Fjb5HGCpEW_Gzt25s,9216
|
5
|
+
dao_treasury/_docker.py,sha256=wY26wCrQ8p-L0KfMJftyzI_YAszHjnBWLw94eh0LxxY,5607
|
6
|
+
dao_treasury/_nicknames.cp311-win32.pyd,sha256=LzTsAnZrhxH1gh9AwEuRvDVLNPYPfl0nX2_ucN846k8,9216
|
7
|
+
dao_treasury/_nicknames.py,sha256=NpbQ4NtmZF_A_vqTGSal2KzKzkH5t7_wbI8EtMBqEr4,497
|
8
|
+
dao_treasury/_wallet.cp311-win32.pyd,sha256=PRXGH5qVv5W-xtWKi1K1p0j9tEqSdTcfqcCc2Vuvr8w,9216
|
9
|
+
dao_treasury/_wallet.py,sha256=q-H3YrRLWHIjOplVEcY2zqYnv6J--nxXtcx_-a1jcQw,10584
|
10
|
+
dao_treasury/constants.cp311-win32.pyd,sha256=TPzH-Z6HiFr_05UohE4WMPT0Okn7Fu95mojwzb30tbM,9216
|
11
|
+
dao_treasury/constants.py,sha256=VNzlrwC8HB8LdKNmoIfncUACKHfXZxp2GtBBrH_lZKE,522
|
12
|
+
dao_treasury/db.py,sha256=naWIASCJCGROMijxdkS-A0g8_HEFrCqCP0FsAjaDRm8,46167
|
13
|
+
dao_treasury/docker-compose.yaml,sha256=trksBUUwNGJGcrsgX0k0xVEsl-SjWzYE_4tpO1DjuJg,1421
|
14
|
+
dao_treasury/main.py,sha256=yVQGwDgO4XKcxV6tQQPxEcLcZPSEEK1yJS8Djfq3Esc,8294
|
15
|
+
dao_treasury/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
+
dao_treasury/treasury.py,sha256=64_z0lsjOngGFASCVQNUM-QfbC5YqhKMgOdtLh758co,6678
|
17
|
+
dao_treasury/types.cp311-win32.pyd,sha256=HdLGIg8tn3TmX7LVmmfN_3Hlg1OAMdbsfKo4MFOGJBU,9216
|
18
|
+
dao_treasury/types.py,sha256=KFz4WKPp4t_RBwIT6YGwOcgbzw8tdHIOcXTFsUA0pJA,3818
|
19
|
+
dao_treasury/.grafana/provisioning/dashboards/dashboards.yaml,sha256=DzPkgm6Sz2hB5nNpKyTWTEfRplsYVQ9KUgQZ-yQOu6c,747
|
20
|
+
dao_treasury/.grafana/provisioning/dashboards/streams/LlamaPay.json,sha256=zKisR5WewEdpPMMSVLe3w-K8YBg30CYEuzrYjW_LZTQ,3518
|
21
|
+
dao_treasury/.grafana/provisioning/dashboards/summary/Monthly.json,sha256=pEHnKgiFgL_oscujk2SVEmkwe-PZiuGjlKf7bcLm2uc,8022
|
22
|
+
dao_treasury/.grafana/provisioning/dashboards/transactions/Treasury Transactions.json,sha256=kXaEYWwbwVbkYmj2iWYpHzHVeeBidFpGQoLm5rxUUGU,16361
|
23
|
+
dao_treasury/.grafana/provisioning/dashboards/treasury/Cashflow.json,sha256=Q544KS0P30LnBWAjMFr9jHOW83-Eoj76AQEWjNDS93o,43367
|
24
|
+
dao_treasury/.grafana/provisioning/dashboards/treasury/Treasury.json,sha256=6y7Fp-2g1iRanRBtWKKN13sXjaKxBvqld7416ZJu4YI,72196
|
25
|
+
dao_treasury/.grafana/provisioning/datasources/datasources.yaml,sha256=gLmJsOkEXNzWRDibShfHFySWeuExW-dSB_U0OSfH868,344
|
26
|
+
dao_treasury/sorting/__init__.cp311-win32.pyd,sha256=1ztL5Mfy1iDcH3dcTQvM_E5xg7QZm3-wPeYCwcHlRMI,9216
|
27
|
+
dao_treasury/sorting/__init__.py,sha256=hHH9LLX11m2YR5NFKiTSVtdLBNGdAyJsvgDNzG3BTBI,5977
|
28
|
+
dao_treasury/sorting/_matchers.cp311-win32.pyd,sha256=AVmENDIDj5n_Xp2E1H7hpzorWBECAcnNW5EwxB9HJK4,9216
|
29
|
+
dao_treasury/sorting/_matchers.py,sha256=ACi6aXZCKW5OTiztsID7CXCGJounj5c6ivhOCg2436M,14392
|
30
|
+
dao_treasury/sorting/_rules.cp311-win32.pyd,sha256=ySzsr1IqekZ5l-2XDjMXY_lcRJDl8OYo0yQBLOhYeKE,9216
|
31
|
+
dao_treasury/sorting/_rules.py,sha256=DxhdUgpS0q0LWeLl9W1Bjn5LZz9z4OLA03vQllPMH9k,9229
|
32
|
+
dao_treasury/sorting/factory.cp311-win32.pyd,sha256=bSsfW1zihJufl64mrhsmKuAQ_oM7tl2gN-tGJcbufpU,9216
|
33
|
+
dao_treasury/sorting/factory.py,sha256=zlJ18xlMTxv8ACV6_MimCVIDsw3K5AZcyvKaNYY7R14,10484
|
34
|
+
dao_treasury/sorting/rule.cp311-win32.pyd,sha256=nOEnTwMtalLi7aDKAedecZpEEWgWZI4fGCr36UCYI3k,9216
|
35
|
+
dao_treasury/sorting/rule.py,sha256=TsSlaU4UlYr4QWJBdUY7EOAfC_SK6_VA3wEFOFNUUrU,11837
|
36
|
+
dao_treasury/sorting/rules/__init__.cp311-win32.pyd,sha256=phEQajfd15iAzB3sKOaVZHFZfO4AbQqzIx0VoEtWvzA,9216
|
37
|
+
dao_treasury/sorting/rules/__init__.py,sha256=hcLfejOEwD8RaM2RE-38Ej6_WkvL9BVGvIIGY848E6E,49
|
38
|
+
dao_treasury/sorting/rules/ignore/__init__.cp311-win32.pyd,sha256=V_ZNCO3ieQDLrq15DcYwmCActMOmdZ46gDCDT5SBvWA,9216
|
39
|
+
dao_treasury/sorting/rules/ignore/__init__.py,sha256=16THKoGdj6qfkkytuCFVG_R1M6KSErMI4AVE1p0ukS4,58
|
40
|
+
dao_treasury/sorting/rules/ignore/llamapay.cp311-win32.pyd,sha256=C0H8O-KgxSaxafPjPNSit1MW0vDFjj4m3jD0PIGw8Ds,9216
|
41
|
+
dao_treasury/sorting/rules/ignore/llamapay.py,sha256=aYyAJRlmv419IeaqkcV5o3ffx0UVfteU0lTl80j0BGo,783
|
42
|
+
dao_treasury/streams/__init__.cp311-win32.pyd,sha256=jRktdMp4izk3npkqyBtBmIlNq3YRk1qNyERPZoO-g4s,9216
|
43
|
+
dao_treasury/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
|
+
dao_treasury/streams/llamapay.cp311-win32.pyd,sha256=2g7nK1BT2pHbYiwZ0TNjz_J3Fn2eUWHmvEGldr7vL6Y,9216
|
45
|
+
dao_treasury/streams/llamapay.py,sha256=d1nx4WCUDXs2LdEtYqTGVYEHWb9HxREmwE6-gzjPzn8,12828
|
46
|
+
dao_treasury-0.0.31.dist-info/METADATA,sha256=-YSFQ51Hao57Mg7SAVkWdAk2Ala5Q1Spu8PA2MspqmM,7149
|
47
|
+
dao_treasury-0.0.31.dist-info/WHEEL,sha256=Ri8zddKrjGdgjlj1OpSsvpDnvHfnQhMQWi3E_v2pqng,97
|
48
|
+
dao_treasury-0.0.31.dist-info/top_level.txt,sha256=VtgTZeHElDnBUEn2XLN6Hy_nev5HfhK5c-OnfU04bUs,41
|
49
|
+
dao_treasury-0.0.31.dist-info/RECORD,,
|
Binary file
|
@@ -1,37 +0,0 @@
|
|
1
|
-
8d7637a0eb7617042369__mypyc.cp311-win32.pyd,sha256=v1D6N-nGDQ9OLqTdHLaROSSUGI6j5WrAf6v_QB-Z5i0,216064
|
2
|
-
dao_treasury/ENVIRONMENT_VARIABLES.py,sha256=ci7djcsXc9uRi6_vBQv-avGQrrIacyftXmcwKuitWWU,203
|
3
|
-
dao_treasury/__init__.py,sha256=U8BsakN_w15wVE_7MjVbHD9LBal48LfJ6a1Icf5oHdY,1052
|
4
|
-
dao_treasury/_docker.py,sha256=q0xhHc9YEaPfk3RrJLXeMaFRl-emedIwzH7Xhz3Mapw,5289
|
5
|
-
dao_treasury/_nicknames.cp311-win32.pyd,sha256=Dtcd3MEwQc3qKnStqnX4Td2No1e27A-Oc7H9oxGCmWQ,9216
|
6
|
-
dao_treasury/_nicknames.py,sha256=NpbQ4NtmZF_A_vqTGSal2KzKzkH5t7_wbI8EtMBqEr4,497
|
7
|
-
dao_treasury/_wallet.cp311-win32.pyd,sha256=ZyVa83KfLXWviAUo0aT-TDS6LuqM6wYoXH_vP7ehSY0,9216
|
8
|
-
dao_treasury/_wallet.py,sha256=q-H3YrRLWHIjOplVEcY2zqYnv6J--nxXtcx_-a1jcQw,10584
|
9
|
-
dao_treasury/constants.cp311-win32.pyd,sha256=VXCmUr2jtcEpKNQOt1FJxVfAzF6pxd7m5yAhcYhMHTk,9216
|
10
|
-
dao_treasury/constants.py,sha256=VNzlrwC8HB8LdKNmoIfncUACKHfXZxp2GtBBrH_lZKE,522
|
11
|
-
dao_treasury/db.py,sha256=HMFp3X8rRn3TE0FbqnqgzbMm-WFF6GaGq6UmYYViHgY,41205
|
12
|
-
dao_treasury/docker-compose.yaml,sha256=z1avJCfQY5aKrhw6Bq72dnjzRLAm4ttbzvjWeh69bM4,1378
|
13
|
-
dao_treasury/main.py,sha256=roM8SLoZg4Z_rlQtm5kDAL9U9YisCYQ81lX9PQ93AvA,7517
|
14
|
-
dao_treasury/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
dao_treasury/treasury.py,sha256=N5skZSxvb9-7CajhHrynKJMnUuy5pYEpRr75SDjGVoE,5843
|
16
|
-
dao_treasury/types.cp311-win32.pyd,sha256=e5CEROsVHwpThAnHDWwQ44sPkSrJ_0o1SIsua3vmeWs,9216
|
17
|
-
dao_treasury/types.py,sha256=KFz4WKPp4t_RBwIT6YGwOcgbzw8tdHIOcXTFsUA0pJA,3818
|
18
|
-
dao_treasury/.grafana/provisioning/dashboards/dashboards.yaml,sha256=43Lof370qTV5TPnVRpWxXIzjRpPX_u9khgee41lAyYQ,570
|
19
|
-
dao_treasury/.grafana/provisioning/dashboards/summary/Monthly.json,sha256=pEHnKgiFgL_oscujk2SVEmkwe-PZiuGjlKf7bcLm2uc,8022
|
20
|
-
dao_treasury/.grafana/provisioning/dashboards/transactions/Treasury Transactions.json,sha256=kXaEYWwbwVbkYmj2iWYpHzHVeeBidFpGQoLm5rxUUGU,16361
|
21
|
-
dao_treasury/.grafana/provisioning/dashboards/treasury/Cashflow.json,sha256=xrUS1csp0hsfHcxhxFekxwzZ3ul-03aDwNw1EurBG9U,43323
|
22
|
-
dao_treasury/.grafana/provisioning/dashboards/treasury/Treasury.json,sha256=6y7Fp-2g1iRanRBtWKKN13sXjaKxBvqld7416ZJu4YI,72196
|
23
|
-
dao_treasury/.grafana/provisioning/datasources/datasources.yaml,sha256=gLmJsOkEXNzWRDibShfHFySWeuExW-dSB_U0OSfH868,344
|
24
|
-
dao_treasury/sorting/__init__.cp311-win32.pyd,sha256=uM01xD1-GRbG78894Fx9gFiEBuY2plBabznbUwvzYqc,9216
|
25
|
-
dao_treasury/sorting/__init__.py,sha256=8scT00tm4LtZrKTnpv6MGdJWqC9L0ksDt_V7lrK7c98,5935
|
26
|
-
dao_treasury/sorting/_matchers.cp311-win32.pyd,sha256=5Re6-cNTxuzNECNk0hXdeisGvq64Gvufeu3BO-wPQmU,9216
|
27
|
-
dao_treasury/sorting/_matchers.py,sha256=ACi6aXZCKW5OTiztsID7CXCGJounj5c6ivhOCg2436M,14392
|
28
|
-
dao_treasury/sorting/_rules.cp311-win32.pyd,sha256=_TZ-fyM_2Cr-_HOk5xcr0jan1RQmcSQ98oDyYrI242I,9216
|
29
|
-
dao_treasury/sorting/_rules.py,sha256=DxhdUgpS0q0LWeLl9W1Bjn5LZz9z4OLA03vQllPMH9k,9229
|
30
|
-
dao_treasury/sorting/factory.cp311-win32.pyd,sha256=ARzijT4JB6c51IRiFU5gvV7nyvgvRyBmYVd5H6CJr4Q,9216
|
31
|
-
dao_treasury/sorting/factory.py,sha256=zlJ18xlMTxv8ACV6_MimCVIDsw3K5AZcyvKaNYY7R14,10484
|
32
|
-
dao_treasury/sorting/rule.cp311-win32.pyd,sha256=ciBxvWn1RuM4dFCien1bB5Ly7XZ245VSYLQLbjevqek,9216
|
33
|
-
dao_treasury/sorting/rule.py,sha256=It0DNHavo6eqCNn5l2Mhqt7UUzvf03zhl2KIMkHz65M,11676
|
34
|
-
dao_treasury-0.0.29.dist-info/METADATA,sha256=hsvWQC4YKnwOlGQW_1mPkPV3qruScLJpgi-023To2iI,5015
|
35
|
-
dao_treasury-0.0.29.dist-info/WHEEL,sha256=Ri8zddKrjGdgjlj1OpSsvpDnvHfnQhMQWi3E_v2pqng,97
|
36
|
-
dao_treasury-0.0.29.dist-info/top_level.txt,sha256=gLBdPS4UkA4KGtYmzVb1Jqq5N0FmaTK76zmC9-mACEE,41
|
37
|
-
dao_treasury-0.0.29.dist-info/RECORD,,
|
File without changes
|