Bug/select wallet incomplete / Refactor wallet Services (#4159)

* Addressbook: display error if select an incomplete wallet

* Check if wallet is complete/needs_backup for Glidera and Coinbase

* Ref/create a walletService

* Ref. walletService

* Fix Glidera and Coinbase

* Removes txService

* Fix glidera connection for mobile. Fix bitcode for xcode

* Fix duplicated entry

* Revert "Bump bwc version 2.3.1"

* adds karma-mocha

* Refactor

* Refactor lock function

* Refactor reject, remove and broadcast tx

* add walletService tests WIP

* add walletService tests WIP 2

* merge

* update tests to mocha

* fix tests. Angular 1.5?

* Fix test

* Generate angular-bwc before testing

* Rever gitignore

* Wording
This commit is contained in:
Gustavo Maximiliano Cortez 2016-05-09 15:56:44 -03:00
commit 98471e952a
No known key found for this signature in database
GPG key ID: 15EDAD8D9F2EB1AF
27 changed files with 698 additions and 628 deletions

View file

@ -256,7 +256,7 @@ module.exports = function(grunt) {
grunt.registerTask('prod', ['default', 'uglify']); grunt.registerTask('prod', ['default', 'uglify']);
grunt.registerTask('translate', ['nggettext_extract']); grunt.registerTask('translate', ['nggettext_extract']);
grunt.registerTask('test', ['karma:unit']); grunt.registerTask('test', ['karma:unit']);
grunt.registerTask('test-coveralls', ['karma:prod', 'coveralls']); grunt.registerTask('test-coveralls', ['browserify', 'karma:prod', 'coveralls']);
grunt.registerTask('desktop', ['prod', 'nodewebkit', 'copy:linux', 'compress:linux']); grunt.registerTask('desktop', ['prod', 'nodewebkit', 'copy:linux', 'compress:linux']);
grunt.registerTask('osx', ['prod', 'nodewebkit', 'exec:osx']); grunt.registerTask('osx', ['prod', 'nodewebkit', 'exec:osx']);
grunt.registerTask('release', ['string-replace:dist']); grunt.registerTask('release', ['string-replace:dist']);

View file

@ -8,6 +8,7 @@
], ],
"dependencies": { "dependencies": {
"angular": "1.4.6", "angular": "1.4.6",
"angular-mocks": "1.4.10",
"angular-foundation": "0.8.0", "angular-foundation": "0.8.0",
"angular-gettext": "2.2.1", "angular-gettext": "2.2.1",
"angular-moment": "0.10.1", "angular-moment": "0.10.1",

View file

@ -79,9 +79,6 @@ if [ ! -d $PROJECT ]; then
echo "${OpenColor}${Green}* Installing plugins... ${CloseColor}" echo "${OpenColor}${Green}* Installing plugins... ${CloseColor}"
cordova plugin add cordova-plugin-disable-bitcode
checkOK
cordova plugin add https://github.com/florentvaldelievre/virtualartifacts-webIntent.git cordova plugin add https://github.com/florentvaldelievre/virtualartifacts-webIntent.git
checkOK checkOK
@ -158,6 +155,9 @@ if [ ! -d $PROJECT ]; then
cordova plugin add cordova-ios-requires-fullscreen cordova plugin add cordova-ios-requires-fullscreen
checkOK checkOK
cordova plugin add cordova-plugin-disable-bitcode
checkOK
fi fi
if $DBGJS if $DBGJS

View file

@ -63,21 +63,28 @@
}, },
"devDependencies": { "devDependencies": {
"adm-zip": "^0.4.7", "adm-zip": "^0.4.7",
"angular": "^1.3.14", "angular": "1.4.6",
"angular-mocks": "^1.3.14", "angular-mocks": "1.4.10",
"bhttp": "^1.2.1", "bhttp": "^1.2.1",
"chai": "^3.5.0",
"cordova": "5.4.1", "cordova": "5.4.1",
"cordova-android": "5.1.1", "cordova-android": "5.1.1",
"grunt-contrib-sass": "^1.0.0", "grunt-contrib-sass": "^1.0.0",
"grunt-karma": "^0.10.1", "grunt-karma": "^1.0.0",
"grunt-karma-coveralls": "^2.5.3", "grunt-karma-coveralls": "^2.5.4",
"grunt-node-webkit-builder": "^1.0.2", "grunt-node-webkit-builder": "^1.0.2",
"grunt-string-replace": "^1.2.1", "grunt-string-replace": "^1.2.1",
"karma": "^0.12.31", "karma": "^0.13.22",
"karma-cli": "0.0.4", "karma-chai": "^0.1.0",
"karma-coverage": "^0.2.7", "karma-chrome-launcher": "^1.0.1",
"karma-jasmine": "^0.3.5", "karma-cli": "^1.0.0",
"karma-phantomjs-launcher": "^0.1.4", "karma-coverage": "^1.0.0",
"karma-mocha": "^1.0.1",
"karma-mocha-reporter": "^2.0.3",
"karma-phantomjs-launcher": "^1.0.0",
"karma-sinon": "^1.0.5",
"mocha": "^2.4.5",
"phantomjs-prebuilt": "^2.1.7",
"xcode": "^0.8.2" "xcode": "^0.8.2"
} }
} }

View file

@ -35,7 +35,7 @@
<div class="row m20t"> <div class="row m20t">
<div class="columns"> <div class="columns">
<div class="box-notification m20b" ng-show="index.glideraLimits.transactDisabledPendingFirstTransaction"> <div class="box-notification m20b" ng-show="index.glideraLimits.transactDisabledPendingFirstTransaction && !buy.success">
<span class="text-warning"> <span class="text-warning">
This operation was disabled because you have a pending first transaction This operation was disabled because you have a pending first transaction
</span> </span>

View file

@ -23,7 +23,7 @@
<div class="large-12 columns"> <div class="large-12 columns">
<div class="text-center"> <div class="text-center">
<img src="img/glidera-logo.png" <img src="img/glidera-logo.png"
ng-click="index.updateGlidera(index.glideraToken, index.glideraPermissions)" width="100"> ng-click="index.updateGlidera()" width="100">
</div> </div>
<div class="m10t text-center" ng-show="glidera.error"> <div class="m10t text-center" ng-show="glidera.error">

View file

@ -64,9 +64,9 @@
<i class="icon-wallet size-21"></i> <i class="icon-wallet size-21"></i>
</div> </div>
<div class="ellipsis name-wallet text-bold">{{w.name || w.id}} <div class="ellipsis name-wallet text-bold">{{w.name || w.id}}
<span class="has-error right text-light size-12" ng-show="needsBackup[w.id]"> <span class="has-error right text-light size-12" ng-show="errorSelectedWallet[w.id]">
<i class="icon-close-circle size-14"></i> <i class="icon-close-circle size-14"></i>
<span class="vm" translate>Needs backup</span> <span class="vm">{{errorSelectedWallet[w.id] }}</span>
</span> </span>
</div> </div>
<div class="size-12">{{w.m}} of {{w.n}} <div class="size-12">{{w.m}} of {{w.n}}

View file

