1
0

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.
This commit is contained in:
Sander van Grieken
2025-04-28 16:28:32 +02:00
parent f535817006
commit 6566f2f0a4
3 changed files with 45 additions and 42 deletions

View File

@@ -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(

View File

@@ -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):

View File

@@ -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: