diff --git a/src/websocket.js b/src/websocket.js index 42de4ffb..d21c8ab1 100644 --- a/src/websocket.js +++ b/src/websocket.js @@ -7,7 +7,10 @@ import { createHmacSignature, createAsymmetricSignature } from 'signature' const endpoints = { base: 'wss://stream.binance.com:9443/ws', - futures: 'wss://fstream.binance.com/ws', + futures: 'wss://fstream.binance.com/market/ws', + futuresPublic: 'wss://fstream.binance.com/public/ws', + futuresMarket: 'wss://fstream.binance.com/market/ws', + futuresPrivate: 'wss://fstream.binance.com/private/ws', delivery: 'wss://dstream.binance.com/ws', } @@ -56,9 +59,13 @@ const depth = (payload, cb, transform = true, variator) => { const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => { const [symbolName, updateSpeed] = symbol.toLowerCase().split('@') const w = openWebSocket( - `${variator ? endpoints[variator] : endpoints.base}/${symbolName}@depth${ - updateSpeed ? `@${updateSpeed}` : '' - }`, + `${ + variator === 'futures' + ? endpoints.futuresPublic + : variator + ? endpoints[variator] + : endpoints.base + }/${symbolName}@depth${updateSpeed ? `@${updateSpeed}` : ''}`, ) w.onmessage = msg => { const obj = JSONbig.parse(msg.data) @@ -86,7 +93,7 @@ const depth = (payload, cb, transform = true, variator) => { const futuresRpiDepth = (payload, cb, transform = true) => { const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => { const symbolName = symbol.toLowerCase() - const w = openWebSocket(`${endpoints.futures}/${symbolName}@rpiDepth@500ms`) + const w = openWebSocket(`${endpoints.futuresPublic}/${symbolName}@rpiDepth@500ms`) w.onmessage = msg => { const obj = JSONbig.parse(msg.data) cb(transform ? futuresDepthTransform(obj) : obj) @@ -140,9 +147,13 @@ const partialDepth = (payload, cb, transform = true, variator) => { const cache = (Array.isArray(payload) ? payload : [payload]).map(({ symbol, level }) => { const [symbolName, updateSpeed] = symbol.toLowerCase().split('@') const w = openWebSocket( - `${variator ? endpoints[variator] : endpoints.base}/${symbolName}@depth${level}${ - updateSpeed ? `@${updateSpeed}` : '' - }`, + `${ + variator === 'futures' + ? endpoints.futuresPublic + : variator + ? endpoints[variator] + : endpoints.base + }/${symbolName}@depth${level}${updateSpeed ? `@${updateSpeed}` : ''}`, ) w.onmessage = msg => { const obj = JSONbig.parse(msg.data) @@ -211,7 +222,11 @@ const candles = (payload, interval, cb, transform = true, variator) => { const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => { const w = openWebSocket( `${ - variator ? endpoints[variator] : endpoints.base + variator === 'futures' + ? endpoints.futuresMarket + : variator === 'delivery' + ? endpoints.delivery + : endpoints.base }/${symbol.toLowerCase()}@kline_${interval}`, ) w.onmessage = msg => { @@ -367,7 +382,7 @@ const ticker = (payload, cb, transform = true, variator) => { const w = openWebSocket( `${ variator === 'futures' - ? endpoints.futures + ? endpoints.futuresMarket : variator === 'delivery' ? endpoints.delivery : endpoints.base @@ -400,7 +415,7 @@ const allTickers = (cb, transform = true, variator) => { const w = new openWebSocket( `${ variator === 'futures' - ? endpoints.futures + ? endpoints.futuresMarket : variator === 'delivery' ? endpoints.delivery : endpoints.base @@ -550,7 +565,7 @@ const aggTrades = (payload, cb, transform = true, variator) => { const w = openWebSocket( `${ variator === 'futures' - ? endpoints.futures + ? endpoints.futuresMarket : variator === 'delivery' ? endpoints.delivery : endpoints.base @@ -593,7 +608,7 @@ const futuresLiqsTransform = m => ({ const futuresLiquidations = (payload, cb, transform = true) => { const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => { - const w = openWebSocket(`${endpoints.futures}/${symbol.toLowerCase()}@forceOrder`) + const w = openWebSocket(`${endpoints.futuresMarket}/${symbol.toLowerCase()}@forceOrder`) w.onmessage = msg => { const obj = JSONbig.parse(msg.data) @@ -610,7 +625,7 @@ const futuresLiquidations = (payload, cb, transform = true) => { } const futuresAllLiquidations = (cb, transform = true) => { - const w = new openWebSocket(`${endpoints.futures}/!forceOrder@arr`) + const w = new openWebSocket(`${endpoints.futuresMarket}/!forceOrder@arr`) w.onmessage = msg => { const obj = JSONbig.parse(msg.data) @@ -620,6 +635,218 @@ const futuresAllLiquidations = (cb, transform = true) => { return options => w.close(1000, 'Close handle was called', { keepClosed: true, ...options }) } +const futuresBookTicker = (payload, cb, transform = true) => { + const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => { + const w = openWebSocket(`${endpoints.futuresPublic}/${symbol.toLowerCase()}@bookTicker`) + + w.onmessage = msg => { + const obj = JSONbig.parse(msg.data) + cb(transform ? bookTickerTransform(obj) : obj) + } + + return w + }) + + return options => + cache.forEach(w => + w.close(1000, 'Close handle was called', { keepClosed: true, ...options }), + ) +} + +const futuresAllBookTickers = (cb, transform = true) => { + const w = openWebSocket(`${endpoints.futuresPublic}/!bookTicker`) + + w.onmessage = msg => { + const obj = JSONbig.parse(msg.data) + cb(transform ? bookTickerTransform(obj) : obj) + } + + return options => w.close(1000, 'Close handle was called', { keepClosed: true, ...options }) +} + +const futuresMarkPriceTransform = m => ({ + eventType: m.e, + eventTime: m.E, + symbol: m.s, + markPrice: m.p, + indexPrice: m.i, + settlePrice: m.P, + fundingRate: m.r, + nextFundingRate: m.T, +}) + +const futuresMarkPrice = (payload, cb, transform = true) => { + const cache = (Array.isArray(payload) ? payload : [payload]).map(input => { + const symbol = typeof input === 'object' ? input.symbol : input + const updateSpeed = typeof input === 'object' ? input.updateSpeed : undefined + const stream = + updateSpeed === '1s' + ? `${symbol.toLowerCase()}@markPrice@1s` + : `${symbol.toLowerCase()}@markPrice` + + const w = openWebSocket(`${endpoints.futuresMarket}/${stream}`) + + w.onmessage = msg => { + const obj = JSONbig.parse(msg.data) + cb(transform ? futuresMarkPriceTransform(obj) : obj) + } + + return w + }) + + return options => + cache.forEach(w => + w.close(1000, 'Close handle was called', { keepClosed: true, ...options }), + ) +} + +const futuresContinuousCandles = (payload, interval, cb, transform = true) => { + if (!interval || !cb) { + throw new Error('Please pass a pair, contractType, interval and callback.') + } + + const pair = payload.pair.toLowerCase() + const contractType = payload.contractType.toLowerCase() + + const w = openWebSocket( + `${endpoints.futuresMarket}/${pair}_${contractType}@continuousKline_${interval}`, + ) + + w.onmessage = msg => { + const obj = JSONbig.parse(msg.data) + const { e: eventType, E: eventTime, ps: pairSymbol, ct: contType, k: tick } = obj + + cb( + transform + ? { + eventType, + eventTime, + pair: pairSymbol, + contractType: contType, + ...candleTransform(tick), + } + : obj, + ) + } + + return options => w.close(1000, 'Close handle was called', { keepClosed: true, ...options }) +} + +const futuresCompositeIndex = (payload, cb, transform = true) => { + const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => { + const w = openWebSocket(`${endpoints.futuresMarket}/${symbol.toLowerCase()}@compositeIndex`) + + w.onmessage = msg => { + const obj = JSONbig.parse(msg.data) + cb( + transform + ? { + eventType: obj.e, + eventTime: obj.E, + symbol: obj.s, + price: obj.p, + composition: obj.c + ? obj.c.map(c => ({ + baseAsset: c.b, + quoteAsset: c.q, + weightInQuantity: c.w, + weightInPercentage: c.W, + indexPrice: c.i, + })) + : [], + } + : obj, + ) + } + + return w + }) + + return options => + cache.forEach(w => + w.close(1000, 'Close handle was called', { keepClosed: true, ...options }), + ) +} + +const futuresContractInfo = (cb, transform = true) => { + const w = openWebSocket(`${endpoints.futuresMarket}/!contractInfo`) + + w.onmessage = msg => { + const obj = JSONbig.parse(msg.data) + cb( + transform + ? { + eventType: obj.e, + eventTime: obj.E, + symbol: obj.s, + pair: obj.ps, + contractType: obj.ct, + deliveryDate: obj.dt, + onboardDate: obj.ot, + contractStatus: obj.cs, + brackets: obj.bks + ? obj.bks.map(b => ({ + notionalBracket: b.bs, + floorNotional: b.bnf, + capNotional: b.bnc, + maintenanceRatio: b.mmr, + auxiliaryNumber: b.cf, + minLeverage: b.mi, + maxLeverage: b.ma, + })) + : [], + } + : obj, + ) + } + + return options => w.close(1000, 'Close handle was called', { keepClosed: true, ...options }) +} + +const futuresAssetIndexTransform = m => ({ + eventType: m.e, + eventTime: m.E, + symbol: m.s, + index: m.i, + bidBuffer: m.b, + askBuffer: m.a, + bidRate: m.B, + askRate: m.A, + autoExchangeBidBuffer: m.q, + autoExchangeAskBuffer: m.Q, + autoExchangeBidRate: m.g, + autoExchangeAskRate: m.G, +}) + +const futuresAssetIndex = (payload, cb, transform = true) => { + const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => { + const w = openWebSocket(`${endpoints.futuresMarket}/${symbol.toLowerCase()}@assetIndex`) + + w.onmessage = msg => { + const obj = JSONbig.parse(msg.data) + cb(transform ? futuresAssetIndexTransform(obj) : obj) + } + + return w + }) + + return options => + cache.forEach(w => + w.close(1000, 'Close handle was called', { keepClosed: true, ...options }), + ) +} + +const futuresAllAssetIndex = (cb, transform = true) => { + const w = openWebSocket(`${endpoints.futuresMarket}/!assetIndex@arr`) + + w.onmessage = msg => { + const arr = JSONbig.parse(msg.data) + cb(transform ? arr.map(futuresAssetIndexTransform) : arr) + } + + return options => w.close(1000, 'Close handle was called', { keepClosed: true, ...options }) +} + const tradesTransform = m => ({ eventType: m.e, eventTime: m.E, @@ -1225,7 +1452,7 @@ const user = (opts, variator) => (cb, transform) => { w = openWebSocket( `${ variator === 'futures' - ? endpoints.futures + ? endpoints.futuresPrivate : variator === 'delivery' ? endpoints.delivery : endpoints.base @@ -1282,7 +1509,7 @@ const futuresAllMarkPricesTransform = m => const futuresAllMarkPrices = (payload, cb, transform = true) => { const variant = payload.updateSpeed === '1s' ? '!markPrice@arr@1s' : '!markPrice@arr' - const w = openWebSocket(`${endpoints.futures}/${variant}`) + const w = openWebSocket(`${endpoints.futuresMarket}/${variant}`) w.onmessage = msg => { const arr = JSONbig.parse(msg.data) @@ -1294,7 +1521,15 @@ const futuresAllMarkPrices = (payload, cb, transform = true) => { export default opts => { if (opts && opts.wsBase) endpoints.base = opts.wsBase - if (opts && opts.wsFutures) endpoints.futures = opts.wsFutures + if (opts && opts.wsFutures) { + endpoints.futures = opts.wsFutures + endpoints.futuresPublic = opts.wsFutures + endpoints.futuresMarket = opts.wsFutures + endpoints.futuresPrivate = opts.wsFutures + } + if (opts && opts.wsFuturesPublic) endpoints.futuresPublic = opts.wsFuturesPublic + if (opts && opts.wsFuturesMarket) endpoints.futuresMarket = opts.wsFuturesMarket + if (opts && opts.wsFuturesPrivate) endpoints.futuresPrivate = opts.wsFuturesPrivate if (opts && opts.wsDelivery) endpoints.delivery = opts.wsDelivery if (opts && opts.proxy) { @@ -1344,6 +1579,14 @@ export default opts => { aggTrades(payload, cb, transform, 'delivery'), futuresLiquidations, futuresAllLiquidations, + futuresBookTicker, + futuresAllBookTickers, + futuresMarkPrice, + futuresContinuousCandles, + futuresCompositeIndex, + futuresContractInfo, + futuresAssetIndex, + futuresAllAssetIndex, futuresUser: user(opts, 'futures'), deliveryUser: user(opts, 'delivery'), futuresCustomSubStream: (payload, cb) => customSubStream(payload, cb, 'futures'), diff --git a/test/utils.js b/test/utils.js index 1443366c..35fecebd 100644 --- a/test/utils.js +++ b/test/utils.js @@ -2,7 +2,7 @@ import http from 'http' export const checkFields = (t, object, fields) => { fields.forEach(field => { - t.truthy(object[field]) + t.truthy(field in object) }) } diff --git a/types/base.d.ts b/types/base.d.ts index 8683d211..b0bd72dd 100644 --- a/types/base.d.ts +++ b/types/base.d.ts @@ -10,6 +10,9 @@ export interface BinanceRestOptions { wsBase?: string; wsFutures?: string; + wsFuturesPublic?: string; + wsFuturesMarket?: string; + wsFuturesPrivate?: string; wsDelivery?: string; wsApi?: string; wsApiTestnet?: string; diff --git a/types/websocket.d.ts b/types/websocket.d.ts index fdc5be69..a81dcf42 100644 --- a/types/websocket.d.ts +++ b/types/websocket.d.ts @@ -298,6 +298,76 @@ export interface FuturesAllMarkPricesPayload { updateSpeed?: '1s' | '3s'; } +// Futures mark price payload (individual symbol) +export interface FuturesMarkPricePayload { + symbol: string; + updateSpeed?: '1s'; +} + +// Futures continuous candles payload +export interface FuturesContinuousCandlesPayload { + pair: string; + contractType: string; +} + +// Futures continuous candle event +export interface FuturesContinuousCandleEvent extends CandleEvent { + pair: string; + contractType: string; +} + +// Futures composite index event +export interface FuturesCompositeIndexEvent { + eventType: string; + eventTime: number; + symbol: string; + price: string; + composition: { + baseAsset: string; + quoteAsset: string; + weightInQuantity: string; + weightInPercentage: string; + indexPrice: string; + }[]; +} + +// Futures contract info event +export interface FuturesContractInfoEvent { + eventType: string; + eventTime: number; + symbol: string; + pair: string; + contractType: string; + deliveryDate: number; + onboardDate: number; + contractStatus: string; + brackets: { + notionalBracket: number; + floorNotional: number; + capNotional: number; + maintenanceRatio: string; + auxiliaryNumber: number; + minLeverage: number; + maxLeverage: number; + }[]; +} + +// Futures asset index event +export interface FuturesAssetIndexEvent { + eventType: string; + eventTime: number; + symbol: string; + index: string; + bidBuffer: string; + askBuffer: string; + bidRate: string; + askRate: string; + autoExchangeBidBuffer: string; + autoExchangeAskBuffer: string; + autoExchangeBidRate: string; + autoExchangeAskRate: string; +} + export interface BinanceWebSocket { // Spot depth(payload: string | string[], cb: (data: DepthEvent) => void, transform?: boolean): CleanupFn; @@ -326,6 +396,14 @@ export interface BinanceWebSocket { futuresAggTrades(payload: string | string[], cb: (data: FuturesAggTradeEvent) => void, transform?: boolean): CleanupFn; futuresLiquidations(payload: string | string[], cb: (data: FuturesLiquidationEvent) => void, transform?: boolean): CleanupFn; futuresAllLiquidations(cb: (data: FuturesLiquidationEvent) => void, transform?: boolean): CleanupFn; + futuresBookTicker(payload: string | string[], cb: (data: BookTickerEvent) => void, transform?: boolean): CleanupFn; + futuresAllBookTickers(cb: (data: BookTickerEvent) => void, transform?: boolean): CleanupFn; + futuresMarkPrice(payload: string | string[] | FuturesMarkPricePayload | FuturesMarkPricePayload[], cb: (data: FuturesMarkPriceEvent) => void, transform?: boolean): CleanupFn; + futuresContinuousCandles(payload: FuturesContinuousCandlesPayload, interval: string, cb: (data: FuturesContinuousCandleEvent) => void, transform?: boolean): CleanupFn; + futuresCompositeIndex(payload: string | string[], cb: (data: FuturesCompositeIndexEvent) => void, transform?: boolean): CleanupFn; + futuresContractInfo(cb: (data: FuturesContractInfoEvent) => void, transform?: boolean): CleanupFn; + futuresAssetIndex(payload: string | string[], cb: (data: FuturesAssetIndexEvent) => void, transform?: boolean): CleanupFn; + futuresAllAssetIndex(cb: (data: FuturesAssetIndexEvent[]) => void, transform?: boolean): CleanupFn; futuresUser(cb: (data: any) => void, transform?: boolean): Promise; futuresCustomSubStream(payload: string | string[], cb: (data: any) => void): CleanupFn; futuresAllMarkPrices(payload: FuturesAllMarkPricesPayload, cb: (data: FuturesMarkPriceEvent[]) => void): CleanupFn;