@pear-protocol/symmio-client 0.3.11 → 0.3.13

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.
@@ -8552,12 +8552,16 @@ declare function useSymmBalances(params: {
8552
8552
  }): _tanstack_react_query.UseQueryResult<node_modules__pear_protocol_symm_core_dist_types.BalanceInfoResponse, Error>;
8553
8553
 
8554
8554
  type SymmTradeAuthParams = {
8555
+ accountAddress?: Address;
8555
8556
  address?: Address;
8556
8557
  chainId?: number;
8557
8558
  };
8558
8559
 
8559
8560
  type OpenBasketMutationRequest = WithOptionalAuthToken<OpenBasketPositionRequest>;
8560
- type ClosePositionMutationRequest = WithOptionalAuthToken<ClosePositionRequest>;
8561
+ type ClosePositionMutationRequest = WithOptionalAuthToken<ClosePositionRequest> & {
8562
+ accountAddress?: Address;
8563
+ chainId?: number;
8564
+ };
8561
8565
  type CloseAllPositionsMutationRequest = WithOptionalAuthToken<Parameters<NonNullable<ReturnType<typeof useSymmContext>['symmCoreClient']>['positions']['closeAll']>[0]>;
8562
8566
  type UpdatePositionMutationRequest = WithOptionalAuthToken<UpdatePositionRequest>;
8563
8567
  /**
@@ -8575,9 +8579,7 @@ declare function useSymmClosePositionMutation(paramsOrOptions?: SymmTradeAuthPar
8575
8579
  mutation?: SymmMutationConfig<unknown, Error, ClosePositionMutationRequest>;
8576
8580
  }, maybeOptions?: {
8577
8581
  mutation?: SymmMutationConfig<unknown, Error, ClosePositionMutationRequest>;
8578
- }): _tanstack_react_query.UseMutationResult<_pear_protocol_symm_core.ClosePositionResponse | _pear_protocol_symm_core.CloseCommandResult, Error, Omit<ClosePositionRequest, "authToken"> & {
8579
- authToken?: string | undefined;
8580
- }, unknown>;
8582
+ }): _tanstack_react_query.UseMutationResult<_pear_protocol_symm_core.ClosePositionResponse | _pear_protocol_symm_core.CloseCommandResult, Error, ClosePositionMutationRequest, unknown>;
8581
8583
  /**
8582
8584
  * Use case: Close all open positions for the provided request scope.
8583
8585
  */
@@ -8552,12 +8552,16 @@ declare function useSymmBalances(params: {
8552
8552
  }): _tanstack_react_query.UseQueryResult<node_modules__pear_protocol_symm_core_dist_types.BalanceInfoResponse, Error>;
8553
8553
 
8554
8554
  type SymmTradeAuthParams = {
8555
+ accountAddress?: Address;
8555
8556
  address?: Address;
8556
8557
  chainId?: number;
8557
8558
  };
8558
8559
 
8559
8560
  type OpenBasketMutationRequest = WithOptionalAuthToken<OpenBasketPositionRequest>;
8560
- type ClosePositionMutationRequest = WithOptionalAuthToken<ClosePositionRequest>;
8561
+ type ClosePositionMutationRequest = WithOptionalAuthToken<ClosePositionRequest> & {
8562
+ accountAddress?: Address;
8563
+ chainId?: number;
8564
+ };
8561
8565
  type CloseAllPositionsMutationRequest = WithOptionalAuthToken<Parameters<NonNullable<ReturnType<typeof useSymmContext>['symmCoreClient']>['positions']['closeAll']>[0]>;
8562
8566
  type UpdatePositionMutationRequest = WithOptionalAuthToken<UpdatePositionRequest>;
