@pear-protocol/symmio-client 0.2.27 → 0.2.28

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.
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { createContext, useContext, useMemo, useRef, useCallback, useEffect } from 'react';
3
3
  import { createSymmSDK, isAuthExpiredError, isNetworkError, isInsufficientMarginError, isRateLimitedError, isTimeoutError } from '@pear-protocol/symm-core';
4
- import { jsx } from 'react/jsx-runtime';
5
4
  import { create } from 'zustand';
5
+ import { jsx } from 'react/jsx-runtime';
6
6
  import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
7
7
  import { isAddress, encodeFunctionData } from 'viem';
8
8
 
@@ -14,342 +14,773 @@ function useSymmContext() {
14
14
  }
15
15
  return ctx;
16
16
  }
17
- function SymmProvider({
18
- chainId = 42161,
19
- address,
20
- symmCoreConfig = {
21
- apiUrl: "https://nginx-server-staging.up.railway.app",
22
- wsUrl: "wss://nginx-server-staging.up.railway.app"
23
- },
24
- symmioConfig,
25
- children
26
- }) {
27
- const symmCoreClient = useMemo(() => {
28
- return createSymmSDK({
29
- apiUrl: symmCoreConfig.apiUrl,
30
- wsUrl: symmCoreConfig.wsUrl,
31
- defaultChainId: chainId
32
- });
33
- }, [chainId, symmCoreConfig.apiUrl, symmCoreConfig.wsUrl]);
34
- const value = useMemo(
35
- () => ({
36
- symmCoreClient,
37
- chainId,
38
- address,
39
- symmioConfig
40
- }),
41
- [symmCoreClient, chainId, address, symmioConfig]
42
- );
43
- return /* @__PURE__ */ jsx(SymmContext.Provider, { value, children });
44
- }
45
-
46
- // src/react/hooks/use-symm-core-client.ts
47
- function useSymmCoreClient() {
48
- return useSymmContext().symmCoreClient;
49
- }
50
17
 
