dao-treasury 0.0.29__cp312-cp312-win_amd64.whl → 0.0.31__cp312-cp312-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 (38) hide show
  1. 17ebe61b88bd37338d0a__mypyc.cp312-win_amd64.pyd +0 -0
  2. dao_treasury/.grafana/provisioning/dashboards/dashboards.yaml +8 -0
  3. dao_treasury/.grafana/provisioning/dashboards/streams/LlamaPay.json +116 -0
  4. dao_treasury/.grafana/provisioning/dashboards/treasury/Cashflow.json +22 -22
  5. dao_treasury/_docker.cp312-win_amd64.pyd +0 -0
  6. dao_treasury/_docker.py +23 -18
  7. dao_treasury/_nicknames.cp312-win_amd64.pyd +0 -0
  8. dao_treasury/_wallet.cp312-win_amd64.pyd +0 -0
  9. dao_treasury/constants.cp312-win_amd64.pyd +0 -0
  10. dao_treasury/db.py +172 -16
  11. dao_treasury/docker-compose.yaml +1 -0
  12. dao_treasury/main.py +27 -3
  13. dao_treasury/sorting/__init__.cp312-win_amd64.pyd +0 -0
  14. dao_treasury/sorting/__init__.py +1 -0
  15. dao_treasury/sorting/_matchers.cp312-win_amd64.pyd +0 -0
  16. dao_treasury/sorting/_rules.cp312-win_amd64.pyd +0 -0
  17. dao_treasury/sorting/factory.cp312-win_amd64.pyd +0 -0
  18. dao_treasury/sorting/rule.cp312-win_amd64.pyd +0 -0
  19. dao_treasury/sorting/rule.py +5 -0
  20. dao_treasury/sorting/rules/__init__.cp312-win_amd64.pyd +0 -0
  21. dao_treasury/sorting/rules/__init__.py +1 -0
  22. dao_treasury/sorting/rules/ignore/__init__.cp312-win_amd64.pyd +0 -0
  23. dao_treasury/sorting/rules/ignore/__init__.py +1 -0
  24. dao_treasury/sorting/rules/ignore/llamapay.cp312-win_amd64.pyd +0 -0
  25. dao_treasury/sorting/rules/ignore/llamapay.py +20 -0
  26. dao_treasury/streams/__init__.cp312-win_amd64.pyd +0 -0
  27. dao_treasury/streams/__init__.py +0 -0
  28. dao_treasury/streams/llamapay.cp312-win_amd64.pyd +0 -0
  29. dao_treasury/streams/llamapay.py +363 -0
  30. dao_treasury/treasury.py +25 -4
  31. dao_treasury/types.cp312-win_amd64.pyd +0 -0
  32. {dao_treasury-0.0.29.dist-info → dao_treasury-0.0.31.dist-info}/METADATA +37 -5
  33. dao_treasury-0.0.31.dist-info/RECORD +49 -0
  34. dao_treasury-0.0.31.dist-info/top_level.txt +2 -0
  35. 8d7637a0eb7617042369__mypyc.cp312-win_amd64.pyd +0 -0
  36. dao_treasury-0.0.29.dist-info/RECORD +0 -37
  37. dao_treasury-0.0.29.dist-info/top_level.txt +0 -2
  38. {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 populate_db(
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.populate_db(0, 10000)
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.29
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.18.dev0
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: 8080)
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
- Enjoy!
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.cp312-win_amd64.pyd,sha256=xEG9guYY74jRDh6YbDvbljCNK8XPKUAMioP2sUaX2w4,498176
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.cp312-win_amd64.pyd,sha256=SvQNnXFrGjCZa4TQY0QMnJIAjL_qL-hCRedxKos3pus,10752
5
+ dao_treasury/_docker.py,sha256=wY26wCrQ8p-L0KfMJftyzI_YAszHjnBWLw94eh0LxxY,5607
6
+ dao_treasury/_nicknames.cp312-win_amd64.pyd,sha256=GqpjXWXQi4r4-K4FsgSWPWNW1KNc_3Wg5sH5eQFJO-g,10752
7
+ dao_treasury/_nicknames.py,sha256=NpbQ4NtmZF_A_vqTGSal2KzKzkH5t7_wbI8EtMBqEr4,497
8
+ dao_treasury/_wallet.cp312-win_amd64.pyd,sha256=7ECvOq8J3R2w8-Ea2DRI-fPpI3bwZSDNzWHdxGpWxQQ,10752
9
+ dao_treasury/_wallet.py,sha256=q-H3YrRLWHIjOplVEcY2zqYnv6J--nxXtcx_-a1jcQw,10584
10
+ dao_treasury/constants.cp312-win_amd64.pyd,sha256=iuw-SvnPNIPNDzmN47mGONd9rLAISDZe7ip55yCI8Q8,10752
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.cp312-win_amd64.pyd,sha256=fceOt7NJQ_kjjkPIBCfYsKsqVTvgSt5DKsAEo6_yS-M,10752
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__.cp312-win_amd64.pyd,sha256=wnVWUGdH2iWiGHZLAcW7GQfMWA2Xt3-_432hyNBBFTQ,10752
27
+ dao_treasury/sorting/__init__.py,sha256=hHH9LLX11m2YR5NFKiTSVtdLBNGdAyJsvgDNzG3BTBI,5977
28
+ dao_treasury/sorting/_matchers.cp312-win_amd64.pyd,sha256=iJkBwsNi4iJ2QRxhZDuKZzybL2Hm1vASS_TLkV3aZyk,10752
29
+ dao_treasury/sorting/_matchers.py,sha256=ACi6aXZCKW5OTiztsID7CXCGJounj5c6ivhOCg2436M,14392
30
+ dao_treasury/sorting/_rules.cp312-win_amd64.pyd,sha256=qITbOzwGnKFmy9usK1RbvI2JjzbdkOUyT-ue2W-Lxsk,10752
31
+ dao_treasury/sorting/_rules.py,sha256=DxhdUgpS0q0LWeLl9W1Bjn5LZz9z4OLA03vQllPMH9k,9229
32
+ dao_treasury/sorting/factory.cp312-win_amd64.pyd,sha256=UA20tdt-XcAUJqcAlp7IfzUNvmhR3Iz0Wsi0xir_heU,10752
33
+ dao_treasury/sorting/factory.py,sha256=zlJ18xlMTxv8ACV6_MimCVIDsw3K5AZcyvKaNYY7R14,10484
34
+ dao_treasury/sorting/rule.cp312-win_amd64.pyd,sha256=fREquVBMVMyxvD2EJ5Ohouv0I7BwN4Yg7fwqWkOLZ-o,10752
35
+ dao_treasury/sorting/rule.py,sha256=TsSlaU4UlYr4QWJBdUY7EOAfC_SK6_VA3wEFOFNUUrU,11837
36
+ dao_treasury/sorting/rules/__init__.cp312-win_amd64.pyd,sha256=VZAIpO0oumsTcRj5nubIZCnqa8ghcWEPBzt0nAOT1FA,10752
37
+ dao_treasury/sorting/rules/__init__.py,sha256=hcLfejOEwD8RaM2RE-38Ej6_WkvL9BVGvIIGY848E6E,49
38
+ dao_treasury/sorting/rules/ignore/__init__.cp312-win_amd64.pyd,sha256=xxpn6Xl2XfHoHuDiAJLOlR4u_Lk8jrg_ufSaxr_YAco,10752
39
+ dao_treasury/sorting/rules/ignore/__init__.py,sha256=16THKoGdj6qfkkytuCFVG_R1M6KSErMI4AVE1p0ukS4,58
40
+ dao_treasury/sorting/rules/ignore/llamapay.cp312-win_amd64.pyd,sha256=9TUrvjX0vAC7X11rOexBd0_AHznnDsiw2l9V8rmf4-0,10752
41
+ dao_treasury/sorting/rules/ignore/llamapay.py,sha256=aYyAJRlmv419IeaqkcV5o3ffx0UVfteU0lTl80j0BGo,783
42
+ dao_treasury/streams/__init__.cp312-win_amd64.pyd,sha256=avZ07zztVmXliyfBrRhsEAc9X3MDCUMuwYbCLCXttX4,10752
43
+ dao_treasury/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
+ dao_treasury/streams/llamapay.cp312-win_amd64.pyd,sha256=T1YnZaNWer3rO6rU31sVIunNl2GObawlaN-1pW9BoXM,10752
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=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
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,,
@@ -0,0 +1,2 @@
1
+ 17ebe61b88bd37338d0a__mypyc
2
+ dao_treasury
@@ -1,37 +0,0 @@
1
- 8d7637a0eb7617042369__mypyc.cp312-win_amd64.pyd,sha256=cwHxEqf3q7aPJ35PeGA88OO3HKIC1mIAxacubJMSgoI,263168
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.cp312-win_amd64.pyd,sha256=peBoF87buWkUFT9quH1FV0vvcLp10VET536BMSR0yiM,10752
6
- dao_treasury/_nicknames.py,sha256=NpbQ4NtmZF_A_vqTGSal2KzKzkH5t7_wbI8EtMBqEr4,497
7
- dao_treasury/_wallet.cp312-win_amd64.pyd,sha256=xQUZJPk-hMXy1t3hZQyj0WM0ZaOMZTW1pN4K6RLo8hs,10752
8
- dao_treasury/_wallet.py,sha256=q-H3YrRLWHIjOplVEcY2zqYnv6J--nxXtcx_-a1jcQw,10584
9
- dao_treasury/constants.cp312-win_amd64.pyd,sha256=36JCL_q5wiZ9rYYa8I-l56rUUHXwGCTu00h0ipiIqz8,10752
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.cp312-win_amd64.pyd,sha256=WXXePmRQDGrFFYYyzgCKjOOyEjNveZKnSyOA8uN8UW0,10752
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__.cp312-win_amd64.pyd,sha256=ZRbFmcTSYqUP7apXDqMtIZJMLgsDAJg-52R8cSyDnVw,10752
25
- dao_treasury/sorting/__init__.py,sha256=8scT00tm4LtZrKTnpv6MGdJWqC9L0ksDt_V7lrK7c98,5935
26
- dao_treasury/sorting/_matchers.cp312-win_amd64.pyd,sha256=3ycg5pY0ku2HRrCJbLUWSVB40UizneXLMYAL2lAfOp4,10752
27
- dao_treasury/sorting/_matchers.py,sha256=ACi6aXZCKW5OTiztsID7CXCGJounj5c6ivhOCg2436M,14392
28
- dao_treasury/sorting/_rules.cp312-win_amd64.pyd,sha256=14r-FHkJZIZp89T4PUT6QZGeJptBxd_NhBN8SdH0RMo,10752
29
- dao_treasury/sorting/_rules.py,sha256=DxhdUgpS0q0LWeLl9W1Bjn5LZz9z4OLA03vQllPMH9k,9229
30
- dao_treasury/sorting/factory.cp312-win_amd64.pyd,sha256=wm5DlQwTY7oVo3OAHYXIC4h3PeWdnDhAklGkdntGzSk,10752
31
- dao_treasury/sorting/factory.py,sha256=zlJ18xlMTxv8ACV6_MimCVIDsw3K5AZcyvKaNYY7R14,10484
32
- dao_treasury/sorting/rule.cp312-win_amd64.pyd,sha256=EBupYMdXYWPghFgM0wDaVbXVcw32FIDJvx9oQJWD_us,10752
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=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
36
- dao_treasury-0.0.29.dist-info/top_level.txt,sha256=gLBdPS4UkA4KGtYmzVb1Jqq5N0FmaTK76zmC9-mACEE,41
37
- dao_treasury-0.0.29.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- 8d7637a0eb7617042369__mypyc
2
- dao_treasury