From aa47a960a79106256359dc567aca222cc15f1148 Mon Sep 17 00:00:00 2001 From: f321x Date: Wed, 10 Dec 2025 16:48:17 +0100 Subject: [PATCH 1/2] ledger: throw UserFacingException for OSError Throws UserFacingException if the communication with the ledger fails due to an OSError. This happens e.g. if the Bitcoin app has been closed. We shouldn't get crash reports for errors due to disconnection. --- electrum/plugins/ledger/ledger.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py index 17f084853..f10f97f44 100644 --- a/electrum/plugins/ledger/ledger.py +++ b/electrum/plugins/ledger/ledger.py @@ -137,6 +137,11 @@ def test_pin_unlocked(func): return func(self, *args, **kwargs) except SecurityStatusNotSatisfiedError: raise UserFacingException(_('Your Ledger is locked. Please unlock it.')) + except OSError as e: + _logger.exception('') + raise UserFacingException( + _('Communication with Ledger failed. Open the Bitcoin app and try again.') + f'\n{str(e)}', + ) return catch_exception From 4712417969fa261d6e1c18edd5f64fd69582d310 Mon Sep 17 00:00:00 2001 From: f321x Date: Wed, 10 Dec 2025 16:58:54 +0100 Subject: [PATCH 2/2] wizard: handle UserFacingException in WCWalletPasswordHardware Handles `UserFacingException` in the `WCWalletPasswordHardware` step of the hardware wallet wizard flow. This fixes the previous FIXME and prevents the crash reporter from getting triggered if the the user e.g. disconnects his hardware wallet during the wallet encryption step. --- electrum/gui/qt/wizard/wallet.py | 37 +++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/electrum/gui/qt/wizard/wallet.py b/electrum/gui/qt/wizard/wallet.py index 6d0926b6e..8397b7ba3 100644 --- a/electrum/gui/qt/wizard/wallet.py +++ b/electrum/gui/qt/wizard/wallet.py @@ -1275,19 +1275,40 @@ class WCWalletPasswordHardware(WalletWizardComponent): self.layout().addLayout(self.playout.layout()) self.layout().addStretch(1) - self._valid = True + self._hw_password = None # type: Optional[str] + self._valid = False + + def on_ready(self): + _name, info = self.wizard_data['hardware_device'] + device_id = info.device.id_ + client = self.plugins.device_manager.client_by_id(device_id, scan_now=False) + if client is None: + self.valid = False + self.error = _("Client for hardware device was unpaired.") + return + + def retrieve_password_task(): + try: + self._hw_password = client.get_password_for_storage_encryption() + self.valid = True + except UserFacingException as e: + self.error = str(e) + self.valid = False + finally: + self.busy = False + + self.busy = True + t = threading.Thread(target=retrieve_password_task, daemon=True) + t.start() def apply(self): + if not self.valid: + return self.wizard_data['encrypt'] = True if self.playout.should_encrypt_storage_with_xpub(): self.wizard_data['xpub_encrypt'] = True - _name, _info = self.wizard_data['hardware_device'] - device_id = _info.device.id_ - client = self.plugins.device_manager.client_by_id(device_id, scan_now=False) - # client.handler = self.plugin.create_handler(self.wizard) - # FIXME client can be None if it was recently disconnected. - # also, even if not None, this might raise (e.g. if it disconnected *just now*): - self.wizard_data['password'] = client.get_password_for_storage_encryption() + assert self._hw_password + self.wizard_data['password'] = self._hw_password else: self.wizard_data['xpub_encrypt'] = False self.wizard_data['password'] = self.playout.new_password()