diff --git a/README.md b/README.md index 6a2a1152..877e0c16 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ npm install coinbase/gdax-node ``` You can learn about the API responses of each endpoint [by reading our -documentation](https://docs.gdax.com/#market-data). +documentation](https://docs.gdax.com). ## Quick Start @@ -49,6 +49,24 @@ publicClient }); ``` +Promises will be resolved with an object `data` whose structure varies depending +on the GDAX endpoint called (see the [GDAX API docs](https://docs.gdax.com)). + +Upon encountering an error, the Promise will be rejected with an `error` object. +If the error is related to the network or a response recieved from the GDAX API, +`error` may contain additional properties as described in `superagent`'s [error +handling docs](https://visionmedia.github.io/superagent/#error-handling). + +```js +publicClient + .getProducts() // assume this produces some error + .then(data => { /* ... */ }) + .catch(error => { + error typeof Error; // true + typeof error.message; // string + }); +``` + The promise API can be used as expected in `async` functions in ES2017+ environments: @@ -68,17 +86,14 @@ async function yourFunction() { ### Using Callbacks -Your callback should accept two arguments: +Your callback should accept two arguments, `error` and `data`, as described +above. In callbacks, -- `error`: contains an error message (`string`), or `null` if no was error - encountered -- `response`: a generic HTTP response abstraction created by the [`request` - library](https://github.com/request/request) -- `data`: contains data returned by the GDAX API, or `undefined` if an error was - encountered +- `error` will be `null` if no error is encountered +- `data` will be `undefined` if an error is encountered ```js -publicClient.getProducts((error, response, data) => { +publicClient.getProducts((error, data) => { if (error) { // handle the error } else { @@ -92,7 +107,7 @@ prevent potential `UnhandledPromiseRejectionWarning`s, which will cause future versions of Node to terminate. ```js -const myCallback = (err, response, data) => { /* ... */ }; +const myCallback = (err, data) => { /* ... */ }; const result = publicClient.getProducts(myCallback); @@ -106,7 +121,8 @@ Some methods accept optional parameters, e.g. ```js publicClient .getProductOrderBook('BTC-USD', { level: 3 }) - .then(book => { /* ... */ }); + .then(book => { /* ... */ }) + .catch(error => { /* ... */ }; ``` To use optional parameters with callbacks, supply the options as the first @@ -120,11 +136,11 @@ publicClient ### The Public API Client ```js -const publicClient = new Gdax.PublicClient(endpoint); +const publicClient = new Gdax.PublicClient(apiURI); ``` -- `productID` *optional* - defaults to 'BTC-USD' if not specified. -- `endpoint` *optional* - defaults to 'https://api.gdax.com' if not specified. +- `productID` *optional* - defaults to `'BTC-USD'` +- `apiURI` *optional* - defaults to `'https://api.gdax.com'` #### Public API Methods @@ -219,9 +235,9 @@ const authedClient = new Gdax.AuthenticatedClient(key, secret, passphrase, apiUR Like `PublicClient`, all API methods can be used with either callbacks or will return promises. -`AuthenticatedClient` inherits all of the API methods from -`PublicClient`, so if you're hitting both public and private API endpoints you -only need to create a single client. +`AuthenticatedClient` inherits all of the API methods from `PublicClient`, so if +you're hitting both public and private API endpoints you only need to create a +single authenticated client. #### Private API Methods diff --git a/lib/clients/authenticated.js b/lib/clients/authenticated.js index ae01d8c6..734fe293 100644 --- a/lib/clients/authenticated.js +++ b/lib/clients/authenticated.js @@ -1,39 +1,38 @@ const { signRequest } = require('../../lib/request_signer'); +const PublicClient = require('./public'); -const PublicClient = require('./public.js'); +const { GDAX_AUTHED_RATE_LIMIT } = require('../constants'); class AuthenticatedClient extends PublicClient { constructor(key, secret, passphrase, apiURI, options = {}) { + if (!key || !secret || !passphrase) { + throw new Error( + 'AuthenticatedClient constructor requires arguments `key`, `secret`, and `passphrase`.' + ); + } + + if (!(+options.rateLimit >= 0)) { + options.rateLimit = GDAX_AUTHED_RATE_LIMIT; + } + super(apiURI, options); this.key = key; this.secret = secret; this.passphrase = passphrase; } - request(method, uriParts, opts = {}, callback) { - if (!callback && typeof opts === 'function') { - callback = opts; - opts = {}; - } - - this.addHeaders( - opts, - this._getSignature( - method.toUpperCase(), - this.makeRelativeURI(uriParts), - opts - ) - ); - - return super.request(method, uriParts, opts, callback); + _request({ method, uri, queries = {}, headers = {} }) { + const [arg] = arguments; + arg.queries = queries; + arg.headers = headers; + method = method.toUpperCase(); + Object.assign(headers, this._getSignatureHeaders(method, uri, queries)); + return super._request(arg); } - _getSignature(method, relativeURI, opts) { - const sig = signRequest(this, method, relativeURI, opts); + _getSignatureHeaders(method, relativeURI, queries, body) { + const sig = signRequest(this, method, relativeURI, queries, body); - if (opts.body) { - opts.body = JSON.stringify(opts.body); - } return { 'CB-ACCESS-KEY': sig.key, 'CB-ACCESS-SIGN': sig.signature, @@ -51,29 +50,37 @@ class AuthenticatedClient extends PublicClient { } getAccounts(callback) { - return this.get(['accounts'], callback); + return this._get({ uri: `/accounts`, callback }); } getAccount(accountID, callback) { - return this.get(['accounts', accountID], callback); + return this._get({ uri: `/accounts/${accountID}`, callback }); } - getAccountHistory(accountID, args = {}, callback) { - if (!callback && typeof args === 'function') { - callback = args; - args = {}; + getAccountHistory(accountID, opts = {}, callback) { + if (!callback && typeof opts === 'function') { + callback = opts; + opts = {}; } - return this.get(['accounts', accountID, 'ledger'], { qs: args }, callback); + return this._get({ + uri: `/accounts/${accountID}/ledger`, + queries: opts, + callback, + }); } - getAccountHolds(accountID, args = {}, callback) { - if (!callback && typeof args === 'function') { - callback = args; - args = {}; + getAccountHolds(accountID, opts = {}, callback) { + if (!callback && typeof opts === 'function') { + callback = opts; + opts = {}; } - return this.get(['accounts', accountID, 'holds'], { qs: args }, callback); + return this._get({ + uri: `/accounts/${accountID}/holds`, + queries: opts, + callback, + }); } placeOrder(params, callback) { @@ -85,7 +92,7 @@ class AuthenticatedClient extends PublicClient { } this._requireParams(params, requiredParams); - + if (!needsSize && !params.size && !params.funds) { throw new Error('`opts` must include either `size` or `funds`'); } @@ -94,7 +101,7 @@ class AuthenticatedClient extends PublicClient { throw new Error('`side` must be `buy` or `sell`'); } - return this.post(['orders'], { body: params }, callback); + return this._post({ uri: '/orders', body: params, callback }); } buy(params, callback) { @@ -108,7 +115,7 @@ class AuthenticatedClient extends PublicClient { } getTrailingVolume(callback) { - return this.get(['users', 'self', 'trailing-volume'], {}, callback); + return this._get({ uri: `/users/self/trailing-volume`, callback }); } cancelOrder(orderID, callback) { @@ -120,46 +127,35 @@ class AuthenticatedClient extends PublicClient { return Promise.reject(err); } - return this.delete(['orders', orderID], callback); + return this._delete({ uri: `/orders/${orderID}`, callback }); } cancelOrders(callback) { - return this.delete(['orders'], callback); + return this._delete({ uri: `/orders`, callback }); } - cancelAllOrders(args = {}, callback) { - if (!callback && typeof args === 'function') { - callback = args; - args = {}; + cancelAllOrders(opts = {}, callback) { + if (!callback && typeof opts === 'function') { + callback = opts; + opts = {}; } - - const opts = { qs: args }; const totalDeletedOrders = []; - const p = function deleteNext() { - return new Promise((resolve, reject) => { - this.delete(['orders'], opts, (err, response, data) => { - if (err) { - reject(err); - } else { - resolve([response, data]); - } - }); + const deleteNext = () => { + return this._delete({ + uri: `/orders`, + queries: opts, }) - .then(values => { - let [response, data] = values; + .then(data => { totalDeletedOrders.push(...data); if (data.length) { - return deleteNext.call(this); + return deleteNext(); } else { - return response; - } - }) - .then(response => { - if (callback) { - callback(undefined, response, totalDeletedOrders); + if (callback) { + callback(undefined, totalDeletedOrders); + } + return totalDeletedOrders; } - return totalDeletedOrders; }) .catch(err => { if (callback) { @@ -167,23 +163,18 @@ class AuthenticatedClient extends PublicClient { } throw err; }); - }.call(this); + }; - if (callback) { - p.catch(() => {}); - return undefined; - } else { - return p; - } + return deleteNext(); } - getOrders(args = {}, callback) { - if (!callback && typeof args === 'function') { - callback = args; - args = {}; + getOrders(opts = {}, callback) { + if (!callback && typeof opts === 'function') { + callback = opts; + opts = {}; } - return this.get(['orders'], { qs: args }, callback); + return this._get({ uri: `/orders`, queries: opts, callback }); } getOrder(orderID, callback) { @@ -195,25 +186,25 @@ class AuthenticatedClient extends PublicClient { return Promise.reject(err); } - return this.get(['orders', orderID], callback); + return this._get({ uri: `/orders/${orderID}`, callback }); } - getFills(args = {}, callback) { - if (!callback && typeof args === 'function') { - callback = args; - args = {}; + getFills(opts = {}, callback) { + if (!callback && typeof opts === 'function') { + callback = opts; + opts = {}; } - return this.get(['fills'], { qs: args }, callback); + return this._get({ uri: `/fills`, queries: opts, callback }); } getFundings(callback) { - return this.get(['funding'], callback); + return this._get({ uri: `/funding`, callback }); } repay(params, callback) { this._requireParams(params, ['amount', 'currency']); - return this.post(['funding/repay'], { body: params }, callback); + return this._post({ uri: `/funding/repay`, body: params, callback }); } marginTransfer(params, callback) { @@ -223,23 +214,27 @@ class AuthenticatedClient extends PublicClient { 'currency', 'amount', ]); - return this.post(['profiles/margin-transfer'], { body: params }, callback); + return this._post({ + uri: `/profiles/margin-transfer`, + body: params, + callback, + }); } closePosition(params, callback) { this._requireParams(params, ['repay_only']); - return this.post(['position/close'], { body: params }, callback); + return this._post({ uri: `/position/close`, body: params, callback }); } deposit(params, callback) { this._requireParams(params, ['amount', 'currency', 'coinbase_account_id']); - return this.post(['deposits/coinbase-account'], { body: params }, callback); + return this._post(`deposits/coinbase-account`, { body: params }, callback); } withdraw(params, callback) { this._requireParams(params, ['amount', 'currency', 'coinbase_account_id']); - return this.post( - ['withdrawals/coinbase-account'], + return this._post( + `withdrawals/coinbase-account`, { body: params }, callback ); @@ -247,7 +242,7 @@ class AuthenticatedClient extends PublicClient { withdrawCrypto(body, callback) { this._requireParams(body, ['amount', 'currency', 'crypto_address']); - return this.post(['withdrawals/crypto'], { body }, callback); + return this.post(`withdrawals/crypto`, { body }, callback); } _requireParams(params, required) { diff --git a/lib/clients/public.js b/lib/clients/public.js index 8e508d61..328e96f0 100644 --- a/lib/clients/public.js +++ b/lib/clients/public.js @@ -1,9 +1,29 @@ -const request = require('request'); const { Readable } = require('stream'); +const superagent = require('superagent'); +const agentUse = require('superagent-use'); +const agentPrefix = require('superagent-prefix'); +const Throttle = require('superagent-throttle'); + +const { + DEFAULT_BOOK_LEVEL, + GDAX_API_URI, + GDAX_PUBLIC_RATE_LIMIT, +} = require('../constants'); + +const staticAgent = agentUse(superagent).use(agentPrefix(GDAX_API_URI)).use( + new Throttle({ + rate: GDAX_PUBLIC_RATE_LIMIT, + ratePer: 1000, + }).plugin() +); + const DEFAULT_TIMEOUT = 10 * 1000; // 10 sec class PublicClient { - constructor(apiURI = 'https://api.gdax.com', options = {}) { + constructor( + apiURI = GDAX_API_URI, + { rateLimit = GDAX_PUBLIC_RATE_LIMIT, timeout = DEFAULT_TIMEOUT } = {} + ) { this.productID = 'BTC-USD'; if (apiURI && !apiURI.startsWith('http')) { process.emitWarning( @@ -13,123 +33,125 @@ class PublicClient { this.productID = apiURI; apiURI = arguments[1] || 'https://api.gdax.com'; } + this._API_LIMIT = 100; + this.timeout = +timeout > 0 ? +timeout : DEFAULT_TIMEOUT; + this._agent = agentUse(superagent).use(agentPrefix(apiURI)); + if (rateLimit) { + this._agent.use( + new Throttle({ + rate: rateLimit, + ratePer: 1000, + }).plugin() + ); + } + } - this.apiURI = apiURI; - this.API_LIMIT = 100; - this.timeout = +options.timeout > 0 ? options.timeout : DEFAULT_TIMEOUT; + static _get(opts) { + return PublicClient._request(Object.assign({ method: 'get' }, opts)); } - get(...args) { - return this.request('get', ...args); + _get(opts) { + return this._request(Object.assign({ method: 'get' }, opts)); } - put(...args) { - return this.request('put', ...args); + _put(opts) { + return this._request(Object.assign({ method: 'put' }, opts)); } - post(...args) { - return this.request('post', ...args); + _post(opts) { + return this._request(Object.assign({ method: 'post' }, opts)); } - delete(...args) { - return this.request('delete', ...args); + _delete(opts) { + return this._request(Object.assign({ method: 'delete' }, opts)); } - addHeaders(obj, additional) { - obj.headers = obj.headers || {}; - return Object.assign( - obj.headers, - { - 'User-Agent': 'gdax-node-client', - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - additional - ); + static _request(arg) { + return PublicClient.prototype._request.call({ _agent: staticAgent }, arg); } - makeRelativeURI(parts) { - return '/' + parts.join('/'); - } + _request({ method, uri, queries = {}, headers = {}, body = {}, callback }) { + method = method.toLowerCase(); - makeAbsoluteURI(relativeURI) { - return this.apiURI + relativeURI; - } + const request = this._agent[method](uri); - makeRequestCallback(callback, resolve, reject) { - return (err, response, data) => { - try { - data = JSON.parse(data); - } catch (e) { - data = null; - } + request.set('User-Agent', 'gdax-node-client').query(queries).accept('json'); - if (err) { - err.response = response; - } else if (response.statusCode > 299) { - err = new Error( - `HTTP ${response.statusCode} Error: ${data && data.message}` - ); - err.response = response; - err.data = data; - } else if (data === null) { - err = new Error('Response could not be parsed as JSON'); - err.response = response; - } + for (let h in headers) { + request.set(h, headers[h]); + } + + if (method === 'put' || method === 'post') { + request.type('json').send(body); + } + + if (this.timeout) { + request.timeout(this.timeout); + } - if (typeof callback === 'function') { - if (err) { - callback(err); + let responsePromise = request + .then(response => { + if (callback) { + callback(null, response.body); } else { - callback(null, response, data); + return response.body; + } + }) + .catch(error => { + // If GDAX returns an error message, override and make it the primary + // message of the error object returned to the user. + // GDAX API returns json body { "message": "" } + if ( + typeof error.response === 'object' && + typeof error.response.body === 'object' && + error.response.body.message + ) { + error.message = error.response.body.message; } - return; - } - - if (err) { - reject(err); - } else { - resolve(data); - } - }; - } - - request(method, uriParts, opts = {}, callback) { - if (!callback && typeof opts === 'function') { - callback = opts; - opts = {}; - } - Object.assign(opts, { - method: method.toUpperCase(), - uri: this.makeAbsoluteURI(this.makeRelativeURI(uriParts)), - qsStringifyOptions: { arrayFormat: 'repeat' }, - timeout: this.timeout, - }); - this.addHeaders(opts); - const p = new Promise((resolve, reject) => { - request(opts, this.makeRequestCallback(callback, resolve, reject)); - }); + if (callback) { + callback(error); + } else { + throw error; + } + }); if (callback) { - p.catch(() => {}); return undefined; } else { - return p; + return responsePromise; + } + } + + static _requireParams() { + return PublicClient.prototype._requireParams(...arguments); + } + + _requireParams(params, required) { + for (let param of required) { + if (params[param] === undefined) { + throw new Error('parameter `' + param + '` is required'); + } } + return true; } getProducts(callback) { - return this.get(['products'], callback); + return this._get({ uri: '/products', callback }); } - getProductOrderBook(productID, args, callback) { - [productID, args, callback] = this._normalizeProductArgs( + getProductOrderBook(productID, opts = {}, callback) { + [productID, opts, callback] = this._normalizeProductArgs( productID, - args, + opts, callback, 'getProductOrderBook' ); - const path = ['products', productID, 'book']; - return this.get(path, { qs: args }, callback); + opts.level = opts.level || DEFAULT_BOOK_LEVEL; + + return this._get({ + uri: `/products/${productID}/book`, + queries: opts, + callback, + }); } getProductTicker(productID, callback) { @@ -139,24 +161,28 @@ class PublicClient { callback, 'getProductTicker' ); - - const path = ['products', productID, 'ticker']; - return this.get(path, callback); + + return this._get({ uri: `/products/${this._productID}/ticker`, callback }); } - getProductTrades(productID, args, callback) { - [productID, args, callback] = this._normalizeProductArgs( + getProductTrades(productID, opts = {}, callback) { + [productID, opts, callback] = this._normalizeProductArgs( productID, - args, + opts, callback, 'getProductTrades' ); - const path = ['products', productID, 'trades']; - return this.get(path, { qs: args }, callback); + return this._get({ + uri: `/products/${this._productID}/trades`, + queries: opts, + callback, + }); } getProductTradeStream(productID, tradesFrom, tradesTo) { + let stopFn = () => false; + if (!productID || typeof productID !== 'string') { [tradesFrom, tradesTo] = Array.prototype.slice.call(arguments); } @@ -171,89 +197,72 @@ class PublicClient { let shouldStop = null; if (typeof tradesTo === 'function') { - shouldStop = tradesTo; - tradesTo = null; + stopFn = tradesTo; + tradesTo = Infinity; } - const rs = new Readable({ objectMode: true }); + const stream = new Readable({ objectMode: true }); let started = false; - rs._read = () => { + stream._read = () => { if (!started) { started = true; - fetchTrades.call(this, rs, tradesFrom, tradesTo, shouldStop, 0); + fetchTrades.call(this, tradesFrom, tradesTo); } }; - return rs; + return stream; - function fetchTrades(stream, tradesFrom, tradesTo, shouldStop) { - let after = tradesFrom + this.API_LIMIT + 1; - let loop = true; + function fetchTrades(tradesFrom, tradesTo) { + let after = tradesFrom + this._API_LIMIT + 1; - if (tradesTo && tradesTo <= after) { + if (tradesTo < after) { after = tradesTo; - loop = false; } - let opts = { before: tradesFrom, after: after, limit: this.API_LIMIT }; - - this.getProductTrades(productID, opts, (err, resp, data) => { - if (err) { - stream.emit('error', err); - return; - } + let opts = { before: tradesFrom, after: after, limit: this._API_LIMIT }; - if (resp.statusCode === 429) { - // rate-limited, try again - setTimeout(() => { - fetchTrades.call(this, stream, tradesFrom, tradesTo, shouldStop); - }, 900); - return; - } + this.getProductTrades(productID, opts) + .then(data => { + let trade; - if (resp.statusCode !== 200) { - stream.emit( - 'error', - new Error('Encountered status code ' + resp.statusCode) - ); - } + while (data.length) { + trade = data.pop(); + trade.trade_id = parseInt(trade.trade_id); - for (let i = data.length - 1; i >= 0; i--) { - if (shouldStop && shouldStop(data[i])) { - stream.push(null); - return; - } + if (stopFn(trade)) { + stream.push(null); + return; + } - stream.push(data[i]); - } + if (trade.trade_id >= tradesTo - 1) { + stream.push(trade); + stream.push(null); + return; + } - if (!loop || data.length === 0) { - stream.push(null); - return; - } + stream.push(trade); + } - fetchTrades.call( - this, - stream, - tradesFrom + this.API_LIMIT, - tradesTo, - shouldStop - ); - }); + fetchTrades.call(this, trade.trade_id, tradesTo); + }) + .catch(err => stream.emit('error', err)); } } - getProductHistoricRates(productID, args, callback) { - [productID, args, callback] = this._normalizeProductArgs( + getProductHistoricRates(productID, opts, callback) { + [productID, opts, callback] = this._normalizeProductArgs( productID, - args, + opts, callback, 'getProductHistoricRates' ); - const path = ['products', productID, 'candles']; - return this.get(path, { qs: args }, callback); + return this._get({ + uri: `/products/${this._productID}/candles`, + queries: opts, + callback, + }); } getProduct24HrStats(productID, callback) { @@ -264,16 +273,15 @@ class PublicClient { 'getProduct24HrStats' ); - const path = ['products', productID, 'stats']; - return this.get(path, callback); + return this._get({ uri: `/products/${this._productID}/stats`, callback }); } getCurrencies(callback) { - return this.get(['currencies'], callback); + return this._get({ uri: '/currencies', callback }); } getTime(callback) { - return this.get(['time'], callback); + return this._get({ uri: '/time', callback }); } _normalizeProductArgs(productID, args, callback, caller) { diff --git a/lib/clients/websocket.js b/lib/clients/websocket.js index d80917d6..516eb657 100644 --- a/lib/clients/websocket.js +++ b/lib/clients/websocket.js @@ -1,8 +1,10 @@ const { EventEmitter } = require('events'); const Websocket = require('ws'); -const Utils = require('../utilities.js'); +const Utils = require('../utilities'); const { signRequest } = require('../../lib/request_signer'); +const { GDAX_WEBSOCKET_URI } = require('../constants'); + /** * Create a new connection to a websocket feed * @param {String[]} [productIDs] - The GDAX products to listen to. Default: ['BTC-USD'] @@ -12,7 +14,7 @@ const { signRequest } = require('../../lib/request_signer'); class WebsocketClient extends EventEmitter { constructor( productIDs, - websocketURI = 'wss://ws-feed.gdax.com', + websocketURI = GDAX_WEBSOCKET_URI, auth = null, { channels = null } = {} ) { diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 00000000..a573147b --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,7 @@ +module.exports = { + DEFAULT_BOOK_LEVEL: 3, + GDAX_API_URI: 'https://api.gdax.com', + GDAX_AUTHED_RATE_LIMIT: 5, + GDAX_PUBLIC_RATE_LIMIT: 3, + GDAX_WEBSOCKET_URI: 'wss://ws-feed.gdax.com', +}; diff --git a/lib/orderbook_sync.js b/lib/orderbook_sync.js index 1ac06c8d..b76e8b2a 100644 --- a/lib/orderbook_sync.js +++ b/lib/orderbook_sync.js @@ -4,12 +4,14 @@ const PublicClient = require('./clients/public.js'); const Orderbook = require('./orderbook.js'); const Utils = require('./utilities.js'); +const { GDAX_WEBSOCKET_URI } = require('./constants'); + // Orderbook syncing class OrderbookSync extends WebsocketClient { constructor( productIDs, - apiURI = 'https://api.gdax.com', - websocketURI = 'wss://ws-feed.gdax.com', + apiURI = GDAX_API_URI, + websocketURI = GDAX_WEBSOCKET_URI, auth = null ) { super(productIDs, websocketURI, auth); @@ -58,7 +60,7 @@ class OrderbookSync extends WebsocketClient { } } - loadOrderbook(productID) { + loadOrderbook(productID, opts = {}) { if (!this.books[productID]) { return; } @@ -68,21 +70,22 @@ class OrderbookSync extends WebsocketClient { .then(onData.bind(this)) .catch(onError.bind(this)); - function onData(data) { + const onData = data => { this.books[productID].state(data); - this._sequences[productID] = data.sequence; - this._queues[productID].forEach(this.processMessage.bind(this)); + this._queues[productID].forEach(message => this._processMessage(message)); this._queues[productID] = []; - } + }; - function onError(err) { + const onError = err => { err = err ? (err.message ? err.message : err) : ''; this.emit('error', new Error('Failed to load orderbook: ' + err)); - } + }; + + this._client.getProductOrderBook(opts).then(onData).catch(onError); } - processMessage(data) { + _processMessage(data) { const { product_id } = data; if (this._sequences[product_id] === -1) { diff --git a/lib/request_signer.js b/lib/request_signer.js index b4f4275f..53976595 100644 --- a/lib/request_signer.js +++ b/lib/request_signer.js @@ -3,26 +3,32 @@ const crypto = require('crypto'); const querystring = require('querystring'); /** Signs request messages for authenticated requests to GDAX - * @param auth {object} hash containing key, secret and passphrase - * @param method {string} The REST method to use - * @param path {string} The request path, e.g. /products/BTC-USD/ticker - * @param options {object} An optional object containing one of - * @param options.body {object} A hash of body properties - * @param options.qs {object} A hash of query string parameters + * @param {object} auth - hash containing key, secret and passphrase + * @param {string} method - The REST method to use + * @param {string} path - The request path, e.g. /products/BTC-USD/ticker + * @param {object} queries - An optional object containing one of + * @param {object} body - An object used as JSON payload for a POST or PUT request * @returns {{key: string, signature: *, timestamp: number, passphrase: string}} */ -module.exports.signRequest = (auth, method, path, options = {}) => { - const timestamp = Date.now() / 1000; - let body = ''; - if (options.body) { - body = JSON.stringify(options.body); - } else if (options.qs && Object.keys(options.qs).length !== 0) { - body = '?' + querystring.stringify(options.qs); +module.exports.signRequest = (auth, method, path, queries, body) => { + let queriesString = ''; + let bodyString = ''; + + if (queries && typeof queries === 'object') { + queriesString = '?' + querystring.stringify(queries); + } + if (body && typeof body === 'object') { + bodyString = JSON.stringify(body); } - const what = timestamp + method.toUpperCase() + path + body; + + const timestamp = Date.now() / 1000; + + const what = + timestamp + method.toUpperCase() + path + queriesString + bodyString; const key = Buffer(auth.secret, 'base64'); const hmac = crypto.createHmac('sha256', key); const signature = hmac.update(what).digest('base64'); + return { key: auth.key, signature: signature, diff --git a/package.json b/package.json index 53798d12..1c2bbb2e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,10 @@ "dependencies": { "bintrees": "1.0.1", "num": "0.3.0", - "request": "2.81.0", + "superagent": "3.5.2", + "superagent-prefix": "0.0.2", + "superagent-throttle": "0.2.5", + "superagent-use": "0.1.0", "ws": "3.0.0" }, "description": "Client for the GDAX API", diff --git a/tests/authenticated.spec.js b/tests/authenticated.spec.js index 912fa485..48988f8f 100644 --- a/tests/authenticated.spec.js +++ b/tests/authenticated.spec.js @@ -4,12 +4,21 @@ const nock = require('nock'); const Gdax = require('../index.js'); const key = 'key'; -const secret = 'secret'; +const b64secret = 'secret'; const passphrase = 'passphrase'; -const EXCHANGE_API_URL = 'https://api.gdax.com'; +const { DEFAULT_BOOK_LEVEL, GDAX_API_URI } = require('../lib/constants'); -const authClient = new Gdax.AuthenticatedClient(key, secret, passphrase); +const authClient = new Gdax.AuthenticatedClient( + key, + b64secret, + passphrase, + undefined, + undefined, + { + rateLimit: false, + } +); suite('AuthenticatedClient', () => { afterEach(() => nock.cleanAll()); @@ -22,7 +31,7 @@ suite('AuthenticatedClient', () => { uri: 'https://api.gdax.com/orders', }; - const sig = authClient._getSignature(method, relativeURI, opts); + const sig = authClient._getSignatureHeaders(method, relativeURI, opts); assert.equal(sig['CB-ACCESS-KEY'], key); assert.equal(sig['CB-ACCESS-PASSPHRASE'], passphrase); @@ -68,7 +77,7 @@ suite('AuthenticatedClient', () => { .catch(err => assert.ifError(err) || assert.fail()); }); - test('.getAccount()', done => { + test('.getAccount()', () => { const expectedResponse = { id: 'a1b2c3d4', balance: '1.100', @@ -77,13 +86,13 @@ suite('AuthenticatedClient', () => { currency: 'USD', }; - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .get('/accounts/test-id') .times(2) .reply(200, expectedResponse); let cbtest = new Promise((resolve, reject) => - authClient.getAccount('test-id', (err, resp, data) => { + authClient.getAccount('test-id', (err, data) => { if (err) { reject(err); } @@ -96,12 +105,10 @@ suite('AuthenticatedClient', () => { .getAccount('test-id') .then(data => assert.deepEqual(data, expectedResponse)); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('.getAccounts()', done => { + test('.getAccounts()', () => { const expectedResponse = [ { id: 'a1b2c3d4', @@ -112,13 +119,10 @@ suite('AuthenticatedClient', () => { }, ]; - nock(EXCHANGE_API_URL) - .get('/accounts') - .times(2) - .reply(200, expectedResponse); + nock(GDAX_API_URI).get('/accounts').times(2).reply(200, expectedResponse); let cbtest = new Promise((resolve, reject) => - authClient.getAccounts((err, resp, data) => { + authClient.getAccounts((err, data) => { if (err) { reject(err); } @@ -131,12 +135,10 @@ suite('AuthenticatedClient', () => { .getAccounts() .then(data => assert.deepEqual(data, expectedResponse)); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('.getAccountHistory()', done => { + test('.getAccountHistory()', () => { const expectedResponse = [ { id: '100', @@ -152,13 +154,13 @@ suite('AuthenticatedClient', () => { }, ]; - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .get('/accounts/test-id/ledger') .times(2) .reply(200, expectedResponse); let cbtest = new Promise((resolve, reject) => - authClient.getAccountHistory('test-id', (err, resp, data) => { + authClient.getAccountHistory('test-id', (err, data) => { if (err) { reject(err); } @@ -171,12 +173,10 @@ suite('AuthenticatedClient', () => { .getAccountHistory('test-id') .then(data => assert.deepEqual(data, expectedResponse)); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('.getAccountHolds()', done => { + test('.getAccountHolds()', () => { const expectedResponse = [ { id: '82dcd140-c3c7-4507-8de4-2c529cd1a28f', @@ -189,13 +189,13 @@ suite('AuthenticatedClient', () => { }, ]; - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .get('/accounts/test-id/holds') .times(2) .reply(200, expectedResponse); let cbtest = new Promise((resolve, reject) => { - authClient.getAccountHolds('test-id', (err, resp, data) => { + authClient.getAccountHolds('test-id', (err, data) => { if (err) { reject(err); } @@ -208,9 +208,7 @@ suite('AuthenticatedClient', () => { .getAccountHolds('test-id') .then(data => assert.deepEqual(data, expectedResponse)); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); test('.placeOrder()', done => { @@ -240,7 +238,7 @@ suite('AuthenticatedClient', () => { }); }); - test('.buy()', done => { + test('.buy()', () => { const order = { size: '10', product_id: 'BTC-USD', @@ -254,13 +252,13 @@ suite('AuthenticatedClient', () => { id: '0428b97b-bec1-429e-a94c-59992926778d', }; - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .post('/orders', expectedOrder) .times(2) .reply(200, expectedResponse); let cbtest = new Promise((resolve, reject) => { - authClient.buy(order, (err, resp, data) => { + authClient.buy(order, (err, data) => { if (err) { reject(err); } @@ -273,12 +271,10 @@ suite('AuthenticatedClient', () => { .buy(order) .then(data => assert.deepEqual(data, expectedResponse)); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('.buy() market order', done => { + test('.buy() market order', () => { const order = { funds: '20.00', product_id: 'BTC-USD', @@ -292,20 +288,29 @@ suite('AuthenticatedClient', () => { id: '0428b97b-bec1-429e-a94c-59992926778d', }; - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .post('/orders', expectedOrder) + .times(2) .reply(200, expectedResponse); - authClient.buy(order, (err, resp, data) => { - assert.ifError(err); - assert.deepEqual(data, expectedResponse); - - nock.cleanAll(); - done(); + const cbTest = new Promise((resolve, reject) => { + authClient.buy(order, (err, data) => { + if (err) { + reject(err); + } + assert.deepEqual(data, expectedResponse); + resolve(); + }); }); + + const promiseTest = cbTest + .then(() => authClient.buy(order)) + .then(data => assert.deepEqual(data, expectedResponse)); + + return Promise.all([cbTest, promiseTest]); }); - test('.sell()', done => { + test('.sell()', () => { const order = { size: '10', product_id: 'BTC-USD', @@ -319,13 +324,13 @@ suite('AuthenticatedClient', () => { id: '0428b97b-bec1-429e-a94c-59992926778d', }; - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .post('/orders', expectedOrder) .times(2) .reply(200, expectedResponse); - let cbtest = new Promise((resolve, reject) => { - authClient.sell(order, (err, resp, data) => { + const cbtest = new Promise((resolve, reject) => { + authClient.sell(order, (err, data) => { if (err) { reject(err); } @@ -334,13 +339,37 @@ suite('AuthenticatedClient', () => { }); }); - let promisetest = authClient - .sell(order) + const promisetest = cbtest + .then(() => authClient.sell(order)) .then(data => assert.deepEqual(data, expectedResponse)); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); + }); + + test('.getProductOrderBook()', () => { + nock(GDAX_API_URI) + .get(`/products/BTC-USD/book?level=${DEFAULT_BOOK_LEVEL}`) + .times(2) + .reply(200, { + asks: [], + bids: [], + }); + + let cbtest = new Promise((resolve, reject) => { + authClient.getProductOrderBook({ level: 3 }, (err, data) => { + if (err) { + reject(err); + } + assert(data); + resolve(); + }); + }); + + let promisetest = authClient + .getProductOrderBook({ level: 3 }) + .then(data => assert(data)); + + return Promise.all([cbtest, promisetest]); }); suite('.cancelAllOrders()', () => { @@ -358,13 +387,14 @@ suite('AuthenticatedClient', () => { 'deleted-id-7', 'deleted-id-8', ]; - const totalExpectedDeleted = cancelledOrdersOne.concat( - cancelledOrdersTwo - ); + const totalExpectedDeleted = [ + ...cancelledOrdersOne, + ...cancelledOrdersTwo, + ]; const nockSetup = () => { // first list of Id's that just got cancelled - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .delete('/orders') .reply(200, cancelledOrdersOne) // second list of Id's that just got cancelled @@ -379,7 +409,7 @@ suite('AuthenticatedClient', () => { nockSetup(); let cbtest = new Promise((resolve, reject) => { - const p = authClient.cancelAllOrders((err, resp, data) => { + authClient.cancelAllOrders((err, data) => { if (err) { reject(err); } else { @@ -387,10 +417,6 @@ suite('AuthenticatedClient', () => { } resolve(); }); - - if (p !== undefined) { - reject(); - } }); return cbtest @@ -432,11 +458,12 @@ suite('AuthenticatedClient', () => { }); suite('.cancelOrder()', () => { - test('requires orderID', done => { + test('requires orderID', () => { let cbtest = new Promise(resolve => { authClient - .cancelOrder(err => { + .cancelOrder((err, data) => { assert(err); + assert.ifError(data); resolve(); }) .catch(() => {}); @@ -447,12 +474,10 @@ suite('AuthenticatedClient', () => { .catch(err => !assert(err) && true) .then(val => assert.strictEqual(val, true)); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('cancels order', done => { + test('cancels order', () => { const expectedResponse = [ { id: 'd50ec984-77a8-460a-b958-66f114b0de9b', @@ -468,13 +493,10 @@ suite('AuthenticatedClient', () => { }, ]; - nock(EXCHANGE_API_URL) - .get('/orders') - .times(2) - .reply(200, expectedResponse); + nock(GDAX_API_URI).get('/orders').times(2).reply(200, expectedResponse); let cbtest = new Promise((resolve, reject) => { - authClient.getOrders((err, resp, data) => { + authClient.getOrders((err, data) => { if (err) { reject(err); } @@ -487,13 +509,11 @@ suite('AuthenticatedClient', () => { .getOrders() .then(data => assert.deepEqual(data, expectedResponse)); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); }); - test('.getFills()', done => { + test('.getFills()', () => { const expectedResponse = [ { trade_id: 74, @@ -515,7 +535,7 @@ suite('AuthenticatedClient', () => { .reply(200, expectedResponse); let cbtest = new Promise((resolve, reject) => { - authClient.getFills((err, response, data) => { + authClient.getFills((err, data) => { if (err) { reject(err); } @@ -528,12 +548,10 @@ suite('AuthenticatedClient', () => { .getFills() .then(data => assert.deepEqual(data, expectedResponse)); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('.getFundings()', done => { + test('.getFundings()', () => { const expectedResponse = [ { id: '280c0a56-f2fa-4d3b-a199-92df76fff5cd', @@ -547,13 +565,10 @@ suite('AuthenticatedClient', () => { }, ]; - nock(EXCHANGE_API_URL) - .get('/funding') - .times(2) - .reply(200, expectedResponse); + nock(GDAX_API_URI).get('/funding').times(2).reply(200, expectedResponse); let cbtest = new Promise((resolve, reject) => { - authClient.getFundings((err, resp, data) => { + authClient.getFundings((err, data) => { if (err) { reject(err); } @@ -564,41 +579,38 @@ suite('AuthenticatedClient', () => { let promisetest = authClient.getFundings(); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('.repay()', done => { + test('.repay()', () => { const params = { amount: 10000, currency: 'USD', }; - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .post('/funding/repay', params) .reply(200, {}); - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .post('/funding/repay', params) .reply(200, {}); let cbtest = new Promise((resolve, reject) => { - authClient.repay(params, err => { + authClient.repay(params, (err, data) => { if (err) { reject(err); } + assert.deepEqual(data, {}); resolve(); }); }); let promisetest = authClient.repay(params); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('.marginTransfer()', done => { + test('.marginTransfer()', () => { const params = { margin_profile_id: '45fa9e3b-00ba-4631-b907-8a98cbdf21be', type: 'deposit', @@ -621,13 +633,13 @@ suite('AuthenticatedClient', () => { nonce: 25, }; - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .post('/profiles/margin-transfer', params) .times(2) .reply(200, expectedResponse); let cbtest = new Promise((resolve, reject) => { - authClient.marginTransfer(params, (err, resp, data) => { + authClient.marginTransfer(params, (err, data) => { if (err) { reject(err); } @@ -638,38 +650,32 @@ suite('AuthenticatedClient', () => { let promisetest = authClient.marginTransfer(params); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('.closePosition()', done => { + test('.closePosition()', () => { const params = { repay_only: false, }; - nock(EXCHANGE_API_URL) - .post('/position/close', params) - .times(2) - .reply(200, {}); + nock(GDAX_API_URI).post('/position/close', params).times(2).reply(200, {}); // TODO mock with real data let cbtest = new Promise((resolve, reject) => { - authClient.closePosition(params, err => { + authClient.closePosition(params, (err, data) => { if (err) { reject(err); } + assert.deepEqual(data, {}); resolve(); }); }); let promisetest = authClient.closePosition(params); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); - test('.deposit()', done => { + test('.deposit()', () => { const transfer = { amount: 10480, currency: 'USD', @@ -678,28 +684,27 @@ suite('AuthenticatedClient', () => { const expectedTransfer = transfer; - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .post('/deposits/coinbase-account', expectedTransfer) .times(2) - .reply(200, {}); + .reply(200, {}); // TODO mock with real data; let cbtest = new Promise((resolve, reject) => { - authClient.deposit(transfer, err => { + authClient.deposit(transfer, (err, data) => { if (err) { reject(err); } + assert.deepEqual(data, {}); resolve(); }); }); let promisetest = authClient.deposit(transfer); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail); + return Promise.all([cbtest, promisetest]); }); - test('.withdraw()', done => { + test('.withdraw()', () => { const transfer = { amount: 10480, currency: 'USD', @@ -708,25 +713,24 @@ suite('AuthenticatedClient', () => { const expectedTransfer = transfer; - nock(EXCHANGE_API_URL) + nock(GDAX_API_URI) .post('/withdrawals/coinbase-account', expectedTransfer) .times(2) - .reply(200, {}); + .reply(200, {}); // TODO mock with real data let cbtest = new Promise((resolve, reject) => { - authClient.withdraw(transfer, err => { + authClient.withdraw(transfer, (err, data) => { if (err) { reject(err); } + assert.deepEqual(data, {}); resolve(); }); }); let promisetest = authClient.withdraw(transfer); - Promise.all([cbtest, promisetest]) - .then(() => done()) - .catch(err => assert.ifError(err) || assert.fail()); + return Promise.all([cbtest, promisetest]); }); test('.createReport()', done => { diff --git a/tests/orderbook_sync.spec.js b/tests/orderbook_sync.spec.js index 49f88ac6..deb2027c 100644 --- a/tests/orderbook_sync.spec.js +++ b/tests/orderbook_sync.spec.js @@ -6,7 +6,11 @@ const Gdax = require('../index.js'); const testserver = require('./lib/ws_testserver'); let port = 56632; -const EXCHANGE_API_URL = 'https://api.gdax.com'; +const { DEFAULT_BOOK_LEVEL, GDAX_API_URI } = require('../lib/constants'); + +const key = 'key'; +const b64secret = 'secret'; +const passphrase = 'passphrase'; suite('OrderbookSync', () => { test('not passes authentication details to websocket', done => { @@ -78,8 +82,8 @@ suite('OrderbookSync', () => { }); test('emits a message event', done => { - nock(EXCHANGE_API_URL) - .get('/products/BTC-USD/book?level=3') + nock(GDAX_API_URI) + .get(`/products/BTC-USD/book?level=${DEFAULT_BOOK_LEVEL}`) .times(2) .reply(200, { asks: [], @@ -89,7 +93,6 @@ suite('OrderbookSync', () => { const server = testserver(port, () => { const orderbookSync = new Gdax.OrderbookSync( 'BTC-USD', - EXCHANGE_API_URL, 'ws://localhost:' + port ); orderbookSync.on('message', data => { @@ -107,6 +110,9 @@ suite('OrderbookSync', () => { }); }); + test('emits a message event (with AuthenticatedClient)', done => { + nock(GDAX_API_URI) + .get(`/products/BTC-USD/book?level=${DEFAULT_BOOK_LEVEL}`) test('emits a message event (with auth)', done => { nock(EXCHANGE_API_URL) .get('/products/BTC-USD/book?level=3') @@ -117,9 +123,16 @@ suite('OrderbookSync', () => { }); const server = testserver(port, () => { + const authClient = new Gdax.AuthenticatedClient( + key, + b64secret, + passphrase, + { + rateLimit: false, + } + ); const orderbookSync = new Gdax.OrderbookSync( 'BTC-USD', - EXCHANGE_API_URL, 'ws://localhost:' + port, { key: 'key', secret: 'secret', passphrase: 'pass' } ); @@ -139,14 +152,13 @@ suite('OrderbookSync', () => { }); test('emits an error event on error', done => { - nock(EXCHANGE_API_URL) - .get('/products/BTC-USD/book?level=3') + nock(GDAX_API_URI) + .get(`/products/BTC-USD/book?level=${DEFAULT_BOOK_LEVEL}`) .replyWithError('whoops'); const server = testserver(port, () => { const orderbookSync = new Gdax.OrderbookSync( 'BTC-USD', - EXCHANGE_API_URL, 'ws://localhost:' + port ); @@ -164,15 +176,25 @@ suite('OrderbookSync', () => { }); }); - test('emits an error event on error (with auth)', done => { - nock(EXCHANGE_API_URL) - .get('/products/BTC-USD/book?level=3') + test('emits an error event on error (with AuthenticatedClient)', done => { + nock(GDAX_API_URI) + .get(`/products/BTC-USD/book?level=${DEFAULT_BOOK_LEVEL}`) .replyWithError('whoops'); const server = testserver(port, () => { + const authClient = new Gdax.AuthenticatedClient( + key, + b64secret, + passphrase, + undefined, + undefined, + { + rateLimit: false, + } + ); + const orderbookSync = new Gdax.OrderbookSync( 'BTC-USD', - EXCHANGE_API_URL, 'ws://localhost:' + port, { key: 'key', secret: 'secret', passphrase: 'pass' } ); @@ -192,16 +214,16 @@ suite('OrderbookSync', () => { }); test('builds specified books', done => { - nock(EXCHANGE_API_URL) - .get('/products/BTC-USD/book?level=3') + nock(GDAX_API_URI) + .get(`/products/BTC-USD/book?level=${DEFAULT_BOOK_LEVEL}`) .times(2) .reply(200, { asks: [], bids: [], }); - nock(EXCHANGE_API_URL) - .get('/products/ETH-USD/book?level=3') + nock(GDAX_API_URI) + .get(`/products/ETH-USD/book?level=${DEFAULT_BOOK_LEVEL}`) .times(2) .reply(200, { asks: [], @@ -211,7 +233,6 @@ suite('OrderbookSync', () => { const server = testserver(port, () => { const orderbookSync = new Gdax.OrderbookSync( ['BTC-USD', 'ETH-USD'], - EXCHANGE_API_URL, 'ws://localhost:' + port ); const btc_usd_state = orderbookSync.books['BTC-USD'].state(); diff --git a/tests/public_client.spec.js b/tests/public_client.spec.js index f37af97d..d03f9e66 100644 --- a/tests/public_client.spec.js +++ b/tests/public_client.spec.js @@ -2,9 +2,11 @@ const assert = require('assert'); const nock = require('nock'); const Gdax = require('../index.js'); -const publicClient = new Gdax.PublicClient(); +const publicClient = new Gdax.PublicClient(undefined, undefined, { + rateLimit: false, +}); -const EXCHANGE_API_URL = 'https://api.gdax.com'; +const { GDAX_API_URI } = require('../lib/constants'); suite('PublicClient', () => { afterEach(() => nock.cleanAll()); @@ -122,12 +124,12 @@ suite('PublicClient', () => { }, ]; - nock(EXCHANGE_API_URL) - .get('/products/LTC-USD/trades') + nock(GDAX_API_URI) + .get('/products/BTC-USD/trades') .times(2) .reply(200, expectedResponse); - const cbtest = new Promise((resolve, reject) => { + let cbtest = new Promise((resolve, reject) => { publicClient.getProductTrades('LTC-USD', (err, resp, data) => { if (err) { reject(err); @@ -135,6 +137,10 @@ suite('PublicClient', () => { assert.deepEqual(data, expectedResponse); resolve(); }); + + if (typeof p !== 'undefined') { + reject('Should not return Promise when callback provided.'); + } }); const promisetest = publicClient @@ -165,15 +171,12 @@ suite('PublicClient', () => { .then(data => assert.deepEqual(data, expectedResponse)); }); - test('.getProductTicker()', () => { - nock(EXCHANGE_API_URL) - .get('/products/ETH-USD/ticker') - .times(2) - .reply(200, { - trade_id: 'test-id', - price: '9.00', - size: '5', - }); + test('.getProductTicker() should return values', () => { + nock(GDAX_API_URI).get('/products/BTC-USD/ticker').times(2).reply(200, { + trade_id: 'test-id', + price: '9.00', + size: '5', + }); const cbtest = new Promise((resolve, reject) => { publicClient.getProductTicker('ETH-USD', (err, resp, data) => { @@ -285,8 +288,8 @@ suite('PublicClient', () => { from, trade => Date.parse(trade.time) >= 1463068800000 ) - .on('data', data => { - current = data.trade_id; + .on('data', trade => { + current = trade.trade_id; assert.equal(typeof current, 'number'); assert.equal( current,