This commit is contained in:
Kadir Sekha 2017-10-16 18:05:09 +09:00
commit a0261a6c9f
146 changed files with 16800 additions and 5578 deletions

View file

@ -1,8 +1,19 @@
'use strict';
angular.module('copayApp.services').factory('addressbookService', function(bitcore, storageService, lodash) {
angular.module('copayApp.services').factory('addressbookService', function($log, bitcore, bitcoreCash, storageService, lodash) {
var root = {};
var getNetwork = function(address) {
var network;
try {
network = (new bitcore.Address(address)).network.name;
} catch(e) {
$log.warn('No valid bitcoin address. Trying bitcoin cash...');
network = (new bitcoreCash.Address(address)).network.name;
}
return network;
};
root.get = function(addr, cb) {
storageService.getAddressbook('testnet', function(err, ab) {
if (err) return cb(err);
@ -35,7 +46,8 @@ angular.module('copayApp.services').factory('addressbookService', function(bitco
};
root.add = function(entry, cb) {
var network = (new bitcore.Address(entry.address)).network.name;
var network = getNetwork(entry.address);
if (lodash.isEmpty(network)) return cb('Not valid bitcoin address');
storageService.getAddressbook(network, function(err, ab) {
if (err) return cb(err);
if (ab) ab = JSON.parse(ab);
@ -53,7 +65,8 @@ angular.module('copayApp.services').factory('addressbookService', function(bitco
};
root.remove = function(addr, cb) {
var network = (new bitcore.Address(addr)).network.name;
var network = getNetwork(addr);
if (lodash.isEmpty(network)) return cb('Not valid bitcoin address');
storageService.getAddressbook(network, function(err, ab) {
if (err) return cb(err);
if (ab) ab = JSON.parse(ab);

View file

@ -0,0 +1,6 @@
'use strict';
angular.module('copayApp.services')
.factory('bitcoreCash', function bitcoreFactory(bwcService) {
var bitcoreCash = bwcService.getBitcoreCash();
return bitcoreCash;
});

View file

@ -53,6 +53,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
'wallet:sells:create,' +
'wallet:transactions:read,' +
'wallet:transactions:send,' +
'wallet:transactions:send:bypass-2fa,' +
'wallet:payment-methods:read';
// NW has a bug with Window Object
@ -169,9 +170,9 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
var _getNetAmount = function(amount, cb) {
// Fee Normal for a single transaction (450 bytes)
var txNormalFeeKB = 450 / 1000;
feeService.getFeeRate(null, 'normal', function(err, feePerKB) {
feeService.getFeeRate('btc', 'livenet', 'normal', function(err, feePerKb) {
if (err) return cb(err);
var feeBTC = (feePerKB * txNormalFeeKB / 100000000).toFixed(8);
var feeBTC = (feePerKb * txNormalFeeKB / 100000000).toFixed(8);
return cb(null, amount - feeBTC, feeBTC);
});

View file

@ -69,7 +69,8 @@ angular.module('copayApp.services').factory('configService', function(storageSer
bannedUntil: null,
},
// External services
cashSupport: false,
recentTransactions: {
enabled: true,
},
@ -141,6 +142,11 @@ angular.module('copayApp.services').factory('configService', function(storageSer
configCache.hideNextSteps = defaultConfig.hideNextSteps;
}
if (!configCache.cashSupport) {
configCache.cashSupport = defaultConfig.cashSupport;
}
if (!configCache.recentTransactions) {
configCache.recentTransactions = defaultConfig.recentTransactions;
}
@ -151,6 +157,14 @@ angular.module('copayApp.services').factory('configService', function(storageSer
configCache.bitpayAccount = defaultConfig.bitpayAccount;
}
if (configCache.wallet.settings.unitCode == 'bit') {
// Convert to BTC. Bits will be disabled
configCache.wallet.settings.unitName = defaultConfig.wallet.settings.unitName;
configCache.wallet.settings.unitToSatoshi = defaultConfig.wallet.settings.unitToSatoshi;
configCache.wallet.settings.unitDecimals = defaultConfig.wallet.settings.unitDecimals;
configCache.wallet.settings.unitCode = defaultConfig.wallet.settings.unitCode;
}
} else {
configCache = lodash.clone(defaultConfig);
};

View file

@ -17,6 +17,7 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou
var cache = {
updateTs: 0,
coin: ''
};
root.getCurrentFeeLevel = function() {
@ -24,20 +25,20 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou
};
root.getFeeRate = function(network, feeLevel, cb) {
root.getFeeRate = function(coin, network, feeLevel, cb) {
if (feeLevel == 'custom') return cb();
network = network || 'livenet';
root.getFeeLevels(function(err, levels, fromCache) {
root.getFeeLevels(coin, function(err, levels, fromCache) {
if (err) return cb(err);
var feeLevelRate = lodash.find(levels[network], {
level: feeLevel
});
if (!feeLevelRate || !feeLevelRate.feePerKB) {
if (!feeLevelRate || !feeLevelRate.feePerKb) {
return cb({
message: gettextCatalog.getString("Could not get dynamic fee for level: {{feeLevel}}", {
feeLevel: feeLevel
@ -45,34 +46,35 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou
});
}
var feeRate = feeLevelRate.feePerKB;
var feeRate = feeLevelRate.feePerKb;
if (!fromCache) $log.debug('Dynamic fee: ' + feeLevel + '/' + network + ' ' + (feeLevelRate.feePerKB / 1000).toFixed() + ' SAT/B');
if (!fromCache) $log.debug('Dynamic fee: ' + feeLevel + '/' + network + ' ' + (feeLevelRate.feePerKb / 1000).toFixed() + ' SAT/B');
return cb(null, feeRate);
});
};
root.getCurrentFeeRate = function(network, cb) {
return root.getFeeRate(network, root.getCurrentFeeLevel(), cb);
root.getCurrentFeeRate = function(coin, network, cb) {
return root.getFeeRate(coin, network, root.getCurrentFeeLevel(), cb);
};
root.getFeeLevels = function(cb) {
root.getFeeLevels = function(coin, cb) {
coin = coin || 'btc';
if (cache.updateTs > Date.now() - CACHE_TIME_TS * 1000) {
if (cache.coin == coin && cache.updateTs > Date.now() - CACHE_TIME_TS * 1000) {
return cb(null, cache.data, true);
}
var walletClient = bwcService.getClient();
var unitName = configService.getSync().wallet.settings.unitName;
walletClient.getFeeLevels('livenet', function(errLivenet, levelsLivenet) {
walletClient.getFeeLevels('testnet', function(errTestnet, levelsTestnet) {
walletClient.getFeeLevels(coin, 'livenet', function(errLivenet, levelsLivenet) {
walletClient.getFeeLevels('btc', 'testnet', function(errTestnet, levelsTestnet) {
if (errLivenet || errTestnet) {
return cb(gettextCatalog.getString('Could not get dynamic fee'));
}
cache.updateTs = Date.now();
cache.coin = coin;
cache.data = {
'livenet': levelsLivenet,
'testnet': levelsTestnet

View file

@ -34,6 +34,7 @@ angular.module('copayApp.services')
};
root.add = function(level, msg) {
msg = msg.replace('/xpriv.*/', 'xpriv[Hidden]');
logs.push({
timestamp: new Date().toISOString(),
level: level,

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, $rootScope, payproService, scannerService, appConfigService, popupService, gettextCatalog) {
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, appConfigService, popupService, gettextCatalog) {
var root = {};
@ -46,7 +46,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
return true;
}
function goSend(addr, amount, message) {
function goSend(addr, amount, message, coin) {
$state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
@ -57,18 +57,20 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
$state.transitionTo('tabs.send.confirm', {
toAmount: amount,
toAddress: addr,
description: message
description: message,
coin: coin
});
} else {
$state.transitionTo('tabs.send.amount', {
toAddress: addr
toAddress: addr,
coin: coin
});
}
}, 100);
}
// data extensions for Payment Protocol with non-backwards-compatible request
if ((/^bitcoin:\?r=[\w+]/).exec(data)) {
data = decodeURIComponent(data.replace('bitcoin:?r=', ''));
if ((/^bitcoin(cash)?:\?r=[\w+]/).exec(data)) {
data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, ''));
$state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
@ -82,27 +84,97 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
data = sanitizeUri(data);
// BIP21
// Bitcoin URL
if (bitcore.URI.isValid(data)) {
var parsed = new bitcore.URI(data);
var coin = 'btc';
var parsed = new bitcore.URI(data);
var addr = parsed.address ? parsed.address.toString() : '';
var message = parsed.message;
var addr = parsed.address ? parsed.address.toString() : '';
var message = parsed.message;
var amount = parsed.amount ? parsed.amount : '';
var amount = parsed.amount ? parsed.amount : '';
if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) {
if (err) {
if (addr && amount) goSend(addr, amount, message);
else popupService.showAlert(gettextCatalog.getString('Error'), err);
} else handlePayPro(details);
});
} else {
goSend(addr, amount, message);
}
if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) {
if (err) {
if (addr && amount) goSend(addr, amount, message, coin);
else popupService.showAlert(gettextCatalog.getString('Error'), err);
} else handlePayPro(details);
});
} else {
goSend(addr, amount, message, coin);
}
return true;
// Cash URI
} else if (bitcoreCash.URI.isValid(data)) {
var coin = 'bch';
var parsed = new bitcoreCash.URI(data);
var addr = parsed.address ? parsed.address.toString() : '';
var message = parsed.message;
var amount = parsed.amount ? parsed.amount : '';
// paypro not yet supported on cash
if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) {
if (err) {
if (addr && amount)
goSend(addr, amount, message, coin);
else
popupService.showAlert(gettextCatalog.getString('Error'), err);
}
handlePayPro(details, coin);
});
} else {
goSend(addr, amount, message, coin);
}
return true;
// Cash URI with bitcoin core address version number?
} else if (bitcore.URI.isValid(data.replace(/^bitcoincash:/,'bitcoin:'))) {
$log.debug('Handling bitcoincash URI with legacy address');
var coin = 'bch';
var parsed = new bitcore.URI(data.replace(/^bitcoincash:/,'bitcoin:'));
var oldAddr = parsed.address ? parsed.address.toString() : '';
if (!oldAddr) return false;
var addr = '';
var a = bitcore.Address(oldAddr).toObject();
addr = bitcoreCash.Address.fromObject(a).toString();
// Translate address
$log.debug('address transalated to:' + addr);
popupService.showConfirm(
gettextCatalog.getString('Bitcoin cash Payment'),
gettextCatalog.getString('Payment address was translated to new Bitcoin Cash address format: ' + addr),
gettextCatalog.getString('OK'),
gettextCatalog.getString('Cancel'),
function(ret) {
if (!ret) return false;
var message = parsed.message;
var amount = parsed.amount ? parsed.amount : '';
// paypro not yet supported on cash
if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) {
if (err) {
if (addr && amount)
goSend(addr, amount, message, coin);
else
popupService.showAlert(gettextCatalog.getString('Error'), err);
}
handlePayPro(details, coin);
});
} else {
goSend(addr, amount, message, coin);
}
}
);
return true;
// Plain URL
} else if (/^https?:\/\//.test(data)) {
@ -127,6 +199,16 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
} else {
goToAmountPage(data);
}
} else if (bitcoreCash.Address.isValid(data, 'livenet')) {
if ($state.includes('tabs.scan')) {
root.showMenu({
data: data,
type: 'bitcoinAddress',
coin: 'bch',
});
} else {
goToAmountPage(data, 'bch');
}
} else if (data && data.indexOf(appConfigService.name + '://glidera') === 0) {
var code = getParameterByName('code', data);
$ionicHistory.nextViewOptions({
@ -236,29 +318,29 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
});
}
}
return false;
};
function goToAmountPage(toAddress) {
function goToAmountPage(toAddress, coin) {
$state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
});
$timeout(function() {
$state.transitionTo('tabs.send.amount', {
toAddress: toAddress
toAddress: toAddress,
coin: coin,
});
}, 100);
}
function handlePayPro(payProDetails) {
function handlePayPro(payProDetails, coin) {
var stateParams = {
toAmount: payProDetails.amount,
toAddress: payProDetails.toAddress,
description: payProDetails.memo,
paypro: payProDetails
paypro: payProDetails,
coin: coin,
};
scannerService.pausePreview();
$state.go('tabs.send', {}, {

View file

@ -0,0 +1,183 @@
'use strict';
angular.module('copayApp.services').factory('mercadoLibreService', function($http, $log, lodash, moment, storageService, configService, platformInfo, nextStepsService, homeIntegrationsService) {
var root = {};
var credentials = {};
// Not used yet
var availableCountries = [{
'country': 'Brazil',
'currency': 'BRL',
'name': 'Mercado Livre',
'url': 'https://www.mercadolivre.com.br'
}];
/*
* Development: 'testnet'
* Production: 'livenet'
*/
credentials.NETWORK = 'livenet';
//credentials.NETWORK = 'testnet';
if (credentials.NETWORK == 'testnet') {
credentials.BITPAY_API_URL = "https://test.bitpay.com";
} else {
credentials.BITPAY_API_URL = "https://bitpay.com";
};
var homeItem = {
name: 'mercadoLibre',
title: 'Vales-Presente do Mercado Livre Brasil',
icon: 'icon-ml',
sref: 'tabs.giftcards.mercadoLibre',
};
var nextStepItem = {
name: 'mercadoLibre',
title: 'Comprar um Vale-Presente Mercado Livre',
icon: 'icon-ml',
sref: 'tabs.giftcards.mercadoLibre',
};
var _getBitPay = function(endpoint) {
return {
method: 'GET',
url: credentials.BITPAY_API_URL + endpoint,
headers: {
'content-type': 'application/json'
}
};
};
var _postBitPay = function(endpoint, data) {
return {
method: 'POST',
url: credentials.BITPAY_API_URL + endpoint,
headers: {
'content-type': 'application/json'
},
data: data
};
};
root.getNetwork = function() {
return credentials.NETWORK;
};
root.savePendingGiftCard = function(gc, opts, cb) {
var network = root.getNetwork();
storageService.getMercadoLibreGiftCards(network, function(err, oldGiftCards) {
if (lodash.isString(oldGiftCards)) {
oldGiftCards = JSON.parse(oldGiftCards);
}
if (lodash.isString(gc)) {
gc = JSON.parse(gc);
}
var inv = oldGiftCards || {};
inv[gc.invoiceId] = gc;
if (opts && (opts.error || opts.status)) {
inv[gc.invoiceId] = lodash.assign(inv[gc.invoiceId], opts);
}
if (opts && opts.remove) {
delete(inv[gc.invoiceId]);
}
inv = JSON.stringify(inv);
storageService.setMercadoLibreGiftCards(network, inv, function(err) {
homeIntegrationsService.register(homeItem);
nextStepsService.unregister(nextStepItem.name);
return cb(err);
});
});
};
root.getPendingGiftCards = function(cb) {
var network = root.getNetwork();
storageService.getMercadoLibreGiftCards(network, function(err, giftCards) {
var _gcds = giftCards ? JSON.parse(giftCards) : null;
return cb(err, _gcds);
});
};
root.createBitPayInvoice = function(data, cb) {
var dataSrc = {
currency: data.currency,
amount: data.amount,
clientId: data.uuid
};
$http(_postBitPay('/mercado-libre-gift/pay', dataSrc)).then(function(data) {
$log.info('BitPay Create Invoice: SUCCESS');
return cb(null, data.data);
}, function(data) {
$log.error('BitPay Create Invoice: ERROR', JSON.stringify(data.data));
return cb(data.data);
});
};
root.getBitPayInvoice = function(id, cb) {
$http(_getBitPay('/invoices/' + id)).then(function(data) {
$log.info('BitPay Get Invoice: SUCCESS');
return cb(null, data.data.data);
}, function(data) {
$log.error('BitPay Get Invoice: ERROR', JSON.stringify(data.data));
return cb(data.data);
});
};
root.createGiftCard = function(data, cb) {
var dataSrc = {
"clientId": data.uuid,
"invoiceId": data.invoiceId,
"accessKey": data.accessKey
};
$http(_postBitPay('/mercado-libre-gift/redeem', dataSrc)).then(function(data) {
var status = data.data.status == 'new' ? 'PENDING' : (data.data.status == 'paid') ? 'PENDING' : data.data.status;
data.data.status = status;
$log.info('Mercado Libre Gift Card Create/Update: ' + status);
return cb(null, data.data);
}, function(data) {
$log.error('Mercado Libre Gift Card Create/Update: ERROR', JSON.stringify(data.data));
return cb(data.data);
});
};
/*
* Disabled for now *
*/
/*
root.cancelGiftCard = function(data, cb) {
var dataSrc = {
"clientId": data.uuid,
"invoiceId": data.invoiceId,
"accessKey": data.accessKey
};
$http(_postBitPay('/mercado-libre-gift/cancel', dataSrc)).then(function(data) {
$log.info('Mercado Libre Gift Card Cancel: SUCCESS');
return cb(null, data.data);
}, function(data) {
$log.error('Mercado Libre Gift Card Cancel: ' + data.data.message);
return cb(data.data);
});
};
*/
var register = function() {
storageService.getMercadoLibreGiftCards(root.getNetwork(), function(err, giftCards) {
if (giftCards) {
homeIntegrationsService.register(homeItem);
} else {
nextStepsService.register(nextStepItem);
}
});
};
// Hide Mercado Libre
// register();
return root;
});

View file

@ -45,7 +45,8 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'cancelingGiftCard': 'Canceling Gift Card...',
'creatingGiftCard': 'Creating Gift Card...',
'buyingGiftCard': 'Buying Gift Card...',
'topup': gettext('Top up in progress...')
'topup': gettext('Top up in progress...'),
'duplicatingWallet': gettext('Duplicating wallet...'),
};
root.clear = function() {

View file

@ -57,10 +57,10 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop
// This event is sent to an existent instance of Copay (only for standalone apps)
gui.App.on('open', function(pathData) {
if (pathData.indexOf('bitcoin:') != -1) {
if (pathData.indexOf(/^bitcoin(cash)?:/) != -1) {
$log.debug('Bitcoin URL found');
handleOpenURL({
url: pathData.substring(pathData.indexOf('bitcoin:'))
url: pathData.substring(pathData.indexOf(/^bitcoin(cash)?:/))
});
} else if (pathData.indexOf(appConfigService.name + '://') != -1) {
$log.debug(appConfigService.name + ' URL found');
@ -84,6 +84,7 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop
if (navigator.registerProtocolHandler) {
$log.debug('Registering Browser handlers base:' + base);
navigator.registerProtocolHandler('bitcoin', url, 'Copay Bitcoin Handler');
navigator.registerProtocolHandler('web+bitcoincash', url, 'Copay Bitcoin Cash Handler');
navigator.registerProtocolHandler('web+copay', url, 'Copay Wallet Handler');
navigator.registerProtocolHandler('web+bitpay', url, 'BitPay Wallet Handler');
}

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services')
.factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, sjcl, lodash, storageService, bwcService, configService, gettextCatalog, bwcError, uxLanguage, platformInfo, txFormatService, $state) {
.factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, $state, sjcl, lodash, storageService, bwcService, configService, gettextCatalog, bwcError, uxLanguage, platformInfo, txFormatService, appConfigService) {
var isChromeApp = platformInfo.isChromeApp;
@ -89,6 +89,7 @@ angular.module('copayApp.services')
wallet.copayerId = wallet.credentials.copayerId;
wallet.m = wallet.credentials.m;
wallet.n = wallet.credentials.n;
wallet.coin = wallet.credentials.coin;
root.updateWalletSettings(wallet);
root.wallet[walletId] = wallet;
@ -222,11 +223,12 @@ angular.module('copayApp.services')
return ((config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url);
};
var client = bwcService.getClient(JSON.stringify(credentials), {
bwsurl: getBWSURL(credentials.walletId),
});
var skipKeyValidation = shouldSkipValidation(credentials.walletId);
if (!skipKeyValidation)
root.runValidation(client, 500);
@ -328,6 +330,7 @@ angular.module('copayApp.services')
passphrase: opts.passphrase,
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
coin: opts.coin
});
} catch (ex) {
@ -336,7 +339,12 @@ angular.module('copayApp.services')
}
} else if (opts.extendedPrivateKey) {
try {
walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey);
walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey, {
network: network,
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
coin: opts.coin,
});
} catch (ex) {
$log.warn(ex);
return cb(gettextCatalog.getString('Could not create using the specified extended private key'));
@ -346,6 +354,7 @@ angular.module('copayApp.services')
walletClient.seedFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
coin: opts.coin
});
walletClient.credentials.hwInfo = opts.hwInfo;
} catch (ex) {
@ -360,6 +369,7 @@ angular.module('copayApp.services')
passphrase: opts.passphrase,
language: lang,
account: 0,
coin: opts.coin
});
} catch (e) {
$log.info('Error creating recovery phrase: ' + e.message);
@ -369,6 +379,7 @@ angular.module('copayApp.services')
network: network,
passphrase: opts.passphrase,
account: 0,
coin: opts.coin
});
} else {
return cb(e);
@ -380,7 +391,11 @@ angular.module('copayApp.services')
// Creates a wallet on BWC/BWS
var doCreateWallet = function(opts, cb) {
$log.debug('Creating Wallet:', opts);
var showOpts = lodash.clone(opts);
if (showOpts.extendedPrivateKey) showOpts.extendedPrivateKey='[hidden]';
if (showOpts.mnemonic) showOpts.mnemonic='[hidden]';
$log.debug('Creating Wallet:', showOpts);
$timeout(function() {
seedWallet(opts, function(err, walletClient) {
if (err) return cb(err);
@ -392,6 +407,7 @@ angular.module('copayApp.services')
network: opts.networkName,
singleAddress: opts.singleAddress,
walletPrivKey: opts.walletPrivKey,
coin: opts.coin
}, function(err, secret) {
if (err) return bwcError.cb(err, gettextCatalog.getString('Error creating wallet'), cb);
return cb(null, walletClient, secret);
@ -435,7 +451,9 @@ angular.module('copayApp.services')
seedWallet(opts, function(err, walletClient) {
if (err) return cb(err);
walletClient.joinWallet(opts.secret, opts.myName || 'me', {}, function(err) {
walletClient.joinWallet(opts.secret, opts.myName || 'me', {
coin: opts.coin
}, function(err) {
if (err) return bwcError.cb(err, gettextCatalog.getString('Could not join wallet'), cb);
addAndBindWalletClient(walletClient, {
bwsurl: opts.bwsurl
@ -495,7 +513,9 @@ angular.module('copayApp.services')
var walletId = client.credentials.walletId
if (!root.profile.addWallet(JSON.parse(client.export())))
return cb(gettextCatalog.getString('Wallet already in Copay'));
return cb(gettextCatalog.getString("Wallet already in {{appName}}", {
appName: appConfigService.nameCase
}));
var skipKeyValidation = shouldSkipValidation(walletId);
@ -621,6 +641,7 @@ angular.module('copayApp.services')
entropySourcePath: opts.entropySourcePath,
derivationStrategy: opts.derivationStrategy || 'BIP44',
account: opts.account || 0,
coin: opts.coin
}, function(err) {
if (err) {
if (err instanceof errors.NOT_AUTHORIZED)
@ -642,6 +663,7 @@ angular.module('copayApp.services')
walletClient.importFromExtendedPublicKey(opts.extendedPublicKey, opts.externalSource, opts.entropySource, {
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
coin: opts.coin
}, function(err) {
if (err) {
@ -682,6 +704,7 @@ angular.module('copayApp.services')
opts.m = 1;
opts.n = 1;
opts.networkName = 'livenet';
opts.coin = 'btc';
root.createWallet(opts, cb);
};
@ -747,6 +770,12 @@ angular.module('copayApp.services')
var ret = lodash.values(root.wallet);
if (opts.coin) {
ret = lodash.filter(ret, function(x) {
return (x.credentials.coin == opts.coin);
});
}
if (opts.network) {
ret = lodash.filter(ret, function(x) {
return (x.credentials.network == opts.network);
@ -767,12 +796,14 @@ angular.module('copayApp.services')
if (opts.hasFunds) {
ret = lodash.filter(ret, function(w) {
if (!w.status) return;
return (w.status.availableBalanceSat > 0);
});
}
if (opts.minAmount) {
ret = lodash.filter(ret, function(w) {
if (!w.status) return;
return (w.status.availableBalanceSat > opts.minAmount);
});
}
@ -857,7 +888,7 @@ angular.module('copayApp.services')
x.types = [x.type];
if (x.data && x.data.amount)
x.amountStr = txFormatService.formatAmountStr(x.data.amount);
x.amountStr = txFormatService.formatAmountStr(x.wallet.coin, x.data.amount);
x.action = function() {
// TODO?

View file

@ -25,9 +25,10 @@ var RateService = function(opts) {
self._isAvailable = false;
self._rates = {};
self._alternatives = [];
self._ratesBCH = {};
self._queued = [];
self._fetchCurrencies();
self.updateRates();
};
@ -39,14 +40,20 @@ RateService.singleton = function(opts) {
return _instance;
};
RateService.prototype._fetchCurrencies = function() {
RateService.prototype.updateRates = function() {
var self = this;
var backoffSeconds = 5;
var updateFrequencySeconds = 5 * 60;
var rateServiceUrl = 'https://bitpay.com/api/rates';
var bchRateServiceUrl = 'https://api.kraken.com/0/public/Ticker?pair=BCHUSD,BCHEUR';
function getBTC(cb, tries) {
tries = tries || 0;
if (!self.httprequest) return;
if (tries > 5) return cb('could not get BTC rates');
var retrieve = function() {
//log.info('Fetching exchange rates');
self.httprequest.get(rateServiceUrl).success(function(res) {
self.lodash.each(res, function(currency) {
@ -57,27 +64,64 @@ RateService.prototype._fetchCurrencies = function() {
rate: currency.rate
});
});
return cb();
}).error(function() {
//log.debug('Error fetching exchange rates', err);
setTimeout(function() {
backoffSeconds *= 1.5;
getBTC(cb, tries++);
}, backoffSeconds * 1000);
return;
})
}
function getBCH(cb, tries) {
tries = tries || 0;
if (!self.httprequest) return;
if (tries > 5) return cb('could not get BCH rates');
function retry(tries) {
//log.debug('Error fetching exchange rates', err);
setTimeout(function() {
backoffSeconds *= 1.5;
getBTC(cb, tries++);
}, backoffSeconds * 1000);
return;
}
self.httprequest.get(bchRateServiceUrl).success(function(res) {
self.lodash.each(res.result, function(data, paircode) {
var code = paircode.substr(3,3);
var rate =data.c[0];
self._ratesBCH[code] = rate;
})
return cb();
}).error(function() {
return retry(tries);
})
}
getBTC(function(err) {
if (err) return;
getBCH(function(err) {
if (err) return;
self._isAvailable = true;
self.lodash.each(self._queued, function(callback) {
setTimeout(callback, 1);
});
setTimeout(retrieve, updateFrequencySeconds * 1000);
}).error(function(err) {
//log.debug('Error fetching exchange rates', err);
setTimeout(function() {
backoffSeconds *= 1.5;
retrieve();
}, backoffSeconds * 1000);
return;
});
setTimeout( self.updateRates , updateFrequencySeconds * 1000);
})
})
};
retrieve();
};
RateService.prototype.getRate = function(code) {
return this._rates[code];
RateService.prototype.getRate = function(code, chain) {
if (chain == 'bch')
return this._ratesBCH[code];
else
return this._rates[code];
};
RateService.prototype.getAlternatives = function() {
@ -90,25 +134,25 @@ RateService.prototype.isAvailable = function() {
RateService.prototype.whenAvailable = function(callback) {
if (this.isAvailable()) {
setTimeout(callback, 1);
setTimeout(callback, 10);
} else {
this._queued.push(callback);
}
};
RateService.prototype.toFiat = function(satoshis, code) {
RateService.prototype.toFiat = function(satoshis, code, chain) {
if (!this.isAvailable()) {
return null;
}
return satoshis * this.SAT_TO_BTC * this.getRate(code);
return satoshis * this.SAT_TO_BTC * this.getRate(code, chain);
};
RateService.prototype.fromFiat = function(amount, code) {
RateService.prototype.fromFiat = function(amount, code, chain) {
if (!this.isAvailable()) {
return null;
}
return amount / this.getRate(code) * this.BTC_TO_SAT;
return amount / this.getRate(code, chain) * this.BTC_TO_SAT;
};
RateService.prototype.listAlternatives = function(sort) {

View file

@ -10,7 +10,7 @@ angular.module('copayApp.services').service('sendMaxService', function(feeServic
*
*/
this.getInfo = function(wallet, cb) {
feeService.getCurrentFeeRate(wallet.credentials.network, function(err, feePerKb) {
feeService.getCurrentFeeRate(wallet.coin, wallet.credentials.network, function(err, feePerKb) {
if (err) return cb(err);
var config = configService.getSync().wallet;

View file

@ -610,5 +610,17 @@ angular.module('copayApp.services')
storage.remove('txConfirmNotif-' + txid, cb);
};
root.setMercadoLibreGiftCards = function(network, gcs, cb) {
storage.set('mercadoLibreGiftCards-' + network, gcs, cb);
};
root.getMercadoLibreGiftCards = function(network, cb) {
storage.get('mercadoLibreGiftCards-' + network, cb);
};
root.removeMercadoLibreGiftCards = function(network, cb) {
storage.remove('MercadoLibreGiftCards-' + network, cb);
};
return root;
});

View file

@ -7,7 +7,7 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
root.formatAmount = function(satoshis, fullPrecision) {
var config = configService.getSync().wallet.settings;
var config = configService.getDefaults().wallet.settings;
if (config.unitCode == 'sat') return satoshis;
//TODO : now only works for english, specify opts to change thousand separator and decimal separator
@ -17,16 +17,15 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
return this.Utils.formatAmount(satoshis, config.unitCode, opts);
};
root.formatAmountStr = function(satoshis) {
root.formatAmountStr = function(coin, satoshis) {
if (isNaN(satoshis)) return;
var config = configService.getSync().wallet.settings;
return root.formatAmount(satoshis) + ' ' + config.unitName;
return root.formatAmount(satoshis) + ' ' + (coin).toUpperCase();
};
root.toFiat = function(satoshis, code, cb) {
root.toFiat = function(coin, satoshis, code, cb) {
if (isNaN(satoshis)) return;
var val = function() {
var v1 = rateService.toFiat(satoshis, code);
var v1 = rateService.toFiat(satoshis, code, coin);
if (!v1) return null;
return v1.toFixed(2);
@ -43,10 +42,10 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
};
};
root.formatToUSD = function(satoshis, cb) {
root.formatToUSD = function(coin, satoshis, cb) {
if (isNaN(satoshis)) return;
var val = function() {
var v1 = rateService.toFiat(satoshis, 'USD');
var v1 = rateService.toFiat(satoshis, 'USD', coin);
if (!v1) return null;
return v1.toFixed(2);
@ -63,12 +62,12 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
};
};
root.formatAlternativeStr = function(satoshis, cb) {
root.formatAlternativeStr = function(coin, satoshis, cb) {
if (isNaN(satoshis)) return;
var config = configService.getSync().wallet.settings;
var val = function() {
var v1 = parseFloat((rateService.toFiat(satoshis, config.alternativeIsoCode)).toFixed(2));
var v1 = parseFloat((rateService.toFiat(satoshis, config.alternativeIsoCode, coin)).toFixed(2));
v1 = $filter('formatFiatAmount')(v1);
if (!v1) return null;
@ -86,7 +85,7 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
};
};
root.processTx = function(tx) {
root.processTx = function(coin, tx) {
if (!tx || tx.action == 'invalid')
return tx;
@ -101,17 +100,17 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
tx.hasMultiplesOutputs = true;
}
tx.amount = lodash.reduce(tx.outputs, function(total, o) {
o.amountStr = root.formatAmountStr(o.amount);
o.alternativeAmountStr = root.formatAlternativeStr(o.amount);
o.amountStr = root.formatAmountStr(coin, o.amount);
o.alternativeAmountStr = root.formatAlternativeStr(coin, o.amount);
return total + o.amount;
}, 0);
}
tx.toAddress = tx.outputs[0].toAddress;
}
tx.amountStr = root.formatAmountStr(tx.amount);
tx.alternativeAmountStr = root.formatAlternativeStr(tx.amount);
tx.feeStr = root.formatAmountStr(tx.fee || tx.fees);
tx.amountStr = root.formatAmountStr(coin, tx.amount);
tx.alternativeAmountStr = root.formatAlternativeStr(coin, tx.amount);
tx.feeStr = root.formatAmountStr(coin, tx.fee || tx.fees);
if (tx.amountStr) {
tx.amountValueStr = tx.amountStr.split(' ')[0];
@ -145,8 +144,6 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
lodash.each(txps, function(tx) {
tx = txFormatService.processTx(tx);
// no future transactions...
if (tx.createdOn > now)
tx.createdOn = now;
@ -157,6 +154,8 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
return;
}
tx = txFormatService.processTx(tx.wallet.coin, tx);
var action = lodash.find(tx.actions, {
copayerId: tx.wallet.copayerId
});
@ -180,7 +179,7 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
return txps;
};
root.parseAmount = function(amount, currency) {
root.parseAmount = function(coin, amount, currency) {
var config = configService.getSync().wallet.settings;
var satToBtc = 1 / 100000000;
var unitToSatoshi = config.unitToSatoshi;
@ -189,21 +188,21 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
var alternativeIsoCode = config.alternativeIsoCode;
// If fiat currency
if (currency != 'bits' && currency != 'BTC' && currency != 'sat') {
if (currency != 'BCH' && currency != 'BTC' && currency != 'sat') {
amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency;
amountSat = rateService.fromFiat(amount, currency).toFixed(0);
amountSat = rateService.fromFiat(amount, currency, coin).toFixed(0);
} else if (currency == 'sat') {
amountSat = amount;
amountUnitStr = root.formatAmountStr(amountSat);
// convert sat to BTC
amountUnitStr = root.formatAmountStr(coin, amountSat);
// convert sat to BTC or BCH
amount = (amountSat * satToBtc).toFixed(8);
currency = 'BTC';
currency = (coin).toUpperCase();
} else {
amountSat = parseInt((amount * unitToSatoshi).toFixed(0));
amountUnitStr = root.formatAmountStr(amountSat);
// convert unit to BTC
amountUnitStr = root.formatAmountStr(coin, amountSat);
// convert unit to BTC or BCH
amount = (amountSat * satToBtc).toFixed(8);
currency = 'BTC';
currency = (coin).toUpperCase();
}
return {

View file

@ -17,6 +17,9 @@ angular.module('copayApp.services')
}, {
name: 'Italiano',
isoCode: 'it',
}, {
name: 'Nederlands',
isoCode: 'nl',
}, {
name: 'Polski',
isoCode: 'pl',

View file

@ -2,8 +2,8 @@
angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, intelTEE, storageService, configService, rateService, uxLanguage, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txFormatService, $ionicModal, $state, bwcService, bitcore, popupService) {
// Ratio low amount warning (fee/amount) in incoming TX
var LOW_AMOUNT_RATIO = 0.15;
// Ratio low amount warning (fee/amount) in incoming TX
var LOW_AMOUNT_RATIO = 0.15;
// Ratio of "many utxos" warning in total balance (fee/amount)
var TOTAL_LOW_WARNING_RATIO = .3;
@ -104,7 +104,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
root.getStatus = function(wallet, opts, cb) {
opts = opts || {};
var walletId = wallet.id;
function processPendingTxps(status) {
var txps = status.pendingTxps;
@ -130,7 +130,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
lodash.each(txps, function(tx) {
tx = txFormatService.processTx(tx);
tx = txFormatService.processTx(wallet.coin, tx);
// no future transactions...
if (tx.createdOn > now)
@ -213,14 +213,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
// Selected unit
cache.unitToSatoshi = config.settings.unitToSatoshi;
cache.satToUnit = 1 / cache.unitToSatoshi;
cache.unitName = config.settings.unitName;
//STR
cache.totalBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat) + ' ' + cache.unitName;
cache.lockedBalanceStr = txFormatService.formatAmount(cache.lockedBalanceSat) + ' ' + cache.unitName;
cache.availableBalanceStr = txFormatService.formatAmount(cache.availableBalanceSat) + ' ' + cache.unitName;
cache.spendableBalanceStr = txFormatService.formatAmount(cache.spendableAmount) + ' ' + cache.unitName;
cache.pendingBalanceStr = txFormatService.formatAmount(cache.pendingAmount) + ' ' + cache.unitName;
cache.totalBalanceStr = txFormatService.formatAmountStr(wallet.coin, cache.totalBalanceSat);
cache.lockedBalanceStr = txFormatService.formatAmountStr(wallet.coin, cache.lockedBalanceSat);
cache.availableBalanceStr = txFormatService.formatAmountStr(wallet.coin, cache.availableBalanceSat);
cache.spendableBalanceStr = txFormatService.formatAmountStr(wallet.coin, cache.spendableAmount);
cache.pendingBalanceStr = txFormatService.formatAmountStr(wallet.coin, cache.pendingAmount);
cache.alternativeName = config.settings.alternativeName;
cache.alternativeIsoCode = config.settings.alternativeIsoCode;
@ -238,11 +237,11 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
rateService.whenAvailable(function() {
var totalBalanceAlternative = rateService.toFiat(cache.totalBalanceSat, cache.alternativeIsoCode);
var pendingBalanceAlternative = rateService.toFiat(cache.pendingAmount, cache.alternativeIsoCode);
var lockedBalanceAlternative = rateService.toFiat(cache.lockedBalanceSat, cache.alternativeIsoCode);
var spendableBalanceAlternative = rateService.toFiat(cache.spendableAmount, cache.alternativeIsoCode);
var alternativeConversionRate = rateService.toFiat(100000000, cache.alternativeIsoCode);
var totalBalanceAlternative = rateService.toFiat(cache.totalBalanceSat, cache.alternativeIsoCode, wallet.coin);
var pendingBalanceAlternative = rateService.toFiat(cache.pendingAmount, cache.alternativeIsoCode, wallet.coin);
var lockedBalanceAlternative = rateService.toFiat(cache.lockedBalanceSat, cache.alternativeIsoCode, wallet.coin);
var spendableBalanceAlternative = rateService.toFiat(cache.spendableAmount, cache.alternativeIsoCode, wallet.coin);
var alternativeConversionRate = rateService.toFiat(100000000, cache.alternativeIsoCode, wallet.coin);
cache.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative);
cache.pendingBalanceAlternative = $filter('formatFiatAmount')(pendingBalanceAlternative);
@ -260,6 +259,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
};
function cacheStatus(status) {
if (status.wallet && status.wallet.scanStatus == 'running') return;
wallet.cachedStatus = status ||  {};
var cache = wallet.cachedStatus;
cache.statusUpdatedOn = Date.now();
@ -304,6 +305,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
cacheStatus(status);
wallet.scanning = status.wallet && status.wallet.scanStatus == 'running';
return cb(null, status);
});
};
@ -366,7 +369,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
wallet.hasUnsafeConfirmed = false;
lodash.each(txs, function(tx) {
tx = txFormatService.processTx(tx);
tx = txFormatService.processTx(wallet.coin, tx);
// no future transactions...
if (tx.time > now)
@ -400,7 +403,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var LIMIT = 50;
var requestLimit = FIRST_LIMIT;
var walletId = wallet.credentials.walletId;
var config = configService.getSync().wallet.settings;
var opts = opts || {};
var progressFn = opts.progressFn || function() {};
@ -414,18 +416,16 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var fixTxsUnit = function(txs) {
if (!txs || !txs[0] || !txs[0].amountStr) return;
var cacheUnit = txs[0].amountStr.split(' ')[1];
var cacheCoin = txs[0].amountStr.split(' ')[1];
if (cacheUnit == config.unitName)
return;
if (cacheCoin == 'bits') {
var name = ' ' + config.unitName;
$log.debug('Fixing Tx Cache Unit to:' + name)
lodash.each(txs, function(tx) {
tx.amountStr = txFormatService.formatAmount(tx.amount) + name;
tx.feeStr = txFormatService.formatAmount(tx.fees) + name;
});
$log.debug('Fixing Tx Cache Unit to: ' + wallet.coin)
lodash.each(txs, function(tx) {
tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.amount);
tx.feeStr = txFormatService.formatAmountStr(wallet.coin, tx.fees);
});
}
};
getSavedTxs(walletId, function(err, txsFromLocal) {
@ -788,7 +788,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
//prefs.email (may come from arguments)
prefs.email = config.emailNotifications.email;
prefs.language = uxLanguage.getCurrentLanguage();
prefs.unit = walletSettings.unitCode;
// prefs.unit = walletSettings.unitCode; // TODO: remove, not used
updateRemotePreferencesFor(lodash.clone(clients), prefs, function(err) {
if (err) return cb(err);
@ -820,13 +820,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
$log.debug('Scanning wallet ' + wallet.id);
if (!wallet.isComplete()) return;
wallet.updating = true;
ongoingProcess.set('scanning', true);
wallet.scanning = true;
wallet.startScan({
includeCopayerBranches: true,
}, function(err) {
wallet.updating = false;
ongoingProcess.set('scanning', false);
return cb(err);
});
};
@ -922,28 +919,30 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
};
// Approx utxo amount, from which the uxto is economically redeemable
// Approx utxo amount, from which the uxto is economically redeemable
root.getMinFee = function(wallet, feeLevels, nbOutputs) {
var lowLevelRate = (lodash.find(feeLevels[wallet.network], {
level: 'normal',
}).feePerKB / 1000).toFixed(0);
}).feePerKb / 1000).toFixed(0);
var size = root.getEstimatedTxSize(wallet, nbOutputs);
return size * lowLevelRate;
};
// Approx utxo amount, from which the uxto is economically redeemable
// Approx utxo amount, from which the uxto is economically redeemable
root.getLowAmount = function(wallet, feeLevels, nbOutputs) {
var minFee = root.getMinFee(wallet,feeLevels, nbOutputs);
return parseInt( minFee / LOW_AMOUNT_RATIO);
var minFee = root.getMinFee(wallet, feeLevels, nbOutputs);
return parseInt(minFee / LOW_AMOUNT_RATIO);
};
root.getLowUtxos = function(wallet, levels, cb) {
wallet.getUtxos({}, function(err, resp) {
wallet.getUtxos({
coin: wallet.coin
}, function(err, resp) {
if (err || !resp || !resp.length) return cb();
var minFee = root.getMinFee(wallet, levels, resp.length);
@ -959,7 +958,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var totalLow = lodash.sum(lowUtxos, 'satoshis');
return cb(err, {
allUtxos: resp || [],
allUtxos: resp || [],
lowUtxos: lowUtxos || [],
warning: minFee / balance > TOTAL_LOW_WARNING_RATIO,
minFee: minFee,
@ -1236,5 +1235,38 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
root.getProtocolHandler = function(wallet) {
if (wallet.coin== 'bch') return 'bitcoincash';
else return 'bitcoin';
}
root.copyCopayers = function(wallet, newWallet, cb) {
var c = wallet.credentials;
var walletPrivKey = bitcore.PrivateKey.fromString(c.walletPrivKey);
var copayer = 1,
i = 0,
l = c.publicKeyRing.length;
var mainErr = null;
lodash.each(c.publicKeyRing, function(item) {
var name = item.copayerName || ('copayer ' + copayer++);
newWallet._doJoinWallet(newWallet.credentials.walletId, walletPrivKey, item.xPubKey, item.requestPubKey, name, {
coin: newWallet.credentials.coin,
}, function(err) {
//Ignore error is copayer already in wallet
if (err && !(err instanceof errors.COPAYER_IN_WALLET)) {
mainErr = err;
}
if (++i == l) {
return cb(mainErr);
}
});
});
};
return root;
});