Merge branch 'master' of https://github.com/bitpay/copay
This commit is contained in:
commit
98c93e3c6f
133 changed files with 5157 additions and 3511 deletions
|
|
@ -14,18 +14,18 @@ angular.module('copayApp.services').factory('bitpayAccountService', function($lo
|
|||
* email: email address associated with bitpay account
|
||||
* otp: two-factor one-time use password
|
||||
* }
|
||||
*
|
||||
*
|
||||
* pairingReason - text string to be embedded into popup message. If `null` then the reason
|
||||
* message is not shown to the UI.
|
||||
* "To {{reason}} you must pair this app with your BitPay account ({{email}})."
|
||||
*
|
||||
*
|
||||
* cb - callback after completion
|
||||
* callback(err, paired, apiContext)
|
||||
*
|
||||
* err - something unexpected happened which prevented the pairing
|
||||
*
|
||||
*
|
||||
* paired - boolean indicating whether the pairing was compledted by the user
|
||||
*
|
||||
*
|
||||
* apiContext - the context needed for making future api calls
|
||||
* {
|
||||
* token: api token for use in future calls
|
||||
|
|
@ -33,6 +33,7 @@ angular.module('copayApp.services').factory('bitpayAccountService', function($lo
|
|||
* appIdentity: the identity of this app
|
||||
* }
|
||||
*/
|
||||
|
||||
root.pair = function(pairData, pairingReason, cb) {
|
||||
checkOtp(pairData, function(otp) {
|
||||
pairData.otp = otp;
|
||||
|
|
@ -66,14 +67,19 @@ angular.module('copayApp.services').factory('bitpayAccountService', function($lo
|
|||
fetchBasicInfo(apiContext, function(err, basicInfo) {
|
||||
if (err) return cb(err);
|
||||
var title = gettextCatalog.getString('Add BitPay Account?');
|
||||
var msgDetail = 'Add this BitPay account ({{email}})?';
|
||||
var msg;
|
||||
|
||||
if (pairingReason) {
|
||||
msgDetail = 'To {{reason}} you must first add your BitPay account - {{email}}';
|
||||
}
|
||||
var msg = gettextCatalog.getString(msgDetail, {
|
||||
reason: pairingReason,
|
||||
email: pairData.email
|
||||
});
|
||||
msg = gettextCatalog.getString('To {{reason}} you must first add your BitPay account - {{email}}', {
|
||||
reason: pairingReason,
|
||||
email: pairData.email
|
||||
});
|
||||
} else {
|
||||
msg = gettextCatalog.getString('Add this BitPay account ({{email}})?', {
|
||||
email: pairData.email
|
||||
});
|
||||
}
|
||||
|
||||
var ok = gettextCatalog.getString('Add Account');
|
||||
var cancel = gettextCatalog.getString('Go back');
|
||||
popupService.showConfirm(title, msg, ok, cancel, function(res) {
|
||||
|
|
@ -182,5 +188,5 @@ angular.module('copayApp.services').factory('bitpayAccountService', function($lo
|
|||
};
|
||||
|
||||
return root;
|
||||
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ angular.module('copayApp.services')
|
|||
body = gettextCatalog.getString('Amount below minimum allowed');
|
||||
break;
|
||||
case 'INCORRECT_ADDRESS_NETWORK':
|
||||
body = gettextCatalog.getString('Incorrect address network');
|
||||
body = gettextCatalog.getString('Incorrect network address');
|
||||
break;
|
||||
case 'COPAYER_REGISTERED':
|
||||
body = gettextCatalog.getString('Key already associated with an existing wallet');
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('coinbaseService', function($http, $log, $window, $filter, platformInfo, lodash, storageService, configService, appConfigService, txFormatService, buyAndSellService, $rootScope) {
|
||||
angular.module('copayApp.services').factory('coinbaseService', function($http, $log, $window, $filter, platformInfo, lodash, storageService, configService, appConfigService, txFormatService, buyAndSellService, $rootScope, feeService) {
|
||||
var root = {};
|
||||
var credentials = {};
|
||||
var isCordova = platformInfo.isCordova;
|
||||
var isNW = platformInfo.isNW;
|
||||
var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova;
|
||||
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
||||
|
||||
root.priceSensitivity = [{
|
||||
value: 0.5,
|
||||
|
|
@ -107,6 +107,19 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
|
|||
};
|
||||
};
|
||||
|
||||
root.checkEnoughFundsForFee = function(amount, cb) {
|
||||
_getNetAmount(amount, function(err, reducedAmount) {
|
||||
if (err) return cb(err);
|
||||
|
||||
// Check if transaction has enough funds to transfer bitcoin from Coinbase to Copay
|
||||
if (reducedAmount < 0) {
|
||||
return cb('Not enough funds for fee');
|
||||
}
|
||||
|
||||
return cb();
|
||||
});
|
||||
};
|
||||
|
||||
root.getSignupUrl = function() {
|
||||
return credentials.HOST + '/signup';
|
||||
}
|
||||
|
|
@ -153,6 +166,17 @@ 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) {
|
||||
if (err) return cb(err);
|
||||
var feeBTC = (feePerKB * txNormalFeeKB / 100000000).toFixed(8);
|
||||
|
||||
return cb(null, amount - feeBTC, feeBTC);
|
||||
});
|
||||
};
|
||||
|
||||
var _refreshToken = function(refreshToken, cb) {
|
||||
var req = {
|
||||
method: 'POST',
|
||||
|
|
@ -303,14 +327,14 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
|
|||
};
|
||||
|
||||
root.getBuyOrder = function(token, accountId, buyId, cb) {
|
||||
if (!token) return cb('Invalid Token');
|
||||
$http(_get('/accounts/' + accountId + '/buys/' + buyId, token)).then(function(data) {
|
||||
$log.info('Coinbase Buy Info: SUCCESS');
|
||||
return cb(null, data.data);
|
||||
}, function(data) {
|
||||
$log.error('Coinbase Buy Info: ERROR ' + data.statusText);
|
||||
return cb(data.data);
|
||||
});
|
||||
if (!token) return cb('Invalid Token');
|
||||
$http(_get('/accounts/' + accountId + '/buys/' + buyId, token)).then(function(data) {
|
||||
$log.info('Coinbase Buy Info: SUCCESS');
|
||||
return cb(null, data.data);
|
||||
}, function(data) {
|
||||
$log.error('Coinbase Buy Info: ERROR ' + data.statusText);
|
||||
return cb(data.data);
|
||||
});
|
||||
};
|
||||
|
||||
root.getTransaction = function(token, accountId, transactionId, cb) {
|
||||
|
|
@ -657,13 +681,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
|
|||
var _sendToWallet = function(tx, accessToken, accountId, coinbasePendingTransactions) {
|
||||
if (!tx) return;
|
||||
var desc = appConfigService.nameCase + ' Wallet';
|
||||
var data = {
|
||||
to: tx.toAddr,
|
||||
amount: tx.amount.amount,
|
||||
currency: tx.amount.currency,
|
||||
description: desc
|
||||
};
|
||||
root.sendTo(accessToken, accountId, data, function(err, res) {
|
||||
_getNetAmount(tx.amount.amount, function(err, amountBTC, feeBTC) {
|
||||
if (err) {
|
||||
_savePendingTransaction(tx, {
|
||||
status: 'error',
|
||||
|
|
@ -672,8 +690,18 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
|
|||
if (err) $log.debug(err);
|
||||
_updateTxs(coinbasePendingTransactions);
|
||||
});
|
||||
} else {
|
||||
if (res.data && !res.data.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = {
|
||||
to: tx.toAddr,
|
||||
amount: amountBTC,
|
||||
currency: tx.amount.currency,
|
||||
description: desc,
|
||||
fee: feeBTC
|
||||
};
|
||||
root.sendTo(accessToken, accountId, data, function(err, res) {
|
||||
if (err) {
|
||||
_savePendingTransaction(tx, {
|
||||
status: 'error',
|
||||
error: err
|
||||
|
|
@ -681,19 +709,29 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
|
|||
if (err) $log.debug(err);
|
||||
_updateTxs(coinbasePendingTransactions);
|
||||
});
|
||||
return;
|
||||
}
|
||||
root.getTransaction(accessToken, accountId, res.data.id, function(err, sendTx) {
|
||||
_savePendingTransaction(tx, {
|
||||
remove: true
|
||||
}, function(err) {
|
||||
_savePendingTransaction(sendTx.data, {}, function(err) {
|
||||
} else {
|
||||
if (res.data && !res.data.id) {
|
||||
_savePendingTransaction(tx, {
|
||||
status: 'error',
|
||||
error: err
|
||||
}, function(err) {
|
||||
if (err) $log.debug(err);
|
||||
_updateTxs(coinbasePendingTransactions);
|
||||
});
|
||||
return;
|
||||
}
|
||||
root.getTransaction(accessToken, accountId, res.data.id, function(err, sendTx) {
|
||||
_savePendingTransaction(tx, {
|
||||
remove: true
|
||||
}, function(err) {
|
||||
_savePendingTransaction(sendTx.data, {}, function(err) {
|
||||
if (err) $log.debug(err);
|
||||
_updateTxs(coinbasePendingTransactions);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -723,7 +761,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
|
|||
|
||||
var register = function() {
|
||||
|
||||
root.isActive(function(err, isActive){
|
||||
root.isActive(function(err, isActive) {
|
||||
if (err) return;
|
||||
|
||||
buyAndSellService.register({
|
||||
|
|
@ -742,7 +780,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
|
|||
|
||||
$rootScope.$on('bwsEvent', function(e, walletId, type, n) {
|
||||
if (type == 'NewBlock' && n && n.data && n.data.network == 'livenet') {
|
||||
root.isActive(function(err,isActive){
|
||||
root.isActive(function(err, isActive) {
|
||||
// Update Coinbase
|
||||
if (isActive)
|
||||
root.updatePendingTransactions();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('configService', function(storageService, lodash, $log, $timeout, $rootScope) {
|
||||
angular.module('copayApp.services').factory('configService', function(storageService, lodash, $log, $timeout, $rootScope, platformInfo) {
|
||||
var root = {};
|
||||
|
||||
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
||||
|
||||
var defaultConfig = {
|
||||
// wallet limits
|
||||
limits: {
|
||||
|
|
@ -73,7 +75,7 @@ angular.module('copayApp.services').factory('configService', function(storageSer
|
|||
},
|
||||
|
||||
hideNextSteps: {
|
||||
enabled: false,
|
||||
enabled: isWindowsPhoneApp ? true : false,
|
||||
},
|
||||
|
||||
rates: {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('feeService', function($log, $stateParams, bwcService, walletService, configService, gettext, lodash, txFormatService, gettextCatalog) {
|
||||
angular.module('copayApp.services').factory('feeService', function($log, $timeout, $stateParams, bwcService, walletService, configService, gettext, lodash, txFormatService, gettextCatalog) {
|
||||
var root = {};
|
||||
|
||||
var CACHE_TIME_TS = 60; // 1 min
|
||||
|
||||
// Constant fee options to translate
|
||||
root.feeOpts = {
|
||||
urgent: gettext('Urgent'),
|
||||
|
|
@ -12,22 +14,26 @@ angular.module('copayApp.services').factory('feeService', function($log, $stateP
|
|||
superEconomy: gettext('Super Economy')
|
||||
};
|
||||
|
||||
var cache = {
|
||||
updateTs: 0,
|
||||
};
|
||||
|
||||
root.getCurrentFeeLevel = function() {
|
||||
return configService.getSync().wallet.settings.feeLevel || 'normal';
|
||||
};
|
||||
|
||||
root.getCurrentFeeValue = function(network, customFeeLevel, cb) {
|
||||
network = network || 'livenet';
|
||||
var feeLevel = customFeeLevel || root.getCurrentFeeLevel();
|
||||
|
||||
root.getFeeLevels(function(err, levels) {
|
||||
root.getFeeRate = function(network, feeLevel, cb) {
|
||||
network = network || 'livenet';
|
||||
|
||||
root.getFeeLevels(function(err, levels, fromCache) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var feeLevelValue = lodash.find(levels[network], {
|
||||
var feeLevelRate = lodash.find(levels[network], {
|
||||
level: feeLevel
|
||||
});
|
||||
|
||||
if (!feeLevelValue || !feeLevelValue.feePerKB) {
|
||||
if (!feeLevelRate || !feeLevelRate.feePerKB) {
|
||||
return cb({
|
||||
message: gettextCatalog.getString("Could not get dynamic fee for level: {{feeLevel}}", {
|
||||
feeLevel: feeLevel
|
||||
|
|
@ -35,14 +41,24 @@ angular.module('copayApp.services').factory('feeService', function($log, $stateP
|
|||
});
|
||||
}
|
||||
|
||||
var fee = feeLevelValue.feePerKB;
|
||||
$log.debug('Dynamic fee: ' + feeLevel + ' ' + fee + ' SAT');
|
||||
var feeRate = feeLevelRate.feePerKB;
|
||||
|
||||
return cb(null, fee);
|
||||
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.getFeeLevels = function(cb) {
|
||||
|
||||
if (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;
|
||||
|
||||
|
|
@ -51,13 +67,18 @@ angular.module('copayApp.services').factory('feeService', function($log, $stateP
|
|||
if (errLivenet || errTestnet) {
|
||||
return cb(gettextCatalog.getString('Could not get dynamic fee'));
|
||||
}
|
||||
return cb(null, {
|
||||
|
||||
cache.updateTs = Date.now();
|
||||
cache.data = {
|
||||
'livenet': levelsLivenet,
|
||||
'testnet': levelsTestnet
|
||||
});
|
||||
};
|
||||
|
||||
return cb(null, cache.data);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
|
|||
var root = {};
|
||||
var credentials = {};
|
||||
var isCordova = platformInfo.isCordova;
|
||||
var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova;
|
||||
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
||||
|
||||
var setCredentials = function() {
|
||||
if (!$window.externalServices || !$window.externalServices.glidera) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
angular.module('copayApp.services').factory('ongoingProcess', function($log, $timeout, $filter, lodash, $ionicLoading, gettext, platformInfo) {
|
||||
var root = {};
|
||||
var isCordova = platformInfo.isCordova;
|
||||
var isWP = platformInfo.isWP;
|
||||
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
||||
|
||||
var ongoingProcess = {};
|
||||
|
||||
|
|
@ -17,8 +17,8 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
|
|||
'creatingTx': gettext('Creating transaction'),
|
||||
'creatingWallet': gettext('Creating Wallet...'),
|
||||
'deletingWallet': gettext('Deleting Wallet...'),
|
||||
'extractingWalletInfo': gettext('Extracting Wallet Information...'),
|
||||
'fetchingPayPro': gettext('Fetching Payment Information'),
|
||||
'extractingWalletInfo': gettext('Extracting Wallet information...'),
|
||||
'fetchingPayPro': gettext('Fetching payment information'),
|
||||
'generatingCSV': gettext('Generating .csv file...'),
|
||||
'gettingFeeLevels': gettext('Getting fee levels...'),
|
||||
'importingWallet': gettext('Importing Wallet...'),
|
||||
|
|
@ -45,12 +45,12 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
|
|||
'cancelingGiftCard': 'Canceling Gift Card...',
|
||||
'creatingGiftCard': 'Creating Gift Card...',
|
||||
'buyingGiftCard': 'Buying Gift Card...',
|
||||
'topup': 'Top up in progress...'
|
||||
'topup': gettext('Top up in progress...')
|
||||
};
|
||||
|
||||
root.clear = function() {
|
||||
ongoingProcess = {};
|
||||
if (isCordova && !isWP) {
|
||||
if (isCordova && !isWindowsPhoneApp) {
|
||||
window.plugins.spinnerDialog.hide();
|
||||
} else {
|
||||
$ionicLoading.hide();
|
||||
|
|
@ -80,19 +80,19 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
|
|||
if (customHandler) {
|
||||
customHandler(processName, showName, isOn);
|
||||
} else if (root.onGoingProcessName) {
|
||||
if (isCordova && !isWP) {
|
||||
if (isCordova && !isWindowsPhoneApp) {
|
||||
window.plugins.spinnerDialog.show(null, showName, root.clear);
|
||||
} else {
|
||||
|
||||
var tmpl;
|
||||
if (isWP) tmpl = '<div>' + showName + '</div>';
|
||||
if (isWindowsPhoneApp) tmpl = '<div>' + showName + '</div>';
|
||||
else tmpl = '<div class="item-icon-left">' + showName + '<ion-spinner class="spinner-stable" icon="lines"></ion-spinner></div>';
|
||||
$ionicLoading.show({
|
||||
template: tmpl
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (isCordova && !isWP) {
|
||||
if (isCordova && !isWindowsPhoneApp) {
|
||||
window.plugins.spinnerDialog.hide();
|
||||
} else {
|
||||
$ionicLoading.hide();
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@
|
|||
angular.module('copayApp.services').service('popupService', function($log, $ionicPopup, platformInfo, gettextCatalog) {
|
||||
|
||||
var isCordova = platformInfo.isCordova;
|
||||
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
||||
|
||||
/*************** Ionic ****************/
|
||||
|
||||
var _ionicAlert = function(title, message, cb, buttonName) {
|
||||
var _ionicAlert = function(title, message, cb, okText) {
|
||||
if (!cb) cb = function() {};
|
||||
$ionicPopup.alert({
|
||||
title: title,
|
||||
subTitle: message,
|
||||
okType: 'button-clear button-positive',
|
||||
okText: buttonName || gettextCatalog.getString('OK'),
|
||||
okText: okText || gettextCatalog.getString('OK'),
|
||||
}).then(cb);
|
||||
};
|
||||
|
||||
|
|
@ -45,9 +46,11 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
|
|||
|
||||
/*************** Cordova ****************/
|
||||
|
||||
var _cordovaAlert = function(title, message, cb, buttonName) {
|
||||
var _cordovaAlert = function(title, message, cb, okText) {
|
||||
if (!cb) cb = function() {};
|
||||
navigator.notification.alert(message, cb, title, buttonName);
|
||||
title = title ? title : '';
|
||||
okText = okText || gettextCatalog.getString('OK');
|
||||
navigator.notification.alert(message, cb, title, okText);
|
||||
};
|
||||
|
||||
var _cordovaConfirm = function(title, message, okText, cancelText, cb) {
|
||||
|
|
@ -57,6 +60,7 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
|
|||
}
|
||||
okText = okText || gettextCatalog.getString('OK');
|
||||
cancelText = cancelText || gettextCatalog.getString('Cancel');
|
||||
title = title ? title : '';
|
||||
navigator.notification.confirm(message, onConfirm, title, [cancelText, okText]);
|
||||
};
|
||||
|
||||
|
|
@ -65,7 +69,10 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
|
|||
if (results.buttonIndex == 1) return cb(results.input1);
|
||||
else return cb();
|
||||
}
|
||||
navigator.notification.prompt(message, onPrompt, title, null, opts.defaultText);
|
||||
var okText = gettextCatalog.getString('OK');
|
||||
var cancelText = gettextCatalog.getString('Cancel');
|
||||
title = title ? title : '';
|
||||
navigator.notification.prompt(message, onPrompt, title, [cancelText, okText], opts.defaultText);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -76,14 +83,14 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
|
|||
* @param {Callback} Function (optional)
|
||||
*/
|
||||
|
||||
this.showAlert = function(title, msg, cb, buttonName) {
|
||||
this.showAlert = function(title, msg, cb, okText) {
|
||||
var message = (msg && msg.message) ? msg.message : msg;
|
||||
$log.warn(title ? (title + ': ' + message) : message);
|
||||
|
||||
if (isCordova)
|
||||
_cordovaAlert(title, message, cb, buttonName);
|
||||
_cordovaAlert(title, message, cb, okText);
|
||||
else
|
||||
_ionicAlert(title, message, cb, buttonName);
|
||||
_ionicAlert(title, message, cb, okText);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -121,7 +128,7 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
|
|||
|
||||
opts = opts || {};
|
||||
|
||||
if (isCordova && !opts.forceHTMLPrompt)
|
||||
if (isCordova && !isWindowsPhoneApp && !opts.forceHTMLPrompt)
|
||||
_cordovaPrompt(title, message, opts, cb);
|
||||
else
|
||||
_ionicPrompt(title, message, opts, cb);
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ angular.module('copayApp.services')
|
|||
|
||||
var isChromeApp = platformInfo.isChromeApp;
|
||||
var isCordova = platformInfo.isCordova;
|
||||
var isWP = platformInfo.isWP;
|
||||
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
||||
var isIOS = platformInfo.isIOS;
|
||||
|
||||
var root = {};
|
||||
var errors = bwcService.getErrors();
|
||||
var usePushNotifications = isCordova && !isWP;
|
||||
var usePushNotifications = isCordova && !isWindowsPhoneApp;
|
||||
|
||||
var UPDATE_PERIOD = 15;
|
||||
|
||||
|
|
@ -208,9 +208,9 @@ angular.module('copayApp.services')
|
|||
};
|
||||
|
||||
var shouldSkipValidation = function(walletId) {
|
||||
return root.profile.isChecked(platformInfo.ua, walletId) || isIOS || isWP;
|
||||
}
|
||||
// Used when reading wallets from the profile
|
||||
return root.profile.isChecked(platformInfo.ua, walletId) || isIOS || isWindowsPhoneApp;
|
||||
}
|
||||
// Used when reading wallets from the profile
|
||||
root.bindWallet = function(credentials, cb) {
|
||||
if (!credentials.walletId || !credentials.m)
|
||||
return cb('bindWallet should receive credentials JSON');
|
||||
|
|
|
|||
|
|
@ -17,27 +17,27 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
var canChangeCamera = false;
|
||||
var canOpenSettings = false;
|
||||
|
||||
function _checkCapabilities(status){
|
||||
function _checkCapabilities(status) {
|
||||
$log.debug('scannerService is reviewing platform capabilities...');
|
||||
// Permission can be assumed on the desktop builds
|
||||
hasPermission = (isDesktop || status.authorized)? true: false;
|
||||
isDenied = status.denied? true : false;
|
||||
isRestricted = status.restricted? true : false;
|
||||
canEnableLight = status.canEnableLight? true : false;
|
||||
canChangeCamera = status.canChangeCamera? true : false;
|
||||
canOpenSettings = status.canOpenSettings? true : false;
|
||||
hasPermission = (isDesktop || status.authorized) ? true : false;
|
||||
isDenied = status.denied ? true : false;
|
||||
isRestricted = status.restricted ? true : false;
|
||||
canEnableLight = status.canEnableLight ? true : false;
|
||||
canChangeCamera = status.canChangeCamera ? true : false;
|
||||
canOpenSettings = status.canOpenSettings ? true : false;
|
||||
_logCapabilities();
|
||||
}
|
||||
|
||||
function _logCapabilities(){
|
||||
function _orIsNot(bool){
|
||||
return bool? '' : 'not ';
|
||||
function _logCapabilities() {
|
||||
function _orIsNot(bool) {
|
||||
return bool ? '' : 'not ';
|
||||
}
|
||||
$log.debug('A camera is ' + _orIsNot(isAvailable) + 'available to this app.');
|
||||
var access = 'not authorized';
|
||||
if(hasPermission) access = 'authorized';
|
||||
if(isDenied) access = 'denied';
|
||||
if(isRestricted) access = 'restricted';
|
||||
if (hasPermission) access = 'authorized';
|
||||
if (isDenied) access = 'denied';
|
||||
if (isRestricted) access = 'restricted';
|
||||
$log.debug('Camera access is ' + access + '.');
|
||||
$log.debug('Support for opening device settings is ' + _orIsNot(canOpenSettings) + 'available on this platform.');
|
||||
$log.debug('A light is ' + _orIsNot(canEnableLight) + 'available on this platform.');
|
||||
|
|
@ -47,7 +47,7 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
/**
|
||||
* Immediately return known capabilities of the current platform.
|
||||
*/
|
||||
this.getCapabilities = function(){
|
||||
this.getCapabilities = function() {
|
||||
return {
|
||||
isAvailable: isAvailable,
|
||||
hasPermission: hasPermission,
|
||||
|
|
@ -68,18 +68,18 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
* The `status` of QRScanner is returned to the callback.
|
||||
*/
|
||||
this.gentleInitialize = function(callback) {
|
||||
if(initializeStarted && !isDesktop){
|
||||
QRScanner.getStatus(function(status){
|
||||
if (initializeStarted && !isDesktop) {
|
||||
QRScanner.getStatus(function(status) {
|
||||
_completeInitialization(status, callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
initializeStarted = true;
|
||||
$log.debug('Trying to pre-initialize QRScanner.');
|
||||
if(!isDesktop){
|
||||
QRScanner.getStatus(function(status){
|
||||
if (!isDesktop) {
|
||||
QRScanner.getStatus(function(status) {
|
||||
_checkCapabilities(status);
|
||||
if(status.authorized){
|
||||
if (status.authorized) {
|
||||
$log.debug('Camera permission already granted.');
|
||||
initialize(callback);
|
||||
} else {
|
||||
|
|
@ -92,14 +92,14 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
}
|
||||
};
|
||||
|
||||
function initialize(callback){
|
||||
function initialize(callback) {
|
||||
$log.debug('Initializing scanner...');
|
||||
QRScanner.prepare(function(err, status){
|
||||
if(err){
|
||||
QRScanner.prepare(function(err, status) {
|
||||
if (err) {
|
||||
isAvailable = false;
|
||||
$log.error(err);
|
||||
// does not return `status` if there is an error
|
||||
QRScanner.getStatus(function(status){
|
||||
QRScanner.getStatus(function(status) {
|
||||
_completeInitialization(status, callback);
|
||||
});
|
||||
} else {
|
||||
|
|
@ -112,18 +112,19 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
// This could be much cleaner with a Promise API
|
||||
// (needs a polyfill for some platforms)
|
||||
var initializeCompleted = false;
|
||||
function _completeInitialization(status, callback){
|
||||
|
||||
function _completeInitialization(status, callback) {
|
||||
_checkCapabilities(status);
|
||||
initializeCompleted = true;
|
||||
$rootScope.$emit('scannerServiceInitialized');
|
||||
if(typeof callback === "function"){
|
||||
if (typeof callback === "function") {
|
||||
callback(status);
|
||||
}
|
||||
}
|
||||
this.isInitialized = function(){
|
||||
this.isInitialized = function() {
|
||||
return initializeCompleted;
|
||||
};
|
||||
this.initializeStarted = function(){
|
||||
this.initializeStarted = function() {
|
||||
return initializeStarted;
|
||||
};
|
||||
|
||||
|
|
@ -140,21 +141,21 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
*/
|
||||
this.activate = function(callback) {
|
||||
$log.debug('Activating scanner...');
|
||||
QRScanner.show(function(status){
|
||||
initializeCompleted = true;
|
||||
_checkCapabilities(status);
|
||||
if(typeof callback === "function"){
|
||||
callback(status);
|
||||
}
|
||||
});
|
||||
if(nextHide !== null){
|
||||
$timeout.cancel(nextHide);
|
||||
nextHide = null;
|
||||
}
|
||||
if(nextDestroy !== null){
|
||||
$timeout.cancel(nextDestroy);
|
||||
nextDestroy = null;
|
||||
QRScanner.show(function(status) {
|
||||
initializeCompleted = true;
|
||||
_checkCapabilities(status);
|
||||
if (typeof callback === "function") {
|
||||
callback(status);
|
||||
}
|
||||
});
|
||||
if (nextHide !== null) {
|
||||
$timeout.cancel(nextHide);
|
||||
nextHide = null;
|
||||
}
|
||||
if (nextDestroy !== null) {
|
||||
$timeout.cancel(nextDestroy);
|
||||
nextDestroy = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -193,18 +194,18 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
// Natively hide the QRScanner's preview
|
||||
// On mobile platforms, this can reduce GPU/power usage
|
||||
// On desktop, this fully turns off the camera (and any associated privacy lights)
|
||||
function _hide(){
|
||||
function _hide() {
|
||||
$log.debug('Scanner not in use for ' + hideAfterSeconds + ' seconds, hiding...');
|
||||
QRScanner.hide();
|
||||
}
|
||||
|
||||
// Reduce QRScanner power/processing consumption by the maximum amount
|
||||
function _destroy(){
|
||||
function _destroy() {
|
||||
$log.debug('Scanner not in use for ' + destroyAfterSeconds + ' seconds, destroying...');
|
||||
QRScanner.destroy();
|
||||
}
|
||||
|
||||
this.reinitialize = function(callback){
|
||||
this.reinitialize = function(callback) {
|
||||
initializeCompleted = false;
|
||||
QRScanner.destroy();
|
||||
initialize(callback);
|
||||
|
|
@ -217,17 +218,18 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
*/
|
||||
this.toggleLight = function(callback) {
|
||||
$log.debug('Toggling light...');
|
||||
if(lightEnabled){
|
||||
if (lightEnabled) {
|
||||
QRScanner.disableLight(_handleResponse);
|
||||
} else {
|
||||
QRScanner.enableLight(_handleResponse);
|
||||
}
|
||||
function _handleResponse(err, status){
|
||||
if(err){
|
||||
|
||||
function _handleResponse(err, status) {
|
||||
if (err) {
|
||||
$log.error(err);
|
||||
} else {
|
||||
lightEnabled = status.lightEnabled;
|
||||
var state = lightEnabled? 'enabled' : 'disabled';
|
||||
var state = lightEnabled ? 'enabled' : 'disabled';
|
||||
$log.debug('Light ' + state + '.');
|
||||
}
|
||||
callback(lightEnabled);
|
||||
|
|
@ -241,16 +243,17 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
* is complete.
|
||||
*/
|
||||
this.toggleCamera = function(callback) {
|
||||
var nextCamera = backCamera? 1 : 0;
|
||||
function cameraToString(index){
|
||||
return index === 1? 'front' : 'back'; // front = 1, back = 0
|
||||
var nextCamera = backCamera ? 1 : 0;
|
||||
|
||||
function cameraToString(index) {
|
||||
return index === 1 ? 'front' : 'back'; // front = 1, back = 0
|
||||
}
|
||||
$log.debug('Toggling to the ' + cameraToString(nextCamera) + ' camera...');
|
||||
QRScanner.useCamera(nextCamera, function(err, status){
|
||||
if(err){
|
||||
QRScanner.useCamera(nextCamera, function(err, status) {
|
||||
if (err) {
|
||||
$log.error(err);
|
||||
}
|
||||
backCamera = status.currentCamera === 1? false : true;
|
||||
backCamera = status.currentCamera === 1 ? false : true;
|
||||
$log.debug('Camera toggled. Now using the ' + cameraToString(backCamera) + ' camera.');
|
||||
callback(status);
|
||||
});
|
||||
|
|
@ -260,4 +263,15 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
$log.debug('Attempting to open device settings...');
|
||||
QRScanner.openSettings();
|
||||
};
|
||||
|
||||
this.useOldScanner = function(callback) {
|
||||
cordova.plugins.barcodeScanner.scan(
|
||||
function(result) {
|
||||
callback(null, result.text);
|
||||
},
|
||||
function(error) {
|
||||
callback(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ angular.module('copayApp.services').service('sendMaxService', function(feeServic
|
|||
*
|
||||
*/
|
||||
this.getInfo = function(wallet, cb) {
|
||||
feeService.getCurrentFeeValue(wallet.credentials.network, null, function(err, feePerKb) {
|
||||
feeService.getCurrentFeeRate(wallet.credentials.network, function(err, feePerKb) {
|
||||
if (err) return cb(err);
|
||||
|
||||
var config = configService.getSync().wallet;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,26 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
|
|||
return root.formatAmount(satoshis) + ' ' + config.unitName;
|
||||
};
|
||||
|
||||
root.toFiat = function(satoshis, code, cb) {
|
||||
if (isNaN(satoshis)) return;
|
||||
var val = function() {
|
||||
var v1 = rateService.toFiat(satoshis, code);
|
||||
if (!v1) return null;
|
||||
|
||||
return v1.toFixed(2);
|
||||
};
|
||||
|
||||
// Async version
|
||||
if (cb) {
|
||||
rateService.whenAvailable(function() {
|
||||
return cb(val());
|
||||
});
|
||||
} else {
|
||||
if (!rateService.isAvailable()) return null;
|
||||
return val();
|
||||
};
|
||||
};
|
||||
|
||||
root.formatToUSD = function(satoshis, cb) {
|
||||
if (isNaN(satoshis)) return;
|
||||
var val = function() {
|
||||
|
|
@ -93,6 +113,11 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
|
|||
tx.alternativeAmountStr = root.formatAlternativeStr(tx.amount);
|
||||
tx.feeStr = root.formatAmountStr(tx.fee || tx.fees);
|
||||
|
||||
if (tx.amountStr) {
|
||||
tx.amountValueStr = tx.amountStr.split(' ')[0];
|
||||
tx.amountUnitStr = tx.amountStr.split(' ')[1];
|
||||
}
|
||||
|
||||
return tx;
|
||||
};
|
||||
|
||||
|
|
@ -164,9 +189,15 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
|
|||
var alternativeIsoCode = config.alternativeIsoCode;
|
||||
|
||||
// If fiat currency
|
||||
if (currency != 'bits' && currency != 'BTC') {
|
||||
if (currency != 'bits' && currency != 'BTC' && currency != 'sat') {
|
||||
amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency;
|
||||
amountSat = rateService.fromFiat(amount, currency).toFixed(0);
|
||||
} else if (currency == 'sat') {
|
||||
amountSat = amount;
|
||||
amountUnitStr = root.formatAmountStr(amountSat);
|
||||
// convert sat to BTC
|
||||
amount = (amountSat * satToBtc).toFixed(8);
|
||||
currency = 'BTC';
|
||||
} else {
|
||||
amountSat = parseInt((amount * unitToSatoshi).toFixed(0));
|
||||
amountUnitStr = root.formatAmountStr(amountSat);
|
||||
|
|
@ -176,8 +207,8 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
|
|||
}
|
||||
|
||||
return {
|
||||
amount: amount,
|
||||
currency: currency,
|
||||
amount: amount,
|
||||
currency: currency,
|
||||
alternativeIsoCode: alternativeIsoCode,
|
||||
amountSat: amountSat,
|
||||
amountUnitStr: amountUnitStr
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
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) {
|
||||
// `wallet` is a decorated version of client.
|
||||
|
||||
// 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;
|
||||
|
||||
var root = {};
|
||||
|
||||
|
|
@ -401,6 +406,11 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
var progressFn = opts.progressFn || function() {};
|
||||
var foundLimitTx = false;
|
||||
|
||||
|
||||
if (opts.feeLevels) {
|
||||
opts.lowAmount = root.getLowAmount(wallet, opts.feeLevels);
|
||||
}
|
||||
|
||||
var fixTxsUnit = function(txs) {
|
||||
if (!txs || !txs[0] || !txs[0].amountStr) return;
|
||||
|
||||
|
|
@ -413,7 +423,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
|
||||
$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;
|
||||
});
|
||||
|
|
@ -511,6 +520,16 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
});
|
||||
}
|
||||
|
||||
function updateLowAmount(txs) {
|
||||
if (!opts.lowAmount) return;
|
||||
|
||||
lodash.each(txs, function(tx) {
|
||||
tx.lowAmount = tx.amount < opts.lowAmount;
|
||||
});
|
||||
};
|
||||
|
||||
updateLowAmount(txs);
|
||||
|
||||
updateNotes(function() {
|
||||
|
||||
// <HACK>
|
||||
|
|
@ -567,9 +586,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
|
||||
root.getTx = function(wallet, txid, cb) {
|
||||
|
||||
function finish(list){
|
||||
function finish(list) {
|
||||
var tx = lodash.find(list, {
|
||||
txid: txid
|
||||
txid: txid
|
||||
});
|
||||
|
||||
if (!tx) return cb('Could not get transaction');
|
||||
|
|
@ -602,7 +621,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
root.getTxHistory = function(wallet, opts, cb) {
|
||||
opts = opts || {};
|
||||
|
|
@ -873,6 +892,81 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
// These 2 functions were taken from
|
||||
// https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/txproposal.js#L243
|
||||
|
||||
function getEstimatedSizeForSingleInput(wallet) {
|
||||
switch (wallet.credentials.addressType) {
|
||||
case 'P2PKH':
|
||||
return 147;
|
||||
default:
|
||||
case 'P2SH':
|
||||
return wallet.m * 72 + wallet.n * 36 + 44;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
root.getEstimatedTxSize = function(wallet, nbOutputs) {
|
||||
// Note: found empirically based on all multisig P2SH inputs and within m & n allowed limits.
|
||||
var safetyMargin = 0.02;
|
||||
|
||||
var overhead = 4 + 4 + 9 + 9;
|
||||
var inputSize = getEstimatedSizeForSingleInput(wallet);
|
||||
var outputSize = 34;
|
||||
var nbInputs = 1; //Assume 1 input
|
||||
var nbOutputs = nbOutputs || 2; // Assume 2 outputs
|
||||
|
||||
var size = overhead + inputSize * nbInputs + outputSize * nbOutputs;
|
||||
return parseInt((size * (1 + safetyMargin)).toFixed(0));
|
||||
};
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
var size = root.getEstimatedTxSize(wallet, nbOutputs);
|
||||
return size * lowLevelRate;
|
||||
};
|
||||
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
|
||||
|
||||
root.getLowUtxos = function(wallet, levels, cb) {
|
||||
|
||||
wallet.getUtxos({}, function(err, resp) {
|
||||
if (err || !resp || !resp.length) return cb();
|
||||
|
||||
var minFee = root.getMinFee(wallet, levels, resp.length);
|
||||
|
||||
var balance = lodash.sum(resp, 'satoshis');
|
||||
|
||||
// for 2 outputs
|
||||
var lowAmount = root.getLowAmount(wallet, levels);
|
||||
var lowUtxos = lodash.filter(resp, function(x) {
|
||||
return x.satoshis < lowAmount;
|
||||
});
|
||||
|
||||
var totalLow = lodash.sum(lowUtxos, 'satoshis');
|
||||
|
||||
return cb(err, {
|
||||
allUtxos: resp || [],
|
||||
lowUtxos: lowUtxos || [],
|
||||
warning: minFee / balance > TOTAL_LOW_WARNING_RATIO,
|
||||
minFee: minFee,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
root.getAddress = function(wallet, forceNew, cb) {
|
||||
storageService.getLastAddress(wallet.id, function(err, addr) {
|
||||
if (err) return cb(err);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue