dao-treasury 0.0.42__cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.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 (51) hide show
  1. bf2b4fe1f86ad2ea158b__mypyc.cpython-310-i386-linux-gnu.so +0 -0
  2. dao_treasury/.grafana/provisioning/dashboards/dashboards.yaml +60 -0
  3. dao_treasury/.grafana/provisioning/dashboards/streams/LlamaPay.json +225 -0
  4. dao_treasury/.grafana/provisioning/dashboards/summary/Monthly.json +107 -0
  5. dao_treasury/.grafana/provisioning/dashboards/transactions/Treasury Transactions.json +387 -0
  6. dao_treasury/.grafana/provisioning/dashboards/treasury/Cashflow (Including Unsorted).json +835 -0
  7. dao_treasury/.grafana/provisioning/dashboards/treasury/Cashflow.json +615 -0
  8. dao_treasury/.grafana/provisioning/dashboards/treasury/Operating Cashflow.json +492 -0
  9. dao_treasury/.grafana/provisioning/dashboards/treasury/Treasury.json +2018 -0
  10. dao_treasury/.grafana/provisioning/datasources/datasources.yaml +17 -0
  11. dao_treasury/ENVIRONMENT_VARIABLES.py +20 -0
  12. dao_treasury/__init__.py +62 -0
  13. dao_treasury/_docker.cpython-310-i386-linux-gnu.so +0 -0
  14. dao_treasury/_docker.py +190 -0
  15. dao_treasury/_nicknames.cpython-310-i386-linux-gnu.so +0 -0
  16. dao_treasury/_nicknames.py +32 -0
  17. dao_treasury/_wallet.cpython-310-i386-linux-gnu.so +0 -0
  18. dao_treasury/_wallet.py +250 -0
  19. dao_treasury/constants.cpython-310-i386-linux-gnu.so +0 -0
  20. dao_treasury/constants.py +34 -0
  21. dao_treasury/db.py +1408 -0
  22. dao_treasury/docker-compose.yaml +41 -0
  23. dao_treasury/main.py +247 -0
  24. dao_treasury/py.typed +0 -0
  25. dao_treasury/sorting/__init__.cpython-310-i386-linux-gnu.so +0 -0
  26. dao_treasury/sorting/__init__.py +295 -0
  27. dao_treasury/sorting/_matchers.cpython-310-i386-linux-gnu.so +0 -0
  28. dao_treasury/sorting/_matchers.py +387 -0
  29. dao_treasury/sorting/_rules.cpython-310-i386-linux-gnu.so +0 -0
  30. dao_treasury/sorting/_rules.py +235 -0
  31. dao_treasury/sorting/factory.cpython-310-i386-linux-gnu.so +0 -0
  32. dao_treasury/sorting/factory.py +299 -0
  33. dao_treasury/sorting/rule.cpython-310-i386-linux-gnu.so +0 -0
  34. dao_treasury/sorting/rule.py +346 -0
  35. dao_treasury/sorting/rules/__init__.cpython-310-i386-linux-gnu.so +0 -0
  36. dao_treasury/sorting/rules/__init__.py +1 -0
  37. dao_treasury/sorting/rules/ignore/__init__.cpython-310-i386-linux-gnu.so +0 -0
  38. dao_treasury/sorting/rules/ignore/__init__.py +1 -0
  39. dao_treasury/sorting/rules/ignore/llamapay.cpython-310-i386-linux-gnu.so +0 -0
  40. dao_treasury/sorting/rules/ignore/llamapay.py +20 -0
  41. dao_treasury/streams/__init__.cpython-310-i386-linux-gnu.so +0 -0
  42. dao_treasury/streams/__init__.py +0 -0
  43. dao_treasury/streams/llamapay.cpython-310-i386-linux-gnu.so +0 -0
  44. dao_treasury/streams/llamapay.py +388 -0
  45. dao_treasury/treasury.py +191 -0
  46. dao_treasury/types.cpython-310-i386-linux-gnu.so +0 -0
  47. dao_treasury/types.py +133 -0
  48. dao_treasury-0.0.42.dist-info/METADATA +119 -0
  49. dao_treasury-0.0.42.dist-info/RECORD +51 -0
  50. dao_treasury-0.0.42.dist-info/WHEEL +7 -0
  51. dao_treasury-0.0.42.dist-info/top_level.txt +2 -0
