1
################################################################################
3
# Copyright (C) 2011-2014, Armory Technologies, Inc. #
4
# Distributed under the GNU Affero General Public License (AGPL v3) #
5
# See LICENSE or http://www.gnu.org/licenses/agpl.html #
7
################################################################################
9
from PyQt4.Qt import * #@UnusedWildImport
10
from PyQt4.QtGui import * #@UnusedWildImport
11
from armoryengine.ArmoryUtils import USE_TESTNET
12
from ui.WalletFrames import NewWalletFrame, SetPassphraseFrame, VerifyPassphraseFrame,\
13
WalletBackupFrame, WizardCreateWatchingOnlyWalletFrame
14
from ui.TxFrames import SendBitcoinsFrame, SignBroadcastOfflineTxFrame,\
16
from qtdefines import USERMODE, GETFONT, tr, AddToRunningDialogsList
17
from armoryengine.PyBtcWallet import PyBtcWallet
18
from CppBlockUtils import SecureBinaryData
19
from armoryengine.BDM import TheBDM
20
from qtdialogs import DlgProgress
22
# This class is intended to be an abstract Wizard class that
23
# will hold all of the functionality that is common to all
25
class ArmoryWizard(QWizard):
26
def __init__(self, parent, main):
27
super(QWizard, self).__init__(parent)
28
self.setWizardStyle(QWizard.ClassicStyle)
31
self.setFont(GETFONT('var'))
32
self.setWindowFlags(Qt.Window)
33
# Need to adjust the wizard frame size whenever the page changes.
34
self.connect(self, SIGNAL('currentIdChanged(int)'), self.fitContents)
36
self.setWindowTitle('Armory - Bitcoin Wallet Management [TESTNET]')
37
self.setWindowIcon(QIcon(':/armory_icon_green_32x32.png'))
39
self.setWindowTitle('Armory - Bitcoin Wallet Management')
40
self.setWindowIcon(QIcon(':/armory_icon_32x32.png'))
42
def fitContents(self):
45
@AddToRunningDialogsList
47
return super(ArmoryWizard, self).exec_()
49
# This class is intended to be an abstract Wizard Page class that
50
# will hold all of the functionality that is common to all
51
# Wizard pages in Armory.
52
# The layout is QVBoxLayout and holds a single QFrame (self.pageFrame)
53
class ArmoryWizardPage(QWizardPage):
54
def __init__(self, wizard, pageFrame):
55
super(ArmoryWizardPage, self).__init__(wizard)
56
self.pageFrame = pageFrame
57
self.pageLayout = QVBoxLayout()
58
self.pageLayout.addWidget(self.pageFrame)
59
self.setLayout(self.pageLayout)
61
# override this method to implement validators
62
def validatePage(self):
65
################################ Wallet Wizard ################################
66
# Wallet Wizard has these pages:
69
# 3. Verify Passphrase
70
# 4. Create Paper Backup
71
# 5. Create Watcing Only Wallet
72
class WalletWizard(ArmoryWizard):
73
def __init__(self, parent, main):
74
super(WalletWizard,self).__init__(parent, main)
76
self.isBackupCreated = False
77
self.setWindowTitle(tr("Wallet Creation Wizard"))
78
self.setOption(QWizard.HaveFinishButtonOnEarlyPages, on=True)
79
self.setOption(QWizard.IgnoreSubTitles, on=True)
81
# Page 1: Create Wallet
82
self.walletCreationPage = WalletCreationPage(self)
83
self.addPage(self.walletCreationPage)
85
# Page 2: Set Passphrase
86
self.setPassphrasePage = SetPassphrasePage(self)
87
self.addPage(self.setPassphrasePage)
89
# Page 3: Verify Passphrase
90
self.verifyPassphrasePage = VerifyPassphrasePage(self)
91
self.addPage(self.verifyPassphrasePage)
93
# Page 4: Create Paper Backup
94
self.walletBackupPage = WalletBackupPage(self)
95
self.addPage(self.walletBackupPage)
97
# Page 5: Create Watching Only Wallet -- but only if expert, or offline
98
self.hasCWOWPage = False
99
if self.main.usermode==USERMODE.Expert or not self.main.internetAvail:
100
self.hasCWOWPage = True
101
self.createWOWPage = CreateWatchingOnlyWalletPage(self)
102
self.addPage(self.createWOWPage)
104
self.setButtonLayout([QWizard.BackButton,
107
QWizard.FinishButton])
109
def initializePage(self, *args, **kwargs):
111
if self.currentPage() == self.verifyPassphrasePage:
112
self.verifyPassphrasePage.setPassphrase(
113
self.setPassphrasePage.pageFrame.getPassphrase())
114
elif self.hasCWOWPage and self.currentPage() == self.createWOWPage:
115
self.createWOWPage.pageFrame.setWallet(self.newWallet)
117
if self.currentPage() == self.walletBackupPage:
118
self.createNewWalletFromWizard()
119
self.walletBackupPage.pageFrame.setPassphrase(
120
self.setPassphrasePage.pageFrame.getPassphrase())
121
self.walletBackupPage.pageFrame.setWallet(self.newWallet)
123
# Hide the back button on wallet backup page
124
self.setButtonLayout([QWizard.Stretch,
126
QWizard.FinishButton])
127
elif self.currentPage() == self.walletCreationPage:
128
# Hide the back button on the first page
129
self.setButtonLayout([QWizard.Stretch,
131
QWizard.FinishButton])
133
self.setButtonLayout([QWizard.BackButton,
136
QWizard.FinishButton])
137
def done(self, event):
138
if self.newWallet and not self.walletBackupPage.pageFrame.isBackupCreated:
139
reply = QMessageBox.question(self, tr('Wallet Backup Warning'), tr("""
140
You have not made a backup for your new wallet. You only have
141
to make a backup of your wallet <u>one time</u> to protect
142
all the funds held by this wallet <i>any time in the future</i>
143
(it is a backup of the signing keys, not the coins themselves).
145
If you do not make a backup, you will <u>permanently</u> lose
146
the money in this wallet if you ever forget your password, or
147
suffer from hardware failure.
149
Are you sure that you want to leave this wizard without backing
150
up your wallet?"""), \
151
QMessageBox.Yes | QMessageBox.No)
152
if reply == QMessageBox.No:
155
return super(WalletWizard, self).done(event)
157
def createNewWalletFromWizard(self):
158
self.newWallet = PyBtcWallet().createNewWallet( \
159
securePassphrase=self.setPassphrasePage.pageFrame.getPassphrase(), \
160
kdfTargSec=self.walletCreationPage.pageFrame.getKdfSec(), \
161
kdfMaxMem=self.walletCreationPage.pageFrame.getKdfBytes(), \
162
shortLabel=self.walletCreationPage.pageFrame.getName(), \
163
longLabel=self.walletCreationPage.pageFrame.getDescription(), \
164
doRegisterWithBDM=False, \
165
extraEntropy=self.main.getExtraEntropyForKeyGen())
167
self.newWallet.unlock(securePassphrase=
168
SecureBinaryData(self.setPassphrasePage.pageFrame.getPassphrase()))
169
# We always want to fill the address pool, right away.
170
fillPoolProgress = DlgProgress(self, self.main, HBar=1, \
171
Title="Creating Wallet")
172
fillPoolProgress.exec_(self.newWallet.fillAddressPool, doRegister=False,
173
Progress=fillPoolProgress.UpdateHBar)
175
# Reopening from file helps make sure everything is correct -- don't
176
# let the user use a wallet that triggers errors on reading it
177
wltpath = self.newWallet.walletPath
178
walletFromDisk = PyBtcWallet().readWalletFile(wltpath)
179
self.main.addWalletToApplication(walletFromDisk, walletIsNew=True)
180
if TheBDM.getBDMState() in ('Uninitialized', 'Offline'):
181
TheBDM.registerWallet(walletFromDisk, isFresh=True, wait=False)
183
self.main.newWalletList.append([walletFromDisk, True])
185
def cleanupPage(self, *args, **kwargs):
186
if self.hasCWOWPage and self.currentPage() == self.createWOWPage:
187
self.setButtonLayout([QWizard.Stretch,
189
QWizard.FinishButton])
190
# If we are backing up from setPassphrasePage must be going
192
elif self.currentPage() == self.setPassphrasePage:
193
# Hide the back button on the first page
194
self.setButtonLayout([QWizard.Stretch,
196
QWizard.FinishButton])
198
self.setButtonLayout([QWizard.BackButton,
201
QWizard.FinishButton])
203
class WalletCreationPage(ArmoryWizardPage):
204
def __init__(self, wizard):
205
super(WalletCreationPage, self).__init__(wizard,
206
NewWalletFrame(wizard, wizard.main, "Primary Wallet"))
207
self.setTitle(tr("Step 1: Create Wallet"))
208
self.setSubTitle(tr("""
209
Create a new wallet for managing your funds.
210
The name and description can be changed at any time."""))
212
# override this method to implement validators
213
def validatePage(self):
215
if self.pageFrame.getKdfSec() == -1:
216
QMessageBox.critical(self, 'Invalid Target Compute Time', \
217
'You entered Target Compute Time incorrectly.\n\nEnter: <Number> (ms, s)', QMessageBox.Ok)
219
elif self.pageFrame.getKdfBytes() == -1:
220
QMessageBox.critical(self, 'Invalid Max Memory Usage', \
221
'You entered Max Memory Usag incorrectly.\n\nnter: <Number> (kb, mb)', QMessageBox.Ok)
225
class SetPassphrasePage(ArmoryWizardPage):
226
def __init__(self, wizard):
227
super(SetPassphrasePage, self).__init__(wizard,
228
SetPassphraseFrame(wizard, wizard.main, "Set Passphrase", self.updateNextButton))
229
self.setTitle(tr("Step 2: Set Passphrase"))
230
self.updateNextButton()
232
def updateNextButton(self):
233
self.emit(SIGNAL("completeChanged()"))
235
def isComplete(self):
236
return self.pageFrame.checkPassphrase(False)
238
class VerifyPassphrasePage(ArmoryWizardPage):
239
def __init__(self, wizard):
240
super(VerifyPassphrasePage, self).__init__(wizard,
241
VerifyPassphraseFrame(wizard, wizard.main, "Verify Passphrase"))
242
self.passphrase = None
243
self.setTitle(tr("Step 3: Verify Passphrase"))
245
def setPassphrase(self, passphrase):
246
self.passphrase = passphrase
248
def validatePage(self):
249
result = self.passphrase == str(self.pageFrame.edtPasswd3.text())
251
QMessageBox.critical(self, 'Invalid Passphrase', \
252
'You entered your confirmation passphrase incorrectly!', QMessageBox.Ok)
255
class WalletBackupPage(ArmoryWizardPage):
256
def __init__(self, wizard):
257
super(WalletBackupPage, self).__init__(wizard,
258
WalletBackupFrame(wizard, wizard.main, "Backup Wallet"))
259
self.myWizard = wizard
260
self.setTitle(tr("Step 4: Backup Wallet"))
261
self.setFinalPage(True)
263
class CreateWatchingOnlyWalletPage(ArmoryWizardPage):
264
def __init__(self, wizard):
265
super(CreateWatchingOnlyWalletPage, self).__init__(wizard,
266
WizardCreateWatchingOnlyWalletFrame(wizard, wizard.main, "Create Watching Only Wallet"))
267
self.setTitle(tr("Step 5: Create Watching Only Wallet"))
269
############################### Offline TX Wizard ##############################
270
# Offline TX Wizard has these pages:
271
# 1. Create Transaction
272
# 2. Sign Transaction on Offline Computer
273
# 3. Broadcast Transaction
274
class TxWizard(ArmoryWizard):
275
def __init__(self, parent, main, wlt, prefill=None, onlyOfflineWallets=False):
276
super(TxWizard,self).__init__(parent, main)
277
self.setWindowTitle(tr("Offline Transaction Wizard"))
278
self.setOption(QWizard.IgnoreSubTitles, on=True)
279
self.setOption(QWizard.HaveCustomButton1, on=True)
280
self.setOption(QWizard.HaveFinishButtonOnEarlyPages, on=True)
282
# Page 1: Create Offline TX
283
self.createTxPage = CreateTxPage(self, wlt, prefill, onlyOfflineWallets=onlyOfflineWallets)
284
self.addPage(self.createTxPage)
286
# Page 2: Sign Offline TX
287
self.reviewOfflineTxPage = ReviewOfflineTxPage(self)
288
self.addPage(self.reviewOfflineTxPage)
290
# Page 3: Broadcast Offline TX
291
self.signBroadcastOfflineTxPage = SignBroadcastOfflineTxPage(self)
292
self.addPage(self.signBroadcastOfflineTxPage)
294
self.setButtonText(QWizard.NextButton, tr('Create Unsigned Transaction'))
295
self.setButtonText(QWizard.CustomButton1, tr('Send!'))
296
self.connect(self, SIGNAL('customButtonClicked(int)'), self.sendClicked)
297
self.setButtonLayout([QWizard.CancelButton,
301
QWizard.CustomButton1])
305
def initializePage(self, *args, **kwargs):
306
if self.currentPage() == self.createTxPage:
307
self.createTxPage.pageFrame.fireWalletChange()
308
elif self.currentPage() == self.reviewOfflineTxPage:
309
self.setButtonText(QWizard.NextButton, tr('Next'))
310
self.setButtonLayout([QWizard.BackButton,
313
QWizard.FinishButton])
314
self.reviewOfflineTxPage.pageFrame.setTxDp(self.createTxPage.txdp)
315
self.reviewOfflineTxPage.pageFrame.setWallet(
316
self.createTxPage.pageFrame.wlt)
318
def cleanupPage(self, *args, **kwargs):
319
if self.currentPage() == self.reviewOfflineTxPage:
320
self.updateOnSelectWallet(self.createTxPage.pageFrame.wlt)
321
self.setButtonText(QWizard.NextButton, tr('Create Unsigned Transaction'))
323
def sendClicked(self, customButtonIndex):
324
self.createTxPage.pageFrame.createTxAndBroadcast()
327
def updateOnSelectWallet(self, wlt):
329
self.setButtonLayout([QWizard.CancelButton,
334
self.setButtonLayout([QWizard.CancelButton,
338
QWizard.CustomButton1])
340
class CreateTxPage(ArmoryWizardPage):
341
def __init__(self, wizard, wlt, prefill=None, onlyOfflineWallets=False):
342
super(CreateTxPage, self).__init__(wizard,
343
SendBitcoinsFrame(wizard, wizard.main,
344
"Create Transaction", wlt, prefill,
345
selectWltCallback=self.updateOnSelectWallet,
346
onlyOfflineWallets=onlyOfflineWallets))
347
self.setTitle(tr("Step 1: Create Transaction"))
350
def validatePage(self):
351
result = self.pageFrame.validateInputsGetTxDP()
352
# the validator also computes the transaction and returns it or False if not valid
358
def updateOnSelectWallet(self, wlt):
359
self.wizard().updateOnSelectWallet(wlt)
361
class ReviewOfflineTxPage(ArmoryWizardPage):
362
def __init__(self, wizard):
363
super(ReviewOfflineTxPage, self).__init__(wizard,
364
ReviewOfflineTxFrame(wizard, wizard.main, "Review Offline Transaction"))
365
self.setTitle(tr("Step 2: Review Offline Transaction"))
366
self.setFinalPage(True)
368
class SignBroadcastOfflineTxPage(ArmoryWizardPage):
369
def __init__(self, wizard):
370
super(SignBroadcastOfflineTxPage, self).__init__(wizard,
371
SignBroadcastOfflineTxFrame(wizard, wizard.main, "Sign/Broadcast Offline Transaction"))
372
self.setTitle(tr("Step 3: Sign/Broadcast Offline Transaction"))