This commit is contained in:
Josh Harvey 2016-03-27 16:57:18 +02:00
parent 668ba7d08c
commit c8ba96515f

View file

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