diff --git a/public/views/backup.html b/public/views/backup.html index 25a2c3ce5..33e4061c9 100644 --- a/public/views/backup.html +++ b/public/views/backup.html @@ -1,133 +1,228 @@ -
-
+
+ -
-
- Wallet seed not available. You can still export it from Advanced > Export. -
-
- -
-
- +
+
+ + {{wordsC.error}} +
+ -
-
- Your Wallet Seed -
-
- -
-
-
- {{word}}  -
-
-
- -
-
- - - - WARNING: This seed was created with a passphrase. To recover this wallet both the mnemonic and passphrase are needed. - +
+
+
Write your wallet seed
+
+ + To restore this {{index.m}}-{{index.n}} shared wallet you will need + : +
    +
  1. Your wallet seed and access to the server that coordinated the initial wallet creation. You still need {{index.m}} keys to spend.
  2. +
  3. OR the wallet seed of all copayers in the wallet
  4. +
  5. OR 1 wallet export file and the remaining quorum of wallet seeds (e.g. in a 3-5 wallet: 1 wallet export file + 2 wallet seeds of any of the other copayers).
  6. +
-
+
+ + To restore this {{index.m}}-{{index.n}} shared wallet you will need + : +
    +
  1. Your wallet seed and access to the server that coordinated the initial wallet creation. You still need {{index.m}} keys to spend.
  2. +
  3. OR the wallet seeds of all copayers in the wallet
  4. +
+ +
+
-
-
-
- - Once you have copied your wallet seed down, it is recommended to delete it from this device. - +
+
+ Wallet seed not available. You can still export it from Advanced > Export. +
+
+ +
+

+ + You need the wallet seed to restore this personal wallet. Write it down and keep them somewhere safe. + +

+
+ -
-
+
+
+
+ {{word}}  +
+
+
+
- -
-
-
- You can safely install your wallet on another device and use it from multiple devices at the same time. - - Learn more about Copay backups - +
+
+ + + This seed was created with a passphrase. To recover this wallet both the mnemonic and passphrase are needed. +
+ +
+ +
-
+ +
+
+
Confirm your wallet seed
+

+ Please tap the words in order to confirm your backup phrase is correctly written. +

+
+
+
+
+ + + + +
+
+ +
+ +
+
+ + + +
+
+
Enter your passphrase
+

+ In order to verify your wallet backup, please type your passphrase: +

+
+ +
+
+ +
+ +
+
+ + + +
+
+
+ +
+
Congratulation
+

+ You backed up your new wallet. You can now restore this wallet at any time +

+ +
+ + +
+
+
+ You can safely install your wallet on another device and use it from multiple devices at the same time. + + Learn more about Copay backups + +
+
+
+
+
+
+
+ +
Backup failed +

+ Failed to verify backup. Please check your information +

+
+
+ You can safely install your wallet on another device and use it from multiple devices at the same time. + + Learn more about Copay backups + +
+
+ +
+ +
+
+
+
diff --git a/public/views/export.html b/public/views/export.html index 2d5a2a558..4b3f24b67 100644 --- a/public/views/export.html +++ b/public/views/export.html @@ -6,17 +6,17 @@ -
+

-
-
+
+
Failed to export
-
+
The private key for this wallet is encrypted. Exporting keep the private key encrypted in the export archive. @@ -31,14 +31,14 @@
+ name="password" ng-model="exportC.password">
+ name="password" ng-model="exportC.repeatpassword">
@@ -83,25 +83,25 @@
- -

Export options

- -
@@ -109,11 +109,11 @@
-
+

Wallet Export

- +
diff --git a/public/views/walletHome.html b/public/views/walletHome.html index 2e1004e61..102ef57b6 100644 --- a/public/views/walletHome.html +++ b/public/views/walletHome.html @@ -182,23 +182,18 @@ -->
-
-
-
- - - WARNING: Backup needed - -
-
- Before receiving funds, it is highly recommended you backup your wallet keys. -
- - +
+
+
+
Backup Needed
+

+ Before receiving funds, it is highly recommended you backup your wallet. If you lose this device, it is impossible to access your funds without a backup. +

