@pear-protocol/symmio-client 0.2.26 → 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",
@@ -24522,852 +24953,540 @@ function useSymmAccountSummary(params) {
24522
24953
  userAddress,
24523
24954
  chainId
24524
24955
  }),
24525
- enabled: internalEnabled && (params.query?.enabled ?? true)
24526
- });
24527
- }
24528
- function useSymmAccountData(params) {
24529
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24530
- const { address, upnl } = params;
24531
- const chainId = params.chainId ?? ctxChainId;
24532
- const internalEnabled = !!symmCoreClient && !!address;
24533
- return useQuery({
24534
- ...params.query,
24535
- 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
24956
+ enabled: internalEnabled && (params.query?.enabled ?? true)
24708
24957
  });
24709
24958
  }
24710
- function useSymmOpenOrders(params) {
24959
+ function useSymmAccountData(params) {
24711
24960
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24712
- const { accountAddress, address } = params;
24713
- const resolvedAddress = accountAddress ? void 0 : address;
24961
+ const { address, upnl } = params;
24714
24962
  const chainId = params.chainId ?? ctxChainId;
24715
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24963
+ const internalEnabled = !!symmCoreClient && !!address;
24716
24964
  return useQuery({
24717
24965
  ...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
24966
+ queryKey: symmKeys.accountData(address, chainId, upnl),
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);
24891
- }
24892
- }),
24893
- mutationFn: async () => {
24894
- if (!symmCoreClient || !orderId) {
24895
- throw new Error("symm-core client or orderId not available");
25096
+ invalidatePositions(queryClient);
24896
25097
  }
24897
- return symmCoreClient.orders.clearTriggerConfig(orderId);
24898
- }
24899
- });
24900
- }
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
25098
  }),
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)
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");
25111
+ }
25112
+ return symmCoreClient.positions.update(positionId, {
25113
+ ...request,
25114
+ authToken
25115
+ });
25116
+ }
24955
25117
  });
24956
25118
  }