@@ -0,0 +1,299 @@
1
+ from typing import Any, Final, Generic, Optional, Union, final, overload
2
+
3
+ from dao_treasury.constants import CHAINID
4
+ from dao_treasury.sorting.rule import (
5
+ CostOfRevenueSortRule,
6
+ ExpenseSortRule,
7
+ IgnoreSortRule,
8
+ OtherExpenseSortRule,
9
+ OtherIncomeSortRule,
10
+ RevenueSortRule,
11
+ TRule,
12
+ )
13
+ from dao_treasury.types import Networks, SortFunction, TxGroupName
14
+
15
+
16
+ def revenue(
17
+ txgroup: TxGroupName, networks: Networks = CHAINID
18
+ ) -> "SortRuleFactory[RevenueSortRule]":
19
+ """Create a factory to register revenue sort rules for a given transaction group.
20
+
21
+ Args:
22
+ txgroup: Base name of the transaction group to categorize as revenue.
23
+ networks: Network ID or iterable of network IDs on which this rule applies.
24
+
25
+ See Also:
26
+ :func:`cost_of_revenue`
27
+ :class:`SortRuleFactory`
28
+
29
+ Examples:
30
+ >>> from dao_treasury.sorting.factory import revenue
31
+ >>> @revenue("Token Sales")
32
+ ... def match_sales(tx):
33
+ ... return tx.amount > 0 and tx.to_address is not None
34
+ """
35
+ return SortRuleFactory(txgroup, networks, RevenueSortRule)
36
+
37
+
38
+ def cost_of_revenue(
39
+ txgroup: TxGroupName, networks: Networks = CHAINID
40
+ ) -> "SortRuleFactory[CostOfRevenueSortRule]":
41
+ """Create a factory to register cost‐of‐revenue sort rules for a given transaction group.
42
+
43
+ Args:
44
+ txgroup: Base name of the transaction group to categorize as cost of revenue.
45
+ networks: Network ID or iterable of network IDs on which this rule applies.
46
+
47
+ See Also:
48
+ :func:`revenue`
49
+ :class:`SortRuleFactory`
50
+
51
+ Examples:
52
+ >>> from dao_treasury.sorting.factory import cost_of_revenue
53
+ >>> @cost_of_revenue("Manufacturing")
54
+ ... def match_manufacturing(tx):
55
+ ... return tx.from_address is not None and tx.amount_usd > 1000
56
+ """
57
+ return SortRuleFactory(txgroup, networks, CostOfRevenueSortRule)
58
+
59
+
60
+ def expense(
61
+ txgroup: TxGroupName, networks: Networks = CHAINID
62
+ ) -> "SortRuleFactory[ExpenseSortRule]":
63
+ """Create a factory to register expense sort rules for a given transaction group.
64
+
65
+ Args:
66
+ txgroup: Base name of the transaction group to categorize as expense.
67
+ networks: Network ID or iterable of network IDs on which this rule applies.
68
+
69
+ See Also:
70
+ :func:`other_expense`
71
+ :class:`SortRuleFactory`
72
+
73
+ Examples:
74
+ >>> from dao_treasury.sorting.factory import expense
75
+ >>> @expense("Office Supplies")
76
+ ... def match_supplies(tx):
77
+ ... return tx.symbol == "USD" and tx.amount < 500
78
+ """
79
+ return SortRuleFactory(txgroup, networks, ExpenseSortRule)
80
+
81
+
82
+ def other_income(
83
+ txgroup: TxGroupName, networks: Networks = CHAINID
84
+ ) -> "SortRuleFactory[OtherIncomeSortRule]":
85
+ """Create a factory to register other‐income sort rules for a given transaction group.
86
+
87
+ Args:
88
+ txgroup: Base name of the transaction group to categorize as other income.
89
+ networks: Network ID or iterable of network IDs on which this rule applies.
90
+
91
+ See Also:
92
+ :func:`revenue`
93
+ :class:`SortRuleFactory`
94
+
95
+ Examples:
96
+ >>> from dao_treasury.sorting.factory import other_income
97
+ >>> @other_income("Interest")
98
+ ... def match_interest(tx):
99
+ ... return tx.token_address == SOME_TOKEN and tx.amount > 0
100
+ """
101
+ return SortRuleFactory(txgroup, networks, OtherIncomeSortRule)
102
+
103
+
104
+ def other_expense(
105
+ txgroup: TxGroupName, networks: Networks = CHAINID
106
+ ) -> "SortRuleFactory[OtherExpenseSortRule]":
107
+ """Create a factory to register other‐expense sort rules for a given transaction group.
108
+
109
+ Args:
110
+ txgroup: Base name of the transaction group to categorize as other expense.
111
+ networks: Network ID or iterable of network IDs on which this rule applies.
112
+
113
+ See Also:
114
+ :func:`expense`
115
+ :class:`SortRuleFactory`
116
+
117
+ Examples:
118
+ >>> from dao_treasury.sorting.factory import other_expense
119
+ >>> @other_expense("Misc Fees")
120
+ ... def match_misc(tx):
121
+ ... return tx.amount_usd < 0 and tx.symbol == "ETH"
122
+ """
123
+ return SortRuleFactory(txgroup, networks, OtherExpenseSortRule)
124
+
125
+
126
+ def ignore(
127
+ txgroup: TxGroupName, networks: Networks = CHAINID
128
+ ) -> "SortRuleFactory[IgnoreSortRule]":
129
+ """Create a factory to register ignore sort rules for a given transaction group.
130
+
131
+ Args:
132
+ txgroup: Base name of the transaction group to categorize as ignored.
133
+ networks: Network ID or iterable of network IDs on which this rule applies.
134
+
135
+ See Also:
136
+ :class:`SortRuleFactory`
137
+
138
+ Examples:
139
+ >>> from dao_treasury.sorting.factory import ignore
140
+ >>> @ignore("Dust")
141
+ ... def match_dust(tx):
142
+ ... return abs(tx.value_usd) < 0.01
143
+ """
144
+ return SortRuleFactory(txgroup, networks, IgnoreSortRule)
145
+
146
+
147
+ @final
148
+ class SortRuleFactory(Generic[TRule]):
149
+ """Builder for creating sort rule instances for a specific transaction group and network(s).
150
+
151
+ This factory supports two patterns:
152
+
153
+ 1. Decorating a function to register a dynamic matching rule.
154
+ 2. Calling :meth:`match` to supply static match attributes.
155
+
156
+ Use the convenience functions like :func:`revenue`, :func:`expense`, etc.,
157
+ to obtain an instance of this factory preconfigured with the appropriate rule type.
158
+
159
+ Examples:
160
+ >>> from dao_treasury.sorting.factory import revenue
161
+ >>> @revenue("Sales", networks=[1, 3])
162
+ ... def match_large_sales(tx):
163
+ ... return tx.value_usd > 1000
164
+ """
165
+
166
+ def __init__(
167
+ self,
168
+ txgroup: TxGroupName,
169
+ networks: Networks,
170
+ rule_type: TRule,
171
+ ) -> None:
172
+ """Initialize the sort rule factory.
173
+
174
+ Args:
175
+ txgroup: Base name of the transaction group.
176
+ networks: Single network ID or iterable of network IDs where the rule applies.
177
+ rule_type: Sort rule class (e.g., RevenueSortRule) to instantiate.
178
+ """
179
+ self.txgroup: Final = txgroup
180
+ self.networks: Final = (
181
+ [networks] if isinstance(networks, int) else list(networks)
182
+ )
183
+ self.rule_type: Final = rule_type
184
+ self._rule: Optional[TRule] = None
185
+
186
+ @overload
187
+ def __call__(
188
+ self, txgroup_name: TxGroupName, networks: Optional[Networks] = None
189
+ ) -> "SortRuleFactory":
190
+ """Configure a nested sub‐group.
191
+
192
+ Args:
193
+ txgroup_name: Sub‐group name.
194
+ networks: Optional network specification.
195
+ """
196
+
197
+ @overload
198
+ def __call__(self, func: SortFunction) -> SortFunction:
199
+ """Register a matching function.
200
+
201
+ Args:
202
+ func: The custom matching function.
203
+ """
204
+
205
+ def __call__( # type: ignore [misc]
206
+ self,
207
+ func: Union[TxGroupName, SortFunction],
208
+ networks: Optional[Networks] = None,
209
+ ) -> Union["SortRuleFactory", SortFunction]:
210
+ """Configure a nested sub‐group or register a matching function.
211
+
212
+ Overloads:
213
+ * If `func` is a string, returns a new factory for `txgroup:func`.
214
+ * If `func` is callable, registers it as the match logic.
215
+
216
+ Args:
217
+ func: Sub‐group suffix (str) or a custom matching function.
218
+ networks: Optional networks override (only valid when `func` is str).
219
+
220
+ Raises:
221
+ RuntimeError: If `networks` is passed when `func` is callable.
222
+ ValueError: If `func` is neither str nor callable.
223
+
224
+ See Also:
225
+ :meth:`match`
226
+
227
+ Examples:
228
+ >>> fees = expense("Fees")
229
+ >>> @fees("Gas")
230
+ ... def match_gas(tx):
231
+ ... return tx.symbol == "ETH"
232
+ """
233
+ if isinstance(func, str):
234
+ return SortRuleFactory(
235
+ f"{self.txgroup}:{func}", networks or self.networks, self.rule_type
236
+ )
237
+ elif callable(func):
238
+ if networks:
239
+ raise RuntimeError("you can only pass networks if `func` is a string")
240
+ if CHAINID in self.networks:
241
+ self.__check_locked()
242
+ self._rule = self.rule_type(txgroup=self.txgroup, func=func)
243
+ return func
244
+ raise ValueError(func)
245
+
246
+ @property
247
+ def rule(self) -> Optional[TRule]:
248
+ """Return the created sort rule instance, if any.
249
+
250
+ After decoration or a call to :meth:`match`, this property holds the
251
+ concrete :class:`~dao_treasury.types.SortRule` instance.
252
+
253
+ Examples:
254
+ >>> @other_income("Interest")
255
+ ... def match_i(tx):
256
+ ... return tx.value_usd > 100
257
+ """
258
+ return self._rule
259
+
260
+ def match(
261
+ self, func: None = None, **match_values: Any
262
+ ) -> None: # TODO: give this proper kwargs
263
+ """Define static matching attributes for the sort rule.
264
+
265
+ Call this method with keyword matchers corresponding to rule attributes
266
+ (e.g., hash, from_address, symbol) to create a rule matching based on these values.
267
+
268
+ Args:
269
+ func: Must be None; a function match must use the decorator form.
270
+ **match_values: Attribute values for matching (e.g., hash="0x123", symbol="DAI").
271
+
272
+ Raises:
273
+ ValueError: If `func` is not None.
274
+ RuntimeError: If a matcher has already been set.
275
+
276
+ See Also:
277
+ :meth:`__call__`
278
+
279
+ Examples:
280
+ >>> ignore("Dust").match(symbol="WETH", from_address="0xAAA")
281
+ """
282
+ if func is not None:
283
+ raise ValueError(
284
+ f"You cannot pass a func here, call {self} with the function as the sole arg instead"
285
+ )
286
+ # Only instantiate when we're on an allowed network
287
+ if CHAINID in self.networks:
288
+ self.__check_locked()
289
+ self._rule = self.rule_type(txgroup=self.txgroup, **match_values)
290
+ self.locked = True
291
+
292
+ def __check_locked(self) -> None:
293
+ """Ensure that no matcher has already been registered.
294
+
295
+ Raises:
296
+ RuntimeError: If this factory already has a matcher assigned.
297
+ """
298
+ if self._rule is not None:
299
+ raise RuntimeError(f"{self} already has a matcher")
@@ -0,0 +1,346 @@
1
+ # dao_treasury/sorting/rule.py
2
+
3
+ """Module defining transaction sorting rules for the DAO treasury.
4
+
5
+ This module provides the `_SortRule` base class and subclasses for categorizing
6
+ `TreasuryTx` entries based on their attributes or a custom function. When a rule
7
+ is instantiated, it registers itself in the global `SORT_RULES` mapping under its
8
+ class and configures which transaction attributes to match via `_match_all`.
9
+
10
+ Examples:
11
+ # Define a revenue rule for sales (assuming you only transact in DAI for sales)
12
+ >>> from dao_treasury.sorting.rule import RevenueSortRule, SORT_RULES
13
+ >>> RevenueSortRule(
14
+ ... txgroup='Sale',
15
+ ... token_address='0x6B175474E89094d879c81e570a000000000000',
16
+ ... symbol='DAI'
17
+ ... )
18
+ # Inspect rules registered for RevenueSortRule
19
+ >>> len(SORT_RULES[RevenueSortRule])
20
+ 1
21
+
22
+ # Iterate over all ExpenseSortRule instances
23
+ >>> from dao_treasury.sorting.rule import ExpenseSortRule
24
+ >>> for rule in SORT_RULES[ExpenseSortRule]:
25
+ ... print(rule.txgroup)
26
+
27
+ See Also:
28
+ :const:`~dao_treasury.sorting.rule.SORT_RULES`
29
+ :class:`~dao_treasury.sorting.rule._SortRule`
30
+ """
31
+
32
+ from collections import defaultdict
33
+ from dataclasses import dataclass
34
+ from logging import getLogger
35
+ from typing import (
36
+ TYPE_CHECKING,
37
+ DefaultDict,
38
+ Dict,
39
+ Final,
40
+ List,
41
+ Optional,
42
+ Type,
43
+ TypeVar,
44
+ )
45
+
46
+ from brownie.convert.datatypes import EthAddress
47
+ from eth_typing import HexStr
48
+ from mypy_extensions import mypyc_attr
49
+
50
+ from dao_treasury._wallet import TreasuryWallet
51
+ from dao_treasury.types import SortFunction, SortRule, TxGroupDbid, TxGroupName
52
+
53
+ if TYPE_CHECKING:
54
+ from dao_treasury.db import TreasuryTx
55
+
56
+
57
+ logger: Final = getLogger(__name__)
58
+ _log_debug: Final = logger.debug
59
+
60
+ SORT_RULES: DefaultDict[Type[SortRule], List[SortRule]] = defaultdict(list)
61
+ """Mapping from sort rule classes to lists of instantiated rules, in creation order per class.
62
+
63
+ Each key is a subclass of :class:`~dao_treasury.types.SortRule` and the corresponding
64
+ value is the list of rule instances of that class.
65
+
66
+ Examples:
67
+ >>> from dao_treasury.sorting.rule import RevenueSortRule, SORT_RULES
68
+ >>> RevenueSortRule(txgroup='Interest', symbol='DAI')
69
+ >>> SORT_RULES[RevenueSortRule][0].txgroup
70
+ 'Revenue:Interest'
71
+ """
72
+
73
+ _match_all: Final[Dict[TxGroupName, List[str]]] = {}
74
+ """An internal cache defining which matcher attributes are used for each `txgroup`."""
75
+
76
+ _MATCHING_ATTRS: Final = (
77
+ "hash",
78
+ "from_address",
79
+ "from_nickname",
80
+ "to_address",
81
+ "to_nickname",
82
+ "token_address",
83
+ "symbol",
84
+ "log_index",
85
+ )
86
+
87
+
88
+ @mypyc_attr(native_class=False)
89
+ @dataclass(kw_only=True, frozen=True)
90
+ class _SortRule:
91
+ """Base class for defining transaction matching rules.
92
+
93
+ When instantiated, a rule validates its inputs, determines which transaction
94
+ attributes to match (or uses a custom function), and registers itself
95
+ in the global `SORT_RULES` mapping under its class.
96
+
97
+ Matched transactions are assigned to the specified `txgroup`.
98
+
99
+ See Also:
100
+ :const:`dao_treasury.sorting.rule.SORT_RULES`
101
+ """
102
+
103
+ txgroup: TxGroupName
104
+ """Name of the transaction group to assign upon match."""
105
+
106
+ hash: Optional[HexStr] = None
107
+ """Exact transaction hash to match."""
108
+
109
+ from_address: Optional[EthAddress] = None
110
+ """Source wallet address to match."""
111
+
112
+ from_nickname: Optional[str] = None
113
+ """Sender nickname (alias) to match."""
114
+
115
+ to_address: Optional[EthAddress] = None
116
+ """Recipient wallet address to match."""
117
+
118
+ to_nickname: Optional[str] = None
119
+ """Recipient nickname (alias) to match."""
120
+
121
+ token_address: Optional[EthAddress] = None
122
+ """Token contract address to match."""
123
+
124
+ symbol: Optional[str] = None
125
+ """Token symbol to match."""
126
+
127
+ log_index: Optional[int] = None
128
+ """Log index within the transaction receipt to match."""
129
+
130
+ func: Optional[SortFunction] = None
131
+ """Custom matching function that takes a `TreasuryTx` and returns a bool or an awaitable that returns a bool."""
132
+
133
+ # __instances__: ClassVar[List[Self]] = []
134
+
135
+ def __post_init__(self) -> None:
136
+ """Validate inputs, checksum addresses, and register the rule.
137
+
138
+ - Ensures no duplicate rule exists for the same `txgroup`.
139
+ - Converts address fields to checksummed format.
140
+ - Determines which attributes will be used for direct matching.
141
+ - Validates that exactly one of attribute-based or function-based matching is provided.
142
+ - Registers the instance in :attr:`SORT_RULES` and :data:`_match_all`.
143
+ """
144
+ if self.txgroup in _match_all:
145
+ raise ValueError(
146
+ f"there is already a matcher defined for txgroup {self.txgroup}: {self}"
147
+ )
148
+
149
+ # ensure addresses are checksummed if applicable
150
+ for attr in ["from_address", "to_address", "token_address"]:
151
+ value = getattr(self, attr)
152
+ if value is not None:
153
+ checksummed = EthAddress(value)
154
+ # NOTE: we must use object.__setattr__ to modify a frozen dataclass instance
155
+ object.__setattr__(self, attr, checksummed)
156
+
157
+ # define matchers used for this instance
158
+ matchers = [attr for attr in _MATCHING_ATTRS if getattr(self, attr) is not None]
159
+ _match_all[self.txgroup] = matchers
160
+
161
+ if self.func is not None and matchers:
162
+ raise ValueError(
163
+ "You must specify attributes for matching or pass in a custom matching function, not both."
164
+ )
165
+
166
+ if self.func is None and not matchers:
167
+ raise ValueError(
168
+ "You must specify attributes for matching or pass in a custom matching function."
169
+ )
170
+
171
+ if self.func is not None and not callable(self.func):
172
+ raise TypeError(f"func must be callable. You passed {self.func}")
173
+
174
+ # append new instance to instances classvar
175
+ # TODO: fix dataclass ClassVar handling in mypyc and reenable
176
+ # self.__instances__.append(self)
177
+
178
+ # append new instance under its class key
179
+ SORT_RULES[type(self)].append(self)
180
+
181
+ @property
182
+ def txgroup_dbid(self) -> TxGroupDbid:
183
+ """Compute the database ID for this rule's `txgroup`.
184
+
185
+ Splits the `txgroup` string on ':' and resolves or creates the hierarchical
186
+ `TxGroup` entries in the database, returning the final group ID.
187
+
188
+ See Also:
189
+ :class:`~dao_treasury.db.TxGroup`.
190
+ """
191
+ from dao_treasury.db import TxGroup
192
+
193
+ txgroup = None
194
+ for part in self.txgroup.split(":"):
195
+ txgroup = TxGroup.get_dbid(part, txgroup)
196
+ return txgroup
197
+
198
+ async def match(self, tx: "TreasuryTx") -> bool:
199
+ """Determine if the given transaction matches this rule.
200
+
201
+ Args:
202
+ tx: A `TreasuryTx` entity to test against this rule.
203
+
204
+ Returns:
205
+ True if the transaction matches the rule criteria; otherwise False.
206
+
207
+ Examples:
208
+ # match by symbol and recipient
209
+ >>> rule = _SortRule(txgroup='Foo', symbol='DAI', to_address='0xabc...')
210
+ >>> await rule.match(tx) # where tx.symbol == 'DAI' and tx.to_address == '0xabc...'
211
+ True
212
+
213
+ See Also:
214
+ :attr:`_match_all`
215
+ """
216
+ if matchers := _match_all[self.txgroup]:
217
+ return all(
218
+ getattr(tx, matcher) == getattr(self, matcher) for matcher in matchers
219
+ )
220
+
221
+ _log_debug("checking %s for %s", tx, self.func)
222
+ match = self.func(tx) # type: ignore [misc]
223
+ return match if isinstance(match, bool) else await match
224
+
225
+
226
+ @mypyc_attr(native_class=False)
227
+ class _InboundSortRule(_SortRule):
228
+ """Sort rule that applies only to inbound transactions (to the DAO's wallet).
229
+
230
+ Checks that the transaction's `to_address` belongs to a known `TreasuryWallet`
231
+ before applying the base matching logic.
232
+ """
233
+
234
+ async def match(self, tx: "TreasuryTx") -> bool:
235
+ return (
236
+ tx.to_address is not None
237
+ and TreasuryWallet.check_membership(tx.to_address.address, tx.block)
238
+ and await super().match(tx)
239
+ )
240
+
241
+
242
+ @mypyc_attr(native_class=False)
243
+ class _OutboundSortRule(_SortRule):
244
+ """Sort rule that applies only to outbound transactions (from the DAO's wallet).
245
+
246
+ Checks that the transaction's `from_address` belongs to a known `TreasuryWallet`
247
+ before applying the base matching logic.
248
+ """
249
+
250
+ async def match(self, tx: "TreasuryTx") -> bool:
251
+ return TreasuryWallet.check_membership(
252
+ tx.from_address.address, tx.block
253
+ ) and await super().match(tx)
254
+
255
+
256
+ @mypyc_attr(native_class=False)
257
+ class RevenueSortRule(_InboundSortRule):
258
+ """Rule to categorize inbound transactions as revenue.
259
+
260
+ Prepends 'Revenue:' to the `txgroup` name before registration.
261
+
262
+ Examples:
263
+ >>> RevenueSortRule(txgroup='Sale', to_address='0xabc...', symbol='DAI')
264
+ # results in a rule with txgroup 'Revenue:Sale'
265
+ """
266
+
267
+ def __post_init__(self) -> None:
268
+ """Prepends `self.txgroup` with 'Revenue:'."""
269
+ object.__setattr__(self, "txgroup", f"Revenue:{self.txgroup}")
270
+ super().__post_init__()
271
+
272
+
273
+ @mypyc_attr(native_class=False)
274
+ class CostOfRevenueSortRule(_OutboundSortRule):
275
+ """Rule to categorize outbound transactions as cost of revenue.
276
+
277
+ Prepends 'Cost of Revenue:' to the `txgroup` name before registration.
278
+ """
279
+
280
+ def __post_init__(self) -> None:
281
+ """Prepends `self.txgroup` with 'Cost of Revenue:'."""
282
+ object.__setattr__(self, "txgroup", f"Cost of Revenue:{self.txgroup}")
283
+ super().__post_init__()
284
+
285
+
286
+ @mypyc_attr(native_class=False)
287
+ class ExpenseSortRule(_OutboundSortRule):
288
+ """Rule to categorize outbound transactions as expenses.
289
+
290
+ Prepends 'Expenses:' to the `txgroup` name before registration.
291
+ """
292
+
293
+ def __post_init__(self) -> None:
294
+ """Prepends `self.txgroup` with 'Expenses:'."""
295
+ object.__setattr__(self, "txgroup", f"Expenses:{self.txgroup}")
296
+ super().__post_init__()
297
+
298
+
299
+ @mypyc_attr(native_class=False)
300
+ class OtherIncomeSortRule(_InboundSortRule):
301
+ """Rule to categorize inbound transactions as other income.
302
+
303
+ Prepends 'Other Income:' to the `txgroup` name before registration.
304
+ """
305
+
306
+ def __post_init__(self) -> None:
307
+ """Prepends `self.txgroup` with 'Other Income:'."""
308
+ object.__setattr__(self, "txgroup", f"Other Income:{self.txgroup}")
309
+ super().__post_init__()
310
+
311
+
312
+ @mypyc_attr(native_class=False)
313
+ class OtherExpenseSortRule(_OutboundSortRule):
314
+ """Rule to categorize outbound transactions as other expenses.
315
+
316
+ Prepends 'Other Expenses:' to the `txgroup` name before registration.
317
+ """
318
+
319
+ def __post_init__(self) -> None:
320
+ """Prepends `self.txgroup` with 'Other Expenses:'."""
321
+ object.__setattr__(self, "txgroup", f"Other Expenses:{self.txgroup}")
322
+ super().__post_init__()
323
+
324
+
325
+ @mypyc_attr(native_class=False)
326
+ class IgnoreSortRule(_SortRule):
327
+ """Rule to ignore certain transactions.
328
+
329
+ Prepends 'Ignore:' to the `txgroup` name before registration.
330
+ """
331
+
332
+ def __post_init__(self) -> None:
333
+ """Prepends `self.txgroup` with 'Ignore:'."""
334
+ object.__setattr__(self, "txgroup", f"Ignore:{self.txgroup}")
335
+ super().__post_init__()
336
+
337
+
338
+ TRule = TypeVar(
339
+ "TRule",
340
+ RevenueSortRule,
341
+ CostOfRevenueSortRule,
342
+ ExpenseSortRule,
343
+ OtherIncomeSortRule,
344
+ OtherExpenseSortRule,
345
+ IgnoreSortRule,
346
+ )
@@ -0,0 +1 @@
1
+ from dao_treasury.sorting.rules.ignore import *
@@ -0,0 +1 @@
1
+ from dao_treasury.sorting.rules.ignore.llamapay import *
@@ -0,0 +1,20 @@
1
+ from dao_treasury import TreasuryTx
2
+ from dao_treasury.sorting.factory import ignore
3
+ from dao_treasury.streams import llamapay
4
+
5
+
6
+ @ignore("LlamaPay")
7
+ def is_llamapay_stream_replenishment(tx: TreasuryTx) -> bool:
8
+ if tx.to_address.address in llamapay.factories: # type: ignore [operator]
9
+ # We amortize these streams daily in the `llamapay` module, you'll sort each stream appropriately.
10
+ return True
11
+
12
+ # NOTE: not sure if we want this yet
13
+ # Puling unused funds back from vesting escrow / llamapay
14
+ # elif tx.from_address == "Contract: LlamaPay" and "StreamCancelled" in tx.events:
15
+ # if tx.amount > 0:
16
+ # tx.amount *= -1
17
+ # if tx.value_usd > 0:
18
+ # tx.value_usd *= -1
19
+ # return True
20
+ return False
File without changes