A while back I wrote a small script to extract the Bitcoin
transactions in a wallet in the
#!/usr/bin/python3 # -*- coding: utf-8 -*- # Copyright (c) 2023-2024 Petter Reinholdtsen from decimal import Decimal import json import subprocess import time import numpy def format_float(num): return numpy.format_float_positional(num, trim='-') accounts = { u'amount' : 'Assets:BTC:main', } addresses = { '' : 'Assets:bankkonto', ' ' : 'Assets:bankkonto', } def exec_json(cmd): proc = subprocess.Popen(cmd,stdout=subprocess.PIPE) j = json.loads(proc.communicate()[0], parse_float=Decimal) return j def list_txs(): # get all transactions for all accounts / addresses c = 0 txs = [] txidfee = {} limit=100000 cmd = ['bitcoin-cli', 'listtransactions', '*', str(limit)] if True: txs.extend(exec_json(cmd)) else: # Useful for debugging with open('transactions.json') as f: txs.extend(json.load(f, parse_float=Decimal)) #print txs for tx in sorted(txs, key=lambda a: a['time']): # print tx['category'] if 'abandoned' in tx and tx['abandoned']: continue if 'confirmations' in tx and 0 >= tx['confirmations']: continue when = time.strftime('%Y-%m-%d %H:%M', time.localtime(tx['time'])) if 'message' in tx: desc = tx['message'] elif 'comment' in tx: desc = tx['comment'] elif 'label' in tx: desc = tx['label'] else: desc = 'n/a' print("%s %s" % (when, desc)) if 'address' in tx: print(" ; to bitcoin address %s" % tx['address']) else: print(" ; missing address in transaction, txid=%s" % tx['txid']) print(f" ; amount={tx['amount']}") if 'fee'in tx: print(f" ; fee={tx['fee']}") for f in accounts.keys(): if f in tx and Decimal(0) != tx[f]: amount = tx[f] print(" %-20s %s BTC" % (accounts[f], format_float(amount))) if 'fee' in tx and Decimal(0) != tx['fee']: # Make sure to list fee used in several transactions only once. if 'fee' in tx and tx['txid'] in txidfee \ and tx['fee'] == txidfee[tx['txid']]: True else: fee = tx['fee'] print(" %-20s %s BTC" % (accounts['amount'], format_float(fee))) print(" %-20s %s BTC" % ('Expences:BTC-fee', format_float(-fee))) txidfee[tx['txid']] = tx['fee'] if 'address' in tx and tx['address'] in addresses: print(" %s" % addresses[tx['address']]) else: if 'generate' == tx['category']: print(" Income:BTC-mining") else: if amount < Decimal(0): print(f" Assets:unknown:sent:update-script-addr-{tx['address']}") else: print(f" Assets:unknown:received:update-script-addr-{tx['address']}") print() c = c + 1 print("# Found %d transactions" % c) if limit == c: print(f"# Warning: Limit {limit} reached, consider increasing limit.") def main(): list_txs() main()
It is more of a proof of concept, and I do not expect it to handle all edge cases, but it worked for me, and perhaps you can find it useful too.
To get a more interesting result, it is useful to map accounts sent to or received from to accounting accounts, using the addresses hash. As these will be very context dependent, I leave out my list to allow each user to fill out their own list of accounts. Out of the box, 'ledger reg BTC:main' should be able to show the amount of BTCs present in the wallet at any given time in the past. For other and more valuable analysis, a account plan need to be set up in the addresses hash. Here is an example transaction:
2024-03-07 17:00 Donated to good cause Assets:BTC:main -0.1 BTC Assets:BTC:main -0.00001 BTC Expences:BTC-fee 0.00001 BTC Expences:donations 0.1 BTC
It need a running Bitcoin Core daemon running, as it connect to it using bitcoin-cli listtransactions * 100000 to extract the transactions listed in the Wallet.
As usual, if you use Bitcoin and want to show your support of my activities, please send Bitcoin donations to my address 15oWEoG9dUPovwmUL9KWAnYRtNJEkP1u1b.