move history export from HistoryList to Abstract_Wallet
What changed, and why it matters
This commit simply moves an existing wallet history export feature from the Qt graphical user interface code into the core wallet code. The actual export logic is copied almost unchanged, so users will see no difference in behavior. It is a code cleanup/refactoring change, not a security fix or vulnerability.
No security action required. Review as normal refactoring if auditing the codebase.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change relocates do_export_history() from electrum/gui/qt/history_list.py to Abstract_Wallet.export_history_to_file() in electrum/wallet.py. The method body is essentially identical, with only minor import adjustments and a call-site update. The purpose stated in the commit message is to make the function unit-testable and callable from non-Qt contexts such as CLI or QML. No security-sensitive logic was altered.
Changed components
electrum/gui/qt/history_list.pyelectrum/wallet.pyInspect captured patch +73 / −77
diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py
index 8b956c5..366a27c 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 247dfa9..aea8a5c 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
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.