diff --git a/lib/plugins.js b/lib/plugins.js index 492ea1ab..84da6173 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -18,9 +18,9 @@ var DEPOSIT_TIMEOUT = 130 * 1000; var db = null; -var tickerPlugin = null; +var tickerPlugins = {}; var traderPlugin = null; -var walletPlugin = null; +var walletPlugins = {}; var idVerifierPlugin = null; var infoPlugin = null; @@ -29,14 +29,9 @@ var currentlyUsedPlugins = {}; var cachedConfig = null; var deviceCurrency = 'USD'; -var lastBalances = null; +var lastBalances = {}; var lastRates = {}; -var balanceInterval = null; -var rateInterval = null; -var tradeInterval = null; -var reapTxInterval = null; - var tradesQueue = []; // that's basically a constructor @@ -104,10 +99,15 @@ function loadPlugin(name, config) { return plugin; } -function loadOrConfigPlugin(pluginHandle, pluginType, currency, +function loadOrConfigPlugin(pluginHandle, pluginType, cryptoCoin, currency, onChangeCallback) { - var currentName = cachedConfig.exchanges.plugins.current[pluginType]; - var pluginChanged = currentlyUsedPlugins[pluginType] !== currentName; + + if (!cryptoCoin) cryptoCoin = 'any' + var currentName = cryptoCoin === 'any' || cryptoCoin === 'BTC' + ? cachedConfig.exchanges.plugins.current[pluginType] + : cachedConfig.exchanges.plugins.current[cryptoCoin][pluginType] + + var pluginChanged = currentlyUsedPlugins[cryptoCoin][pluginType] !== currentName; if (!currentName) pluginHandle = null; else { // some plugins may be disabled @@ -119,9 +119,10 @@ function loadOrConfigPlugin(pluginHandle, pluginType, currency, if (pluginHandle && !pluginChanged) pluginHandle.config(pluginConfig); else { pluginHandle = loadPlugin(currentName, pluginConfig); - currentlyUsedPlugins[pluginType] = currentName - logger.debug('plugin(%s) loaded: %s', pluginType, pluginHandle.NAME || - currentName); + currentlyUsedPlugins[cryptoCoin] ||= {} + currentlyUsedPlugins[cryptoCoin][pluginType] = currentName + logger.debug('[%s] plugin(%s) loaded: %s', cryptoCoin, pluginType, pluginHandle.NAME || + currentName); } } @@ -138,34 +139,40 @@ exports.configure = function configure(config) { cachedConfig = config; deviceCurrency = config.exchanges.settings.currency; + cryptoCoins = config.exchanges.settings.coins || ['BTC']; - // TICKER [required] configure (or load) - loadOrConfigPlugin( - tickerPlugin, - 'ticker', - deviceCurrency, // device currency - function onTickerChange(newTicker) { - tickerPlugin = newTicker; - pollRate(); - } - ); + cryptoCoins.forEach(function (cryptoCoin) { + // TICKER [required] configure (or load) + loadOrConfigPlugin( + tickerPlugins[cryptoCoin], + 'ticker', + cryptoCoin, + deviceCurrency, // device currency + function onTickerChange(newTicker) { + tickerPlugins[cryptoCoin] = newTicker; + pollRate(cryptoCoin); + } + ); - // WALLET [required] configure (or load) - loadOrConfigPlugin( - walletPlugin, - 'transfer', - null, - function onWalletChange(newWallet) { - walletPlugin = newWallet; - pollBalance(); - } - ); + // WALLET [required] configure (or load) + loadOrConfigPlugin( + walletPlugins[cryptoCoin], + 'transfer', + cryptoCoin, + null, + function onWalletChange(newWallet) { + walletPlugins[cryptoCoin] = newWallet; + pollBalance(cryptoCoin); + } + ); + }) // TRADER [optional] configure (or load) traderPlugin = loadOrConfigPlugin( traderPlugin, 'trade', null, + null, function onTraderChange(newTrader) { traderPlugin = newTrader; if (newTrader === null) stopTrader(); @@ -222,26 +229,35 @@ exports.pollQueries = function pollQueries(session, cb) { }); }; -function _sendBitcoins(toAddress, satoshis, cb) { +function _sendCoins(toAddress, cryptoUnits, cryptoCoin, cb) { + var walletPlugin = walletPlugins[cryptoCoin] var transactionFee = cachedConfig.exchanges.settings.transactionFee; - walletPlugin.sendBitcoins(toAddress, satoshis, transactionFee, cb); + if (cryptoCoin === 'BTC') + walletPlugin.sendBitcoins(toAddress, cryptoUnits, transactionFee, cb); + else + walletPlugin.sendCoins(toAddress, cryptoUnits, cryptoCoin, transactionFee, cb); } function executeTx(session, tx, authority, cb) { db.addOutgoingTx(session, tx, function(err, toSend) { if (err) return cb(err); - var satoshisToSend = toSend.satoshis; - if (satoshisToSend === 0) + var cryptoUnitsToSend = toSend.cryptoUnits; + if (cryptoUnitsToSend === 0) return cb(null, {statusCode: 204, txId: tx.txId, txHash: null}); - _sendBitcoins(tx.toAddress, satoshisToSend, function(_err, txHash) { + _sendCoins(tx.toAddress, cryptoUnitsToSend, function(_err, txHash) { var fee = null; // Need to fill this out in plugins - if (_err) toSend = {satoshis: 0, fiat: 0}; + if (_err) toSend = {cryptoUnits: new BigNumber(0), fiat: 0}; db.sentCoins(session, tx, authority, toSend, fee, _err, txHash); if (_err) return cb(_err); - pollBalance(); + var cryptoCoin = tx.coin + ? tx.coin.unitCode + : 'BTC' + + pollBalance('BTC'); + cb(null, { statusCode: 201, // Created txHash: txHash, @@ -326,6 +342,13 @@ exports.cashOut = function cashOut(session, tx, cb) { label: 'TX ' + Date.now(), account: 'deposit' }; + + var cryptoCoin = tx.coin + ? tx.coin.unitCode + : 'BTC' + + var walletPlugin = walletPlugins[cryptoCoin] + walletPlugin.newAddress(tmpInfo, function(err, address) { if (err) return cb(err); @@ -341,11 +364,12 @@ exports.dispenseAck = function dispenseAck(session, rec) { db.addDispense(session, rec.tx, rec.cartridges); }; -exports.fiatBalance = function fiatBalance() { - var rawRate = exports.getDeviceRate().rates.ask; +exports.fiatBalance = function fiatBalance(cryptoCoin) { + var rawRate = exports.getDeviceRate(cryptoCoin).rates.ask; var commission = cachedConfig.exchanges.settings.commission; + var lastBalance = lastBalances[cryptoCoin] - if (!rawRate || !lastBalances) return null; + if (!rawRate || !lastBalance) return null; // The rate is actually our commission times real rate. var rate = commission * rawRate; @@ -356,16 +380,9 @@ exports.fiatBalance = function fiatBalance() { // `balance.transferBalance` is the balance of our transfer account (the one // we use to send Bitcoins to clients) in satoshis. - var transferBalance = lastBalances.transferBalance.BTC; + var transferBalance = lastBalances.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; + var fiatTransferBalance = (transferBalance * rate) / lowBalanceMargin; return fiatTransferBalance; }; @@ -376,14 +393,12 @@ exports.fiatBalance = function fiatBalance() { exports.startPolling = function startPolling() { executeTrades(); - if (!balanceInterval) - balanceInterval = setInterval(pollBalance, POLLING_RATE); + cryptoCoins.forEach(function (coin) { + setInterval(async.apply(pollBalance, coin), POLLING_RATE); + setInterval(async.apply(pollRate, coin), POLLING_RATE); + }); - if (!rateInterval) - rateInterval = setInterval(pollRate, POLLING_RATE); - - if (!reapTxInterval) - reapTxInterval = setInterval(reapTxs, REAP_RATE); + setInterval(reapTxs, REAP_RATE); startTrader(); }; @@ -400,6 +415,7 @@ function startTrader() { ); } } + function stopTrader() { if (tradeInterval) { clearInterval(tradeInterval); @@ -408,36 +424,40 @@ function stopTrader() { } } -function pollBalance(cb) { - logger.debug('collecting balance'); - +function pollBalance(cryptoCoin, cb) { + logger.debug('[%s] collecting balance', cryptoCoin); + + var walletPlugin = walletPlugins[cryptoCoin] 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) { + walletPlugin.balance(function(err, balance) { if (err) { logger.error(err); return cb && cb(err); } - logger.debug('Balance update:', balance); + logger.debug('[%s] Balance update:', cryptoCoin, balance); balance.timestamp = Date.now(); - lastBalances = balance; + lastBalances[cryptoCoin] = balance; return cb && cb(null, lastBalances); }); } -function pollRate(cb) { - logger.debug('polling for rates (%s)', tickerPlugin.NAME); - +function pollRates (cb) { + var polls = cryptoCoins.map(function (cryptoCoin) { + async.apply(pollRate, cryptoCoin) + }); + + async.parallel(polls, cb); +} + +function pollRate(cryptoCoin, cb) { + logger.debug('[%s] polling for rates (%s)', cryptoCoin, tickerPlugin.NAME); + var tickerPlugin = tickerPlugins[cryptoCoin]; + tickerPlugin.ticker(deviceCurrency, function(err, resRates) { if (err) { logger.error(err); @@ -446,7 +466,7 @@ function pollRate(cb) { logger.debug('got rates: %j', resRates); resRates.timestamp = new Date(); - lastRates = resRates; + lastRates[cryptoCoin] = resRates; return cb && cb(null, lastRates); }); @@ -455,16 +475,14 @@ function pollRate(cb) { /* * Getters | Helpers */ -function getLastRate(currency) { - if (!lastRates) return null; - var tmpCurrency = currency || deviceCurrency; - if (!lastRates[tmpCurrency]) return null; +exports.getDeviceRate = function getDeviceRate(cryptoCoin) { + if (!lastRates[cryptoCoin]) return null; - return lastRates[tmpCurrency]; -} -exports.getDeviceRate = function getDeviceRate() { - return getLastRate(deviceCurrency); + var lastRate = lastRates[cryptoCoin] + if (!lastRate) return null; + + return lastRate[deviceCurrency]; }; exports.getBalance = function getBalance() { @@ -479,7 +497,7 @@ exports.getBalance = function getBalance() { function purchase(trade, cb) { traderPlugin.purchase(trade.satoshis, null, function(err) { if (err) return cb(err); - pollBalance(); + pollBalance('BTC'); if (typeof cb === 'function') cb(); }); }