+
@@ -473,6 +468,14 @@ Transactions Downloaded
+
+ Initial transaction history synchronization can take some minutes for wallets with many transactions.
+ Please stand by. +
+
+ {{index.txProgress}} + Transactions
Downloaded
+
@@ -514,7 +517,7 @@
{{btx.message}}
- {{index.addressbook[btx.addressTo] || btx.addressTo}} + To: {{index.addressbook[btx.addressTo] || btx.addressTo}}
diff --git a/src/css/main.css b/src/css/main.css index 274a9da1e..9dd839587 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -367,6 +367,55 @@ ul.tx-copayers { margin: 0 auto; } +.receive .circle-icon { + padding: 0.2rem; + margin-bottom: 2rem; +} + +.backup .circle-icon { + background: #fff; + padding: 0.2rem; + margin-bottom: 2rem; +} + +.receive h5, .backup h5 { + font-weight: 500; + color: #4B6178; + margin-bottom: 1rem; +} + +.receive p, .backup p { + font-size: 0.9rem; + margin-bottom: 2rem; +} + +.backup .tab-bar { + background: #f1f3f5; + border-bottom: none; +} + +.backup .button-box { + background: #F1F3F5; + position: fixed; + bottom: 0; + left: 0; + width: 100%; + padding: 0.5rem 1rem 1rem; + z-index: 9999; +} + +.backup input[type="text"] { + border-bottom: 1px solid #CAD4DB; +} + +.backup input[type="text"]:focus { + border-bottom: 1px solid #A5B2BF; +} + +.extra-padding-bottom { + padding-bottom: 78px; +} + .date-message { background-color: #213140; border-radius: 3px; @@ -988,6 +1037,30 @@ input.ng-invalid-match, input.ng-invalid-match:focus { padding: 1rem; } +.panel.words { + background: #E6EAEE; + border: 1px dashed #A5B2BF; + min-height: 147px; +} + +.backup .panel { + padding: 0.5rem; +} + +.backup button.words { + background: #FFFFFF; + box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.30); + color: #4B6178; + text-transform: lowercase; + font-size: 0.8rem; + margin: 5px; + padding: 0.5rem; +} + +.backup button[disabled] { + box-shadow: none; +} + .panel qrcode { background-color: white; } diff --git a/src/js/controllers/backup.js b/src/js/controllers/backup.js index 8b46b20c1..c6d1a7b5b 100644 --- a/src/js/controllers/backup.js +++ b/src/js/controllers/backup.js @@ -1,69 +1,73 @@ 'use strict'; -angular.module('copayApp.controllers').controller('wordsController', - function($rootScope, $scope, $timeout, profileService, go, gettext, confirmDialog, notification, bwsError, $log) { +angular.module('copayApp.controllers').controller('backupController', + function($rootScope, $scope, $timeout, $log, $state, $compile, go, lodash, profileService, gettext, bwcService, bwsError) { - var msg = gettext('Are you sure you want to delete the backup words?'); - var successMsg = gettext('Backup words deleted'); var self = this; - self.show = false; var fc = profileService.focusedClient; + var customWords = []; - if (fc.isPrivKeyEncrypted()) self.credentialsEncrypted = true; - else { - setWords(fc.getMnemonic()); + function init() { + $scope.passphrase = ''; + resetAllButtons(); + customWords = []; + self.step = 1; + self.deleted = false; + self.credentialsEncrypted = false; + self.selectComplete = false; + self.backupError = false; } - if (fc.credentials && !fc.credentials.mnemonicEncrypted && !fc.credentials.mnemonic) { + + init(); + + if (fc.credentials && !fc.credentials.mnemonicEncrypted && !fc.credentials.mnemonic) self.deleted = true; + + if (fc.isPrivKeyEncrypted() && !self.deleted) { + self.credentialsEncrypted = true; + passwordRequest(); + } else { + if (!self.deleted) + initWords(); } + self.goToStep = function(n) { + self.step = n; + if (self.step == 1) + init(); + if (self.step == 3 && !self.mnemonicHasPassphrase) + self.step++; + if (self.step == 4) { + confirm(); + } + } + + function initWords() { + var words = fc.getMnemonic(); + self.xPrivKey = fc.credentials.xPrivKey; + profileService.lockFC(); + self.mnemonicWords = words.split(/[\u3000\s]+/); + self.shuffledMnemonicWords = lodash.sortBy(self.mnemonicWords);; + self.mnemonicHasPassphrase = fc.mnemonicHasPassphrase(); + self.useIdeograms = words.indexOf("\u3000") >= 0; + }; + self.toggle = function() { self.error = ""; - if (!self.credentialsEncrypted) { - if (!self.show) - $rootScope.$emit('Local/BackupDone'); - self.show = !self.show; - } if (self.credentialsEncrypted) - self.passwordRequest(); + passwordRequest(); $timeout(function() { $scope.$apply(); }, 1); }; - self.delete = function() { - confirmDialog.show(msg, function(ok) { - if (ok) { - fc.clearMnemonic(); - profileService.updateCredentialsFC(function() { - self.deleted = true; - notification.success(successMsg); - go.walletHome(); - }); - } - }); - }; - - $scope.$on('$destroy', function() { - profileService.lockFC(); - }); - - function setWords(words) { - if (words) { - self.mnemonicWords = words.split(/[\u3000\s]+/); - self.mnemonicHasPassphrase = fc.mnemonicHasPassphrase(); - self.useIdeograms = words.indexOf("\u3000") >= 0; - } - }; - - self.passwordRequest = function() { + function passwordRequest() { try { - setWords(fc.getMnemonic()); + initWords(); } catch (e) { if (e.message && e.message.match(/encrypted/) && fc.isPrivKeyEncrypted()) { - self.credentialsEncrypted = true; $timeout(function() { $scope.$apply(); @@ -75,13 +79,99 @@ angular.module('copayApp.controllers').controller('wordsController', $log.warn('Error decrypting credentials:', self.error); //TODO return; } - if (!self.show && self.credentialsEncrypted) - self.show = !self.show; + self.credentialsEncrypted = false; - setWords(fc.getMnemonic()); - $rootScope.$emit('Local/BackupDone'); + initWords(); + + $timeout(function() { + $scope.$apply(); + }, 1); }); } } } + + function resetAllButtons() { + document.getElementById('addWord').innerHTML = ''; + var nodes = document.getElementById("buttons").getElementsByTagName('button'); + lodash.each(nodes, function(n) { + document.getElementById(n.id).disabled = false; + }); + } + + self.enableButton = function(word) { + document.getElementById(word).disabled = false; + lodash.remove(customWords, function(v) { + return v == word; + }); + } + + self.disableButton = function(index, word) { + var element = { + index: index, + word: word + }; + document.getElementById(index + word).disabled = true; + customWords.push(element); + self.addButton(index, word); + } + + self.addButton = function(index, word) { + var btnhtml = ''; + var temp = $compile(btnhtml)($scope); + angular.element(document.getElementById('addWord')).append(temp); + self.shouldContinue(); + } + + self.removeButton = function(event) { + var id = (event.target.id); + document.getElementById(id).remove(); + self.enableButton(id.substring(1)); + lodash.remove(customWords, function(d) { + return d.index == id.substring(1, 3); + }); + self.shouldContinue(); + } + + self.shouldContinue = function() { + if (customWords.length == 12) + self.selectComplete = true; + else + self.selectComplete = false; + } + + function confirm() { + self.backupError = false; + + var walletClient = bwcService.getClient(); + var separator = self.useIdeograms ? '\u3000' : ' '; + var customSentence = lodash.pluck(customWords, 'word').join(separator); + var passphrase = $scope.passphrase || ''; + + try { + walletClient.seedFromMnemonic(customSentence, { + network: fc.credentials.network, + passphrase: passphrase, + account: fc.credentials.account + }) + } catch (err) { + return backupError(err); + } + + if (walletClient.credentials.xPrivKey != self.xPrivKey) { + return backupError('Private key mismatch'); + } + + $rootScope.$emit('Local/BackupDone'); + } + + function backupError(err) { + $log.debug('Failed to verify backup: ', err); + self.backupError = true; + + $timeout(function() { + $scope.$apply(); + }, 1); + }; }); diff --git a/src/js/controllers/export.js b/src/js/controllers/export.js index b25150550..af2f9ba4c 100644 --- a/src/js/controllers/export.js +++ b/src/js/controllers/export.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('backupController', +angular.module('copayApp.controllers').controller('exportController', function($rootScope, $scope, $timeout, $log, backupService, storageService, profileService, isMobile, notification, go, gettext, gettextCatalog) { var self = this; diff --git a/src/js/controllers/index.js b/src/js/controllers/index.js index acdf30dff..d2e3d5ab4 100644 --- a/src/js/controllers/index.js +++ b/src/js/controllers/index.js @@ -9,6 +9,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r self.onGoingProcess = {}; self.historyShowLimit = 10; self.updatingTxHistory = {}; + self.prevState = 'walletHome'; function strip(number) { return (parseFloat(number.toPrecision(12))); @@ -1105,6 +1106,11 @@ angular.module('copayApp.controllers').controller('indexController', function($r }); }; + $rootScope.$on('$stateChangeSuccess', function(ev, to, toParams, from, fromParams) { + self.prevState = from.name || 'walletHome'; + self.tab = 'walletHome'; + }); + $rootScope.$on('Local/ClearHistory', function(event) { $log.debug('The wallet transaction history has been deleted'); self.txHistory = self.completeHistory = [];