953 lines
63 KiB
HTML
953 lines
63 KiB
HTML
<!DOCTYPE html>
|
||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="generator" content="pandoc" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||
<title>ACCOUNTING-ANALYSIS-NET-SETTLEMENT</title>
|
||
<style>
|
||
code{white-space: pre-wrap;}
|
||
span.smallcaps{font-variant: small-caps;}
|
||
div.columns{display: flex; gap: min(4vw, 1.5em);}
|
||
div.column{flex: auto; overflow-x: auto;}
|
||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||
/* The extra [class] is a hack that increases specificity enough to
|
||
override a similar rule in reveal.js */
|
||
ul.task-list[class]{list-style: none;}
|
||
ul.task-list li input[type="checkbox"] {
|
||
font-size: inherit;
|
||
width: 0.8em;
|
||
margin: 0 0.8em 0.2em -1.6em;
|
||
vertical-align: middle;
|
||
}
|
||
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
|
||
/* CSS for syntax highlighting */
|
||
html { -webkit-text-size-adjust: 100%; }
|
||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
|
||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||
.sourceCode { overflow: visible; }
|
||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||
div.sourceCode { margin: 1em 0; }
|
||
pre.sourceCode { margin: 0; }
|
||
@media screen {
|
||
div.sourceCode { overflow: auto; }
|
||
}
|
||
@media print {
|
||
pre > code.sourceCode { white-space: pre-wrap; }
|
||
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
||
}
|
||
pre.numberSource code
|
||
{ counter-reset: source-line 0; }
|
||
pre.numberSource code > span
|
||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||
pre.numberSource code > span > a:first-child::before
|
||
{ content: counter(source-line);
|
||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||
border: none; display: inline-block;
|
||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||
-khtml-user-select: none; -moz-user-select: none;
|
||
-ms-user-select: none; user-select: none;
|
||
padding: 0 4px; width: 4em;
|
||
color: #aaaaaa;
|
||
}
|
||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||
div.sourceCode
|
||
{ }
|
||
@media screen {
|
||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||
}
|
||
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||
code span.at { color: #7d9029; } /* Attribute */
|
||
code span.bn { color: #40a070; } /* BaseN */
|
||
code span.bu { color: #008000; } /* BuiltIn */
|
||
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||
code span.ch { color: #4070a0; } /* Char */
|
||
code span.cn { color: #880000; } /* Constant */
|
||
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||
code span.dt { color: #902000; } /* DataType */
|
||
code span.dv { color: #40a070; } /* DecVal */
|
||
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||
code span.ex { } /* Extension */
|
||
code span.fl { color: #40a070; } /* Float */
|
||
code span.fu { color: #06287e; } /* Function */
|
||
code span.im { color: #008000; font-weight: bold; } /* Import */
|
||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||
code span.op { color: #666666; } /* Operator */
|
||
code span.ot { color: #007020; } /* Other */
|
||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||
code span.ss { color: #bb6688; } /* SpecialString */
|
||
code span.st { color: #4070a0; } /* String */
|
||
code span.va { color: #19177c; } /* Variable */
|
||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||
</style>
|
||
<link rel="stylesheet" href="https://latex.now.sh/style.css" />
|
||
</head>
|
||
<body>
|
||
<nav id="TOC" role="doc-toc">
|
||
<ul>
|
||
<li><a href="#accounting-analysis-net-settlement-entry-pattern"
|
||
id="toc-accounting-analysis-net-settlement-entry-pattern">Accounting
|
||
Analysis: Net Settlement Entry Pattern</a>
|
||
<ul>
|
||
<li><a href="#executive-summary" id="toc-executive-summary">Executive
|
||
Summary</a></li>
|
||
<li><a href="#background-the-technical-challenge"
|
||
id="toc-background-the-technical-challenge">Background: The Technical
|
||
Challenge</a></li>
|
||
<li><a href="#current-implementation"
|
||
id="toc-current-implementation">Current Implementation</a>
|
||
<ul>
|
||
<li><a href="#transaction-example"
|
||
id="toc-transaction-example">Transaction Example</a></li>
|
||
<li><a href="#code-implementation" id="toc-code-implementation">Code
|
||
Implementation</a></li>
|
||
</ul></li>
|
||
<li><a href="#accounting-issues-identified"
|
||
id="toc-accounting-issues-identified">Accounting Issues Identified</a>
|
||
<ul>
|
||
<li><a href="#issue-1-zero-amount-postings"
|
||
id="toc-issue-1-zero-amount-postings">Issue 1: Zero-Amount
|
||
Postings</a></li>
|
||
<li><a href="#issue-2-redundant-satoshi-tracking"
|
||
id="toc-issue-2-redundant-satoshi-tracking">Issue 2: Redundant Satoshi
|
||
Tracking</a></li>
|
||
<li><a href="#issue-3-no-exchange-gainloss-recognition"
|
||
id="toc-issue-3-no-exchange-gainloss-recognition">Issue 3: No Exchange
|
||
Gain/Loss Recognition</a></li>
|
||
<li><a href="#issue-4-semantic-misuse-of-price-notation"
|
||
id="toc-issue-4-semantic-misuse-of-price-notation">Issue 4: Semantic
|
||
Misuse of Price Notation</a></li>
|
||
<li><a href="#issue-5-misnamed-function-and-incorrect-usage"
|
||
id="toc-issue-5-misnamed-function-and-incorrect-usage">Issue 5: Misnamed
|
||
Function and Incorrect Usage</a></li>
|
||
</ul></li>
|
||
<li><a href="#traditional-accounting-approaches"
|
||
id="toc-traditional-accounting-approaches">Traditional Accounting
|
||
Approaches</a>
|
||
<ul>
|
||
<li><a
|
||
href="#approach-1-record-bitcoin-at-fair-market-value-tax-compliant"
|
||
id="toc-approach-1-record-bitcoin-at-fair-market-value-tax-compliant">Approach
|
||
1: Record Bitcoin at Fair Market Value (Tax Compliant)</a></li>
|
||
<li><a href="#approach-2-simplified-eur-only-ledger-no-sats-positions"
|
||
id="toc-approach-2-simplified-eur-only-ledger-no-sats-positions">Approach
|
||
2: Simplified EUR-Only Ledger (No SATS Positions)</a></li>
|
||
<li><a
|
||
href="#approach-3-true-net-settlement-when-both-obligations-exist"
|
||
id="toc-approach-3-true-net-settlement-when-both-obligations-exist">Approach
|
||
3: True Net Settlement (When Both Obligations Exist)</a></li>
|
||
</ul></li>
|
||
<li><a href="#recommendations"
|
||
id="toc-recommendations">Recommendations</a>
|
||
<ul>
|
||
<li><a href="#priority-1-immediate-fixes-easy-wins"
|
||
id="toc-priority-1-immediate-fixes-easy-wins">Priority 1: Immediate
|
||
Fixes (Easy Wins)</a></li>
|
||
<li><a href="#priority-2-medium-term-improvements-compliance"
|
||
id="toc-priority-2-medium-term-improvements-compliance">Priority 2:
|
||
Medium-Term Improvements (Compliance)</a></li>
|
||
<li><a href="#priority-3-long-term-architectural-decisions"
|
||
id="toc-priority-3-long-term-architectural-decisions">Priority 3:
|
||
Long-Term Architectural Decisions</a></li>
|
||
</ul></li>
|
||
<li><a href="#code-files-requiring-changes"
|
||
id="toc-code-files-requiring-changes">Code Files Requiring Changes</a>
|
||
<ul>
|
||
<li><a href="#high-priority-immediate-fixes"
|
||
id="toc-high-priority-immediate-fixes">High Priority (Immediate
|
||
Fixes)</a></li>
|
||
<li><a href="#medium-priority-compliance"
|
||
id="toc-medium-priority-compliance">Medium Priority
|
||
(Compliance)</a></li>
|
||
</ul></li>
|
||
<li><a href="#testing-requirements"
|
||
id="toc-testing-requirements">Testing Requirements</a>
|
||
<ul>
|
||
<li><a href="#test-case-1-simple-receivable-payment-no-payable"
|
||
id="toc-test-case-1-simple-receivable-payment-no-payable">Test Case 1:
|
||
Simple Receivable Payment (No Payable)</a></li>
|
||
<li><a href="#test-case-2-true-net-settlement"
|
||
id="toc-test-case-2-true-net-settlement">Test Case 2: True Net
|
||
Settlement</a></li>
|
||
<li><a href="#test-case-3-exchange-gainloss-future"
|
||
id="toc-test-case-3-exchange-gainloss-future">Test Case 3: Exchange
|
||
Gain/Loss (Future)</a></li>
|
||
</ul></li>
|
||
<li><a href="#conclusion" id="toc-conclusion">Conclusion</a>
|
||
<ul>
|
||
<li><a href="#summary-of-issues" id="toc-summary-of-issues">Summary of
|
||
Issues</a></li>
|
||
<li><a href="#professional-assessment"
|
||
id="toc-professional-assessment">Professional Assessment</a></li>
|
||
<li><a href="#next-steps" id="toc-next-steps">Next Steps</a></li>
|
||
</ul></li>
|
||
<li><a href="#references" id="toc-references">References</a></li>
|
||
</ul></li>
|
||
</ul>
|
||
</nav>
|
||
<h1 id="accounting-analysis-net-settlement-entry-pattern">Accounting
|
||
Analysis: Net Settlement Entry Pattern</h1>
|
||
<p><strong>Date</strong>: 2025-01-12 <strong>Prepared By</strong>:
|
||
Senior Accounting Review <strong>Subject</strong>: Castle Extension -
|
||
Lightning Payment Settlement Entries <strong>Status</strong>: Technical
|
||
Review</p>
|
||
<hr />
|
||
<h2 id="executive-summary">Executive Summary</h2>
|
||
<p>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.</p>
|
||
<p><strong>Key Findings</strong>: - ✅ 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</p>
|
||
<hr />
|
||
<h2 id="background-the-technical-challenge">Background: The Technical
|
||
Challenge</h2>
|
||
<p>Castle operates as a Lightning Network-integrated accounting system
|
||
for collectives (co-living spaces, makerspaces). It faces a unique
|
||
accounting challenge:</p>
|
||
<p><strong>Scenario</strong>: User creates a receivable in EUR (e.g.,
|
||
€200 for room rent), then pays via Lightning Network in satoshis
|
||
(225,033 sats).</p>
|
||
<p><strong>Challenge</strong>: 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</p>
|
||
<hr />
|
||
<h2 id="current-implementation">Current Implementation</h2>
|
||
<h3 id="transaction-example">Transaction Example</h3>
|
||
<pre class="beancount"><code>; 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></pre>
|
||
<h3 id="code-implementation">Code Implementation</h3>
|
||
<p><strong>Location</strong>:
|
||
<code>beancount_format.py:739-760</code></p>
|
||
<div class="sourceCode" id="cb2"><pre
|
||
class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Build postings for net settlement</span></span>
|
||
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>postings <span class="op">=</span> [</span>
|
||
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> {</span>
|
||
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: payment_account,</span>
|
||
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"</span><span class="sc">{</span><span class="bu">abs</span>(amount_sats)<span class="sc">}</span><span class="ss"> SATS @@ </span><span class="sc">{</span><span class="bu">abs</span>(net_fiat_amount)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {<span class="st">"payment-hash"</span>: payment_hash} <span class="cf">if</span> payment_hash <span class="cf">else</span> {}</span>
|
||
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> },</span>
|
||
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> {</span>
|
||
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: receivable_account,</span>
|
||
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"-</span><span class="sc">{</span><span class="bu">abs</span>(total_receivable_fiat)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {<span class="st">"sats-equivalent"</span>: <span class="bu">str</span>(<span class="bu">abs</span>(amount_sats))}</span>
|
||
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a> },</span>
|
||
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a> {</span>
|
||
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: payable_account,</span>
|
||
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"</span><span class="sc">{</span><span class="bu">abs</span>(total_payable_fiat)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {}</span>
|
||
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a> }</span>
|
||
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a>]</span></code></pre></div>
|
||
<p><strong>Three-Posting Structure</strong>: 1. <strong>Lightning
|
||
Account</strong>: Records SATS received with <code>@@</code> total price
|
||
notation 2. <strong>Receivable Account</strong>: Clears EUR receivable
|
||
with sats-equivalent metadata 3. <strong>Payable Account</strong>:
|
||
Clears any outstanding EUR payables (often 0.00)</p>
|
||
<hr />
|
||
<h2 id="accounting-issues-identified">Accounting Issues Identified</h2>
|
||
<h3 id="issue-1-zero-amount-postings">Issue 1: Zero-Amount Postings</h3>
|
||
<p><strong>Problem</strong>: The third posting often records
|
||
<code>0.00 EUR</code> when no payable exists.</p>
|
||
<pre class="beancount"><code>Liabilities:Payable:User-375ec158 0.00 EUR</code></pre>
|
||
<p><strong>Why This Is Wrong</strong>: - 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)</p>
|
||
<p><strong>Accounting Principle Violated</strong>: > “Transactions
|
||
should only include postings that represent actual economic events or
|
||
changes in account balances.”</p>
|
||
<p><strong>Impact</strong>: Low severity, but unprofessional
|
||
presentation</p>
|
||
<p><strong>Recommendation</strong>:</p>
|
||
<div class="sourceCode" id="cb4"><pre
|
||
class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Make payable posting conditional</span></span>
|
||
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>postings <span class="op">=</span> [</span>
|
||
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> {<span class="st">"account"</span>: payment_account, <span class="st">"amount"</span>: ...},</span>
|
||
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> {<span class="st">"account"</span>: receivable_account, <span class="st">"amount"</span>: ...}</span>
|
||
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>]</span>
|
||
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="co"># Only add payable posting if there's actually a payable</span></span>
|
||
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> total_payable_fiat <span class="op">></span> <span class="dv">0</span>:</span>
|
||
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a> postings.append({</span>
|
||
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: payable_account,</span>
|
||
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"</span><span class="sc">{</span><span class="bu">abs</span>(total_payable_fiat)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {}</span>
|
||
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a> })</span></code></pre></div>
|
||
<hr />
|
||
<h3 id="issue-2-redundant-satoshi-tracking">Issue 2: Redundant Satoshi
|
||
Tracking</h3>
|
||
<p><strong>Problem</strong>: Satoshis are tracked in TWO places in the
|
||
same transaction:</p>
|
||
<ol type="1">
|
||
<li><p><strong>Position Amount</strong> (via <code>@@</code>
|
||
notation):</p>
|
||
<pre class="beancount"><code>Assets:Bitcoin:Lightning 225033 SATS @@ 200.00 EUR</code></pre></li>
|
||
<li><p><strong>Metadata</strong> (sats-equivalent):</p>
|
||
<pre class="beancount"><code>Assets:Receivable:User-375ec158 -200.00 EUR
|
||
sats-equivalent: "225033"</code></pre></li>
|
||
</ol>
|
||
<p><strong>Why This Is Problematic</strong>: - The <code>@@</code>
|
||
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</p>
|
||
<p><strong>Technical Detail</strong>:</p>
|
||
<p>The <code>@@</code> notation means “total price” and Beancount
|
||
converts it to per-unit price:</p>
|
||
<pre class="beancount"><code>; 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...)</code></pre>
|
||
<p>Beancount can query this:</p>
|
||
<div class="sourceCode" id="cb8"><pre
|
||
class="sourceCode sql"><code class="sourceCode sql"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">SELECT</span> <span class="kw">account</span>, <span class="fu">sum</span>(<span class="fu">convert</span>(position, SATS))</span>
|
||
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="kw">WHERE</span> <span class="kw">account</span> <span class="op">=</span> <span class="st">'Assets:Bitcoin:Lightning'</span></span></code></pre></div>
|
||
<p><strong>Recommendation</strong>:</p>
|
||
<p>Choose ONE approach consistently:</p>
|
||
<p><strong>Option A - Use @ notation</strong> (Beancount standard):</p>
|
||
<pre class="beancount"><code>Assets:Bitcoin:Lightning 225033 SATS @@ 200.00 EUR
|
||
payment-hash: "8d080ec4..."
|
||
Assets:Receivable:User-375ec158 -200.00 EUR
|
||
; No sats-equivalent needed here</code></pre>
|
||
<p><strong>Option B - Use EUR positions with metadata</strong> (Castle’s
|
||
current approach):</p>
|
||
<pre class="beancount"><code>Assets:Bitcoin:Lightning 200.00 EUR
|
||
sats-received: "225033"
|
||
payment-hash: "8d080ec4..."
|
||
Assets:Receivable:User-375ec158 -200.00 EUR
|
||
sats-cleared: "225033"</code></pre>
|
||
<p><strong>Don’t</strong>: Mix both in the same transaction (current
|
||
implementation)</p>
|
||
<hr />
|
||
<h3 id="issue-3-no-exchange-gainloss-recognition">Issue 3: No Exchange
|
||
Gain/Loss Recognition</h3>
|
||
<p><strong>Problem</strong>: When receivables are denominated in one
|
||
currency (EUR) and paid in another (SATS), exchange rate fluctuations
|
||
create gains or losses that should be recognized.</p>
|
||
<p><strong>Example Scenario</strong>:</p>
|
||
<pre><code>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</code></pre>
|
||
<p><strong>Current Implementation</strong>: Forces balance by
|
||
calculating the <code>@</code> rate to make it exactly 200 EUR:</p>
|
||
<pre class="beancount"><code>Assets:Bitcoin:Lightning 225033 SATS @ 0.000888... EUR ; = exactly 200.00 EUR</code></pre>
|
||
<p>This <strong>hides the exchange variance</strong> by treating the
|
||
payment as if it was worth exactly the receivable amount.</p>
|
||
<p><strong>GAAP/IFRS Requirement</strong>:</p>
|
||
<p>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.</p>
|
||
<p><strong>Proper Accounting Treatment</strong>:</p>
|
||
<pre class="beancount"><code>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</code></pre>
|
||
<p><strong>Impact</strong>: Moderate severity - affects financial
|
||
statement accuracy</p>
|
||
<p><strong>Why This Matters</strong>: - 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</p>
|
||
<hr />
|
||
<h3 id="issue-4-semantic-misuse-of-price-notation">Issue 4: Semantic
|
||
Misuse of Price Notation</h3>
|
||
<p><strong>Problem</strong>: The <code>@</code> notation in Beancount
|
||
represents <strong>acquisition cost</strong>, not <strong>settlement
|
||
value</strong>.</p>
|
||
<p><strong>Current Usage</strong>:</p>
|
||
<pre class="beancount"><code>Assets:Bitcoin:Lightning 225033 SATS @ 0.000888... EUR</code></pre>
|
||
<p><strong>What this notation means in accounting</strong>: “We
|
||
<strong>purchased</strong> 225,033 satoshis at a cost of 0.000888 EUR
|
||
per satoshi”</p>
|
||
<p><strong>What actually happened</strong>: “We
|
||
<strong>received</strong> 225,033 satoshis as payment for a debt”</p>
|
||
<p><strong>Economic Difference</strong>: - <strong>Purchase</strong>:
|
||
You exchange cash for an asset (buying Bitcoin) - <strong>Payment
|
||
Receipt</strong>: You receive an asset in settlement of a receivable</p>
|
||
<p><strong>Accounting Substance vs. Form</strong>: -
|
||
<strong>Form</strong>: The transaction looks like a Bitcoin purchase -
|
||
<strong>Substance</strong>: The transaction is actually a receivable
|
||
collection</p>
|
||
<p><strong>GAAP Principle (ASC 105-10-05)</strong>: > “Accounting
|
||
should reflect the economic substance of transactions, not merely their
|
||
legal form.”</p>
|
||
<p><strong>Why This Creates Issues</strong>:</p>
|
||
<ol type="1">
|
||
<li><strong>Cost Basis Tracking</strong>: For tax purposes, the “cost”
|
||
of Bitcoin received as payment should be its fair market value at
|
||
receipt, not the receivable amount</li>
|
||
<li><strong>Price Database Pollution</strong>: Beancount’s price
|
||
database now contains “prices” that aren’t real market prices</li>
|
||
<li><strong>Auditor Confusion</strong>: An auditor reviewing this would
|
||
question why purchase prices don’t match market rates</li>
|
||
</ol>
|
||
<p><strong>Proper Accounting Approach</strong>:</p>
|
||
<pre class="beancount"><code>; 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</code></pre>
|
||
<hr />
|
||
<h3 id="issue-5-misnamed-function-and-incorrect-usage">Issue 5: Misnamed
|
||
Function and Incorrect Usage</h3>
|
||
<p><strong>Problem</strong>: Function is called
|
||
<code>format_net_settlement_entry</code>, but it’s used for simple
|
||
payments that aren’t true net settlements.</p>
|
||
<p><strong>Example from User’s Transaction</strong>: - Receivable:
|
||
200.00 EUR - Payable: 0.00 EUR - Net: 200.00 EUR (this is just a
|
||
<strong>payment</strong>, not a <strong>settlement</strong>)</p>
|
||
<p><strong>Accounting Terminology</strong>:</p>
|
||
<ul>
|
||
<li><strong>Payment</strong>: Settling a single obligation (receivable
|
||
OR payable)</li>
|
||
<li><strong>Net Settlement</strong>: Offsetting multiple obligations
|
||
(receivable AND payable)</li>
|
||
</ul>
|
||
<p><strong>When Net Settlement is Appropriate</strong>:</p>
|
||
<pre><code>User owes Castle: 555.00 EUR (receivable)
|
||
Castle owes User: 38.00 EUR (payable)
|
||
Net amount due: 517.00 EUR (true settlement)</code></pre>
|
||
<p>Proper three-posting entry:</p>
|
||
<pre class="beancount"><code>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 ✓</code></pre>
|
||
<p><strong>When Two Postings Suffice</strong>:</p>
|
||
<pre><code>User owes Castle: 200.00 EUR (receivable)
|
||
Castle owes User: 0.00 EUR (no payable)
|
||
Amount due: 200.00 EUR (simple payment)</code></pre>
|
||
<p>Simpler two-posting entry:</p>
|
||
<pre class="beancount"><code>Assets:Bitcoin:Lightning 225033 SATS @@ 200.00 EUR
|
||
Assets:Receivable:User -200.00 EUR</code></pre>
|
||
<p><strong>Best Practice</strong>: Use the simplest journal entry
|
||
structure that accurately represents the transaction.</p>
|
||
<p><strong>Recommendation</strong>: 1. Rename function to
|
||
<code>format_payment_entry</code> or
|
||
<code>format_receivable_payment_entry</code> 2. Create separate
|
||
<code>format_net_settlement_entry</code> for true netting scenarios 3.
|
||
Use conditional logic to choose 2-posting vs 3-posting based on whether
|
||
both receivables AND payables exist</p>
|
||
<hr />
|
||
<h2 id="traditional-accounting-approaches">Traditional Accounting
|
||
Approaches</h2>
|
||
<h3
|
||
id="approach-1-record-bitcoin-at-fair-market-value-tax-compliant">Approach
|
||
1: Record Bitcoin at Fair Market Value (Tax Compliant)</h3>
|
||
<pre class="beancount"><code>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</code></pre>
|
||
<p><strong>Pros</strong>: - ✅ Tax compliant (establishes cost basis) -
|
||
✅ Recognizes exchange gain/loss - ✅ Uses actual market rates - ✅
|
||
Audit trail for cryptocurrency receipts</p>
|
||
<p><strong>Cons</strong>: - ❌ Requires real-time price feeds - ❌
|
||
Creates taxable events</p>
|
||
<hr />
|
||
<h3
|
||
id="approach-2-simplified-eur-only-ledger-no-sats-positions">Approach 2:
|
||
Simplified EUR-Only Ledger (No SATS Positions)</h3>
|
||
<pre class="beancount"><code>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</code></pre>
|
||
<p><strong>Pros</strong>: - ✅ Simple and clean - ✅ EUR positions match
|
||
accounting reality - ✅ SATS tracked in metadata for reference - ✅ No
|
||
artificial price notation</p>
|
||
<p><strong>Cons</strong>: - ❌ SATS not queryable via Beancount
|
||
positions - ❌ Requires metadata parsing for SATS balances</p>
|
||
<hr />
|
||
<h3
|
||
id="approach-3-true-net-settlement-when-both-obligations-exist">Approach
|
||
3: True Net Settlement (When Both Obligations Exist)</h3>
|
||
<pre class="beancount"><code>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</code></pre>
|
||
<p><strong>When to Use</strong>: Only when <strong>both</strong>
|
||
receivables and payables exist and you’re truly netting them.</p>
|
||
<hr />
|
||
<h2 id="recommendations">Recommendations</h2>
|
||
<h3 id="priority-1-immediate-fixes-easy-wins">Priority 1: Immediate
|
||
Fixes (Easy Wins)</h3>
|
||
<h4 id="remove-zero-amount-postings">1.1 Remove Zero-Amount
|
||
Postings</h4>
|
||
<p><strong>File</strong>: <code>beancount_format.py:739-760</code></p>
|
||
<p><strong>Current Code</strong>:</p>
|
||
<div class="sourceCode" id="cb23"><pre
|
||
class="sourceCode python"><code class="sourceCode python"><span id="cb23-1"><a href="#cb23-1" aria-hidden="true" tabindex="-1"></a>postings <span class="op">=</span> [</span>
|
||
<span id="cb23-2"><a href="#cb23-2" aria-hidden="true" tabindex="-1"></a> {...}, <span class="co"># Lightning</span></span>
|
||
<span id="cb23-3"><a href="#cb23-3" aria-hidden="true" tabindex="-1"></a> {...}, <span class="co"># Receivable</span></span>
|
||
<span id="cb23-4"><a href="#cb23-4" aria-hidden="true" tabindex="-1"></a> { <span class="co"># Payable (always included, even if 0.00)</span></span>
|
||
<span id="cb23-5"><a href="#cb23-5" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: payable_account,</span>
|
||
<span id="cb23-6"><a href="#cb23-6" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"</span><span class="sc">{</span><span class="bu">abs</span>(total_payable_fiat)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb23-7"><a href="#cb23-7" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {}</span>
|
||
<span id="cb23-8"><a href="#cb23-8" aria-hidden="true" tabindex="-1"></a> }</span>
|
||
<span id="cb23-9"><a href="#cb23-9" aria-hidden="true" tabindex="-1"></a>]</span></code></pre></div>
|
||
<p><strong>Fixed Code</strong>:</p>
|
||
<div class="sourceCode" id="cb24"><pre
|
||
class="sourceCode python"><code class="sourceCode python"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true" tabindex="-1"></a>postings <span class="op">=</span> [</span>
|
||
<span id="cb24-2"><a href="#cb24-2" aria-hidden="true" tabindex="-1"></a> {</span>
|
||
<span id="cb24-3"><a href="#cb24-3" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: payment_account,</span>
|
||
<span id="cb24-4"><a href="#cb24-4" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"</span><span class="sc">{</span><span class="bu">abs</span>(amount_sats)<span class="sc">}</span><span class="ss"> SATS @@ </span><span class="sc">{</span><span class="bu">abs</span>(net_fiat_amount)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb24-5"><a href="#cb24-5" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {<span class="st">"payment-hash"</span>: payment_hash} <span class="cf">if</span> payment_hash <span class="cf">else</span> {}</span>
|
||
<span id="cb24-6"><a href="#cb24-6" aria-hidden="true" tabindex="-1"></a> },</span>
|
||
<span id="cb24-7"><a href="#cb24-7" aria-hidden="true" tabindex="-1"></a> {</span>
|
||
<span id="cb24-8"><a href="#cb24-8" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: receivable_account,</span>
|
||
<span id="cb24-9"><a href="#cb24-9" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"-</span><span class="sc">{</span><span class="bu">abs</span>(total_receivable_fiat)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb24-10"><a href="#cb24-10" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {<span class="st">"sats-equivalent"</span>: <span class="bu">str</span>(<span class="bu">abs</span>(amount_sats))}</span>
|
||
<span id="cb24-11"><a href="#cb24-11" aria-hidden="true" tabindex="-1"></a> }</span>
|
||
<span id="cb24-12"><a href="#cb24-12" aria-hidden="true" tabindex="-1"></a>]</span>
|
||
<span id="cb24-13"><a href="#cb24-13" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb24-14"><a href="#cb24-14" aria-hidden="true" tabindex="-1"></a><span class="co"># Only add payable posting if there's actually a payable to clear</span></span>
|
||
<span id="cb24-15"><a href="#cb24-15" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> total_payable_fiat <span class="op">></span> <span class="dv">0</span>:</span>
|
||
<span id="cb24-16"><a href="#cb24-16" aria-hidden="true" tabindex="-1"></a> postings.append({</span>
|
||
<span id="cb24-17"><a href="#cb24-17" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: payable_account,</span>
|
||
<span id="cb24-18"><a href="#cb24-18" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"</span><span class="sc">{</span><span class="bu">abs</span>(total_payable_fiat)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb24-19"><a href="#cb24-19" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {}</span>
|
||
<span id="cb24-20"><a href="#cb24-20" aria-hidden="true" tabindex="-1"></a> })</span></code></pre></div>
|
||
<p><strong>Impact</strong>: Cleaner journal, professional presentation,
|
||
easier auditing</p>
|
||
<hr />
|
||
<h4 id="choose-one-sats-tracking-method">1.2 Choose One SATS Tracking
|
||
Method</h4>
|
||
<p><strong>Decision Required</strong>: Select either position-based OR
|
||
metadata-based satoshi tracking.</p>
|
||
<p><strong>Option A - Keep Metadata Approach</strong> (recommended for
|
||
Castle):</p>
|
||
<div class="sourceCode" id="cb25"><pre
|
||
class="sourceCode python"><code class="sourceCode python"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true" tabindex="-1"></a><span class="co"># In format_net_settlement_entry()</span></span>
|
||
<span id="cb25-2"><a href="#cb25-2" aria-hidden="true" tabindex="-1"></a>postings <span class="op">=</span> [</span>
|
||
<span id="cb25-3"><a href="#cb25-3" aria-hidden="true" tabindex="-1"></a> {</span>
|
||
<span id="cb25-4"><a href="#cb25-4" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: payment_account,</span>
|
||
<span id="cb25-5"><a href="#cb25-5" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"</span><span class="sc">{</span><span class="bu">abs</span>(net_fiat_amount)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>, <span class="co"># EUR only</span></span>
|
||
<span id="cb25-6"><a href="#cb25-6" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {</span>
|
||
<span id="cb25-7"><a href="#cb25-7" aria-hidden="true" tabindex="-1"></a> <span class="st">"sats-received"</span>: <span class="bu">str</span>(<span class="bu">abs</span>(amount_sats)),</span>
|
||
<span id="cb25-8"><a href="#cb25-8" aria-hidden="true" tabindex="-1"></a> <span class="st">"payment-hash"</span>: payment_hash</span>
|
||
<span id="cb25-9"><a href="#cb25-9" aria-hidden="true" tabindex="-1"></a> }</span>
|
||
<span id="cb25-10"><a href="#cb25-10" aria-hidden="true" tabindex="-1"></a> },</span>
|
||
<span id="cb25-11"><a href="#cb25-11" aria-hidden="true" tabindex="-1"></a> {</span>
|
||
<span id="cb25-12"><a href="#cb25-12" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: receivable_account,</span>
|
||
<span id="cb25-13"><a href="#cb25-13" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"-</span><span class="sc">{</span><span class="bu">abs</span>(total_receivable_fiat)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb25-14"><a href="#cb25-14" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {<span class="st">"sats-cleared"</span>: <span class="bu">str</span>(<span class="bu">abs</span>(amount_sats))}</span>
|
||
<span id="cb25-15"><a href="#cb25-15" aria-hidden="true" tabindex="-1"></a> }</span>
|
||
<span id="cb25-16"><a href="#cb25-16" aria-hidden="true" tabindex="-1"></a>]</span></code></pre></div>
|
||
<p><strong>Option B - Use Position-Based Tracking</strong>:</p>
|
||
<div class="sourceCode" id="cb26"><pre
|
||
class="sourceCode python"><code class="sourceCode python"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Remove sats-equivalent metadata entirely</span></span>
|
||
<span id="cb26-2"><a href="#cb26-2" aria-hidden="true" tabindex="-1"></a>postings <span class="op">=</span> [</span>
|
||
<span id="cb26-3"><a href="#cb26-3" aria-hidden="true" tabindex="-1"></a> {</span>
|
||
<span id="cb26-4"><a href="#cb26-4" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: payment_account,</span>
|
||
<span id="cb26-5"><a href="#cb26-5" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"</span><span class="sc">{</span><span class="bu">abs</span>(amount_sats)<span class="sc">}</span><span class="ss"> SATS @@ </span><span class="sc">{</span><span class="bu">abs</span>(net_fiat_amount)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb26-6"><a href="#cb26-6" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {<span class="st">"payment-hash"</span>: payment_hash}</span>
|
||
<span id="cb26-7"><a href="#cb26-7" aria-hidden="true" tabindex="-1"></a> },</span>
|
||
<span id="cb26-8"><a href="#cb26-8" aria-hidden="true" tabindex="-1"></a> {</span>
|
||
<span id="cb26-9"><a href="#cb26-9" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: receivable_account,</span>
|
||
<span id="cb26-10"><a href="#cb26-10" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"-</span><span class="sc">{</span><span class="bu">abs</span>(total_receivable_fiat)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb26-11"><a href="#cb26-11" aria-hidden="true" tabindex="-1"></a> <span class="co"># No sats-equivalent needed - queryable via price database</span></span>
|
||
<span id="cb26-12"><a href="#cb26-12" aria-hidden="true" tabindex="-1"></a> }</span>
|
||
<span id="cb26-13"><a href="#cb26-13" aria-hidden="true" tabindex="-1"></a>]</span></code></pre></div>
|
||
<p><strong>Recommendation</strong>: Choose Option A (metadata) for
|
||
consistency with Castle’s architecture.</p>
|
||
<hr />
|
||
<h4 id="rename-function-for-clarity">1.3 Rename Function for
|
||
Clarity</h4>
|
||
<p><strong>File</strong>: <code>beancount_format.py</code></p>
|
||
<p><strong>Current</strong>:
|
||
<code>format_net_settlement_entry()</code></p>
|
||
<p><strong>New</strong>: <code>format_receivable_payment_entry()</code>
|
||
or <code>format_payment_settlement_entry()</code></p>
|
||
<p><strong>Rationale</strong>: More accurately describes what the
|
||
function does (processes payments, not always net settlements)</p>
|
||
<hr />
|
||
<h3 id="priority-2-medium-term-improvements-compliance">Priority 2:
|
||
Medium-Term Improvements (Compliance)</h3>
|
||
<h4 id="add-exchange-gainloss-tracking">2.1 Add Exchange Gain/Loss
|
||
Tracking</h4>
|
||
<p><strong>File</strong>: <code>tasks.py:259-276</code> (get balance and
|
||
calculate settlement)</p>
|
||
<p><strong>New Logic</strong>:</p>
|
||
<div class="sourceCode" id="cb27"><pre
|
||
class="sourceCode python"><code class="sourceCode python"><span id="cb27-1"><a href="#cb27-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Get user's current balance</span></span>
|
||
<span id="cb27-2"><a href="#cb27-2" aria-hidden="true" tabindex="-1"></a>balance <span class="op">=</span> <span class="cf">await</span> fava.get_user_balance(user_id)</span>
|
||
<span id="cb27-3"><a href="#cb27-3" aria-hidden="true" tabindex="-1"></a>fiat_balances <span class="op">=</span> balance.get(<span class="st">"fiat_balances"</span>, {})</span>
|
||
<span id="cb27-4"><a href="#cb27-4" aria-hidden="true" tabindex="-1"></a>total_fiat_balance <span class="op">=</span> fiat_balances.get(fiat_currency, Decimal(<span class="dv">0</span>))</span>
|
||
<span id="cb27-5"><a href="#cb27-5" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb27-6"><a href="#cb27-6" aria-hidden="true" tabindex="-1"></a><span class="co"># Calculate expected fiat value of SATS payment at current market rate</span></span>
|
||
<span id="cb27-7"><a href="#cb27-7" aria-hidden="true" tabindex="-1"></a>market_rate <span class="op">=</span> <span class="cf">await</span> get_current_sats_eur_rate() <span class="co"># New function needed</span></span>
|
||
<span id="cb27-8"><a href="#cb27-8" aria-hidden="true" tabindex="-1"></a>market_value <span class="op">=</span> Decimal(amount_sats) <span class="op">*</span> market_rate</span>
|
||
<span id="cb27-9"><a href="#cb27-9" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb27-10"><a href="#cb27-10" aria-hidden="true" tabindex="-1"></a><span class="co"># Calculate exchange variance</span></span>
|
||
<span id="cb27-11"><a href="#cb27-11" aria-hidden="true" tabindex="-1"></a>receivable_amount <span class="op">=</span> <span class="bu">abs</span>(total_fiat_balance) <span class="cf">if</span> total_fiat_balance <span class="op">></span> <span class="dv">0</span> <span class="cf">else</span> Decimal(<span class="dv">0</span>)</span>
|
||
<span id="cb27-12"><a href="#cb27-12" aria-hidden="true" tabindex="-1"></a>exchange_variance <span class="op">=</span> market_value <span class="op">-</span> receivable_amount</span>
|
||
<span id="cb27-13"><a href="#cb27-13" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb27-14"><a href="#cb27-14" aria-hidden="true" tabindex="-1"></a><span class="co"># If variance is material (> 1 cent), create exchange gain/loss posting</span></span>
|
||
<span id="cb27-15"><a href="#cb27-15" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="bu">abs</span>(exchange_variance) <span class="op">></span> Decimal(<span class="st">"0.01"</span>):</span>
|
||
<span id="cb27-16"><a href="#cb27-16" aria-hidden="true" tabindex="-1"></a> <span class="co"># Add exchange gain/loss to postings</span></span>
|
||
<span id="cb27-17"><a href="#cb27-17" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> exchange_variance <span class="op">></span> <span class="dv">0</span>:</span>
|
||
<span id="cb27-18"><a href="#cb27-18" aria-hidden="true" tabindex="-1"></a> <span class="co"># Gain: payment worth more than receivable</span></span>
|
||
<span id="cb27-19"><a href="#cb27-19" aria-hidden="true" tabindex="-1"></a> exchange_account <span class="op">=</span> <span class="st">"Revenue:Foreign-Exchange-Gain"</span></span>
|
||
<span id="cb27-20"><a href="#cb27-20" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb27-21"><a href="#cb27-21" aria-hidden="true" tabindex="-1"></a> <span class="co"># Loss: payment worth less than receivable</span></span>
|
||
<span id="cb27-22"><a href="#cb27-22" aria-hidden="true" tabindex="-1"></a> exchange_account <span class="op">=</span> <span class="st">"Expenses:Foreign-Exchange-Loss"</span></span>
|
||
<span id="cb27-23"><a href="#cb27-23" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb27-24"><a href="#cb27-24" aria-hidden="true" tabindex="-1"></a> <span class="co"># Include in entry creation</span></span>
|
||
<span id="cb27-25"><a href="#cb27-25" aria-hidden="true" tabindex="-1"></a> exchange_posting <span class="op">=</span> {</span>
|
||
<span id="cb27-26"><a href="#cb27-26" aria-hidden="true" tabindex="-1"></a> <span class="st">"account"</span>: exchange_account,</span>
|
||
<span id="cb27-27"><a href="#cb27-27" aria-hidden="true" tabindex="-1"></a> <span class="st">"amount"</span>: <span class="ss">f"</span><span class="sc">{</span><span class="bu">abs</span>(exchange_variance)<span class="sc">:.2f}</span><span class="ss"> </span><span class="sc">{</span>fiat_currency<span class="sc">}</span><span class="ss">"</span>,</span>
|
||
<span id="cb27-28"><a href="#cb27-28" aria-hidden="true" tabindex="-1"></a> <span class="st">"meta"</span>: {</span>
|
||
<span id="cb27-29"><a href="#cb27-29" aria-hidden="true" tabindex="-1"></a> <span class="st">"sats-amount"</span>: <span class="bu">str</span>(amount_sats),</span>
|
||
<span id="cb27-30"><a href="#cb27-30" aria-hidden="true" tabindex="-1"></a> <span class="st">"market-rate"</span>: <span class="bu">str</span>(market_rate),</span>
|
||
<span id="cb27-31"><a href="#cb27-31" aria-hidden="true" tabindex="-1"></a> <span class="st">"receivable-amount"</span>: <span class="bu">str</span>(receivable_amount)</span>
|
||
<span id="cb27-32"><a href="#cb27-32" aria-hidden="true" tabindex="-1"></a> }</span>
|
||
<span id="cb27-33"><a href="#cb27-33" aria-hidden="true" tabindex="-1"></a> }</span></code></pre></div>
|
||
<p><strong>Benefits</strong>: - ✅ Tax compliance - ✅ Accurate
|
||
financial reporting - ✅ Audit trail for cryptocurrency gains/losses -
|
||
✅ Regulatory compliance (GAAP/IFRS)</p>
|
||
<hr />
|
||
<h4 id="implement-true-net-settlement-vs.-simple-payment-logic">2.2
|
||
Implement True Net Settlement vs. Simple Payment Logic</h4>
|
||
<p><strong>File</strong>: <code>tasks.py</code> or new
|
||
<code>payment_logic.py</code></p>
|
||
<div class="sourceCode" id="cb28"><pre
|
||
class="sourceCode python"><code class="sourceCode python"><span id="cb28-1"><a href="#cb28-1" aria-hidden="true" tabindex="-1"></a><span class="cf">async</span> <span class="kw">def</span> create_payment_entry(</span>
|
||
<span id="cb28-2"><a href="#cb28-2" aria-hidden="true" tabindex="-1"></a> user_id: <span class="bu">str</span>,</span>
|
||
<span id="cb28-3"><a href="#cb28-3" aria-hidden="true" tabindex="-1"></a> amount_sats: <span class="bu">int</span>,</span>
|
||
<span id="cb28-4"><a href="#cb28-4" aria-hidden="true" tabindex="-1"></a> fiat_amount: Decimal,</span>
|
||
<span id="cb28-5"><a href="#cb28-5" aria-hidden="true" tabindex="-1"></a> fiat_currency: <span class="bu">str</span>,</span>
|
||
<span id="cb28-6"><a href="#cb28-6" aria-hidden="true" tabindex="-1"></a> payment_hash: <span class="bu">str</span></span>
|
||
<span id="cb28-7"><a href="#cb28-7" aria-hidden="true" tabindex="-1"></a>):</span>
|
||
<span id="cb28-8"><a href="#cb28-8" aria-hidden="true" tabindex="-1"></a> <span class="co">"""</span></span>
|
||
<span id="cb28-9"><a href="#cb28-9" aria-hidden="true" tabindex="-1"></a><span class="co"> Create appropriate payment entry based on user's balance situation.</span></span>
|
||
<span id="cb28-10"><a href="#cb28-10" aria-hidden="true" tabindex="-1"></a><span class="co"> Uses 2-posting for simple payments, 3-posting for net settlements.</span></span>
|
||
<span id="cb28-11"><a href="#cb28-11" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb28-12"><a href="#cb28-12" aria-hidden="true" tabindex="-1"></a> <span class="co"># Get user balance</span></span>
|
||
<span id="cb28-13"><a href="#cb28-13" aria-hidden="true" tabindex="-1"></a> balance <span class="op">=</span> <span class="cf">await</span> fava.get_user_balance(user_id)</span>
|
||
<span id="cb28-14"><a href="#cb28-14" aria-hidden="true" tabindex="-1"></a> fiat_balances <span class="op">=</span> balance.get(<span class="st">"fiat_balances"</span>, {})</span>
|
||
<span id="cb28-15"><a href="#cb28-15" aria-hidden="true" tabindex="-1"></a> total_balance <span class="op">=</span> fiat_balances.get(fiat_currency, Decimal(<span class="dv">0</span>))</span>
|
||
<span id="cb28-16"><a href="#cb28-16" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb28-17"><a href="#cb28-17" aria-hidden="true" tabindex="-1"></a> receivable_amount <span class="op">=</span> Decimal(<span class="dv">0</span>)</span>
|
||
<span id="cb28-18"><a href="#cb28-18" aria-hidden="true" tabindex="-1"></a> payable_amount <span class="op">=</span> Decimal(<span class="dv">0</span>)</span>
|
||
<span id="cb28-19"><a href="#cb28-19" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb28-20"><a href="#cb28-20" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> total_balance <span class="op">></span> <span class="dv">0</span>:</span>
|
||
<span id="cb28-21"><a href="#cb28-21" aria-hidden="true" tabindex="-1"></a> receivable_amount <span class="op">=</span> total_balance</span>
|
||
<span id="cb28-22"><a href="#cb28-22" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> total_balance <span class="op"><</span> <span class="dv">0</span>:</span>
|
||
<span id="cb28-23"><a href="#cb28-23" aria-hidden="true" tabindex="-1"></a> payable_amount <span class="op">=</span> <span class="bu">abs</span>(total_balance)</span>
|
||
<span id="cb28-24"><a href="#cb28-24" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb28-25"><a href="#cb28-25" aria-hidden="true" tabindex="-1"></a> <span class="co"># Determine entry type</span></span>
|
||
<span id="cb28-26"><a href="#cb28-26" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> receivable_amount <span class="op">></span> <span class="dv">0</span> <span class="kw">and</span> payable_amount <span class="op">></span> <span class="dv">0</span>:</span>
|
||
<span id="cb28-27"><a href="#cb28-27" aria-hidden="true" tabindex="-1"></a> <span class="co"># TRUE NET SETTLEMENT: Both obligations exist</span></span>
|
||
<span id="cb28-28"><a href="#cb28-28" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="cf">await</span> format_net_settlement_entry(</span>
|
||
<span id="cb28-29"><a href="#cb28-29" aria-hidden="true" tabindex="-1"></a> user_id<span class="op">=</span>user_id,</span>
|
||
<span id="cb28-30"><a href="#cb28-30" aria-hidden="true" tabindex="-1"></a> amount_sats<span class="op">=</span>amount_sats,</span>
|
||
<span id="cb28-31"><a href="#cb28-31" aria-hidden="true" tabindex="-1"></a> receivable_amount<span class="op">=</span>receivable_amount,</span>
|
||
<span id="cb28-32"><a href="#cb28-32" aria-hidden="true" tabindex="-1"></a> payable_amount<span class="op">=</span>payable_amount,</span>
|
||
<span id="cb28-33"><a href="#cb28-33" aria-hidden="true" tabindex="-1"></a> fiat_amount<span class="op">=</span>fiat_amount,</span>
|
||
<span id="cb28-34"><a href="#cb28-34" aria-hidden="true" tabindex="-1"></a> fiat_currency<span class="op">=</span>fiat_currency,</span>
|
||
<span id="cb28-35"><a href="#cb28-35" aria-hidden="true" tabindex="-1"></a> payment_hash<span class="op">=</span>payment_hash</span>
|
||
<span id="cb28-36"><a href="#cb28-36" aria-hidden="true" tabindex="-1"></a> )</span>
|
||
<span id="cb28-37"><a href="#cb28-37" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> receivable_amount <span class="op">></span> <span class="dv">0</span>:</span>
|
||
<span id="cb28-38"><a href="#cb28-38" aria-hidden="true" tabindex="-1"></a> <span class="co"># SIMPLE RECEIVABLE PAYMENT: Only receivable exists</span></span>
|
||
<span id="cb28-39"><a href="#cb28-39" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="cf">await</span> format_receivable_payment_entry(</span>
|
||
<span id="cb28-40"><a href="#cb28-40" aria-hidden="true" tabindex="-1"></a> user_id<span class="op">=</span>user_id,</span>
|
||
<span id="cb28-41"><a href="#cb28-41" aria-hidden="true" tabindex="-1"></a> amount_sats<span class="op">=</span>amount_sats,</span>
|
||
<span id="cb28-42"><a href="#cb28-42" aria-hidden="true" tabindex="-1"></a> receivable_amount<span class="op">=</span>receivable_amount,</span>
|
||
<span id="cb28-43"><a href="#cb28-43" aria-hidden="true" tabindex="-1"></a> fiat_amount<span class="op">=</span>fiat_amount,</span>
|
||
<span id="cb28-44"><a href="#cb28-44" aria-hidden="true" tabindex="-1"></a> fiat_currency<span class="op">=</span>fiat_currency,</span>
|
||
<span id="cb28-45"><a href="#cb28-45" aria-hidden="true" tabindex="-1"></a> payment_hash<span class="op">=</span>payment_hash</span>
|
||
<span id="cb28-46"><a href="#cb28-46" aria-hidden="true" tabindex="-1"></a> )</span>
|
||
<span id="cb28-47"><a href="#cb28-47" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb28-48"><a href="#cb28-48" aria-hidden="true" tabindex="-1"></a> <span class="co"># PAYABLE PAYMENT: Castle paying user (different flow)</span></span>
|
||
<span id="cb28-49"><a href="#cb28-49" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="cf">await</span> format_payable_payment_entry(...)</span></code></pre></div>
|
||
<hr />
|
||
<h3 id="priority-3-long-term-architectural-decisions">Priority 3:
|
||
Long-Term Architectural Decisions</h3>
|
||
<h4 id="establish-primary-currency-hierarchy">3.1 Establish Primary
|
||
Currency Hierarchy</h4>
|
||
<p><strong>Current Issue</strong>: Mixed approach (EUR positions with
|
||
SATS metadata, but also SATS positions with @ notation)</p>
|
||
<p><strong>Decision Required</strong>: Choose ONE of the following
|
||
architectures:</p>
|
||
<p><strong>Architecture A - EUR Primary, SATS Secondary</strong>
|
||
(recommended):</p>
|
||
<pre class="beancount"><code>; 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"</code></pre>
|
||
<p><strong>Architecture B - SATS Primary, EUR Secondary</strong>:</p>
|
||
<pre class="beancount"><code>; 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"</code></pre>
|
||
<p><strong>Recommendation</strong>: 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</p>
|
||
<hr />
|
||
<h4 id="consider-separate-ledger-for-cryptocurrency-holdings">3.2
|
||
Consider Separate Ledger for Cryptocurrency Holdings</h4>
|
||
<p><strong>Advanced Approach</strong>: Separate cryptocurrency movements
|
||
from fiat accounting</p>
|
||
<p><strong>Main Ledger</strong> (EUR-denominated):</p>
|
||
<pre class="beancount"><code>2025-11-12 * "Payment received from user"
|
||
Assets:Bitcoin-Custody:User-375ec158 200.00 EUR
|
||
Assets:Receivable:User-375ec158 -200.00 EUR</code></pre>
|
||
<p><strong>Cryptocurrency Sub-Ledger</strong> (SATS-denominated):</p>
|
||
<pre class="beancount"><code>2025-11-12 * "Lightning payment received"
|
||
Assets:Bitcoin:Lightning:Castle 225033 SATS
|
||
Assets:Bitcoin:Custody:User-375ec 225033 SATS</code></pre>
|
||
<p><strong>Benefits</strong>: - ✅ Clean separation of concerns - ✅
|
||
Cryptocurrency movements tracked independently - ✅ Fiat accounting
|
||
unaffected by Bitcoin volatility - ✅ Can generate separate financial
|
||
statements</p>
|
||
<p><strong>Drawbacks</strong>: - ❌ Increased complexity - ❌
|
||
Reconciliation between ledgers required - ❌ Two sets of books to
|
||
maintain</p>
|
||
<hr />
|
||
<h2 id="code-files-requiring-changes">Code Files Requiring Changes</h2>
|
||
<h3 id="high-priority-immediate-fixes">High Priority (Immediate
|
||
Fixes)</h3>
|
||
<ol type="1">
|
||
<li><strong><code>beancount_format.py:739-760</code></strong>
|
||
<ul>
|
||
<li>Remove zero-amount postings</li>
|
||
<li>Make payable posting conditional</li>
|
||
</ul></li>
|
||
<li><strong><code>beancount_format.py:692</code></strong>
|
||
<ul>
|
||
<li>Rename function to <code>format_receivable_payment_entry</code></li>
|
||
</ul></li>
|
||
</ol>
|
||
<h3 id="medium-priority-compliance">Medium Priority (Compliance)</h3>
|
||
<ol start="3" type="1">
|
||
<li><strong><code>tasks.py:235-310</code></strong>
|
||
<ul>
|
||
<li>Add exchange gain/loss calculation</li>
|
||
<li>Implement payment vs. settlement logic</li>
|
||
</ul></li>
|
||
<li><strong>New file: <code>exchange_rates.py</code></strong>
|
||
<ul>
|
||
<li>Create <code>get_current_sats_eur_rate()</code> function</li>
|
||
<li>Implement price feed integration</li>
|
||
</ul></li>
|
||
<li><strong><code>beancount_format.py</code></strong>
|
||
<ul>
|
||
<li>Create new <code>format_net_settlement_entry()</code> for true
|
||
netting</li>
|
||
<li>Create <code>format_receivable_payment_entry()</code> for simple
|
||
payments</li>
|
||
</ul></li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="testing-requirements">Testing Requirements</h2>
|
||
<h3 id="test-case-1-simple-receivable-payment-no-payable">Test Case 1:
|
||
Simple Receivable Payment (No Payable)</h3>
|
||
<p><strong>Setup</strong>: - User has receivable: 200.00 EUR - User has
|
||
payable: 0.00 EUR - User pays: 225,033 SATS</p>
|
||
<p><strong>Expected Entry</strong> (after fixes):</p>
|
||
<pre class="beancount"><code>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"</code></pre>
|
||
<p><strong>Verify</strong>: - ✅ Only 2 postings (no zero-amount
|
||
payable) - ✅ Entry balances - ✅ SATS tracked in metadata - ✅ User
|
||
balance becomes 0 (both EUR and SATS)</p>
|
||
<hr />
|
||
<h3 id="test-case-2-true-net-settlement">Test Case 2: True Net
|
||
Settlement</h3>
|
||
<p><strong>Setup</strong>: - 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)</p>
|
||
<p><strong>Expected Entry</strong>:</p>
|
||
<pre class="beancount"><code>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</code></pre>
|
||
<p><strong>Verify</strong>: - ✅ 3 postings (receivable + payable
|
||
cleared) - ✅ Net amount = receivable - payable - ✅ Both balances
|
||
become 0 - ✅ Mathematically balanced</p>
|
||
<hr />
|
||
<h3 id="test-case-3-exchange-gainloss-future">Test Case 3: Exchange
|
||
Gain/Loss (Future)</h3>
|
||
<p><strong>Setup</strong>: - 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</p>
|
||
<p><strong>Expected Entry</strong> (with exchange tracking):</p>
|
||
<pre class="beancount"><code>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</code></pre>
|
||
<p><strong>Verify</strong>: - ✅ Bitcoin recorded at fair market value -
|
||
✅ Exchange loss recognized - ✅ Receivable cleared at book value - ✅
|
||
Entry balances</p>
|
||
<hr />
|
||
<h2 id="conclusion">Conclusion</h2>
|
||
<h3 id="summary-of-issues">Summary of Issues</h3>
|
||
<table>
|
||
<colgroup>
|
||
<col style="width: 12%" />
|
||
<col style="width: 18%" />
|
||
<col style="width: 34%" />
|
||
<col style="width: 34%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr>
|
||
<th>Issue</th>
|
||
<th>Severity</th>
|
||
<th>Accounting Impact</th>
|
||
<th>Recommended Action</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Zero-amount postings</td>
|
||
<td>Low</td>
|
||
<td>Presentation only</td>
|
||
<td>Remove immediately</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Redundant SATS tracking</td>
|
||
<td>Low</td>
|
||
<td>Storage/efficiency</td>
|
||
<td>Choose one method</td>
|
||
</tr>
|
||
<tr>
|
||
<td>No exchange gain/loss</td>
|
||
<td><strong>High</strong></td>
|
||
<td>Financial accuracy</td>
|
||
<td>Implement for compliance</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Semantic misuse of @</td>
|
||
<td>Medium</td>
|
||
<td>Audit clarity</td>
|
||
<td>Consider EUR-only positions</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Misnamed function</td>
|
||
<td>Low</td>
|
||
<td>Code clarity</td>
|
||
<td>Rename function</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<h3 id="professional-assessment">Professional Assessment</h3>
|
||
<p><strong>Is this “best practice” accounting?</strong>
|
||
<strong>No</strong>, this implementation deviates from traditional
|
||
accounting standards in several ways.</p>
|
||
<p><strong>Is it acceptable for Castle’s use case?</strong> <strong>Yes,
|
||
with modifications</strong>, it’s a reasonable pragmatic solution for a
|
||
novel problem (cryptocurrency payments of fiat debts).</p>
|
||
<p><strong>Critical improvements needed</strong>: 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)</p>
|
||
<p><strong>The fundamental challenge</strong>: 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.</p>
|
||
<h3 id="next-steps">Next Steps</h3>
|
||
<ol type="1">
|
||
<li><strong>Week 1</strong>: Implement Priority 1 fixes (remove zero
|
||
postings, rename function)</li>
|
||
<li><strong>Week 2-3</strong>: Design and implement exchange gain/loss
|
||
tracking</li>
|
||
<li><strong>Week 4</strong>: Add payment vs. settlement logic</li>
|
||
<li><strong>Ongoing</strong>: Monitor regulatory guidance on
|
||
cryptocurrency accounting</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="references">References</h2>
|
||
<ul>
|
||
<li><strong>FASB ASC 830</strong>: Foreign Currency Matters</li>
|
||
<li><strong>IAS 21</strong>: The Effects of Changes in Foreign Exchange
|
||
Rates</li>
|
||
<li><strong>FASB Concept Statement No. 2</strong>: Qualitative
|
||
Characteristics of Accounting Information</li>
|
||
<li><strong>ASC 105-10-05</strong>: Substance Over Form</li>
|
||
<li><strong>Beancount Documentation</strong>:
|
||
http://furius.ca/beancount/doc/index</li>
|
||
<li><strong>Castle Extension</strong>:
|
||
<code>docs/SATS-EQUIVALENT-METADATA.md</code></li>
|
||
<li><strong>BQL Analysis</strong>:
|
||
<code>docs/BQL-BALANCE-QUERIES.md</code></li>
|
||
</ul>
|
||
<hr />
|
||
<p><strong>Document Version</strong>: 1.0 <strong>Last Updated</strong>:
|
||
2025-01-12 <strong>Next Review</strong>: After Priority 1 fixes
|
||
implemented</p>
|
||
<hr />
|
||
<p><em>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.</em></p>
|
||
</body>
|
||
</html>
|