URI: 
       tintroduce UserFacingException - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit bd32b88f625a698bcc466faa17a3adb58ed229a4
   DIR parent dace2e5495165a1a6ff186442966dde1934b69c0
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Thu,  8 Nov 2018 19:46:15 +0100
       
       introduce UserFacingException
       
       we should not raise generic Exception when wanting to communicate with
       tthe user. it makes distinguishing programming errors and messages hard,
       as the caller will necessarily need to catch all Exceptions then
       
       Diffstat:
         M electrum/gui/qt/main_window.py      |      13 +++++++++----
         M electrum/plugin.py                  |       4 +++-
         M electrum/plugins/coldcard/coldcard… |      14 +++++++-------
         M electrum/plugins/digitalbitbox/dig… |      20 ++++++++++----------
         M electrum/plugins/hw_wallet/plugin.… |       6 +++---
         M electrum/plugins/keepkey/keepkey.py |      12 ++++++------
         M electrum/plugins/ledger/ledger.py   |      32 ++++++++++++++++----------------
         M electrum/plugins/safe_t/safe_t.py   |      12 ++++++------
         M electrum/plugins/trezor/trezor.py   |      12 ++++++------
         M electrum/util.py                    |       4 ++++
       
       10 files changed, 70 insertions(+), 59 deletions(-)
       ---
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -55,7 +55,7 @@ from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
                                   export_meta, import_meta, bh2u, bfh, InvalidPassword,
                                   base_units, base_units_list, base_unit_name_to_decimal_point,
                                   decimal_point_to_base_unit_name, quantize_feerate,
       -                           UnknownBaseUnit, DECIMAL_POINT_DEFAULT)
       +                           UnknownBaseUnit, DECIMAL_POINT_DEFAULT, UserFacingException)
        from electrum.transaction import Transaction, TxOutput
        from electrum.address_synchronizer import AddTransactionException
        from electrum.wallet import (Multisig_Wallet, CannotBumpFee, Abstract_Wallet,
       t@@ -300,12 +300,17 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.raise_()
        
            def on_error(self, exc_info):
       -        if not isinstance(exc_info[1], UserCancelled):
       +        e = exc_info[1]
       +        if isinstance(e, UserCancelled):
       +            pass
       +        elif isinstance(e, UserFacingException):
       +            self.show_error(str(e))
       +        else:
                    try:
                        traceback.print_exception(*exc_info)
                    except OSError:
       -                pass  # see #4418; try to at least show popup:
       -            self.show_error(str(exc_info[1]))
       +                pass  # see #4418
       +            self.show_error(str(e))
        
            def on_network(self, event, *args):
                if event == 'wallet_updated':
   DIR diff --git a/electrum/plugin.py b/electrum/plugin.py
       t@@ -32,7 +32,7 @@ from typing import NamedTuple, Any, Union, TYPE_CHECKING, Optional
        
        from .i18n import _
        from .util import (profiler, PrintError, DaemonThread, UserCancelled,
       -                   ThreadJob, print_error)
       +                   ThreadJob, print_error, UserFacingException)
        from . import bip32
        from . import plugins
        from .simple_config import SimpleConfig
       t@@ -500,6 +500,8 @@ class DeviceMgr(ThreadJob, PrintError):
                        continue
                    try:
                        client = self.create_client(device, handler, plugin)
       +            except UserFacingException:
       +                raise
                    except BaseException as e:
                        self.print_error(f'failed to create client for {plugin.name} at {device.path}: {repr(e)}')
                        continue
   DIR diff --git a/electrum/plugins/coldcard/coldcard.py b/electrum/plugins/coldcard/coldcard.py
       t@@ -13,7 +13,7 @@ from electrum.keystore import Hardware_KeyStore, xpubkey_to_pubkey, Xpub
        from electrum.transaction import Transaction
        from electrum.wallet import Standard_Wallet
        from electrum.crypto import hash_160
       -from electrum.util import print_error, bfh, bh2u, versiontuple
       +from electrum.util import print_error, bfh, bh2u, versiontuple, UserFacingException
        from electrum.base_wizard import ScriptTypeNotSupported
        
        from ..hw_wallet import HW_PluginBase
       t@@ -190,8 +190,8 @@ class CKCCClient:
                try:
                    __, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub)
                except InvalidMasterKeyVersionBytes:
       -            raise Exception(_('Invalid xpub magic. Make sure your {} device is set to the correct chain.')
       -                            .format(self.device)) from None
       +            raise UserFacingException(_('Invalid xpub magic. Make sure your {} device is set to the correct chain.')
       +                                      .format(self.device)) from None
                if xtype != 'standard':
                    xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
                return xpub
       t@@ -305,7 +305,7 @@ class Coldcard_KeyStore(Hardware_KeyStore):
                    self.ux_busy = False
                if clear_client:
                    self.client = None
       -        raise Exception(message)
       +        raise UserFacingException(message)
        
            def wrap_busy(func):
                # decorator: function takes over the UX on the device.
       t@@ -318,7 +318,7 @@ class Coldcard_KeyStore(Hardware_KeyStore):
                return wrapper
        
            def decrypt_message(self, pubkey, message, password):
       -        raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device))
       +        raise UserFacingException(_('Encryption and decryption are currently not supported for {}').format(self.device))
        
            @wrap_busy
            def sign_message(self, sequence, message, password):
       t@@ -650,8 +650,8 @@ class ColdcardPlugin(HW_PluginBase):
                device_id = device_info.device.id_
                client = devmgr.client_by_id(device_id)
                if client is None:
       -            raise Exception(_('Failed to create a client for this device.') + '\n' +
       -                            _('Make sure it is in the correct state.'))
       +            raise UserFacingException(_('Failed to create a client for this device.') + '\n' +
       +                                      _('Make sure it is in the correct state.'))
                client.handler = self.create_handler(wizard)
        
            def get_xpub(self, device_id, derivation, xtype, wizard):
   DIR diff --git a/electrum/plugins/digitalbitbox/digitalbitbox.py b/electrum/plugins/digitalbitbox/digitalbitbox.py
       t@@ -16,7 +16,7 @@ try:
            from electrum.i18n import _
            from electrum.keystore import Hardware_KeyStore
            from ..hw_wallet import HW_PluginBase
       -    from electrum.util import print_error, to_string, UserCancelled
       +    from electrum.util import print_error, to_string, UserCancelled, UserFacingException
            from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET
        
            import time
       t@@ -114,7 +114,7 @@ class DigitalBitbox_Client():
            def dbb_has_password(self):
                reply = self.hid_send_plain(b'{"ping":""}')
                if 'ping' not in reply:
       -            raise Exception(_('Device communication error. Please unplug and replug your Digital Bitbox.'))
       +            raise UserFacingException(_('Device communication error. Please unplug and replug your Digital Bitbox.'))
                if reply['ping'] == 'password':
                    return True
                return False
       t@@ -221,7 +221,7 @@ class DigitalBitbox_Client():
                        return
                else:
                    if self.hid_send_encrypt(b'{"device":"info"}')['device']['lock']:
       -                raise Exception(_("Full 2FA enabled. This is not supported yet."))
       +                raise UserFacingException(_("Full 2FA enabled. This is not supported yet."))
                    # Use existing seed
                self.isInitialized = True
        
       t@@ -294,7 +294,7 @@ class DigitalBitbox_Client():
                msg = ('{"seed":{"source": "create", "key": "%s", "filename": "%s", "entropy": "%s"}}' % (key, filename, 'Digital Bitbox Electrum Plugin')).encode('utf8')
                reply = self.hid_send_encrypt(msg)
                if 'error' in reply:
       -            raise Exception(reply['error']['message'])
       +            raise UserFacingException(reply['error']['message'])
        
        
            def dbb_erase(self):
       t@@ -304,16 +304,16 @@ class DigitalBitbox_Client():
                hid_reply = self.hid_send_encrypt(b'{"reset":"__ERASE__"}')
                self.handler.finished()
                if 'error' in hid_reply:
       -            raise Exception(hid_reply['error']['message'])
       +            raise UserFacingException(hid_reply['error']['message'])
                else:
                    self.password = None
       -            raise Exception('Device erased')
       +            raise UserFacingException('Device erased')
        
        
            def dbb_load_backup(self, show_msg=True):
                backups = self.hid_send_encrypt(b'{"backup":"list"}')
                if 'error' in backups:
       -            raise Exception(backups['error']['message'])
       +            raise UserFacingException(backups['error']['message'])
                try:
                    f = self.handler.win.query_choice(_("Choose a backup file:"), backups['backup'])
                except Exception:
       t@@ -330,7 +330,7 @@ class DigitalBitbox_Client():
                hid_reply = self.hid_send_encrypt(msg)
                self.handler.finished()
                if 'error' in hid_reply:
       -            raise Exception(hid_reply['error']['message'])
       +            raise UserFacingException(hid_reply['error']['message'])
                return True
        
        
       t@@ -388,7 +388,7 @@ class DigitalBitbox_Client():
                    r = to_string(r, 'utf8')
                    reply = json.loads(r)
                except Exception as e:
       -            print_error('Exception caught ' + str(e))
       +            print_error('Exception caught ' + repr(e))
                return reply
        
        
       t@@ -405,7 +405,7 @@ class DigitalBitbox_Client():
                    if 'error' in reply:
                        self.password = None
                except Exception as e:
       -            print_error('Exception caught ' + str(e))
       +            print_error('Exception caught ' + repr(e))
                return reply
        
        
   DIR diff --git a/electrum/plugins/hw_wallet/plugin.py b/electrum/plugins/hw_wallet/plugin.py
       t@@ -27,7 +27,7 @@
        from electrum.plugin import BasePlugin, hook
        from electrum.i18n import _
        from electrum.bitcoin import is_address, TYPE_SCRIPT
       -from electrum.util import bfh, versiontuple
       +from electrum.util import bfh, versiontuple, UserFacingException
        from electrum.transaction import opcodes, TxOutput, Transaction
        
        
       t@@ -130,9 +130,9 @@ def trezor_validate_op_return_output_and_get_data(output: TxOutput) -> bytes:
            script = bfh(output.address)
            if not (script[0] == opcodes.OP_RETURN and
                    script[1] == len(script) - 2 and script[1] <= 75):
       -        raise Exception(_("Only OP_RETURN scripts, with one constant push, are supported."))
       +        raise UserFacingException(_("Only OP_RETURN scripts, with one constant push, are supported."))
            if output.value != 0:
       -        raise Exception(_("Amount for OP_RETURN output must be zero."))
       +        raise UserFacingException(_("Amount for OP_RETURN output must be zero."))
            return script[2:]
        
        
   DIR diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py
       t@@ -2,7 +2,7 @@ from binascii import hexlify, unhexlify
        import traceback
        import sys
        
       -from electrum.util import bfh, bh2u, UserCancelled
       +from electrum.util import bfh, bh2u, UserCancelled, UserFacingException
        from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
        from electrum.bip32 import deserialize_xpub
        from electrum import constants
       t@@ -30,7 +30,7 @@ class KeepKey_KeyStore(Hardware_KeyStore):
                return self.plugin.get_client(self, force_pair)
        
            def decrypt_message(self, sequence, message, password):
       -        raise RuntimeError(_('Encryption and decryption are not implemented by {}').format(self.device))
       +        raise UserFacingException(_('Encryption and decryption are not implemented by {}').format(self.device))
        
            def sign_message(self, sequence, message, password):
                client = self.get_client()
       t@@ -50,7 +50,7 @@ class KeepKey_KeyStore(Hardware_KeyStore):
                    pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
                    tx_hash = txin['prevout_hash']
                    if txin.get('prev_tx') is None and not Transaction.is_segwit_input(txin):
       -                raise Exception(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
       +                raise UserFacingException(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
                    prev_tx[tx_hash] = txin['prev_tx']
                    for x_pubkey in x_pubkeys:
                        if not is_xpubkey(x_pubkey):
       t@@ -138,7 +138,7 @@ class KeepKeyPlugin(HW_PluginBase):
                    if handler:
                        handler.show_error(msg)
                    else:
       -                raise Exception(msg)
       +                raise UserFacingException(msg)
                    return None
        
                return client
       t@@ -242,8 +242,8 @@ class KeepKeyPlugin(HW_PluginBase):
                device_id = device_info.device.id_
                client = devmgr.client_by_id(device_id)
                if client is None:
       -            raise Exception(_('Failed to create a client for this device.') + '\n' +
       -                            _('Make sure it is in the correct state.'))
       +            raise UserFacingException(_('Failed to create a client for this device.') + '\n' +
       +                                      _('Make sure it is in the correct state.'))
                # fixme: we should use: client.handler = wizard
                client.handler = self.create_handler(wizard)
                if not device_info.initialized:
   DIR diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py
       t@@ -9,7 +9,7 @@ from electrum.i18n import _
        from electrum.keystore import Hardware_KeyStore
        from electrum.transaction import Transaction
        from electrum.wallet import Standard_Wallet
       -from electrum.util import print_error, bfh, bh2u, versiontuple
       +from electrum.util import print_error, bfh, bh2u, versiontuple, UserFacingException
        from electrum.base_wizard import ScriptTypeNotSupported
        
        from ..hw_wallet import HW_PluginBase
       t@@ -46,7 +46,7 @@ def test_pin_unlocked(func):
                    return func(self, *args, **kwargs)
                except BTChipException as e:
                    if e.sw == 0x6982:
       -                raise Exception(_('Your Ledger is locked. Please unlock it.'))
       +                raise UserFacingException(_('Your Ledger is locked. Please unlock it.'))
                    else:
                        raise
            return catch_exception
       t@@ -92,9 +92,9 @@ class Ledger_Client():
                #self.get_client() # prompt for the PIN before displaying the dialog if necessary
                #self.handler.show_message("Computing master public key")
                if xtype in ['p2wpkh', 'p2wsh'] and not self.supports_native_segwit():
       -            raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT)
       +            raise UserFacingException(MSG_NEEDS_FW_UPDATE_SEGWIT)
                if xtype in ['p2wpkh-p2sh', 'p2wsh-p2sh'] and not self.supports_segwit():
       -            raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT)
       +            raise UserFacingException(MSG_NEEDS_FW_UPDATE_SEGWIT)
                splitPath = bip32_path.split('/')
                if splitPath[0] == 'm':
                    splitPath = splitPath[1:]
       t@@ -154,7 +154,7 @@ class Ledger_Client():
        
                    if not checkFirmware(firmwareInfo):
                        self.dongleObject.dongle.close()
       -                raise Exception(MSG_NEEDS_FW_UPDATE_GENERIC)
       +                raise UserFacingException(MSG_NEEDS_FW_UPDATE_GENERIC)
                    try:
                        self.dongleObject.getOperationMode()
                    except BTChipException as e:
       t@@ -172,18 +172,18 @@ class Ledger_Client():
                            msg = "Enter your Ledger PIN - WARNING : LAST ATTEMPT. If the PIN is not correct, the dongle will be wiped."
                        confirmed, p, pin = self.password_dialog(msg)
                        if not confirmed:
       -                    raise Exception('Aborted by user - please unplug the dongle and plug it again before retrying')
       +                    raise UserFacingException('Aborted by user - please unplug the dongle and plug it again before retrying')
                        pin = pin.encode()
                        self.dongleObject.verifyPin(pin)
                except BTChipException as e:
                    if (e.sw == 0x6faa):
       -                raise Exception("Dongle is temporarily locked - please unplug it and replug it again")
       +                raise UserFacingException("Dongle is temporarily locked - please unplug it and replug it again")
                    if ((e.sw & 0xFFF0) == 0x63c0):
       -                raise Exception("Invalid PIN - please unplug the dongle and plug it again before retrying")
       +                raise UserFacingException("Invalid PIN - please unplug the dongle and plug it again before retrying")
                    if e.sw == 0x6f00 and e.message == 'Invalid channel':
                        # based on docs 0x6f00 might be a more general error, hence we also compare message to be sure
       -                raise Exception("Invalid channel.\n"
       -                                "Please make sure that 'Browser support' is disabled on your device.")
       +                raise UserFacingException("Invalid channel.\n"
       +                                          "Please make sure that 'Browser support' is disabled on your device.")
                    raise e
        
            def checkDevice(self):
       t@@ -192,7 +192,7 @@ class Ledger_Client():
                        self.perform_hw1_preflight()
                    except BTChipException as e:
                        if (e.sw == 0x6d00 or e.sw == 0x6700):
       -                    raise Exception(_("Device not in Bitcoin mode")) from e
       +                    raise UserFacingException(_("Device not in Bitcoin mode")) from e
                        raise e
                    self.preflightDone = True
        
       t@@ -238,7 +238,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
                    self.signing = False
                if clear_client:
                    self.client = None
       -        raise Exception(message)
       +        raise UserFacingException(message)
        
            def set_and_unset_signing(func):
                """Function decorator to set and unset self.signing."""
       t@@ -258,7 +258,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
                return address_path[2:]
        
            def decrypt_message(self, pubkey, message, password):
       -        raise RuntimeError(_('Encryption and decryption are currently not supported for {}').format(self.device))
       +        raise UserFacingException(_('Encryption and decryption are currently not supported for {}').format(self.device))
        
            @test_pin_unlocked
            @set_and_unset_signing
       t@@ -357,7 +357,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
                    redeemScript = Transaction.get_preimage_script(txin)
                    txin_prev_tx = txin.get('prev_tx')
                    if txin_prev_tx is None and not Transaction.is_segwit_input(txin):
       -                raise Exception(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
       +                raise UserFacingException(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
                    txin_prev_tx_raw = txin_prev_tx.raw if txin_prev_tx else None
                    inputs.append([txin_prev_tx_raw,
                                   txin['prevout_n'],
       t@@ -586,8 +586,8 @@ class LedgerPlugin(HW_PluginBase):
                device_id = device_info.device.id_
                client = devmgr.client_by_id(device_id)
                if client is None:
       -            raise Exception(_('Failed to create a client for this device.') + '\n' +
       -                            _('Make sure it is in the correct state.'))
       +            raise UserFacingException(_('Failed to create a client for this device.') + '\n' +
       +                                      _('Make sure it is in the correct state.'))
                client.handler = self.create_handler(wizard)
                client.get_xpub("m/44'/0'", 'standard') # TODO replace by direct derivation once Nano S > 1.1
        
   DIR diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py
       t@@ -2,7 +2,7 @@ from binascii import hexlify, unhexlify
        import traceback
        import sys
        
       -from electrum.util import bfh, bh2u, versiontuple, UserCancelled
       +from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException
        from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
        from electrum.bip32 import deserialize_xpub
        from electrum import constants
       t@@ -31,7 +31,7 @@ class SafeTKeyStore(Hardware_KeyStore):
                return self.plugin.get_client(self, force_pair)
        
            def decrypt_message(self, sequence, message, password):
       -        raise RuntimeError(_('Encryption and decryption are not implemented by {}').format(self.device))
       +        raise UserFacingException(_('Encryption and decryption are not implemented by {}').format(self.device))
        
            def sign_message(self, sequence, message, password):
                client = self.get_client()
       t@@ -51,7 +51,7 @@ class SafeTKeyStore(Hardware_KeyStore):
                    pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
                    tx_hash = txin['prevout_hash']
                    if txin.get('prev_tx') is None and not Transaction.is_segwit_input(txin):
       -                raise Exception(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
       +                raise UserFacingException(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
                    prev_tx[tx_hash] = txin['prev_tx']
                    for x_pubkey in x_pubkeys:
                        if not is_xpubkey(x_pubkey):
       t@@ -137,7 +137,7 @@ class SafeTPlugin(HW_PluginBase):
                    if handler:
                        handler.show_error(msg)
                    else:
       -                raise Exception(msg)
       +                raise UserFacingException(msg)
                    return None
        
                return client
       t@@ -253,8 +253,8 @@ class SafeTPlugin(HW_PluginBase):
                device_id = device_info.device.id_
                client = devmgr.client_by_id(device_id)
                if client is None:
       -            raise Exception(_('Failed to create a client for this device.') + '\n' +
       -                            _('Make sure it is in the correct state.'))
       +            raise UserFacingException(_('Failed to create a client for this device.') + '\n' +
       +                                      _('Make sure it is in the correct state.'))
                # fixme: we should use: client.handler = wizard
                client.handler = self.create_handler(wizard)
                if not device_info.initialized:
   DIR diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py
       t@@ -2,7 +2,7 @@ from binascii import hexlify, unhexlify
        import traceback
        import sys
        
       -from electrum.util import bfh, bh2u, versiontuple, UserCancelled
       +from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException
        from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
        from electrum.bip32 import deserialize_xpub
        from electrum import constants
       t@@ -32,7 +32,7 @@ class TrezorKeyStore(Hardware_KeyStore):
                return self.plugin.get_client(self, force_pair)
        
            def decrypt_message(self, sequence, message, password):
       -        raise RuntimeError(_('Encryption and decryption are not implemented by {}').format(self.device))
       +        raise UserFacingException(_('Encryption and decryption are not implemented by {}').format(self.device))
        
            def sign_message(self, sequence, message, password):
                client = self.get_client()
       t@@ -52,7 +52,7 @@ class TrezorKeyStore(Hardware_KeyStore):
                    pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
                    tx_hash = txin['prevout_hash']
                    if txin.get('prev_tx') is None and not Transaction.is_segwit_input(txin):
       -                raise Exception(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
       +                raise UserFacingException(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
                    prev_tx[tx_hash] = txin['prev_tx']
                    for x_pubkey in x_pubkeys:
                        if not is_xpubkey(x_pubkey):
       t@@ -139,7 +139,7 @@ class TrezorPlugin(HW_PluginBase):
                    if handler:
                        handler.show_error(msg)
                    else:
       -                raise Exception(msg)
       +                raise UserFacingException(msg)
                    return None
        
                return client
       t@@ -265,8 +265,8 @@ class TrezorPlugin(HW_PluginBase):
                device_id = device_info.device.id_
                client = devmgr.client_by_id(device_id)
                if client is None:
       -            raise Exception(_('Failed to create a client for this device.') + '\n' +
       -                            _('Make sure it is in the correct state.'))
       +            raise UserFacingException(_('Failed to create a client for this device.') + '\n' +
       +                                      _('Make sure it is in the correct state.'))
                # fixme: we should use: client.handler = wizard
                client.handler = self.create_handler(wizard)
                if not device_info.initialized:
   DIR diff --git a/electrum/util.py b/electrum/util.py
       t@@ -119,6 +119,10 @@ class WalletFileException(Exception): pass
        class BitcoinException(Exception): pass
        
        
       +class UserFacingException(Exception):
       +    """Exception that contains information intended to be shown to the user."""
       +
       +
        # Throw this exception to unwind the stack like when an error occurs.
        # However unlike other exceptions the user won't be informed.
        class UserCancelled(Exception):