Merge branch 'wallet/prod' into master
This commit is contained in:
commit
49a6711970
88 changed files with 5949 additions and 2708 deletions
|
|
@ -19,7 +19,9 @@ var modules = [
|
|||
'copayApp.controllers',
|
||||
'copayApp.directives',
|
||||
'copayApp.addons',
|
||||
'bitcoincom.directives'
|
||||
'bitcoincom.controllers',
|
||||
'bitcoincom.directives',
|
||||
'bitcoincom.services'
|
||||
];
|
||||
|
||||
var copayApp = window.copayApp = angular.module('copayApp', modules);
|
||||
|
|
@ -29,4 +31,6 @@ angular.module('copayApp.services', []);
|
|||
angular.module('copayApp.controllers', []);
|
||||
angular.module('copayApp.directives', []);
|
||||
angular.module('copayApp.addons', []);
|
||||
angular.module('bitcoincom.controllers', []);
|
||||
angular.module('bitcoincom.directives', []);
|
||||
angular.module('bitcoincom.services', []);
|
||||
|
|
|
|||
|
|
@ -21,28 +21,14 @@ angular.module('copayApp.controllers').controller('addressbookViewController', f
|
|||
});
|
||||
|
||||
$scope.sendTo = function() {
|
||||
$ionicHistory.removeBackView();
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.send');
|
||||
$timeout(function() {
|
||||
var to = '';
|
||||
if ($scope.addressbookEntry.coin == 'bch') {
|
||||
var a = 'bitcoincash:' + $scope.addressbookEntry.address;
|
||||
to = bitcoinCashJsService.readAddress(a).legacy;
|
||||
} else {
|
||||
to = $scope.addressbookEntry.address;
|
||||
}
|
||||
var stateParams = {
|
||||
data: $scope.addressbookEntry.address,
|
||||
toName: $scope.addressbookEntry.name,
|
||||
toEmail: $scope.addressbookEntry.email,
|
||||
coin: $scope.addressbookEntry.coin
|
||||
};
|
||||
|
||||
var stateParams = {
|
||||
toAddress: to,
|
||||
toName: $scope.addressbookEntry.name,
|
||||
toEmail: $scope.addressbookEntry.email,
|
||||
coin: $scope.addressbookEntry.coin
|
||||
};
|
||||
|
||||
sendFlowService.pushState(stateParams);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}, 100);
|
||||
sendFlowService.start(stateParams);
|
||||
};
|
||||
|
||||
$scope.remove = function(addressbookEntry) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('amountController', amountController);
|
||||
(function(){
|
||||
|
||||
function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window) {
|
||||
angular
|
||||
.module('bitcoincom.controllers')
|
||||
.controller('amountController', amountController);
|
||||
|
||||
function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, ongoingProcess, popupService, profileService, walletService, $window) {
|
||||
var vm = this;
|
||||
|
||||
// Variables
|
||||
vm.allowSend = false;
|
||||
vm.altCurrencyList = [];
|
||||
vm.alternativeAmount = '';
|
||||
vm.alternativeUnit = '';
|
||||
vm.amount = '0';
|
||||
vm.availableFunds = '';
|
||||
vm.canSendAllAvailableFunds = true;
|
||||
vm.errorMessage = '';
|
||||
// Use insufficient for logic, as when the amount is invalid, funds being
|
||||
// either sufficent or insufficient doesn't make sense.
|
||||
vm.fundsAreInsufficient = false;
|
||||
|
|
@ -20,9 +27,13 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
vm.lastUsedPopularList = [];
|
||||
vm.maxAmount = 0;
|
||||
vm.minAmount = 0;
|
||||
vm.sendableFunds = '';
|
||||
vm.showSendMaxButton = false;
|
||||
vm.showSendLimitMaxButton = false;
|
||||
vm.thirdParty = false;
|
||||
vm.unit = '';
|
||||
|
||||
// Functions
|
||||
vm.changeUnit = changeUnit;
|
||||
vm.close = close;
|
||||
vm.findCurrency = findCurrency;
|
||||
|
|
@ -35,7 +46,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
vm.removeDigit = removeDigit;
|
||||
vm.save = save;
|
||||
vm.sendMax = sendMax;
|
||||
vm.errorMessage = '';
|
||||
|
||||
|
||||
$scope.$on('$ionicView.beforeEnter', onBeforeEnter);
|
||||
$scope.$on('$ionicView.leave', onLeave);
|
||||
|
|
@ -46,10 +57,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
|
||||
var altCurrencyModal = null;
|
||||
var altUnitIndex = 0;
|
||||
var availableFundsInCrypto = '';
|
||||
var availableFundsInFiat = '';
|
||||
var availableSatoshis = null;
|
||||
var availableUnits = [];
|
||||
var canSendMax = true;
|
||||
var fiatCode;
|
||||
var isNW = platformInfo.isNW;
|
||||
var isAndroid = platformInfo.isAndroid;
|
||||
|
|
@ -57,10 +66,18 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
var lastUsedAltCurrencyList = [];
|
||||
var passthroughParams = {};
|
||||
var satToUnit;
|
||||
var transactionSendableAmount = {
|
||||
crypto: '',
|
||||
satoshis: null
|
||||
};
|
||||
var unitDecimals;
|
||||
var unitIndex = 0;
|
||||
var unitToSatoshi;
|
||||
var useSendMax = false;
|
||||
var walletSpendableAmount = {
|
||||
crypto: '',
|
||||
satoshis: null
|
||||
};
|
||||
|
||||
function onLeave() {
|
||||
angular.element($window).off('keydown');
|
||||
|
|
@ -68,42 +85,26 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
|
||||
function onBeforeEnter(event, data) {
|
||||
if (data.direction == "back") {
|
||||
sendFlowService.popState();
|
||||
sendFlowService.state.pop();
|
||||
}
|
||||
console.log('amount onBeforeEnter after back sendflow ', sendFlowService.state);
|
||||
|
||||
initCurrencies();
|
||||
|
||||
passthroughParams = sendFlowService.getStateClone();
|
||||
passthroughParams = sendFlowService.state.getClone();
|
||||
console.log('amount onBeforeEnter after back sendflow ', passthroughParams);
|
||||
|
||||
vm.fromWalletId = passthroughParams.fromWalletId;
|
||||
vm.toWalletId = passthroughParams.toWalletId;
|
||||
vm.minAmount = parseFloat(passthroughParams.minAmount);
|
||||
vm.maxAmount = parseFloat(passthroughParams.maxAmount);
|
||||
|
||||
if (passthroughParams.thirdParty) {
|
||||
vm.thirdParty = passthroughParams.thirdParty; // Parse stringified JSON-object
|
||||
if (vm.thirdParty) {
|
||||
if (vm.thirdParty.id === 'shapeshift') {
|
||||
if (!vm.thirdParty.data) {
|
||||
vm.thirdParty.data = {};
|
||||
}
|
||||
vm.thirdParty.data['fromWalletId'] = vm.fromWalletId;
|
||||
|
||||
vm.fromWallet = profileService.getWallet(vm.fromWalletId);
|
||||
vm.toWallet = profileService.getWallet(vm.toWalletId);
|
||||
|
||||
shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(data) {
|
||||
vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum);
|
||||
vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.isRequestingSpecificAmount = !passthroughParams.fromWalletId;
|
||||
vm.showSendMaxButton = !vm.isRequestingSpecificAmount;
|
||||
|
||||
var config = configService.getSync().wallet.settings;
|
||||
unitToSatoshi = config.unitToSatoshi;
|
||||
satToUnit = 1 / unitToSatoshi;
|
||||
unitDecimals = config.unitDecimals;
|
||||
|
||||
setAvailableUnits();
|
||||
updateUnitUI();
|
||||
|
|
@ -112,7 +113,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
var reOp = /^[\*\+\-\/]$/;
|
||||
|
||||
if (!isAndroid && !isIos) {
|
||||
var disableKeys = angular.element($window).on('keydown', function(e) {
|
||||
angular.element($window).on('keydown', function(e) {
|
||||
if (!e.key) return;
|
||||
if (e.which === 8) { // you can add others here inside brackets.
|
||||
if (!altCurrencyModal) {
|
||||
|
|
@ -135,10 +136,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
});
|
||||
}
|
||||
|
||||
unitToSatoshi = config.unitToSatoshi;
|
||||
satToUnit = 1 / unitToSatoshi;
|
||||
unitDecimals = config.unitDecimals;
|
||||
|
||||
|
||||
resetAmount();
|
||||
|
||||
processAmount();
|
||||
|
|
@ -210,13 +208,48 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
var fromWallet = profileService.getWallet(passthroughParams.fromWalletId);
|
||||
updateAvailableFundsFromWallet(fromWallet);
|
||||
}
|
||||
|
||||
if (passthroughParams.thirdParty) {
|
||||
vm.thirdParty = passthroughParams.thirdParty; // Parse stringified JSON-object
|
||||
if (vm.thirdParty) {
|
||||
initShapeshift();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
$ionicHistory.goBack();
|
||||
sendFlowService.router.goBack();
|
||||
}
|
||||
|
||||
function initShapeshift() {
|
||||
if (vm.thirdParty.id === 'shapeshift') {
|
||||
vm.thirdParty.data = vm.thirdParty.data || {};
|
||||
|
||||
vm.fromWallet = profileService.getWallet(vm.fromWalletId);
|
||||
vm.toWallet = profileService.getWallet(vm.toWalletId);
|
||||
|
||||
vm.showSendMaxButton = false;
|
||||
vm.showSendLimitMaxButton = false;
|
||||
vm.canSendAllAvailableFunds = false;
|
||||
|
||||
ongoingProcess.set('connectingShapeshift', true);
|
||||
shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function onMarketData(err, data) {
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
if (err) {
|
||||
// Error stop here
|
||||
popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.message, function () {
|
||||
goBack();
|
||||
});
|
||||
} else {
|
||||
vm.thirdParty.data.minAmount = vm.minAmount = parseFloat(data.minimum);
|
||||
vm.thirdParty.data.maxAmount = vm.maxAmount = parseFloat(data.maxLimit);
|
||||
setMaximumButtonFromWallet(vm.fromWallet);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function paste(value) {
|
||||
vm.amount = value;
|
||||
processAmount();
|
||||
|
|
@ -232,8 +265,28 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
}
|
||||
|
||||
function sendMax() {
|
||||
useSendMax = true;
|
||||
finish();
|
||||
if (canSendMax) {
|
||||
useSendMax = true;
|
||||
finish();
|
||||
} else {
|
||||
var transactionSendableAmountInUnits = transactionSendableAmount.satoshis * satToUnit;
|
||||
if (vm.minAmount && transactionSendableAmountInUnits < vm.minAmount) {
|
||||
popupService.showAlert(
|
||||
gettextCatalog.getString('Insufficient funds'),
|
||||
gettextCatalog.getString('Amount below minimum allowed')
|
||||
);
|
||||
} else {
|
||||
// Need to be precise, so use crypto directly rather than fiat with exchange rate
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
var tempIndex = altUnitIndex;
|
||||
altUnitIndex = unitIndex;
|
||||
unitIndex = tempIndex;
|
||||
}
|
||||
vm.amount = transactionSendableAmountInUnits.toFixed(LENGTH_AFTER_COMMA_EXPRESSION_LIMIT);
|
||||
useSendMax = true;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateUnitUI() {
|
||||
|
|
@ -353,8 +406,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
amountInCrypto = a;
|
||||
var amountInSatoshis = a * unitToSatoshi;
|
||||
vm.fundsAreInsufficient = !!passthroughParams.fromWalletId
|
||||
&& availableSatoshis !== null
|
||||
&& availableSatoshis < amountInSatoshis;
|
||||
&& walletSpendableAmount.satoshis !== null
|
||||
&& walletSpendableAmount.satoshis < amountInSatoshis;
|
||||
|
||||
vm.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true);
|
||||
vm.allowSend = lodash.isNumber(a)
|
||||
|
|
@ -374,8 +427,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
} else {
|
||||
amountInCrypto = result;
|
||||
vm.fundsAreInsufficient = passthroughParams.fromWalletId
|
||||
&& availableSatoshis !== null
|
||||
&& availableSatoshis < result * unitToSatoshi;
|
||||
&& walletSpendableAmount.satoshis !== null
|
||||
&& walletSpendableAmount.satoshis < result * unitToSatoshi;
|
||||
|
||||
vm.alternativeAmount = $filter('formatFiatAmount')(toFiat(result));
|
||||
vm.allowSend = lodash.isNumber(result)
|
||||
|
|
@ -449,13 +502,13 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
|
||||
var satoshis = 0;
|
||||
if (unit.isFiat) {
|
||||
satoshis = (fromFiat(uiAmount) * unitToSatoshi).toFixed(0);
|
||||
satoshis = Math.floor(fromFiat(uiAmount) * unitToSatoshi);
|
||||
} else {
|
||||
satoshis = (uiAmount * unitToSatoshi).toFixed(0);
|
||||
satoshis = Math.floor(uiAmount * unitToSatoshi);
|
||||
}
|
||||
|
||||
var confirmData = {
|
||||
amount: useSendMax ? undefined : satoshis,
|
||||
amount: (useSendMax && canSendMax) ? undefined : satoshis,
|
||||
displayAddress: passthroughParams.displayAddress,
|
||||
fromWalletId: passthroughParams.fromWalletId,
|
||||
sendMax: useSendMax,
|
||||
|
|
@ -467,12 +520,11 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
confirmData.thirdParty = vm.thirdParty;
|
||||
}
|
||||
|
||||
sendFlowService.pushState(confirmData);
|
||||
if (!confirmData.fromWalletId) {
|
||||
$state.transitionTo('tabs.paymentRequest.confirm', confirmData);
|
||||
} else {
|
||||
$state.transitionTo('tabs.send.review', confirmData);
|
||||
$scope.useSendMax = null;
|
||||
sendFlowService.goNext(confirmData);
|
||||
useSendMax = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -590,18 +642,73 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
}
|
||||
|
||||
function updateAvailableFundsStringIfNeeded() {
|
||||
if (passthroughParams.fromWalletId && availableSatoshis !== null) {
|
||||
availableFundsInFiat = '';
|
||||
vm.availableFunds = availableFundsInCrypto;
|
||||
if (passthroughParams.fromWalletId && walletSpendableAmount.satoshis !== null) {
|
||||
vm.availableFunds = walletSpendableAmount.crypto;
|
||||
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
var coin = availableUnits[altUnitIndex].id;
|
||||
txFormatService.formatAlternativeStr(coin, availableSatoshis, function formatCallback(formatted){
|
||||
if (formatted) {
|
||||
availableFundsInFiat = formatted;
|
||||
txFormatService.formatAlternativeStr(coin, walletSpendableAmount.satoshis, function formatCallback(formatted){
|
||||
|
||||
if (formatted) {
|
||||
$scope.$apply(function() {
|
||||
vm.availableFunds = availableFundsInFiat;
|
||||
vm.availableFunds = formatted;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
updateMaximumButtonIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
function updateAvailableFundsFromWallet(wallet) {
|
||||
console.log('amount updateAvailableFundsFromWallet()');
|
||||
var availableFundsInFiat = '';
|
||||
if (wallet.status && wallet.status.isValid) {
|
||||
walletSpendableAmount.crypto = wallet.status.spendableBalanceStr;
|
||||
walletSpendableAmount.satoshis = wallet.status.spendableAmount;
|
||||
if (wallet.status.alternativeBalanceAvailable) {
|
||||
availableFundsInFiat = wallet.status.spendableBalanceAlternative + ' ' + wallet.status.alternativeIsoCode;
|
||||
} else {
|
||||
availableFundsInFiat = '';
|
||||
}
|
||||
|
||||
} else if (wallet.cachedStatus && wallet.cachedStatus.isValid) {
|
||||
|
||||
if (wallet.cachedStatus.alternativeBalanceAvailable) {
|
||||
availableFundsInFiat = wallet.cachedStatus.spendableBalanceAlternative + ' ' + wallet.cachedStatus.alternativeIsoCode;
|
||||
} else {
|
||||
availableFundsInFiat = '';
|
||||
}
|
||||
walletSpendableAmount.crypto = wallet.cachedStatus.spendableBalanceStr;
|
||||
walletSpendableAmount.satoshis = wallet.cachedStatus.spendableAmount;
|
||||
|
||||
} else {
|
||||
|
||||
walletSpendableAmount.crypto = '';
|
||||
walletSpendableAmount.satoshis = null;
|
||||
}
|
||||
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
vm.availableFunds = availableFundsInFiat || walletSpendableAmount.crypto;
|
||||
} else {
|
||||
vm.availableFunds = walletSpendableAmount.crypto;
|
||||
}
|
||||
|
||||
setMaximumButtonFromWallet(wallet);
|
||||
}
|
||||
|
||||
function updateMaximumButtonIfNeeded() {
|
||||
console.log('sendmax updateMaximumButtonIfNeeded()');
|
||||
if (vm.showSendMaxButton || vm.showSendLimitMaxButton) {
|
||||
transactionSendableAmount.fiat = '';
|
||||
vm.sendableFunds = transactionSendableAmount.crypto;
|
||||
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
var coin = availableUnits[altUnitIndex].id;
|
||||
txFormatService.formatAlternativeStr(coin, transactionSendableAmount.satoshis, function formatCallback(formatted){
|
||||
if (formatted) {
|
||||
$scope.$apply(function onApply() {
|
||||
vm.sendableFunds = formatted;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -609,37 +716,59 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory,
|
|||
}
|
||||
}
|
||||
|
||||
function updateAvailableFundsFromWallet(wallet) {
|
||||
if (wallet.status && wallet.status.isValid) {
|
||||
availableFundsInCrypto = wallet.status.spendableBalanceStr;
|
||||
availableSatoshis = wallet.status.spendableAmount;
|
||||
if (wallet.status.alternativeBalanceAvailable) {
|
||||
availableFundsInFiat = wallet.status.spendableBalanceAlternative + ' ' + wallet.status.alternativeIsoCode;
|
||||
function setMaximumButtonFromWallet(wallet) {
|
||||
console.log('sendmax setMaximumButtonFromWallet()');
|
||||
var minSatoshis = vm.minAmount * unitToSatoshi;
|
||||
var maxSatoshis = vm.maxAmount * unitToSatoshi;
|
||||
|
||||
if (minSatoshis > walletSpendableAmount.satoshis) {
|
||||
console.log('sendmax Hiding max buttons as minimum is too high.');
|
||||
canSendMax = false;
|
||||
vm.showSendMaxButton = true;
|
||||
vm.showSendLimitMaxButton = false;
|
||||
transactionSendableAmount.satoshis = walletSpendableAmount.satoshis;
|
||||
|
||||
} else if (maxSatoshis) {
|
||||
if (walletSpendableAmount.satoshis > maxSatoshis) {
|
||||
console.log('sendmax Showing max limit button as available is greater than max limit.');
|
||||
canSendMax = false;
|
||||
vm.showSendMaxButton = false;
|
||||
vm.showSendLimitMaxButton = true;
|
||||
transactionSendableAmount.satoshis = maxSatoshis;
|
||||
} else {
|
||||
availableFundsInFiat = '';
|
||||
console.log('sendmax Showing sendmax as all available as less than max limit.');
|
||||
// Enabling send max here is a little dangerous, if they receive funds between pressing
|
||||
// this and the calculation in the Review screen.
|
||||
canSendMax = false;
|
||||
vm.showSendMaxButton = true;
|
||||
vm.showSendLimitMaxButton = false;
|
||||
transactionSendableAmount.satoshis = walletSpendableAmount.satoshis;
|
||||
}
|
||||
|
||||
} else if (wallet.cachedStatus && wallet.status.isValid) {
|
||||
} else {
|
||||
console.log('sendmax Showing sendmax as all available because no limits.');
|
||||
canSendMax = true;
|
||||
vm.showSendMaxButton = true;
|
||||
vm.showSendLimitMaxButton = false;
|
||||
transactionSendableAmount.satoshis = walletSpendableAmount.satoshis;
|
||||
}
|
||||
|
||||
if (wallet.cachedStatus.alternativeBalanceAvailable) {
|
||||
availableFundsInFiat = wallet.cachedStatus.spendableBalanceAlternative + ' ' + wallet.cachedStatus.alternativeIsoCode;
|
||||
} else {
|
||||
availableFundsInFiat = '';
|
||||
if (vm.showSendMaxButton || vm.showSendLimitMaxButton) {
|
||||
console.log('sendmax Setting max button text');
|
||||
transactionSendableAmount.crypto = txFormatService.formatAmountStr(wallet.coin, transactionSendableAmount.satoshis);
|
||||
vm.sendableFunds = transactionSendableAmount.crypto;
|
||||
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
txFormatService.formatAlternativeStr(wallet.coin, transactionSendableAmount.satoshis, function onFormat(formatted){
|
||||
if (formatted) {
|
||||
$scope.$apply(function onApply() {
|
||||
vm.sendableFunds = formatted;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
availableFundsInCrypto = wallet.cachedStatus.spendableBalanceStr;
|
||||
availableSatoshis = wallet.cachedStatus.spendableAmount;
|
||||
|
||||
} else {
|
||||
|
||||
availableFundsInFiat = '';
|
||||
availableFundsInCrypto = '';
|
||||
availableSatoshis = null;
|
||||
}
|
||||
|
||||
if (availableUnits[unitIndex].isFiat) {
|
||||
vm.availableFunds = availableFundsInFiat || availableFundsInCrypto;
|
||||
} else {
|
||||
vm.availableFunds = availableFundsInCrypto;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,12 +1,20 @@
|
|||
describe('amountController', function(){
|
||||
var configCache,
|
||||
configService,
|
||||
configService,
|
||||
gettextCatalog,
|
||||
$controller,
|
||||
$ionicHistory,
|
||||
$rootScope,
|
||||
ongoingProcess,
|
||||
platformInfo,
|
||||
popupService,
|
||||
profileService,
|
||||
rateService,
|
||||
sendFlowService,
|
||||
shapeshiftService,
|
||||
txFormatService,
|
||||
$scope,
|
||||
$state,
|
||||
$stateParams;
|
||||
|
||||
|
||||
|
|
@ -18,7 +26,7 @@ describe('amountController', function(){
|
|||
configCache = {
|
||||
wallet: {
|
||||
settings: {
|
||||
|
||||
unitToSatoshi: 100000000
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -31,18 +39,42 @@ describe('amountController', function(){
|
|||
});
|
||||
configService.getSync.and.returnValue(configCache);
|
||||
|
||||
gettextCatalog = jasmine.createSpyObj(['getString']);
|
||||
gettextCatalog.getString.and.callFake(function(str){ return str; });
|
||||
$ionicHistory = jasmine.createSpyObj(['backView']);
|
||||
|
||||
ongoingProcess = jasmine.createSpyObj(['set']);
|
||||
|
||||
platformInfo = {
|
||||
isChromeApp: false,
|
||||
isAndroid: false,
|
||||
isIos: true
|
||||
};
|
||||
|
||||
profileService = jasmine.createSpyObj(['getWallets']);
|
||||
popupService = jasmine.createSpyObj(['showAlert']);
|
||||
profileService = jasmine.createSpyObj(['getWallet', 'getWallets']);
|
||||
|
||||
rateService = jasmine.createSpyObj(['fromFiat', 'whenAvailable']);
|
||||
rateService = jasmine.createSpyObj(['fromFiat', 'listAlternatives', 'updateRates', 'whenAvailable']);
|
||||
sendFlowService = jasmine.createSpyObj(['getStateClone', 'pushState']);
|
||||
shapeshiftService = jasmine.createSpyObj(['getMarketData']);
|
||||
txFormatService = jasmine.createSpyObj(['formatAlternativeStr', 'formatAmountStr']);
|
||||
|
||||
txFormatService.formatAlternativeStr.and.callFake(function(coin, satoshis, cb) {
|
||||
if (typeof satoshis !== "number") {
|
||||
throw "satoshis in formatAlternativeStr() is not a number."
|
||||
}
|
||||
var units = satoshis / 100000000;
|
||||
var formatted = (units * 10000).toFixed(2) + ' USD';
|
||||
cb(formatted);
|
||||
});
|
||||
|
||||
txFormatService.formatAmountStr.and.callFake(function(coin, satoshis) {
|
||||
if (typeof satoshis !== "number") {
|
||||
throw "satoshis in formatAmountStr() is not a number."
|
||||
}
|
||||
return (satoshis * 100000000).toFixed(8) + ' ' + (coin || 'bch').toUpperCase();
|
||||
});
|
||||
|
||||
$state = jasmine.createSpyObj(['transitionTo']);
|
||||
$stateParams = {};
|
||||
|
||||
inject(function(_$controller_, _$rootScope_){
|
||||
|
|
@ -61,6 +93,14 @@ describe('amountController', function(){
|
|||
stateName: 'ignoreme'
|
||||
};
|
||||
$ionicHistory.backView.and.returnValue(backView);
|
||||
|
||||
var wallet = {
|
||||
status: {
|
||||
isValid: true,
|
||||
spendableAmount: 123456
|
||||
}
|
||||
};
|
||||
profileService.getWallet.and.returnValue(wallet);
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
rateService.fromFiat.and.returnValue(12); // satoshis or coins?
|
||||
|
||||
|
|
@ -69,33 +109,496 @@ describe('amountController', function(){
|
|||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: {},
|
||||
gettextCatalog: gettextCatalog,
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: {},
|
||||
popupService: popupService,
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: {},
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: {},
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
var data = {
|
||||
stateParams: {
|
||||
fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b',
|
||||
toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'
|
||||
}
|
||||
var sendFlowState = {
|
||||
fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b',
|
||||
toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'
|
||||
};
|
||||
$scope.$emit('$ionicView.beforeEnter', data);
|
||||
|
||||
expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b');
|
||||
expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s');
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
//expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b');
|
||||
//expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s');
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('Shapeshift', function() {
|
||||
var walletFrom;
|
||||
var walletTo;
|
||||
|
||||
beforeEach(function(){
|
||||
walletFrom = {};
|
||||
walletTo = {};
|
||||
|
||||
profileService.getWallet.and.callFake(function(walletId){
|
||||
if (walletId === '4cd7673e-7320-4dfa-86e5-d4edb51d460a') {
|
||||
return walletFrom;
|
||||
} else if (walletId === 'bf00af8f-0788-4b57-b30a-0390747407e9') {
|
||||
return walletTo;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
rateService.listAlternatives.and.returnValue([
|
||||
{name: "Australian Dollar", isoCode: "AUD"},
|
||||
{name: "United States Dollar", isoCode: "USD"}
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
it ('with available balance below limit, shows sendMax for triggering alert', function() {
|
||||
|
||||
walletFrom.coin = 'btc';
|
||||
walletFrom.status = {
|
||||
isValid: true,
|
||||
spendableAmount: 789
|
||||
};
|
||||
walletTo.coin = 'bch';
|
||||
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
rateService.fromFiat.and.returnValue(12);
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: gettextCatalog,
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: popupService,
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: $state,
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
rateService.whenAvailable.and.callFake(function(cb){
|
||||
cb();
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a',
|
||||
sendMax: false,
|
||||
thirdParty: {
|
||||
id: 'shapeshift',
|
||||
data: {},
|
||||
},
|
||||
toAddress: '',
|
||||
toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
var reqCoinIn = '';
|
||||
var reqCoinOut = '';
|
||||
shapeshiftService.getMarketData.and.callFake(function(coinIn, coinOut, cb){
|
||||
reqCoinIn = coinIn;
|
||||
reqCoinOut = coinOut;
|
||||
cb({
|
||||
maxLimit: '0.6846239',
|
||||
minimum: '0.00013692'
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
expect(rateService.updateRates.calls.any()).toEqual(true);
|
||||
|
||||
expect(reqCoinIn).toBe('btc');
|
||||
expect(reqCoinOut).toBe('bch');
|
||||
|
||||
expect(amountController.maxAmount).toBe(0.68462390);
|
||||
expect(amountController.minAmount).toBe(0.00013692);
|
||||
|
||||
expect(amountController.showSendMaxButton).toEqual(true);
|
||||
expect(amountController.showSendLimitMaxButton).toEqual(false);
|
||||
|
||||
expect(amountController.sendableFunds).toEqual('0.08 USD');
|
||||
|
||||
// Now hit the Send Max button
|
||||
amountController.sendMax();
|
||||
|
||||
expect(popupService.showAlert.calls.argsFor(0)[0]).toEqual('Insufficient funds');
|
||||
expect(popupService.showAlert.calls.argsFor(0)[1]).toEqual('Amount below minimum allowed');
|
||||
expect(sendFlowService.pushState.calls.any()).toEqual(false);
|
||||
expect($state.transitionTo.calls.any()).toEqual(false);
|
||||
});
|
||||
|
||||
it ('with available balance between limits, uses sendMax', function() {
|
||||
|
||||
walletFrom.coin = 'btc';
|
||||
walletFrom.status = {
|
||||
isValid: true,
|
||||
spendableAmount: 456789
|
||||
};
|
||||
walletTo.coin = 'bch';
|
||||
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
rateService.fromFiat.and.returnValue(12);
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: {},
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: {},
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: $state,
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
rateService.whenAvailable.and.callFake(function(cb){
|
||||
cb();
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a',
|
||||
sendMax: false,
|
||||
thirdParty: {
|
||||
id: 'shapeshift',
|
||||
data: {},
|
||||
},
|
||||
toAddress: '',
|
||||
toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
var reqCoinIn = '';
|
||||
var reqCoinOut = '';
|
||||
shapeshiftService.getMarketData.and.callFake(function(coinIn, coinOut, cb){
|
||||
reqCoinIn = coinIn;
|
||||
reqCoinOut = coinOut;
|
||||
cb({
|
||||
maxLimit: '0.6846239',
|
||||
minimum: '0.00013692'
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
expect(rateService.updateRates.calls.any()).toEqual(true);
|
||||
|
||||
expect(reqCoinIn).toBe('btc');
|
||||
expect(reqCoinOut).toBe('bch');
|
||||
|
||||
expect(amountController.maxAmount).toBe(0.68462390);
|
||||
expect(amountController.minAmount).toBe(0.00013692);
|
||||
|
||||
expect(amountController.showSendMaxButton).toEqual(true);
|
||||
expect(amountController.showSendLimitMaxButton).toEqual(false);
|
||||
|
||||
// Now hit the Send Max button
|
||||
var pushedState = null;
|
||||
sendFlowService.pushState.and.callFake(function (sendFlowState){
|
||||
pushedState = sendFlowState;
|
||||
});
|
||||
|
||||
amountController.sendMax();
|
||||
|
||||
expect(pushedState.amount).toBeUndefined();
|
||||
expect(pushedState.fromWalletId).toEqual('4cd7673e-7320-4dfa-86e5-d4edb51d460a');
|
||||
expect(pushedState.sendMax).toEqual(true);
|
||||
expect(pushedState.toWalletId).toEqual('bf00af8f-0788-4b57-b30a-0390747407e9');
|
||||
|
||||
expect(pushedState.thirdParty.id).toEqual('shapeshift');
|
||||
expect(pushedState.thirdParty.data.maxAmount).toEqual(0.6846239);
|
||||
expect(pushedState.thirdParty.data.minAmount).toEqual(0.00013692);
|
||||
|
||||
expect($state.transitionTo.calls.count()).toEqual(1);
|
||||
expect($state.transitionTo.calls.argsFor(0)[0]).toEqual('tabs.send.review');
|
||||
});
|
||||
|
||||
it ('with available balance higher than max, uses send limit max instead of sendMax', function() {
|
||||
|
||||
walletFrom.coin = 'btc';
|
||||
walletFrom.status = {
|
||||
isValid: true,
|
||||
spendableAmount: 123456789
|
||||
};
|
||||
walletTo.coin = 'bch';
|
||||
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
rateService.fromFiat.and.returnValue(12); // satoshis or coins?
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: {},
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: {},
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: $state,
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
rateService.whenAvailable.and.callFake(function(cb){
|
||||
cb();
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a',
|
||||
sendMax: false,
|
||||
thirdParty: {
|
||||
id: 'shapeshift',
|
||||
data: {},
|
||||
},
|
||||
toAddress: '',
|
||||
toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
var reqCoinIn = '';
|
||||
var reqCoinOut = '';
|
||||
shapeshiftService.getMarketData.and.callFake(function(coinIn, coinOut, cb){
|
||||
reqCoinIn = coinIn;
|
||||
reqCoinOut = coinOut;
|
||||
cb({
|
||||
maxLimit: '0.6846239',
|
||||
minimum: '0.00013692'
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
expect(rateService.updateRates.calls.any()).toEqual(true);
|
||||
|
||||
expect(reqCoinIn).toBe('btc');
|
||||
expect(reqCoinOut).toBe('bch');
|
||||
|
||||
expect(amountController.maxAmount).toBe(0.6846239);
|
||||
expect(amountController.minAmount).toBe(0.00013692);
|
||||
|
||||
expect(amountController.showSendMaxButton).toEqual(false);
|
||||
expect(amountController.showSendLimitMaxButton).toEqual(true);
|
||||
|
||||
// Now hit the Send Max button
|
||||
var pushedState = null;
|
||||
sendFlowService.pushState.and.callFake(function (sendFlowState){
|
||||
pushedState = sendFlowState;
|
||||
});
|
||||
|
||||
amountController.sendMax();
|
||||
|
||||
expect(pushedState.amount).toEqual(68462390);
|
||||
expect(pushedState.fromWalletId).toEqual('4cd7673e-7320-4dfa-86e5-d4edb51d460a');
|
||||
expect(pushedState.sendMax).toEqual(false);
|
||||
expect(pushedState.toWalletId).toEqual('bf00af8f-0788-4b57-b30a-0390747407e9');
|
||||
|
||||
expect(pushedState.thirdParty.id).toEqual('shapeshift');
|
||||
expect(pushedState.thirdParty.data.maxAmount).toEqual(0.6846239);
|
||||
expect(pushedState.thirdParty.data.minAmount).toEqual(0.00013692);
|
||||
|
||||
expect($state.transitionTo.calls.count()).toEqual(1);
|
||||
expect($state.transitionTo.calls.argsFor(0)[0]).toEqual('tabs.send.review');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Wallet transfer', function() {
|
||||
var walletFrom;
|
||||
var walletTo;
|
||||
|
||||
beforeEach(function(){
|
||||
walletFrom = {};
|
||||
walletTo = {};
|
||||
|
||||
profileService.getWallet.and.callFake(function(walletId){
|
||||
if (walletId === '4cd7673e-7320-4dfa-86e5-d4edb51d460a') {
|
||||
return walletFrom;
|
||||
} else if (walletId === 'bf00af8f-0788-4b57-b30a-0390747407e9') {
|
||||
return walletTo;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
rateService.listAlternatives.and.returnValue([
|
||||
{name: "Australian Dollar", isoCode: "AUD"},
|
||||
{name: "United States Dollar", isoCode: "USD"}
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
it('wallet transfer send max.', function() {
|
||||
|
||||
walletFrom.coin = 'btc';
|
||||
walletFrom.status = {
|
||||
isValid: true,
|
||||
spendableAmount: 123456789
|
||||
};
|
||||
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: gettextCatalog,
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: popupService,
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: $state,
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a',
|
||||
toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
expect(amountController.showSendMaxButton).toEqual(true);
|
||||
expect(amountController.showSendLimitMaxButton).toEqual(false);
|
||||
|
||||
expect(amountController.sendableFunds).toEqual('12345.68 USD');
|
||||
|
||||
// Now hit the Send Max button
|
||||
var pushedState = null;
|
||||
sendFlowService.pushState.and.callFake(function (sendFlowState){
|
||||
pushedState = sendFlowState;
|
||||
});
|
||||
|
||||
amountController.sendMax();
|
||||
|
||||
expect(pushedState.amount).toBeUndefined();
|
||||
expect(pushedState.fromWalletId).toEqual('4cd7673e-7320-4dfa-86e5-d4edb51d460a');
|
||||
expect(pushedState.sendMax).toEqual(true);
|
||||
expect(pushedState.toWalletId).toEqual('bf00af8f-0788-4b57-b30a-0390747407e9');
|
||||
|
||||
expect($state.transitionTo.calls.count()).toEqual(1);
|
||||
expect($state.transitionTo.calls.argsFor(0)[0]).toEqual('tabs.send.review');
|
||||
});
|
||||
|
||||
|
||||
// This situation was seen in real life
|
||||
it('wallet transfer with valid cached status only.', function() {
|
||||
|
||||
walletFrom.coin = 'btc';
|
||||
walletFrom.status = {
|
||||
isValid: false,
|
||||
};
|
||||
walletFrom.cachedStatus = {
|
||||
isValid: true,
|
||||
spendableAmount: 5678
|
||||
};
|
||||
|
||||
profileService.getWallets.and.returnValue([{}]);
|
||||
|
||||
var $scope = $rootScope.$new();
|
||||
|
||||
var amountController = $controller('amountController', {
|
||||
configService: configService,
|
||||
gettextCatalog: gettextCatalog,
|
||||
$ionicHistory: $ionicHistory,
|
||||
$ionicModal: {},
|
||||
$ionicScrollDelegate: {},
|
||||
nodeWebkitService: {},
|
||||
ongoingProcess: ongoingProcess,
|
||||
platformInfo: platformInfo,
|
||||
profileService: profileService,
|
||||
popupService: popupService,
|
||||
rateService: rateService,
|
||||
$scope: $scope,
|
||||
sendFlowService: sendFlowService,
|
||||
shapeshiftService: shapeshiftService,
|
||||
$state: $state,
|
||||
$stateParams: $stateParams,
|
||||
txFormatService: txFormatService,
|
||||
walletService: {}
|
||||
});
|
||||
|
||||
var sendFlowState = {
|
||||
fromWalletId: '4cd7673e-7320-4dfa-86e5-d4edb51d460a',
|
||||
toWalletId: 'bf00af8f-0788-4b57-b30a-0390747407e9'
|
||||
};
|
||||
|
||||
sendFlowService.getStateClone.and.returnValue(sendFlowState);
|
||||
|
||||
$scope.$emit('$ionicView.beforeEnter', {});
|
||||
|
||||
expect(amountController.showSendMaxButton).toEqual(true);
|
||||
expect(amountController.showSendLimitMaxButton).toEqual(false);
|
||||
|
||||
expect(amountController.sendableFunds).toEqual('0.57 USD');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
angular.module('copayApp.controllers').controller('tourController',
|
||||
function($scope, $state, $log, $timeout, $filter, ongoingProcess, profileService, rateService, popupService, gettextCatalog, startupService, storageService, walletService, $q) {
|
||||
function ($scope, $state, $log, $timeout, $filter, ongoingProcess, configService, profileService, rateService, popupService, gettextCatalog, lodash, startupService, storageService, uxLanguage, walletService, $q) {
|
||||
|
||||
$scope.data = {
|
||||
index: 0
|
||||
|
|
@ -46,62 +46,90 @@ angular.module('copayApp.controllers').controller('tourController',
|
|||
creatingWallet = true;
|
||||
ongoingProcess.set('creatingWallet', true);
|
||||
$timeout(function() {
|
||||
profileService.createDefaultWallet(function(err, walletClients) {
|
||||
if (err) {
|
||||
$log.warn(err);
|
||||
uxLanguage.init(function(lang) {
|
||||
var rateCode = uxLanguage.getRateCode(lang);
|
||||
console.log("When Available: rateService");
|
||||
rateService.whenAvailable(function() {
|
||||
var alternatives = rateService.listAlternatives(true);
|
||||
|
||||
return $timeout(function() {
|
||||
$log.warn('Retrying to create default wallet.....:' + ++retryCount);
|
||||
if (retryCount > 3) {
|
||||
ongoingProcess.set('creatingWallet', false);
|
||||
popupService.showAlert(
|
||||
gettextCatalog.getString('Cannot Create Wallet'), err,
|
||||
function() {
|
||||
retryCount = 0;
|
||||
return $scope.createDefaultWallet();
|
||||
}, gettextCatalog.getString('Retry'));
|
||||
} else {
|
||||
return $scope.createDefaultWallet();
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
ongoingProcess.set('creatingWallet', false);
|
||||
var bchWallet = walletClients[0];
|
||||
var btcWallet = walletClients[1];
|
||||
var bchWalletId = bchWallet.credentials.walletId;
|
||||
var btcWalletId = btcWallet.credentials.walletId;
|
||||
|
||||
function createAddressPromise(wallet) {
|
||||
return $q(function(resolve, reject) {
|
||||
walletService.getAddress(wallet, true, function(e, addr) {
|
||||
if (e) reject(e);
|
||||
resolve(addr);
|
||||
var newAltCurrency = lodash.find(alternatives, {
|
||||
'isoCode': rateCode
|
||||
});
|
||||
|
||||
configService.whenAvailable(function(config) {
|
||||
var opts = {
|
||||
wallet: {
|
||||
settings: {
|
||||
alternativeName: newAltCurrency.name,
|
||||
alternativeIsoCode: newAltCurrency.isoCode,
|
||||
}
|
||||
}
|
||||
};
|
||||
configService.set(opts, function(err) {
|
||||
if (err) $log.warn(err);
|
||||
|
||||
profileService.createDefaultWallet(function(err, walletClients) {
|
||||
if (err) {
|
||||
$log.warn(err);
|
||||
|
||||
return $timeout(function() {
|
||||
$log.warn('Retrying to create default wallet.....:' + ++retryCount);
|
||||
if (retryCount > 3) {
|
||||
ongoingProcess.set('creatingWallet', false);
|
||||
popupService.showAlert(
|
||||
gettextCatalog.getString('Cannot Create Wallet'), err,
|
||||
function() {
|
||||
retryCount = 0;
|
||||
return $scope.createDefaultWallet();
|
||||
}, gettextCatalog.getString('Retry'));
|
||||
} else {
|
||||
return $scope.createDefaultWallet();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
;
|
||||
|
||||
ongoingProcess.set('creatingWallet', false);
|
||||
var bchWallet = walletClients[0];
|
||||
var btcWallet = walletClients[1];
|
||||
var bchWalletId = bchWallet.credentials.walletId;
|
||||
var btcWalletId = btcWallet.credentials.walletId;
|
||||
|
||||
function createAddressPromise(wallet) {
|
||||
return $q(function (resolve, reject) {
|
||||
walletService.getAddress(wallet, true, function (e, addr) {
|
||||
if (e) reject(e);
|
||||
resolve(addr);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function goToCollectEmail() {
|
||||
$state.go('onboarding.collectEmail', {
|
||||
bchWalletId: bchWalletId,
|
||||
btcWalletId: btcWalletId
|
||||
});
|
||||
}
|
||||
|
||||
var bchAddressPromise = createAddressPromise(bchWallet);
|
||||
var btcAddressPromise = createAddressPromise(btcWallet);
|
||||
ongoingProcess.set('generatingNewAddress', true);
|
||||
|
||||
$q.all([bchAddressPromise, btcAddressPromise]).then(function (addresses) {
|
||||
ongoingProcess.set('generatingNewAddress', false);
|
||||
$state.go('tabs.home');
|
||||
}, function (e) {
|
||||
ongoingProcess.set('generatingNewAddress', false);
|
||||
$log.warn(e);
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), e);
|
||||
$state.go('tabs.home');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
$log.debug('Setting default currency : ' + newAltCurrency);
|
||||
});
|
||||
}
|
||||
|
||||
function goToCollectEmail() {
|
||||
$state.go('onboarding.collectEmail', {
|
||||
bchWalletId: bchWalletId,
|
||||
btcWalletId: btcWalletId
|
||||
});
|
||||
}
|
||||
|
||||
var bchAddressPromise = createAddressPromise(bchWallet);
|
||||
var btcAddressPromise = createAddressPromise(btcWallet);
|
||||
ongoingProcess.set('generatingNewAddress', true);
|
||||
|
||||
$q.all([bchAddressPromise, btcAddressPromise]).then(function(addresses) {
|
||||
ongoingProcess.set('generatingNewAddress', false);
|
||||
$state.go('tabs.home');
|
||||
}, function(e) {
|
||||
ongoingProcess.set('generatingNewAddress', false);
|
||||
$log.warn(e);
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), e);
|
||||
$state.go('tabs.home');
|
||||
});
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
});
|
||||
})
|
||||
}, 300);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -6,27 +6,11 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi
|
|||
|
||||
$scope.showMyAddress = showMyAddress;
|
||||
|
||||
function generateAddress(wallet, cb) {
|
||||
if (!wallet) return;
|
||||
walletService.getAddress(wallet, false, function(err, addr) {
|
||||
if (err) {
|
||||
popupService.showAlert(err);
|
||||
}
|
||||
return cb(addr);
|
||||
});
|
||||
}
|
||||
|
||||
function showToWallets() {
|
||||
$scope.toWallets = $scope.fromWallet.coin === 'btc' ? walletsBch : walletsBtc;
|
||||
$scope.onToWalletSelect($scope.toWallets[0]);
|
||||
$scope.singleToWallet = $scope.toWallets.length === 1;
|
||||
}
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
||||
walletsBtc = profileService.getWallets({coin: 'btc'});
|
||||
walletsBch = profileService.getWallets({coin: 'bch'});
|
||||
$scope.fromWallets = lodash.filter(walletsBtc.concat(walletsBch), function(w) {
|
||||
return w.status.balance.availableAmount > 0;
|
||||
return (w.status && w.status.balance && w.status.balance.availableAmount > 0);
|
||||
});
|
||||
|
||||
$scope.singleFromWallet = $scope.fromWallets.length === 1;
|
||||
|
|
@ -62,18 +46,7 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi
|
|||
id: 'shapeshift'
|
||||
}
|
||||
};
|
||||
|
||||
// Starting new send flow, so ensure everything is reset
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.home').then(function() {
|
||||
$ionicHistory.clearHistory();
|
||||
$state.go('tabs.send').then(function() {
|
||||
$timeout(function () {
|
||||
sendFlowService.pushState(stateParams);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}, 60);
|
||||
});
|
||||
});
|
||||
sendFlowService.start(stateParams);
|
||||
}
|
||||
|
||||
function showMyAddress() {
|
||||
|
|
|
|||
|
|
@ -43,21 +43,20 @@ angular.module('copayApp.controllers').controller('tabHomeController',
|
|||
});
|
||||
}
|
||||
|
||||
if ($scope.isNW) {
|
||||
latestReleaseService.checkLatestRelease(function(err, newRelease) {
|
||||
if (err) {
|
||||
$log.warn(err);
|
||||
return;
|
||||
}
|
||||
if (newRelease) {
|
||||
$scope.newRelease = true;
|
||||
$scope.updateText = gettextCatalog.getString('There is a new version of {{appName}} available', {
|
||||
appName: $scope.name
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
latestReleaseService.checkLatestRelease(function(err, newReleaseData) {
|
||||
if (err) {
|
||||
$log.warn(err);
|
||||
return;
|
||||
}
|
||||
if (newReleaseData) {
|
||||
$scope.newRelease = true;
|
||||
$scope.newReleaseText = gettextCatalog.getString('There is a new version of {{appName}} available', {
|
||||
appName: $scope.name
|
||||
});
|
||||
$scope.newReleaseNotes = newReleaseData.releaseNotes;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onEnter(event, data) {
|
||||
$ionicNavBarDelegate.showBar(true);
|
||||
|
|
@ -109,31 +108,24 @@ angular.module('copayApp.controllers').controller('tabHomeController',
|
|||
$scope.$apply();
|
||||
}, 10);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function onLeave (event, data) {
|
||||
lodash.each(listeners, function(x) {
|
||||
x();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
$scope.createdWithinPastDay = function(time) {
|
||||
return timeService.withinPastDay(time);
|
||||
};
|
||||
|
||||
$scope.startFreshSend = function() {
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.send');
|
||||
sendFlowService.start();
|
||||
}
|
||||
|
||||
$scope.openExternalLink = function() {
|
||||
var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest';
|
||||
var optIn = true;
|
||||
var title = gettextCatalog.getString('Update Available');
|
||||
var message = gettextCatalog.getString('An update to this app is available. For your security, please update to the latest version.');
|
||||
var okText = gettextCatalog.getString('View Update');
|
||||
var cancelText = gettextCatalog.getString('Go Back');
|
||||
externalLinkService.open(url, optIn, title, message, okText, cancelText);
|
||||
$scope.showUpdatePopup = function() {
|
||||
latestReleaseService.showUpdatePopup();
|
||||
};
|
||||
|
||||
$scope.openBannerUrl = function() {
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
angular.module('copayApp.controllers').controller('tabReceiveController', function($rootScope, $scope, $timeout, $log, $ionicModal, $state, $ionicHistory, $ionicPopover, storageService, platformInfo, walletService, profileService, configService, lodash, gettextCatalog, popupService, bwcError, bitcoinCashJsService, $ionicNavBarDelegate, sendFlowService, txFormatService, soundService, clipboardService) {
|
||||
|
||||
var CLOSE_NORMAL = 1000;
|
||||
var listeners = [];
|
||||
$scope.bchAddressType = { type: 'cashaddr' };
|
||||
var bchAddresses = {};
|
||||
|
|
@ -10,20 +11,63 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
|
|||
$scope.isCordova = platformInfo.isCordova;
|
||||
$scope.isNW = platformInfo.isNW;
|
||||
|
||||
var currentAddressSocket = {};
|
||||
var paymentSubscriptionObj = { op:"addr_sub" }
|
||||
|
||||
var config;
|
||||
var currentAddressSocket = null;
|
||||
var paymentSubscriptionObj = { op:'addr_sub' };
|
||||
|
||||
$scope.displayBalanceAsFiat = true;
|
||||
$scope.$on('$ionicView.beforeLeave', onBeforeLeave);
|
||||
|
||||
$scope.requestSpecificAmount = function() {
|
||||
sendFlowService.pushState({
|
||||
toWalletId: $scope.wallet.credentials.walletId
|
||||
sendFlowService.start({
|
||||
toWalletId: $scope.wallet.credentials.walletId,
|
||||
isRequestAmount: true
|
||||
});
|
||||
$state.go('tabs.paymentRequest.amount');
|
||||
};
|
||||
|
||||
|
||||
function connectSocket() {
|
||||
// Close existing socket if not connected with current address
|
||||
if (currentAddressSocket) {
|
||||
currentAddressSocket.close([CLOSE_NORMAL]);
|
||||
}
|
||||
|
||||
if ($scope.wallet.coin === 'bch') {
|
||||
// listen to bch address
|
||||
currentAddressSocket = new WebSocket('wss://ws.blockchain.info/bch/inv');
|
||||
paymentSubscriptionObj.addr = $scope.addrBchLegacy;
|
||||
} else {
|
||||
// listen to btc address
|
||||
currentAddressSocket = new WebSocket('wss://ws.blockchain.info/inv');
|
||||
paymentSubscriptionObj.addr = $scope.addr;
|
||||
}
|
||||
|
||||
// create subscription to address
|
||||
var msg = JSON.stringify(paymentSubscriptionObj);
|
||||
currentAddressSocket.onopen = function (event) {
|
||||
currentAddressSocket.send(msg);
|
||||
};
|
||||
|
||||
// listen for response
|
||||
currentAddressSocket.onmessage = function (event) {
|
||||
//console.log("message received:" + event.data);
|
||||
receivedPayment(event.data);
|
||||
};
|
||||
|
||||
currentAddressSocket.onclose = function(event) {
|
||||
if (event.code !== CLOSE_NORMAL) {
|
||||
$log.debug('Socket was closed abnormally. Reconnect will be attempted in 1 second.');
|
||||
$timeout(function() {
|
||||
connectSocket();
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
currentAddressSocket.onerror = function(err) {
|
||||
console.error('Socket encountered error: ', err, 'Closing socket');
|
||||
currentAddressSocket.close();
|
||||
};
|
||||
}
|
||||
|
||||
$scope.setAddress = function(newAddr, copyAddress) {
|
||||
$scope.addr = null;
|
||||
if (!$scope.wallet || $scope.generatingAddress || !$scope.wallet.isComplete()) return;
|
||||
|
|
@ -36,49 +80,24 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
|
|||
popupService.showAlert(err);
|
||||
}
|
||||
|
||||
//close existing socket
|
||||
if (currentAddressSocket.close === 'function') {
|
||||
currentAddressSocket.close();
|
||||
}
|
||||
|
||||
if ($scope.wallet.coin == 'bch') {
|
||||
bchAddresses = bitcoinCashJsService.translateAddresses(addr);
|
||||
$scope.addr = bchAddresses[$scope.bchAddressType.type];
|
||||
$scope.addrBchLegacy = bchAddresses['legacy'];
|
||||
|
||||
// listen to bch address
|
||||
currentAddressSocket = new WebSocket("wss://ws.blockchain.info/bch/inv");
|
||||
paymentSubscriptionObj.addr = bchAddresses['legacy'];
|
||||
|
||||
if ($scope.wallet.coin === 'bch') {
|
||||
bchAddresses = bitcoinCashJsService.translateAddresses(addr);
|
||||
$scope.addr = bchAddresses[$scope.bchAddressType.type];
|
||||
$scope.addrBchLegacy = bchAddresses['legacy'];
|
||||
} else {
|
||||
$scope.addr = addr;
|
||||
|
||||
// listen to btc address
|
||||
currentAddressSocket = new WebSocket("wss://ws.blockchain.info/inv");
|
||||
paymentSubscriptionObj.addr = $scope.addr
|
||||
$scope.addr = addr;
|
||||
}
|
||||
|
||||
connectSocket();
|
||||
|
||||
if (copyAddress === true) {
|
||||
try {
|
||||
clipboardService.copyToClipboard($scope.wallet.coin == 'bch' && $scope.bchAddressType.type == 'cashaddr' ? 'bitcoincash:' + $scope.addr : $scope.addr);
|
||||
} catch (error) {
|
||||
$log.debug("Error copying to clipboard:");
|
||||
$log.debug('Error copying to clipboard:');
|
||||
$log.debug(error);
|
||||
}
|
||||
}
|
||||
// create subscription
|
||||
var msg = JSON.stringify(paymentSubscriptionObj);
|
||||
currentAddressSocket.onopen = function (event) {
|
||||
//console.log("message sent: " + msg);
|
||||
currentAddressSocket.send(msg);
|
||||
}
|
||||
|
||||
|
||||
// listen for response
|
||||
currentAddressSocket.onmessage = function (event) {
|
||||
//console.log("message received:" + event.data);
|
||||
receivedPayment(event.data);
|
||||
}
|
||||
|
||||
$timeout(function() {
|
||||
$scope.$apply();
|
||||
|
|
@ -164,7 +183,6 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
|
|||
// Notify new tx
|
||||
$scope.$emit('bwsEvent', $scope.wallet.id);
|
||||
|
||||
|
||||
$scope.$apply(function () {
|
||||
$scope.showingPaymentReceived = true;
|
||||
});
|
||||
|
|
@ -233,6 +251,10 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
|
|||
}
|
||||
};
|
||||
|
||||
function onBeforeLeave() {
|
||||
currentAddressSocket.close([CLOSE_NORMAL]);
|
||||
}
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
||||
$scope.wallets = profileService.getWallets();
|
||||
$scope.singleWallet = $scope.wallets.length == 1;
|
||||
|
|
@ -258,7 +280,6 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
|
|||
|
||||
configService.whenAvailable(function(_config) {
|
||||
$scope.displayBalanceAsFiat = _config.wallet.settings.priceDisplay === 'fiat';
|
||||
config = _config;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabScanController', function($scope, $log, $timeout, scannerService, incomingData, $state, $ionicHistory, $rootScope, $ionicNavBarDelegate) {
|
||||
angular.module('copayApp.controllers').controller('tabScanController', function(gettextCatalog, popupService, $scope, $log, $timeout, scannerService, incomingDataService, $state, $ionicHistory, $rootScope, $ionicNavBarDelegate) {
|
||||
|
||||
var scannerStates = {
|
||||
unauthorized: 'unauthorized',
|
||||
|
|
@ -111,7 +111,18 @@ angular.module('copayApp.controllers').controller('tabScanController', function(
|
|||
// Sometimes (testing in Chrome, when reading QR Code) data is an object
|
||||
// that has a string data.result.
|
||||
contents = contents.result || contents;
|
||||
incomingData.redir(contents);
|
||||
incomingDataService.redir(contents, function onError(err) {
|
||||
if (err) {
|
||||
var title = gettextCatalog.getString('Scan Failed');
|
||||
popupService.showAlert(title, err.message, function onAlertShown() {
|
||||
// Enable another scan since we won't receive incomingDataMenu.menuHidden
|
||||
activate();
|
||||
});
|
||||
} else {
|
||||
scannerService.resumePreview();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$rootScope.$on('incomingDataMenu.menuHidden', function() {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, $ionicLoading, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, sendFlowService, bwcError, gettextCatalog, scannerService, configService, bitcoinCashJsService, $ionicPopup, $ionicNavBarDelegate, clipboardService) {
|
||||
angular.module('copayApp.controllers').controller('tabSendController', function(bitcoinUriService, $scope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, platformInfo, sendFlowService, gettextCatalog, configService, $ionicPopup, $ionicNavBarDelegate, clipboardService, incomingDataService) {
|
||||
var clipboardHasAddress = false;
|
||||
var clipboardHasContent = false;
|
||||
var originalList;
|
||||
|
|
@ -29,7 +29,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
|
||||
$scope.$on("$ionicView.enter", function(event, data) {
|
||||
|
||||
var stateParams = sendFlowService.getStateClone();
|
||||
var stateParams = sendFlowService.state.getClone();
|
||||
$scope.fromWallet = profileService.getWallet(stateParams.fromWalletId);
|
||||
|
||||
clipboardService.readFromClipboard(function(text) {
|
||||
|
|
@ -39,7 +39,9 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
|
||||
$scope.clipboardHasAddress = false;
|
||||
$scope.clipboardHasContent = false;
|
||||
if ((text.indexOf('bitcoincash:') === 0 || text[0] === 'C' || text[0] === 'H' || text[0] === 'p' || text[0] === 'q') && text.replace('bitcoincash:', '').length === 42) { // CashAddr
|
||||
var parsed = bitcoinUriService.parse(text);
|
||||
console.log('parsed', parsed);
|
||||
if (parsed.isValid && parsed.publicAddress && parsed.coin === 'bch' && !parsed.testnet) { // CashAddr
|
||||
$scope.clipboardHasAddress = true;
|
||||
} else if ((text[0] === "1" || text[0] === "3" || text.substring(0, 3) === "bc1") && text.length >= 26 && text.length <= 35) { // Legacy Addresses
|
||||
$scope.clipboardHasAddress = true;
|
||||
|
|
@ -60,11 +62,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
});
|
||||
|
||||
$scope.findContact = function(search) {
|
||||
|
||||
if (incomingData.redir(search)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!search || search.length < 1) {
|
||||
$scope.list = originalList;
|
||||
$timeout(function() {
|
||||
|
|
@ -73,12 +70,16 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
return;
|
||||
}
|
||||
|
||||
var result = lodash.filter(originalList, function(item) {
|
||||
var val = item.name;
|
||||
return lodash.startsWith(val.toLowerCase(), search.toLowerCase());
|
||||
var params = sendFlowService.state.getClone();
|
||||
params.data = search;
|
||||
sendFlowService.start(params, function onError() {
|
||||
var result = lodash.filter(originalList, function(item) {
|
||||
var val = item.name;
|
||||
return lodash.startsWith(val.toLowerCase(), search.toLowerCase());
|
||||
});
|
||||
|
||||
$scope.list = result;
|
||||
});
|
||||
|
||||
$scope.list = result;
|
||||
};
|
||||
|
||||
var hasWallets = function() {
|
||||
|
|
@ -184,27 +185,18 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
|
||||
$log.debug('Got toAddress:' + toAddress + ' | ' + item.name);
|
||||
|
||||
var stateParams = sendFlowService.getStateClone();
|
||||
stateParams.toAddress = toAddress,
|
||||
var stateParams = sendFlowService.state.getClone();
|
||||
stateParams.toAddress = toAddress;
|
||||
stateParams.coin = item.coin;
|
||||
sendFlowService.pushState(stateParams);
|
||||
|
||||
if (!stateParams.fromWalletId) { // If we have no toAddress or fromWallet
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
} else {
|
||||
$state.transitionTo('tabs.send.amount');
|
||||
}
|
||||
|
||||
sendFlowService.start(stateParams);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.startWalletToWalletTransfer = function() {
|
||||
console.log('startWalletToWalletTransfer()');
|
||||
var params = sendFlowService.getStateClone();
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.wallet-to-wallet', {
|
||||
fromWalletId: sendFlowService.fromWalletId
|
||||
});
|
||||
var params = sendFlowService.state.getClone();
|
||||
params.isWalletTransfer = true;
|
||||
sendFlowService.start(params);
|
||||
}
|
||||
|
||||
// This could probably be enhanced refactoring the routes abstract states
|
||||
|
|
@ -238,7 +230,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
|||
});
|
||||
|
||||
if (data.direction == "back") {
|
||||
sendFlowService.clear();
|
||||
sendFlowService.state.clear();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingData, lodash, popupService, gettextCatalog, scannerService, sendFlowService) {
|
||||
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingDataService, lodash, popupService, gettextCatalog, scannerService, sendFlowService) {
|
||||
|
||||
$scope.onScan = function(data) {
|
||||
if (!incomingData.redir(data)) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid data'));
|
||||
}
|
||||
incomingDataService.redir(data, function onError(err) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setScanFn = function(scanFn) {
|
||||
|
|
@ -16,8 +18,7 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
|
|||
};
|
||||
|
||||
$scope.startFreshSend = function() {
|
||||
sendFlowService.clear();
|
||||
$state.go('tabs.send');
|
||||
sendFlowService.start();
|
||||
};
|
||||
|
||||
$scope.importInit = function() {
|
||||
|
|
@ -28,7 +29,6 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
|
|||
};
|
||||
|
||||
$scope.chooseScanner = function() {
|
||||
sendFlowService.clear();
|
||||
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
||||
|
||||
if (!isWindowsPhoneApp) {
|
||||
|
|
@ -38,10 +38,14 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
|
|||
|
||||
scannerService.useOldScanner(function(err, contents) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
return;
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
} else {
|
||||
incomingDataService.redir(contents, function onError(err) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
incomingData.redir(contents);
|
||||
});
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,52 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService) {
|
||||
|
||||
var HISTORY_SHOW_LIMIT = 10;
|
||||
var currentTxHistoryPage = 0;
|
||||
angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService, walletHistoryService) {
|
||||
// Desktop can display 13 rows of transactions, bump it up to a nice round 15.
|
||||
var DISPLAY_PAGE_SIZE = 15;
|
||||
var currentTxHistoryDisplayPage = 0;
|
||||
var completeTxHistory = []
|
||||
var listeners = [];
|
||||
$scope.txps = [];
|
||||
$scope.completeTxHistory = [];
|
||||
$scope.openTxpModal = txpModalService.open;
|
||||
|
||||
// For gradual migration for doing it properly
|
||||
$scope.vm = {
|
||||
allowInfiniteScroll: false,
|
||||
gettingCachedHistory: true,
|
||||
gettingInitialHistory: true,
|
||||
updatingTxHistory: false,
|
||||
fetchedAllTxHistory: false,
|
||||
//updateTxHistoryError: false
|
||||
updateTxHistoryFailed: false
|
||||
};
|
||||
|
||||
// Need flag for when to allow infinite scroll at bottom
|
||||
// - ie not when loading initial data and there is no more cached data
|
||||
|
||||
$scope.amountIsCollapsible = false;
|
||||
$scope.color = '#888888';
|
||||
|
||||
$scope.filteredTxHistory = [];
|
||||
$scope.isCordova = platformInfo.isCordova;
|
||||
$scope.isAndroid = platformInfo.isAndroid;
|
||||
$scope.isIOS = platformInfo.isIOS;
|
||||
$scope.isSearching = false;
|
||||
$scope.openTxpModal = txpModalService.open;
|
||||
$scope.requiresMultipleSignatures = false;
|
||||
$scope.showBalanceButton = false;
|
||||
$scope.status = null;
|
||||
// Displaying 50 transactions when entering the screen takes a while, so only display a subset
|
||||
// of everything we have, not the complete history.
|
||||
$scope.txHistory = []; // This is what is displayed
|
||||
$scope.txHistorySearchResults = [];
|
||||
$scope.txps = [];
|
||||
$scope.updatingStatus = false;
|
||||
$scope.updateStatusError = null;
|
||||
$scope.updatingTxHistoryProgress = 0;
|
||||
$scope.wallet = null;
|
||||
$scope.walletId = '';
|
||||
$scope.walletNotRegistered = false;
|
||||
|
||||
|
||||
|
||||
|
||||
var channel = "ga";
|
||||
if (platformInfo.isCordova) {
|
||||
|
|
@ -19,34 +55,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
var log = new window.BitAnalytics.LogEvent("wallet_details_open", [], [channel]);
|
||||
window.BitAnalytics.LogEventHandlers.postEvent(log);
|
||||
|
||||
$scope.amountIsCollapsible = !$scope.isAndroid;
|
||||
|
||||
$scope.openExternalLink = function(url, target) {
|
||||
externalLinkService.open(url, target);
|
||||
};
|
||||
|
||||
var setPendingTxps = function(txps) {
|
||||
|
||||
/* Uncomment to test multiple outputs */
|
||||
|
||||
// var txp = {
|
||||
// message: 'test multi-output',
|
||||
// fee: 1000,
|
||||
// createdOn: new Date() / 1000,
|
||||
// outputs: [],
|
||||
// wallet: $scope.wallet
|
||||
// };
|
||||
//
|
||||
// function addOutput(n) {
|
||||
// txp.outputs.push({
|
||||
// amount: 600,
|
||||
// toAddress: '2N8bhEwbKtMvR2jqMRcTCQqzHP6zXGToXcK',
|
||||
// message: 'output #' + (Number(n) + 1)
|
||||
// });
|
||||
// };
|
||||
// lodash.times(15, addOutput);
|
||||
// txps.push(txp);
|
||||
|
||||
if (!txps) {
|
||||
$scope.txps = [];
|
||||
return;
|
||||
|
|
@ -73,6 +86,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
$scope.updatingStatus = true;
|
||||
$scope.updateStatusError = null;
|
||||
$scope.walletNotRegistered = false;
|
||||
$scope.vm.fetchedAllTxHistory = false;
|
||||
|
||||
walletService.getStatus($scope.wallet, {
|
||||
force: !!force,
|
||||
|
|
@ -156,68 +170,97 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
if (err) return;
|
||||
$timeout(function() {
|
||||
walletService.startScan($scope.wallet, function() {
|
||||
$scope.updateAll();
|
||||
$scope.updateAll(true, true);
|
||||
$scope.$apply();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var updateTxHistory = function(cb) {
|
||||
if (!cb) cb = function() {};
|
||||
$scope.updateTxHistoryError = false;
|
||||
$scope.updatingTxHistoryProgress = 0;
|
||||
|
||||
feeService.getFeeLevels($scope.wallet.coin, function(err, levels) {
|
||||
walletService.getTxHistory($scope.wallet, {
|
||||
feeLevels: levels
|
||||
}, function(err, txHistory) {
|
||||
$scope.updatingTxHistory = false;
|
||||
if (err) {
|
||||
$scope.txHistory = null;
|
||||
$scope.updateTxHistoryError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
applyCurrencyAliases(txHistory);
|
||||
|
||||
var config = configService.getSync();
|
||||
var fiatCode = config.wallet.settings.alternativeIsoCode;
|
||||
lodash.each(txHistory, function(t) {
|
||||
var r = rateService.toFiat(t.amount, fiatCode, $scope.wallet.coin);
|
||||
t.alternativeAmountStr = r.toFixed(2) + ' ' + fiatCode;
|
||||
});
|
||||
|
||||
$scope.completeTxHistory = txHistory;
|
||||
|
||||
$scope.showHistory();
|
||||
$timeout(function() {
|
||||
$scope.$apply();
|
||||
});
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function applyCurrencyAliases(txHistory) {
|
||||
var defaults = configService.getDefaults();
|
||||
var configCache = configService.getSync();
|
||||
|
||||
lodash.each(txHistory, function(t) {
|
||||
t.amountUnitStr = $scope.wallet.coin == 'btc'
|
||||
lodash.each(txHistory, function onTx(tx) {
|
||||
tx.amountUnitStr = $scope.wallet.coin == 'btc'
|
||||
? (configCache.bitcoinAlias || defaults.bitcoinAlias)
|
||||
: (configCache.bitcoinCashAlias || defaults.bitcoinCashAlias);
|
||||
|
||||
t.amountUnitStr = t.amountUnitStr.toUpperCase();
|
||||
tx.amountUnitStr = tx.amountUnitStr.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.showHistory = function() {
|
||||
if ($scope.completeTxHistory) {
|
||||
$scope.txHistory = $scope.completeTxHistory.slice(0, (currentTxHistoryPage + 1) * HISTORY_SHOW_LIMIT);
|
||||
$scope.txHistoryShowMore = $scope.completeTxHistory.length > $scope.txHistory.length;
|
||||
function formatTxHistoryForDisplay(txHistory) {
|
||||
applyCurrencyAliases(txHistory);
|
||||
|
||||
var config = configService.getSync();
|
||||
var fiatCode = config.wallet.settings.alternativeIsoCode;
|
||||
lodash.each(txHistory, function(t) {
|
||||
var r = rateService.toFiat(t.amount, fiatCode, $scope.wallet.coin);
|
||||
t.alternativeAmountStr = r.toFixed(2) + ' ' + fiatCode;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function updateTxHistoryFromCachedData() {
|
||||
$scope.vm.gettingCachedHistory = true;
|
||||
walletHistoryService.getCachedTxHistory($scope.wallet.id, function onGetCachedTxHistory(err, txHistory){
|
||||
$scope.vm.gettingCachedHistory = false;
|
||||
if (err) {
|
||||
// Don't display an error because we are also requesting the history.
|
||||
$log.error('Error getting cached tx history.', err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!txHistory) {
|
||||
$log.debug('No cached tx history.');
|
||||
return;
|
||||
}
|
||||
|
||||
formatTxHistoryForDisplay(txHistory);
|
||||
|
||||
completeTxHistory = txHistory;
|
||||
showHistory(false);
|
||||
$scope.$apply();
|
||||
});
|
||||
}
|
||||
|
||||
function fetchAndShowTxHistory(getLatest, flushCacheOnNew) {
|
||||
$scope.vm.updatingTxHistory = true;
|
||||
|
||||
walletHistoryService.updateLocalTxHistoryByPage($scope.wallet, getLatest, flushCacheOnNew, function onUpdateLocalTxHistoryByPage(err, txHistory, fetchedAllTransactions) {
|
||||
$scope.vm.gettingInitialHistory = false;
|
||||
$scope.vm.updatingTxHistory = false;
|
||||
$scope.$broadcast('scroll.infiniteScrollComplete');
|
||||
|
||||
if (err) {
|
||||
console.error('pagination Failed to get history.', err);
|
||||
$scope.vm.updateTxHistoryFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fetchedAllTransactions) {
|
||||
$scope.vm.fetchedAllTxHistory = true;
|
||||
}
|
||||
|
||||
formatTxHistoryForDisplay(txHistory);
|
||||
|
||||
completeTxHistory = txHistory;
|
||||
showHistory(true);
|
||||
$scope.$apply();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function showHistory(showAll) {
|
||||
if (completeTxHistory) {
|
||||
$scope.txHistory = showAll ? completeTxHistory : completeTxHistory.slice(0, (currentTxHistoryDisplayPage + 1) * DISPLAY_PAGE_SIZE);
|
||||
$scope.vm.allowInfiniteScroll = !$scope.vm.fetchedAllTxHistory && !(completeTxHistory.length === $scope.txHistory.length && $scope.vm.gettingInitialHistory);
|
||||
} else {
|
||||
$scope.vm.allowInfiniteScroll = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
$scope.getDate = function(txCreated) {
|
||||
var date = new Date(txCreated * 1000);
|
||||
|
|
@ -256,24 +299,35 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
return !tx.confirmations || tx.confirmations === 0;
|
||||
};
|
||||
|
||||
// on-infinite="showMore()"
|
||||
$scope.showMore = function() {
|
||||
$timeout(function() {
|
||||
currentTxHistoryPage++;
|
||||
$scope.showHistory();
|
||||
// Check if we have more than we are displaying
|
||||
if (completeTxHistory.length > $scope.txHistory.length) {
|
||||
currentTxHistoryDisplayPage++;
|
||||
showHistory(false);
|
||||
$scope.$broadcast('scroll.infiniteScrollComplete');
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.vm.updatingTxHistory) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetchAndShowTxHistory(false, false);
|
||||
};
|
||||
|
||||
// on-refresh="onRefresh()"
|
||||
$scope.onRefresh = function() {
|
||||
$timeout(function() {
|
||||
$scope.$broadcast('scroll.refreshComplete');
|
||||
}, 300);
|
||||
$scope.updateAll(true);
|
||||
$scope.updateAll(true, false);
|
||||
};
|
||||
|
||||
$scope.updateAll = function(force, cb) {
|
||||
updateStatus(force);
|
||||
updateTxHistory(cb);
|
||||
$scope.updateAll = function(forceStatusUpdate, flushTxCacheOnNew) {
|
||||
updateStatus(forceStatusUpdate);
|
||||
//updateTxHistory(cb);
|
||||
fetchAndShowTxHistory(true, flushTxCacheOnNew);
|
||||
};
|
||||
|
||||
$scope.hideToggle = function() {
|
||||
|
|
@ -283,103 +337,36 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
};
|
||||
|
||||
var prevPos;
|
||||
|
||||
$scope.txHistoryPaddingBottom = 0;
|
||||
function getScrollPosition() {
|
||||
var scrollPosition = $ionicScrollDelegate.getScrollPosition();
|
||||
|
||||
$timeout(function() {
|
||||
getScrollPosition();
|
||||
}, 200);
|
||||
|
||||
if (!scrollPosition) {
|
||||
$window.requestAnimationFrame(function() {
|
||||
getScrollPosition();
|
||||
});
|
||||
return;
|
||||
}
|
||||
var pos = scrollPosition.top;
|
||||
if (pos > 0) {
|
||||
$scope.txHistoryPaddingBottom = "200px";
|
||||
}
|
||||
if (pos === prevPos) {
|
||||
$window.requestAnimationFrame(function() {
|
||||
getScrollPosition();
|
||||
});
|
||||
return;
|
||||
}
|
||||
prevPos = pos;
|
||||
refreshAmountSection(pos);
|
||||
};
|
||||
|
||||
function refreshAmountSection(scrollPos) {
|
||||
var AMOUNT_HEIGHT_BASE = 210;
|
||||
$scope.showBalanceButton = false;
|
||||
if ($scope.status) {
|
||||
$scope.showBalanceButton = ($scope.status.totalBalanceSat != $scope.status.spendableAmount);
|
||||
if ($scope.showBalanceButton) {
|
||||
AMOUNT_HEIGHT_BASE = 270;
|
||||
}
|
||||
}
|
||||
if (!$scope.amountIsCollapsible) {
|
||||
var t = ($scope.showBalanceButton ? 15 : 45);
|
||||
$scope.amountScale = 'translateY(' + t + 'px)';
|
||||
return;
|
||||
}
|
||||
|
||||
scrollPos = scrollPos || 0;
|
||||
var amountHeight = AMOUNT_HEIGHT_BASE - scrollPos;
|
||||
if (amountHeight < 80) {
|
||||
amountHeight = 80;
|
||||
}
|
||||
var contentMargin = amountHeight;
|
||||
if (contentMargin > AMOUNT_HEIGHT_BASE) {
|
||||
contentMargin = AMOUNT_HEIGHT_BASE;
|
||||
}
|
||||
|
||||
var amountScale = (amountHeight / AMOUNT_HEIGHT_BASE);
|
||||
if (amountScale < 0.5) {
|
||||
amountScale = 0.5;
|
||||
}
|
||||
if (amountScale > 1.1) {
|
||||
amountScale = 1.1;
|
||||
}
|
||||
|
||||
var s = amountScale;
|
||||
|
||||
// Make space for the balance button when it needs to display.
|
||||
var TOP_NO_BALANCE_BUTTON = 115;
|
||||
var TOP_BALANCE_BUTTON = 30;
|
||||
var top = TOP_NO_BALANCE_BUTTON;
|
||||
if ($scope.showBalanceButton) {
|
||||
top = TOP_BALANCE_BUTTON;
|
||||
}
|
||||
|
||||
var amountTop = ((amountScale - 0.80) / 0.80) * top;
|
||||
if (amountTop < -2) {
|
||||
amountTop = -2;
|
||||
}
|
||||
if (amountTop > top) {
|
||||
amountTop = top;
|
||||
}
|
||||
|
||||
var t = amountTop;
|
||||
|
||||
$scope.altAmountOpacity = (amountHeight - 100) / 80;
|
||||
$scope.buttonsOpacity = (amountHeight - 140) / 70;
|
||||
$window.requestAnimationFrame(function() {
|
||||
$scope.amountHeight = amountHeight + 'px';
|
||||
$scope.contentMargin = contentMargin + 'px';
|
||||
$scope.amountScale = 'scale3d(' + s + ',' + s + ',' + s + ') translateY(' + t + 'px)';
|
||||
$scope.$digest();
|
||||
getScrollPosition();
|
||||
});
|
||||
$scope.scrollPosition = pos;
|
||||
}
|
||||
|
||||
var scrollWatcherInitialized;
|
||||
|
||||
$scope.$on("$ionicView.enter", function(event, data) {
|
||||
if ($scope.isCordova && $scope.isAndroid) setAndroidStatusBarColor();
|
||||
if (scrollWatcherInitialized || !$scope.amountIsCollapsible) {
|
||||
return;
|
||||
}
|
||||
scrollWatcherInitialized = true;
|
||||
});
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
||||
sendFlowService.clear();
|
||||
|
||||
configService.whenAvailable(function (config) {
|
||||
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
|
||||
|
||||
|
|
@ -393,7 +380,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
if (!$scope.wallet) return;
|
||||
$scope.requiresMultipleSignatures = $scope.wallet.credentials.m > 1;
|
||||
|
||||
$scope.updatingTxHistory = true;
|
||||
$scope.vm.gettingInitialHistory = true;
|
||||
|
||||
addressbookService.list(function(err, ab) {
|
||||
if (err) $log.error(err);
|
||||
|
|
@ -403,21 +390,25 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
listeners = [
|
||||
$rootScope.$on('bwsEvent', function(e, walletId) {
|
||||
if (walletId == $scope.wallet.id && e.type != 'NewAddress')
|
||||
$scope.updateAll();
|
||||
$scope.updateAll(false, false);
|
||||
}),
|
||||
$rootScope.$on('Local/TxAction', function(e, walletId) {
|
||||
if (walletId == $scope.wallet.id)
|
||||
$scope.updateAll();
|
||||
$scope.updateAll(false, false);
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
var refreshInterval;
|
||||
|
||||
$scope.$on("$ionicView.afterEnter", function(event, data) {
|
||||
$scope.updateAll();
|
||||
refreshAmountSection();
|
||||
$scope.$on("$ionicView.afterEnter", function onAfterEnter(event, data) {
|
||||
updateTxHistoryFromCachedData();
|
||||
$scope.updateAll(true, true);
|
||||
// refreshAmountSection();
|
||||
refreshInterval = $interval($scope.onRefresh, 10 * 1000);
|
||||
$timeout(function() {
|
||||
getScrollPosition();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
$scope.$on("$ionicView.afterLeave", function(event, data) {
|
||||
|
|
@ -477,16 +468,10 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
|
|||
}
|
||||
|
||||
$scope.goToSend = function() {
|
||||
sendFlowService.startSend({
|
||||
sendFlowService.start({
|
||||
fromWalletId: $scope.wallet.id
|
||||
});
|
||||
|
||||
// Go home first so that the Home tab works properly
|
||||
$state.go('tabs.home').then(function () {
|
||||
$ionicHistory.clearHistory();
|
||||
$state.go('tabs.send');
|
||||
});
|
||||
|
||||
};
|
||||
$scope.goToReceive = function() {
|
||||
$state.go('tabs.home', {
|
||||
204
src/js/controllers/wallet-selector.controller.js
Normal file
204
src/js/controllers/wallet-selector.controller.js
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
'use strict';
|
||||
|
||||
(function () {
|
||||
|
||||
angular
|
||||
.module('copayApp.controllers')
|
||||
.controller('walletSelectorController', walletSelectorController);
|
||||
|
||||
function walletSelectorController ($scope, $state, sendFlowService, configService, gettextCatalog, ongoingProcess, profileService, walletService, txFormatService) {
|
||||
var fromWalletId = '';
|
||||
var priceDisplayAsFiat = false;
|
||||
var unitDecimals = 0;
|
||||
var unitsFromSatoshis = 0;
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", onBeforeEnter);
|
||||
$scope.$on("$ionicView.enter", onEnter);
|
||||
|
||||
function onBeforeEnter(event, data) {
|
||||
if (data.direction == "back") {
|
||||
sendFlowService.state.pop();
|
||||
}
|
||||
|
||||
$scope.params = sendFlowService.state.getClone();
|
||||
|
||||
console.log('walletSelector onBeforeEnter after back sendflow', $scope.params);
|
||||
|
||||
var config = configService.getSync().wallet.settings;
|
||||
priceDisplayAsFiat = config.priceDisplay === 'fiat';
|
||||
unitDecimals = config.unitDecimals;
|
||||
unitsFromSatoshis = 1 / config.unitToSatoshi;
|
||||
|
||||
if ($scope.params.isWalletTransfer) {
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets');
|
||||
} else if (!$scope.params.thirdParty) {
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Send');
|
||||
}
|
||||
|
||||
$scope.coin = false; // Wallets to show (for destination screen or contacts)
|
||||
$scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination
|
||||
fromWalletId = $scope.params['fromWalletId'];
|
||||
|
||||
if ($scope.type === 'destination' && $scope.params.toAddress) {
|
||||
$state.transitionTo(getNextStep($scope.params));
|
||||
}
|
||||
|
||||
if ($scope.params.coin) {
|
||||
$scope.coin = $scope.params.coin; // Contacts have a coin embedded
|
||||
}
|
||||
|
||||
if ($scope.params.amount) { // There is an amount, so presume that it is a payment request
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Payment Request');
|
||||
$scope.specificAmount = $scope.specificAlternativeAmount = '';
|
||||
$scope.isPaymentRequest = true;
|
||||
}
|
||||
if ($scope.params.thirdParty) {
|
||||
$scope.thirdParty = $scope.params.thirdParty;
|
||||
}
|
||||
};
|
||||
|
||||
function onEnter (event, data) {
|
||||
configService.whenAvailable(function(config) {
|
||||
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
|
||||
});
|
||||
|
||||
if ($scope.thirdParty) {
|
||||
// Third party services specific logic
|
||||
handleThirdPartyIfShapeshift();
|
||||
}
|
||||
|
||||
prepareWalletLists();
|
||||
formatRequestedAmount();
|
||||
};
|
||||
|
||||
function formatRequestedAmount() {
|
||||
if ($scope.params.amount) {
|
||||
var cryptoAmount = (unitsFromSatoshis * $scope.params.amount).toFixed(unitDecimals);
|
||||
var cryptoCoin = $scope.coin.toUpperCase();
|
||||
|
||||
txFormatService.formatAlternativeStr($scope.coin, $scope.params.amount, function onFormatAlternativeStr(formatted){
|
||||
if (formatted) {
|
||||
var fiatParts = formatted.split(' ');
|
||||
var fiatAmount = fiatParts[0];
|
||||
var fiatCurrrency = fiatParts.length > 1 ? fiatParts[1] : '';
|
||||
|
||||
if (priceDisplayAsFiat) {
|
||||
$scope.requestAmount = fiatAmount;
|
||||
$scope.requestCurrency = fiatCurrrency;
|
||||
|
||||
$scope.requestAmountSecondary = cryptoAmount;
|
||||
$scope.requestCurrencySecondary = cryptoCoin;
|
||||
} else {
|
||||
$scope.requestAmount = cryptoAmount;
|
||||
$scope.requestCurrency = cryptoCoin;
|
||||
|
||||
$scope.requestAmountSecondary = fiatAmount;
|
||||
$scope.requestCurrencySecondary = fiatCurrrency;
|
||||
}
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleThirdPartyIfShapeshift() {
|
||||
console.log($scope.thirdParty, $scope.coin);
|
||||
if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the
|
||||
$scope.coin = profileService.getWallet(fromWalletId).coin;
|
||||
if ($scope.coin === 'bch') {
|
||||
$scope.coin = 'btc';
|
||||
} else {
|
||||
$scope.coin = 'bch';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prepareWalletLists() {
|
||||
var walletsAll = [];
|
||||
var walletsSufficientFunds = [];
|
||||
$scope.walletsInsufficientFunds = []; // For origin screen
|
||||
|
||||
if ($scope.type === 'origin') {
|
||||
$scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from');
|
||||
|
||||
if ($scope.params.amount || $scope.coin) {
|
||||
|
||||
walletsAll = profileService.getWallets({coin: $scope.coin});
|
||||
ongoingProcess.set('scanning', true);
|
||||
walletsAll.forEach(function forWallet(wallet) {
|
||||
if (!wallet.status && !wallet.cachedStatus) {
|
||||
walletService.getStatus(wallet, {}, function(err, status) {
|
||||
wallet.status = status;
|
||||
if (status.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) {
|
||||
walletsSufficientFunds.push(wallet);
|
||||
} else {
|
||||
$scope.walletsInsufficientFunds.push(wallet);
|
||||
}
|
||||
if ($scope.coin === 'btc') { // As this is a promise
|
||||
$scope.walletsBtc = walletsSufficientFunds;
|
||||
} else {
|
||||
$scope.walletsBch = walletsSufficientFunds;
|
||||
}
|
||||
ongoingProcess.set('scanning', false);
|
||||
});
|
||||
} else {
|
||||
var walletStatus = null;
|
||||
if (wallet.status && wallet.status.isValid) {
|
||||
walletStatus = wallet.status;
|
||||
} else if (wallet.cachedStatus && wallet.status.isValid) {
|
||||
walletStatus = wallet.cachedStatus;
|
||||
}
|
||||
|
||||
if (walletStatus && walletStatus.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) {
|
||||
walletsSufficientFunds.push(wallet);
|
||||
} else {
|
||||
$scope.walletsInsufficientFunds.push(wallet);
|
||||
}
|
||||
ongoingProcess.set('scanning', false);
|
||||
}
|
||||
});
|
||||
|
||||
if ($scope.coin === 'btc') {
|
||||
$scope.walletsBtc = walletsSufficientFunds;
|
||||
} else {
|
||||
$scope.walletsBch = walletsSufficientFunds;
|
||||
}
|
||||
} else {
|
||||
$scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: true});
|
||||
$scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: true});
|
||||
$scope.walletsInsufficientFunds = profileService.getWallets({coin: $scope.coin, hasNoFunds: true});
|
||||
}
|
||||
|
||||
} else if ($scope.type === 'destination') {
|
||||
if (!$scope.coin) { // Allow for the coin to be set by a third party
|
||||
$scope.fromWallet = profileService.getWallet(fromWalletId);
|
||||
$scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin
|
||||
}
|
||||
$scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to');
|
||||
|
||||
if ($scope.coin === 'btc') { // if no specific coin is set or coin is set btc
|
||||
$scope.walletsBtc = profileService.getWallets({coin: $scope.coin});
|
||||
} else {
|
||||
$scope.walletsBch = profileService.getWallets({coin: $scope.coin});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
$scope.useWallet = function(wallet) {
|
||||
var params = sendFlowService.state.getClone();
|
||||
if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from
|
||||
params.fromWalletId = wallet.id;
|
||||
} else { // we're on the destination screen, set wallet to send to
|
||||
params.toWalletId = wallet.id;
|
||||
}
|
||||
sendFlowService.goNext(params);
|
||||
};
|
||||
|
||||
$scope.goBack = function() {
|
||||
sendFlowService.router.goBack();
|
||||
}
|
||||
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, sendFlowService, configService, gettextCatalog, profileService, txFormatService) {
|
||||
|
||||
var fromWalletId = '';
|
||||
var priceDisplayAsFiat = false;
|
||||
var unitDecimals = 0;
|
||||
var unitsFromSatoshis = 0;
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", onBeforeEnter);
|
||||
$scope.$on("$ionicView.enter", onEnter);
|
||||
|
||||
function onBeforeEnter(event, data) {
|
||||
if (data.direction == "back") {
|
||||
sendFlowService.popState();
|
||||
}
|
||||
console.log('walletSelector onBeforeEnter after back sendflow', sendFlowService.state);
|
||||
|
||||
$scope.params = sendFlowService.getStateClone();
|
||||
|
||||
var config = configService.getSync().wallet.settings;
|
||||
priceDisplayAsFiat = config.priceDisplay === 'fiat';
|
||||
unitDecimals = config.unitDecimals;
|
||||
unitsFromSatoshis = 1 / config.unitToSatoshi;
|
||||
|
||||
switch($state.current.name) {
|
||||
case 'tabs.send.wallet-to-wallet':
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets');
|
||||
break;
|
||||
case 'tabs.send.destination':
|
||||
if ($scope.params.fromWalletId && !$scope.params.thirdParty) {
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!$scope.params.thirdParty) {
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Send');
|
||||
}
|
||||
// nop
|
||||
}
|
||||
|
||||
$scope.coin = false; // Wallets to show (for destination screen or contacts)
|
||||
$scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination
|
||||
fromWalletId = $scope.params['fromWalletId'];
|
||||
|
||||
if ($scope.type === 'destination' && $scope.params.toAddress) {
|
||||
$state.transitionTo(getNextStep($scope.params));
|
||||
}
|
||||
|
||||
if ($scope.params.coin) {
|
||||
$scope.coin = $scope.params.coin; // Contacts have a coin embedded
|
||||
}
|
||||
|
||||
if ($scope.params.amount) { // There is an amount, so presume that it is a payment request
|
||||
$scope.sendFlowTitle = gettextCatalog.getString('Payment Request');
|
||||
$scope.specificAmount = $scope.specificAlternativeAmount = '';
|
||||
$scope.isPaymentRequest = true;
|
||||
}
|
||||
if ($scope.params.thirdParty) {
|
||||
$scope.thirdParty = $scope.params.thirdParty;
|
||||
}
|
||||
};
|
||||
|
||||
function onEnter (event, data) {
|
||||
configService.whenAvailable(function(config) {
|
||||
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
|
||||
});
|
||||
|
||||
if ($scope.thirdParty) {
|
||||
// Third party services specific logic
|
||||
handleThirdPartyIfShapeshift();
|
||||
}
|
||||
|
||||
prepareWalletLists();
|
||||
formatRequestedAmount();
|
||||
};
|
||||
|
||||
function formatRequestedAmount() {
|
||||
if ($scope.params.amount) {
|
||||
var cryptoAmount = (unitsFromSatoshis * $scope.params.amount).toFixed(unitDecimals);
|
||||
var cryptoCoin = $scope.coin.toUpperCase();
|
||||
|
||||
txFormatService.formatAlternativeStr($scope.coin, $scope.params.amount, function onFormatAlternativeStr(formatted){
|
||||
if (formatted) {
|
||||
var fiatParts = formatted.split(' ');
|
||||
var fiatAmount = fiatParts[0];
|
||||
var fiatCurrrency = fiatParts.length > 1 ? fiatParts[1] : '';
|
||||
|
||||
if (priceDisplayAsFiat) {
|
||||
$scope.requestAmount = fiatAmount;
|
||||
$scope.requestCurrency = fiatCurrrency;
|
||||
|
||||
$scope.requestAmountSecondary = cryptoAmount;
|
||||
$scope.requestCurrencySecondary = cryptoCoin;
|
||||
} else {
|
||||
$scope.requestAmount = cryptoAmount;
|
||||
$scope.requestCurrency = cryptoCoin;
|
||||
|
||||
$scope.requestAmountSecondary = fiatAmount;
|
||||
$scope.requestCurrencySecondary = fiatCurrrency;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getNextStep(params) {
|
||||
if (!params.toWalletId && !params.toAddress) { // If we have no toAddress or fromWallet
|
||||
return 'tabs.send.destination';
|
||||
} else if (!params.amount) { // If we have no amount
|
||||
return 'tabs.send.amount';
|
||||
} else { // If we do have them
|
||||
return 'tabs.send.review';
|
||||
}
|
||||
}
|
||||
|
||||
function handleThirdPartyIfShapeshift() {
|
||||
console.log($scope.thirdParty, $scope.coin);
|
||||
if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the
|
||||
$scope.coin = profileService.getWallet(fromWalletId).coin;
|
||||
if ($scope.coin === 'bch') {
|
||||
$scope.coin = 'btc';
|
||||
} else {
|
||||
$scope.coin = 'bch';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prepareWalletLists() {
|
||||
var walletsAll = [];
|
||||
var walletsSufficientFunds = [];
|
||||
$scope.walletsInsufficientFunds = []; // For origin screen
|
||||
|
||||
if ($scope.type === 'origin') {
|
||||
$scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from');
|
||||
|
||||
if ($scope.params.amount) {
|
||||
|
||||
walletsAll = profileService.getWallets({coin: $scope.coin});
|
||||
|
||||
walletsAll.forEach(function forWallet(wallet){
|
||||
if (wallet.status.availableBalanceSat > $scope.params.amount) {
|
||||
walletsSufficientFunds.push(wallet);
|
||||
} else {
|
||||
$scope.walletsInsufficientFunds.push(wallet);
|
||||
}
|
||||
});
|
||||
|
||||
if ($scope.coin === 'btc') {
|
||||
$scope.walletsBtc = walletsSufficientFunds;
|
||||
} else {
|
||||
$scope.walletsBch = walletsSufficientFunds;
|
||||
}
|
||||
|
||||
} else if ($scope.coin) {
|
||||
walletsAll = profileService.getWallets({coin: $scope.coin});
|
||||
walletsAll.forEach(function forWallet(wallet){
|
||||
if (wallet.status.availableBalanceSat > 0) {
|
||||
walletsSufficientFunds.push(wallet);
|
||||
} else {
|
||||
$scope.walletsInsufficientFunds.push(wallet);
|
||||
}
|
||||
});
|
||||
|
||||
if ($scope.coin === 'btc') {
|
||||
$scope.walletsBtc = walletsSufficientFunds;
|
||||
} else {
|
||||
$scope.walletsBch = walletsSufficientFunds;
|
||||
}
|
||||
} else {
|
||||
$scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: true});
|
||||
$scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: true});
|
||||
$scope.walletsInsufficientFunds = profileService.getWallets({coin: $scope.coin, hasNoFunds: true});
|
||||
}
|
||||
|
||||
} else if ($scope.type === 'destination') {
|
||||
if (!$scope.coin) { // Allow for the coin to be set by a third party
|
||||
$scope.fromWallet = profileService.getWallet(fromWalletId);
|
||||
$scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin
|
||||
}
|
||||
$scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to');
|
||||
|
||||
if ($scope.coin === 'btc') { // if no specific coin is set or coin is set btc
|
||||
$scope.walletsBtc = profileService.getWallets({coin: $scope.coin});
|
||||
} else {
|
||||
$scope.walletsBch = profileService.getWallets({coin: $scope.coin});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
$scope.useWallet = function(wallet) {
|
||||
var params = sendFlowService.getStateClone();
|
||||
if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from
|
||||
params.fromWalletId = wallet.id;
|
||||
} else { // we're on the destination screen, set wallet to send to
|
||||
params.toWalletId = wallet.id;
|
||||
}
|
||||
sendFlowService.pushState(params);
|
||||
var nextStep = getNextStep(params);
|
||||
console.log('walletSelector nextStep', nextStep);
|
||||
$state.transitionTo(nextStep, $scope.params);
|
||||
};
|
||||
|
||||
$scope.goBack = function() {
|
||||
$ionicHistory.goBack();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.directives')
|
||||
.directive('incomingDataMenu', function($timeout, $rootScope, $state, externalLinkService) {
|
||||
.directive('incomingDataMenu', function($timeout, $rootScope, $state, externalLinkService, sendFlowService, bitcoinCashJsService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'views/includes/incomingDataMenu.html',
|
||||
link: function(scope, element, attrs) {
|
||||
$rootScope.$on('incomingDataMenu.showMenu', function(event, data) {
|
||||
$timeout(function() {
|
||||
scope.data = data.data;
|
||||
scope.type = data.type;
|
||||
scope.showMenu = true;
|
||||
scope.https = false;
|
||||
scope.data = data;
|
||||
|
||||
if (scope.type === 'url') {
|
||||
if (scope.data.indexOf('https://') === 0) {
|
||||
scope.https = true;
|
||||
}
|
||||
if (scope.data.parsed.privateKey) {
|
||||
scope.type = "privateKey";
|
||||
} else if (scope.data.parsed.url) {
|
||||
scope.type = "url";
|
||||
} else if (scope.data.parsed.publicAddress) {
|
||||
scope.type = "bitcoinAddress";
|
||||
var prefix = scope.data.parsed.isTestnet ? 'bchtest:' : 'bitcoincash:';
|
||||
scope.data.toAddress = (prefix + scope.data.parsed.publicAddress.cashAddr) || scope.data.parsed.publicAddress.legacy || scope.data.parsed.publicAddress.bitpay;
|
||||
} else {
|
||||
scope.type = "text";
|
||||
}
|
||||
|
||||
scope.showMenu = true;
|
||||
});
|
||||
});
|
||||
scope.hide = function() {
|
||||
|
|
@ -28,18 +33,9 @@ angular.module('copayApp.directives')
|
|||
externalLinkService.open(url);
|
||||
};
|
||||
scope.sendPaymentToAddress = function(bitcoinAddress) {
|
||||
var noPrefixInAddress = 0;
|
||||
if (bitcoinAddress.toLowerCase().indexOf('bitcoin') < 0) {
|
||||
noPrefixInAddress = 1;
|
||||
}
|
||||
scope.showMenu = false;
|
||||
$state.go('tabs.send').then(function() {
|
||||
$timeout(function() {
|
||||
$state.transitionTo('tabs.send.amount', {
|
||||
toAddress: bitcoinAddress,
|
||||
noPrefix: noPrefixInAddress
|
||||
});
|
||||
}, 50);
|
||||
sendFlowService.start({
|
||||
data: bitcoinAddress
|
||||
});
|
||||
};
|
||||
scope.addToAddressBook = function(bitcoinAddress) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function($interval, shapeshiftApiService, profileService, incomingData, ongoingProcess) {
|
||||
angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function($interval, shapeshiftApiService, profileService, incomingDataService, ongoingProcess) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
|
|
@ -111,7 +111,8 @@ angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function
|
|||
orderId: $scope.depositInfo.orderId
|
||||
};
|
||||
|
||||
if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) {
|
||||
// How to handle this
|
||||
if (incomingDataService.redir(sendAddress, 'shapeshift', shapeshiftData)) {
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,8 @@ angular.module('copayApp.directives')
|
|||
|
||||
function getTransformStyle(translatePct) {
|
||||
return {
|
||||
'transform': 'translateX(' + translatePct + '%)'
|
||||
'transform': 'translateX(' + translatePct + '%)',
|
||||
'-webkit-transform': 'translateX(' + translatePct + '%)'
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
totalBalanceSat: '@',
|
||||
// The Wallet object is sometimes not stringify()-able, so not interpolatable,
|
||||
// so can't be passed to a directive.
|
||||
walletCoin: '@',
|
||||
walletStatus: '@',
|
||||
walletCachedBalance: '@',
|
||||
walletCachedBalanceUpdatedOn: '@',
|
||||
|
|
@ -31,7 +32,6 @@
|
|||
});
|
||||
|
||||
function displayCryptoBalance(walletStatus, walletCachedBalance, walletCachedBalanceUpdatedOn, walletCachedStatus) {
|
||||
console.log('displayCryptoBalance()');
|
||||
|
||||
if (walletStatus && walletStatus.isValid && walletStatus.totalBalanceStr) {
|
||||
setDisplay(walletStatus.totalBalanceStr, '');
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
setDisplay('', '');
|
||||
}
|
||||
|
||||
function displayFiatBalance(walletStatus, walletCachedStatus) {
|
||||
function displayFiatBalance(walletStatus, walletCachedStatus, walletCoin) {
|
||||
var displayAmount = '';
|
||||
if (walletStatus && walletStatus.isValid && walletStatus.alternativeBalanceAvailable) {
|
||||
displayAmount = walletStatus.totalBalanceAlternative + ' ' + walletStatus.alternativeIsoCode;
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
getFiatBalance(wallet);
|
||||
getFiatBalance(walletStatus, walletCachedStatus, walletCoin);
|
||||
}
|
||||
|
||||
function formatBalance() {
|
||||
|
|
@ -94,19 +94,30 @@
|
|||
}
|
||||
|
||||
if (displayAsFiat) {
|
||||
displayFiatBalance(walletStatusObj, walletCachedStatusObj);
|
||||
displayFiatBalance(walletStatusObj, walletCachedStatusObj, $scope.walletCoin);
|
||||
}
|
||||
}
|
||||
|
||||
function getFiatBalance(wallet) {
|
||||
if (!(wallet.status && wallet.status.isValid)) {
|
||||
$log.warn('Abandoning call to get fiat balance, because no valid wallet status.');
|
||||
function getFiatBalance(walletStatus, walletCachedStatus, walletCoin) {
|
||||
var totalBalanceSat = null;
|
||||
|
||||
if (walletStatus && walletStatus.isValid) {
|
||||
totalBalanceSat = walletStatus.totalBalanceSat
|
||||
} else if (walletCachedStatus && walletCachedStatus.isValid) {
|
||||
totalBalanceSat = walletCachedStatus.totalBalanceSat
|
||||
}
|
||||
|
||||
// 0 is valid
|
||||
if (totalBalanceSat === null) {
|
||||
$log.warn('Abandoning call to get fiat balance, because no valid wallet status (cached or otherwise).');
|
||||
return;
|
||||
}
|
||||
|
||||
txFormatService.formatAlternativeStr(wallet.coin, wallet.status.totalBalanceSat, function onFormatAlernativeStr(formatted) {
|
||||
txFormatService.formatAlternativeStr(walletCoin, totalBalanceSat, function onFormatAlernativeStr(formatted) {
|
||||
if (formatted) {
|
||||
setDisplay(formatted, '');
|
||||
} else {
|
||||
$log.error('Failed to format fiat balance of wallet.');
|
||||
}
|
||||
});
|
||||
}
|
||||
416
src/js/services/bitcoin-uri.service.js
Normal file
416
src/js/services/bitcoin-uri.service.js
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
'use strict';
|
||||
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0072.mediawiki
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('bitcoinUriService', bitcoinUriService);
|
||||
|
||||
function bitcoinUriService(bitcoinCashJsService, bwcService, $log) {
|
||||
var bch = bitcoinCashJsService.getBitcoinCashJs();
|
||||
var bitcore = bwcService.getBitcore();
|
||||
|
||||
var service = {
|
||||
parse: parse
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function bitpayAddrOnMainnet(address) {
|
||||
var Address = bch.Address;
|
||||
var BitpayFormat = Address.BitpayFormat;
|
||||
|
||||
var mainnet = bch.Networks.mainnet;
|
||||
|
||||
var result = null;
|
||||
if (address[0] == 'C') {
|
||||
try {
|
||||
result = Address.fromString(address, mainnet, 'pubkeyhash', BitpayFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'H') {
|
||||
try {
|
||||
result = Address.fromString(address, mainnet, 'scripthash', BitpayFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function cashAddrOnMainnet(address) {
|
||||
var Address = bch.Address;
|
||||
var CashAddrFormat = Address.CashAddrFormat;
|
||||
|
||||
var mainnet = bch.Networks.mainnet;
|
||||
|
||||
var prefixed = 'bitcoincash:' + address;
|
||||
var result = null;
|
||||
if (address[0] == 'q') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, mainnet, 'pubkeyhash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'p') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, mainnet, 'scripthash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function cashAddrOnTestnet(address) {
|
||||
var Address = bch.Address;
|
||||
var CashAddrFormat = Address.CashAddrFormat;
|
||||
|
||||
var testnet = bch.Networks.testnet;
|
||||
|
||||
var prefixed = 'bchtest:' + address;
|
||||
var result = null;
|
||||
if (address[0] == 'q') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, testnet, 'pubkeyhash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
} else if (address[0] == 'p') {
|
||||
try {
|
||||
result = Address.fromString(prefixed, testnet, 'scripthash', CashAddrFormat);
|
||||
} catch (e) {};
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function infoFromWalletImportText(data) {
|
||||
var split = data.split('|');
|
||||
// Copay seems to use extra parameter for coin.
|
||||
if (split.length < 5 || split.length > 6) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var type = parseInt(split[0], 10);
|
||||
if (isNaN(type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = split[1];
|
||||
var network = split[2];
|
||||
if (!(network === 'livenet' || network === 'testnet')) {
|
||||
return null;
|
||||
}
|
||||
var isTestnet = network === 'testnet';
|
||||
|
||||
var derivationPath = split[3];
|
||||
if (!/^m\/\d+'\/\d+'\/\d+'$/.test(derivationPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var hasPassphraseText = split[4];
|
||||
if (!(hasPassphraseText === 'true' || hasPassphraseText === 'false')) {
|
||||
return null;
|
||||
}
|
||||
var hasPassphrase = hasPassphraseText === 'true';
|
||||
|
||||
var coin; // Intentionally undefined as may not be present
|
||||
if (split.length > 5) {
|
||||
var coinText = split[5];
|
||||
if (!(coinText === 'bch' || coinText === 'btc')) {
|
||||
return null;
|
||||
}
|
||||
coin = coinText;
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
data: data,
|
||||
isTestnet: isTestnet,
|
||||
derivationPath: derivationPath,
|
||||
hasPassphrase: hasPassphrase,
|
||||
coin: coin
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
For parsing:
|
||||
BIP21
|
||||
BIP72
|
||||
|
||||
returns:
|
||||
{
|
||||
amount: '',
|
||||
amountInSatoshis: 0,
|
||||
bareUrl: '',
|
||||
coin: '',
|
||||
copayInvitation: '',
|
||||
import: { // testnet info in root, coin info in root if available
|
||||
data: '',
|
||||
derivationPath: '',
|
||||
hasPassphrase: false,
|
||||
type: 1,
|
||||
},
|
||||
isValid: false,
|
||||
label: '',
|
||||
message: '',
|
||||
other: {
|
||||
somethingIDontUnderstand: 'Its value'
|
||||
},
|
||||
privateKey: {
|
||||
encrypted: '',
|
||||
wif: ''
|
||||
}'',
|
||||
publicAddress: {
|
||||
bitpay: '',
|
||||
cashAddr: '',
|
||||
legacy: '',
|
||||
},
|
||||
req: {
|
||||
"req-param0": '',
|
||||
"req-param1": ''
|
||||
},
|
||||
testnet: false,
|
||||
url: '' // For BIP70
|
||||
}
|
||||
|
||||
Only fields that are present in the data are defined in the returned object. Both privateKey and publicAddress only have 1 field defined, if they exist at all.
|
||||
The exception to this is the coin property, which is determined from other data, such as the prefix or address type.
|
||||
|
||||
*/
|
||||
|
||||
function parse(data) {
|
||||
var parsed = {
|
||||
isValid: false
|
||||
};
|
||||
|
||||
if (typeof data !== 'string') {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Identify prefix
|
||||
var trimmed = data.trim();
|
||||
var colonSplit = /^([\w-]*):?(.*)$/.exec(trimmed);
|
||||
if (!colonSplit) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var addressAndParams = '';
|
||||
var preColonLower = colonSplit[1].toLowerCase();
|
||||
if (preColonLower === 'bitcoin') {
|
||||
parsed.coin = 'btc';
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is btc');
|
||||
|
||||
} else if (/^(?:bitcoincash)|(?:bitcoin-cash)$/.test(preColonLower)) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.test = false;
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is bch');
|
||||
|
||||
} else if (/^(?:bchtest)$/.test(preColonLower)) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.isTestnet = true;
|
||||
addressAndParams = colonSplit[2].trim();
|
||||
console.log('Is bch');
|
||||
|
||||
} else if (colonSplit[2] === '') {
|
||||
// No colon and no coin specifier.
|
||||
addressAndParams = colonSplit[1].trim();
|
||||
console.log('No prefix.');
|
||||
|
||||
} else if (/^https?$/.test(colonSplit[1])) { // Plain URL
|
||||
addressAndParams = trimmed;
|
||||
|
||||
} else if (colonSplit[2].indexOf('|') == 0) { // Import
|
||||
addressAndParams = trimmed
|
||||
} else {
|
||||
// Something we don't recognise
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Remove erroneous leading slashes
|
||||
//var leadingSlashes = /^\/*([^\/]+(?:.*))$/.exec(addressAndParams);
|
||||
var leadingSlashes = /^\/*(.*)$/.exec(addressAndParams);
|
||||
if (!leadingSlashes) {
|
||||
return parsed;
|
||||
}
|
||||
addressAndParams = leadingSlashes[1];
|
||||
|
||||
var questionMarkSplit = /^([^\?]*)\??([^\?]*)$/.exec(addressAndParams);
|
||||
if (!questionMarkSplit) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var address = questionMarkSplit[1];
|
||||
var params = questionMarkSplit[2];
|
||||
|
||||
if (params.length > 0) {
|
||||
var paramsSplit = params.split('&');
|
||||
var others;
|
||||
var req;
|
||||
var paramCount = paramsSplit.length;
|
||||
for(var i = 0; i < paramCount; i++) {
|
||||
var param = paramsSplit[i];
|
||||
var valueSplit = param.split('=');
|
||||
if (valueSplit.length !== 2) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
var key = valueSplit[0];
|
||||
var value = valueSplit[1];
|
||||
var decodedValue = decodeURIComponent(value);
|
||||
switch(key) {
|
||||
case 'amount':
|
||||
var amount = parseFloat(decodedValue);
|
||||
if (amount) { // Checking for NaN, or no numbers at all etc. & convert to satoshi
|
||||
parsed.amount = decodedValue; // Need to check if a currency is precised
|
||||
parsed.amountInSatoshis = amount * 100000000
|
||||
} else {
|
||||
return parsed;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'label':
|
||||
parsed.label = decodedValue;
|
||||
break;
|
||||
|
||||
case 'message':
|
||||
parsed.message = decodedValue;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
// Could use a more comprehesive regex to test URL validity, but then how would we know
|
||||
// which part of the validation it failed?
|
||||
if (decodedValue.startsWith('https://')) {
|
||||
parsed.url = decodedValue;
|
||||
} else {
|
||||
return parsed;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (key.startsWith('req-')) {
|
||||
req = req || {};
|
||||
req[key] = decodedValue;
|
||||
} else {
|
||||
others = others || {};
|
||||
others[key] = decodedValue;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
parsed.others = others;
|
||||
parsed.req = req;
|
||||
|
||||
|
||||
if (address) {
|
||||
var addressLowerCase = address.toLowerCase();
|
||||
var copayInvitationRe = /^[0-9A-HJ-NP-Za-km-z]{70,80}$/;
|
||||
//var legacyRe = /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
||||
//var legacyTestnetRe = /^[mn][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
||||
var privateKeyEncryptedRe = /^6P[1-9A-HJ-NP-Za-km-z]{56}$/;
|
||||
var privateKeyForUncompressedPublicKeyRe = /^5[1-9A-HJ-NP-Za-km-z]{50}$/;
|
||||
var privateKeyForUncompressedPublicKeyTestnetRe = /^9[1-9A-HJ-NP-Za-km-z]{50}$/;
|
||||
var privateKeyForCompressedPublicKeyRe = /^[KL][1-9A-HJ-NP-Za-km-z]{51}$/;
|
||||
var privateKeyForCompressedPublicKeyTestnetRe = /^[c][1-9A-HJ-NP-Za-km-z]{51}$/;
|
||||
var urlRe = /^https?:\/\/.+/;
|
||||
|
||||
var bitpayAddrMainnet = bitpayAddrOnMainnet(address);
|
||||
var cashAddrTestnet = cashAddrOnTestnet(addressLowerCase);
|
||||
var cashAddrMainnet = cashAddrOnMainnet(addressLowerCase);
|
||||
var importInfo = infoFromWalletImportText(address);
|
||||
var privateKey = '';
|
||||
|
||||
if (parsed.isTestnet && cashAddrTestnet) {
|
||||
parsed.address = addressLowerCase;
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
cashAddr: addressLowerCase
|
||||
};
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (cashAddrMainnet) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
cashAddr: addressLowerCase
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitcore.Address.isValid(address, 'livenet')) {
|
||||
parsed.publicAddress = {
|
||||
legacy: address
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitcore.Address.isValid(address, 'testnet')) {
|
||||
parsed.publicAddress = {
|
||||
legacy: address
|
||||
};
|
||||
parsed.isTestnet = true;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (bitpayAddrMainnet) {
|
||||
parsed.coin = 'bch';
|
||||
parsed.publicAddress = {
|
||||
bitpay: address
|
||||
};
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (copayInvitationRe.test(address) ) {
|
||||
parsed.copayInvitation = address;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (privateKeyForUncompressedPublicKeyRe.test(address) || privateKeyForCompressedPublicKeyRe.test(address)) {
|
||||
privateKey = address;
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'livenet');
|
||||
parsed.privateKey = { wif: privateKey };
|
||||
parsed.isTestnet = false;
|
||||
parsed.isValid = true;
|
||||
} catch (e) {}
|
||||
|
||||
} else if (privateKeyForUncompressedPublicKeyTestnetRe.test(address) || privateKeyForCompressedPublicKeyTestnetRe.test(address)) {
|
||||
privateKey = address;
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'testnet');
|
||||
parsed.privateKey = { wif: privateKey };
|
||||
parsed.isTestnet = true;
|
||||
parsed.isValid = true;
|
||||
} catch (e) {}
|
||||
|
||||
} else if (privateKeyEncryptedRe.test(address)) {
|
||||
parsed.privateKey = { encrypted: address };
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (urlRe.test(address)) {
|
||||
parsed.bareUrl = trimmed;
|
||||
parsed.isValid = true;
|
||||
|
||||
} else if (importInfo) {
|
||||
parsed.import = {
|
||||
type: importInfo.type,
|
||||
data: importInfo.data,
|
||||
derivationPath: importInfo.derivationPath,
|
||||
hasPassphrase: importInfo.hasPassphrase
|
||||
};
|
||||
parsed.coin = importInfo.coin;
|
||||
parsed.isTestnet = importInfo.isTestnet;
|
||||
parsed.isValid = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
parsed.isValid = !!parsed.url; // BIP72
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
429
src/js/services/bitcoin-uri.service.spec.js
Normal file
429
src/js/services/bitcoin-uri.service.spec.js
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
describe('bitcoinUriService', function() {
|
||||
var bitcoinUriService;
|
||||
|
||||
beforeEach(function() {
|
||||
module('bitcoinCashJsModule');
|
||||
module('bitcoincom.services');
|
||||
module('bwcModule');
|
||||
|
||||
inject(function($injector){
|
||||
bitcoinUriService = $injector.get('bitcoinUriService');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Bitcoin BIP72', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:?r=https://bitpay.com/i/CwzbKP3k3JNgXJBfuoerDr');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.isTestnet).toBeUndefined();
|
||||
expect(parsed.publicAddress).toBeUndefined();
|
||||
expect(parsed.url).toBe('https://bitpay.com/i/CwzbKP3k3JNgXJBfuoerDr');
|
||||
});
|
||||
|
||||
it('Bitcoin Cash BIP72', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:?r=https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress).toBeUndefined();
|
||||
expect(parsed.isTestnet).toBeUndefined();
|
||||
expect(parsed.url).toBe('https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu');
|
||||
});
|
||||
|
||||
it('Bitcoin Cash prefix with legacy address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:1G9FA9fFnHfTYxvmXeAbBD9FwzPAVMbd3j');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.legacy).toBe('1G9FA9fFnHfTYxvmXeAbBD9FwzPAVMbd3j');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash prefix with legacy address on testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:mkDQrKfSFD441JxrD1iPBsJFExgkvrPGQn');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.legacy).toBe('mkDQrKfSFD441JxrD1iPBsJFExgkvrPGQn');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash uri with extended params', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qr8v2vqnzntykakht43rqmxq8cdjzjp795fc3vsjgc?unknown=something&mystery=Melton%20probang&req-one=ichi&req-beta=Ni%20san');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.others.mystery).toBe('Melton probang');
|
||||
expect(parsed.others.unknown).toBe('something');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qr8v2vqnzntykakht43rqmxq8cdjzjp795fc3vsjgc');
|
||||
expect(parsed.req['req-beta']).toBe('Ni san');
|
||||
expect(parsed.req['req-one']).toBe('ichi');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin Cash uri with invalid amount', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qq0knhwj4d5zy3kdph24w6etq58vwzua6sm7lhcmuk?amount=three');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('Bitcoin testnet address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBeUndefined();
|
||||
expect(parsed.publicAddress.legacy).toBe('mtWcoToWhbtPoCby5fvs8xdBujT5GGenD4');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('Bitcoin uri', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:15yCdKWVKRvfXMJpPYZBqMhiGKwjKzZdLN');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('15yCdKWVKRvfXMJpPYZBqMhiGKwjKzZdLN');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with encoded label', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:1MxudKDEBWZ1yjizUSf6htacenNtb3DWbT?label=Mr.%20Smith');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.label).toBe('Mr. Smith');
|
||||
expect(parsed.publicAddress.legacy).toBe('1MxudKDEBWZ1yjizUSf6htacenNtb3DWbT');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with params', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:12nCRhMDfxVnuF3uYMXv2fNxBohNmacfWu?amount=20.3&label=Luke-Jr&message=Donation%20for%20project%20xyz');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.amount).toBe('20.3');
|
||||
expect(parsed.amountInSatoshis).toBe(2030000000);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.label).toBe('Luke-Jr');
|
||||
expect(parsed.publicAddress.legacy).toBe('12nCRhMDfxVnuF3uYMXv2fNxBohNmacfWu');
|
||||
expect(parsed.message).toBe('Donation for project xyz');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with slash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin:/1GhpYmbRaf73AZRxDwAGr6653iZBGzdgeA');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('1GhpYmbRaf73AZRxDwAGr6653iZBGzdgeA');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with slashes', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin://18PCPhgZJjLxe9g3Q1BXLpL5aVut1fW3aX');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('18PCPhgZJjLxe9g3Q1BXLpL5aVut1fW3aX');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitcoin uri with space', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin: 19cPoKU5ZazY8NkLEsxK7drBqJnpGkax3d');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('btc');
|
||||
expect(parsed.publicAddress.legacy).toBe('19cPoKU5ZazY8NkLEsxK7drBqJnpGkax3d');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('Bitpay without prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('CJoRov8TirekvajiimQpb5Hk95evA7H2Yz');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.bitpay).toBe('CJoRov8TirekvajiimQpb5Hk95evA7H2Yz');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('legacy address', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBeUndefined();
|
||||
expect(parsed.publicAddress.legacy).toBe('1JXeGEu7bNEAYu6URT6dU6g1Ys6ffSAWYW');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr testnet with prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bchtest:qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('cashAddr uppercase', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('BITCOINCASH:QZZG9NMC5VX8GAP6XFATX3TWNSDN2YRMCSSULSMY44');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qzzg9nmc5vx8gap6xfatx3twnsdn2yrmcssulsmy44');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with dash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoin-cash:qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qrq9p82a247lecv08ldk5p5h6ahtnjzpqcnh8yhq92');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qrq9p82a247lecv08ldk5p5h6ahtnjzpqcnh8yhq92');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with slash', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:/qzdectfmuw0xxztfx7mh045830dqcshj85hr44l35a');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qzdectfmuw0xxztfx7mh045830dqcshj85hr44l35a');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with slashes', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash://qpj966w8utue75lqqq3rlgh20zkz3rmydqpq8syv9c');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpj966w8utue75lqqq3rlgh20zkz3rmydqpq8syv9c');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('cashAddr with space', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bitcoincash: qpar9ldle8z6alcwgclejdhc24ha2xrg0szs5802ce');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qpar9ldle8z6alcwgclejdhc24ha2xrg0szs5802ce');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('cashAddr with space on testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('bchtest: qqjxkmtaxk4nv6w9h5ht2fjcj9c7ruh0fu7cnxsx5j');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qqjxkmtaxk4nv6w9h5ht2fjcj9c7ruh0fu7cnxsx5j');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('cashAddr without prefix', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('qqen2y3l28dpk0dzsag8w027ds96u7z4pc0uxtl0nq');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.coin).toBe('bch');
|
||||
expect(parsed.publicAddress.cashAddr).toBe('qqen2y3l28dpk0dzsag8w027ds96u7z4pc0uxtl0nq');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('copay invitation', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('PD5B7rEEj72st9d5nFszyuKxJP6FAGS7idVC2SMqiMxUcWVd8JifZDJw1UgjUctxefUFE3Sz6qLbch');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.copayInvitation).toBe('PD5B7rEEj72st9d5nFszyuKxJP6FAGS7idVC2SMqiMxUcWVd8JifZDJw1UgjUctxefUFE3Sz6qLbch');
|
||||
});
|
||||
|
||||
|
||||
it ('import BCH wallet no password', function() {
|
||||
var parsed = bitcoinUriService.parse("1|suggest route obvious broccoli good position hidden tone history around final lobster|livenet|m/44'/0'/0'|false");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('suggest route obvious broccoli good position hidden tone history around final lobster');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/0'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(false);
|
||||
});
|
||||
|
||||
it ('import BCH wallet with passphrase', function() {
|
||||
var parsed = bitcoinUriService.parse("1|fringe hazard all hobby trap myth fire stand sock empty soon east|livenet|m/44'/0'/0'|true");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('fringe hazard all hobby trap myth fire stand sock empty soon east');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/0'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(true);
|
||||
});
|
||||
|
||||
it ('import BTC wallet testnet', function() {
|
||||
// From copay
|
||||
var parsed = bitcoinUriService.parse("1|cat wealth column firm wet sauce tornado era feature monster click eyebrow|testnet|m/44'/1'/0'|false|btc");
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.import.type).toBe(1);
|
||||
expect(parsed.import.data).toBe('cat wealth column firm wet sauce tornado era feature monster click eyebrow');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
expect(parsed.import.derivationPath).toBe("m/44'/1'/0'");
|
||||
expect(parsed.import.hasPassphrase).toBe(false);
|
||||
});
|
||||
|
||||
// Invalid addresses from https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md
|
||||
it('invalid cashAddr style 1', function() {
|
||||
var parsed = bitcoinUriService.parse('prefix:x64nx6hz');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 2', function() {
|
||||
var parsed = bitcoinUriService.parse('p:gpf8m4h7');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 3', function() {
|
||||
var parsed = bitcoinUriService.parse('bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 4', function() {
|
||||
var parsed = bitcoinUriService.parse('bchtest:testnetaddress4d6njnut');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('invalid cashAddr style 5', function() {
|
||||
var parsed = bitcoinUriService.parse('bchreg:555555555555555555555555555555555555555555555udxmlmrz');
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('non-string', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse([1, 2, 3, 4]);
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key encrypted with BIP38', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('6PRN5nEDmX842gsBzJryPu8Tw5kcsaQq1GPLcjVQPcEStvbFAtz11JX9pX');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.encrypted).toBe('6PRN5nEDmX842gsBzJryPu8Tw5kcsaQq1GPLcjVQPcEStvbFAtz11JX9pX');
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey mainnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey mainnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTu');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMm');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('private key for compressed pubkey testnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('cNJFgo1driFnPcBdBX8BrJrpxchBWXwXCvNH5SoSkdcF6JXXwHMM');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey mainnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTMwx');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTMwx');
|
||||
expect(parsed.isTestnet).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey mainnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('L18V3rAhCKEioPnJ4BHLCCsaYa8eSNFrMjNQ2EdwgeAdmBSnTTwx');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey testnet', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcc');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.privateKey.wif).toBe('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcc');
|
||||
expect(parsed.isTestnet).toBe(true);
|
||||
});
|
||||
|
||||
it('private key for uncompressed pubkey testnet with wrong checksum', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('92Pg46rUhgTT7romnV7iGW6W1gbGdeezqdbJCzShkCsYNzyyNcC');
|
||||
|
||||
expect(parsed.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('URL only, http', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('http://paperwallet.bitcoin.com');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.bareUrl).toBe('http://paperwallet.bitcoin.com');
|
||||
});
|
||||
|
||||
it('URL only, https with query', function() {
|
||||
|
||||
var parsed = bitcoinUriService.parse('https://purse.io/?one=two&three=four');
|
||||
|
||||
expect(parsed.isValid).toBe(true);
|
||||
expect(parsed.bareUrl).toBe('https://purse.io/?one=two&three=four');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -85,7 +85,7 @@ angular.module('copayApp.services').factory('bitcoincomService', function(gettex
|
|||
};
|
||||
|
||||
var register = function() {
|
||||
if (!platformInfo.isAndroid) {
|
||||
if (!platformInfo.isAndroid) { // To comply with Google Play policies
|
||||
nextStepsService.register(cashGamesItem);
|
||||
}
|
||||
nextStepsService.register(newsItem);
|
||||
|
|
|
|||
79
src/js/services/incoming-data.service.js
Normal file
79
src/js/services/incoming-data.service.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* incomingDataService is an intermediate to redirect either to the sendFlow
|
||||
* or to import/join a wallet.
|
||||
*/
|
||||
angular.module('copayApp.services').factory('incomingDataService', function(bitcoinUriService, $log, $state, $rootScope, scannerService, sendFlowService, gettextCatalog) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.showMenu = function(data) {
|
||||
$rootScope.$broadcast('incomingDataMenu.showMenu', data);
|
||||
};
|
||||
|
||||
root.redir = function(data, cbError) {
|
||||
var parsed = bitcoinUriService.parse(data);
|
||||
|
||||
console.log(parsed);
|
||||
$log.debug(parsed);
|
||||
|
||||
|
||||
if (parsed.isValid) {
|
||||
if (parsed.isTestnet) {
|
||||
if (cbError) {
|
||||
var errorMessage = gettextCatalog.getString('Testnet is not supported.');
|
||||
cbError(new Error(errorMessage));
|
||||
}
|
||||
} else {
|
||||
scannerService.pausePreview();
|
||||
|
||||
/**
|
||||
* Strategy for the action
|
||||
*/
|
||||
if (parsed.copayInvitation) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
} else if (parsed.import) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.import', {
|
||||
code: data
|
||||
});
|
||||
});
|
||||
} else if (
|
||||
!parsed.isValid
|
||||
|| parsed.privateKey
|
||||
|| (sendFlowService.state.isEmpty() && !parsed.url && !parsed.amount)
|
||||
) {
|
||||
root.showMenu({
|
||||
original: data,
|
||||
parsed: parsed
|
||||
});
|
||||
} else {
|
||||
var state = sendFlowService.state.getClone();
|
||||
state.data = data;
|
||||
|
||||
sendFlowService.start(state, function onError(err) {
|
||||
/**
|
||||
* OnError, open the menu (link not validated)
|
||||
*/
|
||||
root.showMenu({
|
||||
original: data,
|
||||
parsed: parsed
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cbError) {
|
||||
var errorMessage = gettextCatalog.getString('Data not recognised.');
|
||||
cbError(new Error(errorMessage));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
@ -1,475 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, sendFlowService, appConfigService, popupService, gettextCatalog, bitcoinCashJsService) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.showMenu = function(data) {
|
||||
$rootScope.$broadcast('incomingDataMenu.showMenu', data);
|
||||
};
|
||||
|
||||
root.redir = function(data, serviceId, serviceData) {
|
||||
var originalAddress = null;
|
||||
var noPrefixInAddress = 0;
|
||||
|
||||
if (data.toLowerCase().indexOf('bitcoin') < 0) {
|
||||
noPrefixInAddress = 1;
|
||||
}
|
||||
|
||||
if (typeof(data) == 'string' && !(/^bitcoin(cash)?:\?r=[\w+]/).exec(data) && (data.toLowerCase().indexOf('bitcoincash:') >= 0 || data[0] == 'q' || data[0] == 'p' || data[0] == 'C' || data[0] == 'H')) {
|
||||
try {
|
||||
noPrefixInAddress = 0;
|
||||
|
||||
if (data[0] == 'p' || data[0] == 'q') {
|
||||
data = 'bitcoincash:' + data;
|
||||
}
|
||||
var paramString = '';
|
||||
if (data.indexOf('?') >= 0) {
|
||||
paramString = data.substring(data.indexOf('?'));
|
||||
data = data.substring(0, data.indexOf('?'));
|
||||
}
|
||||
|
||||
if (data.indexOf('BITCOINCASH:') >= 0) {
|
||||
data = data.toLowerCase();
|
||||
}
|
||||
originalAddress = data.replace('bitcoincash:', '');
|
||||
var legacyAddress = bitcoinCashJsService.readAddress(data).legacy;
|
||||
data = 'bitcoincash:' + legacyAddress + paramString;
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
$log.debug('Processing incoming data: ' + data);
|
||||
|
||||
function sanitizeUri(data) {
|
||||
// Fixes when a region uses comma to separate decimals
|
||||
var regex = /[\?\&]amount=(\d+([\,\.]\d+)?)/i;
|
||||
var match = regex.exec(data);
|
||||
if (!match || match.length === 0) {
|
||||
return data;
|
||||
}
|
||||
var value = match[0].replace(',', '.');
|
||||
var newUri = data.replace(regex, value);
|
||||
|
||||
// mobile devices, uris like copay://glidera
|
||||
newUri.replace('://', ':');
|
||||
|
||||
return newUri;
|
||||
}
|
||||
|
||||
function getParameterByName(name, url) {
|
||||
if (!url) return;
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
function checkPrivateKey(privateKey) {
|
||||
try {
|
||||
new bitcore.PrivateKey(privateKey, 'livenet');
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function goSend(addr, amount, message, coin, serviceId, serviceData) {
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
});
|
||||
// Timeout is required to enable the "Back" button
|
||||
$timeout(function() {
|
||||
var params = sendFlowService.getStateClone();
|
||||
|
||||
if (amount) {
|
||||
params.amount = amount;
|
||||
}
|
||||
|
||||
if (addr) {
|
||||
params.toAddress = addr;
|
||||
params.displayAddress = originalAddress ? originalAddress : addr;
|
||||
}
|
||||
|
||||
if (coin) {
|
||||
params.coin = coin;
|
||||
}
|
||||
|
||||
if (noPrefixInAddress) {
|
||||
params.noPrefixInAddress = noPrefixInAddress;
|
||||
}
|
||||
|
||||
if (serviceId) {
|
||||
params.thirdParty = [];
|
||||
params.thirdParty.id = serviceId;
|
||||
params.thirdParty.data = serviceData;
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.amount');
|
||||
} else {
|
||||
sendFlowService.pushState(params);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
// data extensions for Payment Protocol with non-backwards-compatible request
|
||||
if ((/^bitcoin(cash)?:\?r=[\w+]/).exec(data)) {
|
||||
var coin = data.indexOf('bitcoincash') >= 0 ? 'bch' : 'btc';
|
||||
data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, ''));
|
||||
if (coin == 'bch') {
|
||||
payproService.getPayProDetailsViaHttp(data, function onGetPayProDetailsViaHttp(err, details) {
|
||||
if (err) {
|
||||
var message = err.toString();
|
||||
if (typeof err.data === 'string') {
|
||||
// i.e. 'This invoice is no longer accepting payments'
|
||||
message = gettextCatalog.getString(err.data);
|
||||
}
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), message)
|
||||
} else {
|
||||
handlePayPro(details, coin);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
payproService.getPayProDetails(data, coin, function onGetPayProDetails(err, details) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else {
|
||||
handlePayPro(details, coin);
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
data = sanitizeUri(data);
|
||||
|
||||
// Bitcoin URL
|
||||
if (bitcore.URI.isValid(data)) {
|
||||
var coin = 'btc';
|
||||
var parsed = new bitcore.URI(data);
|
||||
|
||||
var addr = parsed.address ? parsed.address.toString() : '';
|
||||
var message = parsed.message;
|
||||
|
||||
var amount = parsed.amount ? parsed.amount : '';
|
||||
|
||||
if (parsed.r) {
|
||||
payproService.getPayProDetails(parsed.r, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount) goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
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, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount)
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
}
|
||||
handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
return true;
|
||||
|
||||
// Cash URI with bitcoin (btc) 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, coin, function(err, details) {
|
||||
if (err) {
|
||||
if (addr && amount)
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
else
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
}
|
||||
handlePayPro(details, coin);
|
||||
});
|
||||
} else {
|
||||
goSend(addr, amount, message, coin, serviceId, serviceData);
|
||||
}
|
||||
}
|
||||
);
|
||||
return true;
|
||||
// Plain URL
|
||||
} else if (/^https?:\/\//.test(data)) {
|
||||
payproService.getPayProDetails(data, coin, function(err, details) {
|
||||
if (err) {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'url'
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
handlePayPro(details);
|
||||
return true;
|
||||
});
|
||||
// Plain Address
|
||||
} else if (bitcore.Address.isValid(data, 'livenet') || bitcore.Address.isValid(data, 'testnet')) {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'bitcoinAddress'
|
||||
});
|
||||
} 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({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.transitionTo('tabs.buyandsell.glidera', {
|
||||
code: code
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
} else if (data && data.indexOf(appConfigService.name + '://coinbase') === 0) {
|
||||
var code = getParameterByName('code', data);
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.transitionTo('tabs.buyandsell.coinbase', {
|
||||
code: code
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
// BitPayCard Authentication
|
||||
} else if (data && data.indexOf(appConfigService.name + '://') === 0) {
|
||||
|
||||
// Disable BitPay Card
|
||||
if (!appConfigService._enabledExtensions.debitcard) return false;
|
||||
|
||||
var secret = getParameterByName('secret', data);
|
||||
var email = getParameterByName('email', data);
|
||||
var otp = getParameterByName('otp', data);
|
||||
var reason = getParameterByName('r', data);
|
||||
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
switch (reason) {
|
||||
default:
|
||||
case '0':
|
||||
/* For BitPay card binding */
|
||||
$state.transitionTo('tabs.bitpayCardIntro', {
|
||||
secret: secret,
|
||||
email: email,
|
||||
otp: otp
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
// Join
|
||||
} else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
// Old join
|
||||
} else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
|
||||
$state.go('tabs.home', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.home' ? false : true
|
||||
}).then(function() {
|
||||
$state.transitionTo('tabs.add.join', {
|
||||
url: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
} else if (data && (data.substring(0, 2) == '6P' || checkPrivateKey(data))) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'privateKey'
|
||||
});
|
||||
} else if (data && ((data.substring(0, 2) == '1|') || (data.substring(0, 2) == '2|') || (data.substring(0, 2) == '3|'))) {
|
||||
$state.go('tabs.home').then(function() {
|
||||
$state.transitionTo('tabs.add.import', {
|
||||
code: data
|
||||
});
|
||||
});
|
||||
return true;
|
||||
|
||||
} else {
|
||||
if ($state.includes('tabs.scan')) {
|
||||
root.showMenu({
|
||||
data: data,
|
||||
type: 'text'
|
||||
});
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
function goToAmountPage(toAddress, coin) {
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
});
|
||||
$timeout(function() {
|
||||
var stateParams = {
|
||||
toAddress: toAddress,
|
||||
displayAddress: toAddress,
|
||||
coin: coin,
|
||||
noPrefix: 1
|
||||
};
|
||||
sendFlowService.pushState(stateParams);
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function handlePayPro(payProData, coin) {
|
||||
|
||||
console.log(payProData);
|
||||
|
||||
var toAddr = payProData.toAddress;
|
||||
var amount = payProData.amount;
|
||||
var paymentUrl = payProData.url;
|
||||
var expires = payProData.expires;
|
||||
var time = payProData.time;
|
||||
|
||||
if (coin === 'bch') {
|
||||
var displayAddr = payProData.outputs[0].address;
|
||||
toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy;
|
||||
amount = payProData.outputs[0].amount;
|
||||
paymentUrl = payProData.paymentUrl;
|
||||
expires = Math.floor(new Date(expires).getTime() / 1000)
|
||||
time = Math.ceil(new Date(time).getTime() / 1000)
|
||||
}
|
||||
|
||||
var name = payProData.domain;
|
||||
|
||||
if (payProData.memo.indexOf('eGifter') > -1) {
|
||||
name = 'eGifter'
|
||||
} else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
|
||||
name = 'BitPay';
|
||||
}
|
||||
|
||||
var thirdPartyData = {
|
||||
id: 'bip70',
|
||||
amount: amount,
|
||||
caTrusted: true,
|
||||
name: name,
|
||||
domain: payProData.domain,
|
||||
expires: expires,
|
||||
memo: payProData.memo,
|
||||
network: 'livenet',
|
||||
requiredFeeRate: payProData.requiredFeeRate,
|
||||
selfSigned: 0,
|
||||
time: time,
|
||||
displayAddress: displayAddr,
|
||||
toAddress: toAddr,
|
||||
url: paymentUrl,
|
||||
verified: true
|
||||
};
|
||||
|
||||
var stateParams = {
|
||||
amount: thirdPartyData.amount,
|
||||
toAddress: thirdPartyData.toAddress,
|
||||
coin: coin,
|
||||
thirdParty: thirdPartyData
|
||||
};
|
||||
|
||||
// fee
|
||||
if (thirdPartyData.requiredFeeRate) {
|
||||
stateParams.requiredFeeRate = thirdPartyData.requiredFeeRate * 1024;
|
||||
}
|
||||
|
||||
// This does not make sense, thirdPartyData gets added by stateParams below
|
||||
//sendFlowService.pushState(thirdPartyData);
|
||||
|
||||
scannerService.pausePreview();
|
||||
$state.go('tabs.send', {}, {
|
||||
'reload': true,
|
||||
'notify': $state.current.name == 'tabs.send' ? false : true
|
||||
}).then(function() {
|
||||
$timeout(function() {
|
||||
sendFlowService.pushState(stateParams); // Need to do more here
|
||||
$state.transitionTo('tabs.send.origin');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return root;
|
||||
});
|
||||
180
src/js/services/latest-release.service.js
Normal file
180
src/js/services/latest-release.service.js
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
'use strict';
|
||||
|
||||
(function() {
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('latestReleaseService', latestReleaseService);
|
||||
|
||||
function latestReleaseService($log, $http, $ionicPopup, configService, externalLinkService, gettextCatalog, platformInfo) {
|
||||
|
||||
var service = {
|
||||
// Functions
|
||||
checkLatestRelease: checkLatestRelease,
|
||||
requestLatestRelease: requestLatestRelease,
|
||||
showUpdatePopup: showUpdatePopup
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function checkLatestRelease(cb) {
|
||||
var releaseURL = configService.getDefaults().release.url;
|
||||
|
||||
requestLatestRelease(releaseURL, function (err, releaseData) {
|
||||
if (err) return cb(err);
|
||||
var currentVersion = window.version;
|
||||
var latestVersion = releaseData.tag_name;
|
||||
|
||||
if (!verifyTagFormat(currentVersion))
|
||||
return cb('Cannot verify the format of version tag: ' + currentVersion);
|
||||
if (!verifyTagFormat(latestVersion))
|
||||
return cb('Cannot verify the format of latest release tag: ' + latestVersion);
|
||||
|
||||
var current = formatTagNumber(currentVersion);
|
||||
var latest = formatTagNumber(latestVersion);
|
||||
|
||||
if (latest.major < current.major || (latest.major === current.major && latest.minor <= current.minor)) {
|
||||
return cb(null, false);
|
||||
}
|
||||
|
||||
var releaseSearchTerm = "";
|
||||
if (platformInfo.isNW) { // XX SP: DESKTOP: Check if the latest release is already available for current OS
|
||||
var platform = process.platform;
|
||||
if (platform === "darwin") {
|
||||
releaseSearchTerm = "osx";
|
||||
} else if (platform === "win32") {
|
||||
releaseSearchTerm = "win";
|
||||
} else if (platform === "linux") {
|
||||
releaseSearchTerm = "linux";
|
||||
}
|
||||
var foundNewVersion = false;
|
||||
for (var i in releaseData.assets) {
|
||||
if (releaseData.assets[i].name.indexOf(releaseSearchTerm) !== -1) {
|
||||
foundNewVersion = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$log.debug('A new version is available: ' + latestVersion);
|
||||
|
||||
var releaseNotes = false;
|
||||
if (releaseData.body) {
|
||||
var releaseLines = releaseData.body.split('\n');
|
||||
for (var lineNum in releaseLines) {
|
||||
if (releaseLines[lineNum].substring(0, 2) === "# ") {
|
||||
releaseLines[lineNum] = "<strong>" + releaseLines[lineNum].substring(2) + "</strong>";
|
||||
} else if (releaseLines[lineNum].substring(0, 2) === "- ") {
|
||||
releaseLines[lineNum] = "• " + releaseLines[lineNum].substring(2);
|
||||
}
|
||||
}
|
||||
releaseNotes = releaseLines.join('\n');
|
||||
}
|
||||
|
||||
return cb(null, {latestVersion: latestVersion, releaseNotes: releaseNotes});
|
||||
});
|
||||
|
||||
function verifyTagFormat(tag) {
|
||||
var regex = /^v?\d+\.\d+(\.\d+)?(-rc\d)?$/i;
|
||||
return regex.exec(tag);
|
||||
}
|
||||
|
||||
function formatTagNumber(tag) {
|
||||
var label = false;
|
||||
if (tag.split("-")[1]) { // Move postfixes like "-rc2" to a variable
|
||||
label = tag.split("-")[1];
|
||||
tag = tag.split("-")[0];
|
||||
}
|
||||
|
||||
var formattedNumber = tag.replace(/^v/i, '').split('.');
|
||||
return {
|
||||
major: +(formattedNumber[0] ? +formattedNumber[0] : 0),
|
||||
minor: +(formattedNumber[1] ? +formattedNumber[1] : 0),
|
||||
patch: +(formattedNumber[2] ? +formattedNumber[2] : 0),
|
||||
label: label /* XX SP: Maybe we can use this in a later stage (with for example 1.0.0-rc2 the value will be "rc2" and false if there is no label) */
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function requestLatestRelease(releaseURL, cb) {
|
||||
$log.debug('Retrieving latest release information...');
|
||||
|
||||
var request = {
|
||||
url: releaseURL,
|
||||
method: 'GET',
|
||||
json: true
|
||||
};
|
||||
|
||||
$http(request).then(function (release) {
|
||||
$log.debug('Latest release: ' + release.data.name);
|
||||
return cb(null, release.data);
|
||||
}, function (err) {
|
||||
return cb('Cannot get the release information: ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
function showUpdatePopup() {
|
||||
var buttons = [];
|
||||
|
||||
if (!platformInfo.isIOS) { // There is no GitHub-release for iPhone
|
||||
buttons.push({
|
||||
text: "GitHub",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (platformInfo.isAndroid) {
|
||||
buttons.unshift({
|
||||
text: "Google Play Store",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://play.google.com/store/apps/details?id=com.bitcoin.mwallet';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (platformInfo.isIOS) {
|
||||
buttons.unshift({
|
||||
text: "App Store",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://itunes.apple.com/app/id1252903728';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
} else if (platformInfo.isNW) {
|
||||
if (process.platform === 'darwin') {
|
||||
buttons.unshift({
|
||||
text: "Mac App Store",
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
var url = 'https://itunes.apple.com/app/bitcoin-com-wallet/id1383072453';
|
||||
externalLinkService.open(url, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (buttons.length === 1) { // There is only one source to download (probably on desktop, so open GitHub release page..)
|
||||
buttons[0].onTap();
|
||||
} else {
|
||||
buttons.push({
|
||||
text: gettextCatalog.getString('Go Back'),
|
||||
type: 'button-positive',
|
||||
onTap: function () {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
$ionicPopup.show({
|
||||
title: gettextCatalog.getString('Update Available'),
|
||||
subTitle: gettextCatalog.getString('An update to this app is available. For your security, please update to the latest version.'),
|
||||
cssClass: 'popup-update',
|
||||
buttons: buttons
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
'use strict';
|
||||
angular.module('copayApp.services')
|
||||
.factory('latestReleaseService', function latestReleaseServiceFactory($log, $http, configService) {
|
||||
|
||||
var root = {};
|
||||
|
||||
root.checkLatestRelease = function(cb) {
|
||||
var releaseURL = configService.getDefaults().release.url;
|
||||
|
||||
requestLatestRelease(releaseURL, function(err, release) {
|
||||
if (err) return cb(err);
|
||||
var currentVersion = window.version;
|
||||
var latestVersion = release.data.tag_name;
|
||||
|
||||
if (!verifyTagFormat(currentVersion))
|
||||
return cb('Cannot verify the format of version tag: ' + currentVersion);
|
||||
if (!verifyTagFormat(latestVersion))
|
||||
return cb('Cannot verify the format of latest release tag: ' + latestVersion);
|
||||
|
||||
var current = formatTagNumber(currentVersion);
|
||||
var latest = formatTagNumber(latestVersion);
|
||||
|
||||
if (latest.major < current.major || (latest.major == current.major && latest.minor <= current.minor))
|
||||
return cb(null, false);
|
||||
|
||||
$log.debug('A new version is available: ' + latestVersion);
|
||||
return cb(null, true);
|
||||
});
|
||||
|
||||
function verifyTagFormat(tag) {
|
||||
var regex = /^v?\d+\.\d+\.\d+$/i;
|
||||
return regex.exec(tag);
|
||||
};
|
||||
|
||||
function formatTagNumber(tag) {
|
||||
var formattedNumber = tag.replace(/^v/i, '').split('.');
|
||||
return {
|
||||
major: +formattedNumber[0],
|
||||
minor: +formattedNumber[1],
|
||||
patch: +formattedNumber[2]
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function requestLatestRelease(releaseURL, cb) {
|
||||
$log.debug('Retrieving latest relsease information...');
|
||||
|
||||
var request = {
|
||||
url: releaseURL,
|
||||
method: 'GET',
|
||||
json: true
|
||||
};
|
||||
|
||||
$http(request).then(function(release) {
|
||||
$log.debug('Latest release: ' + release.data.name);
|
||||
return cb(null, release);
|
||||
}, function(err) {
|
||||
return cb('Cannot get the release information: ' + err);
|
||||
});
|
||||
};
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
@ -52,11 +52,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
|
|||
|
||||
root.clear = function() {
|
||||
ongoingProcess = {};
|
||||
if (isCordova && !isWindowsPhoneApp) {
|
||||
window.plugins.spinnerDialog.hide();
|
||||
} else {
|
||||
$ionicLoading.hide();
|
||||
}
|
||||
$ionicLoading.hide();
|
||||
};
|
||||
|
||||
root.get = function(processName) {
|
||||
|
|
@ -78,28 +74,21 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
|
|||
root.onGoingProcessName = name;
|
||||
|
||||
var showName = $filter('translate')(processNames[name] || name);
|
||||
|
||||
if (root.onGoingProcessName) {
|
||||
var tmpl;
|
||||
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 {
|
||||
$ionicLoading.hide();
|
||||
}
|
||||
|
||||
if (customHandler) {
|
||||
customHandler(processName, showName, isOn);
|
||||
} else if (root.onGoingProcessName) {
|
||||
if (isCordova && !isWindowsPhoneApp) {
|
||||
window.plugins.spinnerDialog.show(null, showName, root.clear);
|
||||
} else {
|
||||
|
||||
var tmpl;
|
||||
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 && !isWindowsPhoneApp) {
|
||||
window.plugins.spinnerDialog.hide();
|
||||
} else {
|
||||
$ionicLoading.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return root;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('openURLService', function($rootScope, $ionicHistory, $document, $log, $state, platformInfo, lodash, profileService, incomingData, appConfigService) {
|
||||
angular.module('copayApp.services').factory('openURLService', function($rootScope, $ionicHistory, $document, $log, $state, platformInfo, lodash, profileService, incomingDataService, appConfigService) {
|
||||
var root = {};
|
||||
|
||||
var handleOpenURL = function(args) {
|
||||
|
|
@ -23,9 +23,12 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop
|
|||
|
||||
document.addEventListener('handleopenurl', handleOpenURL, false);
|
||||
|
||||
if (!incomingData.redir(url)) {
|
||||
$log.warn('Unknown URL! : ' + url);
|
||||
}
|
||||
incomingDataService.redir(url, function onError(err) {
|
||||
if (err) {
|
||||
$log.warn('Unknown URL! : ' + url);
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var handleResume = function() {
|
||||
|
|
|
|||
85
src/js/services/send-flow-router.service.js
Normal file
85
src/js/services/send-flow-router.service.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowRouterService', sendFlowRouterService);
|
||||
|
||||
function sendFlowRouterService(
|
||||
sendFlowStateService
|
||||
, $state, $ionicHistory, $timeout
|
||||
) {
|
||||
|
||||
var service = {
|
||||
// Functions
|
||||
start: start,
|
||||
goNext: goNext,
|
||||
goBack: goBack,
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/**
|
||||
* Start new send flow
|
||||
*/
|
||||
function start() {
|
||||
var state = sendFlowStateService.state;
|
||||
|
||||
if (state.isRequestAmount) {
|
||||
$state.go('tabs.paymentRequest.amount');
|
||||
} else {
|
||||
if ($state.current.name != 'tabs.send') {
|
||||
$state.go('tabs.home').then(function () {
|
||||
$ionicHistory.clearHistory();
|
||||
$state.go('tabs.send').then(function () {
|
||||
$timeout(function () {
|
||||
goNext();
|
||||
}, 60);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
goNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the next page
|
||||
* Routing strategy : https://bitcoindotcom.atlassian.net/wiki/x/BQDWKQ
|
||||
*/
|
||||
function goNext() {
|
||||
var state = sendFlowStateService.state;
|
||||
|
||||
var needsDestination = !state.toWalletId && !state.toAddress;
|
||||
var needsOrigin = !state.fromWalletId;
|
||||
var needsAmount = !state.amount && !state.sendMax;
|
||||
|
||||
if (needsDestination) {
|
||||
if (!state.isWalletTransfer && !state.thirdParty) {
|
||||
$state.go('tabs.send');
|
||||
return;
|
||||
} else if (!needsOrigin) {
|
||||
$state.go('tabs.send.destination');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsOrigin) {
|
||||
$state.go('tabs.send.origin');
|
||||
} else if (needsAmount) {
|
||||
$state.go('tabs.send.amount');
|
||||
} else {
|
||||
$state.go('tabs.send.review');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the previous page
|
||||
*/
|
||||
function goBack() {
|
||||
$ionicHistory.goBack();
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
142
src/js/services/send-flow-state.service.js
Normal file
142
src/js/services/send-flow-state.service.js
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowStateService', sendFlowStateService);
|
||||
|
||||
function sendFlowStateService($log) {
|
||||
|
||||
var service = {
|
||||
// Variables
|
||||
state: {
|
||||
amount: 0,
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: '',
|
||||
coin: '',
|
||||
isRequestAmount: false,
|
||||
isWalletTransfer: false
|
||||
},
|
||||
previousStates: [],
|
||||
|
||||
// Functions
|
||||
init: init,
|
||||
clear: clear,
|
||||
getClone: getClone,
|
||||
map: map,
|
||||
pop: pop,
|
||||
push: push,
|
||||
isEmpty: isEmpty
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/**
|
||||
* Init state & stack
|
||||
* @param {Object} params
|
||||
*/
|
||||
function init(params) {
|
||||
$log.debug("send-flow-state init()");
|
||||
|
||||
clear();
|
||||
|
||||
if (params) {
|
||||
push(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a state & stack
|
||||
*/
|
||||
function clear() {
|
||||
$log.debug("send-flow-state clear()");
|
||||
|
||||
clearCurrent();
|
||||
service.previousStates = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear current state only
|
||||
*/
|
||||
function clearCurrent() {
|
||||
$log.debug("send-flow-state clearCurrent()");
|
||||
|
||||
service.state = {
|
||||
amount: 0,
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: '',
|
||||
coin: '',
|
||||
isRequestAmount: false,
|
||||
isWalletTransfer: false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a clone of the current state
|
||||
*/
|
||||
function getClone() {
|
||||
var currentState = {};
|
||||
Object.keys(service.state).forEach(function forCurrentParam(key) {
|
||||
if (typeof service.state[key] !== 'function' && key !== 'previousStates') {
|
||||
currentState[key] = service.state[key];
|
||||
}
|
||||
});
|
||||
return currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in the current state from the params
|
||||
* @param {Object} params
|
||||
*/
|
||||
function map(params) {
|
||||
Object.keys(params).forEach(function forNewParam(key) {
|
||||
service.state[key] = params[key];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop state
|
||||
*/
|
||||
function pop() {
|
||||
$log.debug('send-flow-state pop');
|
||||
|
||||
if (service.previousStates.length) {
|
||||
var params = service.previousStates.pop();
|
||||
clearCurrent();
|
||||
map(params);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push state
|
||||
* @param {Object} params
|
||||
*/
|
||||
function push(params) {
|
||||
$log.debug('send-flow-state push');
|
||||
|
||||
var currentParams = getClone();
|
||||
service.previousStates.push(currentParams);
|
||||
clearCurrent();
|
||||
map(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is empty stack
|
||||
*/
|
||||
function isEmpty() {
|
||||
return service.previousStates.length == 0;
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
148
src/js/services/send-flow.service.js
Normal file
148
src/js/services/send-flow.service.js
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('sendFlowService', sendFlowService);
|
||||
|
||||
function sendFlowService(
|
||||
sendFlowStateService, sendFlowRouterService
|
||||
, bitcoinUriService, payproService, bitcoinCashJsService
|
||||
, popupService, gettextCatalog
|
||||
, $state, $log
|
||||
) {
|
||||
|
||||
var service = {
|
||||
// Variables
|
||||
state: sendFlowStateService,
|
||||
router: sendFlowRouterService,
|
||||
|
||||
// Functions
|
||||
start: start,
|
||||
goNext: goNext,
|
||||
goBack: goBack
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
/**
|
||||
* Start a new send flow
|
||||
* @param {Object} params
|
||||
* @param {Function} onError
|
||||
*/
|
||||
function start(params, onError) {
|
||||
$log.debug('send-flow start()');
|
||||
|
||||
if (params && params.data) {
|
||||
var res = bitcoinUriService.parse(params.data);
|
||||
|
||||
if (res.isValid) {
|
||||
|
||||
// If BIP70 (url)
|
||||
if (res.url) {
|
||||
var url = res.url;
|
||||
var coin = res.coin || '';
|
||||
payproService.getPayProDetails(url, coin, function onGetPayProDetails(err, payProData) {
|
||||
if (err) {
|
||||
popupService.showAlert(gettextCatalog.getString('Error'), err);
|
||||
} else {
|
||||
var name = payProData.domain;
|
||||
|
||||
// Detect some merchant that we know
|
||||
if (payProData.memo.indexOf('eGifter') > -1) {
|
||||
name = 'eGifter'
|
||||
} else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
|
||||
name = 'BitPay';
|
||||
}
|
||||
|
||||
// Init thirdParty
|
||||
var thirdPartyData = {
|
||||
id: 'bip70',
|
||||
caTrusted: true,
|
||||
name: name,
|
||||
domain: payProData.domain,
|
||||
expires: payProData.expires,
|
||||
memo: payProData.memo,
|
||||
network: 'livenet',
|
||||
requiredFeeRate: payProData.requiredFeeRate,
|
||||
selfSigned: 0,
|
||||
time: payProData.time,
|
||||
url: payProData.url,
|
||||
verified: true
|
||||
};
|
||||
|
||||
// Fill in params
|
||||
params.amount = payProData.amount,
|
||||
params.toAddress = payProData.toAddress,
|
||||
params.coin = coin,
|
||||
params.thirdParty = thirdPartyData
|
||||
}
|
||||
|
||||
// Resolve
|
||||
_next();
|
||||
});
|
||||
} else {
|
||||
if (res.coin) {
|
||||
params.coin = res.coin;
|
||||
}
|
||||
|
||||
if (res.amountInSatoshis) {
|
||||
params.amount = res.amountInSatoshis;
|
||||
}
|
||||
|
||||
if (res.publicAddress) {
|
||||
var prefix = res.isTestnet ? 'bchtest:' : 'bitcoincash:';
|
||||
params.displayAddress = res.publicAddress.cashAddr || res.publicAddress.legacy || res.publicAddress.bitpay;
|
||||
var formatAddress = res.publicAddress.cashAddr ? prefix + params.displayAddress : params.displayAddress;
|
||||
params.toAddress = bitcoinCashJsService.readAddress(formatAddress).legacy;
|
||||
}
|
||||
|
||||
_next();
|
||||
}
|
||||
} else {
|
||||
if (onError) {
|
||||
onError();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_next();
|
||||
}
|
||||
|
||||
|
||||
// Next used for sync the async task
|
||||
function _next() {
|
||||
sendFlowStateService.init(params);
|
||||
|
||||
// Routing strategy to -> send-flow-router.service
|
||||
sendFlowRouterService.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the next step
|
||||
* @param {Object} state
|
||||
*/
|
||||
function goNext(state) {
|
||||
$log.debug('send-flow goNext()');
|
||||
|
||||
// Save the current route before leaving
|
||||
state.route = $state.current.name;
|
||||
|
||||
// Save the state and redirect the user
|
||||
sendFlowStateService.push(state);
|
||||
sendFlowRouterService.goNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the previous step
|
||||
*/
|
||||
function goBack() {
|
||||
$log.debug('send-flow goBack()');
|
||||
|
||||
// Remove the state on top and redirect the user
|
||||
sendFlowStateService.pop();
|
||||
sendFlowRouterService.goBack();
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('copayApp.services')
|
||||
.factory('sendFlowService', sendFlowService);
|
||||
|
||||
function sendFlowService($log) {
|
||||
|
||||
var service = {
|
||||
// A separate state variable so we can ensure it is cleared of everything,
|
||||
// even other properties added that this service does not know about. (such as "coin")
|
||||
state: {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: ''
|
||||
},
|
||||
previousStates: [],
|
||||
|
||||
// Functions
|
||||
clear: clear,
|
||||
getStateClone: getStateClone,
|
||||
map: map,
|
||||
popState: popState,
|
||||
pushState: pushState,
|
||||
startSend: startSend
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function clear() {
|
||||
console.log("sendFlow clear()");
|
||||
clearCurrent();
|
||||
service.previousStates = [];
|
||||
}
|
||||
|
||||
function clearCurrent() {
|
||||
console.log("sendFlow clearCurrent()");
|
||||
service.state = {
|
||||
amount: '',
|
||||
displayAddress: null,
|
||||
fromWalletId: '',
|
||||
sendMax: false,
|
||||
thirdParty: null,
|
||||
toAddress: '',
|
||||
toWalletId: ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handy for debugging
|
||||
*/
|
||||
function getStateClone() {
|
||||
var currentState = {};
|
||||
Object.keys(service.state).forEach(function forCurrentParam(key) {
|
||||
if (typeof service.state[key] !== 'function' && key !== 'previousStates') {
|
||||
currentState[key] = service.state[key];
|
||||
}
|
||||
});
|
||||
return currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all previous state
|
||||
*/
|
||||
function startSend(params) {
|
||||
console.log('startSend()');
|
||||
clear();
|
||||
map(params);
|
||||
}
|
||||
|
||||
function map(params) {
|
||||
Object.keys(params).forEach(function forNewParam(key) {
|
||||
service.state[key] = params[key];
|
||||
});
|
||||
};
|
||||
|
||||
function popState() {
|
||||
console.log('sendFlow pop');
|
||||
if (service.previousStates.length) {
|
||||
var params = service.previousStates.pop();
|
||||
clearCurrent();
|
||||
map(params);
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
};
|
||||
|
||||
function pushState(params) {
|
||||
console.log('sendFlow push');
|
||||
var currentParams = getStateClone();
|
||||
service.previousStates.push(currentParams);
|
||||
clearCurrent();
|
||||
map(params);
|
||||
};
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -328,18 +328,23 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q)
|
|||
$scope.amount, $scope.withdrawalAddress,
|
||||
$scope.coinIn, $scope.coinOut
|
||||
);
|
||||
console.log('shapeshiftApiService.FixedAmountTx()');
|
||||
console.log(fixedTx);
|
||||
SSA.FixedAmountTx(fixedTx, function (data) {
|
||||
console.log(data)
|
||||
return promise.resolve({ fixedTxData : data.success });
|
||||
console.log(data);
|
||||
promise.resolve(data);
|
||||
});
|
||||
return promise.promise;
|
||||
},
|
||||
NormalTx : function($scope){
|
||||
var promise = $q.defer();
|
||||
var normalTx = SSA.CreateNormalTx($scope.withdrawalAddress, $scope.coinIn, $scope.coinOut);
|
||||
|
||||
console.log('shapeshiftApiService.NormalTx()');
|
||||
console.log(normalTx);
|
||||
SSA.NormalTx(normalTx, function (data) {
|
||||
promise.resolve({ normalTxData : data });
|
||||
console.log(data);
|
||||
promise.resolve(data);
|
||||
});
|
||||
return promise.promise;
|
||||
},
|
||||
|
|
@ -360,11 +365,12 @@ angular.module('copayApp.services').factory('shapeshiftApiService', function($q)
|
|||
return promise.promise;
|
||||
},
|
||||
ValidateAddress : function(address, coin) {
|
||||
var promise = $q.defer();
|
||||
SSA.ValidateAdddress(address, coin, function(data){
|
||||
promise.resolve(data);
|
||||
});
|
||||
return promise.promise;
|
||||
var promise = $q.defer();
|
||||
SSA.ValidateAdddress(address, coin, function onRequest(data){
|
||||
console.log(data);
|
||||
promise.resolve(data);
|
||||
});
|
||||
return promise.promise;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
112
src/js/services/shapeshift.service.js
Normal file
112
src/js/services/shapeshift.service.js
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('shapeshiftService', shapeshiftService);
|
||||
|
||||
function shapeshiftService(shapeshiftApiService, gettextCatalog) {
|
||||
|
||||
var service = {
|
||||
// Variables
|
||||
coinIn: '',
|
||||
coinOut: '',
|
||||
withdrawalAddress: '',
|
||||
returnAddress: '',
|
||||
amount: '',
|
||||
marketData: {},
|
||||
coins: {
|
||||
'BTC': {name: 'Bitcoin', symbol: 'BTC'},
|
||||
'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'}
|
||||
},
|
||||
|
||||
// Functions
|
||||
getMarketData: getMarketData,
|
||||
shiftIt: shiftIt
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
function handleError(response, defaultMessage, cb) {
|
||||
if (response && typeof response.error === "string") {
|
||||
cb(new Error(response.error));
|
||||
} else if (response && response.error && response.error.message) {
|
||||
cb(new Error(response.error.message));
|
||||
} else {
|
||||
cb(new Error(defaultMessage));
|
||||
}
|
||||
}
|
||||
|
||||
function getMarketData(coinIn, coinOut, cb) {
|
||||
service.coinIn = coinIn;
|
||||
service.coinOut = coinOut;
|
||||
shapeshiftApiService
|
||||
.marketInfo(service.coinIn, service.coinOut)
|
||||
.then(function (response) {
|
||||
if (!response || response.error) {
|
||||
handleError(response, 'Invalid response from Shapeshift', cb);
|
||||
} else {
|
||||
service.marketData = response;
|
||||
service.rateString = service.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase();
|
||||
cb(null, response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function shiftIt(coinIn, coinOut, withdrawalAddress, returnAddress, amount, cb) {
|
||||
// Test if the amount is correct depending on the min and max
|
||||
if (!amount || typeof amount !== 'number') {
|
||||
cb(new Error(gettextCatalog.getString('Amount is not defined')));
|
||||
} else if (amount < service.marketData.minimum) {
|
||||
cb(new Error(gettextCatalog.getString('Amount is below the minimun')));
|
||||
} else if (amount > service.marketData.maxLimit) {
|
||||
cb(new Error(gettextCatalog.getString('Amount is above the limit')));
|
||||
} else {
|
||||
// Init service data
|
||||
service.withdrawalAddress = withdrawalAddress;
|
||||
service.returnAddress = returnAddress;
|
||||
service.coinIn = coinIn;
|
||||
service.coinOut = coinOut;
|
||||
service.amount = amount;
|
||||
|
||||
// Check the address
|
||||
shapeshiftApiService
|
||||
.ValidateAddress(withdrawalAddress, coinOut)
|
||||
.then(function onSuccess(response) {
|
||||
if (response && response.isvalid) {
|
||||
// Prepare the transaction shapeshift side
|
||||
shapeshiftApiService.NormalTx(service).then(function onResponse(response) {
|
||||
// If error, return it
|
||||
if (!response || response.error) {
|
||||
handleError(response, gettextCatalog.getString('Invalid response from Shapeshift'), cb);
|
||||
} else {
|
||||
var txData = response;
|
||||
|
||||
// If the content is not that it was expected, get back an error
|
||||
if (!txData || !txData.orderId || !txData.deposit) {
|
||||
cb(new Error(gettextCatalog.getString('Invalid response from Shapeshift')));
|
||||
} else {
|
||||
// Get back the data
|
||||
service.depositInfo = txData;
|
||||
var shapeshiftData = {
|
||||
coinIn: coinIn,
|
||||
coinOut: coinOut,
|
||||
toWalletId: service.toWalletId,
|
||||
minAmount: service.marketData.minimum,
|
||||
maxAmount: service.marketData.maxLimit,
|
||||
orderId: txData.orderId,
|
||||
toAddress: txData.deposit
|
||||
};
|
||||
cb(null, shapeshiftData);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cb(new Error(gettextCatalog.getString('Invalid address')));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').factory('shapeshiftService', function ($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingData, platformInfo, servicesService) {
|
||||
var root = {};
|
||||
root.ShiftState = 'Shift';
|
||||
root.coinIn = '';
|
||||
root.coinOut = '';
|
||||
root.withdrawalAddress = '';
|
||||
root.returnAddress = '';
|
||||
root.amount = '';
|
||||
root.marketData = {};
|
||||
|
||||
root.getMarketDataIn = function (coin) {
|
||||
if (coin === root.coinOut) return root.getMarketData(root.coinOut, root.coinIn);
|
||||
return root.getMarketData(coin, root.coinOut);
|
||||
};
|
||||
root.getMarketDataOut = function (coin) {
|
||||
if (coin === root.coinIn) return root.getMarketData(root.coinOut, root.coinIn);
|
||||
return root.getMarketData(root.coinIn, coin);
|
||||
};
|
||||
root.getMarketData = function (coinIn, coinOut, cb) {
|
||||
root.coinIn = coinIn;
|
||||
root.coinOut = coinOut;
|
||||
if (root.coinIn === undefined || root.coinOut === undefined) return;
|
||||
shapeshiftApiService
|
||||
.marketInfo(root.coinIn, root.coinOut)
|
||||
.then(function (marketData) {
|
||||
root.marketData = marketData;
|
||||
root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase();
|
||||
if (cb) {
|
||||
cb(marketData);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*shapeshiftApiService.coins().then(function(coins){
|
||||
root.coins = coins;
|
||||
root.coinIn = coins['BTC'].symbol;
|
||||
root.coinOut = coins['BCH'].symbol;
|
||||
root.getMarketData(root.coinIn, root.coinOut);
|
||||
});*/
|
||||
|
||||
root.coins = {
|
||||
'BTC': {name: 'Bitcoin', symbol: 'BTC'},
|
||||
'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'}
|
||||
};
|
||||
|
||||
function checkForError(data) {
|
||||
if (data.err) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) {
|
||||
ongoingProcess.set('connectingShapeshift', true);
|
||||
root.withdrawalAddress = withdrawalAddress;
|
||||
root.returnAddress = returnAddress;
|
||||
root.coinIn = coinIn;
|
||||
root.coinOut = coinOut;
|
||||
shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function (valid) {
|
||||
var tx = ShapeShift();
|
||||
var coin;
|
||||
console.log("Starting");
|
||||
tx.then(function (txData) {
|
||||
console.log("Got txData", txData);
|
||||
if (txData['fixedTxData']) {
|
||||
txData = txData.fixedTxData;
|
||||
if (checkForError(txData)) return cb(txData.err);
|
||||
//console.log(txData)
|
||||
var coinPair = txData.pair.split('_');
|
||||
txData.depositType = coinPair[0].toUpperCase();
|
||||
txData.withdrawalType = coinPair[1].toUpperCase();
|
||||
coin = root.coins[txData.depositType].name.toLowerCase();
|
||||
|
||||
txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount;
|
||||
|
||||
root.txFixedPending = true;
|
||||
|
||||
} else if (txData['normalTxData']) {
|
||||
txData = txData.normalTxData;
|
||||
if (checkForError(txData)) return cb(txData.err);
|
||||
coin = root.coins[txData.depositType.toUpperCase()].name.toLowerCase();
|
||||
txData.depositQR = coin + ":" + txData.deposit;
|
||||
} else if (txData['cancelTxData']) {
|
||||
txData = txData.cancelTxData;
|
||||
if (checkForError(txData)) return cb(txData.err);
|
||||
if (root.txFixedPending) {
|
||||
root.txFixedPending = false;
|
||||
}
|
||||
root.ShiftState = 'Shift';
|
||||
}
|
||||
root.depositInfo = txData;
|
||||
//console.log(root.marketData);
|
||||
//console.log(root.depositInfo);
|
||||
var sendAddress = txData.depositQR;
|
||||
if (sendAddress && sendAddress.indexOf('bitcoin cash') >= 0)
|
||||
sendAddress = sendAddress.replace('bitcoin cash', 'bitcoincash');
|
||||
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
|
||||
root.ShiftState = 'Cancel';
|
||||
//root.GetStatus();
|
||||
//root.txInterval=$interval(root.GetStatus, 8000);
|
||||
|
||||
var shapeshiftData = {
|
||||
coinIn: coinIn,
|
||||
coinOut: coinOut,
|
||||
toWalletId: root.toWalletId,
|
||||
minAmount: root.marketData.minimum,
|
||||
maxAmount: root.marketData.maxLimit,
|
||||
orderId: root.depositInfo.orderId,
|
||||
toAddress: txData.deposit
|
||||
};
|
||||
//
|
||||
// if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) {
|
||||
ongoingProcess.set('connectingShapeshift', false);
|
||||
// return;
|
||||
// }
|
||||
cb(null, shapeshiftData);
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
function ShapeShift() {
|
||||
if (parseFloat(root.amount) > 0) return shapeshiftApiService.FixedAmountTx(root);
|
||||
return shapeshiftApiService.NormalTx(root);
|
||||
}
|
||||
|
||||
root.GetStatus = function () {
|
||||
var address = root.depositInfo.deposit
|
||||
shapeshiftApiService.GetStatusOfDepositToAddress(address).then(function (data) {
|
||||
root.DepositStatus = data;
|
||||
if (root.DepositStatus.status === 'complete') {
|
||||
$interval.cancel(root.txInterval);
|
||||
root.depositInfo = null;
|
||||
root.ShiftState = 'Shift'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return root;
|
||||
});
|
||||
|
|
@ -10,7 +10,7 @@ angular.module('copayApp.services')
|
|||
isoCode: 'en',
|
||||
rateCode: 'USD'
|
||||
}, {
|
||||
name: 'català',
|
||||
name: 'Català',
|
||||
isoCode: 'ca',
|
||||
rateCode: 'EUR'
|
||||
},{
|
||||
|
|
@ -59,10 +59,6 @@ angular.module('copayApp.services')
|
|||
name: 'Português',
|
||||
isoCode: 'pt',
|
||||
rateCode: 'EUR'
|
||||
}, {
|
||||
name: 'русский язык',
|
||||
isoCode: 'ru',
|
||||
rateCode: 'RUB'
|
||||
}, {
|
||||
name: '한국어',
|
||||
isoCode: 'ko',
|
||||
|
|
|
|||
275
src/js/services/wallet-history.service.js
Normal file
275
src/js/services/wallet-history.service.js
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
'use strict';
|
||||
|
||||
(function(){
|
||||
|
||||
angular
|
||||
.module('bitcoincom.services')
|
||||
.factory('walletHistoryService', walletHistoryService);
|
||||
|
||||
function walletHistoryService(configService, storageService, lodash, $log, txFormatService) {
|
||||
//var PAGE_SIZE = 50;
|
||||
var PAGE_SIZE = 20; // For dev only
|
||||
// How much to overlap on each end of the page, for mitigating inconsistent sort order.
|
||||
var PAGE_OVERLAP_FRACTION = 0.2;
|
||||
var PAGE_OVERLAP = Math.floor(PAGE_SIZE * PAGE_OVERLAP_FRACTION);
|
||||
// The fraction of transactions in the new overlapping resultset that we already know about.
|
||||
// If we know about at least this many, then there are probably no gaps.
|
||||
var MIN_KNOWN_TX_OVERLAP_FRACTION = 0.5;
|
||||
|
||||
var SAFE_CONFIRMATIONS = 6;
|
||||
|
||||
var allTransactionsFetched = false;
|
||||
var service = {
|
||||
getCachedTxHistory: getCachedTxHistory,
|
||||
updateLocalTxHistoryByPage: updateLocalTxHistoryByPage,
|
||||
};
|
||||
return service;
|
||||
|
||||
function addEarlyTransactions(walletId, cachedTxs, newTxs) {
|
||||
|
||||
var cachedTxIndexFromId = {};
|
||||
cachedTxs.forEach(function forCachedTx(tx){
|
||||
cachedTxIndexFromId[tx.txid] = true;
|
||||
});
|
||||
|
||||
var confirmationsUpdated = false;
|
||||
var someTransactionsWereNew = false;
|
||||
var overlappingTxsCount = 0;
|
||||
|
||||
newTxs.forEach(function forNewTx(tx){
|
||||
if (typeof cachedTxIndexFromId[tx.txid] === "undefined") {
|
||||
someTransactionsWereNew = true;
|
||||
cachedTxs.push(tx);
|
||||
} else {
|
||||
var txUpdated = updateCachedTx(cachedTxs, cachedTxIndexFromId, tx);
|
||||
confirmationsUpdated = confirmationsUpdated || txUpdated;
|
||||
overlappingTxsCount++;
|
||||
}
|
||||
});
|
||||
|
||||
var overlappingTxFraction = overlappingTxsCount / Math.min(cachedTxs.length, PAGE_OVERLAP);
|
||||
console.log('overlappingTxFraction:', overlappingTxFraction);
|
||||
|
||||
if (overlappingTxFraction >= MIN_KNOWN_TX_OVERLAP_FRACTION) { // We are good
|
||||
if (someTransactionsWereNew) {
|
||||
saveTxHistory(walletId, cachedTxs);
|
||||
} else if (confirmationsUpdated) {
|
||||
saveTxHistory(walletId, cachedTxs);
|
||||
} else if (overlappingTxsCount === newTxs.length) {
|
||||
allTransactionsFetched = true;
|
||||
}
|
||||
return cachedTxs;
|
||||
} else {
|
||||
// We might be missing some txs.
|
||||
console.error('We might be missing some txs in the history.');
|
||||
// Our history is wrong, so remove it - we could instead, try to fetch data that was not so early.
|
||||
storageService.removeTxHistory(walletId, function onRemoveTxHistory(){});
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function addLatestTransactions(walletId, cachedTxs, newTxs) {
|
||||
var cachedTxIndexFromId = {};
|
||||
cachedTxs.forEach(function forCachedTx(tx, txIndex){
|
||||
cachedTxIndexFromId[tx.txid] = txIndex;
|
||||
});
|
||||
|
||||
var someTransactionsWereNew = false;
|
||||
var confirmationsUpdated = false;
|
||||
var overlappingTxsCount = 0;
|
||||
var uniqueNewTxs = [];
|
||||
|
||||
newTxs.forEach(function forNewTx(tx){
|
||||
if (typeof cachedTxIndexFromId[tx.txid] === "undefined") {
|
||||
someTransactionsWereNew = true;
|
||||
uniqueNewTxs.push(tx);
|
||||
} else {
|
||||
var txUpdated = updateCachedTx(cachedTxs, cachedTxIndexFromId, tx);
|
||||
confirmationsUpdated = confirmationsUpdated || txUpdated;
|
||||
overlappingTxsCount++;
|
||||
}
|
||||
});
|
||||
|
||||
var overlappingTxFraction = overlappingTxsCount / Math.min(cachedTxs.length, PAGE_OVERLAP);
|
||||
|
||||
if (overlappingTxFraction >= MIN_KNOWN_TX_OVERLAP_FRACTION) { // We are good
|
||||
if (someTransactionsWereNew) {
|
||||
var allTxs = uniqueNewTxs.concat(cachedTxs);
|
||||
saveTxHistory(walletId, allTxs);
|
||||
return allTxs;
|
||||
} else {
|
||||
if (confirmationsUpdated) {
|
||||
saveTxHistory(walletId, cachedTxs);
|
||||
}
|
||||
return cachedTxs;
|
||||
}
|
||||
} else {
|
||||
// We might be missing some txs.
|
||||
// Our history is wrong, so just include the latest ones
|
||||
saveTxHistory(walletId, newTxs);
|
||||
return newTxs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Only clear the cache once we have received new transactions from the server.
|
||||
/**
|
||||
* @param wallet
|
||||
* @param start
|
||||
* @param {function(err, txs)} cb - transactions is always an array, may be empty
|
||||
*/
|
||||
function fetchTxHistoryByPage(wallet, start, cb) {
|
||||
var skip = Math.max(0, start - PAGE_OVERLAP);
|
||||
var limit = PAGE_SIZE;
|
||||
|
||||
var opts = {
|
||||
skip: skip,
|
||||
limit: limit
|
||||
};
|
||||
wallet.getTxHistory(opts, function onTxHistory(err, txsFromServer) {
|
||||
if (err) {
|
||||
return cb(err, []);
|
||||
}
|
||||
|
||||
if (txsFromServer.length === 0) {
|
||||
return cb(null, []);
|
||||
}
|
||||
|
||||
var processedTxs = processNewTxs(wallet, txsFromServer);
|
||||
|
||||
return cb(null, processedTxs);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} walletId
|
||||
* @param {function(error, txs)} cb - txs is always an array, may be empty
|
||||
*/
|
||||
function getCachedTxHistory(walletId, cb) {
|
||||
console.log('txhistory updateLocalTxHistoryByPage()');
|
||||
storageService.getTxHistory(walletId, function onGetTxHistory(err, txHistoryString){
|
||||
if (err) {
|
||||
return cb(err, []);
|
||||
}
|
||||
|
||||
if (!txHistoryString) {
|
||||
return cb(null, []);
|
||||
}
|
||||
|
||||
try {
|
||||
var txHistory = JSON.parse(txHistoryString);
|
||||
return cb(null, txHistory);
|
||||
} catch (e) {
|
||||
$log.error('Failed to parse tx history.', e);
|
||||
return cb(e, []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processNewTxs(wallet, txs) {
|
||||
var now = Math.floor(Date.now() / 1000);
|
||||
var txHistoryUnique = {};
|
||||
var processedTxs = [];
|
||||
wallet.hasUnsafeConfirmed = false;
|
||||
|
||||
lodash.each(txs, function(tx) {
|
||||
tx = txFormatService.processTx(wallet.coin, tx);
|
||||
|
||||
// no future transactions...
|
||||
if (tx.time > now)
|
||||
tx.time = now;
|
||||
|
||||
if (tx.confirmations >= SAFE_CONFIRMATIONS) {
|
||||
tx.safeConfirmed = SAFE_CONFIRMATIONS + '+';
|
||||
} else {
|
||||
tx.safeConfirmed = false;
|
||||
wallet.hasUnsafeConfirmed = true;
|
||||
}
|
||||
|
||||
if (tx.note) {
|
||||
delete tx.note.encryptedEditedByName;
|
||||
delete tx.note.encryptedBody;
|
||||
}
|
||||
|
||||
if (!txHistoryUnique[tx.txid]) {
|
||||
processedTxs.push(tx);
|
||||
txHistoryUnique[tx.txid] = true;
|
||||
} else {
|
||||
$log.debug('Ignoring duplicate TX in history: ' + tx.txid)
|
||||
}
|
||||
});
|
||||
|
||||
return processedTxs;
|
||||
}
|
||||
|
||||
function saveTxHistory(walletId, processedTxs) {
|
||||
storageService.setTxHistory(processedTxs, walletId, function onSetTxHistory(error){
|
||||
if (error) {
|
||||
$log.error('pagination Failed to save tx history.', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the cached tx was updated
|
||||
* @param {*} cachedTxs
|
||||
* @param {*} cachedTxIndexFromId - Indices for cachedTxs, based on txid
|
||||
* @param {*} tx - The most recent tx info
|
||||
*/
|
||||
function updateCachedTx(cachedTxs, cachedTxIndexFromId, tx) {
|
||||
var updated = false;
|
||||
var txIndex = cachedTxIndexFromId[tx.txid];
|
||||
var cachedTx = cachedTxs[txIndex];
|
||||
|
||||
if (cachedTx.confirmations < SAFE_CONFIRMATIONS && tx.confirmations > cachedTx.confirmations) {
|
||||
cachedTxs[txIndex].confirmations = tx.confirmations;
|
||||
updated = true;
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) {
|
||||
console.log('txhistory updaetLocalTxHistoryByPage()');
|
||||
if (flushCacheOnNew) {
|
||||
fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){
|
||||
if (err) {
|
||||
return cb(err, txs);
|
||||
}
|
||||
saveTxHistory(wallet.id, txs);
|
||||
return cb(null, txs);
|
||||
});
|
||||
} else {
|
||||
getCachedTxHistory(wallet.id, function onCachedHistory(err, cachedTxs){
|
||||
if (err) {
|
||||
$log.error('Failed to get cached tx history.', err);
|
||||
return cb(err, []);
|
||||
}
|
||||
|
||||
var start = getLatest ? 0 : cachedTxs.length;
|
||||
fetchTxHistoryByPage(wallet, start, function onFetchHistory(err, fetchedTxs){
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if (fetchedTxs.length === 0) {
|
||||
return cb(null, cachedTxs, true /*fetchedAllTransactions*/);
|
||||
}
|
||||
|
||||
var txs = [];
|
||||
if (getLatest) {
|
||||
txs = addLatestTransactions(wallet.id, cachedTxs, fetchedTxs);
|
||||
} else {
|
||||
allTransactionsFetched = false;
|
||||
txs = addEarlyTransactions(wallet.id, cachedTxs, fetchedTxs);
|
||||
return cb(null, txs, allTransactionsFetched);
|
||||
}
|
||||
return cb(null, txs);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -396,6 +396,23 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
return ret;
|
||||
};
|
||||
|
||||
var skipped = 0;
|
||||
|
||||
function fixTxsUnit(txs) {
|
||||
if (!txs || !txs[0] || !txs[0].amountStr) return;
|
||||
|
||||
var cacheCoin = txs[0].amountStr.split(' ')[1];
|
||||
|
||||
if (cacheCoin == 'bits') {
|
||||
|
||||
$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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var updateLocalTxHistory = function(wallet, opts, cb) {
|
||||
var FIRST_LIMIT = 5;
|
||||
var LIMIT = 50;
|
||||
|
|
@ -406,25 +423,10 @@ 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;
|
||||
|
||||
var cacheCoin = txs[0].amountStr.split(' ')[1];
|
||||
|
||||
if (cacheCoin == 'bits') {
|
||||
|
||||
$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) {
|
||||
if (err) return cb(err);
|
||||
|
|
@ -435,13 +437,14 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
var endingTxid = confirmedTxs[0] ? confirmedTxs[0].txid : null;
|
||||
var endingTs = confirmedTxs[0] ? confirmedTxs[0].time : null;
|
||||
|
||||
$log.debug('Confirmed TXs. Got:' + confirmedTxs.length + '/' + txsFromLocal.length);
|
||||
console.log('pagination Hard confirmed TXs. Got:' + confirmedTxs.length + '/' + txsFromLocal.length);
|
||||
|
||||
// First update
|
||||
progressFn(txsFromLocal, 0);
|
||||
wallet.completeHistory = txsFromLocal;
|
||||
|
||||
function getNewTxs(newTxs, skip, next) {
|
||||
console.log('pagination getNewTxs skip: ' + skip);
|
||||
getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res) {
|
||||
if (err) {
|
||||
$log.warn(bwcError.msg(err, 'Server Error')); //TODO
|
||||
|
|
@ -454,6 +457,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
return next(err);
|
||||
}
|
||||
|
||||
console.log('pagination Result count: ' + res.length);
|
||||
// Check if new txs are founds, if yes, lets investigate in the 50 next
|
||||
// To be sure we are not missing txs by sorting (maybe a new tx is after the "endingTxid"
|
||||
var newDiscoveredTxs = res.filter(function (x) {
|
||||
|
|
@ -462,7 +466,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
}).length == 0;
|
||||
});
|
||||
|
||||
$log.debug('Discovering TXs. Got:' + newDiscoveredTxs.length);
|
||||
console.log('pagination Discovering new TXs. Got:' + newDiscoveredTxs.length);
|
||||
|
||||
var shouldContinue = newDiscoveredTxs.length > 0;
|
||||
|
||||
|
|
@ -475,7 +479,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
|
||||
skip = skip + requestLimit;
|
||||
|
||||
$log.debug('Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue);
|
||||
console.log('pagination Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue);
|
||||
|
||||
// TODO Dirty <HACK>
|
||||
// do not sync all history, just looking for a single TX.
|
||||
|
|
@ -492,14 +496,16 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
}
|
||||
// </HACK>
|
||||
|
||||
shouldContinue = false;
|
||||
|
||||
skipped = skip;
|
||||
|
||||
if (!shouldContinue) {
|
||||
$log.debug('Finished Sync: New / soft confirmed Txs: ' + newTxs.length);
|
||||
console.log('pagination Finished Sync: New / soft confirmed Txs: ' + newTxs.length);
|
||||
return next(null, newTxs);
|
||||
}
|
||||
|
||||
requestLimit = LIMIT;
|
||||
getNewTxs(newTxs, skip, next);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -535,6 +541,87 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
if (opts.getMoreTxs) {
|
||||
var requestLimit = LIMIT;
|
||||
getNewTxs([], skipped, function(err, txs) {
|
||||
if (err) return cb(err);
|
||||
|
||||
console.log();
|
||||
createReceivedEvents(txs);
|
||||
|
||||
var newHistory = lodash.uniq(lodash.compact(txs.concat(confirmedTxs)), function(x) {
|
||||
return x.txid;
|
||||
});
|
||||
|
||||
|
||||
function updateNotes(cb2) {
|
||||
if (!endingTs) return cb2();
|
||||
|
||||
$log.debug('Syncing notes from: ' + endingTs);
|
||||
wallet.getTxNotes({
|
||||
minTs: endingTs
|
||||
}, function(err, notes) {
|
||||
if (err) {
|
||||
$log.warn(err);
|
||||
return cb2();
|
||||
};
|
||||
lodash.each(notes, function(note) {
|
||||
$log.debug('Note for ' + note.txid);
|
||||
lodash.each(newHistory, function(tx) {
|
||||
if (tx.txid == note.txid) {
|
||||
$log.debug('...updating note for ' + note.txid);
|
||||
tx.note = note;
|
||||
}
|
||||
});
|
||||
});
|
||||
return cb2();
|
||||
});
|
||||
}
|
||||
|
||||
function updateLowAmount(txs) {
|
||||
if (!opts.lowAmount) return;
|
||||
|
||||
lodash.each(txs, function(tx) {
|
||||
tx.lowAmount = tx.amount < opts.lowAmount;
|
||||
});
|
||||
};
|
||||
|
||||
updateLowAmount(txs);
|
||||
|
||||
updateNotes(function() {
|
||||
|
||||
// <HACK>
|
||||
if (foundLimitTx) {
|
||||
$log.debug('Tx history read until limitTx: ' + opts.limitTx);
|
||||
return cb(null, newHistory);
|
||||
}
|
||||
// </HACK>
|
||||
|
||||
var historyToSave = JSON.stringify(newHistory);
|
||||
|
||||
lodash.each(txs, function(tx) {
|
||||
tx.recent = true;
|
||||
})
|
||||
|
||||
$log.debug('Tx History synced. Total Txs: ' + newHistory.length);
|
||||
|
||||
// Final update
|
||||
if (walletId == wallet.credentials.walletId) {
|
||||
wallet.completeHistory = newHistory;
|
||||
}
|
||||
|
||||
return storageService.setTxHistory(historyToSave, walletId, function() {
|
||||
$log.debug('Tx History saved.');
|
||||
return cb(null, newHistory);
|
||||
});
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
skipped = 0;
|
||||
|
||||
getNewTxs([], 0, function(err, txs) {
|
||||
if (err) return cb(err);
|
||||
|
||||
|
|
@ -603,14 +690,19 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
|
||||
return storageService.setTxHistory(historyToSave, walletId, function() {
|
||||
$log.debug('Tx History saved.');
|
||||
|
||||
return cb();
|
||||
return cb(null, newHistory);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
root.getMoreTxs = function(wallet, cb) {
|
||||
var opts = {};
|
||||
opts.getMoreTxs = true;
|
||||
updateLocalTxHistory(wallet, opts, cb);
|
||||
};
|
||||
|
||||
root.getTxNote = function(wallet, txid, cb) {
|
||||
wallet.getTxNote({
|
||||
txid: txid
|
||||
|
|
@ -634,7 +726,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
};
|
||||
|
||||
root.getTx = function(wallet, txid, cb) {
|
||||
|
||||
function finish(list) {
|
||||
var tx = lodash.find(list, {
|
||||
txid: txid
|
||||
|
|
@ -651,7 +742,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
limitTx: txid
|
||||
}, function(err, txHistory) {
|
||||
if (err) return cb(err);
|
||||
|
||||
finish(txHistory);
|
||||
});
|
||||
}
|
||||
|
|
@ -675,16 +765,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
|
|||
root.getTxHistory = function(wallet, opts, cb) {
|
||||
opts = opts || {};
|
||||
|
||||
var walletId = wallet.credentials.walletId;
|
||||
|
||||
if (!wallet.isComplete()) return cb();
|
||||
|
||||
function isHistoryCached() {
|
||||
return wallet.completeHistory && wallet.completeHistory.isValid;
|
||||
};
|
||||
|
||||
// disable caching
|
||||
//if (isHistoryCached() && !opts.force) return cb(null, wallet.completeHistory);
|
||||
// var historyIsCached = wallet.completeHistory && wallet.completeHistory.isValid;
|
||||
// if (historyIsCached && !opts.force) return cb(null, wallet.completeHistory);
|
||||
|
||||
$log.debug('Updating Transaction History');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
%button-standard {
|
||||
width: 85%;
|
||||
width: 90%;
|
||||
max-width: 300px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
.fee-summary {
|
||||
position: relative;
|
||||
background-color: #F2F2F2;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 5px 12px 15px;
|
||||
box-sizing: border-box;
|
||||
background-color: #F2F2F2;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
|
|
@ -18,12 +18,11 @@
|
|||
}
|
||||
|
||||
.amount {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.fee-fiat {
|
||||
display: inline;
|
||||
|
||||
&.positive {
|
||||
color: #70955F;
|
||||
}
|
||||
|
|
@ -35,6 +34,7 @@
|
|||
|
||||
.fee-crypto {
|
||||
color: #A7A7A7;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@
|
|||
left: 13px;
|
||||
top: 50%;
|
||||
padding: 0;
|
||||
-webkit-transform: translate(0,-50%);
|
||||
transform: translate(0,-50%);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@
|
|||
.button {
|
||||
font-weight: bold;
|
||||
font-size: 19px;
|
||||
line-height: 26px;
|
||||
padding: 8px 6px;
|
||||
}
|
||||
}
|
||||
.button-first-contact img {
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@
|
|||
.primary-amount-display {
|
||||
margin-right: 5px;
|
||||
word-break: break-all;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@
|
|||
}
|
||||
|
||||
.fee-summary {
|
||||
position: absolute;
|
||||
bottom: 92px;
|
||||
bottom: calc(92px + constant(safe-area-inset-bottom)); /* iOS 11.0 */
|
||||
bottom: calc(92px + env(safe-area-inset-bottom)); /* iOS 11.2 */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.shapeshift-banner, .bitpay-banner, .egifter-banner {
|
||||
|
|
@ -17,4 +19,5 @@
|
|||
.warning {
|
||||
color: $v-warning-color-2;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -83,14 +83,14 @@
|
|||
.button {
|
||||
border: 2px solid;
|
||||
border-radius: 47px;
|
||||
padding: 0 15px 0 15px;
|
||||
padding: 8px 2px 8px 2px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
font-size: 19px;
|
||||
font-weight: bolder;
|
||||
min-height: auto;
|
||||
line-height: 36px;
|
||||
min-height: 0;
|
||||
line-height: 19px;
|
||||
}
|
||||
}
|
||||
.wallet-coin-logo {
|
||||
|
|
@ -173,6 +173,11 @@
|
|||
font-weight: 700;
|
||||
color: #444;
|
||||
}
|
||||
.release-notes {
|
||||
white-space: pre;
|
||||
white-space: pre-line;
|
||||
text-align: left;
|
||||
}
|
||||
.button {
|
||||
width: 100%;
|
||||
border: none;
|
||||
|
|
@ -190,3 +195,13 @@
|
|||
top:11px;
|
||||
}
|
||||
}
|
||||
.popup-update {
|
||||
.popup-buttons {
|
||||
display: block;
|
||||
}
|
||||
.popup-buttons .button{
|
||||
display:block;
|
||||
min-width: 100% !important;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +84,9 @@
|
|||
width: 100%;
|
||||
}
|
||||
.payment-received-container {
|
||||
svg {
|
||||
max-height: 400px;
|
||||
}
|
||||
margin: 0 20px;
|
||||
.payment-received-amount {
|
||||
font-size: 1.8em;
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@
|
|||
width: auto;
|
||||
margin: 2px 0 4px;
|
||||
}
|
||||
height: 60px;
|
||||
min-height: 65px;
|
||||
line-height: 16px;
|
||||
margin-right: 0px;
|
||||
width: 95%;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
$wallet-details-collapse-transition: all 0.25s ease-in-out;
|
||||
.wallet-details {
|
||||
&__tx-amount {
|
||||
font-size: 16px;
|
||||
|
|
@ -137,6 +138,20 @@
|
|||
margin-top: 20px;
|
||||
margin-top: env(safe-area-inset-top);
|
||||
}
|
||||
&.collapse {
|
||||
ion-content {
|
||||
margin-top: 40px;
|
||||
}
|
||||
.amount {
|
||||
&__scale, &__error {
|
||||
-webkit-transform: scale3d(0.5, 0.5, 0.5) translateY(0px);
|
||||
transform: scale3d(0.5, 0.5, 0.5) translateY(0px);
|
||||
}
|
||||
}
|
||||
.amount-alternative, .send-receive-buttons, .wallet-details-wallet-info {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bar-header {
|
||||
border: 0;
|
||||
|
|
@ -152,13 +167,14 @@
|
|||
background-color: inherit !important;
|
||||
}
|
||||
ion-content {
|
||||
|
||||
&.collapsible {
|
||||
margin-top: 230px;
|
||||
}
|
||||
|
||||
padding-top: 0;
|
||||
top: 0;
|
||||
transition: $wallet-details-collapse-transition;
|
||||
|
||||
margin-top: 185px;
|
||||
@media only screen and (max-height:500px) {
|
||||
margin-top: 165px;
|
||||
}
|
||||
margin-bottom: 16px;
|
||||
|
||||
.scroll {
|
||||
|
|
@ -199,7 +215,7 @@
|
|||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
|
||||
transition: $wallet-details-collapse-transition;
|
||||
>.col {
|
||||
padding: 5px 10px;
|
||||
margin-bottom: 0;
|
||||
|
|
@ -207,30 +223,37 @@
|
|||
.button {
|
||||
border: 2px solid;
|
||||
border-radius: 47px;
|
||||
padding: 0 15px 0 15px;
|
||||
padding: 6px 2px 6px 2px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
font-size: 19px;
|
||||
font-weight: bolder;
|
||||
min-height: auto;
|
||||
line-height: 36px;
|
||||
min-height: 0;
|
||||
line-height: 19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.amount {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
height: 230px;
|
||||
padding-top: 40px;
|
||||
display: block;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
display: block;
|
||||
|
||||
height: 230px;
|
||||
@media only screen and (max-height:500px) {
|
||||
height: 210px;
|
||||
}
|
||||
|
||||
justify-content: center;
|
||||
padding-top: 40px;
|
||||
text-align: center;
|
||||
transition: $wallet-details-collapse-transition;
|
||||
width: 100%;
|
||||
|
||||
&__balance {
|
||||
-webkit-transform: scale3d(1, 1, 1) translateY(45px);
|
||||
transform: scale3d(1, 1, 1) translateY(45px);
|
||||
transition: $wallet-details-collapse-transition;
|
||||
}
|
||||
|
||||
&__updating {
|
||||
|
|
@ -240,6 +263,7 @@
|
|||
|
||||
&-alternative {
|
||||
line-height: 36px;
|
||||
transition: $wallet-details-collapse-transition;
|
||||
}
|
||||
|
||||
&__button-balance {
|
||||
|
|
@ -255,6 +279,7 @@
|
|||
&__error {
|
||||
font-size: 14px;
|
||||
padding: 35px 20px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -341,4 +366,6 @@ a.item {
|
|||
|
||||
.loading-wallet svg {
|
||||
margin-top: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
|
@ -8,4 +8,56 @@ if (!ArrayBuffer['isView']) {
|
|||
ArrayBuffer.isView = function(a) {
|
||||
return a !== null && typeof(a) === "object" && a['buffer'] instanceof ArrayBuffer;
|
||||
};
|
||||
}
|
||||
|
||||
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
|
||||
if (!Array.prototype.includes) {
|
||||
Object.defineProperty(Array.prototype, 'includes', {
|
||||
value: function(searchElement, fromIndex) {
|
||||
|
||||
if (this == null) {
|
||||
throw new TypeError('"this" is null or not defined');
|
||||
}
|
||||
|
||||
// 1. Let O be ? ToObject(this value).
|
||||
var o = Object(this);
|
||||
|
||||
// 2. Let len be ? ToLength(? Get(O, "length")).
|
||||
var len = o.length >>> 0;
|
||||
|
||||
// 3. If len is 0, return false.
|
||||
if (len === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Let n be ? ToInteger(fromIndex).
|
||||
// (If fromIndex is undefined, this step produces the value 0.)
|
||||
var n = fromIndex | 0;
|
||||
|
||||
// 5. If n ≥ 0, then
|
||||
// a. Let k be n.
|
||||
// 6. Else n < 0,
|
||||
// a. Let k be len + n.
|
||||
// b. If k < 0, let k be 0.
|
||||
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
|
||||
|
||||
function sameValueZero(x, y) {
|
||||
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
|
||||
}
|
||||
|
||||
// 7. Repeat, while k < len
|
||||
while (k < len) {
|
||||
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
|
||||
// b. If SameValueZero(searchElement, elementK) is true, return true.
|
||||
if (sameValueZero(o[k], searchElement)) {
|
||||
return true;
|
||||
}
|
||||
// c. Increase k by 1.
|
||||
k++;
|
||||
}
|
||||
|
||||
// 8. Return false
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue