{{$root.wallet.getName()}}
diff --git a/js/app.js b/js/app.js
index 177755b2a..e1ddb0b7a 100644
--- a/js/app.js
+++ b/js/app.js
@@ -10,15 +10,16 @@ if (localConfig) {
var lmv = localConfig.version ? localConfig.version.split('.')[1] : '-1';
if (cmv === lmv) {
_.each(localConfig, function(value, key) {
- if (key === 'networkName' && config['forceNetwork']) {
- return;
- }
config[key] = value;
});
}
}
-var copayApp = window.copayApp = angular.module('copayApp', [
+var log = function() {
+ if (config.verbose) console.log(arguments);
+}
+
+var modules = [
'ngRoute',
'angularMoment',
'mm.foundation',
@@ -29,7 +30,13 @@ var copayApp = window.copayApp = angular.module('copayApp', [
'copayApp.services',
'copayApp.controllers',
'copayApp.directives',
-]);
+];
+
+if (Object.keys(config.plugins).length)
+ modules.push('angularLoad');
+
+
+var copayApp = window.copayApp = angular.module('copayApp', modules);
copayApp.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
diff --git a/js/controllers/copayers.js b/js/controllers/copayers.js
index fc140593b..57573b4d0 100644
--- a/js/controllers/copayers.js
+++ b/js/controllers/copayers.js
@@ -5,6 +5,12 @@ angular.module('copayApp.controllers').controller('CopayersController',
$scope.hideAdv = true;
+
+ $scope.skipBackup = function() {
+ var w = $rootScope.wallet;
+ w.setBackupReady(true);
+ };
+
$scope.backup = function() {
var w = $rootScope.wallet;
w.setBackupReady();
diff --git a/js/controllers/create.js b/js/controllers/create.js
index 81308f51d..6631dcce9 100644
--- a/js/controllers/create.js
+++ b/js/controllers/create.js
@@ -41,6 +41,7 @@ angular.module('copayApp.controllers').controller('CreateController',
$scope.walletPassword = $rootScope.walletPassword;
$scope.isMobile = !!window.cordova;
$scope.hideAdv = true;
+ $scope.networkName = config.networkName;
// ng-repeat defined number of times instead of repeating over array?
$scope.getNumber = function(num) {
@@ -83,9 +84,11 @@ angular.module('copayApp.controllers').controller('CreateController',
nickname: $scope.myNickname,
passphrase: passphrase,
privateKeyHex: $scope.private,
+ networkName: $scope.networkName,
};
- var w = walletFactory.create(opts);
- controllerUtils.startNetwork(w, $scope);
+ walletFactory.create(opts, function(err, w) {
+ controllerUtils.startNetwork(w, $scope);
+ });
});
};
diff --git a/js/controllers/home.js b/js/controllers/home.js
index 2ac3e7381..23119ded1 100644
--- a/js/controllers/home.js
+++ b/js/controllers/home.js
@@ -1,10 +1,12 @@
'use strict';
-angular.module('copayApp.controllers').controller('HomeController',
- function($scope, $rootScope, $location, walletFactory, notification, controllerUtils) {
-
- controllerUtils.redirIfLogged();
+angular.module('copayApp.controllers').controller('HomeController', function($scope, $rootScope, $location, walletFactory, notification, controllerUtils) {
- $scope.loading = false;
- $scope.hasWallets = (walletFactory.getWallets() && walletFactory.getWallets().length > 0) ? true : false;
+ controllerUtils.redirIfLogged();
+
+ $scope.retreiving = true;
+ walletFactory.getWallets(function(err,ret) {
+ $scope.retreiving = false;
+ $scope.hasWallets = (ret && ret.length > 0) ? true : false;
});
+});
diff --git a/js/controllers/import.js b/js/controllers/import.js
index e52f77e86..58b3227f9 100644
--- a/js/controllers/import.js
+++ b/js/controllers/import.js
@@ -2,12 +2,11 @@
angular.module('copayApp.controllers').controller('ImportController',
function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, notification) {
-
controllerUtils.redirIfLogged();
$scope.title = 'Import a backup';
$scope.importStatus = 'Importing wallet - Reading backup...';
- $scope.hideAdv=true;
+ $scope.hideAdv = true;
var reader = new FileReader();
@@ -59,7 +58,7 @@ angular.module('copayApp.controllers').controller('ImportController',
$rootScope.wallet = w;
controllerUtils.startNetwork($rootScope.wallet, $scope);
});
-
+
});
};
@@ -98,13 +97,13 @@ angular.module('copayApp.controllers').controller('ImportController',
if (!backupFile) {
$scope.loading = false;
- notification.error('Error', 'Please, select your backup file or paste the file contents');
+ notification.error('Error', 'Please, select your backup file');
$scope.loading = false;
return;
}
if (backupFile) {
reader.readAsBinaryString(backupFile);
- }
+ }
};
});
diff --git a/js/controllers/join.js b/js/controllers/join.js
index d94c24354..25a3e5dca 100644
--- a/js/controllers/join.js
+++ b/js/controllers/join.js
@@ -7,7 +7,7 @@ angular.module('copayApp.controllers').controller('JoinController',
$scope.loading = false;
$scope.isMobile = !!window.cordova;
- // QR code Scanner
+ // QR code Scanner
var cameraInput;
var video;
var canvas;
@@ -15,14 +15,13 @@ angular.module('copayApp.controllers').controller('JoinController',
var context;
var localMediaStream;
- $scope.hideAdv=true;
-
+ $scope.hideAdv = true;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
- if (!window.cordova && !navigator.getUserMedia)
- $scope.disableScanner =1;
+ if (!window.cordova && !navigator.getUserMedia)
+ $scope.disableScanner = 1;
var _scan = function(evt) {
if (localMediaStream) {
@@ -118,10 +117,14 @@ angular.module('copayApp.controllers').controller('JoinController',
}
$scope.loading = true;
- walletFactory.network.on('badSecret', function() {});
Passphrase.getBase64Async($scope.joinPassword, function(passphrase) {
- walletFactory.joinCreateSession($scope.connectionId, $scope.nickname, passphrase, $scope.private, function(err, w) {
+ walletFactory.joinCreateSession({
+ secret: $scope.connectionId,
+ nickname: $scope.nickname,
+ passphrase: passphrase,
+ privateHex: $scope.private,
+ }, function(err, w) {
$scope.loading = false;
if (err || !w) {
if (err === 'joinError')
@@ -129,9 +132,11 @@ angular.module('copayApp.controllers').controller('JoinController',
else if (err === 'walletFull')
notification.error('The wallet is full');
else if (err === 'badNetwork')
- notification.error('Network Error', 'The wallet your are trying to join uses a different Bitcoin Network. Check your settings.');
+ notification.error('Network Error', 'Wallet network configuration missmatch');
else if (err === 'badSecret')
notification.error('Bad secret', 'The secret string you entered is invalid');
+ else if (err === 'connectionError')
+ notification.error('Networking Error', 'Could not connect to the Insight server. Check your settings and network configuration');
else
notification.error('Unknown error');
controllerUtils.onErrorDigest();
diff --git a/js/controllers/more.js b/js/controllers/more.js
index 87c76ebec..1ee3cc397 100644
--- a/js/controllers/more.js
+++ b/js/controllers/more.js
@@ -1,45 +1,98 @@
'use strict';
angular.module('copayApp.controllers').controller('MoreController',
- function($scope, $rootScope, $location, backupService, walletFactory, controllerUtils, notification) {
+ function($scope, $rootScope, $location, $filter, backupService, walletFactory, controllerUtils, notification, rateService) {
var w = $rootScope.wallet;
- $scope.hideAdv=true;
- $scope.hidePriv=true;
+ $scope.unitOpts = [{
+ name: 'Satoshis (100,000,000 satoshis = 1BTC)',
+ shortName: 'SAT',
+ value: 1,
+ decimals: 0
+ }, {
+ name: 'bits (1,000,000 bits = 1BTC)',
+ shortName: 'bits',
+ value: 100,
+ decimals: 2
+ }, {
+ name: 'mBTC (1,000 mBTC = 1BTC)',
+ shortName: 'mBTC',
+ value: 100000,
+ decimals: 5
+ }, {
+ name: 'BTC',
+ shortName: 'BTC',
+ value: 100000000,
+ decimals: 8
+ }];
+
+ $scope.selectedAlternative = {
+ name: w.settings.alternativeName,
+ isoCode: w.settings.alternativeIsoCode
+ };
+ $scope.alternativeOpts = rateService.isAvailable ?
+ rateService.listAlternatives() : [$scope.selectedAlternative];
+
+ rateService.whenAvailable(function() {
+ $scope.alternativeOpts = rateService.listAlternatives();
+ for (var ii in $scope.alternativeOpts) {
+ if (w.settings.alternativeIsoCode === $scope.alternativeOpts[ii].isoCode) {
+ $scope.selectedAlternative = $scope.alternativeOpts[ii];
+ }
+ }
+ });
+
+
+ for (var ii in $scope.unitOpts) {
+ if (w.settings.unitName === $scope.unitOpts[ii].shortName) {
+ $scope.selectedUnit = $scope.unitOpts[ii];
+ break;
+ }
+ }
+ $scope.save = function() {
+ w.changeSettings({
+ unitName: $scope.selectedUnit.shortName,
+ unitToSatoshi: $scope.selectedUnit.value,
+ unitDecimals: $scope.selectedUnit.decimals,
+ alternativeName: $scope.selectedAlternative.name,
+ alternativeIsoCode: $scope.selectedAlternative.isoCode,
+ });
+ controllerUtils.updateBalance();
+ };
+
+
+ $scope.hideAdv = true;
+ $scope.hidePriv = true;
if (w)
$scope.priv = w.privateKey.toObj().extendedPrivateKeyString;
$scope.downloadBackup = function() {
- var w = $rootScope.wallet;
backupService.download(w);
}
$scope.deleteWallet = function() {
- var w = $rootScope.wallet;
walletFactory.delete(w.id, function() {
controllerUtils.logout();
});
};
$scope.purge = function(deleteAll) {
- var w = $rootScope.wallet;
var removed = w.purgeTxProposals(deleteAll);
- if (removed){
+ if (removed) {
controllerUtils.updateBalance();
}
- notification.info('Tx Proposals Purged', removed + ' transaction proposal purged');
+ notification.info('Transactions Proposals Purged', removed + ' ' + $filter('translate')('transaction proposal purged'));
};
$scope.updateIndexes = function() {
- var w = $rootScope.wallet;
- notification.info('Scaning for transactions','Using derived addresses from your wallet');
+ notification.info('Scaning for transactions', 'Using derived addresses from your wallet');
w.updateIndexes(function(err) {
notification.info('Scan Ended', 'Updating balance');
if (err) {
- notification.error('Error', 'Error updating indexes: ' + err);
+ notification.error('Error', $filter('translate')('Error updating indexes: ') + err);
}
controllerUtils.updateAddressList();
- controllerUtils.updateBalance(function(){
+ controllerUtils.updateBalance(function() {
notification.info('Finished', 'The balance is updated using the derived addresses');
w.sendIndexes();
});
diff --git a/js/controllers/open.js b/js/controllers/open.js
index 019932988..a98ff6a5e 100644
--- a/js/controllers/open.js
+++ b/js/controllers/open.js
@@ -14,15 +14,32 @@ angular.module('copayApp.controllers').controller('OpenController', function($sc
};
$rootScope.fromSetup = false;
$scope.loading = false;
- $scope.wallets = walletFactory.getWallets().sort(cmp);
- $scope.selectedWalletId = walletFactory.storage.getLastOpened() || ($scope.wallets[0] && $scope.wallets[0].id);
+ $scope.retreiving = true;
+
+ walletFactory.getWallets(function(err, wallets) {
+
+ if (err || !wallets || !wallets.length) {
+ $location.path('/');
+ } else {
+ $scope.retreiving = false;
+ $scope.wallets = wallets.sort(cmp);
+
+ walletFactory.storage.getLastOpened(function(ret) {
+ if (ret && _.indexOf(_.pluck($scope.wallets, 'id')) == -1)
+ ret = null;
+
+ $scope.selectedWalletId = ret || ($scope.wallets[0] && $scope.wallets[0].id);
+
+ setTimeout(function() {
+ $rootScope.$digest();
+ }, 0);
+ });
+ }
+ });
+
$scope.openPassword = '';
$scope.isMobile = !!window.cordova;
- if (!$scope.wallets.length){
- $location.path('/');
- }
-
$scope.open = function(form) {
if (form && form.$invalid) {
notification.error('Error', 'Please enter the required fields');
@@ -34,19 +51,16 @@ angular.module('copayApp.controllers').controller('OpenController', function($sc
Passphrase.getBase64Async(password, function(passphrase) {
var w, errMsg;
- try {
- w = walletFactory.open($scope.selectedWalletId, passphrase);
- } catch (e) {
- errMsg = e.message;
- };
- if (!w) {
- $scope.loading = false;
- notification.error('Error', errMsg || 'Wrong password');
- $rootScope.$digest();
- return;
- }
- $rootScope.updatingBalance = true;
- controllerUtils.startNetwork(w, $scope);
+ walletFactory.open($scope.selectedWalletId, passphrase, function(err, w) {
+ if (!w) {
+ $scope.loading = false;
+ notification.error('Error', err.errMsg || 'Wrong password');
+ $rootScope.$digest();
+ } else {
+ $rootScope.updatingBalance = true;
+ controllerUtils.startNetwork(w, $scope);
+ }
+ });
});
};
diff --git a/js/controllers/send.js b/js/controllers/send.js
index 788217edf..84c6d7efc 100644
--- a/js/controllers/send.js
+++ b/js/controllers/send.js
@@ -1,17 +1,22 @@
'use strict';
var bitcore = require('bitcore');
+var preconditions = require('preconditions').singleton();
angular.module('copayApp.controllers').controller('SendController',
function($scope, $rootScope, $window, $timeout, $anchorScroll, $modal, isMobile, notification, controllerUtils, rateService) {
+ var w = $rootScope.wallet;
+ preconditions.checkState(w);
+ preconditions.checkState(w.settings.unitToSatoshi);
+
$scope.title = 'Send';
$scope.loading = false;
- var satToUnit = 1 / config.unitToSatoshi;
+ var satToUnit = 1 / w.settings.unitToSatoshi;
$scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit;
- $scope.unitToBtc = config.unitToSatoshi / bitcore.util.COIN;
- $scope.unitToSatoshi = config.unitToSatoshi;
+ $scope.unitToBtc = w.settings.unitToSatoshi / bitcore.util.COIN;
+ $scope.unitToSatoshi = w.settings.unitToSatoshi;
- $scope.alternativeName = config.alternativeName;
- $scope.alternativeIsoCode = config.alternativeIsoCode;
+ $scope.alternativeName = w.settings.alternativeName;
+ $scope.alternativeIsoCode = w.settings.alternativeIsoCode;
$scope.isRateAvailable = false;
$scope.rateService = rateService;
@@ -36,7 +41,7 @@ angular.module('copayApp.controllers').controller('SendController',
this._alternative = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
this._amount = parseFloat(
- (rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit).toFixed(config.unitDecimals), 10);
+ (rateService.fromFiat(newValue, w.settings.alternativeIsoCode) * satToUnit).toFixed(w.settings.unitDecimals), 10);
} else {
this._amount = 0;
}
@@ -53,7 +58,7 @@ angular.module('copayApp.controllers').controller('SendController',
this._amount = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
this._alternative = parseFloat(
- (rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode)).toFixed(2), 10);
+ (rateService.toFiat(newValue * w.settings.unitToSatoshi, w.settings.alternativeIsoCode)).toFixed(2), 10);
} else {
this._alternative = 0;
}
@@ -75,7 +80,6 @@ angular.module('copayApp.controllers').controller('SendController',
}
$scope.showAddressBook = function() {
- var w = $rootScope.wallet;
var flag;
if (w) {
for (var k in w.addressBook) {
@@ -91,7 +95,7 @@ angular.module('copayApp.controllers').controller('SendController',
if ($rootScope.pendingPayment) {
var pp = $rootScope.pendingPayment;
$scope.address = pp.address + '';
- var amount = pp.data.amount / config.unitToSatoshi * 100000000;
+ var amount = pp.data.amount / w.settings.unitToSatoshi * 100000000;
$scope.amount = amount;
$scope.commentText = pp.data.message;
}
@@ -105,7 +109,7 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.submitForm = function(form) {
if (form.$invalid) {
- var message = 'Unable to send transaction proposal.';
+ var message = 'Unable to send transaction proposal';
notification.error('Error', message);
return;
}
@@ -113,11 +117,9 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.loading = true;
var address = form.address.$modelValue;
- var amount = parseInt((form.amount.$modelValue * config.unitToSatoshi).toFixed(0));
+ var amount = parseInt((form.amount.$modelValue * w.settings.unitToSatoshi).toFixed(0));
var commentText = form.comment.$modelValue;
- var w = $rootScope.wallet;
-
function done(err, ntxid, merchantData) {
if (err) {
var message = 'The transaction' + (w.isShared() ? ' proposal' : '') + ' could not be created';
@@ -150,7 +152,7 @@ angular.module('copayApp.controllers').controller('SendController',
message += ' Message from server: ' + merchantData.ack.memo;
message += ' For merchant: ' + merchantData.pr.pd.payment_url;
}
- notification.success('Success!', message);
+ notification.success('Success', message);
$scope.loadTxs();
} else {
w.sendTx(ntxid, function(txid, merchantData) {
@@ -163,9 +165,9 @@ angular.module('copayApp.controllers').controller('SendController',
message += ' Message from server: ' + merchantData.ack.memo;
message += ' For merchant: ' + merchantData.pr.pd.payment_url;
}
- notification.success('Transaction broadcast', message);
+ notification.success('Transaction broadcasted', message);
} else {
- notification.error('Error', 'There was an error sending the transaction.');
+ notification.error('Error', 'There was an error sending the transaction');
}
$scope.loading = false;
$scope.loadTxs();
@@ -344,7 +346,6 @@ angular.module('copayApp.controllers').controller('SendController',
}
$scope.toggleAddressBookEntry = function(key) {
- var w = $rootScope.wallet;
w.toggleAddressBookEntry(key);
};
@@ -379,7 +380,6 @@ angular.module('copayApp.controllers').controller('SendController',
});
modalInstance.result.then(function(entry) {
- var w = $rootScope.wallet;
$timeout(function() {
$scope.loading = false;
@@ -403,7 +403,7 @@ angular.module('copayApp.controllers').controller('SendController',
};
$scope.getAvailableAmount = function() {
- var amount = ((($rootScope.availableBalance * config.unitToSatoshi).toFixed(0) - bitcore.TransactionBuilder.FEE_PER_1000B_SAT) / config.unitToSatoshi);
+ var amount = ((($rootScope.availableBalance * w.settings.unitToSatoshi).toFixed(0) - bitcore.TransactionBuilder.FEE_PER_1000B_SAT) / w.settings.unitToSatoshi);
return amount > 0 ? amount : 0;
};
@@ -416,13 +416,12 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.send = function(ntxid, cb) {
$scope.loading = true;
$rootScope.txAlertCount = 0;
- var w = $rootScope.wallet;
w.sendTx(ntxid, function(txid, merchantData) {
if (!txid) {
notification.error('Error', 'There was an error sending the transaction');
} else {
if (!merchantData) {
- notification.success('Transaction broadcast', 'Transaction id: ' + txid);
+ notification.success('Transaction broadcasted', 'Transaction id: ' + txid);
} else {
var message = 'Transaction ID: ' + txid;
if (merchantData.pr.ca) {
@@ -441,7 +440,6 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.sign = function(ntxid) {
$scope.loading = true;
- var w = $rootScope.wallet;
w.sign(ntxid, function(ret) {
if (!ret) {
notification.error('Error', 'There was an error signing the transaction');
@@ -461,13 +459,46 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.reject = function(ntxid) {
$scope.loading = true;
$rootScope.txAlertCount = 0;
- var w = $rootScope.wallet;
w.reject(ntxid);
notification.warning('Transaction rejected', 'You rejected the transaction successfully');
$scope.loading = false;
$scope.loadTxs();
};
+ $scope.clearMerchant = function(callback) {
+ var scope = $scope;
+ // TODO: Find a better way of detecting
+ // whether we're in the Send scope or not.
+ if (!scope.sendForm || !scope.sendForm.address) {
+ delete $rootScope.merchant;
+ if (callback) callback();
+ return;
+ }
+ var val = scope.sendForm.address.$viewValue || '';
+ var uri;
+ // If we're setting the domain, ignore the change.
+ if ($rootScope.merchant && $rootScope.merchant.domain && val === $rootScope.merchant.domain) {
+ uri = {
+ merchant: $rootScope.merchant.request_url
+ };
+ }
+ if (val.indexOf('bitcoin:') === 0) {
+ uri = new bitcore.BIP21(val).data;
+ } else if (/^https?:\/\//.test(val)) {
+ uri = {
+ merchant: val
+ };
+ }
+ if (!uri || !uri.merchant) {
+ delete $rootScope.merchant;
+ scope.sendForm.amount.$setViewValue('');
+ scope.sendForm.amount.$render();
+ if (callback) callback();
+ if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') {
+ $rootScope.$apply();
+ }
+ }
+ };
$scope.onChanged = function() {
var scope = $scope;
@@ -497,7 +528,7 @@ angular.module('copayApp.controllers').controller('SendController',
// Payment Protocol URI (BIP-72)
scope.wallet.fetchPaymentTx(uri.merchant, function(err, merchantData) {
var balance = $rootScope.availableBalance;
- var available = +(balance * config.unitToSatoshi).toFixed(0);
+ var available = +(balance * w.settings.unitToSatoshi).toFixed(0);
if (merchantData && available < +merchantData.total) {
err = new Error('No unspent outputs available.');
@@ -508,7 +539,7 @@ angular.module('copayApp.controllers').controller('SendController',
scope.sendForm.address.$isValid = false;
if (err.amount) {
- scope.sendForm.amount.$setViewValue(+err.amount / config.unitToSatoshi);
+ scope.sendForm.amount.$setViewValue(+err.amount / w.settings.unitToSatoshi);
scope.sendForm.amount.$render();
scope.sendForm.amount.$isValid = false;
scope.notEnoughAmount = true;
@@ -538,7 +569,7 @@ angular.module('copayApp.controllers').controller('SendController',
var url = merchantData.request_url;
var domain = /^(?:https?)?:\/\/([^\/:]+).*$/.exec(url)[1];
- merchantData.unitTotal = (+merchantData.total / config.unitToSatoshi) + '';
+ merchantData.unitTotal = (+merchantData.total / w.settings.unitToSatoshi) + '';
merchantData.expiration = new Date(
merchantData.pr.pd.expires * 1000).toISOString();
merchantData.domain = domain;
@@ -555,31 +586,8 @@ angular.module('copayApp.controllers').controller('SendController',
// If the address changes to a non-payment-protocol one,
// delete the `merchant` property from the scope.
- var unregister = scope.$watch('address', function() {
- var val = scope.sendForm.address.$viewValue || '';
- var uri;
- // If we're setting the domain, ignore the change.
- if ($rootScope.merchant && $rootScope.merchant.domain && val === $rootScope.merchant.domain) {
- uri = {
- merchant: $rootScope.merchant.request_url
- };
- }
- if (val.indexOf('bitcoin:') === 0) {
- uri = new bitcore.BIP21(val).data;
- } else if (/^https?:\/\//.test(val)) {
- uri = {
- merchant: val
- };
- }
- if (!uri || !uri.merchant) {
- delete $rootScope.merchant;
- scope.sendForm.amount.$setViewValue('');
- scope.sendForm.amount.$render();
- unregister();
- if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') {
- $rootScope.$apply();
- }
- }
+ var unregister = $rootScope.$watch(function() {
+ $scope.clearMerchant(unregister);
});
if ($rootScope.$$phase !== '$apply' && $rootScope.$$phase !== '$digest') {
@@ -587,8 +595,10 @@ angular.module('copayApp.controllers').controller('SendController',
}
notification.info('Payment Request',
- 'Server is requesting ' + merchantData.unitTotal + ' ' + config.unitName + '.' + ' Message: ' + merchantData.pr.pd.memo);
+ 'Server is requesting ' + merchantData.unitTotal +
+ ' ' + w.settings.unitName +
+ '.' + ' Message: ' + merchantData.pr.pd.memo);
});
};
- });
\ No newline at end of file
+ });
diff --git a/js/controllers/settings.js b/js/controllers/settings.js
index 4e3020666..135b5ae34 100644
--- a/js/controllers/settings.js
+++ b/js/controllers/settings.js
@@ -1,15 +1,12 @@
'use strict';
-angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $location, controllerUtils, rateService) {
+angular.module('copayApp.controllers').controller('SettingsController', function($scope, $rootScope, $window, $location, controllerUtils) {
controllerUtils.redirIfLogged();
$scope.title = 'Settings';
- $scope.networkName = config.networkName;
- $scope.insightHost = config.blockchain.host;
- $scope.insightPort = config.blockchain.port;
- $scope.insightSecure = config.blockchain.schema === 'https';
- $scope.forceNetwork = config.forceNetwork;
$scope.defaultLanguage = config.defaultLanguage || 'en';
+ $scope.insightLivenet = config.network.livenet.url;
+ $scope.insightTestnet = config.network.testnet.url;
$scope.availableLanguages = [{
name: 'English',
@@ -26,86 +23,18 @@ angular.module('copayApp.controllers').controller('SettingsController', function
}
}
- $scope.unitOpts = [{
- name: 'Satoshis (100,000,000 satoshis = 1BTC)',
- shortName: 'SAT',
- value: 1,
- decimals: 0
- }, {
- name: 'bits (1,000,000 bits = 1BTC)',
- shortName: 'bits',
- value: 100,
- decimals: 2
- }, {
- name: 'mBTC (1,000 mBTC = 1BTC)',
- shortName: 'mBTC',
- value: 100000,
- decimals: 5
- }, {
- name: 'BTC',
- shortName: 'BTC',
- value: 100000000,
- decimals: 8
- }];
-
- $scope.selectedAlternative = {
- name: config.alternativeName,
- isoCode: config.alternativeIsoCode
- };
- $scope.alternativeOpts = rateService.isAvailable ?
- rateService.listAlternatives() : [$scope.selectedAlternative];
-
- rateService.whenAvailable(function() {
- $scope.alternativeOpts = rateService.listAlternatives();
- for (var ii in $scope.alternativeOpts) {
- if (config.alternativeIsoCode === $scope.alternativeOpts[ii].isoCode) {
- $scope.selectedAlternative = $scope.alternativeOpts[ii];
- }
- }
- });
-
- for (var ii in $scope.unitOpts) {
- if (config.unitName === $scope.unitOpts[ii].shortName) {
- $scope.selectedUnit = $scope.unitOpts[ii];
- break;
- }
- }
-
- $scope.changeNetwork = function() {
- $scope.insightHost = $scope.networkName !== 'testnet' ? 'test-insight.bitpay.com' : 'insight.bitpay.com';
- };
-
-
- $scope.changeInsightSSL = function() {
- $scope.insightPort = $scope.insightSecure ? 80 : 443;
- };
-
-
$scope.save = function() {
- var network = config.network;
- network.host = $scope.insightHost;
- network.port = $scope.insightPort;
- network.schema = $scope.insightSecure ? 'https' : 'http';
+ var insightSettings = {
+ livenet: {
+ url: $scope.insightLivenet,
+ },
+ testnet: {
+ url: $scope.insightTestnet,
+ },
+ }
localStorage.setItem('config', JSON.stringify({
- networkName: $scope.networkName,
- blockchain: {
- host: $scope.insightHost,
- port: $scope.insightPort,
- schema: $scope.insightSecure ? 'https' : 'http',
- },
- socket: {
- host: $scope.insightHost,
- port: $scope.insightPort,
- schema: $scope.insightSecure ? 'https' : 'http',
- },
- network: network,
- unitName: $scope.selectedUnit.shortName,
- unitToSatoshi: $scope.selectedUnit.value,
- unitDecimals: $scope.selectedUnit.decimals,
- alternativeName: $scope.selectedAlternative.name,
- alternativeIsoCode: $scope.selectedAlternative.isoCode,
-
+ network: insightSettings,
version: copay.version,
defaultLanguage: $scope.selectedLanguage.isoCode
}));
diff --git a/js/controllers/sidebar.js b/js/controllers/sidebar.js
index 54fc3df8c..6cccc126f 100644
--- a/js/controllers/sidebar.js
+++ b/js/controllers/sidebar.js
@@ -1,6 +1,6 @@
'use strict';
-angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $sce, $location, $http, notification, controllerUtils) {
+angular.module('copayApp.controllers').controller('SidebarController', function($scope, $rootScope, $sce, $location, $http, $filter, notification, controllerUtils) {
$scope.menu = [{
'title': 'Receive',
@@ -60,7 +60,7 @@ angular.module('copayApp.controllers').controller('SidebarController', function(
if ($rootScope.wallet) {
$scope.$on('$idleWarn', function(a,countdown) {
if (!(countdown%5))
- notification.warning('Session will be closed', 'Your session is about to expire due to inactivity in ' + countdown + ' seconds');
+ notification.warning('Session will be closed', $filter('translate')('Your session is about to expire due to inactivity in') + ' ' + countdown + ' ' + $filter('translate')('seconds'));
});
$scope.$on('$idleTimeout', function() {
diff --git a/js/controllers/transactions.js b/js/controllers/transactions.js
index 7f15c02df..7b51f0135 100644
--- a/js/controllers/transactions.js
+++ b/js/controllers/transactions.js
@@ -4,6 +4,8 @@ var bitcore = require('bitcore');
angular.module('copayApp.controllers').controller('TransactionsController',
function($scope, $rootScope, $timeout, controllerUtils, notification) {
+ var w = $rootScope.wallet;
+
$scope.title = 'Transactions';
$scope.loading = false;
$scope.lastShowed = false;
@@ -12,7 +14,7 @@ angular.module('copayApp.controllers').controller('TransactionsController',
$scope.txpItemsPerPage = 4;
$scope.blockchain_txs = [];
- var satToUnit = 1 / config.unitToSatoshi;
+ var satToUnit = 1 / w.settings.unitToSatoshi;
$scope.update = function() {
$scope.loading = true;
@@ -139,7 +141,7 @@ angular.module('copayApp.controllers').controller('TransactionsController',
}
$scope.getShortNetworkName = function() {
- return config.networkName.substring(0, 4);
+ return w.getNetworkName().substring(0, 4);
};
// Autoload transactions on 1-of-1
diff --git a/js/controllers/version.js b/js/controllers/version.js
index f61e7af4b..8c0cbc389 100644
--- a/js/controllers/version.js
+++ b/js/controllers/version.js
@@ -1,12 +1,13 @@
'use strict';
angular.module('copayApp.controllers').controller('VersionController',
- function($scope, $rootScope, $http, notification) {
+ function($scope, $rootScope, $http, $filter, notification) {
+
+ var w = $rootScope.wallet;
$scope.version = copay.version;
$scope.commitHash = copay.commitHash;
- $scope.networkName = config.networkName;
- $scope.defaultLanguage = config.defaultLanguage;
+ $scope.networkName = w ? w.getNetworkName() : '';
if (_.isUndefined($rootScope.checkVersion))
$rootScope.checkVersion = true;
@@ -18,7 +19,7 @@ angular.module('copayApp.controllers').controller('VersionController',
};
var latestVersion = data[0].name.replace('v', '').split('.').map(toInt);
var currentVersion = copay.version.split('.').map(toInt);
- var title = 'Copay ' + data[0].name + ' available.';
+ var title = 'Copay ' + data[0].name + ' ' + $filter('translate')('available.');
var content;
if (currentVersion[0] < latestVersion[0]) {
content = 'It\'s important that you update your wallet at https://copay.io';
@@ -30,4 +31,4 @@ angular.module('copayApp.controllers').controller('VersionController',
});
}
- });
\ No newline at end of file
+ });
diff --git a/js/directives.js b/js/directives.js
index e8024cab5..b4b1a84a3 100644
--- a/js/directives.js
+++ b/js/directives.js
@@ -1,20 +1,21 @@
'use strict';
-angular.module('copayApp.directives')
- .directive('validAddress', ['$rootScope', function($rootScope) {
- var bitcore = require('bitcore');
- var Address = bitcore.Address;
- var bignum = bitcore.Bignum;
+var bitcore = require('bitcore');
+var Address = bitcore.Address;
+var bignum = bitcore.Bignum;
+var preconditions = require('preconditions').singleton();
+angular.module('copayApp.directives')
+
+.directive('validAddress', ['$rootScope',
+ function($rootScope) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var validator = function(value) {
// If we're setting the domain, ignore the change.
- if ($rootScope.merchant
- && $rootScope.merchant.domain
- && value === $rootScope.merchant.domain) {
+ if ($rootScope.merchant && $rootScope.merchant.domain && value === $rootScope.merchant.domain) {
ctrl.$setValidity('validAddress', true);
return value;
}
@@ -25,35 +26,41 @@ angular.module('copayApp.directives')
return value;
}
+
// Bip21 uri
if (/^bitcoin:/.test(value)) {
var uri = new bitcore.BIP21(value);
- var hasAddress = uri.address && uri.isValid() && uri.address.network().name === config.networkName;
+ var hasAddress = uri.address && uri.isValid() && uri.address.network().name === $rootScope.wallet.getNetworkName();
ctrl.$setValidity('validAddress', uri.data.merchant || hasAddress);
return value;
}
// Regular Address
var a = new Address(value);
- ctrl.$setValidity('validAddress', a.isValid() && a.network().name === config.networkName);
+ ctrl.$setValidity('validAddress', a.isValid() && a.network().name === $rootScope.wallet.getNetworkName());
return value;
};
+
ctrl.$parsers.unshift(validator);
ctrl.$formatters.unshift(validator);
}
};
- }])
+ }
+])
.directive('enoughAmount', ['$rootScope',
function($rootScope) {
- var bitcore = require('bitcore');
+ var w = $rootScope.wallet;
+ preconditions.checkState(w);
+ preconditions.checkState(w.settings.unitToSatoshi);
+
var feeSat = Number(bitcore.TransactionBuilder.FEE_PER_1000B_SAT);
return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var val = function(value) {
- var availableBalanceNum = Number(($rootScope.availableBalance * config.unitToSatoshi).toFixed(0));
- var vNum = Number((value * config.unitToSatoshi).toFixed(0));
+ var availableBalanceNum = Number(($rootScope.availableBalance * w.settings.unitToSatoshi).toFixed(0));
+ var vNum = Number((value * w.settings.unitToSatoshi).toFixed(0));
if (typeof vNum == "number" && vNum > 0) {
vNum = vNum + feeSat;
@@ -81,7 +88,8 @@ angular.module('copayApp.directives')
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var validator = function(value) {
- ctrl.$setValidity('walletSecret', Boolean(walletFactory.decodeSecret(value)));
+ var a = new Address(value);
+ ctrl.$setValidity('walletSecret', !a.isValid() && Boolean(walletFactory.decodeSecret(value)));
return value;
};
@@ -270,7 +278,7 @@ angular.module('copayApp.directives')
client.on('datarequested', function(client) {
client.setText(scope.clipCopy);
- } );
+ });
client.on('complete', function(client, args) {
elm.removeClass('btn-copy').addClass('btn-copied').html('Copied!');
diff --git a/js/filters.js b/js/filters.js
index a52237d1f..9cd467bbf 100644
--- a/js/filters.js
+++ b/js/filters.js
@@ -44,43 +44,44 @@ angular.module('copayApp.filters', [])
return addrs;
};
})
- .filter('noFractionNumber',
- [ '$filter', '$locale',
- function(filter, locale) {
- var numberFilter = filter('number');
- var formats = locale.NUMBER_FORMATS;
- return function(amount, n) {
- var fractionSize = (typeof(n) != 'undefined') ? n : config.unitToSatoshi.toString().length - 1;
- var value = numberFilter(amount, fractionSize);
- var sep = value.indexOf(formats.DECIMAL_SEP);
- var group = value.indexOf(formats.GROUP_SEP);
- if(amount >= 0) {
- if (group > 0) {
- if (sep < 0) {
+ .filter('noFractionNumber', ['$filter', '$locale', '$rootScope',
+ function(filter, locale, $rootScope) {
+ var numberFilter = filter('number');
+ var formats = locale.NUMBER_FORMATS;
+ return function(amount, n) {
+ if (typeof(n) === 'undefined' && !$rootScope.wallet) return amount;
+
+ var fractionSize = (typeof(n) !== 'undefined') ?
+ n : $rootScope.wallet.settings.unitToSatoshi.toString().length - 1;
+ var value = numberFilter(amount, fractionSize);
+ var sep = value.indexOf(formats.DECIMAL_SEP);
+ var group = value.indexOf(formats.GROUP_SEP);
+ if (amount >= 0) {
+ if (group > 0) {
+ if (sep < 0) {
+ return value;
+ }
+ var intValue = value.substring(0, sep);
+ var floatValue = parseFloat(value.substring(sep));
+ if (floatValue === 0) {
+ floatValue = '';
+ } else {
+ if (floatValue % 1 === 0) {
+ floatValue = floatValue.toFixed(0);
+ }
+ floatValue = floatValue.toString().substring(1);
+ }
+ var finalValue = intValue + floatValue;
+ return finalValue;
+ } else {
+ value = parseFloat(value);
+ if (value % 1 === 0) {
+ value = value.toFixed(0);
+ }
return value;
}
- var intValue = value.substring(0, sep);
- var floatValue = parseFloat(value.substring(sep));
- if (floatValue === 0) {
- floatValue = '';
- }
- else {
- if(floatValue % 1 === 0) {
- floatValue = floatValue.toFixed(0);
- }
- floatValue = floatValue.toString().substring(1);
- }
- var finalValue = intValue + floatValue;
- return finalValue;
}
- else {
- value = parseFloat(value);
- if(value % 1 === 0) {
- value = value.toFixed(0);
- }
- return value;
- }
- }
- return 0;
- };
- } ]);
+ return 0;
+ };
+ }
+ ]);
diff --git a/js/log.js b/js/log.js
index 8a011a665..6dc4305dd 100644
--- a/js/log.js
+++ b/js/log.js
@@ -101,5 +101,4 @@ Logger.prototype.setLevel = function(level) {
var logger = new Logger('copay');
logger.setLevel(config.logLevel);
-logger.log('Log level:' + config.logLevel);
module.exports = logger;
diff --git a/js/models/Storage.js b/js/models/Storage.js
new file mode 100644
index 000000000..35cf1f976
--- /dev/null
+++ b/js/models/Storage.js
@@ -0,0 +1,320 @@
+'use strict';
+var preconditions = require('preconditions').singleton();
+var CryptoJS = require('node-cryptojs-aes').CryptoJS;
+var bitcore = require('bitcore');
+var preconditions = require('preconditions').instance();
+var _ = require('underscore');
+var CACHE_DURATION = 1000 * 60 * 5;
+var id = 0;
+
+function Storage(opts) {
+ opts = opts || {};
+
+ this.wListCache = {};
+ this.__uniqueid = ++id;
+ if (opts.password)
+ this.setPassphrase(opts.password);
+
+ try {
+ this.storage = opts.storage || localStorage;
+ this.sessionStorage = opts.sessionStorage || sessionStorage;
+ } catch (e) {
+ console.log('Error in storage:', e); //TODO
+ };
+
+ preconditions.checkState(this.storage, 'No storage defined');
+ preconditions.checkState(this.sessionStorage, 'No sessionStorage defined');
+}
+
+var pps = {};
+Storage.prototype._getPassphrase = function() {
+
+ if (!pps[this.__uniqueid])
+ throw new Error('NOPASSPHRASE: No passphrase set');
+
+ return pps[this.__uniqueid];
+}
+
+Storage.prototype.setPassphrase = function(password) {
+ pps[this.__uniqueid] = password;
+}
+
+Storage.prototype._encrypt = function(string) {
+ var encrypted = CryptoJS.AES.encrypt(string, this._getPassphrase());
+ var encryptedBase64 = encrypted.toString();
+ return encryptedBase64;
+};
+
+Storage.prototype._decrypt = function(base64) {
+ var decryptedStr = null;
+ try {
+ var decrypted = CryptoJS.AES.decrypt(base64, this._getPassphrase());
+ if (decrypted)
+ decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
+ } catch (e) {
+ // Error while decrypting
+ return null;
+ }
+ return decryptedStr;
+};
+
+
+Storage.prototype._read = function(k, cb) {
+ preconditions.checkArgument(cb);
+
+ var self = this;
+ this.storage.getItem(k, function(ret) {
+ if (!ret) return cb(null);
+ var ret = self._decrypt(ret);
+ if (!ret) return cb(null);
+
+ ret = ret.toString(CryptoJS.enc.Utf8);
+ ret = JSON.parse(ret);
+ return cb(ret);
+ });
+};
+
+Storage.prototype._write = function(k, v, cb) {
+ preconditions.checkArgument(cb);
+
+ v = JSON.stringify(v);
+ v = this._encrypt(v);
+ this.storage.setItem(k, v, cb);
+};
+
+// get value by key
+Storage.prototype.getGlobal = function(k, cb) {
+ preconditions.checkArgument(cb);
+
+ this.storage.getItem(k, function(item) {
+ cb(item == 'undefined' ? undefined : item);
+ });
+};
+
+// set value for key
+Storage.prototype.setGlobal = function(k, v, cb) {
+ preconditions.checkArgument(cb);
+
+ this.storage.setItem(k, typeof v === 'object' ? JSON.stringify(v) : v, cb);
+};
+
+// remove value for key
+Storage.prototype.removeGlobal = function(k, cb) {
+ preconditions.checkArgument(cb);
+ this.storage.removeItem(k, cb);
+};
+
+Storage.prototype.getSessionId = function(cb) {
+ preconditions.checkArgument(cb);
+ var self = this;
+
+ self.sessionStorage.getItem('sessionId', function(sessionId) {
+ if (sessionId)
+ return cb(sessionId);
+
+ sessionId = bitcore.SecureRandom.getRandomBuffer(8).toString('hex');
+ self.sessionStorage.setItem('sessionId', sessionId, function() {
+ return cb(sessionId);
+ });
+ });
+};
+
+Storage.prototype.setSessionId = function(sessionId, cb) {
+ this.sessionStorage.setItem('sessionId', sessionId, cb);
+};
+
+Storage.prototype._key = function(walletId, k) {
+ return walletId + '::' + k;
+};
+// get value by key
+Storage.prototype.get = function(walletId, k, cb) {
+ preconditions.checkArgument(walletId, k, cb);
+ this._read(this._key(walletId, k), cb);
+};
+
+
+Storage.prototype._readHelper = function(walletId, k, cb) {
+ var wk = this._key(walletId, k);
+ this._read(wk, function(v) {
+ return cb(v, k);
+ });
+};
+
+Storage.prototype.getMany = function(walletId, keys, cb) {
+ preconditions.checkArgument(cb);
+
+ var self = this;
+ var ret = {};
+
+ var l = keys.length,
+ i = 0;
+
+ for (var ii in keys) {
+ this._readHelper(walletId, keys[ii], function(v, k) {
+ ret[k] = v;
+ if (++i == l) {
+ return cb(ret);
+ }
+ });
+ }
+};
+
+// set value for key
+Storage.prototype.set = function(walletId, k, v, cb) {
+ preconditions.checkArgument(walletId && k && cb);
+
+ if (_.isUndefined(v)) return cb();
+
+ this._write(this._key(walletId, k), v, cb);
+};
+
+// remove value for key
+Storage.prototype.remove = function(walletId, k, cb) {
+ preconditions.checkArgument(walletId && k && cb);
+ this.removeGlobal(this._key(walletId, k), cb);
+};
+
+Storage.prototype.setName = function(walletId, name, cb) {
+ preconditions.checkArgument(walletId && name && cb);
+ this.setGlobal('nameFor::' + walletId, name, cb);
+};
+
+Storage.prototype.getName = function(walletId, cb) {
+ preconditions.checkArgument(walletId && cb);
+ this.getGlobal('nameFor::' + walletId, cb);
+};
+
+Storage.prototype.getWalletIds = function(cb) {
+ preconditions.checkArgument(cb);
+ var walletIds = [];
+ var uniq = {};
+ this.storage.allKeys(function(keys) {
+ for (var ii in keys) {
+ var key = keys[ii];
+ var split = key.split('::');
+ if (split.length == 2) {
+ var walletId = split[0];
+
+ if (!walletId || walletId === 'nameFor' || walletId === 'lock')
+ continue;
+
+ if (typeof uniq[walletId] === 'undefined') {
+ walletIds.push(walletId);
+ uniq[walletId] = 1;
+ }
+ }
+ }
+ return cb(walletIds);
+ });
+};
+
+Storage.prototype.getWallets = function(cb) {
+ preconditions.checkArgument(cb);
+
+ if (this.wListCache.ts > Date.now())
+ return cb(this.wListCache.data)
+
+ var wallets = [];
+ var self = this;
+
+ this.getWalletIds(function(ids) {
+ var l = ids.length,
+ i = 0;
+ if (!l)
+ return cb([]);
+
+ _.each(ids, function(id) {
+ self.getName(id, function(name) {
+ wallets.push({
+ id: id,
+ name: name,
+ });
+ if (++i == l) {
+ self.wListCache.data = wallets;
+ self.wListCache.ts = Date.now() + CACHE_DURATION;
+ return cb(wallets);
+ }
+ });
+ });
+ });
+};
+
+Storage.prototype.deleteWallet = function(walletId, cb) {
+ preconditions.checkArgument(walletId);
+ preconditions.checkArgument(cb);
+ var err;
+ var self = this;
+
+ var toDelete = {};
+
+ this.storage.allKeys(function(allKeys) {
+ for (var ii in allKeys) {
+ var key = allKeys[ii];
+ var split = key.split('::');
+ if (split.length == 2 && split[0] === walletId) {
+ toDelete[key] = 1;
+ };
+ }
+ var l = Object.keys(toDelete).length,
+ j = 0;
+ if (!l)
+ return cb(new Error('WNOTFOUND: Wallet not found'));
+
+ toDelete['nameFor::' + walletId] = 1;
+ l++;
+
+ for (var i in toDelete) {
+ self.removeGlobal(i, function() {
+ if (++j == l)
+ return cb(err);
+ });
+
+ }
+ });
+
+
+};
+
+Storage.prototype.setLastOpened = function(walletId, cb) {
+ this.setGlobal('lastOpened', walletId, cb);
+}
+
+Storage.prototype.getLastOpened = function(cb) {
+ this.getGlobal('lastOpened', cb);
+}
+
+//obj contains keys to be set
+Storage.prototype.setFromObj = function(walletId, obj, cb) {
+ preconditions.checkArgument(cb);
+ var self = this;
+
+ var l = Object.keys(obj).length,
+ i = 0;
+ for (var k in obj) {
+ self.set(walletId, k, obj[k], function() {
+ if (++i == l) {
+ if (obj.opts.name)
+ self.setName(walletId, obj.opts.name, cb);
+ else
+ return cb();
+ }
+ });
+ }
+};
+
+// remove all values
+Storage.prototype.clearAll = function(cb) {
+ this.storage.clear(cb);
+};
+
+Storage.prototype.import = function(base64) {
+ var decryptedStr = this._decrypt(base64);
+ return JSON.parse(decryptedStr);
+};
+
+Storage.prototype.export = function(obj) {
+ var string = JSON.stringify(obj);
+ return this._encrypt(string);
+};
+
+module.exports = Storage;
diff --git a/js/models/blockchain/Insight.js b/js/models/blockchain/Insight.js
index 00a4dddba..82d29e81d 100644
--- a/js/models/blockchain/Insight.js
+++ b/js/models/blockchain/Insight.js
@@ -15,9 +15,7 @@ var preconditions = require('preconditions').singleton();
subscribing to transactions on adressess and blocks.
Opts:
- - host
- - port
- - schema
+ - url
- reconnection (optional)
- reconnectionDelay (optional)
@@ -29,22 +27,22 @@ var preconditions = require('preconditions').singleton();
*/
var Insight = function(opts) {
+ preconditions.checkArgument(opts)
+ .shouldBeObject(opts)
+ .checkArgument(opts.url)
+
this.status = this.STATUS.DISCONNECTED;
this.subscribed = {};
this.listeningBlocks = false;
- preconditions.checkArgument(opts).shouldBeObject(opts)
- .checkArgument(opts.host)
- .checkArgument(opts.port)
- .checkArgument(opts.schema);
-
- this.url = opts.schema + '://' + opts.host + ':' + opts.port;
+ this.url = opts.url;
this.opts = {
'reconnection': opts.reconnection || true,
'reconnectionDelay': opts.reconnectionDelay || 1000,
- 'secure': opts.schema === 'https'
+ 'secure': opts.url.indexOf('https') === 0
};
+ this.socket = this.getSocket();
}
util.inherits(Insight, EventEmitter);
@@ -105,7 +103,7 @@ Insight.prototype._setMainHandlers = function(url, opts) {
/** @private */
-Insight.prototype.getSocket = function(url, opts) {
+Insight.prototype.getSocket = function() {
if (!this.socket) {
this.socket = this._getSocketIO(this.url, this.opts);
@@ -148,7 +146,6 @@ Insight.prototype.subscribe = function(addresses) {
return function(txid) {
// verify the address is still subscribed
if (!self.subscribed[address]) return;
-
log.debug('insight tx event');
self.emit('tx', {
diff --git a/js/models/core/HDPath.js b/js/models/core/HDPath.js
index 3960284bf..041912742 100644
--- a/js/models/core/HDPath.js
+++ b/js/models/core/HDPath.js
@@ -7,10 +7,9 @@ var _ = require('underscore');
/**
* @namespace
- *
+ * @desc
* HDPath contains helper functions to handle BIP32 branches as
* Copay uses them.
- *
* Based on https://github.com/maraoz/bips/blob/master/bip-NNNN.mediawiki
*
* m / purpose' / copayerIndex / change:boolean / addressIndex
diff --git a/js/models/core/PluginManager.js b/js/models/core/PluginManager.js
new file mode 100644
index 000000000..c7574e794
--- /dev/null
+++ b/js/models/core/PluginManager.js
@@ -0,0 +1,52 @@
+'use strict';
+var preconditions = require('preconditions').singleton();
+var log = require('../../log');
+
+function PluginManager(config) {
+ this.registered = {};
+ this.scripts = [];
+
+ for (var ii in config.plugins) {
+ var pluginName = ii;
+
+ if (!config.plugins[pluginName])
+ continue;
+
+ log.info('Loading plugin: ' + pluginName);
+ var pluginClass = require('../plugins/' + pluginName);
+ var pluginObj = new pluginClass(config[pluginName]);
+ pluginObj.init();
+ this._register(pluginObj, pluginName);
+ }
+};
+
+var KIND_UNIQUE = PluginManager.KIND_UNIQUE = 1;
+var KIND_MULTIPLE = PluginManager.KIND_MULTIPLE = 2;
+
+PluginManager.TYPE = {};
+PluginManager.TYPE['STORAGE'] = KIND_UNIQUE;
+
+PluginManager.prototype._register = function(obj, name) {
+ preconditions.checkArgument(obj.type, 'Plugin has not type:' + name);
+ var type = obj.type;
+ var kind = PluginManager.TYPE[type];
+
+ preconditions.checkArgument(kind, 'Plugin has unknown type' + name);
+ preconditions.checkState(kind !== PluginManager.KIND_UNIQUE || !this.registered[type], 'Plugin kind already registered: ' + name);
+
+ if (kind === PluginManager.KIND_UNIQUE) {
+ this.registered[type] = obj;
+ } else {
+ this.registered[type] = this.registered[type] || [];
+ this.registered[type].push(obj);
+ }
+
+ this.scripts = this.scripts.concat(obj.scripts || []);
+};
+
+
+PluginManager.prototype.get = function(type) {
+ return this.registered[type];
+};
+
+module.exports = PluginManager;
diff --git a/js/models/core/PrivateKey.js b/js/models/core/PrivateKey.js
index 8df3325c7..e8dcd8737 100644
--- a/js/models/core/PrivateKey.js
+++ b/js/models/core/PrivateKey.js
@@ -21,6 +21,7 @@ var HDPath = require('./HDPath');
* @param {string} opts.extendedPrivateKeyString if set, use this private key
* string, othewise create a new
* private key
+ * @constructor
*/
function PrivateKey(opts) {
opts = opts || {};
diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js
index 0c22694aa..54fca0faf 100644
--- a/js/models/core/PublicKeyRing.js
+++ b/js/models/core/PublicKeyRing.js
@@ -12,7 +12,7 @@ var HDPath = require('./HDPath');
var HDParams = require('./HDParams');
/**
- * @desc
+ * @desc Represents a public key ring, the set of all public keys and the used indexes
*
* @constructor
* @param {Object} opts
@@ -20,10 +20,10 @@ var HDParams = require('./HDParams');
* @param {string} opts.network 'livenet' to signal the bitcoin main network, all others are testnet
* @param {number=} opts.requiredCopayers - defaults to 3
* @param {number=} opts.totalCopayers - defaults to 5
- * @param {Object[]=} opts.indexes - an array to be deserialized using {@link HDParams#fromList}
+ * @param {Object[]} [opts.indexes] - an array to be deserialized using {@link HDParams#fromList}
* (defaults to all indexes in zero)
* @param {Object=} opts.nicknameFor - nicknames for other copayers
- * @param {boolean[]=} opts.copayersBackup - whether other copayers have backed up their wallets
+ * @param {boolean[]} [opts.copayersBackup] - whether other copayers have backed up their wallets
*/
function PublicKeyRing(opts) {
opts = opts || {};
@@ -527,7 +527,7 @@ PublicKeyRing.prototype.getForPath = function(path) {
* @see PublicKeyRing#getForPath
*
* @param {string[]} paths - the BIP32 paths
- * @return {Buffer[][]} the public keys, in buffer format
+ * @return {Array[]} the public keys, in buffer format (matrix of Buffer, Buffer[][])
*/
PublicKeyRing.prototype.getForPaths = function(paths) {
preconditions.checkArgument(!_.isUndefined(paths));
diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js
index 44f1f9bb7..80e3648c5 100644
--- a/js/models/core/TxProposals.js
+++ b/js/models/core/TxProposals.js
@@ -50,7 +50,6 @@ TxProposals.prototype.getNtxidsSince = function(sinceTs) {
if (txp.createdTs >= sinceTs)
ret.push(ii);
}
-console.log('[TxProposals.js.52:ret:]',ret); //TODO
return ret;
};
diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js
index f9af99d39..4246c3a0e 100644
--- a/js/models/core/Wallet.js
+++ b/js/models/core/Wallet.js
@@ -53,6 +53,7 @@ var copayConfig = require('../../../config');
* @TODO: figure out if reconnectDelay is set in milliseconds
* @param {number} opts.reconnectDelay - amount of seconds to wait before
* attempting to reconnect
+ * @constructor
*/
function Wallet(opts) {
var self = this;
@@ -63,19 +64,16 @@ function Wallet(opts) {
'publicKeyRing', 'txProposals', 'privateKey', 'version',
'reconnectDelay'
].forEach(function(k) {
- preconditions.checkArgument(!_.isUndefined(opts[k]), 'missing required option for Wallet: ' + k);
+ preconditions.checkArgument(!_.isUndefined(opts[k]), 'MISSOPT: missing required option for Wallet: ' + k);
self[k] = opts[k];
});
- preconditions.checkArgument(!copayConfig.forceNetwork || this.getNetworkName() === copayConfig.networkName,
- 'Network forced to ' + copayConfig.networkName +
- ' and tried to create a Wallet with network ' + this.getNetworkName());
this.id = opts.id || Wallet.getRandomId();
this.secretNumber = opts.secretNumber || Wallet.getRandomNumber();
this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin);
+ this.settings = opts.settings || copayConfig.wallet.settings;
this.name = opts.name;
- this.verbose = opts.verbose;
this.publicKeyRing.walletId = this.id;
this.txProposals.walletId = this.id;
this.network.maxPeers = this.totalCopayers;
@@ -86,6 +84,13 @@ function Wallet(opts) {
this.lastTimestamp = opts.lastTimestamp || undefined;
this.lastMessageFrom = {};
+ //to avoid confirmation of copayer's backups if is imported from a file
+ this.isImported = opts.isImported || false;
+
+
+ //to avoid waiting others copayers to make a backup and login immediatly
+ this.forcedLogin = opts.forcedLogin || false;
+
this.paymentRequests = opts.paymentRequests || {};
//network nonces are 8 byte buffers, representing a big endian number
@@ -112,6 +117,21 @@ Wallet.builderOpts = {
feeSat: undefined,
};
+/**
+ * @desc static list with persisted properties of a wallet.
+ * These are the properties that get stored/read from localstorage
+ */
+Wallet.PERSISTED_PROPERTIES = [
+ 'opts',
+ 'settings',
+ 'publicKeyRing',
+ 'txProposals',
+ 'privateKey',
+ 'addressBook',
+ 'backupOffered',
+ 'lastTimestamp',
+];
+
/**
* @desc Retrieve a random id for the wallet
* @TODO: Discuss changing to a UUID
@@ -149,7 +169,7 @@ Wallet.prototype.seedCopayer = function(pubKey) {
*
* @param {string} senderId - the sender id
* @param {Object} data - the data recived, {@see HDParams#fromList}
- * @emits {publicKeyRingUpdated}
+ * @emits publicKeyRingUpdated
*/
Wallet.prototype._onIndexes = function(senderId, data) {
log.debug('RECV INDEXES:', data);
@@ -161,6 +181,22 @@ Wallet.prototype._onIndexes = function(senderId, data) {
}
};
+/**
+ * @desc
+ * Changes wallet settings. The settings format is:
+ *
+ * var settings = {
+ * unitName: 'bits',
+ * unitToSatoshi: 100,
+ * alternativeName: 'US Dollar',
+ * alternativeIsoCode: 'USD',
+ * };
+ */
+Wallet.prototype.changeSettings = function(settings) {
+ this.settings = settings;
+ this.store();
+};
+
/**
* @desc
* Handles a 'PUBLICKEYRING' message from senderId.
@@ -178,8 +214,8 @@ Wallet.prototype._onIndexes = function(senderId, data) {
* @param {Object} data - the data recived, {@see HDParams#fromList}
* @param {Object} data.publicKeyRing - data to be deserialized into a {@link PublicKeyRing}
* using {@link PublicKeyRing#fromObj}
- * @emits {publicKeyRingUpdated}
- * @emits {connectionError}
+ * @emits publicKeyRingUpdated
+ * @emits connectionError
*/
Wallet.prototype._onPublicKeyRing = function(senderId, data) {
log.debug('RECV PUBLICKEYRING:', data);
@@ -214,7 +250,7 @@ Wallet.prototype._onPublicKeyRing = function(senderId, data) {
*
* @param {string} senderId - the copayer that sent this event
* @param {Object} m - the data received
- * @emits {txProposalEvent}
+ * @emits txProposalEvent
*/
Wallet.prototype._processProposalEvents = function(senderId, m) {
var ev;
@@ -481,7 +517,6 @@ Wallet.prototype._onData = function(senderId, data, ts) {
preconditions.checkArgument(data.type);
preconditions.checkArgument(ts);
preconditions.checkArgument(_.isNumber(ts));
-
log.debug('RECV', senderId, data);
if (data.type !== 'walletId' && this.id !== data.walletId) {
@@ -490,7 +525,6 @@ Wallet.prototype._onData = function(senderId, data, ts) {
return;
}
-
switch (data.type) {
// This handler is repeaded on WalletFactory (#join). TODO
case 'walletId':
@@ -570,6 +604,7 @@ Wallet.prototype._optsToObj = function() {
totalCopayers: this.totalCopayers,
name: this.name,
version: this.version,
+ networkName: this.getNetworkName(),
};
return obj;
@@ -600,6 +635,14 @@ Wallet.prototype.getMyCopayerIdPriv = function() {
return this.privateKey.getIdPriv(); //copayer idpriv is hex of a private key
};
+/**
+ * @desc Get my own nickname
+ * @return {string} copayer nickname
+ */
+Wallet.prototype.getMyCopayerNickname = function() {
+ return this.publicKeyRing.nicknameForCopayer(this.getMyCopayerId());
+};
+
/**
* @desc Returns the secret value for other users to join this wallet
* @return {string} my own pubkey, base58 encoded
@@ -615,7 +658,11 @@ Wallet.prototype.getSecretNumber = function() {
* @return {string}
*/
Wallet.prototype.getSecret = function() {
- var buf = new Buffer(this.getMyCopayerId() + this.getSecretNumber(), 'hex');
+ var buf = new Buffer(
+ this.getMyCopayerId() +
+ this.getSecretNumber() +
+ (this.getNetworkName() === 'livenet' ? '00' : '01'),
+ 'hex');
var str = Base58Check.encode(buf);
return str;
};
@@ -630,9 +677,11 @@ Wallet.decodeSecret = function(secretB) {
var secret = Base58Check.decode(secretB);
var pubKeyBuf = secret.slice(0, 33);
var secretNumber = secret.slice(33, 38);
+ var networkName = secret.slice(38, 39).toString('hex') === '00' ? 'livenet' : 'testnet';
return {
pubKey: pubKeyBuf.toString('hex'),
- secretNumber: secretNumber.toString('hex')
+ secretNumber: secretNumber.toString('hex'),
+ networkName: networkName,
}
};
@@ -661,7 +710,13 @@ Wallet.prototype._setBlockchainListeners = function() {
});
this.blockchain.on('tx', function(tx) {
log.debug('blockchain tx event');
- self.emit('tx', tx.address);
+ var addresses = self.getAddressesInfo();
+ var addr = _.findWhere(addresses, {
+ addressStr: tx.address
+ });
+ if (addr) {
+ self.emit('tx', tx.address, addr.isChange);
+ }
});
if (!self.spendUnconfirmed) {
@@ -767,23 +822,28 @@ Wallet.prototype.getRegisteredPeerIds = function() {
* @emits locked - in case the wallet is opened in another instance
*/
Wallet.prototype.keepAlive = function() {
- try {
- this.lock.keepAlive();
- } catch (e) {
- log.debug(e);
- this.emit('locked', null, 'Wallet appears to be openned on other browser instance. Closing this one.');
- }
+ var self = this;
+
+ this.lock.keepAlive(function(err) {
+ if (err) {
+ log.debug(err);
+ self.emit('locked', null, 'Wallet appears to be openned on other browser instance. Closing this one.');
+ }
+ });
};
/**
* @desc Store the wallet's state
+ * @param {function} callback (err)
*/
-Wallet.prototype.store = function() {
+Wallet.prototype.store = function(cb) {
+ var self = this;
this.keepAlive();
-
- var wallet = this.toObj();
- this.storage.setFromObj(this.id, wallet);
- log.debug('Wallet stored');
+ this.storage.setFromObj(this.id, this.toObj(), function(err) {
+ log.debug('Wallet stored');
+ if (cb)
+ cb(err);
+ });
};
/**
@@ -793,13 +853,11 @@ Wallet.prototype.store = function() {
Wallet.prototype.toObj = function() {
var optsObj = this._optsToObj();
- var networkNonce = this.network.getHexNonce();
- var networkNonces = this.network.getHexNonces();
-
var walletObj = {
opts: optsObj,
- networkNonce: networkNonce, //yours
- networkNonces: networkNonces, //copayers
+ settings: this.settings,
+ networkNonce: this.network.getHexNonce(), //yours
+ networkNonces: this.network.getHexNonces(), //copayers
publicKeyRing: this.publicKeyRing.toObj(),
txProposals: this.txProposals.toObj(),
privateKey: this.privateKey ? this.privateKey.toObj() : undefined,
@@ -827,10 +885,11 @@ Wallet.prototype.toObj = function() {
*/
Wallet.fromObj = function(o, storage, network, blockchain) {
- // TODO: What is this supposed to do?
+ // clone opts
var opts = JSON.parse(JSON.stringify(o.opts));
opts.addressBook = o.addressBook;
+ opts.settings = o.settings;
if (o.privateKey) {
opts.privateKey = PrivateKey.fromObj(o.privateKey);
@@ -867,6 +926,7 @@ Wallet.fromObj = function(o, storage, network, blockchain) {
opts.storage = storage;
opts.network = network;
opts.blockchain = blockchain;
+ opts.isImported = true;
return new Wallet(opts);
};
@@ -896,7 +956,6 @@ Wallet.prototype.send = function(recipients, obj) {
Wallet.prototype.sendAllTxProposals = function(recipients, sinceTs) {
var ntxids = sinceTs ? this.txProposals.getNtxidsSince(sinceTs) : this.txProposals.getNtxids();
var self = this;
-
_.each(ntxids, function(ntxid, key) {
self.sendTxProposal(ntxid, recipients);
});
@@ -905,7 +964,7 @@ Wallet.prototype.sendAllTxProposals = function(recipients, sinceTs) {
/**
* @desc Send a TxProposal identified by transaction id to a set of recipients
* @param {string} ntxid - the transaction proposal id
- * @param {string[]=} recipients - the pubkeys of the recipients
+ * @param {string[]} [recipients] - the pubkeys of the recipients
*/
Wallet.prototype.sendTxProposal = function(ntxid, recipients) {
preconditions.checkArgument(ntxid);
@@ -947,7 +1006,7 @@ Wallet.prototype.sendReject = function(ntxid) {
/**
* @desc Notify other peers that a wallet has been backed up and it's ready to be used
- * @param {string[]=} recipients - the pubkeys of the recipients
+ * @param {string[]} [recipients] - the pubkeys of the recipients
*/
Wallet.prototype.sendWalletReady = function(recipients, sinceTs) {
log.debug('### SENDING WalletReady TO:', recipients || 'All');
@@ -962,7 +1021,7 @@ Wallet.prototype.sendWalletReady = function(recipients, sinceTs) {
/**
* @desc Notify other peers of the walletId
* @TODO: Why is this needed? Can't everybody just calculate the walletId?
- * @param {string[]=} recipients - the pubkeys of the recipients
+ * @param {string[]} [recipients] - the pubkeys of the recipients
*/
Wallet.prototype.sendWalletId = function(recipients) {
log.debug('### SENDING walletId TO:', recipients || 'All', this.id);
@@ -977,7 +1036,7 @@ Wallet.prototype.sendWalletId = function(recipients) {
/**
* @desc Send the current PublicKeyRing to other recipients
- * @param {string[]=} recipients - the pubkeys of the recipients
+ * @param {string[]} [recipients] - the pubkeys of the recipients
*/
Wallet.prototype.sendPublicKeyRing = function(recipients) {
log.debug('### SENDING publicKeyRing TO:', recipients || 'All', this.publicKeyRing.toObj());
@@ -992,7 +1051,7 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) {
/**
* @desc Send the current indexes of our public key ring to other peers
- * @param {string[]=} recipients - the pubkeys of the recipients
+ * @param {string[]} recipients - the pubkeys of the recipients
*/
Wallet.prototype.sendIndexes = function(recipients) {
var indexes = HDParams.serialize(this.publicKeyRing.indexes);
@@ -1007,7 +1066,7 @@ Wallet.prototype.sendIndexes = function(recipients) {
/**
* @desc Send our addressBook to other recipients
- * @param {string[]=} recipients - the pubkeys of the recipients
+ * @param {string[]} recipients - the pubkeys of the recipients
*/
Wallet.prototype.sendAddressBook = function(recipients) {
log.debug('### SENDING addressBook TO:', recipients || 'All', this.addressBook);
@@ -1244,7 +1303,7 @@ Wallet.prototype.createPaymentTx = function(options, cb) {
return self.receivePaymentRequest(options, pr, cb);
})
.error(function(data, status, headers, config) {
- return cb(new Error('Status: ' + JSON.stringify(status)));
+ return cb(new Error('Status: ' + status));
});
};
@@ -1357,7 +1416,9 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) {
expires: expires,
memo: memo || 'This server would like some BTC from you.',
payment_url: payment_url,
- merchant_data: merchant_data.toString('hex')
+ merchant_data: merchant_data ? merchant_data.toString('hex')
+ // : new Buffer('none', 'utf8').toString('hex')
+ : '00'
},
signature: sig.toString('hex'),
ca: trust.caName,
@@ -1521,7 +1582,7 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) {
return self.receivePaymentRequestACK(ntxid, tx, txp, ack, cb);
})
.error(function(data, status, headers, config) {
- return cb(new Error('Status: ' + JSON.stringify(status)));
+ return cb(new Error('Status: ' + status));
});
};
@@ -1891,21 +1952,16 @@ Wallet.prototype.getAddressesStr = function(opts) {
});
};
+Wallet.prototype.subscribeToAddresses = function() {
+ var addrInfo = this.publicKeyRing.getAddressesInfo();
+ this.blockchain.subscribe(_.pluck(addrInfo, 'addressStr'));
+};
+
/**
* @desc Alias for {@link PublicKeyRing#getAddressesInfo}
*/
Wallet.prototype.getAddressesInfo = function(opts) {
- var addrInfo = this.publicKeyRing.getAddressesInfo(opts, this.publicKey);
- var currentAddrs = this.blockchain.getSubscriptions();
-
- var newAddrs = [];
- for (var i in addrInfo) {
- var a = addrInfo[i];
- if (!currentAddrs[a.addressStr] && !a.isChange)
- newAddrs.push(a.addressStr);
- }
- this.blockchain.subscribe(newAddrs);
- return addrInfo;
+ return this.publicKeyRing.getAddressesInfo(opts, this.publicKey);
};
/**
* @desc Returns true if a given address was generated by deriving our master public key
@@ -2293,11 +2349,14 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb)
/**
* @desc Closes the wallet and disconnects all services
*/
-Wallet.prototype.close = function() {
+Wallet.prototype.close = function(cb) {
+ var self =this;
log.debug('## CLOSING');
- this.lock.release();
- this.network.cleanUp();
- this.blockchain.destroy();
+ this.lock.release(function() {
+ self.network.cleanUp();
+ self.blockchain.destroy();
+ if (cb) return cb();
+ });
};
/**
@@ -2389,7 +2448,7 @@ Wallet.prototype.isShared = function() {
* @return {boolean}
*/
Wallet.prototype.isReady = function() {
- var ret = this.publicKeyRing.isComplete() && this.publicKeyRing.isFullyBackup();
+ var ret = this.publicKeyRing.isComplete() && (this.publicKeyRing.isFullyBackup() || this.isImported || this.forcedLogin);
return ret;
};
@@ -2398,7 +2457,8 @@ Wallet.prototype.isReady = function() {
*
* Also backs up the wallet
*/
-Wallet.prototype.setBackupReady = function() {
+Wallet.prototype.setBackupReady = function(forcedLogin) {
+ this.forcedLogin = forcedLogin;
this.publicKeyRing.setBackupReady();
this.sendPublicKeyRing();
this.store();
@@ -2436,15 +2496,6 @@ Wallet.prototype.verifySignedJson = function(senderId, payload, signature) {
return v;
}
-// NOTE: Angular $http module does not send ArrayBuffers correctly, so we're
-// not going to use it. We'll have to write our own. Otherwise, we could
-// hex-encoded our messages and decode them on the other side, but that
-// deviates from BIP-70.
-
-// if (typeof angular !== 'undefined') {
-// var $http = angular.bootstrap().get('$http');
-// }
-
/**
* @desc Create a HTTP request
* @TODO: This shouldn't be a wallet responsibility
@@ -2510,7 +2561,13 @@ Wallet.request = function(options, callback) {
};
xhr.onerror = function(event) {
- return ret._error(null, new Error(event.message), null, options);
+ var status;
+ if (xhr.status === 0 || !xhr.statusText) {
+ status = 'HTTP Request Error: This endpoint likely does not support cross-origin requests.';
+ } else {
+ status = xhr.statusText;
+ }
+ return ret._error(null, status, null, options);
};
if (req.body) {
@@ -2522,4 +2579,4 @@ Wallet.request = function(options, callback) {
return ret;
};
-module.exports = Wallet;
\ No newline at end of file
+module.exports = Wallet;
diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js
index 2df26a931..5dc57dd19 100644
--- a/js/models/core/WalletFactory.js
+++ b/js/models/core/WalletFactory.js
@@ -1,4 +1,5 @@
'use strict';
+var preconditions = require('preconditions').singleton();
var TxProposals = require('./TxProposals');
var PublicKeyRing = require('./PublicKeyRing');
@@ -6,9 +7,11 @@ var PrivateKey = require('./PrivateKey');
var Wallet = require('./Wallet');
var _ = require('underscore');
var log = require('../../log');
+var PluginManager = require('./PluginManager');
var Async = module.exports.Async = require('../network/Async');
var Insight = module.exports.Insight = require('../blockchain/Insight');
-var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('../storage/LocalEncrypted');
+var preconditions = require('preconditions').singleton();
+var Storage = module.exports.Storage = require('../Storage');
/**
* @desc
@@ -23,97 +26,103 @@ var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('../s
* @param {Storage} config.Storage - the class to instantiate to store the wallet (StorageLocalEncrypted by default)
* @param {Object} config.storage - the configuration to be sent to the Storage constructor
* @param {Network} config.Network - the class to instantiate to make network requests to copayers (the Async module by default)
- * @param {Object} config.network - the configuration to be sent to the Network constructor
+ * @param {Object} config.network - the configurations to be sent to the Network and Blockchain constructors
* @param {Blockchain} config.Blockchain - the class to instantiate to get information about the blockchain (Insight by default)
- * @param {Object} config.blockchain - the configuration to be sent to the Blockchain constructor
- * @param {string} config.networkName - the name of the bitcoin network to use ('testnet' or 'livenet')
* @TODO: Investigate what parameters go inside this object
* @param {Object} config.wallet - default configuration for the wallet
* @TODO: put `version` inside of the config object
* @param {string} version - the version of copay for which this wallet was generated (for example, 0.4.7)
+ * @constructor
*/
-function WalletFactory(config, version) {
- var self = this;
- config = config || {};
- this.Storage = config.Storage || StorageLocalEncrypted;
+function WalletFactory(config, version, pluginManager) {
+ var self = this;
+ preconditions.checkArgument(config);
+ preconditions.checkArgument(config.network);
+
+ this.Storage = config.Storage || Storage;
this.Network = config.Network || Async;
this.Blockchain = config.Blockchain || Insight;
- this.storage = new this.Storage(config.storage);
- this.network = new this.Network(config.network);
- this.blockchain = new this.Blockchain(config.blockchain);
+ var storageOpts = {};
- this.networkName = config.networkName;
- this.walletDefaults = config.wallet;
+ if (pluginManager) {
+ storageOpts = {
+ storage: pluginManager.get('STORAGE')
+ };
+ }
+
+ this.storage = new this.Storage(storageOpts);
+
+ this.networks = {
+ 'livenet': new this.Network(config.network.livenet),
+ 'testnet': new this.Network(config.network.testnet),
+ };
+ this.blockchains = {
+ 'livenet': new this.Blockchain(config.network.livenet),
+ 'testnet': new this.Blockchain(config.network.testnet),
+ };
+
+ this.walletDefaults = config.wallet || {};
this.version = version;
};
+
/**
- * @desc
- * Returns true if the storage instance can retrieve the following keys using a given walletId
- *
- * - publicKeyRing
- * - txProposals
- * - opts
- * - privateKey
- *
- * @param {string} walletId
- * @return {boolean} true if all the keys are present in the storage instance
+ * @desc obtain network name from serialized wallet
+ * @param {Object} wallet object
+ * @return {string} network name
*/
-WalletFactory.prototype._checkRead = function(walletId) {
- var s = this.storage;
- var ret =
- s.get(walletId, 'publicKeyRing') &&
- s.get(walletId, 'txProposals') &&
- s.get(walletId, 'opts') &&
- s.get(walletId, 'privateKey');
- return !!ret;
+WalletFactory.prototype.obtainNetworkName = function(obj) {
+ return obj.networkName ||
+ obj.opts.networkName ||
+ obj.publicKeyRing.networkName ||
+ obj.privateKey.networkName;
};
/**
* @desc Deserialize an object to a Wallet
- * @param {Object} obj
+ * @param {Object} wallet object
* @param {string[]} skipFields - fields to skip when importing
* @return {Wallet}
*/
-WalletFactory.prototype.fromObj = function(obj, skipFields) {
+WalletFactory.prototype.fromObj = function(inObj, skipFields) {
+ var networkName = this.obtainNetworkName(inObj);
+ preconditions.checkState(networkName);
+ preconditions.checkArgument(inObj);
+
+ var obj = JSON.parse(JSON.stringify(inObj));
// not stored options
+ obj.opts = obj.opts || {};
obj.opts.reconnectDelay = this.walletDefaults.reconnectDelay;
- // this is only used if private key or public key ring is skipped
- obj.opts.networkName = this.networkName;
-
skipFields = skipFields || [];
- skipFields.forEach(function(k){
+ skipFields.forEach(function(k) {
if (obj[k]) {
delete obj[k];
- } else
+ } else
throw new Error('unknown field:' + k);
});
- var w = Wallet.fromObj(obj, this.storage, this.network, this.blockchain);
+ var w = Wallet.fromObj(obj, this.storage, this.networks[networkName], this.blockchains[networkName]);
if (!w) return false;
- w.verbose = this.verbose;
this._checkVersion(w.version);
- this._checkNetwork(w.getNetworkName());
return w;
};
/**
* @desc Imports a wallet from an encrypted base64 object
* @param {string} base64 - the base64 encoded object
- * @param {string} password - password to decrypt it
+ * @param {string} passphrase - passphrase to decrypt it
* @param {string[]} skipFields - fields to ignore when importing
* @return {Wallet}
*/
-WalletFactory.prototype.fromEncryptedObj = function(base64, password, skipFields) {
- this.storage._setPassphrase(password);
+WalletFactory.prototype.fromEncryptedObj = function(base64, passphrase, skipFields) {
+ this.storage.setPassphrase(passphrase);
var walletObj = this.storage.import(base64);
if (!walletObj) return false;
- var w = this.fromObj(walletObj, skipFields);
- return w;
+ return this.fromObj(walletObj, skipFields);
};
/**
@@ -121,15 +130,15 @@ WalletFactory.prototype.fromEncryptedObj = function(base64, password, skipFields
* @TODO: this is essentialy the same method as {@link WalletFactory#fromEncryptedObj}!
* @desc Imports a wallet from an encrypted base64 object
* @param {string} base64 - the base64 encoded object
- * @param {string} password - password to decrypt it
+ * @param {string} passphrase - passphrase to decrypt it
* @param {string[]} skipFields - fields to ignore when importing
* @return {Wallet}
*/
-WalletFactory.prototype.import = function(base64, password, skipFields) {
+WalletFactory.prototype.import = function(base64, passphrase, skipFields) {
var self = this;
- var w = self.fromEncryptedObj(base64, password, skipFields);
+ var w = self.fromEncryptedObj(base64, passphrase, skipFields);
- if (!w) throw new Error('Wrong password');
+ if (!w) throw new Error('Wrong passphrase');
return w;
};
@@ -137,30 +146,52 @@ WalletFactory.prototype.import = function(base64, password, skipFields) {
* @desc Retrieve a wallet from storage
* @param {string} walletId - the wallet id
* @param {string[]} skipFields - parameters to ignore when importing
- * @return {Wallet}
+ * @param {function} callback - {err, Wallet}
*/
-WalletFactory.prototype.read = function(walletId, skipFields) {
- if (!this._checkRead(walletId))
- return false;
-
+WalletFactory.prototype.read = function(walletId, skipFields, cb) {
+ var self = this,
+ err;
var obj = {};
- var s = this.storage;
- obj.id = walletId;
- obj.opts = s.get(walletId, 'opts');
- obj.publicKeyRing = s.get(walletId, 'publicKeyRing');
- obj.txProposals = s.get(walletId, 'txProposals');
- obj.privateKey = s.get(walletId, 'privateKey');
- obj.addressBook = s.get(walletId, 'addressBook');
- obj.backupOffered = s.get(walletId, 'backupOffered');
- obj.lastTimestamp = s.get(walletId, 'lastTimestamp');
+ this.storage.getMany(walletId, Wallet.PERSISTED_PROPERTIES, function(ret) {
+ for (var ii in ret) {
+ obj[ii] = ret[ii];
+ }
- var w = this.fromObj(obj, skipFields);
- return w;
+ if (!_.any(_.values(obj)))
+ return cb(new Error('Wallet not found'));
+
+ var w, err;
+ obj.id = walletId;
+ try {
+ w = self.fromObj(obj, skipFields);
+ } catch (e) {
+ if (e && e.message && e.message.indexOf('MISSOPTS')) {
+ err = new Error('Could not read: ' + walletId);
+ } else {
+ err = e;
+ }
+ w = null;
+ }
+ return cb(err, w);
+ });
+};
+
+
+/**
+ * @desc This method instantiates a wallet. Usefull for stubbing.
+ *
+ * @param {opts} opts, ready for new Wallet(opts)
+ *
+ */
+
+
+WalletFactory.prototype._getWallet = function(opts) {
+ return new Wallet(opts);
};
/**
- * @desc This method instantiates a wallet
+ * @desc This method prepares options for a new Wallet
*
* @param {Object} opts
* @param {string} opts.id
@@ -176,17 +207,22 @@ WalletFactory.prototype.read = function(walletId, skipFields) {
* @TODO: Figure out in what unit is this reconnect delay.
* @param {number} opts.reconnectDelay milliseconds?
* @param {number=} opts.version
+ * @param {callback} opts.version
* @return {Wallet}
*/
-WalletFactory.prototype.create = function(opts) {
+WalletFactory.prototype.create = function(opts, cb) {
+ preconditions.checkArgument(cb);
+
opts = opts || {};
+ opts.networkName = opts.networkName || 'testnet';
+
log.debug('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey'));
var privOpts = {
- networkName: this.networkName,
+ networkName: opts.networkName,
};
- if (opts.privateKeyHex && opts.privateKeyHex.length>1) {
+ if (opts.privateKeyHex && opts.privateKeyHex.length > 1) {
privOpts.extendedPrivateKeyString = opts.privateKeyHex;
}
@@ -197,7 +233,7 @@ WalletFactory.prototype.create = function(opts) {
opts.lockTimeoutMin = this.walletDefaults.idleDurationMin;
opts.publicKeyRing = opts.publicKeyRing || new PublicKeyRing({
- networkName: this.networkName,
+ networkName: opts.networkName,
requiredCopayers: requiredCopayers,
totalCopayers: totalCopayers,
});
@@ -208,16 +244,14 @@ WalletFactory.prototype.create = function(opts) {
log.debug('\t### PublicKeyRing Initialized');
opts.txProposals = opts.txProposals || new TxProposals({
- networkName: this.networkName,
+ networkName: opts.networkName,
});
log.debug('\t### TxProposals Initialized');
- this.storage._setPassphrase(opts.passphrase);
opts.storage = this.storage;
- opts.network = this.network;
- opts.blockchain = this.blockchain;
- opts.verbose = this.verbose;
+ opts.network = this.networks[opts.networkName];
+ opts.blockchain = this.blockchains[opts.networkName];
opts.spendUnconfirmed = opts.spendUnconfirmed || this.walletDefaults.spendUnconfirmed;
opts.reconnectDelay = opts.reconnectDelay || this.walletDefaults.reconnectDelay;
@@ -225,10 +259,15 @@ WalletFactory.prototype.create = function(opts) {
opts.totalCopayers = totalCopayers;
opts.version = opts.version || this.version;
- var w = new Wallet(opts);
- w.store();
- this.storage.setLastOpened(w.id);
- return w;
+ this.storage.setPassphrase(opts.passphrase);
+ var w = this._getWallet(opts);
+ var self = this;
+ w.store(function(err) {
+ if (err) return cb(err);
+ self.storage.setLastOpened(w.id, function(err) {
+ return cb(err, w);
+ });
+ });
};
/**
@@ -245,20 +284,9 @@ WalletFactory.prototype._checkVersion = function(inVersion) {
//We only check for major version differences
if (thisV0 < inV0) {
throw new Error('Major difference in software versions' +
- '. Received:' + inVersion +
- '. Current version:' + this.version +
- '. Aborting.');
- }
-};
-
-/**
- * @desc Throw an error if the network name is different to {@link WalletFactory#networkName}
- * @param {string} inNetworkName - the network name to check
- * @throws {Error}
- */
-WalletFactory.prototype._checkNetwork = function(inNetworkName) {
- if (this.networkName !== inNetworkName) {
- throw new Error('This Wallet is configured for ' + inNetworkName + ' while currently Copay is configured for: ' + this.networkName + '. Check your settings.');
+ '. Received:' + inVersion +
+ '. Current version:' + this.version +
+ '. Aborting.');
}
};
@@ -266,28 +294,31 @@ WalletFactory.prototype._checkNetwork = function(inNetworkName) {
* @desc Retrieve a wallet from the storage
* @param {string} walletId - the id of the wallet
* @param {string} passphrase - the passphrase to decode it
- * @return {Wallet}
+ * @param {function} callback (err, {Wallet})
+ * @return
*/
-WalletFactory.prototype.open = function(walletId, passphrase) {
- this.storage._setPassphrase(passphrase);
- var w = this.read(walletId);
- if (w)
- w.store();
+WalletFactory.prototype.open = function(walletId, passphrase, cb) {
+ preconditions.checkArgument(cb);
+ var self = this;
+ self.storage.setPassphrase(passphrase);
+ self.read(walletId, null, function(err, w) {
+ if (err) return cb(err);
- this.storage.setLastOpened(walletId);
- return w;
+ w.store(function(err) {
+ self.storage.setLastOpened(walletId, function() {
+ return cb(err, w);
+ });
+ });
+ });
};
-/**
- * @desc Retrieve all wallets stored without encription in the storage instance
- * @returns {Wallet[]}
- */
-WalletFactory.prototype.getWallets = function() {
- var ret = this.storage.getWallets();
- ret.forEach(function(i) {
- i.show = i.name ? ((i.name + ' <' + i.id + '>')) : i.id;
+WalletFactory.prototype.getWallets = function(cb) {
+ this.storage.getWallets(function(ret) {
+ ret.forEach(function(i) {
+ i.show = i.name ? ((i.name + ' <' + i.id + '>')) : i.id;
+ });
+ return cb(null, ret);
});
- return ret;
};
/**
@@ -300,9 +331,12 @@ WalletFactory.prototype.getWallets = function() {
*/
WalletFactory.prototype.delete = function(walletId, cb) {
var s = this.storage;
- s.deleteWallet(walletId);
- s.setLastOpened(undefined);
- return cb();
+ s.deleteWallet(walletId, function(err) {
+ if (err) return cb(err);
+ s.setLastOpened(null, function(err) {
+ return cb(err);
+ });
+ });
};
/**
@@ -318,7 +352,7 @@ WalletFactory.prototype.decodeSecret = function(secret) {
/**
* @callback walletCreationCallback
- * @param {?=} err - an error, if any, that happened during the wallet creation
+ * @param {?} err - an error, if any, that happened during the wallet creation
* @param {Wallet=} wallet - the wallet created
*/
@@ -330,64 +364,86 @@ WalletFactory.prototype.decodeSecret = function(secret) {
* information locally using passphrase. privateHex is the
* private extended master key. cb has two params: error and wallet.
*
- * @param {string} secret - the wallet secret
- * @param {string} nickname - a nickname for the current user
- * @param {string} passphrase - a passphrase to use to encrypt the wallet for persistance
- * @param {string} privateHex - the private extended master key
+ * @param {object} opts
+ * @param {string} opts.secret - the wallet secret
+ * @param {string} opts.passphrase - a passphrase to use to encrypt the wallet for persistance
+ * @param {string} opts.nickname - a nickname for the current user
+ * @param {string} opts.privateHex - the private extended master key
* @param {walletCreationCallback} cb - a callback
*/
-WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphrase, privateHex, cb) {
+WalletFactory.prototype.joinCreateSession = function(opts, cb) {
+ preconditions.checkArgument(opts);
+ preconditions.checkArgument(opts.secret);
+ preconditions.checkArgument(opts.passphrase);
+ preconditions.checkArgument(opts.nickname);
+ preconditions.checkArgument(cb);
var self = this;
- var s = self.decodeSecret(secret);
- if (!s) return cb('badSecret');
+ var decodedSecret = this.decodeSecret(opts.secret);
+ if (!decodedSecret || !decodedSecret.networkName || !decodedSecret.pubKey) {
+ return cb('badSecret');
+ }
var privOpts = {
- networkName: this.networkName,
+ networkName: decodedSecret.networkName,
};
- if (privateHex && privateHex.length>1) {
+ if (opts.privateHex && opts.privateHex.length > 1) {
privOpts.extendedPrivateKeyString = privateHex;
}
//Create our PrivateK
var privateKey = new PrivateKey(privOpts);
log.debug('\t### PrivateKey Initialized');
- var opts = {
+ var joinOpts = {
copayerId: privateKey.getId(),
privkey: privateKey.getIdPriv(),
key: privateKey.getIdKey(),
- secretNumber : s.secretNumber,
+ secretNumber: decodedSecret.secretNumber,
};
- self.network.cleanUp();
+
+ var joinNetwork = this.networks[decodedSecret.networkName];
+ joinNetwork.cleanUp();
// This is a hack to reconize if the connection was rejected or the peer wasn't there.
var connectedOnce = false;
- self.network.on('connected', function(sender, data) {
+ joinNetwork.on('connected', function(sender, data) {
connectedOnce = true;
});
- self.network.on('serverError', function() {
+ joinNetwork.on('connect_error', function() {
+ return cb('connectionError');
+ });
+
+ joinNetwork.on('serverError', function() {
return cb('joinError');
});
- self.network.start(opts, function() {
- self.network.greet(s.pubKey,opts.secretNumber);
- self.network.on('data', function(sender, data) {
- if (data.type === 'walletId') {
- if (data.networkName !== self.networkName) {
+ joinNetwork.start(joinOpts, function() {
+
+ joinNetwork.greet(decodedSecret.pubKey, joinOpts.secretNumber);
+ joinNetwork.on('data', function(sender, data) {
+ if (data.type === 'walletId' && data.opts) {
+ if (data.networkName !== decodedSecret.networkName) {
return cb('badNetwork');
}
- data.opts.privateKey = privateKey;
- data.opts.nickname = nickname;
- data.opts.passphrase = passphrase;
- data.opts.id = data.walletId;
- var w = self.create(data.opts);
- w.sendWalletReady(s.pubKey);
- //w.seedCopayer(s.pubKey);
- return cb(null, w);
- } else {
- return cb('walletFull', w);
+ var walletOpts = _.clone(data.opts);
+ walletOpts.id = data.walletId;
+
+ walletOpts.privateKey = privateKey;
+ walletOpts.nickname = opts.nickname;
+ walletOpts.passphrase = opts.passphrase;
+
+ self.create(walletOpts, function(err, w) {
+
+ if (w) {
+ w.sendWalletReady(decodedSecret.pubKey);
+ } else {
+ if (!err) err = 'walletFull';
+ log.info(err);
+ }
+ return cb(err, w);
+ });
}
});
});
diff --git a/js/models/core/WalletLock.js b/js/models/core/WalletLock.js
index edd2bf67d..0b1fe503d 100644
--- a/js/models/core/WalletLock.js
+++ b/js/models/core/WalletLock.js
@@ -6,50 +6,95 @@ function WalletLock(storage, walletId, timeoutMin) {
preconditions.checkArgument(storage);
preconditions.checkArgument(walletId);
- this.sessionId = storage.getSessionId();
this.storage = storage;
this.timeoutMin = timeoutMin || 5;
this.key = WalletLock._keyFor(walletId);
- this._setLock();
}
+
+WalletLock.prototype.init = function(cb) {
+ preconditions.checkArgument(cb);
+ var self = this;
+
+ self.storage.getSessionId(function(sid) {
+ preconditions.checkState(sid);
+
+ self.sessionId = sid;
+ cb();
+ });
+};
+
WalletLock._keyFor = function(walletId) {
return 'lock' + '::' + walletId;
};
-WalletLock.prototype._isLockedByOther = function() {
- var json = this.storage.getGlobal(this.key);
- var wl = json ? JSON.parse(json) : null;
- var t = wl ? (Date.now() - wl.expireTs) : false;
- // is not locked?
- if (!wl || t > 0 || wl.sessionId === this.sessionId)
- return false;
+WalletLock.prototype._isLockedByOther = function(cb) {
+ var self = this;
- // Seconds remainding
- return parseInt(-t/1000.);
-};
+ this.storage.getGlobal(this.key, function(json) {
+ var wl = json ? JSON.parse(json) : null;
+ if (!wl || !wl.expireTs)
+ return cb(false);
+ var expiredSince = Date.now() - wl.expireTs;
+ if (expiredSince >= 0)
+ return cb(false);
-WalletLock.prototype._setLock = function() {
- this.storage.setGlobal(this.key, {
- sessionId: this.sessionId,
- expireTs: Date.now() + this.timeoutMin * 60 * 1000,
+ var isMyself = wl.sessionId === self.sessionId;
+
+ if (isMyself)
+ return cb(false);
+
+ // Seconds remainding
+ return cb(parseInt(-expiredSince / 1000));
});
};
-WalletLock.prototype.keepAlive = function() {
+WalletLock.prototype._setLock = function(cb) {
+ preconditions.checkArgument(cb);
preconditions.checkState(this.sessionId);
+ var self = this;
- var t = this._isLockedByOther();
- if (t)
- throw new Error('Wallet is already open. Close it to proceed or wait '+ t + ' seconds if you close it already' );
- this._setLock();
+ this.storage.setGlobal(this.key, {
+ sessionId: this.sessionId,
+ expireTs: Date.now() + this.timeoutMin * 60 * 1000,
+ }, function() {
+
+ cb(null);
+ });
};
-WalletLock.prototype.release = function() {
- this.storage.removeGlobal(this.key);
+WalletLock.prototype._doKeepAlive = function(cb) {
+ preconditions.checkArgument(cb);
+ preconditions.checkState(this.sessionId);
+
+ var self = this;
+
+ this._isLockedByOther(function(t) {
+ if (t)
+ return cb(new Error('LOCKED: Wallet is locked for ' + t + ' srcs'));
+
+ self._setLock(cb);
+ });
+};
+
+
+
+WalletLock.prototype.keepAlive = function(cb) {
+ var self = this;
+
+ if (!self.sessionId) {
+ return self.init(self._doKeepAlive.bind(self, cb));
+ };
+
+ return this._doKeepAlive(cb);
+};
+
+
+WalletLock.prototype.release = function(cb) {
+ this.storage.removeGlobal(this.key, cb);
};
diff --git a/js/models/network/Async.js b/js/models/network/Async.js
index c6d001b7d..a2ca61017 100644
--- a/js/models/network/Async.js
+++ b/js/models/network/Async.js
@@ -11,12 +11,11 @@ var io = require('socket.io-client');
var preconditions = require('preconditions').singleton();
function Network(opts) {
- var self = this;
+ preconditions.checkArgument(opts);
+ preconditions.checkArgument(opts.url);
opts = opts || {};
this.maxPeers = opts.maxPeers || 12;
- this.host = opts.host || 'localhost';
- this.port = opts.port || 3001;
- this.schema = opts.schema || 'https';
+ this.url = opts.url;
this.secretNumber = opts.secretNumber;
this.cleanUp();
}
@@ -74,12 +73,12 @@ Network.prototype.connectedCopayers = function() {
return ret;
};
-Network.prototype._sendHello = function(copayerId,secretNumber) {
+Network.prototype._sendHello = function(copayerId, secretNumber) {
this.send(copayerId, {
type: 'hello',
copayerId: this.copayerId,
- secretNumber : secretNumber
+ secretNumber: secretNumber
});
};
@@ -197,11 +196,10 @@ Network.prototype._onMessage = function(enc) {
var self = this;
switch (payload.type) {
case 'hello':
- if (typeof payload.secretNumber === 'undefined' || payload.secretNumber !== this.secretNumber)
- {
+ if (typeof payload.secretNumber === 'undefined' || payload.secretNumber !== this.secretNumber) {
this._sendRejectConnection(sender);
this._deletePeer(enc.pubkey, 'incorrect secret number');
- return;
+ return;
}
// if we locked allowed copayers, check if it belongs
if (this.allowedCopayerIds && !this.allowedCopayerIds[payload.copayerId]) {
@@ -274,8 +272,8 @@ Network.prototype._onError = function(err) {
this.criticalError = err.message;
};
-Network.prototype.greet = function(copayerId,secretNumber) {
- this._sendHello(copayerId,secretNumber);
+Network.prototype.greet = function(copayerId, secretNumber) {
+ this._sendHello(copayerId, secretNumber);
var peerId = this.peerFromCopayer(copayerId);
this._addCopayerMap(peerId, copayerId);
};
@@ -326,11 +324,10 @@ Network.prototype.start = function(opts, openCallback) {
};
Network.prototype.createSocket = function() {
- var hostPort = this.schema + '://' + this.host + ':' + this.port;
- return io.connect(hostPort, {
+ return io.connect(this.url, {
reconnection: true,
'force new connection': true,
- 'secure': this.schema === 'https',
+ 'secure': this.url.indexOf('https') === 0,
});
};
diff --git a/js/models/storage/LocalEncrypted.js b/js/models/storage/LocalEncrypted.js
deleted file mode 100644
index b093eaaa4..000000000
--- a/js/models/storage/LocalEncrypted.js
+++ /dev/null
@@ -1,208 +0,0 @@
-'use strict';
-
-var CryptoJS = require('node-cryptojs-aes').CryptoJS;
-var bitcore = require('bitcore');
-var preconditions = require('preconditions').instance();
-var id = 0;
-
-function Storage(opts) {
- opts = opts || {};
-
- this.__uniqueid = ++id;
- if (opts.password)
- this._setPassphrase(opts.password);
-
- try {
- this.localStorage = opts.localStorage || localStorage;
- this.sessionStorage = opts.sessionStorage || sessionStorage;
- } catch (e) {}
- preconditions.checkState(this.localStorage, 'No localstorage found');
- preconditions.checkState(this.sessionStorage, 'No sessionStorage found');
-}
-
-var pps = {};
-Storage.prototype._getPassphrase = function() {
- if (!pps[this.__uniqueid])
- throw new Error('No passprase set');
-
- return pps[this.__uniqueid];
-}
-
-Storage.prototype._setPassphrase = function(password) {
- pps[this.__uniqueid] = password;
-}
-
-Storage.prototype._encrypt = function(string) {
- var encrypted = CryptoJS.AES.encrypt(string, this._getPassphrase());
- var encryptedBase64 = encrypted.toString();
- return encryptedBase64;
-};
-
-Storage.prototype._decrypt = function(base64) {
- var decryptedStr = null;
- try {
- var decrypted = CryptoJS.AES.decrypt(base64, this._getPassphrase());
- if (decrypted)
- decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
- } catch (e) {
- // Error while decrypting
- return null;
- }
- return decryptedStr;
-};
-
-
-Storage.prototype._read = function(k) {
- var ret;
- ret = this.localStorage.getItem(k);
- if (!ret) return null;
- ret = this._decrypt(ret);
- if (!ret) return null;
- ret = ret.toString(CryptoJS.enc.Utf8);
- ret = JSON.parse(ret);
- return ret;
-};
-
-Storage.prototype._write = function(k, v) {
- v = JSON.stringify(v);
- v = this._encrypt(v);
-
- this.localStorage.setItem(k, v);
-};
-
-// get value by key
-Storage.prototype.getGlobal = function(k) {
- var item = this.localStorage.getItem(k);
- return item == 'undefined' ? undefined : item;
-};
-
-// set value for key
-Storage.prototype.setGlobal = function(k, v) {
- this.localStorage.setItem(k, typeof v === 'object' ? JSON.stringify(v) : v);
-};
-
-// remove value for key
-Storage.prototype.removeGlobal = function(k) {
- this.localStorage.removeItem(k);
-};
-
-Storage.prototype.getSessionId = function() {
- var sessionId = this.sessionStorage.getItem('sessionId');
- if (!sessionId) {
- sessionId = bitcore.SecureRandom.getRandomBuffer(8).toString('hex');
- this.sessionStorage.setItem('sessionId', sessionId);
- }
- return sessionId;
-};
-
-Storage.prototype._key = function(walletId, k) {
- return walletId + '::' + k;
-};
-// get value by key
-Storage.prototype.get = function(walletId, k) {
- var ret = this._read(this._key(walletId, k));
- return ret;
-};
-
-// set value for key
-Storage.prototype.set = function(walletId, k, v) {
- this._write(this._key(walletId, k), v);
-};
-
-// remove value for key
-Storage.prototype.remove = function(walletId, k) {
- this.removeGlobal(this._key(walletId, k));
-};
-
-Storage.prototype.setName = function(walletId, name) {
- this.setGlobal('nameFor::' + walletId, name);
-};
-
-Storage.prototype.getName = function(walletId) {
- var ret = this.getGlobal('nameFor::' + walletId);
- return ret;
-};
-
-Storage.prototype.getWalletIds = function() {
- var walletIds = [];
- var uniq = {};
-
- for (var i = 0; i < this.localStorage.length; i++) {
- var key = this.localStorage.key(i);
- var split = key.split('::');
- if (split.length == 2) {
- var walletId = split[0];
-
- if (!walletId || walletId === 'nameFor' || walletId === 'lock')
- continue;
-
- if (typeof uniq[walletId] === 'undefined') {
- walletIds.push(walletId);
- uniq[walletId] = 1;
- }
- }
- }
- return walletIds;
-};
-
-Storage.prototype.getWallets = function() {
- var wallets = [];
- var ids = this.getWalletIds();
-
- for (var i in ids) {
- wallets.push({
- id: ids[i],
- name: this.getName(ids[i]),
- });
- }
- return wallets;
-};
-
-Storage.prototype.deleteWallet = function(walletId) {
- var toDelete = {};
- toDelete['nameFor::' + walletId] = 1;
-
- for (var i = 0; i < this.localStorage.length; i++) {
- var key = this.localStorage.key(i);
- var split = key.split('::');
- if (split.length == 2 && split[0] === walletId) {
- toDelete[key] = 1;
- }
- }
- for (var i in toDelete) {
- this.removeGlobal(i);
- }
-};
-
-Storage.prototype.setLastOpened = function(walletId) {
- this.setGlobal('lastOpened', walletId);
-}
-
-Storage.prototype.getLastOpened = function() {
- return this.getGlobal('lastOpened');
-}
-
-//obj contains keys to be set
-Storage.prototype.setFromObj = function(walletId, obj) {
- for (var k in obj) {
- this.set(walletId, k, obj[k]);
- }
- this.setName(walletId, obj.opts.name);
-};
-
-// remove all values
-Storage.prototype.clearAll = function() {
- this.localStorage.clear();
-};
-
-Storage.prototype.import = function(base64) {
- var decryptedStr = this._decrypt(base64);
- return JSON.parse(decryptedStr);
-};
-
-Storage.prototype.export = function(obj) {
- var string = JSON.stringify(obj);
- return this._encrypt(string);
-};
-
-module.exports = Storage;
diff --git a/js/routes.js b/js/routes.js
index 02c036251..6f01ffc23 100644
--- a/js/routes.js
+++ b/js/routes.js
@@ -76,8 +76,8 @@ angular
// IDLE timeout
var timeout = config.wallet.idleDurationMin * 60 || 300;
$idleProvider.idleDuration(timeout); // in seconds
- $idleProvider.warningDuration(20); // in seconds
- $keepaliveProvider.interval(2); // in seconds
+ $idleProvider.warningDuration(40); // in seconds
+ $keepaliveProvider.interval(30); // in seconds
})
.run(function($rootScope, $location, $idle, gettextCatalog) {
gettextCatalog.currentLanguage = config.defaultLanguage;
diff --git a/js/services/backupService.js b/js/services/backupService.js
index dc915d632..74ca8a7f0 100644
--- a/js/services/backupService.js
+++ b/js/services/backupService.js
@@ -9,14 +9,17 @@ BackupService.prototype.getName = function(wallet) {
return (wallet.name ? (wallet.name + '-') : '') + wallet.id;
};
+BackupService.prototype.getCopayer = function(wallet) {
+ return wallet.totalCopayers > 1 ? wallet.getMyCopayerNickname() : '';
+};
+
BackupService.prototype.download = function(wallet) {
var ew = wallet.toEncryptedObj();
- var partial = !wallet.publicKeyRing.isComplete();
- var walletName = this.getName(wallet) + (partial ? '-Partial' : '');
- var filename = walletName + '-keybackup.json.aes';
+ var walletName = this.getName(wallet);
+ var copayerName = this.getCopayer(wallet);
+ var filename = (copayerName ? copayerName + '-' : '') + walletName + '-keybackup.json.aes';
- var notify = partial ? 'Partial backup created' : 'Backup created';
- this.notifications.success(notify, 'Encrypted backup file saved.');
+ this.notifications.success('Backup created', 'Encrypted backup file saved');
var blob = new Blob([ew], {
type: 'text/plain;charset=utf-8'
});
@@ -32,9 +35,8 @@ BackupService.prototype.download = function(wallet) {
// throw an email intent if we are in the mobile version
if (window.cordova) {
var name = wallet.name ? wallet.name + ' ' : '';
- var partial = partial ? 'Partial ' : '';
return window.plugin.email.open({
- subject: 'Copay - ' + name + 'Wallet ' + partial + 'Backup',
+ subject: 'Copay - ' + name + 'Wallet ' + 'Backup',
body: 'Here is the encrypted backup of the wallet ' + wallet.id,
attachments: ['base64:' + filename + '//' + btoa(ew)]
});
diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js
index a11e19238..5200b4e1c 100644
--- a/js/services/controllerUtils.js
+++ b/js/services/controllerUtils.js
@@ -2,7 +2,7 @@
var bitcore = require('bitcore');
angular.module('copayApp.services')
- .factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, uriHandler, rateService) {
+ .factory('controllerUtils', function($rootScope, $sce, $location, $filter, notification, $timeout, uriHandler, rateService) {
var root = {};
root.redirIfLogged = function() {
@@ -50,16 +50,18 @@ angular.module('copayApp.services')
$scope.loading = false;
});
-
w.on('corrupt', function(peerId) {
- notification.error('Error', 'Received corrupt message from ' + peerId);
+ notification.error('Error', $filter('translate')('Received corrupt message from ') + peerId);
});
w.on('ready', function(myPeerID) {
$rootScope.wallet = w;
- if ($rootScope.pendingPayment) {
- $location.path('send');
- } else {
- $location.path('receive');
+ if ($rootScope.initialConnection) {
+ $rootScope.initialConnection = false;
+ if ($rootScope.pendingPayment) {
+ $location.path('send');
+ } else {
+ $location.path('receive');
+ }
}
});
@@ -70,8 +72,10 @@ angular.module('copayApp.services')
}
});
- w.on('tx', function(address) {
- notification.funds('Funds received!', address);
+ w.on('tx', function(address, isChange) {
+ if (!isChange) {
+ notification.funds('Funds received!', address);
+ }
root.updateBalance(function() {
$rootScope.$digest();
});
@@ -108,17 +112,17 @@ angular.module('copayApp.services')
}, 3000);
});
w.on('txProposalEvent', function(e) {
-
+
var user = w.publicKeyRing.nicknameForCopayer(e.cId);
switch (e.type) {
case 'signed':
- notification.info('Transaction Update', 'A transaction was signed by ' + user);
+ notification.info('Transaction Update', $filter('translate')('A transaction was signed by') + ' ' + user);
break;
case 'rejected':
- notification.info('Transaction Update', 'A transaction was rejected by ' + user);
+ notification.info('Transaction Update', $filter('translate')('A transaction was rejected by') + ' ' + user);
break;
case 'corrupt':
- notification.error('Transaction Error', 'Received corrupt transaction from ' + user);
+ notification.error('Transaction Error', $filter('translate')('Received corrupt transaction from') + ' ' + user);
break;
}
});
@@ -138,12 +142,13 @@ angular.module('copayApp.services')
uriHandler.register();
$rootScope.unitName = config.unitName;
$rootScope.txAlertCount = 0;
+ $rootScope.initialConnection = true;
$rootScope.reconnecting = false;
$rootScope.isCollapsed = true;
$rootScope.$watch('txAlertCount', function(txAlertCount) {
if (txAlertCount && txAlertCount > 0) {
- notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount);
+ notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : $filter('translate')('You have') + ' ' + $rootScope.txAlertCount + ' ' + $filter('translate')('pending transaction proposals'), txAlertCount);
}
});
};
@@ -159,8 +164,10 @@ angular.module('copayApp.services')
// TODO movie this to wallet
root.updateAddressList = function() {
var w = $rootScope.wallet;
- if (w && w.isReady())
+ if (w && w.isReady()) {
+ w.subscribeToAddresses();
$rootScope.addrInfos = w.getAddressesInfo();
+ }
};
root.updateBalance = function(cb) {
@@ -176,7 +183,7 @@ angular.module('copayApp.services')
w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat) {
if (err) throw err;
- var satToUnit = 1 / config.unitToSatoshi;
+ var satToUnit = 1 / w.settings.unitToSatoshi;
var COIN = bitcore.util.COIN;
$rootScope.totalBalance = balanceSat * satToUnit;
@@ -196,11 +203,10 @@ angular.module('copayApp.services')
$rootScope.updatingBalance = false;
rateService.whenAvailable(function() {
- $rootScope.totalBalanceAlternative = rateService.toFiat(balanceSat, config.alternativeIsoCode);
- $rootScope.alternativeIsoCode = config.alternativeIsoCode;
- $rootScope.lockedBalanceAlternative = rateService.toFiat(balanceSat - safeBalanceSat, config.alternativeIsoCode);
-
-
+ $rootScope.totalBalanceAlternative = rateService.toFiat(balanceSat, w.settings.alternativeIsoCode);
+ $rootScope.alternativeIsoCode = w.settings.alternativeIsoCode;
+ $rootScope.lockedBalanceAlternative = rateService.toFiat(balanceSat - safeBalanceSat, w.settings.alternativeIsoCode);
+ $rootScope.alternativeConversionRate = rateService.toFiat(100000000, w.settings.alternativeIsoCode);
return cb ? cb() : null;
});
});
@@ -211,7 +217,7 @@ angular.module('copayApp.services')
if (!w) return;
opts = opts || $rootScope.txsOpts || {};
- var satToUnit = 1 / config.unitToSatoshi;
+ var satToUnit = 1 / w.settings.unitToSatoshi;
var myCopayerId = w.getMyCopayerId();
var pendingForUs = 0;
var inT = w.getTxProposals().sort(function(t1, t2) {
@@ -235,7 +241,7 @@ angular.module('copayApp.services')
var tx = i.builder.build();
var outs = [];
tx.outs.forEach(function(o) {
- var addr = bitcore.Address.fromScriptPubKey(o.getScript(), config.networkName)[0].toString();
+ var addr = bitcore.Address.fromScriptPubKey(o.getScript(), w.getNetworkName())[0].toString();
if (!w.addressIsOwn(addr, {
excludeMain: true
})) {
diff --git a/js/services/pluginManager.js b/js/services/pluginManager.js
new file mode 100644
index 000000000..1b85d15a6
--- /dev/null
+++ b/js/services/pluginManager.js
@@ -0,0 +1,18 @@
+'use strict';
+
+angular.module('copayApp.services').factory('pluginManager', function(angularLoad){
+ var pm = new copay.PluginManager(config);
+ var scripts = pm.scripts;
+
+ for(var ii in scripts){
+ var src = scripts[ii].src;
+
+ console.log('\tLoading ',src); //TODO
+ angularLoad.loadScript(src)
+ .then(scripts[ii].then || null)
+ .catch(function() {
+ throw new Error('Loading ' + src);
+ })
+ }
+ return pm;
+});
diff --git a/js/services/rate.js b/js/services/rate.js
index 0c28ae046..68f96c313 100644
--- a/js/services/rate.js
+++ b/js/services/rate.js
@@ -4,6 +4,7 @@ var RateService = function(request) {
this.isAvailable = false;
this.UNAVAILABLE_ERROR = 'Service is not available - check for service.isAvailable or use service.whenAvailable';
this.SAT_TO_BTC = 1 / 1e8;
+ this.BTC_TO_SAT = 1e8;
var MINS_IN_HOUR = 60;
var MILLIS_IN_SECOND = 1000;
var rateServiceConfig = config.rate;
@@ -62,7 +63,7 @@ RateService.prototype.fromFiat = function(amount, code) {
if (!this.isAvailable) {
throw new Error(this.UNAVAILABLE_ERROR);
}
- return amount / this.rates[code] / this.SAT_TO_BTC;
+ return amount / this.rates[code] * this.BTC_TO_SAT;
};
RateService.prototype.listAlternatives = function() {
diff --git a/js/services/walletFactory.js b/js/services/walletFactory.js
index 16580af9d..a592470d2 100644
--- a/js/services/walletFactory.js
+++ b/js/services/walletFactory.js
@@ -1,3 +1,5 @@
'use strict';
+angular.module('copayApp.services').factory('walletFactory', function(pluginManager){
+ return new copay.WalletFactory(config, copay.version, pluginManager);
+});
-angular.module('copayApp.services').value('walletFactory', new copay.WalletFactory(config, copay.version));
diff --git a/jsdoc.conf.json b/jsdoc.conf.json
new file mode 100644
index 000000000..5c5c11650
--- /dev/null
+++ b/jsdoc.conf.json
@@ -0,0 +1,18 @@
+{
+ "tags": {
+ "allowUnknownTags": true
+ },
+ "source": {
+ "includePattern": ".+\\.js(doc)?$",
+ "excludePattern": "(^|\\/|\\\\)_"
+ },
+ "plugins": [],
+ "templates": {
+ "cleverLinks": false,
+ "monospaceLinks": false,
+ "default": {
+ "outputSourceFiles": true
+ },
+ "theme": "flatly"
+ }
+}
diff --git a/karma.conf.js b/karma.conf.js
index e9c89b577..06b14adcf 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -28,6 +28,7 @@ module.exports = function(config) {
'lib/angular-route/angular-route.min.js',
'lib/angular-foundation/mm-foundation.min.js',
'lib/angular-foundation/mm-foundation-tpls.min.js',
+ 'lib/angular-load/angular-load.min.js',
'lib/angular-gettext/dist/angular-gettext.min.js',
'lib/inherits/inherits.js',
'lib/bitcore.js',
@@ -60,6 +61,7 @@ module.exports = function(config) {
'test/mocks/FakeWallet.js',
'test/mocks/FakeBlockchainSocket.js',
'test/mocks/FakePayProServer.js',
+ 'test/mocks/FakeLocalStorage.js',
'test/mocha.conf.js',
diff --git a/package.json b/package.json
index 482302d54..65773c273 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"bugs": {
"url": "https://github.com/bitpay/copay/issues"
},
- "version": "0.5.0",
+ "version": "0.6.1",
"dependencies": {
"browser-request": "^0.3.2",
"inherits": "^2.0.1",
@@ -42,6 +42,7 @@
],
"devDependencies": {
"async": "0.9.0",
+ "bitcore": "0.1.36",
"blanket": "1.1.6",
"browser-pack": "2.0.1",
"browser-request": "0.3.2",
@@ -55,17 +56,18 @@
"express": "4.0.0",
"github-releases": "0.2.0",
"grunt": "^0.4.5",
+ "grunt-angular-gettext": "^0.2.15",
"grunt-browserify": "2.0.8",
"grunt-cli": "^0.1.13",
"grunt-contrib-concat": "0.5.0",
"grunt-contrib-cssmin": "0.10.0",
"grunt-contrib-uglify": "^0.5.1",
"grunt-contrib-watch": "0.5.3",
+ "grunt-jsdoc": "^0.5.7",
"grunt-markdown": "0.5.0",
- "bitcore": "0.1.36",
"grunt-mocha-test": "0.8.2",
+ "grunt-release": "^0.7.0",
"grunt-shell": "0.6.4",
- "grunt-angular-gettext": "^0.2.15",
"istanbul": "0.2.10",
"karma": "0.12.9",
"karma-chrome-launcher": "0.1.3",
diff --git a/plugins/GoogleDrive.js b/plugins/GoogleDrive.js
new file mode 100644
index 000000000..4c8783ac6
--- /dev/null
+++ b/plugins/GoogleDrive.js
@@ -0,0 +1,322 @@
+'use strict';
+
+var preconditions = require('preconditions').singleton();
+var loaded = 0;
+var SCOPES = 'https://www.googleapis.com/auth/drive';
+var log = require('../js/log');
+
+function GoogleDrive(config) {
+ preconditions.checkArgument(config && config.clientId, 'No clientId at GoogleDrive config');
+
+ this.clientId = config.clientId;
+ this.home = config.home || 'copay';
+ this.idCache = {};
+
+ this.type = 'STORAGE';
+
+ this.scripts = [{
+ then: this.initLoaded.bind(this),
+ src: 'https://apis.google.com/js/client.js?onload=InitGoogleDrive'
+ }];
+
+ this.isReady = false;
+ this.useImmediate = true;
+ this.ts = 100;
+};
+
+window.InitGoogleDrive = function() {
+ log.debug('googleDrive loadeded'); //TODO
+ loaded = 1;
+};
+
+GoogleDrive.prototype.init = function() {};
+
+/**
+ * Called when the client library is loaded to start the auth flow.
+ */
+GoogleDrive.prototype.initLoaded = function() {
+ if (!loaded) {
+ window.setTimeout(this.initLoaded.bind(this), 500);
+ } else {
+ window.setTimeout(this.checkAuth.bind(this), 1);
+ }
+}
+
+/**
+ * Check if the current user has authorized the application.
+ */
+GoogleDrive.prototype.checkAuth = function() {
+
+ log.debug('Google Drive: Checking Auth');
+ gapi.auth.authorize({
+ 'client_id': this.clientId,
+ 'scope': SCOPES,
+ 'immediate': this.useImmediate,
+ },
+ this.handleAuthResult.bind(this));
+};
+
+/**
+ * Called when authorization server replies.
+ */
+GoogleDrive.prototype.handleAuthResult = function(authResult) {
+ var self = this;
+ log.debug('Google Drive: authResult', authResult); //TODO
+
+ if (authResult.error) {
+ if (authResult.error) {
+ self.useImmediate = false;
+ return this.checkAuth();
+ };
+ throw new Error(authResult.error);
+ }
+
+ gapi.client.load('drive', 'v2', function() {
+ self.isReady = true;
+ });
+}
+
+GoogleDrive.prototype.checkReady = function() {
+ if (!this.isReady)
+ throw new Error('goggle drive is not ready!');
+};
+
+GoogleDrive.prototype._httpGet = function(theUrl) {
+ var accessToken = gapi.auth.getToken().access_token;
+ var xmlHttp = null;
+
+ xmlHttp = new XMLHttpRequest();
+ xmlHttp.open("GET", theUrl, false);
+ xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
+ xmlHttp.send(null);
+ return xmlHttp.responseText;
+}
+
+GoogleDrive.prototype.getItem = function(k, cb) {
+ //console.log('[googleDrive.js.95:getItem:]', k); //TODO
+ var self = this;
+
+ self.checkReady();
+ self._idForName(k, function(kId) {
+ // console.log('[googleDrive.js.89:kId:]', kId); //TODO
+ if (!kId)
+ return cb(null);
+
+
+ var args = {
+ 'path': '/drive/v2/files/' + kId,
+ 'method': 'GET',
+ };
+ // console.log('[googleDrive.js.95:args:]', args); //TODO
+
+ var request = gapi.client.request(args);
+ request.execute(function(res) {
+ // console.log('[googleDrive.js.175:res:]', res); //TODO
+ if (!res || !res.downloadUrl)
+ return cb(null);
+
+ return cb(self._httpGet(res.downloadUrl));
+ });
+
+ });
+};
+
+GoogleDrive.prototype.setItem = function(k, v, cb) {
+ // console.log('[googleDrive.js.111:setItem:]', k, v); //TODO
+ var self = this;
+
+ self.checkReady();
+ self._idForName(this.home, function(parentId) {
+ preconditions.checkState(parentId);
+ // console.log('[googleDrive.js.118:parentId:]', parentId); //TODO
+ self._idForName(k, function(kId) {
+
+ // console.log('[googleDrive.js.105]', parentId, kId); //TODO
+
+
+ var boundary = '-------314159265358979323846';
+ var delimiter = "\r\n--" + boundary + "\r\n";
+ var close_delim = "\r\n--" + boundary + "--";
+
+ var metadata = {
+ 'title': k,
+ 'mimeType': 'application/octet-stream',
+ 'parents': [{
+ 'id': parentId
+ }],
+ };
+
+ var base64Data = btoa(v);
+ var multipartRequestBody =
+ delimiter +
+ 'Content-Type: application/json\r\n\r\n' +
+ JSON.stringify(metadata) +
+ delimiter +
+ 'Content-Type: application/octet-stream \r\n' +
+ 'Content-Transfer-Encoding: base64\r\n' +
+ '\r\n' +
+ base64Data +
+ close_delim;
+
+ var args = {
+ 'path': '/upload/drive/v2/files' + (kId ? '/' + kId : ''),
+ 'method': kId ? 'PUT' : 'POST',
+ 'params': {
+ 'uploadType': 'multipart',
+ },
+ 'headers': {
+ 'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
+ },
+ 'body': multipartRequestBody
+ }
+ // console.log('[googleDrive.js.148:args:]', args); //TODO
+
+ var request = gapi.client.request(args);
+ request.execute(function(ret) {
+ return cb(ret.kind === 'drive#file' ? null : new Error('error saving file on drive'));
+ });
+ });
+ });
+};
+
+GoogleDrive.prototype.removeItem = function(k, cb) {
+ var self = this;
+
+ self.checkReady();
+ self._idForName(this.home, function(parentId) {
+ preconditions.checkState(parentId);
+ self._idForName(k, function(kId) {
+
+ var args = {
+ 'path': '/drive/v2/files/' + kId,
+ 'method': 'DELETE',
+ };
+ var request = gapi.client.request(args);
+ request.execute(function() {
+ if (cb)
+ cb();
+ });
+ });
+ });
+};
+
+GoogleDrive.prototype.clear = function() {
+ this.checkReady();
+ throw new Error('clear not implemented');
+};
+
+
+GoogleDrive.prototype._mkdir = function(cb) {
+ preconditions.checkArgument(cb);
+ var self = this;
+
+ log.debug('Creating drive folder ' + this.home);
+
+ var request = gapi.client.request({
+ 'path': '/drive/v2/files',
+ 'method': 'POST',
+ 'body': JSON.stringify({
+ 'title': this.home,
+ 'mimeType': "application/vnd.google-apps.folder",
+ }),
+ });
+ request.execute(function() {
+ self._idForName(self.home, cb);
+ });
+};
+
+
+GoogleDrive.prototype._idForName = function(name, cb) {
+ // console.log('[googleDrive.js.199:_idForName:]', name); //TODO
+ preconditions.checkArgument(name);
+ preconditions.checkArgument(cb);
+ var self = this;
+
+ if (!self.isReady) {
+ log.debug('Waiting for Google Drive');
+ self.ts = self.ts * 1.5;
+ return setTimeout(self._idForName.bind(self, name, cb), self.ts);
+ }
+
+ if (self.idCache[name]) {
+ // console.log('[googleDrive.js.212:] FROM CACHE', name, self.idCache[name]); //TODO
+ return cb(self.idCache[name]);
+ }
+
+ log.debug('GoogleDrive Querying for: ', name); //TODO
+ var args;
+
+ var idParent = name == this.home ? 'root' : self.idCache[this.home];
+
+ if (!idParent) {
+ return self._mkdir(function() {
+ self._idForName(name, cb);
+ });
+ }
+ // console.log('[googleDrive.js.177:idParent:]', idParent); //TODO
+ preconditions.checkState(idParent);
+
+ args = {
+ 'path': '/drive/v2/files',
+ 'method': 'GET',
+ 'params': {
+ 'q': "title='" + name + "' and trashed = false and '" + idParent + "' in parents",
+ }
+ };
+
+ var request = gapi.client.request(args);
+ request.execute(function(res) {
+ var i = res.items && res.items[0] ? res.items[0].id : false;
+ if (i)
+ self.idCache[name] = i;
+ // console.log('[googleDrive.js.238] CACHING ' + name + ':' + i); //TODO
+ return cb(self.idCache[name]);
+ });
+};
+
+GoogleDrive.prototype._checkHomeDir = function(cb) {
+ var self = this;
+
+ this._idForName(this.home, function(homeId) {
+ if (!homeId)
+ return self._mkdir(cb);
+
+ return cb(homeId);
+ });
+};
+
+GoogleDrive.prototype.allKeys = function(cb) {
+ var self = this;
+
+ this._checkHomeDir(function(homeId) {
+ preconditions.checkState(homeId);
+
+ var request = gapi.client.request({
+ 'path': '/drive/v2/files',
+ 'method': 'GET',
+ 'params': {
+ 'q': "'" + homeId + "' in parents and trashed = false",
+ 'fields': 'items(id,title)'
+ },
+ });
+ request.execute(function(res) {
+ // console.log('[googleDrive.js.152:res:]', res); //TODO
+ if (res.error)
+ throw new Error(res.error.message);
+
+ var ret = [];
+ for (var ii in res.items) {
+ ret.push(res.items[ii].title);
+ }
+ return cb(ret);
+ });
+ });
+};
+
+GoogleDrive.prototype.key = function(k) {
+ var v = localStorage.key(k);
+ return v;
+};
+
+
+module.exports = GoogleDrive;
diff --git a/plugins/LocalStorage.js b/plugins/LocalStorage.js
new file mode 100644
index 000000000..0035fc12b
--- /dev/null
+++ b/plugins/LocalStorage.js
@@ -0,0 +1,41 @@
+'use strict';
+
+function LocalStorage() {
+ this.type = 'STORAGE';
+};
+
+LocalStorage.prototype.init = function() {
+};
+
+
+LocalStorage.prototype.getItem = function(k,cb) {
+ return cb(localStorage.getItem(k));
+};
+
+LocalStorage.prototype.setItem = function(k,v,cb) {
+ localStorage.setItem(k,v);
+ return cb();
+};
+
+LocalStorage.prototype.removeItem = function(k,cb) {
+ localStorage.removeItem(k);
+ return cb();
+};
+
+LocalStorage.prototype.clear = function(cb) {
+ localStorage.clear();
+ return cb();
+};
+
+LocalStorage.prototype.allKeys = function(cb) {
+ var l = localStorage.length;
+ var ret = [];
+
+ for(var i=0; iALL peers of a wallet, to prevent the old proposals to be resynced "
-"again.\n"
-" "
+"ALL Transactions Proposals will be discarted. This needs to be done on "
+"ALL peers of a wallet, to prevent the old proposals to be resynced "
+"again."
msgstr ""
"TODAS las Propuestas de Transacciones serán descartadas. Es necesario que lo "
-"hagan TODOS los compañeros del monedero, para prevenir que las viejas "
-"propuestas sean re sincronizadas de nuevo.\n"
-" "
+"hagan TODOS los compañeros del monedero, para prevenir que las viejas "
+"propuestas sean re sincronizadas de nuevo."
-#: views/modals/address-book.html
-msgid "Add Address"
-msgstr "Agregar Dirección"
-
-#: views/modals/address-book.html
-msgid "Add Address Book Entry"
-msgstr "Nueva entrada"
-
-#: views/send.html
-msgid "Add New Entry"
-msgstr "Nueva Entrada"
+#: views/send.html views/modals/address-book.html
+msgid "Add"
+msgstr "Agregar"
#: views/send.html views/modals/address-book.html
msgid "Address"
msgstr "Dirección"
-#: views/send.html
+#: views/send.html views/modals/address-book.html
msgid "Address Book"
msgstr "Libreta de Direcciones"
@@ -52,7 +50,7 @@ msgstr "Libreta de Direcciones"
msgid "Addresses"
msgstr "Direcciones"
-#: views/settings.html
+#: views/more.html
msgid "Alternative Currency"
msgstr "Moneda Alternativa"
@@ -81,6 +79,10 @@ msgstr "Volver"
msgid "Backup"
msgstr "Copia de Seguridad"
+#: views/dummy-translations.html
+msgid "Backup created"
+msgstr "Copia de Seguridad creada"
+
#: views/copayers.html
msgid "Backup wallet"
msgstr "Hacer copia de seguridad"
@@ -93,9 +95,9 @@ msgstr "Balance"
msgid "Balance locked in pending transaction proposals"
msgstr "Balance bloqueado en las propuestas de transacción pendientes"
-#: views/settings.html
-msgid "Bitcoin Network"
-msgstr "Red Bitcoin"
+#: views/send.html
+msgid "Bitcoin address"
+msgstr "Dirección bitcoin"
#: views/includes/transaction.html
msgid "Broadcast Transaction"
@@ -113,11 +115,15 @@ msgstr "Cancelar"
msgid "Certificate:"
msgstr "Certificado:"
+#: views/create.html
+msgid "Choose a password"
+msgstr "Escribe una contraseña"
+
#: views/import.html
msgid "Choose backup file from your computer"
msgstr "Seleccione el archivo backup de su computadora"
-#: views/create.html views/join.html
+#: views/join.html
msgid "Choose your password"
msgstr "Escribe tu contraseña"
@@ -141,10 +147,21 @@ msgstr "Continuar de todas maneras"
msgid "Copayers"
msgstr "Compañeros"
+#: views/dummy-translations.html
+msgid "Copied to clipboard"
+msgstr "Copiado al portapapeles"
+
#: views/modals/qr-address.html
msgid "Copy to clipboard"
msgstr "Copiar al portapapeles"
+#: views/dummy-translations.html
+msgid ""
+"Could not connect to the Insight server. Check your settings and network "
+"configuration"
+msgstr ""
+"No se pudo conectar con el servidor Insight. Verifica la configuración de red"
+
#: views/home.html
msgid "Create a new wallet"
msgstr "Crear un nuevo monedero"
@@ -161,6 +178,12 @@ msgstr "Crear nuevo monedero"
msgid "Create {{requiredCopayers}}-of-{{totalCopayers}} wallet"
msgstr "Crea monedero {{requiredCopayers}}-de-{{totalCopayers}}"
+#: views/copayers.html
+msgid "Creating and storing a backup will allow you to recover wallet funds"
+msgstr ""
+"Crear y guardar una copia de seguridad le permitirá recuperar el dinero de "
+"su monedero"
+
#: views/create.html
msgid "Creating wallet..."
msgstr "Creando monedero..."
@@ -181,10 +204,6 @@ msgstr "Eliminar"
msgid "Delete Wallet"
msgstr "Borrar Monedero"
-#: views/copayers.html
-msgid "Delete wallet"
-msgstr "Borrar monedero"
-
#: views/copayers.html
msgid "Download Backup"
msgstr "Descargar Copia de Seguridad"
@@ -193,28 +212,48 @@ msgstr "Descargar Copia de Seguridad"
msgid "Download File"
msgstr "Descargar Archivo"
-#: views/copayers.html
-msgid "Download seed backup"
-msgstr "Descargar copia de seguridad"
-
#: views/send.html
msgid "Empty. Create an alias for your addresses"
msgstr "VacÃo. Crea una etiqueta para tus direcciones"
+#: views/dummy-translations.html
+msgid "Encrypted backup file saved"
+msgstr "Archivo de copia de seguridad encriptado guardado"
+
+#: views/dummy-translations.html
+msgid "Error updating indexes:"
+msgstr "Error al actualizar Ãndices:"
+
#: views/create.html
msgid "Family vacation funds"
msgstr "Fondos para vacaciones en familia"
+#: views/dummy-translations.html
+msgid "Fatal error connecting to Insight server"
+msgstr "Error fatal al conectar con el servidor Insight"
+
#: views/transactions.html views/includes/transaction.html
msgid "Fee"
msgstr "Tasa"
+#: views/dummy-translations.html
+msgid "Finished"
+msgstr "Finalizado"
+
+#: views/dummy-translations.html
+msgid "Form Error"
+msgstr "Error en formulario"
+
+#: views/dummy-translations.html
+msgid "Funds received!"
+msgstr "¡Fondos recibidos!"
+
#: views/join.html views/send.html
msgid "Get QR code"
msgstr "Obtener código QR"
-#: views/copayers.html views/create.html views/import.html views/join.html
-#: views/more.html views/transactions.html
+#: views/create.html views/import.html views/join.html views/more.html
+#: views/transactions.html
msgid "Hide"
msgstr "Ocultar"
@@ -230,29 +269,41 @@ msgstr ""
"Si todos los fondos fueron removidos de tu monedero y no deseas tener los "
"datos guardados en tu computadora, puedes eliminar tu monedero."
-#: views/home.html
+#: views/dummy-translations.html views/home.html
msgid "Import a backup"
-msgstr "Importar backup"
+msgstr "Importar una copia de seguridad"
#: views/import.html
msgid "Import backup"
msgstr "Importar copia de seguridad"
+#: views/dummy-translations.html
+msgid "Importing wallet - Reading backup..."
+msgstr "Importando monedero - Leyendo archivo..."
+
+#: views/dummy-translations.html
+msgid "Importing wallet - Setting things up..."
+msgstr "Importando monedero - Configurando..."
+
+#: views/dummy-translations.html
+msgid "Importing wallet - We are almost there..."
+msgstr "Importando monedero - Finalizando..."
+
#: views/send.html
msgid "Including fee of"
msgstr "Incluye tasa de"
#: views/settings.html
msgid "Insight API server"
-msgstr "Servidor API Insight"
+msgstr "Servidor de API Insight"
#: views/settings.html
msgid ""
-"Insight API server is open-source software. You can run your own instance, "
+"Insight API server is open-source software. You can run your own instances, "
"check Insight API Homepage"
"a>"
msgstr ""
-"Servidor API de insight es un software código-abierto. Puedes correr tu "
+"Servidor de API insight es un software código-abierto. Puedes correr tu "
"propia instancia en Insight "
"API Homepage"
@@ -260,13 +311,17 @@ msgstr ""
msgid "Insufficient funds"
msgstr "Fondos insuficientes"
+#: views/dummy-translations.html
+msgid "It's important that you update your wallet at https://copay.io"
+msgstr "Es importante que actualices tu monedero en https://copay.io"
+
#: views/more.html
msgid ""
"It's important to backup your wallet so that you can recover it in case of "
"disaster"
msgstr ""
"Es importante hacer copia de seguridad de tu monedero para que puedas "
-"recuperarlo en caso de pérdidas"
+"recuperarlo en caso de pérdidas de datos de tu computadora"
#: views/join.html
msgid "Join"
@@ -296,6 +351,14 @@ msgstr "Dejar mensaje privado a tus compañeros"
msgid "Locked"
msgstr "Bloqueado"
+#: views/dummy-translations.html
+msgid "Login Required"
+msgstr "Inicio de Sesión Requerido"
+
+#: views/includes/sidebar.html
+msgid "Manual Update"
+msgstr "Actualización Manual"
+
#: views/more.html
msgid "Master Private Key"
msgstr "Master Private Key"
@@ -316,20 +379,22 @@ msgstr "Nombre"
msgid "Network Error. Attempting to reconnect..."
msgstr "Error de Red. Intentando reconectar..."
-#: views/settings.html
-msgid ""
-"Network has been fixed to {{networkName}} in this setup. "
-"See copay.io for options to use Copay on "
-"both livenet and testnet."
-msgstr ""
-"La red fue fijada a {{networkName}} para esta "
-"configuración. Ver copay.io para más "
-"opciones de uso de Copay en livenet y testnet."
+#: views/dummy-translations.html
+msgid "Networking Error"
+msgstr "Error de Red"
+
+#: views/dummy-translations.html
+msgid "New Transaction"
+msgstr "Nueva Transacción"
#: views/copayers.html
msgid "New Wallet Created"
msgstr "Nuevo Monedero Creado"
+#: views/dummy-translations.html
+msgid "New entry has been created"
+msgstr "Nueva entrada fue creada"
+
#: views/create.html
msgid "Next"
msgstr "Siguiente"
@@ -389,6 +454,10 @@ msgstr "Página no encontrada"
msgid "Password"
msgstr "Contraseña"
+#: views/create.html views/join.html
+msgid "Passwords must match"
+msgstr "Las contraseñas deben coincidir"
+
#: views/join.html
msgid "Paste wallet secret here"
msgstr "Pegar código secreto del monedero aquÃ"
@@ -399,25 +468,39 @@ msgstr "Vencimiento de Pago:"
#: views/more.html
msgid ""
-"Pending Transactions Proposals will be discarted. This need to be done on "
-"ALL peers of a wallet, to prevent the old proposals to be resynced "
-"again.\n"
-" "
+"Pending Transactions Proposals will be discarted. This needs to be done on "
+"ALL peers of a wallet, to prevent the old proposals to be resynced "
+"again."
msgstr ""
"Las Propuestas de Transacciones Pendientes serán descartadas. Esto es "
"necesario hacerlo con TODOS los compañeros del monedero, para "
-"prevenir que viejas propuestas sean re sincronizadas de nuevo.\n"
-" "
+"prevenir que viejas propuestas sean re sincronizadas de nuevo."
-#: views/settings.html
-msgid "Port"
-msgstr "Puerto"
+#: views/dummy-translations.html
+msgid "Please complete required fields"
+msgstr "Por favor complete los campos requeridos"
+
+#: views/dummy-translations.html
+msgid "Please enter the required fields"
+msgstr "Por favor ingrese los campos requeridos"
+
+#: views/dummy-translations.html
+msgid "Please open wallet to complete payment"
+msgstr "Por favor abrir un monedero para completar el pago"
+
+#: views/dummy-translations.html
+msgid "Please update your wallet at https://copay.io"
+msgstr "Por favor actualiza tu monedero de https://copay.io"
+
+#: views/dummy-translations.html
+msgid "Please, select your backup file"
+msgstr "Por favor, selecciona el archivo de copia de seguridad"
#: views/uri-payment.html
msgid "Preparing payment..."
msgstr "Preparando pago..."
-#: views/create.html views/join.html
+#: views/join.html
msgid "Private Key (Hex)"
msgstr "Clave Privada (Hex)"
@@ -449,6 +532,14 @@ msgstr "Listo"
msgid "Receive"
msgstr "Recibir"
+#: views/dummy-translations.html
+msgid "Received corrupt message from"
+msgstr "Se recibió un mensaje corrupto de"
+
+#: views/dummy-translations.html
+msgid "Received corrupt transaction from"
+msgstr "Se recibió una transacción corrupta de"
+
#: views/includes/transaction.html
msgid "Reject"
msgstr "Rechazar"
@@ -457,11 +548,11 @@ msgstr "Rechazar"
msgid "Repeat password"
msgstr "Repite la contraseña"
-#: views/create.html views/import.html views/join.html
+#: views/import.html views/join.html views/modals/address-book.html
msgid "Required"
msgstr "Requerido"
-#: views/settings.html
+#: views/more.html views/settings.html
msgid "Save"
msgstr "Guardar"
@@ -473,6 +564,10 @@ msgstr "Explorar"
msgid "Scan Wallet Addresses"
msgstr "Explorar Direcciones del Monedero"
+#: views/dummy-translations.html
+msgid "Scaning for transactions"
+msgstr "Explorando transacciones"
+
#: views/import.html
msgid "Select a backup file"
msgstr "Seleccionar el archivo de copia de seguridad"
@@ -485,6 +580,10 @@ msgstr "Seleccione las firmas requeridas (*)"
msgid "Select total number of copayers (*)"
msgstr "Seleccione el total de compañeros (*)"
+#: views/dummy-translations.html
+msgid "Send"
+msgstr "Enviar"
+
#: views/send.html
msgid "Send Proposals"
msgstr "Enviar Propuestas"
@@ -505,6 +604,18 @@ msgstr "Enviado"
msgid "Server Says:"
msgstr "Mensaje del Servidor:"
+#: views/dummy-translations.html
+msgid "Session closed"
+msgstr "Sesión cerrada"
+
+#: views/dummy-translations.html
+msgid "Session closed because a long time of inactivity"
+msgstr "La sesión fue cerrada por mucho tiempo de inactividad"
+
+#: views/dummy-translations.html
+msgid "Session will be closed"
+msgstr "La sesión se cerrará"
+
#: views/home.html views/more.html
msgid "Settings"
msgstr "Configuración"
@@ -513,8 +624,8 @@ msgstr "Configuración"
msgid "Share this secret with your other copayers"
msgstr "Compartir el código secreto con tus otros compañeros"
-#: views/copayers.html views/create.html views/import.html views/join.html
-#: views/more.html views/transactions.html
+#: views/create.html views/import.html views/join.html views/more.html
+#: views/transactions.html
msgid "Show"
msgstr "Mostrar"
@@ -530,6 +641,10 @@ msgstr "Ver menos"
msgid "Sign"
msgstr "Firmar"
+#: views/copayers.html
+msgid "Skip Backup"
+msgstr "Saltear Copia de Seguridad"
+
#: views/import.html
msgid "Skip public keys from peers"
msgstr "Ignorar claves pública de los compañeros"
@@ -542,6 +657,34 @@ msgstr "Ignorar propuestas de transacciones desde la Copia de Seguridad"
msgid "Skipping fields: {{skipFields}}"
msgstr "Saltear campos: {{skipFields}}"
+#: views/dummy-translations.html
+msgid "Success"
+msgstr "Listo"
+
+#: views/dummy-translations.html
+msgid "The balance is updated using the derived addresses"
+msgstr "El balance es actualizado utilizando direcciones derivadas"
+
+#: views/dummy-translations.html
+msgid "The secret string you entered is invalid"
+msgstr "La palabra secreta ingresada no es válida"
+
+#: views/dummy-translations.html
+msgid "The transaction proposal has been created"
+msgstr "La propuesta de transacción fue creada"
+
+#: views/dummy-translations.html
+msgid "The wallet is full"
+msgstr "El monedero esta completo"
+
+#: views/dummy-translations.html
+msgid "There was an error sending the transaction"
+msgstr "Hubo un error al enviar la transacción"
+
+#: views/dummy-translations.html
+msgid "There was an error signing the transaction"
+msgstr "Hubo un error al firmar la transacción"
+
#: views/warning.html
msgid "This wallet appears to be currently open."
msgstr "Este monedero parece estar actualmente abierto."
@@ -559,8 +702,8 @@ msgstr ""
"sincronización de direcciones a los demás compañeros conectados."
#: views/send.html
-msgid "To address"
-msgstr "Dirección"
+msgid "To"
+msgstr "A"
#: views/transactions.html
msgid "Total"
@@ -570,30 +713,62 @@ msgstr "Total"
msgid "Total amount for this transaction:"
msgstr "Cantidad total de esta transacción:"
+#: views/dummy-translations.html
+msgid "Transaction Error"
+msgstr "Error en Transacción"
+
#: views/includes/transaction.html
msgid "Transaction ID"
-msgstr "ID Transacción"
+msgstr "ID de Transacción"
#: views/transactions.html
msgid "Transaction Proposals"
msgstr "Propuestas de Transacción"
+#: views/dummy-translations.html
+msgid "Transaction Update"
+msgstr "Actualización de una Transacción"
+
+#: views/dummy-translations.html
+msgid "Transaction broadcasted"
+msgstr "Transacción transmitida"
+
#: views/includes/transaction.html
msgid "Transaction finally rejected"
+msgstr "Transacción finalmente rechazada"
+
+#: views/dummy-translations.html
+msgid "Transaction rejected"
msgstr "Transacción rechazada"
-#: views/settings.html
-msgid "Use SSL"
-msgstr "Usar SSL"
+#: views/dummy-translations.html
+msgid "Transactions Proposals Purged"
+msgstr "Propuestas de Transacciones Purgadas"
+
+#: views/dummy-translations.html
+msgid "Unable to send transaction proposal"
+msgstr "No se puede enviar propuesta de transacción"
+
+#: views/dummy-translations.html
+msgid "Updating balance"
+msgstr "Actualizando balance"
#: views/send.html
msgid "Use all funds"
msgstr "Todos los fondos"
+#: views/create.html
+msgid "Use test network"
+msgstr "Red de prueba"
+
#: views/join.html
msgid "User information"
msgstr "Información de Usuario"
+#: views/dummy-translations.html
+msgid "Using derived addresses from your wallet"
+msgstr "Usando direcciones derivadas de tu monedero"
+
#: views/modals/address-book.html
msgid "Valid"
msgstr "Válido"
@@ -622,7 +797,7 @@ msgstr "Código Secreto del Monedero"
msgid "Wallet Secret is not valid!"
msgstr "¡El código secreto no es válido!"
-#: views/settings.html
+#: views/more.html
msgid "Wallet Unit"
msgstr "Unidad del monedero"
@@ -630,13 +805,29 @@ msgstr "Unidad del monedero"
msgid "Wallet name"
msgstr "Nombre del monedero"
+#: views/dummy-translations.html
+msgid "Wallet network configuration missmatch"
+msgstr "Configuración de la Red del monedero no coinciden"
+
#: views/warning.html
msgid "Warning!"
msgstr "¡Advertencia!"
-#: views/create.html
-msgid "Your Wallet Password"
-msgstr "Contraseña de tu Monedero"
+#: views/dummy-translations.html
+msgid "Wrong password"
+msgstr "Contraseña incorrecta"
+
+#: views/dummy-translations.html
+msgid "You have"
+msgstr "Tienes"
+
+#: views/dummy-translations.html
+msgid "You have a pending transaction proposal"
+msgstr "Tienes una propuesta de transacción pendiente"
+
+#: views/dummy-translations.html
+msgid "You rejected the transaction successfully"
+msgstr "Rechazaste la transacción con éxito"
#: views/more.html
msgid ""
@@ -654,19 +845,26 @@ msgstr "Tu nombre"
msgid "Your name (optional)"
msgstr "Tu nombre (opcional)"
-#: views/open.html
+#: views/create.html views/open.html
msgid "Your password"
msgstr "Tu contraseña"
+#: views/dummy-translations.html
+msgid "Your session is about to expire due to inactivity in"
+msgstr "Tu sesión está va a expirar por inactividad en"
+
#: views/import.html
msgid "Your wallet password"
msgstr "Contraseña de tu monedero"
-#: views/copayers.html views/create.html views/import.html views/join.html
-#: views/more.html
+#: views/create.html views/import.html views/join.html views/more.html
msgid "advanced options"
msgstr "opciones avanzadas"
+#: views/dummy-translations.html
+msgid "available."
+msgstr "disponible."
+
#: views/addresses.html
msgid "change"
msgstr "vuelto"
@@ -676,8 +874,8 @@ msgid "first seen at"
msgstr "Visto el"
#: views/transactions.html
-msgid "mined at"
-msgstr "Minado el"
+msgid "mined"
+msgstr "minado el"
#: views/send.html
msgid "not valid"
@@ -691,18 +889,30 @@ msgstr "de"
msgid "optional"
msgstr "opcional"
+#: views/dummy-translations.html
+msgid "pending transaction proposals"
+msgstr "propuestas de transacciones pendientes"
+
#: views/copayers.html
msgid "people have"
msgstr "personas"
-#: views/send.html views/modals/address-book.html
+#: views/send.html
msgid "required"
msgstr "requerido"
+#: views/dummy-translations.html
+msgid "seconds"
+msgstr "segundos"
+
#: views/send.html
msgid "too long!"
msgstr "¡demasiado largo!"
+#: views/dummy-translations.html
+msgid "transaction proposal purged"
+msgstr "propuestas de transacciones purgadas"
+
#: views/send.html
msgid "valid!"
msgstr "¡válido!"
@@ -719,8 +929,55 @@ msgstr "deben unirse"
msgid "{{tx.missingSignatures}} signatures missing"
msgstr "Faltan {{tx.missingSignatures}} firmas"
-#~ msgid "Send"
-#~ msgstr "Enviar"
+#~ msgid "Scan Ended"
+#~ msgstr "Búsqueda Finalizada"
+
+#~ msgid "There is an error in the form."
+#~ msgstr "Hubo un error en el formulario."
+
+#, fuzzy
+#~ msgid "Wrong password que parece"
+#~ msgstr "Contraseña incorrecta"
+
+#~ msgid "Add Address"
+#~ msgstr "Agregar Dirección"
+
+#~ msgid "Add Address Book Entry"
+#~ msgstr "Nueva entrada"
+
+#~ msgid "Add New Entry"
+#~ msgstr "Nueva Entrada"
+
+#, fuzzy
+#~ msgid "Your Password"
+#~ msgstr "Tu contraseña"
+
+#~ msgid "Bitcoin Network"
+#~ msgstr "Red Bitcoin"
+
+#~ msgid "Delete wallet"
+#~ msgstr "Borrar monedero"
+
+#~ msgid "Download seed backup"
+#~ msgstr "Descargar copia de seguridad"
+
+#~ msgid ""
+#~ "Network has been fixed to {{networkName}} in this setup. "
+#~ "See copay.io for options to use Copay on "
+#~ "both livenet and testnet."
+#~ msgstr ""
+#~ "La red fue fijada a {{networkName}} para esta "
+#~ "configuración. Ver copay.io para más "
+#~ "opciones de uso de Copay en livenet y testnet."
+
+#~ msgid "Port"
+#~ msgstr "Puerto"
+
+#~ msgid "Use SSL"
+#~ msgstr "Usar SSL"
+
+#~ msgid "Your Wallet Password"
+#~ msgstr "Contraseña de tu Monedero"
#~ msgid ""
#~ "{{$root.wallet.requiredCopayers}}-of-{{$root.wallet.totalCopayers}} wallet"
diff --git a/test/mocks/FakeBlockchain.js b/test/mocks/FakeBlockchain.js
index c6dd8ca4f..7de66350d 100644
--- a/test/mocks/FakeBlockchain.js
+++ b/test/mocks/FakeBlockchain.js
@@ -11,7 +11,7 @@ FakeBlockchain.prototype.getTransaction = function(txid, cb) {
};
FakeBlockchain.prototype.getTransactions = function(addresses, cb) {
- return cb(null, []);
+ cb(null, []);
};
diff --git a/test/mocks/FakeLocalStorage.js b/test/mocks/FakeLocalStorage.js
index e39280719..c2ae7ce14 100644
--- a/test/mocks/FakeLocalStorage.js
+++ b/test/mocks/FakeLocalStorage.js
@@ -1,27 +1,33 @@
//localstorage Mock
-ls = {};
-function LocalStorage(opts) {}
-FakeLocalStorage = {};
-FakeLocalStorage.length = 0;
-FakeLocalStorage.removeItem = function(key) {
- delete ls[key];
- this.length = Object.keys(ls).length;
+function FakeLocalStorage() {
+ this.ls = {};
+};
+FakeLocalStorage.prototype.removeItem = function(key, cb) {
+ delete this.ls[key];
+ cb();
};
-FakeLocalStorage.getItem = function(k) {
- return ls[k];
+FakeLocalStorage.prototype.getItem = function(k, cb) {
+ return cb(this.ls[k]);
};
-FakeLocalStorage.key = function(i) {
- return Object.keys(ls)[i];
+FakeLocalStorage.prototype.allKeys = function(cb) {
+ return cb(Object.keys(this.ls));
};
-FakeLocalStorage.setItem = function(k, v) {
- ls[k] = v;
- this.key[this.length] = k;
- this.length = Object.keys(ls).length;
+FakeLocalStorage.prototype.setItem = function(k, v, cb) {
+ this.ls[k] = v;
+ return cb();
};
+FakeLocalStorage.prototype.clear = function() {
+ this.ls = {};
+}
module.exports = FakeLocalStorage;
+
+module.exports.storageParams = {
+ storage: new FakeLocalStorage(),
+ sessionStorage: new FakeLocalStorage(),
+};
diff --git a/test/mocks/FakeStorage.js b/test/mocks/FakeStorage.js
deleted file mode 100644
index 9aea4d2ea..000000000
--- a/test/mocks/FakeStorage.js
+++ /dev/null
@@ -1,131 +0,0 @@
-var FakeStorage = function() {
- this.reset();
-};
-
-
-FakeStorage.prototype.reset = function(password) {
- this.storage = {};
-};
-
-FakeStorage.prototype._setPassphrase = function(password) {
- this.storage.passphrase = password;
-};
-
-FakeStorage.prototype.setGlobal = function(id, v) {
- this.storage[id] = typeof v === 'object' ? JSON.stringify(v) : v;
-};
-
-FakeStorage.prototype.getGlobal = function(id) {
- return this.storage[id];
-};
-
-FakeStorage.prototype.setLastOpened = function(val) {
- this.storage['lastOpened'] = val;
-};
-
-FakeStorage.prototype.getLastOpened = function() {
- return this.storage['lastOpened'];
-};
-
-FakeStorage.prototype.setLock = function(id) {
- this.storage[id + '::lock'] = true;
-}
-
-FakeStorage.prototype.getLock = function(id) {
- return this.storage[id + '::lock'];
-}
-
-FakeStorage.prototype.getSessionId = function() {
- return this.sessionId || 'aSessionId';
-};
-
-
-FakeStorage.prototype.removeLock = function(id) {
- delete this.storage[id + '::lock'];
-}
-
-FakeStorage.prototype.removeGlobal = function(id) {
- delete this.storage[id];
-};
-
-
-FakeStorage.prototype.set = function(wid, id, payload) {
- this.storage[wid + '::' + id] = payload;
-};
-
-FakeStorage.prototype.get = function(wid, id) {
- return this.storage[wid + '::' + id];
-};
-
-FakeStorage.prototype.clear = function() {
- delete this['storage'];
-};
-
-FakeStorage.prototype.getWalletIds = function() {
- var walletIds = [];
- var uniq = {};
-
- for (var ii in this.storage) {
- var split = ii.split('::');
- if (split.length == 2) {
- var walletId = split[0];
-
- if (!walletId || walletId === 'nameFor' || walletId ==='lock')
- continue;
-
- if (typeof uniq[walletId] === 'undefined') {
- walletIds.push(walletId);
- uniq[walletId] = 1;
- }
- }
- }
- return walletIds;
-};
-
-FakeStorage.prototype.deleteWallet = function(walletId) {
- var toDelete = {};
- toDelete['nameFor::' + walletId] = 1;
-
- for (var key in this.storage) {
- var split = key.split('::');
- if (split.length == 2 && split[0] === walletId) {
- toDelete[key] = 1;
- }
- }
- for (var i in toDelete) {
- this.removeGlobal(i);
- }
-};
-
-
-FakeStorage.prototype.getName = function(walletId) {
- return this.getGlobal('nameFor::' + walletId);
-};
-
-
-FakeStorage.prototype.setName = function(walletId, name) {
- this.setGlobal('nameFor::' + walletId, name);
-};
-
-
-FakeStorage.prototype.getWallets = function() {
- var wallets = [];
- var ids = this.getWalletIds();
-
- for (var i in ids) {
- wallets.push({
- id: ids[i],
- name: this.getName(ids[i]),
- });
- }
- return wallets;
-};
-
-FakeStorage.prototype.setFromObj = function(walletId, obj) {
- for (var k in obj) {
- this.set(walletId, k, obj[k]);
- }
- this.setName(walletId, obj.opts.name);
-};
-
-module.exports = FakeStorage;
diff --git a/test/mocks/FakeWallet.js b/test/mocks/FakeWallet.js
index ee2621a1e..bec56a931 100644
--- a/test/mocks/FakeWallet.js
+++ b/test/mocks/FakeWallet.js
@@ -6,11 +6,10 @@ if (is_browser) {
}
var Wallet = copay.Wallet;
-var FakePrivateKey = function () {
-};
+var FakePrivateKey = function() {};
FakePrivateKey.prototype.toObj = function() {
- return extendedPublicKeyString = 'privHex';
+ return extendedPublicKeyString = 'privHex';
};
var FakeWallet = function() {
@@ -24,6 +23,7 @@ var FakeWallet = function() {
'1CjPR7Z5ZSyWk6WtXvSFgkptmpoi4UM9BC': 1000
};
this.name = 'myTESTwullet';
+ this.nickname = 'myNickname';
this.addressBook = {
'2NFR2kzH9NUdp8vsXTB4wWQtTtzhpKxsyoJ': {
label: 'John',
@@ -37,11 +37,21 @@ var FakeWallet = function() {
}
};
this.blockchain = {
- getSubscriptions: function(){ return []; },
- subscribe: function(){}
+ getSubscriptions: function() {
+ return [];
+ },
+ subscribe: function() {},
+ getTransactions: function() {}
};
this.privateKey = new FakePrivateKey();
+ this.settings = {
+ unitName: 'bits',
+ unitToSatoshi: 100,
+ unitDecimals: 2,
+ alternativeName: 'US Dollar',
+ alternativeIsoCode: 'USD',
+ };
};
FakeWallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) {
@@ -52,6 +62,9 @@ FakeWallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts,
FakeWallet.prototype.sendTx = function(ntxid, cb) {
cb(8);
}
+FakeWallet.prototype.getAddressesStr = function() {
+ return ['2Mw2YXxyMD7fhtPhHYY39X6BVWiBRaez5Zn'];
+};
FakeWallet.prototype.set = function(balance, safeBalance, balanceByAddr) {
this.balance = balance;
@@ -72,6 +85,12 @@ FakeWallet.prototype.getAddressesInfo = function() {
return ret;
};
+FakeWallet.prototype.subscribeToAddresses = function() {};
+
+FakeWallet.prototype.getMyCopayerNickname = function() {
+ return this.nickname;
+};
+
FakeWallet.prototype.isShared = function() {
return this.totalCopayers > 1;
}
@@ -98,8 +117,7 @@ FakeWallet.prototype.getBalance = function(cb) {
return cb(null, this.balance, this.balanceByAddr, this.safeBalance);
};
-FakeWallet.prototype.removeTxWithSpentInputs = function (cb) {
-};
+FakeWallet.prototype.removeTxWithSpentInputs = function(cb) {};
FakeWallet.prototype.setEnc = function(enc) {
this.enc = enc;
@@ -109,7 +127,10 @@ FakeWallet.prototype.toEncryptedObj = function() {
return this.enc;
};
-FakeWallet.prototype.close = function() {
+FakeWallet.prototype.close = function() {};
+
+FakeWallet.prototype.getNetworkName = function() {
+ return 'testnet';
};
// TODO a try catch was here
diff --git a/test/test.LocalEncrypted.js b/test/test.LocalEncrypted.js
deleted file mode 100644
index 32149e58f..000000000
--- a/test/test.LocalEncrypted.js
+++ /dev/null
@@ -1,249 +0,0 @@
-'use strict';
-var chai = chai || require('chai');
-var should = chai.should();
-var is_browser = typeof process == 'undefined' || typeof process.versions === 'undefined';
-var copay = copay || require('../copay');
-var LocalEncrypted = copay.StorageLocalEncrypted;
-
-var fakeWallet = 'fake-wallet-id';
-var timeStamp = Date.now();
-var localMock = require('./mocks/FakeLocalStorage');
-var sessionMock = require('./mocks/FakeLocalStorage');
-
-
-describe('Storage/LocalEncrypted model', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- });
- s._setPassphrase('mysupercoolpassword');
-
- it('should create an instance', function() {
- var s2 = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- });
- should.exist(s2);
- });
- it('should fail when encrypting without a password', function() {
- var s2 = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- });
- (function() {
- s2.set(fakeWallet, timeStamp, 1);
- }).should.throw();
- });
- it('should be able to encrypt and decrypt', function() {
- s._write(fakeWallet + timeStamp, 'value');
- s._read(fakeWallet + timeStamp).should.equal('value');
- localMock.removeItem(fakeWallet + timeStamp);
- });
- it('should be able to set a value', function() {
- s.set(fakeWallet, timeStamp, 1);
- localMock.removeItem(fakeWallet + '::' + timeStamp);
- });
- var getSetData = [
- 1, 1000, -15, -1000,
- 0.1, -0.5, -0.5e-10, Math.PI,
- 'hi', 'auydoaiusyodaisudyoa', '0b5b8556a0c2ce828c9ccfa58b3dd0a1ae879b9b',
- '1CjPR7Z5ZSyWk6WtXvSFgkptmpoi4UM9BC', 'OP_DUP OP_HASH160 80ad90d4035', [1, 2, 3, 4, 5, 6], {
- x: 1,
- y: 2
- }, {
- x: 'hi',
- y: null
- }, {
- a: {},
- b: [],
- c: [1, 2, 'hi']
- },
- null
- ];
- getSetData.forEach(function(obj) {
- it('should be able to set a value and get it for ' + JSON.stringify(obj), function() {
- s.set(fakeWallet, timeStamp, obj);
- var obj2 = s.get(fakeWallet, timeStamp);
- JSON.stringify(obj2).should.equal(JSON.stringify(obj));
- localMock.removeItem(fakeWallet + '::' + timeStamp);
- });
- });
-
- describe('#export', function() {
- it('should export the encrypted wallet', function() {
- var storage = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password',
- });
- storage.set(fakeWallet, timeStamp, 'testval');
- var obj = {
- test: 'testval'
- };
- var encrypted = storage.export(obj);
- encrypted.length.should.be.greaterThan(10);
- localMock.removeItem(fakeWallet + '::' + timeStamp);
- //encrypted.slice(0,6).should.equal("53616c");
- });
- });
-
- describe('#remove', function() {
- it('should remove an item', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password'
- });
- s.set('1', "hola", 'juan');
- s.get('1', 'hola').should.equal('juan');
- s.remove('1', 'hola');
-
- should.not.exist(s.get('1', 'hola'));
- });
- });
-
-
- describe('#getWalletIds', function() {
- it('should get wallet ids', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password'
- });
- s.set('1', "hola", 'juan');
- s.set('2', "hola", 'juan');
- s.getWalletIds().should.deep.equal(['1', '2']);
- });
- });
-
- describe('#getName #setName', function() {
- it('should get/set names', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password'
- });
- s.setName(1, 'hola');
- s.getName(1).should.equal('hola');
- });
- });
-
- describe('#getLastOpened #setLastOpened', function() {
- it('should get/set names', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password'
- });
- s.setLastOpened('hey');
- s.getLastOpened().should.equal('hey');
- });
- });
-
- if (is_browser) {
- describe('#getSessionId', function() {
- it('should get SessionId', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password'
- });
- var sid = s.getSessionId();
- should.exist(sid);
- var sid2 = s.getSessionId();
- sid2.should.equal(sid);
- });
- });
- }
-
- describe('#getWallets', function() {
- it('should retreive wallets from storage', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password'
- });
- s.set('1', "hola", 'juan');
- s.set('2', "hola", 'juan');
- s.setName(1, 'hola');
- s.getWallets()[0].should.deep.equal({
- id: '1',
- name: 'hola',
- });
- s.getWallets()[1].should.deep.equal({
- id: '2',
- name: undefined
- });
- });
- });
- describe('#deleteWallet', function() {
- it('should delete a wallet', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password'
- });
- s.set('1', "hola", 'juan');
- s.set('2', "hola", 'juan');
- s.setName(1, 'hola');
-
- s.deleteWallet('1');
- s.getWallets().length.should.equal(1);
- s.getWallets()[0].should.deep.equal({
- id: '2',
- name: undefined
- });
- });
- });
-
- describe('#setFromObj', function() {
- it('set localstorage from an object', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password'
- });
- s.setFromObj('id1', {
- 'key': 'val',
- 'opts': {
- 'name': 'nameid1'
- },
- });
-
- s.get('id1', 'key').should.equal('val');
-
- });
- });
-
-
- describe('#globals', function() {
- it('should set, get and remove keys', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password'
- });
- s.setGlobal('a', {
- b: 1
- });
- JSON.parse(s.getGlobal('a')).should.deep.equal({
- b: 1
- });
- s.removeGlobal('a');
- should.not.exist(s.getGlobal('a'));
- });
- });
-
-
- describe('session storage', function() {
- it('should get a session ID', function() {
- var s = new LocalEncrypted({
- localStorage: localMock,
- sessionStorage: sessionMock,
- password: 'password'
- });
- s.getSessionId().length.should.equal(16);
- (new Buffer(s.getSessionId(),'hex')).length.should.equal(8);
- });
- });
-});
diff --git a/test/test.Passphrase.js b/test/test.Passphrase.js
index 75074108b..dd249f283 100644
--- a/test/test.Passphrase.js
+++ b/test/test.Passphrase.js
@@ -36,5 +36,4 @@ describe('Passphrase model', function() {
done();
});
});
-
});
diff --git a/test/test.PayPro.js b/test/test.PayPro.js
index fbbeaf7ef..e1d3d88ea 100644
--- a/test/test.PayPro.js
+++ b/test/test.PayPro.js
@@ -11,7 +11,6 @@ if (is_browser) {
}
var Wallet = copay.Wallet;
var PrivateKey = copay.PrivateKey;
-var Storage = require('./mocks/FakeStorage');
var Network = require('./mocks/FakeNetwork');
var Blockchain = require('./mocks/FakeBlockchain');
var bitcore = bitcore || require('bitcore');
@@ -21,6 +20,10 @@ var Address = bitcore.Address;
var PayPro = bitcore.PayPro;
var bignum = bitcore.Bignum;
var startServer = copay.FakePayProServer; // TODO should be require('./mocks/FakePayProServer');
+var localMock = require('./mocks/FakeLocalStorage');
+var sessionMock = require('./mocks/FakeLocalStorage');
+var Storage = copay.Storage;
+
var server;
@@ -30,6 +33,7 @@ var walletConfig = {
spendUnconfirmed: true,
reconnectDelay: 100,
networkName: 'testnet',
+ storage: require('./mocks/FakeLocalStorage').storageParams,
};
var getNewEpk = function() {
@@ -41,6 +45,7 @@ var getNewEpk = function() {
};
describe('PayPro (in Wallet) model', function() {
+
if (!is_browser) {
var createW = function(N, conf) {
var c = JSON.parse(JSON.stringify(conf || walletConfig));
@@ -64,6 +69,7 @@ describe('PayPro (in Wallet) model', function() {
});
var storage = new Storage(walletConfig.storage);
+ storage.setPassphrase('xxx');
var network = new Network(walletConfig.network);
var blockchain = new Blockchain(walletConfig.blockchain);
c.storage = storage;
@@ -86,7 +92,6 @@ describe('PayPro (in Wallet) model', function() {
};
c.networkName = walletConfig.networkName;
- c.verbose = walletConfig.verbose;
c.version = '0.0.1';
return new Wallet(c);
diff --git a/test/test.Storage.js b/test/test.Storage.js
new file mode 100644
index 000000000..559dae141
--- /dev/null
+++ b/test/test.Storage.js
@@ -0,0 +1,321 @@
+'use strict';
+var chai = chai || require('chai');
+var sinon = require('sinon');
+var should = chai.should();
+var is_browser = typeof process == 'undefined' || typeof process.versions === 'undefined';
+var copay = copay || require('../copay');
+var Storage = copay.Storage;
+
+var fakeWallet = 'fake-wallet-id';
+var timeStamp = Date.now();
+
+describe('Storage model', function() {
+
+ var s;
+ beforeEach(function() {
+ s = new Storage(require('./mocks/FakeLocalStorage').storageParams);
+ s.setPassphrase('mysupercoolpassword');
+ s.storage.clear();
+ s.sessionStorage.clear();
+ });
+
+
+ it('should create an instance', function() {
+ var s2 = new Storage(require('./mocks/FakeLocalStorage').storageParams);
+ should.exist(s2);
+ });
+ it('should fail when encrypting without a password', function() {
+ var s2 = new Storage(require('./mocks/FakeLocalStorage').storageParams);
+ (function() {
+ s2.set(fakeWallet, timeStamp, 1, function() {});
+ }).should.throw('NOPASSPHRASE');
+ });
+ it('should be able to encrypt and decrypt', function(done) {
+ s._write(fakeWallet + timeStamp, 'value', function() {
+ s._read(fakeWallet + timeStamp, function(v) {
+ v.should.equal('value');
+ done();
+ });
+ });
+ });
+ it('should be able to set a value', function(done) {
+ s.set(fakeWallet, timeStamp, 1, function() {
+ done();
+ });
+ });
+ var getSetData = [
+ 1, 1000, -15, -1000,
+ 0.1, -0.5, -0.5e-10, Math.PI,
+ 'hi', 'auydoaiusyodaisudyoa', '0b5b8556a0c2ce828c9ccfa58b3dd0a1ae879b9b',
+ '1CjPR7Z5ZSyWk6WtXvSFgkptmpoi4UM9BC', 'OP_DUP OP_HASH160 80ad90d4035', [1, 2, 3, 4, 5, 6], {
+ x: 1,
+ y: 2
+ }, {
+ x: 'hi',
+ y: null
+ }, {
+ a: {},
+ b: [],
+ c: [1, 2, 'hi']
+ },
+ null
+ ];
+ getSetData.forEach(function(obj) {
+ it('should be able to set a value and get it for ' + JSON.stringify(obj), function(done) {
+ s.set(fakeWallet, timeStamp, obj, function() {
+ s.get(fakeWallet, timeStamp, function(obj2) {
+ JSON.stringify(obj2).should.equal(JSON.stringify(obj));
+ done();
+ });
+ });
+ });
+ });
+
+ describe('#export', function() {
+ it('should export the encrypted wallet', function(done) {
+ s.set(fakeWallet, timeStamp, 'testval', function() {
+ var obj = {
+ test: 'testval'
+ };
+ var encrypted = s.export(obj);
+ encrypted.length.should.be.greaterThan(10);
+ done();
+ });
+ });
+ });
+
+ describe('#remove', function() {
+ it('should remove an item', function(done) {
+ s.set('1', "hola", 'juan', function() {
+ s.get('1', 'hola', function(v) {
+ v.should.equal('juan');
+ s.remove('1', 'hola', function() {
+ s.get('1', 'hola', function(v) {
+ should.not.exist(v);
+ done();
+ });
+ });
+ })
+ })
+ });
+ });
+
+
+ describe('#getWalletIds', function() {
+ it('should get wallet ids', function(done) {
+ s.set('1', "hola", 'juan', function() {
+ s.set('2', "hola", 'juan', function() {
+ s.getWalletIds(function(v) {
+ v.should.deep.equal(['1', '2']);
+ done();
+ });
+ });
+ });
+ });
+ });
+
+ describe('#getName #setName', function() {
+ it('should get/set names', function(done) {
+ s.setName(1, 'hola', function() {
+ s.getName(1, function(v) {
+ v.should.equal('hola');
+ done();
+ });
+ });
+ });
+ });
+
+ describe('#getLastOpened #setLastOpened', function() {
+ it('should get/set last opened', function() {
+ s.setLastOpened('hey', function() {
+ s.getLastOpened(function(v) {
+ v.should.equal('hey');
+ });
+ });
+ });
+ });
+
+ if (is_browser) {
+ describe('#getSessionId', function() {
+ it('should get SessionId', function(done) {
+ s.getSessionId(function(sid) {
+ should.exist(sid);
+ s.getSessionId(function(sid2) {
+ sid2.should.equal(sid);
+ done();
+ });
+ });
+ });
+ });
+ }
+
+ describe('#getWallets', function() {
+ it('should retreive wallets from storage', function(done) {
+ s.set('1', "hola", 'juan', function() {
+ s.set('2', "hola", 'juan', function() {
+ s.setName(1, 'hola', function() {
+
+ s.getWallets(function(ws) {
+ ws[0].should.deep.equal({
+ id: '1',
+ name: 'hola',
+ });
+ ws[1].should.deep.equal({
+ id: '2',
+ name: undefined
+ });
+ done();
+ });
+ });
+ });
+ });
+ });
+ it('should retreive wallets from storage (with delay)', function(done) {
+ s.set('1', "hola", 'juan', function() {
+ s.set('2', "hola", 'juan', function() {
+ s.setName(1, 'hola', function() {
+
+ var orig = s.getName.bind(s);
+ s.getName = function(wid, cb) {
+ setTimeout(function() {
+ orig(wid, cb);
+ },1);
+ };
+
+ s.getWallets(function(ws) {
+ ws[0].should.deep.equal({
+ id: '1',
+ name: 'hola',
+ });
+ ws[1].should.deep.equal({
+ id: '2',
+ name: undefined
+ });
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+
+ describe('#deleteWallet', function() {
+ it('should fail to delete a unexisting wallet', function(done) {
+ s.set('1', "hola", 'juan', function() {
+ s.set('2', "hola", 'juan', function() {
+ s.deleteWallet('3', function(err) {
+ err.toString().should.include('WNOTFOUND');
+ done();
+ });
+ });
+ });
+ });
+
+ it('should delete a wallet', function(done) {
+ s.set('1', "hola", 'juan', function() {
+ s.set('2', "hola", 'juan', function() {
+ s.deleteWallet('1', function(err) {
+ should.not.exist(err);
+ s.getWallets(function(ws) {
+ ws.length.should.equal(1);
+ ws[0].should.deep.equal({
+ id: '2',
+ name: undefined
+ });
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+
+ describe('#setFromObj', function() {
+ it('set localstorage from an object', function(done) {
+ s.setFromObj('id1', {
+ 'key': 'val',
+ 'opts': {
+ 'name': 'nameid1'
+ },
+ }, function() {
+ s.get('id1', 'key', function(v) {
+ v.should.equal('val');
+ done();
+ });
+ });
+ });
+ });
+
+
+ describe('#globals', function() {
+ it('should set, get and remove keys', function(done) {
+ s.setGlobal('a', {
+ b: 1
+ }, function() {
+ s.getGlobal('a', function(v) {
+
+ JSON.parse(v).should.deep.equal({
+ b: 1
+ });
+ s.removeGlobal('a', function() {
+ s.getGlobal('a', function(v) {
+ should.not.exist(v);
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+
+
+ describe('session storage', function() {
+ it('should get a session ID', function(done) {
+ s.getSessionId(function(s) {
+ should.exist(s);
+ s.length.should.equal(16);
+ (new Buffer(s, 'hex')).length.should.equal(8);
+ done();
+ });
+ });
+ });
+
+ describe('#import', function() {
+ it('should not be able to decrypt with wrong password', function() {
+ s.setPassphrase('xxx');
+ var wo = s.import(encryptedLegacy1);
+ should.not.exist(wo);
+ });
+
+ it('should be able to decrypt an old backup', function() {
+ s.setPassphrase(legacyPassword1);
+ var wo = s.import(encryptedLegacy1);
+ should.exist(wo);
+ wo.opts.id.should.equal('48ba2f1ffdfe9708');
+ wo.opts.spendUnconfirmed.should.equal(true);
+ wo.opts.requiredCopayers.should.equal(1);
+ wo.opts.totalCopayers.should.equal(1);
+ wo.opts.name.should.equal('pepe wallet');
+ wo.opts.version.should.equal('0.4.7');
+ wo.publicKeyRing.walletId.should.equal('48ba2f1ffdfe9708');
+ wo.publicKeyRing.networkName.should.equal('testnet');
+ wo.publicKeyRing.requiredCopayers.should.equal(1);
+ wo.publicKeyRing.totalCopayers.should.equal(1);
+ wo.publicKeyRing.indexes.length.should.equal(2);
+ JSON.stringify(wo.publicKeyRing.indexes[0]).should.equal('{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1}');
+ JSON.stringify(wo.publicKeyRing.indexes[1]).should.equal('{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}');
+ wo.publicKeyRing.copayersBackup.length.should.equal(1);
+ wo.publicKeyRing.copayersBackup[0].should.equal('0298f65b2694c55f9048bc05f10368242727c7f9d2065cbd788c3ecde1ec57f33f');
+ wo.publicKeyRing.copayersExtPubKeys.length.should.equal(1);
+ wo.publicKeyRing.copayersExtPubKeys[0].should.equal('tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6');
+ wo.privateKey.extendedPrivateKeyString.should.equal('tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm');
+ wo.privateKey.networkName.should.equal('testnet');
+
+
+ });
+ });
+
+});
+
+var legacyPassword1 = '1DUpLRbuVpgLkcEY8gY8iod/SmA7+OheGZJ9PtvmTlvNE0FkEWpCKW9STdzXYJqbn0wiAapE4ojHNYj2hjYYAQ==';
+var encryptedLegacy1 = 'U2FsdGVkX19yGM1uBAIzQa8Po/dvUicmxt1YyRk/S97PcZ6I6rHMp9dMagIrehg4Qd6JHn/ustmFHS7vmBYj0EBpf6rdXiQezaWnVAJS9/xYjAO36EFUbl+NmUanuwujAxgYdSP/sNssRLeInvExmZYW993EEclxkwL6YUyX66kKsxGQo2oWng0NreBJNhFmrbOEWeFje2PiWP57oUjKsurFzwpluAAarUTYSLud+nXeabC7opzOP5yqniWBMJz0Ou8gpNCWCMhG/P9F9ccVPY7juyd0Hf41FVse8nd2++axKB57+paozLdO+HRfV6zkMqC3h8gWY7LkS75j3bvqcTw9LhXmzE0Sz21n9yDnRpA4chiAvtwQvvBGgj1pFMKhNQU6Obac9ZwKYzUTgdDn3Uzg1UlDzgyOh9S89rbRTV84WB+hXwhuVluWzbNNYV3vXe5PFrocVktIrtS3xQh+k/7my4A6/gRRrzNYpKrUASJqDS/9u9WBkG35xD63J/qXjtG2M0YPwbI57BK1IK4K510b8V72lz5U2XQrIC4ldBwni1rpSavwCJV9xF6hUdOmNV8fZsVHP0NeN1PYlLkSb2QgfuoWnkcsJerwuFR7GZC/i6efrswtpO0wMEQr/J0CLbeXlHAru6xxjCBhWoJvZpMGw72zgnDLoyMNsEVglNhx/VlV9ZMYkkdaEYAxPOEIyZdQ5MS+2jEAlXf818n/xzJSVrniCn9be8EPePvkw35pivprvy09vbW4cKsWBKvgIyoT6A3OhUOCCS8E9cg0WAjjav2EymrbKmGWRHaiD+EoJqaDg6s20zhHn1YEa/YwvGGSB5+Hg8baLHD8ZASvxz4cFFAAVZrBUedRFgHzqwaMUlFXLgueivWUj7RXlIw6GuNhLoo1QkhZMacf23hrFxxQYvGBRw1hekBuDmcsGWljA28udBxBd5f9i+3gErttMLJ6IPaud590uvrxRIclu0Sz9R2EQX64YJxqDtLpMY0PjddSMu8vaDRpK9/ZSrnz/xrXsyabaafz4rE/ItFXjwFUFkvtmuauHTz6nmuKjVfxvNLNAiKb/gI7vQyUhnTbKIApe7XyJsjedNDtZqsPoJRIzdDmrZYxGStbAZ7HThqFJlSJ9NPNhH+E2jm3TwL5mwt0fFZ5h+p497lHMtIcKffESo7KNa2juSVNMDREk0NcyxGXGiVB2FWl4sLdvyhcsVq0I7tmW6OGZKRf8W49GCJXq6Ie69DJ9LB1DO67NV1jsYbsLx9uhE2yEmpWZ3jkoCV/Eas4grxt0CGN6EavzQ==';
diff --git a/test/test.Wallet.js b/test/test.Wallet.js
index d6b765b46..beb20f0bb 100644
--- a/test/test.Wallet.js
+++ b/test/test.Wallet.js
@@ -1,5 +1,6 @@
'use strict';
+var _ = require('underscore');
var chai = chai || require('chai');
var should = chai.should();
var sinon = require('sinon');
@@ -12,7 +13,7 @@ if (is_browser) {
var copayConfig = require('../config');
var Wallet = copay.Wallet;
var PrivateKey = copay.PrivateKey;
-var Storage = require('./mocks/FakeStorage');
+var Storage = copay.Storage;
var Network = require('./mocks/FakeNetwork');
var Blockchain = require('./mocks/FakeBlockchain');
var Builder = require('./mocks/FakeBuilder');
@@ -27,6 +28,7 @@ var walletConfig = {
spendUnconfirmed: true,
reconnectDelay: 100,
networkName: 'testnet',
+ storage: require('./mocks/FakeLocalStorage').storageParams,
};
var getNewEpk = function() {
@@ -80,6 +82,7 @@ describe('Wallet model', function() {
});
var storage = new Storage(walletConfig.storage);
+ storage.setPassphrase('xxx');
var network = new Network(walletConfig.network);
var blockchain = new Blockchain(walletConfig.blockchain);
c.storage = storage;
@@ -102,7 +105,6 @@ describe('Wallet model', function() {
};
c.networkName = walletConfig.networkName;
- c.verbose = walletConfig.verbose;
c.version = '0.0.1';
@@ -341,8 +343,10 @@ describe('Wallet model', function() {
// non stored options
o.opts.reconnectDelay = 100;
+ var s = new Storage(walletConfig.storage);
+ s.setPassphrase('xxx');
var w2 = Wallet.fromObj(o,
- new Storage(walletConfig.storage),
+ s,
new Network(walletConfig.network),
new Blockchain(walletConfig.blockchain));
should.exist(w2);
@@ -363,7 +367,18 @@ describe('Wallet model', function() {
var s = Wallet.decodeSecret(sb);
s.pubKey.should.equal(id);
s.secretNumber.should.equal(secretNumber);
+ s.networkName.should.equal(w.getNetworkName());
+ });
+ it('#getSecret decodeSecret livenet', function() {
+ var w = cachedCreateW2();
+ var stub = sinon.stub(w, 'getNetworkName');
+ stub.returns('livenet');
+ var sb = w.getSecret();
+ should.exist(sb);
+ var s = Wallet.decodeSecret(sb);
+ s.networkName.should.equal('livenet');
+ stub.restore();
});
@@ -391,6 +406,37 @@ describe('Wallet model', function() {
});
+ describe('#_onData', function() {
+ var w = cachedCreateW();
+ var sender = '025c046aaf505a6d23203edd343132e9d4d21818b962d1e9a9c98573cc2031bfc9';
+ var ts = 1410810974778246;
+ it('should fail on message unknown', function() {
+ var data = {
+ type: "xxx",
+ walletId: w.id
+ };
+
+ (function() {
+ w._onData(sender, data, ts);
+ }).should.
+ throw('unknown message type received: xxx from: 025c046aaf505a6d23203edd343132e9d4d21818b962d1e9a9c98573cc2031bfc9');
+
+ });
+
+ it('should call sendWalletReady', function() {
+ var data = {
+ type: "walletId",
+ walletId: w.id
+ };
+
+ var spy = sinon.spy(w, 'sendWalletReady');
+ w._onData(sender, data, ts);
+ sinon.assert.callCount(spy, 1);
+ });
+
+ });
+
+
describe('#purgeTxProposals', function() {
it('should delete all', function() {
var w = cachedCreateW();
@@ -904,6 +950,22 @@ 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);
+ });
+ });
+
describe('#send', function() {
it('should call this.network.send', function() {
var w = cachedCreateW2();
@@ -1194,6 +1256,15 @@ describe('Wallet model', function() {
});
});
+ describe('#getMyCopayerNickname', function() {
+ it('should call publicKeyRing.nicknameForCopayer', function() {
+ var w = cachedCreateW2();
+ w.publicKeyRing.nicknameForCopayer = sinon.spy();
+ w.getMyCopayerNickname();
+ w.publicKeyRing.nicknameForCopayer.calledOnce.should.equal(true);
+ });
+ });
+
describe('#netStart', function() {
it('should call Network.start', function() {
var w = cachedCreateW2();
@@ -1211,15 +1282,6 @@ describe('Wallet model', function() {
});
- describe('#forceNetwork in config', function() {
- it('should throw if network is different', function() {
- var backup = copayConfig.forceNetwork;
- copayConfig.forceNetwork = true;
- walletConfig.networkName = 'livenet';
- createW2.should.throw(Error);
- copayConfig.forceNetwork = backup;
- });
- });
describe('_getKeymap', function() {
var w = cachedCreateW();
@@ -1533,4 +1595,27 @@ describe('Wallet model', function() {
should.exist(n.networkNonce);
});
-});
\ No newline at end of file
+ it('should emit notification when tx received', function(done) {
+ var w = cachedCreateW2();
+ w.blockchain.removeAllListeners = sinon.stub();
+ var spy = sinon.spy(w, 'emit');
+
+ 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();
+ });
+ });
+ });
+
+});
diff --git a/test/test.WalletFactory.js b/test/test.WalletFactory.js
index b79bdcd5c..0b1a16d3a 100644
--- a/test/test.WalletFactory.js
+++ b/test/test.WalletFactory.js
@@ -1,90 +1,59 @@
'use strict';
+
+var _ = require('underscore');
var chai = chai || require('chai');
var should = chai.should();
-var FakeStorage = require('./mocks/FakeLocalStorage');
var copay = copay || require('../copay');
var sinon = require('sinon');
var FakeNetwork = require('./mocks/FakeNetwork');
var FakeBlockchain = require('./mocks/FakeBlockchain');
-var FakeStorage = require('./mocks/FakeStorage');
+var FakeStorage = function FakeStorage() {};
var WalletFactory = require('../js/models/core/WalletFactory');
var Passphrase = require('../js/models/core/Passphrase');
-var LocalEncrypted = copay.StorageLocalEncrypted;
var mockLocalStorage = require('./mocks/FakeLocalStorage');
var mockSessionStorage = require('./mocks/FakeLocalStorage');
-/**
- * A better way to compare two objects in Javascript
- **/
-function getKeys(obj) {
- var keys;
- if (obj.keys) {
- keys = obj.keys();
- } else {
- keys = [];
+var PERSISTED_PROPERTIES = (copay.Wallet || require('../js/models/core/Wallet')).PERSISTED_PROPERTIES;
- for (var k in obj) {
- if (Object.prototype.hasOwnProperty.call(obj, k)) {
- keys.push(k);
- }
+function assertObjectEqual(a, b) {
+ PERSISTED_PROPERTIES.forEach(function(k) {
+ if (a[k] && b[k]) {
+ _.omit(a[k],'name').should.be.deep.equal(b[k], k + ' differs');
}
- }
-
- return keys;
-}
-
-/**
- * Create a new object so the keys appear in the provided order.
- * @param {Object} obj The object to be the base for the new object
- * @param {Array} keys The order in which properties of the new object should appear
- **/
-function reconstructObject(obj, keys) {
- var result = {};
- for (var i = 0, l = keys.length; i < l; i++) {
- if (Object.prototype.hasOwnProperty.call(obj, keys[i])) {
- result[keys[i]] = obj[keys[i]];
- }
- }
-
- return result;
-}
-
-function assertObjectEqual(a, b, msg) {
- msg = msg || '';
- if (Object.prototype.toString.call(a) === '[object Array]' && Object.prototype.toString.call(b) === '[object Array]') {
- // special case: array of objects
- if (a.filter(function(e) {
- return Object.prototype.toString.call(e) === '[object Object]'
- }).length > 0 ||
- b.filter(function(e) {
- return Object.prototype.toString.call(e) === '[object Object]'
- }).length > 0) {
-
- if (a.length !== b.length) {
- JSON.stringify(a).should.equal(JSON.stringify(b), msg);
- } else {
- for (var i = 0, l = a.length; i < l; i++) {
- assertObjectEqual(a[i], b[i], msg + '[elements at index ' + i + ' should be equal]');
- }
- }
- // simple array of primitives
- } else {
- JSON.stringify(a).should.equal(JSON.stringify(b), msg);
- }
- } else {
- var orderedA = reconstructObject(a, getKeys(a).sort()),
- orderedB = reconstructObject(b, getKeys(b).sort());
-
- // compare as strings for diff tolls to show us the difference
- JSON.stringify(orderedA).should.equal(JSON.stringify(orderedB), msg)
- }
+ })
}
describe('WalletFactory model', function() {
+
+ var wf;
+
+ beforeEach(function() {
+ wf = new WalletFactory(config, '0.0.1');
+
+ wf.storage.setPassphrase = sinon.spy();
+ wf.storage.getSessionId = sinon.spy();
+ wf.storage.setFromObj = sinon.spy();
+ wf.storage.setLastOpened = sinon.stub().yields(null);
+
+
+
+ var w = sinon.stub();
+ w.store = sinon.stub().yields(null);
+
+ wf._getWallet = sinon.stub().returns(w);
+ });
+
+
+ afterEach(function() {
+ wf = undefined;
+ });
+
+
+
var config = {
Network: FakeNetwork,
Blockchain: FakeBlockchain,
@@ -106,333 +75,434 @@ describe('WalletFactory model', function() {
iterations: 100,
storageSalt: 'mjuBtGybi/4=',
},
+
+ // network layer config
+ network: {
+ testnet: {
+ url: 'https://test-insight.bitpay.com:443'
+ },
+ livenet: {
+ url: 'https://insight.bitpay.com:443'
+ },
+ },
+
};
- it('should create the factory', function() {
- var wf = new WalletFactory(config, '0.0.1');
- should.exist(wf);
- wf.networkName.should.equal(config.networkName);
- wf.walletDefaults.should.deep.equal(config.wallet);
- wf.version.should.equal('0.0.1');
- });
-
- it('#_checkRead should return false', function() {
- var wf = new WalletFactory(config);
- wf._checkRead('dummy').should.equal(false);
- wf.read('dummy').should.equal(false);
- });
-
- it('should be able to create wallets', function() {
- var wf = new WalletFactory(config, '0.0.1');
- var w = wf.create();
- should.exist(w);
- });
-
- it('should be able to create wallets with given pk', function() {
- var wf = new WalletFactory(config, '0.0.1');
- var priv = 'tprv8ZgxMBicQKsPdEqHcA7RjJTayxA3gSSqeRTttS1JjVbgmNDZdSk9EHZK5pc52GY5xFmwcakmUeKWUDzGoMLGAhrfr5b3MovMUZUTPqisL2m';
- var w = wf.create({
- privateKeyHex: priv,
+ describe('#constructor', function() {
+ it('should create the factory', function() {
+ var wf = new WalletFactory(config, '0.0.1');
+ should.exist(wf);
+ wf.walletDefaults.should.deep.equal(config.wallet);
+ wf.version.should.equal('0.0.1');
});
- w.privateKey.toObj().extendedPrivateKeyString.should.equal(priv);
});
- it('should be able to create wallets with random pk', function() {
- var wf = new WalletFactory(config, '0.0.1');
- var priv = 'tprv8ZgxMBicQKsPdEqHcA7RjJTayxA3gSSqeRTttS1JjVbgmNDZdSk9EHZK5pc52GY5xFmwcakmUeKWUDzGoMLGAhrfr5b3MovMUZUTPqisL2m';
- var w1 = wf.create();
- var w2 = wf.create();
- w1.privateKey.toObj().extendedPrivateKeyString.should.not.equal(
- w2.privateKey.toObj().extendedPrivateKeyString
- );
+ // TODO this is a WALLET TEST! not Wallet Factory. Move it.
+ describe('#fromObj / #toObj', function() {
+ it('round trip', function() {
+ var wf = new WalletFactory(config, '0.0.5');
+ var original = JSON.parse(o);
+ var o2 = wf.fromObj(original).toObj();
+ assertObjectEqual(o2, original);
+ });
+
+ it('round trip, using old copayerIndex', function() {
+ var wf = new WalletFactory(config, '0.0.5');
+ var w = wf.fromObj(JSON.parse(o));
+
+ should.exist(w);
+ w.id.should.equal("dbfe10c3fae71cea");
+ should.exist(w.publicKeyRing.getCopayerId);
+ should.exist(w.txProposals.toObj());
+ should.exist(w.privateKey.toObj());
+ assertObjectEqual(w.toObj(), JSON.parse(o));
+ });
+
+ it('#fromObj, skipping fields', function() {
+ var wf = new WalletFactory(config, '0.0.5');
+ var w = wf.fromObj(JSON.parse(o), ['publicKeyRing']);
+
+ should.exist(w);
+ w.id.should.equal("dbfe10c3fae71cea");
+ should.exist(w.publicKeyRing.getCopayerId);
+ should.exist(w.txProposals.toObj());
+ should.exist(w.privateKey.toObj());
+ (function() {
+ assertObjectEqual(w.toObj(), JSON.parse(o))
+ }).should.throw();
+ });
+
+ it('support old index schema: #fromObj #toObj round trip', function() {
+ var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}';
+ var o2 = '{"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":2147483647,"changeIndex":0,"receiveIndex":0},{"copayerIndex":0,"changeIndex":0,"receiveIndex":0},{"copayerIndex":1,"changeIndex":0,"receiveIndex":0},{"copayerIndex":2,"changeIndex":0,"receiveIndex":0},{"copayerIndex":3,"changeIndex":0,"receiveIndex":0},{"copayerIndex":4,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}';
+
+ var wf = new WalletFactory(config, '0.0.5');
+ var w = wf.fromObj(JSON.parse(o));
+
+ should.exist(w);
+ w.id.should.equal("dbfe10c3fae71cea");
+ should.exist(w.publicKeyRing.getCopayerId);
+ should.exist(w.txProposals.toObj);
+ should.exist(w.privateKey.toObj);
+
+ assertObjectEqual(w.toObj(), JSON.parse(o2));
+ });
+ });
+
+ describe('#fromEncryptedObj', function() {
+ it('should create wallet from encrypted object', function() {
+ wf.storage.setPassphrase = sinon.spy();
+ wf.storage.import = sinon.stub().withArgs('base64').returns('walletObj');
+ wf.fromObj = sinon.stub().withArgs('walletObj').returns('ok');
+
+ var w = wf.fromEncryptedObj("encrypted object", "123");
+
+ w.should.equal('ok');
+ wf.storage.setPassphrase.calledOnce.should.be.true;
+ wf.storage.setPassphrase.getCall(0).args[0].should.equal('123');
+ wf.storage.import.calledOnce.should.be.true;
+ wf.fromObj.calledWith('walletObj').should.be.true;
+ });
+ });
+
+ describe('#import', function() {
+ it('should import and update indexes', function() {
+ var wallet = {
+ id: "fake wallet",
+ updateIndexes: function(cb) {
+ cb();
+ }
+ };
+ wf.fromEncryptedObj = sinon.stub().returns(wallet);
+
+ var w = wf.import("encrypted", "password");
+
+ should.exist(w);
+ wallet.should.equal(w);
+
+ wf.fromEncryptedObj = sinon.stub().returns(null);
+ (function() {
+ wf.import("encrypted", "password")
+ }).should.throw();
+ });
+ });
+
+ describe('#getWallets', function() {
+ it('should return empty array if no wallets', function(done) {
+ wf.storage.getWallets = sinon.stub().yields([]);
+
+ wf.getWallets(function(err, ws) {
+ should.not.exist(err);
+ ws.should.deep.equal([]);
+ done();
+ });
+ });
+
+ it('should be able to get current wallets', function(done) {
+ wf.storage.getWallets = sinon.stub().yields([{
+ name: 'w1',
+ id: 'id1',
+ }, {
+ name: 'w',
+ id: 'id2',
+ }]);
+
+ wf.getWallets(function(err, ws) {
+ should.not.exist(err);
+ ws.should.deep.equal([{
+ name: 'w1',
+ id: 'id1',
+ show: 'w1 '
+ }, {
+ name: 'w',
+ id: 'id2',
+ show: 'w '
+ }]);
+ done();
+ });
+ });
+ });
+
+ describe('#delete', function() {
+ it('should call deleteWallet', function(done) {
+ wf.storage.deleteWallet = sinon.stub().yields(null);
+ wf.delete('xxx', function() {
+ wf.storage.deleteWallet.getCall(0).args[0].should.equal('xxx');
+ done();
+ });
+ });
+
+ it('should call lastOpened', function(done) {
+ wf.storage.deleteWallet = sinon.stub().yields(null);
+ wf.storage.setLastOpened = sinon.stub().yields(null);
+ wf.delete('xxx', function() {
+ wf.storage.setLastOpened.calledOnce.should.equal(true);
+ should.not.exist(wf.storage.setLastOpened.getCall(0).args[0]);
+ done();
+ });
+ });
});
- it('should be able to get wallets', function() {
- var wf = new WalletFactory(config, '0.0.1');
- var w = wf.create();
+ describe('#read', function() {
+ it('should fail to read unexisting wallet', function(done) {
+ wf.storage.getMany = sinon.stub().yields({});
- var w2 = wf.read(w.id);
- should.exist(w2);
- w2.id.should.equal(w.id);
+ wf.read('id', [], function(err, w) {
+ should.not.exist(w);
+ should.exist(err);
+ should.exist(err.message);
+ var m = err.message.toString();
+ m.should.to.have.string('Wallet not found');
+ done();
+ });
+ });
+ it('should fail to read broken wallet', function(done) {
+ wf.storage.getMany = sinon.stub().yields({
+ 'opts': 1
+ });
+ wf.read('id', [], function(err, w) {
+ should.not.exist(w);
+ should.exist(err);
+ should.exist(err.message);
+ var m = err.message.toString();
+ m.should.to.have.string('Could not read');
+ done();
+ });
+ });
+ it('should read existing wallet', function(done) {
+ var wf = new WalletFactory(config, '0.0.1');
+ wf.storage.getMany = sinon.stub().yields({
+ 'opts': 1
+ });
+ wf.fromObj = sinon.stub().returns('ok');
+ wf.read('id', [], function(err, w) {
+ should.not.exist(err);
+ should.exist(w);
+ done();
+ });
+ });
});
- it('#fromObj #toObj round trip', function() {
- var wf = new WalletFactory(config, '0.0.5');
- var w = wf.fromObj(JSON.parse(o));
-
- should.exist(w);
- w.id.should.equal("dbfe10c3fae71cea");
- should.exist(w.publicKeyRing.getCopayerId);
- should.exist(w.txProposals.toObj());
- should.exist(w.privateKey.toObj());
-
- assertObjectEqual(w.toObj(), JSON.parse(o));
- });
-
-
-
- it('#fromObj #toObj round trip, using old copayerIndex', function() {
- var wf = new WalletFactory(config, '0.0.5');
- var w = wf.fromObj(JSON.parse(o));
-
- should.exist(w);
- w.id.should.equal("dbfe10c3fae71cea");
- should.exist(w.publicKeyRing.getCopayerId);
- should.exist(w.txProposals.toObj());
- should.exist(w.privateKey.toObj());
- assertObjectEqual(w.toObj(), JSON.parse(o));
- });
-
-
- it('#fromObj, skipping fields', function() {
- var wf = new WalletFactory(config, '0.0.5');
- var w = wf.fromObj(JSON.parse(o), ['publicKeyRing']);
-
- should.exist(w);
- w.id.should.equal("dbfe10c3fae71cea");
- should.exist(w.publicKeyRing.getCopayerId);
- should.exist(w.txProposals.toObj());
- should.exist(w.privateKey.toObj());
- (function() {
- assertObjectEqual(w.toObj(), JSON.parse(o))
- }).should.throw();
- });
-
- it('support old index schema: #fromObj #toObj round trip', function() {
- var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}';
- var o2 = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":0},{"copayerIndex":0,"changeIndex":0,"receiveIndex":0},{"copayerIndex":1,"changeIndex":0,"receiveIndex":0},{"copayerIndex":2,"changeIndex":0,"receiveIndex":0},{"copayerIndex":3,"changeIndex":0,"receiveIndex":0},{"copayerIndex":4,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}';
-
- var wf = new WalletFactory(config, '0.0.5');
- var w = wf.fromObj(JSON.parse(o));
-
- should.exist(w);
- w.id.should.equal("dbfe10c3fae71cea");
- should.exist(w.publicKeyRing.getCopayerId);
- should.exist(w.txProposals.toObj);
- should.exist(w.privateKey.toObj);
-
- //
- var expected = JSON.parse(o2.replace(/cosigner/g, 'copayerIndex'));
- assertObjectEqual(w.toObj(), expected);
- });
-
- it('should create wallet from encrypted object', function() {
- var wf = new WalletFactory(config, '0.0.1');
- var walletObj = JSON.parse(o);
- wf.storage._setPassphrase = sinon.spy();
- wf.storage.import = sinon.stub().withArgs("encrypted object").returns(walletObj);
- wf.fromObj = sinon.stub().withArgs(walletObj).returns(walletObj);
-
- var w = wf.fromEncryptedObj("encrypted object", "password");
- should.exist(w);
- wf.storage._setPassphrase.called.should.be.true;
- wf.storage.import.called.should.be.true;
- wf.fromObj.calledWith(walletObj).should.be.true;
- });
-
- it('should return false if decrypted object is wrong', function() {
- var wf = new WalletFactory(config, '0.0.1');
- var walletObj = JSON.parse(o);
- wf.storage._setPassphrase = sinon.spy();
- wf.storage.import = sinon.spy();
- wf.fromObj = sinon.stub().withArgs(walletObj).returns(walletObj);
-
- var w = wf.fromEncryptedObj("encrypted object", "password");
- should.exist(w);
- wf.storage._setPassphrase.called.should.be.true;
- wf.storage.import.called.should.be.true;
- wf.fromObj.calledWith(walletObj).should.be.false;
- });
-
- it('should import and update indexes', function() {
- var wf = new WalletFactory(config, '0.0.1');
- var wallet = {
- id: "fake wallet",
- updateIndexes: function(cb) {
- cb();
- }
- };
- wf.fromEncryptedObj = sinon.stub().returns(wallet);
-
- var w = wf.import("encrypted", "password");
-
- should.exist(w);
- wallet.should.equal(w);
-
- wf.fromEncryptedObj = sinon.stub().returns(null);
- (function() {
- wf.import("encrypted", "password")
- }).should.throw();
- });
-
- it('BIP32 length problem', function() {
- var sconfig = {
- Network: FakeNetwork,
- Blockchain: FakeBlockchain,
- Storage: FakeStorage,
- "networkName": "testnet",
- "network": {
- "key": "g23ihfh82h35rf",
- "host": "162.242.219.26",
- "port": 10009,
- "path": "/",
- "maxPeers": 15,
- "debug": 3
- },
- "limits": {
- "totalCopayers": 10,
- "mPlusN": 15
- },
- "wallet": {
- "requiredCopayers": 2,
- "totalCopayers": 3,
- "reconnectDelay": 100,
- "spendUnconfirmed": 1,
- "verbose": 0
- },
- "blockchain": {
- "host": "test.insight.is",
- "port": 3001
- },
- "socket": {
- "host": "test.insight.is",
- "port": 3001
- },
- "verbose": 0,
- "themes": ["default"],
- };
- var wf = new WalletFactory(sconfig, '0.0.1');
+ describe('#open', function() {
var opts = {
- 'requiredCopayers': 2,
- 'totalCopayers': 3
+ 'requiredcopayers': 2,
+ 'totalcopayers': 3
};
- var w = wf.create(opts);
- });
+ it('should call setPassphrase', function(done) {
+ var wf = new WalletFactory(config, '0.0.1');
+ wf.storage.setPassphrase = sinon.spy();
- it('should be able to get current wallets', function() {
- var wf = new WalletFactory(config, '0.0.1');
- var ws = wf.getWallets();
+ var s1 = sinon.stub();
+ s1.store = sinon.stub().yields(null);
+ wf.read = sinon.stub().yields(null, s1);
+ wf.storage.setLastOpened = sinon.stub().yields(null);
- var w = wf.create({
- name: 'test wallet'
+ wf.open('dummy', 'xxx', function(err, w) {
+ wf.storage.setPassphrase.calledOnce.should.equal(true);
+ wf.storage.setPassphrase.getCall(0).args[0].should.equal('xxx');
+ done();
+ });
});
- ws = wf.getWallets();
- ws.length.should.equal(1);
- ws[0].name.should.equal('test wallet');
- });
+ it('should call return wallet', function(done) {
+ var wf = new WalletFactory(config, '0.0.1');
+ wf.storage.setPassphrase = sinon.spy();
- it('should be able to delete wallet', function(done) {
- var wf = new WalletFactory(config, '0.0.1');
- var w = wf.create({
- name: 'test wallet'
+ var s1 = sinon.stub();
+ s1.store = sinon.stub().yields(null);
+ wf.read = sinon.stub().yields(null, s1);
+ wf.storage.setLastOpened = sinon.stub().yields(null);
+
+ wf.open('dummy', 'xxx', function(err, w) {
+ w.should.equal(s1);
+ s1.store.calledOnce.should.equal(true);
+ done();
+ });
});
- var ws = wf.getWallets();
- ws.length.should.equal(1);
- wf.delete(ws[0].id, function() {
- ws = wf.getWallets();
- ws.length.should.equal(0);
- done();
+
+
+ it('should call #store', function(done) {
+ var wf = new WalletFactory(config, '0.0.1');
+ wf.storage.setPassphrase = sinon.spy();
+
+ var s1 = sinon.stub();
+ s1.store = sinon.stub().yields(null);
+ wf.read = sinon.stub().yields(null, s1);
+ wf.storage.setLastOpened = sinon.stub().yields(null);
+
+ wf.open('dummy', 'xxx', function(err, w) {
+ s1.store.calledOnce.should.equal(true);
+ done();
+ });
+ });
+
+ it('should call #setLastOpened', function(done) {
+ var wf = new WalletFactory(config, '0.0.1');
+ wf.storage.setPassphrase = sinon.spy();
+
+ var s1 = sinon.stub();
+ s1.store = sinon.stub().yields(null);
+ wf.read = sinon.stub().yields(null, s1);
+ wf.storage.setLastOpened = sinon.stub().yields(null);
+
+ wf.open('dummy', 'xxx', function(err, w) {
+ wf.storage.setLastOpened.calledOnce.should.equal(true);
+ wf.storage.setLastOpened.getCall(0).args[0].should.equal('dummy');
+ done();
+ });
});
});
- it('should clean lastOpened on delete wallet', function(done) {
- var wf = new WalletFactory(config, '0.0.1');
- var w = wf.create({
- name: 'test wallet'
+ describe('#create', function() {
+ it('should create wallet', function(done) {
+ wf.create(null, function(err, w) {
+ should.exist(w);
+ should.not.exist(err);
+ done();
+ });
});
- wf.storage.setLastOpened(w.id);
- wf.delete(w.id, function() {
- var last = wf.storage.getLastOpened();
- should.equal(last, undefined);
- done();
+ it('should be able to create wallets with given pk', function(done) {
+ var priv = 'tprv8ZgxMBicQKsPdEqHcA7RjJTayxA3gSSqeRTttS1JjVbgmNDZdSk9EHZK5pc52GY5xFmwcakmUeKWUDzGoMLGAhrfr5b3MovMUZUTPqisL2m';
+ wf.create({
+ privateKeyHex: priv,
+ }, function(err, w) {
+ wf._getWallet.getCall(0).args[0].privateKey.toObj().extendedPrivateKeyString.should.equal(priv);
+ should.not.exist(err);
+ done();
+ });
});
- });
- it('should return false if wallet does not exist', function() {
- var opts = {
- 'requiredCopayers': 2,
- 'totalCopayers': 3
- };
- var wf = new WalletFactory(config, '0.0.1');
-
- var w = wf.open('dummy', opts);
- should.exist(w);
- });
-
- it('should open a wallet', function() {
- var opts = {
- 'requiredCopayers': 2,
- 'totalCopayers': 3
- };
- var wf = new WalletFactory(config, '0.0.1');
- var w = wf.create(opts);
- var walletId = w.id;
-
- wf.read = sinon.stub().withArgs(walletId).returns(w);
- var wo = wf.open(walletId, opts);
- should.exist(wo);
- wf.read.calledWith(walletId).should.be.true;
- });
-
- it('should save lastOpened on create/open a wallet', function() {
- var opts = {
- 'requiredCopayers': 2,
- 'totalCopayers': 3
- };
- var wf = new WalletFactory(config, '0.0.1');
- var w = wf.create(opts);
- var last = wf.storage.getLastOpened();
- should.equal(last, w.id);
-
- wf.storage.setLastOpened('other_id');
-
- var wo = wf.open(w.id, opts);
- last = wf.storage.getLastOpened();
- should.equal(last, w.id);
- });
-
- it('should return error if network are differents', function() {
- var opts = {
- 'requiredCopayers': 2,
- 'totalCopayers': 3
- };
- var wf = new WalletFactory(config, '0.0.1');
- var w = wf.create(opts);
- (function() {
- wf._checkNetwork('livenet');
- }).should.throw();
+ it('should be able to create wallets with random pk', function(done) {
+ wf.create(null, function(err, w1) {
+ wf.create(null, function(err, w2) {
+ wf._getWallet.getCall(0).args[0].privateKey.toObj().extendedPrivateKeyString.should.not.equal(
+ wf._getWallet.getCall(1).args[0].privateKey.toObj().extendedPrivateKeyString
+ );
+ done();
+ });
+ });
+ });
});
describe('#joinCreateSession', function() {
- it('should call network.start', function() {
- var wf = new WalletFactory(config, '0.0.1');
- wf.network.cleanUp = sinon.spy();
- wf.network.start = sinon.spy();
- wf.joinCreateSession('8WtTuiFTkhP5ao7AF2QErSwV39Cbur6pdMebKzQXFqL59RscXM', 'test');
- wf.network.start.calledOnce.should.equal(true);
+ var opts = {
+ secret: '8WtTuiFTkhP5ao7AF2QErSwV39Cbur6pdMebKzQXFqL59RscXM',
+ nickname: 'test',
+ passphrase: 'pass'
+ };
+
+ it('should yield bad network error', function(done) {
+ var net = wf.networks['testnet'];
+ net.greet = sinon.stub();
+ net.on = sinon.stub();
+ net.on.withArgs('data').yields('senderId', {
+ type: 'walletId',
+ networkName: 'aWeirdNetworkName',
+ opts: {},
+ });
+ opts.privHex = undefined;
+ wf.joinCreateSession(opts, function(err, w) {
+ err.should.equal('badNetwork');
+ done();
+ });
});
+
+
+ it('should yield to join error', function(done) {
+ opts.privHex = undefined;
+ var net = wf.networks['testnet'];
+ net.greet = sinon.stub();
+ net.on = sinon.stub();
+ net.on.withArgs('serverError').yields(null);
+ net.on.withArgs('data').yields('senderId', {
+ type: 'walletId',
+ networkName: wf.networkName,
+ });
+ wf.joinCreateSession(opts, function(err, w) {
+ err.should.equal('joinError');
+ done();
+ });
+ });
+
+
+ it('should call network.start / create', function(done) {
+ opts.privHex = undefined;
+ var net = wf.networks['testnet'];
+ net.cleanUp = sinon.spy();
+ net.greet = sinon.spy();
+ net.start = sinon.stub().yields(null);
+
+ net.on = sinon.stub();
+ net.on.withArgs('connected').yields(null);
+ net.on.withArgs('data').yields('senderId', {
+ type: 'walletId',
+ networkName: 'testnet',
+ opts: {},
+ });
+
+ var w = sinon.stub();
+ w.sendWalletReady = sinon.spy();
+ wf.create = sinon.stub().yields(null, w);
+ wf.joinCreateSession(opts, function(err, w) {
+ net.start.calledOnce.should.equal(true);
+ wf.create.calledOnce.should.equal(true);
+ wf.create.calledOnce.should.equal(true);
+
+ w.sendWalletReady.calledOnce.should.equal(true);
+ w.sendWalletReady.getCall(0).args[0].should.equal('03ddbc4711534bc62ccf576ab05f2a0afd11f9e2f4016781f3f5a88de9543a229a');
+ done();
+ });
+ });
+
+ it('should return walletFull', function(done) {
+ opts.privHex = undefined;
+ var net = wf.networks['testnet'];
+ net.cleanUp = sinon.spy();
+ net.greet = sinon.spy();
+ net.start = sinon.stub().yields(null);
+
+ net.on = sinon.stub();
+ net.on.withArgs('connected').yields(null);
+ net.on.withArgs('data').yields('senderId', {
+ type: 'walletId',
+ networkName: 'testnet',
+ opts: {},
+ });
+ wf.create = sinon.stub().yields(null, null);
+ wf.joinCreateSession(opts, function(err, w) {
+ err.should.equal('walletFull');
+ done();
+ });
+ });
+
it('should accept a priv key a input', function() {
var wf = new WalletFactory(config, '0.0.1');
- var privHex = 'tprv8ZgxMBicQKsPf7MCvCjnhnr4uiR2Z2gyNC27vgd9KUu98F9mM1tbaRrWMyddVju36GxLbeyntuSadBAttriwGGMWUkRgVmUUCg5nFioGZsd';
- wf.network.cleanUp = sinon.spy();
- wf.network.start = sinon.spy();
- wf.joinCreateSession('8WtTuiFTkhP5ao7AF2QErSwV39Cbur6pdMebKzQXFqL59RscXM', 'test', null, privHex);
- wf.network.start.getCall(0).args[0].privkey.should.equal('ddc2fa8c583a73c4b2a24630ec7c283df4e7c230a02c4e48bc36ec61687afd7d');
+ opts.privHex = 'tprv8ZgxMBicQKsPf7MCvCjnhnr4uiR2Z2gyNC27vgd9KUu98F9mM1tbaRrWMyddVju36GxLbeyntuSadBAttriwGGMWUkRgVmUUCg5nFioGZsd';
+ var net = wf.networks['testnet'];
+ net.cleanUp = sinon.spy();
+ net.start = sinon.spy();
+ wf.joinCreateSession(opts, function(err, w) {
+ net.start.getCall(0).args[0].privkey.should.equal('ddc2fa8c583a73c4b2a24630ec7c283df4e7c230a02c4e48bc36ec61687afd7d');
+ });
});
it('should call network.start with private key', function() {
+ opts.privHex = undefined;
var wf = new WalletFactory(config, '0.0.1');
- wf.network.cleanUp = sinon.spy();
- wf.network.start = sinon.spy();
- wf.joinCreateSession('8WtTuiFTkhP5ao7AF2QErSwV39Cbur6pdMebKzQXFqL59RscXM', 'test', null, undefined);
- wf.network.start.getCall(0).args[0].privkey.length.should.equal(64); //privkey is hex of private key buffer
+ var net = wf.networks['testnet'];
+ net.cleanUp = sinon.spy();
+ net.start = sinon.spy();
+ wf.joinCreateSession(opts, function(err, w) {
+ net.start.getCall(0).args[0].privkey.length.should.equal(64); //privkey is hex of private key buffer
+ });
});
});
- describe('dont break backwards compatibility of wallets', function() {
+
+
+ describe('Backwards compatibility tests', function() {
it('should be able to import unencrypted legacy wallet TxProposal: v0', function() {
var wf = new WalletFactory(config, '0.0.5');
var w = wf.fromObj(JSON.parse(legacyO));
@@ -444,43 +514,38 @@ describe('WalletFactory model', function() {
should.exist(w.privateKey.toObj());
});
- it('should be able to import simple 1-of-1 encrypted legacy testnet wallet', function(done) {
- var pp = new Passphrase(config.passphrase);
- var alternateConfig = JSON.parse(JSON.stringify(config));
- alternateConfig.Storage = LocalEncrypted;
- alternateConfig.storage = {
- localStorage: mockLocalStorage,
- sessionStorage: mockSessionStorage
- };
- var wf = new WalletFactory(alternateConfig, '0.4.7');
+ it('should be able to import simple 1-of-1 encrypted legacy testnet wallet', function() {
- pp.getBase64Async(legacyPassword, function(passphrase) {
- var w, errMsg;
- w = wf.import(encryptedLegacyO, passphrase);
- should.exist(w);
- w.isReady().should.equal(true);
- var wo = w.toObj();
- wo.opts.id.should.equal('48ba2f1ffdfe9708');
- wo.opts.spendUnconfirmed.should.equal(true);
- wo.opts.requiredCopayers.should.equal(1);
- wo.opts.totalCopayers.should.equal(1);
- wo.opts.name.should.equal('pepe wallet');
- wo.opts.version.should.equal('0.4.7');
- wo.publicKeyRing.walletId.should.equal('48ba2f1ffdfe9708');
- wo.publicKeyRing.networkName.should.equal('testnet');
- wo.publicKeyRing.requiredCopayers.should.equal(1);
- wo.publicKeyRing.totalCopayers.should.equal(1);
- wo.publicKeyRing.indexes.length.should.equal(2);
- JSON.stringify(wo.publicKeyRing.indexes[0]).should.equal('{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1}');
- JSON.stringify(wo.publicKeyRing.indexes[1]).should.equal('{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}');
- wo.publicKeyRing.copayersBackup.length.should.equal(1);
- wo.publicKeyRing.copayersBackup[0].should.equal('0298f65b2694c55f9048bc05f10368242727c7f9d2065cbd788c3ecde1ec57f33f');
- wo.publicKeyRing.copayersExtPubKeys.length.should.equal(1);
- wo.publicKeyRing.copayersExtPubKeys[0].should.equal('tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6');
- wo.privateKey.extendedPrivateKeyString.should.equal('tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm');
- wo.privateKey.networkName.should.equal('testnet');
- done();
- });
+ wf.storage.import = sinon.stub();
+ wf.storage.setPassphrase = sinon.spy();
+ wf.storage.import.withArgs('dummy').returns(JSON.parse(legacy1));
+
+ var w = wf.import('dummy', 'xxx');
+ should.exist(w);
+ wf.storage.setPassphrase.calledOnce.should.equal(true);
+ wf.storage.setPassphrase.getCall(0).args[0].should.equal('xxx');
+
+ w.isReady().should.equal(true);
+ var wo = w.toObj();
+ wo.opts.id.should.equal('48ba2f1ffdfe9708');
+ wo.opts.spendUnconfirmed.should.equal(true);
+ wo.opts.requiredCopayers.should.equal(1);
+ wo.opts.totalCopayers.should.equal(1);
+ wo.opts.name.should.equal('pepe wallet');
+ wo.opts.version.should.equal('0.4.7');
+ wo.publicKeyRing.walletId.should.equal('48ba2f1ffdfe9708');
+ wo.publicKeyRing.networkName.should.equal('testnet');
+ wo.publicKeyRing.requiredCopayers.should.equal(1);
+ wo.publicKeyRing.totalCopayers.should.equal(1);
+ wo.publicKeyRing.indexes.length.should.equal(2);
+ JSON.stringify(wo.publicKeyRing.indexes[0]).should.equal('{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1}');
+ JSON.stringify(wo.publicKeyRing.indexes[1]).should.equal('{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}');
+ wo.publicKeyRing.copayersBackup.length.should.equal(1);
+ wo.publicKeyRing.copayersBackup[0].should.equal('0298f65b2694c55f9048bc05f10368242727c7f9d2065cbd788c3ecde1ec57f33f');
+ wo.publicKeyRing.copayersExtPubKeys.length.should.equal(1);
+ wo.publicKeyRing.copayersExtPubKeys[0].should.equal('tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6');
+ wo.privateKey.extendedPrivateKeyString.should.equal('tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm');
+ wo.privateKey.networkName.should.equal('testnet');
});
});
@@ -488,7 +553,10 @@ describe('WalletFactory model', function() {
});
-var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"networkNonce":"0000000000000001","networkNonces":[],"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":[{"copayerIndex":2,"changeIndex":0,"receiveIndex":0}],"copayersBackup":[],"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet"},"addressBook":{}}';
+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}],"copayersBackup":[],"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 legacyO = '{"opts":{"id":"55d4bd062d32f90a","spendUnconfirmed":true,"requiredCopayers":2,"totalCopayers":2,"name":"xcvzxcv","version":"0.3.2"},"networkNonce":"53d25e8600000009","networkNonces":[],"publicKeyRing":{"walletId":"55d4bd062d32f90a","networkName":"testnet","requiredCopayers":2,"totalCopayers":2,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":0},{"copayerIndex":0,"changeIndex":4,"receiveIndex":2},{"copayerIndex":1,"changeIndex":5,"receiveIndex":2}],"copayersBackup":["02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5"],"copayersExtPubKeys":["tpubD94LTzAUiW99mpA59nyf6fAHh4xKGmnwbgCV4gU2bRpeN9CRiMSurqme22px5NmJAo6FdcdH883Zu98VbqyhesCJ86kUEjH3Zpufy5FfcaC","tpubDA2U9H6LkRHDRbRxHBp4VTbxPc7JqsvtcLxrE5QJF8z1iT6hMJ1pXSVf57GWRcxXutYvpoXRurDVGsscJauMtnJBkYAWBVExYmm91XQE2zz"],"nicknameFor":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":"asdf","02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":"qwerqw"},"publicKeysCache":{"m/0/0/0":["028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90","0332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec8"],"m/1/0/0":["0220ad514cf593d0c3905d3bb49bc5767a9410823bf9b77ea5ef2cf1d1016d77a8","02fd42cf66f1dbdc7bbb9ae09aecea72df479ffe5a0c4641301067e331d12e416d"],"m/1/0/1":["0315f7868eaf1f9b7127e3f7e0222c5e473eea003e34700f4758b6873c525d6723","02a2e8ed5e90dd39e3842fc790e06178997dbca319987f365317589e2a71a93658"],"m/0/1/0":["0244a25a0b97b26707fd855c15b046b901be85a3b70a781d0678608e633440eeca","0358cdcbc528ddfb7173b0dab283f702be82546ff031e4a832a7270080cb875959"],"m/0/1/1":["025c9b49bdf17d97bd82ea1b87793082f857247f0f9b999937a166ec994bb1b41f","020389327ee8ae7d0ee3f8187842d23a4070bdd8a27c0bcddd05d80ef39009253d"],"m/1/1/0":["02fd0e7c62b7b58d1ea7bb4cb84d53b019df99d3703a42aed73a2cfa15f3af5d08","0355a15912e76072ef50e6643376b8a9da8422ed4f8ea07b1d84d4989be5a39b2e"],"m/1/1/1":["03bc3e1f4db32efd8eb1fd44a1665938d59628429c67e1e8b7054ab5717f4e6750","03c4c817b633ac31f44f16f390af831d35f7d98744a52a0f23e9598967342255f8"],"m/1/1/2":["02826fe7e9da408480ddeb1d4414c5100b350f862ca718e27122681e1a0ca35077","02bd25af907bb3edbf6b2cd1ea90eaa92cc93ec47bea7d339af44c1d2c05708e99"],"m/0/1/2":["0337a1a70364b94745d6e26d2d28919cf528304f52765f12ef43e3d6da0a6c8dc0","039d83db9aa43e6e00e0304e6971b6079d79dc12d8d55ce2e6fc24a52ba8d41329"],"m/0/0/1":["0359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b8138","037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d93"],"m/1/1/3":["02600e5c41670773a213a4cb58c8f2fa3e83840784bc7f0b56925e1075e06632c2","036d01867af5f61371151ef7d9026fa0400a623f6924e404ee0b856625268972f9"],"m/0/1/3":["03e5a9b039b187ca8e065627df402e4a5b196b94198542da7036879de08be63d2d","0304f3e0b70f696d80e5785dc7747d6dcb55ba24c31f2d80bf184b4e582e6b47fc"],"m/1/1/4":["03741afa5bd50d6ba5801064c810fae84f6a4557d6a88ddc8591d0d4eb68a8fc41","0214dd6ce6073b05999fb887098ca6f7e1d0b4fdc0760557786907df353df90d1c"],"m/2147483647/1/0":["033e072a53ea835763a03c66e35c35384736210a1bb7d7ee6d9a3e109e82426b30","02e37b5570c053da8a8ee587be86fc629775c4db890aba2745ccc4e4dcc8c31041"],"m/2147483647/1/1":["0228a6de42ef421c263d1efd9f28d9a7d15a261995028a24eff6b9f1c3fc46e6bf","0226cff885cb0d607cc9cf69a7608316eb3fb2ec344c0c9956246ba776116fc396"],"m/2147483647/1/2":["034fe2a8f0b98445eb5810fe36572ad2f64ed9bf64dc9de624f99c0142cb07c682","02f2c5c758e32293f5c193fd69afadbba83abafb397db01e6f2b447690e900475a"],"m/2147483647/1/3":["02b25ef9434446c51f10678f787e4913de582e34d164bd3b06af7732c5476df1a8","025d51a1efd59bcff22ee2e0af61b21a7ba5f639e20dfdf25690e926005177dd0c"],"m/2147483647/1/4":["03e5734e1d29b2f684d0446b7a2ffbd0ba8952570a502d0d14b1efd8f24b61be53","0258fc28a324848d8d0154e8614815e35c668d274a8f01957bb99aab8dc8f386c0"],"m/2147483647/1/5":["021f9e775246765e1cfba0ae453b4eae6cd4ae5a57a09c319edbe89d4dbbf23be3","02857f66571a1c3eb9e72d22ae88e734c03d448bced4dcfd345c2059468124c741"],"m/2147483647/1/6":["02c072f329391a25255dc6452e5f5220966869dbf736ba8a8c3ae9d273a84bc3fd","030920a8b8e88c4db2871a7df0878a86cf0695f6d96bb50c701c3454f3df25176a"],"m/2147483647/1/7":["036bf329fc19bce10cf1999fae5bfa80290ff7b44776b49c7b0dc9eec6cffcfa21","03955a549875b4f7b9be28b9ff4bcd51ad2bc224430b1634baef890585885d5e1b"],"m/2147483647/1/8":["024879c9c9a261b3141ecfa1c79c4efc25278c844ecd1dcfcb95d9c19581fbdd25","03fb4a5fdb91239df3ccf7f61a5b99e7e72483101e21c9d1ee0d85544e9354c6c7"],"m/2147483647/1/9":["035928a107ec01f78cd586914d5a49710fd42e352b1312e3ad0eeb2c9666fdf8e7","03a54c03093797854829c75357f092356352a109042bbb83bdac20cb4e5eca27ea"],"m/2147483647/1/10":["021e7a3a7efe888c5e820b5cf0f03317b2b4bf438d8563449aeb7a77cade97f136","03ec0960b3d1df52ca3cc2c82b7d97063400da4dd051bba2f9bab6cb44aee01efa"],"m/2147483647/1/11":["035d70c26b7f429861f555f7c0d99947411b23b7f95303fb8d5de5b82a95aa30fc","038b922f7024f5446d6b48e5253643543b35c006d90fd37688105c6cefcd8adb8a"],"m/2147483647/1/12":["02158d6503891c6c65a606221dbf5c68d0832288975914007968419939588ecb24","0248264cb1763a3f4de9b34787b4bc5443ec92ef915927494bb9f1c1c0b498c7ca"],"m/2147483647/1/13":["0349965eea38a25ae0c061faeac4c4e57e648bc4c0f059d07b3b8b7962cbc0dde5","0352243d9269565ce2a1ffdd0b8e43a442c6dd1c9edda86eaaf2cba5a4a95c40f1"],"m/2147483647/1/14":["030fa6e3d0c5cedc0581955395c77cbe134c912a47971023b9695332df3f7bb200","03f2cf09e33326fb59bf3f13e6298d2d5d29c9eae3b872e5a851e8d8d77259c883"],"m/2147483647/1/15":["02bf0d45e41339f552df6f8baf4392142921fd38b0f2a4388a905ff6cbacbc278a","03fabe46bb6706a1b8edfd28c046a8891b4530bbe5305080b72b0d08ebdf7b8c0a"],"m/2147483647/1/16":["03a4e3146ed34d6a8af4e4379e6edcff32cb0373ba232b3d746af3052f674133ac","030311b73c6f5c46ddffc0cfce6e5ed0b671d94267d8e52cd8837f2a479916eb91"],"m/2147483647/1/17":["03233df93c762d2f06c7f5f388e4e0a8dbdb13302acba0d2d6995c487d8aec9f2f","024badfdcb7e772ac7fc1c46d3943b07500edbbece105cdeff3eb9e9fcc9f54782"],"m/2147483647/1/18":["0364035475a098e00eb010c500cad3c90af3e81a4bd613144bc9433a150f14718b","028223dc8142154e7477ce000b3dc13e1d15a901553d9b18864c8645b582b38fe6"],"m/2147483647/1/19":["03971b74b4ac4bdaadf636baa4caa82fe5355471ed6ea05a9cbe5fc6c9e4b9db76","0202ebffacd01f83849e5bc5c0e2c317bc5fb2fbcb2d6d4482a5235f9f1308b61a"],"m/0/1/4":["03005ee9ff028c98fd132e531023f2f2b61ff0d26022f979dd98088d2ba167b031","0345ea82e8dfe38277f0c3aee18d2dd93edb63e8663ac83328a7934d2ca57006f6"],"m/0/1/5":["0391bc4990b71d8a3f156ae7107929ed6372b0b4ba8a868253f71ba7189d1efa02","0312a74cf2e7c0dd41897d04fabfd8cc3187b84a28305cfc79315b24e6fe23a6b4"],"m/0/1/6":["021a38c492607ff9684a4fec445e47b5b7100d3ef9e9dc0d0b37c0a646d28d4f77","03ae0b46ab36f97447ebaa53f2b5c8f090f15395378785f2fd285eeba17fbf3f65"],"m/0/1/7":["0308cdec88c1ffe16edc98853d9c08dbd4ba2541ba566668ca17bda19d7eb3481f","02dd622267c2e68287287b8b61724f76fbe84096a56aa5054af92f8fe25380e2d1"],"m/0/1/8":["039647da9ad725836bcb28a3e0497659a28d7749d1416c421a0a01c62d237ee962","022e22aa61eafda0dd8820427f1a06314d352a15ea8645e7ab9b80920017084d82"],"m/0/1/9":["03a4ade946076c6962b70c70ac7fad3a87efb59a1d0a4e32bda13a6d47fe9df961","029a07235aba04ab69526e117d836d5b3fae5cfc8c5e72b10c6d1afd261ccc19f3"],"m/0/1/10":["03c78e9b6493b22790db1acea20df9444e0f9c424fc5756e7a32c290ae01783953","0254c130ee467a96570c9f5ebea89de04f0b1db1686b164f2694339bef8f25dd88"],"m/0/1/11":["03a762c43318ef8d4840fab04c8db73797dc648825fac60f2730b4c76678df1cf3","0212c684a4de8e750ad2dfe2b136370ab9803eca178ed9a27b3990c29b067de35c"],"m/0/1/12":["02702d221f9b15c5cf75ac2f497a6c63e60213087c3d2d3be46768e3ebd238e26e","03ed58580744deb357258e44548212038670769d8d51e385d4fb8414311fd01b52"],"m/0/1/13":["0320e0597b54c62768352f433389cee4725d6094d7bcb5c72265edcc0933829aff","02c5706f11b9a85f3176c572842b7c9812c2195058d24d945bc026b00312740e76"],"m/0/1/14":["02fe43077676b844226d3aaa62e8a86d237710d92f882366944acbde0c8992fcaf","039a6a8662abb8910741cf331320549665e9feb28ca94d1ab6a43c84fa330b94ee"],"m/0/1/15":["0369f99f72847af93d50ab8ee75b6e7e912d26e27be96f6d6b7215cf7daeff7ba5","02521700cc07c953ba5aa586fb0e4795a34dffc68c5fb43e038be3866e40f4daed"],"m/0/1/16":["02f67d1d89bd8fe2f91c5b973cbdacfb4ba440e7656bce284cf73d549625607347","035da9cfac5a803dcb2b283b02a2515a4a1bcbf3d19e0d180aee8fc30193bc0555"],"m/0/1/17":["02c024ec199d240e8d6c66276b94b91071f7cdf2bef540c29d6d18d25de7b1cf7c","02190865f9dafae3f7f05c093463be5632946422ddda0a6fef6904390792516067"],"m/0/1/18":["035ed504d7704ad984a333b8eb0fceb8be043da9284de31ed84d9e68d90c75507d","033303c415b50421732402df00f4baa219f334647a7eb5014b9f8079864d6ab558"],"m/0/1/19":["02ce49fe86b0eee73663b1ee867b16b97c876af26f12764c528a2e6d0eb55ad3d7","03ab969bc81796b88e44c340d854df955fc60ea17ea92db5d3115595d6dec890d8"],"m/0/1/20":["03e2fa915378cbdffa0d919b0fb50c7256ca731b9d571b3365e486893a1d43079c","038d058b895cf084dccfcc9367e4796a5cf4ddceed6c35f6885d75c80119613350"],"m/0/1/21":["02fcb1bf644446b5b42205272af72f0aeab9e92ca29aafa91c5fb69142764017aa","035c5fe5c8811603279a5b72b6c30735d702817db1eab937c622269e28192ffa90"],"m/0/1/22":["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5","022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"],"m/0/1/23":["03f40b82fe8cacff08879f13c45f443a3dc3ea98e1d75d5f32a19f5e5a8f7a905b","028415ee458e4dcfd440ce969726f3b58ae74fb6cf3995ced099579211e7419844"],"m/1/1/5":["032748a6282e21f571b8c8dd49e775deb83c90fcf88dc4ba81d878536973709c3f","020837cd68f14ce571b335eecd1b6fa0af43e1576dd9721aaca2a8ab639ac6b7cd"],"m/1/1/6":["0337032efb013dc92bb8dccfbdda9f5c28f0039a9c60953d41003d095e9f9778af","03ceed2da6b9603297061dc8eb930112ba726b2ccf5eec67f4866a05ca4049a22b"],"m/1/1/7":["0383c96ac2af7d203f69133b2fab6b68366b5075ad6957fa06759df3b20fbfec70","0311385f79834cedaf2230a48c0f9dc8e794da1869fc595db2518d62debb85579a"],"m/1/1/8":["03efc649680280f4e4df96da923bc88330275004125ebe5483c2f3e05ca52e19a4","02803c02d197d780388259afbd001ae41fa3eb3e2bac9627aff540521c184c3b23"],"m/1/1/9":["03af2fe6aa027a76b42c1c4050a040bfd026ad2daec1bb96a5fe2d026a7df919de","02ce14163047c640228796fb1f72bbe3afb05819ad141598a4f021058a6f79dd3b"],"m/1/1/10":["033770378bd762cf0408e44e4e604bef77e336170428c506949b1a4f1f2963e574","02c58ed43946f699dbd3e36d3e9aab2714cadeb19ecd3a56e4328c50336b4a76cb"],"m/1/1/11":["02898a1545fa19bdca92adc498698d27b86529cd4c08946d9d29604734b86f31af","02b402767a045ede072600924401c0d720000b2ed59fa444bfdbef4a5f1cead745"],"m/1/1/12":["039b8659430be49913e2cd869aa8c99ccf49a13df35837370b792033dadb891483","03264e63df292257cc76babb15d15bef620d1c2f8c3bbc78d6ea02d127e5ee7386"],"m/1/1/13":["02381a559791b8e86bf546e2c718ae63cf24eed0518a58e4d4a4b310adf2cd38fa","02d7f8283a4418d912508901b4a3db0d2103206dfdd74b3c75648671e20ecfd445"],"m/1/1/14":["020376e8c550b7d9faa0b2da947a2a36fab22c6e8190b6f99460b6022017bb97d7","03fbc5299190e6628de28c92aaa12e3a131b21eb7266462c46fbedeb86fa878055"],"m/1/1/15":["027209fd3b0cf7368180a5dbb16b928c997d33fccb78505d48440c7d23eadf5460","03450bfb22858726cd7e228e6733f69457546978a95188565c53e0d1c0d6070ea8"],"m/1/1/16":["03cb355ba04f64293793855121bab5831f84a3a3edf7cd31fccaa6d67c407a4912","028bc897a39c1224610b765a80f4cd8ab79cb37776f58fec9c10ac6f649d1f3c72"],"m/1/1/17":["03f4cb0564d7e2c6b85673503b7954db22779f29a8f3374904573984e318a96bf1","037c11b6ee906d84aa7eed359d758d986d912b6f8e5cbb1acf0982a77b3ef812c4"],"m/1/1/18":["02d2e5798f33f6889472857744316f2d253f25f88379610063f40cfe5798d9858f","0253cefdfe9ca987cbf1c950b6246d5b7a194d8dfad47c3a78dbbc5c1d01511d97"],"m/1/1/19":["0336c325f5aed366ffc10d553f2bfd4d69e66cbe1688d77af14efc8827aea2e318","0378b1b9a6074f9f2ab4fa9ad1e14649c621b0c8124a1b148914d3c10e6ab390c6"],"m/1/1/20":["03ea55740a734689ce778a8c00df8ebf4274c8f66de7d05646fe5c927773ff7f2e","02275b558d49aef955b6dee51a3c0a53f4b076b97bb3f26abcc82540168ec87cac"],"m/1/1/21":["03c77869c9984664eac9c238f4b6d806c9f48ca8a736c48450f398834db2aa915c","02d984f548c7f60c09dad3287cfc48807bc8157123989636c713be61be6a2e9ced"],"m/1/1/22":["03ed7c6a3c854c1f9459891691cc32671402f9e47126919878251e568dbdf353f8","02a113dab22cd9e46967b3fd76b9b9ec1d227d88817a9300e42d332cca2a0877fd"],"m/1/1/23":["02ee186432dcf69fda50a6fdbd94651817d8a271c273a5b70cab3ec4ae77a3753b","02291370aad9de0dac676355ced64e268b0c431a51f42f12d13f5144940fce4285"],"m/1/1/24":["02bf71435e84e66547c8c583d5ba226a5ac4d935e0a9f9603ecd8925c3e847e91a","03578d8657d285a89d9d597632db662cfef9baccfb55c76b1e87948a94fc9de30d"],"m/2147483647/0/0":["02a8425bbe23426219065969f695a6c3e242b24e57226bffdd542be8fd6be968c9","03057a42fdb6569fb1615b173ccb702453db2eac5be4291b82d4511461eafbed87"],"m/2147483647/0/1":["0250c3d3e86e332010c5233c2ec3bc728026002f0037cb3382d6318409b0e70796","02cfac1e7c4c88191201080f8316af52d9faa6ba624a6e160279e9fac4d1cf79a9"],"m/2147483647/0/2":["02a8c266a5b92eb50c8be91f95e4d1ad968b2f57d527377fd642d63fb84474f61a","028cc954ab31bd179ff80b8a05f95430ae534e61b3ff35f5284fa2fbe1832ceccb"],"m/2147483647/0/3":["02f719e1a7ab00ea98611453fb03d44c1da04655bed74af392534d70099039b4c2","03bfa548bfd4718c50bfce173f780eadcfb679d9c0206c91a2fa1879a9cf7558b2"],"m/2147483647/0/4":["0362c0695d397ca26bf47f0e641bb3cfb06ff29ccac2e1d56ded3afcf88b1e688d","02f9d87b05bdb3b9e82f506b43f813041c0e403274adc23d11e5e1651e34b606c2"],"m/2147483647/0/5":["033731323032d4ee08e858fc71f93970444333e183a1d5052e1d08cfb511e262c8","023e12556cef67ade35b7758916b5e1a3ebe074ccd35c5d8eff6b01321f63eb495"],"m/2147483647/0/6":["025d11b90081972bc1c258c9d6f476dfc2f95b69f0e9935322bf9c21deb580ff64","02b065f56a378907354f0738a0ed74f10660c6b5dd68c9f992093b75ce3d7d8b72"],"m/2147483647/0/7":["0210e721e8a35db9d8c855a0d346f60c09208f3be80b39e03af2c29db777332c71","0277f352969fadb1f1835f9a0fa99c6a3c7b6c281be5b2794c88a708eb177ea33d"],"m/2147483647/0/8":["02998d8d41e4215cd2a961a415a3ed0b1f984f1627719a7b102a75864943c4d87b","03d8ed7fc8f68a77f68d3afd007b7aa4c89944195143630ce183f0fa5438f2b559"],"m/2147483647/0/9":["0324fa91737588e4f85937303ce65c3b91b5f2ae506a72d92b83e3f5f9aeeb3c6f","02a011be72c4a400319212228106af278823a97acfe0a67e1ecd866d446b315114"],"m/2147483647/0/10":["025886ba287922a904881c7315e6fcc410a7976741771a5937d3a1a01b529f21fd","0243bb91ceed9d29d0c2ca66a8ab77e82110bbcc023beb4106f787964f44a0b972"],"m/2147483647/0/11":["0369d21684894cc2d4b2f5e581ede3cac9e8db4161a08e7737c1be129bb673d3d5","03c9ef27e3cd3dadc078fdfd9936a7ad9bf7954747085cf8f8a2a5bb3431f68a9f"],"m/2147483647/0/12":["03a73b8fd859bf6acebffdfffa2597199091daedd2c011ac67fc3494d8a1a8ceb6","025a213f7771c8be03f43f2e7f469ad4ef2cf6907ea284b227a786d1f55dfa7144"],"m/2147483647/0/13":["03a09f7ca257e1ab263cd5e6b0addc3ff868b93df132321d98775ca3505efb576f","03454c715739164bc55f347a651439cdf3ec146b35d2927beb60e8290b3916e082"],"m/2147483647/0/14":["03a64b1f7bd94a6b1a6e84ea444e0ba04e9deb86460934ccc37c0615a134a8257b","02794f09210b1811a455f3e1c7bcd35c76dff2523190fef9615eb27e2376acac1c"],"m/2147483647/0/15":["0392dca2fd9a3bc2b2a7d90a848719069fbc5f22bff7327bb8186c032514085263","032ee8a33ea76d70c7ae839448ca6c5b1af89146f2922e23ba1822df42dbc7e66a"],"m/2147483647/0/16":["031a22a1a3c1abad7c4d782ef6ba3cc00f2e8fe549eb33e0732200aff6d3174831","03bdce9781289e0c31cf727f4c93fe46f7930dd8fd68f818ce241f1ede268e8e0e"],"m/2147483647/0/17":["03b12d27e9aea2c2ad598e54e40860a705ac2ca2427aa511b501b38ec368ea5c7d","03e60d35d84d4536cad895215256b312bb4879a8d417251c279995e58f25da3d54"],"m/2147483647/0/18":["0380266cc9a9673676ad6a1b2e7148766df9c25b4dce299e5edc4f65b72aa58e64","0329e2a8a48c06c0c45dfdd2ab33e6455551557d8ebaf8c12fdf7470f8c45f1d28"],"m/2147483647/0/19":["036fe62af85560d7eea7c7af55e60b32a97dca80134d0aedffb19eb2705b9d6e01","02381c2c30b9f81e2a53c69028fbe11803acad0420b267719b7a80870be0baaeb7"],"m/0/0/2":["027bf94b8fc4e9b42683af25fda125ccab8760040717d100270dd4afd032692daf","026382c6c9357250d96dc21e43c053857a64efeac1887fdcbc107fbe3ecfc6115a"],"m/0/0/3":["03fd203acbd9af3cbbfb709458f8952078234a36094f12d00372e4b2b14cfdf419","03f2e5db59aea5dc89f53ac2a9f4ef66d41265c45afc5d763e0ca61ab70c7c61ec"],"m/0/0/4":["02a1d7cf4fcdbbf4de4002b844c3bff1639073f1cd6e5c4a4e02596b45d3f518c2","03b5fba813294e6ae096ea158833453caa5a945609b0a554696091b9b152bb0f7d"],"m/0/0/5":["0261d37e3b56ef4e106c59753037f516a4b1c45e056b2a3e00f8b77f15aaa7f8a5","0256a55e66e0de1603f0d600c0eb5f5486cf3512a776a36f3ab0d1941fc0dc9b09"],"m/0/0/6":["031db2826af215fe6cbe3f6e121b0497840fc49be133cff0a4d4eab679d6b99d70","021dd722c3f35dd04fcdb57f09b76c723d521fb36751de03ffd08096ddf1dc1f86"],"m/0/0/7":["0354ea75bdd9eb5beae7262e4a5eeb58bd10103ee0185e85b749ea39f6615d0f62","03f2c8f3b6478c0501a8578d5caf5ac2974f8213fc5e699d62dd2af58fbe8781d4"],"m/0/0/8":["0282e67df3bcd1e1662469b4c3151fb50ee1e46b75d787d91184c16b9803131f82","02921a7054af1e425f4137a5eb6b34d1f2b9d81c2625230194bc30657bb4277e11"],"m/0/0/9":["033e7e387933983ceab37c8388bd8ebc5119760f493ffe6f083bef0e5dfe22891d","02d660d60cc55d80912e0745cb142a8596a4604fbf72f9aadec0599aa2ed62461a"],"m/0/0/10":["022ce5b2750ae34512199856eab9e912dc25281cd8b88e7688a46c3b9a389701cb","02f14aa1608fce3b6088148709eb5fe72b61699c931fa8d95a45fab1106859d1b0"],"m/0/0/11":["0288dbef3302c1bc5556028adb33e2f9e03c119dbad4f706befb8ce86cea459f2b","03f13ced465e2e0a3aaa8895f3185d5711e0bebdaf507610b7a669ac8fc82da8fe"],"m/0/0/12":["031ab4677885340d2f927ccc9747f4346b79e4eb6c750695095a8a2524610fa94a","038c881910fbd8b50d193db4e0c84f5b7840820397f92cf0718a8e06d027125503"],"m/0/0/13":["031b568452cba22eb7a88c6085489e53e35abd16068882e71a140e47e12dee9c61","020d09885ee362101d12d34ce0918d41593634db1b9413e5415c6755753b9330e8"],"m/0/0/14":["024177bc9aa03cfc72eda2dfddffd7fe9d0c2f007fc3ba1a48280feae2b9fb117a","03394ad321668440c08da76eb35475ba3a8c0e8cbe0ed81468673a8c72d38fe457"],"m/0/0/15":["02037b1cc696ffbe9eba3684edd53653386ef6cd7728401c40120037593a4c2ae2","020ab8d6900ec9c11ca5d96dfc0ce7cf0ee71653a7c45118e89abb4b113147e53a"],"m/0/0/16":["023bcbb8d4726a546087cdb83740adf0ace879b7195a572c652fa8ce4dbe195a04","0392721b230d5163d28b27fc7e059b875711f12b3da448eabe7229bde57530e637"],"m/0/0/17":["02498ee74e849d3e9261dd1863038caf83d6a3bc2eeebecf17055d4bab44dee77f","03d4dc104b2e0981693e8097437de9b05334a85e2c8edb02783897859bdbc93e32"],"m/0/0/18":["0218a9f524fe54abf8c3afd21314296cfd93eaa9227acbd457e6c9a742dc233cf4","03760f3d0c5db969bda698ff9352e3b7c332216c34825f4c6e857e39c9aee7cd35"],"m/0/0/19":["033dd51f7737f0e9db79f5c38e4298bf3396346904ef3933d290a22e5b77048d9e","0221b2eedccb9a37515263071550069b3b349a166f0f131d0028e8600d9a2251b9"],"m/0/0/20":["02cb6c39161f3244d7769f7ab96346cae2cf21cb6f4538f5e7382d363dc2f836c7","034f7bda4d1e9ed6a3774608a4d6cd8582ab59fe3187f8a7a7cf914d89426ebe28"],"m/0/0/21":["035490549d65f1360f10340037250b171470ff4c86966318a2b1eead6d8b969aea","03f6a04f6fcd07a4f32c82d53710ed30e0f54d43d41c67c661d158b3d0830c3ea2"],"m/1/0/2":["02972eae7e4302e319c266578e14a07839c1e788296a92906e6d66d938211dad5f","039ed6b488f1571ad6527acd6b6c5b8453eacf6665dc5cb7852e33d1c8ea73f9fe"],"m/1/0/3":["02bec4728888c2c045108353994bae5731ec7a7b41459023b0023e10b8d616bd30","03ce1efe16214c9eac595382e46a68143dd11a335b3f7c971ddd719ac544a5fc4b"],"m/1/0/4":["030e2df1d341568225d8dfbe5d07e98dae9f90e0f43e19dcc68c998a6ed7bcc1f0","0380f4c07dc84faf42d51779f104aa6e3b5c3ce2d7684b3cb76d49faeefc2b69d6"],"m/1/0/5":["029a54ddaa25f433b493f4b72df8c1d41be2c4d2963b8b61ee63cc86d16c12d066","021567c95e0317442e7367aa4e3378dd46c5bcef5860f789272fea83b917de0669"],"m/1/0/6":["03590320d80b61cc0874b579f467c9b5ccc50d9ef875bcf6bdd12e2d0c211e8973","03ee4677b6ee89a9d355851f2230506c6897ff219062c0df4ad9a85c60f3535f93"],"m/1/0/7":["03caf98ab1c9b79d1dc8029453a6137c08787b04043b79af3cb42d41d2d3f1338f","023f39ae4e2f4f3887d5fc58e0d3a0d7ee267dc04aa257c75b6b2d67d2f5580f81"],"m/1/0/8":["0352a2a3ea8209c9a2b633d788796ac2d16c08022440e04a77ab2835c7f971d266","0291bc248b3da997f35e8fae98a75a91fdac2819d74c4e270899338d48f7389e87"],"m/1/0/9":["02468d32d9c3c62418d506d4cd0da6cd2022d5bcafdb5f847cf7bde7a48ec6848b","032713d90d12eb6a072f3c1db6c0d3b680d3f78883016135fc0f78e8193d41d4b4"],"m/1/0/10":["034863cc6bab9b059be53413ba75c5fc286647c20d7f9e5512ef4754ea301dd1ce","03a33ab9c32a2264ee2464ebbb5892f0e34acf0fdede4f87395a89e9dacdd4930e"],"m/1/0/11":["031e19296695bfe8a96ba3bf58afa805ee1bd5471fddb3929b1678d69d442d69c9","0270feb33956fd9e937019d629523e26437493c0856514011e6aec88baf7721295"],"m/1/0/12":["03cce695d3c3843bf73e851b2446a77d7e235e5b80b4f4474f9946292eb8218742","039ea96c8822f0ec7ed28308d277f3e730480d7573579cd11b89aef4364cd9ffeb"],"m/1/0/13":["02ab4ac38eb405e822d12c0f0f354f04f9ee1d991dde887a5c1171096fe503158f","036809e60cae1203da8884ea1f85d4669ce6e053f8ba605d775e271b70ab4f6787"],"m/1/0/14":["039d61da23a8610fa0ee58eb37d7cea7ea9396c79153da97280ccf5e46718e3bac","03015c27bcc778682781fd6ad30aa6041db0b7e24270818cdceece0043ccc34b26"],"m/1/0/15":["03c088ed669132835d2728b0ecf294271c8388988c6ae264d43ca24f50e4005f81","03e2c118c9445a2ddc4c8afeb0ba49e21be3f818a483d346418b8922b8a371a2b7"],"m/1/0/16":["02bba7df9847f463c6b23eca37a4bd6efa3801a52b8ddfad804d902e783b70c81c","03764b657f23996e31c64a701facc1cbeb0c9edfdd605e2c1ed36cf48197565d45"],"m/1/0/17":["020445179c522295b89bf4bfd582eb03422e3fa20dcd29263925e9f44282d476d8","036e47bdd32f3061aed1c1f8c2a32b038c7b72391cb1f80ebfc150e58f88372766"],"m/1/0/18":["024d88c4bfcbba713d49e1edcd035234aaa1ee76ad7bcf75bf074a16658a6b0b6d","02b861e7a20d89f6875d2e44c78dbadb99503e282e5e60e9f65657af6fea81d425"],"m/1/0/19":["023a8ca9d5300181f157e1930d3b0800eebe7683d8df72e6cbf28834dbf1be5d60","026053c4f84c10d15890c0b254522972931bc2d5b7cdf9c1f9f3137c22edf3ecd3"],"m/1/0/20":["03137c66e9f3d61aba659f408d77a293fa0f3fea4ccb911074a681d6f61a55d023","0291aa1bbfbef59b16b0e37e185a706c589d448cb02e860c5df9c9d7242ecc739f"],"m/1/0/21":["03c08673e0cae55318bc9dcc4b5f11eb3ff71d42de04015e255dde3fd8cba7e09e","02423d4eab06cd5b26e71d145283523c011d58032700c517f00b328d2c90cf109f"]}},"txProposals":{"txps":[{"creator":"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5","createdTs":1405543144016,"seenBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543144016,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543144645},"signedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543144016},"rejectedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543170040},"sentTs":null,"sentTxid":null,"inputChainPaths":["m/45\'/0/0/0"],"comment":"blablabla","builderObj":{"valueInSat":"29000000","valueOutSat":"8900000","feeSat":"10000","remainderSat":"20090000","hashToScriptMap":{"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj":"5221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852ae"},"selectedUtxos":[{"address":"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj","txid":"a9f4dda3f092e37244bc4e77ea921fed01d5b8ea49613dfdc0dc8afdd70190b5","vout":1,"ts":1405543855,"scriptPubKey":"a914cc93216398b77b5f8c451ca3a357bef961678be987","amount":0.29,"confirmations":0,"confirmationsFromCache":false}],"inputsSigned":0,"signaturesAdded":1,"signhash":1,"spendUnconfirmed":true,"tx":"0100000001b59001d7fd8adcc0fd3d6149eab8d501ed1f92ea774ebc4472e392f0a3ddf4a9010000009300493046022100ccbb8f398f74a76236629b8499ffc6f9518a2091f5a61a9a352c0a10f615961e022100b8f0769c76cf33bec3d7f81d9da2b74cf6e8a5e0a24ee5f48172854d8bcdbfa101475221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852aeffffffff02a0cd8700000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288ac908c32010000000017a914560c292066792531164149c5ed63ad2793a61b928700000000"}},{"creator":"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5","createdTs":1405543188745,"seenBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543188745,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543189341},"signedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543188745,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543206819},"rejectedBy":{},"sentTs":1405543207304,"sentTxid":"169bc92693dd2e27724eeba81e54210e842035bd3af6c52e6a6a5e908f1a4f66","inputChainPaths":["m/45\'/0/0/0"],"comment":"que parece","builderObj":{"valueInSat":"29000000","valueOutSat":"9000000","feeSat":"10000","remainderSat":"19990000","hashToScriptMap":{"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj":"5221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852ae"},"selectedUtxos":[{"address":"2NBtv6DdXj8HBunyGqpW9H8bUtW5x3rfVTj","txid":"a9f4dda3f092e37244bc4e77ea921fed01d5b8ea49613dfdc0dc8afdd70190b5","vout":1,"ts":1405543855,"scriptPubKey":"a914cc93216398b77b5f8c451ca3a357bef961678be987","amount":0.29,"confirmations":1,"confirmationsFromCache":false}],"inputsSigned":1,"signaturesAdded":2,"signhash":1,"spendUnconfirmed":true,"tx":"0100000001b59001d7fd8adcc0fd3d6149eab8d501ed1f92ea774ebc4472e392f0a3ddf4a901000000da00483045022035423cc74824ba904907678dda3b62a20a787b96d1b3e9f3e9546f9c57f4e45902210080a1ff1c39f458ac1642b9e948bd62fd70563b5252e749cc8fc642cd763ee830014730440220524a13f36cfb03caa246d7d84de634ec9386f2c39c19bfa926037f48da86262b022050e58a6503d105ad2805f86806810a1aa7f20d6271e1340b42fa91ab6a30f3e801475221028a4b63f26253f3a8731577b8e1ee480950ad5833ebbf106fe3463bfc07cc3b90210332efa054c08cb77506a35ee0762cb7156f244566703ec08e433568ec0397bec852aeffffffff0240548900000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288acf00531010000000017a9146130a9d51f996b7a1b9d3e10c80930834251909d8700000000"}},{"creator":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","createdTs":1405543505848,"seenBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543505848,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543590221},"signedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543505848,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543590221},"rejectedBy":{},"sentTs":1405543610315,"sentTxid":"6fe851b54b777a75fe80fa204dc674395e2af69efb1f7c0017e909eb82c3d914","inputChainPaths":["m/45\'/0/1/1"],"comment":"mandaaaaaaa","builderObj":{"valueInSat":"19990000","valueOutSat":"19980000","feeSat":"10000","remainderSat":"0","hashToScriptMap":{"2N277q5r8Ab6XLJNCjXXFdh5itDJRQCv9ts":"5221020389327ee8ae7d0ee3f8187842d23a4070bdd8a27c0bcddd05d80ef39009253d21025c9b49bdf17d97bd82ea1b87793082f857247f0f9b999937a166ec994bb1b41f52ae"},"selectedUtxos":[{"address":"2N277q5r8Ab6XLJNCjXXFdh5itDJRQCv9ts","txid":"169bc92693dd2e27724eeba81e54210e842035bd3af6c52e6a6a5e908f1a4f66","vout":1,"ts":1405543157,"scriptPubKey":"a9146130a9d51f996b7a1b9d3e10c80930834251909d87","amount":0.1999,"confirmationsFromCache":false}],"inputsSigned":1,"signaturesAdded":2,"signhash":1,"spendUnconfirmed":true,"tx":"0100000001664f1a8f905e6a6a2ec5f63abd3520840e21541ea8eb4e72272edd9326c99b1601000000db0048304502206b18b3dba2646c552469d8ef52d7656f6a65f563032530f622abdfd8bd4c5cee022100e804b406eddebbc827646141e74dc64c76a770ed4e35183ffd35d265ad9f7d3b01483045022100f6c013638ff0a316b1baa93dfffba6a98cf3033c133e8bd899e933c9c3e47ce10220530f40e7ea52ae58bec695edbec6d566d2ee8e7b5f33f95e33093ad1e29a125401475221020389327ee8ae7d0ee3f8187842d23a4070bdd8a27c0bcddd05d80ef39009253d21025c9b49bdf17d97bd82ea1b87793082f857247f0f9b999937a166ec994bb1b41f52aeffffffff01e0de3001000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288ac00000000"}},{"creator":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","createdTs":1405543781381,"seenBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543781381,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543782017},"signedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543781381},"rejectedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543794590},"sentTs":null,"sentTxid":null,"inputChainPaths":["m/45\'/0/0/1"],"comment":"1","builderObj":{"valueInSat":"29000000","valueOutSat":"1000000","feeSat":"10000","remainderSat":"27990000","hashToScriptMap":{"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb":"52210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352ae"},"selectedUtxos":[{"address":"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb","txid":"6c9da5b0da4bab0d576033325e987b10ccf2b9bf479d306b6aae36efeaa56892","vout":0,"ts":1405543698,"scriptPubKey":"a9147d274ac50968d7823b6cbc1b38770deb7157995387","amount":0.29,"confirmationsFromCache":false}],"inputsSigned":0,"signaturesAdded":1,"signhash":1,"spendUnconfirmed":true,"tx":"01000000019268a5eaef36ae6a6b309d47bfb9f2cc107b985e323360570dab4bdab0a59d6c000000009200483045022064d877bc5171fbaef909c2a1a924e0023b3ccc0b530cb46653f06ecb230283e8022100bc6658d60ad4f7120d9226c8f6eada87f3b0388f73c458011988bab36e78ba15014752210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352aeffffffff0240420f00000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288acf017ab010000000017a91421c4a435d9ac263ec55b35a1a5ca95e979639b9b8700000000"}},{"creator":"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5","createdTs":1405543835343,"seenBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543835343,"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543835968},"signedBy":{"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543835343},"rejectedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543850998},"sentTs":null,"sentTxid":null,"inputChainPaths":["m/45\'/0/0/1"],"comment":"2","builderObj":{"valueInSat":"29000000","valueOutSat":"1000000","feeSat":"10000","remainderSat":"27990000","hashToScriptMap":{"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb":"52210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352ae"},"selectedUtxos":[{"address":"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb","txid":"6c9da5b0da4bab0d576033325e987b10ccf2b9bf479d306b6aae36efeaa56892","vout":0,"ts":1405543698,"scriptPubKey":"a9147d274ac50968d7823b6cbc1b38770deb7157995387","amount":0.29,"confirmationsFromCache":false}],"inputsSigned":0,"signaturesAdded":1,"signhash":1,"spendUnconfirmed":true,"tx":"01000000019268a5eaef36ae6a6b309d47bfb9f2cc107b985e323360570dab4bdab0a59d6c0000000092004830450220302baae7de2e0f102bf3af2d5f450f673e51bd143020141a769ccdcdf16af188022100e7abc087c76050ed649e7139a5a136969e74e24a8d8f6223d3219ad033a26451014752210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352aeffffffff0240420f00000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288acf017ab010000000017a9148b102abba0729fb0690c61cf7187064d692d43d78700000000"}},{"creator":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","createdTs":1405543869803,"seenBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543869803,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543870411},"signedBy":{"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba":1405543869803,"02b0c868a3889cd0cfc0e7fef9eaa6d85d7cf6f7573ae5c9d1d13645d22e2eb7e5":1405543890406},"rejectedBy":{},"sentTs":1405543890913,"sentTxid":"6a0f61574ad65e537e7e99298968db565f97b894b61f4c8f8fac8fcaedb83e2b","inputChainPaths":["m/45\'/0/0/1"],"comment":"3","builderObj":{"valueInSat":"29000000","valueOutSat":"1100000","feeSat":"10000","remainderSat":"27890000","hashToScriptMap":{"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb":"52210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352ae"},"selectedUtxos":[{"address":"2N4eyXKikdnnUT4S74MRNAYqXChhUYmZ1Sb","txid":"6c9da5b0da4bab0d576033325e987b10ccf2b9bf479d306b6aae36efeaa56892","vout":0,"ts":1405543698,"scriptPubKey":"a9147d274ac50968d7823b6cbc1b38770deb7157995387","amount":0.29,"confirmationsFromCache":false}],"inputsSigned":1,"signaturesAdded":2,"signhash":1,"spendUnconfirmed":true,"tx":"01000000019268a5eaef36ae6a6b309d47bfb9f2cc107b985e323360570dab4bdab0a59d6c00000000db00483045022100a8ce7907f9fd7dd41dd65c2dec425e008efea06ee7c80787c10c0e210fbf181302207712c0fdd1cb25836ac1fc2fd303c1e26b85e8980417719b9ed50e977a9693ec01483045022100d1780c4f028cd898920aca3eaceba352ed9306cd17f019ae2f634e8facad149a02203c84ab2093da8e22577e93f27a732f0728d4e6db0c749f3cd3d898d6a025152a014752210359c6d0d0d31f83301169901a6ffad9535f14014b5ab3b43561dbb2436a7b813821037d06f713f13a11967fd5edca265ff4c77528693a712c482256505693e4890d9352aeffffffff02e0c81000000000001976a91485eb47fe98f349065d6f044e27a4ac541af79ee288ac5091a9010000000017a914cc1cab78458b1a951b91c6dcd7eeeeb682f506388700000000"}}],"walletId":"55d4bd062d32f90a","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPdWUAmaaopPftevC72Jtiu19V8ee5XijL9JvogqfR95uVrL85f8yBdQMq3KyQtG3Q91yWQb3XDbWWpcdWFDAmJ7Xy2XWkGJu","networkName":"testnet","privateKeyCache":{"m/45\'/0/0/0":"b6fd8d1a079efd523da34f31ba81f544fc3d0a728a8a98299d8980682518e79c","m/45\'/0/1/1":"0f4d52d2a99e4c8c1c2edf09fef12407c3abd2304b961198c3f131a8c8443a13","m/45\'/0/0/1":"de5c191c343bd6017b98708c03344849624a14e2c167cfd6eb8dcb075d139293"}},"addressBook":{"msj42CCGruhRsFrGATiUuh25dtxYtnpbTx":{"hidden":false,"createdTs":1405543109222,"copayerId":"02c7b87033e4357d8afc6ab7fe31fff054772ea6251f0d9c8a835b1c1ac74f6fba","label":"faucet","signature":"3045022067576e5b37f2707a8dc66e57511ad9b10a3125bd95193fff6f8f6402969c3bf3022100adff9f417db07d88face13b3d13f422740d4421440cade1a205684dfdc5d733a"}}}';
-var encryptedLegacyO = 'U2FsdGVkX19yGM1uBAIzQa8Po/dvUicmxt1YyRk/S97PcZ6I6rHMp9dMagIrehg4Qd6JHn/ustmFHS7vmBYj0EBpf6rdXiQezaWnVAJS9/xYjAO36EFUbl+NmUanuwujAxgYdSP/sNssRLeInvExmZYW993EEclxkwL6YUyX66kKsxGQo2oWng0NreBJNhFmrbOEWeFje2PiWP57oUjKsurFzwpluAAarUTYSLud+nXeabC7opzOP5yqniWBMJz0Ou8gpNCWCMhG/P9F9ccVPY7juyd0Hf41FVse8nd2++axKB57+paozLdO+HRfV6zkMqC3h8gWY7LkS75j3bvqcTw9LhXmzE0Sz21n9yDnRpA4chiAvtwQvvBGgj1pFMKhNQU6Obac9ZwKYzUTgdDn3Uzg1UlDzgyOh9S89rbRTV84WB+hXwhuVluWzbNNYV3vXe5PFrocVktIrtS3xQh+k/7my4A6/gRRrzNYpKrUASJqDS/9u9WBkG35xD63J/qXjtG2M0YPwbI57BK1IK4K510b8V72lz5U2XQrIC4ldBwni1rpSavwCJV9xF6hUdOmNV8fZsVHP0NeN1PYlLkSb2QgfuoWnkcsJerwuFR7GZC/i6efrswtpO0wMEQr/J0CLbeXlHAru6xxjCBhWoJvZpMGw72zgnDLoyMNsEVglNhx/VlV9ZMYkkdaEYAxPOEIyZdQ5MS+2jEAlXf818n/xzJSVrniCn9be8EPePvkw35pivprvy09vbW4cKsWBKvgIyoT6A3OhUOCCS8E9cg0WAjjav2EymrbKmGWRHaiD+EoJqaDg6s20zhHn1YEa/YwvGGSB5+Hg8baLHD8ZASvxz4cFFAAVZrBUedRFgHzqwaMUlFXLgueivWUj7RXlIw6GuNhLoo1QkhZMacf23hrFxxQYvGBRw1hekBuDmcsGWljA28udBxBd5f9i+3gErttMLJ6IPaud590uvrxRIclu0Sz9R2EQX64YJxqDtLpMY0PjddSMu8vaDRpK9/ZSrnz/xrXsyabaafz4rE/ItFXjwFUFkvtmuauHTz6nmuKjVfxvNLNAiKb/gI7vQyUhnTbKIApe7XyJsjedNDtZqsPoJRIzdDmrZYxGStbAZ7HThqFJlSJ9NPNhH+E2jm3TwL5mwt0fFZ5h+p497lHMtIcKffESo7KNa2juSVNMDREk0NcyxGXGiVB2FWl4sLdvyhcsVq0I7tmW6OGZKRf8W49GCJXq6Ie69DJ9LB1DO67NV1jsYbsLx9uhE2yEmpWZ3jkoCV/Eas4grxt0CGN6EavzQ==';
-var legacyPassword = '1';
+
+
+
+var legacy1 = '{"opts":{"id":"48ba2f1ffdfe9708","spendUnconfirmed":true,"requiredCopayers":1,"totalCopayers":1,"name":"pepe wallet","version":"0.4.7"},"networkNonce":"5405f06b00000001","networkNonces":[],"publicKeyRing":{"walletId":"48ba2f1ffdfe9708","networkName":"testnet","requiredCopayers":1,"totalCopayers":1,"indexes":[{"copayerIndex":2147483647,"changeIndex":0,"receiveIndex":1},{"copayerIndex":0,"changeIndex":0,"receiveIndex":1}],"copayersBackup":["0298f65b2694c55f9048bc05f10368242727c7f9d2065cbd788c3ecde1ec57f33f"],"copayersExtPubKeys":["tpubD9SGoP7CXsqSKTiQxCZSCpicDcophqnE4yuqjfw5M9tAR3fSjT9GDGwPEUFCN7SSmRKGDLZgKQePYFaLWyK32akeSan45TNTd8sgef9Ymh6"],"nicknameFor":{}},"txProposals":{"txps":[],"walletId":"48ba2f1ffdfe9708","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPfQCscb7CtJKzixxcVSyrCVcfr3WCFbtT8kYTzNubhjQ5R7AuYJgPCcSH4R8T34YVxeohKGhAB9wbB4eFBbQFjUpjGCqptHm","networkName":"testnet"},"addressBook":{}}';
diff --git a/test/test.WalletLock.js b/test/test.WalletLock.js
index f3ccf051d..6b2cb3ee2 100644
--- a/test/test.WalletLock.js
+++ b/test/test.WalletLock.js
@@ -11,12 +11,20 @@ if (is_browser) {
}
var copayConfig = require('../config');
var WalletLock = copay.WalletLock;
-
var PrivateKey = copay.PrivateKey;
-var Storage = require('./mocks/FakeStorage');
+var Storage = copay.Storage;
+
+
+var storage;
describe('WalletLock model', function() {
- var storage = new Storage();
+
+ beforeEach(function() {
+ storage = new Storage(require('./mocks/FakeLocalStorage').storageParams);
+ storage.setPassphrase('mysupercoolpassword');
+ storage.storage.clear();
+ storage.sessionStorage.clear();
+ });
it('should fail with missing args', function() {
(function() {
@@ -36,45 +44,68 @@ describe('WalletLock model', function() {
should.exist(w);
});
- it('should NOT fail if locked already', function() {
+
+ it('should generate a sessionId with init', function(done) {
+ var w = new WalletLock(storage, 'id');
+ var spy = sinon.spy(storage, 'getSessionId');
+ w.init(function() {
+ spy.calledOnce.should.equal(true);
+ done();
+ });
+ });
+
+ it('#keepAlive should call getsessionId if not called before', function(done) {
+ var w = new WalletLock(storage, 'id');
+ var spy = sinon.spy(storage, 'getSessionId');
+ w.keepAlive(function() {
+ spy.calledOnce.should.equal(true);
+ done();
+ });
+ });
+
+ it('should NOT fail if locked already by me', function(done) {
+ var w = new WalletLock(storage, 'walletId2');
+ w.keepAlive(function() {
+ var w2 = new WalletLock(storage, 'walletId2');
+ w2.init(function() {
+ w2.keepAlive(function() {
+ w.sessionId.should.equal(w2.sessionId);
+ should.exist(w2);
+ done();
+ });
+ });
+ })
+ });
+
+ it('should FAIL if locked by someone else', function(done) {
var w = new WalletLock(storage, 'walletId');
- storage.sessionId = 'xxx';
- var w2= new WalletLock(storage, 'walletId');
- should.exist(w2);
- });
+ w.keepAlive(function() {
+ storage.setSessionId('session2', function() {
+ var w2 = new WalletLock(storage, 'walletId');
+ w2.keepAlive(function(locked) {
+ should.exist(locked);
+ locked.message.should.contain('LOCKED');
+ done();
+ });
+ });
+ });
+ })
- it('should change status of previously openned wallet', function() {
- storage.sessionId = 'session1';
+ it('should FAIL if locked by someone else but expired', function(done) {
var w = new WalletLock(storage, 'walletId');
- storage.sessionId = 'xxx';
- var w2= new WalletLock(storage, 'walletId');
- w2.keepAlive();
- (function() {w.keepAlive();}).should.throw('already open');
-
- });
-
-
- it('should not fail if locked by me', function() {
- var s = new Storage();
- var w = new WalletLock(s, 'walletId');
- var w2 = new WalletLock(s, 'walletId')
- w2.keepAlive();
- should.exist(w2);
- });
-
- it('should not fail if expired', function() {
- var s = new Storage();
- var w = new WalletLock(s, 'walletId');
- var k = Object.keys(s.storage)[0];
- var v = JSON.parse(s.storage[k]);
- v.expireTs = Date.now() - 60 * 6 * 1000;
- s.storage[k] = JSON.stringify(v);
-
- s.sessionId = 'xxx';
- var w2 = new WalletLock(s, 'walletId')
- should.exist(w2);
- });
-
-
+ w.keepAlive(function() {
+ storage.setSessionId('session2', function() {
+ var json = JSON.parse(storage.storage.ls['lock::walletId']);
+ json.expireTs -= 3600 * 1000;
+ storage.storage.ls['lock::walletId'] = JSON.stringify(json);
+ var w2 = new WalletLock(storage, 'walletId');
+ w2.keepAlive(function(locked) {
+ w2.sessionId.should.equal('session2');
+ should.not.exist(locked);
+ done();
+ });
+ });
+ });
+ })
});
diff --git a/test/test.blockchain.Insight.js b/test/test.blockchain.Insight.js
index b43003ed6..032725f18 100644
--- a/test/test.blockchain.Insight.js
+++ b/test/test.blockchain.Insight.js
@@ -40,9 +40,7 @@ var UNSPENT = [{
}];
var FAKE_OPTS = {
- host: 'something.com',
- port: 123,
- schema: 'http'
+ url: 'http://something.com:123',
}
describe('Insight model', function() {
@@ -348,7 +346,7 @@ describe('Insight model', function() {
});
describe('Events', function() {
- it('should emmit event on a new block', function(done) {
+ it('should emit event on a new block', function(done) {
var blockchain = new Insight(FAKE_OPTS);
var socket = blockchain.getSocket();
blockchain.on('connect', function() {
@@ -362,7 +360,7 @@ describe('Insight model', function() {
});
});
- it('should emmit event on a transaction for subscribed addresses', function(done) {
+ it('should emit event on a transaction for subscribed addresses', function(done) {
var blockchain = new Insight(FAKE_OPTS);
var socket = blockchain.getSocket();
blockchain.subscribe('2NFjCBFZSsxiwWAD7CKQ3hzWFtf9DcqTucY');
@@ -378,7 +376,7 @@ describe('Insight model', function() {
});
});
- it('should\'t emmit event on a transaction for non subscribed addresses', function(done) {
+ it('should\'t emit event on a transaction for non subscribed addresses', function(done) {
var blockchain = new Insight(FAKE_OPTS);
var socket = blockchain.getSocket();
blockchain.on('connect', function() {
@@ -392,7 +390,7 @@ describe('Insight model', function() {
});
});
- it('should emmit event on connection', function(done) {
+ it('should emit event on connection', function(done) {
var blockchain = new Insight(FAKE_OPTS);
var socket = blockchain.getSocket();
blockchain.on('connect', function() {
@@ -400,7 +398,7 @@ describe('Insight model', function() {
});
});
- it('should emmit event on disconnection', function(done) {
+ it('should emit event on disconnection', function(done) {
var blockchain = new Insight(FAKE_OPTS);
var socket = blockchain.getSocket();
blockchain.on('connect', function() {
diff --git a/test/test.network.Async.js b/test/test.network.Async.js
index 7de41751f..b7097c61f 100644
--- a/test/test.network.Async.js
+++ b/test/test.network.Async.js
@@ -13,7 +13,9 @@ describe('Network / Async', function() {
var createN = function(pk) {
- var n = new Async();
+ var n = new Async({
+ url: 'http://insight.example.com:1234'
+ });
var fakeSocket = {};
fakeSocket.emit = function() {};
fakeSocket.on = function() {};
diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js
index abd154840..86c0ba7af 100644
--- a/test/unit/controllers/controllersSpec.js
+++ b/test/unit/controllers/controllersSpec.js
@@ -5,24 +5,29 @@
var sinon = require('sinon');
// Replace saveAs plugin
-saveAsLastCall = null;
-saveAs = function(o) {
- saveAsLastCall = o;
+saveAs = function(blob, filename) {
+ saveAsLastCall = {
+ blob: blob,
+ filename: filename
+ };
};
var startServer = require('../../mocks/FakePayProServer');
describe("Unit: Controllers", function() {
+ config.plugins.LocalStorage=true;
+ config.plugins.GoogleDrive=null;
+
var invalidForm = {
$invalid: true
};
var scope;
-
var server;
beforeEach(module('copayApp.services'));
beforeEach(module('copayApp.controllers'));
+ beforeEach(angular.mock.module('copayApp'));
var walletConfig = {
requiredCopayers: 3,
@@ -34,11 +39,6 @@ describe("Unit: Controllers", function() {
alternativeIsoCode: 'LOL'
};
- it('Copay config should be binded', function() {
- should.exist(config);
- should.exist(config.unitToSatoshi);
- });
-
describe('More Controller', function() {
var ctrl;
@@ -50,21 +50,32 @@ describe("Unit: Controllers", function() {
$scope: scope,
$modal: {},
});
+ saveAsLastCall = null;
}));
it('Backup controller #download', function() {
scope.wallet.setEnc('1234567');
expect(saveAsLastCall).equal(null);
scope.downloadBackup();
- expect(saveAsLastCall.size).equal(7);
- expect(saveAsLastCall.type).equal('text/plain;charset=utf-8');
+ expect(saveAsLastCall.blob.size).equal(7);
+ expect(saveAsLastCall.blob.type).equal('text/plain;charset=utf-8');
});
- it('Backup controller #delete', function() {
- expect(scope.wallet).not.equal(undefined);
- scope.deleteWallet();
- expect(scope.wallet).equal(undefined);
+ it('Backup controller should name backup correctly for multiple copayers', function() {
+ scope.wallet.setEnc('1234567');
+ expect(saveAsLastCall).equal(null);
+ scope.downloadBackup();
+ expect(saveAsLastCall.filename).equal('myNickname-myTESTwullet-testID-keybackup.json.aes');
});
+
+ it('Backup controller should name backup correctly for 1-1 wallet', function() {
+ scope.wallet.setEnc('1234567');
+ expect(saveAsLastCall).equal(null);
+ scope.wallet.totalCopayers = 1;
+ scope.downloadBackup();
+ expect(saveAsLastCall.filename).equal('myTESTwullet-testID-keybackup.json.aes');
+ });
+
});
describe('Create Controller', function() {
@@ -110,6 +121,7 @@ describe("Unit: Controllers", function() {
var transactionsCtrl;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
+ $rootScope.wallet = new FakeWallet(walletConfig);
transactionsCtrl = $controller('TransactionsController', {
$scope: scope,
});
@@ -131,7 +143,11 @@ describe("Unit: Controllers", function() {
beforeEach(module(function($provide) {
$provide.value('request', {
'get': function(_, cb) {
- cb(null, null, [{name: 'lol currency', code: 'LOL', rate: 2}]);
+ cb(null, null, [{
+ name: 'lol currency',
+ code: 'LOL',
+ rate: 2
+ }]);
}
});
}));
@@ -139,8 +155,8 @@ describe("Unit: Controllers", function() {
scope = $rootScope.$new();
scope.rateService = rateService;
$rootScope.wallet = new FakeWallet(walletConfig);
- config.alternativeName = 'lol currency';
- config.alternativeIsoCode = 'LOL';
+ $rootScope.wallet.settings.alternativeName = 'lol currency';
+ $rootScope.wallet.settings.alternativeIsoCode = 'LOL';
var element = angular.element(
'
-
+