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_INCOMING_TX_AGE = T.day
|
||||||
const STALE_LIVE_INCOMING_TX_AGE = 10 * T.minutes
|
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 MAX_NOTIFY_AGE = T.day
|
||||||
const MIN_NOTIFY_AGE = 5 * T.minutes
|
const MIN_NOTIFY_AGE = 5 * T.minutes
|
||||||
const INSUFFICIENT_FUNDS_CODE = 570
|
const INSUFFICIENT_FUNDS_CODE = 570
|
||||||
|
|
@ -91,21 +90,17 @@ function postProcess (txVector, justAuthorized, pi) {
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchOpenTxs (statuses, fromAge, toAge, applyFilter, coinFilter) {
|
function fetchOpenTxs (statuses, fromAge, toAge) {
|
||||||
const notClause = applyFilter ? '' : 'not'
|
|
||||||
const sql = `select *
|
const sql = `select *
|
||||||
from cash_out_txs
|
from cash_out_txs
|
||||||
where ((extract(epoch from (now() - created))) * 1000)>$1
|
where ((extract(epoch from (now() - created))) * 1000)>$1
|
||||||
and ((extract(epoch from (now() - created))) * 1000)<$2
|
and ((extract(epoch from (now() - created))) * 1000)<$2
|
||||||
${_.isEmpty(coinFilter)
|
and status in ($3^)
|
||||||
? ``
|
and error is distinct from 'Operator cancel'`
|
||||||
: `and crypto_code ${notClause} in ($3^)`}
|
|
||||||
and status in ($4^)`
|
|
||||||
|
|
||||||
const coinClause = _.map(pgp.as.text, coinFilter).join(',')
|
|
||||||
const statusClause = _.map(pgp.as.text, statuses).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))
|
.then(rows => rows.map(toObj))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,22 +159,18 @@ function getWalletScore (tx, pi) {
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
|
||||||
function monitorLiveIncoming (settings, applyFilter, coinFilter) {
|
function monitorLiveIncoming (settings) {
|
||||||
const statuses = ['notSeen', 'published', 'insufficientFunds']
|
const statuses = ['notSeen', 'published', 'insufficientFunds']
|
||||||
const toAge = applyFilter ? STALE_LIVE_INCOMING_TX_AGE_FILTER : STALE_LIVE_INCOMING_TX_AGE
|
return monitorIncoming(settings, statuses, 0, STALE_LIVE_INCOMING_TX_AGE)
|
||||||
|
|
||||||
return monitorIncoming(settings, statuses, 0, toAge, applyFilter, coinFilter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function monitorStaleIncoming (settings, applyFilter, coinFilter) {
|
function monitorStaleIncoming (settings) {
|
||||||
const statuses = ['notSeen', 'published', 'authorized', 'instant', 'rejected', 'insufficientFunds']
|
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, STALE_LIVE_INCOMING_TX_AGE, STALE_INCOMING_TX_AGE)
|
||||||
|
|
||||||
return monitorIncoming(settings, statuses, fromAge, STALE_INCOMING_TX_AGE, applyFilter, coinFilter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function monitorIncoming (settings, statuses, fromAge, toAge, applyFilter, coinFilter) {
|
function monitorIncoming (settings, statuses, fromAge, toAge) {
|
||||||
return fetchOpenTxs(statuses, fromAge, toAge, applyFilter, coinFilter)
|
return fetchOpenTxs(statuses, fromAge, toAge)
|
||||||
.then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings)))
|
.then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings)))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.code === dbErrorCodes.SERIALIZATION_FAILURE) {
|
if (err.code === dbErrorCodes.SERIALIZATION_FAILURE) {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@ const RECEIPT = 'sms_receipt'
|
||||||
|
|
||||||
const WALLET_SCORE_THRESHOLD = 9
|
const WALLET_SCORE_THRESHOLD = 9
|
||||||
|
|
||||||
|
const BALANCE_FETCH_SPEED_MULTIPLIER = {
|
||||||
|
NORMAL: 1,
|
||||||
|
SLOW: 3
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
anonymousCustomer,
|
anonymousCustomer,
|
||||||
CASSETTE_MAX_CAPACITY,
|
CASSETTE_MAX_CAPACITY,
|
||||||
|
|
@ -39,5 +44,6 @@ module.exports = {
|
||||||
CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES,
|
CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES,
|
||||||
CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES,
|
CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES,
|
||||||
WALLET_SCORE_THRESHOLD,
|
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_in_txs UNION
|
||||||
SELECT 'address' AS type, to_address AS value FROM cash_out_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, ${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`
|
) f`
|
||||||
|
|
||||||
return db.any(sql)
|
return db.any(sql)
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@ const resolvers = {
|
||||||
isAnonymous: parent => (parent.customerId === anonymous.uuid)
|
isAnonymous: parent => (parent.customerId === anonymous.uuid)
|
||||||
},
|
},
|
||||||
Query: {
|
Query: {
|
||||||
transactions: (...[, { from, until, limit, offset, deviceId, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, excludeTestingCustomers }]) =>
|
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, 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, timezone, excludeTestingCustomers, simplified }]) =>
|
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, 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']))),
|
.then(data => parseAsync(logDateFormat(timezone, data, ['created', 'sendTime', 'publishedAt']))),
|
||||||
transactionCsv: (...[, { id, txClass, timezone }]) =>
|
transactionCsv: (...[, { id, txClass, timezone }]) =>
|
||||||
transactions.getTx(id, txClass).then(data =>
|
transactions.getTx(id, txClass).then(data =>
|
||||||
parseAsync(logDateFormat(timezone, [data], ['created', 'sendTime']))
|
parseAsync(logDateFormat(timezone, [data], ['created', 'sendTime']))
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ const typeDef = gql`
|
||||||
batchError: String
|
batchError: String
|
||||||
walletScore: Int
|
walletScore: Int
|
||||||
profit: String
|
profit: String
|
||||||
|
swept: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type Filter {
|
type Filter {
|
||||||
|
|
@ -58,8 +59,8 @@ const typeDef = gql`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
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
|
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, timezone: String, excludeTestingCustomers: Boolean, simplified: Boolean): String @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
|
transactionCsv(id: ID, txClass: String, timezone: String): String @auth
|
||||||
txAssociatedDataCsv(id: ID, txClass: String, timezone: String): String @auth
|
txAssociatedDataCsv(id: ID, txClass: String, timezone: String): String @auth
|
||||||
transactionFilters: [Filter] @auth
|
transactionFilters: [Filter] @auth
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ function batch (
|
||||||
cryptoCode = null,
|
cryptoCode = null,
|
||||||
toAddress = null,
|
toAddress = null,
|
||||||
status = null,
|
status = null,
|
||||||
|
swept = null,
|
||||||
excludeTestingCustomers = false,
|
excludeTestingCustomers = false,
|
||||||
simplified
|
simplified
|
||||||
) {
|
) {
|
||||||
|
|
@ -109,14 +110,33 @@ function batch (
|
||||||
AND ($11 is null or txs.crypto_code = $11)
|
AND ($11 is null or txs.crypto_code = $11)
|
||||||
AND ($12 is null or txs.to_address = $12)
|
AND ($12 is null or txs.to_address = $12)
|
||||||
AND ($13 is null or txs.txStatus = $13)
|
AND ($13 is null or txs.txStatus = $13)
|
||||||
|
AND ($14 is null or txs.swept = $14)
|
||||||
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
|
||||||
AND (fiat > 0)
|
AND (fiat > 0)
|
||||||
ORDER BY created DESC limit $4 offset $5`
|
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(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(packager)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (simplified) return simplifiedBatch(res)
|
if (simplified) return simplifiedBatch(res)
|
||||||
|
|
|
||||||
|
|
@ -806,8 +806,8 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sweepHd () {
|
function sweepHd () {
|
||||||
const sql = `select id, crypto_code, hd_index from cash_out_txs
|
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')`
|
WHERE hd_index IS NOT NULL AND NOT swept AND status IN ('confirmed', 'instant') AND created > now() - interval '1 week'`
|
||||||
|
|
||||||
return db.any(sql)
|
return db.any(sql)
|
||||||
.then(rows => Promise.all(rows.map(sweepHdRow)))
|
.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 Tx = require('ethereumjs-tx')
|
||||||
const util = require('ethereumjs-util')
|
const util = require('ethereumjs-util')
|
||||||
const coins = require('@lamassu/coins')
|
const coins = require('@lamassu/coins')
|
||||||
|
const { default: PQueue } = require('p-queue')
|
||||||
|
|
||||||
const _pify = require('pify')
|
const _pify = require('pify')
|
||||||
const BN = require('../../../bn')
|
const BN = require('../../../bn')
|
||||||
const ABI = require('../../tokens')
|
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)}`)
|
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) {
|
function connect (url) {
|
||||||
web3.setProvider(new web3.providers.HttpProvider(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 wallet = paymentHdNode(account).deriveChild(hdIndex).getWallet()
|
||||||
const fromAddress = wallet.getChecksumAddressString()
|
const fromAddress = wallet.getChecksumAddressString()
|
||||||
|
|
||||||
return confirmedBalance(fromAddress, cryptoCode)
|
return SWEEP_QUEUE.add(() => confirmedBalance(fromAddress, cryptoCode)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
if (r.eq(0)) return
|
if (r.eq(0)) return
|
||||||
|
|
||||||
return generateTx(defaultAddress(account), wallet, r, true, cryptoCode)
|
return generateTx(defaultAddress(account), wallet, r, true, cryptoCode)
|
||||||
.then(signedTx => pify(web3.eth.sendSignedTransaction)(signedTx))
|
.then(signedTx => pify(web3.eth.sendSignedTransaction)(signedTx))
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function newAddress (account, info, tx, settings, operatorId) {
|
function newAddress (account, info, tx, settings, operatorId) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
const NodeCache = require('node-cache')
|
||||||
const base = require('../geth/base')
|
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'
|
const NAME = 'infura'
|
||||||
|
|
||||||
|
|
@ -12,4 +17,54 @@ function run (account) {
|
||||||
base.connect(endpoint)
|
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 INCOMING_TX_INTERVAL = 30 * T.seconds
|
||||||
const LIVE_INCOMING_TX_INTERVAL = 5 * 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 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 TRADE_INTERVAL = 60 * T.seconds
|
||||||
const PONG_INTERVAL = 10 * T.seconds
|
const PONG_INTERVAL = 10 * T.seconds
|
||||||
const LOGS_CLEAR_INTERVAL = 1 * T.day
|
const LOGS_CLEAR_INTERVAL = 1 * T.day
|
||||||
|
|
@ -60,7 +58,6 @@ const QUEUE = {
|
||||||
SLOW: SLOW_QUEUE
|
SLOW: SLOW_QUEUE
|
||||||
}
|
}
|
||||||
|
|
||||||
const coinFilter = ['ETH']
|
|
||||||
const schemaCallbacks = new Map()
|
const schemaCallbacks = new Map()
|
||||||
|
|
||||||
const cachedVariables = new NodeCache({
|
const cachedVariables = new NodeCache({
|
||||||
|
|
@ -167,24 +164,16 @@ function doPolling (schema) {
|
||||||
pi().executeTrades()
|
pi().executeTrades()
|
||||||
pi().pong()
|
pi().pong()
|
||||||
pi().clearOldLogs()
|
pi().clearOldLogs()
|
||||||
cashOutTx.monitorLiveIncoming(settings(), false, coinFilter)
|
cashOutTx.monitorLiveIncoming(settings())
|
||||||
cashOutTx.monitorStaleIncoming(settings(), false, coinFilter)
|
cashOutTx.monitorStaleIncoming(settings())
|
||||||
if (!_.isEmpty(coinFilter)) {
|
|
||||||
cashOutTx.monitorLiveIncoming(settings(), true, coinFilter)
|
|
||||||
cashOutTx.monitorStaleIncoming(settings(), true, coinFilter)
|
|
||||||
}
|
|
||||||
cashOutTx.monitorUnnotified(settings())
|
cashOutTx.monitorUnnotified(settings())
|
||||||
pi().sweepHd()
|
pi().sweepHd()
|
||||||
notifier.checkNotification(pi())
|
notifier.checkNotification(pi())
|
||||||
updateCoinAtmRadar()
|
updateCoinAtmRadar()
|
||||||
|
|
||||||
addToQueue(pi().executeTrades, TRADE_INTERVAL, schema, QUEUE.FAST)
|
addToQueue(pi().executeTrades, TRADE_INTERVAL, schema, QUEUE.FAST)
|
||||||
addToQueue(cashOutTx.monitorLiveIncoming, LIVE_INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings, false, coinFilter)
|
addToQueue(cashOutTx.monitorLiveIncoming, LIVE_INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings)
|
||||||
addToQueue(cashOutTx.monitorStaleIncoming, INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings, false, coinFilter)
|
addToQueue(cashOutTx.monitorStaleIncoming, INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings)
|
||||||
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.monitorUnnotified, UNNOTIFIED_INTERVAL, schema, QUEUE.FAST, settings)
|
addToQueue(cashOutTx.monitorUnnotified, UNNOTIFIED_INTERVAL, schema, QUEUE.FAST, settings)
|
||||||
addToQueue(cashInTx.monitorPending, PENDING_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)
|
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 logger = require('./logger')
|
||||||
const { getOpenBatchCryptoValue } = require('./tx-batching')
|
const { getOpenBatchCryptoValue } = require('./tx-batching')
|
||||||
const BN = require('./bn')
|
const BN = require('./bn')
|
||||||
|
const { BALANCE_FETCH_SPEED_MULTIPLIER } = require('./constants')
|
||||||
|
|
||||||
const FETCH_INTERVAL = 5000
|
const FETCH_INTERVAL = 5000
|
||||||
const INSUFFICIENT_FUNDS_CODE = 570
|
const INSUFFICIENT_FUNDS_CODE = 570
|
||||||
|
|
@ -254,20 +255,28 @@ function checkBlockchainStatus (settings, cryptoCode) {
|
||||||
.then(({ checkBlockchainStatus }) => checkBlockchainStatus(cryptoCode))
|
.then(({ checkBlockchainStatus }) => checkBlockchainStatus(cryptoCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
const coinFilter = ['ETH']
|
|
||||||
|
|
||||||
const balance = (settings, cryptoCode) => {
|
const balance = (settings, cryptoCode) => {
|
||||||
if (_.includes(coinFilter, cryptoCode)) return balanceFiltered(settings, cryptoCode)
|
return fetchWallet(settings, cryptoCode)
|
||||||
return balanceUnfiltered(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, {
|
const balanceNormal = mem(_balance, {
|
||||||
maxAge: FETCH_INTERVAL,
|
maxAge: BALANCE_FETCH_SPEED_MULTIPLIER.NORMAL * FETCH_INTERVAL,
|
||||||
cacheKey: (settings, cryptoCode) => cryptoCode
|
cacheKey: (settings, cryptoCode) => cryptoCode
|
||||||
})
|
})
|
||||||
|
|
||||||
const balanceFiltered = mem(_balance, {
|
const balanceSlow = mem(_balance, {
|
||||||
maxAge: 3 * FETCH_INTERVAL,
|
maxAge: BALANCE_FETCH_SPEED_MULTIPLIER.SLOW * FETCH_INTERVAL,
|
||||||
cacheKey: (settings, cryptoCode) => cryptoCode
|
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 CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
||||||
import { ReactComponent as FilterIcon } from 'src/styling/icons/button/filter/white.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 { 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'
|
import { chipStyles, styles } from './SearchFilter.styles'
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ const SearchFilter = ({
|
||||||
filters,
|
filters,
|
||||||
onFilterDelete,
|
onFilterDelete,
|
||||||
deleteAllFilters,
|
deleteAllFilters,
|
||||||
entries
|
entries = 0
|
||||||
}) => {
|
}) => {
|
||||||
const chipClasses = useChipStyles()
|
const chipClasses = useChipStyles()
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
@ -40,8 +40,11 @@ const SearchFilter = ({
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.deleteWrapper}>
|
<div className={classes.deleteWrapper}>
|
||||||
{
|
{
|
||||||
<Label3 className={classes.entries}>{`${entries ??
|
<Label3 className={classes.entries}>{`${entries} ${singularOrPlural(
|
||||||
0} entries`}</Label3>
|
entries,
|
||||||
|
`entry`,
|
||||||
|
`entries`
|
||||||
|
)}`}</Label3>
|
||||||
}
|
}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import {
|
||||||
offErrorColor
|
offErrorColor
|
||||||
} from 'src/styling/variables'
|
} from 'src/styling/variables'
|
||||||
import { URI } from 'src/utils/apollo'
|
import { URI } from 'src/utils/apollo'
|
||||||
|
import { SWEEPABLE_CRYPTOS } from 'src/utils/constants'
|
||||||
import * as Customer from 'src/utils/customer'
|
import * as Customer from 'src/utils/customer'
|
||||||
|
|
||||||
import CopyToClipboard from './CopyToClipboard'
|
import CopyToClipboard from './CopyToClipboard'
|
||||||
|
|
@ -405,6 +406,14 @@ const DetailsRow = ({ it: tx, timezone }) => {
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<Label>Other actions</Label>
|
<Label>Other actions</Label>
|
||||||
<div className={classes.otherActionsGroup}>
|
<div className={classes.otherActionsGroup}>
|
||||||
|
|
|
||||||
|
|
@ -131,5 +131,8 @@ export default {
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
color: tomato
|
color: tomato
|
||||||
|
},
|
||||||
|
swept: {
|
||||||
|
width: 250
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ const GET_TRANSACTIONS = gql`
|
||||||
$cryptoCode: String
|
$cryptoCode: String
|
||||||
$toAddress: String
|
$toAddress: String
|
||||||
$status: String
|
$status: String
|
||||||
|
$swept: Boolean
|
||||||
) {
|
) {
|
||||||
transactions(
|
transactions(
|
||||||
limit: $limit
|
limit: $limit
|
||||||
|
|
@ -87,6 +88,7 @@ const GET_TRANSACTIONS = gql`
|
||||||
cryptoCode: $cryptoCode
|
cryptoCode: $cryptoCode
|
||||||
toAddress: $toAddress
|
toAddress: $toAddress
|
||||||
status: $status
|
status: $status
|
||||||
|
swept: $swept
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
txClass
|
txClass
|
||||||
|
|
@ -121,6 +123,7 @@ const GET_TRANSACTIONS = gql`
|
||||||
rawTickerPrice
|
rawTickerPrice
|
||||||
batchError
|
batchError
|
||||||
walletScore
|
walletScore
|
||||||
|
swept
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -246,7 +249,8 @@ const Transactions = () => {
|
||||||
fiatCode: filtersObject.fiat,
|
fiatCode: filtersObject.fiat,
|
||||||
cryptoCode: filtersObject.crypto,
|
cryptoCode: filtersObject.crypto,
|
||||||
toAddress: filtersObject.address,
|
toAddress: filtersObject.address,
|
||||||
status: filtersObject.status
|
status: filtersObject.status,
|
||||||
|
swept: filtersObject.swept === 'Swept'
|
||||||
})
|
})
|
||||||
|
|
||||||
refetch && refetch()
|
refetch && refetch()
|
||||||
|
|
@ -269,7 +273,8 @@ const Transactions = () => {
|
||||||
fiatCode: filtersObject.fiat,
|
fiatCode: filtersObject.fiat,
|
||||||
cryptoCode: filtersObject.crypto,
|
cryptoCode: filtersObject.crypto,
|
||||||
toAddress: filtersObject.address,
|
toAddress: filtersObject.address,
|
||||||
status: filtersObject.status
|
status: filtersObject.status,
|
||||||
|
swept: filtersObject.swept === 'Swept'
|
||||||
})
|
})
|
||||||
|
|
||||||
refetch && refetch()
|
refetch && refetch()
|
||||||
|
|
@ -287,7 +292,8 @@ const Transactions = () => {
|
||||||
fiatCode: filtersObject.fiat,
|
fiatCode: filtersObject.fiat,
|
||||||
cryptoCode: filtersObject.crypto,
|
cryptoCode: filtersObject.crypto,
|
||||||
toAddress: filtersObject.address,
|
toAddress: filtersObject.address,
|
||||||
status: filtersObject.status
|
status: filtersObject.status,
|
||||||
|
swept: filtersObject.swept === 'Swept'
|
||||||
})
|
})
|
||||||
|
|
||||||
refetch && refetch()
|
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 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 {
|
export {
|
||||||
CURRENCY_MAX,
|
CURRENCY_MAX,
|
||||||
MIN_NUMBER_OF_CASSETTES,
|
MIN_NUMBER_OF_CASSETTES,
|
||||||
|
|
@ -15,5 +17,6 @@ export {
|
||||||
AUTOMATIC,
|
AUTOMATIC,
|
||||||
MANUAL,
|
MANUAL,
|
||||||
WALLET_SCORING_DEFAULT_THRESHOLD,
|
WALLET_SCORING_DEFAULT_THRESHOLD,
|
||||||
IP_CHECK_REGEX
|
IP_CHECK_REGEX,
|
||||||
|
SWEEPABLE_CRYPTOS
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.js": "/static/js/main.be5427dd.chunk.js",
|
"main.js": "/static/js/main.cbb37932.chunk.js",
|
||||||
"main.js.map": "/static/js/main.be5427dd.chunk.js.map",
|
"main.js.map": "/static/js/main.cbb37932.chunk.js.map",
|
||||||
"runtime-main.js": "/static/js/runtime-main.5b925903.js",
|
"runtime-main.js": "/static/js/runtime-main.5b925903.js",
|
||||||
"runtime-main.js.map": "/static/js/runtime-main.5b925903.js.map",
|
"runtime-main.js.map": "/static/js/runtime-main.5b925903.js.map",
|
||||||
"static/js/2.9bb48edb.chunk.js": "/static/js/2.9bb48edb.chunk.js",
|
"static/js/2.9bb48edb.chunk.js": "/static/js/2.9bb48edb.chunk.js",
|
||||||
|
|
@ -154,6 +154,6 @@
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/js/runtime-main.5b925903.js",
|
"static/js/runtime-main.5b925903.js",
|
||||||
"static/js/2.9bb48edb.chunk.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