Merge pull request #1891 from matiu/opt/pubkey

Opt/pubkey
This commit is contained in:
Gustavo Maximiliano Cortez 2014-12-01 11:10:26 -03:00
commit 5d8b83823a
50 changed files with 1470 additions and 1421 deletions

View File

@ -49,12 +49,11 @@
<i class="fi-loop icon-rotate"></i>
<span translate> <strong class="size-16">Network Error</strong>.<br> Attempting to reconnect..</span>
</span>
<nav class="tab-bar" ng-if="$root.wallet &&
$root.wallet.isReady() && !$root.wallet.isLocked">
<nav class="tab-bar" ng-if="$root.iden" >
<section class="left-small">
<a class="left-off-canvas-toggle menu-icon" ><span></span></a>
</section>
<section class="right-small text-center top-balance">
<section class="right-small text-center top-balance" ng-if="$root.wallet && $root.wallet.isComplete() && !$root.wallet.isLocked" >
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
@ -91,7 +90,7 @@
<section ng-class="{'main':$root.iden && !$root.starting}" ng-view></section>
<div class="bottom-bar" ng-if="$root.wallet &&
$root.wallet.isReady() && !$root.wallet.isLocked">
$root.wallet.isComplete() && !$root.wallet.isLocked">
<div ng-include="'views/includes/bottombar-mobile.html'"></div>
</div>

View File

