@pear-protocol/symmio-client 0.2.28 → 0.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -69,7 +69,7 @@ function toBinanceSymbol(symmSymbol) {
69
69
  }
70
70
 
71
71
  // src/utils/binance-ws.ts
72
- var BINANCE_WS_URL = "wss://fstream.binance.com/ws";
72
+ var BINANCE_WS_URL = "wss://fstream.binance.com/market/ws";
73
73
  var RECONNECT_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
74
74
  var STABLE_QUOTES = ["USDT0", "USDT", "USDC", "USDE", "USDH", "USD"];
75
75
  function normalizeBaseSymbol(symbol) {
@@ -99,6 +99,22 @@ function extractTickers(payload) {
99
99
  }
100
100
  return [];
101
101
  }
102
+ function extractWsPayload(payload) {
103
+ if (payload && typeof payload === "object" && "data" in payload) {
104
+ return payload.data ?? payload;
105
+ }
106
+ return payload;
107
+ }
108
+ function isKlinePayload(payload) {
109
+ return Boolean(
110
+ payload && typeof payload === "object" && payload.e === "kline" && typeof payload.s === "string" && payload.k && typeof payload.k.i === "string"
111
+ );
112
+ }
113
+ function isMarkPricePayload(payload) {
114
+ return Boolean(
115
+ payload && typeof payload === "object" && payload.e === "markPriceUpdate" && typeof payload.s === "string"
116
+ );
117
+ }
102
118
  var BinanceWsManager = class {
103
119
  ws = null;
104
120
  streams = /* @__PURE__ */ new Map();
@@ -143,7 +159,9 @@ var BinanceWsManager = class {
143
159
  symbol: normalizeBaseSymbol(raw.s),
144
160
  markPrice: parseFloat(raw.p),
145
161
  indexPrice: parseFloat(raw.i),
146
- time: raw.E
162
+ time: raw.E,
163
+ fundingRate: parseFloat(raw.r ?? "0"),
164
+ nextFundingTime: Number(raw.T ?? 0)
147
165
  });
148
166
  };
149
167
  this.addStreamCallback(streamName, id, wrappedCb);
@@ -162,7 +180,9 @@ var BinanceWsManager = class {
162
180
  symbol: normalizeBaseSymbol(entry.s),
163
181
  markPrice: parseFloat(entry.p),
164
182
  indexPrice: parseFloat(entry.i),
165
- time: entry.E
183
+ time: entry.E,
184
+ fundingRate: parseFloat(entry.r ?? "0"),
185
+ nextFundingTime: Number(entry.T ?? 0)
166
186
  }))
167
187
  );
168
188
  };
@@ -252,17 +272,18 @@ var BinanceWsManager = class {
252
272
  };
253
273
  }
