diff --git a/public/views/export.html b/public/views/export.html index 7aa69fae8..6a1b61e2f 100644 --- a/public/views/export.html +++ b/public/views/export.html @@ -5,89 +5,118 @@
-

-
-
- - Failed to export +

+
+
+ +
+ QR Code +
+
+
+
+
+ + Failed to export +
+ +
-
- - A spending password is set for this wallet. Exporting keeps the spending password in the export archive. + +
+ +
+ + +
+
+ -
-
-
- -
- -
+
+
+ +
+
From the destination device, go to Add wallet > Import wallet and scan this QR code
+
- -
- -
-
-
-
+
+
Exporting via QR not supported for this wallet
+
-

+ - +
+ Do not include private key +
-
- - - +
+ + + WARNING: The private key of this wallet is not available. The export allows to check the wallet balance, transaction history, and create spend proposals from the export. However, does not allow to approve (sign) proposals, so funds will not be accessible from the export. - -
+
+
- -
- - - - WARNING: Not including the private key allows to check the wallet balance, transaction history, and create spend proposals from the export. However, does not allow to approve (sign) proposals, so funds will not be accessible from the export. - +
+ + + + WARNING: Not including the private key allows to check the wallet balance, transaction history, and create spend proposals from the export. However, does not allow to approve (sign) proposals, so funds will not be accessible from the export. -
+
+
-
-
- + +
+

Export options

+ - + -
-

Export options

- - -
+ ng-click="sendWalletBackup()"> + Send by email
- +
@@ -102,6 +131,5 @@
-
diff --git a/public/views/import.html b/public/views/import.html index 5ce29400c..7a72834e8 100644 --- a/public/views/import.html +++ b/public/views/import.html @@ -4,17 +4,17 @@ ng-init="titleSection='Import wallet'; goBackToState = 'add'; noColor = true">
-
+
@@ -23,84 +23,87 @@
-
-
-
Could not access the wallet at the server. Please check:
-
    -
  • The password of the recovery phrase (if set) -
  • -
  • The derivation path -
  • -
  • The wallet service URL -
-
- NOTE: To import a wallet from a 3rd party software, please go to Add Wallet > Create Wallet, and specify the Recovery Phrase there.
-
+
+
+
Could not access the wallet at the server. Please check:
+
    +
  • The password of the recovery phrase (if set)
  • +
  • The derivation path
  • +
  • The wallet service URL
  • +
