TxEditor: move swap request to TxEditor
Moves the logic requesting the forward swap into the TxEditor so it can use the open transport and doesn't have to reconnect to the relays again. Also disables the "Preview" button in the TxEditor when the transaction will send change to lightning. This should prevent the user from saving the transaction to history and broadcasting it later or exporting it and broadcasting it through some external way. Broadcasting needs to happen directly after the TxEditor so we can send the second rpc call to the swapserver and await the incoming htlcs before broadcasting the (funding-) transaction.
This commit is contained in:
@@ -28,6 +28,7 @@ from decimal import Decimal
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
from concurrent.futures import Future
|
||||
from enum import Enum, auto
|
||||
|
||||
from PyQt6.QtCore import Qt, QTimer, pyqtSlot, pyqtSignal
|
||||
from PyQt6.QtGui import QIcon
|
||||
@@ -35,20 +36,20 @@ from PyQt6.QtWidgets import (QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QPus
|
||||
QComboBox, QTabWidget, QWidget, QStackedWidget)
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import (quantize_feerate, profiler, NotEnoughFunds, NoDynamicFeeEstimates,
|
||||
get_asyncio_loop, wait_for2)
|
||||
from electrum.util import (UserCancelled, quantize_feerate, profiler, NotEnoughFunds, NoDynamicFeeEstimates,
|
||||
get_asyncio_loop, wait_for2, UserFacingException)
|
||||
from electrum.plugin import run_hook
|
||||
from electrum.transaction import PartialTransaction, TxOutput
|
||||
from electrum.transaction import PartialTransaction, PartialTxOutput
|
||||
from electrum.wallet import InternalAddressCorruption
|
||||
from electrum.bitcoin import DummyAddress
|
||||
from electrum.fee_policy import FeePolicy, FixedFeePolicy, FeeMethod
|
||||
from electrum.logging import Logger
|
||||
from electrum.submarine_swaps import NostrTransport, HttpTransport, SwapServerTransport
|
||||
from electrum.submarine_swaps import NostrTransport, HttpTransport, SwapServerTransport, SwapServerError
|
||||
from electrum.gui.messages import MSG_SUBMARINE_PAYMENT_HELP_TEXT
|
||||
|
||||
from .util import (WindowModalDialog, ColorScheme, HelpLabel, Buttons, CancelButton, WWLabel,
|
||||
read_QIcon, qt_event_listener, QtEventListener, IconLabel,
|
||||
HelpButton)
|
||||
HelpButton, RunCoroutineDialog)
|
||||
from .transaction_dialog import TxSizeLabel, TxFiatLabel, TxInOutWidget
|
||||
from .fee_slider import FeeSlider, FeeComboBox
|
||||
from .amountedit import FeerateEdit, BTCAmountEdit
|
||||
@@ -60,6 +61,15 @@ if TYPE_CHECKING:
|
||||
from .main_window import ElectrumWindow
|
||||
|
||||
|
||||
class TxEditorContext(Enum):
|
||||
"""
|
||||
Context for which the TxEditor gets launched.
|
||||
Allows to enable/disable certain features.
|
||||
"""
|
||||
PAYMENT = auto()
|
||||
CHANNEL_FUNDING = auto()
|
||||
|
||||
|
||||
class TxEditor(WindowModalDialog, QtEventListener, Logger):
|
||||
|
||||
swap_availability_changed = pyqtSignal()
|
||||
@@ -69,8 +79,8 @@ class TxEditor(WindowModalDialog, QtEventListener, Logger):
|
||||
window: 'ElectrumWindow',
|
||||
make_tx,
|
||||
output_value: Union[int, str],
|
||||
payee_outputs: Optional[list[TxOutput]] = None,
|
||||
allow_preview=True,
|
||||
payee_outputs: Optional[list[PartialTxOutput]] = None,
|
||||
context: TxEditorContext = TxEditorContext.PAYMENT,
|
||||
batching_candidates=None,
|
||||
):
|
||||
|
||||
@@ -93,8 +103,7 @@ class TxEditor(WindowModalDialog, QtEventListener, Logger):
|
||||
self.not_enough_funds = False
|
||||
self.no_dynfee_estimates = False
|
||||
self.needs_update = False
|
||||
# preview is disabled for lightning channel funding
|
||||
self.allow_preview = allow_preview
|
||||
self.context = context
|
||||
self.is_preview = False
|
||||
self._base_tx = None # for batching
|
||||
self.batching_candidates = batching_candidates
|
||||
@@ -311,8 +320,7 @@ class TxEditor(WindowModalDialog, QtEventListener, Logger):
|
||||
# always show onchain payment tab
|
||||
self.tab_widget.addTab(self.onchain_tab, _('Onchain Transaction'))
|
||||
|
||||
# allow_preview is false for ln channel opening txs
|
||||
allow_swaps = self.allow_preview and self.payee_outputs and self.swap_manager
|
||||
allow_swaps = self.context == TxEditorContext.PAYMENT and self.payee_outputs and self.swap_manager
|
||||
if self.config.WALLET_ENABLE_SUBMARINE_PAYMENTS and allow_swaps:
|
||||
i = self.tab_widget.addTab(self.submarine_payment_tab, _('Submarine Payment'))
|
||||
tooltip = self.config.cv.WALLET_ENABLE_SUBMARINE_PAYMENTS.get_long_desc()
|
||||
@@ -477,7 +485,7 @@ class TxEditor(WindowModalDialog, QtEventListener, Logger):
|
||||
self.change_to_ln_swap_providers_button = SwapProvidersButton(lambda: self.swap_transport, self.config, self.main_window)
|
||||
self.preview_button = QPushButton(_('Preview'))
|
||||
self.preview_button.clicked.connect(self.on_preview)
|
||||
self.preview_button.setVisible(self.allow_preview)
|
||||
self.preview_button.setVisible(self.context != TxEditorContext.CHANNEL_FUNDING)
|
||||
self.ok_button = QPushButton(_('OK'))
|
||||
self.ok_button.clicked.connect(self.on_send)
|
||||
self.ok_button.setDefault(True)
|
||||
@@ -608,9 +616,13 @@ class TxEditor(WindowModalDialog, QtEventListener, Logger):
|
||||
return self.tx if not cancelled else None
|
||||
|
||||
def on_send(self):
|
||||
if self.tx and self.tx.get_dummy_output(DummyAddress.SWAP):
|
||||
if not self.request_forward_swap():
|
||||
return
|
||||
self.accept()
|
||||
|
||||
def on_preview(self):
|
||||
assert not self.tx.get_dummy_output(DummyAddress.SWAP), "no preview when sending change to ln"
|
||||
self.is_preview = True
|
||||
self.accept()
|
||||
|
||||
@@ -746,8 +758,12 @@ class TxEditor(WindowModalDialog, QtEventListener, Logger):
|
||||
self.message_label.setText(self.error or message_str)
|
||||
|
||||
def _update_send_button(self):
|
||||
# disable preview button when sending change to lightning to prevent the user from saving or
|
||||
# exporting the transaction and broadcasting it later somehow.
|
||||
send_change_to_ln = self.tx and self.tx.get_dummy_output(DummyAddress.SWAP)
|
||||
enabled = bool(self.tx) and not self.error
|
||||
self.preview_button.setEnabled(enabled)
|
||||
self.preview_button.setEnabled(enabled and not send_change_to_ln)
|
||||
self.preview_button.setToolTip(_("Can't show preview when sending change to lightning") if send_change_to_ln else "")
|
||||
self.ok_button.setEnabled(enabled)
|
||||
|
||||
def can_pay_assuming_zero_fees(self, confirmed_only: bool) -> bool:
|
||||
@@ -1073,6 +1089,26 @@ class TxEditor(WindowModalDialog, QtEventListener, Logger):
|
||||
self.submarine_stacked_widget.setCurrentIndex(1)
|
||||
self.submarine_ok_button.setEnabled(False)
|
||||
|
||||
# --- send change to lightning swap functionality ---
|
||||
def request_forward_swap(self):
|
||||
swap_dummy_output = self.tx.get_dummy_output(DummyAddress.SWAP)
|
||||
sm, transport = self.swap_manager, self.swap_transport
|
||||
assert sm and transport and swap_dummy_output and isinstance(swap_dummy_output.value, int)
|
||||
coro = sm.request_swap_for_amount(transport=transport, onchain_amount=int(swap_dummy_output.value))
|
||||
coro_dialog = RunCoroutineDialog(self, _('Requesting swap invoice...'), coro)
|
||||
try:
|
||||
swap, swap_invoice = coro_dialog.run()
|
||||
except (SwapServerError, UserFacingException) as e:
|
||||
self.show_error(str(e))
|
||||
return False
|
||||
except UserCancelled:
|
||||
return False
|
||||
self.tx.replace_output_address(DummyAddress.SWAP, swap.lockup_address)
|
||||
assert self.tx.get_dummy_output(DummyAddress.SWAP) is None
|
||||
self.tx.swap_invoice = swap_invoice
|
||||
self.tx.swap_payment_hash = swap.payment_hash
|
||||
return True
|
||||
|
||||
|
||||
class ConfirmTxDialog(TxEditor):
|
||||
help_text = '' #_('Set the mining fee of your transaction')
|
||||
@@ -1082,8 +1118,8 @@ class ConfirmTxDialog(TxEditor):
|
||||
window: 'ElectrumWindow',
|
||||
make_tx,
|
||||
output_value: Union[int, str],
|
||||
payee_outputs: Optional[list[TxOutput]] = None,
|
||||
allow_preview=True,
|
||||
payee_outputs: Optional[list[PartialTxOutput]] = None,
|
||||
context: TxEditorContext = TxEditorContext.PAYMENT,
|
||||
batching_candidates=None,
|
||||
):
|
||||
|
||||
@@ -1094,7 +1130,7 @@ class ConfirmTxDialog(TxEditor):
|
||||
output_value=output_value,
|
||||
payee_outputs=payee_outputs,
|
||||
title=_("New Transaction"), # todo: adapt title for channel funding tx, swaps
|
||||
allow_preview=allow_preview, # false for channel funding
|
||||
context=context,
|
||||
batching_candidates=batching_candidates,
|
||||
)
|
||||
self.trigger_update()
|
||||
|
||||
@@ -96,7 +96,7 @@ from .wizard.wallet import WIF_HELP_TEXT
|
||||
from .history_list import HistoryList, HistoryModel
|
||||
from .update_checker import UpdateCheck, UpdateCheckThread
|
||||
from .channels_list import ChannelsList
|
||||
from .confirm_tx_dialog import ConfirmTxDialog
|
||||
from .confirm_tx_dialog import ConfirmTxDialog, TxEditorContext
|
||||
from .rbf_dialog import BumpFeeDialog, DSCancelDialog
|
||||
from .qrreader import scan_qrcode_from_camera
|
||||
from .swap_dialog import SwapDialog, InvalidSwapParameters
|
||||
@@ -1504,7 +1504,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
return
|
||||
# we need to know the fee before we broadcast, because the txid is required
|
||||
make_tx = self.mktx_for_open_channel(funding_sat=funding_sat, node_id=node_id)
|
||||
funding_tx, _, _ = self.confirm_tx_dialog(make_tx, funding_sat, allow_preview=False)
|
||||
funding_tx, _, _ = self.confirm_tx_dialog(make_tx, funding_sat, context=TxEditorContext.CHANNEL_FUNDING)
|
||||
if not funding_tx:
|
||||
return
|
||||
self._open_channel(connect_str, funding_sat, push_amt, funding_tx)
|
||||
@@ -1514,7 +1514,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
make_tx,
|
||||
output_value, *,
|
||||
payee_outputs: Optional[list[TxOutput]] = None,
|
||||
allow_preview=True,
|
||||
context: TxEditorContext = TxEditorContext.PAYMENT,
|
||||
batching_candidates=None,
|
||||
) -> tuple[Optional[PartialTransaction], bool, bool]:
|
||||
d = ConfirmTxDialog(
|
||||
@@ -1522,7 +1522,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
make_tx=make_tx,
|
||||
output_value=output_value,
|
||||
payee_outputs=payee_outputs,
|
||||
allow_preview=allow_preview,
|
||||
context=context,
|
||||
batching_candidates=batching_candidates,
|
||||
)
|
||||
return d.run(), d.is_preview, d.did_swap
|
||||
|
||||
@@ -351,24 +351,6 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
# user cancelled or paid with swap
|
||||
return
|
||||
|
||||
if swap_dummy_output := tx.get_dummy_output(DummyAddress.SWAP):
|
||||
sm = self.wallet.lnworker.swap_manager
|
||||
with self.window.create_sm_transport() as transport:
|
||||
if not self.window.initialize_swap_manager(transport):
|
||||
return
|
||||
coro = sm.request_swap_for_amount(transport=transport, onchain_amount=swap_dummy_output.value)
|
||||
try:
|
||||
swap, swap_invoice = self.window.run_coroutine_dialog(coro, _('Requesting swap invoice...'))
|
||||
except (SwapServerError, UserFacingException) as e:
|
||||
self.show_error(str(e))
|
||||
return
|
||||
except UserCancelled:
|
||||
return
|
||||
tx.replace_output_address(DummyAddress.SWAP, swap.lockup_address)
|
||||
assert tx.get_dummy_output(DummyAddress.SWAP) is None
|
||||
tx.swap_invoice = swap_invoice
|
||||
tx.swap_payment_hash = swap.payment_hash
|
||||
|
||||
if is_preview:
|
||||
self.window.show_transaction(
|
||||
tx,
|
||||
|
||||
Reference in New Issue
Block a user