diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py
index 620910db9..2cd23476e 100644
--- a/electrum/gui/qt/seed_dialog.py
+++ b/electrum/gui/qt/seed_dialog.py
@@ -69,66 +69,10 @@ def seed_warning_msg(seed):
]).format(len(seed.split()))
-class SeedLayout(QVBoxLayout):
+class SeedWidget(QWidget):
updated = pyqtSignal()
-
- def seed_options(self):
- dialog = QDialog()
- dialog.setWindowTitle(_("Seed Options"))
- vbox = QVBoxLayout(dialog)
-
- seed_types = [
- (value, title) for value, title in (
- ('electrum', _('Electrum')),
- ('bip39', _('BIP39 seed')),
- ('slip39', _('SLIP39 seed')),
- )
- if value in self.options or value == 'electrum'
- ]
-
- if 'ext' in self.options:
- cb_ext = QCheckBox(_('Extend this seed with custom words'))
- cb_ext.setChecked(self.is_ext)
- vbox.addWidget(cb_ext)
-
- if len(seed_types) >= 2:
- def on_selected(idx):
- self.seed_type = seed_type_choice.selected_key
- self.is_seed = (lambda x: bool(x)) if self.seed_type != 'electrum' else self.saved_is_seed
- self.slip39_current_mnemonic_invalid = None
- self.seed_status.setText('')
- self.on_edit()
- if self.seed_type == 'bip39':
- msg = ' '.join([
- '' + _('Warning') + ': ',
- _('BIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
- _('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'),
- _('BIP39 seeds do not include a version number, which compromises compatibility with future software.'),
- _('We do not guarantee that BIP39 imports will always be supported in Electrum.'),
- ])
- elif self.seed_type == 'slip39':
- msg = ' '.join([
- '' + _('Warning') + ': ',
- _('SLIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
- _('However, we do not generate SLIP39 seeds.'),
- ])
- else:
- msg = ''
- self.update_share_buttons()
- self.initialize_completer()
- self.seed_warning.setText(msg)
-
- seed_type_choice = ChoiceWidget(message=_('Seed type'), choices=seed_types, selected=self.seed_type)
- seed_type_choice.itemSelected.connect(on_selected)
- vbox.addWidget(seed_type_choice)
-
- vbox.addLayout(Buttons(OkButton(dialog)))
- if not dialog.exec():
- return None
- self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False
- self.seed_type = seed_type_choice.selected_key if len(seed_types) >= 2 else 'electrum'
- self.updated.emit()
+ validChanged = pyqtSignal([bool], arguments=['valid'])
def __init__(
self,
@@ -137,20 +81,38 @@ class SeedLayout(QVBoxLayout):
icon=True,
msg=None,
options=None,
- is_seed=None,
+ is_seed=None, # only used for electrum seeds
passphrase=None,
parent=None,
for_seed_words=True,
*,
config: 'SimpleConfig',
):
- QVBoxLayout.__init__(self)
- self.parent = parent
+ QWidget.__init__(self, parent)
+ vbox = QVBoxLayout()
+ self.setLayout(vbox)
+
self.options = options
self.config = config
- self.seed_type = 'electrum'
+
+ if options:
+ self.seed_types = [
+ (value, title) for value, title in (
+ ('electrum', _('Electrum')),
+ ('bip39', _('BIP39 seed')),
+ ('slip39', _('SLIP39 seed')),
+ )
+ if value in self.options
+ ]
+ assert len(self.seed_types)
+ self.seed_type = self.seed_types[0][0]
+ else:
+ self.seed_type = 'electrum'
+
+ self.is_seed = is_seed
+
if title:
- self.addWidget(WWLabel(title))
+ vbox.addWidget(WWLabel(title))
if seed: # "read only", we already have the text
if for_seed_words:
self.seed_e = ButtonsTextEdit()
@@ -162,8 +124,6 @@ class SeedLayout(QVBoxLayout):
assert for_seed_words
self.seed_e = CompletionTextEdit()
self.seed_e.setTabChangesFocus(False) # so that tab auto-completes
- self.is_seed = is_seed
- self.saved_is_seed = self.is_seed
self.seed_e.textChanged.connect(self.on_edit)
self.initialize_completer()
@@ -176,7 +136,7 @@ class SeedLayout(QVBoxLayout):
logo.setMaximumWidth(60)
hbox.addWidget(logo)
hbox.addWidget(self.seed_e)
- self.addLayout(hbox)
+ vbox.addLayout(hbox)
hbox = QHBoxLayout()
hbox.addStretch(1)
self.seed_type_label = QLabel('')
@@ -187,7 +147,7 @@ class SeedLayout(QVBoxLayout):
if options:
opt_button = EnterButton(_('Options'), self.seed_options)
hbox.addWidget(opt_button)
- self.addLayout(hbox)
+ vbox.addLayout(hbox)
if passphrase:
hbox = QHBoxLayout()
passphrase_e = QLineEdit()
@@ -195,7 +155,7 @@ class SeedLayout(QVBoxLayout):
passphrase_e.setReadOnly(True)
hbox.addWidget(QLabel(_("Your seed extension is") + ':'))
hbox.addWidget(passphrase_e)
- self.addLayout(hbox)
+ vbox.addLayout(hbox)
# slip39 shares
self.slip39_mnemonic_index = 0
@@ -211,15 +171,75 @@ class SeedLayout(QVBoxLayout):
self.next_share_btn.clicked.connect(self.on_next_share)
hbox.addWidget(self.next_share_btn)
self.update_share_buttons()
- self.addLayout(hbox)
+ vbox.addLayout(hbox)
- self.addStretch(1)
+ vbox.addStretch(1)
self.seed_status = WWLabel('')
- self.addWidget(self.seed_status)
+ vbox.addWidget(self.seed_status)
self.seed_warning = WWLabel('')
if msg:
self.seed_warning.setText(seed_warning_msg(seed))
- self.addWidget(self.seed_warning)
+ else:
+ self.update_seed_warning()
+
+ vbox.addWidget(self.seed_warning)
+
+ def seed_options(self):
+ dialog = QDialog()
+ dialog.setWindowTitle(_("Seed Options"))
+ vbox = QVBoxLayout(dialog)
+
+ if 'ext' in self.options:
+ cb_ext = QCheckBox(_('Extend this seed with custom words'))
+ cb_ext.setChecked(self.is_ext)
+ vbox.addWidget(cb_ext)
+
+ def on_selected(idx):
+ self.seed_type = seed_type_choice.selected_key
+ self.slip39_current_mnemonic_invalid = None
+ self.seed_status.setText('')
+ self.update_seed_warning()
+ self.on_edit()
+ self.update_share_buttons()
+ self.initialize_completer()
+
+ if len(self.seed_types) > 1:
+ seed_type_choice = ChoiceWidget(message=_('Seed type'), choices=self.seed_types, selected=self.seed_type)
+ seed_type_choice.itemSelected.connect(on_selected)
+ vbox.addWidget(seed_type_choice)
+
+ vbox.addLayout(Buttons(OkButton(dialog)))
+
+ if not dialog.exec():
+ return None
+
+ if 'ext' in self.options:
+ self.is_ext = cb_ext.isChecked()
+ if len(self.seed_types) > 1:
+ self.seed_type = seed_type_choice.selected_key
+
+ self.update_seed_warning()
+ self.updated.emit()
+
+ def update_seed_warning(self):
+ if self.seed_type == 'bip39':
+ msg = ' '.join([
+ '' + _('Warning') + ': ',
+ _('BIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
+ _('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'),
+ _('BIP39 seeds do not include a version number, which compromises compatibility with future software.'),
+ _('We do not guarantee that BIP39 imports will always be supported in Electrum.'),
+ ])
+ elif self.seed_type == 'slip39':
+ msg = ' '.join([
+ '' + _('Warning') + ': ',
+ _('SLIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
+ _('However, we do not generate SLIP39 seeds.'),
+ ])
+ else:
+ msg = ''
+
+ self.seed_warning.setText(msg)
def initialize_completer(self):
if self.seed_type != 'slip39':
@@ -261,12 +281,12 @@ class SeedLayout(QVBoxLayout):
def on_edit(self):
s = ' '.join(self.get_seed_words())
- b = self.is_seed(s)
if self.seed_type == 'bip39':
from electrum.keystore import bip39_is_checksum_valid
is_checksum, is_wordlist = bip39_is_checksum_valid(s)
label = ''
- if bool(s):
+ valid = bool(s)
+ if valid:
label = ('' if is_checksum else _('BIP39 checksum failed')) if is_wordlist else _('Unknown BIP39 wordlist')
elif self.seed_type == 'slip39':
self.slip39_mnemonics[self.slip39_mnemonic_index] = s
@@ -287,15 +307,13 @@ class SeedLayout(QVBoxLayout):
self.seed_status.setText(seed_status)
self.slip39_current_mnemonic_invalid = current_mnemonic_invalid
- b = self.slip39_seed is not None
+ valid = self.slip39_seed is not None
self.update_share_buttons()
else:
+ valid = self.is_seed(s)
t = calc_seed_type(s)
label = _('Seed Type') + ': ' + t if t else ''
- if t and not b: # electrum seed, but does not conform to dialog rules
- # FIXME we should just accept any electrum seed and "redirect" the wizard automatically.
- # i.e. if user selected wallet_type=="standard" but entered a 2fa seed, accept and redirect
- # if user selected wallet_type=="2fa" but entered a std electrum seed, accept and redirect
+ if t and not valid: # electrum seed, but does not conform to dialog rules
wiztype_fullname = _('Wallet with two-factor authentication') if is_any_2fa_seed_type(t) else _("Standard wallet")
msg = ' '.join([
'' + _('Warning') + ': ',
@@ -307,7 +325,7 @@ class SeedLayout(QVBoxLayout):
self.seed_warning.setText("")
self.seed_type_label.setText(label)
- self.parent.next_button.setEnabled(b)
+ self.validChanged.emit(valid)
# disable suggestions if user already typed an unknown word
for word in self.get_seed_words()[:-1]:
@@ -354,7 +372,10 @@ class SeedLayout(QVBoxLayout):
self.slip39_current_mnemonic_invalid = None
-class KeysLayout(QVBoxLayout):
+class KeysWidget(QWidget):
+
+ validChanged = pyqtSignal([bool], arguments=['valid'])
+
def __init__(
self,
parent=None,
@@ -364,29 +385,28 @@ class KeysLayout(QVBoxLayout):
*,
config: 'SimpleConfig',
):
- QVBoxLayout.__init__(self)
- self.parent = parent
+ QWidget.__init__(self, parent)
+ vbox = QVBoxLayout()
+ self.setLayout(vbox)
+
self.is_valid = is_valid
self.text_e = ScanQRTextEdit(allow_multi=allow_multi, config=config)
self.text_e.textChanged.connect(self.on_edit)
if isinstance(header_layout, str):
- self.addWidget(WWLabel(header_layout))
+ vbox.addWidget(WWLabel(header_layout))
else:
- self.addLayout(header_layout)
- self.addWidget(self.text_e)
+ vbox.addLayout(header_layout)
+ vbox.addWidget(self.text_e)
def get_text(self):
return self.text_e.text()
def on_edit(self):
- valid = False
try:
valid = self.is_valid(self.get_text())
except Exception as e:
- self.parent.next_button.setToolTip(f'{_("Error")}: {str(e)}')
- else:
- self.parent.next_button.setToolTip('')
- self.parent.next_button.setEnabled(valid)
+ valid = False
+ self.validChanged.emit(valid)
class SeedDialog(WindowModalDialog):
@@ -395,13 +415,7 @@ class SeedDialog(WindowModalDialog):
WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
self.setMinimumWidth(400)
vbox = QVBoxLayout(self)
- title = _("Your wallet generation seed is:")
- slayout = SeedLayout(
- title=title,
- seed=seed,
- msg=True,
- passphrase=passphrase,
- config=config,
- )
- vbox.addLayout(slayout)
+ title = _("Your wallet generation seed is:")
+ seed_widget = SeedWidget(title=title, seed=seed, msg=True, passphrase=passphrase, config=config)
+ vbox.addWidget(seed_widget)
vbox.addLayout(Buttons(CloseButton(self)))
diff --git a/electrum/gui/qt/wizard/wallet.py b/electrum/gui/qt/wizard/wallet.py
index 4a2596e62..4437f5c24 100644
--- a/electrum/gui/qt/wizard/wallet.py
+++ b/electrum/gui/qt/wizard/wallet.py
@@ -27,10 +27,9 @@ from electrum.wizard import NewWalletWizard
from electrum.gui.qt.bip39_recovery_dialog import Bip39RecoveryDialog
from electrum.gui.qt.password_dialog import PasswordLayout, PW_NEW, MSG_ENTER_PASSWORD, PasswordLayoutForHW
-from electrum.gui.qt.seed_dialog import SeedLayout, MSG_PASSPHRASE_WARN_ISSUE4566, KeysLayout
+from electrum.gui.qt.seed_dialog import SeedWidget, MSG_PASSPHRASE_WARN_ISSUE4566, KeysWidget
from electrum.gui.qt.util import (PasswordLineEdit, char_width_in_lineedit, WWLabel, InfoButton, font_height,
- ChoiceWidget, MessageBoxMixin, WindowModalDialog, CancelButton,
- Buttons, OkButton, icon_path)
+ ChoiceWidget, MessageBoxMixin, icon_path)
if TYPE_CHECKING:
from electrum.simple_config import SimpleConfig
@@ -437,7 +436,7 @@ class WCCreateSeed(WalletWizardComponent):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Wallet Seed'))
self._busy = True
self.seed_type = 'standard' if self.wizard.config.WIZARD_DONT_CREATE_SEGWIT else 'segwit'
- self.slayout = None
+ self.seed_widget = None
self.seed = None
def on_ready(self):
@@ -446,10 +445,10 @@ class WCCreateSeed(WalletWizardComponent):
QTimer.singleShot(1, self.create_seed)
def apply(self):
- if self.slayout:
+ if self.seed_widget:
self.wizard_data['seed'] = self.seed
self.wizard_data['seed_type'] = self.seed_type
- self.wizard_data['seed_extend'] = self.slayout.is_ext
+ self.wizard_data['seed_extend'] = self.seed_widget.is_ext
self.wizard_data['seed_variant'] = 'electrum'
self.wizard_data['seed_extra_words'] = '' # empty default
@@ -457,15 +456,15 @@ class WCCreateSeed(WalletWizardComponent):
self.busy = True
self.seed = mnemonic.Mnemonic('en').make_seed(seed_type=self.seed_type)
- self.slayout = SeedLayout(
+ self.seed_widget = SeedWidget(
title=_('Your wallet generation seed is:'),
seed=self.seed,
- options=['ext'],
+ options=['ext', 'electrum'],
msg=True,
parent=self,
config=self.wizard.config,
)
- self.layout().addLayout(self.slayout)
+ self.layout().addWidget(self.seed_widget)
self.layout().addStretch(1)
self.busy = False
self.valid = True
@@ -482,19 +481,16 @@ class WCConfirmSeed(WalletWizardComponent):
self.layout().addWidget(WWLabel(message))
- # TODO: SeedLayout assumes too much in parent, refactor SeedLayout
- # for now, fake parent.next_button.setEnabled
- class Hack:
- def setEnabled(self2, b):
- self.valid = b
- self.next_button = Hack()
-
- self.slayout = SeedLayout(
+ self.seed_widget = SeedWidget(
is_seed=lambda x: x == self.wizard_data['seed'],
- parent=self,
config=self.wizard.config,
)
- self.layout().addLayout(self.slayout)
+
+ def seed_valid_changed(valid):
+ self.valid = valid
+
+ self.seed_widget.validChanged.connect(seed_valid_changed)
+ self.layout().addWidget(self.seed_widget)
wizard.app.clipboard().clear()
@@ -583,37 +579,39 @@ class WCHaveSeed(WalletWizardComponent, Logger):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Enter Seed'))
Logger.__init__(self)
- self.slayout = None
-
self.layout().addWidget(WWLabel(_('Please enter your seed phrase in order to restore your wallet.')))
- # TODO: SeedLayout assumes too much in parent, refactor SeedLayout
- # for now, fake parent.next_button.setEnabled
- class Hack:
- def setEnabled(self2, b):
- if not b:
- self.valid = b
- else:
- self.validate()
-
- self.next_button = Hack()
-
+ self.seed_widget = None
self.can_passphrase = True
def on_ready(self):
- options = ['ext'] if self.wizard_data['wallet_type'] == '2fa' else ['ext', 'bip39', 'slip39']
- self.slayout = SeedLayout(
+ options = ['ext', 'electrum', 'bip39', 'slip39']
+ if self.wizard_data['wallet_type'] == '2fa':
+ options = ['ext', 'electrum']
+ else:
+ if self.params and 'seed_options' in self.params:
+ options = self.params['seed_options']
+
+ self.seed_widget = SeedWidget(
is_seed=self.is_seed,
options=options,
- parent=self,
config=self.wizard.config,
)
- self.slayout.updated.connect(self.validate)
- self.layout().addLayout(self.slayout)
+ def seed_valid_changed(valid):
+ if not valid:
+ self.valid = valid
+ else:
+ self.validate()
+
+ self.seed_widget.validChanged.connect(seed_valid_changed)
+ self.seed_widget.updated.connect(self.validate)
+
+ self.layout().addWidget(self.seed_widget)
self.layout().addStretch(1)
def is_seed(self, x):
+ # really only used for electrum seeds. bip39 and slip39 are validated in SeedWidget
t = mnemonic.calc_seed_type(x)
if self.wizard_data['wallet_type'] == 'standard':
return mnemonic.is_seed(x) and not mnemonic.is_any_2fa_seed_type(t)
@@ -624,9 +622,9 @@ class WCHaveSeed(WalletWizardComponent, Logger):
return t in ['standard', 'segwit']
def validate(self):
- # precond: only call when SeedLayout deems seed a valid seed
- seed = self.slayout.get_seed()
- seed_variant = self.slayout.seed_type
+ # precond: only call when SeedWidget deems seed a valid seed
+ seed = self.seed_widget.get_seed()
+ seed_variant = self.seed_widget.seed_type
wallet_type = self.wizard_data['wallet_type']
seed_valid, seed_type, validation_message, self.can_passphrase = self.wizard.validate_seed(seed, seed_variant, wallet_type)
@@ -646,13 +644,13 @@ class WCHaveSeed(WalletWizardComponent, Logger):
def apply(self):
cosigner_data = self.wizard.current_cosigner(self.wizard_data)
- cosigner_data['seed'] = self.slayout.get_seed()
- cosigner_data['seed_variant'] = self.slayout.seed_type
- if self.slayout.seed_type == 'electrum':
- cosigner_data['seed_type'] = mnemonic.calc_seed_type(self.slayout.get_seed())
+ cosigner_data['seed'] = self.seed_widget.get_seed()
+ cosigner_data['seed_variant'] = self.seed_widget.seed_type
+ if self.seed_widget.seed_type == 'electrum':
+ cosigner_data['seed_type'] = mnemonic.calc_seed_type(self.seed_widget.get_seed())
else:
- cosigner_data['seed_type'] = self.slayout.seed_type
- cosigner_data['seed_extend'] = self.slayout.is_ext if self.can_passphrase else False
+ cosigner_data['seed_type'] = self.seed_widget.seed_type
+ cosigner_data['seed_extend'] = self.seed_widget.is_ext if self.can_passphrase else False
cosigner_data['seed_extra_words'] = '' # empty default
@@ -790,13 +788,13 @@ class WCCosignerKeystore(WalletWizardComponent):
# different from old wizard: master public key for sharing is now shown on this page
self.layout().addSpacing(20)
self.layout().addWidget(WWLabel(_('Below is your master public key. Please share it with your cosigners')))
- slayout = SeedLayout(
+ seed_widget = SeedWidget(
self.wizard_data['multisig_master_pubkey'],
icon=False,
for_seed_words=False,
config=self.wizard.config,
)
- self.layout().addLayout(slayout)
+ self.layout().addWidget(seed_widget)
self.layout().addStretch(1)
def apply(self):
@@ -811,7 +809,7 @@ class WCHaveMasterKey(WalletWizardComponent):
def __init__(self, parent, wizard):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Create keystore from a master key'))
- self.slayout = None
+ self.keys_widget = None
self.message_create = ' '.join([
_("To create a watching-only wallet, please enter your master public key (xpub/ypub/zpub)."),
@@ -827,16 +825,6 @@ class WCHaveMasterKey(WalletWizardComponent):
self.label.setMinimumWidth(400)
self.header_layout.addWidget(self.label)
- # TODO: KeysLayout assumes too much in parent, refactor KeysLayout
- # for now, fake parent.next_button.setEnabled
- class Hack:
- def setEnabled(self2, b):
- self.valid = b
-
- def setToolTip(self2, b):
- pass
- self.next_button = Hack()
-
def on_ready(self):
if self.wizard_data['wallet_type'] == 'standard':
self.label.setText(self.message_create)
@@ -860,12 +848,19 @@ class WCHaveMasterKey(WalletWizardComponent):
return True
else:
raise Exception(f"unexpected wallet type: {self.wizard_data['wallet_type']}")
- self.slayout = KeysLayout(parent=self, header_layout=self.header_layout, is_valid=is_valid,
- allow_multi=False, config=self.wizard.config)
- self.layout().addLayout(self.slayout)
+
+ self.keys_widget = KeysWidget(parent=self, header_layout=self.header_layout, is_valid=is_valid,
+ allow_multi=False, config=self.wizard.config)
+
+ def key_valid_changed(valid):
+ self.valid = valid
+
+ self.keys_widget.validChanged.connect(key_valid_changed)
+
+ self.layout().addWidget(self.keys_widget)
def apply(self):
- text = self.slayout.get_text()
+ text = self.keys_widget.get_text()
cosigner_data = self.wizard.current_cosigner(self.wizard_data)
cosigner_data['master_key'] = text
@@ -942,25 +937,20 @@ class WCImport(WalletWizardComponent):
header_layout.addWidget(label)
header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignmentFlag.AlignRight)
- # TODO: KeysLayout assumes too much in parent, refactor KeysLayout
- # for now, fake parent.next_button.setEnabled
- class Hack:
- def setEnabled(self2, b):
- self.valid = b
-
- def setToolTip(self2, b):
- pass
- self.next_button = Hack()
-
def is_valid(x) -> bool:
return keystore.is_address_list(x) or keystore.is_private_key_list(x, raise_on_error=True)
- self.slayout = KeysLayout(parent=self, header_layout=header_layout, is_valid=is_valid,
- allow_multi=True, config=self.wizard.config)
- self.layout().addLayout(self.slayout)
+ self.keys_widget = KeysWidget(header_layout=header_layout, is_valid=is_valid,
+ allow_multi=True, config=self.wizard.config)
+
+ def key_valid_changed(valid):
+ self.valid = valid
+
+ self.keys_widget.validChanged.connect(key_valid_changed)
+ self.layout().addWidget(self.keys_widget)
def apply(self):
- text = self.slayout.get_text()
+ text = self.keys_widget.get_text()
if keystore.is_address_list(text):
self.wizard_data['address_list'] = text
elif keystore.is_private_key_list(text):