24957
- function useSymmHedgerMarketBySymbol(params) {
25119
+ function useSymmPositions(params) {
24958
25120
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24959
- const { symbol } = params;
25121
+ const { accountAddress, address } = params;
25122
+ const resolvedAddress = accountAddress ? void 0 : address;
24960
25123
  const chainId = params.chainId ?? ctxChainId;
24961
- const internalEnabled = !!symmCoreClient && !!symbol;
25124
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25125
+ const enabled = internalEnabled && (params.query?.enabled ?? true);
24962
25126
  return useQuery({
24963
25127
  ...params.query,
24964
- queryKey: symmKeys.hedgerMarketBySymbol(symbol, chainId),
24965
- queryFn: () => symmCoreClient.markets.getBySymbol(symbol, chainId),
24966
- enabled: internalEnabled && (params.query?.enabled ?? true)
25128
+ queryKey: symmKeys.positions({
25129
+ accountAddress,
25130
+ address: resolvedAddress,
25131
+ chainId
25132
+ }),
25133
+ queryFn: () => symmCoreClient.positions.getOpen({
25134
+ accountAddress,
25135
+ address: resolvedAddress,
25136
+ chainId
25137
+ }),
25138
+ enabled
24967
25139
  });
24968
25140
  }
24969
- function useSymmLockedParams(params) {
25141
+ function useSymmOpenOrders(params) {
24970
25142
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24971
- const { marketName, leverage } = params;
25143
+ const { accountAddress, address } = params;
25144
+ const resolvedAddress = accountAddress ? void 0 : address;
24972
25145
  const chainId = params.chainId ?? ctxChainId;
24973
- const internalEnabled = !!symmCoreClient && !!marketName && leverage != null;
25146
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24974
25147
  return useQuery({
24975
25148
  ...params.query,
24976
- queryKey: symmKeys.lockedParams(marketName, leverage, chainId),
24977
- queryFn: () => symmCoreClient.markets.getLockedParams({
24978
- marketName,
24979
- leverage,
25149
+ queryKey: symmKeys.openOrders({
25150
+ accountAddress,
25151
+ address: resolvedAddress,
25152
+ chainId
25153
+ }),
25154
+ queryFn: () => symmCoreClient.orders.list({
25155
+ address: accountAddress ?? resolvedAddress,
24980
25156
  chainId
24981
25157
  }),
24982
25158
  enabled: internalEnabled && (params.query?.enabled ?? true)
24983
- });
24984
- }
24985
- function useSymmHedgerMarkets(params) {
24986
- 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;
24997
- 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
-
25042
- // src/utils/binance-symbol-map.ts
25043
- var SYMBOL_OVERRIDES = {
25044
- // Add overrides here as needed for SYMM markets that don't map 1:1 to Binance
25045
- // e.g., 'SOME_SYMM_SYMBOL': 'BINANCE_SYMBOL',
25046
- };
25047
- var UNSUPPORTED_SYMBOLS = /* @__PURE__ */ new Set([
25048
- // Add symbols here that have no Binance equivalent
25049
- ]);
25050
- function resolveBinanceSymbol(symmSymbol) {
25051
- if (!symmSymbol || !symmSymbol.trim()) {
25052
- return {
25053
- symmSymbol,
25054
- normalizedSymbol: "",
25055
- binanceSymbol: null,
25056
- supported: false,
25057
- reason: "missing_symbol"
25058
- };
25059
- }
25060
- const normalized = symmSymbol.toUpperCase().trim();
25061
- if (!/^[A-Z0-9]+$/.test(normalized)) {
25062
- return {
25063
- symmSymbol,
25064
- normalizedSymbol: normalized,
25065
- binanceSymbol: null,
25066
- supported: false,
25067
- reason: "invalid_symbol"
25068
- };
25069
- }
25070
- if (UNSUPPORTED_SYMBOLS.has(normalized)) {
25071
- return {
25072
- symmSymbol,
25073
- normalizedSymbol: normalized,
25074
- binanceSymbol: null,
25075
- supported: false,
25076
- reason: "unsupported_symbol"
25077
- };
25078
- }
25079
- const binanceSymbol = SYMBOL_OVERRIDES[normalized] ?? (normalized.endsWith("USDT") ? normalized : `${normalized}USDT`);
25080
- return {
25081
- symmSymbol,
25082
- normalizedSymbol: normalized,
25083
- binanceSymbol,
25084
- supported: true,
25085
- reason: null
25086
- };
25087
- }
25088
- function getUnsupportedBinanceSymbols(symbols) {
25089
- return symbols.filter((symbol) => !resolveBinanceSymbol(symbol).supported);
25090
- }
25091
- function toBinanceSymbol(symmSymbol) {
25092
- return resolveBinanceSymbol(symmSymbol).binanceSymbol;
25093
- }
25094
-
25095
- // src/utils/binance-ws.ts
25096
- var BINANCE_WS_URL = "wss://fstream.binance.com/ws";
25097
- var RECONNECT_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
25098
- var BinanceWsManager = class {
25099
- ws = null;
25100
- streams = /* @__PURE__ */ new Map();
25101
- reconnectAttempt = 0;
25102
- reconnectTimer = null;
25103
- intentionalClose = false;
25104
- pendingSubscribes = [];
25105
- idCounter = 0;
25106
- /**
25107
- * Subscribe to a kline stream. Returns an unsubscribe function.
25108
- */
25109
- subscribeKline(symbol, interval, cb) {
25110
- const streamName = `${symbol.toLowerCase()}@kline_${interval}`;
25111
- const id = this.generateId();
25112
- const wrappedCb = (raw) => {
25113
- const k = raw.k;
25114
- if (!k) return;
25115
- cb({
25116
- symbol: raw.s,
25117
- interval: k.i,
25118
- openTime: k.t,
25119
- closeTime: k.T,
25120
- open: parseFloat(k.o),
25121
- high: parseFloat(k.h),
25122
- low: parseFloat(k.l),
25123
- close: parseFloat(k.c),
25124
- volume: parseFloat(k.v),
25125
- isFinal: k.x
25126
- });
25127
- };
25128
- this.addStreamCallback(streamName, id, wrappedCb);
25129
- return () => this.removeStreamCallback(streamName, id);
25130
- }
25131
- /**
25132
- * Subscribe to a mark price stream (1s updates). Returns an unsubscribe function.
25133
- */
25134
- subscribeMarkPrice(symbol, cb) {
25135
- const streamName = `${symbol.toLowerCase()}@markPrice@1s`;
25136
- const id = this.generateId();
25137
- const wrappedCb = (raw) => {
25138
- cb({
25139
- symbol: raw.s,
25140
- markPrice: parseFloat(raw.p),
25141
- indexPrice: parseFloat(raw.i),
25142
- time: raw.E
25143
- });
25144
- };
25145
- this.addStreamCallback(streamName, id, wrappedCb);
25146
- return () => this.removeStreamCallback(streamName, id);
25147
- }
25148
- /**
25149
- * Destroy the manager and close the connection.
25150
- */
25151
- destroy() {
25152
- this.intentionalClose = true;
25153
- if (this.reconnectTimer) {
25154
- clearTimeout(this.reconnectTimer);
25155
- this.reconnectTimer = null;
25156
- }
25157
- if (this.ws) {
25158
- this.ws.close();
25159
- this.ws = null;
25160
- }
25161
- this.streams.clear();
25162
- }
25163
- // --- Private ---
25164
- generateId() {
25165
- return `sub_${++this.idCounter}_${Date.now()}`;
25166
- }
25167
- addStreamCallback(streamName, id, cb) {
25168
- let sub = this.streams.get(streamName);
25169
- const isNew = !sub;
25170
- if (!sub) {
25171
- sub = { callbacks: /* @__PURE__ */ new Map() };
25172
- this.streams.set(streamName, sub);
25173
- }
25174
- sub.callbacks.set(id, cb);
25175
- if (isNew) {
25176
- this.ensureConnected();
25177
- this.sendSubscribe([streamName]);
25178
- }
25179
- }
25180
- removeStreamCallback(streamName, id) {
25181
- const sub = this.streams.get(streamName);
25182
- if (!sub) return;
25183
- sub.callbacks.delete(id);
25184
- if (sub.callbacks.size === 0) {
25185
- this.streams.delete(streamName);
25186
- this.sendUnsubscribe([streamName]);
25187
- if (this.streams.size === 0 && this.ws) {
25188
- this.intentionalClose = true;
25189
- this.ws.close();
25190
- this.ws = null;
25191
- this.intentionalClose = false;
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);
25192
25193
  }
25194
+ }),
25195
+ mutationFn: async (request) => {
25196
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25197
+ return symmCoreClient.orders.setTpsl(request);
25193
25198
  }
25194
- }
25195
- ensureConnected() {
25196
- if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
25197
- 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);
25198
25214
  }
25199
- this.connect();
25200
- }
25201
- connect() {
25202
- if (typeof WebSocket === "undefined") return;
25203
- this.intentionalClose = false;
25204
- this.ws = new WebSocket(BINANCE_WS_URL);
25205
- this.ws.onopen = () => {
25206
- this.reconnectAttempt = 0;
25207
- const activeStreams = Array.from(this.streams.keys());
25208
- if (activeStreams.length > 0) {
25209
- 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);
25210
25271
  }
25211
- if (this.pendingSubscribes.length > 0) {
25212
- this.sendSubscribe(this.pendingSubscribes);
25213
- 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);
25214
25301
  }
25215
- };
25216
- this.ws.onmessage = (event) => {
25217
- try {
25218
- const data = JSON.parse(event.data);
25219
- this.handleMessage(data);
25220
- } catch {
25302
+ }),
25303
+ mutationFn: async (request) => {
25304
+ if (!symmCoreClient || !orderId) {
25305
+ throw new Error("symm-core client or orderId not available");
25221
25306
  }
25222
- };
25223
- this.ws.onclose = () => {
25224
- if (this.intentionalClose) return;
25225
- this.scheduleReconnect();
25226
- };
25227
- this.ws.onerror = () => {
25228
- };
25229
- }
25230
- handleMessage(data) {
25231
- if (data.e === "kline") {
25232
- const k = data.k;
25233
- const streamName = `${data.s.toLowerCase()}@kline_${k.i}`;
25234
- this.dispatchToStream(streamName, data);
25235
- } else if (data.e === "markPriceUpdate") {
25236
- const streamName = `${data.s.toLowerCase()}@markPrice@1s`;
25237
- this.dispatchToStream(streamName, data);
25307
+ return symmCoreClient.orders.setTriggerConfig(orderId, request);
25238
25308
  }
25239
- }
25240
- dispatchToStream(streamName, data) {
25241
- const sub = this.streams.get(streamName);
25242
- if (!sub) return;
25243
- sub.callbacks.forEach((cb) => {
25244
- try {
25245
- cb(data);
25246
- } 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);
25247
25322
  }
25248
- });
25249
- }
25250
- sendSubscribe(streams) {
25251
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
25252
- this.pendingSubscribes.push(...streams);
25253
- return;
25254
- }
25255
- this.ws.send(JSON.stringify({
25256
- method: "SUBSCRIBE",
25257
- params: streams,
25258
- id: Date.now()
25259
- }));
25260
- }
25261
- sendUnsubscribe(streams) {
25262
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
25263
- this.pendingSubscribes = this.pendingSubscribes.filter(
25264
- (s) => !streams.includes(s)
25265
- );
25266
- 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);
25267
25329
  }
