From a0c291d12d09dcbf7503844033a23e30cebee49e Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 6 Apr 2026 02:52:51 +0200 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20split=20USD=E2=93=88-M=20futures=20?= =?UTF-8?q?WebSocket=20URLs=20into=20public/market/private?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Binance is retiring the legacy fstream.binance.com/ws endpoint on 2026-04-23. This migrates to the new dedicated endpoints: - /public → depth, partialDepth, rpiDepth (high-frequency order book) - /market → aggTrades, candles, ticker, liquidations, markPrices, etc. - /private → user data streams (listenKey-based) Adds wsFuturesPublic, wsFuturesMarket, wsFuturesPrivate config options. The existing wsFutures option remains as a blanket override for all three. --- src/websocket.js | 45 ++++++++++++++++++++++++++++++++------------- types/base.d.ts | 3 +++ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/websocket.js b/src/websocket.js index 42de4ffb..c325bc10 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) @@ -593,7 +604,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 +621,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) @@ -1225,7 +1236,7 @@ const user = (opts, variator) => (cb, transform) => { w = openWebSocket( `${ variator === 'futures' - ? endpoints.futures + ? endpoints.futuresPrivate : variator === 'delivery' ? endpoints.delivery : endpoints.base @@ -1282,7 +1293,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 +1305,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) { 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; From 38e5fadb474099ca8c9c0ca9d056a315d84202bb Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 6 Apr 2026 03:11:08 +0200 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20add=20missing=20USD=E2=93=88-M=20fu?= =?UTF-8?q?tures=20WebSocket=20streams?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds all streams from Binance's endpoint mapping that were not previously implemented: Public: - futuresBookTicker, futuresAllBookTickers Market: - futuresMarkPrice (individual symbol, supports 1s update speed) - futuresContinuousCandles (pair + contractType klines) - futuresCompositeIndex (index composition data) - futuresContractInfo (contract listing/status changes) - futuresAssetIndex, futuresAllAssetIndex (multi-assets mode) --- src/websocket.js | 225 +++++++++++++++++++++++++++++++++++++++++++ types/websocket.d.ts | 78 +++++++++++++++ 2 files changed, 303 insertions(+) diff --git a/src/websocket.js b/src/websocket.js index c325bc10..b1dc4438 100644 --- a/src/websocket.js +++ b/src/websocket.js @@ -631,6 +631,223 @@ 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, @@ -1363,6 +1580,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/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; From c34d72bd1183b2f6f1c7305b108b40e35728d0dd Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 6 Apr 2026 03:13:09 +0200 Subject: [PATCH 3/4] style: fix prettier formatting --- src/websocket.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/websocket.js b/src/websocket.js index b1dc4438..880f5ae9 100644 --- a/src/websocket.js +++ b/src/websocket.js @@ -633,9 +633,7 @@ const futuresAllLiquidations = (cb, transform = true) => { const futuresBookTicker = (payload, cb, transform = true) => { const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => { - const w = openWebSocket( - `${endpoints.futuresPublic}/${symbol.toLowerCase()}@bookTicker`, - ) + const w = openWebSocket(`${endpoints.futuresPublic}/${symbol.toLowerCase()}@bookTicker`) w.onmessage = msg => { const obj = JSONbig.parse(msg.data) @@ -677,9 +675,10 @@ 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 stream = + updateSpeed === '1s' + ? `${symbol.toLowerCase()}@markPrice@1s` + : `${symbol.toLowerCase()}@markPrice` const w = openWebSocket(`${endpoints.futuresMarket}/${stream}`) @@ -731,9 +730,7 @@ const futuresContinuousCandles = (payload, interval, cb, transform = true) => { const futuresCompositeIndex = (payload, cb, transform = true) => { const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => { - const w = openWebSocket( - `${endpoints.futuresMarket}/${symbol.toLowerCase()}@compositeIndex`, - ) + const w = openWebSocket(`${endpoints.futuresMarket}/${symbol.toLowerCase()}@compositeIndex`) w.onmessage = msg => { const obj = JSONbig.parse(msg.data) @@ -819,9 +816,7 @@ const futuresAssetIndexTransform = m => ({ const futuresAssetIndex = (payload, cb, transform = true) => { const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => { - const w = openWebSocket( - `${endpoints.futuresMarket}/${symbol.toLowerCase()}@assetIndex`, - ) + const w = openWebSocket(`${endpoints.futuresMarket}/${symbol.toLowerCase()}@assetIndex`) w.onmessage = msg => { const obj = JSONbig.parse(msg.data) From 23e75a164ebfdc1eaf35ac846148ee8a6fc7d227 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 6 Apr 2026 03:38:10 +0200 Subject: [PATCH 4/4] fix: use explicit futuresMarket endpoint in shared WS functions and fix flaky checkFields test Shared functions (candles, ticker, allTickers, aggTrades) now explicitly route futures variator to endpoints.futuresMarket instead of relying on endpoints.futures coincidentally pointing to /market/ws. Also fix checkFields to use `field in object` instead of `t.truthy(value)` so fields with valid falsy values like `trades: 0` don't cause failures. --- src/websocket.js | 12 ++++++++---- test/utils.js | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/websocket.js b/src/websocket.js index 880f5ae9..d21c8ab1 100644 --- a/src/websocket.js +++ b/src/websocket.js @@ -222,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 => { @@ -378,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 @@ -411,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 @@ -561,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 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) }) }