1 Title: Plain text accounting file from your bitcoin transactions
5 <p>A while back I wrote a small script to extract the Bitcoin
6 transactions in a wallet in the
7 <ahref="https://plaintextaccounting.org/">ledger plain text accounting
8 format</a>. The last few days I spent some time to get it working
9 better with more special cases. In case it can be useful for others,
14 # -*- coding: utf-8 -*-
15 # Copyright (c) 2023-2024 Petter Reinholdtsen
17 from decimal import Decimal
24 def format_float(num):
25 return numpy.format_float_positional(num, trim='-')
28 u'amount' : 'Assets:BTC:main',
32 '<some address>' : 'Assets:bankkonto',
33 '<some address>' : 'Assets:bankkonto',
37 proc = subprocess.Popen(cmd,stdout=subprocess.PIPE)
38 j = json.loads(proc.communicate()[0], parse_float=Decimal)
42 # get all transactions for all accounts / addresses
47 cmd = ['bitcoin-cli', 'listtransactions', '*', str(limit)]
49 txs.extend(exec_json(cmd))
51 # Useful for debugging
52 with open('transactions.json') as f:
53 txs.extend(json.load(f, parse_float=Decimal))
55 for tx in sorted(txs, key=lambda a: a['time']):
56 # print tx['category']
57 if 'abandoned' in tx and tx['abandoned']:
59 if 'confirmations' in tx and 0 >= tx['confirmations']:
61 when = time.strftime('%Y-%m-%d %H:%M', time.localtime(tx['time']))
70 print("%s %s" % (when, desc))
72 print(" ; to bitcoin address %s" % tx['address'])
74 print(" ; missing address in transaction, txid=%s" % tx['txid'])
75 print(f" ; amount={tx['amount']}")
77 print(f" ; fee={tx['fee']}")
78 for f in accounts.keys():
79 if f in tx and Decimal(0) != tx[f]:
81 print(" %-20s %s BTC" % (accounts[f], format_float(amount)))
82 if 'fee' in tx and Decimal(0) != tx['fee']:
83 # Make sure to list fee used in several transactions only once.
84 if 'fee' in tx and tx['txid'] in txidfee \
85 and tx['fee'] == txidfee[tx['txid']]:
89 print(" %-20s %s BTC" % (accounts['amount'], format_float(fee)))
90 print(" %-20s %s BTC" % ('Expences:BTC-fee', format_float(-fee)))
91 txidfee[tx['txid']] = tx['fee']
93 if 'address' in tx and tx['address'] in addresses:
94 print(" %s" % addresses[tx['address']])
96 if 'generate' == tx['category']:
97 print(" Income:BTC-mining")
99 if amount < Decimal(0):
100 print(f" Assets:unknown:sent:update-script-addr-{tx['address']}")
102 print(f" Assets:unknown:received:update-script-addr-{tx['address']}")
106 print("# Found %d transactions" % c)
108 print(f"# Warning: Limit {limit} reached, consider increasing limit.")
114 </pre></blockquote></p>
116 <p>It is more of a proof of concept, and I do not expect it to handle
117 all edge cases, but it worked for me, and perhaps you can find it
120 <p>To get a more interesting result, it is useful to map accounts sent
121 to or received from to accounting accounts, using the
122 <tt>addresses</tt> hash. As these will be very context dependent, I
123 leave out my list to allow each user to fill out their own list of
124 accounts. Out of the box, 'ledger reg BTC:main' should be able to
125 show the amount of BTCs present in the wallet at any given time in the
126 past. For other and more valuable analysis, a account plan need to be
127 set up in the <tt>addresses</tt> hash. Here is an example
131 2024-03-07 17:00 Donated to good cause
132 Assets:BTC:main -0.1 BTC
133 Assets:BTC:main -0.00001 BTC
134 Expences:BTC-fee 0.00001 BTC
135 Expences:donations 0.1 BTC
136 </pre></blockquote></p>
138 <p>It need a running Bitcoin Core daemon running, as it connect to it
139 using <tt>bitcoin-cli listtransactions * 100000</tt> to extract the
140 transactions listed in the Wallet.</p>
142 <p>As usual, if you use Bitcoin and want to show your support of my
143 activities, please send Bitcoin donations to my address
144 <b><a href="bitcoin:15oWEoG9dUPovwmUL9KWAnYRtNJEkP1u1b">15oWEoG9dUPovwmUL9KWAnYRtNJEkP1u1b</a></b>.</p>