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

861 lines
26 KiB
Markdown

# 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
```beancount
; 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`
```python
# 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.
```beancount
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**:
```python
# 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):
```beancount
Assets:Bitcoin:Lightning 225033 SATS @@ 200.00 EUR
```
2. **Metadata** (sats-equivalent):
```beancount
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:
```beancount
; 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:
```sql
SELECT account, sum(convert(position, SATS))
WHERE account = 'Assets:Bitcoin:Lightning'
```
**Recommendation**:
Choose ONE approach consistently:
**Option A - Use @ notation** (Beancount standard):
```beancount
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):
```beancount
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:
```beancount
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**:
```beancount
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**:
```beancount
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**:
```beancount
; 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:
```beancount
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:
```beancount
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)
```beancount
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)
```beancount
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)
```beancount
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**:
```python
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**:
```python
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):
```python
# 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**:
```python
# 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**:
```python
# 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`
```python
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):
```beancount
; 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**:
```beancount
; 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):
```beancount
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):
```beancount
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)
3. **`tasks.py:235-310`**
- Add exchange gain/loss calculation
- Implement payment vs. settlement logic
4. **New file: `exchange_rates.py`**
- Create `get_current_sats_eur_rate()` function
- Implement price feed integration
5. **`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):
```beancount
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**:
```beancount
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):
```beancount
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.*