From f3540a21af3ace864b93812e974769d51c944917 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 28 Apr 2025 16:28:32 +0200 Subject: [PATCH 1/2] 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 31518139882..ef62956c436 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -1165,7 +1165,7 @@ def show_transaction( 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, ): @@ -1173,7 +1173,7 @@ def show_transaction( 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, ) @@ -1409,18 +1409,18 @@ def get_manually_selected_coins(self) -> Optional[Sequence[PartialTxInput]]: """ 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 81c9ca3fdd2..ecefdea9e44 100644 --- a/electrum/gui/qt/send_tab.py +++ b/electrum/gui/qt/send_tab.py @@ -304,10 +304,6 @@ def pay_onchain_dialog( 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: @@ -353,12 +349,12 @@ def make_tx(fee_policy, *, confirmed_only=False, base_tx=None): 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, @@ -711,7 +707,7 @@ def pay_lightning_invoice(self, invoice: Invoice): 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 @@ -724,9 +720,7 @@ def pay_lightning_invoice(self, invoice: Invoice): 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) @@ -741,7 +735,7 @@ def broadcast_transaction(self, tx: Transaction, *, payment_identifier: PaymentI 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)) @@ -750,19 +744,22 @@ def broadcast_thread(): 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 47d86d60cce..e73952eafd9 100644 --- a/electrum/gui/qt/transaction_dialog.py +++ b/electrum/gui/qt/transaction_dialog.py @@ -64,7 +64,7 @@ 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__) @@ -409,7 +409,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, @@ -420,7 +420,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: @@ -445,7 +445,7 @@ def __init__( 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. @@ -458,13 +458,15 @@ def __init__( 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]] @@ -483,13 +485,8 @@ def __init__( 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) @@ -570,6 +567,13 @@ def on_edited(): 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 @@ -598,7 +602,7 @@ def do_broadcast(self): 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 @@ -713,6 +717,7 @@ def sign_done(success): 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) @@ -842,7 +847,8 @@ def update(self): # 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: From 08d4c4ddae7639e46350c8c81abbb08683e1dec1 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 28 Apr 2025 12:29:53 +0200 Subject: [PATCH 2/2] psbt_nostr: send label along with PSBT --- .../gui/qml/components/ExportTxDialog.qml | 1 + .../gui/qml/components/WalletMainView.qml | 3 +- electrum/gui/qml/qetxdetails.py | 11 +++-- electrum/gui/qml/qetxfinalizer.py | 3 +- electrum/plugins/psbt_nostr/psbt_nostr.py | 46 +++++++++++++------ electrum/plugins/psbt_nostr/qml.py | 15 +++--- electrum/plugins/psbt_nostr/qml/main.qml | 27 +++++++---- electrum/plugins/psbt_nostr/qt.py | 30 +++++++----- 8 files changed, 88 insertions(+), 48 deletions(-) diff --git a/electrum/gui/qml/components/ExportTxDialog.qml b/electrum/gui/qml/components/ExportTxDialog.qml index 62b4291bf5c..249f07c18a9 100644 --- a/electrum/gui/qml/components/ExportTxDialog.qml +++ b/electrum/gui/qml/components/ExportTxDialog.qml @@ -13,6 +13,7 @@ ElDialog { // if text_qr is undefined text will be used property string text_help property string text_warn + property string tx_label title: qsTr('Share Transaction') diff --git a/electrum/gui/qml/components/WalletMainView.qml b/electrum/gui/qml/components/WalletMainView.qml index 5f8747c2780..e17b4efaedb 100644 --- a/electrum/gui/qml/components/WalletMainView.qml +++ b/electrum/gui/qml/components/WalletMainView.qml @@ -98,7 +98,8 @@ Item { ? '' : [qsTr('Warning: Some data (prev txs / "full utxos") was left out of the QR code as it would not fit.'), qsTr('This might cause issues if signing offline.'), - qsTr('As a workaround, copy to clipboard or use the Share option instead.')].join(' ') + qsTr('As a workaround, copy to clipboard or use the Share option instead.')].join(' '), + tx_label: data[3] }) dialog.open() } diff --git a/electrum/gui/qml/qetxdetails.py b/electrum/gui/qml/qetxdetails.py index 5ddad940c4d..9f0a2ac6105 100644 --- a/electrum/gui/qml/qetxdetails.py +++ b/electrum/gui/qml/qetxdetails.py @@ -383,9 +383,11 @@ def update(self, from_txid: bool = False): self.detailsChanged.emit() - if self._label != txinfo.label: - self._label = txinfo.label - self.labelChanged.emit() + if self._txid: + label = self._wallet.wallet.get_label_for_txid(self._txid) + if self._label != label: + self._label = label + self.labelChanged.emit() def update_mined_status(self, tx_mined_info: TxMinedInfo): self._mempool_depth = '' @@ -505,4 +507,5 @@ def save(self): @pyqtSlot(result='QVariantList') def getSerializedTx(self): txqr = self._tx.to_qr_data() - return [str(self._tx), txqr[0], txqr[1]] + label = self._wallet.wallet.get_label_for_txid(self._tx.txid()) + return [str(self._tx), txqr[0], txqr[1], label] diff --git a/electrum/gui/qml/qetxfinalizer.py b/electrum/gui/qml/qetxfinalizer.py index 2223dea801b..3908c7e31d6 100644 --- a/electrum/gui/qml/qetxfinalizer.py +++ b/electrum/gui/qml/qetxfinalizer.py @@ -494,7 +494,8 @@ def on_sign_failed(self, msg: str = None): @pyqtSlot(result='QVariantList') def getSerializedTx(self): txqr = self._tx.to_qr_data() - return [str(self._tx), txqr[0], txqr[1]] + label = self._wallet.wallet.get_label_for_txid(self._tx.txid()) + return [str(self._tx), txqr[0], txqr[1], label] class TxMonMixin(QtEventListener): diff --git a/electrum/plugins/psbt_nostr/psbt_nostr.py b/electrum/plugins/psbt_nostr/psbt_nostr.py index 0be6ab995a9..5fcbd7937f0 100644 --- a/electrum/plugins/psbt_nostr/psbt_nostr.py +++ b/electrum/plugins/psbt_nostr/psbt_nostr.py @@ -23,6 +23,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import asyncio +import json import ssl import time from contextlib import asynccontextmanager @@ -30,7 +31,7 @@ import electrum_ecc as ecc import electrum_aionostr as aionostr from electrum_aionostr.key import PrivateKey -from typing import Dict, TYPE_CHECKING, Union, List, Tuple, Optional +from typing import Dict, TYPE_CHECKING, Union, List, Tuple, Optional, Callable from electrum import util, Transaction from electrum.crypto import sha256 @@ -38,8 +39,9 @@ from electrum.logging import Logger from electrum.plugin import BasePlugin from electrum.transaction import PartialTransaction, tx_from_any -from electrum.util import (log_exceptions, OldTaskGroup, ca_path, trigger_callback, event_listener, - make_aiohttp_proxy_connector) +from electrum.util import ( + log_exceptions, OldTaskGroup, ca_path, trigger_callback, event_listener, json_decode, make_aiohttp_proxy_connector +) from electrum.wallet import Multisig_Wallet if TYPE_CHECKING: @@ -165,11 +167,11 @@ async def nostr_manager(self): yield manager @log_exceptions - async def send_direct_messages(self, messages: List[Tuple[str, str]]): + async def send_direct_messages(self, messages: List[Tuple[str, dict]]): our_private_key: PrivateKey = aionostr.key.PrivateKey(bytes.fromhex(self.nostr_privkey)) async with self.nostr_manager() as manager: for pubkey, msg in messages: - encrypted_msg: str = our_private_key.encrypt_message(msg, pubkey) + encrypted_msg: str = our_private_key.encrypt_message(json.dumps(msg), pubkey) eid = await aionostr._add_event( manager, kind=NOSTR_EVENT_KIND, @@ -206,13 +208,16 @@ async def check_direct_messages(self): self.known_events[event.id] = now() continue try: - tx = tx_from_any(message) + message = json_decode(message) + tx_hex = message.get('tx') + label = message.get('label', '') + tx = tx_from_any(tx_hex) except Exception as e: self.logger.info(_("Unable to deserialize the transaction:") + "\n" + str(e)) self.known_events[event.id] = now() continue self.logger.info(f"received PSBT from {event.pubkey}") - trigger_callback('psbt_nostr_received', self.wallet, event.pubkey, event.id, tx) + trigger_callback('psbt_nostr_received', self.wallet, event.pubkey, event.id, tx, label) await self.pending.wait() self.pending.clear() @@ -242,25 +247,34 @@ def mark_pending_event_rcvd(self, event_id): self.known_events[event_id] = now() self.pending.set() - def prepare_messages(self, tx: Union[Transaction, PartialTransaction]) -> List[Tuple[str, str]]: + def prepare_messages(self, tx: Union[Transaction, PartialTransaction], label: str = None) -> List[Tuple[str, dict]]: messages = [] for xpub, pubkey in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue - raw_tx_bytes = tx.serialize_as_bytes() - messages.append((pubkey, raw_tx_bytes.hex())) + payload = {'tx': tx.serialize_as_bytes().hex()} + if label: + payload['label'] = label + messages.append((pubkey, payload)) return messages - def send_psbt(self, tx: Union[Transaction, PartialTransaction]): - self.do_send(self.prepare_messages(tx), tx.txid()) + def send_psbt(self, tx: Union[Transaction, PartialTransaction], label: str): + self.do_send(self.prepare_messages(tx, label), tx.txid()) - def do_send(self, messages: List[Tuple[str, str]], txid: Optional[str] = None): + def do_send(self, messages: List[Tuple[str, dict]], txid: Optional[str] = None): raise NotImplementedError() - def on_receive(self, pubkey, event_id, tx): + def on_receive(self, pubkey, event_id, tx, label: str): raise NotImplementedError() - def add_transaction_to_wallet(self, tx, *, on_failure=None, on_success=None): + def add_transaction_to_wallet( + self, + tx: Union['Transaction', 'PartialTransaction'], + *, + label: str = None, + on_failure: Callable = None, + on_success: Callable = None + ) -> None: try: # TODO: adding tx should be handled more gracefully here: # 1) don't replace tx with same tx with less signatures @@ -269,6 +283,8 @@ def add_transaction_to_wallet(self, tx, *, on_failure=None, on_success=None): if not self.wallet.adb.add_transaction(tx): # TODO: instead of bool return value, we could use specific fail reason exceptions here raise Exception('transaction was not added') + if label: + self.wallet.set_label(tx.txid(), label) except Exception as e: if on_failure: on_failure(str(e)) diff --git a/electrum/plugins/psbt_nostr/qml.py b/electrum/plugins/psbt_nostr/qml.py index caf48328ea4..989fd244621 100644 --- a/electrum/plugins/psbt_nostr/qml.py +++ b/electrum/plugins/psbt_nostr/qml.py @@ -50,7 +50,7 @@ def __init__(self, plugin: 'Plugin'): QObject.__init__(self) self._plugin = plugin - cosignerReceivedPsbt = pyqtSignal(str, str, str) + cosignerReceivedPsbt = pyqtSignal(str, str, str, str) sendPsbtFailed = pyqtSignal(str, arguments=['reason']) sendPsbtSuccess = pyqtSignal() @@ -66,11 +66,12 @@ def canSendPsbt(self, wallet: 'QEWallet', tx: str) -> bool: return cosigner_wallet.can_send_psbt(tx_from_any(tx, deserialize=True)) @pyqtSlot(QEWallet, str) - def sendPsbt(self, wallet: 'QEWallet', tx: str): + @pyqtSlot(QEWallet, str, str) + def sendPsbt(self, wallet: 'QEWallet', tx: str, label: str = None): cosigner_wallet = self._plugin.cosigner_wallets.get(wallet.wallet) if not cosigner_wallet: return - cosigner_wallet.send_psbt(tx_from_any(tx, deserialize=True)) + cosigner_wallet.send_psbt(tx_from_any(tx, deserialize=True), label) @pyqtSlot(QEWallet, str) def acceptPsbt(self, wallet: 'QEWallet', event_id: str): @@ -126,20 +127,20 @@ def __init__(self, wallet: 'Multisig_Wallet', plugin: 'Plugin'): self.user_prompt_cooldown = None @event_listener - def on_event_psbt_nostr_received(self, wallet, pubkey, event_id, tx: 'PartialTransaction'): + def on_event_psbt_nostr_received(self, wallet, pubkey, event_id, tx: 'PartialTransaction', label: str): if self.wallet == wallet: self.tx = tx if not (self.user_prompt_cooldown and self.user_prompt_cooldown > now()): - self.plugin.so.cosignerReceivedPsbt.emit(pubkey, event_id, tx.serialize()) + self.plugin.so.cosignerReceivedPsbt.emit(pubkey, event_id, tx.serialize(), label) else: self.mark_pending_event_rcvd(event_id) - self.add_transaction_to_wallet(self.tx, on_failure=self.on_add_fail) + self.add_transaction_to_wallet(self.tx, label=label, on_failure=self.on_add_fail) def close(self): super().close() self.unregister_callbacks() - def do_send(self, messages: List[Tuple[str, str]], txid: Optional[str] = None): + def do_send(self, messages: List[Tuple[str, dict]], txid: Optional[str] = None): if not messages: return coro = self.send_direct_messages(messages) diff --git a/electrum/plugins/psbt_nostr/qml/main.qml b/electrum/plugins/psbt_nostr/qml/main.qml index 7df6d6aed52..8067d105d6f 100644 --- a/electrum/plugins/psbt_nostr/qml/main.qml +++ b/electrum/plugins/psbt_nostr/qml/main.qml @@ -7,13 +7,16 @@ import "../../../gui/qml/components/controls" Item { Connections { target: AppController ? AppController.plugin('psbt_nostr') : null - function onCosignerReceivedPsbt(pubkey, event, tx) { + function onCosignerReceivedPsbt(pubkey, event, tx, label) { var dialog = app.messageDialog.createObject(app, { text: [ - qsTr('A transaction was received from your cosigner.'), + label + ? qsTr('A transaction was received from your cosigner with label:

%1').arg(label) + : qsTr('A transaction was received from your cosigner.'), qsTr('Do you want to open it now?') - ].join('\n'), - yesno: true + ].join('

'), + yesno: true, + richText: true }) dialog.accepted.connect(function () { var page = app.stack.push(Qt.resolvedUrl('../../../gui/qml/components/TxDetails.qml'), { @@ -40,16 +43,24 @@ Item { onClicked: { console.log('about to psbt nostr send') psbt_nostr_send_button.enabled = false - AppController.plugin('psbt_nostr').sendPsbt(Daemon.currentWallet, dialog.text) + AppController.plugin('psbt_nostr').sendPsbt(Daemon.currentWallet, dialog.text, dialog.tx_label) } Connections { target: AppController ? AppController.plugin('psbt_nostr') : null + function onSendPsbtSuccess() { + dialog.close() + var msgdialog = app.messageDialog.createObject(app, { + text: qsTr('PSBT sent successfully') + }) + msgdialog.open() + } function onSendPsbtFailed(message) { psbt_nostr_send_button.enabled = true - var dialog = app.messageDialog.createObject(app, { - text: qsTr('Sending PSBT to co-signer failed:\n%1').arg(message) + var msgdialog = app.messageDialog.createObject(app, { + text: qsTr('Sending PSBT to co-signer failed:\n%1').arg(message), + iconSource: Qt.resolvedUrl('../../../gui/icons/warning.png') }) - dialog.open() + msgdialog.open() } } diff --git a/electrum/plugins/psbt_nostr/qt.py b/electrum/plugins/psbt_nostr/qt.py index e18f31af5f4..b86c13e64f5 100644 --- a/electrum/plugins/psbt_nostr/qt.py +++ b/electrum/plugins/psbt_nostr/qt.py @@ -24,7 +24,7 @@ # SOFTWARE. import asyncio from functools import partial -from typing import TYPE_CHECKING, List, Tuple, Optional +from typing import TYPE_CHECKING, List, Tuple, Optional, Union from PyQt6.QtCore import QObject, pyqtSignal from PyQt6.QtWidgets import QPushButton, QMessageBox @@ -39,11 +39,12 @@ from .psbt_nostr import PsbtNostrPlugin, CosignerWallet if TYPE_CHECKING: + from electrum.transaction import Transaction, PartialTransaction from electrum.gui.qt.main_window import ElectrumWindow class QReceiveSignalObject(QObject): - cosignerReceivedPsbt = pyqtSignal(str, str, object) + cosignerReceivedPsbt = pyqtSignal(str, str, object, str) class Plugin(PsbtNostrPlugin): @@ -71,7 +72,7 @@ def transaction_dialog(self, d: 'TxDialog'): d.cosigner_send_button = b = QPushButton(_("Send to cosigner")) icon = read_QIcon_from_bytes(self.read_file("nostr_multisig.png")) b.setIcon(icon) - b.clicked.connect(lambda: cw.send_to_cosigners(d.tx)) + b.clicked.connect(lambda: cw.send_to_cosigners(d.tx, d.desc)) d.buttons.insert(0, b) b.setVisible(False) @@ -100,11 +101,11 @@ def on_event_psbt_nostr_received(self, wallet, *args): if self.wallet == wallet: self.obj.cosignerReceivedPsbt.emit(*args) # put on UI thread via signal - def send_to_cosigners(self, tx): - self.add_transaction_to_wallet(tx, on_failure=self.on_add_fail) - self.send_psbt(tx) + def send_to_cosigners(self, tx: Union['Transaction', 'PartialTransaction'], label: str): + self.add_transaction_to_wallet(tx, label=label, on_failure=self.on_add_fail) + self.send_psbt(tx, label) - def do_send(self, messages: List[Tuple[str, str]], txid: Optional[str] = None): + def do_send(self, messages: List[Tuple[str, dict]], txid: Optional[str] = None): if not messages: return coro = self.send_direct_messages(messages) @@ -122,21 +123,26 @@ def do_send(self, messages: List[Tuple[str, str]], txid: Optional[str] = None): self.window.show_message( _("Your transaction was sent to your cosigners via Nostr.") + '\n\n' + txid) - def on_receive(self, pubkey, event_id, tx): - msg = _("A transaction was received from your cosigner ({}).").format(str(event_id)[0:8]) + '\n' + \ - _("Do you want to open it now?") - result = self.window.show_message(msg, icon=QMessageBox.Icon.Question, buttons=[ + def on_receive(self, pubkey, event_id, tx, label): + msg = '
'.join([ + _("A transaction was received from your cosigner.") if not label else + _("A transaction was received from your cosigner with label:
{}
").format(label), + _("Do you want to open it now?") + ]) + result = self.window.show_message(msg, rich_text=True, icon=QMessageBox.Icon.Question, buttons=[ QMessageBox.StandardButton.Open, (QPushButton('Discard'), QMessageBox.ButtonRole.DestructiveRole, 100), (QPushButton('Save to wallet'), QMessageBox.ButtonRole.AcceptRole, 101)] ) if result == QMessageBox.StandardButton.Open: + if label: + self.wallet.set_label(tx.txid(), label) show_transaction(tx, parent=self.window, prompt_if_unsaved=True, on_closed=partial(self.on_tx_dialog_closed, event_id)) else: self.mark_pending_event_rcvd(event_id) if result == 100: # Discard return - self.add_transaction_to_wallet(tx, on_failure=self.on_add_fail) + self.add_transaction_to_wallet(tx, label=label, on_failure=self.on_add_fail) self.window.update_tabs() def on_tx_dialog_closed(self, event_id):