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', 'create_2of2_1', 'create_2fa_2', 'create_2of3_1']:
416 seed = wallet.make_seed()
417 sid = None if action == 'create' else 'hot'
418 if not self.show_seed(seed, sid):
420 if not self.verify_seed(seed, sid):
422 password = self.password_dialog()
423 wallet.add_seed(seed, password)
424 if action == 'create':
425 wallet.create_accounts(password)
426 self.waiting_dialog(wallet.synchronize)
427 elif action == 'create_2of2_1':
428 action = 'create_2of2_2'
429 elif action == 'create_2of3_1':
430 action = 'create_2of3_2'
431 elif action == 'create_2fa_2':
432 action = 'create_2fa_3'
434 if action == 'create_2of2_2':
435 xpub_hot = wallet.master_public_keys.get("m/")
436 xpub = self.multi_mpk_dialog(xpub_hot, 1)
439 wallet.add_master_public_key("cold/", xpub)
440 wallet.create_account()
441 self.waiting_dialog(wallet.synchronize)
444 if action == 'create_2of3_2':
445 xpub_hot = wallet.master_public_keys.get("m/")
446 r = self.multi_mpk_dialog(xpub_hot, 2)
450 wallet.add_master_public_key("cold/", xpub1)
451 wallet.add_master_public_key("remote/", xpub2)
452 wallet.create_account()
453 self.waiting_dialog(wallet.synchronize)
456 if action == 'create_2fa_3':
457 run_hook('create_remote_key', wallet, self)
458 if not wallet.master_public_keys.get("remote/"):
460 wallet.create_account()
461 self.waiting_dialog(wallet.synchronize)
464 if action == 'restore':
467 text = self.enter_seed_dialog(MSG_ENTER_ANYTHING, None)
470 if Wallet.is_seed(text):
471 password = self.password_dialog()
472 wallet = Wallet.from_seed(text, self.storage)
473 wallet.add_seed(text, password)
474 wallet.create_accounts(password)
475 elif Wallet.is_mpk(text):
476 wallet = Wallet.from_mpk(text, self.storage)
477 elif Wallet.is_address(text):
478 wallet = Wallet.from_address(text, self.storage)
479 elif Wallet.is_private_key(text):
480 wallet = Wallet.from_private_key(text, self.storage)
484 elif t in ['2fa', '2of2']:
485 r = self.multi_seed_dialog(1)
489 password = self.password_dialog()
491 wallet = Wallet_2of2(self.storage)
493 wallet = Wallet_2of3(self.storage)
495 wallet = Wallet_2of3(self.storage)
497 if Wallet.is_seed(text1):
498 wallet.add_seed(text1, password)
499 if Wallet.is_seed(text2):
500 wallet.add_cold_seed(text2, password)
502 wallet.add_master_public_key("cold/", text2)
504 elif Wallet.is_mpk(text1):
505 if Wallet.is_seed(text2):
506 wallet.add_seed(text2, password)
507 wallet.add_master_public_key("cold/", text1)
509 wallet.add_master_public_key("m/", text1)
510 wallet.add_master_public_key("cold/", text2)
513 run_hook('restore_third_key', wallet, self)
515 wallet.create_account()
518 r = self.multi_seed_dialog(2)
521 text1, text2, text3 = r
522 password = self.password_dialog()
523 wallet = Wallet_2of3(self.storage)
525 if Wallet.is_seed(text1):
526 wallet.add_seed(text1, password)
527 if Wallet.is_seed(text2):
528 wallet.add_cold_seed(text2, password)
530 wallet.add_master_public_key("cold/", text2)
532 elif Wallet.is_mpk(text1):
533 if Wallet.is_seed(text2):
534 wallet.add_seed(text2, password)
535 wallet.add_master_public_key("cold/", text1)
537 wallet.add_master_public_key("m/", text1)
538 wallet.add_master_public_key("cold/", text2)
540 wallet.create_account()
547 #if not self.config.get('server'):
549 if self.network.interfaces:
550 self.network_dialog()
552 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
556 # start wallet threads
557 wallet.start_threads(self.network)
559 if action == 'restore':
561 self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
564 if wallet.is_found():
565 QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
567 QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
569 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))