Merge branch 'releases/v10.1' into releases/v10.0.7

This commit is contained in:
Rafael Taranto 2024-12-24 10:11:57 +00:00 committed by GitHub
commit 5075e90c87
62 changed files with 742 additions and 603 deletions

View file

@ -3,7 +3,7 @@ name: Docker Build and Publish
on:
push:
branches:
- release-10.0
- dev
env:
DOCKERHUB_SERVER_REPO: lamassu/lamassu-server

View file

@ -104,7 +104,7 @@ NOW, THEREFORE, in consideration of the mutual promises set forth herein, Lamass
13. Non-assignment
13.1. This Agreement, any claims and the licenses granted by it may not be assigned, sublicensed, or otherwise transferred by Licensee without the prior written consent of Lamassu. This Section 16.1 does not apply to Licensee with regard to current and future entities of Lamassu.
13.1. This Agreement, any claims and the licenses granted by it may not be assigned, sublicensed, or otherwise transferred by Licensee without the prior written consent of Lamassu.
13.2. This Agreement and any claims hereunder may not be assigned by Lamassu to any third party without the prior written consent of Licensee.

47
bin/lamassu-btc-bumpfee Normal file
View file

@ -0,0 +1,47 @@
#!/usr/bin/env node
const inquirer = require('inquirer')
const bitcoind = require('../lib/plugins/wallet/bitcoind/bitcoind')
const BN = require('../lib/bn')
const mempool = require('../lib/blockexplorers/mempool.space')
const txId = process.argv[2]
if (!txId) {
console.error('Please provide a BTC transaction hash as input.')
process.exit(1)
}
const bumpTransactionFee = async (txId) => {
const txData = await bitcoind.fetch('gettransaction', [txId, true, true])
const fee = new BN(txData.fee).abs().shiftedBy(8).decimalPlaces(0)
const size = txData.decoded.vsize
const satPerVb = fee.div(size)
console.log(`Current fee: ${satPerVb.toFixed(2).toString()} sat/vB`)
const recommendedFees = await mempool.getSatBEstimateFees()
console.log('Recommended fees (sat/vB):', recommendedFees)
const { selectedFee } = await inquirer.prompt([
{
type: 'list',
name: 'selectedFee',
message: 'Select a fee higher than the current one:',
choices: Object.entries(recommendedFees)
.filter(([_, value]) => satPerVb.lt(value))
.map(([key, value]) => ({name: `${key}: ${value} sat/vB`, value})),
},
])
const { txid } = await bitcoind.fetch('bumpfee', [txId, {fee_rate: selectedFee}])
console.log(`
Fee bumped to ${selectedFee.toFixed(2)} sat/vB
Transaction ID: ${txid}
`)
}
bumpTransactionFee(txId)

53
bin/lamassu-trx-recovery Normal file
View file

@ -0,0 +1,53 @@
#!/usr/bin/env node
require('../lib/environment-helper')
const TronWeb = require('tronweb')
const db = require('../lib/db')
const _ = require('lodash/fp')
const pify = require('pify')
const fs = pify(require('fs'))
const MNEMONIC_PATH = process.env.MNEMONIC_PATH
const defaultPrefixPath = "m/44'/195'/0'/0"
const paymentPrefixPath = "m/44'/195'/1'/0"
const address = process.argv[2]
if (!MNEMONIC_PATH) {
console.error(`Unable to fetch mnemonic from your account!`)
process.exit(1)
}
if (!address) {
console.log('Usage: lamassu-trx-recovery <cash-out address>')
process.exit(2)
}
function run (address) {
Promise.all([fetchMnemonic(), searchForHdIndex(address)])
.then(([mnemonic, hdIndex]) => {
try {
const prefix = !_.isNil(hdIndex) ? `${paymentPrefixPath}/${hdIndex}` : `${defaultPrefixPath}/0`
const privKey = TronWeb.fromMnemonic(mnemonic.replace(/[\r\n]/gm, ' ').trim(), prefix).privateKey
console.log(`Private key: `, privKey.slice(2))
process.exit(0)
} catch (err) {
console.error(`Error while retrieving private key!`)
process.exit(3)
}
})
}
function searchForHdIndex (address) {
const sql = `SELECT hd_index FROM cash_out_txs WHERE to_address = $1`
return db.oneOrNone(sql, [address])
.then(result => _.get('hd_index', result))
}
function fetchMnemonic () {
return fs.readFile(MNEMONIC_PATH, 'utf8')
}
run(address)

View file

