Skip to content

QML send change to lightning#10723

Open
accumulator wants to merge 6 commits into
spesmilo:masterfrom
accumulator:qml_send_change_to_lightning
Open

QML send change to lightning#10723
accumulator wants to merge 6 commits into
spesmilo:masterfrom
accumulator:qml_send_change_to_lightning

Conversation

@accumulator

Copy link
Copy Markdown
Member

Add change-to-lightning option in ConfirmTxDialog.

This builds on the prep work done in #10694

Options available to the user in ConfirmTxDialog is now a flag on the finalizer, instead of a showOptions boolean property on the QML side. Sending change to lightning is disabled for sweeps and channel-opens.

@accumulator
accumulator marked this pull request as ready for review July 1, 2026 13:31
@accumulator accumulator added this to the 4.8.1 milestone Jul 1, 2026
@accumulator
accumulator force-pushed the qml_send_change_to_lightning branch from 8d5a558 to 708dc41 Compare July 10, 2026 11:24
Layout.fillWidth: true
visible: Daemon.currentWallet.isLightning && Daemon.currentWallet.lightningCanReceive.satsInt > 0
visible: finalizer.txOptions & TxFinalizer.TxOptions.SEND_CHANGE_TO_LIGHTNING
&& Daemon.currentWallet.isLightning && Daemon.currentWallet.lightningCanReceive.satsInt > 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe using > submarine_swaps.MIN_SWAP_AMOUNT_SAT would be more reliable UX than > 0 as it is still not possible to receive change on lightning if the inbound liquidity is lower than the minimum swap amount.

transport = self.swap_transport

try:
if not swap_manager.is_initialized.is_set():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user hasn't configured a swap provider already previously by doing a manual swap this will always time out and show "Will not send change to lightning" without a reason.

Would it be possible to let the NostrSwapServerDialog pop-up similar to the manual swap view if no swapserver was configured or the configured swapserver isn't active anymore?

Otherwise the user might never figure out how to actually find and select a swapserver.

onSwapStart: {
_swapwaitdialog = app.messageDialog.createObject(mainView, {
title: qsTr('Please wait...'),
text: [qsTr('waiting for lightning invoice'), ''].join('\n\n'),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
text: [qsTr('waiting for lightning invoice'), ''].join('\n\n'),
text: [qsTr('Requesting Submarine Swap for change...'), ''].join('\n\n'),

Might be easier to understand? It might not be clear to the user why they have to wait for a lightning invoice if they aren't aware of the swap technicalities.

Image

if not self._wallet.wallet.sign_transaction(tx, self._wallet.password):
raise Exception('tx not signed')
self.swapStart.emit()
funding_txid = await swap_manager.wait_for_htlcs_and_broadcast(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit weird that after the swap is funded it goes back to the InvoiceDialog, allowing the user to modify the AmountEdit and showing a incorrect This invoice was already paid, instead of showing the regular Payment Sent message popup. Maybe this is because the broadcast doesn't emit broadcastSucceeded?

Image

try:
if not swap_manager.is_initialized.is_set():
await asyncio.wait_for(swap_manager.is_initialized.wait(), timeout=5)
except Exception as e:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the Exception branches in the swap task call self.update() after the tx has been mutated with the dummy address, so that if the user clicks "Pay" again after a failure he doesn't accidentally fund the failed swaps dummy address?

Comment on lines +731 to +733
_swapwaitdialog.accepted.connect(function() {
cancelSwap()
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should clicking outside the dialog/back maybe also cancel the swap?

_swapwaitdialog.rejected.connect(function() {
    cancelSwap()
})

Alternatively we could also disallow closing the dialog by clicking outside so the user must either wait or click cancel.

):
super().__init__(parent)
TxFeeSlider.__init__(self, parent)
SubmarineSwapMixin.__init__(self, None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.register_callbacks() is not called in the QETxFinalizer.__init__(), so the SubmarineSwapMixin callbacks will never emit swapAvailabilityChanged

return

send_change_to_lightning = self._send_change_to_lightning()
if send_change_to_lightning:

@f321x f321x Jul 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the relay connection fails swapAvailabilityChanged calls update() which then tries to prepare the swap transport again, so this is a infinite loop. On desktop we try to connect only once, maybe we should temporarily disable change to lightning if it isn't possible to connect or discover swap providers?


def on_destroy(self):
self._logger.debug('on_destroy')
self.swap_transport_cleanup()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This raises trying to emit swapAvailabilityChanged after the QETxFinalizer has been destroyed:

311.03 | I | submarine_swaps.NostrTransport | connected relays: ['wss://relay.getalby.com/v1', 'wss://relay.damus.io', 'wss://nostr.einundzwanzig.space', 'wss://nostr.mom', 'wss://nos.lol', 'wss://relay.primal.net', 'wss://ftp.halifax.rwth-aachen.de/nostr']
311.07 | D | gui.qml.qetxfinalizer | on_destroy
311.08 | E | concurrent.futures | exception calling callback for <Future at 0x7f98a020b1e0 state=cancelled>
Traceback (most recent call last):
  File "/usr/lib64/python3.14/concurrent/futures/_base.py", line 335, in _invoke_callbacks
    callback(self)
    ~~~~~~~~^^^^^^
  File "/var/home/user/code/code_vm/electrum-2/electrum/gui/common_qt/swaps.py", line 85, in transport_initialize_done
    self.swapAvailabilityChanged.emit()
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: wrapped C/C++ object of type QETxFinalizer has been deleted
311.08 | I | submarine_swaps.NostrTransport | shutting down nostr transport
311.14 | I | submarine_swaps.NostrTransport | taskgroup stopped.
311.15 | I | submarine_swaps.NostrTransport | nostr transport shut down

})
confirmdialog.accepted.connect(function() {
finalizerDialog.finalizer.send()
close()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
finalizerDialog.close()

Pressing "Sweep" in a watch-only wallet doesn't close the sweep view anymore. I guess close() needs to be replaced with finalizerDialog.close().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants