format for latest standard

This commit is contained in:
Josh Harvey 2018-03-10 18:59:40 +00:00
parent 4108efd9c7
commit c2af183911
77 changed files with 1697 additions and 1693 deletions

View file

@ -7,44 +7,44 @@ var db = pgp(psqlUrl)
db.manyOrNone(`select * from transactions where incoming=false db.manyOrNone(`select * from transactions where incoming=false
and stage='final_request' and authority='machine'`) and stage='final_request' and authority='machine'`)
.then(rs => .then(rs =>
db.tx(t => db.tx(t =>
t.batch(rs.map(r => db.none(`insert into cash_in_txs (session_id, t.batch(rs.map(r => db.none(`insert into cash_in_txs (session_id,
device_fingerprint, to_address, crypto_atoms, crypto_code, fiat, device_fingerprint, to_address, crypto_atoms, crypto_code, fiat,
currency_code, fee, tx_hash, error, created) values ($1, $2, $3, $4, $5, 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, $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.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code, r.fee,
r.tx_hash, r.error, r.created])) 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'`)) and stage='initial_request' and authority='pending'`))
.then(rs => .then(rs =>
db.tx(t => db.tx(t =>
t.batch(rs.map(r => db.none(`insert into cash_out_txs (session_id, t.batch(rs.map(r => db.none(`insert into cash_out_txs (session_id,
device_fingerprint, to_address, crypto_atoms, crypto_code, fiat, device_fingerprint, to_address, crypto_atoms, crypto_code, fiat,
currency_code, tx_hash, phone, error, created) values ($1, $2, $3, $4, $5, 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, $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.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code,
r.tx_hash, r.phone, r.error, r.created])) 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'`)) and stage='dispense' and authority='authorized'`))
.then(rs => .then(rs =>
db.tx(t => db.tx(t =>
t.batch(rs.map(r => t.batch(rs.map(r =>
db.none(`update cash_out_txs set dispensed=true where session_id=$1`, [r.session_id]) 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(() => db.none(`insert into cash_out_actions (session_id, action,
created) values ($1, $2, $3)`, [r.session_id, 'dispensed', r.created])) created) values ($1, $2, $3)`, [r.session_id, 'dispensed', r.created]))
)) ))
)
) )
) .then(() => pgp.end())
.then(() => pgp.end()) .then(() => console.log('Success.'))
.then(() => console.log('Success.')) .catch(e => {
.catch(e => { console.log(e)
console.log(e) pgp.end()
pgp.end() })
})

View file

@ -2,11 +2,11 @@ const settingsLoader = require('../lib/settings-loader')
const pp = require('../lib/pp') const pp = require('../lib/pp')
settingsLoader.loadLatest() settingsLoader.loadLatest()
.then(r => { .then(r => {
pp('config')(r) pp('config')(r)
process.exit(0) process.exit(0)
}) })
.catch(e => { .catch(e => {
console.log(e.stack) console.log(e.stack)
process.exit(1) process.exit(1)
}) })

View file

@ -8,38 +8,38 @@ const psqlUrl = require('../lib/options').postgresql
const db = pgp(psqlUrl) const db = pgp(psqlUrl)
db.many('select data from user_config', 'exchanges') db.many('select data from user_config', 'exchanges')
.then(rows => { .then(rows => {
const config = rows.filter(r => r.type === 'exchanges')[0].data const config = rows.filter(r => r.type === 'exchanges')[0].data
const brain = rows.filter(r => r.type === 'unit')[0].data const brain = rows.filter(r => r.type === 'unit')[0].data
const settings = config.exchanges.settings const settings = config.exchanges.settings
const compliance = settings.compliance const compliance = settings.compliance
const newConfig = { const newConfig = {
global: { global: {
cashInTransactionLimit: compliance.maximum.limit, cashInTransactionLimit: compliance.maximum.limit,
cashOutTransactionLimit: settings.fiatTxLimit, cashOutTransactionLimit: settings.fiatTxLimit,
cashInCommission: settings.commission, cashInCommission: settings.commission,
cashOutCommission: settings.fiatCommission || settings.commission, cashOutCommission: settings.fiatCommission || settings.commission,
idVerificationEnabled: compliance.idVerificationEnabled, idVerificationEnabled: compliance.idVerificationEnabled,
idVerificationLimit: compliance.idVerificationLimit, idVerificationLimit: compliance.idVerificationLimit,
lowBalanceMargin: settings.lowBalanceMargin, lowBalanceMargin: settings.lowBalanceMargin,
zeroConfLimit: settings.zeroConfLimit, zeroConfLimit: settings.zeroConfLimit,
fiatCurrency: settings.currency, fiatCurrency: settings.currency,
topCashOutDenomination: settings.cartridges[0], topCashOutDenomination: settings.cartridges[0],
bottomCashOutDenomination: settings.cartridges[1], bottomCashOutDenomination: settings.cartridges[1],
virtualCashOutDenomination: settings.virtualCartridges[0], virtualCashOutDenomination: settings.virtualCartridges[0],
machineLanguages: brain.locale.localeInfo.primaryLocales, machineLanguages: brain.locale.localeInfo.primaryLocales,
coins: settings.coins coins: settings.coins
}, },
accounts: settings.plugins.settings accounts: settings.plugins.settings
} }
db.none('insert into user_config (type, data) values ($1, $2)', ['global', newConfig]) db.none('insert into user_config (type, data) values ($1, $2)', ['global', newConfig])
.then(() => { .then(() => {
console.log('Success.') console.log('Success.')
process.exit(0) process.exit(0)
})
.catch(err => {
console.error('Error: %s', err)
process.exit(1)
})
}) })
.catch(err => {
console.error('Error: %s', err)
process.exit(1)
})
})

View file

@ -13,7 +13,7 @@ const headers = {
const body = JSON.stringify({tx: tx}) const body = JSON.stringify({tx: tx})
got('http://localhost:3000/dispense', {body: body, json: true, headers: headers}) got('http://localhost:3000/dispense', {body: body, json: true, headers: headers})
.then(res => { .then(res => {
console.log(res.body) console.log(res.body)
}) })
.catch(console.log) .catch(console.log)

View file

@ -15,17 +15,17 @@ db.init(psqlUrl)
notifier.init(db, getBalances, {lowBalanceThreshold: 10}) notifier.init(db, getBalances, {lowBalanceThreshold: 10})
console.log('DEBUG0') console.log('DEBUG0')
notifier.checkStatus() notifier.checkStatus()
.then(function (alertRec) { .then(function (alertRec) {
console.log('DEBUG1') console.log('DEBUG1')
console.log('%j', alertRec) console.log('%j', alertRec)
var subject = notifier.alertSubject(alertRec) var subject = notifier.alertSubject(alertRec)
console.log(subject) console.log(subject)
var body = notifier.printEmailAlerts(alertRec) var body = notifier.printEmailAlerts(alertRec)
console.log(body) console.log(body)
console.log(notifier.alertFingerprint(alertRec)) console.log(notifier.alertFingerprint(alertRec))
process.exit(0) process.exit(0)
}) })
.catch(function (err) { .catch(function (err) {
console.log(err.stack) console.log(err.stack)
process.exit(1) process.exit(1)
}) })

View file

@ -17,13 +17,13 @@ var rec = {
var db = config.connection var db = config.connection
config.loadConfig(db) config.loadConfig(db)
.then(function (config) { .then(function (config) {
plugins.configure(config) plugins.configure(config)
plugins.sendMessage(rec) plugins.sendMessage(rec)
.then(function () { .then(function () {
console.log('Success.') console.log('Success.')
})
.catch(function (err) {
console.log(err.stack)
})
}) })
.catch(function (err) {
console.log(err.stack)
})
})

View file

@ -8,22 +8,21 @@ const schemas = ph.loadSchemas()
function fetchAccounts () { function fetchAccounts () {
return db.oneOrNone('select data from user_config where type=$1', ['accounts']) return db.oneOrNone('select data from user_config where type=$1', ['accounts'])
.then(row => { .then(row => {
// Hard code this for now // Hard code this for now
const accounts = [{ const accounts = [{
code: 'blockcypher', code: 'blockcypher',
display: 'Blockcypher', display: 'Blockcypher',
fields: [ fields: [
{ code: 'confidenceFactor', display: 'Confidence Factor', fieldType: 'integer', required: true, value: 90 } { code: 'confidenceFactor', display: 'Confidence Factor', fieldType: 'integer', required: true, value: 90 }
] ]
}] }]
return row return row
? Promise.resolve(row.data.accounts) ? Promise.resolve(row.data.accounts)
: db.none('insert into user_config (type, data, valid) values ($1, $2, $3)', ['accounts', {accounts}, true]) : db.none('insert into user_config (type, data, valid) values ($1, $2, $3)', ['accounts', {accounts}, true])
.then(fetchAccounts) .then(fetchAccounts)
}) })
} }
function selectedAccounts () { function selectedAccounts () {

View file

@ -53,7 +53,7 @@ module.exports = {run}
function dbNotify () { function dbNotify () {
return got.post('http://localhost:3030/dbChange') 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 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') if (!name) return res.status(400).send('Name is required')
return pairing.totem(hostname, name) return pairing.totem(hostname, name)
.then(totem => res.send(totem)) .then(totem => res.send(totem))
}) })
app.get('/api/accounts', (req, res) => { app.get('/api/accounts', (req, res) => {
accounts.selectedAccounts() accounts.selectedAccounts()
.then(accounts => res.json({accounts: accounts})) .then(accounts => res.json({accounts: accounts}))
}) })
app.get('/api/account/:account', (req, res) => { app.get('/api/account/:account', (req, res) => {
accounts.getAccount(req.params.account) accounts.getAccount(req.params.account)
.then(account => res.json(account)) .then(account => res.json(account))
}) })
app.post('/api/account', (req, res) => { app.post('/api/account', (req, res) => {
return accounts.updateAccount(req.body) return accounts.updateAccount(req.body)
.then(account => res.json(account)) .then(account => res.json(account))
.then(() => dbNotify()) .then(() => dbNotify())
}) })
app.get('/api/config/:config', (req, res) => 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) => { app.post('/api/config', (req, res, next) => {
config.saveConfigGroup(req.body) config.saveConfigGroup(req.body)
.then(c => res.json(c)) .then(c => res.json(c))
.then(() => dbNotify()) .then(() => dbNotify())
.catch(next) .catch(next)
}) })
app.get('/api/accounts/account/:account', (req, res) => { app.get('/api/accounts/account/:account', (req, res) => {
accounts.getAccount(req.params.account) accounts.getAccount(req.params.account)
.then(r => res.send(r)) .then(r => res.send(r))
}) })
app.get('/api/machines', (req, res) => { app.get('/api/machines', (req, res) => {
machineLoader.getMachineNames() machineLoader.getMachineNames()
.then(r => res.send({machines: r})) .then(r => res.send({machines: r}))
}) })
app.post('/api/machines', (req, res) => { app.post('/api/machines', (req, res) => {
machineLoader.setMachine(req.body) machineLoader.setMachine(req.body)
.then(() => machineLoader.getMachineNames()) .then(() => machineLoader.getMachineNames())
.then(r => res.send({machines: r})) .then(r => res.send({machines: r}))
.then(() => dbNotify()) .then(() => dbNotify())
}) })
app.get('/api/funding', (req, res) => { app.get('/api/funding', (req, res) => {
return funding.getFunding() return funding.getFunding()
.then(r => res.json(r)) .then(r => res.json(r))
}) })
app.get('/api/funding/:cryptoCode', (req, res) => { app.get('/api/funding/:cryptoCode', (req, res) => {
const cryptoCode = req.params.cryptoCode const cryptoCode = req.params.cryptoCode
return funding.getFunding(cryptoCode) return funding.getFunding(cryptoCode)
.then(r => res.json(r)) .then(r => res.json(r))
}) })
app.get('/api/status', (req, res, next) => { app.get('/api/status', (req, res, next) => {
return Promise.all([server.status(), config.validateCurrentConfig()]) return Promise.all([server.status(), config.validateCurrentConfig()])
.then(([serverStatus, invalidConfigGroups]) => res.send({ .then(([serverStatus, invalidConfigGroups]) => res.send({
server: serverStatus, server: serverStatus,
invalidConfigGroups invalidConfigGroups
})) }))
.catch(next) .catch(next)
}) })
app.get('/api/transactions', (req, res, next) => { app.get('/api/transactions', (req, res, next) => {
return transactions.batch() return transactions.batch()
.then(r => res.send({transactions: r})) .then(r => res.send({transactions: r}))
.catch(next) .catch(next)
}) })
app.get('/api/transaction/:id', (req, res, next) => { app.get('/api/transaction/:id', (req, res, next) => {
return transactions.single(req.params.id) return transactions.single(req.params.id)
.then(r => { .then(r => {
if (!r) return res.status(404).send({Error: 'Not found'}) if (!r) return res.status(404).send({Error: 'Not found'})
return res.send(r) return res.send(r)
}) })
}) })
app.patch('/api/transaction/:id', (req, res, next) => { app.patch('/api/transaction/:id', (req, res, next) => {
if (!req.query.cancel) return res.status(400).send({Error: 'Requires cancel'}) if (!req.query.cancel) return res.status(400).send({Error: 'Requires cancel'})
return transactions.cancel(req.params.id) return transactions.cancel(req.params.id)
.then(r => { .then(r => {
return res.send(r) return res.send(r)
}) })
.catch(() => res.status(404).send({Error: 'Not found'})) .catch(() => res.status(404).send({Error: 'Not found'}))
}) })
app.get('/api/customers', (req, res, next) => { app.get('/api/customers', (req, res, next) => {
return customers.batch() return customers.batch()
.then(r => res.send({customers: r})) .then(r => res.send({customers: r}))
.catch(next) .catch(next)
}) })
app.get('/api/customer/:id', (req, res, next) => { app.get('/api/customer/:id', (req, res, next) => {
return customers.getById(req.params.id) return customers.getById(req.params.id)
.then(r => { .then(r => {
if (!r) return res.status(404).send({Error: 'Not found'}) if (!r) return res.status(404).send({Error: 'Not found'})
return res.send(r) return res.send(r)
}) })
}) })
app.get('/api/logs/:deviceId', (req, res, next) => { app.get('/api/logs/:deviceId', (req, res, next) => {
return logs.getMachineLogs(req.params.deviceId) return logs.getMachineLogs(req.params.deviceId)
.then(r => res.send(r)) .then(r => res.send(r))
.catch(next) .catch(next)
}) })
app.get('/api/logs', (req, res, next) => { app.get('/api/logs', (req, res, next) => {
return machineLoader.getMachines() return machineLoader.getMachines()
.then(machines => { .then(machines => {
const firstMachine = _.first(machines) const firstMachine = _.first(machines)
if (!firstMachine) return res.status(404).send({Error: 'No machines'}) if (!firstMachine) return res.status(404).send({Error: 'No machines'})
return logs.getMachineLogs(firstMachine.deviceId) return logs.getMachineLogs(firstMachine.deviceId)
.then(r => res.send(r)) .then(r => res.send(r))
}) })
.catch(next) .catch(next)
}) })
app.get('/api/support_logs', (req, res, next) => { app.get('/api/support_logs', (req, res, next) => {
return supportLogs.batch() return supportLogs.batch()
.then(supportLogs => res.send({ supportLogs })) .then(supportLogs => res.send({ supportLogs }))
.catch(next) .catch(next)
}) })
app.get('/api/support_logs/logs', (req, res, next) => { app.get('/api/support_logs/logs', (req, res, next) => {
return supportLogs.get(req.query.supportLogId) return supportLogs.get(req.query.supportLogId)
.then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first)) .then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first))
.then(result => { .then(result => {
const log = result || {} const log = result || {}
return logs.getMachineLogs(log.deviceId, log.timestamp) return logs.getMachineLogs(log.deviceId, log.timestamp)
}) })
.then(r => res.send(r)) .then(r => res.send(r))
.catch(next) .catch(next)
}) })
app.post('/api/support_logs', (req, res, next) => { app.post('/api/support_logs', (req, res, next) => {
return supportLogs.insert(req.query.deviceId) return supportLogs.insert(req.query.deviceId)
.then(r => res.send(r)) .then(r => res.send(r))
.catch(next) .catch(next)
}) })
app.patch('/api/customer/:id', (req, res, next) => { app.patch('/api/customer/:id', (req, res, next) => {
if (!req.params.id) return res.status(400).send({Error: 'Requires id'}) if (!req.params.id) return res.status(400).send({Error: 'Requires id'})
const token = req.token || req.cookies.token const token = req.token || req.cookies.token
return customers.update(req.params.id, req.query, token) return customers.update(req.params.id, req.query, token)
.then(r => res.send(r)) .then(r => res.send(r))
.catch(() => res.status(404).send({Error: 'Not found'})) .catch(() => res.status(404).send({Error: 'Not found'}))
}) })
app.use((err, req, res, next) => { app.use((err, req, res, next) => {
@ -253,35 +253,35 @@ function register (req, res, next) {
if (!otp) return next() if (!otp) return next()
return login.register(otp) return login.register(otp)
.then(r => { .then(r => {
if (r.expired) return res.status(401).send('OTP expired, generate new registration link') if (r.expired) return res.status(401).send('OTP expired, generate new registration link')
// Maybe user is using old registration key, attempt to authenticate // Maybe user is using old registration key, attempt to authenticate
if (!r.success) return next() if (!r.success) return next()
const cookieOpts = { const cookieOpts = {
httpOnly: true, httpOnly: true,
secure: true, secure: true,
domain: hostname, domain: hostname,
sameSite: true, sameSite: true,
expires: NEVER expires: NEVER
} }
const token = r.token const token = r.token
req.token = token req.token = token
res.cookie('token', token, cookieOpts) res.cookie('token', token, cookieOpts)
next() next()
}) })
} }
function authenticate (req, res, next) { function authenticate (req, res, next) {
const token = req.token || req.cookies.token const token = req.token || req.cookies.token
return login.authenticate(token) return login.authenticate(token)
.then(success => { .then(success => {
if (!success) return res.status(401).send('Authentication failed') if (!success) return res.status(401).send('Authentication failed')
next() next()
}) })
} }
process.on('unhandledRejection', err => { process.on('unhandledRejection', err => {
@ -303,27 +303,27 @@ const wss = new WebSocket.Server({server: webServer})
function establishSocket (ws, token) { function establishSocket (ws, token) {
return login.authenticate(token) return login.authenticate(token)
.then(success => { .then(success => {
if (!success) return ws.close(1008, 'Authentication error') if (!success) return ws.close(1008, 'Authentication error')
const listener = data => { const listener = data => {
ws.send(JSON.stringify(data)) ws.send(JSON.stringify(data))
} }
// Reauthenticate every once in a while, in case token expired // Reauthenticate every once in a while, in case token expired
setInterval(() => { setInterval(() => {
return login.authenticate(token) return login.authenticate(token)
.then(success => { .then(success => {
if (!success) { if (!success) {
socketEmitter.removeListener('message', listener) socketEmitter.removeListener('message', listener)
ws.close() ws.close()
} }
}) })
}, REAUTHENTICATE_INTERVAL) }, REAUTHENTICATE_INTERVAL)
socketEmitter.on('message', listener) socketEmitter.on('message', listener)
ws.send('Testing123') ws.send('Testing123')
}) })
} }
wss.on('connection', ws => { wss.on('connection', ws => {

View file

@ -32,25 +32,25 @@ const certOptions = {
app.get('/api/support_logs', (req, res, next) => { app.get('/api/support_logs', (req, res, next) => {
return supportLogs.batch() return supportLogs.batch()
.then(supportLogs => res.send({ supportLogs })) .then(supportLogs => res.send({ supportLogs }))
.catch(next) .catch(next)
}) })
app.get('/api/support_logs/logs', (req, res, next) => { app.get('/api/support_logs/logs', (req, res, next) => {
return supportLogs.get(req.query.supportLogId) return supportLogs.get(req.query.supportLogId)
.then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first)) .then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first))
.then(result => { .then(result => {
const log = result || {} const log = result || {}
return logs.getUnlimitedMachineLogs(log.deviceId, log.timestamp) return logs.getUnlimitedMachineLogs(log.deviceId, log.timestamp)
}) })
.then(r => res.send(r)) .then(r => res.send(r))
.catch(next) .catch(next)
}) })
app.post('/api/support_logs', (req, res, next) => { app.post('/api/support_logs', (req, res, next) => {
return supportLogs.insert(req.query.deviceId) return supportLogs.insert(req.query.deviceId)
.then(r => res.send(r)) .then(r => res.send(r))
.catch(next) .catch(next)
}) })
function run (port) { function run (port) {

View file

@ -19,7 +19,7 @@ function fetchSchema () {
const schemaPath = path.resolve(options.lamassuServerPath, 'lamassu-schema.json') const schemaPath = path.resolve(options.lamassuServerPath, 'lamassu-schema.json')
return fs.readFile(schemaPath) return fs.readFile(schemaPath)
.then(JSON.parse) .then(JSON.parse)
} }
function fetchConfig () { function fetchConfig () {
@ -27,7 +27,7 @@ function fetchConfig () {
order by id desc limit 1` order by id desc limit 1`
return db.oneOrNone(sql, ['config']) return db.oneOrNone(sql, ['config'])
.then(row => row ? row.data.config : []) .then(row => row ? row.data.config : [])
} }
function allScopes (cryptoScopes, machineScopes) { function allScopes (cryptoScopes, machineScopes) {
@ -65,11 +65,11 @@ function getField (schema, group, fieldCode) {
} }
const fetchMachines = () => machineLoader.getMachines() const fetchMachines = () => machineLoader.getMachines()
.then(machineList => machineList.map(r => r.deviceId)) .then(machineList => machineList.map(r => r.deviceId))
function validateCurrentConfig () { function validateCurrentConfig () {
return fetchConfig() return fetchConfig()
.then(configValidate.validateRequires) .then(configValidate.validateRequires)
} }
function decorateEnabledIf (schemaFields, schemaField) { function decorateEnabledIf (schemaFields, schemaField) {
@ -85,43 +85,43 @@ function decorateEnabledIf (schemaFields, schemaField) {
function fetchConfigGroup (code) { function fetchConfigGroup (code) {
const fieldLocatorCodeEq = R.pathEq(['fieldLocator', 'code']) const fieldLocatorCodeEq = R.pathEq(['fieldLocator', 'code'])
return Promise.all([fetchSchema(), fetchData(), fetchConfig(), fetchMachines()]) return Promise.all([fetchSchema(), fetchData(), fetchConfig(), fetchMachines()])
.then(([schema, data, config, machineList]) => { .then(([schema, data, config, machineList]) => {
const groupSchema = schema.groups.find(r => r.code === code) 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 const schemaFields = groupSchema.fields
.map(R.curry(getField)(schema, groupSchema)) .map(R.curry(getField)(schema, groupSchema))
.map(f => _.assign(f, { .map(f => _.assign(f, {
fieldEnabledIfAny: f.enabledIfAny || [], fieldEnabledIfAny: f.enabledIfAny || [],
fieldEnabledIfAll: f.enabledIfAll || [] fieldEnabledIfAll: f.enabledIfAll || []
})) }))
const candidateFields = [ const candidateFields = [
schemaFields.map(R.prop('requiredIf')), schemaFields.map(R.prop('requiredIf')),
schemaFields.map(R.prop('enabledIfAny')), schemaFields.map(R.prop('enabledIfAny')),
schemaFields.map(R.prop('enabledIfAll')), schemaFields.map(R.prop('enabledIfAll')),
groupSchema.fields, groupSchema.fields,
'fiatCurrency' 'fiatCurrency'
] ]
const configFields = R.uniq(R.flatten(candidateFields)).filter(R.identity) const configFields = R.uniq(R.flatten(candidateFields)).filter(R.identity)
const reducer = (acc, configField) => { const reducer = (acc, configField) => {
return acc.concat(config.filter(fieldLocatorCodeEq(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.fields = undefined
groupSchema.entries = schemaFields groupSchema.entries = schemaFields
return { return {
schema: groupSchema, schema: groupSchema,
values, values,
selectedCryptos: getCryptos(config, machineList), selectedCryptos: getCryptos(config, machineList),
data data
} }
}) })
} }
function massageCurrencies (currencies) { function massageCurrencies (currencies) {
@ -154,55 +154,55 @@ const ALL_CRYPTOS = ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']
function fetchData () { function fetchData () {
return machineLoader.getMachineNames() return machineLoader.getMachineNames()
.then(machineList => ({ .then(machineList => ({
currencies: massageCurrencies(currencies), currencies: massageCurrencies(currencies),
cryptoCurrencies: [ cryptoCurrencies: [
{crypto: 'BTC', display: 'Bitcoin'}, {crypto: 'BTC', display: 'Bitcoin'},
{crypto: 'ETH', display: 'Ethereum'}, {crypto: 'ETH', display: 'Ethereum'},
{crypto: 'LTC', display: 'Litecoin'}, {crypto: 'LTC', display: 'Litecoin'},
{crypto: 'DASH', display: 'Dash'}, {crypto: 'DASH', display: 'Dash'},
{crypto: 'ZEC', display: 'Zcash'}, {crypto: 'ZEC', display: 'Zcash'},
{crypto: 'BCH', display: 'BCH'} {crypto: 'BCH', display: 'BCH'}
], ],
languages: languages, languages: languages,
countries, countries,
accounts: [ accounts: [
{code: 'bitpay', display: 'Bitpay', class: 'ticker', cryptos: ['BTC', 'BCH']}, {code: 'bitpay', display: 'Bitpay', class: 'ticker', cryptos: ['BTC', 'BCH']},
{code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', '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: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
{code: 'coinbase', display: 'Coinbase', 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: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS},
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']}, {code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
{code: 'lnd', display: 'Lightning Network', class: 'wallet', cryptos: ['BTC']}, {code: 'lnd', display: 'Lightning Network', class: 'wallet', cryptos: ['BTC']},
{code: 'geth', display: 'geth', class: 'wallet', cryptos: ['ETH']}, {code: 'geth', display: 'geth', class: 'wallet', cryptos: ['ETH']},
{code: 'zcashd', display: 'zcashd', class: 'wallet', cryptos: ['ZEC']}, {code: 'zcashd', display: 'zcashd', class: 'wallet', cryptos: ['ZEC']},
{code: 'litecoind', display: 'litecoind', class: 'wallet', cryptos: ['LTC']}, {code: 'litecoind', display: 'litecoind', class: 'wallet', cryptos: ['LTC']},
{code: 'dashd', display: 'dashd', class: 'wallet', cryptos: ['DASH']}, {code: 'dashd', display: 'dashd', class: 'wallet', cryptos: ['DASH']},
{code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']}, {code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']},
{code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']}, {code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']},
{code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, {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: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
{code: 'mock-wallet', display: 'Mock wallet', class: 'wallet', cryptos: ALL_CRYPTOS}, {code: 'mock-wallet', display: 'Mock wallet', class: 'wallet', cryptos: ALL_CRYPTOS},
{code: 'no-exchange', display: 'No exchange', class: 'exchange', 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-exchange', display: 'Mock exchange', class: 'exchange', cryptos: ALL_CRYPTOS},
{code: 'mock-sms', display: 'Mock SMS', class: 'sms'}, {code: 'mock-sms', display: 'Mock SMS', class: 'sms'},
{code: 'mock-id-verify', display: 'Mock ID verifier', class: 'idVerifier'}, {code: 'mock-id-verify', display: 'Mock ID verifier', class: 'idVerifier'},
{code: 'twilio', display: 'Twilio', class: 'sms'}, {code: 'twilio', display: 'Twilio', class: 'sms'},
{code: 'mailjet', display: 'Mailjet', class: 'email'}, {code: 'mailjet', display: 'Mailjet', class: 'email'},
{code: 'all-zero-conf', display: 'Always 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']}, {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: 'no-zero-conf', display: 'Always 1-conf', class: 'zeroConf', cryptos: ALL_CRYPTOS},
{code: 'blockcypher', display: 'Blockcypher', class: 'zeroConf', cryptos: ['BTC']}, {code: 'blockcypher', display: 'Blockcypher', class: 'zeroConf', cryptos: ['BTC']},
{code: 'mock-zero-conf', display: 'Mock 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']} {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})) machines: machineList.map(machine => ({machine: machine.deviceId, display: machine.name}))
})) }))
} }
function saveConfigGroup (results) { function saveConfigGroup (results) {
if (results.values.length === 0) return fetchConfigGroup(results.groupCode) if (results.values.length === 0) return fetchConfigGroup(results.groupCode)
return settingsLoader.modifyConfig(results.values) return settingsLoader.modifyConfig(results.values)
.then(() => fetchConfigGroup(results.groupCode)) .then(() => fetchConfigGroup(results.groupCode))
} }
module.exports = { module.exports = {

View file

@ -36,7 +36,7 @@ function getCryptos (config, machineList) {
function fetchMachines () { function fetchMachines () {
return machineLoader.getMachines() return machineLoader.getMachines()
.then(machineList => machineList.map(r => r.deviceId)) .then(machineList => machineList.map(r => r.deviceId))
} }
function computeCrypto (cryptoCode, _balance) { function computeCrypto (cryptoCode, _balance) {
@ -55,45 +55,45 @@ function computeFiat (rate, cryptoCode, _balance) {
function getFunding (_cryptoCode) { function getFunding (_cryptoCode) {
return Promise.all([settingsLoader.loadLatest(), fetchMachines()]) return Promise.all([settingsLoader.loadLatest(), fetchMachines()])
.then(([settings, machineList]) => { .then(([settings, machineList]) => {
const config = configManager.unscoped(settings.config) const config = configManager.unscoped(settings.config)
const cryptoCodes = getCryptos(settings.config, machineList) const cryptoCodes = getCryptos(settings.config, machineList)
const cryptoCode = _cryptoCode || cryptoCodes[0] const cryptoCode = _cryptoCode || cryptoCodes[0]
const fiatCode = config.fiatCurrency const fiatCode = config.fiatCurrency
const pareCoins = c => _.includes(c.cryptoCode, cryptoCodes) const pareCoins = c => _.includes(c.cryptoCode, cryptoCodes)
const cryptoCurrencies = coinUtils.cryptoCurrencies() const cryptoCurrencies = coinUtils.cryptoCurrencies()
const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies) const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies)
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
if (!cryptoRec) throw new Error(`Unsupported coin: ${cryptoCode}`) if (!cryptoRec) throw new Error(`Unsupported coin: ${cryptoCode}`)
const promises = [ const promises = [
wallet.newFunding(settings, cryptoCode), wallet.newFunding(settings, cryptoCode),
ticker.getRates(settings, fiatCode, cryptoCode) ticker.getRates(settings, fiatCode, cryptoCode)
] ]
return Promise.all(promises) return Promise.all(promises)
.then(([fundingRec, ratesRec]) => { .then(([fundingRec, ratesRec]) => {
const rates = ratesRec.rates const rates = ratesRec.rates
const rate = (rates.ask.add(rates.bid)).div(2) const rate = (rates.ask.add(rates.bid)).div(2)
const fundingConfirmedBalance = fundingRec.fundingConfirmedBalance const fundingConfirmedBalance = fundingRec.fundingConfirmedBalance
const fiatConfirmedBalance = computeFiat(rate, cryptoCode, fundingConfirmedBalance) const fiatConfirmedBalance = computeFiat(rate, cryptoCode, fundingConfirmedBalance)
const pending = fundingRec.fundingPendingBalance.sub(fundingConfirmedBalance) const pending = fundingRec.fundingPendingBalance.sub(fundingConfirmedBalance)
const fiatPending = computeFiat(rate, cryptoCode, pending) const fiatPending = computeFiat(rate, cryptoCode, pending)
const fundingAddress = fundingRec.fundingAddress const fundingAddress = fundingRec.fundingAddress
const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress) const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress)
return { return {
cryptoCode, cryptoCode,
cryptoDisplays, cryptoDisplays,
fundingAddress, fundingAddress,
fundingAddressUrl, fundingAddressUrl,
confirmedBalance: computeCrypto(cryptoCode, fundingConfirmedBalance).toFormat(5), confirmedBalance: computeCrypto(cryptoCode, fundingConfirmedBalance).toFormat(5),
pending: computeCrypto(cryptoCode, pending).toFormat(5), pending: computeCrypto(cryptoCode, pending).toFormat(5),
fiatConfirmedBalance: fiatConfirmedBalance.toFormat(2), fiatConfirmedBalance: fiatConfirmedBalance.toFormat(2),
fiatPending: fiatPending.toFormat(2), fiatPending: fiatPending.toFormat(2),
fiatCode fiatCode
} }
})
}) })
})
} }

View file

@ -8,7 +8,7 @@ function generateOTP (name) {
const sql = 'insert into one_time_passes (token, name) values ($1, $2)' const sql = 'insert into one_time_passes (token, name) values ($1, $2)'
return db.none(sql, [otp, name]) return db.none(sql, [otp, name])
.then(() => otp) .then(() => otp)
} }
function validateOTP (otp) { function validateOTP (otp) {
@ -17,22 +17,22 @@ function validateOTP (otp) {
returning name, created < now() - interval '1 hour' as expired` returning name, created < now() - interval '1 hour' as expired`
return db.one(sql, [otp]) return db.one(sql, [otp])
.then(r => ({success: !r.expired, expired: r.expired, name: r.name})) .then(r => ({success: !r.expired, expired: r.expired, name: r.name}))
.catch(() => ({success: false, expired: false})) .catch(() => ({success: false, expired: false}))
} }
function register (otp) { function register (otp) {
return validateOTP(otp) return validateOTP(otp)
.then(r => { .then(r => {
if (!r.success) return r if (!r.success) return r
const token = crypto.randomBytes(32).toString('hex') const token = crypto.randomBytes(32).toString('hex')
const sql = 'insert into user_tokens (token, name) values ($1, $2)' const sql = 'insert into user_tokens (token, name) values ($1, $2)'
return db.none(sql, [token, r.name]) return db.none(sql, [token, r.name])
.then(() => ({success: true, token: token})) .then(() => ({success: true, token: token}))
}) })
.catch(() => ({success: false, expired: false})) .catch(() => ({success: false, expired: false}))
} }
function authenticate (token) { function authenticate (token) {

View file

@ -17,17 +17,17 @@ function totem (hostname, name) {
const caPath = options.caPath const caPath = options.caPath
return readFile(caPath) return readFile(caPath)
.then(data => { .then(data => {
const caHash = crypto.createHash('sha256').update(data).digest() const caHash = crypto.createHash('sha256').update(data).digest()
const token = crypto.randomBytes(32) const token = crypto.randomBytes(32)
const hexToken = token.toString('hex') const hexToken = token.toString('hex')
const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex') const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex')
const buf = Buffer.concat([caHash, token, Buffer.from(hostname)]) const buf = Buffer.concat([caHash, token, Buffer.from(hostname)])
const sql = 'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)' const sql = 'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)'
return db.none(sql, [hexToken, caHexToken, name]) return db.none(sql, [hexToken, caHexToken, name])
.then(() => bsAlpha.encode(buf)) .then(() => bsAlpha.encode(buf))
}) })
} }
module.exports = {totem, unpair} module.exports = {totem, unpair}

View file

@ -11,8 +11,8 @@ const CONSIDERED_UP_SECS = 30
function checkWasConfigured () { function checkWasConfigured () {
return settingsLoader.loadLatest() return settingsLoader.loadLatest()
.then(() => true) .then(() => true)
.catch(() => false) .catch(() => false)
} }
function machinesLastPing () { function machinesLastPing () {
@ -21,28 +21,28 @@ function machinesLastPing () {
group by device_id` group by device_id`
return Promise.all([machineLoader.getMachineNames(), db.any(sql)]) return Promise.all([machineLoader.getMachineNames(), db.any(sql)])
.then(([machines, events]) => { .then(([machines, events]) => {
if (machines.length === 0) return 'No paired machines' if (machines.length === 0) return 'No paired machines'
const addName = event => { const addName = event => {
const machine = _.find(['deviceId', event.deviceId], machines) const machine = _.find(['deviceId', event.deviceId], machines)
if (!machine) return null if (!machine) return null
return _.set('name', machine.name, event) return _.set('name', machine.name, event)
} }
const mapper = _.flow(_.filter(row => row.age > CONSIDERED_UP_SECS), _.map(addName), _.compact) const mapper = _.flow(_.filter(row => row.age > CONSIDERED_UP_SECS), _.map(addName), _.compact)
const downRows = mapper(events) 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) { if (downRows.length === 1) {
const row = downRows[0] const row = downRows[0]
const age = moment.duration(row.age, 'seconds') const age = moment.duration(row.age, 'seconds')
return `${row.name} down for ${age.humanize()}` return `${row.name} down for ${age.humanize()}`
} }
return 'Multiple machines down' return 'Multiple machines down'
}) })
} }
function status () { function status () {
@ -53,32 +53,32 @@ function status () {
limit 1` limit 1`
return Promise.all([checkWasConfigured(), db.oneOrNone(sql, ['ping']), machinesLastPing()]) return Promise.all([checkWasConfigured(), db.oneOrNone(sql, ['ping']), machinesLastPing()])
.then(([wasConfigured, statusRow, machineStatus]) => { .then(([wasConfigured, statusRow, machineStatus]) => {
const age = statusRow && moment.duration(statusRow.age, 'seconds') const age = statusRow && moment.duration(statusRow.age, 'seconds')
const up = statusRow ? statusRow.age < CONSIDERED_UP_SECS : false const up = statusRow ? statusRow.age < CONSIDERED_UP_SECS : false
const lastPing = statusRow && age.humanize() const lastPing = statusRow && age.humanize()
return settingsLoader.loadLatest() return settingsLoader.loadLatest()
.catch(() => null) .catch(() => null)
.then(settings => { .then(settings => {
return getRates(settings) return getRates(settings)
.then(rates => ({wasConfigured, up, lastPing, rates, machineStatus})) .then(rates => ({wasConfigured, up, lastPing, rates, machineStatus}))
})
}) })
})
} }
function getRates (settings) { function getRates (settings) {
if (!settings) return Promise.resolve([]) if (!settings) return Promise.resolve([])
return ticker.getRates(settings, 'USD', 'BTC') return ticker.getRates(settings, 'USD', 'BTC')
.then(ratesRec => { .then(ratesRec => {
return [{ return [{
crypto: 'BTC', crypto: 'BTC',
bid: parseFloat(ratesRec.rates.bid), bid: parseFloat(ratesRec.rates.bid),
ask: parseFloat(ratesRec.rates.ask) ask: parseFloat(ratesRec.rates.ask)
}] }]
}) })
.catch(() => []) .catch(() => [])
} }
module.exports = {status} module.exports = {status}

View file

@ -9,15 +9,15 @@ const NUM_RESULTS = 20
function addNames (txs) { function addNames (txs) {
return machineLoader.getMachineNames() return machineLoader.getMachineNames()
.then(machines => { .then(machines => {
const addName = tx => { const addName = tx => {
const machine = _.find(['deviceId', tx.deviceId], machines) const machine = _.find(['deviceId', tx.deviceId], machines)
const name = machine ? machine.name : 'Unpaired' const name = machine ? machine.name : 'Unpaired'
return _.set('machineName', name, tx) return _.set('machineName', name, tx)
} }
return _.map(addName, txs) return _.map(addName, txs)
}) })
} }
const camelize = _.mapKeys(_.camelCase) const camelize = _.mapKeys(_.camelCase)
@ -36,7 +36,7 @@ function batch () {
order by created desc limit $1` order by created desc limit $1`
return Promise.all([db.any(cashInSql, [cashInTx.PENDING_INTERVAL, NUM_RESULTS]), db.any(cashOutSql, [NUM_RESULTS])]) return Promise.all([db.any(cashInSql, [cashInTx.PENDING_INTERVAL, NUM_RESULTS]), db.any(cashOutSql, [NUM_RESULTS])])
.then(packager) .then(packager)
} }
function single (txId) { function single (txId) {
@ -56,13 +56,13 @@ function single (txId) {
db.oneOrNone(cashInSql, [cashInTx.PENDING_INTERVAL, txId]), db.oneOrNone(cashInSql, [cashInTx.PENDING_INTERVAL, txId]),
db.oneOrNone(cashOutSql, [txId]) db.oneOrNone(cashOutSql, [txId])
]) ])
.then(packager) .then(packager)
.then(_.head) .then(_.head)
} }
function cancel (txId) { function cancel (txId) {
return tx.cancel(txId) return tx.cancel(txId)
.then(() => single(txId)) .then(() => single(txId))
} }
module.exports = {batch, single, cancel} module.exports = {batch, single, cancel}

View file

@ -24,8 +24,8 @@ function run () {
} }
const runner = () => runOnce() const runner = () => runOnce()
.then(() => clearInterval(handler)) .then(() => clearInterval(handler))
.catch(errorHandler) .catch(errorHandler)
const handler = setInterval(runner, 10000) const handler = setInterval(runner, 10000)
return runner() return runner()
@ -33,35 +33,35 @@ function run () {
function runOnce () { function runOnce () {
return settingsLoader.loadLatest() return settingsLoader.loadLatest()
.then(settings => { .then(settings => {
poller.start(settings) poller.start(settings)
const httpsServerOptions = { const httpsServerOptions = {
key: fs.readFileSync(options.keyPath), key: fs.readFileSync(options.keyPath),
cert: fs.readFileSync(options.certPath), cert: fs.readFileSync(options.certPath),
requestCert: true, requestCert: true,
rejectUnauthorized: false rejectUnauthorized: false
} }
const server = devMode const server = devMode
? http.createServer(routes.app) ? http.createServer(routes.app)
: https.createServer(httpsServerOptions, routes.app) : https.createServer(httpsServerOptions, routes.app)
const port = argv.port || 3000 const port = argv.port || 3000
const localPort = 3030 const localPort = 3030
const localServer = http.createServer(routes.localApp) 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, () => { server.listen(port, () => {
console.log('lamassu-server listening on port ' + console.log('lamassu-server listening on port ' +
port + ' ' + (devMode ? '(http)' : '(https)')) port + ' ' + (devMode ? '(http)' : '(https)'))
}) })
localServer.listen(localPort, 'localhost', () => { localServer.listen(localPort, 'localhost', () => {
console.log('lamassu-server listening on local port ' + localPort) console.log('lamassu-server listening on local port ' + localPort)
})
}) })
})
} }
module.exports = {run} module.exports = {run}

View file

@ -13,36 +13,36 @@ module.exports = {run}
function run () { function run () {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
schema.groups.forEach(group => { schema.groups.forEach(group => {
return group.fields.forEach(fieldCode => { return group.fields.forEach(fieldCode => {
const field = schema.fields.find(r => r.code === fieldCode) const field = schema.fields.find(r => r.code === fieldCode)
if (!field) throw new Error('No such field: ' + fieldCode) if (!field) throw new Error('No such field: ' + fieldCode)
if (_.isNil(field.default)) return if (_.isNil(field.default)) return
if (group.machineScope === 'specific') return if (group.machineScope === 'specific') return
const crypto = group.cryptoScope === 'specific' const crypto = group.cryptoScope === 'specific'
? DEFAULT_CRYPTO ? DEFAULT_CRYPTO
: 'global' : 'global'
return newFields.push({ return newFields.push({
fieldLocator: { fieldLocator: {
fieldScope: { fieldScope: {
crypto, crypto,
machine: 'global' machine: 'global'
},
code: fieldCode,
fieldType: field.fieldType,
fieldClass: field.fieldClass
}, },
code: fieldCode, fieldValue: {
fieldType: field.fieldType, fieldType: field.fieldType,
fieldClass: field.fieldClass value: field.default
}, }
fieldValue: { })
fieldType: field.fieldType,
value: field.default
}
}) })
}) })
})
return settingsLoader.save(newFields) return settingsLoader.save(newFields)
}) })
} }

View file

@ -112,6 +112,5 @@ function run () {
}) })
inquirer.prompt(questions) inquirer.prompt(questions)
.then(answers => processCryptos(answers.crypto)) .then(answers => processCryptos(answers.crypto))
} }

View file

@ -17,21 +17,21 @@ function atomic (machineTx, pi) {
const sql2 = 'select * from bills where cash_in_txs_id=$1' const sql2 = 'select * from bills where cash_in_txs_id=$1'
return t.oneOrNone(sql, [machineTx.id]) return t.oneOrNone(sql, [machineTx.id])
.then(row => { .then(row => {
if (row && row.tx_version >= machineTx.txVersion) throw new E.StaleTxError('Stale tx') if (row && row.tx_version >= machineTx.txVersion) throw new E.StaleTxError('Stale tx')
return t.any(sql2, [machineTx.id]) return t.any(sql2, [machineTx.id])
.then(billRows => { .then(billRows => {
const dbTx = cashInLow.toObj(row) const dbTx = cashInLow.toObj(row)
return preProcess(dbTx, machineTx, pi) return preProcess(dbTx, machineTx, pi)
.then(preProcessedTx => cashInLow.upsert(t, dbTx, preProcessedTx)) .then(preProcessedTx => cashInLow.upsert(t, dbTx, preProcessedTx))
.then(r => { .then(r => {
return insertNewBills(t, billRows, machineTx) return insertNewBills(t, billRows, machineTx)
.then(newBills => _.set('newBills', newBills, r)) .then(newBills => _.set('newBills', newBills, r))
}) })
})
}) })
})
} }
transaction.txMode = tmSRD transaction.txMode = tmSRD
@ -48,7 +48,7 @@ function insertNewBills (t, billRows, machineTx) {
const sql = pgp.helpers.insert(dbBills, columns, 'bills') const sql = pgp.helpers.insert(dbBills, columns, 'bills')
return t.none(sql) return t.none(sql)
.then(() => bills) .then(() => bills)
} }
function pullNewBills (billRows, machineTx) { function pullNewBills (billRows, machineTx) {

View file

@ -15,8 +15,8 @@ module.exports = {toObj, upsert, insert, update, massage, isClearToSend}
function convertBigNumFields (obj) { function convertBigNumFields (obj) {
const convert = value => value && value.isBigNumber const convert = value => value && value.isBigNumber
? value.toString() ? value.toString()
: value : value
return _.mapValues(convert, obj) return _.mapValues(convert, obj)
} }
@ -45,18 +45,18 @@ function toObj (row) {
function upsert (t, dbTx, preProcessedTx) { function upsert (t, dbTx, preProcessedTx) {
if (!dbTx) { if (!dbTx) {
return insert(t, preProcessedTx) return insert(t, preProcessedTx)
.then(tx => ({dbTx, tx})) .then(tx => ({dbTx, tx}))
} }
return update(t, dbTx, diff(dbTx, preProcessedTx)) return update(t, dbTx, diff(dbTx, preProcessedTx))
.then(tx => ({dbTx, tx})) .then(tx => ({dbTx, tx}))
} }
function insert (t, tx) { function insert (t, tx) {
const dbTx = massage(tx) const dbTx = massage(tx)
const sql = pgp.helpers.insert(dbTx, null, 'cash_in_txs') + ' returning *' const sql = pgp.helpers.insert(dbTx, null, 'cash_in_txs') + ' returning *'
return t.one(sql) return t.one(sql)
.then(toObj) .then(toObj)
} }
function update (t, tx, changes) { function update (t, tx, changes) {
@ -67,7 +67,7 @@ function update (t, tx, changes) {
pgp.as.format(' where id=$1', [tx.id]) + ' returning *' pgp.as.format(' where id=$1', [tx.id]) + ' returning *'
return t.one(sql) return t.one(sql)
.then(toObj) .then(toObj)
} }
function diff (oldTx, newTx) { function diff (oldTx, newTx) {

View file

@ -16,13 +16,13 @@ module.exports = {post, monitorPending, cancel, PENDING_INTERVAL}
function post (machineTx, pi) { function post (machineTx, pi) {
return db.tx(cashInAtomic.atomic(machineTx, pi)) return db.tx(cashInAtomic.atomic(machineTx, pi))
.then(r => { .then(r => {
const updatedTx = r.tx const updatedTx = r.tx
return postProcess(r, pi) return postProcess(r, pi)
.then(changes => cashInLow.update(db, updatedTx, changes)) .then(changes => cashInLow.update(db, updatedTx, changes))
.then(tx => _.set('bills', machineTx.bills, tx)) .then(tx => _.set('bills', machineTx.bills, tx))
}) })
} }
function registerTrades (pi, newBills) { function registerTrades (pi, newBills) {
@ -41,7 +41,7 @@ function logAction (rec, tx) {
const sql = pgp.helpers.insert(action, null, 'cash_in_actions') const sql = pgp.helpers.insert(action, null, 'cash_in_actions')
return db.none(sql) return db.none(sql)
.then(_.constant(rec)) .then(_.constant(rec))
} }
function logActionById (action, _rec, txId) { function logActionById (action, _rec, txId) {
@ -57,30 +57,30 @@ function postProcess (r, pi) {
if (!cashInLow.isClearToSend(r.dbTx, r.tx)) return Promise.resolve({}) if (!cashInLow.isClearToSend(r.dbTx, r.tx)) return Promise.resolve({})
return pi.sendCoins(r.tx) return pi.sendCoins(r.tx)
.then(txHash => ({ .then(txHash => ({
txHash, txHash,
sendConfirmed: true, sendConfirmed: true,
sendTime: 'now()^', sendTime: 'now()^',
sendPending: false, sendPending: false,
error: null, error: null,
errorCode: null errorCode: null
})) }))
.catch(err => { .catch(err => {
// Important: We don't know what kind of error this is // Important: We don't know what kind of error this is
// so not safe to assume that funds weren't sent. // so not safe to assume that funds weren't sent.
// Therefore, don't set sendPending to false except for // Therefore, don't set sendPending to false except for
// errors (like InsufficientFundsError) that are guaranteed // errors (like InsufficientFundsError) that are guaranteed
// not to send. // not to send.
const sendPending = err.name !== 'InsufficientFundsError' const sendPending = err.name !== 'InsufficientFundsError'
return { return {
sendTime: 'now()^', sendTime: 'now()^',
error: err.message, error: err.message,
errorCode: err.name, errorCode: err.name,
sendPending sendPending
} }
}) })
.then(sendRec => logAction(sendRec, r.tx)) .then(sendRec => logAction(sendRec, r.tx))
} }
function monitorPending (settings) { function monitorPending (settings) {
@ -98,12 +98,12 @@ function monitorPending (settings) {
const pi = plugins(settings, tx.deviceId) const pi = plugins(settings, tx.deviceId)
return post(tx, pi) return post(tx, pi)
.catch(logger.error) .catch(logger.error)
} }
return db.any(sql, [PENDING_INTERVAL, MAX_PENDING]) return db.any(sql, [PENDING_INTERVAL, MAX_PENDING])
.then(rows => pEachSeries(rows, row => processPending(row))) .then(rows => pEachSeries(rows, row => processPending(row)))
.catch(logger.error) .catch(logger.error)
} }
function cancel (txId) { function cancel (txId) {
@ -114,13 +114,13 @@ function cancel (txId) {
} }
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
return pgp.helpers.update(updateRec, null, 'cash_in_txs') + return pgp.helpers.update(updateRec, null, 'cash_in_txs') +
pgp.as.format(' where id=$1', [txId]) pgp.as.format(' where id=$1', [txId])
}) })
.then(sql => db.result(sql, false)) .then(sql => db.result(sql, false))
.then(res => { .then(res => {
if (res.rowCount !== 1) throw new Error('No such tx-id') if (res.rowCount !== 1) throw new Error('No such tx-id')
}) })
.then(() => logActionById('operatorCompleted', {}, txId)) .then(() => logActionById('operatorCompleted', {}, txId))
} }

View file

@ -22,7 +22,7 @@ function logAction (t, action, _rec, tx) {
const sql = pgp.helpers.insert(rec, null, 'cash_out_actions') const sql = pgp.helpers.insert(rec, null, 'cash_out_actions')
return t.none(sql) return t.none(sql)
.then(_.constant(tx)) .then(_.constant(tx))
} }
function logError (t, action, err, tx) { function logError (t, action, err, tx) {

View file

@ -21,14 +21,14 @@ function atomic (tx, pi, fromClient) {
const sql = 'select * from cash_out_txs where id=$1' const sql = 'select * from cash_out_txs where id=$1'
return t.oneOrNone(sql, [tx.id]) return t.oneOrNone(sql, [tx.id])
.then(toObj) .then(toObj)
.then(oldTx => { .then(oldTx => {
const isStale = fromClient && oldTx && (oldTx.txVersion >= tx.txVersion) const isStale = fromClient && oldTx && (oldTx.txVersion >= tx.txVersion)
if (isStale) throw new E.StaleTxError('Stale tx') if (isStale) throw new E.StaleTxError('Stale tx')
return preProcess(t, oldTx, tx, pi) return preProcess(t, oldTx, tx, pi)
.then(preProcessedTx => cashOutLow.upsert(t, oldTx, preProcessedTx)) .then(preProcessedTx => cashOutLow.upsert(t, oldTx, preProcessedTx))
}) })
} }
transaction.txMode = tmSRD transaction.txMode = tmSRD
@ -39,61 +39,61 @@ function atomic (tx, pi, fromClient) {
function preProcess (t, oldTx, newTx, pi) { function preProcess (t, oldTx, newTx, pi) {
if (!oldTx) { if (!oldTx) {
return pi.isHd(newTx) return pi.isHd(newTx)
.then(isHd => nextHd(t, isHd, newTx)) .then(isHd => nextHd(t, isHd, newTx))
.then(newTxHd => { .then(newTxHd => {
return pi.newAddress(newTxHd) return pi.newAddress(newTxHd)
.then(_.set('toAddress', _, newTxHd)) .then(_.set('toAddress', _, newTxHd))
.then(_.unset('isLightning')) .then(_.unset('isLightning'))
}) })
.then(addressedTx => { .then(addressedTx => {
const rec = {to_address: addressedTx.toAddress} const rec = {to_address: addressedTx.toAddress}
return cashOutActions.logAction(t, 'provisionAddress', rec, addressedTx) return cashOutActions.logAction(t, 'provisionAddress', rec, addressedTx)
}) })
.catch(err => { .catch(err => {
return cashOutActions.logError(t, 'provisionAddress', err, newTx) return cashOutActions.logError(t, 'provisionAddress', err, newTx)
.then(() => { throw err }) .then(() => { throw err })
}) })
} }
return Promise.resolve(updateStatus(oldTx, newTx)) return Promise.resolve(updateStatus(oldTx, newTx))
.then(updatedTx => { .then(updatedTx => {
if (updatedTx.status !== oldTx.status) { if (updatedTx.status !== oldTx.status) {
const isZeroConf = pi.isZeroConf(updatedTx) const isZeroConf = pi.isZeroConf(updatedTx)
if (wasJustAuthorized(oldTx, updatedTx, isZeroConf)) pi.sell(updatedTx) if (wasJustAuthorized(oldTx, updatedTx, isZeroConf)) pi.sell(updatedTx)
const rec = { const rec = {
to_address: updatedTx.toAddress, to_address: updatedTx.toAddress,
tx_hash: updatedTx.txHash 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 if (hasError || hasDispenseOccurred) {
const hasDispenseOccurred = !dispenseOccurred(oldTx.bills) && dispenseOccurred(newTx.bills) return cashOutActions.logDispense(t, updatedTx)
.then(updateCassettes(t, updatedTx))
}
if (hasError || hasDispenseOccurred) { if (!oldTx.phone && newTx.phone) {
return cashOutActions.logDispense(t, updatedTx) return cashOutActions.logAction(t, 'addPhone', {}, updatedTx)
.then(updateCassettes(t, updatedTx)) }
}
if (!oldTx.phone && newTx.phone) { if (!oldTx.redeem && newTx.redeem) {
return cashOutActions.logAction(t, 'addPhone', {}, updatedTx) return cashOutActions.logAction(t, 'redeemLater', {}, updatedTx)
} }
if (!oldTx.redeem && newTx.redeem) { return updatedTx
return cashOutActions.logAction(t, 'redeemLater', {}, updatedTx) })
}
return updatedTx
})
} }
function nextHd (t, isHd, tx) { function nextHd (t, isHd, tx) {
if (!isHd) return Promise.resolve(tx) if (!isHd) return Promise.resolve(tx)
return t.one("select nextval('hd_indices_seq') as hd_index") 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) { function updateCassettes (t, tx) {
@ -112,7 +112,7 @@ function updateCassettes (t, tx) {
] ]
return t.one(sql, values) 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) { function wasJustAuthorized (oldTx, newTx, isZeroConf) {
@ -138,12 +138,12 @@ function updateStatus (oldTx, newTx) {
const newStatus = ratchetStatus(oldStatus, newTx.status) const newStatus = ratchetStatus(oldStatus, newTx.status)
const publishedAt = !oldTx.publishedAt && isPublished(newStatus) const publishedAt = !oldTx.publishedAt && isPublished(newStatus)
? 'now()^' ? 'now()^'
: undefined : undefined
const confirmedAt = !oldTx.confirmedAt && isConfirmed(newStatus) const confirmedAt = !oldTx.confirmedAt && isConfirmed(newStatus)
? 'now()^' ? 'now()^'
: undefined : undefined
const updateRec = { const updateRec = {
publishedAt, publishedAt,

View file

@ -12,12 +12,12 @@ const mapValuesWithKey = _.mapValues.convert({cap: false})
function convertBigNumFields (obj) { function convertBigNumFields (obj) {
const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat']) const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat'])
? value.toString() ? value.toString()
: value : value
const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat']) const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat'])
? key + '#' ? key + '#'
: key : key
return _.mapKeys(convertKey, mapValuesWithKey(convert, obj)) return _.mapKeys(convertKey, mapValuesWithKey(convert, obj))
} }
@ -91,5 +91,5 @@ function redeemableTxs (deviceId) {
and (extract(epoch from (now() - greatest(created, confirmed_at))) * 1000) < $4` and (extract(epoch from (now() - greatest(created, confirmed_at))) * 1000) < $4`
return db.any(sql, [deviceId, true, false, REDEEMABLE_AGE]) return db.any(sql, [deviceId, true, false, REDEEMABLE_AGE])
.then(_.map(toObj)) .then(_.map(toObj))
} }

View file

@ -14,11 +14,11 @@ module.exports = {upsert, update, insert}
function upsert (t, oldTx, tx) { function upsert (t, oldTx, tx) {
if (!oldTx) { if (!oldTx) {
return insert(t, tx) return insert(t, tx)
.then(newTx => [oldTx, newTx]) .then(newTx => [oldTx, newTx])
} }
return update(t, tx, diff(oldTx, tx)) return update(t, tx, diff(oldTx, tx))
.then(newTx => [oldTx, newTx]) .then(newTx => [oldTx, newTx])
} }
function insert (t, tx) { function insert (t, tx) {
@ -26,7 +26,7 @@ function insert (t, tx) {
const sql = pgp.helpers.insert(dbTx, null, 'cash_out_txs') + ' returning *' const sql = pgp.helpers.insert(dbTx, null, 'cash_out_txs') + ' returning *'
return t.one(sql) return t.one(sql)
.then(toObj) .then(toObj)
} }
function update (t, tx, changes) { function update (t, tx, changes) {
@ -39,7 +39,7 @@ function update (t, tx, changes) {
const newTx = _.merge(tx, changes) const newTx = _.merge(tx, changes)
return t.none(sql) return t.none(sql)
.then(() => newTx) .then(() => newTx)
} }
function diff (oldTx, newTx) { function diff (oldTx, newTx) {

View file

@ -43,11 +43,11 @@ function selfPost (tx, pi) {
function post (tx, pi, fromClient = true) { function post (tx, pi, fromClient = true) {
return db.tx(cashOutAtomic.atomic(tx, pi, fromClient)) return db.tx(cashOutAtomic.atomic(tx, pi, fromClient))
.then(txVector => { .then(txVector => {
const [, newTx] = txVector const [, newTx] = txVector
return postProcess(txVector, pi) return postProcess(txVector, pi)
.then(changes => cashOutLow.update(db, newTx, changes)) .then(changes => cashOutLow.update(db, newTx, changes))
}) })
} }
function postProcess (txVector, pi) { function postProcess (txVector, pi) {
@ -55,32 +55,32 @@ function postProcess (txVector, pi) {
if ((newTx.dispense && !oldTx.dispense) || (newTx.redeem && !oldTx.redeem)) { if ((newTx.dispense && !oldTx.dispense) || (newTx.redeem && !oldTx.redeem)) {
return pi.buildAvailableCassettes(newTx.id) return pi.buildAvailableCassettes(newTx.id)
.then(cassettes => { .then(cassettes => {
const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat) const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat)
if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE) if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE)
return bills return bills
}) })
.then(bills => { .then(bills => {
const provisioned1 = bills[0].provisioned const provisioned1 = bills[0].provisioned
const provisioned2 = bills[1].provisioned const provisioned2 = bills[1].provisioned
const denomination1 = bills[0].denomination const denomination1 = bills[0].denomination
const denomination2 = bills[1].denomination const denomination2 = bills[1].denomination
const rec = { const rec = {
provisioned_1: provisioned1, provisioned_1: provisioned1,
provisioned_2: provisioned2, provisioned_2: provisioned2,
denomination_1: denomination1, denomination_1: denomination1,
denomination_2: denomination2 denomination_2: denomination2
} }
return cashOutActions.logAction(db, 'provisionNotes', rec, newTx) return cashOutActions.logAction(db, 'provisionNotes', rec, newTx)
.then(_.constant({bills})) .then(_.constant({bills}))
}) })
.catch(err => { .catch(err => {
return cashOutActions.logError(db, 'provisionNotesError', err, newTx) return cashOutActions.logError(db, 'provisionNotesError', err, newTx)
.then(() => { throw err }) .then(() => { throw err })
}) })
} }
return Promise.resolve({}) return Promise.resolve({})
@ -95,31 +95,31 @@ function fetchOpenTxs (statuses, age) {
const statusClause = _.map(pgp.as.text, statuses).join(',') const statusClause = _.map(pgp.as.text, statuses).join(',')
return db.any(sql, [age, statusClause]) return db.any(sql, [age, statusClause])
.then(rows => rows.map(toObj)) .then(rows => rows.map(toObj))
} }
function processTxStatus (tx, settings) { function processTxStatus (tx, settings) {
const pi = plugins(settings, tx.deviceId) const pi = plugins(settings, tx.deviceId)
return pi.getStatus(tx) return pi.getStatus(tx)
.then(res => _.assign(tx, {status: res.status})) .then(res => _.assign(tx, {status: res.status}))
.then(_tx => selfPost(_tx, pi)) .then(_tx => selfPost(_tx, pi))
} }
function monitorLiveIncoming (settings) { function monitorLiveIncoming (settings) {
const statuses = ['notSeen', 'published', 'insufficientFunds'] const statuses = ['notSeen', 'published', 'insufficientFunds']
return fetchOpenTxs(statuses, STALE_LIVE_INCOMING_TX_AGE) return fetchOpenTxs(statuses, STALE_LIVE_INCOMING_TX_AGE)
.then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings))) .then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings)))
.catch(logger.error) .catch(logger.error)
} }
function monitorStaleIncoming (settings) { function monitorStaleIncoming (settings) {
const statuses = ['notSeen', 'published', 'authorized', 'instant', 'rejected', 'insufficientFunds'] const statuses = ['notSeen', 'published', 'authorized', 'instant', 'rejected', 'insufficientFunds']
return fetchOpenTxs(statuses, STALE_INCOMING_TX_AGE) return fetchOpenTxs(statuses, STALE_INCOMING_TX_AGE)
.then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings))) .then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings)))
.catch(logger.error) .catch(logger.error)
} }
function monitorUnnotified (settings) { function monitorUnnotified (settings) {
@ -133,9 +133,9 @@ function monitorUnnotified (settings) {
const notify = tx => plugins(settings, tx.deviceId).notifyConfirmation(tx) const notify = tx => plugins(settings, tx.deviceId).notifyConfirmation(tx)
return db.any(sql, [MAX_NOTIFY_AGE, false, false, true, MIN_NOTIFY_AGE]) return db.any(sql, [MAX_NOTIFY_AGE, false, false, true, MIN_NOTIFY_AGE])
.then(rows => _.map(toObj, rows)) .then(rows => _.map(toObj, rows))
.then(txs => Promise.all(txs.map(notify))) .then(txs => Promise.all(txs.map(notify)))
.catch(logger.error) .catch(logger.error)
} }
function cancel (txId) { function cancel (txId) {
@ -146,13 +146,13 @@ function cancel (txId) {
} }
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
return pgp.helpers.update(updateRec, null, 'cash_out_txs') + return pgp.helpers.update(updateRec, null, 'cash_out_txs') +
pgp.as.format(' where id=$1', [txId]) pgp.as.format(' where id=$1', [txId])
}) })
.then(sql => db.result(sql, false)) .then(sql => db.result(sql, false))
.then(res => { .then(res => {
if (res.rowCount !== 1) throw new Error('No such tx-id') if (res.rowCount !== 1) throw new Error('No such tx-id')
}) })
.then(() => cashOutActions.logActionById(db, 'operatorCompleted', {}, txId)) .then(() => cashOutActions.logActionById(db, 'operatorCompleted', {}, txId))
} }

View file

@ -66,12 +66,12 @@ function satisfiesRequire (config, cryptos, machineList, field, anyFields, allFi
function isScopeEnabled (config, cryptos, machineList, refField, scope) { function isScopeEnabled (config, cryptos, machineList, refField, scope) {
const [cryptoScope, machineScope] = scope const [cryptoScope, machineScope] = scope
const candidateCryptoScopes = cryptoScope === 'global' const candidateCryptoScopes = cryptoScope === 'global'
? allCryptoScopes(cryptos, refField.cryptoScope) ? allCryptoScopes(cryptos, refField.cryptoScope)
: [cryptoScope] : [cryptoScope]
const candidateMachineScopes = machineScope === 'global' const candidateMachineScopes = machineScope === 'global'
? allMachineScopes(machineList, refField.machineScope) ? allMachineScopes(machineList, refField.machineScope)
: [ machineScope ] : [ machineScope ]
const allRefCandidateScopes = allScopes(candidateCryptoScopes, candidateMachineScopes) const allRefCandidateScopes = allScopes(candidateCryptoScopes, candidateMachineScopes)
const getFallbackValue = scope => configManager.scopedValue(scope[0], scope[1], refField.code, config) const getFallbackValue = scope => configManager.scopedValue(scope[0], scope[1], refField.code, config)
@ -108,13 +108,13 @@ function getMachines () {
function fetchMachines () { function fetchMachines () {
return getMachines() return getMachines()
.then(machineList => machineList.map(r => r.device_id)) .then(machineList => machineList.map(r => r.device_id))
} }
function validateFieldParameter (value, validator) { function validateFieldParameter (value, validator) {
switch (validator.code) { switch (validator.code) {
case 'required': case 'required':
return true // We don't validate this here return true // We don't validate this here
case 'min': case 'min':
return value >= validator.min return value >= validator.min
case 'max': case 'max':
@ -128,58 +128,58 @@ function ensureConstraints (config) {
const pickField = fieldCode => schema.fields.find(r => r.code === fieldCode) const pickField = fieldCode => schema.fields.find(r => r.code === fieldCode)
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
config.every(fieldInstance => { config.every(fieldInstance => {
const fieldCode = fieldInstance.fieldLocator.code const fieldCode = fieldInstance.fieldLocator.code
const field = pickField(fieldCode) const field = pickField(fieldCode)
if (!field) { if (!field) {
logger.warn('No such field: %s, %j', fieldCode, fieldInstance.fieldLocator.fieldScope) logger.warn('No such field: %s, %j', fieldCode, fieldInstance.fieldLocator.fieldScope)
return return
} }
const fieldValue = fieldInstance.fieldValue const fieldValue = fieldInstance.fieldValue
const isValid = field.fieldValidation const isValid = field.fieldValidation
.every(validator => validateFieldParameter(fieldValue.value, validator)) .every(validator => validateFieldParameter(fieldValue.value, validator))
if (isValid) return true if (isValid) return true
throw new Error('Invalid config value') throw new Error('Invalid config value')
})
}) })
})
} }
const pp = require('./pp') const pp = require('./pp')
function validateRequires (config) { function validateRequires (config) {
return fetchMachines() return fetchMachines()
.then(machineList => { .then(machineList => {
const cryptos = getCryptos(config, machineList) const cryptos = getCryptos(config, machineList)
return schema.groups.filter(group => { return schema.groups.filter(group => {
return group.fields.some(fieldCode => { return group.fields.some(fieldCode => {
const field = getGroupField(group, 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 refFieldsAny = _.map(_.partial(getField, group), field.enabledIfAny)
const refFieldsAll = _.map(_.partial(getField, group), field.enabledIfAll) const refFieldsAll = _.map(_.partial(getField, group), field.enabledIfAll)
const isInvalid = !satisfiesRequire(config, cryptos, machineList, field, refFieldsAny, refFieldsAll) 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) { function validate (config) {
return Promise.resolve() return Promise.resolve()
.then(() => ensureConstraints(config)) .then(() => ensureConstraints(config))
.then(() => validateRequires(config)) .then(() => validateRequires(config))
.then(arr => { .then(arr => {
if (arr.length === 0) return config if (arr.length === 0) return config
throw new Error('Invalid configuration:' + arr) throw new Error('Invalid configuration:' + arr)
}) })
} }
module.exports = {validate, ensureConstraints, validateRequires} module.exports = {validate, ensureConstraints, validateRequires}

View file

@ -39,8 +39,8 @@ function add (customer) {
function get (phone) { function get (phone) {
const sql = 'select * from customers where phone=$1' const sql = 'select * from customers where phone=$1'
return db.oneOrNone(sql, [phone]) return db.oneOrNone(sql, [phone])
.then(populateDailyVolume) .then(populateDailyVolume)
.then(camelize) .then(camelize)
} }
/** /**
@ -64,11 +64,11 @@ function update (id, data, userToken) {
' where id=$1 returning *' ' where id=$1 returning *'
return db.one(sql, [id]) return db.one(sql, [id])
.then(addComplianceOverrides(id, updateData, userToken)) .then(addComplianceOverrides(id, updateData, userToken))
.then(populateOverrideUsernames) .then(populateOverrideUsernames)
.then(computeStatus) .then(computeStatus)
.then(populateDailyVolume) .then(populateDailyVolume)
.then(camelize) .then(camelize)
} }
/** /**
@ -85,10 +85,10 @@ function update (id, data, userToken) {
function getById (id, userToken) { function getById (id, userToken) {
const sql = 'select * from customers where id=$1' const sql = 'select * from customers where id=$1'
return db.oneOrNone(sql, [id]) return db.oneOrNone(sql, [id])
.then(populateOverrideUsernames) .then(populateOverrideUsernames)
.then(computeStatus) .then(computeStatus)
.then(populateDailyVolume) .then(populateDailyVolume)
.then(camelize) .then(camelize)
} }
/** /**
@ -192,7 +192,7 @@ function enhanceAtFields (fields) {
*/ */
function enhanceOverrideFields (fields, userToken) { function enhanceOverrideFields (fields, userToken) {
if (!userToken) return fields 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 _.reduce(_.assign, fields, _.map((type) => {
return (fields[type + '_override']) return (fields[type + '_override'])
? { ? {
@ -232,7 +232,7 @@ function addComplianceOverrides (id, customer, userToken) {
// Save all the updated override fields // Save all the updated override fields
return Promise.all(_.map(complianceOverrides.add, _.compact(overrides))) return Promise.all(_.map(complianceOverrides.add, _.compact(overrides)))
.then(() => customer) .then(() => customer)
} }
/** /**
@ -295,15 +295,15 @@ function populateOverrideUsernames (customer) {
const queryTokens = _.map('token', fieldsToUpdate) const queryTokens = _.map('token', fieldsToUpdate)
return users.getByIds(queryTokens) return users.getByIds(queryTokens)
.then(usersList => { .then(usersList => {
return _.map(userField => { return _.map(userField => {
const user = _.find({token: userField.token}, usersList) const user = _.find({token: userField.token}, usersList)
return { return {
[userField.field]: user ? user.name : null [userField.field]: user ? user.name : null
} }
}, fieldsToUpdate) }, fieldsToUpdate)
}) })
.then(_.reduce(_.assign, customer)) .then(_.reduce(_.assign, customer))
} }
/** /**
@ -320,12 +320,12 @@ function batch () {
where id != $1 where id != $1
order by created desc limit $2` order by created desc limit $2`
return db.any(sql, [ anonymous.uuid, NUM_RESULTS ]) return db.any(sql, [ anonymous.uuid, NUM_RESULTS ])
.then(customers => Promise.all(_.map(customer => { .then(customers => Promise.all(_.map(customer => {
return populateOverrideUsernames(customer) return populateOverrideUsernames(customer)
.then(computeStatus) .then(computeStatus)
.then(populateDailyVolume) .then(populateDailyVolume)
.then(camelize) .then(camelize)
}, customers))) }, customers)))
} }
module.exports = { add, get, batch, getById, update } module.exports = { add, get, batch, getById, update }

View file

@ -3,13 +3,13 @@ const ph = require('./plugin-helper')
function sendMessage (settings, rec) { function sendMessage (settings, rec) {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
const pluginCode = configManager.unscoped(settings.config).email const pluginCode = configManager.unscoped(settings.config).email
const plugin = ph.load(ph.EMAIL, pluginCode) const plugin = ph.load(ph.EMAIL, pluginCode)
const account = settings.accounts[pluginCode] const account = settings.accounts[pluginCode]
return plugin.sendMessage(account, rec) return plugin.sendMessage(account, rec)
}) })
} }
module.exports = {sendMessage} module.exports = {sendMessage}

View file

@ -9,24 +9,24 @@ function lookupExchange (settings, cryptoCode) {
function fetchExchange (settings, cryptoCode) { function fetchExchange (settings, cryptoCode) {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
const plugin = lookupExchange(settings, cryptoCode) const plugin = lookupExchange(settings, cryptoCode)
if (!plugin) throw new Error('No exchange set') if (!plugin) throw new Error('No exchange set')
const exchange = ph.load(ph.EXCHANGE, plugin) const exchange = ph.load(ph.EXCHANGE, plugin)
const account = settings.accounts[plugin] const account = settings.accounts[plugin]
return {exchange, account} return {exchange, account}
}) })
} }
function buy (settings, cryptoAtoms, fiatCode, cryptoCode) { function buy (settings, cryptoAtoms, fiatCode, cryptoCode) {
return fetchExchange(settings, 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) { function sell (settings, cryptoAtoms, fiatCode, cryptoCode) {
return fetchExchange(settings, 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) { function active (settings, cryptoCode) {

View file

@ -22,7 +22,7 @@ function getLastSeen (deviceId) {
where device_id=$1 where device_id=$1
order by timestamp desc, serial desc limit 1` order by timestamp desc, serial desc limit 1`
return db.oneOrNone(sql, [deviceId]) 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) { function update (deviceId, logLines) {
const cs = new pgp.helpers.ColumnSet([ const cs = new pgp.helpers.ColumnSet([
'id', 'device_id', 'log_level', 'timestamp', 'serial', 'message'], 'id', 'device_id', 'log_level', 'timestamp', 'serial', 'message'],
{table: 'logs'}) {table: 'logs'})
const logs = _.map(log => { const logs = _.map(log => {
const formatted = { const formatted = {
@ -65,10 +65,10 @@ function getUnlimitedMachineLogs (deviceId, until = new Date().toISOString()) {
order by timestamp asc, serial asc` order by timestamp asc, serial asc`
return Promise.all([db.any(sql, [ deviceId, until ]), getMachineName(deviceId)]) return Promise.all([db.any(sql, [ deviceId, until ]), getMachineName(deviceId)])
.then(([logs, machineName]) => ({ .then(([logs, machineName]) => ({
logs: _.map(_.mapKeys(_.camelCase), logs), logs: _.map(_.mapKeys(_.camelCase), logs),
currentMachine: {deviceId, name: machineName} currentMachine: {deviceId, name: machineName}
})) }))
} }
function getMachineLogs (deviceId, until = new Date().toISOString()) { function getMachineLogs (deviceId, until = new Date().toISOString()) {
@ -79,10 +79,10 @@ function getMachineLogs (deviceId, until = new Date().toISOString()) {
limit $2` limit $2`
return Promise.all([db.any(sql, [ deviceId, NUM_RESULTS, until ]), getMachineName(deviceId)]) return Promise.all([db.any(sql, [ deviceId, NUM_RESULTS, until ]), getMachineName(deviceId)])
.then(([logs, machineName]) => ({ .then(([logs, machineName]) => ({
logs: _.map(_.mapKeys(_.camelCase), logs), logs: _.map(_.mapKeys(_.camelCase), logs),
currentMachine: {deviceId, name: machineName} currentMachine: {deviceId, name: machineName}
})) }))
} }
module.exports = { getUnlimitedMachineLogs, getMachineLogs, update, getLastSeen } module.exports = { getUnlimitedMachineLogs, getMachineLogs, update, getLastSeen }

View file

@ -10,13 +10,13 @@ module.exports = {getMachineName, getMachines, getMachineNames, setMachine}
function getMachines () { function getMachines () {
return db.any('select * from devices where display=TRUE order by created') return db.any('select * from devices where display=TRUE order by created')
.then(rr => rr.map(r => ({ .then(rr => rr.map(r => ({
deviceId: r.device_id, deviceId: r.device_id,
cashbox: r.cashbox, cashbox: r.cashbox,
cassette1: r.cassette1, cassette1: r.cassette1,
cassette2: r.cassette2, cassette2: r.cassette2,
paired: r.paired paired: r.paired
}))) })))
} }
function getConfig (defaultConfig) { function getConfig (defaultConfig) {
@ -27,17 +27,17 @@ function getConfig (defaultConfig) {
function getMachineNames (config) { function getMachineNames (config) {
return Promise.all([getMachines(), getConfig(config)]) return Promise.all([getMachines(), getConfig(config)])
.then(([machines, config]) => { .then(([machines, config]) => {
const addName = r => { const addName = r => {
const machineScoped = configManager.machineScoped(r.deviceId, config) const machineScoped = configManager.machineScoped(r.deviceId, config)
const name = machineScoped.machineName const name = machineScoped.machineName
const cashOut = machineScoped.cashOutEnabled 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) { function getMachineName (machineId) {
return settingsLoader.loadRecentConfig() return settingsLoader.loadRecentConfig()
.then(config => { .then(config => {
const machineScoped = configManager.machineScoped(machineId, config) const machineScoped = configManager.machineScoped(machineId, config)
return machineScoped.machineName return machineScoped.machineName
}) })
} }
function resetCashOutBills (rec) { function resetCashOutBills (rec) {

View file

@ -16,4 +16,3 @@ function run () {
}) })
}) })
} }

View file

@ -60,46 +60,46 @@ function checkNotification (plugins) {
if (!plugins.notificationsEnabled()) return Promise.resolve() if (!plugins.notificationsEnabled()) return Promise.resolve()
return checkStatus(plugins) return checkStatus(plugins)
.then(alertRec => { .then(alertRec => {
const currentAlertFingerprint = buildAlertFingerprint(alertRec) const currentAlertFingerprint = buildAlertFingerprint(alertRec)
if (!currentAlertFingerprint) { if (!currentAlertFingerprint) {
const inAlert = !!alertFingerprint const inAlert = !!alertFingerprint
alertFingerprint = null alertFingerprint = null
lastAlertTime = null lastAlertTime = null
if (inAlert) return sendNoAlerts(plugins) 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)
} }
}
alertFingerprint = currentAlertFingerprint
lastAlertTime = Date.now()
return plugins.sendMessage(rec) const alertChanged = currentAlertFingerprint === alertFingerprint &&
}) lastAlertTime - Date.now() < ALERT_SEND_INTERVAL
.then(results => { if (alertChanged) return
if (results && results.length > 0) logger.debug('Successfully sent alerts')
}) const subject = alertSubject(alertRec)
.catch(logger.error) 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) const getDeviceTime = _.flow(_.get('device_time'), Date.parse)
function dropRepeatsWith (comparator, arr) { function dropRepeatsWith (comparator, arr) {
const iteratee = (acc, val) => val === acc.last const iteratee = (acc, val) => val === acc.last
? acc ? acc
: {arr: _.concat(acc.arr, val), last: val} : {arr: _.concat(acc.arr, val), last: val}
return _.reduce(iteratee, {arr: []}, arr).arr return _.reduce(iteratee, {arr: []}, arr).arr
} }
@ -135,11 +135,11 @@ function checkPing (deviceId) {
limit 1` limit 1`
return db.oneOrNone(sql, [deviceId]) return db.oneOrNone(sql, [deviceId])
.then(row => { .then(row => {
if (!row) return [{code: PING}] if (!row) return [{code: PING}]
if (row.age > NETWORK_DOWN_TIME) return [{code: PING, age: row.age}] if (row.age > NETWORK_DOWN_TIME) return [{code: PING, age: row.age}]
return [] return []
}) })
} }
function checkPings (devices) { function checkPings (devices) {
@ -147,37 +147,37 @@ function checkPings (devices) {
const promises = _.map(checkPing, deviceIds) const promises = _.map(checkPing, deviceIds)
return Promise.all(promises) return Promise.all(promises)
.then(_.zipObject(deviceIds)) .then(_.zipObject(deviceIds))
} }
function checkStatus (plugins) { function checkStatus (plugins) {
const alerts = {devices: {}, deviceNames: {}} const alerts = {devices: {}, deviceNames: {}}
return Promise.all([plugins.checkBalances(), dbm.machineEvents(), plugins.getMachineNames()]) return Promise.all([plugins.checkBalances(), dbm.machineEvents(), plugins.getMachineNames()])
.then(([balances, events, devices]) => { .then(([balances, events, devices]) => {
return checkPings(devices) return checkPings(devices)
.then(pings => { .then(pings => {
alerts.general = _.filter(r => !r.deviceId, balances) alerts.general = _.filter(r => !r.deviceId, balances)
devices.forEach(function (device) { devices.forEach(function (device) {
const deviceId = device.deviceId const deviceId = device.deviceId
const deviceName = device.name const deviceName = device.name
const deviceEvents = events.filter(function (eventRow) { const deviceEvents = events.filter(function (eventRow) {
return eventRow.device_id === deviceId 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) { function formatCurrency (num, code) {

View file

@ -38,43 +38,43 @@ function removeDeviceConfig (deviceId) {
function unpair (deviceId) { function unpair (deviceId) {
const sql = 'delete from devices where device_id=$1' const sql = 'delete from devices where device_id=$1'
return db.none(sql, [deviceId]) return db.none(sql, [deviceId])
.then(() => removeDeviceConfig(deviceId)) .then(() => removeDeviceConfig(deviceId))
} }
function pair (token, deviceId, machineModel) { function pair (token, deviceId, machineModel) {
return pullToken(token) return pullToken(token)
.then(r => { .then(r => {
if (r.expired) return false 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) on conflict (device_id)
do update set paired=TRUE, display=TRUE` do update set paired=TRUE, display=TRUE`
return configureNewDevice(deviceId, r.name, machineModel) return configureNewDevice(deviceId, r.name, machineModel)
.then(() => db.none(insertSql, [deviceId, r.name])) .then(() => db.none(insertSql, [deviceId, r.name]))
.then(() => true) .then(() => true)
}) })
.catch(err => { .catch(err => {
logger.debug(err) logger.debug(err)
return false return false
}) })
} }
function authorizeCaDownload (caToken) { function authorizeCaDownload (caToken) {
return pullToken(caToken) return pullToken(caToken)
.then(r => { .then(r => {
if (r.expired) throw new Error('Expired') if (r.expired) throw new Error('Expired')
const caPath = options.caPath const caPath = options.caPath
return readFile(caPath, {encoding: 'utf8'}) return readFile(caPath, {encoding: 'utf8'})
}) })
} }
function isPaired (deviceId) { function isPaired (deviceId) {
const sql = 'select device_id from devices where device_id=$1 and paired=TRUE' const sql = 'select device_id from devices where device_id=$1 and paired=TRUE'
return db.oneOrNone(sql, [deviceId]) 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} module.exports = {pair, unpair, authorizeCaDownload, isPaired}

View file

@ -44,8 +44,8 @@ function plugins (settings, deviceId) {
const cashInCommission = BN(1).add(BN(cryptoConfig.cashInCommission).div(100)) const cashInCommission = BN(1).add(BN(cryptoConfig.cashInCommission).div(100))
const cashOutCommission = _.isNil(cryptoConfig.cashOutCommission) const cashOutCommission = _.isNil(cryptoConfig.cashOutCommission)
? undefined ? undefined
: BN(1).add(BN(cryptoConfig.cashOutCommission).div(100)) : BN(1).add(BN(cryptoConfig.cashOutCommission).div(100))
if (Date.now() - rateRec.timestamp > STALE_TICKER) return logger.warn('Stale rate for ' + cryptoCode) if (Date.now() - rateRec.timestamp > STALE_TICKER) return logger.warn('Stale rate for ' + cryptoCode)
const rate = rateRec.rates const rate = rateRec.rates
@ -131,34 +131,34 @@ function plugins (settings, deviceId) {
const virtualCassettes = [config.virtualCashOutDenomination] const virtualCassettes = [config.virtualCashOutDenomination]
return Promise.all([dbm.cassetteCounts(deviceId), cashOutHelper.redeemableTxs(deviceId, excludeTxId)]) return Promise.all([dbm.cassetteCounts(deviceId), cashOutHelper.redeemableTxs(deviceId, excludeTxId)])
.then(([rec, _redeemableTxs]) => { .then(([rec, _redeemableTxs]) => {
const redeemableTxs = _.reject(_.matchesProperty('id', excludeTxId), _redeemableTxs) const redeemableTxs = _.reject(_.matchesProperty('id', excludeTxId), _redeemableTxs)
const counts = argv.cassettes const counts = argv.cassettes
? argv.cassettes.split(',') ? argv.cassettes.split(',')
: rec.counts : rec.counts
const cassettes = [ const cassettes = [
{ {
denomination: parseInt(denominations[0], 10), denomination: parseInt(denominations[0], 10),
count: parseInt(counts[0], 10) count: parseInt(counts[0], 10)
}, },
{ {
denomination: parseInt(denominations[1], 10), denomination: parseInt(denominations[1], 10),
count: parseInt(counts[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 () { function fetchCurrentConfigVersion () {
@ -169,7 +169,7 @@ function plugins (settings, deviceId) {
limit 1` limit 1`
return db.one(sql, ['config']) return db.one(sql, ['config'])
.then(row => row.id) .then(row => row.id)
} }
function mapCoinSettings (coinParams) { function mapCoinSettings (coinParams) {
@ -207,23 +207,23 @@ function plugins (settings, deviceId) {
].concat(tickerPromises, balancePromises, testnetPromises) ].concat(tickerPromises, balancePromises, testnetPromises)
return Promise.all(promises) return Promise.all(promises)
.then(arr => { .then(arr => {
const cassettes = arr[0] const cassettes = arr[0]
const configVersion = arr[2] const configVersion = arr[2]
const cryptoCodesCount = cryptoCodes.length const cryptoCodesCount = cryptoCodes.length
const tickers = arr.slice(3, cryptoCodesCount + 3) const tickers = arr.slice(3, cryptoCodesCount + 3)
const balances = arr.slice(cryptoCodesCount + 3, 2 * cryptoCodesCount + 3) const balances = arr.slice(cryptoCodesCount + 3, 2 * cryptoCodesCount + 3)
const testNets = arr.slice(2 * cryptoCodesCount + 3) const testNets = arr.slice(2 * cryptoCodesCount + 3)
const coinParams = _.zip(cryptoCodes, testNets) const coinParams = _.zip(cryptoCodes, testNets)
return { return {
cassettes, cassettes,
rates: buildRates(tickers), rates: buildRates(tickers),
balances: buildBalances(balances), balances: buildBalances(balances),
coins: _.map(mapCoinSettings, coinParams), coins: _.map(mapCoinSettings, coinParams),
configVersion configVersion
} }
}) })
} }
function sendCoins (tx) { function sendCoins (tx) {
@ -275,26 +275,26 @@ function plugins (settings, deviceId) {
ticker.getRates(settings, fiatCode, cryptoCode), ticker.getRates(settings, fiatCode, cryptoCode),
wallet.balance(settings, cryptoCode) wallet.balance(settings, cryptoCode)
]) ])
.then(([rates, balanceRec]) => { .then(([rates, balanceRec]) => {
if (!rates || !balanceRec) return null if (!rates || !balanceRec) return null
const rawRate = rates.rates.ask const rawRate = rates.rates.ask
const cashInCommission = BN(1).minus(BN(config.cashInCommission).div(100)) const cashInCommission = BN(1).minus(BN(config.cashInCommission).div(100))
const balance = balanceRec.balance 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 cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
const unitScale = cryptoRec.unitScale const unitScale = cryptoRec.unitScale
const shiftedRate = rate.shift(-unitScale) const shiftedRate = rate.shift(-unitScale)
const fiatTransferBalance = balance.mul(shiftedRate).div(lowBalanceMargin) 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) { function notifyConfirmation (tx) {
@ -310,17 +310,17 @@ function plugins (settings, deviceId) {
} }
return sms.sendMessage(settings, rec) return sms.sendMessage(settings, rec)
.then(() => { .then(() => {
const sql = 'update cash_out_txs set notified=$1 where id=$2' const sql = 'update cash_out_txs set notified=$1 where id=$2'
const values = [true, tx.id] const values = [true, tx.id]
return db.none(sql, values) return db.none(sql, values)
}) })
} }
function pong () { function pong () {
db.none('insert into server_events (event_type) values ($1)', ['ping']) db.none('insert into server_events (event_type) values ($1)', ['ping'])
.catch(logger.error) .catch(logger.error)
} }
function pongClear () { function pongClear () {
@ -329,7 +329,7 @@ function plugins (settings, deviceId) {
and created < now() - interval $2` and created < now() - interval $2`
db.none(sql, ['ping', PONG_TTL]) 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 t1 = Date.now()
const filtered = marketTradesQueues const filtered = marketTradesQueues
.filter(tradeEntry => { .filter(tradeEntry => {
return t1 - tradeEntry.timestamp < TRADE_TTL return t1 - tradeEntry.timestamp < TRADE_TTL
}) })
const filteredCount = marketTradesQueues.length - filtered.length const filteredCount = marketTradesQueues.length - filtered.length
@ -389,7 +389,7 @@ function plugins (settings, deviceId) {
if (filtered.length === 0) return null if (filtered.length === 0) return null
const cryptoAtoms = filtered 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) 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 () { function executeTrades () {
return machineLoader.getMachines() return machineLoader.getMachines()
.then(devices => { .then(devices => {
const deviceIds = devices.map(device => device.deviceId) const deviceIds = devices.map(device => device.deviceId)
const lists = deviceIds.map(deviceId => { const lists = deviceIds.map(deviceId => {
const config = configManager.machineScoped(deviceId, settings.config) const config = configManager.machineScoped(deviceId, settings.config)
const fiatCode = config.fiatCurrency const fiatCode = config.fiatCurrency
const cryptoCodes = config.cryptoCurrencies 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)
}) })
.catch(logger.error)
const tradesPromises = _.uniq(_.flatten(lists))
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode))
return Promise.all(tradesPromises)
})
.catch(logger.error)
} }
function executeTradesForMarket (settings, fiatCode, cryptoCode) { function executeTradesForMarket (settings, fiatCode, cryptoCode) {
@ -435,11 +435,11 @@ function plugins (settings, deviceId) {
if (tradeEntry === null || tradeEntry.cryptoAtoms.eq(0)) return if (tradeEntry === null || tradeEntry.cryptoAtoms.eq(0)) return
return executeTradeForType(tradeEntry) return executeTradeForType(tradeEntry)
.catch(err => { .catch(err => {
tradesQueues[market].push(tradeEntry) tradesQueues[market].push(tradeEntry)
if (err.name === 'orderTooSmall') return logger.debug(err.message) if (err.name === 'orderTooSmall') return logger.debug(err.message)
logger.error(err) logger.error(err)
}) })
} }
function executeTradeForType (_tradeEntry) { function executeTradeForType (_tradeEntry) {
@ -452,17 +452,17 @@ function plugins (settings, deviceId) {
const execute = tradeEntry.type === 'buy' ? exchange.buy : exchange.sell const execute = tradeEntry.type === 'buy' ? exchange.buy : exchange.sell
return execute(settings, tradeEntry.cryptoAtoms, tradeEntry.fiatCode, tradeEntry.cryptoCode) return execute(settings, tradeEntry.cryptoAtoms, tradeEntry.fiatCode, tradeEntry.cryptoCode)
.then(() => recordTrade(tradeEntry)) .then(() => recordTrade(tradeEntry))
} }
function convertBigNumFields (obj) { function convertBigNumFields (obj) {
const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat']) const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat'])
? value.toString() ? value.toString()
: value : value
const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat']) const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat'])
? key + '#' ? key + '#'
: key : key
return _.mapKeys(convertKey, mapValuesWithKey(convert, obj)) return _.mapKeys(convertKey, mapValuesWithKey(convert, obj))
} }
@ -501,18 +501,28 @@ function plugins (settings, deviceId) {
const cashOutEnabled = config.cashOutEnabled const cashOutEnabled = config.cashOutEnabled
const cashInAlert = device.cashbox > config.cashInAlertThreshold const cashInAlert = device.cashbox > config.cashInAlertThreshold
? {code: 'CASH_BOX_FULL', machineName, deviceId: device.deviceId, notes: device.cashbox} ? {code: 'CASH_BOX_FULL', machineName, deviceId: device.deviceId, notes: device.cashbox}
: null : null
const cassette1Alert = cashOutEnabled && device.cassette1 < config.cashOutCassette1AlertThreshold const cassette1Alert = cashOutEnabled && device.cassette1 < config.cashOutCassette1AlertThreshold
? {code: 'LOW_CASH_OUT', cassette: 1, machineName, deviceId: device.deviceId, ? {code: 'LOW_CASH_OUT',
notes: device.cassette1, denomination: denomination1, fiatCode} cassette: 1,
: null machineName,
deviceId: device.deviceId,
notes: device.cassette1,
denomination: denomination1,
fiatCode}
: null
const cassette2Alert = cashOutEnabled && device.cassette2 < config.cashOutCassette2AlertThreshold const cassette2Alert = cashOutEnabled && device.cassette2 < config.cashOutCassette2AlertThreshold
? {code: 'LOW_CASH_OUT', cassette: 2, machineName, deviceId: device.deviceId, ? {code: 'LOW_CASH_OUT',
notes: device.cassette2, denomination: denomination2, fiatCode} cassette: 2,
: null machineName,
deviceId: device.deviceId,
notes: device.cassette2,
denomination: denomination2,
fiatCode}
: null
return _.compact([cashInAlert, cassette1Alert, cassette2Alert]) return _.compact([cashInAlert, cassette1Alert, cassette2Alert])
} }
@ -530,7 +540,7 @@ function plugins (settings, deviceId) {
const checkCryptoBalanceWithFiat = _.partial(checkCryptoBalance, [fiatCode]) const checkCryptoBalanceWithFiat = _.partial(checkCryptoBalance, [fiatCode])
return Promise.all(fiatBalancePromises(cryptoCodes)) return Promise.all(fiatBalancePromises(cryptoCodes))
.then(balances => _.map(checkCryptoBalanceWithFiat, _.zip(cryptoCodes, balances))) .then(balances => _.map(checkCryptoBalanceWithFiat, _.zip(cryptoCodes, balances)))
} }
function checkCryptoBalance (fiatCode, rec) { function checkCryptoBalance (fiatCode, rec) {
@ -542,8 +552,8 @@ function plugins (settings, deviceId) {
const cryptoAlertThreshold = config.cryptoAlertThreshold const cryptoAlertThreshold = config.cryptoAlertThreshold
return BN(fiatBalance.balance).lt(cryptoAlertThreshold) return BN(fiatBalance.balance).lt(cryptoAlertThreshold)
? {code: 'LOW_CRYPTO_BALANCE', cryptoCode, fiatBalance, fiatCode} ? {code: 'LOW_CRYPTO_BALANCE', cryptoCode, fiatBalance, fiatCode}
: null : null
} }
function checkBalances () { function checkBalances () {
@ -551,13 +561,13 @@ function plugins (settings, deviceId) {
const fiatCode = globalConfig.fiatCurrency const fiatCode = globalConfig.fiatCurrency
return machineLoader.getMachines() return machineLoader.getMachines()
.then(devices => { .then(devices => {
return Promise.all([ return Promise.all([
checkCryptoBalances(fiatCode, devices), checkCryptoBalances(fiatCode, devices),
checkDevicesCashBalances(fiatCode, devices) checkDevicesCashBalances(fiatCode, devices)
]) ])
.then(_.flow(_.flattenDeep, _.compact)) .then(_.flow(_.flattenDeep, _.compact))
}) })
} }
function randomCode () { function randomCode () {
@ -566,8 +576,8 @@ function plugins (settings, deviceId) {
function getPhoneCode (phone) { function getPhoneCode (phone) {
const code = argv.mockSms const code = argv.mockSms
? '123' ? '123'
: randomCode() : randomCode()
const rec = { const rec = {
sms: { sms: {
@ -577,24 +587,24 @@ function plugins (settings, deviceId) {
} }
return sms.sendMessage(settings, rec) return sms.sendMessage(settings, rec)
.then(() => code) .then(() => code)
} }
function sweepHdRow (row) { function sweepHdRow (row) {
const cryptoCode = row.crypto_code const cryptoCode = row.crypto_code
return wallet.sweep(settings, cryptoCode, row.hd_index) return wallet.sweep(settings, cryptoCode, row.hd_index)
.then(txHash => { .then(txHash => {
if (txHash) { if (txHash) {
logger.debug('[%s] Swept address with tx: %s', cryptoCode, 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` where id=$1`
return db.none(sql, row.id) return db.none(sql, row.id)
} }
}) })
.catch(err => logger.error('[%s] Sweep error: %s', cryptoCode, err.message)) .catch(err => logger.error('[%s] Sweep error: %s', cryptoCode, err.message))
} }
function sweepHd () { function sweepHd () {
@ -602,8 +612,8 @@ function plugins (settings, deviceId) {
where hd_index is not null and not swept and status in ('confirmed', 'instant')` where hd_index is not null and not swept and status in ('confirmed', 'instant')`
return db.any(sql) return db.any(sql)
.then(rows => Promise.all(rows.map(sweepHdRow))) .then(rows => Promise.all(rows.map(sweepHdRow)))
.catch(err => logger.error(err)) .catch(err => logger.error(err))
} }
function getMachineNames () { function getMachineNames () {

View file

@ -37,10 +37,10 @@ function authRequest (config, path, data) {
const msg = [nonce, config.clientId, config.key].join('') const msg = [nonce, config.clientId, config.key].join('')
const signature = crypto const signature = crypto
.createHmac('sha256', Buffer.from(config.secret)) .createHmac('sha256', Buffer.from(config.secret))
.update(msg) .update(msg)
.digest('hex') .digest('hex')
.toUpperCase() .toUpperCase()
const signedData = _.merge(data, { const signedData = _.merge(data, {
key: config.key, key: config.key,
@ -76,7 +76,7 @@ function request (path, method, data) {
if (data) options.data = querystring.stringify(data) if (data) options.data = querystring.stringify(data)
return axios(options) return axios(options)
.then(r => r.data) .then(r => r.data)
} }
module.exports = { module.exports = {

View file

@ -18,17 +18,17 @@ function fetch (account, method, params) {
url: `http://localhost:${account.port}`, url: `http://localhost:${account.port}`,
data data
}) })
.then(r => { .then(r => {
if (r.error) throw r.error if (r.error) throw r.error
return r.data.result return r.data.result
}) })
.catch(err => { .catch(err => {
console.log(err.message) console.log(err.message)
try { try {
console.log(err.response.data.error) console.log(err.response.data.error)
} catch (__) {} } catch (__) {}
throw err throw err
}) })
} }
function split (str) { function split (str) {

View file

@ -30,11 +30,11 @@ function trade (type, account, cryptoAtoms, _fiatCode, cryptoCode) {
const options = {amount: cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)} const options = {amount: cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)}
return common.authRequest(account, '/' + type + '/market/' + market, options) return common.authRequest(account, '/' + type + '/market/' + market, options)
.catch(e => { .catch(e => {
if (e.response) handleErrors(e.response.data) if (e.response) handleErrors(e.response.data)
throw e throw e
}) })
.then(handleErrors) .then(handleErrors)
} catch (e) { } catch (e) {
return Promise.reject(e) return Promise.reject(e)
} }

View file

@ -21,10 +21,10 @@ function trade (account, type, cryptoAtoms, fiatCode, cryptoCode) {
const amount = common.toUnit(cryptoAtoms, cryptoCode) const amount = common.toUnit(cryptoAtoms, cryptoCode)
const amountStr = amount.toFixed(6) const amountStr = amount.toFixed(6)
const pair = _.includes(fiatCode, ['USD', 'EUR']) const pair = _.includes(fiatCode, ['USD', 'EUR'])
? PAIRS[cryptoCode][fiatCode] ? PAIRS[cryptoCode][fiatCode]
: PAIRS[cryptoCode]['EUR'] : PAIRS[cryptoCode]['EUR']
var orderInfo = { var orderInfo = {
pair, pair,
type, type,

View file

@ -18,15 +18,15 @@ function sendMessage (account, rec) {
} }
return client.messages.create(opts) return client.messages.create(opts)
.catch(err => { .catch(err => {
if (_.includes(err.code, BAD_NUMBER_CODES)) { if (_.includes(err.code, BAD_NUMBER_CODES)) {
const badNumberError = new Error(err.message) const badNumberError = new Error(err.message)
badNumberError.name = 'BadNumberError' badNumberError.name = 'BadNumberError'
throw badNumberError throw badNumberError
} }
throw new Error(err.message) throw new Error(err.message)
}) })
} }
module.exports = { module.exports = {

View file

@ -2,18 +2,17 @@ const axios = require('axios')
const BN = require('../../../bn') const BN = require('../../../bn')
function ticker (account, fiatCode, cryptoCode) { function ticker (account, fiatCode, cryptoCode) {
return axios.get('https://bitpay.com/api/rates/' + cryptoCode + '/' + fiatCode) return axios.get('https://bitpay.com/api/rates/' + cryptoCode + '/' + fiatCode)
.then(r => { .then(r => {
const data = r.data const data = r.data
const price = BN(data.rate) const price = BN(data.rate)
return { return {
rates: { rates: {
ask: price, ask: price,
bid: price bid: price
}
} }
} })
})
} }
module.exports = { module.exports = {

View file

@ -3,16 +3,16 @@ const common = require('../../common/bitstamp')
function ticker (account, fiatCode, cryptoCode) { function ticker (account, fiatCode, cryptoCode) {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
const market = common.buildMarket(fiatCode, cryptoCode) const market = common.buildMarket(fiatCode, cryptoCode)
return common.request('/ticker/' + market, 'GET') return common.request('/ticker/' + market, 'GET')
}) })
.then(r => ({ .then(r => ({
rates: { rates: {
ask: BN(r.ask), ask: BN(r.ask),
bid: BN(r.bid) bid: BN(r.bid)
} }
})) }))
} }
module.exports = { module.exports = {

View file

@ -11,7 +11,7 @@ function getBuyPrice (obj) {
url: `https://api.coinbase.com/v2/prices/${currencyPair}/buy`, url: `https://api.coinbase.com/v2/prices/${currencyPair}/buy`,
headers: {'CB-Version': '2017-07-10'} headers: {'CB-Version': '2017-07-10'}
}) })
.then(r => r.data) .then(r => r.data)
} }
function getSellPrice (obj) { function getSellPrice (obj) {
@ -22,34 +22,33 @@ function getSellPrice (obj) {
url: `https://api.coinbase.com/v2/prices/${currencyPair}/sell`, url: `https://api.coinbase.com/v2/prices/${currencyPair}/sell`,
headers: {'CB-Version': '2017-07-10'} headers: {'CB-Version': '2017-07-10'}
}) })
.then(r => r.data) .then(r => r.data)
} }
function ticker (account, fiatCode, cryptoCode) { function ticker (account, fiatCode, cryptoCode) {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) { if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) {
throw new Error('Unsupported crypto: ' + cryptoCode) throw new Error('Unsupported crypto: ' + cryptoCode)
} }
}) })
.then(() => { .then(() => {
const currencyPair = `${cryptoCode}-${fiatCode}` const currencyPair = `${cryptoCode}-${fiatCode}`
const promises = [ const promises = [
getBuyPrice({currencyPair}), getBuyPrice({currencyPair}),
getSellPrice({currencyPair}) getSellPrice({currencyPair})
] ]
return Promise.all(promises) return Promise.all(promises)
}) })
.then(([buyPrice, sellPrice]) => ({ .then(([buyPrice, sellPrice]) => ({
rates: { rates: {
ask: BN(buyPrice.data.amount), ask: BN(buyPrice.data.amount),
bid: BN(sellPrice.data.amount) bid: BN(sellPrice.data.amount)
} }
})) }))
} }
module.exports = { module.exports = {
ticker ticker
} }

