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
|
||||
and stage='final_request' and authority='machine'`)
|
||||
.then(rs =>
|
||||
db.tx(t =>
|
||||
t.batch(rs.map(r => db.none(`insert into cash_in_txs (session_id,
|
||||
.then(rs =>
|
||||
db.tx(t =>
|
||||
t.batch(rs.map(r => db.none(`insert into cash_in_txs (session_id,
|
||||
device_fingerprint, to_address, crypto_atoms, crypto_code, fiat,
|
||||
currency_code, fee, tx_hash, error, created) values ($1, $2, $3, $4, $5,
|
||||
$6, $7, $8, $9, $10, $11)`, [r.session_id, r.device_fingerprint,
|
||||
r.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code, r.fee,
|
||||
r.tx_hash, r.error, r.created]))
|
||||
r.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code, r.fee,
|
||||
r.tx_hash, r.error, r.created]))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(() => db.manyOrNone(`select * from transactions where incoming=true
|
||||
.then(() => db.manyOrNone(`select * from transactions where incoming=true
|
||||
and stage='initial_request' and authority='pending'`))
|
||||
.then(rs =>
|
||||
db.tx(t =>
|
||||
t.batch(rs.map(r => db.none(`insert into cash_out_txs (session_id,
|
||||
.then(rs =>
|
||||
db.tx(t =>
|
||||
t.batch(rs.map(r => db.none(`insert into cash_out_txs (session_id,
|
||||
device_fingerprint, to_address, crypto_atoms, crypto_code, fiat,
|
||||
currency_code, tx_hash, phone, error, created) values ($1, $2, $3, $4, $5,
|
||||
$6, $7, $8, $9, $10, $11)`, [r.session_id, r.device_fingerprint,
|
||||
r.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code,
|
||||
r.tx_hash, r.phone, r.error, r.created]))
|
||||
r.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code,
|
||||
r.tx_hash, r.phone, r.error, r.created]))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(() => db.manyOrNone(`select * from transactions where incoming=true
|
||||
.then(() => db.manyOrNone(`select * from transactions where incoming=true
|
||||
and stage='dispense' and authority='authorized'`))
|
||||
.then(rs =>
|
||||
db.tx(t =>
|
||||
t.batch(rs.map(r =>
|
||||
db.none(`update cash_out_txs set dispensed=true where session_id=$1`, [r.session_id])
|
||||
.then(() => db.none(`insert into cash_out_actions (session_id, action,
|
||||
.then(rs =>
|
||||
db.tx(t =>
|
||||
t.batch(rs.map(r =>
|
||||
db.none(`update cash_out_txs set dispensed=true where session_id=$1`, [r.session_id])
|
||||
.then(() => db.none(`insert into cash_out_actions (session_id, action,
|
||||
created) values ($1, $2, $3)`, [r.session_id, 'dispensed', r.created]))
|
||||
))
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(() => pgp.end())
|
||||
.then(() => console.log('Success.'))
|
||||
.catch(e => {
|
||||
console.log(e)
|
||||
pgp.end()
|
||||
})
|
||||
.then(() => pgp.end())
|
||||
.then(() => console.log('Success.'))
|
||||
.catch(e => {
|
||||
console.log(e)
|
||||
pgp.end()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ const settingsLoader = require('../lib/settings-loader')
|
|||
const pp = require('../lib/pp')
|
||||
|
||||
settingsLoader.loadLatest()
|
||||
.then(r => {
|
||||
pp('config')(r)
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e.stack)
|
||||
process.exit(1)
|
||||
})
|
||||
.then(r => {
|
||||
pp('config')(r)
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e.stack)
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,38 +8,38 @@ const psqlUrl = require('../lib/options').postgresql
|
|||
const db = pgp(psqlUrl)
|
||||
|
||||
db.many('select data from user_config', 'exchanges')
|
||||
.then(rows => {
|
||||
const config = rows.filter(r => r.type === 'exchanges')[0].data
|
||||
const brain = rows.filter(r => r.type === 'unit')[0].data
|
||||
const settings = config.exchanges.settings
|
||||
const compliance = settings.compliance
|
||||
const newConfig = {
|
||||
global: {
|
||||
cashInTransactionLimit: compliance.maximum.limit,
|
||||
cashOutTransactionLimit: settings.fiatTxLimit,
|
||||
cashInCommission: settings.commission,
|
||||
cashOutCommission: settings.fiatCommission || settings.commission,
|
||||
idVerificationEnabled: compliance.idVerificationEnabled,
|
||||
idVerificationLimit: compliance.idVerificationLimit,
|
||||
lowBalanceMargin: settings.lowBalanceMargin,
|
||||
zeroConfLimit: settings.zeroConfLimit,
|
||||
fiatCurrency: settings.currency,
|
||||
topCashOutDenomination: settings.cartridges[0],
|
||||
bottomCashOutDenomination: settings.cartridges[1],
|
||||
virtualCashOutDenomination: settings.virtualCartridges[0],
|
||||
machineLanguages: brain.locale.localeInfo.primaryLocales,
|
||||
coins: settings.coins
|
||||
},
|
||||
accounts: settings.plugins.settings
|
||||
}
|
||||
.then(rows => {
|
||||
const config = rows.filter(r => r.type === 'exchanges')[0].data
|
||||
const brain = rows.filter(r => r.type === 'unit')[0].data
|
||||
const settings = config.exchanges.settings
|
||||
const compliance = settings.compliance
|
||||
const newConfig = {
|
||||
global: {
|
||||
cashInTransactionLimit: compliance.maximum.limit,
|
||||
cashOutTransactionLimit: settings.fiatTxLimit,
|
||||
cashInCommission: settings.commission,
|
||||
cashOutCommission: settings.fiatCommission || settings.commission,
|
||||
idVerificationEnabled: compliance.idVerificationEnabled,
|
||||
idVerificationLimit: compliance.idVerificationLimit,
|
||||
lowBalanceMargin: settings.lowBalanceMargin,
|
||||
zeroConfLimit: settings.zeroConfLimit,
|
||||
fiatCurrency: settings.currency,
|
||||
topCashOutDenomination: settings.cartridges[0],
|
||||
bottomCashOutDenomination: settings.cartridges[1],
|
||||
virtualCashOutDenomination: settings.virtualCartridges[0],
|
||||
machineLanguages: brain.locale.localeInfo.primaryLocales,
|
||||
coins: settings.coins
|
||||
},
|
||||
accounts: settings.plugins.settings
|
||||
}
|
||||
|
||||
db.none('insert into user_config (type, data) values ($1, $2)', ['global', newConfig])
|
||||
.then(() => {
|
||||
console.log('Success.')
|
||||
process.exit(0)
|
||||
db.none('insert into user_config (type, data) values ($1, $2)', ['global', newConfig])
|
||||
.then(() => {
|
||||
console.log('Success.')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error: %s', err)
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error: %s', err)
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const headers = {
|
|||
|
||||
const body = JSON.stringify({tx: tx})
|
||||
got('http://localhost:3000/dispense', {body: body, json: true, headers: headers})
|
||||
.then(res => {
|
||||
console.log(res.body)
|
||||
})
|
||||
.catch(console.log)
|
||||
.then(res => {
|
||||
console.log(res.body)
|
||||
})
|
||||
.catch(console.log)
|
||||
|
|
|
|||
|
|
@ -15,17 +15,17 @@ db.init(psqlUrl)
|
|||
notifier.init(db, getBalances, {lowBalanceThreshold: 10})
|
||||
console.log('DEBUG0')
|
||||
notifier.checkStatus()
|
||||
.then(function (alertRec) {
|
||||
console.log('DEBUG1')
|
||||
console.log('%j', alertRec)
|
||||
var subject = notifier.alertSubject(alertRec)
|
||||
console.log(subject)
|
||||
var body = notifier.printEmailAlerts(alertRec)
|
||||
console.log(body)
|
||||
console.log(notifier.alertFingerprint(alertRec))
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.log(err.stack)
|
||||
process.exit(1)
|
||||
})
|
||||
.then(function (alertRec) {
|
||||
console.log('DEBUG1')
|
||||
console.log('%j', alertRec)
|
||||
var subject = notifier.alertSubject(alertRec)
|
||||
console.log(subject)
|
||||
var body = notifier.printEmailAlerts(alertRec)
|
||||
console.log(body)
|
||||
console.log(notifier.alertFingerprint(alertRec))
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.log(err.stack)
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ var rec = {
|
|||
|
||||
var db = config.connection
|
||||
config.loadConfig(db)
|
||||
.then(function (config) {
|
||||
plugins.configure(config)
|
||||
plugins.sendMessage(rec)
|
||||
.then(function () {
|
||||
console.log('Success.')
|
||||
.then(function (config) {
|
||||
plugins.configure(config)
|
||||
plugins.sendMessage(rec)
|
||||
.then(function () {
|
||||
console.log('Success.')
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.log(err.stack)
|
||||
})
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.log(err.stack)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,22 +8,21 @@ const schemas = ph.loadSchemas()
|
|||
|
||||
function fetchAccounts () {
|
||||
return db.oneOrNone('select data from user_config where type=$1', ['accounts'])
|
||||
.then(row => {
|
||||
|
||||
.then(row => {
|
||||
// Hard code this for now
|
||||
const accounts = [{
|
||||
code: 'blockcypher',
|
||||
display: 'Blockcypher',
|
||||
fields: [
|
||||
{ code: 'confidenceFactor', display: 'Confidence Factor', fieldType: 'integer', required: true, value: 90 }
|
||||
]
|
||||
}]
|
||||
const accounts = [{
|
||||
code: 'blockcypher',
|
||||
display: 'Blockcypher',
|
||||
fields: [
|
||||
{ code: 'confidenceFactor', display: 'Confidence Factor', fieldType: 'integer', required: true, value: 90 }
|
||||
]
|
||||
}]
|
||||
|
||||
return row
|
||||
? Promise.resolve(row.data.accounts)
|
||||
: db.none('insert into user_config (type, data, valid) values ($1, $2, $3)', ['accounts', {accounts}, true])
|
||||
.then(fetchAccounts)
|
||||
})
|
||||
return row
|
||||
? Promise.resolve(row.data.accounts)
|
||||
: db.none('insert into user_config (type, data, valid) values ($1, $2, $3)', ['accounts', {accounts}, true])
|
||||
.then(fetchAccounts)
|
||||
})
|
||||
}
|
||||
|
||||
function selectedAccounts () {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ module.exports = {run}
|
|||
|
||||
function dbNotify () {
|
||||
return got.post('http://localhost:3030/dbChange')
|
||||
.catch(e => console.error('Error: lamassu-server not responding'))
|
||||
.catch(e => console.error('Error: lamassu-server not responding'))
|
||||
}
|
||||
|
||||
const skip = (req, res) => req.path === '/api/status/' && res.statusCode === 200
|
||||
|
|
@ -81,23 +81,23 @@ app.get('/api/totem', (req, res) => {
|
|||
if (!name) return res.status(400).send('Name is required')
|
||||
|
||||
return pairing.totem(hostname, name)
|
||||
.then(totem => res.send(totem))
|
||||
.then(totem => res.send(totem))
|
||||
})
|
||||
|
||||
app.get('/api/accounts', (req, res) => {
|
||||
accounts.selectedAccounts()
|
||||
.then(accounts => res.json({accounts: accounts}))
|
||||
.then(accounts => res.json({accounts: accounts}))
|
||||
})
|
||||
|
||||
app.get('/api/account/:account', (req, res) => {
|
||||
accounts.getAccount(req.params.account)
|
||||
.then(account => res.json(account))
|
||||
.then(account => res.json(account))
|
||||
})
|
||||
|
||||
app.post('/api/account', (req, res) => {
|
||||
return accounts.updateAccount(req.body)
|
||||
.then(account => res.json(account))
|
||||
.then(() => dbNotify())
|
||||
.then(account => res.json(account))
|
||||
.then(() => dbNotify())
|
||||
})
|
||||
|
||||
app.get('/api/config/:config', (req, res) =>
|
||||
|
|
@ -105,133 +105,133 @@ app.get('/api/config/:config', (req, res) =>
|
|||
|
||||
app.post('/api/config', (req, res, next) => {
|
||||
config.saveConfigGroup(req.body)
|
||||
.then(c => res.json(c))
|
||||
.then(() => dbNotify())
|
||||
.catch(next)
|
||||
.then(c => res.json(c))
|
||||
.then(() => dbNotify())
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/accounts/account/:account', (req, res) => {
|
||||
accounts.getAccount(req.params.account)
|
||||
.then(r => res.send(r))
|
||||
.then(r => res.send(r))
|
||||
})
|
||||
|
||||
app.get('/api/machines', (req, res) => {
|
||||
machineLoader.getMachineNames()
|
||||
.then(r => res.send({machines: r}))
|
||||
.then(r => res.send({machines: r}))
|
||||
})
|
||||
|
||||
app.post('/api/machines', (req, res) => {
|
||||
machineLoader.setMachine(req.body)
|
||||
.then(() => machineLoader.getMachineNames())
|
||||
.then(r => res.send({machines: r}))
|
||||
.then(() => dbNotify())
|
||||
.then(() => machineLoader.getMachineNames())
|
||||
.then(r => res.send({machines: r}))
|
||||
.then(() => dbNotify())
|
||||
})
|
||||
|
||||
app.get('/api/funding', (req, res) => {
|
||||
return funding.getFunding()
|
||||
.then(r => res.json(r))
|
||||
.then(r => res.json(r))
|
||||
})
|
||||
|
||||
app.get('/api/funding/:cryptoCode', (req, res) => {
|
||||
const cryptoCode = req.params.cryptoCode
|
||||
|
||||
return funding.getFunding(cryptoCode)
|
||||
.then(r => res.json(r))
|
||||
.then(r => res.json(r))
|
||||
})
|
||||
|
||||
app.get('/api/status', (req, res, next) => {
|
||||
return Promise.all([server.status(), config.validateCurrentConfig()])
|
||||
.then(([serverStatus, invalidConfigGroups]) => res.send({
|
||||
server: serverStatus,
|
||||
invalidConfigGroups
|
||||
}))
|
||||
.catch(next)
|
||||
.then(([serverStatus, invalidConfigGroups]) => res.send({
|
||||
server: serverStatus,
|
||||
invalidConfigGroups
|
||||
}))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/transactions', (req, res, next) => {
|
||||
return transactions.batch()
|
||||
.then(r => res.send({transactions: r}))
|
||||
.catch(next)
|
||||
.then(r => res.send({transactions: r}))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/transaction/:id', (req, res, next) => {
|
||||
return transactions.single(req.params.id)
|
||||
.then(r => {
|
||||
if (!r) return res.status(404).send({Error: 'Not found'})
|
||||
return res.send(r)
|
||||
})
|
||||
.then(r => {
|
||||
if (!r) return res.status(404).send({Error: 'Not found'})
|
||||
return res.send(r)
|
||||
})
|
||||
})
|
||||
|
||||
app.patch('/api/transaction/:id', (req, res, next) => {
|
||||
if (!req.query.cancel) return res.status(400).send({Error: 'Requires cancel'})
|
||||
|
||||
return transactions.cancel(req.params.id)
|
||||
.then(r => {
|
||||
return res.send(r)
|
||||
})
|
||||
.catch(() => res.status(404).send({Error: 'Not found'}))
|
||||
.then(r => {
|
||||
return res.send(r)
|
||||
})
|
||||
.catch(() => res.status(404).send({Error: 'Not found'}))
|
||||
})
|
||||
|
||||
app.get('/api/customers', (req, res, next) => {
|
||||
return customers.batch()
|
||||
.then(r => res.send({customers: r}))
|
||||
.catch(next)
|
||||
.then(r => res.send({customers: r}))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/customer/:id', (req, res, next) => {
|
||||
return customers.getById(req.params.id)
|
||||
.then(r => {
|
||||
if (!r) return res.status(404).send({Error: 'Not found'})
|
||||
return res.send(r)
|
||||
})
|
||||
.then(r => {
|
||||
if (!r) return res.status(404).send({Error: 'Not found'})
|
||||
return res.send(r)
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/api/logs/:deviceId', (req, res, next) => {
|
||||
return logs.getMachineLogs(req.params.deviceId)
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/logs', (req, res, next) => {
|
||||
return machineLoader.getMachines()
|
||||
.then(machines => {
|
||||
const firstMachine = _.first(machines)
|
||||
if (!firstMachine) return res.status(404).send({Error: 'No machines'})
|
||||
return logs.getMachineLogs(firstMachine.deviceId)
|
||||
.then(r => res.send(r))
|
||||
})
|
||||
.catch(next)
|
||||
.then(machines => {
|
||||
const firstMachine = _.first(machines)
|
||||
if (!firstMachine) return res.status(404).send({Error: 'No machines'})
|
||||
return logs.getMachineLogs(firstMachine.deviceId)
|
||||
.then(r => res.send(r))
|
||||
})
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/support_logs', (req, res, next) => {
|
||||
return supportLogs.batch()
|
||||
.then(supportLogs => res.send({ supportLogs }))
|
||||
.catch(next)
|
||||
.then(supportLogs => res.send({ supportLogs }))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/support_logs/logs', (req, res, next) => {
|
||||
return supportLogs.get(req.query.supportLogId)
|
||||
.then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first))
|
||||
.then(result => {
|
||||
const log = result || {}
|
||||
return logs.getMachineLogs(log.deviceId, log.timestamp)
|
||||
})
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
.then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first))
|
||||
.then(result => {
|
||||
const log = result || {}
|
||||
return logs.getMachineLogs(log.deviceId, log.timestamp)
|
||||
})
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.post('/api/support_logs', (req, res, next) => {
|
||||
return supportLogs.insert(req.query.deviceId)
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.patch('/api/customer/:id', (req, res, next) => {
|
||||
if (!req.params.id) return res.status(400).send({Error: 'Requires id'})
|
||||
const token = req.token || req.cookies.token
|
||||
return customers.update(req.params.id, req.query, token)
|
||||
.then(r => res.send(r))
|
||||
.catch(() => res.status(404).send({Error: 'Not found'}))
|
||||
.then(r => res.send(r))
|
||||
.catch(() => res.status(404).send({Error: 'Not found'}))
|
||||
})
|
||||
|
||||
app.use((err, req, res, next) => {
|
||||
|
|
@ -253,35 +253,35 @@ function register (req, res, next) {
|
|||
if (!otp) return next()
|
||||
|
||||
return login.register(otp)
|
||||
.then(r => {
|
||||
if (r.expired) return res.status(401).send('OTP expired, generate new registration link')
|
||||
.then(r => {
|
||||
if (r.expired) return res.status(401).send('OTP expired, generate new registration link')
|
||||
|
||||
// Maybe user is using old registration key, attempt to authenticate
|
||||
if (!r.success) return next()
|
||||
// Maybe user is using old registration key, attempt to authenticate
|
||||
if (!r.success) return next()
|
||||
|
||||
const cookieOpts = {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
domain: hostname,
|
||||
sameSite: true,
|
||||
expires: NEVER
|
||||
}
|
||||
const cookieOpts = {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
domain: hostname,
|
||||
sameSite: true,
|
||||
expires: NEVER
|
||||
}
|
||||
|
||||
const token = r.token
|
||||
req.token = token
|
||||
res.cookie('token', token, cookieOpts)
|
||||
next()
|
||||
})
|
||||
const token = r.token
|
||||
req.token = token
|
||||
res.cookie('token', token, cookieOpts)
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
function authenticate (req, res, next) {
|
||||
const token = req.token || req.cookies.token
|
||||
|
||||
return login.authenticate(token)
|
||||
.then(success => {
|
||||
if (!success) return res.status(401).send('Authentication failed')
|
||||
next()
|
||||
})
|
||||
.then(success => {
|
||||
if (!success) return res.status(401).send('Authentication failed')
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', err => {
|
||||
|
|
@ -303,27 +303,27 @@ const wss = new WebSocket.Server({server: webServer})
|
|||
|
||||
function establishSocket (ws, token) {
|
||||
return login.authenticate(token)
|
||||
.then(success => {
|
||||
if (!success) return ws.close(1008, 'Authentication error')
|
||||
.then(success => {
|
||||
if (!success) return ws.close(1008, 'Authentication error')
|
||||
|
||||
const listener = data => {
|
||||
ws.send(JSON.stringify(data))
|
||||
}
|
||||
const listener = data => {
|
||||
ws.send(JSON.stringify(data))
|
||||
}
|
||||
|
||||
// Reauthenticate every once in a while, in case token expired
|
||||
setInterval(() => {
|
||||
return login.authenticate(token)
|
||||
.then(success => {
|
||||
if (!success) {
|
||||
socketEmitter.removeListener('message', listener)
|
||||
ws.close()
|
||||
}
|
||||
})
|
||||
}, REAUTHENTICATE_INTERVAL)
|
||||
// Reauthenticate every once in a while, in case token expired
|
||||
setInterval(() => {
|
||||
return login.authenticate(token)
|
||||
.then(success => {
|
||||
if (!success) {
|
||||
socketEmitter.removeListener('message', listener)
|
||||
ws.close()
|
||||
}
|
||||
})
|
||||
}, REAUTHENTICATE_INTERVAL)
|
||||
|
||||
socketEmitter.on('message', listener)
|
||||
ws.send('Testing123')
|
||||
})
|
||||
socketEmitter.on('message', listener)
|
||||
ws.send('Testing123')
|
||||
})
|
||||
}
|
||||
|
||||
wss.on('connection', ws => {
|
||||
|
|
|
|||
|
|
@ -32,25 +32,25 @@ const certOptions = {
|
|||
|
||||
app.get('/api/support_logs', (req, res, next) => {
|
||||
return supportLogs.batch()
|
||||
.then(supportLogs => res.send({ supportLogs }))
|
||||
.catch(next)
|
||||
.then(supportLogs => res.send({ supportLogs }))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/support_logs/logs', (req, res, next) => {
|
||||
return supportLogs.get(req.query.supportLogId)
|
||||
.then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first))
|
||||
.then(result => {
|
||||
const log = result || {}
|
||||
return logs.getUnlimitedMachineLogs(log.deviceId, log.timestamp)
|
||||
})
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
.then(log => (!_.isNil(log) && !_.isEmpty(log)) ? log : supportLogs.batch().then(_.first))
|
||||
.then(result => {
|
||||
const log = result || {}
|
||||
return logs.getUnlimitedMachineLogs(log.deviceId, log.timestamp)
|
||||
})
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.post('/api/support_logs', (req, res, next) => {
|
||||
return supportLogs.insert(req.query.deviceId)
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
function run (port) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ function fetchSchema () {
|
|||
const schemaPath = path.resolve(options.lamassuServerPath, 'lamassu-schema.json')
|
||||
|
||||
return fs.readFile(schemaPath)
|
||||
.then(JSON.parse)
|
||||
.then(JSON.parse)
|
||||
}
|
||||
|
||||
function fetchConfig () {
|
||||
|
|
@ -27,7 +27,7 @@ function fetchConfig () {
|
|||
order by id desc limit 1`
|
||||
|
||||
return db.oneOrNone(sql, ['config'])
|
||||
.then(row => row ? row.data.config : [])
|
||||
.then(row => row ? row.data.config : [])
|
||||
}
|
||||
|
||||
function allScopes (cryptoScopes, machineScopes) {
|
||||
|
|
@ -65,11 +65,11 @@ function getField (schema, group, fieldCode) {
|
|||
}
|
||||
|
||||
const fetchMachines = () => machineLoader.getMachines()
|
||||
.then(machineList => machineList.map(r => r.deviceId))
|
||||
.then(machineList => machineList.map(r => r.deviceId))
|
||||
|
||||
function validateCurrentConfig () {
|
||||
return fetchConfig()
|
||||
.then(configValidate.validateRequires)
|
||||
.then(configValidate.validateRequires)
|
||||
}
|
||||
|
||||
function decorateEnabledIf (schemaFields, schemaField) {
|
||||
|
|
@ -85,43 +85,43 @@ function decorateEnabledIf (schemaFields, schemaField) {
|
|||
function fetchConfigGroup (code) {
|
||||
const fieldLocatorCodeEq = R.pathEq(['fieldLocator', 'code'])
|
||||
return Promise.all([fetchSchema(), fetchData(), fetchConfig(), fetchMachines()])
|
||||
.then(([schema, data, config, machineList]) => {
|
||||
const groupSchema = schema.groups.find(r => r.code === code)
|
||||
.then(([schema, data, config, machineList]) => {
|
||||
const groupSchema = schema.groups.find(r => r.code === code)
|
||||
|
||||
if (!groupSchema) throw new Error('No such group schema: ' + code)
|
||||
if (!groupSchema) throw new Error('No such group schema: ' + code)
|
||||
|
||||
const schemaFields = groupSchema.fields
|
||||
.map(R.curry(getField)(schema, groupSchema))
|
||||
.map(f => _.assign(f, {
|
||||
fieldEnabledIfAny: f.enabledIfAny || [],
|
||||
fieldEnabledIfAll: f.enabledIfAll || []
|
||||
}))
|
||||
const schemaFields = groupSchema.fields
|
||||
.map(R.curry(getField)(schema, groupSchema))
|
||||
.map(f => _.assign(f, {
|
||||
fieldEnabledIfAny: f.enabledIfAny || [],
|
||||
fieldEnabledIfAll: f.enabledIfAll || []
|
||||
}))
|
||||
|
||||
const candidateFields = [
|
||||
schemaFields.map(R.prop('requiredIf')),
|
||||
schemaFields.map(R.prop('enabledIfAny')),
|
||||
schemaFields.map(R.prop('enabledIfAll')),
|
||||
groupSchema.fields,
|
||||
'fiatCurrency'
|
||||
]
|
||||
const configFields = R.uniq(R.flatten(candidateFields)).filter(R.identity)
|
||||
const candidateFields = [
|
||||
schemaFields.map(R.prop('requiredIf')),
|
||||
schemaFields.map(R.prop('enabledIfAny')),
|
||||
schemaFields.map(R.prop('enabledIfAll')),
|
||||
groupSchema.fields,
|
||||
'fiatCurrency'
|
||||
]
|
||||
const configFields = R.uniq(R.flatten(candidateFields)).filter(R.identity)
|
||||
|
||||
const reducer = (acc, configField) => {
|
||||
return acc.concat(config.filter(fieldLocatorCodeEq(configField)))
|
||||
}
|
||||
const reducer = (acc, configField) => {
|
||||
return acc.concat(config.filter(fieldLocatorCodeEq(configField)))
|
||||
}
|
||||
|
||||
const values = _.map(f => decorateEnabledIf(schema.fields, f), configFields.reduce(reducer, []))
|
||||
const values = _.map(f => decorateEnabledIf(schema.fields, f), configFields.reduce(reducer, []))
|
||||
|
||||
groupSchema.fields = undefined
|
||||
groupSchema.entries = schemaFields
|
||||
groupSchema.fields = undefined
|
||||
groupSchema.entries = schemaFields
|
||||
|
||||
return {
|
||||
schema: groupSchema,
|
||||
values,
|
||||
selectedCryptos: getCryptos(config, machineList),
|
||||
data
|
||||
}
|
||||
})
|
||||
return {
|
||||
schema: groupSchema,
|
||||
values,
|
||||
selectedCryptos: getCryptos(config, machineList),
|
||||
data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function massageCurrencies (currencies) {
|
||||
|
|
@ -154,55 +154,55 @@ const ALL_CRYPTOS = ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']
|
|||
|
||||
function fetchData () {
|
||||
return machineLoader.getMachineNames()
|
||||
.then(machineList => ({
|
||||
currencies: massageCurrencies(currencies),
|
||||
cryptoCurrencies: [
|
||||
{crypto: 'BTC', display: 'Bitcoin'},
|
||||
{crypto: 'ETH', display: 'Ethereum'},
|
||||
{crypto: 'LTC', display: 'Litecoin'},
|
||||
{crypto: 'DASH', display: 'Dash'},
|
||||
{crypto: 'ZEC', display: 'Zcash'},
|
||||
{crypto: 'BCH', display: 'BCH'}
|
||||
],
|
||||
languages: languages,
|
||||
countries,
|
||||
accounts: [
|
||||
{code: 'bitpay', display: 'Bitpay', class: 'ticker', cryptos: ['BTC', 'BCH']},
|
||||
{code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
||||
{code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS},
|
||||
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
|
||||
{code: 'lnd', display: 'Lightning Network', class: 'wallet', cryptos: ['BTC']},
|
||||
{code: 'geth', display: 'geth', class: 'wallet', cryptos: ['ETH']},
|
||||
{code: 'zcashd', display: 'zcashd', class: 'wallet', cryptos: ['ZEC']},
|
||||
{code: 'litecoind', display: 'litecoind', class: 'wallet', cryptos: ['LTC']},
|
||||
{code: 'dashd', display: 'dashd', class: 'wallet', cryptos: ['DASH']},
|
||||
{code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']},
|
||||
{code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']},
|
||||
{code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
||||
{code: 'mock-wallet', display: 'Mock wallet', class: 'wallet', cryptos: ALL_CRYPTOS},
|
||||
{code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS},
|
||||
{code: 'mock-exchange', display: 'Mock exchange', class: 'exchange', cryptos: ALL_CRYPTOS},
|
||||
{code: 'mock-sms', display: 'Mock SMS', class: 'sms'},
|
||||
{code: 'mock-id-verify', display: 'Mock ID verifier', class: 'idVerifier'},
|
||||
{code: 'twilio', display: 'Twilio', class: 'sms'},
|
||||
{code: 'mailjet', display: 'Mailjet', class: 'email'},
|
||||
{code: 'all-zero-conf', display: 'Always 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']},
|
||||
{code: 'no-zero-conf', display: 'Always 1-conf', class: 'zeroConf', cryptos: ALL_CRYPTOS},
|
||||
{code: 'blockcypher', display: 'Blockcypher', class: 'zeroConf', cryptos: ['BTC']},
|
||||
{code: 'mock-zero-conf', display: 'Mock 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']}
|
||||
],
|
||||
machines: machineList.map(machine => ({machine: machine.deviceId, display: machine.name}))
|
||||
}))
|
||||
.then(machineList => ({
|
||||
currencies: massageCurrencies(currencies),
|
||||
cryptoCurrencies: [
|
||||
{crypto: 'BTC', display: 'Bitcoin'},
|
||||
{crypto: 'ETH', display: 'Ethereum'},
|
||||
{crypto: 'LTC', display: 'Litecoin'},
|
||||
{crypto: 'DASH', display: 'Dash'},
|
||||
{crypto: 'ZEC', display: 'Zcash'},
|
||||
{crypto: 'BCH', display: 'BCH'}
|
||||
],
|
||||
languages: languages,
|
||||
countries,
|
||||
accounts: [
|
||||
{code: 'bitpay', display: 'Bitpay', class: 'ticker', cryptos: ['BTC', 'BCH']},
|
||||
{code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
||||
{code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS},
|
||||
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
|
||||
{code: 'lnd', display: 'Lightning Network', class: 'wallet', cryptos: ['BTC']},
|
||||
{code: 'geth', display: 'geth', class: 'wallet', cryptos: ['ETH']},
|
||||
{code: 'zcashd', display: 'zcashd', class: 'wallet', cryptos: ['ZEC']},
|
||||
{code: 'litecoind', display: 'litecoind', class: 'wallet', cryptos: ['LTC']},
|
||||
{code: 'dashd', display: 'dashd', class: 'wallet', cryptos: ['DASH']},
|
||||
{code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']},
|
||||
{code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']},
|
||||
{code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
||||
{code: 'mock-wallet', display: 'Mock wallet', class: 'wallet', cryptos: ALL_CRYPTOS},
|
||||
{code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS},
|
||||
{code: 'mock-exchange', display: 'Mock exchange', class: 'exchange', cryptos: ALL_CRYPTOS},
|
||||
{code: 'mock-sms', display: 'Mock SMS', class: 'sms'},
|
||||
{code: 'mock-id-verify', display: 'Mock ID verifier', class: 'idVerifier'},
|
||||
{code: 'twilio', display: 'Twilio', class: 'sms'},
|
||||
{code: 'mailjet', display: 'Mailjet', class: 'email'},
|
||||
{code: 'all-zero-conf', display: 'Always 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']},
|
||||
{code: 'no-zero-conf', display: 'Always 1-conf', class: 'zeroConf', cryptos: ALL_CRYPTOS},
|
||||
{code: 'blockcypher', display: 'Blockcypher', class: 'zeroConf', cryptos: ['BTC']},
|
||||
{code: 'mock-zero-conf', display: 'Mock 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']}
|
||||
],
|
||||
machines: machineList.map(machine => ({machine: machine.deviceId, display: machine.name}))
|
||||
}))
|
||||
}
|
||||
|
||||
function saveConfigGroup (results) {
|
||||
if (results.values.length === 0) return fetchConfigGroup(results.groupCode)
|
||||
|
||||
return settingsLoader.modifyConfig(results.values)
|
||||
.then(() => fetchConfigGroup(results.groupCode))
|
||||
.then(() => fetchConfigGroup(results.groupCode))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ function getCryptos (config, machineList) {
|
|||
|
||||
function fetchMachines () {
|
||||
return machineLoader.getMachines()
|
||||
.then(machineList => machineList.map(r => r.deviceId))
|
||||
.then(machineList => machineList.map(r => r.deviceId))
|
||||
}
|
||||
|
||||
function computeCrypto (cryptoCode, _balance) {
|
||||
|
|
@ -55,45 +55,45 @@ function computeFiat (rate, cryptoCode, _balance) {
|
|||
|
||||
function getFunding (_cryptoCode) {
|
||||
return Promise.all([settingsLoader.loadLatest(), fetchMachines()])
|
||||
.then(([settings, machineList]) => {
|
||||
const config = configManager.unscoped(settings.config)
|
||||
const cryptoCodes = getCryptos(settings.config, machineList)
|
||||
const cryptoCode = _cryptoCode || cryptoCodes[0]
|
||||
const fiatCode = config.fiatCurrency
|
||||
const pareCoins = c => _.includes(c.cryptoCode, cryptoCodes)
|
||||
const cryptoCurrencies = coinUtils.cryptoCurrencies()
|
||||
const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies)
|
||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
||||
.then(([settings, machineList]) => {
|
||||
const config = configManager.unscoped(settings.config)
|
||||
const cryptoCodes = getCryptos(settings.config, machineList)
|
||||
const cryptoCode = _cryptoCode || cryptoCodes[0]
|
||||
const fiatCode = config.fiatCurrency
|
||||
const pareCoins = c => _.includes(c.cryptoCode, cryptoCodes)
|
||||
const cryptoCurrencies = coinUtils.cryptoCurrencies()
|
||||
const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies)
|
||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
||||
|
||||
if (!cryptoRec) throw new Error(`Unsupported coin: ${cryptoCode}`)
|
||||
if (!cryptoRec) throw new Error(`Unsupported coin: ${cryptoCode}`)
|
||||
|
||||
const promises = [
|
||||
wallet.newFunding(settings, cryptoCode),
|
||||
ticker.getRates(settings, fiatCode, cryptoCode)
|
||||
]
|
||||
const promises = [
|
||||
wallet.newFunding(settings, cryptoCode),
|
||||
ticker.getRates(settings, fiatCode, cryptoCode)
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(([fundingRec, ratesRec]) => {
|
||||
const rates = ratesRec.rates
|
||||
const rate = (rates.ask.add(rates.bid)).div(2)
|
||||
const fundingConfirmedBalance = fundingRec.fundingConfirmedBalance
|
||||
const fiatConfirmedBalance = computeFiat(rate, cryptoCode, fundingConfirmedBalance)
|
||||
const pending = fundingRec.fundingPendingBalance.sub(fundingConfirmedBalance)
|
||||
const fiatPending = computeFiat(rate, cryptoCode, pending)
|
||||
const fundingAddress = fundingRec.fundingAddress
|
||||
const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress)
|
||||
return Promise.all(promises)
|
||||
.then(([fundingRec, ratesRec]) => {
|
||||
const rates = ratesRec.rates
|
||||
const rate = (rates.ask.add(rates.bid)).div(2)
|
||||
const fundingConfirmedBalance = fundingRec.fundingConfirmedBalance
|
||||
const fiatConfirmedBalance = computeFiat(rate, cryptoCode, fundingConfirmedBalance)
|
||||
const pending = fundingRec.fundingPendingBalance.sub(fundingConfirmedBalance)
|
||||
const fiatPending = computeFiat(rate, cryptoCode, pending)
|
||||
const fundingAddress = fundingRec.fundingAddress
|
||||
const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress)
|
||||
|
||||
return {
|
||||
cryptoCode,
|
||||
cryptoDisplays,
|
||||
fundingAddress,
|
||||
fundingAddressUrl,
|
||||
confirmedBalance: computeCrypto(cryptoCode, fundingConfirmedBalance).toFormat(5),
|
||||
pending: computeCrypto(cryptoCode, pending).toFormat(5),
|
||||
fiatConfirmedBalance: fiatConfirmedBalance.toFormat(2),
|
||||
fiatPending: fiatPending.toFormat(2),
|
||||
fiatCode
|
||||
}
|
||||
return {
|
||||
cryptoCode,
|
||||
cryptoDisplays,
|
||||
fundingAddress,
|
||||
fundingAddressUrl,
|
||||
confirmedBalance: computeCrypto(cryptoCode, fundingConfirmedBalance).toFormat(5),
|
||||
pending: computeCrypto(cryptoCode, pending).toFormat(5),
|
||||
fiatConfirmedBalance: fiatConfirmedBalance.toFormat(2),
|
||||
fiatPending: fiatPending.toFormat(2),
|
||||
fiatCode
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ function generateOTP (name) {
|
|||
const sql = 'insert into one_time_passes (token, name) values ($1, $2)'
|
||||
|
||||
return db.none(sql, [otp, name])
|
||||
.then(() => otp)
|
||||
.then(() => otp)
|
||||
}
|
||||
|
||||
function validateOTP (otp) {
|
||||
|
|
@ -17,22 +17,22 @@ function validateOTP (otp) {
|
|||
returning name, created < now() - interval '1 hour' as expired`
|
||||
|
||||
return db.one(sql, [otp])
|
||||
.then(r => ({success: !r.expired, expired: r.expired, name: r.name}))
|
||||
.catch(() => ({success: false, expired: false}))
|
||||
.then(r => ({success: !r.expired, expired: r.expired, name: r.name}))
|
||||
.catch(() => ({success: false, expired: false}))
|
||||
}
|
||||
|
||||
function register (otp) {
|
||||
return validateOTP(otp)
|
||||
.then(r => {
|
||||
if (!r.success) return r
|
||||
.then(r => {
|
||||
if (!r.success) return r
|
||||
|
||||
const token = crypto.randomBytes(32).toString('hex')
|
||||
const sql = 'insert into user_tokens (token, name) values ($1, $2)'
|
||||
const token = crypto.randomBytes(32).toString('hex')
|
||||
const sql = 'insert into user_tokens (token, name) values ($1, $2)'
|
||||
|
||||
return db.none(sql, [token, r.name])
|
||||
.then(() => ({success: true, token: token}))
|
||||
})
|
||||
.catch(() => ({success: false, expired: false}))
|
||||
return db.none(sql, [token, r.name])
|
||||
.then(() => ({success: true, token: token}))
|
||||
})
|
||||
.catch(() => ({success: false, expired: false}))
|
||||
}
|
||||
|
||||
function authenticate (token) {
|
||||
|
|
|
|||
|
|
@ -17,17 +17,17 @@ function totem (hostname, name) {
|
|||
const caPath = options.caPath
|
||||
|
||||
return readFile(caPath)
|
||||
.then(data => {
|
||||
const caHash = crypto.createHash('sha256').update(data).digest()
|
||||
const token = crypto.randomBytes(32)
|
||||
const hexToken = token.toString('hex')
|
||||
const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex')
|
||||
const buf = Buffer.concat([caHash, token, Buffer.from(hostname)])
|
||||
const sql = 'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)'
|
||||
.then(data => {
|
||||
const caHash = crypto.createHash('sha256').update(data).digest()
|
||||
const token = crypto.randomBytes(32)
|
||||
const hexToken = token.toString('hex')
|
||||
const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex')
|
||||
const buf = Buffer.concat([caHash, token, Buffer.from(hostname)])
|
||||
const sql = 'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)'
|
||||
|
||||
return db.none(sql, [hexToken, caHexToken, name])
|
||||
.then(() => bsAlpha.encode(buf))
|
||||
})
|
||||
return db.none(sql, [hexToken, caHexToken, name])
|
||||
.then(() => bsAlpha.encode(buf))
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {totem, unpair}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ const CONSIDERED_UP_SECS = 30
|
|||
|
||||
function checkWasConfigured () {
|
||||
return settingsLoader.loadLatest()
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
}
|
||||
|
||||
function machinesLastPing () {
|
||||
|
|
@ -21,28 +21,28 @@ function machinesLastPing () {
|
|||
group by device_id`
|
||||
|
||||
return Promise.all([machineLoader.getMachineNames(), db.any(sql)])
|
||||
.then(([machines, events]) => {
|
||||
if (machines.length === 0) return 'No paired machines'
|
||||
.then(([machines, events]) => {
|
||||
if (machines.length === 0) return 'No paired machines'
|
||||
|
||||
const addName = event => {
|
||||
const machine = _.find(['deviceId', event.deviceId], machines)
|
||||
if (!machine) return null
|
||||
return _.set('name', machine.name, event)
|
||||
}
|
||||
const addName = event => {
|
||||
const machine = _.find(['deviceId', event.deviceId], machines)
|
||||
if (!machine) return null
|
||||
return _.set('name', machine.name, event)
|
||||
}
|
||||
|
||||
const mapper = _.flow(_.filter(row => row.age > CONSIDERED_UP_SECS), _.map(addName), _.compact)
|
||||
const downRows = mapper(events)
|
||||
const mapper = _.flow(_.filter(row => row.age > CONSIDERED_UP_SECS), _.map(addName), _.compact)
|
||||
const downRows = mapper(events)
|
||||
|
||||
if (downRows.length === 0) return 'All machines are up'
|
||||
if (downRows.length === 0) return 'All machines are up'
|
||||
|
||||
if (downRows.length === 1) {
|
||||
const row = downRows[0]
|
||||
const age = moment.duration(row.age, 'seconds')
|
||||
return `${row.name} down for ${age.humanize()}`
|
||||
}
|
||||
if (downRows.length === 1) {
|
||||
const row = downRows[0]
|
||||
const age = moment.duration(row.age, 'seconds')
|
||||
return `${row.name} down for ${age.humanize()}`
|
||||
}
|
||||
|
||||
return 'Multiple machines down'
|
||||
})
|
||||
return 'Multiple machines down'
|
||||
})
|
||||
}
|
||||
|
||||
function status () {
|
||||
|
|
@ -53,32 +53,32 @@ function status () {
|
|||
limit 1`
|
||||
|
||||
return Promise.all([checkWasConfigured(), db.oneOrNone(sql, ['ping']), machinesLastPing()])
|
||||
.then(([wasConfigured, statusRow, machineStatus]) => {
|
||||
const age = statusRow && moment.duration(statusRow.age, 'seconds')
|
||||
const up = statusRow ? statusRow.age < CONSIDERED_UP_SECS : false
|
||||
const lastPing = statusRow && age.humanize()
|
||||
.then(([wasConfigured, statusRow, machineStatus]) => {
|
||||
const age = statusRow && moment.duration(statusRow.age, 'seconds')
|
||||
const up = statusRow ? statusRow.age < CONSIDERED_UP_SECS : false
|
||||
const lastPing = statusRow && age.humanize()
|
||||
|
||||
return settingsLoader.loadLatest()
|
||||
.catch(() => null)
|
||||
.then(settings => {
|
||||
return getRates(settings)
|
||||
.then(rates => ({wasConfigured, up, lastPing, rates, machineStatus}))
|
||||
return settingsLoader.loadLatest()
|
||||
.catch(() => null)
|
||||
.then(settings => {
|
||||
return getRates(settings)
|
||||
.then(rates => ({wasConfigured, up, lastPing, rates, machineStatus}))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getRates (settings) {
|
||||
if (!settings) return Promise.resolve([])
|
||||
|
||||
return ticker.getRates(settings, 'USD', 'BTC')
|
||||
.then(ratesRec => {
|
||||
return [{
|
||||
crypto: 'BTC',
|
||||
bid: parseFloat(ratesRec.rates.bid),
|
||||
ask: parseFloat(ratesRec.rates.ask)
|
||||
}]
|
||||
})
|
||||
.catch(() => [])
|
||||
.then(ratesRec => {
|
||||
return [{
|
||||
crypto: 'BTC',
|
||||
bid: parseFloat(ratesRec.rates.bid),
|
||||
ask: parseFloat(ratesRec.rates.ask)
|
||||
}]
|
||||
})
|
||||
.catch(() => [])
|
||||
}
|
||||
|
||||
module.exports = {status}
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ const NUM_RESULTS = 20
|
|||
|
||||
function addNames (txs) {
|
||||
return machineLoader.getMachineNames()
|
||||
.then(machines => {
|
||||
const addName = tx => {
|
||||
const machine = _.find(['deviceId', tx.deviceId], machines)
|
||||
const name = machine ? machine.name : 'Unpaired'
|
||||
return _.set('machineName', name, tx)
|
||||
}
|
||||
.then(machines => {
|
||||
const addName = tx => {
|
||||
const machine = _.find(['deviceId', tx.deviceId], machines)
|
||||
const name = machine ? machine.name : 'Unpaired'
|
||||
return _.set('machineName', name, tx)
|
||||
}
|
||||
|
||||
return _.map(addName, txs)
|
||||
})
|
||||
return _.map(addName, txs)
|
||||
})
|
||||
}
|
||||
|
||||
const camelize = _.mapKeys(_.camelCase)
|
||||
|
|
@ -36,7 +36,7 @@ function batch () {
|
|||
order by created desc limit $1`
|
||||
|
||||
return Promise.all([db.any(cashInSql, [cashInTx.PENDING_INTERVAL, NUM_RESULTS]), db.any(cashOutSql, [NUM_RESULTS])])
|
||||
.then(packager)
|
||||
.then(packager)
|
||||
}
|
||||
|
||||
function single (txId) {
|
||||
|
|
@ -56,13 +56,13 @@ function single (txId) {
|
|||
db.oneOrNone(cashInSql, [cashInTx.PENDING_INTERVAL, txId]),
|
||||
db.oneOrNone(cashOutSql, [txId])
|
||||
])
|
||||
.then(packager)
|
||||
.then(_.head)
|
||||
.then(packager)
|
||||
.then(_.head)
|
||||
}
|
||||
|
||||
function cancel (txId) {
|
||||
return tx.cancel(txId)
|
||||
.then(() => single(txId))
|
||||
.then(() => single(txId))
|
||||
}
|
||||
|
||||
module.exports = {batch, single, cancel}
|
||||
|
|
|
|||
46
lib/app.js
46
lib/app.js
|
|
@ -24,8 +24,8 @@ function run () {
|
|||
}
|
||||
|
||||
const runner = () => runOnce()
|
||||
.then(() => clearInterval(handler))
|
||||
.catch(errorHandler)
|
||||
.then(() => clearInterval(handler))
|
||||
.catch(errorHandler)
|
||||
|
||||
const handler = setInterval(runner, 10000)
|
||||
return runner()
|
||||
|
|
@ -33,35 +33,35 @@ function run () {
|
|||
|
||||
function runOnce () {
|
||||
return settingsLoader.loadLatest()
|
||||
.then(settings => {
|
||||
poller.start(settings)
|
||||
.then(settings => {
|
||||
poller.start(settings)
|
||||
|
||||
const httpsServerOptions = {
|
||||
key: fs.readFileSync(options.keyPath),
|
||||
cert: fs.readFileSync(options.certPath),
|
||||
requestCert: true,
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
const httpsServerOptions = {
|
||||
key: fs.readFileSync(options.keyPath),
|
||||
cert: fs.readFileSync(options.certPath),
|
||||
requestCert: true,
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
|
||||
const server = devMode
|
||||
? http.createServer(routes.app)
|
||||
: https.createServer(httpsServerOptions, routes.app)
|
||||
const server = devMode
|
||||
? http.createServer(routes.app)
|
||||
: https.createServer(httpsServerOptions, routes.app)
|
||||
|
||||
const port = argv.port || 3000
|
||||
const localPort = 3030
|
||||
const localServer = http.createServer(routes.localApp)
|
||||
const port = argv.port || 3000
|
||||
const localPort = 3030
|
||||
const localServer = http.createServer(routes.localApp)
|
||||
|
||||
if (options.devMode) logger.info('In dev mode')
|
||||
if (options.devMode) logger.info('In dev mode')
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log('lamassu-server listening on port ' +
|
||||
server.listen(port, () => {
|
||||
console.log('lamassu-server listening on port ' +
|
||||
port + ' ' + (devMode ? '(http)' : '(https)'))
|
||||
})
|
||||
})
|
||||
|
||||
localServer.listen(localPort, 'localhost', () => {
|
||||
console.log('lamassu-server listening on local port ' + localPort)
|
||||
localServer.listen(localPort, 'localhost', () => {
|
||||
console.log('lamassu-server listening on local port ' + localPort)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {run}
|
||||
|
|
|
|||
|
|
@ -13,36 +13,36 @@ module.exports = {run}
|
|||
|
||||
function run () {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
schema.groups.forEach(group => {
|
||||
return group.fields.forEach(fieldCode => {
|
||||
const field = schema.fields.find(r => r.code === fieldCode)
|
||||
if (!field) throw new Error('No such field: ' + fieldCode)
|
||||
if (_.isNil(field.default)) return
|
||||
if (group.machineScope === 'specific') return
|
||||
.then(() => {
|
||||
schema.groups.forEach(group => {
|
||||
return group.fields.forEach(fieldCode => {
|
||||
const field = schema.fields.find(r => r.code === fieldCode)
|
||||
if (!field) throw new Error('No such field: ' + fieldCode)
|
||||
if (_.isNil(field.default)) return
|
||||
if (group.machineScope === 'specific') return
|
||||
|
||||
const crypto = group.cryptoScope === 'specific'
|
||||
? DEFAULT_CRYPTO
|
||||
: 'global'
|
||||
const crypto = group.cryptoScope === 'specific'
|
||||
? DEFAULT_CRYPTO
|
||||
: 'global'
|
||||
|
||||
return newFields.push({
|
||||
fieldLocator: {
|
||||
fieldScope: {
|
||||
crypto,
|
||||
machine: 'global'
|
||||
return newFields.push({
|
||||
fieldLocator: {
|
||||
fieldScope: {
|
||||
crypto,
|
||||
machine: 'global'
|
||||
},
|
||||
code: fieldCode,
|
||||
fieldType: field.fieldType,
|
||||
fieldClass: field.fieldClass
|
||||
},
|
||||
code: fieldCode,
|
||||
fieldType: field.fieldType,
|
||||
fieldClass: field.fieldClass
|
||||
},
|
||||
fieldValue: {
|
||||
fieldType: field.fieldType,
|
||||
value: field.default
|
||||
}
|
||||
fieldValue: {
|
||||
fieldType: field.fieldType,
|
||||
value: field.default
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return settingsLoader.save(newFields)
|
||||
})
|
||||
return settingsLoader.save(newFields)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,5 @@ function run () {
|
|||
})
|
||||
|
||||
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'
|
||||
|
||||
return t.oneOrNone(sql, [machineTx.id])
|
||||
.then(row => {
|
||||
if (row && row.tx_version >= machineTx.txVersion) throw new E.StaleTxError('Stale tx')
|
||||
.then(row => {
|
||||
if (row && row.tx_version >= machineTx.txVersion) throw new E.StaleTxError('Stale tx')
|
||||
|
||||
return t.any(sql2, [machineTx.id])
|
||||
.then(billRows => {
|
||||
const dbTx = cashInLow.toObj(row)
|
||||
return t.any(sql2, [machineTx.id])
|
||||
.then(billRows => {
|
||||
const dbTx = cashInLow.toObj(row)
|
||||
|
||||
return preProcess(dbTx, machineTx, pi)
|
||||
.then(preProcessedTx => cashInLow.upsert(t, dbTx, preProcessedTx))
|
||||
.then(r => {
|
||||
return insertNewBills(t, billRows, machineTx)
|
||||
.then(newBills => _.set('newBills', newBills, r))
|
||||
})
|
||||
return preProcess(dbTx, machineTx, pi)
|
||||
.then(preProcessedTx => cashInLow.upsert(t, dbTx, preProcessedTx))
|
||||
.then(r => {
|
||||
return insertNewBills(t, billRows, machineTx)
|
||||
.then(newBills => _.set('newBills', newBills, r))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
transaction.txMode = tmSRD
|
||||
|
|
@ -48,7 +48,7 @@ function insertNewBills (t, billRows, machineTx) {
|
|||
const sql = pgp.helpers.insert(dbBills, columns, 'bills')
|
||||
|
||||
return t.none(sql)
|
||||
.then(() => bills)
|
||||
.then(() => bills)
|
||||
}
|
||||
|
||||
function pullNewBills (billRows, machineTx) {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ module.exports = {toObj, upsert, insert, update, massage, isClearToSend}
|
|||
|
||||
function convertBigNumFields (obj) {
|
||||
const convert = value => value && value.isBigNumber
|
||||
? value.toString()
|
||||
: value
|
||||
? value.toString()
|
||||
: value
|
||||
|
||||
return _.mapValues(convert, obj)
|
||||
}
|
||||
|
|
@ -45,18 +45,18 @@ function toObj (row) {
|
|||
function upsert (t, dbTx, preProcessedTx) {
|
||||
if (!dbTx) {
|
||||
return insert(t, preProcessedTx)
|
||||
.then(tx => ({dbTx, tx}))
|
||||
.then(tx => ({dbTx, tx}))
|
||||
}
|
||||
|
||||
return update(t, dbTx, diff(dbTx, preProcessedTx))
|
||||
.then(tx => ({dbTx, tx}))
|
||||
.then(tx => ({dbTx, tx}))
|
||||
}
|
||||
|
||||
function insert (t, tx) {
|
||||
const dbTx = massage(tx)
|
||||
const sql = pgp.helpers.insert(dbTx, null, 'cash_in_txs') + ' returning *'
|
||||
return t.one(sql)
|
||||
.then(toObj)
|
||||
.then(toObj)
|
||||
}
|
||||
|
||||
function update (t, tx, changes) {
|
||||
|
|
@ -67,7 +67,7 @@ function update (t, tx, changes) {
|
|||
pgp.as.format(' where id=$1', [tx.id]) + ' returning *'
|
||||
|
||||
return t.one(sql)
|
||||
.then(toObj)
|
||||
.then(toObj)
|
||||
}
|
||||
|
||||
function diff (oldTx, newTx) {
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@ module.exports = {post, monitorPending, cancel, PENDING_INTERVAL}
|
|||
|
||||
function post (machineTx, pi) {
|
||||
return db.tx(cashInAtomic.atomic(machineTx, pi))
|
||||
.then(r => {
|
||||
const updatedTx = r.tx
|
||||
.then(r => {
|
||||
const updatedTx = r.tx
|
||||
|
||||
return postProcess(r, pi)
|
||||
.then(changes => cashInLow.update(db, updatedTx, changes))
|
||||
.then(tx => _.set('bills', machineTx.bills, tx))
|
||||
})
|
||||
return postProcess(r, pi)
|
||||
.then(changes => cashInLow.update(db, updatedTx, changes))
|
||||
.then(tx => _.set('bills', machineTx.bills, tx))
|
||||
})
|
||||
}
|
||||
|
||||
function registerTrades (pi, newBills) {
|
||||
|
|
@ -41,7 +41,7 @@ function logAction (rec, tx) {
|
|||
const sql = pgp.helpers.insert(action, null, 'cash_in_actions')
|
||||
|
||||
return db.none(sql)
|
||||
.then(_.constant(rec))
|
||||
.then(_.constant(rec))
|
||||
}
|
||||
|
||||
function logActionById (action, _rec, txId) {
|
||||
|
|
@ -57,30 +57,30 @@ function postProcess (r, pi) {
|
|||
if (!cashInLow.isClearToSend(r.dbTx, r.tx)) return Promise.resolve({})
|
||||
|
||||
return pi.sendCoins(r.tx)
|
||||
.then(txHash => ({
|
||||
txHash,
|
||||
sendConfirmed: true,
|
||||
sendTime: 'now()^',
|
||||
sendPending: false,
|
||||
error: null,
|
||||
errorCode: null
|
||||
}))
|
||||
.catch(err => {
|
||||
.then(txHash => ({
|
||||
txHash,
|
||||
sendConfirmed: true,
|
||||
sendTime: 'now()^',
|
||||
sendPending: false,
|
||||
error: null,
|
||||
errorCode: null
|
||||
}))
|
||||
.catch(err => {
|
||||
// Important: We don't know what kind of error this is
|
||||
// so not safe to assume that funds weren't sent.
|
||||
// Therefore, don't set sendPending to false except for
|
||||
// errors (like InsufficientFundsError) that are guaranteed
|
||||
// not to send.
|
||||
const sendPending = err.name !== 'InsufficientFundsError'
|
||||
const sendPending = err.name !== 'InsufficientFundsError'
|
||||
|
||||
return {
|
||||
sendTime: 'now()^',
|
||||
error: err.message,
|
||||
errorCode: err.name,
|
||||
sendPending
|
||||
}
|
||||
})
|
||||
.then(sendRec => logAction(sendRec, r.tx))
|
||||
return {
|
||||
sendTime: 'now()^',
|
||||
error: err.message,
|
||||
errorCode: err.name,
|
||||
sendPending
|
||||
}
|
||||
})
|
||||
.then(sendRec => logAction(sendRec, r.tx))
|
||||
}
|
||||
|
||||
function monitorPending (settings) {
|
||||
|
|
@ -98,12 +98,12 @@ function monitorPending (settings) {
|
|||
const pi = plugins(settings, tx.deviceId)
|
||||
|
||||
return post(tx, pi)
|
||||
.catch(logger.error)
|
||||
.catch(logger.error)
|
||||
}
|
||||
|
||||
return db.any(sql, [PENDING_INTERVAL, MAX_PENDING])
|
||||
.then(rows => pEachSeries(rows, row => processPending(row)))
|
||||
.catch(logger.error)
|
||||
.then(rows => pEachSeries(rows, row => processPending(row)))
|
||||
.catch(logger.error)
|
||||
}
|
||||
|
||||
function cancel (txId) {
|
||||
|
|
@ -114,13 +114,13 @@ function cancel (txId) {
|
|||
}
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return pgp.helpers.update(updateRec, null, 'cash_in_txs') +
|
||||
.then(() => {
|
||||
return pgp.helpers.update(updateRec, null, 'cash_in_txs') +
|
||||
pgp.as.format(' where id=$1', [txId])
|
||||
})
|
||||
.then(sql => db.result(sql, false))
|
||||
.then(res => {
|
||||
if (res.rowCount !== 1) throw new Error('No such tx-id')
|
||||
})
|
||||
.then(() => logActionById('operatorCompleted', {}, txId))
|
||||
})
|
||||
.then(sql => db.result(sql, false))
|
||||
.then(res => {
|
||||
if (res.rowCount !== 1) throw new Error('No such tx-id')
|
||||
})
|
||||
.then(() => logActionById('operatorCompleted', {}, txId))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ function logAction (t, action, _rec, tx) {
|
|||
const sql = pgp.helpers.insert(rec, null, 'cash_out_actions')
|
||||
|
||||
return t.none(sql)
|
||||
.then(_.constant(tx))
|
||||
.then(_.constant(tx))
|
||||
}
|
||||
|
||||
function logError (t, action, err, tx) {
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ function atomic (tx, pi, fromClient) {
|
|||
const sql = 'select * from cash_out_txs where id=$1'
|
||||
|
||||
return t.oneOrNone(sql, [tx.id])
|
||||
.then(toObj)
|
||||
.then(oldTx => {
|
||||
const isStale = fromClient && oldTx && (oldTx.txVersion >= tx.txVersion)
|
||||
if (isStale) throw new E.StaleTxError('Stale tx')
|
||||
.then(toObj)
|
||||
.then(oldTx => {
|
||||
const isStale = fromClient && oldTx && (oldTx.txVersion >= tx.txVersion)
|
||||
if (isStale) throw new E.StaleTxError('Stale tx')
|
||||
|
||||
return preProcess(t, oldTx, tx, pi)
|
||||
.then(preProcessedTx => cashOutLow.upsert(t, oldTx, preProcessedTx))
|
||||
})
|
||||
return preProcess(t, oldTx, tx, pi)
|
||||
.then(preProcessedTx => cashOutLow.upsert(t, oldTx, preProcessedTx))
|
||||
})
|
||||
}
|
||||
|
||||
transaction.txMode = tmSRD
|
||||
|
|
@ -39,61 +39,61 @@ function atomic (tx, pi, fromClient) {
|
|||
function preProcess (t, oldTx, newTx, pi) {
|
||||
if (!oldTx) {
|
||||
return pi.isHd(newTx)
|
||||
.then(isHd => nextHd(t, isHd, newTx))
|
||||
.then(newTxHd => {
|
||||
return pi.newAddress(newTxHd)
|
||||
.then(_.set('toAddress', _, newTxHd))
|
||||
.then(_.unset('isLightning'))
|
||||
})
|
||||
.then(addressedTx => {
|
||||
const rec = {to_address: addressedTx.toAddress}
|
||||
return cashOutActions.logAction(t, 'provisionAddress', rec, addressedTx)
|
||||
})
|
||||
.catch(err => {
|
||||
return cashOutActions.logError(t, 'provisionAddress', err, newTx)
|
||||
.then(() => { throw err })
|
||||
})
|
||||
.then(isHd => nextHd(t, isHd, newTx))
|
||||
.then(newTxHd => {
|
||||
return pi.newAddress(newTxHd)
|
||||
.then(_.set('toAddress', _, newTxHd))
|
||||
.then(_.unset('isLightning'))
|
||||
})
|
||||
.then(addressedTx => {
|
||||
const rec = {to_address: addressedTx.toAddress}
|
||||
return cashOutActions.logAction(t, 'provisionAddress', rec, addressedTx)
|
||||
})
|
||||
.catch(err => {
|
||||
return cashOutActions.logError(t, 'provisionAddress', err, newTx)
|
||||
.then(() => { throw err })
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.resolve(updateStatus(oldTx, newTx))
|
||||
.then(updatedTx => {
|
||||
if (updatedTx.status !== oldTx.status) {
|
||||
const isZeroConf = pi.isZeroConf(updatedTx)
|
||||
if (wasJustAuthorized(oldTx, updatedTx, isZeroConf)) pi.sell(updatedTx)
|
||||
.then(updatedTx => {
|
||||
if (updatedTx.status !== oldTx.status) {
|
||||
const isZeroConf = pi.isZeroConf(updatedTx)
|
||||
if (wasJustAuthorized(oldTx, updatedTx, isZeroConf)) pi.sell(updatedTx)
|
||||
|
||||
const rec = {
|
||||
to_address: updatedTx.toAddress,
|
||||
tx_hash: updatedTx.txHash
|
||||
const rec = {
|
||||
to_address: updatedTx.toAddress,
|
||||
tx_hash: updatedTx.txHash
|
||||
}
|
||||
|
||||
return cashOutActions.logAction(t, updatedTx.status, rec, updatedTx)
|
||||
}
|
||||
|
||||
return cashOutActions.logAction(t, updatedTx.status, rec, updatedTx)
|
||||
}
|
||||
const hasError = !oldTx.error && newTx.error
|
||||
const hasDispenseOccurred = !dispenseOccurred(oldTx.bills) && dispenseOccurred(newTx.bills)
|
||||
|
||||
const hasError = !oldTx.error && newTx.error
|
||||
const hasDispenseOccurred = !dispenseOccurred(oldTx.bills) && dispenseOccurred(newTx.bills)
|
||||
if (hasError || hasDispenseOccurred) {
|
||||
return cashOutActions.logDispense(t, updatedTx)
|
||||
.then(updateCassettes(t, updatedTx))
|
||||
}
|
||||
|
||||
if (hasError || hasDispenseOccurred) {
|
||||
return cashOutActions.logDispense(t, updatedTx)
|
||||
.then(updateCassettes(t, updatedTx))
|
||||
}
|
||||
if (!oldTx.phone && newTx.phone) {
|
||||
return cashOutActions.logAction(t, 'addPhone', {}, updatedTx)
|
||||
}
|
||||
|
||||
if (!oldTx.phone && newTx.phone) {
|
||||
return cashOutActions.logAction(t, 'addPhone', {}, updatedTx)
|
||||
}
|
||||
if (!oldTx.redeem && newTx.redeem) {
|
||||
return cashOutActions.logAction(t, 'redeemLater', {}, updatedTx)
|
||||
}
|
||||
|
||||
if (!oldTx.redeem && newTx.redeem) {
|
||||
return cashOutActions.logAction(t, 'redeemLater', {}, updatedTx)
|
||||
}
|
||||
|
||||
return updatedTx
|
||||
})
|
||||
return updatedTx
|
||||
})
|
||||
}
|
||||
|
||||
function nextHd (t, isHd, tx) {
|
||||
if (!isHd) return Promise.resolve(tx)
|
||||
|
||||
return t.one("select nextval('hd_indices_seq') as hd_index")
|
||||
.then(row => _.set('hdIndex', row.hd_index, tx))
|
||||
.then(row => _.set('hdIndex', row.hd_index, tx))
|
||||
}
|
||||
|
||||
function updateCassettes (t, tx) {
|
||||
|
|
@ -112,7 +112,7 @@ function updateCassettes (t, tx) {
|
|||
]
|
||||
|
||||
return t.one(sql, values)
|
||||
.then(r => socket.emit(_.assign(r, {op: 'cassetteUpdate', deviceId: tx.deviceId})))
|
||||
.then(r => socket.emit(_.assign(r, {op: 'cassetteUpdate', deviceId: tx.deviceId})))
|
||||
}
|
||||
|
||||
function wasJustAuthorized (oldTx, newTx, isZeroConf) {
|
||||
|
|
@ -138,12 +138,12 @@ function updateStatus (oldTx, newTx) {
|
|||
const newStatus = ratchetStatus(oldStatus, newTx.status)
|
||||
|
||||
const publishedAt = !oldTx.publishedAt && isPublished(newStatus)
|
||||
? 'now()^'
|
||||
: undefined
|
||||
? 'now()^'
|
||||
: undefined
|
||||
|
||||
const confirmedAt = !oldTx.confirmedAt && isConfirmed(newStatus)
|
||||
? 'now()^'
|
||||
: undefined
|
||||
? 'now()^'
|
||||
: undefined
|
||||
|
||||
const updateRec = {
|
||||
publishedAt,
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ const mapValuesWithKey = _.mapValues.convert({cap: false})
|
|||
|
||||
function convertBigNumFields (obj) {
|
||||
const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat'])
|
||||
? value.toString()
|
||||
: value
|
||||
? value.toString()
|
||||
: value
|
||||
|
||||
const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat'])
|
||||
? key + '#'
|
||||
: key
|
||||
? key + '#'
|
||||
: key
|
||||
|
||||
return _.mapKeys(convertKey, mapValuesWithKey(convert, obj))
|
||||
}
|
||||
|
|
@ -91,5 +91,5 @@ function redeemableTxs (deviceId) {
|
|||
and (extract(epoch from (now() - greatest(created, confirmed_at))) * 1000) < $4`
|
||||
|
||||
return db.any(sql, [deviceId, true, false, REDEEMABLE_AGE])
|
||||
.then(_.map(toObj))
|
||||
.then(_.map(toObj))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ module.exports = {upsert, update, insert}
|
|||
function upsert (t, oldTx, tx) {
|
||||
if (!oldTx) {
|
||||
return insert(t, tx)
|
||||
.then(newTx => [oldTx, newTx])
|
||||
.then(newTx => [oldTx, newTx])
|
||||
}
|
||||
|
||||
return update(t, tx, diff(oldTx, tx))
|
||||
.then(newTx => [oldTx, newTx])
|
||||
.then(newTx => [oldTx, newTx])
|
||||
}
|
||||
|
||||
function insert (t, tx) {
|
||||
|
|
@ -26,7 +26,7 @@ function insert (t, tx) {
|
|||
|
||||
const sql = pgp.helpers.insert(dbTx, null, 'cash_out_txs') + ' returning *'
|
||||
return t.one(sql)
|
||||
.then(toObj)
|
||||
.then(toObj)
|
||||
}
|
||||
|
||||
function update (t, tx, changes) {
|
||||
|
|
@ -39,7 +39,7 @@ function update (t, tx, changes) {
|
|||
const newTx = _.merge(tx, changes)
|
||||
|
||||
return t.none(sql)
|
||||
.then(() => newTx)
|
||||
.then(() => newTx)
|
||||
}
|
||||
|
||||
function diff (oldTx, newTx) {
|
||||
|
|
|
|||
|
|
@ -43,11 +43,11 @@ function selfPost (tx, pi) {
|
|||
|
||||
function post (tx, pi, fromClient = true) {
|
||||
return db.tx(cashOutAtomic.atomic(tx, pi, fromClient))
|
||||
.then(txVector => {
|
||||
const [, newTx] = txVector
|
||||
return postProcess(txVector, pi)
|
||||
.then(changes => cashOutLow.update(db, newTx, changes))
|
||||
})
|
||||
.then(txVector => {
|
||||
const [, newTx] = txVector
|
||||
return postProcess(txVector, pi)
|
||||
.then(changes => cashOutLow.update(db, newTx, changes))
|
||||
})
|
||||
}
|
||||
|
||||
function postProcess (txVector, pi) {
|
||||
|
|
@ -55,32 +55,32 @@ function postProcess (txVector, pi) {
|
|||
|
||||
if ((newTx.dispense && !oldTx.dispense) || (newTx.redeem && !oldTx.redeem)) {
|
||||
return pi.buildAvailableCassettes(newTx.id)
|
||||
.then(cassettes => {
|
||||
const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat)
|
||||
.then(cassettes => {
|
||||
const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat)
|
||||
|
||||
if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE)
|
||||
return bills
|
||||
})
|
||||
.then(bills => {
|
||||
const provisioned1 = bills[0].provisioned
|
||||
const provisioned2 = bills[1].provisioned
|
||||
const denomination1 = bills[0].denomination
|
||||
const denomination2 = bills[1].denomination
|
||||
if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE)
|
||||
return bills
|
||||
})
|
||||
.then(bills => {
|
||||
const provisioned1 = bills[0].provisioned
|
||||
const provisioned2 = bills[1].provisioned
|
||||
const denomination1 = bills[0].denomination
|
||||
const denomination2 = bills[1].denomination
|
||||
|
||||
const rec = {
|
||||
provisioned_1: provisioned1,
|
||||
provisioned_2: provisioned2,
|
||||
denomination_1: denomination1,
|
||||
denomination_2: denomination2
|
||||
}
|
||||
const rec = {
|
||||
provisioned_1: provisioned1,
|
||||
provisioned_2: provisioned2,
|
||||
denomination_1: denomination1,
|
||||
denomination_2: denomination2
|
||||
}
|
||||
|
||||
return cashOutActions.logAction(db, 'provisionNotes', rec, newTx)
|
||||
.then(_.constant({bills}))
|
||||
})
|
||||
.catch(err => {
|
||||
return cashOutActions.logError(db, 'provisionNotesError', err, newTx)
|
||||
.then(() => { throw err })
|
||||
})
|
||||
return cashOutActions.logAction(db, 'provisionNotes', rec, newTx)
|
||||
.then(_.constant({bills}))
|
||||
})
|
||||
.catch(err => {
|
||||
return cashOutActions.logError(db, 'provisionNotesError', err, newTx)
|
||||
.then(() => { throw err })
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.resolve({})
|
||||
|
|
@ -95,31 +95,31 @@ function fetchOpenTxs (statuses, age) {
|
|||
const statusClause = _.map(pgp.as.text, statuses).join(',')
|
||||
|
||||
return db.any(sql, [age, statusClause])
|
||||
.then(rows => rows.map(toObj))
|
||||
.then(rows => rows.map(toObj))
|
||||
}
|
||||
|
||||
function processTxStatus (tx, settings) {
|
||||
const pi = plugins(settings, tx.deviceId)
|
||||
|
||||
return pi.getStatus(tx)
|
||||
.then(res => _.assign(tx, {status: res.status}))
|
||||
.then(_tx => selfPost(_tx, pi))
|
||||
.then(res => _.assign(tx, {status: res.status}))
|
||||
.then(_tx => selfPost(_tx, pi))
|
||||
}
|
||||
|
||||
function monitorLiveIncoming (settings) {
|
||||
const statuses = ['notSeen', 'published', 'insufficientFunds']
|
||||
|
||||
return fetchOpenTxs(statuses, STALE_LIVE_INCOMING_TX_AGE)
|
||||
.then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings)))
|
||||
.catch(logger.error)
|
||||
.then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings)))
|
||||
.catch(logger.error)
|
||||
}
|
||||
|
||||
function monitorStaleIncoming (settings) {
|
||||
const statuses = ['notSeen', 'published', 'authorized', 'instant', 'rejected', 'insufficientFunds']
|
||||
|
||||
return fetchOpenTxs(statuses, STALE_INCOMING_TX_AGE)
|
||||
.then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings)))
|
||||
.catch(logger.error)
|
||||
.then(txs => pEachSeries(txs, tx => processTxStatus(tx, settings)))
|
||||
.catch(logger.error)
|
||||
}
|
||||
|
||||
function monitorUnnotified (settings) {
|
||||
|
|
@ -133,9 +133,9 @@ function monitorUnnotified (settings) {
|
|||
|
||||
const notify = tx => plugins(settings, tx.deviceId).notifyConfirmation(tx)
|
||||
return db.any(sql, [MAX_NOTIFY_AGE, false, false, true, MIN_NOTIFY_AGE])
|
||||
.then(rows => _.map(toObj, rows))
|
||||
.then(txs => Promise.all(txs.map(notify)))
|
||||
.catch(logger.error)
|
||||
.then(rows => _.map(toObj, rows))
|
||||
.then(txs => Promise.all(txs.map(notify)))
|
||||
.catch(logger.error)
|
||||
}
|
||||
|
||||
function cancel (txId) {
|
||||
|
|
@ -146,13 +146,13 @@ function cancel (txId) {
|
|||
}
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return pgp.helpers.update(updateRec, null, 'cash_out_txs') +
|
||||
.then(() => {
|
||||
return pgp.helpers.update(updateRec, null, 'cash_out_txs') +
|
||||
pgp.as.format(' where id=$1', [txId])
|
||||
})
|
||||
.then(sql => db.result(sql, false))
|
||||
.then(res => {
|
||||
if (res.rowCount !== 1) throw new Error('No such tx-id')
|
||||
})
|
||||
.then(() => cashOutActions.logActionById(db, 'operatorCompleted', {}, txId))
|
||||
})
|
||||
.then(sql => db.result(sql, false))
|
||||
.then(res => {
|
||||
if (res.rowCount !== 1) throw new Error('No such tx-id')
|
||||
})
|
||||
.then(() => cashOutActions.logActionById(db, 'operatorCompleted', {}, txId))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,12 +66,12 @@ function satisfiesRequire (config, cryptos, machineList, field, anyFields, allFi
|
|||
function isScopeEnabled (config, cryptos, machineList, refField, scope) {
|
||||
const [cryptoScope, machineScope] = scope
|
||||
const candidateCryptoScopes = cryptoScope === 'global'
|
||||
? allCryptoScopes(cryptos, refField.cryptoScope)
|
||||
: [cryptoScope]
|
||||
? allCryptoScopes(cryptos, refField.cryptoScope)
|
||||
: [cryptoScope]
|
||||
|
||||
const candidateMachineScopes = machineScope === 'global'
|
||||
? allMachineScopes(machineList, refField.machineScope)
|
||||
: [ machineScope ]
|
||||
? allMachineScopes(machineList, refField.machineScope)
|
||||
: [ machineScope ]
|
||||
|
||||
const allRefCandidateScopes = allScopes(candidateCryptoScopes, candidateMachineScopes)
|
||||
const getFallbackValue = scope => configManager.scopedValue(scope[0], scope[1], refField.code, config)
|
||||
|
|
@ -108,13 +108,13 @@ function getMachines () {
|
|||
|
||||
function fetchMachines () {
|
||||
return getMachines()
|
||||
.then(machineList => machineList.map(r => r.device_id))
|
||||
.then(machineList => machineList.map(r => r.device_id))
|
||||
}
|
||||
|
||||
function validateFieldParameter (value, validator) {
|
||||
switch (validator.code) {
|
||||
case 'required':
|
||||
return true // We don't validate this here
|
||||
return true // We don't validate this here
|
||||
case 'min':
|
||||
return value >= validator.min
|
||||
case 'max':
|
||||
|
|
@ -128,58 +128,58 @@ function ensureConstraints (config) {
|
|||
const pickField = fieldCode => schema.fields.find(r => r.code === fieldCode)
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
config.every(fieldInstance => {
|
||||
const fieldCode = fieldInstance.fieldLocator.code
|
||||
const field = pickField(fieldCode)
|
||||
if (!field) {
|
||||
logger.warn('No such field: %s, %j', fieldCode, fieldInstance.fieldLocator.fieldScope)
|
||||
return
|
||||
}
|
||||
.then(() => {
|
||||
config.every(fieldInstance => {
|
||||
const fieldCode = fieldInstance.fieldLocator.code
|
||||
const field = pickField(fieldCode)
|
||||
if (!field) {
|
||||
logger.warn('No such field: %s, %j', fieldCode, fieldInstance.fieldLocator.fieldScope)
|
||||
return
|
||||
}
|
||||
|
||||
const fieldValue = fieldInstance.fieldValue
|
||||
const fieldValue = fieldInstance.fieldValue
|
||||
|
||||
const isValid = field.fieldValidation
|
||||
.every(validator => validateFieldParameter(fieldValue.value, validator))
|
||||
const isValid = field.fieldValidation
|
||||
.every(validator => validateFieldParameter(fieldValue.value, validator))
|
||||
|
||||
if (isValid) return true
|
||||
throw new Error('Invalid config value')
|
||||
if (isValid) return true
|
||||
throw new Error('Invalid config value')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const pp = require('./pp')
|
||||
|
||||
function validateRequires (config) {
|
||||
return fetchMachines()
|
||||
.then(machineList => {
|
||||
const cryptos = getCryptos(config, machineList)
|
||||
.then(machineList => {
|
||||
const cryptos = getCryptos(config, machineList)
|
||||
|
||||
return schema.groups.filter(group => {
|
||||
return group.fields.some(fieldCode => {
|
||||
const field = getGroupField(group, fieldCode)
|
||||
return schema.groups.filter(group => {
|
||||
return group.fields.some(fieldCode => {
|
||||
const field = getGroupField(group, fieldCode)
|
||||
|
||||
if (!field.fieldValidation.find(r => r.code === 'required')) return false
|
||||
if (!field.fieldValidation.find(r => r.code === 'required')) return false
|
||||
|
||||
const refFieldsAny = _.map(_.partial(getField, group), field.enabledIfAny)
|
||||
const refFieldsAll = _.map(_.partial(getField, group), field.enabledIfAll)
|
||||
const isInvalid = !satisfiesRequire(config, cryptos, machineList, field, refFieldsAny, refFieldsAll)
|
||||
const refFieldsAny = _.map(_.partial(getField, group), field.enabledIfAny)
|
||||
const refFieldsAll = _.map(_.partial(getField, group), field.enabledIfAll)
|
||||
const isInvalid = !satisfiesRequire(config, cryptos, machineList, field, refFieldsAny, refFieldsAll)
|
||||
|
||||
return isInvalid
|
||||
return isInvalid
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.then(arr => arr.map(r => r.code))
|
||||
.then(arr => arr.map(r => r.code))
|
||||
}
|
||||
|
||||
function validate (config) {
|
||||
return Promise.resolve()
|
||||
.then(() => ensureConstraints(config))
|
||||
.then(() => validateRequires(config))
|
||||
.then(arr => {
|
||||
if (arr.length === 0) return config
|
||||
throw new Error('Invalid configuration:' + arr)
|
||||
})
|
||||
.then(() => ensureConstraints(config))
|
||||
.then(() => validateRequires(config))
|
||||
.then(arr => {
|
||||
if (arr.length === 0) return config
|
||||
throw new Error('Invalid configuration:' + arr)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {validate, ensureConstraints, validateRequires}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ function add (customer) {
|
|||
function get (phone) {
|
||||
const sql = 'select * from customers where phone=$1'
|
||||
return db.oneOrNone(sql, [phone])
|
||||
.then(populateDailyVolume)
|
||||
.then(camelize)
|
||||
.then(populateDailyVolume)
|
||||
.then(camelize)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -64,11 +64,11 @@ function update (id, data, userToken) {
|
|||
' where id=$1 returning *'
|
||||
|
||||
return db.one(sql, [id])
|
||||
.then(addComplianceOverrides(id, updateData, userToken))
|
||||
.then(populateOverrideUsernames)
|
||||
.then(computeStatus)
|
||||
.then(populateDailyVolume)
|
||||
.then(camelize)
|
||||
.then(addComplianceOverrides(id, updateData, userToken))
|
||||
.then(populateOverrideUsernames)
|
||||
.then(computeStatus)
|
||||
.then(populateDailyVolume)
|
||||
.then(camelize)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -85,10 +85,10 @@ function update (id, data, userToken) {
|
|||
function getById (id, userToken) {
|
||||
const sql = 'select * from customers where id=$1'
|
||||
return db.oneOrNone(sql, [id])
|
||||
.then(populateOverrideUsernames)
|
||||
.then(computeStatus)
|
||||
.then(populateDailyVolume)
|
||||
.then(camelize)
|
||||
.then(populateOverrideUsernames)
|
||||
.then(computeStatus)
|
||||
.then(populateDailyVolume)
|
||||
.then(camelize)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -192,7 +192,7 @@ function enhanceAtFields (fields) {
|
|||
*/
|
||||
function enhanceOverrideFields (fields, userToken) {
|
||||
if (!userToken) return fields
|
||||
// Populate with computedFields (user who overrode and overriden timestamps date)
|
||||
// Populate with computedFields (user who overrode and overriden timestamps date)
|
||||
return _.reduce(_.assign, fields, _.map((type) => {
|
||||
return (fields[type + '_override'])
|
||||
? {
|
||||
|
|
@ -232,7 +232,7 @@ function addComplianceOverrides (id, customer, userToken) {
|
|||
|
||||
// Save all the updated override fields
|
||||
return Promise.all(_.map(complianceOverrides.add, _.compact(overrides)))
|
||||
.then(() => customer)
|
||||
.then(() => customer)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -295,15 +295,15 @@ function populateOverrideUsernames (customer) {
|
|||
const queryTokens = _.map('token', fieldsToUpdate)
|
||||
|
||||
return users.getByIds(queryTokens)
|
||||
.then(usersList => {
|
||||
return _.map(userField => {
|
||||
const user = _.find({token: userField.token}, usersList)
|
||||
return {
|
||||
[userField.field]: user ? user.name : null
|
||||
}
|
||||
}, fieldsToUpdate)
|
||||
})
|
||||
.then(_.reduce(_.assign, customer))
|
||||
.then(usersList => {
|
||||
return _.map(userField => {
|
||||
const user = _.find({token: userField.token}, usersList)
|
||||
return {
|
||||
[userField.field]: user ? user.name : null
|
||||
}
|
||||
}, fieldsToUpdate)
|
||||
})
|
||||
.then(_.reduce(_.assign, customer))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -320,12 +320,12 @@ function batch () {
|
|||
where id != $1
|
||||
order by created desc limit $2`
|
||||
return db.any(sql, [ anonymous.uuid, NUM_RESULTS ])
|
||||
.then(customers => Promise.all(_.map(customer => {
|
||||
return populateOverrideUsernames(customer)
|
||||
.then(computeStatus)
|
||||
.then(populateDailyVolume)
|
||||
.then(camelize)
|
||||
}, customers)))
|
||||
.then(customers => Promise.all(_.map(customer => {
|
||||
return populateOverrideUsernames(customer)
|
||||
.then(computeStatus)
|
||||
.then(populateDailyVolume)
|
||||
.then(camelize)
|
||||
}, customers)))
|
||||
}
|
||||
|
||||
module.exports = { add, get, batch, getById, update }
|
||||
|
|
|
|||
12
lib/email.js
12
lib/email.js
|
|
@ -3,13 +3,13 @@ const ph = require('./plugin-helper')
|
|||
|
||||
function sendMessage (settings, rec) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const pluginCode = configManager.unscoped(settings.config).email
|
||||
const plugin = ph.load(ph.EMAIL, pluginCode)
|
||||
const account = settings.accounts[pluginCode]
|
||||
.then(() => {
|
||||
const pluginCode = configManager.unscoped(settings.config).email
|
||||
const plugin = ph.load(ph.EMAIL, pluginCode)
|
||||
const account = settings.accounts[pluginCode]
|
||||
|
||||
return plugin.sendMessage(account, rec)
|
||||
})
|
||||
return plugin.sendMessage(account, rec)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {sendMessage}
|
||||
|
|
|
|||
|
|
@ -9,24 +9,24 @@ function lookupExchange (settings, cryptoCode) {
|
|||
|
||||
function fetchExchange (settings, cryptoCode) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const plugin = lookupExchange(settings, cryptoCode)
|
||||
if (!plugin) throw new Error('No exchange set')
|
||||
const exchange = ph.load(ph.EXCHANGE, plugin)
|
||||
const account = settings.accounts[plugin]
|
||||
.then(() => {
|
||||
const plugin = lookupExchange(settings, cryptoCode)
|
||||
if (!plugin) throw new Error('No exchange set')
|
||||
const exchange = ph.load(ph.EXCHANGE, plugin)
|
||||
const account = settings.accounts[plugin]
|
||||
|
||||
return {exchange, account}
|
||||
})
|
||||
return {exchange, account}
|
||||
})
|
||||
}
|
||||
|
||||
function buy (settings, cryptoAtoms, fiatCode, cryptoCode) {
|
||||
return fetchExchange(settings, cryptoCode)
|
||||
.then(r => r.exchange.buy(r.account, cryptoAtoms, fiatCode, cryptoCode))
|
||||
.then(r => r.exchange.buy(r.account, cryptoAtoms, fiatCode, cryptoCode))
|
||||
}
|
||||
|
||||
function sell (settings, cryptoAtoms, fiatCode, cryptoCode) {
|
||||
return fetchExchange(settings, cryptoCode)
|
||||
.then(r => r.exchange.sell(r.account, cryptoAtoms, fiatCode, cryptoCode))
|
||||
.then(r => r.exchange.sell(r.account, cryptoAtoms, fiatCode, cryptoCode))
|
||||
}
|
||||
|
||||
function active (settings, cryptoCode) {
|
||||
|
|
|
|||
20
lib/logs.js
20
lib/logs.js
|
|
@ -22,7 +22,7 @@ function getLastSeen (deviceId) {
|
|||
where device_id=$1
|
||||
order by timestamp desc, serial desc limit 1`
|
||||
return db.oneOrNone(sql, [deviceId])
|
||||
.then(log => log ? {timestamp: log.timestamp, serial: log.serial, id: log.id} : null)
|
||||
.then(log => log ? {timestamp: log.timestamp, serial: log.serial, id: log.id} : null)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -40,7 +40,7 @@ function getLastSeen (deviceId) {
|
|||
function update (deviceId, logLines) {
|
||||
const cs = new pgp.helpers.ColumnSet([
|
||||
'id', 'device_id', 'log_level', 'timestamp', 'serial', 'message'],
|
||||
{table: 'logs'})
|
||||
{table: 'logs'})
|
||||
|
||||
const logs = _.map(log => {
|
||||
const formatted = {
|
||||
|
|
@ -65,10 +65,10 @@ function getUnlimitedMachineLogs (deviceId, until = new Date().toISOString()) {
|
|||
order by timestamp asc, serial asc`
|
||||
|
||||
return Promise.all([db.any(sql, [ deviceId, until ]), getMachineName(deviceId)])
|
||||
.then(([logs, machineName]) => ({
|
||||
logs: _.map(_.mapKeys(_.camelCase), logs),
|
||||
currentMachine: {deviceId, name: machineName}
|
||||
}))
|
||||
.then(([logs, machineName]) => ({
|
||||
logs: _.map(_.mapKeys(_.camelCase), logs),
|
||||
currentMachine: {deviceId, name: machineName}
|
||||
}))
|
||||
}
|
||||
|
||||
function getMachineLogs (deviceId, until = new Date().toISOString()) {
|
||||
|
|
@ -79,10 +79,10 @@ function getMachineLogs (deviceId, until = new Date().toISOString()) {
|
|||
limit $2`
|
||||
|
||||
return Promise.all([db.any(sql, [ deviceId, NUM_RESULTS, until ]), getMachineName(deviceId)])
|
||||
.then(([logs, machineName]) => ({
|
||||
logs: _.map(_.mapKeys(_.camelCase), logs),
|
||||
currentMachine: {deviceId, name: machineName}
|
||||
}))
|
||||
.then(([logs, machineName]) => ({
|
||||
logs: _.map(_.mapKeys(_.camelCase), logs),
|
||||
currentMachine: {deviceId, name: machineName}
|
||||
}))
|
||||
}
|
||||
|
||||
module.exports = { getUnlimitedMachineLogs, getMachineLogs, update, getLastSeen }
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ module.exports = {getMachineName, getMachines, getMachineNames, setMachine}
|
|||
|
||||
function getMachines () {
|
||||
return db.any('select * from devices where display=TRUE order by created')
|
||||
.then(rr => rr.map(r => ({
|
||||
deviceId: r.device_id,
|
||||
cashbox: r.cashbox,
|
||||
cassette1: r.cassette1,
|
||||
cassette2: r.cassette2,
|
||||
paired: r.paired
|
||||
})))
|
||||
.then(rr => rr.map(r => ({
|
||||
deviceId: r.device_id,
|
||||
cashbox: r.cashbox,
|
||||
cassette1: r.cassette1,
|
||||
cassette2: r.cassette2,
|
||||
paired: r.paired
|
||||
})))
|
||||
}
|
||||
|
||||
function getConfig (defaultConfig) {
|
||||
|
|
@ -27,17 +27,17 @@ function getConfig (defaultConfig) {
|
|||
|
||||
function getMachineNames (config) {
|
||||
return Promise.all([getMachines(), getConfig(config)])
|
||||
.then(([machines, config]) => {
|
||||
const addName = r => {
|
||||
const machineScoped = configManager.machineScoped(r.deviceId, config)
|
||||
const name = machineScoped.machineName
|
||||
const cashOut = machineScoped.cashOutEnabled
|
||||
.then(([machines, config]) => {
|
||||
const addName = r => {
|
||||
const machineScoped = configManager.machineScoped(r.deviceId, config)
|
||||
const name = machineScoped.machineName
|
||||
const cashOut = machineScoped.cashOutEnabled
|
||||
|
||||
return _.assign(r, {name, cashOut})
|
||||
}
|
||||
return _.assign(r, {name, cashOut})
|
||||
}
|
||||
|
||||
return _.map(addName, machines)
|
||||
})
|
||||
return _.map(addName, machines)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -52,10 +52,10 @@ function getMachineNames (config) {
|
|||
*/
|
||||
function getMachineName (machineId) {
|
||||
return settingsLoader.loadRecentConfig()
|
||||
.then(config => {
|
||||
const machineScoped = configManager.machineScoped(machineId, config)
|
||||
return machineScoped.machineName
|
||||
})
|
||||
.then(config => {
|
||||
const machineScoped = configManager.machineScoped(machineId, config)
|
||||
return machineScoped.machineName
|
||||
})
|
||||
}
|
||||
|
||||
function resetCashOutBills (rec) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
return checkStatus(plugins)
|
||||
.then(alertRec => {
|
||||
const currentAlertFingerprint = buildAlertFingerprint(alertRec)
|
||||
if (!currentAlertFingerprint) {
|
||||
const inAlert = !!alertFingerprint
|
||||
alertFingerprint = null
|
||||
lastAlertTime = null
|
||||
if (inAlert) return sendNoAlerts(plugins)
|
||||
}
|
||||
|
||||
const alertChanged = currentAlertFingerprint === alertFingerprint &&
|
||||
lastAlertTime - Date.now() < ALERT_SEND_INTERVAL
|
||||
if (alertChanged) return
|
||||
|
||||
const subject = alertSubject(alertRec)
|
||||
const rec = {
|
||||
sms: {
|
||||
body: subject
|
||||
},
|
||||
email: {
|
||||
subject,
|
||||
body: printEmailAlerts(alertRec)
|
||||
.then(alertRec => {
|
||||
const currentAlertFingerprint = buildAlertFingerprint(alertRec)
|
||||
if (!currentAlertFingerprint) {
|
||||
const inAlert = !!alertFingerprint
|
||||
alertFingerprint = null
|
||||
lastAlertTime = null
|
||||
if (inAlert) return sendNoAlerts(plugins)
|
||||
}
|
||||
}
|
||||
alertFingerprint = currentAlertFingerprint
|
||||
lastAlertTime = Date.now()
|
||||
|
||||
return plugins.sendMessage(rec)
|
||||
})
|
||||
.then(results => {
|
||||
if (results && results.length > 0) logger.debug('Successfully sent alerts')
|
||||
})
|
||||
.catch(logger.error)
|
||||
const alertChanged = currentAlertFingerprint === alertFingerprint &&
|
||||
lastAlertTime - Date.now() < ALERT_SEND_INTERVAL
|
||||
if (alertChanged) return
|
||||
|
||||
const subject = alertSubject(alertRec)
|
||||
const rec = {
|
||||
sms: {
|
||||
body: subject
|
||||
},
|
||||
email: {
|
||||
subject,
|
||||
body: printEmailAlerts(alertRec)
|
||||
}
|
||||
}
|
||||
alertFingerprint = currentAlertFingerprint
|
||||
lastAlertTime = Date.now()
|
||||
|
||||
return plugins.sendMessage(rec)
|
||||
})
|
||||
.then(results => {
|
||||
if (results && results.length > 0) logger.debug('Successfully sent alerts')
|
||||
})
|
||||
.catch(logger.error)
|
||||
}
|
||||
|
||||
const getDeviceTime = _.flow(_.get('device_time'), Date.parse)
|
||||
|
||||
function dropRepeatsWith (comparator, arr) {
|
||||
const iteratee = (acc, val) => val === acc.last
|
||||
? acc
|
||||
: {arr: _.concat(acc.arr, val), last: val}
|
||||
? acc
|
||||
: {arr: _.concat(acc.arr, val), last: val}
|
||||
|
||||
return _.reduce(iteratee, {arr: []}, arr).arr
|
||||
}
|
||||
|
|
@ -135,11 +135,11 @@ function checkPing (deviceId) {
|
|||
limit 1`
|
||||
|
||||
return db.oneOrNone(sql, [deviceId])
|
||||
.then(row => {
|
||||
if (!row) return [{code: PING}]
|
||||
if (row.age > NETWORK_DOWN_TIME) return [{code: PING, age: row.age}]
|
||||
return []
|
||||
})
|
||||
.then(row => {
|
||||
if (!row) return [{code: PING}]
|
||||
if (row.age > NETWORK_DOWN_TIME) return [{code: PING, age: row.age}]
|
||||
return []
|
||||
})
|
||||
}
|
||||
|
||||
function checkPings (devices) {
|
||||
|
|
@ -147,37 +147,37 @@ function checkPings (devices) {
|
|||
const promises = _.map(checkPing, deviceIds)
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(_.zipObject(deviceIds))
|
||||
.then(_.zipObject(deviceIds))
|
||||
}
|
||||
|
||||
function checkStatus (plugins) {
|
||||
const alerts = {devices: {}, deviceNames: {}}
|
||||
|
||||
return Promise.all([plugins.checkBalances(), dbm.machineEvents(), plugins.getMachineNames()])
|
||||
.then(([balances, events, devices]) => {
|
||||
return checkPings(devices)
|
||||
.then(pings => {
|
||||
alerts.general = _.filter(r => !r.deviceId, balances)
|
||||
devices.forEach(function (device) {
|
||||
const deviceId = device.deviceId
|
||||
const deviceName = device.name
|
||||
const deviceEvents = events.filter(function (eventRow) {
|
||||
return eventRow.device_id === deviceId
|
||||
.then(([balances, events, devices]) => {
|
||||
return checkPings(devices)
|
||||
.then(pings => {
|
||||
alerts.general = _.filter(r => !r.deviceId, balances)
|
||||
devices.forEach(function (device) {
|
||||
const deviceId = device.deviceId
|
||||
const deviceName = device.name
|
||||
const deviceEvents = events.filter(function (eventRow) {
|
||||
return eventRow.device_id === deviceId
|
||||
})
|
||||
|
||||
const balanceAlerts = _.filter(['deviceId', deviceId], balances)
|
||||
const ping = pings[deviceId] || []
|
||||
const stuckScreen = checkStuckScreen(deviceEvents)
|
||||
|
||||
const deviceAlerts = _.isEmpty(ping) ? stuckScreen : ping
|
||||
|
||||
alerts.devices[deviceId] = _.concat(deviceAlerts, balanceAlerts)
|
||||
alerts.deviceNames[deviceId] = deviceName
|
||||
})
|
||||
|
||||
return alerts
|
||||
})
|
||||
|
||||
const balanceAlerts = _.filter(['deviceId', deviceId], balances)
|
||||
const ping = pings[deviceId] || []
|
||||
const stuckScreen = checkStuckScreen(deviceEvents)
|
||||
|
||||
const deviceAlerts = _.isEmpty(ping) ? stuckScreen : ping
|
||||
|
||||
alerts.devices[deviceId] = _.concat(deviceAlerts, balanceAlerts)
|
||||
alerts.deviceNames[deviceId] = deviceName
|
||||
})
|
||||
|
||||
return alerts
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function formatCurrency (num, code) {
|
||||
|
|
|
|||
|
|
@ -38,43 +38,43 @@ function removeDeviceConfig (deviceId) {
|
|||
function unpair (deviceId) {
|
||||
const sql = 'delete from devices where device_id=$1'
|
||||
return db.none(sql, [deviceId])
|
||||
.then(() => removeDeviceConfig(deviceId))
|
||||
.then(() => removeDeviceConfig(deviceId))
|
||||
}
|
||||
|
||||
function pair (token, deviceId, machineModel) {
|
||||
return pullToken(token)
|
||||
.then(r => {
|
||||
if (r.expired) return false
|
||||
.then(r => {
|
||||
if (r.expired) return false
|
||||
|
||||
const insertSql = `insert into devices (device_id, name) values ($1, $2)
|
||||
const insertSql = `insert into devices (device_id, name) values ($1, $2)
|
||||
on conflict (device_id)
|
||||
do update set paired=TRUE, display=TRUE`
|
||||
|
||||
return configureNewDevice(deviceId, r.name, machineModel)
|
||||
.then(() => db.none(insertSql, [deviceId, r.name]))
|
||||
.then(() => true)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.debug(err)
|
||||
return false
|
||||
})
|
||||
return configureNewDevice(deviceId, r.name, machineModel)
|
||||
.then(() => db.none(insertSql, [deviceId, r.name]))
|
||||
.then(() => true)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.debug(err)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function authorizeCaDownload (caToken) {
|
||||
return pullToken(caToken)
|
||||
.then(r => {
|
||||
if (r.expired) throw new Error('Expired')
|
||||
.then(r => {
|
||||
if (r.expired) throw new Error('Expired')
|
||||
|
||||
const caPath = options.caPath
|
||||
return readFile(caPath, {encoding: 'utf8'})
|
||||
})
|
||||
const caPath = options.caPath
|
||||
return readFile(caPath, {encoding: 'utf8'})
|
||||
})
|
||||
}
|
||||
|
||||
function isPaired (deviceId) {
|
||||
const sql = 'select device_id from devices where device_id=$1 and paired=TRUE'
|
||||
|
||||
return db.oneOrNone(sql, [deviceId])
|
||||
.then(row => row && row.device_id === deviceId)
|
||||
.then(row => row && row.device_id === deviceId)
|
||||
}
|
||||
|
||||
module.exports = {pair, unpair, authorizeCaDownload, isPaired}
|
||||
|
|
|
|||
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 cashOutCommission = _.isNil(cryptoConfig.cashOutCommission)
|
||||
? undefined
|
||||
: BN(1).add(BN(cryptoConfig.cashOutCommission).div(100))
|
||||
? undefined
|
||||
: BN(1).add(BN(cryptoConfig.cashOutCommission).div(100))
|
||||
|
||||
if (Date.now() - rateRec.timestamp > STALE_TICKER) return logger.warn('Stale rate for ' + cryptoCode)
|
||||
const rate = rateRec.rates
|
||||
|
|
@ -131,34 +131,34 @@ function plugins (settings, deviceId) {
|
|||
const virtualCassettes = [config.virtualCashOutDenomination]
|
||||
|
||||
return Promise.all([dbm.cassetteCounts(deviceId), cashOutHelper.redeemableTxs(deviceId, excludeTxId)])
|
||||
.then(([rec, _redeemableTxs]) => {
|
||||
const redeemableTxs = _.reject(_.matchesProperty('id', excludeTxId), _redeemableTxs)
|
||||
.then(([rec, _redeemableTxs]) => {
|
||||
const redeemableTxs = _.reject(_.matchesProperty('id', excludeTxId), _redeemableTxs)
|
||||
|
||||
const counts = argv.cassettes
|
||||
? argv.cassettes.split(',')
|
||||
: rec.counts
|
||||
const counts = argv.cassettes
|
||||
? argv.cassettes.split(',')
|
||||
: rec.counts
|
||||
|
||||
const cassettes = [
|
||||
{
|
||||
denomination: parseInt(denominations[0], 10),
|
||||
count: parseInt(counts[0], 10)
|
||||
},
|
||||
{
|
||||
denomination: parseInt(denominations[1], 10),
|
||||
count: parseInt(counts[1], 10)
|
||||
const cassettes = [
|
||||
{
|
||||
denomination: parseInt(denominations[0], 10),
|
||||
count: parseInt(counts[0], 10)
|
||||
},
|
||||
{
|
||||
denomination: parseInt(denominations[1], 10),
|
||||
count: parseInt(counts[1], 10)
|
||||
}
|
||||
]
|
||||
|
||||
try {
|
||||
return {
|
||||
cassettes: computeAvailableCassettes(cassettes, redeemableTxs),
|
||||
virtualCassettes
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
return {cassettes, virtualCassettes}
|
||||
}
|
||||
]
|
||||
|
||||
try {
|
||||
return {
|
||||
cassettes: computeAvailableCassettes(cassettes, redeemableTxs),
|
||||
virtualCassettes
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
return {cassettes, virtualCassettes}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function fetchCurrentConfigVersion () {
|
||||
|
|
@ -169,7 +169,7 @@ function plugins (settings, deviceId) {
|
|||
limit 1`
|
||||
|
||||
return db.one(sql, ['config'])
|
||||
.then(row => row.id)
|
||||
.then(row => row.id)
|
||||
}
|
||||
|
||||
function mapCoinSettings (coinParams) {
|
||||
|
|
@ -207,23 +207,23 @@ function plugins (settings, deviceId) {
|
|||
].concat(tickerPromises, balancePromises, testnetPromises)
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(arr => {
|
||||
const cassettes = arr[0]
|
||||
const configVersion = arr[2]
|
||||
const cryptoCodesCount = cryptoCodes.length
|
||||
const tickers = arr.slice(3, cryptoCodesCount + 3)
|
||||
const balances = arr.slice(cryptoCodesCount + 3, 2 * cryptoCodesCount + 3)
|
||||
const testNets = arr.slice(2 * cryptoCodesCount + 3)
|
||||
const coinParams = _.zip(cryptoCodes, testNets)
|
||||
.then(arr => {
|
||||
const cassettes = arr[0]
|
||||
const configVersion = arr[2]
|
||||
const cryptoCodesCount = cryptoCodes.length
|
||||
const tickers = arr.slice(3, cryptoCodesCount + 3)
|
||||
const balances = arr.slice(cryptoCodesCount + 3, 2 * cryptoCodesCount + 3)
|
||||
const testNets = arr.slice(2 * cryptoCodesCount + 3)
|
||||
const coinParams = _.zip(cryptoCodes, testNets)
|
||||
|
||||
return {
|
||||
cassettes,
|
||||
rates: buildRates(tickers),
|
||||
balances: buildBalances(balances),
|
||||
coins: _.map(mapCoinSettings, coinParams),
|
||||
configVersion
|
||||
}
|
||||
})
|
||||
return {
|
||||
cassettes,
|
||||
rates: buildRates(tickers),
|
||||
balances: buildBalances(balances),
|
||||
coins: _.map(mapCoinSettings, coinParams),
|
||||
configVersion
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function sendCoins (tx) {
|
||||
|
|
@ -275,26 +275,26 @@ function plugins (settings, deviceId) {
|
|||
ticker.getRates(settings, fiatCode, cryptoCode),
|
||||
wallet.balance(settings, cryptoCode)
|
||||
])
|
||||
.then(([rates, balanceRec]) => {
|
||||
if (!rates || !balanceRec) return null
|
||||
.then(([rates, balanceRec]) => {
|
||||
if (!rates || !balanceRec) return null
|
||||
|
||||
const rawRate = rates.rates.ask
|
||||
const cashInCommission = BN(1).minus(BN(config.cashInCommission).div(100))
|
||||
const balance = balanceRec.balance
|
||||
const rawRate = rates.rates.ask
|
||||
const cashInCommission = BN(1).minus(BN(config.cashInCommission).div(100))
|
||||
const balance = balanceRec.balance
|
||||
|
||||
if (!rawRate || !balance) return null
|
||||
if (!rawRate || !balance) return null
|
||||
|
||||
const rate = rawRate.div(cashInCommission)
|
||||
const rate = rawRate.div(cashInCommission)
|
||||
|
||||
const lowBalanceMargin = BN(1)
|
||||
const lowBalanceMargin = BN(1)
|
||||
|
||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
||||
const unitScale = cryptoRec.unitScale
|
||||
const shiftedRate = rate.shift(-unitScale)
|
||||
const fiatTransferBalance = balance.mul(shiftedRate).div(lowBalanceMargin)
|
||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
||||
const unitScale = cryptoRec.unitScale
|
||||
const shiftedRate = rate.shift(-unitScale)
|
||||
const fiatTransferBalance = balance.mul(shiftedRate).div(lowBalanceMargin)
|
||||
|
||||
return {timestamp: balanceRec.timestamp, balance: fiatTransferBalance.truncated().toString()}
|
||||
})
|
||||
return {timestamp: balanceRec.timestamp, balance: fiatTransferBalance.truncated().toString()}
|
||||
})
|
||||
}
|
||||
|
||||
function notifyConfirmation (tx) {
|
||||
|
|
@ -310,17 +310,17 @@ function plugins (settings, deviceId) {
|
|||
}
|
||||
|
||||
return sms.sendMessage(settings, rec)
|
||||
.then(() => {
|
||||
const sql = 'update cash_out_txs set notified=$1 where id=$2'
|
||||
const values = [true, tx.id]
|
||||
.then(() => {
|
||||
const sql = 'update cash_out_txs set notified=$1 where id=$2'
|
||||
const values = [true, tx.id]
|
||||
|
||||
return db.none(sql, values)
|
||||
})
|
||||
return db.none(sql, values)
|
||||
})
|
||||
}
|
||||
|
||||
function pong () {
|
||||
db.none('insert into server_events (event_type) values ($1)', ['ping'])
|
||||
.catch(logger.error)
|
||||
.catch(logger.error)
|
||||
}
|
||||
|
||||
function pongClear () {
|
||||
|
|
@ -329,7 +329,7 @@ function plugins (settings, deviceId) {
|
|||
and created < now() - interval $2`
|
||||
|
||||
db.none(sql, ['ping', PONG_TTL])
|
||||
.catch(logger.error)
|
||||
.catch(logger.error)
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -375,9 +375,9 @@ function plugins (settings, deviceId) {
|
|||
const t1 = Date.now()
|
||||
|
||||
const filtered = marketTradesQueues
|
||||
.filter(tradeEntry => {
|
||||
return t1 - tradeEntry.timestamp < TRADE_TTL
|
||||
})
|
||||
.filter(tradeEntry => {
|
||||
return t1 - tradeEntry.timestamp < TRADE_TTL
|
||||
})
|
||||
|
||||
const filteredCount = marketTradesQueues.length - filtered.length
|
||||
|
||||
|
|
@ -389,7 +389,7 @@ function plugins (settings, deviceId) {
|
|||
if (filtered.length === 0) return null
|
||||
|
||||
const cryptoAtoms = filtered
|
||||
.reduce((prev, current) => prev.plus(current.cryptoAtoms), BN(0))
|
||||
.reduce((prev, current) => prev.plus(current.cryptoAtoms), BN(0))
|
||||
|
||||
const timestamp = filtered.map(r => r.timestamp).reduce((acc, r) => Math.max(acc, r), 0)
|
||||
|
||||
|
|
@ -408,22 +408,22 @@ function plugins (settings, deviceId) {
|
|||
|
||||
function executeTrades () {
|
||||
return machineLoader.getMachines()
|
||||
.then(devices => {
|
||||
const deviceIds = devices.map(device => device.deviceId)
|
||||
const lists = deviceIds.map(deviceId => {
|
||||
const config = configManager.machineScoped(deviceId, settings.config)
|
||||
const fiatCode = config.fiatCurrency
|
||||
const cryptoCodes = config.cryptoCurrencies
|
||||
.then(devices => {
|
||||
const deviceIds = devices.map(device => device.deviceId)
|
||||
const lists = deviceIds.map(deviceId => {
|
||||
const config = configManager.machineScoped(deviceId, settings.config)
|
||||
const fiatCode = config.fiatCurrency
|
||||
const cryptoCodes = config.cryptoCurrencies
|
||||
|
||||
return cryptoCodes.map(cryptoCode => ({fiatCode, cryptoCode}))
|
||||
return cryptoCodes.map(cryptoCode => ({fiatCode, cryptoCode}))
|
||||
})
|
||||
|
||||
const tradesPromises = _.uniq(_.flatten(lists))
|
||||
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode))
|
||||
|
||||
return Promise.all(tradesPromises)
|
||||
})
|
||||
|
||||
const tradesPromises = _.uniq(_.flatten(lists))
|
||||
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode))
|
||||
|
||||
return Promise.all(tradesPromises)
|
||||
})
|
||||
.catch(logger.error)
|
||||
.catch(logger.error)
|
||||
}
|
||||
|
||||
function executeTradesForMarket (settings, fiatCode, cryptoCode) {
|
||||
|
|
@ -435,11 +435,11 @@ function plugins (settings, deviceId) {
|
|||
if (tradeEntry === null || tradeEntry.cryptoAtoms.eq(0)) return
|
||||
|
||||
return executeTradeForType(tradeEntry)
|
||||
.catch(err => {
|
||||
tradesQueues[market].push(tradeEntry)
|
||||
if (err.name === 'orderTooSmall') return logger.debug(err.message)
|
||||
logger.error(err)
|
||||
})
|
||||
.catch(err => {
|
||||
tradesQueues[market].push(tradeEntry)
|
||||
if (err.name === 'orderTooSmall') return logger.debug(err.message)
|
||||
logger.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
function executeTradeForType (_tradeEntry) {
|
||||
|
|
@ -452,17 +452,17 @@ function plugins (settings, deviceId) {
|
|||
const execute = tradeEntry.type === 'buy' ? exchange.buy : exchange.sell
|
||||
|
||||
return execute(settings, tradeEntry.cryptoAtoms, tradeEntry.fiatCode, tradeEntry.cryptoCode)
|
||||
.then(() => recordTrade(tradeEntry))
|
||||
.then(() => recordTrade(tradeEntry))
|
||||
}
|
||||
|
||||
function convertBigNumFields (obj) {
|
||||
const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat'])
|
||||
? value.toString()
|
||||
: value
|
||||
? value.toString()
|
||||
: value
|
||||
|
||||
const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat'])
|
||||
? key + '#'
|
||||
: key
|
||||
? key + '#'
|
||||
: key
|
||||
|
||||
return _.mapKeys(convertKey, mapValuesWithKey(convert, obj))
|
||||
}
|
||||
|
|
@ -501,18 +501,28 @@ function plugins (settings, deviceId) {
|
|||
const cashOutEnabled = config.cashOutEnabled
|
||||
|
||||
const cashInAlert = device.cashbox > config.cashInAlertThreshold
|
||||
? {code: 'CASH_BOX_FULL', machineName, deviceId: device.deviceId, notes: device.cashbox}
|
||||
: null
|
||||
? {code: 'CASH_BOX_FULL', machineName, deviceId: device.deviceId, notes: device.cashbox}
|
||||
: null
|
||||
|
||||
const cassette1Alert = cashOutEnabled && device.cassette1 < config.cashOutCassette1AlertThreshold
|
||||
? {code: 'LOW_CASH_OUT', cassette: 1, machineName, deviceId: device.deviceId,
|
||||
notes: device.cassette1, denomination: denomination1, fiatCode}
|
||||
: null
|
||||
? {code: 'LOW_CASH_OUT',
|
||||
cassette: 1,
|
||||
machineName,
|
||||
deviceId: device.deviceId,
|
||||
notes: device.cassette1,
|
||||
denomination: denomination1,
|
||||
fiatCode}
|
||||
: null
|
||||
|
||||
const cassette2Alert = cashOutEnabled && device.cassette2 < config.cashOutCassette2AlertThreshold
|
||||
? {code: 'LOW_CASH_OUT', cassette: 2, machineName, deviceId: device.deviceId,
|
||||
notes: device.cassette2, denomination: denomination2, fiatCode}
|
||||
: null
|
||||
? {code: 'LOW_CASH_OUT',
|
||||
cassette: 2,
|
||||
machineName,
|
||||
deviceId: device.deviceId,
|
||||
notes: device.cassette2,
|
||||
denomination: denomination2,
|
||||
fiatCode}
|
||||
: null
|
||||
|
||||
return _.compact([cashInAlert, cassette1Alert, cassette2Alert])
|
||||
}
|
||||
|
|
@ -530,7 +540,7 @@ function plugins (settings, deviceId) {
|
|||
const checkCryptoBalanceWithFiat = _.partial(checkCryptoBalance, [fiatCode])
|
||||
|
||||
return Promise.all(fiatBalancePromises(cryptoCodes))
|
||||
.then(balances => _.map(checkCryptoBalanceWithFiat, _.zip(cryptoCodes, balances)))
|
||||
.then(balances => _.map(checkCryptoBalanceWithFiat, _.zip(cryptoCodes, balances)))
|
||||
}
|
||||
|
||||
function checkCryptoBalance (fiatCode, rec) {
|
||||
|
|
@ -542,8 +552,8 @@ function plugins (settings, deviceId) {
|
|||
const cryptoAlertThreshold = config.cryptoAlertThreshold
|
||||
|
||||
return BN(fiatBalance.balance).lt(cryptoAlertThreshold)
|
||||
? {code: 'LOW_CRYPTO_BALANCE', cryptoCode, fiatBalance, fiatCode}
|
||||
: null
|
||||
? {code: 'LOW_CRYPTO_BALANCE', cryptoCode, fiatBalance, fiatCode}
|
||||
: null
|
||||
}
|
||||
|
||||
function checkBalances () {
|
||||
|
|
@ -551,13 +561,13 @@ function plugins (settings, deviceId) {
|
|||
const fiatCode = globalConfig.fiatCurrency
|
||||
|
||||
return machineLoader.getMachines()
|
||||
.then(devices => {
|
||||
return Promise.all([
|
||||
checkCryptoBalances(fiatCode, devices),
|
||||
checkDevicesCashBalances(fiatCode, devices)
|
||||
])
|
||||
.then(_.flow(_.flattenDeep, _.compact))
|
||||
})
|
||||
.then(devices => {
|
||||
return Promise.all([
|
||||
checkCryptoBalances(fiatCode, devices),
|
||||
checkDevicesCashBalances(fiatCode, devices)
|
||||
])
|
||||
.then(_.flow(_.flattenDeep, _.compact))
|
||||
})
|
||||
}
|
||||
|
||||
function randomCode () {
|
||||
|
|
@ -566,8 +576,8 @@ function plugins (settings, deviceId) {
|
|||
|
||||
function getPhoneCode (phone) {
|
||||
const code = argv.mockSms
|
||||
? '123'
|
||||
: randomCode()
|
||||
? '123'
|
||||
: randomCode()
|
||||
|
||||
const rec = {
|
||||
sms: {
|
||||
|
|
@ -577,24 +587,24 @@ function plugins (settings, deviceId) {
|
|||
}
|
||||
|
||||
return sms.sendMessage(settings, rec)
|
||||
.then(() => code)
|
||||
.then(() => code)
|
||||
}
|
||||
|
||||
function sweepHdRow (row) {
|
||||
const cryptoCode = row.crypto_code
|
||||
|
||||
return wallet.sweep(settings, cryptoCode, row.hd_index)
|
||||
.then(txHash => {
|
||||
if (txHash) {
|
||||
logger.debug('[%s] Swept address with tx: %s', cryptoCode, txHash)
|
||||
.then(txHash => {
|
||||
if (txHash) {
|
||||
logger.debug('[%s] Swept address with tx: %s', cryptoCode, txHash)
|
||||
|
||||
const sql = `update cash_out_txs set swept='t'
|
||||
const sql = `update cash_out_txs set swept='t'
|
||||
where id=$1`
|
||||
|
||||
return db.none(sql, row.id)
|
||||
}
|
||||
})
|
||||
.catch(err => logger.error('[%s] Sweep error: %s', cryptoCode, err.message))
|
||||
return db.none(sql, row.id)
|
||||
}
|
||||
})
|
||||
.catch(err => logger.error('[%s] Sweep error: %s', cryptoCode, err.message))
|
||||
}
|
||||
|
||||
function sweepHd () {
|
||||
|
|
@ -602,8 +612,8 @@ function plugins (settings, deviceId) {
|
|||
where hd_index is not null and not swept and status in ('confirmed', 'instant')`
|
||||
|
||||
return db.any(sql)
|
||||
.then(rows => Promise.all(rows.map(sweepHdRow)))
|
||||
.catch(err => logger.error(err))
|
||||
.then(rows => Promise.all(rows.map(sweepHdRow)))
|
||||
.catch(err => logger.error(err))
|
||||
}
|
||||
|
||||
function getMachineNames () {
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ function authRequest (config, path, data) {
|
|||
const msg = [nonce, config.clientId, config.key].join('')
|
||||
|
||||
const signature = crypto
|
||||
.createHmac('sha256', Buffer.from(config.secret))
|
||||
.update(msg)
|
||||
.digest('hex')
|
||||
.toUpperCase()
|
||||
.createHmac('sha256', Buffer.from(config.secret))
|
||||
.update(msg)
|
||||
.digest('hex')
|
||||
.toUpperCase()
|
||||
|
||||
const signedData = _.merge(data, {
|
||||
key: config.key,
|
||||
|
|
@ -76,7 +76,7 @@ function request (path, method, data) {
|
|||
if (data) options.data = querystring.stringify(data)
|
||||
|
||||
return axios(options)
|
||||
.then(r => r.data)
|
||||
.then(r => r.data)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -18,17 +18,17 @@ function fetch (account, method, params) {
|
|||
url: `http://localhost:${account.port}`,
|
||||
data
|
||||
})
|
||||
.then(r => {
|
||||
if (r.error) throw r.error
|
||||
return r.data.result
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err.message)
|
||||
try {
|
||||
console.log(err.response.data.error)
|
||||
} catch (__) {}
|
||||
throw err
|
||||
})
|
||||
.then(r => {
|
||||
if (r.error) throw r.error
|
||||
return r.data.result
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err.message)
|
||||
try {
|
||||
console.log(err.response.data.error)
|
||||
} catch (__) {}
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function split (str) {
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ function trade (type, account, cryptoAtoms, _fiatCode, cryptoCode) {
|
|||
const options = {amount: cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8)}
|
||||
|
||||
return common.authRequest(account, '/' + type + '/market/' + market, options)
|
||||
.catch(e => {
|
||||
if (e.response) handleErrors(e.response.data)
|
||||
throw e
|
||||
})
|
||||
.then(handleErrors)
|
||||
.catch(e => {
|
||||
if (e.response) handleErrors(e.response.data)
|
||||
throw e
|
||||
})
|
||||
.then(handleErrors)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ function trade (account, type, cryptoAtoms, fiatCode, cryptoCode) {
|
|||
const amount = common.toUnit(cryptoAtoms, cryptoCode)
|
||||
const amountStr = amount.toFixed(6)
|
||||
|
||||
const pair = _.includes(fiatCode, ['USD', 'EUR'])
|
||||
? PAIRS[cryptoCode][fiatCode]
|
||||
: PAIRS[cryptoCode]['EUR']
|
||||
|
||||
const pair = _.includes(fiatCode, ['USD', 'EUR'])
|
||||
? PAIRS[cryptoCode][fiatCode]
|
||||
: PAIRS[cryptoCode]['EUR']
|
||||
|
||||
var orderInfo = {
|
||||
pair,
|
||||
type,
|
||||
|
|
|
|||
|
|
@ -18,15 +18,15 @@ function sendMessage (account, rec) {
|
|||
}
|
||||
|
||||
return client.messages.create(opts)
|
||||
.catch(err => {
|
||||
if (_.includes(err.code, BAD_NUMBER_CODES)) {
|
||||
const badNumberError = new Error(err.message)
|
||||
badNumberError.name = 'BadNumberError'
|
||||
throw badNumberError
|
||||
}
|
||||
.catch(err => {
|
||||
if (_.includes(err.code, BAD_NUMBER_CODES)) {
|
||||
const badNumberError = new Error(err.message)
|
||||
badNumberError.name = 'BadNumberError'
|
||||
throw badNumberError
|
||||
}
|
||||
|
||||
throw new Error(err.message)
|
||||
})
|
||||
throw new Error(err.message)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -2,18 +2,17 @@ const axios = require('axios')
|
|||
const BN = require('../../../bn')
|
||||
|
||||
function ticker (account, fiatCode, cryptoCode) {
|
||||
|
||||
return axios.get('https://bitpay.com/api/rates/' + cryptoCode + '/' + fiatCode)
|
||||
.then(r => {
|
||||
const data = r.data
|
||||
const price = BN(data.rate)
|
||||
return {
|
||||
rates: {
|
||||
ask: price,
|
||||
bid: price
|
||||
.then(r => {
|
||||
const data = r.data
|
||||
const price = BN(data.rate)
|
||||
return {
|
||||
rates: {
|
||||
ask: price,
|
||||
bid: price
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ const common = require('../../common/bitstamp')
|
|||
|
||||
function ticker (account, fiatCode, cryptoCode) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const market = common.buildMarket(fiatCode, cryptoCode)
|
||||
return common.request('/ticker/' + market, 'GET')
|
||||
})
|
||||
.then(r => ({
|
||||
rates: {
|
||||
ask: BN(r.ask),
|
||||
bid: BN(r.bid)
|
||||
}
|
||||
}))
|
||||
.then(() => {
|
||||
const market = common.buildMarket(fiatCode, cryptoCode)
|
||||
return common.request('/ticker/' + market, 'GET')
|
||||
})
|
||||
.then(r => ({
|
||||
rates: {
|
||||
ask: BN(r.ask),
|
||||
bid: BN(r.bid)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ function getBuyPrice (obj) {
|
|||
url: `https://api.coinbase.com/v2/prices/${currencyPair}/buy`,
|
||||
headers: {'CB-Version': '2017-07-10'}
|
||||
})
|
||||
.then(r => r.data)
|
||||
.then(r => r.data)
|
||||
}
|
||||
|
||||
function getSellPrice (obj) {
|
||||
|
|
@ -22,34 +22,33 @@ function getSellPrice (obj) {
|
|||
url: `https://api.coinbase.com/v2/prices/${currencyPair}/sell`,
|
||||
headers: {'CB-Version': '2017-07-10'}
|
||||
})
|
||||
.then(r => r.data)
|
||||
.then(r => r.data)
|
||||
}
|
||||
|
||||
function ticker (account, fiatCode, cryptoCode) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) {
|
||||
throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
const currencyPair = `${cryptoCode}-${fiatCode}`
|
||||
const promises = [
|
||||
getBuyPrice({currencyPair}),
|
||||
getSellPrice({currencyPair})
|
||||
]
|
||||
.then(() => {
|
||||
if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) {
|
||||
throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
const currencyPair = `${cryptoCode}-${fiatCode}`
|
||||
const promises = [
|
||||
getBuyPrice({currencyPair}),
|
||||
getSellPrice({currencyPair})
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([buyPrice, sellPrice]) => ({
|
||||
rates: {
|
||||
ask: BN(buyPrice.data.amount),
|
||||
bid: BN(sellPrice.data.amount)
|
||||
}
|
||||
}))
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([buyPrice, sellPrice]) => ({
|
||||
rates: {
|
||||
ask: BN(buyPrice.data.amount),
|
||||
bid: BN(sellPrice.data.amount)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ticker
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,32 +21,32 @@ exports.ticker = function ticker (account, fiatCode, cryptoCode) {
|
|||
}
|
||||
|
||||
return axios.get('https://bitpay.com/api/rates')
|
||||
.then(response => {
|
||||
const fxRates = response.data
|
||||
const usdRate = findCurrency(fxRates, 'USD')
|
||||
const fxRate = findCurrency(fxRates, fiatCode).div(usdRate)
|
||||
.then(response => {
|
||||
const fxRates = response.data
|
||||
const usdRate = findCurrency(fxRates, 'USD')
|
||||
const fxRate = findCurrency(fxRates, fiatCode).div(usdRate)
|
||||
|
||||
return getCurrencyRates('USD', cryptoCode)
|
||||
.then(res => ({
|
||||
rates: {
|
||||
ask: res.rates.ask.times(fxRate),
|
||||
bid: res.rates.bid.times(fxRate)
|
||||
}
|
||||
}))
|
||||
})
|
||||
return getCurrencyRates('USD', cryptoCode)
|
||||
.then(res => ({
|
||||
rates: {
|
||||
ask: res.rates.ask.times(fxRate),
|
||||
bid: res.rates.bid.times(fxRate)
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function getCurrencyRates (fiatCode, cryptoCode) {
|
||||
const pair = PAIRS[cryptoCode][fiatCode]
|
||||
|
||||
return axios.get('https://api.kraken.com/0/public/Ticker?pair=' + pair)
|
||||
.then(function (response) {
|
||||
const rates = response.data.result[pair]
|
||||
return {
|
||||
rates: {
|
||||
ask: BN(rates.a[0]),
|
||||
bid: BN(rates.b[0])
|
||||
.then(function (response) {
|
||||
const rates = response.data.result[pair]
|
||||
return {
|
||||
rates: {
|
||||
ask: BN(rates.a[0]),
|
||||
bid: BN(rates.b[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) {
|
|||
|
||||
function accountBalance (account, cryptoCode, confirmations) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('getbalance', ['', confirmations]))
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
.then(() => fetch('getbalance', ['', confirmations]))
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
}
|
||||
|
||||
// We want a balance that includes all spends (0 conf) but only deposits that
|
||||
|
|
@ -59,71 +59,71 @@ function sendCoins (account, address, cryptoAtoms, cryptoCode) {
|
|||
const btcAddress = bchToBtcAddress(address)
|
||||
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('sendtoaddress', [btcAddress, coins]))
|
||||
.catch(err => {
|
||||
if (err.code === -6) throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
.then(() => fetch('sendtoaddress', [btcAddress, coins]))
|
||||
.catch(err => {
|
||||
if (err.code === -6) throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function newAddress (account, info) {
|
||||
return checkCryptoCode(info.cryptoCode)
|
||||
.then(() => fetch('getnewaddress'))
|
||||
.then(() => fetch('getnewaddress'))
|
||||
}
|
||||
|
||||
function addressBalance (address, confs) {
|
||||
const btcAddress = bchToBtcAddress(address)
|
||||
|
||||
return fetch('getreceivedbyaddress', [btcAddress, confs])
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
}
|
||||
|
||||
function confirmedBalance (address, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => addressBalance(address, 1))
|
||||
.then(() => addressBalance(address, 1))
|
||||
}
|
||||
|
||||
function pendingBalance (address, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => addressBalance(address, 0))
|
||||
.then(() => addressBalance(address, 0))
|
||||
}
|
||||
|
||||
function getStatus (account, toAddress, requested, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => confirmedBalance(toAddress, cryptoCode))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(requested)) return {status: 'confirmed'}
|
||||
.then(() => confirmedBalance(toAddress, cryptoCode))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(requested)) return {status: 'confirmed'}
|
||||
|
||||
return pendingBalance(toAddress, cryptoCode)
|
||||
.then(pending => {
|
||||
if (pending.gte(requested)) return {status: 'authorized'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
return pendingBalance(toAddress, cryptoCode)
|
||||
.then(pending => {
|
||||
if (pending.gte(requested)) return {status: 'authorized'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function newFunding (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => {
|
||||
const promises = [
|
||||
accountBalance(account, cryptoCode, 0),
|
||||
accountBalance(account, cryptoCode, 1),
|
||||
newAddress(account, {cryptoCode})
|
||||
]
|
||||
.then(() => {
|
||||
const promises = [
|
||||
accountBalance(account, cryptoCode, 0),
|
||||
accountBalance(account, cryptoCode, 1),
|
||||
newAddress(account, {cryptoCode})
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
}
|
||||
|
||||
function cryptoNetwork (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main')
|
||||
.then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ function checkCryptoCode (cryptoCode) {
|
|||
|
||||
function accountBalance (acount, cryptoCode, confirmations) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('getbalance', ['', confirmations]))
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
.then(() => fetch('getbalance', ['', confirmations]))
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
}
|
||||
|
||||
// We want a balance that includes all spends (0 conf) but only deposits that
|
||||
|
|
@ -40,69 +40,69 @@ function sendCoins (account, address, cryptoAtoms, cryptoCode) {
|
|||
const coins = cryptoAtoms.shift(-unitScale).toFixed(8)
|
||||
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('sendtoaddress', [address, coins]))
|
||||
.catch(err => {
|
||||
if (err.code === -6) throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
.then(() => fetch('sendtoaddress', [address, coins]))
|
||||
.catch(err => {
|
||||
if (err.code === -6) throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function newAddress (account, info) {
|
||||
return checkCryptoCode(info.cryptoCode)
|
||||
.then(() => fetch('getnewaddress'))
|
||||
.then(() => fetch('getnewaddress'))
|
||||
}
|
||||
|
||||
function addressBalance (address, confs) {
|
||||
return fetch('getreceivedbyaddress', [address, confs])
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
}
|
||||
|
||||
function confirmedBalance (address, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => addressBalance(address, 1))
|
||||
.then(() => addressBalance(address, 1))
|
||||
}
|
||||
|
||||
function pendingBalance (address, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => addressBalance(address, 0))
|
||||
.then(() => addressBalance(address, 0))
|
||||
}
|
||||
|
||||
function getStatus (account, toAddress, requested, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => confirmedBalance(toAddress, cryptoCode))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(requested)) return {status: 'confirmed'}
|
||||
.then(() => confirmedBalance(toAddress, cryptoCode))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(requested)) return {status: 'confirmed'}
|
||||
|
||||
return pendingBalance(toAddress, cryptoCode)
|
||||
.then(pending => {
|
||||
if (pending.gte(requested)) return {status: 'authorized'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
return pendingBalance(toAddress, cryptoCode)
|
||||
.then(pending => {
|
||||
if (pending.gte(requested)) return {status: 'authorized'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function newFunding (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => {
|
||||
const promises = [
|
||||
accountBalance(account, cryptoCode, 0),
|
||||
accountBalance(account, cryptoCode, 1),
|
||||
newAddress(account, {cryptoCode})
|
||||
]
|
||||
.then(() => {
|
||||
const promises = [
|
||||
accountBalance(account, cryptoCode, 0),
|
||||
accountBalance(account, cryptoCode, 1),
|
||||
newAddress(account, {cryptoCode})
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
}
|
||||
|
||||
function cryptoNetwork (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main')
|
||||
.then(() => parseInt(rpcConfig.port, 10) === 18332 ? 'test' : 'main')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -29,83 +29,83 @@ function checkCryptoCode (cryptoCode) {
|
|||
|
||||
function sendCoins (account, address, cryptoAtoms, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => getWallet(account))
|
||||
.then(wallet => {
|
||||
const params = {
|
||||
address: address,
|
||||
amount: cryptoAtoms.toNumber(),
|
||||
walletPassphrase: account.walletPassphrase
|
||||
}
|
||||
return wallet.sendCoins(params)
|
||||
})
|
||||
.then(result => {
|
||||
return result.hash
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.message === 'Insufficient funds') throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
.then(() => getWallet(account))
|
||||
.then(wallet => {
|
||||
const params = {
|
||||
address: address,
|
||||
amount: cryptoAtoms.toNumber(),
|
||||
walletPassphrase: account.walletPassphrase
|
||||
}
|
||||
return wallet.sendCoins(params)
|
||||
})
|
||||
.then(result => {
|
||||
return result.hash
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.message === 'Insufficient funds') throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function balance (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => getWallet(account))
|
||||
.then(wallet => BN(wallet.wallet.spendableConfirmedBalance))
|
||||
.then(() => getWallet(account))
|
||||
.then(wallet => BN(wallet.wallet.spendableConfirmedBalance))
|
||||
}
|
||||
|
||||
function newAddress (account, info) {
|
||||
return checkCryptoCode(info.cryptoCode)
|
||||
.then(() => getWallet(account))
|
||||
.then(wallet => {
|
||||
return wallet.createAddress()
|
||||
.then(result => {
|
||||
const address = result.address
|
||||
.then(() => getWallet(account))
|
||||
.then(wallet => {
|
||||
return wallet.createAddress()
|
||||
.then(result => {
|
||||
const address = result.address
|
||||
|
||||
// If a label was provided, set the label
|
||||
if (info.label) {
|
||||
return wallet.setLabel({ address: address, label: info.label })
|
||||
.then(() => address)
|
||||
}
|
||||
// If a label was provided, set the label
|
||||
if (info.label) {
|
||||
return wallet.setLabel({ address: address, label: info.label })
|
||||
.then(() => address)
|
||||
}
|
||||
|
||||
return address
|
||||
return address
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getStatus (account, toAddress, requested, cryptoCode) {
|
||||
const bitgo = buildBitgo(account)
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => bitgo.blockchain().getAddress({address: toAddress}))
|
||||
.then(rec => {
|
||||
if (rec.balance === 0) return {status: 'notSeen'}
|
||||
if (requested.gt(rec.balance)) return {status: 'insufficientFunds'}
|
||||
if (requested.gt(rec.confirmedBalance)) return {status: 'authorized'}
|
||||
return {status: 'confirmed'}
|
||||
})
|
||||
.then(() => bitgo.blockchain().getAddress({address: toAddress}))
|
||||
.then(rec => {
|
||||
if (rec.balance === 0) return {status: 'notSeen'}
|
||||
if (requested.gt(rec.balance)) return {status: 'insufficientFunds'}
|
||||
if (requested.gt(rec.confirmedBalance)) return {status: 'authorized'}
|
||||
return {status: 'confirmed'}
|
||||
})
|
||||
}
|
||||
|
||||
function newFunding (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => {
|
||||
return getWallet(account)
|
||||
.then(wallet => {
|
||||
return wallet.createAddress()
|
||||
.then(result => {
|
||||
const fundingAddress = result.address
|
||||
return wallet.setLabel({address: fundingAddress, label: 'Funding Address'})
|
||||
.then(() => ({
|
||||
fundingPendingBalance: BN(wallet.wallet.balance),
|
||||
fundingConfirmedBalance: BN(wallet.wallet.confirmedBalance),
|
||||
fundingAddress
|
||||
}))
|
||||
})
|
||||
.then(() => {
|
||||
return getWallet(account)
|
||||
.then(wallet => {
|
||||
return wallet.createAddress()
|
||||
.then(result => {
|
||||
const fundingAddress = result.address
|
||||
return wallet.setLabel({address: fundingAddress, label: 'Funding Address'})
|
||||
.then(() => ({
|
||||
fundingPendingBalance: BN(wallet.wallet.balance),
|
||||
fundingConfirmedBalance: BN(wallet.wallet.confirmedBalance),
|
||||
fundingAddress
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function cryptoNetwork (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => account.environment === 'test' ? 'test' : 'main')
|
||||
.then(() => account.environment === 'test' ? 'test' : 'main')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) {
|
|||
|
||||
function accountBalance (acount, cryptoCode, confirmations) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('getbalance', ['', confirmations]))
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
.then(() => fetch('getbalance', ['', confirmations]))
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
}
|
||||
|
||||
// We want a balance that includes all spends (0 conf) but only deposits that
|
||||
|
|
@ -41,64 +41,64 @@ function sendCoins (account, address, cryptoAtoms, cryptoCode) {
|
|||
const coins = cryptoAtoms.shift(-unitScale).toFixed(8)
|
||||
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('sendtoaddress', [address, coins]))
|
||||
.catch(err => {
|
||||
if (err.code === -6) throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
.then(() => fetch('sendtoaddress', [address, coins]))
|
||||
.catch(err => {
|
||||
if (err.code === -6) throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function newAddress (account, info) {
|
||||
return checkCryptoCode(info.cryptoCode)
|
||||
.then(() => fetch('getnewaddress'))
|
||||
.then(() => fetch('getnewaddress'))
|
||||
}
|
||||
|
||||
function addressBalance (address, confs) {
|
||||
return fetch('getreceivedbyaddress', [address, confs])
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
}
|
||||
|
||||
function confirmedBalance (address, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => addressBalance(address, 1))
|
||||
.then(() => addressBalance(address, 1))
|
||||
}
|
||||
|
||||
function pendingBalance (address, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => addressBalance(address, 0))
|
||||
.then(() => addressBalance(address, 0))
|
||||
}
|
||||
|
||||
function getStatus (account, toAddress, requested, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => confirmedBalance(toAddress, cryptoCode))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(requested)) return {status: 'confirmed'}
|
||||
.then(() => confirmedBalance(toAddress, cryptoCode))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(requested)) return {status: 'confirmed'}
|
||||
|
||||
return pendingBalance(toAddress, cryptoCode)
|
||||
.then(pending => {
|
||||
if (pending.gte(requested)) return {status: 'authorized'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
return pendingBalance(toAddress, cryptoCode)
|
||||
.then(pending => {
|
||||
if (pending.gte(requested)) return {status: 'authorized'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function newFunding (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => {
|
||||
const promises = [
|
||||
accountBalance(account, cryptoCode, 0),
|
||||
accountBalance(account, cryptoCode, 1),
|
||||
newAddress(account, {cryptoCode})
|
||||
]
|
||||
.then(() => {
|
||||
const promises = [
|
||||
accountBalance(account, cryptoCode, 0),
|
||||
accountBalance(account, cryptoCode, 1),
|
||||
newAddress(account, {cryptoCode})
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ function privateKey (account) {
|
|||
|
||||
function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) {
|
||||
return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false)
|
||||
.then(pify(web3.eth.sendRawTransaction))
|
||||
.then(pify(web3.eth.sendRawTransaction))
|
||||
}
|
||||
|
||||
function checkCryptoCode (cryptoCode) {
|
||||
|
|
@ -52,7 +52,7 @@ function checkCryptoCode (cryptoCode) {
|
|||
|
||||
function balance (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => pendingBalance(defaultAddress(account)))
|
||||
.then(() => pendingBalance(defaultAddress(account)))
|
||||
}
|
||||
|
||||
const pendingBalance = address => _balance(true, address)
|
||||
|
|
@ -81,31 +81,31 @@ function generateTx (_toAddress, wallet, amount, includesFee) {
|
|||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(arr => {
|
||||
const gas = arr[0]
|
||||
const gasPrice = arr[1]
|
||||
const txCount = arr[2]
|
||||
.then(arr => {
|
||||
const gas = arr[0]
|
||||
const gasPrice = arr[1]
|
||||
const txCount = arr[2]
|
||||
|
||||
const toSend = includesFee
|
||||
? amount.minus(gasPrice.times(gas))
|
||||
: amount
|
||||
const toSend = includesFee
|
||||
? amount.minus(gasPrice.times(gas))
|
||||
: amount
|
||||
|
||||
const rawTx = {
|
||||
nonce: txCount,
|
||||
gasPrice: hex(gasPrice),
|
||||
gasLimit: gas,
|
||||
to: toAddress,
|
||||
from: fromAddress,
|
||||
value: hex(toSend)
|
||||
}
|
||||
const rawTx = {
|
||||
nonce: txCount,
|
||||
gasPrice: hex(gasPrice),
|
||||
gasLimit: gas,
|
||||
to: toAddress,
|
||||
from: fromAddress,
|
||||
value: hex(toSend)
|
||||
}
|
||||
|
||||
const tx = new Tx(rawTx)
|
||||
const privateKey = wallet.getPrivateKey()
|
||||
const tx = new Tx(rawTx)
|
||||
const privateKey = wallet.getPrivateKey()
|
||||
|
||||
tx.sign(privateKey)
|
||||
tx.sign(privateKey)
|
||||
|
||||
return '0x' + tx.serialize().toString('hex')
|
||||
})
|
||||
return '0x' + tx.serialize().toString('hex')
|
||||
})
|
||||
}
|
||||
|
||||
function defaultWallet (account) {
|
||||
|
|
@ -121,12 +121,12 @@ function sweep (account, cryptoCode, hdIndex) {
|
|||
const fromAddress = wallet.getChecksumAddressString()
|
||||
|
||||
return confirmedBalance(fromAddress)
|
||||
.then(r => {
|
||||
if (r.eq(0)) return
|
||||
.then(r => {
|
||||
if (r.eq(0)) return
|
||||
|
||||
return generateTx(defaultAddress(account), wallet, r, true)
|
||||
.then(signedTx => pify(web3.eth.sendRawTransaction)(signedTx))
|
||||
})
|
||||
return generateTx(defaultAddress(account), wallet, r, true)
|
||||
.then(signedTx => pify(web3.eth.sendRawTransaction)(signedTx))
|
||||
})
|
||||
}
|
||||
|
||||
function newAddress (account, info) {
|
||||
|
|
@ -136,17 +136,17 @@ function newAddress (account, info) {
|
|||
|
||||
function getStatus (account, toAddress, cryptoAtoms, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => confirmedBalance(toAddress))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(cryptoAtoms)) return {status: 'confirmed'}
|
||||
.then(() => confirmedBalance(toAddress))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(cryptoAtoms)) return {status: 'confirmed'}
|
||||
|
||||
return pendingBalance(toAddress)
|
||||
.then(pending => {
|
||||
if (pending.gte(cryptoAtoms)) return {status: 'published'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
return pendingBalance(toAddress)
|
||||
.then(pending => {
|
||||
if (pending.gte(cryptoAtoms)) return {status: 'published'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function paymentHdNode (account) {
|
||||
|
|
@ -165,19 +165,19 @@ function defaultHdNode (account) {
|
|||
|
||||
function newFunding (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => {
|
||||
const fundingAddress = defaultAddress(account)
|
||||
.then(() => {
|
||||
const fundingAddress = defaultAddress(account)
|
||||
|
||||
const promises = [
|
||||
pendingBalance(fundingAddress),
|
||||
confirmedBalance(fundingAddress)
|
||||
]
|
||||
const promises = [
|
||||
pendingBalance(fundingAddress),
|
||||
confirmedBalance(fundingAddress)
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
})
|
||||
return Promise.all(promises)
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) {
|
|||
|
||||
function accountBalance (acount, cryptoCode, confirmations) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('getbalance', ['', confirmations]))
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
.then(() => fetch('getbalance', ['', confirmations]))
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
}
|
||||
|
||||
// We want a balance that includes all spends (0 conf) but only deposits that
|
||||
|
|
@ -41,64 +41,64 @@ function sendCoins (account, address, cryptoAtoms, cryptoCode) {
|
|||
const coins = cryptoAtoms.shift(-unitScale).toFixed(8)
|
||||
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('sendtoaddress', [address, coins]))
|
||||
.catch(err => {
|
||||
if (err.code === -6) throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
.then(() => fetch('sendtoaddress', [address, coins]))
|
||||
.catch(err => {
|
||||
if (err.code === -6) throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function newAddress (account, info) {
|
||||
return checkCryptoCode(info.cryptoCode)
|
||||
.then(() => fetch('getnewaddress'))
|
||||
.then(() => fetch('getnewaddress'))
|
||||
}
|
||||
|
||||
function addressBalance (address, confs) {
|
||||
return fetch('getreceivedbyaddress', [address, confs])
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
}
|
||||
|
||||
function confirmedBalance (address, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => addressBalance(address, 1))
|
||||
.then(() => addressBalance(address, 1))
|
||||
}
|
||||
|
||||
function pendingBalance (address, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => addressBalance(address, 0))
|
||||
.then(() => addressBalance(address, 0))
|
||||
}
|
||||
|
||||
function getStatus (account, toAddress, requested, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => confirmedBalance(toAddress, cryptoCode))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(requested)) return {status: 'confirmed'}
|
||||
.then(() => confirmedBalance(toAddress, cryptoCode))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(requested)) return {status: 'confirmed'}
|
||||
|
||||
return pendingBalance(toAddress, cryptoCode)
|
||||
.then(pending => {
|
||||
if (pending.gte(requested)) return {status: 'authorized'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
return pendingBalance(toAddress, cryptoCode)
|
||||
.then(pending => {
|
||||
if (pending.gte(requested)) return {status: 'authorized'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function newFunding (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => {
|
||||
const promises = [
|
||||
accountBalance(account, cryptoCode, 0),
|
||||
accountBalance(account, cryptoCode, 1),
|
||||
newAddress(account, {cryptoCode})
|
||||
]
|
||||
.then(() => {
|
||||
const promises = [
|
||||
accountBalance(account, cryptoCode, 0),
|
||||
accountBalance(account, cryptoCode, 1),
|
||||
newAddress(account, {cryptoCode})
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -34,11 +34,11 @@ function checkCryptoCode (cryptoCode) {
|
|||
|
||||
function balance (acount, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(connect)
|
||||
.then(c => c.channelBalance({}))
|
||||
.then(_.get('balance'))
|
||||
.then(BN)
|
||||
.then(r => r.shift(unitScale).round())
|
||||
.then(connect)
|
||||
.then(c => c.channelBalance({}))
|
||||
.then(_.get('balance'))
|
||||
.then(BN)
|
||||
.then(r => r.shift(unitScale).round())
|
||||
}
|
||||
|
||||
function sendCoins (account, address, cryptoAtoms, cryptoCode) {
|
||||
|
|
@ -53,37 +53,37 @@ function newFunding (account, cryptoCode) {
|
|||
|
||||
function newAddress (account, info) {
|
||||
return checkCryptoCode(info.cryptoCode)
|
||||
.then(connect)
|
||||
.then(c => {
|
||||
if (info.isLightning) {
|
||||
return c.addInvoice({memo: 'Lamassu cryptomat deposit', value: info.cryptoAtoms.toNumber()})
|
||||
.then(r => `${r.r_hash.toString('hex')}:${r.payment_request}`)
|
||||
}
|
||||
.then(connect)
|
||||
.then(c => {
|
||||
if (info.isLightning) {
|
||||
return c.addInvoice({memo: 'Lamassu cryptomat deposit', value: info.cryptoAtoms.toNumber()})
|
||||
.then(r => `${r.r_hash.toString('hex')}:${r.payment_request}`)
|
||||
}
|
||||
|
||||
return c.newAddress({type: 2})
|
||||
.then(_.get('address'))
|
||||
})
|
||||
return c.newAddress({type: 2})
|
||||
.then(_.get('address'))
|
||||
})
|
||||
}
|
||||
|
||||
function getStatus (account, toAddress, requested, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => {
|
||||
const parts = _.split(':', toAddress)
|
||||
const isLightning = _.size(parts) === 2
|
||||
const rHashStr = isLightning && _.head(parts)
|
||||
.then(() => {
|
||||
const parts = _.split(':', toAddress)
|
||||
const isLightning = _.size(parts) === 2
|
||||
const rHashStr = isLightning && _.head(parts)
|
||||
|
||||
return connect()
|
||||
.then(c => {
|
||||
if (isLightning) {
|
||||
return c.lookupInvoice({r_hash_str: rHashStr})
|
||||
.then(r => {
|
||||
if (r.settled) return {status: 'confirmed'}
|
||||
return connect()
|
||||
.then(c => {
|
||||
if (isLightning) {
|
||||
return c.lookupInvoice({r_hash_str: rHashStr})
|
||||
.then(r => {
|
||||
if (r.settled) return {status: 'confirmed'}
|
||||
return {status: 'notSeen'}
|
||||
})
|
||||
}
|
||||
|
||||
// Note: this must be handled outside of lnd
|
||||
return {status: 'notSeen'}
|
||||
})
|
||||
}
|
||||
|
||||
// Note: this must be handled outside of lnd
|
||||
return {status: 'notSeen'}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ function _balance (cryptoCode) {
|
|||
|
||||
function balance (account, cryptoCode) {
|
||||
return Promise.resolve()
|
||||
.then(() => _balance(cryptoCode))
|
||||
.then(() => _balance(cryptoCode))
|
||||
}
|
||||
|
||||
function pendingBalance (account, cryptoCode) {
|
||||
return balance(account, cryptoCode)
|
||||
.then(b => b.mul(1.1))
|
||||
.then(b => b.mul(1.1))
|
||||
}
|
||||
|
||||
function confirmedBalance (account, cryptoCode) {
|
||||
|
|
@ -69,11 +69,11 @@ function newFunding (account, cryptoCode) {
|
|||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
}
|
||||
|
||||
function getStatus (account, toAddress, cryptoAtoms, cryptoCode) {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ function checkCryptoCode (cryptoCode) {
|
|||
|
||||
function accountBalance (acount, cryptoCode, confirmations) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('getbalance', ['', confirmations]))
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
.then(() => fetch('getbalance', ['', confirmations]))
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
}
|
||||
|
||||
// We want a balance that includes all spends (0 conf) but only deposits that
|
||||
|
|
@ -41,64 +41,64 @@ function sendCoins (account, address, cryptoAtoms, cryptoCode) {
|
|||
const coins = cryptoAtoms.shift(-unitScale).toFixed(8)
|
||||
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => fetch('sendtoaddress', [address, coins]))
|
||||
.catch(err => {
|
||||
if (err.code === -6) throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
.then(() => fetch('sendtoaddress', [address, coins]))
|
||||
.catch(err => {
|
||||
if (err.code === -6) throw new E.InsufficientFundsError()
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function newAddress (account, info) {
|
||||
return checkCryptoCode(info.cryptoCode)
|
||||
.then(() => fetch('getnewaddress'))
|
||||
.then(() => fetch('getnewaddress'))
|
||||
}
|
||||
|
||||
function addressBalance (address, confs) {
|
||||
return fetch('getreceivedbyaddress', [address, confs])
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
.then(r => BN(r).shift(unitScale).round())
|
||||
}
|
||||
|
||||
function confirmedBalance (address, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => addressBalance(address, 1))
|
||||
.then(() => addressBalance(address, 1))
|
||||
}
|
||||
|
||||
function pendingBalance (address, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => addressBalance(address, 0))
|
||||
.then(() => addressBalance(address, 0))
|
||||
}
|
||||
|
||||
function getStatus (account, toAddress, requested, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => confirmedBalance(toAddress, cryptoCode))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(requested)) return {status: 'confirmed'}
|
||||
.then(() => confirmedBalance(toAddress, cryptoCode))
|
||||
.then(confirmed => {
|
||||
if (confirmed.gte(requested)) return {status: 'confirmed'}
|
||||
|
||||
return pendingBalance(toAddress, cryptoCode)
|
||||
.then(pending => {
|
||||
if (pending.gte(requested)) return {status: 'authorized'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
return pendingBalance(toAddress, cryptoCode)
|
||||
.then(pending => {
|
||||
if (pending.gte(requested)) return {status: 'authorized'}
|
||||
if (pending.gt(0)) return {status: 'insufficientFunds'}
|
||||
return {status: 'notSeen'}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function newFunding (account, cryptoCode) {
|
||||
return checkCryptoCode(cryptoCode)
|
||||
.then(() => {
|
||||
const promises = [
|
||||
accountBalance(account, cryptoCode, 0),
|
||||
accountBalance(account, cryptoCode, 1),
|
||||
newAddress(account, {cryptoCode})
|
||||
]
|
||||
.then(() => {
|
||||
const promises = [
|
||||
accountBalance(account, cryptoCode, 0),
|
||||
accountBalance(account, cryptoCode, 1),
|
||||
newAddress(account, {cryptoCode})
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({
|
||||
fundingPendingBalance,
|
||||
fundingConfirmedBalance,
|
||||
fundingAddress
|
||||
}))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -12,27 +12,26 @@ function highConfidence (confidence, txref) {
|
|||
|
||||
function authorize (account, toAddress, cryptoAtoms, cryptoCode) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||
.then(() => {
|
||||
if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||
|
||||
const query = qs.stringify({
|
||||
token: account.token,
|
||||
includeConfidence: true
|
||||
const query = qs.stringify({
|
||||
token: account.token,
|
||||
includeConfidence: true
|
||||
})
|
||||
|
||||
const confidence = account.confidenceFactor
|
||||
|
||||
const url = `https://api.blockcypher.com/v1/btc/main/addrs/${toAddress}?${query}`
|
||||
|
||||
return axios.get(url)
|
||||
.then(r => {
|
||||
const data = r.data
|
||||
const sumTxRefs = txrefs => _.sumBy(txref => highConfidence(confidence, txref), txrefs)
|
||||
|
||||
const authorizedValue = sumTxRefs(data.txrefs) + sumTxRefs(data.unconfirmed_txrefs)
|
||||
|
||||
return cryptoAtoms.lte(authorizedValue)
|
||||
})
|
||||
})
|
||||
|
||||
const confidence = account.confidenceFactor
|
||||
|
||||
const url = `https://api.blockcypher.com/v1/btc/main/addrs/${toAddress}?${query}`
|
||||
|
||||
return axios.get(url)
|
||||
.then(r => {
|
||||
const data = r.data
|
||||
const sumTxRefs = txrefs => _.sumBy(txref => highConfidence(confidence, txref), txrefs)
|
||||
|
||||
const authorizedValue = sumTxRefs(data.txrefs) + sumTxRefs(data.unconfirmed_txrefs)
|
||||
|
||||
return cryptoAtoms.lte(authorizedValue)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ module.exports = {authorize}
|
|||
|
||||
function authorize (account, toAddress, cryptoAtoms, cryptoCode) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||
.then(() => {
|
||||
if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||
|
||||
const isAuthorized = false
|
||||
return isAuthorized
|
||||
})
|
||||
const isAuthorized = false
|
||||
return isAuthorized
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ exports.cassetteCounts = function cassetteCounts (deviceId) {
|
|||
'WHERE device_id=$1'
|
||||
|
||||
return db.one(sql, [deviceId])
|
||||
.then(row => {
|
||||
const counts = [row.cassette1, row.cassette2]
|
||||
return {counts}
|
||||
})
|
||||
.then(row => {
|
||||
const counts = [row.cassette1, row.cassette2]
|
||||
return {counts}
|
||||
})
|
||||
}
|
||||
|
||||
// Note: since we only prune on insert, we'll always have
|
||||
|
|
@ -47,7 +47,7 @@ exports.machineEvent = function machineEvent (rec) {
|
|||
and created < now() - interval '2 days'`
|
||||
|
||||
return db.none(sql, values)
|
||||
.then(() => db.none(deleteSql, [rec.deviceId, rec.eventType]))
|
||||
.then(() => db.none(deleteSql, [rec.deviceId, rec.eventType]))
|
||||
}
|
||||
|
||||
exports.machineEvents = function machineEvents () {
|
||||
|
|
|
|||
|
|
@ -55,32 +55,32 @@ function fetchPhoneTx (phone) {
|
|||
const values = [phone, false, TRANSACTION_EXPIRATION]
|
||||
|
||||
return db.any(sql, values)
|
||||
.then(_.map(toCashOutTx))
|
||||
.then(txs => {
|
||||
const confirmedTxs = txs.filter(tx => R.contains(tx.status, ['instant', 'confirmed']))
|
||||
if (confirmedTxs.length > 0) {
|
||||
const maxTx = R.reduce((acc, val) => {
|
||||
return !acc || val.cryptoAtoms.gt(acc.cryptoAtoms) ? val : acc
|
||||
}, null, confirmedTxs)
|
||||
.then(_.map(toCashOutTx))
|
||||
.then(txs => {
|
||||
const confirmedTxs = txs.filter(tx => R.contains(tx.status, ['instant', 'confirmed']))
|
||||
if (confirmedTxs.length > 0) {
|
||||
const maxTx = R.reduce((acc, val) => {
|
||||
return !acc || val.cryptoAtoms.gt(acc.cryptoAtoms) ? val : acc
|
||||
}, null, confirmedTxs)
|
||||
|
||||
return maxTx
|
||||
}
|
||||
return maxTx
|
||||
}
|
||||
|
||||
if (txs.length > 0) throw httpError('Pending transactions', 412)
|
||||
throw httpError('No transactions', 404)
|
||||
})
|
||||
if (txs.length > 0) throw httpError('Pending transactions', 412)
|
||||
throw httpError('No transactions', 404)
|
||||
})
|
||||
}
|
||||
|
||||
function fetchStatusTx (txId, status) {
|
||||
const sql = 'select * from cash_out_txs where id=$1'
|
||||
|
||||
return db.oneOrNone(sql, [txId])
|
||||
.then(toCashOutTx)
|
||||
.then(tx => {
|
||||
if (!tx) throw httpError('No transaction', 404)
|
||||
if (tx.status === status) throw httpError('Not Modified', 304)
|
||||
return tx
|
||||
})
|
||||
.then(toCashOutTx)
|
||||
.then(tx => {
|
||||
if (!tx) throw httpError('No transaction', 404)
|
||||
if (tx.status === status) throw httpError('Not Modified', 304)
|
||||
return tx
|
||||
})
|
||||
}
|
||||
|
||||
function updateDeviceConfigVersion (versionId) {
|
||||
|
|
@ -105,9 +105,9 @@ function updateMachineDefaults (deviceId) {
|
|||
}]
|
||||
|
||||
return settingsLoader.loadLatest()
|
||||
.then(settings => {
|
||||
return settingsLoader.save(settingsLoader.mergeValues(settings.config, newFields))
|
||||
})
|
||||
.then(settings => {
|
||||
return settingsLoader.save(settingsLoader.mergeValues(settings.config, newFields))
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
246
lib/routes.js
246
lib/routes.js
|
|
@ -41,60 +41,60 @@ function poll (req, res, next) {
|
|||
pids[deviceId] = {pid, ts: Date.now()}
|
||||
|
||||
return pi.pollQueries(serialNumber, deviceTime, req.query)
|
||||
.then(results => {
|
||||
const cassettes = results.cassettes
|
||||
.then(results => {
|
||||
const cassettes = results.cassettes
|
||||
|
||||
const reboot = pid && reboots[deviceId] && reboots[deviceId] === pid
|
||||
const langs = config.machineLanguages
|
||||
const reboot = pid && reboots[deviceId] && reboots[deviceId] === pid
|
||||
const langs = config.machineLanguages
|
||||
|
||||
const locale = {
|
||||
fiatCode: config.fiatCurrency,
|
||||
localeInfo: {
|
||||
primaryLocale: langs[0],
|
||||
primaryLocales: langs,
|
||||
country: config.country
|
||||
const locale = {
|
||||
fiatCode: config.fiatCurrency,
|
||||
localeInfo: {
|
||||
primaryLocale: langs[0],
|
||||
primaryLocales: langs,
|
||||
country: config.country
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const response = {
|
||||
error: null,
|
||||
locale,
|
||||
txLimit: config.cashInTransactionLimit,
|
||||
idVerificationEnabled: config.idVerificationEnabled,
|
||||
smsVerificationActive: config.smsVerificationActive,
|
||||
smsVerificationThreshold: config.smsVerificationThreshold,
|
||||
hardLimitVerificationActive: config.hardLimitVerificationActive,
|
||||
hardLimitVerificationThreshold: config.hardLimitVerificationThreshold,
|
||||
idCardDataVerificationActive: config.idCardDataVerificationActive,
|
||||
idCardDataVerificationThreshold: config.idCardDataVerificationThreshold,
|
||||
idCardPhotoVerificationActive: config.idCardPhotoVerificationActive,
|
||||
idCardPhotoVerificationThreshold: config.idCardPhotoVerificationThreshold,
|
||||
sanctionsVerificationActive: config.sanctionsVerificationActive,
|
||||
sanctionsVerificationThreshold: config.sanctionsVerificationThreshold,
|
||||
crossRefVerificationActive: config.crossRefVerificationActive,
|
||||
crossRefVerificationThreshold: config.crossRefVerificationThreshold,
|
||||
frontCameraVerificationActive: config.frontCameraVerificationActive,
|
||||
frontCameraVerificationThreshold: config.frontCameraVerificationThreshold,
|
||||
cassettes,
|
||||
twoWayMode: config.cashOutEnabled,
|
||||
zeroConfLimit: config.zeroConfLimit,
|
||||
reboot
|
||||
}
|
||||
const response = {
|
||||
error: null,
|
||||
locale,
|
||||
txLimit: config.cashInTransactionLimit,
|
||||
idVerificationEnabled: config.idVerificationEnabled,
|
||||
smsVerificationActive: config.smsVerificationActive,
|
||||
smsVerificationThreshold: config.smsVerificationThreshold,
|
||||
hardLimitVerificationActive: config.hardLimitVerificationActive,
|
||||
hardLimitVerificationThreshold: config.hardLimitVerificationThreshold,
|
||||
idCardDataVerificationActive: config.idCardDataVerificationActive,
|
||||
idCardDataVerificationThreshold: config.idCardDataVerificationThreshold,
|
||||
idCardPhotoVerificationActive: config.idCardPhotoVerificationActive,
|
||||
idCardPhotoVerificationThreshold: config.idCardPhotoVerificationThreshold,
|
||||
sanctionsVerificationActive: config.sanctionsVerificationActive,
|
||||
sanctionsVerificationThreshold: config.sanctionsVerificationThreshold,
|
||||
crossRefVerificationActive: config.crossRefVerificationActive,
|
||||
crossRefVerificationThreshold: config.crossRefVerificationThreshold,
|
||||
frontCameraVerificationActive: config.frontCameraVerificationActive,
|
||||
frontCameraVerificationThreshold: config.frontCameraVerificationThreshold,
|
||||
cassettes,
|
||||
twoWayMode: config.cashOutEnabled,
|
||||
zeroConfLimit: config.zeroConfLimit,
|
||||
reboot
|
||||
}
|
||||
|
||||
if (response.idVerificationEnabled) {
|
||||
response.idVerificationLimit = config.idVerificationLimit
|
||||
}
|
||||
if (response.idVerificationEnabled) {
|
||||
response.idVerificationLimit = config.idVerificationLimit
|
||||
}
|
||||
|
||||
return res.json(_.assign(response, results))
|
||||
})
|
||||
.catch(next)
|
||||
return res.json(_.assign(response, results))
|
||||
})
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function getTx (req, res, next) {
|
||||
if (req.query.status) {
|
||||
return helpers.fetchStatusTx(req.params.id, req.query.status)
|
||||
.then(r => res.json(r))
|
||||
.catch(next)
|
||||
.then(r => res.json(r))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
return next(httpError('Not Found', 404))
|
||||
|
|
@ -103,8 +103,8 @@ function getTx (req, res, next) {
|
|||
function getPhoneTx (req, res, next) {
|
||||
if (req.query.phone) {
|
||||
return helpers.fetchPhoneTx(req.query.phone)
|
||||
.then(r => res.json(r))
|
||||
.catch(next)
|
||||
.then(r => res.json(r))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
return next(httpError('Not Found', 404))
|
||||
|
|
@ -114,99 +114,99 @@ function postTx (req, res, next) {
|
|||
const pi = plugins(req.settings, req.deviceId)
|
||||
|
||||
return Tx.post(_.set('deviceId', req.deviceId, req.body), pi)
|
||||
.then(tx => {
|
||||
if (tx.errorCode) {
|
||||
logger.error(tx.error)
|
||||
throw httpError(tx.error, 500)
|
||||
}
|
||||
.then(tx => {
|
||||
if (tx.errorCode) {
|
||||
logger.error(tx.error)
|
||||
throw httpError(tx.error, 500)
|
||||
}
|
||||
|
||||
return res.json(tx)
|
||||
})
|
||||
.catch(err => {
|
||||
if (err instanceof E.StaleTxError) return res.status(409).json({})
|
||||
if (err instanceof E.RatchetError) return res.status(409).json({})
|
||||
return res.json(tx)
|
||||
})
|
||||
.catch(err => {
|
||||
if (err instanceof E.StaleTxError) return res.status(409).json({})
|
||||
if (err instanceof E.RatchetError) return res.status(409).json({})
|
||||
|
||||
throw err
|
||||
})
|
||||
.catch(next)
|
||||
throw err
|
||||
})
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function stateChange (req, res, next) {
|
||||
helpers.stateChange(req.deviceId, req.deviceTime, req.body)
|
||||
.then(() => respond(req, res))
|
||||
.catch(next)
|
||||
.then(() => respond(req, res))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function deviceEvent (req, res, next) {
|
||||
const pi = plugins(req.settings, req.deviceId)
|
||||
pi.logEvent(req.body)
|
||||
.then(() => respond(req, res))
|
||||
.catch(next)
|
||||
.then(() => respond(req, res))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function verifyUser (req, res, next) {
|
||||
const pi = plugins(req.settings, req.deviceId)
|
||||
pi.verifyUser(req.body)
|
||||
.then(idResult => respond(req, res, idResult))
|
||||
.catch(next)
|
||||
.then(idResult => respond(req, res, idResult))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function verifyTx (req, res, next) {
|
||||
const pi = plugins(req.settings, req.deviceId)
|
||||
pi.verifyTransaction(req.body)
|
||||
.then(idResult => respond(req, res, idResult))
|
||||
.catch(next)
|
||||
.then(idResult => respond(req, res, idResult))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function getCustomerWithPhoneCode (req, res, next) {
|
||||
const pi = plugins(req.settings, req.deviceId)
|
||||
const phone = req.body.phone
|
||||
return pi.getPhoneCode(phone)
|
||||
.then(code => {
|
||||
return customers.get(phone)
|
||||
.then(customer => {
|
||||
if (customer) return respond(req, res, {code, customer})
|
||||
return customers.add(req.body)
|
||||
.then(customer => respond(req, res, {code, customer}))
|
||||
.then(code => {
|
||||
return customers.get(phone)
|
||||
.then(customer => {
|
||||
if (customer) return respond(req, res, {code, customer})
|
||||
return customers.add(req.body)
|
||||
.then(customer => respond(req, res, {code, customer}))
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.name === 'BadNumberError') throw httpError('Bad number', 401)
|
||||
throw err
|
||||
})
|
||||
.catch(next)
|
||||
.catch(err => {
|
||||
if (err.name === 'BadNumberError') throw httpError('Bad number', 401)
|
||||
throw err
|
||||
})
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function updateCustomer (req, res, next) {
|
||||
const id = req.params.id
|
||||
const patch = req.body
|
||||
customers.getById(id)
|
||||
.then(customer => {
|
||||
if (!customer) { throw httpError('Not Found', 404)}
|
||||
return customers.update(id, patch)
|
||||
})
|
||||
.then(customer => respond(req, res, {customer}))
|
||||
.catch(next)
|
||||
.then(customer => {
|
||||
if (!customer) { throw httpError('Not Found', 404) }
|
||||
return customers.update(id, patch)
|
||||
})
|
||||
.then(customer => respond(req, res, {customer}))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function getLastSeen (req, res, next) {
|
||||
return logs.getLastSeen(req.deviceId)
|
||||
.then(r => res.json(r))
|
||||
.catch(next)
|
||||
.then(r => res.json(r))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function updateLogs (req, res, next) {
|
||||
return logs.update(req.deviceId, req.body.logs)
|
||||
.then(status => res.json({success: status}))
|
||||
.catch(next)
|
||||
.then(status => res.json({success: status}))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function ca (req, res) {
|
||||
const token = req.query.token
|
||||
|
||||
return pairing.authorizeCaDownload(token)
|
||||
.then(ca => res.json({ca}))
|
||||
.catch(() => res.status(403).json({error: 'forbidden'}))
|
||||
.then(ca => res.json({ca}))
|
||||
.catch(() => res.status(403).json({error: 'forbidden'}))
|
||||
}
|
||||
|
||||
function pair (req, res, next) {
|
||||
|
|
@ -215,21 +215,21 @@ function pair (req, res, next) {
|
|||
const model = req.query.model
|
||||
|
||||
return pairing.pair(token, deviceId, model)
|
||||
.then(valid => {
|
||||
if (valid) {
|
||||
return helpers.updateMachineDefaults(deviceId)
|
||||
.then(() => res.json({status: 'paired'}))
|
||||
}
|
||||
.then(valid => {
|
||||
if (valid) {
|
||||
return helpers.updateMachineDefaults(deviceId)
|
||||
.then(() => res.json({status: 'paired'}))
|
||||
}
|
||||
|
||||
throw httpError('Pairing failed')
|
||||
})
|
||||
.catch(next)
|
||||
throw httpError('Pairing failed')
|
||||
})
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function errorHandler (err, req, res, next) {
|
||||
const statusCode = err.name === 'HTTPError'
|
||||
? err.code || 500
|
||||
: 500
|
||||
? err.code || 500
|
||||
: 500
|
||||
|
||||
const json = {error: err.message}
|
||||
|
||||
|
|
@ -269,15 +269,15 @@ function authorize (req, res, next) {
|
|||
const deviceId = req.deviceId
|
||||
|
||||
return pairing.isPaired(deviceId)
|
||||
.then(r => {
|
||||
if (r) {
|
||||
req.deviceId = deviceId
|
||||
return next()
|
||||
}
|
||||
.then(r => {
|
||||
if (r) {
|
||||
req.deviceId = deviceId
|
||||
return next()
|
||||
}
|
||||
|
||||
return res.status(403).json({error: 'Forbidden'})
|
||||
})
|
||||
.catch(next)
|
||||
return res.status(403).json({error: 'Forbidden'})
|
||||
})
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) &&
|
||||
|
|
@ -349,12 +349,12 @@ localApp.post('/reboot', (req, res) => {
|
|||
|
||||
localApp.post('/dbChange', (req, res, next) => {
|
||||
return settingsLoader.loadLatest()
|
||||
.then(poller.reload)
|
||||
.then(() => logger.info('Config reloaded'))
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
res.sendStatus(500)
|
||||
})
|
||||
.then(poller.reload)
|
||||
.then(() => logger.info('Config reloaded'))
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
res.sendStatus(500)
|
||||
})
|
||||
})
|
||||
|
||||
function sha256 (buf) {
|
||||
|
|
@ -367,8 +367,8 @@ function sha256 (buf) {
|
|||
|
||||
function populateDeviceId (req, res, next) {
|
||||
const deviceId = _.isFunction(req.connection.getPeerCertificate)
|
||||
? sha256(req.connection.getPeerCertificate().raw)
|
||||
: null
|
||||
? sha256(req.connection.getPeerCertificate().raw)
|
||||
: null
|
||||
|
||||
req.deviceId = deviceId
|
||||
req.deviceTime = req.get('date')
|
||||
|
|
@ -386,16 +386,16 @@ function populateSettings (req, res, next) {
|
|||
|
||||
if (!versionId) {
|
||||
return settingsLoader.loadLatest()
|
||||
.then(settings => { req.settings = settings })
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
.then(settings => { req.settings = settings })
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
settingsLoader.load(versionId)
|
||||
.then(settings => { req.settings = settings })
|
||||
.then(() => helpers.updateDeviceConfigVersion(versionId))
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
.then(settings => { req.settings = settings })
|
||||
.then(() => helpers.updateDeviceConfigVersion(versionId))
|
||||
.then(() => next())
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
module.exports = {app, localApp}
|
||||
|
|
|
|||
|
|
@ -21,15 +21,15 @@ function loadFixture () {
|
|||
const fixturePath = fixture => path.resolve(__dirname, '..', 'test', 'fixtures', fixture + '.json')
|
||||
|
||||
const promise = fixture
|
||||
? pify(fs.readFile)(fixturePath(fixture)).then(JSON.parse)
|
||||
: Promise.resolve([])
|
||||
? pify(fs.readFile)(fixturePath(fixture)).then(JSON.parse)
|
||||
: Promise.resolve([])
|
||||
|
||||
return promise
|
||||
.then(values => _.map(v => {
|
||||
return (v.fieldLocator.fieldScope.machine === 'machine')
|
||||
? _.set('fieldLocator.fieldScope.machine', machine, v)
|
||||
: v
|
||||
}, values))
|
||||
.then(values => _.map(v => {
|
||||
return (v.fieldLocator.fieldScope.machine === 'machine')
|
||||
? _.set('fieldLocator.fieldScope.machine', machine, v)
|
||||
: v
|
||||
}, values))
|
||||
}
|
||||
|
||||
function isEquivalentField (a, b) {
|
||||
|
|
@ -48,18 +48,18 @@ function load (versionId) {
|
|||
if (!versionId) throw new Error('versionId is required')
|
||||
|
||||
return Promise.all([loadConfig(versionId), loadAccounts()])
|
||||
.then(([config, accounts]) => ({
|
||||
config,
|
||||
accounts
|
||||
}))
|
||||
.then(([config, accounts]) => ({
|
||||
config,
|
||||
accounts
|
||||
}))
|
||||
}
|
||||
|
||||
function loadLatest () {
|
||||
return Promise.all([loadLatestConfig(), loadAccounts()])
|
||||
.then(([config, accounts]) => ({
|
||||
config,
|
||||
accounts
|
||||
}))
|
||||
.then(([config, accounts]) => ({
|
||||
config,
|
||||
accounts
|
||||
}))
|
||||
}
|
||||
|
||||
function loadConfig (versionId) {
|
||||
|
|
@ -71,15 +71,15 @@ function loadConfig (versionId) {
|
|||
and valid`
|
||||
|
||||
return db.one(sql, [versionId, 'config'])
|
||||
.then(row => row.data.config)
|
||||
.then(configValidate.validate)
|
||||
.catch(err => {
|
||||
if (err.name === 'QueryResultError') {
|
||||
throw new Error('No such config version: ' + versionId)
|
||||
}
|
||||
.then(row => row.data.config)
|
||||
.then(configValidate.validate)
|
||||
.catch(err => {
|
||||
if (err.name === 'QueryResultError') {
|
||||
throw new Error('No such config version: ' + versionId)
|
||||
}
|
||||
|
||||
throw err
|
||||
})
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function loadLatestConfig () {
|
||||
|
|
@ -93,15 +93,15 @@ function loadLatestConfig () {
|
|||
limit 1`
|
||||
|
||||
return db.one(sql, ['config'])
|
||||
.then(row => row.data.config)
|
||||
.then(configValidate.validate)
|
||||
.catch(err => {
|
||||
if (err.name === 'QueryResultError') {
|
||||
throw new Error('lamassu-server is not configured')
|
||||
}
|
||||
.then(row => row.data.config)
|
||||
.then(configValidate.validate)
|
||||
.catch(err => {
|
||||
if (err.name === 'QueryResultError') {
|
||||
throw new Error('lamassu-server is not configured')
|
||||
}
|
||||
|
||||
throw err
|
||||
})
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function loadRecentConfig () {
|
||||
|
|
@ -114,7 +114,7 @@ function loadRecentConfig () {
|
|||
limit 1`
|
||||
|
||||
return db.one(sql, ['config'])
|
||||
.then(row => row.data.config)
|
||||
.then(row => row.data.config)
|
||||
}
|
||||
|
||||
function loadAccounts () {
|
||||
|
|
@ -122,10 +122,10 @@ function loadAccounts () {
|
|||
const toPairs = r => [r.code, toFields(r.fields)]
|
||||
|
||||
return db.oneOrNone('select data from user_config where type=$1', 'accounts')
|
||||
.then(function (data) {
|
||||
if (!data) return {}
|
||||
return _.fromPairs(_.map(toPairs, data.data.accounts))
|
||||
})
|
||||
.then(function (data) {
|
||||
if (!data) return {}
|
||||
return _.fromPairs(_.map(toPairs, data.data.accounts))
|
||||
})
|
||||
}
|
||||
|
||||
function settings () {
|
||||
|
|
@ -136,8 +136,8 @@ function save (config) {
|
|||
const sql = 'insert into user_config (type, data, valid) values ($1, $2, $3)'
|
||||
|
||||
return configValidate.validate(config)
|
||||
.then(() => db.none(sql, ['config', {config}, true]))
|
||||
.catch(() => db.none(sql, ['config', {config}, false]))
|
||||
.then(() => db.none(sql, ['config', {config}, true]))
|
||||
.catch(() => db.none(sql, ['config', {config}, false]))
|
||||
}
|
||||
|
||||
function configAddField (scope, fieldCode, fieldType, fieldClass, value) {
|
||||
|
|
@ -222,11 +222,11 @@ function modifyConfig (newFields) {
|
|||
|
||||
function transaction (t) {
|
||||
return loadRecentConfig()
|
||||
.then(oldConfig => {
|
||||
const oldConfigWithDefaults = addCryptoDefaults(oldConfig, newFields)
|
||||
const doSave = _.flow(mergeValues, save)
|
||||
return doSave(oldConfigWithDefaults, newFields)
|
||||
})
|
||||
.then(oldConfig => {
|
||||
const oldConfigWithDefaults = addCryptoDefaults(oldConfig, newFields)
|
||||
const doSave = _.flow(mergeValues, save)
|
||||
return doSave(oldConfigWithDefaults, newFields)
|
||||
})
|
||||
}
|
||||
|
||||
transaction.txMode = tmSRD
|
||||
|
|
|
|||
12
lib/sms.js
12
lib/sms.js
|
|
@ -3,13 +3,13 @@ const ph = require('./plugin-helper')
|
|||
|
||||
function sendMessage (settings, rec) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const pluginCode = configManager.unscoped(settings.config).sms
|
||||
const plugin = ph.load(ph.SMS, pluginCode)
|
||||
const account = settings.accounts[pluginCode]
|
||||
.then(() => {
|
||||
const pluginCode = configManager.unscoped(settings.config).sms
|
||||
const plugin = ph.load(ph.SMS, pluginCode)
|
||||
const account = settings.accounts[pluginCode]
|
||||
|
||||
return plugin.sendMessage(account, rec)
|
||||
})
|
||||
return plugin.sendMessage(account, rec)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {sendMessage}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function get (id) {
|
|||
if (!id || _.isEmpty(id)) return Promise.resolve()
|
||||
const sql = 'select * from support_logs where id=$1'
|
||||
return db.oneOrNone(sql, [id])
|
||||
.then(_.mapKeys(_.camelCase))
|
||||
.then(_.mapKeys(_.camelCase))
|
||||
}
|
||||
/**
|
||||
* Insert a single support_logs row in db
|
||||
|
|
@ -34,7 +34,7 @@ function insert (deviceId) {
|
|||
const sql = `insert into support_logs
|
||||
(id, device_id) values ($1, $2) returning *`
|
||||
return db.one(sql, [uuid.v4(), deviceId])
|
||||
.then(_.mapKeys(_.camelCase))
|
||||
.then(_.mapKeys(_.camelCase))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -52,7 +52,7 @@ function batch () {
|
|||
where timestamp > (now() - interval '1 week')
|
||||
order by s.timestamp desc`
|
||||
return db.any(sql)
|
||||
.then(_.map(_.mapKeys(_.camelCase)))
|
||||
.then(_.map(_.mapKeys(_.camelCase)))
|
||||
}
|
||||
|
||||
module.exports = { get, insert, batch }
|
||||
|
|
|
|||
|
|
@ -9,29 +9,29 @@ const FETCH_INTERVAL = 10000
|
|||
|
||||
function _getRates (settings, fiatCode, cryptoCode) {
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
const config = settings.config
|
||||
const plugin = configManager.cryptoScoped(cryptoCode, config).ticker
|
||||
.then(() => {
|
||||
const config = settings.config
|
||||
const plugin = configManager.cryptoScoped(cryptoCode, config).ticker
|
||||
|
||||
const account = settings.accounts[plugin]
|
||||
const ticker = ph.load(ph.TICKER, plugin)
|
||||
const account = settings.accounts[plugin]
|
||||
const ticker = ph.load(ph.TICKER, plugin)
|
||||
|
||||
const market = [cryptoCode, fiatCode].join('-')
|
||||
const market = [cryptoCode, fiatCode].join('-')
|
||||
|
||||
return ticker.ticker(account, fiatCode, cryptoCode)
|
||||
.then(r => ({
|
||||
rates: r.rates,
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
.then(r => {
|
||||
lastRate[market] = r
|
||||
return r
|
||||
return ticker.ticker(account, fiatCode, cryptoCode)
|
||||
.then(r => ({
|
||||
rates: r.rates,
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
.then(r => {
|
||||
lastRate[market] = r
|
||||
return r
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
return lastRate[market]
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(err)
|
||||
return lastRate[market]
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getRates = mem(_getRates, {
|
||||
|
|
|
|||
32
lib/tx.js
32
lib/tx.js
|
|
@ -13,7 +13,7 @@ function process (tx, pi) {
|
|||
|
||||
function post (tx, pi) {
|
||||
return process(tx, pi)
|
||||
.then(_.set('dirty', false))
|
||||
.then(_.set('dirty', false))
|
||||
}
|
||||
|
||||
function massage (tx) {
|
||||
|
|
@ -24,17 +24,17 @@ function massage (tx) {
|
|||
|
||||
const mapBN = r => {
|
||||
const update = r.direction === 'cashIn'
|
||||
? {
|
||||
cryptoAtoms: BN(r.cryptoAtoms),
|
||||
fiat: BN(r.fiat),
|
||||
cashInFee: BN(r.cashInFee),
|
||||
cashInFeeCrypto: BN(r.cashInFeeCrypto),
|
||||
minimumTx: BN(r.minimumTx)
|
||||
}
|
||||
: {
|
||||
cryptoAtoms: BN(r.cryptoAtoms),
|
||||
fiat: BN(r.fiat)
|
||||
}
|
||||
? {
|
||||
cryptoAtoms: BN(r.cryptoAtoms),
|
||||
fiat: BN(r.fiat),
|
||||
cashInFee: BN(r.cashInFee),
|
||||
cashInFeeCrypto: BN(r.cashInFeeCrypto),
|
||||
minimumTx: BN(r.minimumTx)
|
||||
}
|
||||
: {
|
||||
cryptoAtoms: BN(r.cryptoAtoms),
|
||||
fiat: BN(r.fiat)
|
||||
}
|
||||
|
||||
return _.assign(r, update)
|
||||
}
|
||||
|
|
@ -51,10 +51,10 @@ function cancel (txId) {
|
|||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(r => {
|
||||
if (_.some(r)) return
|
||||
throw new Error('No such transaction')
|
||||
})
|
||||
.then(r => {
|
||||
if (_.some(r)) return
|
||||
throw new Error('No such transaction')
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {post, cancel}
|
||||
|
|
|
|||
104
lib/wallet.js
104
lib/wallet.js
|
|
@ -30,14 +30,14 @@ function computeSeed (masterSeed) {
|
|||
|
||||
function fetchWallet (settings, cryptoCode) {
|
||||
return fs.readFile(options.seedPath, 'utf8')
|
||||
.then(hex => {
|
||||
const masterSeed = Buffer.from(hex.trim(), 'hex')
|
||||
const plugin = configManager.cryptoScoped(cryptoCode, settings.config).wallet
|
||||
const wallet = ph.load(ph.WALLET, plugin)
|
||||
const account = settings.accounts[plugin]
|
||||
.then(hex => {
|
||||
const masterSeed = Buffer.from(hex.trim(), 'hex')
|
||||
const plugin = configManager.cryptoScoped(cryptoCode, settings.config).wallet
|
||||
const wallet = ph.load(ph.WALLET, plugin)
|
||||
const account = settings.accounts[plugin]
|
||||
|
||||
return {wallet, account: _.set('seed', computeSeed(masterSeed), account)}
|
||||
})
|
||||
return {wallet, account: _.set('seed', computeSeed(masterSeed), account)}
|
||||
})
|
||||
}
|
||||
|
||||
const lastBalance = {}
|
||||
|
|
@ -45,54 +45,54 @@ const lastBalance = {}
|
|||
function _balance (settings, cryptoCode) {
|
||||
logger.debug('Polled wallet balance')
|
||||
return fetchWallet(settings, cryptoCode)
|
||||
.then(r => r.wallet.balance(r.account, cryptoCode))
|
||||
.then(balance => ({balance, timestamp: Date.now()}))
|
||||
.then(r => {
|
||||
lastBalance[cryptoCode] = r
|
||||
return r
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
return lastBalance[cryptoCode]
|
||||
})
|
||||
.then(r => r.wallet.balance(r.account, cryptoCode))
|
||||
.then(balance => ({balance, timestamp: Date.now()}))
|
||||
.then(r => {
|
||||
lastBalance[cryptoCode] = r
|
||||
return r
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
return lastBalance[cryptoCode]
|
||||
})
|
||||
}
|
||||
|
||||
function sendCoins (settings, toAddress, cryptoAtoms, cryptoCode) {
|
||||
return fetchWallet(settings, cryptoCode)
|
||||
.then(r => {
|
||||
return r.wallet.sendCoins(r.account, toAddress, cryptoAtoms, cryptoCode)
|
||||
.then(res => {
|
||||
mem.clear(module.exports.balance)
|
||||
return res
|
||||
.then(r => {
|
||||
return r.wallet.sendCoins(r.account, toAddress, cryptoAtoms, cryptoCode)
|
||||
.then(res => {
|
||||
mem.clear(module.exports.balance)
|
||||
return res
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.name === INSUFFICIENT_FUNDS_NAME) {
|
||||
throw httpError(INSUFFICIENT_FUNDS_NAME, INSUFFICIENT_FUNDS_CODE)
|
||||
}
|
||||
.catch(err => {
|
||||
if (err.name === INSUFFICIENT_FUNDS_NAME) {
|
||||
throw httpError(INSUFFICIENT_FUNDS_NAME, INSUFFICIENT_FUNDS_CODE)
|
||||
}
|
||||
|
||||
throw err
|
||||
})
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function newAddress (settings, info) {
|
||||
return fetchWallet(settings, info.cryptoCode)
|
||||
.then(r => r.wallet.newAddress(r.account, info))
|
||||
.then(r => r.wallet.newAddress(r.account, info))
|
||||
}
|
||||
|
||||
function newFunding (settings, cryptoCode, address) {
|
||||
return fetchWallet(settings, cryptoCode)
|
||||
.then(r => {
|
||||
const wallet = r.wallet
|
||||
const account = r.account
|
||||
.then(r => {
|
||||
const wallet = r.wallet
|
||||
const account = r.account
|
||||
|
||||
return wallet.newFunding(account, cryptoCode)
|
||||
})
|
||||
return wallet.newFunding(account, cryptoCode)
|
||||
})
|
||||
}
|
||||
|
||||
function getWalletStatus (settings, tx) {
|
||||
return fetchWallet(settings, tx.cryptoCode)
|
||||
.then(r => r.wallet.getStatus(r.account, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode))
|
||||
.then(r => r.wallet.getStatus(r.account, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode))
|
||||
}
|
||||
|
||||
function authorizeZeroConf (settings, tx, machineId) {
|
||||
|
|
@ -115,34 +115,34 @@ function authorizeZeroConf (settings, tx, machineId) {
|
|||
|
||||
function getStatus (settings, tx, machineId) {
|
||||
return getWalletStatus(settings, tx)
|
||||
.then((statusRec) => {
|
||||
if (statusRec.status === 'authorized') {
|
||||
return authorizeZeroConf(settings, tx, machineId)
|
||||
.then(isAuthorized => {
|
||||
const publishAge = Date.now() - tx.publishedAt
|
||||
.then((statusRec) => {
|
||||
if (statusRec.status === 'authorized') {
|
||||
return authorizeZeroConf(settings, tx, machineId)
|
||||
.then(isAuthorized => {
|
||||
const publishAge = Date.now() - tx.publishedAt
|
||||
|
||||
const unauthorizedStatus = publishAge < ZERO_CONF_EXPIRATION
|
||||
? 'published'
|
||||
: 'rejected'
|
||||
const unauthorizedStatus = publishAge < ZERO_CONF_EXPIRATION
|
||||
? 'published'
|
||||
: 'rejected'
|
||||
|
||||
const status = isAuthorized ? 'authorized' : unauthorizedStatus
|
||||
const status = isAuthorized ? 'authorized' : unauthorizedStatus
|
||||
|
||||
return {status}
|
||||
})
|
||||
}
|
||||
return {status}
|
||||
})
|
||||
}
|
||||
|
||||
return statusRec
|
||||
})
|
||||
return statusRec
|
||||
})
|
||||
}
|
||||
|
||||
function sweep (settings, cryptoCode, hdIndex) {
|
||||
return fetchWallet(settings, cryptoCode)
|
||||
.then(r => r.wallet.sweep(r.account, cryptoCode, hdIndex))
|
||||
.then(r => r.wallet.sweep(r.account, cryptoCode, hdIndex))
|
||||
}
|
||||
|
||||
function isHd (settings, cryptoCode) {
|
||||
return fetchWallet(settings, cryptoCode)
|
||||
.then(r => r.wallet.supportsHd)
|
||||
.then(r => r.wallet.supportsHd)
|
||||
}
|
||||
|
||||
function cryptoNetwork (settings, cryptoCode) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ function singleQuotify (item) { return '\'' + item + '\'' }
|
|||
exports.up = function (next) {
|
||||
var statuses = ['notSeen', 'published', 'authorized', 'instant',
|
||||
'confirmed', 'rejected', 'insufficientFunds']
|
||||
.map(singleQuotify).join(',')
|
||||
.map(singleQuotify).join(',')
|
||||
|
||||
var sql = [
|
||||
'create type status_stage AS enum (' + statuses + ')',
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ function singleQuotify (item) { return '\'' + item + '\'' }
|
|||
|
||||
exports.up = function (next) {
|
||||
var actions = ['published', 'authorized', 'instant', 'confirmed', 'rejected',
|
||||
'insufficientFunds', 'dispenseRequested', 'dispensed', 'notified',
|
||||
'addedPhone', 'redeem']
|
||||
.map(singleQuotify).join(',')
|
||||
'insufficientFunds', 'dispenseRequested', 'dispensed', 'notified',
|
||||
'addedPhone', 'redeem']
|
||||
.map(singleQuotify).join(',')
|
||||
|
||||
var sql = [
|
||||
`create table cash_in_txs (
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ exports.up = function (next) {
|
|||
`insert into customers (id, name) VALUES ( '${anonymous.uuid}','${anonymous.name}' )`,
|
||||
`alter table cash_in_txs add column customer_id uuid references customers (id) DEFAULT '${anonymous.uuid}'`,
|
||||
`alter table cash_out_txs add column customer_id uuid references customers (id) DEFAULT '${anonymous.uuid}'`
|
||||
]
|
||||
]
|
||||
|
||||
db.multi(sql, next)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ var db = require('./db')
|
|||
exports.up = function (next) {
|
||||
const sql =
|
||||
[ "create type compliance_types as enum ('manual', 'sanctions', 'sanctions_override')",
|
||||
`create table compliance_authorizations (
|
||||
`create table compliance_authorizations (
|
||||
id uuid PRIMARY KEY,
|
||||
customer_id uuid REFERENCES customers (id),
|
||||
compliance_type compliance_types NOT NULL,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ exports.up = function (next) {
|
|||
id uuid PRIMARY KEY,
|
||||
device_id text,
|
||||
timestamp timestamptz not null default now() )`,
|
||||
'alter table logs add column server_timestamp timestamptz not null default now() '
|
||||
'alter table logs add column server_timestamp timestamptz not null default now() '
|
||||
]
|
||||
|
||||
db.multi(sql, next)
|
||||
|
|
|
|||
|
|
@ -3,23 +3,23 @@ const migrateTools = require('./migrate-tools')
|
|||
|
||||
exports.up = function (next) {
|
||||
return migrateTools.migrateNames()
|
||||
.then(updateSql => {
|
||||
const sql = [
|
||||
'alter table devices add column name text',
|
||||
updateSql,
|
||||
'alter table devices alter column name set not null'
|
||||
]
|
||||
.then(updateSql => {
|
||||
const sql = [
|
||||
'alter table devices add column name text',
|
||||
updateSql,
|
||||
'alter table devices alter column name set not null'
|
||||
]
|
||||
|
||||
return db.multi(sql, next)
|
||||
})
|
||||
.catch(() => {
|
||||
const sql = [
|
||||
'alter table devices add column name text',
|
||||
'alter table devices alter column name set not null'
|
||||
]
|
||||
return db.multi(sql, next)
|
||||
})
|
||||
.catch(() => {
|
||||
const sql = [
|
||||
'alter table devices add column name text',
|
||||
'alter table devices alter column name set not null'
|
||||
]
|
||||
|
||||
return db.multi(sql, next)
|
||||
})
|
||||
return db.multi(sql, next)
|
||||
})
|
||||
}
|
||||
|
||||
exports.down = function (next) {
|
||||
|
|
|
|||
|
|
@ -7,17 +7,17 @@ function multi (sqls, cb) {
|
|||
const doQuery = s => {
|
||||
return () => {
|
||||
return db.none(s)
|
||||
.catch(err => {
|
||||
console.log(err.stack)
|
||||
throw err
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err.stack)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return sequential(sqls.map(doQuery))
|
||||
.then(() => cb())
|
||||
.catch(err => {
|
||||
console.log(err.stack)
|
||||
cb(err)
|
||||
})
|
||||
.then(() => cb())
|
||||
.catch(err => {
|
||||
console.log(err.stack)
|
||||
cb(err)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ function migrateNames () {
|
|||
const cs = new pgp.helpers.ColumnSet(['device_id', 'name'], {table: 'devices'})
|
||||
|
||||
return settingsLoader.loadLatest()
|
||||
.then(r => machineLoader.getMachineNames(r.config))
|
||||
.then(_.map(r => ({device_id: r.deviceId, name: r.name})))
|
||||
.then(data => pgp.helpers.update(data, cs))
|
||||
.then(r => machineLoader.getMachineNames(r.config))
|
||||
.then(_.map(r => ({device_id: r.deviceId, name: r.name})))
|
||||
.then(data => pgp.helpers.update(data, cs))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@ test('bigger merge', t => {
|
|||
)
|
||||
|
||||
const expected = [
|
||||
{fieldLocator: fieldLocator1, fieldValue: fieldValue4},
|
||||
{fieldLocator: fieldLocator4, fieldValue: fieldValue5},
|
||||
{fieldLocator: fieldLocator2, fieldValue: fieldValue2},
|
||||
{fieldLocator: fieldLocator3, fieldValue: fieldValue3}
|
||||
{fieldLocator: fieldLocator1, fieldValue: fieldValue4},
|
||||
{fieldLocator: fieldLocator4, fieldValue: fieldValue5},
|
||||
{fieldLocator: fieldLocator2, fieldValue: fieldValue2},
|
||||
{fieldLocator: fieldLocator3, fieldValue: fieldValue3}
|
||||
]
|
||||
|
||||
t.deepEqual(merged, expected)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const rawCountries = require('../raw-countries.json')
|
|||
const topCodes = ['US', 'GB', 'CA', 'AU']
|
||||
|
||||
const countries = rawCountries
|
||||
.map(r => ({code: r.cca2, display: r.name.common}))
|
||||
.map(r => ({code: r.cca2, display: r.name.common}))
|
||||
|
||||
const topCountries = topCodes.map(c => countries.find(_.matchesProperty('code', c)))
|
||||
const final = _.uniqBy(_.get('code'), _.concat(topCountries, countries))
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const fields = [
|
|||
]
|
||||
|
||||
settingsLoader.modifyConfig(fields)
|
||||
.then(() => {
|
||||
console.log('success.')
|
||||
process.exit(0)
|
||||
})
|
||||
.then(() => {
|
||||
console.log('success.')
|
||||
process.exit(0)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@ function dbFetchConfig () {
|
|||
'select data from user_config where type=$1 order by id desc limit 1',
|
||||
['config']
|
||||
)
|
||||
.then(row => row && row.data)
|
||||
.then(row => row && row.data)
|
||||
}
|
||||
|
||||
dbFetchConfig()
|
||||
.then(config => {
|
||||
pp(config)
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e)
|
||||
process.exit(1)
|
||||
})
|
||||
.then(config => {
|
||||
pp(config)
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e)
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue