From f3ba25df7d2f6e3dd33aa075a3345c8bd186134d Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 2 Mar 2026 18:04:08 +0100 Subject: [PATCH 1/2] qt: utxo_list: only enable 'fully spend...' menu if there are unfrozen coins in the selection. otherwise, when selecting only frozen coins, the set of usable coins is empty, and the menu options will instead fall back to using ALL coins without informing the user. --- electrum/gui/qt/utxo_list.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py index 94f8fe086..a5aef66e4 100644 --- a/electrum/gui/qt/utxo_list.py +++ b/electrum/gui/qt/utxo_list.py @@ -301,11 +301,15 @@ class UTXOList(MyTreeView): def create_menu(self, position): selected = self.get_selected_outpoints() - menu = QMenu() - menu.setSeparatorsCollapsible(True) # consecutive separators are merged together coins = [self._utxo_dict[name] for name in selected] + if not coins: return + + unfrozen_coins = self._filter_frozen_coins(coins) + menu = QMenu() + menu.setSeparatorsCollapsible(True) # consecutive separators are merged together + if len(coins) == 1: idx = self.indexAt(position) if not idx.isValid(): @@ -320,18 +324,20 @@ class UTXOList(MyTreeView): cc = self.add_copy_menu(menu, idx) cc.addAction(_("Long Output point"), lambda: self.place_text_on_clipboard(utxo.prevout.to_str(), title="Long Output point")) # fully spend - menu_spend = menu.addMenu(_("Fully spend") + '…') - m = menu_spend.addAction(_("send to address in clipboard"), lambda: self.pay_to_clipboard_address(coins)) + m = menu_spend = menu.addMenu(_("Fully spend") + '…') + m.setEnabled(bool(unfrozen_coins)) + m = menu_spend.addAction(_("send to address in clipboard"), lambda: self.pay_to_clipboard_address(unfrozen_coins)) m.setEnabled(self.clipboard_contains_address()) - m = menu_spend.addAction(_("in new channel"), lambda: self.open_channel_with_coins(coins)) - m.setEnabled(self.can_open_channel(coins)) - m = menu_spend.addAction(_("in submarine swap"), lambda: self.swap_coins(coins)) - m.setEnabled(self.can_swap_coins(coins)) + m = menu_spend.addAction(_("in new channel"), lambda: self.open_channel_with_coins(unfrozen_coins)) + m.setEnabled(self.can_open_channel(unfrozen_coins)) + m = menu_spend.addAction(_("in submarine swap"), lambda: self.swap_coins(unfrozen_coins)) + m.setEnabled(self.can_swap_coins(unfrozen_coins)) # coin control if self.are_in_coincontrol(coins): menu.addAction(_("Remove from coin control"), lambda: self.remove_from_coincontrol(coins)) else: - menu.addAction(_("Add to coin control"), lambda: self.add_to_coincontrol(coins)) + m = menu.addAction(_("Add to coin control"), lambda: self.add_to_coincontrol(coins)) + m.setEnabled(bool(unfrozen_coins)) # Freeze menu if len(coins) == 1: utxo = coins[0] From 4c27b8de8d22a9e8869932cc3d0c36b456be9686 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 3 Mar 2026 17:29:22 +0000 Subject: [PATCH 2/2] qt: utxo_list: add asserts to helper methods that coins are selected - otherwise they default to selecting all coins, which is unlikely to be what the user intends - prev commit makes sure this should never happen --- electrum/gui/qt/utxo_list.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py index a5aef66e4..f55d4d856 100644 --- a/electrum/gui/qt/utxo_list.py +++ b/electrum/gui/qt/utxo_list.py @@ -253,7 +253,8 @@ class UTXOList(MyTreeView): return False return True - def swap_coins(self, coins): + def swap_coins(self, coins: list[PartialTxInput]) -> None: + assert coins, "no coins selected?" #self.clear_coincontrol() self.add_to_coincontrol(coins) self.main_window.run_swap_dialog(is_reverse=False, recv_amount_sat_or_max='!') @@ -265,7 +266,8 @@ class UTXOList(MyTreeView): value = sum(x.value_sats() for x in coins) return value >= MIN_FUNDING_SAT and value <= self.config.LIGHTNING_MAX_FUNDING_SAT - def open_channel_with_coins(self, coins): + def open_channel_with_coins(self, coins: list[PartialTxInput]) -> None: + assert coins, "no coins selected?" # todo : use a single dialog in new flow #self.clear_coincontrol() self.add_to_coincontrol(coins) @@ -279,11 +281,12 @@ class UTXOList(MyTreeView): d.run() self.clear_coincontrol() - def clipboard_contains_address(self): + def clipboard_contains_address(self) -> bool: text = self.main_window.app.clipboard().text() return is_address(text) - def pay_to_clipboard_address(self, coins): + def pay_to_clipboard_address(self, coins: list[PartialTxInput]) -> None: + assert coins, "no coins selected?" if not self.clipboard_contains_address(): self.main_window.show_error(_('Clipboard doesn\'t contain a valid address')) return