This commit is contained in:
magmahindenburg 2017-07-06 11:27:29 +09:00
commit 98c93e3c6f
133 changed files with 5157 additions and 3511 deletions

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('addressesController', function($scope, $log, $stateParams, $state, $timeout, $ionicHistory, $ionicScrollDelegate, configService, popupService, gettextCatalog, ongoingProcess, lodash, profileService, walletService, bwcError, platformInfo, appConfigService) {
angular.module('copayApp.controllers').controller('addressesController', function($scope, $log, $stateParams, $state, $timeout, $ionicHistory, $ionicScrollDelegate, configService, popupService, gettextCatalog, ongoingProcess, lodash, profileService, walletService, bwcError, platformInfo, appConfigService, txFormatService, feeService) {
var UNUSED_ADDRESS_LIMIT = 5;
var BALANCE_ADDRESS_LIMIT = 5;
var config = configService.getSync().wallet.settings;
@ -55,7 +55,7 @@ angular.module('copayApp.controllers').controller('addressesController', functio
$scope.latestWithBalance = lodash.slice(withBalance, 0, BALANCE_ADDRESS_LIMIT);
lodash.each(withBalance, function(a) {
a.balanceStr = (a.amount * satToUnit).toFixed(unitDecimals) + ' ' + unitName;
a.balanceStr = txFormatService.formatAmount(a.amount);
});
$scope.viewAll = {
@ -72,6 +72,31 @@ angular.module('copayApp.controllers').controller('addressesController', functio
});
});
});
feeService.getFeeLevels(function(err, levels){
walletService.getLowUtxos($scope.wallet, levels, function(err, resp) {
if (err) return;
if (resp.allUtxos && resp.allUtxos.length) {
var allSum = lodash.sum(resp.allUtxos || 0, 'satoshis');
var per = (resp.minFee / allSum) * 100;
$scope.lowWarning = resp.warning;
$scope.lowUtxosNb = resp.lowUtxos.length;
$scope.allUtxosNb = resp.allUtxos.length;
$scope.lowUtxosSum = txFormatService.formatAmountStr(lodash.sum(resp.lowUtxos || 0, 'satoshis'));
$scope.allUtxosSum = txFormatService.formatAmountStr(allSum);
$scope.minFee = txFormatService.formatAmountStr(resp.minFee || 0);
$scope.minFeePer = per.toFixed(2) + '%';
}
});
});
};
function processPaths(list) {
@ -116,7 +141,14 @@ angular.module('copayApp.controllers').controller('addressesController', functio
};
$scope.viewAllAddresses = function() {
$state.go('tabs.settings.allAddresses', {
var fromView = $ionicHistory.currentStateName();
var path;
if (fromView.indexOf('settings') !== -1) {
path = 'tabs.settings.allAddresses';
} else {
path = 'tabs.wallet.allAddresses';
}
$state.go(path, {
walletId: $scope.wallet.id
});
};

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $log, configService) {
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $log, configService, platformInfo) {
var updateConfig = function() {
var config = configService.getSync();
@ -50,6 +50,7 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
updateConfig();
});

View file

@ -44,66 +44,77 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency != 'bits' && data.stateParams.currency != 'BTC' ? true : false;
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.amount,
data.stateParams.currency);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
// Buy always in BTC
amount = (parsedAmount.amountSat / 100000000).toFixed(8);
currency = 'BTC';
$scope.amountUnitStr = parsedAmount.amountUnitStr;
$scope.network = coinbaseService.getNetwork();
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets available');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) {
ongoingProcess.set('calculatingFee', true);
coinbaseService.checkEnoughFundsForFee(amount, function(err) {
ongoingProcess.set('calculatingFee', false);
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var accessToken = res.accessToken;
coinbaseService.buyPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, b) {
$scope.buyPrice = b.data || null;
$scope.network = coinbaseService.getNetwork();
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network
});
$scope.paymentMethods = [];
$scope.selectedPaymentMethodId = { value : null };
coinbaseService.getPaymentMethods(accessToken, function(err, p) {
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets available');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var accessToken = res.accessToken;
var hasPrimary;
var pm;
for(var i = 0; i < p.data.length; i++) {
pm = p.data[i];
if (pm.allow_buy) {
$scope.paymentMethods.push(pm);
coinbaseService.buyPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, b) {
$scope.buyPrice = b.data || null;
});
$scope.paymentMethods = [];
$scope.selectedPaymentMethodId = { value : null };
coinbaseService.getPaymentMethods(accessToken, function(err, p) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
if (pm.allow_buy && pm.primary_buy) {
hasPrimary = true;
$scope.selectedPaymentMethodId.value = pm.id;
var hasPrimary;
var pm;
for(var i = 0; i < p.data.length; i++) {
pm = p.data[i];
if (pm.allow_buy) {
$scope.paymentMethods.push(pm);
}
if (pm.allow_buy && pm.primary_buy) {
hasPrimary = true;
$scope.selectedPaymentMethodId.value = pm.id;
}
}
}
if (lodash.isEmpty($scope.paymentMethods)) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack('No payment method available to buy');
return;
}
if (!hasPrimary) $scope.selectedPaymentMethodId.value = $scope.paymentMethods[0].id;
$scope.buyRequest();
if (lodash.isEmpty($scope.paymentMethods)) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack('No payment method available to buy');
return;
}
if (!hasPrimary) $scope.selectedPaymentMethodId.value = $scope.paymentMethods[0].id;
$scope.buyRequest();
});
});
});
});
@ -139,12 +150,12 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
};
$scope.buyConfirm = function() {
var message = 'Buy bitcoin for ' + amount + ' ' + currency;
var message = 'Buy bitcoin for ' + $scope.amountUnitStr;
var okText = 'Confirm';
var cancelText = 'Cancel';
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return;
ongoingProcess.set('buyingBitcoin', true, statusChangeHandler);
coinbaseService.init(function(err, res) {
if (err) {

View file

@ -1,14 +1,38 @@
'use strict';
angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, profileService, bitcore, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bwcError) {
var cachedTxp = {};
var feeLevel;
var feePerKb;
var toAmount;
var isChromeApp = platformInfo.isChromeApp;
var countDown = null;
var cachedSendMax = {};
$scope.isCordova = platformInfo.isCordova;
var CONFIRM_LIMIT_USD = 20;
var FEE_TOO_HIGH_LIMIT_PER = 15;
var tx = {};
// Config Related values
var config = configService.getSync();
var walletConfig = config.wallet;
var unitToSatoshi = walletConfig.settings.unitToSatoshi;
var unitDecimals = walletConfig.settings.unitDecimals;
var satToUnit = 1 / unitToSatoshi;
var configFeeLevel = walletConfig.settings.feeLevel ? walletConfig.settings.feeLevel : 'normal';
// Platform info
var isChromeApp = platformInfo.isChromeApp;
var isCordova = platformInfo.isCordova;
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
function refresh() {
$timeout(function() {
$scope.$apply();
}, 1);
}
$scope.showWalletSelector = function() {
$scope.walletSelector = true;
refresh();
};
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
@ -18,305 +42,346 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$ionicConfig.views.swipeBackEnabled(false);
});
function exitWithError(err) {
$log.info('Error setting wallet selector:' + err);
popupService.showAlert(gettextCatalog.getString(), bwcError.msg(err), function() {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.send');
});
};
function setNoWallet(msg) {
$scope.wallet = null;
$scope.noWalletMessage = gettextCatalog.getString(msg);
$log.warn('Not ready to make the payment:' + msg);
$timeout(function() {
$scope.$apply();
});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
toAmount = data.stateParams.toAmount;
cachedSendMax = {};
function setWalletSelector(network, minAmount, cb) {
// no min amount? (sendMax) => look for no empty wallets
minAmount = minAmount || 1;
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: network
});
if (!$scope.wallets || !$scope.wallets.length) {
setNoWallet('No wallets available');
return cb();
}
var filteredWallets = [];
var index = 0;
var walletsUpdated = 0;
lodash.each($scope.wallets, function(w) {
walletService.getStatus(w, {}, function(err, status) {
if (err || !status) {
$log.error(err);
} else {
walletsUpdated++;
w.status = status;
if (!status.availableBalanceSat)
$log.debug('No balance available in: ' + w.name);
if (status.availableBalanceSat > minAmount) {
filteredWallets.push(w);
}
}
if (++index == $scope.wallets.length) {
if (!walletsUpdated)
return cb('Could not update any wallet');
if (lodash.isEmpty(filteredWallets)) {
setNoWallet('Insufficent funds');
}
$scope.wallets = lodash.clone(filteredWallets);
return cb();
}
});
});
};
// Setup $scope
// Grab stateParams
tx = {
toAmount: parseInt(data.stateParams.toAmount),
sendMax: data.stateParams.useSendMax == 'true' ? true : false,
toAddress: data.stateParams.toAddress,
description: data.stateParams.description,
paypro: data.stateParams.paypro,
feeLevel: configFeeLevel,
spendUnconfirmed: walletConfig.spendUnconfirmed,
// Vanity tx info (not in the real tx)
recipientType: data.stateParams.recipientType || null,
toName: data.stateParams.toName,
toEmail: data.stateParams.toEmail,
toColor: data.stateParams.toColor,
network: (new bitcore.Address(data.stateParams.toAddress)).network.name,
txp: {},
};
// Other Scope vars
$scope.isCordova = isCordova;
$scope.isWindowsPhoneApp = isWindowsPhoneApp;
$scope.showAddress = false;
$scope.useSendMax = data.stateParams.useSendMax == 'true' ? true : false;
$scope.recipientType = data.stateParams.recipientType || null;
$scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName;
$scope.toEmail = data.stateParams.toEmail;
$scope.toColor = data.stateParams.toColor;
$scope.description = data.stateParams.description;
$scope.paypro = data.stateParams.paypro;
$scope.insufficientFunds = false;
$scope.noMatchingWallet = false;
$scope.paymentExpired = {
value: false
};
$scope.remainingTimeStr = {
value: null
};
$scope.network = (new bitcore.Address($scope.toAddress)).network.name;
setFee();
resetValues();
setwallets();
applyButtonText();
updateTx(tx, null, {}, function() {
$scope.walletSelectorTitle = gettextCatalog.getString('Send from');
setWalletSelector(tx.network, tx.toAmount, function(err) {
if (err) {
return exitWithError('Could not update wallets');
}
if ($scope.wallets.length > 1) {
$scope.showWalletSelector();
} else if ($scope.wallets.length) {
setWallet($scope.wallets[0], tx);
}
});
});
});
function setFee(customFeeLevel, cb) {
feeService.getCurrentFeeValue($scope.network, customFeeLevel, function(err, currentFeePerKb) {
var config = configService.getSync().wallet;
var configFeeLevel = (config.settings && config.settings.feeLevel) ? config.settings.feeLevel : 'normal';
feePerKb = currentFeePerKb;
feeLevel = customFeeLevel ? customFeeLevel : configFeeLevel;
$scope.feeLevel = feeService.feeOpts[feeLevel];
if (cb) return cb();
function getSendMaxInfo(tx, wallet, cb) {
if (!tx.sendMax) return cb();
//ongoingProcess.set('retrievingInputs', true);
walletService.getSendMaxInfo(wallet, {
feePerKb: tx.feeRate,
excludeUnconfirmedUtxos: !tx.spendUnconfirmed,
returnInputs: true,
}, cb);
};
function getTxp(tx, wallet, dryRun, cb) {
// ToDo: use a credential's (or fc's) function for this
if (tx.description && !wallet.credentials.sharedEncryptingKey) {
var msg = gettextCatalog.getString('Could not add message to imported wallet without shared encrypting key');
$log.warn(msg);
return setSendError(msg);
}
if (tx.toAmount > Number.MAX_SAFE_INTEGER) {
var msg = gettextCatalog.getString('Amount too big');
$log.warn(msg);
return setSendError(msg);
}
var txp = {};
txp.outputs = [{
'toAddress': tx.toAddress,
'amount': tx.toAmount,
'message': tx.description
}];
if (tx.sendMaxInfo) {
txp.inputs = tx.sendMaxInfo.inputs;
txp.fee = tx.sendMaxInfo.fee;
} else {
txp.feeLevel = tx.feeLevel;
}
txp.message = tx.description;
if (tx.paypro) {
txp.payProUrl = tx.paypro.url;
}
txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed;
txp.dryRun = dryRun;
walletService.createTx(wallet, txp, function(err, ctxp) {
if (err) {
setSendError(err);
return cb(err);
}
return cb(null, ctxp);
});
};
function updateTx(tx, wallet, opts, cb) {
if (opts.clearCache) {
tx.txp = {};
}
$scope.tx = tx;
function updateAmount() {
if (!tx.toAmount) return;
// Amount
tx.amountStr = txFormatService.formatAmountStr(tx.toAmount);
tx.amountValueStr = tx.amountStr.split(' ')[0];
tx.amountUnitStr = tx.amountStr.split(' ')[1];
txFormatService.formatAlternativeStr(tx.toAmount, function(v) {
tx.alternativeAmountStr = v;
});
}
updateAmount();
refresh();
// End of quick refresh, before wallet is selected.
if (!wallet)return cb();
feeService.getFeeRate(tx.network, tx.feeLevel, function(err, feeRate) {
if (err) return cb(err);
tx.feeRate = feeRate;
tx.feeLevelName = feeService.feeOpts[tx.feeLevel];
if (!wallet)
return cb();
getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) {
if (err) {
var msg = gettextCatalog.getString('Error getting SendMax information');
return setSendError(msg);
}
if (sendMaxInfo) {
$log.debug('Send max info', sendMaxInfo);
if (tx.sendMax && sendMaxInfo.amount == 0) {
setNoWallet('Insufficent funds');
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'));
return cb('no_funds');
}
tx.sendMaxInfo = sendMaxInfo;
tx.toAmount = tx.sendMaxInfo.amount;
updateAmount();
showSendMaxWarning(sendMaxInfo);
}
// txp already generated for this wallet?
if (tx.txp[wallet.id]) {
refresh();
return cb();
}
getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) {
if (err) return cb(err);
txp.feeStr = txFormatService.formatAmountStr(txp.fee);
txFormatService.formatAlternativeStr(txp.fee, function(v) {
txp.alternativeFeeStr = v;
});
var per = (txp.fee / (txp.amount + txp.fee) * 100);
txp.feeRatePerStr = per.toFixed(2) + '%';
txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PER;
tx.txp[wallet.id] = txp;
$log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx);
refresh();
return cb();
});
});
});
}
function useSelectedWallet() {
if (!$scope.useSendMax) displayValues();
if (!$scope.useSendMax) {
showAmount(tx.toAmount);
}
$scope.onWalletSelect($scope.wallet);
}
function applyButtonText(multisig) {
$scope.buttonText = $scope.isCordova ? gettextCatalog.getString('Slide') + ' ' : gettextCatalog.getString('Click') + ' ';
function setButtonText(isMultisig, isPayPro) {
$scope.buttonText = gettextCatalog.getString(isCordova && !isWindowsPhoneApp ? 'Slide' : 'Click') + ' ';
if ($scope.paypro) {
if (isPayPro) {
$scope.buttonText += gettextCatalog.getString('to pay');
} else if (multisig) {
} else if (isMultisig) {
$scope.buttonText += gettextCatalog.getString('to accept');
} else
$scope.buttonText += gettextCatalog.getString('to send');
};
function setwallets() {
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network
});
if (!$scope.wallets || !$scope.wallets.length) {
$scope.noMatchingWallet = true;
displayValues();
$log.warn('No ' + $scope.network + ' wallets to make the payment');
$timeout(function() {
$scope.$apply();
});
return;
}
var filteredWallets = [];
var index = 0;
var enoughFunds = false;
var walletsUpdated = 0;
lodash.each($scope.wallets, function(w) {
walletService.getStatus(w, {}, function(err, status) {
if (err || !status) {
$log.error(err);
} else {
walletsUpdated++;
w.status = status;
if (!status.availableBalanceSat) $log.debug('No balance available in: ' + w.name);
if (status.availableBalanceSat > toAmount) {
filteredWallets.push(w);
enoughFunds = true;
}
}
if (++index == $scope.wallets.length) {
if (!lodash.isEmpty(filteredWallets)) {
$scope.wallets = lodash.clone(filteredWallets);
if ($scope.useSendMax) {
if ($scope.wallets.length > 1)
$scope.showWalletSelector();
else {
$scope.wallet = $scope.wallets[0];
$scope.getSendMaxInfo();
}
} else initConfirm();
} else {
// Were we able to update any wallet?
if (walletsUpdated) {
if (!enoughFunds) $scope.insufficientFunds = true;
displayValues();
$log.warn('No wallet available to make the payment');
} else {
popupService.showAlert(gettextCatalog.getString('Could not update wallets'), bwcError.msg(err), function() {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.send');
});
}
}
$timeout(function() {
$scope.$apply();
});
}
});
});
};
$scope.toggleAddress = function() {
$scope.showAddress = !$scope.showAddress;
};
var initConfirm = function() {
if ($scope.paypro) _paymentTimeControl($scope.paypro.expires);
displayValues();
if ($scope.wallets.length > 1) $scope.showWalletSelector();
else setWallet($scope.wallets[0]);
$timeout(function() {
$scope.$apply();
});
};
function showSendMaxWarning(sendMaxInfo) {
function displayValues() {
toAmount = parseInt(toAmount);
$scope.amountStr = txFormatService.formatAmountStr(toAmount);
$scope.displayAmount = getDisplayAmount($scope.amountStr);
$scope.displayUnit = getDisplayUnit($scope.amountStr);
txFormatService.formatAlternativeStr(toAmount, function(v) {
$scope.alternativeAmountStr = v;
});
};
function resetValues() {
$scope.displayAmount = $scope.displayUnit = $scope.fee = $scope.feeFiat = $scope.feeRateStr = $scope.alternativeAmountStr = $scope.insufficientFunds = $scope.noMatchingWallet = null;
$scope.showAddress = false;
};
$scope.getSendMaxInfo = function() {
resetValues();
var config = configService.getSync().wallet;
ongoingProcess.set('retrievingInputs', true);
walletService.getSendMaxInfo($scope.wallet, {
feePerKb: feePerKb,
excludeUnconfirmedUtxos: !config.spendUnconfirmed,
returnInputs: true,
}, function(err, resp) {
ongoingProcess.set('retrievingInputs', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
function verifyExcludedUtxos() {
var warningMsg = [];
if (sendMaxInfo.utxosBelowFee > 0) {
warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", {
amountBelowFeeStr: txFormatService.formatAmountStr(sendMaxInfo.amountBelowFee)
}));
}
if (resp.amount == 0) {
$scope.insufficientFunds = true;
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'));
return;
if (sendMaxInfo.utxosAboveMaxSize > 0) {
warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", {
amountAboveMaxSizeStr: txFormatService.formatAmountStr(sendMaxInfo.amountAboveMaxSize)
}));
}
return warningMsg.join('\n');
};
$scope.sendMaxInfo = {
sendMax: true,
amount: resp.amount,
inputs: resp.inputs,
fee: resp.fee,
feePerKb: feePerKb,
};
cachedSendMax[$scope.wallet.id] = $scope.sendMaxInfo;
var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees.", {
fee: txFormatService.formatAmountStr(resp.fee)
});
var warningMsg = verifyExcludedUtxos();
if (!lodash.isEmpty(warningMsg))
msg += '\n' + warningMsg;
popupService.showAlert(null, msg, function() {
setSendMaxValues(resp);
createTx($scope.wallet, true, function(err, txp) {
if (err) return;
cachedTxp[$scope.wallet.id] = txp;
apply(txp);
});
});
function verifyExcludedUtxos() {
var warningMsg = [];
if (resp.utxosBelowFee > 0) {
warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", {
amountBelowFeeStr: txFormatService.formatAmountStr(resp.amountBelowFee)
}));
}
if (resp.utxosAboveMaxSize > 0) {
warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", {
amountAboveMaxSizeStr: txFormatService.formatAmountStr(resp.amountAboveMaxSize)
}));
}
return warningMsg.join('\n');
};
var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees.", {
fee: txFormatService.formatAmountStr(sendMaxInfo.fee)
});
};
var warningMsg = verifyExcludedUtxos();
function setSendMaxValues(data) {
resetValues();
var config = configService.getSync().wallet;
var unitToSatoshi = config.settings.unitToSatoshi;
var satToUnit = 1 / unitToSatoshi;
var unitDecimals = config.settings.unitDecimals;
if (!lodash.isEmpty(warningMsg))
msg += '\n' + warningMsg;
$scope.amountStr = txFormatService.formatAmountStr(data.amount, true);
$scope.displayAmount = getDisplayAmount($scope.amountStr);
$scope.displayUnit = getDisplayUnit($scope.amountStr);
$scope.fee = txFormatService.formatAmountStr(data.fee);
txFormatService.formatAlternativeStr(data.fee, function(v) {
$scope.feeFiat = v;
});
toAmount = parseFloat((data.amount * satToUnit).toFixed(unitDecimals));
txFormatService.formatAlternativeStr(data.amount, function(v) {
$scope.alternativeAmountStr = v;
});
$scope.feeRateStr = (data.fee / (data.amount + data.fee) * 100).toFixed(2) + '%';
$timeout(function() {
$scope.$apply();
});
};
$scope.$on('accepted', function(event) {
$scope.approve();
});
$scope.showWalletSelector = function() {
$scope.walletSelectorTitle = gettextCatalog.getString('Send from');
if (!$scope.useSendMax && ($scope.insufficientFunds || $scope.noMatchingWallet)) return;
$scope.showWallets = true;
popupService.showAlert(null, msg, function() {});
};
$scope.onWalletSelect = function(wallet) {
if ($scope.useSendMax) {
$scope.wallet = wallet;
if (cachedSendMax[wallet.id]) {
$log.debug('Send max cached for wallet:', wallet.id);
setSendMaxValues(cachedSendMax[wallet.id]);
return;
}
$scope.getSendMaxInfo();
} else
setWallet(wallet);
applyButtonText(wallet.credentials.m > 1);
setWallet(wallet, tx);
};
$scope.showDescriptionPopup = function() {
$scope.showDescriptionPopup = function(tx) {
var message = gettextCatalog.getString('Add description');
var opts = {
defaultText: $scope.description
defaultText: tx.description
};
popupService.showPrompt(null, message, opts, function(res) {
if (typeof res != 'undefined') $scope.description = res;
if (typeof res != 'undefined') tx.description = res;
$timeout(function() {
$scope.$apply();
});
});
};
function getDisplayAmount(amountStr) {
return $scope.amountStr.split(' ')[0];
};
function getDisplayUnit(amountStr) {
return $scope.amountStr.split(' ')[1];
};
function _paymentTimeControl(expirationTime) {
$scope.paymentExpired.value = false;
$scope.paymentExpired = false;
setExpirationTime();
countDown = $interval(function() {
@ -334,12 +399,12 @@ angular.module('copayApp.controllers').controller('confirmController', function(
var totalSecs = expirationTime - now;
var m = Math.floor(totalSecs / 60);
var s = totalSecs % 60;
$scope.remainingTimeStr.value = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
$scope.remainingTimeStr = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
};
function setExpiredValues() {
$scope.paymentExpired.value = true;
$scope.remainingTimeStr.value = gettextCatalog.getString('Expired');
$scope.paymentExpired = true;
$scope.remainingTimeStr = gettextCatalog.getString('Expired');
if (countDown) $interval.cancel(countDown);
$timeout(function() {
$scope.$apply();
@ -347,31 +412,27 @@ angular.module('copayApp.controllers').controller('confirmController', function(
};
};
function setWallet(wallet, delayed) {
var stop;
/* sets a wallet on the UI, creates a TXPs for that wallet */
function setWallet(wallet, tx) {
$scope.wallet = wallet;
$scope.fee = $scope.txp = null;
if (stop) {
$timeout.cancel(stop);
stop = null;
}
if (cachedTxp[wallet.id]) {
apply(cachedTxp[wallet.id]);
} else {
stop = $timeout(function() {
createTx(wallet, true, function(err, txp) {
if (err) return;
cachedTxp[wallet.id] = txp;
apply(txp);
});
}, delayed ? 2000 : 1);
}
setButtonText(wallet.credentials.m > 1, !!tx.paypro);
if (tx.paypro)
_paymentTimeControl(tx.paypro.expires);
updateTx(tx, wallet, {
dryRun: true
}, function(err) {
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
});
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
};
var setSendError = function(msg) {
@ -382,75 +443,6 @@ angular.module('copayApp.controllers').controller('confirmController', function(
popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg));
};
function apply(txp) {
$scope.fee = txFormatService.formatAmountStr(txp.fee);
txFormatService.formatAlternativeStr(txp.fee, function(v) {
$scope.feeFiat = v;
});
$scope.txp = txp;
$scope.feeRateStr = (txp.fee / (txp.amount + txp.fee) * 100).toFixed(2) + '%';
$timeout(function() {
$scope.$apply();
});
};
var createTx = function(wallet, dryRun, cb) {
var config = configService.getSync().wallet;
var currentSpendUnconfirmed = config.spendUnconfirmed;
var paypro = $scope.paypro;
var toAddress = $scope.toAddress;
var description = $scope.description;
var unitToSatoshi = config.settings.unitToSatoshi;
var unitDecimals = config.settings.unitDecimals;
// ToDo: use a credential's (or fc's) function for this
if (description && !wallet.credentials.sharedEncryptingKey) {
var msg = gettextCatalog.getString('Could not add message to imported wallet without shared encrypting key');
$log.warn(msg);
return setSendError(msg);
}
if (toAmount > Number.MAX_SAFE_INTEGER) {
var msg = gettextCatalog.getString('Amount too big');
$log.warn(msg);
return setSendError(msg);
}
var txp = {};
var amount;
if ($scope.useSendMax) amount = parseFloat((toAmount * unitToSatoshi).toFixed(0));
else amount = toAmount;
txp.outputs = [{
'toAddress': toAddress,
'amount': amount,
'message': description
}];
if ($scope.sendMaxInfo) {
txp.inputs = $scope.sendMaxInfo.inputs;
txp.fee = $scope.sendMaxInfo.fee;
} else
txp.feeLevel = feeLevel;
txp.message = description;
if (paypro) {
txp.payProUrl = paypro.url;
}
txp.excludeUnconfirmedUtxos = !currentSpendUnconfirmed;
txp.dryRun = dryRun;
walletService.createTx(wallet, txp, function(err, ctxp) {
if (err) {
setSendError(err);
return cb(err);
}
return cb(null, ctxp);
});
};
$scope.openPPModal = function() {
$ionicModal.fromTemplateUrl('views/modals/paypro.html', {
scope: $scope
@ -464,14 +456,11 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.payproModal.hide();
};
$scope.approve = function(onSendStatusChange) {
$scope.approve = function(tx, wallet, onSendStatusChange) {
var wallet = $scope.wallet;
if (!wallet) {
return;
}
if (!tx || !wallet) return;
if ($scope.paypro && $scope.paymentExpired.value) {
if ($scope.paymentExpired) {
popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.'));
$scope.sendStatus = '';
$timeout(function() {
@ -481,46 +470,54 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}
ongoingProcess.set('creatingTx', true, onSendStatusChange);
createTx(wallet, false, function(err, txp) {
getTxp(lodash.clone(tx), wallet, false, function(err, txp) {
ongoingProcess.set('creatingTx', false, onSendStatusChange);
if (err) return;
var config = configService.getSync();
var spendingPassEnabled = walletService.isEncrypted(wallet);
var touchIdEnabled = config.touchIdFor && config.touchIdFor[wallet.id];
var isCordova = $scope.isCordova;
var bigAmount = parseFloat(txFormatService.formatToUSD(txp.amount)) > 20;
var message = gettextCatalog.getString('Sending {{amountStr}} from your {{name}} wallet', {
amountStr: $scope.amountStr,
name: wallet.name
});
var okText = gettextCatalog.getString('Confirm');
var cancelText = gettextCatalog.getString('Cancel');
// confirm txs for more that 20usd, if not spending/touchid is enabled
function confirmTx(cb) {
if (walletService.isEncrypted(wallet))
return cb();
if (!spendingPassEnabled && !touchIdEnabled) {
if (isCordova) {
if (bigAmount) {
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) {
$scope.sendStatus = '';
$timeout(function() {
$scope.$apply();
});
return;
}
publishAndSign(wallet, txp, onSendStatusChange);
});
} else publishAndSign(wallet, txp, onSendStatusChange);
} else {
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) {
$scope.sendStatus = '';
return;
}
publishAndSign(wallet, txp, onSendStatusChange);
});
var amountUsd = parseFloat(txFormatService.formatToUSD(txp.amount));
if (amountUsd <= CONFIRM_LIMIT_USD)
return cb();
var message = gettextCatalog.getString('Sending {{amountStr}} from your {{name}} wallet', {
amountStr: tx.amountStr,
name: wallet.name
});
var okText = gettextCatalog.getString('Confirm');
var cancelText = gettextCatalog.getString('Cancel');
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
return cb(!ok);
});
};
function publishAndSign() {
if (!wallet.canSign() && !wallet.isPrivKeyExternal()) {
$log.info('No signing proposal: No private key');
return walletService.onlyPublish(wallet, txp, function(err) {
if (err) setSendError(err);
}, onSendStatusChange);
}
} else publishAndSign(wallet, txp, onSendStatusChange);
walletService.publishAndSign(wallet, txp, function(err, txp) {
if (err) return setSendError(err);
}, onSendStatusChange);
};
confirmTx(function(nok) {
if (nok) {
$scope.sendStatus = '';
$timeout(function() {
$scope.$apply();
});
return;
}
publishAndSign();
});
});
};
@ -543,68 +540,47 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.statusChangeHandler = statusChangeHandler;
$scope.onConfirm = function() {
$scope.approve(statusChangeHandler);
};
$scope.onSuccessConfirm = function() {
var previousView = $ionicHistory.viewHistory().backView && $ionicHistory.viewHistory().backView.stateName;
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$ionicHistory.removeBackView();
$scope.sendStatus = '';
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.send').then(function() {
$ionicHistory.clearHistory();
$state.transitionTo('tabs.home');
});
};
function publishAndSign(wallet, txp, onSendStatusChange) {
$scope.chooseFeeLevel = function(tx, wallet) {
if (!wallet.canSign() && !wallet.isPrivKeyExternal()) {
$log.info('No signing proposal: No private key');
var scope = $rootScope.$new(true);
scope.network = tx.network;
scope.feeLevel = tx.feeLevel;
scope.noSave = true;
return walletService.onlyPublish(wallet, txp, function(err) {
if (err) setSendError(err);
}, onSendStatusChange);
}
walletService.publishAndSign(wallet, txp, function(err, txp) {
if (err) return setSendError(err);
}, onSendStatusChange);
};
$scope.chooseFeeLevel = function() {
$scope.customFeeLevel = feeLevel;
$ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', {
scope: $scope,
scope: scope,
}).then(function(modal) {
$scope.chooseFeeLevelModal = modal;
$scope.openModal();
scope.chooseFeeLevelModal = modal;
scope.openModal();
});
$scope.openModal = function() {
$scope.chooseFeeLevelModal.show();
scope.openModal = function() {
scope.chooseFeeLevelModal.show();
};
$scope.hideModal = function(customFeeLevel) {
if (customFeeLevel) {
cachedTxp = {};
cachedSendMax = {};
ongoingProcess.set('gettingFeeLevels', true);
setFee(customFeeLevel, function() {
ongoingProcess.set('gettingFeeLevels', false);
resetValues();
if ($scope.wallet) useSelectedWallet();
})
}
$scope.chooseFeeLevelModal.hide();
scope.hideModal = function(customFeeLevel) {
scope.chooseFeeLevelModal.hide();
$log.debug('Custom fee level choosen:' + customFeeLevel + ' was:' + tx.feeLevel);
if (tx.feeLevel == customFeeLevel)
return;
tx.feeLevel = customFeeLevel;
updateTx(tx, wallet, {
clearCache: true,
dryRun: true,
}, function() {
});
};
};

View file

@ -5,7 +5,7 @@ angular.module('copayApp.controllers').controller('rateAppController', function(
$scope.appName = appConfigService.nameCase;
var isAndroid = platformInfo.isAndroid;
var isIOS = platformInfo.isIOS;
var isWP = platformInfo.isWP;
var config = configService.getSync();
$scope.skip = function() {
@ -42,8 +42,6 @@ angular.module('copayApp.controllers').controller('rateAppController', function(
url = $scope.appName == 'Copay' ? defaults.rateApp.copay.android : defaults.rateApp.bitpay.android;
if (isIOS)
url = $scope.appName == 'Copay' ? defaults.rateApp.copay.ios : defaults.rateApp.bitpay.ios;
// if (isWP)
// url = $scope.appName == 'Copay' ? defaults.rateApp.copay.windows : defaults.rateApp.bitpay.windows;
externalLinkService.open(url);
$state.go('tabs.rate.complete', {

View file

@ -50,6 +50,7 @@ angular.module('copayApp.controllers').controller('joinController',
$scope.onQrCodeScannedJoin = function(data) {
$scope.formData.secret = data;
$scope.$apply();
};
if ($stateParams.url) {
@ -136,7 +137,7 @@ angular.module('copayApp.controllers').controller('joinController',
}
if ($scope.formData.seedSource.id == walletService.externalSource.ledger.id || $scope.formData.seedSource.id == walletService.externalSource.trezor.id || $scope.formData.seedSource.id == walletService.externalSource.intelTEE.id) {
var account = $scope.account;
var account = $scope.formData.account;
if (!account || account < 1) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number'));
return;

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, $log, ongoingProcess, platformInfo, $ionicScrollDelegate, txFormatService, bwcError, gettextCatalog, lodash, walletService, popupService, $ionicHistory) {
angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, $log, ongoingProcess, platformInfo, $ionicScrollDelegate, txFormatService, bwcError, gettextCatalog, lodash, walletService, popupService, $ionicHistory, feeService) {
var isGlidera = $scope.isGlidera;
var GLIDERA_LOCK_TIME = 6 * 60 * 60;
var now = Math.floor(Date.now() / 1000);
@ -9,37 +9,41 @@ angular.module('copayApp.controllers').controller('txpDetailsController', functi
$scope.init = function() {
$scope.loading = null;
$scope.isCordova = platformInfo.isCordova;
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
$scope.copayers = $scope.wallet.status.wallet.copayers;
$scope.copayerId = $scope.wallet.credentials.copayerId;
$scope.isShared = $scope.wallet.credentials.n > 1;
$scope.canSign = $scope.wallet.canSign() || $scope.wallet.isPrivKeyExternal();
$scope.color = $scope.wallet.color;
$scope.data = {};
$scope.displayAmount = getDisplayAmount($scope.tx.amountStr);
$scope.displayUnit = getDisplayUnit($scope.tx.amountStr);
displayFeeValues();
initActionList();
checkPaypro();
applyButtonText();
};
function displayFeeValues() {
txFormatService.formatAlternativeStr($scope.tx.fee, function(v) {
$scope.tx.feeFiatStr = v;
});
$scope.tx.feeRateStr = ($scope.tx.fee / ($scope.tx.amount + $scope.tx.fee) * 100).toFixed(2) + '%';
$scope.tx.feeLevelStr = feeService.feeOpts[$scope.tx.feeLevel];
};
function applyButtonText() {
$scope.buttonText = $scope.isCordova ? gettextCatalog.getString('Slide') + ' ' : gettextCatalog.getString('Click') + ' ';
$scope.buttonText = $scope.isCordova && !$scope.isWindowsPhoneApp ? gettextCatalog.getString('Slide') + ' ' : gettextCatalog.getString('Click') + ' ';
var lastSigner = lodash.filter($scope.tx.actions, {
type: 'accept'
}).length == $scope.tx.requiredSignatures - 1;
if (lastSigner)
if (lastSigner) {
$scope.buttonText += gettextCatalog.getString('to send');
else
$scope.successText = gettextCatalog.getString('Payment Sent');
} else {
$scope.buttonText += gettextCatalog.getString('to accept');
};
function getDisplayAmount(amountStr) {
return amountStr.split(' ')[0];
};
function getDisplayUnit(amountStr) {
return amountStr.split(' ')[1];
$scope.successText = gettextCatalog.getString('Payment Accepted');
}
};
function initActionList() {

View file

@ -60,7 +60,7 @@ angular.module('copayApp.controllers').controller('paperWalletController',
$scope.wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, null, function(err, testTx) {
if (err) return cb(err);
var rawTxLength = testTx.serialize().length;
feeService.getCurrentFeeValue('livenet', null, function(err, feePerKB) {
feeService.getCurrentFeeRate('livenet', function(err, feePerKB) {
var opts = {};
opts.fee = Math.round((feePerKB * rawTxLength) / 2000);
$scope.wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, opts, function(err, tx) {

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesController',
function($scope, $rootScope, $timeout, $log, $ionicHistory, configService, profileService, fingerprintService, walletService) {
function($scope, $rootScope, $timeout, $log, $ionicHistory, configService, profileService, fingerprintService, walletService, platformInfo) {
var wallet;
var walletId;
@ -76,7 +76,7 @@ angular.module('copayApp.controllers').controller('preferencesController',
wallet = profileService.getWallet(data.stateParams.walletId);
walletId = wallet.credentials.walletId;
$scope.wallet = wallet;
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
$scope.externalSource = null;
if (!wallet)

View file

@ -2,13 +2,14 @@
angular.module('copayApp.controllers').controller('preferencesFeeController', function($scope, $timeout, $ionicHistory, lodash, gettextCatalog, configService, feeService, ongoingProcess, popupService) {
$scope.save = function(newFee) {
var network;
if ($scope.customFeeLevel) {
$scope.currentFeeLevel = newFee;
updateCurrentValues();
$scope.save = function(newFee) {
$scope.currentFeeLevel = newFee;
updateCurrentValues();
if ($scope.noSave)
return;
}
var opts = {
wallet: {
@ -20,8 +21,6 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
configService.set(opts, function(err) {
if (err) $log.debug(err);
$scope.currentFeeLevel = newFee;
updateCurrentValues();
$timeout(function() {
$scope.$apply();
});
@ -33,8 +32,10 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
});
$scope.init = function() {
$scope.network = $scope.network || 'livenet';
$scope.feeOpts = feeService.feeOpts;
$scope.currentFeeLevel = $scope.customFeeLevel ? $scope.customFeeLevel : feeService.getCurrentFeeLevel();
$scope.currentFeeLevel = $scope.feeLevel || feeService.getCurrentFeeLevel();
$scope.loadingFee = true;
feeService.getFeeLevels(function(err, levels) {
$scope.loadingFee = false;
@ -45,22 +46,27 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
}
$scope.feeLevels = levels;
updateCurrentValues();
$scope.$apply();
$timeout(function() {
$scope.$apply();
});
});
};
var updateCurrentValues = function() {
if (lodash.isEmpty($scope.feeLevels) || lodash.isEmpty($scope.currentFeeLevel)) return;
var feeLevelValue = lodash.find($scope.feeLevels['livenet'], {
var value = lodash.find($scope.feeLevels[$scope.network], {
level: $scope.currentFeeLevel
});
if (lodash.isEmpty(feeLevelValue)) {
if (lodash.isEmpty(value)) {
$scope.feePerSatByte = null;
$scope.avgConfirmationTime = null;
return;
}
$scope.feePerSatByte = (feeLevelValue.feePerKB / 1000).toFixed();
$scope.avgConfirmationTime = feeLevelValue.nbBlocks * 10;
$scope.feePerSatByte = (value.feePerKB / 1000).toFixed();
$scope.avgConfirmationTime = value.nbBlocks * 10;
};
$scope.chooseNewFee = function() {

View file

@ -12,6 +12,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
$scope.homeTip = $stateParams.fromOnboarding;
$scope.isCordova = platformInfo.isCordova;
$scope.isAndroid = platformInfo.isAndroid;
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
$scope.isNW = platformInfo.isNW;
$scope.showRateCard = {};
@ -42,6 +43,11 @@ angular.module('copayApp.controllers').controller('tabHomeController',
}
storageService.getFeedbackInfo(function(error, info) {
if ($scope.isWindowsPhoneApp) {
$scope.showRateCard.value = false;
return;
}
if (!info) {
initFeedBackInfo();
} else {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, bwcError, gettextCatalog) {
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, bwcError, gettextCatalog, scannerService) {
var originalList;
var CONTACTS_SHOW_LIMIT;
@ -120,7 +120,20 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
};
$scope.openScanner = function() {
$state.go('tabs.scan');
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
if (!isWindowsPhoneApp) {
$state.go('tabs.scan');
return;
}
scannerService.useOldScanner(function(err, contents) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
incomingData.redir(contents);
});
};
$scope.showMore = function() {

View file

@ -51,6 +51,7 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isCordova = platformInfo.isCordova;
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
$scope.isDevel = platformInfo.isDevel;
$scope.appName = appConfigService.nameCase;
configService.whenAvailable(function(config) {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, incomingData, lodash, popupService, gettextCatalog) {
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingData, lodash, popupService, gettextCatalog, scannerService) {
$scope.onScan = function(data) {
if (!incomingData.redir(data)) {
@ -22,8 +22,23 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
}, 1);
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$rootScope.hideTabs = '';
});
$scope.chooseScanner = function() {
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
if (!isWindowsPhoneApp) {
$state.go('tabs.scan');
return;
}
scannerService.useOldScanner(function(err, contents) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
incomingData.redir(contents);
});
};
});

View file

@ -1,24 +1,18 @@
'use strict';
angular.module('copayApp.controllers').controller('topUpController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicConfig, lodash, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, bitpayService, bitpayCardService, payproService, bwcError, txFormatService, sendMaxService) {
var amount;
var currency;
var cardId;
var sendMax;
angular.module('copayApp.controllers').controller('topUpController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicConfig, lodash, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, bitpayService, bitpayCardService, payproService, bwcError, txFormatService, sendMaxService, gettextCatalog) {
$scope.isCordova = platformInfo.isCordova;
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
var cardId;
var useSendMax;
var amount;
var currency;
var createdTx;
var message;
var configWallet = configService.getSync().wallet;
var showErrorAndBack = function(title, msg) {
title = title || 'Error';
title = title || gettextCatalog.getString('Error');
$scope.sendStatus = '';
$log.error(msg);
msg = msg.errors ? msg.errors[0].message : msg;
@ -27,17 +21,24 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
});
};
var showError = function(title, msg) {
title = title || 'Error';
var showError = function(title, msg, cb) {
cb = cb || function() {};
title = title || gettextCatalog.getString('Error');
$scope.sendStatus = '';
$log.error(msg);
msg = msg.errors ? msg.errors[0].message : msg;
popupService.showAlert(title, msg);
popupService.showAlert(title, msg, cb);
};
var satToFiat = function(sat, cb) {
txFormatService.toFiat(sat, $scope.currencyIsoCode, function(value) {
return cb(value);
});
};
var publishAndSign = function (wallet, txp, onSendStatusChange, cb) {
if (!wallet.canSign() && !wallet.isPrivKeyExternal()) {
var err = 'No signing proposal: No private key';
var err = gettextCatalog.getString('No signing proposal: No private key');
$log.info(err);
return cb(err);
}
@ -50,7 +51,7 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
var statusChangeHandler = function (processName, showName, isOn) {
$log.debug('statusChangeHandler: ', processName, showName, isOn);
if ( processName == 'topup' && !isOn) {
if (processName == 'topup' && !isOn) {
$scope.sendStatus = 'success';
$timeout(function() {
$scope.$digest();
@ -60,137 +61,237 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
}
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
cardId = data.stateParams.id;
sendMax = data.stateParams.useSendMax;
var setTotalAmount = function(amountSat, invoiceFeeSat, networkFeeSat) {
satToFiat(amountSat, function(a) {
$scope.amount = Number(a);
if (!cardId) {
showErrorAndBack(null, 'No card selected');
return;
satToFiat(invoiceFeeSat, function(i) {
$scope.invoiceFee = Number(i);
satToFiat(networkFeeSat, function(n) {
$scope.networkFee = Number(n);
$scope.totalAmount = $scope.amount + $scope.invoiceFee + $scope.networkFee;
$timeout(function() {
$scope.$digest();
});
});
});
});
};
var createInvoice = function(data, cb) {
bitpayCardService.topUp(cardId, data, function(err, invoiceId) {
if (err) {
return cb({
title: gettextCatalog.getString('Could not create the invoice'),
message: err
});
}
bitpayCardService.getInvoice(invoiceId, function(err, inv) {
if (err) {
return cb({
title: gettextCatalog.getString('Could not get the invoice'),
message: err
});
}
return cb(null, inv);
});
});
};
var createTx = function(wallet, invoice, message, cb) {
var payProUrl = (invoice && invoice.paymentUrls) ? invoice.paymentUrls.BIP73 : null;
if (!payProUrl) {
return cb({
title: gettextCatalog.getString('Error in Payment Protocol'),
message: gettextCatalog.getString('Invalid URL')
});
}
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
var outputs = [];
var toAddress = invoice.bitcoinAddress;
var amountSat = parseInt(invoice.btcDue * 100000000); // BTC to Satoshi
$scope.network = bitpayService.getEnvironment().network;
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network,
hasFunds: true,
minAmount: parsedAmount.amountSat
outputs.push({
'toAddress': toAddress,
'amount': amountSat,
'message': message
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack(null, 'Insufficient funds');
return;
var txp = {
toAddress: toAddress,
amount: amountSat,
outputs: outputs,
message: message,
payProUrl: payProUrl,
excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true,
feeLevel: configWallet.settings.feeLevel || 'normal'
};
walletService.createTx(wallet, txp, function(err, ctxp) {
if (err) {
return cb({
title: gettextCatalog.getString('Could not create transaction'),
message: bwcError.msg(err)
});
}
return cb(null, ctxp);
});
};
var calculateAmount = function(wallet, cb) {
// Global variables defined beforeEnter
var a = amount;
var c = currency;
if (useSendMax) {
sendMaxService.getInfo(wallet, function(err, maxValues) {
if (err) {
return cb({
title: null,
message: err
})
}
if (maxValues.amount == 0) {
return cb({message: gettextCatalog.getString('Insufficient funds for fee')});
}
var maxAmountBtc = Number((maxValues.amount / 100000000).toFixed(8));
createInvoice({amount: maxAmountBtc, currency: 'BTC'}, function(err, inv) {
if (err) return cb(err);
var invoiceFeeSat = parseInt((inv.buyerPaidBtcMinerFee * 100000000).toFixed());
var newAmountSat = maxValues.amount - invoiceFeeSat;
if (newAmountSat <= 0) {
return cb({message: gettextCatalog.getString('Insufficient funds for fee')});
}
return cb(null, newAmountSat, 'sat');
});
});
} else {
return cb(null, a, c);
}
$scope.onWalletSelect($scope.wallets[0]); // Default first wallet
};
var initializeTopUp = function(wallet, parsedAmount) {
$scope.amountUnitStr = parsedAmount.amountUnitStr;
var dataSrc = {
amount: parsedAmount.amount,
currency: parsedAmount.currency
};
ongoingProcess.set('loadingTxInfo', true);
createInvoice(dataSrc, function(err, invoice) {
if (err) {
ongoingProcess.set('loadingTxInfo', false);
showErrorAndBack(err.title, err.message);
return;
}
var invoiceFeeSat = (invoice.buyerPaidBtcMinerFee * 100000000).toFixed();
message = gettextCatalog.getString("Top up {{amountStr}} to debit card ({{cardLastNumber}})", {
amountStr: $scope.amountUnitStr,
cardLastNumber: $scope.lastFourDigits
});
createTx(wallet, invoice, message, function(err, ctxp) {
ongoingProcess.set('loadingTxInfo', false);
if (err) {
showErrorAndBack(err.title, err.message);
return;
}
// Save TX in memory
createdTx = ctxp;
$scope.totalAmountStr = txFormatService.formatAmountStr(ctxp.amount);
setTotalAmount(parsedAmount.amountSat, invoiceFeeSat, ctxp.fee);
});
});
};
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
cardId = data.stateParams.id;
useSendMax = data.stateParams.useSendMax;
amount = data.stateParams.amount;
currency = data.stateParams.currency;
bitpayCardService.get({ cardId: cardId, noRefresh: true }, function(err, card) {
if (err) {
showErrorAndBack(null, err);
return;
}
$scope.cardInfo = card[0];
bitpayCardService.setCurrencySymbol($scope.cardInfo);
bitpayCardService.getRates($scope.cardInfo.currency, function(err, data) {
if (err) $log.error(err);
$scope.rate = data.rate;
});
});
bitpayCardService.setCurrencySymbol(card[0]);
$scope.lastFourDigits = card[0].lastFourDigits;
$scope.currencySymbol = card[0].currencySymbol;
$scope.currencyIsoCode = card[0].currency;
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: bitpayService.getEnvironment().network,
hasFunds: true
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack(null, gettextCatalog.getString('No wallets available'));
return;
}
bitpayCardService.getRates($scope.currencyIsoCode, function(err, r) {
if (err) $log.error(err);
$scope.rate = r.rate;
});
$scope.onWalletSelect($scope.wallets[0]); // Default first wallet
});
});
$scope.topUpConfirm = function() {
var config = configService.getSync();
var configWallet = config.wallet;
var walletSettings = configWallet.settings;
if (!createdTx) {
showError(null, gettextCatalog.getString('Transaction has not been created'));
return;
}
var message = 'Add ' + amount + ' ' + currency + ' to debit card';
var okText = 'Confirm';
var cancelText = 'Cancel';
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return;
var title = gettextCatalog.getString('Confirm');
var okText = gettextCatalog.getString('OK');
var cancelText = gettextCatalog.getString('Cancel');
popupService.showConfirm(title, message, okText, cancelText, function(ok) {
if (!ok) {
$scope.sendStatus = '';
return;
}
var dataSrc = {
amount: amount,
currency: currency
};
ongoingProcess.set('topup', true, statusChangeHandler);
bitpayCardService.topUp(cardId, dataSrc, function(err, invoiceId) {
publishAndSign($scope.wallet, createdTx, function() {}, function(err, txSent) {
if (err) {
ongoingProcess.set('topup', false, statusChangeHandler);
showErrorAndBack('Could not create the invoice', err);
ongoingProcess.set('topup', false);
$scope.sendStatus = '';
showError(gettextCatalog.getString('Could not send transaction'), err);
return;
}
bitpayCardService.getInvoice(invoiceId, function(err, invoice) {
if (err) {
ongoingProcess.set('topup', false, statusChangeHandler);
showError('Could not get the invoice', err);
return;
}
var payProUrl = (invoice && invoice.paymentUrls) ? invoice.paymentUrls.BIP73 : null;
if (!payProUrl) {
ongoingProcess.set('topup', false, statusChangeHandler);
showError('Error in Payment Protocol', 'Invalid URL');
return;
}
payproService.getPayProDetails(payProUrl, function(err, payProDetails) {
if (err) {
ongoingProcess.set('topup', false, statusChangeHandler);
showError('Error fetching invoice', err);
return;
}
var outputs = [];
var toAddress = payProDetails.toAddress;
var amountSat = payProDetails.amount;
var comment = 'Top up ' + amount + ' ' + currency + ' to Debit Card (' + $scope.cardInfo.lastFourDigits + ')';
outputs.push({
'toAddress': toAddress,
'amount': amountSat,
'message': comment
});
var txp = {
toAddress: toAddress,
amount: amountSat,
outputs: outputs,
message: comment,
payProUrl: payProUrl,
excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true,
feeLevel: walletSettings.feeLevel || 'normal'
};
walletService.createTx($scope.wallet, txp, function(err, ctxp) {
if (err) {
ongoingProcess.set('topup', false, statusChangeHandler);
showError('Could not create transaction', bwcError.msg(err));
return;
}
publishAndSign($scope.wallet, ctxp, function() {}, function(err, txSent) {
ongoingProcess.set('topup', false, statusChangeHandler);
if (err) {
showError('Could not send transaction', err);
return;
}
});
});
}, true); // Disable loader
});
});
});
ongoingProcess.set('topup', false, statusChangeHandler);
});
});
};
$scope.showWalletSelector = function() {
@ -200,29 +301,19 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
$scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet;
if (sendMax) {
ongoingProcess.set('retrievingInputs', true);
sendMaxService.getInfo($scope.wallet, function(err, values) {
ongoingProcess.set('retrievingInputs', false);
if (err) {
showErrorAndBack(null, err);
return;
}
var config = configService.getSync().wallet.settings;
var unitName = config.unitName;
var amountUnit = txFormatService.satToUnit(values.amount);
var parsedAmount = txFormatService.parseAmount(
amountUnit,
unitName);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
$timeout(function() {
$scope.$digest();
}, 100);
});
}
ongoingProcess.set('retrievingInputs', true);
calculateAmount(wallet, function(err, a, c) {
ongoingProcess.set('retrievingInputs', false);
if (err) {
createdTx = message = $scope.totalAmountStr = $scope.amountUnitStr = $scope.wallet = null;
showError(err.title, err.message, function() {
$scope.showWalletSelector();
});
return;
}
var parsedAmount = txFormatService.parseAmount(a, c);
initializeTopUp(wallet, parsedAmount);
});
};
$scope.goBackHome = function() {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('txDetailsController', function($rootScope, $log, $ionicHistory, $scope, $timeout, walletService, lodash, gettextCatalog, profileService, externalLinkService, popupService, ongoingProcess, txFormatService, txConfirmNotification) {
angular.module('copayApp.controllers').controller('txDetailsController', function($rootScope, $log, $ionicHistory, $scope, $timeout, walletService, lodash, gettextCatalog, profileService, externalLinkService, popupService, ongoingProcess, txFormatService, txConfirmNotification, feeService) {
var txId;
var listeners = [];
@ -11,20 +11,22 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
$scope.wallet = profileService.getWallet(data.stateParams.walletId);
$scope.color = $scope.wallet.color;
$scope.copayerId = $scope.wallet.credentials.copayerId;
$scope.isShared = $scope.wallet.credentials.n > 1;
$scope.isShared = $scope.wallet.credentials.n > 1;
txConfirmNotification.checkIfEnabled(txId, function(res) {
$scope.txNotification = { value: res };
$scope.txNotification = {
value: res
};
});
});
$scope.$on("$ionicView.afterEnter", function(event) {
updateTx();
listeners = [
$rootScope.$on('bwsEvent', function(e, walletId, type, n) {
if (type == 'NewBlock' && n && n.data && n.data.network == 'livenet') {
updateTxDebounced({hideLoading: true});
updateTxDebounced({
hideLoading: true
});
}
})
];
@ -36,14 +38,6 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
});
});
function getDisplayAmount(amountStr) {
return amountStr.split(' ')[0];
}
function getDisplayUnit(amountStr) {
return amountStr.split(' ')[1];
}
function updateMemo() {
walletService.getTxNote($scope.wallet, $scope.btx.txid, function(err, note) {
if (err) {
@ -108,7 +102,8 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
$scope.btx = txFormatService.processTx(tx);
txFormatService.formatAlternativeStr(tx.fees, function(v) {
$scope.feeFiatStr = v;
$scope.btx.feeFiatStr = v;
$scope.btx.feeRateStr = ($scope.btx.fees / ($scope.btx.amount + $scope.btx.fees) * 100).toFixed(2) + '%';
});
if ($scope.btx.action != 'invalid') {
@ -117,20 +112,30 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
if ($scope.btx.action == 'moved') $scope.title = gettextCatalog.getString('Moved Funds');
}
$scope.displayAmount = getDisplayAmount($scope.btx.amountStr);
$scope.displayUnit = getDisplayUnit($scope.btx.amountStr);
updateMemo();
initActionList();
getFiatRate();
$timeout(function() {
$scope.$apply();
$scope.$digest();
});
feeService.getFeeLevels(function(err, levels) {
if (err) return;
walletService.getLowAmount($scope.wallet, levels, function(err, amount) {
if (err) return;
$scope.btx.lowAmount = tx.amount < amount;
$timeout(function() {
$scope.$apply();
});
});
});
});
};
var updateTxDebounced = lodash.debounce(updateTx, 5000);
$scope.showCommentPopup = function() {
var opts = {};
if ($scope.btx.message) {
@ -197,7 +202,9 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
$scope.txConfirmNotificationChange = function() {
if ($scope.txNotification.value) {
txConfirmNotification.subscribe($scope.wallet, { txid: txId });
txConfirmNotification.subscribe($scope.wallet, {
txid: txId
});
} else {
txConfirmNotification.unsubscribe($scope.wallet, txId);
}

View file

@ -1,6 +1,6 @@
'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, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService) {
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, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService) {
var HISTORY_SHOW_LIMIT = 10;
var currentTxHistoryPage = 0;
@ -47,6 +47,21 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
$scope.txps = lodash.sortBy(txps, 'createdOn').reverse();
};
var analyzeUtxosDone;
var analyzeUtxos = function() {
if (analyzeUtxosDone) return;
feeService.getFeeLevels(function(err, levels){
if (err) return;
walletService.getLowUtxos($scope.wallet, levels, function(err, resp){
if (err || !resp) return;
analyzeUtxosDone = true;
$scope.lowUtxosWarning = resp.warning;
});
});
};
var updateStatus = function(force) {
$scope.updatingStatus = true;
$scope.updateStatusError = null;
@ -72,6 +87,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
$scope.$apply();
});
analyzeUtxos();
});
};
@ -154,9 +171,10 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
});
};
$timeout(function() {
feeService.getFeeLevels(function(err, levels){
walletService.getTxHistory($scope.wallet, {
progressFn: progressFn,
feeLevels: levels,
}, function(err, txHistory) {
$scope.updatingTxHistory = false;
if (err) {