254
274
  handleMessage(data) {
255
- if (Array.isArray(data)) {
256
- this.dispatchToStream("!markPrice@arr@1s", data);
275
+ const payload = extractWsPayload(data);
276
+ if (Array.isArray(payload)) {
277
+ this.dispatchToStream("!markPrice@arr@1s", payload);
257
278
  return;
258
279
  }
259
- if (data.e === "kline") {
260
- const k = data.k;
261
- const streamName = `${data.s.toLowerCase()}@kline_${k.i}`;
262
- this.dispatchToStream(streamName, data);
263
- } else if (data.e === "markPriceUpdate") {
264
- const streamName = `${data.s.toLowerCase()}@markPrice@1s`;
265
- this.dispatchToStream(streamName, data);
280
+ if (isKlinePayload(payload)) {
281
+ const k = payload.k;
282
+ const streamName = `${payload.s.toLowerCase()}@kline_${k.i}`;
283
+ this.dispatchToStream(streamName, payload);
284
+ } else if (isMarkPricePayload(payload)) {
285
+ const streamName = `${payload.s.toLowerCase()}@markPrice@1s`;
286
+ this.dispatchToStream(streamName, payload);
266
287
  }
267
288
  }
268
289
  dispatchToStream(streamName, data) {
@@ -341,6 +362,8 @@ function getPrevRefCount(binanceSymbol) {
341
362
  }
342
363
  var useBinanceMarkPriceStore = create((set) => ({
343
364
  markPrices: {},
365
+ fundingRates: {},
366
+ nextFundingTimes: {},
344
367
  subscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
345
368
  const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
346
369
  const nextRefCount = getNextRefCount(binanceSymbol);
@@ -353,16 +376,29 @@ var useBinanceMarkPriceStore = create((set) => ({
353
376
  allMarkPricesUnsubscribe = wsManager.subscribeAllMarkPrices((entries) => {
354
377
  set((state) => {
355
378
  let nextMarkPrices = null;
379
+ let nextFundingRates = null;
380
+ let nextFundingTimes = null;
356
381
  entries.forEach((entry) => {
357
382
  const canonicalSymbol = normalizeBinanceSymbol(entry.symbol);
358
383
  const mappedSymbols = streamSymbols.get(canonicalSymbol);
359
384
  if (!mappedSymbols || mappedSymbols.size === 0) return;
360
385
  nextMarkPrices ??= { ...state.markPrices };
386
+ nextFundingRates ??= { ...state.fundingRates };
387
+ nextFundingTimes ??= { ...state.nextFundingTimes };
361
388
  mappedSymbols.forEach((mappedSymbol) => {
362
389
  nextMarkPrices[mappedSymbol] = entry.markPrice;
390
+ nextFundingRates[mappedSymbol] = entry.fundingRate;
391
+ nextFundingTimes[mappedSymbol] = entry.nextFundingTime;
363
392
  });
364
393
  });
365
- return nextMarkPrices ? { markPrices: nextMarkPrices } : state;
394
+ if (!nextMarkPrices || !nextFundingRates || !nextFundingTimes) {
395
+ return state;
396
+ }
397
+ return {
398
+ markPrices: nextMarkPrices,
399
+ fundingRates: nextFundingRates,
400
+ nextFundingTimes
401
+ };
366
402
  });
367
403
  });
368
404
  }
@@ -391,10 +427,20 @@ var useBinanceMarkPriceStore = create((set) => ({
391
427
  allMarkPricesUnsubscribe = null;
392
428
  }
393
429
  set((state) => {
394
- if (state.markPrices[symmSymbol] == null) return state;
430
+ if (state.markPrices[symmSymbol] == null && state.fundingRates[symmSymbol] == null && state.nextFundingTimes[symmSymbol] == null) {
431
+ return state;
432
+ }
395
433
  const nextMarkPrices = { ...state.markPrices };
434
+ const nextFundingRates = { ...state.fundingRates };
435
+ const nextFundingTimes = { ...state.nextFundingTimes };
396
436
  delete nextMarkPrices[symmSymbol];
397
- return { markPrices: nextMarkPrices };
437
+ delete nextFundingRates[symmSymbol];
438
+ delete nextFundingTimes[symmSymbol];
439
+ return {
440
+ markPrices: nextMarkPrices,
441
+ fundingRates: nextFundingRates,
442
+ nextFundingTimes
443
+ };
398
444
  });
399
445
  }
400
446
  }));
@@ -25435,7 +25481,7 @@ function useSymmHedgerMarkets(params) {
25435
25481
 
25436
25482
  // src/utils/binance-api.ts
25437
25483
  var BINANCE_FAPI_BASE = "https://fapi.binance.com";
25438
- async function fetchMarkPriceKlines(symbol, interval, startTime, endTime, limit = 1500) {
25484
+ async function fetchKlines(symbol, interval, startTime, endTime, limit = 1500) {
25439
25485
  const params = new URLSearchParams({
25440
25486
  symbol,
25441
25487
  interval,
@@ -25443,9 +25489,9 @@ async function fetchMarkPriceKlines(symbol, interval, startTime, endTime, limit
25443
25489
  endTime: String(endTime),
25444
25490
  limit: String(Math.min(limit, 1500))
25445
25491
  });
25446
- const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/markPriceKlines?${params}`);
25492
+ const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/klines?${params}`);
25447
25493
  if (!response.ok) {
25448
- throw new Error(`Binance markPriceKlines failed: ${response.status}`);
25494
+ throw new Error(`Binance klines failed: ${response.status}`);
25449
25495
  }
25450
25496
  const data = await response.json();
25451
25497
  return data.map((k) => ({
@@ -25465,7 +25511,7 @@ async function fetch24hrTicker(symbol) {
25465
25511
  const data = await response.json();
25466
25512
  return {
25467
25513
  lastPrice: parseFloat(data.lastPrice),
25468
- prevClosePrice: parseFloat(data.prevClosePrice),
25514
+ openPrice: parseFloat(data.openPrice),
25469
25515
  priceChangePercent: parseFloat(data.priceChangePercent)
25470
25516
  };
25471
25517
  }
@@ -25479,7 +25525,7 @@ async function fetch24hrTickers() {
25479
25525
  for (const item of data) {
25480
25526
  result[item.symbol] = {
25481
25527
  lastPrice: parseFloat(item.lastPrice),
25482
- prevClosePrice: parseFloat(item.prevClosePrice),
25528
+ openPrice: parseFloat(item.openPrice),
25483
25529
  priceChangePercent: parseFloat(item.priceChangePercent)
25484
25530
  };
25485
25531
  }
@@ -25515,7 +25561,7 @@ function useSymmTokenSelectionMarkets(params) {
25515
25561
  return;
25516
25562
  }
25517
25563
  const ticker = allTickers[binanceSymbol];
25518
- tickerSnapshots[symbol] = ticker ? { prevClosePrice: ticker.prevClosePrice } : null;
25564
+ tickerSnapshots[symbol] = ticker ? { openPrice: ticker.openPrice } : null;
25519
25565
  });
25520
25566
  return { tickerSnapshots };
25521
25567
  },
@@ -25528,7 +25574,7 @@ function useSymmTokenSelectionMarkets(params) {
25528
25574
  return baseMarkets.map((market) => {
25529
25575
  const symbol = market.symbol ?? "";
25530
25576
  const markPrice = symbol ? liveMarkPrices[symbol] ?? null : null;
25531
- const prevDayPrice = symbol ? snapshots[symbol]?.prevClosePrice ?? null : null;
25577
+ const prevDayPrice = symbol ? snapshots[symbol]?.openPrice ?? null : null;
25532
25578
  const priceChange24h = markPrice != null && prevDayPrice != null ? markPrice - prevDayPrice : null;
25533
25579
  const priceChange24hPercent = markPrice != null && prevDayPrice != null && prevDayPrice !== 0 ? (markPrice - prevDayPrice) / prevDayPrice * 100 : null;
25534
25580
  return {
@@ -25543,7 +25589,9 @@ function useSymmTokenSelectionMarkets(params) {
25543
25589
  }, [baseMarkets, liveMarkPrices, priceQuery.data]);
25544
25590
  const marketsBySymbol = useMemo(
25545
25591
  () => new Map(
25546
- markets.filter((m) => !!m.symbol).map((m) => [m.symbol, m])
25592
+ markets.filter(
25593
+ (m) => !!m.symbol
25594
+ ).map((m) => [m.symbol, m])
25547
25595
  ),
25548
25596
  [markets]
25549
25597
  );
@@ -25551,64 +25599,7 @@ function useSymmTokenSelectionMarkets(params) {
25551
25599
  () => new Map(markets.map((market) => [market.id, market])),
25552
25600
  [markets]
25553
25601
  );
25554
- useEffect(() => {
25555
- console.debug("[useSymmTokenSelectionMarkets] data flow", {
25556
- params,
25557
- query: {
25558
- status: query.status,
25559
- fetchStatus: query.fetchStatus,
25560
- isLoading: query.isLoading,
25561
- isSuccess: query.isSuccess,
25562
- isError: query.isError,
25563
- error: query.error
25564
- },
25565
- priceQuery: {
25566
- status: priceQuery.status,
25567
- fetchStatus: priceQuery.fetchStatus,
25568
- isLoading: priceQuery.isLoading,
25569
- isSuccess: priceQuery.isSuccess,
25570
- isError: priceQuery.isError,
25571
- error: priceQuery.error
25572
- },
25573
- marketSymbols,
25574
- symbolsKey,
25575
- liveMarkPrices,
25576
- tickerSnapshots: priceQuery.data?.tickerSnapshots ?? {},
25577
- result: {
25578
- marketCount: markets.length,
25579
- markets: markets.map((market) => ({
25580
- id: market.id,
25581
- symbol: market.symbol,
25582
- markPrice: market.markPrice,
25583
- prevDayPrice: market.prevDayPrice,
25584
- priceChange24h: market.priceChange24h,
25585
- priceChange24hPercent: market.priceChange24hPercent
25586
- }))
25587
- }
25588
- });
25589
- }, [
25590
- liveMarkPrices,
25591
- marketSymbols,
25592
- markets,
25593
- params,
25594
- priceQuery.data,
25595
- priceQuery.error,
25596
- priceQuery.fetchStatus,
25597
- priceQuery.isError,
25598
- priceQuery.isLoading,
25599
- priceQuery.isSuccess,
25600
- priceQuery.status,
25601
- query.error,
25602
- query.fetchStatus,
25603
- query.isError,
25604
- query.isLoading,
25605
- query.isSuccess,
25606
- query.status,
25607
- symbolsKey
25608
- ]);
25609
25602
  return {
25610
- query,
25611
- priceQuery,
25612
25603
  markets,
25613
25604
  marketsBySymbol,
25614
25605
  marketsById,
@@ -26146,10 +26137,10 @@ async function fetchTickerSnapshot(symbol) {
26146
26137
  const ticker = await fetch24hrTicker(resolution.binanceSymbol);
26147
26138
  if (!ticker) return null;
26148
26139
  return {
26149
- prevClosePrice: ticker.prevClosePrice
26140
+ openPrice: ticker.openPrice
26150
26141
  };
26151
26142
  }
26152
- function toSymmTokenMetadata(currentPrice, prevDayPrice) {
26143
+ function toSymmTokenMetadata(currentPrice, prevDayPrice, fundingRate, nextFundingTime) {
26153
26144
  const priceChange24h = currentPrice - prevDayPrice;
26154
26145
  const priceChange24hPercent = prevDayPrice !== 0 ? (currentPrice - prevDayPrice) / prevDayPrice * 100 : 0;
26155
26146
  return {
@@ -26157,9 +26148,10 @@ function toSymmTokenMetadata(currentPrice, prevDayPrice) {
26157
26148
  prevDayPrice,
26158
26149
  priceChange24h,
26159
26150
  priceChange24hPercent,
26160
- netFunding: 0,
26161
- // SYMM funding integration deferred
26162
- markPrice: currentPrice
26151
+ netFunding: fundingRate,
26152
+ markPrice: currentPrice,
26153
+ fundingRate,
26154
+ nextFundingTime
26163
26155
  };
26164
26156
  }
26165
26157
  function useSymmTokenSelectionMetadata(selection, options = {}) {
@@ -26173,6 +26165,12 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26173
26165
  const unavailableReason = isUnsupported ? `Binance market data is unavailable for ${unsupportedSymbols.join(", ")}.` : null;
26174
26166
  const symbolsKey = [...selectedSymbols].sort().join(",");
26175
26167
  const liveMarkPrices = useBinanceMarkPriceStore((state) => state.markPrices);
26168
+ const liveFundingRates = useBinanceMarkPriceStore(
26169
+ (state) => state.fundingRates
26170
+ );
26171
+ const liveNextFundingTimes = useBinanceMarkPriceStore(
26172
+ (state) => state.nextFundingTimes
26173
+ );
26176
26174
  const query = useQuery({
26177
26175
  queryKey: ["symm", "chart-metadata", symbolsKey],
26178
26176
  queryFn: async () => {
@@ -26205,13 +26203,27 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26205
26203
  const isLoading = query.isLoading;
26206
26204
  for (const token of longTokens) {
26207
26205
  const currentPrice = liveMarkPrices[token.symbol];
26206
+ const fundingRate = liveFundingRates[token.symbol];
26207
+ const nextFundingTime = liveNextFundingTimes[token.symbol];
26208
26208
  const ticker = tickerSnapshots[token.symbol];
26209
- longMeta[token.symbol] = currentPrice != null && ticker ? toSymmTokenMetadata(currentPrice, ticker.prevClosePrice) : null;
26209
+ longMeta[token.symbol] = currentPrice != null && ticker ? toSymmTokenMetadata(
26210
+ currentPrice,
26211
+ ticker.openPrice,
26212
+ fundingRate ?? 0,
26213
+ nextFundingTime ?? 0
26214
+ ) : null;
26210
26215
  }
26211
26216
  for (const token of shortTokens) {
26212
26217
  const currentPrice = liveMarkPrices[token.symbol];
26218
+ const fundingRate = liveFundingRates[token.symbol];
26219
+ const nextFundingTime = liveNextFundingTimes[token.symbol];
26213
26220
  const ticker = tickerSnapshots[token.symbol];
26214
- shortMeta[token.symbol] = currentPrice != null && ticker ? toSymmTokenMetadata(currentPrice, ticker.prevClosePrice) : null;
26221
+ shortMeta[token.symbol] = currentPrice != null && ticker ? toSymmTokenMetadata(
26222
+ currentPrice,
26223
+ ticker.openPrice,
26224
+ fundingRate ?? 0,
26225
+ nextFundingTime ?? 0
26226
+ ) : null;
26215
26227
  }
26216
26228
  const allLongReady = longTokens.every(
26217
26229
  (t) => longMeta[t.symbol]?.currentPrice != null
@@ -26249,60 +26261,17 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26249
26261
  }, [
26250
26262
  isUnsupported,
26251
26263
  longTokens,
26264
+ query,
26252
26265
  query.data,
26253
26266
  query.isLoading,
26254
26267
  shortTokens,
26255
26268
  unavailableReason,
26256
26269
  unsupportedSymbols,
26257
26270
  selectedSymbols.length,
26271
+ liveFundingRates,
26272
+ liveNextFundingTimes,
26258
26273
  liveMarkPrices
26259
26274
  ]);
26260
- useEffect(() => {
26261
- console.debug("[useSymmTokenSelectionMetadata] data flow", {
26262
- selection,
26263
- options,
26264
- query: {
26265
- status: query.status,
26266
- fetchStatus: query.fetchStatus,
26267
- isLoading: query.isLoading,
26268
- isSuccess: query.isSuccess,
26269
- isError: query.isError,
26270
- error: query.error
26271
- },
26272
- selectedSymbols,
26273
- unsupportedSymbols,
26274
- unavailableReason,
26275
- liveMarkPrices,
26276
- tickerSnapshots: query.data?.tickerSnapshots ?? {},
26277
- result: {
26278
- isLoading: result.isLoading,
26279
- isPriceDataReady: result.isPriceDataReady,
26280
- isUnsupported: result.isUnsupported,
26281
- longTokensMetadata: result.longTokensMetadata,
26282
- shortTokensMetadata: result.shortTokensMetadata,
26283
- weightedRatio: result.weightedRatio,
26284
- weightedRatio24h: result.weightedRatio24h,
26285
- priceRatio: result.priceRatio,
26286
- priceRatio24h: result.priceRatio24h,
26287
- sumNetFunding: result.sumNetFunding
26288
- }
26289
- });
26290
- }, [
26291
- liveMarkPrices,
26292
- options,
26293
- query.data,
26294
- query.error,
26295
- query.fetchStatus,
26296
- query.isError,
26297
- query.isLoading,
26298
- query.isSuccess,
26299
- query.status,
26300
- result,
26301
- selectedSymbols,
26302
- selection,
26303
- unavailableReason,
26304
- unsupportedSymbols
26305
- ]);
26306
26275
  return result;
26307
26276
  }
26308
26277
 
@@ -26332,10 +26301,13 @@ function toBinanceInterval(interval) {
26332
26301
  }
26333
26302
 
26334
26303
  // src/react/hooks/use-symm-chart-candles.ts
26304
+ function areIntervalsEqual(currentInterval, nextInterval) {
26305
+ return currentInterval === toBinanceInterval(nextInterval);
26306
+ }
26335
26307
  async function fetchSymbolKlines(symbol, interval, start, end) {
26336
26308
  const binanceSymbol = toBinanceSymbol(symbol);
26337
26309
  if (!binanceSymbol) return [];
26338
- const klines = await fetchMarkPriceKlines(
26310
+ const klines = await fetchKlines(
26339
26311
  binanceSymbol,
26340
26312
  toBinanceInterval(interval),
26341
26313
  start,
@@ -26496,6 +26468,15 @@ function useSymmChartCandles(selection) {
26496
26468
  wsUnsubsRef.current.push(unsub);
26497
26469
  }
26498
26470
  }, [emitRealtimeBar, isUnsupported, selectedSymbols]);
26471
+ const setRealtimeInterval = useCallback((interval) => {
26472
+ if (areIntervalsEqual(activeIntervalRef.current, interval)) {
26473
+ return;
26474
+ }
26475
+ activeIntervalRef.current = toBinanceInterval(interval);
26476
+ if (listenersRef.current.size > 0) {
26477
+ setupWsSubscriptions();
26478
+ }
26479
+ }, [setupWsSubscriptions]);
26499
26480
  useEffect(() => {
26500
26481
  if (listenersRef.current.size > 0) {
26501
26482
  setupWsSubscriptions();
@@ -26507,6 +26488,7 @@ function useSymmChartCandles(selection) {
26507
26488
  }, [setupWsSubscriptions]);
26508
26489
  const fetchBasketCandles = useCallback(
26509
26490
  async (start, end, interval) => {
26491
+ setRealtimeInterval(interval);
26510
26492
  const longSymbol = longTokens[0]?.symbol;
26511
26493
  const shortSymbol = shortTokens[0]?.symbol;
26512
26494
  if (isUnsupported) {
@@ -26532,18 +26514,20 @@ function useSymmChartCandles(selection) {
26532
26514
  Object.fromEntries(entries)
26533
26515
  );
26534
26516
  },
26535
- [isUnsupported, longTokens, shortTokens]
26517
+ [isUnsupported, longTokens, setRealtimeInterval, shortTokens]
26536
26518
  );
26537
26519
  const fetchPerformanceCandles = useCallback(
26538
26520
  async (start, end, interval, symbol) => {
26521
+ setRealtimeInterval(interval);
26539
26522
  const parts = symbol.split(" ");
26540
26523
  const assetSymbol = parts.length >= 2 ? parts.slice(1).join(" ") : symbol;
26541
26524
  return fetchSymbolKlines(assetSymbol, interval, start, end);
26542
26525
  },
26543
- []
26526
+ [setRealtimeInterval]
26544
26527
  );
26545
26528
  const fetchOverallPerformanceCandles = useCallback(
26546
26529
  async (start, end, interval) => {
26530
+ setRealtimeInterval(interval);
26547
26531
  const longSymbol = longTokens[0]?.symbol;
26548
26532
  const shortSymbol = shortTokens[0]?.symbol;
26549
26533
  if (isUnsupported) return [];
@@ -26600,7 +26584,7 @@ function useSymmChartCandles(selection) {
26600
26584
  }
26601
26585
  return result;
26602
26586
  },
26603
- [isUnsupported, longTokens, shortTokens]
26587
+ [isUnsupported, longTokens, setRealtimeInterval, shortTokens]
26604
26588
  );
26605
26589
  const addRealtimeListener = useCallback((cb) => {
26606
26590
  const id = Math.random().toString(36).slice(2);