castle/CLAUDE.md
padreug 5e67ce562b Adds CLAUDE.md to guide Claude Code
Creates a markdown file to provide guidance to Claude Code (claude.ai/code) when interacting with the Castle Accounting codebase.

This document outlines project overview, architecture, key files, database schema, transaction flows, API endpoints, development notes, and other crucial information. It aims to improve Claude's ability to understand and assist with code-related tasks.
2025-11-01 23:22:25 +01:00

11 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Castle Accounting is a double-entry bookkeeping extension for LNbits that enables collectives (co-living spaces, makerspaces, community projects) to track finances with proper accounting principles. It integrates Lightning Network payments with traditional accounting, supporting both cryptocurrency and fiat currency tracking.

Architecture

Core Design Principles

Double-Entry Accounting: Every transaction affects at least two accounts. Debits must equal credits. Five account types: Assets, Liabilities, Equity, Revenue (Income), Expenses.

Pure Functional Core: The core/ directory contains pure accounting logic independent of the database layer:

  • core/balance.py - Balance calculation from journal entries
  • core/inventory.py - Multi-currency position tracking (similar to Beancount's Inventory)
  • core/validation.py - Entry validation rules

Account Hierarchy: Beancount-style hierarchical naming with : separators:

  • Assets:Lightning:Balance
  • Assets:Receivable:User-af983632
  • Liabilities:Payable:User-af983632
  • Expenses:Food:Supplies

Metadata System: Each entry_line stores JSON metadata preserving original fiat amounts. Critical: fiat balances are calculated by summing fiat_amount from metadata, NOT by converting current satoshi balances. This prevents exchange rate fluctuations from affecting historical records.

Key Files

  • models.py - Pydantic models for API I/O and data structures
  • crud.py - Database operations (create/read/update accounts, journal entries)
  • views_api.py - FastAPI endpoints for all operations
  • views.py - Web interface routing
  • services.py - Settings management layer
  • migrations.py - Database schema migrations
  • tasks.py - Background tasks (daily reconciliation checks)
  • account_utils.py - Hierarchical account naming utilities

Database Schema

accounts: Chart of accounts with hierarchical names

  • user_id field for per-user accounts (Receivable, Payable, Equity)
  • Indexed on user_id and account_type

journal_entries: Transaction headers

  • flag field: * (cleared), ! (pending), # (flagged), x (void)
  • meta field: JSON storing source, tags, audit info
  • reference field: Links to payment_hash, invoice numbers, etc.

entry_lines: Individual debit/credit lines

  • Always balanced (sum of debits = sum of credits per entry)
  • metadata field stores fiat currency info as JSON
  • Indexed on journal_entry_id and account_id

balance_assertions: Reconciliation checkpoints (Beancount-style)

  • Assert expected balance at a date
  • Status: pending, passed, failed
  • Used for daily reconciliation checks

extension_settings: Castle wallet configuration (admin-only)

user_wallet_settings: Per-user wallet configuration

manual_payment_requests: User requests for cash/manual payments

Transaction Flows

User Adds Expense (Liability)

User pays cash for groceries, Castle owes them:

DR Expenses:Food              39,669 sats
   CR Liabilities:Payable:User-af983632    39,669 sats

Metadata preserves: {"fiat_currency": "EUR", "fiat_amount": "36.93", "fiat_rate": "1074.192"}

Castle Adds Receivable

User owes Castle for accommodation:

DR Assets:Receivable:User-af983632    268,548 sats
   CR Income:Accommodation                    268,548 sats

User Pays with Lightning

Invoice generated on Castle's wallet (not user's). After payment:

DR Assets:Lightning:Balance           268,548 sats
   CR Assets:Receivable:User-af983632         268,548 sats

Manual Payment Approval

User requests cash payment → Admin approves → Journal entry created:

DR Liabilities:Payable:User-af983632   39,669 sats
   CR Assets:Lightning:Balance                39,669 sats

Balance Calculation Logic

User Balance:

  • Positive = Castle owes user (LIABILITY accounts have credit balance)
  • Negative = User owes Castle (ASSET accounts have debit balance)
  • Calculated from sum of all entry lines across user's accounts
  • Fiat balances summed from metadata, NOT converted from sats

Perspective-Based UI:

  • User View: Green = Castle owes them, Red = They owe Castle
  • Castle Admin View: Green = User owes Castle, Red = Castle owes user

API Endpoints

Accounts

  • GET /api/v1/accounts - List all accounts
  • POST /api/v1/accounts - Create account (admin)
  • GET /api/v1/accounts/{id}/balance - Get account balance

Journal Entries

  • POST /api/v1/entries/expense - User adds expense (creates liability or equity)
  • POST /api/v1/entries/receivable - Admin records what user owes (admin only)
  • POST /api/v1/entries/revenue - Admin records direct revenue (admin only)
  • GET /api/v1/entries/user - Get user's journal entries
  • POST /api/v1/entries - Create raw journal entry (admin only)

Payments & Balances

  • GET /api/v1/balance - Get user balance (or Castle total if super user)
  • GET /api/v1/balances/all - Get all user balances (admin, enriched with usernames)
  • POST /api/v1/generate-payment-invoice - Generate invoice for user to pay Castle
  • POST /api/v1/record-payment - Record Lightning payment from user to Castle
  • POST /api/v1/settle-receivable - Manually settle receivable (cash/bank)
  • POST /api/v1/pay-user - Castle pays user (cash/bank/lightning)

