format for latest standard

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

View file

@ -7,44 +7,44 @@ var db = pgp(psqlUrl)
db.manyOrNone(`select * from transactions where incoming=false
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()
})

View file

@ -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)
})

View file

@ -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)
})
})

View file

@ -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)

View file

@ -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)
})

View file

@ -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)
})
})

View file

@ -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 () {

View file

@ -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 => {

View file

@ -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) {

View file

@ -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 = {

View file

@ -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
}
})
})
})
}

View file

@ -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) {

View file

@ -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}

View file

@ -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}

View file

@ -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}

View file

@ -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}

View file

@ -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)
})
}

View file

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

View file

@ -17,21 +17,21 @@ function atomic (machineTx, pi) {
const sql2 = 'select * from bills where cash_in_txs_id=$1'
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) {

View file

@ -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) {

View file

@ -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))
}

View file

@ -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) {

View file

@ -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,

View file

@ -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))
}

View file

@ -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) {

View file

@ -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))
}

View file

@ -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}

View file

@ -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 }

View file

@ -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}

View file

@ -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) {

View file

@ -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 }

View file

@ -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) {

View file

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

View file

@ -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) {

View file

@ -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}

View file

@ -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 () {

View file

@ -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 = {

View file

@ -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) {

View file

@ -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)
}

View file

@ -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,

View file

@ -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 = {

View file

@ -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 = {

View file

@ -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 = {

View file

@ -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
}

View file

@ -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])
}
}
}
})
})
}

View file

@ -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 = {

View file

@ -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 = {

View file

@ -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 = {

View file

@ -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 = {

View file

@ -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
}))
})
}

View file

@ -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 = {

View file

@ -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'}
})
})
}

View file

@ -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) {

View file

@ -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 = {

View file

@ -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)
})
})
}

View file

@ -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
})
}

View file

@ -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 () {

View file

@ -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 = {

View file

@ -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}

View file

@ -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

View file

@ -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}

View file

@ -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 }

View file

@ -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, {

View file

@ -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}

View file

@ -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) {

View file

@ -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 + ')',

View file

@ -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 (

View file

@ -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)
}

View file

@ -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,

View file

@ -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)

View file

@ -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) {

View file

@ -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)
})
}

View file

@ -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))
}

View file

@ -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)

View file

@ -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))

View file

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

View file

@ -11,15 +11,15 @@ function dbFetchConfig () {
'select data from user_config where type=$1 order by id desc limit 1',
['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)
})