@pear-protocol/symmio-client 0.2.23 → 0.2.26

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.
@@ -24936,11 +24936,10 @@ function useSymmMarkets(params) {
24936
24936
  ...params?.query,
24937
24937
  queryKey: symmKeys.markets(chainId, searchText),
24938
24938
  queryFn: () => {
24939
- const marketsApi = symmCoreClient.markets;
24940
24939
  if (searchText) {
24941
- return marketsApi.search(searchText, chainId);
24940
+ return symmCoreClient.markets.search(searchText, { chainId });
24942
24941
  }
24943
- return marketsApi.list({ pageSize: "500", chainId });
24942
+ return symmCoreClient.markets.list({ pageSize: "1000", chainId });
24944
24943
  },
24945
24944
  enabled: internalEnabled && (params?.query?.enabled ?? true)
24946
24945
  });
@@ -25004,634 +25003,128 @@ function useSymmHedgerMarkets(params) {
25004
25003
  enabled: internalEnabled && consumerEnabled
25005
25004
  });
25006
25005
  }
25007
- function useSymmFunding(params) {
25008
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25009
- const chainId = params?.chainId ?? ctxChainId;
25010
- const internalEnabled = !!symmCoreClient;
25011
- return reactQuery.useQuery({
25012
- ...params?.query,
25013
- queryKey: symmKeys.fundingRates(chainId),
25014
- queryFn: () => symmCoreClient.funding.getRates({ chainId }),
25015
- enabled: internalEnabled && (params?.query?.enabled ?? true)
25016
- });
25017
- }
25018
- function useSymmFundingHistory(params) {
25019
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25020
- const chainId = params.chainId ?? ctxChainId;
25021
- const {
25022
- period,
25023
- longSymbols,
25024
- shortSymbols,
25025
- interval,
25026
- weightMode,
25027
- includeContributions
25028
- } = params;
25029
- const internalEnabled = !!symmCoreClient;
25030
- const request = {
25031
- chainId,
25032
- period,
25033
- longSymbols,
25034
- shortSymbols,
25006
+
25007
+ // src/utils/binance-api.ts
25008
+ var BINANCE_FAPI_BASE = "https://fapi.binance.com";
25009
+ async function fetchMarkPriceKlines(symbol, interval, startTime, endTime, limit = 1500) {
25010
+ const params = new URLSearchParams({
25011
+ symbol,
25035
25012
  interval,
25036
- weightMode,
25037
- includeContributions
25038
- };
25039
- return reactQuery.useQuery({
25040
- ...params.query,
25041
- queryKey: symmKeys.fundingHistory(request),
25042
- queryFn: () => symmCoreClient.funding.getHistory(request),
25043
- enabled: internalEnabled && (params.query?.enabled ?? true)
25013
+ startTime: String(startTime),
25014
+ endTime: String(endTime),
25015
+ limit: String(Math.min(limit, 1500))
25044
25016
  });
25017
+ const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/markPriceKlines?${params}`);
25018
+ if (!response.ok) {
25019
+ throw new Error(`Binance markPriceKlines failed: ${response.status}`);
25020
+ }
25021
+ const data = await response.json();
25022
+ return data.map((k) => ({
25023
+ openTime: Number(k[0]),
25024
+ open: parseFloat(k[1]),
25025
+ high: parseFloat(k[2]),
25026
+ low: parseFloat(k[3]),
25027
+ close: parseFloat(k[4]),
25028
+ volume: parseFloat(k[5]),
25029
+ closeTime: Number(k[6])
25030
+ }));
25045
25031
  }
25046
- function useSymmFundingPayments(params) {
25047
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25048
- const { address, positionId, limit, offset } = params;
25049
- const chainId = params.chainId ?? ctxChainId;
25050
- const internalEnabled = !!symmCoreClient && !!(address || positionId);
25051
- const request = {
25052
- address,
25053
- positionId,
25054
- chainId,
25055
- limit,
25056
- offset
25032
+ async function fetch24hrTicker(symbol) {
25033
+ const params = new URLSearchParams({ symbol });
25034
+ const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/ticker/24hr?${params}`);
25035
+ if (!response.ok) return null;
25036
+ const data = await response.json();
25037
+ return {
25038
+ lastPrice: parseFloat(data.lastPrice),
25039
+ prevClosePrice: parseFloat(data.prevClosePrice),
25040
+ priceChangePercent: parseFloat(data.priceChangePercent)
25057
25041
  };
25058
- return reactQuery.useQuery({
25059
- ...params.query,
25060
- queryKey: symmKeys.fundingPayments({
25061
- address,
25062
- positionId,
25063
- chainId,
25064
- limit,
25065
- offset
25066
- }),
25067
- queryFn: () => symmCoreClient.funding.getPayments(request),
25068
- enabled: internalEnabled && (params.query?.enabled ?? true)
25069
- });
25070
- }
25071
- function useSymmPortfolio(params) {
25072
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25073
- const { accountAddress, address } = params;
25074
- const resolvedAddress = accountAddress ? void 0 : address;
25075
- const chainId = params.chainId ?? ctxChainId;
25076
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25077
- return reactQuery.useQuery({
25078
- ...params.query,
25079
- queryKey: symmKeys.portfolio({
25080
- accountAddress,
25081
- address: resolvedAddress,
25082
- chainId
25083
- }),
25084
- queryFn: () => {
25085
- const request = {
25086
- accountAddress,
25087
- address: resolvedAddress,
25088
- chainId
25089
- };
25090
- return symmCoreClient.portfolio.getMetrics(request);
25091
- },
25092
- enabled: internalEnabled && (params.query?.enabled ?? true)
25093
- });
25094
25042
  }
25095
- function useResolvedNotificationsParams(params) {
25096
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25097
- const chainId = params.chainId ?? ctxChainId;
25043
+
25044
+ // src/utils/binance-symbol-map.ts
25045
+ var SYMBOL_OVERRIDES = {
25046
+ // Add overrides here as needed for SYMM markets that don't map 1:1 to Binance
25047
+ // e.g., 'SOME_SYMM_SYMBOL': 'BINANCE_SYMBOL',
25048
+ };
25049
+ var UNSUPPORTED_SYMBOLS = /* @__PURE__ */ new Set([
25050
+ // Add symbols here that have no Binance equivalent
25051
+ ]);
25052
+ function resolveBinanceSymbol(symmSymbol) {
25053
+ if (!symmSymbol || !symmSymbol.trim()) {
25054
+ return {
25055
+ symmSymbol,
25056
+ normalizedSymbol: "",
25057
+ binanceSymbol: null,
25058
+ supported: false,
25059
+ reason: "missing_symbol"
25060
+ };
25061
+ }
25062
+ const normalized = symmSymbol.toUpperCase().trim();
25063
+ if (!/^[A-Z0-9]+$/.test(normalized)) {
25064
+ return {
25065
+ symmSymbol,
25066
+ normalizedSymbol: normalized,
25067
+ binanceSymbol: null,
25068
+ supported: false,
25069
+ reason: "invalid_symbol"
25070
+ };
25071
+ }
25072
+ if (UNSUPPORTED_SYMBOLS.has(normalized)) {
25073
+ return {
25074
+ symmSymbol,
25075
+ normalizedSymbol: normalized,
25076
+ binanceSymbol: null,
25077
+ supported: false,
25078
+ reason: "unsupported_symbol"
25079
+ };
25080
+ }
25081
+ const binanceSymbol = SYMBOL_OVERRIDES[normalized] ?? (normalized.endsWith("USDT") ? normalized : `${normalized}USDT`);
25098
25082
  return {
25099
- symmCoreClient,
25100
- chainId,
25101
- userAddress: params.userAddress,
25102
- query: params.query
25083
+ symmSymbol,
25084
+ normalizedSymbol: normalized,
25085
+ binanceSymbol,
25086
+ supported: true,
25087
+ reason: null
25103
25088
  };
25104
25089
  }
25105
- function useSymmNotificationsQuery(params) {
25106
- const { symmCoreClient, chainId, userAddress, query } = useResolvedNotificationsParams(params);
25107
- const internalEnabled = !!symmCoreClient && !!userAddress;
25108
- return reactQuery.useQuery({
25109
- ...query,
25110
- queryKey: symmKeys.notifications(userAddress, chainId),
25111
- queryFn: () => symmCoreClient.notifications.list({
25112
- userAddress,
25113
- chainId
25114
- }),
25115
- enabled: internalEnabled && (query?.enabled ?? true)
25116
- });
25090
+ function getUnsupportedBinanceSymbols(symbols) {
25091
+ return symbols.filter((symbol) => !resolveBinanceSymbol(symbol).supported);
25117
25092
  }
25118
- function useSymmUnreadCountQuery(params) {
25119
- const { symmCoreClient, chainId, userAddress, query } = useResolvedNotificationsParams(params);
25120
- const internalEnabled = !!symmCoreClient && !!userAddress;
25121
- return reactQuery.useQuery({
25122
- ...query,
25123
- queryKey: symmKeys.unreadCount(userAddress, chainId),
25124
- queryFn: () => symmCoreClient.notifications.getUnreadCount({
25125
- userAddress,
25126
- chainId
25127
- }),
25128
- enabled: internalEnabled && (query?.enabled ?? true)
25129
- });
25093
+ function toBinanceSymbol(symmSymbol) {
25094
+ return resolveBinanceSymbol(symmSymbol).binanceSymbol;
25130
25095
  }
25131
- function useSymmMarkReadNotificationMutation(params, options) {
25132
- const { symmCoreClient, chainId, userAddress } = useResolvedNotificationsParams(params);
25133
- const queryClient = reactQuery.useQueryClient();
25134
- return reactQuery.useMutation({
25135
- ...withSymmMutationConfig(options?.mutation, {
25136
- onSuccess: () => {
25137
- queryClient.invalidateQueries({
25138
- queryKey: symmKeys.notifications(userAddress, chainId)
25139
- });
25140
- queryClient.invalidateQueries({
25141
- queryKey: symmKeys.unreadCount(userAddress, chainId)
25142
- });
25143
- }
25144
- }),
25145
- mutationFn: async ({ id, timestamp }) => {
25146
- if (!symmCoreClient || !userAddress) {
25147
- throw new Error("symm-core client not available");
25148
- }
25149
- return symmCoreClient.notifications.markRead({
25150
- id,
25151
- timestamp,
25152
- userAddress,
25153
- chainId
25154
- });
25155
- }
25156
- });
25157
- }
25158
- function useSymmPendingIds(params) {
25159
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25160
- const { accountAddress } = params;
25161
- const chainId = params.chainId ?? ctxChainId;
25162
- const internalEnabled = !!symmCoreClient && !!accountAddress;
25163
- return reactQuery.useQuery({
25164
- ...params.query,
25165
- queryKey: symmKeys.pendingIds(accountAddress, chainId),
25166
- queryFn: () => symmCoreClient.positions.getPendingIds({
25167
- address: accountAddress,
25168
- chainId
25169
- }),
25170
- enabled: internalEnabled && (params.query?.enabled ?? true)
25171
- });
25172
- }
25173
- function useSymmPendingInstantOpens(params) {
25174
- const {
25175
- symmCoreClient,
25176
- chainId: ctxChainId
25177
- } = useSymmContext();
25178
- const { accountAddress, authToken: providedAuthToken } = params;
25179
- const chainId = params.chainId ?? ctxChainId;
25180
- const internalEnabled = !!symmCoreClient && !!accountAddress;
25181
- return reactQuery.useQuery({
25182
- ...params.query,
25183
- queryKey: symmKeys.pendingInstantOpens(accountAddress, chainId),
25184
- queryFn: async () => {
25185
- const authToken = providedAuthToken ?? useSymmAuthStore.getState().getToken(accountAddress, chainId);
25186
- if (!authToken) {
25187
- throw new Error("failed to acquire auth token for pending instant opens");
25188
- }
25189
- return symmCoreClient.positions.getPendingInstantOpens({
25190
- accountAddress,
25191
- chainId,
25192
- authToken
25193
- });
25194
- },
25195
- enabled: internalEnabled && (params.query?.enabled ?? true)
25196
- });
25197
- }
25198
- function useSymmTwapOrder(params) {
25199
- const { symmCoreClient } = useSymmContext();
25200
- const { orderId } = params;
25201
- const internalEnabled = !!symmCoreClient && !!orderId;
25202
- return reactQuery.useQuery({
25203
- ...params.query,
25204
- queryKey: symmKeys.twapOrder(orderId),
25205
- queryFn: () => symmCoreClient.orders.getTwapOrder(orderId),
25206
- enabled: internalEnabled && (params.query?.enabled ?? true)
25207
- });
25208
- }
25209
- var useSymmWsStore = zustand.create((set) => ({
25210
- isConnected: false,
25211
- setConnected: (isConnected) => set({ isConnected })
25212
- }));
25213
-
25214
- // src/react/hooks/use-symm-ws.ts
25215
- function asUnsubscribeFn(value) {
25216
- return typeof value === "function" ? value : null;
25217
- }
25218
- function useSymmWs(params = {}) {
25219
- const {
25220
- symmCoreClient: ctxClient,
25221
- address: ctxAddress,
25222
- chainId: ctxChainId
25223
- } = useSymmContext();
25224
- const queryClient = reactQuery.useQueryClient();
25225
- const isConnected = useSymmWsStore((state) => state.isConnected);
25226
- const setConnected = useSymmWsStore((state) => state.setConnected);
25227
- const symmCoreClient = params.symmCoreClient ?? ctxClient;
25228
- const accountAddress = params.accountAddress ?? ctxAddress;
25229
- const chainId = params.chainId ?? ctxChainId;
25230
- react.useEffect(() => {
25231
- if (!symmCoreClient || !accountAddress) {
25232
- setConnected(false);
25233
- return;
25234
- }
25235
- const ws = symmCoreClient.ws;
25236
- const addr = accountAddress;
25237
- const unsubscribers = [];
25238
- const removeOnConnect = ws.onConnect(() => setConnected(true));
25239
- const removeOnDisconnect = ws.onDisconnect(() => setConnected(false));
25240
- unsubscribers.push(removeOnConnect, removeOnDisconnect);
25241
- const positionsUnsub = asUnsubscribeFn(
25242
- ws.subscribeToPositions(addr, chainId, () => {
25243
- queryClient.invalidateQueries({
25244
- queryKey: ["symm", "positions"]
25245
- });
25246
- })
25247
- );
25248
- if (positionsUnsub) unsubscribers.push(positionsUnsub);
25249
- const openOrdersUnsub = asUnsubscribeFn(
25250
- ws.subscribeToOpenOrders(addr, chainId, () => {
25251
- queryClient.invalidateQueries({
25252
- queryKey: ["symm", "openOrders"]
25253
- });
25254
- })
25255
- );
25256
- if (openOrdersUnsub) unsubscribers.push(openOrdersUnsub);
25257
- const tradesUnsub = asUnsubscribeFn(
25258
- ws.subscribeToTrades(addr, chainId, () => {
25259
- queryClient.invalidateQueries({
25260
- queryKey: ["symm", "tradeHistory"]
25261
- });
25262
- })
25263
- );
25264
- if (tradesUnsub) unsubscribers.push(tradesUnsub);
25265
- const accountSummaryUnsub = asUnsubscribeFn(
25266
- ws.subscribeToAccountSummary(addr, chainId, () => {
25267
- queryClient.invalidateQueries({
25268
- queryKey: symmKeys.balances(accountAddress, chainId)
25269
- });
25270
- queryClient.invalidateQueries({
25271
- queryKey: symmKeys.accountSummary(accountAddress, chainId)
25272
- });
25273
- })
25274
- );
25275
- if (accountSummaryUnsub) unsubscribers.push(accountSummaryUnsub);
25276
- const notificationsUnsub = asUnsubscribeFn(
25277
- ws.subscribeToNotifications(addr, chainId, () => {
25278
- queryClient.invalidateQueries({
25279
- queryKey: symmKeys.notifications(accountAddress, chainId)
25280
- });
25281
- queryClient.invalidateQueries({
25282
- queryKey: symmKeys.unreadCount(accountAddress, chainId)
25283
- });
25284
- })
25285
- );
25286
- if (notificationsUnsub) unsubscribers.push(notificationsUnsub);
25287
- const tpslUnsub = asUnsubscribeFn(
25288
- ws.subscribeToTpsl(addr, chainId, () => {
25289
- queryClient.invalidateQueries({ queryKey: ["symm", "tpslOrders"] });
25290
- queryClient.invalidateQueries({ queryKey: ["symm", "openOrders"] });
25291
- })
25292
- );
25293
- if (tpslUnsub) unsubscribers.push(tpslUnsub);
25294
- const twapUnsub = asUnsubscribeFn(
25295
- ws.subscribeToTwapOrders(addr, chainId, () => {
25296
- queryClient.invalidateQueries({ queryKey: ["symm", "twapOrders"] });
25297
- queryClient.invalidateQueries({ queryKey: ["symm", "openOrders"] });
25298
- })
25299
- );
25300
- if (twapUnsub) unsubscribers.push(twapUnsub);
25301
- const triggerOrdersUnsub = asUnsubscribeFn(
25302
- ws.subscribeToTriggerOrders(addr, chainId, () => {
25303
- queryClient.invalidateQueries({ queryKey: ["symm", "triggerOrders"] });
25304
- queryClient.invalidateQueries({ queryKey: ["symm", "openOrders"] });
25305
- })
25306
- );
25307
- if (triggerOrdersUnsub) unsubscribers.push(triggerOrdersUnsub);
25308
- const executionsUnsub = asUnsubscribeFn(
25309
- ws.subscribeToExecutions(addr, chainId, () => {
25310
- queryClient.invalidateQueries({
25311
- queryKey: ["symm", "positions"]
25312
- });
25313
- queryClient.invalidateQueries({
25314
- queryKey: ["symm", "portfolio"]
25315
- });
25316
- })
25317
- );
25318
- if (executionsUnsub) unsubscribers.push(executionsUnsub);
25319
- return () => {
25320
- unsubscribers.forEach((unsubscribe) => unsubscribe());
25321
- };
25322
- }, [symmCoreClient, accountAddress, chainId, queryClient, setConnected]);
25323
- return { isConnected };
25324
- }
25325
- var STABLE_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USD", "USDT", "USDE", "USDH", "USDT0"]);
25326
- function useSymmChartSelection(input) {
25327
- const {
25328
- longSymbol,
25329
- shortSymbol,
25330
- longWeight = 100,
25331
- shortWeight = 100,
25332
- longTokens: explicitLongTokens,
25333
- shortTokens: explicitShortTokens
25334
- } = input;
25335
- return react.useMemo(() => {
25336
- const longTokens = explicitLongTokens?.length ? explicitLongTokens : longSymbol ? [{ symbol: longSymbol, weight: longWeight }] : [];
25337
- const shortTokens = explicitShortTokens?.length ? explicitShortTokens : shortSymbol ? [{ symbol: shortSymbol, weight: shortWeight }] : [];
25338
- const selectedSymbols = [
25339
- ...longTokens.map((t) => t.symbol),
25340
- ...shortTokens.map((t) => t.symbol)
25341
- ];
25342
- const uniqueSelectedSymbols = Array.from(new Set(selectedSymbols));
25343
- const longPrimarySymbol = longTokens[0]?.symbol ?? null;
25344
- const shortPrimarySymbol = shortTokens[0]?.symbol ?? null;
25345
- const longIsStable = longPrimarySymbol ? STABLE_SYMBOLS.has(longPrimarySymbol.toUpperCase()) : true;
25346
- const shortIsStable = shortPrimarySymbol ? STABLE_SYMBOLS.has(shortPrimarySymbol.toUpperCase()) : true;
25347
- let isSingleSided = false;
25348
- let activeSide = null;
25349
- let activeTokenSymbol;
25350
- if (longTokens.length > 0 && !longIsStable && (shortTokens.length === 0 || shortIsStable)) {
25351
- isSingleSided = true;
25352
- activeSide = "long";
25353
- activeTokenSymbol = longPrimarySymbol ?? void 0;
25354
- } else if (shortTokens.length > 0 && !shortIsStable && (longTokens.length === 0 || longIsStable)) {
25355
- isSingleSided = true;
25356
- activeSide = "short";
25357
- activeTokenSymbol = shortPrimarySymbol ?? void 0;
25358
- }
25359
- const longLabel = longTokens.map((token) => token.symbol).join("+") || "USDC";
25360
- const shortLabel = shortTokens.map((token) => token.symbol).join("+") || "USDC";
25361
- const pairLabel = `${longLabel}/${shortLabel}`;
25362
- return {
25363
- longTokens,
25364
- shortTokens,
25365
- isSingleSided,
25366
- activeSide,
25367
- activeTokenSymbol,
25368
- pairLabel,
25369
- selectedSymbols: uniqueSelectedSymbols
25370
- };
25371
- }, [
25372
- explicitLongTokens,
25373
- explicitShortTokens,
25374
- longSymbol,
25375
- shortSymbol,
25376
- longWeight,
25377
- shortWeight
25378
- ]);
25379
- }
25380
-
25381
- // src/utils/binance-symbol-map.ts
25382
- var SYMBOL_OVERRIDES = {
25383
- // Add overrides here as needed for SYMM markets that don't map 1:1 to Binance
25384
- // e.g., 'SOME_SYMM_SYMBOL': 'BINANCE_SYMBOL',
25385
- };
25386
- var UNSUPPORTED_SYMBOLS = /* @__PURE__ */ new Set([
25387
- // Add symbols here that have no Binance equivalent
25388
- ]);
25389
- function resolveBinanceSymbol(symmSymbol) {
25390
- if (!symmSymbol || !symmSymbol.trim()) {
25391
- return {
25392
- symmSymbol,
25393
- normalizedSymbol: "",
25394
- binanceSymbol: null,
25395
- supported: false,
25396
- reason: "missing_symbol"
25397
- };
25398
- }
25399
- const normalized = symmSymbol.toUpperCase().trim();
25400
- if (!/^[A-Z0-9]+$/.test(normalized)) {
25401
- return {
25402
- symmSymbol,
25403
- normalizedSymbol: normalized,
25404
- binanceSymbol: null,
25405
- supported: false,
25406
- reason: "invalid_symbol"
25407
- };
25408
- }
25409
- if (UNSUPPORTED_SYMBOLS.has(normalized)) {
25410
- return {
25411
- symmSymbol,
25412
- normalizedSymbol: normalized,
25413
- binanceSymbol: null,
25414
- supported: false,
25415
- reason: "unsupported_symbol"
25416
- };
25417
- }
25418
- const binanceSymbol = SYMBOL_OVERRIDES[normalized] ?? (normalized.endsWith("USDT") ? normalized : `${normalized}USDT`);
25419
- return {
25420
- symmSymbol,
25421
- normalizedSymbol: normalized,
25422
- binanceSymbol,
25423
- supported: true,
25424
- reason: null
25425
- };
25426
- }
25427
- function getUnsupportedBinanceSymbols(symbols) {
25428
- return symbols.filter((symbol) => !resolveBinanceSymbol(symbol).supported);
25429
- }
25430
- function toBinanceSymbol(symmSymbol) {
25431
- return resolveBinanceSymbol(symmSymbol).binanceSymbol;
25432
- }
25433
-
25434
- // src/utils/binance-api.ts
25435
- var BINANCE_FAPI_BASE = "https://fapi.binance.com";
25436
- async function fetchMarkPriceKlines(symbol, interval, startTime, endTime, limit = 1500) {
25437
- const params = new URLSearchParams({
25438
- symbol,
25439
- interval,
25440
- startTime: String(startTime),
25441
- endTime: String(endTime),
25442
- limit: String(Math.min(limit, 1500))
25443
- });
25444
- const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/markPriceKlines?${params}`);
25445
- if (!response.ok) {
25446
- throw new Error(`Binance markPriceKlines failed: ${response.status}`);
25447
- }
25448
- const data = await response.json();
25449
- return data.map((k) => ({
25450
- openTime: Number(k[0]),
25451
- open: parseFloat(k[1]),
25452
- high: parseFloat(k[2]),
25453
- low: parseFloat(k[3]),
25454
- close: parseFloat(k[4]),
25455
- volume: parseFloat(k[5]),
25456
- closeTime: Number(k[6])
25457
- }));
25458
- }
25459
- async function fetch24hrTicker(symbol) {
25460
- const params = new URLSearchParams({ symbol });
25461
- const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/ticker/24hr?${params}`);
25462
- if (!response.ok) return null;
25463
- const data = await response.json();
25464
- return {
25465
- lastPrice: parseFloat(data.lastPrice),
25466
- prevClosePrice: parseFloat(data.prevClosePrice),
25467
- priceChangePercent: parseFloat(data.priceChangePercent)
25468
- };
25469
- }
25470
-
25471
- // src/utils/chart-metrics.ts
25472
- function getPositiveValue(metadata, field) {
25473
- const value = metadata?.[field];
25474
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
25475
- return null;
25476
- }
25477
- return value;
25478
- }
25479
- function computeWeightedProduct(tokens, metadataMap, field, invert = false) {
25480
- let product = 1;
25481
- let hasToken = false;
25482
- for (const token of tokens) {
25483
- if (!(token.weight > 0)) {
25484
- continue;
25485
- }
25486
- const price = getPositiveValue(metadataMap[token.symbol], field);
25487
- if (price === null) {
25488
- return null;
25489
- }
25490
- hasToken = true;
25491
- const exponent = invert ? -(token.weight / 100) : token.weight / 100;
25492
- product *= Math.pow(price, exponent);
25493
- }
25494
- return hasToken ? product : 1;
25495
- }
25496
- function computePriceRatio({
25497
- longTokens,
25498
- shortTokens,
25499
- longTokensMetadata,
25500
- shortTokensMetadata
25501
- }) {
25502
- const firstLong = longTokens[0];
25503
- const firstShort = shortTokens[0];
25504
- const longPrice = firstLong ? getPositiveValue(longTokensMetadata[firstLong.symbol], "currentPrice") : null;
25505
- const shortPrice = firstShort ? getPositiveValue(shortTokensMetadata[firstShort.symbol], "currentPrice") : null;
25506
- if (longPrice !== null && shortPrice !== null) {
25507
- return longPrice / shortPrice;
25508
- }
25509
- if (longPrice !== null && shortTokens.length === 0) {
25510
- return longPrice;
25511
- }
25512
- if (shortPrice !== null && longTokens.length === 0) {
25513
- return shortPrice;
25514
- }
25515
- return null;
25516
- }
25517
- function computePriceRatio24h({
25518
- longTokens,
25519
- shortTokens,
25520
- longTokensMetadata,
25521
- shortTokensMetadata
25522
- }) {
25523
- const firstLong = longTokens[0];
25524
- const firstShort = shortTokens[0];
25525
- const longPrice = firstLong ? getPositiveValue(longTokensMetadata[firstLong.symbol], "prevDayPrice") : null;
25526
- const shortPrice = firstShort ? getPositiveValue(shortTokensMetadata[firstShort.symbol], "prevDayPrice") : null;
25527
- if (longPrice !== null && shortPrice !== null) {
25528
- return longPrice / shortPrice;
25529
- }
25530
- if (longPrice !== null && shortTokens.length === 0) {
25531
- return longPrice;
25532
- }
25533
- if (shortPrice !== null && longTokens.length === 0) {
25534
- return shortPrice;
25535
- }
25536
- return null;
25537
- }
25538
- function computeWeightedRatio({
25539
- longTokens,
25540
- shortTokens,
25541
- longTokensMetadata,
25542
- shortTokensMetadata
25543
- }) {
25544
- const longProduct = computeWeightedProduct(
25545
- longTokens,
25546
- longTokensMetadata,
25547
- "currentPrice"
25548
- );
25549
- const shortProduct = computeWeightedProduct(
25550
- shortTokens,
25551
- shortTokensMetadata,
25552
- "currentPrice",
25553
- true
25554
- );
25555
- if (longProduct === null || shortProduct === null) {
25556
- return null;
25557
- }
25558
- return longProduct * shortProduct;
25559
- }
25560
- function computeWeightedRatio24h({
25561
- longTokens,
25562
- shortTokens,
25563
- longTokensMetadata,
25564
- shortTokensMetadata
25565
- }) {
25566
- const longProduct = computeWeightedProduct(
25567
- longTokens,
25568
- longTokensMetadata,
25569
- "prevDayPrice"
25570
- );
25571
- const shortProduct = computeWeightedProduct(
25572
- shortTokens,
25573
- shortTokensMetadata,
25574
- "prevDayPrice",
25575
- true
25576
- );
25577
- if (longProduct === null || shortProduct === null) {
25578
- return null;
25579
- }
25580
- return longProduct * shortProduct;
25581
- }
25582
- function computeNetFundingSum({
25583
- longTokens,
25584
- shortTokens,
25585
- longTokensMetadata,
25586
- shortTokensMetadata
25587
- }) {
25588
- let funding = 0;
25589
- for (const token of longTokens) {
25590
- const value = longTokensMetadata[token.symbol]?.netFunding;
25591
- if (typeof value === "number" && Number.isFinite(value)) {
25592
- funding += value;
25593
- }
25594
- }
25595
- for (const token of shortTokens) {
25596
- const value = shortTokensMetadata[token.symbol]?.netFunding;
25597
- if (typeof value === "number" && Number.isFinite(value)) {
25598
- funding += value;
25599
- }
25600
- }
25601
- return funding;
25602
- }
25603
-
25604
- // src/utils/binance-ws.ts
25605
- var BINANCE_WS_URL = "wss://fstream.binance.com/ws";
25606
- var RECONNECT_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
25607
- var BinanceWsManager = class {
25608
- ws = null;
25609
- streams = /* @__PURE__ */ new Map();
25610
- reconnectAttempt = 0;
25611
- reconnectTimer = null;
25612
- intentionalClose = false;
25613
- pendingSubscribes = [];
25614
- idCounter = 0;
25615
- /**
25616
- * Subscribe to a kline stream. Returns an unsubscribe function.
25617
- */
25618
- subscribeKline(symbol, interval, cb) {
25619
- const streamName = `${symbol.toLowerCase()}@kline_${interval}`;
25620
- const id = this.generateId();
25621
- const wrappedCb = (raw) => {
25622
- const k = raw.k;
25623
- if (!k) return;
25624
- cb({
25625
- symbol: raw.s,
25626
- interval: k.i,
25627
- openTime: k.t,
25628
- closeTime: k.T,
25629
- open: parseFloat(k.o),
25630
- high: parseFloat(k.h),
25631
- low: parseFloat(k.l),
25632
- close: parseFloat(k.c),
25633
- volume: parseFloat(k.v),
25634
- isFinal: k.x
25096
+
25097
+ // src/utils/binance-ws.ts
25098
+ var BINANCE_WS_URL = "wss://fstream.binance.com/ws";
25099
+ var RECONNECT_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
25100
+ var BinanceWsManager = class {
25101
+ ws = null;
25102
+ streams = /* @__PURE__ */ new Map();
25103
+ reconnectAttempt = 0;
25104
+ reconnectTimer = null;
25105
+ intentionalClose = false;
25106
+ pendingSubscribes = [];
25107
+ idCounter = 0;
25108
+ /**
25109
+ * Subscribe to a kline stream. Returns an unsubscribe function.
25110
+ */
25111
+ subscribeKline(symbol, interval, cb) {
25112
+ const streamName = `${symbol.toLowerCase()}@kline_${interval}`;
25113
+ const id = this.generateId();
25114
+ const wrappedCb = (raw) => {
25115
+ const k = raw.k;
25116
+ if (!k) return;
25117
+ cb({
25118
+ symbol: raw.s,
25119
+ interval: k.i,
25120
+ openTime: k.t,
25121
+ closeTime: k.T,
25122
+ open: parseFloat(k.o),
25123
+ high: parseFloat(k.h),
25124
+ low: parseFloat(k.l),
25125
+ close: parseFloat(k.c),
25126
+ volume: parseFloat(k.v),
25127
+ isFinal: k.x
25635
25128
  });
25636
25129
  };
25637
25130
  this.addStreamCallback(streamName, id, wrappedCb);
@@ -25650,225 +25143,837 @@ var BinanceWsManager = class {
25650
25143
  indexPrice: parseFloat(raw.i),
25651
25144
  time: raw.E
25652
25145
  });
25653
- };
25654
- this.addStreamCallback(streamName, id, wrappedCb);
25655
- return () => this.removeStreamCallback(streamName, id);
25656
- }
25657
- /**
25658
- * Destroy the manager and close the connection.
25659
- */
25660
- destroy() {
25661
- this.intentionalClose = true;
25662
- if (this.reconnectTimer) {
25663
- clearTimeout(this.reconnectTimer);
25664
- this.reconnectTimer = null;
25665
- }
25666
- if (this.ws) {
25667
- this.ws.close();
25668
- this.ws = null;
25146
+ };
25147
+ this.addStreamCallback(streamName, id, wrappedCb);
25148
+ return () => this.removeStreamCallback(streamName, id);
25149
+ }
25150
+ /**
25151
+ * Destroy the manager and close the connection.
25152
+ */
25153
+ destroy() {
25154
+ this.intentionalClose = true;
25155
+ if (this.reconnectTimer) {
25156
+ clearTimeout(this.reconnectTimer);
25157
+ this.reconnectTimer = null;
25158
+ }
25159
+ if (this.ws) {
25160
+ this.ws.close();
25161
+ this.ws = null;
25162
+ }
25163
+ this.streams.clear();
25164
+ }
25165
+ // --- Private ---
25166
+ generateId() {
25167
+ return `sub_${++this.idCounter}_${Date.now()}`;
25168
+ }
25169
+ addStreamCallback(streamName, id, cb) {
25170
+ let sub = this.streams.get(streamName);
25171
+ const isNew = !sub;
25172
+ if (!sub) {
25173
+ sub = { callbacks: /* @__PURE__ */ new Map() };
25174
+ this.streams.set(streamName, sub);
25175
+ }
25176
+ sub.callbacks.set(id, cb);
25177
+ if (isNew) {
25178
+ this.ensureConnected();
25179
+ this.sendSubscribe([streamName]);
25180
+ }
25181
+ }
25182
+ removeStreamCallback(streamName, id) {
25183
+ const sub = this.streams.get(streamName);
25184
+ if (!sub) return;
25185
+ sub.callbacks.delete(id);
25186
+ if (sub.callbacks.size === 0) {
25187
+ this.streams.delete(streamName);
25188
+ this.sendUnsubscribe([streamName]);
25189
+ if (this.streams.size === 0 && this.ws) {
25190
+ this.intentionalClose = true;
25191
+ this.ws.close();
25192
+ this.ws = null;
25193
+ this.intentionalClose = false;
25194
+ }
25195
+ }
25196
+ }
25197
+ ensureConnected() {
25198
+ if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
25199
+ return;
25200
+ }
25201
+ this.connect();
25202
+ }
25203
+ connect() {
25204
+ if (typeof WebSocket === "undefined") return;
25205
+ this.intentionalClose = false;
25206
+ this.ws = new WebSocket(BINANCE_WS_URL);
25207
+ this.ws.onopen = () => {
25208
+ this.reconnectAttempt = 0;
25209
+ const activeStreams = Array.from(this.streams.keys());
25210
+ if (activeStreams.length > 0) {
25211
+ this.sendSubscribe(activeStreams);
25212
+ }
25213
+ if (this.pendingSubscribes.length > 0) {
25214
+ this.sendSubscribe(this.pendingSubscribes);
25215
+ this.pendingSubscribes = [];
25216
+ }
25217
+ };
25218
+ this.ws.onmessage = (event) => {
25219
+ try {
25220
+ const data = JSON.parse(event.data);
25221
+ this.handleMessage(data);
25222
+ } catch {
25223
+ }
25224
+ };
25225
+ this.ws.onclose = () => {
25226
+ if (this.intentionalClose) return;
25227
+ this.scheduleReconnect();
25228
+ };
25229
+ this.ws.onerror = () => {
25230
+ };
25231
+ }
25232
+ handleMessage(data) {
25233
+ if (data.e === "kline") {
25234
+ const k = data.k;
25235
+ const streamName = `${data.s.toLowerCase()}@kline_${k.i}`;
25236
+ this.dispatchToStream(streamName, data);
25237
+ } else if (data.e === "markPriceUpdate") {
25238
+ const streamName = `${data.s.toLowerCase()}@markPrice@1s`;
25239
+ this.dispatchToStream(streamName, data);
25240
+ }
25241
+ }
25242
+ dispatchToStream(streamName, data) {
25243
+ const sub = this.streams.get(streamName);
25244
+ if (!sub) return;
25245
+ sub.callbacks.forEach((cb) => {
25246
+ try {
25247
+ cb(data);
25248
+ } catch {
25249
+ }
25250
+ });
25251
+ }
25252
+ sendSubscribe(streams) {
25253
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
25254
+ this.pendingSubscribes.push(...streams);
25255
+ return;
25256
+ }
25257
+ this.ws.send(JSON.stringify({
25258
+ method: "SUBSCRIBE",
25259
+ params: streams,
25260
+ id: Date.now()
25261
+ }));
25262
+ }
25263
+ sendUnsubscribe(streams) {
25264
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
25265
+ this.pendingSubscribes = this.pendingSubscribes.filter(
25266
+ (s) => !streams.includes(s)
25267
+ );
25268
+ return;
25269
+ }
25270
+ this.ws.send(JSON.stringify({
25271
+ method: "UNSUBSCRIBE",
25272
+ params: streams,
25273
+ id: Date.now()
25274
+ }));
25275
+ }
25276
+ scheduleReconnect() {
25277
+ if (this.reconnectTimer) return;
25278
+ if (this.streams.size === 0) return;
25279
+ const delay = RECONNECT_DELAYS[Math.min(this.reconnectAttempt, RECONNECT_DELAYS.length - 1)];
25280
+ this.reconnectAttempt++;
25281
+ this.reconnectTimer = setTimeout(() => {
25282
+ this.reconnectTimer = null;
25283
+ this.connect();
25284
+ }, delay);
25285
+ }
25286
+ };
25287
+ var _instance = null;
25288
+ function getBinanceWsManager() {
25289
+ if (!_instance) {
25290
+ _instance = new BinanceWsManager();
25291
+ }
25292
+ return _instance;
25293
+ }
25294
+
25295
+ // src/react/stores/use-binance-mark-price-store.ts
25296
+ var refCounts = /* @__PURE__ */ new Map();
25297
+ var streamUnsubs = /* @__PURE__ */ new Map();
25298
+ var streamSymbols = /* @__PURE__ */ new Map();
25299
+ function normalizeBinanceSymbol(symbol) {
25300
+ return symbol.toUpperCase().trim();
25301
+ }
25302
+ function getNextRefCount(binanceSymbol) {
25303
+ return (refCounts.get(binanceSymbol) ?? 0) + 1;
25304
+ }
25305
+ function getPrevRefCount(binanceSymbol) {
25306
+ return Math.max(0, (refCounts.get(binanceSymbol) ?? 0) - 1);
25307
+ }
25308
+ var useBinanceMarkPriceStore = zustand.create((set) => ({
25309
+ markPrices: {},
25310
+ subscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
25311
+ const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
25312
+ const nextRefCount = getNextRefCount(binanceSymbol);
25313
+ refCounts.set(binanceSymbol, nextRefCount);
25314
+ const symbols = streamSymbols.get(binanceSymbol) ?? /* @__PURE__ */ new Set();
25315
+ symbols.add(symmSymbol);
25316
+ streamSymbols.set(binanceSymbol, symbols);
25317
+ if (nextRefCount === 1) {
25318
+ const wsManager = getBinanceWsManager();
25319
+ const unsubscribe = wsManager.subscribeMarkPrice(binanceSymbol, (data) => {
25320
+ const canonicalSymbol = normalizeBinanceSymbol(data.symbol);
25321
+ const mappedSymbols = streamSymbols.get(canonicalSymbol);
25322
+ if (!mappedSymbols || mappedSymbols.size === 0) return;
25323
+ set((state) => {
25324
+ const nextMarkPrices = { ...state.markPrices };
25325
+ mappedSymbols.forEach((mappedSymbol) => {
25326
+ nextMarkPrices[mappedSymbol] = data.markPrice;
25327
+ });
25328
+ return { markPrices: nextMarkPrices };
25329
+ });
25330
+ });
25331
+ streamUnsubs.set(binanceSymbol, unsubscribe);
25669
25332
  }
25670
- this.streams.clear();
25671
- }
25672
- // --- Private ---
25673
- generateId() {
25674
- return `sub_${++this.idCounter}_${Date.now()}`;
25675
- }
25676
- addStreamCallback(streamName, id, cb) {
25677
- let sub = this.streams.get(streamName);
25678
- const isNew = !sub;
25679
- if (!sub) {
25680
- sub = { callbacks: /* @__PURE__ */ new Map() };
25681
- this.streams.set(streamName, sub);
25333
+ },
25334
+ unsubscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
25335
+ const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
25336
+ const symbols = streamSymbols.get(binanceSymbol);
25337
+ if (symbols) {
25338
+ symbols.delete(symmSymbol);
25339
+ if (symbols.size === 0) {
25340
+ streamSymbols.delete(binanceSymbol);
25341
+ } else {
25342
+ streamSymbols.set(binanceSymbol, symbols);
25343
+ }
25682
25344
  }
25683
- sub.callbacks.set(id, cb);
25684
- if (isNew) {
25685
- this.ensureConnected();
25686
- this.sendSubscribe([streamName]);
25345
+ const nextRefCount = getPrevRefCount(binanceSymbol);
25346
+ if (nextRefCount === 0) {
25347
+ const unsubscribe = streamUnsubs.get(binanceSymbol);
25348
+ if (unsubscribe) unsubscribe();
25349
+ streamUnsubs.delete(binanceSymbol);
25350
+ refCounts.delete(binanceSymbol);
25351
+ } else {
25352
+ refCounts.set(binanceSymbol, nextRefCount);
25687
25353
  }
25354
+ set((state) => {
25355
+ if (state.markPrices[symmSymbol] == null) return state;
25356
+ const nextMarkPrices = { ...state.markPrices };
25357
+ delete nextMarkPrices[symmSymbol];
25358
+ return { markPrices: nextMarkPrices };
25359
+ });
25688
25360
  }
25689
- removeStreamCallback(streamName, id) {
25690
- const sub = this.streams.get(streamName);
25691
- if (!sub) return;
25692
- sub.callbacks.delete(id);
25693
- if (sub.callbacks.size === 0) {
25694
- this.streams.delete(streamName);
25695
- this.sendUnsubscribe([streamName]);
25696
- if (this.streams.size === 0 && this.ws) {
25697
- this.intentionalClose = true;
25698
- this.ws.close();
25699
- this.ws = null;
25700
- this.intentionalClose = false;
25361
+ }));
25362
+
25363
+ // src/react/hooks/use-symm-token-selection-markets.ts
25364
+ async function fetchTickerSnapshot(symbol) {
25365
+ const resolution = resolveBinanceSymbol(symbol);
25366
+ if (!resolution.binanceSymbol) return null;
25367
+ const ticker = await fetch24hrTicker(resolution.binanceSymbol);
25368
+ if (!ticker) return null;
25369
+ return {
25370
+ prevClosePrice: ticker.prevClosePrice
25371
+ };
25372
+ }
25373
+ function useSymmTokenSelectionMarkets(params) {
25374
+ const query = useSymmHedgerMarkets(params);
25375
+ const baseMarkets = query.data?.filteredMarkets ?? query.data?.markets ?? [];
25376
+ const marketSymbols = react.useMemo(
25377
+ () => Array.from(
25378
+ new Set(
25379
+ baseMarkets.map((market) => market.symbol).filter((symbol) => !!symbol)
25380
+ )
25381
+ ),
25382
+ [baseMarkets]
25383
+ );
25384
+ const symbolsKey = react.useMemo(
25385
+ () => [...marketSymbols].sort().join(","),
25386
+ [marketSymbols]
25387
+ );
25388
+ const symbolToBinanceMap = react.useMemo(
25389
+ () => new Map(
25390
+ marketSymbols.map((symbol) => {
25391
+ const binanceSymbol = resolveBinanceSymbol(symbol).binanceSymbol;
25392
+ if (!binanceSymbol) return null;
25393
+ return [symbol, binanceSymbol];
25394
+ }).filter((entry) => !!entry)
25395
+ ),
25396
+ [marketSymbols]
25397
+ );
25398
+ const liveMarkPrices = useBinanceMarkPriceStore((state) => state.markPrices);
25399
+ const subscribeSymbol = useBinanceMarkPriceStore((state) => state.subscribeSymbol);
25400
+ const unsubscribeSymbol = useBinanceMarkPriceStore((state) => state.unsubscribeSymbol);
25401
+ const priceQuery = reactQuery.useQuery({
25402
+ queryKey: ["symm", "token-selection-markets-price", symbolsKey],
25403
+ queryFn: async () => {
25404
+ const tickerSnapshots = {};
25405
+ await Promise.all(
25406
+ marketSymbols.map(async (symbol) => {
25407
+ tickerSnapshots[symbol] = await fetchTickerSnapshot(symbol);
25408
+ })
25409
+ );
25410
+ return { tickerSnapshots };
25411
+ },
25412
+ enabled: query.isSuccess && marketSymbols.length > 0,
25413
+ staleTime: 6e4,
25414
+ gcTime: 5 * 6e4
25415
+ });
25416
+ react.useEffect(() => {
25417
+ if (!query.isSuccess || symbolToBinanceMap.size === 0) {
25418
+ return;
25419
+ }
25420
+ const symbolEntries = Array.from(symbolToBinanceMap.entries());
25421
+ symbolEntries.forEach(
25422
+ ([symbol, binanceSymbol]) => subscribeSymbol(symbol, binanceSymbol)
25423
+ );
25424
+ return () => {
25425
+ symbolEntries.forEach(
25426
+ ([symbol, binanceSymbol]) => unsubscribeSymbol(symbol, binanceSymbol)
25427
+ );
25428
+ };
25429
+ }, [query.isSuccess, symbolsKey, symbolToBinanceMap, subscribeSymbol, unsubscribeSymbol]);
25430
+ const markets = react.useMemo(() => {
25431
+ const snapshots = priceQuery.data?.tickerSnapshots ?? {};
25432
+ return baseMarkets.map((market) => {
25433
+ const symbol = market.symbol ?? "";
25434
+ const markPrice = symbol ? liveMarkPrices[symbol] ?? null : null;
25435
+ const prevDayPrice = symbol ? snapshots[symbol]?.prevClosePrice ?? null : null;
25436
+ const priceChange24h = markPrice != null && prevDayPrice != null ? markPrice - prevDayPrice : null;
25437
+ const priceChange24hPercent = markPrice != null && prevDayPrice != null && prevDayPrice !== 0 ? (markPrice - prevDayPrice) / prevDayPrice * 100 : null;
25438
+ return {
25439
+ ...market,
25440
+ collateralToken: "USDC",
25441
+ markPrice,
25442
+ prevDayPrice,
25443
+ priceChange24h,
25444
+ priceChange24hPercent
25445
+ };
25446
+ });
25447
+ }, [baseMarkets, liveMarkPrices, priceQuery.data]);
25448
+ const marketsBySymbol = react.useMemo(
25449
+ () => new Map(
25450
+ markets.filter((m) => !!m.symbol).map((m) => [m.symbol, m])
25451
+ ),
25452
+ [markets]
25453
+ );
25454
+ const marketsById = react.useMemo(
25455
+ () => new Map(markets.map((market) => [market.id, market])),
25456
+ [markets]
25457
+ );
25458
+ return {
25459
+ query,
25460
+ priceQuery,
25461
+ markets,
25462
+ marketsBySymbol,
25463
+ marketsById,
25464
+ isLoading: query.isLoading || priceQuery.isLoading,
25465
+ isPriceLoading: priceQuery.isLoading
25466
+ };
25467
+ }
25468
+ function useSymmFunding(params) {
25469
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25470
+ const chainId = params?.chainId ?? ctxChainId;
25471
+ const internalEnabled = !!symmCoreClient;
25472
+ return reactQuery.useQuery({
25473
+ ...params?.query,
25474
+ queryKey: symmKeys.fundingRates(chainId),
25475
+ queryFn: () => symmCoreClient.funding.getRates({ chainId }),
25476
+ enabled: internalEnabled && (params?.query?.enabled ?? true)
25477
+ });
25478
+ }
25479
+ function useSymmFundingHistory(params) {
25480
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25481
+ const chainId = params.chainId ?? ctxChainId;
25482
+ const {
25483
+ period,
25484
+ longSymbols,
25485
+ shortSymbols,
25486
+ interval,
25487
+ weightMode,
25488
+ includeContributions
25489
+ } = params;
25490
+ const internalEnabled = !!symmCoreClient;
25491
+ const request = {
25492
+ chainId,
25493
+ period,
25494
+ longSymbols,
25495
+ shortSymbols,
25496
+ interval,
25497
+ weightMode,
25498
+ includeContributions
25499
+ };
25500
+ return reactQuery.useQuery({
25501
+ ...params.query,
25502
+ queryKey: symmKeys.fundingHistory(request),
25503
+ queryFn: () => symmCoreClient.funding.getHistory(request),
25504
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25505
+ });
25506
+ }
25507
+ function useSymmFundingPayments(params) {
25508
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25509
+ const { address, positionId, limit, offset } = params;
25510
+ const chainId = params.chainId ?? ctxChainId;
25511
+ const internalEnabled = !!symmCoreClient && !!(address || positionId);
25512
+ const request = {
25513
+ address,
25514
+ positionId,
25515
+ chainId,
25516
+ limit,
25517
+ offset
25518
+ };
25519
+ return reactQuery.useQuery({
25520
+ ...params.query,
25521
+ queryKey: symmKeys.fundingPayments({
25522
+ address,
25523
+ positionId,
25524
+ chainId,
25525
+ limit,
25526
+ offset
25527
+ }),
25528
+ queryFn: () => symmCoreClient.funding.getPayments(request),
25529
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25530
+ });
25531
+ }
25532
+ function useSymmPortfolio(params) {
25533
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25534
+ const { accountAddress, address } = params;
25535
+ const resolvedAddress = accountAddress ? void 0 : address;
25536
+ const chainId = params.chainId ?? ctxChainId;
25537
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25538
+ return reactQuery.useQuery({
25539
+ ...params.query,
25540
+ queryKey: symmKeys.portfolio({
25541
+ accountAddress,
25542
+ address: resolvedAddress,
25543
+ chainId
25544
+ }),
25545
+ queryFn: () => {
25546
+ const request = {
25547
+ accountAddress,
25548
+ address: resolvedAddress,
25549
+ chainId
25550
+ };
25551
+ return symmCoreClient.portfolio.getMetrics(request);
25552
+ },
25553
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25554
+ });
25555
+ }
25556
+ function useResolvedNotificationsParams(params) {
25557
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25558
+ const chainId = params.chainId ?? ctxChainId;
25559
+ return {
25560
+ symmCoreClient,
25561
+ chainId,
25562
+ userAddress: params.userAddress,
25563
+ query: params.query
25564
+ };
25565
+ }
25566
+ function useSymmNotificationsQuery(params) {
25567
+ const { symmCoreClient, chainId, userAddress, query } = useResolvedNotificationsParams(params);
25568
+ const internalEnabled = !!symmCoreClient && !!userAddress;
25569
+ return reactQuery.useQuery({
25570
+ ...query,
25571
+ queryKey: symmKeys.notifications(userAddress, chainId),
25572
+ queryFn: () => symmCoreClient.notifications.list({
25573
+ userAddress,
25574
+ chainId
25575
+ }),
25576
+ enabled: internalEnabled && (query?.enabled ?? true)
25577
+ });
25578
+ }
25579
+ function useSymmUnreadCountQuery(params) {
25580
+ const { symmCoreClient, chainId, userAddress, query } = useResolvedNotificationsParams(params);
25581
+ const internalEnabled = !!symmCoreClient && !!userAddress;
25582
+ return reactQuery.useQuery({
25583
+ ...query,
25584
+ queryKey: symmKeys.unreadCount(userAddress, chainId),
25585
+ queryFn: () => symmCoreClient.notifications.getUnreadCount({
25586
+ userAddress,
25587
+ chainId
25588
+ }),
25589
+ enabled: internalEnabled && (query?.enabled ?? true)
25590
+ });
25591
+ }
25592
+ function useSymmMarkReadNotificationMutation(params, options) {
25593
+ const { symmCoreClient, chainId, userAddress } = useResolvedNotificationsParams(params);
25594
+ const queryClient = reactQuery.useQueryClient();
25595
+ return reactQuery.useMutation({
25596
+ ...withSymmMutationConfig(options?.mutation, {
25597
+ onSuccess: () => {
25598
+ queryClient.invalidateQueries({
25599
+ queryKey: symmKeys.notifications(userAddress, chainId)
25600
+ });
25601
+ queryClient.invalidateQueries({
25602
+ queryKey: symmKeys.unreadCount(userAddress, chainId)
25603
+ });
25604
+ }
25605
+ }),
25606
+ mutationFn: async ({ id, timestamp }) => {
25607
+ if (!symmCoreClient || !userAddress) {
25608
+ throw new Error("symm-core client not available");
25701
25609
  }
25610
+ return symmCoreClient.notifications.markRead({
25611
+ id,
25612
+ timestamp,
25613
+ userAddress,
25614
+ chainId
25615
+ });
25702
25616
  }
25703
- }
25704
- ensureConnected() {
25705
- if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
25617
+ });
25618
+ }
25619
+ function useSymmPendingIds(params) {
25620
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25621
+ const { accountAddress } = params;
25622
+ const chainId = params.chainId ?? ctxChainId;
25623
+ const internalEnabled = !!symmCoreClient && !!accountAddress;
25624
+ return reactQuery.useQuery({
25625
+ ...params.query,
25626
+ queryKey: symmKeys.pendingIds(accountAddress, chainId),
25627
+ queryFn: () => symmCoreClient.positions.getPendingIds({
25628
+ address: accountAddress,
25629
+ chainId
25630
+ }),
25631
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25632
+ });
25633
+ }
25634
+ function useSymmPendingInstantOpens(params) {
25635
+ const {
25636
+ symmCoreClient,
25637
+ chainId: ctxChainId
25638
+ } = useSymmContext();
25639
+ const { accountAddress, authToken: providedAuthToken } = params;
25640
+ const chainId = params.chainId ?? ctxChainId;
25641
+ const internalEnabled = !!symmCoreClient && !!accountAddress;
25642
+ return reactQuery.useQuery({
25643
+ ...params.query,
25644
+ queryKey: symmKeys.pendingInstantOpens(accountAddress, chainId),
25645
+ queryFn: async () => {
25646
+ const authToken = providedAuthToken ?? useSymmAuthStore.getState().getToken(accountAddress, chainId);
25647
+ if (!authToken) {
25648
+ throw new Error("failed to acquire auth token for pending instant opens");
25649
+ }
25650
+ return symmCoreClient.positions.getPendingInstantOpens({
25651
+ accountAddress,
25652
+ chainId,
25653
+ authToken
25654
+ });
25655
+ },
25656
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25657
+ });
25658
+ }
25659
+ function useSymmTwapOrder(params) {
25660
+ const { symmCoreClient } = useSymmContext();
25661
+ const { orderId } = params;
25662
+ const internalEnabled = !!symmCoreClient && !!orderId;
25663
+ return reactQuery.useQuery({
25664
+ ...params.query,
25665
+ queryKey: symmKeys.twapOrder(orderId),
25666
+ queryFn: () => symmCoreClient.orders.getTwapOrder(orderId),
25667
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25668
+ });
25669
+ }
25670
+ var useSymmWsStore = zustand.create((set) => ({
25671
+ isConnected: false,
25672
+ setConnected: (isConnected) => set({ isConnected })
25673
+ }));
25674
+
25675
+ // src/react/hooks/use-symm-ws.ts
25676
+ function asUnsubscribeFn(value) {
25677
+ return typeof value === "function" ? value : null;
25678
+ }
25679
+ function useSymmWs(params = {}) {
25680
+ const {
25681
+ symmCoreClient: ctxClient,
25682
+ address: ctxAddress,
25683
+ chainId: ctxChainId
25684
+ } = useSymmContext();
25685
+ const queryClient = reactQuery.useQueryClient();
25686
+ const isConnected = useSymmWsStore((state) => state.isConnected);
25687
+ const setConnected = useSymmWsStore((state) => state.setConnected);
25688
+ const symmCoreClient = params.symmCoreClient ?? ctxClient;
25689
+ const accountAddress = params.accountAddress ?? ctxAddress;
25690
+ const chainId = params.chainId ?? ctxChainId;
25691
+ react.useEffect(() => {
25692
+ if (!symmCoreClient || !accountAddress) {
25693
+ setConnected(false);
25706
25694
  return;
25707
25695
  }
25708
- this.connect();
25709
- }
25710
- connect() {
25711
- if (typeof WebSocket === "undefined") return;
25712
- this.intentionalClose = false;
25713
- this.ws = new WebSocket(BINANCE_WS_URL);
25714
- this.ws.onopen = () => {
25715
- this.reconnectAttempt = 0;
25716
- const activeStreams = Array.from(this.streams.keys());
25717
- if (activeStreams.length > 0) {
25718
- this.sendSubscribe(activeStreams);
25719
- }
25720
- if (this.pendingSubscribes.length > 0) {
25721
- this.sendSubscribe(this.pendingSubscribes);
25722
- this.pendingSubscribes = [];
25723
- }
25724
- };
25725
- this.ws.onmessage = (event) => {
25726
- try {
25727
- const data = JSON.parse(event.data);
25728
- this.handleMessage(data);
25729
- } catch {
25730
- }
25731
- };
25732
- this.ws.onclose = () => {
25733
- if (this.intentionalClose) return;
25734
- this.scheduleReconnect();
25696
+ const ws = symmCoreClient.ws;
25697
+ const addr = accountAddress;
25698
+ const unsubscribers = [];
25699
+ const removeOnConnect = ws.onConnect(() => setConnected(true));
25700
+ const removeOnDisconnect = ws.onDisconnect(() => setConnected(false));
25701
+ unsubscribers.push(removeOnConnect, removeOnDisconnect);
25702
+ const positionsUnsub = asUnsubscribeFn(
25703
+ ws.subscribeToPositions(addr, chainId, () => {
25704
+ queryClient.invalidateQueries({
25705
+ queryKey: ["symm", "positions"]
25706
+ });
25707
+ })
25708
+ );
25709
+ if (positionsUnsub) unsubscribers.push(positionsUnsub);
25710
+ const openOrdersUnsub = asUnsubscribeFn(
25711
+ ws.subscribeToOpenOrders(addr, chainId, () => {
25712
+ queryClient.invalidateQueries({
25713
+ queryKey: ["symm", "openOrders"]
25714
+ });
25715
+ })
25716
+ );
25717
+ if (openOrdersUnsub) unsubscribers.push(openOrdersUnsub);
25718
+ const tradesUnsub = asUnsubscribeFn(
25719
+ ws.subscribeToTrades(addr, chainId, () => {
25720
+ queryClient.invalidateQueries({
25721
+ queryKey: ["symm", "tradeHistory"]
25722
+ });
25723
+ })
25724
+ );
25725
+ if (tradesUnsub) unsubscribers.push(tradesUnsub);
25726
+ const accountSummaryUnsub = asUnsubscribeFn(
25727
+ ws.subscribeToAccountSummary(addr, chainId, () => {
25728
+ queryClient.invalidateQueries({
25729
+ queryKey: symmKeys.balances(accountAddress, chainId)
25730
+ });
25731
+ queryClient.invalidateQueries({
25732
+ queryKey: symmKeys.accountSummary(accountAddress, chainId)
25733
+ });
25734
+ })
25735
+ );
25736
+ if (accountSummaryUnsub) unsubscribers.push(accountSummaryUnsub);
25737
+ const notificationsUnsub = asUnsubscribeFn(
25738
+ ws.subscribeToNotifications(addr, chainId, () => {
25739
+ queryClient.invalidateQueries({
25740
+ queryKey: symmKeys.notifications(accountAddress, chainId)
25741
+ });
25742
+ queryClient.invalidateQueries({
25743
+ queryKey: symmKeys.unreadCount(accountAddress, chainId)
25744
+ });
25745
+ })
25746
+ );
25747
+ if (notificationsUnsub) unsubscribers.push(notificationsUnsub);
25748
+ const tpslUnsub = asUnsubscribeFn(
25749
+ ws.subscribeToTpsl(addr, chainId, () => {
25750
+ queryClient.invalidateQueries({ queryKey: ["symm", "tpslOrders"] });
25751
+ queryClient.invalidateQueries({ queryKey: ["symm", "openOrders"] });
25752
+ })
25753
+ );
25754
+ if (tpslUnsub) unsubscribers.push(tpslUnsub);
25755
+ const twapUnsub = asUnsubscribeFn(
25756
+ ws.subscribeToTwapOrders(addr, chainId, () => {
25757
+ queryClient.invalidateQueries({ queryKey: ["symm", "twapOrders"] });
25758
+ queryClient.invalidateQueries({ queryKey: ["symm", "openOrders"] });
25759
+ })
25760
+ );
25761
+ if (twapUnsub) unsubscribers.push(twapUnsub);
25762
+ const triggerOrdersUnsub = asUnsubscribeFn(
25763
+ ws.subscribeToTriggerOrders(addr, chainId, () => {
25764
+ queryClient.invalidateQueries({ queryKey: ["symm", "triggerOrders"] });
25765
+ queryClient.invalidateQueries({ queryKey: ["symm", "openOrders"] });
25766
+ })
25767
+ );
25768
+ if (triggerOrdersUnsub) unsubscribers.push(triggerOrdersUnsub);
25769
+ const executionsUnsub = asUnsubscribeFn(
25770
+ ws.subscribeToExecutions(addr, chainId, () => {
25771
+ queryClient.invalidateQueries({
25772
+ queryKey: ["symm", "positions"]
25773
+ });
25774
+ queryClient.invalidateQueries({
25775
+ queryKey: ["symm", "portfolio"]
25776
+ });
25777
+ })
25778
+ );
25779
+ if (executionsUnsub) unsubscribers.push(executionsUnsub);
25780
+ return () => {
25781
+ unsubscribers.forEach((unsubscribe) => unsubscribe());
25735
25782
  };
25736
- this.ws.onerror = () => {
25783
+ }, [symmCoreClient, accountAddress, chainId, queryClient, setConnected]);
25784
+ return { isConnected };
25785
+ }
25786
+ var STABLE_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USD", "USDT", "USDE", "USDH", "USDT0"]);
25787
+ function useSymmChartSelection(input) {
25788
+ const {
25789
+ longSymbol,
25790
+ shortSymbol,
25791
+ longWeight = 100,
25792
+ shortWeight = 100,
25793
+ longTokens: explicitLongTokens,
25794
+ shortTokens: explicitShortTokens
25795
+ } = input;
25796
+ return react.useMemo(() => {
25797
+ const longTokens = explicitLongTokens?.length ? explicitLongTokens : longSymbol ? [{ symbol: longSymbol, weight: longWeight }] : [];
25798
+ const shortTokens = explicitShortTokens?.length ? explicitShortTokens : shortSymbol ? [{ symbol: shortSymbol, weight: shortWeight }] : [];
25799
+ const selectedSymbols = [
25800
+ ...longTokens.map((t) => t.symbol),
25801
+ ...shortTokens.map((t) => t.symbol)
25802
+ ];
25803
+ const uniqueSelectedSymbols = Array.from(new Set(selectedSymbols));
25804
+ const longPrimarySymbol = longTokens[0]?.symbol ?? null;
25805
+ const shortPrimarySymbol = shortTokens[0]?.symbol ?? null;
25806
+ const longIsStable = longPrimarySymbol ? STABLE_SYMBOLS.has(longPrimarySymbol.toUpperCase()) : true;
25807
+ const shortIsStable = shortPrimarySymbol ? STABLE_SYMBOLS.has(shortPrimarySymbol.toUpperCase()) : true;
25808
+ let isSingleSided = false;
25809
+ let activeSide = null;
25810
+ let activeTokenSymbol;
25811
+ if (longTokens.length > 0 && !longIsStable && (shortTokens.length === 0 || shortIsStable)) {
25812
+ isSingleSided = true;
25813
+ activeSide = "long";
25814
+ activeTokenSymbol = longPrimarySymbol ?? void 0;
25815
+ } else if (shortTokens.length > 0 && !shortIsStable && (longTokens.length === 0 || longIsStable)) {
25816
+ isSingleSided = true;
25817
+ activeSide = "short";
25818
+ activeTokenSymbol = shortPrimarySymbol ?? void 0;
25819
+ }
25820
+ const longLabel = longTokens.map((token) => token.symbol).join("+") || "USDC";
25821
+ const shortLabel = shortTokens.map((token) => token.symbol).join("+") || "USDC";
25822
+ const pairLabel = `${longLabel}/${shortLabel}`;
25823
+ return {
25824
+ longTokens,
25825
+ shortTokens,
25826
+ isSingleSided,
25827
+ activeSide,
25828
+ activeTokenSymbol,
25829
+ pairLabel,
25830
+ selectedSymbols: uniqueSelectedSymbols
25737
25831
  };
25832
+ }, [
25833
+ explicitLongTokens,
25834
+ explicitShortTokens,
25835
+ longSymbol,
25836
+ shortSymbol,
25837
+ longWeight,
25838
+ shortWeight
25839
+ ]);
25840
+ }
25841
+
25842
+ // src/utils/chart-metrics.ts
25843
+ function getPositiveValue(metadata, field) {
25844
+ const value = metadata?.[field];
25845
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
25846
+ return null;
25738
25847
  }
25739
- handleMessage(data) {
25740
- if (data.e === "kline") {
25741
- const k = data.k;
25742
- const streamName = `${data.s.toLowerCase()}@kline_${k.i}`;
25743
- this.dispatchToStream(streamName, data);
25744
- } else if (data.e === "markPriceUpdate") {
25745
- const streamName = `${data.s.toLowerCase()}@markPrice@1s`;
25746
- this.dispatchToStream(streamName, data);
25848
+ return value;
25849
+ }
25850
+ function computeWeightedProduct(tokens, metadataMap, field, invert = false) {
25851
+ let product = 1;
25852
+ let hasToken = false;
25853
+ for (const token of tokens) {
25854
+ if (!(token.weight > 0)) {
25855
+ continue;
25747
25856
  }
25748
- }
25749
- dispatchToStream(streamName, data) {
25750
- const sub = this.streams.get(streamName);
25751
- if (!sub) return;
25752
- sub.callbacks.forEach((cb) => {
25753
- try {
25754
- cb(data);
25755
- } catch {
25756
- }
25757
- });
25758
- }
25759
- sendSubscribe(streams) {
25760
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
25761
- this.pendingSubscribes.push(...streams);
25762
- return;
25857
+ const price = getPositiveValue(metadataMap[token.symbol], field);
25858
+ if (price === null) {
25859
+ return null;
25763
25860
  }
25764
- this.ws.send(JSON.stringify({
25765
- method: "SUBSCRIBE",
25766
- params: streams,
25767
- id: Date.now()
25768
- }));
25861
+ hasToken = true;
25862
+ const exponent = invert ? -(token.weight / 100) : token.weight / 100;
25863
+ product *= Math.pow(price, exponent);
25769
25864
  }
25770
- sendUnsubscribe(streams) {
25771
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
25772
- this.pendingSubscribes = this.pendingSubscribes.filter(
25773
- (s) => !streams.includes(s)
25774
- );
25775
- return;
25776
- }
25777
- this.ws.send(JSON.stringify({
25778
- method: "UNSUBSCRIBE",
25779
- params: streams,
25780
- id: Date.now()
25781
- }));
25865
+ return hasToken ? product : 1;
25866
+ }
25867
+ function computePriceRatio({
25868
+ longTokens,
25869
+ shortTokens,
25870
+ longTokensMetadata,
25871
+ shortTokensMetadata
25872
+ }) {
25873
+ const firstLong = longTokens[0];
25874
+ const firstShort = shortTokens[0];
25875
+ const longPrice = firstLong ? getPositiveValue(longTokensMetadata[firstLong.symbol], "currentPrice") : null;
25876
+ const shortPrice = firstShort ? getPositiveValue(shortTokensMetadata[firstShort.symbol], "currentPrice") : null;
25877
+ if (longPrice !== null && shortPrice !== null) {
25878
+ return longPrice / shortPrice;
25782
25879
  }
25783
- scheduleReconnect() {
25784
- if (this.reconnectTimer) return;
25785
- if (this.streams.size === 0) return;
25786
- const delay = RECONNECT_DELAYS[Math.min(this.reconnectAttempt, RECONNECT_DELAYS.length - 1)];
25787
- this.reconnectAttempt++;
25788
- this.reconnectTimer = setTimeout(() => {
25789
- this.reconnectTimer = null;
25790
- this.connect();
25791
- }, delay);
25880
+ if (longPrice !== null && shortTokens.length === 0) {
25881
+ return longPrice;
25792
25882
  }
25793
- };
25794
- var _instance = null;
25795
- function getBinanceWsManager() {
25796
- if (!_instance) {
25797
- _instance = new BinanceWsManager();
25883
+ if (shortPrice !== null && longTokens.length === 0) {
25884
+ return shortPrice;
25798
25885
  }
25799
- return _instance;
25886
+ return null;
25800
25887
  }
25801
-
25802
- // src/react/stores/use-binance-mark-price-store.ts
25803
- var refCounts = /* @__PURE__ */ new Map();
25804
- var streamUnsubs = /* @__PURE__ */ new Map();
25805
- var streamSymbols = /* @__PURE__ */ new Map();
25806
- function normalizeBinanceSymbol(symbol) {
25807
- return symbol.toUpperCase().trim();
25888
+ function computePriceRatio24h({
25889
+ longTokens,
25890
+ shortTokens,
25891
+ longTokensMetadata,
25892
+ shortTokensMetadata
25893
+ }) {
25894
+ const firstLong = longTokens[0];
25895
+ const firstShort = shortTokens[0];
25896
+ const longPrice = firstLong ? getPositiveValue(longTokensMetadata[firstLong.symbol], "prevDayPrice") : null;
25897
+ const shortPrice = firstShort ? getPositiveValue(shortTokensMetadata[firstShort.symbol], "prevDayPrice") : null;
25898
+ if (longPrice !== null && shortPrice !== null) {
25899
+ return longPrice / shortPrice;
25900
+ }
25901
+ if (longPrice !== null && shortTokens.length === 0) {
25902
+ return longPrice;
25903
+ }
25904
+ if (shortPrice !== null && longTokens.length === 0) {
25905
+ return shortPrice;
25906
+ }
25907
+ return null;
25808
25908
  }
25809
- function getNextRefCount(binanceSymbol) {
25810
- return (refCounts.get(binanceSymbol) ?? 0) + 1;
25909
+ function computeWeightedRatio({
25910
+ longTokens,
25911
+ shortTokens,
25912
+ longTokensMetadata,
25913
+ shortTokensMetadata
25914
+ }) {
25915
+ const longProduct = computeWeightedProduct(
25916
+ longTokens,
25917
+ longTokensMetadata,
25918
+ "currentPrice"
25919
+ );
25920
+ const shortProduct = computeWeightedProduct(
25921
+ shortTokens,
25922
+ shortTokensMetadata,
25923
+ "currentPrice",
25924
+ true
25925
+ );
25926
+ if (longProduct === null || shortProduct === null) {
25927
+ return null;
25928
+ }
25929
+ return longProduct * shortProduct;
25811
25930
  }
25812
- function getPrevRefCount(binanceSymbol) {
25813
- return Math.max(0, (refCounts.get(binanceSymbol) ?? 0) - 1);
25931
+ function computeWeightedRatio24h({
25932
+ longTokens,
25933
+ shortTokens,
25934
+ longTokensMetadata,
25935
+ shortTokensMetadata
25936
+ }) {
25937
+ const longProduct = computeWeightedProduct(
25938
+ longTokens,
25939
+ longTokensMetadata,
25940
+ "prevDayPrice"
25941
+ );
25942
+ const shortProduct = computeWeightedProduct(
25943
+ shortTokens,
25944
+ shortTokensMetadata,
25945
+ "prevDayPrice",
25946
+ true
25947
+ );
25948
+ if (longProduct === null || shortProduct === null) {
25949
+ return null;
25950
+ }
25951
+ return longProduct * shortProduct;
25814
25952
  }
25815
- var useBinanceMarkPriceStore = zustand.create((set) => ({
25816
- markPrices: {},
25817
- subscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
25818
- const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
25819
- const nextRefCount = getNextRefCount(binanceSymbol);
25820
- refCounts.set(binanceSymbol, nextRefCount);
25821
- const symbols = streamSymbols.get(binanceSymbol) ?? /* @__PURE__ */ new Set();
25822
- symbols.add(symmSymbol);
25823
- streamSymbols.set(binanceSymbol, symbols);
25824
- if (nextRefCount === 1) {
25825
- const wsManager = getBinanceWsManager();
25826
- const unsubscribe = wsManager.subscribeMarkPrice(binanceSymbol, (data) => {
25827
- const canonicalSymbol = normalizeBinanceSymbol(data.symbol);
25828
- const mappedSymbols = streamSymbols.get(canonicalSymbol);
25829
- if (!mappedSymbols || mappedSymbols.size === 0) return;
25830
- set((state) => {
25831
- const nextMarkPrices = { ...state.markPrices };
25832
- mappedSymbols.forEach((mappedSymbol) => {
25833
- nextMarkPrices[mappedSymbol] = data.markPrice;
25834
- });
25835
- return { markPrices: nextMarkPrices };
25836
- });
25837
- });
25838
- streamUnsubs.set(binanceSymbol, unsubscribe);
25839
- }
25840
- },
25841
- unsubscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
25842
- const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
25843
- const symbols = streamSymbols.get(binanceSymbol);
25844
- if (symbols) {
25845
- symbols.delete(symmSymbol);
25846
- if (symbols.size === 0) {
25847
- streamSymbols.delete(binanceSymbol);
25848
- } else {
25849
- streamSymbols.set(binanceSymbol, symbols);
25850
- }
25953
+ function computeNetFundingSum({
25954
+ longTokens,
25955
+ shortTokens,
25956
+ longTokensMetadata,
25957
+ shortTokensMetadata
25958
+ }) {
25959
+ let funding = 0;
25960
+ for (const token of longTokens) {
25961
+ const value = longTokensMetadata[token.symbol]?.netFunding;
25962
+ if (typeof value === "number" && Number.isFinite(value)) {
25963
+ funding += value;
25851
25964
  }
25852
- const nextRefCount = getPrevRefCount(binanceSymbol);
25853
- if (nextRefCount === 0) {
25854
- const unsubscribe = streamUnsubs.get(binanceSymbol);
25855
- if (unsubscribe) unsubscribe();
25856
- streamUnsubs.delete(binanceSymbol);
25857
- refCounts.delete(binanceSymbol);
25858
- } else {
25859
- refCounts.set(binanceSymbol, nextRefCount);
25965
+ }
25966
+ for (const token of shortTokens) {
25967
+ const value = shortTokensMetadata[token.symbol]?.netFunding;
25968
+ if (typeof value === "number" && Number.isFinite(value)) {
25969
+ funding += value;
25860
25970
  }
25861
- set((state) => {
25862
- if (state.markPrices[symmSymbol] == null) return state;
25863
- const nextMarkPrices = { ...state.markPrices };
25864
- delete nextMarkPrices[symmSymbol];
25865
- return { markPrices: nextMarkPrices };
25866
- });
25867
25971
  }
25868
- }));
25972
+ return funding;
25973
+ }
25869
25974
 
25870
25975
  // src/react/hooks/use-symm-token-selection-metadata.ts
25871
- async function fetchTickerSnapshot(symbol) {
25976
+ async function fetchTickerSnapshot2(symbol) {
25872
25977
  const resolution = resolveBinanceSymbol(symbol);
25873
25978
  if (!resolution.binanceSymbol) return null;
25874
25979
  const ticker = await fetch24hrTicker(resolution.binanceSymbol);
@@ -25924,7 +26029,7 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
25924
26029
  const results = await Promise.all(
25925
26030
  allSymbols.map(async ({ symbol }) => ({
25926
26031
  symbol,
25927
- ticker: await fetchTickerSnapshot(symbol)
26032
+ ticker: await fetchTickerSnapshot2(symbol)
25928
26033
  }))
25929
26034
  );
25930
26035
  const tickerSnapshots = {};
@@ -26456,6 +26561,7 @@ exports.useSymmSetTpslMutation = useSymmSetTpslMutation;
26456
26561
  exports.useSymmSetTriggerConfigMutation = useSymmSetTriggerConfigMutation;
26457
26562
  exports.useSymmSignTermsMutation = useSymmSignTermsMutation;
26458
26563
  exports.useSymmSignatureQuery = useSymmSignatureQuery;
26564
+ exports.useSymmTokenSelectionMarkets = useSymmTokenSelectionMarkets;
26459
26565
  exports.useSymmTokenSelectionMetadata = useSymmTokenSelectionMetadata;
26460
26566
  exports.useSymmTpslOrders = useSymmTpslOrders;
26461
26567
  exports.useSymmTradeHistory = useSymmTradeHistory;