castle/docs/ACCOUNTING-ANALYSIS-NET-SETTLEMENT.md
2025-12-14 12:47:34 +01:00

26 KiB

Accounting Analysis: Net Settlement Entry Pattern

Date: 2025-01-12 Prepared By: Senior Accounting Review Subject: Castle Extension - Lightning Payment Settlement Entries Status: Technical Review


Executive Summary

This document provides a professional accounting assessment of Castle's net settlement entry pattern used for recording Lightning Network payments that settle fiat-denominated receivables. The analysis identifies areas where the implementation deviates from traditional accounting best practices and provides specific recommendations for improvement.

Key Findings:

  • Double-entry integrity maintained
  • Functional for intended purpose
  • Zero-amount postings violate accounting principles
  • Redundant satoshi tracking
  • No exchange gain/loss recognition
  • ⚠️ Mixed currency approach lacks clear hierarchy

Background: The Technical Challenge

Castle operates as a Lightning Network-integrated accounting system for collectives (co-living spaces, makerspaces). It faces a unique accounting challenge:

Scenario: User creates a receivable in EUR (e.g., €200 for room rent), then pays via Lightning Network in satoshis (225,033 sats).

Challenge: Record the payment while:

  1. Clearing the exact EUR receivable amount
  2. Recording the exact satoshi amount received
  3. Handling cases where users have both receivables (owe Castle) and payables (Castle owes them)
  4. Maintaining Beancount double-entry balance

Current Implementation

Transaction Example

; Step 1: Receivable Created
2025-11-12 * "room (200.00 EUR)" #receivable-entry
  user-id: "375ec158"
  source: "castle-api"
  sats-amount: "225033"
  Assets:Receivable:User-375ec158     200.00 EUR
    sats-equivalent: "225033"
  Income:Accommodation:Guests        -200.00 EUR
    sats-equivalent: "225033"

; Step 2: Lightning Payment Received
2025-11-12 * "Lightning payment settlement from user 375ec158"
  #lightning-payment #net-settlement
  user-id: "375ec158"
  source: "lightning_payment"
  payment-type: "net-settlement"
  payment-hash: "8d080ec4cc4301715535004156085dd50c159185..."
  Assets:Bitcoin:Lightning            225033 SATS @ 0.0008887585... EUR
    payment-hash: "8d080ec4cc4301715535004156085dd50c159185..."
  Assets:Receivable:User-375ec158    -200.00 EUR
    sats-equivalent: "225033"
  Liabilities:Payable:User-375ec158     0.00 EUR

Code Implementation

Location: beancount_format.py:739-760

# Build postings for net settlement
postings = [
    {
        "account": payment_account,
        "amount": f"{abs(amount_sats)} SATS @@ {abs(net_fiat_amount):.2f} {fiat_currency}",
        "meta": {"payment-hash": payment_hash} if payment_hash else {}
    },
    {
        "account": receivable_account,
        "amount": f"-{abs(total_receivable_fiat):.2f} {fiat_currency}",
        "meta": {"sats-equivalent": str(abs(amount_sats))}
    },
    {
        "account": payable_account,
        "amount": f"{abs(total_payable_fiat):.2f} {fiat_currency}",
        "meta": {}
    }
]

Three-Posting Structure:

  1. Lightning Account: Records SATS received with @@ total price notation
  2. Receivable Account: Clears EUR receivable with sats-equivalent metadata
  3. Payable Account: Clears any outstanding EUR payables (often 0.00)

Accounting Issues Identified

Issue 1: Zero-Amount Postings

Problem: The third posting often records 0.00 EUR when no payable exists.

Liabilities:Payable:User-375ec158     0.00 EUR

Why This Is Wrong:

  • Zero-amount postings have no economic substance
  • Clutters the journal with non-events
  • Violates the principle of materiality (GAAP Concept Statement 2)
  • Makes auditing more difficult (reviewers must verify why zero amounts exist)

Accounting Principle Violated:

"Transactions should only include postings that represent actual economic events or changes in account balances."

Impact: Low severity, but unprofessional presentation

Recommendation:

# Make payable posting conditional
postings = [
    {"account": payment_account, "amount": ...},
    {"account": receivable_account, "amount": ...}
]

# Only add payable posting if there's actually a payable
if total_payable_fiat > 0:
    postings.append({
        "account": payable_account,
        "amount": f"{abs(total_payable_fiat):.2f} {fiat_currency}",
        "meta": {}
    })

