Skip to content

gui: Add option to show warnings on wallet close, add warning for swaps #9715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion electrum/gui/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from functools import partial
import queue
import asyncio
from typing import Optional, TYPE_CHECKING, Sequence, Union, Dict, Mapping
from typing import Optional, TYPE_CHECKING, Sequence, Union, Dict, Mapping, Callable, List, Set
import concurrent.futures

from PyQt6.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics, QAction, QShortcut
Expand Down Expand Up @@ -271,6 +271,9 @@ def add_optional_tab(tabs, tab, icon, description):

# network callbacks
self.register_callbacks()
# wallet closing warning callbacks
self.closing_warning_callbacks = [] # type: List[Callable[[], Optional[str]]]
self.register_closing_warning_callback(self._check_ongoing_submarine_swaps_callback)
# banner may already be there
if self.network and self.network.banner:
self.console.showMessage(self.network.banner)
Expand Down Expand Up @@ -2684,8 +2687,64 @@ def settings_dialog(self):
if d.need_restart:
self.show_warning(_('Please restart Electrum to activate the new GUI settings'), title=_('Success'))

def _show_closing_warnings(self) -> bool:
"""Show any closing warnings and return True if the user chose to quit anyway."""

warnings: Set[str] = set()
for cb in self.closing_warning_callbacks:
if warning := cb():
warnings.add(warning)

for warning in list(warnings)[:3]:
warning = _("An ongoing operation prevents Electrum from closing:") + "\n\n" + warning
result = self.question(
msg=warning,
icon=QMessageBox.Icon.Warning,
title=_("Are you sure you want to close?"),
)
if not result:
break
else:
# user chose to cancel all warnings or there were no warnings
return True
return False

def register_closing_warning_callback(self, callback: Callable[[], Optional[str]]) -> None:
"""
Registers a callback that will be called when the wallet is closed. If the callback
returns a string it will be shown to the user as a warning to prevent them closing the wallet.
"""
assert not asyncio.iscoroutinefunction(callback)
def warning_callback() -> Optional[str]:
try:
return callback()
except Exception:
self.logger.exception("Error in closing warning callback")
return None
self.logger.debug(f"registering wallet closing warning callback")
self.closing_warning_callbacks.append(warning_callback)

def _check_ongoing_submarine_swaps_callback(self) -> Optional[str]:
"""Callback that will return a warning string if there are unconfirmed swap funding txs."""
if not (self.wallet.has_lightning() and self.wallet.lnworker.swap_manager):
return None
if ongoing_swaps := self.wallet.lnworker.swap_manager.get_pending_swaps():
return "".join((
f"{str(len(ongoing_swaps))} ",
_("pending submarine swap") if len(ongoing_swaps) == 1 else _("pending submarine swaps"),
":\n",
_("Wait until the funding transaction of your swap confirms, otherwise you risk losing your"),
" ",
_("funds") if any(not swap.is_reverse for swap in ongoing_swaps) else _("mining fee prepayment"),
".",
))
return None

def closeEvent(self, event):
# note that closeEvent is NOT called if the user quits with Ctrl-C
if not self._show_closing_warnings():
event.ignore()
return
self.clean_up()
event.accept()

Expand Down
12 changes: 11 additions & 1 deletion electrum/submarine_swaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import json
import os
import ssl
from typing import TYPE_CHECKING, Optional, Dict, Union, Sequence, Tuple, Iterable
from typing import TYPE_CHECKING, Optional, Dict, Sequence, Tuple, Iterable, List
from decimal import Decimal
import math
import time
Expand Down Expand Up @@ -1279,6 +1279,16 @@ def get_group_id_for_payment_hash(self, payment_hash):
if swap:
return swap.spending_txid if swap.is_reverse else swap.funding_txid

def get_pending_swaps(self) -> List[SwapData]:
"""Returns a list of swaps with unconfirmed funding tx (which require us to stay online)."""
pending_swaps: List[SwapData] = []
for swap in self.swaps.values():
if swap.funding_txid and not swap.is_redeemed:
# swap is funded but not marked redeemed by _claim_swap
if self.lnworker.wallet.adb.get_tx_height(swap.funding_txid).height < 1:
# funding TX is not yet confirmed, wallet should not be closed
pending_swaps.append(swap)
return pending_swaps

class SwapServerTransport(Logger):

Expand Down