View file

@ -21,32 +21,32 @@ exports.ticker = function ticker (account, fiatCode, cryptoCode) {
} }
return axios.get('https://bitpay.com/api/rates') return axios.get('https://bitpay.com/api/rates')
.then(response => { .then(response => {
const fxRates = response.data const fxRates = response.data
const usdRate = findCurrency(fxRates, 'USD') const usdRate = findCurrency(fxRates, 'USD')
const fxRate = findCurrency(fxRates, fiatCode).div(usdRate) const fxRate = findCurrency(fxRates, fiatCode).div(usdRate)
return getCurrencyRates('USD', cryptoCode) return getCurrencyRates('USD', cryptoCode)
.then(res => ({ .then(res => ({
rates: { rates: {
ask: res.rates.ask.times(fxRate), ask: res.rates.ask.times(fxRate),
bid: res.rates.bid.times(fxRate) bid: res.rates.bid.times(fxRate)
} }
})) }))
}) })
} }
function getCurrencyRates (fiatCode, cryptoCode) { function getCurrencyRates (fiatCode, cryptoCode) {
const pair = PAIRS[cryptoCode][fiatCode] const pair = PAIRS[cryptoCode][fiatCode]
return axios.get('https://api.kraken.com/0/public/Ticker?pair=' + pair) return axios.get('https://api.kraken.com/0/public/Ticker?pair=' + pair)
.then(function (response) { .then(function (response) {
const rates = response.data.result[pair] const rates = response.data.result[pair]
return { return {
rates: { rates: {
ask: BN(rates.a[0]), ask: BN(rates.a[0]),
bid: BN(rates.b[0]) bid: BN(rates.b[0])
}
} }
} })
})
} }

