commit e7d25faf028543760f7ae114c395a4400c507636
parent 97dc130e26b36af84f5fb54b468d867423243c97
Author: ThomasV <thomasv@electrum.org>
Date: Mon, 20 Jun 2016 16:25:11 +0200
Finish wizard unification
Diffstat:
11 files changed, 450 insertions(+), 648 deletions(-)
diff --git a/gui/kivy/__init__.py b/gui/kivy/__init__.py
@@ -45,6 +45,7 @@ class ElectrumGui:
def __init__(self, config, daemon, plugins):
Logger.debug('ElectrumGUI: initialising')
+ self.daemon = daemon
self.network = daemon.network
self.config = config
self.plugins = plugins
diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py
@@ -199,8 +199,8 @@ class ElectrumWindow(App):
self.plugins = kwargs.get('plugins', [])
self.gui_object = kwargs.get('gui_object', None)
+ self.daemon = self.gui_object.daemon
- #self.config = self.gui_object.config
self.contacts = Contacts(self.electrum_config)
self.invoices = InvoiceStore(self.electrum_config)
@@ -408,36 +408,32 @@ class ElectrumWindow(App):
else:
return ''
- def load_wallet_by_name(self, wallet_path):
- if not wallet_path:
- return
- config = self.electrum_config
- try:
- storage = WalletStorage(wallet_path)
- except IOError:
- self.show_error("Cannot read wallet file")
+ def on_wizard_complete(self, instance, wallet):
+ if wallet:
+ self.daemon.add_wallet(wallet)
+ self.load_wallet(wallet)
+ self.on_resume()
+
+ def load_wallet_by_name(self, path):
+ if not path:
return
- if storage.file_exists:
- wallet = Wallet(storage)
- action = wallet.get_action()
+ wallet = self.daemon.load_wallet(path)
+ if wallet:
+ self.load_wallet(wallet)
+ self.on_resume()
else:
- action = 'new'
- if action is not None:
- # start installation wizard
Logger.debug('Electrum: Wallet not found. Launching install wizard')
- wizard = Factory.InstallWizard(config, self.network, storage)
- wizard.bind(on_wizard_complete=lambda instance, wallet: self.load_wallet(wallet))
+ wizard = Factory.InstallWizard(self.electrum_config, self.network, path)
+ wizard.bind(on_wizard_complete=self.on_wizard_complete)
+ action = wizard.get_action()
wizard.run(action)
- else:
- self.load_wallet(wallet)
- self.on_resume()
def on_stop(self):
self.stop_wallet()
def stop_wallet(self):
if self.wallet:
- self.wallet.stop_threads()
+ self.daemon.stop_wallet(self.wallet.storage.path)
self.wallet = None
def on_key_down(self, instance, key, keycode, codepoint, modifiers):
@@ -539,9 +535,10 @@ class ElectrumWindow(App):
@profiler
def load_wallet(self, wallet):
+ print "load wallet", wallet.storage.path
+
self.stop_wallet()
self.wallet = wallet
- self.wallet.start_threads(self.network)
self.current_account = self.wallet.storage.get('current_account', None)
self.update_wallet()
# Once GUI has been initialized check if we want to announce something
diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py
@@ -24,7 +24,9 @@ from password_dialog import PasswordDialog
from electrum.base_wizard import BaseWizard
-
+is_test = True
+test_seed = "time taxi field recycle tiny license olive virus report rare steel portion achieve"
+test_xpub = "xpub661MyMwAqRbcEbvVtRRSjqxVnaWVUMewVzMiURAKyYratih4TtBpMypzzefmv8zUNebmNVzB3PojdC5sV2P9bDgMoo9B3SARw1MXUUfU1GL"
Builder.load_string('''
#:import Window kivy.core.window.Window
@@ -152,7 +154,7 @@ Builder.load_string('''
<WizardChoiceDialog>
- msg : ''
+ message : ''
Widget:
size_hint: 1, 1
Label:
@@ -160,7 +162,7 @@ Builder.load_string('''
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
- text: root.msg
+ text: root.message
Widget
size_hint: 1, 1
GridLayout:
@@ -408,11 +410,12 @@ class WizardDialog(EventsDialog):
'''
crcontent = ObjectProperty(None)
- def __init__(self, **kwargs):
+ def __init__(self, wizard, **kwargs):
super(WizardDialog, self).__init__(**kwargs)
- #self.action = kwargs.get('action')
+ self.wizard = wizard
+ self.ids.back.disabled = not wizard.can_go_back()
+ self.app = App.get_running_app()
self.run_next = kwargs['run_next']
- self.run_prev = kwargs['run_prev']
_trigger_size_dialog = Clock.create_trigger(self._size_dialog)
Window.bind(size=_trigger_size_dialog,
rotation=_trigger_size_dialog)
@@ -443,7 +446,7 @@ class WizardDialog(EventsDialog):
app.stop()
def get_params(self, button):
- return ()
+ return (None,)
def on_release(self, button):
self._on_release = True
@@ -452,7 +455,7 @@ class WizardDialog(EventsDialog):
self.parent.dispatch('on_wizard_complete', None)
return
if button is self.ids.back:
- self.run_prev()
+ self.wizard.go_back()
return
params = self.get_params(button)
self.run_next(*params)
@@ -467,13 +470,13 @@ class WizardMultisigDialog(WizardDialog):
class WizardChoiceDialog(WizardDialog):
- def __init__(self, **kwargs):
- super(WizardChoiceDialog, self).__init__(**kwargs)
- self.msg = kwargs.get('msg', '')
+ def __init__(self, wizard, **kwargs):
+ super(WizardChoiceDialog, self).__init__(wizard, **kwargs)
+ self.message = kwargs.get('message', '')
choices = kwargs.get('choices', [])
layout = self.ids.choices
layout.bind(minimum_height=layout.setter('height'))
- for text, action in choices:
+ for action, text in choices:
l = WizardButton(text=text)
l.action = action
l.height = '48dp'
@@ -508,17 +511,18 @@ class WordButton(Button):
class WizardButton(Button):
pass
+
class RestoreSeedDialog(WizardDialog):
message = StringProperty('')
- def __init__(self, **kwargs):
- super(RestoreSeedDialog, self).__init__(**kwargs)
- self._test = kwargs['test']
+ def __init__(self, wizard, **kwargs):
+ super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
+ self._test = kwargs['is_valid']
from electrum.mnemonic import Mnemonic
from electrum.old_mnemonic import words as old_wordlist
self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
- self.ids.text_input_seed.text = ''
+ self.ids.text_input_seed.text = test_seed if is_test else ''
def get_suggestions(self, prefix):
for w in self.words:
@@ -616,9 +620,8 @@ class RestoreSeedDialog(WizardDialog):
class ShowXpubDialog(WizardDialog):
- def __init__(self, **kwargs):
- WizardDialog.__init__(self, **kwargs)
- self.app = App.get_running_app()
+ def __init__(self, wizard, **kwargs):
+ WizardDialog.__init__(self, wizard, **kwargs)
self.xpub = kwargs['xpub']
self.ids.next.disabled = False
@@ -636,15 +639,14 @@ class ShowXpubDialog(WizardDialog):
class AddXpubDialog(WizardDialog):
- def __init__(self, **kwargs):
- WizardDialog.__init__(self, **kwargs)
- self.app = App.get_running_app()
- self._test = kwargs['test']
+ def __init__(self, wizard, **kwargs):
+ WizardDialog.__init__(self, wizard, **kwargs)
+ self.is_valid = kwargs['is_valid']
self.title = kwargs['title']
self.message = kwargs['message']
def check_text(self, dt):
- self.ids.next.disabled = not bool(self._test(self.get_text()))
+ self.ids.next.disabled = not bool(self.is_valid(self.get_text()))
def get_text(self):
ti = self.ids.text_input
@@ -659,7 +661,7 @@ class AddXpubDialog(WizardDialog):
self.app.scan_qr(on_complete)
def do_paste(self):
- self.ids.text_input.text = unicode(self.app._clipboard.paste())
+ self.ids.text_input.text = test_xpub if is_test else unicode(self.app._clipboard.paste())
def do_clear(self):
self.ids.text_input.text = ''
@@ -681,7 +683,7 @@ class InstallWizard(BaseWizard, Widget):
"""overriden by main_window"""
pass
- def waiting_dialog(self, task, msg, on_complete=None):
+ def waiting_dialog(self, task, msg):
'''Perform a blocking task in the background by running the passed
method in a thread.
'''
@@ -693,8 +695,6 @@ class InstallWizard(BaseWizard, Widget):
Clock.schedule_once(lambda dt: app.show_error(str(err)))
# on completion hide message
Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1)
- if on_complete:
- on_complete()
app.show_info_bubble(
text=msg, icon='atlas://gui/kivy/theming/light/important',
@@ -702,17 +702,42 @@ class InstallWizard(BaseWizard, Widget):
t = threading.Thread(target = target)
t.start()
- def choice_dialog(self, **kwargs): WizardChoiceDialog(**kwargs).open()
- def multisig_dialog(self, **kwargs): WizardMultisigDialog(**kwargs).open()
- def show_seed_dialog(self, **kwargs): ShowSeedDialog(**kwargs).open()
- def restore_seed_dialog(self, **kwargs): RestoreSeedDialog(**kwargs).open()
- def add_xpub_dialog(self, **kwargs): AddXpubDialog(**kwargs).open()
- def show_xpub_dialog(self, **kwargs): ShowXpubDialog(**kwargs).open()
+ def terminate(self, **kwargs):
+ self.wallet.start_threads(self.network)
+ self.dispatch('on_wizard_complete', self.wallet)
+
+ def choice_dialog(self, **kwargs): WizardChoiceDialog(self, **kwargs).open()
+ def multisig_dialog(self, **kwargs): WizardMultisigDialog(self, **kwargs).open()
+ def show_seed_dialog(self, **kwargs): ShowSeedDialog(self, **kwargs).open()
+ def enter_seed_dialog(self, **kwargs): RestoreSeedDialog(self, **kwargs).open()
+ def add_xpub_dialog(self, **kwargs): AddXpubDialog(self, **kwargs).open()
+ def show_xpub_dialog(self, **kwargs): ShowXpubDialog(self, **kwargs).open()
+
+ def show_error(self, msg):
+ app.show_error(msg, duration=0.5)
def password_dialog(self, message, callback):
popup = PasswordDialog()
popup.init(message, callback)
popup.open()
- def show_error(self, msg):
- app.show_error(msg, duration=0.5)
+ def request_password(self, run_next):
+ def callback(pin):
+ if pin:
+ self.run('confirm_password', (pin, run_next))
+ else:
+ run_next(None)
+ self.password_dialog('Choose a PIN code', callback)
+
+ def confirm_password(self, pin, run_next):
+ def callback(conf):
+ if conf == pin:
+ run_next(pin)
+ else:
+ self.show_error(_('PIN mismatch'))
+ self.run('request_password', (run_next,))
+ self.password_dialog('Confirm your PIN code', callback)
+
+ def action_dialog(self, action, run_next):
+ f = getattr(self, action)
+ f()
diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py
@@ -149,9 +149,6 @@ class ElectrumGui:
run_hook('on_new_window', w)
return w
- def get_wizard(self):
- return InstallWizard(self.config, self.app, self.plugins)
-
def start_new_window(self, path, uri):
'''Raises the window for the wallet if it is open. Otherwise
opens the wallet and creates a new window for it.'''
@@ -160,14 +157,18 @@ class ElectrumGui:
w.bring_to_top()
break
else:
- wallet = self.daemon.load_wallet(path, self.get_wizard)
+ wallet = self.daemon.load_wallet(path)
if not wallet:
- return
+ wizard = InstallWizard(self.config, self.app, self.plugins, self.daemon.network, path)
+ wallet = wizard.run_and_get_wallet()
+ if not wallet:
+ return
+ if wallet.get_action():
+ return
+ self.daemon.add_wallet(wallet)
w = self.create_window_for_wallet(wallet)
-
if uri:
w.pay_to_URI(uri)
-
return w
def close_window(self, window):
diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
@@ -5,6 +5,10 @@ from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
import electrum
+from electrum.wallet import Wallet
+from electrum.mnemonic import prepare_seed
+from electrum.util import UserCancelled
+from electrum.base_wizard import BaseWizard
from electrum.i18n import _
from seed_dialog import SeedDisplayLayout, SeedWarningLayout, SeedInputLayout
@@ -12,14 +16,23 @@ from network_dialog import NetworkChoiceLayout
from util import *
from password_dialog import PasswordLayout, PW_NEW, PW_PASSPHRASE
-from electrum.wallet import Wallet
-from electrum.mnemonic import prepare_seed
-from electrum.util import UserCancelled
-from electrum.wizard import (WizardBase,
- MSG_ENTER_PASSWORD, MSG_RESTORE_PASSPHRASE,
- MSG_COSIGNER, MSG_ENTER_SEED_OR_MPK,
- MSG_SHOW_MPK, MSG_VERIFY_SEED,
- MSG_GENERATING_WAIT)
+
+class GoBack(Exception):
+ pass
+
+MSG_GENERATING_WAIT = _("Electrum is generating your addresses, please wait...")
+MSG_ENTER_ANYTHING = _("Please enter a seed phrase, a master key, a list of "
+ "Bitcoin addresses, or a list of private keys")
+MSG_ENTER_SEED_OR_MPK = _("Please enter a seed phrase or a master key (xpub or xprv):")
+MSG_VERIFY_SEED = _("Your seed is important!\nTo make sure that you have properly saved your seed, please retype it here.")
+MSG_COSIGNER = _("Please enter the master public key of cosigner #%d:")
+MSG_SHOW_MPK = _("Here is your master public key:")
+MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys. "
+ "Enter nothing if you want to disable encryption.")
+MSG_RESTORE_PASSPHRASE = \
+ _("Please enter the passphrase you used when creating your %s wallet. "
+ "Note this is NOT a password. Enter nothing if you did not use "
+ "one or are unsure.")
def clean_text(seed_e):
text = unicode(seed_e.toPlainText()).strip()
@@ -63,14 +76,42 @@ class CosignWidget(QWidget):
qp.end()
+
+def wizard_dialog(func):
+ def func_wrapper(*args, **kwargs):
+ run_next = kwargs['run_next']
+ wizard = args[0]
+ wizard.back_button.setText(_('Back') if wizard.can_go_back() else _('Cancel'))
+ try:
+ out = func(*args, **kwargs)
+ except GoBack:
+ print "go back"
+ wizard.go_back()
+ return
+ except UserCancelled:
+ print "usercancelled"
+ return
+ #if out is None:
+ # out = ()
+ if type(out) is not tuple:
+ out = (out,)
+ apply(run_next, out)
+ return func_wrapper
+
+
+
# WindowModalDialog must come first as it overrides show_error
-class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
+class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
+
+ def __init__(self, config, app, plugins, network, storage):
- def __init__(self, config, app, plugins):
+ BaseWizard.__init__(self, config, network, storage)
QDialog.__init__(self, None)
+
self.setWindowTitle('Electrum - ' + _('Install Wizard'))
self.app = app
self.config = config
+
# Set for base base class
self.plugins = plugins
self.language_for_seed = config.get('language')
@@ -79,7 +120,7 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
self.connect(self, QtCore.SIGNAL('accept'), self.accept)
self.title = WWLabel()
self.main_widget = QWidget()
- self.cancel_button = QPushButton(_("Cancel"), self)
+ self.back_button = QPushButton(_("Back"), self)
self.next_button = QPushButton(_("Next"), self)
self.next_button.setDefault(True)
self.logo = QLabel()
@@ -87,9 +128,9 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
self.please_wait.setAlignment(Qt.AlignCenter)
self.icon_filename = None
self.loop = QEventLoop()
- self.rejected.connect(lambda: self.loop.exit(False))
- self.cancel_button.clicked.connect(lambda: self.loop.exit(False))
- self.next_button.clicked.connect(lambda: self.loop.exit(True))
+ self.rejected.connect(lambda: self.loop.exit(0))
+ self.back_button.clicked.connect(lambda: self.loop.exit(1))
+ self.next_button.clicked.connect(lambda: self.loop.exit(2))
outer_vbox = QVBoxLayout(self)
inner_vbox = QVBoxLayout()
inner_vbox = QVBoxLayout()
@@ -107,12 +148,35 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
hbox.addLayout(inner_vbox)
hbox.setStretchFactor(inner_vbox, 1)
outer_vbox.addLayout(hbox)
- outer_vbox.addLayout(Buttons(self.cancel_button, self.next_button))
+ outer_vbox.addLayout(Buttons(self.back_button, self.next_button))
self.set_icon(':icons/electrum.png')
self.show()
self.raise_()
self.refresh_gui() # Need for QT on MacOSX. Lame.
+ def run_and_get_wallet(self):
+ # Show network dialog if config does not exist
+ if self.network:
+ if self.config.get('auto_connect') is None:
+ self.choose_server(self.network)
+
+ action = self.get_action()
+ if action != 'new':
+ self.hide()
+ path = self.storage.path
+ msg = _("The file '%s' contains an incompletely created wallet.\n"
+ "Do you want to complete its creation now?") % path
+ if not self.question(msg):
+ if self.question(_("Do you want to delete '%s'?") % path):
+ import os
+ os.remove(path)
+ self.show_warning(_('The file was removed'))
+ return
+ return
+ self.show()
+ self.run(action)
+ return self.wallet
+
def finished(self):
'''Ensure the dialog is closed.'''
self.accept()
@@ -137,15 +201,17 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
if prior_layout:
QWidget().setLayout(prior_layout)
self.main_widget.setLayout(layout)
- self.cancel_button.setEnabled(True)
+ self.back_button.setEnabled(True)
self.next_button.setEnabled(next_enabled)
self.main_widget.setVisible(True)
self.please_wait.setVisible(False)
result = self.loop.exec_()
if not result and raise_on_cancel:
raise UserCancelled
+ if result == 1:
+ raise GoBack
self.title.setVisible(False)
- self.cancel_button.setEnabled(False)
+ self.back_button.setEnabled(False)
self.next_button.setEnabled(False)
self.main_widget.setVisible(False)
self.please_wait.setVisible(True)
@@ -157,58 +223,42 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
self.app.processEvents()
self.app.processEvents()
- def run(self, *args):
- '''Wrap the base wizard implementation with try/except blocks
- to give a sensible error message to the user.'''
- wallet = None
- try:
- wallet = WizardBase.run(self, *args)
- except UserCancelled:
- self.print_error("wallet creation cancelled by user")
- self.accept() # For when called from menu
- except BaseException as e:
- self.on_error(sys.exc_info())
- raise
- return wallet
-
def remove_from_recently_open(self, filename):
self.config.remove_from_recently_open(filename)
- def request_seed(self, title, is_valid=None):
- is_valid = is_valid or Wallet.is_any
- slayout = SeedInputLayout()
+ def text_input(self, title, message, is_valid):
+ slayout = SeedInputLayout(title=message)
def sanitized_seed():
return clean_text(slayout.seed_edit())
def set_enabled():
self.next_button.setEnabled(is_valid(sanitized_seed()))
slayout.seed_edit().textChanged.connect(set_enabled)
self.set_main_layout(slayout.layout(), title, next_enabled=False)
- return sanitized_seed()
+ seed = sanitized_seed()
+ return seed
- def show_seed(self, seed):
- slayout = SeedWarningLayout(seed)
- self.set_main_layout(slayout.layout())
-
- def verify_seed(self, seed, is_valid=None):
- while True:
- r = self.request_seed(MSG_VERIFY_SEED, is_valid)
- if prepare_seed(r) == prepare_seed(seed):
- return
- self.show_error(_('Incorrect seed'))
+ @wizard_dialog
+ def add_xpub_dialog(self, title, message, is_valid, run_next):
+ return self.text_input(title, message, is_valid)
- def show_and_verify_seed(self, seed, is_valid=None):
- """Show the user their seed. Ask them to re-enter it. Return
- True on success."""
- self.show_seed(seed)
+ @wizard_dialog
+ def enter_seed_dialog(self, run_next, title, message, is_valid):
self.app.clipboard().clear()
- self.verify_seed(seed, is_valid)
+ return self.text_input(title, message, is_valid)
+
+ @wizard_dialog
+ def show_seed_dialog(self, run_next, message, seed_text):
+ slayout = SeedWarningLayout(seed_text)
+ self.set_main_layout(slayout.layout())
+ return seed_text
def pw_layout(self, msg, kind):
playout = PasswordLayout(None, msg, kind, self.next_button)
self.set_main_layout(playout.layout())
return playout.new_password()
- def request_passphrase(self, device_text):
+ @wizard_dialog
+ def request_passphrase(self, device_text, run_next):
"""When restoring a wallet, request the passphrase that was used for
the wallet on the given device and confirm it. Should return
a unicode string."""
@@ -218,10 +268,11 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
raise UserCancelled
return phrase
- def request_password(self, msg=None):
+ @wizard_dialog
+ def request_password(self, run_next):
"""Request the user enter a new password and confirm it. Return
the password or None for no password."""
- return self.pw_layout(msg or MSG_ENTER_PASSWORD, PW_NEW)
+ return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW)
def show_restore(self, wallet, network):
# FIXME: these messages are shown after the install wizard is
@@ -244,85 +295,43 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
"contain more addresses than displayed.")
self.show_message(msg)
- def create_addresses(self, wallet):
- def task():
- wallet.synchronize()
- self.emit(QtCore.SIGNAL('accept'))
- t = threading.Thread(target = task)
- t.start()
- self.please_wait.setText(MSG_GENERATING_WAIT)
- self.refresh_gui()
-
- def query_create_or_restore(self, wallet_kinds):
- """Ask the user what they want to do, and which wallet kind.
- wallet_kinds is an array of translated wallet descriptions.
- Return a a tuple (action, kind_index). Action is 'create' or
- 'restore', and kind the index of the wallet kind chosen."""
+ def confirm(self, msg):
+ vbox = QVBoxLayout()
+ vbox.addWidget(WWLabel(msg))
+ self.set_main_layout(vbox)
- actions = [_("Create a new wallet"),
- _("Restore a wallet from seed words or from keys")]
- title = _("Electrum could not find an existing wallet.")
- actions_clayout = ChoicesLayout(_("What do you want to do?"), actions)
- wallet_clayout = ChoicesLayout(_("Wallet kind:"), wallet_kinds)
+ @wizard_dialog
+ def action_dialog(self, action, run_next):
+ self.run(action)
- vbox = QVBoxLayout()
- vbox.addLayout(actions_clayout.layout())
- vbox.addLayout(wallet_clayout.layout())
- self.set_main_layout(vbox, title)
+ def terminate(self):
+ self.wallet.start_threads(self.network)
+ self.emit(QtCore.SIGNAL('accept'))
- action = ['create', 'restore'][actions_clayout.selected_index()]
- return action, wallet_clayout.selected_index()
+ def waiting_dialog(self, task, msg):
+ self.please_wait.setText(MSG_GENERATING_WAIT)
+ self.refresh_gui()
+ t = threading.Thread(target = task)
+ t.start()
- def query_hw_wallet_choice(self, msg, choices):
+ @wizard_dialog
+ def choice_dialog(self, title, message, choices, run_next):
+ c_values = map(lambda x: x[0], choices)
+ c_titles = map(lambda x: x[1], choices)
+ clayout = ChoicesLayout(message, c_titles)
vbox = QVBoxLayout()
- if choices:
- wallet_clayout = ChoicesLayout(msg, choices)
- vbox.addLayout(wallet_clayout.layout())
- else:
- vbox.addWidget(QLabel(msg, wordWrap=True))
- self.set_main_layout(vbox, next_enabled=len(choices) != 0)
- return wallet_clayout.selected_index() if choices else 0
+ vbox.addLayout(clayout.layout())
+ self.set_main_layout(vbox, title)
+ action = c_values[clayout.selected_index()]
+ return action
- def request_many(self, n, xpub_hot=None):
+ @wizard_dialog
+ def show_xpub_dialog(self, xpub, run_next):
vbox = QVBoxLayout()
- scroll = QScrollArea()
- scroll.setWidgetResizable(True)
- scroll.setFrameShape(QFrame.NoFrame)
- vbox.addWidget(scroll)
-
- w = QWidget()
- innerVbox = QVBoxLayout(w)
- scroll.setWidget(w)
-
- entries = []
-
- if xpub_hot:
- layout = SeedDisplayLayout(xpub_hot, title=MSG_SHOW_MPK, sid='hot')
- else:
- layout = SeedInputLayout(title=MSG_ENTER_SEED_OR_MPK, sid='hot')
- entries.append(layout.seed_edit())
- innerVbox.addLayout(layout.layout())
-
- for i in range(n):
- msg = MSG_COSIGNER % (i + 1) if xpub_hot else MSG_ENTER_SEED_OR_MPK
- layout = SeedInputLayout(title=msg, sid='cold')
- innerVbox.addLayout(layout.layout())
- entries.append(layout.seed_edit())
-
- def get_texts():
- return [clean_text(entry) for entry in entries]
- def set_enabled():
- texts = get_texts()
- is_valid = Wallet.is_xpub if xpub_hot else Wallet.is_any
- all_valid = all(is_valid(text) for text in texts)
- if xpub_hot:
- texts.append(xpub_hot)
- has_dups = len(set(texts)) < len(texts)
- self.next_button.setEnabled(all_valid and not has_dups)
- for e in entries:
- e.textChanged.connect(set_enabled)
- self.set_main_layout(vbox, next_enabled=False)
- return get_texts()
+ layout = SeedDisplayLayout(xpub, title=MSG_SHOW_MPK, sid='hot')
+ vbox.addLayout(layout.layout())
+ self.set_main_layout(vbox, MSG_SHOW_MPK)
+ return None
def choose_server(self, network):
title = _("Electrum communicates with remote servers to get "
@@ -335,7 +344,6 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
choices_title = _("How do you want to connect to a server? ")
clayout = ChoicesLayout(choices_title, choices)
self.set_main_layout(clayout.layout(), title)
-
auto_connect = True
if clayout.selected_index() == 1:
nlayout = NetworkChoiceLayout(network, self.config, wizard=True)
@@ -345,12 +353,8 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
self.config.set_key('auto_connect', auto_connect, True)
network.auto_connect = auto_connect
- def query_choice(self, msg, choices):
- clayout = ChoicesLayout(msg, choices)
- self.set_main_layout(clayout.layout(), next_enabled=bool(choices))
- return clayout.selected_index()
-
- def query_multisig(self, action):
+ @wizard_dialog
+ def multisig_dialog(self, run_next):
cw = CosignWidget(2, 2)
m_edit = QSlider(Qt.Horizontal, self)
n_edit = QSlider(Qt.Horizontal, self)
@@ -360,7 +364,6 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
m_edit.setMaximum(2)
n_edit.setValue(2)
m_edit.setValue(2)
-
n_label = QLabel()
m_label = QLabel()
grid = QGridLayout()
@@ -379,14 +382,11 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
m_edit.valueChanged.connect(on_m)
on_n(2)
on_m(2)
-
vbox = QVBoxLayout()
vbox.addWidget(cw)
- vbox.addWidget(WWLabel(_("Choose the number of signatures needed "
- "to unlock funds in your wallet:")))
+ vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:")))
vbox.addLayout(grid)
self.set_main_layout(vbox, _("Multi-Signature Wallet"))
m = int(m_edit.value())
n = int(n_edit.value())
- wallet_type = '%dof%d'%(m,n)
- return wallet_type
+ return (m, n)
diff --git a/lib/base_wizard.py b/lib/base_wizard.py
@@ -24,144 +24,232 @@
# SOFTWARE.
import os
-from electrum.wallet import Wallet, Multisig_Wallet
+from electrum.wallet import Wallet, Multisig_Wallet, WalletStorage
from i18n import _
class BaseWizard(object):
- def __init__(self, config, network, storage):
+ def __init__(self, config, network, path):
super(BaseWizard, self).__init__()
self.config = config
self.network = network
- self.storage = storage
+ self.storage = WalletStorage(path)
self.wallet = None
+ self.stack = []
def run(self, action, *args):
- '''Entry point of our Installation wizard'''
+ self.stack.append((action, args))
if not action:
return
- if hasattr(self, action):
+ if hasattr(self.wallet, 'plugin'):
+ if hasattr(self.wallet.plugin, action):
+ f = getattr(self.wallet.plugin, action)
+ apply(f, (self.wallet, self) + args)
+ elif hasattr(self, action):
f = getattr(self, action)
apply(f, *args)
else:
raise BaseException("unknown action", action)
+ def get_action(self):
+ if self.storage.file_exists:
+ self.wallet = Wallet(self.storage)
+ action = self.wallet.get_action()
+ else:
+ action = 'new'
+ return action
+
+ def get_wallet(self):
+ if self.wallet and self.wallet.get_action() is None:
+ return self.wallet
+
+ def can_go_back(self):
+ return len(self.stack)>1
+
+ def go_back(self):
+ if not self.can_go_back():
+ return
+ self.stack.pop()
+ action, args = self.stack.pop()
+ self.run(action, *args)
+
+ def run_wallet(self):
+ self.stack = []
+ action = self.wallet.get_action()
+ if action:
+ self.action_dialog(action=action, run_next=lambda x: self.run_wallet())
+
def new(self):
name = os.path.basename(self.storage.path)
- msg = "\n".join([
- _("Welcome to the Electrum installation wizard."),
+ title = _("Welcome to the Electrum installation wizard.")
+ message = '\n'.join([
_("The wallet '%s' does not exist.") % name,
_("What kind of wallet do you want to create?")
])
- choices = [
- (_('Standard wallet'), 'create_standard'),
- (_('Multi-signature wallet'), 'create_multisig'),
+ wallet_kinds = [
+ ('standard', _("Standard wallet")),
+ ('twofactor', _("Wallet with two-factor authentication")),
+ ('multisig', _("Multi-signature wallet")),
+ ('hardware', _("Hardware wallet")),
]
- self.choice_dialog(msg=msg, choices=choices, run_prev=self.cancel, run_next=self.run)
+ registered_kinds = Wallet.categories()
+ choices = [pair for pair in wallet_kinds if pair[0] in registered_kinds]
+ self.choice_dialog(title = title, message=message, choices=choices, run_next=self.on_wallet_type)
+
+ def on_wallet_type(self, choice):
+ self.wallet_type = choice
+ if choice == 'standard':
+ action = 'choose_seed'
+ elif choice == 'multisig':
+ action = 'choose_multisig'
+ elif choice == 'hardware':
+ action = 'choose_hw'
+ elif choice == 'twofactor':
+ action = 'choose_seed'
+ self.run(action)
+
+ def choose_multisig(self):
+ def on_multisig(m, n):
+ self.multisig_type = "%dof%d"%(m, n)
+ self.run('choose_seed')
+ self.multisig_dialog(run_next=on_multisig)
def choose_seed(self):
- msg = ' '.join([
- _("Do you want to create a new seed, or to restore a wallet using an existing seed?")
- ])
- choices = [
- (_('Create a new seed'), 'create_seed'),
- (_('I already have a seed'), 'restore_seed'),
- (_('Watching-only wallet'), 'restore_xpub')
- ]
- self.choice_dialog(msg=msg, choices=choices, run_prev=self.new, run_next=self.run)
-
- def create_multisig(self):
- def f(m, n):
- self.wallet_type = "%dof%d"%(m, n)
- self.run('choose_seed')
- name = os.path.basename(self.storage.path)
- self.multisig_dialog(run_prev=self.new, run_next=f)
+ title = _('Private Keys')
+ message = _("Do you want to create a new seed, or to restore a wallet using an existing seed?")
+ if self.wallet_type == 'standard':
+ choices = [
+ ('create_seed', _('Create a new seed')),
+ ('restore_seed', _('I already have a seed')),
+ ('restore_xpub', _('Watching-only wallet')),
+ ]
+ elif self.wallet_type == 'twofactor':
+ choices = [
+ ('create_2fa', _('Create a new seed')),
+ ('restore_2fa', _('I already have a seed')),
+ ]
+ elif self.wallet_type == 'multisig':
+ choices = [
+ ('create_seed', _('Create a new seed')),
+ ('restore_seed', _('I already have a seed')),
+ ('restore_xpub', _('Watching-only wallet')),
+ ('choose_hw', _('Cosign with hardware wallet')),
+ ]
+ self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
+
+ def create_2fa(self):
+ print 'create 2fa'
+ self.storage.put('wallet_type', '2fa')
+ self.wallet = Wallet(self.storage)
+ self.run_wallet()
def restore_seed(self):
msg = _('Please type your seed phrase using the virtual keyboard.')
- self.restore_seed_dialog(run_prev=self.new, run_next=self.enter_pin, test=Wallet.is_seed, message=msg)
+ title = _('Enter Seed')
+ self.enter_seed_dialog(run_next=self.add_password, title=title, message=msg, is_valid=Wallet.is_seed)
def restore_xpub(self):
title = "MASTER PUBLIC KEY"
- message = _('To create a watching-only wallet, paste your master public key, or scan it using the camera button.')
- self.add_xpub_dialog(run_prev=self.new, run_next=lambda xpub: self.create_wallet(xpub, None), title=title, message=message, test=Wallet.is_mpk)
-
- def create_standard(self):
- self.wallet_type = 'standard'
- self.run('choose_seed')
+ message = _('To create a watching-only wallet, paste your master public key, or scan it using the camera button.')
+ self.add_xpub_dialog(run_next=lambda xpub: self.create_wallet(xpub, None), title=title, message=message, is_valid=Wallet.is_mpk)
+
+ def restore_2fa(self):
+ self.storage.put('wallet_type', '2fa')
+ self.wallet = Wallet(self.storage)
+ self.wallet.plugin.on_restore_wallet(self.wallet, self)
+
+ def choose_hw(self):
+ hw_wallet_types, choices = self.plugins.hardware_wallets('create')
+ choices = zip(hw_wallet_types, choices)
+ title = _('Hardware wallet')
+ if choices:
+ msg = _('Select the type of hardware wallet: ')
+ else:
+ msg = ' '.join([
+ _('No hardware wallet support found on your system.'),
+ _('Please install the relevant libraries (eg python-trezor for Trezor).'),
+ ])
+ self.choice_dialog(title=title, message=msg, choices=choices, run_next=self.on_hardware)
+
+ def on_hardware(self, hw_type):
+ self.hw_type = hw_type
+ if self.wallet_type == 'multisig':
+ self.create_hardware_multisig()
+ else:
+ title = _('Hardware wallet') + ' [%s]' % hw_type
+ message = _('Do you have a device, or do you want to restore a wallet using an existing seed?')
+ choices = [
+ ('create_hardware_wallet', _('I have a device')),
+ ('restore_hardware_wallet', _('Use hardware wallet seed')),
+ ]
+ self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
+
+ def create_hardware_multisig(self):
+ self.storage.put('wallet_type', self.multisig_type)
+ self.wallet = Multisig_Wallet(self.storage)
+ # todo: get the xpub from the plugin
+ self.run('create_wallet', xpub, None)
+
+ def create_hardware_wallet(self):
+ self.storage.put('wallet_type', self.hw_type)
+ self.wallet = Wallet(self.storage)
+ self.wallet.plugin.on_create_wallet(self.wallet, self)
+ self.terminate()
+
+ def restore_hardware_wallet(self):
+ self.storage.put('wallet_type', self.wallet_type)
+ self.wallet = Wallet(self.storage)
+ self.wallet.plugin.on_restore_wallet(self.wallet, self)
+ self.terminate()
def create_wallet(self, text, password):
if self.wallet_type == 'standard':
self.wallet = Wallet.from_text(text, password, self.storage)
self.run('create_addresses')
- else:
- self.storage.put('wallet_type', self.wallet_type)
+ elif self.wallet_type == 'multisig':
+ self.storage.put('wallet_type', self.multisig_type)
self.wallet = Multisig_Wallet(self.storage)
self.wallet.add_seed(text, password)
self.wallet.create_master_keys(password)
- action = self.wallet.get_action()
- self.run(action)
+ self.run_wallet()
def add_cosigners(self):
xpub = self.wallet.master_public_keys.get('x1/')
- self.show_xpub_dialog(run_prev=self.create_multisig, run_next=self.add_cosigner, xpub=xpub, test=Wallet.is_xpub)
+ self.show_xpub_dialog(run_next=lambda x: self.add_cosigner(), xpub=xpub)
def add_cosigner(self):
def on_xpub(xpub):
self.wallet.add_cosigner(xpub)
i = self.wallet.get_missing_cosigner()
- action = 'add_cosigner' if i else 'create_main_account'
+ action = 'add_cosigner' if i else 'create_addresses'
self.run(action)
- title = "ADD COSIGNER"
+ i = self.wallet.get_missing_cosigner()
+ title = _("Add Cosigner") + " %d"%(i-1)
message = _('Please paste your cosigners master public key, or scan it using the camera button.')
- self.add_xpub_dialog(run_prev=self.add_cosigners, run_next=on_xpub, title=title, message=message, test=Wallet.is_xpub)
-
- def create_main_account(self):
- self.wallet.create_main_account()
- self.run('create_addresses')
+ self.add_xpub_dialog(run_next=on_xpub, title=title, message=message, is_valid=Wallet.is_any)
def create_addresses(self):
def task():
self.wallet.create_main_account()
self.wallet.synchronize()
+ self.terminate()
msg= _("Electrum is generating your addresses, please wait.")
- self.waiting_dialog(task, msg, on_complete=self.terminate)
+ self.waiting_dialog(task, msg)
def create_seed(self):
from electrum.wallet import BIP32_Wallet
seed = BIP32_Wallet.make_seed()
msg = _("If you forget your PIN or lose your device, your seed phrase will be the "
"only way to recover your funds.")
- self.show_seed_dialog(run_prev=self.new, run_next=self.confirm_seed, message=msg, seed_text=seed)
+ self.show_seed_dialog(run_next=self.confirm_seed, message=msg, seed_text=seed)
def confirm_seed(self, seed):
assert Wallet.is_seed(seed)
+ title = _('Confirm Seed')
msg = _('Please retype your seed phrase, to confirm that you properly saved it')
- self.restore_seed_dialog(run_prev=self.create_seed, run_next=self.enter_pin, test=lambda x: x==seed, message=msg)
-
- def enter_pin(self, seed):
- def callback(pin):
- action = 'confirm_pin' if pin else 'create_wallet'
- self.run(action, (seed, pin))
- self.password_dialog('Choose a PIN code', callback)
-
- def confirm_pin(self, seed, pin):
- def callback(conf):
- if conf == pin:
- self.run('create_wallet', (seed, pin))
- else:
- self.show_error(_('PIN mismatch'))
- self.run('enter_pin', (seed,))
- self.password_dialog('Confirm your PIN code', callback)
-
- def terminate(self):
- self.wallet.start_threads(self.network)
- self.dispatch('on_wizard_complete', self.wallet)
-
- def cancel(self):
- self.dispatch('on_wizard_complete', None)
- return True
-
-
+ self.enter_seed_dialog(run_next=self.add_password, title=title, message=msg, is_valid=lambda x: x==seed)
+ def add_password(self, seed):
+ f = lambda x: self.create_wallet(seed, x)
+ self.request_password(run_next=f)
diff --git a/lib/daemon.py b/lib/daemon.py
@@ -171,27 +171,29 @@ class Daemon(DaemonThread):
response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
return response
- def load_wallet(self, path, get_wizard=None):
+ def load_wallet(self, path):
if path in self.wallets:
wallet = self.wallets[path]
- else:
- storage = WalletStorage(path)
- if storage.file_exists:
- wallet = Wallet(storage)
- action = wallet.get_action()
- else:
- action = 'new'
- if action:
- if get_wizard is None:
- return None
- wizard = get_wizard()
- wallet = wizard.run(self.network, storage)
- else:
- wallet.start_threads(self.network)
- if wallet:
- self.wallets[path] = wallet
+ return wallet
+ storage = WalletStorage(path)
+ if not storage.file_exists:
+ return
+ wallet = Wallet(storage)
+ action = wallet.get_action()
+ if action:
+ return
+ wallet.start_threads(self.network)
+ self.wallets[path] = wallet
return wallet
+ def add_wallet(self, wallet):
+ path = wallet.storage.path
+ self.wallets[path] = wallet
+
+ def stop_wallet(self, path):
+ wallet = self.wallets.pop(path)
+ wallet.stop_threads()
+
def run_cmdline(self, config_options):
config = SimpleConfig(config_options)
cmdname = config.get('cmd')
diff --git a/lib/wizard.py b/lib/wizard.py
@@ -1,328 +0,0 @@
-#!/usr/bin/env python
-#
-# Electrum - lightweight Bitcoin client
-# Copyright (C) 2015 thomasv@gitorious, kyuupichan@gmail
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-from electrum import WalletStorage
-from electrum.plugins import run_hook
-from util import PrintError
-from wallet import Wallet
-from i18n import _
-
-MSG_GENERATING_WAIT = _("Electrum is generating your addresses, please wait...")
-MSG_ENTER_ANYTHING = _("Please enter a seed phrase, a master key, a list of "
- "Bitcoin addresses, or a list of private keys")
-MSG_ENTER_SEED_OR_MPK = _("Please enter a seed phrase or a master key (xpub or xprv):")
-MSG_VERIFY_SEED = _("Your seed is important!\nTo make sure that you have properly saved your seed, please retype it here.")
-MSG_COSIGNER = _("Please enter the master public key of cosigner #%d:")
-MSG_SHOW_MPK = _("Here is your master public key:")
-MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys. "
- "Enter nothing if you want to disable encryption.")
-MSG_RESTORE_PASSPHRASE = \
- _("Please enter the passphrase you used when creating your %s wallet. "
- "Note this is NOT a password. Enter nothing if you did not use "
- "one or are unsure.")
-
-class WizardBase(PrintError):
- '''Base class for gui-specific install wizards.'''
- user_actions = ('create', 'restore')
- wallet_kinds = [
- ('standard', _("Standard wallet")),
- ('twofactor', _("Wallet with two-factor authentication")),
- ('multisig', _("Multi-signature wallet")),
- ('hardware', _("Hardware wallet")),
- ]
-
- # Derived classes must set:
- # self.language_for_seed
- # self.plugins
-
- def show_error(self, msg):
- raise NotImplementedError
-
- def show_warning(self, msg):
- raise NotImplementedError
-
- def remove_from_recently_open(self, filename):
- """Remove filename from the recently used list."""
- raise NotImplementedError
-
- def query_create_or_restore(self, wallet_kinds):
- """Ask the user what they want to do, and which wallet kind.
- wallet_kinds is an array of translated wallet descriptions.
- Return a a tuple (action, kind_index). Action is 'create' or
- 'restore', and kind the index of the wallet kind chosen."""
- raise NotImplementedError
-
- def query_multisig(self, action):
- """Asks the user what kind of multisig wallet they want. Returns a
- string like "2of3". Action is 'create' or 'restore'."""
- raise NotImplementedError
-
- def query_choice(self, msg, choices):
- """Asks the user which of several choices they would like.
- Return the index of the choice."""
- raise NotImplementedError
-
- def query_hw_wallet_choice(self, msg, action, choices):
- """Asks the user which hardware wallet kind they are using. Action is
- 'create' or 'restore' from the initial screen. As this is
- confusing for hardware wallets ask a new question with the
- three possibilities Initialize ('create'), Use ('create') or
- Restore a software-only wallet ('restore'). Return a pair
- (action, choice)."""
- raise NotImplementedError
-
- def show_and_verify_seed(self, seed):
- """Show the user their seed. Ask them to re-enter it. Return
- True on success."""
- raise NotImplementedError
-
- def request_passphrase(self, device_text):
- """When restoring a wallet, request the passphrase that was used for
- the wallet on the given device and confirm it. Should return
- a unicode string."""
- raise NotImplementedError
-
- def request_password(self, msg=None):
- """Request the user enter a new password and confirm it. Return
- the password or None for no password."""
- raise NotImplementedError
-
- def request_seed(self, msg, is_valid=None):
- """Request the user enter a seed. Returns the seed the user entered.
- is_valid is a function that returns True if a seed is valid, for
- dynamic feedback. If not provided, Wallet.is_any is used."""
- raise NotImplementedError
-
- def request_many(self, n, xpub_hot=None):
- """If xpub_hot is provided, a new wallet is being created. Request N
- master public keys for cosigners; xpub_hot is the master xpub
- key for the wallet.
-
- If xpub_hot is None, request N cosigning master xpub keys,
- xprv keys, or seeds in order to perform wallet restore."""
- raise NotImplementedError
-
- def choose_server(self, network):
- """Choose a server if one is not set in the config anyway."""
- raise NotImplementedError
-
- def show_restore(self, wallet, network):
- """Show restore result"""
- pass
-
- def finished(self):
- """Called when the wizard is done."""
- pass
-
- def run(self, network, storage):
- '''The main entry point of the wizard. Open a wallet from the given
- filename. If the file doesn't exist launch the GUI-specific
- install wizard proper, created by calling create_wizard().'''
- need_sync = False
- is_restore = False
-
- if storage.file_exists:
- wallet = Wallet(storage)
- if wallet.imported_keys:
- self.update_wallet_format(wallet)
- action = wallet.get_action()
- if action != 'new':
- self.hide()
- path = storage.path
- msg = _("The file '%s' contains an incompletely created wallet.\n"
- "Do you want to complete its creation now?") % path
- if not self.question(msg):
- if self.question(_("Do you want to delete '%s'?") % path):
- import os
- os.remove(path)
- self.show_warning(_('The file was removed'))
- return
- return
- self.show()
- else:
- cr, wallet = self.create_or_restore(storage)
- if not wallet:
- return
- need_sync = True
- is_restore = (cr == 'restore')
-
- while True:
- action = wallet.get_action()
- if not action:
- break
- need_sync = True
- self.run_wallet_action(wallet, action)
- # Save the wallet after each action
- wallet.storage.write()
-
- if network:
- # Show network dialog if config does not exist
- if self.config.get('auto_connect') is None:
- self.choose_server(network)
- else:
- self.show_warning(_('You are offline'))
-
- if need_sync:
- self.create_addresses(wallet)
-
- # start wallet threads
- if network:
- wallet.start_threads(network)
-
- if is_restore:
- self.show_restore(wallet, network)
-
- self.finished()
-
- return wallet
-
- def run_wallet_action(self, wallet, action):
- self.print_error("action %s on %s" % (action, wallet.basename()))
- # Run the action on the wallet plugin, if any, then the
- # wallet and finally ourselves
- calls = []
- if hasattr(wallet, 'plugin'):
- calls.append((wallet.plugin, (wallet, self)))
- calls.extend([(wallet, ()), (self, (wallet, ))])
- calls = [(getattr(actor, action), args) for (actor, args) in calls
- if hasattr(actor, action)]
- if not calls:
- raise RuntimeError("No handler found for %s action" % action)
- for method, args in calls:
- method(*args)
-
- def create_or_restore(self, storage):
- '''After querying the user what they wish to do, create or restore
- a wallet and return it.'''
- self.remove_from_recently_open(storage.path)
-
- # Filter out any unregistered wallet kinds
- registered_kinds = Wallet.categories()
- kinds, descriptions = zip(*[pair for pair in WizardBase.wallet_kinds
- if pair[0] in registered_kinds])
- action, kind_index = self.query_create_or_restore(descriptions)
- assert action in WizardBase.user_actions
- kind = kinds[kind_index]
- if kind == 'multisig':
- wallet_type = self.query_multisig(action)
- elif kind == 'hardware':
- hw_wallet_types, choices = self.plugins.hardware_wallets(action)
- if choices:
- msg = _('Select the type of hardware wallet: ')
- else:
- msg = ' '.join([
- _('No hardware wallet support found on your system.'),
- _('Please install the relevant libraries (eg python-trezor for Trezor).'),
- ])
- choice = self.query_hw_wallet_choice(msg, choices)
- wallet_type = hw_wallet_types[choice]
- elif kind == 'twofactor':
- wallet_type = '2fa'
- else:
- wallet_type = 'standard'
-
- if action == 'create':
- wallet = self.create_wallet(storage, wallet_type, kind)
- else:
- wallet = self.restore_wallet(storage, wallet_type, kind)
-
- return action, wallet
-
- def construct_wallet(self, storage, wallet_type):
- storage.put('wallet_type', wallet_type)
- return Wallet(storage)
-
- def create_wallet(self, storage, wallet_type, kind):
- wallet = self.construct_wallet(storage, wallet_type)
- if kind == 'hardware':
- wallet.plugin.on_create_wallet(wallet, self)
- return wallet
-
- def restore_wallet(self, storage, wallet_type, kind):
- if wallet_type == 'standard':
- return self.restore_standard_wallet(storage)
-
- if kind == 'multisig':
- return self.restore_multisig_wallet(storage, wallet_type)
-
- # Plugin (two-factor or hardware)
- wallet = self.construct_wallet(storage, wallet_type)
- return wallet.plugin.on_restore_wallet(wallet, self)
-
- def restore_standard_wallet(self, storage):
- text = self.request_seed(MSG_ENTER_ANYTHING)
- need_password = Wallet.should_encrypt(text)
- password = self.request_password() if need_password else None
- return Wallet.from_text(text, password, storage)
-
- def restore_multisig_wallet(self, storage, wallet_type):
- # FIXME: better handling of duplicate keys
- m, n = Wallet.multisig_type(wallet_type)
- key_list = self.request_many(n - 1)
- need_password = any(Wallet.should_encrypt(text) for text in key_list)
- password = self.request_password() if need_password else None
- return Wallet.from_multisig(key_list, password, storage, wallet_type)
-
- def create_seed(self, wallet):
- '''The create_seed action creates a seed and generates
- master keys.'''
- seed = wallet.make_seed(self.language_for_seed)
- self.show_and_verify_seed(seed)
- password = self.request_password()
- wallet.add_seed(seed, password)
- wallet.create_master_keys(password)
-
- def create_main_account(self, wallet):
- # FIXME: BIP44 restore requires password
- wallet.create_main_account()
-
- def create_addresses(self, wallet):
- wallet.synchronize()
-
- def add_cosigners(self, wallet):
- # FIXME: better handling of duplicate keys
- m, n = Wallet.multisig_type(wallet.wallet_type)
- xpub1 = wallet.master_public_keys.get("x1/")
- xpubs = self.request_many(n - 1, xpub1)
- for i, xpub in enumerate(xpubs):
- wallet.add_master_public_key("x%d/" % (i + 2), xpub)
-
- def update_wallet_format(self, wallet):
- # Backwards compatibility: convert old-format imported keys
- msg = _("Please enter your password in order to update "
- "imported keys")
- if wallet.use_encryption:
- password = self.request_password(msg)
- else:
- password = None
-
- try:
- wallet.convert_imported_keys(password)
- except Exception as e:
- self.show_error(str(e))
-
- # Call synchronize to regenerate addresses in case we're offline
- if wallet.get_master_public_keys() and not wallet.addresses():
- wallet.synchronize()
diff --git a/plugins/hw_wallet/plugin.py b/plugins/hw_wallet/plugin.py
@@ -53,21 +53,27 @@ class HW_PluginBase(BasePlugin):
def on_restore_wallet(self, wallet, wizard):
assert isinstance(wallet, self.wallet_class)
-
msg = _("Enter the seed for your %s wallet:" % self.device)
- seed = wizard.request_seed(msg, is_valid = self.is_valid_seed)
+ f = lambda x: wizard.run('on_restore_seed', x)
+ wizard.enter_seed_dialog(run_next=f, title=_('Restore hardware wallet'), message=msg, is_valid=self.is_valid_seed)
+
+ def on_restore_seed(self, wallet, wizard, seed):
+ f = lambda x: wizard.run('on_restore_passphrase', seed, x)
+ wizard.request_passphrase(self.device, run_next=f)
+ def on_restore_passphrase(self, wallet, wizard, seed, passphrase):
+ f = lambda x: wizard.run('on_restore_password', seed, passphrase, x)
+ wizard.request_password(run_next=f)
+
+ def on_restore_password(self, wallet, wizard, seed, passphrase, password):
# Restored wallets are not hardware wallets
wallet_class = self.wallet_class.restore_wallet_class
wallet.storage.put('wallet_type', wallet_class.wallet_type)
wallet = wallet_class(wallet.storage)
-
- passphrase = wizard.request_passphrase(self.device)
- password = wizard.request_password()
wallet.add_seed(seed, password)
wallet.add_xprv_from_seed(seed, 'x/', password, passphrase)
wallet.create_hd_account(password)
- return wallet
+ wizard.create_addresses()
@staticmethod
def is_valid_seed(seed):
diff --git a/plugins/trustedcoin/qt.py b/plugins/trustedcoin/qt.py
@@ -110,14 +110,9 @@ class Plugin(TrustedCoinPlugin):
return WaitingDialog(window, 'Getting billing information...', task,
on_finished)
- def confirm(self, window, msg):
- vbox = QVBoxLayout()
- vbox.addWidget(WWLabel(msg))
- window.set_main_layout(vbox)
-
- def show_disclaimer(self, wallet, window):
- window.set_icon(':icons/trustedcoin.png')
- self.confirm(window, '\n\n'.join(DISCLAIMER))
+ def show_disclaimer(self, wallet, wizard):
+ wizard.set_icon(':icons/trustedcoin.png')
+ wizard.confirm('\n\n'.join(DISCLAIMER))
self.set_enabled(wallet, True)
@hook
diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py
@@ -346,21 +346,32 @@ class TrustedCoinPlugin(BasePlugin):
wallet.price_per_tx = dict(billing_info['price_per_tx'])
return True
- def create_extended_seed(self, wallet, window):
+ def create_extended_seed(self, wallet, wizard):
+ self.wallet = wallet
+ self.wizard = wizard
seed = wallet.make_seed()
- window.show_and_verify_seed(seed, is_valid=self.is_valid_seed)
+ f = lambda x: wizard.run('confirm_seed', x)
+ self.wizard.show_seed_dialog(run_next=f, message="z", seed_text=seed)
- password = window.request_password()
+ def confirm_seed(self, wallet, wizard, seed):
+ title = _('Confirm Seed')
+ msg = _('Please retype your seed phrase, to confirm that you properly saved it')
+ f = lambda x: wizard.run('add_password', x)
+ self.wizard.enter_seed_dialog(run_next=f, title=title, message=msg, is_valid=lambda x: x==seed)
+
+ def add_password(self, wallet, wizard, seed):
+ f = lambda x: self.create_wallet(seed, x)
+ self.wizard.request_password(run_next=f)
+
+ def create_wallet(self, seed, password):
+ wallet = self.wallet
wallet.storage.put('seed_version', wallet.seed_version)
wallet.storage.put('use_encryption', password is not None)
-
words = seed.split()
n = len(words)/2
wallet.add_xprv_from_seed(' '.join(words[0:n]), 'x1/', password)
wallet.add_xpub_from_seed(' '.join(words[n:]), 'x2/')
-
wallet.storage.write()
-
msg = [
_("Your wallet file is: %s.")%os.path.abspath(wallet.storage.path),
_("You need to be online in order to complete the creation of "
@@ -371,7 +382,8 @@ class TrustedCoinPlugin(BasePlugin):
_('If you are online, click on "%s" to continue.') % _('Next')
]
msg = '\n\n'.join(msg)
- self.confirm(window, msg)
+ self.wizard.confirm(msg)
+ return wallet
@hook
def do_clear(self, window):
@@ -379,19 +391,22 @@ class TrustedCoinPlugin(BasePlugin):
def on_restore_wallet(self, wallet, wizard):
assert isinstance(wallet, self.wallet_class)
+ title = _("Restore two-factor Wallet")
+ f = lambda x: wizard.run('on_restore_seed', x)
+ wizard.enter_seed_dialog(run_next=f, title=title, message=RESTORE_MSG, is_valid=self.is_valid_seed)
- seed = wizard.request_seed(RESTORE_MSG, is_valid=self.is_valid_seed)
- password = wizard.request_password()
+ def on_restore_seed(self, wallet, wizard, seed):
+ f = lambda x: wizard.run('on_restore_pw', seed, x)
+ wizard.request_password(run_next=f)
+ def on_restore_pw(self, wallet, wizard, seed, password):
wallet.add_seed(seed, password)
words = seed.split()
n = len(words)/2
wallet.add_xprv_from_seed(' '.join(words[0:n]), 'x1/', password)
wallet.add_xprv_from_seed(' '.join(words[n:]), 'x2/', password)
-
restore_third_key(wallet)
- wallet.create_main_account()
- return wallet
+ wizard.create_addresses()
def create_remote_key(self, wallet, window):
email = self.accept_terms_of_use(window)