Skip to content

Commit 253bf84

Browse files
committed
qt: improve send_change_to_lightning feedback
Improves the GUI feedback given to the user when sending change to lightning by showing different failure messages and the additional fee charged for the swap. Also considers the min/max swap amount announced by the provider when deciding to add the swap dummy output in make tx for improved reliability.
1 parent 3d27992 commit 253bf84

File tree

5 files changed

+72
-27
lines changed

5 files changed

+72
-27
lines changed

electrum/gui/qt/confirm_tx_dialog.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,21 @@ def get_messages(self):
543543
self.error = _('Fee estimates not available. Please set a fixed fee or feerate.')
544544
if self.tx.get_dummy_output(DummyAddress.SWAP):
545545
messages.append(_('This transaction will send funds to a submarine swap.'))
546+
elif self.config.WALLET_SEND_CHANGE_TO_LIGHTNING and self.tx.has_change():
547+
swap_msg = _('Will not send change to lightning: ')
548+
if not self.wallet.has_lightning():
549+
swap_msg += _('Lightning is not enabled.')
550+
elif sum(o.value for o in self.tx.get_change_outputs()) > self.wallet.lnworker.num_sats_can_receive():
551+
swap_msg += _("Your channels cannot receive this amount.")
552+
elif self.wallet.lnworker.swap_manager.is_initialized.is_set():
553+
# we could display the limits here but then the dialog gets very convoluted
554+
swap_msg += _('Change amount outside of your swap providers limits.')
555+
else:
556+
# If initialization of the swap manager failed they already got an error
557+
# popup before, so they should know. It might also be a channel open.
558+
swap_msg = None
559+
if swap_msg:
560+
messages.append(swap_msg)
546561
# warn if spending unconf
547562
if any((txin.block_height is not None and txin.block_height<=0) for txin in self.tx.inputs()):
548563
messages.append(_('This transaction will spend unconfirmed coins.'))
@@ -707,9 +722,22 @@ def create_grid(self):
707722
return grid
708723

709724
def _update_extra_fees(self):
710-
x_fee = run_hook('get_tx_extra_fee', self.wallet, self.tx)
711-
if x_fee:
712-
x_fee_address, x_fee_amount = x_fee
725+
x_fee_total = 0
726+
727+
# fetch plugin extra fees
728+
x_fee_plugin = run_hook('get_tx_extra_fee', self.wallet, self.tx)
729+
if x_fee_plugin:
730+
_x_fee_address_plugin, x_fee_amount_plugin = x_fee_plugin
731+
x_fee_total += x_fee_amount_plugin
732+
733+
# this is a forward swap (send change to lightning)
734+
if dummy_output := self.tx.get_dummy_output(DummyAddress.SWAP):
735+
sm = self.wallet.lnworker.swap_manager
736+
ln_amount_we_recv = sm.get_recv_amount(send_amount=dummy_output.value, is_reverse=False)
737+
if ln_amount_we_recv is not None:
738+
x_fee_total += dummy_output.value - ln_amount_we_recv
739+
740+
if x_fee_total > 0:
713741
self.extra_fee_label.setVisible(True)
714742
self.extra_fee_value.setVisible(True)
715-
self.extra_fee_value.setText(self.main_window.format_amount_and_units(x_fee_amount))
743+
self.extra_fee_value.setText(self.main_window.format_amount_and_units(x_fee_total))

electrum/gui/qt/main_window.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1326,7 +1326,7 @@ async def wait_until_initialized():
13261326
except asyncio.TimeoutError:
13271327
return
13281328
try:
1329-
self.run_coroutine_dialog(wait_until_initialized(), _('Please wait...'))
1329+
self.run_coroutine_dialog(wait_until_initialized(), _('Fetching swap providers...'))
13301330
except UserCancelled:
13311331
return False
13321332
except Exception as e:
@@ -1335,6 +1335,7 @@ async def wait_until_initialized():
13351335

13361336
if not sm.is_initialized.is_set():
13371337
if not self.config.SWAPSERVER_URL:
1338+
assert isinstance(transport, NostrTransport)
13381339
if not self.choose_swapserver_dialog(transport):
13391340
return False
13401341
else:

electrum/gui/qt/send_tab.py

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from electrum.network import TxBroadcastError, BestEffortRequestFailed
2323
from electrum.payment_identifier import (PaymentIdentifierType, PaymentIdentifier, invoice_from_payment_identifier,
2424
payment_identifier_from_invoice)
25-
from electrum.submarine_swaps import SwapServerError
25+
from electrum.submarine_swaps import SwapServerError, SwapServerTransport
2626
from electrum.fee_policy import FeePolicy, FixedFeePolicy
2727

2828
from .amountedit import AmountEdit, BTCAmountEdit, SizedFreezableLineEdit
@@ -291,17 +291,31 @@ def spend_max(self):
291291
msg += "\n" + _("Some coins are frozen: {} (can be unfrozen in the Addresses or in the Coins tab)").format(frozen_bal)
292292
QToolTip.showText(self.max_button.mapToGlobal(QPoint(0, 0)), msg)
293293