@ -1,13 +1,21 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('backupController', angular.module('copayApp.controllers').controller('backupController',
function($rootScope, $scope, $timeout, $log, $state, $compile, go, lodash, profileService, gettext, bwcService, bwsError) { function($rootScope, $scope, $timeout, $log, $state, $compile, go, lodash, profileService, gettext, bwcService, bwsError, walletService) {
var self = this; var self = this;
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
var customWords = []; var customWords = [];
self.walletName = fc.credentials.walletName; self.walletName = fc.credentials.walletName;
var handleEncryptedWallet = function(client, cb) {
if (!walletService.isEncrypted(client)) return cb();
$rootScope.$emit('Local/NeedsPassword', false, function(err, password) {
if (err) return cb(err);
return cb(walletService.unlock(client, password));
});
};
function init() { function init() {
$scope.passphrase = ''; $scope.passphrase = '';
resetAllButtons(); resetAllButtons();
@ -46,7 +54,7 @@ angular.module('copayApp.controllers').controller('backupController',
function initWords() { function initWords() {
var words = fc.getMnemonic(); var words = fc.getMnemonic();
self.xPrivKey = fc.credentials.xPrivKey; self.xPrivKey = fc.credentials.xPrivKey;
profileService.lockFC(); walletService.lock(fc);
self.mnemonicWords = words.split(/[\u3000\s]+/); self.mnemonicWords = words.split(/[\u3000\s]+/);
self.shuffledMnemonicWords = lodash.sortBy(self.mnemonicWords);; self.shuffledMnemonicWords = lodash.sortBy(self.mnemonicWords);;
self.mnemonicHasPassphrase = fc.mnemonicHasPassphrase(); self.mnemonicHasPassphrase = fc.mnemonicHasPassphrase();
@ -74,7 +82,7 @@ angular.module('copayApp.controllers').controller('backupController',
$scope.$apply(); $scope.$apply();
}, 1); }, 1);
profileService.unlockFC({}, function(err) { handleEncryptedWallet(fc, function(err) {
if (err) { if (err) {
self.error = bwsError.msg(err, gettext('Could not decrypt')); self.error = bwsError.msg(err, gettext('Could not decrypt'));
$log.warn('Error decrypting credentials:', self.error); //TODO $log.warn('Error decrypting credentials:', self.error); //TODO

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('buyCoinbaseController', angular.module('copayApp.controllers').controller('buyCoinbaseController',
function($scope, $modal, $log, $timeout, lodash, profileService, coinbaseService, animationService, txService, bwsError, addressService) { function($scope, $modal, $log, $timeout, lodash, profileService, coinbaseService, animationService, bwsError, addressService, walletService) {
window.ignoreMobilePause = true; window.ignoreMobilePause = true;
var self = this; var self = this;
@ -71,17 +71,17 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController',
}; };
$scope.selectWallet = function(walletId, walletName) { $scope.selectWallet = function(walletId, walletName) {
if (!profileService.getClient(walletId).isComplete()) { var client = profileService.getClient(walletId);
self.error = bwsError.msg({ walletService.isReady(client, function(err) {
'code': 'WALLET_NOT_COMPLETE' if (err) {
}, 'Could not choose the wallet'); self.error = {errors: [{ message: err }]};
self.error = {errors: [{ message: 'The Wallet could not be selected' }]}; $modalInstance.dismiss('cancel');
$modalInstance.dismiss('cancel'); } else {
return; $modalInstance.close({
} 'walletId': walletId,
$modalInstance.close({ 'walletName': walletName,
'walletId': walletId, });
'walletName': walletName, }
}); });
}; };
}; };

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('buyGlideraController', angular.module('copayApp.controllers').controller('buyGlideraController',
function($scope, $timeout, $modal, profileService, addressService, glideraService, bwsError, lodash, isChromeApp, animationService) { function($scope, $timeout, $modal, profileService, addressService, glideraService, bwsError, lodash, isChromeApp, animationService, walletService) {
var self = this; var self = this;
this.show2faCodeInput = null; this.show2faCodeInput = null;
@ -50,14 +50,17 @@ angular.module('copayApp.controllers').controller('buyGlideraController',
}; };
$scope.selectWallet = function(walletId, walletName) { $scope.selectWallet = function(walletId, walletName) {
if (!profileService.getClient(walletId).isComplete()) { var client = profileService.getClient(walletId);
self.error = bwsError.msg({'code': 'WALLET_NOT_COMPLETE'}, 'Could not choose the wallet'); walletService.isReady(client, function(err) {
$modalInstance.dismiss('cancel'); if (err) {
return; self.error = err;
} $modalInstance.dismiss('cancel');
$modalInstance.close({ return;
'walletId': walletId, }
'walletName': walletName, $modalInstance.close({
'walletId': walletId,
'walletName': walletName,
});
}); });
}; };
}; };
@ -94,26 +97,24 @@ angular.module('copayApp.controllers').controller('buyGlideraController',
self.gettingBuyPrice = false; self.gettingBuyPrice = false;
if (err) { if (err) {
self.error = 'Could not get exchange information. Please, try again.'; self.error = 'Could not get exchange information. Please, try again.';
return;
} }
else { self.buyPrice = buyPrice;
self.buyPrice = buyPrice;
}
}); });
}; };
this.get2faCode = function(token) { this.get2faCode = function(token) {
var self = this; var self = this;
this.loading = 'Sending 2FA code...'; self.error = null;
self.loading = 'Sending 2FA code...';
$timeout(function() { $timeout(function() {
glideraService.get2faCode(token, function(err, sent) { glideraService.get2faCode(token, function(err, sent) {
self.loading = null; self.loading = null;
if (err) { if (err) {
self.error = 'Could not send confirmation code to your phone'; self.error = 'Could not send confirmation code to your phone';
return;
} }
else { self.show2faCodeInput = sent;
self.error = null;
self.show2faCodeInput = sent;
}
}); });
}, 100); }, 100);
}; };
@ -139,11 +140,10 @@ angular.module('copayApp.controllers').controller('buyGlideraController',
self.loading = null; self.loading = null;
if (err) { if (err) {
self.error = err; self.error = err;
return;
} }
else { self.success = data;
self.success = data; $scope.$emit('Local/GlideraTx');
$scope.$emit('Local/GlideraTx');
}
}); });
}); });
}, 100); }, 100);

View file

