Skip to content

Commit 8430125

Browse files
authored
feat: split USDⓈ-M futures WebSocket URLs into public/market/private (#701)
* feat: split USDⓈ-M futures WebSocket URLs into public/market/private 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. * feat: add missing USDⓈ-M futures WebSocket streams 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) * style: fix prettier formatting * 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.
1 parent 8978e46 commit 8430125

4 files changed

Lines changed: 342 additions & 18 deletions

File tree

src/websocket.js

Lines changed: 260 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { createHmacSignature, createAsymmetricSignature } from 'signature'
77

88
const endpoints = {
99
base: 'wss://stream.binance.com:9443/ws',
10-
futures: 'wss://fstream.binance.com/ws',
10+
futures: 'wss://fstream.binance.com/market/ws',
11+
futuresPublic: 'wss://fstream.binance.com/public/ws',
12+
futuresMarket: 'wss://fstream.binance.com/market/ws',
13+
futuresPrivate: 'wss://fstream.binance.com/private/ws',
1114
delivery: 'wss://dstream.binance.com/ws',
1215
}
1316

@@ -56,9 +59,13 @@ const depth = (payload, cb, transform = true, variator) => {
5659
const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => {
5760
const [symbolName, updateSpeed] = symbol.toLowerCase().split('@')
5861
const w = openWebSocket(
59-
`${variator ? endpoints[variator] : endpoints.base}/${symbolName}@depth${
60-
updateSpeed ? `@${updateSpeed}` : ''
61-
}`,
62+
`${
63+
variator === 'futures'
64+
? endpoints.futuresPublic
65+
: variator
66+
? endpoints[variator]
67+
: endpoints.base
68+
}/${symbolName}@depth${updateSpeed ? `@${updateSpeed}` : ''}`,
6269
)
6370
w.onmessage = msg => {
6471
const obj = JSONbig.parse(msg.data)
@@ -86,7 +93,7 @@ const depth = (payload, cb, transform = true, variator) => {
8693
const futuresRpiDepth = (payload, cb, transform = true) => {
8794
const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => {
8895
const symbolName = symbol.toLowerCase()
89-
const w = openWebSocket(`${endpoints.futures}/${symbolName}@rpiDepth@500ms`)
96+
const w = openWebSocket(`${endpoints.futuresPublic}/${symbolName}@rpiDepth@500ms`)
9097
w.onmessage = msg => {
9198
const obj = JSONbig.parse(msg.data)
9299
cb(transform ? futuresDepthTransform(obj) : obj)
@@ -140,9 +147,13 @@ const partialDepth = (payload, cb, transform = true, variator) => {
140147
const cache = (Array.isArray(payload) ? payload : [payload]).map(({ symbol, level }) => {
141148
const [symbolName, updateSpeed] = symbol.toLowerCase().split('@')
142149
const w = openWebSocket(
143-
`${variator ? endpoints[variator] : endpoints.base}/${symbolName}@depth${level}${
144-
updateSpeed ? `@${updateSpeed}` : ''
145-
}`,
150+
`${
151+
variator === 'futures'
152+
? endpoints.futuresPublic
153+
: variator
154+
? endpoints[variator]
155+
: endpoints.base
156+
}/${symbolName}@depth${level}${updateSpeed ? `@${updateSpeed}` : ''}`,
146157
)
147158
w.onmessage = msg => {
148159
const obj = JSONbig.parse(msg.data)
@@ -211,7 +222,11 @@ const candles = (payload, interval, cb, transform = true, variator) => {
211222
const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => {
212223
const w = openWebSocket(
213224
`${
214-
variator ? endpoints[variator] : endpoints.base
225+
variator === 'futures'
226+
? endpoints.futuresMarket
227+
: variator === 'delivery'
228+
? endpoints.delivery
229+
: endpoints.base
215230
}/${symbol.toLowerCase()}@kline_${interval}`,
216231
)
217232
w.onmessage = msg => {
@@ -367,7 +382,7 @@ const ticker = (payload, cb, transform = true, variator) => {
367382
const w = openWebSocket(
368383
`${
369384
variator === 'futures'
370-
? endpoints.futures
385+
? endpoints.futuresMarket
371386
: variator === 'delivery'
372387
? endpoints.delivery
373388
: endpoints.base
@@ -400,7 +415,7 @@ const allTickers = (cb, transform = true, variator) => {
400415
const w = new openWebSocket(
401416
`${
402417
variator === 'futures'
403-
? endpoints.futures
418+
? endpoints.futuresMarket
404419
: variator === 'delivery'
405420
? endpoints.delivery
406421
: endpoints.base
@@ -550,7 +565,7 @@ const aggTrades = (payload, cb, transform = true, variator) => {
550565
const w = openWebSocket(
551566
`${
552567
variator === 'futures'
553-
? endpoints.futures
568+
? endpoints.futuresMarket
554569
: variator === 'delivery'
555570
? endpoints.delivery
556571
: endpoints.base
@@ -593,7 +608,7 @@ const futuresLiqsTransform = m => ({
593608

594609
const futuresLiquidations = (payload, cb, transform = true) => {
595610
const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => {
596-
const w = openWebSocket(`${endpoints.futures}/${symbol.toLowerCase()}@forceOrder`)
611+
const w = openWebSocket(`${endpoints.futuresMarket}/${symbol.toLowerCase()}@forceOrder`)
597612
w.onmessage = msg => {
598613
const obj = JSONbig.parse(msg.data)
599614

@@ -610,7 +625,7 @@ const futuresLiquidations = (payload, cb, transform = true) => {
610625
}
611626

612627
const futuresAllLiquidations = (cb, transform = true) => {
613-
const w = new openWebSocket(`${endpoints.futures}/!forceOrder@arr`)
628+
const w = new openWebSocket(`${endpoints.futuresMarket}/!forceOrder@arr`)
614629

615630
w.onmessage = msg => {
616631
const obj = JSONbig.parse(msg.data)
@@ -620,6 +635,218 @@ const futuresAllLiquidations = (cb, transform = true) => {
620635
return options => w.close(1000, 'Close handle was called', { keepClosed: true, ...options })
621636
}
622637

638+
const futuresBookTicker = (payload, cb, transform = true) => {
639+
const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => {
640+
const w = openWebSocket(`${endpoints.futuresPublic}/${symbol.toLowerCase()}@bookTicker`)
641+
642+
w.onmessage = msg => {
643+
const obj = JSONbig.parse(msg.data)
644+
cb(transform ? bookTickerTransform(obj) : obj)
645+
}
646+
647+
return w
648+
})
649+
650+
return options =>
651+
cache.forEach(w =>
652+
w.close(1000, 'Close handle was called', { keepClosed: true, ...options }),
653+
)
654+
}
655+
656+
const futuresAllBookTickers = (cb, transform = true) => {
657+
const w = openWebSocket(`${endpoints.futuresPublic}/!bookTicker`)
658+
659+
w.onmessage = msg => {
660+
const obj = JSONbig.parse(msg.data)
661+
cb(transform ? bookTickerTransform(obj) : obj)
662+
}
663+
664+
return options => w.close(1000, 'Close handle was called', { keepClosed: true, ...options })
665+
}
666+
667+
const futuresMarkPriceTransform = m => ({
668+
eventType: m.e,
669+
eventTime: m.E,
670+
symbol: m.s,
671+
markPrice: m.p,
672+
indexPrice: m.i,
673+
settlePrice: m.P,
674+
fundingRate: m.r,
675+
nextFundingRate: m.T,
676+
})
677+
678+
const futuresMarkPrice = (payload, cb, transform = true) => {
679+
const cache = (Array.isArray(payload) ? payload : [payload]).map(input => {
680+
const symbol = typeof input === 'object' ? input.symbol : input
681+
const updateSpeed = typeof input === 'object' ? input.updateSpeed : undefined
682+
const stream =
683+
updateSpeed === '1s'
684+
? `${symbol.toLowerCase()}@markPrice@1s`
685+
: `${symbol.toLowerCase()}@markPrice`
686+
687+
const w = openWebSocket(`${endpoints.futuresMarket}/${stream}`)
688+
689+
w.onmessage = msg => {
690+
const obj = JSONbig.parse(msg.data)
691+
cb(transform ? futuresMarkPriceTransform(obj) : obj)
692+
}
693+
694+
return w
695+
})
696+
697+
return options =>
698+
cache.forEach(w =>
699+
w.close(1000, 'Close handle was called', { keepClosed: true, ...options }),
700+
)
701+
}
702+
703+
const futuresContinuousCandles = (payload, interval, cb, transform = true) => {
704+
if (!interval || !cb) {
705+
throw new Error('Please pass a pair, contractType, interval and callback.')
706+
}
707+
708+
const pair = payload.pair.toLowerCase()
709+
const contractType = payload.contractType.toLowerCase()
710+
711+
const w = openWebSocket(
712+
`${endpoints.futuresMarket}/${pair}_${contractType}@continuousKline_${interval}`,
713+
)
714+
715+
w.onmessage = msg => {
716+
const obj = JSONbig.parse(msg.data)
717+
const { e: eventType, E: eventTime, ps: pairSymbol, ct: contType, k: tick } = obj
718+
719+
cb(
720+
transform
721+
? {
722+
eventType,
723+
eventTime,
724+
pair: pairSymbol,
725+
contractType: contType,
726+
...candleTransform(tick),
727+
}
728+
: obj,
729+
)
730+
}
731+
732+
return options => w.close(1000, 'Close handle was called', { keepClosed: true, ...options })
733+
}
734+
735+
const futuresCompositeIndex = (payload, cb, transform = true) => {
736+
const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => {
737+
const w = openWebSocket(`${endpoints.futuresMarket}/${symbol.toLowerCase()}@compositeIndex`)
738+
739+
w.onmessage = msg => {
740+
const obj = JSONbig.parse(msg.data)
741+
cb(
742+
transform
743+
? {
744+
eventType: obj.e,
745+
eventTime: obj.E,
746+
symbol: obj.s,
747+
price: obj.p,
748+
composition: obj.c
749+
? obj.c.map(c => ({
750+
baseAsset: c.b,
751+
quoteAsset: c.q,
752+
weightInQuantity: c.w,
753+
weightInPercentage: c.W,
754+
indexPrice: c.i,
755+
}))
756+
: [],
757+
}
758+
: obj,
759+
)
760+
}
761+
762+
return w
763+
})
764+
765+
return options =>
766+
cache.forEach(w =>
767+
w.close(1000, 'Close handle was called', { keepClosed: true, ...options }),
768+
)
769+
}
770+
771+
const futuresContractInfo = (cb, transform = true) => {
772+
const w = openWebSocket(`${endpoints.futuresMarket}/!contractInfo`)
773+
774+
w.onmessage = msg => {
775+
const obj = JSONbig.parse(msg.data)
776+
cb(
777+
transform
778+
? {
779+
eventType: obj.e,
780+
eventTime: obj.E,
781+
symbol: obj.s,
782+
pair: obj.ps,
783+
contractType: obj.ct,
784+
deliveryDate: obj.dt,
785+
onboardDate: obj.ot,
786+
contractStatus: obj.cs,
787+
brackets: obj.bks
788+
? obj.bks.map(b => ({
789+
notionalBracket: b.bs,
790+
floorNotional: b.bnf,
791+
capNotional: b.bnc,
792+
maintenanceRatio: b.mmr,
793+
auxiliaryNumber: b.cf,
794+
minLeverage: b.mi,
795+
maxLeverage: b.ma,
796+
}))
797+
: [],
798+
}
799+
: obj,
800+
)
801+
}
802+
803+
return options => w.close(1000, 'Close handle was called', { keepClosed: true, ...options })
804+
}
805+
806+
const futuresAssetIndexTransform = m => ({
807+
eventType: m.e,
808+
eventTime: m.E,
809+
symbol: m.s,
810+
index: m.i,
811+
bidBuffer: m.b,
812+
askBuffer: m.a,
813+
bidRate: m.B,
814+
askRate: m.A,
815+
autoExchangeBidBuffer: m.q,
816+
autoExchangeAskBuffer: m.Q,
817+
autoExchangeBidRate: m.g,
818+
autoExchangeAskRate: m.G,
819+
})
820+
821+
const futuresAssetIndex = (payload, cb, transform = true) => {
822+
const cache = (Array.isArray(payload) ? payload : [payload]).map(symbol => {
823+
const w = openWebSocket(`${endpoints.futuresMarket}/${symbol.toLowerCase()}@assetIndex`)
824+
825+
w.onmessage = msg => {
826+
const obj = JSONbig.parse(msg.data)
827+
cb(transform ? futuresAssetIndexTransform(obj) : obj)
828+
}
829+
830+
return w
831+
})
832+
833+
return options =>
834+
cache.forEach(w =>
835+
w.close(1000, 'Close handle was called', { keepClosed: true, ...options }),
836+
)
837+
}
838+
839+
const futuresAllAssetIndex = (cb, transform = true) => {
840+
const w = openWebSocket(`${endpoints.futuresMarket}/!assetIndex@arr`)
841+
842+
w.onmessage = msg => {
843+
const arr = JSONbig.parse(msg.data)
844+
cb(transform ? arr.map(futuresAssetIndexTransform) : arr)
845+
}
846+
847+
return options => w.close(1000, 'Close handle was called', { keepClosed: true, ...options })
848+
}
849+
623850
const tradesTransform = m => ({
624851
eventType: m.e,
625852
eventTime: m.E,
@@ -1225,7 +1452,7 @@ const user = (opts, variator) => (cb, transform) => {
12251452
w = openWebSocket(
12261453
`${
12271454
variator === 'futures'
1228-
? endpoints.futures
1455+
? endpoints.futuresPrivate
12291456
: variator === 'delivery'
12301457
? endpoints.delivery
12311458
: endpoints.base
@@ -1282,7 +1509,7 @@ const futuresAllMarkPricesTransform = m =>
12821509
const futuresAllMarkPrices = (payload, cb, transform = true) => {
12831510
const variant = payload.updateSpeed === '1s' ? '!markPrice@arr@1s' : '!markPrice@arr'
12841511

1285-
const w = openWebSocket(`${endpoints.futures}/${variant}`)
1512+
const w = openWebSocket(`${endpoints.futuresMarket}/${variant}`)
12861513

12871514
w.onmessage = msg => {
12881515
const arr = JSONbig.parse(msg.data)
@@ -1294,7 +1521,15 @@ const futuresAllMarkPrices = (payload, cb, transform = true) => {
12941521

12951522
export default opts => {
12961523
if (opts && opts.wsBase) endpoints.base = opts.wsBase
1297-
if (opts && opts.wsFutures) endpoints.futures = opts.wsFutures
1524+
if (opts && opts.wsFutures) {
1525+
endpoints.futures = opts.wsFutures
1526+
endpoints.futuresPublic = opts.wsFutures
1527+
endpoints.futuresMarket = opts.wsFutures
1528+
endpoints.futuresPrivate = opts.wsFutures
1529+
}
1530+
if (opts && opts.wsFuturesPublic) endpoints.futuresPublic = opts.wsFuturesPublic
1531+
if (opts && opts.wsFuturesMarket) endpoints.futuresMarket = opts.wsFuturesMarket
1532+
if (opts && opts.wsFuturesPrivate) endpoints.futuresPrivate = opts.wsFuturesPrivate
12981533
if (opts && opts.wsDelivery) endpoints.delivery = opts.wsDelivery
12991534

13001535
if (opts && opts.proxy) {
@@ -1344,6 +1579,14 @@ export default opts => {
13441579
aggTrades(payload, cb, transform, 'delivery'),
13451580
futuresLiquidations,
13461581
futuresAllLiquidations,
1582+
futuresBookTicker,
1583+
futuresAllBookTickers,
1584+
futuresMarkPrice,
1585+
futuresContinuousCandles,
1586+
futuresCompositeIndex,
1587+
futuresContractInfo,
1588+
futuresAssetIndex,
1589+
futuresAllAssetIndex,
13471590
futuresUser: user(opts, 'futures'),
13481591
deliveryUser: user(opts, 'delivery'),
13491592
futuresCustomSubStream: (payload, cb) => customSubStream(payload, cb, 'futures'),

test/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import http from 'http'
22

33
export const checkFields = (t, object, fields) => {
44
fields.forEach(field => {
5-
t.truthy(object[field])
5+
t.truthy(field in object)
66
})
77
}
88

0 commit comments

Comments
 (0)