Manual Payment Requests

  • POST /api/v1/manual-payment-requests - User requests payment
  • GET /api/v1/manual-payment-requests - User's requests
  • GET /api/v1/manual-payment-requests/all - All requests (admin)
  • POST /api/v1/manual-payment-requests/{id}/approve - Approve (admin)
  • POST /api/v1/manual-payment-requests/{id}/reject - Reject (admin)

Reconciliation

  • POST /api/v1/assertions/balance - Create balance assertion
  • GET /api/v1/assertions/balance - List balance assertions
  • POST /api/v1/assertions/balance/{id}/check - Check assertion
  • POST /api/v1/tasks/daily-reconciliation - Run daily reconciliation (admin)

Settings

  • GET /api/v1/settings - Get Castle settings (super user)
  • PUT /api/v1/settings - Update Castle settings (super user)
  • GET /api/v1/user/wallet - Get user wallet settings
  • PUT /api/v1/user/wallet - Update user wallet settings

Development Notes

Testing Entry Creation

When creating journal entries programmatically, use the helper endpoints:

  • POST /api/v1/entries/expense for user expenses (handles account creation automatically)
  • POST /api/v1/entries/receivable for what users owe
  • POST /api/v1/entries/revenue for direct revenue

For custom entries, use POST /api/v1/entries with properly balanced lines.

User Account Management

User-specific accounts are created automatically with format:

  • Assets: Assets:Receivable:User-{user_id[:8]}
  • Liabilities: Liabilities:Payable:User-{user_id[:8]}
  • Equity: Equity:MemberEquity:User-{user_id[:8]}

Use get_or_create_user_account() in crud.py to ensure consistency.

Currency Handling

CRITICAL: Use Decimal for all fiat amounts, never float. Fiat amounts are stored in metadata as strings to preserve precision:

from decimal import Decimal

metadata = {
    "fiat_currency": "EUR",
    "fiat_amount": str(Decimal("250.00")),
    "fiat_rate": str(Decimal("1074.192")),
    "btc_rate": str(Decimal("0.000931"))
}

When reading: fiat_amount = Decimal(metadata["fiat_amount"])

Balance Assertions for Reconciliation

Create balance assertions to verify accounting accuracy:

await create_balance_assertion(
    account_id="lightning_account_id",
    expected_balance_sats=1000000,
    expected_balance_fiat=Decimal("500.00"),
    fiat_currency="EUR",
    tolerance_sats=100
)

Run POST /api/v1/tasks/daily-reconciliation to check all assertions.

Permission Model

  • Super User: Full access (check via wallet.wallet.user == lnbits_settings.super_user)
  • Admin Key: Required for creating receivables, approving payments, viewing all balances
  • Invoice Key: Read access to user's own data
  • Users: Can only see/manage their own accounts and transactions

Extension as LNbits Module

This extension follows LNbits extension structure:

  • Registered via castle_ext router in __init__.py
  • Static files served from static/ directory
  • Templates in templates/castle/
  • Database accessed via db = Database("ext_castle")

Common Tasks

Add New Expense Account

await create_account(CreateAccount(
    name="Expenses:Internet",
    account_type=AccountType.EXPENSE,
    description="Internet service costs"
))

Manually Record Cash Payment

await create_journal_entry(CreateJournalEntry(
    description="Cash payment for groceries",
    lines=[
        CreateEntryLine(account_id=expense_account_id, debit=50000),
        CreateEntryLine(account_id=cash_account_id, credit=50000)
    ],
    flag=JournalEntryFlag.CLEARED,
    meta={"source": "manual", "payment_method": "cash"}
))

Check User Balance

balance = await get_user_balance(user_id)
print(f"Sats: {balance.balance}")  # Positive = Castle owes user
print(f"Fiat: {balance.fiat_balances}")  # {"EUR": Decimal("36.93")}

Export to Beancount (Future)

Follow patterns in docs/BEANCOUNT_PATTERNS.md for implementing Beancount export. Use hierarchical account names and preserve metadata in Beancount comments.

Data Integrity

Critical Invariants:

  1. Every journal entry MUST have balanced debits and credits
  2. Fiat balances calculated from metadata, not from converting sats
  3. User accounts use user_id (NOT wallet_id) for consistency
  4. Balance assertions checked daily via background task

Validation is performed in core/validation.py:

  • validate_journal_entry() - Checks balance, minimum lines
  • validate_balance() - Verifies account balance calculation
  • validate_receivable_entry() - Ensures receivable entries are valid
  • validate_expense_entry() - Ensures expense entries are valid

Known Issues & Future Work

See docs/DOCUMENTATION.md for comprehensive list. Key items:

  • No journal entry editing/deletion (use reversing entries)
  • No date range filtering on list endpoints (hardcoded limit of 100)
  • No batch operations for bulk imports
  • Plugin system architecture designed but not implemented
  • Beancount export endpoint not yet implemented
  • docs/README.md - User-facing overview
  • docs/DOCUMENTATION.md - Comprehensive technical documentation
  • docs/BEANCOUNT_PATTERNS.md - Beancount-inspired design patterns
  • docs/PHASE1_COMPLETE.md, PHASE2_COMPLETE.md, PHASE3_COMPLETE.md - Development milestones
  • docs/EXPENSE_APPROVAL.md - Manual payment request workflow
  • docs/DAILY_RECONCILIATION.md - Automated reconciliation system