Merge remote-tracking branch 'upstream/release-8.0' into chore/merge-8.0-into-8.1

This commit is contained in:
Taranto 2022-08-22 16:00:56 +01:00
commit ec32a6f0cd
19 changed files with 102 additions and 31 deletions

View file

@ -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']]
}
}

View file

@ -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))

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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)))

View file

@ -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')

View file

@ -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"

View file

@ -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'
@ -389,6 +390,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}>

View file

@ -131,5 +131,8 @@ export default {
},
error: {
color: tomato
},
swept: {
width: 250
}
}

View file

@ -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
@ -122,6 +124,7 @@ const GET_TRANSACTIONS = gql`
batchError
walletScore
profit
swept
}
}
`
@ -247,7 +250,8 @@ const Transactions = () => {
fiatCode: filtersObject.fiat,
cryptoCode: filtersObject.crypto,
toAddress: filtersObject.address,
status: filtersObject.status
status: filtersObject.status,
swept: filtersObject.swept === 'Swept'
})
refetch && refetch()
@ -270,7 +274,8 @@ const Transactions = () => {
fiatCode: filtersObject.fiat,
cryptoCode: filtersObject.crypto,
toAddress: filtersObject.address,
status: filtersObject.status
status: filtersObject.status,
swept: filtersObject.swept === 'Swept'
})
refetch && refetch()
@ -288,7 +293,8 @@ const Transactions = () => {
fiatCode: filtersObject.fiat,
cryptoCode: filtersObject.crypto,
toAddress: filtersObject.address,
status: filtersObject.status
status: filtersObject.status,
swept: filtersObject.swept === 'Swept'
})
refetch && refetch()

View file

@ -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
}

View file

@ -115,7 +115,8 @@
"lamassu-operator": "./bin/lamassu-operator",
"lamassu-coinatmradar": "./bin/lamassu-coinatmradar",
"lamassu-eth-recovery": "./bin/lamassu-eth-recovery",
"lamassu-update-cassettes": "./bin/lamassu-update-cassettes"
"lamassu-update-cassettes": "./bin/lamassu-update-cassettes",
"lamassu-clean-parsed-id": "./bin/lamassu-clean-parsed-id"
},
"scripts": {
"start": "node bin/lamassu-server",

View file

@ -1,7 +1,7 @@
{
"files": {
"main.js": "/static/js/main.fa6ae8da.chunk.js",
"main.js.map": "/static/js/main.fa6ae8da.chunk.js.map",
"main.js": "/static/js/main.659fb869.chunk.js",
"main.js.map": "/static/js/main.659fb869.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.0102a725.chunk.js": "/static/js/2.0102a725.chunk.js",
@ -154,6 +154,6 @@
"entrypoints": [
"static/js/runtime-main.5b925903.js",
"static/js/2.0102a725.chunk.js",
"static/js/main.fa6ae8da.chunk.js"
"static/js/main.659fb869.chunk.js"
]
}

View file

@ -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.0102a725.chunk.js"></script><script src="/static/js/main.fa6ae8da.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.0102a725.chunk.js"></script><script src="/static/js/main.659fb869.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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long