diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index a2aaf6a98..927e6820a 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 39ec97d80..74377b319 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 1550e85fd..474cbe483 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 49680a4af..73a003c04 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 6be01fb35..4e6de3511 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 bdf96e5e4..18abdd998 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)