From b5cd9faca78ddba74af060d9c5cf9c177272294b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 25 Sep 2018 16:48:42 +0200 Subject: [PATCH 01/12] Shapeshift, return address should be verified with the coin out --- src/js/services/shapeshift.service.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/services/shapeshift.service.js b/src/js/services/shapeshift.service.js index 73410e478..8fdfe08b9 100644 --- a/src/js/services/shapeshift.service.js +++ b/src/js/services/shapeshift.service.js @@ -63,6 +63,7 @@ angular } 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; @@ -72,7 +73,7 @@ angular // Check the address shapeshiftApiService - .ValidateAddress(withdrawalAddress, coinOut) + .ValidateAddress(returnAddress, coinOut) .then(function onSuccess(response) { if (response && response.isvalid) { // Prepare the transaction shapeshift side From c657cc5bfb2e1984ce5a4ec7d3788eb9c659b7b5 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 26 Sep 2018 15:11:35 +1200 Subject: [PATCH 02/12] Naming anonymous functions for easier debugging. --- src/js/controllers/review.controller.js | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index c82838f7c..c6c484700 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -78,7 +78,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit function onBeforeEnter(event, data) { - console.log('walletSelector onBeforeEnter sendflow ', sendFlowService.state); + console.log('reviewController onBeforeEnter sendflow ', sendFlowService.state); defaults = configService.getDefaults(); sendFlowData = sendFlowService.state.getClone(); originWalletId = sendFlowData.fromWalletId; @@ -95,11 +95,11 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.thirdParty = sendFlowData.thirdParty; switch (vm.thirdParty.id) { case 'shapeshift': - initShapeshift(function (err) { + initShapeshift(function onInitShapeshift(err) { if (err) { // Error stop here ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function onAlert() { $ionicHistory.goBack(); }); } else { @@ -142,18 +142,18 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (!tx || !vm.originWallet) return; if (vm.paymentExpired) { - popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function () { + popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function onAlert() { $ionicHistory.goBack(); })); vm.sendStatus = ''; - $timeout(function() { + $timeout(function onTimeout() { $scope.$apply(); }); return; } ongoingProcess.set('creatingTx', true, statusChangeHandler); - getTxp(lodash.clone(tx), vm.originWallet, false, function(err, txp) { + getTxp(lodash.clone(tx), vm.originWallet, false, function onGetTxp(err, txp) { ongoingProcess.set('creatingTx', false, statusChangeHandler); if (err) return; @@ -170,12 +170,12 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) { $log.info('No signing proposal: No private key'); - return walletService.onlyPublish(vm.originWallet, txp, function(err) { + return walletService.onlyPublish(vm.originWallet, txp, function onOnlyPublish(err) { if (err) setSendError(err); }, statusChangeHandler); } - walletService.publishAndSign(vm.originWallet, txp, function(err, txp) { + walletService.publishAndSign(vm.originWallet, txp, function onPublishAndSign(err, txp) { if (err) return setSendError(err); if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { txConfirmNotification.subscribe(vm.originWallet, { @@ -189,7 +189,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit confirmTx(function(nok) { if (nok) { vm.sendStatus = ''; - $timeout(function() { + $timeout(function onTimeout() { $scope.$apply(); }); return; @@ -375,7 +375,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed; txp.dryRun = dryRun; - walletService.createTx(wallet, txp, function(err, ctxp) { + walletService.createTx(wallet, txp, function onCreateTx(err, ctxp) { if (err) { setSendError(err); return cb(err); @@ -434,7 +434,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } // Check if the recipient is a contact - addressbookService.get(originCoin + address, function(err, contact) { + addressbookService.get(originCoin + address, function onGetContact(err, contact) { if (!err && contact) { handleDestinationAsAddressOfContact(contact); } else { @@ -680,7 +680,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit $timeout(function() { $scope.$apply(); }); - popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function () { + popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function onAlert() { $ionicHistory.goBack(); }); }; @@ -692,7 +692,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit tx.displayAddress = tx.toAddress; } - addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact + addressbookService.get(tx.coin+tx.toAddress, function onGetContact(err, addr) { // Check if the recipient is a contact if (!err && addr) { tx.toName = addr.name; tx.toEmail = addr.email; @@ -711,7 +711,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit updateTx(tx, vm.originWallet, { dryRun: true }, function(err) { - $timeout(function() { + $timeout(function onTimeout() { $scope.$apply(); }, 10); @@ -838,7 +838,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // refresh(); var feeServiceLevel = usingMerchantFee && vm.originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; - feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) { + feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function onGetFeeRate(err, feeRate) { if (err) { ongoingProcess.set('calculatingFee', false); return cb(err); @@ -857,7 +857,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit tx.feeRate = feeRate; } - getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) { + getSendMaxInfo(lodash.clone(tx), wallet, function onGetSendmaxInfo(err, sendMaxInfo) { if (err) { ongoingProcess.set('calculatingFee', false); var msg = gettextCatalog.getString('Error getting SendMax information'); @@ -871,7 +871,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit 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 () { + popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'), function onAlert() { $ionicHistory.goBack(); }); return cb('no_funds'); @@ -897,7 +897,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } console.log('calling getTxp() from getSendMaxInfo cb.'); - getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) { + getTxp(lodash.clone(tx), wallet, opts.dryRun, function onGetTxp(err, txp) { ongoingProcess.set('calculatingFee', false); if (err) { if (err.message == 'Insufficient funds') { @@ -909,7 +909,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee); - txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function(v) { + txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function onFormatAlternativeStr(v) { // txp.alternativeFeeStr = v; // if (txp.alternativeFeeStr.substring(0, 4) == '0.00') // txp.alternativeFeeStr = '< ' + txp.alternativeFeeStr; From 5b7eb57d76a3ba7252b7937e78942a30d707ecc9 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 26 Sep 2018 15:12:37 +1200 Subject: [PATCH 03/12] Fix for sending ShapeShift transaction to Shapeshift deposit address, rather than own wallet. --- src/js/controllers/review.controller.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index c6c484700..2490fc053 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -46,7 +46,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.sendingTitle = gettextCatalog.getString('You are sending'); vm.sendStatus = ''; vm.showAddress = true; - vm.thirdParty = false; + vm.thirdParty = null; vm.wallet = null; vm.memoExpanded = false; @@ -270,6 +270,9 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit txp: {}, }; + if (vm.thirdParty && vm.thirdParty.id === "shapeshift") { + tx.toAddress = vm.thirdParty.toAddress; + } if (data.stateParams.requiredFeeRate) { vm.usingMerchantFee = true; @@ -283,7 +286,14 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit var B = tx.coin === 'bch' ? bitcoreCash : bitcore; var networkName; try { - if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer + // Final destination is a wallet, but this transaction must go to an address for the first stage of the exchange. + if (sendFlowData.thirdParty && sendFlowData.thirdParty.id === 'shapeshift') { + networkName = (new B.Address(tx.toAddress)).network.name; + tx.network = networkName; + console.log('calling setupTx() for shapeshift.'); + setupTx(tx); + + } else if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer ongoingProcess.set('generatingNewAddress', true); var toWallet = profileService.getWallet(destinationWalletId); @@ -498,9 +508,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit 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; @@ -525,9 +532,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (err) { return cb(err); } else { - vm.destination.kind = 'shapeshift'; - vm.destination.address = toAddress; - tx.toAddress = shapeshiftData.toAddress; + // Want it to appear like a wallet-to-wallet transfer, so don't set the main toAddress. + vm.thirdParty.toAddress = shapeshiftData.toAddress; vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; vm.memoExpanded = !!vm.memo; ongoingProcess.set('connectingShapeshift', false); From 6bf0de9974459ea1a3f77bf78348cbec67ae8b55 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 26 Sep 2018 15:31:29 +1200 Subject: [PATCH 04/12] Remove debug logging and other cleanup. --- src/js/controllers/review.controller.js | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 2490fc053..42388933b 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -78,7 +78,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit function onBeforeEnter(event, data) { - console.log('reviewController onBeforeEnter sendflow ', sendFlowService.state); + $log.debug('reviewController onBeforeEnter sendflow ', sendFlowService.state); defaults = configService.getDefaults(); sendFlowData = sendFlowService.state.getClone(); originWalletId = sendFlowData.fromWalletId; @@ -125,7 +125,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit 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(); @@ -246,7 +245,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit }; function createVanityTransaction(data) { - console.log('createVanityTransaction()'); var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal'; // Grab stateParams @@ -290,7 +288,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (sendFlowData.thirdParty && sendFlowData.thirdParty.id === 'shapeshift') { networkName = (new B.Address(tx.toAddress)).network.name; tx.network = networkName; - console.log('calling setupTx() for shapeshift.'); setupTx(tx); } else if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer @@ -298,24 +295,24 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit 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); + if (err) { + $log.error('Error getting address for wallet.', err); + throw new Error(err.message); + } 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); + $log.error('Error setting up tx', e); var message = gettextCatalog.getString('Invalid address'); popupService.showAlert(null, message, function () { $ionicHistory.nextViewOptions({ @@ -568,7 +565,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit }, 1000); function setExpirationTime() { - console.log('setExpirationTime()'); var now = Math.floor(Date.now() / 1000); if (now > expirationTime) { @@ -902,7 +898,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit return cb(); } - console.log('calling getTxp() from getSendMaxInfo cb.'); getTxp(lodash.clone(tx), wallet, opts.dryRun, function onGetTxp(err, txp) { ongoingProcess.set('calculatingFee', false); if (err) { @@ -924,8 +919,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (v.substring(0, 1) === "<") { vm.feeLessThanACent = true; } - - console.log("fiat", vm.feeFiat); }); @@ -935,14 +928,11 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit 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(); From a9b84f394299f6fd0c2231197c19dc0804cc2e0f Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 26 Sep 2018 15:39:12 +1200 Subject: [PATCH 05/12] IIFE to reduce conflicts for merging. --- src/js/controllers/review.controller.js | 1599 ++++++++++++----------- 1 file changed, 801 insertions(+), 798 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 42388933b..36e734c81 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -1,944 +1,947 @@ '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.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 = null; - 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) { - $log.debug('reviewController onBeforeEnter sendflow ', sendFlowService.state); - defaults = configService.getDefaults(); - sendFlowData = sendFlowService.state.getClone(); - originWalletId = sendFlowData.fromWalletId; - satoshis = parseInt(sendFlowData.amount, 10); - 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 onInitShapeshift(err) { - if (err) { - // Error stop here - ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function onAlert() { - $ionicHistory.goBack(); - }); - } else { - _next(data); - } - }); - break; - case 'bip70': - initBip70(); - default: - _next(data); - break; + 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.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 = null; + 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) { + $log.debug('reviewController onBeforeEnter sendflow ', sendFlowService.state); + defaults = configService.getDefaults(); + sendFlowData = sendFlowService.state.getClone(); + originWalletId = sendFlowData.fromWalletId; + satoshis = parseInt(sendFlowData.amount, 10); + 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 onInitShapeshift(err) { + if (err) { + // Error stop here + ongoingProcess.set('connectingShapeshift', false); + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function onAlert() { + $ionicHistory.goBack(); + }); + } else { + _next(data); + } + }); + break; + case 'bip70': + initBip70(); + default: + _next(data); + break; + } + } else { + _next(data); } - } 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); - unitFromSat = 1 / config.wallet.settings.unitToSatoshi; - } - updateSendAmounts(); - getOriginWalletBalance(vm.originWallet); - handleDestinationAsAddress(toAddress, coin); - handleDestinationAsWallet(sendFlowData.toWalletId); - createVanityTransaction(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); + 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; + vm.approve = function() { + + if (!tx || !vm.originWallet) return; - if (vm.paymentExpired) { - popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function onAlert() { - $ionicHistory.goBack(); - })); - vm.sendStatus = ''; - $timeout(function onTimeout() { - $scope.$apply(); - }); - return; - } + if (vm.paymentExpired) { + popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function onAlert() { + $ionicHistory.goBack(); + })); + vm.sendStatus = ''; + $timeout(function onTimeout() { + $scope.$apply(); + }); + return; + } + + ongoingProcess.set('creatingTx', true, statusChangeHandler); + getTxp(lodash.clone(tx), vm.originWallet, false, function onGetTxp(err, txp) { + ongoingProcess.set('creatingTx', false, statusChangeHandler); + if (err) return; - ongoingProcess.set('creatingTx', true, statusChangeHandler); - getTxp(lodash.clone(tx), vm.originWallet, false, function onGetTxp(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(); - // confirm txs for more that 20usd, if not spending/touchid is enabled - function confirmTx(cb) { - if (walletService.isEncrypted(vm.originWallet)) + var amountUsd = parseFloat(txFormatService.formatToUSD(vm.originWallet.coin, txp.amount)); 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'); - function publishAndSign() { - if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) { - $log.info('No signing proposal: No private key'); + return walletService.onlyPublish(vm.originWallet, txp, function onOnlyPublish(err) { + if (err) setSendError(err); + }, statusChangeHandler); + } - return walletService.onlyPublish(vm.originWallet, txp, function onOnlyPublish(err) { - if (err) setSendError(err); + walletService.publishAndSign(vm.originWallet, txp, function onPublishAndSign(err, txp) { + if (err) return setSendError(err); + if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { + txConfirmNotification.subscribe(vm.originWallet, { + txid: txp.txid + }); + lastTxId = txp.txid; + } }, statusChangeHandler); - } + }; - walletService.publishAndSign(vm.originWallet, txp, function onPublishAndSign(err, txp) { - if (err) return setSendError(err); - if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { - txConfirmNotification.subscribe(vm.originWallet, { - txid: txp.txid + confirmTx(function(nok) { + if (nok) { + vm.sendStatus = ''; + $timeout(function onTimeout() { + $scope.$apply(); }); - lastTxId = txp.txid; + return; } - }, statusChangeHandler); - }; - - confirmTx(function(nok) { - if (nok) { - vm.sendStatus = ''; - $timeout(function onTimeout() { - $scope.$apply(); - }); - return; - } - publishAndSign(); + publishAndSign(); + }); }); - }); - }; + }; - vm.chooseFeeLevel = function(tx, wallet) { + vm.chooseFeeLevel = function(tx, wallet) { - if (wallet.coin == 'bch') return; - if (usingMerchantFee) return; + 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; + 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; - } + 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(); - }; + $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); + scope.hideModal = function(newFeeLevel, customFeePerKB) { + scope.chooseFeeLevelModal.hide(); + $log.debug('New fee level choosen:' + newFeeLevel + ' was:' + tx.feeLevel); - usingCustomFee = newFeeLevel == 'custom' ? true : false; + usingCustomFee = newFeeLevel == 'custom' ? true : false; - if (tx.feeLevel == newFeeLevel && !usingCustomFee) return; + if (tx.feeLevel == newFeeLevel && !usingCustomFee) return; - tx.feeLevel = newFeeLevel; - if (usingCustomFee) tx.feeRate = parseInt(customFeePerKB); + tx.feeLevel = newFeeLevel; + if (usingCustomFee) tx.feeRate = parseInt(customFeePerKB); - updateTx(tx, vm.originWallet, { - clearCache: true, - dryRun: true - }, function() {}); - }; - }; - - function createVanityTransaction(data) { - 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: {}, + updateTx(tx, vm.originWallet, { + clearCache: true, + dryRun: true + }, function() {}); + }; }; - if (vm.thirdParty && vm.thirdParty.id === "shapeshift") { - tx.toAddress = vm.thirdParty.toAddress; - } + function createVanityTransaction(data) { + 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 (vm.thirdParty && vm.thirdParty.id === "shapeshift") { + tx.toAddress = vm.thirdParty.toAddress; + } - if (tx.coin && tx.coin === 'bch') { - tx.feeLevel = 'normal'; - } + if (data.stateParams.requiredFeeRate) { + vm.usingMerchantFee = true; + tx.feeRate = parseInt(data.stateParams.requiredFeeRate); + } - var B = tx.coin === 'bch' ? bitcoreCash : bitcore; - var networkName; - try { - // Final destination is a wallet, but this transaction must go to an address for the first stage of the exchange. - if (sendFlowData.thirdParty && sendFlowData.thirdParty.id === 'shapeshift') { - networkName = (new B.Address(tx.toAddress)).network.name; - tx.network = networkName; - setupTx(tx); - - } else 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. - walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) { - if (err) { - $log.error('Error getting address for wallet.', err); - throw new Error(err.message); - } - ongoingProcess.set('generatingNewAddress', false); - tx.toAddress = addr; + if (tx.coin && tx.coin === 'bch') { + tx.feeLevel = 'normal'; + } + + var B = tx.coin === 'bch' ? bitcoreCash : bitcore; + var networkName; + try { + // Final destination is a wallet, but this transaction must go to an address for the first stage of the exchange. + if (sendFlowData.thirdParty && sendFlowData.thirdParty.id === 'shapeshift') { networkName = (new B.Address(tx.toAddress)).network.name; tx.network = networkName; setupTx(tx); + + } else 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. + walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) { + if (err) { + $log.error('Error getting address for wallet.', err); + throw new Error(err.message); + } + ongoingProcess.set('generatingNewAddress', false); + tx.toAddress = addr; + networkName = (new B.Address(tx.toAddress)).network.name; + tx.network = networkName; + setupTx(tx); + }); + } else { // This is a Wallet-to-address transfer + networkName = (new B.Address(tx.toAddress)).network.name; + tx.network = networkName; + setupTx(tx); + } + } catch (e) { + $log.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(); + }); }); - } else { // This is a Wallet-to-address transfer - networkName = (new B.Address(tx.toAddress)).network.name; - tx.network = networkName; - setupTx(tx); + return; } - } catch (e) { - $log.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 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(); + 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); - }; + //ongoingProcess.set('retrievingInputs', true); + walletService.getSendMaxInfo(wallet, { + feePerKb: tx.feeRate, + excludeUnconfirmedUtxos: !tx.spendUnconfirmed, + returnInputs: true, + }, cb); + }; - function getTxp(tx, wallet, dryRun, 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); - } + // 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); - } + 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; - } + var txp = {}; - txp.message = vm.memo; + txp.outputs = [{ + 'toAddress': tx.toAddress, + 'amount': tx.amount, + 'message': vm.memo + }]; - if (tx.paypro) { - txp.payProUrl = tx.paypro.url; - } - txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed; - txp.dryRun = dryRun; - walletService.createTx(wallet, txp, function onCreateTx(err, ctxp) { - if (err) { - setSendError(err); - return cb(err); + 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; } - 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.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] : ''; + txp.message = vm.memo; - if (walletStatus.alternativeBalanceAvailable) { - balanceFiatAmount = walletStatus.spendableBalanceAlternative; - balanceFiatCurrency = walletStatus.alternativeIsoCode; + if (tx.paypro) { + txp.payProUrl = tx.paypro.url; } - } - - if (priceDisplayIsFiat) { - displayAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount; - displayCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode; - } else { - displayAmount = balanceCryptoAmount; - displayCurrency = balanceCryptoCurrencyCode; - } - - return { - amount: displayAmount, - currency: displayCurrency + txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed; + txp.dryRun = dryRun; + walletService.createTx(wallet, txp, function onCreateTx(err, ctxp) { + if (err) { + setSendError(err); + return cb(err); + } + return cb(null, ctxp); + }); }; - } - function goBack() { - sendFlowService.router.goBack(); - } + function getWalletBalanceDisplayText(wallet) { + var balanceCryptoAmount = ''; + var balanceCryptoCurrencyCode = ''; + var balanceFiatAmount = ''; + var balanceFiatCurrency = '' + var displayAmount = ''; + var displayCurrency = ''; + + var walletStatus = null; + if (wallet.status.isValid) { + walletStatus = wallet.status; + } else if (wallet.cachedStatus.isValid) { + walletStatus = wallet.cachedStatus; + } - function handleDestinationAsAddress(address, originCoin) { - if (!address) { - return; - } + if (walletStatus) { + var cryptoBalanceParts = walletStatus.spendableBalanceStr.split(' '); + balanceCryptoAmount = cryptoBalanceParts[0]; + balanceCryptoCurrencyCode = cryptoBalanceParts.length > 1 ? cryptoBalanceParts[1] : ''; - // Check if the recipient is a contact - addressbookService.get(originCoin + address, function onGetContact(err, contact) { - if (!err && contact) { - handleDestinationAsAddressOfContact(contact); - } else { - if (originCoin === 'bch') { - vm.destination.address = bitcoinCashJsService.readAddress(address).cashaddr; - } else { - vm.destination.address = address; + if (walletStatus.alternativeBalanceAvailable) { + balanceFiatAmount = walletStatus.spendableBalanceAlternative; + balanceFiatCurrency = walletStatus.alternativeIsoCode; } - vm.destination.kind = 'address'; } - }); - } + if (priceDisplayIsFiat) { + displayAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount; + displayCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode; + } else { + displayAmount = balanceCryptoAmount; + displayCurrency = balanceCryptoCurrencyCode; + } - 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; - } + return { + amount: displayAmount, + currency: displayCurrency + }; + } - function handleDestinationAsWallet(walletId) { - destinationWalletId = walletId; - if (!destinationWalletId) { - return; + function goBack() { + sendFlowService.router.goBack(); } - 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; + function handleDestinationAsAddress(address, originCoin) { + if (!address) { + return; + } + + // Check if the recipient is a contact + addressbookService.get(originCoin + address, function onGetContact(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'; + } + }); - 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 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 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 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 initShapeshift(cb) { - vm.sendingTitle = gettextCatalog.getString('You are shifting'); + 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, + }; + } - var toWallet = profileService.getWallet(destinationWalletId); - vm.destination.name = toWallet.name; - vm.destination.color = toWallet.color; - vm.destination.currency = toWallet.coin.toUpperCase(); + function initShapeshift(cb) { + vm.sendingTitle = gettextCatalog.getString('You are shifting'); + 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) { + + ongoingProcess.set('connectingShapeshift', true); + walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { 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) { + walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) { if (err) { return cb(err); - } else { - // Want it to appear like a wallet-to-wallet transfer, so don't set the main toAddress. - vm.thirdParty.toAddress = shapeshiftData.toAddress; - vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; - vm.memoExpanded = !!vm.memo; - ongoingProcess.set('connectingShapeshift', false); - cb(); - } + } + + // 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 { + // Want it to appear like a wallet-to-wallet transfer, so don't set the main toAddress. + vm.thirdParty.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; + 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); } - 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() { + function startExpirationTimer(expirationTime) { + vm.paymentExpired = false; setExpirationTime(); - }, 1000); - function setExpirationTime() { - var now = Math.floor(Date.now() / 1000); + countDown = $interval(function() { + setExpirationTime(); + }, 1000); - if (now > expirationTime) { - setExpiredValues(); - return; - } + function setExpirationTime() { + var now = Math.floor(Date.now() / 1000); - var totalSecs = expirationTime - now; - var m = Math.floor(totalSecs / 60); - var s = totalSecs % 60; - vm.remainingTimeStr = m + ":" + ('0' + s).slice(-2); - }; + if (now > expirationTime) { + setExpiredValues(); + return; + } - function setExpiredValues() { - vm.paymentExpired = true; - vm.remainingTimeStr = gettextCatalog.getString('Expired'); - vm.readyToSend = false; - if (countDown) $interval.cancel(countDown); - $timeout(function() { - $scope.$apply(); - }); - }; - }; + var totalSecs = expirationTime - now; + var m = Math.floor(totalSecs / 60); + var s = totalSecs % 60; + vm.remainingTimeStr = m + ":" + ('0' + s).slice(-2); + }; - function updateSendAmounts() { - if (typeof satoshis !== 'number') { - return; - } + function setExpiredValues() { + vm.paymentExpired = true; + vm.remainingTimeStr = gettextCatalog.getString('Expired'); + vm.readyToSend = false; + if (countDown) $interval.cancel(countDown); + $timeout(function() { + $scope.$apply(); + }); + }; + }; - 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 = ''; + function updateSendAmounts() { + if (typeof satoshis !== 'number') { 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; + 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'); + 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 { - vm.buttonText = gettextCatalog.getString('Click to send'); + 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 onAlert() { - $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 onGetContact(err, addr) { // Check if the recipient is a contact - if (!err && addr) { - tx.toName = addr.name; - tx.toEmail = addr.email; - tx.recipientType = 'contact'; - } - }); + 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(); + }); + }; - vm.showAddress = false; + function setSendError(msg) { + $scope.sendStatus = ''; + vm.readyToSend = false; + $timeout(function() { + $scope.$apply(); + }); + popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function onAlert() { + $ionicHistory.goBack(); + }); + }; + function setupTx(tx) { + if (tx.coin === 'bch') { + tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr; + } else { + tx.displayAddress = tx.toAddress; + } - setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro); + addressbookService.get(tx.coin+tx.toAddress, function onGetContact(err, addr) { // Check if the recipient is a contact + if (!err && addr) { + tx.toName = addr.name; + tx.toEmail = addr.email; + tx.recipientType = 'contact'; + } + }); - if (tx.paypro) - startExpirationTimer(tx.paypro.expires); + vm.showAddress = false; - updateTx(tx, vm.originWallet, { - dryRun: true - }, function(err) { - $timeout(function onTimeout() { - $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 = ''; + setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro); - 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 (tx.paypro) + startExpirationTimer(tx.paypro.expires); - 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'); - }; + updateTx(tx, vm.originWallet, { + dryRun: true + }, function(err) { + $timeout(function onTimeout() { + $scope.$apply(); + }, 10); - 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; + // 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); + // } + // }); + } - popupService.showAlert(null, msg, function() {}); - }; + function showSendMaxWarning(wallet, sendMaxInfo) { + var feeAlternative = '', + msg = ''; - 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'; + 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) + })); + } - // Clear the send flow service state - sendFlowService.state.clear(); + 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'); + }; - 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"; + 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) + }); } - // 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; - } - }; + var warningMsg = verifyExcludedUtxos(); - function updateTx(tx, wallet, opts, cb) { - ongoingProcess.set('calculatingFee', true); + if (!lodash.isEmpty(warningMsg)) + msg += '\n' + warningMsg; - if (opts.clearCache) { - tx.txp = {}; - } + popupService.showAlert(null, msg, function() {}); + }; - // $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 onGetFeeRate(err, feeRate) { - if (err) { - ongoingProcess.set('calculatingFee', false); - return cb(err); + 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; } + }; - 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; + function updateTx(tx, wallet, opts, cb) { + ongoingProcess.set('calculatingFee', true); + + if (opts.clearCache) { + tx.txp = {}; } - getSendMaxInfo(lodash.clone(tx), wallet, function onGetSendmaxInfo(err, sendMaxInfo) { + // $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 onGetFeeRate(err, feeRate) { if (err) { ongoingProcess.set('calculatingFee', false); - var msg = gettextCatalog.getString('Error getting SendMax information'); - return setSendError(msg); + return cb(err); } - if (sendMaxInfo) { - - $log.debug('Send max info', sendMaxInfo); + 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; + } - if (tx.sendMax && sendMaxInfo.amount == 0) { + getSendMaxInfo(lodash.clone(tx), wallet, function onGetSendmaxInfo(err, sendMaxInfo) { + if (err) { ongoingProcess.set('calculatingFee', false); - setNotReady(gettextCatalog.getString('Insufficient confirmed funds')); - popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'), function onAlert() { - $ionicHistory.goBack(); - }); - return cb('no_funds'); + var msg = gettextCatalog.getString('Error getting SendMax information'); + return setSendError(msg); } - tx.sendMaxInfo = sendMaxInfo; - tx.amount = tx.sendMaxInfo.amount; - satoshis = tx.amount; - updateSendAmounts(); - ongoingProcess.set('calculatingFee', false); - $timeout(function() { - showSendMaxWarning(wallet, sendMaxInfo); - }, 200); - } + if (sendMaxInfo) { - // txp already generated for this wallet? - if (tx.txp[wallet.id]) { - ongoingProcess.set('calculatingFee', false); - vm.readyToSend = true; - updateSendAmounts(); - $scope.$apply(); - return cb(); - } + $log.debug('Send max info', sendMaxInfo); - getTxp(lodash.clone(tx), wallet, opts.dryRun, function onGetTxp(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')); + 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 onAlert() { + $ionicHistory.goBack(); + }); return cb('no_funds'); - } else - return cb(err); + } + + tx.sendMaxInfo = sendMaxInfo; + tx.amount = tx.sendMaxInfo.amount; + satoshis = tx.amount; + updateSendAmounts(); + ongoingProcess.set('calculatingFee', false); + $timeout(function() { + showSendMaxWarning(wallet, sendMaxInfo); + }, 200); } - txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee); - txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function onFormatAlternativeStr(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; + // txp already generated for this wallet? + if (tx.txp[wallet.id]) { + ongoingProcess.set('calculatingFee', false); + vm.readyToSend = true; + updateSendAmounts(); + $scope.$apply(); + return cb(); + } + + getTxp(lodash.clone(tx), wallet, opts.dryRun, function onGetTxp(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 onFormatAlternativeStr(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; + } - 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; + }); - tx.txp[wallet.id] = txp; - $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); - vm.readyToSend = true; - updateSendAmounts(); - $scope.$apply(); + 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; + + tx.txp[wallet.id] = txp; + $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); + vm.readyToSend = true; + updateSendAmounts(); + $scope.$apply(); - return cb(); + return cb(); + }); }); }); - }); - } + } -} + } +})(); From a426a5abc0a14e4dfbf413b812712aaa42c7cdcf Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 26 Sep 2018 16:27:47 +1200 Subject: [PATCH 06/12] Now handles URIs that begin with 'bch:'. --- src/js/services/bitcoin-uri.service.js | 10 ++++++++-- src/js/services/bitcoin-uri.service.spec.js | 22 ++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/js/services/bitcoin-uri.service.js b/src/js/services/bitcoin-uri.service.js index 3da84f3da..669c14bf3 100644 --- a/src/js/services/bitcoin-uri.service.js +++ b/src/js/services/bitcoin-uri.service.js @@ -151,6 +151,7 @@ hasPassphrase: false, type: 1, }, + isTestnet: false, isValid: false, label: '', message: '', @@ -170,7 +171,6 @@ "req-param0": '', "req-param1": '' }, - testnet: false, url: '' // For BIP70 } @@ -204,7 +204,13 @@ } else if (/^(?:bitcoincash)|(?:bitcoin-cash)$/.test(preColonLower)) { parsed.coin = 'bch'; - parsed.test = false; + parsed.isTestnet = false; + addressAndParams = colonSplit[2].trim(); + console.log('Is bch'); + + } else if (/^(?:bch)$/.test(preColonLower)) { + parsed.coin = 'bch'; + parsed.isTestnet = false; addressAndParams = colonSplit[2].trim(); console.log('Is bch'); diff --git a/src/js/services/bitcoin-uri.service.spec.js b/src/js/services/bitcoin-uri.service.spec.js index 2ddbd0d2e..bf6c9ce9e 100644 --- a/src/js/services/bitcoin-uri.service.spec.js +++ b/src/js/services/bitcoin-uri.service.spec.js @@ -30,7 +30,7 @@ describe('bitcoinUriService', function() { expect(parsed.isValid).toBe(true); expect(parsed.coin).toBe('bch'); expect(parsed.publicAddress).toBeUndefined(); - expect(parsed.isTestnet).toBeUndefined(); + expect(parsed.isTestnet).toBe(false); expect(parsed.url).toBe('https://bitpay.com/i/SmHdie5dvBnG5kouZzEPzu'); }); @@ -171,6 +171,16 @@ describe('bitcoinUriService', function() { expect(parsed.isTestnet).toBe(false); }); + it('legacy address with bch prefix', function() { + + var parsed = bitcoinUriService.parse('bch:19yUdM2H7sADrabR6Afu9zTpmwqr6WYprX'); + + expect(parsed.isValid).toBe(true); + expect(parsed.coin).toBe('bch'); + expect(parsed.publicAddress.legacy).toBe('19yUdM2H7sADrabR6Afu9zTpmwqr6WYprX'); + expect(parsed.isTestnet).toBe(false); + }); + it('cashAddr testnet with prefix', function() { var parsed = bitcoinUriService.parse('bchtest:qpcz6pmurq9ctg5848trzz9zmuuygj4q5qam7ph3gt'); @@ -191,6 +201,16 @@ describe('bitcoinUriService', function() { expect(parsed.isTestnet).toBe(false); }); + it('cashAddr with bch prefix', function() { + + var parsed = bitcoinUriService.parse('bch:qpqzqtjqqc00nsxj0e3kevz65ujg4yt5z5w99jap5f'); + + expect(parsed.isValid).toBe(true); + expect(parsed.coin).toBe('bch'); + expect(parsed.publicAddress.cashAddr).toBe('qpqzqtjqqc00nsxj0e3kevz65ujg4yt5z5w99jap5f'); + expect(parsed.isTestnet).toBe(false); + }); + it('cashAddr with dash', function() { var parsed = bitcoinUriService.parse('bitcoin-cash:qpshfu3dk5s3e7zdcgdcun6xgxtra6uyxs7g580js0'); From 4f7004f5a3ab63836519ba7af3512dd8c4eb13b0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 26 Sep 2018 15:31:48 +0200 Subject: [PATCH 07/12] Set variables before enter in the screen. (Fix lost after merging) --- src/js/controllers/review.controller.js | 103 +++++++++++++----------- 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 36e734c81..d7d69f611 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -9,49 +9,6 @@ angular 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.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 = null; - vm.wallet = null; - vm.memoExpanded = false; - // Functions vm.goBack = goBack; vm.onSuccessConfirm = onSuccessConfirm; @@ -78,9 +35,58 @@ angular $scope.$on("$ionicView.beforeEnter", onBeforeEnter); + function initVariables() { + 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 = null; + vm.wallet = null; + vm.memoExpanded = false; + } function onBeforeEnter(event, data) { $log.debug('reviewController onBeforeEnter sendflow ', sendFlowService.state); + + // Reset from last time + initVariables(); + defaults = configService.getDefaults(); sendFlowData = sendFlowService.state.getClone(); originWalletId = sendFlowData.fromWalletId; @@ -397,14 +403,14 @@ angular var balanceCryptoAmount = ''; var balanceCryptoCurrencyCode = ''; var balanceFiatAmount = ''; - var balanceFiatCurrency = '' + var balanceFiatCurrency = ''; var displayAmount = ''; var displayCurrency = ''; var walletStatus = null; - if (wallet.status.isValid) { + if (wallet.status && wallet.status.isValid) { walletStatus = wallet.status; - } else if (wallet.cachedStatus.isValid) { + } else if (wallet.cachedStatus && wallet.cachedStatus.isValid) { walletStatus = wallet.cachedStatus; } @@ -507,13 +513,15 @@ angular 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) { @@ -942,6 +950,5 @@ angular }); }); } - } })(); From 621c60a2cedc4e7aebef25370b2c3f692e909318 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 26 Sep 2018 20:15:25 +0200 Subject: [PATCH 08/12] Set thirdParty on the sendflow --- src/js/controllers/amount.controller.js | 7 +- src/js/controllers/review.controller.js | 92 +++++++++---------- .../controllers/wallet-selector.controller.js | 9 +- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/js/controllers/amount.controller.js b/src/js/controllers/amount.controller.js index 07f31bb3e..f837ff2f3 100644 --- a/src/js/controllers/amount.controller.js +++ b/src/js/controllers/amount.controller.js @@ -30,7 +30,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.sendableFunds = ''; vm.showSendMaxButton = false; vm.showSendLimitMaxButton = false; - vm.thirdParty = false; + vm.thirdParty = null; vm.unit = ''; // Functions @@ -88,11 +88,16 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, sendFlowService.state.pop(); } + + initCurrencies(); passthroughParams = sendFlowService.state.getClone(); console.log('amount onBeforeEnter after back sendflow ', passthroughParams); + // Init thirdParty, should be done for all the variable + vm.thirdParty = null; + vm.fromWalletId = passthroughParams.fromWalletId; vm.toWalletId = passthroughParams.toWalletId; vm.minAmount = parseFloat(passthroughParams.minAmount); diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index d7d69f611..67a4164c8 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -9,6 +9,50 @@ angular 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 = null; + vm.wallet = null; + vm.memoExpanded = false; + // Functions vm.goBack = goBack; vm.onSuccessConfirm = onSuccessConfirm; @@ -35,57 +79,11 @@ angular $scope.$on("$ionicView.beforeEnter", onBeforeEnter); - function initVariables() { - 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 = null; - vm.wallet = null; - vm.memoExpanded = false; - } - function onBeforeEnter(event, data) { $log.debug('reviewController onBeforeEnter sendflow ', sendFlowService.state); // Reset from last time - initVariables(); + vm.thirdParty = null; defaults = configService.getDefaults(); sendFlowData = sendFlowService.state.getClone(); diff --git a/src/js/controllers/wallet-selector.controller.js b/src/js/controllers/wallet-selector.controller.js index 036727333..3d954821c 100644 --- a/src/js/controllers/wallet-selector.controller.js +++ b/src/js/controllers/wallet-selector.controller.js @@ -52,9 +52,6 @@ angular $scope.specificAmount = $scope.specificAlternativeAmount = ''; $scope.isPaymentRequest = true; } - if ($scope.params.thirdParty) { - $scope.thirdParty = $scope.params.thirdParty; - } }; function onEnter (event, data) { @@ -62,7 +59,7 @@ angular $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; }); - if ($scope.thirdParty) { + if ($scope.params.thirdParty) { // Third party services specific logic handleThirdPartyIfShapeshift(); } @@ -102,8 +99,8 @@ angular } function handleThirdPartyIfShapeshift() { - console.log($scope.thirdParty, $scope.coin); - if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the + console.log($scope.params.thirdParty, $scope.coin); + if ($scope.params.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the $scope.coin = profileService.getWallet(fromWalletId).coin; if ($scope.coin === 'bch') { $scope.coin = 'btc'; From 8f67fde60b2b6b517245968047c3f36758839acf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 26 Sep 2018 21:02:03 +0200 Subject: [PATCH 09/12] Fix - init wallet list --- src/js/controllers/wallet-selector.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/controllers/wallet-selector.controller.js b/src/js/controllers/wallet-selector.controller.js index 3d954821c..0ccb3bd1f 100644 --- a/src/js/controllers/wallet-selector.controller.js +++ b/src/js/controllers/wallet-selector.controller.js @@ -114,6 +114,8 @@ angular var walletsAll = []; var walletsSufficientFunds = []; $scope.walletsInsufficientFunds = []; // For origin screen + $scope.walletsBtc = []; + $scope.walletsBch = []; if ($scope.type === 'origin') { $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); From 6c0cf4d62231531843be19201091fbb7f56dac0f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 26 Sep 2018 21:35:30 +0200 Subject: [PATCH 10/12] Add a link to shapeshift in case it doesn't work --- src/js/controllers/review.controller.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 67a4164c8..40fe81eba 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -6,7 +6,7 @@ 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) { + function reviewController(addressbookService, externalLinkService, 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 = ''; @@ -105,8 +105,12 @@ angular if (err) { // Error stop here ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function onAlert() { - $ionicHistory.goBack(); + popupService.showConfirm(gettextCatalog.getString('Shapeshift Error'), err.toString(), gettextCatalog.getString('Open') + " Shapeshift", gettextCatalog.getString('Go Back'), function onConfirm(hasConfirm) { + if (hasConfirm) { + externalLinkService.open("https://shapeshift.io"); + } else { + $ionicHistory.goBack(); + } }); } else { _next(data); From dd61e72bcfaac15b2b9e66c0c12f8021e609e421 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 26 Sep 2018 21:42:35 +0200 Subject: [PATCH 11/12] Fix go back anyway --- src/js/controllers/review.controller.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 40fe81eba..c4923a5f4 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -108,9 +108,8 @@ angular popupService.showConfirm(gettextCatalog.getString('Shapeshift Error'), err.toString(), gettextCatalog.getString('Open') + " Shapeshift", gettextCatalog.getString('Go Back'), function onConfirm(hasConfirm) { if (hasConfirm) { externalLinkService.open("https://shapeshift.io"); - } else { - $ionicHistory.goBack(); } + $ionicHistory.goBack(); }); } else { _next(data); From e14ab5dab3450d796004b045d9d74a2fdfc8166d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 26 Sep 2018 21:46:07 +0200 Subject: [PATCH 12/12] Update appConfig.json rc4 --- app-template/bitcoincom/appConfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index 5818a4006..48298ea0c 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -24,9 +24,9 @@ "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "pushSenderId": "1036948132229", "description": "A Secure Bitcoin Wallet", - "version": "5.1.4", - "fullVersion": "5.1-rc3", - "androidVersion": "501400", + "version": "5.1.5", + "fullVersion": "5.1-rc4", + "androidVersion": "501500", "_extraCSS": "", "_enabledExtensions": { "coinbase": false,