294+
@staticmethod
295+
def maybe_pass_swap_transport(func):
296+
def wrapper(self, *args, **kwargs):
297+
assert isinstance(self, SendTab)
298+
if self.config.WALLET_SEND_CHANGE_TO_LIGHTNING and not kwargs.get('swap_transport'):
299+
with self.window.create_sm_transport() as transport:
300+
if self.window.initialize_swap_manager(transport):
301+
kwargs['swap_transport'] = transport
302+
return func(self, *args, **kwargs)
303+
return func(self, *args, **kwargs)
304+
return wrapper
305+
294306
# TODO: instead of passing outputs, use an invoice instead (like pay_lightning_invoice)
295307
# so we have more context (we cannot rely on send_tab field contents or payment identifier
296308
# as this method is called from other places as well).
309+
@maybe_pass_swap_transport
297310
def pay_onchain_dialog(
298311
self,
299312
outputs: List[PartialTxOutput],
300313
*,
301314
nonlocal_only=False,
302315
external_keypairs: Mapping[bytes, bytes] = None,
303316
get_coins: Callable[..., Sequence[PartialTxInput]] = None,
304-
invoice: Optional[Invoice] = None
317+
invoice: Optional[Invoice] = None,
318+
swap_transport: Optional['SwapServerTransport'] = None,
305319
) -> None:
306320
# trustedcoin requires this
307321
if run_hook('abort_send', self):
@@ -320,7 +334,7 @@ def make_tx(fee_policy, *, confirmed_only=False, base_tx=None):
320334
outputs=outputs,
321335
base_tx=base_tx,
322336
is_sweep=is_sweep,
323-
send_change_to_lightning=self.config.WALLET_SEND_CHANGE_TO_LIGHTNING,
337+
send_change_to_lightning=bool(swap_transport),
324338
merge_duplicate_outputs=self.config.WALLET_MERGE_DUPLICATE_OUTPUTS,
325339
)
326340
output_values = [x.value for x in outputs]
@@ -341,22 +355,20 @@ def make_tx(fee_policy, *, confirmed_only=False, base_tx=None):
341355
return
342356

343357
if swap_dummy_output := tx.get_dummy_output(DummyAddress.SWAP):
344-
sm = self.wallet.lnworker.swap_manager
345-
with self.window.create_sm_transport() as transport:
346-
if not self.window.initialize_swap_manager(transport):
347-
return
348-
coro = sm.request_swap_for_amount(transport=transport, onchain_amount=swap_dummy_output.value)
349-
try:
350-
swap, swap_invoice = self.window.run_coroutine_dialog(coro, _('Requesting swap invoice...'))
351-
except (SwapServerError, UserFacingException) as e:
352-
self.show_error(str(e))
353-
return
354-
except UserCancelled:
355-
return
356-
tx.replace_output_address(DummyAddress.SWAP, swap.lockup_address)
357-
assert tx.get_dummy_output(DummyAddress.SWAP) is None
358-
tx.swap_invoice = swap_invoice
359-
tx.swap_payment_hash = swap.payment_hash
358+
assert swap_transport and swap_transport.sm.is_initialized.is_set()
359+
sm = swap_transport.sm
360+
coro = sm.request_forward_swap_for_amount(transport=swap_transport, onchain_amount=swap_dummy_output.value)
361+
try:
362+
swap, swap_invoice = self.window.run_coroutine_dialog(coro, _('Requesting swap invoice...'))
363+
except (SwapServerError, UserFacingException) as e:
364+
self.show_error(str(e))
365+
return
366+
except UserCancelled:
367+
return
368+
tx.replace_output_address(DummyAddress.SWAP, swap.lockup_address)
369+
assert tx.get_dummy_output(DummyAddress.SWAP) is None
370+
tx.swap_invoice = swap_invoice
371+
tx.swap_payment_hash = swap.payment_hash
360372

361373
if is_preview:
362374
self.window.show_transaction(tx, external_keypairs=external_keypairs, invoice=invoice)

electrum/submarine_swaps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,7 @@ def create_funding_tx(
977977
return tx
978978

979979
@log_exceptions
980-
async def request_swap_for_amount(
980+
async def request_forward_swap_for_amount(
981981
self,
982982
*,
983983
transport: 'SwapServerTransport',

electrum/wallet.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2059,10 +2059,14 @@ def fee_estimator(size: Union[int, float, Decimal]) -> int:
20592059
dust_threshold=self.dust_threshold(),
20602060
BIP69_sort=BIP69_sort)
20612061
if self.lnworker and send_change_to_lightning:
2062+
sm = self.lnworker.swap_manager
2063+
assert sm and sm.is_initialized.is_set(), "swap manager should be initialized here"
2064+
min_swap_amnt, max_swap_amnt = sm.get_min_amount(), sm.get_provider_max_reverse_amount()
20622065
change = tx.get_change_outputs()
2063-
if len(change) == 1:
2066+
assert len(change) <= 1, len(change)
2067+
if len(change) == 1: # tx creates change
20642068
amount = change[0].value
2065-
if amount <= self.lnworker.num_sats_can_receive():
2069+
if min_swap_amnt <= amount <= min(self.lnworker.num_sats_can_receive(), max_swap_amnt):
20662070
tx.replace_output_address(change[0].address, DummyAddress.SWAP)
20672071
if self.should_keep_reserve_utxo(tx.inputs(), tx.outputs(), is_anchor_channel_opening):
20682072
raise NotEnoughFunds()

0 commit comments

Comments
 (0)