From 6566f2f0a4c27bf535542ae5799aae0731799821 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 28 Apr 2025 16:28:32 +0200 Subject: [PATCH] qt: pass Invoice object to transaction dialog when appropriate. This is purely informational and optional, with the main immediate use to provide the invoice description/message/label to the transaction dialog, so it can be stored when saving the tx in history, or passed along with PSBTs sent to cosigners. Before, the tx description was not saved in history when an invoice was not saved before signing and saving the tx for sending later. --- electrum/gui/qt/main_window.py | 16 ++++++------ electrum/gui/qt/send_tab.py | 37 ++++++++++++--------------- electrum/gui/qt/transaction_dialog.py | 34 ++++++++++++++---------- 3 files changed, 45 insertions(+), 42 deletions(-) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index edf9cba3d..e70b0da20 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -1169,7 +1169,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): tx: Transaction, *, external_keypairs: Mapping[bytes, bytes] = None, - payment_identifier: PaymentIdentifier = None, + invoice: Invoice = None, show_sign_button: bool = True, show_broadcast_button: bool = True, ): @@ -1177,7 +1177,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): tx, parent=self, external_keypairs=external_keypairs, - payment_identifier=payment_identifier, + invoice=invoice, show_sign_button=show_sign_button, show_broadcast_button=show_broadcast_button, ) @@ -1413,18 +1413,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): """ return self.utxo_list.get_spend_list() - def broadcast_or_show(self, tx: Transaction, *, payment_identifier: PaymentIdentifier = None): + def broadcast_or_show(self, tx: Transaction, *, invoice: 'Invoice' = None): if not tx.is_complete(): - self.show_transaction(tx, payment_identifier=payment_identifier) + self.show_transaction(tx, invoice=invoice) return if not self.network: self.show_error(_("You can't broadcast a transaction without a live network connection.")) - self.show_transaction(tx, payment_identifier=payment_identifier) + self.show_transaction(tx, invoice=invoice) return - self.broadcast_transaction(tx, payment_identifier=payment_identifier) + self.broadcast_transaction(tx, invoice=invoice) - def broadcast_transaction(self, tx: Transaction, *, payment_identifier: PaymentIdentifier = None): - self.send_tab.broadcast_transaction(tx, payment_identifier=payment_identifier) + def broadcast_transaction(self, tx: Transaction, *, invoice: Invoice = None): + self.send_tab.broadcast_transaction(tx, invoice=invoice) @protected def sign_tx( diff --git a/electrum/gui/qt/send_tab.py b/electrum/gui/qt/send_tab.py index 583749525..2817784bf 100644 --- a/electrum/gui/qt/send_tab.py +++ b/electrum/gui/qt/send_tab.py @@ -305,10 +305,6 @@ class SendTab(QWidget, MessageBoxMixin, Logger): if run_hook('abort_send', self): return - payment_identifier = None - if invoice and invoice.bip70: - payment_identifier = payment_identifier_from_invoice(self.wallet, invoice) - is_sweep = bool(external_keypairs) # we call get_coins inside make_tx, so that inputs can be changed dynamically if get_coins is None: @@ -354,12 +350,12 @@ class SendTab(QWidget, MessageBoxMixin, Logger): tx.swap_payment_hash = swap.payment_hash if is_preview: - self.window.show_transaction(tx, external_keypairs=external_keypairs, payment_identifier=payment_identifier) + self.window.show_transaction(tx, external_keypairs=external_keypairs, invoice=invoice) return self.save_pending_invoice() def sign_done(success): if success: - self.window.broadcast_or_show(tx, payment_identifier=payment_identifier) + self.window.broadcast_or_show(tx, invoice=invoice) self.window.sign_tx( tx, callback=sign_done, @@ -712,7 +708,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger): chan, swap_recv_amount_sat = can_pay_with_swap self.window.run_swap_dialog(is_reverse=False, recv_amount_sat=swap_recv_amount_sat, channels=[chan]) elif r == 'onchain': - self.pay_onchain_dialog(invoice.get_outputs(), nonlocal_only=True) + self.pay_onchain_dialog(invoice.get_outputs(), nonlocal_only=True, invoice=invoice) return assert lnworker is not None @@ -725,9 +721,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger): coro = lnworker.pay_invoice(invoice, amount_msat=amount_msat) self.window.run_coroutine_from_thread(coro, _('Sending payment')) - def broadcast_transaction(self, tx: Transaction, *, payment_identifier: PaymentIdentifier = None): - # note: payment_identifier is explicitly passed as self.payto_e.payment_identifier might - # already be cleared or otherwise have changed. + def broadcast_transaction(self, tx: Transaction, *, invoice: Invoice = None): if hasattr(tx, 'swap_payment_hash'): sm = self.wallet.lnworker.swap_manager swap = sm.get_swap(tx.swap_payment_hash) @@ -742,7 +736,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger): def broadcast_thread(): # non-GUI thread - if payment_identifier and payment_identifier.has_expired(): + if invoice and invoice.has_expired(): return False, _("Invoice has expired") try: self.network.run_from_another_thread(self.network.broadcast_transaction(tx)) @@ -751,19 +745,22 @@ class SendTab(QWidget, MessageBoxMixin, Logger): except BestEffortRequestFailed as e: return False, repr(e) # success - txid = tx.txid() - if payment_identifier and payment_identifier.need_merchant_notify(): - refund_address = self.wallet.get_receiving_address() - payment_identifier.notify_merchant( - tx=tx, - refund_address=refund_address, - on_finished=self.notify_merchant_done_signal.emit - ) - return True, txid + if invoice and invoice.bip70: + payment_identifier = payment_identifier_from_invoice(invoice) + # FIXME: this should move to backend + if payment_identifier and payment_identifier.need_merchant_notify(): + refund_address = self.wallet.get_receiving_address() + payment_identifier.notify_merchant( + tx=tx, + refund_address=refund_address, + on_finished=self.notify_merchant_done_signal.emit + ) + return True, tx.txid() # Capture current TL window; override might be removed on return parent = self.window.top_level_window(lambda win: isinstance(win, MessageBoxMixin)) + # FIXME: move to backend and let Abstract_Wallet set broadcasting state, not gui self.wallet.set_broadcasting(tx, broadcasting_status=PR_BROADCASTING) def broadcast_done(result): diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py index dfbfc6684..79312087d 100644 --- a/electrum/gui/qt/transaction_dialog.py +++ b/electrum/gui/qt/transaction_dialog.py @@ -64,7 +64,7 @@ from .my_treeview import create_toolbar_with_menu, QMenuWithConfig if TYPE_CHECKING: from .main_window import ElectrumWindow from electrum.wallet import Abstract_Wallet - from electrum.payment_identifier import PaymentIdentifier + from electrum.invoices import Invoice _logger = get_logger(__name__) @@ -418,7 +418,7 @@ def show_transaction( parent: 'ElectrumWindow', prompt_if_unsaved: bool = False, external_keypairs: Mapping[bytes, bytes] = None, - payment_identifier: 'PaymentIdentifier' = None, + invoice: 'Invoice' = None, on_closed: Callable[[], None] = None, show_sign_button: bool = True, show_broadcast_button: bool = True, @@ -429,7 +429,7 @@ def show_transaction( parent=parent, prompt_if_unsaved=prompt_if_unsaved, external_keypairs=external_keypairs, - payment_identifier=payment_identifier, + invoice=invoice, on_closed=on_closed, ) if not show_sign_button: @@ -454,7 +454,7 @@ class TxDialog(QDialog, MessageBoxMixin): parent: 'ElectrumWindow', prompt_if_unsaved: bool, external_keypairs: Mapping[bytes, bytes] = None, - payment_identifier: 'PaymentIdentifier' = None, + invoice: 'Invoice' = None, on_closed: Callable[[], None] = None, ): '''Transactions in the wallet will show their description. @@ -467,13 +467,15 @@ class TxDialog(QDialog, MessageBoxMixin): self.main_window = parent self.config = parent.config self.wallet = parent.wallet - self.payment_identifier = payment_identifier + self.invoice = invoice self.prompt_if_unsaved = prompt_if_unsaved self.on_closed = on_closed self.saved = False self.desc = None if txid := tx.txid(): self.desc = self.wallet.get_label_for_txid(txid) or None + if not self.desc and self.invoice: + self.desc = self.invoice.get_message() self.setMinimumWidth(640) self.psbt_only_widgets = [] # type: List[Union[QWidget, QAction]] @@ -492,13 +494,8 @@ class TxDialog(QDialog, MessageBoxMixin): self.tx_desc_label = QLabel(_("Description:")) vbox.addWidget(self.tx_desc_label) self.tx_desc = ButtonsLineEdit('') - def on_edited(): - text = self.tx_desc.text() - if self.wallet.set_label(txid, text): - self.main_window.history_list.update() - self.main_window.utxo_list.update() - self.main_window.labels_changed_signal.emit() - self.tx_desc.editingFinished.connect(on_edited) + + self.tx_desc.editingFinished.connect(self.store_tx_label) self.tx_desc.addCopyButton() vbox.addWidget(self.tx_desc) @@ -579,6 +576,13 @@ class TxDialog(QDialog, MessageBoxMixin): self.update() self.set_title() + def store_tx_label(self): + text = self.tx_desc.text() + if self.wallet.set_label(self.tx.txid(), text): + self.main_window.history_list.update() + self.main_window.utxo_list.update() + self.main_window.labels_changed_signal.emit() + def set_tx(self, tx: 'Transaction'): # Take a copy; it might get updated in the main window by # e.g. the FX plugin. If this happens during or after a long @@ -607,7 +611,7 @@ class TxDialog(QDialog, MessageBoxMixin): self.main_window.push_top_level_window(self) self.main_window.send_tab.save_pending_invoice() try: - self.main_window.broadcast_transaction(self.tx, payment_identifier=self.payment_identifier) + self.main_window.broadcast_transaction(self.tx, invoice=self.invoice) finally: self.main_window.pop_top_level_window(self) self.saved = True @@ -722,6 +726,7 @@ class TxDialog(QDialog, MessageBoxMixin): def save(self): self.main_window.push_top_level_window(self) if self.main_window.save_transaction_into_wallet(self.tx): + self.store_tx_label() self.save_button.setDisabled(True) self.saved = True self.main_window.pop_top_level_window(self) @@ -851,7 +856,8 @@ class TxDialog(QDialog, MessageBoxMixin): # note: when not finalized, RBF and locktime changes do not trigger # a make_tx, so the txid is unreliable, hence: self.tx_hash_e.setText(_('Unknown')) - if not self.wallet.adb.get_transaction(txid): + tx_in_db = bool(self.wallet.adb.get_transaction(txid)) + if not desc and not tx_in_db: self.tx_desc.hide() self.tx_desc_label.hide() else: