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 cosigning 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 vbox.addLayout(ok_cancel_buttons(self, _('Next')))
137 self.set_layout(vbox)
141 action = 'create' if b1.isChecked() else 'restore'
145 elif bb2.isChecked():
147 elif bb3.isChecked():
149 elif bb4.isChecked():
155 def verify_seed(self, seed, sid):
156 r = self.enter_seed_dialog(MSG_VERIFY_SEED, sid)
161 QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
167 def get_seed_text(self, seed_e):
168 text = unicode(seed_e.toPlainText()).strip()
169 text = ' '.join(text.split())
173 def is_any(self, seed_e):
174 text = self.get_seed_text(seed_e)
175 return Wallet.is_seed(text) or Wallet.is_mpk(text) or Wallet.is_address(text) or Wallet.is_private_key(text)
177 def is_mpk(self, seed_e):
178 text = self.get_seed_text(seed_e)
179 return Wallet.is_mpk(text)
182 def enter_seed_dialog(self, msg, sid):
183 vbox, seed_e = seed_dialog.enter_seed_box(msg, sid)
185 hbox, button = ok_cancel_buttons2(self, _('Next'))
187 button.setEnabled(False)
188 seed_e.textChanged.connect(lambda: button.setEnabled(self.is_any(seed_e)))
189 self.set_layout(vbox)
192 return self.get_seed_text(seed_e)
195 def multi_mpk_dialog(self, xpub_hot, n):
197 vbox0, seed_e0 = seed_dialog.enter_seed_box(MSG_SHOW_MPK, 'hot')
198 vbox.addLayout(vbox0)
199 seed_e0.setText(xpub_hot)
200 seed_e0.setReadOnly(True)
203 vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, 'cold')
204 vbox.addLayout(vbox2)
205 entries.append(seed_e2)
207 hbox, button = ok_cancel_buttons2(self, _('Next'))
209 button.setEnabled(False)
210 f = lambda: button.setEnabled( map(lambda e: self.is_mpk(e), entries) == [True]*len(entries))
212 e.textChanged.connect(f)
213 self.set_layout(vbox)
216 return map(lambda e: self.get_seed_text(e), entries)
219 def multi_seed_dialog(self, n):
221 vbox1, seed_e1 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'hot')
222 vbox.addLayout(vbox1)
225 vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'cold')
226 vbox.addLayout(vbox2)
227 entries.append(seed_e2)
229 hbox, button = ok_cancel_buttons2(self, _('Next'))
231 button.setEnabled(False)
233 f = lambda: button.setEnabled( map(lambda e: self.is_any(e), entries) == [True]*len(entries))
235 e.textChanged.connect(f)
237 self.set_layout(vbox)
240 return map(lambda e: self.get_seed_text(e), entries)
246 def waiting_dialog(self, task, msg= _("Electrum is generating your addresses, please wait.")):
249 self.emit(QtCore.SIGNAL('accept'))
252 self.waiting_label = QLabel(msg)
253 vbox.addWidget(self.waiting_label)
254 self.set_layout(vbox)
255 t = threading.Thread(target = target)
262 def network_dialog(self):
267 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" \
268 + _("How do you want to connect to a server:")+" ")
269 label.setWordWrap(True)
270 grid.addWidget(label, 0, 0)
274 b1 = QRadioButton(gb)
275 b1.setText(_("Auto connect"))
278 b2 = QRadioButton(gb)
279 b2.setText(_("Select server manually"))
281 #b3 = QRadioButton(gb)
282 #b3.setText(_("Stay offline"))
284 grid.addWidget(b1,1,0)
285 grid.addWidget(b2,2,0)
286 #grid.addWidget(b3,3,0)
292 vbox.addLayout(ok_cancel_buttons(self, _('Next')))
294 self.set_layout(vbox)
299 return NetworkDialog(self.network, self.config, None).do_exec()
302 self.config.set_key('auto_cycle', True, True)
306 self.config.set_key("server", None, True)
307 self.config.set_key('auto_cycle', False, True)
311 def show_message(self, msg, icon=None):
313 self.set_layout(vbox)
318 vbox.addWidget(QLabel(msg))
320 vbox.addLayout(close_button(self, _('Next')))
325 def question(self, msg, icon=None):
327 self.set_layout(vbox)
332 vbox.addWidget(QLabel(msg))
334 vbox.addLayout(ok_cancel_buttons(self, _('OK')))
340 def show_seed(self, seed, sid):
341 vbox = seed_dialog.show_seed_box(seed, sid)
342 vbox.addLayout(ok_cancel_buttons(self, _("Next")))
343 self.set_layout(vbox)
347 def password_dialog(self):
348 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
349 +_("Leave these fields empty if you want to disable encryption.")
350 from password_dialog import make_password_dialog, run_password_dialog
351 self.set_layout( make_password_dialog(self, None, msg) )
352 return run_password_dialog(self, None, self)[2]
355 def create_cold_seed(self, wallet):
356 from electrum.bitcoin import mnemonic_to_seed, bip32_root
357 msg = _('You are about to generate the cold storage seed of your wallet.') + '\n' \
358 + _('For safety, you should do this on an offline computer.')
359 icon = QPixmap( ':icons/cold_seed.png').scaledToWidth(56)
360 if not self.question(msg, icon):
363 cold_seed = wallet.make_seed()
364 if not self.show_seed(cold_seed, 'cold'):
366 if not self.verify_seed(cold_seed, 'cold'):
369 hex_seed = mnemonic_to_seed(cold_seed,'').encode('hex')
370 xpriv, xpub = bip32_root(hex_seed)
371 wallet.add_master_public_key('cold/', xpub)
373 msg = _('Your master public key was saved in your wallet file.') + '\n'\
374 + _('Your cold seed must be stored on paper; it is not in the wallet file.')+ '\n\n' \
375 + _('This program is about to close itself.') + '\n'\
376 + _('You will need to reopen your wallet on an online computer, in order to complete the creation of your wallet')
377 self.show_message(msg)
381 def run(self, action):
384 action, t = self.restore_or_create()
389 if action == 'create':
391 wallet = Wallet(self.storage)
394 wallet = Wallet_2of3(self.storage)
395 run_hook('create_cold_seed', wallet, self)
396 self.create_cold_seed(wallet)
400 wallet = Wallet_2of2(self.storage)
401 action = 'create_2of2_1'
404 wallet = Wallet_2of3(self.storage)
405 action = 'create_2of3_1'
408 if action in ['create_2fa_2', 'create_2of3_2']:
409 wallet = Wallet_2of3(self.storage)
411 if action in ['create', 'create_2of2_1', 'create_2fa_2', 'create_2of3_1']:
412 seed = wallet.make_seed()
413 sid = None if action == 'create' else 'hot'
414 if not self.show_seed(seed, sid):
416 if not self.verify_seed(seed, sid):
418 password = self.password_dialog()
419 wallet.add_seed(seed, password)
420 if action == 'create':
421 wallet.create_accounts(password)
422 self.waiting_dialog(wallet.synchronize)
423 elif action == 'create_2of2_1':
424 action = 'create_2of2_2'
425 elif action == 'create_2of3_1':
426 action = 'create_2of3_2'
427 elif action == 'create_2fa_2':
428 action = 'create_2fa_3'
430 if action == 'create_2of2_2':
431 xpub_hot = wallet.master_public_keys.get("m/")
432 xpub = self.multi_mpk_dialog(xpub_hot, 1)
435 wallet.add_master_public_key("cold/", xpub)
436 wallet.create_account()
437 self.waiting_dialog(wallet.synchronize)
440 if action == 'create_2of3_2':
441 xpub_hot = wallet.master_public_keys.get("m/")
442 r = self.multi_mpk_dialog(xpub_hot, 2)
446 wallet.add_master_public_key("cold/", xpub1)
447 wallet.add_master_public_key("remote/", xpub2)
448 wallet.create_account()
449 self.waiting_dialog(wallet.synchronize)
452 if action == 'create_2fa_3':
453 run_hook('create_remote_key', wallet, self)
454 if not wallet.master_public_keys.get("remote/"):
456 wallet.create_account()
457 self.waiting_dialog(wallet.synchronize)
460 if action == 'restore':
463 text = self.enter_seed_dialog(MSG_ENTER_ANYTHING, None)
466 if Wallet.is_seed(text):
467 password = self.password_dialog()
468 wallet = Wallet.from_seed(text, self.storage)
469 wallet.add_seed(text, password)
470 wallet.create_accounts(password)
471 elif Wallet.is_mpk(text):
472 wallet = Wallet.from_mpk(text, self.storage)
473 elif Wallet.is_address(text):
474 wallet = Wallet.from_address(text, self.storage)
475 elif Wallet.is_private_key(text):
476 wallet = Wallet.from_private_key(text, self.storage)
480 elif t in ['2fa', '2of2']:
481 r = self.multi_seed_dialog(1)
485 password = self.password_dialog()
487 wallet = Wallet_2of2(self.storage)
489 wallet = Wallet_2of3(self.storage)
491 wallet = Wallet_2of3(self.storage)
493 if Wallet.is_seed(text1):
494 wallet.add_seed(text1, password)
495 if Wallet.is_seed(text2):
496 wallet.add_cold_seed(text2, password)
498 wallet.add_master_public_key("cold/", text2)
500 elif Wallet.is_mpk(text1):
501 if Wallet.is_seed(text2):
502 wallet.add_seed(text2, password)
503 wallet.add_master_public_key("cold/", text1)
505 wallet.add_master_public_key("m/", text1)
506 wallet.add_master_public_key("cold/", text2)
509 run_hook('restore_third_key', wallet, self)
511 wallet.create_account()
514 r = self.multi_seed_dialog(2)
517 text1, text2, text3 = r
518 password = self.password_dialog()
519 wallet = Wallet_2of3(self.storage)
521 if Wallet.is_seed(text1):
522 wallet.add_seed(text1, password)
523 if Wallet.is_seed(text2):
524 wallet.add_cold_seed(text2, password)
526 wallet.add_master_public_key("cold/", text2)
528 elif Wallet.is_mpk(text1):
529 if Wallet.is_seed(text2):
530 wallet.add_seed(text2, password)
531 wallet.add_master_public_key("cold/", text1)
533 wallet.add_master_public_key("m/", text1)
534 wallet.add_master_public_key("cold/", text2)
536 wallet.create_account()
543 #if not self.config.get('server'):
545 if self.network.interfaces:
546 self.network_dialog()
548 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
552 # start wallet threads
553 wallet.start_threads(self.network)
555 if action == 'restore':
557 self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
560 if wallet.is_found():
561 QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
563 QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
565 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))