Issue 2: Redundant Satoshi Tracking

Problem: Satoshis are tracked in TWO places in the same transaction:

  1. Position Amount (via @@ notation):

    Assets:Bitcoin:Lightning  225033 SATS @@ 200.00 EUR
    
  2. Metadata (sats-equivalent):

    Assets:Receivable:User-375ec158  -200.00 EUR
      sats-equivalent: "225033"
    

Why This Is Problematic:

  • The @@ notation already records the exact satoshi amount
  • Beancount's price database stores this relationship
  • Metadata becomes redundant for this specific posting
  • Increases storage and potential for inconsistency

Technical Detail:

The @@ notation means "total price" and Beancount converts it to per-unit price:

; You write:
Assets:Bitcoin:Lightning  225033 SATS @@ 200.00 EUR

; Beancount stores:
Assets:Bitcoin:Lightning  225033 SATS @ 0.0008887585... EUR
; (where 200.00 / 225033 = 0.0008887585...)

Beancount can query this:

SELECT account, sum(convert(position, SATS))
WHERE account = 'Assets:Bitcoin:Lightning'

Recommendation:

Choose ONE approach consistently:

Option A - Use @ notation (Beancount standard):

Assets:Bitcoin:Lightning           225033 SATS @@ 200.00 EUR
  payment-hash: "8d080ec4..."
Assets:Receivable:User-375ec158   -200.00 EUR
  ; No sats-equivalent needed here

