diff --git a/lib/app.js b/lib/app.js index 847ae3e4..0b7e265b 100644 --- a/lib/app.js +++ b/lib/app.js @@ -5,7 +5,7 @@ var https = require('https'); var express = require('express'); var LamassuConfig = require('lamassu-config'); var routes = require('./routes'); -var Trader = require('./trader'); +var tmpName = require('./tmpName'); var PostgresqlInterface = require('./postgresql_interface'); var logger = require('./logger'); @@ -14,7 +14,6 @@ module.exports = function (options) { var connectionString; var server; var config; - var trader; var db; connectionString = options.postgres || @@ -22,7 +21,8 @@ module.exports = function (options) { config = new LamassuConfig(connectionString); db = new PostgresqlInterface(connectionString); - trader = new Trader(db); + tmpName.init(db); + config.load(function (err, config) { if (err) { @@ -30,8 +30,8 @@ module.exports = function (options) { throw err; } - trader.configure(config); - trader.startPolling(); + tmpName.configure(config); + tmpName.startPolling(); }); config.on('configUpdate', function () { @@ -40,7 +40,7 @@ module.exports = function (options) { return logger.error('Error while reloading config'); } - trader.configure(config); + tmpName.configure(config); logger.info('Config reloaded'); }); }); @@ -89,7 +89,7 @@ module.exports = function (options) { routes.init({ app: app, lamassuConfig: config, - trader: trader, + tmpName: tmpName, authMiddleware: authMiddleware, mock: options.mock }); diff --git a/lib/routes.js b/lib/routes.js index 3acfe623..b15b47aa 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -1,12 +1,13 @@ 'use strict'; -var _trader; -var _lamassuConfig; -var _idVerifier = null; -var _trader = null; -var _mock = false; var logger = require('./logger'); +var idVerifier = null; +var mock = false; + +var tmpName; +var config; + module.exports = { init: init, getFingerprint: getFingerprint @@ -18,8 +19,8 @@ var STALE_TICKER = 180000; var STALE_BALANCE = 180000; function poll(req, res) { - var rateRec = _trader.rate(); - var balanceRec = _trader.balance; + var rateRec = tmpName.getDeviceRate(); + var balanceRec = tmpName.getBalance(); var fingerprint = getFingerprint(req); logger.debug('poll request from: %s', fingerprint); @@ -39,9 +40,9 @@ function poll(req, res) { return res.json({err: 'Stale balance'}); } - var rate = rateRec.rate; + var rate = rateRec.rates.ask; if (rate === null) return res.json({err: 'No rate available'}); - var fiatBalance = _trader.fiatBalance(fingerprint); + var fiatBalance = tmpName.fiatBalance(fingerprint); if (fiatBalance === null) return res.json({err: 'No balance available'}); var idVerificationLimit = _trader.config.exchanges.settings. @@ -51,18 +52,17 @@ function poll(req, res) { res.json({ err: null, - rate: rate * _trader.config.exchanges.settings.commission, + rate: rate * config.exchanges.settings.commission, fiat: fiatBalance, - locale: _trader.config.brain.locale, - txLimit: parseInt(_trader.config.exchanges.settings.compliance.maximum.limit, 10), + locale: config.brain.locale, + txLimit: parseInt(config.exchanges.settings.compliance.maximum.limit, 10), idVerificationLimit: idVerificationLimit }); } function trade(req, res) { var fingerprint = getFingerprint(req); - _trader.trade(req.body, fingerprint); - + tmpName.trade(req.body, fingerprint); res.json({err: null}); } @@ -73,9 +73,9 @@ function deviceEvent(req, res) { } function verifyUser(req, res) { - if (_mock) return res.json({success: true}); + if (mock) return res.json({success: true}); - _idVerifier.verifyUser(req.body, function (err, idResult) { + idVerifier.verifyUser(req.body, function (err, idResult) { if (err) { logger.error(err); return res.json({err: 'Verification failed'}); @@ -87,7 +87,7 @@ function verifyUser(req, res) { function verifyTransaction(req, res) { if (_mock) return res.json({success: true}); - _idVerifier.verifyTransaction(req.body, function (err, idResult) { + idVerifier.verifyTransaction(req.body, function (err, idResult) { if (err) { logger.error(err); return res.json({err: 'Verification failed'}); @@ -98,7 +98,7 @@ function verifyTransaction(req, res) { function send(req, res) { var fingerprint = getFingerprint(req); - _trader.sendBitcoins(fingerprint, req.body, function(err, txHash) { + tmpName.sendBitcoins(fingerprint, req.body, function(err, txHash) { res.json({ err: err && err.message, txHash: txHash, @@ -111,7 +111,7 @@ function pair(req, res) { var token = req.body.token; var name = req.body.name; - _lamassuConfig.pair( + config.pair( token, getFingerprint(req), name, @@ -125,17 +125,17 @@ function pair(req, res) { ); } -function init(config) { - _lamassuConfig = config.lamassuConfig; - _trader = config.trader; - _mock = config.mock; +function init(localConfig) { + config = localConfig.lamassuConfig; + tmpName = localConfig.tmpName; + mock = localConfig.mock; - var authMiddleware = config.authMiddleware; - var app = config.app; - _lamassuConfig.readExchangesConfig(function (err, res) { + var authMiddleware = localConfig.authMiddleware; + var app = localConfig.app; + config.readExchangesConfig(function (err, res) { var idVerifyConfig = res.exchanges.plugins.settings.identitymind; - _idVerifier = require('lamassu-identitymind'); - _idVerifier.init(idVerifyConfig); + idVerifier = require('lamassu-identitymind'); + idVerifier.init(idVerifyConfig); }); app.get('/poll', authMiddleware, poll); diff --git a/lib/tmpName.js b/lib/tmpName.js new file mode 100644 index 00000000..d9832198 --- /dev/null +++ b/lib/tmpName.js @@ -0,0 +1,356 @@ +'use strict'; + +var async = require('async'); + +var logger = require('./logger'); + + +var SATOSHI_FACTOR = 1e8; +var SESSION_TIMEOUT = 60 * 60 * 1000; +var POLLING_RATE = 60 * 1000; // poll each minute + +var db = null; + +var tickerPlugin = null; +var traderPlugin = null; +var walletPlugin = null; + +var cachedConfig = null; +var deviceCurrency = 'USD'; // Can 'USD' it be set as default? + +var lastBalances = null; +var lastRates = {}; + +var balanceInterval = null; +var rateInterval = null; +var tradeInterval = null; + +var tradesQueue = []; +var sessions = {}; + + +// that's basically a constructor +exports.init = function init(databaseHandle) { + if (!databaseHandle) { + throw new Error('`db` is required'); + } + + db = databaseHandle; +} + + +function loadPlugin(name, config) { + var moduleMethods = { + ticker: [ 'ticker' ], + trader: [ 'balance', 'purchase', 'sell' ], + wallet: [ 'balance', 'sendBitcoins' ] + }; + + var plugin = null; + + try { + plugin = require('lamassu-' + name); + + } catch(_) { + throw new Error(name + ' module is not installed. Try running \'npm install --save lamassu-' + name + '\' first'); + } + + plugin.SUPPORTED_MODULES.forEach(function(moduleName) { + moduleMethods[moduleName].forEach(function(methodName) { + if (typeof plugin[methodName] !== 'function') { + throw new Error('\'' + name + '\' declares \'' + moduleName + '\', but fails to implement \'' + methodName + '\' method'); + } + }); + }); + + if (config !== null) { + plugin.config(config); + } + + return plugin; +}; + + +exports.configure = function configure(config) { + if (config.exchanges.settings.lowBalanceMargin < 1) { + throw new Error('`settings.lowBalanceMargin` has to be >= 1'); + } + + deviceCurrency = config.exchanges.settings.currency; + var plugins = config.exchanges.plugins + + // [required] configure (or load) ticker + var tickerName = plugins.current.ticker; + var tickerConfig = plugins.settings[tickerName] || {}; + tickerConfig.currency = deviceCurrency; + + if (tickerPlugin) tickerPlugin.config(tickerConfig); + else tickerPlugin = loadPlugin(tickerName, tickerConfig); + + + // [required] configure (or load) wallet + var walletName = plugins.current.transfer; + var walletConfig = plugins.settings[walletName]; + + if (walletPlugin) walletPlugin.config(walletConfig); + else walletPlugin = loadPlugin(walletName, walletConfig); + + + // [optional] configure (or load) trader + var traderName = plugins.current.trade; + if (traderName) { // traderPlugin may be disabled + var traderConfig = plugins.settings[traderName]; + + if (traderPlugin) traderPlugin.config(traderConfig); + else traderPlugin = loadPlugin(traderName, traderConfig); + } + + cachedConfig = config; + + pollBalance(); + pollRate(); +}; + + +// This is where we record starting trade balance at the beginning +// of the user session +exports.trade = function trade(rawTrade, deviceFingerprint) { + var sessionInfo = sessions[deviceFingerprint]; + + if (!sessionInfo) { + sessions[deviceFingerprint] = { + timestamp: Date.now(), + reaper: setTimeout(function() { + delete sessions[deviceFingerprint]; + }, SESSION_TIMEOUT) + }; + } + + tradesQueue.push({ + currency: rawTrade.currency, + satoshis: rawTrade.satoshis + }); +}; + +exports.fiatBalance = function fiatBalance(deviceFingerprint) { + var rawRate = getDeviceRate().rates.ask; + var commision = cachedConfig.exchanges.settings.commision; + + if (!rawRate || !lastBalances) return null; + + // The rate is actually our commission times real rate. + var rate = commission * rawRate; + + // `lowBalanceMargin` is our safety net. It's a number > 1, and we divide + // all our balances by it to provide a safety margin. + var lowBalanceMargin = cachedConfig.exchanges.settings.lowBalanceMargin; + + // `balance.transferBalance` is the balance of our transfer account (the one + // we use to send Bitcoins to clients) in satoshis. + var transferBalance = balance.transferBalance; + + // Since `transferBalance` is in satoshis, we need to turn it into + // bitcoins and then fiat to learn how much fiat currency we can exchange. + // + // Unit validity proof: [ $ ] = [ (B * 10^8) / 10^8 * $/B ] + // [ $ ] = [ B * $/B ] + // [ $ ] = [ $ ] + var fiatTransferBalance = ((transferBalance / SATOSHI_FACTOR) * rate) / lowBalanceMargin; + + return fiatTransferBalance; +}; + +exports.sendBitcoins = function sendBitcoins(deviceFingerprint, tx, callback) { + db.summonTransaction(deviceFingerprint, tx, function(err, txInfo) { + if (err) return callback(err); + + if (!txInfo) { + clearSession(deviceFingerprint); + + return walletPlugin.sendBitcoins( + tx.toAddress, + tx.satoshis, + cachedConfig.exchanges.settings.transactionFee, + + function(err, txHash) { + if (err) { + var status = err.name === 'InsufficientFunds' ? + 'insufficientFunds' : + 'failed'; + + db.reportTransactionError(tx, err.message, status); + return callback(err); + } + + db.completeTransaction(tx, txHash); + pollBalance(); + callback(null, txHash); + } + ); + } + + // Out of bitcoins: special case + var txErr = null; + if (txInfo.err) { + txErr = new Error(txInfo.err); + if (txInfo.status === 'insufficientFunds') { + txErr.name = 'InsufficientFunds'; + } + } + + // transaction exists, but txHash might be null, + // in which case ATM should continue polling + pollBalance(); + callback(txErr, txInfo.txHash); + }); +}; + + +/* + * Polling livecycle + */ +exports.startPolling = function startPolling() { + executeTrades(); + + if (!balanceInterval) { + balanceInterval = setInterval(pollBalance, POLLING_RATE); + } + + if (!rateInterval) { + rateInterval = setInterval(pollRate, POLLING_RATE); + } + + // Always start trading, even if we don't have a trade exchange configured, + // since configuration can always change in `Trader#configure`. + // `Trader#executeTrades` returns early if we don't have a trade exchange + // configured at the moment. + if (!tradeInterval) { + tradeInterval = setInterval( + executeTrades, + cachedConfig.exchanges.settings.tradeInterval + ); + } +}; +function stopPolling() { + clearInterval(balanceInterval); + clearInterval(rateInterval); + // clearInterval(tradeInterval); // TODO: should this get cleared too? +}; + + +function pollBalance(callback) { + logger.debug('collecting balance'); + + var jobs = { + transferBalance: walletPlugin.balance + }; + + // only add if trader is enabled + // if (traderPlugin) { + // // NOTE: we would need to handle when traderCurrency!=deviceCurrency + // jobs.tradeBalance = traderPlugin.balance; + // } + + async.parallel(jobs, function(err, balance) { + if (err) { + logger.error(err); + return callback && callback(err); + } + + logger.debug('Balance update:', balance); + balance.timestamp = Date.now(); + lastBalances = balance; + + return callback && callback(null, lastBalances); + }); +}; + +function pollRate(callback) { + logger.debug('polling for rates'); + + tickerPlugin.ticker(deviceCurrency, function(err, resRates) { + if (err) { + logger.error(err); + return callback && callback(err); + } + + logger.debug('got rates: %j', resRates); + resRates.timestamp = new Date(); + lastRates = resRates; + + return callback && callback(null, lastRates); + }); +}; + + +/* + * Getters | Helpers + */ +function getLastRate(currency) { + if (!lastRates) return null; + + var tmpCurrency = currency || deviceCurrency; + if (!lastRates[tmpCurrency]) return null; + + return lastRates[tmpCurrency]; +}; +function getDeviceRate() { + return getLastRate(deviceCurrency); +}; + +function getBalance() { + if (!lastBalances) return null; + + return lastBalances.transferBalance; +}; + +function clearSession(deviceFingerprint) { + var session = sessions[deviceFingerprint]; + if (session) { + clearTimeout(session.reaper); + delete sessions[deviceFingerprint]; + } +}; + + +/* + * Trader functions + */ +function purchase(trade, callback) { + traderPlugin.purchase(trade.satoshis, null, function(err, _) { + if (err) return callback(err); + pollBalance(); + callback && callback(); + }); +}; + +function consolidateTrades() { + // NOTE: value in satoshis stays the same no matter the currency + var consolidatedTrade = { + currency: deviceCurrency, + satoshis: tradesQueue.reduce(function(prev, current) { + return prev + current.satoshis; + }, 0) + }; + + logger.debug('consolidated: ', JSON.stringify(consolidatedTrade)); + return consolidatedTrade; +}; + +function executeTrades() { + if (!traderPlugin) return; + + logger.debug('checking for trades'); + + var trade = consolidateTrades(); + + if (trade.satoshis === 0) { + logger.debug('rejecting 0 trade'); + return; + } + + logger.trade.debug('making a trade: %d', trade.satoshis / SATOSHI_FACTOR); + purchase(trade, function(err) { + if (err) logger.error(err); + }); +}; diff --git a/lib/trader.js b/lib/trader.js deleted file mode 100644 index 0bed4a6e..00000000 --- a/lib/trader.js +++ /dev/null @@ -1,367 +0,0 @@ -'use strict'; - -var async = require('async'); -var logger = require('./logger'); - -var SATOSHI_FACTOR = 1e8; - -// TODO: Define this somewhere more global -var SESSION_TIMEOUT = 60 * 60 * 1000; // an hour - - -function findExchange(name) { - try { - return require('lamassu-' + name); - - } catch(_) { - throw new Error(name + ' module is not installed. Try running `npm install --save lamassu-' + name + '` first'); - } -}; - -function findTicker (name) { - var exchange = findExchange(name); - return exchange.ticker || exchange; -}; - -function findTrader (name) { - var exchange = findExchange(name); - return exchange.trader || exchange; -}; - -function findWallet (name) { - var exchange = findExchange(name); - return exchange.wallet || exchange; -}; - - -var Trader = module.exports = function (db) { - if (!db) { - throw new Error('`db` is required'); - } - - this.db = db; - this.rates = {}; - this._tradeQueue = []; - this._sessionInfo = {}; - this.rateInfo = null; -}; - -Trader.prototype._consolidateTrades = function () { - var queue = this._tradeQueue; - - // NOTE: value in satoshis stays the same no matter the currency - var consolidatedTrade = { - currency: this.config.exchanges.settings.currency, - satoshis: queue.reduce(function (prev, current) { - return prev + current.satoshis; - }, 0) - }; - - return consolidatedTrade; -}; - -Trader.prototype._purchase = function (trade, cb) { - var self = this; - var tradeCurrency = this.tradeExchange.currency(); - var rate = this.rate(tradeCurrency).rate; - this.tradeExchange.purchase(trade.satoshis, rate, function (err) { - if (err) return cb(err); - self.pollBalance(); - cb(); - }); -}; - -Trader.prototype.configure = function (config) { - if (config.exchanges.settings.lowBalanceMargin < 1) { - throw new Error('`settings.lowBalanceMargin` has to be >= 1'); - } - - var plugins = config.exchanges.plugins - - // source of current BTC price (init and configure) - var tickerName = plugins.current.ticker; - var tickerConfig = plugins.settings[tickerName] || {}; - tickerConfig.currency = config.exchanges.settings.currency; - this.tickerExchange = findTicker(tickerName).factory(tickerConfig); - - // Exchange used for trading (init and configure) - var traderName = plugins.current.trade; - if (traderName) { - var tradeConfig = plugins.settings[traderName]; - this.tradeExchange = findTrader(traderName).factory(tradeConfig); - } - - // Wallet (init and configure) - var walletName = plugins.current.transfer; - var walletConfig = plugins.settings[walletName]; - this.transferExchange = findWallet(walletName).factory(walletConfig); - - this.config = config; - - this.pollBalance(); - this.pollRate(); -}; - -// IMPORTANT: This function returns the estimated minimum available balance -// in fiat as of the start of the current user session on the device. User -// session starts when a user presses then Start button and ends when we -// send the bitcoins. -Trader.prototype.fiatBalance = function (deviceFingerprint) { - var rawRate = this.rate(this.config.exchanges.settings.currency).rate; - var balance = this.balance; - var commission = this.config.exchanges.settings.commission; - - if (!rawRate || !balance) { - return null; - } - - // The rate is actually our commission times real rate. - var rate = commission * rawRate; - - // `lowBalanceMargin` is our safety net. It's a number > 1, and we divide - // all our balances by it to provide a safety margin. - var lowBalanceMargin = this.config.exchanges.settings.lowBalanceMargin; - - // `balance.transferBalance` is the balance of our transfer account (the one - // we use to send Bitcoins to clients) in satoshis. - var transferBalance = balance.transferBalance; - - // Since `transferBalance` is in satoshis, we need to turn it into - // bitcoins and then fiat to learn how much fiat currency we can exchange. - // - // Unit validity proof: [ $ ] = [ (B * 10^8) / 10^8 * $/B ] - // [ $ ] = [ B * $/B ] - // [ $ ] = [ $ ] - var fiatTransferBalance = ((transferBalance / SATOSHI_FACTOR) * rate) / lowBalanceMargin; - - // If this server is also configured to trade received fiat for Bitcoins, - // we also need to calculate if we have enough funds on our trade exchange. - if (balance.tradeBalance === null) return fiatTransferBalance; - var tradeBalance = balance.tradeBalance; - - // We're reporting balance as of the start of the user session. - var sessionInfo = this._sessionInfo[deviceFingerprint]; - var sessionBalance = sessionInfo ? sessionInfo.tradeBalance : tradeBalance; - var fiatTradeBalance = sessionBalance / lowBalanceMargin; - - // And we return the smallest number. - return Math.min(fiatTransferBalance, fiatTradeBalance); -}; - -Trader.prototype._clearSession = function (deviceFingerprint) { - var sessionInfo = this._sessionInfo[deviceFingerprint]; - if (sessionInfo) { - clearTimeout(sessionInfo.reaper); - delete this._sessionInfo[deviceFingerprint]; - } -}; - -Trader.prototype.sendBitcoins = function (deviceFingerprint, tx, cb) { - var self = this; - - self.db.summonTransaction(deviceFingerprint, tx, function (err, txRec) { - if (err) return cb(err); - - if (!txRec) { - self._clearSession(deviceFingerprint); - return self.transferExchange.sendBitcoins( - tx.toAddress, - tx.satoshis, - self.config.exchanges.settings.transactionFee, - function(err, txHash) { - if (err) { - var status = err.name === 'InsufficientFunds' ? - 'insufficientFunds' : - 'failed'; - self.db.reportTransactionError(tx, err.message, status); - return cb(err); - } - - self.db.completeTransaction(tx, txHash); - self.pollBalance(); - cb(null, txHash); - } - ); - } - - // Out of bitcoins: special case - var txErr = null; - if (txRec.err) { - txErr = new Error(txRec.err); - if (txRec.status === 'insufficientFunds') txErr.name = 'InsufficientFunds'; - } - - // transaction exists, but txHash might be null, - // in which case ATM should continue polling - self.pollBalance(); - cb(txErr, txRec.txHash); - }); -}; - -Trader.prototype.deviceEvent = function deviceEvent(rec, deviceFingerprint) { - this.db.recordDeviceEvent(deviceFingerprint, rec, function (err) { - if (err) logger.error(err); - }); -}; - -Trader.prototype.trade = function (rec, deviceFingerprint) { - this.db.recordBill(deviceFingerprint, rec, function (err) { - if (err) logger.error(err); - }); - - // This is where we record starting trade balance at the beginning - // of the user session - var sessionInfo = this._sessionInfo[deviceFingerprint]; - var self = this; - if (!sessionInfo) { - this._sessionInfo[deviceFingerprint] = { - tradeBalance: this.balance.tradeBalance, - timestamp: Date.now(), - reaper: setTimeout(function () { - delete self._sessionInfo[deviceFingerprint]; - }, SESSION_TIMEOUT) - }; - } - this._tradeQueue.push({ - satoshis: rec.satoshis, - currency: rec.currency - }); -}; - -Trader.prototype.executeTrades = function () { - if (!this.tradeExchange) return; - - logger.debug('checking for trades'); - - var trade = this._consolidateTrades(); - logger.debug('consolidated: ', JSON.stringify(trade)); - - if (trade.satoshis === 0) { - logger.debug('rejecting 0 trade'); - return; - } - - logger.debug('making a trade: %d', trade.satoshis / SATOSHI_FACTOR); - this._purchase(trade, function (err) { - if (err) logger.error(err); - }); -}; - -Trader.prototype.startPolling = function () { - this.executeTrades(); - - this.balanceInterval = setInterval(this.pollBalance.bind(this), 60 * 1000); - this.rateInterval = setInterval(this.pollRate.bind(this), 60 * 1000); - - // Always start trading, even if we don't have a trade exchange configured, - // since configuration can always change in `Trader#configure`. - // `Trader#executeTrades` returns early if we don't have a trade exchange - // configured at the moment. - this.tradeInterval = setInterval( - this.executeTrades.bind(this), - this.config.exchanges.settings.tradeInterval - ); -}; - -Trader.prototype.stopPolling = function () { - clearInterval(this.balanceInterval); - clearInterval(this.rateInterval); -}; - -// Trade exchange could be in a different currency than Bitcoin Machine. -// For instance, trade exchange could be Bitstamp, denominated in USD, -// while Bitcoin Machine is set to ILS. -// -// We need this function to convert the trade exchange balance into the -// Bitcoin Machine denomination, in the example case: ILS. -// -// The best way to do that with available data is to take the ratio between -// the exchange rates for the Bitcoin Machine and the trade exchange. -Trader.prototype._tradeForexMultiplier = function _tradeForexMultiplier() { - var deviceCurrency = this.config.exchanges.settings.currency; - var tradeCurrency = this.tradeExchange.currency(); - if (deviceCurrency === tradeCurrency) - return 1; - - var deviceRate = this._deviceRate(); - var tradeRate = this._tradeRate(); - return deviceRate && tradeRate ? - deviceRate / tradeRate : - null; -}; - -Trader.prototype._tradeBalanceFunc = function _tradeBalanceFunc(callback) { - if (!this.tradeExchange) return callback(null, null); - var forexMultiplier = this._tradeForexMultiplier(); - if (!forexMultiplier) return callback(new Error('Can\'t compute balance, no tickers yet.')); - this.tradeExchange.balance(function (err, localBalance) { - if (err) return callback(err); - callback(null, localBalance * forexMultiplier); - }); -}; - -Trader.prototype.pollBalance = function (callback) { - var self = this; - - logger.debug('collecting balance'); - - var transferBalanceFunc = this.transferExchange.balance.bind(this.transferExchange); - var tradeBalanceFunc = this._tradeBalanceFunc.bind(this); - - async.parallel({ - transferBalance: transferBalanceFunc, - tradeBalance: tradeBalanceFunc - }, function (err, balance) { - if (err) { - return callback && callback(err); - } - - balance.timestamp = Date.now(); - logger.debug('Balance update:', balance); - self.balance = balance; - - return callback && callback(); - }); -}; - -Trader.prototype.pollRate = function (callback) { - var self = this; - - logger.debug('polling for rates...'); - var deviceCurrency = this.config.exchanges.settings.currency; - var currencies = [deviceCurrency]; - if (this.tradeExchange) { - var tradeCurrency = this.tradeExchange.currency(); - if (tradeCurrency !== deviceCurrency) currencies.push(tradeCurrency); - } - - self.tickerExchange.ticker(currencies, function(err, resRates) { - if (err) { - logger.error(err); - return callback && callback(err); - } - - logger.debug('got rates: %j', resRates); - self.rateInfo = {rates: resRates, timestamp: new Date()}; - if (callback) callback(); - }); -}; - -Trader.prototype._deviceRate = function _deviceRate() { - if (!this.rateInfo) return null; - return this.rateInfo.rates[this.config.exchanges.settings.currency].rate; -}; - -Trader.prototype._tradeRate = function _tradeRate() { - if (!this.tradeExchange || !this.rateInfo) return null; - return this.rateInfo.rates[this.tradeExchange.currency()].rate; -}; - -// This is the rate in local currency to quote to the user -Trader.prototype.rate = function () { - if (!this.rateInfo) return null; - return { - rate: this._deviceRate(), - timestamp: this.rateInfo.timestamp - }; -};