25268
- this.ws.send(JSON.stringify({
25269
- method: "UNSUBSCRIBE",
25270
- params: streams,
25271
- id: Date.now()
25272
- }));
25273
- }
25274
- scheduleReconnect() {
25275
- if (this.reconnectTimer) return;
25276
- if (this.streams.size === 0) return;
25277
- const delay = RECONNECT_DELAYS[Math.min(this.reconnectAttempt, RECONNECT_DELAYS.length - 1)];
25278
- this.reconnectAttempt++;
25279
- this.reconnectTimer = setTimeout(() => {
25280
- this.reconnectTimer = null;
25281
- this.connect();
25282
- }, delay);
25283
- }
25284
- };
25285
- var _instance = null;
25286
- function getBinanceWsManager() {
25287
- if (!_instance) {
25288
- _instance = new BinanceWsManager();
25289
- }
25290
- 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
+ });
25291
25358
  }
25292
-
25293
- // src/react/stores/use-binance-mark-price-store.ts
25294
- var refCounts = /* @__PURE__ */ new Map();
25295
- var streamUnsubs = /* @__PURE__ */ new Map();
25296
- var streamSymbols = /* @__PURE__ */ new Map();
25297
- function normalizeBinanceSymbol(symbol) {
25298
- 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
+ });
25299
25375
  }