+
+ NOTE: To import a wallet from a 3rd party software, please go to Add Wallet > Create Wallet, and specify the Recovery Phrase there.
+
-
-
- {{import.error|translate}} -
+
+
{{error|translate}}
-
+ + -
- - +
+ +
+ +
+
+
- -
- - - Show advanced options - Hide advanced options - - - +
+
-
-
+
+ -
@@ -108,33 +111,33 @@
-
+
- {{import.error|translate}} + {{error|translate}}
-
+
+ name="backupFile" ng-model="backupFile" ng-file-select>
- +
+ name="password" ng-model="password">
@@ -157,7 +160,7 @@
@@ -168,65 +171,62 @@
-
+
- {{import.error|translate}} + {{error|translate}}
-
-
-
- No hardware wallets supported on this device -
-
-
- -
- -
- - -
- -
- - Shared Wallet - -
- - -
-
- - -
-
- - - -
+ +
+ No hardware wallets supported on this device
+
+
+ +
+ +
+ + +
+ +
+ + Shared Wallet + +
+ + +
+
+ + +
+
+ + +
diff --git a/src/js/controllers/backup.js b/src/js/controllers/backup.js index 320144800..7cbeaf093 100644 --- a/src/js/controllers/backup.js +++ b/src/js/controllers/backup.js @@ -23,8 +23,8 @@ angular.module('copayApp.controllers').controller('backupController', handleEncryptedWallet(fc, function(err) { if (err) { - $scope.error = bwsError.msg(err, gettext('Could not decrypt')); $log.warn('Error decrypting credentials:', $scope.error); + go.path(prevState); return; } $scope.credentialsEncrypted = false; @@ -69,10 +69,13 @@ angular.module('copayApp.controllers').controller('backupController', }; $scope.goBack = function() { - walletService.lock(fc); go.path(prevState || 'walletHome'); }; + $scope.$on('$destroy', function() { + walletService.lock(fc); + }); + $scope.goToStep = function(n) { if (n == 1) $scope.initFlow(); diff --git a/src/js/controllers/export.js b/src/js/controllers/export.js index cea7af04a..e34866f46 100644 --- a/src/js/controllers/export.js +++ b/src/js/controllers/export.js @@ -1,39 +1,111 @@ 'use strict'; angular.module('copayApp.controllers').controller('exportController', - function($scope, $timeout, $log, backupService, fingerprintService, configService, storageService, profileService, platformInfo, notification, go, gettext, gettextCatalog) { + function($rootScope, $scope, $timeout, $log, lodash, backupService, walletService, fingerprintService, configService, storageService, profileService, platformInfo, notification, go, gettext, gettextCatalog) { + var prevState; var isWP = platformInfo.isWP; var isAndroid = platformInfo.isAndroid; - var isCordova = platformInfo.isCordova; - - $scope.error = null; - $scope.success = null; var fc = profileService.focusedClient; $scope.isEncrypted = fc.isPrivKeyEncrypted(); - $scope.touchidSuccess = null; - $scope.touchidEnabled = null; + $scope.isCordova = platformInfo.isCordova; + $scope.isSafari = platformInfo.isSafari; $scope.error = null; $scope.init = function(state) { - if (!isCordova) return; - - var config = configService.getSync(); - var touchidAvailable = fingerprintService.isAvailable(); - var touchidEnabled = $scope.touchidEnabled = config.touchIdFor ? config.touchIdFor[fc.credentials.walletId] : null; - - if (!touchidAvailable || !touchidEnabled) return; + $scope.supported = true; + $scope.exportQR = false; + $scope.noSignEnabled = false; + $scope.showAdvanced = false; + prevState = state || 'walletHome'; fingerprintService.check(fc, function(err) { - if (err) - go.path(state || 'walletHome'); + if (err) { + go.path(prevState); + return; + } - $scope.touchidSuccess = true; - $timeout(function() { - $scope.$apply(); - }, 10); + handleEncryptedWallet(fc, function(err) { + if (err) { + go.path(prevState); + return; + } + + $scope.exportWalletInfo = encodeWalletInfo(); + $timeout(function() { + $scope.$apply(); + }, 1); + }); }); }; + /* + EXPORT WITHOUT PRIVATE KEY - PENDING + + $scope.noSignEnabledChange = function() { + $scope.exportWalletInfo = encodeWalletInfo(); + $timeout(function() { + $scope.$apply(); + }, 1); + }; + */ + + $scope.$on('$destroy', function() { + walletService.lock(fc); + }); + + function handleEncryptedWallet(client, cb) { + if (!walletService.isEncrypted(client)) { + $scope.credentialsEncrypted = false; + return cb(); + } + + $rootScope.$emit('Local/NeedsPassword', false, function(err, password) { + if (err) return cb(err); + return cb(walletService.unlock(client, password)); + }); + }; + + function encodeWalletInfo() { + var c = fc.credentials; + var derivationPath = fc.credentials.getBaseAddressDerivationPath(); + var encodingType = { + mnemonic: 1, + xpriv: 2, + xpub: 3 + }; + var info; + + $scope.supported = (c.derivationStrategy == 'BIP44' && c.canSign()); + + if ($scope.supported) { + if (c.mnemonic) { + info = { + type: encodingType.mnemonic, + data: c.mnemonic, + } + } else { + info = { + type: encodingType.xpriv, + data: c.xPrivKey + } + } + } else { + /* + EXPORT WITHOUT PRIVATE KEY - PENDING + + info = { + type: encodingType.xpub, + data: c.xPubKey + } + */ + + return null; + } + + var code = info.type + '|' + info.data + '|' + c.network.toLowerCase() + '|' + derivationPath + '|' + (c.mnemonicHasPassphrase); + return code; + }; + $scope.downloadWalletBackup = function() { $scope.getAddressbook(function(err, localAddressBook) { if (err) { diff --git a/src/js/controllers/import.js b/src/js/controllers/import.js index af5a44cd9..a7d147c3e 100644 --- a/src/js/controllers/import.js +++ b/src/js/controllers/import.js @@ -1,58 +1,89 @@ 'use strict'; angular.module('copayApp.controllers').controller('importController', - function($scope, $rootScope, $timeout, $log, profileService, configService, notification, go, sjcl, gettext, lodash, ledger, trezor, derivationPathHelper, platformInfo, bwsError, bwcService, ongoingProcess) { + function($scope, $rootScope, $timeout, $log, profileService, configService, notification, go, sjcl, gettext, ledger, trezor, derivationPathHelper, platformInfo, bwcService, ongoingProcess) { var isChromeApp = platformInfo.isChromeApp; var isDevel = platformInfo.isDevel; - - var self = this; var reader = new FileReader(); var defaults = configService.getDefaults(); var errors = bwcService.getErrors(); + $scope.dataFromQR = null; $scope.bwsurl = defaults.bws.url; $scope.derivationPath = derivationPathHelper.default; $scope.account = 1; - self.importErr = false; + $scope.importErr = false; var updateSeedSourceSelect = function() { - self.seedOptions = []; + $scope.seedOptions = []; if (isChromeApp) { - self.seedOptions.push({ + $scope.seedOptions.push({ id: 'ledger', label: 'Ledger Hardware Wallet', }); } if (isChromeApp || isDevel) { - self.seedOptions.push({ + $scope.seedOptions.push({ id: 'trezor', label: 'Trezor Hardware Wallet', }); - $scope.seedSource = self.seedOptions[0]; + $scope.seedSource = $scope.seedOptions[0]; } }; - this.setType = function(type) { + $scope.processWalletInfo = function(code) { + $scope.dataFromQR = null; + $scope.importErr = false; + $scope.error = null; + var parsedCode = code.split('|'); + + if (parsedCode.length != 5) { + $scope.error = gettext('Cannot read the code properly. Missing parameters'); + return; + } + + var info = { + type: parsedCode[0], + data: parsedCode[1], + network: parsedCode[2], + derivationPath: parsedCode[3], + hasPassphrase: parsedCode[4] == 'true' ? true : false + }; + + if (info.type == 1 && info.hasPassphrase) + $scope.error = gettext('Password required. Make sure to enter your password in advanced options'); + + $scope.derivationPath = info.derivationPath; + $scope.testnetEnabled = info.network == 'testnet' ? true : false; + + $timeout(function() { + $scope.words = null; + $scope.dataFromQR = info.data; + $rootScope.$apply(); + }, 1); + }; + + $scope.setType = function(type) { $scope.type = type; - this.error = null; + $scope.error = null; $timeout(function() { $rootScope.$apply(); - }); + }, 1); }; var _importBlob = function(str, opts) { var str2, err; try { - str2 = sjcl.decrypt(self.password, str); + str2 = sjcl.decrypt($scope.password, str); } catch (e) { err = gettext('Could not decrypt file, check your password'); $log.warn(e); }; if (err) { - self.error = err; + $scope.error = err; $timeout(function() { $rootScope.$apply(); }); @@ -67,7 +98,7 @@ angular.module('copayApp.controllers').controller('importController', profileService.importWallet(str2, opts, function(err, walletId) { ongoingProcess.set('importingWallet', false); if (err) { - self.error = err; + $scope.error = err; } else { $rootScope.$emit('Local/WalletImported', walletId); notification.success(gettext('Success'), gettext('Your wallet has been imported correctly')); @@ -84,9 +115,9 @@ angular.module('copayApp.controllers').controller('importController', ongoingProcess.set('importingWallet', false); if (err) { if (err instanceof errors.NOT_AUTHORIZED) { - self.importErr = true; + $scope.importErr = true; } else { - self.error = err; + $scope.error = err; } return $timeout(function() { $scope.$apply(); @@ -100,6 +131,28 @@ angular.module('copayApp.controllers').controller('importController', }, 100); }; + /* + IMPORT FROM PUBLIC KEY - PENDING + + var _importExtendedPublicKey = function(xPubKey, opts) { + ongoingProcess.set('importingWallet', true); + $timeout(function() { + profileService.importExtendedPublicKey(opts, function(err, walletId) { + ongoingProcess.set('importingWallet', false); + if (err) { + $scope.error = err; + return $timeout(function() { + $scope.$apply(); + }); + } + $rootScope.$emit('Local/WalletImported', walletId); + notification.success(gettext('Success'), gettext('Your wallet has been imported correctly')); + go.walletHome(); + }); + }, 100); + }; + */ + var _importMnemonic = function(words, opts) { ongoingProcess.set('importingWallet', true); @@ -109,9 +162,9 @@ angular.module('copayApp.controllers').controller('importController', if (err) { if (err instanceof errors.NOT_AUTHORIZED) { - self.importErr = true; + $scope.importErr = true; } else { - self.error = err; + $scope.error = err; } return $timeout(function() { $scope.$apply(); @@ -130,7 +183,7 @@ angular.module('copayApp.controllers').controller('importController', $scope.derivationPath = derivationPathHelper.defaultTestnet; else $scope.derivationPath = derivationPathHelper.default; - } + }; $scope.getFile = function() { // If we use onloadend, we need to check the readyState. @@ -143,9 +196,9 @@ angular.module('copayApp.controllers').controller('importController', } }; - this.importBlob = function(form) { + $scope.importBlob = function(form) { if (form.$invalid) { - this.error = gettext('There is an error in the form'); + $scope.error = gettext('There is an error in the form'); $timeout(function() { $scope.$apply(); }); @@ -157,7 +210,7 @@ angular.module('copayApp.controllers').controller('importController', var password = form.password.$modelValue; if (!backupFile && !backupText) { - this.error = gettext('Please, select your backup file'); + $scope.error = gettext('Please, select your backup file'); $timeout(function() { $scope.$apply(); }); @@ -174,9 +227,9 @@ angular.module('copayApp.controllers').controller('importController', } }; - this.importMnemonic = function(form) { + $scope.importMnemonic = function(form) { if (form.$invalid) { - this.error = gettext('There is an error in the form'); + $scope.error = gettext('There is an error in the form'); $timeout(function() { $scope.$apply(); }); @@ -187,32 +240,33 @@ angular.module('copayApp.controllers').controller('importController', if ($scope.bwsurl) opts.bwsurl = $scope.bwsurl; - var pathData = derivationPathHelper.parse($scope.derivationPath); if (!pathData) { - this.error = gettext('Invalid derivation path'); + $scope.error = gettext('Invalid derivation path'); return; } opts.account = pathData.account; opts.networkName = pathData.networkName; opts.derivationStrategy = pathData.derivationStrategy; - var words = form.words.$modelValue; - this.error = null; + var words = form.words.$modelValue || $scope.dataFromQR; + $scope.error = null; if (!words) { - this.error = gettext('Please enter the recovery phrase'); + $scope.error = gettext('Please enter the recovery phrase'); } else if (words.indexOf('xprv') == 0 || words.indexOf('tprv') == 0) { return _importExtendedPrivateKey(words, opts); + } else if (words.indexOf('xpub') == 0 || words.indexOf('tpuv') == 0) { + return _importExtendedPublicKey(words, opts); } else { var wordList = words.split(/[\u3000\s]+/); if ((wordList.length % 3) != 0) { - this.error = gettext('Wrong number of recovery words:') + wordList.length; + $scope.error = gettext('Wrong number of recovery words:') + wordList.length; } } - if (this.error) { + if ($scope.error) { $timeout(function() { $scope.$apply(); }); @@ -225,12 +279,12 @@ angular.module('copayApp.controllers').controller('importController', _importMnemonic(words, opts); }; - this.importTrezor = function(account, isMultisig) { - var self = this; + $scope.importTrezor = function(account, isMultisig) { + var $scope = $scope; trezor.getInfoForNewWallet(isMultisig, account, function(err, lopts) { ongoingProcess.clear(); if (err) { - self.error = err; + $scope.error = err; $scope.$apply(); return; } @@ -243,7 +297,7 @@ angular.module('copayApp.controllers').controller('importController', profileService.importExtendedPublicKey(lopts, function(err, walletId) { ongoingProcess.set('importingWallet', false); if (err) { - self.error = err; + $scope.error = err; return $timeout(function() { $scope.$apply(); }); @@ -255,56 +309,56 @@ angular.module('copayApp.controllers').controller('importController', }, 100); }; - this.importHW = function(form) { + $scope.importHW = function(form) { if (form.$invalid || $scope.account < 0) { - this.error = gettext('There is an error in the form'); + $scope.error = gettext('There is an error in the form'); $timeout(function() { $scope.$apply(); }); return; } - this.error = ''; - this.importErr = false; + $scope.error = ''; + $scope.importErr = false; var account = +$scope.account; - if (self.seedSourceId == 'trezor') { + if ($scope.seedSourceId == 'trezor') { if (account < 1) { - this.error = gettext('Invalid account number'); + $scope.error = gettext('Invalid account number'); return; } account = account - 1; } - switch (self.seedSourceId) { + switch ($scope.seedSourceId) { case ('ledger'): ongoingProcess.set('connectingledger', true); - self.importLedger(account); + $scope.importLedger(account); break; case ('trezor'): ongoingProcess.set('connectingtrezor', true); - self.importTrezor(account, $scope.isMultisig); + $scope.importTrezor(account, $scope.isMultisig); break; default: throw ('Error: bad source id'); }; }; - this.setSeedSource = function() { + $scope.setSeedSource = function() { if (!$scope.seedSource) return; - self.seedSourceId = $scope.seedSource.id; + $scope.seedSourceId = $scope.seedSource.id; $timeout(function() { $rootScope.$apply(); }); }; - this.importLedger = function(account) { - var self = this; + $scope.importLedger = function(account) { + var $scope = $scope; ledger.getInfoForNewWallet(true, account, function(err, lopts) { ongoingProcess.clear(); if (err) { - self.error = err; + $scope.error = err; $scope.$apply(); return; } @@ -317,7 +371,7 @@ angular.module('copayApp.controllers').controller('importController', profileService.importExtendedPublicKey(lopts, function(err, walletId) { ongoingProcess.set('importingWallet', false); if (err) { - self.error = err; + $scope.error = err; return $timeout(function() { $scope.$apply(); }); @@ -330,5 +384,5 @@ angular.module('copayApp.controllers').controller('importController', }; updateSeedSourceSelect(); - self.setSeedSource('new'); + $scope.setSeedSource('new'); }); diff --git a/src/js/services/onGoingProcess.js b/src/js/services/onGoingProcess.js index 9a9ed7547..836061f5f 100644 --- a/src/js/services/onGoingProcess.js +++ b/src/js/services/onGoingProcess.js @@ -28,6 +28,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti 'importingWallet': gettext('Importing Wallet...'), 'sweepingWallet': gettext('Sweeping Wallet...'), 'deletingWallet': gettext('Deleting Wallet...'), + 'extractingWalletInfo': gettext('Extracting Wallet Information...'), }; root.clear = function() { diff --git a/src/sass/main.scss b/src/sass/main.scss index ea0045783..daaeeb4b8 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -29,6 +29,16 @@ body { right: 0; } +.qr-scanner-input-import { + position: absolute; + top: -5px; + right: 0; +} + +.icon-close-import { + padding: 0px 40px 5px 10px; +} + h1, h2, h3, h4, h5, h6 { color: #2C3E50; } @@ -606,6 +616,14 @@ ul.manage li { margin-right: 10px; } +.m40r { + margin-right: 40px; +} + +.m25r { + margin-right: 25px; +} + .m10l { margin-left: 10px; } @@ -695,6 +713,10 @@ ul.manage li { padding-left: 25px; } +.p15l { + padding-left: 15px; +} + .p15 { padding: 15px; } @@ -869,6 +891,12 @@ ul.manage li { background-color: #1ABC9C; } +.lock-fromQR { + position: absolute; + width: 100%; + margin-top: 20px; +} + .tx-proposal i { padding: .1rem .3rem; background-color: #A5B2BF;