qt: utxo_list: only enable 'fully spend...' menu if there are unfrozen coins in the selection.
What changed, and why it matters
This commit fixes a user-interface bug in Electrum's Qt wallet. Previously, if a user right-clicked only 'frozen' coins (coins intentionally locked from spending) and chose 'Fully spend', the wallet would silently switch to using all spendable coins instead of warning that the selected ones were frozen. The fix disables the 'Fully spend' and 'Add to coin control' options when the selection contains no unfrozen coins, preventing accidental, unexpected transactions.
Apply the patch. It is a targeted UI hardening fix. Users should upgrade to a version containing this commit and verify that frozen coins cannot be silently included via the 'Fully spend' menu. No independent CVE or advisory is supplied; consider whether the project should document this as a UI safety fix.
Security signals we found
Unexpected fallback to all UTXOs when selected coins are frozen
UI action enabled in a state that produces unintended transaction inputs
Potential for user to spend coins they believed were frozen/locked
No explicit warning when selected UTXOs are ignored
Evidence from the diff
In electrum/gui/qt/utxo_list.py, create_menu() previously built the context menu before checking whether selected coins were frozen. The ‘Fully spend’ submenu actions (pay to clipboard address, open channel, submarine swap) and ‘Add to coin control’ were wired to the full selected coin list. When only frozen coins were selected, the downstream spend helpers received an empty usable set and fell back to all wallet coins without user notification. The patch filters selected coins with _filter_frozen_coins(), returns early if no coins exist, disables the ‘Fully spend’ submenu and ‘Add to coin control’ when unfrozen_coins is empty, and passes only unfrozen_coins to the spend actions.
Changed components
electrum/gui/qt/utxo_list.pyQt UTXO list context menu'Fully spend' submenu'Add to coin control' actionInspect captured patch +15 / −9
diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py
index 94f8fe0..a5aef66 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]
Why this scored 48/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.