View file

@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) {
function accountBalance (account, cryptoCode, confirmations) { function accountBalance (account, cryptoCode, confirmations) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => fetch('getbalance', ['', confirmations])) .then(() => fetch('getbalance', ['', confirmations]))
.then(r => BN(r).shift(unitScale).round()) .then(r => BN(r).shift(unitScale).round())
} }
// We want a balance that includes all spends (0 conf) but only deposits that // 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) const btcAddress = bchToBtcAddress(address)
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => fetch('sendtoaddress', [btcAddress, coins])) .then(() => fetch('sendtoaddress', [btcAddress, coins]))
.catch(err => { .catch(err => {
if (err.code === -6) throw new E.InsufficientFundsError() if (err.code === -6) throw new E.InsufficientFundsError()
throw err throw err
}) })
} }
function newAddress (account, info) { function newAddress (account, info) {
return checkCryptoCode(info.cryptoCode) return checkCryptoCode(info.cryptoCode)
.then(() => fetch('getnewaddress')) .then(() => fetch('getnewaddress'))
} }
function addressBalance (address, confs) { function addressBalance (address, confs) {
const btcAddress = bchToBtcAddress(address) const btcAddress = bchToBtcAddress(address)
return fetch('getreceivedbyaddress', [btcAddress, confs]) return fetch('getreceivedbyaddress', [btcAddress, confs])
.then(r => BN(r).shift(unitScale).round()) .then(r => BN(r).shift(unitScale).round())
} }
function confirmedBalance (address, cryptoCode) { function confirmedBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => addressBalance(address, 1)) .then(() => addressBalance(address, 1))
} }
function pendingBalance (address, cryptoCode) { function pendingBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => addressBalance(address, 0)) .then(() => addressBalance(address, 0))
} }
function getStatus (account, toAddress, requested, cryptoCode) { function getStatus (account, toAddress, requested, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => confirmedBalance(toAddress, cryptoCode)) .then(() => confirmedBalance(toAddress, cryptoCode))
.then(confirmed => { .then(confirmed => {
if (confirmed.gte(requested)) return {status: 'confirmed'} if (confirmed.gte(requested)) return {status: 'confirmed'}
return pendingBalance(toAddress, cryptoCode) return pendingBalance(toAddress, cryptoCode)
.then(pending => { .then(pending => {
if (pending.gte(requested)) return {status: 'authorized'} if (pending.gte(requested)) return {status: 'authorized'}
if (pending.gt(0)) return {status: 'insufficientFunds'} if (pending.gt(0)) return {status: 'insufficientFunds'}
return {status: 'notSeen'} return {status: 'notSeen'}
})
}) })
})
} }
function newFunding (account, cryptoCode) { function newFunding (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => { .then(() => {
const promises = [ const promises = [
accountBalance(account, cryptoCode, 0), accountBalance(account, cryptoCode, 0),
accountBalance(account, cryptoCode, 1), accountBalance(account, cryptoCode, 1),
newAddress(account, {cryptoCode}) newAddress(account, {cryptoCode})
] ]
return Promise.all(promises) return Promise.all(promises)
}) })
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
fundingPendingBalance, fundingPendingBalance,
fundingConfirmedBalance, fundingConfirmedBalance,
fundingAddress fundingAddress
})) }))
} }
function cryptoNetwork (account, cryptoCode) { function cryptoNetwork (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main') .then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main')
} }
module.exports = { module.exports = {

View file

@ -26,8 +26,8 @@ function checkCryptoCode (cryptoCode) {
function accountBalance (acount, cryptoCode, confirmations) { function accountBalance (acount, cryptoCode, confirmations) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => fetch('getbalance', ['', confirmations])) .then(() => fetch('getbalance', ['', confirmations]))
.then(r => BN(r).shift(unitScale).round()) .then(r => BN(r).shift(unitScale).round())
} }
// We want a balance that includes all spends (0 conf) but only deposits that // 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) const coins = cryptoAtoms.shift(-unitScale).toFixed(8)
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => fetch('sendtoaddress', [address, coins])) .then(() => fetch('sendtoaddress', [address, coins]))
.catch(err => { .catch(err => {
if (err.code === -6) throw new E.InsufficientFundsError() if (err.code === -6) throw new E.InsufficientFundsError()
throw err throw err
}) })
} }
function newAddress (account, info) { function newAddress (account, info) {
return checkCryptoCode(info.cryptoCode) return checkCryptoCode(info.cryptoCode)
.then(() => fetch('getnewaddress')) .then(() => fetch('getnewaddress'))
} }
function addressBalance (address, confs) { function addressBalance (address, confs) {
return fetch('getreceivedbyaddress', [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) { function confirmedBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => addressBalance(address, 1)) .then(() => addressBalance(address, 1))
} }
function pendingBalance (address, cryptoCode) { function pendingBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => addressBalance(address, 0)) .then(() => addressBalance(address, 0))
} }
function getStatus (account, toAddress, requested, cryptoCode) { function getStatus (account, toAddress, requested, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => confirmedBalance(toAddress, cryptoCode)) .then(() => confirmedBalance(toAddress, cryptoCode))
.then(confirmed => { .then(confirmed => {
if (confirmed.gte(requested)) return {status: 'confirmed'} if (confirmed.gte(requested)) return {status: 'confirmed'}
return pendingBalance(toAddress, cryptoCode) return pendingBalance(toAddress, cryptoCode)
.then(pending => { .then(pending => {
if (pending.gte(requested)) return {status: 'authorized'} if (pending.gte(requested)) return {status: 'authorized'}
if (pending.gt(0)) return {status: 'insufficientFunds'} if (pending.gt(0)) return {status: 'insufficientFunds'}
return {status: 'notSeen'} return {status: 'notSeen'}
})
}) })
})
} }
function newFunding (account, cryptoCode) { function newFunding (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => { .then(() => {
const promises = [ const promises = [
accountBalance(account, cryptoCode, 0), accountBalance(account, cryptoCode, 0),
accountBalance(account, cryptoCode, 1), accountBalance(account, cryptoCode, 1),
newAddress(account, {cryptoCode}) newAddress(account, {cryptoCode})
] ]
return Promise.all(promises) return Promise.all(promises)
}) })
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
fundingPendingBalance, fundingPendingBalance,
fundingConfirmedBalance, fundingConfirmedBalance,
fundingAddress fundingAddress
})) }))
} }
function cryptoNetwork (account, cryptoCode) { function cryptoNetwork (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main') .then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main')
} }
module.exports = { module.exports = {

View file

@ -29,83 +29,83 @@ function checkCryptoCode (cryptoCode) {
function sendCoins (account, address, cryptoAtoms, cryptoCode) { function sendCoins (account, address, cryptoAtoms, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => getWallet(account)) .then(() => getWallet(account))
.then(wallet => { .then(wallet => {
const params = { const params = {
address: address, address: address,
amount: cryptoAtoms.toNumber(), amount: cryptoAtoms.toNumber(),
walletPassphrase: account.walletPassphrase walletPassphrase: account.walletPassphrase
} }
return wallet.sendCoins(params) return wallet.sendCoins(params)
}) })
.then(result => { .then(result => {
return result.hash return result.hash
}) })
.catch(err => { .catch(err => {
if (err.message === 'Insufficient funds') throw new E.InsufficientFundsError() if (err.message === 'Insufficient funds') throw new E.InsufficientFundsError()
throw err throw err
}) })
} }
function balance (account, cryptoCode) { function balance (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => getWallet(account)) .then(() => getWallet(account))
.then(wallet => BN(wallet.wallet.spendableConfirmedBalance)) .then(wallet => BN(wallet.wallet.spendableConfirmedBalance))
} }
function newAddress (account, info) { function newAddress (account, info) {
return checkCryptoCode(info.cryptoCode) return checkCryptoCode(info.cryptoCode)
.then(() => getWallet(account)) .then(() => getWallet(account))
.then(wallet => { .then(wallet => {
return wallet.createAddress() return wallet.createAddress()
.then(result => { .then(result => {
const address = result.address const address = result.address
// If a label was provided, set the label // If a label was provided, set the label
if (info.label) { if (info.label) {
return wallet.setLabel({ address: address, label: info.label }) return wallet.setLabel({ address: address, label: info.label })
.then(() => address) .then(() => address)
} }
return address return address
})
}) })
})
} }
function getStatus (account, toAddress, requested, cryptoCode) { function getStatus (account, toAddress, requested, cryptoCode) {
const bitgo = buildBitgo(account) const bitgo = buildBitgo(account)
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => bitgo.blockchain().getAddress({address: toAddress})) .then(() => bitgo.blockchain().getAddress({address: toAddress}))
.then(rec => { .then(rec => {
if (rec.balance === 0) return {status: 'notSeen'} if (rec.balance === 0) return {status: 'notSeen'}
if (requested.gt(rec.balance)) return {status: 'insufficientFunds'} if (requested.gt(rec.balance)) return {status: 'insufficientFunds'}
if (requested.gt(rec.confirmedBalance)) return {status: 'authorized'} if (requested.gt(rec.confirmedBalance)) return {status: 'authorized'}
return {status: 'confirmed'} return {status: 'confirmed'}
}) })
} }
function newFunding (account, cryptoCode) { function newFunding (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => { .then(() => {
return getWallet(account) return getWallet(account)
.then(wallet => { .then(wallet => {
return wallet.createAddress() return wallet.createAddress()
.then(result => { .then(result => {
const fundingAddress = result.address const fundingAddress = result.address
return wallet.setLabel({address: fundingAddress, label: 'Funding Address'}) return wallet.setLabel({address: fundingAddress, label: 'Funding Address'})
.then(() => ({ .then(() => ({
fundingPendingBalance: BN(wallet.wallet.balance), fundingPendingBalance: BN(wallet.wallet.balance),
fundingConfirmedBalance: BN(wallet.wallet.confirmedBalance), fundingConfirmedBalance: BN(wallet.wallet.confirmedBalance),
fundingAddress fundingAddress
})) }))
}) })
})
}) })
})
} }
function cryptoNetwork (account, cryptoCode) { function cryptoNetwork (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => account.environment === 'test' ? 'test' : 'main') .then(() => account.environment === 'test' ? 'test' : 'main')
} }
module.exports = { module.exports = {

View file

@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) {
function accountBalance (acount, cryptoCode, confirmations) { function accountBalance (acount, cryptoCode, confirmations) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => fetch('getbalance', ['', confirmations])) .then(() => fetch('getbalance', ['', confirmations]))
.then(r => BN(r).shift(unitScale).round()) .then(r => BN(r).shift(unitScale).round())
} }
// We want a balance that includes all spends (0 conf) but only deposits that // 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) const coins = cryptoAtoms.shift(-unitScale).toFixed(8)
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => fetch('sendtoaddress', [address, coins])) .then(() => fetch('sendtoaddress', [address, coins]))
.catch(err => { .catch(err => {
if (err.code === -6) throw new E.InsufficientFundsError() if (err.code === -6) throw new E.InsufficientFundsError()
throw err throw err
}) })
} }
function newAddress (account, info) { function newAddress (account, info) {
return checkCryptoCode(info.cryptoCode) return checkCryptoCode(info.cryptoCode)
.then(() => fetch('getnewaddress')) .then(() => fetch('getnewaddress'))
} }
function addressBalance (address, confs) { function addressBalance (address, confs) {
return fetch('getreceivedbyaddress', [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) { function confirmedBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => addressBalance(address, 1)) .then(() => addressBalance(address, 1))
} }
function pendingBalance (address, cryptoCode) { function pendingBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => addressBalance(address, 0)) .then(() => addressBalance(address, 0))
} }
function getStatus (account, toAddress, requested, cryptoCode) { function getStatus (account, toAddress, requested, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => confirmedBalance(toAddress, cryptoCode)) .then(() => confirmedBalance(toAddress, cryptoCode))
.then(confirmed => { .then(confirmed => {
if (confirmed.gte(requested)) return {status: 'confirmed'} if (confirmed.gte(requested)) return {status: 'confirmed'}
return pendingBalance(toAddress, cryptoCode) return pendingBalance(toAddress, cryptoCode)
.then(pending => { .then(pending => {
if (pending.gte(requested)) return {status: 'authorized'} if (pending.gte(requested)) return {status: 'authorized'}
if (pending.gt(0)) return {status: 'insufficientFunds'} if (pending.gt(0)) return {status: 'insufficientFunds'}
return {status: 'notSeen'} return {status: 'notSeen'}
})
}) })
})
} }
function newFunding (account, cryptoCode) { function newFunding (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => { .then(() => {
const promises = [ const promises = [
accountBalance(account, cryptoCode, 0), accountBalance(account, cryptoCode, 0),
accountBalance(account, cryptoCode, 1), accountBalance(account, cryptoCode, 1),
newAddress(account, {cryptoCode}) newAddress(account, {cryptoCode})
] ]
return Promise.all(promises) return Promise.all(promises)
}) })
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
fundingPendingBalance, fundingPendingBalance,
fundingConfirmedBalance, fundingConfirmedBalance,
fundingAddress fundingAddress
})) }))
} }
module.exports = { module.exports = {

View file

@ -42,7 +42,7 @@ function privateKey (account) {
function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) { function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) {
return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false) return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false)
.then(pify(web3.eth.sendRawTransaction)) .then(pify(web3.eth.sendRawTransaction))
} }
function checkCryptoCode (cryptoCode) { function checkCryptoCode (cryptoCode) {
@ -52,7 +52,7 @@ function checkCryptoCode (cryptoCode) {
function balance (account, cryptoCode) { function balance (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => pendingBalance(defaultAddress(account))) .then(() => pendingBalance(defaultAddress(account)))
} }
const pendingBalance = address => _balance(true, address) const pendingBalance = address => _balance(true, address)
@ -81,31 +81,31 @@ function generateTx (_toAddress, wallet, amount, includesFee) {
] ]
return Promise.all(promises) return Promise.all(promises)
.then(arr => { .then(arr => {
const gas = arr[0] const gas = arr[0]
const gasPrice = arr[1] const gasPrice = arr[1]
const txCount = arr[2] const txCount = arr[2]
const toSend = includesFee const toSend = includesFee
? amount.minus(gasPrice.times(gas)) ? amount.minus(gasPrice.times(gas))
: amount : amount
const rawTx = { const rawTx = {
nonce: txCount, nonce: txCount,
gasPrice: hex(gasPrice), gasPrice: hex(gasPrice),
gasLimit: gas, gasLimit: gas,
to: toAddress, to: toAddress,
from: fromAddress, from: fromAddress,
value: hex(toSend) value: hex(toSend)
} }
const tx = new Tx(rawTx) const tx = new Tx(rawTx)
const privateKey = wallet.getPrivateKey() 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) { function defaultWallet (account) {
@ -121,12 +121,12 @@ function sweep (account, cryptoCode, hdIndex) {
const fromAddress = wallet.getChecksumAddressString() const fromAddress = wallet.getChecksumAddressString()
return confirmedBalance(fromAddress) return confirmedBalance(fromAddress)
.then(r => { .then(r => {
if (r.eq(0)) return if (r.eq(0)) return
return generateTx(defaultAddress(account), wallet, r, true) return generateTx(defaultAddress(account), wallet, r, true)
.then(signedTx => pify(web3.eth.sendRawTransaction)(signedTx)) .then(signedTx => pify(web3.eth.sendRawTransaction)(signedTx))
}) })
} }
function newAddress (account, info) { function newAddress (account, info) {
@ -136,17 +136,17 @@ function newAddress (account, info) {
function getStatus (account, toAddress, cryptoAtoms, cryptoCode) { function getStatus (account, toAddress, cryptoAtoms, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => confirmedBalance(toAddress)) .then(() => confirmedBalance(toAddress))
.then(confirmed => { .then(confirmed => {
if (confirmed.gte(cryptoAtoms)) return {status: 'confirmed'} if (confirmed.gte(cryptoAtoms)) return {status: 'confirmed'}
return pendingBalance(toAddress) return pendingBalance(toAddress)
.then(pending => { .then(pending => {
if (pending.gte(cryptoAtoms)) return {status: 'published'} if (pending.gte(cryptoAtoms)) return {status: 'published'}
if (pending.gt(0)) return {status: 'insufficientFunds'} if (pending.gt(0)) return {status: 'insufficientFunds'}
return {status: 'notSeen'} return {status: 'notSeen'}
})
}) })
})
} }
function paymentHdNode (account) { function paymentHdNode (account) {
@ -165,19 +165,19 @@ function defaultHdNode (account) {
function newFunding (account, cryptoCode) { function newFunding (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => { .then(() => {
const fundingAddress = defaultAddress(account) const fundingAddress = defaultAddress(account)
const promises = [ const promises = [
pendingBalance(fundingAddress), pendingBalance(fundingAddress),
confirmedBalance(fundingAddress) confirmedBalance(fundingAddress)
] ]
return Promise.all(promises) return Promise.all(promises)
.then(([fundingPendingBalance, fundingConfirmedBalance]) => ({ .then(([fundingPendingBalance, fundingConfirmedBalance]) => ({
fundingPendingBalance, fundingPendingBalance,
fundingConfirmedBalance, fundingConfirmedBalance,
fundingAddress fundingAddress
})) }))
}) })
} }

View file

@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) {
function accountBalance (acount, cryptoCode, confirmations) { function accountBalance (acount, cryptoCode, confirmations) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => fetch('getbalance', ['', confirmations])) .then(() => fetch('getbalance', ['', confirmations]))
.then(r => BN(r).shift(unitScale).round()) .then(r => BN(r).shift(unitScale).round())
} }
// We want a balance that includes all spends (0 conf) but only deposits that // 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) const coins = cryptoAtoms.shift(-unitScale).toFixed(8)
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => fetch('sendtoaddress', [address, coins])) .then(() => fetch('sendtoaddress', [address, coins]))
.catch(err => { .catch(err => {
if (err.code === -6) throw new E.InsufficientFundsError() if (err.code === -6) throw new E.InsufficientFundsError()
throw err throw err
}) })
} }
function newAddress (account, info) { function newAddress (account, info) {
return checkCryptoCode(info.cryptoCode) return checkCryptoCode(info.cryptoCode)
.then(() => fetch('getnewaddress')) .then(() => fetch('getnewaddress'))
} }
function addressBalance (address, confs) { function addressBalance (address, confs) {
return fetch('getreceivedbyaddress', [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) { function confirmedBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => addressBalance(address, 1)) .then(() => addressBalance(address, 1))
} }
function pendingBalance (address, cryptoCode) { function pendingBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => addressBalance(address, 0)) .then(() => addressBalance(address, 0))
} }
function getStatus (account, toAddress, requested, cryptoCode) { function getStatus (account, toAddress, requested, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => confirmedBalance(toAddress, cryptoCode)) .then(() => confirmedBalance(toAddress, cryptoCode))
.then(confirmed => { .then(confirmed => {
if (confirmed.gte(requested)) return {status: 'confirmed'} if (confirmed.gte(requested)) return {status: 'confirmed'}
return pendingBalance(toAddress, cryptoCode) return pendingBalance(toAddress, cryptoCode)
.then(pending => { .then(pending => {
if (pending.gte(requested)) return {status: 'authorized'} if (pending.gte(requested)) return {status: 'authorized'}
if (pending.gt(0)) return {status: 'insufficientFunds'} if (pending.gt(0)) return {status: 'insufficientFunds'}
return {status: 'notSeen'} return {status: 'notSeen'}
})
}) })
})
} }
function newFunding (account, cryptoCode) { function newFunding (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => { .then(() => {
const promises = [ const promises = [
accountBalance(account, cryptoCode, 0), accountBalance(account, cryptoCode, 0),
accountBalance(account, cryptoCode, 1), accountBalance(account, cryptoCode, 1),
newAddress(account, {cryptoCode}) newAddress(account, {cryptoCode})
] ]
return Promise.all(promises) return Promise.all(promises)
}) })
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
fundingPendingBalance, fundingPendingBalance,
fundingConfirmedBalance, fundingConfirmedBalance,
fundingAddress fundingAddress
})) }))
} }
module.exports = { module.exports = {

View file

@ -34,11 +34,11 @@ function checkCryptoCode (cryptoCode) {
function balance (acount, cryptoCode) { function balance (acount, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(connect) .then(connect)
.then(c => c.channelBalance({})) .then(c => c.channelBalance({}))
.then(_.get('balance')) .then(_.get('balance'))
.then(BN) .then(BN)
.then(r => r.shift(unitScale).round()) .then(r => r.shift(unitScale).round())
} }
function sendCoins (account, address, cryptoAtoms, cryptoCode) { function sendCoins (account, address, cryptoAtoms, cryptoCode) {
@ -53,37 +53,37 @@ function newFunding (account, cryptoCode) {
function newAddress (account, info) { function newAddress (account, info) {
return checkCryptoCode(info.cryptoCode) return checkCryptoCode(info.cryptoCode)
.then(connect) .then(connect)
.then(c => { .then(c => {
if (info.isLightning) { if (info.isLightning) {
return c.addInvoice({memo: 'Lamassu cryptomat deposit', value: info.cryptoAtoms.toNumber()}) return c.addInvoice({memo: 'Lamassu cryptomat deposit', value: info.cryptoAtoms.toNumber()})
.then(r => `${r.r_hash.toString('hex')}:${r.payment_request}`) .then(r => `${r.r_hash.toString('hex')}:${r.payment_request}`)
} }
return c.newAddress({type: 2}) return c.newAddress({type: 2})
.then(_.get('address')) .then(_.get('address'))
}) })
} }
function getStatus (account, toAddress, requested, cryptoCode) { function getStatus (account, toAddress, requested, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => { .then(() => {
const parts = _.split(':', toAddress) const parts = _.split(':', toAddress)
const isLightning = _.size(parts) === 2 const isLightning = _.size(parts) === 2
const rHashStr = isLightning && _.head(parts) const rHashStr = isLightning && _.head(parts)
return connect() return connect()
.then(c => { .then(c => {
if (isLightning) { if (isLightning) {
return c.lookupInvoice({r_hash_str: rHashStr}) return c.lookupInvoice({r_hash_str: rHashStr})
.then(r => { .then(r => {
if (r.settled) return {status: 'confirmed'} if (r.settled) return {status: 'confirmed'}
return {status: 'notSeen'}
})
}
// Note: this must be handled outside of lnd
return {status: 'notSeen'} return {status: 'notSeen'}
}) })
}
// Note: this must be handled outside of lnd
return {status: 'notSeen'}
}) })
})
} }

View file

@ -19,12 +19,12 @@ function _balance (cryptoCode) {
function balance (account, cryptoCode) { function balance (account, cryptoCode) {
return Promise.resolve() return Promise.resolve()
.then(() => _balance(cryptoCode)) .then(() => _balance(cryptoCode))
} }
function pendingBalance (account, cryptoCode) { function pendingBalance (account, cryptoCode) {
return balance(account, cryptoCode) return balance(account, cryptoCode)
.then(b => b.mul(1.1)) .then(b => b.mul(1.1))
} }
function confirmedBalance (account, cryptoCode) { function confirmedBalance (account, cryptoCode) {
@ -69,11 +69,11 @@ function newFunding (account, cryptoCode) {
] ]
return Promise.all(promises) return Promise.all(promises)
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
fundingPendingBalance, fundingPendingBalance,
fundingConfirmedBalance, fundingConfirmedBalance,
fundingAddress fundingAddress
})) }))
} }
function getStatus (account, toAddress, cryptoAtoms, cryptoCode) { function getStatus (account, toAddress, cryptoAtoms, cryptoCode) {

View file

@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) {
function accountBalance (acount, cryptoCode, confirmations) { function accountBalance (acount, cryptoCode, confirmations) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => fetch('getbalance', ['', confirmations])) .then(() => fetch('getbalance', ['', confirmations]))
.then(r => BN(r).shift(unitScale).round()) .then(r => BN(r).shift(unitScale).round())
} }
// We want a balance that includes all spends (0 conf) but only deposits that // 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) const coins = cryptoAtoms.shift(-unitScale).toFixed(8)
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => fetch('sendtoaddress', [address, coins])) .then(() => fetch('sendtoaddress', [address, coins]))
.catch(err => { .catch(err => {
if (err.code === -6) throw new E.InsufficientFundsError() if (err.code === -6) throw new E.InsufficientFundsError()
throw err throw err
}) })
} }
function newAddress (account, info) { function newAddress (account, info) {
return checkCryptoCode(info.cryptoCode) return checkCryptoCode(info.cryptoCode)
.then(() => fetch('getnewaddress')) .then(() => fetch('getnewaddress'))
} }
function addressBalance (address, confs) { function addressBalance (address, confs) {
return fetch('getreceivedbyaddress', [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) { function confirmedBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => addressBalance(address, 1)) .then(() => addressBalance(address, 1))
} }
function pendingBalance (address, cryptoCode) { function pendingBalance (address, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => addressBalance(address, 0)) .then(() => addressBalance(address, 0))
} }
function getStatus (account, toAddress, requested, cryptoCode) { function getStatus (account, toAddress, requested, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => confirmedBalance(toAddress, cryptoCode)) .then(() => confirmedBalance(toAddress, cryptoCode))
.then(confirmed => { .then(confirmed => {
if (confirmed.gte(requested)) return {status: 'confirmed'} if (confirmed.gte(requested)) return {status: 'confirmed'}
return pendingBalance(toAddress, cryptoCode) return pendingBalance(toAddress, cryptoCode)
.then(pending => { .then(pending => {
if (pending.gte(requested)) return {status: 'authorized'} if (pending.gte(requested)) return {status: 'authorized'}
if (pending.gt(0)) return {status: 'insufficientFunds'} if (pending.gt(0)) return {status: 'insufficientFunds'}
return {status: 'notSeen'} return {status: 'notSeen'}
})
}) })
})
} }
function newFunding (account, cryptoCode) { function newFunding (account, cryptoCode) {
return checkCryptoCode(cryptoCode) return checkCryptoCode(cryptoCode)
.then(() => { .then(() => {
const promises = [ const promises = [
accountBalance(account, cryptoCode, 0), accountBalance(account, cryptoCode, 0),
accountBalance(account, cryptoCode, 1), accountBalance(account, cryptoCode, 1),
newAddress(account, {cryptoCode}) newAddress(account, {cryptoCode})
] ]
return Promise.all(promises) return Promise.all(promises)
}) })
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
fundingPendingBalance, fundingPendingBalance,
fundingConfirmedBalance, fundingConfirmedBalance,
fundingAddress fundingAddress
})) }))
} }
module.exports = { module.exports = {

View file

@ -12,27 +12,26 @@ function highConfidence (confidence, txref) {
function authorize (account, toAddress, cryptoAtoms, cryptoCode) { function authorize (account, toAddress, cryptoAtoms, cryptoCode) {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode) if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode)
const query = qs.stringify({ const query = qs.stringify({
token: account.token, token: account.token,
includeConfidence: true 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)
})
})
} }

