diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 29f920f4f..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Launch Program", - "program": "${workspaceFolder}/www/index.html" - } - ] -} \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 7968f2510..1092b3de2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -366,13 +366,13 @@ module.exports = function(grunt) { grunt.registerTask('build-mobile-release', ['build-ios-release', 'build-android-release']); // Build ios - grunt.registerTask('start-ios', ['default', 'exec:build_ios_debug', 'exec:xcode']); - grunt.registerTask('build-ios-debug', ['default', 'exec:build_ios_debug']); + grunt.registerTask('start-ios', ['exec:build_ios_debug', 'exec:xcode']); + grunt.registerTask('build-ios-debug', ['exec:build_ios_debug']); grunt.registerTask('build-ios-release', ['prod', 'exec:build_ios_release']); // Build android grunt.registerTask('start-android', ['build-android-debug', 'exec:run_android']); - grunt.registerTask('build-android-debug', ['default', 'exec:build_android_debug']); + grunt.registerTask('build-android-debug', ['exec:build_android_debug']); grunt.registerTask('start-android-emulator', ['build-android-debug', 'exec:run_android_emulator']); grunt.registerTask('build-android-release', ['prod', 'exec:build_android_release', 'sign-android']); grunt.registerTask('sign-android', ['exec:sign_android']); diff --git a/README.md b/README.md index ae2788217..75ced5808 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,3 @@ -This is a fork of the Bitcoin.com wallet to add additional features. -Features included: - - - Zero fee transactions (only works for Bitcoin Cash). You will be asked for, if you want to send a transaction as zero fee on the confirmation page. - - ## Zero fee transactions: - Because most network nodes on the Bitcoin Cash network don't relay zero fee txs, you will experience some strange issues, but don't worry: for me personally the Bitcoin.com pool has included all my zero fee transactions, but please beware that the receiver probably won't see your tx before it has been confirmed and please do also keep in mind, that the transactions coming after it won't confirm or be seen before the zero fee one has been confirmed. - - If you do already have a Bitcoin.com wallet, you need to create a new one to use this feature or change the wallet URL to: https://bws.freepages.dk/bws/api - - ## Disclaimer - - Please beware this is my personal experimental project. You are more than welcome to play with it, but I don't take any responsibility of loss of funds due to errors in the code, so please make sure you made a backup before running this software. - - ## Builds - - You can build the software yourself using the instructions below or use prebuilt binaries which can be found here (currently Windows and Linux only): https://ipfs.io/ipfs/QmR1DaS3QsDS48SzAWKUWFfmtMfJc4tgMtkSk3JFmuzewe - - ## - - The Bitcoin.com wallet is a fork of the Copay Wallet (https://github.com/bitpay/copay). The Bitcoin.com wallet is a secure bitcoin wallet platform for both desktop and mobile devices. It uses [Bitcore Wallet Service](https://github.com/Bitcoin-com/bitcore-wallet-service) (our fork of the [Bitpay Bitcore Wallet Service](https://github.com/bitpay/bitcore-wallet-service)) (BWS) for peer synchronization and network interfacing. diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index 2eab6232d..a73fae33f 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.3", - "fullVersion": "5.1-rc2", - "androidVersion": "501003", + "version": "5.1.2", + "fullVersion": "5.1-rc1", + "androidVersion": "501002", "_extraCSS": "", "_enabledExtensions": { "coinbase": false, diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index ae4bc7153..76950df73 100644 --- a/src/js/controllers/confirm.js +++ b/src/js/controllers/confirm.js @@ -517,8 +517,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( if (!lodash.isEmpty(warningMsg)) msg += '\n' + warningMsg; - popupService.showAlert(null, msg, function() {}); - + popupService.showAlert(null, msg, function() {}); }; $scope.onWalletSelect = function(wallet) { diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index ae30382fb..15f982f2f 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -1,974 +1,952 @@ '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; +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; + 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 = false; + vm.wallet = null; + vm.memoExpanded = false; - // Functions - vm.goBack = goBack; - vm.onSuccessConfirm = onSuccessConfirm; - vm.onShareTransaction = onShareTransaction; + // 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 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; + var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15; - $scope.$on("$ionicView.beforeEnter", onBeforeEnter); + $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); - }); - } + function onBeforeEnter(event, data) { + console.log('review onBeforeEnter sendflow ', sendFlowService.state); + 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.approve = function() { - - if (!tx || !vm.originWallet) return; + vm.displayAddress = sendFlowData.displayAddress; + vm.originWallet = profileService.getWallet(originWalletId); + vm.origin.currency = vm.originWallet.coin.toUpperCase(); + coin = vm.originWallet.coin; - 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 (sendFlowData.thirdParty) { + vm.thirdParty = sendFlowData.thirdParty; + switch (vm.thirdParty.id) { + case 'shapeshift': + initShapeshift(function (err) { 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; + // Error stop here 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); - - popupService.showConfirm(null, 'Do you want this transaction to be sent without a fee?', 'Yes', 'No', function(ok) { - if(ok){ - tx.feeRate = 0; - // tx.feeLevel = 'free'; - usingCustomFee = true; - } - 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() {}); - //popupService.showConfirm(null, msg, null, null, 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 (tx.feeLevel == 'free'){ - // tx.feeRate = 0; - // } - // else - 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 () { + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { $ionicHistory.goBack(); }); - return cb('no_funds'); + } else { + _next(data); } - - 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(); }); - }); + 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.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(); + }); + }); + }); + } + +} diff --git a/src/js/controllers/servicesController.js b/src/js/controllers/servicesController.js index 0f9b6d4f8..62b13c041 100644 --- a/src/js/controllers/servicesController.js +++ b/src/js/controllers/servicesController.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('servicesController', function(externalLinkService, $scope, $ionicScrollDelegate, $timeout, servicesService, configService) { +angular.module('copayApp.controllers').controller('servicesController', function($scope, $ionicScrollDelegate, $timeout, servicesService, configService) { $scope.hide = false; configService.whenAvailable(function(config) { @@ -20,8 +20,4 @@ angular.module('copayApp.controllers').controller('servicesController', function }, 10); }; - $scope.open = function(url) { - externalLinkService.open(url, false); - } - }); diff --git a/src/js/controllers/shapeshift.js b/src/js/controllers/shapeshift.js index 652f54c34..0dac21a11 100644 --- a/src/js/controllers/shapeshift.js +++ b/src/js/controllers/shapeshift.js @@ -10,7 +10,7 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi walletsBtc = profileService.getWallets({coin: 'btc'}); walletsBch = profileService.getWallets({coin: 'bch'}); $scope.fromWallets = lodash.filter(walletsBtc.concat(walletsBch), function(w) { - return (w.status && w.status.balance && w.status.balance.availableAmount > 0); + return w.status.balance.availableAmount > 0; }); $scope.singleFromWallet = $scope.fromWallets.length === 1; diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js index 645e46739..320afe320 100644 --- a/src/js/controllers/tab-receive.js +++ b/src/js/controllers/tab-receive.js @@ -2,7 +2,6 @@ angular.module('copayApp.controllers').controller('tabReceiveController', function($rootScope, $scope, $timeout, $log, $ionicModal, $state, $ionicHistory, $ionicPopover, storageService, platformInfo, walletService, profileService, configService, lodash, gettextCatalog, popupService, bwcError, bitcoinCashJsService, $ionicNavBarDelegate, sendFlowService, txFormatService, soundService, clipboardService) { - var CLOSE_NORMAL = 1000; var listeners = []; $scope.bchAddressType = { type: 'cashaddr' }; var bchAddresses = {}; @@ -11,11 +10,12 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi $scope.isCordova = platformInfo.isCordova; $scope.isNW = platformInfo.isNW; - var currentAddressSocket = null; - var paymentSubscriptionObj = { op:'addr_sub' }; + var currentAddressSocket = {}; + var paymentSubscriptionObj = { op:"addr_sub" } + + var config; $scope.displayBalanceAsFiat = true; - $scope.$on('$ionicView.beforeLeave', onBeforeLeave); $scope.requestSpecificAmount = function() { sendFlowService.start({ @@ -24,50 +24,6 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi }); }; - - function connectSocket() { - // Close existing socket if not connected with current address - if (currentAddressSocket) { - currentAddressSocket.close([CLOSE_NORMAL]); - } - - if ($scope.wallet.coin === 'bch') { - // listen to bch address - currentAddressSocket = new WebSocket('wss://ws.blockchain.info/bch/inv'); - paymentSubscriptionObj.addr = $scope.addrBchLegacy; - } else { - // listen to btc address - currentAddressSocket = new WebSocket('wss://ws.blockchain.info/inv'); - paymentSubscriptionObj.addr = $scope.addr; - } - - // create subscription to address - var msg = JSON.stringify(paymentSubscriptionObj); - currentAddressSocket.onopen = function (event) { - currentAddressSocket.send(msg); - }; - - // listen for response - currentAddressSocket.onmessage = function (event) { - //console.log("message received:" + event.data); - receivedPayment(event.data); - }; - - currentAddressSocket.onclose = function(event) { - if (event.code !== CLOSE_NORMAL) { - $log.debug('Socket was closed abnormally. Reconnect will be attempted in 1 second.'); - $timeout(function() { - connectSocket(); - }, 1000); - } - }; - - currentAddressSocket.onerror = function(err) { - console.error('Socket encountered error: ', err, 'Closing socket'); - currentAddressSocket.close(); - }; - } - $scope.setAddress = function(newAddr, copyAddress) { $scope.addr = null; if (!$scope.wallet || $scope.generatingAddress || !$scope.wallet.isComplete()) return; @@ -80,24 +36,49 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi popupService.showAlert(err); } - if ($scope.wallet.coin === 'bch') { - bchAddresses = bitcoinCashJsService.translateAddresses(addr); - $scope.addr = bchAddresses[$scope.bchAddressType.type]; - $scope.addrBchLegacy = bchAddresses['legacy']; - } else { - $scope.addr = addr; + //close existing socket + if (currentAddressSocket.close === 'function') { + currentAddressSocket.close(); } - connectSocket(); + if ($scope.wallet.coin == 'bch') { + bchAddresses = bitcoinCashJsService.translateAddresses(addr); + $scope.addr = bchAddresses[$scope.bchAddressType.type]; + $scope.addrBchLegacy = bchAddresses['legacy']; + + // listen to bch address + currentAddressSocket = new WebSocket("wss://ws.blockchain.info/bch/inv"); + paymentSubscriptionObj.addr = bchAddresses['legacy']; + + } else { + $scope.addr = addr; + + // listen to btc address + currentAddressSocket = new WebSocket("wss://ws.blockchain.info/inv"); + paymentSubscriptionObj.addr = $scope.addr + } if (copyAddress === true) { try { clipboardService.copyToClipboard($scope.wallet.coin == 'bch' && $scope.bchAddressType.type == 'cashaddr' ? 'bitcoincash:' + $scope.addr : $scope.addr); } catch (error) { - $log.debug('Error copying to clipboard:'); + $log.debug("Error copying to clipboard:"); $log.debug(error); } } + // create subscription + var msg = JSON.stringify(paymentSubscriptionObj); + currentAddressSocket.onopen = function (event) { + //console.log("message sent: " + msg); + currentAddressSocket.send(msg); + } + + + // listen for response + currentAddressSocket.onmessage = function (event) { + //console.log("message received:" + event.data); + receivedPayment(event.data); + } $timeout(function() { $scope.$apply(); @@ -183,6 +164,7 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi // Notify new tx $scope.$emit('bwsEvent', $scope.wallet.id); + $scope.$apply(function () { $scope.showingPaymentReceived = true; }); @@ -251,10 +233,6 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi } }; - function onBeforeLeave() { - currentAddressSocket.close([CLOSE_NORMAL]); - } - $scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.wallets = profileService.getWallets(); $scope.singleWallet = $scope.wallets.length == 1; @@ -280,6 +258,7 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi configService.whenAvailable(function(_config) { $scope.displayBalanceAsFiat = _config.wallet.settings.priceDisplay === 'fiat'; + config = _config; }); }); diff --git a/src/js/controllers/wallet-details.controller.js b/src/js/controllers/wallet-details.controller.js index 429420741..f3109db8b 100644 --- a/src/js/controllers/wallet-details.controller.js +++ b/src/js/controllers/wallet-details.controller.js @@ -203,7 +203,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun function updateTxHistoryFromCachedData() { - $scope.vm.gettingCachedHistory = true; walletHistoryService.getCachedTxHistory($scope.wallet.id, function onGetCachedTxHistory(err, txHistory){ $scope.vm.gettingCachedHistory = false; if (err) { @@ -401,9 +400,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var refreshInterval; - $scope.$on("$ionicView.afterEnter", function onAfterEnter(event, data) { - updateTxHistoryFromCachedData(); - $scope.updateAll(true, true); + $scope.$on("$ionicView.afterEnter", function(event, data) { + $scope.updateAll(); // refreshAmountSection(); refreshInterval = $interval($scope.onRefresh, 10 * 1000); $timeout(function() { diff --git a/src/js/controllers/wallet-selector.controller.js b/src/js/controllers/wallet-selector.controller.js index 036727333..06e6179da 100644 --- a/src/js/controllers/wallet-selector.controller.js +++ b/src/js/controllers/wallet-selector.controller.js @@ -1,204 +1,189 @@ 'use strict'; -(function () { +angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $state, sendFlowService, configService, gettextCatalog, profileService, txFormatService) { -angular - .module('copayApp.controllers') - .controller('walletSelectorController', walletSelectorController); + var fromWalletId = ''; + var priceDisplayAsFiat = false; + var unitDecimals = 0; + var unitsFromSatoshis = 0; - function walletSelectorController ($scope, $state, sendFlowService, configService, gettextCatalog, ongoingProcess, profileService, walletService, txFormatService) { - var fromWalletId = ''; - var priceDisplayAsFiat = false; - var unitDecimals = 0; - var unitsFromSatoshis = 0; - - $scope.$on("$ionicView.beforeEnter", onBeforeEnter); - $scope.$on("$ionicView.enter", onEnter); - - function onBeforeEnter(event, data) { - if (data.direction == "back") { - sendFlowService.state.pop(); - } - - $scope.params = sendFlowService.state.getClone(); - - console.log('walletSelector onBeforeEnter after back sendflow', $scope.params); - - var config = configService.getSync().wallet.settings; - priceDisplayAsFiat = config.priceDisplay === 'fiat'; - unitDecimals = config.unitDecimals; - unitsFromSatoshis = 1 / config.unitToSatoshi; - - if ($scope.params.isWalletTransfer) { - $scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets'); - } else if (!$scope.params.thirdParty) { - $scope.sendFlowTitle = gettextCatalog.getString('Send'); - } - - $scope.coin = false; // Wallets to show (for destination screen or contacts) - $scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination - fromWalletId = $scope.params['fromWalletId']; - - if ($scope.type === 'destination' && $scope.params.toAddress) { - $state.transitionTo(getNextStep($scope.params)); - } - - if ($scope.params.coin) { - $scope.coin = $scope.params.coin; // Contacts have a coin embedded - } - - if ($scope.params.amount) { // There is an amount, so presume that it is a payment request - $scope.sendFlowTitle = gettextCatalog.getString('Payment Request'); - $scope.specificAmount = $scope.specificAlternativeAmount = ''; - $scope.isPaymentRequest = true; - } - if ($scope.params.thirdParty) { - $scope.thirdParty = $scope.params.thirdParty; - } - }; - - function onEnter (event, data) { - configService.whenAvailable(function(config) { - $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; - }); - - if ($scope.thirdParty) { - // Third party services specific logic - handleThirdPartyIfShapeshift(); - } - - prepareWalletLists(); - formatRequestedAmount(); - }; - - function formatRequestedAmount() { - if ($scope.params.amount) { - var cryptoAmount = (unitsFromSatoshis * $scope.params.amount).toFixed(unitDecimals); - var cryptoCoin = $scope.coin.toUpperCase(); - - txFormatService.formatAlternativeStr($scope.coin, $scope.params.amount, function onFormatAlternativeStr(formatted){ - if (formatted) { - var fiatParts = formatted.split(' '); - var fiatAmount = fiatParts[0]; - var fiatCurrrency = fiatParts.length > 1 ? fiatParts[1] : ''; - - if (priceDisplayAsFiat) { - $scope.requestAmount = fiatAmount; - $scope.requestCurrency = fiatCurrrency; - - $scope.requestAmountSecondary = cryptoAmount; - $scope.requestCurrencySecondary = cryptoCoin; - } else { - $scope.requestAmount = cryptoAmount; - $scope.requestCurrency = cryptoCoin; - - $scope.requestAmountSecondary = fiatAmount; - $scope.requestCurrencySecondary = fiatCurrrency; - } - $scope.$apply(); - } - }); - } + $scope.$on("$ionicView.beforeEnter", onBeforeEnter); + $scope.$on("$ionicView.enter", onEnter); + + function onBeforeEnter(event, data) { + if (data.direction == "back") { + sendFlowService.state.pop(); } - function handleThirdPartyIfShapeshift() { - console.log($scope.thirdParty, $scope.coin); - if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the - $scope.coin = profileService.getWallet(fromWalletId).coin; - if ($scope.coin === 'bch') { - $scope.coin = 'btc'; - } else { - $scope.coin = 'bch'; - } - } + $scope.params = sendFlowService.state.getClone(); + + console.log('walletSelector onBeforeEnter after back sendflow', $scope.params); + + var config = configService.getSync().wallet.settings; + priceDisplayAsFiat = config.priceDisplay === 'fiat'; + unitDecimals = config.unitDecimals; + unitsFromSatoshis = 1 / config.unitToSatoshi; + + if ($scope.params.isWalletTransfer) { + $scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets'); + } else if (!$scope.params.thirdParty) { + $scope.sendFlowTitle = gettextCatalog.getString('Send'); } - function prepareWalletLists() { - var walletsAll = []; - var walletsSufficientFunds = []; - $scope.walletsInsufficientFunds = []; // For origin screen + $scope.coin = false; // Wallets to show (for destination screen or contacts) + $scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination + fromWalletId = $scope.params['fromWalletId']; - if ($scope.type === 'origin') { - $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); + if ($scope.type === 'destination' && $scope.params.toAddress) { + $state.transitionTo(getNextStep($scope.params)); + } - if ($scope.params.amount || $scope.coin) { + if ($scope.params.coin) { + $scope.coin = $scope.params.coin; // Contacts have a coin embedded + } - walletsAll = profileService.getWallets({coin: $scope.coin}); - ongoingProcess.set('scanning', true); - walletsAll.forEach(function forWallet(wallet) { - if (!wallet.status && !wallet.cachedStatus) { - walletService.getStatus(wallet, {}, function(err, status) { - wallet.status = status; - if (status.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { - walletsSufficientFunds.push(wallet); - } else { - $scope.walletsInsufficientFunds.push(wallet); - } - if ($scope.coin === 'btc') { // As this is a promise - $scope.walletsBtc = walletsSufficientFunds; - } else { - $scope.walletsBch = walletsSufficientFunds; - } - ongoingProcess.set('scanning', false); - }); - } else { - var walletStatus = null; - if (wallet.status && wallet.status.isValid) { - walletStatus = wallet.status; - } else if (wallet.cachedStatus && wallet.status.isValid) { - walletStatus = wallet.cachedStatus; - } + if ($scope.params.amount) { // There is an amount, so presume that it is a payment request + $scope.sendFlowTitle = gettextCatalog.getString('Payment Request'); + $scope.specificAmount = $scope.specificAlternativeAmount = ''; + $scope.isPaymentRequest = true; + } + if ($scope.params.thirdParty) { + $scope.thirdParty = $scope.params.thirdParty; + } + }; - if (walletStatus && walletStatus.availableBalanceSat > ($scope.params.amount ? $scope.params.amount : 0)) { - walletsSufficientFunds.push(wallet); - } else { - $scope.walletsInsufficientFunds.push(wallet); - } - ongoingProcess.set('scanning', false); - } - }); + function onEnter (event, data) { + configService.whenAvailable(function(config) { + $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; + }); - if ($scope.coin === 'btc') { - $scope.walletsBtc = walletsSufficientFunds; + if ($scope.thirdParty) { + // Third party services specific logic + handleThirdPartyIfShapeshift(); + } + + prepareWalletLists(); + formatRequestedAmount(); + }; + + function formatRequestedAmount() { + if ($scope.params.amount) { + var cryptoAmount = (unitsFromSatoshis * $scope.params.amount).toFixed(unitDecimals); + var cryptoCoin = $scope.coin.toUpperCase(); + + txFormatService.formatAlternativeStr($scope.coin, $scope.params.amount, function onFormatAlternativeStr(formatted){ + if (formatted) { + var fiatParts = formatted.split(' '); + var fiatAmount = fiatParts[0]; + var fiatCurrrency = fiatParts.length > 1 ? fiatParts[1] : ''; + + if (priceDisplayAsFiat) { + $scope.requestAmount = fiatAmount; + $scope.requestCurrency = fiatCurrrency; + + $scope.requestAmountSecondary = cryptoAmount; + $scope.requestCurrencySecondary = cryptoCoin; } else { - $scope.walletsBch = walletsSufficientFunds; + $scope.requestAmount = cryptoAmount; + $scope.requestCurrency = cryptoCoin; + + $scope.requestAmountSecondary = fiatAmount; + $scope.requestCurrencySecondary = fiatCurrrency; } - } else { - $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: true}); - $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: true}); - $scope.walletsInsufficientFunds = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); + $scope.$apply(); } - - } else if ($scope.type === 'destination') { - if (!$scope.coin) { // Allow for the coin to be set by a third party - $scope.fromWallet = profileService.getWallet(fromWalletId); - $scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin - } - $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); - - if ($scope.coin === 'btc') { // if no specific coin is set or coin is set btc - $scope.walletsBtc = profileService.getWallets({coin: $scope.coin}); - } else { - $scope.walletsBch = profileService.getWallets({coin: $scope.coin}); - } - } + }); } - - - - $scope.useWallet = function(wallet) { - var params = sendFlowService.state.getClone(); - if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from - params.fromWalletId = wallet.id; - } else { // we're on the destination screen, set wallet to send to - params.toWalletId = wallet.id; - } - sendFlowService.goNext(params); - }; - - $scope.goBack = function() { - sendFlowService.router.goBack(); - } - } -})(); \ No newline at end of file + + function handleThirdPartyIfShapeshift() { + console.log($scope.thirdParty, $scope.coin); + if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the + $scope.coin = profileService.getWallet(fromWalletId).coin; + if ($scope.coin === 'bch') { + $scope.coin = 'btc'; + } else { + $scope.coin = 'bch'; + } + } + } + + function prepareWalletLists() { + var walletsAll = []; + var walletsSufficientFunds = []; + $scope.walletsInsufficientFunds = []; // For origin screen + + if ($scope.type === 'origin') { + $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); + + if ($scope.params.amount) { + + walletsAll = profileService.getWallets({coin: $scope.coin}); + + walletsAll.forEach(function forWallet(wallet){ + if (wallet.status.availableBalanceSat > $scope.params.amount) { + walletsSufficientFunds.push(wallet); + } else { + $scope.walletsInsufficientFunds.push(wallet); + } + }); + + if ($scope.coin === 'btc') { + $scope.walletsBtc = walletsSufficientFunds; + } else { + $scope.walletsBch = walletsSufficientFunds; + } + + } else if ($scope.coin) { + walletsAll = profileService.getWallets({coin: $scope.coin}); + walletsAll.forEach(function forWallet(wallet){ + if (wallet.status.availableBalanceSat > 0) { + walletsSufficientFunds.push(wallet); + } else { + $scope.walletsInsufficientFunds.push(wallet); + } + }); + + if ($scope.coin === 'btc') { + $scope.walletsBtc = walletsSufficientFunds; + } else { + $scope.walletsBch = walletsSufficientFunds; + } + } else { + $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: true}); + $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: true}); + $scope.walletsInsufficientFunds = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); + } + + } else if ($scope.type === 'destination') { + if (!$scope.coin) { // Allow for the coin to be set by a third party + $scope.fromWallet = profileService.getWallet(fromWalletId); + $scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin + } + $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); + + if ($scope.coin === 'btc') { // if no specific coin is set or coin is set btc + $scope.walletsBtc = profileService.getWallets({coin: $scope.coin}); + } else { + $scope.walletsBch = profileService.getWallets({coin: $scope.coin}); + } + } + } + + + + $scope.useWallet = function(wallet) { + var params = sendFlowService.state.getClone(); + if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from + params.fromWalletId = wallet.id; + } else { // we're on the destination screen, set wallet to send to + params.toWalletId = wallet.id; + } + sendFlowService.goNext(params); + }; + + $scope.goBack = function() { + sendFlowService.router.goBack(); + } + +}); \ No newline at end of file diff --git a/src/js/directives/wallet-balance.directive.js b/src/js/directives/walletBalanceDirective.js similarity index 81% rename from src/js/directives/wallet-balance.directive.js rename to src/js/directives/walletBalanceDirective.js index 4d8ca2875..1fea59203 100644 --- a/src/js/directives/wallet-balance.directive.js +++ b/src/js/directives/walletBalanceDirective.js @@ -12,7 +12,6 @@ totalBalanceSat: '@', // The Wallet object is sometimes not stringify()-able, so not interpolatable, // so can't be passed to a directive. - walletCoin: '@', walletStatus: '@', walletCachedBalance: '@', walletCachedBalanceUpdatedOn: '@', @@ -32,6 +31,7 @@ }); function displayCryptoBalance(walletStatus, walletCachedBalance, walletCachedBalanceUpdatedOn, walletCachedStatus) { + console.log('displayCryptoBalance()'); if (walletStatus && walletStatus.isValid && walletStatus.totalBalanceStr) { setDisplay(walletStatus.totalBalanceStr, ''); @@ -52,7 +52,7 @@ setDisplay('', ''); } - function displayFiatBalance(walletStatus, walletCachedStatus, walletCoin) { + function displayFiatBalance(walletStatus, walletCachedStatus) { var displayAmount = ''; if (walletStatus && walletStatus.isValid && walletStatus.alternativeBalanceAvailable) { displayAmount = walletStatus.totalBalanceAlternative + ' ' + walletStatus.alternativeIsoCode; @@ -66,7 +66,7 @@ return; } - getFiatBalance(walletStatus, walletCachedStatus, walletCoin); + getFiatBalance(wallet); } function formatBalance() { @@ -94,30 +94,19 @@ } if (displayAsFiat) { - displayFiatBalance(walletStatusObj, walletCachedStatusObj, $scope.walletCoin); + displayFiatBalance(walletStatusObj, walletCachedStatusObj); } } - function getFiatBalance(walletStatus, walletCachedStatus, walletCoin) { - var totalBalanceSat = null; - - if (walletStatus && walletStatus.isValid) { - totalBalanceSat = walletStatus.totalBalanceSat - } else if (walletCachedStatus && walletCachedStatus.isValid) { - totalBalanceSat = walletCachedStatus.totalBalanceSat - } - - // 0 is valid - if (totalBalanceSat === null) { - $log.warn('Abandoning call to get fiat balance, because no valid wallet status (cached or otherwise).'); + function getFiatBalance(wallet) { + if (!(wallet.status && wallet.status.isValid)) { + $log.warn('Abandoning call to get fiat balance, because no valid wallet status.'); return; } - txFormatService.formatAlternativeStr(walletCoin, totalBalanceSat, function onFormatAlernativeStr(formatted) { + txFormatService.formatAlternativeStr(wallet.coin, wallet.status.totalBalanceSat, function onFormatAlernativeStr(formatted) { if (formatted) { setDisplay(formatted, ''); - } else { - $log.error('Failed to format fiat balance of wallet.'); } }); } diff --git a/src/js/services/configService.js b/src/js/services/configService.js index 6f2c0ec67..72cc4825f 100644 --- a/src/js/services/configService.js +++ b/src/js/services/configService.js @@ -18,7 +18,7 @@ angular.module('copayApp.services').factory('configService', function(storageSer }, bwscash: { - url: 'https://bws.freepages.dk/bws/api' + url: 'https://bwscash.bitcoin.com/bws/api' }, download: { diff --git a/src/js/services/feeService.js b/src/js/services/feeService.js index 5189527b2..bba972b81 100644 --- a/src/js/services/feeService.js +++ b/src/js/services/feeService.js @@ -12,7 +12,6 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou normal: gettext('Normal'), economy: gettext('Economy'), superEconomy: gettext('Super Economy'), -// free: gettext('No fee (works only for BCH)'), custom: gettext('Custom') }; @@ -32,7 +31,6 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou root.getFeeRate = function(coin, network, feeLevel, cb) { if (feeLevel == 'custom') return cb(); -// if (feeLevel == 'free') return cb(null, 0); network = network || 'livenet'; @@ -50,8 +48,8 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou }) }); } - var feeRate = feeLevelRate.feePerKb; + var feeRate = feeLevelRate.feePerKb; if (!fromCache) $log.debug('Dynamic fee: ' + feeLevel + '/' + network + ' ' + (feeLevelRate.feePerKb / 1000).toFixed() + ' SAT/B'); diff --git a/src/js/services/onGoingProcess.js b/src/js/services/onGoingProcess.js index 422be070f..9c25c3c26 100644 --- a/src/js/services/onGoingProcess.js +++ b/src/js/services/onGoingProcess.js @@ -74,8 +74,10 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti root.onGoingProcessName = name; var showName = $filter('translate')(processNames[name] || name); - - if (root.onGoingProcessName) { + + if (customHandler) { + customHandler(processName, showName, isOn); + } else if (root.onGoingProcessName) { var tmpl; if (isWindowsPhoneApp) tmpl = '
' + showName + '
'; else tmpl = '
' + showName + '
'; @@ -85,10 +87,6 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti } else { $ionicLoading.hide(); } - - if (customHandler) { - customHandler(processName, showName, isOn); - } }; return root; diff --git a/src/js/services/servicesService.js b/src/js/services/servicesService.js index ddd9db394..316009957 100644 --- a/src/js/services/servicesService.js +++ b/src/js/services/servicesService.js @@ -5,7 +5,7 @@ angular.module('copayApp.services').factory('servicesService', function(configSe name: 'shapeshift', title: 'Shapeshift', icon: 'icon-shapeshift', - href: 'https://shapeshift.io/' + sref: 'tabs.shapeshift', }]; root.register = function(serviceInfo) { diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js index 512a2d8b1..7a6f18a13 100644 --- a/src/js/services/wallet-history.service.js +++ b/src/js/services/wallet-history.service.js @@ -27,23 +27,20 @@ function addEarlyTransactions(walletId, cachedTxs, newTxs) { - var cachedTxIndexFromId = {}; + var cachedTxIds = {}; cachedTxs.forEach(function forCachedTx(tx){ - cachedTxIndexFromId[tx.txid] = true; + cachedTxIds[tx.txid] = true; }); - var confirmationsUpdated = false; var someTransactionsWereNew = false; var overlappingTxsCount = 0; newTxs.forEach(function forNewTx(tx){ - if (typeof cachedTxIndexFromId[tx.txid] === "undefined") { + if (cachedTxIds[tx.txid]) { + overlappingTxsCount++; + } else { someTransactionsWereNew = true; cachedTxs.push(tx); - } else { - var txUpdated = updateCachedTx(cachedTxs, cachedTxIndexFromId, tx); - confirmationsUpdated = confirmationsUpdated || txUpdated; - overlappingTxsCount++; } }); @@ -53,8 +50,6 @@ if (overlappingTxFraction >= MIN_KNOWN_TX_OVERLAP_FRACTION) { // We are good if (someTransactionsWereNew) { saveTxHistory(walletId, cachedTxs); - } else if (confirmationsUpdated) { - saveTxHistory(walletId, cachedTxs); } else if (overlappingTxsCount === newTxs.length) { allTransactionsFetched = true; } @@ -70,24 +65,21 @@ } function addLatestTransactions(walletId, cachedTxs, newTxs) { - var cachedTxIndexFromId = {}; - cachedTxs.forEach(function forCachedTx(tx, txIndex){ - cachedTxIndexFromId[tx.txid] = txIndex; + var cachedTxIds = {}; + cachedTxs.forEach(function forCachedTx(tx){ + cachedTxIds[tx.txid] = true; }); var someTransactionsWereNew = false; - var confirmationsUpdated = false; var overlappingTxsCount = 0; var uniqueNewTxs = []; newTxs.forEach(function forNewTx(tx){ - if (typeof cachedTxIndexFromId[tx.txid] === "undefined") { + if (cachedTxIds[tx.txid]) { + overlappingTxsCount++; + } else { someTransactionsWereNew = true; uniqueNewTxs.push(tx); - } else { - var txUpdated = updateCachedTx(cachedTxs, cachedTxIndexFromId, tx); - confirmationsUpdated = confirmationsUpdated || txUpdated; - overlappingTxsCount++; } }); @@ -99,9 +91,6 @@ saveTxHistory(walletId, allTxs); return allTxs; } else { - if (confirmationsUpdated) { - saveTxHistory(walletId, cachedTxs); - } return cachedTxs; } } else { @@ -115,8 +104,6 @@ // Only clear the cache once we have received new transactions from the server. /** - * @param wallet - * @param start * @param {function(err, txs)} cb - transactions is always an array, may be empty */ function fetchTxHistoryByPage(wallet, start, cb) { @@ -147,7 +134,6 @@ * @param {function(error, txs)} cb - txs is always an array, may be empty */ function getCachedTxHistory(walletId, cb) { - console.log('txhistory updateLocalTxHistoryByPage()'); storageService.getTxHistory(walletId, function onGetTxHistory(err, txHistoryString){ if (err) { return cb(err, []); @@ -201,7 +187,7 @@ }); return processedTxs; - } + }; function saveTxHistory(walletId, processedTxs) { storageService.setTxHistory(processedTxs, walletId, function onSetTxHistory(error){ @@ -211,26 +197,9 @@ }); } - /** - * Returns true if the cached tx was updated - * @param {*} cachedTxs - * @param {*} cachedTxIndexFromId - Indices for cachedTxs, based on txid - * @param {*} tx - The most recent tx info - */ - function updateCachedTx(cachedTxs, cachedTxIndexFromId, tx) { - var updated = false; - var txIndex = cachedTxIndexFromId[tx.txid]; - var cachedTx = cachedTxs[txIndex]; - - if (cachedTx.confirmations < SAFE_CONFIRMATIONS && tx.confirmations > cachedTx.confirmations) { - cachedTxs[txIndex].confirmations = tx.confirmations; - updated = true; - } - return updated; - } function updateLocalTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { - console.log('txhistory updaetLocalTxHistoryByPage()'); + if (flushCacheOnNew) { fetchTxHistoryByPage(wallet, 0, function onFetchTxHistory(err, txs){ if (err) { @@ -271,5 +240,10 @@ }); } } + + + } + + })(); \ No newline at end of file diff --git a/www/css/main.css b/www/css/main.css index 83c50fe17..8602ba35d 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -10083,7 +10083,7 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm .onboarding .button.button-white.button-standard, .onboarding .button.button-green.button-standard, .onboarding .button.button-assertive.button-standard, #shapeshift .button-shapeshift { - width: 90%; + width: 85%; max-width: 300px; margin-left: auto; margin-right: auto; @@ -10195,7 +10195,6 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm .button { border-radius: 6px; } .button.button-full { - border-radius: 0; display: block; } .button-green { border-color: #FFF; @@ -10264,8 +10263,8 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm color: #FFFFFF; text-decoration: none; } .button-white-outline.active, .button-white-outline.activated { - border-color: #FFFFFF; - background-color: #FFFFFF; } + border-color: #FFF; + background-color: #FAFAFA; } .button-white-outline.button-outline { border-color: #FFFFFF; background: transparent; @@ -10633,7 +10632,7 @@ qrcode { #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .primary-amount-display { font-size: 2em; } } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { - font-size: 1.2em; } + font-size: 0.9em; } @media (min-width: 375px) { #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { font-size: 1.3em; } } @@ -10660,8 +10659,7 @@ qrcode { line-height: 1em; } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { margin-right: 5px; - word-break: break-all; - width: 100%; } + word-break: break-all; } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .alternative-amount { color: #6F6F70; } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .switch-currencies { @@ -10682,33 +10680,30 @@ qrcode { justify-content: space-between; margin: 0 14px; } #view-amount .scroll-content .send-amount-extras .available-funds { - color: #6F6F70; - text-align: left; } - #view-amount .scroll-content .send-amount-extras .change-currency { - text-align: right; } + color: #6F6F70; } #view-amount .scroll-content .send-amount-extras .warning { color: #b7664d; } - #view-amount .scroll-content .send-amount-extras .extra { - flex: 1; + #view-amount .scroll-content .send-amount-extras .extra, + #view-amount .scroll-content .send-amount-extras button.extra { + /*display: flex;*/ + flex: 0 1 auto; } + #view-amount .scroll-content .send-amount-extras button.extra { + background: none; + border: none; + color: #000; + font-family: 'ProximaNova'; + font-size: 14px; + line-height: normal; + min-height: auto; + min-width: auto; + padding: 0; } + #view-amount .scroll-content .send-amount-extras .button .icon:before { + font-size: 14px; line-height: normal; } - #view-amount .scroll-content .send-amount-extras .extra .button { - background: none; - border: none; - border-radius: 0; - color: #000; - font-family: 'ProximaNova'; - font-size: 14px; - line-height: normal; - min-height: auto; - min-width: auto; - padding: 0; } - #view-amount .scroll-content .send-amount-extras .extra .button .icon:before { - font-size: 14px; - line-height: normal; } - #view-amount .scroll-content .send-amount-extras .extra .button span { - display: flex; - align-items: center; - justify-content: center; } + #view-amount .scroll-content .send-amount-extras .button span { + display: flex; + align-items: center; + justify-content: center; } #view-amount .scroll-content .button.no-margin { margin: 0; } #view-amount .scroll-content .notification-warning { @@ -10944,14 +10939,14 @@ qrcode { #tab-home .buttons .button { border: 2px solid; border-radius: 47px; - padding: 8px 2px 8px 2px; + padding: 0 15px 0 15px; text-align: center; width: 100%; max-width: 300px; font-size: 19px; font-weight: bolder; - min-height: 0; - line-height: 19px; } + min-height: auto; + line-height: 36px; } #tab-home .wallet-coin-logo { vertical-align: middle; @@ -11018,10 +11013,6 @@ qrcode { #tab-home .release .title { font-weight: 700; color: #444; } - #tab-home .release .release-notes { - white-space: pre; - white-space: pre-line; - text-align: left; } #tab-home .release .button { width: 100%; border: none; } @@ -11034,14 +11025,6 @@ qrcode { #tab-home .badge { top: 11px; } -.popup-update .popup-buttons { - display: block; } - -.popup-update .popup-buttons .button { - display: block; - min-width: 100% !important; - margin-top: 4px; } - #tab-receive .button-share { color: #fff; box-shadow: none; @@ -11118,8 +11101,6 @@ qrcode { #tab-receive .payment-received-container { margin: 0 20px; } - #tab-receive .payment-received-container svg { - max-height: 400px; } #tab-receive .payment-received-container .payment-received-amount { font-size: 1.8em; display: block; @@ -11226,7 +11207,7 @@ qrcode { margin: auto; margin-top: 18px; } #tab-send .send-wrapper .buttons .button { - min-height: 65px; + height: 60px; line-height: 16px; margin-right: 0px; width: 95%; @@ -11303,9 +11284,7 @@ qrcode { margin-top: 18px; } #tab-send .sendTip .buttons .button { font-weight: bold; - font-size: 19px; - line-height: 26px; - padding: 8px 6px; } + font-size: 19px; } #tab-send .sendTip .button-first-contact img { height: 19px; width: 19px; @@ -11941,13 +11920,6 @@ qrcode { #walletDetails .bp-content.status-bar { margin-top: 20px; margin-top: env(safe-area-inset-top); } - #walletDetails .bp-content.collapse ion-content { - margin-top: 40px; } - #walletDetails .bp-content.collapse .amount__scale, #walletDetails .bp-content.collapse .amount__error { - -webkit-transform: scale3d(0.5, 0.5, 0.5) translateY(0px); - transform: scale3d(0.5, 0.5, 0.5) translateY(0px); } - #walletDetails .bp-content.collapse .amount-alternative, #walletDetails .bp-content.collapse .send-receive-buttons, #walletDetails .bp-content.collapse .wallet-details-wallet-info { - opacity: 0; } #walletDetails .bar-header { border: 0; background: #eeb640; } @@ -11960,12 +11932,9 @@ qrcode { #walletDetails ion-content { padding-top: 0; top: 0; - transition: all 0.25s ease-in-out; - margin-top: 185px; margin-bottom: 16px; } - @media only screen and (max-height: 500px) { - #walletDetails ion-content { - margin-top: 165px; } } + #walletDetails ion-content.collapsible { + margin-top: 230px; } #walletDetails ion-content .scroll { background: #f8f8f9; min-height: 300px; } @@ -11996,45 +11965,38 @@ qrcode { justify-content: space-evenly; width: 100%; position: absolute; - bottom: 20px; - transition: all 0.25s ease-in-out; } + bottom: 20px; } #walletDetails .amount-wrapper .send-receive-buttons > .col { padding: 5px 10px; margin-bottom: 0; } #walletDetails .amount-wrapper .send-receive-buttons .button { border: 2px solid; border-radius: 47px; - padding: 6px 2px 6px 2px; + padding: 0 15px 0 15px; text-align: center; width: 100%; max-width: 300px; font-size: 19px; font-weight: bolder; - min-height: 0; - line-height: 19px; } + min-height: auto; + line-height: 36px; } #walletDetails .amount { - align-items: center; - color: #fff; - display: block; - height: 230px; - justify-content: center; - padding-top: 40px; + width: 100%; text-align: center; - transition: all 0.25s ease-in-out; - width: 100%; } - @media only screen and (max-height: 500px) { - #walletDetails .amount { - height: 210px; } } + color: #fff; + height: 230px; + padding-top: 40px; + display: block; + align-items: center; + justify-content: center; } #walletDetails .amount__balance { -webkit-transform: scale3d(1, 1, 1) translateY(45px); - transform: scale3d(1, 1, 1) translateY(45px); - transition: all 0.25s ease-in-out; } + transform: scale3d(1, 1, 1) translateY(45px); } #walletDetails .amount__updating { z-index: 999; margin-top: -2.1rem; } #walletDetails .amount-alternative { - line-height: 36px; - transition: all 0.25s ease-in-out; } + line-height: 36px; } #walletDetails .amount__button-balance { background-color: transparent; border: 1px solid rgba(255, 255, 255, 0.25); @@ -12044,8 +12006,7 @@ qrcode { vertical-align: middle; } #walletDetails .amount__error { font-size: 14px; - padding: 35px 20px; - opacity: 1; } + padding: 35px 20px; } #walletDetails .no-alternative { padding-top: 45px; } #walletDetails .item.item-footer { @@ -12110,9 +12071,7 @@ a.item { font-size: 0.9em; } .loading-wallet svg { - margin-top: 0; - width: 16px; - height: 16px; } + margin-top: 0; } #advanced-settings .list .item:before { display: block; @@ -12166,9 +12125,7 @@ a.item { margin-top: 18px; } #shapeshift .empty-case .buttons .button { font-weight: bold; - font-size: 19px; - line-height: 26px; - padding: 8px 6px; } + font-size: 19px; } #shapeshift .empty-case .button-first-contact img { height: 19px; width: 19px; @@ -13855,7 +13812,6 @@ click-to-accept { height: 92px; width: 100%; } click-to-accept .click-to-accept__button.button.button-primary.button-standard { - border-radius: 0; height: 100%; max-width: 9999px; width: 100%; } @@ -13962,8 +13918,6 @@ slide-to-accept { height: 92px; width: 100%; background: #494949; } - slide-to-accept .slide .button { - border-radius: 0; } slide-to-accept .slide__listener { height: 100%; width: 100%; @@ -15395,25 +15349,20 @@ log-options #check-bar .checkbox-icon { #cash-scan a { cursor: pointer; } -#view-review slide-to-accept, #view-review slide-to-accept-success { - margin-bottom: constant(safe-area-inset-bottom); - /* iOS 11.0 */ - margin-bottom: env(safe-area-inset-bottom); - /* iOS 11.2 */ } - -#view-review .fee-summary { - bottom: 92px; - bottom: calc(92px + constant(safe-area-inset-bottom)); - /* iOS 11.0 */ - bottom: calc(92px + env(safe-area-inset-bottom)); - /* iOS 11.2 */ - position: absolute; } - -#view-review .shapeshift-banner, #view-review .bitpay-banner, #view-review .egifter-banner { - box-shadow: none; } - -#view-review .warning { - color: #b7664d; } +#view-review { + background-color: #494949; } + #view-review slide-to-accept, #view-review slide-to-accept-success { + margin-bottom: constant(safe-area-inset-bottom); + /* iOS 11.0 */ + margin-bottom: env(safe-area-inset-bottom); + /* iOS 11.2 */ } + #view-review .fee-summary { + position: absolute; + bottom: 92px; } + #view-review .shapeshift-banner, #view-review .bitpay-banner, #view-review .egifter-banner { + box-shadow: none; } + #view-review .warning { + color: #b7664d; } .gravatar { border-radius: 3px; @@ -15446,7 +15395,6 @@ log-options #check-bar .checkbox-icon { left: 13px; top: 50%; padding: 0; - -webkit-transform: translate(0, -50%); transform: translate(0, -50%); } .item .item-content.item-content-compact { @@ -15515,8 +15463,8 @@ ion-content.padded-bottom-cta-with-summary { overflow: hidden; text-overflow: ellipsis; } .address-frame.expanded { - white-space: normal; - text-overflow: clip; } + white-space: pre-wrap; + word-break: break-all; } .address-frame .prefix { color: #000000; } .address-frame .mid { @@ -15559,13 +15507,13 @@ ion-content.padded-bottom-cta-with-summary { transform: scale(1, 1); } .fee-summary { - background-color: #F2F2F2; - box-sizing: border-box; + position: relative; display: flex; flex-direction: column; + width: 100%; padding: 5px 12px 15px; - position: relative; - width: 100%; } + box-sizing: border-box; + background-color: #F2F2F2; } .fee-summary:before { content: ''; position: absolute; @@ -15575,16 +15523,16 @@ ion-content.padded-bottom-cta-with-summary { height: 15px; background: linear-gradient(to bottom, rgba(242, 242, 242, 0) 0%, #f2f2f2 100%); } .fee-summary .amount { + display: flex; + flex-direction: row; + justify-content: space-between; width: 100%; } - .fee-summary .amount .fee-fiat { - display: inline; } - .fee-summary .amount .fee-fiat.positive { - color: #70955F; } - .fee-summary .amount .fee-fiat.negative { - color: #C24633; } + .fee-summary .amount .fee-fiat.positive { + color: #70955F; } + .fee-summary .amount .fee-fiat.negative { + color: #C24633; } .fee-summary .amount .fee-crypto { - color: #A7A7A7; - float: right; } + color: #A7A7A7; } .formatted-amount { display: inline-block; } @@ -15609,9 +15557,6 @@ ion-content.padded-bottom-cta-with-summary { margin-left: 5px; text-transform: uppercase; } -.wallet-balance-directive { - display: inline-block; } - /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ */ diff --git a/www/views/includes/services.html b/www/views/includes/services.html index 05e6dcf2d..f57cd1004 100644 --- a/www/views/includes/services.html +++ b/www/views/includes/services.html @@ -6,14 +6,7 @@
- - -
-
- {{service.title || service.name}} - -
- +
diff --git a/www/views/tab-send.html b/www/views/tab-send.html index b8c2b7884..0b62fe0a5 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -17,13 +17,14 @@

{{fromWallet.name}}

+ +
diff --git a/www/views/walletSelector.html b/www/views/walletSelector.html index a9712adf9..2e4c4bc31 100644 --- a/www/views/walletSelector.html +++ b/www/views/walletSelector.html @@ -13,7 +13,7 @@
{{requestAmountSecondary}} {{requestCurrencySecondary}}
-
+
{{headerTitle}}