From 9f26aef7908de1b4ccc56e8e3e7815d9bb4de0fe Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Thu, 1 Feb 2018 13:45:21 -0500 Subject: [PATCH 01/36] Update lamassu-update, add to bin (#92) * Use --allow-root to avoid gyp error Without --allow-root, when running on existing installs the terminal streams an endless: ```gyp WARN EACCES user "root" does not have permission to access the dev dir...``` * Add lamassu-update to bin * Change to --unsafe-perm which is mentioned in npm docs --- bin/lamassu-update | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/lamassu-update b/bin/lamassu-update index fd8efbf2..df6848ac 100644 --- a/bin/lamassu-update +++ b/bin/lamassu-update @@ -3,7 +3,7 @@ set -e supervisorctl stop lamassu-server supervisorctl stop lamassu-admin-server -npm -g install lamassu/lamassu-server#v5 +npm -g install lamassu/lamassu-server#v5 --unsafe-perm lamassu-migrate supervisorctl start lamassu-server supervisorctl start lamassu-admin-server diff --git a/package.json b/package.json index bf2d3d39..9838f139 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,8 @@ "lamassu-mnemonic": "./bin/lamassu-mnemonic", "lamassu-cancel": "./bin/lamassu-cancel", "lamassu-nuke-db": "./bin/lamassu-nuke-db", - "lamassu-coins": "./bin/lamassu-coins" + "lamassu-coins": "./bin/lamassu-coins", + "lamassu-update": "./bin/lamassu-update" }, "scripts": { "start": "node bin/lamassu-server", From 118e4ec4be67d6351de7698e0df9beff9fc16a29 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 1 Feb 2018 01:02:42 +0200 Subject: [PATCH 02/36] refactor cash-in and cash-out --- lib/admin/transactions.js | 2 +- lib/cash-in-tx.js | 327 -------------- lib/cash-in/cash-in-atomic.js | 83 ++++ lib/cash-in/cash-in-low.js | 137 ++++++ lib/cash-in/cash-in-tx.js | 126 ++++++ lib/cash-out-tx.js | 410 ------------------ lib/cash-out/cash-out-actions.js | 50 +++ lib/cash-out/cash-out-atomic.js | 160 +++++++ lib/{ => cash-out}/cash-out-helper.js | 6 +- lib/cash-out/cash-out-low.js | 64 +++ lib/cash-out/cash-out-tx.js | 158 +++++++ lib/plugins.js | 2 +- lib/plugins/wallet/mock-wallet/mock-wallet.js | 2 +- lib/poller.js | 4 +- lib/tx.js | 4 +- package.json | 4 +- tools/modify.js | 2 +- 17 files changed, 791 insertions(+), 750 deletions(-) delete mode 100644 lib/cash-in-tx.js create mode 100644 lib/cash-in/cash-in-atomic.js create mode 100644 lib/cash-in/cash-in-low.js create mode 100644 lib/cash-in/cash-in-tx.js delete mode 100644 lib/cash-out-tx.js create mode 100644 lib/cash-out/cash-out-actions.js create mode 100644 lib/cash-out/cash-out-atomic.js rename lib/{ => cash-out}/cash-out-helper.js (96%) create mode 100644 lib/cash-out/cash-out-low.js create mode 100644 lib/cash-out/cash-out-tx.js diff --git a/lib/admin/transactions.js b/lib/admin/transactions.js index 8a3a8878..d7591858 100644 --- a/lib/admin/transactions.js +++ b/lib/admin/transactions.js @@ -3,7 +3,7 @@ const _ = require('lodash/fp') const db = require('../db') const machineLoader = require('../machine-loader') const tx = require('../tx') -const cashInTx = require('../cash-in-tx') +const cashInTx = require('../cash-in/cash-in-tx') const NUM_RESULTS = 20 diff --git a/lib/cash-in-tx.js b/lib/cash-in-tx.js deleted file mode 100644 index 37ce2171..00000000 --- a/lib/cash-in-tx.js +++ /dev/null @@ -1,327 +0,0 @@ -const _ = require('lodash/fp') -const pgp = require('pg-promise')() -const db = require('./db') -const BN = require('./bn') -const plugins = require('./plugins') -const logger = require('./logger') -const T = require('./time') -const E = require('./error') - -const PENDING_INTERVAL = '60 minutes' -const PENDING_INTERVAL_MS = 60 * T.minutes -const MAX_PENDING = 10 - -module.exports = {post, monitorPending, cancel, PENDING_INTERVAL} - -function atomic (machineTx, pi) { - const TransactionMode = pgp.txMode.TransactionMode - const isolationLevel = pgp.txMode.isolationLevel - const tmSRD = new TransactionMode({tiLevel: isolationLevel.serializable}) - - function transaction (t) { - const sql = 'select * from cash_in_txs where id=$1' - const sql2 = 'select * from bills where cash_in_txs_id=$1' - - return t.oneOrNone(sql, [machineTx.id]) - .then(row => { - if (row && row.tx_version >= machineTx.txVersion) throw new E.StaleTxError('Stale tx') - - return t.any(sql2, [machineTx.id]) - .then(billRows => { - const dbTx = toObj(row) - - return preProcess(dbTx, machineTx, pi) - .then(preProcessedTx => upsert(dbTx, preProcessedTx)) - .then(r => { - return insertNewBills(billRows, machineTx) - .then(newBills => _.set('newBills', newBills, r)) - }) - }) - }) - } - - transaction.txMode = tmSRD - - return transaction -} - -function post (machineTx, pi) { - return db.tx(atomic(machineTx, pi)) - .then(r => { - const updatedTx = r.tx - - return postProcess(r, pi) - .then(changes => update(updatedTx, changes)) - .then(tx => _.set('bills', machineTx.bills, tx)) - }) -} - -function nilEqual (a, b) { - if (_.isNil(a) && _.isNil(b)) return true - - return undefined -} - -function isMonotonic (oldField, newField, fieldKey) { - if (_.isNil(newField)) return false - if (_.isBoolean(oldField)) return oldField === newField || !oldField - if (oldField.isBigNumber) return oldField.lte(newField) - if (_.isNumber(oldField)) return oldField <= newField - - throw new Error(`Unexpected value [${fieldKey}]: ${oldField}, ${newField}`) -} - -function ensureRatchet (oldField, newField, fieldKey) { - const monotonic = ['cryptoAtoms', 'fiat', 'cashInFeeCrypto', 'send', 'sendConfirmed', 'operatorCompleted', 'timedout', 'txVersion'] - const free = ['sendPending', 'error', 'errorCode', 'customerId'] - - if (_.isNil(oldField)) return true - - if (_.includes(fieldKey, monotonic)) return isMonotonic(oldField, newField, fieldKey) - - if (_.includes(fieldKey, free)) { - if (_.isNil(newField)) return false - return true - } - - if (_.isNil(newField)) return false - if (oldField.isBigNumber && newField.isBigNumber) return BN(oldField).eq(newField) - if (oldField.toString() === newField.toString()) return true - - return false -} - -function diff (oldTx, newTx) { - let updatedTx = {} - - if (!oldTx) throw new Error('oldTx must not be null') - if (!newTx) throw new Error('newTx must not be null') - - _.forEach(fieldKey => { - const oldField = oldTx[fieldKey] - const newField = newTx[fieldKey] - if (fieldKey === 'bills') return - if (_.isEqualWith(nilEqual, oldField, newField)) return - - if (!ensureRatchet(oldField, newField, fieldKey)) { - logger.warn('Value from lamassu-machine would violate ratchet [%s]', fieldKey) - logger.warn('Old tx: %j', oldTx) - logger.warn('New tx: %j', newTx) - throw new E.RatchetError('Value from lamassu-machine would violate ratchet') - } - - updatedTx[fieldKey] = newField - }, _.keys(newTx)) - - return updatedTx -} - -function toObj (row) { - if (!row) return null - - const keys = _.keys(row) - let newObj = {} - - keys.forEach(key => { - const objKey = _.camelCase(key) - if (_.includes(key, ['crypto_atoms', 'fiat', 'cash_in_fee', 'cash_in_fee_crypto'])) { - newObj[objKey] = BN(row[key]) - return - } - - newObj[objKey] = row[key] - }) - - newObj.direction = 'cashIn' - - return newObj -} - -function convertBigNumFields (obj) { - const convert = value => value && value.isBigNumber - ? value.toString() - : value - - return _.mapValues(convert, obj) -} - -function pullNewBills (billRows, machineTx) { - if (_.isEmpty(machineTx.bills)) return [] - - const toBill = _.mapKeys(_.camelCase) - const bills = _.map(toBill, billRows) - - return _.differenceBy(_.get('id'), machineTx.bills, bills) -} - -const massage = _.flow(_.omit(['direction', 'cryptoNetwork', 'bills']), convertBigNumFields, _.mapKeys(_.snakeCase)) - -function insertNewBills (billRows, machineTx) { - const bills = pullNewBills(billRows, machineTx) - if (_.isEmpty(bills)) return Promise.resolve([]) - - const dbBills = _.map(massage, bills) - const columns = _.keys(dbBills[0]) - const sql = pgp.helpers.insert(dbBills, columns, 'bills') - - return db.none(sql) - .then(() => bills) -} - -function upsert (dbTx, preProcessedTx) { - if (!dbTx) { - return insert(preProcessedTx) - .then(tx => ({dbTx, tx})) - } - - return update(dbTx, diff(dbTx, preProcessedTx)) - .then(tx => ({dbTx, tx})) -} - -function insert (tx) { - const dbTx = massage(tx) - const sql = pgp.helpers.insert(dbTx, null, 'cash_in_txs') + ' returning *' - return db.one(sql) - .then(toObj) -} - -function update (tx, changes) { - if (_.isEmpty(changes)) return Promise.resolve(tx) - - const dbChanges = massage(changes) - const sql = pgp.helpers.update(dbChanges, null, 'cash_in_txs') + - pgp.as.format(' where id=$1', [tx.id]) + ' returning *' - - return db.one(sql) - .then(toObj) -} - -function registerTrades (pi, newBills) { - _.forEach(bill => pi.buy(bill), newBills) -} - -function logAction (rec, tx) { - const action = { - tx_id: tx.id, - action: rec.action || (rec.sendConfirmed ? 'sendCoins' : 'sendCoinsError'), - error: rec.error, - error_code: rec.errorCode, - tx_hash: rec.txHash - } - - const sql = pgp.helpers.insert(action, null, 'cash_in_actions') - - return db.none(sql) - .then(_.constant(rec)) -} - -function logActionById (action, _rec, txId) { - const rec = _.assign(_rec, {action, tx_id: txId}) - const sql = pgp.helpers.insert(rec, null, 'cash_in_actions') - - return db.none(sql) -} - -function isClearToSend (oldTx, newTx) { - const now = Date.now() - - return newTx.send && - (!oldTx || (!oldTx.sendPending && !oldTx.sendConfirmed)) && - (newTx.created > now - PENDING_INTERVAL_MS) -} - -function postProcess (r, pi) { - registerTrades(pi, r.newBills) - - if (!isClearToSend(r.dbTx, r.tx)) return Promise.resolve({}) - - return pi.sendCoins(r.tx) - .then(txHash => ({ - txHash, - sendConfirmed: true, - sendTime: 'now()^', - sendPending: false, - error: null, - errorCode: null - })) - .catch(err => { - // Important: We don't know what kind of error this is - // so not safe to assume that funds weren't sent. - // Therefore, don't set sendPending to false except for - // errors (like InsufficientFundsError) that are guaranteed - // not to send. - const sendPending = err.name !== 'InsufficientFundsError' - - return { - sendTime: 'now()^', - error: err.message, - errorCode: err.name, - sendPending - } - }) - .then(sendRec => logAction(sendRec, r.tx)) -} - -function preProcess (dbTx, machineTx, pi) { - // Note: The way this works is if we're clear to send, - // we mark the transaction as sendPending. - // - // If another process is trying to also mark this as sendPending - // that means that it saw the tx as sendPending=false. - // But if that's true, then it must be serialized before this - // (otherwise it would see sendPending=true), and therefore we can't - // be seeing sendPending=false (a pre-condition of clearToSend()). - // Therefore, one of the conflicting transactions will error, - // which is what we want. - return new Promise(resolve => { - if (!dbTx) return resolve(machineTx) - - if (isClearToSend(dbTx, machineTx)) { - return resolve(_.set('sendPending', true, machineTx)) - } - - return resolve(machineTx) - }) -} - -function monitorPending (settings) { - const sql = `select * from cash_in_txs - where created > now() - interval $1 - and send - and not send_confirmed - and not send_pending - and not operator_completed - order by created - limit $2` - - const processPending = row => { - const tx = toObj(row) - const pi = plugins(settings, tx.deviceId) - - return post(tx, pi) - .catch(logger.error) - } - - return db.any(sql, [PENDING_INTERVAL, MAX_PENDING]) - .then(rows => Promise.all(_.map(processPending, rows))) - .catch(logger.error) -} - -function cancel (txId) { - const updateRec = { - error: 'Operator cancel', - error_code: 'operatorCancel', - operator_completed: true - } - - return Promise.resolve() - .then(() => { - return pgp.helpers.update(updateRec, null, 'cash_in_txs') + - pgp.as.format(' where id=$1', [txId]) - }) - .then(sql => db.result(sql, false)) - .then(res => { - if (res.rowCount !== 1) throw new Error('No such tx-id') - }) - .then(() => logActionById('operatorCompleted', {}, txId)) -} diff --git a/lib/cash-in/cash-in-atomic.js b/lib/cash-in/cash-in-atomic.js new file mode 100644 index 00000000..899ce501 --- /dev/null +++ b/lib/cash-in/cash-in-atomic.js @@ -0,0 +1,83 @@ +const _ = require('lodash/fp') +const pgp = require('pg-promise')() + +const E = require('../error') + +const cashInLow = require('./cash-in-low') + +module.exports = {atomic} + +function atomic (machineTx, pi) { + const TransactionMode = pgp.txMode.TransactionMode + const isolationLevel = pgp.txMode.isolationLevel + const tmSRD = new TransactionMode({tiLevel: isolationLevel.serializable}) + + function transaction (t) { + const sql = 'select * from cash_in_txs where id=$1' + const sql2 = 'select * from bills where cash_in_txs_id=$1' + + return t.oneOrNone(sql, [machineTx.id]) + .then(row => { + if (row && row.tx_version >= machineTx.txVersion) throw new E.StaleTxError('Stale tx') + + return t.any(sql2, [machineTx.id]) + .then(billRows => { + const dbTx = cashInLow.toObj(row) + + return preProcess(dbTx, machineTx, pi) + .then(preProcessedTx => cashInLow.upsert(t, dbTx, preProcessedTx)) + .then(r => { + return insertNewBills(t, billRows, machineTx) + .then(newBills => _.set('newBills', newBills, r)) + }) + }) + }) + } + + transaction.txMode = tmSRD + + return transaction +} + +function insertNewBills (t, billRows, machineTx) { + const bills = pullNewBills(billRows, machineTx) + if (_.isEmpty(bills)) return Promise.resolve([]) + + const dbBills = _.map(cashInLow.massage, bills) + const columns = _.keys(dbBills[0]) + const sql = pgp.helpers.insert(dbBills, columns, 'bills') + + return t.none(sql) + .then(() => bills) +} + +function pullNewBills (billRows, machineTx) { + if (_.isEmpty(machineTx.bills)) return [] + + const toBill = _.mapKeys(_.camelCase) + const bills = _.map(toBill, billRows) + + return _.differenceBy(_.get('id'), machineTx.bills, bills) +} + +function preProcess (dbTx, machineTx, pi) { + // Note: The way this works is if we're clear to send, + // we mark the transaction as sendPending. + // + // If another process is trying to also mark this as sendPending + // that means that it saw the tx as sendPending=false. + // But if that's true, then it must be serialized before this + // (otherwise it would see sendPending=true), and therefore we can't + // be seeing sendPending=false (a pre-condition of clearToSend()). + // Therefore, one of the conflicting transactions will error, + // which is what we want. + return new Promise(resolve => { + if (!dbTx) return resolve(machineTx) + + if (cashInLow.isClearToSend(dbTx, machineTx)) { + return resolve(_.set('sendPending', true, machineTx)) + } + + return resolve(machineTx) + }) +} diff --git a/lib/cash-in/cash-in-low.js b/lib/cash-in/cash-in-low.js new file mode 100644 index 00000000..8bb91601 --- /dev/null +++ b/lib/cash-in/cash-in-low.js @@ -0,0 +1,137 @@ +const _ = require('lodash/fp') +const pgp = require('pg-promise')() + +const BN = require('../bn') +const T = require('../time') +const logger = require('../logger') + +const PENDING_INTERVAL_MS = 60 * T.minutes + +const massage = _.flow(_.omit(['direction', 'cryptoNetwork', 'bills']), + convertBigNumFields, _.mapKeys(_.snakeCase)) + +module.exports = {toObj, upsert, insert, update, massage, isClearToSend} + +function convertBigNumFields (obj) { + const convert = value => value && value.isBigNumber + ? value.toString() + : value + + return _.mapValues(convert, obj) +} + +function toObj (row) { + if (!row) return null + + const keys = _.keys(row) + let newObj = {} + + keys.forEach(key => { + const objKey = _.camelCase(key) + if (_.includes(key, ['crypto_atoms', 'fiat', 'cash_in_fee', 'cash_in_fee_crypto'])) { + newObj[objKey] = BN(row[key]) + return + } + + newObj[objKey] = row[key] + }) + + newObj.direction = 'cashIn' + + return newObj +} + +function upsert (t, dbTx, preProcessedTx) { + if (!dbTx) { + return insert(t, preProcessedTx) + .then(tx => ({dbTx, tx})) + } + + return update(t, dbTx, diff(dbTx, preProcessedTx)) + .then(tx => ({dbTx, tx})) +} + +function insert (t, tx) { + const dbTx = massage(tx) + const sql = pgp.helpers.insert(dbTx, null, 'cash_in_txs') + ' returning *' + return t.one(sql) + .then(toObj) +} + +function update (t, tx, changes) { + if (_.isEmpty(changes)) return Promise.resolve(tx) + + const dbChanges = massage(changes) + const sql = pgp.helpers.update(dbChanges, null, 'cash_in_txs') + + pgp.as.format(' where id=$1', [tx.id]) + ' returning *' + + return t.one(sql) + .then(toObj) +} + +function diff (oldTx, newTx) { + let updatedTx = {} + + if (!oldTx) throw new Error('oldTx must not be null') + if (!newTx) throw new Error('newTx must not be null') + + _.forEach(fieldKey => { + const oldField = oldTx[fieldKey] + const newField = newTx[fieldKey] + if (fieldKey === 'bills') return + if (_.isEqualWith(nilEqual, oldField, newField)) return + + if (!ensureRatchet(oldField, newField, fieldKey)) { + logger.warn('Value from lamassu-machine would violate ratchet [%s]', fieldKey) + logger.warn('Old tx: %j', oldTx) + logger.warn('New tx: %j', newTx) + throw new Error('Value from lamassu-machine would violate ratchet') + } + + updatedTx[fieldKey] = newField + }, _.keys(newTx)) + + return updatedTx +} + +function ensureRatchet (oldField, newField, fieldKey) { + const monotonic = ['cryptoAtoms', 'fiat', 'cashInFeeCrypto', 'send', 'sendConfirmed', 'operatorCompleted', 'timedout', 'txVersion'] + const free = ['sendPending', 'error', 'errorCode', 'customerId'] + + if (_.isNil(oldField)) return true + if (_.includes(fieldKey, monotonic)) return isMonotonic(oldField, newField, fieldKey) + + if (_.includes(fieldKey, free)) { + if (_.isNil(newField)) return false + return true + } + + if (_.isNil(newField)) return false + if (oldField.isBigNumber && newField.isBigNumber) return BN(oldField).eq(newField) + if (oldField.toString() === newField.toString()) return true + + return false +} + +function isMonotonic (oldField, newField, fieldKey) { + if (_.isNil(newField)) return false + if (_.isBoolean(oldField)) return oldField === newField || !oldField + if (oldField.isBigNumber) return oldField.lte(newField) + if (_.isNumber(oldField)) return oldField <= newField + + throw new Error(`Unexpected value [${fieldKey}]: ${oldField}, ${newField}`) +} + +function nilEqual (a, b) { + if (_.isNil(a) && _.isNil(b)) return true + + return undefined +} + +function isClearToSend (oldTx, newTx) { + const now = Date.now() + + return newTx.send && + (!oldTx || (!oldTx.sendPending && !oldTx.sendConfirmed)) && + (newTx.created > now - PENDING_INTERVAL_MS) +} diff --git a/lib/cash-in/cash-in-tx.js b/lib/cash-in/cash-in-tx.js new file mode 100644 index 00000000..ce74ac0c --- /dev/null +++ b/lib/cash-in/cash-in-tx.js @@ -0,0 +1,126 @@ +const _ = require('lodash/fp') +const pgp = require('pg-promise')() +const pEachSeries = require('p-each-series') + +const db = require('../db') +const plugins = require('../plugins') +const logger = require('../logger') + +const cashInAtomic = require('./cash-in-atomic') +const cashInLow = require('./cash-in-low') + +const PENDING_INTERVAL = '60 minutes' +const MAX_PENDING = 10 + +module.exports = {post, monitorPending, cancel, PENDING_INTERVAL} + +function post (machineTx, pi) { + return db.tx(cashInAtomic.atomic(machineTx, pi)) + .then(r => { + const updatedTx = r.tx + + return postProcess(r, pi) + .then(changes => cashInLow.update(db, updatedTx, changes)) + .then(tx => _.set('bills', machineTx.bills, tx)) + }) +} + +function registerTrades (pi, newBills) { + _.forEach(bill => pi.buy(bill), newBills) +} + +function logAction (rec, tx) { + const action = { + tx_id: tx.id, + action: rec.action || (rec.sendConfirmed ? 'sendCoins' : 'sendCoinsError'), + error: rec.error, + error_code: rec.errorCode, + tx_hash: rec.txHash + } + + const sql = pgp.helpers.insert(action, null, 'cash_in_actions') + + return db.none(sql) + .then(_.constant(rec)) +} + +function logActionById (action, _rec, txId) { + const rec = _.assign(_rec, {action, tx_id: txId}) + const sql = pgp.helpers.insert(rec, null, 'cash_in_actions') + + return db.none(sql) +} + +function postProcess (r, pi) { + registerTrades(pi, r.newBills) + + if (!cashInLow.isClearToSend(r.dbTx, r.tx)) return Promise.resolve({}) + + return pi.sendCoins(r.tx) + .then(txHash => ({ + txHash, + sendConfirmed: true, + sendTime: 'now()^', + sendPending: false, + error: null, + errorCode: null + })) + .catch(err => { + // Important: We don't know what kind of error this is + // so not safe to assume that funds weren't sent. + // Therefore, don't set sendPending to false except for + // errors (like InsufficientFundsError) that are guaranteed + // not to send. + const sendPending = err.name !== 'InsufficientFundsError' + + return { + sendTime: 'now()^', + error: err.message, + errorCode: err.name, + sendPending + } + }) + .then(sendRec => logAction(sendRec, r.tx)) +} + +function monitorPending (settings) { + const sql = `select * from cash_in_txs + where created > now() - interval $1 + and send + and not send_confirmed + and not send_pending + and not operator_completed + order by created + limit $2` + + const processPending = row => { + const tx = cashInLow.toObj(row) + const pi = plugins(settings, tx.deviceId) + + return post(tx, pi) + .catch(logger.error) + } + + return db.any(sql, [PENDING_INTERVAL, MAX_PENDING]) + .then(rows => pEachSeries(rows, row => processPending(row))) + .catch(logger.error) +} + +function cancel (txId) { + const updateRec = { + error: 'Operator cancel', + error_code: 'operatorCancel', + operator_completed: true + } + + return Promise.resolve() + .then(() => { + return pgp.helpers.update(updateRec, null, 'cash_in_txs') + + pgp.as.format(' where id=$1', [txId]) + }) + .then(sql => db.result(sql, false)) + .then(res => { + if (res.rowCount !== 1) throw new Error('No such tx-id') + }) + .then(() => logActionById('operatorCompleted', {}, txId)) +} diff --git a/lib/cash-out-tx.js b/lib/cash-out-tx.js deleted file mode 100644 index d13d0953..00000000 --- a/lib/cash-out-tx.js +++ /dev/null @@ -1,410 +0,0 @@ -const _ = require('lodash/fp') -const pgp = require('pg-promise')() - -const db = require('./db') -const billMath = require('./bill-math') -const T = require('./time') -const logger = require('./logger') -const plugins = require('./plugins') -const helper = require('./cash-out-helper') -const socket = require('./socket-client') -const E = require('./error') - -module.exports = { - post, - monitorLiveIncoming, - monitorStaleIncoming, - monitorUnnotified, - cancel -} - -const UPDATEABLE_FIELDS = ['txHash', 'txVersion', 'status', 'dispense', 'dispenseConfirmed', - 'notified', 'redeem', 'phone', 'error', 'swept', 'publishedAt', 'confirmedAt'] - -const STALE_INCOMING_TX_AGE = T.week -const STALE_LIVE_INCOMING_TX_AGE = 10 * T.minutes -const MAX_NOTIFY_AGE = 2 * T.days -const MIN_NOTIFY_AGE = 5 * T.minutes -const INSUFFICIENT_FUNDS_CODE = 570 - -const toObj = helper.toObj -const toDb = helper.toDb - -function httpError (msg, code) { - const err = new Error(msg) - err.name = 'HTTPError' - err.code = code || 500 - - return err -} - -function selfPost (tx, pi) { - return post(tx, pi, false) -} - -function post (tx, pi, fromClient = true) { - const TransactionMode = pgp.txMode.TransactionMode - const isolationLevel = pgp.txMode.isolationLevel - const tmSRD = new TransactionMode({tiLevel: isolationLevel.serializable}) - - function transaction (t) { - const sql = 'select * from cash_out_txs where id=$1' - - return t.oneOrNone(sql, [tx.id]) - .then(toObj) - .then(oldTx => { - const isStale = fromClient && oldTx && (oldTx.txVersion >= tx.txVersion) - if (isStale) throw new E.StaleTxError('Stale tx') - - return preProcess(oldTx, tx, pi) - .then(preProcessedTx => upsert(oldTx, preProcessedTx)) - }) - } - - transaction.txMode = tmSRD - - return db.tx(transaction) - .then(txVector => { - const [, newTx] = txVector - return postProcess(txVector, pi) - .then(changes => update(newTx, changes)) - }) -} - -function logError (action, err, tx) { - return logAction(action, { - error: err.message, - error_code: err.name - }, tx) -} - -function mapDispense (tx) { - const bills = tx.bills - - if (_.isEmpty(bills)) return {} - - return { - provisioned_1: bills[0].provisioned, - provisioned_2: bills[1].provisioned, - dispensed_1: bills[0].dispensed, - dispensed_2: bills[1].dispensed, - rejected_1: bills[0].rejected, - rejected_2: bills[1].rejected, - denomination_1: bills[0].denomination, - denomination_2: bills[1].denomination - } -} - -function logDispense (tx) { - const baseRec = {error: tx.error, error_code: tx.errorCode} - const rec = _.merge(mapDispense(tx), baseRec) - const action = _.isEmpty(tx.error) ? 'dispense' : 'dispenseError' - return logAction(action, rec, tx) -} - -function logActionById (action, _rec, txId) { - const rec = _.assign(_rec, {action, tx_id: txId, redeem: false}) - const sql = pgp.helpers.insert(rec, null, 'cash_out_actions') - - return db.none(sql) -} - -function logAction (action, _rec, tx) { - const rec = _.assign(_rec, {action, tx_id: tx.id, redeem: !!tx.redeem}) - const sql = pgp.helpers.insert(rec, null, 'cash_out_actions') - - return db.none(sql) - .then(_.constant(tx)) -} - -function nilEqual (a, b) { - if (_.isNil(a) && _.isNil(b)) return true - - return undefined -} - -function diff (oldTx, newTx) { - let updatedTx = {} - - UPDATEABLE_FIELDS.forEach(fieldKey => { - if (oldTx && _.isEqualWith(nilEqual, oldTx[fieldKey], newTx[fieldKey])) return - - // We never null out an existing field - if (oldTx && _.isNil(newTx[fieldKey])) return - - updatedTx[fieldKey] = newTx[fieldKey] - }) - - return updatedTx -} - -function upsert (oldTx, tx) { - if (!oldTx) { - return insert(tx) - .then(newTx => [oldTx, newTx]) - } - - return update(tx, diff(oldTx, tx)) - .then(newTx => [oldTx, newTx]) -} - -function insert (tx) { - const dbTx = toDb(tx) - - const sql = pgp.helpers.insert(dbTx, null, 'cash_out_txs') + ' returning *' - return db.one(sql) - .then(toObj) -} - -function update (tx, changes) { - if (_.isEmpty(changes)) return Promise.resolve(tx) - - const dbChanges = toDb(changes) - const sql = pgp.helpers.update(dbChanges, null, 'cash_out_txs') + - pgp.as.format(' where id=$1', [tx.id]) - - const newTx = _.merge(tx, changes) - - return db.none(sql) - .then(() => newTx) -} - -function nextHd (isHd, tx) { - if (!isHd) return Promise.resolve(tx) - - return db.one("select nextval('hd_indices_seq') as hd_index") - .then(row => _.set('hdIndex', row.hd_index, tx)) -} - -function dispenseOccurred (bills) { - return _.every(_.overEvery([_.has('dispensed'), _.has('rejected')]), bills) -} - -function updateCassettes (tx) { - if (!dispenseOccurred(tx.bills)) return Promise.resolve() - - const sql = `update devices set - cassette1 = cassette1 - $1, - cassette2 = cassette2 - $2 - where device_id = $3 - returning cassette1, cassette2` - - const values = [ - tx.bills[0].dispensed + tx.bills[0].rejected, - tx.bills[1].dispensed + tx.bills[1].rejected, - tx.deviceId - ] - - return db.one(sql, values) - .then(r => socket.emit(_.assign(r, {op: 'cassetteUpdate', deviceId: tx.deviceId}))) -} - -function wasJustAuthorized (oldTx, newTx, isZeroConf) { - const isAuthorized = () => _.includes(oldTx.status, ['notSeen', 'published']) && - _.includes(newTx.status, ['authorized', 'instant', 'confirmed']) - - const isConfirmed = () => _.includes(oldTx.status, ['notSeen', 'published', 'authorized']) && - _.includes(newTx.status, ['instant', 'confirmed']) - - return isZeroConf ? isAuthorized() : isConfirmed() -} - -function preProcess (oldTx, newTx, pi) { - if (!oldTx) { - return pi.isHd(newTx) - .then(isHd => nextHd(isHd, newTx)) - .then(newTxHd => { - return pi.newAddress(newTxHd) - .then(_.set('toAddress', _, newTxHd)) - .then(_.unset('isLightning')) - }) - .then(addressedTx => { - const rec = {to_address: addressedTx.toAddress} - return logAction('provisionAddress', rec, addressedTx) - }) - .catch(err => { - return logError('provisionAddress', err, newTx) - .then(() => { throw err }) - }) - } - - return Promise.resolve(updateStatus(oldTx, newTx)) - .then(updatedTx => { - if (updatedTx.status !== oldTx.status) { - const isZeroConf = pi.isZeroConf(updatedTx) - if (wasJustAuthorized(oldTx, updatedTx, isZeroConf)) pi.sell(updatedTx) - - const rec = { - to_address: updatedTx.toAddress, - tx_hash: updatedTx.txHash - } - - return logAction(updatedTx.status, rec, updatedTx) - } - - const hasError = !oldTx.error && newTx.error - const hasDispenseOccurred = !dispenseOccurred(oldTx.bills) && dispenseOccurred(newTx.bills) - - if (hasError || hasDispenseOccurred) { - return logDispense(updatedTx) - .then(updateCassettes(updatedTx)) - } - - if (!oldTx.phone && newTx.phone) { - return logAction('addPhone', {}, updatedTx) - } - - if (!oldTx.redeem && newTx.redeem) { - return logAction('redeemLater', {}, updatedTx) - } - - return updatedTx - }) -} - -function postProcess (txVector, pi) { - const [oldTx, newTx] = txVector - - if ((newTx.dispense && !oldTx.dispense) || (newTx.redeem && !oldTx.redeem)) { - return pi.buildAvailableCassettes(newTx.id) - .then(cassettes => { - const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat) - - if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE) - return bills - }) - .then(bills => { - const provisioned1 = bills[0].provisioned - const provisioned2 = bills[1].provisioned - const denomination1 = bills[0].denomination - const denomination2 = bills[1].denomination - - const rec = { - provisioned_1: provisioned1, - provisioned_2: provisioned2, - denomination_1: denomination1, - denomination_2: denomination2 - } - - return logAction('provisionNotes', rec, newTx) - .then(_.constant({bills})) - }) - .catch(err => { - return logError('provisionNotesError', err, newTx) - .then(() => { throw err }) - }) - } - - return Promise.resolve({}) -} - -function isPublished (status) { - return _.includes(status, ['published', 'rejected', 'authorized', 'instant', 'confirmed']) -} - -function isConfirmed (status) { - return status === 'confirmed' -} - -function updateStatus (oldTx, newTx) { - const oldStatus = oldTx.status - const newStatus = ratchetStatus(oldStatus, newTx.status) - - const publishedAt = !oldTx.publishedAt && isPublished(newStatus) - ? 'now()^' - : undefined - - const confirmedAt = !oldTx.confirmedAt && isConfirmed(newStatus) - ? 'now()^' - : undefined - - const updateRec = { - publishedAt, - confirmedAt, - status: newStatus - } - - return _.merge(newTx, updateRec) -} - -function ratchetStatus (oldStatus, newStatus) { - const statusOrder = ['notSeen', 'published', 'rejected', - 'authorized', 'instant', 'confirmed'] - - if (oldStatus === newStatus) return oldStatus - if (newStatus === 'insufficientFunds') return newStatus - - const idx = Math.max(statusOrder.indexOf(oldStatus), statusOrder.indexOf(newStatus)) - return statusOrder[idx] -} - -function fetchOpenTxs (statuses, age) { - const sql = `select * - from cash_out_txs - where ((extract(epoch from (now() - created))) * 1000)<$1 - and status in ($2^)` - - const statusClause = _.map(pgp.as.text, statuses).join(',') - - return db.any(sql, [age, statusClause]) - .then(rows => rows.map(toObj)) -} - -function processTxStatus (tx, settings) { - const pi = plugins(settings, tx.deviceId) - - return pi.getStatus(tx) - .then(res => _.assign(tx, {status: res.status})) - .then(_tx => selfPost(_tx, pi)) -} - -function monitorLiveIncoming (settings) { - const statuses = ['notSeen', 'published', 'insufficientFunds'] - - return fetchOpenTxs(statuses, STALE_LIVE_INCOMING_TX_AGE) - .then(txs => Promise.all(txs.map(tx => processTxStatus(tx, settings)))) - .catch(logger.error) -} - -function monitorStaleIncoming (settings) { - const statuses = ['notSeen', 'published', 'authorized', 'instant', 'rejected', 'insufficientFunds'] - - return fetchOpenTxs(statuses, STALE_INCOMING_TX_AGE) - .then(txs => Promise.all(txs.map(tx => processTxStatus(tx, settings)))) - .catch(logger.error) -} - -function monitorUnnotified (settings) { - const sql = `select * - from cash_out_txs - where ((extract(epoch from (now() - created))) * 1000)<$1 - and notified=$2 and dispense=$3 - and phone is not null - and status in ('instant', 'confirmed') - and (redeem=$4 or ((extract(epoch from (now() - created))) * 1000)>$5)` - - const notify = tx => plugins(settings, tx.deviceId).notifyConfirmation(tx) - return db.any(sql, [MAX_NOTIFY_AGE, false, false, true, MIN_NOTIFY_AGE]) - .then(rows => _.map(toObj, rows)) - .then(txs => Promise.all(txs.map(notify))) - .catch(logger.error) -} - -function cancel (txId) { - const updateRec = { - error: 'Operator cancel', - error_code: 'operatorCancel', - dispense: true - } - - return Promise.resolve() - .then(() => { - return pgp.helpers.update(updateRec, null, 'cash_out_txs') + - pgp.as.format(' where id=$1', [txId]) - }) - .then(sql => db.result(sql, false)) - .then(res => { - if (res.rowCount !== 1) throw new Error('No such tx-id') - }) - .then(() => logActionById('operatorCompleted', {}, txId)) -} diff --git a/lib/cash-out/cash-out-actions.js b/lib/cash-out/cash-out-actions.js new file mode 100644 index 00000000..8899ac09 --- /dev/null +++ b/lib/cash-out/cash-out-actions.js @@ -0,0 +1,50 @@ +const _ = require('lodash/fp') +const pgp = require('pg-promise')() + +module.exports = {logDispense, logActionById, logAction, logError} + +function logDispense (t, tx) { + const baseRec = {error: tx.error, error_code: tx.errorCode} + const rec = _.merge(mapDispense(tx), baseRec) + const action = tx.dispenseConfirmed ? 'dispense' : 'dispenseError' + return logAction(t, action, rec, tx) +} + +function logActionById (t, action, _rec, txId) { + const rec = _.assign(_rec, {action, tx_id: txId, redeem: false}) + const sql = pgp.helpers.insert(rec, null, 'cash_out_actions') + + return t.none(sql) +} + +function logAction (t, action, _rec, tx) { + const rec = _.assign(_rec, {action, tx_id: tx.id, redeem: !!tx.redeem}) + const sql = pgp.helpers.insert(rec, null, 'cash_out_actions') + + return t.none(sql) + .then(_.constant(tx)) +} + +function logError (t, action, err, tx) { + return logAction(t, action, { + error: err.message, + error_code: err.name + }, tx) +} + +function mapDispense (tx) { + const bills = tx.bills + + if (_.isEmpty(bills)) return {} + + return { + provisioned_1: bills[0].provisioned, + provisioned_2: bills[1].provisioned, + dispensed_1: bills[0].dispensed, + dispensed_2: bills[1].dispensed, + rejected_1: bills[0].rejected, + rejected_2: bills[1].rejected, + denomination_1: bills[0].denomination, + denomination_2: bills[1].denomination + } +} diff --git a/lib/cash-out/cash-out-atomic.js b/lib/cash-out/cash-out-atomic.js new file mode 100644 index 00000000..acb24741 --- /dev/null +++ b/lib/cash-out/cash-out-atomic.js @@ -0,0 +1,160 @@ +const _ = require('lodash/fp') +const pgp = require('pg-promise')() + +const E = require('../error') +const socket = require('../socket-client') + +const helper = require('./cash-out-helper') +const cashOutActions = require('./cash-out-actions') +const cashOutLow = require('./cash-out-low') + +const toObj = helper.toObj + +module.exports = {atomic} + +function atomic (tx, pi, fromClient) { + const TransactionMode = pgp.txMode.TransactionMode + const isolationLevel = pgp.txMode.isolationLevel + const tmSRD = new TransactionMode({tiLevel: isolationLevel.serializable}) + + function transaction (t) { + const sql = 'select * from cash_out_txs where id=$1' + + return t.oneOrNone(sql, [tx.id]) + .then(toObj) + .then(oldTx => { + const isStale = fromClient && oldTx && (oldTx.txVersion >= tx.txVersion) + if (isStale) throw new E.StaleTxError('Stale tx') + + return preProcess(t, oldTx, tx, pi) + .then(preProcessedTx => cashOutLow.upsert(t, oldTx, preProcessedTx)) + }) + } + + transaction.txMode = tmSRD + + return transaction +} + +function preProcess (t, oldTx, newTx, pi) { + if (!oldTx) { + return pi.isHd(newTx) + .then(isHd => nextHd(t, isHd, newTx)) + .then(newTxHd => { + return pi.newAddress(newTxHd) + .then(_.set('toAddress', _, newTxHd)) + }) + .then(addressedTx => { + const rec = {to_address: addressedTx.toAddress} + return cashOutActions.logAction(t, 'provisionAddress', rec, addressedTx) + }) + .catch(err => { + return cashOutActions.logError(t, 'provisionAddress', err, newTx) + .then(() => { throw err }) + }) + } + + return Promise.resolve(updateStatus(oldTx, newTx)) + .then(updatedTx => { + if (updatedTx.status !== oldTx.status) { + const isZeroConf = pi.isZeroConf(updatedTx) + if (wasJustAuthorized(oldTx, updatedTx, isZeroConf)) pi.sell(updatedTx) + + const rec = { + to_address: updatedTx.toAddress, + tx_hash: updatedTx.txHash + } + + return cashOutActions.logAction(t, updatedTx.status, rec, updatedTx) + } + + if (!oldTx.dispenseConfirmed && updatedTx.dispenseConfirmed) { + return cashOutActions.logDispense(t, updatedTx) + .then(updateCassettes(t, updatedTx)) + } + + if (!oldTx.phone && newTx.phone) { + return cashOutActions.logAction(t, 'addPhone', {}, updatedTx) + } + + if (!oldTx.redeem && newTx.redeem) { + return cashOutActions.logAction(t, 'redeemLater', {}, updatedTx) + } + + return updatedTx + }) +} + +function nextHd (t, isHd, tx) { + if (!isHd) return Promise.resolve(tx) + + return t.one("select nextval('hd_indices_seq') as hd_index") + .then(row => _.set('hdIndex', row.hd_index, tx)) +} + +function updateCassettes (t, tx) { + const sql = `update devices set + cassette1 = cassette1 - $1, + cassette2 = cassette2 - $2 + where device_id = $3 + returning cassette1, cassette2` + + const values = [ + tx.bills[0].dispensed + tx.bills[0].rejected, + tx.bills[1].dispensed + tx.bills[1].rejected, + tx.deviceId + ] + + return t.one(sql, values) + .then(r => socket.emit(_.assign(r, {op: 'cassetteUpdate', deviceId: tx.deviceId}))) +} + +function wasJustAuthorized (oldTx, newTx, isZeroConf) { + const isAuthorized = () => _.includes(oldTx.status, ['notSeen', 'published']) && + _.includes(newTx.status, ['authorized', 'instant', 'confirmed']) + + const isConfirmed = () => _.includes(oldTx.status, ['notSeen', 'published', 'authorized']) && + _.includes(newTx.status, ['instant', 'confirmed']) + + return isZeroConf ? isAuthorized() : isConfirmed() +} + +function isPublished (status) { + return _.includes(status, ['published', 'rejected', 'authorized', 'instant', 'confirmed']) +} + +function isConfirmed (status) { + return status === 'confirmed' +} + +function updateStatus (oldTx, newTx) { + const oldStatus = oldTx.status + const newStatus = ratchetStatus(oldStatus, newTx.status) + + const publishedAt = !oldTx.publishedAt && isPublished(newStatus) + ? 'now()^' + : undefined + + const confirmedAt = !oldTx.confirmedAt && isConfirmed(newStatus) + ? 'now()^' + : undefined + + const updateRec = { + publishedAt, + confirmedAt, + status: newStatus + } + + return _.merge(newTx, updateRec) +} + +function ratchetStatus (oldStatus, newStatus) { + const statusOrder = ['notSeen', 'published', 'rejected', + 'authorized', 'instant', 'confirmed'] + + if (oldStatus === newStatus) return oldStatus + if (newStatus === 'insufficientFunds') return newStatus + + const idx = Math.max(statusOrder.indexOf(oldStatus), statusOrder.indexOf(newStatus)) + return statusOrder[idx] +} diff --git a/lib/cash-out-helper.js b/lib/cash-out/cash-out-helper.js similarity index 96% rename from lib/cash-out-helper.js rename to lib/cash-out/cash-out-helper.js index b5edd152..17fa53d7 100644 --- a/lib/cash-out-helper.js +++ b/lib/cash-out/cash-out-helper.js @@ -1,8 +1,8 @@ const _ = require('lodash/fp') -const db = require('./db') -const T = require('./time') -const BN = require('./bn') +const db = require('../db') +const T = require('../time') +const BN = require('../bn') const REDEEMABLE_AGE = T.day diff --git a/lib/cash-out/cash-out-low.js b/lib/cash-out/cash-out-low.js new file mode 100644 index 00000000..1040b5db --- /dev/null +++ b/lib/cash-out/cash-out-low.js @@ -0,0 +1,64 @@ +const _ = require('lodash/fp') +const pgp = require('pg-promise')() + +const helper = require('./cash-out-helper') + +const toDb = helper.toDb +const toObj = helper.toObj + +const UPDATEABLE_FIELDS = ['txHash', 'txVersion', 'status', 'dispense', 'dispenseConfirmed', + 'notified', 'redeem', 'phone', 'error', 'swept', 'publishedAt', 'confirmedAt'] + +module.exports = {upsert, update, insert} + +function upsert (t, oldTx, tx) { + if (!oldTx) { + return insert(t, tx) + .then(newTx => [oldTx, newTx]) + } + + return update(t, tx, diff(oldTx, tx)) + .then(newTx => [oldTx, newTx]) +} + +function insert (t, tx) { + const dbTx = toDb(tx) + + const sql = pgp.helpers.insert(dbTx, null, 'cash_out_txs') + ' returning *' + return t.one(sql) + .then(toObj) +} + +function update (t, tx, changes) { + if (_.isEmpty(changes)) return Promise.resolve(tx) + + const dbChanges = toDb(changes) + const sql = pgp.helpers.update(dbChanges, null, 'cash_out_txs') + + pgp.as.format(' where id=$1', [tx.id]) + + const newTx = _.merge(tx, changes) + + return t.none(sql) + .then(() => newTx) +} + +function diff (oldTx, newTx) { + let updatedTx = {} + + UPDATEABLE_FIELDS.forEach(fieldKey => { + if (oldTx && _.isEqualWith(nilEqual, oldTx[fieldKey], newTx[fieldKey])) return + + // We never null out an existing field + if (oldTx && _.isNil(newTx[fieldKey])) return + + updatedTx[fieldKey] = newTx[fieldKey] + }) + + return updatedTx +} + +function nilEqual (a, b) { + if (_.isNil(a) && _.isNil(b)) return true + + return undefined +} diff --git a/lib/cash-out/cash-out-tx.js b/lib/cash-out/cash-out-tx.js new file mode 100644 index 00000000..27b3e0af --- /dev/null +++ b/lib/cash-out/cash-out-tx.js @@ -0,0 +1,158 @@ +const _ = require('lodash/fp') +const pgp = require('pg-promise')() +const pEachSeries = require('p-each-series') + +const db = require('../db') +const billMath = require('../bill-math') +const T = require('../time') +const logger = require('../logger') +const plugins = require('../plugins') + +const helper = require('./cash-out-helper') +const cashOutAtomic = require('./cash-out-atomic') +const cashOutActions = require('./cash-out-actions') +const cashOutLow = require('./cash-out-low') + +module.exports = { + post, + monitorLiveIncoming, + monitorStaleIncoming, + monitorUnnotified, + cancel +} + +const STALE_INCOMING_TX_AGE = T.week +const STALE_LIVE_INCOMING_TX_AGE = 10 * T.minutes +const MAX_NOTIFY_AGE = 2 * T.days +const MIN_NOTIFY_AGE = 5 * T.minutes +const INSUFFICIENT_FUNDS_CODE = 570 + +const toObj = helper.toObj + +function httpError (msg, code) { + const err = new Error(msg) + err.name = 'HTTPError' + err.code = code || 500 + + return err +} + +function selfPost (tx, pi) { + return post(tx, pi, false) +} + +function post (tx, pi, fromClient = true) { + return db.tx(cashOutAtomic.atomic(tx, pi, fromClient)) + .then(txVector => { + const [, newTx] = txVector + return postProcess(txVector, pi) + .then(changes => cashOutLow.update(db, newTx, changes)) + }) +} + +function postProcess (txVector, pi) { + const [oldTx, newTx] = txVector + + if ((newTx.dispense && !oldTx.dispense) || (newTx.redeem && !oldTx.redeem)) { + return pi.buildAvailableCassettes(newTx.id) + .then(cassettes => { + const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat) + + if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE) + return bills + }) + .then(bills => { + const provisioned1 = bills[0].provisioned + const provisioned2 = bills[1].provisioned + const denomination1 = bills[0].denomination + const denomination2 = bills[1].denomination + + const rec = { + provisioned_1: provisioned1, + provisioned_2: provisioned2, + denomination_1: denomination1, + denomination_2: denomination2 + } + + return cashOutActions.logAction(db, 'provisionNotes', rec, newTx) + .then(_.constant({bills})) + }) + .catch(err => { + return cashOutActions.logError(db, 'provisionNotesError', err, newTx) + .then(() => { throw err }) + }) + } + + return Promise.resolve({}) +} + +function fetchOpenTxs (statuses, age) { + const sql = `select * + from cash_out_txs + where ((extract(epoch from (now() - created))) * 1000)<$1 + and status in ($2^)` + + const statusClause = _.map(pgp.as.text, statuses).join(',') + + return db.any(sql, [age, statusClause]) + .then(rows => rows.map(toObj)) +} + +function processTxStatus (tx, settings) { + const pi = plugins(settings, tx.deviceId) + + return pi.getStatus(tx) + .then(res => _.assign(tx, {status: res.status})) + .then(_tx => selfPost(_tx, pi)) +} + +function monitorLiveIncoming (settings) { + const statuses = ['notSeen', 'published', 'insufficientFunds'] + + return fetchOpenTxs(statuses, STALE_LIVE_INCOMING_TX_AGE) + .then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings))) + .catch(logger.error) +} + +function monitorStaleIncoming (settings) { + const statuses = ['notSeen', 'published', 'authorized', 'instant', 'rejected', 'insufficientFunds'] + + return fetchOpenTxs(statuses, STALE_INCOMING_TX_AGE) + .then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings))) + .catch(logger.error) +} + +function monitorUnnotified (settings) { + const sql = `select * + from cash_out_txs + where ((extract(epoch from (now() - created))) * 1000)<$1 + and notified=$2 and dispense=$3 + and phone is not null + and status in ('instant', 'confirmed') + and (redeem=$4 or ((extract(epoch from (now() - created))) * 1000)>$5)` + + const notify = tx => plugins(settings, tx.deviceId).notifyConfirmation(tx) + return db.any(sql, [MAX_NOTIFY_AGE, false, false, true, MIN_NOTIFY_AGE]) + .then(rows => _.map(toObj, rows)) + .then(txs => Promise.all(txs.map(notify))) + .catch(logger.error) +} + +function cancel (txId) { + const updateRec = { + error: 'Operator cancel', + error_code: 'operatorCancel', + dispense: true + } + + return Promise.resolve() + .then(() => { + return pgp.helpers.update(updateRec, null, 'cash_out_txs') + + pgp.as.format(' where id=$1', [txId]) + }) + .then(sql => db.result(sql, false)) + .then(res => { + if (res.rowCount !== 1) throw new Error('No such tx-id') + }) + .then(() => cashOutActions.logActionById(db, 'operatorCompleted', {}, txId)) +} diff --git a/lib/plugins.js b/lib/plugins.js index 68594b42..480f69c6 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -16,7 +16,7 @@ const wallet = require('./wallet') const exchange = require('./exchange') const sms = require('./sms') const email = require('./email') -const cashOutHelper = require('./cash-out-helper') +const cashOutHelper = require('./cash-out/cash-out-helper') const machineLoader = require('./machine-loader') const coinUtils = require('./coin-utils') diff --git a/lib/plugins/wallet/mock-wallet/mock-wallet.js b/lib/plugins/wallet/mock-wallet/mock-wallet.js index aed5f8a9..e91674b5 100644 --- a/lib/plugins/wallet/mock-wallet/mock-wallet.js +++ b/lib/plugins/wallet/mock-wallet/mock-wallet.js @@ -83,7 +83,7 @@ function getStatus (account, toAddress, cryptoAtoms, cryptoCode) { if (elapsed < AUTHORIZE_TIME) return Promise.resolve({status: 'published'}) if (elapsed < CONFIRM_TIME) return Promise.resolve({status: 'authorized'}) - console.log('[%s] DEBUG: Mock wallet has confirmed transaction', cryptoCode) + console.log('[%s] DEBUG: Mock wallet has confirmed transaction [%s]', cryptoCode, toAddress.slice(0, 5)) return Promise.resolve({status: 'confirmed'}) } diff --git a/lib/poller.js b/lib/poller.js index e11806d0..af0f9829 100644 --- a/lib/poller.js +++ b/lib/poller.js @@ -2,8 +2,8 @@ const plugins = require('./plugins') const notifier = require('./notifier') const T = require('./time') const logger = require('./logger') -const cashOutTx = require('./cash-out-tx') -const cashInTx = require('./cash-in-tx') +const cashOutTx = require('./cash-out/cash-out-tx') +const cashInTx = require('./cash-in/cash-in-tx') const INCOMING_TX_INTERVAL = 30 * T.seconds const LIVE_INCOMING_TX_INTERVAL = 5 * T.seconds diff --git a/lib/tx.js b/lib/tx.js index 32c0bc70..f6ff3d49 100644 --- a/lib/tx.js +++ b/lib/tx.js @@ -1,7 +1,7 @@ const _ = require('lodash/fp') const BN = require('./bn') -const CashInTx = require('./cash-in-tx') -const CashOutTx = require('./cash-out-tx') +const CashInTx = require('./cash-in/cash-in-tx') +const CashOutTx = require('./cash-out/cash-out-tx') function process (tx, pi) { const mtx = massage(tx) diff --git a/package.json b/package.json index 9838f139..57506fc4 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ "node-hkdf-sync": "^1.0.0", "node-mailjet": "^3.2.1", "numeral": "^2.0.3", - "pg-native": "^2.0.1", - "pg-promise": "^6.3.7", + "pg-native": "^2.2.0", + "pg-promise": "^7.4.1", "pify": "^3.0.0", "pretty-ms": "^2.1.0", "promise-sequential": "^1.1.1", diff --git a/tools/modify.js b/tools/modify.js index d5c34611..307744f3 100644 --- a/tools/modify.js +++ b/tools/modify.js @@ -1,7 +1,7 @@ const settingsLoader = require('../lib/settings-loader') const fields = [ - settingsLoader.configDeleteField({crypto: 'ETH', machine: 'global'}, 'exchange') + settingsLoader.configDeleteField({crypto: 'BTC', machine: 'global'}, 'wallet') ] settingsLoader.modifyConfig(fields) From 777d3fea12fa3137520a40e6014afba7f758e674 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 2 Feb 2018 00:12:10 +0200 Subject: [PATCH 03/36] update packages --- package-lock.json | 209 +++++++++++++++++++++++++++------------------- package.json | 1 + 2 files changed, 125 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index 20f97406..28910626 100644 --- a/package-lock.json +++ b/package-lock.json @@ -199,11 +199,6 @@ "micromatch": "2.3.11" } }, - "ap": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ap/-/ap-0.2.0.tgz", - "integrity": "sha1-rglCYAspkS8NKxTsYMRejzMLYRA=" - }, "aproba": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.2.tgz", @@ -1190,7 +1185,7 @@ } }, "bitcore-lib-cash": { - "version": "git+https://github.com/bitpay/bitcore-lib.git#2b494651c95a31a2a936b28673fb8fc4a76f8099", + "version": "git+https://github.com/bitpay/bitcore-lib.git#8f2633b5c1b5554f29847344c477f10d39c68455", "requires": { "bn.js": "4.11.8", "bs58": "4.0.1", @@ -3671,11 +3666,6 @@ "is-property": "1.0.2" } }, - "generic-pool": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.3.tgz", - "integrity": "sha1-eAw29p360FpaBF3Te+etyhGk9v8=" - }, "get-port": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.1.0.tgz", @@ -5286,6 +5276,11 @@ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.3.1.tgz", "integrity": "sha1-hhIoAhQvCChQKg0d7h2V4lO7AkM=" }, + "js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -5678,11 +5673,6 @@ } } }, - "manakin": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/manakin/-/manakin-0.4.7.tgz", - "integrity": "sha1-QcpEm1W+qcTE/s7Dk7nE1jgY/D8=" - }, "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", @@ -6182,6 +6172,14 @@ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", "integrity": "sha1-ueEjgAvOu3rBOkeb4ZW1B7mNMPo=" }, + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "requires": { + "p-reduce": "1.0.0" + } + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -6202,6 +6200,11 @@ "p-limit": "1.1.0" } }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" + }, "p-timeout": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.0.tgz", @@ -6417,65 +6420,35 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" }, - "pg-connection-string": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", - "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" - }, - "pg-minify": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-0.5.2.tgz", - "integrity": "sha512-NxolpovLgwjQG/daDYSCiHxJfIlo10DtGPUTVyF6ay5KxKhiJPGN8bqOxHje1MYEoefmtqdTLWKnDxSyvv0ykw==" - }, - "pg-native": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-2.0.1.tgz", - "integrity": "sha1-9xOTYOAB9SL0QZLcLBn7alXizxg=", + "pg": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.4.1.tgz", + "integrity": "sha512-Pi5qYuXro5PAD9xXx8h7bFtmHgAQEG6/SCNyi7gS3rvb/ZQYDmxKchfB0zYtiSJNWq9iXTsYsHjrM+21eBcN1A==", "requires": { - "libpq": "1.8.7", - "pg-types": "1.12.0", - "readable-stream": "2.3.3" - } - }, - "pg-pool": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-1.8.0.tgz", - "integrity": "sha1-9+xzgkw3oD8Hb1G/33DjQBR8Tzc=", - "requires": { - "generic-pool": "2.4.3", - "object-assign": "4.1.0" + "buffer-writer": "1.0.1", + "js-string-escape": "1.0.1", + "packet-reader": "0.3.1", + "pg-connection-string": "0.1.3", + "pg-pool": "2.0.3", + "pg-types": "1.12.1", + "pgpass": "1.0.2", + "semver": "4.3.2" }, "dependencies": { - "object-assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", - "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=" - } - } - }, - "pg-promise": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-6.3.7.tgz", - "integrity": "sha512-qUGvluqVeJXIg8viJG2Oqd2iHJ0CNKuTvmRKdOMFjcbotglsj/MFMCMzluPUtb8Kb5XsBux4Gc1NnV7pi1q60w==", - "requires": { - "manakin": "0.4.7", - "pg": "6.4.1", - "pg-minify": "0.5.2", - "spex": "1.2.0" - }, - "dependencies": { - "pg": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-6.4.1.tgz", - "integrity": "sha1-PqvYygVoFEN8dp8X/3oMNqxwI8U=", + "pg-pool": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.3.tgz", + "integrity": "sha1-wCIDLIlJ8xKk+R+2QJzgQHa+Mlc=" + }, + "pg-types": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz", + "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=", "requires": { - "buffer-writer": "1.0.1", - "packet-reader": "0.3.1", - "pg-connection-string": "0.1.3", - "pg-pool": "1.8.0", - "pg-types": "1.12.0", - "pgpass": "1.0.2", - "semver": "4.3.2" + "postgres-array": "1.0.2", + "postgres-bytea": "1.0.0", + "postgres-date": "1.0.3", + "postgres-interval": "1.1.0" } }, "semver": { @@ -6485,16 +6458,87 @@ } } }, - "pg-types": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.0.tgz", - "integrity": "sha1-itO3uJfj/UY+Yt4kGtX8ZAtKZvA=", + "pg-connection-string": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", + "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-native": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-2.2.0.tgz", + "integrity": "sha1-vIR1b07L9gD/fLSOpYVBQYMM8eQ=", "requires": { - "ap": "0.2.0", - "postgres-array": "1.0.2", - "postgres-bytea": "1.0.0", - "postgres-date": "1.0.3", - "postgres-interval": "1.1.0" + "libpq": "1.8.7", + "pg-types": "1.13.0", + "readable-stream": "1.0.31" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "pg-types": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz", + "integrity": "sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "1.0.2", + "postgres-bytea": "1.0.0", + "postgres-date": "1.0.3", + "postgres-interval": "1.1.0" + } + }, + "readable-stream": { + "version": "1.0.31", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.31.tgz", + "integrity": "sha1-jyUC4LyeOw2huUUgqrtOJgPsr64=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "pg-promise": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-7.4.1.tgz", + "integrity": "sha512-WlELd86G2ucrFbomdn+UfOeLMeFuPd/v8qXvn0rXgcxWfuxkn1fU3nKmaEnNFuDaOykDnO4bkxL6O0vnJi+HHg==", + "requires": { + "manakin": "0.5.1", + "pg": "7.4.1", + "pg-minify": "0.5.4", + "spex": "2.0.2" + }, + "dependencies": { + "manakin": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/manakin/-/manakin-0.5.1.tgz", + "integrity": "sha1-xKcRb2sA3z1fGjetPKUV0iBlplg=" + }, + "pg-minify": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/pg-minify/-/pg-minify-0.5.4.tgz", + "integrity": "sha512-GHB2v4OiMHDgwiHH86ZWNfvgEPVijrnfuWLQocseX6Zlf30k+x0imA65zBy4skIpEwfBBEplIEEKP4n3q9KkVA==" + }, + "spex": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/spex/-/spex-2.0.2.tgz", + "integrity": "sha512-LU6TS3qTEpRth+FnNs/fIWEmridYN7JmaN2k1Jk31XVC4ex7+wYxiHMnKguRxS7oKjbOFl4H6seeWNDFFgkVRg==" + } } }, "pgpass": { @@ -7752,11 +7796,6 @@ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" }, - "spex": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/spex/-/spex-1.2.0.tgz", - "integrity": "sha1-YmSzuKy8RER38G27ZtQlwO4QdMA=" - }, "split": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/split/-/split-1.0.0.tgz", diff --git a/package.json b/package.json index 57506fc4..267fbf8c 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "node-hkdf-sync": "^1.0.0", "node-mailjet": "^3.2.1", "numeral": "^2.0.3", + "p-each-series": "^1.0.0", "pg-native": "^2.2.0", "pg-promise": "^7.4.1", "pify": "^3.0.0", From 647bdb51583e15cf027108eba00c4199b0ad3feb Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 2 Feb 2018 00:12:16 +0200 Subject: [PATCH 04/36] 5.7.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 28910626..92277d0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "5.7.6", + "version": "5.7.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 267fbf8c..9fdc5295 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "5.7.6", + "version": "5.7.7", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From 4facfea18ce0d78cdee617cccc8190b05f7b1c7f Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 2 Feb 2018 00:12:33 +0200 Subject: [PATCH 05/36] 5.7.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 92277d0e..8fa7489a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "5.7.7", + "version": "5.7.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9fdc5295..9f69b6f1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "5.7.7", + "version": "5.7.8", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From 3cb5ddb24dd5ddfb41bab31e5af60edc01faa0e9 Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Thu, 1 Feb 2018 17:18:03 -0500 Subject: [PATCH 06/36] Update node wallet versions (#91) * Update zcashd, geth, bitcoin-abc, dashd This version of Bitcoin ABC includes CashAddr support (default) * Update Zcash disable deprecation --- lib/blockchain/common.js | 14 +++++++------- lib/blockchain/zcash.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 9c312e51..22d832e9 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -25,15 +25,15 @@ const BINARIES = { dir: 'bitcoin-0.15.1/bin' }, ETH: { - url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.7.2-1db4ecdc.tar.gz', - dir: 'geth-linux-amd64-1.7.2-1db4ecdc' + url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.7.3-4bb3c89d.tar.gz', + dir: 'geth-linux-amd64-1.7.3-4bb3c89d' }, ZEC: { - url: 'https://z.cash/downloads/zcash-1.0.13-linux64.tar.gz', - dir: 'zcash-1.0.13/bin' + url: 'https://z.cash/downloads/zcash-1.0.14-linux64.tar.gz', + dir: 'zcash-1.0.14/bin' }, DASH: { - url: 'https://github.com/dashpay/dash/releases/download/v0.12.2.1/dashcore-0.12.2.1-linux64.tar.gz', + url: 'https://github.com/dashpay/dash/releases/download/v0.12.2.3/dashcore-0.12.2.3-linux64.tar.gz', dir: 'dashcore-0.12.2/bin' }, LTC: { @@ -41,8 +41,8 @@ const BINARIES = { dir: 'litecoin-0.14.2/bin' }, BCH: { - url: 'https://download.bitcoinabc.org/0.16.1/linux/bitcoin-abc-0.16.1-x86_64-linux-gnu.tar.gz', - dir: 'bitcoin-abc-0.16.1/bin', + url: 'https://download.bitcoinabc.org/0.16.2/linux/bitcoin-abc-0.16.2-x86_64-linux-gnu.tar.gz', + dir: 'bitcoin-abc-0.16.2/bin', files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']] } } diff --git a/lib/blockchain/zcash.js b/lib/blockchain/zcash.js index 1efe470f..ea631346 100644 --- a/lib/blockchain/zcash.js +++ b/lib/blockchain/zcash.js @@ -20,7 +20,7 @@ function setup (dataDir) { logger.info('Finished fetching proofs.') const config = buildConfig() common.writeFile(path.resolve(dataDir, coinRec.configFile), config) - const cmd = `/usr/local/bin/${coinRec.daemon} -datadir=${dataDir} -disabledeprecation=1.0.13` + const cmd = `/usr/local/bin/${coinRec.daemon} -datadir=${dataDir} -disabledeprecation=1.0.14` common.writeSupervisorConfig(coinRec, cmd) } From e22333b7afe910ceeeff61c5c2350f08627a8098 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 2 Feb 2018 00:42:54 +0200 Subject: [PATCH 07/36] backport some missed changes in cash-in and cash-out --- lib/cash-in/cash-in-low.js | 3 ++- lib/cash-out/cash-out-actions.js | 2 +- lib/cash-out/cash-out-atomic.js | 13 ++++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/cash-in/cash-in-low.js b/lib/cash-in/cash-in-low.js index 8bb91601..8326a00a 100644 --- a/lib/cash-in/cash-in-low.js +++ b/lib/cash-in/cash-in-low.js @@ -4,6 +4,7 @@ const pgp = require('pg-promise')() const BN = require('../bn') const T = require('../time') const logger = require('../logger') +const E = require('../error') const PENDING_INTERVAL_MS = 60 * T.minutes @@ -85,7 +86,7 @@ function diff (oldTx, newTx) { logger.warn('Value from lamassu-machine would violate ratchet [%s]', fieldKey) logger.warn('Old tx: %j', oldTx) logger.warn('New tx: %j', newTx) - throw new Error('Value from lamassu-machine would violate ratchet') + throw new E.RatchetError('Value from lamassu-machine would violate ratchet') } updatedTx[fieldKey] = newField diff --git a/lib/cash-out/cash-out-actions.js b/lib/cash-out/cash-out-actions.js index 8899ac09..5387514a 100644 --- a/lib/cash-out/cash-out-actions.js +++ b/lib/cash-out/cash-out-actions.js @@ -6,7 +6,7 @@ module.exports = {logDispense, logActionById, logAction, logError} function logDispense (t, tx) { const baseRec = {error: tx.error, error_code: tx.errorCode} const rec = _.merge(mapDispense(tx), baseRec) - const action = tx.dispenseConfirmed ? 'dispense' : 'dispenseError' + const action = _.isEmpty(tx.error) ? 'dispense' : 'dispenseError' return logAction(t, action, rec, tx) } diff --git a/lib/cash-out/cash-out-atomic.js b/lib/cash-out/cash-out-atomic.js index acb24741..bd9ec167 100644 --- a/lib/cash-out/cash-out-atomic.js +++ b/lib/cash-out/cash-out-atomic.js @@ -43,6 +43,7 @@ function preProcess (t, oldTx, newTx, pi) { .then(newTxHd => { return pi.newAddress(newTxHd) .then(_.set('toAddress', _, newTxHd)) + .then(_.unset('isLightning')) }) .then(addressedTx => { const rec = {to_address: addressedTx.toAddress} @@ -68,7 +69,10 @@ function preProcess (t, oldTx, newTx, pi) { return cashOutActions.logAction(t, updatedTx.status, rec, updatedTx) } - if (!oldTx.dispenseConfirmed && updatedTx.dispenseConfirmed) { + const hasError = !oldTx.error && newTx.error + const hasDispenseOccurred = !dispenseOccurred(oldTx.bills) && dispenseOccurred(newTx.bills) + + if (hasError || hasDispenseOccurred) { return cashOutActions.logDispense(t, updatedTx) .then(updateCassettes(t, updatedTx)) } @@ -93,6 +97,8 @@ function nextHd (t, isHd, tx) { } function updateCassettes (t, tx) { + if (!dispenseOccurred(tx.bills)) return Promise.resolve() + const sql = `update devices set cassette1 = cassette1 - $1, cassette2 = cassette2 - $2 @@ -158,3 +164,8 @@ function ratchetStatus (oldStatus, newStatus) { const idx = Math.max(statusOrder.indexOf(oldStatus), statusOrder.indexOf(newStatus)) return statusOrder[idx] } + +function dispenseOccurred (bills) { + if (_.isEmpty(bills)) return false + return _.every(_.overEvery([_.has('dispensed'), _.has('rejected')]), bills) +} From 9a505d2e9e25284928d3a842b360e0c638ace283 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 2 Feb 2018 00:45:59 +0200 Subject: [PATCH 08/36] 5.7.9 --- 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 8fa7489a..d2ffb9cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "5.7.8", + "version": "5.7.9", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9f69b6f1..530f404a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "5.7.8", + "version": "5.7.9", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From 1a87a256bfb964f97328bb1b05b4821e69445587 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 2 Feb 2018 00:46:01 +0200 Subject: [PATCH 09/36] 5.7.10 --- 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 d2ffb9cf..4913c4a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "5.7.9", + "version": "5.7.10", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 530f404a..b2f059ef 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "5.7.9", + "version": "5.7.10", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From d87781f91f07f545c15852801f3ce735705c2196 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 5 Feb 2018 22:53:46 +0200 Subject: [PATCH 10/36] Fix up cert-gen.sh --- bin/cert-gen.sh | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/bin/cert-gen.sh b/bin/cert-gen.sh index 030854c3..4daf40c2 100755 --- a/bin/cert-gen.sh +++ b/bin/cert-gen.sh @@ -4,11 +4,13 @@ set -e DOMAIN=localhost +CONFIG_DIR=$HOME/.lamassu LOG_FILE=/tmp/cert-gen.log CERT_DIR=$PWD/certs KEY_DIR=$PWD/certs - -CONFIG_DIR=$HOME/.lamassu +LAMASSU_CA_PATH=$PWD/Lamassu_CA.pem +MIGRATE_STATE_PATH=$CONFIG_DIR/.migrate +POSTGRES_PASS=postgres123 mkdir -p $CERT_DIR mkdir -p $CONFIG_DIR >> $LOG_FILE 2>&1 @@ -49,11 +51,7 @@ openssl genrsa \ openssl req -new \ -key $SERVER_KEY_PATH \ -out /tmp/Lamassu_OP.csr.pem \ - -subj "/C=IS/ST=/L=Reykjavik/O=Lamassu Operator/CN=$IP" \ - -reqexts SAN \ - -sha256 \ - -config <(cat /etc/ssl/openssl.cnf \ - <(printf "[SAN]\nsubjectAltName=IP.1:$IP")) \ + -subj "/C=IS/ST=/L=Reykjavik/O=Lamassu Operator/CN=$DOMAIN" \ >> $LOG_FILE 2>&1 openssl x509 \ @@ -62,22 +60,22 @@ openssl x509 \ -CAkey $CA_KEY_PATH \ -CAcreateserial \ -out $SERVER_CERT_PATH \ - -extfile <(cat /etc/ssl/openssl.cnf \ - <(printf "[SAN]\nsubjectAltName=IP.1:$IP")) \ - -extensions SAN \ -days 3650 >> $LOG_FILE 2>&1 rm /tmp/Lamassu_OP.csr.pem cat < $CONFIG_DIR/lamassu.json { - "postgresql": "psql://lamassu:lamassu@localhost/lamassu", + "postgresql": "psql://postgres:$POSTGRES_PASS@localhost/lamassu", "seedPath": "$SEED_FILE", "caPath": "$CA_PATH", "certPath": "$SERVER_CERT_PATH", "keyPath": "$SERVER_KEY_PATH", "hostname": "$DOMAIN", - "logLevel": "debug" + "logLevel": "debug", + "lamassuCaPath": "$LAMASSU_CA_PATH", + "lamassuServerPath": "$PWD", + "migrateStatePath": "$MIGRATE_STATE_PATH" } EOF From 6dc46a6593e2c666957905da9cb68cd0d2572fad Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 5 Feb 2018 22:54:30 +0200 Subject: [PATCH 11/36] Create INSTALL.md --- INSTALL.md | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 INSTALL.md diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000..378719b6 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,86 @@ +# Installation on Ubuntu 16.04 + +Installation for other distros may be slightly different. This assumes nodejs 8 and npm are already installed. All of this is done in the lamassu-server directory. + +## Packages + +``` +sudo apt-get update +sudo apt-get install postgresql postgresql-contrib postgresql-server-dev-9.5 libpq-dev git +``` + +## Set up PostgreSQL + +``` +sudo -u postgres createdb lamassu +sudo -u postgres psql postgres +``` + +In ``psql``, run the following and set password to ``postgres123``: + +``` +\password postgres +ctrl-d +``` + +## Install node modules +Ignore any warnings. + +``` +npm install +``` + +## Generate certificates + +``` +bash bin/cert-gen.sh +``` + +Note: This will create a ``.lamassu`` directory in your home directory. + +## Set up database + +Important: lamassu-migrate currently gripes about a QueryResultError. Ignore this, it works anyway. Also, ignore Debug lines from lamassu-apply-defaults. + +``` +node bin/lamassu-migrate +node bin/lamassu-apply-defaults +``` + +## Register admin user + +You'll use this generated URL in the brower in moment. + +``` +node bin/lamassu-register admin +``` + +## Run lamassu-admin-server + +In first terminal window: + +``` +node bin/lamassu-admin-server --dev +``` + +## Complete configuration + +Paste the URL from lamassu-register exactly as output, into a browser (chrome or firefox). + +**Important**: the host must be localhost. Tell your browser to trust the certificate even though it's not signed by a CA. + +Go to all the required, unconfigured red fields and choose some values. Choose mock services whenever available. + +## Run lamassu-server + +In second terminal window: + +``` +node bin/lamassu-server --mockSms +``` + +## Add a lamassu-machine + +Click on ``+ Add Machine`` in the sidebar. Type in a name for your machine and click **Pair**. Open up development tools to show the JavaScript console and copy the totem. You will use this to run lamassu-machine. This pairing totem expires after an hour. + +Now continue with lamassu-machine instructions from the ``INSTALL.md`` file in lamassu-machine. From a42936ad0d74cc98904f010a6d77bddf6965e4cf Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 9 Feb 2018 15:26:50 +0100 Subject: [PATCH 12/36] Don't complain on all QueryResult errors --- lib/db.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/db.js b/lib/db.js index bed67ddf..a68c9b00 100644 --- a/lib/db.js +++ b/lib/db.js @@ -5,11 +5,8 @@ const logger = require('./logger') const pgp = Pgp({ pgNative: true, error: (err, e) => { - if (e.cn) logger.error('Database not reachable.') - if (e.query) { - logger.error(e.query) - logger.error(e.params) - } + if (e.cn) return logger.error('Database not reachable.') + if (e.query) return logger.error(err) } }) From 0c97df42e57415be5fce08ec27c0458aa6b01b76 Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Fri, 16 Feb 2018 16:14:23 +0000 Subject: [PATCH 13/36] Fix lamassu-cancel (#98) Fix lamassu-cancel --- bin/lamassu-cancel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lamassu-cancel b/bin/lamassu-cancel index 22434b91..0a761bb7 100755 --- a/bin/lamassu-cancel +++ b/bin/lamassu-cancel @@ -2,7 +2,7 @@ const uuid = require('@fczbkk/uuid4') -const tx = require('../lib/cash-out-tx') +const tx = require('../lib/cash-out/cash-out-tx.js') const argv = process.argv.slice(2) From bc66b7b843fc8c6d3537486b65dfb1c796bec2af Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Fri, 16 Feb 2018 16:15:28 +0000 Subject: [PATCH 14/36] Trade on USD Kraken if fiat is other than USD/EUR (#93) * Trade on USD Kraken if fiat is other than USD/EUR * Fix array membership logic * Default to EUR --- lib/plugins/exchange/kraken/kraken.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/plugins/exchange/kraken/kraken.js b/lib/plugins/exchange/kraken/kraken.js index 0ccdc035..20da7d73 100644 --- a/lib/plugins/exchange/kraken/kraken.js +++ b/lib/plugins/exchange/kraken/kraken.js @@ -1,5 +1,6 @@ // Note: Using DeX3/npm-kraken-api to adjust timeout time const Kraken = require('kraken-api') +const _ = require('lodash/fp') const common = require('../../common/kraken') @@ -19,11 +20,14 @@ function trade (account, type, cryptoAtoms, fiatCode, cryptoCode) { const kraken = new Kraken(account.apiKey, account.privateKey, {timeout: 30000}) const amount = common.toUnit(cryptoAtoms, cryptoCode) const amountStr = amount.toFixed(6) - const pair = PAIRS[cryptoCode][fiatCode] + const pair = _.includes(fiatCode, ['USD', 'EUR']) + ? PAIRS[cryptoCode][fiatCode] + : PAIRS[cryptoCode]['EUR'] + var orderInfo = { - pair: pair, - type: type, + pair, + type, ordertype: 'market', volume: amountStr, expiretm: '+60' From c2248e13d79689262d6ae09f06b883df6d01808d Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Fri, 16 Feb 2018 16:55:33 +0000 Subject: [PATCH 15/36] ETH performance (#99) * Update geth to 1.8.0 * Up cache and maxconnections --- lib/blockchain/common.js | 4 ++-- lib/blockchain/ethereum.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 22d832e9..410cadb0 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -25,8 +25,8 @@ const BINARIES = { dir: 'bitcoin-0.15.1/bin' }, ETH: { - url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.7.3-4bb3c89d.tar.gz', - dir: 'geth-linux-amd64-1.7.3-4bb3c89d' + url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.0-5f540757.tar.gz', + dir: 'geth-linux-amd64-1.8.0-5f540757' }, ZEC: { url: 'https://z.cash/downloads/zcash-1.0.14-linux64.tar.gz', diff --git a/lib/blockchain/ethereum.js b/lib/blockchain/ethereum.js index 7d5f2bc3..81d81a55 100644 --- a/lib/blockchain/ethereum.js +++ b/lib/blockchain/ethereum.js @@ -7,6 +7,6 @@ module.exports = {setup} function setup (dataDir) { const coinRec = coinUtils.getCryptoCurrency('ETH') common.firewall([coinRec.defaultPort]) - const cmd = `/usr/local/bin/${coinRec.daemon} --datadir "${dataDir}" --syncmode="fast" --cache 500 --rpc` + const cmd = `/usr/local/bin/${coinRec.daemon} --datadir "${dataDir}" --syncmode="fast" --cache 2048 --maxconnections 40 --rpc` common.writeSupervisorConfig(coinRec, cmd) } From c9120fa961e00aa5572a3f7dedd1f0edfdb5a8da Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Mon, 26 Feb 2018 17:03:15 +0000 Subject: [PATCH 16/36] Fix ETH peers and bump to 1.8.1 (#101) * Fix ETH 'maxpeers' * Update geth to 1.8.1 --- lib/blockchain/common.js | 4 ++-- lib/blockchain/ethereum.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index 410cadb0..e3a3b428 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -25,8 +25,8 @@ const BINARIES = { dir: 'bitcoin-0.15.1/bin' }, ETH: { - url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.0-5f540757.tar.gz', - dir: 'geth-linux-amd64-1.8.0-5f540757' + url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.1-1e67410e.tar.gz', + dir: 'geth-linux-amd64-1.8.1-1e67410e' }, ZEC: { url: 'https://z.cash/downloads/zcash-1.0.14-linux64.tar.gz', diff --git a/lib/blockchain/ethereum.js b/lib/blockchain/ethereum.js index 81d81a55..2124c231 100644 --- a/lib/blockchain/ethereum.js +++ b/lib/blockchain/ethereum.js @@ -7,6 +7,6 @@ module.exports = {setup} function setup (dataDir) { const coinRec = coinUtils.getCryptoCurrency('ETH') common.firewall([coinRec.defaultPort]) - const cmd = `/usr/local/bin/${coinRec.daemon} --datadir "${dataDir}" --syncmode="fast" --cache 2048 --maxconnections 40 --rpc` + const cmd = `/usr/local/bin/${coinRec.daemon} --datadir "${dataDir}" --syncmode="fast" --cache 2048 --maxpeers 40 --rpc` common.writeSupervisorConfig(coinRec, cmd) } From a2d168644f41cb8add64fed398988b09c1afd3dc Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Mon, 26 Feb 2018 17:04:17 +0000 Subject: [PATCH 17/36] Add ETH & BCH to Bitstamp ticker, exchange (#100) * Add ETH & BCH to Bitstamp ticker, exchange * Add ETH & BCH to Bitstamp --- lib/admin/config.js | 4 ++-- lib/plugins/common/bitstamp.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/admin/config.js b/lib/admin/config.js index 059b143a..8783e135 100644 --- a/lib/admin/config.js +++ b/lib/admin/config.js @@ -169,7 +169,7 @@ function fetchData () { 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', 'LTC']}, + {code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, {code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, {code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS}, {code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']}, @@ -180,7 +180,7 @@ function fetchData () { {code: 'dashd', display: 'dashd', class: 'wallet', cryptos: ['DASH']}, {code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']}, {code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']}, - {code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'LTC']}, + {code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, {code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']}, {code: 'mock-wallet', display: 'Mock wallet', class: 'wallet', cryptos: ALL_CRYPTOS}, {code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS}, diff --git a/lib/plugins/common/bitstamp.js b/lib/plugins/common/bitstamp.js index 91a868b0..91c9c90a 100644 --- a/lib/plugins/common/bitstamp.js +++ b/lib/plugins/common/bitstamp.js @@ -52,7 +52,7 @@ function authRequest (config, path, data) { } function buildMarket (fiatCode, cryptoCode) { - if (!_.includes(cryptoCode, ['BTC', 'LTC'])) { + if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) { throw new Error('Unsupported crypto: ' + cryptoCode) } From a8d6cddba99cb6f1b5adb743b861bb1e10cff5fb Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Mon, 26 Feb 2018 17:09:52 +0000 Subject: [PATCH 18/36] Trade on EUR Bitstamp if fiat other than USD/EUR (#102) * Trade on EUR Bitstamp if fiat other than USD/EUR * Don't mutate fiatCode --- lib/plugins/exchange/bitstamp/bitstamp.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/plugins/exchange/bitstamp/bitstamp.js b/lib/plugins/exchange/bitstamp/bitstamp.js index 9a120724..6a111ce4 100644 --- a/lib/plugins/exchange/bitstamp/bitstamp.js +++ b/lib/plugins/exchange/bitstamp/bitstamp.js @@ -22,7 +22,9 @@ function handleErrors (data) { throw err } -function trade (type, account, cryptoAtoms, fiatCode, cryptoCode) { +function trade (type, account, cryptoAtoms, _fiatCode, cryptoCode) { + const fiatCode = _fiatCode === 'USD' ? 'USD' : 'EUR' + try { const market = common.buildMarket(fiatCode, cryptoCode) const options = {amount: cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)} From fbcb8b5ff2352753fe54fcc90613e79100aa7f58 Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Mon, 5 Mar 2018 11:52:45 +0000 Subject: [PATCH 19/36] Update BTC to 0.16.0, native SegWit (#104) Adds native SegWit support on BTC. --- lib/blockchain/common.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index e3a3b428..fc898726 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.15.1/bitcoin-0.15.1-x86_64-linux-gnu.tar.gz', - dir: 'bitcoin-0.15.1/bin' + url: 'https://bitcoin.org/bin/bitcoin-core-0.16.0/bitcoin-0.16.0-x86_64-linux-gnu.tar.gz', + dir: 'bitcoin-0.16.0/bin' }, ETH: { url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.1-1e67410e.tar.gz', From 86dfca60c1514dcc23d4d969215dfb19d48692e7 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sat, 3 Feb 2018 14:32:05 +0200 Subject: [PATCH 20/36] Add id-card compliance --- lib/customers.js | 30 ++++-- lib/db.js | 7 +- lib/route-helpers.js | 8 +- lib/routes.js | 14 +++ package-lock.json | 217 ++++--------------------------------------- package.json | 1 + 6 files changed, 67 insertions(+), 210 deletions(-) diff --git a/lib/customers.js b/lib/customers.js index 6db8a819..931cea0b 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -57,9 +57,12 @@ function get (phone) { */ function update (id, data, userToken) { const formattedData = _.omit(['id'], _.mapKeys(_.snakeCase, data)) - const updateData = enhanceOverrideFields(formattedData, userToken) + + const updateData = enhanceAtFields(enhanceOverrideFields(formattedData, userToken)) + const sql = Pgp.helpers.update(updateData, _.keys(updateData), 'customers') + ' where id=$1 returning *' + return db.one(sql, [id]) .then(addComplianceOverrides(id, updateData, userToken)) .then(populateOverrideUsernames) @@ -100,11 +103,11 @@ function getById (id, userToken) { */ function getDailyVolume (id) { return Promise.all([ - db.one(`select coalesce(sum(fiat), 0) as total from cash_in_txs - where customer_id=$1 + db.one(`select coalesce(sum(fiat), 0) as total from cash_in_txs + where customer_id=$1 and created > now() - interval '1 day'`, [id]), - db.one(`select coalesce(sum(fiat), 0) as total from cash_out_txs - where customer_id=$1 + db.one(`select coalesce(sum(fiat), 0) as total from cash_out_txs + where customer_id=$1 and created > now() - interval '1 day'`, [id]) ]).then(([cashInTotal, cashOutTotal]) => { return BN(cashInTotal.total).add(cashOutTotal.total) @@ -161,6 +164,21 @@ function getComplianceTypes () { 'authorized' ] } +function enhanceAtFields (fields) { + const updateableFields = [ + 'id_card_data', + 'id_card_photo', + 'front_camera', + 'sanctions', + 'authorized' + ] + + const updatedFields = _.intersection(updateableFields, _.keys(fields)) + const atFields = _.fromPairs(_.map(f => [`${f}_at`, 'now()^'], updatedFields)) + + return _.merge(fields, atFields) +} + /** * Add *override_by and *override_at fields with acting user's token * and date of override respectively before saving to db. @@ -298,7 +316,7 @@ function populateOverrideUsernames (customer) { * @returns {array} Array of customers populated with status field */ function batch () { - const sql = `select * from customers + const sql = `select * from customers where id != $1 order by created desc limit $2` return db.any(sql, [ anonymous.uuid, NUM_RESULTS ]) diff --git a/lib/db.js b/lib/db.js index a68c9b00..bed67ddf 100644 --- a/lib/db.js +++ b/lib/db.js @@ -5,8 +5,11 @@ const logger = require('./logger') const pgp = Pgp({ pgNative: true, error: (err, e) => { - if (e.cn) return logger.error('Database not reachable.') - if (e.query) return + if (e.cn) logger.error('Database not reachable.') + if (e.query) { + logger.error(e.query) + logger.error(e.params) + } logger.error(err) } }) diff --git a/lib/route-helpers.js b/lib/route-helpers.js index 2fe8fafe..fdfa114b 100644 --- a/lib/route-helpers.js +++ b/lib/route-helpers.js @@ -110,4 +110,10 @@ function updateMachineDefaults (deviceId) { }) } -module.exports = {stateChange, fetchPhoneTx, fetchStatusTx, updateDeviceConfigVersion, updateMachineDefaults} +module.exports = { + stateChange, + fetchPhoneTx, + fetchStatusTx, + updateDeviceConfigVersion, + updateMachineDefaults +} diff --git a/lib/routes.js b/lib/routes.js index 86882de9..8f169e24 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -177,6 +177,18 @@ function getCustomerWithPhoneCode (req, res, next) { .catch(next) } +function updateCustomer (req, res, next) { + const id = req.params.id + const patch = req.body + customers.getById(id) + .then(customer => { + if (!customer) { throw httpError('Not Found', 404)} + return customers.update(id, patch) + }) + .then(customer => respond(req, res, {customer})) + .catch(next) +} + function getLastSeen (req, res, next) { return logs.getLastSeen(req.deviceId) .then(r => res.json(r)) @@ -304,6 +316,8 @@ app.post('/verify_user', verifyUser) app.post('/verify_transaction', verifyTx) app.post('/phone_code', getCustomerWithPhoneCode) +app.patch('/customer/:id', updateCustomer) + app.post('/tx', postTx) app.get('/tx/:id', getTx) app.get('/tx', getPhoneTx) diff --git a/package-lock.json b/package-lock.json index 4913c4a1..eaa7ce7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1154,7 +1154,7 @@ "bitcore-lib": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-0.15.0.tgz", - "integrity": "sha512-AeXLWhiivF6CDFzrABZHT4jJrflyylDWTi32o30rF92HW9msfuKpjzrHtFKYGa9w0kNVv5HABQjCB3OEav4PhQ==", + "integrity": "sha1-+SS+E4afKqt+BK7sVkKtM1m2zsI=", "requires": { "bn.js": "4.11.8", "bs58": "4.0.1", @@ -1184,38 +1184,6 @@ } } }, - "bitcore-lib-cash": { - "version": "git+https://github.com/bitpay/bitcore-lib.git#8f2633b5c1b5554f29847344c477f10d39c68455", - "requires": { - "bn.js": "4.11.8", - "bs58": "4.0.1", - "buffer-compare": "1.1.1", - "elliptic": "6.4.0", - "inherits": "2.0.1", - "lodash": "4.17.4", - "phantomjs-prebuilt": "2.1.16" - }, - "dependencies": { - "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", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", - "requires": { - "base-x": "3.0.2" - } - }, - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" - } - } - }, "bitgo": { "version": "3.4.11", "resolved": "https://registry.npmjs.org/bitgo/-/bitgo-3.4.11.tgz", @@ -1966,16 +1934,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "typedarray": "0.0.6" - } - }, "configstore": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.0.tgz", @@ -2619,11 +2577,6 @@ "integrity": "sha1-7sXHJurO9Rt/a3PCDbbhsTsGnJg=", "dev": true }, - "es6-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2950,40 +2903,6 @@ "is-extglob": "1.0.0" } }, - "extract-zip": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.6.tgz", - "integrity": "sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=", - "requires": { - "concat-stream": "1.6.0", - "debug": "2.6.9", - "mkdirp": "0.5.0", - "yauzl": "2.4.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", - "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", - "requires": { - "minimist": "0.0.8" - } - } - } - }, "extsprintf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", @@ -2999,14 +2918,6 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "requires": { - "pend": "1.2.0" - } - }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -3147,16 +3058,6 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" }, - "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "2.4.0", - "klaw": "1.3.1" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4615,15 +4516,6 @@ "minimalistic-assert": "1.0.0" } }, - "hasha": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", - "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", - "requires": { - "is-stream": "1.1.0", - "pinkie-promise": "2.0.1" - } - }, "hawk": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", @@ -5159,7 +5051,8 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isobject": { "version": "2.1.0", @@ -5356,14 +5249,6 @@ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "requires": { - "graceful-fs": "4.1.11" - } - }, "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", @@ -5443,11 +5328,6 @@ "sha3": "1.2.0" } }, - "kew": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=" - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -5457,21 +5337,6 @@ "is-buffer": "1.1.5" } }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "requires": { - "graceful-fs": "4.1.11" - } - }, - "kraken-api": { - "version": "github:DeX3/npm-kraken-api#ca939e6dd6c6cd9d3ca9a6ee52dc2170a8d25978", - "requires": { - "querystring": "0.2.0", - "request": "2.81.0" - } - }, "last-line-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/last-line-stream/-/last-line-stream-1.0.0.tgz", @@ -5630,6 +5495,14 @@ "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" }, + "longjohn": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/longjohn/-/longjohn-0.2.12.tgz", + "integrity": "sha1-fKdEawg2VcN351EiE9x1TVKmSn4=", + "requires": { + "source-map-support": "0.4.15" + } + }, "loose-envify": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", @@ -6410,11 +6283,6 @@ "sha.js": "2.4.8" } }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" - }, "performance-now": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", @@ -6423,7 +6291,7 @@ "pg": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/pg/-/pg-7.4.1.tgz", - "integrity": "sha512-Pi5qYuXro5PAD9xXx8h7bFtmHgAQEG6/SCNyi7gS3rvb/ZQYDmxKchfB0zYtiSJNWq9iXTsYsHjrM+21eBcN1A==", + "integrity": "sha1-80Ecjd+faSMi/gXnAXoYiOR/ePE=", "requires": { "buffer-writer": "1.0.1", "js-string-escape": "1.0.1", @@ -6466,7 +6334,7 @@ "pg-int8": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + "integrity": "sha1-lDvUY79bcbQXARX4D478mgwOt4w=" }, "pg-native": { "version": "2.2.0", @@ -6516,7 +6384,7 @@ "pg-promise": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-7.4.1.tgz", - "integrity": "sha512-WlELd86G2ucrFbomdn+UfOeLMeFuPd/v8qXvn0rXgcxWfuxkn1fU3nKmaEnNFuDaOykDnO4bkxL6O0vnJi+HHg==", + "integrity": "sha1-uyDjnS0ObUXZLwu6szidHmEXlxo=", "requires": { "manakin": "0.5.1", "pg": "7.4.1", @@ -6549,22 +6417,6 @@ "split": "1.0.0" } }, - "phantomjs-prebuilt": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", - "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", - "requires": { - "es6-promise": "4.2.4", - "extract-zip": "1.6.6", - "fs-extra": "1.0.0", - "hasha": "2.2.0", - "kew": "0.7.0", - "progress": "1.1.8", - "request": "2.81.0", - "request-progress": "2.0.1", - "which": "1.2.14" - } - }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -6748,11 +6600,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=" - }, "promise-sequential": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/promise-sequential/-/promise-sequential-1.1.1.tgz", @@ -6906,11 +6753,6 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, "ramda": { "version": "0.22.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.22.1.tgz", @@ -7205,14 +7047,6 @@ } } }, - "request-progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", - "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", - "requires": { - "throttleit": "1.0.0" - } - }, "require-precompiled": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/require-precompiled/-/require-precompiled-0.1.0.tgz", @@ -7765,7 +7599,6 @@ "version": "0.4.15", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", - "dev": true, "requires": { "source-map": "0.5.6" }, @@ -7773,8 +7606,7 @@ "source-map": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", - "dev": true + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" } } }, @@ -8047,11 +7879,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -8211,11 +8038,6 @@ "mime-types": "2.1.15" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, "typeforce": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.11.1.tgz", @@ -8408,6 +8230,7 @@ "version": "1.2.14", "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true, "requires": { "isexe": "2.0.0" } @@ -8644,14 +8467,6 @@ "y18n": "3.2.1" } }, - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "requires": { - "fd-slicer": "1.0.1" - } - }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", diff --git a/package.json b/package.json index b2f059ef..136b41be 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "kraken-api": "github:DeX3/npm-kraken-api", "lnd-async": "^1.0.1", "lodash": "^4.17.2", + "longjohn": "^0.2.12", "make-dir": "^1.0.0", "mem": "^1.1.0", "migrate": "^0.2.2", From ba65eea88d2828ffa1809f88d1817c2f1b4a71b7 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Tue, 6 Mar 2018 15:06:06 +0000 Subject: [PATCH 21/36] 5.8.0 --- 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 eaa7ce7d..bbf167c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "5.7.10", + "version": "5.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 136b41be..67baaf2e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "5.7.10", + "version": "5.8.0", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From 4108efd9c729e7f92bc022d1533a149bd6b4ba03 Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Thu, 8 Mar 2018 10:26:44 +0000 Subject: [PATCH 22/36] Fix BadNumberError code (#107) lamassu-machine is expecting 401 for a bad number, but lamassu-server was returning 410 instead. --- lib/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes.js b/lib/routes.js index 8f169e24..36c73e65 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -171,7 +171,7 @@ function getCustomerWithPhoneCode (req, res, next) { }) }) .catch(err => { - if (err.name === 'BadNumberError') throw httpError('Bad number', 410) + if (err.name === 'BadNumberError') throw httpError('Bad number', 401) throw err }) .catch(next) From c2af18391188275c3c926f1dac99f18d5c99841c Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sat, 10 Mar 2018 18:59:40 +0000 Subject: [PATCH 23/36] format for latest standard --- bin/convert-txs.js | 54 ++-- bin/lamassu-display-config.js | 16 +- bin/migrate-config.js | 66 ++--- dev/double-dispense.js | 8 +- dev/notify.js | 28 +- dev/send-message.js | 18 +- lib/admin/accounts.js | 27 +- lib/admin/admin-server.js | 194 ++++++------- lib/admin/admin-support.js | 22 +- lib/admin/config.js | 154 +++++------ lib/admin/funding.js | 74 ++--- lib/admin/login.js | 22 +- lib/admin/pairing.js | 20 +- lib/admin/server.js | 74 ++--- lib/admin/transactions.js | 24 +- lib/app.js | 46 ++-- lib/apply-defaults.js | 52 ++-- lib/blockchain/install.js | 3 +- lib/cash-in/cash-in-atomic.js | 26 +- lib/cash-in/cash-in-low.js | 12 +- lib/cash-in/cash-in-tx.js | 72 ++--- lib/cash-out/cash-out-actions.js | 2 +- lib/cash-out/cash-out-atomic.js | 102 +++---- lib/cash-out/cash-out-helper.js | 10 +- lib/cash-out/cash-out-low.js | 8 +- lib/cash-out/cash-out-tx.js | 92 +++---- lib/config-validate.js | 76 +++--- lib/customers.js | 56 ++-- lib/email.js | 12 +- lib/exchange.js | 18 +- lib/logs.js | 20 +- lib/machine-loader.js | 40 +-- lib/migrate.js | 1 - lib/notifier.js | 120 ++++---- lib/pairing.js | 36 +-- lib/plugins.js | 258 +++++++++--------- lib/plugins/common/bitstamp.js | 10 +- lib/plugins/common/json-rpc.js | 22 +- lib/plugins/exchange/bitstamp/bitstamp.js | 10 +- lib/plugins/exchange/kraken/kraken.js | 8 +- lib/plugins/sms/twilio/twilio.js | 16 +- lib/plugins/ticker/bitpay/bitpay.js | 19 +- lib/plugins/ticker/bitstamp/bitstamp.js | 20 +- lib/plugins/ticker/coinbase/coinbase.js | 43 ++- lib/plugins/ticker/kraken/kraken.js | 40 +-- .../wallet/bitcoincashd/bitcoincashd.js | 68 ++--- lib/plugins/wallet/bitcoind/bitcoind.js | 68 ++--- lib/plugins/wallet/bitgo/bitgo.js | 104 +++---- lib/plugins/wallet/dashd/dashd.js | 66 ++--- lib/plugins/wallet/geth/geth.js | 98 +++---- lib/plugins/wallet/litecoind/litecoind.js | 66 ++--- lib/plugins/wallet/lnd/lnd.js | 58 ++-- lib/plugins/wallet/mock-wallet/mock-wallet.js | 14 +- lib/plugins/wallet/zcashd/zcashd.js | 66 ++--- .../zero-conf/blockcypher/blockcypher.js | 41 ++- .../mock-zero-conf/mock-zero-conf.js | 10 +- lib/postgresql_interface.js | 10 +- lib/route-helpers.js | 42 +-- lib/routes.js | 246 ++++++++--------- lib/settings-loader.js | 86 +++--- lib/sms.js | 12 +- lib/support_logs.js | 6 +- lib/ticker.js | 38 +-- lib/tx.js | 32 +-- lib/wallet.js | 104 +++---- migrations/008-add-two-way.js | 2 +- migrations/011-transactions-reload-2.js | 6 +- migrations/036-add_customers_table.js | 2 +- ...037-add_compliance_authorizations_table.js | 2 +- migrations/1509091634946-support_logs.js | 2 +- ...09439657189-add_machine_name_to_devices.js | 30 +- migrations/db.js | 18 +- migrations/migrate-tools.js | 6 +- test/unit/settings-loader-load-fixture.js | 8 +- tools/gen-countries.js | 2 +- tools/modify.js | 8 +- tools/show.js | 18 +- 77 files changed, 1697 insertions(+), 1693 deletions(-) diff --git a/bin/convert-txs.js b/bin/convert-txs.js index f8504fc6..2e724f98 100755 --- a/bin/convert-txs.js +++ b/bin/convert-txs.js @@ -7,44 +7,44 @@ var db = pgp(psqlUrl) db.manyOrNone(`select * from transactions where incoming=false and stage='final_request' and authority='machine'`) -.then(rs => - db.tx(t => - t.batch(rs.map(r => db.none(`insert into cash_in_txs (session_id, + .then(rs => + db.tx(t => + t.batch(rs.map(r => db.none(`insert into cash_in_txs (session_id, device_fingerprint, to_address, crypto_atoms, crypto_code, fiat, currency_code, fee, tx_hash, error, created) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [r.session_id, r.device_fingerprint, - r.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code, r.fee, - r.tx_hash, r.error, r.created])) + r.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code, r.fee, + r.tx_hash, r.error, r.created])) + ) ) ) -) -.then(() => db.manyOrNone(`select * from transactions where incoming=true + .then(() => db.manyOrNone(`select * from transactions where incoming=true and stage='initial_request' and authority='pending'`)) -.then(rs => - db.tx(t => - t.batch(rs.map(r => db.none(`insert into cash_out_txs (session_id, + .then(rs => + db.tx(t => + t.batch(rs.map(r => db.none(`insert into cash_out_txs (session_id, device_fingerprint, to_address, crypto_atoms, crypto_code, fiat, currency_code, tx_hash, phone, error, created) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [r.session_id, r.device_fingerprint, - r.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code, - r.tx_hash, r.phone, r.error, r.created])) + r.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code, + r.tx_hash, r.phone, r.error, r.created])) + ) ) ) -) -.then(() => db.manyOrNone(`select * from transactions where incoming=true + .then(() => db.manyOrNone(`select * from transactions where incoming=true and stage='dispense' and authority='authorized'`)) -.then(rs => - db.tx(t => - t.batch(rs.map(r => - db.none(`update cash_out_txs set dispensed=true where session_id=$1`, [r.session_id]) - .then(() => db.none(`insert into cash_out_actions (session_id, action, + .then(rs => + db.tx(t => + t.batch(rs.map(r => + db.none(`update cash_out_txs set dispensed=true where session_id=$1`, [r.session_id]) + .then(() => db.none(`insert into cash_out_actions (session_id, action, created) values ($1, $2, $3)`, [r.session_id, 'dispensed', r.created])) - )) + )) + ) ) -) -.then(() => pgp.end()) -.then(() => console.log('Success.')) -.catch(e => { - console.log(e) - pgp.end() -}) + .then(() => pgp.end()) + .then(() => console.log('Success.')) + .catch(e => { + console.log(e) + pgp.end() + }) diff --git a/bin/lamassu-display-config.js b/bin/lamassu-display-config.js index 12caab16..ed29d4b7 100644 --- a/bin/lamassu-display-config.js +++ b/bin/lamassu-display-config.js @@ -2,11 +2,11 @@ const settingsLoader = require('../lib/settings-loader') const pp = require('../lib/pp') settingsLoader.loadLatest() -.then(r => { - pp('config')(r) - process.exit(0) -}) -.catch(e => { - console.log(e.stack) - process.exit(1) -}) + .then(r => { + pp('config')(r) + process.exit(0) + }) + .catch(e => { + console.log(e.stack) + process.exit(1) + }) diff --git a/bin/migrate-config.js b/bin/migrate-config.js index ef8940c6..32e89ae2 100644 --- a/bin/migrate-config.js +++ b/bin/migrate-config.js @@ -8,38 +8,38 @@ const psqlUrl = require('../lib/options').postgresql const db = pgp(psqlUrl) db.many('select data from user_config', 'exchanges') -.then(rows => { - const config = rows.filter(r => r.type === 'exchanges')[0].data - const brain = rows.filter(r => r.type === 'unit')[0].data - const settings = config.exchanges.settings - const compliance = settings.compliance - const newConfig = { - global: { - cashInTransactionLimit: compliance.maximum.limit, - cashOutTransactionLimit: settings.fiatTxLimit, - cashInCommission: settings.commission, - cashOutCommission: settings.fiatCommission || settings.commission, - idVerificationEnabled: compliance.idVerificationEnabled, - idVerificationLimit: compliance.idVerificationLimit, - lowBalanceMargin: settings.lowBalanceMargin, - zeroConfLimit: settings.zeroConfLimit, - fiatCurrency: settings.currency, - topCashOutDenomination: settings.cartridges[0], - bottomCashOutDenomination: settings.cartridges[1], - virtualCashOutDenomination: settings.virtualCartridges[0], - machineLanguages: brain.locale.localeInfo.primaryLocales, - coins: settings.coins - }, - accounts: settings.plugins.settings - } + .then(rows => { + const config = rows.filter(r => r.type === 'exchanges')[0].data + const brain = rows.filter(r => r.type === 'unit')[0].data + const settings = config.exchanges.settings + const compliance = settings.compliance + const newConfig = { + global: { + cashInTransactionLimit: compliance.maximum.limit, + cashOutTransactionLimit: settings.fiatTxLimit, + cashInCommission: settings.commission, + cashOutCommission: settings.fiatCommission || settings.commission, + idVerificationEnabled: compliance.idVerificationEnabled, + idVerificationLimit: compliance.idVerificationLimit, + lowBalanceMargin: settings.lowBalanceMargin, + zeroConfLimit: settings.zeroConfLimit, + fiatCurrency: settings.currency, + topCashOutDenomination: settings.cartridges[0], + bottomCashOutDenomination: settings.cartridges[1], + virtualCashOutDenomination: settings.virtualCartridges[0], + machineLanguages: brain.locale.localeInfo.primaryLocales, + coins: settings.coins + }, + accounts: settings.plugins.settings + } - db.none('insert into user_config (type, data) values ($1, $2)', ['global', newConfig]) - .then(() => { - console.log('Success.') - process.exit(0) + db.none('insert into user_config (type, data) values ($1, $2)', ['global', newConfig]) + .then(() => { + console.log('Success.') + process.exit(0) + }) + .catch(err => { + console.error('Error: %s', err) + process.exit(1) + }) }) - .catch(err => { - console.error('Error: %s', err) - process.exit(1) - }) -}) diff --git a/dev/double-dispense.js b/dev/double-dispense.js index 34b8f5e0..81a652d7 100644 --- a/dev/double-dispense.js +++ b/dev/double-dispense.js @@ -13,7 +13,7 @@ const headers = { const body = JSON.stringify({tx: tx}) got('http://localhost:3000/dispense', {body: body, json: true, headers: headers}) -.then(res => { - console.log(res.body) -}) -.catch(console.log) + .then(res => { + console.log(res.body) + }) + .catch(console.log) diff --git a/dev/notify.js b/dev/notify.js index 3c97ce3f..d99f77b7 100644 --- a/dev/notify.js +++ b/dev/notify.js @@ -15,17 +15,17 @@ db.init(psqlUrl) notifier.init(db, getBalances, {lowBalanceThreshold: 10}) console.log('DEBUG0') notifier.checkStatus() -.then(function (alertRec) { - console.log('DEBUG1') - console.log('%j', alertRec) - var subject = notifier.alertSubject(alertRec) - console.log(subject) - var body = notifier.printEmailAlerts(alertRec) - console.log(body) - console.log(notifier.alertFingerprint(alertRec)) - process.exit(0) -}) -.catch(function (err) { - console.log(err.stack) - process.exit(1) -}) + .then(function (alertRec) { + console.log('DEBUG1') + console.log('%j', alertRec) + var subject = notifier.alertSubject(alertRec) + console.log(subject) + var body = notifier.printEmailAlerts(alertRec) + console.log(body) + console.log(notifier.alertFingerprint(alertRec)) + process.exit(0) + }) + .catch(function (err) { + console.log(err.stack) + process.exit(1) + }) diff --git a/dev/send-message.js b/dev/send-message.js index e4160951..bbc6e024 100644 --- a/dev/send-message.js +++ b/dev/send-message.js @@ -17,13 +17,13 @@ var rec = { var db = config.connection config.loadConfig(db) -.then(function (config) { - plugins.configure(config) - plugins.sendMessage(rec) - .then(function () { - console.log('Success.') + .then(function (config) { + plugins.configure(config) + plugins.sendMessage(rec) + .then(function () { + console.log('Success.') + }) + .catch(function (err) { + console.log(err.stack) + }) }) - .catch(function (err) { - console.log(err.stack) - }) -}) diff --git a/lib/admin/accounts.js b/lib/admin/accounts.js index 14081c18..51cea93a 100644 --- a/lib/admin/accounts.js +++ b/lib/admin/accounts.js @@ -8,22 +8,21 @@ const schemas = ph.loadSchemas() function fetchAccounts () { return db.oneOrNone('select data from user_config where type=$1', ['accounts']) - .then(row => { - + .then(row => { // Hard code this for now - const accounts = [{ - code: 'blockcypher', - display: 'Blockcypher', - fields: [ - { code: 'confidenceFactor', display: 'Confidence Factor', fieldType: 'integer', required: true, value: 90 } - ] - }] + const accounts = [{ + code: 'blockcypher', + display: 'Blockcypher', + fields: [ + { code: 'confidenceFactor', display: 'Confidence Factor', fieldType: 'integer', required: true, value: 90 } + ] + }] - return row - ? Promise.resolve(row.data.accounts) - : db.none('insert into user_config (type, data, valid) values ($1, $2, $3)', ['accounts', {accounts}, true]) - .then(fetchAccounts) - }) + return row + ? Promise.resolve(row.data.accounts) + : db.none('insert into user_config (type, data, valid) values ($1, $2, $3)', ['accounts', {accounts}, true]) + .then(fetchAccounts) + }) } function selectedAccounts () { diff --git a/lib/admin/admin-server.js b/lib/admin/admin-server.js index 1dd84223..10e07ab9 100644 --- a/lib/admin/admin-server.js +++ b/lib/admin/admin-server.js @@ -53,7 +53,7 @@ module.exports = {run} function dbNotify () { return got.post('http://localhost:3030/dbChange') - .catch(e => console.error('Error: lamassu-server not responding')) + .catch(e => console.error('Error: lamassu-server not responding')) } const skip = (req, res) => req.path === '/api/status/' && res.statusCode === 200 @@ -81,23 +81,23 @@ app.get('/api/totem', (req, res) => { if (!name) return res.status(400).send('Name is required') return pairing.totem(hostname, name) - .then(totem => res.send(totem)) + .then(totem => res.send(totem)) }) app.get('/api/accounts', (req, res) => { accounts.selectedAccounts() - .then(accounts => res.json({accounts: accounts})) + .then(accounts => res.json({accounts: accounts})) }) app.get('/api/account/:account', (req, res) => { accounts.getAccount(req.params.account) - .then(account => res.json(account)) + .then(account => res.json(account)) }) app.post('/api/account', (req, res) => { return accounts.updateAccount(req.body) - .then(account => res.json(account)) - .then(() => dbNotify()) + .then(account => res.json(account)) + .then(() => dbNotify()) }) app.get('/api/config/:config', (req, res) => @@ -105,133 +105,133 @@ app.get('/api/config/:config', (req, res) => app.post('/api/config', (req, res, next) => { config.saveConfigGroup(req.body) - .then(c => res.json(c)) - .then(() => dbNotify()) - .catch(next) + .then(c => res.json(c)) + .then(() => dbNotify()) + .catch(next) }) app.get('/api/accounts/account/:account', (req, res) => { accounts.getAccount(req.params.account) - .then(r => res.send(r)) + .then(r => res.send(r)) }) app.get('/api/machines', (req, res) => { machineLoader.getMachineNames() - .then(r => res.send({machines: r})) + .then(r => res.send({machines: r})) }) app.post('/api/machines', (req, res) => { machineLoader.setMachine(req.body) - .then(() => machineLoader.getMachineNames()) - .then(r => res.send({machines: r})) - .then(() => dbNotify()) + .then(() => machineLoader.getMachineNames()) + .then(r => res.send({machines: r})) + .then(() => dbNotify()) }) app.get('/api/funding', (req, res) => { return funding.getFunding() - .then(r => res.json(r)) + .then(r => res.json(r)) }) app.get('/api/funding/:cryptoCode', (req, res) => { const cryptoCode = req.params.cryptoCode return funding.getFunding(cryptoCode) - .then(r => res.json(r)) + .then(r => res.json(r)) }) app.get('/api/status', (req, res, next) => { return Promise.all([server.status(), config.validateCurrentConfig()]) - .then(([serverStatus, invalidConfigGroups]) => res.send({ - server: serverStatus, - invalidConfigGroups - })) - .catch(next) + .then(([serverStatus, invalidConfigGroups]) => res.send({ + server: serverStatus, + invalidConfigGroups + })) + .catch(next) }) app.get('/api/transactions', (req, res, next) => { return transactions.batch() - .then(r => res.send({transactions: r})) - .catch(next) + .then(r => res.send({transactions: r})) + .catch(next) }) app.get('/api/transaction/:id', (req, res, next) => { return transactions.single(req.params.id) - .then(r => { - if (!r) return res.status(404).send({Error: 'Not found'}) - return res.send(r) - }) + .then(r => { + if (!r) return res.status(404).send({Error: 'Not found'}) + return res.send(r) + }) }) app.patch('/api/transaction/:id', (req, res, next) => { if (!req.query.cancel) return res.status(400).send({Error: 'Requires cancel'}) return transactions.cancel(req.params.id) - .then(r => { - return res.send(r) - }) - .catch(() => res.status(404).send({Error: 'Not found'})) + .then(r => { + return res.send(r) + }) + .catch(() => res.status(404).send({Error: 'Not found'})) }) app.get('/api/customers', (req, res, next) => { return customers.batch() - .then(r => res.send({customers: r})) - .catch(next) + .then(r => res.send({customers: r})) + .catch(next) }) app.get('/api/customer/:id', (req, res, next) => { return customers.getById(req.params.id) - .then(r => { - if (!r) return res.status(404).send({Error: 'Not found'}) - return res.send(r) - }) + .then(r => { + if (!r) return res.status(404).send({Error: 'Not found'}) + return res.send(r) + }) }) app.get('/api/logs/:deviceId', (req, res, next) => { return logs.getMachineLogs(req.params.deviceId) - .then(r => res.send(r)) - .catch(next) + .then(r => res.send(r)) + .catch(next) }) app.get('/api/logs', (req, res, next) => { return machineLoader.getMachines() - .then(machines => { - const firstMachine = _.first(machines) - if (!firstMachine) return res.status(404).send({Error: 'No machines'}) - return logs.getMachineLogs(firstMachine.deviceId) - .then(r => res.send(r)) - }) - .catch(next) + .then(machines => { + const firstMachine = _.first(machines) + if (!firstMachine) return res.status(404).send({Error: 'No machines'}) + return logs.getMachineLogs(firstMachine.deviceId) + .then(r => res.send(r)) + }) + .catch(next) }) app.get('/api/support_logs', (req, res, next) => { return supportLogs.batch() - .then(supportLogs => res.send({ supportLogs })) - .catch(next) + .then(supportLogs => res.send({ supportLogs })) + .catch(next) }) app.get('/api/support_logs/logs', (req, res, next) => { return supportLogs.get(req.query.supportLogId) - .then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first)) - .then(result => { - const log = result || {} - return logs.getMachineLogs(log.deviceId, log.timestamp) - }) - .then(r => res.send(r)) - .catch(next) + .then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first)) + .then(result => { + const log = result || {} + return logs.getMachineLogs(log.deviceId, log.timestamp) + }) + .then(r => res.send(r)) + .catch(next) }) app.post('/api/support_logs', (req, res, next) => { return supportLogs.insert(req.query.deviceId) - .then(r => res.send(r)) - .catch(next) + .then(r => res.send(r)) + .catch(next) }) app.patch('/api/customer/:id', (req, res, next) => { if (!req.params.id) return res.status(400).send({Error: 'Requires id'}) const token = req.token || req.cookies.token return customers.update(req.params.id, req.query, token) - .then(r => res.send(r)) - .catch(() => res.status(404).send({Error: 'Not found'})) + .then(r => res.send(r)) + .catch(() => res.status(404).send({Error: 'Not found'})) }) app.use((err, req, res, next) => { @@ -253,35 +253,35 @@ function register (req, res, next) { if (!otp) return next() return login.register(otp) - .then(r => { - if (r.expired) return res.status(401).send('OTP expired, generate new registration link') + .then(r => { + if (r.expired) return res.status(401).send('OTP expired, generate new registration link') - // Maybe user is using old registration key, attempt to authenticate - if (!r.success) return next() + // Maybe user is using old registration key, attempt to authenticate + if (!r.success) return next() - const cookieOpts = { - httpOnly: true, - secure: true, - domain: hostname, - sameSite: true, - expires: NEVER - } + const cookieOpts = { + httpOnly: true, + secure: true, + domain: hostname, + sameSite: true, + expires: NEVER + } - const token = r.token - req.token = token - res.cookie('token', token, cookieOpts) - next() - }) + const token = r.token + req.token = token + res.cookie('token', token, cookieOpts) + next() + }) } function authenticate (req, res, next) { const token = req.token || req.cookies.token return login.authenticate(token) - .then(success => { - if (!success) return res.status(401).send('Authentication failed') - next() - }) + .then(success => { + if (!success) return res.status(401).send('Authentication failed') + next() + }) } process.on('unhandledRejection', err => { @@ -303,27 +303,27 @@ const wss = new WebSocket.Server({server: webServer}) function establishSocket (ws, token) { return login.authenticate(token) - .then(success => { - if (!success) return ws.close(1008, 'Authentication error') + .then(success => { + if (!success) return ws.close(1008, 'Authentication error') - const listener = data => { - ws.send(JSON.stringify(data)) - } + const listener = data => { + ws.send(JSON.stringify(data)) + } - // Reauthenticate every once in a while, in case token expired - setInterval(() => { - return login.authenticate(token) - .then(success => { - if (!success) { - socketEmitter.removeListener('message', listener) - ws.close() - } - }) - }, REAUTHENTICATE_INTERVAL) + // Reauthenticate every once in a while, in case token expired + setInterval(() => { + return login.authenticate(token) + .then(success => { + if (!success) { + socketEmitter.removeListener('message', listener) + ws.close() + } + }) + }, REAUTHENTICATE_INTERVAL) - socketEmitter.on('message', listener) - ws.send('Testing123') - }) + socketEmitter.on('message', listener) + ws.send('Testing123') + }) } wss.on('connection', ws => { diff --git a/lib/admin/admin-support.js b/lib/admin/admin-support.js index a310459a..85c9d250 100644 --- a/lib/admin/admin-support.js +++ b/lib/admin/admin-support.js @@ -32,25 +32,25 @@ const certOptions = { app.get('/api/support_logs', (req, res, next) => { return supportLogs.batch() - .then(supportLogs => res.send({ supportLogs })) - .catch(next) + .then(supportLogs => res.send({ supportLogs })) + .catch(next) }) app.get('/api/support_logs/logs', (req, res, next) => { return supportLogs.get(req.query.supportLogId) - .then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first)) - .then(result => { - const log = result || {} - return logs.getUnlimitedMachineLogs(log.deviceId, log.timestamp) - }) - .then(r => res.send(r)) - .catch(next) + .then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first)) + .then(result => { + const log = result || {} + return logs.getUnlimitedMachineLogs(log.deviceId, log.timestamp) + }) + .then(r => res.send(r)) + .catch(next) }) app.post('/api/support_logs', (req, res, next) => { return supportLogs.insert(req.query.deviceId) - .then(r => res.send(r)) - .catch(next) + .then(r => res.send(r)) + .catch(next) }) function run (port) { diff --git a/lib/admin/config.js b/lib/admin/config.js index 8783e135..1979bb68 100644 --- a/lib/admin/config.js +++ b/lib/admin/config.js @@ -19,7 +19,7 @@ function fetchSchema () { const schemaPath = path.resolve(options.lamassuServerPath, 'lamassu-schema.json') return fs.readFile(schemaPath) - .then(JSON.parse) + .then(JSON.parse) } function fetchConfig () { @@ -27,7 +27,7 @@ function fetchConfig () { order by id desc limit 1` return db.oneOrNone(sql, ['config']) - .then(row => row ? row.data.config : []) + .then(row => row ? row.data.config : []) } function allScopes (cryptoScopes, machineScopes) { @@ -65,11 +65,11 @@ function getField (schema, group, fieldCode) { } const fetchMachines = () => machineLoader.getMachines() -.then(machineList => machineList.map(r => r.deviceId)) + .then(machineList => machineList.map(r => r.deviceId)) function validateCurrentConfig () { return fetchConfig() - .then(configValidate.validateRequires) + .then(configValidate.validateRequires) } function decorateEnabledIf (schemaFields, schemaField) { @@ -85,43 +85,43 @@ function decorateEnabledIf (schemaFields, schemaField) { function fetchConfigGroup (code) { const fieldLocatorCodeEq = R.pathEq(['fieldLocator', 'code']) return Promise.all([fetchSchema(), fetchData(), fetchConfig(), fetchMachines()]) - .then(([schema, data, config, machineList]) => { - const groupSchema = schema.groups.find(r => r.code === code) + .then(([schema, data, config, machineList]) => { + const groupSchema = schema.groups.find(r => r.code === code) - if (!groupSchema) throw new Error('No such group schema: ' + code) + if (!groupSchema) throw new Error('No such group schema: ' + code) - const schemaFields = groupSchema.fields - .map(R.curry(getField)(schema, groupSchema)) - .map(f => _.assign(f, { - fieldEnabledIfAny: f.enabledIfAny || [], - fieldEnabledIfAll: f.enabledIfAll || [] - })) + const schemaFields = groupSchema.fields + .map(R.curry(getField)(schema, groupSchema)) + .map(f => _.assign(f, { + fieldEnabledIfAny: f.enabledIfAny || [], + fieldEnabledIfAll: f.enabledIfAll || [] + })) - const candidateFields = [ - schemaFields.map(R.prop('requiredIf')), - schemaFields.map(R.prop('enabledIfAny')), - schemaFields.map(R.prop('enabledIfAll')), - groupSchema.fields, - 'fiatCurrency' - ] - const configFields = R.uniq(R.flatten(candidateFields)).filter(R.identity) + const candidateFields = [ + schemaFields.map(R.prop('requiredIf')), + schemaFields.map(R.prop('enabledIfAny')), + schemaFields.map(R.prop('enabledIfAll')), + groupSchema.fields, + 'fiatCurrency' + ] + const configFields = R.uniq(R.flatten(candidateFields)).filter(R.identity) - const reducer = (acc, configField) => { - return acc.concat(config.filter(fieldLocatorCodeEq(configField))) - } + const reducer = (acc, configField) => { + return acc.concat(config.filter(fieldLocatorCodeEq(configField))) + } - const values = _.map(f => decorateEnabledIf(schema.fields, f), configFields.reduce(reducer, [])) + const values = _.map(f => decorateEnabledIf(schema.fields, f), configFields.reduce(reducer, [])) - groupSchema.fields = undefined - groupSchema.entries = schemaFields + groupSchema.fields = undefined + groupSchema.entries = schemaFields - return { - schema: groupSchema, - values, - selectedCryptos: getCryptos(config, machineList), - data - } - }) + return { + schema: groupSchema, + values, + selectedCryptos: getCryptos(config, machineList), + data + } + }) } function massageCurrencies (currencies) { @@ -154,55 +154,55 @@ const ALL_CRYPTOS = ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH'] function fetchData () { return machineLoader.getMachineNames() - .then(machineList => ({ - currencies: massageCurrencies(currencies), - cryptoCurrencies: [ - {crypto: 'BTC', display: 'Bitcoin'}, - {crypto: 'ETH', display: 'Ethereum'}, - {crypto: 'LTC', display: 'Litecoin'}, - {crypto: 'DASH', display: 'Dash'}, - {crypto: 'ZEC', display: 'Zcash'}, - {crypto: 'BCH', display: 'BCH'} - ], - languages: languages, - countries, - 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']}, - {code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, - {code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS}, - {code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']}, - {code: 'lnd', display: 'Lightning Network', class: 'wallet', cryptos: ['BTC']}, - {code: 'geth', display: 'geth', class: 'wallet', cryptos: ['ETH']}, - {code: 'zcashd', display: 'zcashd', class: 'wallet', cryptos: ['ZEC']}, - {code: 'litecoind', display: 'litecoind', class: 'wallet', cryptos: ['LTC']}, - {code: 'dashd', display: 'dashd', class: 'wallet', cryptos: ['DASH']}, - {code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']}, - {code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']}, - {code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, - {code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']}, - {code: 'mock-wallet', display: 'Mock wallet', class: 'wallet', cryptos: ALL_CRYPTOS}, - {code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS}, - {code: 'mock-exchange', display: 'Mock exchange', class: 'exchange', cryptos: ALL_CRYPTOS}, - {code: 'mock-sms', display: 'Mock SMS', class: 'sms'}, - {code: 'mock-id-verify', display: 'Mock ID verifier', class: 'idVerifier'}, - {code: 'twilio', display: 'Twilio', class: 'sms'}, - {code: 'mailjet', display: 'Mailjet', class: 'email'}, - {code: 'all-zero-conf', display: 'Always 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']}, - {code: 'no-zero-conf', display: 'Always 1-conf', class: 'zeroConf', cryptos: ALL_CRYPTOS}, - {code: 'blockcypher', display: 'Blockcypher', class: 'zeroConf', cryptos: ['BTC']}, - {code: 'mock-zero-conf', display: 'Mock 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']} - ], - machines: machineList.map(machine => ({machine: machine.deviceId, display: machine.name})) - })) + .then(machineList => ({ + currencies: massageCurrencies(currencies), + cryptoCurrencies: [ + {crypto: 'BTC', display: 'Bitcoin'}, + {crypto: 'ETH', display: 'Ethereum'}, + {crypto: 'LTC', display: 'Litecoin'}, + {crypto: 'DASH', display: 'Dash'}, + {crypto: 'ZEC', display: 'Zcash'}, + {crypto: 'BCH', display: 'BCH'} + ], + languages: languages, + countries, + 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']}, + {code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, + {code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS}, + {code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']}, + {code: 'lnd', display: 'Lightning Network', class: 'wallet', cryptos: ['BTC']}, + {code: 'geth', display: 'geth', class: 'wallet', cryptos: ['ETH']}, + {code: 'zcashd', display: 'zcashd', class: 'wallet', cryptos: ['ZEC']}, + {code: 'litecoind', display: 'litecoind', class: 'wallet', cryptos: ['LTC']}, + {code: 'dashd', display: 'dashd', class: 'wallet', cryptos: ['DASH']}, + {code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']}, + {code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']}, + {code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, + {code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']}, + {code: 'mock-wallet', display: 'Mock wallet', class: 'wallet', cryptos: ALL_CRYPTOS}, + {code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS}, + {code: 'mock-exchange', display: 'Mock exchange', class: 'exchange', cryptos: ALL_CRYPTOS}, + {code: 'mock-sms', display: 'Mock SMS', class: 'sms'}, + {code: 'mock-id-verify', display: 'Mock ID verifier', class: 'idVerifier'}, + {code: 'twilio', display: 'Twilio', class: 'sms'}, + {code: 'mailjet', display: 'Mailjet', class: 'email'}, + {code: 'all-zero-conf', display: 'Always 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']}, + {code: 'no-zero-conf', display: 'Always 1-conf', class: 'zeroConf', cryptos: ALL_CRYPTOS}, + {code: 'blockcypher', display: 'Blockcypher', class: 'zeroConf', cryptos: ['BTC']}, + {code: 'mock-zero-conf', display: 'Mock 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']} + ], + machines: machineList.map(machine => ({machine: machine.deviceId, display: machine.name})) + })) } function saveConfigGroup (results) { if (results.values.length === 0) return fetchConfigGroup(results.groupCode) return settingsLoader.modifyConfig(results.values) - .then(() => fetchConfigGroup(results.groupCode)) + .then(() => fetchConfigGroup(results.groupCode)) } module.exports = { diff --git a/lib/admin/funding.js b/lib/admin/funding.js index 9fcd75d3..b927b0bd 100644 --- a/lib/admin/funding.js +++ b/lib/admin/funding.js @@ -36,7 +36,7 @@ function getCryptos (config, machineList) { function fetchMachines () { return machineLoader.getMachines() - .then(machineList => machineList.map(r => r.deviceId)) + .then(machineList => machineList.map(r => r.deviceId)) } function computeCrypto (cryptoCode, _balance) { @@ -55,45 +55,45 @@ function computeFiat (rate, cryptoCode, _balance) { function getFunding (_cryptoCode) { return Promise.all([settingsLoader.loadLatest(), fetchMachines()]) - .then(([settings, machineList]) => { - const config = configManager.unscoped(settings.config) - const cryptoCodes = getCryptos(settings.config, machineList) - const cryptoCode = _cryptoCode || cryptoCodes[0] - const fiatCode = config.fiatCurrency - const pareCoins = c => _.includes(c.cryptoCode, cryptoCodes) - const cryptoCurrencies = coinUtils.cryptoCurrencies() - const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies) - const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) + .then(([settings, machineList]) => { + const config = configManager.unscoped(settings.config) + const cryptoCodes = getCryptos(settings.config, machineList) + const cryptoCode = _cryptoCode || cryptoCodes[0] + const fiatCode = config.fiatCurrency + const pareCoins = c => _.includes(c.cryptoCode, cryptoCodes) + const cryptoCurrencies = coinUtils.cryptoCurrencies() + const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies) + const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) - if (!cryptoRec) throw new Error(`Unsupported coin: ${cryptoCode}`) + if (!cryptoRec) throw new Error(`Unsupported coin: ${cryptoCode}`) - const promises = [ - wallet.newFunding(settings, cryptoCode), - ticker.getRates(settings, fiatCode, cryptoCode) - ] + const promises = [ + wallet.newFunding(settings, cryptoCode), + ticker.getRates(settings, fiatCode, cryptoCode) + ] - return Promise.all(promises) - .then(([fundingRec, ratesRec]) => { - const rates = ratesRec.rates - 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 fiatPending = computeFiat(rate, cryptoCode, pending) - const fundingAddress = fundingRec.fundingAddress - const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress) + return Promise.all(promises) + .then(([fundingRec, ratesRec]) => { + const rates = ratesRec.rates + 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 fiatPending = computeFiat(rate, cryptoCode, pending) + const fundingAddress = fundingRec.fundingAddress + const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress) - return { - cryptoCode, - cryptoDisplays, - fundingAddress, - fundingAddressUrl, - confirmedBalance: computeCrypto(cryptoCode, fundingConfirmedBalance).toFormat(5), - pending: computeCrypto(cryptoCode, pending).toFormat(5), - fiatConfirmedBalance: fiatConfirmedBalance.toFormat(2), - fiatPending: fiatPending.toFormat(2), - fiatCode - } + return { + cryptoCode, + cryptoDisplays, + fundingAddress, + fundingAddressUrl, + confirmedBalance: computeCrypto(cryptoCode, fundingConfirmedBalance).toFormat(5), + pending: computeCrypto(cryptoCode, pending).toFormat(5), + fiatConfirmedBalance: fiatConfirmedBalance.toFormat(2), + fiatPending: fiatPending.toFormat(2), + fiatCode + } + }) }) - }) } diff --git a/lib/admin/login.js b/lib/admin/login.js index 5d3b6444..c7cf852b 100644 --- a/lib/admin/login.js +++ b/lib/admin/login.js @@ -8,7 +8,7 @@ function generateOTP (name) { const sql = 'insert into one_time_passes (token, name) values ($1, $2)' return db.none(sql, [otp, name]) - .then(() => otp) + .then(() => otp) } function validateOTP (otp) { @@ -17,22 +17,22 @@ function validateOTP (otp) { returning name, created < now() - interval '1 hour' as expired` return db.one(sql, [otp]) - .then(r => ({success: !r.expired, expired: r.expired, name: r.name})) - .catch(() => ({success: false, expired: false})) + .then(r => ({success: !r.expired, expired: r.expired, name: r.name})) + .catch(() => ({success: false, expired: false})) } function register (otp) { return validateOTP(otp) - .then(r => { - if (!r.success) return r + .then(r => { + if (!r.success) return r - const token = crypto.randomBytes(32).toString('hex') - const sql = 'insert into user_tokens (token, name) values ($1, $2)' + const token = crypto.randomBytes(32).toString('hex') + const sql = 'insert into user_tokens (token, name) values ($1, $2)' - return db.none(sql, [token, r.name]) - .then(() => ({success: true, token: token})) - }) - .catch(() => ({success: false, expired: false})) + return db.none(sql, [token, r.name]) + .then(() => ({success: true, token: token})) + }) + .catch(() => ({success: false, expired: false})) } function authenticate (token) { diff --git a/lib/admin/pairing.js b/lib/admin/pairing.js index 854505de..0bdade02 100644 --- a/lib/admin/pairing.js +++ b/lib/admin/pairing.js @@ -17,17 +17,17 @@ function totem (hostname, name) { const caPath = options.caPath return readFile(caPath) - .then(data => { - const caHash = crypto.createHash('sha256').update(data).digest() - const token = crypto.randomBytes(32) - const hexToken = token.toString('hex') - const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex') - const buf = Buffer.concat([caHash, token, Buffer.from(hostname)]) - const sql = 'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)' + .then(data => { + const caHash = crypto.createHash('sha256').update(data).digest() + const token = crypto.randomBytes(32) + const hexToken = token.toString('hex') + const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex') + const buf = Buffer.concat([caHash, token, Buffer.from(hostname)]) + const sql = 'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)' - return db.none(sql, [hexToken, caHexToken, name]) - .then(() => bsAlpha.encode(buf)) - }) + return db.none(sql, [hexToken, caHexToken, name]) + .then(() => bsAlpha.encode(buf)) + }) } module.exports = {totem, unpair} diff --git a/lib/admin/server.js b/lib/admin/server.js index 9095f834..decde990 100644 --- a/lib/admin/server.js +++ b/lib/admin/server.js @@ -11,8 +11,8 @@ const CONSIDERED_UP_SECS = 30 function checkWasConfigured () { return settingsLoader.loadLatest() - .then(() => true) - .catch(() => false) + .then(() => true) + .catch(() => false) } function machinesLastPing () { @@ -21,28 +21,28 @@ function machinesLastPing () { group by device_id` return Promise.all([machineLoader.getMachineNames(), db.any(sql)]) - .then(([machines, events]) => { - if (machines.length === 0) return 'No paired machines' + .then(([machines, events]) => { + if (machines.length === 0) return 'No paired machines' - const addName = event => { - const machine = _.find(['deviceId', event.deviceId], machines) - if (!machine) return null - return _.set('name', machine.name, event) - } + const addName = event => { + const machine = _.find(['deviceId', event.deviceId], machines) + if (!machine) return null + return _.set('name', machine.name, event) + } - const mapper = _.flow(_.filter(row => row.age > CONSIDERED_UP_SECS), _.map(addName), _.compact) - const downRows = mapper(events) + const mapper = _.flow(_.filter(row => row.age > CONSIDERED_UP_SECS), _.map(addName), _.compact) + const downRows = mapper(events) - if (downRows.length === 0) return 'All machines are up' + if (downRows.length === 0) return 'All machines are up' - if (downRows.length === 1) { - const row = downRows[0] - const age = moment.duration(row.age, 'seconds') - return `${row.name} down for ${age.humanize()}` - } + if (downRows.length === 1) { + const row = downRows[0] + const age = moment.duration(row.age, 'seconds') + return `${row.name} down for ${age.humanize()}` + } - return 'Multiple machines down' - }) + return 'Multiple machines down' + }) } function status () { @@ -53,32 +53,32 @@ function status () { limit 1` return Promise.all([checkWasConfigured(), db.oneOrNone(sql, ['ping']), machinesLastPing()]) - .then(([wasConfigured, statusRow, machineStatus]) => { - const age = statusRow && moment.duration(statusRow.age, 'seconds') - const up = statusRow ? statusRow.age < CONSIDERED_UP_SECS : false - const lastPing = statusRow && age.humanize() + .then(([wasConfigured, statusRow, machineStatus]) => { + const age = statusRow && moment.duration(statusRow.age, 'seconds') + const up = statusRow ? statusRow.age < CONSIDERED_UP_SECS : false + const lastPing = statusRow && age.humanize() - return settingsLoader.loadLatest() - .catch(() => null) - .then(settings => { - return getRates(settings) - .then(rates => ({wasConfigured, up, lastPing, rates, machineStatus})) + return settingsLoader.loadLatest() + .catch(() => null) + .then(settings => { + return getRates(settings) + .then(rates => ({wasConfigured, up, lastPing, rates, machineStatus})) + }) }) - }) } function getRates (settings) { if (!settings) return Promise.resolve([]) return ticker.getRates(settings, 'USD', 'BTC') - .then(ratesRec => { - return [{ - crypto: 'BTC', - bid: parseFloat(ratesRec.rates.bid), - ask: parseFloat(ratesRec.rates.ask) - }] - }) - .catch(() => []) + .then(ratesRec => { + return [{ + crypto: 'BTC', + bid: parseFloat(ratesRec.rates.bid), + ask: parseFloat(ratesRec.rates.ask) + }] + }) + .catch(() => []) } module.exports = {status} diff --git a/lib/admin/transactions.js b/lib/admin/transactions.js index d7591858..2367d7e7 100644 --- a/lib/admin/transactions.js +++ b/lib/admin/transactions.js @@ -9,15 +9,15 @@ const NUM_RESULTS = 20 function addNames (txs) { return machineLoader.getMachineNames() - .then(machines => { - const addName = tx => { - const machine = _.find(['deviceId', tx.deviceId], machines) - const name = machine ? machine.name : 'Unpaired' - return _.set('machineName', name, tx) - } + .then(machines => { + const addName = tx => { + const machine = _.find(['deviceId', tx.deviceId], machines) + const name = machine ? machine.name : 'Unpaired' + return _.set('machineName', name, tx) + } - return _.map(addName, txs) - }) + return _.map(addName, txs) + }) } const camelize = _.mapKeys(_.camelCase) @@ -36,7 +36,7 @@ function batch () { order by created desc limit $1` return Promise.all([db.any(cashInSql, [cashInTx.PENDING_INTERVAL, NUM_RESULTS]), db.any(cashOutSql, [NUM_RESULTS])]) - .then(packager) + .then(packager) } function single (txId) { @@ -56,13 +56,13 @@ function single (txId) { db.oneOrNone(cashInSql, [cashInTx.PENDING_INTERVAL, txId]), db.oneOrNone(cashOutSql, [txId]) ]) - .then(packager) - .then(_.head) + .then(packager) + .then(_.head) } function cancel (txId) { return tx.cancel(txId) - .then(() => single(txId)) + .then(() => single(txId)) } module.exports = {batch, single, cancel} diff --git a/lib/app.js b/lib/app.js index 580ad4dc..b4ef4bfa 100644 --- a/lib/app.js +++ b/lib/app.js @@ -24,8 +24,8 @@ function run () { } const runner = () => runOnce() - .then(() => clearInterval(handler)) - .catch(errorHandler) + .then(() => clearInterval(handler)) + .catch(errorHandler) const handler = setInterval(runner, 10000) return runner() @@ -33,35 +33,35 @@ function run () { function runOnce () { return settingsLoader.loadLatest() - .then(settings => { - poller.start(settings) + .then(settings => { + poller.start(settings) - const httpsServerOptions = { - key: fs.readFileSync(options.keyPath), - cert: fs.readFileSync(options.certPath), - requestCert: true, - rejectUnauthorized: false - } + const httpsServerOptions = { + key: fs.readFileSync(options.keyPath), + cert: fs.readFileSync(options.certPath), + requestCert: true, + rejectUnauthorized: false + } - const server = devMode - ? http.createServer(routes.app) - : https.createServer(httpsServerOptions, routes.app) + const server = devMode + ? http.createServer(routes.app) + : https.createServer(httpsServerOptions, routes.app) - const port = argv.port || 3000 - const localPort = 3030 - const localServer = http.createServer(routes.localApp) + const port = argv.port || 3000 + const localPort = 3030 + const localServer = http.createServer(routes.localApp) - if (options.devMode) logger.info('In dev mode') + if (options.devMode) logger.info('In dev mode') - server.listen(port, () => { - console.log('lamassu-server listening on port ' + + server.listen(port, () => { + console.log('lamassu-server listening on port ' + port + ' ' + (devMode ? '(http)' : '(https)')) - }) + }) - localServer.listen(localPort, 'localhost', () => { - console.log('lamassu-server listening on local port ' + localPort) + localServer.listen(localPort, 'localhost', () => { + console.log('lamassu-server listening on local port ' + localPort) + }) }) - }) } module.exports = {run} diff --git a/lib/apply-defaults.js b/lib/apply-defaults.js index 5834b0f3..0d46a516 100644 --- a/lib/apply-defaults.js +++ b/lib/apply-defaults.js @@ -13,36 +13,36 @@ module.exports = {run} function run () { return Promise.resolve() - .then(() => { - schema.groups.forEach(group => { - return group.fields.forEach(fieldCode => { - const field = schema.fields.find(r => r.code === fieldCode) - if (!field) throw new Error('No such field: ' + fieldCode) - if (_.isNil(field.default)) return - if (group.machineScope === 'specific') return + .then(() => { + schema.groups.forEach(group => { + return group.fields.forEach(fieldCode => { + const field = schema.fields.find(r => r.code === fieldCode) + if (!field) throw new Error('No such field: ' + fieldCode) + if (_.isNil(field.default)) return + if (group.machineScope === 'specific') return - const crypto = group.cryptoScope === 'specific' - ? DEFAULT_CRYPTO - : 'global' + const crypto = group.cryptoScope === 'specific' + ? DEFAULT_CRYPTO + : 'global' - return newFields.push({ - fieldLocator: { - fieldScope: { - crypto, - machine: 'global' + return newFields.push({ + fieldLocator: { + fieldScope: { + crypto, + machine: 'global' + }, + code: fieldCode, + fieldType: field.fieldType, + fieldClass: field.fieldClass }, - code: fieldCode, - fieldType: field.fieldType, - fieldClass: field.fieldClass - }, - fieldValue: { - fieldType: field.fieldType, - value: field.default - } + fieldValue: { + fieldType: field.fieldType, + value: field.default + } + }) }) }) - }) - return settingsLoader.save(newFields) - }) + return settingsLoader.save(newFields) + }) } diff --git a/lib/blockchain/install.js b/lib/blockchain/install.js index 96c8d1d9..8778800b 100644 --- a/lib/blockchain/install.js +++ b/lib/blockchain/install.js @@ -112,6 +112,5 @@ function run () { }) inquirer.prompt(questions) - .then(answers => processCryptos(answers.crypto)) + .then(answers => processCryptos(answers.crypto)) } - diff --git a/lib/cash-in/cash-in-atomic.js b/lib/cash-in/cash-in-atomic.js index 899ce501..ea551d5c 100644 --- a/lib/cash-in/cash-in-atomic.js +++ b/lib/cash-in/cash-in-atomic.js @@ -17,21 +17,21 @@ function atomic (machineTx, pi) { const sql2 = 'select * from bills where cash_in_txs_id=$1' return t.oneOrNone(sql, [machineTx.id]) - .then(row => { - if (row && row.tx_version >= machineTx.txVersion) throw new E.StaleTxError('Stale tx') + .then(row => { + if (row && row.tx_version >= machineTx.txVersion) throw new E.StaleTxError('Stale tx') - return t.any(sql2, [machineTx.id]) - .then(billRows => { - const dbTx = cashInLow.toObj(row) + return t.any(sql2, [machineTx.id]) + .then(billRows => { + const dbTx = cashInLow.toObj(row) - return preProcess(dbTx, machineTx, pi) - .then(preProcessedTx => cashInLow.upsert(t, dbTx, preProcessedTx)) - .then(r => { - return insertNewBills(t, billRows, machineTx) - .then(newBills => _.set('newBills', newBills, r)) - }) + return preProcess(dbTx, machineTx, pi) + .then(preProcessedTx => cashInLow.upsert(t, dbTx, preProcessedTx)) + .then(r => { + return insertNewBills(t, billRows, machineTx) + .then(newBills => _.set('newBills', newBills, r)) + }) + }) }) - }) } transaction.txMode = tmSRD @@ -48,7 +48,7 @@ function insertNewBills (t, billRows, machineTx) { const sql = pgp.helpers.insert(dbBills, columns, 'bills') return t.none(sql) - .then(() => bills) + .then(() => bills) } function pullNewBills (billRows, machineTx) { diff --git a/lib/cash-in/cash-in-low.js b/lib/cash-in/cash-in-low.js index 8326a00a..fc42d868 100644 --- a/lib/cash-in/cash-in-low.js +++ b/lib/cash-in/cash-in-low.js @@ -15,8 +15,8 @@ module.exports = {toObj, upsert, insert, update, massage, isClearToSend} function convertBigNumFields (obj) { const convert = value => value && value.isBigNumber - ? value.toString() - : value + ? value.toString() + : value return _.mapValues(convert, obj) } @@ -45,18 +45,18 @@ function toObj (row) { function upsert (t, dbTx, preProcessedTx) { if (!dbTx) { return insert(t, preProcessedTx) - .then(tx => ({dbTx, tx})) + .then(tx => ({dbTx, tx})) } return update(t, dbTx, diff(dbTx, preProcessedTx)) - .then(tx => ({dbTx, tx})) + .then(tx => ({dbTx, tx})) } function insert (t, tx) { const dbTx = massage(tx) const sql = pgp.helpers.insert(dbTx, null, 'cash_in_txs') + ' returning *' return t.one(sql) - .then(toObj) + .then(toObj) } function update (t, tx, changes) { @@ -67,7 +67,7 @@ function update (t, tx, changes) { pgp.as.format(' where id=$1', [tx.id]) + ' returning *' return t.one(sql) - .then(toObj) + .then(toObj) } function diff (oldTx, newTx) { diff --git a/lib/cash-in/cash-in-tx.js b/lib/cash-in/cash-in-tx.js index ce74ac0c..3126d8e3 100644 --- a/lib/cash-in/cash-in-tx.js +++ b/lib/cash-in/cash-in-tx.js @@ -16,13 +16,13 @@ module.exports = {post, monitorPending, cancel, PENDING_INTERVAL} function post (machineTx, pi) { return db.tx(cashInAtomic.atomic(machineTx, pi)) - .then(r => { - const updatedTx = r.tx + .then(r => { + const updatedTx = r.tx - return postProcess(r, pi) - .then(changes => cashInLow.update(db, updatedTx, changes)) - .then(tx => _.set('bills', machineTx.bills, tx)) - }) + return postProcess(r, pi) + .then(changes => cashInLow.update(db, updatedTx, changes)) + .then(tx => _.set('bills', machineTx.bills, tx)) + }) } function registerTrades (pi, newBills) { @@ -41,7 +41,7 @@ function logAction (rec, tx) { const sql = pgp.helpers.insert(action, null, 'cash_in_actions') return db.none(sql) - .then(_.constant(rec)) + .then(_.constant(rec)) } function logActionById (action, _rec, txId) { @@ -57,30 +57,30 @@ function postProcess (r, pi) { if (!cashInLow.isClearToSend(r.dbTx, r.tx)) return Promise.resolve({}) return pi.sendCoins(r.tx) - .then(txHash => ({ - txHash, - sendConfirmed: true, - sendTime: 'now()^', - sendPending: false, - error: null, - errorCode: null - })) - .catch(err => { + .then(txHash => ({ + txHash, + sendConfirmed: true, + sendTime: 'now()^', + sendPending: false, + error: null, + errorCode: null + })) + .catch(err => { // Important: We don't know what kind of error this is // so not safe to assume that funds weren't sent. // Therefore, don't set sendPending to false except for // errors (like InsufficientFundsError) that are guaranteed // not to send. - const sendPending = err.name !== 'InsufficientFundsError' + const sendPending = err.name !== 'InsufficientFundsError' - return { - sendTime: 'now()^', - error: err.message, - errorCode: err.name, - sendPending - } - }) - .then(sendRec => logAction(sendRec, r.tx)) + return { + sendTime: 'now()^', + error: err.message, + errorCode: err.name, + sendPending + } + }) + .then(sendRec => logAction(sendRec, r.tx)) } function monitorPending (settings) { @@ -98,12 +98,12 @@ function monitorPending (settings) { const pi = plugins(settings, tx.deviceId) return post(tx, pi) - .catch(logger.error) + .catch(logger.error) } return db.any(sql, [PENDING_INTERVAL, MAX_PENDING]) - .then(rows => pEachSeries(rows, row => processPending(row))) - .catch(logger.error) + .then(rows => pEachSeries(rows, row => processPending(row))) + .catch(logger.error) } function cancel (txId) { @@ -114,13 +114,13 @@ function cancel (txId) { } return Promise.resolve() - .then(() => { - return pgp.helpers.update(updateRec, null, 'cash_in_txs') + + .then(() => { + return pgp.helpers.update(updateRec, null, 'cash_in_txs') + pgp.as.format(' where id=$1', [txId]) - }) - .then(sql => db.result(sql, false)) - .then(res => { - if (res.rowCount !== 1) throw new Error('No such tx-id') - }) - .then(() => logActionById('operatorCompleted', {}, txId)) + }) + .then(sql => db.result(sql, false)) + .then(res => { + if (res.rowCount !== 1) throw new Error('No such tx-id') + }) + .then(() => logActionById('operatorCompleted', {}, txId)) } diff --git a/lib/cash-out/cash-out-actions.js b/lib/cash-out/cash-out-actions.js index 5387514a..f63b3296 100644 --- a/lib/cash-out/cash-out-actions.js +++ b/lib/cash-out/cash-out-actions.js @@ -22,7 +22,7 @@ function logAction (t, action, _rec, tx) { const sql = pgp.helpers.insert(rec, null, 'cash_out_actions') return t.none(sql) - .then(_.constant(tx)) + .then(_.constant(tx)) } function logError (t, action, err, tx) { diff --git a/lib/cash-out/cash-out-atomic.js b/lib/cash-out/cash-out-atomic.js index bd9ec167..fd438875 100644 --- a/lib/cash-out/cash-out-atomic.js +++ b/lib/cash-out/cash-out-atomic.js @@ -21,14 +21,14 @@ function atomic (tx, pi, fromClient) { const sql = 'select * from cash_out_txs where id=$1' return t.oneOrNone(sql, [tx.id]) - .then(toObj) - .then(oldTx => { - const isStale = fromClient && oldTx && (oldTx.txVersion >= tx.txVersion) - if (isStale) throw new E.StaleTxError('Stale tx') + .then(toObj) + .then(oldTx => { + const isStale = fromClient && oldTx && (oldTx.txVersion >= tx.txVersion) + if (isStale) throw new E.StaleTxError('Stale tx') - return preProcess(t, oldTx, tx, pi) - .then(preProcessedTx => cashOutLow.upsert(t, oldTx, preProcessedTx)) - }) + return preProcess(t, oldTx, tx, pi) + .then(preProcessedTx => cashOutLow.upsert(t, oldTx, preProcessedTx)) + }) } transaction.txMode = tmSRD @@ -39,61 +39,61 @@ function atomic (tx, pi, fromClient) { function preProcess (t, oldTx, newTx, pi) { if (!oldTx) { return pi.isHd(newTx) - .then(isHd => nextHd(t, isHd, newTx)) - .then(newTxHd => { - return pi.newAddress(newTxHd) - .then(_.set('toAddress', _, newTxHd)) - .then(_.unset('isLightning')) - }) - .then(addressedTx => { - const rec = {to_address: addressedTx.toAddress} - return cashOutActions.logAction(t, 'provisionAddress', rec, addressedTx) - }) - .catch(err => { - return cashOutActions.logError(t, 'provisionAddress', err, newTx) - .then(() => { throw err }) - }) + .then(isHd => nextHd(t, isHd, newTx)) + .then(newTxHd => { + return pi.newAddress(newTxHd) + .then(_.set('toAddress', _, newTxHd)) + .then(_.unset('isLightning')) + }) + .then(addressedTx => { + const rec = {to_address: addressedTx.toAddress} + return cashOutActions.logAction(t, 'provisionAddress', rec, addressedTx) + }) + .catch(err => { + return cashOutActions.logError(t, 'provisionAddress', err, newTx) + .then(() => { throw err }) + }) } return Promise.resolve(updateStatus(oldTx, newTx)) - .then(updatedTx => { - if (updatedTx.status !== oldTx.status) { - const isZeroConf = pi.isZeroConf(updatedTx) - if (wasJustAuthorized(oldTx, updatedTx, isZeroConf)) pi.sell(updatedTx) + .then(updatedTx => { + if (updatedTx.status !== oldTx.status) { + const isZeroConf = pi.isZeroConf(updatedTx) + if (wasJustAuthorized(oldTx, updatedTx, isZeroConf)) pi.sell(updatedTx) - const rec = { - to_address: updatedTx.toAddress, - tx_hash: updatedTx.txHash + const rec = { + to_address: updatedTx.toAddress, + tx_hash: updatedTx.txHash + } + + return cashOutActions.logAction(t, updatedTx.status, rec, updatedTx) } - return cashOutActions.logAction(t, updatedTx.status, rec, updatedTx) - } + const hasError = !oldTx.error && newTx.error + const hasDispenseOccurred = !dispenseOccurred(oldTx.bills) && dispenseOccurred(newTx.bills) - const hasError = !oldTx.error && newTx.error - const hasDispenseOccurred = !dispenseOccurred(oldTx.bills) && dispenseOccurred(newTx.bills) + if (hasError || hasDispenseOccurred) { + return cashOutActions.logDispense(t, updatedTx) + .then(updateCassettes(t, updatedTx)) + } - if (hasError || hasDispenseOccurred) { - return cashOutActions.logDispense(t, updatedTx) - .then(updateCassettes(t, updatedTx)) - } + if (!oldTx.phone && newTx.phone) { + return cashOutActions.logAction(t, 'addPhone', {}, updatedTx) + } - if (!oldTx.phone && newTx.phone) { - return cashOutActions.logAction(t, 'addPhone', {}, updatedTx) - } + if (!oldTx.redeem && newTx.redeem) { + return cashOutActions.logAction(t, 'redeemLater', {}, updatedTx) + } - if (!oldTx.redeem && newTx.redeem) { - return cashOutActions.logAction(t, 'redeemLater', {}, updatedTx) - } - - return updatedTx - }) + return updatedTx + }) } function nextHd (t, isHd, tx) { if (!isHd) return Promise.resolve(tx) return t.one("select nextval('hd_indices_seq') as hd_index") - .then(row => _.set('hdIndex', row.hd_index, tx)) + .then(row => _.set('hdIndex', row.hd_index, tx)) } function updateCassettes (t, tx) { @@ -112,7 +112,7 @@ function updateCassettes (t, tx) { ] return t.one(sql, values) - .then(r => socket.emit(_.assign(r, {op: 'cassetteUpdate', deviceId: tx.deviceId}))) + .then(r => socket.emit(_.assign(r, {op: 'cassetteUpdate', deviceId: tx.deviceId}))) } function wasJustAuthorized (oldTx, newTx, isZeroConf) { @@ -138,12 +138,12 @@ function updateStatus (oldTx, newTx) { const newStatus = ratchetStatus(oldStatus, newTx.status) const publishedAt = !oldTx.publishedAt && isPublished(newStatus) - ? 'now()^' - : undefined + ? 'now()^' + : undefined const confirmedAt = !oldTx.confirmedAt && isConfirmed(newStatus) - ? 'now()^' - : undefined + ? 'now()^' + : undefined const updateRec = { publishedAt, diff --git a/lib/cash-out/cash-out-helper.js b/lib/cash-out/cash-out-helper.js index 17fa53d7..f6e79357 100644 --- a/lib/cash-out/cash-out-helper.js +++ b/lib/cash-out/cash-out-helper.js @@ -12,12 +12,12 @@ const mapValuesWithKey = _.mapValues.convert({cap: false}) function convertBigNumFields (obj) { const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat']) - ? value.toString() - : value + ? value.toString() + : value const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat']) - ? key + '#' - : key + ? key + '#' + : key return _.mapKeys(convertKey, mapValuesWithKey(convert, obj)) } @@ -91,5 +91,5 @@ function redeemableTxs (deviceId) { and (extract(epoch from (now() - greatest(created, confirmed_at))) * 1000) < $4` return db.any(sql, [deviceId, true, false, REDEEMABLE_AGE]) - .then(_.map(toObj)) + .then(_.map(toObj)) } diff --git a/lib/cash-out/cash-out-low.js b/lib/cash-out/cash-out-low.js index 1040b5db..a237bfb3 100644 --- a/lib/cash-out/cash-out-low.js +++ b/lib/cash-out/cash-out-low.js @@ -14,11 +14,11 @@ module.exports = {upsert, update, insert} function upsert (t, oldTx, tx) { if (!oldTx) { return insert(t, tx) - .then(newTx => [oldTx, newTx]) + .then(newTx => [oldTx, newTx]) } return update(t, tx, diff(oldTx, tx)) - .then(newTx => [oldTx, newTx]) + .then(newTx => [oldTx, newTx]) } function insert (t, tx) { @@ -26,7 +26,7 @@ function insert (t, tx) { const sql = pgp.helpers.insert(dbTx, null, 'cash_out_txs') + ' returning *' return t.one(sql) - .then(toObj) + .then(toObj) } function update (t, tx, changes) { @@ -39,7 +39,7 @@ function update (t, tx, changes) { const newTx = _.merge(tx, changes) return t.none(sql) - .then(() => newTx) + .then(() => newTx) } function diff (oldTx, newTx) { diff --git a/lib/cash-out/cash-out-tx.js b/lib/cash-out/cash-out-tx.js index 27b3e0af..ac865e05 100644 --- a/lib/cash-out/cash-out-tx.js +++ b/lib/cash-out/cash-out-tx.js @@ -43,11 +43,11 @@ 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) - .then(changes => cashOutLow.update(db, newTx, changes)) - }) + .then(txVector => { + const [, newTx] = txVector + return postProcess(txVector, pi) + .then(changes => cashOutLow.update(db, newTx, changes)) + }) } function postProcess (txVector, pi) { @@ -55,32 +55,32 @@ function postProcess (txVector, pi) { if ((newTx.dispense && !oldTx.dispense) || (newTx.redeem && !oldTx.redeem)) { return pi.buildAvailableCassettes(newTx.id) - .then(cassettes => { - const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat) + .then(cassettes => { + const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat) - if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE) - return bills - }) - .then(bills => { - const provisioned1 = bills[0].provisioned - const provisioned2 = bills[1].provisioned - const denomination1 = bills[0].denomination - const denomination2 = bills[1].denomination + if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE) + return bills + }) + .then(bills => { + const provisioned1 = bills[0].provisioned + const provisioned2 = bills[1].provisioned + const denomination1 = bills[0].denomination + const denomination2 = bills[1].denomination - const rec = { - provisioned_1: provisioned1, - provisioned_2: provisioned2, - denomination_1: denomination1, - denomination_2: denomination2 - } + const rec = { + provisioned_1: provisioned1, + provisioned_2: provisioned2, + denomination_1: denomination1, + denomination_2: denomination2 + } - return cashOutActions.logAction(db, 'provisionNotes', rec, newTx) - .then(_.constant({bills})) - }) - .catch(err => { - return cashOutActions.logError(db, 'provisionNotesError', err, newTx) - .then(() => { throw err }) - }) + return cashOutActions.logAction(db, 'provisionNotes', rec, newTx) + .then(_.constant({bills})) + }) + .catch(err => { + return cashOutActions.logError(db, 'provisionNotesError', err, newTx) + .then(() => { throw err }) + }) } return Promise.resolve({}) @@ -95,31 +95,31 @@ function fetchOpenTxs (statuses, age) { const statusClause = _.map(pgp.as.text, statuses).join(',') return db.any(sql, [age, statusClause]) - .then(rows => rows.map(toObj)) + .then(rows => rows.map(toObj)) } function processTxStatus (tx, settings) { const pi = plugins(settings, tx.deviceId) return pi.getStatus(tx) - .then(res => _.assign(tx, {status: res.status})) - .then(_tx => selfPost(_tx, pi)) + .then(res => _.assign(tx, {status: res.status})) + .then(_tx => selfPost(_tx, pi)) } function monitorLiveIncoming (settings) { const statuses = ['notSeen', 'published', 'insufficientFunds'] return fetchOpenTxs(statuses, STALE_LIVE_INCOMING_TX_AGE) - .then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings))) - .catch(logger.error) + .then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings))) + .catch(logger.error) } function monitorStaleIncoming (settings) { const statuses = ['notSeen', 'published', 'authorized', 'instant', 'rejected', 'insufficientFunds'] return fetchOpenTxs(statuses, STALE_INCOMING_TX_AGE) - .then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings))) - .catch(logger.error) + .then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings))) + .catch(logger.error) } function monitorUnnotified (settings) { @@ -133,9 +133,9 @@ function monitorUnnotified (settings) { const notify = tx => plugins(settings, tx.deviceId).notifyConfirmation(tx) return db.any(sql, [MAX_NOTIFY_AGE, false, false, true, MIN_NOTIFY_AGE]) - .then(rows => _.map(toObj, rows)) - .then(txs => Promise.all(txs.map(notify))) - .catch(logger.error) + .then(rows => _.map(toObj, rows)) + .then(txs => Promise.all(txs.map(notify))) + .catch(logger.error) } function cancel (txId) { @@ -146,13 +146,13 @@ function cancel (txId) { } return Promise.resolve() - .then(() => { - return pgp.helpers.update(updateRec, null, 'cash_out_txs') + + .then(() => { + return pgp.helpers.update(updateRec, null, 'cash_out_txs') + pgp.as.format(' where id=$1', [txId]) - }) - .then(sql => db.result(sql, false)) - .then(res => { - if (res.rowCount !== 1) throw new Error('No such tx-id') - }) - .then(() => cashOutActions.logActionById(db, 'operatorCompleted', {}, txId)) + }) + .then(sql => db.result(sql, false)) + .then(res => { + if (res.rowCount !== 1) throw new Error('No such tx-id') + }) + .then(() => cashOutActions.logActionById(db, 'operatorCompleted', {}, txId)) } diff --git a/lib/config-validate.js b/lib/config-validate.js index b37d2b39..c0b6fc6a 100644 --- a/lib/config-validate.js +++ b/lib/config-validate.js @@ -66,12 +66,12 @@ function satisfiesRequire (config, cryptos, machineList, field, anyFields, allFi function isScopeEnabled (config, cryptos, machineList, refField, scope) { const [cryptoScope, machineScope] = scope const candidateCryptoScopes = cryptoScope === 'global' - ? allCryptoScopes(cryptos, refField.cryptoScope) - : [cryptoScope] + ? allCryptoScopes(cryptos, refField.cryptoScope) + : [cryptoScope] const candidateMachineScopes = machineScope === 'global' - ? allMachineScopes(machineList, refField.machineScope) - : [ machineScope ] + ? allMachineScopes(machineList, refField.machineScope) + : [ machineScope ] const allRefCandidateScopes = allScopes(candidateCryptoScopes, candidateMachineScopes) const getFallbackValue = scope => configManager.scopedValue(scope[0], scope[1], refField.code, config) @@ -108,13 +108,13 @@ function getMachines () { function fetchMachines () { return getMachines() - .then(machineList => machineList.map(r => r.device_id)) + .then(machineList => machineList.map(r => r.device_id)) } function validateFieldParameter (value, validator) { switch (validator.code) { case 'required': - return true // We don't validate this here + return true // We don't validate this here case 'min': return value >= validator.min case 'max': @@ -128,58 +128,58 @@ function ensureConstraints (config) { const pickField = fieldCode => schema.fields.find(r => r.code === fieldCode) return Promise.resolve() - .then(() => { - config.every(fieldInstance => { - const fieldCode = fieldInstance.fieldLocator.code - const field = pickField(fieldCode) - if (!field) { - logger.warn('No such field: %s, %j', fieldCode, fieldInstance.fieldLocator.fieldScope) - return - } + .then(() => { + config.every(fieldInstance => { + const fieldCode = fieldInstance.fieldLocator.code + const field = pickField(fieldCode) + if (!field) { + logger.warn('No such field: %s, %j', fieldCode, fieldInstance.fieldLocator.fieldScope) + return + } - const fieldValue = fieldInstance.fieldValue + const fieldValue = fieldInstance.fieldValue - const isValid = field.fieldValidation - .every(validator => validateFieldParameter(fieldValue.value, validator)) + const isValid = field.fieldValidation + .every(validator => validateFieldParameter(fieldValue.value, validator)) - if (isValid) return true - throw new Error('Invalid config value') + if (isValid) return true + throw new Error('Invalid config value') + }) }) - }) } const pp = require('./pp') function validateRequires (config) { return fetchMachines() - .then(machineList => { - const cryptos = getCryptos(config, machineList) + .then(machineList => { + const cryptos = getCryptos(config, machineList) - return schema.groups.filter(group => { - return group.fields.some(fieldCode => { - const field = getGroupField(group, fieldCode) + return schema.groups.filter(group => { + return group.fields.some(fieldCode => { + const field = getGroupField(group, fieldCode) - if (!field.fieldValidation.find(r => r.code === 'required')) return false + if (!field.fieldValidation.find(r => r.code === 'required')) return false - const refFieldsAny = _.map(_.partial(getField, group), field.enabledIfAny) - const refFieldsAll = _.map(_.partial(getField, group), field.enabledIfAll) - const isInvalid = !satisfiesRequire(config, cryptos, machineList, field, refFieldsAny, refFieldsAll) + const refFieldsAny = _.map(_.partial(getField, group), field.enabledIfAny) + const refFieldsAll = _.map(_.partial(getField, group), field.enabledIfAll) + const isInvalid = !satisfiesRequire(config, cryptos, machineList, field, refFieldsAny, refFieldsAll) - return isInvalid + return isInvalid + }) }) }) - }) - .then(arr => arr.map(r => r.code)) + .then(arr => arr.map(r => r.code)) } function validate (config) { return Promise.resolve() - .then(() => ensureConstraints(config)) - .then(() => validateRequires(config)) - .then(arr => { - if (arr.length === 0) return config - throw new Error('Invalid configuration:' + arr) - }) + .then(() => ensureConstraints(config)) + .then(() => validateRequires(config)) + .then(arr => { + if (arr.length === 0) return config + throw new Error('Invalid configuration:' + arr) + }) } module.exports = {validate, ensureConstraints, validateRequires} diff --git a/lib/customers.js b/lib/customers.js index 931cea0b..7021ebca 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -39,8 +39,8 @@ function add (customer) { function get (phone) { const sql = 'select * from customers where phone=$1' return db.oneOrNone(sql, [phone]) - .then(populateDailyVolume) - .then(camelize) + .then(populateDailyVolume) + .then(camelize) } /** @@ -64,11 +64,11 @@ function update (id, data, userToken) { ' where id=$1 returning *' return db.one(sql, [id]) - .then(addComplianceOverrides(id, updateData, userToken)) - .then(populateOverrideUsernames) - .then(computeStatus) - .then(populateDailyVolume) - .then(camelize) + .then(addComplianceOverrides(id, updateData, userToken)) + .then(populateOverrideUsernames) + .then(computeStatus) + .then(populateDailyVolume) + .then(camelize) } /** @@ -85,10 +85,10 @@ function update (id, data, userToken) { function getById (id, userToken) { const sql = 'select * from customers where id=$1' return db.oneOrNone(sql, [id]) - .then(populateOverrideUsernames) - .then(computeStatus) - .then(populateDailyVolume) - .then(camelize) + .then(populateOverrideUsernames) + .then(computeStatus) + .then(populateDailyVolume) + .then(camelize) } /** @@ -192,7 +192,7 @@ function enhanceAtFields (fields) { */ function enhanceOverrideFields (fields, userToken) { if (!userToken) return fields - // Populate with computedFields (user who overrode and overriden timestamps date) + // Populate with computedFields (user who overrode and overriden timestamps date) return _.reduce(_.assign, fields, _.map((type) => { return (fields[type + '_override']) ? { @@ -232,7 +232,7 @@ function addComplianceOverrides (id, customer, userToken) { // Save all the updated override fields return Promise.all(_.map(complianceOverrides.add, _.compact(overrides))) - .then(() => customer) + .then(() => customer) } /** @@ -295,15 +295,15 @@ function populateOverrideUsernames (customer) { const queryTokens = _.map('token', fieldsToUpdate) return users.getByIds(queryTokens) - .then(usersList => { - return _.map(userField => { - const user = _.find({token: userField.token}, usersList) - return { - [userField.field]: user ? user.name : null - } - }, fieldsToUpdate) - }) - .then(_.reduce(_.assign, customer)) + .then(usersList => { + return _.map(userField => { + const user = _.find({token: userField.token}, usersList) + return { + [userField.field]: user ? user.name : null + } + }, fieldsToUpdate) + }) + .then(_.reduce(_.assign, customer)) } /** @@ -320,12 +320,12 @@ function batch () { where id != $1 order by created desc limit $2` return db.any(sql, [ anonymous.uuid, NUM_RESULTS ]) - .then(customers => Promise.all(_.map(customer => { - return populateOverrideUsernames(customer) - .then(computeStatus) - .then(populateDailyVolume) - .then(camelize) - }, customers))) + .then(customers => Promise.all(_.map(customer => { + return populateOverrideUsernames(customer) + .then(computeStatus) + .then(populateDailyVolume) + .then(camelize) + }, customers))) } module.exports = { add, get, batch, getById, update } diff --git a/lib/email.js b/lib/email.js index 0809e150..73c46b04 100644 --- a/lib/email.js +++ b/lib/email.js @@ -3,13 +3,13 @@ const ph = require('./plugin-helper') function sendMessage (settings, rec) { return Promise.resolve() - .then(() => { - const pluginCode = configManager.unscoped(settings.config).email - const plugin = ph.load(ph.EMAIL, pluginCode) - const account = settings.accounts[pluginCode] + .then(() => { + const pluginCode = configManager.unscoped(settings.config).email + const plugin = ph.load(ph.EMAIL, pluginCode) + const account = settings.accounts[pluginCode] - return plugin.sendMessage(account, rec) - }) + return plugin.sendMessage(account, rec) + }) } module.exports = {sendMessage} diff --git a/lib/exchange.js b/lib/exchange.js index 77f6f088..a15a4b5b 100644 --- a/lib/exchange.js +++ b/lib/exchange.js @@ -9,24 +9,24 @@ function lookupExchange (settings, cryptoCode) { function fetchExchange (settings, cryptoCode) { return Promise.resolve() - .then(() => { - const plugin = lookupExchange(settings, cryptoCode) - if (!plugin) throw new Error('No exchange set') - const exchange = ph.load(ph.EXCHANGE, plugin) - const account = settings.accounts[plugin] + .then(() => { + const plugin = lookupExchange(settings, cryptoCode) + if (!plugin) throw new Error('No exchange set') + const exchange = ph.load(ph.EXCHANGE, plugin) + const account = settings.accounts[plugin] - return {exchange, account} - }) + return {exchange, account} + }) } function buy (settings, cryptoAtoms, fiatCode, cryptoCode) { return fetchExchange(settings, cryptoCode) - .then(r => r.exchange.buy(r.account, cryptoAtoms, fiatCode, cryptoCode)) + .then(r => r.exchange.buy(r.account, cryptoAtoms, fiatCode, cryptoCode)) } function sell (settings, cryptoAtoms, fiatCode, cryptoCode) { return fetchExchange(settings, cryptoCode) - .then(r => r.exchange.sell(r.account, cryptoAtoms, fiatCode, cryptoCode)) + .then(r => r.exchange.sell(r.account, cryptoAtoms, fiatCode, cryptoCode)) } function active (settings, cryptoCode) { diff --git a/lib/logs.js b/lib/logs.js index 11e948ee..ec540cff 100644 --- a/lib/logs.js +++ b/lib/logs.js @@ -22,7 +22,7 @@ function getLastSeen (deviceId) { where device_id=$1 order by timestamp desc, serial desc limit 1` return db.oneOrNone(sql, [deviceId]) - .then(log => log ? {timestamp: log.timestamp, serial: log.serial, id: log.id} : null) + .then(log => log ? {timestamp: log.timestamp, serial: log.serial, id: log.id} : null) } /** @@ -40,7 +40,7 @@ function getLastSeen (deviceId) { function update (deviceId, logLines) { const cs = new pgp.helpers.ColumnSet([ 'id', 'device_id', 'log_level', 'timestamp', 'serial', 'message'], - {table: 'logs'}) + {table: 'logs'}) const logs = _.map(log => { const formatted = { @@ -65,10 +65,10 @@ function getUnlimitedMachineLogs (deviceId, until = new Date().toISOString()) { order by timestamp asc, serial asc` return Promise.all([db.any(sql, [ deviceId, until ]), getMachineName(deviceId)]) - .then(([logs, machineName]) => ({ - logs: _.map(_.mapKeys(_.camelCase), logs), - currentMachine: {deviceId, name: machineName} - })) + .then(([logs, machineName]) => ({ + logs: _.map(_.mapKeys(_.camelCase), logs), + currentMachine: {deviceId, name: machineName} + })) } function getMachineLogs (deviceId, until = new Date().toISOString()) { @@ -79,10 +79,10 @@ function getMachineLogs (deviceId, until = new Date().toISOString()) { limit $2` return Promise.all([db.any(sql, [ deviceId, NUM_RESULTS, until ]), getMachineName(deviceId)]) - .then(([logs, machineName]) => ({ - logs: _.map(_.mapKeys(_.camelCase), logs), - currentMachine: {deviceId, name: machineName} - })) + .then(([logs, machineName]) => ({ + logs: _.map(_.mapKeys(_.camelCase), logs), + currentMachine: {deviceId, name: machineName} + })) } module.exports = { getUnlimitedMachineLogs, getMachineLogs, update, getLastSeen } diff --git a/lib/machine-loader.js b/lib/machine-loader.js index ef165627..3299a11e 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -10,13 +10,13 @@ module.exports = {getMachineName, getMachines, getMachineNames, setMachine} function getMachines () { return db.any('select * from devices where display=TRUE order by created') - .then(rr => rr.map(r => ({ - deviceId: r.device_id, - cashbox: r.cashbox, - cassette1: r.cassette1, - cassette2: r.cassette2, - paired: r.paired - }))) + .then(rr => rr.map(r => ({ + deviceId: r.device_id, + cashbox: r.cashbox, + cassette1: r.cassette1, + cassette2: r.cassette2, + paired: r.paired + }))) } function getConfig (defaultConfig) { @@ -27,17 +27,17 @@ function getConfig (defaultConfig) { function getMachineNames (config) { return Promise.all([getMachines(), getConfig(config)]) - .then(([machines, config]) => { - const addName = r => { - const machineScoped = configManager.machineScoped(r.deviceId, config) - const name = machineScoped.machineName - const cashOut = machineScoped.cashOutEnabled + .then(([machines, config]) => { + const addName = r => { + const machineScoped = configManager.machineScoped(r.deviceId, config) + const name = machineScoped.machineName + const cashOut = machineScoped.cashOutEnabled - return _.assign(r, {name, cashOut}) - } + return _.assign(r, {name, cashOut}) + } - return _.map(addName, machines) - }) + return _.map(addName, machines) + }) } /** @@ -52,10 +52,10 @@ function getMachineNames (config) { */ function getMachineName (machineId) { return settingsLoader.loadRecentConfig() - .then(config => { - const machineScoped = configManager.machineScoped(machineId, config) - return machineScoped.machineName - }) + .then(config => { + const machineScoped = configManager.machineScoped(machineId, config) + return machineScoped.machineName + }) } function resetCashOutBills (rec) { diff --git a/lib/migrate.js b/lib/migrate.js index d5f6462f..c4cc749f 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -16,4 +16,3 @@ function run () { }) }) } - diff --git a/lib/notifier.js b/lib/notifier.js index 47a756a0..e6ddbf04 100644 --- a/lib/notifier.js +++ b/lib/notifier.js @@ -60,46 +60,46 @@ function checkNotification (plugins) { if (!plugins.notificationsEnabled()) return Promise.resolve() return checkStatus(plugins) - .then(alertRec => { - const currentAlertFingerprint = buildAlertFingerprint(alertRec) - if (!currentAlertFingerprint) { - const inAlert = !!alertFingerprint - alertFingerprint = null - lastAlertTime = null - if (inAlert) return sendNoAlerts(plugins) - } - - const alertChanged = currentAlertFingerprint === alertFingerprint && - lastAlertTime - Date.now() < ALERT_SEND_INTERVAL - if (alertChanged) return - - const subject = alertSubject(alertRec) - const rec = { - sms: { - body: subject - }, - email: { - subject, - body: printEmailAlerts(alertRec) + .then(alertRec => { + const currentAlertFingerprint = buildAlertFingerprint(alertRec) + if (!currentAlertFingerprint) { + const inAlert = !!alertFingerprint + alertFingerprint = null + lastAlertTime = null + if (inAlert) return sendNoAlerts(plugins) } - } - alertFingerprint = currentAlertFingerprint - lastAlertTime = Date.now() - return plugins.sendMessage(rec) - }) - .then(results => { - if (results && results.length > 0) logger.debug('Successfully sent alerts') - }) - .catch(logger.error) + const alertChanged = currentAlertFingerprint === alertFingerprint && + lastAlertTime - Date.now() < ALERT_SEND_INTERVAL + if (alertChanged) return + + const subject = alertSubject(alertRec) + const rec = { + sms: { + body: subject + }, + email: { + subject, + body: printEmailAlerts(alertRec) + } + } + alertFingerprint = currentAlertFingerprint + lastAlertTime = Date.now() + + return plugins.sendMessage(rec) + }) + .then(results => { + if (results && results.length > 0) logger.debug('Successfully sent alerts') + }) + .catch(logger.error) } const getDeviceTime = _.flow(_.get('device_time'), Date.parse) function dropRepeatsWith (comparator, arr) { const iteratee = (acc, val) => val === acc.last - ? acc - : {arr: _.concat(acc.arr, val), last: val} + ? acc + : {arr: _.concat(acc.arr, val), last: val} return _.reduce(iteratee, {arr: []}, arr).arr } @@ -135,11 +135,11 @@ function checkPing (deviceId) { limit 1` return db.oneOrNone(sql, [deviceId]) - .then(row => { - if (!row) return [{code: PING}] - if (row.age > NETWORK_DOWN_TIME) return [{code: PING, age: row.age}] - return [] - }) + .then(row => { + if (!row) return [{code: PING}] + if (row.age > NETWORK_DOWN_TIME) return [{code: PING, age: row.age}] + return [] + }) } function checkPings (devices) { @@ -147,37 +147,37 @@ function checkPings (devices) { const promises = _.map(checkPing, deviceIds) return Promise.all(promises) - .then(_.zipObject(deviceIds)) + .then(_.zipObject(deviceIds)) } function checkStatus (plugins) { const alerts = {devices: {}, deviceNames: {}} return Promise.all([plugins.checkBalances(), dbm.machineEvents(), plugins.getMachineNames()]) - .then(([balances, events, devices]) => { - return checkPings(devices) - .then(pings => { - alerts.general = _.filter(r => !r.deviceId, balances) - devices.forEach(function (device) { - const deviceId = device.deviceId - const deviceName = device.name - const deviceEvents = events.filter(function (eventRow) { - return eventRow.device_id === deviceId + .then(([balances, events, devices]) => { + return checkPings(devices) + .then(pings => { + alerts.general = _.filter(r => !r.deviceId, balances) + devices.forEach(function (device) { + const deviceId = device.deviceId + const deviceName = device.name + const deviceEvents = events.filter(function (eventRow) { + return eventRow.device_id === deviceId + }) + + const balanceAlerts = _.filter(['deviceId', deviceId], balances) + const ping = pings[deviceId] || [] + const stuckScreen = checkStuckScreen(deviceEvents) + + const deviceAlerts = _.isEmpty(ping) ? stuckScreen : ping + + alerts.devices[deviceId] = _.concat(deviceAlerts, balanceAlerts) + alerts.deviceNames[deviceId] = deviceName + }) + + return alerts }) - - const balanceAlerts = _.filter(['deviceId', deviceId], balances) - const ping = pings[deviceId] || [] - const stuckScreen = checkStuckScreen(deviceEvents) - - const deviceAlerts = _.isEmpty(ping) ? stuckScreen : ping - - alerts.devices[deviceId] = _.concat(deviceAlerts, balanceAlerts) - alerts.deviceNames[deviceId] = deviceName - }) - - return alerts }) - }) } function formatCurrency (num, code) { diff --git a/lib/pairing.js b/lib/pairing.js index 9adc1c4b..2eec688c 100644 --- a/lib/pairing.js +++ b/lib/pairing.js @@ -38,43 +38,43 @@ function removeDeviceConfig (deviceId) { function unpair (deviceId) { const sql = 'delete from devices where device_id=$1' return db.none(sql, [deviceId]) - .then(() => removeDeviceConfig(deviceId)) + .then(() => removeDeviceConfig(deviceId)) } function pair (token, deviceId, machineModel) { return pullToken(token) - .then(r => { - if (r.expired) return false + .then(r => { + if (r.expired) return false - const insertSql = `insert into devices (device_id, name) values ($1, $2) + const insertSql = `insert into devices (device_id, name) values ($1, $2) on conflict (device_id) do update set paired=TRUE, display=TRUE` - return configureNewDevice(deviceId, r.name, machineModel) - .then(() => db.none(insertSql, [deviceId, r.name])) - .then(() => true) - }) - .catch(err => { - logger.debug(err) - return false - }) + return configureNewDevice(deviceId, r.name, machineModel) + .then(() => db.none(insertSql, [deviceId, r.name])) + .then(() => true) + }) + .catch(err => { + logger.debug(err) + return false + }) } function authorizeCaDownload (caToken) { return pullToken(caToken) - .then(r => { - if (r.expired) throw new Error('Expired') + .then(r => { + if (r.expired) throw new Error('Expired') - const caPath = options.caPath - return readFile(caPath, {encoding: 'utf8'}) - }) + const caPath = options.caPath + return readFile(caPath, {encoding: 'utf8'}) + }) } function isPaired (deviceId) { const sql = 'select device_id from devices where device_id=$1 and paired=TRUE' return db.oneOrNone(sql, [deviceId]) - .then(row => row && row.device_id === deviceId) + .then(row => row && row.device_id === deviceId) } module.exports = {pair, unpair, authorizeCaDownload, isPaired} diff --git a/lib/plugins.js b/lib/plugins.js index 480f69c6..35b969a7 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -44,8 +44,8 @@ function plugins (settings, deviceId) { const cashInCommission = BN(1).add(BN(cryptoConfig.cashInCommission).div(100)) const cashOutCommission = _.isNil(cryptoConfig.cashOutCommission) - ? undefined - : BN(1).add(BN(cryptoConfig.cashOutCommission).div(100)) + ? undefined + : BN(1).add(BN(cryptoConfig.cashOutCommission).div(100)) if (Date.now() - rateRec.timestamp > STALE_TICKER) return logger.warn('Stale rate for ' + cryptoCode) const rate = rateRec.rates @@ -131,34 +131,34 @@ function plugins (settings, deviceId) { const virtualCassettes = [config.virtualCashOutDenomination] return Promise.all([dbm.cassetteCounts(deviceId), cashOutHelper.redeemableTxs(deviceId, excludeTxId)]) - .then(([rec, _redeemableTxs]) => { - const redeemableTxs = _.reject(_.matchesProperty('id', excludeTxId), _redeemableTxs) + .then(([rec, _redeemableTxs]) => { + const redeemableTxs = _.reject(_.matchesProperty('id', excludeTxId), _redeemableTxs) - const counts = argv.cassettes - ? argv.cassettes.split(',') - : rec.counts + const counts = argv.cassettes + ? argv.cassettes.split(',') + : rec.counts - const cassettes = [ - { - denomination: parseInt(denominations[0], 10), - count: parseInt(counts[0], 10) - }, - { - denomination: parseInt(denominations[1], 10), - count: parseInt(counts[1], 10) + const cassettes = [ + { + denomination: parseInt(denominations[0], 10), + count: parseInt(counts[0], 10) + }, + { + denomination: parseInt(denominations[1], 10), + count: parseInt(counts[1], 10) + } + ] + + try { + return { + cassettes: computeAvailableCassettes(cassettes, redeemableTxs), + virtualCassettes + } + } catch (err) { + logger.error(err) + return {cassettes, virtualCassettes} } - ] - - try { - return { - cassettes: computeAvailableCassettes(cassettes, redeemableTxs), - virtualCassettes - } - } catch (err) { - logger.error(err) - return {cassettes, virtualCassettes} - } - }) + }) } function fetchCurrentConfigVersion () { @@ -169,7 +169,7 @@ function plugins (settings, deviceId) { limit 1` return db.one(sql, ['config']) - .then(row => row.id) + .then(row => row.id) } function mapCoinSettings (coinParams) { @@ -207,23 +207,23 @@ function plugins (settings, deviceId) { ].concat(tickerPromises, balancePromises, testnetPromises) return Promise.all(promises) - .then(arr => { - const cassettes = arr[0] - const configVersion = arr[2] - const cryptoCodesCount = cryptoCodes.length - const tickers = arr.slice(3, cryptoCodesCount + 3) - const balances = arr.slice(cryptoCodesCount + 3, 2 * cryptoCodesCount + 3) - const testNets = arr.slice(2 * cryptoCodesCount + 3) - const coinParams = _.zip(cryptoCodes, testNets) + .then(arr => { + const cassettes = arr[0] + const configVersion = arr[2] + const cryptoCodesCount = cryptoCodes.length + const tickers = arr.slice(3, cryptoCodesCount + 3) + const balances = arr.slice(cryptoCodesCount + 3, 2 * cryptoCodesCount + 3) + const testNets = arr.slice(2 * cryptoCodesCount + 3) + const coinParams = _.zip(cryptoCodes, testNets) - return { - cassettes, - rates: buildRates(tickers), - balances: buildBalances(balances), - coins: _.map(mapCoinSettings, coinParams), - configVersion - } - }) + return { + cassettes, + rates: buildRates(tickers), + balances: buildBalances(balances), + coins: _.map(mapCoinSettings, coinParams), + configVersion + } + }) } function sendCoins (tx) { @@ -275,26 +275,26 @@ function plugins (settings, deviceId) { ticker.getRates(settings, fiatCode, cryptoCode), wallet.balance(settings, cryptoCode) ]) - .then(([rates, balanceRec]) => { - if (!rates || !balanceRec) return null + .then(([rates, balanceRec]) => { + if (!rates || !balanceRec) return null - const rawRate = rates.rates.ask - const cashInCommission = BN(1).minus(BN(config.cashInCommission).div(100)) - const balance = balanceRec.balance + const rawRate = rates.rates.ask + const cashInCommission = BN(1).minus(BN(config.cashInCommission).div(100)) + const balance = balanceRec.balance - if (!rawRate || !balance) return null + if (!rawRate || !balance) return null - const rate = rawRate.div(cashInCommission) + const rate = rawRate.div(cashInCommission) - const lowBalanceMargin = BN(1) + const lowBalanceMargin = BN(1) - const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) - const unitScale = cryptoRec.unitScale - const shiftedRate = rate.shift(-unitScale) - const fiatTransferBalance = balance.mul(shiftedRate).div(lowBalanceMargin) + const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) + const unitScale = cryptoRec.unitScale + const shiftedRate = rate.shift(-unitScale) + const fiatTransferBalance = balance.mul(shiftedRate).div(lowBalanceMargin) - return {timestamp: balanceRec.timestamp, balance: fiatTransferBalance.truncated().toString()} - }) + return {timestamp: balanceRec.timestamp, balance: fiatTransferBalance.truncated().toString()} + }) } function notifyConfirmation (tx) { @@ -310,17 +310,17 @@ function plugins (settings, deviceId) { } return sms.sendMessage(settings, rec) - .then(() => { - const sql = 'update cash_out_txs set notified=$1 where id=$2' - const values = [true, tx.id] + .then(() => { + const sql = 'update cash_out_txs set notified=$1 where id=$2' + const values = [true, tx.id] - return db.none(sql, values) - }) + return db.none(sql, values) + }) } function pong () { db.none('insert into server_events (event_type) values ($1)', ['ping']) - .catch(logger.error) + .catch(logger.error) } function pongClear () { @@ -329,7 +329,7 @@ function plugins (settings, deviceId) { and created < now() - interval $2` db.none(sql, ['ping', PONG_TTL]) - .catch(logger.error) + .catch(logger.error) } /* @@ -375,9 +375,9 @@ function plugins (settings, deviceId) { const t1 = Date.now() const filtered = marketTradesQueues - .filter(tradeEntry => { - return t1 - tradeEntry.timestamp < TRADE_TTL - }) + .filter(tradeEntry => { + return t1 - tradeEntry.timestamp < TRADE_TTL + }) const filteredCount = marketTradesQueues.length - filtered.length @@ -389,7 +389,7 @@ function plugins (settings, deviceId) { if (filtered.length === 0) return null const cryptoAtoms = filtered - .reduce((prev, current) => prev.plus(current.cryptoAtoms), BN(0)) + .reduce((prev, current) => prev.plus(current.cryptoAtoms), BN(0)) const timestamp = filtered.map(r => r.timestamp).reduce((acc, r) => Math.max(acc, r), 0) @@ -408,22 +408,22 @@ function plugins (settings, deviceId) { function executeTrades () { return machineLoader.getMachines() - .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 + .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 - return cryptoCodes.map(cryptoCode => ({fiatCode, cryptoCode})) + return cryptoCodes.map(cryptoCode => ({fiatCode, cryptoCode})) + }) + + const tradesPromises = _.uniq(_.flatten(lists)) + .map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode)) + + return Promise.all(tradesPromises) }) - - const tradesPromises = _.uniq(_.flatten(lists)) - .map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode)) - - return Promise.all(tradesPromises) - }) - .catch(logger.error) + .catch(logger.error) } function executeTradesForMarket (settings, fiatCode, cryptoCode) { @@ -435,11 +435,11 @@ function plugins (settings, deviceId) { if (tradeEntry === null || tradeEntry.cryptoAtoms.eq(0)) return return executeTradeForType(tradeEntry) - .catch(err => { - tradesQueues[market].push(tradeEntry) - if (err.name === 'orderTooSmall') return logger.debug(err.message) - logger.error(err) - }) + .catch(err => { + tradesQueues[market].push(tradeEntry) + if (err.name === 'orderTooSmall') return logger.debug(err.message) + logger.error(err) + }) } function executeTradeForType (_tradeEntry) { @@ -452,17 +452,17 @@ function plugins (settings, deviceId) { const execute = tradeEntry.type === 'buy' ? exchange.buy : exchange.sell return execute(settings, tradeEntry.cryptoAtoms, tradeEntry.fiatCode, tradeEntry.cryptoCode) - .then(() => recordTrade(tradeEntry)) + .then(() => recordTrade(tradeEntry)) } function convertBigNumFields (obj) { const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat']) - ? value.toString() - : value + ? value.toString() + : value const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat']) - ? key + '#' - : key + ? key + '#' + : key return _.mapKeys(convertKey, mapValuesWithKey(convert, obj)) } @@ -501,18 +501,28 @@ function plugins (settings, deviceId) { const cashOutEnabled = config.cashOutEnabled const cashInAlert = device.cashbox > config.cashInAlertThreshold - ? {code: 'CASH_BOX_FULL', machineName, deviceId: device.deviceId, notes: device.cashbox} - : null + ? {code: 'CASH_BOX_FULL', machineName, deviceId: device.deviceId, notes: device.cashbox} + : null const cassette1Alert = cashOutEnabled && device.cassette1 < config.cashOutCassette1AlertThreshold - ? {code: 'LOW_CASH_OUT', cassette: 1, machineName, deviceId: device.deviceId, - notes: device.cassette1, denomination: denomination1, fiatCode} - : null + ? {code: 'LOW_CASH_OUT', + cassette: 1, + machineName, + deviceId: device.deviceId, + notes: device.cassette1, + denomination: denomination1, + fiatCode} + : null const cassette2Alert = cashOutEnabled && device.cassette2 < config.cashOutCassette2AlertThreshold - ? {code: 'LOW_CASH_OUT', cassette: 2, machineName, deviceId: device.deviceId, - notes: device.cassette2, denomination: denomination2, fiatCode} - : null + ? {code: 'LOW_CASH_OUT', + cassette: 2, + machineName, + deviceId: device.deviceId, + notes: device.cassette2, + denomination: denomination2, + fiatCode} + : null return _.compact([cashInAlert, cassette1Alert, cassette2Alert]) } @@ -530,7 +540,7 @@ function plugins (settings, deviceId) { const checkCryptoBalanceWithFiat = _.partial(checkCryptoBalance, [fiatCode]) return Promise.all(fiatBalancePromises(cryptoCodes)) - .then(balances => _.map(checkCryptoBalanceWithFiat, _.zip(cryptoCodes, balances))) + .then(balances => _.map(checkCryptoBalanceWithFiat, _.zip(cryptoCodes, balances))) } function checkCryptoBalance (fiatCode, rec) { @@ -542,8 +552,8 @@ function plugins (settings, deviceId) { const cryptoAlertThreshold = config.cryptoAlertThreshold return BN(fiatBalance.balance).lt(cryptoAlertThreshold) - ? {code: 'LOW_CRYPTO_BALANCE', cryptoCode, fiatBalance, fiatCode} - : null + ? {code: 'LOW_CRYPTO_BALANCE', cryptoCode, fiatBalance, fiatCode} + : null } function checkBalances () { @@ -551,13 +561,13 @@ function plugins (settings, deviceId) { const fiatCode = globalConfig.fiatCurrency return machineLoader.getMachines() - .then(devices => { - return Promise.all([ - checkCryptoBalances(fiatCode, devices), - checkDevicesCashBalances(fiatCode, devices) - ]) - .then(_.flow(_.flattenDeep, _.compact)) - }) + .then(devices => { + return Promise.all([ + checkCryptoBalances(fiatCode, devices), + checkDevicesCashBalances(fiatCode, devices) + ]) + .then(_.flow(_.flattenDeep, _.compact)) + }) } function randomCode () { @@ -566,8 +576,8 @@ function plugins (settings, deviceId) { function getPhoneCode (phone) { const code = argv.mockSms - ? '123' - : randomCode() + ? '123' + : randomCode() const rec = { sms: { @@ -577,24 +587,24 @@ function plugins (settings, deviceId) { } return sms.sendMessage(settings, rec) - .then(() => code) + .then(() => code) } function sweepHdRow (row) { const cryptoCode = row.crypto_code return wallet.sweep(settings, cryptoCode, row.hd_index) - .then(txHash => { - if (txHash) { - logger.debug('[%s] Swept address with tx: %s', cryptoCode, txHash) + .then(txHash => { + if (txHash) { + logger.debug('[%s] Swept address with tx: %s', cryptoCode, txHash) - const sql = `update cash_out_txs set swept='t' + const sql = `update cash_out_txs set swept='t' where id=$1` - return db.none(sql, row.id) - } - }) - .catch(err => logger.error('[%s] Sweep error: %s', cryptoCode, err.message)) + return db.none(sql, row.id) + } + }) + .catch(err => logger.error('[%s] Sweep error: %s', cryptoCode, err.message)) } function sweepHd () { @@ -602,8 +612,8 @@ function plugins (settings, deviceId) { where hd_index is not null and not swept and status in ('confirmed', 'instant')` return db.any(sql) - .then(rows => Promise.all(rows.map(sweepHdRow))) - .catch(err => logger.error(err)) + .then(rows => Promise.all(rows.map(sweepHdRow))) + .catch(err => logger.error(err)) } function getMachineNames () { diff --git a/lib/plugins/common/bitstamp.js b/lib/plugins/common/bitstamp.js index 91c9c90a..b031668c 100644 --- a/lib/plugins/common/bitstamp.js +++ b/lib/plugins/common/bitstamp.js @@ -37,10 +37,10 @@ function authRequest (config, path, data) { const msg = [nonce, config.clientId, config.key].join('') const signature = crypto - .createHmac('sha256', Buffer.from(config.secret)) - .update(msg) - .digest('hex') - .toUpperCase() + .createHmac('sha256', Buffer.from(config.secret)) + .update(msg) + .digest('hex') + .toUpperCase() const signedData = _.merge(data, { key: config.key, @@ -76,7 +76,7 @@ function request (path, method, data) { if (data) options.data = querystring.stringify(data) return axios(options) - .then(r => r.data) + .then(r => r.data) } module.exports = { diff --git a/lib/plugins/common/json-rpc.js b/lib/plugins/common/json-rpc.js index 98b5aa26..3ec96248 100644 --- a/lib/plugins/common/json-rpc.js +++ b/lib/plugins/common/json-rpc.js @@ -18,17 +18,17 @@ function fetch (account, method, params) { url: `http://localhost:${account.port}`, data }) - .then(r => { - if (r.error) throw r.error - return r.data.result - }) - .catch(err => { - console.log(err.message) - try { - console.log(err.response.data.error) - } catch (__) {} - throw err - }) + .then(r => { + if (r.error) throw r.error + return r.data.result + }) + .catch(err => { + console.log(err.message) + try { + console.log(err.response.data.error) + } catch (__) {} + throw err + }) } function split (str) { diff --git a/lib/plugins/exchange/bitstamp/bitstamp.js b/lib/plugins/exchange/bitstamp/bitstamp.js index 6a111ce4..36aab2fd 100644 --- a/lib/plugins/exchange/bitstamp/bitstamp.js +++ b/lib/plugins/exchange/bitstamp/bitstamp.js @@ -30,11 +30,11 @@ function trade (type, account, cryptoAtoms, _fiatCode, cryptoCode) { const options = {amount: cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)} return common.authRequest(account, '/' + type + '/market/' + market, options) - .catch(e => { - if (e.response) handleErrors(e.response.data) - throw e - }) - .then(handleErrors) + .catch(e => { + if (e.response) handleErrors(e.response.data) + throw e + }) + .then(handleErrors) } catch (e) { return Promise.reject(e) } diff --git a/lib/plugins/exchange/kraken/kraken.js b/lib/plugins/exchange/kraken/kraken.js index 20da7d73..859fd866 100644 --- a/lib/plugins/exchange/kraken/kraken.js +++ b/lib/plugins/exchange/kraken/kraken.js @@ -21,10 +21,10 @@ function trade (account, type, cryptoAtoms, fiatCode, cryptoCode) { const amount = common.toUnit(cryptoAtoms, cryptoCode) const amountStr = amount.toFixed(6) - const pair = _.includes(fiatCode, ['USD', 'EUR']) - ? PAIRS[cryptoCode][fiatCode] - : PAIRS[cryptoCode]['EUR'] - + const pair = _.includes(fiatCode, ['USD', 'EUR']) + ? PAIRS[cryptoCode][fiatCode] + : PAIRS[cryptoCode]['EUR'] + var orderInfo = { pair, type, diff --git a/lib/plugins/sms/twilio/twilio.js b/lib/plugins/sms/twilio/twilio.js index adf39137..a92a37ec 100644 --- a/lib/plugins/sms/twilio/twilio.js +++ b/lib/plugins/sms/twilio/twilio.js @@ -18,15 +18,15 @@ function sendMessage (account, rec) { } return client.messages.create(opts) - .catch(err => { - if (_.includes(err.code, BAD_NUMBER_CODES)) { - const badNumberError = new Error(err.message) - badNumberError.name = 'BadNumberError' - throw badNumberError - } + .catch(err => { + if (_.includes(err.code, BAD_NUMBER_CODES)) { + const badNumberError = new Error(err.message) + badNumberError.name = 'BadNumberError' + throw badNumberError + } - throw new Error(err.message) - }) + throw new Error(err.message) + }) } module.exports = { diff --git a/lib/plugins/ticker/bitpay/bitpay.js b/lib/plugins/ticker/bitpay/bitpay.js index 48d43853..c331d996 100644 --- a/lib/plugins/ticker/bitpay/bitpay.js +++ b/lib/plugins/ticker/bitpay/bitpay.js @@ -2,18 +2,17 @@ const axios = require('axios') const BN = require('../../../bn') function ticker (account, fiatCode, cryptoCode) { - return axios.get('https://bitpay.com/api/rates/' + cryptoCode + '/' + fiatCode) - .then(r => { - const data = r.data - const price = BN(data.rate) - return { - rates: { - ask: price, - bid: price + .then(r => { + const data = r.data + const price = BN(data.rate) + return { + rates: { + ask: price, + bid: price + } } - } - }) + }) } module.exports = { diff --git a/lib/plugins/ticker/bitstamp/bitstamp.js b/lib/plugins/ticker/bitstamp/bitstamp.js index 8f8d03a9..e8a36a5d 100644 --- a/lib/plugins/ticker/bitstamp/bitstamp.js +++ b/lib/plugins/ticker/bitstamp/bitstamp.js @@ -3,16 +3,16 @@ const common = require('../../common/bitstamp') function ticker (account, fiatCode, cryptoCode) { return Promise.resolve() - .then(() => { - const market = common.buildMarket(fiatCode, cryptoCode) - return common.request('/ticker/' + market, 'GET') - }) - .then(r => ({ - rates: { - ask: BN(r.ask), - bid: BN(r.bid) - } - })) + .then(() => { + const market = common.buildMarket(fiatCode, cryptoCode) + return common.request('/ticker/' + market, 'GET') + }) + .then(r => ({ + rates: { + ask: BN(r.ask), + bid: BN(r.bid) + } + })) } module.exports = { diff --git a/lib/plugins/ticker/coinbase/coinbase.js b/lib/plugins/ticker/coinbase/coinbase.js index 39feebb2..afd4e107 100644 --- a/lib/plugins/ticker/coinbase/coinbase.js +++ b/lib/plugins/ticker/coinbase/coinbase.js @@ -11,7 +11,7 @@ function getBuyPrice (obj) { url: `https://api.coinbase.com/v2/prices/${currencyPair}/buy`, headers: {'CB-Version': '2017-07-10'} }) - .then(r => r.data) + .then(r => r.data) } function getSellPrice (obj) { @@ -22,34 +22,33 @@ function getSellPrice (obj) { url: `https://api.coinbase.com/v2/prices/${currencyPair}/sell`, headers: {'CB-Version': '2017-07-10'} }) - .then(r => r.data) + .then(r => r.data) } function ticker (account, fiatCode, cryptoCode) { return Promise.resolve() - .then(() => { - if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) { - throw new Error('Unsupported crypto: ' + cryptoCode) - } - }) - .then(() => { - const currencyPair = `${cryptoCode}-${fiatCode}` - const promises = [ - getBuyPrice({currencyPair}), - getSellPrice({currencyPair}) - ] + .then(() => { + if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) { + throw new Error('Unsupported crypto: ' + cryptoCode) + } + }) + .then(() => { + const currencyPair = `${cryptoCode}-${fiatCode}` + const promises = [ + getBuyPrice({currencyPair}), + getSellPrice({currencyPair}) + ] - return Promise.all(promises) - }) - .then(([buyPrice, sellPrice]) => ({ - rates: { - ask: BN(buyPrice.data.amount), - bid: BN(sellPrice.data.amount) - } - })) + return Promise.all(promises) + }) + .then(([buyPrice, sellPrice]) => ({ + rates: { + ask: BN(buyPrice.data.amount), + bid: BN(sellPrice.data.amount) + } + })) } module.exports = { ticker } - diff --git a/lib/plugins/ticker/kraken/kraken.js b/lib/plugins/ticker/kraken/kraken.js index 64ffbffb..626f461d 100644 --- a/lib/plugins/ticker/kraken/kraken.js +++ b/lib/plugins/ticker/kraken/kraken.js @@ -21,32 +21,32 @@ exports.ticker = function ticker (account, fiatCode, cryptoCode) { } return axios.get('https://bitpay.com/api/rates') - .then(response => { - const fxRates = response.data - const usdRate = findCurrency(fxRates, 'USD') - const fxRate = findCurrency(fxRates, fiatCode).div(usdRate) + .then(response => { + const fxRates = response.data + const usdRate = findCurrency(fxRates, 'USD') + const fxRate = findCurrency(fxRates, fiatCode).div(usdRate) - return getCurrencyRates('USD', cryptoCode) - .then(res => ({ - rates: { - ask: res.rates.ask.times(fxRate), - bid: res.rates.bid.times(fxRate) - } - })) - }) + return getCurrencyRates('USD', cryptoCode) + .then(res => ({ + rates: { + ask: res.rates.ask.times(fxRate), + bid: res.rates.bid.times(fxRate) + } + })) + }) } function getCurrencyRates (fiatCode, cryptoCode) { const pair = PAIRS[cryptoCode][fiatCode] return axios.get('https://api.kraken.com/0/public/Ticker?pair=' + pair) - .then(function (response) { - const rates = response.data.result[pair] - return { - rates: { - ask: BN(rates.a[0]), - bid: BN(rates.b[0]) + .then(function (response) { + const rates = response.data.result[pair] + return { + rates: { + ask: BN(rates.a[0]), + bid: BN(rates.b[0]) + } } - } - }) + }) } diff --git a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js index 8c6d4ce7..df806d48 100644 --- a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js +++ b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js @@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) { function accountBalance (account, cryptoCode, confirmations) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['', confirmations])) - .then(r => BN(r).shift(unitScale).round()) + .then(() => fetch('getbalance', ['', confirmations])) + .then(r => BN(r).shift(unitScale).round()) } // We want a balance that includes all spends (0 conf) but only deposits that @@ -59,71 +59,71 @@ function sendCoins (account, address, cryptoAtoms, cryptoCode) { const btcAddress = bchToBtcAddress(address) return checkCryptoCode(cryptoCode) - .then(() => fetch('sendtoaddress', [btcAddress, coins])) - .catch(err => { - if (err.code === -6) throw new E.InsufficientFundsError() - throw err - }) + .then(() => fetch('sendtoaddress', [btcAddress, coins])) + .catch(err => { + if (err.code === -6) throw new E.InsufficientFundsError() + throw err + }) } function newAddress (account, info) { return checkCryptoCode(info.cryptoCode) - .then(() => fetch('getnewaddress')) + .then(() => fetch('getnewaddress')) } function addressBalance (address, confs) { const btcAddress = bchToBtcAddress(address) return fetch('getreceivedbyaddress', [btcAddress, confs]) - .then(r => BN(r).shift(unitScale).round()) + .then(r => BN(r).shift(unitScale).round()) } function confirmedBalance (address, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => addressBalance(address, 1)) + .then(() => addressBalance(address, 1)) } function pendingBalance (address, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => addressBalance(address, 0)) + .then(() => addressBalance(address, 0)) } function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => confirmedBalance(toAddress, cryptoCode)) - .then(confirmed => { - if (confirmed.gte(requested)) return {status: 'confirmed'} + .then(() => confirmedBalance(toAddress, cryptoCode)) + .then(confirmed => { + if (confirmed.gte(requested)) return {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'} + return pendingBalance(toAddress, cryptoCode) + .then(pending => { + if (pending.gte(requested)) return {status: 'authorized'} + if (pending.gt(0)) return {status: 'insufficientFunds'} + return {status: 'notSeen'} + }) }) - }) } function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => { - const promises = [ - accountBalance(account, cryptoCode, 0), - accountBalance(account, cryptoCode, 1), - newAddress(account, {cryptoCode}) - ] + .then(() => { + const promises = [ + accountBalance(account, cryptoCode, 0), + accountBalance(account, cryptoCode, 1), + newAddress(account, {cryptoCode}) + ] - return Promise.all(promises) - }) - .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ - fundingPendingBalance, - fundingConfirmedBalance, - fundingAddress - })) + return Promise.all(promises) + }) + .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ + fundingPendingBalance, + fundingConfirmedBalance, + fundingAddress + })) } function cryptoNetwork (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main') + .then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main') } module.exports = { diff --git a/lib/plugins/wallet/bitcoind/bitcoind.js b/lib/plugins/wallet/bitcoind/bitcoind.js index f89788fb..e4c3d95d 100644 --- a/lib/plugins/wallet/bitcoind/bitcoind.js +++ b/lib/plugins/wallet/bitcoind/bitcoind.js @@ -26,8 +26,8 @@ function checkCryptoCode (cryptoCode) { function accountBalance (acount, cryptoCode, confirmations) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['', confirmations])) - .then(r => BN(r).shift(unitScale).round()) + .then(() => fetch('getbalance', ['', confirmations])) + .then(r => BN(r).shift(unitScale).round()) } // We want a balance that includes all spends (0 conf) but only deposits that @@ -40,69 +40,69 @@ function sendCoins (account, address, cryptoAtoms, cryptoCode) { const coins = cryptoAtoms.shift(-unitScale).toFixed(8) return checkCryptoCode(cryptoCode) - .then(() => fetch('sendtoaddress', [address, coins])) - .catch(err => { - if (err.code === -6) throw new E.InsufficientFundsError() - throw err - }) + .then(() => fetch('sendtoaddress', [address, coins])) + .catch(err => { + if (err.code === -6) throw new E.InsufficientFundsError() + throw err + }) } function newAddress (account, info) { return checkCryptoCode(info.cryptoCode) - .then(() => fetch('getnewaddress')) + .then(() => fetch('getnewaddress')) } function addressBalance (address, confs) { return fetch('getreceivedbyaddress', [address, confs]) - .then(r => BN(r).shift(unitScale).round()) + .then(r => BN(r).shift(unitScale).round()) } function confirmedBalance (address, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => addressBalance(address, 1)) + .then(() => addressBalance(address, 1)) } function pendingBalance (address, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => addressBalance(address, 0)) + .then(() => addressBalance(address, 0)) } function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => confirmedBalance(toAddress, cryptoCode)) - .then(confirmed => { - if (confirmed.gte(requested)) return {status: 'confirmed'} + .then(() => confirmedBalance(toAddress, cryptoCode)) + .then(confirmed => { + if (confirmed.gte(requested)) return {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'} + return pendingBalance(toAddress, cryptoCode) + .then(pending => { + if (pending.gte(requested)) return {status: 'authorized'} + if (pending.gt(0)) return {status: 'insufficientFunds'} + return {status: 'notSeen'} + }) }) - }) } function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => { - const promises = [ - accountBalance(account, cryptoCode, 0), - accountBalance(account, cryptoCode, 1), - newAddress(account, {cryptoCode}) - ] + .then(() => { + const promises = [ + accountBalance(account, cryptoCode, 0), + accountBalance(account, cryptoCode, 1), + newAddress(account, {cryptoCode}) + ] - return Promise.all(promises) - }) - .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ - fundingPendingBalance, - fundingConfirmedBalance, - fundingAddress - })) + return Promise.all(promises) + }) + .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ + fundingPendingBalance, + fundingConfirmedBalance, + fundingAddress + })) } function cryptoNetwork (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main') + .then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main') } module.exports = { diff --git a/lib/plugins/wallet/bitgo/bitgo.js b/lib/plugins/wallet/bitgo/bitgo.js index dd1db689..e62d6d1c 100644 --- a/lib/plugins/wallet/bitgo/bitgo.js +++ b/lib/plugins/wallet/bitgo/bitgo.js @@ -29,83 +29,83 @@ function checkCryptoCode (cryptoCode) { function sendCoins (account, address, cryptoAtoms, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => getWallet(account)) - .then(wallet => { - const params = { - address: address, - amount: cryptoAtoms.toNumber(), - walletPassphrase: account.walletPassphrase - } - return wallet.sendCoins(params) - }) - .then(result => { - return result.hash - }) - .catch(err => { - if (err.message === 'Insufficient funds') throw new E.InsufficientFundsError() - throw err - }) + .then(() => getWallet(account)) + .then(wallet => { + const params = { + address: address, + amount: cryptoAtoms.toNumber(), + walletPassphrase: account.walletPassphrase + } + return wallet.sendCoins(params) + }) + .then(result => { + return result.hash + }) + .catch(err => { + if (err.message === 'Insufficient funds') throw new E.InsufficientFundsError() + throw err + }) } function balance (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => getWallet(account)) - .then(wallet => BN(wallet.wallet.spendableConfirmedBalance)) + .then(() => getWallet(account)) + .then(wallet => BN(wallet.wallet.spendableConfirmedBalance)) } function newAddress (account, info) { return checkCryptoCode(info.cryptoCode) - .then(() => getWallet(account)) - .then(wallet => { - return wallet.createAddress() - .then(result => { - const address = result.address + .then(() => getWallet(account)) + .then(wallet => { + return wallet.createAddress() + .then(result => { + const address = result.address - // If a label was provided, set the label - if (info.label) { - return wallet.setLabel({ address: address, label: info.label }) - .then(() => address) - } + // If a label was provided, set the label + if (info.label) { + return wallet.setLabel({ address: address, label: info.label }) + .then(() => address) + } - return address + return address + }) }) - }) } function getStatus (account, toAddress, requested, cryptoCode) { const bitgo = buildBitgo(account) return checkCryptoCode(cryptoCode) - .then(() => bitgo.blockchain().getAddress({address: toAddress})) - .then(rec => { - if (rec.balance === 0) return {status: 'notSeen'} - if (requested.gt(rec.balance)) return {status: 'insufficientFunds'} - if (requested.gt(rec.confirmedBalance)) return {status: 'authorized'} - return {status: 'confirmed'} - }) + .then(() => bitgo.blockchain().getAddress({address: toAddress})) + .then(rec => { + if (rec.balance === 0) return {status: 'notSeen'} + if (requested.gt(rec.balance)) return {status: 'insufficientFunds'} + if (requested.gt(rec.confirmedBalance)) return {status: 'authorized'} + return {status: 'confirmed'} + }) } function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => { - return getWallet(account) - .then(wallet => { - return wallet.createAddress() - .then(result => { - const fundingAddress = result.address - return wallet.setLabel({address: fundingAddress, label: 'Funding Address'}) - .then(() => ({ - fundingPendingBalance: BN(wallet.wallet.balance), - fundingConfirmedBalance: BN(wallet.wallet.confirmedBalance), - fundingAddress - })) - }) + .then(() => { + return getWallet(account) + .then(wallet => { + return wallet.createAddress() + .then(result => { + const fundingAddress = result.address + return wallet.setLabel({address: fundingAddress, label: 'Funding Address'}) + .then(() => ({ + fundingPendingBalance: BN(wallet.wallet.balance), + fundingConfirmedBalance: BN(wallet.wallet.confirmedBalance), + fundingAddress + })) + }) + }) }) - }) } function cryptoNetwork (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => account.environment === 'test' ? 'test' : 'main') + .then(() => account.environment === 'test' ? 'test' : 'main') } module.exports = { diff --git a/lib/plugins/wallet/dashd/dashd.js b/lib/plugins/wallet/dashd/dashd.js index fe83f930..89c3679c 100644 --- a/lib/plugins/wallet/dashd/dashd.js +++ b/lib/plugins/wallet/dashd/dashd.js @@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) { function accountBalance (acount, cryptoCode, confirmations) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['', confirmations])) - .then(r => BN(r).shift(unitScale).round()) + .then(() => fetch('getbalance', ['', confirmations])) + .then(r => BN(r).shift(unitScale).round()) } // We want a balance that includes all spends (0 conf) but only deposits that @@ -41,64 +41,64 @@ function sendCoins (account, address, cryptoAtoms, cryptoCode) { const coins = cryptoAtoms.shift(-unitScale).toFixed(8) return checkCryptoCode(cryptoCode) - .then(() => fetch('sendtoaddress', [address, coins])) - .catch(err => { - if (err.code === -6) throw new E.InsufficientFundsError() - throw err - }) + .then(() => fetch('sendtoaddress', [address, coins])) + .catch(err => { + if (err.code === -6) throw new E.InsufficientFundsError() + throw err + }) } function newAddress (account, info) { return checkCryptoCode(info.cryptoCode) - .then(() => fetch('getnewaddress')) + .then(() => fetch('getnewaddress')) } function addressBalance (address, confs) { return fetch('getreceivedbyaddress', [address, confs]) - .then(r => BN(r).shift(unitScale).round()) + .then(r => BN(r).shift(unitScale).round()) } function confirmedBalance (address, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => addressBalance(address, 1)) + .then(() => addressBalance(address, 1)) } function pendingBalance (address, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => addressBalance(address, 0)) + .then(() => addressBalance(address, 0)) } function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => confirmedBalance(toAddress, cryptoCode)) - .then(confirmed => { - if (confirmed.gte(requested)) return {status: 'confirmed'} + .then(() => confirmedBalance(toAddress, cryptoCode)) + .then(confirmed => { + if (confirmed.gte(requested)) return {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'} + return pendingBalance(toAddress, cryptoCode) + .then(pending => { + if (pending.gte(requested)) return {status: 'authorized'} + if (pending.gt(0)) return {status: 'insufficientFunds'} + return {status: 'notSeen'} + }) }) - }) } function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => { - const promises = [ - accountBalance(account, cryptoCode, 0), - accountBalance(account, cryptoCode, 1), - newAddress(account, {cryptoCode}) - ] + .then(() => { + const promises = [ + accountBalance(account, cryptoCode, 0), + accountBalance(account, cryptoCode, 1), + newAddress(account, {cryptoCode}) + ] - return Promise.all(promises) - }) - .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ - fundingPendingBalance, - fundingConfirmedBalance, - fundingAddress - })) + return Promise.all(promises) + }) + .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ + fundingPendingBalance, + fundingConfirmedBalance, + fundingAddress + })) } module.exports = { diff --git a/lib/plugins/wallet/geth/geth.js b/lib/plugins/wallet/geth/geth.js index c61f10d0..61fb8e5b 100644 --- a/lib/plugins/wallet/geth/geth.js +++ b/lib/plugins/wallet/geth/geth.js @@ -42,7 +42,7 @@ function privateKey (account) { function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) { return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false) - .then(pify(web3.eth.sendRawTransaction)) + .then(pify(web3.eth.sendRawTransaction)) } function checkCryptoCode (cryptoCode) { @@ -52,7 +52,7 @@ function checkCryptoCode (cryptoCode) { function balance (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => pendingBalance(defaultAddress(account))) + .then(() => pendingBalance(defaultAddress(account))) } const pendingBalance = address => _balance(true, address) @@ -81,31 +81,31 @@ function generateTx (_toAddress, wallet, amount, includesFee) { ] return Promise.all(promises) - .then(arr => { - const gas = arr[0] - const gasPrice = arr[1] - const txCount = arr[2] + .then(arr => { + const gas = arr[0] + const gasPrice = arr[1] + const txCount = arr[2] - const toSend = includesFee - ? amount.minus(gasPrice.times(gas)) - : amount + const toSend = includesFee + ? amount.minus(gasPrice.times(gas)) + : amount - const rawTx = { - nonce: txCount, - gasPrice: hex(gasPrice), - gasLimit: gas, - to: toAddress, - from: fromAddress, - value: hex(toSend) - } + const rawTx = { + nonce: txCount, + gasPrice: hex(gasPrice), + gasLimit: gas, + to: toAddress, + from: fromAddress, + value: hex(toSend) + } - const tx = new Tx(rawTx) - const privateKey = wallet.getPrivateKey() + const tx = new Tx(rawTx) + const privateKey = wallet.getPrivateKey() - tx.sign(privateKey) + tx.sign(privateKey) - return '0x' + tx.serialize().toString('hex') - }) + return '0x' + tx.serialize().toString('hex') + }) } function defaultWallet (account) { @@ -121,12 +121,12 @@ function sweep (account, cryptoCode, hdIndex) { const fromAddress = wallet.getChecksumAddressString() return confirmedBalance(fromAddress) - .then(r => { - if (r.eq(0)) return + .then(r => { + if (r.eq(0)) return - return generateTx(defaultAddress(account), wallet, r, true) - .then(signedTx => pify(web3.eth.sendRawTransaction)(signedTx)) - }) + return generateTx(defaultAddress(account), wallet, r, true) + .then(signedTx => pify(web3.eth.sendRawTransaction)(signedTx)) + }) } function newAddress (account, info) { @@ -136,17 +136,17 @@ function newAddress (account, info) { function getStatus (account, toAddress, cryptoAtoms, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => confirmedBalance(toAddress)) - .then(confirmed => { - if (confirmed.gte(cryptoAtoms)) return {status: 'confirmed'} + .then(() => confirmedBalance(toAddress)) + .then(confirmed => { + if (confirmed.gte(cryptoAtoms)) return {status: 'confirmed'} - return pendingBalance(toAddress) - .then(pending => { - if (pending.gte(cryptoAtoms)) return {status: 'published'} - if (pending.gt(0)) return {status: 'insufficientFunds'} - return {status: 'notSeen'} + return pendingBalance(toAddress) + .then(pending => { + if (pending.gte(cryptoAtoms)) return {status: 'published'} + if (pending.gt(0)) return {status: 'insufficientFunds'} + return {status: 'notSeen'} + }) }) - }) } function paymentHdNode (account) { @@ -165,19 +165,19 @@ function defaultHdNode (account) { function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => { - const fundingAddress = defaultAddress(account) + .then(() => { + const fundingAddress = defaultAddress(account) - const promises = [ - pendingBalance(fundingAddress), - confirmedBalance(fundingAddress) - ] + const promises = [ + pendingBalance(fundingAddress), + confirmedBalance(fundingAddress) + ] - return Promise.all(promises) - .then(([fundingPendingBalance, fundingConfirmedBalance]) => ({ - fundingPendingBalance, - fundingConfirmedBalance, - fundingAddress - })) - }) + return Promise.all(promises) + .then(([fundingPendingBalance, fundingConfirmedBalance]) => ({ + fundingPendingBalance, + fundingConfirmedBalance, + fundingAddress + })) + }) } diff --git a/lib/plugins/wallet/litecoind/litecoind.js b/lib/plugins/wallet/litecoind/litecoind.js index 49000019..ec117fa8 100644 --- a/lib/plugins/wallet/litecoind/litecoind.js +++ b/lib/plugins/wallet/litecoind/litecoind.js @@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) { function accountBalance (acount, cryptoCode, confirmations) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['', confirmations])) - .then(r => BN(r).shift(unitScale).round()) + .then(() => fetch('getbalance', ['', confirmations])) + .then(r => BN(r).shift(unitScale).round()) } // We want a balance that includes all spends (0 conf) but only deposits that @@ -41,64 +41,64 @@ function sendCoins (account, address, cryptoAtoms, cryptoCode) { const coins = cryptoAtoms.shift(-unitScale).toFixed(8) return checkCryptoCode(cryptoCode) - .then(() => fetch('sendtoaddress', [address, coins])) - .catch(err => { - if (err.code === -6) throw new E.InsufficientFundsError() - throw err - }) + .then(() => fetch('sendtoaddress', [address, coins])) + .catch(err => { + if (err.code === -6) throw new E.InsufficientFundsError() + throw err + }) } function newAddress (account, info) { return checkCryptoCode(info.cryptoCode) - .then(() => fetch('getnewaddress')) + .then(() => fetch('getnewaddress')) } function addressBalance (address, confs) { return fetch('getreceivedbyaddress', [address, confs]) - .then(r => BN(r).shift(unitScale).round()) + .then(r => BN(r).shift(unitScale).round()) } function confirmedBalance (address, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => addressBalance(address, 1)) + .then(() => addressBalance(address, 1)) } function pendingBalance (address, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => addressBalance(address, 0)) + .then(() => addressBalance(address, 0)) } function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => confirmedBalance(toAddress, cryptoCode)) - .then(confirmed => { - if (confirmed.gte(requested)) return {status: 'confirmed'} + .then(() => confirmedBalance(toAddress, cryptoCode)) + .then(confirmed => { + if (confirmed.gte(requested)) return {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'} + return pendingBalance(toAddress, cryptoCode) + .then(pending => { + if (pending.gte(requested)) return {status: 'authorized'} + if (pending.gt(0)) return {status: 'insufficientFunds'} + return {status: 'notSeen'} + }) }) - }) } function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => { - const promises = [ - accountBalance(account, cryptoCode, 0), - accountBalance(account, cryptoCode, 1), - newAddress(account, {cryptoCode}) - ] + .then(() => { + const promises = [ + accountBalance(account, cryptoCode, 0), + accountBalance(account, cryptoCode, 1), + newAddress(account, {cryptoCode}) + ] - return Promise.all(promises) - }) - .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ - fundingPendingBalance, - fundingConfirmedBalance, - fundingAddress - })) + return Promise.all(promises) + }) + .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ + fundingPendingBalance, + fundingConfirmedBalance, + fundingAddress + })) } module.exports = { diff --git a/lib/plugins/wallet/lnd/lnd.js b/lib/plugins/wallet/lnd/lnd.js index 4ee942c5..8c92a029 100644 --- a/lib/plugins/wallet/lnd/lnd.js +++ b/lib/plugins/wallet/lnd/lnd.js @@ -34,11 +34,11 @@ function checkCryptoCode (cryptoCode) { function balance (acount, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(connect) - .then(c => c.channelBalance({})) - .then(_.get('balance')) - .then(BN) - .then(r => r.shift(unitScale).round()) + .then(connect) + .then(c => c.channelBalance({})) + .then(_.get('balance')) + .then(BN) + .then(r => r.shift(unitScale).round()) } function sendCoins (account, address, cryptoAtoms, cryptoCode) { @@ -53,37 +53,37 @@ function newFunding (account, cryptoCode) { function newAddress (account, info) { return checkCryptoCode(info.cryptoCode) - .then(connect) - .then(c => { - if (info.isLightning) { - return c.addInvoice({memo: 'Lamassu cryptomat deposit', value: info.cryptoAtoms.toNumber()}) - .then(r => `${r.r_hash.toString('hex')}:${r.payment_request}`) - } + .then(connect) + .then(c => { + if (info.isLightning) { + return c.addInvoice({memo: 'Lamassu cryptomat deposit', value: info.cryptoAtoms.toNumber()}) + .then(r => `${r.r_hash.toString('hex')}:${r.payment_request}`) + } - return c.newAddress({type: 2}) - .then(_.get('address')) - }) + return c.newAddress({type: 2}) + .then(_.get('address')) + }) } function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => { - const parts = _.split(':', toAddress) - const isLightning = _.size(parts) === 2 - const rHashStr = isLightning && _.head(parts) + .then(() => { + const parts = _.split(':', toAddress) + const isLightning = _.size(parts) === 2 + const rHashStr = isLightning && _.head(parts) - return connect() - .then(c => { - if (isLightning) { - return c.lookupInvoice({r_hash_str: rHashStr}) - .then(r => { - if (r.settled) return {status: 'confirmed'} + return connect() + .then(c => { + if (isLightning) { + return c.lookupInvoice({r_hash_str: rHashStr}) + .then(r => { + if (r.settled) return {status: 'confirmed'} + return {status: 'notSeen'} + }) + } + + // Note: this must be handled outside of lnd return {status: 'notSeen'} }) - } - - // Note: this must be handled outside of lnd - return {status: 'notSeen'} }) - }) } diff --git a/lib/plugins/wallet/mock-wallet/mock-wallet.js b/lib/plugins/wallet/mock-wallet/mock-wallet.js index e91674b5..153c0082 100644 --- a/lib/plugins/wallet/mock-wallet/mock-wallet.js +++ b/lib/plugins/wallet/mock-wallet/mock-wallet.js @@ -19,12 +19,12 @@ function _balance (cryptoCode) { function balance (account, cryptoCode) { return Promise.resolve() - .then(() => _balance(cryptoCode)) + .then(() => _balance(cryptoCode)) } function pendingBalance (account, cryptoCode) { return balance(account, cryptoCode) - .then(b => b.mul(1.1)) + .then(b => b.mul(1.1)) } function confirmedBalance (account, cryptoCode) { @@ -69,11 +69,11 @@ function newFunding (account, cryptoCode) { ] return Promise.all(promises) - .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ - fundingPendingBalance, - fundingConfirmedBalance, - fundingAddress - })) + .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ + fundingPendingBalance, + fundingConfirmedBalance, + fundingAddress + })) } function getStatus (account, toAddress, cryptoAtoms, cryptoCode) { diff --git a/lib/plugins/wallet/zcashd/zcashd.js b/lib/plugins/wallet/zcashd/zcashd.js index bb0f7309..69ef7a67 100644 --- a/lib/plugins/wallet/zcashd/zcashd.js +++ b/lib/plugins/wallet/zcashd/zcashd.js @@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) { function accountBalance (acount, cryptoCode, confirmations) { return checkCryptoCode(cryptoCode) - .then(() => fetch('getbalance', ['', confirmations])) - .then(r => BN(r).shift(unitScale).round()) + .then(() => fetch('getbalance', ['', confirmations])) + .then(r => BN(r).shift(unitScale).round()) } // We want a balance that includes all spends (0 conf) but only deposits that @@ -41,64 +41,64 @@ function sendCoins (account, address, cryptoAtoms, cryptoCode) { const coins = cryptoAtoms.shift(-unitScale).toFixed(8) return checkCryptoCode(cryptoCode) - .then(() => fetch('sendtoaddress', [address, coins])) - .catch(err => { - if (err.code === -6) throw new E.InsufficientFundsError() - throw err - }) + .then(() => fetch('sendtoaddress', [address, coins])) + .catch(err => { + if (err.code === -6) throw new E.InsufficientFundsError() + throw err + }) } function newAddress (account, info) { return checkCryptoCode(info.cryptoCode) - .then(() => fetch('getnewaddress')) + .then(() => fetch('getnewaddress')) } function addressBalance (address, confs) { return fetch('getreceivedbyaddress', [address, confs]) - .then(r => BN(r).shift(unitScale).round()) + .then(r => BN(r).shift(unitScale).round()) } function confirmedBalance (address, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => addressBalance(address, 1)) + .then(() => addressBalance(address, 1)) } function pendingBalance (address, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => addressBalance(address, 0)) + .then(() => addressBalance(address, 0)) } function getStatus (account, toAddress, requested, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => confirmedBalance(toAddress, cryptoCode)) - .then(confirmed => { - if (confirmed.gte(requested)) return {status: 'confirmed'} + .then(() => confirmedBalance(toAddress, cryptoCode)) + .then(confirmed => { + if (confirmed.gte(requested)) return {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'} + return pendingBalance(toAddress, cryptoCode) + .then(pending => { + if (pending.gte(requested)) return {status: 'authorized'} + if (pending.gt(0)) return {status: 'insufficientFunds'} + return {status: 'notSeen'} + }) }) - }) } function newFunding (account, cryptoCode) { return checkCryptoCode(cryptoCode) - .then(() => { - const promises = [ - accountBalance(account, cryptoCode, 0), - accountBalance(account, cryptoCode, 1), - newAddress(account, {cryptoCode}) - ] + .then(() => { + const promises = [ + accountBalance(account, cryptoCode, 0), + accountBalance(account, cryptoCode, 1), + newAddress(account, {cryptoCode}) + ] - return Promise.all(promises) - }) - .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ - fundingPendingBalance, - fundingConfirmedBalance, - fundingAddress - })) + return Promise.all(promises) + }) + .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ + fundingPendingBalance, + fundingConfirmedBalance, + fundingAddress + })) } module.exports = { diff --git a/lib/plugins/zero-conf/blockcypher/blockcypher.js b/lib/plugins/zero-conf/blockcypher/blockcypher.js index 26b93c13..ea00067a 100644 --- a/lib/plugins/zero-conf/blockcypher/blockcypher.js +++ b/lib/plugins/zero-conf/blockcypher/blockcypher.js @@ -12,27 +12,26 @@ function highConfidence (confidence, txref) { function authorize (account, toAddress, cryptoAtoms, cryptoCode) { return Promise.resolve() - .then(() => { - if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode) + .then(() => { + if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode) - const query = qs.stringify({ - token: account.token, - includeConfidence: true + const query = qs.stringify({ + token: account.token, + includeConfidence: true + }) + + const confidence = account.confidenceFactor + + const url = `https://api.blockcypher.com/v1/btc/main/addrs/${toAddress}?${query}` + + return axios.get(url) + .then(r => { + const data = r.data + const sumTxRefs = txrefs => _.sumBy(txref => highConfidence(confidence, txref), txrefs) + + const authorizedValue = sumTxRefs(data.txrefs) + sumTxRefs(data.unconfirmed_txrefs) + + return cryptoAtoms.lte(authorizedValue) + }) }) - - const confidence = account.confidenceFactor - - const url = `https://api.blockcypher.com/v1/btc/main/addrs/${toAddress}?${query}` - - return axios.get(url) - .then(r => { - const data = r.data - const sumTxRefs = txrefs => _.sumBy(txref => highConfidence(confidence, txref), txrefs) - - const authorizedValue = sumTxRefs(data.txrefs) + sumTxRefs(data.unconfirmed_txrefs) - - return cryptoAtoms.lte(authorizedValue) - }) - }) } - diff --git a/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js b/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js index e6999dfb..d2276170 100644 --- a/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js +++ b/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js @@ -2,10 +2,10 @@ module.exports = {authorize} function authorize (account, toAddress, cryptoAtoms, cryptoCode) { return Promise.resolve() - .then(() => { - if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode) + .then(() => { + if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode) - const isAuthorized = false - return isAuthorized - }) + const isAuthorized = false + return isAuthorized + }) } diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index 1059814c..c6e523b2 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -28,10 +28,10 @@ exports.cassetteCounts = function cassetteCounts (deviceId) { 'WHERE device_id=$1' return db.one(sql, [deviceId]) - .then(row => { - const counts = [row.cassette1, row.cassette2] - return {counts} - }) + .then(row => { + const counts = [row.cassette1, row.cassette2] + return {counts} + }) } // Note: since we only prune on insert, we'll always have @@ -47,7 +47,7 @@ exports.machineEvent = function machineEvent (rec) { and created < now() - interval '2 days'` return db.none(sql, values) - .then(() => db.none(deleteSql, [rec.deviceId, rec.eventType])) + .then(() => db.none(deleteSql, [rec.deviceId, rec.eventType])) } exports.machineEvents = function machineEvents () { diff --git a/lib/route-helpers.js b/lib/route-helpers.js index fdfa114b..d008fac6 100644 --- a/lib/route-helpers.js +++ b/lib/route-helpers.js @@ -55,32 +55,32 @@ function fetchPhoneTx (phone) { const values = [phone, false, TRANSACTION_EXPIRATION] return db.any(sql, values) - .then(_.map(toCashOutTx)) - .then(txs => { - const confirmedTxs = txs.filter(tx => R.contains(tx.status, ['instant', 'confirmed'])) - if (confirmedTxs.length > 0) { - const maxTx = R.reduce((acc, val) => { - return !acc || val.cryptoAtoms.gt(acc.cryptoAtoms) ? val : acc - }, null, confirmedTxs) + .then(_.map(toCashOutTx)) + .then(txs => { + const confirmedTxs = txs.filter(tx => R.contains(tx.status, ['instant', 'confirmed'])) + if (confirmedTxs.length > 0) { + const maxTx = R.reduce((acc, val) => { + return !acc || val.cryptoAtoms.gt(acc.cryptoAtoms) ? val : acc + }, null, confirmedTxs) - return maxTx - } + return maxTx + } - if (txs.length > 0) throw httpError('Pending transactions', 412) - throw httpError('No transactions', 404) - }) + if (txs.length > 0) throw httpError('Pending transactions', 412) + throw httpError('No transactions', 404) + }) } function fetchStatusTx (txId, status) { const sql = 'select * from cash_out_txs where id=$1' return db.oneOrNone(sql, [txId]) - .then(toCashOutTx) - .then(tx => { - if (!tx) throw httpError('No transaction', 404) - if (tx.status === status) throw httpError('Not Modified', 304) - return tx - }) + .then(toCashOutTx) + .then(tx => { + if (!tx) throw httpError('No transaction', 404) + if (tx.status === status) throw httpError('Not Modified', 304) + return tx + }) } function updateDeviceConfigVersion (versionId) { @@ -105,9 +105,9 @@ function updateMachineDefaults (deviceId) { }] return settingsLoader.loadLatest() - .then(settings => { - return settingsLoader.save(settingsLoader.mergeValues(settings.config, newFields)) - }) + .then(settings => { + return settingsLoader.save(settingsLoader.mergeValues(settings.config, newFields)) + }) } module.exports = { diff --git a/lib/routes.js b/lib/routes.js index 36c73e65..6fc2f957 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -41,60 +41,60 @@ function poll (req, res, next) { pids[deviceId] = {pid, ts: Date.now()} return pi.pollQueries(serialNumber, deviceTime, req.query) - .then(results => { - const cassettes = results.cassettes + .then(results => { + const cassettes = results.cassettes - const reboot = pid && reboots[deviceId] && reboots[deviceId] === pid - const langs = config.machineLanguages + const reboot = pid && reboots[deviceId] && reboots[deviceId] === pid + const langs = config.machineLanguages - const locale = { - fiatCode: config.fiatCurrency, - localeInfo: { - primaryLocale: langs[0], - primaryLocales: langs, - country: config.country + const locale = { + fiatCode: config.fiatCurrency, + localeInfo: { + primaryLocale: langs[0], + primaryLocales: langs, + country: config.country + } } - } - const response = { - error: null, - locale, - 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, - cassettes, - twoWayMode: config.cashOutEnabled, - zeroConfLimit: config.zeroConfLimit, - reboot - } + const response = { + error: null, + locale, + 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, + cassettes, + twoWayMode: config.cashOutEnabled, + zeroConfLimit: config.zeroConfLimit, + reboot + } - if (response.idVerificationEnabled) { - response.idVerificationLimit = config.idVerificationLimit - } + if (response.idVerificationEnabled) { + response.idVerificationLimit = config.idVerificationLimit + } - return res.json(_.assign(response, results)) - }) - .catch(next) + return res.json(_.assign(response, results)) + }) + .catch(next) } function getTx (req, res, next) { if (req.query.status) { return helpers.fetchStatusTx(req.params.id, req.query.status) - .then(r => res.json(r)) - .catch(next) + .then(r => res.json(r)) + .catch(next) } return next(httpError('Not Found', 404)) @@ -103,8 +103,8 @@ function getTx (req, res, next) { function getPhoneTx (req, res, next) { if (req.query.phone) { return helpers.fetchPhoneTx(req.query.phone) - .then(r => res.json(r)) - .catch(next) + .then(r => res.json(r)) + .catch(next) } return next(httpError('Not Found', 404)) @@ -114,99 +114,99 @@ function postTx (req, res, next) { const pi = plugins(req.settings, req.deviceId) return Tx.post(_.set('deviceId', req.deviceId, req.body), pi) - .then(tx => { - if (tx.errorCode) { - logger.error(tx.error) - throw httpError(tx.error, 500) - } + .then(tx => { + if (tx.errorCode) { + logger.error(tx.error) + throw httpError(tx.error, 500) + } - return res.json(tx) - }) - .catch(err => { - if (err instanceof E.StaleTxError) return res.status(409).json({}) - if (err instanceof E.RatchetError) return res.status(409).json({}) + return res.json(tx) + }) + .catch(err => { + if (err instanceof E.StaleTxError) return res.status(409).json({}) + if (err instanceof E.RatchetError) return res.status(409).json({}) - throw err - }) - .catch(next) + throw err + }) + .catch(next) } function stateChange (req, res, next) { helpers.stateChange(req.deviceId, req.deviceTime, req.body) - .then(() => respond(req, res)) - .catch(next) + .then(() => respond(req, res)) + .catch(next) } function deviceEvent (req, res, next) { const pi = plugins(req.settings, req.deviceId) pi.logEvent(req.body) - .then(() => respond(req, res)) - .catch(next) + .then(() => respond(req, res)) + .catch(next) } function verifyUser (req, res, next) { const pi = plugins(req.settings, req.deviceId) pi.verifyUser(req.body) - .then(idResult => respond(req, res, idResult)) - .catch(next) + .then(idResult => respond(req, res, idResult)) + .catch(next) } function verifyTx (req, res, next) { const pi = plugins(req.settings, req.deviceId) pi.verifyTransaction(req.body) - .then(idResult => respond(req, res, idResult)) - .catch(next) + .then(idResult => respond(req, res, idResult)) + .catch(next) } function getCustomerWithPhoneCode (req, res, next) { const pi = plugins(req.settings, req.deviceId) const phone = req.body.phone return pi.getPhoneCode(phone) - .then(code => { - return customers.get(phone) - .then(customer => { - if (customer) return respond(req, res, {code, customer}) - return customers.add(req.body) - .then(customer => respond(req, res, {code, customer})) + .then(code => { + return customers.get(phone) + .then(customer => { + if (customer) return respond(req, res, {code, customer}) + return customers.add(req.body) + .then(customer => respond(req, res, {code, customer})) + }) }) - }) - .catch(err => { - if (err.name === 'BadNumberError') throw httpError('Bad number', 401) - throw err - }) - .catch(next) + .catch(err => { + if (err.name === 'BadNumberError') throw httpError('Bad number', 401) + throw err + }) + .catch(next) } function updateCustomer (req, res, next) { const id = req.params.id const patch = req.body customers.getById(id) - .then(customer => { - if (!customer) { throw httpError('Not Found', 404)} - return customers.update(id, patch) - }) - .then(customer => respond(req, res, {customer})) - .catch(next) + .then(customer => { + if (!customer) { throw httpError('Not Found', 404) } + return customers.update(id, patch) + }) + .then(customer => respond(req, res, {customer})) + .catch(next) } function getLastSeen (req, res, next) { return logs.getLastSeen(req.deviceId) - .then(r => res.json(r)) - .catch(next) + .then(r => res.json(r)) + .catch(next) } function updateLogs (req, res, next) { return logs.update(req.deviceId, req.body.logs) - .then(status => res.json({success: status})) - .catch(next) + .then(status => res.json({success: status})) + .catch(next) } function ca (req, res) { const token = req.query.token return pairing.authorizeCaDownload(token) - .then(ca => res.json({ca})) - .catch(() => res.status(403).json({error: 'forbidden'})) + .then(ca => res.json({ca})) + .catch(() => res.status(403).json({error: 'forbidden'})) } function pair (req, res, next) { @@ -215,21 +215,21 @@ function pair (req, res, next) { const model = req.query.model return pairing.pair(token, deviceId, model) - .then(valid => { - if (valid) { - return helpers.updateMachineDefaults(deviceId) - .then(() => res.json({status: 'paired'})) - } + .then(valid => { + if (valid) { + return helpers.updateMachineDefaults(deviceId) + .then(() => res.json({status: 'paired'})) + } - throw httpError('Pairing failed') - }) - .catch(next) + throw httpError('Pairing failed') + }) + .catch(next) } function errorHandler (err, req, res, next) { const statusCode = err.name === 'HTTPError' - ? err.code || 500 - : 500 + ? err.code || 500 + : 500 const json = {error: err.message} @@ -269,15 +269,15 @@ function authorize (req, res, next) { const deviceId = req.deviceId return pairing.isPaired(deviceId) - .then(r => { - if (r) { - req.deviceId = deviceId - return next() - } + .then(r => { + if (r) { + req.deviceId = deviceId + return next() + } - return res.status(403).json({error: 'Forbidden'}) - }) - .catch(next) + return res.status(403).json({error: 'Forbidden'}) + }) + .catch(next) } const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) && @@ -349,12 +349,12 @@ localApp.post('/reboot', (req, res) => { localApp.post('/dbChange', (req, res, next) => { return settingsLoader.loadLatest() - .then(poller.reload) - .then(() => logger.info('Config reloaded')) - .catch(err => { - logger.error(err) - res.sendStatus(500) - }) + .then(poller.reload) + .then(() => logger.info('Config reloaded')) + .catch(err => { + logger.error(err) + res.sendStatus(500) + }) }) function sha256 (buf) { @@ -367,8 +367,8 @@ function sha256 (buf) { function populateDeviceId (req, res, next) { const deviceId = _.isFunction(req.connection.getPeerCertificate) - ? sha256(req.connection.getPeerCertificate().raw) - : null + ? sha256(req.connection.getPeerCertificate().raw) + : null req.deviceId = deviceId req.deviceTime = req.get('date') @@ -386,16 +386,16 @@ function populateSettings (req, res, next) { if (!versionId) { return settingsLoader.loadLatest() - .then(settings => { req.settings = settings }) - .then(() => next()) - .catch(next) + .then(settings => { req.settings = settings }) + .then(() => next()) + .catch(next) } settingsLoader.load(versionId) - .then(settings => { req.settings = settings }) - .then(() => helpers.updateDeviceConfigVersion(versionId)) - .then(() => next()) - .catch(next) + .then(settings => { req.settings = settings }) + .then(() => helpers.updateDeviceConfigVersion(versionId)) + .then(() => next()) + .catch(next) } module.exports = {app, localApp} diff --git a/lib/settings-loader.js b/lib/settings-loader.js index a91f6c48..1d0b99b1 100644 --- a/lib/settings-loader.js +++ b/lib/settings-loader.js @@ -21,15 +21,15 @@ function loadFixture () { const fixturePath = fixture => path.resolve(__dirname, '..', 'test', 'fixtures', fixture + '.json') const promise = fixture - ? pify(fs.readFile)(fixturePath(fixture)).then(JSON.parse) - : Promise.resolve([]) + ? pify(fs.readFile)(fixturePath(fixture)).then(JSON.parse) + : Promise.resolve([]) return promise - .then(values => _.map(v => { - return (v.fieldLocator.fieldScope.machine === 'machine') - ? _.set('fieldLocator.fieldScope.machine', machine, v) - : v - }, values)) + .then(values => _.map(v => { + return (v.fieldLocator.fieldScope.machine === 'machine') + ? _.set('fieldLocator.fieldScope.machine', machine, v) + : v + }, values)) } function isEquivalentField (a, b) { @@ -48,18 +48,18 @@ function load (versionId) { if (!versionId) throw new Error('versionId is required') return Promise.all([loadConfig(versionId), loadAccounts()]) - .then(([config, accounts]) => ({ - config, - accounts - })) + .then(([config, accounts]) => ({ + config, + accounts + })) } function loadLatest () { return Promise.all([loadLatestConfig(), loadAccounts()]) - .then(([config, accounts]) => ({ - config, - accounts - })) + .then(([config, accounts]) => ({ + config, + accounts + })) } function loadConfig (versionId) { @@ -71,15 +71,15 @@ function loadConfig (versionId) { and valid` return db.one(sql, [versionId, 'config']) - .then(row => row.data.config) - .then(configValidate.validate) - .catch(err => { - if (err.name === 'QueryResultError') { - throw new Error('No such config version: ' + versionId) - } + .then(row => row.data.config) + .then(configValidate.validate) + .catch(err => { + if (err.name === 'QueryResultError') { + throw new Error('No such config version: ' + versionId) + } - throw err - }) + throw err + }) } function loadLatestConfig () { @@ -93,15 +93,15 @@ function loadLatestConfig () { limit 1` return db.one(sql, ['config']) - .then(row => row.data.config) - .then(configValidate.validate) - .catch(err => { - if (err.name === 'QueryResultError') { - throw new Error('lamassu-server is not configured') - } + .then(row => row.data.config) + .then(configValidate.validate) + .catch(err => { + if (err.name === 'QueryResultError') { + throw new Error('lamassu-server is not configured') + } - throw err - }) + throw err + }) } function loadRecentConfig () { @@ -114,7 +114,7 @@ function loadRecentConfig () { limit 1` return db.one(sql, ['config']) - .then(row => row.data.config) + .then(row => row.data.config) } function loadAccounts () { @@ -122,10 +122,10 @@ function loadAccounts () { const toPairs = r => [r.code, toFields(r.fields)] return db.oneOrNone('select data from user_config where type=$1', 'accounts') - .then(function (data) { - if (!data) return {} - return _.fromPairs(_.map(toPairs, data.data.accounts)) - }) + .then(function (data) { + if (!data) return {} + return _.fromPairs(_.map(toPairs, data.data.accounts)) + }) } function settings () { @@ -136,8 +136,8 @@ function save (config) { const sql = 'insert into user_config (type, data, valid) values ($1, $2, $3)' return configValidate.validate(config) - .then(() => db.none(sql, ['config', {config}, true])) - .catch(() => db.none(sql, ['config', {config}, false])) + .then(() => db.none(sql, ['config', {config}, true])) + .catch(() => db.none(sql, ['config', {config}, false])) } function configAddField (scope, fieldCode, fieldType, fieldClass, value) { @@ -222,11 +222,11 @@ function modifyConfig (newFields) { function transaction (t) { return loadRecentConfig() - .then(oldConfig => { - const oldConfigWithDefaults = addCryptoDefaults(oldConfig, newFields) - const doSave = _.flow(mergeValues, save) - return doSave(oldConfigWithDefaults, newFields) - }) + .then(oldConfig => { + const oldConfigWithDefaults = addCryptoDefaults(oldConfig, newFields) + const doSave = _.flow(mergeValues, save) + return doSave(oldConfigWithDefaults, newFields) + }) } transaction.txMode = tmSRD diff --git a/lib/sms.js b/lib/sms.js index f340a162..b6b0a82e 100644 --- a/lib/sms.js +++ b/lib/sms.js @@ -3,13 +3,13 @@ const ph = require('./plugin-helper') function sendMessage (settings, rec) { return Promise.resolve() - .then(() => { - const pluginCode = configManager.unscoped(settings.config).sms - const plugin = ph.load(ph.SMS, pluginCode) - const account = settings.accounts[pluginCode] + .then(() => { + const pluginCode = configManager.unscoped(settings.config).sms + const plugin = ph.load(ph.SMS, pluginCode) + const account = settings.accounts[pluginCode] - return plugin.sendMessage(account, rec) - }) + return plugin.sendMessage(account, rec) + }) } module.exports = {sendMessage} diff --git a/lib/support_logs.js b/lib/support_logs.js index 81d94384..3ba128a5 100644 --- a/lib/support_logs.js +++ b/lib/support_logs.js @@ -17,7 +17,7 @@ function get (id) { if (!id || _.isEmpty(id)) return Promise.resolve() const sql = 'select * from support_logs where id=$1' return db.oneOrNone(sql, [id]) - .then(_.mapKeys(_.camelCase)) + .then(_.mapKeys(_.camelCase)) } /** * Insert a single support_logs row in db @@ -34,7 +34,7 @@ function insert (deviceId) { const sql = `insert into support_logs (id, device_id) values ($1, $2) returning *` return db.one(sql, [uuid.v4(), deviceId]) - .then(_.mapKeys(_.camelCase)) + .then(_.mapKeys(_.camelCase)) } /** @@ -52,7 +52,7 @@ function batch () { where timestamp > (now() - interval '1 week') order by s.timestamp desc` return db.any(sql) - .then(_.map(_.mapKeys(_.camelCase))) + .then(_.map(_.mapKeys(_.camelCase))) } module.exports = { get, insert, batch } diff --git a/lib/ticker.js b/lib/ticker.js index a61cbcb5..918ed43c 100644 --- a/lib/ticker.js +++ b/lib/ticker.js @@ -9,29 +9,29 @@ const FETCH_INTERVAL = 10000 function _getRates (settings, fiatCode, cryptoCode) { return Promise.resolve() - .then(() => { - const config = settings.config - const plugin = configManager.cryptoScoped(cryptoCode, config).ticker + .then(() => { + const config = settings.config + const plugin = configManager.cryptoScoped(cryptoCode, config).ticker - const account = settings.accounts[plugin] - const ticker = ph.load(ph.TICKER, plugin) + const account = settings.accounts[plugin] + const ticker = ph.load(ph.TICKER, plugin) - const market = [cryptoCode, fiatCode].join('-') + const market = [cryptoCode, fiatCode].join('-') - return ticker.ticker(account, fiatCode, cryptoCode) - .then(r => ({ - rates: r.rates, - timestamp: Date.now() - })) - .then(r => { - lastRate[market] = r - return r + return ticker.ticker(account, fiatCode, cryptoCode) + .then(r => ({ + rates: r.rates, + timestamp: Date.now() + })) + .then(r => { + lastRate[market] = r + return r + }) + .catch(err => { + logger.error(err) + return lastRate[market] + }) }) - .catch(err => { - logger.error(err) - return lastRate[market] - }) - }) } const getRates = mem(_getRates, { diff --git a/lib/tx.js b/lib/tx.js index f6ff3d49..5afd97cb 100644 --- a/lib/tx.js +++ b/lib/tx.js @@ -13,7 +13,7 @@ function process (tx, pi) { function post (tx, pi) { return process(tx, pi) - .then(_.set('dirty', false)) + .then(_.set('dirty', false)) } function massage (tx) { @@ -24,17 +24,17 @@ function massage (tx) { const mapBN = r => { const update = r.direction === 'cashIn' - ? { - cryptoAtoms: BN(r.cryptoAtoms), - fiat: BN(r.fiat), - cashInFee: BN(r.cashInFee), - cashInFeeCrypto: BN(r.cashInFeeCrypto), - minimumTx: BN(r.minimumTx) - } - : { - cryptoAtoms: BN(r.cryptoAtoms), - fiat: BN(r.fiat) - } + ? { + cryptoAtoms: BN(r.cryptoAtoms), + fiat: BN(r.fiat), + cashInFee: BN(r.cashInFee), + cashInFeeCrypto: BN(r.cashInFeeCrypto), + minimumTx: BN(r.minimumTx) + } + : { + cryptoAtoms: BN(r.cryptoAtoms), + fiat: BN(r.fiat) + } return _.assign(r, update) } @@ -51,10 +51,10 @@ function cancel (txId) { ] return Promise.all(promises) - .then(r => { - if (_.some(r)) return - throw new Error('No such transaction') - }) + .then(r => { + if (_.some(r)) return + throw new Error('No such transaction') + }) } module.exports = {post, cancel} diff --git a/lib/wallet.js b/lib/wallet.js index bac18c01..b92c0ec8 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -30,14 +30,14 @@ function computeSeed (masterSeed) { function fetchWallet (settings, cryptoCode) { return fs.readFile(options.seedPath, 'utf8') - .then(hex => { - const masterSeed = Buffer.from(hex.trim(), 'hex') - const plugin = configManager.cryptoScoped(cryptoCode, settings.config).wallet - const wallet = ph.load(ph.WALLET, plugin) - const account = settings.accounts[plugin] + .then(hex => { + const masterSeed = Buffer.from(hex.trim(), 'hex') + const plugin = configManager.cryptoScoped(cryptoCode, settings.config).wallet + const wallet = ph.load(ph.WALLET, plugin) + const account = settings.accounts[plugin] - return {wallet, account: _.set('seed', computeSeed(masterSeed), account)} - }) + return {wallet, account: _.set('seed', computeSeed(masterSeed), account)} + }) } const lastBalance = {} @@ -45,54 +45,54 @@ const lastBalance = {} function _balance (settings, cryptoCode) { logger.debug('Polled wallet balance') return fetchWallet(settings, cryptoCode) - .then(r => r.wallet.balance(r.account, cryptoCode)) - .then(balance => ({balance, timestamp: Date.now()})) - .then(r => { - lastBalance[cryptoCode] = r - return r - }) - .catch(err => { - console.error(err) - return lastBalance[cryptoCode] - }) + .then(r => r.wallet.balance(r.account, cryptoCode)) + .then(balance => ({balance, timestamp: Date.now()})) + .then(r => { + lastBalance[cryptoCode] = r + return r + }) + .catch(err => { + console.error(err) + return lastBalance[cryptoCode] + }) } function sendCoins (settings, toAddress, cryptoAtoms, cryptoCode) { return fetchWallet(settings, cryptoCode) - .then(r => { - return r.wallet.sendCoins(r.account, toAddress, cryptoAtoms, cryptoCode) - .then(res => { - mem.clear(module.exports.balance) - return res + .then(r => { + return r.wallet.sendCoins(r.account, toAddress, cryptoAtoms, cryptoCode) + .then(res => { + mem.clear(module.exports.balance) + return res + }) }) - }) - .catch(err => { - if (err.name === INSUFFICIENT_FUNDS_NAME) { - throw httpError(INSUFFICIENT_FUNDS_NAME, INSUFFICIENT_FUNDS_CODE) - } + .catch(err => { + if (err.name === INSUFFICIENT_FUNDS_NAME) { + throw httpError(INSUFFICIENT_FUNDS_NAME, INSUFFICIENT_FUNDS_CODE) + } - throw err - }) + throw err + }) } function newAddress (settings, info) { return fetchWallet(settings, info.cryptoCode) - .then(r => r.wallet.newAddress(r.account, info)) + .then(r => r.wallet.newAddress(r.account, info)) } function newFunding (settings, cryptoCode, address) { return fetchWallet(settings, cryptoCode) - .then(r => { - const wallet = r.wallet - const account = r.account + .then(r => { + const wallet = r.wallet + const account = r.account - return wallet.newFunding(account, cryptoCode) - }) + return wallet.newFunding(account, cryptoCode) + }) } function getWalletStatus (settings, tx) { return 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, tx.cryptoCode)) } function authorizeZeroConf (settings, tx, machineId) { @@ -115,34 +115,34 @@ function authorizeZeroConf (settings, tx, machineId) { function getStatus (settings, tx, machineId) { return getWalletStatus(settings, tx) - .then((statusRec) => { - if (statusRec.status === 'authorized') { - return authorizeZeroConf(settings, tx, machineId) - .then(isAuthorized => { - const publishAge = Date.now() - tx.publishedAt + .then((statusRec) => { + if (statusRec.status === 'authorized') { + return authorizeZeroConf(settings, tx, machineId) + .then(isAuthorized => { + const publishAge = Date.now() - tx.publishedAt - const unauthorizedStatus = publishAge < ZERO_CONF_EXPIRATION - ? 'published' - : 'rejected' + const unauthorizedStatus = publishAge < ZERO_CONF_EXPIRATION + ? 'published' + : 'rejected' - const status = isAuthorized ? 'authorized' : unauthorizedStatus + const status = isAuthorized ? 'authorized' : unauthorizedStatus - return {status} - }) - } + return {status} + }) + } - return statusRec - }) + return statusRec + }) } function sweep (settings, cryptoCode, hdIndex) { return fetchWallet(settings, cryptoCode) - .then(r => r.wallet.sweep(r.account, cryptoCode, hdIndex)) + .then(r => r.wallet.sweep(r.account, cryptoCode, hdIndex)) } function isHd (settings, cryptoCode) { return fetchWallet(settings, cryptoCode) - .then(r => r.wallet.supportsHd) + .then(r => r.wallet.supportsHd) } function cryptoNetwork (settings, cryptoCode) { diff --git a/migrations/008-add-two-way.js b/migrations/008-add-two-way.js index 381c4486..db27faa4 100644 --- a/migrations/008-add-two-way.js +++ b/migrations/008-add-two-way.js @@ -5,7 +5,7 @@ function singleQuotify (item) { return '\'' + item + '\'' } exports.up = function (next) { var statuses = ['notSeen', 'published', 'authorized', 'instant', 'confirmed', 'rejected', 'insufficientFunds'] - .map(singleQuotify).join(',') + .map(singleQuotify).join(',') var sql = [ 'create type status_stage AS enum (' + statuses + ')', diff --git a/migrations/011-transactions-reload-2.js b/migrations/011-transactions-reload-2.js index bb84e8df..e4dd0811 100644 --- a/migrations/011-transactions-reload-2.js +++ b/migrations/011-transactions-reload-2.js @@ -4,9 +4,9 @@ function singleQuotify (item) { return '\'' + item + '\'' } exports.up = function (next) { var actions = ['published', 'authorized', 'instant', 'confirmed', 'rejected', - 'insufficientFunds', 'dispenseRequested', 'dispensed', 'notified', - 'addedPhone', 'redeem'] - .map(singleQuotify).join(',') + 'insufficientFunds', 'dispenseRequested', 'dispensed', 'notified', + 'addedPhone', 'redeem'] + .map(singleQuotify).join(',') var sql = [ `create table cash_in_txs ( diff --git a/migrations/036-add_customers_table.js b/migrations/036-add_customers_table.js index cb87a311..253e81e6 100644 --- a/migrations/036-add_customers_table.js +++ b/migrations/036-add_customers_table.js @@ -23,7 +23,7 @@ exports.up = function (next) { `insert into customers (id, name) VALUES ( '${anonymous.uuid}','${anonymous.name}' )`, `alter table cash_in_txs add column customer_id uuid references customers (id) DEFAULT '${anonymous.uuid}'`, `alter table cash_out_txs add column customer_id uuid references customers (id) DEFAULT '${anonymous.uuid}'` - ] + ] db.multi(sql, next) } diff --git a/migrations/037-add_compliance_authorizations_table.js b/migrations/037-add_compliance_authorizations_table.js index 8852cef3..3ccfa05b 100644 --- a/migrations/037-add_compliance_authorizations_table.js +++ b/migrations/037-add_compliance_authorizations_table.js @@ -3,7 +3,7 @@ var db = require('./db') exports.up = function (next) { const sql = [ "create type compliance_types as enum ('manual', 'sanctions', 'sanctions_override')", - `create table compliance_authorizations ( + `create table compliance_authorizations ( id uuid PRIMARY KEY, customer_id uuid REFERENCES customers (id), compliance_type compliance_types NOT NULL, diff --git a/migrations/1509091634946-support_logs.js b/migrations/1509091634946-support_logs.js index 7d4da210..1d87286c 100644 --- a/migrations/1509091634946-support_logs.js +++ b/migrations/1509091634946-support_logs.js @@ -6,7 +6,7 @@ exports.up = function (next) { id uuid PRIMARY KEY, device_id text, timestamp timestamptz not null default now() )`, - 'alter table logs add column server_timestamp timestamptz not null default now() ' + 'alter table logs add column server_timestamp timestamptz not null default now() ' ] db.multi(sql, next) diff --git a/migrations/1509439657189-add_machine_name_to_devices.js b/migrations/1509439657189-add_machine_name_to_devices.js index 38d77384..36895d43 100644 --- a/migrations/1509439657189-add_machine_name_to_devices.js +++ b/migrations/1509439657189-add_machine_name_to_devices.js @@ -3,23 +3,23 @@ const migrateTools = require('./migrate-tools') exports.up = function (next) { return migrateTools.migrateNames() - .then(updateSql => { - const sql = [ - 'alter table devices add column name text', - updateSql, - 'alter table devices alter column name set not null' - ] + .then(updateSql => { + const sql = [ + 'alter table devices add column name text', + updateSql, + 'alter table devices alter column name set not null' + ] - return db.multi(sql, next) - }) - .catch(() => { - const sql = [ - 'alter table devices add column name text', - 'alter table devices alter column name set not null' - ] + return db.multi(sql, next) + }) + .catch(() => { + const sql = [ + 'alter table devices add column name text', + 'alter table devices alter column name set not null' + ] - return db.multi(sql, next) - }) + return db.multi(sql, next) + }) } exports.down = function (next) { diff --git a/migrations/db.js b/migrations/db.js index 7cb08347..a750effc 100644 --- a/migrations/db.js +++ b/migrations/db.js @@ -7,17 +7,17 @@ function multi (sqls, cb) { const doQuery = s => { return () => { return db.none(s) - .catch(err => { - console.log(err.stack) - throw err - }) + .catch(err => { + console.log(err.stack) + throw err + }) } } return sequential(sqls.map(doQuery)) - .then(() => cb()) - .catch(err => { - console.log(err.stack) - cb(err) - }) + .then(() => cb()) + .catch(err => { + console.log(err.stack) + cb(err) + }) } diff --git a/migrations/migrate-tools.js b/migrations/migrate-tools.js index 8a084ede..e1d76e99 100644 --- a/migrations/migrate-tools.js +++ b/migrations/migrate-tools.js @@ -10,7 +10,7 @@ function migrateNames () { const cs = new pgp.helpers.ColumnSet(['device_id', 'name'], {table: 'devices'}) return settingsLoader.loadLatest() - .then(r => machineLoader.getMachineNames(r.config)) - .then(_.map(r => ({device_id: r.deviceId, name: r.name}))) - .then(data => pgp.helpers.update(data, cs)) + .then(r => machineLoader.getMachineNames(r.config)) + .then(_.map(r => ({device_id: r.deviceId, name: r.name}))) + .then(data => pgp.helpers.update(data, cs)) } diff --git a/test/unit/settings-loader-load-fixture.js b/test/unit/settings-loader-load-fixture.js index 530dd3da..5b117462 100644 --- a/test/unit/settings-loader-load-fixture.js +++ b/test/unit/settings-loader-load-fixture.js @@ -40,10 +40,10 @@ test('bigger merge', t => { ) const expected = [ - {fieldLocator: fieldLocator1, fieldValue: fieldValue4}, - {fieldLocator: fieldLocator4, fieldValue: fieldValue5}, - {fieldLocator: fieldLocator2, fieldValue: fieldValue2}, - {fieldLocator: fieldLocator3, fieldValue: fieldValue3} + {fieldLocator: fieldLocator1, fieldValue: fieldValue4}, + {fieldLocator: fieldLocator4, fieldValue: fieldValue5}, + {fieldLocator: fieldLocator2, fieldValue: fieldValue2}, + {fieldLocator: fieldLocator3, fieldValue: fieldValue3} ] t.deepEqual(merged, expected) diff --git a/tools/gen-countries.js b/tools/gen-countries.js index 9f2fcafd..580d9aa8 100644 --- a/tools/gen-countries.js +++ b/tools/gen-countries.js @@ -6,7 +6,7 @@ const rawCountries = require('../raw-countries.json') const topCodes = ['US', 'GB', 'CA', 'AU'] const countries = rawCountries -.map(r => ({code: r.cca2, display: r.name.common})) + .map(r => ({code: r.cca2, display: r.name.common})) const topCountries = topCodes.map(c => countries.find(_.matchesProperty('code', c))) const final = _.uniqBy(_.get('code'), _.concat(topCountries, countries)) diff --git a/tools/modify.js b/tools/modify.js index 307744f3..7ac87974 100644 --- a/tools/modify.js +++ b/tools/modify.js @@ -5,7 +5,7 @@ const fields = [ ] settingsLoader.modifyConfig(fields) -.then(() => { - console.log('success.') - process.exit(0) -}) + .then(() => { + console.log('success.') + process.exit(0) + }) diff --git a/tools/show.js b/tools/show.js index daca1433..876d27e1 100644 --- a/tools/show.js +++ b/tools/show.js @@ -11,15 +11,15 @@ function dbFetchConfig () { 'select data from user_config where type=$1 order by id desc limit 1', ['config'] ) - .then(row => row && row.data) + .then(row => row && row.data) } dbFetchConfig() -.then(config => { - pp(config) - process.exit(0) -}) -.catch(e => { - console.log(e) - process.exit(1) -}) + .then(config => { + pp(config) + process.exit(0) + }) + .catch(e => { + console.log(e) + process.exit(1) + }) From 3b35743bb5edd54516bb9cba2dfb9eab624e62a1 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 11 Mar 2018 14:08:15 +0000 Subject: [PATCH 24/36] fix undefined crypto scope --- lib/config-validate.js | 4 ---- lib/settings-loader.js | 13 ++++++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/config-validate.js b/lib/config-validate.js index c0b6fc6a..e20151ae 100644 --- a/lib/config-validate.js +++ b/lib/config-validate.js @@ -55,10 +55,6 @@ function satisfiesRequire (config, cryptos, machineList, field, anyFields, allFi const isValid = isRequired ? !isBlank : true - if (!isValid) { - pp('DEBUG103')({fieldCode, isBlank, isRequired}) - } - return isValid }) } diff --git a/lib/settings-loader.js b/lib/settings-loader.js index 1d0b99b1..a0718a96 100644 --- a/lib/settings-loader.js +++ b/lib/settings-loader.js @@ -197,6 +197,8 @@ function cryptoCodeDefaults (schema, cryptoCode) { const hasCryptoSpecificDefault = r => r.cryptoScope === 'specific' && !_.isNil(r.default) const cryptoSpecificFields = _.filter(hasCryptoSpecificDefault, schemaEntries) + pp('DEBUG202')({scope, cryptoCode}) + return _.map(r => { const defaultValue = cryptoDefaultOverride(cryptoCode, r.code, r.default) @@ -204,14 +206,19 @@ function cryptoCodeDefaults (schema, cryptoCode) { }, cryptoSpecificFields) } +const uniqCompact = _.flow(_.compact, _.uniq) + +const pp = require('./pp') function addCryptoDefaults (oldConfig, newFields) { const cryptoCodeEntries = _.filter(v => v.fieldLocator.code === 'cryptoCurrencies', newFields) - const cryptoCodes = _.map(_.get('fieldValue.value'), cryptoCodeEntries) - const uniqueCryptoCodes = _.uniq(_.flatten(cryptoCodes)) + const cryptoCodes = _.flatMap(_.get('fieldValue.value'), cryptoCodeEntries) + const uniqueCryptoCodes = uniqCompact(cryptoCodes) const mapDefaults = cryptoCode => cryptoCodeDefaults(schema, cryptoCode) - const defaults = _.flatten(_.map(mapDefaults, uniqueCryptoCodes)) + const defaults = _.flatMap(mapDefaults, uniqueCryptoCodes) + pp('DEBUG201')({cryptoCodeEntries, cryptoCodes, uniqueCryptoCodes}) + pp('DEBUG200')(defaults) return mergeValues(defaults, oldConfig) } From 1aaabac0042db7f063e6b63c84dbf1589718bee6 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 11 Mar 2018 14:08:26 +0000 Subject: [PATCH 25/36] Remove Ramda from config.js; clean invalid crypto scopes --- lib/admin/config.js | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/lib/admin/config.js b/lib/admin/config.js index 1979bb68..0e6ace73 100644 --- a/lib/admin/config.js +++ b/lib/admin/config.js @@ -1,7 +1,6 @@ const pify = require('pify') const fs = pify(require('fs')) const path = require('path') -const R = require('ramda') const _ = require('lodash/fp') const currencies = require('../../currencies.json') @@ -51,17 +50,17 @@ function allMachineScopes (machineList, machineScope) { function getCryptos (config, machineList) { const scopes = allScopes(['global'], allMachineScopes(machineList, 'both')) const scoped = scope => configManager.scopedValue(scope[0], scope[1], 'cryptoCurrencies', config) - return scopes.reduce((acc, scope) => R.union(acc, scoped(scope)), []) + return scopes.reduce((acc, scope) => _.union(acc, scoped(scope)), []) } function getGroup (schema, fieldCode) { - return schema.groups.find(group => group.fields.find(R.equals(fieldCode))) + return schema.groups.find(group => group.fields.find(_.isEqual(fieldCode))) } function getField (schema, group, fieldCode) { if (!group) group = getGroup(schema, fieldCode) const field = schema.fields.find(r => r.code === fieldCode) - return R.merge(R.pick(['cryptoScope', 'machineScope'], group), field) + return _.merge(_.pick(['cryptoScope', 'machineScope'], group), field) } const fetchMachines = () => machineLoader.getMachines() @@ -72,7 +71,7 @@ function validateCurrentConfig () { .then(configValidate.validateRequires) } -function decorateEnabledIf (schemaFields, schemaField) { +const decorateEnabledIf = _.curry((schemaFields, schemaField) => { const code = schemaField.fieldLocator.code const field = _.find(f => f.code === code, schemaFields) @@ -80,10 +79,10 @@ function decorateEnabledIf (schemaFields, schemaField) { fieldEnabledIfAny: field.enabledIfAny || [], fieldEnabledIfAll: field.enabledIfAll || [] }) -} +}) function fetchConfigGroup (code) { - const fieldLocatorCodeEq = R.pathEq(['fieldLocator', 'code']) + const fieldLocatorCodeEq = _.matchesProperty(['fieldLocator', 'code']) return Promise.all([fetchSchema(), fetchData(), fetchConfig(), fetchMachines()]) .then(([schema, data, config, machineList]) => { const groupSchema = schema.groups.find(r => r.code === code) @@ -91,34 +90,42 @@ function fetchConfigGroup (code) { if (!groupSchema) throw new Error('No such group schema: ' + code) const schemaFields = groupSchema.fields - .map(R.curry(getField)(schema, groupSchema)) + .map(_.curry(getField)(schema, groupSchema)) .map(f => _.assign(f, { fieldEnabledIfAny: f.enabledIfAny || [], fieldEnabledIfAll: f.enabledIfAll || [] })) const candidateFields = [ - schemaFields.map(R.prop('requiredIf')), - schemaFields.map(R.prop('enabledIfAny')), - schemaFields.map(R.prop('enabledIfAll')), + schemaFields.map(_.get('requiredIf')), + schemaFields.map(_.get('enabledIfAny')), + schemaFields.map(_.get('enabledIfAll')), groupSchema.fields, 'fiatCurrency' ] - const configFields = R.uniq(R.flatten(candidateFields)).filter(R.identity) + + const smush = _.flow(_.flatten, _.compact, _.uniq) + const configFields = smush(candidateFields) + + // Expand this to check against full schema + const fieldValidator = field => !_.isNil(_.get('fieldLocator.fieldScope.crypto', field)) const reducer = (acc, configField) => { return acc.concat(config.filter(fieldLocatorCodeEq(configField))) } - const values = _.map(f => decorateEnabledIf(schema.fields, f), configFields.reduce(reducer, [])) + const reducedFields = _.filter(fieldValidator, configFields.reduce(reducer, [])) + const values = _.map(decorateEnabledIf(schema.fields), reducedFields) groupSchema.fields = undefined groupSchema.entries = schemaFields + const selectedCryptos = _.defaultTo([], getCryptos(config, machineList)) + return { schema: groupSchema, values, - selectedCryptos: getCryptos(config, machineList), + selectedCryptos, data } }) @@ -130,10 +137,10 @@ function massageCurrencies (currencies) { display: r['Currency'] }) const top5Codes = ['USD', 'EUR', 'GBP', 'CAD', 'AUD'] - const mapped = R.map(convert, currencies) - const codeToRec = code => R.find(R.propEq('code', code), mapped) - const top5 = R.map(codeToRec, top5Codes) - const raw = R.uniqBy(R.prop('code'), R.concat(top5, mapped)) + const mapped = _.map(convert, currencies) + const codeToRec = code => _.find(_.matchesProperty('code', code), mapped) + const top5 = _.map(codeToRec, top5Codes) + const raw = _.uniqBy(_.get('code'), _.concat(top5, mapped)) return raw.filter(r => r.code[0] !== 'X' && r.display.indexOf('(') === -1) } From ba1fc26944b51338129cb0bd6b919425ff2b6d6c Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 11 Mar 2018 15:06:04 +0000 Subject: [PATCH 26/36] fix blank machine name issue --- lib/machine-loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/machine-loader.js b/lib/machine-loader.js index 3299a11e..fab5c890 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -30,7 +30,7 @@ function getMachineNames (config) { .then(([machines, config]) => { const addName = r => { const machineScoped = configManager.machineScoped(r.deviceId, config) - const name = machineScoped.machineName + const name = _.defaultTo('', machineScoped.machineName) const cashOut = machineScoped.cashOutEnabled return _.assign(r, {name, cashOut}) From 28d84cbbcf5c5ce6c16e69270f30cd1edfd766cb Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 11 Mar 2018 15:06:35 +0000 Subject: [PATCH 27/36] 5.8.1 --- 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 bbf167c7..6ffae26f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "5.8.0", + "version": "5.8.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 67baaf2e..6b0584c0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "5.8.0", + "version": "5.8.1", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From 982c60c7f59fb2e275672228bcf5ac5fd2d6a999 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 11 Mar 2018 19:37:27 +0000 Subject: [PATCH 28/36] remove ramda dependency --- lib/route-helpers.js | 9 +++++---- package.json | 1 - tools/currencies.js | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/route-helpers.js b/lib/route-helpers.js index d008fac6..eff676d6 100644 --- a/lib/route-helpers.js +++ b/lib/route-helpers.js @@ -1,5 +1,4 @@ const _ = require('lodash/fp') -const R = require('ramda') const db = require('./db') const dbm = require('./postgresql_interface') @@ -57,11 +56,13 @@ function fetchPhoneTx (phone) { return db.any(sql, values) .then(_.map(toCashOutTx)) .then(txs => { - const confirmedTxs = txs.filter(tx => R.contains(tx.status, ['instant', 'confirmed'])) + const confirmedTxs = txs.filter(tx => _.includes(tx.status, ['instant', 'confirmed'])) if (confirmedTxs.length > 0) { - const maxTx = R.reduce((acc, val) => { + const reducer = (acc, val) => { return !acc || val.cryptoAtoms.gt(acc.cryptoAtoms) ? val : acc - }, null, confirmedTxs) + } + + const maxTx = _.reduce(reducer, null, confirmedTxs) return maxTx } diff --git a/package.json b/package.json index 6b0584c0..ef2e5390 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "pify": "^3.0.0", "pretty-ms": "^2.1.0", "promise-sequential": "^1.1.1", - "ramda": "^0.22.1", "serve-static": "^1.12.4", "socket.io": "^2.0.3", "socket.io-client": "^2.0.3", diff --git a/tools/currencies.js b/tools/currencies.js index 9acf407d..9afb2d68 100644 --- a/tools/currencies.js +++ b/tools/currencies.js @@ -1,7 +1,6 @@ // Pull latest from: http://www.currency-iso.org/en/home/tables/table-a1.html // Convert to JSON at: http://www.csvjson.com/csv2json -const R = require('ramda') const currencies = require('../currencies.json') function goodCurrency (currency) { From edd54c16f5ad443241fb44ffaf44c85dd30492e5 Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Thu, 22 Mar 2018 17:12:53 +0000 Subject: [PATCH 29/36] Support CashAddr format (#109) * Don't convert BCH address * Update for CashAddr * Rename to Bitcoin Cash * fix typo --- lib/admin/config.js | 2 +- lib/coin-utils.js | 4 +-- .../wallet/bitcoincashd/bitcoincashd.js | 25 ++----------------- lib/plugins/wallet/bitcoind/bitcoind.js | 2 +- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/lib/admin/config.js b/lib/admin/config.js index 0e6ace73..dd4347cd 100644 --- a/lib/admin/config.js +++ b/lib/admin/config.js @@ -169,7 +169,7 @@ function fetchData () { {crypto: 'LTC', display: 'Litecoin'}, {crypto: 'DASH', display: 'Dash'}, {crypto: 'ZEC', display: 'Zcash'}, - {crypto: 'BCH', display: 'BCH'} + {crypto: 'BCH', display: 'Bitcoin Cash'} ], languages: languages, countries, diff --git a/lib/coin-utils.js b/lib/coin-utils.js index d5de3f84..47b2c36c 100644 --- a/lib/coin-utils.js +++ b/lib/coin-utils.js @@ -52,7 +52,7 @@ const CRYPTO_CURRENCIES = [ }, { cryptoCode: 'BCH', - display: 'BCH', + display: 'Bitcoin Cash', code: 'bitcoincash', configFile: 'bitcoincash.conf', daemon: 'bitcoincashd', @@ -80,7 +80,7 @@ function buildUrl (cryptoCode, address) { case 'ZEC': return `zcash:${address}` case 'LTC': return `litecoin:${address}` case 'DASH': return `dash:${address}` - case 'BCH': return `bitcoincash:${address}` + case 'BCH': return `${address}` default: throw new Error(`Unsupported crypto: ${cryptoCode}`) } } diff --git a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js index df806d48..949d4a67 100644 --- a/lib/plugins/wallet/bitcoincashd/bitcoincashd.js +++ b/lib/plugins/wallet/bitcoincashd/bitcoincashd.js @@ -1,6 +1,5 @@ const jsonRpc = require('../../common/json-rpc') -const bs58check = require('bs58check') const BN = require('../../../bn') const E = require('../../../error') const coinUtils = require('../../../coin-utils') @@ -37,29 +36,11 @@ function balance (account, cryptoCode) { return accountBalance(account, cryptoCode, 1) } -function bchToBtcVersion (version) { - if (version === 0x1c) return 0x00 - if (version === 0x28) return 0x05 - - return version -} - -// Bitcoin-ABC only accepts BTC style addresses at this point, -// so we need to convert -function bchToBtcAddress (address) { - const buf = bs58check.decode(address) - const version = buf[0] - buf[0] = bchToBtcVersion(version) - return bs58check.encode(buf) -} - function sendCoins (account, address, cryptoAtoms, cryptoCode) { const coins = cryptoAtoms.shift(-unitScale).toFixed(8) - const btcAddress = bchToBtcAddress(address) - return checkCryptoCode(cryptoCode) - .then(() => fetch('sendtoaddress', [btcAddress, coins])) + .then(() => fetch('sendtoaddress', [address, coins])) .catch(err => { if (err.code === -6) throw new E.InsufficientFundsError() throw err @@ -72,9 +53,7 @@ function newAddress (account, info) { } function addressBalance (address, confs) { - const btcAddress = bchToBtcAddress(address) - - return fetch('getreceivedbyaddress', [btcAddress, confs]) + return fetch('getreceivedbyaddress', [address, confs]) .then(r => BN(r).shift(unitScale).round()) } diff --git a/lib/plugins/wallet/bitcoind/bitcoind.js b/lib/plugins/wallet/bitcoind/bitcoind.js index e4c3d95d..ced3f636 100644 --- a/lib/plugins/wallet/bitcoind/bitcoind.js +++ b/lib/plugins/wallet/bitcoind/bitcoind.js @@ -24,7 +24,7 @@ function checkCryptoCode (cryptoCode) { return Promise.resolve() } -function accountBalance (acount, cryptoCode, confirmations) { +function accountBalance (account, cryptoCode, confirmations) { return checkCryptoCode(cryptoCode) .then(() => fetch('getbalance', ['', confirmations])) .then(r => BN(r).shift(unitScale).round()) From dfd8400da9aabf962fa952016046632053ec36c3 Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Thu, 22 Mar 2018 17:13:51 +0000 Subject: [PATCH 30/36] Update ETH, ZEC, LTC wallet versions (#106) * Update ETH, ZEC, LTC * Update -disabledeprecation --- lib/blockchain/common.js | 12 ++++++------ lib/blockchain/zcash.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js index fc898726..46f1a38e 100644 --- a/lib/blockchain/common.js +++ b/lib/blockchain/common.js @@ -25,20 +25,20 @@ const BINARIES = { dir: 'bitcoin-0.16.0/bin' }, ETH: { - url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.1-1e67410e.tar.gz', - dir: 'geth-linux-amd64-1.8.1-1e67410e' + url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.2-b8b9f7f4.tar.gz', + dir: 'geth-linux-amd64-1.8.2-b8b9f7f4' }, ZEC: { - url: 'https://z.cash/downloads/zcash-1.0.14-linux64.tar.gz', - dir: 'zcash-1.0.14/bin' + url: 'https://z.cash/downloads/zcash-1.0.15-linux64.tar.gz', + dir: 'zcash-1.0.15/bin' }, DASH: { url: 'https://github.com/dashpay/dash/releases/download/v0.12.2.3/dashcore-0.12.2.3-linux64.tar.gz', dir: 'dashcore-0.12.2/bin' }, LTC: { - url: 'https://download.litecoin.org/litecoin-0.14.2/linux/litecoin-0.14.2-x86_64-linux-gnu.tar.gz', - dir: 'litecoin-0.14.2/bin' + url: 'https://download.litecoin.org/litecoin-0.15.1/linux/litecoin-0.15.1-x86_64-linux-gnu.tar.gz', + dir: 'litecoin-0.15.1/bin' }, BCH: { url: 'https://download.bitcoinabc.org/0.16.2/linux/bitcoin-abc-0.16.2-x86_64-linux-gnu.tar.gz', diff --git a/lib/blockchain/zcash.js b/lib/blockchain/zcash.js index ea631346..5277446d 100644 --- a/lib/blockchain/zcash.js +++ b/lib/blockchain/zcash.js @@ -20,7 +20,7 @@ function setup (dataDir) { logger.info('Finished fetching proofs.') const config = buildConfig() common.writeFile(path.resolve(dataDir, coinRec.configFile), config) - const cmd = `/usr/local/bin/${coinRec.daemon} -datadir=${dataDir} -disabledeprecation=1.0.14` + const cmd = `/usr/local/bin/${coinRec.daemon} -datadir=${dataDir} -disabledeprecation=1.0.15` common.writeSupervisorConfig(coinRec, cmd) } From 264e3ab65185c70cf4da6a3bb7ca475d78310f84 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 26 Mar 2018 12:12:08 +0300 Subject: [PATCH 31/36] fix enabledIf bug --- lib/admin/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/config.js b/lib/admin/config.js index dd4347cd..c6e189bb 100644 --- a/lib/admin/config.js +++ b/lib/admin/config.js @@ -104,7 +104,7 @@ function fetchConfigGroup (code) { 'fiatCurrency' ] - const smush = _.flow(_.flatten, _.compact, _.uniq) + const smush = _.flow(_.flattenDeep, _.compact, _.uniq) const configFields = smush(candidateFields) // Expand this to check against full schema From aa0bc44e89a4aa222679c2c19e6b2ceb1e724cfd Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 26 Mar 2018 14:58:44 +0300 Subject: [PATCH 32/36] 5.8.2 --- 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 6ffae26f..6ee3560b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "5.8.1", + "version": "5.8.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ef2e5390..36604269 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "5.8.1", + "version": "5.8.2", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From f40cafabae2de1ecaa4077aa3409ff517da96377 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Tue, 27 Mar 2018 18:16:15 +0300 Subject: [PATCH 33/36] latest admin-elm build --- public/elm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/elm.js b/public/elm.js index 6f893768..50f9ebf7 100644 --- a/public/elm.js +++ b/public/elm.js @@ -33803,7 +33803,7 @@ var _user$project$Transactions$rowView = function (tx) { }); } else { var _p3 = _p1._0; - var status = _elm_community$maybe_extra$Maybe_Extra$isJust(_p3.error) ? 'Error' : 'Success'; + var status = _elm_community$maybe_extra$Maybe_Extra$isJust(_p3.error) ? 'Error' : (_p3.dispense ? 'Success' : 'Pending'); return A2( _elm_lang$html$Html$tr, { From ad5501368934e5af708fce4b1c124d9df3faa391 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Wed, 28 Mar 2018 20:04:52 +0300 Subject: [PATCH 34/36] remove debug --- lib/wallet.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/wallet.js b/lib/wallet.js index b92c0ec8..57bd7939 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -43,7 +43,6 @@ function fetchWallet (settings, cryptoCode) { const lastBalance = {} function _balance (settings, cryptoCode) { - logger.debug('Polled wallet balance') return fetchWallet(settings, cryptoCode) .then(r => r.wallet.balance(r.account, cryptoCode)) .then(balance => ({balance, timestamp: Date.now()})) From 8caa1187bc0349aae3c0d6342bd3851b42b4f60c Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Wed, 28 Mar 2018 20:29:23 +0300 Subject: [PATCH 35/36] latest admin-elm --- public/elm.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/public/elm.js b/public/elm.js index 50f9ebf7..ab83b741 100644 --- a/public/elm.js +++ b/public/elm.js @@ -28021,6 +28021,9 @@ var _user$project$Common_Customer_Types$authorizedToString = function (model) { return 'automatic'; } }; +var _user$project$Common_Customer_Types$IdCardData = function (a) { + return {uid: a}; +}; var _user$project$Common_Customer_Types$Customer = function (a) { return function (b) { return function (c) { @@ -28094,6 +28097,11 @@ var _user$project$Common_Customer_Types$Verified = {ctor: 'Verified'}; var _user$project$Common_Customer_Types$Blocked = {ctor: 'Blocked'}; var _user$project$Common_Customer_Types$Automatic = {ctor: 'Automatic'}; +var _user$project$Common_Customer_Decoder$idCardDataDecoder = A3( + _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$required, + 'uid', + _elm_lang$core$Json_Decode$string, + _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$decode(_user$project$Common_Customer_Types$IdCardData)); var _user$project$Common_Customer_Decoder$mapAuthorizedTypes = function (s) { var _p0 = s; switch (_p0) { @@ -28192,7 +28200,7 @@ var _user$project$Common_Customer_Decoder$customerDecoder = A3( A3( _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$required, 'idCardData', - _elm_lang$core$Json_Decode$nullable(_elm_lang$core$Json_Decode$string), + _elm_lang$core$Json_Decode$nullable(_user$project$Common_Customer_Decoder$idCardDataDecoder), A3( _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$required, 'authorizedAt', From ea52d146129f0434075802e7a6d3489c98b4a3a7 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Wed, 28 Mar 2018 20:31:16 +0300 Subject: [PATCH 36/36] 5.8.3 --- 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 6ee3560b..6263d212 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "5.8.2", + "version": "5.8.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 36604269..cd89a5a3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "5.8.2", + "version": "5.8.3", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": {