From 825c9baa5d493e75fc1600f1a11d43e62b1a6ee1 Mon Sep 17 00:00:00 2001 From: naconner <7396812+naconner@users.noreply.github.com> Date: Sun, 8 Dec 2019 11:40:58 -0500 Subject: [PATCH 01/29] Update Infura credential naming --- schemas/infura.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/infura.json b/schemas/infura.json index 153a70fb..1c58f6f1 100644 --- a/schemas/infura.json +++ b/schemas/infura.json @@ -4,7 +4,7 @@ "fields": [ { "code": "apiKey", - "display": "API key", + "display": "Project ID", "fieldType": "string", "secret": true, "required": true, @@ -12,7 +12,7 @@ }, { "code": "apiSecret", - "display": "API secret", + "display": "Project secret", "fieldType": "password", "secret": true, "required": true, From 8e7b59bddc70de6f95c82ff9d74097bd0ea40189 Mon Sep 17 00:00:00 2001 From: naconner <7396812+naconner@users.noreply.github.com> Date: Wed, 18 Dec 2019 12:14:18 -0500 Subject: [PATCH 02/29] chore: version v7.4.6 --- 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 97093932..e206df6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "7.4.4", + "version": "7.4.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 96e13249..252e2a7e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "7.4.4", + "version": "7.4.6", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From 1d0252133475e1aa6b08553c1bfdf00a5ec822d0 Mon Sep 17 00:00:00 2001 From: naconner <7396812+naconner@users.noreply.github.com> Date: Tue, 31 Dec 2019 16:10:31 -0500 Subject: [PATCH 03/29] Bitcoin ABC 0.20.9 getbalance compatibility change --- lib/plugins/wallet/bitcoincashd/bitcoincashd.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js index e7c4e322..0a242791 100644 --- a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js +++ b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js @@ -27,7 +27,7 @@ function checkCryptoCode (cryptoCode) { function accountBalance (account, cryptoCode, confirmations) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['', confirmations])) + .then(() => fetch('getbalance', ['*', confirmations])) .then(r => BN(r).shift(unitScale).round()) } From 6b26e65d28f9523a69e62b741653b82f9b7908f4 Mon Sep 17 00:00:00 2001 From: naconner <7396812+naconner@users.noreply.github.com> Date: Tue, 31 Dec 2019 16:20:14 -0500 Subject: [PATCH 04/29] Update BTC, BCH, ETH, DASH --- lib/blockchain/common.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 6162ec90..1c021ae6 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -21,19 +21,19 @@ module.exports = { const BINARIES = { BTC: { - url: 'https://bitcoin.org/bin/bitcoin-core-0.18.1/bitcoin-0.18.1-x86_64-linux-gnu.tar.gz', - dir: 'bitcoin-0.18.1/bin' + url: 'https://bitcoin.org/bin/bitcoin-core-0.19.0.1/bitcoin-0.19.0.1-x86_64-linux-gnu.tar.gz', + dir: 'bitcoin-0.19.0.1/bin' }, ETH: { - url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.7-a718daa6.tar.gz', - dir: 'geth-linux-amd64-1.9.7-a718daa6' + url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.9-01744997.tar.gz', + dir: 'geth-linux-amd64-1.9.9-01744997' }, ZEC: { url: 'https://z.cash/downloads/zcash-2.1.0-1-linux64-debian-jessie.tar.gz', dir: 'zcash-2.1.0-1/bin' }, DASH: { - url: 'https://github.com/dashpay/dash/releases/download/v0.14.0.3/dashcore-0.14.0.3-x86_64-linux-gnu.tar.gz', + url: 'https://github.com/dashpay/dash/releases/download/v0.14.0.5/dashcore-0.14.0.5-x86_64-linux-gnu.tar.gz', dir: 'dashcore-0.14.0/bin' }, LTC: { @@ -41,8 +41,8 @@ const BINARIES = { dir: 'litecoin-0.17.1/bin' }, BCH: { - url: 'https://download.bitcoinabc.org/0.20.5/linux/bitcoin-abc-0.20.5-x86_64-linux-gnu.tar.gz', - dir: 'bitcoin-abc-0.20.5/bin', + url: 'https://download.bitcoinabc.org/0.20.9/linux/bitcoin-abc-0.20.9-x86_64-linux-gnu.tar.gz', + dir: 'bitcoin-abc-0.20.9/bin', files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']] } } From 8f5f40032c6be9a4b988daab120ab556f184737b Mon Sep 17 00:00:00 2001 From: naconner <7396812+naconner@users.noreply.github.com> Date: Wed, 15 Jan 2020 17:13:10 -0500 Subject: [PATCH 05/29] fix: downgrade BTC, BCH, LTC daemons BTC & LTC 0.17 and above change the account RPC such that unconfirmed deposits are not seen, and may affect availability of unconfirmed change. This happens on BCH 0.20.6 and above as well. Remain on older releases until new RPC calls are accounted for. --- lib/blockchain/common.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 1c021ae6..4224651f 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -21,8 +21,8 @@ module.exports = { const BINARIES = { BTC: { - url: 'https://bitcoin.org/bin/bitcoin-core-0.19.0.1/bitcoin-0.19.0.1-x86_64-linux-gnu.tar.gz', - dir: 'bitcoin-0.19.0.1/bin' + url: 'https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-x86_64-linux-gnu.tar.gz', + dir: 'bitcoin-0.16.3/bin' }, ETH: { url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.9-01744997.tar.gz', @@ -37,12 +37,12 @@ const BINARIES = { dir: 'dashcore-0.14.0/bin' }, LTC: { - url: 'https://download.litecoin.org/litecoin-0.17.1/linux/litecoin-0.17.1-x86_64-linux-gnu.tar.gz', - dir: 'litecoin-0.17.1/bin' + url: 'https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-x86_64-linux-gnu.tar.gz', + dir: 'litecoin-0.16.3/bin' }, BCH: { - url: 'https://download.bitcoinabc.org/0.20.9/linux/bitcoin-abc-0.20.9-x86_64-linux-gnu.tar.gz', - dir: 'bitcoin-abc-0.20.9/bin', + url: 'https://download.bitcoinabc.org/0.20.5/linux/bitcoin-abc-0.20.5-x86_64-linux-gnu.tar.gz', + dir: 'bitcoin-abc-0.20.5/bin', files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']] } } From 77b678e9912860f49b04f779c12612f7ce549376 Mon Sep 17 00:00:00 2001 From: naconner <7396812+naconner@users.noreply.github.com> Date: Tue, 21 Jan 2020 15:20:34 -0500 Subject: [PATCH 06/29] feat: add fixed fee reporting to CAR --- lib/coinatmradar/coinatmradar.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/coinatmradar/coinatmradar.js b/lib/coinatmradar/coinatmradar.js index 78acf983..1edddbd3 100644 --- a/lib/coinatmradar/coinatmradar.js +++ b/lib/coinatmradar/coinatmradar.js @@ -29,6 +29,7 @@ function mapCoin (info, deviceId, settings, cryptoCode) { const cashInFee = showRates ? cryptoConfig.cashInCommission / 100 : null const cashOutFee = showRates ? cryptoConfig.cashOutCommission / 100 : null + const cashInFixedFee = showRates ? cryptoConfig.cashInFee : null const cashInRate = showRates ? _.invoke('cashIn.toNumber', rates) : null const cashOutRate = showRates ? _.invoke('cashOut.toNumber', rates) : null @@ -36,6 +37,7 @@ function mapCoin (info, deviceId, settings, cryptoCode) { cryptoCode, cashInFee, cashOutFee, + cashInFixedFee, cashInRate, cashOutRate } From dc85afda13f5288f505d95229892da38bc09ce77 Mon Sep 17 00:00:00 2001 From: naconner <7396812+naconner@users.noreply.github.com> Date: Sun, 26 Jan 2020 11:32:04 -0500 Subject: [PATCH 07/29] chore: version v7.4.7 --- 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 e206df6e..bc6c81ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "7.4.6", + "version": "7.4.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 252e2a7e..201608fb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "7.4.6", + "version": "7.4.7", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From 22b4df54a04882895fe19f19bf6f669528facd53 Mon Sep 17 00:00:00 2001 From: Neal Conner <7396812+naconner@users.noreply.github.com> Date: Sat, 1 Feb 2020 19:00:44 -0500 Subject: [PATCH 08/29] chore: default to twilio and mailgun --- lamassu-schema.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lamassu-schema.json b/lamassu-schema.json index 3169522c..1ad84fa0 100644 --- a/lamassu-schema.json +++ b/lamassu-schema.json @@ -846,7 +846,8 @@ "notificationsSMSEnabled", "cashOutEnabled" ], - "fieldValidation": [{"code": "required"}] + "fieldValidation": [{"code": "required"}], + "default": "twilio" }, { "code": "email", @@ -857,7 +858,8 @@ "enabledIfAny": [ "notificationsEmailEnabled" ], - "fieldValidation": [{"code": "required"}] + "fieldValidation": [{"code": "required"}], + "default": "mailgun" }, { "code": "coinAtmRadarActive", From 203b1a33e0e1965a745f08461fffbed6fc9b03e4 Mon Sep 17 00:00:00 2001 From: naconner <7396812+naconner@users.noreply.github.com> Date: Thu, 27 Feb 2020 17:22:05 -0500 Subject: [PATCH 09/29] chore: version v7.4.8 --- 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 bc6c81ea..3d90570f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "7.4.7", + "version": "7.4.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 201608fb..5eac4578 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "7.4.7", + "version": "7.4.8", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From 47e5e953329ea957261934838264256f5f5a3c9f Mon Sep 17 00:00:00 2001 From: Taranto Date: Mon, 24 Feb 2020 09:56:01 +0000 Subject: [PATCH 10/29] fix: prevent trading on tx rollback --- lib/cash-out/cash-out-atomic.js | 6 +----- lib/cash-out/cash-out-low.js | 2 +- lib/cash-out/cash-out-tx.js | 12 +++++++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/cash-out/cash-out-atomic.js b/lib/cash-out/cash-out-atomic.js index 69854c19..f3365123 100644 --- a/lib/cash-out/cash-out-atomic.js +++ b/lib/cash-out/cash-out-atomic.js @@ -65,11 +65,7 @@ function preProcess (t, oldTx, newTx, pi) { .then(updatedTx => { if (updatedTx.status !== oldTx.status) { const isZeroConf = pi.isZeroConf(updatedTx) - if (wasJustAuthorized(oldTx, updatedTx, isZeroConf)) { - pi.sell(updatedTx) - pi.notifyOperator(updatedTx, { isRedemption: false }) - .catch((err) => logger.error('Failure sending transaction notification', err)) - } + updatedTx.justAuthorized = wasJustAuthorized(oldTx, updatedTx, isZeroConf) const rec = { to_address: updatedTx.toAddress, diff --git a/lib/cash-out/cash-out-low.js b/lib/cash-out/cash-out-low.js index b47f3c76..2f000c42 100644 --- a/lib/cash-out/cash-out-low.js +++ b/lib/cash-out/cash-out-low.js @@ -18,7 +18,7 @@ function upsert (t, oldTx, tx) { } return update(t, tx, diff(oldTx, tx)) - .then(newTx => [oldTx, newTx]) + .then(newTx => [oldTx, newTx, tx.justAuthorized]) } function insert (t, tx) { diff --git a/lib/cash-out/cash-out-tx.js b/lib/cash-out/cash-out-tx.js index 41ee2261..851f3a92 100644 --- a/lib/cash-out/cash-out-tx.js +++ b/lib/cash-out/cash-out-tx.js @@ -45,15 +45,21 @@ function selfPost (tx, pi) { function post (tx, pi, fromClient = true) { return db.tx(cashOutAtomic.atomic(tx, pi, fromClient)) .then(txVector => { - const [, newTx] = txVector - return postProcess(txVector, pi) + const [, newTx, justAuthorized] = txVector + return postProcess(txVector, justAuthorized, pi) .then(changes => cashOutLow.update(db, newTx, changes)) }) } -function postProcess (txVector, pi) { +function postProcess (txVector, justAuthorized, pi) { const [oldTx, newTx] = txVector + if (justAuthorized) { + pi.sell(newTx) + pi.notifyOperator(newTx, { isRedemption: false }) + .catch((err) => logger.error('Failure sending transaction notification', err)) + } + if ((newTx.dispense && !oldTx.dispense) || (newTx.redeem && !oldTx.redeem)) { return pi.buildAvailableCassettes(newTx.id) .then(cassettes => { From 1cdf29b1185b81a8313e51326e95363e5609c97b Mon Sep 17 00:00:00 2001 From: Neal Conner <7396812+naconner@users.noreply.github.com> Date: Thu, 20 Feb 2020 13:06:13 -0500 Subject: [PATCH 11/29] chore: update DASH, ZEC, ETH nodes; default to PrivateSend on DASH --- lib/blockchain/common.js | 12 ++++++------ lib/blockchain/dash.js | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 4224651f..15df7156 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -25,16 +25,16 @@ const BINARIES = { dir: 'bitcoin-0.16.3/bin' }, ETH: { - url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.9-01744997.tar.gz', - dir: 'geth-linux-amd64-1.9.9-01744997' + url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.11-6a62fe39.tar.gz', + dir: 'geth-linux-amd64-1.9.11-6a62fe39' }, ZEC: { - url: 'https://z.cash/downloads/zcash-2.1.0-1-linux64-debian-jessie.tar.gz', - dir: 'zcash-2.1.0-1/bin' + url: 'https://z.cash/downloads/zcash-2.1.1-1-linux64-debian-jessie.tar.gz', + dir: 'zcash-2.1.1-1/bin' }, DASH: { - url: 'https://github.com/dashpay/dash/releases/download/v0.14.0.5/dashcore-0.14.0.5-x86_64-linux-gnu.tar.gz', - dir: 'dashcore-0.14.0/bin' + url: 'https://github.com/dashpay/dash/releases/download/v0.15.0.0/dashcore-0.15.0.0-x86_64-linux-gnu.tar.gz', + dir: 'dashcore-0.15.0/bin' }, LTC: { url: 'https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-x86_64-linux-gnu.tar.gz', diff --git a/lib/blockchain/dash.js b/lib/blockchain/dash.js index 868734b3..8afb2f55 100644 --- a/lib/blockchain/dash.js +++ b/lib/blockchain/dash.js @@ -23,5 +23,7 @@ dbcache=500 keypool=10000 litemode=1 prune=4000 -txindex=0` +txindex=0 +enableprivatesend=1 +privatesendautostart=1` } From bf341476bbe418e75e4288557e21b6fa9c226720 Mon Sep 17 00:00:00 2001 From: naconner <7396812+naconner@users.noreply.github.com> Date: Wed, 15 Jan 2020 13:45:34 -0500 Subject: [PATCH 12/29] chore: change ETH language --- lib/blockchain/install.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/blockchain/install.js b/lib/blockchain/install.js index c4083f60..4ae45b0a 100644 --- a/lib/blockchain/install.js +++ b/lib/blockchain/install.js @@ -104,7 +104,7 @@ function run () { value: c.code, checked, disabled: c.cryptoCode === 'ETH' - ? 'Installed, use Infura option' + ? 'Use admin\'s Infura plugin' : checked && 'Installed' } }, cryptos) From fa69d2a030dea02299cd048d169bd2318257eb61 Mon Sep 17 00:00:00 2001 From: Taranto Date: Tue, 11 Feb 2020 21:06:30 +0000 Subject: [PATCH 13/29] hotfix: add fudge factor --- lamassu-admin-elm/src/NavBar.elm | 3 +- lamassu-schema.json | 17 + lib/cash-out/cash-out-helper.js | 23 +- lib/cash-out/cash-out-low.js | 3 +- lib/cash-out/cash-out-tx.js | 2 +- .../wallet/bitcoincashd/bitcoincashd.js | 8 +- lib/plugins/wallet/bitcoind/bitcoind.js | 8 +- lib/plugins/wallet/bitgo/bitgo.js | 8 +- lib/plugins/wallet/dashd/dashd.js | 8 +- lib/plugins/wallet/geth/base.js | 8 +- lib/plugins/wallet/litecoind/litecoind.js | 8 +- lib/plugins/wallet/mock-wallet/mock-wallet.js | 6 +- lib/plugins/wallet/zcashd/zcashd.js | 8 +- lib/tx.js | 4 +- lib/wallet.js | 9 +- .../1581455088977-add-amount-received.js | 13 + package-lock.json | 751 +++++++++++++----- public/elm.js | 12 +- 18 files changed, 650 insertions(+), 249 deletions(-) create mode 100644 migrations/1581455088977-add-amount-received.js diff --git a/lamassu-admin-elm/src/NavBar.elm b/lamassu-admin-elm/src/NavBar.elm index a0233995..ad94776a 100644 --- a/lamassu-admin-elm/src/NavBar.elm +++ b/lamassu-admin-elm/src/NavBar.elm @@ -156,7 +156,7 @@ determineConfigCategory : String -> Maybe Category determineConfigCategory configCode = if List.member configCode [ "definition", "setup", "cashOut", "commissions", "balanceAlerts" ] then Just MachineSettingsCat - else if List.member configCode [ "walletSettings", "notifications", "compliance", "coinAtmRadar", "terms", "operatorInfo" ] then + else if List.member configCode [ "walletSettings", "notifications", "compliance", "coinAtmRadar", "terms", "operatorInfo", "fudgeFactor" ] then Just GlobalSettingsCat else Nothing @@ -253,6 +253,7 @@ view route invalidGroups = , configLink "coinAtmRadar" "Coin ATM Radar" , configLink "terms" "Terms and Conditions" , configLink "operatorInfo" "Operator Info" + , configLink "fudgeFactor" "Fudge Factor" ] , ll ( "Third Party Services", AccountCat, AccountRoute "bitgo", True ) [ ( "BitGo", AccountRoute "bitgo", True ) diff --git a/lamassu-schema.json b/lamassu-schema.json index 1ad84fa0..2e41936a 100644 --- a/lamassu-schema.json +++ b/lamassu-schema.json @@ -147,6 +147,15 @@ "operatorInfoWebsite", "operatorInfoCompanyNumber" ] + }, + { + "code": "fudgeFactor", + "display": "Fudge Factor", + "cryptoScope": "global", + "machineScope": "global", + "fields": [ + "fudgeFactorActive" + ] } ], "fields": [ @@ -941,6 +950,14 @@ ], "fieldValidation": [] }, + { + "code": "fudgeFactorActive", + "displayBottom": "Enabled", + "fieldType": "onOff", + "fieldClass": null, + "fieldValidation": [], + "default": false + }, { "code": "operatorInfoActive", "displayBottom": "Info card enabled", diff --git a/lib/cash-out/cash-out-helper.js b/lib/cash-out/cash-out-helper.js index 7ce41156..8a3a37cb 100644 --- a/lib/cash-out/cash-out-helper.js +++ b/lib/cash-out/cash-out-helper.js @@ -11,14 +11,17 @@ module.exports = { redeemableTxs, toObj, toDb, REDEEMABLE_AGE } const mapValuesWithKey = _.mapValues.convert({cap: false}) function convertBigNumFields (obj) { - const convert = (value, key) => _.includes(key, [ - 'cryptoAtoms', - 'fiat', - 'commissionPercentage', - 'rawTickerPrice' - ]) - ? value.toString() - : value + const convert = (value, key) => { + if (_.includes(key, [ 'cryptoAtoms', 'receivedCryptoAtoms', 'fiat' ])) { + return value.toString() + } + + if (_.includes(key, [ 'commissionPercentage', 'rawTickerPrice' ]) && value) { + return value.toString() + } + + return value + } const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat']) ? key + '#' @@ -58,6 +61,10 @@ function toObj (row) { keys.forEach(key => { const objKey = _.camelCase(key) + if (key === 'received_crypto_atoms' && row[key]) { + newObj[objKey] = BN(row[key]) + return + } if (_.includes(key, ['crypto_atoms', 'fiat', 'commission_percentage', 'raw_ticker_price'])) { newObj[objKey] = BN(row[key]) return diff --git a/lib/cash-out/cash-out-low.js b/lib/cash-out/cash-out-low.js index 2f000c42..b5fdd22c 100644 --- a/lib/cash-out/cash-out-low.js +++ b/lib/cash-out/cash-out-low.js @@ -7,7 +7,8 @@ const toDb = helper.toDb const toObj = helper.toObj const UPDATEABLE_FIELDS = ['txHash', 'txVersion', 'status', 'dispense', 'dispenseConfirmed', - 'notified', 'redeem', 'phone', 'error', 'swept', 'publishedAt', 'confirmedAt', 'errorCode'] + 'notified', 'redeem', 'phone', 'error', 'swept', 'publishedAt', 'confirmedAt', 'errorCode', + 'receivedCryptoAtoms' ] module.exports = {upsert, update, insert} diff --git a/lib/cash-out/cash-out-tx.js b/lib/cash-out/cash-out-tx.js index 851f3a92..2a2ebac0 100644 --- a/lib/cash-out/cash-out-tx.js +++ b/lib/cash-out/cash-out-tx.js @@ -112,7 +112,7 @@ function processTxStatus (tx, settings) { const pi = plugins(settings, tx.deviceId) return pi.getStatus(tx) - .then(res => _.assign(tx, {status: res.status})) + .then(res => _.assign(tx, { receivedCryptoAtoms: res.receivedCryptoAtoms, status: res.status })) .then(_tx => selfPost(_tx, pi)) } diff --git a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js index 0a242791..0ef29ede 100644 --- a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js +++ b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js @@ -80,13 +80,13 @@ function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => confirmedBalance(toAddress, cryptoCode)) .then(confirmed => { - if (confirmed.gte(requested)) return {status: 'confirmed'} + if (confirmed.gte(requested)) return { receivedCryptoAtoms: confirmed, status: 'confirmed' } return pendingBalance(toAddress, cryptoCode) .then(pending => { - if (pending.gte(requested)) return {status: 'authorized'} - if (pending.gt(0)) return {status: 'insufficientFunds'} - return {status: 'notSeen'} + if (pending.gte(requested)) return { receivedCryptoAtoms: pending, status: 'authorized' } + if (pending.gt(0)) return { receivedCryptoAtoms: pending, status: 'insufficientFunds' } + return { receivedCryptoAtoms: pending, status: 'notSeen' } }) }) } diff --git a/lib/plugins/wallet/bitcoind/bitcoind.js b/lib/plugins/wallet/bitcoind/bitcoind.js index 68ed5a8d..a8fc91f2 100644 --- a/lib/plugins/wallet/bitcoind/bitcoind.js +++ b/lib/plugins/wallet/bitcoind/bitcoind.js @@ -80,13 +80,13 @@ function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => confirmedBalance(toAddress, cryptoCode)) .then(confirmed => { - if (confirmed.gte(requested)) return {status: 'confirmed'} + if (confirmed.gte(requested)) return { receivedCryptoAtoms: confirmed, status: 'confirmed' } return pendingBalance(toAddress, cryptoCode) .then(pending => { - if (pending.gte(requested)) return {status: 'authorized'} - if (pending.gt(0)) return {status: 'insufficientFunds'} - return {status: 'notSeen'} + if (pending.gte(requested)) return { receivedCryptoAtoms: pending, status: 'authorized' } + if (pending.gt(0)) return { receivedCryptoAtoms: pending, status: 'insufficientFunds' } + return { receivedCryptoAtoms: pending, status: 'notSeen' } }) }) } diff --git a/lib/plugins/wallet/bitgo/bitgo.js b/lib/plugins/wallet/bitgo/bitgo.js index 60820efd..26b1c9db 100644 --- a/lib/plugins/wallet/bitgo/bitgo.js +++ b/lib/plugins/wallet/bitgo/bitgo.js @@ -124,10 +124,10 @@ function getStatus (account, toAddress, requested, cryptoCode) { const confirmed = _.compose(sum, toBn, filterConfirmed)(transfers) const pending = _.compose(sum, toBn, filterPending)(transfers) - if (confirmed.gte(requested)) return { status: 'confirmed' } - if (pending.gte(requested)) return { status: 'authorized' } - if (pending.gt(0)) return { status: 'insufficientFunds' } - return { status: 'notSeen' } + if (confirmed.gte(requested)) return { receivedCryptoAtoms: confirmed, status: 'confirmed' } + if (pending.gte(requested)) return { receivedCryptoAtoms: pending, status: 'authorized' } + if (pending.gt(0)) return { receivedCryptoAtoms: pending, status: 'insufficientFunds' } + return { receivedCryptoAtoms: pending, status: 'notSeen' } }) } diff --git a/lib/plugins/wallet/dashd/dashd.js b/lib/plugins/wallet/dashd/dashd.js index 8fec7587..4d664269 100644 --- a/lib/plugins/wallet/dashd/dashd.js +++ b/lib/plugins/wallet/dashd/dashd.js @@ -81,13 +81,13 @@ function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => confirmedBalance(toAddress, cryptoCode)) .then(confirmed => { - if (confirmed.gte(requested)) return {status: 'confirmed'} + if (confirmed.gte(requested)) return { receivedCryptoAtoms: confirmed, status: 'confirmed' } return pendingBalance(toAddress, cryptoCode) .then(pending => { - if (pending.gte(requested)) return {status: 'authorized'} - if (pending.gt(0)) return {status: 'insufficientFunds'} - return {status: 'notSeen'} + if (pending.gte(requested)) return { receivedCryptoAtoms: pending, status: 'authorized' } + if (pending.gt(0)) return { receivedCryptoAtoms: pending, status: 'insufficientFunds' } + return { receivedCryptoAtoms: pending, status: 'notSeen' } }) }) } diff --git a/lib/plugins/wallet/geth/base.js b/lib/plugins/wallet/geth/base.js index 381bf90d..0e8b8dfa 100644 --- a/lib/plugins/wallet/geth/base.js +++ b/lib/plugins/wallet/geth/base.js @@ -158,13 +158,13 @@ function getStatus (account, toAddress, cryptoAtoms, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => confirmedBalance(toAddress)) .then(confirmed => { - if (confirmed.gte(cryptoAtoms)) return {status: 'confirmed'} + if (confirmed.gte(cryptoAtoms)) return { receivedCryptoAtoms: confirmed, status: 'confirmed' } return pendingBalance(toAddress) .then(pending => { - if (pending.gte(cryptoAtoms)) return {status: 'published'} - if (pending.gt(0)) return {status: 'insufficientFunds'} - return {status: 'notSeen'} + if (pending.gte(cryptoAtoms)) return { receivedCryptoAtoms: pending, status: 'published' } + if (pending.gt(0)) return { receivedCryptoAtoms: pending, status: 'insufficientFunds' } + return { receivedCryptoAtoms: pending, status: 'notSeen' } }) }) } diff --git a/lib/plugins/wallet/litecoind/litecoind.js b/lib/plugins/wallet/litecoind/litecoind.js index 1c8dba56..fc4c9bce 100644 --- a/lib/plugins/wallet/litecoind/litecoind.js +++ b/lib/plugins/wallet/litecoind/litecoind.js @@ -81,13 +81,13 @@ function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => confirmedBalance(toAddress, cryptoCode)) .then(confirmed => { - if (confirmed.gte(requested)) return {status: 'confirmed'} + if (confirmed.gte(requested)) return { receivedCryptoAtoms: confirmed, status: 'confirmed' } return pendingBalance(toAddress, cryptoCode) .then(pending => { - if (pending.gte(requested)) return {status: 'authorized'} - if (pending.gt(0)) return {status: 'insufficientFunds'} - return {status: 'notSeen'} + if (pending.gte(requested)) return { receivedCryptoAtoms: pending, status: 'authorized' } + if (pending.gt(0)) return { receivedCryptoAtoms: pending, status: 'insufficientFunds' } + return { receivedCryptoAtoms: pending, status: 'notSeen' } }) }) } diff --git a/lib/plugins/wallet/mock-wallet/mock-wallet.js b/lib/plugins/wallet/mock-wallet/mock-wallet.js index 2a15e7b0..eea9b3b4 100644 --- a/lib/plugins/wallet/mock-wallet/mock-wallet.js +++ b/lib/plugins/wallet/mock-wallet/mock-wallet.js @@ -79,9 +79,9 @@ function newFunding (account, cryptoCode) { function getStatus (account, toAddress, cryptoAtoms, cryptoCode) { const elapsed = Date.now() - t0 - if (elapsed < PUBLISH_TIME) return Promise.resolve({status: 'notSeen'}) - if (elapsed < AUTHORIZE_TIME) return Promise.resolve({status: 'published'}) - if (elapsed < CONFIRM_TIME) return Promise.resolve({status: 'authorized'}) + if (elapsed < PUBLISH_TIME) return Promise.resolve({ receivedCryptoAtoms: cryptoAtoms - 10, status: 'notSeen' }) + if (elapsed < AUTHORIZE_TIME) return Promise.resolve({ receivedCryptoAtoms: cryptoAtoms - 10, status: 'published' }) + if (elapsed < CONFIRM_TIME) return Promise.resolve({ receivedCryptoAtoms: null, status: 'authorized' }) console.log('[%s] DEBUG: Mock wallet has confirmed transaction [%s]', cryptoCode, toAddress.slice(0, 5)) diff --git a/lib/plugins/wallet/zcashd/zcashd.js b/lib/plugins/wallet/zcashd/zcashd.js index 25618fe7..1f9e7656 100644 --- a/lib/plugins/wallet/zcashd/zcashd.js +++ b/lib/plugins/wallet/zcashd/zcashd.js @@ -81,13 +81,13 @@ function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => confirmedBalance(toAddress, cryptoCode)) .then(confirmed => { - if (confirmed.gte(requested)) return {status: 'confirmed'} + if (confirmed.gte(requested)) return { receivedCryptoAtoms: confirmed, status: 'confirmed' } return pendingBalance(toAddress, cryptoCode) .then(pending => { - if (pending.gte(requested)) return {status: 'authorized'} - if (pending.gt(0)) return {status: 'insufficientFunds'} - return {status: 'notSeen'} + if (pending.gte(requested)) return { receivedCryptoAtoms: pending, status: 'authorized' } + if (pending.gt(0)) return { receivedCryptoAtoms: pending, status: 'insufficientFunds' } + return { receivedCryptoAtoms: pending, status: 'notSeen' } }) }) } diff --git a/lib/tx.js b/lib/tx.js index 32eb3366..9d290ce5 100644 --- a/lib/tx.js +++ b/lib/tx.js @@ -29,13 +29,13 @@ function massage (tx, pi) { cashInFee: BN(r.cashInFee), cashInFeeCrypto: BN(r.cashInFeeCrypto), commissionPercentage: BN(r.commissionPercentage), - rawTickerPrice: BN(r.rawTickerPrice), + rawTickerPrice: r.rawTickerPrice ? BN(r.rawTickerPrice) : null, minimumTx: BN(r.minimumTx) } : { cryptoAtoms: BN(r.cryptoAtoms), fiat: BN(r.fiat), - rawTickerPrice: BN(r.rawTickerPrice), + rawTickerPrice: r.rawTickerPrice ? BN(r.rawTickerPrice) : null, commissionPercentage: BN(r.commissionPercentage) } diff --git a/lib/wallet.js b/lib/wallet.js index 0e069f8c..4fc8e0de 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -104,7 +104,7 @@ function mergeStatus (a, b) { if (!a) return b if (!b) return a - return { status: mergeStatusMode(a.status, b.status) } + return { receivedCryptoAtoms: a.receivedCryptoAtoms, status: mergeStatusMode(a.status, b.status) } } function mergeStatusMode (a, b) { @@ -122,8 +122,11 @@ function mergeStatusMode (a, b) { } function getWalletStatus (settings, tx) { + const fudgeFactorEnabled = configManager.unscoped(settings.config).fudgeFactor + const fudgeFactor = fudgeFactorEnabled ? 10 : 0 + const walletStatusPromise = fetchWallet(settings, tx.cryptoCode) - .then(r => r.wallet.getStatus(r.account, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode)) + .then(r => r.wallet.getStatus(r.account, tx.toAddress, tx.cryptoAtoms.add(fudgeFactor), tx.cryptoCode)) return Promise.all([ walletStatusPromise, @@ -170,7 +173,7 @@ function getStatus (settings, tx, machineId) { const status = isAuthorized ? 'authorized' : unauthorizedStatus - return { status } + return { receivedCryptoAtoms: statusRec.receivedCryptoAtoms, status } }) } diff --git a/migrations/1581455088977-add-amount-received.js b/migrations/1581455088977-add-amount-received.js new file mode 100644 index 00000000..89c27426 --- /dev/null +++ b/migrations/1581455088977-add-amount-received.js @@ -0,0 +1,13 @@ +const db = require('./db') + +exports.up = function (next) { + var sql = [ + 'ALTER TABLE cash_out_txs ADD COLUMN received_crypto_atoms numeric(30) null DEFAULT null' + ] + + db.multi(sql, next) +} + +exports.down = function (next) { + next() +} diff --git a/package-lock.json b/package-lock.json index 3d90570f..6bb7963b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,14 +76,14 @@ "integrity": "sha1-lksiHLlV4csPBEdnqWaCgCOjhLs=" }, "@types/lodash": { - "version": "4.14.118", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.118.tgz", - "integrity": "sha512-iiJbKLZbhSa6FYRip/9ZDX6HXhayXLDGY2Fqws9cOkEQ6XeKfaxB0sC541mowZJueYyMnVUmmG+al5/4fCDrgw==" + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==" }, "@types/node": { - "version": "10.12.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.9.tgz", - "integrity": "sha512-eajkMXG812/w3w4a1OcBlaTwsFPO5F7fJ/amy+tieQxEMWBlbV1JGSjkFM+zkHNf81Cad+dfIRA+IBkvmvdAeA==" + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz", + "integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==" }, "@types/ws": { "version": "3.2.1", @@ -132,9 +132,9 @@ "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "requires": { "es6-promisify": "^5.0.0" } @@ -388,9 +388,9 @@ "dev": true }, "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, "asynckit": { "version": "0.4.0", @@ -1252,9 +1252,9 @@ } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" } } }, @@ -1289,9 +1289,9 @@ } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" } } }, @@ -1372,58 +1372,63 @@ } }, "bitgo": { - "version": "4.40.2", - "resolved": "https://registry.npmjs.org/bitgo/-/bitgo-4.40.2.tgz", - "integrity": "sha512-rQY92xdUaI1yKGyBmlAk6/uoLUj4H13Jla0g+q8Hmw30rniX1QUF7Kfwe7KsmIb/JnVse3Kt5omqg6PYQQIl2A==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/bitgo/-/bitgo-4.47.0.tgz", + "integrity": "sha512-reh8M5UMMRLnUzWo0qY+SyDQmM+LNXnVjwb4S78r2lukdszmSHX+JWl2T7t5hyelJyFXF/5ySAxj6SYpHTiChQ==", "requires": { - "argparse": "~1.0.10", + "argparse": "1.0.10", "assert": "0.4.9", - "big.js": "~3.1.3", + "big.js": "3.1.3", "bigi": "1.4.0", - "bignumber.js": "~4.0.2", - "bitcoinjs-message": "~2.0.0", - "bitgo-utxo-lib": "^1.2.1", - "bluebird": "~3.5.0", - "body-parser": "~1.18.2", + "bignumber.js": "8.0.1", + "bitcoinjs-message": "2.0.0", + "bitgo-utxo-lib": "1.3.0", + "bluebird": "3.5.3", + "body-parser": "1.18.3", "bs58": "2.0.1", "bs58check": "1.0.4", "cashaddress": "1.1.0", - "create-hmac": "~1.1.4", - "debug": "~3.1.0", - "ecurve": "~1.0.2", + "create-hmac": "1.1.7", + "debug": "3.1.0", + "ecurve": "1.0.6", "eol": "0.5.0", - "ethereumjs-abi": "~0.6.2", - "ethereumjs-tx": "^1.3.4", - "ethereumjs-util": "~4.4.1", - "express": "~4.16.2", + "ethereumjs-abi": "0.6.5", + "ethereumjs-tx": "1.3.7", + "ethereumjs-util": "4.4.1", + "express": "4.16.4", "http-proxy": "1.11.1", - "lodash": "~4.17.10", + "lodash": "4.17.11", "minimist": "0.2.0", - "moment": "~2.20.1", - "morgan": "1.9.0", - "prova-lib": "0.2.9", - "ripple-lib": "~0.22.0", + "moment": "2.20.1", + "morgan": "1.9.1", + "prova-lib": "0.2.10", + "ripple-lib": "0.22.0", "sanitize-html": "1.13.0", - "secp256k1": "3.2.5", + "secp256k1": "3.6.1", "secrets.js-grempe": "1.1.0", - "stellar-sdk": "~0.11.0", - "superagent": "~3.8.3", - "superagent-proxy": "~1.0.0" + "stellar-sdk": "0.11.0", + "superagent": "3.8.3", + "superagent-proxy": "1.0.3" }, "dependencies": { "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" + "mime-types": "~2.1.24", + "negotiator": "0.6.2" } }, "bignumber.js": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.0.4.tgz", - "integrity": "sha512-LDXpJKVzEx2/OqNbG9mXBNvHuiRL4PzHCGfnANHMJ+fv68Ads3exDVJeGDJws+AoNEuca93bU3q+S0woeUaCdg==" + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-8.0.1.tgz", + "integrity": "sha512-zAySveTJXkgLYCBi0b14xzfnOs+f3G6x36I8w2a1+PFQpWk/dp0mI0F+ZZK2bu+3ELewDcSyP+Cfq++NcHX7sg==" + }, + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" }, "body-parser": { "version": "1.18.3", @@ -1462,6 +1467,19 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -1584,7 +1602,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "requires": { "debug": "2.6.9", @@ -1618,7 +1636,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { "depd": "~1.1.2", @@ -1635,17 +1653,22 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" }, "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", "requires": { - "mime-db": "~1.37.0" + "mime-db": "1.43.0" } }, "minimist": { @@ -1659,13 +1682,13 @@ "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" }, "morgan": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", - "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", "requires": { "basic-auth": "~2.0.0", "debug": "2.6.9", - "depd": "~1.1.1", + "depd": "~1.1.2", "on-finished": "~2.3.0", "on-headers": "~1.0.1" }, @@ -1680,10 +1703,15 @@ } } }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "qs": { "version": "6.5.2", @@ -1701,6 +1729,22 @@ "unpipe": "1.0.0" } }, + "secp256k1": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.6.1.tgz", + "integrity": "sha512-utLpWv4P4agEw7hakR73wlWX0NBmC5t/vkJ0TAfTyvETAUzo0tm6aFKPYetVYRaVubxMeWm5Ekv9ETwOgcDCqw==", + "optional": true, + "requires": { + "bindings": "^1.2.1", + "bip66": "^1.1.3", + "bn.js": "^4.11.3", + "create-hash": "^1.1.2", + "drbg.js": "^1.0.1", + "elliptic": "^6.2.3", + "nan": "^2.2.1", + "safe-buffer": "^5.1.0" + } + }, "serve-static": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", @@ -1723,12 +1767,12 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.18" + "mime-types": "~2.1.24" } }, "utils-merge": { @@ -1744,9 +1788,9 @@ } }, "bitgo-utxo-lib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bitgo-utxo-lib/-/bitgo-utxo-lib-1.2.1.tgz", - "integrity": "sha512-8qwcU4HZEGAInXYntkCiZwfeFFQ6cwWOVb8+VwEMnoQb+TCNIRh1BFxfJ79ZHrzSGZ+L951HqdzKW5XhFb9E6Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bitgo-utxo-lib/-/bitgo-utxo-lib-1.3.0.tgz", + "integrity": "sha512-jry6Y97g2jr1XlY1Ou5WkWUg1rQbyioLYp9AgyK4OwZ7t2ETRyTB1rcJV9kYOlTnTjC6k+Sef+IKcZLM/u9zjA==", "requires": { "bech32": "0.0.3", "bigi": "^1.4.0", @@ -1762,11 +1806,26 @@ "pushdata-bitcoin": "^1.0.1", "randombytes": "^2.0.1", "safe-buffer": "^5.0.1", + "secp256k1": "^3.5.2", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.0.4", "wif": "^2.0.1" }, "dependencies": { + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, "bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", @@ -1786,9 +1845,9 @@ }, "dependencies": { "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" } } }, @@ -1799,6 +1858,70 @@ "requires": { "ms": "2.0.0" } + }, + "elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "optional": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "optional": true + }, + "secp256k1": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.8.0.tgz", + "integrity": "sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.5.2", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "optional": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "optional": true + } + } } } }, @@ -1841,7 +1964,8 @@ "bluebird": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", - "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=", + "dev": true }, "bn.js": { "version": "4.11.7", @@ -2938,7 +3062,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -3043,25 +3166,30 @@ } }, "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", "requires": { - "domelementtype": "~1.1.1", - "entities": "~1.1.1" + "domelementtype": "^2.0.1", + "entities": "^2.0.0" }, "dependencies": { "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" + }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" } } }, "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" }, "domhandler": { "version": "2.4.2", @@ -4546,9 +4674,9 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -4556,9 +4684,9 @@ }, "dependencies": { "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { "delayed-stream": "~1.0.0" } @@ -5079,8 +5207,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -5695,7 +5822,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -5748,8 +5874,7 @@ "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-to-string-tag-x": { "version": "1.2.0", @@ -5913,34 +6038,39 @@ "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" }, "htmlparser2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", - "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", "requires": { - "domelementtype": "^1.3.0", + "domelementtype": "^1.3.1", "domhandler": "^2.3.0", "domutils": "^1.5.1", "entities": "^1.1.1", "inherits": "^2.0.1", - "readable-stream": "^3.0.6" + "readable-stream": "^3.1.1" }, "dependencies": { "readable-stream": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", - "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz", + "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } } } @@ -6012,9 +6142,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -6246,9 +6376,9 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" }, "ipaddr.js": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" }, "irregular-plurals": { "version": "1.2.0", @@ -6256,6 +6386,11 @@ "integrity": "sha1-OPKZg0uowAwwvpxVThNyaXUv86w=", "dev": true }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -6303,8 +6438,7 @@ "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-dotfile": { "version": "1.0.3", @@ -6361,6 +6495,11 @@ "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", "dev": true }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==" + }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", @@ -6483,7 +6622,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, "requires": { "has-symbols": "^1.0.0" } @@ -6658,15 +6796,21 @@ "dev": true }, "js-xdr": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/js-xdr/-/js-xdr-1.0.5.tgz", - "integrity": "sha512-v0jffMa8bko3uFcGYt1lHrtpd1adhH6qk41RfLPsNPj77/K8fi7LOi4+lUUY3MBEIFnJgaGHrbc6sxdTwHImxQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/js-xdr/-/js-xdr-1.1.3.tgz", + "integrity": "sha512-zFg7dIc6lI7LiZ/eru5U/cOur/SJGkbKflgkwoFIiHewgpmBGsRJNKbeOh/8Ffzz5mYvfmsO6gHAe5wv/ZYM2g==", "requires": { + "core-js": "^2.6.3", "cursor": "^0.1.5", "lodash": "^4.17.5", "long": "^2.2.3" }, "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, "long": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz", @@ -7061,9 +7205,9 @@ "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" }, "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -7276,6 +7420,34 @@ "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=", "dev": true }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "optional": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "optional": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + } + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -7700,11 +7872,90 @@ "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + }, "object-keys": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", - "dev": true + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", + "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + } + } }, "object.omit": { "version": "2.0.1", @@ -7902,9 +8153,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -8520,9 +8771,9 @@ } }, "prova-lib": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/prova-lib/-/prova-lib-0.2.9.tgz", - "integrity": "sha512-YaulnI8L3LozBjwy64afOYmQdz2SzMiZgGvhHSU1BA7TIH8VlHZVN5/PrZ4roKS9tOPT7rl5A0C4RB63BlnVow==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/prova-lib/-/prova-lib-0.2.10.tgz", + "integrity": "sha512-L1K1BiBteuTL4Q/gyMROmPI4BJVG2KsmEkhmQEOKQw5SA3D54UC3iG6u+DSZKW7nvjsTPSbpzDzaCOmSgtTGuA==", "requires": { "bigi": "~1.4.2", "bitcoin-ops": "~1.3.0", @@ -8530,7 +8781,7 @@ "bs58check": "~2.0.1", "ecurve": "~1.0.5", "lodash": "~4.17.4", - "secp256k1": "~3.3.0", + "secp256k1": "~3.5.0", "typeforce": "~1.10.6", "varuint-bitcoin": "~1.0.4" }, @@ -8563,9 +8814,9 @@ } }, "secp256k1": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.3.1.tgz", - "integrity": "sha512-lygjgfjzjBHblEDDkppUF5KK1EeVk6P/Dv2MsJZpYIR3vW5TKFRexOFkf0hHy9J5YxEpjQZ6x98Y3XQpMQO/vA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.5.2.tgz", + "integrity": "sha512-iin3kojdybY6NArd+UFsoTuapOF7bnJNf2UbcWXaY3z+E1sJDipl60vtzB5hbO/uquBu7z0fd4VC4Irp+xoFVQ==", "optional": true, "requires": { "bindings": "^1.2.1", @@ -8575,7 +8826,6 @@ "drbg.js": "^1.0.1", "elliptic": "^6.2.3", "nan": "^2.2.1", - "prebuild-install": "^2.0.0", "safe-buffer": "^5.1.0" } }, @@ -8595,12 +8845,12 @@ } }, "proxy-addr": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", - "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", "requires": { "forwarded": "~0.1.2", - "ipaddr.js": "1.8.0" + "ipaddr.js": "1.9.0" }, "dependencies": { "forwarded": { @@ -8612,7 +8862,7 @@ }, "proxy-agent": { "version": "2.3.1", - "resolved": "http://registry.npmjs.org/proxy-agent/-/proxy-agent-2.3.1.tgz", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.3.1.tgz", "integrity": "sha512-CNKuhC1jVtm8KJYFTS2ZRO71VCBx3QSA92So/e6NrY6GoJonkx3Irnk4047EsCcswczwqAekRj3s8qLRGahSKg==", "requires": { "agent-base": "^4.2.0", @@ -8634,9 +8884,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -8683,9 +8933,9 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz", - "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" }, "randomatic": { "version": "1.1.7", @@ -9455,14 +9705,81 @@ } }, "ripple-hashes": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/ripple-hashes/-/ripple-hashes-0.3.1.tgz", - "integrity": "sha1-8vRvH/BeZIdQCpmDkBkRTNJIJBE=", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/ripple-hashes/-/ripple-hashes-0.3.4.tgz", + "integrity": "sha512-u2kgg9Yu9D44HWnC9R2lNg+amVLllJkMQmXZEEM2DAMFXigr4+ph1O8LLxLv+k0fbdjAjos4aUyWwcw6cxzYMw==", "requires": { "bignumber.js": "^4.1.0", "create-hash": "^1.1.2", - "ripple-address-codec": "^2.0.1", - "ripple-binary-codec": "^0.1.0" + "ripple-address-codec": "^3.0.4", + "ripple-binary-codec": "^0.2.4" + }, + "dependencies": { + "base-x": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.4.tgz", + "integrity": "sha512-UYOadoSIkEI/VrRGSG6qp93rp2WdokiAiNYDfGW5qURAY8GiAQkvMbwNNSDYiVJopqv4gCna7xqf4rrNGp+5AA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "bn.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA==" + }, + "decimal.js": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", + "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==" + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "ripple-address-codec": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-3.0.4.tgz", + "integrity": "sha512-GFk1BgavW+9oy5Z1Cp6YAGMfB51QdbeuhOo0Zir+s+S40F5vHtVZYu6zZE1eOAX92A5kygPuBRX4APH2v8Yhmg==", + "requires": { + "base-x": "3.0.4", + "create-hash": "^1.1.2" + } + }, + "ripple-binary-codec": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.2.6.tgz", + "integrity": "sha512-k0efyjpcde7p+rJ9PXW9tJSYsUDdlC9Z9xU7OPM7fJiHVKlR1E7nfu0jqw9vVXtTG3tujqKeEgtcb8yaa7rMXA==", + "requires": { + "babel-runtime": "^6.6.1", + "bn.js": "^5.1.1", + "create-hash": "^1.1.2", + "decimal.js": "^10.2.0", + "inherits": "^2.0.1", + "lodash": "^4.17.15", + "ripple-address-codec": "^4.0.0" + }, + "dependencies": { + "base-x": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.7.tgz", + "integrity": "sha512-zAKJGuQPihXW22fkrfOclUUZXM2g92z5GzlSMHxhO6r6Qj+Nm0ccaGNBzDZojzwOMkpjAv4J0fOv1U4go+a4iw==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ripple-address-codec": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-4.1.0.tgz", + "integrity": "sha512-C72gJpwvDagaOUiHyh67otqNqFduB4hjvJFiiPz/8I3FCiUYuvFLXeLhb29CEkoAEdoN9p7pPreLgoHUvwzt9w==", + "requires": { + "base-x": "3.0.7", + "create-hash": "^1.1.2" + } + } + } + } } }, "ripple-keypairs": { @@ -9738,7 +10055,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { "depd": "~1.1.2", @@ -10191,9 +10508,9 @@ }, "dependencies": { "tweetnacl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz", - "integrity": "sha1-cT2LgY2kIGh0C/aDhtBHnmb8ins=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.2.tgz", + "integrity": "sha512-+8aPRjmXgf1VqvyxSlBUzKzeYqVS9Ai8vZ28g+mL7dNQl1jlUTCMDZnvNQdAS1xTywMkIXwJsfipsR/6s2+syw==" } } }, @@ -10213,12 +10530,12 @@ }, "dependencies": { "axios": { - "version": "0.18.0", - "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", - "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", + "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", "requires": { - "follow-redirects": "^1.3.0", - "is-buffer": "^1.1.5" + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" } }, "debug": { @@ -10236,6 +10553,11 @@ "requires": { "debug": "=3.1.0" } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" } } }, @@ -10249,6 +10571,24 @@ "strip-ansi": "^3.0.0" } }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", @@ -10347,24 +10687,24 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==" }, "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -10387,7 +10727,7 @@ }, "superagent-proxy": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/superagent-proxy/-/superagent-proxy-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-1.0.3.tgz", "integrity": "sha512-79Ujg1lRL2ICfuHUdX+H2MjIw73kB7bXsIkxLwHURz3j0XUmEEEoJ+u/wq+mKwna21Uejsm2cGR3OESA00TIjA==", "requires": { "debug": "^3.1.0", @@ -10403,9 +10743,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -10732,9 +11072,9 @@ "dev": true }, "toml": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.3.tgz", - "integrity": "sha512-O7L5hhSQHxuufWUdcTRPfuTh3phKfAZ/dqfxZFoxPCj2RYmpaSGLEIs016FCXItQwNr08yefUB5TSjzRYnajTA==" + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz", + "integrity": "sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==" }, "topo": { "version": "1.1.0", @@ -10853,9 +11193,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typeforce": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.16.0.tgz", - "integrity": "sha512-V60F7OHPH7vPlgIU73vYyeebKxWjQqCTlge+MvKlVn09PIhCOi/ZotowYdgREHB5S1dyHOr906ui6NheYXjlVQ==" + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" }, "uid-number": { "version": "0.0.6", @@ -10960,11 +11300,11 @@ "integrity": "sha512-xVrGVi94ueCJNrBSTjWqjvtgvl3cyOTThp2zaMaFNGp3F542TR6sM3f2o8RqZl+AwteClSVmoCyt0ka4RjQOQg==" }, "url-parse": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", - "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", "requires": { - "querystringify": "^2.0.0", + "querystringify": "^2.1.1", "requires-port": "^1.0.0" }, "dependencies": { @@ -10994,11 +11334,22 @@ "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=" }, "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.1.tgz", + "integrity": "sha512-MREAtYOp+GTt9/+kwf00IYoHZyjM8VU4aVrkzUlejyqaIjd2GztVl5V9hGXKlvBKE3gENn/FMfHE5v6hElXGcQ==", "requires": { - "inherits": "2.0.3" + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "object.entries": "^1.1.0", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + } } }, "util-deprecate": { @@ -11033,9 +11384,9 @@ } }, "varuint-bitcoin": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.0.tgz", - "integrity": "sha512-jCEPG+COU/1Rp84neKTyDJQr478/hAfVp5xxYn09QEH0yBjbmPeMfuuQIrp+BUD83hybtYZKhr5elV3bvdV1bA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", "requires": { "safe-buffer": "^5.1.1" } diff --git a/public/elm.js b/public/elm.js index 80c247d2..cede45d8 100644 --- a/public/elm.js +++ b/public/elm.js @@ -37383,7 +37383,11 @@ var _user$project$NavBar$determineConfigCategory = function (configCode) { _1: { ctor: '::', _0: 'operatorInfo', - _1: {ctor: '[]'} + _1: { + ctor: '::', + _0: 'fudgeFactor', + _1: {ctor: '[]'} + } } } } @@ -37836,7 +37840,11 @@ var _user$project$NavBar$view = F2( _1: { ctor: '::', _0: A2(configLink, 'operatorInfo', 'Operator Info'), - _1: {ctor: '[]'} + _1: { + ctor: '::', + _0: A2(configLink, 'fudgeFactor', 'Fudge Factor'), + _1: {ctor: '[]'} + } } } } From 1cc3c820b5e9775d78cff2408059404009ef9fba Mon Sep 17 00:00:00 2001 From: Taranto Date: Fri, 20 Mar 2020 17:55:55 +0000 Subject: [PATCH 14/29] fix: pr fixes --- lib/cash-out/cash-out-helper.js | 3 ++- lib/plugins/wallet/mock-wallet/mock-wallet.js | 6 +++--- lib/wallet.js | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/cash-out/cash-out-helper.js b/lib/cash-out/cash-out-helper.js index 8a3a37cb..dc4597fd 100644 --- a/lib/cash-out/cash-out-helper.js +++ b/lib/cash-out/cash-out-helper.js @@ -16,7 +16,8 @@ function convertBigNumFields (obj) { return value.toString() } - if (_.includes(key, [ 'commissionPercentage', 'rawTickerPrice' ]) && value) { + // Only test isNil for these fields since the others should not be empty. + if (_.includes(key, [ 'commissionPercentage', 'rawTickerPrice' ]) && !_.isNil(value)) { return value.toString() } diff --git a/lib/plugins/wallet/mock-wallet/mock-wallet.js b/lib/plugins/wallet/mock-wallet/mock-wallet.js index eea9b3b4..abaff6f7 100644 --- a/lib/plugins/wallet/mock-wallet/mock-wallet.js +++ b/lib/plugins/wallet/mock-wallet/mock-wallet.js @@ -79,9 +79,9 @@ function newFunding (account, cryptoCode) { function getStatus (account, toAddress, cryptoAtoms, cryptoCode) { const elapsed = Date.now() - t0 - if (elapsed < PUBLISH_TIME) return Promise.resolve({ receivedCryptoAtoms: cryptoAtoms - 10, status: 'notSeen' }) - if (elapsed < AUTHORIZE_TIME) return Promise.resolve({ receivedCryptoAtoms: cryptoAtoms - 10, status: 'published' }) - if (elapsed < CONFIRM_TIME) return Promise.resolve({ receivedCryptoAtoms: null, status: 'authorized' }) + if (elapsed < PUBLISH_TIME) return Promise.resolve({ receivedCryptoAtoms: BN(0), status: 'notSeen' }) + if (elapsed < AUTHORIZE_TIME) return Promise.resolve({ receivedCryptoAtoms: cryptoAtoms, status: 'published' }) + if (elapsed < CONFIRM_TIME) return Promise.resolve({ receivedCryptoAtoms: cryptoAtoms, status: 'authorized' }) console.log('[%s] DEBUG: Mock wallet has confirmed transaction [%s]', cryptoCode, toAddress.slice(0, 5)) diff --git a/lib/wallet.js b/lib/wallet.js index 4fc8e0de..49124ef9 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -126,7 +126,7 @@ function getWalletStatus (settings, tx) { const fudgeFactor = fudgeFactorEnabled ? 10 : 0 const walletStatusPromise = fetchWallet(settings, tx.cryptoCode) - .then(r => r.wallet.getStatus(r.account, tx.toAddress, tx.cryptoAtoms.add(fudgeFactor), tx.cryptoCode)) + .then(r => r.wallet.getStatus(r.account, tx.toAddress, tx.cryptoAtoms.minus(fudgeFactor), tx.cryptoCode)) return Promise.all([ walletStatusPromise, From a7823736e6ddadca4d7acb973959b371363bfc5d Mon Sep 17 00:00:00 2001 From: Neal Conner <7396812+naconner@users.noreply.github.com> Date: Thu, 26 Mar 2020 15:42:44 -0400 Subject: [PATCH 15/29] chore: update BTC, BCH, LTC, ETH wallets --- lib/blockchain/common.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 15df7156..74f99a45 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -21,12 +21,12 @@ module.exports = { const BINARIES = { BTC: { - url: 'https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-x86_64-linux-gnu.tar.gz', - dir: 'bitcoin-0.16.3/bin' + url: 'https://bitcoincore.org/bin/bitcoin-core-0.19.1/bitcoin-0.19.1-x86_64-linux-gnu.tar.gz', + dir: 'bitcoin-0.19.1/bin' }, ETH: { - url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.11-6a62fe39.tar.gz', - dir: 'geth-linux-amd64-1.9.11-6a62fe39' + url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.12-b6f1c8dc.tar.gz', + dir: 'geth-linux-amd64-1.9.12-b6f1c8dc' }, ZEC: { url: 'https://z.cash/downloads/zcash-2.1.1-1-linux64-debian-jessie.tar.gz', @@ -37,12 +37,12 @@ const BINARIES = { dir: 'dashcore-0.15.0/bin' }, LTC: { - url: 'https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-x86_64-linux-gnu.tar.gz', - dir: 'litecoin-0.16.3/bin' + url: 'https://download.litecoin.org/litecoin-0.17.1/linux/litecoin-0.17.1-x86_64-linux-gnu.tar.gz', + dir: 'litecoin-0.17.1/bin' }, BCH: { - url: 'https://download.bitcoinabc.org/0.20.5/linux/bitcoin-abc-0.20.5-x86_64-linux-gnu.tar.gz', - dir: 'bitcoin-abc-0.20.5/bin', + url: 'https://download.bitcoinabc.org/0.21.2/linux/bitcoin-abc-0.21.2-x86_64-linux-gnu.tar.gz', + dir: 'bitcoin-abc-0.21.2/bin', files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']] } } From 8f28b7aa0af9fa06b268166d1e63b73ea2d00aae Mon Sep 17 00:00:00 2001 From: Taranto Date: Wed, 22 Jan 2020 18:13:01 +0000 Subject: [PATCH 16/29] feat: only refetch t&c on config change --- lib/plugins.js | 3 ++- lib/routes.js | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 7f0cb6ae..0b55a815 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -793,7 +793,8 @@ function plugins (settings, deviceId) { buy, sell, notificationsEnabled, - notifyOperator + notifyOperator, + fetchCurrentConfigVersion } } diff --git a/lib/routes.js b/lib/routes.js index 9564eb15..b1a7928d 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -72,8 +72,6 @@ function poll (req, res, next) { } } - const terms = config.termsScreenActive && config.termsScreenText ? createTerms(config) : null - const response = { error: null, locale, @@ -94,7 +92,6 @@ function poll (req, res, next) { frontCameraVerificationActive: config.frontCameraVerificationActive, frontCameraVerificationThreshold: config.frontCameraVerificationThreshold, receiptPrintingActive: config.receiptPrintingActive, - terms, cassettes, twoWayMode: config.cashOutEnabled, zeroConfLimit: config.zeroConfLimit, @@ -120,6 +117,21 @@ function poll (req, res, next) { .catch(next) } +function getTermsConditions (req, res, next) { + const deviceId = req.deviceId + const settings = req.settings + + const config = configManager.unscoped(req.settings.config) + const pi = plugins(settings, deviceId) + + const terms = config.termsScreenActive && config.termsScreenText ? createTerms(config) : null + + return pi.fetchCurrentConfigVersion().then(version => { + return res.json({ terms, version }) + }) + .catch(next) +} + function getTx (req, res, next) { if (req.query.status) { return helpers.fetchStatusTx(req.params.id, req.query.status) @@ -356,6 +368,7 @@ const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) && const configRequiredRoutes = [ '/poll', + '/terms_conditions', '/event', '/phone_code', '/customer', @@ -379,6 +392,7 @@ app.use(configRequiredRoutes, populateSettings) app.use(filterOldRequests) app.get('/poll', poll) +app.get('/terms_conditions', getTermsConditions) app.post('/state', stateChange) app.post('/verify_user', verifyUser) From 7fc8ff3d1d599f24dea4a2afecba2d9571426502 Mon Sep 17 00:00:00 2001 From: Taranto Date: Mon, 24 Feb 2020 10:29:36 +0000 Subject: [PATCH 17/29] fix: make t&c changes backwards compatible --- lib/routes.js | 11 +++++++++++ package-lock.json | 23 +++++++++++++++++++---- package.json | 1 + 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/routes.js b/lib/routes.js index b1a7928d..20bcbc69 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -6,6 +6,7 @@ const bodyParser = require('body-parser') const _ = require('lodash/fp') const express = require('express') const nmd = require('nano-markdown') +const semver = require('semver') const dbErrorCodes = require('./db-error-codes') const options = require('./options') @@ -22,6 +23,8 @@ const customers = require('./customers') const logs = require('./logs') const compliance = require('./compliance') +const version = require('../package.json').version + const argv = require('minimist')(process.argv.slice(2)) const CLOCK_SKEW = 60 * 1000 @@ -44,6 +47,7 @@ function checkHasLightning (settings) { } function poll (req, res, next) { + const machineVersion = req.query.version const deviceId = req.deviceId const deviceTime = req.deviceTime const serialNumber = req.query.sn @@ -75,6 +79,7 @@ function poll (req, res, next) { const response = { error: null, locale, + version, txLimit: config.cashInTransactionLimit, idVerificationEnabled: config.idVerificationEnabled, smsVerificationActive: config.smsVerificationActive, @@ -108,6 +113,12 @@ function poll (req, res, next) { } } + // BACKWARDS_COMPATIBILITY 7.5 + // machines before 7.5 expect t&c on poll + if (!machineVersion || semver.lt(machineVersion, '7.5.0-beta')) { + response.terms = config.termsScreenActive && config.termsScreenText ? createTerms(config) : null + } + if (response.idVerificationEnabled) { response.idVerificationLimit = config.idVerificationLimit } diff --git a/package-lock.json b/package-lock.json index 6bb7963b..fd4b0af5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7816,6 +7816,14 @@ "is-builtin-module": "^1.0.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "normalize-path": { @@ -9991,10 +9999,9 @@ "integrity": "sha1-uztgbdaGN8okRoGhD97mxRIEkpQ=" }, "semver": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", - "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", - "dev": true + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", + "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==" }, "semver-diff": { "version": "2.1.0", @@ -10003,6 +10010,14 @@ "dev": true, "requires": { "semver": "^5.0.3" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "send": { diff --git a/package.json b/package.json index 5eac4578..feb94f43 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "pify": "^3.0.0", "pretty-ms": "^2.1.0", "promise-sequential": "^1.1.1", + "semver": "^7.1.3", "serve-static": "^1.12.4", "socket.io": "^2.0.3", "socket.io-client": "^2.0.3", From 8b149031e3886d25db6482ce53245682546b1cfd Mon Sep 17 00:00:00 2001 From: Taranto Date: Thu, 26 Mar 2020 08:14:59 +0000 Subject: [PATCH 18/29] chore: early t&c release --- lib/routes.js | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/routes.js b/lib/routes.js index 20bcbc69..5dde21c4 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -113,9 +113,9 @@ function poll (req, res, next) { } } - // BACKWARDS_COMPATIBILITY 7.5 - // machines before 7.5 expect t&c on poll - if (!machineVersion || semver.lt(machineVersion, '7.5.0-beta')) { + // BACKWARDS_COMPATIBILITY 7.4.9 + // machines before 7.4.9 expect t&c on poll + if (!machineVersion || semver.lt(machineVersion, '7.4.9')) { response.terms = config.termsScreenActive && config.termsScreenText ? createTerms(config) : null } diff --git a/package.json b/package.json index feb94f43..8b8f473c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "7.4.8", + "version": "7.4.9", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From cf616c1f1637800ff9ae1258ffa97c2426824126 Mon Sep 17 00:00:00 2001 From: Neal Conner <7396812+naconner@users.noreply.github.com> Date: Wed, 25 Mar 2020 14:19:58 -0400 Subject: [PATCH 19/29] feat: reintroduce lowBalanceMargin --- lib/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins.js b/lib/plugins.js index 0b55a815..39c02778 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -308,7 +308,7 @@ function plugins (settings, deviceId) { const rate = rawRate.div(cashInCommission) - const lowBalanceMargin = BN(1) + const lowBalanceMargin = BN(1.03) const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) const unitScale = cryptoRec.unitScale From 086c5ec188de5baaad62a2b25fb3976dd59fc24c Mon Sep 17 00:00:00 2001 From: Taranto Date: Tue, 28 Jan 2020 13:23:38 +0000 Subject: [PATCH 20/29] fix: change rpc call after get balance deprecation --- .../wallet/bitcoincashd/bitcoincashd.js | 20 ++++++++++++------- lib/plugins/wallet/bitcoind/bitcoind.js | 20 ++++++++++++------- lib/plugins/wallet/dashd/dashd.js | 20 ++++++++++++------- lib/plugins/wallet/litecoind/litecoind.js | 20 ++++++++++++------- lib/plugins/wallet/zcashd/zcashd.js | 20 ++++++++++++------- 5 files changed, 65 insertions(+), 35 deletions(-) diff --git a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js index 0ef29ede..fd141ede 100644 --- a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js +++ b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js @@ -25,16 +25,22 @@ function checkCryptoCode (cryptoCode) { return Promise.resolve() } -function accountBalance (account, cryptoCode, confirmations) { +function accountBalance (cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['*', confirmations])) - .then(r => BN(r).shift(unitScale).round()) + .then(() => fetch('getwalletinfo')) + .then(({ balance }) => BN(balance).shift(unitScale).round()) +} + +function accountUnconfirmedBalance (cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => fetch('getwalletinfo')) + .then(({ unconfirmed_balance: balance }) => BN(balance).shift(unitScale).round()) } // We want a balance that includes all spends (0 conf) but only deposits that // have at least 1 confirmation. getbalance does this for us automatically. function balance (account, cryptoCode) { - return accountBalance(account, cryptoCode, 1) + return accountBalance(cryptoCode) } function sendCoins (account, address, cryptoAtoms, cryptoCode) { @@ -95,9 +101,9 @@ function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => { const promises = [ - accountBalance(account, cryptoCode, 0), - accountBalance(account, cryptoCode, 1), - newAddress(account, {cryptoCode}) + accountUnconfirmedBalance(cryptoCode), + accountBalance(cryptoCode), + newAddress(account, { cryptoCode }) ] return Promise.all(promises) diff --git a/lib/plugins/wallet/bitcoind/bitcoind.js b/lib/plugins/wallet/bitcoind/bitcoind.js index a8fc91f2..e424eeef 100644 --- a/lib/plugins/wallet/bitcoind/bitcoind.js +++ b/lib/plugins/wallet/bitcoind/bitcoind.js @@ -25,16 +25,22 @@ function checkCryptoCode (cryptoCode) { return Promise.resolve() } -function accountBalance (account, cryptoCode, confirmations) { +function accountBalance (cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['*', confirmations])) - .then(r => BN(r).shift(unitScale).round()) + .then(() => fetch('getwalletinfo')) + .then(({ balance }) => BN(balance).shift(unitScale).round()) +} + +function accountUnconfirmedBalance (cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => fetch('getwalletinfo')) + .then(({ unconfirmed_balance: balance }) => BN(balance).shift(unitScale).round()) } // We want a balance that includes all spends (0 conf) but only deposits that // have at least 1 confirmation. getbalance does this for us automatically. function balance (account, cryptoCode) { - return accountBalance(account, cryptoCode, 1) + return accountBalance(cryptoCode) } function sendCoins (account, address, cryptoAtoms, cryptoCode) { @@ -95,9 +101,9 @@ function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => { const promises = [ - accountBalance(account, cryptoCode, 0), - accountBalance(account, cryptoCode, 1), - newAddress(account, {cryptoCode}) + accountUnconfirmedBalance(cryptoCode), + accountBalance(cryptoCode), + newAddress(account, { cryptoCode }) ] return Promise.all(promises) diff --git a/lib/plugins/wallet/dashd/dashd.js b/lib/plugins/wallet/dashd/dashd.js index 4d664269..33c86c13 100644 --- a/lib/plugins/wallet/dashd/dashd.js +++ b/lib/plugins/wallet/dashd/dashd.js @@ -26,16 +26,22 @@ function checkCryptoCode (cryptoCode) { return Promise.resolve() } -function accountBalance (acount, cryptoCode, confirmations) { +function accountBalance (cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['', confirmations])) - .then(r => BN(r).shift(unitScale).round()) + .then(() => fetch('getwalletinfo')) + .then(({ balance }) => BN(balance).shift(unitScale).round()) +} + +function accountUnconfirmedBalance (cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => fetch('getwalletinfo')) + .then(({ unconfirmed_balance: balance }) => BN(balance).shift(unitScale).round()) } // We want a balance that includes all spends (0 conf) but only deposits that // have at least 1 confirmation. getbalance does this for us automatically. function balance (account, cryptoCode) { - return accountBalance(account, cryptoCode, 1) + return accountBalance(cryptoCode) } function sendCoins (account, address, cryptoAtoms, cryptoCode) { @@ -96,9 +102,9 @@ function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => { const promises = [ - accountBalance(account, cryptoCode, 0), - accountBalance(account, cryptoCode, 1), - newAddress(account, {cryptoCode}) + accountUnconfirmedBalance(cryptoCode), + accountBalance(cryptoCode), + newAddress(account, { cryptoCode }) ] return Promise.all(promises) diff --git a/lib/plugins/wallet/litecoind/litecoind.js b/lib/plugins/wallet/litecoind/litecoind.js index fc4c9bce..234b816e 100644 --- a/lib/plugins/wallet/litecoind/litecoind.js +++ b/lib/plugins/wallet/litecoind/litecoind.js @@ -26,16 +26,22 @@ function checkCryptoCode (cryptoCode) { return Promise.resolve() } -function accountBalance (acount, cryptoCode, confirmations) { +function accountBalance (cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['*', confirmations])) - .then(r => BN(r).shift(unitScale).round()) + .then(() => fetch('getwalletinfo')) + .then(({ balance }) => BN(balance).shift(unitScale).round()) +} + +function accountUnconfirmedBalance (cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => fetch('getwalletinfo')) + .then(({ unconfirmed_balance: balance }) => BN(balance).shift(unitScale).round()) } // We want a balance that includes all spends (0 conf) but only deposits that // have at least 1 confirmation. getbalance does this for us automatically. function balance (account, cryptoCode) { - return accountBalance(account, cryptoCode, 1) + return accountBalance(cryptoCode) } function sendCoins (account, address, cryptoAtoms, cryptoCode) { @@ -96,9 +102,9 @@ function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => { const promises = [ - accountBalance(account, cryptoCode, 0), - accountBalance(account, cryptoCode, 1), - newAddress(account, {cryptoCode}) + accountUnconfirmedBalance(cryptoCode), + accountBalance(cryptoCode), + newAddress(account, { cryptoCode }) ] return Promise.all(promises) diff --git a/lib/plugins/wallet/zcashd/zcashd.js b/lib/plugins/wallet/zcashd/zcashd.js index 1f9e7656..b5b23da6 100644 --- a/lib/plugins/wallet/zcashd/zcashd.js +++ b/lib/plugins/wallet/zcashd/zcashd.js @@ -26,16 +26,22 @@ function checkCryptoCode (cryptoCode) { return Promise.resolve() } -function accountBalance (acount, cryptoCode, confirmations) { +function accountBalance (cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['', confirmations])) - .then(r => BN(r).shift(unitScale).round()) + .then(() => fetch('getwalletinfo')) + .then(({ balance }) => BN(balance).shift(unitScale).round()) +} + +function accountUnconfirmedBalance (cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => fetch('getwalletinfo')) + .then(({ unconfirmed_balance: balance }) => BN(balance).shift(unitScale).round()) } // We want a balance that includes all spends (0 conf) but only deposits that // have at least 1 confirmation. getbalance does this for us automatically. function balance (account, cryptoCode) { - return accountBalance(account, cryptoCode, 1) + return accountBalance(cryptoCode) } function sendCoins (account, address, cryptoAtoms, cryptoCode) { @@ -96,9 +102,9 @@ function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) .then(() => { const promises = [ - accountBalance(account, cryptoCode, 0), - accountBalance(account, cryptoCode, 1), - newAddress(account, {cryptoCode}) + accountUnconfirmedBalance(cryptoCode), + accountBalance(cryptoCode), + newAddress(account, { cryptoCode }) ] return Promise.all(promises) From 7152230d08345ee394ed538ee7885cd2d70831a3 Mon Sep 17 00:00:00 2001 From: Taranto Date: Thu, 26 Mar 2020 17:44:44 +0000 Subject: [PATCH 21/29] fix: pending already considers confirmed balance --- lib/admin/funding.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/funding.js b/lib/admin/funding.js index b927b0bd..c912d913 100644 --- a/lib/admin/funding.js +++ b/lib/admin/funding.js @@ -78,7 +78,7 @@ function getFunding (_cryptoCode) { const rate = (rates.ask.add(rates.bid)).div(2) const fundingConfirmedBalance = fundingRec.fundingConfirmedBalance const fiatConfirmedBalance = computeFiat(rate, cryptoCode, fundingConfirmedBalance) - const pending = fundingRec.fundingPendingBalance.sub(fundingConfirmedBalance) + const pending = fundingRec.fundingPendingBalance const fiatPending = computeFiat(rate, cryptoCode, pending) const fundingAddress = fundingRec.fundingAddress const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress) From 4425ccce58313b4ae22ddc4a27969fa456d02c14 Mon Sep 17 00:00:00 2001 From: Taranto Date: Tue, 7 Apr 2020 11:34:35 +0100 Subject: [PATCH 22/29] feat: First steps into regtest integration tests --- test/integration/README.md | 1 + test/integration/balance.sh | 35 +++++++++++++++++++++++++++++++++++ test/integration/bitcoind.sh | 7 +++++++ 3 files changed, 43 insertions(+) create mode 100644 test/integration/README.md create mode 100644 test/integration/balance.sh create mode 100644 test/integration/bitcoind.sh diff --git a/test/integration/README.md b/test/integration/README.md new file mode 100644 index 00000000..3ad283f6 --- /dev/null +++ b/test/integration/README.md @@ -0,0 +1 @@ +### Early experiments in regtest diff --git a/test/integration/balance.sh b/test/integration/balance.sh new file mode 100644 index 00000000..81709b8c --- /dev/null +++ b/test/integration/balance.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +PREFIX_W1='docker exec --user bitcoin bitcoin-server bitcoin-cli -rpcwallet=wallet1.dat -regtest ' +PREFIX_W2='docker exec --user bitcoin bitcoin-server bitcoin-cli -rpcwallet=wallet2.dat -regtest ' + +# Create address and generate mine blocks +ADDRESS_1=`$PREFIX_W1 getnewaddress` +ADDRESS_2=`$PREFIX_W2 getnewaddress` +$PREFIX_W1 generatetoaddress 101 $ADDRESS_1 > /dev/null 2>&1 + +echo -e 'Wallet 1 balance' +$PREFIX_W1 getwalletinfo | grep balance + +echo -e '\nWallet 2 balance' +$PREFIX_W2 getwalletinfo | grep balance + +# Wallet one has one UTXO, so this also tests change +echo -e '\n\nCreate send\n' +$PREFIX_W1 sendtoaddress $ADDRESS_2 0.3 > /dev/null 2>&1 + +echo -e 'Wallet 1 balance' +$PREFIX_W1 getwalletinfo | grep balance + +echo -e '\nWallet 2 balance' +$PREFIX_W2 getwalletinfo | grep balance + +echo -e '\n\nMine one block \n' +$PREFIX_W1 generatetoaddress 1 $ADDRESS_1 > /dev/null 2>&1 + +echo -e 'Wallet 1 balance' +$PREFIX_W1 getwalletinfo | grep balance + +echo -e '\nWallet 2 balance' +$PREFIX_W2 getwalletinfo | grep balance + diff --git a/test/integration/bitcoind.sh b/test/integration/bitcoind.sh new file mode 100644 index 00000000..33313887 --- /dev/null +++ b/test/integration/bitcoind.sh @@ -0,0 +1,7 @@ +#!/bin/bash + + docker run --rm --name bitcoin-server -it ruimarinho/bitcoin-core \ + -regtest=1 \ + -wallet=wallet1.dat \ + -wallet=wallet2.dat + From 35aa0a058d5d0c9feb683696fb526a0a8a734259 Mon Sep 17 00:00:00 2001 From: Neal Date: Thu, 23 Apr 2020 17:38:43 -0400 Subject: [PATCH 23/29] chore: update ZEC, BCH, ETH wallets --- lib/blockchain/common.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 74f99a45..be714e82 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -25,12 +25,12 @@ const BINARIES = { dir: 'bitcoin-0.19.1/bin' }, ETH: { - url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.12-b6f1c8dc.tar.gz', - dir: 'geth-linux-amd64-1.9.12-b6f1c8dc' + url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.13-cbc4ac26.tar.gz', + dir: 'geth-linux-amd64-1.9.13-cbc4ac26' }, ZEC: { - url: 'https://z.cash/downloads/zcash-2.1.1-1-linux64-debian-jessie.tar.gz', - dir: 'zcash-2.1.1-1/bin' + url: 'https://z.cash/downloads/zcash-2.1.2-linux64-debian-jessie.tar.gz', + dir: 'zcash-2.1.2/bin' }, DASH: { url: 'https://github.com/dashpay/dash/releases/download/v0.15.0.0/dashcore-0.15.0.0-x86_64-linux-gnu.tar.gz', @@ -41,8 +41,8 @@ const BINARIES = { dir: 'litecoin-0.17.1/bin' }, BCH: { - url: 'https://download.bitcoinabc.org/0.21.2/linux/bitcoin-abc-0.21.2-x86_64-linux-gnu.tar.gz', - dir: 'bitcoin-abc-0.21.2/bin', + url: 'https://download.bitcoinabc.org/0.21.4/linux/bitcoin-abc-0.21.4-x86_64-linux-gnu.tar.gz', + dir: 'bitcoin-abc-0.21.4/bin', files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']] } } From ec73f0b022a1e3fcb5f5cd4d850cca86084eb6a1 Mon Sep 17 00:00:00 2001 From: Taranto Date: Wed, 22 Apr 2020 17:07:19 +0100 Subject: [PATCH 24/29] fix: flat schema on operator info pages --- .../src/components/editableTable/Table.js | 3 +- .../components/EditableNumber.js | 1 - .../components/SingleFieldEditableNumber.js | 2 +- .../sections/FiatBalanceAlerts.js | 2 +- .../sections/FiatBalanceOverrides.js | 12 +- .../OperatorInfo/CoinATMRadar/CoinATMRadar.js | 18 ++- .../src/pages/OperatorInfo/ContactInfo.js | 107 +++++++----------- .../src/pages/OperatorInfo/OperatorInfo.js | 29 ++--- .../src/pages/OperatorInfo/TermsConditions.js | 33 +++--- new-lamassu-admin/src/utils/config.js | 6 +- 10 files changed, 106 insertions(+), 107 deletions(-) diff --git a/new-lamassu-admin/src/components/editableTable/Table.js b/new-lamassu-admin/src/components/editableTable/Table.js index 5e89ba18..b6ce3345 100644 --- a/new-lamassu-admin/src/components/editableTable/Table.js +++ b/new-lamassu-admin/src/components/editableTable/Table.js @@ -51,7 +51,8 @@ const ETable = ({ const [editingId, setEditingId] = useState(null) const [adding, setAdding] = useState(false) - const innerSave = async it => { + const innerSave = async value => { + const it = validationSchema.cast(value) const index = R.findIndex(R.propEq('id', it.id))(data) const list = index !== -1 ? R.update(index, it, data) : R.prepend(it, data) diff --git a/new-lamassu-admin/src/pages/Notifications/components/EditableNumber.js b/new-lamassu-admin/src/pages/Notifications/components/EditableNumber.js index 56fde906..51369e14 100644 --- a/new-lamassu-admin/src/pages/Notifications/components/EditableNumber.js +++ b/new-lamassu-admin/src/pages/Notifications/components/EditableNumber.js @@ -42,7 +42,6 @@ const EditableNumber = ({ name={name} component={TextInput} textAlign="right" - type="text" width={width} /> )} diff --git a/new-lamassu-admin/src/pages/Notifications/components/SingleFieldEditableNumber.js b/new-lamassu-admin/src/pages/Notifications/components/SingleFieldEditableNumber.js index 126d8e9e..52a0e866 100644 --- a/new-lamassu-admin/src/pages/Notifications/components/SingleFieldEditableNumber.js +++ b/new-lamassu-admin/src/pages/Notifications/components/SingleFieldEditableNumber.js @@ -36,7 +36,7 @@ const SingleFieldEditableNumber = ({ enableReinitialize initialValues={{ [name]: (data && data[name]) ?? '' }} validationSchema={schema} - onSubmit={it => save(section, it)} + onSubmit={it => save(section, schema.cast(it))} onReset={() => { setEditing(name, false) }}> diff --git a/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceAlerts.js b/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceAlerts.js index f6d5cd92..769ff9fd 100644 --- a/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceAlerts.js +++ b/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceAlerts.js @@ -42,7 +42,7 @@ const FiatBalance = ({ section }) => { fiatBalanceCassette2: data?.fiatBalanceCassette2 ?? '' }} validationSchema={schema} - onSubmit={it => save(section, it)} + onSubmit={it => save(section, schema.cast(it))} onReset={() => { setEditing(NAME, false) }}> diff --git a/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js b/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js index 0e7a8f56..2e6484e9 100644 --- a/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js +++ b/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js @@ -23,7 +23,7 @@ const FiatBalanceOverrides = ({ section }) => { const overridenMachines = R.map(override => override.machine, setupValues) const suggestionFilter = R.filter( - it => !R.contains(it.code, overridenMachines) + it => !R.contains(it.deviceId, overridenMachines) ) const suggestions = suggestionFilter(machines) @@ -50,17 +50,21 @@ const FiatBalanceOverrides = ({ section }) => { .required() }) + const viewMachine = it => + R.compose(R.path(['name']), R.find(R.propEq('deviceId', it)))(machines) + const elements = [ { name: MACHINE_KEY, width: 238, size: 'sm', - view: R.path(['name']), + view: viewMachine, input: Autocomplete, inputProps: { options: it => R.concat(suggestions, findSuggestion(it)), limit: null, - getOptionSelected: R.eqProps('display') + valueProp: 'deviceId', + getLabel: R.path(['name']) } }, { @@ -90,7 +94,7 @@ const FiatBalanceOverrides = ({ section }) => { enableDelete enableEdit enableCreate - save={it => save(section, it)} + save={it => save(section, validationSchema.cast(it))} initialValues={initialValues} validationSchema={validationSchema} forceDisable={isDisabled(NAME) || !machines} diff --git a/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js b/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js index cf2d8508..0736dbf8 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js @@ -1,6 +1,7 @@ import { useQuery, useMutation } from '@apollo/react-hooks' import { makeStyles } from '@material-ui/core/styles' import { gql } from 'apollo-boost' +import * as R from 'ramda' import React, { useState, memo } from 'react' import Popper from 'src/components/Popper' @@ -9,6 +10,7 @@ import { Button } from 'src/components/buttons' import { Switch } from 'src/components/inputs' import { H4, P, Label2 } from 'src/components/typography' import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg' +import { fromNamespace, toNamespace, namespaces } from 'src/utils/config' import { mainStyles } from './CoinATMRadar.styles' @@ -48,17 +50,25 @@ const CoinATMRadar = memo(() => { // TODO: treat errors on useMutation and useQuery const [saveConfig] = useMutation(SAVE_CONFIG, { onCompleted: configResponse => - setCoinAtmRadarConfig(configResponse.saveConfig.coinAtmRadar) + setCoinAtmRadarConfig( + fromNamespace(namespaces.COIN_ATM_RADAR, configResponse.saveConfig) + ) }) useQuery(GET_CONFIG, { onCompleted: configResponse => { - setCoinAtmRadarConfig( - configResponse?.config?.coinAtmRadar ?? initialValues + const response = fromNamespace( + namespaces.COIN_ATM_RADAR, + configResponse.config ) + const values = R.merge(initialValues, response) + setCoinAtmRadarConfig(values) } }) - const save = it => saveConfig({ variables: { config: { coinAtmRadar: it } } }) + const save = it => + saveConfig({ + variables: { config: toNamespace(namespaces.COIN_ATM_RADAR, it) } + }) const handleOpenHelpPopper = event => { setHelpPopperAnchorEl(helpPopperAnchorEl ? null : event.currentTarget) diff --git a/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js b/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js index 87dd0a4f..702a45d9 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js @@ -1,30 +1,37 @@ -import React, { useState } from 'react' -import classnames from 'classnames' -import * as R from 'ramda' -import * as Yup from 'yup' -import { gql } from 'apollo-boost' -import { Form, Formik, Field as FormikField } from 'formik' -import { makeStyles } from '@material-ui/core' import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import { gql } from 'apollo-boost' +import classnames from 'classnames' +import { Form, Formik, Field as FormikField } from 'formik' +import * as R from 'ramda' +import React, { useState } from 'react' +import * as Yup from 'yup' -import { Info2, Info3, Label1 } from 'src/components/typography' -import TextInputFormik from 'src/components/inputs/formik/TextInput' -import RadioGroupFormik from 'src/components/inputs/formik/RadioGroup' -import { - PhoneNumberInputFormik, - maskValue, - mask -} from 'src/components/inputs/formik/PhoneNumberInput' -import { Link } from 'src/components/buttons' import ErrorMessage from 'src/components/ErrorMessage' +import { Link } from 'src/components/buttons' +import RadioGroupFormik from 'src/components/inputs/formik/RadioGroup' +import TextInputFormik from 'src/components/inputs/formik/TextInput' +import { Info2, Info3, Label1, Label3 } from 'src/components/typography' import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg' +import { fromNamespace, toNamespace, namespaces } from 'src/utils/config' import { styles as globalStyles, contactInfoStyles } from './OperatorInfo.styles' +const validationSchema = Yup.object().shape({ + infoCardEnabled: Yup.boolean().required(), + fullName: Yup.string().required(), + phoneNumber: Yup.string().required(), + email: Yup.string() + .email('Please enter a valid email address') + .required(), + website: Yup.string().required(), + companyNumber: Yup.string().required() +}) + const fieldStyles = { field: { position: 'relative', @@ -38,12 +45,13 @@ const fieldStyles = { '& > p:first-child': { height: 16, lineHeight: '16px', - paddingLeft: 3, + transform: 'scale(0.75)', + transformOrigin: 'left', + paddingLeft: 0, margin: [[0, 0, 5, 0]] }, '& > p:last-child': { - margin: 0, - paddingLeft: 4 + margin: 0 } } } @@ -62,7 +70,7 @@ const Field = ({ editing, field, displayValue, ...props }) => {
{!editing && ( <> - {field.label} + {field.label} {displayValue(field.value)} )} @@ -93,9 +101,6 @@ const SAVE_CONFIG = gql` } ` -const INFO_CARD_ENABLED = 'On' -const INFO_CARD_DISABLED = 'Off' - const styles = R.merge(globalStyles, contactInfoStyles) const contactUseStyles = makeStyles(styles) @@ -106,8 +111,7 @@ const ContactInfo = () => { const [error, setError] = useState(null) const [saveConfig] = useMutation(SAVE_CONFIG, { onCompleted: data => { - const { operatorInfo } = data.saveConfig - setInfo(operatorInfo) + setInfo(fromNamespace(namespaces.CONTACT_INFO, data.saveConfig)) setEditing(false) }, onError: e => setError(e) @@ -115,13 +119,14 @@ const ContactInfo = () => { useQuery(GET_CONFIG, { onCompleted: data => { - const { operatorInfo } = data.config - setInfo(operatorInfo ?? {}) + setInfo(fromNamespace(namespaces.CONTACT_INFO, data.config)) } }) const save = it => { - return saveConfig({ variables: { config: { operatorInfo: it } } }) + return saveConfig({ + variables: { config: toNamespace(namespaces.CONTACT_INFO, it) } + }) } const classes = contactUseStyles() @@ -132,43 +137,37 @@ const ContactInfo = () => { { name: 'infoCardEnabled', label: 'Info Card Enabled', - value: info.infoCardEnabled ?? INFO_CARD_DISABLED, - type: 'select', + value: String(info.infoCardEnabled), component: RadioGroupFormik }, { name: 'fullName', label: 'Full name', value: info.fullName ?? '', - type: 'text', component: TextInputFormik }, { name: 'phoneNumber', label: 'Phone number', - value: maskValue(info.phoneNumber) ?? '', - type: 'text', - component: PhoneNumberInputFormik + value: info.phoneNumber ?? '', + component: TextInputFormik }, { name: 'email', label: 'Email', value: info.email ?? '', - type: 'text', component: TextInputFormik }, { name: 'website', label: 'Website', value: info.website ?? '', - type: 'text', component: TextInputFormik }, { name: 'companyNumber', label: 'Company number', value: info.companyNumber ?? '', - type: 'text', component: TextInputFormik } ] @@ -186,27 +185,7 @@ const ContactInfo = () => { email: findValue('email'), website: findValue('website'), companyNumber: findValue('companyNumber') - }, - validationSchema: Yup.object().shape({ - fullName: Yup.string() - .max(100, 'Too long') - .required(), - phoneNumber: Yup.string() - .matches(mask, { excludeEmptyString: true }) - .max(100, 'Too long') - .required(), - email: Yup.string() - .email('Please enter a valid email address') - .max(100, 'Too long') - .required(), - website: Yup.string() - .url('Please enter a valid url') - .max(100, 'Too long') - .required(), - companyNumber: Yup.string() - .max(30, 'Too long') - .required() - }) + } } return ( @@ -225,9 +204,9 @@ const ContactInfo = () => { save(values)} - onReset={(values, bag) => { + validationSchema={validationSchema} + onSubmit={values => save(validationSchema.cast(values))} + onReset={() => { setEditing(false) setError(null) }}> @@ -236,10 +215,10 @@ const ContactInfo = () => { (it === 'true' ? 'On' : 'Off')} options={[ - { label: 'On', value: INFO_CARD_ENABLED }, - { label: 'Off', value: INFO_CARD_DISABLED } + { display: 'On', code: 'true' }, + { display: 'Off', code: 'false' } ]} className={classes.radioButtons} resetError={() => setError(null)} diff --git a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js index e38be6c7..998f0c0f 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js @@ -1,27 +1,26 @@ import { makeStyles } from '@material-ui/core' -import * as R from 'ramda' +import Grid from '@material-ui/core/Grid' import React, { useState } from 'react' -import Title from 'src/components/Title' import Sidebar from 'src/components/layout/Sidebar' - -import logsStyles from '../Logs.styles' +import TitleSection from 'src/components/layout/TitleSection' import CoinAtmRadar from './CoinATMRadar' import ContactInfo from './ContactInfo' import ReceiptPrinting from './ReceiptPrinting' import TermsConditions from './TermsConditions' -const localStyles = { - contentWrapper: { - width: '100%', +const styles = { + grid: { + flex: 1, + height: '100%' + }, + content: { marginLeft: 48, paddingTop: 15 } } -const styles = R.merge(logsStyles, localStyles) - const useStyles = makeStyles(styles) const CONTACT_INFORMATION = 'Contact information' @@ -39,25 +38,21 @@ const OperatorInfo = () => { return ( <> -
-
- Operator information -
-
-
+ + it} onClick={it => setSelected(it)} /> -
+
{isSelected(CONTACT_INFORMATION) && } {isSelected(RECEIPT) && } {isSelected(TERMS_CONDITIONS) && } {isSelected(COIN_ATM_RADAR) && }
-
+
) } diff --git a/new-lamassu-admin/src/pages/OperatorInfo/TermsConditions.js b/new-lamassu-admin/src/pages/OperatorInfo/TermsConditions.js index a15e3724..88a52837 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/TermsConditions.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/TermsConditions.js @@ -1,17 +1,18 @@ -import React, { useState } from 'react' -import classnames from 'classnames' -import * as R from 'ramda' -import * as Yup from 'yup' -import { Form, Formik, Field } from 'formik' -import { gql } from 'apollo-boost' -import { makeStyles } from '@material-ui/core' import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import { gql } from 'apollo-boost' +import classnames from 'classnames' +import { Form, Formik, Field } from 'formik' +import * as R from 'ramda' +import React, { useState } from 'react' +import * as Yup from 'yup' -import { Info2, Label2 } from 'src/components/typography' +import ErrorMessage from 'src/components/ErrorMessage' +import { Button } from 'src/components/buttons' import { Switch } from 'src/components/inputs' import TextInputFormik from 'src/components/inputs/formik/TextInput' -import { Button } from 'src/components/buttons' -import ErrorMessage from 'src/components/ErrorMessage' +import { Info2, Label2 } from 'src/components/typography' +import { fromNamespace, toNamespace, namespaces } from 'src/utils/config' import { styles as globalStyles, @@ -40,7 +41,10 @@ const TermsConditions = () => { const [error, setError] = useState(null) const [saveConfig] = useMutation(SAVE_CONFIG, { onCompleted: data => { - const { termsAndConditions } = data.saveConfig + const termsAndConditions = fromNamespace( + namespaces.TERMS_CONDITIONS, + data.saveConfig + ) setFormData(termsAndConditions) setShowOnScreen(termsAndConditions.show) setError(null) @@ -52,7 +56,10 @@ const TermsConditions = () => { useQuery(GET_CONFIG, { onCompleted: data => { - const { termsAndConditions } = data.config + const termsAndConditions = fromNamespace( + namespaces.TERMS_CONDITIONS, + data.config + ) setFormData(termsAndConditions ?? {}) setShowOnScreen(termsAndConditions?.show ?? false) } @@ -61,7 +68,7 @@ const TermsConditions = () => { const save = it => { setError(null) return saveConfig({ - variables: { config: { termsAndConditions: it } } + variables: { config: toNamespace(namespaces.TERMS_CONDITIONS, it) } }) } diff --git a/new-lamassu-admin/src/utils/config.js b/new-lamassu-admin/src/utils/config.js index f4f3c810..9fca6ab0 100644 --- a/new-lamassu-admin/src/utils/config.js +++ b/new-lamassu-admin/src/utils/config.js @@ -7,7 +7,11 @@ const namespaces = { NOTIFICATIONS: 'notifications', SERVICES: 'services', LOCALE: 'locale', - COMMISSIONS: 'commissions' + COMMISSIONS: 'commissions', + CONTACT_INFO: 'contactInfo', + RECEIPT: 'receipt', + COIN_ATM_RADAR: 'coinAtmRadar', + TERMS_CONDITIONS: 'termsConditions' } const mapKeys = R.curry((fn, obj) => From 6b3db134e7c38fa7bc542c0ccb40711da3a3a437 Mon Sep 17 00:00:00 2001 From: Taranto Date: Wed, 22 Apr 2020 17:28:11 +0100 Subject: [PATCH 25/29] fix: make accounts a object --- lib/new-admin/graphql/schema.js | 6 ++---- lib/new-settings-loader.js | 13 ++----------- new-lamassu-admin/src/pages/Services/Services.js | 14 ++++++-------- new-lamassu-admin/src/pages/Wallet/Wallet.js | 2 +- new-lamassu-admin/src/pages/Wallet/Wizard.js | 12 +++++++----- new-lamassu-admin/src/pages/Wallet/WizardStep.js | 15 +++++++-------- new-lamassu-admin/src/utils/config.js | 2 +- 7 files changed, 26 insertions(+), 38 deletions(-) diff --git a/lib/new-admin/graphql/schema.js b/lib/new-admin/graphql/schema.js index a59fa041..08e0a4f4 100644 --- a/lib/new-admin/graphql/schema.js +++ b/lib/new-admin/graphql/schema.js @@ -196,7 +196,7 @@ const typeDefs = gql` uptime: [ProcessStatus] serverLogs: [ServerLog] transactions: [Transaction] - accounts: [JSONObject] + accounts: JSONObject config: JSONObject } @@ -221,8 +221,7 @@ const typeDefs = gql` setCustomer(customerId: ID!, customerInput: CustomerInput): Customer saveConfig(config: JSONObject): JSONObject createPairingTotem(name: String!): String - saveAccount(account: JSONObject): [JSONObject] - saveAccounts(accounts: [JSONObject]): [JSONObject] + saveAccounts(accounts: JSONObject): JSONObject } ` @@ -259,7 +258,6 @@ const resolvers = { machineSupportLogs: (...[, { deviceId }]) => supportLogs.insert(deviceId), createPairingTotem: (...[, { name }]) => pairing.totem(name), serverSupportLogs: () => serverLogs.insert(), - saveAccount: (...[, { account }]) => settingsLoader.saveAccounts([account]), saveAccounts: (...[, { accounts }]) => settingsLoader.saveAccounts(accounts), setCustomer: (...[, { customerId, customerInput } ]) => customers.updateCustomer(customerId, customerInput), saveConfig: (...[, { config }]) => settingsLoader.saveConfig(config) diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index 7ae6a3ab..e789a436 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -9,20 +9,11 @@ low(adapter).then(it => { db = it }) -function replace (array, index, value) { - return array.slice(0, index).concat([value]).concat(array.slice(index + 1)) -} - -function replaceOrAdd (accounts, account) { - const index = _.findIndex(['code', account.code], accounts) - return index !== -1 ? replace(accounts, index, account) : _.concat(accounts)(account) -} - function saveAccounts (accountsToSave) { const currentState = db.getState() || {} - const accounts = currentState.accounts || [] + const accounts = currentState.accounts || {} - const newAccounts = _.reduce(replaceOrAdd)(accounts)(accountsToSave) + const newAccounts = _.assign(accounts)(accountsToSave) const newState = _.set('accounts', newAccounts, currentState) db.setState(newState) diff --git a/new-lamassu-admin/src/pages/Services/Services.js b/new-lamassu-admin/src/pages/Services/Services.js index 62c5af9b..54103118 100644 --- a/new-lamassu-admin/src/pages/Services/Services.js +++ b/new-lamassu-admin/src/pages/Services/Services.js @@ -19,8 +19,8 @@ const GET_INFO = gql` ` const SAVE_ACCOUNT = gql` - mutation Save($account: JSONObject) { - saveAccount(account: $account) + mutation Save($accounts: JSONObject) { + saveAccounts(accounts: $accounts) } ` @@ -45,13 +45,11 @@ const Services = ({ key: SCREEN_KEY }) => { const classes = useStyles() - const accounts = data?.accounts ?? [] - - const getValue = code => R.find(R.propEq('code', code))(accounts) + const accounts = data?.accounts ?? {} const getItems = (code, elements) => { const faceElements = R.filter(R.prop('face'))(elements) - const values = getValue(code) || {} + const values = accounts[code] || {} return R.map(({ display, code, long }) => ({ label: display, value: long ? formatLong(values[code]) : values[code] @@ -81,12 +79,12 @@ const Services = ({ key: SCREEN_KEY }) => { saveAccount({ - variables: { account: { code: editingSchema.code, ...it } } + variables: { accounts: { [editingSchema.code]: it } } }) } elements={editingSchema.elements} validationSchema={editingSchema.validationSchema} - value={getValue(editingSchema.code)} + value={accounts[editingSchema.code]} /> )} diff --git a/new-lamassu-admin/src/pages/Wallet/Wallet.js b/new-lamassu-admin/src/pages/Wallet/Wallet.js index 9285969c..492916ed 100644 --- a/new-lamassu-admin/src/pages/Wallet/Wallet.js +++ b/new-lamassu-admin/src/pages/Wallet/Wallet.js @@ -11,7 +11,7 @@ import Wizard from './Wizard' import { WalletSchema, getElements } from './helper' const SAVE_CONFIG = gql` - mutation Save($config: JSONObject, $accounts: [JSONObject]) { + mutation Save($config: JSONObject, $accounts: JSONObject) { saveConfig(config: $config) saveAccounts(accounts: $accounts) } diff --git a/new-lamassu-admin/src/pages/Wallet/Wizard.js b/new-lamassu-admin/src/pages/Wallet/Wizard.js index 71fddde4..b75efd31 100644 --- a/new-lamassu-admin/src/pages/Wallet/Wizard.js +++ b/new-lamassu-admin/src/pages/Wallet/Wizard.js @@ -18,7 +18,7 @@ const filterConfig = (crypto, type) => const getItems = (accountsConfig, accounts, type, crypto) => { const fConfig = filterConfig(crypto, type)(accountsConfig) - const find = code => R.find(R.propEq('code', code))(accounts) + const find = code => accounts && accounts[code] const [filled, unfilled] = R.partition(({ code }) => { const account = find(code) @@ -35,7 +35,7 @@ const Wizard = ({ coin, onClose, accountsConfig, accounts, save, error }) => { const [{ step, config, accountsToSave }, setState] = useState({ step: 0, config: { active: true }, - accountsToSave: [] + accountsToSave: {} }) const title = `Enable ${coin.display}` @@ -48,9 +48,11 @@ const Wizard = ({ coin, onClose, accountsConfig, accounts, save, error }) => { const getValue = code => R.find(R.propEq('code', code))(accounts) - const onContinue = async (it, it2) => { - const newConfig = R.merge(config, it) - const newAccounts = it2 ? R.concat(accountsToSave, [it2]) : accountsToSave + const onContinue = async (stepConfig, stepAccount) => { + const newConfig = R.merge(config, stepConfig) + const newAccounts = stepAccount + ? R.merge(accountsToSave, stepAccount) + : accountsToSave if (isLastStep) { return save(toNamespace(coin.code, newConfig), newAccounts) diff --git a/new-lamassu-admin/src/pages/Wallet/WizardStep.js b/new-lamassu-admin/src/pages/Wallet/WizardStep.js index 4482764f..ed8dd072 100644 --- a/new-lamassu-admin/src/pages/Wallet/WizardStep.js +++ b/new-lamassu-admin/src/pages/Wallet/WizardStep.js @@ -13,6 +13,7 @@ import schema from 'src/pages/Services/schemas' import { startCase } from 'src/utils/string' import styles from './WizardStep.styles' + const useStyles = makeStyles(styles) const initialState = { @@ -41,7 +42,7 @@ const reducer = (state, action) => { iError: false } case 'error': - return R.merge(state, { iError: true }) + return R.merge(state, { innerError: true }) case 'reset': return initialState default: @@ -61,7 +62,7 @@ const WizardStep = ({ getValue }) => { const classes = useStyles() - const [{ iError, selected, form, isNew }, dispatch] = useReducer( + const [{ innerError, selected, form, isNew }, dispatch] = useReducer( reducer, initialState ) @@ -70,7 +71,7 @@ const WizardStep = ({ dispatch({ type: 'reset' }) }, [step]) - const iContinue = (config, account) => { + const innerContinue = (config, account) => { if (!config || !config[type]) { return dispatch({ type: 'error' }) } @@ -81,7 +82,7 @@ const WizardStep = ({ const displayName = name ?? type const subtitleClass = { [classes.subtitle]: true, - [classes.error]: iError + [classes.error]: innerError } return ( @@ -129,9 +130,7 @@ const WizardStep = ({
{form && ( - iContinue({ [type]: form.code }, R.merge(it, { code: form.code })) - } + save={it => innerContinue({ [type]: form.code }, { [form.code]: it })} elements={schema[form.code].elements} validationSchema={schema[form.code].validationSchema} value={getValue(form.code)} @@ -143,7 +142,7 @@ const WizardStep = ({ {error && Failed to save}
diff --git a/new-lamassu-admin/src/utils/config.js b/new-lamassu-admin/src/utils/config.js index 9fca6ab0..f6c8efe8 100644 --- a/new-lamassu-admin/src/utils/config.js +++ b/new-lamassu-admin/src/utils/config.js @@ -8,7 +8,7 @@ const namespaces = { SERVICES: 'services', LOCALE: 'locale', COMMISSIONS: 'commissions', - CONTACT_INFO: 'contactInfo', + CONTACT_INFO: 'operatorInfo', RECEIPT: 'receipt', COIN_ATM_RADAR: 'coinAtmRadar', TERMS_CONDITIONS: 'termsConditions' From 278c9777632f71c447c9556ed2d524149d92a7b5 Mon Sep 17 00:00:00 2001 From: naconner <7396812+naconner@users.noreply.github.com> Date: Wed, 20 May 2020 12:54:44 -0400 Subject: [PATCH 26/29] chore: update BCH, ZEC, ETH --- lib/blockchain/common.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 74f99a45..951152b3 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -25,12 +25,12 @@ const BINARIES = { dir: 'bitcoin-0.19.1/bin' }, ETH: { - url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.12-b6f1c8dc.tar.gz', - dir: 'geth-linux-amd64-1.9.12-b6f1c8dc' + url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.14-6d74d1e5.tar.gz', + dir: 'geth-linux-amd64-1.9.14-6d74d1e5' }, ZEC: { - url: 'https://z.cash/downloads/zcash-2.1.1-1-linux64-debian-jessie.tar.gz', - dir: 'zcash-2.1.1-1/bin' + url: 'https://z.cash/downloads/zcash-2.1.2-3-linux64-debian-jessie.tar.gz', + dir: 'zcash-2.1.2-3/bin' }, DASH: { url: 'https://github.com/dashpay/dash/releases/download/v0.15.0.0/dashcore-0.15.0.0-x86_64-linux-gnu.tar.gz', @@ -41,8 +41,8 @@ const BINARIES = { dir: 'litecoin-0.17.1/bin' }, BCH: { - url: 'https://download.bitcoinabc.org/0.21.2/linux/bitcoin-abc-0.21.2-x86_64-linux-gnu.tar.gz', - dir: 'bitcoin-abc-0.21.2/bin', + url: 'https://download.bitcoinabc.org/0.21.7/linux/bitcoin-abc-0.21.7-x86_64-linux-gnu.tar.gz', + dir: 'bitcoin-abc-0.21.7/bin', files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']] } } From 6e9feb6be4f7dffa7037397cb36fb85d3d3a0340 Mon Sep 17 00:00:00 2001 From: Taranto Date: Wed, 6 May 2020 22:21:39 +0100 Subject: [PATCH 27/29] fix: rename fudge factor and change value --- lib/wallet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/wallet.js b/lib/wallet.js index 49124ef9..10f334d8 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -122,8 +122,8 @@ function mergeStatusMode (a, b) { } function getWalletStatus (settings, tx) { - const fudgeFactorEnabled = configManager.unscoped(settings.config).fudgeFactor - const fudgeFactor = fudgeFactorEnabled ? 10 : 0 + const fudgeFactorEnabled = configManager.unscoped(settings.config).fudgeFactorActive + const fudgeFactor = fudgeFactorEnabled ? 100 : 0 const walletStatusPromise = fetchWallet(settings, tx.cryptoCode) .then(r => r.wallet.getStatus(r.account, tx.toAddress, tx.cryptoAtoms.minus(fudgeFactor), tx.cryptoCode)) From bf8f1d991c6b27869e0bac82f717b6325c5a0012 Mon Sep 17 00:00:00 2001 From: Taranto Date: Wed, 6 May 2020 22:11:55 +0100 Subject: [PATCH 28/29] chore: integrate new admin with l-s --- bin/lamassu-display-config.js | 4 +- bin/lamassu-send-coins | 7 +- dev/coinatmradar.js | 5 +- dev/config.js | 6 +- dev/plugins.js | 2 +- dev/send-message.js | 2 +- lib/app.js | 10 +- lib/cash-in/cash-in-tx.js | 13 +- lib/coinatmradar/coinatmradar.js | 95 ++++--- lib/coinatmradar/new-coinatmradar.js | 178 ------------- lib/compliance-triggers.js | 9 + lib/email.js | 7 +- lib/exchange.js | 4 +- lib/layer2.js | 7 +- lib/machine-loader.js | 29 +- lib/new-admin/admin-server.js | 6 +- lib/new-admin/config/accounts.js | 5 +- lib/new-admin/funding.js | 25 +- lib/new-admin/graphql/schema.js | 6 +- lib/new-admin/machines.js | 4 +- lib/new-config-manager.js | 93 +++++++ lib/new-settings-loader.js | 26 +- lib/notifier.js | 145 +++++++--- lib/pairing.js | 29 +- lib/plugins.js | 162 ++++++------ lib/poller.js | 12 +- lib/route-helpers.js | 27 +- lib/routes.js | 114 ++++---- lib/sms.js | 6 +- lib/ticker.js | 5 +- lib/wallet.js | 13 +- new-lamassu-admin/src/components/Modal.js | 44 +-- .../src/components/editableTable/Row.js | 2 + .../src/components/editableTable/Table.js | 2 + .../src/components/fake-table/Table.js | 15 +- .../src/components/fake-table/Table.styles.js | 12 +- .../src/components/inputs/base/TextInput.js | 2 +- .../components/inputs/formik/RadioGroup.js | 3 +- .../src/components/inputs/formik/index.js | 4 +- .../src/pages/Cashout/Cashout.js | 4 +- new-lamassu-admin/src/pages/Cashout/Wizard.js | 24 +- .../src/pages/Cashout/WizardStep.js | 102 +++---- new-lamassu-admin/src/pages/Cashout/helper.js | 13 +- new-lamassu-admin/src/pages/Cashout/index.js | 3 + new-lamassu-admin/src/pages/Commissions.js | 179 ------------- .../src/pages/Commissions.module.scss | 30 --- .../src/pages/Commissions/Commissions.js | 96 +++++++ .../src/pages/Commissions/helper.js | 156 +++++++++++ .../src/pages/Commissions/index.js | 3 + .../src/pages/Customers/Customers.js | 4 +- .../src/pages/Maintenance/Cashboxes.js | 117 ++++++++ .../MachineDetailsCard.js | 10 +- .../MachineStatus.js | 0 .../src/pages/OperatorInfo/ContactInfo.js | 36 +-- .../ReceiptPrinting/ReceiptPrinting.js | 28 +- .../src/pages/OperatorInfo/TermsConditions.js | 20 +- .../src/pages/Services/Services.js | 2 +- .../src/pages/Triggers/NewTriggerWizard.js | 85 ------ .../pages/Triggers/SelectTriggerDirection.js | 77 ------ .../Triggers/SelectTriggerRequirements.js | 141 ---------- .../src/pages/Triggers/SelectTriggerType.js | 104 -------- .../src/pages/Triggers/Triggers.js | 105 ++++---- .../src/pages/Triggers/Triggers.styles.js | 21 +- .../src/pages/Triggers/Wizard.js | 103 ++++++++ .../src/pages/Triggers/helper.js | 250 ++++++++++++++++++ .../src/pages/Wallet/WizardStep.js | 2 +- new-lamassu-admin/src/pages/common.styles.js | 17 -- .../src/pages/maintenance/Cashboxes.js | 210 --------------- new-lamassu-admin/src/routing/routes.js | 9 +- new-lamassu-admin/src/utils/config.js | 4 +- package-lock.json | 3 +- package.json | 6 +- 72 files changed, 1493 insertions(+), 1611 deletions(-) delete mode 100644 lib/coinatmradar/new-coinatmradar.js create mode 100644 lib/compliance-triggers.js create mode 100644 lib/new-config-manager.js create mode 100644 new-lamassu-admin/src/pages/Cashout/index.js delete mode 100644 new-lamassu-admin/src/pages/Commissions.js delete mode 100644 new-lamassu-admin/src/pages/Commissions.module.scss create mode 100644 new-lamassu-admin/src/pages/Commissions/Commissions.js create mode 100644 new-lamassu-admin/src/pages/Commissions/helper.js create mode 100644 new-lamassu-admin/src/pages/Commissions/index.js create mode 100644 new-lamassu-admin/src/pages/Maintenance/Cashboxes.js rename new-lamassu-admin/src/pages/{maintenance => Maintenance}/MachineDetailsCard.js (100%) rename new-lamassu-admin/src/pages/{maintenance => Maintenance}/MachineStatus.js (100%) delete mode 100644 new-lamassu-admin/src/pages/Triggers/NewTriggerWizard.js delete mode 100644 new-lamassu-admin/src/pages/Triggers/SelectTriggerDirection.js delete mode 100644 new-lamassu-admin/src/pages/Triggers/SelectTriggerRequirements.js delete mode 100644 new-lamassu-admin/src/pages/Triggers/SelectTriggerType.js create mode 100644 new-lamassu-admin/src/pages/Triggers/Wizard.js create mode 100644 new-lamassu-admin/src/pages/Triggers/helper.js delete mode 100644 new-lamassu-admin/src/pages/common.styles.js delete mode 100644 new-lamassu-admin/src/pages/maintenance/Cashboxes.js diff --git a/bin/lamassu-display-config.js b/bin/lamassu-display-config.js index ed29d4b7..2b4cb00b 100644 --- a/bin/lamassu-display-config.js +++ b/bin/lamassu-display-config.js @@ -1,4 +1,4 @@ -const settingsLoader = require('../lib/settings-loader') +const settingsLoader = require('../lib/new-settings-loader') const pp = require('../lib/pp') settingsLoader.loadLatest() @@ -9,4 +9,4 @@ settingsLoader.loadLatest() .catch(e => { console.log(e.stack) process.exit(1) - }) + }) \ No newline at end of file diff --git a/bin/lamassu-send-coins b/bin/lamassu-send-coins index 51ac67b3..9d829002 100755 --- a/bin/lamassu-send-coins +++ b/bin/lamassu-send-coins @@ -1,7 +1,7 @@ #!/usr/bin/env node -const settingsLoader = require('../lib/settings-loader') -const configManager = require('../lib/config-manager') +const settingsLoader = require('../lib/new-settings-loader') +const configManager = require('../lib/new-config-manager') const wallet = require('../lib/wallet') const coinUtils = require('../lib/coin-utils') const BN = require('../lib/bn') @@ -40,8 +40,7 @@ console.log('Loading ticker...') settingsLoader.loadLatest() .then(settings => { - const config = configManager.unscoped(settings.config) - const fiatCode = config.fiatCurrency + const fiatCode = configManager.getGlobalLocale(settings.config).fiatCurrency return wallet.isStrictAddress(settings, cryptoCode, toAddress) .then(isValid => { diff --git a/dev/coinatmradar.js b/dev/coinatmradar.js index 62161f25..4f4cf1be 100644 --- a/dev/coinatmradar.js +++ b/dev/coinatmradar.js @@ -1,14 +1,13 @@ const car = require('../lib/coinatmradar/coinatmradar') const plugins = require('../lib/plugins') -require('../lib/settings-loader').loadLatest() +require('../lib/new-settings-loader').loadLatest() .then(settings => { const pi = plugins(settings) - const config = settings.config return pi.getRawRates() .then(rates => { - return car.update({rates, config}, settings) + return car.update(rates, settings) .then(require('../lib/pp')('DEBUG100')) .catch(console.log) .then(() => process.exit()) diff --git a/dev/config.js b/dev/config.js index 51dc2c3e..97a2d33a 100644 --- a/dev/config.js +++ b/dev/config.js @@ -1,8 +1,8 @@ -const settingsLoader = require('../lib/settings-loader') -const configManager = require('../lib/config-manager') +const settingsLoader = require('../lib/new-settings-loader') +const configManager = require('../lib/new-config-manager') settingsLoader.loadLatest() .then(settings => { const config = settings.config - require('../lib/pp')('config')(configManager.all('cryptoCurrencies', config)) + require('../lib/pp')('config')(configManager.getAllCryptoCurrencies(config)) }) diff --git a/dev/plugins.js b/dev/plugins.js index aea224a3..30488199 100644 --- a/dev/plugins.js +++ b/dev/plugins.js @@ -1,5 +1,5 @@ const plugins = require('../lib/plugins') -const settingsLoader = require('../lib/settings-loader') +const settingsLoader = require('../lib/new-settings-loader') const pp = require('../lib/pp') settingsLoader.loadLatest() diff --git a/dev/send-message.js b/dev/send-message.js index f72f6faf..dcf8acd2 100644 --- a/dev/send-message.js +++ b/dev/send-message.js @@ -1,6 +1,6 @@ require('es6-promise').polyfill() -var config = require('../lib/settings-loader') +var config = require('../lib/new-settings-loader') var sms = require('../lib/sms') var rand = Math.floor(Math.random() * 1e6) diff --git a/lib/app.js b/lib/app.js index a5661ebf..293f5b74 100644 --- a/lib/app.js +++ b/lib/app.js @@ -6,8 +6,9 @@ const argv = require('minimist')(process.argv.slice(2)) const routes = require('./routes') const logger = require('./logger') const poller = require('./poller') -const settingsLoader = require('./settings-loader') -const configManager = require('./config-manager') +const settingsLoader = require('./new-settings-loader') +const configManager = require('./new-config-manager') +const complianceTriggers = require('./compliance-triggers') const options = require('./options') const ofac = require('./ofac/index') const ofacUpdate = require('./ofac/update') @@ -43,9 +44,10 @@ function run () { function loadSanctions (settings) { return Promise.resolve() .then(() => { - const config = configManager.unscoped(settings.config) + const triggers = configManager.getTriggers(settings.config) + const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) - if (!config.sanctionsVerificationActive) return + if (!compatTriggers.sanctions) return logger.info('Loading sanctions DB...') return ofacUpdate.update() diff --git a/lib/cash-in/cash-in-tx.js b/lib/cash-in/cash-in-tx.js index 0ef93717..2c8da04f 100644 --- a/lib/cash-in/cash-in-tx.js +++ b/lib/cash-in/cash-in-tx.js @@ -6,8 +6,8 @@ const blacklist = require('../blacklist') const db = require('../db') const plugins = require('../plugins') const logger = require('../logger') -const settingsLoader = require('../settings-loader') -const configManager = require('../config-manager') +const settingsLoader = require('../new-settings-loader') +// const configManager = require('../new-config-manager') const cashInAtomic = require('./cash-in-atomic') const cashInLow = require('./cash-in-low') @@ -26,7 +26,9 @@ function post (machineTx, pi) { return Promise.all([settingsLoader.loadLatest(), checkForBlacklisted(updatedTx)]) .then(([{ config }, blacklistItems]) => { - const rejectAddressReuseActive = configManager.unscoped(config).rejectAddressReuseActive + // TODO new-admin: addressReuse doesnt exist + // const rejectAddressReuseActive = configManager.unscoped(config).rejectAddressReuseActive + const rejectAddressReuseActive = true if (_.some(it => it.created_by_operator === true)(blacklistItems)) { blacklisted = true @@ -123,8 +125,9 @@ function postProcess (r, pi, isBlacklisted, addressReuse) { }) .then(sendRec => { settingsLoader.loadLatest().then(it => { - const config = configManager.unscoped(it.config) - if (config.rejectAddressReuseActive) { + // TODO new-admin: addressReuse doesnt exist + // const config = configManager.unscoped(it.config) + if (true) { blacklist.addToUsedAddresses(r.tx.toAddress, r.tx.cryptoCode) .catch(err => logger.error('Failure adding to addressReuse', err)) } diff --git a/lib/coinatmradar/coinatmradar.js b/lib/coinatmradar/coinatmradar.js index 78acf983..f774a4c4 100644 --- a/lib/coinatmradar/coinatmradar.js +++ b/lib/coinatmradar/coinatmradar.js @@ -7,7 +7,8 @@ const fs = pify(require('fs')) const db = require('../db') const mnemonicHelpers = require('../mnemonic-helpers') -const configManager = require('../config-manager') +const configManager = require('../new-config-manager') +const complianceTriggers = require('../compliance-triggers') const options = require('../options') const logger = require('../logger') const plugins = require('../plugins') @@ -18,19 +19,21 @@ const MAX_CONTENT_LENGTH = 2000 // How long a machine can be down before it's considered offline const STALE_INTERVAL = '2 minutes' -module.exports = { update, mapRecord } +module.exports = { update } -function mapCoin (info, deviceId, settings, cryptoCode) { - const config = info.config - const rates = plugins(settings, deviceId).buildRates(info.rates)[cryptoCode] || { cashIn: null, cashOut: null } - const cryptoConfig = configManager.scoped(cryptoCode, deviceId, config) - const unscoped = configManager.unscoped(config) - const showRates = unscoped.coinAtmRadarShowRates +function mapCoin (rates, deviceId, settings, cryptoCode) { + const config = settings.config + const buildedRates = plugins(settings, deviceId).buildRates(rates)[cryptoCode] || { cashIn: null, cashOut: null } - const cashInFee = showRates ? cryptoConfig.cashInCommission / 100 : null - const cashOutFee = showRates ? cryptoConfig.cashOutCommission / 100 : null - const cashInRate = showRates ? _.invoke('cashIn.toNumber', rates) : null - const cashOutRate = showRates ? _.invoke('cashOut.toNumber', rates) : null + const commissions = configManager.getCommissions(cryptoCode, deviceId, config) + const coinAtmRadar = configManager.getCoinAtmRadar(config) + + const showCommissions = coinAtmRadar.commissions + + const cashInFee = showCommissions ? commissions.cashIn / 100 : null + const cashOutFee = showCommissions ? commissions.cashOut / 100 : null + const cashInRate = showCommissions ? _.invoke('cashIn.toNumber', buildedRates) : null + const cashOutRate = showCommissions ? _.invoke('cashOut.toNumber', buildedRates) : null return { cryptoCode, @@ -41,33 +44,51 @@ function mapCoin (info, deviceId, settings, cryptoCode) { } } -function mapIdentification (info, deviceId) { - const machineConfig = configManager.machineScoped(deviceId, info.config) +function mapIdentification (config, deviceId) { + const triggers = configManager.getTriggers(deviceId, config) + const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) return { - isPhone: machineConfig.smsVerificationActive, + isPhone: !!compatTriggers.sms, isPalmVein: false, - isPhoto: false, - isIdDocScan: machineConfig.idCardDataVerificationActive, + isPhoto: !!compatTriggers.facephoto, + isIdDocScan: !!compatTriggers.idData, isFingerprint: false } } -function mapMachine (info, settings, machineRow) { +function mapMachine (rates, settings, machineRow) { const deviceId = machineRow.device_id - const config = info.config - const machineConfig = configManager.machineScoped(deviceId, config) + const config = settings.config + + const coinAtmRadar = configManager.getCoinAtmRadar(config) + const triggers = configManager.getTriggers(deviceId, config) + const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) + const locale = configManager.getLocale(deviceId, config) + const cashOutConfig = configManager.getCashOut(deviceId, config) const lastOnline = machineRow.last_online.toISOString() const status = machineRow.stale ? 'online' : 'offline' + const showSupportedCryptocurrencies = coinAtmRadar.supportedCryptocurrencies + const showSupportedFiat = coinAtmRadar.supportedFiat + const showSupportedBuySellDirection = coinAtmRadar.supportedBuySellDirection + const showLimitsAndVerification = coinAtmRadar.limitsAndVerification - const cashLimit = machineConfig.hardLimitVerificationActive - ? machineConfig.hardLimitVerificationThreshold - : Infinity + // TODO new-admin: this is relaying info with backwards compatible triggers + // need to get in touch with coinatmradar before updating this + const cashLimit = showLimitsAndVerification ? ( + !!compatTriggers.block + ? compatTriggers.block + : Infinity ) : null - const cryptoCurrencies = machineConfig.cryptoCurrencies - const identification = mapIdentification(info, deviceId) - const coins = _.map(_.partial(mapCoin, [info, deviceId, settings]), cryptoCurrencies) + const cryptoCurrencies = locale.cryptoCurrencies + const cashInEnabled = showSupportedBuySellDirection ? true : null + const cashOutEnabled = showSupportedBuySellDirection ? cashOutConfig.active : null + const fiat = showSupportedFiat ? locale.fiatCurrency : null + const identification = mapIdentification(config, deviceId) + const coins = showSupportedCryptocurrencies ? + _.map(_.partial(mapCoin, [rates, deviceId, settings]), cryptoCurrencies) + : null return { machineId: deviceId, @@ -85,27 +106,27 @@ function mapMachine (info, settings, machineRow) { }, status, lastOnline, - cashIn: true, - cashOut: machineConfig.cashOutEnabled, + cashIn: cashInEnabled, + cashOut: cashOutEnabled, manufacturer: 'lamassu', cashInTxLimit: cashLimit, cashOutTxLimit: cashLimit, cashInDailyLimit: cashLimit, cashOutDailyLimit: cashLimit, - fiatCurrency: machineConfig.fiatCurrency, + fiatCurrency: fiat, identification, coins } } -function getMachines (info, settings) { +function getMachines (rates, settings) { const sql = `select device_id, last_online, now() - last_online < $1 as stale from devices where display=TRUE and paired=TRUE order by created` return db.any(sql, [STALE_INTERVAL]) - .then(_.map(_.partial(mapMachine, [info, settings]))) + .then(_.map(_.partial(mapMachine, [rates, settings]))) } function sendRadar (data) { @@ -129,9 +150,9 @@ function sendRadar (data) { .then(r => console.log(r.status)) } -function mapRecord (info, settings) { +function mapRecord (rates, settings) { const timestamp = new Date().toISOString() - return Promise.all([getMachines(info, settings), fs.readFile(options.mnemonicPath, 'utf8')]) + return Promise.all([getMachines(rates, settings), fs.readFile(options.mnemonicPath, 'utf8')]) .then(([machines, mnemonic]) => { return { operatorId: computeOperatorId(mnemonicHelpers.toEntropyBuffer(mnemonic)), @@ -146,12 +167,12 @@ function mapRecord (info, settings) { }) } -function update (info, settings) { - const config = configManager.unscoped(info.config) +function update (rates, settings) { + const coinAtmRadar = configManager.getCoinAtmRadar(settings.config) - if (!config.coinAtmRadarActive) return Promise.resolve() + if (!coinAtmRadar.active) return Promise.resolve() - return mapRecord(info, settings) + return mapRecord(rates, settings) .then(sendRadar) .catch(err => logger.error(`Failure to update CoinATMRadar`, err)) } diff --git a/lib/coinatmradar/new-coinatmradar.js b/lib/coinatmradar/new-coinatmradar.js deleted file mode 100644 index 5c590d11..00000000 --- a/lib/coinatmradar/new-coinatmradar.js +++ /dev/null @@ -1,178 +0,0 @@ -const axios = require('axios') -const _ = require('lodash/fp') -const hkdf = require('futoin-hkdf') - -const pify = require('pify') -const fs = pify(require('fs')) - -const db = require('../db') -const mnemonicHelpers = require('../mnemonic-helpers') -const configManager = require('../config-manager') -const options = require('../options') -const logger = require('../logger') -const plugins = require('../plugins') - -const TIMEOUT = 10000 -const MAX_CONTENT_LENGTH = 2000 - -// How long a machine can be down before it's considered offline -const STALE_INTERVAL = '2 minutes' - -module.exports = { update, mapRecord } - -function mapCoin (info, deviceId, settings, cryptoCode) { - const config = info.config - const rates = plugins(settings, deviceId).buildRates(info.rates)[cryptoCode] || { cashIn: null, cashOut: null } - const cryptoConfig = configManager.scoped(cryptoCode, deviceId, config) - const unscoped = configManager.unscoped(config) - const showCommissions = unscoped.coinAtmRadar.sendCommissions - - const cashInFee = showCommissions ? cryptoConfig.cashInCommission / 100 : null - const cashOutFee = showCommissions ? cryptoConfig.cashOutCommission / 100 : null - const cashInRate = showCommissions ? _.invoke('cashIn.toNumber', rates) : null - const cashOutRate = showCommissions ? _.invoke('cashOut.toNumber', rates) : null - - return { - cryptoCode, - cashInFee, - cashOutFee, - cashInRate, - cashOutRate - } -} - -function mapIdentification (info, deviceId) { - const machineConfig = configManager.machineScoped(deviceId, info.config) - - return { - isPhone: machineConfig.smsVerificationActive, - isPalmVein: false, - isPhoto: false, - isIdDocScan: machineConfig.idCardDataVerificationActive, - isFingerprint: false - } -} - -function mapMachine (info, settings, machineRow) { - const deviceId = machineRow.device_id - const config = info.config - const unscoped = configManager.unscoped(config) - const machineConfig = configManager.machineScoped(deviceId, config) - - const lastOnline = machineRow.last_online.toISOString() - const status = machineRow.stale ? 'online' : 'offline' - const showSupportedCryptocurrencies = - unscoped.coinAtmRadar.sendSupportedCryptocurrencies - const showSupportedFiat = - unscoped.coinAtmRadar.sendSupportedFiat - const showSupportedBuySellDirection = - unscoped.coinAtmRadar.sendSupportedBuySellDirection - const showLimitsAndVerification = - unscoped.coinAtmRadar.sendLimitsAndVerification - - const cashLimit = showLimitsAndVerification ? ( - machineConfig.hardLimitVerificationActive - ? machineConfig.hardLimitVerificationThreshold - : Infinity ) : null - - const cryptoCurrencies = machineConfig.cryptoCurrencies - const cashInEnabled = showSupportedBuySellDirection ? true : null - const cashOutEnabled = showSupportedBuySellDirection - ? machineConfig.cashOutEnabled - : null - const fiat = showSupportedFiat ? machineConfig.fiatCurrency : null - const identification = mapIdentification(info, deviceId) - const coins = showSupportedCryptocurrencies ? - _.map(_.partial(mapCoin, [info, deviceId, settings]), cryptoCurrencies) - : null - - return { - machineId: deviceId, - address: { - streetAddress: null, - city: null, - region: null, - postalCode: null, - country: null - }, - location: { - name: null, - url: null, - phone: null - }, - status, - lastOnline, - cashIn: cashInEnabled, - cashOut: cashOutEnabled, - manufacturer: 'lamassu', - cashInTxLimit: cashLimit, - cashOutTxLimit: cashLimit, - cashInDailyLimit: cashLimit, - cashOutDailyLimit: cashLimit, - fiatCurrency: fiat, - identification, - coins - } -} - -function getMachines (info, settings) { - const sql = `select device_id, last_online, now() - last_online < $1 as stale from devices - where display=TRUE and - paired=TRUE - order by created` - - return db.any(sql, [STALE_INTERVAL]) - .then(_.map(_.partial(mapMachine, [info, settings]))) -} - -function sendRadar (data) { - const url = _.get(['coinAtmRadar', 'url'], options) - - if (_.isEmpty(url)) { - return Promise.reject(new Error('Missing coinAtmRadar url!')) - } - - const config = { - url, - method: 'post', - data, - timeout: TIMEOUT, - maxContentLength: MAX_CONTENT_LENGTH - } - - console.log('%j', data) - - return axios(config) - .then(r => console.log(r.status)) -} - -function mapRecord (info, settings) { - const timestamp = new Date().toISOString() - return Promise.all([getMachines(info, settings), fs.readFile(options.mnemonicPath, 'utf8')]) - .then(([machines, mnemonic]) => { - return { - operatorId: computeOperatorId(mnemonicHelpers.toEntropyBuffer(mnemonic)), - operator: { - name: null, - phone: null, - email: null - }, - timestamp, - machines - } - }) -} - -function update (info, settings) { - const config = configManager.unscoped(info.config) - - if (!config.coinAtmRadar.active) return Promise.resolve() - - return mapRecord(info, settings) - .then(sendRadar) - .catch(err => logger.error(`Failure to update CoinATMRadar`, err)) -} - -function computeOperatorId (masterSeed) { - return hkdf(masterSeed, 16, { salt: 'lamassu-server-salt', info: 'operator-id' }).toString('hex') -} diff --git a/lib/compliance-triggers.js b/lib/compliance-triggers.js new file mode 100644 index 00000000..d08cfdb7 --- /dev/null +++ b/lib/compliance-triggers.js @@ -0,0 +1,9 @@ +const _ = require('lodash/fp') + +function getBackwardsCompatibleTriggers (triggers) { + const filtered = _.filter(_.matches({ triggerType: 'volume', cashDirection: 'both' }))(triggers) + const grouped = _.groupBy(_.prop('requirement'))(filtered) + return _.mapValues(_.compose(_.get('threshold'), _.minBy('threshold')))(grouped) +} + +module.exports = { getBackwardsCompatibleTriggers} \ No newline at end of file diff --git a/lib/email.js b/lib/email.js index 73c46b04..b69b2124 100644 --- a/lib/email.js +++ b/lib/email.js @@ -1,10 +1,13 @@ -const configManager = require('./config-manager') +// const configManager = require('./new-config-manager') +const logger = require('./logger') const ph = require('./plugin-helper') function sendMessage (settings, rec) { return Promise.resolve() .then(() => { - const pluginCode = configManager.unscoped(settings.config).email + // TODO new-admin + // const pluginCode = configManager.unscoped(settings.config).email + const pluginCode = 'mailgun' const plugin = ph.load(ph.EMAIL, pluginCode) const account = settings.accounts[pluginCode] diff --git a/lib/exchange.js b/lib/exchange.js index a15a4b5b..10070c2b 100644 --- a/lib/exchange.js +++ b/lib/exchange.js @@ -1,8 +1,8 @@ -const configManager = require('./config-manager') +const configManager = require('./new-config-manager') const ph = require('./plugin-helper') function lookupExchange (settings, cryptoCode) { - const exchange = configManager.cryptoScoped(cryptoCode, settings.config).exchange + const exchange = configManager.getWalletSettings(cryptoCode, settings.config).exchange if (exchange === 'no-exchange') return null return exchange } diff --git a/lib/layer2.js b/lib/layer2.js index 3376eb53..b77403da 100644 --- a/lib/layer2.js +++ b/lib/layer2.js @@ -1,10 +1,9 @@ -const configManager = require('./config-manager') +const configManager = require('./new-config-manager') const ph = require('./plugin-helper') const _ = require('lodash/fp') -const logger = require('./logger') function fetch (settings, cryptoCode) { - const plugin = configManager.cryptoScoped(cryptoCode, settings.config).layer2 + const plugin = configManager.getWalletSettings(cryptoCode, settings.config).layer2 if (_.isEmpty(plugin) || plugin === 'no-layer2') return Promise.resolve() @@ -34,7 +33,7 @@ function getStatus (settings, tx) { } function cryptoNetwork (settings, cryptoCode) { - const plugin = configManager.cryptoScoped(cryptoCode, settings.config).layer2 + const plugin = configManager.getWalletSettings(cryptoCode, settings.config).layer2 const layer2 = ph.load(ph.LAYER2, plugin) const account = settings.accounts[plugin] diff --git a/lib/machine-loader.js b/lib/machine-loader.js index 4bce608b..37e5f11f 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -3,8 +3,8 @@ const axios = require('axios') const db = require('./db') const pairing = require('./pairing') -const configManager = require('./config-manager') -const settingsLoader = require('./settings-loader') +const configManager = require('./new-config-manager') +const settingsLoader = require('./new-settings-loader') module.exports = {getMachineName, getMachines, getMachineNames, setMachine} @@ -17,6 +17,7 @@ function getMachines () { cassette2: r.cassette2, pairedAt: new Date(r.created).valueOf(), lastPing: new Date(r.last_online).valueOf(), + name: r.name, // TODO: we shall start using this JSON field at some point // location: r.location, paired: r.paired @@ -26,18 +27,20 @@ function getMachines () { function getConfig (defaultConfig) { if (defaultConfig) return Promise.resolve(defaultConfig) - return settingsLoader.loadRecentConfig() + return settingsLoader.loadLatest().config } function getMachineNames (config) { return Promise.all([getMachines(), getConfig(config)]) .then(([machines, config]) => { const addName = r => { - const machineScoped = configManager.machineScoped(r.deviceId, config) - const name = _.defaultTo('', machineScoped.machineName) - const cashOut = machineScoped.cashOutEnabled - const machineModel = _.defaultTo('', machineScoped.machineModel) - const machineLocation = _.defaultTo('', machineScoped.machineLocation) + const cashOutConfig = configManager.getCashOut(r.deviceId, config) + + const cashOut = cashOutConfig.active + + // TODO new-admin: these two fields were not ever working + const machineModel = '' + const machineLocation = '' // TODO: obtain next fields from somewhere const printer = null @@ -45,7 +48,7 @@ function getMachineNames (config) { const statuses = [{label: 'Unknown detailed status', type: 'warning'}] const softwareVersion = '' - return _.assign(r, {name, cashOut, machineModel, machineLocation, printer, pingTime, statuses, softwareVersion}) + return _.assign(r, {cashOut, machineModel, machineLocation, printer, pingTime, statuses, softwareVersion}) } return _.map(addName, machines) @@ -63,11 +66,9 @@ function getMachineNames (config) { * @returns {string} machine name */ function getMachineName (machineId) { - return settingsLoader.loadRecentConfig() - .then(config => { - const machineScoped = configManager.machineScoped(machineId, config) - return machineScoped.machineName - }) + const sql = 'select * from devices where device_id=$1' + return db.oneOrNone(sql, [machineId]) + .then(it => it.name) } function resetCashOutBills (rec) { diff --git a/lib/new-admin/admin-server.js b/lib/new-admin/admin-server.js index 9c4e3751..0f416fff 100644 --- a/lib/new-admin/admin-server.js +++ b/lib/new-admin/admin-server.js @@ -59,8 +59,8 @@ apolloServer.applyMiddleware({ // cors on app for /api/register endpoint. app.use(cors({ credentials: true, origin: devMode && 'https://localhost:3000' })) -app.use('/id-card-photo', serveStatic(idPhotoCardBasedir, {index: false})) -app.use('/front-camera-photo', serveStatic(frontCameraBasedir, {index: false})) +app.use('/id-card-photo', serveStatic(idPhotoCardBasedir, { index: false })) +app.use('/front-camera-photo', serveStatic(frontCameraBasedir, { index: false })) app.get('/api/register', (req, res, next) => { const otp = req.query.otp @@ -90,7 +90,7 @@ app.get('/api/register', (req, res, next) => { }) // Everything not on graphql or api/register is redirected to the front-end -app.get('*', (req, res) => res.sendFile(path.resolve('client', 'build', 'index.html'))) +app.get('*', (req, res) => res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html'))) const certOptions = { key: fs.readFileSync(options.keyPath), diff --git a/lib/new-admin/config/accounts.js b/lib/new-admin/config/accounts.js index a972a4e3..4d0a9ac4 100644 --- a/lib/new-admin/config/accounts.js +++ b/lib/new-admin/config/accounts.js @@ -11,7 +11,7 @@ const ID_VERIFIER = 'idVerifier' const EMAIL = 'email' const ZERO_CONF = 'zeroConf' -const ACCOUNT_LIST = [ +const ALL_ACCOUNTS = [ { code: 'bitpay', display: 'Bitpay', class: TICKER, cryptos: [BTC, BCH] }, { code: 'kraken', display: 'Kraken', class: TICKER, cryptos: [BTC, ETH, LTC, DASH, ZEC, BCH] }, { code: 'bitstamp', display: 'Bitstamp', class: TICKER, cryptos: [BTC, ETH, LTC, BCH] }, @@ -43,4 +43,7 @@ const ACCOUNT_LIST = [ { code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH, ETH], dev: true } ] +const devMode = require('minimist')(process.argv.slice(2)).dev +const ACCOUNT_LIST = devMode ? ALL_ACCOUNTS : _.filter(it => !it.dev)(ALL_ACCOUNTS) + module.exports = { ACCOUNT_LIST } diff --git a/lib/new-admin/funding.js b/lib/new-admin/funding.js index c896a4bb..cbb32b37 100644 --- a/lib/new-admin/funding.js +++ b/lib/new-admin/funding.js @@ -1,11 +1,10 @@ const _ = require('lodash/fp') const BN = require('../bn') -const settingsLoader = require('../settings-loader') -const configManager = require('../config-manager') +const settingsLoader = require('../new-settings-loader') +const configManager = require('../new-config-manager') const wallet = require('../wallet') const ticker = require('../ticker') const coinUtils = require('../coin-utils') -const machineLoader = require('../machine-loader') function allScopes (cryptoScopes, machineScopes) { const scopes = [] @@ -25,18 +24,6 @@ function allMachineScopes (machineList, machineScope) { return machineScopes } -function getCryptos (config, machineList) { - const scopes = allScopes(['global'], allMachineScopes(machineList, 'both')) - const scoped = scope => configManager.scopedValue(scope[0], scope[1], 'cryptoCurrencies', config) - - return _.uniq(_.flatten(_.map(scoped, scopes))) -} - -function fetchMachines () { - return machineLoader.getMachines() - .then(machineList => machineList.map(r => r.deviceId)) -} - function computeCrypto (cryptoCode, _balance) { const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) const unitScale = cryptoRec.unitScale @@ -82,11 +69,9 @@ function getSingleCoinFunding (settings, fiatCode, cryptoCode) { } function getFunding () { - return Promise.all([settingsLoader.loadLatest(), fetchMachines()]) - .then(([settings, machineList]) => { - const config = configManager.unscoped(settings.config) - const cryptoCodes = getCryptos(settings.config, machineList) - const fiatCode = config.fiatCurrency + return settingsLoader.loadLatest().then(settings => { + const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config) + const fiatCode = configManager.getGlobalLocale(settings.config).fiatCurrency const pareCoins = c => _.includes(c.cryptoCode, cryptoCodes) const cryptoCurrencies = coinUtils.cryptoCurrencies() const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies) diff --git a/lib/new-admin/graphql/schema.js b/lib/new-admin/graphql/schema.js index 08e0a4f4..07bedf12 100644 --- a/lib/new-admin/graphql/schema.js +++ b/lib/new-admin/graphql/schema.js @@ -62,7 +62,7 @@ const typeDefs = gql` type Customer { id: ID! - name: String! + name: String authorizedOverride: String frontCameraPath: String phone: String @@ -215,7 +215,7 @@ const typeDefs = gql` } type Mutation { - machineAction(deviceId:ID!, action: MachineAction!, cassettes: [Int]): Machine + machineAction(deviceId:ID!, action: MachineAction!, cassette1: Int, cassette2: Int): Machine machineSupportLogs(deviceId: ID!): SupportLogsResponse serverSupportLogs: SupportLogsResponse setCustomer(customerId: ID!, customerInput: CustomerInput): Customer @@ -254,7 +254,7 @@ const resolvers = { accounts: () => settingsLoader.getAccounts() }, Mutation: { - machineAction: (...[, { deviceId, action, cassettes }]) => machineAction({ deviceId, action, cassettes }), + machineAction: (...[, { deviceId, action, cassette1, cassette2 }]) => machineAction({ deviceId, action, cassette1, cassette2 }), machineSupportLogs: (...[, { deviceId }]) => supportLogs.insert(deviceId), createPairingTotem: (...[, { name }]) => pairing.totem(name), serverSupportLogs: () => serverLogs.insert(), diff --git a/lib/new-admin/machines.js b/lib/new-admin/machines.js index cb0924ff..9fd779ef 100644 --- a/lib/new-admin/machines.js +++ b/lib/new-admin/machines.js @@ -6,13 +6,13 @@ function getMachine (machineId) { .then(machines => machines.find(({ deviceId }) => deviceId === machineId)) } -function machineAction ({ deviceId, action, cassettes }) { +function machineAction ({ deviceId, action, cassette1, cassette2 }) { return getMachine(deviceId) .then(machine => { if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId }) return machine }) - .then(machineLoader.setMachine({ deviceId, action, cassettes })) + .then(machineLoader.setMachine({ deviceId, action, cassettes: [cassette1, cassette2] })) .then(getMachine(deviceId)) } diff --git a/lib/new-config-manager.js b/lib/new-config-manager.js new file mode 100644 index 00000000..caddcbb4 --- /dev/null +++ b/lib/new-config-manager.js @@ -0,0 +1,93 @@ +const _ = require('lodash/fp') +const logger = require('./logger') + +const namespaces = { + WALLETS: 'wallets', + OPERATOR_INFO: 'operatorInfo', + NOTIFICATIONS: 'notifications', + LOCALE: 'locale', + COMMISSIONS: 'commissions', + RECEIPT: 'receipt', + COIN_ATM_RADAR: 'coinAtmRadar', + TERMS_CONDITIONS: 'termsConditions', + CASH_OUT: 'cashOut' +} + +const stripl = _.curry((q, str) => _.startsWith(q, str) ? str.slice(q.length) : str) +const filter = namespace => _.pickBy((value, key) => _.startsWith(`${namespace}_`)(key)) +const strip = key => _.mapKeys(stripl(`${key}_`)) + +const fromNamespace = _.curry((key, config) => _.compose(strip(key), filter(key))(config)) +const toNamespace = (key, config) => _.mapKeys(it => `${key}_${it}`)(config) + +const resolveOverrides = (original, filter, overrides, overridesPath = 'overrides') => { + if (_.isEmpty(overrides)) return original + + return _.omit(overridesPath, _.mergeAll([original, ..._.filter(filter)(overrides)])) +} + +const getCommissions = (cryptoCode, deviceId, config) => { + const commissions = fromNamespace(namespaces.COMMISSIONS)(config) + + const filter = it => it.machine === deviceId && _.includes(cryptoCode)(it.cryptoCurrencies) + return resolveOverrides(commissions, filter, commissions.overrides) +} + +const getLocale = (deviceId, it) => { + const locale = fromNamespace(namespaces.LOCALE)(it) + + const filter = _.matches({ machine: deviceId }) + return resolveOverrides(locale, filter, locale.overrides) +} + +const getGlobalLocale = it => getLocale(null, it) + +const getWalletSettings = (key, it) => _.compose(fromNamespace(key), fromNamespace(namespaces.WALLETS))(it) +const getCashOut = (key, it) => _.compose(fromNamespace(key), fromNamespace(namespaces.CASH_OUT))(it) +const getOperatorInfo = fromNamespace(namespaces.OPERATOR_INFO) +const getCoinAtmRadar = fromNamespace(namespaces.COIN_ATM_RADAR) +const getTermsConditions = fromNamespace(namespaces.TERMS_CONDITIONS) +const getReceipt = fromNamespace(namespaces.RECEIPT) + +const getAllCryptoCurrencies = (config) => { + const locale = fromNamespace(namespaces.LOCALE)(config) + const cryptos = locale.cryptoCurrencies + const overridesCryptos = _.map(_.get('cryptoCurrencies'))(locale.overrides) + return _.uniq(_.flatten([cryptos, ...overridesCryptos])) +} + +const getNotifications = (cryptoCurrency, machine, config) => { + const notifications = fromNamespace(namespaces.NOTIFICATIONS)(config) + + const cryptoFilter = _.matches({ cryptoCurrency }) + const withCryptoBalance = resolveOverrides(notifications, cryptoFilter, notifications.cryptoBalanceOverrides, 'cryptoBalanceOverrides') + + const fiatFilter = _.matches({ machine }) + const withFiatBalance = resolveOverrides(withCryptoBalance, fiatFilter, withCryptoBalance.fiatBalanceOverrides, 'fiatBalanceOverrides') + + const withSms = fromNamespace('sms', withFiatBalance) + const withEmail = fromNamespace('email', withFiatBalance) + + const final = { ...withFiatBalance, sms: withSms, email: withEmail } + return final +} + +const getGlobalNotifications = config => getNotifications(null, null, config) + +const getTriggers = _.get('triggers') + +module.exports = { + getWalletSettings, + getOperatorInfo, + getNotifications, + getGlobalNotifications, + getLocale, + getGlobalLocale, + getCommissions, + getReceipt, + getCoinAtmRadar, + getTermsConditions, + getAllCryptoCurrencies, + getTriggers, + getCashOut +} diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index e789a436..3c53bd08 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -42,4 +42,28 @@ function getConfig () { return (state && state.config) || {} } -module.exports = { getConfig, saveConfig, saveAccounts, getAccounts } +function loadLatest () { + return new Promise((resolve) => { + if (!db) { + setTimeout(() => { + return resolve(db.getState()) + }, 1000) + } else { + return resolve(db.getState()) + } + }) +} + +function load (versionId) { + return new Promise((resolve) => { + if (!db) { + setTimeout(() => { + return resolve(db.getState()) + }, 1000) + } else { + return resolve(db.getState()) + } + }) +} + +module.exports = { getConfig, saveConfig, saveAccounts, getAccounts, loadLatest, load } diff --git a/lib/notifier.js b/lib/notifier.js index 0885fd12..5413062f 100644 --- a/lib/notifier.js +++ b/lib/notifier.js @@ -15,6 +15,7 @@ const ALERT_SEND_INTERVAL = T.hour const PING = 'PING' const STALE = 'STALE' 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' @@ -22,6 +23,7 @@ const CODES_DISPLAY = { PING: 'Machine Down', STALE: 'Machine Stuck', LOW_CRYPTO_BALANCE: 'Low Crypto Balance', + HIGH_CRYPTO_BALANCE: 'High Crypto Balance', CASH_BOX_FULL: 'Cash box full', LOW_CASH_OUT: 'Low Cash-out' } @@ -41,47 +43,54 @@ function sameState (a, b) { return a.note.txId === b.note.txId && a.note.state === b.note.state } -function sendNoAlerts (plugins) { +function sendNoAlerts (plugins, smsEnabled, emailEnabled) { const subject = '[Lamassu] All clear' - const rec = { - sms: { - body: subject - }, - email: { - subject, - body: 'No errors are reported for your machines.' - } + + let rec = {} + if (smsEnabled) { + rec = _.set(['sms', 'body'])(subject)(rec) + } + + if (emailEnabled) { + rec = _.set(['email', 'subject'])(subject)(rec) + rec = _.set(['email', 'body'])('No errors are reported for your machines.')(rec) } return plugins.sendMessage(rec) } function checkNotification (plugins) { - if (!plugins.notificationsEnabled()) return Promise.resolve() + const notifications = plugins.getNotificationConfig() + const isActive = it => it.active && (it.balance || it.errors) + const smsEnabled = isActive(notifications.sms) + const emailEnabled = isActive(notifications.email) + + if (!smsEnabled && !emailEnabled) return Promise.resolve() return checkStatus(plugins) .then(alertRec => { - const currentAlertFingerprint = buildAlertFingerprint(alertRec) + const currentAlertFingerprint = buildAlertFingerprint(alertRec, notifications) if (!currentAlertFingerprint) { const inAlert = !!alertFingerprint alertFingerprint = null lastAlertTime = null - if (inAlert) return sendNoAlerts(plugins) + if (inAlert) return sendNoAlerts(plugins, smsEnabled, emailEnabled) } const alertChanged = currentAlertFingerprint === alertFingerprint && lastAlertTime - Date.now() < ALERT_SEND_INTERVAL if (alertChanged) return - const rec = { - sms: { - body: printSmsAlerts(alertRec) - }, - email: { - subject: alertSubject(alertRec), - body: printEmailAlerts(alertRec) - } + let rec = {} + if (smsEnabled) { + rec = _.set(['sms', 'body'])(printSmsAlerts(alertRec, notifications.sms))(rec) } + + if (emailEnabled) { + rec = _.set(['email', 'subject'])(alertSubject(alertRec, notifications.email))(rec) + rec = _.set(['email', 'body'])(printEmailAlerts(alertRec, notifications.email))(rec) + } + alertFingerprint = currentAlertFingerprint lastAlertTime = Date.now() @@ -162,12 +171,13 @@ function checkStatus (plugins) { return eventRow.device_id === deviceId }) - const balanceAlerts = _.filter(['deviceId', deviceId], balances) const ping = pings[deviceId] || [] const stuckScreen = checkStuckScreen(deviceEvents, deviceName) - const deviceAlerts = _.isEmpty(ping) ? stuckScreen : ping - alerts.devices[deviceId] = _.concat(deviceAlerts, balanceAlerts) + if (!alerts.devices[deviceId]) alerts.devices[deviceId] = {} + alerts.devices[deviceId].balanceAlerts = _.filter(['deviceId', deviceId], balances) + alerts.devices[deviceId].deviceAlerts = _.isEmpty(ping) ? stuckScreen : ping + alerts.deviceNames[deviceId] = deviceName }) @@ -194,6 +204,9 @@ function emailAlert (alert) { case LOW_CRYPTO_BALANCE: const balance = formatCurrency(alert.fiatBalance.balance, alert.fiatCode) return `Low balance in ${alert.cryptoCode} [${balance}]` + case HIGH_CRYPTO_BALANCE: + const highBalance = formatCurrency(alert.fiatBalance.balance, alert.fiatCode) + return `High balance in ${alert.cryptoCode} [${highBalance}]` case CASH_BOX_FULL: return `Cash box full on ${alert.machineName} [${alert.notes} banknotes]` case LOW_CASH_OUT: @@ -205,28 +218,48 @@ function emailAlerts (alerts) { return alerts.map(emailAlert).join('\n') + '\n' } -function printEmailAlerts (alertRec) { +function printEmailAlerts (alertRec, config) { let body = 'Errors were reported by your Lamassu Machines.\n' - if (alertRec.general.length !== 0) { + if (config.balance && alertRec.general.length !== 0) { body = body + '\nGeneral errors:\n' - body = body + emailAlerts(alertRec.general) + body = body + emailAlerts(alertRec.general) + '\n' } _.keys(alertRec.devices).forEach(function (device) { const deviceName = alertRec.deviceNames[device] body = body + '\nErrors for ' + deviceName + ':\n' - body = body + emailAlerts(alertRec.devices[device]) + + let alerts = [] + if (config.balance) { + alerts = _.concat(alerts, alertRec.devices[device].balanceAlerts) + } + + if (config.errors) { + alerts = _.concat(alerts, alertRec.devices[device].deviceAlerts) + } + + body = body + emailAlerts(alerts) }) return body } -function alertSubject (alertRec) { - let alerts = alertRec.general +function alertSubject (alertRec, config) { + let alerts = [] + + if (config.balance) { + alerts = _.concat(alerts, alertRec.general) + } _.keys(alertRec.devices).forEach(function (device) { - alerts = _.concat(alerts, alertRec.devices[device]) + if (config.balance) { + alerts = _.concat(alerts, alertRec.devices[device].balanceAlerts) + } + + if (config.errors) { + alerts = _.concat(alerts, alertRec.devices[device].deviceAlerts) + } }) if (alerts.length === 0) return null @@ -235,11 +268,21 @@ function alertSubject (alertRec) { return '[Lamassu] Errors reported: ' + alertTypes.join(', ') } -function printSmsAlerts (alertRec) { - let alerts = alertRec.general +function printSmsAlerts (alertRec, config) { + let alerts = [] + + if (config.balance) { + alerts = _.concat(alerts, alertRec.general) + } _.keys(alertRec.devices).forEach(function (device) { - alerts = _.concat(alerts, alertRec.devices[device]) + if (config.balance) { + alerts = _.concat(alerts, alertRec.devices[device].balanceAlerts) + } + + if (config.errors) { + alerts = _.concat(alerts, alertRec.devices[device].deviceAlerts) + } }) if (alerts.length === 0) return null @@ -265,9 +308,39 @@ function printSmsAlerts (alertRec) { return '[Lamassu] Errors reported: ' + displayAlertTypes.join(', ') } -function buildAlertFingerprint (alertRec) { - const subject = alertSubject(alertRec) - if (!subject) return null +function getAlertTypes (alertRec, config) { + let alerts = [] + + if (!config.active || (!config.balance && !config.errors)) return alerts + + if (config.balance) { + alerts = _.concat(alerts, alertRec.general) + } + + _.keys(alertRec.devices).forEach(function (device) { + if (config.balance) { + alerts = _.concat(alerts, alertRec.devices[device].balanceAlerts) + } + + if (config.errors) { + alerts = _.concat(alerts, alertRec.devices[device].deviceAlerts) + } + }) + + return alerts +} + +function buildAlertFingerprint (alertRec, notifications) { + const sms = getAlertTypes(alertRec, notifications.sms) + const email = getAlertTypes(alertRec, notifications.email) + + if (sms.length === 0 && email.length === 0) return null + + const smsTypes = _.map(codeDisplay, _.uniq(_.map('code', sms))).sort() + const emailTypes = _.map(codeDisplay, _.uniq(_.map('code', email))).sort() + + const subject = _.concat(smsTypes, emailTypes).join(', ') + return crypto.createHash('sha256').update(subject).digest('hex') } diff --git a/lib/pairing.js b/lib/pairing.js index 180aae6b..5932878c 100644 --- a/lib/pairing.js +++ b/lib/pairing.js @@ -4,7 +4,6 @@ const readFile = pify(fs.readFile) const db = require('./db') const options = require('./options') const logger = require('./logger') -const settingsLoader = require('./settings-loader') function pullToken (token) { const sql = `delete from pairing_tokens @@ -13,33 +12,12 @@ function pullToken (token) { return db.one(sql, [token]) } -function configureNewDevice (deviceId, machineName, machineModel) { - const scope = {crypto: 'global', machine: deviceId} - const newFields = [ - settingsLoader.configAddField(scope, 'cashOutEnabled', 'onOff', null, false), - settingsLoader.configAddField(scope, 'machineName', 'string', null, machineName), - settingsLoader.configAddField(scope, 'machineModel', 'string', null, machineModel) - ] - - return settingsLoader.modifyConfig(newFields) -} - -function removeDeviceConfig (deviceId) { - const scope = {crypto: 'global', machine: deviceId} - const newFields = [ - settingsLoader.configDeleteField(scope, 'cashOutEnabled'), - settingsLoader.configDeleteField(scope, 'machineName'), - settingsLoader.configDeleteField(scope, 'machineModel') - ] - - return settingsLoader.modifyConfig(newFields) -} - function unpair (deviceId) { const sql = 'delete from devices where device_id=$1' const deleteMachinePings = 'delete from machine_pings where device_id=$1' + + // TODO new-admin: We should remove all configs related to that device. This can get tricky. return Promise.all([db.none(sql, [deviceId]), db.none(deleteMachinePings, [deviceId])]) - .then(() => removeDeviceConfig(deviceId)) } function pair (token, deviceId, machineModel) { @@ -51,8 +29,7 @@ function pair (token, deviceId, machineModel) { on conflict (device_id) do update set paired=TRUE, display=TRUE` - return configureNewDevice(deviceId, r.name, machineModel) - .then(() => db.none(insertSql, [deviceId, r.name])) + return db.none(insertSql, [deviceId, r.name]) .then(() => true) }) .catch(err => { diff --git a/lib/plugins.js b/lib/plugins.js index 0b55a815..f128f2a2 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -11,7 +11,7 @@ const db = require('./db') const logger = require('./logger') const logs = require('./logs') const T = require('./time') -const configManager = require('./config-manager') +const configManager = require('./new-config-manager') const ticker = require('./ticker') const wallet = require('./wallet') const exchange = require('./exchange') @@ -34,22 +34,22 @@ const tradesQueues = {} function plugins (settings, deviceId) { function buildRates (tickers) { - const config = configManager.machineScoped(deviceId, settings.config) - const cryptoCodes = config.cryptoCurrencies + const localeConfig = configManager.getLocale(deviceId, settings.config) + const cryptoCodes = localeConfig.cryptoCurrencies const rates = {} cryptoCodes.forEach((cryptoCode, i) => { - const cryptoConfig = configManager.scoped(cryptoCode, deviceId, settings.config) + const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) const rateRec = tickers[i] if (!rateRec) return - const cashInCommission = BN(1).add(BN(cryptoConfig.cashInCommission).div(100)) + const cashInCommission = BN(1).add(BN(commissions.cashIn).div(100)) - const cashOutCommission = _.isNil(cryptoConfig.cashOutCommission) + const cashOutCommission = _.isNil(commissions.cashOut) ? undefined - : BN(1).add(BN(cryptoConfig.cashOutCommission).div(100)) + : BN(1).add(BN(commissions.cashOut).div(100)) if (Date.now() - rateRec.timestamp > STALE_TICKER) return logger.warn('Stale rate for ' + cryptoCode) const rate = rateRec.rates @@ -62,19 +62,13 @@ function plugins (settings, deviceId) { return rates } - function transactionNotificationsEnabled () { - const config = configManager.unscoped(settings.config) - return config.transactionNotificationsEnabled - } - - function notificationsEnabled () { - const config = configManager.unscoped(settings.config) - return config.notificationsEnabled + function getNotificationConfig () { + return configManager.getGlobalNotifications(settings.config) } function buildBalances (balanceRecs) { - const config = configManager.machineScoped(deviceId, settings.config) - const cryptoCodes = config.cryptoCurrencies + const localeConfig = configManager.getLocale(deviceId, settings.config) + const cryptoCodes = localeConfig.cryptoCurrencies const balances = {} @@ -90,8 +84,8 @@ function plugins (settings, deviceId) { } function isZeroConf (tx) { - const config = configManager.scoped(tx.cryptoCode, deviceId, settings.config) - const zeroConfLimit = config.zeroConfLimit + const cashOutConfig = configManager.getCashOut(deviceId, settings.config) + const zeroConfLimit = cashOutConfig.zeroConfLimit return tx.fiat.lte(zeroConfLimit) } @@ -131,14 +125,14 @@ function plugins (settings, deviceId) { } function buildAvailableCassettes (excludeTxId) { - const config = configManager.machineScoped(deviceId, settings.config) + const cashOutConfig = configManager.getCashOut(deviceId, settings.config) - if (!config.cashOutEnabled) return Promise.resolve() + if (!cashOutConfig.active) return Promise.resolve() - const denominations = [config.topCashOutDenomination, - config.bottomCashOutDenomination - ] - const virtualCassettes = [config.virtualCashOutDenomination] + const denominations = [cashOutConfig.top, cashOutConfig.bottom] + + // TODO new-admin: will this actually be calculated? + const virtualCassettes = [cashOutConfig.top + cashOutConfig.bottom] return Promise.all([dbm.cassetteCounts(deviceId), cashOutHelper.redeemableTxs(deviceId, excludeTxId)]) .then(([rec, _redeemableTxs]) => { @@ -188,11 +182,11 @@ function plugins (settings, deviceId) { function mapCoinSettings (coinParams) { const cryptoCode = coinParams[0] const cryptoNetwork = coinParams[1] - const config = configManager.scoped(cryptoCode, deviceId, settings.config) - const minimumTx = BN(config.minimumTx) - const cashInFee = BN(config.cashInFee) - const cashInCommission = BN(config.cashInCommission) - const cashOutCommission = _.isNumber(config.cashOutCommission) ? BN(config.cashOutCommission) : null + const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) + const minimumTx = BN(commissions.minimumTx) + const cashInFee = BN(commissions.fixedFee) + const cashInCommission = BN(commissions.cashIn) + const cashOutCommission = _.isNumber(commissions.cashOut) ? BN(commissions.cashOut) : null const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) return { @@ -207,9 +201,10 @@ function plugins (settings, deviceId) { } function pollQueries (serialNumber, deviceTime, deviceRec) { - const config = configManager.machineScoped(deviceId, settings.config) - const fiatCode = config.fiatCurrency - const cryptoCodes = config.cryptoCurrencies + const localeConfig = configManager.getLocale(deviceId, settings.config) + + const fiatCode = localeConfig.fiatCurrency + const cryptoCodes = localeConfig.cryptoCurrencies const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c)) const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c)) @@ -283,16 +278,14 @@ function plugins (settings, deviceId) { } function dispenseAck (tx) { - const config = configManager.machineScoped(deviceId, settings.config) - const cassettes = [config.topCashOutDenomination, - config.bottomCashOutDenomination - ] + const cashOutConfig = configManager.getCashOut(deviceId, settings.config) + const cassettes = [cashOutConfig.top, cashOutConfig.bottom] return dbm.addDispense(deviceId, tx, cassettes) } function fiatBalance (fiatCode, cryptoCode) { - const config = configManager.scoped(cryptoCode, deviceId, settings.config) + const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) return Promise.all([ ticker.getRates(settings, fiatCode, cryptoCode), wallet.balance(settings, cryptoCode) @@ -301,7 +294,7 @@ function plugins (settings, deviceId) { if (!rates || !balanceRec) return null const rawRate = rates.rates.ask - const cashInCommission = BN(1).minus(BN(config.cashInCommission).div(100)) + const cashInCommission = BN(1).minus(BN(commissions.cashIn).div(100)) const balance = balanceRec.balance if (!rawRate || !balance) return null @@ -344,7 +337,12 @@ function plugins (settings, deviceId) { } function notifyOperator (tx, rec) { - if (!transactionNotificationsEnabled()) return Promise.resolve() + const notifications = configManager.getGlobalNotifications(settings.config) + + const notificationsEnabled = notifications.sms.transactions || notifications.email.transactions + const highValueTx = tx.fiat.gt(notifications.highValueTransaction) + + if (!notificationsEnabled || !highValueTx) return Promise.resolve() const isCashOut = tx.direction === 'cashOut' const zeroConf = isCashOut && isZeroConf(tx) @@ -504,9 +502,9 @@ function plugins (settings, deviceId) { .then(devices => { const deviceIds = devices.map(device => device.deviceId) const lists = deviceIds.map(deviceId => { - const config = configManager.machineScoped(deviceId, settings.config) - const fiatCode = config.fiatCurrency - const cryptoCodes = config.cryptoCurrencies + const localeConfig = configManager.getLocale(deviceId, settings.config) + const fiatCode = localeConfig.fiatCurrency + const cryptoCodes = localeConfig.cryptoCurrencies return cryptoCodes.map(cryptoCode => ({ fiatCode, @@ -591,21 +589,25 @@ function plugins (settings, deviceId) { } function sendMessage (rec) { - const config = configManager.unscoped(settings.config) + const notifications = configManager.getGlobalNotifications(settings.config) let promises = [] - if (config.notificationsEmailEnabled) promises.push(email.sendMessage(settings, rec)) - if (config.notificationsSMSEnabled) promises.push(sms.sendMessage(settings, rec)) + if (notifications.email.active && rec.email) promises.push(email.sendMessage(settings, rec)) + if (notifications.sms.active && rec.sms) promises.push(sms.sendMessage(settings, rec)) return Promise.all(promises) } function sendTransactionMessage (rec) { - const config = configManager.unscoped(settings.config) + const notifications = configManager.getGlobalNotifications(settings.config) let promises = [] - if (config.transactionNotificationsEmailEnabled) promises.push(email.sendMessage(settings, rec)) - if (config.transactionNotificationsSMSEnabled) promises.push(sms.sendMessage(settings, rec)) + + const emailActive = notifications.email.active && notifications.email.transactions + if (emailActive) promises.push(email.sendMessage(settings, rec)) + + const smsActive = notifications.sms.active && notifications.sms.transactions + if (smsActive) promises.push(sms.sendMessage(settings, rec)) return Promise.all(promises) } @@ -615,13 +617,16 @@ function plugins (settings, deviceId) { } function checkDeviceCashBalances (fiatCode, device) { - const config = configManager.machineScoped(device.deviceId, settings.config) - const denomination1 = config.topCashOutDenomination - const denomination2 = config.bottomCashOutDenomination - const machineName = config.machineName - const cashOutEnabled = config.cashOutEnabled + const cashOutConfig = configManager.getCashOut(device.deviceId, settings.config) + const denomination1 = cashOutConfig.top + const denomination2 = cashOutConfig.bottom + const cashOutEnabled = cashOutConfig.active - const cashInAlert = device.cashbox > config.cashInAlertThreshold + const notifications = configManager.getNotifications(null, device.deviceId, settings.config) + + const machineName = device.machineName + + const cashInAlert = device.cashbox > notifications.cashInAlertThreshold ? { code: 'CASH_BOX_FULL', machineName, @@ -630,7 +635,7 @@ function plugins (settings, deviceId) { } : null - const cassette1Alert = cashOutEnabled && device.cassette1 < config.cashOutCassette1AlertThreshold + const cassette1Alert = cashOutEnabled && device.cassette1 < notifications.fiatBalanceCassette1 ? { code: 'LOW_CASH_OUT', cassette: 1, @@ -642,7 +647,7 @@ function plugins (settings, deviceId) { } : null - const cassette2Alert = cashOutEnabled && device.cassette2 < config.cashOutCassette2AlertThreshold + const cassette2Alert = cashOutEnabled && device.cassette2 < notifications.fiatBalanceCassette2 ? { code: 'LOW_CASH_OUT', cassette: 2, @@ -661,8 +666,8 @@ function plugins (settings, deviceId) { const fiatBalancePromises = cryptoCodes => _.map(c => fiatBalance(fiatCode, c), cryptoCodes) const fetchCryptoCodes = _deviceId => { - const config = configManager.machineScoped(_deviceId, settings.config) - return config.cryptoCurrencies + const localeConfig = configManager.getLocale(_deviceId, settings.config) + return localeConfig.cryptoCurrencies } const union = _.flow(_.map(fetchCryptoCodes), _.flatten, _.uniq) @@ -678,22 +683,28 @@ function plugins (settings, deviceId) { if (!fiatBalance) return null - const config = configManager.cryptoScoped(cryptoCode, settings.config) - const cryptoAlertThreshold = config.cryptoAlertThreshold + const notifications = configManager.getNotifications(cryptoCode, null, settings.config) + const lowAlertThreshold = notifications.cryptoLowBalance + const highAlertThreshold = notifications.cryptoHighBalance - return BN(fiatBalance.balance).lt(cryptoAlertThreshold) - ? { - code: 'LOW_CRYPTO_BALANCE', - cryptoCode, - fiatBalance, - fiatCode - } - : null + const req = { + cryptoCode, + fiatBalance, + fiatCode, + } + + if (BN(fiatBalance.balance).lt(lowAlertThreshold)) + return _.set('code')('LOW_CRYPTO_BALANCE')(req) + + if (BN(fiatBalance.balance).gt(highAlertThreshold)) + return _.set('code')('HIGH_CRYPTO_BALANCE')(req) + + return null } function checkBalances () { - const globalConfig = configManager.unscoped(settings.config) - const fiatCode = globalConfig.fiatCurrency + const localeConfig = configManager.getGlobalLocale(settings.config) + const fiatCode = localeConfig.fiatCurrency return machineLoader.getMachines() .then(devices => { @@ -756,9 +767,10 @@ function plugins (settings, deviceId) { } function getRawRates () { - const config = configManager.unscoped(settings.config) - const cryptoCodes = _.flatten(configManager.all('cryptoCurrencies', settings.config)) - const fiatCode = config.fiatCurrency + const localeConfig = configManager.getGlobalLocale(settings.config) + const fiatCode = localeConfig.fiatCurrency + + const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config) const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c)) return Promise.all(tickerPromises) @@ -792,7 +804,7 @@ function plugins (settings, deviceId) { buildAvailableCassettes, buy, sell, - notificationsEnabled, + getNotificationConfig, notifyOperator, fetchCurrentConfigVersion } diff --git a/lib/poller.js b/lib/poller.js index 22b15b27..bbd3f708 100644 --- a/lib/poller.js +++ b/lib/poller.js @@ -9,7 +9,8 @@ const cashInTx = require('./cash-in/cash-in-tx') const sanctionsUpdater = require('./ofac/update') const sanctions = require('./ofac/index') const coinAtmRadar = require('./coinatmradar/coinatmradar') -const configManager = require('./config-manager') +const configManager = require('./new-config-manager') +const complianceTriggers = require('./compliance-triggers') const INCOMING_TX_INTERVAL = 30 * T.seconds const LIVE_INCOMING_TX_INTERVAL = 5 * T.seconds @@ -48,9 +49,10 @@ function initialSanctionsDownload () { } function updateAndLoadSanctions () { - const config = configManager.unscoped(settings().config) + const triggers = configManager.getTriggers(settings().config) + const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) - if (!config.sanctionsVerificationActive) return Promise.resolve() + if (!compatTriggers.sanctions) return Promise.resolve() logger.info('Updating sanctions database...') return sanctionsUpdater.update() @@ -59,10 +61,8 @@ function updateAndLoadSanctions () { } function updateCoinAtmRadar () { - const config = settings().config - return pi().getRawRates() - .then(rates => coinAtmRadar.update({ rates, config }, settings())) + .then(rates => coinAtmRadar.update(rates, settings())) } function start (__settings) { diff --git a/lib/route-helpers.js b/lib/route-helpers.js index 857dded8..4351f7d9 100644 --- a/lib/route-helpers.js +++ b/lib/route-helpers.js @@ -4,7 +4,6 @@ const db = require('./db') const dbm = require('./postgresql_interface') const T = require('./time') const BN = require('./bn') -const settingsLoader = require('./settings-loader') const TRANSACTION_EXPIRATION = T.day @@ -90,33 +89,9 @@ function updateDeviceConfigVersion (versionId) { return db.none('update devices set user_config_id=$1', [versionId]) } -function updateMachineDefaults (deviceId) { - const newFields = [{ - fieldLocator: { - fieldScope: { - crypto: 'global', - machine: deviceId - }, - code: 'cashOutEnabled', - fieldType: 'onOff', - fieldClass: null - }, - fieldValue: { - fieldType: 'onOff', - value: false - } - }] - - return settingsLoader.loadLatest() - .then(settings => { - return settingsLoader.save(settingsLoader.mergeValues(settings.config, newFields)) - }) -} - module.exports = { stateChange, fetchPhoneTx, fetchStatusTx, - updateDeviceConfigVersion, - updateMachineDefaults + updateDeviceConfigVersion } diff --git a/lib/routes.js b/lib/routes.js index 52ba93fb..8a6490d4 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -12,9 +12,11 @@ const semver = require('semver') const dbErrorCodes = require('./db-error-codes') const options = require('./options') const logger = require('./logger') -const configManager = require('./config-manager') +const configManager = require('./new-config-manager') +const complianceTriggers = require('./compliance-triggers') const pairing = require('./pairing') -const settingsLoader = require('./settings-loader') +// TODO new-admin: remove old settings loader from here. +const newSettingsLoader = require('./new-settings-loader') const plugins = require('./plugins') const helpers = require('./route-helpers') const poller = require('./poller') @@ -44,7 +46,7 @@ const settingsCache = {} const devMode = argv.dev || options.http function checkHasLightning (settings) { - return configManager.cryptoScoped('BTC', settings.config).layer2 !== 'no-layer2' + return configManager.getWalletSettings('BTC', settings.config).layer2 !== 'no-layer2' } function poll (req, res, next) { @@ -54,10 +56,18 @@ function poll (req, res, next) { const serialNumber = req.query.sn const pid = req.query.pid const settings = req.settings - const config = configManager.machineScoped(deviceId, settings.config) + const localeConfig = configManager.getLocale(deviceId, settings.config) const pi = plugins(settings, deviceId) const hasLightning = checkHasLightning(settings) + const triggers = configManager.getTriggers(settings.config) + const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) + + const operatorInfo = configManager.getOperatorInfo(settings.config) + const terms = configManager.getTermsConditions(settings.config) + const cashOutConfig = configManager.getCashOut(deviceId, settings.config) + const receipt = configManager.getReceipt(settings.config) + pids[deviceId] = { pid, ts: Date.now() } return pi.pollQueries(serialNumber, deviceTime, req.query) @@ -66,14 +76,14 @@ function poll (req, res, next) { const reboot = pid && reboots[deviceId] && reboots[deviceId] === pid const restartServices = pid && restartServicesMap[deviceId] && restartServicesMap[deviceId] === pid - const langs = config.machineLanguages + const langs = localeConfig.languages const locale = { - fiatCode: config.fiatCurrency, + fiatCode: localeConfig.fiatCurrency, localeInfo: { primaryLocale: langs[0], primaryLocales: langs, - country: config.country + country: localeConfig.country } } @@ -81,47 +91,33 @@ function poll (req, res, next) { error: null, locale, version, - txLimit: config.cashInTransactionLimit, - idVerificationEnabled: config.idVerificationEnabled, - smsVerificationActive: config.smsVerificationActive, - smsVerificationThreshold: config.smsVerificationThreshold, - hardLimitVerificationActive: config.hardLimitVerificationActive, - hardLimitVerificationThreshold: config.hardLimitVerificationThreshold, - idCardDataVerificationActive: config.idCardDataVerificationActive, - idCardDataVerificationThreshold: config.idCardDataVerificationThreshold, - idCardPhotoVerificationActive: config.idCardPhotoVerificationActive, - idCardPhotoVerificationThreshold: config.idCardPhotoVerificationThreshold, - sanctionsVerificationActive: config.sanctionsVerificationActive, - sanctionsVerificationThreshold: config.sanctionsVerificationThreshold, - crossRefVerificationActive: config.crossRefVerificationActive, - crossRefVerificationThreshold: config.crossRefVerificationThreshold, - frontCameraVerificationActive: config.frontCameraVerificationActive, - frontCameraVerificationThreshold: config.frontCameraVerificationThreshold, - receiptPrintingActive: config.receiptPrintingActive, + smsVerificationActive: !!compatTriggers.sms, + smsVerificationThreshold: compatTriggers.sms, + hardLimitVerificationActive: !!compatTriggers.block, + hardLimitVerificationThreshold: compatTriggers.block, + idCardDataVerificationActive: !!compatTriggers.idData, + idCardDataVerificationThreshold: compatTriggers.idData, + idCardPhotoVerificationActive: !!compatTriggers.idPhoto, + idCardPhotoVerificationThreshold: compatTriggers.idPhoto, + sanctionsVerificationActive: !!compatTriggers.sancations, + sanctionsVerificationThreshold: compatTriggers.sancations, + frontCameraVerificationActive: !!compatTriggers.facephoto, + frontCameraVerificationThreshold: compatTriggers.facephoto, + receiptPrintingActive: receipt.active, cassettes, - twoWayMode: config.cashOutEnabled, - zeroConfLimit: config.zeroConfLimit, + twoWayMode: cashOutConfig.active, + zeroConfLimit: cashOutConfig.zeroConfLimit, reboot, restartServices, hasLightning, - operatorInfo: { - active: config.operatorInfoActive, - name: config.operatorInfoName, - phone: config.operatorInfoPhone, - email: config.operatorInfoEmail, - website: config.operatorInfoWebsite, - companyNumber: config.operatorInfoCompanyNumber - } + receipt, + operatorInfo } // BACKWARDS_COMPATIBILITY 7.5 // machines before 7.5 expect t&c on poll if (!machineVersion || semver.lt(machineVersion, '7.5.0-beta')) { - response.terms = config.termsScreenActive && config.termsScreenText ? createTerms(config) : null - } - - if (response.idVerificationEnabled) { - response.idVerificationLimit = config.idVerificationLimit + response.terms = createTerms(terms) } return res.json(_.assign(response, results)) @@ -133,13 +129,12 @@ function getTermsConditions (req, res, next) { const deviceId = req.deviceId const settings = req.settings - const config = configManager.unscoped(req.settings.config) + const terms = configManager.getTermsConditions(settings.config) + const pi = plugins(settings, deviceId) - const terms = config.termsScreenActive && config.termsScreenText ? createTerms(config) : null - return pi.fetchCurrentConfigVersion().then(version => { - return res.json({ terms, version }) + return res.json({ terms: createTerms(terms), version }) }) .catch(next) } @@ -213,7 +208,8 @@ function verifyTx (req, res, next) { function addOrUpdateCustomer (req) { const customerData = req.body - const config = configManager.unscoped(req.settings.config) + const triggers = configManager.getTriggers(req.settings.config) + const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) return customers.get(customerData.phone) .then(customer => { @@ -222,7 +218,7 @@ function addOrUpdateCustomer (req) { return customers.add(req.body) }) .then(customer => { - return compliance.validationPatch(req.deviceId, config, customer) + return compliance.validationPatch(req.deviceId, !!compatTriggers.sanctions, customer) .then(patch => { if (_.isEmpty(patch)) return customer return customers.update(customer.id, patch) @@ -250,14 +246,15 @@ function updateCustomer (req, res, next) { const id = req.params.id const txId = req.query.txId const patch = req.body - const config = configManager.unscoped(req.settings.config) + const triggers = configManager.getTriggers(req.settings.config) + const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) customers.getById(id) .then(customer => { if (!customer) { throw httpError('Not Found', 404) } const mergedCustomer = _.merge(customer, patch) - return compliance.validationPatch(req.deviceId, config, mergedCustomer) + return compliance.validationPatch(req.deviceId, !!compatTriggers.sanctions, mergedCustomer) .then(_.merge(patch)) .then(newPatch => customers.updatePhotoCard(id, newPatch)) .then(newPatch => customers.updateFrontCamera(id, newPatch)) @@ -305,8 +302,7 @@ function pair (req, res, next) { return pairing.pair(token, deviceId, model) .then(valid => { if (valid) { - return helpers.updateMachineDefaults(deviceId) - .then(() => res.json({ status: 'paired' })) + return res.json({ status: 'paired' }) } throw httpError('Pairing failed') @@ -457,7 +453,7 @@ localApp.post('/restartServices', (req, res) => { localApp.post('/dbChange', (req, res, next) => { settingsCache.cache = null - return settingsLoader.loadLatest() + return newSettingsLoader.loadLatest() .then(poller.reload) .then(() => logger.info('Config reloaded')) .catch(err => { @@ -504,7 +500,7 @@ function populateSettings (req, res, next) { } if (!versionId && !settingsCache.cache) { - return settingsLoader.loadLatest() + return newSettingsLoader.loadLatest() .then(settings => { settingsCache.cache = settings settingsCache.timestamp = Date.now() @@ -514,20 +510,22 @@ function populateSettings (req, res, next) { .catch(next) } - settingsLoader.load(versionId) + newSettingsLoader.load(versionId) .then(settings => { req.settings = settings }) .then(() => helpers.updateDeviceConfigVersion(versionId)) .then(() => next()) .catch(next) } -function createTerms (config) { +function createTerms (terms) { + if (!terms.active || !terms.text) return null + return { - active: config.termsScreenActive, - title: config.termsScreenTitle, - text: nmd(config.termsScreenText), - accept: config.termsAcceptButtonText, - cancel: config.termsCancelButtonText + active: terms.active, + title: terms.title, + text: nmd(terms.text), + accept: terms.acceptButtonText, + cancel: terms.cancelButtonText } } diff --git a/lib/sms.js b/lib/sms.js index b6b0a82e..c338113b 100644 --- a/lib/sms.js +++ b/lib/sms.js @@ -1,10 +1,12 @@ -const configManager = require('./config-manager') +// const configManager = require('./config-manager') const ph = require('./plugin-helper') function sendMessage (settings, rec) { return Promise.resolve() .then(() => { - const pluginCode = configManager.unscoped(settings.config).sms + // TODO new-admin: how to load mock here? Only on dev? + // const pluginCode = configManager.unscoped(settings.config).sms + const pluginCode = 'twilio' const plugin = ph.load(ph.SMS, pluginCode) const account = settings.accounts[pluginCode] diff --git a/lib/ticker.js b/lib/ticker.js index ed42c7af..5ea068ec 100644 --- a/lib/ticker.js +++ b/lib/ticker.js @@ -1,5 +1,5 @@ const mem = require('mem') -const configManager = require('./config-manager') +const configManager = require('./new-config-manager') const ph = require('./plugin-helper') const logger = require('./logger') @@ -11,8 +11,9 @@ function _getRates (settings, fiatCode, cryptoCode) { return Promise.resolve() .then(() => { const config = settings.config - const plugin = configManager.cryptoScoped(cryptoCode, config).ticker + const plugin = configManager.getWalletSettings(cryptoCode, config).ticker + logger.info(plugin) const account = settings.accounts[plugin] const ticker = ph.load(ph.TICKER, plugin) diff --git a/lib/wallet.js b/lib/wallet.js index 0e069f8c..086d9a4f 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -2,7 +2,7 @@ const _ = require('lodash/fp') const mem = require('mem') const hkdf = require('futoin-hkdf') -const configManager = require('./config-manager') +const configManager = require('./new-config-manager') const pify = require('pify') const fs = pify(require('fs')) @@ -32,7 +32,7 @@ function fetchWallet (settings, cryptoCode) { return fs.readFile(options.mnemonicPath, 'utf8') .then(mnemonic => { const masterSeed = mnemonicHelpers.toEntropyBuffer(mnemonic) - const plugin = configManager.cryptoScoped(cryptoCode, settings.config).wallet + const plugin = configManager.getWalletSettings(cryptoCode, settings.config).wallet const wallet = ph.load(ph.WALLET, plugin) const rawAccount = settings.accounts[plugin] const account = _.set('seed', computeSeed(masterSeed), rawAccount) @@ -135,10 +135,9 @@ function getWalletStatus (settings, tx) { } function authorizeZeroConf (settings, tx, machineId) { - const cryptoConfig = configManager.cryptoScoped(tx.cryptoCode, settings.config) - const machineConfig = configManager.machineScoped(machineId, settings.config) - const plugin = cryptoConfig.zeroConf - const zeroConfLimit = machineConfig.zeroConfLimit + const plugin = configManager.getWalletSettings(tx.cryptoCode, settings.config).zeroConf + const cashOutConfig = configManager.cashOutConfig(machineId, settings.config) + const zeroConfLimit = cashOutConfig.zeroConfLimit if (!_.isObject(tx.fiat)) { return Promise.reject(new Error('tx.fiat is undefined!')) @@ -189,7 +188,7 @@ function isHd (settings, cryptoCode) { } function cryptoNetwork (settings, cryptoCode) { - const plugin = configManager.cryptoScoped(cryptoCode, settings.config).wallet + const plugin = configManager.getWalletSettings(cryptoCode, settings.config).wallet const wallet = ph.load(ph.WALLET, plugin) const account = settings.accounts[plugin] diff --git a/new-lamassu-admin/src/components/Modal.js b/new-lamassu-admin/src/components/Modal.js index 7651be26..fc84d0a8 100644 --- a/new-lamassu-admin/src/components/Modal.js +++ b/new-lamassu-admin/src/components/Modal.js @@ -3,17 +3,19 @@ import classnames from 'classnames' import React from 'react' import { IconButton } from 'src/components/buttons' -import { H1 } from 'src/components/typography' +import { H1, H2 } from 'src/components/typography' import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg' const styles = { modal: { display: 'flex', justifyContent: 'center', + flexDirection: 'column', alignItems: 'center' }, - wrapper: ({ width }) => ({ + wrapper: ({ width, height }) => ({ width, + height, display: 'flex', flexDirection: 'column', minHeight: 400, @@ -45,7 +47,10 @@ const useStyles = makeStyles(styles) const Modal = ({ width, + height, title, + titleSmall, + infoPanel, handleClose, children, className, @@ -53,7 +58,9 @@ const Modal = ({ closeOnBackdropClick, ...props }) => { - const classes = useStyles({ width }) + const classes = useStyles({ width, height }) + const TitleCase = titleSmall ? H2 : H1 + const closeSize = titleSmall ? 16 : 20 const innerClose = (evt, reason) => { if (!closeOnBackdropClick && reason === 'backdropClick') return @@ -63,18 +70,25 @@ const Modal = ({ return ( - -
- {title &&

{title}

} - handleClose()}> - - -
-
{children}
-
+ <> + +
+ {title && {title}} + handleClose()}> + + +
+
{children}
+
+ {infoPanel && ( + + {infoPanel} + + )} +
) } diff --git a/new-lamassu-admin/src/components/editableTable/Row.js b/new-lamassu-admin/src/components/editableTable/Row.js index 0a6b91dc..ab95f363 100644 --- a/new-lamassu-admin/src/components/editableTable/Row.js +++ b/new-lamassu-admin/src/components/editableTable/Row.js @@ -154,6 +154,7 @@ const ERow = ({ editing, disabled }) => { enableEdit, enableDelete, enableToggle, + rowSize, stripeWhen } = useContext(TableCtx) @@ -163,6 +164,7 @@ const ERow = ({ editing, disabled }) => { const innerElements = shouldStripe ? groupStriped(elements) : elements return ( {innerElements.map((it, idx) => { diff --git a/new-lamassu-admin/src/components/editableTable/Table.js b/new-lamassu-admin/src/components/editableTable/Table.js index b6ce3345..7477bcca 100644 --- a/new-lamassu-admin/src/components/editableTable/Table.js +++ b/new-lamassu-admin/src/components/editableTable/Table.js @@ -31,6 +31,7 @@ const ETable = ({ elements = [], data = [], save, + rowSize = 'md', validationSchema, enableCreate, enableEdit, @@ -108,6 +109,7 @@ const ETable = ({ onDelete, deleteWidth, enableToggle, + rowSize, onToggle, toggleWidth, actionColSize, diff --git a/new-lamassu-admin/src/components/fake-table/Table.js b/new-lamassu-admin/src/components/fake-table/Table.js index b5a84760..e7e2a368 100644 --- a/new-lamassu-admin/src/components/fake-table/Table.js +++ b/new-lamassu-admin/src/components/fake-table/Table.js @@ -76,18 +76,8 @@ const ThDoubleLevel = ({ title, children, className }) => { ) } -const CellDoubleLevel = ({ children, className }) => { - const classes = useStyles() - - return ( -
- {children} -
- ) -} - -const Tr = ({ error, errorMessage, children, className }) => { - const classes = useStyles() +const Tr = ({ error, errorMessage, children, className, size }) => { + const classes = useStyles({ size }) const cardClasses = { root: classes.cardContentRoot } const classNames = { [classes.tr]: true, @@ -128,6 +118,5 @@ export { Td, Th, ThDoubleLevel, - CellDoubleLevel, EditCell } diff --git a/new-lamassu-admin/src/components/fake-table/Table.styles.js b/new-lamassu-admin/src/components/fake-table/Table.styles.js index 84acf4a9..05498e4a 100644 --- a/new-lamassu-admin/src/components/fake-table/Table.styles.js +++ b/new-lamassu-admin/src/components/fake-table/Table.styles.js @@ -70,14 +70,16 @@ export default { trError: { backgroundColor: tableErrorColor }, - mainContent: { - display: 'flex', - alignItems: 'center', - minHeight: 48 + mainContent: ({ size }) => { + const minHeight = size === 'lg' ? 68 : 48 + return { + display: 'flex', + alignItems: 'center', + minHeight + } }, // mui-overrides cardContentRoot: { - // display: 'flex', margin: 0, padding: 0, '&:last-child': { diff --git a/new-lamassu-admin/src/components/inputs/base/TextInput.js b/new-lamassu-admin/src/components/inputs/base/TextInput.js index b4b41f3c..243cf829 100644 --- a/new-lamassu-admin/src/components/inputs/base/TextInput.js +++ b/new-lamassu-admin/src/components/inputs/base/TextInput.js @@ -26,7 +26,7 @@ const TextInput = memo( ...props }) => { const classes = useStyles({ textAlign, width, size }) - const filled = !error && value && !R.isEmpty(value) + const filled = !error && !R.isNil(value) && !R.isEmpty(value) const inputClasses = { [classes.bold]: bold diff --git a/new-lamassu-admin/src/components/inputs/formik/RadioGroup.js b/new-lamassu-admin/src/components/inputs/formik/RadioGroup.js index 3702101c..e7b94554 100644 --- a/new-lamassu-admin/src/components/inputs/formik/RadioGroup.js +++ b/new-lamassu-admin/src/components/inputs/formik/RadioGroup.js @@ -13,8 +13,9 @@ const RadioGroupFormik = memo(({ label, ...props }) => { options={props.options} ariaLabel={name} onChange={e => { + console.log(e) onChange(e) - props.resetError() + props.resetError && props.resetError() }} className={props.className} {...props} diff --git a/new-lamassu-admin/src/components/inputs/formik/index.js b/new-lamassu-admin/src/components/inputs/formik/index.js index 66e1e1e2..30937441 100644 --- a/new-lamassu-admin/src/components/inputs/formik/index.js +++ b/new-lamassu-admin/src/components/inputs/formik/index.js @@ -1,4 +1,6 @@ +import Autocomplete from './Autocomplete' import Checkbox from './Checkbox' +import RadioGroup from './RadioGroup' import TextInput from './TextInput' -export { Checkbox, TextInput } +export { Autocomplete, Checkbox, TextInput, RadioGroup } diff --git a/new-lamassu-admin/src/pages/Cashout/Cashout.js b/new-lamassu-admin/src/pages/Cashout/Cashout.js index 5a82d083..03dbd611 100644 --- a/new-lamassu-admin/src/pages/Cashout/Cashout.js +++ b/new-lamassu-admin/src/pages/Cashout/Cashout.js @@ -11,9 +11,8 @@ import Wizard from './Wizard' import { DenominationsSchema, getElements } from './helper' const SAVE_CONFIG = gql` - mutation Save($config: JSONObject, $accounts: [JSONObject]) { + mutation Save($config: JSONObject) { saveConfig(config: $config) - saveAccounts(accounts: $accounts) } ` @@ -44,7 +43,6 @@ const CashOut = ({ name: SCREEN_KEY }) => { const save = (rawConfig, accounts) => { const config = toNamespace(SCREEN_KEY)(rawConfig) setError(false) - return saveConfig({ variables: { config, accounts } }) } diff --git a/new-lamassu-admin/src/pages/Cashout/Wizard.js b/new-lamassu-admin/src/pages/Cashout/Wizard.js index a49576b3..2411f7f8 100644 --- a/new-lamassu-admin/src/pages/Cashout/Wizard.js +++ b/new-lamassu-admin/src/pages/Cashout/Wizard.js @@ -1,11 +1,13 @@ import * as R from 'ramda' import React, { useState } from 'react' +import * as Yup from 'yup' import Modal from 'src/components/Modal' import { toNamespace } from 'src/utils/config' import WizardSplash from './WizardSplash' import WizardStep from './WizardStep' +import { DenominationsSchema } from './helper' const LAST_STEP = 3 const MODAL_WIDTH = 554 @@ -20,12 +22,14 @@ const Wizard = ({ machine, onClose, save, error }) => { const isLastStep = step === LAST_STEP const onContinue = async it => { - const newConfig = R.merge(config, it) - if (isLastStep) { - return save(toNamespace(machine.deviceId, newConfig)) + return save( + toNamespace(machine.deviceId, DenominationsSchema.cast(config)) + ) } + const newConfig = R.merge(config, it) + setState({ step: step + 1, config: newConfig @@ -35,11 +39,17 @@ const Wizard = ({ machine, onClose, save, error }) => { const getStepData = () => { switch (step) { case 1: - return { type: 'top', display: 'Cassete 1 (Top)' } + return { + type: 'top', + display: 'Cassete 1 (Top)', + schema: Yup.object().shape({ top: Yup.number().required() }) + } case 2: - return { type: 'bottom', display: 'Cassete 2' } - case 3: - return { type: 'agreed' } + return { + type: 'bottom', + display: 'Cassete 2', + schema: Yup.object().shape({ bottom: Yup.number().required() }) + } default: return null } diff --git a/new-lamassu-admin/src/pages/Cashout/WizardStep.js b/new-lamassu-admin/src/pages/Cashout/WizardStep.js index e2fe99fa..b0a0a3e9 100644 --- a/new-lamassu-admin/src/pages/Cashout/WizardStep.js +++ b/new-lamassu-admin/src/pages/Cashout/WizardStep.js @@ -1,77 +1,33 @@ import { makeStyles } from '@material-ui/core' import classnames from 'classnames' -import * as R from 'ramda' -import React, { useReducer, useEffect } from 'react' +import { Formik, Form, Field } from 'formik' +import React from 'react' import ErrorMessage from 'src/components/ErrorMessage' import Stepper from 'src/components/Stepper' import { Button } from 'src/components/buttons' -import { TextInput } from 'src/components/inputs' +import { TextInput } from 'src/components/inputs/formik' import { Info2, H4, P } from 'src/components/typography' import styles from './WizardStep.styles' const useStyles = makeStyles(styles) -const initialState = { - selected: null, - iError: false -} - -const reducer = (state, action) => { - switch (action.type) { - case 'select': - return { - form: null, - selected: action.selected, - isNew: null, - iError: false - } - case 'form': - return { - form: action.form, - selected: action.form.code, - isNew: true, - iError: false - } - case 'error': - return R.merge(state, { iError: true }) - case 'reset': - return initialState - default: - throw new Error() - } -} - const WizardStep = ({ type, name, step, + schema, error, lastStep, onContinue, display }) => { const classes = useStyles() - const [{ iError, selected }, dispatch] = useReducer(reducer, initialState) - - useEffect(() => { - dispatch({ type: 'reset' }) - }, [step]) - - const iContinue = config => { - if (lastStep) config[type] = true - - if (!config || !config[type]) { - return dispatch({ type: 'error' }) - } - - onContinue(config) - } const label = lastStep ? 'Finish' : 'Next' const subtitleClass = { [classes.subtitle]: true, - [classes.error]: iError + [classes.error]: error } return ( @@ -81,19 +37,26 @@ const WizardStep = ({ {display &&

Edit {display}

} {!lastStep && ( - - dispatch({ type: 'select', selected: evt.target.value }) - } - autoFocus - id="confirm-input" - type="text" - size="lg" - touched={{}} - error={false} - InputLabelProps={{ shrink: true }} - /> + +
+ +
+ +
+ +
// TODO: there was a disabled link here showing the currency code; restore it )} @@ -112,17 +75,14 @@ const WizardStep = ({ Settings. where you can set exceptions for each of the available cryptocurrencies.

+
+ {error && Failed to save} + +
)} - -
- {error && Failed to save} - -
) } diff --git a/new-lamassu-admin/src/pages/Cashout/helper.js b/new-lamassu-admin/src/pages/Cashout/helper.js index 466da650..8eb9a786 100644 --- a/new-lamassu-admin/src/pages/Cashout/helper.js +++ b/new-lamassu-admin/src/pages/Cashout/helper.js @@ -4,7 +4,8 @@ import TextInput from 'src/components/inputs/formik/TextInput' const DenominationsSchema = Yup.object().shape({ top: Yup.number().required('Required'), - bottom: Yup.number().required('Required') + bottom: Yup.number().required('Required'), + zeroConfLimit: Yup.number().required('Required') }) const getElements = (machines, { fiatCurrency } = {}) => { @@ -28,12 +29,20 @@ const getElements = (machines, { fiatCurrency } = {}) => { }, { name: 'bottom', - header: 'Cassette 2', + header: 'Cassette 2 (Bottom)', view: it => `${it} ${fiatCurrency}`, size: 'sm', stripe: true, width: 265, input: TextInput + }, + { + name: 'zeroConfLimit', + header: '0-conf Limit', + size: 'sm', + stripe: true, + width: 200, + input: TextInput } ] } diff --git a/new-lamassu-admin/src/pages/Cashout/index.js b/new-lamassu-admin/src/pages/Cashout/index.js new file mode 100644 index 00000000..e625c749 --- /dev/null +++ b/new-lamassu-admin/src/pages/Cashout/index.js @@ -0,0 +1,3 @@ +import Cashout from './Cashout' + +export default Cashout diff --git a/new-lamassu-admin/src/pages/Commissions.js b/new-lamassu-admin/src/pages/Commissions.js deleted file mode 100644 index 60c440ba..00000000 --- a/new-lamassu-admin/src/pages/Commissions.js +++ /dev/null @@ -1,179 +0,0 @@ -import React, { useState } from 'react' - -import { Link } from 'src/components/buttons' -import { TextInput } from 'src/components/inputs' -import { - Table, - TableHead, - TableRow, - TableHeader, - TableBody, - TableCell -} from 'src/components/table' -import { H1, H3, Info1, TL2 } from 'src/components/typography' - -const styles = {} -const EditRow = ({ data = {}, commitValues, setEditing }) => { - const [values, setValues] = React.useState(data) - - const handleChange = name => event => { - setValues({ ...values, [name]: event.target.value }) - } - - return ( - <> - - - - - - - - - - - - - - { - setEditing(false) - }}> - Cancel - - { - commitValues(values) - setEditing(false) - }}> - Save - - - - ) -} - -const ViewRow = ({ data, setEditing }) => ( - <> - - - {data.cashInCommission} - - {data.cashInCommission && ( - - % - - )} - - - - {data.cashOutCommission} - - {data.cashOutCommission && ( - - % - - )} - - - - {data.cashInFee} - - {data.cashOutCommission && ( - - EUR - - )} - - - - {data.minimumTx} - - {data.cashOutCommission && ( - - EUR - - )} - - - setEditing(true)}> - Edit - - - -) - -const Commissions = () => { - const [dataset, setDataset] = useState([{}]) - - const commitValues = (values, idx) => { - const clonedDs = dataset.slice() - clonedDs[idx] = Object.assign({}, clonedDs[idx], values) - setDataset(clonedDs) - } - - const EditableRow = () => - - return ( - <> -

Commissions

-

Default Setup

-
- - - - Cash-in - Cash-out - - Cash-in only - - - Edit - - - - Fixed Fee - Minimum Tx - - - - commitValues(value)} - EditRow={EditRow} - ViewRow={ViewRow} - /> - -
-
- - ) -} - -export default Commissions diff --git a/new-lamassu-admin/src/pages/Commissions.module.scss b/new-lamassu-admin/src/pages/Commissions.module.scss deleted file mode 100644 index 5537de93..00000000 --- a/new-lamassu-admin/src/pages/Commissions.module.scss +++ /dev/null @@ -1,30 +0,0 @@ -.multiRowHeader { - border-bottom-left-radius: 10px; - border-bottom-right-radius: 10px; - text-align: center; - background-color: white; //$placeholder-color; -} - -.tableWrapper { - width: 855px; -} - -.centerAlign { - text-align: center; -} - -.firstLink { - margin-right: 32px; -} - -.noMargin { - margin: 0; -} - -.suffix { - margin-left: 8px; -} - -.numberSmallInput { - width: 85px; -} diff --git a/new-lamassu-admin/src/pages/Commissions/Commissions.js b/new-lamassu-admin/src/pages/Commissions/Commissions.js new file mode 100644 index 00000000..140199e7 --- /dev/null +++ b/new-lamassu-admin/src/pages/Commissions/Commissions.js @@ -0,0 +1,96 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { gql } from 'apollo-boost' +import * as R from 'ramda' +import React from 'react' + +import { Table as EditableTable } from 'src/components/editableTable' +import Section from 'src/components/layout/Section' +import TitleSection from 'src/components/layout/TitleSection' +import { fromNamespace, toNamespace } from 'src/utils/config' + +import { + mainFields, + overrides, + schema, + OverridesSchema, + defaults, + overridesDefaults +} from './helper' + +const GET_DATA = gql` + query getData { + config + cryptoCurrencies { + code + display + } + machines { + name + deviceId + } + } +` + +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` + +const Commissions = ({ name: SCREEN_KEY }) => { + const { data } = useQuery(GET_DATA) + const [saveConfig] = useMutation(SAVE_CONFIG, { + refetchQueries: () => ['getData'] + }) + + const config = data?.config && fromNamespace(SCREEN_KEY)(data.config) + + const commission = config && !R.isEmpty(config) ? config : defaults + + const save = it => { + const config = toNamespace(SCREEN_KEY)(it.commissions[0]) + return saveConfig({ variables: { config } }) + } + + const saveOverrides = it => { + const config = toNamespace(SCREEN_KEY)(it) + return saveConfig({ variables: { config } }) + } + + return ( + <> + +
+ +
+
+ +
+ + ) +} + +export default Commissions diff --git a/new-lamassu-admin/src/pages/Commissions/helper.js b/new-lamassu-admin/src/pages/Commissions/helper.js new file mode 100644 index 00000000..5b2bb2e9 --- /dev/null +++ b/new-lamassu-admin/src/pages/Commissions/helper.js @@ -0,0 +1,156 @@ +import * as R from 'ramda' +import * as Yup from 'yup' + +import { TextInput } from 'src/components/inputs/formik' +import Autocomplete from 'src/components/inputs/formik/Autocomplete.js' + +const getOverridesFields = getData => { + const getView = (data, code, compare) => it => { + if (!data) return '' + + return R.compose( + R.prop(code), + R.find(R.propEq(compare ?? 'code', it)) + )(data) + } + + const displayCodeArray = data => it => { + if (!it) return it + + return R.compose(R.join(', '), R.map(getView(data, 'code')))(it) + } + + const machineData = getData(['machines']) + const cryptoData = getData(['cryptoCurrencies']) + + return [ + { + name: 'machine', + width: 196, + size: 'sm', + view: getView(machineData, 'name', 'deviceId'), + input: Autocomplete, + inputProps: { + options: machineData, + valueProp: 'deviceId', + getLabel: R.path(['name']), + limit: null + } + }, + { + name: 'cryptoCurrencies', + width: 270, + size: 'sm', + view: displayCodeArray(cryptoData), + input: Autocomplete, + inputProps: { + options: cryptoData, + valueProp: 'code', + getLabel: R.path(['code']), + multiple: true + } + }, + { + name: 'cashIn', + display: 'Cash-in', + width: 140, + input: TextInput + }, + { + name: 'cashOut', + display: 'Cash-out', + width: 140, + input: TextInput + }, + { + name: 'fixedFee', + display: 'Fixed fee', + width: 140, + input: TextInput + }, + { + name: 'minimumTx', + display: 'Minimun Tx', + width: 140, + input: TextInput + } + ] +} + +const mainFields = auxData => [ + { + name: 'cashIn', + display: 'Cash-in', + width: 169, + size: 'lg', + input: TextInput + }, + { + name: 'cashOut', + display: 'Cash-out', + width: 169, + size: 'lg', + input: TextInput + }, + { + name: 'fixedFee', + display: 'Fixed fee', + width: 169, + size: 'lg', + input: TextInput + }, + { + name: 'minimumTx', + display: 'Minimun Tx', + width: 169, + size: 'lg', + input: TextInput + } +] + +const overrides = auxData => { + const getData = R.path(R.__, auxData) + + return getOverridesFields(getData) +} + +const schema = Yup.object().shape({ + cashIn: Yup.number().required('Required'), + cashOut: Yup.number().required('Required'), + fixedFee: Yup.number().required('Required'), + minimumTx: Yup.number().required('Required') +}) + +const OverridesSchema = Yup.object().shape({ + machine: Yup.string().required('Required'), + cryptoCurrencies: Yup.array().required('Required'), + cashIn: Yup.number().required('Required'), + cashOut: Yup.number().required('Required'), + fixedFee: Yup.number().required('Required'), + minimumTx: Yup.number().required('Required') +}) + +const defaults = { + cashIn: '', + cashOut: '', + fixedFee: '', + minimumTx: '' +} + +const overridesDefaults = { + machine: '', + cryptoCurrencies: [], + cashIn: '', + cashOut: '', + fixedFee: '', + minimumTx: '' +} + +export { + mainFields, + overrides, + schema, + OverridesSchema, + defaults, + overridesDefaults +} diff --git a/new-lamassu-admin/src/pages/Commissions/index.js b/new-lamassu-admin/src/pages/Commissions/index.js new file mode 100644 index 00000000..3e3f2b53 --- /dev/null +++ b/new-lamassu-admin/src/pages/Commissions/index.js @@ -0,0 +1,3 @@ +import Commissions from './Commissions' + +export default Commissions diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js index 9f421323..4e0402df 100644 --- a/new-lamassu-admin/src/pages/Customers/Customers.js +++ b/new-lamassu-admin/src/pages/Customers/Customers.js @@ -1,8 +1,8 @@ import { useQuery } from '@apollo/react-hooks' import { gql } from 'apollo-boost' -import { useHistory } from 'react-router-dom' import * as R from 'ramda' import React from 'react' +import { useHistory } from 'react-router-dom' import CustomersList from './CustomersList' @@ -29,7 +29,7 @@ const Customers = () => { const handleCustomerClicked = customer => history.push(`/compliance/customer/${customer.id}`) - const customersData = R.sortWith([R.descend('lastActive')])( + const customersData = R.sortWith([R.descend(R.prop('lastActive'))])( R.path(['customers'])(customersResponse) ?? [] ) diff --git a/new-lamassu-admin/src/pages/Maintenance/Cashboxes.js b/new-lamassu-admin/src/pages/Maintenance/Cashboxes.js new file mode 100644 index 00000000..adfeea98 --- /dev/null +++ b/new-lamassu-admin/src/pages/Maintenance/Cashboxes.js @@ -0,0 +1,117 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { gql } from 'apollo-boost' +import React from 'react' +import * as Yup from 'yup' + +import { Table as EditableTable } from 'src/components/editableTable' +import TextInputFormik from 'src/components/inputs/formik/TextInput' +import TitleSection from 'src/components/layout/TitleSection' + +const ValidationSchema = Yup.object().shape({ + name: Yup.string().required('Required'), + cassette1: Yup.number() + .required('Required') + .integer() + .min(0), + cassette2: Yup.number() + .required('Required') + .integer() + .min(0) +}) + +const GET_MACHINES_AND_CONFIG = gql` + { + machines { + name + id: deviceId + cassette1 + cassette2 + } + config + } +` + +const RESET_CASHOUT_BILLS = gql` + mutation MachineAction( + $deviceId: ID! + $action: MachineAction! + $cassette1: Int! + $cassette2: Int! + ) { + machineAction( + deviceId: $deviceId + action: $action + cassette1: $cassette1 + cassette2: $cassette2 + ) { + deviceId + cassette1 + cassette2 + } + } +` + +const Cashboxes = () => { + const { data } = useQuery(GET_MACHINES_AND_CONFIG) + + const [resetCashOut] = useMutation(RESET_CASHOUT_BILLS, { + onError: ({ graphQLErrors, message }) => { + const errorMessage = graphQLErrors[0] ? graphQLErrors[0].message : message + // TODO: this should not be final + alert(JSON.stringify(errorMessage)) + } + }) + + const onSave = (...[, { id, cassette1, cassette2 }]) => { + return resetCashOut({ + variables: { + action: 'resetCashOutBills', + deviceId: id, + cassette1, + cassette2 + } + }) + } + + const elements = [ + { + name: 'name', + header: 'Machine', + width: 254, + textAlign: 'left', + view: name => <>{name}, + input: ({ field: { value: name } }) => <>{name} + }, + { + name: 'cassette1', + header: 'Cash-out 1', + width: 265, + textAlign: 'left', + input: TextInputFormik + }, + { + name: 'cassette2', + header: 'Cash-out 2', + width: 265, + textAlign: 'left', + input: TextInputFormik + } + ] + + return ( + <> + + + + + ) +} + +export default Cashboxes diff --git a/new-lamassu-admin/src/pages/maintenance/MachineDetailsCard.js b/new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js similarity index 100% rename from new-lamassu-admin/src/pages/maintenance/MachineDetailsCard.js rename to new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js index 510c33fe..1e39947e 100644 --- a/new-lamassu-admin/src/pages/maintenance/MachineDetailsCard.js +++ b/new-lamassu-admin/src/pages/Maintenance/MachineDetailsCard.js @@ -1,16 +1,16 @@ +import { useMutation } from '@apollo/react-hooks' +import { Dialog, DialogContent } from '@material-ui/core' import { makeStyles } from '@material-ui/core/styles' +import { gql } from 'apollo-boost' import classnames from 'classnames' import moment from 'moment' import React, { useState } from 'react' -import { useMutation } from '@apollo/react-hooks' -import { gql } from 'apollo-boost' -import { Dialog, DialogContent } from '@material-ui/core' import { H4 } from 'src/components/typography' -import ActionButton from '../../components/buttons/ActionButton' import { DialogTitle, ConfirmDialog } from '../../components/ConfirmDialog' import { Status } from '../../components/Status' +import ActionButton from '../../components/buttons/ActionButton' import { ReactComponent as DownloadReversedIcon } from '../../styling/icons/button/download/white.svg' import { ReactComponent as DownloadIcon } from '../../styling/icons/button/download/zodiac.svg' import { ReactComponent as RebootReversedIcon } from '../../styling/icons/button/reboot/white.svg' @@ -19,11 +19,11 @@ import { ReactComponent as ShutdownReversedIcon } from '../../styling/icons/butt import { ReactComponent as ShutdownIcon } from '../../styling/icons/button/shut down/zodiac.svg' import { ReactComponent as UnpairReversedIcon } from '../../styling/icons/button/unpair/white.svg' import { ReactComponent as UnpairIcon } from '../../styling/icons/button/unpair/zodiac.svg' +import { zircon } from '../../styling/variables' import { detailsRowStyles, labelStyles } from '../Transactions/Transactions.styles' -import { zircon } from '../../styling/variables' const MACHINE_ACTION = gql` mutation MachineAction($deviceId: ID!, $action: MachineAction!) { diff --git a/new-lamassu-admin/src/pages/maintenance/MachineStatus.js b/new-lamassu-admin/src/pages/Maintenance/MachineStatus.js similarity index 100% rename from new-lamassu-admin/src/pages/maintenance/MachineStatus.js rename to new-lamassu-admin/src/pages/Maintenance/MachineStatus.js diff --git a/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js b/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js index 702a45d9..8b1adfe3 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js @@ -22,9 +22,9 @@ import { } from './OperatorInfo.styles' const validationSchema = Yup.object().shape({ - infoCardEnabled: Yup.boolean().required(), - fullName: Yup.string().required(), - phoneNumber: Yup.string().required(), + active: Yup.boolean().required(), + name: Yup.string().required(), + phone: Yup.string().required(), email: Yup.string() .email('Please enter a valid email address') .required(), @@ -111,7 +111,7 @@ const ContactInfo = () => { const [error, setError] = useState(null) const [saveConfig] = useMutation(SAVE_CONFIG, { onCompleted: data => { - setInfo(fromNamespace(namespaces.CONTACT_INFO, data.saveConfig)) + setInfo(fromNamespace(namespaces.OPERATOR_INFO, data.saveConfig)) setEditing(false) }, onError: e => setError(e) @@ -119,13 +119,13 @@ const ContactInfo = () => { useQuery(GET_CONFIG, { onCompleted: data => { - setInfo(fromNamespace(namespaces.CONTACT_INFO, data.config)) + setInfo(fromNamespace(namespaces.OPERATOR_INFO, data.config)) } }) const save = it => { return saveConfig({ - variables: { config: toNamespace(namespaces.CONTACT_INFO, it) } + variables: { config: toNamespace(namespaces.OPERATOR_INFO, it) } }) } @@ -135,21 +135,21 @@ const ContactInfo = () => { const fields = [ { - name: 'infoCardEnabled', + name: 'active', label: 'Info Card Enabled', - value: String(info.infoCardEnabled), + value: String(info.active), component: RadioGroupFormik }, { - name: 'fullName', + name: 'name', label: 'Full name', - value: info.fullName ?? '', + value: info.name ?? '', component: TextInputFormik }, { - name: 'phoneNumber', + name: 'phone', label: 'Phone number', - value: info.phoneNumber ?? '', + value: info.phone ?? '', component: TextInputFormik }, { @@ -179,9 +179,9 @@ const ContactInfo = () => { const form = { initialValues: { - infoCardEnabled: findValue('infoCardEnabled'), - fullName: findValue('fullName'), - phoneNumber: info.phoneNumber ?? '', + active: findValue('active'), + name: findValue('name'), + phone: info.phone ?? '', email: findValue('email'), website: findValue('website'), companyNumber: findValue('companyNumber') @@ -213,7 +213,7 @@ const ContactInfo = () => {
(it === 'true' ? 'On' : 'Off')} options={[ @@ -226,13 +226,13 @@ const ContactInfo = () => {
setError(null)} /> setError(null)} diff --git a/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting/ReceiptPrinting.js b/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting/ReceiptPrinting.js index dc5e8565..556637ee 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting/ReceiptPrinting.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting/ReceiptPrinting.js @@ -1,10 +1,12 @@ // import { makeStyles } from '@material-ui/core/styles' import { useQuery, useMutation } from '@apollo/react-hooks' import { gql } from 'apollo-boost' +import * as R from 'ramda' import React, { useState, memo } from 'react' import { BooleanPropertiesTable } from 'src/components/booleanPropertiesTable' import { EditableProperty } from 'src/components/editableProperty' +import { fromNamespace, toNamespace, namespaces } from 'src/utils/config' // import { ActionButton } from 'src/components/buttons' // import { ReactComponent as UploadIcon } from 'src/styling/icons/button/upload/zodiac.svg' // import { ReactComponent as UploadIconInverse } from 'src/styling/icons/button/upload/white.svg' @@ -64,21 +66,28 @@ const ReceiptPrinting = memo(() => { // TODO: treat errors on useMutation and useQuery const [saveConfig] = useMutation(SAVE_CONFIG, { - onCompleted: configResponse => - setReceiptPrintingConfig(configResponse.saveConfig.receiptPrinting) + onCompleted: configResponse => { + console.log(configResponse.saveConfig) + return setReceiptPrintingConfig( + fromNamespace(namespaces.RECEIPT, configResponse.saveConfig) + ) + } }) useQuery(GET_CONFIG, { onCompleted: configResponse => { - setReceiptPrintingConfig( - configResponse?.config?.receiptPrinting ?? initialValues - ) + const response = fromNamespace(namespaces.RECEIPT, configResponse.config) + const values = R.merge(initialValues, response) + setReceiptPrintingConfig(values) } }) const save = it => - saveConfig({ variables: { config: { receiptPrinting: it } } }) + saveConfig({ + variables: { config: toNamespace(namespaces.RECEIPT, it) } + }) if (!receiptPrintingConfig) return null + console.log(receiptPrintingConfig) return ( <> @@ -90,7 +99,12 @@ const ReceiptPrinting = memo(() => { code={receiptPrintingConfig.active} save={it => saveConfig({ - variables: { config: { receiptPrinting: { active: it } } } + variables: { + config: toNamespace( + namespaces.RECEIPT, + R.merge(receiptPrintingConfig, { active: it }) + ) + } }) } /> diff --git a/new-lamassu-admin/src/pages/OperatorInfo/TermsConditions.js b/new-lamassu-admin/src/pages/OperatorInfo/TermsConditions.js index 88a52837..ccfa0e52 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/TermsConditions.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/TermsConditions.js @@ -46,7 +46,7 @@ const TermsConditions = () => { data.saveConfig ) setFormData(termsAndConditions) - setShowOnScreen(termsAndConditions.show) + setShowOnScreen(termsAndConditions.active) setError(null) }, onError: e => setError(e) @@ -61,7 +61,7 @@ const TermsConditions = () => { data.config ) setFormData(termsAndConditions ?? {}) - setShowOnScreen(termsAndConditions?.show ?? false) + setShowOnScreen(termsAndConditions?.active ?? false) } }) @@ -74,21 +74,21 @@ const TermsConditions = () => { const handleEnable = () => { const s = !showOnScreen - save({ show: s }) + save({ active: s }) } if (!formData) return null const fields = [ { - name: 'screenTitle', + name: 'title', label: 'Screen title', - value: formData.screenTitle ?? '' + value: formData.title ?? '' }, { - name: 'textContent', + name: 'text', label: 'Text content', - value: formData.textContent ?? '', + value: formData.text ?? '', multiline: true }, { @@ -109,14 +109,14 @@ const TermsConditions = () => { const findValue = name => findField(name).value const initialValues = { - screenTitle: findValue('screenTitle'), - textContent: findValue('textContent'), + title: findValue('title'), + text: findValue('text'), acceptButtonText: findValue('acceptButtonText'), cancelButtonText: findValue('cancelButtonText') } const validationSchema = Yup.object().shape({ - screenTitle: Yup.string().max(50, 'Too long'), + title: Yup.string().max(50, 'Too long'), acceptButtonText: Yup.string().max(15, 'Too long'), cancelButtonText: Yup.string().max(15, 'Too long') }) diff --git a/new-lamassu-admin/src/pages/Services/Services.js b/new-lamassu-admin/src/pages/Services/Services.js index 54103118..0bc63367 100644 --- a/new-lamassu-admin/src/pages/Services/Services.js +++ b/new-lamassu-admin/src/pages/Services/Services.js @@ -34,7 +34,7 @@ const styles = { const useStyles = makeStyles(styles) -const Services = ({ key: SCREEN_KEY }) => { +const Services = () => { const [editingSchema, setEditingSchema] = useState(null) const { data } = useQuery(GET_INFO) diff --git a/new-lamassu-admin/src/pages/Triggers/NewTriggerWizard.js b/new-lamassu-admin/src/pages/Triggers/NewTriggerWizard.js deleted file mode 100644 index e169f3bc..00000000 --- a/new-lamassu-admin/src/pages/Triggers/NewTriggerWizard.js +++ /dev/null @@ -1,85 +0,0 @@ -import React, { useState } from 'react' -import { useQuery } from '@apollo/react-hooks' -import { gql } from 'apollo-boost' -import { makeStyles } from '@material-ui/core' - -import { Wizard } from 'src/components/wizard' -import { H2, P } from 'src/components/typography' -import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg' -import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg' -import Popper from 'src/components/Popper' - -import SelectTriggerDirection from './SelectTriggerDirection' -import SelectTriggerType from './SelectTriggerType' -import SelectTriggerRequirements from './SelectTriggerRequirements' -import { mainStyles } from './Triggers.styles' - -const useStyles = makeStyles(mainStyles) - -const GET_CONFIG = gql` - { - config - } -` - -const NewTriggerWizard = ({ close, finish }) => { - const { data: configResponse } = useQuery(GET_CONFIG) - const [helpPopperAnchorEl, setHelpPopperAnchorEl] = useState(null) - - const classes = useStyles() - - const fiatCurrencyCode = configResponse?.config?.['locale_fiatCurrency']?.code - - const handleOpenHelpPopper = event => { - setHelpPopperAnchorEl(helpPopperAnchorEl ? null : event.currentTarget) - } - - const handleCloseHelpPopper = () => { - setHelpPopperAnchorEl(null) - } - - const helpPopperOpen = Boolean(helpPopperAnchorEl) - - const wizardHeader = ( -
-

New Compliance Trigger

-
- -
-
- -
-
- ) - - return ( - - - - - - ) -} - -export { NewTriggerWizard } diff --git a/new-lamassu-admin/src/pages/Triggers/SelectTriggerDirection.js b/new-lamassu-admin/src/pages/Triggers/SelectTriggerDirection.js deleted file mode 100644 index 8156543c..00000000 --- a/new-lamassu-admin/src/pages/Triggers/SelectTriggerDirection.js +++ /dev/null @@ -1,77 +0,0 @@ -import { makeStyles } from '@material-ui/core' -import classnames from 'classnames' -import React, { useState } from 'react' - -import Popper from 'src/components/Popper' -import { RadioGroup } from 'src/components/inputs' -import { H4, P } from 'src/components/typography' -import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg' - -import { mainStyles } from './Triggers.styles' - -const useStyles = makeStyles(mainStyles) - -const SelectTriggerDirection = () => { - const [helpPopperAnchorEl, setHelpPopperAnchorEl] = useState(null) - const [radioGroupValue, setRadioGroupValue] = useState('both') - - const classes = useStyles() - - const handleOpenHelpPopper = event => { - setHelpPopperAnchorEl(helpPopperAnchorEl ? null : event.currentTarget) - } - - const handleCloseHelpPopper = () => { - setHelpPopperAnchorEl(null) - } - - const handleRadioButtons = newValue => { - setRadioGroupValue(newValue) - } - - const helpPopperOpen = Boolean(helpPopperAnchorEl) - - const radioButtonOptions = [ - { display: 'Both', code: 'both' }, - { display: 'Only cash-in', code: 'cash-in' }, - { display: 'Only cash-out', code: 'cash-out' } - ] - - return ( -
-
-

In which type of transactions will it trigger?

-
- -
-
-
- handleRadioButtons(event.target.value)} - className={classnames( - classes.radioButtons, - classes.stepOneRadioButtons - )} - /> -
-
- ) -} -export default SelectTriggerDirection diff --git a/new-lamassu-admin/src/pages/Triggers/SelectTriggerRequirements.js b/new-lamassu-admin/src/pages/Triggers/SelectTriggerRequirements.js deleted file mode 100644 index f9d222b2..00000000 --- a/new-lamassu-admin/src/pages/Triggers/SelectTriggerRequirements.js +++ /dev/null @@ -1,141 +0,0 @@ -import { makeStyles } from '@material-ui/core' -import classnames from 'classnames' -import React, { useState } from 'react' - -import Popper from 'src/components/Popper' -import { RadioGroup } from 'src/components/inputs' -import { H4, P } from 'src/components/typography' -import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg' - -import { mainStyles } from './Triggers.styles' - -const useStyles = makeStyles(mainStyles) - -const SelectTriggerRequirements = () => { - const [ - requirementHelpPopperAnchorEl, - setRequirementHelpPopperAnchorEl - ] = useState(null) - const [typeHelpPopperAnchorEl, setTypeHelpPopperAnchorEl] = useState(null) - const [requirementRadioGroupValue, setRequirementRadioGroupValue] = useState( - 'sms' - ) - const [typeRadioGroupValue, setTypeRadioGroupValue] = useState('automatic') - - const classes = useStyles() - - const handleOpenRequirementHelpPopper = event => { - setRequirementHelpPopperAnchorEl( - requirementHelpPopperAnchorEl ? null : event.currentTarget - ) - } - - const handleOpenTypeHelpPopper = event => { - setTypeHelpPopperAnchorEl( - typeHelpPopperAnchorEl ? null : event.currentTarget - ) - } - - const handleCloseRequirementHelpPopper = () => { - setRequirementHelpPopperAnchorEl(null) - } - - const handleCloseTypeHelpPopper = () => { - setTypeHelpPopperAnchorEl(null) - } - - const handleRequirementRadioButtons = newValue => { - setRequirementRadioGroupValue(newValue) - } - - const handleTypeRadioButtons = newValue => { - setTypeRadioGroupValue(newValue) - } - - const requirementHelpPopperOpen = Boolean(requirementHelpPopperAnchorEl) - const typeHelpPopperOpen = Boolean(typeHelpPopperAnchorEl) - - const requirementRadioButtonOptions = [ - { display: 'SMS verification', code: 'sms' }, - { display: 'ID card image', code: 'id-card' }, - { display: 'ID data', code: 'id-data' }, - { display: 'Customer camera', code: 'camera' }, - { display: 'Sanctions', code: 'sanctions' }, - { display: 'Super user', code: 'super-user' }, - { display: 'Suspend', code: 'suspend' }, - { display: 'Block', code: 'block' } - ] - - const typeRadioButtonOptions = [ - { display: 'Fully automatic', code: 'automatic' }, - { display: 'Semi automatic', code: 'semi-automatic' }, - { display: 'Manual', code: 'manual' } - ] - - return ( -
-
-

Choose a requirement

-
- -
-
- handleRequirementRadioButtons(event.target.value)} - className={classnames( - classes.radioButtons, - classes.stepThreeRadioButtons - )} - /> -
-

Choose trigger type

-
- -
-
- handleTypeRadioButtons(event.target.value)} - className={classnames( - classes.radioButtons, - classes.stepThreeRadioButtons - )} - /> -
- ) -} - -export default SelectTriggerRequirements diff --git a/new-lamassu-admin/src/pages/Triggers/SelectTriggerType.js b/new-lamassu-admin/src/pages/Triggers/SelectTriggerType.js deleted file mode 100644 index df5e30b4..00000000 --- a/new-lamassu-admin/src/pages/Triggers/SelectTriggerType.js +++ /dev/null @@ -1,104 +0,0 @@ -import { makeStyles } from '@material-ui/core' -import classnames from 'classnames' -import React, { useState } from 'react' - -import Popper from 'src/components/Popper' -import { RadioGroup, TextInput } from 'src/components/inputs' -import { H4, TL1, P } from 'src/components/typography' -import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg' - -import { mainStyles } from './Triggers.styles' - -const useStyles = makeStyles(mainStyles) - -const SelectTriggerType = ({ fiatCurrencyCode }) => { - const [helpPopperAnchorEl, setHelpPopperAnchorEl] = useState(null) - const [radioGroupValue, setRadioGroupValue] = useState('amount') - const [thresholdValue, setThresholdValue] = useState('') - const [thresholdError, setThresholdError] = useState(false) - - const classes = useStyles() - - const handleOpenHelpPopper = event => { - setHelpPopperAnchorEl(helpPopperAnchorEl ? null : event.currentTarget) - } - - const handleCloseHelpPopper = () => { - setHelpPopperAnchorEl(null) - } - - const handleRadioButtons = newValue => { - setRadioGroupValue(newValue) - } - - const validateThresholdInputIsPositiveInteger = value => { - if ( - (parseFloat(value) === value >>> 0 && !value.includes('.')) || - value === '' - ) { - setThresholdValue(value) - setThresholdError(value === '') - } - } - - const helpPopperOpen = Boolean(helpPopperAnchorEl) - - const radioButtonOptions = [ - { display: 'Transaction amount', code: 'amount' }, - { display: 'Transaction velocity', code: 'velocity' }, - { display: 'Transaction volume', code: 'volume' }, - { display: 'Consecutive days', code: 'days' } - ] - - return ( -
-
-

Choose trigger type

-
- -
-
-
- handleRadioButtons(event.target.value)} - className={classnames( - classes.radioButtons, - classes.stepTwoRadioButtons - )} - /> -
-

Threshold

-
- - validateThresholdInputIsPositiveInteger(event.target.value) - } - error={thresholdError} - size="lg" - value={thresholdValue} - /> - {fiatCurrencyCode} -
-
- ) -} - -export default SelectTriggerType diff --git a/new-lamassu-admin/src/pages/Triggers/Triggers.js b/new-lamassu-admin/src/pages/Triggers/Triggers.js index a5067353..1ebfd830 100644 --- a/new-lamassu-admin/src/pages/Triggers/Triggers.js +++ b/new-lamassu-admin/src/pages/Triggers/Triggers.js @@ -1,5 +1,9 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import { gql } from 'apollo-boost' +import * as R from 'ramda' import React, { useState } from 'react' -import { makeStyles, Modal, Paper } from '@material-ui/core' +import { v4 } from 'uuid' import Title from 'src/components/Title' import { FeatureButton, Link } from 'src/components/buttons' @@ -7,35 +11,50 @@ import { Table as EditableTable } from 'src/components/editableTable' import { ReactComponent as ConfigureInverseIcon } from 'src/styling/icons/button/configure/white.svg' import { ReactComponent as Configure } from 'src/styling/icons/button/configure/zodiac.svg' -import { NewTriggerWizard } from './NewTriggerWizard' import { mainStyles } from './Triggers.styles' +import Wizard from './Wizard' +import { Schema, elements } from './helper' const useStyles = makeStyles(mainStyles) -const sizes = { - triggerType: 236, - requirement: 293, - threshold: 231, - cashDirection: 296 -} +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` + +const GET_INFO = gql` + query getData { + config + } +` const Triggers = () => { - const [wizardModalOpen, setWizardModalOpen] = useState(false) + const [wizard, setWizard] = useState(false) + const [error, setError] = useState(false) + + const { data } = useQuery(GET_INFO) + const triggers = data?.config?.triggers ?? [] + + const [saveConfig] = useMutation(SAVE_CONFIG, { + onCompleted: () => setWizard(false), + onError: () => setError(true), + refetchQueries: () => ['getData'] + }) + + const add = rawConfig => { + const toSave = R.concat([{ id: v4(), ...rawConfig }])(triggers) + setError(false) + return saveConfig({ variables: { config: { triggers: toSave } } }) + } + + const save = config => { + setError(false) + return saveConfig({ variables: { config } }) + } const classes = useStyles() - const handleOpenWizard = () => { - setWizardModalOpen(true) - } - - const handleCloseWizard = () => { - handleFinishWizard() - } - - const handleFinishWizard = () => { - setWizardModalOpen(false) - } - return ( <>
@@ -50,46 +69,22 @@ const Triggers = () => {
- + setWizard(true)}> + Add new trigger
- {wizardModalOpen && ( - - - - - + {wizard && ( + setWizard(null)} /> )} ) diff --git a/new-lamassu-admin/src/pages/Triggers/Triggers.styles.js b/new-lamassu-admin/src/pages/Triggers/Triggers.styles.js index 61584b81..eeae173a 100644 --- a/new-lamassu-admin/src/pages/Triggers/Triggers.styles.js +++ b/new-lamassu-admin/src/pages/Triggers/Triggers.styles.js @@ -1,5 +1,5 @@ -import baseStyles from 'src/pages/Logs.styles' import { booleanPropertiesTableStyles } from 'src/components/booleanPropertiesTable/BooleanPropertiesTable.styles' +import baseStyles from 'src/pages/Logs.styles' const { titleWrapper, titleAndButtonsContainer, buttonsWrapper } = baseStyles const { rowWrapper, radioButtons } = booleanPropertiesTableStyles @@ -10,6 +10,17 @@ const mainStyles = { buttonsWrapper, rowWrapper, radioButtons, + radioGroup: { + flexDirection: 'row' + }, + radioLabel: { + width: 150, + height: 40 + }, + radio: { + padding: 4, + margin: 4 + }, closeButton: { position: 'absolute', width: 16, @@ -34,14 +45,6 @@ const mainStyles = { marginRight: 12 } }, - modal: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - '& > div': { - outline: 'none' - } - }, wizardHeaderText: { display: 'flex', margin: [[24, 0]] diff --git a/new-lamassu-admin/src/pages/Triggers/Wizard.js b/new-lamassu-admin/src/pages/Triggers/Wizard.js new file mode 100644 index 00000000..ff76c550 --- /dev/null +++ b/new-lamassu-admin/src/pages/Triggers/Wizard.js @@ -0,0 +1,103 @@ +import { makeStyles } from '@material-ui/core' +import { Form, Formik } from 'formik' +import * as R from 'ramda' +import React, { useState, Fragment } from 'react' + +import ErrorMessage from 'src/components/ErrorMessage' +import Modal from 'src/components/Modal' +import Stepper from 'src/components/Stepper' +import { Button } from 'src/components/buttons' + +import { direction, type, requirements } from './helper' + +const LAST_STEP = 3 + +const styles = { + stepper: { + margin: [[16, 0, 14, 0]] + }, + submit: { + display: 'flex', + flexDirection: 'row', + margin: [['auto', 0, 24]] + }, + button: { + marginLeft: 'auto' + }, + form: { + height: '100%', + display: 'flex', + flexDirection: 'column' + } +} + +const useStyles = makeStyles(styles) + +const getStep = step => { + switch (step) { + case 1: + return direction + case 2: + return type + case 3: + return requirements + default: + return Fragment + } +} + +const Wizard = ({ machine, onClose, save, error }) => { + const classes = useStyles() + + const [{ step, config }, setState] = useState({ + step: 1 + }) + + const isLastStep = step === LAST_STEP + const stepOptions = getStep(step) + + const onContinue = async it => { + const newConfig = R.merge(config, stepOptions.schema.cast(it)) + + if (isLastStep) { + return save(newConfig) + } + + setState({ + step: step + 1, + config: newConfig + }) + } + + return ( + + + + + +
+ {error && Failed to save} + +
+ +
+
+ ) +} + +export default Wizard diff --git a/new-lamassu-admin/src/pages/Triggers/helper.js b/new-lamassu-admin/src/pages/Triggers/helper.js new file mode 100644 index 00000000..bebc7dc4 --- /dev/null +++ b/new-lamassu-admin/src/pages/Triggers/helper.js @@ -0,0 +1,250 @@ +import { makeStyles, Box } from '@material-ui/core' +import classnames from 'classnames' +import { Field, useFormikContext } from 'formik' +import * as R from 'ramda' +import React from 'react' +import * as Yup from 'yup' + +import { TextInput, RadioGroup } from 'src/components/inputs/formik' +import Autocomplete from 'src/components/inputs/formik/Autocomplete' +import { H4 } from 'src/components/typography' +import { errorColor } from 'src/styling/variables' + +const useStyles = makeStyles({ + radioLabel: { + height: 40, + padding: [[0, 10]] + }, + radio: { + padding: 4, + margin: 4 + }, + radioGroup: { + flexDirection: 'row' + }, + error: { + color: errorColor + }, + specialLabel: { + height: 40, + padding: 0 + }, + specialGrid: { + display: 'grid', + gridTemplateColumns: [[182, 162, 141]] + } +}) + +const cashDirection = Yup.string().required('Required') +const triggerType = Yup.string().required('Required') +const threshold = Yup.number().required('Required') +const requirement = Yup.string().required('Required') + +const Schema = Yup.object().shape({ + triggerType, + requirement, + threshold, + cashDirection +}) + +// Direction +const directionSchema = Yup.object().shape({ cashDirection }) + +const directionOptions = [ + { display: 'Both', code: 'both' }, + { display: 'Only cash-in', code: 'cashIn' }, + { display: 'Only cash-out', code: 'cashOut' } +] + +const Direction = () => { + const classes = useStyles() + const { errors } = useFormikContext() + + const titleClass = { + [classes.error]: errors.cashDirection + } + + return ( + <> + +

+ In which type of transactions will it trigger? +

+
+ + + ) +} + +const direction = { + schema: directionSchema, + options: directionOptions, + Component: Direction, + initialValues: { cashDirection: '' } +} + +// TYPE +const typeSchema = Yup.object().shape({ + triggerType, + threshold +}) + +const typeOptions = [ + { display: 'Transaction amount', code: 'txAmount' }, + { display: 'Transaction velocity', code: 'txVelocity' }, + { display: 'Transaction volume', code: 'txVolume' }, + { display: 'Consecutive days', code: 'consecutiveDays' } +] + +const Type = () => { + const classes = useStyles() + const { errors, touched } = useFormikContext() + + const typeClass = { + [classes.error]: errors.triggerType && touched.triggerType + } + + return ( + <> + +

Choose trigger type

+
+ + + + + ) +} + +const type = { + schema: typeSchema, + options: typeOptions, + Component: Type, + initialValues: { triggerType: '', threshold: '' } +} + +const requirementSchema = Yup.object().shape({ + requirement +}) + +const requirementOptions = [ + { display: 'SMS verification', code: 'sms' }, + { display: 'ID card image', code: 'idPhoto' }, + { display: 'ID data', code: 'idData' }, + { display: 'Customer camera', code: 'facephoto' }, + { display: 'Sanctions', code: 'sanctions' }, + { display: 'Super user', code: 'superuser' }, + { display: 'Suspend', code: 'suspend' }, + { display: 'Block', code: 'block' } +] + +const Requirement = () => { + const classes = useStyles() + const { errors } = useFormikContext() + + const titleClass = { + [classes.error]: errors.requirement + } + + return ( + <> + +

Choose a requirement

+
+ + + ) +} + +const requirements = { + schema: requirementSchema, + options: requirementOptions, + Component: Requirement, + initialValues: { requirement: '' } +} + +const getView = (data, code, compare) => it => { + if (!data) return '' + + return R.compose(R.prop(code), R.find(R.propEq(compare ?? 'code', it)))(data) +} + +const elements = [ + { + name: 'triggerType', + size: 'sm', + width: 271, + input: Autocomplete, + view: getView(typeOptions, 'display'), + inputProps: { + options: typeOptions, + valueProp: 'code', + getLabel: R.path(['display']), + limit: null + } + }, + { + name: 'requirement', + size: 'sm', + width: 271, + input: Autocomplete, + view: getView(requirementOptions, 'display'), + inputProps: { + options: requirementOptions, + valueProp: 'code', + getLabel: R.path(['display']), + limit: null + } + }, + { + name: 'threshold', + size: 'sm', + width: 271, + input: TextInput + }, + { + name: 'cashDirection', + size: 'sm', + width: 200, + view: getView(directionOptions, 'display'), + input: Autocomplete, + inputProps: { + options: directionOptions, + valueProp: 'code', + getLabel: R.path(['display']), + limit: null + } + } +] + +export { Schema, elements, direction, type, requirements } diff --git a/new-lamassu-admin/src/pages/Wallet/WizardStep.js b/new-lamassu-admin/src/pages/Wallet/WizardStep.js index ed8dd072..752e57b4 100644 --- a/new-lamassu-admin/src/pages/Wallet/WizardStep.js +++ b/new-lamassu-admin/src/pages/Wallet/WizardStep.js @@ -7,7 +7,7 @@ import ErrorMessage from 'src/components/ErrorMessage' import Stepper from 'src/components/Stepper' import { Button } from 'src/components/buttons' import { RadioGroup, Autocomplete } from 'src/components/inputs' -import { Info2, H4 } from 'src/components/typography' +import { H4, Info2 } from 'src/components/typography' import FormRenderer from 'src/pages/Services/FormRenderer' import schema from 'src/pages/Services/schemas' import { startCase } from 'src/utils/string' diff --git a/new-lamassu-admin/src/pages/common.styles.js b/new-lamassu-admin/src/pages/common.styles.js deleted file mode 100644 index db9958b5..00000000 --- a/new-lamassu-admin/src/pages/common.styles.js +++ /dev/null @@ -1,17 +0,0 @@ -export default { - titleWrapper: { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - flexDirection: 'row' - }, - titleAndButtonsContainer: { - display: 'flex' - }, - iconButton: { - border: 'none', - outline: 0, - backgroundColor: 'transparent', - cursor: 'pointer' - } -} diff --git a/new-lamassu-admin/src/pages/maintenance/Cashboxes.js b/new-lamassu-admin/src/pages/maintenance/Cashboxes.js deleted file mode 100644 index 214f2ab2..00000000 --- a/new-lamassu-admin/src/pages/maintenance/Cashboxes.js +++ /dev/null @@ -1,210 +0,0 @@ -import { useQuery, useMutation } from '@apollo/react-hooks' -import { gql } from 'apollo-boost' -import React, { useState } from 'react' -import * as Yup from 'yup' - -import { Table as EditableTable } from 'src/components/editableTable' -import { - CashIn, - CashOut, - CashOutFormik, - CashInFormik -} from 'src/components/inputs/cashbox/Cashbox' -import TitleSection from 'src/components/layout/TitleSection' -import { ReactComponent as ErrorIcon } from 'src/styling/icons/status/tomato.svg' - -const ValidationSchema = Yup.object().shape({ - name: Yup.string().required('Required'), - cashin: Yup.object() - .required('Required') - .shape({ - notes: Yup.number() - .required('Required') - .integer() - .min(0) - }), - cashout1: Yup.object() - .required('Required') - .shape({ - notes: Yup.number() - .required('Required') - .integer() - .min(0), - denomination: Yup.number() - .required('Required') - .integer() - .default(0) - }), - cashout2: Yup.object() - .required('Required') - .shape({ - notes: Yup.number() - .required('Required') - .integer() - .min(0), - denomination: Yup.number() - .required('Required') - .integer() - .default(0) - }) -}) - -const GET_MACHINES_AND_CONFIG = gql` - { - machines { - name - deviceId - cashbox - cassette1 - cassette2 - } - config - } -` - -const EMPTY_CASHIN_BILLS = gql` - mutation MachineAction($deviceId: ID!, $action: MachineAction!) { - machineAction(deviceId: $deviceId, action: $action) { - deviceId - cashbox - cassette1 - cassette2 - } - } -` - -const RESET_CASHOUT_BILLS = gql` - mutation MachineAction( - $deviceId: ID! - $action: MachineAction! - $cassettes: [Int]! - ) { - machineAction(deviceId: $deviceId, action: $action, cassettes: $cassettes) { - deviceId - cashbox - cassette1 - cassette2 - } - } -` - -const Cashboxes = () => { - const [machines, setMachines] = useState([]) - - useQuery(GET_MACHINES_AND_CONFIG, { - onCompleted: ({ machines, config }) => - setMachines( - machines.map(m => ({ - ...m, - currency: { code: config.locale_fiatCurrency ?? '' }, - denominations: { - top: config[`denominations_${m.deviceId}_top`], - bottom: config[`denominations_${m.deviceId}_bottom`] - } - })) - ) - }) - - const [resetCashOut] = useMutation(RESET_CASHOUT_BILLS, { - onError: ({ graphQLErrors, message }) => { - const errorMessage = graphQLErrors[0] ? graphQLErrors[0].message : message - // TODO: this should not be final - alert(JSON.stringify(errorMessage)) - } - }) - - const [onEmpty] = useMutation(EMPTY_CASHIN_BILLS, { - onError: ({ graphQLErrors, message }) => { - const errorMessage = graphQLErrors[0] ? graphQLErrors[0].message : message - // TODO: this should not be final - alert(JSON.stringify(errorMessage)) - } - }) - - const onSave = (_, { cashin, cashout1, cashout2 }) => - resetCashOut({ - variables: { - deviceId: cashin.deviceId, - action: 'resetCashOutBills', - cassettes: [Number(cashout1.notes), Number(cashout2.notes)] - } - }) - - const elements = [ - { - name: 'name', - header: 'Machine', - width: 254, - textAlign: 'left', - view: name => <>{name}, - input: ({ field: { value: name } }) => <>{name} - }, - { - name: 'cashin', - header: 'Cash-in', - width: 265, - textAlign: 'left', - view: props => , - input: props => - }, - { - name: 'cashout1', - header: 'Cash-out 1', - width: 265, - textAlign: 'left', - view: props => , - input: CashOutFormik - }, - { - name: 'cashout2', - header: 'Cash-out 2', - width: 265, - textAlign: 'left', - view: props => , - input: CashOutFormik - } - ] - - const data = machines.map( - ({ - name, - cassette1, - cassette2, - currency, - denominations: { top, bottom }, - cashbox, - deviceId - }) => ({ - id: deviceId, - name, - cashin: { notes: cashbox, deviceId }, - cashout1: { notes: cassette1, denomination: top, currency }, - cashout2: { notes: cassette2, denomination: bottom, currency } - }) - ) - - return ( - <> - - - Action required - - } - /> - - - - ) -} - -export default Cashboxes diff --git a/new-lamassu-admin/src/routing/routes.js b/new-lamassu-admin/src/routing/routes.js index 7266898e..d65af0ce 100644 --- a/new-lamassu-admin/src/routing/routes.js +++ b/new-lamassu-admin/src/routing/routes.js @@ -3,12 +3,14 @@ import React from 'react' import { Route, Redirect, Switch } from 'react-router-dom' import AuthRegister from 'src/pages/AuthRegister' -import Cashout from 'src/pages/Cashout/Cashout' +import Cashout from 'src/pages/Cashout' import Commissions from 'src/pages/Commissions' import { Customers, CustomerProfile } from 'src/pages/Customers' import Funding from 'src/pages/Funding' import Locales from 'src/pages/Locales' import MachineLogs from 'src/pages/MachineLogs' +import Cashboxes from 'src/pages/Maintenance/Cashboxes' +import MachineStatus from 'src/pages/Maintenance/MachineStatus' import Notifications from 'src/pages/Notifications/Notifications' import OperatorInfo from 'src/pages/OperatorInfo/OperatorInfo' import ServerLogs from 'src/pages/ServerLogs' @@ -16,11 +18,8 @@ import Services from 'src/pages/Services/Services' import Transactions from 'src/pages/Transactions/Transactions' import Triggers from 'src/pages/Triggers' import WalletSettings from 'src/pages/Wallet/Wallet' -import MachineStatus from 'src/pages/maintenance/MachineStatus' import { namespaces } from 'src/utils/config' -import Cashboxes from '../pages/maintenance/Cashboxes' - const tree = [ { key: 'transactions', @@ -95,7 +94,7 @@ const tree = [ component: Cashout }, { - key: namespaces.SERVICES, + key: 'services', label: '3rd party services', route: '/settings/3rd-party-services', component: Services diff --git a/new-lamassu-admin/src/utils/config.js b/new-lamassu-admin/src/utils/config.js index f6c8efe8..12f4ba7c 100644 --- a/new-lamassu-admin/src/utils/config.js +++ b/new-lamassu-admin/src/utils/config.js @@ -1,14 +1,12 @@ import * as R from 'ramda' const namespaces = { - CASH_OUT: 'denominations', + CASH_OUT: 'cashOut', WALLETS: 'wallets', OPERATOR_INFO: 'operatorInfo', NOTIFICATIONS: 'notifications', - SERVICES: 'services', LOCALE: 'locale', COMMISSIONS: 'commissions', - CONTACT_INFO: 'operatorInfo', RECEIPT: 'receipt', COIN_ATM_RADAR: 'coinAtmRadar', TERMS_CONDITIONS: 'termsConditions' diff --git a/package-lock.json b/package-lock.json index 9457b220..121e11af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12752,7 +12752,6 @@ "resolved": "https://registry.npmjs.org/web3/-/web3-0.20.6.tgz", "integrity": "sha1-PpcwauAk+yThCj11yIQwJWIhUSA=", "requires": { - "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", "crypto-js": "^3.1.4", "utf8": "^2.1.1", "xhr2": "*", @@ -12761,7 +12760,7 @@ "dependencies": { "bignumber.js": { "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", - "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git" + "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" } } }, diff --git a/package.json b/package.json index 8c5e38e2..32f479d8 100644 --- a/package.json +++ b/package.json @@ -112,8 +112,7 @@ "build-admin": "npm run build-admin:css && npm run build-admin:main && npm run build-admin:lamassu" }, "devDependencies": { - "ava": "^0.19.1", - "eslint-plugin-import": "^2.19.1", + "ava": "3.8.2", "mocha": "^5.0.1", "rewire": "^4.0.1", "standard": "^12.0.1" @@ -121,7 +120,8 @@ "standard": { "ignore": [ "/lamassu-admin-elm", - "/public" + "/public", + "/new-lamassu-admin" ] } } From 8f7bb412a13037f4a42a1a0a76e46beff171b068 Mon Sep 17 00:00:00 2001 From: Taranto Date: Mon, 1 Jun 2020 20:54:19 +0100 Subject: [PATCH 29/29] fix: transaction screens fixes --- TODO.json | 46 ++++++ lib/new-admin/transactions.js | 5 +- lib/plugins.js | 1 + .../src/components/fake-table/Table.js | 4 +- .../src/components/fake-table/Table.styles.js | 2 +- .../src/components/tables/DataTable.js | 69 +++++---- .../src/components/tables/DataTable.styles.js | 15 ++ .../src/pages/Transactions/DetailsCard.js | 11 +- .../src/pages/Transactions/Transactions.js | 2 +- new-lamassu-admin/todo.md | 41 ++++++ test/fixtures/new-settings.json | 139 ++++++++++++++++++ test/unit/new-settings-loader.js | 17 +++ 12 files changed, 315 insertions(+), 37 deletions(-) create mode 100644 TODO.json create mode 100644 new-lamassu-admin/todo.md create mode 100644 test/fixtures/new-settings.json create mode 100644 test/unit/new-settings-loader.js diff --git a/TODO.json b/TODO.json new file mode 100644 index 00000000..3449044c --- /dev/null +++ b/TODO.json @@ -0,0 +1,46 @@ +{ + "groups": [ + { + "code": "balanceAlerts", + "fields": [ + "cashInAlertThreshold" + ] + }, + { + "code": "compliance", + "fields": [ + "smsVerificationActive", + "smsVerificationThreshold", + "idCardDataVerificationActive", + "idCardDataVerificationThreshold", + "idCardPhotoVerificationActive", + "idCardPhotoVerificationThreshold", + "sanctionsVerificationActive", + "sanctionsVerificationThreshold", + "frontCameraVerificationActive", + "frontCameraVerificationThreshold", + "hardLimitVerificationActive", + "hardLimitVerificationThreshold", + "receiptPrintingActive", + "rejectAddressReuseActive" + ] + }, + { + "code": "walletSettings", + "fields": [ + "ticker", + "wallet", + "layer2", + "exchange", + "zeroConf" + ] + }, + { + "code": "notifications", + "fields": [ + "sms", + "email" + ] + } + ] +} \ No newline at end of file diff --git a/lib/new-admin/transactions.js b/lib/new-admin/transactions.js index 57131786..249722aa 100644 --- a/lib/new-admin/transactions.js +++ b/lib/new-admin/transactions.js @@ -62,7 +62,10 @@ function batch () { } function getCustomerTransactions (customerId) { - const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']), + const packager = _.flow(it => { + console.log() + return it + }, _.flatten, _.orderBy(_.property('created'), ['desc']), _.take(NUM_RESULTS), _.map(camelize), addNames) const cashInSql = `select 'cashIn' as tx_class, txs.*, diff --git a/lib/plugins.js b/lib/plugins.js index f128f2a2..aa06b197 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -185,6 +185,7 @@ function plugins (settings, deviceId) { const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) const minimumTx = BN(commissions.minimumTx) const cashInFee = BN(commissions.fixedFee) + logger.info('FEE', cashInFee) const cashInCommission = BN(commissions.cashIn) const cashOutCommission = _.isNumber(commissions.cashOut) ? BN(commissions.cashOut) : null const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) diff --git a/new-lamassu-admin/src/components/fake-table/Table.js b/new-lamassu-admin/src/components/fake-table/Table.js index e7e2a368..2ceaee30 100644 --- a/new-lamassu-admin/src/components/fake-table/Table.js +++ b/new-lamassu-admin/src/components/fake-table/Table.js @@ -76,7 +76,7 @@ const ThDoubleLevel = ({ title, children, className }) => { ) } -const Tr = ({ error, errorMessage, children, className, size }) => { +const Tr = ({ onClick, error, errorMessage, children, className, size }) => { const classes = useStyles({ size }) const cardClasses = { root: classes.cardContentRoot } const classNames = { @@ -88,7 +88,7 @@ const Tr = ({ error, errorMessage, children, className, size }) => { return ( <> - +
{children}
{error &&
{errorMessage}
} diff --git a/new-lamassu-admin/src/components/fake-table/Table.styles.js b/new-lamassu-admin/src/components/fake-table/Table.styles.js index 05498e4a..bc459a5c 100644 --- a/new-lamassu-admin/src/components/fake-table/Table.styles.js +++ b/new-lamassu-admin/src/components/fake-table/Table.styles.js @@ -91,7 +91,7 @@ export default { '&:before': { height: 0 }, - margin: [[4, 0]], + margin: [[4, 0, 0, 0]], width: '100%', boxShadow: [[0, 0, 4, 0, 'rgba(0, 0, 0, 0.08)']] }, diff --git a/new-lamassu-admin/src/components/tables/DataTable.js b/new-lamassu-admin/src/components/tables/DataTable.js index dcb8f91b..0da307e0 100644 --- a/new-lamassu-admin/src/components/tables/DataTable.js +++ b/new-lamassu-admin/src/components/tables/DataTable.js @@ -38,36 +38,49 @@ const Row = ({ }) => { const classes = useStyles() + const hasPointer = onClick || expandable + const trClasses = { + [classes.pointer]: hasPointer, + [classes.row]: true, + [classes.expanded]: expanded + } + return ( -
onClick && onClick(data)}> - - {elements.map(({ view = it => it?.toString(), ...props }, idx) => ( - - {view(data)} - - ))} - {expandable && ( - - - - )} - - {expandable && expanded && ( - - -
- +
+
+ { + expandable && expandRow(id) + onClick && onClick(data) + }} + error={data.error} + errorMessage={data.errorMessage}> + {elements.map(({ view = it => it?.toString(), ...props }, idx) => ( + + {view(data)} + + ))} + {expandable && ( + + + + )} +
+ {expandable && expanded && ( +
+ + +
+ + +
)}
) diff --git a/new-lamassu-admin/src/components/tables/DataTable.styles.js b/new-lamassu-admin/src/components/tables/DataTable.styles.js index dc0e3e58..6c5dbc58 100644 --- a/new-lamassu-admin/src/components/tables/DataTable.styles.js +++ b/new-lamassu-admin/src/components/tables/DataTable.styles.js @@ -1,3 +1,5 @@ +import { zircon } from 'src/styling/variables' + export default { expandButton: { border: 'none', @@ -8,6 +10,19 @@ export default { row: { borderRadius: 0 }, + expanded: { + border: [[2, 'solid', zircon]], + boxShadow: '0 0 8px 0 rgba(0,0,0,0.08)' + }, + before: { + paddingTop: 12 + }, + after: { + paddingBottom: 12 + }, + pointer: { + cursor: 'pointer' + }, body: { flex: [[1, 1, 'auto']] }, diff --git a/new-lamassu-admin/src/pages/Transactions/DetailsCard.js b/new-lamassu-admin/src/pages/Transactions/DetailsCard.js index 0ec9694d..c19b9b20 100644 --- a/new-lamassu-admin/src/pages/Transactions/DetailsCard.js +++ b/new-lamassu-admin/src/pages/Transactions/DetailsCard.js @@ -39,10 +39,7 @@ const DetailsRow = ({ it: tx, ...props }) => { 5 ) const commissionPercentage = Number.parseFloat(tx.commissionPercentage, 2) - const commission = - tx.txClass === 'cashOut' - ? fiat * commissionPercentage - : fiat * commissionPercentage + Number.parseFloat(tx.fee) + const commission = fiat * commissionPercentage const customer = tx.customerIdCardData && { name: `${onlyFirstToUpper( tx.customerIdCardData.firstName @@ -163,6 +160,12 @@ const DetailsRow = ({ it: tx, ...props }) => { 100} %)`}
+ {tx.txClass === 'cashIn' && ( +
+ +
{Number.parseFloat(tx.cashInFee)}
+
+ )}
diff --git a/new-lamassu-admin/src/pages/Transactions/Transactions.js b/new-lamassu-admin/src/pages/Transactions/Transactions.js index 50e84533..bed3fa4b 100644 --- a/new-lamassu-admin/src/pages/Transactions/Transactions.js +++ b/new-lamassu-admin/src/pages/Transactions/Transactions.js @@ -33,7 +33,7 @@ const GET_TRANSACTIONS = gql` machineName deviceId fiat - fee + cashInFee fiatCode cryptoAtoms cryptoCode diff --git a/new-lamassu-admin/todo.md b/new-lamassu-admin/todo.md new file mode 100644 index 00000000..24027d7c --- /dev/null +++ b/new-lamassu-admin/todo.md @@ -0,0 +1,41 @@ +UI: +- Large input fields wiggle on editable table edit/non-edit mode change. +- all (machines/coins/...) should be a option on some overrides + +Compliance: +- Reject Address Reuse missing + +CoinATMRadar: +- We now have photo, should we relay that info? + +Locale: +- should we show the user wallet needs to be configured after adding in a new coin? +- check if coin is active before considering it active on other screens + +Commission: +- overrides can be tighter. Hide coins already used by the same machine on another line. + +Sms/email: +- There's no place to pick a third party provider anymore. + +Cashout: +- I've just added a zero conf limit column. Should it be like this? + +Notifications: +- cashInAlertThreshold missing, used to warn to full cashbox + +Machine name: +- Previously we were grabbing that from the config, but since new admin still cant change the name i`m now grabbing it from the db. Possible issues if users change the machine name from the initial one. Investivate alternatives. + +Migrate: +- Need to write config migration. +- Rewrite config validate +- remove apply defaults + +Compliance: +- Currently admin only handles { type: 'volume', direction: 'both' } +- Sanctions should have more care in customers.js, currently just looking if is active as if old config + +Other stuff: +- sms.js +- email.js \ No newline at end of file diff --git a/test/fixtures/new-settings.json b/test/fixtures/new-settings.json new file mode 100644 index 00000000..58f51f7d --- /dev/null +++ b/test/fixtures/new-settings.json @@ -0,0 +1,139 @@ +{ + "config": { + "triggers": [ + { + "id": "ce8f240f-7235-4948-b833-4af56ccfa90f", + "cashDirection": "cash-out", + "triggerType": "volume", + "threshold": "12", + "requirement": "block" + } + ], + "denominations_9682f15e40539e40d3e4050a993cf74e3e157d6d9b7866fb1ebd5206024ae68a_bottom": 12, + "denominations_9682f15e40539e40d3e4050a993cf74e3e157d6d9b7866fb1ebd5206024ae68a_top": 12, + "denominations_9682f15e40539e40d3e4050a993cf74e3e157d6d9b7866fb1ebd5206024ae68a_active": true, + "locale_country": "US", + "locale_fiatCurrency": "USD", + "locale_languages": [ + "en-US" + ], + "locale_cryptoCurrencies": [ + "BTC" + ], + "commissions_minimumTx": 12, + "commissions_fixedFee": 12, + "commissions_cashOut": 12, + "commissions_cashIn": 12, + "operatorInfo_companyNumber": "123", + "operatorInfo_website": "123", + "operatorInfo_email": "123@gmail.com", + "operatorInfo_phone": "123", + "operatorInfo_name": "123", + "operatorInfo_active": true, + "receiptPrinting": { + "active": "on", + "operatorWebsite": true, + "operatorEmail": false, + "operatorPhone": false, + "companyRegistration": false, + "machineLocation": true, + "customerNameOrPhoneNumber": true, + "exchangeRate": false, + "addressQRCode": true + }, + "coinAtmRadar_active": true, + "coinAtmRadar_commissions": true, + "coinAtmRadar_supportedCryptocurrencies": true, + "coinAtmRadar_supportedFiat": true, + "coinAtmRadar_supportedBuySellDirection": true, + "coinAtmRadar_limitsAndVerification": true, + "termsConditions_active": true, + "termsConditions_title": "Rasdkl", + "termsConditions_text": "123oh12", + "termsConditions_acceptButtonText": "op1i2jh3po", + "termsConditions_cancelButtonText": "12341", + "wallets_BTC_active": true, + "wallets_BTC_ticker": "mock-ticker", + "wallets_BTC_wallet": "mock-wallet", + "wallets_BTC_exchange": "mock-exchange", + "wallets_BTC_zeroConf": "mock-zero-conf", + "commissions_overrides": [ + { + "minimumTx": 33, + "fixedFee": 33, + "cashOut": 33, + "cashIn": 33, + "cryptoCurrencies": [ + "BTC" + ], + "machine": "9682f15e40539e40d3e4050a993cf74e3e157d6d9b7866fb1ebd5206024ae68a", + "id": "423a32a9-0bbb-4997-b15a-c6db12334195", + "country": "", + "languages": [] + } + ], + "cashOut_9682f15e40539e40d3e4050a993cf74e3e157d6d9b7866fb1ebd5206024ae68a_bottom": 33, + "cashOut_9682f15e40539e40d3e4050a993cf74e3e157d6d9b7866fb1ebd5206024ae68a_top": 12, + "cashOut_9682f15e40539e40d3e4050a993cf74e3e157d6d9b7866fb1ebd5206024ae68a_active": true, + "notifications_email_active": true, + "notifications_sms_active": true, + "notifications_sms_transactions": true, + "notifications_email_compliance": true, + "notifications_highValueTransaction": 12, + "notifications_fiatBalanceCassette2": 42, + "notifications_fiatBalanceCassette1": 32, + "notifications_fiatBalanceOverrides": [ + { + "cassette2": 67, + "cassette1": 556, + "machine": "9682f15e40539e40d3e4050a993cf74e3e157d6d9b7866fb1ebd5206024ae68a", + "id": "18114e5b-e6e6-4e0e-a7bc-2cc4ad605d12" + } + ], + "notifications_cryptoLowBalance": 23, + "notifications_cryptoHighBalance": 56, + "notifications_cryptoBalanceOverrides": [ + { + "highBalance": 66, + "lowBalance": 545, + "cryptoCurrency": "ETH", + "id": "b59e4310-bca1-435c-a89e-b861a7de9b93" + }, + { + "highBalance": 23, + "lowBalance": 51, + "cryptoCurrency": "DASH", + "id": "a1f5ac7a-4b05-4ec7-8744-4d77c0555f5b" + } + ], + "locale_overrides": [ + { + "id": "f0f9248d-96da-4db5-b592-625251da0203", + "machine": "9682f15e40539e40d3e4050a993cf74e3e157d6d9b7866fb1ebd5206024ae68a", + "country": "US", + "languages": [ + "en-CA" + ], + "cryptoCurrencies": [ + "ETH" + ] + } + ], + "receipt_active": "on", + "receipt_operatorWebsite": false, + "receipt_operatorEmail": false, + "receipt_operatorPhone": false, + "receipt_companyRegistration": true, + "receipt_machineLocation": false, + "receipt_customerNameOrPhoneNumber": true, + "receipt_exchangeRate": false, + "receipt_addressQRCode": false, + "notifications_sms_balance": true, + "notifications_sms_compliance": true, + "notifications_sms_errors": true, + "notifications_email_errors": true, + "notifications_email_transactions": true, + "notifications_email_balance": true + }, + "accounts": {} +} \ No newline at end of file diff --git a/test/unit/new-settings-loader.js b/test/unit/new-settings-loader.js new file mode 100644 index 00000000..8948f06d --- /dev/null +++ b/test/unit/new-settings-loader.js @@ -0,0 +1,17 @@ +import test from 'ava' +import _ from 'lodash/fp' +import settings from '../../db.json' + +import configManager from '../../lib/new-config-manager' + +const machineId = '9682f15e40539e40d3e4050a993cf74e3e157d6d9b7866fb1ebd5206024ae68a' +const config = settings.config + +test('first examples', () => { + const triggers = configManager.getTriggers(config) + const filtered = _.filter(_.matches({ triggerType: 'volume', cashDirection: 'both' }))(triggers) + const grouped = _.groupBy(_.prop('requirement'))(filtered) + const final = _.mapValues(_.compose(_.get('threshold'), _.minBy('threshold')))(grouped) + + console.log(final) +}) \ No newline at end of file