diff --git a/lib/app.js b/lib/app.js index 547169c4..b7a2bbf4 100644 --- a/lib/app.js +++ b/lib/app.js @@ -23,17 +23,44 @@ var express = require('express'); var fs = require('fs'); var LamassuConfig = require('lamassu-config'); var routes = require('./routes'); +var Trader = require('./trader'); +var PostgresqlInterface = require('./protocol/db/postgresql_interface'); module.exports = function (options) { var app = express(); var connectionString; var server; var config; + var trader; + var db; connectionString = options.postgres || 'postgres://lamassu:lamassu@localhost/lamassu'; config = new LamassuConfig(connectionString); + db = new PostgresqlInterface(connectionString); + trader = new Trader(db); + + config.load(function (err, config) { + if (err) { + console.error('Loading config failed'); + throw err; + } + + trader.configure(config); + trader.startPolling(); + }); + + config.on('configUpdate', function () { + config.load(function (err, config) { + if (err) { + return console.error('Error while reloading config'); + } + + trader.configure(config); + console.log('Config reloaded'); + }); + }); app.use(express.logger()); app.use(express.favicon()); @@ -56,28 +83,27 @@ module.exports = function (options) { server = https.createServer(serverOptions, app); } - config.load(function(err, conf) { - if (err) { console.log(err); process.exit(1); } + var authMiddleware = function (req, res, next) { + req.device = {}; + return next(); + }; - var authMiddleware = function (req, res, next) { return next(); }; + if (options.https) { + authMiddleware = function(req, res, next) { + var fingerprint = req.connection.getPeerCertificate().fingerprint; + var e = new Error('Unauthorized'); + e.status = 401; - if (options.https) { - authMiddleware = function(req, res, next) { - var fingerprint = req.connection.getPeerCertificate().fingerprint; - var e = new Error('Unauthorized'); - e.status = 401; + config.isAuthorized(fingerprint, function (err, device) { + if (err) { return next(e); } + if (!device) { return next(e); } + req.device = device; + next(); + }); + }; + } - config.isAuthorized(fingerprint, function (err, device) { - if (err) { return next(e); } - if (!device) { return next(e); } - req.device = device; - next(); - }); - }; - } - - routes.init(app, conf, config, authMiddleware); - }); + routes.init(app, trader, authMiddleware); return server; }; diff --git a/lib/protocol/api/api.js b/lib/protocol/api/api.js deleted file mode 100644 index 2ad7bfa6..00000000 --- a/lib/protocol/api/api.js +++ /dev/null @@ -1,145 +0,0 @@ -'use strict'; - -require('date-utils'); - -//var async = require('async'); -var winston = require('winston'); -var logger = new (winston.Logger)({transports:[new (winston.transports.Console)()]}); -var path = require('path'); - -var _transferExchange; -var _tickerExchange; -var _tradeExchange; -var _rates = {}; -var _config; -var _commission; -var _config; -var SATOSHI_FACTOR = Math.pow(10, 8); - -exports.ticker = require('./ticker'); -exports.trade = require('./trade'); -exports.send = require('./send'); -exports.balance = require('./balance'); -exports._tradeExchange = null; -exports._transferExchange = null; - -exports.findExchange = function (name) { - var exchange; - - try { - exchange = require('lamassu-' + name); - } catch (err) { - if (!err.message.match(/Cannot find module/)) throw err; - exchange = require(path.join(path.dirname(__dirname), 'exchanges', name)); - } - - return exchange; -}; - -exports.findTicker = function (name) { - var exchange = exports.findExchange(name); - return exchange.ticker || exchange; -}; - -exports.findTrader = function (name) { - var exchange = exports.findExchange(name); - return exchange.trader || exchange; -}; - -exports.findWallet = function (name) { - var exchange = exports.findExchange(name); - return exchange.wallet || exchange; -}; - -exports.triggerBalance = function triggerBalance() { - this.balance.triggerBalance(); -}; - -exports.init = function(config) { - _config = config; - - if (config.settings.lowBalanceMargin < 1) { - throw new Error('`settings.lowBalanceMargin` has to be >= 1'); - } - - var tickerExchangeCode = config.plugins.current.ticker; - var tickerExchangeConfig = config.plugins.settings[tickerExchangeCode] || {}; - tickerExchangeConfig.currency = config.settings.currency; - _tickerExchange = exports.findTicker(tickerExchangeCode).factory(tickerExchangeConfig); - - var tradeExchangeCode = config.plugins.current.trade; - if (tradeExchangeCode) { - var tradeExchangeConfig = config.plugins.settings[tradeExchangeCode]; - _tradeExchange = exports.findTrader(tradeExchangeCode).factory(tradeExchangeConfig); - } - - var transferExchangeCode = config.plugins.current.transfer; - var transferExchangeConfig = config.plugins.settings[transferExchangeCode]; - _commission = config.settings.commission; - _transferExchange = exports.findWallet(transferExchangeCode).factory(transferExchangeConfig); - - var doRequestTradeExchange = _tradeExchange && tradeExchangeCode !== transferExchangeCode; - - exports._tradeExchange = _tradeExchange; - exports._transferExchange = _transferExchange; - exports.ticker.init(config, exports, _tickerExchange); - exports.trade.init(config, exports, _tradeExchange, exports.ticker); - exports.send.init(config, exports, _transferExchange, exports.ticker); - exports.balance.init(config, exports, _transferExchange, - doRequestTradeExchange ? _tradeExchange : null); -}; - -/** - * return fiat balance - * - * in input to this function, balance has the following parameters... - * - * balance.transferBalance - in satoshis - * balance.tradeBalance - in USD - * - * Have added conversion here, but this really needs to be thought through, lamassu-bitstamp should perhaps - * return balance in satoshis - */ -exports.fiatBalance = function(rate, balance, transferSatoshis, tradeFiat, callback) { - if (!rate || !balance) return 0; - - // The rate is actually our commission times real rate. - rate = _commission * rate; - - // `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 = _config.settings.lowBalanceMargin; - - // `balance.transferBalance` is the balance of our transfer account (the one - // we use to send Bitcoins to clients). `transferSatoshis` is the number - // of satoshis we're expected to send for this transaction. By subtracting - // them, we get `adjustedTransferBalance`, amount of satoshis we'll have - // after the transaction. - var adjustedTransferBalance = balance.transferBalance - transferSatoshis; - - // Since `adjustedTransferBalance` 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 = ((adjustedTransferBalance / 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 need to secure `tradeFiat` (amount of fiat in this transaction) and - // enough fiat to cover our trading queue (trades aren't executed immediately). - var adjustedFiat = tradeFiat + exports.trade.queueFiatBalance(rate); - - // So we subtract `adjustedFiat` from `tradeBalance` and again, apply - // `lowBalanceMargin`. - var fiatTradeBalance = (tradeBalance - adjustedFiat) / lowBalanceMargin; - - // And we return the smallest number. - return Math.min(fiatTransferBalance, fiatTradeBalance); -}; - - diff --git a/lib/protocol/api/send.js b/lib/protocol/api/send.js deleted file mode 100644 index 6f3b9672..00000000 --- a/lib/protocol/api/send.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -var _transferExchange; -var _api; -var _config; -var _conString = process.env.DATABASE_URL || 'postgres://lamassu:lamassu@localhost/lamassu'; -var _db = require('../db/postgresql_interface').factory(_conString); - -exports.init = function(config, api, transferExchange) { - _api = api; - _config = config; - _transferExchange = transferExchange; -}; - -exports.setDomain = function(domain) { - _transferExchange.setDomain(domain); -}; - -exports.sendBitcoins = function sendBitcoins(deviceFingerprint, tx, cb) { - _db.summonTransaction(deviceFingerprint, tx, function (err, isNew, txHash) { - if (err) return cb(err); - if (isNew) return _transferExchange.sendBitcoins(tx.toAddress, tx.satoshis, - _config.settings.transactionFee, function(err, txHash) { - if (err) { - _db.reportTransactionError(tx, err); - return cb(err); - } - cb(null, txHash); - _db.completeTransaction(tx, txHash); - _api.triggerBalance(); - }); - - // transaction exists, but txHash might be null, - // in which case ATM should continue polling - cb(null, txHash); - }); -}; diff --git a/lib/protocol/api/ticker.js b/lib/protocol/api/ticker.js deleted file mode 100644 index 635c6d97..00000000 --- a/lib/protocol/api/ticker.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -require('date-utils'); -var winston = require('winston'); -var logger = new (winston.Logger)({transports:[new (winston.transports.Console)()]}); - -var _tickerExchange; -var _api; -var _rates = {}; - -var _pollRate = function(currency) { - logger.info('polling for rate...'); - _tickerExchange.ticker(currency, function(err, rate) { - if (err) return; - logger.info('Rate update:', rate); - _rates[currency] = {rate: rate, timestamp: new Date()}; - }); -}; - -exports.init = function(config, api, tickerExchange) { - _api = api; - _tickerExchange = tickerExchange; - - _pollRate(config.settings.currency); - setInterval(function () { - _pollRate(config.settings.currency); - }, 60 * 1000); -}; - -exports.rate = function(currency) { - if (!_rates[currency]) return null; - return _rates[currency]; -}; diff --git a/lib/routes.js b/lib/routes.js index e2a646d6..7ba01df8 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -1,9 +1,6 @@ 'use strict'; -var api = exports.api = require('./protocol/api/api'); -var _config; -var _lamassuConfig; -var _commission; +var _trader; // Make sure these are higher than polling interval // or there will be a lot of errors @@ -26,8 +23,8 @@ var poll = function(req, res) { }); } - var rateRec = api.ticker.rate(req.params.currency); - var satoshiBalanceRec = api.balance.balance(); + var rateRec = _trader.rate(req.params.currency); + var satoshiBalanceRec = _trader.balance; if (rateRec === null || satoshiBalanceRec === null) return res.json({err: 'Server initializing'}); @@ -40,10 +37,10 @@ var poll = function(req, res) { res.json({ err: null, - rate: rate * _commission, - fiat: api.fiatBalance(rate, satoshiBalanceRec, 0, 0), + rate: rate * _trader.config.exchanges.settings.commission, + fiat: api.fiatBalance(0, 0), currency: req.params.currency, - txLimit: parseInt(_config.exchanges.settings.compliance.maximum.limit, 10) + txLimit: parseInt(_trader.config.exchanges.settings.compliance.maximum.limit, 10) }); }; @@ -76,33 +73,13 @@ var pair = function(req, res) { ); }; -exports.init = function(app, config, lamassuConfig, authMiddleware) { - _config = config; - _lamassuConfig = lamassuConfig; - - api.init(_config.exchanges); - - _commission = _config.exchanges.settings.commission; - - exports._tradeExchange = api._tradeExchange; - exports._transferExchange = api._transferExchange; +exports.init = function(app, trader, authMiddleware) { + _trader = trader; app.get('/poll/:currency', authMiddleware, poll); app.post('/trade', authMiddleware, trade); app.post('/send', authMiddleware, send); app.post('/pair', pair); - lamassuConfig.on('configUpdate', function () { - _lamassuConfig.load(function(err, config) { - if (err) { - return console.error('Error while reloading config'); - } - - _config = config; - api.init(_config.exchanges); - console.log('Config reloaded'); - }); - }); - return app; }; diff --git a/lib/trader.js b/lib/trader.js index b28ecf32..2175dd1c 100644 --- a/lib/trader.js +++ b/lib/trader.js @@ -1,6 +1,8 @@ 'use strict'; var path = require('path'); +var async = require('async'); +var winston = require('winston'); var SATOSHI_FACTOR = Math.pow(10, 8); @@ -10,6 +12,7 @@ var Trader = module.exports = function (db) { } this.db = db; + this.rates = {}; this.logger = new (winston.Logger)({ transports: [new (winston.transports.Console)()] }); @@ -29,38 +32,38 @@ Trader.prototype._findExchange = function (name) { }; Trader.prototype._findTicker = function (name) { - var exchange = Trader.prototype.findExchange(name); + var exchange = Trader.prototype._findExchange(name); return exchange.ticker || exchange; }; Trader.prototype._findTrader = function (name) { - var exchange = Trader.prototype.findExchange(name); + var exchange = Trader.prototype._findExchange(name); return exchange.trader || exchange; }; Trader.prototype._findWallet = function (name) { - var exchange = Trader.prototype.findExchange(name); + var exchange = Trader.prototype._findExchange(name); return exchange.wallet || exchange; }; Trader.prototype.configure = function (config) { - if (config.settings.lowBalanceMargin < 1) { + if (config.exchanges.settings.lowBalanceMargin < 1) { throw new Error('`settings.lowBalanceMargin` has to be >= 1'); } - var tickerExchangeCode = config.plugins.current.ticker; - var tickerExchangeConfig = config.plugins.settings[tickerExchangeCode] || {}; - tickerExchangeConfig.currency = config.settings.currency; - this.tickerExchange = exports.findTicker(tickerExchangeCode).factory(tickerExchangeConfig); + var tickerExchangeCode = config.exchanges.plugins.current.ticker; + var tickerExchangeConfig = config.exchanges.plugins.settings[tickerExchangeCode] || {}; + tickerExchangeConfig.currency = config.exchanges.settings.currency; + this.tickerExchange = this._findTicker(tickerExchangeCode).factory(tickerExchangeConfig); - var tradeExchangeCode = config.plugins.current.trade; + var tradeExchangeCode = config.exchanges.plugins.current.trade; if (tradeExchangeCode) { - var tradeExchangeConfig = config.plugins.settings[tradeExchangeCode]; + var tradeExchangeConfig = config.exchanges.plugins.settings[tradeExchangeCode]; this.tradeExchange = this._findTrader(tradeExchangeCode).factory(tradeExchangeConfig); } - var transferExchangeCode = config.plugins.current.transfer; - var transferExchangeConfig = config.plugins.settings[transferExchangeCode]; + var transferExchangeCode = config.exchanges.plugins.current.transfer; + var transferExchangeConfig = config.exchanges.plugins.settings[transferExchangeCode]; this.transferExchange = this._findWallet(transferExchangeCode).factory(transferExchangeConfig); this.config = config; @@ -154,7 +157,11 @@ Trader.prototype.sendBitcoins = function (deviceFingerprint, tx, cb) { }; Trader.prototype.startPolling = function () { + this.pollBalance(); + this.pollRate(); + setInterval(this.pollBalance.bind(this), 60 * 1000); + setInterval(this.pollRate.bind(this), 60 * 1000); }; Trader.prototype.pollBalance = function () { @@ -177,3 +184,19 @@ Trader.prototype.pollBalance = function () { self.balance = balance; }); }; + +Trader.prototype.pollRate = function () { + var self = this; + + var currency = self.config.exchanges.settings.currency; + self.logger.info('polling for rate...'); + self.tickerExchange.ticker(currency, function(err, rate) { + if (err) return; + self.logger.info('Rate update:', rate); + self.rates[currency] = {rate: rate, timestamp: new Date()}; + }); +}; + +Trader.prototype.rate = function (currency) { + return this.rates[currency]; +};