diff --git a/lib/blacklist.js b/lib/blacklist.js
index 4c3d8595..8324fc20 100644
--- a/lib/blacklist.js
+++ b/lib/blacklist.js
@@ -1,48 +1,49 @@
+const _ = require('lodash/fp')
+
const db = require('./db')
const notifierQueries = require('./notifier/queries')
-// Get all blacklist rows from the DB "blacklist" table that were manually inserted by the operator
-const getBlacklist = () => {
- return db.any(`SELECT * FROM blacklist`).then(res =>
- res.map(item => ({
- cryptoCode: item.crypto_code,
- address: item.address
- }))
+const getBlacklist = () =>
+ db.any(
+ `SELECT blacklist.address AS address, blacklist_messages.content AS blacklistMessage
+ FROM blacklist JOIN blacklist_messages
+ ON blacklist.blacklist_message_id = blacklist_messages.id`
)
+
+const deleteFromBlacklist = address => {
+ const sql = `DELETE FROM blacklist WHERE address = $1`
+ notifierQueries.clearBlacklistNotification(address)
+ return db.none(sql, [address])
}
-// Delete row from blacklist table by crypto code and address
-const deleteFromBlacklist = (cryptoCode, address) => {
- const sql = `DELETE FROM blacklist WHERE crypto_code = $1 AND address = $2`
- notifierQueries.clearBlacklistNotification(cryptoCode, address)
- return db.none(sql, [cryptoCode, address])
-}
-
-const insertIntoBlacklist = (cryptoCode, address) => {
+const insertIntoBlacklist = address => {
return db
.none(
- 'INSERT INTO blacklist (crypto_code, address) VALUES ($1, $2);',
- [cryptoCode, address]
+ 'INSERT INTO blacklist (address) VALUES ($1);',
+ [address]
)
}
-function blocked (address, cryptoCode) {
- const sql = `SELECT * FROM blacklist WHERE address = $1 AND crypto_code = $2`
- return db.any(sql, [address, cryptoCode])
+function blocked (address) {
+ const sql = `SELECT address, content FROM blacklist b LEFT OUTER JOIN blacklist_messages bm ON bm.id = b.blacklist_message_id WHERE address = $1`
+ return db.oneOrNone(sql, [address])
}
-function addToUsedAddresses (address, cryptoCode) {
- // ETH reuses addresses
- if (cryptoCode === 'ETH') return Promise.resolve()
+function getMessages () {
+ const sql = `SELECT * FROM blacklist_messages`
+ return db.any(sql)
+}
- const sql = `INSERT INTO blacklist (crypto_code, address) VALUES ($1, $2)`
- return db.oneOrNone(sql, [cryptoCode, address])
+function editBlacklistMessage (id, content) {
+ const sql = `UPDATE blacklist_messages SET content = $1 WHERE id = $2 RETURNING id`
+ return db.oneOrNone(sql, [content, id])
}
module.exports = {
blocked,
- addToUsedAddresses,
getBlacklist,
deleteFromBlacklist,
- insertIntoBlacklist
+ insertIntoBlacklist,
+ getMessages,
+ editBlacklistMessage
}
diff --git a/lib/blockchain/bitcoin.js b/lib/blockchain/bitcoin.js
index 12c96815..9d91ecb9 100644
--- a/lib/blockchain/bitcoin.js
+++ b/lib/blockchain/bitcoin.js
@@ -27,6 +27,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
common.logger.info('Updating Bitcoin Core. This may take a minute...')
!isDevMode() && common.es(`sudo supervisorctl stop bitcoin`)
common.es(`curl -#o /tmp/bitcoin.tar.gz ${coinRec.url}`)
+ if (common.es(`sha256sum /tmp/bitcoin.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
+ common.logger.info('Failed to update Bitcoin Core: Package signature do not match!')
+ return
+ }
common.es(`tar -xzf /tmp/bitcoin.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
@@ -55,6 +59,20 @@ function updateCore (coinRec, isCurrentlyRunning) {
common.es(`echo "\nlistenonion=0" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`)
}
+ if (common.es(`grep "fallbackfee=" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`)) {
+ common.logger.info(`fallbackfee already defined, skipping...`)
+ } else {
+ common.logger.info(`Setting 'fallbackfee=0.00005' in config file...`)
+ common.es(`echo "\nfallbackfee=0.00005" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`)
+ }
+
+ if (common.es(`grep "rpcworkqueue=" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`)) {
+ common.logger.info(`rpcworkqueue already defined, skipping...`)
+ } else {
+ common.logger.info(`Setting 'rpcworkqueue=2000' in config file...`)
+ common.es(`echo "\nrpcworkqueue=2000" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`)
+ }
+
if (isCurrentlyRunning && !isDevMode()) {
common.logger.info('Starting wallet...')
common.es(`sudo supervisorctl start bitcoin`)
diff --git a/lib/blockchain/bitcoincash.js b/lib/blockchain/bitcoincash.js
index d8ac0efe..53e4ab8e 100644
--- a/lib/blockchain/bitcoincash.js
+++ b/lib/blockchain/bitcoincash.js
@@ -20,6 +20,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
common.logger.info('Updating Bitcoin Cash. This may take a minute...')
common.es(`sudo supervisorctl stop bitcoincash`)
common.es(`curl -#Lo /tmp/bitcoincash.tar.gz ${coinRec.url}`)
+ if (common.es(`sha256sum /tmp/bitcoincash.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
+ common.logger.info('Failed to update Bitcoin Cash: Package signature do not match!')
+ return
+ }
common.es(`tar -xzf /tmp/bitcoincash.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js
index 8db73a72..3e844f6c 100644
--- a/lib/blockchain/common.js
+++ b/lib/blockchain/common.js
@@ -29,37 +29,47 @@ module.exports = {
const BINARIES = {
BTC: {
defaultUrl: 'https://bitcoincore.org/bin/bitcoin-core-0.20.1/bitcoin-0.20.1-x86_64-linux-gnu.tar.gz',
+ defaultUrlHash: '376194f06596ecfa40331167c39bc70c355f960280bd2a645fdbf18f66527397',
defaultDir: 'bitcoin-0.20.1/bin',
url: 'https://bitcoincore.org/bin/bitcoin-core-27.1/bitcoin-27.1-x86_64-linux-gnu.tar.gz',
+ UrlHash: 'c9840607d230d65f6938b81deaec0b98fe9cb14c3a41a5b13b2c05d044a48422',
dir: 'bitcoin-27.1/bin'
},
ETH: {
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.14.8-a9523b64.tar.gz',
+ urlHash: 'fff507c90c180443456950e4fc0bf224d26ce5ea6896194ff864c3c3754c136b',
dir: 'geth-linux-amd64-1.14.8-a9523b64'
},
ZEC: {
url: 'https://github.com/zcash/artifacts/raw/master/v5.9.0/bullseye/zcash-5.9.0-linux64-debian-bullseye.tar.gz',
+ urlHash: 'd385b9fbeeb145f60b0b339d256cabb342713ed3014cd634cf2d68078365abd2',
dir: 'zcash-5.9.0/bin'
},
DASH: {
defaultUrl: 'https://github.com/dashpay/dash/releases/download/v18.1.0/dashcore-18.1.0-x86_64-linux-gnu.tar.gz',
+ defaultUrlHash: 'd89c2afd78183f3ee815adcccdff02098be0c982633889e7b1e9c9656fbef219',
defaultDir: 'dashcore-18.1.0/bin',
url: 'https://github.com/dashpay/dash/releases/download/v21.1.0/dashcore-21.1.0-x86_64-linux-gnu.tar.gz',
+ urlHash: 'a7d0c1b04d53a9b1b3499eb82182c0fa57f4c8768c16163e5d05971bf45d7928',
dir: 'dashcore-21.1.0/bin'
},
LTC: {
defaultUrl: 'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
+ defaultUrlHash: 'ca50936299e2c5a66b954c266dcaaeef9e91b2f5307069b9894048acf3eb5751',
defaultDir: 'litecoin-0.18.1/bin',
url: 'https://download.litecoin.org/litecoin-0.21.3/linux/litecoin-0.21.3-x86_64-linux-gnu.tar.gz',
+ urlHash: 'ea231c630e2a243cb01affd4c2b95a2be71560f80b64b9f4bceaa13d736aa7cb',
dir: 'litecoin-0.21.3/bin'
},
BCH: {
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v27.1.0/bitcoin-cash-node-27.1.0-x86_64-linux-gnu.tar.gz',
+ urlHash: '0dcc387cbaa3a039c97ddc8fb99c1fa7bff5dc6e4bd3a01d3c3095f595ad2dce',
dir: 'bitcoin-cash-node-27.1.0/bin',
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
},
XMR: {
url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.3.3.tar.bz2',
+ urlHash: '47c7e6b4b88a57205800a2538065a7874174cd087eedc2526bee1ebcce0cc5e3',
dir: 'monero-x86_64-linux-gnu-v0.18.3.3',
files: [['monerod', 'monerod'], ['monero-wallet-rpc', 'monero-wallet-rpc']]
}
@@ -133,10 +143,15 @@ function fetchAndInstall (coinRec) {
if (!binaries) throw new Error(`No such coin: ${coinRec.code}`)
const url = requiresUpdate ? binaries.defaultUrl : binaries.url
+ const hash = requiresUpdate ? binaries.defaultUrlHash : binaries.urlHash
const downloadFile = path.basename(url)
const binDir = requiresUpdate ? binaries.defaultDir : binaries.dir
es(`wget -q ${url}`)
+ if (es(`sha256sum ${downloadFile} | awk '{print $1}'`).trim() !== hash) {
+ logger.info(`Failed to install ${coinRec.code}: Package signature do not match!`)
+ return
+ }
es(`tar -xf ${downloadFile}`)
const usrBinDir = isDevMode() ? path.resolve(BLOCKCHAIN_DIR, 'bin') : '/usr/local/bin'
diff --git a/lib/blockchain/dash.js b/lib/blockchain/dash.js
index 05ace3a7..51ed159f 100644
--- a/lib/blockchain/dash.js
+++ b/lib/blockchain/dash.js
@@ -20,6 +20,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
common.logger.info('Updating Dash Core. This may take a minute...')
common.es(`sudo supervisorctl stop dash`)
common.es(`curl -#Lo /tmp/dash.tar.gz ${coinRec.url}`)
+ if (common.es(`sha256sum /tmp/dash.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
+ common.logger.info('Failed to update Dash Core: Package signature do not match!')
+ return
+ }
common.es(`tar -xzf /tmp/dash.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
diff --git a/lib/blockchain/ethereum.js b/lib/blockchain/ethereum.js
index dd39468a..9434ebdc 100644
--- a/lib/blockchain/ethereum.js
+++ b/lib/blockchain/ethereum.js
@@ -8,6 +8,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
common.logger.info('Updating the Geth Ethereum wallet. This may take a minute...')
common.es(`sudo supervisorctl stop ethereum`)
common.es(`curl -#o /tmp/ethereum.tar.gz ${coinRec.url}`)
+ if (common.es(`sha256sum /tmp/ethereum.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
+ common.logger.info('Failed to update Geth: Package signature do not match!')
+ return
+ }
common.es(`tar -xzf /tmp/ethereum.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
diff --git a/lib/blockchain/litecoin.js b/lib/blockchain/litecoin.js
index cd02a77f..ce128dd0 100644
--- a/lib/blockchain/litecoin.js
+++ b/lib/blockchain/litecoin.js
@@ -20,6 +20,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
common.logger.info('Updating Litecoin Core. This may take a minute...')
common.es(`sudo supervisorctl stop litecoin`)
common.es(`curl -#o /tmp/litecoin.tar.gz ${coinRec.url}`)
+ if (common.es(`sha256sum /tmp/litecoin.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
+ common.logger.info('Failed to update Litecoin Core: Package signature do not match!')
+ return
+ }
common.es(`tar -xzf /tmp/litecoin.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
diff --git a/lib/blockchain/monero.js b/lib/blockchain/monero.js
index 447b2722..870f3920 100644
--- a/lib/blockchain/monero.js
+++ b/lib/blockchain/monero.js
@@ -22,6 +22,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
common.logger.info('Updating Monero. This may take a minute...')
common.es(`sudo supervisorctl stop monero monero-wallet`)
common.es(`curl -#o /tmp/monero.tar.gz ${coinRec.url}`)
+ if (common.es(`sha256sum /tmp/monero.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
+ common.logger.info('Failed to update Monero: Package signature do not match!')
+ return
+ }
common.es(`tar -xf /tmp/monero.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
diff --git a/lib/blockchain/zcash.js b/lib/blockchain/zcash.js
index 51430969..a6baed51 100644
--- a/lib/blockchain/zcash.js
+++ b/lib/blockchain/zcash.js
@@ -13,6 +13,10 @@ function updateCore (coinRec, isCurrentlyRunning) {
common.logger.info('Updating your Zcash wallet. This may take a minute...')
common.es(`sudo supervisorctl stop zcash`)
common.es(`curl -#Lo /tmp/zcash.tar.gz ${coinRec.url}`)
+ if (common.es(`sha256sum /tmp/zcash.tar.gz | awk '{print $1}'`).trim() !== coinRec.urlHash) {
+ common.logger.info('Failed to update Zcash: Package signature do not match!')
+ return
+ }
common.es(`tar -xzf /tmp/zcash.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
diff --git a/lib/cash-in/cash-in-low.js b/lib/cash-in/cash-in-low.js
index 5b8066a0..0f2942df 100644
--- a/lib/cash-in/cash-in-low.js
+++ b/lib/cash-in/cash-in-low.js
@@ -8,7 +8,7 @@ const E = require('../error')
const PENDING_INTERVAL_MS = 60 * T.minutes
-const massageFields = ['direction', 'cryptoNetwork', 'bills', 'blacklisted', 'addressReuse', 'promoCodeApplied', 'validWalletScore', 'cashInFeeCrypto']
+const massageFields = ['direction', 'cryptoNetwork', 'bills', 'blacklisted', 'blacklistMessage', 'addressReuse', 'promoCodeApplied', 'validWalletScore', 'cashInFeeCrypto']
const massageUpdateFields = _.concat(massageFields, 'cryptoAtoms')
const massage = _.flow(_.omit(massageFields),
diff --git a/lib/cash-in/cash-in-tx.js b/lib/cash-in/cash-in-tx.js
index cc676554..a4e8c228 100644
--- a/lib/cash-in/cash-in-tx.js
+++ b/lib/cash-in/cash-in-tx.js
@@ -32,38 +32,41 @@ function post (machineTx, pi) {
return cashInAtomic.atomic(machineTx, pi)
.then(r => {
const updatedTx = r.tx
- let blacklisted = false
let addressReuse = false
- let walletScore = {}
const promises = [settingsLoader.loadLatestConfig()]
const isFirstPost = !r.tx.fiat || r.tx.fiat.isZero()
if (isFirstPost) {
- promises.push(checkForBlacklisted(updatedTx), doesTxReuseAddress(updatedTx), getWalletScore(updatedTx, pi))
+ promises.push(
+ checkForBlacklisted(updatedTx),
+ doesTxReuseAddress(updatedTx),
+ getWalletScore(updatedTx, pi)
+ )
}
return Promise.all(promises)
- .then(([config, blacklistItems = false, isReusedAddress = false, fetchedWalletScore = null]) => {
- const rejectAddressReuse = configManager.getCompliance(config).rejectAddressReuse
+ .then(([config, blacklisted = false, isReusedAddress = false, walletScore = null]) => {
+ const { rejectAddressReuse } = configManager.getCompliance(config)
+ const isBlacklisted = !!blacklisted
- walletScore = fetchedWalletScore
-
- if (_.some(it => it.address === updatedTx.toAddress)(blacklistItems)) {
- blacklisted = true
+ if (isBlacklisted) {
notifier.notifyIfActive('compliance', 'blacklistNotify', r.tx, false)
} else if (isReusedAddress && rejectAddressReuse) {
notifier.notifyIfActive('compliance', 'blacklistNotify', r.tx, true)
addressReuse = true
}
- return postProcess(r, pi, blacklisted, addressReuse, walletScore)
+ return postProcess(r, pi, isBlacklisted, addressReuse, walletScore)
+ .then(changes => _.set('walletScore', _.isNil(walletScore) ? null : walletScore.score, changes))
+ .then(changes => cashInLow.update(db, updatedTx, changes))
+ .then(_.flow(
+ _.set('bills', machineTx.bills),
+ _.set('blacklisted', isBlacklisted),
+ _.set('blacklistMessage', blacklisted?.content),
+ _.set('addressReuse', addressReuse),
+ _.set('validWalletScore', _.isNil(walletScore) || walletScore.isValid),
+ ))
})
- .then(changes => _.set('walletScore', _.isNil(walletScore) ? null : walletScore.score, changes))
- .then(changes => cashInLow.update(db, updatedTx, changes))
- .then(tx => _.set('bills', machineTx.bills, tx))
- .then(tx => _.set('blacklisted', blacklisted, tx))
- .then(tx => _.set('addressReuse', addressReuse, tx))
- .then(tx => _.set('validWalletScore', _.isNil(walletScore) ? true : walletScore.isValid, tx))
})
}
@@ -94,7 +97,7 @@ function logActionById (action, _rec, txId) {
}
function checkForBlacklisted (tx) {
- return blacklist.blocked(tx.toAddress, tx.cryptoCode)
+ return blacklist.blocked(tx.toAddress)
}
function postProcess (r, pi, isBlacklisted, addressReuse, walletScore) {
diff --git a/lib/cashbox-batches.js b/lib/cashbox-batches.js
index 52f41dc6..35a03d9e 100644
--- a/lib/cashbox-batches.js
+++ b/lib/cashbox-batches.js
@@ -6,7 +6,7 @@ const camelize = require('./utils')
function createCashboxBatch (deviceId, cashboxCount) {
if (_.isEqual(0, cashboxCount)) throw new Error('Cash box is empty. Cash box batch could not be created.')
- const sql = `INSERT INTO cash_unit_operation (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-box-empty')`
+ const sql = `INSERT INTO cash_unit_operation (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-box-empty') RETURNING *`
const sql2 = `
UPDATE bills SET cashbox_batch_id=$1
FROM cash_in_txs
@@ -25,6 +25,7 @@ function createCashboxBatch (deviceId, cashboxCount) {
const q2 = t.none(sql2, [batchId, deviceId])
const q3 = t.none(sql3, [batchId, deviceId])
return t.batch([q1, q2, q3])
+ .then(([it]) => it)
})
}
@@ -100,14 +101,6 @@ function editBatchById (id, performedBy) {
return db.none(sql, [performedBy, id])
}
-function getBillsByBatchId (id) {
- const sql = `SELECT bi.* FROM (
- SELECT b.id, b.fiat, b.fiat_code, b.created, b.cashbox_batch_id, cit.device_id AS device_id FROM bills b LEFT OUTER JOIN (SELECT id, device_id FROM cash_in_txs) AS cit ON cit.id = b.cash_in_txs_id UNION
- SELECT id, fiat, fiat_code, created, cashbox_batch_id, device_id FROM empty_unit_bills
- ) AS bi WHERE bi.cashbox_batch_id=$1`
- return db.any(sql, [id])
-}
-
function logFormatter (data) {
return _.map(
it => {
@@ -124,11 +117,62 @@ function logFormatter (data) {
)
}
+function getMachineUnbatchedBills (deviceId) {
+ const sql = `
+ SELECT now() AS created, cash_in_txs.device_id, json_agg(b.*) AS bills FROM bills b LEFT OUTER JOIN cash_in_txs
+ ON b.cash_in_txs_id = cash_in_txs.id
+ WHERE b.cashbox_batch_id IS NULL AND cash_in_txs.device_id = $1
+ GROUP BY cash_in_txs.device_id
+ `
+
+ return db.oneOrNone(sql, [deviceId])
+ .then(res => _.mapKeys(it => _.camelCase(it), res))
+ .then(logFormatterSingle)
+}
+
+function getBatchById (id) {
+ const sql = `
+ SELECT cb.id, cb.device_id, cb.created, cb.operation_type, cb.bill_count_override, cb.performed_by, json_agg(b.*) AS bills
+ FROM cashbox_batches AS cb
+ LEFT JOIN bills AS b ON cb.id = b.cashbox_batch_id
+ WHERE cb.id = $1
+ GROUP BY cb.id
+ `
+
+ return db.oneOrNone(sql, [id]).then(res => _.mapKeys(it => _.camelCase(it), res))
+ .then(logFormatterSingle)
+}
+
+function logFormatterSingle (data) {
+ const bills = _.filter(
+ it => !(_.isNil(it) || _.isNil(it.fiat_code) || _.isNil(it.fiat) || _.isNaN(it.fiat)),
+ data.bills
+ )
+
+ return {
+ id: data.id,
+ deviceId: data.deviceId,
+ created: data.created,
+ operationType: data.operationType,
+ billCount: _.size(bills),
+ fiatTotals: _.reduce(
+ (acc, value) => {
+ acc[value.fiat_code] = (acc[value.fiat_code] || 0) + value.fiat
+ return acc
+ },
+ {},
+ bills
+ ),
+ billsByDenomination: _.countBy(it => `${it.fiat} ${it.fiat_code}`, bills)
+ }
+}
+
module.exports = {
createCashboxBatch,
updateMachineWithBatch,
getBatches,
- getBillsByBatchId,
editBatchById,
+ getBatchById,
+ getMachineUnbatchedBills,
logFormatter
}
diff --git a/lib/exchange.js b/lib/exchange.js
index 0431a7d5..f9811bb8 100644
--- a/lib/exchange.js
+++ b/lib/exchange.js
@@ -1,6 +1,10 @@
+const _ = require('lodash/fp')
+const { ALL_CRYPTOS } = require('@lamassu/coins')
+
const configManager = require('./new-config-manager')
const ccxt = require('./plugins/exchange/ccxt')
const mockExchange = require('./plugins/exchange/mock-exchange')
+const accounts = require('./new-admin/config/accounts')
function lookupExchange (settings, cryptoCode) {
const exchange = configManager.getWalletSettings(cryptoCode, settings.config).exchange
@@ -45,8 +49,26 @@ function active (settings, cryptoCode) {
return !!lookupExchange(settings, cryptoCode)
}
+function getMarkets () {
+ const filterExchanges = _.filter(it => it.class === 'exchange')
+ const availableExchanges = _.map(it => it.code, filterExchanges(accounts.ACCOUNT_LIST))
+
+ return _.reduce(
+ (acc, value) =>
+ Promise.all([acc, ccxt.getMarkets(value, ALL_CRYPTOS)])
+ .then(([a, markets]) => Promise.resolve({
+ ...a,
+ [value]: markets
+ })),
+ Promise.resolve({}),
+ availableExchanges
+ )
+}
+
module.exports = {
+ fetchExchange,
buy,
sell,
- active
+ active,
+ getMarkets
}
diff --git a/lib/graphql/resolvers.js b/lib/graphql/resolvers.js
index 1270f1f3..5d1f6323 100644
--- a/lib/graphql/resolvers.js
+++ b/lib/graphql/resolvers.js
@@ -61,6 +61,18 @@ const addReceiptInfo = receiptInfo => ret => {
}
+const addMachineScreenOpts = smth => _.update(
+ 'screenOptions',
+ _.flow(
+ addSmthInfo(
+ 'rates',
+ [
+ 'active'
+ ]
+ )(smth.rates)
+ )
+)
+
/* TODO: Simplify this. */
const buildTriggers = allTriggers => {
const normalTriggers = []
@@ -103,7 +115,8 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
_.pick([
'coins',
'configVersion',
- 'timezone'
+ 'timezone',
+ 'screenOptions'
]),
_.update('coins', massageCoins),
_.set('serverVersion', VERSION),
@@ -117,6 +130,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
configManager.getLocale(deviceId, settings.config),
configManager.getOperatorInfo(settings.config),
configManager.getReceipt(settings.config),
+ configManager.getAllMachineScreenOpts(settings.config),
!!configManager.getCashOut(deviceId, settings.config).active,
getMachine(deviceId, currentConfigVersion),
configManager.getCustomerAuthenticationMethod(settings.config)
@@ -129,6 +143,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
localeInfo,
operatorInfo,
receiptInfo,
+ machineScreenOpts,
twoWayMode,
{ numberOfCassettes, numberOfRecyclers },
customerAuthentication,
@@ -153,7 +168,8 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
urlsToPing,
}),
addOperatorInfo(operatorInfo),
- addReceiptInfo(receiptInfo)
+ addReceiptInfo(receiptInfo),
+ addMachineScreenOpts(machineScreenOpts)
)(staticConf))
}
diff --git a/lib/graphql/types.js b/lib/graphql/types.js
index 89296c6c..c0c72b1e 100644
--- a/lib/graphql/types.js
+++ b/lib/graphql/types.js
@@ -49,6 +49,14 @@ type ReceiptInfo {
addressQRCode: Boolean!
}
+type MachineScreenOptions {
+ rates: RateScreenOptions!
+}
+
+type RateScreenOptions {
+ active: Boolean!
+}
+
type SpeedtestFile {
url: String!
size: Int!
@@ -147,6 +155,7 @@ type StaticConfig {
operatorInfo: OperatorInfo
machineInfo: MachineInfo!
receiptInfo: ReceiptInfo
+ screenOptions: MachineScreenOptions
speedtestFiles: [SpeedtestFile!]!
urlsToPing: [String!]!
diff --git a/lib/new-admin/graphql/resolvers/blacklist.resolver.js b/lib/new-admin/graphql/resolvers/blacklist.resolver.js
index 8c15e43f..e0b63d53 100644
--- a/lib/new-admin/graphql/resolvers/blacklist.resolver.js
+++ b/lib/new-admin/graphql/resolvers/blacklist.resolver.js
@@ -2,13 +2,16 @@ const blacklist = require('../../../blacklist')
const resolvers = {
Query: {
- blacklist: () => blacklist.getBlacklist()
+ blacklist: () => blacklist.getBlacklist(),
+ blacklistMessages: () => blacklist.getMessages()
},
Mutation: {
- deleteBlacklistRow: (...[, { cryptoCode, address }]) =>
- blacklist.deleteFromBlacklist(cryptoCode, address),
- insertBlacklistRow: (...[, { cryptoCode, address }]) =>
- blacklist.insertIntoBlacklist(cryptoCode, address)
+ deleteBlacklistRow: (...[, { address }]) =>
+ blacklist.deleteFromBlacklist(address),
+ insertBlacklistRow: (...[, { address }]) =>
+ blacklist.insertIntoBlacklist(address),
+ editBlacklistMessage: (...[, { id, content }]) =>
+ blacklist.editBlacklistMessage(id, content)
}
}
diff --git a/lib/new-admin/graphql/resolvers/index.js b/lib/new-admin/graphql/resolvers/index.js
index a20d9216..ea3cb3fa 100644
--- a/lib/new-admin/graphql/resolvers/index.js
+++ b/lib/new-admin/graphql/resolvers/index.js
@@ -11,6 +11,7 @@ const funding = require('./funding.resolver')
const log = require('./log.resolver')
const loyalty = require('./loyalty.resolver')
const machine = require('./machine.resolver')
+const market = require('./market.resolver')
const notification = require('./notification.resolver')
const pairing = require('./pairing.resolver')
const rates = require('./rates.resolver')
@@ -35,6 +36,7 @@ const resolvers = [
log,
loyalty,
machine,
+ market,
notification,
pairing,
rates,
diff --git a/lib/new-admin/graphql/resolvers/market.resolver.js b/lib/new-admin/graphql/resolvers/market.resolver.js
new file mode 100644
index 00000000..49864417
--- /dev/null
+++ b/lib/new-admin/graphql/resolvers/market.resolver.js
@@ -0,0 +1,9 @@
+const exchange = require('../../../exchange')
+
+const resolvers = {
+ Query: {
+ getMarkets: () => exchange.getMarkets()
+ }
+}
+
+module.exports = resolvers
diff --git a/lib/new-admin/graphql/types/blacklist.type.js b/lib/new-admin/graphql/types/blacklist.type.js
index 3cd1bfa1..7cc34721 100644
--- a/lib/new-admin/graphql/types/blacklist.type.js
+++ b/lib/new-admin/graphql/types/blacklist.type.js
@@ -2,17 +2,26 @@ const { gql } = require('apollo-server-express')
const typeDef = gql`
type Blacklist {
- cryptoCode: String!
address: String!
+ blacklistMessage: BlacklistMessage!
+ }
+
+ type BlacklistMessage {
+ id: ID
+ label: String
+ content: String
+ allowToggle: Boolean
}
type Query {
blacklist: [Blacklist] @auth
+ blacklistMessages: [BlacklistMessage] @auth
}
type Mutation {
- deleteBlacklistRow(cryptoCode: String!, address: String!): Blacklist @auth
- insertBlacklistRow(cryptoCode: String!, address: String!): Blacklist @auth
+ deleteBlacklistRow(address: String!): Blacklist @auth
+ insertBlacklistRow(address: String!): Blacklist @auth
+ editBlacklistMessage(id: ID, content: String): BlacklistMessage @auth
}
`
diff --git a/lib/new-admin/graphql/types/index.js b/lib/new-admin/graphql/types/index.js
index f4794b67..e33c50b5 100644
--- a/lib/new-admin/graphql/types/index.js
+++ b/lib/new-admin/graphql/types/index.js
@@ -11,6 +11,7 @@ const funding = require('./funding.type')
const log = require('./log.type')
const loyalty = require('./loyalty.type')
const machine = require('./machine.type')
+const market = require('./market.type')
const notification = require('./notification.type')
const pairing = require('./pairing.type')
const rates = require('./rates.type')
@@ -35,6 +36,7 @@ const types = [
log,
loyalty,
machine,
+ market,
notification,
pairing,
rates,
diff --git a/lib/new-admin/graphql/types/market.type.js b/lib/new-admin/graphql/types/market.type.js
new file mode 100644
index 00000000..2413a9fe
--- /dev/null
+++ b/lib/new-admin/graphql/types/market.type.js
@@ -0,0 +1,9 @@
+const { gql } = require('apollo-server-express')
+
+const typeDef = gql`
+ type Query {
+ getMarkets: JSONObject @auth
+ }
+`
+
+module.exports = typeDef
diff --git a/lib/new-admin/services/transactions.js b/lib/new-admin/services/transactions.js
index 546ef0b3..8a838b64 100644
--- a/lib/new-admin/services/transactions.js
+++ b/lib/new-admin/services/transactions.js
@@ -50,6 +50,7 @@ function batch (
excludeTestingCustomers = false,
simplified
) {
+ const isCsvExport = _.isBoolean(simplified)
const packager = _.flow(
_.flatten,
_.orderBy(_.property('created'), ['desc']),
@@ -92,7 +93,7 @@ function batch (
AND ($12 is null or txs.to_address = $12)
AND ($13 is null or txs.txStatus = $13)
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
- AND (error IS NOT null OR tb.error_message IS NOT null OR fiat > 0)
+ ${isCsvExport && !simplified ? '' : 'AND (error IS NOT null OR tb.error_message IS NOT null OR fiat > 0)'}
ORDER BY created DESC limit $4 offset $5`
const cashOutSql = `SELECT 'cashOut' AS tx_class,
@@ -126,7 +127,7 @@ function batch (
AND ($13 is null or txs.txStatus = $13)
AND ($14 is null or txs.swept = $14)
${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
- AND (fiat > 0)
+ ${isCsvExport ? '' : 'AND fiat > 0'}
ORDER BY created DESC limit $4 offset $5`
// The swept filter is cash-out only, so omit the cash-in query entirely
@@ -152,14 +153,14 @@ function batch (
return Promise.all(promises)
.then(packager)
- .then(res => {
- if (simplified) return simplifiedBatch(res)
+ .then(res =>
+ !isCsvExport ? res :
// GQL transactions and transactionsCsv both use this function and
// if we don't check for the correct simplified value, the Transactions page polling
// will continuously build a csv in the background
- else if (simplified === false) return advancedBatch(res)
- return res
- })
+ simplified ? simplifiedBatch(res) :
+ advancedBatch(res)
+ )
}
function advancedBatch (data) {
diff --git a/lib/new-config-manager.js b/lib/new-config-manager.js
index 680b57a9..239bfa20 100644
--- a/lib/new-config-manager.js
+++ b/lib/new-config-manager.js
@@ -13,7 +13,12 @@ const namespaces = {
TERMS_CONDITIONS: 'termsConditions',
CASH_OUT: 'cashOut',
CASH_IN: 'cashIn',
- COMPLIANCE: 'compliance'
+ COMPLIANCE: 'compliance',
+ MACHINE_SCREENS: 'machineScreens'
+}
+
+const machineScreens = {
+ RATES: 'rates'
}
const stripl = _.curry((q, str) => _.startsWith(q, str) ? str.slice(q.length) : str)
@@ -72,6 +77,8 @@ const getCoinAtmRadar = fromNamespace(namespaces.COIN_ATM_RADAR)
const getTermsConditions = fromNamespace(namespaces.TERMS_CONDITIONS)
const getReceipt = fromNamespace(namespaces.RECEIPT)
const getCompliance = fromNamespace(namespaces.COMPLIANCE)
+const getMachineScreenOpts = (screenName, config) => _.compose(fromNamespace(screenName), fromNamespace(namespaces.MACHINE_SCREENS))(config)
+const getAllMachineScreenOpts = config => _.reduce((acc, value) => ({ ...acc, [value]: getMachineScreenOpts(value, config) }), {}, _.values(machineScreens))
const getAllCryptoCurrencies = (config) => {
const locale = fromNamespace(namespaces.LOCALE)(config)
@@ -180,6 +187,8 @@ module.exports = {
getWalletSettings,
getCashInSettings,
getOperatorInfo,
+ getMachineScreenOpts,
+ getAllMachineScreenOpts,
getNotifications,
getGlobalNotifications,
getLocale,
diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js
index 76cd120c..e571d1e2 100644
--- a/lib/new-settings-loader.js
+++ b/lib/new-settings-loader.js
@@ -100,16 +100,19 @@ function loadAccounts (schemaVersion) {
.then(_.compose(_.defaultTo({}), _.get('data.accounts')))
}
+function hideSecretFields (accounts) {
+ return _.flow(
+ _.filter(path => !_.isEmpty(_.get(path, accounts))),
+ _.reduce(
+ (accounts, path) => _.assoc(path, PASSWORD_FILLED, accounts),
+ accounts
+ )
+ )(SECRET_FIELDS)
+}
+
function showAccounts (schemaVersion) {
return loadAccounts(schemaVersion)
- .then(accounts => {
- const filledSecretPaths = _.compact(_.map(path => {
- if (!_.isEmpty(_.get(path, accounts))) {
- return path
- }
- }, SECRET_FIELDS))
- return _.compose(_.map(path => _.assoc(path, PASSWORD_FILLED), filledSecretPaths))(accounts)
- })
+ .then(hideSecretFields)
}
const insertConfigRow = (dbOrTx, data) =>
diff --git a/lib/plugins.js b/lib/plugins.js
index d5bfcb4f..9c9d1a29 100644
--- a/lib/plugins.js
+++ b/lib/plugins.js
@@ -278,6 +278,7 @@ function plugins (settings, deviceId) {
const localeConfig = configManager.getLocale(deviceId, settings.config)
const fiatCode = localeConfig.fiatCurrency
const cryptoCodes = localeConfig.cryptoCurrencies
+ const machineScreenOpts = configManager.getAllMachineScreenOpts(settings.config)
const tickerPromises = cryptoCodes.map(c => getTickerRates(fiatCode, c))
const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c))
@@ -327,7 +328,8 @@ function plugins (settings, deviceId) {
coins,
configVersion,
areThereAvailablePromoCodes: numberOfAvailablePromoCodes > 0,
- timezone
+ timezone,
+ screenOptions: machineScreenOpts
}
})
}
@@ -475,25 +477,28 @@ function plugins (settings, deviceId) {
function buyAndSell (rec, doBuy, tx) {
const cryptoCode = rec.cryptoCode
- const fiatCode = rec.fiatCode
- const cryptoAtoms = doBuy ? commissionMath.fiatToCrypto(tx, rec, deviceId, settings.config) : rec.cryptoAtoms.negated()
+ return exchange.fetchExchange(settings, cryptoCode)
+ .then(_exchange => {
+ const fiatCode = _exchange.account.currencyMarket
+ const cryptoAtoms = doBuy ? commissionMath.fiatToCrypto(tx, rec, deviceId, settings.config) : rec.cryptoAtoms.negated()
- const market = [fiatCode, cryptoCode].join('')
+ const market = [fiatCode, cryptoCode].join('')
- if (!exchange.active(settings, cryptoCode)) return
+ if (!exchange.active(settings, cryptoCode)) return
- const direction = doBuy ? 'cashIn' : 'cashOut'
- const internalTxId = tx ? tx.id : rec.id
- logger.debug('[%s] Pushing trade: %d', market, cryptoAtoms)
- if (!tradesQueues[market]) tradesQueues[market] = []
- tradesQueues[market].push({
- direction,
- internalTxId,
- fiatCode,
- cryptoAtoms,
- cryptoCode,
- timestamp: Date.now()
- })
+ const direction = doBuy ? 'cashIn' : 'cashOut'
+ const internalTxId = tx ? tx.id : rec.id
+ logger.debug('[%s] Pushing trade: %d', market, cryptoAtoms)
+ if (!tradesQueues[market]) tradesQueues[market] = []
+ tradesQueues[market].push({
+ direction,
+ internalTxId,
+ fiatCode,
+ cryptoAtoms,
+ cryptoCode,
+ timestamp: Date.now()
+ })
+ })
}
function consolidateTrades (cryptoCode, fiatCode) {
@@ -550,19 +555,22 @@ function plugins (settings, deviceId) {
const deviceIds = devices.map(device => device.deviceId)
const lists = deviceIds.map(deviceId => {
const localeConfig = configManager.getLocale(deviceId, settings.config)
- const fiatCode = localeConfig.fiatCurrency
const cryptoCodes = localeConfig.cryptoCurrencies
- return cryptoCodes.map(cryptoCode => ({
- fiatCode,
- cryptoCode
+ return Promise.all(cryptoCodes.map(cryptoCode => {
+ return exchange.fetchExchange(settings, cryptoCode)
+ .then(exchange => ({
+ fiatCode: exchange.account.currencyMarket,
+ cryptoCode
+ }))
}))
})
-
- const tradesPromises = _.uniq(_.flatten(lists))
- .map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode))
-
- return Promise.all(tradesPromises)
+
+ return Promise.all(lists)
+ })
+ .then(lists => {
+ return Promise.all(_.uniq(_.flatten(lists))
+ .map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode)))
})
.catch(logger.error)
}
diff --git a/lib/plugins/common/ccxt.js b/lib/plugins/common/ccxt.js
index 5312b35c..825c30b7 100644
--- a/lib/plugins/common/ccxt.js
+++ b/lib/plugins/common/ccxt.js
@@ -34,11 +34,8 @@ function buildMarket (fiatCode, cryptoCode, serviceName) {
if (!_.includes(cryptoCode, ALL[serviceName].CRYPTO)) {
throw new Error('Unsupported crypto: ' + cryptoCode)
}
- const fiatSupported = ALL[serviceName].FIAT
- if (fiatSupported !== 'ALL_CURRENCIES' && !_.includes(fiatCode, fiatSupported)) {
- logger.info('Building a market for an unsupported fiat. Defaulting to EUR market')
- return cryptoCode + '/' + 'EUR'
- }
+
+ if (_.isNil(fiatCode)) throw new Error('Market pair building failed: Missing fiat code')
return cryptoCode + '/' + fiatCode
}
diff --git a/lib/plugins/exchange/binance.js b/lib/plugins/exchange/binance.js
index e1b4a4c4..dc2a4c52 100644
--- a/lib/plugins/exchange/binance.js
+++ b/lib/plugins/exchange/binance.js
@@ -8,7 +8,7 @@ const { BTC, BCH, XMR, ETH, LTC, ZEC, LN } = COINS
const CRYPTO = [BTC, ETH, LTC, ZEC, BCH, XMR, LN]
const FIAT = ['EUR']
const DEFAULT_FIAT_MARKET = 'EUR'
-const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
+const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
const loadConfig = (account) => {
const mapper = {
diff --git a/lib/plugins/exchange/binanceus.js b/lib/plugins/exchange/binanceus.js
index ecf058b6..e8f0c371 100644
--- a/lib/plugins/exchange/binanceus.js
+++ b/lib/plugins/exchange/binanceus.js
@@ -7,7 +7,8 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT, USDT_TRON, LN } = COINS
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, USDT_TRON, LN]
const FIAT = ['USD']
-const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
+const DEFAULT_FIAT_MARKET = 'USD'
+const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
const loadConfig = (account) => {
const mapper = {
@@ -17,4 +18,4 @@ const loadConfig = (account) => {
return { ...mapped, timeout: 3000 }
}
-module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
+module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
diff --git a/lib/plugins/exchange/bitfinex.js b/lib/plugins/exchange/bitfinex.js
index 4feccb0c..4e4d85ce 100644
--- a/lib/plugins/exchange/bitfinex.js
+++ b/lib/plugins/exchange/bitfinex.js
@@ -7,6 +7,7 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
const { BTC, ETH, LTC, BCH, USDT, LN } = COINS
const CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
const FIAT = ['USD', 'EUR']
+const DEFAULT_FIAT_MARKET = 'EUR'
const AMOUNT_PRECISION = 8
const REQUIRED_CONFIG_FIELDS = ['key', 'secret']
@@ -18,4 +19,4 @@ const loadConfig = (account) => {
return { ...mapped, timeout: 3000 }
}
-module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
+module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, DEFAULT_FIAT_MARKET, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
diff --git a/lib/plugins/exchange/bitstamp.js b/lib/plugins/exchange/bitstamp.js
index 5494ff1c..bd745d49 100644
--- a/lib/plugins/exchange/bitstamp.js
+++ b/lib/plugins/exchange/bitstamp.js
@@ -7,8 +7,9 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
const { BTC, ETH, LTC, BCH, USDT, LN } = COINS
const CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
const FIAT = ['USD', 'EUR']
+const DEFAULT_FIAT_MARKET = 'EUR'
const AMOUNT_PRECISION = 8
-const REQUIRED_CONFIG_FIELDS = ['key', 'secret', 'clientId']
+const REQUIRED_CONFIG_FIELDS = ['key', 'secret', 'clientId', 'currencyMarket']
const loadConfig = (account) => {
const mapper = {
@@ -19,4 +20,4 @@ const loadConfig = (account) => {
return { ...mapped, timeout: 3000 }
}
-module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
+module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
diff --git a/lib/plugins/exchange/ccxt.js b/lib/plugins/exchange/ccxt.js
index 5de324f5..63b57fa9 100644
--- a/lib/plugins/exchange/ccxt.js
+++ b/lib/plugins/exchange/ccxt.js
@@ -1,9 +1,13 @@
const { utils: coinUtils } = require('@lamassu/coins')
const _ = require('lodash/fp')
const ccxt = require('ccxt')
+const mem = require('mem')
const { buildMarket, ALL, isConfigValid } = require('../common/ccxt')
const { ORDER_TYPES } = require('./consts')
+const logger = require('../../logger')
+const { currencies } = require('../../new-admin/config')
+const T = require('../../time')
const DEFAULT_PRICE_PRECISION = 2
const DEFAULT_AMOUNT_PRECISION = 8
@@ -18,7 +22,8 @@ function trade (side, account, tradeEntry, exchangeName) {
const { USER_REF, loadOptions, loadConfig = _.noop, REQUIRED_CONFIG_FIELDS, ORDER_TYPE, AMOUNT_PRECISION } = exchangeConfig
if (!isConfigValid(account, REQUIRED_CONFIG_FIELDS)) throw Error('Invalid config')
- const symbol = buildMarket(fiatCode, cryptoCode, exchangeName)
+ const selectedFiatMarket = account.currencyMarket
+ const symbol = buildMarket(selectedFiatMarket, cryptoCode, exchangeName)
const precision = _.defaultTo(DEFAULT_AMOUNT_PRECISION, AMOUNT_PRECISION)
const amount = coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(precision)
const accountOptions = _.isFunction(loadOptions) ? loadOptions(account) : {}
@@ -50,4 +55,36 @@ function calculatePrice (side, amount, orderBook) {
throw new Error('Insufficient market depth')
}
-module.exports = { trade }
+function _getMarkets (exchangeName, availableCryptos) {
+ try {
+ const exchange = new ccxt[exchangeName]()
+ const cryptosToQuoteAgainst = ['USDT']
+ const currencyCodes = _.concat(_.map(it => it.code, currencies), cryptosToQuoteAgainst)
+
+ return exchange.fetchMarkets()
+ .then(_.filter(it => (it.type === 'spot' || it.spot)))
+ .then(res =>
+ _.reduce((acc, value) => {
+ if (_.includes(value.base, availableCryptos) && _.includes(value.quote, currencyCodes)) {
+ if (value.quote === value.base) return acc
+
+ if (_.isNil(acc[value.quote])) {
+ return { ...acc, [value.quote]: [value.base] }
+ }
+
+ acc[value.quote].push(value.base)
+ }
+ return acc
+ }, {}, res)
+ )
+ } catch (e) {
+ logger.debug(`No CCXT exchange found for ${exchangeName}`)
+ }
+}
+
+const getMarkets = mem(_getMarkets, {
+ maxAge: T.week,
+ cacheKey: (exchangeName, availableCryptos) => exchangeName
+})
+
+module.exports = { trade, getMarkets }
diff --git a/lib/plugins/exchange/cex.js b/lib/plugins/exchange/cex.js
index 525eb427..b9687e15 100644
--- a/lib/plugins/exchange/cex.js
+++ b/lib/plugins/exchange/cex.js
@@ -7,7 +7,8 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
const { BTC, BCH, DASH, ETH, LTC, USDT, TRX, USDT_TRON, LN } = COINS
const CRYPTO = [BTC, ETH, LTC, DASH, BCH, USDT, TRX, USDT_TRON, LN]
const FIAT = ['USD', 'EUR']
-const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
+const DEFAULT_FIAT_MARKET = 'EUR'
+const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
const loadConfig = (account) => {
const mapper = {
@@ -17,4 +18,4 @@ const loadConfig = (account) => {
return { ...mapped, timeout: 3000 }
}
-module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
+module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
diff --git a/lib/plugins/exchange/itbit.js b/lib/plugins/exchange/itbit.js
index 02572335..d80268e1 100644
--- a/lib/plugins/exchange/itbit.js
+++ b/lib/plugins/exchange/itbit.js
@@ -7,8 +7,9 @@ const ORDER_TYPE = ORDER_TYPES.LIMIT
const { BTC, ETH, USDT, LN } = COINS
const CRYPTO = [BTC, ETH, USDT, LN]
const FIAT = ['USD']
+const DEFAULT_FIAT_MARKET = 'USD'
const AMOUNT_PRECISION = 4
-const REQUIRED_CONFIG_FIELDS = ['clientKey', 'clientSecret', 'userId', 'walletId']
+const REQUIRED_CONFIG_FIELDS = ['clientKey', 'clientSecret', 'userId', 'walletId', 'currencyMarket']
const loadConfig = (account) => {
const mapper = {
@@ -21,4 +22,4 @@ const loadConfig = (account) => {
}
const loadOptions = ({ walletId }) => ({ walletId })
-module.exports = { loadOptions, loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
+module.exports = { loadOptions, loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
diff --git a/lib/plugins/exchange/kraken.js b/lib/plugins/exchange/kraken.js
index 849af0e5..0f050ccf 100644
--- a/lib/plugins/exchange/kraken.js
+++ b/lib/plugins/exchange/kraken.js
@@ -7,8 +7,9 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
const { BTC, BCH, DASH, ETH, LTC, ZEC, XMR, USDT, TRX, USDT_TRON, LN } = COINS
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, XMR, USDT, TRX, USDT_TRON, LN]
const FIAT = ['USD', 'EUR']
+const DEFAULT_FIAT_MARKET = 'EUR'
const AMOUNT_PRECISION = 6
-const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
+const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
const USER_REF = 'userref'
const loadConfig = (account) => {
@@ -26,4 +27,4 @@ const loadConfig = (account) => {
const loadOptions = () => ({ expiretm: '+60' })
-module.exports = { USER_REF, loadOptions, loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
+module.exports = { USER_REF, loadOptions, loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
diff --git a/lib/routes.js b/lib/routes.js
index e6654121..599c77c9 100644
--- a/lib/routes.js
+++ b/lib/routes.js
@@ -61,6 +61,15 @@ morgan.token('bytesRead', (_req, res) => res.bytesRead)
morgan.token('bytesWritten', (_req, res) => res.bytesWritten)
app.use(morgan(':method :url :status :response-time ms -- :bytesRead/:bytesWritten B', { stream: logger.stream }))
+app.use('/robots.txt', (req, res) => {
+ res.type('text/plain')
+ res.send("User-agent: *\nDisallow: /")
+})
+
+app.get('/', (req, res) => {
+ res.sendStatus(404)
+})
+
// app /pair and /ca routes
app.use('/', pairingRoutes)
diff --git a/lib/routes/cashboxRoutes.js b/lib/routes/cashboxRoutes.js
index cec84416..6fcc88bf 100644
--- a/lib/routes/cashboxRoutes.js
+++ b/lib/routes/cashboxRoutes.js
@@ -1,40 +1,41 @@
const express = require('express')
+const _ = require('lodash/fp')
const router = express.Router()
const cashbox = require('../cashbox-batches')
const notifier = require('../notifier')
-const { getMachine, setMachine } = require('../machine-loader')
+const { getMachine, setMachine, getMachineName } = require('../machine-loader')
const { loadLatestConfig } = require('../new-settings-loader')
const { getCashInSettings } = require('../new-config-manager')
const { AUTOMATIC } = require('../constants')
const logger = require('../logger')
-function notifyCashboxRemoval (req, res, next) {
+
+function cashboxRemoval (req, res, next) {
const operatorId = res.locals.operatorId
- logger.info(`** DEBUG ** - Cashbox removal - Received a cashbox opening request from device ${req.deviceId}`)
+ notifier.cashboxNotify(req.deviceId).catch(logger.error)
- return notifier.cashboxNotify(req.deviceId)
- .then(() => Promise.all([getMachine(req.deviceId), loadLatestConfig()]))
+ return Promise.all([getMachine(req.deviceId), loadLatestConfig()])
.then(([machine, config]) => {
- logger.info('** DEBUG ** - Cashbox removal - Retrieving system options for cash-in')
const cashInSettings = getCashInSettings(config)
if (cashInSettings.cashboxReset !== AUTOMATIC) {
- logger.info('** DEBUG ** - Cashbox removal - Cashbox reset is set to manual. A cashbox batch will NOT be created')
- logger.info(`** DEBUG ** - Cashbox removal - Process finished`)
- return res.status(200).send({ status: 'OK' })
+ return Promise.all([
+ cashbox.getMachineUnbatchedBills(req.deviceId),
+ getMachineName(req.deviceId)
+ ])
}
- logger.info('** DEBUG ** - Cashbox removal - Cashbox reset is set to automatic. A cashbox batch WILL be created')
- logger.info('** DEBUG ** - Cashbox removal - Creating new batch...')
- return cashbox.createCashboxBatch(req.deviceId, machine.cashUnits.cashbox)
- .then(() => {
- logger.info(`** DEBUG ** - Cashbox removal - Process finished`)
- return res.status(200).send({ status: 'OK' })
- })
+ return cashbox.createCashboxBatch(req.deviceId, machine.cashbox)
+ .then(batch => Promise.all([
+ cashbox.getBatchById(batch.id),
+ getMachineName(batch.device_id),
+ setMachine({ deviceId: req.deviceId, action: 'emptyCashInBills' }, operatorId)
+ ]))
})
+ .then(([batch, machineName]) => res.status(200).send({ batch: _.merge(batch, { machineName }), status: 'OK' }))
.catch(next)
}
-router.post('/removal', notifyCashboxRemoval)
+router.post('/removal', cashboxRemoval)
module.exports = router
diff --git a/migrations/1732874039534-market-currency.js b/migrations/1732874039534-market-currency.js
new file mode 100644
index 00000000..359db4bd
--- /dev/null
+++ b/migrations/1732874039534-market-currency.js
@@ -0,0 +1,30 @@
+const _ = require('lodash/fp')
+const { loadLatest, saveAccounts } = require('../lib/new-settings-loader')
+const { ACCOUNT_LIST } = require('../lib/new-admin/config/accounts')
+const { ALL } = require('../lib/plugins/common/ccxt')
+
+exports.up = function (next) {
+ return loadLatest()
+ .then(({ accounts }) => {
+ const allExchanges = _.map(it => it.code)(_.filter(it => it.class === 'exchange', ACCOUNT_LIST))
+ const configuredExchanges = _.intersection(allExchanges, _.keys(accounts))
+
+ const newAccounts = _.reduce(
+ (acc, value) => {
+ if (!_.isNil(accounts[value].currencyMarket)) return acc
+ if (_.includes('EUR', ALL[value].FIAT)) return { ...acc, [value]: { currencyMarket: 'EUR' } }
+ return { ...acc, [value]: { currencyMarket: ALL[value].DEFAULT_FIAT_CURRENCY } }
+ },
+ {},
+ configuredExchanges
+ )
+
+ return saveAccounts(newAccounts)
+ })
+ .then(next)
+ .catch(next)
+}
+
+module.exports.down = function (next) {
+ next()
+}
diff --git a/migrations/1732881489395-coin-agnostic-blacklist.js b/migrations/1732881489395-coin-agnostic-blacklist.js
new file mode 100644
index 00000000..9a227308
--- /dev/null
+++ b/migrations/1732881489395-coin-agnostic-blacklist.js
@@ -0,0 +1,18 @@
+var db = require('./db')
+
+exports.up = function (next) {
+ var sql = [
+ `CREATE TABLE blacklist_temp (
+ address TEXT NOT NULL UNIQUE
+ )`,
+ `INSERT INTO blacklist_temp (address) SELECT DISTINCT address FROM blacklist`,
+ `DROP TABLE blacklist`,
+ `ALTER TABLE blacklist_temp RENAME TO blacklist`
+ ]
+
+ db.multi(sql, next)
+}
+
+exports.down = function (next) {
+ next()
+}
diff --git a/migrations/1732881489396-advanced-blacklisting.js b/migrations/1732881489396-advanced-blacklisting.js
new file mode 100644
index 00000000..4648f182
--- /dev/null
+++ b/migrations/1732881489396-advanced-blacklisting.js
@@ -0,0 +1,24 @@
+const uuid = require('uuid')
+
+var db = require('./db')
+
+exports.up = function (next) {
+ const defaultMessageId = uuid.v4()
+
+ var sql = [
+ `CREATE TABLE blacklist_messages (
+ id UUID PRIMARY KEY,
+ label TEXT NOT NULL,
+ content TEXT NOT NULL,
+ allow_toggle BOOLEAN NOT NULL DEFAULT true
+ )`,
+ `INSERT INTO blacklist_messages (id, label, content, allow_toggle) VALUES ('${defaultMessageId}', 'Suspicious address', 'This address may be associated with a deceptive offer or a prohibited group. Please make sure you''re using an address from your own wallet.', false)`,
+ `ALTER TABLE blacklist ADD COLUMN blacklist_message_id UUID REFERENCES blacklist_messages(id) NOT NULL DEFAULT '${defaultMessageId}'`
+ ]
+
+ db.multi(sql, next)
+}
+
+exports.down = function (next) {
+ next()
+}
diff --git a/migrations/1732881659436-rates-screen.js b/migrations/1732881659436-rates-screen.js
new file mode 100644
index 00000000..94db1542
--- /dev/null
+++ b/migrations/1732881659436-rates-screen.js
@@ -0,0 +1,20 @@
+const _ = require('lodash/fp')
+const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
+
+exports.up = function (next) {
+ const newConfig = {}
+ return loadLatest()
+ .then(({ config }) => {
+ if (!_.isNil(config.machineScreens_rates_active)) return
+ newConfig[`machineScreens_rates_active`] = true
+ return saveConfig(newConfig)
+ })
+ .then(next)
+ .catch(err => {
+ return next(err)
+ })
+}
+
+module.exports.down = function (next) {
+ next()
+}
diff --git a/new-lamassu-admin/public/robots.txt b/new-lamassu-admin/public/robots.txt
new file mode 100644
index 00000000..77470cb3
--- /dev/null
+++ b/new-lamassu-admin/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
\ No newline at end of file
diff --git a/new-lamassu-admin/src/components/Popper.js b/new-lamassu-admin/src/components/Popper.js
index db01740d..a933e39d 100644
--- a/new-lamassu-admin/src/components/Popper.js
+++ b/new-lamassu-admin/src/components/Popper.js
@@ -105,20 +105,29 @@ const Popover = ({
const classes = useStyles()
- const arrowClasses = {
+ const getArrowClasses = placement => ({
[classes.arrow]: true,
- [classes.arrowBottom]: props.placement === 'bottom',
- [classes.arrowTop]: props.placement === 'top',
- [classes.arrowRight]: props.placement === 'right',
- [classes.arrowLeft]: props.placement === 'left'
+ [classes.arrowBottom]: placement === 'bottom',
+ [classes.arrowTop]: placement === 'top',
+ [classes.arrowRight]: placement === 'right',
+ [classes.arrowLeft]: placement === 'left'
+ })
+
+ const flipPlacements = {
+ top: ['bottom'],
+ bottom: ['top'],
+ left: ['right'],
+ right: ['left']
}
- const modifiers = R.merge(props.modifiers, {
+ const modifiers = R.mergeDeepLeft(props.modifiers, {
flip: {
- enabled: false
+ enabled: R.defaultTo(false, props.flip),
+ allowedAutoPlacements: flipPlacements[props.placement],
+ boundary: 'clippingParents'
},
preventOverflow: {
- enabled: true,
+ enabled: R.defaultTo(true, props.preventOverflow),
boundariesElement: 'scrollParent'
},
offset: {
@@ -126,7 +135,7 @@ const Popover = ({
offset: '0, 10'
},
arrow: {
- enabled: true,
+ enabled: R.defaultTo(true, props.showArrow),
element: arrowRef
},
computeStyle: {
@@ -134,6 +143,12 @@ const Popover = ({
}
})
+ if (props.preventOverflow === false) {
+ modifiers.hide = {
+ enabled: false
+ }
+ }
+
return (
<>
{props.warningMessage}
+Enable paper wallet (only)
Reject reused addresses
- The "Reject reused addresses" option means that all addresses - that are used once will be automatically rejected if there's - an attempt to use them again on a new transaction. + This option requires a user to scan a fresh wallet address if + they attempt to scan one that had been previously used for a + transaction in your network.
-- For details please read the relevant knowledgebase article: -
-Enable rates screen
+