diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 8ad3d36f..cd4412c4 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -52,8 +52,8 @@ const BINARIES = { files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']] }, XMR: { - url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.0.0.tar.bz2', - dir: 'monero-x86_64-linux-gnu-v0.18.0.0', + url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.1.0.tar.bz2', + dir: 'monero-x86_64-linux-gnu-v0.18.1.0', files: [['monerod', 'monerod'], ['monero-wallet-rpc', 'monero-wallet-rpc']] } } diff --git a/lib/blockchain/install.js b/lib/blockchain/install.js index 84d91a0e..71434198 100644 --- a/lib/blockchain/install.js +++ b/lib/blockchain/install.js @@ -139,19 +139,33 @@ function getBlockchainSyncStatus (cryptoList) { }) } +function isInstalled (crypto) { + return isInstalledSoftware(crypto) && isInstalledVolume(crypto) +} + +function isDisabled (crypto) { + switch (crypto.cryptoCode) { + case 'ETH': + return 'Use admin\'s Infura plugin' + case 'ZEC': + return isInstalled(crypto) && 'Installed' || isInstalled(_.find(it => it.code === 'monero', cryptos)) && 'Insufficient resources. Contact support.' + case 'XMR': + return isInstalled(crypto) && 'Installed' || isInstalled(_.find(it => it.code === 'zcash', cryptos)) && 'Insufficient resources. Contact support.' + default: + return isInstalled(crypto) && 'Installed' + } +} + function run () { const choices = _.flow([ _.filter(c => c.type !== 'erc-20'), _.map(c => { - const checked = isInstalledSoftware(c) && isInstalledVolume(c) const name = c.code === 'ethereum' ? 'Ethereum and/or USDT' : c.display return { name, value: c.code, - checked, - disabled: c.cryptoCode === 'ETH' - ? 'Use admin\'s Infura plugin' - : checked && 'Installed' + checked: isInstalled(c), + disabled: isDisabled(c) } }), ])(cryptos) @@ -160,6 +174,15 @@ function run () { const validateAnswers = async (answers) => { if (_.size(answers) > 2) return { message: `Please insert a maximum of two coins to install.`, isValid: false } + + if ( + _.isEmpty(_.difference(['monero', 'zcash'], answers)) || + (_.includes('monero', answers) && isInstalled(_.find(it => it.code === 'zcash', cryptos))) || + (_.includes('zcash', answers) && isInstalled(_.find(it => it.code === 'monero', cryptos))) + ) { + return { message: `Zcash and Monero installations are temporarily mutually exclusive, given the space needed for their blockchains. Contact support for more information.`, isValid: false } + } + return getBlockchainSyncStatus(cryptos) .then(blockchainStatuses => { const result = _.reduce((acc, value) => ({ ...acc, [value]: _.isNil(acc[value]) ? 1 : acc[value] + 1 }), {}, _.values(blockchainStatuses)) diff --git a/lib/new-admin/filters.js b/lib/new-admin/filters.js index d6a37922..d9b6c32d 100644 --- a/lib/new-admin/filters.js +++ b/lib/new-admin/filters.js @@ -21,7 +21,8 @@ function transaction () { SELECT 'address' AS type, to_address AS value FROM cash_in_txs UNION SELECT 'address' AS type, to_address AS value FROM cash_out_txs UNION SELECT 'status' AS type, ${cashInTx.TRANSACTION_STATES} AS value FROM cash_in_txs UNION - SELECT 'status' AS type, ${CASH_OUT_TRANSACTION_STATES} AS value FROM cash_out_txs + SELECT 'status' AS type, ${CASH_OUT_TRANSACTION_STATES} AS value FROM cash_out_txs UNION + SELECT 'sweep status' AS type, CASE WHEN swept THEN 'Swept' WHEN NOT swept THEN 'Unswept' END AS value FROM cash_out_txs ) f` return db.any(sql) diff --git a/lib/new-admin/graphql/types/transaction.type.js b/lib/new-admin/graphql/types/transaction.type.js index a7385eda..2d95d751 100644 --- a/lib/new-admin/graphql/types/transaction.type.js +++ b/lib/new-admin/graphql/types/transaction.type.js @@ -50,6 +50,7 @@ const typeDef = gql` batchError: String walletScore: Int profit: String + swept: Boolean } type Filter { @@ -58,8 +59,8 @@ const typeDef = gql` } type Query { - transactions(from: Date, until: Date, limit: Int, offset: Int, deviceId: ID, txClass: String, machineName: String, customerName: String, fiatCode: String, cryptoCode: String, toAddress: String, status: String, excludeTestingCustomers: Boolean): [Transaction] @auth - transactionsCsv(from: Date, until: Date, limit: Int, offset: Int, txClass: String, machineName: String, customerName: String, fiatCode: String, cryptoCode: String, toAddress: String, status: String, timezone: String, excludeTestingCustomers: Boolean, simplified: Boolean): String @auth + transactions(from: Date, until: Date, limit: Int, offset: Int, deviceId: ID, txClass: String, machineName: String, customerName: String, fiatCode: String, cryptoCode: String, toAddress: String, status: String, swept: Boolean, excludeTestingCustomers: Boolean): [Transaction] @auth + transactionsCsv(from: Date, until: Date, limit: Int, offset: Int, txClass: String, machineName: String, customerName: String, fiatCode: String, cryptoCode: String, toAddress: String, status: String, swept: Boolean, timezone: String, excludeTestingCustomers: Boolean, simplified: Boolean): String @auth transactionCsv(id: ID, txClass: String, timezone: String): String @auth txAssociatedDataCsv(id: ID, txClass: String, timezone: String): String @auth transactionFilters: [Filter] @auth diff --git a/lib/new-admin/services/transactions.js b/lib/new-admin/services/transactions.js index 42832859..4cdd182a 100644 --- a/lib/new-admin/services/transactions.js +++ b/lib/new-admin/services/transactions.js @@ -46,6 +46,7 @@ function batch ( cryptoCode = null, toAddress = null, status = null, + swept = null, excludeTestingCustomers = false, simplified ) { @@ -109,14 +110,33 @@ function batch ( AND ($11 is null or txs.crypto_code = $11) AND ($12 is null or txs.to_address = $12) AND ($13 is null or txs.txStatus = $13) + AND ($14 is null or txs.swept = $14) ${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``} AND (fiat > 0) ORDER BY created DESC limit $4 offset $5` - return Promise.all([ - db.any(cashInSql, [cashInTx.PENDING_INTERVAL, from, until, limit, offset, id, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status]), - db.any(cashOutSql, [REDEEMABLE_AGE, from, until, limit, offset, id, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status]) - ]) + // The swept filter is cash-out only, so omit the cash-in query entirely + const hasCashInOnlyFilters = false + const hasCashOutOnlyFilters = !_.isNil(swept) + + let promises + + if (hasCashInOnlyFilters && hasCashOutOnlyFilters) { + throw new Error('Trying to filter transactions with mutually exclusive filters') + } + + if (hasCashInOnlyFilters) { + promises = [db.any(cashInSql, [cashInTx.PENDING_INTERVAL, from, until, limit, offset, id, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status])] + } else if (hasCashOutOnlyFilters) { + promises = [db.any(cashOutSql, [REDEEMABLE_AGE, from, until, limit, offset, id, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, swept])] + } else { + promises = [ + db.any(cashInSql, [cashInTx.PENDING_INTERVAL, from, until, limit, offset, id, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status]), + db.any(cashOutSql, [REDEEMABLE_AGE, from, until, limit, offset, id, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, swept]) + ] + } + + return Promise.all(promises) .then(packager) .then(res => { if (simplified) return simplifiedBatch(res) diff --git a/lib/plugins.js b/lib/plugins.js index de194a36..5b65ad56 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -808,7 +808,7 @@ function plugins (settings, deviceId) { function sweepHd () { const sql = `SELECT id, crypto_code, hd_index FROM cash_out_txs - WHERE hd_index IS NOT NULL AND NOT swept AND status IN ('confirmed', 'instant') AND created < now() - interval '1 week'` + WHERE hd_index IS NOT NULL AND NOT swept AND status IN ('confirmed', 'instant') AND created > now() - interval '1 week'` return db.any(sql) .then(rows => Promise.all(rows.map(sweepHdRow))) diff --git a/lib/plugins/wallet/geth/base.js b/lib/plugins/wallet/geth/base.js index 05f31805..e8536a13 100644 --- a/lib/plugins/wallet/geth/base.js +++ b/lib/plugins/wallet/geth/base.js @@ -10,6 +10,7 @@ const Tx = require('ethereumjs-tx') const { default: PQueue } = require('p-queue') const util = require('ethereumjs-util') const coins = require('@lamassu/coins') +const { default: PQueue } = require('p-queue') const _pify = require('pify') const BN = require('../../../bn') diff --git a/new-lamassu-admin/src/components/SearchFilter.js b/new-lamassu-admin/src/components/SearchFilter.js index 0f5abba1..adb16647 100644 --- a/new-lamassu-admin/src/components/SearchFilter.js +++ b/new-lamassu-admin/src/components/SearchFilter.js @@ -7,7 +7,7 @@ import { P, Label3 } from 'src/components/typography' import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg' import { ReactComponent as FilterIcon } from 'src/styling/icons/button/filter/white.svg' import { ReactComponent as ReverseFilterIcon } from 'src/styling/icons/button/filter/zodiac.svg' -import { onlyFirstToUpper } from 'src/utils/string' +import { onlyFirstToUpper, singularOrPlural } from 'src/utils/string' import { chipStyles, styles } from './SearchFilter.styles' @@ -18,7 +18,7 @@ const SearchFilter = ({ filters, onFilterDelete, deleteAllFilters, - entries + entries = 0 }) => { const chipClasses = useChipStyles() const classes = useStyles() @@ -40,8 +40,11 @@ const SearchFilter = ({