Refactored some exchange code
This commit is contained in:
parent
54a231ab60
commit
134eaaa518
20 changed files with 141 additions and 643 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
@ -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}
|
||||
21
lib/plugins/exchange/bitstamp.js
Normal file
21
lib/plugins/exchange/bitstamp.js
Normal file
|
|
@ -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 }
|
||||
|
|
@ -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
|
||||
}
|
||||
51
lib/plugins/exchange/ccxt.js
Normal file
51
lib/plugins/exchange/ccxt.js
Normal file
|
|
@ -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 }
|
||||
|
|
@ -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 }
|
||||
11
lib/plugins/exchange/consts.js
Normal file
11
lib/plugins/exchange/consts.js
Normal file
|
|
@ -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 }
|
||||
22
lib/plugins/exchange/itbit.js
Normal file
22
lib/plugins/exchange/itbit.js
Normal file
|
|
@ -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 }
|
||||
|
|
@ -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')
|
||||
})
|
||||
}
|
||||
21
lib/plugins/exchange/kraken.js
Normal file
21
lib/plugins/exchange/kraken.js
Normal file
|
|
@ -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 }
|
||||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
@ -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]()
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue