Comparing version 1.3.7 to 1.3.8
# Changelog | ||
## 1.3.8 | ||
- Optimise local imports. | ||
- Cleaning, including indentation. | ||
- Deprecate yarn in favour of npm. | ||
- Fix tests. | ||
- Expose utility method for generating custom order ID. | ||
- Add example on providing custom order ID. | ||
- Update docs. | ||
## 1.3.7 | ||
@@ -4,0 +13,0 @@ |
module.exports = { | ||
BinanceRest: require('./rest.js'), | ||
BinanceWS: require('./ws.js'), | ||
ValueProcessor: require('./processor.js') | ||
BinanceRest: require('./rest'), | ||
BinanceWS: require('./ws'), | ||
ValueProcessor: require('./processor') | ||
}; |
832
lib/rest.js
@@ -7,493 +7,511 @@ const request = require('request'); | ||
const Beautifier = require('./beautifier.js'); | ||
const Beautifier = require('./beautifier'); | ||
class BinanceRest { | ||
constructor({ | ||
key, | ||
secret, | ||
recvWindow = false, | ||
timeout = 15000, | ||
disableBeautification = false, | ||
handleDrift = false, | ||
baseUrl = 'https://api.binance.com/', | ||
requestOptions = {} | ||
}) { | ||
this.key = key; | ||
this.secret = secret; | ||
this.recvWindow = recvWindow; | ||
this.timeout = timeout; | ||
this.disableBeautification = disableBeautification; | ||
this.handleDrift = handleDrift; | ||
this.requestOptions = requestOptions; | ||
constructor({ | ||
key, | ||
secret, | ||
recvWindow = false, | ||
timeout = 15000, | ||
disableBeautification = false, | ||
handleDrift = false, | ||
baseUrl = 'https://api.binance.com/', | ||
requestOptions = {} | ||
}) { | ||
this.key = key; | ||
this.secret = secret; | ||
this.recvWindow = recvWindow; | ||
this.timeout = timeout; | ||
this.disableBeautification = disableBeautification; | ||
this.handleDrift = handleDrift; | ||
this.requestOptions = requestOptions; | ||
this._beautifier = new Beautifier(); | ||
this._baseUrl = baseUrl; | ||
// had trailing slash if necessary | ||
if ('/' != this._baseUrl.substr(-1)) { | ||
this._baseUrl += '/'; | ||
} | ||
this._drift = 0; | ||
this._syncInterval = 0; | ||
this._beautifier = new Beautifier(); | ||
this._baseUrl = baseUrl; | ||
// had trailing slash if necessary | ||
if ('/' != this._baseUrl.substr(-1)) { | ||
this._baseUrl += '/'; | ||
} | ||
getBaseUrl() { | ||
return this._baseUrl; | ||
} | ||
this._drift = 0; | ||
this._syncInterval = 0; | ||
} | ||
_makeRequest(query, callback, route, security, method, attempt = 0) { | ||
assert( | ||
_.isUndefined(callback) || _.isFunction(callback), | ||
'callback must be a function or undefined' | ||
); | ||
assert(_.isObject(query), 'query must be an object'); | ||
getBaseUrl() { | ||
return this._baseUrl; | ||
} | ||
let queryString; | ||
const type = _.last(route.split('/')), | ||
options = Object.assign( | ||
{ | ||
url: `${this._baseUrl}${route}`, | ||
timeout: this.timeout | ||
}, | ||
this.requestOptions | ||
); | ||
_makeRequest(query, callback, route, security, method, attempt = 0) { | ||
assert( | ||
_.isUndefined(callback) || _.isFunction(callback), | ||
'callback must be a function or undefined' | ||
); | ||
assert(_.isObject(query), 'query must be an object'); | ||
if (security === 'SIGNED') { | ||
if (this.recvWindow) { | ||
query.recvWindow = this.recvWindow; | ||
} | ||
queryString = qs.stringify(query); | ||
options.url += '?' + queryString; | ||
if (options.url.substr(options.url.length - 1) !== '?') { | ||
options.url += '&'; | ||
} | ||
options.url += `signature=${this._sign(queryString)}`; | ||
} else { | ||
queryString = qs.stringify(query); | ||
if (queryString) { | ||
options.url += '?' + queryString; | ||
} | ||
let queryString; | ||
const type = _.last(route.split('/')), | ||
options = Object.assign( | ||
{ | ||
url: `${this._baseUrl}${route}`, | ||
timeout: this.timeout | ||
}, | ||
this.requestOptions | ||
); | ||
if (security === 'SIGNED') { | ||
if (this.recvWindow) { | ||
query.recvWindow = this.recvWindow; | ||
} | ||
queryString = qs.stringify(query); | ||
options.url += '?' + queryString; | ||
if (options.url.substr(options.url.length - 1) !== '?') { | ||
options.url += '&'; | ||
} | ||
options.url += `signature=${this._sign(queryString)}`; | ||
} else { | ||
queryString = qs.stringify(query); | ||
if (queryString) { | ||
options.url += '?' + queryString; | ||
} | ||
} | ||
if (security === 'API-KEY' || security === 'SIGNED') { | ||
options.headers = { 'X-MBX-APIKEY': this.key }; | ||
} | ||
if (method) { | ||
options.method = method; | ||
} | ||
const action = cb => { | ||
request(options, (err, response, body) => { | ||
let payload; | ||
try { | ||
payload = JSON.parse(body); | ||
} catch (e) { | ||
payload = body; | ||
} | ||
if (security === 'API-KEY' || security === 'SIGNED') { | ||
options.headers = { 'X-MBX-APIKEY': this.key }; | ||
} | ||
if (method) { | ||
options.method = method; | ||
} | ||
const action = cb => { | ||
request(options, (err, response, body) => { | ||
let payload; | ||
try { | ||
payload = JSON.parse(body); | ||
} catch (e) { | ||
payload = body; | ||
} | ||
if (err) { | ||
cb(err, payload); | ||
} else if ( | ||
response.statusCode < 200 || | ||
response.statusCode > 299 | ||
) { | ||
/* | ||
* If we get a response that the timestamp is ahead of the server, | ||
* calculate the drift and then attempt the request again | ||
*/ | ||
if ( | ||
response.statusCode === 400 && | ||
payload.code === -1021 && | ||
this.handleDrift && | ||
attempt === 0 | ||
) { | ||
this.calculateDrift().then(() => { | ||
query.timestamp = this._getTime() + this._drift; | ||
return this._makeRequest( | ||
query, | ||
cb, | ||
route, | ||
security, | ||
method, | ||
++attempt | ||
); | ||
}); | ||
} else { | ||
cb( | ||
new Error(`Response code ${response.statusCode}`), | ||
payload | ||
); | ||
} | ||
} else { | ||
if (_.isArray(payload)) { | ||
payload = _.map(payload, item => { | ||
return this._doBeautifications(item, type); | ||
}); | ||
} else { | ||
payload = this._doBeautifications(payload); | ||
} | ||
cb(err, payload); | ||
} | ||
if (err) { | ||
cb(err, payload); | ||
} else if ( | ||
response.statusCode < 200 || | ||
response.statusCode > 299 | ||
) { | ||
/* | ||
* If we get a response that the timestamp is ahead of the server, | ||
* calculate the drift and then attempt the request again | ||
*/ | ||
if ( | ||
response.statusCode === 400 && | ||
payload.code === -1021 && | ||
this.handleDrift && | ||
attempt === 0 | ||
) { | ||
this.calculateDrift().then(() => { | ||
query.timestamp = this._getTime() + this._drift; | ||
return this._makeRequest( | ||
query, | ||
cb, | ||
route, | ||
security, | ||
method, | ||
++attempt | ||
); | ||
}); | ||
}; | ||
if (callback) { | ||
action(callback); | ||
} else { | ||
cb( | ||
new Error(`Response code ${response.statusCode}`), | ||
payload | ||
); | ||
} | ||
} else { | ||
return new Promise((resolve, reject) => | ||
action((err, payload) => { | ||
if (err) { | ||
if (payload === undefined) { | ||
reject(err); | ||
} else { | ||
reject(payload); | ||
} | ||
} else { | ||
resolve(payload); | ||
} | ||
}) | ||
); | ||
if (_.isArray(payload)) { | ||
payload = _.map(payload, item => { | ||
return this._doBeautifications(item, type); | ||
}); | ||
} else { | ||
payload = this._doBeautifications(payload); | ||
} | ||
cb(err, payload); | ||
} | ||
}); | ||
}; | ||
if (callback) { | ||
action(callback); | ||
} else { | ||
return new Promise((resolve, reject) => | ||
action((err, payload) => { | ||
if (err) { | ||
if (payload === undefined) { | ||
reject(err); | ||
} else { | ||
reject(payload); | ||
} | ||
} else { | ||
resolve(payload); | ||
} | ||
}) | ||
); | ||
} | ||
} | ||
_doBeautifications(response, route) { | ||
if (this.disableBeautification) { | ||
return response; | ||
} | ||
return this._beautifier.beautify(response, route); | ||
_doBeautifications(response, route) { | ||
if (this.disableBeautification) { | ||
return response; | ||
} | ||
return this._beautifier.beautify(response, route); | ||
} | ||
_sign(queryString) { | ||
return crypto | ||
.createHmac('sha256', this.secret) | ||
.update(queryString) | ||
.digest('hex'); | ||
} | ||
_sign(queryString) { | ||
return crypto | ||
.createHmac('sha256', this.secret) | ||
.update(queryString) | ||
.digest('hex'); | ||
} | ||
_setTimestamp(query) { | ||
if (!query.timestamp) { | ||
query.timestamp = this._getTime() + this._drift; | ||
} | ||
_setTimestamp(query) { | ||
if (!query.timestamp) { | ||
query.timestamp = this._getTime() + this._drift; | ||
} | ||
} | ||
_getTime() { | ||
return new Date().getTime(); | ||
} | ||
_getTime() { | ||
return new Date().getTime(); | ||
} | ||
calculateDrift() { | ||
const systemTime = this._getTime(); | ||
return this.time().then(response => { | ||
// Calculate the approximate trip time from here to binance | ||
const transitTime = parseInt((this._getTime() - systemTime) / 2); | ||
this._drift = response.serverTime - (systemTime + transitTime); | ||
}); | ||
generateNewOrderId() { | ||
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; | ||
let randomstring = 'x-U5D79M5B'; | ||
for (let i = 0; i < 20; i++) { | ||
const rnum = Math.floor(Math.random() * chars.length); | ||
randomstring += chars.substring(rnum, rnum + 1); | ||
} | ||
return randomstring; | ||
} | ||
startTimeSync(interval = 300000, onRecalculateDriftCb) { | ||
return new Promise((resolve, reject) => { | ||
// If there's already an interval running, clear it and reset values | ||
if (this._syncInterval !== 0) { | ||
this.endTimeSync(); | ||
return resolve(); | ||
} | ||
calculateDrift() { | ||
const systemTime = this._getTime(); | ||
return this.time().then(response => { | ||
// Calculate the approximate trip time from here to binance | ||
const transitTime = parseInt((this._getTime() - systemTime) / 2); | ||
this._drift = response.serverTime - (systemTime + transitTime); | ||
}); | ||
} | ||
// Calculate initial drift value and setup interval to periodically update it | ||
this.calculateDrift() | ||
.then(resolve) | ||
.catch(reject); | ||
startTimeSync(interval = 300000, onRecalculateDriftCb) { | ||
return new Promise((resolve, reject) => { | ||
// If there's already an interval running, clear it and reset values | ||
if (this._syncInterval !== 0) { | ||
this.endTimeSync(); | ||
return resolve(); | ||
} | ||
this._syncInterval = setInterval(() => { | ||
const promise = this.calculateDrift(); | ||
// Calculate initial drift value and setup interval to periodically update it | ||
this.calculateDrift() | ||
.then(resolve) | ||
.catch(reject); | ||
if (_.isFunction(onRecalculateDriftCb)) { | ||
onRecalculateDriftCb(promise); | ||
} | ||
}, interval); | ||
}); | ||
} | ||
this._syncInterval = setInterval(() => { | ||
const promise = this.calculateDrift(); | ||
endTimeSync() { | ||
clearInterval(this._syncInterval); | ||
this._drift = 0; | ||
this._syncInterval = 0; | ||
} | ||
if (_.isFunction(onRecalculateDriftCb)) { | ||
onRecalculateDriftCb(promise); | ||
} | ||
}, interval); | ||
}); | ||
} | ||
// Public APIs | ||
ping(callback) { | ||
return this._makeRequest({}, callback, 'api/v1/ping'); | ||
} | ||
endTimeSync() { | ||
clearInterval(this._syncInterval); | ||
this._drift = 0; | ||
this._syncInterval = 0; | ||
} | ||
time(callback) { | ||
return this._makeRequest({}, callback, 'api/v1/time'); | ||
} | ||
// Public APIs | ||
ping(callback) { | ||
return this._makeRequest({}, callback, 'api/v1/ping'); | ||
} | ||
depth(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
time(callback) { | ||
return this._makeRequest({}, callback, 'api/v1/time'); | ||
} | ||
return this._makeRequest(query, callback, 'api/v1/depth'); | ||
depth(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
trades(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
return this._makeRequest(query, callback, 'api/v1/depth'); | ||
} | ||
return this._makeRequest(query, callback, 'api/v1/trades'); | ||
trades(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
historicalTrades(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
return this._makeRequest(query, callback, 'api/v1/trades'); | ||
} | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v1/historicalTrades', | ||
'API-KEY' | ||
); | ||
historicalTrades(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
aggTrades(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v1/historicalTrades', | ||
'API-KEY' | ||
); | ||
} | ||
return this._makeRequest(query, callback, 'api/v1/aggTrades'); | ||
aggTrades(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
exchangeInfo(callback) { | ||
return this._makeRequest({}, callback, 'api/v1/exchangeInfo'); | ||
} | ||
return this._makeRequest(query, callback, 'api/v1/aggTrades'); | ||
} | ||
klines(query = {}, callback) { | ||
return this._makeRequest(query, callback, 'api/v1/klines'); | ||
} | ||
exchangeInfo(callback) { | ||
return this._makeRequest({}, callback, 'api/v1/exchangeInfo'); | ||
} | ||
ticker24hr(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
klines(query = {}, callback) { | ||
return this._makeRequest(query, callback, 'api/v1/klines'); | ||
} | ||
return this._makeRequest(query, callback, 'api/v1/ticker/24hr'); | ||
ticker24hr(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
tickerPrice(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
return this._makeRequest(query, callback, 'api/v1/ticker/24hr'); | ||
} | ||
return this._makeRequest(query, callback, 'api/v3/ticker/price'); | ||
tickerPrice(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
bookTicker(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
return this._makeRequest(query, callback, 'api/v3/ticker/price'); | ||
} | ||
return this._makeRequest(query, callback, 'api/v3/ticker/bookTicker'); | ||
bookTicker(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
allBookTickers(callback) { | ||
return this._makeRequest({}, callback, 'api/v1/ticker/allBookTickers'); | ||
} | ||
return this._makeRequest(query, callback, 'api/v3/ticker/bookTicker'); | ||
} | ||
allPrices(callback) { | ||
return this._makeRequest({}, callback, 'api/v1/ticker/allPrices'); | ||
} | ||
allBookTickers(callback) { | ||
return this._makeRequest({}, callback, 'api/v1/ticker/allBookTickers'); | ||
} | ||
// Private APIs | ||
newOrder(query = {}, callback) { | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v3/order', | ||
'SIGNED', | ||
'POST' | ||
); | ||
} | ||
allPrices(callback) { | ||
return this._makeRequest({}, callback, 'api/v1/ticker/allPrices'); | ||
} | ||
testOrder(query = {}, callback) { | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v3/order/test', | ||
'SIGNED', | ||
'POST' | ||
); | ||
// Private APIs | ||
newOrder(query = {}, callback) { | ||
this._setTimestamp(query); | ||
if (!query.newClientOrderId) { | ||
query.newClientOrderId = this.generateNewOrderId(); | ||
} | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v3/order', | ||
'SIGNED', | ||
'POST' | ||
); | ||
} | ||
queryOrder(query = {}, callback) { | ||
this._setTimestamp(query); | ||
return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED'); | ||
testOrder(query = {}, callback) { | ||
this._setTimestamp(query); | ||
if (!query.newClientOrderId) { | ||
query.newClientOrderId = this.generateNewOrderId(); | ||
} | ||
console.log('query: ', query); | ||
cancelOrder(query = {}, callback) { | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v3/order', | ||
'SIGNED', | ||
'DELETE' | ||
); | ||
} | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v3/order/test', | ||
'SIGNED', | ||
'POST' | ||
); | ||
} | ||
openOrders(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v3/openOrders', | ||
'SIGNED' | ||
); | ||
} | ||
queryOrder(query = {}, callback) { | ||
this._setTimestamp(query); | ||
return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED'); | ||
} | ||
allOrders(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest(query, callback, 'api/v3/allOrders', 'SIGNED'); | ||
} | ||
cancelOrder(query = {}, callback) { | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v3/order', | ||
'SIGNED', | ||
'DELETE' | ||
); | ||
} | ||
account(query = {}, callback) { | ||
if (_.isFunction(query)) { | ||
callback = query; | ||
query = {}; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest(query, callback, 'api/v3/account', 'SIGNED'); | ||
openOrders(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v3/openOrders', | ||
'SIGNED' | ||
); | ||
} | ||
myTrades(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest(query, callback, 'api/v3/myTrades', 'SIGNED'); | ||
allOrders(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest(query, callback, 'api/v3/allOrders', 'SIGNED'); | ||
} | ||
withdraw(query = {}, callback) { | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'wapi/v3/withdraw.html', | ||
'SIGNED', | ||
'POST' | ||
); | ||
account(query = {}, callback) { | ||
if (_.isFunction(query)) { | ||
callback = query; | ||
query = {}; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest(query, callback, 'api/v3/account', 'SIGNED'); | ||
} | ||
depositHistory(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { asset: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'wapi/v3/depositHistory.html', | ||
'SIGNED' | ||
); | ||
myTrades(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { symbol: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest(query, callback, 'api/v3/myTrades', 'SIGNED'); | ||
} | ||
withdrawHistory(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { asset: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'wapi/v3/withdrawHistory.html', | ||
'SIGNED' | ||
); | ||
} | ||
withdraw(query = {}, callback) { | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'wapi/v3/withdraw.html', | ||
'SIGNED', | ||
'POST' | ||
); | ||
} | ||
depositAddress(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { asset: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'wapi/v3/depositAddress.html', | ||
'SIGNED' | ||
); | ||
depositHistory(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { asset: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'wapi/v3/depositHistory.html', | ||
'SIGNED' | ||
); | ||
} | ||
depositAddressWithNetwork(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { asset: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'sapi/v1/capital/deposit/hisrec', | ||
'SIGNED' | ||
); | ||
withdrawHistory(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { asset: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'wapi/v3/withdrawHistory.html', | ||
'SIGNED' | ||
); | ||
} | ||
allCoinsInformation(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { asset: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'sapi/v1/capital/config/getall', | ||
'SIGNED' | ||
); | ||
depositAddress(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { asset: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'wapi/v3/depositAddress.html', | ||
'SIGNED' | ||
); | ||
} | ||
accountStatus(callback) { | ||
const query = {}; | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'wapi/v3/accountStatus.html', | ||
'SIGNED' | ||
); | ||
depositAddressWithNetwork(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { asset: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'sapi/v1/capital/deposit/hisrec', | ||
'SIGNED' | ||
); | ||
} | ||
startUserDataStream(callback) { | ||
return this._makeRequest( | ||
{}, | ||
callback, | ||
'api/v1/userDataStream', | ||
'API-KEY', | ||
'POST' | ||
); | ||
allCoinsInformation(query = {}, callback) { | ||
if (_.isString(query)) { | ||
query = { asset: query }; | ||
} | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'sapi/v1/capital/config/getall', | ||
'SIGNED' | ||
); | ||
} | ||
keepAliveUserDataStream(query = {}, callback) { | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v1/userDataStream', | ||
'API-KEY', | ||
'PUT' | ||
); | ||
} | ||
accountStatus(callback) { | ||
const query = {}; | ||
this._setTimestamp(query); | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'wapi/v3/accountStatus.html', | ||
'SIGNED' | ||
); | ||
} | ||
closeUserDataStream(query = {}, callback) { | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v1/userDataStream', | ||
'API-KEY', | ||
'DELETE' | ||
); | ||
} | ||
startUserDataStream(callback) { | ||
return this._makeRequest( | ||
{}, | ||
callback, | ||
'api/v1/userDataStream', | ||
'API-KEY', | ||
'POST' | ||
); | ||
} | ||
keepAliveUserDataStream(query = {}, callback) { | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v1/userDataStream', | ||
'API-KEY', | ||
'PUT' | ||
); | ||
} | ||
closeUserDataStream(query = {}, callback) { | ||
return this._makeRequest( | ||
query, | ||
callback, | ||
'api/v1/userDataStream', | ||
'API-KEY', | ||
'DELETE' | ||
); | ||
} | ||
} | ||
module.exports = BinanceRest; |
274
lib/ws.js
const WebSocket = require('ws'); | ||
const _ = require('underscore'); | ||
const Beautifier = require('./beautifier.js'); | ||
const Beautifier = require('./beautifier'); | ||
const BinanceErrors = Object.freeze({ | ||
INVALID_LISTEN_KEY: -1125 | ||
INVALID_LISTEN_KEY: -1125 | ||
}); | ||
class BinanceWS { | ||
constructor(beautify = true) { | ||
this._baseUrl = 'wss://stream.binance.com:9443/ws/'; | ||
this._combinedBaseUrl = 'wss://stream.binance.com:9443/stream?streams='; | ||
this._sockets = {}; | ||
this._beautifier = new Beautifier(); | ||
this._beautify = beautify; | ||
constructor(beautify = true) { | ||
this._baseUrl = 'wss://stream.binance.com:9443/ws/'; | ||
this._combinedBaseUrl = 'wss://stream.binance.com:9443/stream?streams='; | ||
this._sockets = {}; | ||
this._beautifier = new Beautifier(); | ||
this._beautify = beautify; | ||
this.streams = { | ||
depth: symbol => `${symbol.toLowerCase()}@depth`, | ||
depthLevel: (symbol, level) => | ||
`${symbol.toLowerCase()}@depth${level}`, | ||
kline: (symbol, interval) => | ||
`${symbol.toLowerCase()}@kline_${interval}`, | ||
aggTrade: symbol => `${symbol.toLowerCase()}@aggTrade`, | ||
trade: symbol => `${symbol.toLowerCase()}@trade`, | ||
ticker: symbol => `${symbol.toLowerCase()}@ticker`, | ||
allTickers: () => '!ticker@arr' | ||
}; | ||
this.streams = { | ||
depth: symbol => `${symbol.toLowerCase()}@depth`, | ||
depthLevel: (symbol, level) => | ||
`${symbol.toLowerCase()}@depth${level}`, | ||
kline: (symbol, interval) => | ||
`${symbol.toLowerCase()}@kline_${interval}`, | ||
aggTrade: symbol => `${symbol.toLowerCase()}@aggTrade`, | ||
trade: symbol => `${symbol.toLowerCase()}@trade`, | ||
ticker: symbol => `${symbol.toLowerCase()}@ticker`, | ||
allTickers: () => '!ticker@arr' | ||
}; | ||
// Reference to the setInterval timer for sending keep alive requests in onUserData | ||
this._userDataRefresh = { | ||
intervaId: false, | ||
failCount: 0 | ||
}; | ||
// Reference to the setInterval timer for sending keep alive requests in onUserData | ||
this._userDataRefresh = { | ||
intervaId: false, | ||
failCount: 0 | ||
}; | ||
} | ||
_setupWebSocket(eventHandler, path, isCombined) { | ||
if (this._sockets[path]) { | ||
return this._sockets[path]; | ||
} | ||
path = (isCombined ? this._combinedBaseUrl : this._baseUrl) + path; | ||
const ws = new WebSocket(path); | ||
_setupWebSocket(eventHandler, path, isCombined) { | ||
if (this._sockets[path]) { | ||
return this._sockets[path]; | ||
ws.on('message', message => { | ||
let event; | ||
try { | ||
event = JSON.parse(message); | ||
} catch (e) { | ||
event = message; | ||
} | ||
if (this._beautify) { | ||
if (event.stream) { | ||
event.data = this._beautifyResponse(event.data); | ||
} else { | ||
event = this._beautifyResponse(event); | ||
} | ||
path = (isCombined ? this._combinedBaseUrl : this._baseUrl) + path; | ||
const ws = new WebSocket(path); | ||
} | ||
ws.on('message', message => { | ||
let event; | ||
try { | ||
event = JSON.parse(message); | ||
} catch (e) { | ||
event = message; | ||
} | ||
if (this._beautify) { | ||
if (event.stream) { | ||
event.data = this._beautifyResponse(event.data); | ||
} else { | ||
event = this._beautifyResponse(event); | ||
} | ||
} | ||
eventHandler(event); | ||
}); | ||
eventHandler(event); | ||
}); | ||
ws.on('error', () => { | ||
// node.js EventEmitters will throw and then exit if no error listener is registered | ||
}); | ||
ws.on('error', () => { | ||
// node.js EventEmitters will throw and then exit if no error listener is registered | ||
}); | ||
return ws; | ||
} | ||
return ws; | ||
_beautifyResponse(data) { | ||
if (_.isArray(data)) { | ||
return _.map(data, event => { | ||
if (event.e) { | ||
return this._beautifier.beautify(event, event.e + 'Event'); | ||
} | ||
return event; | ||
}); | ||
} else if (data.e) { | ||
return this._beautifier.beautify(data, data.e + 'Event'); | ||
} | ||
return data; | ||
} | ||
_beautifyResponse(data) { | ||
if (_.isArray(data)) { | ||
return _.map(data, event => { | ||
if (event.e) { | ||
return this._beautifier.beautify(event, event.e + 'Event'); | ||
} | ||
return event; | ||
}); | ||
} else if (data.e) { | ||
return this._beautifier.beautify(data, data.e + 'Event'); | ||
} | ||
return data; | ||
_clearUserDataInterval() { | ||
if (this._userDataRefresh.intervaId) { | ||
clearInterval(this._userDataRefresh.intervaId); | ||
} | ||
_clearUserDataInterval() { | ||
if (this._userDataRefresh.intervaId) { | ||
clearInterval(this._userDataRefresh.intervaId); | ||
} | ||
this._userDataRefresh.intervaId = false; | ||
this._userDataRefresh.failCount = 0; | ||
} | ||
this._userDataRefresh.intervaId = false; | ||
this._userDataRefresh.failCount = 0; | ||
} | ||
_sendUserDataKeepAlive(binanceRest, response) { | ||
return binanceRest.keepAliveUserDataStream(response).catch(e => { | ||
this._userDataRefresh.failCount++; | ||
const msg = | ||
'Failed requesting keepAliveUserDataStream for onUserData listener'; | ||
if (e && e.code === BinanceErrors.INVALID_LISTEN_KEY) { | ||
console.error( | ||
new Date(), | ||
msg, | ||
'listen key expired - clearing keepAlive interval', | ||
e | ||
); | ||
this._clearUserDataInterval(); | ||
return; | ||
} | ||
console.error( | ||
new Date(), | ||
msg, | ||
'failCount: ', | ||
this._userDataRefresh.failCount, | ||
e | ||
); | ||
}); | ||
} | ||
_sendUserDataKeepAlive(binanceRest, response) { | ||
return binanceRest.keepAliveUserDataStream(response).catch(e => { | ||
this._userDataRefresh.failCount++; | ||
const msg = | ||
'Failed requesting keepAliveUserDataStream for onUserData listener'; | ||
if (e && e.code === BinanceErrors.INVALID_LISTEN_KEY) { | ||
console.error( | ||
new Date(), | ||
msg, | ||
'listen key expired - clearing keepAlive interval', | ||
e | ||
); | ||
this._clearUserDataInterval(); | ||
return; | ||
} | ||
console.error( | ||
new Date(), | ||
msg, | ||
'failCount: ', | ||
this._userDataRefresh.failCount, | ||
e | ||
); | ||
}); | ||
} | ||
onDepthUpdate(symbol, eventHandler) { | ||
return this._setupWebSocket(eventHandler, this.streams.depth(symbol)); | ||
} | ||
onDepthUpdate(symbol, eventHandler) { | ||
return this._setupWebSocket(eventHandler, this.streams.depth(symbol)); | ||
} | ||
onDepthLevelUpdate(symbol, level, eventHandler) { | ||
return this._setupWebSocket( | ||
eventHandler, | ||
this.streams.depthLevel(symbol, level) | ||
); | ||
} | ||
onDepthLevelUpdate(symbol, level, eventHandler) { | ||
return this._setupWebSocket( | ||
eventHandler, | ||
this.streams.depthLevel(symbol, level) | ||
); | ||
} | ||
onKline(symbol, interval, eventHandler) { | ||
return this._setupWebSocket( | ||
eventHandler, | ||
this.streams.kline(symbol, interval) | ||
); | ||
} | ||
onKline(symbol, interval, eventHandler) { | ||
return this._setupWebSocket( | ||
eventHandler, | ||
this.streams.kline(symbol, interval) | ||
); | ||
} | ||
onAggTrade(symbol, eventHandler) { | ||
return this._setupWebSocket( | ||
eventHandler, | ||
this.streams.aggTrade(symbol) | ||
); | ||
} | ||
onAggTrade(symbol, eventHandler) { | ||
return this._setupWebSocket( | ||
eventHandler, | ||
this.streams.aggTrade(symbol) | ||
); | ||
} | ||
onTrade(symbol, eventHandler) { | ||
return this._setupWebSocket(eventHandler, this.streams.trade(symbol)); | ||
} | ||
onTrade(symbol, eventHandler) { | ||
return this._setupWebSocket(eventHandler, this.streams.trade(symbol)); | ||
} | ||
onTicker(symbol, eventHandler) { | ||
return this._setupWebSocket(eventHandler, this.streams.ticker(symbol)); | ||
} | ||
onTicker(symbol, eventHandler) { | ||
return this._setupWebSocket(eventHandler, this.streams.ticker(symbol)); | ||
} | ||
onAllTickers(eventHandler) { | ||
return this._setupWebSocket(eventHandler, this.streams.allTickers()); | ||
} | ||
onAllTickers(eventHandler) { | ||
return this._setupWebSocket(eventHandler, this.streams.allTickers()); | ||
} | ||
onUserData(binanceRest, eventHandler, interval = 60000) { | ||
this._clearUserDataInterval(); | ||
return binanceRest.startUserDataStream().then(response => { | ||
this._userDataRefresh.intervaId = setInterval( | ||
() => this._sendUserDataKeepAlive(binanceRest, response), | ||
interval | ||
); | ||
this._userDataRefresh.failCount = 0; | ||
onUserData(binanceRest, eventHandler, interval = 60000) { | ||
this._clearUserDataInterval(); | ||
return binanceRest.startUserDataStream().then(response => { | ||
this._userDataRefresh.intervaId = setInterval( | ||
() => this._sendUserDataKeepAlive(binanceRest, response), | ||
interval | ||
); | ||
this._userDataRefresh.failCount = 0; | ||
return this._setupWebSocket(eventHandler, response.listenKey); | ||
}); | ||
} | ||
return this._setupWebSocket(eventHandler, response.listenKey); | ||
}); | ||
} | ||
onCombinedStream(streams, eventHandler) { | ||
return this._setupWebSocket(eventHandler, streams.join('/'), true); | ||
} | ||
onCombinedStream(streams, eventHandler) { | ||
return this._setupWebSocket(eventHandler, streams.join('/'), true); | ||
} | ||
} | ||
module.exports = BinanceWS; |
128
package.json
{ | ||
"name": "binance", | ||
"version": "1.3.7", | ||
"description": "node.js wrapper for the Binance REST and WebSocket APIs", | ||
"main": "./lib/binance.js", | ||
"files": [ | ||
"lib/*" | ||
], | ||
"scripts": { | ||
"test": "mocha", | ||
"cover": "nyc --reporter=html --reporter=text mocha", | ||
"coveralls": "nyc report --reporter=text-lcov | coveralls", | ||
"lint": "eslint lib test util && prettier --check lib/**/* test/**/* util/**/*", | ||
"lintfix": "eslint lib test util --fix && prettier --write lib/**/* test/**/* util/**/*", | ||
"precommit": "yarn test && yarn lint" | ||
}, | ||
"dependencies": { | ||
"bignumber.js": "^9.0.0", | ||
"request": "^2.88.2", | ||
"underscore": "^1.8.3", | ||
"ws": "^3.3.1" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/tiagosiebler/binance" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/tiagosiebler/binance/issues" | ||
}, | ||
"homepage": "https://github.com/tiagosiebler/binance#readme", | ||
"keywords": [ | ||
"api", | ||
"bitcoin", | ||
"ethereum", | ||
"cryptocurrency", | ||
"binance", | ||
"btc", | ||
"eth" | ||
], | ||
"author": "Zoey Garvey", | ||
"contributors": [ | ||
"Clifford Roche <clifford.roche@gmail.com> (http://www.cliffordroche.ca)", | ||
"Aslam Hadi H <aslamhadi@gmail.com> (https://www.commitcode.com)", | ||
"Andrey Vorobyov <vorandrew@gmail.com>", | ||
"Gavy Aggarwal (http://gavyaggarwal.com/)", | ||
"Tiago Siebler (https://github.com/tiagosiebler)", | ||
"Tony Pettigrew (https://github.com/NeverEnder4)", | ||
"Chris <apexearth@gmail.com> (https://github.com/apexearth)" | ||
], | ||
"funding": { | ||
"type": "individual", | ||
"url": "https://github.com/sponsors/tiagosiebler" | ||
}, | ||
"license": "MIT", | ||
"devDependencies": { | ||
"chai": "^4.1.1", | ||
"coveralls": "^3.0.9", | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.10.0", | ||
"mocha": "^7.1.0", | ||
"mock-require": "^2.0.2", | ||
"nyc": "^15.0.0", | ||
"prettier": "^1.19.1", | ||
"sinon": "^9.0.1" | ||
} | ||
"name": "binance", | ||
"version": "1.3.8", | ||
"description": "node.js wrapper for the Binance REST and WebSocket APIs", | ||
"main": "./lib/binance.js", | ||
"files": [ | ||
"lib/*" | ||
], | ||
"scripts": { | ||
"test": "npx mocha", | ||
"cover": "nyc --reporter=html --reporter=text mocha", | ||
"coveralls": "nyc report --reporter=text-lcov | coveralls", | ||
"lint": "eslint lib test util && prettier --check lib/**/* test/**/* util/**/*", | ||
"lintfix": "eslint lib test util --fix && prettier --write lib/**/* test/**/* util/**/*", | ||
"precommit": "npm run test && npm run lint" | ||
}, | ||
"dependencies": { | ||
"bignumber.js": "^9.0.0", | ||
"request": "^2.88.2", | ||
"underscore": "^1.8.3", | ||
"ws": "^3.3.1" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/tiagosiebler/binance" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/tiagosiebler/binance/issues" | ||
}, | ||
"homepage": "https://github.com/tiagosiebler/binance#readme", | ||
"keywords": [ | ||
"api", | ||
"bitcoin", | ||
"ethereum", | ||
"cryptocurrency", | ||
"binance", | ||
"btc", | ||
"eth" | ||
], | ||
"author": "Zoey Garvey", | ||
"contributors": [ | ||
"Clifford Roche <clifford.roche@gmail.com> (http://www.cliffordroche.ca)", | ||
"Aslam Hadi H <aslamhadi@gmail.com> (https://www.commitcode.com)", | ||
"Andrey Vorobyov <vorandrew@gmail.com>", | ||
"Gavy Aggarwal (http://gavyaggarwal.com/)", | ||
"Tiago Siebler (https://github.com/tiagosiebler)", | ||
"Tony Pettigrew (https://github.com/NeverEnder4)", | ||
"Chris <apexearth@gmail.com> (https://github.com/apexearth)" | ||
], | ||
"funding": { | ||
"type": "individual", | ||
"url": "https://github.com/sponsors/tiagosiebler" | ||
}, | ||
"license": "MIT", | ||
"devDependencies": { | ||
"chai": "^4.1.1", | ||
"coveralls": "^3.0.9", | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.10.0", | ||
"mocha": "^7.1.0", | ||
"mock-require": "^2.0.2", | ||
"nyc": "^15.0.0", | ||
"prettier": "^1.19.1", | ||
"sinon": "^9.0.1" | ||
} | ||
} |
@@ -7,4 +7,3 @@ ![npm downloads](https://img.shields.io/npm/dt/binance.svg) | ||
A wrapper for the Binance REST and WebSocket APIs. Uses both promises and callbacks, and beautifies the | ||
binance API responses that normally use lots of one letter property names. For more information on the API and parameters for requests visit https://github.com/binance-exchange/binance-official-api-docs | ||
A wrapper for the Binance REST and WebSocket APIs. Uses promises and beautifies the binance API responses that normally use lots of one letter property names. For more information on the API and parameters for requests visit https://github.com/binance-exchange/binance-official-api-docs | ||
@@ -59,14 +58,2 @@ # Usage/Example | ||
/* | ||
* Or you can provide a callback. Also, instead of passing an object as the query, routes | ||
* that only mandate a symbol, or symbol and timestamp, can be passed a string. | ||
*/ | ||
binanceRest.allOrders('BNBBTC', (err, data) => { | ||
if (err) { | ||
console.error(err); | ||
} else { | ||
console.log(data); | ||
} | ||
}); | ||
/* | ||
* WebSocket API | ||
@@ -268,4 +255,22 @@ * | ||
Places a new order. | ||
Places a new order. Example: | ||
```javascript | ||
const customOrderId = binanceRest.generateNewOrderId(); | ||
binanceRest | ||
.newOrder({ | ||
symbol: 'BTCUSDT', | ||
quantity: 0.1, | ||
side: 'BUY', | ||
type: 'MARKET', | ||
newClientOrderId: customOrderId, | ||
}) | ||
.then(orderData => { | ||
console.log(orderData); | ||
}) | ||
.catch(err => { | ||
console.error(err); | ||
}); | ||
``` | ||
### **[testOrder(query _object_, [callback _function_])](https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#test-new-order-trade)** | ||
@@ -693,18 +698,10 @@ | ||
Most can be resolved by adjusting your `recvWindow` a bit larger, but if your clock is constantly | ||
or intermittently going out of sync with the server, the library is capable of calculating the | ||
drift and adjusting the timestamps. You have some options. The first is to add the `handleDrift` | ||
option to the constructor, setting it to `true`. In this case, if your clock is ahead of the | ||
server's, or falls behind and is outside the `recvWindow`, and a request fails, the library will | ||
calculate the drift of your clock and reattempt the request. It will also use the drift value to | ||
adjust all subsequent calls. This may add more time to the initial requests that fail, and could | ||
potentially affect highly time sensitive trades. The alternative is to use the | ||
`startTimeSync(interval_in_ms)` and `endTimeSync` functions. The former will begin an interval, | ||
and each time it's called the drift will be calculated and used on all subsequent requests. The | ||
default interval is 5 minutes, and it should be specified in milliseconds. The latter will clear | ||
the interval. You may also calculate the drift manually by calling `calculateDrift()`. The | ||
resulting value will be stored internally and used on all subsequent calls. | ||
Most can be resolved by adjusting your `recvWindow` a bit larger, but if your clock is constantly or intermittently going out of sync with the server, the library is capable of calculating the drift and adjusting the timestamps. You have some options. The first is to add the `handleDrift` option to the constructor, setting it to `true`. | ||
In this case, if your clock is ahead of the server's, or falls behind and is outside the `recvWindow`, and a request fails, the library will calculate the drift of your clock and reattempt the request. It will also use the drift value to adjust all subsequent calls. This may add more time to the initial requests that fail, and could potentially affect highly time sensitive trades. | ||
The alternative is to use the `startTimeSync(interval_in_ms)` and `endTimeSync` functions. The former will begin an interval, and each time it's called the drift will be calculated and used on all subsequent requests. The default interval is 5 minutes, and it should be specified in milliseconds. The latter will clear the interval. You may also calculate the drift manually by calling `calculateDrift()`. The resulting value will be stored internally and used on all subsequent calls. | ||
# License | ||
[MIT](LICENSE) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
921
58862
704