Merge pull request #1281 from maraoz/refactor/settings

Refactor settings
This commit is contained in:
Matias Alejo Garcia 2014-09-11 20:28:21 -03:00
commit d358330d1d
40 changed files with 697 additions and 655 deletions

View file

@ -6,11 +6,6 @@ var defaultConfig = {
forceNetwork: false,
logLevel: 'info',
// DEFAULT unit: Bit
unitName: 'bits',
unitToSatoshi: 100,
alternativeName: 'US Dollar',
alternativeIsoCode: 'USD',
// wallet limits
limits: {
@ -20,9 +15,12 @@ var defaultConfig = {
// network layer config
network: {
host: 'test-insight.bitpay.com',
port: 443,
schema: 'https'
testnet: {
url: 'https://test-insight.bitpay.com:443'
},
livenet: {
url: 'https://insight.bitpay.com:443'
},
},
// wallet default config
@ -30,25 +28,15 @@ var defaultConfig = {
requiredCopayers: 2,
totalCopayers: 3,
spendUnconfirmed: true,
verbose: 1,
// will duplicate itself after each try
reconnectDelay: 5000,
idleDurationMin: 4
},
// blockchain service API config
blockchain: {
schema: 'https',
host: 'test-insight.bitpay.com',
port: 443,
retryDelay: 1000,
},
// socket service API config
socket: {
schema: 'https',
host: 'test-insight.bitpay.com',
port: 443,
reconnectDelay: 1000,
idleDurationMin: 4,
settings: {
unitName: 'bits',
unitToSatoshi: 100,
unitDecimals: 2,
alternativeName: 'US Dollar',
alternativeIsoCode: 'USD',
}
},
// local encryption/security config
@ -62,7 +50,6 @@ var defaultConfig = {
updateFrequencySeconds: 60 * 60
},
verbose: 1,
};
if (typeof module !== 'undefined')
module.exports = defaultConfig;
module.exports = defaultConfig;

View file

@ -17,8 +17,8 @@
<i class="fi-loop icon-rotate m15r"></i>
<span translate> Network Error. Attempting to reconnect...</span>
</span>
<nav class="tab-bar" ng-class="{'hide-tab-bar' : !$root.wallet ||
!$root.wallet.isReady() || $root.wallet.isLocked}">
<nav class="tab-bar" ng-if="$root.wallet &&
$root.wallet.isReady() && !$root.wallet.isLocked">
<section class="left-small">
<a class="left-off-canvas-toggle menu-icon" ><span></span></a>
</section>
@ -28,7 +28,7 @@
<section class="middle tab-bar-section">
<h1 class="right">
{{totalBalance || 0 |noFractionNumber}} {{$root.unitName}}
{{totalBalance || 0 |noFractionNumber}} {{$root.wallet.settings.unitName}}
</h1>
<h1 class="title ellipsis">
{{$root.wallet.getName()}}

View file

@ -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,6 +84,7 @@ 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);

View file

@ -118,7 +118,6 @@ 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) {
@ -129,7 +128,7 @@ 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

View file

@ -1,45 +1,97 @@
'use strict';
angular.module('copayApp.controllers').controller('MoreController',
function($scope, $rootScope, $location, backupService, walletFactory, controllerUtils, notification) {
function($scope, $rootScope, $location, 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');
};
$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);
}
controllerUtils.updateAddressList();
controllerUtils.updateBalance(function(){
controllerUtils.updateBalance(function() {
notification.info('Finished', 'The balance is updated using the derived addresses');
w.sendIndexes();
});

View file

@ -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;
}
@ -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';
@ -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,7 +416,6 @@ 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');
@ -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,7 +459,6 @@ 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;
@ -497,7 +494,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 +505,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 +535,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;
@ -587,8 +584,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);
});
};
});
});

View file

@ -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
}));

View file

@ -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

View file

@ -3,9 +3,11 @@
angular.module('copayApp.controllers').controller('VersionController',
function($scope, $rootScope, $http, notification) {
var w = $rootScope.wallet;
$scope.version = copay.version;
$scope.commitHash = copay.commitHash;
$scope.networkName = config.networkName;
$scope.networkName = w ? w.getNetworkName() : '';
$scope.defaultLanguage = config.defaultLanguage;
if (_.isUndefined($rootScope.checkVersion))
$rootScope.checkVersion = true;
@ -30,4 +32,4 @@ angular.module('copayApp.controllers').controller('VersionController',
});
}
});
});

View file

@ -1,10 +1,12 @@
'use strict';
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) {
var bitcore = require('bitcore');
var Address = bitcore.Address;
var bignum = bitcore.Bignum;
return {
require: 'ngModel',
@ -28,14 +30,14 @@ angular.module('copayApp.directives')
// 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;
};
@ -46,14 +48,17 @@ angular.module('copayApp.directives')
}])
.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;
@ -270,7 +275,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!');

View file

@ -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;
};
}
]);

View file

@ -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', {

View file

@ -73,9 +73,9 @@ function Wallet(opts) {
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;
@ -112,6 +112,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
@ -161,6 +176,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 <tt>senderId</tt>.
@ -570,6 +601,7 @@ Wallet.prototype._optsToObj = function() {
totalCopayers: this.totalCopayers,
name: this.name,
version: this.version,
networkName: this.getNetworkName(),
};
return obj;
@ -615,7 +647,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 +666,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,
}
};
@ -780,9 +818,7 @@ Wallet.prototype.keepAlive = function() {
*/
Wallet.prototype.store = function() {
this.keepAlive();
var wallet = this.toObj();
this.storage.setFromObj(this.id, wallet);
this.storage.setFromObj(this.id, this.toObj());
log.debug('Wallet stored');
};
@ -793,13 +829,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,
@ -831,6 +865,7 @@ Wallet.fromObj = function(o, storage, network, blockchain) {
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);
@ -896,7 +931,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);
});
@ -2522,4 +2556,4 @@ Wallet.request = function(options, callback) {
return ret;
};
module.exports = Wallet;
module.exports = Wallet;

View file

