hooks and workflow for 2of3 wallets
[electrum-nvc.git] / gui / qt / installwizard.py
1 from PyQt4.QtGui import *
2 from PyQt4.QtCore import *
3 import PyQt4.QtCore as QtCore
4
5 from electrum.i18n import _
6 from electrum import Wallet, Wallet_2of3
7
8 from seed_dialog import SeedDialog
9 from network_dialog import NetworkDialog
10 from util import *
11 from amountedit import AmountEdit
12
13 import sys
14 import threading
15 from electrum.plugins import run_hook
16
17 class InstallWizard(QDialog):
18
19     def __init__(self, config, network, storage):
20         QDialog.__init__(self)
21         self.config = config
22         self.network = network
23         self.storage = storage
24         self.setMinimumSize(575, 400)
25         self.setWindowTitle('Electrum')
26         self.connect(self, QtCore.SIGNAL('accept'), self.accept)
27
28         self.stack = QStackedLayout()
29         self.setLayout(self.stack)
30
31
32     def set_layout(self, layout):
33         w = QWidget()
34         w.setLayout(layout)
35         self.stack.setCurrentIndex(self.stack.addWidget(w))
36
37
38     def restore_or_create(self):
39
40         grid = QGridLayout()
41         grid.setSpacing(5)
42
43         msg = _("Electrum could not find an existing wallet.") + "\n\n" \
44             + _("What do you want to do?") + "\n"
45         label = QLabel(msg)
46         label.setWordWrap(True)
47         grid.addWidget(label, 0, 0)
48
49         gb = QGroupBox()
50
51         b1 = QRadioButton(gb)
52         b1.setText(_("Create new wallet"))
53         b1.setChecked(True)
54
55         b2 = QRadioButton(gb)
56         b2.setText(_("Restore an existing wallet from its seed"))
57
58         b3 = QRadioButton(gb)
59         b3.setText(_("Create a watching-only version of an existing wallet"))
60
61         grid.addWidget(b1,1,0)
62         grid.addWidget(b2,2,0)
63         grid.addWidget(b3,3,0)
64
65         vbox = QVBoxLayout()
66         self.set_layout(vbox)
67
68         vbox.addLayout(grid)
69         vbox.addStretch(1)
70         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
71
72         if not self.exec_():
73             return
74         
75         if b1.isChecked():
76             answer = 'create'
77         elif b2.isChecked():
78             answer = 'restore'
79         else:
80             answer = 'watching'
81
82         return answer
83
84
85
86
87
88     def verify_seed(self, seed):
89         r = self.seed_dialog(False)
90         if not r:
91             return
92
93         if r != seed:
94             QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
95             return False
96         else:
97             return True
98
99
100     def seed_dialog(self, is_restore=True):
101
102         vbox = QVBoxLayout()
103         if is_restore:
104             msg = _("Please enter your wallet seed.") + "\n"
105         else:
106             msg = _("Your seed is important!") \
107                 + "\n" + _("To make sure that you have properly saved your seed, please retype it here.")
108         
109         logo = QLabel()
110         logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
111         logo.setMaximumWidth(60)
112
113         label = QLabel(msg)
114         label.setWordWrap(True)
115
116         seed_e = QTextEdit()
117         seed_e.setMaximumHeight(100)
118
119         vbox.addWidget(label)
120
121         grid = QGridLayout()
122         grid.addWidget(logo, 0, 0)
123         grid.addWidget(seed_e, 0, 1)
124
125         vbox.addLayout(grid)
126
127         vbox.addStretch(1)
128         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
129
130         self.set_layout(vbox)
131         if not self.exec_():
132             return
133
134         seed = seed_e.toPlainText()
135         seed = unicode(seed.toLower())
136
137         if not seed:
138             QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
139             return
140
141         if not Wallet.is_seed(seed):
142             QMessageBox.warning(None, _('Error'), _('Invalid seed'), _('OK'))
143             return
144
145         return seed
146
147
148
149     def waiting_dialog(self, task, msg= _("Electrum is generating your addresses, please wait.")):
150         def target():
151             task()
152             self.emit(QtCore.SIGNAL('accept'))
153
154         vbox = QVBoxLayout()
155         self.waiting_label = QLabel(msg)
156         vbox.addWidget(self.waiting_label)
157         self.set_layout(vbox)
158         t = threading.Thread(target = target)
159         t.start()
160         self.exec_()
161
162
163
164     def mpk_dialog(self):
165
166         vbox = QVBoxLayout()
167         vbox.addWidget(QLabel(_("Please enter your master public key.")))
168
169         grid = QGridLayout()
170         grid.setSpacing(8)
171
172         label = QLabel(_("Key")) 
173         grid.addWidget(label, 0, 0)
174         mpk_e = QTextEdit()
175         mpk_e.setMaximumHeight(100)
176         grid.addWidget(mpk_e, 0, 1)
177
178         vbox.addLayout(grid)
179
180         vbox.addStretch(1)
181         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
182
183         self.set_layout(vbox)
184         if not self.exec_(): 
185             return None
186
187         mpk = str(mpk_e.toPlainText()).strip()
188         return mpk
189
190
191     def network_dialog(self):
192         
193         grid = QGridLayout()
194         grid.setSpacing(5)
195
196         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" \
197                       + _("How do you want to connect to a server:")+" ")
198         label.setWordWrap(True)
199         grid.addWidget(label, 0, 0)
200
201         gb = QGroupBox()
202
203         b1 = QRadioButton(gb)
204         b1.setText(_("Auto connect"))
205         b1.setChecked(True)
206
207         b2 = QRadioButton(gb)
208         b2.setText(_("Select server manually"))
209
210         #b3 = QRadioButton(gb)
211         #b3.setText(_("Stay offline"))
212
213         grid.addWidget(b1,1,0)
214         grid.addWidget(b2,2,0)
215         #grid.addWidget(b3,3,0)
216
217         vbox = QVBoxLayout()
218         vbox.addLayout(grid)
219
220         vbox.addStretch(1)
221         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
222
223         self.set_layout(vbox)
224         if not self.exec_():
225             return
226         
227         if b2.isChecked():
228             return NetworkDialog(self.network, self.config, None).do_exec()
229
230         elif b1.isChecked():
231             self.config.set_key('auto_cycle', True, True)
232             return
233
234         else:
235             self.config.set_key("server", None, True)
236             self.config.set_key('auto_cycle', False, True)
237             return
238         
239
240     def show_message(self, msg):
241         vbox = QVBoxLayout()
242         vbox.addWidget(QLabel(msg))
243         vbox.addStretch(1)
244         vbox.addLayout(close_button(self, _('Next')))
245         self.set_layout(vbox)
246         if not self.exec_(): 
247             return None
248
249     def question(self, msg):
250         vbox = QVBoxLayout()
251         vbox.addWidget(QLabel(msg))
252         vbox.addStretch(1)
253         vbox.addLayout(ok_cancel_buttons(self, _('OK')))
254         self.set_layout(vbox)
255         if not self.exec_(): 
256             return None
257         return True
258
259
260     def show_seed(self, seed, sid):
261         from seed_dialog import make_seed_dialog
262         vbox = make_seed_dialog(seed, sid)
263         vbox.addLayout(ok_cancel_buttons(self, _("Next")))
264         self.set_layout(vbox)
265         return self.exec_()
266
267
268     def password_dialog(self, wallet):
269         msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
270               +_("Leave these fields empty if you want to disable encryption.")
271         from password_dialog import make_password_dialog, run_password_dialog
272         self.set_layout( make_password_dialog(self, wallet, msg) )
273         return run_password_dialog(self, wallet, self)
274
275
276     def choose_wallet_type(self):
277         grid = QGridLayout()
278         grid.setSpacing(5)
279
280         msg = _("Choose your wallet.")
281         label = QLabel(msg)
282         label.setWordWrap(True)
283         grid.addWidget(label, 0, 0)
284
285         gb = QGroupBox()
286
287         b1 = QRadioButton(gb)
288         b1.setText(_("Standard wallet (protected by password)"))
289         b1.setChecked(True)
290
291         b2 = QRadioButton(gb)
292         b2.setText(_("Multi-signature wallet (two-factor authentication)"))
293
294         grid.addWidget(b1,1,0)
295         grid.addWidget(b2,2,0)
296
297         vbox = QVBoxLayout()
298
299         vbox.addLayout(grid)
300         vbox.addStretch(1)
301         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
302
303         self.set_layout(vbox)
304         if not self.exec_():
305             return
306         
307         if b1.isChecked():
308             return 'standard'
309         elif b2.isChecked():
310             return '2of3'
311
312
313     def run(self, action = None):
314
315         if action is None:
316             action = self.restore_or_create()
317
318         if action is None: 
319             return
320
321         if action == 'create':            
322             t = self.choose_wallet_type()
323             if t == '2of3':
324                 run_hook('create_cold_seed', self.storage, self)
325                 return
326
327
328         if action in ['create', 'create2of3']:
329
330             wallet = Wallet(self.storage)
331
332             wallet.init_seed("note blind gun eye escape home surprise freedom bee carefully rant alter strength")
333             seed = wallet.get_mnemonic(None)
334             if not self.show_seed(seed, 'hot' if action == 'create2of3' else None):
335                 return
336             if not self.verify_seed(seed):
337                 return
338             ok, old_password, password = self.password_dialog(wallet)
339             wallet.save_seed(password)
340
341             if action == 'create2of3':
342                 run_hook('create_hot_seed', wallet, self)
343
344             wallet.create_accounts(password)
345             def create():
346                 wallet.synchronize()  # generate first addresses offline
347             self.waiting_dialog(create)
348
349         elif action == 'restore':
350             seed = self.seed_dialog()
351             if not Wallet.is_seed(seed):
352                 return
353             wallet = Wallet.from_seed(seed, self.storage)
354             ok, old_password, password = self.password_dialog(wallet)
355             wallet.save_seed(password)
356
357         elif action == 'watching':
358             mpk = self.mpk_dialog()
359             if not mpk:
360                 return
361             wallet = Wallet.from_mpk(mpk, self.storage)
362
363         else: raise
364                 
365         #if not self.config.get('server'):
366         if self.network:
367             if self.network.interfaces:
368                 self.network_dialog()
369             else:
370                 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
371                 self.network.stop()
372                 self.network = None
373
374         # start wallet threads
375         wallet.start_threads(self.network)
376
377         if action == 'restore':
378
379             self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
380
381             if self.network:
382                 if wallet.is_found():
383                     QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
384                 else:
385                     QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
386             else:
387                 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))
388
389         return wallet