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)
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_mpk(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_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_mpk(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 xpub = self.multi_mpk_dialog(xpub_hot, 1)
442 wallet.add_master_public_key("cold/", xpub)
443 wallet.create_account()
444 self.waiting_dialog(wallet.synchronize)
447 if action == 'create_2of3_2':
448 xpub_hot = wallet.master_public_keys.get("m/")
449 r = self.multi_mpk_dialog(xpub_hot, 2)
453 wallet.add_master_public_key("cold/", xpub1)
454 wallet.add_master_public_key("remote/", xpub2)
455 wallet.create_account()
456 self.waiting_dialog(wallet.synchronize)
459 if action == 'create_2fa_3':
460 run_hook('create_remote_key', wallet, self)
461 if not wallet.master_public_keys.get("remote/"):
463 wallet.create_account()
464 self.waiting_dialog(wallet.synchronize)
467 if action == 'restore':
470 text = self.enter_seed_dialog(MSG_ENTER_ANYTHING, None)
473 if Wallet.is_seed(text):
474 password = self.password_dialog()
475 wallet = Wallet.from_seed(text, self.storage)
476 wallet.add_seed(text, password)
477 wallet.create_accounts(password)
478 elif Wallet.is_mpk(text):
479 wallet = Wallet.from_mpk(text, self.storage)
480 elif Wallet.is_address(text):
481 wallet = Wallet.from_address(text, self.storage)
482 elif Wallet.is_private_key(text):
483 wallet = Wallet.from_private_key(text, self.storage)
487 elif t in ['2fa', '2of2']:
488 r = self.multi_seed_dialog(1)
492 password = self.password_dialog()
494 wallet = Wallet_2of2(self.storage)
496 wallet = Wallet_2of3(self.storage)
498 wallet = Wallet_2of3(self.storage)
500 if Wallet.is_seed(text1):
501 wallet.add_seed(text1, password)
502 if Wallet.is_seed(text2):
503 wallet.add_cold_seed(text2, password)
505 wallet.add_master_public_key("cold/", text2)
507 elif Wallet.is_mpk(text1):
508 if Wallet.is_seed(text2):
509 wallet.add_seed(text2, password)
510 wallet.add_master_public_key("cold/", text1)
512 wallet.add_master_public_key("m/", text1)
513 wallet.add_master_public_key("cold/", text2)
516 run_hook('restore_third_key', wallet, self)
518 wallet.create_account()
521 r = self.multi_seed_dialog(2)
524 text1, text2, text3 = r
525 password = self.password_dialog()
526 wallet = Wallet_2of3(self.storage)
528 if Wallet.is_seed(text1):
529 wallet.add_seed(text1, password)
530 if Wallet.is_seed(text2):
531 wallet.add_cold_seed(text2, password)
533 wallet.add_master_public_key("cold/", text2)
535 elif Wallet.is_mpk(text1):
536 if Wallet.is_seed(text2):
537 wallet.add_seed(text2, password)
538 wallet.add_master_public_key("cold/", text1)
540 wallet.add_master_public_key("m/", text1)
541 wallet.add_master_public_key("cold/", text2)
543 wallet.create_account()
550 #if not self.config.get('server'):
552 if self.network.interfaces:
553 self.network_dialog()
555 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
559 # start wallet threads
560 wallet.start_threads(self.network)
562 if action == 'restore':
564 self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
567 if wallet.is_found():
568 QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
570 QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
572 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))