51
- // src/constants/defaults.ts
52
- var GAS_MARGIN_PERCENTAGE = 20n;
53
- var MUON_BASE_URLS = [
54
- "https://muon-oracle1.rasa.capital/v1/",
55
- "https://muon-oracle2.rasa.capital/v1/",
56
- "https://muon-oracle3.rasa.capital/v1/",
57
- "https://muon-oracle4.rasa.capital/v1/"
58
- ];
59
- var MUON_APP_NAME = "symmio";
60
- var MUON_REQUEST_TIMEOUT = 15e3;
61
- var HEDGER_BASE_URLS = {
62
- [42161 /* ARBITRUM */]: "https://www.perps-streaming.com/v1/42161a/0x6273242a7E88b3De90822b31648C212215caaFE4",
63
- [8453 /* BASE */]: "https://www.perps-streaming.com/v1/8453a/0x6273242a7E88b3De90822b31648C212215caaFE4"
18
+ // src/utils/binance-symbol-map.ts
19
+ var SYMBOL_OVERRIDES = {
20
+ // Add overrides here as needed for SYMM markets that don't map 1:1 to Binance
21
+ // e.g., 'SOME_SYMM_SYMBOL': 'BINANCE_SYMBOL',
64
22
  };
65
-
66
- // src/actions/instant.ts
67
- function getHedgerBaseUrl(chainId) {
68
- const baseUrl = HEDGER_BASE_URLS[chainId];
69
- if (!baseUrl) {
70
- throw new Error(`No hedger base URL configured for chain ${chainId}.`);
71
- }
72
- return baseUrl;
73
- }
74
- function createSiweMessage(params) {
75
- const version = params.version ?? "1";
76
- const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
77
- const expirationTime = new Date(
78
- Date.now() + 30 * 24 * 60 * 60 * 1e3
79
- ).toISOString();
80
- const message = [
81
- `${params.domain} wants you to sign in with your Ethereum account:`,
82
- params.address,
83
- "",
84
- params.statement,
85
- "",
86
- `URI: ${params.uri}`,
87
- `Version: ${version}`,
88
- `Chain ID: ${params.chainId}`,
89
- `Nonce: ${params.nonce}`,
90
- `Issued At: ${issuedAt}`,
91
- `Expiration Time: ${expirationTime}`
92
- ].join("\n");
93
- return { message, issuedAt, expirationTime };
94
- }
95
- async function getNonce(chainId, subAccount) {
96
- const hedgerBaseUrl = getHedgerBaseUrl(chainId);
97
- const url = new URL(`nonce/${subAccount}`, hedgerBaseUrl).href;
98
- const response = await fetch(url);
99
- if (!response.ok) {
100
- throw new Error(`Failed to fetch nonce: ${response.statusText}`);
23
+ var UNSUPPORTED_SYMBOLS = /* @__PURE__ */ new Set([
24
+ // Add symbols here that have no Binance equivalent
25
+ ]);
26
+ function resolveBinanceSymbol(symmSymbol) {
27
+ if (!symmSymbol || !symmSymbol.trim()) {
28
+ return {
29
+ symmSymbol,
30
+ normalizedSymbol: "",
31
+ binanceSymbol: null,
32
+ supported: false,
33
+ reason: "missing_symbol"
34
+ };
101
35
  }
102
- const data = await response.json();
103
- return data.nonce;
104
- }
105
- async function login(chainId, params) {
106
- const hedgerBaseUrl = getHedgerBaseUrl(chainId);
107
- const url = new URL("login", hedgerBaseUrl).href;
108
- const body = {
109
- account_address: params.accountAddress,
110
- expiration_time: params.expirationTime,
111
- issued_at: params.issuedAt,
112
- signature: params.signature,
113
- nonce: params.nonce
114
- };
115
- const response = await fetch(url, {
116
- method: "POST",
117
- headers: { "Content-Type": "application/json" },
118
- body: JSON.stringify(body)
119
- });
120
- if (!response.ok) {
121
- const errorData = await response.json().catch(() => null);
122
- throw new Error(
123
- errorData?.error_message ?? `Login failed: ${response.statusText}`
124
- );
36
+ const normalized = symmSymbol.toUpperCase().trim();
37
+ if (!/^[A-Z0-9]+$/.test(normalized)) {
38
+ return {
39
+ symmSymbol,
40
+ normalizedSymbol: normalized,
41
+ binanceSymbol: null,
42
+ supported: false,
43
+ reason: "invalid_symbol"
44
+ };
125
45
  }
126
- const data = await response.json();
127
- if (!data.access_token) {
128
- throw new Error("No access token received");
46
+ if (UNSUPPORTED_SYMBOLS.has(normalized)) {
47
+ return {
48
+ symmSymbol,
49
+ normalizedSymbol: normalized,
50
+ binanceSymbol: null,
51
+ supported: false,
52
+ reason: "unsupported_symbol"
53
+ };
129
54
  }
55
+ const binanceSymbol = SYMBOL_OVERRIDES[normalized] ?? (normalized.endsWith("USDT") ? normalized : `${normalized}USDT`);
130
56
  return {
131
- accessToken: data.access_token,
132
- expirationTime: params.expirationTime,
133
- issuedAt: params.issuedAt
57
+ symmSymbol,
58
+ normalizedSymbol: normalized,
59
+ binanceSymbol,
60
+ supported: true,
61
+ reason: null
134
62
  };
135
63
  }
136
-
137
- // src/react/auth.ts
138
- var TOKEN_STORAGE_PREFIX = "symm_access_token";
139
- var TOKEN_STORAGE_VERSION = 1;
140
- var tokenCache = /* @__PURE__ */ new Map();
141
- function cacheKey(address, chainId) {
142
- return `${address}:${chainId}`;
64
+ function getUnsupportedBinanceSymbols(symbols) {
65
+ return symbols.filter((symbol) => !resolveBinanceSymbol(symbol).supported);
143
66
  }
144
- function storageKey(address, chainId) {
145
- return `${TOKEN_STORAGE_PREFIX}:${TOKEN_STORAGE_VERSION}:${cacheKey(address, chainId)}`;
67
+ function toBinanceSymbol(symmSymbol) {
68
+ return resolveBinanceSymbol(symmSymbol).binanceSymbol;
146
69
  }
147
- function readStoredToken(address, chainId) {
148
- if (typeof window === "undefined") return null;
149
- try {
150
- const raw = window.localStorage.getItem(storageKey(address, chainId));
151
- if (!raw) return null;
152
- const parsed = JSON.parse(raw);
153
- if (!parsed || typeof parsed.token !== "string" || typeof parsed.expiresAt !== "number") {
154
- window.localStorage.removeItem(storageKey(address, chainId));
155
- return null;
156
- }
157
- if (Date.now() >= parsed.expiresAt) {
158
- window.localStorage.removeItem(storageKey(address, chainId));
159
- return null;
70
+
71
+ // src/utils/binance-ws.ts
72
+ var BINANCE_WS_URL = "wss://fstream.binance.com/ws";
73
+ var RECONNECT_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
74
+ var STABLE_QUOTES = ["USDT0", "USDT", "USDC", "USDE", "USDH", "USD"];
75
+ function normalizeBaseSymbol(symbol) {
76
+ const normalized = symbol.toUpperCase().trim();
77
+ for (const quote of STABLE_QUOTES) {
78
+ if (normalized.endsWith(quote) && normalized.length > quote.length) {
79
+ return normalized.slice(0, -quote.length);
160
80
  }
161
- return parsed;
162
- } catch {
163
- window.localStorage.removeItem(storageKey(address, chainId));
164
- return null;
165
81
  }
82
+ return normalized;
166
83
  }
167
- function writeStoredToken(address, chainId, cached) {
168
- if (typeof window === "undefined") return;
169
- try {
170
- window.localStorage.setItem(
171
- storageKey(address, chainId),
172
- JSON.stringify(cached)
173
- );
174
- } catch {
175
- }
84
+ function isBinanceMarkPriceTicker(value) {
85
+ return Boolean(
86
+ value && typeof value === "object" && typeof value.s === "string" && typeof value.p === "string" && typeof value.i === "string" && typeof value.E === "number"
87
+ );
176
88
  }
177
- function getCachedToken(address, chainId) {
178
- const cached = tokenCache.get(cacheKey(address, chainId));
179
- if (cached && Date.now() < cached.expiresAt) {
180
- return cached.token;
89
+ function extractTickers(payload) {
90
+ if (Array.isArray(payload)) {
91
+ return payload.filter(isBinanceMarkPriceTicker);
181
92
  }
182
- if (cached) {
183
- tokenCache.delete(cacheKey(address, chainId));
93
+ if (payload && typeof payload === "object") {
94
+ const maybeData = payload.data;
95
+ if (Array.isArray(maybeData)) {
96
+ return maybeData.filter(isBinanceMarkPriceTicker);
97
+ }
98
+ return isBinanceMarkPriceTicker(payload) ? [payload] : [];
184
99
  }
185
- const stored = readStoredToken(address, chainId);
186
- if (!stored) return null;
187
- tokenCache.set(cacheKey(address, chainId), stored);
188
- return stored.token;
100
+ return [];
189
101
  }
190
- function clearCachedToken(address, chainId) {
191
- tokenCache.delete(cacheKey(address, chainId));
192
- if (typeof window !== "undefined") {
193
- window.localStorage.removeItem(storageKey(address, chainId));
102
+ var BinanceWsManager = class {
103
+ ws = null;
104
+ streams = /* @__PURE__ */ new Map();
105
+ reconnectAttempt = 0;
106
+ reconnectTimer = null;
107
+ intentionalClose = false;
108
+ pendingSubscribes = [];
109
+ idCounter = 0;
110
+ /**
111
+ * Subscribe to a kline stream. Returns an unsubscribe function.
112
+ */
113
+ subscribeKline(symbol, interval, cb) {
114
+ const streamName = `${symbol.toLowerCase()}@kline_${interval}`;
115
+ const id = this.generateId();
116
+ const wrappedCb = (raw) => {
117
+ const k = raw.k;
118
+ if (!k) return;
119
+ cb({
120
+ symbol: raw.s,
121
+ interval: k.i,
122
+ openTime: k.t,
123
+ closeTime: k.T,
124
+ open: parseFloat(k.o),
125
+ high: parseFloat(k.h),
126
+ low: parseFloat(k.l),
127
+ close: parseFloat(k.c),
128
+ volume: parseFloat(k.v),
129
+ isFinal: k.x
130
+ });
131
+ };
132
+ this.addStreamCallback(streamName, id, wrappedCb);
133
+ return () => this.removeStreamCallback(streamName, id);
194
134
  }
195
- }
196
- async function fetchAccessToken(walletClient, signerAddress, accountAddress, chainId, domain) {
197
- const resolvedDomain = domain ?? (typeof window !== "undefined" ? window.location.host : "localhost");
198
- const uri = typeof window !== "undefined" ? window.location.origin : `https://${resolvedDomain}`;
199
- const nonce = await getNonce(chainId, accountAddress);
200
- const { message, issuedAt, expirationTime } = createSiweMessage({
201
- address: signerAddress,
202
- statement: "Sign in to Symm Protocol",
203
- chainId,
204
- nonce,
205
- domain: resolvedDomain,
206
- uri
207
- });
208
- const signature = await walletClient.signMessage({
209
- account: signerAddress,
210
- message
211
- });
212
- const { accessToken } = await login(chainId, {
213
- accountAddress,
214
- signature,
215
- expirationTime,
216
- issuedAt,
217
- nonce
218
- });
219
- const expiresAt = new Date(expirationTime).getTime();
220
- const cachedToken = {
221
- token: accessToken,
222
- expiresAt: expiresAt - 6e4
223
- };
224
- tokenCache.set(cacheKey(accountAddress, chainId), cachedToken);
225
- writeStoredToken(accountAddress, chainId, cachedToken);
226
- return accessToken;
227
- }
228
- function symmAuthTokenKey(address, chainId) {
229
- return `${address.toLowerCase()}:${chainId}`;
230
- }
231
- var useSymmAuthStore = create((set, get) => ({
232
- tokensByKey: {},
233
- setToken: (address, chainId, token) => {
234
- const key = symmAuthTokenKey(address, chainId);
235
- set((state) => ({
236
- tokensByKey: {
237
- ...state.tokensByKey,
238
- [key]: token
135
+ /**
136
+ * Subscribe to a mark price stream (1s updates). Returns an unsubscribe function.
137
+ */
138
+ subscribeMarkPrice(symbol, cb) {
139
+ const streamName = `${symbol.toLowerCase()}@markPrice@1s`;
140
+ const id = this.generateId();
141
+ const wrappedCb = (raw) => {
142
+ cb({
143
+ symbol: normalizeBaseSymbol(raw.s),
144
+ markPrice: parseFloat(raw.p),
145
+ indexPrice: parseFloat(raw.i),
146
+ time: raw.E
147
+ });
148
+ };
149
+ this.addStreamCallback(streamName, id, wrappedCb);
150
+ return () => this.removeStreamCallback(streamName, id);
151
+ }
152
+ /**
153
+ * Subscribe to the all-market mark price stream (1s updates).
154
+ * Returns an unsubscribe function.
155
+ */
156
+ subscribeAllMarkPrices(cb) {
157
+ const streamName = "!markPrice@arr@1s";
158
+ const id = this.generateId();
159
+ const wrappedCb = (raw) => {
160
+ cb(
161
+ extractTickers(raw).map((entry) => ({
162
+ symbol: normalizeBaseSymbol(entry.s),
163
+ markPrice: parseFloat(entry.p),
164
+ indexPrice: parseFloat(entry.i),
165
+ time: entry.E
166
+ }))
167
+ );
168
+ };
169
+ this.addStreamCallback(streamName, id, wrappedCb);
170
+ return () => this.removeStreamCallback(streamName, id);
171
+ }
172
+ /**
173
+ * Destroy the manager and close the connection.
174
+ */
175
+ destroy() {
176
+ this.intentionalClose = true;
177
+ if (this.reconnectTimer) {
178
+ clearTimeout(this.reconnectTimer);
179
+ this.reconnectTimer = null;
180
+ }
181
+ if (this.ws) {
182
+ this.ws.close();
183
+ this.ws = null;
184
+ }
185
+ this.streams.clear();
186
+ }
187
+ // --- Private ---
188
+ generateId() {
189
+ return `sub_${++this.idCounter}_${Date.now()}`;
190
+ }
191
+ addStreamCallback(streamName, id, cb) {
192
+ let sub = this.streams.get(streamName);
193
+ const isNew = !sub;
194
+ if (!sub) {
195
+ sub = { callbacks: /* @__PURE__ */ new Map() };
196
+ this.streams.set(streamName, sub);
197
+ }
198
+ sub.callbacks.set(id, cb);
199
+ if (isNew) {
200
+ this.ensureConnected();
201
+ this.sendSubscribe([streamName]);
202
+ }
203
+ }
204
+ removeStreamCallback(streamName, id) {
205
+ const sub = this.streams.get(streamName);
206
+ if (!sub) return;
207
+ sub.callbacks.delete(id);
208
+ if (sub.callbacks.size === 0) {
209
+ this.streams.delete(streamName);
210
+ this.sendUnsubscribe([streamName]);
211
+ if (this.streams.size === 0 && this.ws) {
212
+ this.intentionalClose = true;
213
+ this.ws.close();
214
+ this.ws = null;
215
+ this.intentionalClose = false;
239
216
  }
240
- }));
241
- },
242
- getToken: (address, chainId) => {
243
- const key = symmAuthTokenKey(address, chainId);
244
- return get().tokensByKey[key] ?? null;
245
- },
246
- clearToken: (address, chainId) => {
247
- const key = symmAuthTokenKey(address, chainId);
248
- set((state) => {
249
- if (!(key in state.tokensByKey)) return state;
250
- const next = { ...state.tokensByKey };
251
- delete next[key];
252
- return { tokensByKey: next };
253
- });
254
- },
255
- clearAll: () => {
256
- set({ tokensByKey: {} });
217
+ }
257
218
  }
258
- }));
259
-
260
- // src/react/hooks/use-symm-auth.ts
261
- function useSymmAuth(params) {
262
- const ctx = useSymmContext();
263
- const address = params?.address ?? ctx.address;
264
- const chainId = params?.chainId ?? ctx.chainId ?? 42161;
265
- const walletClient = params?.walletClient ?? ctx.walletClient;
266
- const siweDomain = params?.siweDomain;
267
- const accessToken = useSymmAuthStore((state) => {
268
- if (address) {
269
- return state.tokensByKey[symmAuthTokenKey(address, chainId)] ?? null;
219
+ ensureConnected() {
220
+ if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
221
+ return;
270
222
  }
271
- return ctx.accessToken ?? ctx.authToken ?? null;
272
- });
273
- const setToken = useSymmAuthStore((state) => state.setToken);
274
- const getToken = useSymmAuthStore((state) => state.getToken);
275
- const clearToken = useSymmAuthStore((state) => state.clearToken);
276
- const previousAddressRef = useRef(address);
277
- const previousChainIdRef = useRef(chainId);
278
- const refreshAuth = useCallback(
279
- async (accountAddress) => {
280
- if (!walletClient || !address) return null;
281
- const resolvedAccountAddress = accountAddress ?? address;
282
- const inMemory = getToken(resolvedAccountAddress, chainId);
283
- if (inMemory) {
284
- return inMemory;
223
+ this.connect();
224
+ }
225
+ connect() {
226
+ if (typeof WebSocket === "undefined") return;
227
+ this.intentionalClose = false;
228
+ this.ws = new WebSocket(BINANCE_WS_URL);
229
+ this.ws.onopen = () => {
230
+ this.reconnectAttempt = 0;
231
+ const activeStreams = Array.from(this.streams.keys());
232
+ if (activeStreams.length > 0) {
233
+ this.sendSubscribe(activeStreams);
285
234
  }
286
- const cached = getCachedToken(resolvedAccountAddress, chainId);
287
- if (cached) {
288
- setToken(resolvedAccountAddress, chainId, cached);
289
- return cached;
235
+ if (this.pendingSubscribes.length > 0) {
236
+ this.sendSubscribe(this.pendingSubscribes);
237
+ this.pendingSubscribes = [];
290
238
  }
239
+ };
240
+ this.ws.onmessage = (event) => {
291
241
  try {
292
- const token = await fetchAccessToken(
293
- walletClient,
294
- address,
295
- resolvedAccountAddress,
296
- chainId,
297
- siweDomain
298
- );
299
- setToken(resolvedAccountAddress, chainId, token);
300
- return token;
242
+ const data = JSON.parse(event.data);
243
+ this.handleMessage(data);
301
244
  } catch {
302
- clearToken(resolvedAccountAddress, chainId);
303
- return null;
304
245
  }
305
- },
306
- [walletClient, address, chainId, siweDomain, getToken, setToken, clearToken]
307
- );
308
- const clearAuth = useCallback(() => {
309
- if (address) {
310
- clearCachedToken(address, chainId);
311
- clearToken(address, chainId);
312
- }
313
- }, [address, chainId, clearToken]);
314
- useEffect(() => {
315
- const previousAddress = previousAddressRef.current;
316
- const previousChainId = previousChainIdRef.current;
317
- const addressChanged = previousAddress !== address;
318
- const chainChanged = previousChainId !== chainId;
319
- previousAddressRef.current = address;
320
- previousChainIdRef.current = chainId;
321
- if (!address || !walletClient) {
246
+ };
247
+ this.ws.onclose = () => {
248
+ if (this.intentionalClose) return;
249
+ this.scheduleReconnect();
250
+ };
251
+ this.ws.onerror = () => {
252
+ };
253
+ }
254
+ handleMessage(data) {
255
+ if (Array.isArray(data)) {
256
+ this.dispatchToStream("!markPrice@arr@1s", data);
322
257
  return;
323
258
  }
324
- if (previousAddress && (addressChanged || chainChanged)) {
325
- clearCachedToken(previousAddress, previousChainId);
326
- clearToken(previousAddress, previousChainId);
259
+ if (data.e === "kline") {
260
+ const k = data.k;
261
+ const streamName = `${data.s.toLowerCase()}@kline_${k.i}`;
262
+ this.dispatchToStream(streamName, data);
263
+ } else if (data.e === "markPriceUpdate") {
264
+ const streamName = `${data.s.toLowerCase()}@markPrice@1s`;
265
+ this.dispatchToStream(streamName, data);
327
266
  }
328
- const cached = getCachedToken(address, chainId);
329
- if (cached) {
330
- setToken(address, chainId, cached);
331
- } else {
332
- clearToken(address, chainId);
267
+ }
268
+ dispatchToStream(streamName, data) {
269
+ const sub = this.streams.get(streamName);
270
+ if (!sub) return;
271
+ sub.callbacks.forEach((cb) => {
272
+ try {
273
+ cb(data);
274
+ } catch {
275
+ }
276
+ });
277
+ }
278
+ sendSubscribe(streams) {
279
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
280
+ this.pendingSubscribes.push(...streams);
281
+ return;
333
282
  }
334
- }, [address, walletClient, chainId, setToken, clearToken]);
335
- return {
336
- accessToken,
337
- authToken: accessToken,
338
- isAuthenticated: !!accessToken,
339
- refresh: refreshAuth,
340
- refreshAuth,
341
- clear: clearAuth
342
- };
343
- }
344
-
345
- // src/constants/selectors.ts
346
- var SEND_QUOTE_WITH_AFFILIATE_SELECTOR = "0x40f1310c";
347
- var CLOSE_QUOTE_SELECTOR = "0x501e891f";
348
- var ALL_TRADING_SELECTORS = [
349
- SEND_QUOTE_WITH_AFFILIATE_SELECTOR,
350
- CLOSE_QUOTE_SELECTOR
351
- ];
352
-
283
+ this.ws.send(JSON.stringify({
284
+ method: "SUBSCRIBE",
285
+ params: streams,
286
+ id: Date.now()
287
+ }));
288
+ }
289
+ sendUnsubscribe(streams) {
290
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
291
+ this.pendingSubscribes = this.pendingSubscribes.filter(
292
+ (s) => !streams.includes(s)
293
+ );
294
+ return;
295
+ }
296
+ this.ws.send(JSON.stringify({
297
+ method: "UNSUBSCRIBE",
298
+ params: streams,
299
+ id: Date.now()
300
+ }));
301
+ }
302
+ scheduleReconnect() {
303
+ if (this.reconnectTimer) return;
304
+ if (this.streams.size === 0) return;
305
+ const delay = RECONNECT_DELAYS[Math.min(this.reconnectAttempt, RECONNECT_DELAYS.length - 1)];
306
+ this.reconnectAttempt++;
307
+ this.reconnectTimer = setTimeout(() => {
308
+ this.reconnectTimer = null;
309
+ this.connect();
310
+ }, delay);
311
+ }
312
+ };
313
+ var _instance = null;
314
+ function getBinanceWsManager() {
315
+ if (!_instance) {
316
+ _instance = new BinanceWsManager();
317
+ }
318
+ return _instance;
319
+ }
320
+
321
+ // src/react/stores/use-binance-mark-price-store.ts
322
+ var refCounts = /* @__PURE__ */ new Map();
323
+ var streamSymbols = /* @__PURE__ */ new Map();
324
+ var allMarkPricesRefCount = 0;
325
+ var allMarkPricesUnsubscribe = null;
326
+ var STABLE_QUOTES2 = ["USDT0", "USDT", "USDC", "USDE", "USDH", "USD"];
327
+ function normalizeBinanceSymbol(symbol) {
328
+ const normalized = symbol.toUpperCase().trim();
329
+ for (const quote of STABLE_QUOTES2) {
330
+ if (normalized.endsWith(quote) && normalized.length > quote.length) {
331
+ return normalized.slice(0, -quote.length);
332
+ }
333
+ }
334
+ return normalized;
335
+ }
336
+ function getNextRefCount(binanceSymbol) {
337
+ return (refCounts.get(binanceSymbol) ?? 0) + 1;
338
+ }
339
+ function getPrevRefCount(binanceSymbol) {
340
+ return Math.max(0, (refCounts.get(binanceSymbol) ?? 0) - 1);
341
+ }
342
+ var useBinanceMarkPriceStore = create((set) => ({
343
+ markPrices: {},
344
+ subscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
345
+ const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
346
+ const nextRefCount = getNextRefCount(binanceSymbol);
347
+ refCounts.set(binanceSymbol, nextRefCount);
348
+ const symbols = streamSymbols.get(binanceSymbol) ?? /* @__PURE__ */ new Set();
349
+ symbols.add(symmSymbol);
350
+ streamSymbols.set(binanceSymbol, symbols);
351
+ if (allMarkPricesRefCount === 0) {
352
+ const wsManager = getBinanceWsManager();
353
+ allMarkPricesUnsubscribe = wsManager.subscribeAllMarkPrices((entries) => {
354
+ set((state) => {
355
+ let nextMarkPrices = null;
356
+ entries.forEach((entry) => {
357
+ const canonicalSymbol = normalizeBinanceSymbol(entry.symbol);
358
+ const mappedSymbols = streamSymbols.get(canonicalSymbol);
359
+ if (!mappedSymbols || mappedSymbols.size === 0) return;
360
+ nextMarkPrices ??= { ...state.markPrices };
361
+ mappedSymbols.forEach((mappedSymbol) => {
362
+ nextMarkPrices[mappedSymbol] = entry.markPrice;
363
+ });
364
+ });
365
+ return nextMarkPrices ? { markPrices: nextMarkPrices } : state;
366
+ });
367
+ });
368
+ }
369
+ allMarkPricesRefCount += 1;
370
+ },
371
+ unsubscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
372
+ const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
373
+ const symbols = streamSymbols.get(binanceSymbol);
374
+ if (symbols) {
375
+ symbols.delete(symmSymbol);
376
+ if (symbols.size === 0) {
377
+ streamSymbols.delete(binanceSymbol);
378
+ } else {
379
+ streamSymbols.set(binanceSymbol, symbols);
380
+ }
381
+ }
382
+ const nextRefCount = getPrevRefCount(binanceSymbol);
383
+ if (nextRefCount === 0) {
384
+ refCounts.delete(binanceSymbol);
385
+ } else {
386
+ refCounts.set(binanceSymbol, nextRefCount);
387
+ }
388
+ allMarkPricesRefCount = Math.max(0, allMarkPricesRefCount - 1);
389
+ if (allMarkPricesRefCount === 0) {
390
+ allMarkPricesUnsubscribe?.();
391
+ allMarkPricesUnsubscribe = null;
392
+ }
393
+ set((state) => {
394
+ if (state.markPrices[symmSymbol] == null) return state;
395
+ const nextMarkPrices = { ...state.markPrices };
396
+ delete nextMarkPrices[symmSymbol];
397
+ return { markPrices: nextMarkPrices };
398
+ });
399
+ }
400
+ }));
401
+
402
+ // src/react/hooks/use-binance-ws.ts
403
+ function useBinanceWs(params) {
404
+ const { symmCoreClient, chainId } = params;
405
+ const subscribeSymbol = useBinanceMarkPriceStore((state) => state.subscribeSymbol);
406
+ const unsubscribeSymbol = useBinanceMarkPriceStore((state) => state.unsubscribeSymbol);
407
+ useEffect(() => {
408
+ if (!symmCoreClient) {
409
+ return;
410
+ }
411
+ let cancelled = false;
412
+ let subscribedPairs = [];
413
+ const run = async () => {
414
+ try {
415
+ const result = await symmCoreClient.markets.listSymmHedger({ chainId });
416
+ if (cancelled) {
417
+ return;
418
+ }
419
+ const uniqueSymbols = Array.from(
420
+ new Set(
421
+ result.markets.map((market) => market.symbol).filter((symbol) => !!symbol)
422
+ )
423
+ );
424
+ subscribedPairs = uniqueSymbols.map((symbol) => {
425
+ const binanceSymbol = resolveBinanceSymbol(symbol).binanceSymbol;
426
+ if (!binanceSymbol) return null;
427
+ return [symbol, binanceSymbol];
428
+ }).filter((entry) => !!entry);
429
+ subscribedPairs.forEach(([symbol, binanceSymbol]) => {
430
+ subscribeSymbol(symbol, binanceSymbol);
431
+ });
432
+ } catch {
433
+ }
434
+ };
435
+ void run();
436
+ return () => {
437
+ cancelled = true;
438
+ subscribedPairs.forEach(([symbol, binanceSymbol]) => {
439
+ unsubscribeSymbol(symbol, binanceSymbol);
440
+ });
441
+ };
442
+ }, [symmCoreClient, chainId, subscribeSymbol, unsubscribeSymbol]);
443
+ }
444
+ function SymmProvider({
445
+ chainId = 42161,
446
+ address,
447
+ symmCoreConfig = {
448
+ apiUrl: "https://nginx-server-staging.up.railway.app",
449
+ wsUrl: "wss://nginx-server-staging.up.railway.app"
450
+ },
451
+ symmioConfig,
452
+ children
453
+ }) {
454
+ const symmCoreClient = useMemo(() => {
455
+ return createSymmSDK({
456
+ apiUrl: symmCoreConfig.apiUrl,
457
+ wsUrl: symmCoreConfig.wsUrl,
458
+ defaultChainId: chainId
459
+ });
460
+ }, [chainId, symmCoreConfig.apiUrl, symmCoreConfig.wsUrl]);
461
+ const value = useMemo(
462
+ () => ({
463
+ symmCoreClient,
464
+ chainId,
465
+ address,
466
+ symmioConfig
467
+ }),
468
+ [symmCoreClient, chainId, address, symmioConfig]
469
+ );
470
+ useBinanceWs({
471
+ symmCoreClient,
472
+ chainId
473
+ });
474
+ return /* @__PURE__ */ jsx(SymmContext.Provider, { value, children });
475
+ }
476
+
477
+ // src/react/hooks/use-symm-core-client.ts
478
+ function useSymmCoreClient() {
479
+ return useSymmContext().symmCoreClient;
480
+ }
481
+
482
+ // src/constants/defaults.ts
483
+ var GAS_MARGIN_PERCENTAGE = 20n;
484
+ var MUON_BASE_URLS = [
485
+ "https://muon-oracle1.rasa.capital/v1/",
486
+ "https://muon-oracle2.rasa.capital/v1/",
487
+ "https://muon-oracle3.rasa.capital/v1/",
488
+ "https://muon-oracle4.rasa.capital/v1/"
489
+ ];
490
+ var MUON_APP_NAME = "symmio";
491
+ var MUON_REQUEST_TIMEOUT = 15e3;
492
+ var HEDGER_BASE_URLS = {
493
+ [42161 /* ARBITRUM */]: "https://www.perps-streaming.com/v1/42161a/0x6273242a7E88b3De90822b31648C212215caaFE4",
494
+ [8453 /* BASE */]: "https://www.perps-streaming.com/v1/8453a/0x6273242a7E88b3De90822b31648C212215caaFE4"
495
+ };
496
+
497
+ // src/actions/instant.ts
498
+ function getHedgerBaseUrl(chainId) {
499
+ const baseUrl = HEDGER_BASE_URLS[chainId];
500
+ if (!baseUrl) {
501
+ throw new Error(`No hedger base URL configured for chain ${chainId}.`);
502
+ }
503
+ return baseUrl;
504
+ }
505
+ function createSiweMessage(params) {
506
+ const version = params.version ?? "1";
507
+ const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
508
+ const expirationTime = new Date(
509
+ Date.now() + 30 * 24 * 60 * 60 * 1e3
510
+ ).toISOString();
511
+ const message = [
512
+ `${params.domain} wants you to sign in with your Ethereum account:`,
513
+ params.address,
514
+ "",
515
+ params.statement,
516
+ "",
517
+ `URI: ${params.uri}`,
518
+ `Version: ${version}`,
519
+ `Chain ID: ${params.chainId}`,
520
+ `Nonce: ${params.nonce}`,
521
+ `Issued At: ${issuedAt}`,
522
+ `Expiration Time: ${expirationTime}`
523
+ ].join("\n");
524
+ return { message, issuedAt, expirationTime };
525
+ }
526
+ async function getNonce(chainId, subAccount) {
527
+ const hedgerBaseUrl = getHedgerBaseUrl(chainId);
528
+ const url = new URL(`nonce/${subAccount}`, hedgerBaseUrl).href;
529
+ const response = await fetch(url);
530
+ if (!response.ok) {
531
+ throw new Error(`Failed to fetch nonce: ${response.statusText}`);
532
+ }
533
+ const data = await response.json();
534
+ return data.nonce;
535
+ }
536
+ async function login(chainId, params) {
537
+ const hedgerBaseUrl = getHedgerBaseUrl(chainId);
538
+ const url = new URL("login", hedgerBaseUrl).href;
539
+ const body = {
540
+ account_address: params.accountAddress,
541
+ expiration_time: params.expirationTime,
542
+ issued_at: params.issuedAt,
543
+ signature: params.signature,
544
+ nonce: params.nonce
545
+ };
546
+ const response = await fetch(url, {
547
+ method: "POST",
548
+ headers: { "Content-Type": "application/json" },
549
+ body: JSON.stringify(body)
550
+ });
551
+ if (!response.ok) {
552
+ const errorData = await response.json().catch(() => null);
553
+ throw new Error(
554
+ errorData?.error_message ?? `Login failed: ${response.statusText}`
555
+ );
556
+ }
557
+ const data = await response.json();
558
+ if (!data.access_token) {
559
+ throw new Error("No access token received");
560
+ }
561
+ return {
562
+ accessToken: data.access_token,
563
+ expirationTime: params.expirationTime,
564
+ issuedAt: params.issuedAt
565
+ };
566
+ }
567
+
568
+ // src/react/auth.ts
569
+ var TOKEN_STORAGE_PREFIX = "symm_access_token";
570
+ var TOKEN_STORAGE_VERSION = 1;
571
+ var tokenCache = /* @__PURE__ */ new Map();
572
+ function cacheKey(address, chainId) {
573
+ return `${address}:${chainId}`;
574
+ }
575
+ function storageKey(address, chainId) {
576
+ return `${TOKEN_STORAGE_PREFIX}:${TOKEN_STORAGE_VERSION}:${cacheKey(address, chainId)}`;
577
+ }
578
+ function readStoredToken(address, chainId) {
579
+ if (typeof window === "undefined") return null;
580
+ try {
581
+ const raw = window.localStorage.getItem(storageKey(address, chainId));
582
+ if (!raw) return null;
583
+ const parsed = JSON.parse(raw);
584
+ if (!parsed || typeof parsed.token !== "string" || typeof parsed.expiresAt !== "number") {
585
+ window.localStorage.removeItem(storageKey(address, chainId));
586
+ return null;
587
+ }
588
+ if (Date.now() >= parsed.expiresAt) {
589
+ window.localStorage.removeItem(storageKey(address, chainId));
590
+ return null;
591
+ }
592
+ return parsed;
593
+ } catch {
594
+ window.localStorage.removeItem(storageKey(address, chainId));
595
+ return null;
596
+ }
597
+ }
598
+ function writeStoredToken(address, chainId, cached) {
599
+ if (typeof window === "undefined") return;
600
+ try {
601
+ window.localStorage.setItem(
602
+ storageKey(address, chainId),
603
+ JSON.stringify(cached)
604
+ );
605
+ } catch {
606
+ }
607
+ }
608
+ function getCachedToken(address, chainId) {
609
+ const cached = tokenCache.get(cacheKey(address, chainId));
610
+ if (cached && Date.now() < cached.expiresAt) {
611
+ return cached.token;
612
+ }
613
+ if (cached) {
614
+ tokenCache.delete(cacheKey(address, chainId));
615
+ }
616
+ const stored = readStoredToken(address, chainId);
617
+ if (!stored) return null;
618
+ tokenCache.set(cacheKey(address, chainId), stored);
619
+ return stored.token;
620
+ }
621
+ function clearCachedToken(address, chainId) {
622
+ tokenCache.delete(cacheKey(address, chainId));
623
+ if (typeof window !== "undefined") {
624
+ window.localStorage.removeItem(storageKey(address, chainId));
625
+ }
626
+ }
627
+ async function fetchAccessToken(walletClient, signerAddress, accountAddress, chainId, domain) {
628
+ const resolvedDomain = domain ?? (typeof window !== "undefined" ? window.location.host : "localhost");
629
+ const uri = typeof window !== "undefined" ? window.location.origin : `https://${resolvedDomain}`;
630
+ const nonce = await getNonce(chainId, accountAddress);
631
+ const { message, issuedAt, expirationTime } = createSiweMessage({
632
+ address: signerAddress,
633
+ statement: "Sign in to Symm Protocol",
634
+ chainId,
635
+ nonce,
636
+ domain: resolvedDomain,
637
+ uri
638
+ });
639
+ const signature = await walletClient.signMessage({
640
+ account: signerAddress,
641
+ message
642
+ });
643
+ const { accessToken } = await login(chainId, {
644
+ accountAddress,
645
+ signature,
646
+ expirationTime,
647
+ issuedAt,
648
+ nonce
649
+ });
650
+ const expiresAt = new Date(expirationTime).getTime();
651
+ const cachedToken = {
652
+ token: accessToken,
653
+ expiresAt: expiresAt - 6e4
654
+ };
655
+ tokenCache.set(cacheKey(accountAddress, chainId), cachedToken);
656
+ writeStoredToken(accountAddress, chainId, cachedToken);
657
+ return accessToken;
658
+ }
659
+ function symmAuthTokenKey(address, chainId) {
660
+ return `${address.toLowerCase()}:${chainId}`;
661
+ }
662
+ var useSymmAuthStore = create((set, get) => ({
663
+ tokensByKey: {},
664
+ setToken: (address, chainId, token) => {
665
+ const key = symmAuthTokenKey(address, chainId);
666
+ set((state) => ({
667
+ tokensByKey: {
668
+ ...state.tokensByKey,
669
+ [key]: token
670
+ }
671
+ }));
672
+ },
673
+ getToken: (address, chainId) => {
674
+ const key = symmAuthTokenKey(address, chainId);
675
+ return get().tokensByKey[key] ?? null;
676
+ },
677
+ clearToken: (address, chainId) => {
678
+ const key = symmAuthTokenKey(address, chainId);
679
+ set((state) => {
680
+ if (!(key in state.tokensByKey)) return state;
681
+ const next = { ...state.tokensByKey };
682
+ delete next[key];
683
+ return { tokensByKey: next };
684
+ });
685
+ },
686
+ clearAll: () => {
687
+ set({ tokensByKey: {} });
688
+ }
689
+ }));
690
+
691
+ // src/react/hooks/use-symm-auth.ts
692
+ function useSymmAuth(params) {
693
+ const ctx = useSymmContext();
694
+ const address = params?.address ?? ctx.address;
695
+ const chainId = params?.chainId ?? ctx.chainId ?? 42161;
696
+ const walletClient = params?.walletClient ?? ctx.walletClient;
697
+ const siweDomain = params?.siweDomain;
698
+ const accessToken = useSymmAuthStore((state) => {
699
+ if (address) {
700
+ return state.tokensByKey[symmAuthTokenKey(address, chainId)] ?? null;
701
+ }
702
+ return ctx.accessToken ?? ctx.authToken ?? null;
703
+ });
704
+ const setToken = useSymmAuthStore((state) => state.setToken);
705
+ const getToken = useSymmAuthStore((state) => state.getToken);
706
+ const clearToken = useSymmAuthStore((state) => state.clearToken);
707
+ const previousAddressRef = useRef(address);
708
+ const previousChainIdRef = useRef(chainId);
709
+ const refreshAuth = useCallback(
710
+ async (accountAddress) => {
711
+ if (!walletClient || !address) return null;
712
+ const resolvedAccountAddress = accountAddress ?? address;
713
+ const inMemory = getToken(resolvedAccountAddress, chainId);
714
+ if (inMemory) {
715
+ return inMemory;
716
+ }
717
+ const cached = getCachedToken(resolvedAccountAddress, chainId);
718
+ if (cached) {
719
+ setToken(resolvedAccountAddress, chainId, cached);
720
+ return cached;
721
+ }
722
+ try {
723
+ const token = await fetchAccessToken(
724
+ walletClient,
725
+ address,
726
+ resolvedAccountAddress,
727
+ chainId,
728
+ siweDomain
729
+ );
730
+ setToken(resolvedAccountAddress, chainId, token);
731
+ return token;
732
+ } catch {
733
+ clearToken(resolvedAccountAddress, chainId);
734
+ return null;
735
+ }
736
+ },
737
+ [walletClient, address, chainId, siweDomain, getToken, setToken, clearToken]
738
+ );
739
+ const clearAuth = useCallback(() => {
740
+ if (address) {
741
+ clearCachedToken(address, chainId);
742
+ clearToken(address, chainId);
743
+ }
744
+ }, [address, chainId, clearToken]);
745
+ useEffect(() => {
746
+ const previousAddress = previousAddressRef.current;
747
+ const previousChainId = previousChainIdRef.current;
748
+ const addressChanged = previousAddress !== address;
749
+ const chainChanged = previousChainId !== chainId;
750
+ previousAddressRef.current = address;
751
+ previousChainIdRef.current = chainId;
752
+ if (!address || !walletClient) {
753
+ return;
754
+ }
755
+ if (previousAddress && (addressChanged || chainChanged)) {
756
+ clearCachedToken(previousAddress, previousChainId);
757
+ clearToken(previousAddress, previousChainId);
758
+ }
759
+ const cached = getCachedToken(address, chainId);
760
+ if (cached) {
761
+ setToken(address, chainId, cached);
762
+ } else {
763
+ clearToken(address, chainId);
764
+ }
765
+ }, [address, walletClient, chainId, setToken, clearToken]);
766
+ return {
767
+ accessToken,
768
+ authToken: accessToken,
769
+ isAuthenticated: !!accessToken,
770
+ refresh: refreshAuth,
771
+ refreshAuth,
772
+ clear: clearAuth
773
+ };
774
+ }
775
+
776
+ // src/constants/selectors.ts
777
+ var SEND_QUOTE_WITH_AFFILIATE_SELECTOR = "0x40f1310c";
778
+ var CLOSE_QUOTE_SELECTOR = "0x501e891f";
779
+ var ALL_TRADING_SELECTORS = [
780
+ SEND_QUOTE_WITH_AFFILIATE_SELECTOR,
781
+ CLOSE_QUOTE_SELECTOR
782
+ ];
783
+
353
784
  // src/constants/addresses.ts
354
785
  var MULTI_ACCOUNT_ADDRESS = {
355
786
  [42161 /* ARBITRUM */]: "0x6273242a7E88b3De90822b31648C212215caaFE4",
@@ -24533,846 +24964,527 @@ function useSymmAccountData(params) {
24533
24964
  return useQuery({
24534
24965
  ...params.query,
24535
24966
  queryKey: symmKeys.accountData(address, chainId, upnl),
24536
- queryFn: () => symmCoreClient.accounts.getData({
24537
- address,
24538
- chainId,
24539
- upnl
24540
- }),
24541
- enabled: internalEnabled && (params.query?.enabled ?? true)
24542
- });
24543
- }
24544
- function useSymmBalances(params) {
24545
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24546
- const { userAddress, multiAccountAddress } = params;
24547
- const chainId = params.chainId ?? ctxChainId;
24548
- const internalEnabled = !!symmCoreClient && !!userAddress;
24549
- return useQuery({
24550
- ...params.query,
24551
- queryKey: symmKeys.balances(userAddress, chainId),
24552
- queryFn: () => symmCoreClient.accounts.getBalanceInfo({
24553
- userAddress,
24554
- chainId,
24555
- multiAccountAddress
24556
- }),
24557
- enabled: internalEnabled && (params.query?.enabled ?? true)
24558
- });
24559
- }
24560
- function useResolveTradeAuthToken() {
24561
- const context = useSymmContext();
24562
- const { address, chainId } = context;
24563
- const { refreshAuth } = useSymmAuth({ address, chainId });
24564
- const refreshAuthFromContext = context.refreshAuth;
24565
- return useCallback(
24566
- async (providedAuthToken, accountAddress) => {
24567
- if (providedAuthToken) {
24568
- return providedAuthToken;
24569
- }
24570
- const resolvedAccountAddress = accountAddress ?? address;
24571
- if (!resolvedAccountAddress) {
24572
- return null;
24573
- }
24574
- const inMemoryToken = useSymmAuthStore.getState().getToken(resolvedAccountAddress, chainId);
24575
- if (inMemoryToken) {
24576
- return inMemoryToken;
24577
- }
24578
- if (refreshAuthFromContext) {
24579
- return refreshAuthFromContext(resolvedAccountAddress);
24580
- }
24581
- return refreshAuth(resolvedAccountAddress);
24582
- },
24583
- [address, chainId, refreshAuth, refreshAuthFromContext]
24584
- );
24585
- }
24586
- function useSymmOpenBasketMutation(options) {
24587
- const { symmCoreClient } = useSymmContext();
24588
- const queryClient = useQueryClient();
24589
- return useMutation({
24590
- ...withSymmMutationConfig(options?.mutation, {
24591
- onSuccess: () => {
24592
- invalidatePositions(queryClient);
24593
- }
24594
- }),
24595
- mutationFn: async (request) => {
24596
- if (!symmCoreClient) throw new Error("symm-core client not available");
24597
- return symmCoreClient.positions.openBasket(request);
24598
- }
24599
- });
24600
- }
24601
- function useSymmClosePositionMutation(options) {
24602
- const { symmCoreClient } = useSymmContext();
24603
- const queryClient = useQueryClient();
24604
- const resolveAuthToken = useResolveTradeAuthToken();
24605
- return useMutation({
24606
- ...withSymmMutationConfig(options?.mutation, {
24607
- onSuccess: () => {
24608
- invalidatePositions(queryClient);
24609
- }
24610
- }),
24611
- mutationFn: async (request) => {
24612
- if (!symmCoreClient) throw new Error("symm-core client not available");
24613
- const typedRequest = request;
24614
- const authToken = await resolveAuthToken(
24615
- typedRequest.authToken,
24616
- typedRequest.accountAddress
24617
- );
24618
- if (!authToken) {
24619
- throw new Error("auth token is required to close a position");
24620
- }
24621
- return symmCoreClient.positions.close({
24622
- ...request,
24623
- authToken
24624
- });
24625
- }
24626
- });
24627
- }
24628
- function useSymmCloseAllPositionsMutation(options) {
24629
- const { symmCoreClient } = useSymmContext();
24630
- const queryClient = useQueryClient();
24631
- return useMutation({
24632
- ...withSymmMutationConfig(options?.mutation, {
24633
- onSuccess: () => {
24634
- invalidatePositions(queryClient);
24635
- }
24636
- }),
24637
- mutationFn: async (request) => {
24638
- if (!symmCoreClient) throw new Error("symm-core client not available");
24639
- return symmCoreClient.positions.closeAll(request);
24640
- }
24641
- });
24642
- }
24643
- function useSymmCancelOpenMutation(options) {
24644
- const { symmCoreClient } = useSymmContext();
24645
- const queryClient = useQueryClient();
24646
- return useMutation({
24647
- ...withSymmMutationConfig(options?.mutation, {
24648
- onSuccess: () => {
24649
- invalidatePositions(queryClient);
24650
- }
24651
- }),
24652
- mutationFn: async (request) => {
24653
- if (!symmCoreClient) throw new Error("symm-core client not available");
24654
- return symmCoreClient.positions.cancelOpen(request);
24655
- }
24656
- });
24657
- }
24658
- function useSymmUpdatePositionMutation(options) {
24659
- const { symmCoreClient } = useSymmContext();
24660
- const queryClient = useQueryClient();
24661
- const resolveAuthToken = useResolveTradeAuthToken();
24662
- return useMutation({
24663
- ...withSymmMutationConfig(options?.mutation, {
24664
- onSuccess: () => {
24665
- invalidatePositions(queryClient);
24666
- }
24667
- }),
24668
- mutationFn: async ({
24669
- positionId,
24670
- request
24671
- }) => {
24672
- if (!symmCoreClient) throw new Error("symm-core client not available");
24673
- const typedRequest = request;
24674
- const authToken = await resolveAuthToken(
24675
- typedRequest.authToken,
24676
- typedRequest.accountAddress
24677
- );
24678
- if (!authToken) {
24679
- throw new Error("auth token is required to update a position");
24680
- }
24681
- return symmCoreClient.positions.update(positionId, {
24682
- ...request,
24683
- authToken
24684
- });
24685
- }
24686
- });
24687
- }
24688
- function useSymmPositions(params) {
24689
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24690
- const { accountAddress, address } = params;
24691
- const resolvedAddress = accountAddress ? void 0 : address;
24692
- const chainId = params.chainId ?? ctxChainId;
24693
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24694
- const enabled = internalEnabled && (params.query?.enabled ?? true);
24695
- return useQuery({
24696
- ...params.query,
24697
- queryKey: symmKeys.positions({
24698
- accountAddress,
24699
- address: resolvedAddress,
24700
- chainId
24701
- }),
24702
- queryFn: () => symmCoreClient.positions.getOpen({
24703
- accountAddress,
24704
- address: resolvedAddress,
24705
- chainId
24706
- }),
24707
- enabled
24708
- });
24709
- }
24710
- function useSymmOpenOrders(params) {
24711
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24712
- const { accountAddress, address } = params;
24713
- const resolvedAddress = accountAddress ? void 0 : address;
24714
- const chainId = params.chainId ?? ctxChainId;
24715
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24716
- return useQuery({
24717
- ...params.query,
24718
- queryKey: symmKeys.openOrders({
24719
- accountAddress,
24720
- address: resolvedAddress,
24721
- chainId
24722
- }),
24723
- queryFn: () => symmCoreClient.orders.list({
24724
- address: accountAddress ?? resolvedAddress,
24725
- chainId
24967
+ queryFn: () => symmCoreClient.accounts.getData({
24968
+ address,
24969
+ chainId,
24970
+ upnl
24726
24971
  }),
24727
24972
  enabled: internalEnabled && (params.query?.enabled ?? true)
24728
24973
  });
24729
24974
  }
24730
- function useSymmTradeHistory(params) {
24975
+ function useSymmBalances(params) {
24731
24976
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24732
- const { accountAddress, address } = params;
24733
- const resolvedAddress = accountAddress ? void 0 : address;
24977
+ const { userAddress, multiAccountAddress } = params;
24734
24978
  const chainId = params.chainId ?? ctxChainId;
24735
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24979
+ const internalEnabled = !!symmCoreClient && !!userAddress;
24736
24980
  return useQuery({
24737
24981
  ...params.query,
24738
- queryKey: symmKeys.tradeHistory({
24739
- accountAddress,
24740
- address: resolvedAddress,
24741
- chainId
24982
+ queryKey: symmKeys.balances(userAddress, chainId),
24983
+ queryFn: () => symmCoreClient.accounts.getBalanceInfo({
24984
+ userAddress,
24985
+ chainId,
24986
+ multiAccountAddress
24742
24987
  }),
24743
- queryFn: () => {
24744
- const request = {
24745
- accountAddress,
24746
- address: resolvedAddress,
24747
- chainId
24748
- };
24749
- return symmCoreClient.positions.getTradeHistory(request);
24750
- },
24751
24988
  enabled: internalEnabled && (params.query?.enabled ?? true)
24752
24989
  });
24753
24990
  }
24754
- function useSymmSetTpslMutation(options) {
24991
+ function useResolveTradeAuthToken() {
24992
+ const context = useSymmContext();
24993
+ const { address, chainId } = context;
24994
+ const { refreshAuth } = useSymmAuth({ address, chainId });
24995
+ const refreshAuthFromContext = context.refreshAuth;
24996
+ return useCallback(
24997
+ async (providedAuthToken, accountAddress) => {
24998
+ if (providedAuthToken) {
24999
+ return providedAuthToken;
25000
+ }
25001
+ const resolvedAccountAddress = accountAddress ?? address;
25002
+ if (!resolvedAccountAddress) {
25003
+ return null;
25004
+ }
25005
+ const inMemoryToken = useSymmAuthStore.getState().getToken(resolvedAccountAddress, chainId);
25006
+ if (inMemoryToken) {
25007
+ return inMemoryToken;
25008
+ }
25009
+ if (refreshAuthFromContext) {
25010
+ return refreshAuthFromContext(resolvedAccountAddress);
25011
+ }
25012
+ return refreshAuth(resolvedAccountAddress);
25013
+ },
25014
+ [address, chainId, refreshAuth, refreshAuthFromContext]
25015
+ );
25016
+ }
25017
+ function useSymmOpenBasketMutation(options) {
24755
25018
  const { symmCoreClient } = useSymmContext();
24756
25019
  const queryClient = useQueryClient();
24757
25020
  return useMutation({
24758
25021
  ...withSymmMutationConfig(options?.mutation, {
24759
25022
  onSuccess: () => {
24760
- invalidateOrders(queryClient);
24761
25023
  invalidatePositions(queryClient);
24762
25024
  }
24763
25025
  }),
24764
25026
  mutationFn: async (request) => {
24765
25027
  if (!symmCoreClient) throw new Error("symm-core client not available");
24766
- return symmCoreClient.orders.setTpsl(request);
25028
+ return symmCoreClient.positions.openBasket(request);
24767
25029
  }
24768
25030
  });
24769
25031
  }
24770
- function useSymmCancelTpslMutation(options) {
25032
+ function useSymmClosePositionMutation(options) {
24771
25033
  const { symmCoreClient } = useSymmContext();
24772
25034
  const queryClient = useQueryClient();
25035
+ const resolveAuthToken = useResolveTradeAuthToken();
24773
25036
  return useMutation({
24774
25037
  ...withSymmMutationConfig(options?.mutation, {
24775
25038
  onSuccess: () => {
24776
- invalidateOrders(queryClient);
24777
25039
  invalidatePositions(queryClient);
24778
25040
  }
24779
25041
  }),
24780
25042
  mutationFn: async (request) => {
24781
25043
  if (!symmCoreClient) throw new Error("symm-core client not available");
24782
- return symmCoreClient.orders.cancelTpsl(request);
25044
+ const typedRequest = request;
25045
+ const authToken = await resolveAuthToken(
25046
+ typedRequest.authToken,
25047
+ typedRequest.accountAddress
25048
+ );
25049
+ if (!authToken) {
25050
+ throw new Error("auth token is required to close a position");
25051
+ }
25052
+ return symmCoreClient.positions.close({
25053
+ ...request,
25054
+ authToken
25055
+ });
24783
25056
  }
24784
25057
  });
24785
25058
  }
24786
- function useSymmTpslOrders(params) {
24787
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24788
- const { accountAddress, address, positionId, type, status } = params;
24789
- const resolvedAddress = accountAddress ? void 0 : address;
24790
- const chainId = params.chainId ?? ctxChainId;
24791
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress || positionId);
24792
- const request = {
24793
- address: accountAddress ?? resolvedAddress,
24794
- positionId,
24795
- type,
24796
- status,
24797
- chainId
24798
- };
24799
- return useQuery({
24800
- ...params.query,
24801
- queryKey: symmKeys.tpslOrdersList({
24802
- accountAddress,
24803
- address: resolvedAddress,
24804
- positionId,
24805
- type,
24806
- status,
24807
- chainId
24808
- }),
24809
- queryFn: () => symmCoreClient.orders.getTpslOrders(request),
24810
- enabled: internalEnabled && (params.query?.enabled ?? true)
24811
- });
24812
- }
24813
- function useResolvedTwapParams(params) {
24814
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24815
- const { accountAddress, address } = params;
24816
- const resolvedAddress = accountAddress ? void 0 : address;
24817
- const chainId = params.chainId ?? ctxChainId;
24818
- return { symmCoreClient, accountAddress, resolvedAddress, chainId, query: params.query };
24819
- }
24820
- function useSymmTwapOrdersQuery(params) {
24821
- const { symmCoreClient, accountAddress, resolvedAddress, chainId, query } = useResolvedTwapParams(params);
24822
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24823
- return useQuery({
24824
- ...query,
24825
- queryKey: symmKeys.twapOrders(accountAddress ?? resolvedAddress, chainId),
24826
- queryFn: () => symmCoreClient.orders.getTwapOrders({
24827
- address: accountAddress ?? resolvedAddress,
24828
- chainId
24829
- }),
24830
- enabled: internalEnabled && (query?.enabled ?? true)
24831
- });
24832
- }
24833
- function useSymmCancelTwapOrderMutation(options) {
25059
+ function useSymmCloseAllPositionsMutation(options) {
24834
25060
  const { symmCoreClient } = useSymmContext();
24835
25061
  const queryClient = useQueryClient();
24836
25062
  return useMutation({
24837
25063
  ...withSymmMutationConfig(options?.mutation, {
24838
25064
  onSuccess: () => {
24839
- invalidateOrders(queryClient);
25065
+ invalidatePositions(queryClient);
24840
25066
  }
24841
25067
  }),
24842
- mutationFn: async (orderId) => {
25068
+ mutationFn: async (request) => {
24843
25069
  if (!symmCoreClient) throw new Error("symm-core client not available");
24844
- return symmCoreClient.orders.cancelTwapOrder(orderId);
25070
+ return symmCoreClient.positions.closeAll(request);
24845
25071
  }
24846
25072
  });
24847
25073
  }
24848
- function useSymmTriggerConfigQuery(params) {
24849
- const { symmCoreClient } = useSymmContext();
24850
- const { orderId } = params;
24851
- const internalEnabled = !!symmCoreClient && !!orderId;
24852
- return useQuery({
24853
- ...params.query,
24854
- queryKey: symmKeys.triggerConfig(orderId),
24855
- queryFn: () => symmCoreClient.orders.getTriggerConfig(orderId),
24856
- enabled: internalEnabled && (params.query?.enabled ?? true)
24857
- });
24858
- }
24859
- function useSymmSetTriggerConfigMutation(params, options) {
25074
+ function useSymmCancelOpenMutation(options) {
24860
25075
  const { symmCoreClient } = useSymmContext();
24861
- const { orderId } = params;
24862
25076
  const queryClient = useQueryClient();
24863
25077
  return useMutation({
24864
25078
  ...withSymmMutationConfig(options?.mutation, {
24865
25079
  onSuccess: () => {
24866
- queryClient.invalidateQueries({
24867
- queryKey: symmKeys.triggerConfig(orderId)
24868
- });
24869
- invalidateOrders(queryClient);
25080
+ invalidatePositions(queryClient);
24870
25081
  }
24871
25082
  }),
24872
25083
  mutationFn: async (request) => {
24873
- if (!symmCoreClient || !orderId) {
24874
- throw new Error("symm-core client or orderId not available");
24875
- }
24876
- return symmCoreClient.orders.setTriggerConfig(orderId, request);
25084
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25085
+ return symmCoreClient.positions.cancelOpen(request);
24877
25086
  }
24878
25087
  });
24879
25088
  }
24880
- function useSymmClearTriggerConfigMutation(params, options) {
25089
+ function useSymmUpdatePositionMutation(options) {
24881
25090
  const { symmCoreClient } = useSymmContext();
24882
- const { orderId } = params;
24883
25091
  const queryClient = useQueryClient();
25092
+ const resolveAuthToken = useResolveTradeAuthToken();
24884
25093
  return useMutation({
24885
25094
  ...withSymmMutationConfig(options?.mutation, {
24886
25095
  onSuccess: () => {
24887
- queryClient.invalidateQueries({
24888
- queryKey: symmKeys.triggerConfig(orderId)
24889
- });
24890
- invalidateOrders(queryClient);
25096
+ invalidatePositions(queryClient);
24891
25097
  }
24892
25098
  }),
24893
- mutationFn: async () => {
24894
- if (!symmCoreClient || !orderId) {
24895
- throw new Error("symm-core client or orderId not available");
25099
+ mutationFn: async ({
25100
+ positionId,
25101
+ request
25102
+ }) => {
25103
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25104
+ const typedRequest = request;
25105
+ const authToken = await resolveAuthToken(
25106
+ typedRequest.authToken,
25107
+ typedRequest.accountAddress
25108
+ );
25109
+ if (!authToken) {
25110
+ throw new Error("auth token is required to update a position");
24896
25111
  }
24897
- return symmCoreClient.orders.clearTriggerConfig(orderId);
25112
+ return symmCoreClient.positions.update(positionId, {
25113
+ ...request,
25114
+ authToken
25115
+ });
24898
25116
  }
24899
25117
  });
24900
25118
  }
24901
- function useSymmTriggerOrders(params) {
24902
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24903
- const { accountAddress, address, status, limit, offset } = params;
24904
- const resolvedAddress = accountAddress ? void 0 : address;
24905
- const chainId = params.chainId ?? ctxChainId;
24906
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24907
- const request = {
24908
- address: accountAddress ?? resolvedAddress,
24909
- chainId,
24910
- status,
24911
- limit,
24912
- offset
24913
- };
24914
- return useQuery({
24915
- ...params.query,
24916
- queryKey: symmKeys.triggerOrders({
24917
- accountAddress,
24918
- address: resolvedAddress,
24919
- chainId,
24920
- status,
24921
- limit,
24922
- offset
24923
- }),
24924
- queryFn: () => symmCoreClient.orders.listTriggerOrders(request),
24925
- enabled: internalEnabled && (params.query?.enabled ?? true)
24926
- });
24927
- }
24928
- function useSymmMarkets(params) {
24929
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24930
- const chainId = params?.chainId ?? ctxChainId;
24931
- const searchText = params?.searchText;
24932
- const internalEnabled = !!symmCoreClient;
24933
- return useQuery({
24934
- ...params?.query,
24935
- queryKey: symmKeys.markets(chainId, searchText),
24936
- queryFn: () => {
24937
- if (searchText) {
24938
- return symmCoreClient.markets.search(searchText, { chainId });
24939
- }
24940
- return symmCoreClient.markets.list({ pageSize: "1000", chainId });
24941
- },
24942
- enabled: internalEnabled && (params?.query?.enabled ?? true)
24943
- });
24944
- }
24945
- function useSymmHedgerMarketById(params) {
24946
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24947
- const { id } = params;
24948
- const chainId = params.chainId ?? ctxChainId;
24949
- const internalEnabled = !!symmCoreClient && id != null;
24950
- return useQuery({
24951
- ...params.query,
24952
- queryKey: symmKeys.hedgerMarketById(id, chainId),
24953
- queryFn: () => symmCoreClient.markets.getById(id, chainId),
24954
- enabled: internalEnabled && (params.query?.enabled ?? true)
24955
- });
24956
- }
24957
- function useSymmHedgerMarketBySymbol(params) {
24958
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24959
- const { symbol } = params;
24960
- const chainId = params.chainId ?? ctxChainId;
24961
- const internalEnabled = !!symmCoreClient && !!symbol;
24962
- return useQuery({
24963
- ...params.query,
24964
- queryKey: symmKeys.hedgerMarketBySymbol(symbol, chainId),
24965
- queryFn: () => symmCoreClient.markets.getBySymbol(symbol, chainId),
24966
- enabled: internalEnabled && (params.query?.enabled ?? true)
24967
- });
24968
- }
24969
- function useSymmLockedParams(params) {
25119
+ function useSymmPositions(params) {
24970
25120
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24971
- const { marketName, leverage } = params;
25121
+ const { accountAddress, address } = params;
25122
+ const resolvedAddress = accountAddress ? void 0 : address;
24972
25123
  const chainId = params.chainId ?? ctxChainId;
24973
- const internalEnabled = !!symmCoreClient && !!marketName && leverage != null;
25124
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25125
+ const enabled = internalEnabled && (params.query?.enabled ?? true);
24974
25126
  return useQuery({
24975
25127
  ...params.query,
24976
- queryKey: symmKeys.lockedParams(marketName, leverage, chainId),
24977
- queryFn: () => symmCoreClient.markets.getLockedParams({
24978
- marketName,
24979
- leverage,
25128
+ queryKey: symmKeys.positions({
25129
+ accountAddress,
25130
+ address: resolvedAddress,
24980
25131
  chainId
24981
25132
  }),
24982
- enabled: internalEnabled && (params.query?.enabled ?? true)
25133
+ queryFn: () => symmCoreClient.positions.getOpen({
25134
+ accountAddress,
25135
+ address: resolvedAddress,
25136
+ chainId
25137
+ }),
25138
+ enabled
24983
25139
  });
24984
25140
  }
24985
- function useSymmHedgerMarkets(params) {
25141
+ function useSymmOpenOrders(params) {
24986
25142
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24987
- const chainId = params?.chainId ?? ctxChainId;
24988
- const searchText = params?.searchText?.trim();
24989
- const consumerEnabled = params?.query?.enabled ?? params?.enabled ?? true;
24990
- const { enabled: _, query: __, ...restParams } = params ?? {};
24991
- const request = {
24992
- ...restParams,
24993
- chainId,
24994
- searchText: searchText || void 0
24995
- };
24996
- const internalEnabled = !!symmCoreClient;
25143
+ const { accountAddress, address } = params;
25144
+ const resolvedAddress = accountAddress ? void 0 : address;
25145
+ const chainId = params.chainId ?? ctxChainId;
25146
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24997
25147
  return useQuery({
24998
- ...params?.query,
24999
- queryKey: symmKeys.hedgerMarkets(request),
25000
- queryFn: () => symmCoreClient.markets.listSymmHedger(request),
25001
- enabled: internalEnabled && consumerEnabled
25002
- });
25003
- }
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,
25010
- interval,
25011
- startTime: String(startTime),
25012
- endTime: String(endTime),
25013
- limit: String(Math.min(limit, 1500))
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
- }));
25029
- }
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)
25039
- };
25040
- }
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;
25056
- }
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`);
25096
- return {
25097
- symmSymbol,
25098
- normalizedSymbol: normalized,
25099
- binanceSymbol,
25100
- supported: true,
25101
- reason: null
25102
- };
25103
- }
25104
- function getUnsupportedBinanceSymbols(symbols) {
25105
- return symbols.filter((symbol) => !resolveBinanceSymbol(symbol).supported);
25106
- }
25107
- function toBinanceSymbol(symmSymbol) {
25108
- return resolveBinanceSymbol(symmSymbol).binanceSymbol;
25109
- }
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
25142
- });
25143
- };
25144
- this.addStreamCallback(streamName, id, wrappedCb);
25145
- return () => this.removeStreamCallback(streamName, id);
25146
- }
25147
- /**
25148
- * Subscribe to a mark price stream (1s updates). Returns an unsubscribe function.
25149
- */
25150
- subscribeMarkPrice(symbol, cb) {
25151
- const streamName = `${symbol.toLowerCase()}@markPrice@1s`;
25152
- const id = this.generateId();
25153
- const wrappedCb = (raw) => {
25154
- cb({
25155
- symbol: raw.s,
25156
- markPrice: parseFloat(raw.p),
25157
- indexPrice: parseFloat(raw.i),
25158
- time: raw.E
25159
- });
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;
25148
+ ...params.query,
25149
+ queryKey: symmKeys.openOrders({
25150
+ accountAddress,
25151
+ address: resolvedAddress,
25152
+ chainId
25153
+ }),
25154
+ queryFn: () => symmCoreClient.orders.list({
25155
+ address: accountAddress ?? resolvedAddress,
25156
+ chainId
25157
+ }),
25158
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25159
+ });
25160
+ }
25161
+ function useSymmTradeHistory(params) {
25162
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25163
+ const { accountAddress, address } = params;
25164
+ const resolvedAddress = accountAddress ? void 0 : address;
25165
+ const chainId = params.chainId ?? ctxChainId;
25166
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25167
+ return useQuery({
25168
+ ...params.query,
25169
+ queryKey: symmKeys.tradeHistory({
25170
+ accountAddress,
25171
+ address: resolvedAddress,
25172
+ chainId
25173
+ }),
25174
+ queryFn: () => {
25175
+ const request = {
25176
+ accountAddress,
25177
+ address: resolvedAddress,
25178
+ chainId
25179
+ };
25180
+ return symmCoreClient.positions.getTradeHistory(request);
25181
+ },
25182
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25183
+ });
25184
+ }
25185
+ function useSymmSetTpslMutation(options) {
25186
+ const { symmCoreClient } = useSymmContext();
25187
+ const queryClient = useQueryClient();
25188
+ return useMutation({
25189
+ ...withSymmMutationConfig(options?.mutation, {
25190
+ onSuccess: () => {
25191
+ invalidateOrders(queryClient);
25192
+ invalidatePositions(queryClient);
25208
25193
  }
25194
+ }),
25195
+ mutationFn: async (request) => {
25196
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25197
+ return symmCoreClient.orders.setTpsl(request);
25209
25198
  }
25210
- }
25211
- ensureConnected() {
25212
- if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
25213
- return;
25199
+ });
25200
+ }
25201
+ function useSymmCancelTpslMutation(options) {
25202
+ const { symmCoreClient } = useSymmContext();
25203
+ const queryClient = useQueryClient();
25204
+ return useMutation({
25205
+ ...withSymmMutationConfig(options?.mutation, {
25206
+ onSuccess: () => {
25207
+ invalidateOrders(queryClient);
25208
+ invalidatePositions(queryClient);
25209
+ }
25210
+ }),
25211
+ mutationFn: async (request) => {
25212
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25213
+ return symmCoreClient.orders.cancelTpsl(request);
25214
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);
25215
+ });
25216
+ }
25217
+ function useSymmTpslOrders(params) {
25218
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25219
+ const { accountAddress, address, positionId, type, status } = params;
25220
+ const resolvedAddress = accountAddress ? void 0 : address;
25221
+ const chainId = params.chainId ?? ctxChainId;
25222
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress || positionId);
25223
+ const request = {
25224
+ address: accountAddress ?? resolvedAddress,
25225
+ positionId,
25226
+ type,
25227
+ status,
25228
+ chainId
25229
+ };
25230
+ return useQuery({
25231
+ ...params.query,
25232
+ queryKey: symmKeys.tpslOrdersList({
25233
+ accountAddress,
25234
+ address: resolvedAddress,
25235
+ positionId,
25236
+ type,
25237
+ status,
25238
+ chainId
25239
+ }),
25240
+ queryFn: () => symmCoreClient.orders.getTpslOrders(request),
25241
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25242
+ });
25243
+ }
25244
+ function useResolvedTwapParams(params) {
25245
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25246
+ const { accountAddress, address } = params;
25247
+ const resolvedAddress = accountAddress ? void 0 : address;
25248
+ const chainId = params.chainId ?? ctxChainId;
25249
+ return { symmCoreClient, accountAddress, resolvedAddress, chainId, query: params.query };
25250
+ }
25251
+ function useSymmTwapOrdersQuery(params) {
25252
+ const { symmCoreClient, accountAddress, resolvedAddress, chainId, query } = useResolvedTwapParams(params);
25253
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25254
+ return useQuery({
25255
+ ...query,
25256
+ queryKey: symmKeys.twapOrders(accountAddress ?? resolvedAddress, chainId),
25257
+ queryFn: () => symmCoreClient.orders.getTwapOrders({
25258
+ address: accountAddress ?? resolvedAddress,
25259
+ chainId
25260
+ }),
25261
+ enabled: internalEnabled && (query?.enabled ?? true)
25262
+ });
25263
+ }
25264
+ function useSymmCancelTwapOrderMutation(options) {
25265
+ const { symmCoreClient } = useSymmContext();
25266
+ const queryClient = useQueryClient();
25267
+ return useMutation({
25268
+ ...withSymmMutationConfig(options?.mutation, {
25269
+ onSuccess: () => {
25270
+ invalidateOrders(queryClient);
25226
25271
  }
25227
- if (this.pendingSubscribes.length > 0) {
25228
- this.sendSubscribe(this.pendingSubscribes);
25229
- this.pendingSubscribes = [];
25272
+ }),
25273
+ mutationFn: async (orderId) => {
25274
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25275
+ return symmCoreClient.orders.cancelTwapOrder(orderId);
25276
+ }
25277
+ });
25278
+ }
25279
+ function useSymmTriggerConfigQuery(params) {
25280
+ const { symmCoreClient } = useSymmContext();
25281
+ const { orderId } = params;
25282
+ const internalEnabled = !!symmCoreClient && !!orderId;
25283
+ return useQuery({
25284
+ ...params.query,
25285
+ queryKey: symmKeys.triggerConfig(orderId),
25286
+ queryFn: () => symmCoreClient.orders.getTriggerConfig(orderId),
25287
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25288
+ });
25289
+ }
25290
+ function useSymmSetTriggerConfigMutation(params, options) {
25291
+ const { symmCoreClient } = useSymmContext();
25292
+ const { orderId } = params;
25293
+ const queryClient = useQueryClient();
25294
+ return useMutation({
25295
+ ...withSymmMutationConfig(options?.mutation, {
25296
+ onSuccess: () => {
25297
+ queryClient.invalidateQueries({
25298
+ queryKey: symmKeys.triggerConfig(orderId)
25299
+ });
25300
+ invalidateOrders(queryClient);
25230
25301
  }
25231
- };
25232
- this.ws.onmessage = (event) => {
25233
- try {
25234
- const data = JSON.parse(event.data);
25235
- this.handleMessage(data);
25236
- } catch {
25302
+ }),
25303
+ mutationFn: async (request) => {
25304
+ if (!symmCoreClient || !orderId) {
25305
+ throw new Error("symm-core client or orderId not available");
25237
25306
  }
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);
25307
+ return symmCoreClient.orders.setTriggerConfig(orderId, request);
25254
25308
  }
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 {
25309
+ });
25310
+ }
25311
+ function useSymmClearTriggerConfigMutation(params, options) {
25312
+ const { symmCoreClient } = useSymmContext();
25313
+ const { orderId } = params;
25314
+ const queryClient = useQueryClient();
25315
+ return useMutation({
25316
+ ...withSymmMutationConfig(options?.mutation, {
25317
+ onSuccess: () => {
25318
+ queryClient.invalidateQueries({
25319
+ queryKey: symmKeys.triggerConfig(orderId)
25320
+ });
25321
+ invalidateOrders(queryClient);
25263
25322
  }
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;
25323
+ }),
25324
+ mutationFn: async () => {
25325
+ if (!symmCoreClient || !orderId) {
25326
+ throw new Error("symm-core client or orderId not available");
25327
+ }
25328
+ return symmCoreClient.orders.clearTriggerConfig(orderId);
25283
25329
  }
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;
25330
+ });
25331
+ }
25332
+ function useSymmTriggerOrders(params) {
25333
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25334
+ const { accountAddress, address, status, limit, offset } = params;
25335
+ const resolvedAddress = accountAddress ? void 0 : address;
25336
+ const chainId = params.chainId ?? ctxChainId;
25337
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25338
+ const request = {
25339
+ address: accountAddress ?? resolvedAddress,
25340
+ chainId,
25341
+ status,
25342
+ limit,
25343
+ offset
25344
+ };
25345
+ return useQuery({
25346
+ ...params.query,
25347
+ queryKey: symmKeys.triggerOrders({
25348
+ accountAddress,
25349
+ address: resolvedAddress,
25350
+ chainId,
25351
+ status,
25352
+ limit,
25353
+ offset
25354
+ }),
25355
+ queryFn: () => symmCoreClient.orders.listTriggerOrders(request),
25356
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25357
+ });
25307
25358
  }
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();
25359
+ function useSymmMarkets(params) {
25360
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25361
+ const chainId = params?.chainId ?? ctxChainId;
25362
+ const searchText = params?.searchText;
25363
+ const internalEnabled = !!symmCoreClient;
25364
+ return useQuery({
25365
+ ...params?.query,
25366
+ queryKey: symmKeys.markets(chainId, searchText),
25367
+ queryFn: () => {
25368
+ if (searchText) {
25369
+ return symmCoreClient.markets.search(searchText, { chainId });
25370
+ }
25371
+ return symmCoreClient.markets.list({ pageSize: "1000", chainId });
25372
+ },
25373
+ enabled: internalEnabled && (params?.query?.enabled ?? true)
25374
+ });
25315
25375
  }
25316
- function getNextRefCount(binanceSymbol) {
25317
- return (refCounts.get(binanceSymbol) ?? 0) + 1;
25376
+ function useSymmHedgerMarketById(params) {
25377
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25378
+ const { id } = params;
25379
+ const chainId = params.chainId ?? ctxChainId;
25380
+ const internalEnabled = !!symmCoreClient && id != null;
25381
+ return useQuery({
25382
+ ...params.query,
25383
+ queryKey: symmKeys.hedgerMarketById(id, chainId),
25384
+ queryFn: () => symmCoreClient.markets.getById(id, chainId),
25385
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25386
+ });
25318
25387
  }
25319
- function getPrevRefCount(binanceSymbol) {
25320
- return Math.max(0, (refCounts.get(binanceSymbol) ?? 0) - 1);
25388
+ function useSymmHedgerMarketBySymbol(params) {
25389
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25390
+ const { symbol } = params;
25391
+ const chainId = params.chainId ?? ctxChainId;
25392
+ const internalEnabled = !!symmCoreClient && !!symbol;
25393
+ return useQuery({
25394
+ ...params.query,
25395
+ queryKey: symmKeys.hedgerMarketBySymbol(symbol, chainId),
25396
+ queryFn: () => symmCoreClient.markets.getBySymbol(symbol, chainId),
25397
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25398
+ });
25321
25399
  }
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);
25346
- }
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
- }
25358
- }
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
- });
25400
+ function useSymmLockedParams(params) {
25401
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25402
+ const { marketName, leverage } = params;
25403
+ const chainId = params.chainId ?? ctxChainId;
25404
+ const internalEnabled = !!symmCoreClient && !!marketName && leverage != null;
25405
+ return useQuery({
25406
+ ...params.query,
25407
+ queryKey: symmKeys.lockedParams(marketName, leverage, chainId),
25408
+ queryFn: () => symmCoreClient.markets.getLockedParams({
25409
+ marketName,
25410
+ leverage,
25411
+ chainId
25412
+ }),
25413
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25414
+ });
25415
+ }
25416
+ function useSymmHedgerMarkets(params) {
25417
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25418
+ const chainId = params?.chainId ?? ctxChainId;
25419
+ const searchText = params?.searchText?.trim();
25420
+ const consumerEnabled = params?.query?.enabled ?? params?.enabled ?? true;
25421
+ const { enabled: _, query: __, ...restParams } = params ?? {};
25422
+ const request = {
25423
+ ...restParams,
25424
+ chainId,
25425
+ searchText: searchText || void 0
25426
+ };
25427
+ const internalEnabled = !!symmCoreClient;
25428
+ return useQuery({
25429
+ ...params?.query,
25430
+ queryKey: symmKeys.hedgerMarkets(request),
25431
+ queryFn: () => symmCoreClient.markets.listSymmHedger(request),
25432
+ enabled: internalEnabled && consumerEnabled
25433
+ });
25434
+ }
25435
+
25436
+ // src/utils/binance-api.ts
25437
+ var BINANCE_FAPI_BASE = "https://fapi.binance.com";
25438
+ async function fetchMarkPriceKlines(symbol, interval, startTime, endTime, limit = 1500) {
25439
+ const params = new URLSearchParams({
25440
+ symbol,
25441
+ interval,
25442
+ startTime: String(startTime),
25443
+ endTime: String(endTime),
25444
+ limit: String(Math.min(limit, 1500))
25445
+ });
25446
+ const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/markPriceKlines?${params}`);
25447
+ if (!response.ok) {
25448
+ throw new Error(`Binance markPriceKlines failed: ${response.status}`);
25374
25449
  }
25375
- }));
25450
+ const data = await response.json();
25451
+ return data.map((k) => ({
25452
+ openTime: Number(k[0]),
25453
+ open: parseFloat(k[1]),
25454
+ high: parseFloat(k[2]),
25455
+ low: parseFloat(k[3]),
25456
+ close: parseFloat(k[4]),
25457
+ volume: parseFloat(k[5]),
25458
+ closeTime: Number(k[6])
25459
+ }));
25460
+ }
25461
+ async function fetch24hrTicker(symbol) {
25462
+ const params = new URLSearchParams({ symbol });
25463
+ const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/ticker/24hr?${params}`);
25464
+ if (!response.ok) return null;
25465
+ const data = await response.json();
25466
+ return {
25467
+ lastPrice: parseFloat(data.lastPrice),
25468
+ prevClosePrice: parseFloat(data.prevClosePrice),
25469
+ priceChangePercent: parseFloat(data.priceChangePercent)
25470
+ };
25471
+ }
25472
+ async function fetch24hrTickers() {
25473
+ const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/ticker/24hr`);
25474
+ if (!response.ok) {
25475
+ throw new Error(`Binance 24hr tickers failed: ${response.status}`);
25476
+ }
25477
+ const data = await response.json();
25478
+ const result = {};
25479
+ for (const item of data) {
25480
+ result[item.symbol] = {
25481
+ lastPrice: parseFloat(item.lastPrice),
25482
+ prevClosePrice: parseFloat(item.prevClosePrice),
25483
+ priceChangePercent: parseFloat(item.priceChangePercent)
25484
+ };
25485
+ }
25486
+ return result;
25487
+ }
25376
25488
 
25377
25489
  // src/react/hooks/use-symm-token-selection-markets.ts
25378
25490
  function useSymmTokenSelectionMarkets(params) {
@@ -25390,19 +25502,7 @@ function useSymmTokenSelectionMarkets(params) {
25390
25502
  () => [...marketSymbols].sort().join(","),
25391
25503
  [marketSymbols]
25392
25504
  );
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
25505
  const liveMarkPrices = useBinanceMarkPriceStore((state) => state.markPrices);
25404
- const subscribeSymbol = useBinanceMarkPriceStore((state) => state.subscribeSymbol);
25405
- const unsubscribeSymbol = useBinanceMarkPriceStore((state) => state.unsubscribeSymbol);
25406
25506
  const priceQuery = useQuery({
25407
25507
  queryKey: ["symm", "token-selection-markets-price", symbolsKey],
25408
25508
  queryFn: async () => {
@@ -25423,20 +25523,6 @@ function useSymmTokenSelectionMarkets(params) {
25423
25523
  staleTime: 6e4,
25424
25524
  gcTime: 5 * 6e4
25425
25525
  });
25426
- useEffect(() => {
25427
- if (!query.isSuccess || symbolToBinanceMap.size === 0) {
25428
- return;
25429
- }
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
25526
  const markets = useMemo(() => {
25441
25527
  const snapshots = priceQuery.data?.tickerSnapshots ?? {};
25442
25528
  return baseMarkets.map((market) => {
@@ -25465,6 +25551,61 @@ function useSymmTokenSelectionMarkets(params) {
25465
25551
  () => new Map(markets.map((market) => [market.id, market])),
25466
25552
  [markets]
25467
25553
  );
25554
+ useEffect(() => {
25555
+ console.debug("[useSymmTokenSelectionMarkets] data flow", {
25556
+ params,
25557
+ query: {
25558
+ status: query.status,
25559
+ fetchStatus: query.fetchStatus,
25560
+ isLoading: query.isLoading,
25561
+ isSuccess: query.isSuccess,
25562
+ isError: query.isError,
25563
+ error: query.error
25564
+ },
25565
+ priceQuery: {
25566
+ status: priceQuery.status,
25567
+ fetchStatus: priceQuery.fetchStatus,
25568
+ isLoading: priceQuery.isLoading,
25569
+ isSuccess: priceQuery.isSuccess,
25570
+ isError: priceQuery.isError,
25571
+ error: priceQuery.error
25572
+ },
25573
+ marketSymbols,
25574
+ symbolsKey,
25575
+ liveMarkPrices,
25576
+ tickerSnapshots: priceQuery.data?.tickerSnapshots ?? {},
25577
+ result: {
25578
+ marketCount: markets.length,
25579
+ markets: markets.map((market) => ({
25580
+ id: market.id,
25581
+ symbol: market.symbol,
25582
+ markPrice: market.markPrice,
25583
+ prevDayPrice: market.prevDayPrice,
25584
+ priceChange24h: market.priceChange24h,
25585
+ priceChange24hPercent: market.priceChange24hPercent
25586
+ }))
25587
+ }
25588
+ });
25589
+ }, [
25590
+ liveMarkPrices,
25591
+ marketSymbols,
25592
+ markets,
25593
+ params,
25594
+ priceQuery.data,
25595
+ priceQuery.error,
25596
+ priceQuery.fetchStatus,
25597
+ priceQuery.isError,
25598
+ priceQuery.isLoading,
25599
+ priceQuery.isSuccess,
25600
+ priceQuery.status,
25601
+ query.error,
25602
+ query.fetchStatus,
25603
+ query.isError,
25604
+ query.isLoading,
25605
+ query.isSuccess,
25606
+ query.status,
25607
+ symbolsKey
25608
+ ]);
25468
25609
  return {
25469
25610
  query,
25470
25611
  priceQuery,
@@ -25479,17 +25620,6 @@ function useSymmTokenMarkPrice(symbol) {
25479
25620
  const normalizedSymbol = symbol?.trim().toUpperCase() || null;
25480
25621
  const resolution = resolveBinanceSymbol(normalizedSymbol ?? "");
25481
25622
  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;
25487
- }
25488
- subscribeSymbol(normalizedSymbol, resolution.binanceSymbol);
25489
- return () => {
25490
- unsubscribeSymbol(normalizedSymbol, resolution.binanceSymbol);
25491
- };
25492
- }, [normalizedSymbol, resolution.binanceSymbol, resolution.supported, subscribeSymbol, unsubscribeSymbol]);
25493
25623
  const markPrice = useMemo(() => {
25494
25624
  if (!normalizedSymbol) return null;
25495
25625
  return liveMarkPrices[normalizedSymbol] ?? null;
@@ -26042,17 +26172,7 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26042
26172
  const isUnsupported = unsupportedSymbols.length > 0;
26043
26173
  const unavailableReason = isUnsupported ? `Binance market data is unavailable for ${unsupportedSymbols.join(", ")}.` : null;
26044
26174
  const symbolsKey = [...selectedSymbols].sort().join(",");
26045
- const symbolToBinanceMap = useMemo(
26046
- () => new Map(
26047
- selectedSymbols.map(
26048
- (symbol) => [symbol, resolveBinanceSymbol(symbol).binanceSymbol]
26049
- ).filter((entry) => !!entry[1])
26050
- ),
26051
- [selectedSymbols]
26052
- );
26053
26175
  const liveMarkPrices = useBinanceMarkPriceStore((state) => state.markPrices);
26054
- const subscribeSymbol = useBinanceMarkPriceStore((state) => state.subscribeSymbol);
26055
- const unsubscribeSymbol = useBinanceMarkPriceStore((state) => state.unsubscribeSymbol);
26056
26176
  const query = useQuery({
26057
26177
  queryKey: ["symm", "chart-metadata", symbolsKey],
26058
26178
  queryFn: async () => {
@@ -26078,28 +26198,7 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26078
26198
  enabled: enabled && selectedSymbols.length > 0 && !isUnsupported,
26079
26199
  gcTime: 5 * 6e4
26080
26200
  });
26081
- useEffect(() => {
26082
- if (!enabled || isUnsupported || selectedSymbols.length === 0) {
26083
- return;
26084
- }
26085
- const symbolEntries = Array.from(symbolToBinanceMap.entries());
26086
- symbolEntries.forEach(
26087
- ([symbol, binanceSymbol]) => subscribeSymbol(symbol, binanceSymbol)
26088
- );
26089
- return () => {
26090
- symbolEntries.forEach(
26091
- ([symbol, binanceSymbol]) => unsubscribeSymbol(symbol, binanceSymbol)
26092
- );
26093
- };
26094
- }, [
26095
- enabled,
26096
- isUnsupported,
26097
- selectedSymbols.length,
26098
- symbolToBinanceMap,
26099
- subscribeSymbol,
26100
- unsubscribeSymbol
26101
- ]);
26102
- return useMemo(() => {
26201
+ const result = useMemo(() => {
26103
26202
  const tickerSnapshots = query.data?.tickerSnapshots ?? {};
26104
26203
  const longMeta = {};
26105
26204
  const shortMeta = {};
@@ -26158,6 +26257,53 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26158
26257
  selectedSymbols.length,
26159
26258
  liveMarkPrices
26160
26259
  ]);
26260
+ useEffect(() => {
26261
+ console.debug("[useSymmTokenSelectionMetadata] data flow", {
26262
+ selection,
26263
+ options,
26264
+ query: {
26265
+ status: query.status,
26266
+ fetchStatus: query.fetchStatus,
26267
+ isLoading: query.isLoading,
26268
+ isSuccess: query.isSuccess,
26269
+ isError: query.isError,
26270
+ error: query.error
26271
+ },
26272
+ selectedSymbols,
26273
+ unsupportedSymbols,
26274
+ unavailableReason,
26275
+ liveMarkPrices,
26276
+ tickerSnapshots: query.data?.tickerSnapshots ?? {},
26277
+ result: {
26278
+ isLoading: result.isLoading,
26279
+ isPriceDataReady: result.isPriceDataReady,
26280
+ isUnsupported: result.isUnsupported,
26281
+ longTokensMetadata: result.longTokensMetadata,
26282
+ shortTokensMetadata: result.shortTokensMetadata,
26283
+ weightedRatio: result.weightedRatio,
26284
+ weightedRatio24h: result.weightedRatio24h,
26285
+ priceRatio: result.priceRatio,
26286
+ priceRatio24h: result.priceRatio24h,
26287
+ sumNetFunding: result.sumNetFunding
26288
+ }
26289
+ });
26290
+ }, [
26291
+ liveMarkPrices,
26292
+ options,
26293
+ query.data,
26294
+ query.error,
26295
+ query.fetchStatus,
26296
+ query.isError,
26297
+ query.isLoading,
26298
+ query.isSuccess,
26299
+ query.status,
26300
+ result,
26301
+ selectedSymbols,
26302
+ selection,
26303
+ unavailableReason,
26304
+ unsupportedSymbols
26305
+ ]);
26306
+ return result;
26161
26307
  }
26162
26308
 
26163
26309
  // src/utils/binance-intervals.ts