'use strict'; (function () { angular .module('copayApp.controllers') .controller('reviewController', reviewController); function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, clipboardService, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicModal, ionicToast, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, sendFlowService, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { var vm = this; vm.buttonText = ''; vm.destination = { address: '', balanceAmount: '', balanceCurrency: '', coin: '', color: '', currency: '', currencyColor: '', kind: '', // 'address', 'contact', 'wallet' name: '' }; vm.displayAddress = ''; vm.feeCrypto = ''; vm.feeFiat = ''; vm.fiatCurrency = ''; vm.feeIsHigh = false; vm.feeLessThanACent = false; vm.isCordova = platformInfo.isCordova; vm.memo = ''; vm.notReadyMessage = ''; vm.origin = { balanceAmount: '', balanceCurrency: '', currency: '', currencyColor: '', }; vm.originWallet = null; vm.paymentExpired = false; vm.personalNotePlaceholder = gettextCatalog.getString('Enter text here'); vm.primaryAmount = ''; vm.primaryCurrency = ''; vm.usingMerchantFee = false; vm.readyToSend = false; vm.remainingTimeStr = ''; vm.secondaryAmount = ''; vm.secondaryCurrency = ''; vm.sendingTitle = gettextCatalog.getString('You are sending'); vm.sendStatus = ''; vm.showAddress = true; vm.thirdParty = false; vm.wallet = null; vm.memoExpanded = false; // Functions vm.goBack = goBack; vm.onSuccessConfirm = onSuccessConfirm; vm.onShareTransaction = onShareTransaction; var sendFlowData; var config = null; var coin = ''; var countDown = null; var defaults = {}; var usingCustomFee = false; var usingMerchantFee = false; var destinationWalletId = ''; var lastTxId = ''; var originWalletId = ''; var priceDisplayIsFiat = true; var satoshis = null; var toAddress = ''; var tx = {}; var txPayproData = null; var unitFromSat = 0; var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15; $scope.$on("$ionicView.beforeEnter", onBeforeEnter); function onBeforeEnter(event, data) { console.log('review onBeforeEnter sendflow ', sendFlowService.state); // Reset from last time vm.memo = ''; defaults = configService.getDefaults(); sendFlowData = sendFlowService.state.getClone(); originWalletId = sendFlowData.fromWalletId; if (typeof sendFlowData.amount === 'string') { satoshis = parseInt(sendFlowData.amount, 10); } else { satoshis = sendFlowData.amount; } toAddress = sendFlowData.toAddress; destinationWalletId = sendFlowData.toWalletId; vm.displayAddress = sendFlowData.displayAddress; vm.originWallet = profileService.getWallet(originWalletId); vm.origin.currency = vm.originWallet.coin.toUpperCase(); coin = vm.originWallet.coin; if (sendFlowData.thirdParty) { vm.thirdParty = sendFlowData.thirdParty; switch (vm.thirdParty.id) { case 'shapeshift': initShapeshift(function (err) { if (err) { // Error stop here ongoingProcess.set('connectingShapeshift', false); popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { $ionicHistory.goBack(); }); } else { _next(data); } }); break; case 'bip70': initBip70(); default: _next(data); break; } } else { _next(data); } function _next() { configService.get(function onConfig(err, configCache) { if (err) { $log.err('Error getting config.', err); } else { config = configCache; priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor); console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc'); unitFromSat = 1 / config.wallet.settings.unitToSatoshi; } updateSendAmounts(); getOriginWalletBalance(vm.originWallet); handleDestinationAsAddress(toAddress, coin); handleDestinationAsWallet(sendFlowData.toWalletId); createVanityTransaction(data); }); } } vm.approve = function() { if (!tx || !vm.originWallet) return; if (vm.paymentExpired) { popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function () { $ionicHistory.goBack(); })); vm.sendStatus = ''; $timeout(function() { $scope.$apply(); }); return; } ongoingProcess.set('creatingTx', true, statusChangeHandler); getTxp(lodash.clone(tx), vm.originWallet, false, function(err, txp) { ongoingProcess.set('creatingTx', false, statusChangeHandler); if (err) return; // confirm txs for more that 20usd, if not spending/touchid is enabled function confirmTx(cb) { if (walletService.isEncrypted(vm.originWallet)) return cb(); var amountUsd = parseFloat(txFormatService.formatToUSD(vm.originWallet.coin, txp.amount)); return cb(); }; function publishAndSign() { if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) { $log.info('No signing proposal: No private key'); return walletService.onlyPublish(vm.originWallet, txp, function(err) { if (err) setSendError(err); }, statusChangeHandler); } walletService.publishAndSign(vm.originWallet, txp, function(err, txp) { if (err) return setSendError(err); if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { txConfirmNotification.subscribe(vm.originWallet, { txid: txp.txid }); lastTxId = txp.txid; } }, statusChangeHandler); }; confirmTx(function(nok) { if (nok) { vm.sendStatus = ''; $timeout(function() { $scope.$apply(); }); return; } publishAndSign(); }); }); }; vm.chooseFeeLevel = function(tx, wallet) { if (wallet.coin == 'bch') return; if (usingMerchantFee) return; var scope = $rootScope.$new(true); scope.network = tx.network; scope.feeLevel = tx.feeLevel; scope.noSave = true; scope.coin = vm.originWallet.coin; if (usingCustomFee) { scope.customFeePerKB = tx.feeRate; scope.feePerSatByte = tx.feeRate / 1000; } $ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', { scope: scope, backdropClickToClose: false, hardwareBackButtonClose: false }).then(function(modal) { scope.chooseFeeLevelModal = modal; scope.openModal(); }); scope.openModal = function() { scope.chooseFeeLevelModal.show(); }; scope.hideModal = function(newFeeLevel, customFeePerKB) { scope.chooseFeeLevelModal.hide(); $log.debug('New fee level choosen:' + newFeeLevel + ' was:' + tx.feeLevel); usingCustomFee = newFeeLevel == 'custom' ? true : false; if (tx.feeLevel == newFeeLevel && !usingCustomFee) return; tx.feeLevel = newFeeLevel; if (usingCustomFee) tx.feeRate = parseInt(customFeePerKB); updateTx(tx, vm.originWallet, { clearCache: true, dryRun: true }, function() {}); }; }; function createVanityTransaction(data) { console.log('createVanityTransaction()'); var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal'; // Grab stateParams tx = { amount: parseInt(sendFlowData.amount), sendMax: sendFlowData.sendMax, fromWalletId: sendFlowData.fromWalletId, toAddress: sendFlowData.toAddress, paypro: txPayproData, feeLevel: configFeeLevel, spendUnconfirmed: config.wallet.spendUnconfirmed, // Vanity tx info (not in the real tx) recipientType: vm.destination.kind || null, toName: vm.destination.name || null, toEmail: vm.destination.email || null, toColor: vm.destination.color || null, network: false, coin: vm.originWallet.coin, txp: {}, }; if (data.stateParams.requiredFeeRate) { vm.usingMerchantFee = true; tx.feeRate = parseInt(data.stateParams.requiredFeeRate); } if (tx.coin && tx.coin === 'bch') { tx.feeLevel = 'normal'; } var B = tx.coin === 'bch' ? bitcoreCash : bitcore; var networkName; try { if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer ongoingProcess.set('generatingNewAddress', true); var toWallet = profileService.getWallet(destinationWalletId); // We need an address to send to, so we ask the walletService to create a new address for the toWallet. console.log('Getting address for wallet...'); walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) { console.log('getAddress cb called', err); ongoingProcess.set('generatingNewAddress', false); tx.toAddress = addr; networkName = (new B.Address(tx.toAddress)).network.name; tx.network = networkName; console.log('calling setupTx() for wallet.'); setupTx(tx); }); } else { // This is a Wallet-to-address transfer networkName = (new B.Address(tx.toAddress)).network.name; tx.network = networkName; console.log('calling setupTx() for address.'); setupTx(tx); } } catch (e) { console.error('Error setting up tx', e); var message = gettextCatalog.getString('Invalid address'); popupService.showAlert(null, message, function () { $ionicHistory.nextViewOptions({ disableAnimate: true, historyRoot: true }); $state.go('tabs.send').then(function () { $ionicHistory.clearHistory(); }); }); return; } } function getOriginWalletBalance(originWallet) { var balanceText = getWalletBalanceDisplayText(vm.originWallet); vm.origin.balanceAmount = balanceText.amount; vm.origin.balanceCurrency = balanceText.currency; } 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.amount > 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.amount, 'message': vm.memo }]; if (tx.sendMaxInfo) { txp.inputs = tx.sendMaxInfo.inputs; txp.fee = tx.sendMaxInfo.fee; } else { if (usingCustomFee || usingMerchantFee) { txp.feePerKb = tx.feeRate; } else txp.feeLevel = tx.feeLevel; } txp.message = vm.memo; 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 getWalletBalanceDisplayText(wallet) { var balanceCryptoAmount = ''; var balanceCryptoCurrencyCode = ''; var balanceFiatAmount = ''; var balanceFiatCurrency = ''; var displayAmount = ''; var displayCurrency = ''; var walletStatus = null; if (wallet.status && wallet.status.isValid) { walletStatus = wallet.status; } else if (wallet.cachedStatus.isValid) { walletStatus = wallet.cachedStatus; } if (walletStatus) { var cryptoBalanceParts = walletStatus.spendableBalanceStr.split(' '); balanceCryptoAmount = cryptoBalanceParts[0]; balanceCryptoCurrencyCode = cryptoBalanceParts.length > 1 ? cryptoBalanceParts[1] : ''; if (walletStatus.alternativeBalanceAvailable) { balanceFiatAmount = walletStatus.spendableBalanceAlternative; balanceFiatCurrency = walletStatus.alternativeIsoCode; } } if (priceDisplayIsFiat) { displayAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount; displayCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode; } else { displayAmount = balanceCryptoAmount; displayCurrency = balanceCryptoCurrencyCode; } return { amount: displayAmount, currency: displayCurrency }; } function goBack() { sendFlowService.router.goBack(); } function handleDestinationAsAddress(address, originCoin) { if (!address) { return; } // Check if the recipient is a contact addressbookService.get(originCoin + address, function(err, contact) { if (!err && contact) { handleDestinationAsAddressOfContact(contact); } else { if (originCoin === 'bch') { vm.destination.address = bitcoinCashJsService.readAddress(address).cashaddr; } else { vm.destination.address = address; } vm.destination.kind = 'address'; } }); } function handleDestinationAsAddressOfContact(contact) { vm.destination.kind = 'contact'; vm.destination.name = contact.name; vm.destination.email = contact.email; vm.destination.color = contact.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; vm.destination.currency = contact.coin.toUpperCase(); vm.destination.currencyColor = vm.destination.color; } function handleDestinationAsWallet(walletId) { destinationWalletId = walletId; if (!destinationWalletId) { return; } var destinationWallet = profileService.getWallet(destinationWalletId); vm.destination.coin = destinationWallet.coin; vm.destination.color = destinationWallet.color; vm.destination.currency = destinationWallet.coin.toUpperCase(); vm.destination.kind = 'wallet'; vm.destination.name = destinationWallet.name; if (defaults) { vm.destination.currencyColor = vm.destination.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; } var balanceText = getWalletBalanceDisplayText(destinationWallet); vm.destination.balanceAmount = balanceText.amount; vm.destination.balanceCurrency = balanceText.currency; } function initBip70() { vm.sendingTitle = gettextCatalog.getString('You are paying'); vm.memo = vm.thirdParty.memo; vm.memoExpanded = !!vm.memo; vm.destination.name = vm.thirdParty.name; txPayproData = { caTrusted: vm.thirdParty.caTrusted, domain: vm.thirdParty.domain, expires: vm.thirdParty.expires, toAddress: toAddress, url: vm.thirdParty.url, verified: vm.thirdParty.verified, }; } function initShapeshift(cb) { vm.sendingTitle = gettextCatalog.getString('You are shifting'); if (!vm.thirdParty.data) { vm.thirdParty.data = {}; } var toWallet = profileService.getWallet(destinationWalletId); vm.destination.name = toWallet.name; vm.destination.color = toWallet.color; vm.destination.currency = toWallet.coin.toUpperCase(); ongoingProcess.set('connectingShapeshift', true); walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { if (err) { return cb(err); } walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) { if (err) { return cb(err); } // Need to use the correct service to do it. var amount = parseFloat(satoshis / 100000000); shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, amount, function onShiftIt(err, shapeshiftData) { if (err) { return cb(err); } else { vm.destination.kind = 'shapeshift'; vm.destination.address = toAddress; tx.toAddress = shapeshiftData.toAddress; vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; vm.memoExpanded = !!vm.memo; ongoingProcess.set('connectingShapeshift', false); cb(); } }); }); }); } function onShareTransaction() { var explorerTxUrl = 'https://explorer.bitcoin.com/' + tx.coin + '/tx/' + lastTxId; if (platformInfo.isCordova) { var text = gettextCatalog.getString('Take a look at this Bitcoin Cash transaction here: ') + explorerTxUrl; if (coin === 'btc') { text = gettextCatalog.getString('Take a look at this Bitcoin transaction here: ') + explorerTxUrl; } window.plugins.socialsharing.share(text, null, null, null); } else { ionicToast.show(gettextCatalog.getString('Copied to clipboard'), 'bottom', false, 3000); clipboardService.copyToClipboard(explorerTxUrl); } } function startExpirationTimer(expirationTime) { vm.paymentExpired = false; setExpirationTime(); countDown = $interval(function() { setExpirationTime(); }, 1000); function setExpirationTime() { console.log('setExpirationTime()'); var now = Math.floor(Date.now() / 1000); if (now > expirationTime) { setExpiredValues(); return; } var totalSecs = expirationTime - now; var m = Math.floor(totalSecs / 60); var s = totalSecs % 60; vm.remainingTimeStr = m + ":" + ('0' + s).slice(-2); }; function setExpiredValues() { vm.paymentExpired = true; vm.remainingTimeStr = gettextCatalog.getString('Expired'); vm.readyToSend = false; if (countDown) $interval.cancel(countDown); $timeout(function() { $scope.$apply(); }); }; }; function updateSendAmounts() { if (typeof satoshis !== 'number') { return; } var cryptoAmount = ''; var cryptoCurrencyCode = ''; var amountStr = txFormatService.formatAmountStr(coin, satoshis); if (amountStr) { var amountParts = amountStr.split(' '); cryptoAmount = amountParts[0]; cryptoCurrencyCode = amountParts.length > 1 ? amountParts[1] : ''; } // Want to avoid flashing of amount strings so do all formatting after this has returned. txFormatService.formatAlternativeStr(coin, satoshis, function(v) { if (!v) { vm.primaryAmount = cryptoAmount; vm.primaryCurrency = cryptoCurrencyCode; vm.secondaryAmount = ''; vm.secondaryCurrency = ''; return; } vm.secondaryAmount = vm.primaryAmount; vm.secondaryCurrency = vm.primaryCurrency; var fiatParts = v.split(' '); var fiatAmount = fiatParts[0]; var fiatCurrency = fiatParts.length > 1 ? fiatParts[1] : ''; if (priceDisplayIsFiat) { vm.primaryAmount = fiatAmount; vm.primaryCurrency = fiatCurrency; vm.secondaryAmount = cryptoAmount; vm.secondaryCurrency = cryptoCurrencyCode; } else { vm.primaryAmount = cryptoAmount; vm.primaryCurrency = cryptoCurrencyCode; vm.secondaryAmount = fiatAmount; vm.secondaryCurrency = fiatCurrency; } }); } function onSuccessConfirm() { vm.sendStatus = ''; $ionicHistory.nextViewOptions({ disableAnimate: true, historyRoot: true }); $state.go('tabs.send').then(function() { $ionicHistory.clearHistory(); $state.transitionTo('tabs.home'); }); }; function setButtonText(isMultisig, isPayPro) { if (isPayPro) { if (vm.isCordova) { vm.buttonText = gettextCatalog.getString('Slide to pay'); } else { vm.buttonText = gettextCatalog.getString('Click to pay'); } } else if (isMultisig) { if (vm.isCordova) { vm.buttonText = gettextCatalog.getString('Slide to accept'); } else { vm.buttonText = gettextCatalog.getString('Click to accept'); } } else { if (vm.isCordova) { vm.buttonText = gettextCatalog.getString('Slide to send'); } else { vm.buttonText = gettextCatalog.getString('Click to send'); } } } function setNotReady(msg, criticalError) { vn.readyToSend = false; vm.notReadyMessage = msg; $scope.criticalError = criticalError; $log.warn('Not ready to make the payment:' + msg); $timeout(function() { $scope.$apply(); }); }; function setSendError(msg) { $scope.sendStatus = ''; vm.readyToSend = false; $timeout(function() { $scope.$apply(); }); popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function () { $ionicHistory.goBack(); }); }; function setupTx(tx) { if (tx.coin === 'bch') { tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr; } else { tx.displayAddress = tx.toAddress; } addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact if (!err && addr) { tx.toName = addr.name; tx.toEmail = addr.email; tx.recipientType = 'contact'; } }); vm.showAddress = false; setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro); if (tx.paypro) startExpirationTimer(tx.paypro.expires); updateTx(tx, vm.originWallet, { dryRun: true }, function(err) { $timeout(function() { $scope.$apply(); }, 10); }); // setWalletSelector(tx.coin, tx.network, tx.amount, function(err) { // if (err) { // return exitWithError('Could not update wallets'); // } // // if (vm.wallets.length > 1) { // vm.showWalletSelector(); // } else if (vm.wallets.length) { // setWallet(vm.wallets[0], tx); // } // }); } function showSendMaxWarning(wallet, sendMaxInfo) { var feeAlternative = '', msg = ''; 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(wallet.coin, sendMaxInfo.amountBelowFee) })); } 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(vm.originWallet.coin, sendMaxInfo.amountAboveMaxSize) })); } return warningMsg.join('\n'); }; feeAlternative = txFormatService.formatAlternativeStr(vm.originWallet.coin, sendMaxInfo.fee); if (feeAlternative) { msg = gettextCatalog.getString("{{feeAlternative}} will be deducted for bitcoin networking fees ({{fee}}).", { fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee), feeAlternative: feeAlternative }); } else { msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees).", { fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee) }); } var warningMsg = verifyExcludedUtxos(); if (!lodash.isEmpty(warningMsg)) msg += '\n' + warningMsg; popupService.showAlert(null, msg, function() {}); }; function statusChangeHandler(processName, showName, isOn) { $log.debug('statusChangeHandler: ', processName, showName, isOn); if ( ( processName === 'broadcastingTx' || ((processName === 'signingTx') && vm.originWallet.m > 1) || (processName == 'sendingTx' && !vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) ) && !isOn) { // Show the popup vm.sendStatus = 'success'; // Clear the send flow service state sendFlowService.state.clear(); if ($state.current.name === "tabs.send.review") { // XX SP: Otherwise all open wallets on other devices play this sound if you have been in a send flow before on that device. soundService.play('misc/payment_sent.mp3'); } var channel = "firebase"; if (platformInfo.isNW) { channel = "ga"; } // When displaying Fiat, if the formatting fails, the crypto will be the primary amount. var amount = unitFromSat * satoshis; var log = new window.BitAnalytics.LogEvent("transfer_success", [{ "coin": vm.originWallet.coin, "type": "outgoing", "amount": amount, "fees": vm.feeCrypto }], [channel, "adjust"]); window.BitAnalytics.LogEventHandlers.postEvent(log); $timeout(function() { $scope.$digest(); }, 100); } else if (showName) { vm.sendStatus = showName; } }; function updateTx(tx, wallet, opts, cb) { ongoingProcess.set('calculatingFee', true); if (opts.clearCache) { tx.txp = {}; } // $scope.tx = tx; // function updateAmount() { // if (!tx.amount) return; // // // Amount // tx.amountStr = txFormatService.formatAmountStr(originWallet.coin, tx.amount); // tx.amountValueStr = tx.amountStr.split(' ')[0]; // tx.amountUnitStr = tx.amountStr.split(' ')[1]; // txFormatService.formatAlternativeStr(wallet.coin, tx.amount, function(v) { // var parts = v.split(' '); // tx.alternativeAmountStr = v; // tx.alternativeAmountValueStr = parts[0]; // tx.alternativeAmountUnitStr = (parts.length > 0) ? parts[1] : ''; // }); // } // // updateAmount(); // refresh(); var feeServiceLevel = usingMerchantFee && vm.originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) { if (err) { ongoingProcess.set('calculatingFee', false); return cb(err); } var msg; if (usingCustomFee) { msg = gettextCatalog.getString('Custom'); tx.feeLevelName = msg; } else if (usingMerchantFee) { $log.info('Using Merchant Fee:' + tx.feeRate + ' vs. Urgent level:' + feeRate); msg = gettextCatalog.getString('Suggested by Merchant'); tx.feeLevelName = msg; } else { tx.feeLevelName = feeService.feeOpts[tx.feeLevel]; tx.feeRate = feeRate; } getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) { if (err) { ongoingProcess.set('calculatingFee', false); 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) { ongoingProcess.set('calculatingFee', false); setNotReady(gettextCatalog.getString('Insufficient confirmed funds')); popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'), function () { $ionicHistory.goBack(); }); return cb('no_funds'); } tx.sendMaxInfo = sendMaxInfo; tx.amount = tx.sendMaxInfo.amount; satoshis = tx.amount; updateSendAmounts(); ongoingProcess.set('calculatingFee', false); $timeout(function() { showSendMaxWarning(wallet, sendMaxInfo); }, 200); } // txp already generated for this wallet? if (tx.txp[wallet.id]) { ongoingProcess.set('calculatingFee', false); vm.readyToSend = true; updateSendAmounts(); $scope.$apply(); return cb(); } console.log('calling getTxp() from getSendMaxInfo cb.'); getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) { ongoingProcess.set('calculatingFee', false); if (err) { if (err.message == 'Insufficient funds') { setNotReady(gettextCatalog.getString('Insufficient funds')); popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); return cb('no_funds'); } else return cb(err); } txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee); txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function(v) { // txp.alternativeFeeStr = v; // if (txp.alternativeFeeStr.substring(0, 4) == '0.00') // txp.alternativeFeeStr = '< ' + txp.alternativeFeeStr; vm.feeFiat = v; vm.fiatCurrency = config.wallet.settings.alternativeIsoCode; if (v.substring(0, 1) === "<") { vm.feeLessThanACent = true; } console.log("fiat", vm.feeFiat); }); var per = (txp.fee / (txp.amount + txp.fee) * 100); var perString = per.toFixed(2); txp.feeRatePerStr = (perString == '0.00' ? '< ' : '') + perString + '%'; txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PERCENTAGE; vm.feeCrypto = (unitFromSat * txp.fee).toFixed(8); vm.feeIsHigh = txp.feeToHigh; console.log("crypto", vm.feeCrypto); tx.txp[wallet.id] = txp; $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); vm.readyToSend = true; updateSendAmounts(); console.log('readyToSend:', vm.readyToSend); $scope.$apply(); return cb(); }); }); }); } } })();