@pear-protocol/symmio-client 0.2.25 → 0.2.27

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