Support itbit exchange/ticker. Additions (#232)
* Initial itBit implementation * Add tests for calculatePrice in exchange/itbit.js * Add etherium to supported crypto codes in common/itbit.js * Apply standardjs to itbit files * itbit trade test. not important * test getCurrencyRates in ticker/itbit.js
This commit is contained in:
parent
8e1f9e0b83
commit
b254f7f2be
12 changed files with 775 additions and 11 deletions
92
lib/plugins/common/itbit.js
Normal file
92
lib/plugins/common/itbit.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
'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 }
|
||||
45
lib/plugins/exchange/itbit/itbit.js
Normal file
45
lib/plugins/exchange/itbit/itbit.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
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')
|
||||
})
|
||||
}
|
||||
54
lib/plugins/ticker/itbit/itbit.js
Normal file
54
lib/plugins/ticker/itbit/itbit.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue