Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

coinbase-exchange

Package Overview
Dependencies
Maintainers
4
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

coinbase-exchange - npm Package Compare versions

Comparing version 0.1.0 to 0.2.0

lib/clients/websocket.js

12

index.js

@@ -1,9 +0,13 @@

var PublicClient = require('./lib/clients/public.js');
var PublicClient = require('./lib/clients/public.js');
var WebsocketClient = require('./lib/clients/websocket.js');
var AuthenticatedClient = require('./lib/clients/authenticated.js');
var OrderBook = require('./lib/orderbook.js');
var Orderbook = require('./lib/orderbook.js');
var OrderbookSync = require('./lib/orderbook_sync.js');
module.exports = exports = {
'PublicClient': PublicClient,
'PublicClient' : PublicClient,
'WebsocketClient' : WebsocketClient,
'AuthenticatedClient': AuthenticatedClient,
'OrderBook': OrderBook,
'Orderbook' : Orderbook,
'OrderbookSync' : OrderbookSync,
};
var util = require('util');
var crypto = require('crypto');
var querystring = require('querystring');

@@ -16,3 +17,4 @@ var _ = {

var self = this;
PublicClient.call(self, apiURI);
PublicClient.call(self, '', apiURI);
self.key = key;

@@ -24,2 +26,3 @@ self.b64secret = b64secret;

util.inherits(AuthenticatedClient, PublicClient);
_.assign(AuthenticatedClient.prototype, new function() {

@@ -30,4 +33,4 @@ var prototype = this;

var self = this;
opts = opts || {};
method = method.toUpperCase();
if (!callback && (opts instanceof Function)) {

@@ -37,19 +40,35 @@ callback = opts;

}
if (!callback) {
throw "Must supply a callback"
}
var relativeURI = self.makeRelativeURI(uriParts);
method = method.toUpperCase();
_.assign(opts, {
'method': method,
'uri': self.makeAbsoluteURI(relativeURI),
'uri': self.makeAbsoluteURI(relativeURI)
});
if (opts.body && (typeof opts.body !== 'string')) {
opts.body = JSON.stringify(opts.body);
self.addHeaders(opts, self._getSignature(method, relativeURI, opts));
request(opts, self.makeRequestCallback(callback));
};
prototype._getSignature = function(method, relativeURI, opts) {
var self = this;
var body = '';
if (opts.body) {
body = JSON.stringify(opts.body);
opts.body = body;
} else if (opts.qs && Object.keys(opts.qs).length !== 0) {
body = '?' + querystring.stringify(opts.qs);
}
var timestamp = Date.now() / 1000;
var what = timestamp + method + relativeURI + (opts.body || '');
var what = timestamp + method + relativeURI + body;
var key = Buffer(self.b64secret, 'base64');
var hmac = crypto.createHmac('sha256', key);
var signature = hmac.update(what).digest('base64');
self.addHeaders(opts, {
return {
'CB-ACCESS-KEY': self.key,

@@ -59,4 +78,3 @@ 'CB-ACCESS-SIGN': signature,

'CB-ACCESS-PASSPHRASE': self.passphrase,
});
request(opts, self.makeRequestCallback(callback));
};
};

@@ -78,10 +96,26 @@

prototype.getAccountHistory = function(accountID, callback) {
prototype.getAccountHistory = function(accountID, args, callback) {
var self = this;
return prototype.get.call(self, ['accounts', accountID, 'ledger'], callback);
args = args || {}
if (!callback && (args instanceof Function)) {
callback = args;
args = {};
}
var opts = { 'qs': args };
return prototype.get.call(self, ['accounts', accountID, 'ledger'], opts, callback);
};
prototype.getAccountHolds = function(accountID, callback) {
prototype.getAccountHolds = function(accountID, args, callback) {
var self = this;
return prototype.get.call(self, ['accounts', accountID, 'holds'], callback);
args = args || {}
if (!callback && (args instanceof Function)) {
callback = args;
args = {};
}
var opts = { 'qs': args };
return prototype.get.call(self, ['accounts', accountID, 'holds'], opts, callback);
};

@@ -96,3 +130,3 @@

});
opts = {'body': params};
var opts = { 'body': params };
return prototype.post.call(self, ['orders'], opts, callback);

@@ -118,5 +152,13 @@ };

prototype.getOrders = function(callback) {
prototype.getOrders = function(args, callback) {
var self = this;
return prototype.get.call(self, ['orders'], callback);
args = args || {}
if (!callback && (args instanceof Function)) {
callback = args;
args = {};
}
var opts = { 'qs': args };
return prototype.get.call(self, ['orders'], opts, callback);
};

@@ -129,5 +171,13 @@

prototype.getFills = function(callback) {
prototype.getFills = function(args, callback) {
var self = this;
return prototype.get.call(self, ['fills'], callback);
args = args || {}
if (!callback && (args instanceof Function)) {
callback = args;
args = {};
}
var opts = { 'qs': args };
return prototype.get.call(self, ['fills'], opts, callback);
};

@@ -154,3 +204,3 @@

});
opts = {'body': params};
var opts = { 'body': params };
return prototype.post.call(self, ['transfers'], opts, callback);

@@ -157,0 +207,0 @@ };

@@ -9,9 +9,11 @@ var request = require('request');

var PublicClient = function(apiURI) {
var PublicClient = function(productID, apiURI) {
var self = this;
self.productID = productID || 'BTC-USD';
self.apiURI = apiURI || 'https://api.exchange.coinbase.com';
};
PublicClient.prototype = new function() {
_.assign(PublicClient.prototype, new function() {
var prototype = this;
prototype.addHeaders = function(obj, additional) {

@@ -31,4 +33,3 @@ obj.headers = obj.headers || {};

prototype.makeAbsoluteURI = function(relativeURI) {
var self = this;
return self.apiURI + relativeURI;
return this.apiURI + relativeURI;
};

@@ -60,3 +61,2 @@

'uri': self.makeAbsoluteURI(self.makeRelativeURI(uriParts)),
'json': true,
});

@@ -76,31 +76,43 @@ self.addHeaders(opts);

prototype.getProductOrderBook = function(productID, level, callback) {
prototype.getProductOrderBook = function(args, callback) {
var self = this;
if (!callback && (level instanceof Function)) {
callback = level;
level = null;
args = args || {}
if (!callback && (args instanceof Function)) {
callback = args;
args = {};
}
var opts = level && {'qs': {'level': level}};
var opts = { 'qs': args };
return prototype.get.call(
self, ['products', productID, 'book'], opts, callback);
self, ['products', self.productID, 'book'], opts, callback);
};
prototype.getProductTicker = function(productID, callback) {
prototype.getProductTicker = function(callback) {
var self = this;
return prototype.get.call(self, ['products', productID, 'ticker'], callback);
return prototype.get.call(self, ['products', self.productID, 'ticker'], callback);
};
prototype.getProductTrades = function(productID, callback) {
prototype.getProductTrades = function(args, callback) {
var self = this;
return prototype.get.call(self, ['products', productID, 'trades'], callback);
var opts = {'qs': args};
return prototype.get.call(self, ['products', self.productID, 'trades'], opts, callback);
};
prototype.getProductHistoricRates = function(productID, callback) {
prototype.getProductHistoricRates = function(args, callback) {
var self = this;
return prototype.get.call(self, ['products', productID, 'candles'], callback);
args = args || {}
if (!callback && (args instanceof Function)) {
callback = args;
args = {};
}
var opts = {'qs': args};
return prototype.get.call(self, ['products', self.productID, 'candles'], opts, callback);
};
prototype.getProduct24HrStats = function(productID, callback) {
prototype.getProduct24HrStats = function(callback) {
var self = this;
return prototype.get.call(self, ['products', productID, 'stats'], callback);
return prototype.get.call(self, ['products', self.productID, 'stats'], callback);
};

@@ -117,4 +129,4 @@

};
};
});
module.exports = exports = PublicClient;

@@ -1,199 +0,168 @@

var EventEmitter = require('events').EventEmitter;
var util = require('util');
var RBTree = require('bintrees').RBTree;
var num = require('num');
var assert = require('assert');
var _ = {assign: require('lodash.assign')}
var WebSocket = require('ws');
var _ = {
'forEach': require('lodash.foreach'),
'assign': require('lodash.assign'),
};
var request = require('request');
var Orderbook = function() {
var self = this;
// Orders hashed by ID
self._ordersByID = {};
var OrderBook = function(productID, websocketURI) {
var self = this;
EventEmitter.call(self);
self._bids = new RBTree(function(a, b) {
return a.price.cmp(b.price);
});
self.productID = productID || 'BTC-USD';
self.websocketURI = websocketURI || 'wss://ws-feed.exchange.coinbase.com';
self.state = self.STATES.closed;
self.queue = [];
self.book = {
'sequence': -1,
'bids': {},
'asks': {},
};
self.connect();
self._asks = new RBTree(function(a, b) {
return a.price.cmp(b.price);
});
};
util.inherits(OrderBook, EventEmitter);
_.assign(OrderBook.prototype, new function() {
_.assign(Orderbook.prototype, new function() {
var prototype = this;
prototype.STATES = {
'closed': 'closed',
'open': 'open',
'syncing': 'syncing',
'processing': 'processing',
'error': 'error',
prototype._getTree = function(side) {
return side == 'buy' ? this._bids : this._asks;
};
prototype.connect = function() {
prototype.state = function(book) {
var self = this;
if (self.socket) {
self.socket.close();
}
self.socket = new WebSocket(self.websocketURI);
self.socket.on('message', self.onMessage.bind(self));
self.socket.on('open', self.onOpen.bind(self));
self.socket.on('close', self.onClose.bind(self));
};
prototype.disconnect = function() {
var self = this;
if (!self.socket) {
throw "Could not disconnect (not connected)"
}
self.socket.close();
self.onClose();
};
if (book) {
prototype.changeState = function(stateName) {
var self = this;
var newState = self.STATES[stateName];
if (newState === undefined) {
throw "Unrecognized state: " + stateName;
}
var oldState = self.state;
self.state = newState;
if (self.state === self.STATES.error) {
self.socket.close();
};
self.emit('statechange', {'old': oldState, 'new': newState});
};
book.bids.forEach(function(order) {
order = {
id: order[2],
side: 'buy',
price: num(order[0]),
size: num(order[1])
}
self.add(order);
});
prototype.onOpen = function() {
var self = this;
self.changeState(self.STATES.open);
self.sync();
};
book.asks.forEach(function(order) {
order = {
id: order[2],
side: 'sell',
price: num(order[0]),
size: num(order[1])
}
self.add(order);
});
prototype.onClose = function() {
var self = this;
self.changeState(self.STATES.closed);
};
} else {
prototype.onMessage = function(datastr) {
var self = this;
var data = JSON.parse(datastr);
if (self.state !== self.STATES.processing) {
self.queue.push(data);
} else {
self.processMessage(data);
book = {
asks: [],
bids: []
};
self._bids.reach(function(bid) {
bid.orders.forEach(function(order) {
book.bids.push(order);
});
});
self._asks.each(function(ask) {
ask.orders.forEach(function(order) {
book.asks.push(order);
});
});
return book;
}
};
prototype.sync = function() {
var self = this;
self.changeState(self.STATES.syncing);
var subscribeMessage = {
'type': 'subscribe',
'product_id': self.productID,
};
self.socket.send(JSON.stringify(subscribeMessage));
self.loadSnapshot();
prototype.get = function(orderId) {
return this._ordersByID[orderId]
};
prototype.loadSnapshot = function(snapshotData) {
prototype.add = function(order) {
var self = this;
var load = function(data) {
var i;
var convertSnapshotArray = function(array) {
return {'price': array[0], 'size': array[1], 'id': array[2]}
};
for (i = 0; data.bids && i < data.bids.length; i++) {
bid = convertSnapshotArray(data.bids[i]);
self.book.bids[bid.id] = bid;
};
for (i = 0; data.asks && i < data.asks.length; i++) {
ask = convertSnapshotArray(data.asks[i]);
self.book.asks[ask.id] = ask;
};
self.book.sequence = data.sequence
_.forEach(self.queue, self.processMessage.bind(self));
self.queue = [];
self.changeState(self.STATES.processing);
order = {
id: order.order_id || order.id,
side: order.side,
price: num(order.price),
size: num(order.size || order.remaining_size),
};
if (snapshotData) {
load(data);
} else {
request({
'url': 'https://api.exchange.coinbase.com/products/BTC-USD/book?level=3',
'headers': {'User-Agent': 'coinbase-node-client'},
}, function(err, response, body) {
if (err) {
self.changeState(self.STATES.error);
throw "Failed to load snapshot: " + err;
}
if (response.statusCode !== 200) {
self.changeState(self.STATES.error);
throw "Failed to load snapshot: " + response.statusCode;
}
load(JSON.parse(body));
});
var tree = self._getTree(order.side);
var node = tree.find({price: order.price});
if (!node) {
node = {
price: order.price,
orders: []
}
tree.insert(node);
}
node.orders.push(order);
self._ordersByID[order.id] = order;
};
prototype.processMessage = function(message) {
prototype.remove = function(orderId) {
var self = this;
if (message.sequence <= self.book.sequence) {
self.emit('ignored', message);
var order = self.get(orderId);
if (!order) {
return;
}
if (message.sequence != self.book.sequence + 1) {
self.changeState(self.STATES.error);
throw "Received message out of order: " + message.sequence;
var tree = self._getTree(order.side);
var node = tree.find({price: order.price});
assert(node);
var orders = node.orders;
orders.splice(orders.indexOf(order), 1);
if (orders.length === 0) {
tree.remove(node);
}
self.book.sequence = message.sequence;
if (message.type === 'open' ||
message.type === 'received' ||
message.type === 'change') {
var dataset = message.side === 'buy' ? self.book.bids : self.book.asks;
var newData = {
'size': message.size || message.remaining_size || message.new_size,
'price': message.price,
'id': message.order_id,
};
dataset[newData.id] = newData;
} else if (message.type === 'match') {
var makerDataset = message.side === 'buy' ? self.book.bids : self.book.asks;
var takerDataset = message.side === 'buy' ? self.book.asks : self.book.bids;
var makerSize = makerDataset[message.maker_order_id].size;
var takerSize = takerDataset[message.taker_order_id].size;
makerDataset[message.maker_order_id].size = '' + (makerSize - message.size);
takerDataset[message.taker_order_id].size = '' + (takerSize - message.size);
delete self._ordersByID[order.id];
};
} else if (message.type === 'done') {
var dataset = message.side === 'buy' ? self.book.bids : self.book.asks;
var id = message.order_id;
delete dataset[id];
prototype.match = function(match) {
var self = this;
} else if (message.type === 'error') {
self.changeState(self.STATES.error);
self.emit(message.type, message);
throw "Received error: " + message.message
var size = num(match.size);
var price = num(match.price);
var tree = self._getTree(match.side);
var node = tree.find({price: price});
assert(node);
} else {
self.changeState(self.STATES.error);
self.emit('unknown', data);
throw "Received unknown message type: " + message.type;
var order = node.orders[0];
assert.equal(order.id, match.maker_order_id);
order.size = order.size.sub(size);
self._ordersByID[order.id] = order;
assert(order.size >= 0);
if (order.size.eq(0)) {
self.remove(order.id);
}
};
self.emit(message.type, message);
prototype.change = function(change) {
var self = this;
var size = num(change.new_size);
var price = num(change.price);
var order = self.get(change.order_id)
var tree = self._getTree(change.side);
var node = tree.find({price: price});
assert(node);
var nodeOrder = node.orders[node.orders.indexOf(order)];
assert.equal(order.size, change.old_size);
nodeOrder.size = size;
self._ordersByID[nodeOrder.id] = nodeOrder;
};
});
module.exports = exports = OrderBook;
module.exports = exports = Orderbook;
{
"name": "coinbase-exchange",
"version": "0.1.0",
"version": "0.2.0",
"author": "Coinbase",

@@ -9,3 +9,6 @@ "bugs": "https://github.com/coinbase/coinbase-exchange-node/issues",

"name": "Peter Downs",
"url": "http://peterdowns.com" }
"url": "http://peterdowns.com" },
{ "email": "maksimus16@gmail.com",
"name": "Maksim Stepanenko",
"url": "http://maksim.ms" }
],

@@ -17,6 +20,10 @@ "dependencies": {

"request": "2.51.0",
"ws": "0.7.0"
"ws": "0.7.0",
"num": "0.2.1",
"bintrees": "1.0.0"
},
"description": "Client for the Coinbase Exchange API",
"devDependencies": {},
"devDependencies": {
"mocha": "1.20.1"
},
"directories": {

@@ -41,3 +48,5 @@ "lib": "./lib"

},
"scripts": {}
"scripts": {
"test": "mocha --ui qunit --bail --reporter list tests/*.js"
}
}

@@ -70,5 +70,5 @@ # Coinbase Exchange

// Get the order book at the default level of detail.
publicClient.getProductOrderBook('BTC-USD', callback);
publicClient.getProductOrderBook(callback);
// Get the order book at a specific level of detail.
publicClient.getProductOrderBook('BTC-USD', 3, callback);
publicClient.getProductOrderBook({'level': 3}, callback);
```

@@ -78,3 +78,3 @@

```javascript
publicClient.getProductTicker('BTC-USD', callback);
publicClient.getProductTicker(callback);
```

@@ -84,3 +84,5 @@

```javascript
publicClient.getProductTrades('BTC-USD', callback);
publicClient.getProductTrades(callback);
// To make paginated requests, include page parameters
publicClient.getProductTrades({'after': 1000}, callback);
```

@@ -90,3 +92,5 @@

```javascript
publicClient.getProductHistoricRates('BTC-USD', callback);
publicClient.getProductHistoricRates(callback);
// To include extra parameters:
publicClient.getProductHistoricRates({'granularity': 3000}, callback);
```

@@ -96,3 +100,3 @@

```javascript
publicClient.getProduct24HrStats('BTC-USD', callback);
publicClient.getProduct24HrStats(callback);
```

@@ -152,2 +156,4 @@

authedClient.getAccountHistory(accountID, callback);
// For pagination, you can include extra page arguments
authedClient.getAccountHistory(accountID, {'before': 3000}, callback);
```

@@ -159,2 +165,4 @@

authedClient.getAccountHolds(accountID, callback);
// For pagination, you can include extra page arguments
authedClient.getAccountHolds(accountID, {'before': 3000}, callback);
```

@@ -190,2 +198,4 @@

authedClient.getOrders(callback);
// For pagination, you can include extra page arguments
authedClient.getOrders({'after': 3000}, callback);
```

@@ -202,2 +212,4 @@

authedClient.getFills(callback);
// For pagination, you can include extra page arguments
authedClient.getFills({'before': 3000}, callback);
```

@@ -234,77 +246,37 @@

### The Order Book
The `OrderBook` is a local mirror of the Coinbase Exchange's order book, synced
via WebSockets.
##### Setup
### Websocket client
The `WebsocketClient` allows you to connect and listen to the
[exchange websocket messages](https://docs.exchange.coinbase.com/#messages).
```javascript
var CoinbaseExchange = require('coinbase-exchange');
var orderBook = new CoinbaseExchange.OrderBook();
var websocket = new CoinbaseExchange.WebsocketClient();
websocket.on('message', function(data) { console.log(data); });
```
The following events can be emitted from the `WebsocketClient`:
* `open`
* `message`
* `close`
##### Listening to Events
The order book is a type of
[`EventEmitter`](http://nodejs.org/api/events.html#events_events). For the
following events, the data emitted is always in the same form as the messages
received over WebSocket – you can [learn more about those message types
here](https://docs.exchange.coinbase.com/#messages).
* [`"received"`](https://docs.exchange.coinbase.com/#received)
* [`"open"`](https://docs.exchange.coinbase.com/#open)
* [`"done"`](https://docs.exchange.coinbase.com/#done)
* [`"match"`](https://docs.exchange.coinbase.com/#match)
* [`"error"`](https://docs.exchange.coinbase.com/#match)
These events are emitted immediately after the OrderBook has been updated to
include the message's contents. So by the time your code is notified, the book
will already reflect the changes described by the message.
*Example: listening to order matches:*
### Orderbook
`Orderbook` is a data structure that can be used to store a local copy of the orderbook.
```javascript
orderBook.on('match', function(message) {
console.log("Order",
message.maker_order_id,
"matched with order",
message.taker_order_id);
console.log(message.size, "BTC @", message.price, "USD");
});
var CoinbaseExchange = require('coinbase-exchange');
var orderbook = new CoinbaseExchange.Orderbook();
```
The orderbook has the following methods:
* `state(book)`
* `get(orderId)`
* `add(order)`
* `remove(orderId)`
* `match(match)`
* `change(change)`
There are other events to which you can listen:
### Orderbook Sync
`OrderbookSync` creates a local mirror of the orderbook on Coinbase Exchange using
`Orderbook` and `WebsocketClient` as described [here](https://docs.exchange.coinbase.com/#real-time-order-book).
* `"ignored"`: Emitted as part of the order book syncing process, once for
every out-of-date message that is ignored. The data is the original message
sent over the websocket, one of the types listed above.
* `"unknown"`: Emitted when a message is received with a type that the
OrderBook doesn't know how to handle. The data is the original message sent
over the websocket.
* `"statechange"`: Emitted any time the order book instance changes state. A
hash with two keys, `"old"` mapping to the previous state, `"new"` mapping to
the new, current state of the order book.
*Example: listening for all errors*
```javascript
orderBook.on('statechange', function(state) {
if (state.new === orderBook.STATES.error) {
console.log("Was", state.old, "now in state", state.new);
// clean up things here
}
});
var CoinbaseExchange = require('coinbase-exchange');
var orderbookSync = new CoinbaseExchange.OrderbookSync();
console.log(orderbookSync.book.state());
```
#### States of the order book
An instance of the order book can be in the following states:
* `"closed"`: the WebSocket connection has been closed and no new messages are
being processed.
* `"open"`: the WebSocket connection is open, but no new messages are being
processed.
* `"syncing"`: the WebSocket connection is open, new messages are being queued,
and the order book snapshot is being loaded.
* `"processing"`: the WebSocket connection is open, the order book is in sync,
and new messages are being processed as they're received.
* `"error"`: an error has occurred and an exception has been thrown. The
WebSocket connection is closed and no new messages are being received or
processed.

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc