diff --git a/cordova/config.xml b/cordova/config.xml index 5e89a1ee9..dedcdcc57 100644 --- a/cordova/config.xml +++ b/cordova/config.xml @@ -1,8 +1,8 @@ + version="1.7.2" + android-versionCode="64" + ios-CFBundleVersion="1.7.2"> Copay A secure bitcoin wallet for friends and companies. diff --git a/cordova/ios/Copay-Info.plist b/cordova/ios/Copay-Info.plist index 7b01cd8f2..6f53a5723 100644 --- a/cordova/ios/Copay-Info.plist +++ b/cordova/ios/Copay-Info.plist @@ -57,11 +57,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.7.1 + 1.7.2 CFBundleSignature ???? CFBundleVersion - 1.7.1 + 1.7.2 LSRequiresIPhoneOS NSMainNibFile diff --git a/cordova/wp/Package.appxmanifest b/cordova/wp/Package.appxmanifest index 88a522150..dccb5be45 100644 --- a/cordova/wp/Package.appxmanifest +++ b/cordova/wp/Package.appxmanifest @@ -1,6 +1,6 @@  - + Copay Bitcoin Wallet diff --git a/cordova/wp/Properties/WMAppManifest.xml b/cordova/wp/Properties/WMAppManifest.xml index 93b29b475..f24055486 100644 --- a/cordova/wp/Properties/WMAppManifest.xml +++ b/cordova/wp/Properties/WMAppManifest.xml @@ -9,7 +9,7 @@ - + Assets\icon@2.png diff --git a/package.json b/package.json index 82906f9fa..72a8549e7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "copay", "description": "A multisignature wallet", "author": "BitPay", - "version": "1.7.1", + "version": "1.7.2", "keywords": [ "wallet", "copay", diff --git a/public/views/backup.html b/public/views/backup.html index 08efd6c1c..3d129432d 100644 --- a/public/views/backup.html +++ b/public/views/backup.html @@ -30,9 +30,9 @@ -->
-
+
Write your wallet seed
-
+
To restore this {{index.m}}-{{index.n}} shared wallet you will need : @@ -43,7 +43,7 @@
-
+
To restore this {{index.m}}-{{index.n}} shared wallet you will need : diff --git a/public/views/modals/txp-details.html b/public/views/modals/txp-details.html index 91a91cb5d..017491ea5 100644 --- a/public/views/modals/txp-details.html +++ b/public/views/modals/txp-details.html @@ -23,7 +23,7 @@ - + Multiple recipients @@ -42,7 +42,7 @@
-
+
-
-
+
+
* A payment proposal can be deleted if 1) you are the creator, and no other copayer has signed, or 2) 24 hours have passed since the proposal was created.
diff --git a/public/views/walletHome.html b/public/views/walletHome.html index 18af971ed..eba6ccf03 100644 --- a/public/views/walletHome.html +++ b/public/views/walletHome.html @@ -324,10 +324,10 @@

My Bitcoin address

