diff --git a/lib/exchange.js b/lib/exchange.js index 10070c2b..3b5640ac 100644 --- a/lib/exchange.js +++ b/lib/exchange.js @@ -1,5 +1,5 @@ const configManager = require('./new-config-manager') -const ph = require('./plugin-helper') +const ccxt = require('./plugins/exchange/ccxt') function lookupExchange (settings, cryptoCode) { const exchange = configManager.getWalletSettings(cryptoCode, settings.config).exchange @@ -10,23 +10,22 @@ function lookupExchange (settings, cryptoCode) { function fetchExchange (settings, cryptoCode) { return Promise.resolve() .then(() => { - const plugin = lookupExchange(settings, cryptoCode) - if (!plugin) throw new Error('No exchange set') - const exchange = ph.load(ph.EXCHANGE, plugin) - const account = settings.accounts[plugin] + const exchangeName = lookupExchange(settings, cryptoCode) + if (!exchangeName) throw new Error('No exchange set') + const account = settings.accounts[exchangeName] - return {exchange, account} + return { exchangeName, account } }) } function buy (settings, cryptoAtoms, fiatCode, cryptoCode) { return fetchExchange(settings, cryptoCode) - .then(r => r.exchange.buy(r.account, cryptoAtoms, fiatCode, cryptoCode)) + .then(r => ccxt.trade('buy', r.account, cryptoAtoms, fiatCode, cryptoCode, r.exchangeName)) } function sell (settings, cryptoAtoms, fiatCode, cryptoCode) { return fetchExchange(settings, cryptoCode) - .then(r => r.exchange.sell(r.account, cryptoAtoms, fiatCode, cryptoCode)) + .then(r => ccxt.trade('sell', r.account, cryptoAtoms, fiatCode, cryptoCode, r.exchangeName)) } function active (settings, cryptoCode) { diff --git a/lib/plugins/common/bitstamp.js b/lib/plugins/common/bitstamp.js deleted file mode 100644 index b031668c..00000000 --- a/lib/plugins/common/bitstamp.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict' - -const querystring = require('querystring') -const axios = require('axios') -const crypto = require('crypto') -const _ = require('lodash/fp') - -const API_ENDPOINT = 'https://www.bitstamp.net/api/v2' - -let counter = -1 -let lastTimestamp = Date.now() - -function pad (num) { - const asString = num.toString(10) - if (num < 10) return '00' + asString - if (num < 100) return '0' + asString - return asString -} - -function generateNonce () { - const timestamp = Date.now() - if (timestamp !== lastTimestamp) counter = -1 - lastTimestamp = timestamp - counter = (counter + 1) % 1000 - return timestamp.toString(10) + pad(counter) -} - -function authRequest (config, path, data) { - if (!config.key || !config.secret || !config.clientId) { - const err = new Error('Must provide key, secret and client ID') - return Promise.reject(err) - } - - data = data || {} - - const nonce = generateNonce() - const msg = [nonce, config.clientId, config.key].join('') - - const signature = crypto - .createHmac('sha256', Buffer.from(config.secret)) - .update(msg) - .digest('hex') - .toUpperCase() - - const signedData = _.merge(data, { - key: config.key, - signature: signature, - nonce: nonce - }) - - return request(path, 'POST', signedData) -} - -function buildMarket (fiatCode, cryptoCode) { - if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) { - throw new Error('Unsupported crypto: ' + cryptoCode) - } - - if (!_.includes(fiatCode, ['USD', 'EUR'])) { - throw new Error('Unsupported fiat: ' + fiatCode) - } - - return `${cryptoCode.toLowerCase()}${fiatCode.toLowerCase()}` -} - -function request (path, method, data) { - const options = { - method: method, - url: API_ENDPOINT + path + '/', - headers: { - 'User-Agent': 'Mozilla/4.0 (compatible; Lamassu client)', - 'Content-Type': 'application/x-www-form-urlencoded' - } - } - - if (data) options.data = querystring.stringify(data) - - return axios(options) - .then(r => r.data) -} - -module.exports = { - authRequest, - request, - buildMarket -} diff --git a/lib/plugins/common/ccxt.js b/lib/plugins/common/ccxt.js index d0793bf3..0b990f79 100644 --- a/lib/plugins/common/ccxt.js +++ b/lib/plugins/common/ccxt.js @@ -13,16 +13,16 @@ const FIAT = { kraken: ['USD', 'EUR'] } -module.exports = { verifyCurrencies } - function verifyCurrencies (exchangeName, fiatCode, cryptoCode) { if (!_.includes(cryptoCode, CRYPTO[exchangeName])) { throw new Error('Unsupported crypto: ' + cryptoCode) } - if (!(exchangeName === 'coinbase')) { // coinbase is only used for ticker and it's expected to support most of the fiat + if (exchangeName !== 'coinbase') { if (!_.includes(fiatCode, FIAT[exchangeName])) { throw new Error('Unsupported fiat: ' + fiatCode) } } return cryptoCode + '/' + fiatCode } + +module.exports = { verifyCurrencies, CRYPTO, FIAT } diff --git a/lib/plugins/common/itbit.js b/lib/plugins/common/itbit.js deleted file mode 100644 index e169c05c..00000000 --- a/lib/plugins/common/itbit.js +++ /dev/null @@ -1,92 +0,0 @@ -'use strict' - -const querystring = require('querystring') -const axios = require('axios') -const crypto = require('crypto') -const _ = require('lodash/fp') - -const API_ENDPOINT = 'https://api.itbit.com/v1' - -let counter = -1 -let lastTimestamp = Date.now() - -function generateNonce () { - const timestamp = Date.now() - if (timestamp !== lastTimestamp) counter = -1 - lastTimestamp = timestamp - counter = (counter + 1) % 1000 - return timestamp.toString() + counter.toString() -} - -function authRequest (account, method, path, data) { - if (!account.userId || !account.walletId || !account.clientKey || !account.clientSecret) { - const err = new Error('Must provide user ID, wallet ID, client key, and client secret') - return Promise.reject(err) - } - - const url = buildURL(method, path, data) - const dataString = method !== 'GET' && !_.isEmpty(data) ? JSON.stringify(data) : '' - const nonce = generateNonce() - const timestamp = Date.now() - const message = nonce + JSON.stringify([method, url, dataString, nonce.toString(), timestamp.toString()]) - - const hashBuffer = crypto - .createHash('sha256') - .update(message).digest() - - const bufferToHash = Buffer.concat([Buffer.from(url), hashBuffer]) - - const signature = crypto - .createHmac('sha512', Buffer.from(account.clientSecret)) - .update(bufferToHash) - .digest('base64') - - return request(method, path, data, { - 'Authorization': account.clientKey + ':' + signature, - 'X-Auth-Timestamp': timestamp, - 'X-Auth-Nonce': nonce - }) -} - -function request (method, path, data, auth) { - const options = { - method: method, - url: buildURL(method, path, data), - headers: { - 'User-Agent': 'Lamassu itBit node.js client', - ...(auth) - }, - ...(method !== 'GET' && { data: data }) - } - - return axios(options) - .then(r => r.data) - .catch(e => { - var description = _.get(e, 'response.data.description') - throw new Error(description || e.message) - }) -} - -const cryptoCodeTranslations = { 'BTC': 'XBT', 'ETH': 'ETH' } -function buildMarket (fiatCode, cryptoCode) { - const translatedCryptoCode = cryptoCodeTranslations[cryptoCode] - if (!translatedCryptoCode) { - throw new Error('Unsupported crypto: ' + cryptoCode) - } - - if (!_.includes(fiatCode, ['USD', 'EUR', 'SGD'])) { - throw new Error('Unsupported fiat: ' + fiatCode) - } - - return translatedCryptoCode + fiatCode -} - -function buildURL (method, path, data) { - let url = API_ENDPOINT + path - if (method === 'GET' && !_.isEmpty(data)) { - url += '?' + querystring.stringify(data) - } - return url -} - -module.exports = { authRequest, request, buildMarket } diff --git a/lib/plugins/common/kraken.js b/lib/plugins/common/kraken.js deleted file mode 100644 index 9bc63928..00000000 --- a/lib/plugins/common/kraken.js +++ /dev/null @@ -1,28 +0,0 @@ -const PAIRS = { - BTC: { - USD: 'XXBTZUSD', - EUR: 'XXBTZEUR' - }, - ETH: { - USD: 'XETHZUSD', - EUR: 'XETHZEUR' - }, - ZEC: { - USD: 'XZECZUSD', - EUR: 'XZECZEUR' - }, - LTC: { - USD: 'XLTCZUSD', - EUR: 'XLTCZEUR' - }, - DASH: { - USD: 'DASHUSD', - EUR: 'DASHEUR' - }, - BCH: { - USD: 'BCHUSD', - EUR: 'BCHEUR' - } -} - -module.exports = {PAIRS} diff --git a/lib/plugins/exchange/bitstamp.js b/lib/plugins/exchange/bitstamp.js new file mode 100644 index 00000000..7e9d9086 --- /dev/null +++ b/lib/plugins/exchange/bitstamp.js @@ -0,0 +1,21 @@ +const common = require('../common/ccxt') +const _ = require('lodash/fp') +const consts = require('./consts') +const ORDER_TYPE = consts.ORDER_TYPES.MARKET +const FIAT = common.FIAT['kraken'] +const CRYPTO = common.CRYPTO['kraken'] + +const loadConfig = (account) => { + const mapper = { + 'key': 'apiKey', + 'clientId': 'uid' + } + const mapped = _.mapKeys(key => mapper[key] ? mapper[key] : key)(account) + return { ...mapped, timeout: 3000 } +} + +const isConfigValid = ({ key, clientId, secret }) => key && secret && clientId + +const amountPrecision = () => 8 + +module.exports = { loadConfig, isConfigValid, amountPrecision , CRYPTO, FIAT, ORDER_TYPE } diff --git a/lib/plugins/exchange/bitstamp/bitstamp.js b/lib/plugins/exchange/bitstamp/bitstamp.js deleted file mode 100644 index 6175e873..00000000 --- a/lib/plugins/exchange/bitstamp/bitstamp.js +++ /dev/null @@ -1,45 +0,0 @@ -const common = require('../../common/bitstamp') -const coinUtils = require('../../../coin-utils') - -function buy (account, cryptoAtoms, fiatCode, cryptoCode) { - return trade('buy', account, cryptoAtoms, fiatCode, cryptoCode) -} - -function sell (account, cryptoAtoms, fiatCode, cryptoCode) { - return trade('sell', account, cryptoAtoms, fiatCode, cryptoCode) -} - -function handleErrors (data) { - if (!data.reason || !data.reason.__all__) return data - - const err = new Error(data.reason.__all__[0]) - - if (data.reason.__all__[0].indexOf('Minimum order size is') === 0) { - err.name = 'orderTooSmall' - } - - throw err -} - -function trade (type, account, cryptoAtoms, _fiatCode, cryptoCode) { - const fiatCode = _fiatCode === 'USD' ? 'USD' : 'EUR' - - try { - const market = common.buildMarket(fiatCode, cryptoCode) - const options = {amount: coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(8)} - - return common.authRequest(account, '/' + type + '/market/' + market, options) - .catch(e => { - if (e.response) handleErrors(e.response.data) - throw e - }) - .then(handleErrors) - } catch (e) { - return Promise.reject(e) - } -} - -module.exports = { - buy, - sell -} diff --git a/lib/plugins/exchange/ccxt.js b/lib/plugins/exchange/ccxt.js new file mode 100644 index 00000000..9f90756b --- /dev/null +++ b/lib/plugins/exchange/ccxt.js @@ -0,0 +1,51 @@ +var ccxt = require('ccxt') +const coinUtils = require('../../coin-utils') +const common = require('../common/ccxt') +const kraken = require('./kraken') +const bitstamp = require('./bitstamp') +const itbit = require('./itbit') +const consts = require('./consts') + +const ALL_EXCHANGES = { + kraken, + bitstamp, + itbit +} + +function trade (side, account, cryptoAtoms, fiatCode, cryptoCode, exchangeName) { + try { + const exchangeConfig = ALL_EXCHANGES[exchangeName] + + if (!exchangeConfig) throw Error('no exchange') + if (exchangeConfig.isConfigValid && !exchangeConfig.isConfigValid(account)) throw Error('Invalid config') + + const exchange = new ccxt[exchangeName](exchangeConfig.loadConfig(account)) + + const symbol = common.verifyCurrencies(exchangeName, fiatCode, cryptoCode) + const precision = exchangeConfig.amountPrecision ? exchangeConfig.amountPrecision() : consts.DECIMAL_PRECISION.DEFAULT_AMOUNT + const amount = coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(precision) + const options = exchangeConfig.loadOptions ? exchangeConfig.loadOptions(account) : {} + if (exchangeConfig.ORDER_TYPE === consts.ORDER_TYPES.MARKET) { + return exchange.createOrder(symbol, consts.ORDER_TYPES.MARKET, side, amount, null, options) + } + return exchange.fetchOrderBook(symbol) + .then(orderBook => { + const price = calculatePrice(side, amount, orderBook).toFixed(consts.DECIMAL_PRECISION.PRICE) + return exchange.createOrder(symbol, consts.ORDER_TYPES.LIMIT, side, amount, price, options) + }) + } catch (e) { + return Promise.reject(e) + } +} + +function calculatePrice (side, amount, orderBook) { + const book = side === 'buy' ? 'asks' : 'bids' + let collected = 0.0 + for (const entry of orderBook[book]) { + collected += parseFloat(entry[1]) + if (collected >= amount) return parseFloat(entry[0]) + } + throw new Error('Insufficient market depth') +} + +module.exports = { trade } diff --git a/lib/plugins/exchange/ccxt/ccxt.js b/lib/plugins/exchange/ccxt/ccxt.js deleted file mode 100644 index cf768477..00000000 --- a/lib/plugins/exchange/ccxt/ccxt.js +++ /dev/null @@ -1,73 +0,0 @@ -var ccxt = require('ccxt') -const coinUtils = require('../../../coin-utils') -const _ = require('lodash/fp') -const common = require('../../common/ccxt') - -function trade (side, account, cryptoAtoms, fiatCode, cryptoCode, exchangeName) { - try { - const exchange = setUpExchange(account, exchangeName) - const symbol = common.verifyCurrencies(exchangeName, fiatCode, cryptoCode) - const amount = coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(8) - if (exchangeName === 'itbit') { - return exchange.fetchOrderBook(symbol) - .then(orderBook => { - const price = calculatePrice(side, amount, orderBook) - return exchange.createOrder(symbol, 'limit', side, amount, price, { walletId: account.walletId }) - }) - } - return exchange.createOrder(symbol, 'market', side, amount) - } catch (e) { - return Promise.reject(e) - } -} - -function setUpExchange (account, exchangeName) { - // map given credentials to cctx properties - if (!_.includes(exchangeName, ccxt.exchanges)) { - throw new Error(`Exchange ${exchangeName} not supported by ccxt.`) - } - - switch (exchangeName) { - case 'itbit': - if (!account.clientKey || !account.clientSecret || !account.userId || !account.walletId) { - throw new Error('Must provide user ID, wallet ID, client key, and client secret') - } - return new ccxt[exchangeName](_.mapKeys((key) => { - if (key === 'clientKey') return 'apiKey' - if (key === 'clientSecret') return 'secret' - if (key === 'userId') return 'uid' - return key - }, _.omit(['walletId'], account))) - case 'kraken': - if (!account.apiKey || !account.privateKey) { - throw new Error('Must provide key and private key') - } - return new ccxt[exchangeName](_.mapKeys((key) => { - if (key === 'privateKey') return 'secret' - return key - }, account)) - case 'bitstamp': - if (!account.key || !account.secret || !account.clientId) { - throw new Error('Must provide key, secret and client ID') - } - return new ccxt[exchangeName](_.mapKeys((key) => { - if (key === 'key') return 'apiKey' - if (key === 'clientId') return 'uid' - return key - }, account)) - default: - throw new Error(`Exchange ${exchangeName} not supported.`) - } -} - -function calculatePrice (side, amount, orderBook) { - const book = side === 'buy' ? 'asks' : 'bids' - let collected = 0.0 - for (const entry of orderBook[book]) { - collected += parseFloat(entry[1]) - if (collected >= amount) return parseFloat(entry[0]) - } - throw new Error('Insufficient market depth') -} - -module.exports = { trade } diff --git a/lib/plugins/exchange/consts.js b/lib/plugins/exchange/consts.js new file mode 100644 index 00000000..31ffea71 --- /dev/null +++ b/lib/plugins/exchange/consts.js @@ -0,0 +1,11 @@ + +const ORDER_TYPES = { + MARKET: 'market', + LIMIT: 'limit' +} +const DECIMAL_PRECISION = { + PRICE: 2, + DEFAULT_AMOUNT: 8 +} + +module.exports = { ORDER_TYPES, DECIMAL_PRECISION } diff --git a/lib/plugins/exchange/itbit.js b/lib/plugins/exchange/itbit.js new file mode 100644 index 00000000..61b80d90 --- /dev/null +++ b/lib/plugins/exchange/itbit.js @@ -0,0 +1,22 @@ +const common = require('../common/ccxt') +const _ = require('lodash/fp') +const consts = require('./consts') +const ORDER_TYPE = consts.ORDER_TYPES.LIMIT +const FIAT = common.FIAT['kraken'] +const CRYPTO = common.CRYPTO['kraken'] + +const loadConfig = (account) => { + const mapper = { + 'clientKey': 'apiKey', + 'clientSecret': 'secret', + 'userId': 'uid' + } + const mapped = _.mapKeys(key => mapper[key] ? mapper[key] : key)(_.omit(['walletId'], account)) + return { ...mapped, timeout: 3000 } +} +const loadOptions = ({ walletId }) => ({ walletId }) +const isConfigValid = ({ clientKey, clientSecret, userId, walletId }) => clientKey && clientSecret && userId && walletId + +const amountPrecision = () => 4 + +module.exports = { amountPrecision, loadOptions, loadConfig, isConfigValid, CRYPTO, FIAT, ORDER_TYPE } diff --git a/lib/plugins/exchange/itbit/itbit.js b/lib/plugins/exchange/itbit/itbit.js deleted file mode 100644 index 05d6d7fa..00000000 --- a/lib/plugins/exchange/itbit/itbit.js +++ /dev/null @@ -1,45 +0,0 @@ -const common = require('../../common/itbit') -const coinUtils = require('../../../coin-utils') - -exports.buy = function (account, cryptoAtoms, fiatCode, cryptoCode) { - return trade('buy', account, cryptoAtoms, fiatCode, cryptoCode) -} - -exports.sell = function (account, cryptoAtoms, fiatCode, cryptoCode) { - return trade('sell', account, cryptoAtoms, fiatCode, cryptoCode) -} - -function trade (type, account, cryptoAtoms, fiatCode, cryptoCode) { - try { - const instrument = common.buildMarket(fiatCode, cryptoCode) - const cryptoAmount = coinUtils.toUnit(cryptoAtoms, cryptoCode) - - return calculatePrice(type, instrument, cryptoAmount) - .then(price => { - const args = { - side: type, - type: 'limit', - currency: cryptoCode, - amount: cryptoAmount.toFixed(4), - price: price.toFixed(2), - instrument: instrument - } - return common.authRequest(account, 'POST', '/wallets/' + account.walletId + '/orders', args) - }) - } catch (e) { - return Promise.reject(e) - } -} - -function calculatePrice (type, tickerSymbol, amount) { - return common.request('GET', '/markets/' + tickerSymbol + '/order_book') - .then(orderBook => { - const book = type == 'buy' ? 'asks' : 'bids' - let collected = 0.0 - for (const entry of orderBook[book]) { - collected += parseFloat(entry[1]) - if (collected >= amount) return parseFloat(entry[0]) - } - throw new Error('Insufficient market depth') - }) -} diff --git a/lib/plugins/exchange/kraken.js b/lib/plugins/exchange/kraken.js new file mode 100644 index 00000000..3491820d --- /dev/null +++ b/lib/plugins/exchange/kraken.js @@ -0,0 +1,21 @@ +const common = require('../common/ccxt') +const _ = require('lodash/fp') +const consts = require('./consts') +const ORDER_TYPE = consts.ORDER_TYPES.MARKET +const FIAT = common.FIAT['kraken'] +const CRYPTO = common.CRYPTO['kraken'] + +const loadConfig = (account) => { + const mapper = { + 'privateKey': 'secret' + } + const mapped = _.mapKeys(key => mapper[key] ? mapper[key] : key)(account) + return { ...mapped, timeout: 3000 } +} + +const loadOptions = () => ({ expiretm: '+60' }) +const isConfigValid = ({ apiKey, privateKey }) => apiKey && privateKey + +const amountPrecision = () => 6 + +module.exports = { amountPrecision, loadOptions, loadConfig, isConfigValid, CRYPTO, FIAT, ORDER_TYPE } diff --git a/lib/plugins/exchange/kraken/kraken.js b/lib/plugins/exchange/kraken/kraken.js deleted file mode 100644 index 35db1738..00000000 --- a/lib/plugins/exchange/kraken/kraken.js +++ /dev/null @@ -1,44 +0,0 @@ -// Note: Using DeX3/npm-kraken-api to adjust timeout time -const Kraken = require('kraken-api') -const _ = require('lodash/fp') - -const common = require('../../common/kraken') -const coinUtils = require('../../../coin-utils') - -var PAIRS = common.PAIRS - -module.exports = {buy, sell} - -function buy (account, cryptoAtoms, fiatCode, cryptoCode) { - return trade(account, 'buy', cryptoAtoms, fiatCode, cryptoCode) -} - -function sell (account, cryptoAtoms, fiatCode, cryptoCode) { - return trade(account, 'sell', cryptoAtoms, fiatCode, cryptoCode) -} - -function trade (account, type, cryptoAtoms, fiatCode, cryptoCode) { - const kraken = new Kraken(account.apiKey, account.privateKey, {timeout: 30000}) - const amount = coinUtils.toUnit(cryptoAtoms, cryptoCode) - const amountStr = amount.toFixed(6) - - const pair = _.includes(fiatCode, ['USD', 'EUR']) - ? PAIRS[cryptoCode][fiatCode] - : PAIRS[cryptoCode]['EUR'] - - var orderInfo = { - pair, - type, - ordertype: 'market', - volume: amountStr, - expiretm: '+60' - } - - return new Promise((resolve, reject) => { - kraken.api('AddOrder', orderInfo, (error, response) => { - if (error) return reject(error) - - return resolve() - }) - }) -} diff --git a/lib/plugins/ticker/bitstamp/bitstamp.js b/lib/plugins/ticker/bitstamp/bitstamp.js deleted file mode 100644 index 1d5ac416..00000000 --- a/lib/plugins/ticker/bitstamp/bitstamp.js +++ /dev/null @@ -1,49 +0,0 @@ -const axios = require('axios') -const _ = require('lodash/fp') - -const BN = require('../../../bn') -const common = require('../../common/bitstamp') - -exports.NAME = 'Bitstamp' -exports.SUPPORTED_MODULES = ['ticker'] - -function findCurrency (fxRates, fiatCode) { - const rates = _.find(_.matchesProperty('code', fiatCode), fxRates) - if (!rates || !rates.rate) throw new Error(`Unsupported currency: ${fiatCode}`) - return BN(rates.rate.toString()) -} - -exports.ticker = function ticker (account, fiatCode, cryptoCode) { - if (fiatCode === 'USD' || fiatCode === 'EUR') { - return getCurrencyRates(fiatCode, cryptoCode) - } - - return axios.get('https://bitpay.com/rates') - .then(response => { - const fxRates = response.data.data - const usdRate = findCurrency(fxRates, 'USD') - const fxRate = findCurrency(fxRates, fiatCode).div(usdRate) - - return getCurrencyRates('USD', cryptoCode) - .then(res => ({ - rates: { - ask: res.rates.ask.times(fxRate), - bid: res.rates.bid.times(fxRate) - } - })) - }) -} - -function getCurrencyRates (fiatCode, cryptoCode) { - return Promise.resolve() - .then(() => { - const market = common.buildMarket(fiatCode, cryptoCode) - return common.request('/ticker/' + market, 'GET') - }) - .then(r => ({ - rates: { - ask: BN(r.ask), - bid: BN(r.bid) - } - })) -} diff --git a/lib/plugins/ticker/ccxt/ccxt.js b/lib/plugins/ticker/ccxt.js similarity index 92% rename from lib/plugins/ticker/ccxt/ccxt.js rename to lib/plugins/ticker/ccxt.js index efcc49bb..2d3f498a 100644 --- a/lib/plugins/ticker/ccxt/ccxt.js +++ b/lib/plugins/ticker/ccxt.js @@ -1,8 +1,8 @@ const ccxt = require('ccxt') -const BN = require('../../../bn') +const BN = require('../../bn') const axios = require('axios') const _ = require('lodash/fp') -const common = require('../../common/ccxt') +const common = require('../common/ccxt') function ticker (exchangeName, fiatCode, cryptoCode) { const exchange = new ccxt[exchangeName]() diff --git a/lib/plugins/ticker/coinbase/coinbase.js b/lib/plugins/ticker/coinbase/coinbase.js deleted file mode 100644 index 772dfcf9..00000000 --- a/lib/plugins/ticker/coinbase/coinbase.js +++ /dev/null @@ -1,54 +0,0 @@ -const _ = require('lodash/fp') -const axios = require('axios') - -const BN = require('../../../bn') - -function getBuyPrice (obj) { - const currencyPair = obj.currencyPair - - return axios({ - method: 'get', - url: `https://api.coinbase.com/v2/prices/${currencyPair}/buy`, - headers: {'CB-Version': '2017-07-10'} - }) - .then(r => r.data) -} - -function getSellPrice (obj) { - const currencyPair = obj.currencyPair - - return axios({ - method: 'get', - url: `https://api.coinbase.com/v2/prices/${currencyPair}/sell`, - headers: {'CB-Version': '2017-07-10'} - }) - .then(r => r.data) -} - -function ticker (account, fiatCode, cryptoCode) { - return Promise.resolve() - .then(() => { - if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH', 'ZEC', 'DASH'])) { - throw new Error('Unsupported crypto: ' + cryptoCode) - } - }) - .then(() => { - const currencyPair = `${cryptoCode}-${fiatCode}` - const promises = [ - getBuyPrice({currencyPair}), - getSellPrice({currencyPair}) - ] - - return Promise.all(promises) - }) - .then(([buyPrice, sellPrice]) => ({ - rates: { - ask: BN(buyPrice.data.amount), - bid: BN(sellPrice.data.amount) - } - })) -} - -module.exports = { - ticker -} diff --git a/lib/plugins/ticker/itbit/itbit.js b/lib/plugins/ticker/itbit/itbit.js deleted file mode 100644 index fcebbf59..00000000 --- a/lib/plugins/ticker/itbit/itbit.js +++ /dev/null @@ -1,54 +0,0 @@ -const axios = require('axios') -const _ = require('lodash/fp') - -const BN = require('../../../bn') -const common = require('../../common/itbit') - -exports.NAME = 'itBit' -exports.SUPPORTED_MODULES = ['ticker'] - -function findCurrency (fxRates, fiatCode) { - const rates = _.find(_.matchesProperty('code', fiatCode), fxRates) - if (!rates || !rates.rate) throw new Error(`Unsupported currency: ${fiatCode}`) - return BN(rates.rate.toString()) -} - -exports.ticker = function ticker (account, fiatCode, cryptoCode) { - if (_.includes(fiatCode, ['USD', 'EUR', 'SGD'])) { - return getCurrencyRates(fiatCode, cryptoCode) - } - - return axios.get('https://bitpay.com/api/rates') - .then(response => { - const fxRates = response.data - try { - const usdRate = findCurrency(fxRates, 'USD') - const fxRate = findCurrency(fxRates, fiatCode).div(usdRate) - - return getCurrencyRates('USD', cryptoCode) - .then(res => ({ - rates: { - ask: res.rates.ask.times(fxRate), - bid: res.rates.bid.times(fxRate) - } - })) - } catch (e) { - return Promise.reject(e) - } - }) -} - -function getCurrencyRates (fiatCode, cryptoCode) { - try { - const market = common.buildMarket(fiatCode, cryptoCode) - return common.request('GET', '/markets/' + market + '/ticker') - .then(r => ({ - rates: { - ask: BN(r.ask), - bid: BN(r.bid) - } - })) - } catch (e) { - return Promise.reject(e) - } -} diff --git a/lib/plugins/ticker/kraken/kraken.js b/lib/plugins/ticker/kraken/kraken.js deleted file mode 100644 index bfa67886..00000000 --- a/lib/plugins/ticker/kraken/kraken.js +++ /dev/null @@ -1,52 +0,0 @@ -const axios = require('axios') -const _ = require('lodash/fp') - -const BN = require('../../../bn') -const common = require('../../common/kraken') - -exports.NAME = 'Kraken' -exports.SUPPORTED_MODULES = ['ticker'] - -const PAIRS = common.PAIRS - -function findCurrency (fxRates, fiatCode) { - const rates = _.find(_.matchesProperty('code', fiatCode), fxRates) - if (!rates || !rates.rate) throw new Error(`Unsupported currency: ${fiatCode}`) - return BN(rates.rate.toString()) -} - -exports.ticker = function ticker (account, fiatCode, cryptoCode) { - if (fiatCode === 'USD' || fiatCode === 'EUR') { - return getCurrencyRates(fiatCode, cryptoCode) - } - - return axios.get('https://bitpay.com/api/rates') - .then(response => { - const fxRates = response.data - const usdRate = findCurrency(fxRates, 'USD') - const fxRate = findCurrency(fxRates, fiatCode).div(usdRate) - - return getCurrencyRates('USD', cryptoCode) - .then(res => ({ - rates: { - ask: res.rates.ask.times(fxRate), - bid: res.rates.bid.times(fxRate) - } - })) - }) -} - -function getCurrencyRates (fiatCode, cryptoCode) { - const pair = PAIRS[cryptoCode][fiatCode] - - return axios.get('https://api.kraken.com/0/public/Ticker?pair=' + pair) - .then(function (response) { - const rates = response.data.result[pair] - return { - rates: { - ask: BN(rates.a[0]), - bid: BN(rates.b[0]) - } - } - }) -} diff --git a/lib/ticker.js b/lib/ticker.js index 85fc3266..7c4ccb72 100644 --- a/lib/ticker.js +++ b/lib/ticker.js @@ -1,8 +1,7 @@ const mem = require('mem') const configManager = require('./new-config-manager') -const ph = require('./plugin-helper') const logger = require('./logger') - +const ccxt = require('./plugins/ticker/ccxt') const lastRate = {} const FETCH_INTERVAL = 60000 @@ -11,14 +10,10 @@ function _getRates (settings, fiatCode, cryptoCode) { return Promise.resolve() .then(() => { const config = settings.config - const plugin = configManager.getWalletSettings(cryptoCode, config).ticker - - const account = settings.accounts[plugin] - const ticker = ph.load(ph.TICKER, plugin) - + const exchangeName = configManager.getWalletSettings(cryptoCode, config).ticker const market = [cryptoCode, fiatCode].join('-') - return ticker.ticker(account, fiatCode, cryptoCode) + return ccxt.ticker(exchangeName, fiatCode, cryptoCode) .then(r => ({ rates: r.rates, timestamp: Date.now()