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 import electrum.bitcoin as bitcoin
10 from network_dialog import NetworkDialog
12 from amountedit import AmountEdit
16 from electrum.plugins import run_hook
19 MSG_ENTER_ANYTHING = _("Please enter a wallet seed, a master public key, a list of Bitcoin addresses, or a list of private keys")
20 MSG_SHOW_MPK = _("This is your master public key")
21 MSG_ENTER_MPK = _("Please enter your master public key")
22 MSG_ENTER_COLD_MPK = _("Please enter the master public key of your cosigner wallet")
23 MSG_ENTER_SEED_OR_MPK = _("Please enter a wallet seed, or master public key")
24 MSG_VERIFY_SEED = _("Your seed is important!") + "\n" + _("To make sure that you have properly saved your seed, please retype it here.")
27 class InstallWizard(QDialog):
29 def __init__(self, config, network, storage):
30 QDialog.__init__(self)
32 self.network = network
33 self.storage = storage
34 self.setMinimumSize(575, 400)
35 self.setWindowTitle('Electrum')
36 self.connect(self, QtCore.SIGNAL('accept'), self.accept)
38 self.stack = QStackedLayout()
39 self.setLayout(self.stack)
42 def set_layout(self, layout):
45 self.stack.setCurrentIndex(self.stack.addWidget(w))
48 def restore_or_create(self):
52 main_label = QLabel(_("Electrum could not find an existing wallet."))
53 vbox.addWidget(main_label)
58 label = QLabel(_("What do you want to do?"))
59 label.setWordWrap(True)
60 grid.addWidget(label, 0, 0)
63 grid.addWidget(gb1, 0, 0)
65 group1 = QButtonGroup()
67 b1 = QRadioButton(gb1)
68 b1.setText(_("Create new wallet"))
71 b2 = QRadioButton(gb1)
72 b2.setText(_("Restore an existing wallet"))
77 grid.addWidget(b1, 1, 0)
78 grid.addWidget(b2, 2, 0)
84 class ClickableLabel(QLabel):
85 def mouseReleaseEvent(self, ev):
86 self.emit(SIGNAL('clicked()'))
88 label2 = ClickableLabel(_("Wallet type:") + " [+]")
90 hbox.addWidget(label2)
91 grid2.addLayout(hbox, 3, 0)
94 grid.addWidget(gb2, 3, 0)
96 group2 = QButtonGroup()
98 bb1 = QRadioButton(gb2)
99 bb1.setText(_("Standard wallet"))
102 bb2 = QRadioButton(gb2)
103 bb2.setText(_("Wallet with two-factor authentication (plugin)"))
105 bb3 = QRadioButton(gb2)
106 bb3.setText(_("Multisig wallet (2 of 2)"))
109 bb4 = QRadioButton(gb2)
110 bb4.setText(_("Multisig wallet (2 of 3)"))
113 grid2.addWidget(bb1, 4, 0)
114 grid2.addWidget(bb2, 5, 0)
115 grid2.addWidget(bb3, 6, 0)
116 grid2.addWidget(bb4, 7, 0)
119 x = not bb3.isHidden()
120 label2.setText(_("Wallet type:") + (' [+]' if x else ' [-]'))
124 self.connect(label2, SIGNAL('clicked()'), toggle)
126 grid2.addWidget(label2)
128 group2.addButton(bb1)
129 group2.addButton(bb2)
130 group2.addButton(bb3)
131 group2.addButton(bb4)
133 vbox.addLayout(grid2)
135 hbox, button = ok_cancel_buttons2(self, _('Next'))
137 self.set_layout(vbox)
140 button.setDefault(True)
145 action = 'create' if b1.isChecked() else 'restore'
149 elif bb2.isChecked():
151 elif bb3.isChecked():
153 elif bb4.isChecked():
159 def verify_seed(self, seed, sid):
160 r = self.enter_seed_dialog(MSG_VERIFY_SEED, sid)
165 QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
171 def get_seed_text(self, seed_e):
172 text = unicode(seed_e.toPlainText()).strip()
173 text = ' '.join(text.split())
177 def is_any(self, seed_e):
178 text = self.get_seed_text(seed_e)
179 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)
181 def is_mpk(self, seed_e):
182 text = self.get_seed_text(seed_e)
183 return Wallet.is_xpub(text) or Wallet.is_old_mpk(text)
186 def enter_seed_dialog(self, msg, sid):
187 vbox, seed_e = seed_dialog.enter_seed_box(msg, sid)
189 hbox, button = ok_cancel_buttons2(self, _('Next'))
191 button.setEnabled(False)
192 seed_e.textChanged.connect(lambda: button.setEnabled(self.is_any(seed_e)))
193 self.set_layout(vbox)
196 return self.get_seed_text(seed_e)
199 def multi_mpk_dialog(self, xpub_hot, n):
201 vbox0, seed_e0 = seed_dialog.enter_seed_box(MSG_SHOW_MPK, 'hot')
202 vbox.addLayout(vbox0)
203 seed_e0.setText(xpub_hot)
204 seed_e0.setReadOnly(True)
207 vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, 'cold')
208 vbox.addLayout(vbox2)
209 entries.append(seed_e2)
211 hbox, button = ok_cancel_buttons2(self, _('Next'))
213 button.setEnabled(False)
214 f = lambda: button.setEnabled( map(lambda e: self.is_xpub(e), entries) == [True]*len(entries))
216 e.textChanged.connect(f)
217 self.set_layout(vbox)
220 return map(lambda e: self.get_seed_text(e), entries)
223 def multi_seed_dialog(self, n):
225 vbox1, seed_e1 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'hot')
226 vbox.addLayout(vbox1)
229 vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'cold')
230 vbox.addLayout(vbox2)
231 entries.append(seed_e2)
233 hbox, button = ok_cancel_buttons2(self, _('Next'))
235 button.setEnabled(False)
237 f = lambda: button.setEnabled( map(lambda e: self.is_any(e), entries) == [True]*len(entries))
239 e.textChanged.connect(f)
241 self.set_layout(vbox)
244 return map(lambda e: self.get_seed_text(e), entries)
250 def waiting_dialog(self, task, msg= _("Electrum is generating your addresses, please wait.")):
253 self.emit(QtCore.SIGNAL('accept'))
256 self.waiting_label = QLabel(msg)
257 vbox.addWidget(self.waiting_label)
258 self.set_layout(vbox)
259 t = threading.Thread(target = target)
266 def network_dialog(self):
271 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" \
272 + _("How do you want to connect to a server:")+" ")
273 label.setWordWrap(True)
274 grid.addWidget(label, 0, 0)
278 b1 = QRadioButton(gb)
279 b1.setText(_("Auto connect"))
282 b2 = QRadioButton(gb)
283 b2.setText(_("Select server manually"))
285 #b3 = QRadioButton(gb)
286 #b3.setText(_("Stay offline"))
288 grid.addWidget(b1,1,0)
289 grid.addWidget(b2,2,0)
290 #grid.addWidget(b3,3,0)
296 vbox.addLayout(ok_cancel_buttons(self, _('Next')))
298 self.set_layout(vbox)
303 return NetworkDialog(self.network, self.config, None).do_exec()
306 self.config.set_key('auto_cycle', True, True)
310 self.config.set_key("server", None, True)
311 self.config.set_key('auto_cycle', False, True)
315 def show_message(self, msg, icon=None):
317 self.set_layout(vbox)
322 vbox.addWidget(QLabel(msg))
324 vbox.addLayout(close_button(self, _('Next')))
329 def question(self, msg, icon=None):
331 self.set_layout(vbox)
336 vbox.addWidget(QLabel(msg))
338 vbox.addLayout(ok_cancel_buttons(self, _('OK')))
344 def show_seed(self, seed, sid):
345 vbox = seed_dialog.show_seed_box(seed, sid)
346 vbox.addLayout(ok_cancel_buttons(self, _("Next")))
347 self.set_layout(vbox)
351 def password_dialog(self):
352 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
353 +_("Leave these fields empty if you want to disable encryption.")
354 from password_dialog import make_password_dialog, run_password_dialog
355 self.set_layout( make_password_dialog(self, None, msg) )
356 return run_password_dialog(self, None, self)[2]
359 def create_cold_seed(self, wallet):
360 from electrum.bitcoin import mnemonic_to_seed, bip32_root
361 msg = _('You are about to generate the cold storage seed of your wallet.') + '\n' \
362 + _('For safety, you should do this on an offline computer.')
363 icon = QPixmap( ':icons/cold_seed.png').scaledToWidth(56)
364 if not self.question(msg, icon):
367 cold_seed = wallet.make_seed()
368 if not self.show_seed(cold_seed, 'cold'):
370 if not self.verify_seed(cold_seed, 'cold'):
373 hex_seed = mnemonic_to_seed(cold_seed,'').encode('hex')
374 xpriv, xpub = bip32_root(hex_seed)
375 wallet.add_master_public_key('cold/', xpub)
377 msg = _('Your master public key was saved in your wallet file.') + '\n'\
378 + _('Your cold seed must be stored on paper; it is not in the wallet file.')+ '\n\n' \
379 + _('This program is about to close itself.') + '\n'\
380 + _('You will need to reopen your wallet on an online computer, in order to complete the creation of your wallet')
381 self.show_message(msg)
385 def run(self, action):
388 action, t = self.restore_or_create()
393 if action == 'create':
395 wallet = Wallet(self.storage)
398 wallet = Wallet_2of3(self.storage)
399 run_hook('create_cold_seed', wallet, self)
400 self.create_cold_seed(wallet)
404 wallet = Wallet_2of2(self.storage)
405 action = 'create_2of2_1'
408 wallet = Wallet_2of3(self.storage)
409 action = 'create_2of3_1'
412 if action in ['create_2fa_2', 'create_2of3_2']:
413 wallet = Wallet_2of3(self.storage)
415 if action in ['create_2of2_2']:
416 wallet = Wallet_2of2(self.storage)
418 if action in ['create', 'create_2of2_1', 'create_2fa_2', 'create_2of3_1']:
419 seed = wallet.make_seed()
420 sid = None if action == 'create' else 'hot'
421 if not self.show_seed(seed, sid):
423 if not self.verify_seed(seed, sid):
425 password = self.password_dialog()
426 wallet.add_seed(seed, password)
427 if action == 'create':
428 wallet.create_accounts(password)
429 self.waiting_dialog(wallet.synchronize)
430 elif action == 'create_2of2_1':
431 action = 'create_2of2_2'
432 elif action == 'create_2of3_1':
433 action = 'create_2of3_2'
434 elif action == 'create_2fa_2':
435 action = 'create_2fa_3'
437 if action == 'create_2of2_2':
438 xpub_hot = wallet.master_public_keys.get("m/")
439 r = self.multi_mpk_dialog(xpub_hot, 1)
443 wallet.add_master_public_key("cold/", xpub_cold)
444 wallet.create_account()
445 self.waiting_dialog(wallet.synchronize)
448 if action == 'create_2of3_2':
449 xpub_hot = wallet.master_public_keys.get("m/")
450 r = self.multi_mpk_dialog(xpub_hot, 2)
454 wallet.add_master_public_key("cold/", xpub1)
455 wallet.add_master_public_key("remote/", xpub2)
456 wallet.create_account()
457 self.waiting_dialog(wallet.synchronize)
460 if action == 'create_2fa_3':
461 run_hook('create_remote_key', wallet, self)
462 if not wallet.master_public_keys.get("remote/"):
464 wallet.create_account()
465 self.waiting_dialog(wallet.synchronize)
468 if action == 'restore':
471 text = self.enter_seed_dialog(MSG_ENTER_ANYTHING, None)
474 if Wallet.is_seed(text):
475 password = self.password_dialog()
476 wallet = Wallet.from_seed(text, self.storage)
477 wallet.add_seed(text, password)
478 wallet.create_accounts(password)
479 elif Wallet.is_xprv(text):
480 password = self.password_dialog()
481 wallet = Wallet.from_xprv(text, password, self.storage)
482 elif Wallet.is_old_mpk(text):
483 wallet = Wallet.from_old_mpk(text, self.storage)
484 elif Wallet.is_xpub(text):
485 wallet = Wallet.from_xpub(text, self.storage)
486 elif Wallet.is_address(text):
487 wallet = Wallet.from_address(text, self.storage)
488 elif Wallet.is_private_key(text):
489 wallet = Wallet.from_private_key(text, self.storage)
493 elif t in ['2fa', '2of2']:
494 r = self.multi_seed_dialog(1)
498 password = self.password_dialog()
500 wallet = Wallet_2of2(self.storage)
502 wallet = Wallet_2of3(self.storage)
504 wallet = Wallet_2of3(self.storage)
506 if Wallet.is_seed(text1):
507 wallet.add_seed(text1, password)
508 if Wallet.is_seed(text2):
509 wallet.add_cold_seed(text2, password)
511 wallet.add_master_public_key("cold/", text2)
513 elif Wallet.is_mpk(text1):
514 if Wallet.is_seed(text2):
515 wallet.add_seed(text2, password)
516 wallet.add_master_public_key("cold/", text1)
518 wallet.add_master_public_key("m/", text1)
519 wallet.add_master_public_key("cold/", text2)
522 run_hook('restore_third_key', wallet, self)
524 wallet.create_account()
527 r = self.multi_seed_dialog(2)
530 text1, text2, text3 = r
531 password = self.password_dialog()
532 wallet = Wallet_2of3(self.storage)
534 if Wallet.is_seed(text1):
535 wallet.add_seed(text1, password)
536 if Wallet.is_seed(text2):
537 wallet.add_cold_seed(text2, password)
539 wallet.add_master_public_key("cold/", text2)
541 elif Wallet.is_mpk(text1):
542 if Wallet.is_seed(text2):
543 wallet.add_seed(text2, password)
544 wallet.add_master_public_key("cold/", text1)
546 wallet.add_master_public_key("m/", text1)
547 wallet.add_master_public_key("cold/", text2)
549 wallet.create_account()
556 #if not self.config.get('server'):
558 if self.network.interfaces:
559 self.network_dialog()
561 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
565 # start wallet threads
566 wallet.start_threads(self.network)
568 if action == 'restore':
570 self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
573 if wallet.is_found():
574 QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
576 QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
578 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))