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']] files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
}, },
XMR: { XMR: {
url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.0.0.tar.bz2', url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.1.0.tar.bz2',
dir: 'monero-x86_64-linux-gnu-v0.18.0.0', dir: 'monero-x86_64-linux-gnu-v0.18.1.0',
files: [['monerod', 'monerod'], ['monero-wallet-rpc', 'monero-wallet-rpc']] 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 () { function run () {
const choices = _.flow([ const choices = _.flow([
_.filter(c => c.type !== 'erc-20'), _.filter(c => c.type !== 'erc-20'),
_.map(c => { _.map(c => {
const checked = isInstalledSoftware(c) && isInstalledVolume(c)
const name = c.code === 'ethereum' ? 'Ethereum and/or USDT' : c.display const name = c.code === 'ethereum' ? 'Ethereum and/or USDT' : c.display
return { return {
name, name,
value: c.code, value: c.code,
checked, checked: isInstalled(c),
disabled: c.cryptoCode === 'ETH' disabled: isDisabled(c)
? 'Use admin\'s Infura plugin'
: checked && 'Installed'
} }
}), }),
])(cryptos) ])(cryptos)
@ -160,6 +174,15 @@ function run () {
const validateAnswers = async (answers) => { const validateAnswers = async (answers) => {
if (_.size(answers) > 2) return { message: `Please insert a maximum of two coins to install.`, isValid: false } 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) return getBlockchainSyncStatus(cryptos)
.then(blockchainStatuses => { .then(blockchainStatuses => {
const result = _.reduce((acc, value) => ({ ...acc, [value]: _.isNil(acc[value]) ? 1 : acc[value] + 1 }), {}, _.values(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_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)

View file

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

View file

@ -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
db.any(cashInSql, [cashInTx.PENDING_INTERVAL, from, until, limit, offset, id, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status]), const hasCashInOnlyFilters = false
db.any(cashOutSql, [REDEEMABLE_AGE, from, until, limit, offset, id, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status]) 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(packager)
.then(res => { .then(res => {
if (simplified) return simplifiedBatch(res) if (simplified) return simplifiedBatch(res)

View file

@ -808,7 +808,7 @@ 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') 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) return db.any(sql)
.then(rows => Promise.all(rows.map(sweepHdRow))) .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 { default: PQueue } = require('p-queue')
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')

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

View file

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

View file

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

View file

@ -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
@ -122,6 +124,7 @@ const GET_TRANSACTIONS = gql`
batchError batchError
walletScore walletScore
profit profit
swept
} }
} }
` `
@ -247,7 +250,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()
@ -270,7 +274,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()
@ -288,7 +293,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()

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

View file

@ -115,7 +115,8 @@
"lamassu-operator": "./bin/lamassu-operator", "lamassu-operator": "./bin/lamassu-operator",
"lamassu-coinatmradar": "./bin/lamassu-coinatmradar", "lamassu-coinatmradar": "./bin/lamassu-coinatmradar",
"lamassu-eth-recovery": "./bin/lamassu-eth-recovery", "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": { "scripts": {
"start": "node bin/lamassu-server", "start": "node bin/lamassu-server",

View file

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