- -
-
-
+ +
+
+
@@ -364,7 +364,7 @@ Request a specific amount
-
+
Share address diff --git a/src/css/mobile.css b/src/css/mobile.css index 468f7787a..cd9c9dd94 100644 --- a/src/css/mobile.css +++ b/src/css/mobile.css @@ -784,12 +784,12 @@ textarea:focus } .payment-proposal-to i { - margin-right: 10px; + position: absolute; + left: 25px; padding-right: 10px; border-right: 1px solid; border-color: rgba(255, 255, 255, 0.1); font-size: 20px; - vertical-align: middle; } ::-webkit-input-placeholder { diff --git a/src/js/controllers/glidera.js b/src/js/controllers/glidera.js index df876cf9a..612a8b6a7 100644 --- a/src/js/controllers/glidera.js +++ b/src/js/controllers/glidera.js @@ -3,7 +3,6 @@ angular.module('copayApp.controllers').controller('glideraController', function($rootScope, $scope, $timeout, $modal, profileService, configService, storageService, glideraService, isChromeApp, animationService, lodash) { - window.ignoreMobilePause = true; this.getAuthenticateUrl = function() { return glideraService.getOauthCodeUrl(); diff --git a/src/js/controllers/index.js b/src/js/controllers/index.js index 17a43609e..4f5c44b7a 100644 --- a/src/js/controllers/index.js +++ b/src/js/controllers/index.js @@ -1463,20 +1463,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r self.setTab(tab, reset); }); - $rootScope.$on('Local/RequestTouchid', function(event, cb) { - window.plugins.touchid.verifyFingerprint( - gettextCatalog.getString('Scan your fingerprint please'), - function(msg) { - // OK - return cb(); - }, - function(msg) { - // ERROR - return cb(gettext('Invalid Touch ID')); - } - ); - }); - $rootScope.$on('Local/NeedsPassword', function(event, isSetup, cb) { self.askPassword = { isSetup: isSetup, @@ -1485,6 +1471,9 @@ angular.module('copayApp.controllers').controller('indexController', function($r return cb(err, pass); }, }; + $timeout(function() { + $rootScope.$apply(); + }); }); lodash.each(['NewCopayer', 'CopayerUpdated'], function(eventName) { diff --git a/src/js/controllers/paymentUri.js b/src/js/controllers/paymentUri.js index 3ae5ad766..c202945bf 100644 --- a/src/js/controllers/paymentUri.js +++ b/src/js/controllers/paymentUri.js @@ -43,9 +43,7 @@ angular.module('copayApp.controllers').controller('paymentUriController', this.selectWallet = function(wid) { var self = this; - if (wid != profileService.focusedClient.credentials.walletId) { - profileService.setAndStoreFocus(wid, function() {}); - } + profileService.setAndStoreFocus(wid, function() {}); $timeout(function() { $rootScope.$emit('paymentUri', self.bitcoinURI); }, 1000); diff --git a/src/js/controllers/sellGlidera.js b/src/js/controllers/sellGlidera.js index cbae3f8f3..a3bc09b62 100644 --- a/src/js/controllers/sellGlidera.js +++ b/src/js/controllers/sellGlidera.js @@ -1,7 +1,7 @@ 'use strict'; -angular.module('copayApp.controllers').controller('sellGlideraController', - function($scope, $timeout, $log, $modal, configService, profileService, addressService, feeService, glideraService, bwsError, lodash, isChromeApp, animationService) { +angular.module('copayApp.controllers').controller('sellGlideraController', + function($scope, $timeout, $log, $modal, configService, profileService, addressService, feeService, glideraService, bwsError, lodash, isChromeApp, animationService, txSignService) { var self = this; var config = configService.getSync(); @@ -38,7 +38,7 @@ angular.module('copayApp.controllers').controller('sellGlideraController', }, 100); } }); - } catch(e) { + } catch (e) { $log.debug(e); }; }; @@ -57,12 +57,14 @@ angular.module('copayApp.controllers').controller('sellGlideraController', $scope.selectWallet = function(walletId, walletName) { if (!profileService.getClient(walletId).isComplete()) { - self.error = bwsError.msg({'code': 'WALLET_NOT_COMPLETE'}, 'Could not choose the wallet'); + self.error = bwsError.msg({ + 'code': 'WALLET_NOT_COMPLETE' + }, 'Could not choose the wallet'); $modalInstance.dismiss('cancel'); return; } $modalInstance.close({ - 'walletId': walletId, + 'walletId': walletId, 'walletName': walletName, }); }; @@ -70,8 +72,8 @@ angular.module('copayApp.controllers').controller('sellGlideraController', var modalInstance = $modal.open({ templateUrl: 'views/modals/glidera-wallets.html', - windowClass: animationService.modalAnimated.slideUp, - controller: ModalInstanceCtrl, + windowClass: animationService.modalAnimated.slideUp, + controller: ModalInstanceCtrl, }); modalInstance.result.finally(function() { @@ -101,24 +103,22 @@ angular.module('copayApp.controllers').controller('sellGlideraController', self.gettingSellPrice = false; if (err) { self.error = 'Could not get exchange information. Please, try again.'; - } - else { + } else { self.error = null; self.sellPrice = sellPrice; } - }); + }); }; this.get2faCode = function(token) { var self = this; - this.loading = 'Sending 2FA code...'; + self.loading = 'Sending 2FA code...'; $timeout(function() { glideraService.get2faCode(token, function(err, sent) { self.loading = null; if (err) { self.error = 'Could not send confirmation code to your phone'; - } - else { + } else { self.show2faCodeInput = sent; } }); @@ -129,107 +129,86 @@ angular.module('copayApp.controllers').controller('sellGlideraController', var self = this; self.error = null; - this.loading = 'Selling Bitcoin...'; - $timeout(function() { - addressService.getAddress(fc.credentials.walletId, null, function(err, refundAddress) { - if (!refundAddress) { - self.loading = null; - self.error = bwsError.msg(err, 'Could not create address'); - return; - } - glideraService.getSellAddress(token, function(error, sellAddress) { - if (!sellAddress) { + + txSignService.prepare(function(err) { + if (err) { + self.error = err; + return; + } + self.loading = 'Selling Bitcoin...'; + $timeout(function() { + addressService.getAddress(fc.credentials.walletId, null, function(err, refundAddress) { + if (!refundAddress) { self.loading = null; - self.error = 'Could not get the destination bitcoin address'; + self.error = bwsError.msg(err, 'Could not create address'); return; } - var amount = parseInt((self.sellPrice.qty * 100000000).toFixed(0)); + glideraService.getSellAddress(token, function(error, sellAddress) { + if (!sellAddress) { + self.loading = null; + self.error = 'Could not get the destination bitcoin address'; + return; + } + var amount = parseInt((self.sellPrice.qty * 100000000).toFixed(0)); - feeService.getCurrentFeeValue(self.currentFeeLevel, function(err, feePerKb) { - if (err) $log.debug(err); - fc.sendTxProposal({ - toAddress: sellAddress, - amount: amount, - message: 'Glidera transaction', - customData: {'glideraToken': token}, - payProUrl: null, - feePerKb: feePerKb, - excludeUnconfirmedUtxos: self.currentSpendUnconfirmed ? false : true - }, function(err, txp) { - if (err) { - profileService.lockFC(); - $log.error(err); - $timeout(function() { - self.loading = null; - self.error = bwsError.msg(err, 'Error'); - }, 1); - return; - } - - if (!fc.canSign()) { - self.loading = null; - $log.info('No signing proposal: No private key'); - return; - } - - _signTx(txp, function(err, txp, rawTx) { - profileService.lockFC(); + feeService.getCurrentFeeValue(self.currentFeeLevel, function(err, feePerKb) { + if (err) $log.debug(err); + fc.sendTxProposal({ + toAddress: sellAddress, + amount: amount, + message: 'Glidera transaction', + customData: { + 'glideraToken': token + }, + payProUrl: null, + feePerKb: feePerKb, + excludeUnconfirmedUtxos: self.currentSpendUnconfirmed ? false : true + }, function(err, txp) { if (err) { - self.loading = null; - self.error = err; - $scope.$apply(); - } - else { - var data = { - refundAddress: refundAddress, - signedTransaction: rawTx, - priceUuid: self.sellPrice.priceUuid, - useCurrentPrice: self.sellPrice.priceUuid ? false : true, - ip: null - }; - glideraService.sell(token, twoFaCode, data, function(err, data) { + profileService.lockFC(); + $log.error(err); + $timeout(function() { self.loading = null; - if (err) { - self.error = err; - fc.removeTxProposal(txp, function(err, txpb) { - $timeout(function() { - $scope.$emit('Local/GlideraError'); - }, 100); - }); - } - else { - self.success = data; - $scope.$emit('Local/GlideraTx'); - } - }); + self.error = bwsError.msg(err, 'Error'); + }, 1); + return; } + + txSignService.sign(txp, function(err, txp) { + if (err) { + self.loading = null; + self.error = err; + $scope.$apply(); + } else { + var rawTx = txp.raw; + var data = { + refundAddress: refundAddress, + signedTransaction: rawTx, + priceUuid: self.sellPrice.priceUuid, + useCurrentPrice: self.sellPrice.priceUuid ? false : true, + ip: null + }; + glideraService.sell(token, twoFaCode, data, function(err, data) { + self.loading = null; + if (err) { + self.error = err; + fc.removeTxProposal(txp, function(err, txp) { + $timeout(function() { + $scope.$emit('Local/GlideraError'); + }, 100); + }); + } else { + self.success = data; + $scope.$emit('Local/GlideraTx'); + } + }); + } + }); }); }); }); }); - }); - - }, 100); - - }; - - var _signTx = function(txp, cb) { - var self = this; - fc.signTxProposal(txp, function(err, signedTx) { - profileService.lockFC(); - if (err) { - err = bwsError.msg(err, 'Could not accept payment'); - return cb(err); - } - else { - if (signedTx.status == 'accepted') { - return cb(null, txp, signedTx.raw); - - } else { - return cb('The transaction could not be signed'); - } - } + }, 100); }); }; - }); diff --git a/src/js/controllers/walletHome.js b/src/js/controllers/walletHome.js index a9b977348..8bd468743 100644 --- a/src/js/controllers/walletHome.js +++ b/src/js/controllers/walletHome.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletHomeController', function($scope, $rootScope, $timeout, $filter, $modal, $log, notification, txStatus, isCordova, isMobile, profileService, lodash, configService, rateService, storageService, bitcore, isChromeApp, gettext, gettextCatalog, nodeWebkit, addressService, ledger, bwsError, confirmDialog, txFormatService, animationService, addressbookService, go, feeService) { +angular.module('copayApp.controllers').controller('walletHomeController', function($scope, $rootScope, $timeout, $filter, $modal, $log, notification, txStatus, isCordova, isMobile, profileService, lodash, configService, rateService, storageService, bitcore, isChromeApp, gettext, gettextCatalog, nodeWebkit, addressService, ledger, bwsError, confirmDialog, txFormatService, animationService, addressbookService, go, feeService, txSignService) { var self = this; window.ignoreMobilePause = false; @@ -87,16 +87,6 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi $rootScope.hideMenuBar = false; }); - var requestTouchid = function(cb) { - var fc = profileService.focusedClient; - config.touchIdFor = config.touchIdFor || {}; - if (window.touchidAvailable && config.touchIdFor[fc.credentials.walletId]) { - $rootScope.$emit('Local/RequestTouchid', cb); - } else { - return cb(); - } - }; - this.onQrCodeScanned = function(data) { if (data) go.send(); $rootScope.$emit('dataScanned', data); @@ -335,72 +325,26 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi $scope.sign = function(txp) { var fc = profileService.focusedClient; - - if (!fc.canSign() && !fc.isPrivKeyExternal()) - return; - - if (fc.isPrivKeyEncrypted()) { - profileService.unlockFC(function(err) { - if (err) { - $scope.error = bwsError.msg(err); - return; - } - return $scope.sign(txp); - }); - return; - }; - - self._setOngoingForSigning(); - $scope.loading = true; $scope.error = null; - $timeout(function() { - requestTouchid(function(err) { - if (err) { - self.setOngoingProcess(); - $scope.loading = false; - profileService.lockFC(); - $scope.error = err; + $scope.loading = true; + + txSignService.prepareAndSignAndBroadcast(txp, { + reporterFn: self.setOngoingProcess.bind(self) + }, function(err, txp) { + $scope.loading = false; + $scope.$emit('UpdateTx'); + + if (err) { + $scope.error = err; + $timeout(function() { $scope.$digest(); - return; - } - - profileService.signTxProposal(txp, function(err, txpsi) { - self.setOngoingProcess(); - if (err) { - $scope.$emit('UpdateTx'); - $scope.loading = false; - $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not accept payment')); - $scope.$digest(); - } else { - //if txp has required signatures then broadcast it - var txpHasRequiredSignatures = txpsi.status == 'accepted'; - if (txpHasRequiredSignatures) { - self.setOngoingProcess(gettextCatalog.getString('Broadcasting transaction')); - $scope.loading = true; - fc.broadcastTxProposal(txpsi, function(err, txpsb, memo) { - self.setOngoingProcess(); - $scope.loading = false; - if (err) { - $scope.$emit('UpdateTx'); - $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not broadcast payment')); - $scope.$digest(); - } else { - $log.debug('Transaction signed and broadcasted') - if (memo) - $log.info(memo); - - refreshUntilItChanges = true; - $modalInstance.close(txpsb); - } - }); - } else { - $scope.loading = false; - $modalInstance.close(txpsi); - } - } }); - }); - }, 100); + return; + } + refreshUntilItChanges = true; + $modalInstance.close(txp); + return; + }); }; $scope.reject = function(txp) { @@ -833,6 +777,8 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi var currentSpendUnconfirmed = configWallet.spendUnconfirmed; var currentFeeLevel = walletSettings.feeLevel || 'normal'; + this.resetError(); + if (isCordova && this.isWindowsPhoneApp) { this.hideAddress = false; this.hideAmount = false; @@ -844,14 +790,6 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi return; } - if (fc.isPrivKeyEncrypted()) { - profileService.unlockFC(function(err) { - if (err) return self.setSendError(err); - return self.submitForm(); - }); - return; - }; - var comment = form.comment.$modelValue; // ToDo: use a credential's (or fc's) function for this @@ -869,7 +807,6 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi } }; - self.setOngoingProcess(gettextCatalog.getString('Creating transaction')); $timeout(function() { var paypro = self._paypro; var address, amount; @@ -877,17 +814,11 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi address = form.address.$modelValue; amount = parseInt((form.amount.$modelValue * unitToSat).toFixed(0)); - requestTouchid(function(err) { + txSignService.prepare(function(err) { if (err) { - profileService.lockFC(); - self.setOngoingProcess(); - self.error = err; - $timeout(function() { - $scope.$digest(); - }, 1); - return; + return self.setSendError(err); } - + self.setOngoingProcess(gettextCatalog.getString('Creating transaction')); getFee(function(err, feePerKb) { if (err) $log.debug(err); fc.sendTxProposal({ @@ -900,13 +831,12 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi }, function(err, txp) { if (err) { self.setOngoingProcess(); - profileService.lockFC(); return self.setSendError(err); } if (!fc.canSign() && !fc.isPrivKeyExternal()) { - $log.info('No signing proposal: No private key') self.setOngoingProcess(); + $log.info('No signing proposal: No private key') self.resetForm(); txStatus.notify(txp, function() { return $scope.$emit('Local/TxProposalAction'); @@ -914,8 +844,9 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi return; } - self.signAndBroadcast(txp, function(err) { - self.setOngoingProcess(); + txSignService.signAndBroadcast(txp, { + reporterFn: self.setOngoingProcess.bind(self) + }, function(err, txp) { self.resetForm(); if (err) { self.error = err.message ? err.message : gettext('The payment was created but could not be completed. Please try again from home screen'); @@ -923,7 +854,12 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi $timeout(function() { $scope.$digest(); }, 1); - } else go.walletHome(); + } else { + go.walletHome(); + txStatus.notify(txp, function() { + $scope.$emit('Local/TxProposalAction', true); + }); + }; }); }); }); @@ -931,56 +867,6 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi }, 100); }; - this._setOngoingForSigning = function() { - var fc = profileService.focusedClient; - - if (fc.isPrivKeyExternal() && fc.getPrivKeyExternalSourceName() == 'ledger') { - self.setOngoingProcess(gettextCatalog.getString('Requesting Ledger Wallet to sign')); - } else { - self.setOngoingProcess(gettextCatalog.getString('Signing payment')); - } - }; - - this.signAndBroadcast = function(txp, cb) { - var fc = profileService.focusedClient; - - this._setOngoingForSigning(); - profileService.signTxProposal(txp, function(err, signedTx) { - self.setOngoingProcess(); - if (err) { - if (!lodash.isObject(err)) { - err = { message: err}; - } - err.message = bwsError.msg(err, gettextCatalog.getString('The payment was created but could not be signed. Please try again from home screen')); - return cb(err); - } - - if (signedTx.status == 'accepted') { - self.setOngoingProcess(gettextCatalog.getString('Broadcasting transaction')); - fc.broadcastTxProposal(signedTx, function(err, btx, memo) { - self.setOngoingProcess(); - if (err) { - err.message = bwsError.msg(err, gettextCatalog.getString('The payment was signed but could not be broadcasted. Please try again from home screen')); - return cb(err); - } - if (memo) - $log.info(memo); - - txStatus.notify(btx, function() { - $scope.$emit('Local/TxProposalAction', true); - return cb(); - }); - }); - } else { - self.setOngoingProcess(); - txStatus.notify(signedTx, function() { - $scope.$emit('Local/TxProposalAction'); - return cb(); - }); - } - }); - }; - this.setForm = function(to, amount, comment) { var form = $scope.sendForm; if (to) { diff --git a/src/js/init.js b/src/js/init.js index 3bcc05d45..298a40883 100644 --- a/src/js/init.js +++ b/src/js/init.js @@ -46,7 +46,7 @@ angular.element(document).ready(function() { } setTimeout(function() { var loc = window.location; - var ignoreMobilePause = loc.toString().match(/(glidera|buy|sell)/) ? true : false; + var ignoreMobilePause = loc.toString().match(/(buy|sell|uri-payment)/) ? true : false; window.ignoreMobilePause = ignoreMobilePause; }, 100); }, false); diff --git a/src/js/services/profileService.js b/src/js/services/profileService.js index 952882480..37ff6a700 100644 --- a/src/js/services/profileService.js +++ b/src/js/services/profileService.js @@ -1,6 +1,6 @@ 'use strict'; angular.module('copayApp.services') - .factory('profileService', function profileServiceFactory($rootScope, $location, $timeout, $filter, $log, lodash, storageService, bwcService, configService, notificationService, isChromeApp, isCordova, gettext, gettextCatalog, nodeWebkit, bwsError, uxLanguage, ledger, bitcore, trezor) { + .factory('profileService', function profileServiceFactory($rootScope, $location, $timeout, $filter, $log, lodash, storageService, bwcService, configService, notificationService, isChromeApp, isCordova, gettext, gettextCatalog, nodeWebkit, bwsError, uxLanguage, bitcore) { var root = {}; @@ -99,6 +99,11 @@ angular.module('copayApp.services') root.walletClients[credentials.walletId].started = true; root.walletClients[credentials.walletId].doNotVerifyPayPro = isChromeApp; + if (client.hasPrivKeyEncrypted() && !client.isPrivKeyEncrypted()) { + $log.warn('Auto locking unlocked wallet:' + credentials.walletId); + client.lock(); + } + client.initialize({}, function(err) { if (err) { $log.error('Could not init notifications err:', err); @@ -110,8 +115,8 @@ angular.module('copayApp.services') root.setWalletClients = function() { var credentials = root.profile.credentials; - lodash.each(credentials, function(credentials) { - root.setWalletClient(credentials); + lodash.each(credentials, function(credential) { + root.setWalletClient(credential); }); $rootScope.$emit('Local/WalletListUpdated'); }; @@ -630,6 +635,10 @@ angular.module('copayApp.services') root.unlockFC = function(cb) { var fc = root.focusedClient; + + if (!fc.isPrivKeyEncrypted()) + return cb(); + $log.debug('Wallet is encrypted'); $rootScope.$emit('Local/NeedsPassword', false, function(err2, password) { if (err2 || !password) { @@ -645,12 +654,6 @@ angular.module('copayApp.services') message: gettext('Wrong password') }); } - $timeout(function() { - if (fc.hasPrivKeyEncrypted()) { - $log.debug('Locking wallet automatically'); - root.lockFC(); - }; - }, 2000); return cb(); }); }; @@ -680,59 +683,5 @@ angular.module('copayApp.services') return lodash.sortBy(ret, 'name'); }; - root._signWithLedger = function(txp, cb) { - var fc = root.focusedClient; - $log.info('Requesting Ledger Chrome app to sign the transaction'); - - ledger.signTx(txp, fc.credentials.account, function(result) { - $log.debug('Ledger response', result); - if (!result.success) - return cb(result.message || result.error); - - txp.signatures = lodash.map(result.signatures, function(s) { - return s.substring(0, s.length - 2); - }); - return fc.signTxProposal(txp, cb); - }); - }; - - - root._signWithTrezor = function(txp, cb) { - var fc = root.focusedClient; - $log.info('Requesting Trezor to sign the transaction'); - - var xPubKeys = lodash.pluck(fc.credentials.publicKeyRing, 'xPubKey'); - trezor.signTx(xPubKeys, txp, fc.credentials.account, function(err, result) { - if (err) return cb(err); - - $log.debug('Trezor response', result); - txp.signatures = result.signatures; - return fc.signTxProposal(txp, cb); - }); - }; - - - root.signTxProposal = function(txp, cb) { - var fc = root.focusedClient; - - if (fc.isPrivKeyExternal()) { - switch (fc.getPrivKeyExternalSourceName()) { - case 'ledger': - return root._signWithLedger(txp, cb); - case 'trezor': - return root._signWithTrezor(txp, cb); - default: - var msg = 'Unsupported External Key:' + fc.getPrivKeyExternalSourceName(); - $log.error(msg); - return cb(msg); - } - } else { - return fc.signTxProposal(txp, function(err, signedTxp) { - root.lockFC(); - return cb(err, signedTxp); - }); - } - }; - return root; }); diff --git a/src/js/services/txSignService.js b/src/js/services/txSignService.js new file mode 100644 index 000000000..fb71cf289 --- /dev/null +++ b/src/js/services/txSignService.js @@ -0,0 +1,184 @@ +'use strict'; + +angular.module('copayApp.services').factory('txSignService', function($rootScope, profileService, gettextCatalog, lodash, trezor, ledger, configService, bwsError, $log) { + var root = {}; + var config = configService.getSync(); + + var reportSigningStatus = function(opts) { + if (!opts.reporterFn) return; + + var fc = profileService.focusedClient; + + if (fc.isPrivKeyExternal()) { + if (fc.getPrivKeyExternalSourceName() == 'ledger') { + opts.reporterFn(gettextCatalog.getString('Requesting Ledger Wallet to sign')); + } else if (fc.getPrivKeyExternalSourceName() == 'trezor') { + opts.reporterFn(gettextCatalog.getString('Requesting Trezor Wallet to sign')); + } + } else { + opts.reporterFn(gettextCatalog.getString('Signing payment')); + } + }; + + var reportBroadcastingStatus = function(opts) { + if (!opts.reporterFn) return; + + opts.reporterFn(gettextCatalog.getString('Broadcasting transaction')); + }; + + var stopReport = function(opts) { + if (!opts.reporterFn) return; + + opts.reporterFn(); + }; + + var requestTouchId = function(cb) { + try { + window.plugins.touchid.verifyFingerprint( + gettextCatalog.getString('Scan your fingerprint please'), + function(msg) { + $log.debug('Touch ID OK'); + return cb(); + }, + function(msg) { + $log.debug('Touch ID Failed:' + msg); + return cb(gettext('Touch ID Failed:') + msg); + } + ); + } catch (e) { + $log.debug('Touch ID Failed:' + e); + return cb(gettext('Touch ID Failed:') + e); + }; + }; + + root.checkTouchId = function(cb) { + var fc = profileService.focusedClient; + config.touchIdFor = config.touchIdFor || {}; + if (window.touchidAvailable && config.touchIdFor[fc.credentials.walletId]) { + requestTouchId(cb); + } else { + return cb(); + } + }; + + root.prepare = function(cb) { + var fc = profileService.focusedClient; + if (!fc.canSign() && !fc.isPrivKeyExternal()) + return cb('Cannot sign'); // should never happen, no need to translate + + root.checkTouchId(function(err) { + if (err) { + return cb(err); + }; + + profileService.unlockFC(function(err) { + if (err) { + return cb(bwsError.msg(err)); + }; + + return cb(); + }); + }); + }; + + var _signWithLedger = function(txp, cb) { + var fc = root.focusedClient; + $log.info('Requesting Ledger Chrome app to sign the transaction'); + + ledger.signTx(txp, fc.credentials.account, function(result) { + $log.debug('Ledger response', result); + if (!result.success) + return cb(result.message || result.error); + + txp.signatures = lodash.map(result.signatures, function(s) { + return s.substring(0, s.length - 2); + }); + return fc.signTxProposal(txp, cb); + }); + }; + + var _signWithTrezor = function(txp, cb) { + var fc = root.focusedClient; + $log.info('Requesting Trezor to sign the transaction'); + + var xPubKeys = lodash.pluck(fc.credentials.publicKeyRing, 'xPubKey'); + trezor.signTx(xPubKeys, txp, fc.credentials.account, function(err, result) { + if (err) return cb(err); + + $log.debug('Trezor response', result); + txp.signatures = result.signatures; + return fc.signTxProposal(txp, cb); + }); + }; + + root.sign = function(txp, cb) { + var fc = profileService.focusedClient; + + if (fc.isPrivKeyExternal()) { + switch (fc.getPrivKeyExternalSourceName()) { + case 'ledger': + return _signWithLedger(txp, cb); + case 'trezor': + return _signWithTrezor(txp, cb); + default: + var msg = 'Unsupported External Key:' + fc.getPrivKeyExternalSourceName(); + $log.error(msg); + return cb(msg); + } + } else { + fc.signTxProposal(txp, function(err, signedTxp) { + profileService.lockFC(); + return cb(err, signedTxp); + }); + } + }; + + root.signAndBroadcast = function(txp, opts, cb) { + reportSigningStatus(opts); + + var fc = profileService.focusedClient; + root.sign(txp, function(err, txp) { + if (err) { + stopReport(opts); + return cb(bwsError.msg(err), gettextCatalog.getString('Could not accept payment')); + }; + + if (txp.status != 'accepted') + return (null, txp); + + reportBroadcastingStatus(opts); + fc.broadcastTxProposal(txp, function(err, txp, memo) { + stopReport(opts); + + if (err) { + return cb(bwsError.msg(err, gettextCatalog.getString('Could not broadcast payment'))); + }; + + $log.debug('Transaction signed and broadcasted') + + if (memo) + $log.info(memo); + + return cb(null, txp); + }); + }); + }; + + root.prepareAndSignAndBroadcast = function(txp, opts, cb) { + reportSigningStatus(opts); + root.prepare(function(err) { + if (err) { + stopReport(opts); + return cb(err); + }; + root.signAndBroadcast(txp, opts, function(err, txp) { + if (err) { + stopReport(opts); + return cb(err); + }; + return cb(null, txp); + }); + }); + }; + return root; +}); diff --git a/webkitbuilds/.desktop b/webkitbuilds/.desktop index c9b59610a..91a53b6d1 100644 --- a/webkitbuilds/.desktop +++ b/webkitbuilds/.desktop @@ -1,6 +1,6 @@ [Desktop Entry] Type=Application -Version=1.7.1 +Version=1.7.2 Name=Copay Comment=A multisignature wallet Exec=copay diff --git a/webkitbuilds/setup-win.iss b/webkitbuilds/setup-win.iss index dff677608..fdf0a34d1 100755 --- a/webkitbuilds/setup-win.iss +++ b/webkitbuilds/setup-win.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "Copay" -#define MyAppVersion "1.7.1" +#define MyAppVersion "1.7.2" #define MyAppPublisher "BitPay" #define MyAppURL "https://copay.io" #define MyAppExeName "Copay.exe"