25300
- function getNextRefCount(binanceSymbol) {
25301
- 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
+ });
25302
25387
  }
25303
- function getPrevRefCount(binanceSymbol) {
25304
- 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
+ });
25399
+ }
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
+ });
25305
25434
  }
25306
- var useBinanceMarkPriceStore = create((set) => ({
25307
- markPrices: {},
25308
- subscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
25309
- const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
25310
- const nextRefCount = getNextRefCount(binanceSymbol);
25311
- refCounts.set(binanceSymbol, nextRefCount);
25312
- const symbols = streamSymbols.get(binanceSymbol) ?? /* @__PURE__ */ new Set();
25313
- symbols.add(symmSymbol);
25314
- streamSymbols.set(binanceSymbol, symbols);
25315
- if (nextRefCount === 1) {
25316
- const wsManager = getBinanceWsManager();
25317
- const unsubscribe = wsManager.subscribeMarkPrice(binanceSymbol, (data) => {
25318
- const canonicalSymbol = normalizeBinanceSymbol(data.symbol);
25319
- const mappedSymbols = streamSymbols.get(canonicalSymbol);
25320
- if (!mappedSymbols || mappedSymbols.size === 0) return;
25321
- set((state) => {
25322
- const nextMarkPrices = { ...state.markPrices };
25323
- mappedSymbols.forEach((mappedSymbol) => {
25324
- nextMarkPrices[mappedSymbol] = data.markPrice;
25325
- });
25326
- return { markPrices: nextMarkPrices };
25327
- });
25328
- });
25329
- streamUnsubs.set(binanceSymbol, unsubscribe);
25330
- }
25331
- },
25332
- unsubscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
25333
- const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
25334
- const symbols = streamSymbols.get(binanceSymbol);
25335
- if (symbols) {
25336
- symbols.delete(symmSymbol);
25337
- if (symbols.size === 0) {
25338
- streamSymbols.delete(binanceSymbol);
25339
- } else {
25340
- streamSymbols.set(binanceSymbol, symbols);
25341
- }
25342
- }
25343
- const nextRefCount = getPrevRefCount(binanceSymbol);
25344
- if (nextRefCount === 0) {
25345
- const unsubscribe = streamUnsubs.get(binanceSymbol);
25346
- if (unsubscribe) unsubscribe();
25347
- streamUnsubs.delete(binanceSymbol);
25348
- refCounts.delete(binanceSymbol);
25349
- } else {
25350
- refCounts.set(binanceSymbol, nextRefCount);
25351
- }
25352
- set((state) => {
25353
- if (state.markPrices[symmSymbol] == null) return state;
25354
- const nextMarkPrices = { ...state.markPrices };
25355
- delete nextMarkPrices[symmSymbol];
25356
- return { markPrices: nextMarkPrices };
25357
- });
25358
- }
25359
- }));
25360
25435
 