@ -9,6 +9,7 @@ var log = require('../../log');
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();
/**
* @desc
@ -23,10 +24,8 @@ 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
@ -41,10 +40,15 @@ function WalletFactory(config, version) {
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);
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.networkName = config.networkName;
this.walletDefaults = config.wallet;
this.version = version;
};
@ -71,33 +75,41 @@ WalletFactory.prototype._checkRead = function(walletId) {
return !!ret;
};
/**
* @desc obtain network name from serialized wallet
* @param {Object} wallet object
* @return {string} network name
*/
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) {
var networkName = this.obtainNetworkName(obj);
preconditions.checkState(networkName);
// not stored options
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;
};
@ -147,13 +159,9 @@ WalletFactory.prototype.read = function(walletId, skipFields) {
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');
_.each(Wallet.PERSISTED_PROPERTIES, function(value) {
obj[value] = s.get(walletId, value);
});
var w = this.fromObj(obj, skipFields);
return w;
@ -179,14 +187,17 @@ WalletFactory.prototype.read = function(walletId, skipFields) {
* @return {Wallet}
*/
WalletFactory.prototype.create = function(opts) {
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 +208,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 +219,15 @@ 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;
@ -245,20 +255,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.');
}
};
@ -271,8 +270,9 @@ WalletFactory.prototype._checkNetwork = function(inNetworkName) {
WalletFactory.prototype.open = function(walletId, passphrase) {
this.storage._setPassphrase(passphrase);
var w = this.read(walletId);
if (w)
if (w) {
w.store();
}
this.storage.setLastOpened(walletId);
return w;
@ -338,14 +338,16 @@ WalletFactory.prototype.decodeSecret = function(secret) {
*/
WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphrase, privateHex, cb) {
var self = this;
var s = self.decodeSecret(secret);
if (!s) return cb('badSecret');
var decodedSecret = this.decodeSecret(secret);
if (!decodedSecret || !decodedSecret.networkName || !decodedSecret.pubKey) {
return cb('badSecret');
}
var privOpts = {
networkName: this.networkName,
networkName: decodedSecret.networkName,
};
if (privateHex && privateHex.length>1) {
if (privateHex && privateHex.length > 1) {
privOpts.extendedPrivateKeyString = privateHex;
}
@ -356,25 +358,27 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras
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('serverError', function() {
return cb('joinError');
});
self.network.start(opts, function() {
self.network.greet(s.pubKey,opts.secretNumber);
self.network.on('data', function(sender, data) {
joinNetwork.start(opts, function() {
joinNetwork.greet(decodedSecret.pubKey, opts.secretNumber);
joinNetwork.on('data', function(sender, data) {
if (data.type === 'walletId') {
if (data.networkName !== self.networkName) {
if (data.networkName !== decodedSecret.networkName) {
return cb('badNetwork');
}
@ -383,8 +387,7 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras
data.opts.passphrase = passphrase;
data.opts.id = data.walletId;
var w = self.create(data.opts);
w.sendWalletReady(s.pubKey);
//w.seedCopayer(s.pubKey);
w.sendWalletReady(decodedSecret.pubKey);
return cb(null, w);
} else {
return cb('walletFull', w);

View file

@ -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,
});
};

View file

@ -50,7 +50,6 @@ angular.module('copayApp.services')
$scope.loading = false;
});
w.on('corrupt', function(peerId) {
notification.error('Error', 'Received corrupt message from ' + peerId);
});
@ -176,7 +175,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,9 +195,9 @@ 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);
return cb ? cb() : null;
@ -211,7 +210,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 +234,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
})) {

View file

@ -9,7 +9,7 @@
"bugs": {
"url": "https://github.com/bitpay/copay/issues"
},
"version": "0.5.0",
"version": "0.6.0",
"dependencies": {
"browser-request": "^0.3.2",
"inherits": "^2.0.1",

View file

@ -11,7 +11,7 @@ FakeBlockchain.prototype.getTransaction = function(txid, cb) {
};
FakeBlockchain.prototype.getTransactions = function(addresses, cb) {
return cb(null, []);
cb(null, []);
};

View file

@ -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() {
@ -37,11 +36,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 +61,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;
@ -98,8 +110,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 +120,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

View file

@ -86,7 +86,6 @@ describe('PayPro (in Wallet) model', function() {
};
c.networkName = walletConfig.networkName;
c.verbose = walletConfig.verbose;
c.version = '0.0.1';
return new Wallet(c);

View file

@ -102,7 +102,6 @@ describe('Wallet model', function() {
};
c.networkName = walletConfig.networkName;
c.verbose = walletConfig.verbose;
c.version = '0.0.1';
@ -363,7 +362,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();
});
@ -1533,4 +1543,4 @@ describe('Wallet model', function() {
should.exist(n.networkNonce);
});
});
});

File diff suppressed because one or more lines are too long

View file

@ -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() {

View file

@ -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() {};

View file

@ -34,12 +34,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;
beforeEach(inject(function($controller, $rootScope) {
@ -110,6 +104,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 +126,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 +138,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(
'<form name="form">' +
'<input type="text" id="newaddress" name="newaddress" ng-disabled="loading" placeholder="Address" ng-model="newaddress" valid-address required>' +
@ -224,35 +223,35 @@ describe("Unit: Controllers", function() {
sinon.assert.callCount(spy2, 0);
sinon.assert.callCount(scope.loadTxs, 1);
spy.getCall(0).args[0].should.equal('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
spy.getCall(0).args[1].should.equal(1000 * config.unitToSatoshi);
spy.getCall(0).args[1].should.equal(1000 * scope.wallet.settings.unitToSatoshi);
(typeof spy.getCall(0).args[2]).should.equal('undefined');
});
it('should handle big values in 100 BTC', function() {
var old = config.unitToSatoshi;
config.unitToSatoshi = 100000000;;
var old = scope.wallet.settings.unitToSatoshi;
scope.wallet.settings.unitToSatoshi = 100000000;;
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
sendForm.amount.$setViewValue(100);
var spy = sinon.spy(scope.wallet, 'createTx');
scope.loadTxs = sinon.spy();
scope.submitForm(sendForm);
spy.getCall(0).args[1].should.equal(100 * config.unitToSatoshi);
config.unitToSatoshi = old;
spy.getCall(0).args[1].should.equal(100 * scope.wallet.settings.unitToSatoshi);
scope.wallet.settings.unitToSatoshi = old;
});
it('should handle big values in 5000 BTC', function() {
var old = config.unitToSatoshi;
config.unitToSatoshi = 100000000;;
it('should handle big values in 5000 BTC', inject(function($rootScope) {
var old = $rootScope.wallet.settings.unitToSatoshi;
$rootScope.wallet.settings.unitToSatoshi = 100000000;;
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
sendForm.amount.$setViewValue(5000);
var spy = sinon.spy(scope.wallet, 'createTx');
scope.loadTxs = sinon.spy();
scope.submitForm(sendForm);
spy.getCall(0).args[1].should.equal(5000 * config.unitToSatoshi);
config.unitToSatoshi = old;
});
spy.getCall(0).args[1].should.equal(5000 * $rootScope.wallet.settings.unitToSatoshi);
$rootScope.wallet.settings.unitToSatoshi = old;
}));
it('should convert bits amount to fiat', function(done) {
scope.rateService.whenAvailable(function() {
@ -305,15 +304,15 @@ describe("Unit: Controllers", function() {
beforeEach(inject(function($controller, $injector) {
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when('GET', GH)
.respond([{
name: "v100.1.6",
zipball_url: "https://api.github.com/repos/bitpay/copay/zipball/v0.0.6",
tarball_url: "https://api.github.com/repos/bitpay/copay/tarball/v0.0.6",
commit: {
sha: "ead7352bf2eca705de58d8b2f46650691f2bc2c7",
url: "https://api.github.com/repos/bitpay/copay/commits/ead7352bf2eca705de58d8b2f46650691f2bc2c7"
}
}]);
.respond([{
name: "v100.1.6",
zipball_url: "https://api.github.com/repos/bitpay/copay/zipball/v0.0.6",
tarball_url: "https://api.github.com/repos/bitpay/copay/tarball/v0.0.6",
commit: {
sha: "ead7352bf2eca705de58d8b2f46650691f2bc2c7",
url: "https://api.github.com/repos/bitpay/copay/commits/ead7352bf2eca705de58d8b2f46650691f2bc2c7"
}
}]);
}));
var rootScope;
@ -358,11 +357,6 @@ describe("Unit: Controllers", function() {
scope.$apply();
});
it('should return networkName', function() {
$httpBackend.flush(); // need flush
var networkName = scope.networkName;
expect(networkName).equal('testnet');
});
});
describe("Unit: Sidebar Controller", function() {
@ -390,6 +384,7 @@ describe("Unit: Controllers", function() {
beforeEach(inject(function($compile, $rootScope, $controller) {
scope = $rootScope.$new();
$rootScope.availableBalance = 123456;
$rootScope.wallet = new FakeWallet(walletConfig);
var element = angular.element(
'<form name="form">' +

View file

@ -8,17 +8,23 @@ describe("Unit: Testing Directives", function() {
beforeEach(module('copayApp.directives'));
beforeEach(function() {
config.unitToSatoshi = 100;
config.unitName = 'bits';
});
var walletConfig = {
requiredCopayers: 3,
totalCopayers: 5,
spendUnconfirmed: 1,
reconnectDelay: 100,
networkName: 'testnet',
alternativeName: 'lol currency',
alternativeIsoCode: 'LOL'
};
describe('Check config', function() {
it('unit should be set to BITS in config.js', function() {
expect(config.unitToSatoshi).to.equal(100);
expect(config.unitName).to.equal('bits');
});
});
beforeEach(inject(function($rootScope) {
$rootScope.wallet = new FakeWallet(walletConfig);
var w = $rootScope.wallet;
w.settings.unitToSatoshi = 100;
w.settings.unitName = 'bits';
}));
describe('Validate Address', function() {
beforeEach(inject(function($compile, $rootScope) {
@ -36,16 +42,16 @@ describe("Unit: Testing Directives", function() {
form = $scope.form;
}));
it('should validate with network', function() {
config.networkName = 'testnet';
it('should validate with network', inject(function($rootScope) {
$rootScope.wallet.getNetworkName = sinon.stub().returns('testnet');
form.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
expect(form.address.$invalid).to.equal(false);
});
it('should not validate with other network', function() {
config.networkName = 'livenet';
}));
it('should not validate with other network', inject(function($rootScope) {
$rootScope.wallet.getNetworkName = sinon.stub().returns('livenet');
form.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
expect(form.address.$invalid).to.equal(true);
});
}));
it('should not validate random', function() {
form.address.$setViewValue('thisisaninvalidaddress');
expect(form.address.$invalid).to.equal(true);
@ -94,9 +100,12 @@ describe("Unit: Testing Directives", function() {
describe('Unit: BTC', function() {
beforeEach(inject(function($compile, $rootScope) {
config.unitToSatoshi = 100000000;
config.unitName = 'BTC';
$scope = $rootScope;
var w = new FakeWallet(walletConfig);
w.settings.unitToSatoshi = 100000000;
w.settings.unitName = 'BTC';
$rootScope.wallet = w;
$rootScope.availableBalance = 0.04;
var element = angular.element(
'<form name="form">' +

View file

@ -5,6 +5,15 @@
describe('Unit: Testing Filters', function() {
beforeEach(module('copayApp.filters'));
var walletConfig = {
requiredCopayers: 3,
totalCopayers: 5,
spendUnconfirmed: 1,
reconnectDelay: 100,
networkName: 'testnet',
alternativeName: 'lol currency',
alternativeIsoCode: 'LOL'
};
describe('limitAddress', function() {
@ -103,68 +112,76 @@ describe('Unit: Testing Filters', function() {
}));
});
describe('noFractionNumber bits', function() {
beforeEach(function() {
config.unitToSatoshi = 100;
config.unitName = 'bits';
describe('noFractionNumber', function() {
describe('noFractionNumber bits', function() {
beforeEach(inject(function($rootScope) {
$rootScope.wallet = new FakeWallet(walletConfig);
var w = $rootScope.wallet;
w.settings.unitToSatoshi = 100;
w.settings.unitName = 'bits';
}));
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(3100)).to.equal('3,100');
expect(noFraction(3100200)).to.equal('3,100,200');
expect(noFraction(3)).to.equal('3');
expect(noFraction(0.3)).to.equal(0.3);
expect(noFraction(0.30000000)).to.equal(0.3);
expect(noFraction(3200.01)).to.equal('3,200.01');
expect(noFraction(3200890.010000)).to.equal('3,200,890.01');
}));
});
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(3100)).to.equal('3,100');
expect(noFraction(3100200)).to.equal('3,100,200');
expect(noFraction(3)).to.equal('3');
expect(noFraction(0.3)).to.equal(0.3);
expect(noFraction(0.30000000)).to.equal(0.3);
expect(noFraction(3200.01)).to.equal('3,200.01');
expect(noFraction(3200890.010000)).to.equal('3,200,890.01');
}));
});
describe('noFractionNumber BTC', function() {
beforeEach(function() {
config.unitToSatoshi = 100000000;
config.unitName = 'BTC';
describe('noFractionNumber BTC', function() {
beforeEach(inject(function($rootScope) {
$rootScope.wallet = new FakeWallet(walletConfig);
var w = $rootScope.wallet;
w.settings.unitToSatoshi = 100000000;
w.settings.unitName = 'BTC';
}));
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(0.30000000)).to.equal(0.3);
expect(noFraction(0.00302000)).to.equal(0.00302);
expect(noFraction(1.00000001)).to.equal(1.00000001);
expect(noFraction(3.10000012)).to.equal(3.10000012);
expect(noFraction(0.00100000)).to.equal(0.001);
expect(noFraction(0.00100009)).to.equal(0.00100009);
expect(noFraction(2000.00312011)).to.equal('2,000.00312011');
expect(noFraction(2000998.00312011)).to.equal('2,000,998.00312011');
}));
});
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(0.30000000)).to.equal(0.3);
expect(noFraction(0.00302000)).to.equal(0.00302);
expect(noFraction(1.00000001)).to.equal(1.00000001);
expect(noFraction(3.10000012)).to.equal(3.10000012);
expect(noFraction(0.00100000)).to.equal(0.001);
expect(noFraction(0.00100009)).to.equal(0.00100009);
expect(noFraction(2000.00312011)).to.equal('2,000.00312011');
expect(noFraction(2000998.00312011)).to.equal('2,000,998.00312011');
}));
});
describe('noFractionNumber mBTC', function() {
beforeEach(function() {
config.unitToSatoshi = 100000;
config.unitName = 'mBTC';
describe('noFractionNumber mBTC', function() {
beforeEach(inject(function($rootScope) {
$rootScope.wallet = new FakeWallet(walletConfig);
var w = $rootScope.wallet;
w.settings.unitToSatoshi = 100000;
w.settings.unitName = 'mBTC';
}));
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(0.30000)).to.equal(0.3);
expect(noFraction(0.00302)).to.equal(0.00302);
expect(noFraction(1.00001)).to.equal(1.00001);
expect(noFraction(3.10002)).to.equal(3.10002);
expect(noFraction(0.00100000)).to.equal(0.001);
expect(noFraction(0.00100009)).to.equal(0.001);
expect(noFraction(2000.00312)).to.equal('2,000.00312');
expect(noFraction(2000998.00312)).to.equal('2,000,998.00312');
}));
});
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(0.30000)).to.equal(0.3);
expect(noFraction(0.00302)).to.equal(0.00302);
expect(noFraction(1.00001)).to.equal(1.00001);
expect(noFraction(3.10002)).to.equal(3.10002);
expect(noFraction(0.00100000)).to.equal(0.001);
expect(noFraction(0.00100009)).to.equal(0.001);
expect(noFraction(2000.00312)).to.equal('2,000.00312');
expect(noFraction(2000998.00312)).to.equal('2,000,998.00312');
}));
});
describe('noFractionNumber:custom fractionSize', function() {
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(0.30000, 0)).to.equal('0');
expect(noFraction(1.00001, 0)).to.equal('1');
expect(noFraction(3.10002, 0)).to.equal('3');
expect(noFraction(2000.00312, 0)).to.equal('2,000');
expect(noFraction(2000998.00312, 0)).to.equal('2,000,998');
}));
});
describe('noFractionNumber:custom fractionSize', function() {
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(0.30000, 0)).to.equal('0');
expect(noFraction(1.00001, 0)).to.equal('1');
expect(noFraction(3.10002, 0)).to.equal('3');
expect(noFraction(2000.00312, 0)).to.equal('2,000');
expect(noFraction(2000998.00312, 0)).to.equal('2,000,998');
}));
});
});
});

View file

@ -4,19 +4,7 @@
//
//
var sinon = require('sinon');
beforeEach(function() {
config.unitToSatoshi = 100;
config.unitName = 'bits';
});
describe('Check config', function() {
it('unit should be set to BITS in config.js', function() {
expect(config.unitToSatoshi).to.equal(100);
expect(config.unitName).to.equal('bits');
});
});
var preconditions = require('preconditions').singleton();
describe("Unit: Walletfactory Service", function() {
beforeEach(angular.mock.module('copayApp.services'));

View file

@ -15,7 +15,9 @@ var getCommitHash = function() {
//exec git command to get the hash of the current commit
//git rev-parse HEAD
var hash = shell.exec('git rev-parse HEAD',{silent:true}).output.trim().substr(0,7);
var hash = shell.exec('git rev-parse HEAD', {
silent: true
}).output.trim().substr(0, 7);
return hash;
}
@ -23,7 +25,7 @@ var createVersion = function() {
var json = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
var content = 'module.exports.version="' + json.version + '";';
content = content + '\nmodule.exports.commitHash="' + getCommitHash() + '";';
content = content + '\nmodule.exports.commitHash="' + getCommitHash() + '";';
fs.writeFileSync("./version.js", content);
};
@ -43,9 +45,9 @@ var createBundle = function(opts) {
b.require('browser-request', {
expose: 'request'
});
b.require('underscore', {
expose: 'underscore'
});
b.require('underscore');
b.require('assert');
b.require('preconditions');
b.require('./copay', {
expose: 'copay'
@ -130,10 +132,10 @@ if (require.main === module) {
};
var program = require('commander');
program
.version('0.0.1')
.option('-d, --debug', 'Development. Don\'t minify the codem and include debug packages.')
.option('-o, --stdout', 'Specify output as stdout')
.parse(process.argv);
.version('0.0.1')
.option('-d, --debug', 'Development. Don\'t minify the codem and include debug packages.')
.option('-o, --stdout', 'Specify output as stdout')
.parse(process.argv);
createVersion();
var copayBundle = createBundle(program);

View file

@ -4,39 +4,39 @@
<span translate>Addresses</span>
<span class="button primary small side-bar" ng-click="newAddr()" ng-disabled="loading"><i class="fi-plus"></i></span>
</h1>
<div class="large-12 medium-12" ng-if="!!(addresses|removeEmpty).length">
<div class="large-12 medium-12" ng-init="showAll=0">
<div class="panel radius oh" ng-repeat="addr in addresses|removeEmpty|limitAddress:showAll">
<div class="row collapse">
<div class="large-10 medium-9 small-8 column" >
<div class="ellipsis list-addr">
<i class="fi-thumbnails size-48 show-for-large-up" ng-click="openAddressModal(addr)">&nbsp;</i>
<span>
<div class="panel radius oh" ng-repeat="addr in addresses|removeEmpty|limitAddress:showAll">
<div class="row collapse">
<div class="large-10 medium-9 small-8 column">
<div class="ellipsis list-addr">
<i class="fi-thumbnails size-48 show-for-large-up" ng-click="openAddressModal(addr)">&nbsp;</i>
<span>
<contact address="{{addr.address}}" tooltip-popup-delay="500" tooltip tooltip-placement="right"/>
</span>
<span class="btn-copy" clip-copy="addr.address"> </span>
<small translate class="label" ng-if="addr.isChange">change</small>
</div>
</div>
<div class="large-2 medium-3 small-4 column text-right">
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span class="size-12" ng-if="!$root.updatingBalance">
{{addr.balance || 0|noFractionNumber}} {{$root.unitName}}
</span>
<span class="btn-copy" clip-copy="addr.address"> </span>
<small translate class="label" ng-if="addr.isChange">change</small>
</div>
</div>
<div class="large-2 medium-3 small-4 column text-right">
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span class="size-12" ng-if="!$root.updatingBalance">
{{addr.balance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}}
</span>
</div>
</div>
<a class="secondary radius" ng-click="showAll=!showAll" ng-show="(addresses|removeEmpty).length != (addresses|removeEmpty|limitAddress).length">
<span translate ng-if="!showAll">Show all</span>
<span translate ng-if="showAll">Show less</span>
</a>
</div>
</div>
<a class="secondary radius" ng-click="showAll=!showAll" ng-show="(addresses|removeEmpty).length != (addresses|removeEmpty|limitAddress).length">
<span translate ng-if="!showAll">Show all</span>
<span translate ng-if="showAll">Show less</span>
</a>
</div>
</div>
</div>

View file

@ -25,42 +25,31 @@
</div>
<div>
<label for="walletPassword"><span translate>Your Wallet Password</span>
<small translate data-options="disable_for_touch:true" class="has-tip text-gray" tooltip="doesn't need to be shared" >Required</small>
<small translate data-options="disable_for_touch:true" class="has-tip text-gray" tooltip="doesn't need to be shared">Required</small>
</label>
<input id="walletPassword" type="password"
placeholder="{{'Choose your password'|translate}}" class="form-control"
ng-model="$parent.walletPassword"
name="walletPassword"
check-strength="passwordStrength"
tooltip-html-unsafe="Password strength:
<input id="walletPassword" type="password" placeholder="{{'Choose your password'|translate}}" class="form-control" ng-model="$parent.walletPassword" name="walletPassword" check-strength="passwordStrength" tooltip-html-unsafe="Password strength:
<i>{{passwordStrength}}</i><br/><span
class='size-12'>Tip: Use lower and uppercase, numbers and
symbols</span>"
tooltip-trigger="focus" required
tooltip-placement="top">
<input type="password"
placeholder="{{'Repeat password'|translate}}"
name="walletPasswordConfirm"
ng-model="walletPasswordConfirm"
match="walletPassword"
required>
symbols</span>" tooltip-trigger="focus" required tooltip-placement="top">
<input type="password" placeholder="{{'Repeat password'|translate}}" name="walletPasswordConfirm" ng-model="walletPasswordConfirm" match="walletPassword" required>
<div class="text-left">
<input id="network-name" type="checkbox" ng-model="networkName" ng-true-value="testnet" ng-false-value="livenet" class="form-control" ng-click="changeNetwork()" ng-checked="networkName == 'testnet' ? true : false">
<label for="network-name">Use test network</label>
</div>
</div>
<a class="expand small" ng-click="hideAdv=!hideAdv">
<a class="expand small" ng-click="hideAdv=!hideAdv">
<span translate ng-hide="!hideAdv">Show</span>
<span translate ng-hide="hideAdv">Hide</span>
<span translate>advanced options</span>
</a>
<div ng-hide="hideAdv">
<p>
<input type="text"
placeholder="{{'Private Key (Hex)'|translate}}"
name="private"
ng-model="private"
>
<input type="text" placeholder="{{'Private Key (Hex)'|translate}}" name="private" ng-model="private">
</div>
</div>
<div class="row" ng-show="!isSetupWalletPage">
<div class="large-6 medium-6 columns">
@ -78,24 +67,17 @@
</div>
<div class="box-setup-copayers" ng-show="!isSetupWalletPage">
<div class="box-setup-copayers p10">
<img class="br100 oh box-setup-copay m10" ng-repeat="i in getNumber(totalCopayers) track by $index"
src="./img/satoshi.gif"
title="Copayer {{$index+1}}-{{totalCopayers}}"
ng-class="{'box-setup-copay-required': ($index+1) <= requiredCopayers}"
width="50px">
<img class="br100 oh box-setup-copay m10" ng-repeat="i in getNumber(totalCopayers) track by $index" src="./img/satoshi.gif" title="Copayer {{$index+1}}-{{totalCopayers}}" ng-class="{'box-setup-copay-required': ($index+1) <= requiredCopayers}" width="50px">
</div>
</div>
<p translate class="comment" ng-show="totalCopayers>1 && !isSetupWalletPage">(*) The limits are imposed by the bitcoin network.</p>
<div class="text-right">
<a ng-show="!isSetupWalletPage" class="back-button m20r"
href="#!/">&laquo; <span translate>Back</span></a>
<a ng-show="isSetupWalletPage" class="back-button m20r"
ng-click="setupWallet()">&laquo; <span translate>Back</span></a>
<a ng-show="!isSetupWalletPage" class="back-button m20r" href="#!/">&laquo; <span translate>Back</span></a>
<a ng-show="isSetupWalletPage" class="back-button m20r" ng-click="setupWallet()">&laquo; <span translate>Back</span></a>
<button translate ng-show="isSetupWalletPage" type="submit" class="button secondary m0" ng-disabled="setupForm.$invalid || loading">
Create {{requiredCopayers}}-of-{{totalCopayers}} wallet
</button>
<a translate class="button secondary m0" ng-show="!isSetupWalletPage"
ng-click="setupWallet()">Next</a>
<a translate class="button secondary m0" ng-show="!isSetupWalletPage" ng-click="setupWallet()">Next</a>
</div>
</div>
</div>
@ -103,4 +85,3 @@
</form>
</div>
</div>

View file

@ -6,9 +6,41 @@
</a>
<div ng-include="'views/includes/version.html'"></div>
</div>
<div class="line-sidebar-b"></div>
<div>
<div ng-if="$root.wallet" class="founds size-12 text-center box-founds p10t">
<div class="m10b">
Balance
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-if="$root.wallet && !$root.updatingBalance"
data-options="disable_for_touch:true"
tooltip="{{totalBalanceBTC |noFractionNumber:8}} BTC"
tooltip-trigger="mouseenter"
tooltip-placement="bottom">{{totalBalance || 0
|noFractionNumber}} {{$root.wallet.settings.unitName}}
</span>
</div>
<div>
Locked
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-if="$root.wallet && !$root.updatingBalance"
data-options="disable_for_touch:true"
tooltip="{{lockedBalanceBTC |noFractionNumber:8}} BTC"
tooltip-trigger="mouseenter"
tooltip-placement="bottom">{{lockedBalance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}}
</span> &nbsp;<i class="fi-info medium" tooltip="Balance locked in pending transaction proposals" tooltip-placement="bottom"></i>
</div>
<div class="line-sidebar-b"></div>
</div>
</header>
<div class="line-sidebar-b"></div>
<div class="founds size-12 box-founds p15" ng-disabled="$root.loading" ng-click="refresh()">
<div ng-if="$root.wallet" class="founds size-12 box-founds p15" ng-disabled="$root.loading" ng-click="refresh()">
<p class="text-gray">
<span>{{$root.wallet.getName()}}</span>
<span class="size-12 right">{{$root.wallet.requiredCopayers}}-of-{{$root.wallet.totalCopayers}}</span>
@ -20,7 +52,7 @@
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-if="!$root.updatingBalance">{{totalBalance || 0
|noFractionNumber}} {{$root.unitName}}
|noFractionNumber}} {{$root.wallet.settings.unitName}}
</span>
</div>
<div class="m10t" ng-show="lockedBalance">
@ -28,7 +60,7 @@
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-show="!$root.updatingBalance">{{lockedBalance || 0|noFractionNumber}} {{$root.unitName}}
<span ng-show="!$root.updatingBalance">{{lockedBalance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}}
</span> &nbsp;<i class="fi-info medium" tooltip="{{'Balance locked in pending transaction proposals'|translate}}" tooltip-placement="bottom"></i>
</div>
</div>

View file

@ -20,26 +20,26 @@
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-if="!$root.updatingBalance"
<span ng-if="$root.wallet && !$root.updatingBalance"
class="has-tip"
data-options="disable_for_touch:true"
tooltip-popup-delay='500'
tooltip="{{totalBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}}"
tooltip-trigger="mouseenter"
tooltip-placement="bottom">{{totalBalance || 0 |noFractionNumber}} {{$root.unitName}}
tooltip-placement="bottom">{{totalBalance || 0 |noFractionNumber}} {{$root.wallet.settings.unitName}}
</span>
<div class="m10t" ng-show="lockedBalance">
<span translate>Locked</span> &nbsp;
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-show="!$root.updatingBalance"
<span ng-if="$root.wallet && !$root.updatingBalance"
class="has-tip"
data-options="disable_for_touch:true"
tooltip-popup-delay='500'
tooltip="{{lockedBalanceAlternative |noFractionNumber:2}} {{alternativeIsoCode}}"
tooltip-trigger="mouseenter"
tooltip-placement="bottom">{{lockedBalance || 0|noFractionNumber}} {{$root.unitName}}
tooltip-placement="bottom">{{lockedBalance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}}
</span> &nbsp;<i class="fi-info medium" tooltip="{{'Balance locked in pending transaction proposals'|translate}}" tooltip-placement="bottom"></i>
</div>
</div>

View file

@ -8,20 +8,19 @@
</a>
</div>
<div class="show-for-small-only small-12 columns m10b" ng-show="tx.comment">
<p class="size-14 label" >
{{tx.comment}} -
{{$root.wallet.publicKeyRing.nicknameForCopayer(tx.creator)}}
<p class="size-14 label">
{{tx.comment}} - {{$root.wallet.publicKeyRing.nicknameForCopayer(tx.creator)}}
</p>
</div>
<div class="large-8 medium-8 small-8 columns">
<div ng-repeat="out in tx.outs">
<div class="large-3 medium-3 small-3 columns">
<p class="size-14 hide-for-small-only">{{out.value | noFractionNumber}} {{$root.unitName}}</p>
<p class="size-12 show-for-small-only">{{out.value | noFractionNumber}} {{$root.unitName}}</p>
<p class="size-14 hide-for-small-only">{{out.value | noFractionNumber}} {{$root.wallet.settings.unitName}}</p>
<p class="size-12 show-for-small-only">{{out.value | noFractionNumber}} {{$root.wallet.settings.unitName}}</p>
</div>
<div class="large-1 medium-1 small-2 columns fi-arrow-right"> </div>
<div class="large-1 medium-1 small-2 columns fi-arrow-right"></div>
<div class="large-8 medium-8 small-7 columns ellipsis">
<contact address="{{out.address}}" tooltip-popup-delay="500" tooltip tooltip-placement="right"/>
<contact address="{{out.address}}" tooltip-popup-delay="500" tooltip tooltip-placement="right" />
</div>
</div>
</div>
@ -37,25 +36,25 @@
</a>
<div class="box-status">
<a ng-if="c.actions.create" tooltip-popup-delay="1000" tooltip="Created {{c.actions.create | amTimeAgo}}">
<i class="fi-crown icon-status icon-active"></i>
<i class="fi-crown icon-status icon-active"></i>
</a>
<a ng-if="!c.actions.create"><i class="fi-crown icon-status"></i></a>
<a ng-if="c.actions.seen" tooltip-popup-delay="1000" tooltip="Seen {{c.actions.seen | amTimeAgo}}">
<i class="fi-eye icon-status icon-active"></i>
<i class="fi-eye icon-status icon-active"></i>
</a>
<a ng-if="!c.actions.seen"><i class="fi-eye icon-status"></i></a>
<a ng-if="c.actions.rejected" tooltip-popup-delay="1000" tooltip="Rejected {{c.actions.rejected | amTimeAgo}}">
<i class="fi-x icon-status icon-active-x"></i>
<i class="fi-x icon-status icon-active-x"></i>
</a>
<a ng-if="c.actions.sign" tooltip-popup-delay="1000" tooltip="Signed {{c.actions.sign | amTimeAgo}}">
<i class="fi-check icon-status icon-active-check"></i>
<i class="fi-check icon-status icon-active-check"></i>
</a>
<a ng-if="!c.actions.sign && !c.actions.rejected && tx.missingSignatures" class="icon-status">
<i class="fi-loop icon-rotate"></i>
<i class="fi-loop icon-rotate"></i>
</a>
</div>
@ -100,7 +99,7 @@
</div>
<div ng-show="!tx.missingSignatures && tx.sentTs">
<div class="is-valid m10b">
<strong translate>Sent</strong> <span class="text-gray" am-time-ago="tx.sentTs"></span>
<strong translate>Sent</strong> <span class="text-gray" am-time-ago="tx.sentTs"></span>
</div>
<div class="ellipsis small">
<span translate>Transaction ID</span>:
@ -110,12 +109,12 @@
</div>
</div>
<p translate class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures==1">
One signature missing
One signature missing
</p>
<p translate class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures>1">
{{tx.missingSignatures}} signatures missing</p>
{{tx.missingSignatures}} signatures missing</p>
<div class="ellipsis small text-gray">
<strong translate>Fee</strong>: {{tx.fee|noFractionNumber}} {{$root.unitName}}
<strong translate>Fee</strong>: {{tx.fee|noFractionNumber}} {{$root.wallet.settings.unitName}}
<strong translate>Proposal ID</strong>: {{tx.ntxid}}
</div>
</div>

View file

@ -1,6 +1,6 @@
<div ng-controller="VersionController">
<small>v{{version}} ({{defaultLanguage}})</small>
<small>#{{commitHash}}</small>
<small ng-if="networkName=='testnet'">[ {{networkName}} ]</small>
<small ng-if="networkName ==='testnet' || networkName ==='livenet'">[ {{networkName}} ]</small>
</div>

View file

@ -7,7 +7,7 @@
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<p class="m15b" ng-if="!$root.updatingBalance">
{{address.balance || 0|noFractionNumber}} {{$root.unitName}}
{{address.balance || 0|noFractionNumber}} {{$root.wallet.settings.unitName}}
</p>
<button class="m15t button secondary" open-external address="{{address.address}}">
<i class="fi-link">&nbsp;</i> <span translate>Open in external application</span>

View file

@ -1,62 +1,79 @@
<div class="backup" ng-controller="MoreController">
<h1 translate>Settings </h1>
<div class="oh large-12 columns panel">
<h3><i class="fi-download m10r"></i> <span translate>Backup</span> </h3>
<p translate class="large-8 columns text-gray">It's important to backup your wallet so that you can recover it in case of disaster</p>
<div class="large-4 columns">
<a translate class="button primary expand" ng-click="downloadBackup()">Download File</a>
</div>
<div class="oh large-12 columns panel">
<h3><i class="fi-download m10r"></i> <span translate>Backup</span> </h3>
<p translate class="large-8 columns text-gray">It's important to backup your wallet so that you can recover it in case of disaster</p>
<div class="large-4 columns">
<a translate class="button primary expand" ng-click="downloadBackup()">Download File</a>
</div>
<div class="large-12 columns line-dashed-h m15b"> </div>
<div>
<div class="oh large-12 columns panel">
<h3><i class="fi-minus-circle m10r"></i>
<span translate>Delete Wallet</span> </h3>
<p translate class="large-8 columns text-gray">If all funds have been removed from your wallet and you do not wish to have the wallet data stored on your computer anymore, you can delete your wallet.</p>
<div class="large-4 columns">
<a translate class="button warning expand"
ng-really-message="'Are you sure to delete this wallet from this computer?'|translate" ng-really-click="deleteWallet()"> Delete</a>
</div>
<div class="large-12 columns line-dashed-h m15b"></div>
<div class="row collapse">
<form name="settingsForm" class="large-6 small-12 columns">
<fieldset>
<legend translate>Wallet Unit</legend>
<select class="form-control" ng-model="selectedUnit" ng-options="o.name for o in unitOpts" required>
</select>
</fieldset>
<fieldset>
<legend translate>Alternative Currency</legend>
<select class="form-control" ng-model="selectedAlternative" ng-options="alternative.name for alternative in alternativeOpts" required>
</select>
</fieldset>
<div class="text-left">
<button translate type="submit" class="large-6 small-12 columns button primary m0 ng-binding" ng-disabled="setupForm.$invalid || loading" disabled="disabled" ng-click="save()">
Save
</button>
</div>
</form>
</div>
<div class="large-12 columns line-dashed-h m15b"></div>
<div class="oh large-12 columns panel">
<h3><i class="fi-minus-circle m10r"></i> <span translate> Delete Wallet </span></h3>
<p translate class="large-8 columns text-gray">If all funds have been removed from your wallet and you do not wish to have the wallet data stored on your computer anymore, you can delete your wallet.</p>
<div class="large-4 columns">
<a translate class="button warning expand" ng-really-message="'Are you sure to delete this wallet from this computer?'|translate" ng-really-click="deleteWallet()"> Delete</a>
</div>
</div>
<p>
<a class="expand small" ng-click="hideAdv=!hideAdv">
<span translate ng-hide="!hideAdv">Show</span>
<span translate ng-hide="hideAdv">Hide</span>
<span translate>advanced options</span>
</a>
<a class="expand small" ng-click="hideAdv=!hideAdv">
<span translate ng-hide="!hideAdv">Show</span>
<span translate ng-hide="hideAdv">Hide</span>
<span translate>advanced options</span>
</a>
<div ng-hide="hideAdv">
<div class="oh large-12 columns panel">
<h3><i class="fi-minus-circle m10r"></i>
<div ng-hide="hideAdv">
<div class="oh large-12 columns panel">
<h3><i class="fi-minus-circle m10r"></i>
<span translate>Master Private Key</span> </h3>
<p translate class="large-8 columns text-gray">
Your master private key contains the information to sign <b>any</b> transaction on this wallet. Handle with care.
</p>
<div class="large-4 columns">
<a class="button primary expand" ng-click="hidePriv=!hidePriv">
<a class="button primary expand" ng-click="hidePriv=!hidePriv">
<span translate ng-hide="!hidePriv">Show</span>
<span translate ng-hide="hidePriv">Hide</span>
</a>
</div>
<textarea ng-hide="hidePriv" readonly>{{priv}}</textarea>
</div>
<div class="oh large-12 columns panel">
<h3><i class="fi-minus-circle m10r"></i> <span translate>Scan Wallet Addresses</span> </h3>
</div>
<div class="oh large-12 columns panel">
<h3><i class="fi-minus-circle m10r"></i> <span translate>Scan Wallet Addresses</span> </h3>
<p translate class="large-8 columns text-gray">
This will scan the blockchain looking for addresses derived from your wallet, in case you have funds in addresses not yet generated (e.g.: you restored an old backup). This will also trigger a syncronization of addresses to other connected peers.
This will scan the blockchain looking for addresses derived from your wallet, in case you have funds in addresses not yet generated (e.g.: you restored an old backup). This will also trigger a syncronization of addresses to other connected peers.
</p>
<div class="large-4 columns">
<a translate class="button primary expand" ng-click="updateIndexes()">
<a translate class="button primary expand" ng-click="updateIndexes()">
Scan
</a>
</div>
</div>
<div class="oh large-12 columns panel">
<h3><i class="fi-minus-circle m10r"></i> <span translate>Purge Pending Transaction Proposals</span> </h3>
</div>
<div class="oh large-12 columns panel">
<h3><i class="fi-minus-circle m10r"></i> <span translate>Purge Pending Transaction Proposals</span> </h3>
<p translate class="large-8 columns text-gray">
Pending Transactions Proposals will be discarted. This need to be done on <b>ALL<b> peers of a wallet, to prevent the old proposals to be resynced again.
Pending Transactions Proposals will be discarted. This need to be done on <b>ALL<b> peers of a wallet, to prevent the old proposals to be resynced again.
</p>
<div class="large-4 columns">
<a translate class="button warning expand" ng-click="purge()">
@ -78,4 +95,3 @@
</div>
</div>

View file

@ -14,14 +14,14 @@
<div class="row collapse">
<div class="large-12 columns">
<div class="row collapse">
<label for="address"><span translate>To address</span>
<label for="address"><span translate>To:</span>
<small translate ng-hide="!sendForm.address.$pristine || address">required</small>
<small translate class="is-valid" ng-show="!sendForm.address.$invalid && address">valid!</small>
<small translate class="has-error" ng-show="sendForm.address.$invalid && address">not valid</small>
</label>
<div class="small-10 columns">
<input type="text" id="address" name="address" ng-disabled="loading || !!$root.merchant"
placeholder="{{'Send to'|translate}}" ng-model="address" ng-change="onChanged()" valid-address required>
placeholder="{{'Bitcoin address'|translate}}" ng-model="address" ng-change="onChanged()" valid-address required>
<small class="icon-input" ng-show="!sendForm.address.$invalid && address"><i class="fi-check"></i></small>
<small class="icon-input" ng-show="sendForm.address.$invalid && address"><i class="fi-x"></i></small>
</div>
@ -77,11 +77,11 @@
<a class="small input-note" title="{{'Send all funds'|translate}}"
ng-show="$root.availableBalance > 0 && (!$root.merchant || +$root.merchant.total === 0)"
ng-click="topAmount(sendForm)">
<span translate>Use all funds</span> ({{getAvailableAmount()}} {{$root.unitName}})
<span translate>Use all funds</span> ({{getAvailableAmount()}} {{$root.wallet.settings.unitName}})
</a>
</div>
<div class="small-3 columns">
<span class="postfix">{{$root.unitName}}</span>
<span class="postfix">{{$root.wallet.settings.unitName}}</span>
</div>
</div>
</div>
@ -138,13 +138,13 @@
</p>
<h6 translate>Total amount for this transaction:</h6>
<p class="text-gray" ng-class="{'hidden': sendForm.amount.$invalid || !amount > 0}">
<b>{{amount + defaultFee |noFractionNumber}}</b> {{$root.unitName}}
<b>{{amount + defaultFee |noFractionNumber}}</b> {{$root.wallet.settings.unitName}}
<small ng-if="isRateAvailable">
{{ rateService.toFiat((amount + defaultFee) * unitToSatoshi, alternativeIsoCode) | noFractionNumber: 2 }} {{ alternativeIsoCode }}
<br>
</small>
<small>
<span translate>Including fee of</span> {{defaultFee|noFractionNumber}} {{$root.unitName}}
<span translate>Including fee of</span> {{defaultFee|noFractionNumber}} {{$root.wallet.settings.unitName}}
</small>
</p>
<div ng-show="wallet.isShared()">

View file

@ -10,45 +10,20 @@
<form name="settingsForm">
<fieldset>
<legend translate>Language</legend>
<select class="form-control" ng-model="selectedLanguage"
ng-options="o.name for o in availableLanguages" required>
<select class="form-control" ng-model="selectedLanguage" ng-options="o.name for o in availableLanguages" required>
</select>
</fieldset>
<fieldset>
<legend translate>Bitcoin Network</legend>
<input id="network-name" type="checkbox" ng-model="networkName"
ng-true-value="livenet" ng-false-value="testnet" class="form-control" ng-click="changeNetwork()"
ng-disabled="forceNetwork"
ng-checked="networkName == 'livenet' ? true : false">
<label for="network-name">Livenet</label>
<div translate ng-show="forceNetwork">
Network has been fixed to <strong>{{networkName}}</strong> in this setup. See <a href="https://copay.io">copay.io</a> for options to use Copay on both livenet and testnet.
</div>
</fieldset>
<fieldset>
<legend translate>Wallet Unit</legend>
<select class="form-control" ng-model="selectedUnit" ng-options="o.name for o in unitOpts" required>
</select>
</fieldset>
<fieldset>
<legend translate>Alternative Currency</legend>
<select class="form-control" ng-model="selectedAlternative" ng-options="alternative.name for alternative in alternativeOpts" required>
</select>
</fieldset>
<fieldset>
<legend translate>Insight API server</legend>
<label for="insight-host">Host</label>
<input type="text" ng-model="insightHost" class="form-control" name="insight-host">
<label for="insight-port" translate>Port</label>
<input type="number" ng-model="insightPort" class="form-control" name="insight-port">
<input id="insight-secure" type="checkbox" ng-model="insightSecure" class="form-control" ng-click="changeInsightSSL()">
<label for="insight-secure" translate>Use SSL</label>
<legend translate>Insight API servers</legend>
<label for="insight-livenet">Livenet</label>
<input type="text" ng-model="insightLivenet" class="form-control" name="insight-livenet">
<label for="insight-testnet">Testnet</label>
<input type="text" ng-model="insightTestnet" class="form-control" name="insight-testnet">
<p translate class="small">
Insight API server is open-source software. You can run your own instance, check <a href="http://insight.is" target="_blank">Insight API Homepage</a></p>
Insight API server is open-source software. You can run your own instances, check <a href="http://insight.is" target="_blank">Insight API Homepage</a>
</p>
</fieldset>
<div class="text-right">
<a class="back-button text-white m20r" href="#!/">&laquo; <span translate>Back</span></a>
<button translate type="submit" class="button primary m0 ng-binding" ng-disabled="setupForm.$invalid || loading" disabled="disabled" ng-click="save()">
@ -60,4 +35,3 @@
</div>
</div>
</div>

View file

@ -7,7 +7,8 @@
<div class="last-transactions" ng-repeat="tx in txs | paged">
<div ng-include="'views/includes/transaction.html'"></div>
</div>
<p ng-show="txs.length == 0"><span translate>No transactions proposals yet.</span></p>
<p ng-show="txs.length == 0"><span translate>No transactions proposals yet.</span>
</p>
<pagination ng-show="txs.length > txpItemsPerPage" total-items="txs.length" items-per-page="txpItemsPerPage" page="txpCurrentPage" on-select-page="show()" class="pagination-small primary"></pagination>
</div>
@ -20,10 +21,8 @@
<div class="large-12">
<div class="m10b size-12" ng-hide="wallet.totalCopayers == 1">
<a class="text-gray active" ng-click="toogleLast()"
ng-disabled="loading" loading="Updating" ng-hide="lastShowed && !loading">[ <span translate>Show</span> ]</a>
<a class="text-gray" ng-click="toogleLast()" ng-disabled="loading"
loading="Updating" ng-show="lastShowed && !loading">[ <span translate>Hide</span> ]</a>
<a class="text-gray active" ng-click="toogleLast()" ng-disabled="loading" loading="Updating" ng-hide="lastShowed && !loading">[ <span translate>Show</span> ]</a>
<a class="text-gray" ng-click="toogleLast()" ng-disabled="loading" loading="Updating" ng-show="lastShowed && !loading">[ <span translate>Hide</span> ]</a>
</div>
<div class="btransactions" ng-if="lastShowed">
@ -52,9 +51,9 @@
<div class="last-transactions-content">
<div class="large-5 medium-5 small-12 columns">
<div ng-repeat="vin in btx.vinSimple">
<small class="right m5t">{{vin.value| noFractionNumber}} {{$root.unitName}}</small>
<small class="right m5t">{{vin.value| noFractionNumber}} {{$root.wallet.settings.unitName}}</small>
<p class="ellipsis text-gray size-12">
<contact address="{{vin.addr}}" tooltip-popup-delay="500" tooltip tooltip-placement="right"/>
<contact address="{{vin.addr}}" tooltip-popup-delay="500" tooltip tooltip-placement="right" />
</p>
</div>
</div>
@ -66,20 +65,20 @@
</div>
<div class="large-6 medium-6 small-12 columns">
<div ng-repeat="vout in btx.voutSimple">
<small class="right m5t">{{vout.value| noFractionNumber}} {{$root.unitName}}</small>
<small class="right m5t">{{vout.value| noFractionNumber}} {{$root.wallet.settings.unitName}}</small>
<p class="ellipsis text-gray size-12">
<contact address="{{vout.addr}}" tooltip-popup-delay="500" tooltip tooltip-placement="right"/>
<contact address="{{vout.addr}}" tooltip-popup-delay="500" tooltip tooltip-placement="right" />
</p>
</div>
</div>
</div>
<div class="last-transactions-footer">
<div class="large-6 medium-6 small-6 columns">
<p class="size-12"><span translate>Fee</span>: {{btx.fees | noFractionNumber}} {{$root.unitName}}</p>
<p class="size-12"><span translate>Confirmations</span>: {{btx.confirmations || 0}}</p>
<p class="size-12"><span translate>Fee</span>: {{btx.fees | noFractionNumber}} {{$root.wallet.settings.unitName}}</p>
<p class="size-12"><span translate>Confirmations</span>: {{btx.confirmations || 0}}</p>
</div>
<div class="large-6 medium-6 small-6 columns text-right">
<p class="label size-14"><span translate>Total</span>: {{btx.valueOut| noFractionNumber}} {{$root.unitName}}</p>
<p class="label size-14"><span translate>Total</span>: {{btx.valueOut| noFractionNumber}} {{$root.wallet.settings.unitName}}</p>
</div>
</div>
</div>
@ -87,4 +86,3 @@
</div>
</div>
</div>