@ -3,8 +3,6 @@
angular.module('copayApp.controllers').controller('glideraController', angular.module('copayApp.controllers').controller('glideraController',
function($rootScope, $scope, $timeout, $modal, profileService, configService, storageService, glideraService, isChromeApp, animationService, lodash) { function($rootScope, $scope, $timeout, $modal, profileService, configService, storageService, glideraService, isChromeApp, animationService, lodash) {
window.ignoreMobilePause = true;
this.getAuthenticateUrl = function() { this.getAuthenticateUrl = function() {
return glideraService.getOauthCodeUrl(); return glideraService.getOauthCodeUrl();
}; };

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, latestReleaseService, bwcService, pushNotificationsService, lodash, go, profileService, configService, isCordova, rateService, storageService, addressService, gettext, gettextCatalog, amMoment, nodeWebkit, addonManager, isChromeApp, bwsError, txFormatService, uxLanguage, $state, glideraService, coinbaseService, isMobile, addressbookService) { angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, latestReleaseService, bwcService, pushNotificationsService, lodash, go, profileService, configService, isCordova, rateService, storageService, addressService, gettext, gettextCatalog, amMoment, nodeWebkit, addonManager, isChromeApp, bwsError, txFormatService, uxLanguage, $state, glideraService, coinbaseService, isMobile, addressbookService, walletService) {
var self = this; var self = this;
var SOFT_CONFIRMATION_LIMIT = 12; var SOFT_CONFIRMATION_LIMIT = 12;
var errors = bwcService.getErrors(); var errors = bwcService.getErrors();
@ -161,7 +161,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
} }
} }
profileService.isBackupNeeded(self.walletId, function(needsBackup) { walletService.isBackupNeeded(fc, function(needsBackup) {
self.needsBackup = needsBackup; self.needsBackup = needsBackup;
self.openWallet(function() { self.openWallet(function() {
if (!self.isComplete) { if (!self.isComplete) {

View file

@ -1,37 +1,39 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesController', angular.module('copayApp.controllers').controller('preferencesController',
function($scope, $rootScope, $timeout, $log, configService, profileService, txService) { function($scope, $rootScope, $timeout, $log, configService, profileService, fingerprintService, walletService) {
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
var config = configService.getSync();
$scope.deleted = false; $scope.deleted = false;
if (fc.credentials && !fc.credentials.mnemonicEncrypted && !fc.credentials.mnemonic) { if (fc.credentials && !fc.credentials.mnemonicEncrypted && !fc.credentials.mnemonic) {
$scope.deleted = true; $scope.deleted = true;
} }
this.init = function() { this.init = function() {
var config = configService.getSync();
var fc = profileService.focusedClient;
if (fc) { if (fc) {
$scope.encrypt = fc.hasPrivKeyEncrypted(); $scope.encrypt = walletService.isEncrypted(fc);
this.externalSource = fc.getPrivKeyExternalSourceName() == 'ledger' ? "Ledger" : null; this.externalSource = fc.getPrivKeyExternalSourceName() == 'ledger' ? "Ledger" : null;
// TODO externalAccount // TODO externalAccount
//this.externalIndex = fc.getExternalIndex(); //this.externalIndex = fc.getExternalIndex();
} }
var walletId = fc.credentials.walletId; this.touchidAvailable = fingerprintService.isAvailable();
config.touchIdFor = config.touchIdFor || {}; $scope.touchid = config.touchIdFor ? config.touchIdFor[fc.credentials.walletId] : null;
$scope.touchid = config.touchIdFor[walletId]; };
if (window.touchidAvailable) var handleEncryptedWallet = function(client, cb) {
this.touchidAvailable = true; $rootScope.$emit('Local/NeedsPassword', false, function(err, password) {
if (err) return cb(err);
return cb(walletService.unlock(client, password));
});
}; };
var unwatchEncrypt = $scope.$watch('encrypt', function(val) { var unwatchEncrypt = $scope.$watch('encrypt', function(val) {
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
if (!fc) return; if (!fc) return;
if (val && !fc.hasPrivKeyEncrypted()) { if (val && !walletService.isEncrypted(fc)) {
$rootScope.$emit('Local/NeedsPassword', true, function(err, password) { $rootScope.$emit('Local/NeedsPassword', true, function(err, password) {
if (err || !password) { if (err || !password) {
$scope.encrypt = false; $scope.encrypt = false;
@ -43,8 +45,8 @@ angular.module('copayApp.controllers').controller('preferencesController',
}); });
}); });
} else { } else {
if (!val && fc.hasPrivKeyEncrypted()) { if (!val && walletService.isEncrypted(fc)) {
profileService.unlockFC({}, function(err) { handleEncryptedWallet(fc, function(err) {
if (err) { if (err) {
$scope.encrypt = true; $scope.encrypt = true;
return; return;
@ -68,29 +70,29 @@ angular.module('copayApp.controllers').controller('preferencesController',
$scope.touchidError = false; $scope.touchidError = false;
return; return;
} }
var walletId = profileService.focusedClient.credentials.walletId; var walletId = fc.credentials.walletId;
var opts = { var opts = {
touchIdFor: {} touchIdFor: {}
}; };
opts.touchIdFor[walletId] = newVal; opts.touchIdFor[walletId] = newVal;
txService.setTouchId(function(err) { fingerprintService.check(fc, function(err) {
if (err) { if (err) {
$log.debug(err); $log.debug(err);
$timeout(function() { $timeout(function() {
$scope.touchidError = true; $scope.touchidError = true;
$scope.touchid = oldVal; $scope.touchid = oldVal;
}, 100); }, 100);
} else { return;
configService.set(opts, function(err) {
if (err) {
$log.debug(err);
$scope.touchidError = true;
$scope.touchid = oldVal;
}
});
} }
configService.set(opts, function(err) {
if (err) {
$log.debug(err);
$scope.touchidError = true;
$scope.touchid = oldVal;
}
});
}); });
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('sellCoinbaseController', angular.module('copayApp.controllers').controller('sellCoinbaseController',
function($scope, $modal, $log, $timeout, lodash, profileService, coinbaseService, animationService, txService, bwsError) { function($scope, $modal, $log, $timeout, lodash, profileService, coinbaseService, animationService, bwsError, configService, walletService, fingerprintService) {
window.ignoreMobilePause = true; window.ignoreMobilePause = true;
var self = this; var self = this;
@ -38,6 +38,14 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController',
}); });
}; };
var handleEncryptedWallet = function(client, cb) {
if (!walletService.isEncrypted(client)) return cb();
$rootScope.$emit('Local/NeedsPassword', false, function(err, password) {
if (err) return cb(err);
return cb(walletService.unlock(client, password));
});
};
this.init = function(testnet) { this.init = function(testnet) {
self.otherWallets = otherWallets(testnet); self.otherWallets = otherWallets(testnet);
// Choose focused wallet // Choose focused wallet
@ -179,6 +187,9 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController',
var accountId = account.id; var accountId = account.id;
var dataSrc = { name : 'Received from Copay: ' + self.selectedWalletName }; var dataSrc = { name : 'Received from Copay: ' + self.selectedWalletName };
var outputs = []; var outputs = [];
var config = configService.getSync();
var configWallet = config.wallet;
var walletSettings = configWallet.settings;
self.loading = 'Creating transaction...'; self.loading = 'Creating transaction...';
@ -202,17 +213,18 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController',
'amount': amount, 'amount': amount,
'message': comment 'message': comment
}); });
var opts = { var txp = {
selectedClient: fc,
toAddress: address, toAddress: address,
amount: amount, amount: amount,
outputs: outputs, outputs: outputs,
message: comment, message: comment,
payProUrl: null payProUrl: null,
excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true,
feeLevel: walletSettings.feeLevel || 'normal'
}; };
txService.createTx(opts, function(err, txp) { walletService.createTx(fc, txp, function(err, createdTxp) {
if (err) { if (err) {
$log.debug(err); $log.debug(err);
self.loading = null; self.loading = null;
@ -220,10 +232,10 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController',
$scope.$apply(); $scope.$apply();
return; return;
} }
$scope.$emit('Local/NeedsConfirmation', txp, function(accept) { $scope.$emit('Local/NeedsConfirmation', createdTxp, function(accept) {
self.loading = null; self.loading = null;
if (accept) { if (accept) {
self.confirmTx(txp, function(err, tx) { self.confirmTx(createdTxp, function(err, tx) {
if (err) { if (err) {
self.error = {errors: [{ message: 'Could not create transaction: ' + err.message }]}; self.error = {errors: [{ message: 'Could not create transaction: ' + err.message }]};
return; return;
@ -266,34 +278,54 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController',
}; };
this.confirmTx = function(txp, cb) { this.confirmTx = function(txp, cb) {
txService.prepare({selectedClient: fc}, function(err) {
fingerprintService.check(fc, function(err) {
if (err) { if (err) {
$log.debug(err); $log.debug(err);
return cb(err); return cb(err);
} }
self.loading = 'Sending bitcoin to Coinbase...';
txService.publishTx(txp, {selectedClient: fc}, function(err, txpPublished) { handleEncryptedWallet(fc, function(err) {
if (err) { if (err) {
self.loading = null;
$log.debug(err); $log.debug(err);
return cb({errors: [{ message: 'Transaction could not be published: ' + err.message }]}); return cb(err);
} else { }
txService.signAndBroadcast(txpPublished, {selectedClient: fc}, function(err, txp) {
self.loading = 'Sending bitcoin to Coinbase...';
walletService.publishTx(fc, txp, function(err, publishedTxp) {
if (err) {
self.loading = null;
$log.debug(err);
return cb({errors: [{ message: 'Transaction could not be published: ' + err.message }]});
}
walletService.signTx(fc, publishedTxp, function(err, signedTxp) {
walletService.lock(fc);
if (err) { if (err) {
self.loading = null; self.loading = null;
$log.debug(err); $log.debug(err);
txService.removeTx(txp, function(err) { walletService.removeTx(fc, signedTxp, function(err) {
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
return cb({errors: [{ message: 'The payment was created but could not be completed: ' + err.message }]}); return cb({errors: [{ message: 'The payment was created but could not be completed: ' + err.message }]});
} else { }
walletService.broadcastTx(fc, signedTxp, function(err, broadcastedTxp) {
if (err) {
self.loading = null;
$log.debug(err);
walletService.removeTx(fc, broadcastedTxp, function(err) {
if (err) $log.debug(err);
});
return cb({errors: [{ message: 'The payment was created but could not be broadcasted: ' + err.message }]});
}
$timeout(function() { $timeout(function() {
self.loading = null; self.loading = null;
return cb(null, txp); return cb(null, txp);
}, 5000); }, 5000);
} });
}); });
} });
}); });
}); });
}; };

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('sellGlideraController', angular.module('copayApp.controllers').controller('sellGlideraController',
function($scope, $timeout, $log, $modal, configService, profileService, addressService, feeService, glideraService, bwsError, lodash, isChromeApp, animationService, txService) { function($rootScope, $scope, $timeout, $log, $modal, configService, profileService, addressService, feeService, glideraService, bwsError, lodash, isChromeApp, animationService, walletService, fingerprintService) {
var self = this; var self = this;
var config = configService.getSync(); var config = configService.getSync();
@ -10,7 +10,6 @@ angular.module('copayApp.controllers').controller('sellGlideraController',
this.success = null; this.success = null;
this.error = null; this.error = null;
this.loading = null; this.loading = null;
this.currentSpendUnconfirmed = config.wallet.spendUnconfirmed;
var fc; var fc;
window.ignoreMobilePause = true; window.ignoreMobilePause = true;
@ -22,6 +21,14 @@ angular.module('copayApp.controllers').controller('sellGlideraController',
}); });
}; };
var handleEncryptedWallet = function(client, cb) {
if (!walletService.isEncrypted(client)) return cb();
$rootScope.$emit('Local/NeedsPassword', false, function(err, password) {
if (err) return cb(err);
return cb(walletService.unlock(client, password));
});
};
this.init = function(testnet) { this.init = function(testnet) {
self.otherWallets = otherWallets(testnet); self.otherWallets = otherWallets(testnet);
// Choose focused wallet // Choose focused wallet
@ -92,20 +99,19 @@ angular.module('copayApp.controllers').controller('sellGlideraController',
this.getSellPrice = function(token, price) { this.getSellPrice = function(token, price) {
var self = this; var self = this;
this.error = null; self.error = null;
if (!price || (price && !price.qty && !price.fiat)) { if (!price || (price && !price.qty && !price.fiat)) {
this.sellPrice = null; self.sellPrice = null;
return; return;
} }
this.gettingSellPrice = true; self.gettingSellPrice = true;
glideraService.sellPrice(token, price, function(err, sellPrice) { glideraService.sellPrice(token, price, function(err, sellPrice) {
self.gettingSellPrice = false; self.gettingSellPrice = false;
if (err) { if (err) {
self.error = 'Could not get exchange information. Please, try again.'; self.error = 'Could not get exchange information. Please, try again.';
} else { return;
self.error = null;
self.sellPrice = sellPrice;
} }
self.sellPrice = sellPrice;
}); });
}; };
@ -127,87 +133,112 @@ angular.module('copayApp.controllers').controller('sellGlideraController',
this.createTx = function(token, permissions, twoFaCode) { this.createTx = function(token, permissions, twoFaCode) {
var self = this; var self = this;
self.error = null; self.error = null;
var outputs = [];
var configWallet = config.wallet;
txService.prepare({selectedClient: fc}, function(err) { var walletSettings = configWallet.settings;
if (err) {
self.error = err; addressService.getAddress(fc.credentials.walletId, null, function(err, refundAddress) {
if (!refundAddress) {
self.loading = null;
self.error = bwsError.msg(err, 'Could not create address');
return; return;
} }
self.loading = 'Selling Bitcoin...'; glideraService.getSellAddress(token, function(error, sellAddress) {
$timeout(function() { if (!sellAddress) {
addressService.getAddress(fc.credentials.walletId, null, function(err, refundAddress) { self.loading = null;
if (!refundAddress) { self.error = 'Could not get the destination bitcoin address';
self.loading = null; return;
self.error = bwsError.msg(err, 'Could not create address'); }
var amount = parseInt((self.sellPrice.qty * 100000000).toFixed(0));
var comment = 'Glidera transaction';
outputs.push({
'toAddress': sellAddress,
'amount': amount,
'message': comment
});
var txp = {
toAddress: sellAddress,
amount: amount,
outputs: outputs,
message: comment,
payProUrl: null,
excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true,
feeLevel: walletSettings.feeLevel || 'normal',
customData: {
'glideraToken': token
}
};
self.loading = 'Creating transaction...';
walletService.createTx(fc, txp, function(err, createdTxp) {
self.loading = null;
if (err) {
self.error = err.message || bwsError.msg(err);
return; return;
} }
glideraService.getSellAddress(token, function(error, sellAddress) { $scope.$emit('Local/NeedsConfirmation', createdTxp, function(accept) {
if (!sellAddress) { if (accept) {
self.loading = null; fingerprintService.check(fc, function(err) {
self.error = 'Could not get the destination bitcoin address';
return;
}
var amount = parseInt((self.sellPrice.qty * 100000000).toFixed(0));
feeService.getCurrentFeeValue(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) { if (err) {
profileService.lockFC(); self.error = err.message || bwsError.msg(err);
$log.error(err);
$timeout(function() {
self.loading = null;
self.error = bwsError.msg(err, 'Error');
}, 1);
return; return;
} }
txService.sign(txp, {selectedClient: fc}, function(err, txp) { handleEncryptedWallet(fc, function(err) {
if (err) { if (err) {
self.loading = null; self.error = err.message || bwsError.msg(err);
self.error = err; return;
$scope.$apply(); }
} else {
var rawTx = txp.raw; self.loading = 'Signing transaction...';
var data = {
refundAddress: refundAddress, walletService.publishTx(fc, createdTxp, function(err, publishedTxp) {
signedTransaction: rawTx, if (err) {
priceUuid: self.sellPrice.priceUuid,
useCurrentPrice: self.sellPrice.priceUuid ? false : true,
ip: null
};
glideraService.sell(token, twoFaCode, data, function(err, data) {
self.loading = null; self.loading = null;
self.error = err.message || bwsError.msg(err);
}
walletService.signTx(fc, publishedTxp, function(err, signedTxp) {
walletService.lock(fc);
walletService.removeTx(fc, signedTxp, function(err) {
if (err) $log.debug(err);
});
if (err) { if (err) {
self.error = err; self.loading = null;
fc.removeTxProposal(txp, function(err, txp) { self.error = err.message || bwsError.msg(err);
return;
}
var rawTx = signedTxp.raw;
var data = {
refundAddress: refundAddress,
signedTransaction: rawTx,
priceUuid: self.sellPrice.priceUuid,
useCurrentPrice: self.sellPrice.priceUuid ? false : true,
ip: null
};
self.loading = 'Selling bitcoin...';
glideraService.sell(token, twoFaCode, data, function(err, data) {
self.loading = null;
if (err) {
self.error = err.message || bwsError.msg(err);
$timeout(function() { $timeout(function() {
$scope.$emit('Local/GlideraError'); $scope.$emit('Local/GlideraError');
}, 100); }, 100);
}); return;
} else { }
self.success = data; self.success = data;
$scope.$emit('Local/GlideraTx'); $scope.$emit('Local/GlideraTx');
} });
}); });
} });
}); });
}); });
}); }
}); });
}); });
}, 100); });
}); });
}; };
}); });

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('walletHomeController', function($scope, $rootScope, $interval, $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, txService) { angular.module('copayApp.controllers').controller('walletHomeController', function($scope, $rootScope, $interval, $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, walletService, fingerprintService) {
var self = this; var self = this;
window.ignoreMobilePause = false; window.ignoreMobilePause = false;
@ -127,6 +127,14 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
}); });
}; };
var handleEncryptedWallet = function(client, cb) {
if (!walletService.isEncrypted(client)) return cb();
$rootScope.$emit('Local/NeedsPassword', false, function(err, password) {
if (err) return cb(err);
return cb(walletService.unlock(client, password));
});
};
var accept_msg = gettextCatalog.getString('Accept'); var accept_msg = gettextCatalog.getString('Accept');
var cancel_msg = gettextCatalog.getString('Cancel'); var cancel_msg = gettextCatalog.getString('Cancel');
var confirm_msg = gettextCatalog.getString('Confirm'); var confirm_msg = gettextCatalog.getString('Confirm');
@ -251,28 +259,30 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
}; };
$scope.selectWallet = function(walletId, walletName) { $scope.selectWallet = function(walletId, walletName) {
profileService.isBackupNeeded(walletId, function(needsBackup) { var client = profileService.getClient(walletId);
$scope.needsBackup = {}; $scope.errorSelectedWallet = {};
$scope.needsBackup[walletId] = needsBackup;
if (needsBackup) return;
$scope.gettingAddress = true; walletService.isReady(client, function(err) {
$scope.selectedWalletName = walletName; if (err) $scope.errorSelectedWallet[walletId] = err;
$timeout(function() { else {
$scope.$apply(); $scope.gettingAddress = true;
}); $scope.selectedWalletName = walletName;
$timeout(function() {
$scope.$apply();
});
addressService.getAddress(walletId, false, function(err, addr) { addressService.getAddress(walletId, false, function(err, addr) {
$scope.gettingAddress = false; $scope.gettingAddress = false;
if (err) { if (err) {
self.error = err; self.error = err;
$modalInstance.dismiss('cancel'); $modalInstance.dismiss('cancel');
return; return;
} }
$modalInstance.close(addr); $modalInstance.close(addr);
}); });
}
}); });
}; };
}; };
@ -413,36 +423,60 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
}; };
$scope.sign = function(txp) { $scope.sign = function(txp) {
var fc = profileService.focusedClient; var client = profileService.focusedClient;
$scope.error = null; $scope.error = null;
$scope.loading = true; $scope.loading = true;
txService.prepareAndSignAndBroadcast(txp, { fingerprintService.check(client, function(err) {
reporterFn: self.setOngoingProcess.bind(self)
}, function(err, txp) {
$scope.loading = false;
$scope.$emit('UpdateTx');
if (err) { if (err) {
$scope.loading = false;
$scope.error = err; $scope.error = err;
$timeout(function() {
$scope.$digest();
});
return; return;
} }
$modalInstance.close(txp);
return; handleEncryptedWallet(client, function(err) {
if (err) {
$scope.loading = false;
$scope.error = err;
return;
}
walletService.signTx(client, txp, function(err, signedTxp) {
walletService.lock(client);
if (err) {
$scope.loading = false;
$scope.error = err;
return;
}
if (signedTxp.status == 'accepted') {
walletService.broadcastTx(client, signedTxp, function(err, broadcastedTxp) {
$scope.loading = false;
$scope.$emit('UpdateTx');
$modalInstance.close(broadcastedTxp);
if (err) {
$scope.loading = false;
$scope.error = err;
}
});
} else {
$scope.loading = false;
$scope.$emit('UpdateTx');
$modalInstance.close(signedTxp);
}
});
});
}); });
}; };
$scope.reject = function(txp) { $scope.reject = function(txp) {
var client = profileService.focusedClient;
self.setOngoingProcess(gettextCatalog.getString('Rejecting payment')); self.setOngoingProcess(gettextCatalog.getString('Rejecting payment'));
$scope.loading = true; $scope.loading = true;
$scope.error = null; $scope.error = null;
// TODO: This should be in txService
$timeout(function() { $timeout(function() {
fc.rejectTxProposal(txp, null, function(err, txpr) { walletService.rejectTx(client, txp, function(err, txpr) {
self.setOngoingProcess(); self.setOngoingProcess();
$scope.loading = false; $scope.loading = false;
if (err) { if (err) {
@ -458,11 +492,12 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
$scope.remove = function(txp) { $scope.remove = function(txp) {
var client = profileService.focusedClient;
self.setOngoingProcess(gettextCatalog.getString('Deleting payment')); self.setOngoingProcess(gettextCatalog.getString('Deleting payment'));
$scope.loading = true; $scope.loading = true;
$scope.error = null; $scope.error = null;
$timeout(function() { $timeout(function() {
fc.removeTxProposal(txp, function(err, txpb) { walletService.removeTx(client, txp, function(err) {
self.setOngoingProcess(); self.setOngoingProcess();
$scope.loading = false; $scope.loading = false;
@ -479,23 +514,20 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
}; };
$scope.broadcast = function(txp) { $scope.broadcast = function(txp) {
var client = profileService.focusedClient;
self.setOngoingProcess(gettextCatalog.getString('Broadcasting Payment')); self.setOngoingProcess(gettextCatalog.getString('Broadcasting Payment'));
$scope.loading = true; $scope.loading = true;
$scope.error = null; $scope.error = null;
$timeout(function() { $timeout(function() {
fc.broadcastTxProposal(txp, function(err, txpb, memo) { walletService.broadcastTx(client, txp, function(err, txpb) {
self.setOngoingProcess(); self.setOngoingProcess();
$scope.loading = false; $scope.loading = false;
if (err) { if (err) {
$scope.error = bwsError.msg(err, gettextCatalog.getString('Could not broadcast payment')); $scope.error = bwsError.msg(err, gettextCatalog.getString('Could not broadcast payment'));
$scope.$digest(); $scope.$digest();
} else { return;
if (memo)
$log.info(memo);
$modalInstance.close(txpb);
} }
$modalInstance.close(txpb);
}); });
}, 100); }, 100);
}; };
@ -876,7 +908,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
this.submitForm = function() { this.submitForm = function() {
if (!$scope._amount || !$scope._address) return; if (!$scope._amount || !$scope._address) return;
var fc = profileService.focusedClient; var client = profileService.focusedClient;
var unitToSat = this.unitToSatoshi; var unitToSat = this.unitToSatoshi;
var currentSpendUnconfirmed = configWallet.spendUnconfirmed; var currentSpendUnconfirmed = configWallet.spendUnconfirmed;
@ -893,7 +925,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
var comment = form.comment.$modelValue; var comment = form.comment.$modelValue;
// ToDo: use a credential's (or fc's) function for this // ToDo: use a credential's (or fc's) function for this
if (comment && !fc.credentials.sharedEncryptingKey) { if (comment && !client.credentials.sharedEncryptingKey) {
var msg = 'Could not add message to imported wallet without shared encrypting key'; var msg = 'Could not add message to imported wallet without shared encrypting key';
$log.warn(msg); $log.warn(msg);
return self.setSendError(gettext(msg)); return self.setSendError(gettext(msg));
@ -912,90 +944,100 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
'message': comment 'message': comment
}); });
var opts = {}; var txp = {};
if (!lodash.isEmpty(self.sendMaxInfo)) { if (!lodash.isEmpty(self.sendMaxInfo)) {
opts.sendMax = true; txp.sendMax = true;
opts.inputs = self.sendMaxInfo.inputs; txp.inputs = self.sendMaxInfo.inputs;
opts.fee = self.sendMaxInfo.fee; txp.fee = self.sendMaxInfo.fee;
}else { }else {
opts.amount = amount; txp.amount = amount;
} }
opts.toAddress = address; txp.toAddress = address;
opts.outputs = outputs; txp.outputs = outputs;
opts.message = comment; txp.message = comment;
opts.payProUrl = paypro ? paypro.url : null; txp.payProUrl = paypro ? paypro.url : null;
opts.lockedCurrentFeePerKb = self.lockedCurrentFeePerKb; txp.excludeUnconfirmedUtxos = configWallet.spendUnconfirmed ? false : true;
txp.feeLevel = walletSettings.feeLevel || 'normal';
self.setOngoingProcess(gettextCatalog.getString('Creating transaction')); self.setOngoingProcess(gettextCatalog.getString('Creating transaction'));
txService.createTx(opts, function(err, txp) { walletService.createTx(client, txp, function(err, createdTxp) {
self.setOngoingProcess(); self.setOngoingProcess();
if (err) { if (err) {
return self.setSendError(err); return self.setSendError(err);
} }
if (!fc.canSign() && !fc.isPrivKeyExternal()) { if (!client.canSign() && !client.isPrivKeyExternal()) {
self.setOngoingProcess();
$log.info('No signing proposal: No private key'); $log.info('No signing proposal: No private key');
self.resetForm(); self.resetForm();
txStatus.notify(txp, function() { txStatus.notify(createdTxp, function() {
return $scope.$emit('Local/TxProposalAction'); return $scope.$emit('Local/TxProposalAction');
}); });
return;
} else { } else {
$rootScope.$emit('Local/NeedsConfirmation', txp, function(accept) { $rootScope.$emit('Local/NeedsConfirmation', createdTxp, function(accept) {
if (accept) self.confirmTx(txp); if (accept) self.confirmTx(createdTxp);
else self.resetForm(); else self.resetForm();
}); });
} }
}); });
}, 100); }, 100);
}; };
this.confirmTx = function(txp) { this.confirmTx = function(txp) {
var client = profileService.focusedClient;
var self = this; var self = this;
$log.info('at .confirmTx'); fingerprintService.check(client, function(err) {
txService.prepare({}, function(err) {
$log.info('after .prepare:', err);
if (err) { if (err) {
self.setOngoingProcess();
$log.warn('confirmTx/Prepare error:', err);
return self.setSendError(err); return self.setSendError(err);
} }
self.setOngoingProcess(gettextCatalog.getString('Sending transaction'));
txService.publishTx(txp, {}, function(err, txpPublished) {
$log.info('after .publishTx:', err);
handleEncryptedWallet(client, function(err) {
if (err) { if (err) {
self.setOngoingProcess(); return self.setSendError(err);
self.setSendError(err); }
return;
}
txService.signAndBroadcast(txpPublished, {
reporterFn: self.setOngoingProcess.bind(self)
}, function(err, txp) {
$log.info('after .signAndBroadcast:', err);
self.resetForm();
self.setOngoingProcess();
self.setOngoingProcess(gettextCatalog.getString('Sending transaction'));
walletService.publishTx(client, txp, function(err, publishedTxp) {
if (err) { if (err) {
self.error = bwsError.msg(err, gettextCatalog.getString('The payment was created but could not be completed. Please try again from home screen')); self.setOngoingProcess();
$scope.$emit('Local/TxProposalAction'); return self.setSendError(err);
$timeout(function() { }
$scope.$digest();
}, 1);
return;
}
$log.info('Transaction status:', txp.status); self.setOngoingProcess(gettextCatalog.getString('Signing transaction'));
go.walletHome(); walletService.signTx(client, publishedTxp, function(err, signedTxp) {
txStatus.notify(txp, function() { self.setOngoingProcess();
$scope.$emit('Local/TxProposalAction', txp.status == 'broadcasted'); walletService.lock(client);
if (err) {
$scope.$emit('Local/TxProposalAction');
return self.setSendError(
err.message ?
err.message :
gettext('The payment was created but could not be completed. Please try again from home screen'));
}
if (signedTxp.status == 'accepted') {
self.setOngoingProcess(gettextCatalog.getString('Broadcasting transaction'));
walletService.broadcastTx(client, signedTxp, function(err, broadcastedTxp) {
self.setOngoingProcess();
if (err) {
return self.setSendError(err);
}
self.resetForm();
go.walletHome();
txStatus.notify(broadcastedTxp, function() {
$scope.$emit('Local/TxProposalAction', broadcastedTxp.status == 'broadcasted');
});
});
} else {
self.resetForm();
go.walletHome();
txStatus.notify(signedTxp, function() {
$scope.$emit('Local/TxProposalAction');
});
}
}); });
}); });
}); });
@ -1030,7 +1072,6 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
this.sendMaxInfo = {}; this.sendMaxInfo = {};
if (this.countDown) $interval.cancel(this.countDown); if (this.countDown) $interval.cancel(this.countDown);
this._paypro = null; this._paypro = null;
this.lockedCurrentFeePerKb = null;
this.lockAddress = false; this.lockAddress = false;
this.lockAmount = false; this.lockAmount = false;

View file

@ -127,6 +127,18 @@ angular.module('copayApp.services')
case 'WALLET_NOT_COMPLETE': case 'WALLET_NOT_COMPLETE':
body = gettextCatalog.getString('Wallet is not complete'); body = gettextCatalog.getString('Wallet is not complete');
break; break;
case 'WALLET_NEEDS_BACKUP':
body = gettextCatalog.getString('Wallet needs backup');
break;
case 'MISSING_PARAMETER':
body = gettextCatalog.getString('Missing parameter');
break;
case 'NO_PASSWORD_GIVEN':
body = gettextCatalog.getString('Spending Password needed');
break;
case 'PASSWORD_INCORRECT':
body = gettextCatalog.getString('Wrong spending password');
break;
case 'ERROR': case 'ERROR':
body = (err.message || err.error); body = (err.message || err.error);
break; break;
@ -147,7 +159,7 @@ angular.module('copayApp.services')
}; };
root.cb = function(err, prefix, cb) { root.cb = function(err, prefix, cb) {
return cb(root.msg(err, prefix)) return cb(root.msg(err, prefix));
}; };
return root; return root;

View file

@ -0,0 +1,40 @@
'use strict';
angular.module('copayApp.services').factory('fingerprintService', function(gettextCatalog, configService) {
var root = {};
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:' + JSON.stringify(msg));
return cb(gettextCatalog.getString('Touch ID Failed') + ': ' + msg.localizedDescription);
}
);
} catch (e) {
$log.debug('Touch ID Failed:' + JSON.stringify(e));
return cb(gettextCatalog.getString('Touch ID Failed'));
};
};
root.isAvailable = function(client) {
var config = configService.getSync();
config.touchIdFor = config.touchIdFor || {};
return (window.touchidAvailable && config.touchIdFor[client.credentials.walletId]);
};
root.check = function(client, cb) {
if (root.isAvailable()) {
requestTouchId(cb);
} else {
return cb();
}
};
return root;
});

View file

@ -9,13 +9,13 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
credentials.HOST = 'https://sandbox.glidera.io'; credentials.HOST = 'https://sandbox.glidera.io';
if (isCordova) { if (isCordova) {
credentials.REDIRECT_URI = 'copay://glidera'; credentials.REDIRECT_URI = 'copay://glidera';
credentials.CLIENT_ID = 'dfc56e4336e32bb8ba46dde34f3d7d6d'; credentials.CLIENT_ID = '6163427a2f37d1b2022ececd6d6c9cdd';
credentials.CLIENT_SECRET = '5eb679058f6c7eb81123162323d4fba5'; credentials.CLIENT_SECRET = '599cc3af26108c6fece8ab17c3f35867';
} }
else { else {
credentials.REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'; credentials.REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';
credentials.CLIENT_ID = '9915b6ffa6dc3baffb87135ed3873d49'; credentials.CLIENT_ID = 'c402f4a753755456e8c384fb65b7be1d';
credentials.CLIENT_SECRET = 'd74eda05b9c6a228fd5c85cfbd0eb7eb'; credentials.CLIENT_SECRET = '3ce826198e3618d0b8ed341ab91fe4e5';
} }
} }
else { else {

View file

@ -638,7 +638,7 @@ angular.module('copayApp.services')
$log.debug('Encrypting private key for', fc.credentials.walletName); $log.debug('Encrypting private key for', fc.credentials.walletName);
fc.setPrivateKeyEncryption(password); fc.setPrivateKeyEncryption(password);
root.lockFC(); fc.lock();
root.updateCredentialsFC(function() { root.updateCredentialsFC(function() {
$log.debug('Wallet encrypted'); $log.debug('Wallet encrypted');
return cb(); return cb();
@ -661,51 +661,6 @@ angular.module('copayApp.services')
}); });
}; };
root.lockFC = function() {
var fc = root.focusedClient;
try {
fc.lock();
} catch (e) {};
};
root.unlockFC = function(opts, cb) {
opts = opts || {};
var fc = opts.selectedClient || root.focusedClient;
if (!fc.isPrivKeyEncrypted())
return cb();
$log.debug('Wallet is encrypted');
$rootScope.$emit('Local/NeedsPassword', false, function(err2, password) {
if (err2)
return cb(err2);
if (!password)
return cb(gettext('Spending Password needed'));
try {
fc.unlock(password);
} catch (e) {
$log.warn('Error decrypting wallet:', e);
return cb(gettext('Wrong spending password'));
}
return cb();
});
};
root.isBackupNeeded = function(walletId, cb) {
var c = root.getClient(walletId);
if (c.isPrivKeyExternal()) return cb(false);
if (!c.credentials.mnemonic) return cb(false);
if (c.credentials.network == 'testnet') return cb(false);
storageService.getBackupFlag(walletId, function(err, val) {
if (err || val) return cb(false);
return cb(true);
});
};
root.getWallets = function(network) { root.getWallets = function(network) {
if (!root.profile) return []; if (!root.profile) return [];

View file

@ -1,267 +0,0 @@
'use strict';
angular.module('copayApp.services').factory('txService', function($rootScope, profileService, gettextCatalog, lodash, trezor, ledger, configService, bwsError, $log, feeService) {
var root = {};
var reportSigningStatus = function(opts) {
opts = opts || {};
if (!opts.reporterFn) return;
var fc = opts.selectedClient || 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:' + JSON.stringify(msg));
return cb(gettextCatalog.getString('Touch ID Failed') + ': ' + msg.localizedDescription);
}
);
} catch (e) {
$log.debug('Touch ID Failed:' + JSON.stringify(e));
return cb(gettextCatalog.getString('Touch ID Failed'));
};
};
root.setTouchId = function(cb) {
if (window.touchidAvailable) {
requestTouchId(cb);
} else {
return cb();
}
};
root.checkTouchId = function(opts, cb) {
opts = opts || {};
var config = configService.getSync();
var fc = opts.selectedClient || profileService.focusedClient;
config.touchIdFor = config.touchIdFor || {};
if (window.touchidAvailable && config.touchIdFor[fc.credentials.walletId]) {
requestTouchId(cb);
} else {
return cb();
}
};
root.prepare = function(opts, cb) {
$log.info("at .prepare");
opts = opts || {};
var fc = opts.selectedClient || profileService.focusedClient;
$log.info('FC Client Dump:' + fc);
if (!fc.canSign() && !fc.isPrivKeyExternal())
return cb('Cannot sign'); // should never happen, no need to translate
root.checkTouchId(opts, function(err) {
if (err) {
$log.warn('CheckTouchId error:', err);
return cb(err);
};
profileService.unlockFC(opts, function(err) {
if (err) {
$log.warn("prepare/unlockFC error:", err);
return cb(err);
};
return cb();
});
});
};
root.removeTx = function(txp, opts, cb) {
opts = opts || {};
var fc = opts.selectedClient || profileService.focusedClient;
fc.removeTxProposal(txp, function(err) {
return cb(err);
});
};
root.createTx = function(opts, cb) {
opts = opts || {};
var fc = opts.selectedClient || profileService.focusedClient;
var currentSpendUnconfirmed = configService.getSync().wallet.spendUnconfirmed;
var getFee = function(cb) {
if (opts.lockedCurrentFeePerKb) {
cb(null, opts.lockedCurrentFeePerKb);
} else {
feeService.getCurrentFeeValue(cb);
}
};
if (opts.sendMax) {
fc.createTxProposal(opts, function(err, txp) {
if (err) return cb(err);
else return cb(null, txp);
});
}else {
getFee(function(err, feePerKb) {
if (err) return cb(err);
opts.feePerKb = feePerKb;
opts.excludeUnconfirmedUtxos = currentSpendUnconfirmed ? false : true;
fc.createTxProposal(opts, function(err, txp) {
if (err) return cb(err);
else return cb(null, txp);
});
});
}
};
root.publishTx = function(txp, opts, cb) {
opts = opts || {};
var fc = opts.selectedClient || profileService.focusedClient;
fc.publishTxProposal({txp: txp}, function(err, txp) {
if (err) return cb(err);
else return cb(null, txp);
});
};
var _signWithLedger = function(txp, opts, cb) {
opts = opts || {};
var fc = opts.selectedClient || profileService.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, opts, cb) {
opts = opts || {};
var fc = opts.selectedClient || profileService.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, opts, cb) {
$log.info('at .sign');
opts = opts || {};
var fc = opts.selectedClient || profileService.focusedClient;
if (fc.isPrivKeyExternal()) {
switch (fc.getPrivKeyExternalSourceName()) {
case 'ledger':
return _signWithLedger(txp, opts, cb);
case 'trezor':
return _signWithTrezor(txp, opts, cb);
default:
var msg = 'Unsupported External Key:' + fc.getPrivKeyExternalSourceName();
$log.error(msg);
return cb(msg);
}
} else {
txp.signatures = null;
$log.info('at .sign: (isEncrypted):', fc.isPrivKeyEncrypted());
$log.info('txp BEFORE .signTxProposal:', txp);
fc.signTxProposal(txp, function(err, signedTxp) {
$log.info('txp AFTER .signTxProposal:',err, signedTxp);
profileService.lockFC();
return cb(err, signedTxp);
});
}
};
root.signAndBroadcast = function(txp, opts, cb) {
$log.info('at .signAndBroadcast');
opts = opts || {};
reportSigningStatus(opts);
var fc = opts.selectedClient || profileService.focusedClient;
root.sign(txp, opts, function(err, txp) {
if (err) {
stopReport(opts);
return bwsError.cb(err, gettextCatalog.getString('Could not accept payment'), cb);
};
if (txp.status != 'accepted') {
stopReport(opts);
return cb(null, txp);
}
reportBroadcastingStatus(opts);
fc.broadcastTxProposal(txp, function(err, txp, memo) {
stopReport(opts);
if (err) {
return bwsError.cb(err, gettextCatalog.getString('Could not broadcast payment'), cb);
};
$log.debug('Transaction signed and broadcasted')
if (memo)
$log.info(memo);
return cb(null, txp);
});
});
};
root.prepareAndSignAndBroadcast = function(txp, opts, cb) {
opts = opts || {};
$log.info('at .prepareSignAndBroadcast');
root.prepare(opts, function(err) {
if (err) {
$log.warn('Prepare error:' + 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;
});

View file

@ -0,0 +1,201 @@
'use strict';
angular.module('copayApp.services').factory('walletService', function($log, lodash, trezor, ledger, storageService) {
var root = {};
var _signWithLedger = function(client, txp, cb) {
$log.info('Requesting Ledger Chrome app to sign the transaction');
ledger.signTx(txp, client.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 client.signTxProposal(txp, cb);
});
};
var _signWithTrezor = function(client, txp, cb) {
$log.info('Requesting Trezor to sign the transaction');
var xPubKeys = lodash.pluck(client.credentials.publicKeyRing, 'xPubKey');
trezor.signTx(xPubKeys, txp, client.credentials.account, function(err, result) {
if (err) return cb(err);
$log.debug('Trezor response', result);
txp.signatures = result.signatures;
return client.signTxProposal(txp, cb);
});
};
root.isBackupNeeded = function(client, cb) {
if (client.isPrivKeyExternal()) return cb(false);
if (!client.credentials.mnemonic) return cb(false);
if (client.credentials.network == 'testnet') return cb(false);
storageService.getBackupFlag(client.credentials.walletId, function(err, val) {
if (err) $log.error(err);
if (val) return cb(false);
return cb(true);
});
};
root.isReady = function(client, cb) {
if(!client.isComplete())
return cb('WALLET_NOT_COMPLETE');
root.isBackupNeeded(client, function(needsBackup) {
if (needsBackup)
return cb('WALLET_NEEDS_BACKUP');
return cb();
});
};
root.isEncrypted = function(client) {
if (lodash.isEmpty(client)) return;
var isEncrypted = client.isPrivKeyEncrypted();
if (isEncrypted) $log.debug('Wallet is encrypted');
return isEncrypted;
};
root.lock = function(client) {
try {
client.lock();
} catch (e) {
$log.warn('Encrypting wallet:', e);
};
};
root.unlock = function(client, password) {
if (lodash.isEmpty(client))
return 'MISSING_PARAMETER';
if (lodash.isEmpty(password))
return 'NO_PASSWORD_GIVEN';
try {
client.unlock(password);
} catch (e) {
$log.warn('Decrypting wallet:', e);
return 'PASSWORD_INCORRECT';
}
};
root.createTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER');
if (txp.sendMax) {
client.createTxProposal(txp, function(err, createdTxp) {
if (err) return cb(err);
else return cb(null, createdTxp);
});
} else {
client.getFeeLevels(client.credentials.network, function(err, levels) {
if (err) return cb(err);
var feeLevelValue = lodash.find(levels, {
level: txp.feeLevel
});
if (!feeLevelValue || !feeLevelValue.feePerKB)
return cb({
message: 'Could not get dynamic fee for level: ' + feeLevel
});
$log.debug('Dynamic fee: ' + txp.feeLevel + ' ' + feeLevelValue.feePerKB + ' SAT');
txp.feePerKb = feeLevelValue.feePerKB;
client.createTxProposal(txp, function(err, createdTxp) {
if (err) return cb(err);
else {
$log.debug('Transaction created');
return cb(null, createdTxp);
}
});
});
}
};
root.publishTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER');
client.publishTxProposal({txp: txp}, function(err, publishedTx) {
if (err) return cb(err);
else {
$log.debug('Transaction published');
return cb(null, publishedTx);
}
});
};
root.signTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER');
if (client.isPrivKeyExternal()) {
switch (client.getPrivKeyExternalSourceName()) {
case 'ledger':
return _signWithLedger(client, txp, cb);
case 'trezor':
return _signWithTrezor(client, txp, cb);
default:
var msg = 'Unsupported External Key:' + client.getPrivKeyExternalSourceName();
$log.error(msg);
return cb(msg);
}
} else {
try {
client.signTxProposal(txp, function(err, signedTxp) {
$log.debug('Transaction signed');
return cb(err, signedTxp);
});
} catch (e) {
$log.warn('Error at signTxProposal:', e);
return cb(e);
}
}
};
root.broadcastTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER');
if (txp.status != 'accepted')
return cb('TX_NOT_ACCEPTED');
client.broadcastTxProposal(txp, function(err, broadcastedTxp, memo) {
if (err)
return cb(err);
$log.debug('Transaction broadcasted');
if (memo) $log.info(memo);
return cb(null, broadcastedTxp);
});
};
root.rejectTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER');
client.rejectTxProposal(txp, null, function(err, rejectedTxp) {
$log.debug('Transaction rejected');
return cb(err, rejectedTxp);
});
};
root.removeTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER');
client.removeTxProposal(txp, function(err) {
$log.debug('Transaction removed');
return cb(err);
});
};
return root;
});

View file

@ -1,11 +0,0 @@
'use strict';
describe('sidebarController', function(){
var scope, controller;
beforeEach(angular.mock.module('copayApp.controllers'));
it('wallet selection', function(){
expect(true).not.toBeUndefined();
});
});

View file

@ -10,7 +10,7 @@ module.exports = function(config) {
// frameworks to use // frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'], frameworks: ['mocha', 'chai'],
// list of files / patterns to load in the browser // list of files / patterns to load in the browser
@ -20,16 +20,20 @@ module.exports = function(config) {
'bower_components/moment/min/moment-with-locales.js', 'bower_components/moment/min/moment-with-locales.js',
'bower_components/angular/angular.js', 'bower_components/angular/angular.js',
'bower_components/angular-ui-router/release/angular-ui-router.js', 'bower_components/angular-ui-router/release/angular-ui-router.js',
'bower_components/angular-foundation/mm-foundation.js',
'bower_components/angular-foundation/mm-foundation-tpls.js', 'bower_components/angular-foundation/mm-foundation-tpls.js',
'bower_components/angular-animate/angular-animate.js', 'bower_components/angular-touch/angular-touch.js',
'bower_components/fastclick/lib/fastclick.js',
'bower_components/stateful-fastclick/dist/stateful-fastclick.js',
'bower_components/angular-stateful-fastclick/lib/angular-stateful-fastclick.js',
'bower_components/angular-moment/angular-moment.js', 'bower_components/angular-moment/angular-moment.js',
'bower_components/ng-lodash/build/ng-lodash.js', 'bower_components/ng-lodash/build/ng-lodash.js',
'bower_components/angular-qrcode/qrcode.js', 'bower_components/angular-qrcode/angular-qrcode.js',
'bower_components/angular-gettext/dist/angular-gettext.js', 'bower_components/angular-gettext/dist/angular-gettext.js',
'bower_components/angular-touch/angular-touch.js',
'bower_components/angular-ui-switch/angular-ui-switch.js', 'bower_components/angular-ui-switch/angular-ui-switch.js',
'node_modules/angular-mocks/angular-mocks.js', 'bower_components/angular-sanitize/angular-sanitize.js',
'bower_components/ng-csv/build/ng-csv.js',
'bower_components/angular-mocks/angular-mocks.js',
'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js',
'src/js/**/*.js', 'src/js/**/*.js',
'test/**/*.js' 'test/**/*.js'
], ],
@ -38,7 +42,7 @@ module.exports = function(config) {
// list of files to exclude // list of files to exclude
exclude: [ exclude: [
'src/js/translations.js', 'src/js/translations.js',
'src/js/version.js', // 'src/js/version.js',
'test/karma.conf.js', 'test/karma.conf.js',
'test/old/*' 'test/old/*'
], ],
@ -54,7 +58,7 @@ module.exports = function(config) {
// test results reporter to use // test results reporter to use
// possible values: 'dots', 'progress' // possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter // available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', , 'coverage'], reporters: ['mocha', 'coverage'],
// optionally, configure the reporter // optionally, configure the reporter
coverageReporter: { coverageReporter: {
@ -91,6 +95,16 @@ module.exports = function(config) {
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['PhantomJS'], browsers: ['PhantomJS'],
plugins: [
'karma-mocha-reporter',
'karma-coverage',
'karma-mocha',
'karma-chai',
'karma-sinon',
'karma-phantomjs-launcher',
'karma-chrome-launcher',
],
// Continuous Integration mode // Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits // if true, Karma captures browsers, runs the tests and exits

View file

@ -1,30 +0,0 @@
angular.module('stateMock',[]);
angular.module('stateMock').service("$state", function($q){
this.expectedTransitions = [];
this.transitionTo = function(stateName){
if(this.expectedTransitions.length > 0){
var expectedState = this.expectedTransitions.shift();
if(expectedState !== stateName){
throw Error("Expected transition to state: " + expectedState + " but transitioned to " + stateName );
}
}else{
throw Error("No more transitions were expected! Tried to transition to "+ stateName );
}
console.log("Mock transition to: " + stateName);
var deferred = $q.defer();
var promise = deferred.promise;
deferred.resolve();
return promise;
}
this.go = this.transitionTo;
this.expectTransitionTo = function(stateName){
this.expectedTransitions.push(stateName);
}
this.ensureAllTransitionsHappened = function(){
if(this.expectedTransitions.length > 0){
throw Error("Not all transitions happened!");
}
}
});

11
test/old/sidebar.test.js Normal file
View file

@ -0,0 +1,11 @@
// 'use strict';
//
// describe('sidebarController', function(){
// var scope, controller;
//
// beforeEach(angular.mock.module('copayApp.controllers'));
//
// it('dummy test', function(){
// should.exist(true);
// });
// });

View file

@ -0,0 +1,25 @@
describe('walletService', function() {
var walletService;
// Adds walletService's module dependencies
beforeEach(function() {
module('ngLodash');
module('gettext');
module('bwcModule');
module('copayApp.services');
});
beforeEach(inject(function(_walletService_) {
walletService = _walletService_;
}));
describe('walletService', function() {
it('should be defined', function() {
should.exist(walletService);
});
});
});