format for latest standard
This commit is contained in:
parent
4108efd9c7
commit
c2af183911
77 changed files with 1697 additions and 1693 deletions
|
|
@ -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()
|
})
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -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 () {
|
||||||
|
|
|
||||||
|
|
@ -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 => {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
46
lib/app.js
46
lib/app.js
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,5 @@ function run () {
|
||||||
})
|
})
|
||||||
|
|
||||||
inquirer.prompt(questions)
|
inquirer.prompt(questions)
|
||||||
.then(answers => processCryptos(answers.crypto))
|
.then(answers => processCryptos(answers.crypto))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
12
lib/email.js
12
lib/email.js
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
20
lib/logs.js
20
lib/logs.js
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,3 @@ function run () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
120
lib/notifier.js
120
lib/notifier.js
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
258
lib/plugins.js
258
lib/plugins.js
|
|
@ -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 () {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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'}
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 () {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
246
lib/routes.js
246
lib/routes.js
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
12
lib/sms.js
12
lib/sms.js
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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, {
|
||||||
|
|
|
||||||
32
lib/tx.js
32
lib/tx.js
|
|
@ -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}
|
||||||
|
|
|
||||||
104
lib/wallet.js
104
lib/wallet.js
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 + ')',
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue