Merge branch 'release-8.1' into fix/ct-ui-score-error-color
This commit is contained in:
commit
01e0c57655
40 changed files with 332 additions and 160 deletions
|
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const argv = require('minimist')(process.argv.slice(2))
|
||||
const _ = require('lodash')
|
||||
const db = require('../lib/db')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const install = require('../lib/blockchain/install')
|
||||
|
||||
install.run()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
require('../lib/environment-helper')
|
||||
|
||||
const settingsLoader = require('../lib/new-settings-loader')
|
||||
const pp = require('../lib/pp')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const hdkey = require('ethereumjs-wallet/hdkey')
|
||||
const hkdf = require('futoin-hkdf')
|
||||
const crypto = require('crypto')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const migrate = require('../lib/migrate-options')
|
||||
|
||||
migrate.run()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const ofac = require('../lib/ofac/update')
|
||||
|
||||
console.log('Updating OFAC databases.')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const settingsLoader = require('../lib/new-settings-loader')
|
||||
const configManager = require('../lib/new-config-manager')
|
||||
const wallet = require('../lib/wallet')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const _ = require('lodash')
|
||||
const db = require('../lib/db')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
const _ = require('lodash/fp')
|
||||
const common = require('../lib/blockchain/common')
|
||||
const { utils: coinUtils } = require('@lamassu/coins')
|
||||
|
|
|
|||
36
build/Dockerfile
Normal file
36
build/Dockerfile
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
FROM ubuntu:20.04 as base
|
||||
|
||||
ARG VERSION
|
||||
ENV SERVER_VERSION=$VERSION
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Europe/Lisbon
|
||||
|
||||
RUN apt-get update
|
||||
|
||||
RUN apt-get install -y -q curl \
|
||||
sudo \
|
||||
git \
|
||||
python2-minimal \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
net-tools \
|
||||
tar
|
||||
|
||||
RUN curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
|
||||
RUN apt-get install nodejs -y -q
|
||||
|
||||
FROM base as l-s-build
|
||||
|
||||
WORKDIR /lamassu
|
||||
|
||||
RUN git clone https://github.com/lamassu/lamassu-server -b ${SERVER_VERSION}
|
||||
RUN rm -rf /lamassu/lamassu-server/public/*
|
||||
RUN cd lamassu-server && npm install --production
|
||||
|
||||
RUN cd lamassu-server/new-lamassu-admin && npm install && npm run build
|
||||
RUN cp -r /lamassu/lamassu-server/new-lamassu-admin/build/* /lamassu/lamassu-server/public
|
||||
RUN rm -rf /lamassu/lamassu-server/new-lamassu-admin/node_modules
|
||||
|
||||
RUN tar -zcvf lamassu-server-$SERVER_VERSION.tar.gz lamassu-server/
|
||||
|
||||
ENTRYPOINT [ "/bin/bash" ]
|
||||
13
build/build.sh
Executable file
13
build/build.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Error: no arguments specified"
|
||||
echo "Usage: ./build.sh <SERVER_VERSION_TAG>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker build --rm --build-arg VERSION=$1 --tag l-s-prepackage:$1 --file Dockerfile .
|
||||
|
||||
id=$(docker create l-s-prepackage:$1)
|
||||
docker cp $id:/lamassu/lamassu-server-$1.tar.gz ./lamassu-server-$1.tar.gz
|
||||
docker rm -v $id
|
||||
|
|
@ -33,12 +33,12 @@ const BINARIES = {
|
|||
dir: 'geth-linux-amd64-1.10.25-69568c55'
|
||||
},
|
||||
ZEC: {
|
||||
url: 'https://z.cash/downloads/zcash-5.2.0-linux64-debian-bullseye.tar.gz',
|
||||
dir: 'zcash-5.2.0/bin'
|
||||
url: 'https://z.cash/downloads/zcash-5.3.0-linux64-debian-bullseye.tar.gz',
|
||||
dir: 'zcash-5.3.0/bin'
|
||||
},
|
||||
DASH: {
|
||||
url: 'https://github.com/dashpay/dash/releases/download/v18.0.1/dashcore-18.0.1-x86_64-linux-gnu.tar.gz',
|
||||
dir: 'dashcore-18.0.1/bin'
|
||||
url: 'https://github.com/dashpay/dash/releases/download/v18.1.0/dashcore-18.1.0-x86_64-linux-gnu.tar.gz',
|
||||
dir: 'dashcore-18.1.0/bin'
|
||||
},
|
||||
LTC: {
|
||||
defaultUrl: 'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
|
||||
|
|
|
|||
|
|
@ -168,13 +168,21 @@ function postProcess (r, pi, isBlacklisted, addressReuse, walletScore) {
|
|||
|
||||
function doesTxReuseAddress (tx) {
|
||||
if (!tx.fiat || tx.fiat.isZero()) {
|
||||
const sql = `SELECT EXISTS (SELECT DISTINCT to_address FROM cash_in_txs WHERE to_address = $1)`
|
||||
return db.any(sql, [tx.toAddress])
|
||||
const sql = `
|
||||
SELECT EXISTS (
|
||||
SELECT DISTINCT to_address FROM (
|
||||
SELECT to_address FROM cash_in_txs WHERE id != $1
|
||||
) AS x WHERE to_address = $2
|
||||
)`
|
||||
return db.one(sql, [tx.id, tx.toAddress]).then(({ exists }) => exists)
|
||||
}
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
|
||||
function getWalletScore (tx, pi) {
|
||||
pi.isWalletScoringEnabled(tx)
|
||||
.then(isEnabled => {
|
||||
if(!isEnabled) return null
|
||||
if (!tx.fiat || tx.fiat.isZero()) {
|
||||
return pi.rateWallet(tx.cryptoCode, tx.toAddress)
|
||||
}
|
||||
|
|
@ -185,6 +193,7 @@ function getWalletScore (tx, pi) {
|
|||
score: tx.walletScore,
|
||||
isValid
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function monitorPending (settings) {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ function selfPost (tx, pi) {
|
|||
}
|
||||
|
||||
function post (tx, pi, fromClient = true) {
|
||||
logger.silly('Updating cashout tx:', tx)
|
||||
logger.silly('Updating cashout -- tx:', JSON.stringify(tx))
|
||||
logger.silly('Updating cashout -- fromClient:', JSON.stringify(fromClient))
|
||||
return cashOutAtomic.atomic(tx, pi, fromClient)
|
||||
.then(txVector => {
|
||||
const [, newTx, justAuthorized] = txVector
|
||||
|
|
@ -63,7 +64,7 @@ function postProcess (txVector, justAuthorized, pi) {
|
|||
fiat: newTx.fiat
|
||||
})
|
||||
const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat)
|
||||
logger.silly('Bills to dispense:', bills)
|
||||
logger.silly('Bills to dispense:', JSON.stringify(bills))
|
||||
|
||||
if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE)
|
||||
return bills
|
||||
|
|
@ -121,6 +122,9 @@ function getWalletScore (tx, pi) {
|
|||
return tx
|
||||
|
||||
// Transaction shows up on the blockchain, we can request the sender address
|
||||
return pi.isWalletScoringEnabled(tx)
|
||||
.then(isEnabled => {
|
||||
if (!isEnabled) return tx
|
||||
return pi.getTransactionHash(tx)
|
||||
.then(rejectEmpty("No transaction hashes"))
|
||||
.then(txHashes => pi.getInputAddresses(tx, txHashes))
|
||||
|
|
@ -145,6 +149,7 @@ function getWalletScore (tx, pi) {
|
|||
errorCode: 'ciphertraceError',
|
||||
dispense: true
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function monitorLiveIncoming (settings) {
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ function deleteEditedData (id, data) {
|
|||
*/
|
||||
async function updateEditedPhoto (id, photo, photoType) {
|
||||
const newPatch = {}
|
||||
const baseDir = photoType === 'frontCamera' ? frontCameraBaseDir : idPhotoCardBasedir
|
||||
const baseDir = photoType === 'frontCamera' ? FRONT_CAMERA_DIR : ID_PHOTO_CARD_DIR
|
||||
const { createReadStream, filename } = photo
|
||||
const stream = createReadStream()
|
||||
|
||||
|
|
|
|||
|
|
@ -198,7 +198,10 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
|
|||
_.toPairs,
|
||||
|
||||
/* [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] => [{ cryptoCode, balance, ask, bid, cashIn, cashOut }, ...] */
|
||||
_.map(([cryptoCode, obj]) => _.set('cryptoCode', cryptoCode, obj))
|
||||
_.map(([cryptoCode, obj]) => _.set('cryptoCode', cryptoCode, obj)),
|
||||
|
||||
/* Only send coins which have all information needed by the machine. This prevents the machine going down if there's an issue with the coin node */
|
||||
_.filter(coin => ['ask', 'bid', 'balance', 'cashIn', 'cashOut', 'cryptoCode'].every(it => it in coin))
|
||||
)(_.concat(balances, coins))
|
||||
}),
|
||||
|
||||
|
|
@ -232,6 +235,7 @@ const configs = (parent, { currentConfigVersion }, { deviceId, deviceName, opera
|
|||
|
||||
|
||||
const massageTerms = terms => (terms.active && terms.text) ? ({
|
||||
tcPhoto: Boolean(terms.tcPhoto),
|
||||
delay: Boolean(terms.delay),
|
||||
title: terms.title,
|
||||
text: nmd(terms.text),
|
||||
|
|
|
|||
|
|
@ -58,6 +58,32 @@ type TriggersAutomation {
|
|||
usSsn: Boolean!
|
||||
}
|
||||
|
||||
type CustomScreen {
|
||||
text: String!
|
||||
title: String!
|
||||
}
|
||||
|
||||
type CustomInput {
|
||||
type: String!
|
||||
constraintType: String!
|
||||
label1: String
|
||||
label2: String
|
||||
choiceList: [String]
|
||||
}
|
||||
|
||||
type CustomRequest {
|
||||
name: String!
|
||||
input: CustomInput!
|
||||
screen1: CustomScreen!
|
||||
screen2: CustomScreen!
|
||||
}
|
||||
|
||||
type CustomInfoRequest {
|
||||
id: String!
|
||||
enabled: Boolean!
|
||||
customRequest: CustomRequest!
|
||||
}
|
||||
|
||||
type Trigger {
|
||||
id: String!
|
||||
customInfoRequestId: String!
|
||||
|
|
@ -68,9 +94,11 @@ type Trigger {
|
|||
suspensionDays: Float
|
||||
threshold: Int
|
||||
thresholdDays: Int
|
||||
customInfoRequest: CustomInfoRequest
|
||||
}
|
||||
|
||||
type TermsDetails {
|
||||
tcPhoto: Boolean!
|
||||
delay: Boolean!
|
||||
title: String!
|
||||
accept: String!
|
||||
|
|
|
|||
|
|
@ -55,30 +55,54 @@ const populateSettings = function (req, res, next) {
|
|||
}
|
||||
|
||||
try {
|
||||
const operatorSettings = settingsCache.get(operatorId)
|
||||
if (!versionId && (!operatorSettings || !!needsSettingsReload[operatorId])) {
|
||||
return newSettingsLoader.loadLatest()
|
||||
// Priority of configs to retrieve
|
||||
// 1. Machine is in the middle of a transaction and has the config-version header set, fetch that config from cache or database, depending on whether it exists in cache
|
||||
// 2. The operator settings changed, so we must update the cache
|
||||
// 3. There's a cached config, send the cached value
|
||||
// 4. There's no cached config, cache and send the latest config
|
||||
|
||||
if (versionId) {
|
||||
const cachedVersionedSettings = settingsCache.get(`${operatorId}-v${versionId}`)
|
||||
|
||||
if (!cachedVersionedSettings) {
|
||||
logger.debug('Fetching a specific config version cached value')
|
||||
return newSettingsLoader.load(versionId)
|
||||
.then(settings => {
|
||||
settingsCache.set(operatorId, settings)
|
||||
delete needsSettingsReload[operatorId]
|
||||
settingsCache.set(`${operatorId}-v${versionId}`, settings)
|
||||
req.settings = settings
|
||||
})
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
if (!versionId && operatorSettings) {
|
||||
req.settings = operatorSettings
|
||||
logger.debug('Fetching and caching a specific config version')
|
||||
req.settings = cachedVersionedSettings
|
||||
return next()
|
||||
}
|
||||
|
||||
const operatorSettings = settingsCache.get(`${operatorId}-latest`)
|
||||
|
||||
if (!!needsSettingsReload[operatorId] || !operatorSettings) {
|
||||
!!needsSettingsReload[operatorId]
|
||||
? logger.debug('Fetching and caching a new latest config value, as a reload was requested')
|
||||
: logger.debug('Fetching the latest config version because there\'s no cached value')
|
||||
|
||||
return newSettingsLoader.loadLatest()
|
||||
.then(settings => {
|
||||
settingsCache.set(`${operatorId}-latest`, settings)
|
||||
if (!!needsSettingsReload[operatorId]) delete needsSettingsReload[operatorId]
|
||||
req.settings = settings
|
||||
})
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
logger.debug('Fetching the latest config value from cache')
|
||||
req.settings = operatorSettings
|
||||
return next()
|
||||
} catch (e) {
|
||||
logger.error(e)
|
||||
}
|
||||
|
||||
newSettingsLoader.load(versionId)
|
||||
.then(settings => { req.settings = settings })
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
module.exports = populateSettings
|
||||
|
|
|
|||
|
|
@ -145,13 +145,13 @@ const getTriggersAutomation = (customInfoRequests, config) => {
|
|||
|
||||
const splitGetFirst = _.compose(_.head, _.split('_'))
|
||||
|
||||
const getCryptosFromWalletNamespace = config => {
|
||||
return _.uniq(_.map(splitGetFirst, _.keys(fromNamespace('wallets', config))))
|
||||
}
|
||||
const getCryptosFromWalletNamespace =
|
||||
_.compose(_.without(['advanced']), _.uniq, _.map(splitGetFirst), _.keys, fromNamespace('wallets'))
|
||||
|
||||
const getCashInSettings = config => fromNamespace(namespaces.CASH_IN)(config)
|
||||
|
||||
const getCryptoUnits = (crypto, config) => getWalletSettings(crypto, config).cryptoUnits
|
||||
const getCryptoUnits = (crypto, config) =>
|
||||
getWalletSettings(crypto, config).cryptoUnits ?? 'full'
|
||||
|
||||
const setTermsConditions = toNamespace(namespaces.TERMS_CONDITIONS)
|
||||
|
||||
|
|
|
|||
|
|
@ -850,6 +850,10 @@ function plugins (settings, deviceId) {
|
|||
return walletScoring.getInputAddresses(settings, tx.cryptoCode, txHashes)
|
||||
}
|
||||
|
||||
function isWalletScoringEnabled (tx) {
|
||||
return walletScoring.isWalletScoringEnabled(settings, tx.cryptoCode)
|
||||
}
|
||||
|
||||
return {
|
||||
getRates,
|
||||
recordPing,
|
||||
|
|
@ -882,7 +886,8 @@ function plugins (settings, deviceId) {
|
|||
rateWallet,
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
getInputAddresses,
|
||||
isWalletScoringEnabled
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,18 +28,18 @@ function rateWallet (account, cryptoCode, address) {
|
|||
const { apiVersion, authHeader } = client
|
||||
const threshold = account.scoreThreshold
|
||||
|
||||
console.log(`** DEBUG ** rateWallet ENDPOINT: https://rest.ciphertrace.com/aml/${apiVersion}/${_.toLower(cryptoCode)}/risk?address=${address}`)
|
||||
logger.info(`** DEBUG ** rateWallet ENDPOINT: https://rest.ciphertrace.com/aml/${apiVersion}/${_.toLower(cryptoCode)}/risk?address=${address}`)
|
||||
|
||||
return axios.get(`https://rest.ciphertrace.com/aml/${apiVersion}/${_.toLower(cryptoCode)}/risk?address=${address}`, {
|
||||
headers: authHeader
|
||||
})
|
||||
.then(res => ({ address, score: res.data.risk, isValid: res.data.risk < threshold }))
|
||||
.then(result => {
|
||||
console.log('** DEBUG ** rateWallet RETURN:', result)
|
||||
logger.info(`** DEBUG ** rateWallet RETURN: ${result}`)
|
||||
return result
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`** DEBUG ** rateWallet ERROR: ${err.message}`)
|
||||
logger.error(`** DEBUG ** rateWallet ERROR: ${err.message}`)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
|
@ -54,7 +54,8 @@ function isValidWalletScore (account, score) {
|
|||
|
||||
function getAddressTransactionsHashes (receivingAddress, cryptoCode, client, wallet) {
|
||||
const { apiVersion, authHeader } = client
|
||||
console.log(`** DEBUG ** getTransactionHash ENDPOINT: https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}address/search?features=tx&address=${receivingAddress}&mempool=true`)
|
||||
|
||||
logger.info(`** DEBUG ** getTransactionHash ENDPOINT: https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}address/search?features=tx&address=${receivingAddress}&mempool=true`)
|
||||
|
||||
return axios.get(`https://rest.ciphertrace.com/api/${apiVersion}/${_.toLower(cryptoCode) !== 'btc' ? `${_.toLower(cryptoCode)}_` : ``}address/search?features=tx&address=${receivingAddress}&mempool=true`, {
|
||||
headers: authHeader
|
||||
|
|
@ -64,8 +65,8 @@ function getAddressTransactionsHashes (receivingAddress, cryptoCode, client, wal
|
|||
_.map(_.get(['txHash']))
|
||||
))
|
||||
.catch(err => {
|
||||
console.log(`** DEBUG ** getTransactionHash ERROR: ${err}`)
|
||||
console.log(`** DEBUG ** Fetching transactions hashes via wallet node...`)
|
||||
logger.error(`** DEBUG ** getTransactionHash ERROR: ${err}`)
|
||||
logger.error(`** DEBUG ** Fetching transactions hashes via wallet node...`)
|
||||
return wallet.getTxHashesByAddress(cryptoCode, receivingAddress)
|
||||
})
|
||||
}
|
||||
|
|
@ -78,11 +79,11 @@ function getTransactionHash (account, cryptoCode, receivingAddress, wallet) {
|
|||
if (_.size(txHashes) > 1) {
|
||||
logger.warn('An address generated by this wallet was used in more than one transaction')
|
||||
}
|
||||
console.log('** DEBUG ** getTransactionHash RETURN: ', _.join(', ', txHashes))
|
||||
logger.info('** DEBUG ** getTransactionHash RETURN: ', _.join(', ', txHashes))
|
||||
return txHashes
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('** DEBUG ** getTransactionHash from wallet node ERROR: ', err)
|
||||
logger.error('** DEBUG ** getTransactionHash from wallet node ERROR: ', err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
|
@ -116,20 +117,29 @@ function getInputAddresses (account, cryptoCode, txHashes) {
|
|||
const transactionInputs = _.flatMap(it => it.inputs, data.transactions)
|
||||
const inputAddresses = _.map(it => it.address, transactionInputs)
|
||||
|
||||
console.log(`** DEBUG ** getInputAddresses RETURN: ${inputAddresses}`)
|
||||
logger.info(`** DEBUG ** getInputAddresses RETURN: ${inputAddresses}`)
|
||||
|
||||
return inputAddresses
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`** DEBUG ** getInputAddresses ERROR: ${err.message}`)
|
||||
logger.error(`** DEBUG ** getInputAddresses ERROR: ${err.message}`)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function isWalletScoringEnabled (account, cryptoCode) {
|
||||
if (!SUPPORTED_COINS.includes(cryptoCode)) {
|
||||
return Promise.reject(new Error('Unsupported crypto: ' + cryptoCode))
|
||||
}
|
||||
|
||||
return Promise.resolve(!_.isNil(account) && account.enabled)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
NAME,
|
||||
rateWallet,
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
getInputAddresses,
|
||||
isWalletScoringEnabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,10 +36,20 @@ function getInputAddresses (account, cryptoCode, txHashes) {
|
|||
})
|
||||
}
|
||||
|
||||
function isWalletScoringEnabled (account, cryptoCode) {
|
||||
return new Promise((resolve, _) => {
|
||||
setTimeout(() => {
|
||||
return resolve(true)
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
NAME,
|
||||
rateWallet,
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
getInputAddresses,
|
||||
isWalletScoringEnabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,9 +47,19 @@ function getInputAddresses (settings, cryptoCode, txHashes) {
|
|||
})
|
||||
}
|
||||
|
||||
function isWalletScoringEnabled (settings, cryptoCode) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const { plugin, account } = loadWalletScoring(settings)
|
||||
|
||||
return plugin.isWalletScoringEnabled(account, cryptoCode)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
rateWallet,
|
||||
isValidWalletScore,
|
||||
getTransactionHash,
|
||||
getInputAddresses
|
||||
getInputAddresses,
|
||||
isWalletScoringEnabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import classnames from 'classnames'
|
||||
import { compareAsc, differenceInDays, set } from 'date-fns/fp'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
import Calendar from './Calendar'
|
||||
|
|
@ -37,7 +38,12 @@ const DateRangePicker = ({ minDate, maxDate, className, onRangeChange }) => {
|
|||
set({ hours: 23, minutes: 59, seconds: 59, milliseconds: 999 }, day)
|
||||
)
|
||||
} else {
|
||||
setTo(from)
|
||||
setTo(
|
||||
set(
|
||||
{ hours: 23, minutes: 59, seconds: 59, milliseconds: 999 },
|
||||
R.clone(from)
|
||||
)
|
||||
)
|
||||
setFrom(day)
|
||||
}
|
||||
return
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ const Header = () => {
|
|||
}
|
||||
|
||||
const mapElement = (
|
||||
{ name, width = DEFAULT_COL_SIZE, header, textAlign },
|
||||
{ name, display, width = DEFAULT_COL_SIZE, header, textAlign },
|
||||
idx
|
||||
) => {
|
||||
const orderClasses = classnames({
|
||||
|
|
@ -99,7 +99,7 @@ const Header = () => {
|
|||
<>{attachOrderedByToComplexHeader(header) ?? header}</>
|
||||
) : (
|
||||
<span className={orderClasses}>
|
||||
{startCase(name)}{' '}
|
||||
{!R.isNil(display) ? display : startCase(name)}{' '}
|
||||
{!R.isNil(orderedBy) && R.equals(name, orderedBy.code) && '-'}
|
||||
</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ const Analytics = () => {
|
|||
case 'topMachines':
|
||||
return (
|
||||
<TopMachinesWrapper
|
||||
title="Transactions over time"
|
||||
title="Top 5 Machines"
|
||||
representing={representing}
|
||||
period={period}
|
||||
data={R.map(convertFiatToLocale)(filteredData(period.code).current)}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ const GET_TRANSACTIONS = gql`
|
|||
customerId
|
||||
isAnonymous
|
||||
rawTickerPrice
|
||||
profit
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ const CASSETTE_LIST = [
|
|||
]
|
||||
|
||||
const widthsByNumberOfCassettes = {
|
||||
2: { machine: 230, cassette: 250 },
|
||||
3: { machine: 216, cassette: 270 },
|
||||
4: { machine: 210, cassette: 204 }
|
||||
2: { machine: 230, cashbox: 150, cassette: 250 },
|
||||
3: { machine: 216, cashbox: 150, cassette: 270 },
|
||||
4: { machine: 210, cashbox: 150, cassette: 204 }
|
||||
}
|
||||
|
||||
const FiatBalanceOverrides = ({ config, section }) => {
|
||||
|
|
@ -44,19 +44,17 @@ const FiatBalanceOverrides = ({ config, section }) => {
|
|||
|
||||
const setupValues = data?.fiatBalanceOverrides ?? []
|
||||
const innerSetEditing = it => setEditing(NAME, it)
|
||||
|
||||
const cashoutConfig = it => fromNamespace(it)(config)
|
||||
|
||||
const overriddenMachines = R.map(override => override.machine, setupValues)
|
||||
const suggestionFilter = R.filter(
|
||||
it =>
|
||||
!R.includes(it.deviceId, overriddenMachines) &&
|
||||
cashoutConfig(it.deviceId).active
|
||||
const suggestions = R.differenceWith(
|
||||
(it, m) => it.deviceId === m,
|
||||
machines,
|
||||
overriddenMachines
|
||||
)
|
||||
const suggestions = suggestionFilter(machines)
|
||||
|
||||
const findSuggestion = it => {
|
||||
const coin = R.compose(R.find(R.propEq('deviceId', it?.machine)))(machines)
|
||||
const coin = R.find(R.propEq('deviceId', it?.machine), machines)
|
||||
return coin ? [coin] : []
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +81,6 @@ const FiatBalanceOverrides = ({ config, section }) => {
|
|||
.shape({
|
||||
[MACHINE_KEY]: Yup.string()
|
||||
.label('Machine')
|
||||
.nullable()
|
||||
.required(),
|
||||
[CASHBOX_KEY]: Yup.number()
|
||||
.label('Cash box')
|
||||
|
|
@ -121,23 +118,24 @@ const FiatBalanceOverrides = ({ config, section }) => {
|
|||
.max(percentMax)
|
||||
.nullable()
|
||||
})
|
||||
.test((values, context) => {
|
||||
const picked = R.pick(CASSETTE_LIST, values)
|
||||
|
||||
if (CASSETTE_LIST.some(it => !R.isNil(picked[it]))) return
|
||||
|
||||
return context.createError({
|
||||
path: CASSETTE_1_KEY,
|
||||
message: 'At least one of the cassettes must have a value'
|
||||
})
|
||||
.test((values, context) =>
|
||||
R.any(key => !R.isNil(values[key]), R.prepend(CASHBOX_KEY, CASSETTE_LIST))
|
||||
? undefined
|
||||
: context.createError({
|
||||
path: CASHBOX_KEY,
|
||||
message:
|
||||
'The cash box or at least one of the cassettes must have a value'
|
||||
})
|
||||
)
|
||||
|
||||
const viewMachine = it =>
|
||||
R.compose(R.path(['name']), R.find(R.propEq('deviceId', it)))(machines)
|
||||
|
||||
const elements = [
|
||||
const elements = R.concat(
|
||||
[
|
||||
{
|
||||
name: MACHINE_KEY,
|
||||
display: 'Machine',
|
||||
width: widthsByNumberOfCassettes[maxNumberOfCassettes].machine,
|
||||
size: 'sm',
|
||||
view: viewMachine,
|
||||
|
|
@ -150,8 +148,8 @@ const FiatBalanceOverrides = ({ config, section }) => {
|
|||
},
|
||||
{
|
||||
name: CASHBOX_KEY,
|
||||
display: 'Cashbox',
|
||||
width: 155,
|
||||
display: 'Cash box',
|
||||
width: widthsByNumberOfCassettes[maxNumberOfCassettes].cashbox,
|
||||
textAlign: 'right',
|
||||
bold: true,
|
||||
input: NumberInput,
|
||||
|
|
@ -160,12 +158,9 @@ const FiatBalanceOverrides = ({ config, section }) => {
|
|||
decimalPlaces: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
R.until(
|
||||
R.gt(R.__, maxNumberOfCassettes),
|
||||
it => {
|
||||
elements.push({
|
||||
],
|
||||
R.map(
|
||||
it => ({
|
||||
name: `fillingPercentageCassette${it}`,
|
||||
display: `Cash cassette ${it}`,
|
||||
width: widthsByNumberOfCassettes[maxNumberOfCassettes].cassette,
|
||||
|
|
@ -177,15 +172,18 @@ const FiatBalanceOverrides = ({ config, section }) => {
|
|||
inputProps: {
|
||||
decimalPlaces: 0
|
||||
},
|
||||
view: it => it?.toString() ?? '—',
|
||||
view: el => el?.toString() ?? '—',
|
||||
isHidden: value =>
|
||||
!cashoutConfig(value.machine).active ||
|
||||
it >
|
||||
R.defaultTo(
|
||||
0,
|
||||
machines.find(({ deviceId }) => deviceId === value.machine)
|
||||
?.numberOfCassettes
|
||||
})
|
||||
return R.add(1, it)
|
||||
},
|
||||
1
|
||||
)
|
||||
}),
|
||||
R.range(1, maxNumberOfCassettes + 1)
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ const DetailsRow = ({ it: tx, timezone }) => {
|
|||
const zip = new JSZip()
|
||||
|
||||
const [fetchSummary] = useLazyQuery(TX_SUMMARY, {
|
||||
onCompleted: data => createCsv(data)
|
||||
onCompleted: data => createCsv(R.filter(it => !R.isEmpty(it), data))
|
||||
})
|
||||
|
||||
const [cancelTransaction] = useMutation(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useQuery, useMutation } from '@apollo/react-hooks'
|
||||
import { utils as coinUtils } from '@lamassu/coins'
|
||||
import { makeStyles } from '@material-ui/core'
|
||||
import gql from 'graphql-tag'
|
||||
import * as R from 'ramda'
|
||||
|
|
@ -54,13 +53,9 @@ const AllSet = ({ data: currentData, doContinue }) => {
|
|||
const cryptoCurrencies = data?.cryptoCurrencies ?? []
|
||||
|
||||
const save = () => {
|
||||
const defaultCryptoUnit = R.head(
|
||||
R.keys(coinUtils.getCryptoCurrency(coin).units)
|
||||
)
|
||||
const adjustedData = {
|
||||
zeroConfLimit: 0,
|
||||
...currentData,
|
||||
cryptoUnits: defaultCryptoUnit
|
||||
...currentData
|
||||
}
|
||||
if (!WalletSchema.isValidSync(adjustedData)) return setError(true)
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@
|
|||
"test": "mocha --recursive tests",
|
||||
"jtest": "jest --detectOpenHandles",
|
||||
"build-admin": "npm run build-admin:css && npm run build-admin:main && npm run build-admin:lamassu",
|
||||
"server": "nodemon bin/lamassu-server --mockSms --logLevel silly",
|
||||
"server": "nodemon bin/lamassu-server --mockSms --mockScoring --logLevel silly",
|
||||
"admin-server": "nodemon bin/lamassu-admin-server --dev --logLevel silly",
|
||||
"graphql-server": "nodemon bin/new-graphql-dev-insecure",
|
||||
"watch": "concurrently \"npm:server\" \"npm:admin-server\" \"npm:graphql-server\"",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"files": {
|
||||
"main.js": "/static/js/main.589d2bd0.chunk.js",
|
||||
"main.js.map": "/static/js/main.589d2bd0.chunk.js.map",
|
||||
"main.js": "/static/js/main.aa68bc4d.chunk.js",
|
||||
"main.js.map": "/static/js/main.aa68bc4d.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.56a90c80.chunk.js": "/static/js/2.56a90c80.chunk.js",
|
||||
"static/js/2.56a90c80.chunk.js.map": "/static/js/2.56a90c80.chunk.js.map",
|
||||
"static/js/2.4b3df17b.chunk.js": "/static/js/2.4b3df17b.chunk.js",
|
||||
"static/js/2.4b3df17b.chunk.js.map": "/static/js/2.4b3df17b.chunk.js.map",
|
||||
"index.html": "/index.html",
|
||||
"static/js/2.56a90c80.chunk.js.LICENSE.txt": "/static/js/2.56a90c80.chunk.js.LICENSE.txt",
|
||||
"static/js/2.4b3df17b.chunk.js.LICENSE.txt": "/static/js/2.4b3df17b.chunk.js.LICENSE.txt",
|
||||
"static/media/3-cassettes-open-1-left.d6d9aa73.svg": "/static/media/3-cassettes-open-1-left.d6d9aa73.svg",
|
||||
"static/media/3-cassettes-open-2-left.a9ee8d4c.svg": "/static/media/3-cassettes-open-2-left.a9ee8d4c.svg",
|
||||
"static/media/3-cassettes-open-3-left.08fed660.svg": "/static/media/3-cassettes-open-3-left.08fed660.svg",
|
||||
|
|
@ -153,7 +153,7 @@
|
|||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.5b925903.js",
|
||||
"static/js/2.56a90c80.chunk.js",
|
||||
"static/js/main.589d2bd0.chunk.js"
|
||||
"static/js/2.4b3df17b.chunk.js",
|
||||
"static/js/main.aa68bc4d.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.56a90c80.chunk.js"></script><script src="/static/js/main.589d2bd0.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.4b3df17b.chunk.js"></script><script src="/static/js/main.aa68bc4d.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
2
public/static/js/main.aa68bc4d.chunk.js
Normal file
2
public/static/js/main.aa68bc4d.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
public/static/js/main.aa68bc4d.chunk.js.map
Normal file
1
public/static/js/main.aa68bc4d.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