diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py index 8b956c5f7..366a27cf2 100644 --- a/electrum/gui/qt/history_list.py +++ b/electrum/gui/qt/history_list.py @@ -27,7 +27,7 @@ import os import time import datetime from datetime import date -from typing import TYPE_CHECKING, Tuple, Dict, Any, Optional +from typing import TYPE_CHECKING, Tuple, Dict, Any import threading import enum from decimal import Decimal @@ -43,7 +43,7 @@ from electrum.address_synchronizer import TX_HEIGHT_LOCAL from electrum.i18n import _ from electrum.util import (block_explorer_URL, profiler, TxMinedInfo, OrderedDictWithIndex, timestamp_to_datetime, - Satoshis, format_time, Fiat) + Satoshis, format_time) from electrum.logging import get_logger, Logger from electrum.simple_config import SimpleConfig @@ -55,7 +55,6 @@ from .my_treeview import MyTreeView if TYPE_CHECKING: from electrum.wallet import Abstract_Wallet - from electrum.exchange_rate import FxThread from .main_window import ElectrumWindow @@ -845,8 +844,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): if not filename: return try: - self.do_export_history( - self.wallet, + self.wallet.export_history_to_file( self.main_window.fx if self.hm.should_show_fiat() else None, filename, csv_button.isChecked(), @@ -857,78 +855,6 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): return self.main_window.show_message(_("Your wallet history has been successfully exported.")) - @staticmethod - def do_export_history(wallet: 'Abstract_Wallet', fx: Optional['FxThread'], file_path: str, is_csv: bool): - txns = wallet.get_full_history(fx=fx) - lines = [] - - def get_all_fees_paid_by_item(h_item: dict) -> Tuple[int, Optional[Fiat]]: - # gets all fees paid in an item (or group), as the outer group doesn't contain the - # transaction fees paid by the children - fees_sat = 0 - fees_fiat = Fiat(ccy=fx.ccy, value=Decimal()) if fx else None - for child in h_item.get('children', []): - fees_sat += child['fee_sat'] or 0 if 'fee_sat' in child \ - else (child.get('fee_msat', 0) or 0) // 1000 # FIXME: loses msat precision - if fees_fiat is not None and (child_fiat_fee := child.get('fiat_fee')): - fees_fiat += child_fiat_fee - - fees_sat += h_item['fee_sat'] or 0 if 'fee_sat' in h_item \ - else (h_item.get('fee_msat', 0) or 0) // 1000 # FIXME: loses msat precision - if fees_fiat is not None and (h_item_fiat_fee := h_item.get('fiat_fee')): - fees_fiat += h_item_fiat_fee - - fiat_value = h_item.get('fiat_value') - if fees_fiat is not None and isinstance(fiat_value, Fiat) \ - and (fiat_value.value is None or fiat_value.value.is_nan()): - # ensure that str(fees_fiat) == 'No Data' if str(fiat_value) == 'No Data' - fees_fiat = Fiat(ccy=fx.ccy, value=None) - - return fees_sat, fees_fiat - - if is_csv: - # sort by timestamp so the generated csv is more understandable on first sight - txns = dict(sorted(txns.items(), key=lambda h_item: h_item[1]['timestamp'] or 0)) - for item in txns.values(): - # tx groups will are shown as single element - fees_sat, fees_fiat = get_all_fees_paid_by_item(item) - # users are sensitive to changes of these fields as they have scripts/spreadsheets - # depending on them. E.g. https://github.com/spesmilo/electrum/issues/10445 - assert str(fees_fiat) == 'No Data' if str(item.get('fiat_value')) == 'No Data' else True - line = [ - item.get('txid', ''), - item.get('payment_hash', ''), - item.get('label', ''), - item.get('confirmations', ''), - item['bc_value'], - item['ln_value'], - item.get('fiat_value', ''), - fees_sat, - str(fees_fiat or ''), - item['date'] - ] - lines.append(line) - - with open(file_path, "w+", encoding='utf-8') as f: - if is_csv: - import csv - transaction = csv.writer(f, lineterminator='\n') - transaction.writerow(["oc_transaction_hash", - "ln_payment_hash", - "label", - "confirmations", - "amount_chain_bc", - "amount_lightning_bc", - "fiat_value", - "network_fee_satoshi", - "fiat_fee", - "timestamp"]) - for line in lines: - transaction.writerow(line) - else: - from electrum.util import json_encode - f.write(json_encode(txns)) - def get_text_from_coordinate(self, row, col): return self.get_role_data_from_coordinate(row, col, role=Qt.ItemDataRole.DisplayRole) diff --git a/electrum/wallet.py b/electrum/wallet.py index 247dfa9c4..aea8a5c75 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -3614,6 +3614,76 @@ class Abstract_Wallet(ABC, Logger, EventListener): util.trigger_callback('wallet_updated', self) self.adb.set_future_tx(tx.txid(), wanted_height=wanted_height) + def export_history_to_file(self, fx: Optional['FxThread'], file_path: str, is_csv: bool): + txns = self.get_full_history(fx=fx) + lines = [] + + def get_all_fees_paid_by_item(h_item: dict) -> Tuple[int, Optional[Fiat]]: + # gets all fees paid in an item (or group), as the outer group doesn't contain the + # transaction fees paid by the children + fees_sat = 0 + fees_fiat = Fiat(ccy=fx.ccy, value=Decimal()) if fx else None + for child in h_item.get('children', []): + fees_sat += child['fee_sat'] or 0 if 'fee_sat' in child \ + else (child.get('fee_msat', 0) or 0) // 1000 # FIXME: loses msat precision + if fees_fiat is not None and (child_fiat_fee := child.get('fiat_fee')): + fees_fiat += child_fiat_fee + + fees_sat += h_item['fee_sat'] or 0 if 'fee_sat' in h_item \ + else (h_item.get('fee_msat', 0) or 0) // 1000 # FIXME: loses msat precision + if fees_fiat is not None and (h_item_fiat_fee := h_item.get('fiat_fee')): + fees_fiat += h_item_fiat_fee + + fiat_value = h_item.get('fiat_value') + if fees_fiat is not None and isinstance(fiat_value, Fiat) \ + and (fiat_value.value is None or fiat_value.value.is_nan()): + # ensure that str(fees_fiat) == 'No Data' if str(fiat_value) == 'No Data' + fees_fiat = Fiat(ccy=fx.ccy, value=None) + + return fees_sat, fees_fiat + + if is_csv: + # sort by timestamp so the generated csv is more understandable on first sight + txns = dict(sorted(txns.items(), key=lambda h_item: h_item[1]['timestamp'] or 0)) + for item in txns.values(): + # tx groups will are shown as single element + fees_sat, fees_fiat = get_all_fees_paid_by_item(item) + # users are sensitive to changes of these fields as they have scripts/spreadsheets + # depending on them. E.g. https://github.com/spesmilo/electrum/issues/10445 + assert str(fees_fiat) == 'No Data' if str(item.get('fiat_value')) == 'No Data' else True + line = [ + item.get('txid', ''), + item.get('payment_hash', ''), + item.get('label', ''), + item.get('confirmations', ''), + item['bc_value'], + item['ln_value'], + item.get('fiat_value', ''), + fees_sat, + str(fees_fiat or ''), + item['date'] + ] + lines.append(line) + + with open(file_path, "w+", encoding='utf-8') as f: + if is_csv: + import csv + transaction = csv.writer(f, lineterminator='\n') + transaction.writerow(["oc_transaction_hash", + "ln_payment_hash", + "label", + "confirmations", + "amount_chain_bc", + "amount_lightning_bc", + "fiat_value", + "network_fee_satoshi", + "fiat_fee", + "timestamp"]) + for line in lines: + transaction.writerow(line) + else: + f.write(util.json_encode(txns)) + class Simple_Wallet(Abstract_Wallet): # wallet with a single keystore