View file

@ -2,10 +2,10 @@ module.exports = {authorize}
function authorize (account, toAddress, cryptoAtoms, cryptoCode) { function authorize (account, toAddress, cryptoAtoms, cryptoCode) {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode) if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode)
const isAuthorized = false const isAuthorized = false
return isAuthorized return isAuthorized
}) })
} }

View file

@ -28,10 +28,10 @@ exports.cassetteCounts = function cassetteCounts (deviceId) {
'WHERE device_id=$1' 'WHERE device_id=$1'
return db.one(sql, [deviceId]) return db.one(sql, [deviceId])
.then(row => { .then(row => {
const counts = [row.cassette1, row.cassette2] const counts = [row.cassette1, row.cassette2]
return {counts} return {counts}
}) })
} }
// Note: since we only prune on insert, we'll always have // 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'` and created < now() - interval '2 days'`
return db.none(sql, values) 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 () { exports.machineEvents = function machineEvents () {

View file

@ -55,32 +55,32 @@ function fetchPhoneTx (phone) {
const values = [phone, false, TRANSACTION_EXPIRATION] const values = [phone, false, TRANSACTION_EXPIRATION]
return db.any(sql, values) return db.any(sql, values)
.then(_.map(toCashOutTx)) .then(_.map(toCashOutTx))
.then(txs => { .then(txs => {
const confirmedTxs = txs.filter(tx => R.contains(tx.status, ['instant', 'confirmed'])) const confirmedTxs = txs.filter(tx => R.contains(tx.status, ['instant', 'confirmed']))
if (confirmedTxs.length > 0) { if (confirmedTxs.length > 0) {
const maxTx = R.reduce((acc, val) => { const maxTx = R.reduce((acc, val) => {
return !acc || val.cryptoAtoms.gt(acc.cryptoAtoms) ? val : acc return !acc || val.cryptoAtoms.gt(acc.cryptoAtoms) ? val : acc
}, null, confirmedTxs) }, null, confirmedTxs)
return maxTx return maxTx
} }
if (txs.length > 0) throw httpError('Pending transactions', 412) if (txs.length > 0) throw httpError('Pending transactions', 412)
throw httpError('No transactions', 404) throw httpError('No transactions', 404)
}) })
} }
function fetchStatusTx (txId, status) { function fetchStatusTx (txId, status) {
const sql = 'select * from cash_out_txs where id=$1' const sql = 'select * from cash_out_txs where id=$1'
return db.oneOrNone(sql, [txId]) return db.oneOrNone(sql, [txId])
.then(toCashOutTx) .then(toCashOutTx)
.then(tx => { .then(tx => {
if (!tx) throw httpError('No transaction', 404) if (!tx) throw httpError('No transaction', 404)
if (tx.status === status) throw httpError('Not Modified', 304) if (tx.status === status) throw httpError('Not Modified', 304)
return tx return tx
}) })
} }
function updateDeviceConfigVersion (versionId) { function updateDeviceConfigVersion (versionId) {
@ -105,9 +105,9 @@ function updateMachineDefaults (deviceId) {
}] }]
return settingsLoader.loadLatest() return settingsLoader.loadLatest()
.then(settings => { .then(settings => {
return settingsLoader.save(settingsLoader.mergeValues(settings.config, newFields)) return settingsLoader.save(settingsLoader.mergeValues(settings.config, newFields))
}) })
} }
module.exports = { module.exports = {

View file

@ -41,60 +41,60 @@ function poll (req, res, next) {
pids[deviceId] = {pid, ts: Date.now()} pids[deviceId] = {pid, ts: Date.now()}
return pi.pollQueries(serialNumber, deviceTime, req.query) return pi.pollQueries(serialNumber, deviceTime, req.query)
.then(results => { .then(results => {
const cassettes = results.cassettes const cassettes = results.cassettes
const reboot = pid && reboots[deviceId] && reboots[deviceId] === pid const reboot = pid && reboots[deviceId] && reboots[deviceId] === pid
const langs = config.machineLanguages const langs = config.machineLanguages
const locale = { const locale = {
fiatCode: config.fiatCurrency, fiatCode: config.fiatCurrency,
localeInfo: { localeInfo: {
primaryLocale: langs[0], primaryLocale: langs[0],
primaryLocales: langs, primaryLocales: langs,
country: config.country country: config.country
}
} }
}
const response = { const response = {
error: null, error: null,
locale, locale,
txLimit: config.cashInTransactionLimit, txLimit: config.cashInTransactionLimit,
idVerificationEnabled: config.idVerificationEnabled, idVerificationEnabled: config.idVerificationEnabled,
smsVerificationActive: config.smsVerificationActive, smsVerificationActive: config.smsVerificationActive,
smsVerificationThreshold: config.smsVerificationThreshold, smsVerificationThreshold: config.smsVerificationThreshold,
hardLimitVerificationActive: config.hardLimitVerificationActive, hardLimitVerificationActive: config.hardLimitVerificationActive,
hardLimitVerificationThreshold: config.hardLimitVerificationThreshold, hardLimitVerificationThreshold: config.hardLimitVerificationThreshold,
idCardDataVerificationActive: config.idCardDataVerificationActive, idCardDataVerificationActive: config.idCardDataVerificationActive,
idCardDataVerificationThreshold: config.idCardDataVerificationThreshold, idCardDataVerificationThreshold: config.idCardDataVerificationThreshold,
idCardPhotoVerificationActive: config.idCardPhotoVerificationActive, idCardPhotoVerificationActive: config.idCardPhotoVerificationActive,
idCardPhotoVerificationThreshold: config.idCardPhotoVerificationThreshold, idCardPhotoVerificationThreshold: config.idCardPhotoVerificationThreshold,
sanctionsVerificationActive: config.sanctionsVerificationActive, sanctionsVerificationActive: config.sanctionsVerificationActive,
sanctionsVerificationThreshold: config.sanctionsVerificationThreshold, sanctionsVerificationThreshold: config.sanctionsVerificationThreshold,
crossRefVerificationActive: config.crossRefVerificationActive, crossRefVerificationActive: config.crossRefVerificationActive,
crossRefVerificationThreshold: config.crossRefVerificationThreshold, crossRefVerificationThreshold: config.crossRefVerificationThreshold,
frontCameraVerificationActive: config.frontCameraVerificationActive, frontCameraVerificationActive: config.frontCameraVerificationActive,
frontCameraVerificationThreshold: config.frontCameraVerificationThreshold, frontCameraVerificationThreshold: config.frontCameraVerificationThreshold,
cassettes, cassettes,
twoWayMode: config.cashOutEnabled, twoWayMode: config.cashOutEnabled,
zeroConfLimit: config.zeroConfLimit, zeroConfLimit: config.zeroConfLimit,
reboot reboot
} }
if (response.idVerificationEnabled) { if (response.idVerificationEnabled) {
response.idVerificationLimit = config.idVerificationLimit response.idVerificationLimit = config.idVerificationLimit
} }
return res.json(_.assign(response, results)) return res.json(_.assign(response, results))
}) })
.catch(next) .catch(next)
} }
function getTx (req, res, next) { function getTx (req, res, next) {
if (req.query.status) { if (req.query.status) {
return helpers.fetchStatusTx(req.params.id, req.query.status) return helpers.fetchStatusTx(req.params.id, req.query.status)
.then(r => res.json(r)) .then(r => res.json(r))
.catch(next) .catch(next)
} }
return next(httpError('Not Found', 404)) return next(httpError('Not Found', 404))
@ -103,8 +103,8 @@ function getTx (req, res, next) {
function getPhoneTx (req, res, next) { function getPhoneTx (req, res, next) {
if (req.query.phone) { if (req.query.phone) {
return helpers.fetchPhoneTx(req.query.phone) return helpers.fetchPhoneTx(req.query.phone)
.then(r => res.json(r)) .then(r => res.json(r))
.catch(next) .catch(next)
} }
return next(httpError('Not Found', 404)) return next(httpError('Not Found', 404))
@ -114,99 +114,99 @@ function postTx (req, res, next) {
const pi = plugins(req.settings, req.deviceId) const pi = plugins(req.settings, req.deviceId)
return Tx.post(_.set('deviceId', req.deviceId, req.body), pi) return Tx.post(_.set('deviceId', req.deviceId, req.body), pi)
.then(tx => { .then(tx => {
if (tx.errorCode) { if (tx.errorCode) {
logger.error(tx.error) logger.error(tx.error)
throw httpError(tx.error, 500) throw httpError(tx.error, 500)
} }
return res.json(tx) return res.json(tx)
}) })
.catch(err => { .catch(err => {
if (err instanceof E.StaleTxError) return res.status(409).json({}) if (err instanceof E.StaleTxError) return res.status(409).json({})
if (err instanceof E.RatchetError) return res.status(409).json({}) if (err instanceof E.RatchetError) return res.status(409).json({})
throw err throw err
}) })
.catch(next) .catch(next)
} }
function stateChange (req, res, next) { function stateChange (req, res, next) {
helpers.stateChange(req.deviceId, req.deviceTime, req.body) helpers.stateChange(req.deviceId, req.deviceTime, req.body)
.then(() => respond(req, res)) .then(() => respond(req, res))
.catch(next) .catch(next)
} }
function deviceEvent (req, res, next) { function deviceEvent (req, res, next) {
const pi = plugins(req.settings, req.deviceId) const pi = plugins(req.settings, req.deviceId)
pi.logEvent(req.body) pi.logEvent(req.body)
.then(() => respond(req, res)) .then(() => respond(req, res))
.catch(next) .catch(next)
} }
function verifyUser (req, res, next) { function verifyUser (req, res, next) {
const pi = plugins(req.settings, req.deviceId) const pi = plugins(req.settings, req.deviceId)
pi.verifyUser(req.body) pi.verifyUser(req.body)
.then(idResult => respond(req, res, idResult)) .then(idResult => respond(req, res, idResult))
.catch(next) .catch(next)
} }
function verifyTx (req, res, next) { function verifyTx (req, res, next) {
const pi = plugins(req.settings, req.deviceId) const pi = plugins(req.settings, req.deviceId)
pi.verifyTransaction(req.body) pi.verifyTransaction(req.body)
.then(idResult => respond(req, res, idResult)) .then(idResult => respond(req, res, idResult))
.catch(next) .catch(next)
} }
function getCustomerWithPhoneCode (req, res, next) { function getCustomerWithPhoneCode (req, res, next) {
const pi = plugins(req.settings, req.deviceId) const pi = plugins(req.settings, req.deviceId)
const phone = req.body.phone const phone = req.body.phone
return pi.getPhoneCode(phone) return pi.getPhoneCode(phone)
.then(code => { .then(code => {
return customers.get(phone) return customers.get(phone)
.then(customer => { .then(customer => {
if (customer) return respond(req, res, {code, customer}) if (customer) return respond(req, res, {code, customer})
return customers.add(req.body) return customers.add(req.body)
.then(customer => respond(req, res, {code, customer})) .then(customer => respond(req, res, {code, customer}))
})
}) })
}) .catch(err => {
.catch(err => { if (err.name === 'BadNumberError') throw httpError('Bad number', 401)
if (err.name === 'BadNumberError') throw httpError('Bad number', 401) throw err
throw err })
}) .catch(next)
.catch(next)
} }
function updateCustomer (req, res, next) { function updateCustomer (req, res, next) {
const id = req.params.id const id = req.params.id
const patch = req.body const patch = req.body
customers.getById(id) customers.getById(id)
.then(customer => { .then(customer => {
if (!customer) { throw httpError('Not Found', 404)} if (!customer) { throw httpError('Not Found', 404) }
return customers.update(id, patch) return customers.update(id, patch)
}) })
.then(customer => respond(req, res, {customer})) .then(customer => respond(req, res, {customer}))
.catch(next) .catch(next)
} }
function getLastSeen (req, res, next) { function getLastSeen (req, res, next) {
return logs.getLastSeen(req.deviceId) return logs.getLastSeen(req.deviceId)
.then(r => res.json(r)) .then(r => res.json(r))
.catch(next) .catch(next)
} }
function updateLogs (req, res, next) { function updateLogs (req, res, next) {
return logs.update(req.deviceId, req.body.logs) return logs.update(req.deviceId, req.body.logs)
.then(status => res.json({success: status})) .then(status => res.json({success: status}))
.catch(next) .catch(next)
} }
function ca (req, res) { function ca (req, res) {
const token = req.query.token const token = req.query.token
return pairing.authorizeCaDownload(token) return pairing.authorizeCaDownload(token)
.then(ca => res.json({ca})) .then(ca => res.json({ca}))
.catch(() => res.status(403).json({error: 'forbidden'})) .catch(() => res.status(403).json({error: 'forbidden'}))
} }
function pair (req, res, next) { function pair (req, res, next) {
@ -215,21 +215,21 @@ function pair (req, res, next) {
const model = req.query.model const model = req.query.model
return pairing.pair(token, deviceId, model) return pairing.pair(token, deviceId, model)
.then(valid => { .then(valid => {
if (valid) { if (valid) {
return helpers.updateMachineDefaults(deviceId) return helpers.updateMachineDefaults(deviceId)
.then(() => res.json({status: 'paired'})) .then(() => res.json({status: 'paired'}))
} }
throw httpError('Pairing failed') throw httpError('Pairing failed')
}) })
.catch(next) .catch(next)
} }
function errorHandler (err, req, res, next) { function errorHandler (err, req, res, next) {
const statusCode = err.name === 'HTTPError' const statusCode = err.name === 'HTTPError'
? err.code || 500 ? err.code || 500
: 500 : 500
const json = {error: err.message} const json = {error: err.message}
@ -269,15 +269,15 @@ function authorize (req, res, next) {
const deviceId = req.deviceId const deviceId = req.deviceId
return pairing.isPaired(deviceId) return pairing.isPaired(deviceId)
.then(r => { .then(r => {
if (r) { if (r) {
req.deviceId = deviceId req.deviceId = deviceId
return next() return next()
} }
return res.status(403).json({error: 'Forbidden'}) return res.status(403).json({error: 'Forbidden'})
}) })
.catch(next) .catch(next)
} }
const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) && 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) => { localApp.post('/dbChange', (req, res, next) => {
return settingsLoader.loadLatest() return settingsLoader.loadLatest()
.then(poller.reload) .then(poller.reload)
.then(() => logger.info('Config reloaded')) .then(() => logger.info('Config reloaded'))
.catch(err => { .catch(err => {
logger.error(err) logger.error(err)
res.sendStatus(500) res.sendStatus(500)
}) })
}) })
function sha256 (buf) { function sha256 (buf) {
@ -367,8 +367,8 @@ function sha256 (buf) {
function populateDeviceId (req, res, next) { function populateDeviceId (req, res, next) {
const deviceId = _.isFunction(req.connection.getPeerCertificate) const deviceId = _.isFunction(req.connection.getPeerCertificate)
? sha256(req.connection.getPeerCertificate().raw) ? sha256(req.connection.getPeerCertificate().raw)
: null : null
req.deviceId = deviceId req.deviceId = deviceId
req.deviceTime = req.get('date') req.deviceTime = req.get('date')
@ -386,16 +386,16 @@ function populateSettings (req, res, next) {
if (!versionId) { if (!versionId) {
return settingsLoader.loadLatest() return settingsLoader.loadLatest()
.then(settings => { req.settings = settings }) .then(settings => { req.settings = settings })
.then(() => next()) .then(() => next())
.catch(next) .catch(next)
} }
settingsLoader.load(versionId) settingsLoader.load(versionId)
.then(settings => { req.settings = settings }) .then(settings => { req.settings = settings })
.then(() => helpers.updateDeviceConfigVersion(versionId)) .then(() => helpers.updateDeviceConfigVersion(versionId))
.then(() => next()) .then(() => next())
.catch(next) .catch(next)
} }
module.exports = {app, localApp} module.exports = {app, localApp}

View file

@ -21,15 +21,15 @@ function loadFixture () {
const fixturePath = fixture => path.resolve(__dirname, '..', 'test', 'fixtures', fixture + '.json') const fixturePath = fixture => path.resolve(__dirname, '..', 'test', 'fixtures', fixture + '.json')
const promise = fixture const promise = fixture
? pify(fs.readFile)(fixturePath(fixture)).then(JSON.parse) ? pify(fs.readFile)(fixturePath(fixture)).then(JSON.parse)
: Promise.resolve([]) : Promise.resolve([])
return promise return promise
.then(values => _.map(v => { .then(values => _.map(v => {
return (v.fieldLocator.fieldScope.machine === 'machine') return (v.fieldLocator.fieldScope.machine === 'machine')
? _.set('fieldLocator.fieldScope.machine', machine, v) ? _.set('fieldLocator.fieldScope.machine', machine, v)
: v : v
}, values)) }, values))
} }
function isEquivalentField (a, b) { function isEquivalentField (a, b) {
@ -48,18 +48,18 @@ function load (versionId) {
if (!versionId) throw new Error('versionId is required') if (!versionId) throw new Error('versionId is required')
return Promise.all([loadConfig(versionId), loadAccounts()]) return Promise.all([loadConfig(versionId), loadAccounts()])
.then(([config, accounts]) => ({ .then(([config, accounts]) => ({
config, config,
accounts accounts
})) }))
} }
function loadLatest () { function loadLatest () {
return Promise.all([loadLatestConfig(), loadAccounts()]) return Promise.all([loadLatestConfig(), loadAccounts()])
.then(([config, accounts]) => ({ .then(([config, accounts]) => ({
config, config,
accounts accounts
})) }))
} }
function loadConfig (versionId) { function loadConfig (versionId) {
@ -71,15 +71,15 @@ function loadConfig (versionId) {
and valid` and valid`
return db.one(sql, [versionId, 'config']) return db.one(sql, [versionId, 'config'])
.then(row => row.data.config) .then(row => row.data.config)
.then(configValidate.validate) .then(configValidate.validate)
.catch(err => { .catch(err => {
if (err.name === 'QueryResultError') { if (err.name === 'QueryResultError') {
throw new Error('No such config version: ' + versionId) throw new Error('No such config version: ' + versionId)
} }
throw err throw err
}) })
} }
function loadLatestConfig () { function loadLatestConfig () {
@ -93,15 +93,15 @@ function loadLatestConfig () {
limit 1` limit 1`
return db.one(sql, ['config']) return db.one(sql, ['config'])
.then(row => row.data.config) .then(row => row.data.config)
.then(configValidate.validate) .then(configValidate.validate)
.catch(err => { .catch(err => {
if (err.name === 'QueryResultError') { if (err.name === 'QueryResultError') {
throw new Error('lamassu-server is not configured') throw new Error('lamassu-server is not configured')
} }
throw err throw err
}) })
} }
function loadRecentConfig () { function loadRecentConfig () {
@ -114,7 +114,7 @@ function loadRecentConfig () {
limit 1` limit 1`
return db.one(sql, ['config']) return db.one(sql, ['config'])
.then(row => row.data.config) .then(row => row.data.config)
} }
function loadAccounts () { function loadAccounts () {
@ -122,10 +122,10 @@ function loadAccounts () {
const toPairs = r => [r.code, toFields(r.fields)] const toPairs = r => [r.code, toFields(r.fields)]
return db.oneOrNone('select data from user_config where type=$1', 'accounts') return db.oneOrNone('select data from user_config where type=$1', 'accounts')
.then(function (data) { .then(function (data) {
if (!data) return {} if (!data) return {}
return _.fromPairs(_.map(toPairs, data.data.accounts)) return _.fromPairs(_.map(toPairs, data.data.accounts))
}) })
} }
function settings () { function settings () {
@ -136,8 +136,8 @@ function save (config) {
const sql = 'insert into user_config (type, data, valid) values ($1, $2, $3)' const sql = 'insert into user_config (type, data, valid) values ($1, $2, $3)'
return configValidate.validate(config) return configValidate.validate(config)
.then(() => db.none(sql, ['config', {config}, true])) .then(() => db.none(sql, ['config', {config}, true]))
.catch(() => db.none(sql, ['config', {config}, false])) .catch(() => db.none(sql, ['config', {config}, false]))
} }
function configAddField (scope, fieldCode, fieldType, fieldClass, value) { function configAddField (scope, fieldCode, fieldType, fieldClass, value) {
@ -222,11 +222,11 @@ function modifyConfig (newFields) {
function transaction (t) { function transaction (t) {
return loadRecentConfig() return loadRecentConfig()
.then(oldConfig => { .then(oldConfig => {
const oldConfigWithDefaults = addCryptoDefaults(oldConfig, newFields) const oldConfigWithDefaults = addCryptoDefaults(oldConfig, newFields)
const doSave = _.flow(mergeValues, save) const doSave = _.flow(mergeValues, save)
return doSave(oldConfigWithDefaults, newFields) return doSave(oldConfigWithDefaults, newFields)
}) })
} }
transaction.txMode = tmSRD transaction.txMode = tmSRD

View file

@ -3,13 +3,13 @@ const ph = require('./plugin-helper')
function sendMessage (settings, rec) { function sendMessage (settings, rec) {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
const pluginCode = configManager.unscoped(settings.config).sms const pluginCode = configManager.unscoped(settings.config).sms
const plugin = ph.load(ph.SMS, pluginCode) const plugin = ph.load(ph.SMS, pluginCode)
const account = settings.accounts[pluginCode] const account = settings.accounts[pluginCode]
return plugin.sendMessage(account, rec) return plugin.sendMessage(account, rec)
}) })
} }
module.exports = {sendMessage} module.exports = {sendMessage}

View file

@ -17,7 +17,7 @@ function get (id) {
if (!id || _.isEmpty(id)) return Promise.resolve() if (!id || _.isEmpty(id)) return Promise.resolve()
const sql = 'select * from support_logs where id=$1' const sql = 'select * from support_logs where id=$1'
return db.oneOrNone(sql, [id]) return db.oneOrNone(sql, [id])
.then(_.mapKeys(_.camelCase)) .then(_.mapKeys(_.camelCase))
} }
/** /**
* Insert a single support_logs row in db * Insert a single support_logs row in db
@ -34,7 +34,7 @@ function insert (deviceId) {
const sql = `insert into support_logs const sql = `insert into support_logs
(id, device_id) values ($1, $2) returning *` (id, device_id) values ($1, $2) returning *`
return db.one(sql, [uuid.v4(), deviceId]) 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') where timestamp > (now() - interval '1 week')
order by s.timestamp desc` order by s.timestamp desc`
return db.any(sql) return db.any(sql)
.then(_.map(_.mapKeys(_.camelCase))) .then(_.map(_.mapKeys(_.camelCase)))
} }
module.exports = { get, insert, batch } module.exports = { get, insert, batch }

View file

@ -9,29 +9,29 @@ const FETCH_INTERVAL = 10000
function _getRates (settings, fiatCode, cryptoCode) { function _getRates (settings, fiatCode, cryptoCode) {
return Promise.resolve() return Promise.resolve()
.then(() => { .then(() => {
const config = settings.config const config = settings.config
const plugin = configManager.cryptoScoped(cryptoCode, config).ticker const plugin = configManager.cryptoScoped(cryptoCode, config).ticker
const account = settings.accounts[plugin] const account = settings.accounts[plugin]
const ticker = ph.load(ph.TICKER, plugin) const ticker = ph.load(ph.TICKER, plugin)
const market = [cryptoCode, fiatCode].join('-') const market = [cryptoCode, fiatCode].join('-')
return ticker.ticker(account, fiatCode, cryptoCode) return ticker.ticker(account, fiatCode, cryptoCode)
.then(r => ({ .then(r => ({
rates: r.rates, rates: r.rates,
timestamp: Date.now() timestamp: Date.now()
})) }))
.then(r => { .then(r => {
lastRate[market] = r lastRate[market] = r
return r return r
})
.catch(err => {
logger.error(err)
return lastRate[market]
})
}) })
.catch(err => {
logger.error(err)
return lastRate[market]
})
})
} }
const getRates = mem(_getRates, { const getRates = mem(_getRates, {

View file

@ -13,7 +13,7 @@ function process (tx, pi) {
function post (tx, pi) { function post (tx, pi) {
return process(tx, pi) return process(tx, pi)
.then(_.set('dirty', false)) .then(_.set('dirty', false))
} }
function massage (tx) { function massage (tx) {
@ -24,17 +24,17 @@ function massage (tx) {
const mapBN = r => { const mapBN = r => {
const update = r.direction === 'cashIn' const update = r.direction === 'cashIn'
? { ? {
cryptoAtoms: BN(r.cryptoAtoms), cryptoAtoms: BN(r.cryptoAtoms),
fiat: BN(r.fiat), fiat: BN(r.fiat),
cashInFee: BN(r.cashInFee), cashInFee: BN(r.cashInFee),
cashInFeeCrypto: BN(r.cashInFeeCrypto), cashInFeeCrypto: BN(r.cashInFeeCrypto),
minimumTx: BN(r.minimumTx) minimumTx: BN(r.minimumTx)
} }
: { : {
cryptoAtoms: BN(r.cryptoAtoms), cryptoAtoms: BN(r.cryptoAtoms),
fiat: BN(r.fiat) fiat: BN(r.fiat)
} }
return _.assign(r, update) return _.assign(r, update)
} }
@ -51,10 +51,10 @@ function cancel (txId) {
] ]
return Promise.all(promises) return Promise.all(promises)
.then(r => { .then(r => {
if (_.some(r)) return if (_.some(r)) return
throw new Error('No such transaction') throw new Error('No such transaction')
}) })
} }
module.exports = {post, cancel} module.exports = {post, cancel}

View file

@ -30,14 +30,14 @@ function computeSeed (masterSeed) {
function fetchWallet (settings, cryptoCode) { function fetchWallet (settings, cryptoCode) {
return fs.readFile(options.seedPath, 'utf8') return fs.readFile(options.seedPath, 'utf8')
.then(hex => { .then(hex => {
const masterSeed = Buffer.from(hex.trim(), 'hex') const masterSeed = Buffer.from(hex.trim(), 'hex')
const plugin = configManager.cryptoScoped(cryptoCode, settings.config).wallet const plugin = configManager.cryptoScoped(cryptoCode, settings.config).wallet
const wallet = ph.load(ph.WALLET, plugin) const wallet = ph.load(ph.WALLET, plugin)
const account = settings.accounts[plugin] const account = settings.accounts[plugin]
return {wallet, account: _.set('seed', computeSeed(masterSeed), account)} return {wallet, account: _.set('seed', computeSeed(masterSeed), account)}
}) })
} }
const lastBalance = {} const lastBalance = {}
@ -45,54 +45,54 @@ const lastBalance = {}
function _balance (settings, cryptoCode) { function _balance (settings, cryptoCode) {
logger.debug('Polled wallet balance') logger.debug('Polled wallet balance')
return fetchWallet(settings, cryptoCode) return fetchWallet(settings, cryptoCode)
.then(r => r.wallet.balance(r.account, cryptoCode)) .then(r => r.wallet.balance(r.account, cryptoCode))
.then(balance => ({balance, timestamp: Date.now()})) .then(balance => ({balance, timestamp: Date.now()}))
.then(r => { .then(r => {
lastBalance[cryptoCode] = r lastBalance[cryptoCode] = r
return r return r
}) })
.catch(err => { .catch(err => {
console.error(err) console.error(err)
return lastBalance[cryptoCode] return lastBalance[cryptoCode]
}) })
} }
function sendCoins (settings, toAddress, cryptoAtoms, cryptoCode) { function sendCoins (settings, toAddress, cryptoAtoms, cryptoCode) {
return fetchWallet(settings, cryptoCode) return fetchWallet(settings, cryptoCode)
.then(r => { .then(r => {
return r.wallet.sendCoins(r.account, toAddress, cryptoAtoms, cryptoCode) return r.wallet.sendCoins(r.account, toAddress, cryptoAtoms, cryptoCode)
.then(res => { .then(res => {
mem.clear(module.exports.balance) mem.clear(module.exports.balance)
return res return res
})
}) })
}) .catch(err => {
.catch(err => { if (err.name === INSUFFICIENT_FUNDS_NAME) {
if (err.name === INSUFFICIENT_FUNDS_NAME) { throw httpError(INSUFFICIENT_FUNDS_NAME, INSUFFICIENT_FUNDS_CODE)
throw httpError(INSUFFICIENT_FUNDS_NAME, INSUFFICIENT_FUNDS_CODE) }
}
throw err throw err
}) })
} }
function newAddress (settings, info) { function newAddress (settings, info) {
return fetchWallet(settings, info.cryptoCode) 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) { function newFunding (settings, cryptoCode, address) {
return fetchWallet(settings, cryptoCode) return fetchWallet(settings, cryptoCode)
.then(r => { .then(r => {
const wallet = r.wallet const wallet = r.wallet
const account = r.account const account = r.account
return wallet.newFunding(account, cryptoCode) return wallet.newFunding(account, cryptoCode)
}) })
} }
function getWalletStatus (settings, tx) { function getWalletStatus (settings, tx) {
return fetchWallet(settings, tx.cryptoCode) 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) { function authorizeZeroConf (settings, tx, machineId) {
@ -115,34 +115,34 @@ function authorizeZeroConf (settings, tx, machineId) {
function getStatus (settings, tx, machineId) { function getStatus (settings, tx, machineId) {
return getWalletStatus(settings, tx) return getWalletStatus(settings, tx)
.then((statusRec) => { .then((statusRec) => {
if (statusRec.status === 'authorized') { if (statusRec.status === 'authorized') {
return authorizeZeroConf(settings, tx, machineId) return authorizeZeroConf(settings, tx, machineId)
.then(isAuthorized => { .then(isAuthorized => {
const publishAge = Date.now() - tx.publishedAt const publishAge = Date.now() - tx.publishedAt
const unauthorizedStatus = publishAge < ZERO_CONF_EXPIRATION const unauthorizedStatus = publishAge < ZERO_CONF_EXPIRATION
? 'published' ? 'published'
: 'rejected' : 'rejected'
const status = isAuthorized ? 'authorized' : unauthorizedStatus const status = isAuthorized ? 'authorized' : unauthorizedStatus
return {status} return {status}
}) })
} }
return statusRec return statusRec
}) })
} }
function sweep (settings, cryptoCode, hdIndex) { function sweep (settings, cryptoCode, hdIndex) {
return fetchWallet(settings, cryptoCode) 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) { function isHd (settings, cryptoCode) {
return fetchWallet(settings, cryptoCode) return fetchWallet(settings, cryptoCode)
.then(r => r.wallet.supportsHd) .then(r => r.wallet.supportsHd)
} }
function cryptoNetwork (settings, cryptoCode) { function cryptoNetwork (settings, cryptoCode) {

View file

@ -5,7 +5,7 @@ function singleQuotify (item) { return '\'' + item + '\'' }
exports.up = function (next) { exports.up = function (next) {
var statuses = ['notSeen', 'published', 'authorized', 'instant', var statuses = ['notSeen', 'published', 'authorized', 'instant',
'confirmed', 'rejected', 'insufficientFunds'] 'confirmed', 'rejected', 'insufficientFunds']
.map(singleQuotify).join(',') .map(singleQuotify).join(',')
var sql = [ var sql = [
'create type status_stage AS enum (' + statuses + ')', 'create type status_stage AS enum (' + statuses + ')',

View file

@ -4,9 +4,9 @@ function singleQuotify (item) { return '\'' + item + '\'' }
exports.up = function (next) { exports.up = function (next) {
var actions = ['published', 'authorized', 'instant', 'confirmed', 'rejected', var actions = ['published', 'authorized', 'instant', 'confirmed', 'rejected',
'insufficientFunds', 'dispenseRequested', 'dispensed', 'notified', 'insufficientFunds', 'dispenseRequested', 'dispensed', 'notified',
'addedPhone', 'redeem'] 'addedPhone', 'redeem']
.map(singleQuotify).join(',') .map(singleQuotify).join(',')
var sql = [ var sql = [
`create table cash_in_txs ( `create table cash_in_txs (

View file

@ -23,7 +23,7 @@ exports.up = function (next) {
`insert into customers (id, name) VALUES ( '${anonymous.uuid}','${anonymous.name}' )`, `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_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}'` `alter table cash_out_txs add column customer_id uuid references customers (id) DEFAULT '${anonymous.uuid}'`
] ]
db.multi(sql, next) db.multi(sql, next)
} }

View file

@ -3,7 +3,7 @@ var db = require('./db')
exports.up = function (next) { exports.up = function (next) {
const sql = const sql =
[ "create type compliance_types as enum ('manual', 'sanctions', 'sanctions_override')", [ "create type compliance_types as enum ('manual', 'sanctions', 'sanctions_override')",
`create table compliance_authorizations ( `create table compliance_authorizations (
id uuid PRIMARY KEY, id uuid PRIMARY KEY,
customer_id uuid REFERENCES customers (id), customer_id uuid REFERENCES customers (id),
compliance_type compliance_types NOT NULL, compliance_type compliance_types NOT NULL,

View file

@ -6,7 +6,7 @@ exports.up = function (next) {
id uuid PRIMARY KEY, id uuid PRIMARY KEY,
device_id text, device_id text,
timestamp timestamptz not null default now() )`, 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) db.multi(sql, next)

View file

@ -3,23 +3,23 @@ const migrateTools = require('./migrate-tools')
exports.up = function (next) { exports.up = function (next) {
return migrateTools.migrateNames() return migrateTools.migrateNames()
.then(updateSql => { .then(updateSql => {
const sql = [ const sql = [
'alter table devices add column name text', 'alter table devices add column name text',
updateSql, updateSql,
'alter table devices alter column name set not null' 'alter table devices alter column name set not null'
] ]
return db.multi(sql, next) return db.multi(sql, next)
}) })
.catch(() => { .catch(() => {
const sql = [ const sql = [
'alter table devices add column name text', 'alter table devices add column name text',
'alter table devices alter column name set not null' 'alter table devices alter column name set not null'
] ]
return db.multi(sql, next) return db.multi(sql, next)
}) })
} }
exports.down = function (next) { exports.down = function (next) {

View file

@ -7,17 +7,17 @@ function multi (sqls, cb) {
const doQuery = s => { const doQuery = s => {
return () => { return () => {
return db.none(s) return db.none(s)
.catch(err => { .catch(err => {
console.log(err.stack) console.log(err.stack)
throw err throw err
}) })
} }
} }
return sequential(sqls.map(doQuery)) return sequential(sqls.map(doQuery))
.then(() => cb()) .then(() => cb())
.catch(err => { .catch(err => {
console.log(err.stack) console.log(err.stack)
cb(err) cb(err)
}) })
} }

View file

@ -10,7 +10,7 @@ function migrateNames () {
const cs = new pgp.helpers.ColumnSet(['device_id', 'name'], {table: 'devices'}) const cs = new pgp.helpers.ColumnSet(['device_id', 'name'], {table: 'devices'})
return settingsLoader.loadLatest() return settingsLoader.loadLatest()
.then(r => machineLoader.getMachineNames(r.config)) .then(r => machineLoader.getMachineNames(r.config))
.then(_.map(r => ({device_id: r.deviceId, name: r.name}))) .then(_.map(r => ({device_id: r.deviceId, name: r.name})))
.then(data => pgp.helpers.update(data, cs)) .then(data => pgp.helpers.update(data, cs))
} }

View file

@ -40,10 +40,10 @@ test('bigger merge', t => {
) )
const expected = [ const expected = [
{fieldLocator: fieldLocator1, fieldValue: fieldValue4}, {fieldLocator: fieldLocator1, fieldValue: fieldValue4},
{fieldLocator: fieldLocator4, fieldValue: fieldValue5}, {fieldLocator: fieldLocator4, fieldValue: fieldValue5},
{fieldLocator: fieldLocator2, fieldValue: fieldValue2}, {fieldLocator: fieldLocator2, fieldValue: fieldValue2},
{fieldLocator: fieldLocator3, fieldValue: fieldValue3} {fieldLocator: fieldLocator3, fieldValue: fieldValue3}
] ]
t.deepEqual(merged, expected) t.deepEqual(merged, expected)

View file

@ -6,7 +6,7 @@ const rawCountries = require('../raw-countries.json')
const topCodes = ['US', 'GB', 'CA', 'AU'] const topCodes = ['US', 'GB', 'CA', 'AU']
const countries = rawCountries 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 topCountries = topCodes.map(c => countries.find(_.matchesProperty('code', c)))
const final = _.uniqBy(_.get('code'), _.concat(topCountries, countries)) const final = _.uniqBy(_.get('code'), _.concat(topCountries, countries))

View file

@ -5,7 +5,7 @@ const fields = [
] ]
settingsLoader.modifyConfig(fields) settingsLoader.modifyConfig(fields)
.then(() => { .then(() => {
console.log('success.') console.log('success.')
process.exit(0) process.exit(0)
}) })

View file

@ -11,15 +11,15 @@ function dbFetchConfig () {
'select data from user_config where type=$1 order by id desc limit 1', 'select data from user_config where type=$1 order by id desc limit 1',
['config'] ['config']
) )
.then(row => row && row.data) .then(row => row && row.data)
} }
dbFetchConfig() dbFetchConfig()
.then(config => { .then(config => {
pp(config) pp(config)
process.exit(0) process.exit(0)
}) })
.catch(e => { .catch(e => {
console.log(e) console.log(e)
process.exit(1) process.exit(1)
}) })