@ -1,17 +1,16 @@
'use strict';
angular.module('copayApp.controllers').controller('CopayersController',
function($scope, $rootScope, $location, controllerUtils) {
if (!$rootScope.wallet.isReady()) {
function($scope, $rootScope, $location) {
if (!$rootScope.wallet.isComplete()) {
$rootScope.title = 'Waiting copayers for ' + $rootScope.wallet.getName();
}
$scope.loading = false;
$scope.secret = $rootScope.wallet.getSecret();
$scope.goToWallet = function() {
controllerUtils.updateAddressList();
$location.path('/homeWallet');
};
$scope.copayersList = function() {

View File

@ -1,10 +1,9 @@
'use strict';
angular.module('copayApp.controllers').controller('CreateController',
function($scope, $rootScope, $location, $timeout, controllerUtils, backupService, notification, defaults) {
function($scope, $rootScope, $location, $timeout, identityService, backupService, notification, defaults) {
$rootScope.fromSetup = true;
$rootScope.starting = false;
$scope.loading = false;
$scope.walletPassword = $rootScope.walletPassword;
$scope.isMobile = !!window.cordova;
@ -45,7 +44,6 @@ angular.module('copayApp.controllers').controller('CreateController',
notification.error('Error', 'Please enter the required fields');
return;
}
$scope.loading = true;
var opts = {
requiredCopayers: $scope.requiredCopayers,
totalCopayers: $scope.totalCopayers,
@ -53,10 +51,14 @@ angular.module('copayApp.controllers').controller('CreateController',
privateKeyHex: $scope.private,
networkName: $scope.networkName,
};
$rootScope.iden.createWallet(opts, function(err, w) {
$scope.loading = false;
controllerUtils.installWalletHandlers($scope, w);
controllerUtils.setFocusedWallet(w);
$rootScope.starting = true;
identityService.createWallet(opts, function(err, wallet){
$rootScope.starting = false;
if (err || !wallet) {
copay.logger.debug(err);
$scope.error = 'Could not create wallet.' + err;
}
$rootScope.$digest()
});
};
});

View File

@ -1,15 +1,24 @@
'use strict';
angular.module('copayApp.controllers').controller('CreateProfileController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager, identityService) {
controllerUtils.redirIfLogged();
$scope.loading = false;
angular.module('copayApp.controllers').controller('CreateProfileController', function($scope, $rootScope, $location, notification, pluginManager, identityService) {
identityService.goWalletHome();
$scope.createProfile = function(form) {
if (form && form.$invalid) {
$scope.error('Error', 'Please enter the required fields');
return;
}
$rootScope.starting = true;
identityService.create($scope, form);
identityService.create(
form.email.$modelValue, form.password.$modelValue, function(err) {
$rootScope.starting = false;
if (err) {
var msg = err.toString();
if (msg.indexOf('EEXIST')>=0 || msg.indexOf('BADC')>=0 ) {
msg = 'This profile already exists'
}
$scope.error = msg;
}
});
}
});

View File

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('HeadController', function($scope, $rootScope, $filter, $timeout, notification, controllerUtils) {
angular.module('copayApp.controllers').controller('HeadController', function($scope, $rootScope, $filter, $timeout, notification, identityService, balanceService) {
$scope.username = $rootScope.iden.getName();
$scope.hoverMenu = false;
@ -14,21 +14,19 @@ angular.module('copayApp.controllers').controller('HeadController', function($sc
$scope.signout = function() {
$rootScope.signingOut = true;
controllerUtils.logout();
identityService.signout();
};
$scope.refresh = function() {
var w = $rootScope.wallet;
if (!w) return;
if (w.isReady()) {
if (w.isComplete()) {
w.sendWalletReady();
if ($rootScope.addrInfos.length > 0) {
controllerUtils.clearBalanceCache(w);
controllerUtils.updateBalance(w, function() {
$rootScope.$digest();
});
}
balanceService.clearBalanceCache(w);
balanceService.update(w, function() {
$rootScope.$digest();
}, true);
}
};

View File

@ -2,9 +2,7 @@
var bitcore = require('bitcore');
angular.module('copayApp.controllers').controller('HistoryController',
function($scope, $rootScope, $timeout, controllerUtils, notification, rateService) {
controllerUtils.redirIfNotComplete();
function($scope, $rootScope, $filter, rateService) {
var w = $rootScope.wallet;
$rootScope.title = 'History';
@ -19,15 +17,11 @@ angular.module('copayApp.controllers').controller('HistoryController',
$scope.blockchain_txs = [];
$scope.alternativeCurrency = [];
$scope.selectPage = function(page) {
$scope.currentPage = page;
$scope.update();
};
$scope.downloadHistory = function() {
var w = $rootScope.wallet;
if (!w) return;
@ -144,6 +138,7 @@ angular.module('copayApp.controllers').controller('HistoryController',
_.each(items, function(tx) {
tx.ts = tx.minedTs || tx.sentTs;
tx.rateTs = Math.floor((tx.ts || now) / 1000);
tx.amount = $filter('noFractionNumber')(tx.amount);
});
var index = _.indexBy(items, 'rateTs');
@ -151,7 +146,8 @@ angular.module('copayApp.controllers').controller('HistoryController',
if (!err && res) {
_.each(res, function(r) {
var tx = index[r.ts];
tx.alternativeAmount = r.rate != null ? tx.amountSat * rateService.SAT_TO_BTC * r.rate : null;
var alternativeAmount = (r.rate != null ? tx.amountSat * rateService.SAT_TO_BTC * r.rate : null);
tx.alternativeAmount = alternativeAmount ? $filter('noFractionNumber')(alternativeAmount) : null;
});
setTimeout(function() {
$scope.$digest();
@ -159,13 +155,10 @@ angular.module('copayApp.controllers').controller('HistoryController',
}
});
$scope.blockchain_txs = w.cached_txs = items;
$scope.nbPages = res.nbPages;
$scope.totalItems = res.nbItems;
$scope.loading = false;
setTimeout(function() {
$scope.$digest();

View File

@ -1,8 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, notification, controllerUtils, pluginManager, identityService, Compatibility) {
controllerUtils.redirIfLogged();
angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, $timeout, notification, identityService, Compatibility) {
// This is only for backwards compat, insight api should link to #!/confirmed directly
if (getParam('confirmed')) {
var hashIndex = window.location.href.indexOf('/?');
@ -18,6 +16,20 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc
Compatibility.check($scope);
$scope.done = function() {
$rootScope.starting = false;
};
$scope.$on("$destroy", function(){
var iden = $rootScope.iden;
if (iden) {
iden.removeListener('newWallets', $scope.done );
iden.removeListener('noWallets', $scope.done );
}
});
$scope.openProfile = function(form) {
$scope.confirmedEmail = false;
if (form && form.$invalid) {
@ -25,7 +37,22 @@ angular.module('copayApp.controllers').controller('HomeController', function($sc
return;
}
$rootScope.starting = true;
identityService.open($scope, form);
identityService.open(form.email.$modelValue, form.password.$modelValue, function(err, iden) {
if (err) {
$rootScope.starting = false;
copay.logger.warn(err);
if ((err.toString() || '').match('PNOTFOUND')) {
$scope.error = 'Invalid email or password';
} else {
$scope.error = 'Unknown error';
}
}
if (iden) {
iden.on('newWallet', $scope.done);
iden.on('noWallets', $scope.done);
}
});
}
function getParam(sname) {

View File

@ -1,11 +1,13 @@
'use strict';
angular.module('copayApp.controllers').controller('HomeWalletController',
function($scope, $rootScope, $timeout, $modal, controllerUtils) {
controllerUtils.redirIfNotComplete();
$rootScope.starting = false;
function($scope, $rootScope) {
$rootScope.title = 'Home';
$scope.addr = _.last($rootScope.wallet.getReceiveAddresses());
// This is necesarry, since wallet can change in homeWallet, without running init() again.
$rootScope.$watch('wallet', function() {
$scope.addr = _.last($rootScope.wallet.getReceiveAddresses());
});
}
);

View File

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('ImportController',
function($scope, $rootScope, $location, controllerUtils, notification, isMobile, Compatibility) {
function($scope, $rootScope, $location, identityService, notification, isMobile, Compatibility) {
$rootScope.title = 'Import wallet';
$scope.importStatus = 'Importing wallet - Reading backup...';
@ -18,28 +18,18 @@ angular.module('copayApp.controllers').controller('ImportController',
$scope.$digest();
}
$scope._doImport = function(encryptedObj, password) {
updateStatus('Importing wallet - Procesing backup...');
copay.Compatibility.importEncryptedWallet($rootScope.iden, encryptedObj,
$scope.password, $scope.importOpts, function(err, wallet) {
if (err) {
$scope.loading = false;
$scope.error = 'Could not read wallet. Please check your password';
} else {
controllerUtils.installWalletHandlers($scope, wallet);
controllerUtils.setFocusedWallet(wallet);
}
}
);
};
$scope.getFile = function() {
// If we use onloadend, we need to check the readyState.
reader.onloadend = function(evt) {
if (evt.target.readyState == FileReader.DONE) { // DONE == 2
var encryptedObj = evt.target.result;
$scope._doImport(encryptedObj, $scope.password);
updateStatus('Importing wallet - Procesing backup...');
identityService.importWallet(encryptedObj, $scope.password, {}, function(err){
if (err) {
$scope.loading = false;
$scope.error = 'Could not read wallet. Please check your password';
}
});
}
};
};
@ -85,8 +75,14 @@ angular.module('copayApp.controllers').controller('ImportController',
if (backupFile) {
reader.readAsBinaryString(backupFile);
} else {
$scope._doImport(backupText, $scope.password);
copay.Compatibility.deleteOldWallet(backupOldWallet);
updateStatus('Importing wallet - Procesing backup...');
identityService.importWallet(encryptedObj, $scope.password, $scope.importOpts, function(err){
if (err) {
$scope.loading = false;
$scope.error = 'Could not read wallet. Please check your password';
}
copay.Compatibility.deleteOldWallet(backupOldWallet);
});
}
};
});

View File

@ -1,9 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('ImportProfileController',
function($scope, $rootScope, $location, controllerUtils, notification, isMobile, pluginManager, identityService) {
controllerUtils.redirIfLogged();
function($scope, $rootScope, $location, notification, isMobile, identityService) {
$scope.title = 'Import a backup';
$scope.importStatus = 'Importing wallet - Reading backup...';
$scope.hideAdv = true;
@ -20,16 +18,10 @@ angular.module('copayApp.controllers').controller('ImportProfileController',
var password = $scope.password;
updateStatus('Importing profile - Setting things up...');
copay.Identity.importFromEncryptedFullJson(str, password, {
pluginManager: pluginManager,
network: config.network,
networkName: config.networkName,
walletDefaults: config.wallet,
passphraseConfig: config.passphraseConfig,
}, function(err, iden) {
identityService.importProfile(str,password, function(err, iden) {
$scope.loading = false;
if (err) {
$scope.loading = false;
copay.logger.warn(err);
if ((err.toString() || '').match('BADSTR')) {
$scope.error = 'Bad password or corrupt profile file';
} else if ((err.toString() || '').match('EEXISTS')) {
@ -37,11 +29,7 @@ angular.module('copayApp.controllers').controller('ImportProfileController',
} else {
$scope.error = 'Unknown error';
}
$scope.$digest();
} else {
var firstWallet = iden.getLastFocusedWallet();
controllerUtils.bindProfile($scope, iden, firstWallet);
$rootScope.$digest();
}
});
};
@ -57,10 +45,8 @@ angular.module('copayApp.controllers').controller('ImportProfileController',
};
$scope.import = function(form) {
$scope.loading = true;
if (form.$invalid) {
$scope.loading = false;
$scope.error = 'Please enter the required fields';
return;
}
@ -69,11 +55,11 @@ angular.module('copayApp.controllers').controller('ImportProfileController',
var password = form.password.$modelValue;
if (!backupFile && !backupText) {
$scope.loading = false;
$scope.error = 'Please, select your backup file';
return;
}
$scope.loading = true;
if (backupFile) {
reader.readAsBinaryString(backupFile);
} else {

View File

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('JoinController',
function($scope, $rootScope, $timeout, isMobile, controllerUtils, notification) {
function($scope, $rootScope, $timeout, isMobile, notification) {
$rootScope.fromSetup = false;
$scope.loading = false;
$scope.isMobile = isMobile.any();
@ -119,15 +119,13 @@ angular.module('copayApp.controllers').controller('JoinController',
}
$scope.loading = true;
$rootScope.iden.joinWallet({
identityService.joinWallet({
secret: $scope.connectionId,
nickname: $scope.nickname,
privateHex: $scope.private,
}, function(err, w) {
}, function(err) {
$scope.loading = false;
if (err || !w) {
if (err) {
if (err === 'joinError')
notification.error('Fatal error connecting to Insight server');
else if (err === 'walletFull')
@ -139,10 +137,6 @@ angular.module('copayApp.controllers').controller('JoinController',
else {
notification.error('Error', err.message || err);
}
controllerUtils.onErrorDigest();
} else {
controllerUtils.installWalletHandlers($scope, w);
controllerUtils.setFocusedWallet(w);
}
});
}

View File

@ -1,8 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('MoreController',
function($scope, $rootScope, $location, $filter, controllerUtils, notification, rateService) {
controllerUtils.redirIfNotComplete();
function($scope, $rootScope, $location, $filter, balanceService, notification, rateService) {
var w = $rootScope.wallet;
$scope.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
@ -76,7 +75,7 @@ angular.module('copayApp.controllers').controller('MoreController',
alternativeIsoCode: $scope.selectedAlternative.isoCode,
});
notification.success('Success', $filter('translate')('settings successfully updated'));
controllerUtils.updateBalance(w, function() {
balanceService.update(w, function() {
$rootScope.$digest();
});
};
@ -84,9 +83,9 @@ angular.module('copayApp.controllers').controller('MoreController',
$scope.purge = function(deleteAll) {
var removed = w.purgeTxProposals(deleteAll);
if (removed) {
controllerUtils.updateBalance(w, function() {
balanceService.update(w, function() {
$rootScope.$digest();
});
}, true);
}
notification.info('Transactions Proposals Purged', removed + ' ' + $filter('translate')('transaction proposal purged'));
};
@ -99,12 +98,11 @@ angular.module('copayApp.controllers').controller('MoreController',
if (err) {
notification.error('Error', $filter('translate')('Error updating indexes: ') + err);
}
controllerUtils.updateAddressList();
controllerUtils.updateBalance(w, function() {
balanceService.update(w, function() {
notification.info('Finished', 'The balance is updated using the derived addresses');
w.sendIndexes();
$rootScope.$digest();
});
}, true);
});
};
});

View File

@ -1,37 +1,13 @@
'use strict';
var bitcore = require('bitcore');
angular.module('copayApp.controllers').controller('PaymentIntentController', function($rootScope, $scope, $modal, $location, balanceService) {
angular.module('copayApp.controllers').controller('PaymentIntentController', function($rootScope, $scope, $modal, $location, controllerUtils) {
$scope.wallets = [];
$rootScope.title = 'Payment intent';
$rootScope.starting = true;
var wids = _.pluck($rootScope.iden.listWallets(), 'id');
_.each(wids, function(wid) {
var w = $rootScope.iden.getWalletById(wid);
if (w && w.isReady()) {
$scope.wallets.push(w);
$rootScope.starting = false;
controllerUtils.clearBalanceCache(w);
controllerUtils.updateBalance(w, function() {
$rootScope.$digest();
}, true);
}
});
$scope.open = function() {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
resolve: {
items: function() {
return $scope.wallets;
}
}
controller: ModalInstanceCtrl
});
};
@ -39,17 +15,31 @@ angular.module('copayApp.controllers').controller('PaymentIntentController', fun
// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.
var ModalInstanceCtrl = function($scope, $modalInstance, items, controllerUtils) {
$scope.wallets = items;
var ModalInstanceCtrl = function($scope, $modalInstance, identityService) {
$scope.loading = true;
$scope.setWallets = function() {
if (!$rootScope.iden) return;
var ret = _.filter($rootScope.iden.listWallets(), function(w) {
return w.balanceInfo && w.balanceInfo.totalBalanceBTC;
});
$scope.wallets = ret;
$scope.loading = false;
};
if ($rootScope.iden) {
var iden = $rootScope.iden;
iden.on('newWallet', function() {
$scope.setWallets();
});
}
$scope.ok = function(selectedItem) {
controllerUtils.setPaymentWallet(selectedItem);
identityService.setPaymentWallet(selectedItem);
$modalInstance.close();
};
$scope.cancel = function() {
$rootScope.pendingPayment = null;
$location.path('/');
$modalInstance.close();
$location.path('/homeWallet');
};
};

View File

@ -1,5 +1,5 @@
'use strict';
angular.module('copayApp.controllers').controller('ProfileController', function($scope, $rootScope, $location, $modal, controllerUtils, backupService) {
angular.module('copayApp.controllers').controller('ProfileController', function($scope, $rootScope, $location, $modal, backupService, identityService) {
$scope.username = $rootScope.iden.getName();
$scope.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
@ -14,33 +14,22 @@ angular.module('copayApp.controllers').controller('ProfileController', function(
$scope.hideViewProfileBackup = true;
};
$scope.getWallets = function() {
if (!$rootScope.iden) return;
$scope.wallets = [];
var wids = _.pluck($rootScope.iden.listWallets(), 'id');
_.each(wids, function(wid) {
var w = $rootScope.iden.getWalletById(wid);
$scope.wallets.push(w);
controllerUtils.updateBalance(w, function() {
$rootScope.$digest();
}, true);
$scope.deleteWallet = function(w) {
if (!w) return;
identityService.deleteWallet(w, function(err) {
$scope.loading = false;
if (err) {
log.warn(err);
}
});
};
$scope.deleteWallet = function(w) {
if (!w) return;
$scope.loading = w.id;
controllerUtils.deleteWallet($scope, w, function() {
if ($rootScope.wallet.id === w.id) {
$rootScope.wallet = null;
var lastFocused = $rootScope.iden.getLastFocusedWallet();
controllerUtils.bindProfile($scope, $rootScope.iden, lastFocused);
}
$scope.loading = false;
$scope.getWallets();
});
$scope.setWallets = function() {
if (!$rootScope.iden) return;
$scope.wallets=$rootScope.iden.listWallets();
};
$scope.downloadWalletBackup = function(w) {
if (!w) return;
backupService.walletDownload(w);

View File

@ -1,25 +1,19 @@
'use strict';
angular.module('copayApp.controllers').controller('ReceiveController',
function($scope, $rootScope, $timeout, $modal, controllerUtils) {
controllerUtils.redirIfNotComplete();
function($scope, $rootScope, $timeout, $modal) {
$rootScope.title = 'Receive';
$scope.loading = false;
$scope.showAll = false;
$scope.isNewAddr = false;
$scope.newAddr = function() {
var w = $rootScope.wallet;
$scope.loading = true;
$scope.isNewAddr = false;
w.generateAddress(null, function() {
$timeout(function() {
controllerUtils.updateAddressList();
$scope.loading = false;
$scope.isNewAddr = true;
}, 1);
});
w.generateAddress(null);
$scope.setAddressList();
$timeout(function() {
$scope.loading = false;
}, 1);
};
$scope.openAddressModal = function(address) {
@ -43,53 +37,32 @@ angular.module('copayApp.controllers').controller('ReceiveController',
});
};
$rootScope.$watch('addrInfos', function() {
if ($rootScope.updatingBalance) return;
$scope.addressList();
});
$scope.toggleShowAll = function() {
$scope.showAll = !$scope.showAll;
$scope.addressList();
$scope.setAddressList();
};
$scope.limitAddress = function(elements, isNewAddr) {
$scope.setAddressList = function() {
var w = $rootScope.wallet;
var balance = w.balanceInfo.balanceByAddr;
if(!isNewAddr){
elements = elements.sort(function(a, b) {
return (+a.isChange - +b.isChange);
});
}
var addresses = w.getAddressesOrderer();
if (addresses) {
$scope.addrLength = addresses.length;
if (elements.length <= 1 || $scope.showAll) {
return elements;
}
if (!$scope.showAll)
addresses = addresses.slice(0,3);
// Show last 3 non-change addresses plus those with balance
var addrs = elements.filter(function(e, i) {
return (!e.isChange && i < 3) || (e.balance && e.balance > 0);
});
return addrs;
};
$scope.addressList = function() {
$scope.addresses = [];
if ($rootScope.addrInfos) {
var addrInfos = $rootScope.addrInfos;
$scope.addrLength = addrInfos.length;
for (var i = 0; i < addrInfos.length; i++) {
var addrinfo = addrInfos[i];
$scope.addresses.push({
'index': i,
'address': addrinfo.addressStr,
'balance': $rootScope.balanceByAddr ? $rootScope.balanceByAddr[addrinfo.addressStr] : 0,
'isChange': addrinfo.isChange,
'owned': addrinfo.owned
var list = [];
_.each(addresses, function(address, index){
list.push({
'index': index,
'address': address,
'balance': balance ? balance[address] : null,
'isChange': w.addressIsChange(address),
});
}
$scope.addresses = $scope.limitAddress($scope.addresses, $scope.isNewAddr);
});
$scope.addresses = list;
}
};
}

View File

@ -3,10 +3,7 @@ var bitcore = require('bitcore');
var preconditions = require('preconditions').singleton();
angular.module('copayApp.controllers').controller('SendController',
function($scope, $rootScope, $window, $timeout, $modal, isMobile, notification, controllerUtils, rateService) {
controllerUtils.redirIfNotComplete();
function($scope, $rootScope, $window, $timeout, $modal, $filter, isMobile, notification, rateService) {
var w = $rootScope.wallet;
preconditions.checkState(w);
preconditions.checkState(w.settings.unitToSatoshi);
@ -33,10 +30,44 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.$digest();
});
$scope.setAlternativeAmount = function(w, tx, cb) {
rateService.whenAvailable(function() {
_.each(tx.outs, function(out) {
var valueSat = out.value * w.settings.unitToSatoshi;
out.alternativeAmount = rateService.toFiat(valueSat, $scope.alternativeIsoCode);
out.alternativeIsoCode = $scope.alternativeIsoCode;
});
if (cb) return cb(tx);
});
};
$scope.updateTxs = _.throttle(function() {
console.log('[send.js.44:updateTxs:]'); //TODO
var w = $rootScope.wallet;
if (!w) return;
var res = w.getPendingTxProposals();
_.each(res.txs, function(tx) {
$scope.setAlternativeAmount(w, tx);
if (tx.merchant) {
var url = tx.merchant.request_url;
var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1];
tx.merchant.domain = domain;
}
if (tx.outs) {
_.each(tx.outs, function(out) {
out.value = $filter('noFractionNumber')(out.value);
});
}
});
$scope.txps = res.txs;
}, 1000);
/**
* Setting the two related amounts as properties prevents an infinite
* recursion for watches while preserving the original angular updates
*
*/
Object.defineProperty($scope,
"alternative", {
@ -47,7 +78,7 @@ angular.module('copayApp.controllers').controller('SendController',
this._alternative = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
this._amount = parseFloat(
(rateService.fromFiat(newValue, w.settings.alternativeIsoCode) * satToUnit).toFixed(w.settings.unitDecimals), 10);
(rateService.fromFiat(newValue, $scope.alternativeIsoCode) * satToUnit).toFixed(w.settings.unitDecimals), 10);
} else {
this._amount = 0;
}
@ -65,7 +96,7 @@ angular.module('copayApp.controllers').controller('SendController',
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
this._alternative = parseFloat(
(rateService.toFiat(newValue * w.settings.unitToSatoshi, w.settings.alternativeIsoCode)).toFixed(2), 10);
(rateService.toFiat(newValue * w.settings.unitToSatoshi, $scope.alternativeIsoCode)).toFixed(2), 10);
} else {
this._alternative = 0;
}
@ -75,13 +106,17 @@ angular.module('copayApp.controllers').controller('SendController',
});
$scope.loadTxs = function() {
controllerUtils.updateTxs();
setTimeout(function() {
$scope.loading = false;
$rootScope.$digest();
}, 1);
}
$scope.init = function() {
$rootScope.pendingTxCount = 0;
$scope.updateTxs();
var w = $rootScope.wallet;
w.on('txProposalEvent', $scope.updateTxs);
};
$scope.$on("$destroy", function(){
var w = $rootScope.wallet;
w.removeListener('txProposalEvent', $scope.updateTxs );
});
$scope.showAddressBook = function() {
return w && _.keys(w.addressBook).length > 0;
@ -89,9 +124,17 @@ angular.module('copayApp.controllers').controller('SendController',
if ($rootScope.pendingPayment) {
var pp = $rootScope.pendingPayment;
$scope.address = pp.address + '';
var amount = pp.data.amount / w.settings.unitToSatoshi * 100000000;
$scope.amount = amount;
var amount = pp.data.amount * 100000000 * satToUnit;
var alternativeAmountPayPro = rateService.toFiat((amount + $scope.defaultFee) * w.settings.unitToSatoshi, $scope.alternativeIsoCode);
if (pp.data.merchant) {
$scope.address = 'bitcoin:' + pp.address.data + '?amount=' + amount + '&r=' + pp.data.r;
}
else {
$scope.address = pp.address + '';
$scope.amount = amount;
$scope.alternative = alternativeAmountPayPro;
}
$scope.alternativeAmountPayPro = $filter('noFractionNumber')(alternativeAmountPayPro, 2);
$scope.commentText = pp.data.message;
}
@ -116,7 +159,7 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.error = message;
$scope.loading = false;
$scope.loadTxs();
$scope.updateTxs();
};
$scope.submitForm = function(form) {
@ -153,6 +196,7 @@ angular.module('copayApp.controllers').controller('SendController',
comment: commentText,
url: (payInfo && payInfo.merchant) ? payInfo.merchant : null,
}, function(err, txid, status) {
$scope.loading = false;
// reset fields
$scope.address = $scope.amount = $scope.commentText = null;
form.address.$pristine = form.amount.$pristine = true;
@ -160,7 +204,7 @@ angular.module('copayApp.controllers').controller('SendController',
if (err) return $scope._showError(err);
$scope.notifyStatus(status);
$scope.loadTxs();
$scope.updateTxs();
});
};
@ -381,26 +425,24 @@ angular.module('copayApp.controllers').controller('SendController',
w.issueTx(ntxid, function(err, txid, status) {
$scope.notifyStatus(status);
if (cb) return cb();
else $scope.loadTxs();
else $scope.updateTxs();
});
};
$scope.sign = function(ntxid) {
$scope.loading = true;
$scope.error = $scope.success = null;
w.signAndSend(ntxid, function(err, id, status) {
$scope.loading = false;
$scope.notifyStatus(status);
$scope.loadTxs();
$scope.updateTxs();
});
};
$scope.reject = function(ntxid) {
$scope.loading = true;
$rootScope.txAlertCount = 0;
w.reject(ntxid);
notification.warning('Transaction rejected', 'You rejected the transaction successfully');
$scope.loadTxs();
$scope.updateTxs();
};
$scope.clearMerchant = function(callback) {

View File

@ -1,8 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $route, $location, $anchorScroll, controllerUtils, notification) {
controllerUtils.redirIfLogged();
angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $route, $location, $anchorScroll, notification, applicationService) {
$scope.title = 'Settings';
$scope.defaultLanguage = config.defaultLanguage || 'en';
$scope.insightLivenet = config.network.livenet.url;
@ -96,16 +94,13 @@ angular.module('copayApp.controllers').controller('SettingsController', function
}),
}));
// Go home reloading the application
var hashIndex = window.location.href.indexOf('#!/');
window.location = window.location.href.substr(0, hashIndex);
applicationService.restart();
};
$scope.reset = function() {
localStorage.removeItem('config');
// Go home reloading the application
window.location.reload();
applicationService.reload();
};
});

View File

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $location, controllerUtils) {
angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $location, $timeout, identityService) {
$scope.menu = [{
'title': 'Home',
@ -24,21 +24,6 @@ angular.module('copayApp.controllers').controller('SidebarController', function(
'link': 'more'
}];
$scope.refresh = function() {
var w = $rootScope.wallet;
if (!w) return;
if (w.isReady()) {
w.sendWalletReady();
if ($rootScope.addrInfos.length > 0) {
controllerUtils.clearBalanceCache(w);
controllerUtils.updateBalance(w, function() {
$rootScope.$digest();
});
}
}
};
$scope.signout = function() {
$scope.$emit('signout');
};
@ -47,35 +32,51 @@ angular.module('copayApp.controllers').controller('SidebarController', function(
return item.link && item.link == $location.path().split('/')[1];
};
if ($rootScope.wallet) {
$rootScope.$watch('wallet.id', function() {
$scope.walletSelection = false;
$scope.getWallets();
});
}
$scope.switchWallet = function(wid) {
controllerUtils.setFocusedWallet(wid);
$scope.walletSelection = false;
identityService.setFocusedWallet(wid);
identityService.goWalletHome();
};
$scope.toggleWalletSelection = function() {
$scope.walletSelection = !$scope.walletSelection;
if (!$scope.walletSelection) return;
$scope.getWallets();
$scope.setWallets();
};
$scope.getWallets = function() {
$scope.init = function() {
// This should be called only once.
// focused wallet change
if ($rootScope.wallet) {
$rootScope.$watch('wallet', function() {
$scope.walletSelection = false;
$scope.setWallets();
});
}
// wallet list chane
if ($rootScope.iden) {
var iden = $rootScope.iden;
iden.on('newWallet', function() {
$scope.walletSelection = false;
$scope.setWallets();
});
iden.on('deleteWallet', function() {
$scope.walletSelection = false;
$scope.setWallets();
});
}
};
$scope.setWallets = function() {
if (!$rootScope.iden) return;
$scope.wallets = [];
var wids = _.pluck($rootScope.iden.listWallets(), 'id');
_.each(wids, function(wid) {
if (controllerUtils.isFocusedWallet(wid)) return;
var w = $rootScope.iden.getWalletById(wid);
$scope.wallets.push(w);
controllerUtils.updateBalance(w, function() {
$rootScope.$digest();
})
var ret = _.filter($rootScope.iden.listWallets(), function(w) {
return !identityService.isFocused(w.getId());
});
$scope.wallets = ret;
};
});

View File

@ -1,16 +1,14 @@
'use strict';
angular.module('copayApp.controllers').controller('WarningController', function($scope, $rootScope, $location, controllerUtils) {
angular.module('copayApp.controllers').controller('WarningController', function($scope, $rootScope, $location, identityService) {
$scope.checkLock = function() {
if (!$rootScope.tmp || !$rootScope.tmp.getLock()) {
controllerUtils.redirIfLogged();
console.log('[warning.js.7] TODO LOCK'); //TODO
}
};
$scope.signout = function() {
controllerUtils.logout();
identityService.signout();
};
$scope.ignoreLock = function() {
@ -22,7 +20,8 @@ angular.module('copayApp.controllers').controller('WarningController', function(
} else {
w.ignoreLock = 1;
$scope.loading = true;
controllerUtils.startNetwork(w, $scope);
//controllerUtils.startNetwork(w, $scope);
// TODO
}
};
});

View File

@ -28,7 +28,6 @@ function Network(opts) {
if (opts.transports) {
this.socketOptions['transports'] = opts.transports;
}
this.socket = this.createSocket();
}
nodeUtil.inherits(Network, EventEmitter);
@ -236,7 +235,7 @@ Network.prototype._onMessage = function(enc) {
}
};
Network.prototype._setupConnectionHandlers = function(opts, cb) {
Network.prototype._setupSocketHandlers = function(opts, cb) {
preconditions.checkState(this.socket);
log.debug('setting up connection', opts);
var self = this;
@ -274,10 +273,7 @@ Network.prototype._setupConnectionHandlers = function(opts, cb) {
});
self.socket.on('error', self._onError.bind(self));
self.socket.on('no messages', self.emit.bind(self, 'no messages'));
var pubkey = self.getKey().public.toString('hex');
self.socket.on('no_messages', self.emit.bind(self, 'no_messages'));
self.socket.on('connect', function() {
var pubkey = self.getKey().public.toString('hex');
log.debug('Async subscribing to pubkey:', pubkey);
@ -287,11 +283,8 @@ Network.prototype._setupConnectionHandlers = function(opts, cb) {
self.socket.on('disconnect', function() {
self.socket.emit('subscribe', pubkey);
});
if (typeof cb === 'function') cb();
});
};
Network.prototype._onError = function(err) {
@ -348,7 +341,9 @@ Network.prototype.start = function(opts, openCallback) {
this.privkey = opts.privkey;
this.setCopayerId(opts.copayerId);
this.maxPeers = opts.maxPeers || this.maxPeers;
this._setupConnectionHandlers(opts, openCallback);
this.socket = this.createSocket();
this._setupSocketHandlers(opts, openCallback);
};
Network.prototype.createSocket = function() {

View File

@ -1,19 +1,22 @@
'use strict';
var preconditions = require('preconditions').singleton();
var _ = require('lodash');
var bitcore = require('bitcore');
var preconditions = require('preconditions').singleton();
var inherits = require('inherits');
var events = require('events');
var log = require('../log');
var async = require('async');
var cryptoUtil = require('../util/crypto');
var version = require('../../version').version;
var bitcore = require('bitcore');
var TxProposals = require('./TxProposals');
var PublicKeyRing = require('./PublicKeyRing');
var PrivateKey = require('./PrivateKey');
var Wallet = require('./Wallet');
var PluginManager = require('./PluginManager');
var Async = module.exports.Async = require('./Async');
var Async = require('./Async');
var version = require('../../version').version;
var cryptoUtil = require('../util/crypto');
/**
* @desc
@ -56,9 +59,14 @@ function Identity(opts) {
this.walletDefaults = opts.walletDefaults || {};
this.version = opts.version || version;
this.walletIds = opts.walletIds || {};
this.wallets = opts.wallets || {};
this.focusedTimestamps = opts.focusedTimestamps || {};
};
inherits(Identity, events.EventEmitter);
Identity.getStoragePrefix = function() {
return 'profile::';
};
@ -96,7 +104,11 @@ Identity.create = function(opts, cb) {
/**
* Open an Identity from the given storage
* Open an Identity from the given storage.
*
* After opening a profile, and setting its wallet event handlers,
* the client must run .netStart on each
* (probably on iden's newWallet handler
*
* @param {Object} opts
* @param {Object} opts.storage
@ -105,42 +117,77 @@ Identity.create = function(opts, cb) {
* @param {Function} cb
*/
Identity.open = function(opts, cb) {
preconditions.checkArgument(_.isObject(opts));
preconditions.checkArgument(_.isFunction(cb));
var storage = opts.storage || opts.pluginManager.get('DB');
storage.setCredentials(opts.email, opts.password, opts);
storage.getItem(Identity.getKeyForEmail(opts.email), function(err, data) {
var exported;
if (err) {
return cb(err);
}
return Identity.createFromPartialJson(data, opts, cb);
try {
exported = JSON.parse(data);
} catch (e) {
return cb(e);
}
return cb(null, new Identity(_.extend(opts, exported)));
});
};
/**
* Creates an Identity, retrieves all Wallets remotely, and activates network
* readAndBindWallet
*
* @param {string} wid walletId to be readed
* @param {function} cb
*
* @param {string} jsonString - a string containing a json object with options to rebuild the identity
* @param {Object} opts
* @param {Function} cb
*/
Identity.createFromPartialJson = function(jsonString, opts, callback) {
var exported;
try {
exported = JSON.parse(jsonString);
} catch (e) {
return callback('Invalid JSON');
Identity.prototype.readAndBindWallet = function(walletId, cb) {
var self = this;
self.retrieveWalletFromStorage(walletId, {}, function(error, wallet) {
if (!error) {
self.bindWallet(wallet);
}
return cb(error);
});
};
Identity.prototype.emitAndKeepAlive = function(args) {
var args = Array.prototype.slice.call(arguments);
log.debug('Ident Emitting:', args);
//this.keepAlive(); // TODO
this.emit.apply(this, arguments);
};
/**
* @desc open profile's wallets. Call it AFTER setting
* the proper even listeners. no callback.
*
*/
Identity.prototype.openWallets = function() {
var self = this;
if (_.isEmpty(self.walletIds)) {
self.emitAndKeepAlive('noWallets')
return;
}
var identity = new Identity(_.extend(opts, exported));
async.map(exported.walletIds, function(walletId, callback) {
identity.retrieveWalletFromStorage(walletId, {}, function(error, wallet) {
if (!error) {
identity.wallets[wallet.getId()] = wallet;
identity.bindWallet(wallet);
wallet.netStart();
}
callback(error, wallet);
});
}, function(err) {
return callback(err, identity);
// First read the lastFocused wallet
self.walletIds.sort(function(a, b) {
var va = self.focusedTimestamps[a] || 0;
var vb = self.focusedTimestamps[b] || 0;
return va < vb ? 1 : (va === vb ? 0 : -1);
});
// opens the wallets, in the order they were last accessed. Emits open events (newWallet)
async.eachSeries(self.walletIds, function(walletId, a_cb) {
self.readAndBindWallet(walletId, a_cb);
});
};
@ -148,16 +195,16 @@ Identity.createFromPartialJson = function(jsonString, opts, callback) {
* @param {string} walletId
* @param {} opts
* opts.importWallet
* @param {Function} callback
* @param {Function} cb
*/
Identity.prototype.retrieveWalletFromStorage = function(walletId, opts, callback) {
Identity.prototype.retrieveWalletFromStorage = function(walletId, opts, cb) {
var self = this;
var importFunction = opts.importWallet || Wallet.fromUntrustedObj;
this.storage.getItem(Wallet.getStorageKey(walletId), function(error, walletData) {
if (error) {
return callback(error);
return cb(error);
}
try {
log.info('## OPENING Wallet:', walletId);
@ -169,18 +216,15 @@ Identity.prototype.retrieveWalletFromStorage = function(walletId, opts, callback
blockchainOpts: self.blockchainOpts,
skipFields: []
};
return callback(null, importFunction(walletData, readOpts));
} catch (e) {
log.debug("ERROR: ", e.message);
if (e && e.message && e.message.indexOf('MISSOPTS') !== -1) {
return callback(new Error('WERROR: Could not read: ' + walletId + ': ' + e.message));
return cb(new Error('WERROR: Could not read: ' + walletId + ': ' + e.message));
} else {
return callback(e);
return cb(e);
}
}
return cb(null, importFunction(walletData, readOpts));
});
};
@ -219,9 +263,9 @@ Identity.storeWalletDebounced = _.debounce(function(identity, wallet, cb) {
Identity.prototype.toObj = function() {
return _.extend({
walletIds: _.keys(this.wallets)
walletIds: _.isEmpty(this.wallets) ? this.walletsIds : _.keys(this.wallets),
},
_.pick(this, 'version', 'fullName', 'password', 'email'));
_.pick(this, 'version', 'fullName', 'password', 'email', 'focusedTimestamps'));
};
Identity.prototype.exportEncryptedWithWalletInfo = function(opts) {
@ -270,15 +314,14 @@ Identity.prototype._cleanUp = function() {
/**
* @desc Closes the wallet and disconnects all services
*/
Identity.prototype.close = function(cb) {
async.map(this.wallets, function(wallet, callback) {
wallet.close(callback);
}, cb);
Identity.prototype.close = function() {
var self = this;
self.store({}, function(err) {
self.emitAndKeepAlive('closed');
});
};
// TODO: Add feedback function
//
Identity.prototype.importWalletFromObj = function(obj, opts, cb) {
var self = this;
preconditions.checkArgument(cb);
@ -292,28 +335,30 @@ Identity.prototype.importWalletFromObj = function(obj, opts, cb) {
var w = importFunction(obj, readOpts);
if (!w) return cb(new Error('Could not decrypt'));
log.debug('Wallet decryped:' + w.getName());
log.debug('Wallet decrypted:' + w.getName());
self._checkVersion(w.version);
log.debug('Updating Indexes for wallet:' + w.getName());
w.updateIndexes(function(err) {
log.debug('Adding wallet to profile:' + w.getName());
self.addWallet(w);
self.bindWallet(w);
var writeOpts = _.extend({
noWallets: true
}, opts);
self.storeWallet(w, function(err) {
if (err) return cb(err);
self.store(writeOpts, function(err) {
return cb(err, w);
});
});
self.storeWallet(w, cb);
});
};
Identity.prototype.importMultipleWalletsFromObj = function(objs, opts) {
var self = this;
opts = opts || {};
async.eachSeries(objs, function(walletData, cb) {
if (!walletData)
return cb();
self.importWalletFromObj(walletData, opts, cb);
});
};
/**
* @param {Wallet} wallet
* @param {Function} cb
@ -321,8 +366,8 @@ Identity.prototype.importWalletFromObj = function(obj, opts, cb) {
Identity.prototype.closeWallet = function(wallet, cb) {
preconditions.checkState(wallet, 'Wallet not found');
var self = this;
wallet.close(function(err) {
delete self.wallets[wid];
return cb(err);
});
};
@ -370,33 +415,18 @@ Identity.importFromFullJson = function(str, password, opts, cb) {
iden.store(opts, function(err) {
if (err) return cb(err); //profile already exists
opts.failIfExists = false;
async.map(json.wallets, function(walletData, callback) {
if (!walletData)
return callback();
iden.importWalletFromObj(walletData, opts, function(err, w) {
if (err) return callback(err);
log.debug('Wallet ' + w.getId() + ' imported');
callback();
});
}, function(err, results) {
if (err) return cb(err);
iden.store(opts, function(err) {
return cb(err, iden);
});
});
return cb(null, iden, json.wallets);
});
};
/**
* @desc binds a wallet's events and emits 'newWallet'
* @param {string} walletId Wallet id to be binded
* @emits newWallet (walletId)
*/
Identity.prototype.bindWallet = function(w) {
var self = this;
self.wallets[w.getId()] = w;
log.debug('Binding wallet:' + w.getName());
@ -421,6 +451,8 @@ Identity.prototype.bindWallet = function(w) {
w.on('publicKeyRingUpdated', function() {
Identity.storeWalletDebounced(self, w);
});
this.emitAndKeepAlive('newWallet', w.getId());
};
/**
@ -494,12 +526,10 @@ Identity.prototype.createWallet = function(opts, cb) {
var self = this;
var w = new walletClass(opts);
this.addWallet(w);
self.bindWallet(w);
w.netStart();
self.updateFocusedTimestamp(w.getId());
self.storeWallet(w, function(err) {
if (err) return cb(err);
self.store({
noWallets: true
}, function(err) {
@ -508,12 +538,6 @@ Identity.prototype.createWallet = function(opts, cb) {
});
};
Identity.prototype.addWallet = function(wallet) {
preconditions.checkArgument(wallet);
preconditions.checkArgument(wallet.getId);
this.wallets[wallet.getId()] = wallet;
};
/**
* @desc Checks if a version is compatible with the current version
* @param {string} inVersion - a version, with major, minor, and revision, period-separated (x.y.z)
@ -561,11 +585,17 @@ Identity.prototype.listWallets = function() {
Identity.prototype.deleteWallet = function(walletId, cb) {
var self = this;
var w = this.getWalletById(walletId);
w.close();
delete this.wallets[walletId];
delete this.focusedTimestamps[walletId];
this.storage.removeItem(Wallet.getStorageKey(walletId), function(err) {
if (err) {
return cb(err);
}
self.emitAndKeepAlive('deletedWallet', walletId);
self.store(null, cb);
});
};
@ -581,11 +611,26 @@ Identity.prototype.decodeSecret = function(secret) {
}
};
Identity.prototype.getLastFocusedWallet = function() {
if (_.keys(this.wallets).length == 0) return;
return _.max(this.wallets, function(wallet) {
return wallet.focusedTimestamp || 0;
});
/**
* getLastFocusedWalletId
*
* @return {string} walletId
*/
Identity.prototype.getLastFocusedWalletId = function() {
var max = _.max(this.focusedTimestamp);
var aId = _.findKey(this.wallets) || this.walletIds[0];
if (!max)
return aId;
return _.findKey(this.focusedTimestamps, function(ts) {
return ts == max;
}) || aId;
};
Identity.prototype.updateFocusedTimestamp = function(wid) {
preconditions.checkArgument(wid && this.wallets[wid]);
this.focusedTimestamps[wid] = Date.now();
};
/**

View File

@ -220,8 +220,7 @@ Insight.prototype.subscribe = function(addresses) {
if (!self.subscribed[address]) {
var handler = handlerFor(self, address);
self.subscribed[address] = handler;
log.debug('Subcribe to: ', address);
// log.debug('Subscribe to: ', address);
s.emit('subscribe', address);
s.on(address, handler);
}

View File

@ -42,10 +42,20 @@ function PublicKeyRing(opts) {
this.publicKeysCache = {};
this.nicknameFor = opts.nicknameFor || {};
this.copayerIds = [];
this.addressToPath = {};
this.resetCache();
};
PublicKeyRing.prototype.resetCache = function() {
this.cache = {};
this.cache.addressToPath = {};
this.cache.pathToAddress = {};
// Non persistent cache
this._isChange = {};
};
/**
* @desc Returns an object with only the keys needed to rebuild a PublicKeyRing
*
@ -85,22 +95,42 @@ PublicKeyRing.trim = function(data) {
* @param {object} data - a serialized version of PublicKeyRing {@see PublicKeyRing#trim}
* @return {PublicKeyRing} - the deserialized object
*/
PublicKeyRing.fromObj = function(data) {
preconditions.checkArgument(!(data instanceof PublicKeyRing), 'bad data format: Did you use .toObj()?');
var opts = PublicKeyRing.trim(data);
PublicKeyRing.fromObj = function(opts) {
preconditions.checkArgument(!(opts instanceof PublicKeyRing), 'bad opts format: Did you use .toObj()?');
// Support old indexes schema
if (!Array.isArray(opts.indexes)) {
opts.indexes = HDParams.update(opts.indexes, opts.totalCopayers);
}
var ret = new PublicKeyRing(opts);
var pkr = new PublicKeyRing(opts);
for (var k in opts.copayersExtPubKeys) {
ret.addCopayer(opts.copayersExtPubKeys[k]);
pkr.addCopayer(opts.copayersExtPubKeys[k]);
}
return ret;
if (opts.cache && opts.cache.addressToPath) {
log.info('PublicKeyRing: Using address cache');
pkr.cache.addressToPath = opts.cache.addressToPath;
pkr.rebuildCache();
}
return pkr;
};
PublicKeyRing.prototype.rebuildCache = function() {
if (!this.cache.addressToPath)
return;
var self = this;
_.each(this.cache.addressToPath, function(path, address) {
self.cache.pathToAddress[path] = address;
});
};
PublicKeyRing.fromUntrustedObj = function(opts) {
return PublicKeyRing.fromObj(PublicKeyRing.trim(opts));
};
/**
@ -120,10 +150,21 @@ PublicKeyRing.prototype.toObj = function() {
copayersExtPubKeys: this.copayersHK.map(function(b) {
return b.extendedPublicKeyString();
}),
nicknameFor: this.nicknameFor
nicknameFor: this.nicknameFor,
// We only store addressToPath and derive the reset from it
cache: {
addressToPath: this.cache.addressToPath
},
};
};
PublicKeyRing.prototype.toTrimmedObj = function() {
return PublicKeyRing.trim(this.toObj());
}
/**
* @desc
* Retrieve a copayer's public key as a hexadecimal encoded string
@ -285,20 +326,12 @@ PublicKeyRing.prototype.addCopayer = function(newHexaExtendedPublicKey, nickname
PublicKeyRing.prototype.getPubKeys = function(index, isChange, copayerIndex) {
this._checkKeys();
log.warn('Slow pubkey derivation...');
var path = HDPath.Branch(index, isChange, copayerIndex);
var pubKeys = this.publicKeysCache[path];
if (!pubKeys) {
pubKeys = _.map(this.copayersHK, function(hdKey) {
return hdKey.derive(path).eckey.public;
});
this.publicKeysCache[path] = pubKeys.map(function(pk) {
return pk.toString('hex');
});
} else {
pubKeys = pubKeys.map(function(s) {
return new Buffer(s, 'hex');
});
}
var pubKeys = _.map(this.copayersHK, function(hdKey) {
return hdKey.derive(path).eckey.public;
});
return pubKeys;
};
@ -332,29 +365,23 @@ PublicKeyRing.prototype.getRedeemScript = function(index, isChange, copayerIndex
* @param {number} copayerIndex - the index of the copayer that requested the derivation
* @returns {bitcore.Address}
*/
PublicKeyRing.prototype.getAddress = function(index, isChange, id) {
PublicKeyRing.prototype._getAddress = function(index, isChange, id) {
var copayerIndex = this.getCosigner(id);
if (!this._cachedAddress(index, isChange, id)) {
var script = this.getRedeemScript(index, isChange, copayerIndex);
var address = Address.fromScript(script, this.network.name);
this.addressToPath[address.toString()] = HDPath.FullBranch(index, isChange, copayerIndex);
this._cacheAddress(index, isChange, copayerIndex, address);
}
return this._cachedAddress(index, isChange, copayerIndex);
var path = HDPath.FullBranch(index, isChange, copayerIndex);
if (this.cache.pathToAddress[path])
return this.cache.pathToAddress[path];
log.info('Generating Address:', index, isChange, copayerIndex);
var script = this.getRedeemScript(index, isChange, copayerIndex);
var address = Address.fromScript(script, this.network.name).toString();
this._cacheAddress(address, path, isChange);
return address;
};
PublicKeyRing.prototype._cacheAddress = function(index, isChange, copayerIndex, address) {
var changeIndex = isChange ? 1 : 0;
if (!this._cacheAddressMap) this._cacheAddressMap = {};
if (!this._cacheAddressMap[index]) this._cacheAddressMap[index] = {};
if (!this._cacheAddressMap[index][changeIndex]) this._cacheAddressMap[index][changeIndex] = {};
this._cacheAddressMap[index][changeIndex][copayerIndex] = address;
};
PublicKeyRing.prototype._cachedAddress = function(index, isChange, copayerIndex) {
var changeIndex = isChange ? 1 : 0;
if (!this._cacheAddressMap) return undefined;
if (!this._cacheAddressMap[index]) return undefined;
if (!this._cacheAddressMap[index][changeIndex]) return undefined;
return this._cacheAddressMap[index][changeIndex][copayerIndex];
PublicKeyRing.prototype._cacheAddress = function(address, path, isChange) {
this.cache.addressToPath[address] = path;
this.cache.pathToAddress[path] = address;
};
/**
@ -384,26 +411,12 @@ PublicKeyRing.prototype.getHDParams = function(id) {
* @return {HDPath}
*/
PublicKeyRing.prototype.pathForAddress = function(address) {
var path = this.addressToPath[address];
this._checkCache();
var path = this.cache.addressToPath[address];
if (!path) throw new Error('Couldn\'t find path for address ' + address);
return path;
};
/**
* @desc
* Get the hexadecimal representation of a P2SH script
*
* @param {number} index - index to use when generating the address
* @param {boolean} isChange - generate a change address or a receive addres
* @param {number|string} pubkey - index of the copayer, or his public key
* @returns {string} hexadecimal encoded P2SH hash
*/
PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange, pubkey) {
var copayerIndex = this.getCosigner(pubkey);
var addr = this.getAddress(index, isChange, copayerIndex);
return Script.createP2SH(addr.payload()).getBuffer().toString('hex');
};
/**
* @desc
* Generates a new address and updates the last index used
@ -416,26 +429,44 @@ PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange, pubkey) {
*/
PublicKeyRing.prototype.generateAddress = function(isChange, pubkey) {
isChange = !!isChange;
var HDParams = this.getHDParams(pubkey);
var index = isChange ? HDParams.getChangeIndex() : HDParams.getReceiveIndex();
var ret = this.getAddress(index, isChange, HDParams.copayerIndex);
HDParams.increment(isChange);
var hdParams = this.getHDParams(pubkey);
var index = isChange ? hdParams.getChangeIndex() : hdParams.getReceiveIndex();
var ret = this._getAddress(index, isChange, hdParams.copayerIndex);
hdParams.increment(isChange);
return ret;
};
/**
* @desc
* Retrieve the addresses from a getAddressInfo return object
* @desc Is an address is from this wallet?
*
* {@see PublicKeyRing#getAddressInfo}
* @returns {string[]} the result of retrieving the addresses from calling
* @param {string} address
* @return {boolean}
*/
PublicKeyRing.prototype.getAddresses = function(opts) {
return this.getAddressesInfo(opts).map(function(info) {
return info.address;
});
PublicKeyRing.prototype.addressIsOwn = function(address) {
return !!this.cache.addressToPath[address];
};
/**
* @desc Is an address is a change address?
*
* @param {string} address
* @return {boolean}
*/
PublicKeyRing.prototype.addressIsChange = function(address) {
this._checkCache();
var path = this.cache.addressToPath[address];
if (!path)
return null;
var p = HDPath.indexesForPath(path);
//Memoization Only, never stored.
this._isChange[address] = p.isChange;
return !!this._isChange[address];
};
/**
* @desc
* Maps a copayer's public key to his index in the keyring
@ -461,68 +492,98 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) {
return index;
};
PublicKeyRing.prototype.buildAddressCache = function() {
var ret = [];
var self = this;
_.each(this.indexes, function(index) {
for (var i = 0; i < index.receiveIndex; i++) {
self._getAddress(i, false, index.copayerIndex);
}
for (var i = 0; i < index.changeIndex; i++) {
self._getAddress(i, true, index.copayerIndex);
}
});
};
PublicKeyRing.prototype.size = function(opts) {
var self = this;
return _.reduce(this.indexes, function(sum, index) {
return sum + index.receiveIndex + index.changeIndex
}, 0);
};
PublicKeyRing.prototype._checkCache = function(opts) {
if (_.isEmpty(this.cache.addressToPath)) {
this.buildAddressCache();
}
if (_.size(this.cache.addressToPath) !== this.size()) {
this.buildAddressCache();
}
};
/**
* @desc
* Gets information about addresses for a copayer
*
* @see PublicKeyRing#getAddressesInfoForIndex
* @param {Object} opts
* @param {string|number} pubkey - the pubkey or index of a copayer in the ring
* @returns {AddressInfo[]}
*/
PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) {
var ret = [];
var self = this;
var copayerIndex = pubkey && this.getCosigner(pubkey);
this.indexes.forEach(function(index) {
ret = ret.concat(self.getAddressesInfoForIndex(index, opts, copayerIndex));
});
return ret;
PublicKeyRing.prototype.getAddresses = function() {
this._checkCache();
return _.keys(this.cache.addressToPath);
};
/**
* @typedef AddressInfo
* @property {bitcore.Address} address - the address generated
* @property {string} addressStr - the base58 encoded address
* @property {boolean} isChange - true if it's a change address
* @property {boolean} owned - true if it's an address generated by a copayer
* getAddressesOrderer
* {@link Wallet#getAddressesOrderer}
*
* @param pubkey
* @return {string[]}
*/
PublicKeyRing.prototype.getAddressesOrderer = function(pubkey) {
this._checkCache();
var info = _.map(this.cache.addressToPath, function(path, addr) {
var p = HDPath.indexesForPath(path);
p.address = addr;
return p;
});
var copayerIndex = this.getCosigner(pubkey);
var l = info.length;
var sortedInfo = _.sortBy(info, function(i) {
var goodness = ( (i.copayerIndex !== copayerIndex) ? 2 * l : 0 ) +( i.isChange ? l : 0 ) + l - i.addressIndex;
return goodness;
});
return _.pluck(sortedInfo, 'address');
};
/**
* @desc
* Retrieves info about addresses generated by a copayer
* Gets information about addresses for a copayer
*
* @param {HDParams} index - HDParams of the copayer
* @param {Object} opts
* @param {boolean} opts.excludeChange - don't append information about change addresses
* @param {boolean} opts.excludeMain - don't append information about receive addresses
* @param {string|number|undefined} copayerIndex - copayer index, pubkey, or undefined to fetch info
* about shared addresses
* @return {AddressInfo[]} a list of AddressInfo
* @returns {AddressInfo[]}
*/
PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayerIndex) {
opts = opts || {};
var isOwned = index.copayerIndex === HDPath.SHARED_INDEX || index.copayerIndex === copayerIndex;
var ret = [];
var appendAddressInfo = function(address, isChange) {
ret.push({
address: address,
addressStr: address.toString(),
isChange: isChange,
owned: isOwned
});
};
PublicKeyRing.prototype.getReceiveAddresses = function() {
this._checkCache();
for (var i = 0; !opts.excludeChange && i < index.changeIndex; i++) {
appendAddressInfo(this.getAddress(i, true, index.copayerIndex), true);
}
for (var i = 0; !opts.excludeMain && i < index.receiveIndex; i++) {
appendAddressInfo(this.getAddress(i, false, index.copayerIndex), false);
}
return ret;
var self = this;
return _.filter(this.getAddresses(), function(addr) {
return !self.addressIsChange(addr);
});
};
/**
* @desc
* Retrieve the public keys for all cosigners for a given path
@ -530,43 +591,43 @@ PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayer
* @param {string} path - the BIP32 path
* @return {Buffer[]} the public keys, in buffer format
*/
PublicKeyRing.prototype.getForPath = function(path) {
PublicKeyRing.prototype._getForPath = function(path) {
var p = HDPath.indexesForPath(path);
return this.getPubKeys(p.addressIndex, p.isChange, p.copayerIndex);
};
/**
* @desc
* Retrieve the public keys for all cosigners for multiple paths
* @see PublicKeyRing#getForPath
*
* @param {string[]} paths - the BIP32 paths
* @return {Array[]} the public keys, in buffer format (matrix of Buffer, Buffer[][])
*/
PublicKeyRing.prototype.getForPaths = function(paths) {
preconditions.checkArgument(!_.isUndefined(paths));
preconditions.checkArgument(_.isArray(paths));
preconditions.checkArgument(_.all(paths, _.isString));
return paths.map(this.getForPath.bind(this));
};
/**
* @desc
* Retrieve the public keys for derived addresses and the public keys for copayers
*
* @TODO: Should this exist? A user should just call getForPath(paths)
* @TODO: Should this exist? A user should just call _getForPath(paths)
*
* @param {string[]} paths - the paths to be derived
* @return {Object} with keys pubKeys and copayerIds
*/
PublicKeyRing.prototype.forPaths = function(paths) {
return {
pubKeys: paths.map(this.getForPath.bind(this)),
pubKeys: paths.map(this._getForPath.bind(this)),
copayerIds: this.copayerIds,
}
};
/**
* @desc
* Retrieve the public keys for all cosigners for multiple paths
*
* @param {string[]} paths - the BIP32 paths
* @return {Array[]} the public keys, in buffer format (matrix of Buffer, Buffer[][])
*/
PublicKeyRing.prototype._getForPaths = function(paths) {
preconditions.checkArgument(!_.isUndefined(paths));
preconditions.checkArgument(_.isArray(paths));
preconditions.checkArgument(_.all(paths, _.isString));
return paths.map(this._getForPath.bind(this));
};
/**
* @desc
* Returns a map from a pubkey of an address to the id that generated it
@ -584,7 +645,7 @@ PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) {
inKeyMap[pubkeys[i]] = 1;
};
var keys = this.getForPaths(paths);
var keys = this._getForPaths(paths);
for (var i in keys) {
for (var copayerIndex in keys[i]) {
var kHex = keys[i][copayerIndex].toString('hex');
@ -693,25 +754,6 @@ PublicKeyRing.prototype._mergePubkeys = function(inPKR) {
return hasChanged;
};
/**
* @desc
* Merges this public key ring with another one, optionally ignoring the
* wallet id
*
* @param {PublicKeyRing} inPkr
* @param {boolean} ignoreId
* @return {boolean} true if the internal state has changed
*/
PublicKeyRing.prototype.merge = function(inPKR, ignoreId) {
this._checkInPKR(inPKR, ignoreId);
var hasChanged = false;
hasChanged |= this.mergeIndexes(inPKR.indexes);
hasChanged |= this._mergePubkeys(inPKR);
return !!hasChanged;
};
/**
* @desc
@ -734,4 +776,25 @@ PublicKeyRing.prototype.mergeIndexes = function(indexes) {
}
/**
* @desc
* Merges this public key ring with another one, optionally ignoring the
* wallet id
*
* @param {PublicKeyRing} inPkr
* @param {boolean} ignoreId
* @return {boolean} true if the internal state has changed
*/
PublicKeyRing.prototype.merge = function(inPKR, ignoreId) {
this._checkInPKR(inPKR, ignoreId);
var hasChanged = false;
hasChanged |= this.mergeIndexes(inPKR.indexes);
hasChanged |= this._mergePubkeys(inPKR);
return !!hasChanged;
};
module.exports = PublicKeyRing;

View File

@ -95,7 +95,6 @@ function Wallet(opts) {
this.registeredPeerIds = [];
this.addressBook = opts.addressBook || {};
this.publicKey = this.privateKey.publicHex;
this.focusedTimestamp = opts.focusedTimestamp || 0;
this.syncedTimestamp = opts.syncedTimestamp || 0;
this.lastMessageFrom = {};
@ -126,7 +125,7 @@ Wallet.TX_SIGNED_AND_BROADCASTED = 'txSignedAndBroadcasted';
Wallet.prototype.emitAndKeepAlive = function(args) {
var args = Array.prototype.slice.call(arguments);
log.debug('Wallet Emitting:', args);
log.debug('Wallet:'+ this.getName() + ' Emitting:', args);
this.keepAlive();
this.emit.apply(this, arguments);
};
@ -158,7 +157,6 @@ Wallet.PERSISTED_PROPERTIES = [
'txProposals',
'privateKey',
'addressBook',
'focusedTimestamp',
'syncedTimestamp',
'secretNumber',
];
@ -314,7 +312,7 @@ Wallet.prototype.changeSettings = function(settings) {
Wallet.prototype._onPublicKeyRing = function(senderId, data) {
log.debug('Wallet:' + this.id + ' RECV PUBLICKEYRING:', data);
var inPKR = PublicKeyRing.fromObj(data.publicKeyRing);
var inPKR = PublicKeyRing.fromUntrustedObj(data.publicKeyRing);
var wasIncomplete = !this.publicKeyRing.isComplete();
var hasChanged;
@ -631,7 +629,7 @@ Wallet.prototype._onAddressBook = function(senderId, data) {
var self = this,
hasChange;
_.each(data.addressBook, function(value, key) {
if (!self.addressBook[key] && Address.validate(key)) {
if (key && !self.addressBook[key] && Address.validate(key)) {
self.addressBook[key] = _.pick(value, ['createdTs', 'label']);
@ -647,18 +645,6 @@ Wallet.prototype._onAddressBook = function(senderId, data) {
}
};
/**
* @desc Updates the wallet's last modified timestamp and triggers a save
* @param {number} ts - the timestamp
*/
Wallet.prototype.updateFocusedTimestamp = function(ts) {
preconditions.checkArgument(ts);
preconditions.checkArgument(_.isNumber(ts));
preconditions.checkArgument(ts > 2999999999, 'use miliseconds');
this.focusedTimestamp = ts;
};
Wallet.prototype.updateSyncedTimestamp = function(ts) {
preconditions.checkArgument(ts);
preconditions.checkArgument(_.isNumber(ts));
@ -877,15 +863,9 @@ Wallet.decodeSecret = function(secretB) {
}
};
/**
* @desc Locks other sessions from connecting to the wallet
* @see Async#lockIncommingConnections
*/
Wallet.prototype._lockIncomming = function() {
this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds());
};
Wallet.prototype._setBlockchainListeners = function() {
Wallet.prototype._setupBlockchainHandlers = function() {
var self = this;
self.blockchain.removeAllListeners();
self.subscribeToAddresses();
@ -900,14 +880,12 @@ Wallet.prototype._setBlockchainListeners = function() {
log.debug('Wallet:' + self.id + 'blockchain disconnect event');
self.emitAndKeepAlive('insightError');
});
self.blockchain.on('tx', function(tx) {
log.debug('Wallet:' + self.id + ' blockchain tx event');
var addresses = self.getAddressesInfo();
var addr = _.findWhere(addresses, {
addressStr: tx.address
});
if (addr) {
self.emitAndKeepAlive('tx', tx.address, addr.isChange);
var addresses = self.getAddresses();
if (_.indexOf(addresses,tx.address)>=0) {
self.emitAndKeepAlive('tx', tx.address, self.addressIsChange(tx.address));
}
});
@ -916,6 +894,19 @@ Wallet.prototype._setBlockchainListeners = function() {
}
}
Wallet.prototype._setupNetworkHandlers = function() {
var self = this;
var net = this.network;
net.removeAllListeners();
net.on('connect', self._onConnect.bind(self));
net.on('data', self._onData.bind(self));
net.on('no_messages', self._onNoMessages.bind(self));
net.on('connect_error', function() {
self.emitAndKeepAlive('connectionError');
});
};
/**
* @desc Sets up the networking with other peers.
*
@ -925,43 +916,26 @@ Wallet.prototype._setBlockchainListeners = function() {
* @emits ready
* @emits txProposalsUpdated
*
* @TODO: FIX PROTOCOL -- emit with a space is shitty
* @emits no messages
*/
Wallet.prototype.netStart = function() {
var self = this;
if (self.netStarted)
return;
self._setupBlockchainHandlers();
self.netStarted= true;
if (!this.isShared()) {
self.emitAndKeepAlive('ready');
return;
}
var net = this.network;
net.removeAllListeners();
net.on('connect', self._onConnect.bind(self));
net.on('data', self._onData.bind(self));
net.on('no messages', self._onNoMessages.bind(self));
net.on('connect_error', function() {
self.emitAndKeepAlive('connectionError');
});
if (this.publicKeyRing.isComplete()) {
this._lockIncomming();
}
if (net.started) {
log.debug('Wallet:' + self.getName() + ': Wallet networking was ready')
self.emitAndKeepAlive('ready', net.getPeer());
return;
}
self._setupNetworkHandlers();
var myId = self.getMyCopayerId();
var myIdPriv = self.getMyCopayerIdPriv();
var startOpts = {
copayerId: myId,
privkey: myIdPriv,
@ -970,11 +944,12 @@ Wallet.prototype.netStart = function() {
secretNumber: self.secretNumber,
};
if (this.publicKeyRing.isComplete()) {
this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds());
}
log.debug('Wallet:' + self.id + ' Starting network.');
net.start(startOpts, function() {
self._setBlockchainListeners();
self.emitAndKeepAlive('ready', net.getPeer());
this.network.start(startOpts, function() {
self.emitAndKeepAlive('ready');
});
};
@ -1058,7 +1033,6 @@ Wallet.prototype.toObj = function() {
privateKey: this.privateKey ? this.privateKey.toObj() : undefined,
addressBook: this.addressBook,
syncedTimestamp: this.syncedTimestamp || 0,
focusedTimestamp: this.focusedTimestamp || 0,
secretNumber: this.secretNumber,
};
@ -1102,7 +1076,6 @@ Wallet.fromUntrustedObj = function(obj, readOpts) {
* @param {string} o.networkName - 'livenet' or 'testnet'
* @param {Object} o.publicKeyRing - PublicKeyRing to be deserialized by {@link PublicKeyRing#fromObj}
* @param {number} o.syncedTimestamp - ts of the last synced message with insifht (in microseconds, as insight returns ts)
* @param {number} o.focusedTimestamp - last time this wallet was focused (open) by a user (in miliseconds)
* @param {Object} o.txProposals - TxProposals to be deserialized by {@link TxProposals#fromObj}
* @param {string} o.nickname - user's nickname
*
@ -1179,8 +1152,6 @@ Wallet.fromObj = function(o, readOpts) {
}
opts.syncedTimestamp = o.syncedTimestamp || 0;
opts.focusedTimestamp = o.focusedTimestamp || 0;
opts.blockchainOpts = readOpts.blockchainOpts;
opts.networkOpts = readOpts.networkOpts;
@ -1307,7 +1278,7 @@ Wallet.prototype.sendWalletId = function(recipients) {
* @param {string[]} [recipients] - the pubkeys of the recipients
*/
Wallet.prototype.sendPublicKeyRing = function(recipients) {
var publicKeyRingObj = this.publicKeyRing.toObj();
var publicKeyRingObj = this.publicKeyRing.toTrimmedObj();
this._sendToPeers(recipients, {
type: 'publicKeyRing',
@ -1375,21 +1346,15 @@ Wallet.prototype._doGenerateAddress = function(isChange) {
return this.publicKeyRing.generateAddress(isChange, this.publicKey);
};
/**
* @callback addressCallback
* @param {string} addr - all the addresses of the wallet
*/
/**
* @desc Generate a new address
* @param {boolean} isChange - whether to generate a change address or a receive address
* @param {addressCallback} cb
* @return {string[]} a list of all the addresses generated so far for the wallet
*/
Wallet.prototype.generateAddress = function(isChange, cb) {
Wallet.prototype.generateAddress = function(isChange) {
var addr = this._doGenerateAddress(isChange);
this.sendIndexes();
this._newAddresses();
if (cb) return cb(addr);
return addr;
};
@ -1418,6 +1383,35 @@ Wallet.prototype._getActionList = function(txp) {
return peers;
};
/**
* @desc Retrieve Pendings Transaction proposals (see {@link TxProposals})
* @return {Object[]} each object returned represents a transaction proposal
*/
Wallet.prototype.getPendingTxProposalsCount = function() {
var self = this;
var txps = this.txProposals.txps;
var maxRejectCount = this.maxRejectCount();
var myId = this.getMyCopayerId();
var pending =0, pendingForUs = 0;
_.each(txps, function(inTxp, ntxid) {
if (!inTxp.isPending(maxRejectCount))
return;
pending++;
if (!inTxp.signedBy[myId] && !inTxp.rejectedBy[myId] )
pendingForUs++
});
return {
pending: pending,
pendingForUs: pendingForUs,
};
};
/**
* @desc Retrieve Pendings Transaction proposals (see {@link TxProposals})
* @return {Object[]} each object returned represents a transaction proposal
@ -1426,7 +1420,6 @@ Wallet.prototype.getPendingTxProposals = function() {
var self = this;
var ret = [];
ret.txs = [];
var pendingForUs = 0;
var txps = this.txProposals.txps;
var maxRejectCount = this.maxRejectCount();
var satToUnit = 1 / this.settings.unitToSatoshi;
@ -1438,14 +1431,11 @@ Wallet.prototype.getPendingTxProposals = function() {
var txp = _.clone(inTxp);
txp.ntxid = ntxid;
pendingForUs++;
var addresses = {};
var outs = JSON.parse(txp.builder.vanilla.outs);
outs.forEach(function(o) {
if (!self.addressIsOwn(o.address)) {
if (!addresses[o.address]) addresses[o.address] = 0;
addresses[o.address] += (o.amountSatStr || Math.round(o.amount * bitcore.util.COIN));
};
if (!addresses[o.address]) addresses[o.address] = 0;
addresses[o.address] += (o.amountSatStr || Math.round(o.amount * bitcore.util.COIN));
});
txp.outs = [];
_.each(addresses, function(value, address) {
@ -1461,7 +1451,6 @@ Wallet.prototype.getPendingTxProposals = function() {
ret.txs.push(txp);
});
ret.pendingForUs = pendingForUs;
return ret;
};
@ -1843,12 +1832,21 @@ Wallet.prototype.parsePaymentRequest = function(options, rawData) {
*/
Wallet.prototype._getPayProRefundOutputs = function(txp) {
var pkr = this.publicKeyRing;
var index = pkr.getHDParams(this.publicKey);
var amount = +txp.merchant.total.toString(10);
var output = new PayPro.Output();
var script = pkr.getScriptPubKeyHex(index.changeIndex, true, this.pubkey);
output.set('script', new Buffer(script, 'hex'));
var opts = JSON.parse(txp.builder.vanilla.opts);
if (!opts.remainderOut) {
log.warn('no remainder set. Not setting refund in PayPro');
return;
}
console.log('[Wallet.js.1842:builder:]',txp.builder.vanilla.opts); //TODO
var addrStr = opts.remainderOut.address;
var addr = new bitcore.Address(addrStr);
var script = bitcore.Script.createP2SH(addr.payload()).getBuffer();
log.debug('PayPro refund address set to:' + addrStr);
output.set('script', script);
output.set('amount', amount);
return [output];
};
@ -1866,7 +1864,6 @@ Wallet.prototype.createPayProPayment = function(txp) {
var tx = txp.builder.build();
var txBuf = tx.serialize();
var refund_outputs = this._getPayProRefundOutputs(txp);
// We send this to the serve after receiving a PaymentRequest
var pay = new PayPro();
@ -1877,9 +1874,11 @@ Wallet.prototype.createPayProPayment = function(txp) {
merchant_data = new Buffer(merchant_data, 'hex');
pay.set('merchant_data', merchant_data);
}
pay.set('transactions', [txBuf]);
pay.set('refund_to', refund_outputs);
var refund_outputs = this._getPayProRefundOutputs(txp);
if (refund_outputs)
pay.set('refund_to', refund_outputs);
// Unused for now
// options.memo = '';
@ -1980,48 +1979,60 @@ Wallet.prototype.addSeenToTxProposals = function() {
/**
* @desc Alias for {@link PublicKeyRing#getAddresses}
* @TODO: remove this method and use getAddressesInfo everywhere
* @return {Buffer[]}
*/
Wallet.prototype.getAddresses = function(opts) {
return this.publicKeyRing.getAddresses(opts);
Wallet.prototype.getAddresses = function() {
return this.publicKeyRing.getAddresses();
};
/**
* @desc gets the list of addresses, orderder for the caller:
* 1) himselfs first
* 2) receive address first
* 3) last created first
*/
Wallet.prototype.getAddressesOrderer = function() {
return this.publicKeyRing.getAddressesOrderer(this.publicKey);
};
/**
* @desc Retrieves all addresses as strings.
*
* @param {Object} opts - Same options as {@link PublicKeyRing#getAddresses}
* @return {string[]}
* @desc Alias for {@link PublicKeyRing#getAddresses}
* @return {Buffer[]}
*/
Wallet.prototype.getAddressesStr = function(opts) {
return this.getAddresses(opts).map(function(a) {
return a.toString();
});
Wallet.prototype.getReceiveAddresses = function() {
return this.publicKeyRing.getReceiveAddresses();
};
Wallet.prototype.subscribeToAddresses = function() {
if (!this.publicKeyRing.isComplete()) return;
var addrInfo = this.publicKeyRing.getAddressesInfo();
this.blockchain.subscribe(_.pluck(addrInfo, 'addressStr'));
log.debug('Subscribed to ' + addrInfo.length + ' addresses');
var addresses = this.getAddresses();
this.blockchain.subscribe(addresses);
log.debug('Wallet:' + this.getName() + ' Subscribed to:' + addresses.length + ' addresses');
};
/**
* @desc Alias for {@link PublicKeyRing#getAddressesInfo}
*/
Wallet.prototype.getAddressesInfo = function(opts) {
return this.publicKeyRing.getAddressesInfo(opts, this.publicKey);
};
/**
* @desc Returns true if a given address was generated by deriving our master public key
* @return {boolean}
*/
Wallet.prototype.addressIsOwn = function(addrStr) {
return !!this.publicKeyRing.addressToPath[addrStr];
return this.publicKeyRing.addressIsOwn(addrStr);
};
/**
* @desc Returns true if a given address is a change address (remainder)
* @param addrStr
* @return {boolean}
*/
Wallet.prototype.addressIsChange = function(addrStr) {
return this.publicKeyRing.addressIsChange(addrStr);
};
/**
* Estimate a tx fee in satoshis given its input count
* (only used when spending all wallet funds)
@ -2108,7 +2119,11 @@ Wallet.prototype.maxRejectCount = function() {
// TODO: Can we add cache to getUnspent?
Wallet.prototype.getUnspent = function(cb) {
var self = this;
this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) {
var addresses = this.getAddresses();
log.debug('Wallet ' + this.getName() + ': Getting unspents from ' + addresses.length + ' addresses');
this.blockchain.getUnspent(addresses, function(err, unspentList) {
if (err) {
return cb(err);
@ -2316,7 +2331,7 @@ Wallet.prototype._createTxProposal = function(toAddress, amountSat, comment, utx
*/
Wallet.prototype.updateIndexes = function(callback) {
var self = this;
if (!self.isReady())
if (!self.isComplete())
return callback();
log.debug('Wallet:' + this.id + ' Updating indexes...');
var tasks = this.publicKeyRing.indexes.map(function(index) {
@ -2370,7 +2385,8 @@ Wallet.prototype.deriveAddresses = function(index, amount, isChange, copayerInde
var ret = new Array(amount);
for (var i = 0; i < amount; i++) {
ret[i] = this.publicKeyRing.getAddress(index + i, isChange, copayerIndex).toString();
// TODO
ret[i] = this.publicKeyRing._getAddress(index + i, isChange, copayerIndex).toString();
}
return ret;
};
@ -2433,10 +2449,12 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb)
* @desc Closes the wallet and disconnects all services
*/
Wallet.prototype.close = function(cb) {
log.debug('## CLOSING Wallet: ' + this.id);
this.network.removeAllListeners();
this.network.cleanUp();
this.blockchain.removeAllListeners();
this.blockchain.destroy();
log.debug('## CLOSING Wallet: ' + this.id);
// TODO
// this.lock.release(function() {
if (cb) return cb();
@ -2512,7 +2530,7 @@ Wallet.prototype.requiresMultipleSignatures = function() {
* @desc Returns true if the keyring is complete
* @return {boolean}
*/
Wallet.prototype.isReady = function() {
Wallet.prototype.isComplete = function() {
return this.publicKeyRing.isComplete();
};
@ -2532,7 +2550,7 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) {
}
opts = opts || {};
var addresses = self.getAddressesInfo();
var addresses = self.getAddresses();
var proposals = self.txProposals.txps;
var satToUnit = 1 / self.settings.unitToSatoshi;
@ -2542,33 +2560,26 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) {
function extractInsOuts(tx) {
// Inputs
var inputs = _.map(tx.vin, function(item) {
var addr = _.findWhere(addresses, {
addressStr: item.addr
});
return {
type: 'in',
address: addr ? addr.addressStr : item.addr,
isMine: !_.isUndefined(addr),
isChange: addr ? !!addr.isChange : false,
address: item.addr,
isMine: self.addressIsOwn(item.addr),
isChange: self.addressIsChange(item.addr),
amountSat: item.valueSat,
}
});
var outputs = _.map(tx.vout, function(item) {
var addr;
var itemAddr;
// If classic multisig, ignore
if (item.scriptPubKey && item.scriptPubKey.addresses.length == 1) {
itemAddr = item.scriptPubKey.addresses[0];
addr = _.findWhere(addresses, {
addressStr: itemAddr,
});
}
return {
type: 'out',
address: addr ? addr.addressStr : itemAddr,
isMine: !_.isUndefined(addr),
isChange: addr ? !!addr.isChange : false,
address: itemAddr,
isMine: self.addressIsOwn(itemAddr),
isChange: self.addressIsChange(itemAddr),
label: self.addressBook[itemAddr] ? self.addressBook[itemAddr].label : undefined,
amountSat: parseInt((item.value * bitcore.util.COIN).toFixed(0)),
}
@ -2606,6 +2617,8 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) {
var fees = parseInt((tx.fees * bitcore.util.COIN).toFixed(0));
var amount;
if (amountIn == (amountOut + amountOutChange + (amountIn > 0 ? fees : 0))) {
tx.action = 'moved';
amount = amountOut;
@ -2660,10 +2673,9 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) {
};
if (addresses.length > 0) {
var addressesStr = _.pluck(addresses, 'addressStr');
var from = (opts.currentPage - 1) * opts.itemsPerPage;
var to = opts.currentPage * opts.itemsPerPage;
self.blockchain.getTransactions(addressesStr, from, to, function(err, res) {
self.blockchain.getTransactions(addresses, from, to, function(err, res) {
if (err) return cb(err);
_.each(res.items, function(tx) {

View File

@ -85,7 +85,6 @@ InsightStorage.prototype._makeGetRequest = function(passphrase, key, callback) {
'Authorization': authHeader
}
};
log.debug('Insight request', getParams);
this.request.get(getParams,
function(err, response, body) {
if (err) {

View File

@ -47,26 +47,32 @@ angular
})
.when('/homeWallet', {
templateUrl: 'views/homeWallet.html',
walletShouldBeComplete: true,
logged: true
})
.when('/receive', {
templateUrl: 'views/receive.html',
walletShouldBeComplete: true,
logged: true
})
.when('/history', {
templateUrl: 'views/history.html',
walletShouldBeComplete: true,
logged: true
})
.when('/send', {
templateUrl: 'views/send.html',
walletShouldBeComplete: true,
logged: true
})
.when('/more', {
templateUrl: 'views/more.html',
walletShouldBeComplete: true,
logged: true
})
.when('/settings', {
templateUrl: 'views/settings.html',
walletShouldBeComplete: true,
logged: false
})
.when('/warning', {
@ -119,6 +125,9 @@ angular
$idle.unwatch();
$location.path('/');
}
if ($rootScope.wallet && !$rootScope.wallet.isComplete() && next.walletShouldBeComplete) {
$location.path('/copayers');
}
}
});
})

View File

@ -0,0 +1,17 @@
'use strict';
angular.module('copayApp.services')
.factory('applicationService', function() {
var root = {};
root.restart = function() {
// Go home reloading the application
var hashIndex = window.location.href.indexOf('#!/');
window.location = window.location.href.substr(0, hashIndex);
};
root.reload = function() {
window.location.reload();
};
return root;
});

View File

@ -0,0 +1,93 @@
'use strict';
var bitcore = require('bitcore');
angular.module('copayApp.services')
.factory('balanceService', function($rootScope, $filter, rateService) {
var root = {};
var _balanceCache = {};
root.clearBalanceCache = function(w) {
delete _balanceCache[w.getId()];
};
root._fetchBalance = function(w, cb) {
cb = cb || function() {};
var satToUnit = 1 / w.settings.unitToSatoshi;
var COIN = bitcore.util.COIN;
w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat, safeUnspentCount) {
if (err) return cb(err);
var r = {};
r.totalBalance = $filter('noFractionNumber')(balanceSat * satToUnit);
r.totalBalanceBTC = (balanceSat / COIN);
var availableBalanceNr = safeBalanceSat * satToUnit;
r.availableBalance = $filter('noFractionNumber')(safeBalanceSat * satToUnit);
r.availableBalanceBTC = (safeBalanceSat / COIN);
r.safeUnspentCount = safeUnspentCount;
r.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit;
r.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN;
if (r.safeUnspentCount) {
var estimatedFee = copay.Wallet.estimatedFee(r.safeUnspentCount);
r.topAmount = (((availableBalanceNr * w.settings.unitToSatoshi).toFixed(0) - estimatedFee) / w.settings.unitToSatoshi);
}
var balanceByAddr = {};
for (var ii in balanceByAddrSat) {
balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit;
}
r.balanceByAddr = balanceByAddr;
if (rateService.isAvailable()) {
var totalBalanceAlternative = rateService.toFiat(balanceSat, w.settings.alternativeIsoCode);
var lockedBalanceAlternative = rateService.toFiat(balanceSat - safeBalanceSat, w.settings.alternativeIsoCode);
var alternativeConversionRate = rateService.toFiat(100000000, w.settings.alternativeIsoCode);
r.totalBalanceAlternative = $filter('noFractionNumber')(totalBalanceAlternative, 2);
r.lockedBalanceAlternative = $filter('noFractionNumber')(lockedBalanceAlternative, 2);
r.alternativeConversionRate = $filter('noFractionNumber')(alternativeConversionRate, 2);
r.alternativeBalanceAvailable = true;
r.alternativeIsoCode = w.settings.alternativeIsoCode;
};
r.updatingBalance = false;
return cb(null, r)
});
};
root.update = function(w, cb, isFocused) {
w = w || $rootScope.wallet;
if (!w || !w.isComplete()) return;
copay.logger.debug('Updating balance of:', w.getName(), isFocused);
var wid = w.getId();
// cache available? Set the cached values until we updated them
if (_balanceCache[wid]) {
w.balanceInfo = _balanceCache[wid];
} else {
if (isFocused)
$rootScope.updatingBalance = true;
}
w.balanceInfo = w.balanceInfo || {};
w.balanceInfo.updating = true;
root._fetchBalance(w, function(err, res) {
if (err) throw err;
w.balanceInfo=_balanceCache[wid] = res;
w.balanceInfo.updating = false;
if (isFocused) {
$rootScope.updatingBalance = false;
}
if (cb) cb();
});
};
return root;
});

View File

@ -1,382 +0,0 @@
'use strict';
var bitcore = require('bitcore');
angular.module('copayApp.services')
.factory('controllerUtils', function($rootScope, $sce, $location, $filter, notification, $timeout, rateService) {
var root = {};
root.redirIfNotComplete = function() {
var w = $rootScope.wallet;
if (w) {
if (!w.isReady()) {
$location.path('/copayers');
}
} else {
$location.path('/');
}
};
root.redirIfLogged = function() {
var w = $rootScope.wallet;
if (w) {
if (!w.isReady()) {
$location.path('/copayers');
} else {
$location.path('homeWallet');
}
}
};
root.logout = function() {
if ($rootScope.iden) {
$rootScope.iden.store(null, function() {
$rootScope.iden.close();
delete $rootScope['wallet'];
delete $rootScope['iden'];
// Go home reloading the application
var hashIndex = window.location.href.indexOf('#!/');
window.location = window.location.href.substr(0, hashIndex);
});
}
};
root.onError = function(scope) {
if (scope) {
scope.loading = false;
}
}
root.onErrorDigest = function(scope, msg) {
root.onError(scope);
if (msg) {
notification.error('Error', msg);
}
};
root.isFocusedWallet = function(wid) {
return $rootScope.wallet && wid === $rootScope.wallet.getId();
};
root.installWalletHandlers = function($scope, w) {
var wid = w.getId();
w.on('connectionError', function() {
if (root.isFocusedWallet(wid)) {
var message = "Could not connect to the Insight server. Check your settings and network configuration";
notification.error('Networking Error', message);
root.onErrorDigest($scope);
}
});
w.on('corrupt', function(peerId) {
if (root.isFocusedWallet(wid)) {
notification.error('Error', $filter('translate')('Received corrupt message from ') + peerId);
}
});
w.on('ready', function(myPeerID) {
$scope.loading = false;
if ($rootScope.initialConnection) {
$rootScope.initialConnection = false;
if ($rootScope.pendingPayment) {
$location.path('paymentIntent');
} else {
root.redirIfLogged();
}
}
});
w.on('tx', function(address, isChange) {
if (!isChange) {
notification.funds('Funds received on ' + w.getName(), address);
}
root.updateBalance(w, function() {
$rootScope.$digest();
});
});
w.on('balanceUpdated', function() {
root.updateBalance(w, function() {
$rootScope.$digest();
});
});
w.on('insightReconnected', function() {
$rootScope.reconnecting = false;
root.updateAddressList(w.getId());
root.updateBalance(w, function() {
$rootScope.$digest();
});
});
w.on('insightError', function() {
if (root.isFocusedWallet(wid)) {
$rootScope.reconnecting = true;
$rootScope.$digest();
}
});
w.on('newAddresses', function() {
root.updateBalance(w);
});
w.on('txProposalsUpdated', function() {
if (root.isFocusedWallet(wid)) {
root.updateTxs();
}
});
w.on('paymentACK', function(memo) {
notification.success('Payment Acknowledged', memo);
});
w.on('txProposalEvent', function(e) {
if (root.isFocusedWallet(wid)) {
root.updateTxs();
}
// TODO: add wallet name notification
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
var name = w.getName();
switch (e.type) {
case 'new':
notification.info('[' + name + '] New Transaction',
$filter('translate')('You received a transaction proposal from') + ' ' + user);
break;
case 'signed':
notification.info('[' + name + '] Transaction Signed',
$filter('translate')('A transaction was signed by') + ' ' + user);
break;
case 'signedAndBroadcasted':
notification.info('[' + name + '] Transaction Approved',
$filter('translate')('A transaction was signed and broadcasted by') + ' ' + user);
break;
case 'rejected':
notification.info('[' + name + '] Transaction Rejected',
$filter('translate')('A transaction was rejected by') + ' ' + user);
break;
case 'corrupt':
notification.error('[' + name + '] Transaction Error',
$filter('translate')('Received corrupt transaction from') + ' ' + user);
break;
}
$rootScope.$digest();
});
w.on('addressBookUpdated', function(dontDigest) {
if (root.isFocusedWallet(wid)) {
if (!dontDigest) {
$rootScope.$digest();
}
}
});
w.on('connect', function(peerID) {
$rootScope.$digest();
});
w.on('close', root.onErrorDigest);
w.on('locked', root.onErrorDigest.bind(this));
};
root.setupGlobalVariables = function(iden) {
notification.enableHtml5Mode(); // for chrome: if support, enable it
$rootScope.unitName = config.unitName;
$rootScope.pendingTxCount = 0;
$rootScope.initialConnection = true;
$rootScope.reconnecting = false;
$rootScope.isCollapsed = true;
$rootScope.iden = iden;
};
root.rebindWallets = function($scope, iden) {
_.each(iden.listWallets(), function(wallet) {
preconditions.checkState(wallet);
root.installWalletHandlers($scope, wallet);
});
};
root.setPaymentWallet = function(w) {
root.setFocusedWallet(w);
$location.path('/send');
};
root.setFocusedWallet = function(w) {
if (!_.isObject(w))
w = $rootScope.iden.getWalletById(w);
preconditions.checkState(w && _.isObject(w));
$rootScope.wallet = w;
w.updateFocusedTimestamp(Date.now());
root.redirIfLogged();
root.updateTxs();
root.updateBalance(w, function() {
$rootScope.$digest();
})
};
root.bindProfile = function($scope, iden, w) {
root.setupGlobalVariables(iden);
root.rebindWallets($scope, iden);
if (w) {
root.setFocusedWallet(w);
} else {
$location.path('/create');
}
$timeout(function() {
$rootScope.$digest()
}, 1);
};
// On the focused wallet
root.updateAddressList = function(wid) {
if (!wid || root.isFocusedWallet(wid)) {
var w = $rootScope.wallet;
if (w && w.isReady()) {
$rootScope.addrInfos = w.getAddressesInfo();
}
}
};
var _balanceCache = {};
root.clearBalanceCache = function(w) {
delete _balanceCache[w.getId()];
};
root._fetchBalance = function(w, cb) {
cb = cb || function() {};
var satToUnit = 1 / w.settings.unitToSatoshi;
var COIN = bitcore.util.COIN;
w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat, safeUnspentCount) {
if (err) return cb(err);
var r = {};
r.totalBalance = balanceSat * satToUnit;
r.totalBalanceBTC = (balanceSat / COIN);
r.availableBalance = safeBalanceSat * satToUnit;
r.availableBalanceBTC = (safeBalanceSat / COIN);
r.safeUnspentCount = safeUnspentCount;
r.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit;
r.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN;
if (r.safeUnspentCount) {
var estimatedFee = copay.Wallet.estimatedFee(r.safeUnspentCount);
r.topAmount = (((r.availableBalance * w.settings.unitToSatoshi).toFixed(0) - estimatedFee) / w.settings.unitToSatoshi);
}
var balanceByAddr = {};
for (var ii in balanceByAddrSat) {
balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit;
}
r.balanceByAddr = balanceByAddr;
root.updateAddressList();
if (rateService.isAvailable()) {
r.totalBalanceAlternative = rateService.toFiat(balanceSat, w.settings.alternativeIsoCode);
r.alternativeIsoCode = w.settings.alternativeIsoCode;
r.lockedBalanceAlternative = rateService.toFiat(balanceSat - safeBalanceSat, w.settings.alternativeIsoCode);
r.alternativeConversionRate = rateService.toFiat(100000000, w.settings.alternativeIsoCode);
r.alternativeBalanceAvailable = true;
};
r.updatingBalance = false;
return cb(null, r)
});
};
root._updateScope = function(w, data, scope, cb) {
_.each(data, function(v, k) {
scope[k] = data[k];
})
if (cb) return cb();
};
root.updateBalance = function(w, cb, refreshAll) {
w = w || $rootScope.wallet;
if (!w) return root.onErrorDigest();
if (!w.isReady()) return;
w.balanceInfo = {};
var scope = root.isFocusedWallet(w.id) && !refreshAll ? $rootScope : w.balanceInfo;
var wid = w.getId();
if (_balanceCache[wid]) {
root._updateScope(w, _balanceCache[wid], scope, function() {
if (root.isFocusedWallet(w.id) && !refreshAll) {
setTimeout(function() {
$rootScope.$digest();
}, 1);
}
});
} else {
scope.updatingBalance = true;
}
root._fetchBalance(w, function(err, res) {
if (err) throw err;
_balanceCache[wid] = res;
root._updateScope(w, _balanceCache[wid], scope, function() {
scope.updatingBalance = false;
if (cb) cb();
});
});
};
root.setAlternativeAmount = function(w, tx, cb) {
rateService.whenAvailable(function() {
_.each(tx.outs, function(out) {
var valueSat = out.value * w.settings.unitToSatoshi;
out.alternativeAmount = rateService.toFiat(valueSat, w.settings.alternativeIsoCode);
out.alternativeIsoCode = w.settings.alternativeIsoCode;
});
if (cb) return cb(tx);
});
};
root.updateTxs = function() {
var w = $rootScope.wallet;
if (!w) return;
var res = w.getPendingTxProposals();
_.each(res.txs, function(tx) {
root.setAlternativeAmount(w, tx);
if (tx.merchant) {
var url = tx.merchant.request_url;
var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1];
tx.merchant.domain = domain;
}
});
$rootScope.txps = res.txs;
$rootScope.pendingTxCount = res.pendingForUs;
};
root.deleteWallet = function($scope, w, cb) {
if (!w) return root.onErrorDigest();
var name = w.getName();
$rootScope.iden.deleteWallet(w.id, function() {
notification.info(name + ' deleted', $filter('translate')('This wallet was deleted'));
return cb();
});
};
return root;
});

View File

@ -1,9 +1,9 @@
'use strict';
angular.module('copayApp.services')
.factory('identityService', function($rootScope, $location, $timeout, pluginManager, controllerUtils) {
var root = {};
.factory('identityService', function($rootScope, $location, $timeout, $filter, pluginManager, notification, pendingTxsService, balanceService, applicationService) {
notification.enableHtml5Mode(); // for chrome: if support, enable it
var root = {};
root.check = function(scope) {
copay.Identity.checkIfExistsAny({
pluginManager: pluginManager,
@ -22,10 +22,25 @@ angular.module('copayApp.services')
});
};
root.create = function(scope, form) {
root.goWalletHome = function() {
var w = $rootScope.wallet;
if (w) {
if (!w.isComplete()) {
$location.path('/copayers');
} else {
if ($rootScope.pendingPayment) {
$location.path('paymentIntent');
} else {
$location.path('homeWallet');
}
}
}
};
root.create = function(email, password, cb) {
copay.Identity.create({
email: form.email.$modelValue,
password: form.password.$modelValue,
email: email,
password: password,
pluginManager: pluginManager,
network: config.network,
networkName: config.networkName,
@ -33,19 +48,10 @@ angular.module('copayApp.services')
passphraseConfig: config.passphraseConfig,
failIfExists: true,
}, function(err, iden) {
if (err || !iden) {
copay.logger.debug(err);
if (err && (err.match('EEXISTS') || err.match('BADCREDENTIALS'))) {
scope.error = 'User already exists!';
} else {
scope.error = 'Unknown error when connecting Insight Server';
}
$rootScope.starting = false;
$timeout(function() {
$rootScope.$digest()
}, 1);
return;
}
if (err) return cb(err);
preconditions.checkState(iden);
root.bind(iden);
var walletOptions = {
nickname: iden.fullName,
networkName: config.networkName,
@ -55,47 +61,255 @@ angular.module('copayApp.services')
name: 'My wallet',
};
iden.createWallet(walletOptions, function(err, wallet) {
if (err || !wallet) {
copay.logger.debug(err);
scope.error = 'Could not create default wallet';
$rootScope.starting = false;
$timeout(function() {
$rootScope.$digest()
}, 1);
return;
}
controllerUtils.bindProfile(scope, iden, wallet.id);
return cb(err);
});
});
};
root.open = function(scope, form) {
copay.Identity.open({
email: form.email.$modelValue,
password: form.password.$modelValue,
root.open = function(email, password, cb) {
var opts = {
email: email,
password: password,
pluginManager: pluginManager,
network: config.network,
networkName: config.networkName,
walletDefaults: config.wallet,
passphraseConfig: config.passphraseConfig,
}, function(err, iden) {
if (err && !iden) {
if ((err.toString() || '').match('PNOTFOUND')) {
scope.error = 'Invalid email or password';
} else {
scope.error = 'Unknown error';
}
$rootScope.starting = false;
$timeout(function() {
$rootScope.$digest()
}, 1);
} else {
var firstWallet = iden.getLastFocusedWallet();
controllerUtils.bindProfile(scope, iden, firstWallet);
};
copay.Identity.open(opts, function(err, iden) {
if (err) return cb(err);
root.bind(iden);
iden.openWallets();
return cb();
});
};
root.deleteWallet = function(w, cb) {
$rootScope.iden.deleteWallet(w.id, cb);
};
root.isFocused = function(wid) {
return $rootScope.wallet && wid === $rootScope.wallet.getId();
};
root.setupGlobalVariables = function(iden) {
$rootScope.pendingTxCount = 0;
$rootScope.reconnecting = false;
$rootScope.iden = iden;
};
root.setPaymentWallet = function(w) {
root.setFocusedWallet(w);
$location.path('/send');
};
root.setFocusedWallet = function(w, dontUpdateIt) {
if (!_.isObject(w))
w = $rootScope.iden.getWalletById(w);
preconditions.checkState(w && _.isObject(w));
copay.logger.debug('Set focus:', w.getName());
$rootScope.wallet = w;
if (!dontUpdateIt)
$rootScope.iden.updateFocusedTimestamp(w.getId());
pendingTxsService.update();
$timeout(function() {
$rootScope.$digest();
})
};
root.installWalletHandlers = function(w) {
var wid = w.getId();
w.on('connectionError', function() {
if (root.isFocused(wid)) {
var message = "Could not connect to the Insight server. Check your settings and network configuration";
notification.error('Networking Error', message);
}
});
w.on('corrupt', function(peerId) {
if (root.isFocused(wid)) {
notification.error('Error', $filter('translate')('Received corrupt message from ') + peerId);
}
});
w.on('ready', function() {
var isFocused = root.isFocused(wid);
copay.logger.debug('Wallet:' + w.getName() + ' is ready. Focused:', isFocused);
balanceService.update(w, function() {
$rootScope.$digest();
}, isFocused);
});
w.on('tx', function(address, isChange) {
if (!isChange) {
notification.funds('Funds received on ' + w.getName(), address);
}
balanceService.update(w, function() {
$rootScope.$digest();
}, root.isFocused(wid));
});
w.on('balanceUpdated', function() {
balanceService.update(w, function() {
$rootScope.$digest();
}, root.isFocused(wid));
});
w.on('insightReconnected', function() {
$rootScope.reconnecting = false;
balanceService.update(w, function() {
$rootScope.$digest();
}, root.isFocused(wid));
});
w.on('insightError', function() {
if (root.isFocused(wid)) {
$rootScope.reconnecting = true;
$rootScope.$digest();
}
});
w.on('newAddresses', function() {
// Nothing yet
});
w.on('txProposalsUpdated', function() {
if (root.isFocused(wid)) {
pendingTxsService.update();
}
});
w.on('paymentACK', function(memo) {
notification.success('Payment Acknowledged', memo);
});
w.on('txProposalEvent', function(e) {
if (root.isFocused(wid)) {
pendingTxsService.update();
}
// TODO: add wallet name notification
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
var name = w.getName();
switch (e.type) {
case 'new':
notification.info('[' + name + '] New Transaction',
$filter('translate')('You received a transaction proposal from') + ' ' + user);
break;
case 'signed':
notification.info('[' + name + '] Transaction Signed',
$filter('translate')('A transaction was signed by') + ' ' + user);
break;
case 'signedAndBroadcasted':
notification.info('[' + name + '] Transaction Approved',
$filter('translate')('A transaction was signed and broadcasted by') + ' ' + user);
break;
case 'rejected':
notification.info('[' + name + '] Transaction Rejected',
$filter('translate')('A transaction was rejected by') + ' ' + user);
break;
case 'corrupt':
notification.error('[' + name + '] Transaction Error',
$filter('translate')('Received corrupt transaction from') + ' ' + user);
break;
}
$rootScope.$digest();
});
w.on('addressBookUpdated', function(dontDigest) {
if (root.isFocused(wid)) {
if (!dontDigest) {
$rootScope.$digest();
}
}
});
w.on('connect', function(peerID) {
$rootScope.$digest();
});
// TODO?
// w.on('close', );
// w.on('locked',);
};
root.bind = function(iden) {
preconditions.checkArgument(_.isObject(iden));
copay.logger.debug('Binding profile...');
var self = this;
root.setupGlobalVariables(iden);
iden.on('newWallet', function(wid) {
var w = iden.getWalletById(wid);
copay.logger.debug('newWallet:', w.getName(), wid, iden.getLastFocusedWalletId());
root.installWalletHandlers(w);
if (wid == iden.getLastFocusedWalletId()) {
$rootScope.starting = false;
copay.logger.debug('GOT Focused wallet:', w.getName());
root.setFocusedWallet(w, true);
root.goWalletHome();
}
// At the end (after all handlers are in place)...start the wallet.
w.netStart();
});
iden.on('noWallets', function() {
$location.path('/create');
$rootScope.$digest()
});
iden.on('deletedWallet', function(wid) {
notification.info('Wallet deleted', $filter('translate')('This wallet was deleted'));
if ($rootScope.wallet.id === wid) {
$rootScope.wallet = null;
var lastFocused = iden.getLastFocusedWalletId();
root.setFocusedWallet(lastFocused);
}
});
iden.on('closed', function() {
delete $rootScope['wallet'];
delete $rootScope['iden'];
applicationService.restart();
});
};
root.signout = function() {
if ($rootScope.iden) {
$rootScope.iden.close();
}
};
root.createWallet = function(opts, cb) {
$rootScope.iden.createWallet(opts, cb);
};
root.importWallet = function(encryptedObj, pass, opts, cb) {
copay.Compatibility.importEncryptedWallet($rootScope.iden, encryptedObj, pass, opts);
};
root.joinWallet = function(opts, cb) {
$rootScope.iden.joinWallet(opts, function(err, w) {
return cb(err);
});
};
root.importProfile = function(str, password, cb) {
copay.Identity.importFromEncryptedFullJson(str, password, {
pluginManager: pluginManager,
network: config.network,
networkName: config.networkName,
walletDefaults: config.wallet,
passphraseConfig: config.passphraseConfig,
}, function(err, iden, walletObjs) {
if (err) return cb(err);
root.bind(iden);
iden.importMultipleWalletsFromObj(walletObjs);
return cb();
});
};
return root;

View File

@ -0,0 +1,15 @@
'use strict';
angular.module('copayApp.services')
.factory('pendingTxsService', function($rootScope) {
var root = {};
root.update = function(w) {
var w = $rootScope.wallet;
if (!w) return;
var ret = w.getPendingTxProposalsCount();
$rootScope.pendingTxCount = ret.pendingForUs;
};
return root;
});

View File

@ -136,7 +136,8 @@ describe('PublicKeyRing model', function() {
[true, false].forEach(function(isChange) {
for (var i = 0; i < 2; i++) {
var a = w.generateAddress(isChange, k.pub);
var aStr = w.generateAddress(isChange, k.pub);
var a= new bitcore.Address(aStr);
a.isValid().should.equal(true);
a.isScript().should.equal(true);
a.network().name.should.equal('livenet');
@ -152,27 +153,46 @@ describe('PublicKeyRing model', function() {
var setup = getCachedW();
var pubkeyring = setup.w;
var address = pubkeyring.getAddress(3, false, 4);
(pubkeyring._cacheAddressMap[3][0][4]).should.equal(address);
var address = pubkeyring._getAddress(3, false, 4);
pubkeyring.cache.addressToPath[address].should.equal("m/45'/4/0/3");
});
it('getAddress cache hit doesn\'t alter state', function() {
it('cach4es calls to getAddress (2)', function() {
var setup = getCachedW();
var pubkeyring = setup.w;
var spySave;
pubkeyring.getAddress(3, false, 4);
spySave = sinon.stub(pubkeyring, '_cacheAddress');
spySave.onFirstCall().throws(new Error());
pubkeyring.getAddress(3, false, 4);
spySave.restore();
var address = pubkeyring.generateAddress(false, setup.pub);
_.indexOf(pubkeyring.getReceiveAddresses(),address).should.be.above(-1);
_.indexOf(pubkeyring.getAddresses(),address).should.be.above(-1);
});
it('should return PublicKeyRing addresses', function() {
it('cach4es calls to getAddress (3)', function() {
var setup = getCachedW();
var pubkeyring = setup.w;
var address = pubkeyring.generateAddress(true, setup.pub);
_.indexOf(pubkeyring.getReceiveAddresses(),address).should.be.equal(-1);
_.indexOf(pubkeyring.getAddresses(),address).should.be.above(-1);
});
it('should generate one address by default', function() {
var k = createW();
var w = k.w;
var a = w.getAddresses();
a.length.should.equal(1);
});
it('should generate one address by default', function() {
var k = createW();
var w = k.w;
var a = w.getAddresses();
a.length.should.equal(1);
a = w.getAddresses();
a.length.should.equal(1);
});
it('should generate 4+1 addresses', function() {
var k = createW();
var w = k.w;
@ -184,15 +204,16 @@ describe('PublicKeyRing model', function() {
w.generateAddress(isChange, k.pub);
}
});
});
var as = w.getAddressesInfo();
as.length.should.equal(5); // include pre-generated shared one
for (var j in as) {
var a = as[j];
a.address.isValid().should.equal(true);
a.addressStr.should.equal(a.address.toString());
a.isChange.should.equal([false, true, true, false, false][j]);
}
it('should check isChange 4+1 addresses', function() {
var k = createW();
var w = k.w;
var a = w.getAddresses();
_.each(a, function(a, j) {
var addr = new bitcore.Address(a);
w.addressIsChange(a).should.equal([false, true, true, false, false][j]);
});
});
@ -405,7 +426,7 @@ describe('PublicKeyRing model', function() {
(function() {
PublicKeyRing.fromObj(pkr);
}).should.throw('bad data format: Did you use .toObj()?');
}).should.throw('format');
});
@ -432,6 +453,33 @@ describe('PublicKeyRing model', function() {
});
it('#fromObj old backup ', function() {
var pkr = PublicKeyRing.fromObj(JSON.parse(obj));
should.exist(pkr);
pkr.isComplete().should.equal(true);
pkr.requiredCopayers.should.equal(2);
pkr.totalCopayers.should.equal(2);
});
it('#fromObj #toObj rountrip', function() {
var obj2 = PublicKeyRing.fromObj(JSON.parse(obj)).toObj();
var pkr = PublicKeyRing.fromObj(obj2);
pkr.isComplete().should.equal(true);
pkr.requiredCopayers.should.equal(2);
pkr.totalCopayers.should.equal(2);
});
it('#fromUntrustedObj #toObj rountrip', function() {
var obj2 = PublicKeyRing.fromUntrustedObj(JSON.parse(obj)).toObj();
var pkr = PublicKeyRing.fromUntrustedObj(obj2);
pkr.isComplete().should.equal(true);
pkr.requiredCopayers.should.equal(2);
pkr.totalCopayers.should.equal(2);
});
it('#getHDParams should return the right one', function() {
var config = {
networkName: 'livenet',
@ -475,15 +523,15 @@ describe('PublicKeyRing model', function() {
});
});
it('#getForPath should return 5 pubkeys', function() {
it('#_getForPath should return 5 pubkeys', function() {
var w = getCachedW().w;
var pubkeys = w.getForPath('m/45\'/2147483647/1/0');
var pubkeys = w._getForPath('m/45\'/2147483647/1/0');
pubkeys.length.should.equal(5);
});
it('#getForPaths should return 2 arrays of 5 pubkey ', function() {
it('#_getForPaths should return 2 arrays of 5 pubkey ', function() {
var w = getCachedW().w;
var pubkeys = w.getForPaths(['m/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1']);
var pubkeys = w._getForPaths(['m/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1']);
pubkeys.length.should.equal(2);
pubkeys[0].length.should.equal(5);
pubkeys[1].length.should.equal(5);
@ -498,4 +546,8 @@ describe('PublicKeyRing model', function() {
ret.pubKeys[1].length.should.equal(5);
});
});
var obj = '{"walletId":"0a903a2eb33793d1","networkName":"testnet","requiredCopayers":2,"totalCopayers":2,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1},{"copayerIndex":0,"changeIndex":39,"receiveIndex":0},{"copayerIndex":1,"changeIndex":102,"receiveIndex":39}],"copayersExtPubKeys":["tpubD9peJo88ArhgmJNqRkQmhHt4zAGTYVowsHrDj385xyXyMy4RhWZpV5Qx2mMDUVzpbAD5V9jci5D7cZaHhjLYP8gEkngmTKtSF4Y7V3qkAsy","tpubD8udwzKWwNUgoE2WG7LYsXKf5m1eRtJ1Etp43vnoxViFmrmZ1ND2CkdqGyQtuidcN1CiqdBUvbKegbdsMQaj5VLY2hbA4LEnLDrqkgSzikz"],"nicknameFor":{"03338b105850c7126f1f5b0439b357765b17ead8eed15bcdfdbd28d0e3915b696f":"5@queparece","0286b376d65cc4af0de5932fb8299cbef2ca9ed37ec9fdb0edfd4e9cb74eac45da":"4@queparece"}}';

View File

@ -8,6 +8,7 @@ var Transaction = bitcore.Transaction;
var Address = bitcore.Address;
var PayPro = bitcore.PayPro;
var Buffer = bitcore.Buffer;
var Script = bitcore.Script;
function assertObjectEqual(a, b) {
@ -163,17 +164,13 @@ describe('Wallet model', function() {
should.exist(w.addressBook);
});
it('should provide some basic features', function(done) {
it('should provide some basic features', function() {
var opts = {};
var w = cachedCreateW();
addCopayers(w);
w.publicKeyRing.generateAddress(false, w.publicKey);
w.publicKeyRing.isComplete().should.equal(true);
w.generateAddress(true).isValid().should.equal(true);
w.generateAddress(true, function(addr) {
addr.isValid().should.equal(true);
done();
});
(new bitcore.Address(w.generateAddress(true))).isValid().should.equal(true);
});
var unspentTest = [{
@ -224,14 +221,18 @@ describe('Wallet model', function() {
return w;
};
var unSpentTestFromWallet = function(w, addrStr) {
unspentTest[0].address = addrStr;
var a = new bitcore.Address(addrStr);
unspentTest[0].scriptPubKey = Script.createP2SH(a.payload()).getBuffer().toString('hex');
};
it('#create, fail for network', function() {
var w = cachedCreateW2();
unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true));
var f = function() {
w._createTxProposal(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
@ -240,15 +241,14 @@ describe('Wallet model', function() {
unspentTest
);
};
f.should.throw(Error);
f.should.throw('networkname');
});
it('#create, check builder opts', function() {
var w = cachedCreateW2();
unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey);
unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true));
var txp = w._createTxProposal(
'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79',
'123456789',
@ -264,9 +264,7 @@ describe('Wallet model', function() {
it('#create, 1 sign', function() {
var w = cachedCreateW2();
unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey);
unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true));
var txp = w._createTxProposal(
'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79',
@ -288,9 +286,7 @@ describe('Wallet model', function() {
var w = cachedCreateW2();
var comment = 'This is a comment';
unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey);
unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true));
var txp = w._createTxProposal(
'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79',
@ -308,9 +304,7 @@ describe('Wallet model', function() {
var w = cachedCreateW2();
var comment = 'Lorem ipsum dolor sit amet, suas euismod vis te, velit deleniti vix an. Pri ex suscipit similique, inermis per';
unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey);
unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true));
(function() {
w._createTxProposal(
@ -324,7 +318,7 @@ describe('Wallet model', function() {
it('#addressIsOwn', function() {
var wallet = cachedCreateW2();
var allAddresses = wallet.getAddressesStr();
var allAddresses = wallet.getAddresses();
for (var i = 0; i < allAddresses.length; i++) {
wallet.addressIsOwn(allAddresses[i]).should.equal(true);
}
@ -340,8 +334,7 @@ describe('Wallet model', function() {
var ts = Date.now();
for (var isChange = false; !isChange; isChange = true) {
for (var index = 0; index < 3; index++) {
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange, w.publicKey).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange, w.publicKey);
unSpentTestFromWallet(unspentTest[0], w.publicKeyRing.generateAddress(true));
var txp = w._createTxProposal(
'mgGJEugdPnvhmRuFdbdQcFfoFLc1XXeB79',
'123456789',
@ -531,14 +524,14 @@ describe('Wallet model', function() {
it('#isReady', function() {
it('#isComplete', function() {
var w = createW();
w.publicKeyRing.isComplete().should.equal(false);
w.isReady().should.equal(false);
w.isComplete().should.equal(false);
var w2 = createW2();
w2.publicKeyRing.isComplete().should.equal(true);
w2.isReady().should.equal(true);
w2.isComplete().should.equal(true);
});
it('handle network indexes correctly', function() {
@ -910,7 +903,7 @@ describe('Wallet model', function() {
w.issueTx(ntxid, function(err, txid, status) {
should.not.exist(err);
txp.getSent().should.be.above(now-1);
txp.getSent().should.be.above(now - 1);
txp.sentTxid.should.be.equal(txid);
txid.should.equal(1234);
status.should.equal(Wallet.TX_BROADCASTED);
@ -1170,16 +1163,14 @@ describe('Wallet model', function() {
describe('#subscribeToAddresses', function() {
it('should subscribe successfully', function() {
var w = cachedCreateW2();
var addr1 = w.getAddresses()[0].toString();
var addr2 = w.generateAddress().toString();
var addr3 = w.generateAddress(true).toString();
chai.expect(w.getAddresses().length).to.equal(3);
w.blockchain.subscribe = sinon.spy();
w.subscribeToAddresses();
w.blockchain.subscribe.calledOnce.should.equal(true);
var arg = w.blockchain.subscribe.getCall(0).args[0];
chai.expect(_.difference(arg, [addr1, addr2, addr3]).length).to.equal(0);
_.intersection(arg, [addr2, addr3]).length.should.be.equal(2);
});
});
@ -1967,27 +1958,40 @@ describe('Wallet model', function() {
it('should emit notification when tx received', function(done) {
it('should emit notification when tx received', function() {
var w = cachedCreateW2();
var addr1 = w.generateAddress(false);
sinon.stub(w,'subscribeToAddresses');
w.blockchain.removeAllListeners = sinon.stub();
var spy = sinon.spy(w, 'emit');
w.blockchain.on = sinon.stub();
w.generateAddress(false, function(addr1) {
w.generateAddress(true, function(addr2) {
w.blockchain.on = sinon.stub().withArgs('tx').yields({
address: addr1.toString(),
});
w._setBlockchainListeners();
spy.calledWith('tx', addr1.toString(), false).should.be.true;
w.blockchain.on = sinon.stub().withArgs('tx').yields({
address: addr2.toString(),
});
w._setBlockchainListeners();
spy.calledWith('tx', addr2.toString(), true).should.be.true;
done();
});
w.blockchain.on.withArgs('tx').yields({
address: addr1,
});
var spy = sinon.spy(w, 'emit');
w._setupBlockchainHandlers();
spy.calledWith('tx', addr1, false).should.equal(true);
});
it('should emit notification when tx received (change addr)', function() {
var w = cachedCreateW2();
var addr1 = w.generateAddress(true);
sinon.stub(w,'subscribeToAddresses');
w.blockchain.removeAllListeners = sinon.stub();
w.blockchain.on = sinon.stub();
w.blockchain.on.withArgs('tx').yields({
address: addr1,
});
var spy = sinon.spy(w, 'emit');
w._setupBlockchainHandlers();
spy.calledWith('tx', addr1, true).should.equal(true);
});
describe('#fromObj / #toObj', function() {
@ -2041,13 +2045,28 @@ describe('Wallet model', function() {
should.exist(w.txProposals.toObj);
should.exist(w.privateKey.toObj);
assertObjectEqual(w.toObj(), JSON.parse(o2));
var obj = w.toObj();
// remove data from new versions
delete obj.publicKeyRing['cache'];
assertObjectEqual(obj, JSON.parse(o2));
});
});
describe('#getTransactionHistory', function() {
var w;
beforeEach(function() {
w = cachedCreateW2();
});
afterEach(function() {
if (w.publicKeyRing.addressIsOwn.restore)
w.publicKeyRing.addressIsOwn.restore();
if (w.publicKeyRing.addressIsChange.restore)
w.publicKeyRing.addressIsChange.restore();
});
it('should return list of txs', function(done) {
var w = cachedCreateW2();
var txs = [{
vin: [{
addr: 'addr_in_1',
@ -2092,11 +2111,17 @@ describe('Wallet model', function() {
items: txs,
totalItems: txs.length,
});
w.getAddressesInfo = sinon.stub().returns([{
addressStr: 'addr_in_1'
}, {
addressStr: 'addr_out_2'
}]);
sinon.stub(w,'getAddresses').returns([ 'addr_in_1', 'addr_out_2' ]);
var s = sinon.stub(w.publicKeyRing,'addressIsOwn');
s.withArgs('addr_in_1').returns(true);
s.withArgs('addr_in_2').returns(false);
s.withArgs('addr_out_2').returns(true);
var s2 = sinon.stub(w.publicKeyRing,'addressIsChange');
s2.withArgs('addr_out_1').returns(false);
s2.withArgs('addr_out_2').returns(false);
w.getTransactionHistory(function(err, res) {
res.should.exist;
@ -2113,7 +2138,6 @@ describe('Wallet model', function() {
});
});
it('should return paginated list of txs', function(done) {
var w = cachedCreateW2();
var txs = [{
txid: 'id1',
vin: [{
@ -2161,11 +2185,6 @@ describe('Wallet model', function() {
items: txs.slice(2, 3),
totalItems: txs.length,
});
w.getAddressesInfo = sinon.stub().returns([{
addressStr: 'addr_in_1'
}, {
addressStr: 'addr_out_2'
}]);
w.getTransactionHistory({
currentPage: 2,
@ -2182,17 +2201,11 @@ describe('Wallet model', function() {
});
});
it('should paginate empty list', function(done) {
var w = cachedCreateW2();
var txs = [];
w.blockchain.getTransactions = sinon.stub().yields(null, {
items: txs,
totalItems: txs.length,
});
w.getAddressesInfo = sinon.stub().returns([{
addressStr: 'addr_in_1'
}, {
addressStr: 'addr_out_2'
}]);
w.getTransactionHistory({
currentPage: 2,
@ -2207,7 +2220,6 @@ describe('Wallet model', function() {
});
});
it('should compute sent amount correctly', function(done) {
var w = cachedCreateW2();
var txs = [{
vin: [{
addr: 'addr_in_1',
@ -2234,14 +2246,17 @@ describe('Wallet model', function() {
items: txs,
totalItems: txs.length,
});
w.getAddressesInfo = sinon.stub().returns([{
addressStr: 'addr_in_1'
}, {
addressStr: 'addr_in_2'
}, {
addressStr: 'change',
isChange: true,
}]);
sinon.stub(w,'getAddresses').returns([ 'addr_in_1', 'addr_in_2', 'change']);
var s = sinon.stub(w.publicKeyRing,'addressIsOwn');
s.withArgs('addr_in_1').returns(true);
s.withArgs('addr_in_2').returns(true);
s.withArgs('change').returns(true);
var s2 = sinon.stub(w.publicKeyRing,'addressIsChange');
s2.withArgs('addr_out_2').returns(false);
s2.withArgs('change').returns(true);
w.getTransactionHistory(function(err, res) {
res.should.exist;
@ -2253,7 +2268,6 @@ describe('Wallet model', function() {
});
});
it('should compute moved amount correctly', function(done) {
var w = cachedCreateW2();
var txs = [{
vin: [{
addr: 'addr_1',
@ -2280,14 +2294,16 @@ describe('Wallet model', function() {
items: txs,
totalItems: txs.length,
});
w.getAddressesInfo = sinon.stub().returns([{
addressStr: 'addr_1'
}, {
addressStr: 'addr_2'
}, {
addressStr: 'change',
isChange: true,
}]);
sinon.stub(w,'getAddresses').returns([ 'addr_in_1', 'addr_in_2', 'change']);
var s = sinon.stub(w.publicKeyRing,'addressIsOwn');
s.withArgs('addr_1').returns(true);
s.withArgs('addr_2').returns(true);
s.withArgs('change').returns(true);
var s2 = sinon.stub(w.publicKeyRing,'addressIsChange');
s2.withArgs('addr_1').returns(false);
s2.withArgs('change').returns(true);
w.getTransactionHistory(function(err, res) {
res.should.exist;
@ -2299,7 +2315,6 @@ describe('Wallet model', function() {
});
});
it('should assign label when address in address book', function(done) {
var w = cachedCreateW2();
var txs = [{
vin: [{
addr: 'addr_in_1',
@ -2349,7 +2364,6 @@ describe('Wallet model', function() {
});
});
it('should assign comment from tx proposal if found', function(done) {
var w = cachedCreateW2();
var txs = [{
txid: 'id1',
vin: [{
@ -2483,7 +2497,7 @@ describe('Wallet model', function() {
blockchainOpts: {},
}, function(err, w) {
should.exist(w);
w.isReady().should.equal(true);
w.isComplete().should.equal(true);
var wo = w.toObj();
wo.opts.id.should.equal('48ba2f1ffdfe9708');
wo.opts.spendUnconfirmed.should.equal(true);
@ -2517,7 +2531,7 @@ describe('Wallet model', function() {
// DATA
var o = '{"opts":{"id":"dbfe10c3fae71cea", "spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{},"settings":{"unitName":"BTC","unitToSatoshi":100000000,"unitDecimals":8,"alternativeName":"Argentine Peso","alternativeIsoCode":"ARS"}}';
var o = '{"opts":{"id":"dbfe10c3fae71cea", "spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5","networkName":"testnet"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{ "cache": { "addressToPath": {}}, "walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2,"changeIndex":0,"receiveIndex":0}],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{},"settings":{"unitName":"BTC","unitToSatoshi":100000000,"unitDecimals":8,"alternativeName":"Argentine Peso","alternativeIsoCode":"ARS"}}';
});

View File

@ -17,6 +17,9 @@ FakeBlockchain.prototype.getTransactions = function(addresses, from, to, cb) {
FakeBlockchain.prototype.subscribe = function() {
};
FakeBlockchain.prototype.on = function() {
};
FakeBlockchain.prototype.fixUnspent = function(u) {
this.u = u;
};

View File

@ -1,75 +0,0 @@
'use strict';
var PrivateKey = copay.PrivateKey;
var PublicKeyRing = copay.PublicKeyRing;
var getNewEpk = function() {
return new PrivateKey({
networkName: 'livenet',
})
.deriveBIP45Branch()
.extendedPublicKeyString();
}
describe('Performance tests', function() {
describe('PrivateKey', function() {
it('should optimize BIP32 private key gen time with cache', function() {
var k1 = new PrivateKey();
var generateN = 25;
var generated = [];
var start1 = new Date().getTime();
for (var i = 0; i < generateN; i++) {
var k = JSON.stringify(k1.get(i, false).storeObj());
generated.push(k);
}
var delta1 = new Date().getTime() - start1;
var start2 = new Date().getTime();
for (var i = 0; i < generateN; i++) {
var k = JSON.stringify(k1.get(i, false).storeObj());
generated[i].should.equal(k);
}
var delta2 = new Date().getTime() - start2;
delta2.should.be.below(delta1);
});
});
describe('PublicKeyRing', function() {
var maxN = 7;
for (var n = 1; n < maxN; n++) {
for (var m = 1; m <= n; m++) {
if ((m === 3 && n === 5) ||
(m === 2 && n === 3)) {
var M = m;
var N = n;
(function(M, N) {
it('should optimize BIP32 publickey gen time with cache for ' + M + '-of-' + N, function() {
var pkr1 = new PublicKeyRing({
totalCopayers: N,
requiredCopayers: M
});
for (var i = 0; i < N; i++) {
pkr1.addCopayer(getNewEpk()); // add new random ext public key
}
var generateN = 5;
var generated = [];
var start1 = new Date().getTime();
for (var i = 0; i < generateN; i++) {
var pubKeys = JSON.stringify(pkr1.getPubKeys(i, false));
generated.push(pubKeys);
}
var delta1 = new Date().getTime() - start1;
var start2 = new Date().getTime();
for (var i = 0; i < generateN; i++) {
var pubKeys = JSON.stringify(pkr1.getPubKeys(i, false));
generated[i].should.equal(pubKeys);
}
var delta2 = new Date().getTime() - start2;
delta2.should.be.below(delta1);
});
})(M, N);
}
}
}
});
});

View File

@ -15,6 +15,8 @@ saveAs = function(blob, filename) {
describe("Unit: Controllers", function() {
config.plugins.LocalStorage = true;
config.plugins.GoogleDrive = null;
config.plugins.InsightStorage = null;
config.plugins.EncryptedInsightStorage= null;
var anAddr = 'mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy';
var anAmount = 1000;
@ -50,7 +52,7 @@ describe("Unit: Controllers", function() {
var w = {};
w.id = 1234;
w.isReady = sinon.stub().returns(true);
w.isComplete = sinon.stub().returns(true);
w.privateKey = {};
w.settings = {
unitToSatoshi: 100,
@ -254,13 +256,13 @@ describe("Unit: Controllers", function() {
sendForm.amount.$setViewValue(anAmount);
sendForm.comment.$setViewValue(aComment);
scope.loadTxs = sinon.spy();
scope.updateTxs = sinon.spy();
var w = scope.wallet;
scope.submitForm(sendForm);
sinon.assert.callCount(w.spend, 1);
sinon.assert.callCount(w.broadcastTx, 0);
sinon.assert.callCount(scope.loadTxs, 1);
sinon.assert.callCount(scope.updateTxs, 1);
var spendArgs = w.spend.getCall(0).args[0];
spendArgs.toAddress.should.equal(anAddr);
spendArgs.amountSat.should.equal(anAmount * scope.wallet.settings.unitToSatoshi);
@ -270,12 +272,12 @@ describe("Unit: Controllers", function() {
it('should handle big values in 100 BTC', function() {
var old = scope.wallet.settings.unitToSatoshi;
scope.wallet.settings.unitToSatoshi = 100000000;;
scope.wallet.settings.unitToSatoshi = 100000000;
sendForm.address.$setViewValue(anAddr);
sendForm.amount.$setViewValue(100);
sendForm.address.$setViewValue(anAddr);
scope.loadTxs = sinon.spy();
scope.updateTxs = sinon.spy();
scope.submitForm(sendForm);
var w = scope.wallet;
w.spend.getCall(0).args[0].amountSat.should.equal(100 * scope.wallet.settings.unitToSatoshi);
@ -289,7 +291,7 @@ describe("Unit: Controllers", function() {
var old = $rootScope.wallet.settings.unitToSatoshi;
$rootScope.wallet.settings.unitToSatoshi = 100000000;;
$rootScope.wallet.settings.unitToSatoshi = 100000000;
sendForm.address.$setViewValue(anAddr);
sendForm.amount.$setViewValue(5000);
scope.submitForm(sendForm);
@ -602,9 +604,10 @@ describe("Unit: Controllers", function() {
it('Delete a wallet', function() {
var w = scope.wallet;
scope.deleteWallet(w);
scope.$digest();
expect(scope.wallet).equal(null);
scope.deleteWallet(w, function() {
scope.$digest();
expect(scope.wallet).equal(null);
});
});
});

View File

@ -10,7 +10,7 @@ describe("Unit: Testing Directives", function() {
beforeEach(inject(function($rootScope) {
var w = {};
w.isReady = sinon.stub().returns(true);
w.isComplete = sinon.stub().returns(true);
w.privateKey = {};
w.settings = {
unitToSatoshi: 100,

View File

@ -9,7 +9,7 @@ describe('Angular Filters', function() {
beforeEach(inject(function($rootScope) {
var w = {};
w.isReady = sinon.stub().returns(true);
w.isComplete = sinon.stub().returns(true);
w.privateKey = {};
w.settings = {
unitToSatoshi: 100,

View File

@ -26,7 +26,7 @@ describe("Angular services", function() {
beforeEach(inject(function($rootScope) {
var w = {};
w.isReady = sinon.stub().returns(true);
w.isComplete = sinon.stub().returns(true);
w.privateKey = {};
w.settings = {
unitToSatoshi: 100,
@ -65,49 +65,34 @@ describe("Angular services", function() {
describe("Unit: controllerUtils", function() {
describe("Unit: balanceService", function() {
it('should updateBalance in bits', inject(function(controllerUtils, $rootScope) {
it('should updateBalance in bits', inject(function(balanceService, $rootScope) {
var w = $rootScope.wallet;
expect(controllerUtils.updateBalance).not.to.equal(null);
expect(balanceService.update).not.to.equal(null);
var Waddr = Object.keys($rootScope.wallet.balanceByAddr)[0];
var a = {};
a[Waddr] = 200;
w.getBalance = sinon.stub().yields(null, 100000001, a, 90000002, 5);
var orig =controllerUtils.isFocusedWallet;
controllerUtils.isFocusedWallet = sinon.stub().returns(true);
//retuns values in DEFAULT UNIT(bits)
controllerUtils.updateBalance(null, function() {
balanceService.update(w, function() {
var b = w.balanceInfo;
expect(b.totalBalanceBTC).to.be.equal(1.00000001);
expect(b.availableBalanceBTC).to.be.equal(0.90000002);
expect(b.lockedBalanceBTC).to.be.equal(0.09999999);
expect(b.totalBalance).to.be.equal('1,000,000.01');
expect(b.availableBalance).to.be.equal('900,000.02');
expect(b.lockedBalance).to.be.equal(99999.99);
expect($rootScope.totalBalanceBTC).to.be.equal(1.00000001);
expect($rootScope.availableBalanceBTC).to.be.equal(0.90000002);
expect($rootScope.lockedBalanceBTC).to.be.equal(0.09999999);
expect($rootScope.totalBalance).to.be.equal(1000000.01);
expect($rootScope.availableBalance).to.be.equal(900000.02);
expect($rootScope.lockedBalance).to.be.equal(99999.99);
expect($rootScope.balanceByAddr[Waddr]).to.equal(2);
expect($rootScope.safeUnspentCount).to.equal(5);
expect($rootScope.topAmount).to.equal(899800.02);
});
controllerUtils.isFocusedWallet = orig;
expect(b.balanceByAddr[Waddr]).to.equal(2);
expect(b.safeUnspentCount).to.equal(5);
expect(b.topAmount).to.equal(899800.02);
},false);
}));
it('should set the rootScope', inject(function(controllerUtils, $rootScope) {
controllerUtils.setupGlobalVariables(function() {
expect($rootScope.txAlertCount).to.be.equal(0);
expect($rootScope.insightError).to.be.equal(0);
expect($rootScope.isCollapsed).to.be.equal(0);
expect($rootScope.unitName).to.be.equal('bits');
});
}));
});
describe("Unit: Notification Service", function() {

View File

@ -1,21 +1,19 @@
<div ng-controller="CopayersController">
<div ng-if='$root.wallet && $root.wallet.isReady()' ng-init="goToWallet()"></div>
<div ng-if='$root.wallet && $root.wallet.isComplete()' ng-init="goToWallet()"></div>
<div class="row hide-for-large-up">
<div class="medium-12 small-12 columns">
<h1 translate>
Waiting copayers for {{$root.wallet.getName()}}
<small>{{$root.wallet.requiredCopayers}}-of-{{$root.wallet.totalCopayers}}</small>
Waiting copayers...
</h1>
</div>
</div>
<div class="row">
<div class="large-12 columns">
<div ng-if="!$root.wallet.isReady()">
<div ng-if="!$root.wallet.isComplete()">
<div class="panel oh">
<h2 class="line-b">
Share this secret with your other copayers
<small>{{$root.wallet.requiredCopayers}}-of-{{$root.wallet.totalCopayers}}</small>
</h2>
<div class="text-center">
<qrcode size="250" data="{{secret}}"></qrcode>
@ -27,7 +25,7 @@
</div>
<div class="panel oh">
<div ng-include="'views/includes/copayer.html'"></div>
<div class="copay-box" ng-if="!$root.wallet.isReady()">
<div class="copay-box" ng-if="!$root.wallet.isComplete()">
<span ng-include="'views/includes/photo.html'"></span>
<p class="size-12 text-white text-light m0">
<i class="fi-loop icon-rotate spinner"></i>

View File

@ -1,5 +1,4 @@
<div class="transactions" data-ng-controller="HistoryController" data-ng-init="update()">
<div ng-show='$root.wallet.isReady()'>
<div class="row">
<div class="large-12 medium-12 small-12 columns">
<h1 class="hide-for-large-up">{{$root.title}}</h1>
@ -46,8 +45,8 @@
'text-primary' : btx.action == 'received',
'text-warning': btx.action == 'sent',
'text-gray': btx.action == 'moved'}">
<b>{{btx.amount| noFractionNumber}} {{$root.wallet.settings.unitName}}</b>
<b ng-show="btx.alternativeAmount != null">{{btx.alternativeAmount| noFractionNumber}} {{$root.wallet.settings.alternativeIsoCode}}</b>
<b>{{btx.amount}} {{$root.wallet.settings.unitName}}</b>
<b ng-show="btx.alternativeAmount != null">{{btx.alternativeAmount}} {{$root.wallet.settings.alternativeIsoCode}}</b>
</div>
</div>
@ -140,5 +139,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@ -1,5 +1,4 @@
<div class="home-wallet" ng-controller="HomeWalletController">
<div ng-show='$root.wallet.isReady()'>
<div class="row hide-for-large-up">
<div class="medium-12 small-12 columns">
<h1 translate>Home</h1>
@ -18,15 +17,15 @@
<div class="text-right">
<span class="size-21">
<strong>
<span ng-if="!$root.updatingBalance">{{totalBalance || 0 |noFractionNumber}}</span>
<span ng-if="!$root.updatingBalance">{{$root.wallet.balanceInfo.totalBalance || 0}}</span>
<span ng-if="$root.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
{{$root.wallet.settings.unitName}}
</strong>
</span>
<span class="size-14 db m5t text-gray">
<span ng-if="!$root.updatingBalance && alternativeBalanceAvailable">{{totalBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}}</span>
<span ng-if="!$root.updatingBalance && !alternativeBalanceAvailable">N/A</span>
<span ng-if="$root.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<span ng-if="!$root.wallet.balanceInfo.updatingBalance && $root.wallet.balanceInfo.alternativeBalanceAvailable">{{$root.wallet.balanceInfo.totalBalanceAlternative}} {{$root.wallet.balanceInfo.alternativeIsoCode}}</span>
<span ng-if="!$root.wallet.balanceInfo.updatingBalance && !$root.wallet.balanceInfo.alternativeBalanceAvailable">N/A</span>
<span ng-if="$root.wallet.balanceInfo.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
</span>
</div>
</div>
@ -41,10 +40,10 @@
<div class="panel oh">
<h2 class="line-b" translate>Quick receive</h2>
<div class="text-center">
<qrcode size="220" data="bitcoin:{{$root.addrInfos[0].addressStr}}"></qrcode>
<qrcode size="220" data="bitcoin:{{addr}}"></qrcode>
<div class="m10t">
<h4 class="size-12">{{$root.addrInfos[0].addressStr}} </h4>
<h4 class="size-12">{{addr}} </h4>
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
@ -60,7 +59,5 @@
</div>
</div>
</div>
</div>
</div>

View File

@ -12,13 +12,13 @@
</div>
</div>
<div class="founds size-12">
<span ng-if="!$root.wallet.isReady()">Waiting for copayers...</span>
<div ng-if="$root.wallet.isReady()">
<span ng-if="$root.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<div ng-if="$root.wallet && !$root.updatingBalance" data-options="disable_for_touch:true">
<b class="m5r">{{totalBalance || 0 |noFractionNumber}} {{$root.wallet.settings.unitName}}</b>
<span ng-if="alternativeBalanceAvailable" class="alt-currency">{{totalBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}}</span>
<span ng-if="!alternativeBalanceAvailable" class="alt-currency">N/A</span>
<span ng-if="!$root.wallet.isComplete()">Waiting for copayers...</span>
<div ng-if="$root.wallet.isComplete()">
<span ng-if="$root.wallet.balanceInfo.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<div ng-if="$root.wallet && !$root.wallet.balanceInfo.updatingBalance" data-options="disable_for_touch:true">
<b class="m5r">{{$root.wallet.balanceInfo.totalBalance || 0}} {{$root.wallet.settings.unitName}}</b>
<span ng-if="$root.wallet.balanceInfo.alternativeBalanceAvailable" class="alt-currency">{{$root.wallet.balanceInfo.totalBalanceAlternative}} {{$root.wallet.balanceInfo.alternativeIsoCode}}</span>
<span ng-if="!$root.wallet.balanceInfo.alternativeBalanceAvailable" class="alt-currency">N/A</span>
</div>
</div>
</div>
@ -56,12 +56,12 @@
<div class="ellipsis name-wallet">{{item.name || item.id}}</div>
</div>
<div class="oh">
<span ng-if="item.isReady() && item.balanceInfo.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<div ng-if="item.isReady() && !item.balanceInfo.updatingBalance" data-options="disable_for_touch:true">
<b class="m5r size-12">{{item.balanceInfo.totalBalance || 0 |noFractionNumber}} {{item.settings.unitName}}</b>
<span class="alt-currency size-10">{{item.balanceInfo.totalBalanceAlternative |noFractionNumber:2}} {{item.balanceInfo.alternativeIsoCode}}</span>
<span ng-if="item.isComplete() && item.balanceInfo.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<div ng-if="item.isComplete() && !item.balanceInfo.updatingBalance" data-options="disable_for_touch:true">
<b class="m5r size-12">{{item.balanceInfo.totalBalance || 0}} {{item.settings.unitName}}</b>
<span class="alt-currency size-10">{{item.balanceInfo.totalBalanceAlternative}} {{item.balanceInfo.alternativeIsoCode}}</span>
</div>
<span ng-if="!item.isReady()">Waiting for copayers...</span>
<span ng-if="!item.isComplete()">Waiting for copayers...</span>
</div>
</a>
</div>

View File

@ -1,9 +1,9 @@
<div ng-controller="SidebarController" ng-init="getWallets()">
<div ng-controller="SidebarController" ng-init="init()">
<header ng-show="$root.wallet">
<div class="col1">
<div class="avatar-wallet">{{$root.wallet.getName() | limitTo: 1}}</div>
</div>
<div class="col2" ng-class="{'col2_full':!wallets[0]}">
<div class="col2" ng-class="{'col2_full':!wallets.length}">
<div class="oh m5t m10r">
<div class="right size-10">[ {{$root.wallet.requiredCopayers}} of {{$root.wallet.totalCopayers}} ]</div>
<div class="name-wallet">
@ -11,18 +11,18 @@
</div>
</div>
<div class="founds size-12">
<span ng-if="!$root.wallet.isReady()">Waiting for copayers...</span>
<div ng-if="$root.wallet.isReady()">
<span ng-if="$root.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<div ng-if="$root.wallet && !$root.updatingBalance" data-options="disable_for_touch:true">
<b class="m5r">{{totalBalance || 0 |noFractionNumber}} {{$root.wallet.settings.unitName}}</b>
<span ng-if="alternativeBalanceAvailable" class="alt-currency">{{totalBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}}</span>
<span ng-if="!alternativeBalanceAvailable" class="alt-currency">N/A</span>
<span ng-if="!$root.wallet.isComplete()">Waiting for copayers...</span>
<div ng-if="$root.wallet.isComplete()">
<span ng-if="$root.wallet.balanceInfo.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<div ng-if="$root.wallet && !$root.wallet.balanceInfo.updatingBalance" data-options="disable_for_touch:true">
<b class="m5r">{{$root.wallet.balanceInfo.totalBalance || 0}} {{$root.wallet.settings.unitName}}</b>
<span ng-if="$root.wallet.balanceInfo.alternativeBalanceAvailable" class="alt-currency">{{$root.wallet.balanceInfo.totalBalanceAlternative}} {{$root.wallet.balanceInfo.alternativeIsoCode}}</span>
<span ng-if="!$root.wallet.balanceInfo.alternativeBalanceAvailable" class="alt-currency">N/A</span>
</div>
</div>
</div>
</div>
<div class="col3" ng-if="wallets[0]">
<div class="col3" ng-if="wallets.length">
<a ng-class="{'selected':walletSelection}"
ng-click="toggleWalletSelection()">
<span ng-show="!walletSelection">
@ -34,12 +34,12 @@
</a>
</div>
</header>
<div class="locked" ng-show="lockedBalance && !walletSelection">
<div class="locked" ng-show="$root.wallet.balanceInfo.lockedBalance && !walletSelection">
<span class="text-gray">
<i class="fi-lock"></i> {{'Locked'|translate}} &nbsp;
</span>
<span ng-if="$root.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<span ng-if="$root.wallet && !$root.updatingBalance" class="text-gray"><b>{{lockedBalance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}} </b> - {{lockedBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}}
<span ng-if="$root.wallet.balanceInfo.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<span ng-if="$root.wallet && !$root.wallet.balanceInfo.updatingBalance" class="text-gray"><b>{{$root.wallet.balanceInfo.lockedBalance || 0}} {{$root.wallet.settings.unitName}} </b> - {{$root.wallet.balanceInfo.lockedBalanceAlternative}} {{$root.wallet.balanceInfo.alternativeIsoCode}}
</span>
<i class="fi-info medium right text-gray size-14"
tooltip="{{'Balance locked in pending transaction proposals'|translate}}"
@ -62,12 +62,12 @@
<div class="ellipsis name-wallet">{{item.name || item.id}}</div>
</div>
<div class="oh">
<span ng-if="item.isReady() && item.balanceInfo.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<div ng-if="item.isReady() && !item.balanceInfo.updatingBalance" data-options="disable_for_touch:true">
<b class="m5r size-12">{{item.balanceInfo.totalBalance || 0 |noFractionNumber}} {{item.settings.unitName}}</b>
<span class="alt-currency size-10">{{item.balanceInfo.totalBalanceAlternative |noFractionNumber:2}} {{item.balanceInfo.alternativeIsoCode}}</span>
<span ng-if="item.isComplete() && item.balanceInfo.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<div ng-if="item.isComplete() && !item.balanceInfo.updatingBalance" data-options="disable_for_touch:true">
<b class="m5r size-12">{{item.balanceInfo.totalBalance || 0}} {{item.settings.unitName}}</b>
<span class="alt-currency size-10">{{item.balanceInfo.totalBalanceAlternative}} {{item.balanceInfo.alternativeIsoCode}}</span>
</div>
<span ng-if="!item.isReady()">Waiting for copayers...</span>
<span ng-if="!item.isComplete()">Waiting for copayers...</span>
</div>
</a>
</div>
@ -75,7 +75,7 @@
</ul>
</div>
<ul class="side-nav" ng-if="(!walletSelection || !wallets[0]) && $root.wallet.isReady()">
<ul class="side-nav" ng-if="(!walletSelection || !wallets[0]) && $root.wallet.isComplete()">
<li data-ng-repeat="item in menu" ui-route="{{item.link}}" class="nav-item" data-ng-class="{active: isActive(item)}">
<a href="#!/{{item.link}}" ng-click="toggleCollapse()" class="db p20h">
<i class="size-21 m20r {{item.icon}}"></i> {{item.title|translate}}

View File

@ -9,9 +9,9 @@
<div class="row" ng-repeat="out in tx.outs">
<div class="large-3 medium-3 small-4 columns">
<div class="size-12">
<span>{{out.value |noFractionNumber}} {{$root.wallet.settings.unitName}}</span>
<span>{{out.value}} {{$root.wallet.settings.unitName}}</span>
<span class="label secondary round">
{{out.alternativeAmount|noFractionNumber}} {{out.alternativeIsoCode}}
{{out.alternativeAmount}} {{out.alternativeIsoCode}}
</span>
</div>
</div>
@ -106,7 +106,7 @@
{{tx.missingSignatures}} signatures missing
</div>
<div class="ellipsis small text-gray show-for-large-up m5t">
<strong translate>Fee</strong>: {{tx.fee|noFractionNumber}} {{$root.wallet.settings.unitName}}
<strong translate>Fee</strong>: {{tx.fee}} {{$root.wallet.settings.unitName}}
<strong translate>Proposal ID</strong>: {{tx.ntxid}}
</div>
</div>

View File

@ -13,7 +13,7 @@
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-show="!$root.updatingBalance">
{{address.balance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}}
{{address.balance || 0}} {{$root.wallet.settings.unitName}}
</span>
</p>
</div>

View File

@ -1,6 +1,7 @@
<div ng-controller="PaymentIntentController" ng-init="open()">
<script type="text/ng-template" id="myModalContent.html">
<h3>Select a wallet to make the payment</h3>
<span ng-show="loading">Loading...</span>
<ul class="w-popup-menu" ng-show="wallets[0]"
ng-class="{'large':wallets.length > 4, 'medium':wallets.length > 2 && wallets.length < 5}">
<li data-ng-repeat="item in wallets track by $index" class="nav-item" ng-click="ok(item)">
@ -17,8 +18,8 @@
<div class="w-popup-sub">
<span ng-if="item.balanceInfo.updatingBalance"><i class="fi-bitcoin-circle icon-rotate spinner"></i></span>
<div ng-if="!item.balanceInfo.updatingBalance" data-options="disable_for_touch:true">
<b class="m5r size-12">{{item.balanceInfo.totalBalance || 0 |noFractionNumber}} {{item.settings.unitName}}</b>
<span class="alt-currency size-10">{{item.balanceInfo.totalBalanceAlternative |noFractionNumber:2}} {{item.balanceInfo.alternativeIsoCode}}</span>
<b class="m5r size-12">{{item.balanceInfo.totalBalance || 0}} {{item.settings.unitName}}</b>
<span class="alt-currency size-10">{{item.balanceInfo.totalBalanceAlternative}} {{item.balanceInfo.alternativeIsoCode}}</span>
</div>
</div>
</a>

View File

@ -1,4 +1,4 @@
<div class="backup" ng-controller="ProfileController">
<div class="backup" ng-controller="ProfileController" ng-init="setWallets()">
<div class="row hide-for-large-up">
<div class="large-12 medium-12 small-12 columns">
<h1>{{$root.title}}</h1>
@ -49,26 +49,33 @@
<th>Type</th>
<th class="hide-for-small-only">Status</th>
<th>Balance</th>
<th>Approx Size</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<tr
data-ng-repeat="item in wallets | orderBy:'name'"
ng-init="isReady = item.isReady();
ng-init="isComplete = item.isComplete();
networkName = item.getNetworkName()"
ng-class="{'deleting':loading==item.id}">
<td>{{item.name || item.id }}</td>
<td>{{item.requiredCopayers}} of {{item.totalCopayers}} - {{networkName}}</td>
<td class="hide-for-small-only">
{{isReady ? 'Complete' : 'Waiting for copayers...'}}
{{isComplete ? 'Complete' : 'Waiting for copayers...'}}
</td>
<td>
<span ng-if="!isReady">-</span>
<span ng-if="isReady">
{{item.balanceInfo.totalBalance || 0 |noFractionNumber}} {{item.settings.unitName}}
<span ng-if="!isComplete">-</span>
<span ng-if="isComplete">
{{item.balanceInfo.totalBalance || 0}} {{item.settings.unitName}}
</span>
</td>
<td>
<span>
{{item.sizes().total/1000}} kB
</span>
</td>
<td class="text-right">
<div ng-show="loading != item.id">
<a title="Download Backup" class="text-gray" ng-click="downloadWalletBackup(item)"

View File

@ -1,5 +1,4 @@
<div class="addresses" ng-controller="ReceiveController">
<div ng-show='$root.wallet.isReady()'>
<div class="addresses" ng-controller="ReceiveController" ng-init="setAddressList()">
<div class="row">
<div class="large-12 medium-12 small-12 columns">
<h1 class="hide-for-large-up">{{$root.title}}</h1>
@ -9,8 +8,8 @@
</div>
</div>
<div class="row">
<div class="large-12 columns" ng-if="!!(addresses|removeEmpty).length">
<div ng-repeat="addr in addresses|removeEmpty|orderBy:'-index':true">
<div class="large-12 columns" ng-if="addresses.length">
<div ng-repeat="addr in addresses">
<div class="panel">
<div class="row show-for-large-up">
<div class="large-7 medium-9 columns">
@ -24,7 +23,7 @@
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span class="size-14" ng-show="!$root.updatingBalance">
<b>{{addr.balance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}}</b>
<b>{{addr.balance || '0'}} {{$root.wallet.settings.unitName}}</b>
</span>
</div>
<div class="large-2 columns show-for-large-up text-right">
@ -43,7 +42,7 @@
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span class="size-14" ng-show="!$root.updatingBalance">
<b>{{addr.balance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}}</b>
<b>{{addr.balance || '0'}} {{$root.wallet.settings.unitName}}</b>
</span>
</div>
</div>
@ -64,6 +63,5 @@
</a>
</div>
</div>
</div>
</div>

View File

@ -1,18 +1,16 @@
<div class="send" ng-controller="SendController" ng-init="loadTxs()">
<div ng-show='$root.wallet.isReady()'>
<div class="row" ng-show="$root.txps.length != 0">
<div class="send" ng-controller="SendController" ng-init="init()">
<div class="row" ng-show="txps.length != 0">
<div class="large-12 columns">
<h2 translate>Pending Transactions Proposals</h2>
<div class="last-transactions"
ng-repeat="tx in $root.txps | paged"
ng-repeat="tx in txps | paged"
ng-include="'views/includes/transaction.html'"></div>
</div>
</div>
<div class="row">
<div class="large-12 medium-12 small-12 columns">
<div ng-show="$root.txps.length != 0" class="line-dashed-h m20b"></div>
<div ng-show="txps.length != 0" class="line-dashed-h m20b"></div>
<h1 class="hide-for-large-up">{{$root.title}}</h1>
</div>
</div>
@ -110,7 +108,7 @@
<div class="input">
<input type="number" id="alternative_amount"
ng-disabled="loading || !isRateAvailable || ($root.merchant && +$root.merchant.total > 0)"
name="alternative" placeholder="{{'Amount'|translate}}" ng-model="alternative"requiredautocomplete="off">
name="alternative" placeholder="{{'Amount'|translate}}" ng-model="alternative" requiredautocomplete="off">
<i class="icon-usd"></i>
</div>
</div>
@ -138,13 +136,13 @@
</p>
<p>
<i>{{amount + defaultFee |noFractionNumber}} {{$root.wallet.settings.unitName}}</i>
<i>{{amount + defaultFee}} {{$root.wallet.settings.unitName}}</i>
<span class="text-gray" ng-if="isRateAvailable">
{{ rateService.toFiat((amount + defaultFee) * unitToSatoshi, alternativeIsoCode) | noFractionNumber: 2 }} {{ alternativeIsoCode }}
{{ alternativeAmountPayPro }} {{ alternativeIsoCode }}
</span>
<span class="text-gray" >
(<span translate>Including fee of</span>
{{defaultFee|noFractionNumber}}
{{defaultFee}}
{{$root.wallet.settings.unitName}})
</span>
<p>
@ -187,10 +185,10 @@
</div>
</div><!-- end of row -->
<div class="row m20b" ng-show="$root.alternativeConversionRate > 0">
<div class="row m20b" ng-show="$root.wallet.balanceInfo.alternativeConversionRate > 0">
<div class="large-12 columns size-12">
<i class="fi-bitcoin-circle"></i>
1 BTC = {{alternativeConversionRate|noFractionNumber:2}} {{alternativeIsoCode}}
1 BTC = {{$root.wallet.balanceInfo.alternativeConversionRate}} {{alternativeIsoCode}}
</div>
</div>
@ -230,6 +228,5 @@
<i class="fi-plus m5r"></i> Add</button>
</div>
</div>
</div>
</div>