Option B - Use EUR positions with metadata (Castle's current approach):

Assets:Bitcoin:Lightning           200.00 EUR
  sats-received: "225033"
  payment-hash: "8d080ec4..."
Assets:Receivable:User-375ec158   -200.00 EUR
  sats-cleared: "225033"

Don't: Mix both in the same transaction (current implementation)


Issue 3: No Exchange Gain/Loss Recognition

Problem: When receivables are denominated in one currency (EUR) and paid in another (SATS), exchange rate fluctuations create gains or losses that should be recognized.

Example Scenario:

Day 1 - Receivable Created:
  200 EUR = 225,033 SATS (rate: 1,125.165 sats/EUR)

Day 5 - Payment Received:
  225,033 SATS = 199.50 EUR (rate: 1,127.682 sats/EUR)
  Exchange rate moved unfavorably

Economic Reality: 0.50 EUR LOSS

Current Implementation: Forces balance by calculating the @ rate to make it exactly 200 EUR:

Assets:Bitcoin:Lightning  225033 SATS @ 0.000888... EUR  ; = exactly 200.00 EUR

This hides the exchange variance by treating the payment as if it was worth exactly the receivable amount.

GAAP/IFRS Requirement:

Under both US GAAP (ASC 830) and IFRS (IAS 21), exchange gains and losses on monetary items (like receivables) should be recognized in the period they occur.

Proper Accounting Treatment:

2025-11-12 * "Lightning payment with exchange loss"
  Assets:Bitcoin:Lightning           225033 SATS @ 0.000886... EUR
    ; Market rate at payment time = 199.50 EUR
  Expenses:Foreign-Exchange-Loss     0.50 EUR
  Assets:Receivable:User-375ec158   -200.00 EUR

Impact: Moderate severity - affects financial statement accuracy

Why This Matters:

  • Tax reporting may require exchange gain/loss recognition
  • Financial statements misstate true economic results
  • Auditors would flag this as a compliance issue
  • Cannot accurately calculate ROI or performance metrics

Issue 4: Semantic Misuse of Price Notation

Problem: The @ notation in Beancount represents acquisition cost, not settlement value.

Current Usage:

Assets:Bitcoin:Lightning  225033 SATS @ 0.000888... EUR

What this notation means in accounting: "We purchased 225,033 satoshis at a cost of 0.000888 EUR per satoshi"

What actually happened: "We received 225,033 satoshis as payment for a debt"

Economic Difference:

  • Purchase: You exchange cash for an asset (buying Bitcoin)
  • Payment Receipt: You receive an asset in settlement of a receivable

Accounting Substance vs. Form:

  • Form: The transaction looks like a Bitcoin purchase
  • Substance: The transaction is actually a receivable collection

GAAP Principle (ASC 105-10-05):

"Accounting should reflect the economic substance of transactions, not merely their legal form."

Why This Creates Issues:

  1. Cost Basis Tracking: For tax purposes, the "cost" of Bitcoin received as payment should be its fair market value at receipt, not the receivable amount
  2. Price Database Pollution: Beancount's price database now contains "prices" that aren't real market prices
  3. Auditor Confusion: An auditor reviewing this would question why purchase prices don't match market rates

Proper Accounting Approach:

; Approach 1: Record at fair market value
Assets:Bitcoin:Lightning           225033 SATS @ 0.000886... EUR
  ; Using actual market price at time of receipt
  acquisition-type: "payment-received"
Revenue:Exchange-Gain              0.50 EUR
Assets:Receivable:User-375ec158   -200.00 EUR

; Approach 2: Don't use @ notation at all
Assets:Bitcoin:Lightning           200.00 EUR
  sats-received: "225033"
  fmv-at-receipt: "199.50 EUR"
Assets:Receivable:User-375ec158   -200.00 EUR

Issue 5: Misnamed Function and Incorrect Usage

Problem: Function is called format_net_settlement_entry, but it's used for simple payments that aren't true net settlements.

Example from User's Transaction:

  • Receivable: 200.00 EUR
  • Payable: 0.00 EUR
  • Net: 200.00 EUR (this is just a payment, not a settlement)

Accounting Terminology:

  • Payment: Settling a single obligation (receivable OR payable)
  • Net Settlement: Offsetting multiple obligations (receivable AND payable)

When Net Settlement is Appropriate:

User owes Castle:    555.00 EUR (receivable)
Castle owes User:     38.00 EUR (payable)
Net amount due:      517.00 EUR (true settlement)

Proper three-posting entry:

Assets:Bitcoin:Lightning           565251 SATS @@ 517.00 EUR
Assets:Receivable:User            -555.00 EUR
Liabilities:Payable:User            38.00 EUR
; Net: 517.00 = -555.00 + 38.00 ✓

When Two Postings Suffice:

User owes Castle:    200.00 EUR (receivable)
Castle owes User:      0.00 EUR (no payable)
Amount due:          200.00 EUR (simple payment)

Simpler two-posting entry:

Assets:Bitcoin:Lightning           225033 SATS @@ 200.00 EUR
Assets:Receivable:User            -200.00 EUR

Best Practice: Use the simplest journal entry structure that accurately represents the transaction.

Recommendation:

  1. Rename function to format_payment_entry or format_receivable_payment_entry
  2. Create separate format_net_settlement_entry for true netting scenarios
  3. Use conditional logic to choose 2-posting vs 3-posting based on whether both receivables AND payables exist

Traditional Accounting Approaches

Approach 1: Record Bitcoin at Fair Market Value (Tax Compliant)

2025-11-12 * "Bitcoin payment from user 375ec158"
  Assets:Bitcoin:Lightning           199.50 EUR
    sats-received: "225033"
    fmv-per-sat: "0.000886 EUR"
    cost-basis: "199.50 EUR"
    payment-hash: "8d080ec4..."
  Revenue:Exchange-Gain              0.50 EUR
    source: "cryptocurrency-receipt"
  Assets:Receivable:User-375ec158   -200.00 EUR

Pros:

  • Tax compliant (establishes cost basis)
  • Recognizes exchange gain/loss
  • Uses actual market rates
  • Audit trail for cryptocurrency receipts

Cons:

  • Requires real-time price feeds
  • Creates taxable events

Approach 2: Simplified EUR-Only Ledger (No SATS Positions)

2025-11-12 * "Bitcoin payment from user 375ec158"
  Assets:Bitcoin:Lightning           200.00 EUR
    sats-received: "225033"
    sats-rate: "1125.165"
    payment-hash: "8d080ec4..."
  Assets:Receivable:User-375ec158   -200.00 EUR

Pros:

  • Simple and clean
  • EUR positions match accounting reality
  • SATS tracked in metadata for reference
  • No artificial price notation

Cons:

  • SATS not queryable via Beancount positions
  • Requires metadata parsing for SATS balances

Approach 3: True Net Settlement (When Both Obligations Exist)

2025-11-12 * "Net settlement via Lightning"
  ; User owes 555 EUR, Castle owes 38 EUR, net: 517 EUR
  Assets:Bitcoin:Lightning           517.00 EUR
    sats-received: "565251"
  Assets:Receivable:User-375ec158   -555.00 EUR
  Liabilities:Payable:User-375ec158   38.00 EUR

When to Use: Only when both receivables and payables exist and you're truly netting them.


Recommendations

Priority 1: Immediate Fixes (Easy Wins)

1.1 Remove Zero-Amount Postings

File: beancount_format.py:739-760

Current Code:

postings = [
    {...},  # Lightning
    {...},  # Receivable
    {       # Payable (always included, even if 0.00)
        "account": payable_account,
        "amount": f"{abs(total_payable_fiat):.2f} {fiat_currency}",
        "meta": {}
    }
]

Fixed Code:

postings = [
    {
        "account": payment_account,
        "amount": f"{abs(amount_sats)} SATS @@ {abs(net_fiat_amount):.2f} {fiat_currency}",
        "meta": {"payment-hash": payment_hash} if payment_hash else {}
    },
    {
        "account": receivable_account,
        "amount": f"-{abs(total_receivable_fiat):.2f} {fiat_currency}",
        "meta": {"sats-equivalent": str(abs(amount_sats))}
    }
]

# Only add payable posting if there's actually a payable to clear
if total_payable_fiat > 0:
    postings.append({
        "account": payable_account,
        "amount": f"{abs(total_payable_fiat):.2f} {fiat_currency}",
        "meta": {}
    })

Impact: Cleaner journal, professional presentation, easier auditing


1.2 Choose One SATS Tracking Method

Decision Required: Select either position-based OR metadata-based satoshi tracking.

Option A - Keep Metadata Approach (recommended for Castle):

# In format_net_settlement_entry()
postings = [
    {
        "account": payment_account,
        "amount": f"{abs(net_fiat_amount):.2f} {fiat_currency}",  # EUR only
        "meta": {
            "sats-received": str(abs(amount_sats)),
            "payment-hash": payment_hash
        }
    },
    {
        "account": receivable_account,
        "amount": f"-{abs(total_receivable_fiat):.2f} {fiat_currency}",
        "meta": {"sats-cleared": str(abs(amount_sats))}
    }
]

Option B - Use Position-Based Tracking:

# Remove sats-equivalent metadata entirely
postings = [
    {
        "account": payment_account,
        "amount": f"{abs(amount_sats)} SATS @@ {abs(net_fiat_amount):.2f} {fiat_currency}",
        "meta": {"payment-hash": payment_hash}
    },
    {
        "account": receivable_account,
        "amount": f"-{abs(total_receivable_fiat):.2f} {fiat_currency}",
        # No sats-equivalent needed - queryable via price database
    }
]

Recommendation: Choose Option A (metadata) for consistency with Castle's architecture.


1.3 Rename Function for Clarity

File: beancount_format.py

Current: format_net_settlement_entry()

New: format_receivable_payment_entry() or format_payment_settlement_entry()

Rationale: More accurately describes what the function does (processes payments, not always net settlements)


Priority 2: Medium-Term Improvements (Compliance)

2.1 Add Exchange Gain/Loss Tracking

File: tasks.py:259-276 (get balance and calculate settlement)

New Logic:

# Get user's current balance
balance = await fava.get_user_balance(user_id)
fiat_balances = balance.get("fiat_balances", {})
total_fiat_balance = fiat_balances.get(fiat_currency, Decimal(0))

# Calculate expected fiat value of SATS payment at current market rate
market_rate = await get_current_sats_eur_rate()  # New function needed
market_value = Decimal(amount_sats) * market_rate

# Calculate exchange variance
receivable_amount = abs(total_fiat_balance) if total_fiat_balance > 0 else Decimal(0)
exchange_variance = market_value - receivable_amount

# If variance is material (> 1 cent), create exchange gain/loss posting
if abs(exchange_variance) > Decimal("0.01"):
    # Add exchange gain/loss to postings
    if exchange_variance > 0:
        # Gain: payment worth more than receivable
        exchange_account = "Revenue:Foreign-Exchange-Gain"
    else:
        # Loss: payment worth less than receivable
        exchange_account = "Expenses:Foreign-Exchange-Loss"

    # Include in entry creation
    exchange_posting = {
        "account": exchange_account,
        "amount": f"{abs(exchange_variance):.2f} {fiat_currency}",
        "meta": {
            "sats-amount": str(amount_sats),
            "market-rate": str(market_rate),
            "receivable-amount": str(receivable_amount)
        }
    }

Benefits:

  • Tax compliance
  • Accurate financial reporting
  • Audit trail for cryptocurrency gains/losses
  • Regulatory compliance (GAAP/IFRS)

2.2 Implement True Net Settlement vs. Simple Payment Logic

File: tasks.py or new payment_logic.py

async def create_payment_entry(
    user_id: str,
    amount_sats: int,
    fiat_amount: Decimal,
    fiat_currency: str,
    payment_hash: str
):
    """
    Create appropriate payment entry based on user's balance situation.
    Uses 2-posting for simple payments, 3-posting for net settlements.
    """
    # Get user balance
    balance = await fava.get_user_balance(user_id)
    fiat_balances = balance.get("fiat_balances", {})
    total_balance = fiat_balances.get(fiat_currency, Decimal(0))

    receivable_amount = Decimal(0)
    payable_amount = Decimal(0)

    if total_balance > 0:
        receivable_amount = total_balance
    elif total_balance < 0:
        payable_amount = abs(total_balance)

    # Determine entry type
    if receivable_amount > 0 and payable_amount > 0:
        # TRUE NET SETTLEMENT: Both obligations exist
        return await format_net_settlement_entry(
            user_id=user_id,
            amount_sats=amount_sats,
            receivable_amount=receivable_amount,
            payable_amount=payable_amount,
            fiat_amount=fiat_amount,
            fiat_currency=fiat_currency,
            payment_hash=payment_hash
        )
    elif receivable_amount > 0:
        # SIMPLE RECEIVABLE PAYMENT: Only receivable exists
        return await format_receivable_payment_entry(
            user_id=user_id,
            amount_sats=amount_sats,
            receivable_amount=receivable_amount,
            fiat_amount=fiat_amount,
            fiat_currency=fiat_currency,
            payment_hash=payment_hash
        )
    else:
        # PAYABLE PAYMENT: Castle paying user (different flow)
        return await format_payable_payment_entry(...)

Priority 3: Long-Term Architectural Decisions

3.1 Establish Primary Currency Hierarchy

Current Issue: Mixed approach (EUR positions with SATS metadata, but also SATS positions with @ notation)

Decision Required: Choose ONE of the following architectures:

Architecture A - EUR Primary, SATS Secondary (recommended):

; All positions in EUR, SATS in metadata
2025-11-12 * "Payment"
  Assets:Bitcoin:Lightning           200.00 EUR
    sats-received: "225033"
  Assets:Receivable:User            -200.00 EUR
    sats-cleared: "225033"

Architecture B - SATS Primary, EUR Secondary:

; All positions in SATS, EUR in metadata
2025-11-12 * "Payment"
  Assets:Bitcoin:Lightning           225033 SATS
    eur-value: "200.00"
  Assets:Receivable:User            -225033 SATS
    eur-cleared: "200.00"

Recommendation: Architecture A (EUR primary) because:

  1. Most receivables created in EUR
  2. Financial reporting requirements typically in fiat
  3. Tax obligations calculated in fiat
  4. Aligns with current Castle metadata approach

3.2 Consider Separate Ledger for Cryptocurrency Holdings

Advanced Approach: Separate cryptocurrency movements from fiat accounting

Main Ledger (EUR-denominated):

2025-11-12 * "Payment received from user"
  Assets:Bitcoin-Custody:User-375ec158  200.00 EUR
  Assets:Receivable:User-375ec158      -200.00 EUR

Cryptocurrency Sub-Ledger (SATS-denominated):

2025-11-12 * "Lightning payment received"
  Assets:Bitcoin:Lightning:Castle    225033 SATS
  Assets:Bitcoin:Custody:User-375ec  225033 SATS

Benefits:

  • Clean separation of concerns
  • Cryptocurrency movements tracked independently
  • Fiat accounting unaffected by Bitcoin volatility
  • Can generate separate financial statements

Drawbacks:

  • Increased complexity
  • Reconciliation between ledgers required
  • Two sets of books to maintain

Code Files Requiring Changes

High Priority (Immediate Fixes)

  1. beancount_format.py:739-760

    • Remove zero-amount postings
    • Make payable posting conditional
  2. beancount_format.py:692

    • Rename function to format_receivable_payment_entry

Medium Priority (Compliance)

  1. tasks.py:235-310

    • Add exchange gain/loss calculation
    • Implement payment vs. settlement logic
  2. New file: exchange_rates.py

    • Create get_current_sats_eur_rate() function
    • Implement price feed integration
  3. beancount_format.py

    • Create new format_net_settlement_entry() for true netting
    • Create format_receivable_payment_entry() for simple payments

Testing Requirements

Test Case 1: Simple Receivable Payment (No Payable)

Setup:

  • User has receivable: 200.00 EUR
  • User has payable: 0.00 EUR
  • User pays: 225,033 SATS

Expected Entry (after fixes):

2025-11-12 * "Lightning payment from user"
  Assets:Bitcoin:Lightning           200.00 EUR
    sats-received: "225033"
    payment-hash: "8d080ec4..."
  Assets:Receivable:User            -200.00 EUR
    sats-cleared: "225033"

Verify:

  • Only 2 postings (no zero-amount payable)
  • Entry balances
  • SATS tracked in metadata
  • User balance becomes 0 (both EUR and SATS)

Test Case 2: True Net Settlement

Setup:

  • User has receivable: 555.00 EUR
  • User has payable: 38.00 EUR
  • Net owed: 517.00 EUR
  • User pays: 565,251 SATS (worth 517.00 EUR)

Expected Entry:

2025-11-12 * "Net settlement via Lightning"
  Assets:Bitcoin:Lightning           517.00 EUR
    sats-received: "565251"
    payment-hash: "abc123..."
  Assets:Receivable:User            -555.00 EUR
    sats-portion: "565251"
  Liabilities:Payable:User            38.00 EUR

Verify:

  • 3 postings (receivable + payable cleared)
  • Net amount = receivable - payable
  • Both balances become 0
  • Mathematically balanced

Test Case 3: Exchange Gain/Loss (Future)

Setup:

  • User has receivable: 200.00 EUR (created at 1,125 sats/EUR)
  • User pays: 225,033 SATS (now worth 199.50 EUR at market)
  • Exchange loss: 0.50 EUR

Expected Entry (with exchange tracking):

2025-11-12 * "Lightning payment with exchange loss"
  Assets:Bitcoin:Lightning           199.50 EUR
    sats-received: "225033"
    market-rate: "0.000886"
  Expenses:Foreign-Exchange-Loss     0.50 EUR
  Assets:Receivable:User            -200.00 EUR

Verify:

  • Bitcoin recorded at fair market value
  • Exchange loss recognized
  • Receivable cleared at book value
  • Entry balances

Conclusion

Summary of Issues

Issue Severity Accounting Impact Recommended Action
Zero-amount postings Low Presentation only Remove immediately
Redundant SATS tracking Low Storage/efficiency Choose one method
No exchange gain/loss High Financial accuracy Implement for compliance
Semantic misuse of @ Medium Audit clarity Consider EUR-only positions
Misnamed function Low Code clarity Rename function

Professional Assessment

Is this "best practice" accounting? No, this implementation deviates from traditional accounting standards in several ways.

Is it acceptable for Castle's use case? Yes, with modifications, it's a reasonable pragmatic solution for a novel problem (cryptocurrency payments of fiat debts).

Critical improvements needed:

  1. Remove zero-amount postings (easy fix, professional presentation)
  2. Implement exchange gain/loss tracking (required for compliance)
  3. Separate payment vs. settlement logic (accuracy and clarity)

The fundamental challenge: Traditional accounting wasn't designed for this scenario. There is no established "standard" for recording cryptocurrency payments of fiat-denominated receivables. Castle's approach is functional, but should be refined to align better with accounting principles where possible.

Next Steps

  1. Week 1: Implement Priority 1 fixes (remove zero postings, rename function)
  2. Week 2-3: Design and implement exchange gain/loss tracking
  3. Week 4: Add payment vs. settlement logic
  4. Ongoing: Monitor regulatory guidance on cryptocurrency accounting

References

  • FASB ASC 830: Foreign Currency Matters
  • IAS 21: The Effects of Changes in Foreign Exchange Rates
  • FASB Concept Statement No. 2: Qualitative Characteristics of Accounting Information
  • ASC 105-10-05: Substance Over Form
  • Beancount Documentation: http://furius.ca/beancount/doc/index
  • Castle Extension: docs/SATS-EQUIVALENT-METADATA.md
  • BQL Analysis: docs/BQL-BALANCE-QUERIES.md

Document Version: 1.0 Last Updated: 2025-01-12 Next Review: After Priority 1 fixes implemented


This analysis was prepared for internal review and development planning. It represents a professional accounting assessment of the current implementation and should be used to guide improvements to Castle's payment recording system.