### Start DAO Treasury with Renderer Container (CLI) Source: https://github.com/bobthebuidler/dao-treasury/blob/master/docs/index.md Command to run the DAO Treasury CLI and explicitly start the Grafana renderer container using the `--start-renderer` flag. This enables dashboard image export. ```bash dao-treasury run --wallet 0x123 --network mainnet --start-renderer ``` -------------------------------- ### Example YAML Wallet Configuration Source: https://github.com/bobthebuidler/dao-treasury/blob/master/docs/wallets.md An example of a YAML file used for advanced treasury wallet configuration. It shows how to set networks, start/end timestamps, and chain-specific block numbers for multiple wallet addresses. ```yaml 0xABCDEF0123456789ABCDEF0123456789ABCDEF01: networks: - 1 - 420 start: timestamp: 1600000000 1: block: 1000000 end: timestamp: 1700000000 1: block: 2000000 0x1234567890ABCDEF1234567890ABCDEF12345678: start: timestamp: 1610000000 end: 1: block: 2500000 # Bare address entry (default settings, all chains) 0xAAAABBBBCCCCDDDDEEEEFFFF0000111122223333: ``` -------------------------------- ### Install DAO Treasury Package Source: https://github.com/bobthebuidler/dao-treasury/blob/master/README.md Installs the DAO Treasury Python package using pip. This command fetches and installs the latest version of the library and its dependencies from the Python Package Index (PyPI). Ensure you have Python 3.10 or higher and pip installed. ```bash pip install dao-treasury ``` -------------------------------- ### Run DAO Treasury CLI (poetry installation) Source: https://github.com/bobthebuidler/dao-treasury/blob/master/README.md This command executes the DAO Treasury data export process when installed via poetry. It requires a wallet address and network specification, with an optional interval for data snapshots. ```bash poetry run dao-treasury run --wallet 0x123 --network mainnet --interval 12h ``` -------------------------------- ### Run DAO Treasury CLI (pip installation) Source: https://github.com/bobthebuidler/dao-treasury/blob/master/README.md This command executes the DAO Treasury data export process using a pip installation. It requires a wallet address and network specification, with an optional interval for data snapshots. ```bash dao-treasury run --wallet 0x123 --network mainnet --interval 12h ``` -------------------------------- ### Service Orchestration with Docker Compose Source: https://github.com/bobthebuidler/dao-treasury/blob/master/cline_docs/systemPatterns.md Example of orchestrating services like Grafana using Docker Compose. This pattern facilitates environment setup, dependency management, and consistent deployment across different environments. ```yaml services: grafana: image: grafana/grafana:10.2.0 ports: - 127.0.0.1:3004:3000 environment: - GF_AUTH_ANONYMOUS_ENABLED=true volumes: - grafana_data:/var/lib/grafana ``` -------------------------------- ### Load Wallets from YAML Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Loads wallet configurations from a YAML file. It takes a Path object pointing to the YAML file as input and returns a list of wallet objects. Each wallet object contains details such as address, start block, and associated networks. ```python # Load wallets from YAML file from pathlib import Path from dao_treasury._wallet import load_wallets_from_yaml wallets = load_wallets_from_yaml(Path("./treasury-wallets.yaml")) for wallet in wallets: print(f"Wallet: {wallet.address}, Start: {wallet._start_block}, Networks: {wallet.networks}") ``` -------------------------------- ### YAML Configuration for Wallets Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Define complex wallet configurations using YAML files for multi-wallet and multi-chain DAO treasury setups. This allows for detailed specification of wallet addresses, network constraints, and time-based activation periods. ```yaml # treasury-wallets.yaml ``` -------------------------------- ### Testing with pytest Source: https://github.com/bobthebuidler/dao-treasury/blob/master/cline_docs/systemPatterns.md A basic example of a test function using the pytest framework. This pattern emphasizes the use of pytest for comprehensive unit and integration testing, ensuring code quality and reliability. ```python def test_wallet_balance(): # Test logic here assert ... ``` -------------------------------- ### Database Models: Address and Token Management (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Defines and demonstrates the usage of database models for storing on-chain addresses and ERC-20 token metadata. It includes functionalities for getting or creating records, setting and batching custom nicknames for addresses, and retrieving token details with value scaling. Requires the 'pony' ORM and 'dao_treasury.db' module. ```python from pony.orm import db_session from dao_treasury.db import Address, Token, init_db init_db() with db_session: # Get or create an address record addr = Address.get_or_insert("0x6B175474E89094C44Da98b954EedeAC495271d0F") print(f"Address: {addr.address}") print(f"Is contract: {addr.is_contract}") print(f"Nickname: {addr.nickname}") # Set a custom nickname Address.set_nickname( "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD4c", "Treasury Multisig" ) # Batch set nicknames Address.set_nicknames({ "0xAAA...": "Operations Wallet", "0xBBB...": "Grants Wallet", "0xCCC...": "Investment Wallet" }) # Get or create a token record dai = Token.get_or_insert("0x6B175474E89094C44Da98b954EedeAC495271d0F") print(f"Symbol: {dai.symbol}") # Output: DAI print(f"Name: {dai.name}") # Output: Dai Stablecoin print(f"Decimals: {dai.decimals}") # Output: 18 # Scale a raw token amount raw_amount = 1500000000000000000 # 1.5 DAI in wei scaled = dai.scale_value(raw_amount) print(f"Scaled amount: {scaled}") # Output: Decimal('1.5') ``` -------------------------------- ### Manage Hierarchical Transaction Grouping (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Demonstrates the management of hierarchical transaction grouping using Pony ORM. It shows how to get or create top-level categories and nested subcategories, and how to retrieve full hierarchical names and child groups. ```python from pony.orm import db_session from dao_treasury.db import TxGroup, init_db init_db() with db_session: # Get or create top-level categories revenue_id = TxGroup.get_dbid("Revenue") expenses_id = TxGroup.get_dbid("Expenses") # Create nested subcategories sales_id = TxGroup.get_dbid("Sales", parent=revenue_id) subscriptions_id = TxGroup.get_dbid("Subscriptions", parent=revenue_id) # Get full hierarchical name sales_group = TxGroup[sales_id] print(f"Full name: {sales_group.fullname}") # Output: "Revenue:Sales" # Get top-level category print(f"Top category: {sales_group.top_txgroup.name}") # Output: "Revenue" # List all child groups revenue_group = TxGroup[revenue_id] for child in revenue_group.child_txgroups: print(f"Subcategory: {child.name}") ``` -------------------------------- ### Run DAO Treasury CLI Commands Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Execute the DAO Treasury CLI for continuous data collection and dashboard visualization. Supports single/multiple wallets, custom categorization buckets, network specification, and Grafana port configuration. Advanced options include using YAML for wallet configurations and enabling image rendering. ```bash # Basic usage with a single wallet dao-treasury run --wallet 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD4c --network mainnet --interval 12h # Multiple wallets with custom buckets for categorization dao-treasury run \ --wallet 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD4c \ --wallet 0x8B3392483BA26D65E331dB86D4F430E9B3814E5e \ --network mainnet \ --interval 6h \ --custom-bucket "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD4c:Operations" \ --custom-bucket "0x8B3392483BA26D65E331dB86D4F430E9B3814E5e:Grants" \ --grafana-port 3004 \ --sort-rules ./rules # Using advanced wallet configuration from YAML dao-treasury run --wallets ./treasury-wallets.yaml --network mainnet --interval 1d # With renderer for dashboard image export dao-treasury run --wallet 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD4c --start-renderer --renderer-port 8091 ``` -------------------------------- ### Static Attribute Matching via Factory (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Demonstrates static attribute matching using a factory pattern for 'ignore' and 'expense' rules. It ignores transactions from a known spam address and categorizes expenses from a specific office address with USDC. ```python ignore("Known Spam").match(from_address="0xSPAM000...") expense("Office").match(to_address="0xOFFICE000...", symbol="USDC") ``` -------------------------------- ### Specify Wallets using CLI Flag Source: https://github.com/bobthebuidler/dao-treasury/blob/master/docs/wallets.md This command demonstrates the simple mode for specifying one or more treasury wallet addresses directly via the --wallet flag in the DAO Treasury CLI. ```bash dao-treasury --wallet 0xABC... 0xDEF... ``` -------------------------------- ### Environment Variables Configuration (Bash) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Configures environment variables for the DAO Treasury project, including database connection details (user, password, name, host, port), Grafana and renderer ports, SQL debugging enablement, Etherscan API token, and Brownie network ID. These variables control the application's behavior and external service integrations. ```bash # Database configuration export DAO_TREASURY_DB_USER="dao_treasury" export DAO_TREASURY_DB_PASSWORD="dao_treasury" export DAO_TREASURY_DB_NAME="dao_treasury" export DAO_TREASURY_DB_HOST="127.0.0.1" export DAO_TREASURY_DB_PORT="8675" # Grafana and renderer ports (also configurable via CLI) export DAO_TREASURY_GRAFANA_PORT="3004" export DAO_TREASURY_RENDERER_PORT="8091" # Enable SQL debugging export DAO_TREASURY_SQL_DEBUG="true" # Required: Etherscan API token for contract verification export ETHERSCAN_TOKEN="your-etherscan-api-key" # Brownie network configuration export BROWNIE_NETWORK_ID="mainnet" ``` -------------------------------- ### Initialize and Use Treasury Class in Python Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Instantiate the Treasury class to manage DAO wallets, transaction ingestion, sorting rules, and database population. This class orchestrates treasury analytics and can operate asynchronously. It allows fetching balances and populating the database within specified block ranges. ```python from pathlib import Path from dao_treasury import Treasury, TreasuryWallet # Initialize Treasury with wallet addresses and optional sort rules treasury = Treasury( wallets=[ "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD4c", TreasuryWallet( address="0x8B3392483BA26D65E331dB86D4F430E9B3814E5e", start_block=15000000, end_block=17000000 ), ], sort_rules=Path("./rules"), start_block=14000000, label="My DAO Treasury", custom_buckets={ "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD4c": "Operations", "0x8B3392483BA26D65E331dB86D4F430E9B3814E5e": "Grants" }, asynchronous=True ) # Get portfolio balances at a specific block import asyncio async def get_treasury_state(): balances = await treasury.describe(block=17500000) print(f"Treasury balances: {balances}") # Populate database with transactions from block range await treasury.populate_db(start_block=14000000, end_block=17500000) asyncio.run(get_treasury_state()) ``` -------------------------------- ### Assign Custom Buckets to Wallets via CLI Source: https://github.com/bobthebuidler/dao-treasury/blob/master/README.md This command demonstrates how to assign custom categories (buckets) to specific wallet addresses for granular reporting using the `--custom-bucket` option. Multiple buckets can be assigned. ```bash dao-treasury run --wallet 0x123 --network mainnet --custom-bucket "0x123:Operations" --custom-bucket "0x456:Grants" --custom-bucket "0x789:Investments" ``` -------------------------------- ### Query Treasury Transactions by Category (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Demonstrates querying treasury transactions by category using Pony ORM. It initializes the database, retrieves the 'Revenue' group ID, and selects the top 10 revenue transactions, printing their details. ```python from pony.orm import db_session, select from dao_treasury.db import TreasuryTx, TxGroup, Token, Address, init_db # Initialize the database connection init_db() # Query transactions by category with db_session: revenue_group_id = TxGroup.get_dbid("Revenue") revenue_txs = select( tx for tx in TreasuryTx if tx.txgroup.top_txgroup.txgroup_id == revenue_group_id ) for tx in revenue_txs.limit(10): print(f"Hash: {tx.hash}") print(f"Amount: {tx.amount} {tx.symbol}") print(f"USD Value: ${tx.value_usd}") print(f"From: {tx.from_nickname or tx.from_address.address}") print(f"To: {tx.to_nickname or tx.to_address.address}") print(f"Category: {tx.txgroup.fullname}") print("---") ``` -------------------------------- ### Insert a Ledger Entry into TreasuryTx (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Shows how to insert a ledger entry into the 'TreasuryTx' table using an asynchronous function. It utilizes the 'TokenTransfer' struct from 'eth_portfolio.structs' to represent the transaction data. ```python import asyncio from eth_portfolio.structs import TokenTransfer async def insert_transaction(): entry = TokenTransfer( block_number=17500000, transaction_hash="0xabc123...", log_index=0, token_address="0x6B175474E89094C44Da98b954EedeAC495271d0F", # DAI from_address="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD4c", to_address="0x8B3392483BA26D65E331dB86D4F430E9B3814E5e", value=1000000000000000000000, # 1000 DAI price=1.0, value_usd=1000.0 ) await TreasuryTx.insert(entry) asyncio.run(insert_transaction()) ``` -------------------------------- ### Streaming Payments: LlamaPay Support (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Provides support for streaming payment protocols, specifically LlamaPay, with daily aggregation and categorization. Demonstrates querying active streams and retrieving streamed funds for a specific date. Requires 'pony' ORM, 'dao_treasury.db' module, 'datetime', and 'decimal' libraries. ```python from pony.orm import db_session from dao_treasury.db import Stream, StreamedFunds, init_db from datetime import date from decimal import Decimal init_db() with db_session: # Query active streams active_streams = Stream.select(lambda s: s.status == "Active") for stream in active_streams: print(f"Stream ID: {stream.stream_id}") print(f"Token: {stream.token.symbol}") print(f"From: {stream.from_address.address}") print(f"To: {stream.to_address.address}") print(f"Amount per day: {stream.amount_per_day / stream.scale}") print(f"Status: {stream.status}") print(f"Start date: {stream.start_date}") print("---") # Query streamed funds for a specific date funds = StreamedFunds.select( lambda sf: sf.date == date(2024, 1, 15) ) for fund in funds: print(f"Stream: {fund.stream.stream_id}") print(f"Amount: {fund.amount}") print(f"Price: {fund.price}") print(f"USD Value: {fund.value_usd}") print(f"Seconds active: {fund.seconds_active}") ``` -------------------------------- ### Enable y.stuck? Debugger in Python Source: https://github.com/bobthebuidler/dao-treasury/blob/master/CONTRIBUTING.md This Python code snippet demonstrates how to enable the DEBUG logger for 'y.stuck?'. This is useful for debugging slow price lookups in the DAO Treasury project, as it logs messages when long-running async calls exceed a certain duration. ```python import logging logging.basicConfig(level=logging.INFO) logging.getLogger("y.stuck?").setLevel(logging.DEBUG) ``` -------------------------------- ### SortRuleFactory Decorators for Custom Rules Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Provides factory functions for creating sort rules using decorator syntax. These decorators allow for complex categorization logic, enabling custom matching for various transaction types like revenue, expenses, other income, other expenses, ignored transactions, and cost of revenue. ```python from dao_treasury import revenue, expense, other_income, other_expense, ignore, cost_of_revenue ``` -------------------------------- ### Enable External Sort Rules via CLI Source: https://github.com/bobthebuidler/dao-treasury/blob/master/docs/sort_rules.md Shows the command-line interface command to enable and apply external sort rules defined in YAML files. The `--sort-rules` flag specifies the directory containing the rule definitions. ```bash dao-treasury --sort-rules ./rules … ``` -------------------------------- ### Directory Structure for Sort Rules Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Illustrates the directory structure for organizing external sort rules. Rules are categorized by transaction type (revenue, expense, etc.) and further by matching criteria (e.g., to_address, hash, from_address). ```text # Directory structure for sort rules rules/ ├── revenue/ │ ├── match_on_hash.yaml │ ├── match_on_to_address.yaml │ └── match_on_from_address.yaml ├── expense/ │ └── match_on_to_address.yaml ├── cost_of_revenue/ │ └── match_on_to_address.yaml ├── other_income/ │ └── match_on_hash.yaml ├── other_expense/ │ └── match_on_to_address.yaml └── ignore/ └── match_on_from_address.yaml ``` -------------------------------- ### Define Revenue Rule with Async Matching Function (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Defines a revenue rule named 'Token Sales' that applies to Mainnet and Optimism networks. It uses an asynchronous matching function 'match_large_sales' to identify transactions where the USD value exceeds 1000 and the symbol is 'DAI'. ```python @revenue("Token Sales", networks=[1, 10]) # Mainnet and Optimism async def match_large_sales(tx): return tx.value_usd > 1000 and tx.symbol == "DAI" ``` -------------------------------- ### Define TreasuryWallet Instances in Python Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Create TreasuryWallet objects to represent individual DAO wallets. These objects can specify address, block-based or timestamp-based time ranges, and network filters for multi-chain support. The class also provides a static method to check wallet membership at a given block. ```python from dao_treasury import TreasuryWallet # Basic wallet - active from genesis on all networks wallet1 = TreasuryWallet(address="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD4c") # Wallet with block-based time range wallet2 = TreasuryWallet( address="0x8B3392483BA26D65E331dB86D4F430E9B3814E5e", start_block=15000000, end_block=17000000 ) # Wallet with timestamp-based time range wallet3 = TreasuryWallet( address="0xABCDEF0123456789ABCDEF0123456789ABCDEF01", start_timestamp=1640000000, # Unix timestamp end_timestamp=1700000000 ) # Multi-chain wallet (only active on Ethereum mainnet and Optimism) wallet4 = TreasuryWallet( address="0x1234567890ABCDEF1234567890ABCDEF12345678", networks=[1, 10], # Chain IDs: 1=Mainnet, 10=Optimism start_block=15000000 ) # Check if an address is a treasury member at a specific block is_member = TreasuryWallet.check_membership( address="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD4c", block=16000000 ) print(f"Is treasury member: {is_member}") # Output: True ``` -------------------------------- ### YAML Configuration for Treasury Wallets Source: https://github.com/bobthebuidler/dao-treasury/blob/master/docs/wallets.md This YAML structure defines advanced configuration for treasury wallets, mapping addresses to network-specific settings, start/end timestamps, and block numbers. It supports universal timestamps and chain-specific block configurations. ```yaml address: networks: - - start: timestamp: : block: end: timestamp: : block: ``` -------------------------------- ### YAML Configuration for Revenue Rules Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt External YAML configuration for revenue rules, mapping chain IDs to categories and lists of addresses. This allows for declarative transaction categorization without code changes. ```yaml # rules/revenue/match_on_to_address.yaml # Chain ID -> Category -> List of addresses 1: TokenSales: - "0xSALES00000000000000000000000000000000001" - "0xSALES00000000000000000000000000000000002" Subscriptions: - "0xSUBS000000000000000000000000000000000001" 10: # Optimism TokenSales: - "0xOPSALES0000000000000000000000000000001" ``` -------------------------------- ### Define Cost of Revenue Rule (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Defines a 'Cost of Revenue' rule for 'Infrastructure'. It uses an asynchronous matching function 'match_infra_costs' to identify transactions where the 'to_nickname' attribute contains the substring 'Server'. ```python @cost_of_revenue("Infrastructure") async def match_infra_costs(tx): return tx.to_nickname and "Server" in tx.to_nickname ``` -------------------------------- ### YAML Configuration for Other Income Rules Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt External YAML configuration for other income rules, mapping chain IDs to categories and transaction hashes. This enables declarative categorization of income sources like grants and airdrops. ```yaml # rules/other_income/match_on_hash.yaml 1: GrantReceived: - "0xgranttx123456789abcdef..." - "0xgranttx987654321fedcba..." AirdropClaim: - "0xairdrop123456789..." ``` -------------------------------- ### Sorting Rule Definition in YAML Source: https://github.com/bobthebuidler/dao-treasury/blob/master/cline_docs/systemPatterns.md Illustrates a sorting rule defined in YAML format. This pattern allows for dynamic loading and extensibility of sorting logic, separating configuration from core code. ```yaml 1: DonationReceived: - 0xabc123... ``` -------------------------------- ### Register Custom Async Matcher with Revenue Factory Decorator Source: https://github.com/bobthebuidler/dao-treasury/blob/master/docs/sort_rules.md Registers an asynchronous custom matching function for revenue transactions using the @revenue decorator. The function `match_large_sales` will be invoked for transactions on specified networks if their USD value exceeds 1000. ```python from dao_treasury.sorting.factory import revenue @revenue("Token Sales", networks=[1, 3]) async def match_large_sales(tx): return tx.value_usd > 1000 ``` -------------------------------- ### Define Other Income Rule (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Defines an 'Other Income' rule for 'Interest'. It uses an asynchronous matching function 'match_interest' to identify transactions where the 'from_nickname' attribute contains the substring 'Aave'. ```python @other_income("Interest") async def match_interest(tx): return tx.from_nickname and "Aave" in tx.from_nickname ``` -------------------------------- ### Define Revenue Sort Rule with Static Attributes Source: https://github.com/bobthebuidler/dao-treasury/blob/master/docs/sort_rules.md Creates a RevenueSortRule instance to match inbound transactions based on specific attributes like token symbol and recipient address. This rule is registered under a composite key 'Revenue:Sales' and categorizes matching transactions. ```python from dao_treasury.sorting.rule import RevenueSortRule # Create a revenue rule that matches inbound transactions where the token symbol # is 'DAI' and the to_address is '0xAbC1230000000000000000000000000000000000' # The rule is registered under a composite key "Revenue:Sales" # Once defined, all transactions matching this rule will be sorted to group # "Revenue:Sales" in the db rule = RevenueSortRule( txgroup='Sales', symbol='DAI', to_address='0xAbC1230000000000000000000000000000000000' ) ``` -------------------------------- ### YAML Configuration for Ignore Rules Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt External YAML configuration for ignore rules, mapping chain IDs to categories and sender addresses. This is used to declaratively filter out spam or internal transfer transactions. ```yaml # rules/ignore/match_on_from_address.yaml 1: Spam: - "0xSPAM0000000000000000000000000000000001" - "0xSPAM0000000000000000000000000000000002" InternalTransfer: - "0xINTERNAL000000000000000000000000000001" ``` -------------------------------- ### Data Model Definition with Pony ORM Source: https://github.com/bobthebuidler/dao-treasury/blob/master/cline_docs/systemPatterns.md Defines a data model entity for treasury transactions using Pony ORM. This pattern ensures structured data persistence and allows for easy extension of database schemas. ```python class TreasuryTx(db.Entity): _table_ = "treasury_txs" treasury_tx_id = PrimaryKey(int, auto=True) # ... other fields ... ``` -------------------------------- ### Define Ignore Rule for Dust Transactions (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Defines an 'Ignore' rule named 'Dust'. It uses a synchronous matching function 'match_dust' to identify transactions where the absolute USD value is less than 0.01. ```python @ignore("Dust") def match_dust(tx): return abs(tx.value_usd or 0) < 0.01 ``` -------------------------------- ### Define Revenue Sort Rule Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Categorizes inbound transactions as revenue. This rule can match revenue based on token symbol and recipient address, sender address, or a specific transaction hash. When registered, it automatically prefixes the txgroup with 'Revenue:'. ```python from dao_treasury import RevenueSortRule # Match revenue by token symbol and recipient address RevenueSortRule( txgroup='Sales', symbol='DAI', to_address='0xAbC1230000000000000000000000000000000000' ) # Registered as "Revenue:Sales" in the database # Match revenue by sender address RevenueSortRule( txgroup='Subscriptions', from_address='0xDEF4560000000000000000000000000000000000' ) # Registered as "Revenue:Subscriptions" # Match revenue by specific transaction hash RevenueSortRule( txgroup='One-Time Payment', hash='0xabc123def456789...' ) ``` -------------------------------- ### Define Expense Rule with Nested Category (Python) Source: https://context7.com/bobthebuidler/dao-treasury/llms.txt Defines an expense rule categorized under 'Fees'. It includes two sub-rules: 'Gas' for matching ETH transactions to null addresses and 'Protocol' for matching transactions to contracts with a 'fee' attribute and a value greater than 100 USD. ```python fees = expense("Fees") @fees("Gas") def match_gas_fees(tx): return tx.symbol == "ETH" and tx.to_address is None @fees("Protocol") async def match_protocol_fees(tx): contract = await tx.to_address.contract_coro return hasattr(contract, 'fee') and tx.value_usd > 100 ```