1 from PyQt4.QtGui import *
2 from PyQt4.QtCore import *
3 import PyQt4.QtCore as QtCore
5 from electrum.i18n import _
6 from electrum import Wallet, Wallet_2of2, Wallet_2of3
7 from electrum import bitcoin
8 from electrum import util
11 from network_dialog import NetworkDialog
13 from amountedit import AmountEdit
17 from electrum.plugins import run_hook
20 MSG_ENTER_ANYTHING = _("Please enter a wallet seed, a master public key, a list of Bitcoin addresses, or a list of private keys")
21 MSG_SHOW_MPK = _("This is your master public key")
22 MSG_ENTER_MPK = _("Please enter your master public key")
23 MSG_ENTER_COLD_MPK = _("Please enter the master public key of your cosigner wallet")
24 MSG_ENTER_SEED_OR_MPK = _("Please enter a wallet seed, or master public key")
25 MSG_VERIFY_SEED = _("Your seed is important!") + "\n" + _("To make sure that you have properly saved your seed, please retype it here.")
28 class InstallWizard(QDialog):
30 def __init__(self, config, network, storage):
31 QDialog.__init__(self)
33 self.network = network
34 self.storage = storage
35 self.setMinimumSize(575, 400)
36 self.setWindowTitle('Electrum')
37 self.connect(self, QtCore.SIGNAL('accept'), self.accept)
39 self.stack = QStackedLayout()
40 self.setLayout(self.stack)
43 def set_layout(self, layout):
46 self.stack.setCurrentIndex(self.stack.addWidget(w))
49 def restore_or_create(self):
53 main_label = QLabel(_("Electrum could not find an existing wallet."))
54 vbox.addWidget(main_label)
59 label = QLabel(_("What do you want to do?"))
60 label.setWordWrap(True)
61 grid.addWidget(label, 0, 0)
64 grid.addWidget(gb1, 0, 0)
66 group1 = QButtonGroup()
68 b1 = QRadioButton(gb1)
69 b1.setText(_("Create new wallet"))
72 b2 = QRadioButton(gb1)
73 b2.setText(_("Restore an existing wallet"))
78 grid.addWidget(b1, 1, 0)
79 grid.addWidget(b2, 2, 0)
85 class ClickableLabel(QLabel):
86 def mouseReleaseEvent(self, ev):
87 self.emit(SIGNAL('clicked()'))
89 label2 = ClickableLabel(_("Wallet type:") + " [+]")
91 hbox.addWidget(label2)
92 grid2.addLayout(hbox, 0, 0)
95 grid.addWidget(gb2, 3, 0)
96 group2 = QButtonGroup()
99 ('standard', _("Standard wallet"), Wallet),
100 ('2of2', _("Multisig wallet (2 of 2)"), Wallet_2of2),
101 ('2of3', _("Multisig wallet (2 of 3)"), Wallet_2of3)
103 run_hook('add_wallet_types', self.wallet_types)
105 for i, (t,l,c) in enumerate(self.wallet_types):
106 button = QRadioButton(gb2)
108 grid2.addWidget(button, i+1, 0)
109 group2.addButton(button)
110 group2.setId(button, i)
112 button.setChecked(True)
114 # button.setHidden(True)
118 buttons = group2.buttons()
119 x = buttons[1].isHidden()
120 label2.setText(_("Wallet type:") + (' [+]' if x else ' [-]'))
121 for b in buttons[1:]:
124 self.connect(label2, SIGNAL('clicked()'), toggle)
125 grid2.addWidget(label2)
127 vbox.addLayout(grid2)
129 hbox, button = ok_cancel_buttons2(self, _('Next'))
131 self.set_layout(vbox)
134 button.setDefault(True)
139 action = 'create' if b1.isChecked() else 'restore'
140 wallet_type = self.wallet_types[group2.checkedId()][0]
141 return action, wallet_type
144 def verify_seed(self, seed, sid):
145 r = self.enter_seed_dialog(MSG_VERIFY_SEED, sid)
150 QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
156 def get_seed_text(self, seed_e):
157 text = unicode(seed_e.toPlainText()).strip()
158 text = ' '.join(text.split())
161 def is_any(self, seed_e):
162 text = self.get_seed_text(seed_e)
163 return Wallet.is_seed(text) or Wallet.is_old_mpk(text) or Wallet.is_xpub(text) or Wallet.is_xprv(text) or Wallet.is_address(text) or Wallet.is_private_key(text)
165 def is_mpk(self, seed_e):
166 text = self.get_seed_text(seed_e)
167 return Wallet.is_xpub(text) or Wallet.is_old_mpk(text)
169 def is_xpub(self, seed_e):
170 text = self.get_seed_text(seed_e)
171 return Wallet.is_xpub(text)
173 def enter_seed_dialog(self, msg, sid):
174 vbox, seed_e = seed_dialog.enter_seed_box(msg, sid)
176 hbox, button = ok_cancel_buttons2(self, _('Next'))
178 button.setEnabled(False)
179 seed_e.textChanged.connect(lambda: button.setEnabled(self.is_any(seed_e)))
180 self.set_layout(vbox)
183 return self.get_seed_text(seed_e)
186 def multi_mpk_dialog(self, xpub_hot, n):
188 vbox0, seed_e0 = seed_dialog.enter_seed_box(MSG_SHOW_MPK, 'hot')
189 vbox.addLayout(vbox0)
190 seed_e0.setText(xpub_hot)
191 seed_e0.setReadOnly(True)
194 vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, 'cold')
195 vbox.addLayout(vbox2)
196 entries.append(seed_e2)
198 hbox, button = ok_cancel_buttons2(self, _('Next'))
200 button.setEnabled(False)
201 f = lambda: button.setEnabled( map(lambda e: self.is_xpub(e), entries) == [True]*len(entries))
203 e.textChanged.connect(f)
204 self.set_layout(vbox)
207 return map(lambda e: self.get_seed_text(e), entries)
210 def multi_seed_dialog(self, n):
212 vbox1, seed_e1 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'hot')
213 vbox.addLayout(vbox1)
216 vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'cold')
217 vbox.addLayout(vbox2)
218 entries.append(seed_e2)
220 hbox, button = ok_cancel_buttons2(self, _('Next'))
222 button.setEnabled(False)
224 f = lambda: button.setEnabled( map(lambda e: self.is_any(e), entries) == [True]*len(entries))
226 e.textChanged.connect(f)
228 self.set_layout(vbox)
231 return map(lambda e: self.get_seed_text(e), entries)
237 def waiting_dialog(self, task, msg= _("Electrum is generating your addresses, please wait.")):
240 self.emit(QtCore.SIGNAL('accept'))
243 self.waiting_label = QLabel(msg)
244 vbox.addWidget(self.waiting_label)
245 self.set_layout(vbox)
246 t = threading.Thread(target = target)
253 def network_dialog(self):
258 label = QLabel(_("Electrum communicates with remote servers to get information about your transactions and addresses. The servers all fulfil the same purpose only differing in hardware. In most cases you simply want to let Electrum pick one at random if you have a preference though feel free to select a server manually.") + "\n\n" \
259 + _("How do you want to connect to a server:")+" ")
260 label.setWordWrap(True)
261 grid.addWidget(label, 0, 0)
265 b1 = QRadioButton(gb)
266 b1.setText(_("Auto connect"))
269 b2 = QRadioButton(gb)
270 b2.setText(_("Select server manually"))
272 #b3 = QRadioButton(gb)
273 #b3.setText(_("Stay offline"))
275 grid.addWidget(b1,1,0)
276 grid.addWidget(b2,2,0)
277 #grid.addWidget(b3,3,0)
283 vbox.addLayout(ok_cancel_buttons(self, _('Next')))
285 self.set_layout(vbox)
290 return NetworkDialog(self.network, self.config, None).do_exec()
293 self.config.set_key('auto_cycle', True, True)
297 self.config.set_key("server", None, True)
298 self.config.set_key('auto_cycle', False, True)
302 def show_message(self, msg, icon=None):
304 self.set_layout(vbox)
309 vbox.addWidget(QLabel(msg))
311 vbox.addLayout(close_button(self, _('Next')))
316 def question(self, msg, icon=None):
318 self.set_layout(vbox)
323 vbox.addWidget(QLabel(msg))
325 vbox.addLayout(ok_cancel_buttons(self, _('OK')))
331 def show_seed(self, seed, sid):
332 vbox = seed_dialog.show_seed_box(seed, sid)
333 vbox.addLayout(ok_cancel_buttons(self, _("Next")))
334 self.set_layout(vbox)
338 def password_dialog(self):
339 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
340 +_("Leave these fields empty if you want to disable encryption.")
341 from password_dialog import make_password_dialog, run_password_dialog
342 self.set_layout( make_password_dialog(self, None, msg) )
343 return run_password_dialog(self, None, self)[2]
346 def create_cold_seed(self, wallet):
347 from electrum.bitcoin import mnemonic_to_seed, bip32_root
348 msg = _('You are about to generate the cold storage seed of your wallet.') + '\n' \
349 + _('For safety, you should do this on an offline computer.')
350 icon = QPixmap( ':icons/cold_seed.png').scaledToWidth(56)
351 if not self.question(msg, icon):
354 cold_seed = wallet.make_seed()
355 if not self.show_seed(cold_seed, 'cold'):
357 if not self.verify_seed(cold_seed, 'cold'):
360 hex_seed = mnemonic_to_seed(cold_seed,'').encode('hex')
361 xpriv, xpub = bip32_root(hex_seed)
362 wallet.add_master_public_key('cold/', xpub)
364 msg = _('Your master public key was saved in your wallet file.') + '\n'\
365 + _('Your cold seed must be stored on paper; it is not in the wallet file.')+ '\n\n' \
366 + _('This program is about to close itself.') + '\n'\
367 + _('You will need to reopen your wallet on an online computer, in order to complete the creation of your wallet')
368 self.show_message(msg)
372 def run(self, action):
375 action, wallet_type = self.restore_or_create()
376 self.storage.put('wallet_type', wallet_type, False)
381 if action == 'restore':
382 wallet = self.restore(wallet_type)
388 wallet = Wallet(self.storage)
389 action = wallet.get_action()
390 # fixme: password is only needed for multiple accounts
393 while action is not None:
395 util.print_error("installwizard:", wallet, action)
397 if action == 'create_seed':
398 seed = wallet.make_seed()
399 if not self.show_seed(seed, None):
401 if not self.verify_seed(seed, None):
403 password = self.password_dialog()
404 wallet.add_seed(seed, password)
406 elif action == 'add_cosigner':
407 xpub_hot = wallet.master_public_keys.get("m/")
408 r = self.multi_mpk_dialog(xpub_hot, 1)
412 wallet.add_master_public_key("cold/", xpub_cold)
414 elif action == 'add_two_cosigners':
415 xpub_hot = wallet.master_public_keys.get("m/")
416 r = self.multi_mpk_dialog(xpub_hot, 2)
420 wallet.add_master_public_key("cold/", xpub1)
421 wallet.add_master_public_key("remote/", xpub2)
423 elif action == 'create_accounts':
424 wallet.create_accounts(password)
425 self.waiting_dialog(wallet.synchronize)
427 elif action == 'create_cold_seed':
428 self.create_cold_seed(wallet)
432 r = run_hook('install_wizard_action', self, wallet, action)
434 raise BaseException('unknown wizard action', action)
437 action = wallet.get_action()
441 if self.network.interfaces:
442 self.network_dialog()
444 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
448 # start wallet threads
449 wallet.start_threads(self.network)
451 if action == 'restore':
452 self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
454 if wallet.is_found():
455 QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
457 QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
459 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))
465 def restore(self, t):
468 text = self.enter_seed_dialog(MSG_ENTER_ANYTHING, None)
471 if Wallet.is_seed(text):
472 password = self.password_dialog()
473 wallet = Wallet.from_seed(text, self.storage)
474 wallet.add_seed(text, password)
475 wallet.create_accounts(password)
476 elif Wallet.is_xprv(text):
477 password = self.password_dialog()
478 wallet = Wallet.from_xprv(text, password, self.storage)
479 elif Wallet.is_old_mpk(text):
480 wallet = Wallet.from_old_mpk(text, self.storage)
481 elif Wallet.is_xpub(text):
482 wallet = Wallet.from_xpub(text, self.storage)
483 elif Wallet.is_address(text):
484 wallet = Wallet.from_address(text, self.storage)
485 elif Wallet.is_private_key(text):
486 wallet = Wallet.from_private_key(text, self.storage)
491 r = self.multi_seed_dialog(1)
495 wallet = Wallet_2of2(self.storage)
496 if Wallet.is_seed(text1) or Wallet.is_seed(text2):
497 password = self.password_dialog()
501 if Wallet.is_seed(text1):
502 wallet.add_seed(text1, password)
503 if Wallet.is_seed(text2):
504 wallet.add_cold_seed(text2, password)
506 wallet.add_master_public_key("cold/", text2)
508 assert Wallet.is_xpub(text1)
509 if Wallet.is_seed(text2):
510 wallet.add_seed(text2, password)
511 wallet.add_master_public_key("cold/", text1)
513 wallet.add_master_public_key("m/", text1)
514 wallet.add_master_public_key("cold/", text2)
516 wallet.create_accounts(password)
520 r = self.multi_seed_dialog(2)
523 text1, text2, text3 = r
524 wallet = Wallet_2of3(self.storage)
525 if Wallet.is_seed(text1) or Wallet.is_seed(text2) or Wallet.is_seed(text3):
526 password = self.password_dialog()
530 if Wallet.is_seed(text1):
531 wallet.add_seed(text1, password)
532 if Wallet.is_seed(text2):
533 wallet.add_cold_seed(text2, password)
535 wallet.add_master_public_key("cold/", text2)
537 elif Wallet.is_xpub(text1):
538 if Wallet.is_seed(text2):
539 wallet.add_seed(text2, password)
540 wallet.add_master_public_key("cold/", text1)
542 wallet.add_master_public_key("m/", text1)
543 wallet.add_master_public_key("cold/", text2)
545 wallet.create_accounts(password)
548 wallet = run_hook('installwizard_restore', self, self.storage)
552 # create first keys offline
553 self.waiting_dialog(wallet.synchronize)