From 92de30de015dce1e3da7fd06d1c8ae703e687b9d Mon Sep 17 00:00:00 2001 From: Rafael Taranto Date: Mon, 19 Aug 2024 13:31:45 +0100 Subject: [PATCH 01/48] feat: elliptic wallet scoring --- lib/new-admin/config/accounts.js | 1 + .../wallet-scoring/elliptic/elliptic.js | 96 ++++++++++ .../src/pages/Services/schemas/elliptic.js | 64 +++++++ .../src/pages/Services/schemas/index.js | 2 + package-lock.json | 167 +++++++++++------- package.json | 3 +- tools/cert-gen.sh | 2 +- 7 files changed, 273 insertions(+), 62 deletions(-) create mode 100644 lib/plugins/wallet-scoring/elliptic/elliptic.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/elliptic.js diff --git a/lib/new-admin/config/accounts.js b/lib/new-admin/config/accounts.js index fdabccbd..8a2279d9 100644 --- a/lib/new-admin/config/accounts.js +++ b/lib/new-admin/config/accounts.js @@ -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 }, diff --git a/lib/plugins/wallet-scoring/elliptic/elliptic.js b/lib/plugins/wallet-scoring/elliptic/elliptic.js new file mode 100644 index 00000000..3bc3e3ff --- /dev/null +++ b/lib/plugins/wallet-scoring/elliptic/elliptic.js @@ -0,0 +1,96 @@ +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', + // TODO needs api to fetch blockchain name + blockchain: 'zcash' + }, + BCH: { + asset: 'BCH', + // TODO needs api to fetch blockchain name + blockchain: 'bitcoincash' + } +} + +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: 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 + + // normalize score to 0-10 where 0 is the lowest risk + // elliptic score can be null and contains decimals + return {score: Math.trunc((resScore || 0) / 10), isValid: (resScore || 0) < 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 +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/elliptic.js b/new-lamassu-admin/src/pages/Services/schemas/elliptic.js new file mode 100644 index 00000000..287a8fa6 --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/elliptic.js @@ -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 + ) + }) + } +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/index.js b/new-lamassu-admin/src/pages/Services/schemas/index.js index f1a1b53a..22368537 100644 --- a/new-lamassu-admin/src/pages/Services/schemas/index.js +++ b/new-lamassu-admin/src/pages/Services/schemas/index.js @@ -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, diff --git a/package-lock.json b/package-lock.json index a2fcb74d..06db5144 100644 --- a/package-lock.json +++ b/package-lock.json @@ -990,38 +990,6 @@ "secp256k1": "^4.0.2", "secrets.js-grempe": "^1.1.0", "superagent": "3.8.3" - }, - "dependencies": { - "bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "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": { - "secp256k1": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz", - "integrity": "sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==", - "requires": { - "elliptic": "^6.5.4", - "node-addon-api": "^5.0.0", - "node-gyp-build": "^4.2.0" - } - } - } - } } }, "@bitgo/sdk-coin-bch": { @@ -1137,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==" } } }, @@ -1230,6 +1203,40 @@ "fastpriorityqueue": "^0.7.1", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2" + }, + "dependencies": { + "bip174": { + "version": "npm:@bitgo-forks/bip174@3.1.0-master.4", + "resolved": "https://registry.npmjs.org/@bitgo-forks/bip174/-/bip174-3.1.0-master.4.tgz", + "integrity": "sha512-WDRNzPSdJGDqQNqfN+L5KHNHFDmNOPYnUnT7NkEkfHWn5m1jSOfcf8Swaslt5P0xcSDiERdN2gZxFc6XtOqRYg==" + }, + "bitcoinjs-lib": { + "version": "npm:@bitgo-forks/bitcoinjs-lib@7.1.0-master.7", + "resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-lib/-/bitcoinjs-lib-7.1.0-master.7.tgz", + "integrity": "sha512-FZle7954KnbbVXFCc5uYGtjq+0PFOnFxVchNwt3Kcv2nVusezTp29aeQwDi2Y+lM1dCoup2gJGXMkkREenY7KQ==", + "requires": { + "bech32": "^2.0.0", + "bip174": "npm:@bitgo-forks/bip174@3.1.0-master.4", + "bs58check": "^2.1.2", + "create-hash": "^1.1.0", + "fastpriorityqueue": "^0.7.1", + "json5": "^2.2.3", + "ripemd160": "^2.0.2", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2", + "wif": "^2.0.1" + } + }, + "ecpair": { + "version": "npm:@bitgo/ecpair@2.1.0-rc.0", + "resolved": "https://registry.npmjs.org/@bitgo/ecpair/-/ecpair-2.1.0-rc.0.tgz", + "integrity": "sha512-qPZetcEA1Lzzm9NsqsGF9NGorAGaXrv20eZjopLUjsdwftWcsYTE7lwzE/Xjdf4fcq6G4+vjrCudWAMGNfJqOQ==", + "requires": { + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" + } + } } }, "@bitgo/utxo-ord": { @@ -1300,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": { @@ -4289,11 +4318,6 @@ "safe-buffer": "^5.2.1" } }, - "bip174": { - "version": "npm:@bitgo-forks/bip174@3.1.0-master.4", - "resolved": "https://registry.npmjs.org/@bitgo-forks/bip174/-/bip174-3.1.0-master.4.tgz", - "integrity": "sha512-WDRNzPSdJGDqQNqfN+L5KHNHFDmNOPYnUnT7NkEkfHWn5m1jSOfcf8Swaslt5P0xcSDiERdN2gZxFc6XtOqRYg==" - }, "bip32": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bip32/-/bip32-3.1.0.tgz", @@ -4332,21 +4356,34 @@ "resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz", "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" }, - "bitcoinjs-lib": { - "version": "npm:@bitgo-forks/bitcoinjs-lib@7.1.0-master.7", - "resolved": "https://registry.npmjs.org/@bitgo-forks/bitcoinjs-lib/-/bitcoinjs-lib-7.1.0-master.7.tgz", - "integrity": "sha512-FZle7954KnbbVXFCc5uYGtjq+0PFOnFxVchNwt3Kcv2nVusezTp29aeQwDi2Y+lM1dCoup2gJGXMkkREenY7KQ==", + "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": "^2.0.0", - "bip174": "npm:@bitgo-forks/bip174@3.1.0-master.4", + "bech32": "^1.1.3", "bs58check": "^2.1.2", - "create-hash": "^1.1.0", - "fastpriorityqueue": "^0.7.1", - "json5": "^2.2.3", - "ripemd160": "^2.0.2", - "typeforce": "^1.11.3", - "varuint-bitcoin": "^1.1.2", - "wif": "^2.0.1" + "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==" + }, + "secp256k1": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz", + "integrity": "sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==", + "requires": { + "elliptic": "^6.5.4", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + } + } } }, "bitcore-lib": { @@ -6158,16 +6195,6 @@ "safe-buffer": "^5.0.1" } }, - "ecpair": { - "version": "npm:@bitgo/ecpair@2.1.0-rc.0", - "resolved": "https://registry.npmjs.org/@bitgo/ecpair/-/ecpair-2.1.0-rc.0.tgz", - "integrity": "sha512-qPZetcEA1Lzzm9NsqsGF9NGorAGaXrv20eZjopLUjsdwftWcsYTE7lwzE/Xjdf4fcq6G4+vjrCudWAMGNfJqOQ==", - "requires": { - "randombytes": "^2.1.0", - "typeforce": "^1.18.0", - "wif": "^2.0.6" - } - }, "ecurve": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz", @@ -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", diff --git a/package.json b/package.json index 89b99d97..9e109bb2 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/tools/cert-gen.sh b/tools/cert-gen.sh index 9d3dcd4d..103728b7 100755 --- a/tools/cert-gen.sh +++ b/tools/cert-gen.sh @@ -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." From d7d2b89556415b5a23e299d5127d5331767175fe Mon Sep 17 00:00:00 2001 From: Rafael Taranto Date: Tue, 27 Aug 2024 10:33:15 +0100 Subject: [PATCH 02/48] fix: normalize return from elliptic --- lib/plugins/wallet-scoring/elliptic/elliptic.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/plugins/wallet-scoring/elliptic/elliptic.js b/lib/plugins/wallet-scoring/elliptic/elliptic.js index 3bc3e3ff..ca17c11e 100644 --- a/lib/plugins/wallet-scoring/elliptic/elliptic.js +++ b/lib/plugins/wallet-scoring/elliptic/elliptic.js @@ -15,13 +15,11 @@ const HOLLISTIC_COINS = { const SINGLE_ASSET_COINS = { ZEC: { asset: 'ZEC', - // TODO needs api to fetch blockchain name blockchain: 'zcash' }, BCH: { asset: 'BCH', - // TODO needs api to fetch blockchain name - blockchain: 'bitcoincash' + blockchain: 'bitcoin_cash' } } @@ -47,7 +45,7 @@ function rate (account, objectType, cryptoCode, objectId) { subject: { asset: isHolistic ? 'holistic' : SINGLE_ASSET_COINS[cryptoCode].asset, blockchain: isHolistic ? 'holistic' : SINGLE_ASSET_COINS[cryptoCode].blockchain, - type: TYPE[objectType], + type: objectType, hash: objectId }, type: objectType === TYPE.ADDRESS ? 'wallet_exposure' : 'source_of_funds' @@ -61,9 +59,10 @@ function rate (account, objectType, cryptoCode, objectId) { .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: Math.trunc((resScore || 0) / 10), isValid: (resScore || 0) < threshold} + return {score: (resScore || 0) * 10, isValid: ((resScore || 0) * 100) < threshold} }) }) } From d619974de782b279665ea8a3b3c0083739da3248 Mon Sep 17 00:00:00 2001 From: Rafael Taranto Date: Tue, 27 Aug 2024 10:59:00 +0100 Subject: [PATCH 03/48] feat: pick wallet scoring without UI --- lib/new-settings-loader.js | 10 ++++++++++ lib/wallet-scoring.js | 23 ++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index 1b10db64..58a490cd 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -63,6 +63,16 @@ function saveAccounts (accounts) { return Promise.all([loadAccounts(), getOperatorId('middleware')]) .then(([currentAccounts, operatorId]) => { const newAccounts = _.merge(currentAccounts, accounts) + + // 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 => { 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 })])) diff --git a/lib/wallet-scoring.js b/lib/wallet-scoring.js index 516ac19b..9b426faf 100644 --- a/lib/wallet-scoring.js +++ b/lib/wallet-scoring.js @@ -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) { From ac2f9fdf00866f5dbc83c52a79d012d63c239dc6 Mon Sep 17 00:00:00 2001 From: siiky Date: Fri, 30 Aug 2024 17:37:31 +0100 Subject: [PATCH 04/48] fix: non-user-friendly error message on empty machine field --- .../src/pages/Notifications/sections/FiatBalanceOverrides.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js b/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js index 98ef8fb4..6769fc56 100644 --- a/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js +++ b/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js @@ -59,7 +59,7 @@ const FiatBalanceOverrides = ({ config, section }) => { } const initialValues = { - [MACHINE_KEY]: null, + [MACHINE_KEY]: '', [CASHBOX_KEY]: '', [CASSETTE_1_KEY]: '', [CASSETTE_2_KEY]: '', From c3c837f87dfe4ec31dfbd989b08c97715b4587cc Mon Sep 17 00:00:00 2001 From: siiky Date: Fri, 30 Aug 2024 18:07:26 +0100 Subject: [PATCH 05/48] refactor: destruct on arguments list --- .../src/pages/Triggers/CustomInfoRequests/Wizard.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js index 756ee865..d5886129 100644 --- a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js +++ b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js @@ -139,10 +139,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, From fc47944ac83eee48f2b7419850bd444748b7db3d Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 2 Sep 2024 12:48:48 +0100 Subject: [PATCH 06/48] refactor: make it obvious what it's doing --- new-lamassu-admin/src/pages/AddMachine/AddMachine.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/new-lamassu-admin/src/pages/AddMachine/AddMachine.js b/new-lamassu-admin/src/pages/AddMachine/AddMachine.js index c44d55c1..54d4ed1b 100644 --- a/new-lamassu-admin/src/pages/AddMachine/AddMachine.js +++ b/new-lamassu-admin/src/pages/AddMachine/AddMachine.js @@ -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) ) ) }) From f1566c1bcc9192c59e891abee5438134ad3444ee Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 2 Sep 2024 15:24:22 +0100 Subject: [PATCH 07/48] fix: give fields a user-friendly label --- .../CustomInfoRequests/Forms/ChooseType.js | 4 +++- .../Forms/Screen1Information.js | 8 +++++-- .../Forms/Screen2Information.js | 8 +++++-- .../Forms/TypeFields/index.js | 22 ++++++++++++++----- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/ChooseType.js b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/ChooseType.js index 3fc81127..2efeea83 100644 --- a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/ChooseType.js +++ b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/ChooseType.js @@ -65,7 +65,9 @@ const ChooseType = () => { } const validationSchema = Yup.object().shape({ - inputType: Yup.string().required() + inputType: Yup.string() + .label('Input type') + .required() }) const defaultValues = { diff --git a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/Screen1Information.js b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/Screen1Information.js index 85d0ceef..c7f0de6d 100644 --- a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/Screen1Information.js +++ b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/Screen1Information.js @@ -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 = { diff --git a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/Screen2Information.js b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/Screen2Information.js index 3b8957b3..0003dd1c 100644 --- a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/Screen2Information.js +++ b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/Screen2Information.js @@ -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 = { diff --git a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/TypeFields/index.js b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/TypeFields/index.js index a55345e8..2516d37e 100644 --- a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/TypeFields/index.js +++ b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/TypeFields/index.js @@ -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', From 08c87f4f18588f8e3efbddb82aaef9a6e7f9b9da Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 2 Sep 2024 16:26:43 +0100 Subject: [PATCH 08/48] refactor: simplify enabled custom info requests processing --- new-lamassu-admin/src/pages/Triggers/Triggers.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/new-lamassu-admin/src/pages/Triggers/Triggers.js b/new-lamassu-admin/src/pages/Triggers/Triggers.js index 2292de5a..07629699 100644 --- a/new-lamassu-admin/src/pages/Triggers/Triggers.js +++ b/new-lamassu-admin/src/pages/Triggers/Triggers.js @@ -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' From 9e5f027379fc824f6f675d13b457ac60b243c78f Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 2 Sep 2024 16:41:20 +0100 Subject: [PATCH 09/48] refactor: extract building of initial values --- .../Triggers/CustomInfoRequests/Wizard.js | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js index d5886129..086b2b6b 100644 --- a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js +++ b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js @@ -173,6 +173,14 @@ 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 ? R.filter(it => it.id !== toBeEdited.id, existingRequirements) @@ -201,6 +209,7 @@ const Wizard = ({ } const editingValues = isEditing ? makeEditingValues(toBeEdited) : {} + const initialValues = R.mergeWith(chooseNotNull, defaultValues, editingValues) const wizardTitle = isEditing ? 'Editing custom requirement' : 'New custom requirement' @@ -225,17 +234,7 @@ const Wizard = ({ validateOnChange={false} enableReinitialize={true} onSubmit={onContinue} - initialValues={R.mergeWith( - chooseNotNull, - { - ...nameOfReqDefaults, - ...screen1InfoDefaults, - ...screen2InfoDefaults, - ...chooseTypeDefaults, - ...typeFieldsDefaults - }, - editingValues - )} + initialValues={initialValues} validationSchema={stepOptions.schema}> {({ errors }) => (
From 87293cd21bd908d39fa40f00ea29e50d3d42faaa Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 2 Sep 2024 16:46:01 +0100 Subject: [PATCH 10/48] fix: `Formik` validation schema parameter --- .../booleanPropertiesTable/BooleanPropertiesTable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js b/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js index 29140c37..71c8a11c 100644 --- a/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js +++ b/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js @@ -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 ( From 185c620835b1d6320bbafb38b7613689dba2685e Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 2 Sep 2024 16:20:02 +0100 Subject: [PATCH 11/48] fix: missing name error message --- .../CustomInfoRequests/Forms/NameOfRequirement.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/NameOfRequirement.js b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/NameOfRequirement.js index 9450b432..a9ef4593 100644 --- a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/NameOfRequirement.js +++ b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Forms/NameOfRequirement.js @@ -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) ) ) }) From c840d49ba3a143ba4817d93f1e0df266bf4e9b68 Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 2 Sep 2024 16:56:34 +0100 Subject: [PATCH 12/48] refactor: simplify `chooseNotNull` --- .../src/pages/Triggers/CustomInfoRequests/Wizard.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js index 086b2b6b..346e8a7c 100644 --- a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js +++ b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js @@ -156,10 +156,7 @@ const makeEditingValues = ({ customRequest, id }) => { } } -const chooseNotNull = (a, b) => { - if (!R.isNil(b)) return b - return a -} +const chooseNotNull = (a, b) => (R.isNil(b) ? a : b) const Wizard = ({ onClose, From b368fbaeab73b80244222bbb493e592d512dd2bd Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 2 Sep 2024 17:03:39 +0100 Subject: [PATCH 13/48] refactor: `getStep()` --- .../Triggers/CustomInfoRequests/Wizard.js | 59 ++++++++----------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js index 346e8a7c..ddb020d9 100644 --- a/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js +++ b/new-lamassu-admin/src/pages/Triggers/CustomInfoRequests/Wizard.js @@ -53,39 +53,26 @@ const styles = { const useStyles = makeStyles(styles) -const getStep = (step, existingRequirements) => { - switch (step) { - case 1: - return { - schema: nameOfReqSchema(existingRequirements), - Component: NameOfRequirement - } - case 2: - return { - schema: screen1InfoSchema, - Component: Screen1Information - } - case 3: - return { schema: chooseTypeSchema, Component: ChooseType } - case 4: - return { - schema: screen2InfoSchema, - Component: Screen2Information - } - case 5: - return { - schema: typeFieldsValidationSchema, - Component: TypeFields - } - default: - return { - schema: {}, - Component: () => { - return

Default component step

- } - } - } -} +const getStep = (step, existingRequirements) => + [ + { + validationSchema: nameOfReqSchema(existingRequirements), + Component: NameOfRequirement + }, + { + validationSchema: screen1InfoSchema, + Component: Screen1Information + }, + { validationSchema: chooseTypeSchema, Component: ChooseType }, + { + validationSchema: screen2InfoSchema, + Component: Screen2Information + }, + { + validationSchema: typeFieldsValidationSchema, + Component: TypeFields + } + ][step - 1] const nonEmptyStr = obj => obj.text && obj.text.length @@ -179,10 +166,10 @@ const Wizard = ({ } // 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) => { @@ -232,7 +219,7 @@ const Wizard = ({ enableReinitialize={true} onSubmit={onContinue} initialValues={initialValues} - validationSchema={stepOptions.schema}> + validationSchema={stepOptions.validationSchema}> {({ errors }) => ( From f920ea5aa9259ff56d14b470713f8cb3f14a7ab5 Mon Sep 17 00:00:00 2001 From: Rafael Taranto Date: Fri, 13 Sep 2024 19:00:50 +0100 Subject: [PATCH 14/48] chore: add trx privkey recover script --- bin/lamassu-trx-recovery | 53 ++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 54 insertions(+) create mode 100644 bin/lamassu-trx-recovery diff --git a/bin/lamassu-trx-recovery b/bin/lamassu-trx-recovery new file mode 100644 index 00000000..f3d3bae5 --- /dev/null +++ b/bin/lamassu-trx-recovery @@ -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 ') + 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) diff --git a/package.json b/package.json index 397f0bd7..78d227f5 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "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" }, From 39e78a37a245f5460e91dd9acb14b1b53c110f62 Mon Sep 17 00:00:00 2001 From: siiky Date: Thu, 19 Sep 2024 15:23:10 +0100 Subject: [PATCH 15/48] fix: don't crash on customer page with bad dates --- .../src/pages/Customers/CustomerData.js | 14 +++--------- .../src/pages/Customers/helper.js | 22 ++++++++++++++----- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index 049c0227..b605eb07 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -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 ?? '' diff --git a/new-lamassu-admin/src/pages/Customers/helper.js b/new-lamassu-admin/src/pages/Customers/helper.js index 50a38bc1..42e38317 100644 --- a/new-lamassu-admin/src/pages/Customers/helper.js +++ b/new-lamassu-admin/src/pages/Customers/helper.js @@ -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 From 6f7746c564778dd6c60e2a97aafbb4ade274ae1b Mon Sep 17 00:00:00 2001 From: siiky Date: Thu, 12 Sep 2024 16:23:20 +0100 Subject: [PATCH 16/48] refactor: don't use parameter in DB query --- lib/plugins.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index fe6b5b79..5eb83f51 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -239,12 +239,12 @@ function plugins (settings, deviceId) { function fetchCurrentConfigVersion () { const sql = `select id from user_config - where type=$1 + where type='config' and valid order by id desc limit 1` - return db.one(sql, ['config']) + return db.one(sql) .then(row => row.id) } From 083a7764d2407410c90f2227072940fe0aa12d07 Mon Sep 17 00:00:00 2001 From: siiky Date: Thu, 12 Sep 2024 16:24:41 +0100 Subject: [PATCH 17/48] chore: query formatting --- lib/plugins.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 5eb83f51..bd7a1a92 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -238,14 +238,12 @@ function plugins (settings, deviceId) { } function 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) + 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) } function mapCoinSettings (coinParams) { From 446ac9f8dbe957be7568f239ea39a5b0b28e759b Mon Sep 17 00:00:00 2001 From: siiky Date: Thu, 12 Sep 2024 16:38:37 +0100 Subject: [PATCH 18/48] chore: SQL formatting --- lib/new-settings-loader.js | 73 +++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index 58a490cd..ec830209 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -56,9 +56,10 @@ 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 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)` + function saveAccounts (accounts) { return Promise.all([loadAccounts(), getOperatorId('middleware')]) .then(([currentAccounts, operatorId]) => { @@ -92,13 +93,13 @@ function resetAccounts (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'))) @@ -116,7 +117,7 @@ function showAccounts (schemaVersion) { }) } -const configSql = 'insert into user_config (type, data, valid, schema_version) values ($1, $2, $3, $4)' +const configSql = 'INSERT INTO user_config (type, data, valid, schema_version) VALUES ($1, $2, $3, $4)' function saveConfig (config) { return Promise.all([loadLatestConfigOrNone(), getOperatorId('middleware')]) .then(([currentConfig, operatorId]) => { @@ -170,13 +171,13 @@ 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 = $1 + AND schema_version = $2 + AND valid + ORDER BY id DESC + LIMIT 1` return db.one(sql, ['config', NEW_SETTINGS_LOADER_SCHEMA_VERSION]) .then(row => row ? row.data.config : {}) @@ -186,36 +187,36 @@ 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 = $1 + AND schema_version = $2 + ORDER BY id DESC + LIMIT 1` return db.oneOrNone(sql, ['config', 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 = $1 + AND schema_version = $2 + ORDER BY id DESC + LIMIT 1` return db.oneOrNone(sql, ['config', 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 = $2 + AND schema_version = $3 + AND valid` return db.one(sql, [versionId, 'config', NEW_SETTINGS_LOADER_SCHEMA_VERSION]) .then(row => row.data.config) From 19c7d73444e58618af9b9b45b36ae4bc08505d91 Mon Sep 17 00:00:00 2001 From: siiky Date: Thu, 12 Sep 2024 17:07:31 +0100 Subject: [PATCH 19/48] chore: remove dead code --- lib/config-migration.js | 6 +- .../graphql/resolvers/settings.resolver.js | 3 - lib/new-admin/graphql/types/settings.type.js | 3 - lib/new-settings-loader.js | 23 ------ .../src/pages/ConfigMigration.js | 82 ------------------- new-lamassu-admin/src/routing/routes.js | 1 - 6 files changed, 1 insertion(+), 117 deletions(-) delete mode 100644 new-lamassu-admin/src/pages/ConfigMigration.js diff --git a/lib/config-migration.js b/lib/config-migration.js index b6f16e10..493bc4ea 100644 --- a/lib/config-migration.js +++ b/lib/config-migration.js @@ -474,8 +474,4 @@ function migrate (config, accounts) { } } -module.exports = { - migrateConfig, - migrateAccounts, - migrate -} +module.exports = { migrate } diff --git a/lib/new-admin/graphql/resolvers/settings.resolver.js b/lib/new-admin/graphql/resolvers/settings.resolver.js index 383f9309..e0453511 100644 --- a/lib/new-admin/graphql/resolvers/settings.resolver.js +++ b/lib/new-admin/graphql/resolvers/settings.resolver.js @@ -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() } } diff --git a/lib/new-admin/graphql/types/settings.type.js b/lib/new-admin/graphql/types/settings.type.js index 3385a585..8079b18a 100644 --- a/lib/new-admin/graphql/types/settings.type.js +++ b/lib/new-admin/graphql/types/settings.type.js @@ -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 } ` diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index ec830209..eb9def63 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -80,17 +80,6 @@ function saveAccounts (accounts) { }).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 @@ -149,18 +138,6 @@ function migrationSaveConfig (config) { }) } -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]) => ({ diff --git a/new-lamassu-admin/src/pages/ConfigMigration.js b/new-lamassu-admin/src/pages/ConfigMigration.js deleted file mode 100644 index 6da89560..00000000 --- a/new-lamassu-admin/src/pages/ConfigMigration.js +++ /dev/null @@ -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 ( - <> - Config Migration - - - - - - - ) -} - -export default ConfigMigration diff --git a/new-lamassu-admin/src/routing/routes.js b/new-lamassu-admin/src/routing/routes.js index 18876686..78f5edcf 100644 --- a/new-lamassu-admin/src/routing/routes.js +++ b/new-lamassu-admin/src/routing/routes.js @@ -143,7 +143,6 @@ const Routes = () => { - {/* */} From ccca6c345ccbb28980bb823d2ed27e0f8aaf9835 Mon Sep 17 00:00:00 2001 From: siiky Date: Thu, 12 Sep 2024 17:08:06 +0100 Subject: [PATCH 20/48] chore: remove dead code --- lib/new-settings-loader.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index eb9def63..b9a5cefb 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -216,29 +216,15 @@ 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 - }) -} - module.exports = { saveConfig, migrationSaveConfig, - resetConfig, saveAccounts, - resetAccounts, loadAccounts, showAccounts, loadLatest, loadLatestConfig, loadLatestConfigOrNone, load, - migrate, removeFromConfig } From 47692f445680ff5ebb20f93637810a8c24dea672 Mon Sep 17 00:00:00 2001 From: siiky Date: Thu, 12 Sep 2024 17:08:15 +0100 Subject: [PATCH 21/48] chore: drop unused require --- migrations/1621430588944-notify-cashbox-removal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/1621430588944-notify-cashbox-removal.js b/migrations/1621430588944-notify-cashbox-removal.js index c8a7a21b..9f3d34e0 100644 --- a/migrations/1621430588944-notify-cashbox-removal.js +++ b/migrations/1621430588944-notify-cashbox-removal.js @@ -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 = [ From 6cf170303a6a35956e12f5f748cfdc2f21d2a1f7 Mon Sep 17 00:00:00 2001 From: siiky Date: Thu, 12 Sep 2024 17:23:56 +0100 Subject: [PATCH 22/48] refactor: extract duplicate code into its own function --- lib/new-settings-loader.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index b9a5cefb..9d4d5d50 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -106,15 +106,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]) + return db.tx(t => + insertConfigRow(t, { config: newConfig }) .then(() => t.none('NOTIFY $1:name, $2', ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })])) - }).catch(console.error) + ).catch(console.error) }) } @@ -122,10 +127,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]) + return db.tx(t => + insertConfigRow(t, { config: newConfig }) .then(() => t.none('NOTIFY $1:name, $2', ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })])) - }).catch(console.error) + ).catch(console.error) }) } @@ -133,7 +138,7 @@ 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) }) } From 3a548fea6f7e82be63196892d0e475e732b47b07 Mon Sep 17 00:00:00 2001 From: siiky Date: Thu, 12 Sep 2024 17:24:24 +0100 Subject: [PATCH 23/48] refactor: extract duplicate code into its own function --- lib/new-settings-loader.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index 9d4d5d50..199c2481 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -56,6 +56,12 @@ const addTermsHash = configs => { )(terms) } +const notifyReload = (dbOrTx, operatorId) => + dbOrTx.none( + 'NOTIFY $1:name, $2', + ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })] + ) + 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)` @@ -76,7 +82,7 @@ function saveAccounts (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 })])) + .then(() => notifyReload(t, operatorId)) }).catch(console.error) }) } @@ -118,7 +124,7 @@ function saveConfig (config) { const newConfig = addTermsHash(_.assign(currentConfig, config)) return db.tx(t => insertConfigRow(t, { config: newConfig }) - .then(() => t.none('NOTIFY $1:name, $2', ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })])) + .then(() => notifyReload(t, operatorId)) ).catch(console.error) }) } @@ -129,7 +135,7 @@ function removeFromConfig (fields) { const newConfig = _.omit(fields, currentConfig) return db.tx(t => insertConfigRow(t, { config: newConfig }) - .then(() => t.none('NOTIFY $1:name, $2', ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })])) + .then(() => notifyReload(t, operatorId)) ).catch(console.error) }) } From 437a1d3505259b5e6e314ba91bebcc869b9550ab Mon Sep 17 00:00:00 2001 From: siiky Date: Tue, 17 Sep 2024 14:57:30 +0100 Subject: [PATCH 24/48] refactor: remove unnecessary query parameters --- lib/new-settings-loader.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index 199c2481..229ed2dd 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -161,13 +161,13 @@ function loadLatest (schemaVersion) { function loadLatestConfig () { const sql = `SELECT data FROM user_config - WHERE type = $1 - AND schema_version = $2 + 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.one(sql, [NEW_SETTINGS_LOADER_SCHEMA_VERSION]) .then(row => row ? row.data.config : {}) .catch(err => { throw err @@ -177,24 +177,25 @@ function loadLatestConfig () { function loadLatestConfigOrNoneReturningVersion (schemaVersion) { const sql = `SELECT data, id FROM user_config - WHERE type = $1 - AND schema_version = $2 + 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 + 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 : {}) } @@ -202,11 +203,11 @@ function loadConfig (versionId) { const sql = `SELECT data FROM user_config WHERE id = $1 - AND type = $2 - AND schema_version = $3 + 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') { From 5342e9a8beb2afc6e29930132538efa247e9ebde Mon Sep 17 00:00:00 2001 From: siiky Date: Tue, 17 Sep 2024 14:58:58 +0100 Subject: [PATCH 25/48] fix: use more specific function `loadLatest()` returns a `Promise`, therefore accessing the `config` property is not correct. --- lib/machine-loader.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/machine-loader.js b/lib/machine-loader.js index fe08f15e..dcaa78b6 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -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, From cac88fda45779e4b356a8dec16aa44ffc0578a64 Mon Sep 17 00:00:00 2001 From: siiky Date: Tue, 17 Sep 2024 15:38:35 +0100 Subject: [PATCH 26/48] refactor: use `loadLatestConfig()` in place of `loadLatest()` where applicable --- lib/admin/settings-loader.js | 1 + lib/cash-in/cash-in-tx.js | 4 ++-- lib/notifier/index.js | 10 +++++----- .../1617742522808-zeroConfLimit-migrate.js | 4 ++-- migrations/1618507684019-rename-0-conf.js | 2 +- ...683-fiat-balance-notification-to-percent.js | 6 +++--- migrations/1620319260238-timezones.js | 4 ++-- ...1623975493095-add-crypto-units-to-config.js | 8 ++++---- .../1630432869178-add-more-cassette-support.js | 12 ++++++------ ...45010873828-add-advanced-wallet-settings.js | 18 ++++++++---------- migrations/1645459054117-default-timezone.js | 9 ++++----- .../1655807727853-default_timezone_fix.js | 9 ++++----- ...0716689-remove-coin-specific-cryptounits.js | 8 ++++---- .../1661125970289-eth-zero-conf-value.js | 9 ++++----- migrations/migrate-tools.js | 4 ++-- 15 files changed, 52 insertions(+), 56 deletions(-) diff --git a/lib/admin/settings-loader.js b/lib/admin/settings-loader.js index eccd677f..ef5b2f2c 100644 --- a/lib/admin/settings-loader.js +++ b/lib/admin/settings-loader.js @@ -240,6 +240,7 @@ module.exports = { loadRecentConfig, load, loadLatest, + loadLatestConfig, save, loadFixture, mergeValues, diff --git a/lib/cash-in/cash-in-tx.js b/lib/cash-in/cash-in-tx.js index 57ea8ccf..8f7703cb 100644 --- a/lib/cash-in/cash-in-tx.js +++ b/lib/cash-in/cash-in-tx.js @@ -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 diff --git a/lib/notifier/index.js b/lib/notifier/index.js index 8bad9a51..8e61f162 100644 --- a/lib/notifier/index.js +++ b/lib/notifier/index.js @@ -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) diff --git a/migrations/1617742522808-zeroConfLimit-migrate.js b/migrations/1617742522808-zeroConfLimit-migrate.js index 789c0656..4aa746a9 100644 --- a/migrations/1617742522808-zeroConfLimit-migrate.js +++ b/migrations/1617742522808-zeroConfLimit-migrate.js @@ -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) diff --git a/migrations/1618507684019-rename-0-conf.js b/migrations/1618507684019-rename-0-conf.js index 7a416239..79d7e437 100644 --- a/migrations/1618507684019-rename-0-conf.js +++ b/migrations/1618507684019-rename-0-conf.js @@ -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` diff --git a/migrations/1619968992683-fiat-balance-notification-to-percent.js b/migrations/1619968992683-fiat-balance-notification-to-percent.js index a3d6b500..ed0f6628 100644 --- a/migrations/1619968992683-fiat-balance-notification-to-percent.js +++ b/migrations/1619968992683-fiat-balance-notification-to-percent.js @@ -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 diff --git a/migrations/1620319260238-timezones.js b/migrations/1620319260238-timezones.js index c2d08484..789655b8 100644 --- a/migrations/1620319260238-timezones.js +++ b/migrations/1620319260238-timezones.js @@ -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) diff --git a/migrations/1623975493095-add-crypto-units-to-config.js b/migrations/1623975493095-add-crypto-units-to-config.js index 95d87c07..c251b2b1 100644 --- a/migrations/1623975493095-add-crypto-units-to-config.js +++ b/migrations/1623975493095-add-crypto-units-to-config.js @@ -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)) diff --git a/migrations/1630432869178-add-more-cassette-support.js b/migrations/1630432869178-add-more-cassette-support.js index 70c906fe..5c73a17b 100644 --- a/migrations/1630432869178-add-more-cassette-support.js +++ b/migrations/1630432869178-add-more-cassette-support.js @@ -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 diff --git a/migrations/1645010873828-add-advanced-wallet-settings.js b/migrations/1645010873828-add-advanced-wallet-settings.js index 34252cc6..1b2fad5d 100644 --- a/migrations/1645010873828-add-advanced-wallet-settings.js +++ b/migrations/1645010873828-add-advanced-wallet-settings.js @@ -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() - return saveConfig(newConfig) - }) + 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) diff --git a/migrations/1645459054117-default-timezone.js b/migrations/1645459054117-default-timezone.js index d2e64fb0..de8ef590 100644 --- a/migrations/1645459054117-default-timezone.js +++ b/migrations/1645459054117-default-timezone.js @@ -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) diff --git a/migrations/1655807727853-default_timezone_fix.js b/migrations/1655807727853-default_timezone_fix.js index fba11a26..6d2cc4a7 100644 --- a/migrations/1655807727853-default_timezone_fix.js +++ b/migrations/1655807727853-default_timezone_fix.js @@ -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) } }) diff --git a/migrations/1658940716689-remove-coin-specific-cryptounits.js b/migrations/1658940716689-remove-coin-specific-cryptounits.js index e91bca7c..d9bada35 100644 --- a/migrations/1658940716689-remove-coin-specific-cryptounits.js +++ b/migrations/1658940716689-remove-coin-specific-cryptounits.js @@ -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)) diff --git a/migrations/1661125970289-eth-zero-conf-value.js b/migrations/1661125970289-eth-zero-conf-value.js index 4376d67e..1dda9e4e 100644 --- a/migrations/1661125970289-eth-zero-conf-value.js +++ b/migrations/1661125970289-eth-zero-conf-value.js @@ -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) } }) diff --git a/migrations/migrate-tools.js b/migrations/migrate-tools.js index 06f5c463..0aa1f62c 100644 --- a/migrations/migrate-tools.js +++ b/migrations/migrate-tools.js @@ -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') } From d3d4042d97d641aeaf801a42bccfb69c0a3f1730 Mon Sep 17 00:00:00 2001 From: siiky Date: Tue, 17 Sep 2024 16:48:27 +0100 Subject: [PATCH 27/48] refactor: inline query parameters --- lib/new-settings-loader.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index 229ed2dd..70768037 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -62,11 +62,11 @@ const notifyReload = (dbOrTx, operatorId) => ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })] ) -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)` - 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) @@ -80,10 +80,10 @@ function saveAccounts (accounts) { newAccounts.elliptic.enabled = false } - return db.tx(t => { - return t.none(accountsSql, ['accounts', { accounts: newAccounts }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION]) + return db.tx(t => + t.none(accountsSql, [{ accounts: newAccounts }, NEW_SETTINGS_LOADER_SCHEMA_VERSION]) .then(() => notifyReload(t, operatorId)) - }).catch(console.error) + ).catch(console.error) }) } From bb440eedd6cb367c53f766921b79b20ed9f78b40 Mon Sep 17 00:00:00 2001 From: siiky Date: Tue, 17 Sep 2024 18:16:10 +0100 Subject: [PATCH 28/48] refactor: move `fetchCurrentConfigVersion()` to settings loader --- lib/graphql/resolvers.js | 4 ++-- lib/new-settings-loader.js | 12 +++++++++++- lib/plugins.js | 13 ++----------- lib/routes/termsAndConditionsRoutes.js | 15 +++++---------- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/lib/graphql/resolvers.js b/lib/graphql/resolvers.js index 9b158ccf..cf4f4da0 100644 --- a/lib/graphql/resolvers.js +++ b/lib/graphql/resolvers.js @@ -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) diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index 70768037..09cb3e6a 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -228,6 +228,15 @@ function load (versionId) { })) } +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, @@ -238,5 +247,6 @@ module.exports = { loadLatestConfig, loadLatestConfigOrNone, load, - removeFromConfig + removeFromConfig, + fetchCurrentConfigVersion, } diff --git a/lib/plugins.js b/lib/plugins.js index bd7a1a92..1abb0489 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -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') @@ -237,15 +238,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 = 'config' - AND valid - ORDER BY id DESC - LIMIT 1` - return db.one(sql).then(row => row.id) - } - function mapCoinSettings (coinParams) { const [ cryptoCode, cryptoNetwork ] = coinParams const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) @@ -287,7 +279,7 @@ function plugins (settings, deviceId) { return Promise.all([ buildAvailableCassettes(), buildAvailableRecyclers(), - fetchCurrentConfigVersion(), + settingsLoader.fetchCurrentConfigVersion(), millisecondsToMinutes(getTimezoneOffset(localeConfig.timezone)), loyalty.getNumberOfAvailablePromoCodes(), Promise.all(supportsBatchingPromise), @@ -1030,7 +1022,6 @@ function plugins (settings, deviceId) { sell, getNotificationConfig, notifyOperator, - fetchCurrentConfigVersion, pruneMachinesHeartbeat, rateAddress, isWalletScoringEnabled, diff --git a/lib/routes/termsAndConditionsRoutes.js b/lib/routes/termsAndConditionsRoutes.js index 5becd362..fef9f52d 100644 --- a/lib/routes/termsAndConditionsRoutes.js +++ b/lib/routes/termsAndConditionsRoutes.js @@ -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) } From ea9a373676830f8931ef6d30144f9066efb8d796 Mon Sep 17 00:00:00 2001 From: siiky Date: Thu, 19 Sep 2024 16:10:18 +0100 Subject: [PATCH 29/48] fix: `No data returned from the query` errors on migration --- lib/admin/settings-loader.js | 2 +- lib/new-settings-loader.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/admin/settings-loader.js b/lib/admin/settings-loader.js index ef5b2f2c..cb4bacf7 100644 --- a/lib/admin/settings-loader.js +++ b/lib/admin/settings-loader.js @@ -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 => { diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index 09cb3e6a..76cd120c 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -167,7 +167,7 @@ function loadLatestConfig () { ORDER BY id DESC LIMIT 1` - return db.one(sql, [NEW_SETTINGS_LOADER_SCHEMA_VERSION]) + return db.oneOrNone(sql, [NEW_SETTINGS_LOADER_SCHEMA_VERSION]) .then(row => row ? row.data.config : {}) .catch(err => { throw err From a6d875d8d384348428aece46dd6d5a546d913bc6 Mon Sep 17 00:00:00 2001 From: siiky Date: Wed, 2 Oct 2024 12:01:56 +0200 Subject: [PATCH 30/48] refactor: deprecate GraphQL's `Trigger.customInfoRequestId` --- lib/graphql/types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphql/types.js b/lib/graphql/types.js index 26622c37..8614a5c7 100644 --- a/lib/graphql/types.js +++ b/lib/graphql/types.js @@ -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 } From 1f8975d68611c322ca8b5565998a8476e2cade29 Mon Sep 17 00:00:00 2001 From: siiky Date: Fri, 27 Sep 2024 11:08:16 +0100 Subject: [PATCH 31/48] chore: deprecate `deviceId` from `MachineInfo` --- lib/graphql/types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphql/types.js b/lib/graphql/types.js index 26622c37..e2d8595e 100644 --- a/lib/graphql/types.js +++ b/lib/graphql/types.js @@ -29,7 +29,7 @@ type OperatorInfo { } type MachineInfo { - deviceId: String! + deviceId: String! @deprecated(reason: "unused by the machine") deviceName: String numberOfCassettes: Int numberOfRecyclers: Int From ae30f2e704c8237ead07f10a3b33ef734e7070ab Mon Sep 17 00:00:00 2001 From: Rafael Taranto Date: Tue, 15 Oct 2024 09:39:59 +0100 Subject: [PATCH 32/48] Update docker-build.yml --- .github/workflows/docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 6e0cc44b..691bb368 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -3,7 +3,7 @@ name: Docker Build and Publish on: push: branches: - - release-10.0 + - dev env: DOCKERHUB_SERVER_REPO: lamassu/lamassu-server @@ -59,4 +59,4 @@ jobs: - name: Move cache run: | rm -rf /tmp/.buildx-cache - mv /tmp/.buildx-cache-new /tmp/.buildx-cache \ No newline at end of file + mv /tmp/.buildx-cache-new /tmp/.buildx-cache From fdae38aed8ced75002b318f5209d4a3ac1c4358a Mon Sep 17 00:00:00 2001 From: siiky Date: Fri, 25 Oct 2024 16:07:02 +0100 Subject: [PATCH 33/48] feat: add request size to logs --- lib/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes.js b/lib/routes.js index 26a16c3d..6248fc8e 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -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) From f597c0e4f822a72c92ebbc88c419213e8cfd3bed Mon Sep 17 00:00:00 2001 From: siiky Date: Fri, 25 Oct 2024 18:01:04 +0100 Subject: [PATCH 34/48] chore: remove dead code --- lib/plugins.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 1abb0489..c56f996d 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -695,9 +695,6 @@ function plugins (settings, deviceId) { 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) From 7a7292d2faf83e1a93aabd6282aa93919085df43 Mon Sep 17 00:00:00 2001 From: siiky Date: Fri, 25 Oct 2024 17:53:26 +0100 Subject: [PATCH 35/48] refactor: deduplicate balance alerts checking code --- lib/plugins.js | 203 ++++++++++++++----------------------------------- 1 file changed, 55 insertions(+), 148 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index c56f996d..a97e1350 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -682,166 +682,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 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) - ? { - code: 'LOW_CASH_OUT', - cassette: 1, - machineName, - deviceId: device.deviceId, - notes: device.cashUnits.cassette1, - denomination: denomination1, - fiatCode - } - : null + const cashOutConfig = configManager.getCashOut(deviceId, settings.config) + const cashOutEnabled = cashOutConfig.active + const isUnitLow = (have, max, limit) => ((have / max) * 100) < limit - 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 + if (!cashOutEnabled) + return cashInAlerts - 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 cassetteCapacity = getCashUnitCapacity(device.model, 'cassette') + const cassetteAlerts = Array(Math.min(device.numberOfCassettes ?? 0, 4)) + .fill(null) + .flatMap((_elem, idx) => { + const nth = idx + 1 + const cassetteField = `cassette${nth}` + const notes = device.cashUnits[cassetteField] + const denomination = cashOutConfig[cassetteField] - 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 limit = notifications[`fillingPercentageCassette${nth}`] + return isUnitLow(notes, cassetteCapacity, limit) ? + [{ + code: 'LOW_CASH_OUT', + cassette: nth, + machineName, + deviceId, + notes, + denomination, + fiatCode + }] : + [] + }) - const recycler1Alert = device.numberOfRecyclers >= 1 && isUnitLow(device.cashUnits.recycler1, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler1) - ? { - code: 'LOW_RECYCLER_STACKER', - cassette: 4, - machineName, - deviceId: device.deviceId, - notes: device.cashUnits.recycler1, - denomination: denominationRecycler1, - fiatCode - } - : null + const recyclerCapacity = getCashUnitCapacity(device.model, 'recycler') + const recyclerAlerts = Array(Math.min(device.numberOfRecyclers ?? 0, 6)) + .fill(null) + .flatMap((_elem, idx) => { + const nth = idx + 1 + const recyclerField = `recycler${nth}` + const notes = device.cashUnits[recyclerField] + const denomination = cashOutConfig[recyclerField] - 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 limit = notifications[`fillingPercentageRecycler${nth}`] + return isUnitLow(notes, recyclerCapacity, limit) ? + [{ + code: 'LOW_RECYCLER_STACKER', + cassette: nth, // @see DETAIL_TEMPLATE in /lib/notifier/utils.js + machineName, + deviceId, + notes, + denomination, + fiatCode + }] : + [] + }) - 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) { From 5c39140ea88a8511c4e5b3c7d14f5a405decc65c Mon Sep 17 00:00:00 2001 From: siiky Date: Fri, 25 Oct 2024 18:09:06 +0100 Subject: [PATCH 36/48] refactor: replace magic constants --- lib/constants.js | 2 ++ lib/plugins.js | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/constants.js b/lib/constants.js index 60fcd128..07c2ad84 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -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, diff --git a/lib/plugins.js b/lib/plugins.js index a97e1350..84a62cf8 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -24,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') @@ -703,7 +709,7 @@ function plugins (settings, deviceId) { return cashInAlerts const cassetteCapacity = getCashUnitCapacity(device.model, 'cassette') - const cassetteAlerts = Array(Math.min(device.numberOfCassettes ?? 0, 4)) + const cassetteAlerts = Array(Math.min(device.numberOfCassettes ?? 0, CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES)) .fill(null) .flatMap((_elem, idx) => { const nth = idx + 1 @@ -726,7 +732,7 @@ function plugins (settings, deviceId) { }) const recyclerCapacity = getCashUnitCapacity(device.model, 'recycler') - const recyclerAlerts = Array(Math.min(device.numberOfRecyclers ?? 0, 6)) + const recyclerAlerts = Array(Math.min(device.numberOfRecyclers ?? 0, CASH_OUT_MAXIMUM_AMOUNT_OF_RECYCLERS)) .fill(null) .flatMap((_elem, idx) => { const nth = idx + 1 From 815fd36ede15c59ff11ac1c7ac2f2dc2b6a6bb76 Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 28 Oct 2024 16:26:10 +0000 Subject: [PATCH 37/48] refactor: use notification codes & explicitly test all possible codes --- lib/notifier/notificationCenter.js | 40 ++++++++++++++++++------------ 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/notifier/notificationCenter.js b/lib/notifier/notificationCenter.js index 06f6e0ca..b53ca4eb 100644 --- a/lib/notifier/notificationCenter.js +++ b/lib/notifier/notificationCenter.js @@ -2,20 +2,26 @@ const _ = require('lodash/fp') const queries = require('./queries') const utils = require('./utils') -const codes = require('./codes') const customers = require('../customers') +const { + NOTIFICATION_TYPES: { + SECURITY, + COMPLIANCE, + CRYPTO_BALANCE, + FIAT_BALANCE, + ERROR, + HIGH_VALUE_TX, + NORMAL_VALUE_TX + }, -const { NOTIFICATION_TYPES: { - SECURITY, - COMPLIANCE, - CRYPTO_BALANCE, - FIAT_BALANCE, - ERROR, - HIGH_VALUE_TX, - NORMAL_VALUE_TX } -} = codes + STALE, + PING, -const { STALE, PING } = codes + HIGH_CRYPTO_BALANCE, + LOW_CRYPTO_BALANCE, + CASH_BOX_FULL, + LOW_CASH_OUT, +} = require('./codes') const sanctionsNotify = (customer, phone) => { const code = 'SANCTIONS' @@ -71,9 +77,11 @@ 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 === 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 +113,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 +121,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]) const by = o => isCryptoCode(o) ? 'crypto' : isFiatCode(o) ? 'fiat' : From 1395448e78317504fd2ddfde14a17fce7c02a503 Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 28 Oct 2024 16:27:23 +0000 Subject: [PATCH 38/48] feat: add notif message for `LOW_RECYCLER_STACKER` --- lib/notifier/codes.js | 2 ++ lib/notifier/notificationCenter.js | 3 +++ 2 files changed, 5 insertions(+) diff --git a/lib/notifier/codes.js b/lib/notifier/codes.js index cb7da476..b0e02895 100644 --- a/lib/notifier/codes.js +++ b/lib/notifier/codes.js @@ -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, diff --git a/lib/notifier/notificationCenter.js b/lib/notifier/notificationCenter.js index b53ca4eb..5b143bdd 100644 --- a/lib/notifier/notificationCenter.js +++ b/lib/notifier/notificationCenter.js @@ -21,6 +21,7 @@ const { LOW_CRYPTO_BALANCE, CASH_BOX_FULL, LOW_CASH_OUT, + LOW_RECYCLER_STACKER, } = require('./codes') const sanctionsNotify = (customer, phone) => { @@ -79,6 +80,8 @@ const fiatBalancesNotify = (fiatWarnings) => { }, notInvalidated)) return const message = balance.code === LOW_CASH_OUT ? `Cash-out cassette ${balance.cassette} low or empty!` : + 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 */ From e7e104d6a01a82bf1c826bd83c95d070f1eb68e1 Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 28 Oct 2024 16:51:48 +0000 Subject: [PATCH 39/48] fix: recycler notifications --- lib/notifier/notificationCenter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/notifier/notificationCenter.js b/lib/notifier/notificationCenter.js index 5b143bdd..7f3480b0 100644 --- a/lib/notifier/notificationCenter.js +++ b/lib/notifier/notificationCenter.js @@ -125,7 +125,7 @@ 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 isFiatCode = c => _.includes(c, [LOW_CASH_OUT, CASH_BOX_FULL, LOW_RECYCLER_STACKER]) const by = o => isCryptoCode(o) ? 'crypto' : isFiatCode(o) ? 'fiat' : From 5ca6fc4cdf62533ba9003a1b444e63eb9f60ca1a Mon Sep 17 00:00:00 2001 From: siiky Date: Mon, 28 Oct 2024 17:35:30 +0000 Subject: [PATCH 40/48] fix: enable recycler notifs in email --- lib/notifier/email.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/notifier/email.js b/lib/notifier/email.js index 5ad4b774..c53df248 100644 --- a/lib/notifier/email.js +++ b/lib/notifier/email.js @@ -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}` } From f308b732d520affcdac178f3eb19962ca7e50060 Mon Sep 17 00:00:00 2001 From: siiky Date: Tue, 5 Nov 2024 16:01:45 +0000 Subject: [PATCH 41/48] chore: fix license --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index c5df9d5f..78c1fcb5 100644 --- a/LICENSE +++ b/LICENSE @@ -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. From b1b7c233448ed75e3eadf0a45adeaf9f51b5535f Mon Sep 17 00:00:00 2001 From: Rafael Date: Mon, 18 Nov 2024 14:36:13 +0000 Subject: [PATCH 42/48] fix: email authentication last used machine --- lib/routes/customerRoutes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js index 3a00fe6e..f9120b84 100644 --- a/lib/routes/customerRoutes.js +++ b/lib/routes/customerRoutes.js @@ -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 => { From ca52b93e50bf7171956ac9ea75077ecbafef5553 Mon Sep 17 00:00:00 2001 From: Rafael Date: Thu, 21 Nov 2024 10:28:34 +0000 Subject: [PATCH 43/48] fix: reduce amount of infura requests --- lib/plugins/wallet/infura/infura.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/plugins/wallet/infura/infura.js b/lib/plugins/wallet/infura/infura.js index 0b5d702b..5732bc2c 100644 --- a/lib/plugins/wallet/infura/infura.js +++ b/lib/plugins/wallet/infura/infura.js @@ -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 From ed1fb5252a3616a2bcaac42809a2bebb87912902 Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 3 Dec 2024 13:54:28 +0000 Subject: [PATCH 44/48] chore: bump version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index aef7e6ec..9ec3d0e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "10.0.6", + "version": "10.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 77c3504e..9d4511c6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "10.0.6", + "version": "10.1.0", "license": "./LICENSE", "author": "Lamassu (https://lamassu.is)", "dependencies": { From 9592d92bfdb22bbafd6af35493f3e5761a6fc003 Mon Sep 17 00:00:00 2001 From: CrypticaScriptura <7396812+CrypticaScriptura@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:25:09 -0500 Subject: [PATCH 45/48] chore: update wallet node releases --- lib/blockchain/common.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 8db73a72..5777d2ab 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -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']] } } From 46e34a78a2c3b0d4534d6509307f92a76b03388a Mon Sep 17 00:00:00 2001 From: Rafael Taranto Date: Fri, 26 Apr 2024 10:03:24 +0100 Subject: [PATCH 46/48] feat: add lamassu bumpfee script --- bin/lamassu-btc-bumpfee | 44 +++++++++++++++++++++++++ lib/blockexplorers/mempool.space.js | 24 +++++++++----- lib/plugins/wallet/bitcoind/bitcoind.js | 1 + 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 bin/lamassu-btc-bumpfee diff --git a/bin/lamassu-btc-bumpfee b/bin/lamassu-btc-bumpfee new file mode 100644 index 00000000..8bf8cd04 --- /dev/null +++ b/bin/lamassu-btc-bumpfee @@ -0,0 +1,44 @@ +#!/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 = 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.gt(value)) + .map(([key, value]) => ({name: `${key}: ${value} sat/vB`, value})), + }, + ]) + + await bitcoind.fetch('bumpfee', [txId, {fee_rate: selectedFee}]) + + console.log(`Fee bumped to ${selectedFee.toFixed(2)} sat/vB`) +} + +bumpTransactionFee(txId) \ No newline at end of file diff --git a/lib/blockexplorers/mempool.space.js b/lib/blockexplorers/mempool.space.js index 389d6727..383c1c72 100644 --- a/lib/blockexplorers/mempool.space.js +++ b/lib/blockexplorers/mempool.space.js @@ -1,8 +1,16 @@ -const axios = require("axios"); - -const getSatBEstimateFee = () => { - return axios.get('https://mempool.space/api/v1/fees/recommended') - .then(r => r.data.hourFee) -} - -module.exports = { getSatBEstimateFee } \ No newline at end of file +const axios = require("axios"); + +const getSatBEstimateFee = () => { + return axios.get('https://mempool.space/api/v1/fees/recommended') + .then(r => r.data.hourFee) +} + +const getSatBEstimateFees = () => { + return axios.get('https://mempool.space/api/v1/fees/recommended') + .then(r => r.data) +} + +module.exports = { + getSatBEstimateFees, + getSatBEstimateFee +} \ No newline at end of file diff --git a/lib/plugins/wallet/bitcoind/bitcoind.js b/lib/plugins/wallet/bitcoind/bitcoind.js index ad979a5c..ba5d17ae 100644 --- a/lib/plugins/wallet/bitcoind/bitcoind.js +++ b/lib/plugins/wallet/bitcoind/bitcoind.js @@ -219,5 +219,6 @@ module.exports = { sendCoinsBatch, checkBlockchainStatus, getTxHashesByAddress, + fetch, SUPPORTS_BATCHING } From 1e6f762c2c3507e30dfba6dbb7e59729321ff9fe Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 3 Dec 2024 17:16:52 +0000 Subject: [PATCH 47/48] fix: comparator, missing await, print new txid --- bin/lamassu-btc-bumpfee | 11 +++++++---- package.json | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bin/lamassu-btc-bumpfee b/bin/lamassu-btc-bumpfee index 8bf8cd04..bd17b3af 100644 --- a/bin/lamassu-btc-bumpfee +++ b/bin/lamassu-btc-bumpfee @@ -21,7 +21,7 @@ const bumpTransactionFee = async (txId) => { console.log(`Current fee: ${satPerVb.toFixed(2).toString()} sat/vB`) - const recommendedFees = mempool.getSatBEstimateFees() + const recommendedFees = await mempool.getSatBEstimateFees() console.log('Recommended fees (sat/vB):', recommendedFees) @@ -31,14 +31,17 @@ const bumpTransactionFee = async (txId) => { name: 'selectedFee', message: 'Select a fee higher than the current one:', choices: Object.entries(recommendedFees) - .filter(([_, value]) => satPerVb.gt(value)) + .filter(([_, value]) => satPerVb.lt(value)) .map(([key, value]) => ({name: `${key}: ${value} sat/vB`, value})), }, ]) - await bitcoind.fetch('bumpfee', [txId, {fee_rate: selectedFee}]) + const { txid } = await bitcoind.fetch('bumpfee', [txId, {fee_rate: selectedFee}]) - console.log(`Fee bumped to ${selectedFee.toFixed(2)} sat/vB`) + console.log(` +Fee bumped to ${selectedFee.toFixed(2)} sat/vB +Transaction ID: ${txid} +`) } bumpTransactionFee(txId) \ No newline at end of file diff --git a/package.json b/package.json index 9d4511c6..47798153 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "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-ofac-update-sources": "./bin/lamassu-ofac-update-sources", From 2ade6c21cc505d00a8fcbb530f0dab6a55ffc786 Mon Sep 17 00:00:00 2001 From: siiky Date: Tue, 17 Dec 2024 13:07:46 +0000 Subject: [PATCH 48/48] fix: serialize ETH `sendCoins()` --- lib/plugins/wallet/geth/base.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/plugins/wallet/geth/base.js b/lib/plugins/wallet/geth/base.js index bdf5bd73..8b9fa9dc 100644 --- a/lib/plugins/wallet/geth/base.js +++ b/lib/plugins/wallet/geth/base.js @@ -42,6 +42,10 @@ const SWEEP_QUEUE = new PQueue({ interval: 250, }) +const SEND_QUEUE = new PQueue({ + concurrency: 1, +}) + const infuraCalls = {} const pify = _function => { @@ -78,18 +82,20 @@ 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) - .then(pify(web3.eth.sendSignedTransaction)) - .then(txid => { - return pify(web3.eth.getTransaction)(txid) - .then(tx => { - if (!tx) return { txid } + 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) + .then(tx => { + if (!tx) return { txid } - const fee = new BN(tx.gas).times(new BN(tx.gasPrice)).decimalPlaces(0) + const fee = new BN(tx.gas).times(new BN(tx.gasPrice)).decimalPlaces(0) - return { txid, fee } - }) - }) + return { txid, fee } + }) + }) + ) } function checkCryptoCode (cryptoCode) {