@ -92,7 +92,7 @@ function loadLatestConfig (filterSchemaVersion = true) {
order by id desc
limit 1`
return db.one(sql, ['config', configValidate.SETTINGS_LOADER_SCHEMA_VERSION])
return db.oneOrNone(sql, ['config', configValidate.SETTINGS_LOADER_SCHEMA_VERSION])
.then(row => row.data.config)
.then(configValidate.validate)
.catch(err => {
@ -240,6 +240,7 @@ module.exports = {
loadRecentConfig,
load,
loadLatest,
loadLatestConfig,
save,
loadFixture,
mergeValues,

View file

@ -30,37 +30,37 @@ const BINARIES = {
BTC: {
defaultUrl: 'https://bitcoincore.org/bin/bitcoin-core-0.20.1/bitcoin-0.20.1-x86_64-linux-gnu.tar.gz',
defaultDir: 'bitcoin-0.20.1/bin',
url: 'https://bitcoincore.org/bin/bitcoin-core-27.1/bitcoin-27.1-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-27.1/bin'
url: 'https://bitcoincore.org/bin/bitcoin-core-28.0/bitcoin-28.0-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-28.0/bin'
},
ETH: {
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.14.8-a9523b64.tar.gz',
dir: 'geth-linux-amd64-1.14.8-a9523b64'
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.14.12-293a300d.tar.gz',
dir: 'geth-linux-amd64-1.14.12-293a300d'
},
ZEC: {
url: 'https://github.com/zcash/artifacts/raw/master/v5.9.0/bullseye/zcash-5.9.0-linux64-debian-bullseye.tar.gz',
dir: 'zcash-5.9.0/bin'
url: 'https://download.z.cash/downloads/zcash-6.0.0-linux64-debian-bullseye.tar.gz',
dir: 'zcash-6.0.0/bin'
},
DASH: {
defaultUrl: 'https://github.com/dashpay/dash/releases/download/v18.1.0/dashcore-18.1.0-x86_64-linux-gnu.tar.gz',
defaultDir: 'dashcore-18.1.0/bin',
url: 'https://github.com/dashpay/dash/releases/download/v21.1.0/dashcore-21.1.0-x86_64-linux-gnu.tar.gz',
dir: 'dashcore-21.1.0/bin'
url: 'https://github.com/dashpay/dash/releases/download/v21.1.1/dashcore-21.1.1-x86_64-linux-gnu.tar.gz',
dir: 'dashcore-21.1.1/bin'
},
LTC: {
defaultUrl: 'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
defaultDir: 'litecoin-0.18.1/bin',
url: 'https://download.litecoin.org/litecoin-0.21.3/linux/litecoin-0.21.3-x86_64-linux-gnu.tar.gz',
dir: 'litecoin-0.21.3/bin'
url: 'https://download.litecoin.org/litecoin-0.21.4/linux/litecoin-0.21.4-x86_64-linux-gnu.tar.gz',
dir: 'litecoin-0.21.4/bin'
},
BCH: {
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v27.1.0/bitcoin-cash-node-27.1.0-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-cash-node-27.1.0/bin',
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v28.0.0/bitcoin-cash-node-28.0.0-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-cash-node-28.0.0/bin',
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
},
XMR: {
url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.3.3.tar.bz2',
dir: 'monero-x86_64-linux-gnu-v0.18.3.3',
url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.3.4.tar.bz2',
dir: 'monero-x86_64-linux-gnu-v0.18.3.4',
files: [['monerod', 'monerod'], ['monero-wallet-rpc', 'monero-wallet-rpc']]
}
}

View file

@ -5,4 +5,12 @@ const getSatBEstimateFee = () => {
.then(r => r.data.hourFee)
}
module.exports = { getSatBEstimateFee }
const getSatBEstimateFees = () => {
return axios.get('https://mempool.space/api/v1/fees/recommended')
.then(r => r.data)
}
module.exports = {
getSatBEstimateFees,
getSatBEstimateFee
}

View file

@ -36,7 +36,7 @@ function post (machineTx, pi) {
let addressReuse = false
let walletScore = {}
const promises = [settingsLoader.loadLatest()]
const promises = [settingsLoader.loadLatestConfig()]
const isFirstPost = !r.tx.fiat || r.tx.fiat.isZero()
if (isFirstPost) {
@ -44,7 +44,7 @@ function post (machineTx, pi) {
}
return Promise.all(promises)
.then(([{ config }, blacklistItems = false, isReusedAddress = false, fetchedWalletScore = null]) => {
.then(([config, blacklistItems = false, isReusedAddress = false, fetchedWalletScore = null]) => {
const rejectAddressReuse = configManager.getCompliance(config).rejectAddressReuse
walletScore = fetchedWalletScore

View file

@ -474,8 +474,4 @@ function migrate (config, accounts) {
}
}
module.exports = {
migrateConfig,
migrateAccounts,
migrate
}
module.exports = { migrate }

View file

@ -51,6 +51,7 @@ const CASH_UNIT_CAPACITY = {
const CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES = 2
const CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES = 4
const CASH_OUT_MAXIMUM_AMOUNT_OF_RECYCLERS = 6
const AUTHENTICATOR_ISSUER_ENTITY = 'Lamassu'
const AUTH_TOKEN_EXPIRATION_TIME = '30 minutes'
const REGISTRATION_TOKEN_EXPIRATION_TIME = '30 minutes'
@ -85,6 +86,7 @@ module.exports = {
CONFIRMATION_CODE,
CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES,
CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES,
CASH_OUT_MAXIMUM_AMOUNT_OF_RECYCLERS,
WALLET_SCORE_THRESHOLD,
RECEIPT,
PSQL_URL,

View file

@ -3,6 +3,7 @@ const nmd = require('nano-markdown')
const plugins = require('../plugins')
const configManager = require('../new-config-manager')
const settingsLoader = require('../new-settings-loader')
const { batchGetCustomInfoRequest, getCustomInfoRequests } = require('../new-admin/services/customInfoRequests')
const state = require('../middlewares/state')
const { getMachine } = require('../machine-loader')
@ -323,8 +324,7 @@ const terms = (parent, { currentConfigVersion, currentHash }, { deviceId, settin
const isHashNew = hash !== currentHash
const text = isHashNew ? latestTerms.text : null
return plugins(settings, deviceId)
.fetchCurrentConfigVersion()
return settingsLoader.fetchCurrentConfigVersion()
.catch(() => null)
.then(configVersion => isHashNew || _.isNil(currentConfigVersion) || currentConfigVersion < configVersion)
.then(isVersionNew => isVersionNew ? _.omit(['text'], latestTerms) : null)

View file

@ -29,7 +29,7 @@ type OperatorInfo {
}
type MachineInfo {
deviceId: String!
deviceId: String! @deprecated(reason: "unused by the machine")
deviceName: String
numberOfCassettes: Int
numberOfRecyclers: Int
@ -107,7 +107,7 @@ type Trigger {
suspensionDays: Float
threshold: Int
thresholdDays: Int
customInfoRequestId: String
customInfoRequestId: String @deprecated(reason: "use customInfoRequest.id")
customInfoRequest: CustomInfoRequest
externalService: String
}

View file

@ -11,7 +11,6 @@ const pairing = require('./pairing')
const { checkPings, checkStuckScreen } = require('./notifier')
const dbm = require('./postgresql_interface')
const configManager = require('./new-config-manager')
const settingsLoader = require('./new-settings-loader')
const notifierUtils = require('./notifier/utils')
const notifierQueries = require('./notifier/queries')
const { ApolloError } = require('apollo-server-errors');
@ -94,9 +93,7 @@ function getUnpairedMachines () {
}
function getConfig (defaultConfig) {
if (defaultConfig) return Promise.resolve(defaultConfig)
return settingsLoader.loadLatest().config
return defaultConfig ? Promise.resolve(defaultConfig) : loadLatestConfig()
}
const getStatus = (ping, stuck) => {
@ -529,7 +526,6 @@ module.exports = {
updateNetworkHeartbeat,
getNetworkPerformance,
getNetworkHeartbeat,
getConfig,
getMachineIds,
emptyMachineUnits,
refillMachineUnits,

View file

@ -62,6 +62,7 @@ const ALL_ACCOUNTS = [
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
{ code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true },
{ code: 'scorechain', display: 'Scorechain', class: WALLET_SCORING, cryptos: [BTC, ETH, LTC, BCH, DASH, USDT, USDT_TRON, TRX] },
{ code: 'elliptic', display: 'Elliptic', class: WALLET_SCORING, cryptos: [BTC, ETH, LTC, BCH, USDT, USDT_TRON, TRX, ZEC] },
{ code: 'mock-scoring', display: 'Mock scoring', class: WALLET_SCORING, cryptos: ALL_CRYPTOS, dev: true },
{ code: 'sumsub', display: 'Sumsub', class: COMPLIANCE },
{ code: 'mock-compliance', display: 'Mock Compliance', class: COMPLIANCE, dev: true },

View file

@ -7,10 +7,7 @@ const resolvers = {
},
Mutation: {
saveAccounts: (...[, { accounts }]) => settingsLoader.saveAccounts(accounts),
// resetAccounts: (...[, { schemaVersion }]) => settingsLoader.resetAccounts(schemaVersion),
saveConfig: (...[, { config }]) => settingsLoader.saveConfig(config),
// resetConfig: (...[, { schemaVersion }]) => settingsLoader.resetConfig(schemaVersion),
// migrateConfigAndAccounts: () => settingsLoader.migrate()
}
}

View file

@ -8,10 +8,7 @@ const typeDef = gql`
type Mutation {
saveAccounts(accounts: JSONObject): JSONObject @auth
# resetAccounts(schemaVersion: Int): JSONObject @auth
saveConfig(config: JSONObject): JSONObject @auth
# resetConfig(schemaVersion: Int): JSONObject @auth
# migrateConfigAndAccounts: JSONObject @auth
}
`

View file

@ -56,39 +56,45 @@ const addTermsHash = configs => {
)(terms)
}
const accountsSql = `update user_config set data = $2, valid = $3, schema_version = $4 where type = $1;
insert into user_config (type, data, valid, schema_version)
select $1, $2, $3, $4 where $1 not in (select type from user_config)`
const notifyReload = (dbOrTx, operatorId) =>
dbOrTx.none(
'NOTIFY $1:name, $2',
['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })]
)
function saveAccounts (accounts) {
const accountsSql = `UPDATE user_config SET data = $1, valid = TRUE, schema_version = $2 WHERE type = 'accounts';
INSERT INTO user_config (type, data, valid, schema_version)
SELECT 'accounts', $1, TRUE, $2 WHERE 'accounts' NOT IN (SELECT type FROM user_config)`
return Promise.all([loadAccounts(), getOperatorId('middleware')])
.then(([currentAccounts, operatorId]) => {
const newAccounts = _.merge(currentAccounts, accounts)
return db.tx(t => {
return t.none(accountsSql, ['accounts', { accounts: newAccounts }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
.then(() => t.none('NOTIFY $1:name, $2', ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })]))
}).catch(console.error)
// Only allow one wallet scoring active at a time
if (accounts.elliptic?.enabled && newAccounts.scorechain) {
newAccounts.scorechain.enabled = false
}
if (accounts.scorechain?.enabled && newAccounts.elliptic) {
newAccounts.elliptic.enabled = false
}
return db.tx(t =>
t.none(accountsSql, [{ accounts: newAccounts }, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
.then(() => notifyReload(t, operatorId))
).catch(console.error)
})
}
function resetAccounts (schemaVersion) {
return db.none(
accountsSql,
[
'accounts',
{ accounts: NEW_SETTINGS_LOADER_SCHEMA_VERSION ? {} : [] },
true,
schemaVersion
]
)
}
function loadAccounts (schemaVersion) {
const sql = `select data
from user_config
where type=$1
and schema_version=$2
and valid
order by id desc
limit 1`
const sql = `SELECT data
FROM user_config
WHERE type = $1
AND schema_version = $2
AND valid
ORDER BY id DESC
LIMIT 1`
return db.oneOrNone(sql, ['accounts', schemaVersion || NEW_SETTINGS_LOADER_SCHEMA_VERSION])
.then(_.compose(_.defaultTo({}), _.get('data.accounts')))
@ -106,15 +112,20 @@ function showAccounts (schemaVersion) {
})
}
const configSql = 'insert into user_config (type, data, valid, schema_version) values ($1, $2, $3, $4)'
const insertConfigRow = (dbOrTx, data) =>
dbOrTx.none(
"INSERT INTO user_config (type, data, valid, schema_version) VALUES ('config', $1, TRUE, $2)",
[data, NEW_SETTINGS_LOADER_SCHEMA_VERSION]
)
function saveConfig (config) {
return Promise.all([loadLatestConfigOrNone(), getOperatorId('middleware')])
.then(([currentConfig, operatorId]) => {
const newConfig = addTermsHash(_.assign(currentConfig, config))
return db.tx(t => {
return t.none(configSql, ['config', { config: newConfig }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
.then(() => t.none('NOTIFY $1:name, $2', ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })]))
}).catch(console.error)
return db.tx(t =>
insertConfigRow(t, { config: newConfig })
.then(() => notifyReload(t, operatorId))
).catch(console.error)
})
}
@ -122,10 +133,10 @@ function removeFromConfig (fields) {
return Promise.all([loadLatestConfigOrNone(), getOperatorId('middleware')])
.then(([currentConfig, operatorId]) => {
const newConfig = _.omit(fields, currentConfig)
return db.tx(t => {
return t.none(configSql, ['config', { config: newConfig }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
.then(() => t.none('NOTIFY $1:name, $2', ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })]))
}).catch(console.error)
return db.tx(t =>
insertConfigRow(t, { config: newConfig })
.then(() => notifyReload(t, operatorId))
).catch(console.error)
})
}
@ -133,23 +144,11 @@ function migrationSaveConfig (config) {
return loadLatestConfigOrNone()
.then(currentConfig => {
const newConfig = _.assign(currentConfig, config)
return db.none(configSql, ['config', { config: newConfig }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
return insertConfigRow(db, { config: newConfig })
.catch(console.error)
})
}
function resetConfig (schemaVersion) {
return db.none(
configSql,
[
'config',
{ config: schemaVersion === NEW_SETTINGS_LOADER_SCHEMA_VERSION ? {} : [] },
true,
schemaVersion
]
)
}
function loadLatest (schemaVersion) {
return Promise.all([loadLatestConfigOrNoneReturningVersion(schemaVersion), loadAccounts(schemaVersion)])
.then(([configObj, accounts]) => ({
@ -160,15 +159,15 @@ function loadLatest (schemaVersion) {
}
function loadLatestConfig () {
const sql = `select data
from user_config
where type=$1
and schema_version=$2
and valid
order by id desc
limit 1`
const sql = `SELECT data
FROM user_config
WHERE type = 'config'
AND schema_version = $1
AND valid
ORDER BY id DESC
LIMIT 1`
return db.one(sql, ['config', NEW_SETTINGS_LOADER_SCHEMA_VERSION])
return db.oneOrNone(sql, [NEW_SETTINGS_LOADER_SCHEMA_VERSION])
.then(row => row ? row.data.config : {})
.catch(err => {
throw err
@ -176,38 +175,39 @@ function loadLatestConfig () {
}
function loadLatestConfigOrNoneReturningVersion (schemaVersion) {
const sql = `select data, id
from user_config
where type=$1
and schema_version=$2
order by id desc
limit 1`
const sql = `SELECT data, id
FROM user_config
WHERE type = 'config'
AND schema_version = $1
AND valid
ORDER BY id DESC
LIMIT 1`
return db.oneOrNone(sql, ['config', schemaVersion || NEW_SETTINGS_LOADER_SCHEMA_VERSION])
return db.oneOrNone(sql, [schemaVersion || NEW_SETTINGS_LOADER_SCHEMA_VERSION])
.then(row => row ? { config: row.data.config, version: row.id } : {})
}
function loadLatestConfigOrNone (schemaVersion) {
const sql = `select data
from user_config
where type=$1
and schema_version=$2
order by id desc
limit 1`
const sql = `SELECT data
FROM user_config
WHERE type = 'config'
AND schema_version = $1
ORDER BY id DESC
LIMIT 1`
return db.oneOrNone(sql, ['config', schemaVersion || NEW_SETTINGS_LOADER_SCHEMA_VERSION])
return db.oneOrNone(sql, [schemaVersion || NEW_SETTINGS_LOADER_SCHEMA_VERSION])
.then(row => row ? row.data.config : {})
}
function loadConfig (versionId) {
const sql = `select data
from user_config
where id=$1
and type=$2
and schema_version=$3
and valid`
const sql = `SELECT data
FROM user_config
WHERE id = $1
AND type = 'config'
AND schema_version = $2
AND valid`
return db.one(sql, [versionId, 'config', NEW_SETTINGS_LOADER_SCHEMA_VERSION])
return db.one(sql, [versionId, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
.then(row => row.data.config)
.catch(err => {
if (err.name === 'QueryResultError') {
@ -228,29 +228,25 @@ function load (versionId) {
}))
}
function migrate () {
return loadLatest(OLD_SETTINGS_LOADER_SCHEMA_VERSION)
.then(res => {
const migrated = migration.migrate(res.config, res.accounts)
saveConfig(migrated.config)
saveAccounts(migrated.accounts)
return migrated
})
const fetchCurrentConfigVersion = () => {
const sql = `SELECT id FROM user_config
WHERE type = 'config'
AND valid
ORDER BY id DESC
LIMIT 1`
return db.one(sql).then(row => row.id)
}
module.exports = {
saveConfig,
migrationSaveConfig,
resetConfig,
saveAccounts,
resetAccounts,
loadAccounts,
showAccounts,
loadLatest,
loadLatestConfig,
loadLatestConfigOrNone,
load,
migrate,
removeFromConfig
removeFromConfig,
fetchCurrentConfigVersion,
}

View file

@ -6,6 +6,7 @@ const LOW_CRYPTO_BALANCE = 'LOW_CRYPTO_BALANCE'
const HIGH_CRYPTO_BALANCE = 'HIGH_CRYPTO_BALANCE'
const CASH_BOX_FULL = 'CASH_BOX_FULL'
const LOW_CASH_OUT = 'LOW_CASH_OUT'
const LOW_RECYCLER_STACKER = 'LOW_RECYCLER_STACKER'
const SECURITY = 'SECURITY'
const CODES_DISPLAY = {
@ -41,6 +42,7 @@ module.exports = {
HIGH_CRYPTO_BALANCE,
CASH_BOX_FULL,
LOW_CASH_OUT,
LOW_RECYCLER_STACKER,
SECURITY,
CODES_DISPLAY,
NETWORK_DOWN_TIME,

View file

@ -10,6 +10,7 @@ const {
HIGH_CRYPTO_BALANCE,
CASH_BOX_FULL,
LOW_CASH_OUT,
LOW_RECYCLER_STACKER,
SECURITY
} = require('./codes')
@ -80,6 +81,8 @@ function emailAlert (alert) {
return `Cash box full on ${alert.machineName} [${alert.notes} banknotes]`
case LOW_CASH_OUT:
return `Cassette for ${alert.denomination} ${alert.fiatCode} low [${alert.notes} banknotes]`
case LOW_RECYCLER_STACKER:
return `Recycler for ${alert.denomination} ${alert.fiatCode} low [${alert.notes} banknotes]`
case SECURITY:
return `Cashbox removed on ${alert.machineName}`
}

View file

@ -130,8 +130,8 @@ function checkStuckScreen (deviceEvents, machine) {
}
function transactionNotify (tx, rec) {
return settingsLoader.loadLatest().then(settings => {
const notifSettings = configManager.getGlobalNotifications(settings.config)
return settingsLoader.loadLatestConfig().then(config => {
const notifSettings = configManager.getGlobalNotifications(config)
const highValueTx = tx.fiat.gt(notifSettings.highValueTransaction || Infinity)
const isCashOut = tx.direction === 'cashOut'
@ -147,7 +147,7 @@ function transactionNotify (tx, rec) {
}
// alert through sms or email any transaction or high value transaction, if SMS || email alerts are enabled
const walletSettings = configManager.getWalletSettings(tx.cryptoCode, settings.config)
const walletSettings = configManager.getWalletSettings(tx.cryptoCode, config)
const zeroConfLimit = walletSettings.zeroConfLimit || 0
const zeroConf = isCashOut && tx.fiat.lte(zeroConfLimit)
const notificationsEnabled = notifSettings.sms.transactions || notifSettings.email.transactions
@ -308,8 +308,8 @@ function cashboxNotify (deviceId) {
// for notification center, check if type of notification is active before calling the respective notify function
const notifyIfActive = (type, fnName, ...args) => {
return settingsLoader.loadLatest().then(settings => {
const notificationSettings = configManager.getGlobalNotifications(settings.config).notificationCenter
return settingsLoader.loadLatestConfig().then(config => {
const notificationSettings = configManager.getGlobalNotifications(config).notificationCenter
if (!notificationCenter[fnName]) return Promise.reject(new Error(`Notification function ${fnName} for type ${type} does not exist`))
if (!(notificationSettings.active && notificationSettings[type])) return Promise.resolve()
return notificationCenter[fnName](...args)

View file

@ -2,20 +2,27 @@ const _ = require('lodash/fp')
const queries = require('./queries')
const utils = require('./utils')
const codes = require('./codes')
const customers = require('../customers')
const { NOTIFICATION_TYPES: {
const {
NOTIFICATION_TYPES: {
SECURITY,
COMPLIANCE,
CRYPTO_BALANCE,
FIAT_BALANCE,
ERROR,
HIGH_VALUE_TX,
NORMAL_VALUE_TX }
} = codes
NORMAL_VALUE_TX
},
const { STALE, PING } = codes
STALE,
PING,
HIGH_CRYPTO_BALANCE,
LOW_CRYPTO_BALANCE,
CASH_BOX_FULL,
LOW_CASH_OUT,
LOW_RECYCLER_STACKER,
} = require('./codes')
const sanctionsNotify = (customer, phone) => {
const code = 'SANCTIONS'
@ -71,9 +78,13 @@ const fiatBalancesNotify = (fiatWarnings) => {
const { cassette, deviceId } = o.detail
return cassette === balance.cassette && deviceId === balance.deviceId
}, notInvalidated)) return
const message = balance.code === 'LOW_CASH_OUT' ?
const message = balance.code === LOW_CASH_OUT ?
`Cash-out cassette ${balance.cassette} low or empty!` :
`Cash box full or almost full!`
balance.code === LOW_RECYCLER_STACKER ?
`Recycler ${balance.cassette} low or empty!` :
balance.code === CASH_BOX_FULL ?
`Cash box full or almost full!` :
`Cash box full or almost full!` /* Shouldn't happen */
const detailB = utils.buildDetail({ deviceId: balance.deviceId, cassette: balance.cassette })
return queries.addNotification(FIAT_BALANCE, message, detailB)
})
@ -105,7 +116,7 @@ const cryptoBalancesNotify = (cryptoWarnings) => {
}, notInvalidated)) return
const fiat = utils.formatCurrency(balance.fiatBalance.balance, balance.fiatCode)
const message = `${balance.code === 'HIGH_CRYPTO_BALANCE' ? 'High' : 'Low'} balance in ${balance.cryptoCode} [${fiat}]`
const message = `${balance.code === HIGH_CRYPTO_BALANCE ? 'High' : 'Low'} balance in ${balance.cryptoCode} [${fiat}]`
const detailB = utils.buildDetail({ cryptoCode: balance.cryptoCode, code: balance.code })
return queries.addNotification(CRYPTO_BALANCE, message, detailB)
})
@ -113,8 +124,8 @@ const cryptoBalancesNotify = (cryptoWarnings) => {
}
const balancesNotify = (balances) => {
const isCryptoCode = c => _.includes(c, ['HIGH_CRYPTO_BALANCE', 'LOW_CRYPTO_BALANCE'])
const isFiatCode = c => _.includes(c, ['LOW_CASH_OUT', 'CASH_BOX_FULL'])
const isCryptoCode = c => _.includes(c, [HIGH_CRYPTO_BALANCE, LOW_CRYPTO_BALANCE])
const isFiatCode = c => _.includes(c, [LOW_CASH_OUT, CASH_BOX_FULL, LOW_RECYCLER_STACKER])
const by = o =>
isCryptoCode(o) ? 'crypto' :
isFiatCode(o) ? 'fiat' :

View file

@ -11,6 +11,7 @@ const logger = require('./logger')
const logs = require('./logs')
const T = require('./time')
const configManager = require('./new-config-manager')
const settingsLoader = require('./new-settings-loader')
const ticker = require('./ticker')
const wallet = require('./wallet')
const walletScoring = require('./wallet-scoring')
@ -23,7 +24,13 @@ const commissionMath = require('./commission-math')
const loyalty = require('./loyalty')
const transactionBatching = require('./tx-batching')
const { CASH_UNIT_CAPACITY, CASH_OUT_DISPENSE_READY, CONFIRMATION_CODE } = require('./constants')
const {
CASH_OUT_DISPENSE_READY,
CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES,
CASH_OUT_MAXIMUM_AMOUNT_OF_RECYCLERS,
CASH_UNIT_CAPACITY,
CONFIRMATION_CODE,
} = require('./constants')
const notifier = require('./notifier')
@ -237,17 +244,6 @@ function plugins (settings, deviceId) {
.then(([cassettes, recyclers]) => ({ cassettes: cassettes.cassettes, recyclers: recyclers.recyclers }))
}
function fetchCurrentConfigVersion () {
const sql = `select id from user_config
where type=$1
and valid
order by id desc
limit 1`
return db.one(sql, ['config'])
.then(row => row.id)
}
function mapCoinSettings (coinParams) {
const [ cryptoCode, cryptoNetwork ] = coinParams
const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config)
@ -289,7 +285,7 @@ function plugins (settings, deviceId) {
return Promise.all([
buildAvailableCassettes(),
buildAvailableRecyclers(),
fetchCurrentConfigVersion(),
settingsLoader.fetchCurrentConfigVersion(),
millisecondsToMinutes(getTimezoneOffset(localeConfig.timezone)),
loyalty.getNumberOfAvailablePromoCodes(),
Promise.all(supportsBatchingPromise),
@ -692,169 +688,73 @@ function plugins (settings, deviceId) {
}
function checkDeviceCashBalances (fiatCode, device) {
const cashOutConfig = configManager.getCashOut(device.deviceId, settings.config)
const denomination1 = cashOutConfig.cassette1
const denomination2 = cashOutConfig.cassette2
const denomination3 = cashOutConfig.cassette3
const denomination4 = cashOutConfig.cassette4
const denominationRecycler1 = cashOutConfig.recycler1
const denominationRecycler2 = cashOutConfig.recycler2
const denominationRecycler3 = cashOutConfig.recycler3
const denominationRecycler4 = cashOutConfig.recycler4
const denominationRecycler5 = cashOutConfig.recycler5
const denominationRecycler6 = cashOutConfig.recycler6
const cashOutEnabled = cashOutConfig.active
const isUnitLow = (have, max, limit) => cashOutEnabled && ((have / max) * 100) < limit
// const isUnitHigh = (have, max, limit) => cashOutEnabled && ((have / max) * 100) > limit
// const isUnitOutOfBounds = (have, max, lowerBound, upperBound) => isUnitLow(have, max, lowerBound) || isUnitHigh(have, max, upperBound)
const notifications = configManager.getNotifications(null, device.deviceId, settings.config)
const deviceId = device.deviceId
const machineName = device.name
const notifications = configManager.getNotifications(null, deviceId, settings.config)
const cashInAlert = device.cashUnits.cashbox > notifications.cashInAlertThreshold
? {
const cashInAlerts = device.cashUnits.cashbox > notifications.cashInAlertThreshold
? [{
code: 'CASH_BOX_FULL',
machineName,
deviceId: device.deviceId,
deviceId,
notes: device.cashUnits.cashbox
}
: null
}]
: []
const cassette1Alert = device.numberOfCassettes >= 1 && isUnitLow(device.cashUnits.cassette1, getCashUnitCapacity(device.model, 'cassette'), notifications.fillingPercentageCassette1)
? {
const cashOutConfig = configManager.getCashOut(deviceId, settings.config)
const cashOutEnabled = cashOutConfig.active
const isUnitLow = (have, max, limit) => ((have / max) * 100) < limit
if (!cashOutEnabled)
return cashInAlerts
const cassetteCapacity = getCashUnitCapacity(device.model, 'cassette')
const cassetteAlerts = Array(Math.min(device.numberOfCassettes ?? 0, CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES))
.fill(null)
.flatMap((_elem, idx) => {
const nth = idx + 1
const cassetteField = `cassette${nth}`
const notes = device.cashUnits[cassetteField]
const denomination = cashOutConfig[cassetteField]
const limit = notifications[`fillingPercentageCassette${nth}`]
return isUnitLow(notes, cassetteCapacity, limit) ?
[{
code: 'LOW_CASH_OUT',
cassette: 1,
cassette: nth,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.cassette1,
denomination: denomination1,
deviceId,
notes,
denomination,
fiatCode
}
: null
}] :
[]
})
const cassette2Alert = device.numberOfCassettes >= 2 && isUnitLow(device.cashUnits.cassette2, getCashUnitCapacity(device.model, 'cassette'), notifications.fillingPercentageCassette2)
? {
code: 'LOW_CASH_OUT',
cassette: 2,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.cassette2,
denomination: denomination2,
fiatCode
}
: null
const recyclerCapacity = getCashUnitCapacity(device.model, 'recycler')
const recyclerAlerts = Array(Math.min(device.numberOfRecyclers ?? 0, CASH_OUT_MAXIMUM_AMOUNT_OF_RECYCLERS))
.fill(null)
.flatMap((_elem, idx) => {
const nth = idx + 1
const recyclerField = `recycler${nth}`
const notes = device.cashUnits[recyclerField]
const denomination = cashOutConfig[recyclerField]
const cassette3Alert = device.numberOfCassettes >= 3 && isUnitLow(device.cashUnits.cassette3, getCashUnitCapacity(device.model, 'cassette'), notifications.fillingPercentageCassette3)
? {
code: 'LOW_CASH_OUT',
cassette: 3,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.cassette3,
denomination: denomination3,
fiatCode
}
: null
const cassette4Alert = device.numberOfCassettes >= 4 && isUnitLow(device.cashUnits.cassette4, getCashUnitCapacity(device.model, 'cassette'), notifications.fillingPercentageCassette4)
? {
code: 'LOW_CASH_OUT',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.cassette4,
denomination: denomination4,
fiatCode
}
: null
const recycler1Alert = device.numberOfRecyclers >= 1 && isUnitLow(device.cashUnits.recycler1, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler1)
? {
const limit = notifications[`fillingPercentageRecycler${nth}`]
return isUnitLow(notes, recyclerCapacity, limit) ?
[{
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
cassette: nth, // @see DETAIL_TEMPLATE in /lib/notifier/utils.js
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler1,
denomination: denominationRecycler1,
deviceId,
notes,
denomination,
fiatCode
}
: null
}] :
[]
})
const recycler2Alert = device.numberOfRecyclers >= 2 && isUnitLow(device.cashUnits.recycler2, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler2)
? {
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler2,
denomination: denominationRecycler2,
fiatCode
}
: null
const recycler3Alert = device.numberOfRecyclers >= 3 && isUnitLow(device.cashUnits.recycler3, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler3)
? {
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler3,
denomination: denominationRecycler3,
fiatCode
}
: null
const recycler4Alert = device.numberOfRecyclers >= 4 && isUnitLow(device.cashUnits.recycler4, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler4)
? {
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler4,
denomination: denominationRecycler4,
fiatCode
}
: null
const recycler5Alert = device.numberOfRecyclers >= 5 && isUnitLow(device.cashUnits.recycler5, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler5)
? {
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler5,
denomination: denominationRecycler5,
fiatCode
}
: null
const recycler6Alert = device.numberOfRecyclers >= 6 && isUnitLow(device.cashUnits.recycler6, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler6)
? {
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler6,
denomination: denominationRecycler6,
fiatCode
}
: null
return _.compact([
cashInAlert,
cassette1Alert,
cassette2Alert,
cassette3Alert,
cassette4Alert,
recycler1Alert,
recycler2Alert,
recycler3Alert,
recycler4Alert,
recycler5Alert,
recycler6Alert
])
return [].concat(cashInAlerts, cassetteAlerts, recyclerAlerts)
}
function checkCryptoBalances (fiatCode, devices) {
@ -1036,7 +936,6 @@ function plugins (settings, deviceId) {
sell,
getNotificationConfig,
notifyOperator,
fetchCurrentConfigVersion,
pruneMachinesHeartbeat,
rateAddress,
rateTransaction,

View file

@ -0,0 +1,95 @@
const { AML } = require('elliptic-sdk')
const _ = require('lodash/fp')
const NAME = 'Elliptic'
const HOLLISTIC_COINS = {
BTC: 'BTC',
ETH: 'ETH',
USDT: 'USDT',
USDT_TRON: 'USDT',
LTC: 'LTC',
TRX: 'TRX'
}
const SINGLE_ASSET_COINS = {
ZEC: {
asset: 'ZEC',
blockchain: 'zcash'
},
BCH: {
asset: 'BCH',
blockchain: 'bitcoin_cash'
}
}
const TYPE = {
TRANSACTION: 'transaction',
ADDRESS: 'address'
}
const SUPPORTED_COINS = { ...HOLLISTIC_COINS, ...SINGLE_ASSET_COINS }
function rate (account, objectType, cryptoCode, objectId) {
return isWalletScoringEnabled(account, cryptoCode).then(isEnabled => {
if (!isEnabled) return Promise.resolve(null)
const aml = new AML({
key: account.apiKey,
secret: account.apiSecret
})
const isHolistic = Object.keys(HOLLISTIC_COINS).includes(cryptoCode)
const requestBody = {
subject: {
asset: isHolistic ? 'holistic' : SINGLE_ASSET_COINS[cryptoCode].asset,
blockchain: isHolistic ? 'holistic' : SINGLE_ASSET_COINS[cryptoCode].blockchain,
type: objectType,
hash: objectId
},
type: objectType === TYPE.ADDRESS ? 'wallet_exposure' : 'source_of_funds'
}
const threshold = account.scoreThreshold
const endpoint = objectType === TYPE.ADDRESS ? '/v2/wallet/synchronous' : '/v2/analysis/synchronous'
return aml.client
.post(endpoint, requestBody)
.then((res) => {
const resScore = res.data?.risk_score
// elliptic returns 0-1 score, but we're accepting 0-100 config
// normalize score to 0-10 where 0 is the lowest risk
// elliptic score can be null and contains decimals
return {score: (resScore || 0) * 10, isValid: ((resScore || 0) * 100) < threshold}
})
})
}
function rateTransaction (account, cryptoCode, transactionId) {
return rate(account, TYPE.TRANSACTION, cryptoCode, transactionId)
}
function rateAddress (account, cryptoCode, address) {
return rate(account, TYPE.ADDRESS, cryptoCode, address)
}
function isWalletScoringEnabled (account, cryptoCode) {
const isAccountEnabled = !_.isNil(account) && account.enabled
if (!isAccountEnabled) return Promise.resolve(false)
if (!Object.keys(SUPPORTED_COINS).includes(cryptoCode)) {
return Promise.reject(new Error('Unsupported crypto: ' + cryptoCode))
}
return Promise.resolve(true)
}
module.exports = {
NAME,
rateAddress,
rateTransaction,
isWalletScoringEnabled
}

View file

@ -219,5 +219,6 @@ module.exports = {
sendCoinsBatch,
checkBlockchainStatus,
getTxHashesByAddress,
fetch,
SUPPORTS_BATCHING
}

View file

@ -42,6 +42,10 @@ const SWEEP_QUEUE = new PQueue({
interval: 250,
})
const SEND_QUEUE = new PQueue({
concurrency: 1,
})
const infuraCalls = {}
const pify = _function => {
@ -78,7 +82,8 @@ function sendCoins (account, tx, settings, operatorId, feeMultiplier) {
const { toAddress, cryptoAtoms, cryptoCode } = tx
const isErc20Token = coins.utils.isErc20Token(cryptoCode)
return (isErc20Token ? generateErc20Tx : generateTx)(toAddress, defaultWallet(account), cryptoAtoms, false, cryptoCode)
return SEND_QUEUE.add(() =>
(isErc20Token ? generateErc20Tx : generateTx)(toAddress, defaultWallet(account), cryptoAtoms, false, cryptoCode)
.then(pify(web3.eth.sendSignedTransaction))
.then(txid => {
return pify(web3.eth.getTransaction)(txid)
@ -90,6 +95,7 @@ function sendCoins (account, tx, settings, operatorId, feeMultiplier) {
return { txid, fee }
})
})
)
}
function checkCryptoCode (cryptoCode) {

View file

@ -4,8 +4,6 @@ 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'
function run (account) {
@ -27,21 +25,13 @@ 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
if (timePassedSinceTx < 3 * T.minutes) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 10 * T.seconds
if (timePassedSinceTx < 5 * T.minutes) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 20 * T.seconds
if (timePassedSinceTx < 30 * T.minutes) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > T.minute
if (timePassedSinceTx < 1 * T.hour) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 2 * T.minute
if (timePassedSinceTx < 3 * T.hours) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > 5 * T.minute
if (timePassedSinceTx < 1 * T.day) return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > T.hour
return _.isNil(txsCache.get(tx.id).res) || timePassedSinceReq > T.hour
}
// Override geth's getStatus function to allow for different polling timing

View file

@ -54,7 +54,7 @@ app.use(compression({ threshold: 500 }))
app.use(helmet())
app.use(nocache())
app.use(express.json({ limit: '2mb' }))
app.use(morgan(':method :url :status :response-time ms - :res[content-length]', { stream: logger.stream }))
app.use(morgan(':method :url :status :response-time ms -- :req[content-length]/:res[content-length] b', { stream: logger.stream }))
// app /pair and /ca routes
app.use('/', pairingRoutes)

View file

@ -311,6 +311,7 @@ function getOrAddCustomerPhone (req, res, next) {
}
function getOrAddCustomerEmail (req, res, next) {
const deviceId = req.deviceId
const customerData = req.body
const pi = plugins(req.settings, req.deviceId)
@ -318,7 +319,7 @@ function getOrAddCustomerEmail (req, res, next) {
return pi.getEmailCode(email)
.then(code => {
return addOrUpdateCustomer(customerData, req.settings.config, true)
return addOrUpdateCustomer(customerData, deviceId, req.settings.config, true)
.then(customer => respond(req, res, { code, customer }))
})
.catch(err => {

View file

@ -4,7 +4,7 @@ const nmd = require('nano-markdown')
const router = express.Router()
const configManager = require('../new-config-manager')
const plugins = require('../plugins')
const settingsLoader = require('../new-settings-loader')
const createTerms = terms => (terms.active && terms.text) ? ({
delay: terms.delay,
@ -18,15 +18,10 @@ const createTerms = terms => (terms.active && terms.text) ? ({
function getTermsConditions (req, res, next) {
const deviceId = req.deviceId
const settings = req.settings
const terms = configManager.getTermsConditions(settings.config)
const pi = plugins(settings, deviceId)
return pi.fetchCurrentConfigVersion().then(version => {
return res.json({ terms: createTerms(terms), version })
})
const { config } = req.settings
const terms = configManager.getTermsConditions(config)
return settingsLoader.fetchCurrentConfigVersion()
.then(version => res.json({ terms: createTerms(terms), version }))
.catch(next)
}

View file

@ -1,14 +1,23 @@
const ph = require('./plugin-helper')
const argv = require('minimist')(process.argv.slice(2))
const configManager = require('./new-config-manager')
function loadWalletScoring (settings, cryptoCode) {
const pluginCode = argv.mockScoring ? 'mock-scoring' : 'scorechain'
const wallet = cryptoCode ? ph.load(ph.WALLET, configManager.getWalletSettings(cryptoCode, settings.config).wallet) : null
const plugin = ph.load(ph.WALLET_SCORING, pluginCode)
const account = settings.accounts[pluginCode]
// TODO - This function should be rolled back after UI is created for this feature
function loadWalletScoring (settings) {
if (argv.mockScoring) {
const mock = ph.load(ph.WALLET_SCORING, 'mock-scoring')
return { plugin: mock, account: {} }
}
return { plugin, account, wallet }
const scorechainAccount = settings.accounts['scorechain']
if (scorechainAccount?.enabled) {
const scorechain = ph.load(ph.WALLET_SCORING, 'scorechain')
return { plugin: scorechain, account: scorechainAccount}
}
const ellipticAccount = settings.accounts['elliptic']
const elliptic = ph.load(ph.WALLET_SCORING, 'elliptic')
return { plugin: elliptic, account: ellipticAccount }
}
function rateTransaction (settings, cryptoCode, address) {

View file

@ -5,9 +5,9 @@ const configManager = require('../lib/new-config-manager')
exports.up = function (next) {
return db.tx(async t => {
const settingsPromise = settingsLoader.loadLatest()
const settingsPromise = settingsLoader.loadLatestConfig()
const machinesPromise = t.any('SELECT device_id FROM devices')
const [{ config }, machines] = await Promise.all([settingsPromise, machinesPromise])
const [config, machines] = await Promise.all([settingsPromise, machinesPromise])
const cryptoCodes = configManager.getCryptosFromWalletNamespace(config)
const deviceIds = _.map(_.get('device_id'))(machines)

View file

@ -3,7 +3,7 @@ const settingsLoader = require('../lib/new-settings-loader')
const configManager = require('../lib/new-config-manager')
exports.up = async function (next) {
const { config } = await settingsLoader.loadLatest()
const config = await settingsLoader.loadLatestConfig()
const cryptoCodes = configManager.getCryptosFromWalletNamespace(config)
_.forEach(cryptoCode => {
const key = `wallets_${cryptoCode}_zeroConf`

View file

@ -1,10 +1,10 @@
const _ = require('lodash/fp')
const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader')
const { migrationSaveConfig, loadLatestConfig } = require('../lib/new-settings-loader')
const CASSETTE_MAX_CAPACITY = 500
exports.up = function (next) {
return loadLatest()
.then(({ config }) => {
return loadLatestConfig()
.then(config => {
const fiatBalance1 = config.notifications_fiatBalanceCassette1
const fiatBalance2 = config.notifications_fiatBalanceCassette2
const fiatBalance3 = config.notifications_fiatBalanceCassette3

View file

@ -2,8 +2,8 @@ const _ = require('lodash/fp')
const settingsLoader = require('../lib/new-settings-loader')
exports.up = function (next) {
settingsLoader.loadLatest()
.then(({ config }) => {
settingsLoader.loadLatestConfig()
.then(config => {
if (!_.isEmpty(config))
config.locale_timezone = '0:0'
return settingsLoader.migrationSaveConfig(config)

View file

@ -1,5 +1,5 @@
const db = require('./db')
const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader')
const { migrationSaveConfig } = require('../lib/new-settings-loader')
exports.up = function (next) {
const sql = [

View file

@ -1,13 +1,13 @@
const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader')
const { migrationSaveConfig, loadLatestConfig } = require('../lib/new-settings-loader')
const { getCryptosFromWalletNamespace } = require('../lib/new-config-manager.js')
const { utils: coinUtils } = require('@lamassu/coins')
const _ = require('lodash/fp')
exports.up = function (next) {
loadLatest()
.then(settings => {
loadLatestConfig()
.then(config => {
const newSettings = {}
const activeCryptos = getCryptosFromWalletNamespace(settings.config)
const activeCryptos = getCryptosFromWalletNamespace(config)
if (!activeCryptos.length) return Promise.resolve()
_.map(crypto => {
const defaultUnit = _.head(_.keys(coinUtils.getCryptoCurrency(crypto).units))

View file

@ -1,6 +1,6 @@
var db = require('./db')
const _ = require('lodash/fp')
const { migrationSaveConfig, loadLatest } = require('../lib/new-settings-loader')
const { migrationSaveConfig, loadLatestConfig } = require('../lib/new-settings-loader')
const { getMachineIds } = require('../lib/machine-loader')
exports.up = function (next) {
@ -25,16 +25,16 @@ exports.up = function (next) {
ADD COLUMN denomination_4 INTEGER`
]
return Promise.all([loadLatest(), getMachineIds()])
return Promise.all([loadLatestConfig(), getMachineIds()])
.then(([config, machineIds]) => {
const newConfig = _.reduce((acc, value) => {
const deviceId = value.device_id
if (_.includes(`cashOut_${deviceId}_top`, _.keys(config.config))) {
acc[`cashOut_${deviceId}_cassette1`] = config.config[`cashOut_${deviceId}_top`]
if (_.includes(`cashOut_${deviceId}_top`, _.keys(config))) {
acc[`cashOut_${deviceId}_cassette1`] = config[`cashOut_${deviceId}_top`]
}
if (_.includes(`cashOut_${deviceId}_bottom`, _.keys(config.config))) {
acc[`cashOut_${deviceId}_cassette2`] = config.config[`cashOut_${deviceId}_bottom`]
if (_.includes(`cashOut_${deviceId}_bottom`, _.keys(config))) {
acc[`cashOut_${deviceId}_cassette2`] = config[`cashOut_${deviceId}_bottom`]
}
return acc

View file

@ -1,16 +1,14 @@
const uuid = require('uuid')
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
const { saveConfig } = require('../lib/new-settings-loader')
exports.up = function (next) {
const newConfig = {}
return loadLatest()
.then(config => {
newConfig[`wallets_advanced_feeMultiplier`] = '1'
newConfig[`wallets_advanced_cryptoUnits`] = 'full'
newConfig[`wallets_advanced_allowTransactionBatching`] = false
newConfig[`wallets_advanced_id`] = uuid.v4()
const newConfig = {
wallets_advanced_feeMultiplier: '1',
wallets_advanced_cryptoUnits: 'full',
wallets_advanced_allowTransactionBatching: false,
wallets_advanced_id: uuid.v4(),
}
return saveConfig(newConfig)
})
.then(next)
.catch(err => {
return next(err)

View file

@ -1,12 +1,11 @@
const _ = require('lodash/fp')
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
const { saveConfig, loadLatestConfig } = require('../lib/new-settings-loader')
exports.up = function (next) {
const newConfig = {}
return loadLatest()
return loadLatestConfig()
.then(config => {
if (!_.isNil(config.config.locale_timezone)) return
newConfig[`locale_timezone`] = 'GMT'
if (!_.isNil(config.locale_timezone)) return
const newConfig = { locale_timezone: 'GMT' }
return saveConfig(newConfig)
})
.then(next)

View file

@ -1,11 +1,10 @@
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
const { saveConfig, loadLatestConfig } = require('../lib/new-settings-loader')
exports.up = function (next) {
const newConfig = {}
return loadLatest()
return loadLatestConfig()
.then(config => {
if (config.config.locale_timezone === "0:0") {
newConfig[`locale_timezone`] = 'GMT'
if (config.locale_timezone === "0:0") {
const newConfig = { locale_timezone: 'GMT' }
return saveConfig(newConfig)
}
})

View file

@ -1,11 +1,11 @@
const { removeFromConfig, loadLatest } = require('../lib/new-settings-loader')
const { removeFromConfig, loadLatestConfig } = require('../lib/new-settings-loader')
const { getCryptosFromWalletNamespace } = require('../lib/new-config-manager.js')
const _ = require('lodash/fp')
exports.up = function (next) {
loadLatest()
.then(settings => {
const configuredCryptos = getCryptosFromWalletNamespace(settings.config)
loadLatestConfig()
.then(config => {
const configuredCryptos = getCryptosFromWalletNamespace(config)
if (!configuredCryptos.length) return Promise.resolve()
return removeFromConfig(_.map(it => `wallets_${it}_cryptoUnits`, configuredCryptos))

View file

@ -1,13 +1,12 @@
const _ = require('lodash/fp')
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
const { saveConfig, loadLatestConfig } = require('../lib/new-settings-loader')
exports.up = function (next) {
const newConfig = {}
return loadLatest()
return loadLatestConfig()
.then(config => {
if (!_.isNil(config.config.wallets_ETH_zeroConfLimit) && config.config.wallets_ETH_zeroConfLimit !== 0) {
newConfig[`wallets_ETH_zeroConfLimit`] = 0
if (!_.isNil(config.wallets_ETH_zeroConfLimit) && config.wallets_ETH_zeroConfLimit !== 0) {
const newConfig = { wallets_ETH_zeroConfLimit: 0 }
return saveConfig(newConfig)
}
})

View file

@ -9,8 +9,8 @@ module.exports = {migrateNames}
function migrateNames () {
const cs = new pgp.helpers.ColumnSet(['?device_id', 'name'], {table: 'devices'})
return settingsLoader.loadLatest(false)
.then(r => machineLoader.getMachineNames(r.config))
return settingsLoader.loadLatestConfig(false)
.then(config => machineLoader.getMachineNames(config))
.then(_.map(r => ({device_id: r.deviceId, name: r.name})))
.then(data => pgp.helpers.update(data, cs) + ' WHERE t.device_id=v.device_id')
}

View file

@ -30,7 +30,7 @@ const BooleanPropertiesTable = memo(
elements.map(it => [it.name, data[it.name]?.toString() ?? null])
)
const schemaValidation = R.fromPairs(
const validationSchema = R.fromPairs(
elements.map(it => [it.name, Yup.boolean().required()])
)
@ -56,7 +56,7 @@ const BooleanPropertiesTable = memo(
enableReinitialize
onSubmit={innerSave}
initialValues={initialValues}
schemaValidation={schemaValidation}>
validationSchema={validationSchema}>
{({ resetForm }) => {
return (
<Form>

View file

@ -126,9 +126,9 @@ const validationSchema = Yup.object().shape({
'unique-name',
'Machine name is already in use.',
(value, context) =>
!R.any(
it => R.equals(R.toLower(it), R.toLower(value)),
context.options.context.machineNames
!R.includes(
R.toLower(value),
R.map(R.toLower, context.options.context.machineNames)
)
)
})

View file

@ -1,82 +0,0 @@
import { useMutation } from '@apollo/react-hooks'
import { Box } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles'
import gql from 'graphql-tag'
import React, { useState } from 'react'
import Title from 'src/components/Title'
import { Button } from 'src/components/buttons'
const styles = {
button: {
marginBottom: 10
}
}
const useStyles = makeStyles(styles)
const RESET = gql`
mutation Reset($schemaVersion: Int) {
resetConfig(schemaVersion: $schemaVersion)
resetAccounts(schemaVersion: $schemaVersion)
}
`
const MIGRATE = gql`
mutation Migrate {
migrateConfigAndAccounts
}
`
const OLD_SCHEMA_VERSION = 1
const NEW_SCHEMA_VERSION = 2
const ConfigMigration = () => {
const [loading, setLoading] = useState(false)
const [reset] = useMutation(RESET, {
onCompleted: () => setLoading(false)
})
const [migrate] = useMutation(MIGRATE, {
onCompleted: () => setLoading(false)
})
const classes = useStyles()
const innerReset = schemaVersion => {
setLoading(true)
reset({ variables: { schemaVersion } })
}
const innerMigrate = () => {
setLoading(true)
migrate()
}
return (
<>
<Title>Config Migration</Title>
<Box display="flex" alignItems="center" flexDirection="column">
<Button
className={classes.button}
disabled={loading}
onClick={() => innerReset(OLD_SCHEMA_VERSION)}>
Reset old admin
</Button>
<Button
className={classes.button}
disabled={loading}
onClick={() => innerReset(NEW_SCHEMA_VERSION)}>
Reset new admin
</Button>
<Button
className={classes.button}
disabled={loading}
onClick={() => innerMigrate()}>
Migrate
</Button>
</Box>
</>
)
}
export default ConfigMigration

View file

@ -1,6 +1,5 @@
import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles'
import { parse, format } from 'date-fns/fp'
import * as R from 'ramda'
import { useState, React } from 'react'
import * as Yup from 'yup'
@ -30,6 +29,7 @@ import {
customerDataElements,
customerDataSchemas,
formatDates,
tryFormatDate,
getFormattedPhone
} from './helper.js'
@ -113,18 +113,10 @@ const CustomerData = ({
firstName: R.path(['firstName'])(idData) ?? '',
lastName: R.path(['lastName'])(idData) ?? '',
documentNumber: R.path(['documentNumber'])(idData) ?? '',
dateOfBirth:
(rawDob &&
format('yyyy-MM-dd')(parse(new Date(), 'yyyyMMdd', rawDob))) ??
'',
dateOfBirth: tryFormatDate(rawDob),
gender: R.path(['gender'])(idData) ?? '',
country: R.path(['country'])(idData) ?? '',
expirationDate:
(rawExpirationDate &&
format('yyyy-MM-dd')(
parse(new Date(), 'yyyyMMdd', rawExpirationDate)
)) ??
''
expirationDate: tryFormatDate(rawExpirationDate)
},
usSsn: {
usSsn: customer.usSsn ?? ''

View file

@ -528,13 +528,22 @@ const requirementElements = {
}
}
const tryFormatDate = rawDate => {
try {
return (
(rawDate &&
format('yyyy-MM-dd')(parse(new Date(), 'yyyyMMdd', rawDate))) ??
''
)
} catch (err) {
return ''
}
}
const formatDates = values => {
R.map(
elem =>
(values[elem] = format('yyyyMMdd')(
parse(new Date(), 'yyyy-MM-dd', values[elem])
))
)(['dateOfBirth', 'expirationDate'])
R.forEach(elem => {
values[elem] = tryFormatDate(values[elem])
})(['dateOfBirth', 'expirationDate'])
return values
}
@ -579,6 +588,7 @@ export {
customerDataElements,
customerDataSchemas,
formatDates,
tryFormatDate,
REQUIREMENT,
CUSTOM,
ID_CARD_DATA

View file

@ -59,7 +59,7 @@ const FiatBalanceOverrides = ({ config, section }) => {
}
const initialValues = {
[MACHINE_KEY]: null,
[MACHINE_KEY]: '',
[CASHBOX_KEY]: '',
[CASSETTE_1_KEY]: '',
[CASSETTE_2_KEY]: '',

View file

@ -0,0 +1,64 @@
import * as Yup from 'yup'
import CheckboxFormik from 'src/components/inputs/formik/Checkbox'
import NumberInputFormik from 'src/components/inputs/formik/NumberInput'
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
import { secretTest, leadingZerosTest } from './helper'
export default {
code: 'elliptic',
name: 'Elliptic',
title: 'Elliptic (Scoring)',
elements: [
{
code: 'apiKey',
display: 'API Key',
component: SecretInputFormik
},
{
code: 'apiSecret',
display: 'API Secret',
component: SecretInputFormik
},
{
code: 'scoreThreshold',
display: 'Score threshold',
component: NumberInputFormik,
face: true,
long: false
},
{
code: 'enabled',
component: CheckboxFormik,
settings: {
enabled: true,
disabledMessage: 'This plugin is disabled',
label: 'Enabled',
requirement: null,
rightSideLabel: true
},
face: true
}
],
getValidationSchema: account => {
return Yup.object().shape({
apiKey: Yup.string('The API key must be a string')
.max(100, 'Too long')
.test(secretTest(account?.apiKey, 'API key')),
apiSecret: Yup.string('The API secret must be a string')
.max(100, 'Too long')
.test(secretTest(account?.apiKey, 'API key')),
scoreThreshold: Yup.number('The score threshold must be a number')
.required('A score threshold is required')
.min(1, 'The score threshold must be between 1 and 100')
.max(100, 'The score threshold must be between 1 and 100')
.integer('The score threshold must be an integer')
.test(
'no-leading-zeros',
'The score threshold must not have leading zeros',
leadingZerosTest
)
})
}
}

View file

@ -5,6 +5,7 @@ import bitgo from './bitgo'
import bitstamp from './bitstamp'
import blockcypher from './blockcypher'
import cex from './cex'
import elliptic from './elliptic'
import galoy from './galoy'
import inforu from './inforu'
import infura from './infura'
@ -23,6 +24,7 @@ export default {
[galoy.code]: galoy,
[bitstamp.code]: bitstamp,
[blockcypher.code]: blockcypher,
[elliptic.code]: elliptic,
[inforu.code]: inforu,
[infura.code]: infura,
[itbit.code]: itbit,

View file

@ -65,7 +65,9 @@ const ChooseType = () => {
}
const validationSchema = Yup.object().shape({
inputType: Yup.string().required()
inputType: Yup.string()
.label('Input type')
.required()
})
const defaultValues = {

View file

@ -29,14 +29,14 @@ const NameOfRequirement = () => {
const validationSchema = existingRequirements =>
Yup.object().shape({
requirementName: Yup.string()
.required('A requirement name is required')
.required('Name is required')
.test(
'unique-name',
'A custom information requirement with that name already exists',
(value, _context) =>
!R.any(
it => R.equals(R.toLower(it), R.toLower(value)),
R.map(it => it.customRequest.name, existingRequirements)
!R.includes(
R.toLower(R.defaultTo('', value)),
R.map(it => R.toLower(it.customRequest.name), existingRequirements)
)
)
})

View file

@ -32,8 +32,12 @@ const Screen1Information = () => {
}
const validationSchema = Yup.object().shape({
screen1Title: Yup.string().required(),
screen1Text: Yup.string().required()
screen1Title: Yup.string()
.label('Screen title')
.required(),
screen1Text: Yup.string()
.label('Screen text')
.required()
})
const defaultValues = {

View file

@ -30,8 +30,12 @@ const ScreenInformation = () => {
}
const validationSchema = Yup.object().shape({
screen2Title: Yup.string().required(),
screen2Text: Yup.string().required()
screen2Title: Yup.string()
.label('Screen title')
.required(),
screen2Text: Yup.string()
.label('Screen text')
.required()
})
const defaultValues = {

View file

@ -40,28 +40,38 @@ const validationSchema = Yup.lazy(values => {
switch (values.inputType) {
case 'numerical':
return Yup.object({
constraintType: Yup.string().required(),
constraintType: Yup.string()
.label('Constraint type')
.required(),
inputLength: Yup.number().when('constraintType', {
is: 'length',
then: Yup.number()
.min(0)
.required(),
.required('The number of digits is required'),
else: Yup.mixed().notRequired()
})
})
case 'text':
return Yup.object({
constraintType: Yup.string().required(),
inputLabel1: Yup.string().required(),
constraintType: Yup.string()
.label('Constraint type')
.required(),
inputLabel1: Yup.string()
.label('Text entry label')
.required(),
inputLabel2: Yup.string().when('constraintType', {
is: 'spaceSeparation',
then: Yup.string().required(),
then: Yup.string()
.label('Second word label')
.required(),
else: Yup.mixed().notRequired()
})
})
case 'choiceList':
return Yup.object({
constraintType: Yup.string().required(),
constraintType: Yup.string()
.label('Constraint type')
.required(),
listChoices: Yup.array().test(
'has-2-or-more',
'Choice list needs to have two or more non empty fields',

View file

@ -53,39 +53,26 @@ const styles = {
const useStyles = makeStyles(styles)
const getStep = (step, existingRequirements) => {
switch (step) {
case 1:
return {
schema: nameOfReqSchema(existingRequirements),
const getStep = (step, existingRequirements) =>
[
{
validationSchema: nameOfReqSchema(existingRequirements),
Component: NameOfRequirement
}
case 2:
return {
schema: screen1InfoSchema,
},
{
validationSchema: screen1InfoSchema,
Component: Screen1Information
}
case 3:
return { schema: chooseTypeSchema, Component: ChooseType }
case 4:
return {
schema: screen2InfoSchema,
},
{ validationSchema: chooseTypeSchema, Component: ChooseType },
{
validationSchema: screen2InfoSchema,
Component: Screen2Information
}
case 5:
return {
schema: typeFieldsValidationSchema,
},
{
validationSchema: typeFieldsValidationSchema,
Component: TypeFields
}
default:
return {
schema: {},
Component: () => {
return <h1>Default component step</h1>
}
}
}
}
][step - 1]
const nonEmptyStr = obj => obj.text && obj.text.length
@ -139,10 +126,9 @@ const formatValues = (values, isEditing) => {
return resObj
}
const makeEditingValues = it => {
const { customRequest } = it
const makeEditingValues = ({ customRequest, id }) => {
return {
id: it.id,
id,
requirementName: customRequest.name,
screen1Title: customRequest.screen1.title,
screen1Text: customRequest.screen1.text,
@ -157,10 +143,7 @@ const makeEditingValues = it => {
}
}
const chooseNotNull = (a, b) => {
if (!R.isNil(b)) return b
return a
}
const chooseNotNull = (a, b) => (R.isNil(b) ? a : b)
const Wizard = ({
onClose,
@ -174,11 +157,19 @@ const Wizard = ({
const isEditing = !R.isNil(toBeEdited)
const [step, setStep] = useState(isEditing ? 1 : 0)
const defaultValues = {
...nameOfReqDefaults,
...screen1InfoDefaults,
...screen2InfoDefaults,
...chooseTypeDefaults,
...typeFieldsDefaults
}
// If we're editing, filter out the requirement being edited so that validation schemas don't enter in circular conflicts
const _existingRequirements = isEditing
existingRequirements = isEditing
? R.filter(it => it.id !== toBeEdited.id, existingRequirements)
: existingRequirements
const stepOptions = getStep(step, _existingRequirements)
const stepOptions = getStep(step, existingRequirements)
const isLastStep = step === LAST_STEP
const onContinue = (values, actions) => {
@ -202,6 +193,7 @@ const Wizard = ({
}
const editingValues = isEditing ? makeEditingValues(toBeEdited) : {}
const initialValues = R.mergeWith(chooseNotNull, defaultValues, editingValues)
const wizardTitle = isEditing
? 'Editing custom requirement'
: 'New custom requirement'
@ -226,18 +218,8 @@ const Wizard = ({
validateOnChange={false}
enableReinitialize={true}
onSubmit={onContinue}
initialValues={R.mergeWith(
chooseNotNull,
{
...nameOfReqDefaults,
...screen1InfoDefaults,
...screen2InfoDefaults,
...chooseTypeDefaults,
...typeFieldsDefaults
},
editingValues
)}
validationSchema={stepOptions.schema}>
initialValues={initialValues}
validationSchema={stepOptions.validationSchema}>
{({ errors }) => (
<Form className={classes.form} id={'custom-requirement-form'}>
<stepOptions.Component />

View file

@ -73,11 +73,12 @@ const Triggers = () => {
const [twilioSetupPopup, setTwilioSetupPopup] = useState(false)
const customInfoRequests =
R.path(['customInfoRequests'])(customInfoReqData) ?? []
const enabledCustomInfoRequests = R.filter(R.propEq('enabled', true))(
customInfoRequests
)
const enabledCustomInfoRequests = R.pipe(
R.path(['customInfoRequests']),
R.defaultTo([]),
R.filter(R.propEq('enabled', true))
)(customInfoReqData)
const emailAuth =
data?.config?.triggersConfig_customerAuthentication === 'EMAIL'

View file

@ -143,7 +143,6 @@ const Routes = () => {
<PrivateRoute path="/machines" component={Machines} />
<PrivateRoute path="/wizard" component={Wizard} />
<PublicRoute path="/register" component={Register} />
{/* <PublicRoute path="/configmigration" component={ConfigMigration} /> */}
<PublicRoute path="/login" restricted component={Login} />
<PublicRoute path="/resetpassword" component={ResetPassword} />
<PublicRoute path="/reset2fa" component={Reset2FA} />

49
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "lamassu-server",
"version": "10.0.7",
"version": "10.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -1105,6 +1105,11 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
},
"buffer-equals": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz",
"integrity": "sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA=="
}
}
},
@ -1302,6 +1307,28 @@
"superagent": "^3.8.3",
"tweetnacl": "^1.0.3",
"uuid": "^8.3.2"
},
"dependencies": {
"bitcoinjs-message": {
"version": "npm:@bitgo-forks/bitcoinjs-message@1.0.0-master.2",
"resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-message/-/bitcoinjs-message-1.0.0-master.2.tgz",
"integrity": "sha512-XSDGM3rA75vcDxeKqHPexika/TgWUFWdfKTv1lV8TZTb5XFHHD6ARckLdMOBiCf29eZSzbJQvF/OIWqNqMl/2A==",
"requires": {
"bech32": "^1.1.3",
"bs58check": "^2.1.2",
"buffer-equals": "^1.0.3",
"create-hash": "^1.1.2",
"secp256k1": "5.0.0",
"varuint-bitcoin": "^1.0.1"
},
"dependencies": {
"bech32": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
}
}
}
}
},
"@bitgo/sdk-lib-mpc": {
@ -6209,6 +6236,26 @@
}
}
},
"elliptic-sdk": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/elliptic-sdk/-/elliptic-sdk-0.7.2.tgz",
"integrity": "sha512-TMhcMmBGyuGe7GcDHEd2AnTPjq4G9+aYn7D93U9/r3fwqiD/WRCQLg63gzWdXAmsq9KnuE4bbRiFmyF6tItbZw==",
"requires": {
"axios": "^1.3.4"
},
"dependencies": {
"axios": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"requires": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
}
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",

View file

@ -2,7 +2,7 @@
"name": "lamassu-server",
"description": "bitcoin atm client server protocol module",
"keywords": [],
"version": "10.0.7",
"version": "10.1.0",
"license": "./LICENSE",
"author": "Lamassu (https://lamassu.is)",
"dependencies": {
@ -19,8 +19,8 @@
"@lamassu/coins": "v1.4.12",
"@simplewebauthn/server": "^3.0.0",
"@vonage/auth": "1.5.0",
"@vonage/sms": "1.7.0",
"@vonage/server-client": "1.7.0",
"@vonage/sms": "1.7.0",
"@vonage/vetch": "1.5.0",
"apollo-server-express": "2.25.1",
"argon2": "0.28.2",
@ -42,6 +42,7 @@
"date-fns-tz": "^1.1.6",
"dateformat": "^3.0.3",
"dotenv": "^16.0.0",
"elliptic-sdk": "^0.7.2",
"ethereumjs-tx": "^1.3.3",
"ethereumjs-util": "^5.2.0",
"ethereumjs-wallet": "^0.6.3",
@ -112,12 +113,14 @@
"lamassu-ofac-update": "./bin/lamassu-ofac-update",
"lamassu-send-coins": "./bin/lamassu-send-coins",
"lamassu-update-to-mnemonic": "./bin/lamassu-update-to-mnemonic",
"lamassu-btc-bumpfee": "./bin/lamassu-btc-bumpfee",
"lamassu-update-wallet-nodes": "./bin/lamassu-update-wallet-nodes",
"lamassu-configure-frontcamera": "./bin/lamassu-configure-frontcamera",
"lamassu-devices": "./bin/lamassu-devices",
"lamassu-operator": "./bin/lamassu-operator",
"lamassu-coinatmradar": "./bin/lamassu-coinatmradar",
"lamassu-eth-recovery": "./bin/lamassu-eth-recovery",
"lamassu-trx-recovery": "./bin/lamassu-trx-recovery",
"lamassu-update-cassettes": "./bin/lamassu-update-cassettes",
"lamassu-clean-parsed-id": "./bin/lamassu-clean-parsed-id"
},

View file

@ -90,6 +90,6 @@ rm /tmp/Lamassu_OP.csr.pem
mkdir -p $OFAC_DATA_DIR/sources
touch $OFAC_DATA_DIR/etags.json
node bin/scripts/build-dev-env.js
node tools/build-dev-env.js
echo "Done."