8563
8567
  /**
@@ -8575,9 +8579,7 @@ declare function useSymmClosePositionMutation(paramsOrOptions?: SymmTradeAuthPar
8575
8579
  mutation?: SymmMutationConfig<unknown, Error, ClosePositionMutationRequest>;
8576
8580
  }, maybeOptions?: {
8577
8581
  mutation?: SymmMutationConfig<unknown, Error, ClosePositionMutationRequest>;
8578
- }): _tanstack_react_query.UseMutationResult<_pear_protocol_symm_core.ClosePositionResponse | _pear_protocol_symm_core.CloseCommandResult, Error, Omit<ClosePositionRequest, "authToken"> & {
8579
- authToken?: string | undefined;
8580
- }, unknown>;
8582
+ }): _tanstack_react_query.UseMutationResult<_pear_protocol_symm_core.ClosePositionResponse | _pear_protocol_symm_core.CloseCommandResult, Error, ClosePositionMutationRequest, unknown>;
8581
8583
  /**
8582
8584
  * Use case: Close all open positions for the provided request scope.
8583
8585
  */
@@ -72,6 +72,9 @@ function toBinanceSymbol(symmSymbol) {
72
72
  // src/utils/binance-ws.ts
73
73
  var BINANCE_WS_URL = "wss://fstream.binance.com/market/ws";
74
74
  var RECONNECT_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
75
+ var IDLE_CLOSE_DELAY_MS = 3e4;
76
+ var STALE_CONNECTION_MS = 3e4;
77
+ var STALE_CHECK_INTERVAL_MS = 1e4;
75
78
  var STABLE_QUOTES = ["USDT0", "USDT", "USDC", "USDE", "USDH", "USD"];
76
79
  function normalizeBaseSymbol(symbol) {
77
80
  const normalized = symbol.toUpperCase().trim();
@@ -121,8 +124,11 @@ var BinanceWsManager = class {
121
124
  streams = /* @__PURE__ */ new Map();
122
125
  reconnectAttempt = 0;
123
126
  reconnectTimer = null;
127
+ idleCloseTimer = null;
128
+ staleCheckTimer = null;
124
129
  intentionalClose = false;
125
- pendingSubscribes = [];
130
+ pendingSubscribes = /* @__PURE__ */ new Set();
131
+ lastMessageAt = 0;
126
132
  idCounter = 0;
127
133
  /**
128
134
  * Subscribe to a kline stream. Returns an unsubscribe function.
@@ -195,10 +201,10 @@ var BinanceWsManager = class {
195
201
  */
196
202
  destroy() {
197
203
  this.intentionalClose = true;
198
- if (this.reconnectTimer) {
199
- clearTimeout(this.reconnectTimer);
200
- this.reconnectTimer = null;
201
- }
204
+ this.clearReconnectTimer();
205
+ this.clearIdleCloseTimer();
206
+ this.clearStaleCheckTimer();
207
+ this.pendingSubscribes.clear();
202
208
  if (this.ws) {
203
209
  this.ws.close();
204
210
  this.ws = null;
@@ -218,6 +224,7 @@ var BinanceWsManager = class {
218
224
  }
219
225
  sub.callbacks.set(id, cb);
220
226
  if (isNew) {
227
+ this.clearIdleCloseTimer();
221
228
  this.ensureConnected();
222
229
  this.sendSubscribe([streamName]);
223
230
  }
@@ -229,18 +236,14 @@ var BinanceWsManager = class {
229
236
  if (sub.callbacks.size === 0) {
230
237
  this.streams.delete(streamName);
231
238
  this.sendUnsubscribe([streamName]);
232
- if (this.streams.size === 0 && this.ws) {
233
- this.intentionalClose = true;
234
- this.ws.close();
235
- this.ws = null;
236
- this.intentionalClose = false;
237
- }
239
+ this.scheduleIdleClose();
238
240
  }
239
241
  }
240
242
  ensureConnected() {
241
243
  if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
242
244
  return;
243
245
  }
246
+ this.clearReconnectTimer();
244
247
  this.connect();
245
248
  }
246
249
  connect() {
@@ -249,23 +252,27 @@ var BinanceWsManager = class {
249
252
  this.ws = new WebSocket(BINANCE_WS_URL);
250
253
  this.ws.onopen = () => {
251
254
  this.reconnectAttempt = 0;
252
- const activeStreams = Array.from(this.streams.keys());
255
+ this.lastMessageAt = Date.now();
256
+ this.startStaleCheck();
257
+ const activeStreams = Array.from(
258
+ /* @__PURE__ */ new Set([...this.streams.keys(), ...this.pendingSubscribes])
259
+ );
260
+ this.pendingSubscribes.clear();
253
261
  if (activeStreams.length > 0) {
254
262
  this.sendSubscribe(activeStreams);
255
263
  }
256
- if (this.pendingSubscribes.length > 0) {
257
- this.sendSubscribe(this.pendingSubscribes);
258
- this.pendingSubscribes = [];
259
- }
260
264
  };
261
265
  this.ws.onmessage = (event) => {
262
266
  try {
263
267
  const data = JSON.parse(event.data);
268
+ this.lastMessageAt = Date.now();
264
269
  this.handleMessage(data);
265
270
  } catch {
266
271
  }
267
272
  };
268
273
  this.ws.onclose = () => {
274
+ this.ws = null;
275
+ this.clearStaleCheckTimer();
269
276
  if (this.intentionalClose) return;
270
277
  this.scheduleReconnect();
271
278
  };
@@ -299,25 +306,27 @@ var BinanceWsManager = class {
299
306
  }
300
307
  sendSubscribe(streams) {
301
308
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
302
- this.pendingSubscribes.push(...streams);
309
+ streams.forEach((stream) => this.pendingSubscribes.add(stream));
303
310
  return;
304
311
  }
312
+ const params = Array.from(new Set(streams));
313
+ if (params.length === 0) return;
305
314
  this.ws.send(JSON.stringify({
306
315
  method: "SUBSCRIBE",
307
- params: streams,
316
+ params,
308
317
  id: Date.now()
309
318
  }));
310
319
  }
311
320
  sendUnsubscribe(streams) {
312
321
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
313
- this.pendingSubscribes = this.pendingSubscribes.filter(
314
- (s) => !streams.includes(s)
315
- );
322
+ streams.forEach((stream) => this.pendingSubscribes.delete(stream));
316
323
  return;
317
324
  }
325
+ const params = Array.from(new Set(streams));
326
+ if (params.length === 0) return;
318
327
  this.ws.send(JSON.stringify({
319
328
  method: "UNSUBSCRIBE",
320
- params: streams,
329
+ params,
321
330
  id: Date.now()
322
331
  }));
323
332
  }
@@ -331,6 +340,44 @@ var BinanceWsManager = class {
331
340
  this.connect();
332
341
  }, delay);
333
342
  }
343
+ scheduleIdleClose() {
344
+ if (this.streams.size > 0 || this.idleCloseTimer) return;
345
+ this.clearReconnectTimer();
346
+ if (!this.ws) return;
347
+ this.idleCloseTimer = setTimeout(() => {
348
+ this.idleCloseTimer = null;
349
+ if (this.streams.size > 0 || !this.ws) return;
350
+ this.intentionalClose = true;
351
+ this.ws.close();
352
+ this.ws = null;
353
+ this.intentionalClose = false;
354
+ this.clearStaleCheckTimer();
355
+ }, IDLE_CLOSE_DELAY_MS);
356
+ }
357
+ startStaleCheck() {
358
+ this.clearStaleCheckTimer();
359
+ this.staleCheckTimer = setInterval(() => {
360
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
361
+ if (this.streams.size === 0) return;
362
+ if (Date.now() - this.lastMessageAt < STALE_CONNECTION_MS) return;
363
+ this.ws.close();
364
+ }, STALE_CHECK_INTERVAL_MS);
365
+ }
366
+ clearReconnectTimer() {
367
+ if (!this.reconnectTimer) return;
368
+ clearTimeout(this.reconnectTimer);
369
+ this.reconnectTimer = null;
370
+ }
371
+ clearIdleCloseTimer() {
372
+ if (!this.idleCloseTimer) return;
373
+ clearTimeout(this.idleCloseTimer);
374
+ this.idleCloseTimer = null;
375
+ }
376
+ clearStaleCheckTimer() {
377
+ if (!this.staleCheckTimer) return;
378
+ clearInterval(this.staleCheckTimer);
379
+ this.staleCheckTimer = null;
380
+ }
334
381
  };
335
382
  var _instance = null;
336
383
  function getBinanceWsManager() {
@@ -361,6 +408,28 @@ function getNextRefCount(binanceSymbol) {
361
408
  function getPrevRefCount(binanceSymbol) {
362
409
  return Math.max(0, (refCounts.get(binanceSymbol) ?? 0) - 1);
363
410
  }
411
+ function addMappedSymbol(binanceSymbol, symmSymbol) {
412
+ const symbols = streamSymbols.get(binanceSymbol) ?? /* @__PURE__ */ new Map();
413
+ symbols.set(symmSymbol, (symbols.get(symmSymbol) ?? 0) + 1);
414
+ streamSymbols.set(binanceSymbol, symbols);
415
+ }
416
+ function removeMappedSymbol(binanceSymbol, symmSymbol) {
417
+ const symbols = streamSymbols.get(binanceSymbol);
418
+ if (!symbols) return false;
419
+ const currentCount = symbols.get(symmSymbol) ?? 0;
420
+ if (currentCount <= 0) return false;
421
+ if (currentCount === 1) {
422
+ symbols.delete(symmSymbol);
423
+ } else {
424
+ symbols.set(symmSymbol, currentCount - 1);
425
+ }
426
+ if (symbols.size === 0) {
427
+ streamSymbols.delete(binanceSymbol);
428
+ } else {
429
+ streamSymbols.set(binanceSymbol, symbols);
430
+ }
431
+ return true;
432
+ }
364
433
  var useBinanceMarkPriceStore = zustand.create((set) => ({
365
434
  markPrices: {},
366
435
  fundingRates: {},
@@ -369,9 +438,7 @@ var useBinanceMarkPriceStore = zustand.create((set) => ({
369
438
  const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
370
439
  const nextRefCount = getNextRefCount(binanceSymbol);
371
440
  refCounts.set(binanceSymbol, nextRefCount);
372
- const symbols = streamSymbols.get(binanceSymbol) ?? /* @__PURE__ */ new Set();
373
- symbols.add(symmSymbol);
374
- streamSymbols.set(binanceSymbol, symbols);
441
+ addMappedSymbol(binanceSymbol, symmSymbol);
375
442
  if (allMarkPricesRefCount === 0) {
376
443
  const wsManager = getBinanceWsManager();
377
444
  allMarkPricesUnsubscribe = wsManager.subscribeAllMarkPrices((entries) => {
@@ -386,7 +453,7 @@ var useBinanceMarkPriceStore = zustand.create((set) => ({
386
453
  nextMarkPrices ??= { ...state.markPrices };
387
454
  nextFundingRates ??= { ...state.fundingRates };
388
455
  nextFundingTimes ??= { ...state.nextFundingTimes };
389
- mappedSymbols.forEach((mappedSymbol) => {
456
+ mappedSymbols.forEach((_count, mappedSymbol) => {
390
457
  nextMarkPrices[mappedSymbol] = entry.markPrice;
391
458
  nextFundingRates[mappedSymbol] = entry.fundingRate;
392
459
  nextFundingTimes[mappedSymbol] = entry.nextFundingTime;
@@ -407,14 +474,9 @@ var useBinanceMarkPriceStore = zustand.create((set) => ({
407
474
  },
408
475
  unsubscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
409
476
  const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
410
- const symbols = streamSymbols.get(binanceSymbol);
411
- if (symbols) {
412
- symbols.delete(symmSymbol);
413
- if (symbols.size === 0) {
414
- streamSymbols.delete(binanceSymbol);
415
- } else {
416
- streamSymbols.set(binanceSymbol, symbols);
417
- }
477
+ const removedSubscription = removeMappedSymbol(binanceSymbol, symmSymbol);
478
+ if (!removedSubscription) {
479
+ return;
418
480
  }
419
481
  const nextRefCount = getPrevRefCount(binanceSymbol);
420
482
  if (nextRefCount === 0) {
@@ -428,6 +490,10 @@ var useBinanceMarkPriceStore = zustand.create((set) => ({
428
490
  allMarkPricesUnsubscribe = null;
429
491
  }
430
492
  set((state) => {
493
+ const hasMappedSymbol = Boolean(streamSymbols.get(binanceSymbol)?.has(symmSymbol));
494
+ if (hasMappedSymbol) {
495
+ return state;
496
+ }
431
497
  if (state.markPrices[symmSymbol] == null && state.fundingRates[symmSymbol] == null && state.nextFundingTimes[symmSymbol] == null) {
432
498
  return state;
433
499
  }
@@ -545,8 +611,33 @@ var useSymmWsStore = zustand.create((set) => ({
545
611
  }));
546
612
 
547
613
  // src/react/hooks/use-symm-ws.ts
548
- function asUnsubscribeFn(value) {
549
- return typeof value === "function" ? value : null;
614
+ var wsOwnerCounts = /* @__PURE__ */ new WeakMap();
615
+ var wsConnectPromises = /* @__PURE__ */ new WeakMap();
616
+ function addWsOwner(ws) {
617
+ wsOwnerCounts.set(ws, (wsOwnerCounts.get(ws) ?? 0) + 1);
618
+ }
619
+ function removeWsOwner(ws) {
620
+ const nextCount = Math.max(0, (wsOwnerCounts.get(ws) ?? 0) - 1);
621
+ if (nextCount === 0) {
622
+ wsOwnerCounts.delete(ws);
623
+ } else {
624
+ wsOwnerCounts.set(ws, nextCount);
625
+ }
626
+ return nextCount;
627
+ }
628
+ function connectShared(ws) {
629
+ if (ws.isConnected()) {
630
+ return Promise.resolve();
631
+ }
632
+ const existing = wsConnectPromises.get(ws);
633
+ if (existing) {
634
+ return existing;
635
+ }
636
+ const promise = ws.connect().finally(() => {
637
+ wsConnectPromises.delete(ws);
638
+ });
639
+ wsConnectPromises.set(ws, promise);
640
+ return promise;
550
641
  }
551
642
  function useSymmWs(params = {}) {
552
643
  const ctx = react.useContext(SymmContext);
@@ -563,114 +654,76 @@ function useSymmWs(params = {}) {
563
654
  }
564
655
  const ws = symmCoreClient.ws;
565
656
  const addr = accountAddress;
566
- const unsubscribers = [];
567
657
  let cancelled = false;
568
- const removeOnConnect = ws.onConnect(() => {
569
- setConnected(true);
658
+ addWsOwner(ws);
659
+ const removeOnConnect = ws.onConnect(() => setConnected(true));
660
+ const removeOnDisconnect = ws.onDisconnect(() => setConnected(false));
661
+ const subscriptions = [];
662
+ const addSubscription = (channel, handler) => {
663
+ ws.subscribe(channel, addr, chainId, handler);
664
+ subscriptions.push({ channel, handler });
665
+ };
666
+ addSubscription("positions", () => {
667
+ queryClient.invalidateQueries({ queryKey: symmKeys.positionsRoot });
570
668
  });
571
- const removeOnDisconnect = ws.onDisconnect(() => {
572
- setConnected(false);
669
+ addSubscription("open-orders", () => {
670
+ queryClient.invalidateQueries({ queryKey: symmKeys.openOrdersRoot });
573
671
  });
574
- const removeOnError = asUnsubscribeFn(ws.onError?.(() => {
575
- }));
576
- const removeOnWelcome = asUnsubscribeFn(ws.onWelcome?.(() => {
577
- }));
578
- unsubscribers.push(removeOnConnect, removeOnDisconnect);
579
- if (removeOnError) unsubscribers.push(removeOnError);
580
- if (removeOnWelcome) unsubscribers.push(removeOnWelcome);
581
- const positionsUnsub = asUnsubscribeFn(
582
- ws.subscribeToPositions(addr, chainId, () => {
583
- queryClient.invalidateQueries({
584
- queryKey: symmKeys.positionsRoot
585
- });
586
- })
587
- );
588
- if (positionsUnsub) unsubscribers.push(positionsUnsub);
589
- const openOrdersUnsub = asUnsubscribeFn(
590
- ws.subscribeToOpenOrders(addr, chainId, () => {
591
- queryClient.invalidateQueries({
592
- queryKey: symmKeys.openOrdersRoot
593
- });
594
- })
595
- );
596
- if (openOrdersUnsub) unsubscribers.push(openOrdersUnsub);
597
- const tradesUnsub = asUnsubscribeFn(
598
- ws.subscribeToTrades(addr, chainId, () => {
599
- queryClient.invalidateQueries({
600
- queryKey: symmKeys.tradeHistoryRoot
601
- });
602
- })
603
- );
604
- if (tradesUnsub) unsubscribers.push(tradesUnsub);
605
- const accountSummaryUnsub = asUnsubscribeFn(
606
- ws.subscribeToAccountSummary(addr, chainId, () => {
607
- queryClient.invalidateQueries({
608
- queryKey: symmKeys.balances(accountAddress, chainId)
609
- });
610
- queryClient.invalidateQueries({
611
- queryKey: symmKeys.accountSummary(accountAddress, chainId)
612
- });
613
- })
614
- );
615
- if (accountSummaryUnsub) unsubscribers.push(accountSummaryUnsub);
616
- const notificationsUnsub = asUnsubscribeFn(
617
- ws.subscribeToNotifications(addr, chainId, () => {
618
- queryClient.invalidateQueries({
619
- queryKey: symmKeys.notifications({ accountAddress, chainId })
620
- });
621
- queryClient.invalidateQueries({
622
- queryKey: symmKeys.unreadCount({ accountAddress, chainId })
623
- });
624
- })
625
- );
626
- if (notificationsUnsub) unsubscribers.push(notificationsUnsub);
627
- const tpslUnsub = asUnsubscribeFn(
628
- ws.subscribeToTpsl(addr, chainId, () => {
629
- queryClient.invalidateQueries({ queryKey: symmKeys.tpslOrdersRoot });
630
- queryClient.invalidateQueries({ queryKey: symmKeys.openOrdersRoot });
631
- })
632
- );
633
- if (tpslUnsub) unsubscribers.push(tpslUnsub);
634
- const twapUnsub = asUnsubscribeFn(
635
- ws.subscribeToTwapOrders(addr, chainId, () => {
636
- queryClient.invalidateQueries({ queryKey: symmKeys.twapOrdersRoot });
637
- queryClient.invalidateQueries({ queryKey: symmKeys.openOrdersRoot });
638
- })
639
- );
640
- if (twapUnsub) unsubscribers.push(twapUnsub);
641
- const triggerOrdersUnsub = asUnsubscribeFn(
642
- ws.subscribeToTriggerOrders(addr, chainId, () => {
643
- queryClient.invalidateQueries({ queryKey: symmKeys.triggerOrdersRoot });
644
- queryClient.invalidateQueries({ queryKey: symmKeys.openOrdersRoot });
645
- })
646
- );
647
- if (triggerOrdersUnsub) unsubscribers.push(triggerOrdersUnsub);
648
- const executionsUnsub = asUnsubscribeFn(
649
- ws.subscribeToExecutions(addr, chainId, () => {
650
- queryClient.invalidateQueries({
651
- queryKey: symmKeys.positionsRoot
652
- });
653
- queryClient.invalidateQueries({
654
- queryKey: symmKeys.portfolioRoot
655
- });
656
- })
657
- );
658
- if (executionsUnsub) unsubscribers.push(executionsUnsub);
659
- void ws.connect().then(() => {
660
- if (cancelled) {
661
- return;
672
+ addSubscription("trades", () => {
673
+ queryClient.invalidateQueries({ queryKey: symmKeys.tradeHistoryRoot });
674
+ });
675
+ addSubscription("account-summary", () => {
676
+ queryClient.invalidateQueries({
677
+ queryKey: symmKeys.balances(accountAddress, chainId)
678
+ });
679
+ queryClient.invalidateQueries({
680
+ queryKey: symmKeys.accountSummary(accountAddress, chainId)
681
+ });
682
+ });
683
+ addSubscription("notifications", () => {
684
+ queryClient.invalidateQueries({
685
+ queryKey: symmKeys.notifications({ accountAddress, chainId })
686
+ });
687
+ queryClient.invalidateQueries({
688
+ queryKey: symmKeys.unreadCount({ accountAddress, chainId })
689
+ });
690
+ });
691
+ addSubscription("tpsl", () => {
692
+ queryClient.invalidateQueries({ queryKey: symmKeys.tpslOrdersRoot });
693
+ queryClient.invalidateQueries({ queryKey: symmKeys.openOrdersRoot });
694
+ });
695
+ addSubscription("twap-orders", () => {
696
+ queryClient.invalidateQueries({ queryKey: symmKeys.twapOrdersRoot });
697
+ queryClient.invalidateQueries({ queryKey: symmKeys.openOrdersRoot });
698
+ });
699
+ addSubscription("trigger-orders", () => {
700
+ queryClient.invalidateQueries({ queryKey: symmKeys.triggerOrdersRoot });
701
+ queryClient.invalidateQueries({ queryKey: symmKeys.openOrdersRoot });
702
+ });
703
+ addSubscription("executions", () => {
704
+ queryClient.invalidateQueries({ queryKey: symmKeys.positionsRoot });
705
+ queryClient.invalidateQueries({ queryKey: symmKeys.portfolioRoot });
706
+ });
707
+ void connectShared(ws).then(() => {
708
+ if (cancelled) return;
709
+ if (ws.isConnected()) {
710
+ setConnected(true);
662
711
  }
663
712
  }).catch(() => {
664
- if (cancelled) {
665
- return;
666
- }
713
+ if (cancelled) return;
667
714
  setConnected(false);
668
715
  });
669
716
  return () => {
670
717
  cancelled = true;
671
- unsubscribers.forEach((unsubscribe) => unsubscribe());
672
- ws.disconnect();
673
- setConnected(false);
718
+ removeOnConnect();
719
+ removeOnDisconnect();
720
+ subscriptions.forEach(({ channel, handler }) => {
721
+ ws.unsubscribe(channel, addr, chainId, handler);
722
+ });
723
+ if (removeWsOwner(ws) === 0) {
724
+ ws.disconnect();
725
+ setConnected(false);
726
+ }
674
727
  };
675
728
  }, [symmCoreClient, accountAddress, chainId, queryClient, setConnected]);
676
729
  return { isConnected };
@@ -833,28 +886,54 @@ async function login(chainId, params) {
833
886
  var TOKEN_STORAGE_PREFIX = "symm_access_token";
834
887
  var TOKEN_STORAGE_VERSION = 1;
835
888
  function cacheKey(address, chainId) {
836
- return `${address}:${chainId}`;
889
+ return `${address.toLowerCase()}:${chainId}`;
837
890
  }
838
891
  function storageKey(address, chainId) {
839
892
  return `${TOKEN_STORAGE_PREFIX}:${TOKEN_STORAGE_VERSION}:${cacheKey(address, chainId)}`;
840
893
  }
894
+ function findStorageKey(address, chainId) {
895
+ const canonicalKey = storageKey(address, chainId);
896
+ if (typeof window === "undefined") return canonicalKey;
897
+ if (window.localStorage.getItem(canonicalKey) != null) {
898
+ return canonicalKey;
899
+ }
900
+ const normalizedKey = canonicalKey.toLowerCase();
901
+ for (let index = 0; index < window.localStorage.length; index += 1) {
902
+ const key = window.localStorage.key(index);
903
+ if (key?.toLowerCase() === normalizedKey) {
904
+ return key;
905
+ }
906
+ }
907
+ return canonicalKey;
908
+ }
909
+ function removeStoredTokenKeys(address, chainId) {
910
+ if (typeof window === "undefined") return;
911
+ const normalizedKey = storageKey(address, chainId).toLowerCase();
912
+ for (let index = window.localStorage.length - 1; index >= 0; index -= 1) {
913
+ const key = window.localStorage.key(index);
914
+ if (key?.toLowerCase() === normalizedKey) {
915
+ window.localStorage.removeItem(key);
916
+ }
917
+ }
918
+ }
841
919
  function getCachedTokenEntry(address, chainId) {
842
920
  if (typeof window === "undefined") return null;
921
+ const key = findStorageKey(address, chainId);
843
922
  try {
844
- const raw = window.localStorage.getItem(storageKey(address, chainId));
923
+ const raw = window.localStorage.getItem(key);
845
924
  if (!raw) return null;
846
925
  const parsed = JSON.parse(raw);
847
926
  if (!parsed || typeof parsed.token !== "string" || typeof parsed.expiresAt !== "number") {
848
- window.localStorage.removeItem(storageKey(address, chainId));
927
+ window.localStorage.removeItem(key);
849
928
  return null;
850
929
  }
851
930
  if (Date.now() >= parsed.expiresAt) {
852
- window.localStorage.removeItem(storageKey(address, chainId));
931
+ window.localStorage.removeItem(key);
853
932
  return null;
854
933
  }
855
934
  return parsed;
856
935
  } catch {
857
- window.localStorage.removeItem(storageKey(address, chainId));
936
+ window.localStorage.removeItem(key);
858
937
  return null;
859
938
  }
860
939
  }
@@ -869,9 +948,7 @@ function writeStoredToken(address, chainId, cached) {
869
948
  }
870
949
  }
871
950
  function clearCachedToken(address, chainId) {
872
- if (typeof window !== "undefined") {
873
- window.localStorage.removeItem(storageKey(address, chainId));
874
- }
951
+ removeStoredTokenKeys(address, chainId);
875
952
  }
876
953
  async function fetchAccessTokenEntry(walletClient, signerAddress, accountAddress, chainId, domain) {
877
954
  const resolvedDomain = domain ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
@@ -905,7 +982,7 @@ async function fetchAccessTokenEntry(walletClient, signerAddress, accountAddress
905
982
  return cachedToken;
906
983
  }
907
984
  function authStoreKey(accountAddress, chainId, signerAddress) {
908
- return `${accountAddress}:${chainId}:${signerAddress ?? ""}`;
985
+ return `${accountAddress.toLowerCase()}:${chainId}:${signerAddress?.toLowerCase() ?? ""}`;
909
986
  }
910
987
  function getEntryFromSnapshot(state, accountAddress, chainId, signerAddress) {
911
988
  const signerScoped = state.entries[authStoreKey(accountAddress, chainId, signerAddress)];
@@ -2346,14 +2423,15 @@ function splitTradeHookArgs(paramsOrOptions, options) {
2346
2423
  function useResolveTradeAuthToken(params = {}) {
2347
2424
  const context = useSymmContext();
2348
2425
  const address = params.address ?? context.address;
2426
+ const defaultAccountAddress = params.accountAddress ?? address;
2349
2427
  const chainId = params.chainId ?? context.chainId;
2350
2428
  return react.useCallback(
2351
- async (providedAuthToken, accountAddress, overrideChainId) => {
2429
+ async (providedAuthToken, requestAccountAddress, overrideChainId) => {
2352
2430
  const resolvedChainId = overrideChainId ?? chainId;
2353
2431
  if (providedAuthToken) {
2354
2432
  return providedAuthToken;
2355
2433
  }
2356
- const resolvedAccountAddress = accountAddress ?? address;
2434
+ const resolvedAccountAddress = requestAccountAddress ?? defaultAccountAddress;
2357
2435
  if (!resolvedAccountAddress) {
2358
2436
  return null;
2359
2437
  }
@@ -2367,7 +2445,7 @@ function useResolveTradeAuthToken(params = {}) {
2367
2445
  }
2368
2446
  return null;
2369
2447
  },
2370
- [address, chainId]
2448
+ [address, defaultAccountAddress, chainId]
2371
2449
  );
2372
2450
  }
2373
2451
 
@@ -25616,7 +25694,8 @@ function useSymmClosePositionMutation(paramsOrOptions, maybeOptions) {
25616
25694
  const typedRequest = request;
25617
25695
  const authToken = await resolveAuthToken(
25618
25696
  typedRequest.authToken,
25619
- typedRequest.accountAddress
25697
+ typedRequest.accountAddress,
25698
+ typedRequest.chainId
25620
25699
  );
25621
25700
  if (!authToken) {
25622
25701
  throw new Error("auth token is required to close a position");