Merge pull request #1327 from chaotixkilla/release-8.0-cherry-picked
backport ETH commits for v8.0.1
This commit is contained in:
commit
d6fb19ed4a
22 changed files with 179 additions and 75 deletions
|
|
@ -25,7 +25,6 @@ module.exports = {
|
|||
|
||||
const STALE_INCOMING_TX_AGE = T.day
|
||||
const STALE_LIVE_INCOMING_TX_AGE = 10 * T.minutes
|
||||
const STALE_LIVE_INCOMING_TX_AGE_FILTER = 5 * T.minutes
|
||||
const MAX_NOTIFY_AGE = T.day
|
||||
const MIN_NOTIFY_AGE = 5 * T.minutes
|
||||
const INSUFFICIENT_FUNDS_CODE = 570
|
||||
|
|
@ -91,21 +90,17 @@ function postProcess (txVector, justAuthorized, pi) {
|
|||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
function fetchOpenTxs (statuses, fromAge, toAge, applyFilter, coinFilter) {
|
||||
const notClause = applyFilter ? '' : 'not'
|
||||
function fetchOpenTxs (statuses, fromAge, toAge) {
|
||||
const sql = `select *
|
||||
from cash_out_txs
|
||||
where ((extract(epoch from (now() - created))) * 1000)>$1
|
||||
and ((extract(epoch from (now() - created))) * 1000)<$2
|
||||
${_.isEmpty(coinFilter)
|
||||
? ``
|
||||
: `and crypto_code ${notClause} in ($3^)`}
|
||||
and status in ($4^)`
|
||||
and status in ($3^)
|
||||
and error is distinct from 'Operator cancel'`
|
||||
|
||||
const coinClause = _.map(pgp.as.text, coinFilter).join(',')
|
||||
const statusClause = _.map(pgp.as.text, statuses).join(',')
|
||||
|
||||
return db.any(sql, [fromAge, toAge, coinClause, statusClause])
|
||||
return db.any(sql, [fromAge, toAge, statusClause])
|
||||
.then(rows => rows.map(toObj))
|
||||
}
|
||||
|
||||
|
|
@ -164,22 +159,18 @@ function getWalletScore (tx, pi) {
|
|||
return tx
|
||||
}
|
||||
|
||||
function monitorLiveIncoming (settings, applyFilter, coinFilter) {
|
||||
function monitorLiveIncoming (settings) {
|
||||
const statuses = ['notSeen', 'published', 'insufficientFunds']
|
||||
const toAge = applyFilter ? STALE_LIVE_INCOMING_TX_AGE_FILTER : STALE_LIVE_INCOMING_TX_AGE
|
||||
|
||||
return monitorIncoming(settings, statuses, 0, toAge, applyFilter, coinFilter)
|
||||
return monitorIncoming(settings, statuses, 0, STALE_LIVE_INCOMING_TX_AGE)
|
||||
}
|
||||
|
||||
function monitorStaleIncoming (settings, applyFilter, coinFilter) {
|
||||
function monitorStaleIncoming (settings) {
|
||||
const statuses = ['notSeen', 'published', 'authorized', 'instant', 'rejected', 'insufficientFunds']
|
||||
const fromAge = applyFilter ? STALE_LIVE_INCOMING_TX_AGE_FILTER : STALE_LIVE_INCOMING_TX_AGE
|
||||
|
||||
return monitorIncoming(settings, statuses, fromAge, STALE_INCOMING_TX_AGE, applyFilter, coinFilter)
|
||||
return monitorIncoming(settings, statuses, STALE_LIVE_INCOMING_TX_AGE, STALE_INCOMING_TX_AGE)
|
||||
}
|
||||
|
||||
function monitorIncoming (settings, statuses, fromAge, toAge, applyFilter, coinFilter) {
|
||||
return fetchOpenTxs(statuses, fromAge, toAge, applyFilter, coinFilter)
|
||||
function monitorIncoming (settings, statuses, fromAge, toAge) {
|
||||
return fetchOpenTxs(statuses, fromAge, toAge)
|
||||
.then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings)))
|
||||
.catch(err => {
|
||||
if (err.code === dbErrorCodes.SERIALIZATION_FAILURE) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ const RECEIPT = 'sms_receipt'
|
|||
|
||||
const WALLET_SCORE_THRESHOLD = 9
|
||||
|
||||
const BALANCE_FETCH_SPEED_MULTIPLIER = {
|
||||
NORMAL: 1,
|
||||
SLOW: 3
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
anonymousCustomer,
|
||||
CASSETTE_MAX_CAPACITY,
|
||||
|
|
@ -39,5 +44,6 @@ module.exports = {
|
|||
CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES,
|
||||
CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES,
|
||||
WALLET_SCORE_THRESHOLD,
|
||||
RECEIPT
|
||||
RECEIPT,
|
||||
BALANCE_FETCH_SPEED_MULTIPLIER
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ const resolvers = {
|
|||
isAnonymous: parent => (parent.customerId === anonymous.uuid)
|
||||
},
|
||||
Query: {
|
||||
transactions: (...[, { from, until, limit, offset, deviceId, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, excludeTestingCustomers }]) =>
|
||||
transactions.batch(from, until, limit, offset, deviceId, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, excludeTestingCustomers),
|
||||
transactionsCsv: (...[, { from, until, limit, offset, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, timezone, excludeTestingCustomers, simplified }]) =>
|
||||
transactions.batch(from, until, limit, offset, null, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, excludeTestingCustomers, simplified)
|
||||
.then(data => parseAsync(logDateFormat(timezone, data, ['created', 'sendTime']))),
|
||||
transactions: (...[, { from, until, limit, offset, deviceId, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, swept, excludeTestingCustomers }]) =>
|
||||
transactions.batch(from, until, limit, offset, deviceId, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, swept, excludeTestingCustomers),
|
||||
transactionsCsv: (...[, { from, until, limit, offset, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, swept, timezone, excludeTestingCustomers, simplified }]) =>
|
||||
transactions.batch(from, until, limit, offset, null, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, swept, excludeTestingCustomers, simplified)
|
||||
.then(data => parseAsync(logDateFormat(timezone, data, ['created', 'sendTime', 'publishedAt']))),
|
||||
transactionCsv: (...[, { id, txClass, timezone }]) =>
|
||||
transactions.getTx(id, txClass).then(data =>
|
||||
parseAsync(logDateFormat(timezone, [data], ['created', 'sendTime']))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
// 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])
|
||||
])
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -806,8 +806,8 @@ 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')`
|
||||
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'`
|
||||
|
||||
return db.any(sql)
|
||||
.then(rows => Promise.all(rows.map(sweepHdRow)))
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ const { default: Common, Chain, Hardfork } = require('@ethereumjs/common')
|
|||
const Tx = require('ethereumjs-tx')
|
||||
const util = require('ethereumjs-util')
|
||||
const coins = require('@lamassu/coins')
|
||||
const { default: PQueue } = require('p-queue')
|
||||
|
||||
const _pify = require('pify')
|
||||
const BN = require('../../../bn')
|
||||
const ABI = require('../../tokens')
|
||||
|
|
@ -48,6 +50,11 @@ const logInfuraCall = call => {
|
|||
logger.info(`Calling web3 method ${call} via Infura. Current count for this session: ${JSON.stringify(infuraCalls)}`)
|
||||
}
|
||||
|
||||
const SWEEP_QUEUE = new PQueue({
|
||||
concurrency: 3,
|
||||
interval: 250,
|
||||
})
|
||||
|
||||
function connect (url) {
|
||||
web3.setProvider(new web3.providers.HttpProvider(url))
|
||||
}
|
||||
|
|
@ -236,13 +243,14 @@ function sweep (account, cryptoCode, hdIndex, settings, operatorId) {
|
|||
const wallet = paymentHdNode(account).deriveChild(hdIndex).getWallet()
|
||||
const fromAddress = wallet.getChecksumAddressString()
|
||||
|
||||
return confirmedBalance(fromAddress, cryptoCode)
|
||||
return SWEEP_QUEUE.add(() => confirmedBalance(fromAddress, cryptoCode)
|
||||
.then(r => {
|
||||
if (r.eq(0)) return
|
||||
|
||||
return generateTx(defaultAddress(account), wallet, r, true, cryptoCode)
|
||||
.then(signedTx => pify(web3.eth.sendSignedTransaction)(signedTx))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function newAddress (account, info, tx, settings, operatorId) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
const _ = require('lodash/fp')
|
||||
const NodeCache = require('node-cache')
|
||||
const base = require('../geth/base')
|
||||
const T = require('../../../time')
|
||||
const { BALANCE_FETCH_SPEED_MULTIPLIER } = require('../../../constants')
|
||||
|
||||
const REGULAR_TX_POLLING = 5 * T.seconds
|
||||
|
||||
const NAME = 'infura'
|
||||
|
||||
|
|
@ -12,4 +17,54 @@ function run (account) {
|
|||
base.connect(endpoint)
|
||||
}
|
||||
|
||||
module.exports = _.merge(base, { NAME, run })
|
||||
const txsCache = new NodeCache({
|
||||
stdTTL: T.hour / 1000,
|
||||
checkperiod: T.minute / 1000,
|
||||
deleteOnExpire: true
|
||||
})
|
||||
|
||||
function shouldGetStatus (tx) {
|
||||
const timePassedSinceTx = Date.now() - new Date(tx.created)
|
||||
const timePassedSinceReq = Date.now() - new Date(txsCache.get(tx.id).lastReqTime)
|
||||
|
||||
// Allow for infura to gradually lower the amount of requests based on the time passed since the transaction
|
||||
// Until first 5 minutes - 1/2 regular polling speed
|
||||
// Until first 10 minutes - 1/4 regular polling speed
|
||||
// Until first hour - 1/8 polling speed
|
||||
// Until first two hours - 1/12 polling speed
|
||||
// Until first four hours - 1/16 polling speed
|
||||
// Until first day - 1/24 polling speed
|
||||
// After first day - 1/32 polling speed
|
||||
if (timePassedSinceTx < 5 * T.minutes) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 2 * REGULAR_TX_POLLING
|
||||
if (timePassedSinceTx < 10 * T.minutes) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 4 * REGULAR_TX_POLLING
|
||||
if (timePassedSinceTx < 1 * T.hour) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 8 * REGULAR_TX_POLLING
|
||||
if (timePassedSinceTx < 2 * T.hours) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 12 * REGULAR_TX_POLLING
|
||||
if (timePassedSinceTx < 4 * T.hours) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 16 * REGULAR_TX_POLLING
|
||||
if (timePassedSinceTx < 1 * T.day) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 24 * REGULAR_TX_POLLING
|
||||
return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 32 * REGULAR_TX_POLLING
|
||||
}
|
||||
|
||||
// Override geth's getStatus function to allow for different polling timing
|
||||
function getStatus (account, tx, requested, settings, operatorId) {
|
||||
if (_.isNil(txsCache.get(tx.id))) {
|
||||
txsCache.set(tx.id, { lastReqTime: Date.now() })
|
||||
}
|
||||
|
||||
// return last available response
|
||||
if (!shouldGetStatus(tx)) {
|
||||
return Promise.resolve(txsCache.get(tx.id).res)
|
||||
}
|
||||
|
||||
return base.getStatus(account, tx, requested, settings, operatorId)
|
||||
.then(res => {
|
||||
if (res.status === 'confirmed') {
|
||||
txsCache.del(tx.id) // Transaction reached final status, can trim it from the caching obj
|
||||
} else {
|
||||
txsCache.set(tx.id, { lastReqTime: Date.now(), res })
|
||||
txsCache.ttl(tx.id, T.hour / 1000)
|
||||
}
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = _.merge(base, { NAME, run, getStatus, fetchSpeed: BALANCE_FETCH_SPEED_MULTIPLIER.SLOW })
|
||||
|
|
|
|||
|
|
@ -21,10 +21,8 @@ const processBatches = require('./tx-batching-processing')
|
|||
|
||||
const INCOMING_TX_INTERVAL = 30 * T.seconds
|
||||
const LIVE_INCOMING_TX_INTERVAL = 5 * T.seconds
|
||||
const INCOMING_TX_INTERVAL_FILTER = 1 * T.minute
|
||||
const LIVE_INCOMING_TX_INTERVAL_FILTER = 10 * T.seconds
|
||||
const UNNOTIFIED_INTERVAL = 10 * T.seconds
|
||||
const SWEEP_HD_INTERVAL = T.minute
|
||||
const SWEEP_HD_INTERVAL = 5 * T.minute
|
||||
const TRADE_INTERVAL = 60 * T.seconds
|
||||
const PONG_INTERVAL = 10 * T.seconds
|
||||
const LOGS_CLEAR_INTERVAL = 1 * T.day
|
||||
|
|
@ -60,7 +58,6 @@ const QUEUE = {
|
|||
SLOW: SLOW_QUEUE
|
||||
}
|
||||
|
||||
const coinFilter = ['ETH']
|
||||
const schemaCallbacks = new Map()
|
||||
|
||||
const cachedVariables = new NodeCache({
|
||||
|
|
@ -167,24 +164,16 @@ function doPolling (schema) {
|
|||
pi().executeTrades()
|
||||
pi().pong()
|
||||
pi().clearOldLogs()
|
||||
cashOutTx.monitorLiveIncoming(settings(), false, coinFilter)
|
||||
cashOutTx.monitorStaleIncoming(settings(), false, coinFilter)
|
||||
if (!_.isEmpty(coinFilter)) {
|
||||
cashOutTx.monitorLiveIncoming(settings(), true, coinFilter)
|
||||
cashOutTx.monitorStaleIncoming(settings(), true, coinFilter)
|
||||
}
|
||||
cashOutTx.monitorLiveIncoming(settings())
|
||||
cashOutTx.monitorStaleIncoming(settings())
|
||||
cashOutTx.monitorUnnotified(settings())
|
||||
pi().sweepHd()
|
||||
notifier.checkNotification(pi())
|
||||
updateCoinAtmRadar()
|
||||
|
||||
addToQueue(pi().executeTrades, TRADE_INTERVAL, schema, QUEUE.FAST)
|
||||
addToQueue(cashOutTx.monitorLiveIncoming, LIVE_INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings, false, coinFilter)
|
||||
addToQueue(cashOutTx.monitorStaleIncoming, INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings, false, coinFilter)
|
||||
if (!_.isEmpty(coinFilter)) {
|
||||
addToQueue(cashOutTx.monitorLiveIncoming, LIVE_INCOMING_TX_INTERVAL_FILTER, schema, QUEUE.FAST, settings, true, coinFilter)
|
||||
addToQueue(cashOutTx.monitorStaleIncoming, INCOMING_TX_INTERVAL_FILTER, schema, QUEUE.FAST, settings, true, coinFilter)
|
||||
}
|
||||
addToQueue(cashOutTx.monitorLiveIncoming, LIVE_INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings)
|
||||
addToQueue(cashOutTx.monitorStaleIncoming, INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings)
|
||||
addToQueue(cashOutTx.monitorUnnotified, UNNOTIFIED_INTERVAL, schema, QUEUE.FAST, settings)
|
||||
addToQueue(cashInTx.monitorPending, PENDING_INTERVAL, schema, QUEUE.FAST, settings)
|
||||
addToQueue(processBatches, UNNOTIFIED_INTERVAL, schema, QUEUE.FAST, settings, TRANSACTION_BATCH_LIFECYCLE)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const httpError = require('./route-helpers').httpError
|
|||
const logger = require('./logger')
|
||||
const { getOpenBatchCryptoValue } = require('./tx-batching')
|
||||
const BN = require('./bn')
|
||||
const { BALANCE_FETCH_SPEED_MULTIPLIER } = require('./constants')
|
||||
|
||||
const FETCH_INTERVAL = 5000
|
||||
const INSUFFICIENT_FUNDS_CODE = 570
|
||||
|
|
@ -254,20 +255,28 @@ function checkBlockchainStatus (settings, cryptoCode) {
|
|||
.then(({ checkBlockchainStatus }) => checkBlockchainStatus(cryptoCode))
|
||||
}
|
||||
|
||||
const coinFilter = ['ETH']
|
||||
|
||||
const balance = (settings, cryptoCode) => {
|
||||
if (_.includes(coinFilter, cryptoCode)) return balanceFiltered(settings, cryptoCode)
|
||||
return balanceUnfiltered(settings, cryptoCode)
|
||||
return fetchWallet(settings, cryptoCode)
|
||||
.then(r => r.wallet.fetchSpeed ?? BALANCE_FETCH_SPEED_MULTIPLIER.NORMAL)
|
||||
.then(multiplier => {
|
||||
switch (multiplier) {
|
||||
case BALANCE_FETCH_SPEED_MULTIPLIER.NORMAL:
|
||||
return balanceNormal(settings, cryptoCode)
|
||||
case BALANCE_FETCH_SPEED_MULTIPLIER.SLOW:
|
||||
return balanceSlow(settings, cryptoCode)
|
||||
default:
|
||||
throw new Error()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const balanceUnfiltered = mem(_balance, {
|
||||
maxAge: FETCH_INTERVAL,
|
||||
const balanceNormal = mem(_balance, {
|
||||
maxAge: BALANCE_FETCH_SPEED_MULTIPLIER.NORMAL * FETCH_INTERVAL,
|
||||
cacheKey: (settings, cryptoCode) => cryptoCode
|
||||
})
|
||||
|
||||
const balanceFiltered = mem(_balance, {
|
||||
maxAge: 3 * FETCH_INTERVAL,
|
||||
const balanceSlow = mem(_balance, {
|
||||
maxAge: BALANCE_FETCH_SPEED_MULTIPLIER.SLOW * FETCH_INTERVAL,
|
||||
cacheKey: (settings, cryptoCode) => cryptoCode
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
|||
</div>
|
||||
<div className={classes.deleteWrapper}>
|
||||
{
|
||||
<Label3 className={classes.entries}>{`${entries ??
|
||||
0} entries`}</Label3>
|
||||
<Label3 className={classes.entries}>{`${entries} ${singularOrPlural(
|
||||
entries,
|
||||
`entry`,
|
||||
`entries`
|
||||
)}`}</Label3>
|
||||
}
|
||||
<ActionButton
|
||||
color="secondary"
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {
|
|||
offErrorColor
|
||||
} from 'src/styling/variables'
|
||||
import { URI } from 'src/utils/apollo'
|
||||
import { SWEEPABLE_CRYPTOS } from 'src/utils/constants'
|
||||
import * as Customer from 'src/utils/customer'
|
||||
|
||||
import CopyToClipboard from './CopyToClipboard'
|
||||
|
|
@ -405,6 +406,14 @@ const DetailsRow = ({ it: tx, timezone }) => {
|
|||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
{!R.isNil(tx.swept) && R.includes(tx.cryptoCode, SWEEPABLE_CRYPTOS) && (
|
||||
<div className={classes.swept}>
|
||||
<Label>Sweep status</Label>
|
||||
<span className={classes.bold}>
|
||||
{tx.swept ? `Swept` : `Unswept`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Label>Other actions</Label>
|
||||
<div className={classes.otherActionsGroup}>
|
||||
|
|
|
|||
|
|
@ -131,5 +131,8 @@ export default {
|
|||
},
|
||||
error: {
|
||||
color: tomato
|
||||
},
|
||||
swept: {
|
||||
width: 250
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ const GET_TRANSACTIONS = gql`
|
|||
$cryptoCode: String
|
||||
$toAddress: String
|
||||
$status: String
|
||||
$swept: Boolean
|
||||
) {
|
||||
transactions(
|
||||
limit: $limit
|
||||
|
|
@ -87,6 +88,7 @@ const GET_TRANSACTIONS = gql`
|
|||
cryptoCode: $cryptoCode
|
||||
toAddress: $toAddress
|
||||
status: $status
|
||||
swept: $swept
|
||||
) {
|
||||
id
|
||||
txClass
|
||||
|
|
@ -121,6 +123,7 @@ const GET_TRANSACTIONS = gql`
|
|||
rawTickerPrice
|
||||
batchError
|
||||
walletScore
|
||||
swept
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
@ -246,7 +249,8 @@ const Transactions = () => {
|
|||
fiatCode: filtersObject.fiat,
|
||||
cryptoCode: filtersObject.crypto,
|
||||
toAddress: filtersObject.address,
|
||||
status: filtersObject.status
|
||||
status: filtersObject.status,
|
||||
swept: filtersObject.swept === 'Swept'
|
||||
})
|
||||
|
||||
refetch && refetch()
|
||||
|
|
@ -269,7 +273,8 @@ const Transactions = () => {
|
|||
fiatCode: filtersObject.fiat,
|
||||
cryptoCode: filtersObject.crypto,
|
||||
toAddress: filtersObject.address,
|
||||
status: filtersObject.status
|
||||
status: filtersObject.status,
|
||||
swept: filtersObject.swept === 'Swept'
|
||||
})
|
||||
|
||||
refetch && refetch()
|
||||
|
|
@ -287,7 +292,8 @@ const Transactions = () => {
|
|||
fiatCode: filtersObject.fiat,
|
||||
cryptoCode: filtersObject.crypto,
|
||||
toAddress: filtersObject.address,
|
||||
status: filtersObject.status
|
||||
status: filtersObject.status,
|
||||
swept: filtersObject.swept === 'Swept'
|
||||
})
|
||||
|
||||
refetch && refetch()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ const MANUAL = 'manual'
|
|||
|
||||
const IP_CHECK_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
|
||||
|
||||
const SWEEPABLE_CRYPTOS = ['ETH']
|
||||
|
||||
export {
|
||||
CURRENCY_MAX,
|
||||
MIN_NUMBER_OF_CASSETTES,
|
||||
|
|
@ -15,5 +17,6 @@ export {
|
|||
AUTOMATIC,
|
||||
MANUAL,
|
||||
WALLET_SCORING_DEFAULT_THRESHOLD,
|
||||
IP_CHECK_REGEX
|
||||
IP_CHECK_REGEX,
|
||||
SWEEPABLE_CRYPTOS
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"files": {
|
||||
"main.js": "/static/js/main.be5427dd.chunk.js",
|
||||
"main.js.map": "/static/js/main.be5427dd.chunk.js.map",
|
||||
"main.js": "/static/js/main.cbb37932.chunk.js",
|
||||
"main.js.map": "/static/js/main.cbb37932.chunk.js.map",
|
||||
"runtime-main.js": "/static/js/runtime-main.5b925903.js",
|
||||
"runtime-main.js.map": "/static/js/runtime-main.5b925903.js.map",
|
||||
"static/js/2.9bb48edb.chunk.js": "/static/js/2.9bb48edb.chunk.js",
|
||||
|
|
@ -154,6 +154,6 @@
|
|||
"entrypoints": [
|
||||
"static/js/runtime-main.5b925903.js",
|
||||
"static/js/2.9bb48edb.chunk.js",
|
||||
"static/js/main.be5427dd.chunk.js"
|
||||
"static/js/main.cbb37932.chunk.js"
|
||||
]
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="robots" content="noindex"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Lamassu Admin</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="root"></div><script>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/";var l=this["webpackJsonplamassu-admin"]=this["webpackJsonplamassu-admin"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="/static/js/2.9bb48edb.chunk.js"></script><script src="/static/js/main.be5427dd.chunk.js"></script></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="robots" content="noindex"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Lamassu Admin</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="root"></div><script>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/";var l=this["webpackJsonplamassu-admin"]=this["webpackJsonplamassu-admin"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="/static/js/2.9bb48edb.chunk.js"></script><script src="/static/js/main.cbb37932.chunk.js"></script></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/static/js/main.cbb37932.chunk.js
Normal file
2
public/static/js/main.cbb37932.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
public/static/js/main.cbb37932.chunk.js.map
Normal file
1
public/static/js/main.cbb37932.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue