Feat: save highVolumeTxs on DB and plugins code refactor

Fix: remove unused module and add space before '('

Chore: add jest tests for transactionNotify
This commit is contained in:
Cesar 2020-12-04 19:58:17 +00:00 committed by Josh Harvey
parent 366adad375
commit 096c4bc87b
4 changed files with 479 additions and 318 deletions

View file

@ -84,4 +84,8 @@ function emailAlert (alert) {
const sendMessage = email.sendMessage const sendMessage = email.sendMessage
<<<<<<< HEAD
=======
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
module.exports = { alertSubject, printEmailAlerts, sendMessage } module.exports = { alertSubject, printEmailAlerts, sendMessage }

View file

@ -2,6 +2,8 @@ const BigNumber = require('../../../lib/bn')
const notifier = require('..') const notifier = require('..')
const utils = require('../utils') const utils = require('../utils')
const queries = require("../queries")
const emailFuncs = require('../email')
const smsFuncs = require('../sms') const smsFuncs = require('../sms')
afterEach(() => { afterEach(() => {
@ -81,79 +83,76 @@ const notifSettings = {
email_errors: false, email_errors: false,
sms_errors: true, sms_errors: true,
sms_transactions: true, sms_transactions: true,
highValueTransaction: Infinity, // this will make highValueTx always false highValueTransaction: Infinity, //this will make highValueTx always false
sms: { sms: {
active: true, active: true,
errors: true, errors: true,
transactions: false // force early return transactions: false // force early return
}, },
email: { email: {
active: false, active: false,
errors: false, errors: false,
transactions: false // force early return transactions: false // force early return
} }
} }
describe('checkNotifications', () => { test('Exits checkNotifications with Promise.resolve() if SMS and Email are disabled', async () => {
test('Exits checkNotifications with Promise.resolve() if SMS and Email are disabled', async () => { expect.assertions(1)
expect.assertions(1) await expect(
await expect( notifier.checkNotification({
notifier.checkNotification({ getNotificationConfig: () => ({
getNotificationConfig: () => ({ sms: { active: false, errors: false },
sms: { active: false, errors: false }, email: { active: false, errors: false }
email: { active: false, errors: false }
})
}) })
).resolves.toBe(undefined) })
}) ).resolves.toBe(undefined)
})
test('Exits checkNotifications with Promise.resolve() if SMS and Email are disabled even if errors or balance are defined to something', async () => {
expect.assertions(1) test('Exits checkNotifications with Promise.resolve() if SMS and Email are disabled even if errors or balance are defined to something', async () => {
await expect( expect.assertions(1)
notifier.checkNotification({ await expect(
getNotificationConfig: () => ({ notifier.checkNotification({
sms: { active: false, errors: true, balance: true }, getNotificationConfig: () => ({
email: { active: false, errors: true, balance: true } sms: { active: false, errors: true, balance: true },
}) email: { active: false, errors: true, balance: true }
}) })
).resolves.toBe(undefined) })
).resolves.toBe(undefined)
})
test("Check Pings should return code PING for devices that haven't been pinged recently", () => {
expect(
notifier.checkPings([
{
deviceId:
'7e531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4',
lastPing: '2020-11-16T13:11:03.169Z',
name: 'Abc123'
}
])
).toMatchObject({
'7e531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4': [
{ code: 'PING', machineName: 'Abc123' }
]
}) })
}) })
describe('checkPings', () => { test('Checkpings returns empty array as the value for the id prop, if the lastPing is more recent than 60 seconds', () => {
test("Check Pings should return code PING for devices that haven't been pinged recently", () => { expect(
expect( notifier.checkPings([
notifier.checkPings([ {
{ deviceId:
deviceId: '7a531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4',
'7e531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4', lastPing: new Date(),
lastPing: '2020-11-16T13:11:03.169Z', name: 'Abc123'
name: 'Abc123' }
} ])
]) ).toMatchObject({
).toMatchObject({ '7a531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4': []
'7e531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4': [
{ code: 'PING', machineName: 'Abc123' }
]
})
})
test('Checkpings returns empty array as the value for the id prop, if the lastPing is more recent than 60 seconds', () => {
expect(
notifier.checkPings([
{
deviceId:
'7a531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4',
lastPing: new Date(),
name: 'Abc123'
}
])
).toMatchObject({
'7a531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4': []
})
}) })
}) })
test('Check notification resolves to undefined if shouldNotAlert is called and is true', async () => { test('Check notification resolves to undefined if shouldNotAlert is called and is true', async () => {
const mockShouldNotAlert = jest.spyOn(utils, 'shouldNotAlert') const mockShouldNotAlert = jest.spyOn(utils, 'shouldNotAlert')
mockShouldNotAlert.mockReturnValue(true) mockShouldNotAlert.mockReturnValue(true)
@ -191,42 +190,25 @@ test('If no alert fingerprint and inAlert is true, exits on call to sendNoAlerts
expect(mockSendNoAlerts).toHaveBeenCalledTimes(1) expect(mockSendNoAlerts).toHaveBeenCalledTimes(1)
}) })
describe('checkStuckScreen', () => { // vvv tests for checkstuckscreen...
test('checkStuckScreen returns [] when no events are found', () => { test('checkStuckScreen returns [] when no events are found', () => {
expect(notifier.checkStuckScreen([], 'Abc123')).toEqual([]) expect(notifier.checkStuckScreen([], 'Abc123')).toEqual([])
}) })
test('checkStuckScreen returns [] if most recent event is idle', () => { test('checkStuckScreen returns [] if most recent event is idle', () => {
// device_time is what matters for the sorting of the events by recency // device_time is what matters for the sorting of the events by recency
expect( expect(
notifier.checkStuckScreen([ notifier.checkStuckScreen([
{ {
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2', id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
device_id: device_id:
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05', 'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
event_type: 'stateChange', event_type: 'stateChange',
note: '{"state":"chooseCoin","isIdle":false}', note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z', created: '2020-11-23T19:30:29.209Z',
device_time: '1999-11-23T19:30:29.177Z', device_time: '1999-11-23T19:30:29.177Z',
age: 157352628.123 age: 157352628.123
}, },
{
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
device_id:
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
event_type: 'stateChange',
note: '{"state":"chooseCoin","isIdle":true}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z',
age: 157352628.123
}
])
).toEqual([])
})
test('checkStuckScreen returns object array of length 1 with prop code: "STALE" if age > STALE_STATE', () => {
// there is an age 0 and an isIdle true in the first object but it will be below the second one in the sorting order and thus ignored
const result = notifier.checkStuckScreen([
{ {
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2', id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
device_id: device_id:
@ -234,106 +216,122 @@ describe('checkStuckScreen', () => {
event_type: 'stateChange', event_type: 'stateChange',
note: '{"state":"chooseCoin","isIdle":true}', note: '{"state":"chooseCoin","isIdle":true}',
created: '2020-11-23T19:30:29.209Z', created: '2020-11-23T19:30:29.209Z',
device_time: '1999-11-23T19:30:29.177Z',
age: 0
},
{
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
device_id:
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
event_type: 'stateChange',
note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z', device_time: '2020-11-23T19:30:29.177Z',
age: 157352628.123 age: 157352628.123
} }
]) ])
expect(result[0]).toMatchObject({ code: 'STALE' }) ).toEqual([])
})
test('checkStuckScreen returns empty array if age < STALE_STATE', () => {
const STALE_STATE = require('../codes').STALE_STATE
const result1 = notifier.checkStuckScreen([
{
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
device_id:
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
event_type: 'stateChange',
note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z',
age: 0
}
])
const result2 = notifier.checkStuckScreen([
{
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
device_id:
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
event_type: 'stateChange',
note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z',
age: STALE_STATE
}
])
expect(result1).toEqual([])
expect(result2).toEqual([])
})
}) })
test('calls sendRedemptionMessage if !zeroConf and rec.isRedemption', () => { test('checkStuckScreen returns object array of length 1 with prop code: "STALE" if age > STALE_STATE', () => {
// there is an age 0 and an isIdle true in the first object but it will be below the second one in the sorting order and thus ignored
const result = notifier.checkStuckScreen([
{
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
device_id:
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
event_type: 'stateChange',
note: '{"state":"chooseCoin","isIdle":true}',
created: '2020-11-23T19:30:29.209Z',
device_time: '1999-11-23T19:30:29.177Z',
age: 0
},
{
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
device_id:
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
event_type: 'stateChange',
note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z',
age: 157352628.123
}
])
expect(result[0]).toMatchObject({ code: 'STALE' })
})
test('checkStuckScreen returns empty array if age < STALE_STATE', () => {
const STALE_STATE = require('../codes').STALE_STATE
const result1 = notifier.checkStuckScreen([
{
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
device_id:
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
event_type: 'stateChange',
note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z',
age: 0
}
])
const result2 = notifier.checkStuckScreen([
{
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
device_id:
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
event_type: 'stateChange',
note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z',
age: STALE_STATE
}
])
expect(result1).toEqual([])
expect(result2).toEqual([])
})
test("calls sendRedemptionMessage if !zeroConf and rec.isRedemption", async () => {
const configManager = require('../../new-config-manager') const configManager = require('../../new-config-manager')
const settingsLoader = require('../../new-settings-loader') const settingsLoader = require('../../new-settings-loader')
const loadLatest = jest.spyOn(settingsLoader, 'loadLatest') const loadLatest = jest.spyOn(settingsLoader, 'loadLatest')
const getGlobalNotifications = jest.spyOn(configManager, 'getGlobalNotifications') const getGlobalNotifications = jest.spyOn(configManager, 'getGlobalNotifications')
const getCashOut = jest.spyOn(configManager, 'getCashOut') const getCashOut = jest.spyOn(configManager, 'getCashOut')
// sendRedemptionMessage will cause this func to be called // sendRedemptionMessage will cause this func to be called
jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => rec) jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => rec)
getCashOut.mockReturnValue({ zeroConfLimit: -Infinity }) getCashOut.mockReturnValue({zeroConfLimit: -Infinity})
loadLatest.mockReturnValue(Promise.resolve({})) loadLatest.mockReturnValue({})
getGlobalNotifications.mockReturnValue({ ...notifSettings, sms: { active: true, errors: true, transactions: true } }) getGlobalNotifications.mockReturnValue({... notifSettings, sms: { active: true, errors: true, transactions: true }})
return notifier.transactionNotify(tx, { isRedemption: true }).then(response => { const response = await notifier.transactionNotify(tx, {isRedemption: true})
// this type of response implies sendRedemptionMessage was called
expect(response[0]).toMatchObject({ // this type of response implies sendRedemptionMessage was called
sms: { expect(response[0]).toMatchObject({
body: "Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00 - It was just dispensed successfully" sms: {
}, body: "Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00 - It was just dispensed successfully"
email: { },
subject: "Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00", email: {
body: 'It was just dispensed successfully' subject: "Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00",
} body: 'It was just dispensed successfully'
}) }
}) })
}) })
test('calls sendTransactionMessage if !zeroConf and !rec.isRedemption', async () => { test("calls sendTransactionMessage if !zeroConf and !rec.isRedemption", async () => {
const configManager = require('../../new-config-manager') const configManager = require('../../new-config-manager')
const settingsLoader = require('../../new-settings-loader') const settingsLoader = require('../../new-settings-loader')
const machineLoader = require('../../machine-loader') const machineLoader = require('../../machine-loader')
const loadLatest = jest.spyOn(settingsLoader, 'loadLatest') const loadLatest = jest.spyOn(settingsLoader, 'loadLatest')
const getGlobalNotifications = jest.spyOn(configManager, 'getGlobalNotifications') const getGlobalNotifications = jest.spyOn(configManager, 'getGlobalNotifications')
const getCashOut = jest.spyOn(configManager, 'getCashOut') const getCashOut = jest.spyOn(configManager, 'getCashOut')
const getMachineName = jest.spyOn(machineLoader, 'getMachineName') const getMachineName = jest.spyOn(machineLoader, 'getMachineName')
const buildTransactionMessage = jest.spyOn(utils, 'buildTransactionMessage') const buildTransactionMessage = jest.spyOn(utils, 'buildTransactionMessage')
// sendMessage on emailFuncs isn't called because it is disabled in getGlobalNotifications.mockReturnValue // sendMessage on emailFuncs isn't called because it is disabled in getGlobalNotifications.mockReturnValue
jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => ({ prop: rec })) jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => ({prop: rec}))
buildTransactionMessage.mockImplementation(() => ['mock message', false]) buildTransactionMessage.mockImplementation(() => ["mock message", false])
getMachineName.mockReturnValue('mockMachineName') getMachineName.mockReturnValue("mockMachineName")
getCashOut.mockReturnValue({ zeroConfLimit: -Infinity }) getCashOut.mockReturnValue({zeroConfLimit: -Infinity})
loadLatest.mockReturnValue(Promise.resolve({})) loadLatest.mockReturnValue({})
getGlobalNotifications.mockReturnValue({ ...notifSettings, sms: { active: true, errors: true, transactions: true } }) getGlobalNotifications.mockReturnValue({... notifSettings, sms: { active: true, errors: true, transactions: true }})
const response = await notifier.transactionNotify(tx, { isRedemption: false }) const response = await notifier.transactionNotify(tx, {isRedemption: false})
// If the return object is this, it means the code went through all the functions expected to go through if // If the return object is this, it means the code went through all the functions expected to go through if
// getMachineName, buildTransactionMessage and sendTransactionMessage were called, in this order // getMachineName, buildTransactionMessage and sendTransactionMessage were called, in this order
expect(response).toEqual([{ prop: 'mock message' }]) expect(response).toEqual([{prop: 'mock message'}])
}) })

View file

@ -108,8 +108,13 @@ const buildTransactionMessage = (tx, rec, highValueTx, machineName, customer) =>
status = !isCashOut status = !isCashOut
? 'Successful' ? 'Successful'
: !rec.isRedemption : !rec.isRedemption
<<<<<<< HEAD
? 'Successful & awaiting redemption' ? 'Successful & awaiting redemption'
: 'Successful & dispensed' : 'Successful & dispensed'
=======
? 'Successful & awaiting redemption'
: 'Successful & dispensed'
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
} }
const body = ` const body = `
@ -136,6 +141,7 @@ const buildTransactionMessage = (tx, rec, highValueTx, machineName, customer) =>
}, highValueTx] }, highValueTx]
} }
<<<<<<< HEAD
function formatCurrency (num, code) { function formatCurrency (num, code) {
return numeral(num).format('0,0.00') + ' ' + code return numeral(num).format('0,0.00') + ' ' + code
} }
@ -180,6 +186,8 @@ function getAlertTypes (alertRec, config) {
return alerts return alerts
} }
=======
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
module.exports = { module.exports = {
codeDisplay, codeDisplay,
parseEventNote, parseEventNote,
@ -192,9 +200,13 @@ module.exports = {
shouldNotAlert, shouldNotAlert,
buildAlertFingerprint, buildAlertFingerprint,
sendNoAlerts, sendNoAlerts,
<<<<<<< HEAD
buildTransactionMessage, buildTransactionMessage,
formatCurrency, formatCurrency,
formatAge, formatAge,
buildDetail, buildDetail,
deviceAlerts deviceAlerts
=======
buildTransactionMessage
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
} }

View file

@ -25,6 +25,8 @@ const promoCodes = require('./promo-codes')
const notifier = require('./notifier') const notifier = require('./notifier')
const notifier = require('./notifier/index')
const mapValuesWithKey = _.mapValues.convert({ const mapValuesWithKey = _.mapValues.convert({
cap: false cap: false
}) })
@ -55,7 +57,8 @@ function plugins (settings, deviceId) {
? undefined ? undefined
: BN(1).add(BN(commissions.cashOut).div(100)) : BN(1).add(BN(commissions.cashOut).div(100))
if (Date.now() - rateRec.timestamp > STALE_TICKER) return logger.warn('Stale rate for ' + cryptoCode) if (Date.now() - rateRec.timestamp > STALE_TICKER)
return logger.warn('Stale rate for ' + cryptoCode)
const rate = rateRec.rates const rate = rateRec.rates
withCommission ? rates[cryptoCode] = { withCommission ? rates[cryptoCode] = {
@ -89,8 +92,10 @@ function plugins (settings, deviceId) {
cryptoCodes.forEach((cryptoCode, i) => { cryptoCodes.forEach((cryptoCode, i) => {
const balanceRec = balanceRecs[i] const balanceRec = balanceRecs[i]
if (!balanceRec) return logger.warn('No balance for ' + cryptoCode + ' yet') if (!balanceRec)
if (Date.now() - balanceRec.timestamp > STALE_BALANCE) return logger.warn('Stale balance for ' + cryptoCode) return logger.warn('No balance for ' + cryptoCode + ' yet')
if (Date.now() - balanceRec.timestamp > STALE_BALANCE)
return logger.warn('Stale balance for ' + cryptoCode)
balances[cryptoCode] = balanceRec.balance balances[cryptoCode] = balanceRec.balance
}) })
@ -110,10 +115,13 @@ function plugins (settings, deviceId) {
const sumTxs = (sum, tx) => { const sumTxs = (sum, tx) => {
const bills = tx.bills const bills = tx.bills
const sameDenominations = a => a[0].denomination === a[1].denomination const sameDenominations = a => a[0].denomination === a[1].denomination
const doDenominationsMatch = _.every(sameDenominations, _.zip(cassettes, bills)) const doDenominationsMatch = _.every(
sameDenominations,
_.zip(cassettes, bills)
)
if (!doDenominationsMatch) { if (!doDenominationsMatch) {
throw new Error('Denominations don\'t add up, cassettes were changed.') throw new Error("Denominations don't add up, cassettes were changed.")
} }
return _.map(r => r[0] + r[1].provisioned, _.zip(sum, tx.bills)) return _.map(r => r[0] + r[1].provisioned, _.zip(sum, tx.bills))
@ -139,6 +147,21 @@ function plugins (settings, deviceId) {
] ]
} }
<<<<<<< HEAD
=======
function getLcmOrBigx2 (n1, n2) {
let big = Math.max(n1, n2)
let small = Math.min(n1, n2)
let i = big * 2
while (i % small !== 0) {
i += lar
}
return i
}
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
function buildAvailableCassettes (excludeTxId) { function buildAvailableCassettes (excludeTxId) {
const cashOutConfig = configManager.getCashOut(deviceId, settings.config) const cashOutConfig = configManager.getCashOut(deviceId, settings.config)
@ -146,6 +169,7 @@ function plugins (settings, deviceId) {
const denominations = [cashOutConfig.top, cashOutConfig.bottom] const denominations = [cashOutConfig.top, cashOutConfig.bottom]
<<<<<<< HEAD
const virtualCassettes = [Math.max(cashOutConfig.top, cashOutConfig.bottom) * 2] const virtualCassettes = [Math.max(cashOutConfig.top, cashOutConfig.bottom) * 2]
return Promise.all([dbm.cassetteCounts(deviceId), cashOutHelper.redeemableTxs(deviceId, excludeTxId)]) return Promise.all([dbm.cassetteCounts(deviceId), cashOutHelper.redeemableTxs(deviceId, excludeTxId)])
@ -155,31 +179,47 @@ function plugins (settings, deviceId) {
const counts = argv.cassettes const counts = argv.cassettes
? argv.cassettes.split(',') ? argv.cassettes.split(',')
: rec.counts : rec.counts
=======
const virtualCassettes = [
getLcmOrBigx2(cashOutConfig.top, cashOutConfig.bottom)
]
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
const cassettes = [ return Promise.all([
{ dbm.cassetteCounts(deviceId),
denomination: parseInt(denominations[0], 10), cashOutHelper.redeemableTxs(deviceId, excludeTxId)
count: parseInt(counts[0], 10) ]).then(([rec, _redeemableTxs]) => {
}, const redeemableTxs = _.reject(
{ _.matchesProperty('id', excludeTxId),
denomination: parseInt(denominations[1], 10), _redeemableTxs
count: parseInt(counts[1], 10) )
}
]
try { const counts = argv.cassettes ? argv.cassettes.split(',') : rec.counts
return {
cassettes: computeAvailableCassettes(cassettes, redeemableTxs), const cassettes = [
virtualCassettes {
} denomination: parseInt(denominations[0], 10),
} catch (err) { count: parseInt(counts[0], 10)
logger.error(err) },
return { {
cassettes, denomination: parseInt(denominations[1], 10),
virtualCassettes count: parseInt(counts[1], 10)
}
} }
}) ]
try {
return {
cassettes: computeAvailableCassettes(cassettes, redeemableTxs),
virtualCassettes
}
} catch (err) {
logger.error(err)
return {
cassettes,
virtualCassettes
}
}
})
} }
function fetchCurrentConfigVersion () { function fetchCurrentConfigVersion () {
@ -189,18 +229,23 @@ function plugins (settings, deviceId) {
order by id desc order by id desc
limit 1` limit 1`
return db.one(sql, ['config']) return db.one(sql, ['config']).then(row => row.id)
.then(row => row.id)
} }
function mapCoinSettings (coinParams) { function mapCoinSettings (coinParams) {
const cryptoCode = coinParams[0] const cryptoCode = coinParams[0]
const cryptoNetwork = coinParams[1] const cryptoNetwork = coinParams[1]
const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) const commissions = configManager.getCommissions(
cryptoCode,
deviceId,
settings.config
)
const minimumTx = BN(commissions.minimumTx) const minimumTx = BN(commissions.minimumTx)
const cashInFee = BN(commissions.fixedFee) const cashInFee = BN(commissions.fixedFee)
const cashInCommission = BN(commissions.cashIn) const cashInCommission = BN(commissions.cashIn)
const cashOutCommission = _.isNumber(commissions.cashOut) ? BN(commissions.cashOut) : null const cashOutCommission = _.isNumber(commissions.cashOut)
? BN(commissions.cashOut)
: null
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
return { return {
@ -214,15 +259,25 @@ function plugins (settings, deviceId) {
} }
} }
function pollQueries (serialNumber, deviceTime, deviceRec, machineVersion, machineModel) { function pollQueries (
serialNumber,
deviceTime,
deviceRec,
machineVersion,
machineModel
) {
const localeConfig = configManager.getLocale(deviceId, settings.config) const localeConfig = configManager.getLocale(deviceId, settings.config)
const fiatCode = localeConfig.fiatCurrency const fiatCode = localeConfig.fiatCurrency
const cryptoCodes = localeConfig.cryptoCurrencies const cryptoCodes = localeConfig.cryptoCurrencies
const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c)) const tickerPromises = cryptoCodes.map(c =>
ticker.getRates(settings, fiatCode, c)
)
const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c)) const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c))
const testnetPromises = cryptoCodes.map(c => wallet.cryptoNetwork(settings, c)) const testnetPromises = cryptoCodes.map(c =>
wallet.cryptoNetwork(settings, c)
)
const pingPromise = recordPing(deviceTime, machineVersion, machineModel) const pingPromise = recordPing(deviceTime, machineVersion, machineModel)
const currentConfigVersionPromise = fetchCurrentConfigVersion() const currentConfigVersionPromise = fetchCurrentConfigVersion()
const currentAvailablePromoCodes = promoCodes.getNumberOfAvailablePromoCodes() const currentAvailablePromoCodes = promoCodes.getNumberOfAvailablePromoCodes()
@ -233,6 +288,7 @@ function plugins (settings, deviceId) {
currentConfigVersionPromise currentConfigVersionPromise
].concat(tickerPromises, balancePromises, testnetPromises, currentAvailablePromoCodes) ].concat(tickerPromises, balancePromises, testnetPromises, currentAvailablePromoCodes)
<<<<<<< HEAD
return Promise.all(promises) return Promise.all(promises)
.then(arr => { .then(arr => {
const cassettes = arr[0] const cassettes = arr[0]
@ -254,10 +310,35 @@ function plugins (settings, deviceId) {
areThereAvailablePromoCodes areThereAvailablePromoCodes
} }
}) })
=======
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)
const coinsWithoutRate = _.map(mapCoinSettings, coinParams)
return {
cassettes,
rates: buildRates(tickers),
balances: buildBalances(balances),
coins: _.zipWith(_.assign, coinsWithoutRate, tickers),
configVersion
}
})
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
} }
function sendCoins (tx) { function sendCoins (tx) {
return wallet.sendCoins(settings, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode) return wallet.sendCoins(
settings,
tx.toAddress,
tx.cryptoAtoms,
tx.cryptoCode
)
} }
function recordPing (deviceTime, version, model) { function recordPing (deviceTime, version, model) {
@ -268,11 +349,18 @@ function plugins (settings, deviceId) {
} }
return Promise.all([ return Promise.all([
db.none(`insert into machine_pings(device_id, device_time) values($1, $2) db.none(
ON CONFLICT (device_id) DO UPDATE SET device_time = $2, updated = now()`, [deviceId, deviceTime]), `insert into machine_pings(device_id, device_time) values($1, $2)
db.none(pgp.helpers.update(devices, null, 'devices') + 'WHERE device_id = ${deviceId}', { ON CONFLICT (device_id) DO UPDATE SET device_time = $2, updated = now()`,
deviceId [deviceId, deviceTime]
}) ),
db.none(
pgp.helpers.update(devices, null, 'devices') +
'WHERE device_id = ${deviceId}',
{
deviceId
}
)
]) ])
} }
@ -304,34 +392,37 @@ function plugins (settings, deviceId) {
} }
function fiatBalance (fiatCode, cryptoCode) { function fiatBalance (fiatCode, cryptoCode) {
const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) const commissions = configManager.getCommissions(
cryptoCode,
deviceId,
settings.config
)
return Promise.all([ return Promise.all([
ticker.getRates(settings, fiatCode, cryptoCode), ticker.getRates(settings, fiatCode, cryptoCode),
wallet.balance(settings, cryptoCode) wallet.balance(settings, cryptoCode)
]) ]).then(([rates, balanceRec]) => {
.then(([rates, balanceRec]) => { if (!rates || !balanceRec) return null
if (!rates || !balanceRec) return null
const rawRate = rates.rates.ask const rawRate = rates.rates.ask
const cashInCommission = BN(1).minus(BN(commissions.cashIn).div(100)) const cashInCommission = BN(1).minus(BN(commissions.cashIn).div(100))
const balance = balanceRec.balance const balance = balanceRec.balance
if (!rawRate || !balance) return null if (!rawRate || !balance) return null
const rate = rawRate.div(cashInCommission) const rate = rawRate.div(cashInCommission)
const lowBalanceMargin = BN(1.03) const lowBalanceMargin = BN(1.03)
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
const unitScale = cryptoRec.unitScale const unitScale = cryptoRec.unitScale
const shiftedRate = rate.shift(-unitScale) const shiftedRate = rate.shift(-unitScale)
const fiatTransferBalance = balance.mul(shiftedRate).div(lowBalanceMargin) const fiatTransferBalance = balance.mul(shiftedRate).div(lowBalanceMargin)
return { return {
timestamp: balanceRec.timestamp, timestamp: balanceRec.timestamp,
balance: fiatTransferBalance.truncated().toString() balance: fiatTransferBalance.truncated().toString()
} }
}) })
} }
function notifyConfirmation (tx) { function notifyConfirmation (tx) {
@ -346,13 +437,12 @@ function plugins (settings, deviceId) {
} }
} }
return sms.sendMessage(settings, rec) return sms.sendMessage(settings, rec).then(() => {
.then(() => { const sql = 'update cash_out_txs set notified=$1 where id=$2'
const sql = 'update cash_out_txs set notified=$1 where id=$2' const values = [true, tx.id]
const values = [true, tx.id]
return db.none(sql, values) return db.none(sql, values)
}) })
} }
function notifyOperator (tx, rec) { function notifyOperator (tx, rec) {
@ -361,14 +451,17 @@ function plugins (settings, deviceId) {
} }
function clearOldLogs () { function clearOldLogs () {
return logs.clearOldLogs() return logs.clearOldLogs().catch(logger.error)
.catch(logger.error)
} }
function pong () { function pong () {
return db.none(`UPDATE server_events SET created=now() WHERE event_type=$1; return db
.none(
`UPDATE server_events SET created=now() WHERE event_type=$1;
INSERT INTO server_events (event_type) SELECT $1 INSERT INTO server_events (event_type) SELECT $1
WHERE NOT EXISTS (SELECT 1 FROM server_events WHERE event_type=$1);`, ['ping']) WHERE NOT EXISTS (SELECT 1 FROM server_events WHERE event_type=$1);`,
['ping']
)
.catch(logger.error) .catch(logger.error)
} }
@ -409,15 +502,18 @@ function plugins (settings, deviceId) {
const marketTradesQueues = tradesQueues[market] const marketTradesQueues = tradesQueues[market]
if (!marketTradesQueues || marketTradesQueues.length === 0) return null if (!marketTradesQueues || marketTradesQueues.length === 0) return null
logger.debug('[%s] tradesQueues size: %d', market, marketTradesQueues.length) logger.debug(
'[%s] tradesQueues size: %d',
market,
marketTradesQueues.length
)
logger.debug('[%s] tradesQueues head: %j', market, marketTradesQueues[0]) logger.debug('[%s] tradesQueues head: %j', market, marketTradesQueues[0])
const t1 = Date.now() const t1 = Date.now()
const filtered = marketTradesQueues const filtered = marketTradesQueues.filter(tradeEntry => {
.filter(tradeEntry => { return t1 - tradeEntry.timestamp < TRADE_TTL
return t1 - tradeEntry.timestamp < TRADE_TTL })
})
const filteredCount = marketTradesQueues.length - filtered.length const filteredCount = marketTradesQueues.length - filtered.length
@ -428,10 +524,14 @@ function plugins (settings, deviceId) {
if (filtered.length === 0) return null if (filtered.length === 0) return null
const cryptoAtoms = filtered const cryptoAtoms = filtered.reduce(
.reduce((prev, current) => prev.plus(current.cryptoAtoms), BN(0)) (prev, current) => prev.plus(current.cryptoAtoms),
BN(0)
)
const timestamp = filtered.map(r => r.timestamp).reduce((acc, r) => Math.max(acc, r), 0) const timestamp = filtered
.map(r => r.timestamp)
.reduce((acc, r) => Math.max(acc, r), 0)
const consolidatedTrade = { const consolidatedTrade = {
fiatCode, fiatCode,
@ -447,11 +547,15 @@ function plugins (settings, deviceId) {
} }
function executeTrades () { function executeTrades () {
return machineLoader.getMachines() return machineLoader
.getMachines()
.then(devices => { .then(devices => {
const deviceIds = devices.map(device => device.deviceId) const deviceIds = devices.map(device => device.deviceId)
const lists = deviceIds.map(deviceId => { const lists = deviceIds.map(deviceId => {
const localeConfig = configManager.getLocale(deviceId, settings.config) const localeConfig = configManager.getLocale(
deviceId,
settings.config
)
const fiatCode = localeConfig.fiatCurrency const fiatCode = localeConfig.fiatCurrency
const cryptoCodes = localeConfig.cryptoCurrencies const cryptoCodes = localeConfig.cryptoCurrencies
@ -461,8 +565,9 @@ function plugins (settings, deviceId) {
})) }))
}) })
const tradesPromises = _.uniq(_.flatten(lists)) const tradesPromises = _.uniq(_.flatten(lists)).map(r =>
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode)) executeTradesForMarket(settings, r.fiatCode, r.cryptoCode)
)
return Promise.all(tradesPromises) return Promise.all(tradesPromises)
}) })
@ -477,41 +582,43 @@ function plugins (settings, deviceId) {
if (tradeEntry === null || tradeEntry.cryptoAtoms.eq(0)) return if (tradeEntry === null || tradeEntry.cryptoAtoms.eq(0)) return
return executeTradeForType(tradeEntry) return executeTradeForType(tradeEntry).catch(err => {
.catch(err => { tradesQueues[market].push(tradeEntry)
tradesQueues[market].push(tradeEntry) if (err.name === 'orderTooSmall') return logger.debug(err.message)
if (err.name === 'orderTooSmall') return logger.debug(err.message) logger.error(err)
logger.error(err) })
})
} }
function executeTradeForType (_tradeEntry) { function executeTradeForType (_tradeEntry) {
const expand = te => _.assign(te, { const expand = te =>
cryptoAtoms: te.cryptoAtoms.abs(), _.assign(te, {
type: te.cryptoAtoms.gte(0) ? 'buy' : 'sell' cryptoAtoms: te.cryptoAtoms.abs(),
}) type: te.cryptoAtoms.gte(0) ? 'buy' : 'sell'
})
const tradeEntry = expand(_tradeEntry) const tradeEntry = expand(_tradeEntry)
const execute = tradeEntry.type === 'buy' ? exchange.buy : exchange.sell const execute = tradeEntry.type === 'buy' ? exchange.buy : exchange.sell
return execute(settings, tradeEntry.cryptoAtoms, tradeEntry.fiatCode, tradeEntry.cryptoCode) return execute(
settings,
tradeEntry.cryptoAtoms,
tradeEntry.fiatCode,
tradeEntry.cryptoCode
)
.then(() => recordTrade(tradeEntry)) .then(() => recordTrade(tradeEntry))
.catch(err => { .catch(err => {
return recordTrade(tradeEntry, err) return recordTrade(tradeEntry, err).then(() => {
.then(() => { throw err
throw err })
})
}) })
} }
function convertBigNumFields (obj) { function convertBigNumFields (obj) {
const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat']) const convert = (value, key) =>
? value.toString() _.includes(key, ['cryptoAtoms', 'fiat']) ? value.toString() : value
: value
const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat']) const convertKey = key =>
? key + '#' _.includes(key, ['cryptoAtoms', 'fiat']) ? key + '#' : key
: key
return _.mapKeys(convertKey, mapValuesWithKey(convert, obj)) return _.mapKeys(convertKey, mapValuesWithKey(convert, obj))
} }
@ -541,8 +648,15 @@ function plugins (settings, deviceId) {
const notifications = configManager.getGlobalNotifications(settings.config) const notifications = configManager.getGlobalNotifications(settings.config)
let promises = [] let promises = []
<<<<<<< HEAD
if (notifications.email.active && rec.email) promises.push(email.sendMessage(settings, rec)) if (notifications.email.active && rec.email) promises.push(email.sendMessage(settings, rec))
if (notifications.sms.active && rec.sms) promises.push(sms.sendMessage(settings, rec)) if (notifications.sms.active && rec.sms) promises.push(sms.sendMessage(settings, rec))
=======
if (notifications.email.active && rec.email)
promises.push(email.sendMessage(settings, rec))
if (notifications.sms.active && rec.sms)
promises.push(sms.sendMessage(settings, rec))
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
return Promise.all(promises) return Promise.all(promises)
} }
@ -552,53 +666,64 @@ function plugins (settings, deviceId) {
} }
function checkDeviceCashBalances (fiatCode, device) { function checkDeviceCashBalances (fiatCode, device) {
const cashOutConfig = configManager.getCashOut(device.deviceId, settings.config) const cashOutConfig = configManager.getCashOut(
device.deviceId,
settings.config
)
const denomination1 = cashOutConfig.top const denomination1 = cashOutConfig.top
const denomination2 = cashOutConfig.bottom const denomination2 = cashOutConfig.bottom
const cashOutEnabled = cashOutConfig.active const cashOutEnabled = cashOutConfig.active
const notifications = configManager.getNotifications(null, device.deviceId, settings.config) const notifications = configManager.getNotifications(
null,
device.deviceId,
settings.config
)
const machineName = device.name const machineName = device.name
const cashInAlert = device.cashbox > notifications.cashInAlertThreshold const cashInAlert =
? { device.cashbox > notifications.cashInAlertThreshold
code: 'CASH_BOX_FULL', ? {
machineName, code: 'CASH_BOX_FULL',
deviceId: device.deviceId, machineName,
notes: device.cashbox deviceId: device.deviceId,
} notes: device.cashbox
: null }
: null
const cassette1Alert = cashOutEnabled && device.cassette1 < notifications.fiatBalanceCassette1 const cassette1Alert =
? { cashOutEnabled && device.cassette1 < notifications.fiatBalanceCassette1
code: 'LOW_CASH_OUT', ? {
cassette: 1, code: 'LOW_CASH_OUT',
machineName, cassette: 1,
deviceId: device.deviceId, machineName,
notes: device.cassette1, deviceId: device.deviceId,
denomination: denomination1, notes: device.cassette1,
fiatCode denomination: denomination1,
} fiatCode
: null }
: null
const cassette2Alert = cashOutEnabled && device.cassette2 < notifications.fiatBalanceCassette2 const cassette2Alert =
? { cashOutEnabled && device.cassette2 < notifications.fiatBalanceCassette2
code: 'LOW_CASH_OUT', ? {
cassette: 2, code: 'LOW_CASH_OUT',
machineName, cassette: 2,
deviceId: device.deviceId, machineName,
notes: device.cassette2, deviceId: device.deviceId,
denomination: denomination2, notes: device.cassette2,
fiatCode denomination: denomination2,
} fiatCode
: null }
: null
return _.compact([cashInAlert, cassette1Alert, cassette2Alert]) return _.compact([cashInAlert, cassette1Alert, cassette2Alert])
} }
function checkCryptoBalances (fiatCode, devices) { function checkCryptoBalances (fiatCode, devices) {
const fiatBalancePromises = cryptoCodes => _.map(c => fiatBalance(fiatCode, c), cryptoCodes) const fiatBalancePromises = cryptoCodes =>
_.map(c => fiatBalance(fiatCode, c), cryptoCodes)
const fetchCryptoCodes = _deviceId => { const fetchCryptoCodes = _deviceId => {
const localeConfig = configManager.getLocale(_deviceId, settings.config) const localeConfig = configManager.getLocale(_deviceId, settings.config)
@ -609,15 +734,20 @@ function plugins (settings, deviceId) {
const cryptoCodes = union(devices) const cryptoCodes = union(devices)
const checkCryptoBalanceWithFiat = _.partial(checkCryptoBalance, [fiatCode]) const checkCryptoBalanceWithFiat = _.partial(checkCryptoBalance, [fiatCode])
return Promise.all(fiatBalancePromises(cryptoCodes)) return Promise.all(fiatBalancePromises(cryptoCodes)).then(balances =>
.then(balances => _.map(checkCryptoBalanceWithFiat, _.zip(cryptoCodes, balances))) _.map(checkCryptoBalanceWithFiat, _.zip(cryptoCodes, balances))
)
} }
function checkCryptoBalance (fiatCode, rec) { function checkCryptoBalance (fiatCode, rec) {
const [cryptoCode, fiatBalance] = rec const [cryptoCode, fiatBalance] = rec
if (!fiatBalance) return null if (!fiatBalance) return null
const notifications = configManager.getNotifications(cryptoCode, null, settings.config) const notifications = configManager.getNotifications(
cryptoCode,
null,
settings.config
)
const lowAlertThreshold = notifications.cryptoLowBalance const lowAlertThreshold = notifications.cryptoLowBalance
const highAlertThreshold = notifications.cryptoHighBalance const highAlertThreshold = notifications.cryptoHighBalance
@ -627,11 +757,25 @@ function plugins (settings, deviceId) {
fiatCode fiatCode
} }
<<<<<<< HEAD
if (_.isFinite(lowAlertThreshold) && BN(fiatBalance.balance).lt(lowAlertThreshold)) { if (_.isFinite(lowAlertThreshold) && BN(fiatBalance.balance).lt(lowAlertThreshold)) {
=======
if (
_.isFinite(lowAlertThreshold) &&
BN(fiatBalance.balance).lt(lowAlertThreshold)
)
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
return _.set('code')('LOW_CRYPTO_BALANCE')(req) return _.set('code')('LOW_CRYPTO_BALANCE')(req)
} }
<<<<<<< HEAD
if (_.isFinite(highAlertThreshold) && BN(fiatBalance.balance).gt(highAlertThreshold)) { if (_.isFinite(highAlertThreshold) && BN(fiatBalance.balance).gt(highAlertThreshold)) {
=======
if (
_.isFinite(highAlertThreshold) &&
BN(fiatBalance.balance).gt(highAlertThreshold)
)
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
return _.set('code')('HIGH_CRYPTO_BALANCE')(req) return _.set('code')('HIGH_CRYPTO_BALANCE')(req)
} }
@ -642,24 +786,23 @@ function plugins (settings, deviceId) {
const localeConfig = configManager.getGlobalLocale(settings.config) const localeConfig = configManager.getGlobalLocale(settings.config)
const fiatCode = localeConfig.fiatCurrency const fiatCode = localeConfig.fiatCurrency
return machineLoader.getMachines() return machineLoader.getMachines().then(devices => {
.then(devices => { return Promise.all([
return Promise.all([ checkCryptoBalances(fiatCode, devices),
checkCryptoBalances(fiatCode, devices), checkDevicesCashBalances(fiatCode, devices)
checkDevicesCashBalances(fiatCode, devices) ]).then(_.flow(_.flattenDeep, _.compact))
]) })
.then(_.flow(_.flattenDeep, _.compact))
})
} }
function randomCode () { function randomCode () {
return BN(crypto.randomBytes(3).toString('hex'), 16).shift(-6).toFixed(6).slice(-6) return BN(crypto.randomBytes(3).toString('hex'), 16)
.shift(-6)
.toFixed(6)
.slice(-6)
} }
function getPhoneCode (phone) { function getPhoneCode (phone) {
const code = argv.mockSms const code = argv.mockSms ? '123' : randomCode()
? '123'
: randomCode()
const rec = { const rec = {
sms: { sms: {
@ -668,14 +811,14 @@ function plugins (settings, deviceId) {
} }
} }
return sms.sendMessage(settings, rec) return sms.sendMessage(settings, rec).then(() => code)
.then(() => code)
} }
function sweepHdRow (row) { function sweepHdRow (row) {
const cryptoCode = row.crypto_code const cryptoCode = row.crypto_code
return wallet.sweep(settings, cryptoCode, row.hd_index) return wallet
.sweep(settings, cryptoCode, row.hd_index)
.then(txHash => { .then(txHash => {
if (txHash) { if (txHash) {
logger.debug('[%s] Swept address with tx: %s', cryptoCode, txHash) logger.debug('[%s] Swept address with tx: %s', cryptoCode, txHash)
@ -686,14 +829,17 @@ function plugins (settings, deviceId) {
return db.none(sql, row.id) return db.none(sql, row.id)
} }
}) })
.catch(err => logger.error('[%s] Sweep error: %s', cryptoCode, err.message)) .catch(err =>
logger.error('[%s] Sweep error: %s', cryptoCode, err.message)
)
} }
function sweepHd () { function sweepHd () {
const sql = `select id, crypto_code, hd_index from cash_out_txs const sql = `select id, crypto_code, hd_index from cash_out_txs
where hd_index is not null and not swept and status in ('confirmed', 'instant')` where hd_index is not null and not swept and status in ('confirmed', 'instant')`
return db.any(sql) return db
.any(sql)
.then(rows => Promise.all(rows.map(sweepHdRow))) .then(rows => Promise.all(rows.map(sweepHdRow)))
.catch(err => logger.error(err)) .catch(err => logger.error(err))
} }
@ -707,14 +853,15 @@ function plugins (settings, deviceId) {
const fiatCode = localeConfig.fiatCurrency const fiatCode = localeConfig.fiatCurrency
const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config) const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config)
const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c)) const tickerPromises = cryptoCodes.map(c =>
ticker.getRates(settings, fiatCode, c)
)
return Promise.all(tickerPromises) return Promise.all(tickerPromises)
} }
function getRates () { function getRates () {
return getRawRates() return getRawRates().then(buildRates)
.then(buildRates)
} }
return { return {