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
|
|
@ -258,6 +258,7 @@ view route invalidGroups =
|
|||
, ( "Bitstamp", AccountRoute "bitstamp", True )
|
||||
, ( "Blockcypher", AccountRoute "blockcypher", True )
|
||||
, ( "Infura", AccountRoute "infura", True )
|
||||
, ( "itBit", AccountRoute "itbit", True )
|
||||
, ( "Kraken", AccountRoute "kraken", True )
|
||||
, ( "Mailgun", AccountRoute "mailgun", True )
|
||||
, ( "QuadrigaCX", AccountRoute "quadrigacx", True )
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ function fetchData () {
|
|||
{code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
||||
{code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'itbit', display: 'itBit', class: 'ticker', cryptos: ['BTC']},
|
||||
{code: 'quadrigacx', display: 'QuadrigaCX', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS},
|
||||
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
|
||||
|
|
@ -191,6 +192,7 @@ function fetchData () {
|
|||
{code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']},
|
||||
{code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']},
|
||||
{code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'itbit', display: 'itBit', class: 'exchange', cryptos: ['BTC']},
|
||||
{code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
||||
{code: 'quadrigacx', display: 'QuadrigaCX', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||
{code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS},
|
||||
|
|
|
|||
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)
|
||||
}
|
||||
}
|
||||
365
package-lock.json
generated
365
package-lock.json
generated
|
|
@ -9017,6 +9017,356 @@
|
|||
"signal-exit": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"rewire": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rewire/-/rewire-4.0.1.tgz",
|
||||
"integrity": "sha512-+7RQ/BYwTieHVXetpKhT11UbfF6v1kGhKFrtZN7UDL2PybMsSt/rpLWeEUGF5Ndsl1D5BxiCB14VDJyoX+noYw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint": "^4.19.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
|
||||
"integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
|
||||
"integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^3.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
|
||||
"integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
||||
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"co": "^4.6.0",
|
||||
"fast-deep-equal": "^1.0.0",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"ajv-keywords": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz",
|
||||
"integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
|
||||
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"co": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
|
||||
"dev": true
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^4.0.1",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"eslint": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "http://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz",
|
||||
"integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^5.3.0",
|
||||
"babel-code-frame": "^6.22.0",
|
||||
"chalk": "^2.1.0",
|
||||
"concat-stream": "^1.6.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"debug": "^3.1.0",
|
||||
"doctrine": "^2.1.0",
|
||||
"eslint-scope": "^3.7.1",
|
||||
"eslint-visitor-keys": "^1.0.0",
|
||||
"espree": "^3.5.4",
|
||||
"esquery": "^1.0.0",
|
||||
"esutils": "^2.0.2",
|
||||
"file-entry-cache": "^2.0.0",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"glob": "^7.1.2",
|
||||
"globals": "^11.0.1",
|
||||
"ignore": "^3.3.3",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"inquirer": "^3.0.6",
|
||||
"is-resolvable": "^1.0.0",
|
||||
"js-yaml": "^3.9.1",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"levn": "^0.3.0",
|
||||
"lodash": "^4.17.4",
|
||||
"minimatch": "^3.0.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.8.2",
|
||||
"path-is-inside": "^1.0.2",
|
||||
"pluralize": "^7.0.0",
|
||||
"progress": "^2.0.0",
|
||||
"regexpp": "^1.0.1",
|
||||
"require-uncached": "^1.0.3",
|
||||
"semver": "^5.3.0",
|
||||
"strip-ansi": "^4.0.0",
|
||||
"strip-json-comments": "~2.0.1",
|
||||
"table": "4.0.2",
|
||||
"text-table": "~0.2.0"
|
||||
}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "3.7.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz",
|
||||
"integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esrecurse": "^4.1.0",
|
||||
"estraverse": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"espree": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz",
|
||||
"integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^5.5.0",
|
||||
"acorn-jsx": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
|
||||
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
|
||||
"dev": true
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
|
||||
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=",
|
||||
"dev": true
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.9.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz",
|
||||
"integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
},
|
||||
"ignore": {
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
|
||||
"integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
|
||||
"dev": true
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
|
||||
"integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-escapes": "^3.0.0",
|
||||
"chalk": "^2.0.0",
|
||||
"cli-cursor": "^2.1.0",
|
||||
"cli-width": "^2.0.0",
|
||||
"external-editor": "^2.0.4",
|
||||
"figures": "^2.0.0",
|
||||
"lodash": "^4.3.0",
|
||||
"mute-stream": "0.0.7",
|
||||
"run-async": "^2.2.0",
|
||||
"rx-lite": "^4.0.8",
|
||||
"rx-lite-aggregates": "^4.0.8",
|
||||
"string-width": "^2.1.0",
|
||||
"strip-ansi": "^4.0.0",
|
||||
"through": "^2.3.6"
|
||||
}
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
|
||||
"integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
|
||||
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
|
||||
"dev": true
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.4.tgz",
|
||||
"integrity": "sha512-EPstzZ23znHUVLKj+lcXO1KvZkrlw+ZirdwvOmnAnA/1PB4ggyXJ77LRkCqkff+ShQ+cqoxCxLQOh4cKITO5iA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pseudomap": "^1.0.2",
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"dev": true
|
||||
},
|
||||
"progress": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz",
|
||||
"integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==",
|
||||
"dev": true
|
||||
},
|
||||
"regexpp": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "http://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz",
|
||||
"integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
|
||||
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
|
||||
"dev": true
|
||||
},
|
||||
"slice-ansi": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
|
||||
"integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
|
||||
"integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^5.2.3",
|
||||
"ajv-keywords": "^2.1.0",
|
||||
"chalk": "^2.1.0",
|
||||
"lodash": "^4.17.4",
|
||||
"slice-ansi": "1.0.0",
|
||||
"string-width": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
|
||||
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
||||
|
|
@ -9176,6 +9526,21 @@
|
|||
"integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"rx-lite": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
|
||||
"integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
|
||||
"dev": true
|
||||
},
|
||||
"rx-lite-aggregates": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz",
|
||||
"integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"rx-lite": "*"
|
||||
}
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "5.5.10",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz",
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@
|
|||
"devDependencies": {
|
||||
"ava": "^0.19.1",
|
||||
"mocha": "^5.0.1",
|
||||
"rewire": "^4.0.1",
|
||||
"standard": "^12.0.1"
|
||||
},
|
||||
"standard": {
|
||||
|
|
|
|||
|
|
@ -37728,6 +37728,14 @@ var _user$project$NavBar$view = F2(
|
|||
_1: _user$project$CoreTypes$AccountRoute('infura'),
|
||||
_2: true
|
||||
},
|
||||
_1: {
|
||||
ctor: '::',
|
||||
_0: {
|
||||
ctor: '_Tuple3',
|
||||
_0: 'itBit',
|
||||
_1: _user$project$CoreTypes$AccountRoute('itbit'),
|
||||
_2: true
|
||||
},
|
||||
_1: {
|
||||
ctor: '::',
|
||||
_0: {
|
||||
|
|
@ -37777,6 +37785,7 @@ var _user$project$NavBar$view = F2(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
_1: {
|
||||
ctor: '::',
|
||||
|
|
|
|||
34
schemas/itbit.json
Normal file
34
schemas/itbit.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"code": "itbit",
|
||||
"display": "itBit",
|
||||
"fields": [
|
||||
{
|
||||
"code": "userId",
|
||||
"display": "User ID",
|
||||
"fieldType": "string",
|
||||
"required": true,
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"code": "walletId",
|
||||
"display": "Wallet ID",
|
||||
"fieldType": "string",
|
||||
"required": true,
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"code": "clientKey",
|
||||
"display": "Client key",
|
||||
"fieldType": "string",
|
||||
"required": true,
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"code": "clientSecret",
|
||||
"display": "Client secret",
|
||||
"fieldType": "password",
|
||||
"required": true,
|
||||
"value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
78
test/unit/itbit-calculate-price.js
Normal file
78
test/unit/itbit-calculate-price.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import test from 'ava'
|
||||
import rewire from 'rewire'
|
||||
|
||||
function rewireCalculatePrice (commonMock) {
|
||||
const itbit = rewire('../../lib/plugins/exchange/itbit/itbit')
|
||||
|
||||
itbit.__set__('common', commonMock)
|
||||
|
||||
const calculatePrice = itbit.__get__('calculatePrice')
|
||||
|
||||
return calculatePrice
|
||||
}
|
||||
|
||||
test('calculate minimum available price for buy', async t => {
|
||||
const commonMock = {
|
||||
request () {
|
||||
return Promise.resolve({
|
||||
asks: [
|
||||
[2, 10],
|
||||
[4, 15],
|
||||
[4.5, 17]
|
||||
],
|
||||
bids: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const calculatePrice = rewireCalculatePrice(commonMock)
|
||||
|
||||
let price = await calculatePrice('buy', 'XBTUSD', 20)
|
||||
|
||||
t.is(price, 4)
|
||||
})
|
||||
|
||||
test('calculate minimum available price for sell', async t => {
|
||||
const commonMock = {
|
||||
request () {
|
||||
return Promise.resolve({
|
||||
bids: [
|
||||
[2, 10],
|
||||
[3, 15],
|
||||
[4.5, 17]
|
||||
],
|
||||
asks: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const calculatePrice = rewireCalculatePrice(commonMock)
|
||||
|
||||
let price = await calculatePrice('sell', 'XBTUSD', 20)
|
||||
|
||||
t.is(price, 3)
|
||||
})
|
||||
|
||||
test('throw error on insufficient trade depth', async t => {
|
||||
t.plan(1)
|
||||
|
||||
const commonMock = {
|
||||
request () {
|
||||
return Promise.resolve({
|
||||
asks: [
|
||||
[2, 10],
|
||||
[4, 15],
|
||||
[4.5, 17]
|
||||
],
|
||||
bids: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const calculatePrice = rewireCalculatePrice(commonMock)
|
||||
|
||||
calculatePrice('buy', 'XBTUSD', 100)
|
||||
.catch(err => {
|
||||
t.is(err.message, 'Insufficient market depth')
|
||||
})
|
||||
})
|
||||
48
test/unit/itbit-get-currency-rates.js
Normal file
48
test/unit/itbit-get-currency-rates.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import test from 'ava'
|
||||
import rewire from 'rewire'
|
||||
import BN from '../../lib/bn'
|
||||
|
||||
function rewireGetCurrencyRates (commonMock) {
|
||||
const itbit = rewire('../../lib/plugins/ticker/itbit/itbit')
|
||||
|
||||
itbit.__set__('common', commonMock)
|
||||
|
||||
const getCurrencyRates = itbit.__get__('getCurrencyRates')
|
||||
|
||||
return getCurrencyRates
|
||||
}
|
||||
|
||||
test('get currency rates of BTC USD', async t => {
|
||||
function mockRequest() {
|
||||
return Promise.resolve({
|
||||
pair: 'XBTUSD',
|
||||
bid: '622',
|
||||
bidAmt: '0.0006',
|
||||
ask: '641.29',
|
||||
askAmt: '0.5',
|
||||
lastPrice: '618.00000000',
|
||||
lastAmt: '0.00040000',
|
||||
volume24h: '0.00040000',
|
||||
volumeToday: '0.00040000',
|
||||
high24h: '618.00000000',
|
||||
low24h: '618.00000000',
|
||||
highToday: '618.00000000',
|
||||
lowToday: '618.00000000',
|
||||
openToday: '618.00000000',
|
||||
vwapToday: '618.00000000',
|
||||
vwap24h: '618.00000000',
|
||||
serverTimeUTC: '2014-06-24T20:42:35.6160000Z'
|
||||
})
|
||||
}
|
||||
|
||||
const common = rewire('../../lib/plugins/common/itbit')
|
||||
|
||||
common.request = mockRequest
|
||||
|
||||
const getCurrencyRates = rewireGetCurrencyRates(common)
|
||||
|
||||
let result = await getCurrencyRates('USD', 'BTC')
|
||||
|
||||
t.true(result.rates.bid.eq('622'))
|
||||
t.true(result.rates.ask.eq('641.29'))
|
||||
})
|
||||
35
test/unit/itbit-trade.js
Normal file
35
test/unit/itbit-trade.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import test from 'ava'
|
||||
import rewire from 'rewire'
|
||||
import BN from '../../lib/bn'
|
||||
|
||||
function rewireTrade (commonMock, calculatePrice = () => Promise.resolve(15)) {
|
||||
const itbit = rewire('../../lib/plugins/exchange/itbit/itbit')
|
||||
|
||||
itbit.__set__('common', commonMock)
|
||||
itbit.__set__('calculatePrice', calculatePrice)
|
||||
|
||||
const trade = itbit.__get__('trade')
|
||||
|
||||
return trade
|
||||
}
|
||||
|
||||
test('should handle itbit error response', async t => {
|
||||
t.plan(1)
|
||||
|
||||
const commonMock = {
|
||||
mock: true,
|
||||
authRequest () {
|
||||
return Promise.reject(new Error('The wallet provided does not have the funds required to place the order.'))
|
||||
},
|
||||
buildMarket () {
|
||||
return 'XBTUSD'
|
||||
}
|
||||
}
|
||||
|
||||
const trade = rewireTrade(commonMock)
|
||||
|
||||
trade('buy', { walletId: 'id' }, BN('93410'), 'USD', 'BTC')
|
||||
.catch(err => {
|
||||
t.regex(err.message, /wallet provided/g)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue