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:
Davit Abulashvili 2018-12-11 13:47:34 +04:00 committed by Josh Harvey
parent 8e1f9e0b83
commit b254f7f2be
12 changed files with 775 additions and 11 deletions

View file

@ -258,6 +258,7 @@ view route invalidGroups =
, ( "Bitstamp", AccountRoute "bitstamp", True ) , ( "Bitstamp", AccountRoute "bitstamp", True )
, ( "Blockcypher", AccountRoute "blockcypher", True ) , ( "Blockcypher", AccountRoute "blockcypher", True )
, ( "Infura", AccountRoute "infura", True ) , ( "Infura", AccountRoute "infura", True )
, ( "itBit", AccountRoute "itbit", True )
, ( "Kraken", AccountRoute "kraken", True ) , ( "Kraken", AccountRoute "kraken", True )
, ( "Mailgun", AccountRoute "mailgun", True ) , ( "Mailgun", AccountRoute "mailgun", True )
, ( "QuadrigaCX", AccountRoute "quadrigacx", True ) , ( "QuadrigaCX", AccountRoute "quadrigacx", True )

View file

@ -179,6 +179,7 @@ function fetchData () {
{code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']}, {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: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
{code: 'coinbase', display: 'Coinbase', 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: 'quadrigacx', display: 'QuadrigaCX', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
{code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS}, {code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS},
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']}, {code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
@ -191,6 +192,7 @@ function fetchData () {
{code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']}, {code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']},
{code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']}, {code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']},
{code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, {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: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
{code: 'quadrigacx', display: 'QuadrigaCX', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, {code: 'quadrigacx', display: 'QuadrigaCX', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
{code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS}, {code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS},

View 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 }

View 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')
})
}

View 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
View file

@ -9017,6 +9017,356 @@
"signal-exit": "^3.0.2" "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": { "rimraf": {
"version": "2.6.2", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
@ -9176,6 +9526,21 @@
"integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
"dev": true "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": { "rxjs": {
"version": "5.5.10", "version": "5.5.10",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz",

View file

@ -94,6 +94,7 @@
"devDependencies": { "devDependencies": {
"ava": "^0.19.1", "ava": "^0.19.1",
"mocha": "^5.0.1", "mocha": "^5.0.1",
"rewire": "^4.0.1",
"standard": "^12.0.1" "standard": "^12.0.1"
}, },
"standard": { "standard": {

View file

@ -37732,43 +37732,52 @@ var _user$project$NavBar$view = F2(
ctor: '::', ctor: '::',
_0: { _0: {
ctor: '_Tuple3', ctor: '_Tuple3',
_0: 'Kraken', _0: 'itBit',
_1: _user$project$CoreTypes$AccountRoute('kraken'), _1: _user$project$CoreTypes$AccountRoute('itbit'),
_2: true _2: true
}, },
_1: { _1: {
ctor: '::', ctor: '::',
_0: { _0: {
ctor: '_Tuple3', ctor: '_Tuple3',
_0: 'Mailgun', _0: 'Kraken',
_1: _user$project$CoreTypes$AccountRoute('mailgun'), _1: _user$project$CoreTypes$AccountRoute('kraken'),
_2: true _2: true
}, },
_1: { _1: {
ctor: '::', ctor: '::',
_0: { _0: {
ctor: '_Tuple3', ctor: '_Tuple3',
_0: 'QuadrigaCX', _0: 'Mailgun',
_1: _user$project$CoreTypes$AccountRoute('quadrigacx'), _1: _user$project$CoreTypes$AccountRoute('mailgun'),
_2: true _2: true
}, },
_1: { _1: {
ctor: '::', ctor: '::',
_0: { _0: {
ctor: '_Tuple3', ctor: '_Tuple3',
_0: 'Strike', _0: 'QuadrigaCX',
_1: _user$project$CoreTypes$AccountRoute('strike'), _1: _user$project$CoreTypes$AccountRoute('quadrigacx'),
_2: true _2: true
}, },
_1: { _1: {
ctor: '::', ctor: '::',
_0: { _0: {
ctor: '_Tuple3', ctor: '_Tuple3',
_0: 'Twilio', _0: 'Strike',
_1: _user$project$CoreTypes$AccountRoute('twilio'), _1: _user$project$CoreTypes$AccountRoute('strike'),
_2: true _2: true
}, },
_1: {ctor: '[]'} _1: {
ctor: '::',
_0: {
ctor: '_Tuple3',
_0: 'Twilio',
_1: _user$project$CoreTypes$AccountRoute('twilio'),
_2: true
},
_1: {ctor: '[]'}
}
} }
} }
} }

34
schemas/itbit.json Normal file
View 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": ""
}
]
}

View 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')
})
})

View 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
View 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)
})
})