swaps: add type hints to gui swap suggestion flow
What changed, and why it matters
This commit is a code cleanup: it adds type hints and renames a parameter from recv_amount_sat to recv_amount_sat_or_max to reflect that it can be either a number or the special 'max' marker '!'. It also makes the run_swap_dialog method consistently return a boolean. There is no security-relevant behavior change.
No security action required; treat as routine refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch is a refactoring-only change across the Qt GUI swap suggestion flow and lnworker.py. It introduces Optional/Union/Sequence type annotations, renames recv_amount_sat to recv_amount_sat_or_max, adds an isinstance assertion, and ensures run_swap_dialog returns False in all early-exit/error paths. The lnworker.py change casts delta values to int before appending to suggestions. No functional security fix is present.
Changed components
electrum/gui/qt/main_window.pyelectrum/gui/qt/receive_tab.pyelectrum/gui/qt/send_tab.pyelectrum/gui/qt/swap_dialog.pyelectrum/gui/qt/utxo_list.pyelectrum/lnworker.pyInspect captured patch +45 / −24
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
index a2aaf6a..927e682 100644
--- a/electrum/gui/qt/main_window.py
+++ b/electrum/gui/qt/main_window.py
@@ -107,6 +107,7 @@ from electrum.gui.common_qt.util import TaskThread
if TYPE_CHECKING:
from . import ElectrumGui
from electrum.submarine_swaps import SwapOffer
+ from electrum.lnchannel import Channel
class StatusBarButton(QToolButton):
@@ -1258,32 +1259,43 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def protect(self, func, args, password):
return func(*args, password)
- def run_swap_dialog(self, is_reverse=None, recv_amount_sat=None, channels=None):
+ def run_swap_dialog(
+ self,
+ is_reverse: Optional[bool] = None,
+ recv_amount_sat_or_max: Optional[Union[int, str]] = None,
+ channels: Optional[Sequence['Channel']] = None,
+ ) -> bool:
if not self.network:
self.show_error(_("You are offline."))
- return
+ return False
if not self.wallet.lnworker:
self.show_error(_('Lightning is disabled'))
- return
+ return False
if not self.wallet.lnworker.num_sats_can_send() and not self.wallet.lnworker.num_sats_can_receive():
self.show_error(_("You do not have liquidity in your active channels."))
- return
+ return False
transport = self.create_sm_transport()
if not transport:
- return
+ return False
with transport:
if not self.initialize_swap_manager(transport):
- return
- d = SwapDialog(self, transport, is_reverse=is_reverse, recv_amount_sat=recv_amount_sat, channels=channels)
+ return False
+ d = SwapDialog(
+ self,
+ transport,
+ is_reverse=is_reverse,
+ recv_amount_sat_or_max=recv_amount_sat_or_max,
+ channels=channels
+ )
try:
return d.run(transport)
except InvalidSwapParameters as e:
self.show_error(str(e))
- return
+ return False
except UserCancelled:
- return
+ return False
def create_sm_transport(self) -> Optional['SwapServerTransport']:
sm = self.wallet.lnworker.swap_manager
diff --git a/electrum/gui/qt/receive_tab.py b/electrum/gui/qt/receive_tab.py
index 39ec97d..74377b3 100644
--- a/electrum/gui/qt/receive_tab.py
+++ b/electrum/gui/qt/receive_tab.py
@@ -112,7 +112,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
def on_receive_swap():
if self.receive_swap_button.suggestion:
chan, swap_recv_amount_sat = self.receive_swap_button.suggestion
- self.window.run_swap_dialog(is_reverse=True, recv_amount_sat=swap_recv_amount_sat, channels=[chan])
+ self.window.run_swap_dialog(is_reverse=True, recv_amount_sat_or_max=swap_recv_amount_sat, channels=[chan])
self.receive_swap_button.clicked.connect(on_receive_swap)
buttons = QHBoxLayout()
buttons.addWidget(self.receive_rebalance_button)
diff --git a/electrum/gui/qt/send_tab.py b/electrum/gui/qt/send_tab.py
index 1550e85..474cbe4 100644
--- a/electrum/gui/qt/send_tab.py
+++ b/electrum/gui/qt/send_tab.py
@@ -712,7 +712,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
self.window.new_channel_dialog(amount_sat=amount_sat, min_amount_sat=min_amount_sat)
elif r == 'swap':
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])
+ self.window.run_swap_dialog(is_reverse=False, recv_amount_sat_or_max=swap_recv_amount_sat, channels=[chan])
elif r == 'onchain':
self.pay_onchain_dialog(invoice.get_outputs(), nonlocal_only=True, invoice=invoice)
return
diff --git a/electrum/gui/qt/swap_dialog.py b/electrum/gui/qt/swap_dialog.py
index 49680a4..73a003c 100644
--- a/electrum/gui/qt/swap_dialog.py
+++ b/electrum/gui/qt/swap_dialog.py
@@ -27,6 +27,7 @@ from .my_treeview import create_toolbar_with_menu, MyTreeView
if TYPE_CHECKING:
from .main_window import ElectrumWindow
from electrum.submarine_swaps import SwapServerTransport, SwapOffer
+ from electrum.lnchannel import Channel
CANNOT_RECEIVE_WARNING = _(
"""The requested amount is higher than what you can receive in your currently open channels.
@@ -43,7 +44,14 @@ class InvalidSwapParameters(Exception): pass
class SwapDialog(WindowModalDialog, QtEventListener):
- def __init__(self, window: 'ElectrumWindow', transport: 'SwapServerTransport', is_reverse=None, recv_amount_sat=None, channels=None):
+ def __init__(
+ self,
+ window: 'ElectrumWindow',
+ transport: 'SwapServerTransport',
+ is_reverse: Optional[bool] = None,
+ recv_amount_sat_or_max: Optional[Union[int, str]] = None, # sat or '!'
+ channels: Optional[Sequence['Channel']] = None,
+ ):
WindowModalDialog.__init__(self, window, _('Submarine Swap'))
self.window = window
self.config = window.config
@@ -120,8 +128,9 @@ class SwapDialog(WindowModalDialog, QtEventListener):
buttons = Buttons(CancelButton(self), self.ok_button)
vbox.addLayout(buttons)
buttons.insertWidget(0, self.server_button)
- if recv_amount_sat:
- self.init_recv_amount(recv_amount_sat)
+ if recv_amount_sat_or_max:
+ assert isinstance(recv_amount_sat_or_max, (int, str)), f"invalid {type(recv_amount_sat_or_max)=}"
+ self.init_recv_amount(recv_amount_sat_or_max)
self.update()
self.needs_tx_update = True
@@ -321,15 +330,15 @@ class SwapDialog(WindowModalDialog, QtEventListener):
self.fee_label.setText(fee_text)
self.fee_label.repaint() # macOS hack for #6269
- def run(self, transport):
+ def run(self, transport: 'SwapServerTransport') -> bool:
"""Can raise InvalidSwapParameters."""
if not self.exec():
- return
+ return False
if self.is_reverse:
lightning_amount = self.send_amount_e.get_amount()
onchain_amount = self.recv_amount_e.get_amount()
if lightning_amount is None or onchain_amount is None:
- return
+ return False
sm = self.swap_manager
coro = sm.reverse_swap(
transport=transport,
@@ -342,17 +351,17 @@ class SwapDialog(WindowModalDialog, QtEventListener):
funding_txid = self.window.run_coroutine_dialog(coro, _('Initiating swap...'))
except Exception as e:
self.window.show_error(f"Reverse swap failed: {str(e)}")
- return
+ return False
self.window.on_swap_result(funding_txid, is_reverse=True)
return True
else:
lightning_amount = self.recv_amount_e.get_amount()
onchain_amount = self.send_amount_e.get_amount()
if lightning_amount is None or onchain_amount is None:
- return
+ return False
if lightning_amount > self.lnworker.num_sats_can_receive():
if not self.window.question(CANNOT_RECEIVE_WARNING):
- return
+ return False
self.window.protect(self.do_normal_swap, (transport, lightning_amount, onchain_amount))
return True
diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py
index 6be01fb..4e6de35 100644
--- a/electrum/gui/qt/utxo_list.py
+++ b/electrum/gui/qt/utxo_list.py
@@ -255,7 +255,7 @@ class UTXOList(MyTreeView):
def swap_coins(self, coins):
#self.clear_coincontrol()
self.add_to_coincontrol(coins)
- self.main_window.run_swap_dialog(is_reverse=False, recv_amount_sat='!')
+ self.main_window.run_swap_dialog(is_reverse=False, recv_amount_sat_or_max='!')
self.clear_coincontrol()
def can_open_channel(self, coins):
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
index bdf96e5..18abdd9 100644
--- a/electrum/lnworker.py
+++ b/electrum/lnworker.py
@@ -2925,10 +2925,10 @@ class LNWallet(LNWorker):
# add safety margin
delta += delta // 100 + 1
if func(deltas={chan:delta}) >= amount_sat:
- suggestions.append((chan, delta))
+ suggestions.append((chan, int(delta)))
elif direction == RECEIVED and func(deltas={chan:2*delta}) >= amount_sat:
# MPP heuristics has a 0.5 slope
- suggestions.append((chan, 2*delta))
+ suggestions.append((chan, int(2*delta)))
if not suggestions:
raise NotEnoughFunds
return suggestions
@@ -3002,7 +3002,7 @@ class LNWallet(LNWorker):
return chan, swap_recv_amount
return None
- def suggest_swap_to_receive(self, amount_sat):
+ def suggest_swap_to_receive(self, amount_sat: int):
assert amount_sat > self.num_sats_can_receive(), f"{amount_sat=} | {self.num_sats_can_receive()=}"
try:
suggestions = self._suggest_channels_for_rebalance(RECEIVED, amount_sat)
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.