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