25361
- // src/react/hooks/use-symm-token-selection-markets.ts
25362
- async function fetchTickerSnapshot(symbol) {
25363
- const resolution = resolveBinanceSymbol(symbol);
25364
- if (!resolution.binanceSymbol) return null;
25365
- const ticker = await fetch24hrTicker(resolution.binanceSymbol);
25366
- if (!ticker) return null;
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}`);
25449
+ }
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();
25367
25466
  return {
25368
- prevClosePrice: ticker.prevClosePrice
25467
+ lastPrice: parseFloat(data.lastPrice),
25468
+ prevClosePrice: parseFloat(data.prevClosePrice),
25469
+ priceChangePercent: parseFloat(data.priceChangePercent)
25369
25470
  };
25370
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
+ }
25488
+
25489
+ // src/react/hooks/use-symm-token-selection-markets.ts
25371
25490
  function useSymmTokenSelectionMarkets(params) {
25372
25491
  const query = useSymmHedgerMarkets(params);
25373
25492
  const baseMarkets = query.data?.filteredMarkets ?? query.data?.markets ?? [];
@@ -25383,48 +25502,27 @@ function useSymmTokenSelectionMarkets(params) {
25383
25502
  () => [...marketSymbols].sort().join(","),
25384
25503
  [marketSymbols]
25385
25504
  );
25386
- const symbolToBinanceMap = useMemo(
25387
- () => new Map(
25388
- marketSymbols.map((symbol) => {
25389
- const binanceSymbol = resolveBinanceSymbol(symbol).binanceSymbol;
25390
- if (!binanceSymbol) return null;
25391
- return [symbol, binanceSymbol];
25392
- }).filter((entry) => !!entry)
25393
- ),
25394
- [marketSymbols]
25395
- );
25396
25505
  const liveMarkPrices = useBinanceMarkPriceStore((state) => state.markPrices);
25397
- const subscribeSymbol = useBinanceMarkPriceStore((state) => state.subscribeSymbol);
25398
- const unsubscribeSymbol = useBinanceMarkPriceStore((state) => state.unsubscribeSymbol);
25399
25506
  const priceQuery = useQuery({
25400
25507
  queryKey: ["symm", "token-selection-markets-price", symbolsKey],
25401
25508
  queryFn: async () => {
25509
+ const allTickers = await fetch24hrTickers();
25402
25510
  const tickerSnapshots = {};
25403
- await Promise.all(
25404
- marketSymbols.map(async (symbol) => {
25405
- tickerSnapshots[symbol] = await fetchTickerSnapshot(symbol);
25406
- })
25407
- );
25511
+ marketSymbols.forEach((symbol) => {
25512
+ const binanceSymbol = resolveBinanceSymbol(symbol).binanceSymbol;
25513
+ if (!binanceSymbol) {
25514
+ tickerSnapshots[symbol] = null;
25515
+ return;
25516
+ }
25517
+ const ticker = allTickers[binanceSymbol];
25518
+ tickerSnapshots[symbol] = ticker ? { prevClosePrice: ticker.prevClosePrice } : null;
25519
+ });
25408
25520
  return { tickerSnapshots };
25409
25521
  },
25410
25522
  enabled: query.isSuccess && marketSymbols.length > 0,
25411
25523
  staleTime: 6e4,
25412
25524
  gcTime: 5 * 6e4
25413
25525
  });
25414
- useEffect(() => {
25415
- if (!query.isSuccess || symbolToBinanceMap.size === 0) {
25416
- return;
25417
- }
25418
- const symbolEntries = Array.from(symbolToBinanceMap.entries());
25419
- symbolEntries.forEach(
25420
- ([symbol, binanceSymbol]) => subscribeSymbol(symbol, binanceSymbol)
25421
- );
25422
- return () => {
25423
- symbolEntries.forEach(
25424
- ([symbol, binanceSymbol]) => unsubscribeSymbol(symbol, binanceSymbol)
25425
- );
25426
- };
25427
- }, [query.isSuccess, symbolsKey, symbolToBinanceMap, subscribeSymbol, unsubscribeSymbol]);
25428
25526
  const markets = useMemo(() => {
25429
25527
  const snapshots = priceQuery.data?.tickerSnapshots ?? {};
25430
25528
  return baseMarkets.map((market) => {
@@ -25453,6 +25551,61 @@ function useSymmTokenSelectionMarkets(params) {
25453
25551
  () => new Map(markets.map((market) => [market.id, market])),
25454
25552
  [markets]
25455
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
+ ]);
25456
25609
  return {
25457
25610
  query,
25458
25611
  priceQuery,
@@ -25463,6 +25616,22 @@ function useSymmTokenSelectionMarkets(params) {
25463
25616
  isPriceLoading: priceQuery.isLoading
25464
25617
  };
25465
25618
  }
25619
+ function useSymmTokenMarkPrice(symbol) {
25620
+ const normalizedSymbol = symbol?.trim().toUpperCase() || null;
25621
+ const resolution = resolveBinanceSymbol(normalizedSymbol ?? "");
25622
+ const liveMarkPrices = useBinanceMarkPriceStore((state) => state.markPrices);
25623
+ const markPrice = useMemo(() => {
25624
+ if (!normalizedSymbol) return null;
25625
+ return liveMarkPrices[normalizedSymbol] ?? null;
25626
+ }, [liveMarkPrices, normalizedSymbol]);
25627
+ return {
25628
+ symbol: normalizedSymbol,
25629
+ binanceSymbol: resolution.binanceSymbol,
25630
+ markPrice,
25631
+ isSupported: resolution.supported,
25632
+ reason: resolution.reason
25633
+ };
25634
+ }
25466
25635
  function useSymmFunding(params) {
25467
25636
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25468
25637
  const chainId = params?.chainId ?? ctxChainId;
@@ -25971,7 +26140,7 @@ function computeNetFundingSum({
25971
26140
  }
25972
26141
 
25973
26142
  // src/react/hooks/use-symm-token-selection-metadata.ts
25974
- async function fetchTickerSnapshot2(symbol) {
26143
+ async function fetchTickerSnapshot(symbol) {
25975
26144
  const resolution = resolveBinanceSymbol(symbol);
25976
26145
  if (!resolution.binanceSymbol) return null;
25977
26146
  const ticker = await fetch24hrTicker(resolution.binanceSymbol);
@@ -26003,17 +26172,7 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26003
26172
  const isUnsupported = unsupportedSymbols.length > 0;
26004
26173
  const unavailableReason = isUnsupported ? `Binance market data is unavailable for ${unsupportedSymbols.join(", ")}.` : null;
26005
26174
  const symbolsKey = [...selectedSymbols].sort().join(",");
26006
- const symbolToBinanceMap = useMemo(
26007
- () => new Map(
26008
- selectedSymbols.map(
26009
- (symbol) => [symbol, resolveBinanceSymbol(symbol).binanceSymbol]
26010
- ).filter((entry) => !!entry[1])
26011
- ),
26012
- [selectedSymbols]
26013
- );
26014
26175
  const liveMarkPrices = useBinanceMarkPriceStore((state) => state.markPrices);
26015
- const subscribeSymbol = useBinanceMarkPriceStore((state) => state.subscribeSymbol);
26016
- const unsubscribeSymbol = useBinanceMarkPriceStore((state) => state.unsubscribeSymbol);
26017
26176
  const query = useQuery({
26018
26177
  queryKey: ["symm", "chart-metadata", symbolsKey],
26019
26178
  queryFn: async () => {
@@ -26027,7 +26186,7 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26027
26186
  const results = await Promise.all(
26028
26187
  allSymbols.map(async ({ symbol }) => ({
26029
26188
  symbol,
26030
- ticker: await fetchTickerSnapshot2(symbol)
26189
+ ticker: await fetchTickerSnapshot(symbol)
26031
26190
  }))
26032
26191
  );
26033
26192
  const tickerSnapshots = {};
@@ -26039,28 +26198,7 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26039
26198
  enabled: enabled && selectedSymbols.length > 0 && !isUnsupported,
26040
26199
  gcTime: 5 * 6e4
26041
26200
  });
26042
- useEffect(() => {
26043
- if (!enabled || isUnsupported || selectedSymbols.length === 0) {
26044
- return;
26045
- }
26046
- const symbolEntries = Array.from(symbolToBinanceMap.entries());
26047
- symbolEntries.forEach(
26048
- ([symbol, binanceSymbol]) => subscribeSymbol(symbol, binanceSymbol)
26049
- );
26050
- return () => {
26051
- symbolEntries.forEach(
26052
- ([symbol, binanceSymbol]) => unsubscribeSymbol(symbol, binanceSymbol)
26053
- );
26054
- };
26055
- }, [
26056
- enabled,
26057
- isUnsupported,
26058
- selectedSymbols.length,
26059
- symbolToBinanceMap,
26060
- subscribeSymbol,
26061
- unsubscribeSymbol
26062
- ]);
26063
- return useMemo(() => {
26201
+ const result = useMemo(() => {
26064
26202
  const tickerSnapshots = query.data?.tickerSnapshots ?? {};
26065
26203
  const longMeta = {};
26066
26204
  const shortMeta = {};
@@ -26119,6 +26257,53 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26119
26257
  selectedSymbols.length,
26120
26258
  liveMarkPrices
26121
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;
26122
26307
  }
26123
26308
 
26124
26309
  // src/utils/binance-intervals.ts
@@ -26500,6 +26685,6 @@ function getSymmErrorMessage(error) {
26500
26685
  return "An unexpected error occurred.";
26501
26686
  }
26502
26687
 
26503
- export { SymmProvider, getSymmErrorMessage, symmKeys, useBinanceMarkPriceStore, useSymmAccountData, useSymmAccountSummary, useSymmAccountsApi, useSymmAccountsLength, useSymmAccountsQuery, useSymmAccountsWithPositions, useSymmAllocateCollateralMutation, useSymmApprovalQuery, useSymmApproveMutation, useSymmAuth, useSymmAuthStore, useSymmAvailableMargin, useSymmBalances, useSymmCancelClose, useSymmCancelOpenMutation, useSymmCancelTpslMutation, useSymmCancelTwapOrderMutation, useSymmChartCandles, useSymmChartSelection, useSymmClearTriggerConfigMutation, useSymmCloseAllPositionsMutation, useSymmCloseOrder, useSymmClosePositionMutation, useSymmContext, useSymmCoreClient, useSymmCreateAccountMutation, useSymmDeallocateCollateralMutation, useSymmDelegation, useSymmDepositAndAllocateMutation, useSymmDepositMutation, useSymmEditAccountNameMutation, useSymmFunding, useSymmFundingHistory, useSymmFundingPayments, useSymmHedgerMarketById, useSymmHedgerMarketBySymbol, useSymmHedgerMarkets, useSymmInstantTradeEnsureReadyMutation, useSymmInstantTradeExecuteMutation, useSymmInternalTransferCollateralMutation, useSymmLockedParams, useSymmMarkReadNotificationMutation, useSymmMarkets, useSymmNotificationsQuery, useSymmOpenBasketMutation, useSymmOpenOrders, useSymmPendingIds, useSymmPendingInstantOpens, useSymmPerformanceOverlays, useSymmPortfolio, useSymmPositions, useSymmSetTpslMutation, useSymmSetTriggerConfigMutation, useSymmSignTermsMutation, useSymmSignatureQuery, useSymmTokenSelectionMarkets, useSymmTokenSelectionMetadata, useSymmTpslOrders, useSymmTradeHistory, useSymmTriggerConfigQuery, useSymmTriggerOrders, useSymmTwapOrder, useSymmTwapOrdersQuery, useSymmUnreadCountQuery, useSymmUpdatePositionMutation, useSymmWithdraw, useSymmWs, useSymmWsStore };
26688
+ export { SymmProvider, getSymmErrorMessage, symmKeys, useBinanceMarkPriceStore, useSymmAccountData, useSymmAccountSummary, useSymmAccountsApi, useSymmAccountsLength, useSymmAccountsQuery, useSymmAccountsWithPositions, useSymmAllocateCollateralMutation, useSymmApprovalQuery, useSymmApproveMutation, useSymmAuth, useSymmAuthStore, useSymmAvailableMargin, useSymmBalances, useSymmCancelClose, useSymmCancelOpenMutation, useSymmCancelTpslMutation, useSymmCancelTwapOrderMutation, useSymmChartCandles, useSymmChartSelection, useSymmClearTriggerConfigMutation, useSymmCloseAllPositionsMutation, useSymmCloseOrder, useSymmClosePositionMutation, useSymmContext, useSymmCoreClient, useSymmCreateAccountMutation, useSymmDeallocateCollateralMutation, useSymmDelegation, useSymmDepositAndAllocateMutation, useSymmDepositMutation, useSymmEditAccountNameMutation, useSymmFunding, useSymmFundingHistory, useSymmFundingPayments, useSymmHedgerMarketById, useSymmHedgerMarketBySymbol, useSymmHedgerMarkets, useSymmInstantTradeEnsureReadyMutation, useSymmInstantTradeExecuteMutation, useSymmInternalTransferCollateralMutation, useSymmLockedParams, useSymmMarkReadNotificationMutation, useSymmMarkets, useSymmNotificationsQuery, useSymmOpenBasketMutation, useSymmOpenOrders, useSymmPendingIds, useSymmPendingInstantOpens, useSymmPerformanceOverlays, useSymmPortfolio, useSymmPositions, useSymmSetTpslMutation, useSymmSetTriggerConfigMutation, useSymmSignTermsMutation, useSymmSignatureQuery, useSymmTokenMarkPrice, useSymmTokenSelectionMarkets, useSymmTokenSelectionMetadata, useSymmTpslOrders, useSymmTradeHistory, useSymmTriggerConfigQuery, useSymmTriggerOrders, useSymmTwapOrder, useSymmTwapOrdersQuery, useSymmUnreadCountQuery, useSymmUpdatePositionMutation, useSymmWithdraw, useSymmWs, useSymmWsStore };
26504
26689
  //# sourceMappingURL=index.mjs.map
26505
26690
  //# sourceMappingURL=index.mjs.map