@pear-protocol/symmio-client 0.2.27 → 0.2.29

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,348 +14,796 @@ 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/market/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
+ function extractWsPayload(payload) {
103
+ if (payload && typeof payload === "object" && "data" in payload) {
104
+ return payload.data ?? payload;
194
105
  }
106
+ return payload;
195
107
  }
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;
108
+ function isKlinePayload(payload) {
109
+ return Boolean(
110
+ payload && typeof payload === "object" && payload.e === "kline" && typeof payload.s === "string" && payload.k && typeof payload.k.i === "string"
111
+ );
227
112
  }
228
- function symmAuthTokenKey(address, chainId) {
229
- return `${address.toLowerCase()}:${chainId}`;
113
+ function isMarkPricePayload(payload) {
114
+ return Boolean(
115
+ payload && typeof payload === "object" && payload.e === "markPriceUpdate" && typeof payload.s === "string"
116
+ );
230
117
  }
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
239
- }
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: {} });
118
+ var BinanceWsManager = class {
119
+ ws = null;
120
+ streams = /* @__PURE__ */ new Map();
121
+ reconnectAttempt = 0;
122
+ reconnectTimer = null;
123
+ intentionalClose = false;
124
+ pendingSubscribes = [];
125
+ idCounter = 0;
126
+ /**
127
+ * Subscribe to a kline stream. Returns an unsubscribe function.
128
+ */
129
+ subscribeKline(symbol, interval, cb) {
130
+ const streamName = `${symbol.toLowerCase()}@kline_${interval}`;
131
+ const id = this.generateId();
132
+ const wrappedCb = (raw) => {
133
+ const k = raw.k;
134
+ if (!k) return;
135
+ cb({
136
+ symbol: raw.s,
137
+ interval: k.i,
138
+ openTime: k.t,
139
+ closeTime: k.T,
140
+ open: parseFloat(k.o),
141
+ high: parseFloat(k.h),
142
+ low: parseFloat(k.l),
143
+ close: parseFloat(k.c),
144
+ volume: parseFloat(k.v),
145
+ isFinal: k.x
146
+ });
147
+ };
148
+ this.addStreamCallback(streamName, id, wrappedCb);
149
+ return () => this.removeStreamCallback(streamName, id);
257
150
  }
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;
151
+ /**
152
+ * Subscribe to a mark price stream (1s updates). Returns an unsubscribe function.
153
+ */
154
+ subscribeMarkPrice(symbol, cb) {
155
+ const streamName = `${symbol.toLowerCase()}@markPrice@1s`;
156
+ const id = this.generateId();
157
+ const wrappedCb = (raw) => {
158
+ cb({
159
+ symbol: normalizeBaseSymbol(raw.s),
160
+ markPrice: parseFloat(raw.p),
161
+ indexPrice: parseFloat(raw.i),
162
+ time: raw.E
163
+ });
164
+ };
165
+ this.addStreamCallback(streamName, id, wrappedCb);
166
+ return () => this.removeStreamCallback(streamName, id);
167
+ }
168
+ /**
169
+ * Subscribe to the all-market mark price stream (1s updates).
170
+ * Returns an unsubscribe function.
171
+ */
172
+ subscribeAllMarkPrices(cb) {
173
+ const streamName = "!markPrice@arr@1s";
174
+ const id = this.generateId();
175
+ const wrappedCb = (raw) => {
176
+ cb(
177
+ extractTickers(raw).map((entry) => ({
178
+ symbol: normalizeBaseSymbol(entry.s),
179
+ markPrice: parseFloat(entry.p),
180
+ indexPrice: parseFloat(entry.i),
181
+ time: entry.E
182
+ }))
183
+ );
184
+ };
185
+ this.addStreamCallback(streamName, id, wrappedCb);
186
+ return () => this.removeStreamCallback(streamName, id);
187
+ }
188
+ /**
189
+ * Destroy the manager and close the connection.
190
+ */
191
+ destroy() {
192
+ this.intentionalClose = true;
193
+ if (this.reconnectTimer) {
194
+ clearTimeout(this.reconnectTimer);
195
+ this.reconnectTimer = null;
270
196
  }
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;
197
+ if (this.ws) {
198
+ this.ws.close();
199
+ this.ws = null;
200
+ }
201
+ this.streams.clear();
202
+ }
203
+ // --- Private ---
204
+ generateId() {
205
+ return `sub_${++this.idCounter}_${Date.now()}`;
206
+ }
207
+ addStreamCallback(streamName, id, cb) {
208
+ let sub = this.streams.get(streamName);
209
+ const isNew = !sub;
210
+ if (!sub) {
211
+ sub = { callbacks: /* @__PURE__ */ new Map() };
212
+ this.streams.set(streamName, sub);
213
+ }
214
+ sub.callbacks.set(id, cb);
215
+ if (isNew) {
216
+ this.ensureConnected();
217
+ this.sendSubscribe([streamName]);
218
+ }
219
+ }
220
+ removeStreamCallback(streamName, id) {
221
+ const sub = this.streams.get(streamName);
222
+ if (!sub) return;
223
+ sub.callbacks.delete(id);
224
+ if (sub.callbacks.size === 0) {
225
+ this.streams.delete(streamName);
226
+ this.sendUnsubscribe([streamName]);
227
+ if (this.streams.size === 0 && this.ws) {
228
+ this.intentionalClose = true;
229
+ this.ws.close();
230
+ this.ws = null;
231
+ this.intentionalClose = false;
285
232
  }
286
- const cached = getCachedToken(resolvedAccountAddress, chainId);
287
- if (cached) {
288
- setToken(resolvedAccountAddress, chainId, cached);
289
- return cached;
233
+ }
234
+ }
235
+ ensureConnected() {
236
+ if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
237
+ return;
238
+ }
239
+ this.connect();
240
+ }
241
+ connect() {
242
+ if (typeof WebSocket === "undefined") return;
243
+ this.intentionalClose = false;
244
+ this.ws = new WebSocket(BINANCE_WS_URL);
245
+ this.ws.onopen = () => {
246
+ this.reconnectAttempt = 0;
247
+ const activeStreams = Array.from(this.streams.keys());
248
+ if (activeStreams.length > 0) {
249
+ this.sendSubscribe(activeStreams);
250
+ }
251
+ if (this.pendingSubscribes.length > 0) {
252
+ this.sendSubscribe(this.pendingSubscribes);
253
+ this.pendingSubscribes = [];
290
254
  }
255
+ };
256
+ this.ws.onmessage = (event) => {
291
257
  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;
258
+ const data = JSON.parse(event.data);
259
+ this.handleMessage(data);
301
260
  } catch {
302
- clearToken(resolvedAccountAddress, chainId);
303
- return null;
304
261
  }
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) {
262
+ };
263
+ this.ws.onclose = () => {
264
+ if (this.intentionalClose) return;
265
+ this.scheduleReconnect();
266
+ };
267
+ this.ws.onerror = () => {
268
+ };
269
+ }
270
+ handleMessage(data) {
271
+ const payload = extractWsPayload(data);
272
+ if (Array.isArray(payload)) {
273
+ this.dispatchToStream("!markPrice@arr@1s", payload);
322
274
  return;
323
275
  }
324
- if (previousAddress && (addressChanged || chainChanged)) {
325
- clearCachedToken(previousAddress, previousChainId);
326
- clearToken(previousAddress, previousChainId);
276
+ if (isKlinePayload(payload)) {
277
+ const k = payload.k;
278
+ const streamName = `${payload.s.toLowerCase()}@kline_${k.i}`;
279
+ this.dispatchToStream(streamName, payload);
280
+ } else if (isMarkPricePayload(payload)) {
281
+ const streamName = `${payload.s.toLowerCase()}@markPrice@1s`;
282
+ this.dispatchToStream(streamName, payload);
327
283
  }
328
- const cached = getCachedToken(address, chainId);
329
- if (cached) {
330
- setToken(address, chainId, cached);
331
- } else {
332
- clearToken(address, chainId);
284
+ }
285
+ dispatchToStream(streamName, data) {
286
+ const sub = this.streams.get(streamName);
287
+ if (!sub) return;
288
+ sub.callbacks.forEach((cb) => {
289
+ try {
290
+ cb(data);
291
+ } catch {
292
+ }
293
+ });
294
+ }
295
+ sendSubscribe(streams) {
296
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
297
+ this.pendingSubscribes.push(...streams);
298
+ return;
333
299
  }
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
-
353
- // src/constants/addresses.ts
354
- var MULTI_ACCOUNT_ADDRESS = {
355
- [42161 /* ARBITRUM */]: "0x6273242a7E88b3De90822b31648C212215caaFE4",
356
- [8453 /* BASE */]: "0xE43166cE17d3511B09438a359dAa53513225101D"
357
- };
358
- var SYMMIO_DIAMOND_ADDRESS = {
300
+ this.ws.send(JSON.stringify({
301
+ method: "SUBSCRIBE",
302
+ params: streams,
303
+ id: Date.now()
304
+ }));
305
+ }
306
+ sendUnsubscribe(streams) {
307
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
308
+ this.pendingSubscribes = this.pendingSubscribes.filter(
309
+ (s) => !streams.includes(s)
310
+ );
311
+ return;
312
+ }
313
+ this.ws.send(JSON.stringify({
314
+ method: "UNSUBSCRIBE",
315
+ params: streams,
316
+ id: Date.now()
317
+ }));
318
+ }
319
+ scheduleReconnect() {
320
+ if (this.reconnectTimer) return;
321
+ if (this.streams.size === 0) return;
322
+ const delay = RECONNECT_DELAYS[Math.min(this.reconnectAttempt, RECONNECT_DELAYS.length - 1)];
323
+ this.reconnectAttempt++;
324
+ this.reconnectTimer = setTimeout(() => {
325
+ this.reconnectTimer = null;
326
+ this.connect();
327
+ }, delay);
328
+ }
329
+ };
330
+ var _instance = null;
331
+ function getBinanceWsManager() {
332
+ if (!_instance) {
333
+ _instance = new BinanceWsManager();
334
+ }
335
+ return _instance;
336
+ }
337
+
338
+ // src/react/stores/use-binance-mark-price-store.ts
339
+ var refCounts = /* @__PURE__ */ new Map();
340
+ var streamSymbols = /* @__PURE__ */ new Map();
341
+ var allMarkPricesRefCount = 0;
342
+ var allMarkPricesUnsubscribe = null;
343
+ var STABLE_QUOTES2 = ["USDT0", "USDT", "USDC", "USDE", "USDH", "USD"];
344
+ function normalizeBinanceSymbol(symbol) {
345
+ const normalized = symbol.toUpperCase().trim();
346
+ for (const quote of STABLE_QUOTES2) {
347
+ if (normalized.endsWith(quote) && normalized.length > quote.length) {
348
+ return normalized.slice(0, -quote.length);
349
+ }
350
+ }
351
+ return normalized;
352
+ }
353
+ function getNextRefCount(binanceSymbol) {
354
+ return (refCounts.get(binanceSymbol) ?? 0) + 1;
355
+ }
356
+ function getPrevRefCount(binanceSymbol) {
357
+ return Math.max(0, (refCounts.get(binanceSymbol) ?? 0) - 1);
358
+ }
359
+ var useBinanceMarkPriceStore = create((set) => ({
360
+ markPrices: {},
361
+ subscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
362
+ const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
363
+ const nextRefCount = getNextRefCount(binanceSymbol);
364
+ refCounts.set(binanceSymbol, nextRefCount);
365
+ const symbols = streamSymbols.get(binanceSymbol) ?? /* @__PURE__ */ new Set();
366
+ symbols.add(symmSymbol);
367
+ streamSymbols.set(binanceSymbol, symbols);
368
+ if (allMarkPricesRefCount === 0) {
369
+ const wsManager = getBinanceWsManager();
370
+ allMarkPricesUnsubscribe = wsManager.subscribeAllMarkPrices((entries) => {
371
+ set((state) => {
372
+ let nextMarkPrices = null;
373
+ entries.forEach((entry) => {
374
+ const canonicalSymbol = normalizeBinanceSymbol(entry.symbol);
375
+ const mappedSymbols = streamSymbols.get(canonicalSymbol);
376
+ if (!mappedSymbols || mappedSymbols.size === 0) return;
377
+ nextMarkPrices ??= { ...state.markPrices };
378
+ mappedSymbols.forEach((mappedSymbol) => {
379
+ nextMarkPrices[mappedSymbol] = entry.markPrice;
380
+ });
381
+ });
382
+ return nextMarkPrices ? { markPrices: nextMarkPrices } : state;
383
+ });
384
+ });
385
+ }
386
+ allMarkPricesRefCount += 1;
387
+ },
388
+ unsubscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
389
+ const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
390
+ const symbols = streamSymbols.get(binanceSymbol);
391
+ if (symbols) {
392
+ symbols.delete(symmSymbol);
393
+ if (symbols.size === 0) {
394
+ streamSymbols.delete(binanceSymbol);
395
+ } else {
396
+ streamSymbols.set(binanceSymbol, symbols);
397
+ }
398
+ }
399
+ const nextRefCount = getPrevRefCount(binanceSymbol);
400
+ if (nextRefCount === 0) {
401
+ refCounts.delete(binanceSymbol);
402
+ } else {
403
+ refCounts.set(binanceSymbol, nextRefCount);
404
+ }
405
+ allMarkPricesRefCount = Math.max(0, allMarkPricesRefCount - 1);
406
+ if (allMarkPricesRefCount === 0) {
407
+ allMarkPricesUnsubscribe?.();
408
+ allMarkPricesUnsubscribe = null;
409
+ }
410
+ set((state) => {
411
+ if (state.markPrices[symmSymbol] == null) return state;
412
+ const nextMarkPrices = { ...state.markPrices };
413
+ delete nextMarkPrices[symmSymbol];
414
+ return { markPrices: nextMarkPrices };
415
+ });
416
+ }
417
+ }));
418
+
419
+ // src/react/hooks/use-binance-ws.ts
420
+ function useBinanceWs(params) {
421
+ const { symmCoreClient, chainId } = params;
422
+ const subscribeSymbol = useBinanceMarkPriceStore((state) => state.subscribeSymbol);
423
+ const unsubscribeSymbol = useBinanceMarkPriceStore((state) => state.unsubscribeSymbol);
424
+ useEffect(() => {
425
+ if (!symmCoreClient) {
426
+ return;
427
+ }
428
+ let cancelled = false;
429
+ let subscribedPairs = [];
430
+ const run = async () => {
431
+ try {
432
+ const result = await symmCoreClient.markets.listSymmHedger({ chainId });
433
+ if (cancelled) {
434
+ return;
435
+ }
436
+ const uniqueSymbols = Array.from(
437
+ new Set(
438
+ result.markets.map((market) => market.symbol).filter((symbol) => !!symbol)
439
+ )
440
+ );
441
+ subscribedPairs = uniqueSymbols.map((symbol) => {
442
+ const binanceSymbol = resolveBinanceSymbol(symbol).binanceSymbol;
443
+ if (!binanceSymbol) return null;
444
+ return [symbol, binanceSymbol];
445
+ }).filter((entry) => !!entry);
446
+ subscribedPairs.forEach(([symbol, binanceSymbol]) => {
447
+ subscribeSymbol(symbol, binanceSymbol);
448
+ });
449
+ } catch {
450
+ }
451
+ };
452
+ void run();
453
+ return () => {
454
+ cancelled = true;
455
+ subscribedPairs.forEach(([symbol, binanceSymbol]) => {
456
+ unsubscribeSymbol(symbol, binanceSymbol);
457
+ });
458
+ };
459
+ }, [symmCoreClient, chainId, subscribeSymbol, unsubscribeSymbol]);
460
+ }
461
+ function SymmProvider({
462
+ chainId = 42161,
463
+ address,
464
+ symmCoreConfig = {
465
+ apiUrl: "https://nginx-server-staging.up.railway.app",
466
+ wsUrl: "wss://nginx-server-staging.up.railway.app"
467
+ },
468
+ symmioConfig,
469
+ children
470
+ }) {
471
+ const symmCoreClient = useMemo(() => {
472
+ return createSymmSDK({
473
+ apiUrl: symmCoreConfig.apiUrl,
474
+ wsUrl: symmCoreConfig.wsUrl,
475
+ defaultChainId: chainId
476
+ });
477
+ }, [chainId, symmCoreConfig.apiUrl, symmCoreConfig.wsUrl]);
478
+ const value = useMemo(
479
+ () => ({
480
+ symmCoreClient,
481
+ chainId,
482
+ address,
483
+ symmioConfig
484
+ }),
485
+ [symmCoreClient, chainId, address, symmioConfig]
486
+ );
487
+ useBinanceWs({
488
+ symmCoreClient,
489
+ chainId
490
+ });
491
+ return /* @__PURE__ */ jsx(SymmContext.Provider, { value, children });
492
+ }
493
+
494
+ // src/react/hooks/use-symm-core-client.ts
495
+ function useSymmCoreClient() {
496
+ return useSymmContext().symmCoreClient;
497
+ }
498
+
499
+ // src/constants/defaults.ts
500
+ var GAS_MARGIN_PERCENTAGE = 20n;
501
+ var MUON_BASE_URLS = [
502
+ "https://muon-oracle1.rasa.capital/v1/",
503
+ "https://muon-oracle2.rasa.capital/v1/",
504
+ "https://muon-oracle3.rasa.capital/v1/",
505
+ "https://muon-oracle4.rasa.capital/v1/"
506
+ ];
507
+ var MUON_APP_NAME = "symmio";
508
+ var MUON_REQUEST_TIMEOUT = 15e3;
509
+ var HEDGER_BASE_URLS = {
510
+ [42161 /* ARBITRUM */]: "https://www.perps-streaming.com/v1/42161a/0x6273242a7E88b3De90822b31648C212215caaFE4",
511
+ [8453 /* BASE */]: "https://www.perps-streaming.com/v1/8453a/0x6273242a7E88b3De90822b31648C212215caaFE4"
512
+ };
513
+
514
+ // src/actions/instant.ts
515
+ function getHedgerBaseUrl(chainId) {
516
+ const baseUrl = HEDGER_BASE_URLS[chainId];
517
+ if (!baseUrl) {
518
+ throw new Error(`No hedger base URL configured for chain ${chainId}.`);
519
+ }
520
+ return baseUrl;
521
+ }
522
+ function createSiweMessage(params) {
523
+ const version = params.version ?? "1";
524
+ const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
525
+ const expirationTime = new Date(
526
+ Date.now() + 30 * 24 * 60 * 60 * 1e3
527
+ ).toISOString();
528
+ const message = [
529
+ `${params.domain} wants you to sign in with your Ethereum account:`,
530
+ params.address,
531
+ "",
532
+ params.statement,
533
+ "",
534
+ `URI: ${params.uri}`,
535
+ `Version: ${version}`,
536
+ `Chain ID: ${params.chainId}`,
537
+ `Nonce: ${params.nonce}`,
538
+ `Issued At: ${issuedAt}`,
539
+ `Expiration Time: ${expirationTime}`
540
+ ].join("\n");
541
+ return { message, issuedAt, expirationTime };
542
+ }
543
+ async function getNonce(chainId, subAccount) {
544
+ const hedgerBaseUrl = getHedgerBaseUrl(chainId);
545
+ const url = new URL(`nonce/${subAccount}`, hedgerBaseUrl).href;
546
+ const response = await fetch(url);
547
+ if (!response.ok) {
548
+ throw new Error(`Failed to fetch nonce: ${response.statusText}`);
549
+ }
550
+ const data = await response.json();
551
+ return data.nonce;
552
+ }
553
+ async function login(chainId, params) {
554
+ const hedgerBaseUrl = getHedgerBaseUrl(chainId);
555
+ const url = new URL("login", hedgerBaseUrl).href;
556
+ const body = {
557
+ account_address: params.accountAddress,
558
+ expiration_time: params.expirationTime,
559
+ issued_at: params.issuedAt,
560
+ signature: params.signature,
561
+ nonce: params.nonce
562
+ };
563
+ const response = await fetch(url, {
564
+ method: "POST",
565
+ headers: { "Content-Type": "application/json" },
566
+ body: JSON.stringify(body)
567
+ });
568
+ if (!response.ok) {
569
+ const errorData = await response.json().catch(() => null);
570
+ throw new Error(
571
+ errorData?.error_message ?? `Login failed: ${response.statusText}`
572
+ );
573
+ }
574
+ const data = await response.json();
575
+ if (!data.access_token) {
576
+ throw new Error("No access token received");
577
+ }
578
+ return {
579
+ accessToken: data.access_token,
580
+ expirationTime: params.expirationTime,
581
+ issuedAt: params.issuedAt
582
+ };
583
+ }
584
+
585
+ // src/react/auth.ts
586
+ var TOKEN_STORAGE_PREFIX = "symm_access_token";
587
+ var TOKEN_STORAGE_VERSION = 1;
588
+ var tokenCache = /* @__PURE__ */ new Map();
589
+ function cacheKey(address, chainId) {
590
+ return `${address}:${chainId}`;
591
+ }
592
+ function storageKey(address, chainId) {
593
+ return `${TOKEN_STORAGE_PREFIX}:${TOKEN_STORAGE_VERSION}:${cacheKey(address, chainId)}`;
594
+ }
595
+ function readStoredToken(address, chainId) {
596
+ if (typeof window === "undefined") return null;
597
+ try {
598
+ const raw = window.localStorage.getItem(storageKey(address, chainId));
599
+ if (!raw) return null;
600
+ const parsed = JSON.parse(raw);
601
+ if (!parsed || typeof parsed.token !== "string" || typeof parsed.expiresAt !== "number") {
602
+ window.localStorage.removeItem(storageKey(address, chainId));
603
+ return null;
604
+ }
605
+ if (Date.now() >= parsed.expiresAt) {
606
+ window.localStorage.removeItem(storageKey(address, chainId));
607
+ return null;
608
+ }
609
+ return parsed;
610
+ } catch {
611
+ window.localStorage.removeItem(storageKey(address, chainId));
612
+ return null;
613
+ }
614
+ }
615
+ function writeStoredToken(address, chainId, cached) {
616
+ if (typeof window === "undefined") return;
617
+ try {
618
+ window.localStorage.setItem(
619
+ storageKey(address, chainId),
620
+ JSON.stringify(cached)
621
+ );
622
+ } catch {
623
+ }
624
+ }
625
+ function getCachedToken(address, chainId) {
626
+ const cached = tokenCache.get(cacheKey(address, chainId));
627
+ if (cached && Date.now() < cached.expiresAt) {
628
+ return cached.token;
629
+ }
630
+ if (cached) {
631
+ tokenCache.delete(cacheKey(address, chainId));
632
+ }
633
+ const stored = readStoredToken(address, chainId);
634
+ if (!stored) return null;
635
+ tokenCache.set(cacheKey(address, chainId), stored);
636
+ return stored.token;
637
+ }
638
+ function clearCachedToken(address, chainId) {
639
+ tokenCache.delete(cacheKey(address, chainId));
640
+ if (typeof window !== "undefined") {
641
+ window.localStorage.removeItem(storageKey(address, chainId));
642
+ }
643
+ }
644
+ async function fetchAccessToken(walletClient, signerAddress, accountAddress, chainId, domain) {
645
+ const resolvedDomain = domain ?? (typeof window !== "undefined" ? window.location.host : "localhost");
646
+ const uri = typeof window !== "undefined" ? window.location.origin : `https://${resolvedDomain}`;
647
+ const nonce = await getNonce(chainId, accountAddress);
648
+ const { message, issuedAt, expirationTime } = createSiweMessage({
649
+ address: signerAddress,
650
+ statement: "Sign in to Symm Protocol",
651
+ chainId,
652
+ nonce,
653
+ domain: resolvedDomain,
654
+ uri
655
+ });
656
+ const signature = await walletClient.signMessage({
657
+ account: signerAddress,
658
+ message
659
+ });
660
+ const { accessToken } = await login(chainId, {
661
+ accountAddress,
662
+ signature,
663
+ expirationTime,
664
+ issuedAt,
665
+ nonce
666
+ });
667
+ const expiresAt = new Date(expirationTime).getTime();
668
+ const cachedToken = {
669
+ token: accessToken,
670
+ expiresAt: expiresAt - 6e4
671
+ };
672
+ tokenCache.set(cacheKey(accountAddress, chainId), cachedToken);
673
+ writeStoredToken(accountAddress, chainId, cachedToken);
674
+ return accessToken;
675
+ }
676
+ function symmAuthTokenKey(address, chainId) {
677
+ return `${address.toLowerCase()}:${chainId}`;
678
+ }
679
+ var useSymmAuthStore = create((set, get) => ({
680
+ tokensByKey: {},
681
+ setToken: (address, chainId, token) => {
682
+ const key = symmAuthTokenKey(address, chainId);
683
+ set((state) => ({
684
+ tokensByKey: {
685
+ ...state.tokensByKey,
686
+ [key]: token
687
+ }
688
+ }));
689
+ },
690
+ getToken: (address, chainId) => {
691
+ const key = symmAuthTokenKey(address, chainId);
692
+ return get().tokensByKey[key] ?? null;
693
+ },
694
+ clearToken: (address, chainId) => {
695
+ const key = symmAuthTokenKey(address, chainId);
696
+ set((state) => {
697
+ if (!(key in state.tokensByKey)) return state;
698
+ const next = { ...state.tokensByKey };
699
+ delete next[key];
700
+ return { tokensByKey: next };
701
+ });
702
+ },
703
+ clearAll: () => {
704
+ set({ tokensByKey: {} });
705
+ }
706
+ }));
707
+
708
+ // src/react/hooks/use-symm-auth.ts
709
+ function useSymmAuth(params) {
710
+ const ctx = useSymmContext();
711
+ const address = params?.address ?? ctx.address;
712
+ const chainId = params?.chainId ?? ctx.chainId ?? 42161;
713
+ const walletClient = params?.walletClient ?? ctx.walletClient;
714
+ const siweDomain = params?.siweDomain;
715
+ const accessToken = useSymmAuthStore((state) => {
716
+ if (address) {
717
+ return state.tokensByKey[symmAuthTokenKey(address, chainId)] ?? null;
718
+ }
719
+ return ctx.accessToken ?? ctx.authToken ?? null;
720
+ });
721
+ const setToken = useSymmAuthStore((state) => state.setToken);
722
+ const getToken = useSymmAuthStore((state) => state.getToken);
723
+ const clearToken = useSymmAuthStore((state) => state.clearToken);
724
+ const previousAddressRef = useRef(address);
725
+ const previousChainIdRef = useRef(chainId);
726
+ const refreshAuth = useCallback(
727
+ async (accountAddress) => {
728
+ if (!walletClient || !address) return null;
729
+ const resolvedAccountAddress = accountAddress ?? address;
730
+ const inMemory = getToken(resolvedAccountAddress, chainId);
731
+ if (inMemory) {
732
+ return inMemory;
733
+ }
734
+ const cached = getCachedToken(resolvedAccountAddress, chainId);
735
+ if (cached) {
736
+ setToken(resolvedAccountAddress, chainId, cached);
737
+ return cached;
738
+ }
739
+ try {
740
+ const token = await fetchAccessToken(
741
+ walletClient,
742
+ address,
743
+ resolvedAccountAddress,
744
+ chainId,
745
+ siweDomain
746
+ );
747
+ setToken(resolvedAccountAddress, chainId, token);
748
+ return token;
749
+ } catch {
750
+ clearToken(resolvedAccountAddress, chainId);
751
+ return null;
752
+ }
753
+ },
754
+ [walletClient, address, chainId, siweDomain, getToken, setToken, clearToken]
755
+ );
756
+ const clearAuth = useCallback(() => {
757
+ if (address) {
758
+ clearCachedToken(address, chainId);
759
+ clearToken(address, chainId);
760
+ }
761
+ }, [address, chainId, clearToken]);
762
+ useEffect(() => {
763
+ const previousAddress = previousAddressRef.current;
764
+ const previousChainId = previousChainIdRef.current;
765
+ const addressChanged = previousAddress !== address;
766
+ const chainChanged = previousChainId !== chainId;
767
+ previousAddressRef.current = address;
768
+ previousChainIdRef.current = chainId;
769
+ if (!address || !walletClient) {
770
+ return;
771
+ }
772
+ if (previousAddress && (addressChanged || chainChanged)) {
773
+ clearCachedToken(previousAddress, previousChainId);
774
+ clearToken(previousAddress, previousChainId);
775
+ }
776
+ const cached = getCachedToken(address, chainId);
777
+ if (cached) {
778
+ setToken(address, chainId, cached);
779
+ } else {
780
+ clearToken(address, chainId);
781
+ }
782
+ }, [address, walletClient, chainId, setToken, clearToken]);
783
+ return {
784
+ accessToken,
785
+ authToken: accessToken,
786
+ isAuthenticated: !!accessToken,
787
+ refresh: refreshAuth,
788
+ refreshAuth,
789
+ clear: clearAuth
790
+ };
791
+ }
792
+
793
+ // src/constants/selectors.ts
794
+ var SEND_QUOTE_WITH_AFFILIATE_SELECTOR = "0x40f1310c";
795
+ var CLOSE_QUOTE_SELECTOR = "0x501e891f";
796
+ var ALL_TRADING_SELECTORS = [
797
+ SEND_QUOTE_WITH_AFFILIATE_SELECTOR,
798
+ CLOSE_QUOTE_SELECTOR
799
+ ];
800
+
801
+ // src/constants/addresses.ts
802
+ var MULTI_ACCOUNT_ADDRESS = {
803
+ [42161 /* ARBITRUM */]: "0x6273242a7E88b3De90822b31648C212215caaFE4",
804
+ [8453 /* BASE */]: "0xE43166cE17d3511B09438a359dAa53513225101D"
805
+ };
806
+ var SYMMIO_DIAMOND_ADDRESS = {
359
807
  [42161 /* ARBITRUM */]: "0x8F06459f184553e5d04F07F868720BDaCAB39395",
360
808
  [8453 /* BASE */]: "0x91Cf2D8Ed503EC52768999aA6D8DBeA6e52dbe43"
361
809
  };
@@ -24525,854 +24973,535 @@ function useSymmAccountSummary(params) {
24525
24973
  enabled: internalEnabled && (params.query?.enabled ?? true)
24526
24974
  });
24527
24975
  }
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
24708
- });
24709
- }
24710
- function useSymmOpenOrders(params) {
24976
+ function useSymmAccountData(params) {
24711
24977
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24712
- const { accountAddress, address } = params;
24713
- const resolvedAddress = accountAddress ? void 0 : address;
24978
+ const { address, upnl } = params;
24714
24979
  const chainId = params.chainId ?? ctxChainId;
24715
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24980
+ const internalEnabled = !!symmCoreClient && !!address;
24716
24981
  return useQuery({
24717
24982
  ...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
24983
+ queryKey: symmKeys.accountData(address, chainId, upnl),
24984
+ queryFn: () => symmCoreClient.accounts.getData({
24985
+ address,
24986
+ chainId,
24987
+ upnl
24726
24988
  }),
24727
24989
  enabled: internalEnabled && (params.query?.enabled ?? true)
24728
24990
  });
24729
24991
  }
24730
- function useSymmTradeHistory(params) {
24992
+ function useSymmBalances(params) {
24731
24993
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24732
- const { accountAddress, address } = params;
24733
- const resolvedAddress = accountAddress ? void 0 : address;
24994
+ const { userAddress, multiAccountAddress } = params;
24734
24995
  const chainId = params.chainId ?? ctxChainId;
24735
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24996
+ const internalEnabled = !!symmCoreClient && !!userAddress;
24736
24997
  return useQuery({
24737
24998
  ...params.query,
24738
- queryKey: symmKeys.tradeHistory({
24739
- accountAddress,
24740
- address: resolvedAddress,
24741
- chainId
24999
+ queryKey: symmKeys.balances(userAddress, chainId),
25000
+ queryFn: () => symmCoreClient.accounts.getBalanceInfo({
25001
+ userAddress,
25002
+ chainId,
25003
+ multiAccountAddress
24742
25004
  }),
24743
- queryFn: () => {
24744
- const request = {
24745
- accountAddress,
24746
- address: resolvedAddress,
24747
- chainId
24748
- };
24749
- return symmCoreClient.positions.getTradeHistory(request);
24750
- },
24751
25005
  enabled: internalEnabled && (params.query?.enabled ?? true)
24752
25006
  });
24753
25007
  }
24754
- function useSymmSetTpslMutation(options) {
25008
+ function useResolveTradeAuthToken() {
25009
+ const context = useSymmContext();
25010
+ const { address, chainId } = context;
25011
+ const { refreshAuth } = useSymmAuth({ address, chainId });
25012
+ const refreshAuthFromContext = context.refreshAuth;
25013
+ return useCallback(
25014
+ async (providedAuthToken, accountAddress) => {
25015
+ if (providedAuthToken) {
25016
+ return providedAuthToken;
25017
+ }
25018
+ const resolvedAccountAddress = accountAddress ?? address;
25019
+ if (!resolvedAccountAddress) {
25020
+ return null;
25021
+ }
25022
+ const inMemoryToken = useSymmAuthStore.getState().getToken(resolvedAccountAddress, chainId);
25023
+ if (inMemoryToken) {
25024
+ return inMemoryToken;
25025
+ }
25026
+ if (refreshAuthFromContext) {
25027
+ return refreshAuthFromContext(resolvedAccountAddress);
25028
+ }
25029
+ return refreshAuth(resolvedAccountAddress);
25030
+ },
25031
+ [address, chainId, refreshAuth, refreshAuthFromContext]
25032
+ );
25033
+ }
25034
+ function useSymmOpenBasketMutation(options) {
24755
25035
  const { symmCoreClient } = useSymmContext();
24756
25036
  const queryClient = useQueryClient();
24757
25037
  return useMutation({
24758
25038
  ...withSymmMutationConfig(options?.mutation, {
24759
25039
  onSuccess: () => {
24760
- invalidateOrders(queryClient);
24761
25040
  invalidatePositions(queryClient);
24762
25041
  }
24763
25042
  }),
24764
25043
  mutationFn: async (request) => {
24765
25044
  if (!symmCoreClient) throw new Error("symm-core client not available");
24766
- return symmCoreClient.orders.setTpsl(request);
25045
+ return symmCoreClient.positions.openBasket(request);
24767
25046
  }
24768
25047
  });
24769
25048
  }
24770
- function useSymmCancelTpslMutation(options) {
25049
+ function useSymmClosePositionMutation(options) {
24771
25050
  const { symmCoreClient } = useSymmContext();
24772
25051
  const queryClient = useQueryClient();
25052
+ const resolveAuthToken = useResolveTradeAuthToken();
24773
25053
  return useMutation({
24774
25054
  ...withSymmMutationConfig(options?.mutation, {
24775
25055
  onSuccess: () => {
24776
- invalidateOrders(queryClient);
24777
25056
  invalidatePositions(queryClient);
24778
25057
  }
24779
25058
  }),
24780
25059
  mutationFn: async (request) => {
24781
25060
  if (!symmCoreClient) throw new Error("symm-core client not available");
24782
- return symmCoreClient.orders.cancelTpsl(request);
25061
+ const typedRequest = request;
25062
+ const authToken = await resolveAuthToken(
25063
+ typedRequest.authToken,
25064
+ typedRequest.accountAddress
25065
+ );
25066
+ if (!authToken) {
25067
+ throw new Error("auth token is required to close a position");
25068
+ }
25069
+ return symmCoreClient.positions.close({
25070
+ ...request,
25071
+ authToken
25072
+ });
24783
25073
  }
24784
25074
  });
24785
25075
  }
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) {
25076
+ function useSymmCloseAllPositionsMutation(options) {
24834
25077
  const { symmCoreClient } = useSymmContext();
24835
25078
  const queryClient = useQueryClient();
24836
25079
  return useMutation({
24837
25080
  ...withSymmMutationConfig(options?.mutation, {
24838
25081
  onSuccess: () => {
24839
- invalidateOrders(queryClient);
25082
+ invalidatePositions(queryClient);
24840
25083
  }
24841
25084
  }),
24842
- mutationFn: async (orderId) => {
25085
+ mutationFn: async (request) => {
24843
25086
  if (!symmCoreClient) throw new Error("symm-core client not available");
24844
- return symmCoreClient.orders.cancelTwapOrder(orderId);
25087
+ return symmCoreClient.positions.closeAll(request);
24845
25088
  }
24846
25089
  });
24847
25090
  }
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) {
25091
+ function useSymmCancelOpenMutation(options) {
24860
25092
  const { symmCoreClient } = useSymmContext();
24861
- const { orderId } = params;
24862
25093
  const queryClient = useQueryClient();
24863
25094
  return useMutation({
24864
25095
  ...withSymmMutationConfig(options?.mutation, {
24865
25096
  onSuccess: () => {
24866
- queryClient.invalidateQueries({
24867
- queryKey: symmKeys.triggerConfig(orderId)
24868
- });
24869
- invalidateOrders(queryClient);
25097
+ invalidatePositions(queryClient);
24870
25098
  }
24871
25099
  }),
24872
25100
  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);
25101
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25102
+ return symmCoreClient.positions.cancelOpen(request);
24877
25103
  }
24878
25104
  });
24879
25105
  }
24880
- function useSymmClearTriggerConfigMutation(params, options) {
25106
+ function useSymmUpdatePositionMutation(options) {
24881
25107
  const { symmCoreClient } = useSymmContext();
24882
- const { orderId } = params;
24883
25108
  const queryClient = useQueryClient();
25109
+ const resolveAuthToken = useResolveTradeAuthToken();
24884
25110
  return useMutation({
24885
25111
  ...withSymmMutationConfig(options?.mutation, {
24886
25112
  onSuccess: () => {
24887
- queryClient.invalidateQueries({
24888
- queryKey: symmKeys.triggerConfig(orderId)
24889
- });
24890
- invalidateOrders(queryClient);
25113
+ invalidatePositions(queryClient);
24891
25114
  }
24892
25115
  }),
24893
- mutationFn: async () => {
24894
- if (!symmCoreClient || !orderId) {
24895
- throw new Error("symm-core client or orderId not available");
25116
+ mutationFn: async ({
25117
+ positionId,
25118
+ request
25119
+ }) => {
25120
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25121
+ const typedRequest = request;
25122
+ const authToken = await resolveAuthToken(
25123
+ typedRequest.authToken,
25124
+ typedRequest.accountAddress
25125
+ );
25126
+ if (!authToken) {
25127
+ throw new Error("auth token is required to update a position");
24896
25128
  }
24897
- return symmCoreClient.orders.clearTriggerConfig(orderId);
25129
+ return symmCoreClient.positions.update(positionId, {
25130
+ ...request,
25131
+ authToken
25132
+ });
24898
25133
  }
24899
25134
  });
24900
25135
  }
24901
- function useSymmTriggerOrders(params) {
24902
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24903
- const { accountAddress, address, status, limit, offset } = params;
24904
- const resolvedAddress = accountAddress ? void 0 : address;
24905
- const chainId = params.chainId ?? ctxChainId;
24906
- const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24907
- const request = {
24908
- address: accountAddress ?? resolvedAddress,
24909
- chainId,
24910
- status,
24911
- limit,
24912
- offset
24913
- };
24914
- return useQuery({
24915
- ...params.query,
24916
- queryKey: symmKeys.triggerOrders({
24917
- accountAddress,
24918
- address: resolvedAddress,
24919
- chainId,
24920
- status,
24921
- limit,
24922
- offset
24923
- }),
24924
- queryFn: () => symmCoreClient.orders.listTriggerOrders(request),
24925
- enabled: internalEnabled && (params.query?.enabled ?? true)
24926
- });
24927
- }
24928
- function useSymmMarkets(params) {
24929
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24930
- const chainId = params?.chainId ?? ctxChainId;
24931
- const searchText = params?.searchText;
24932
- const internalEnabled = !!symmCoreClient;
24933
- return useQuery({
24934
- ...params?.query,
24935
- queryKey: symmKeys.markets(chainId, searchText),
24936
- queryFn: () => {
24937
- if (searchText) {
24938
- return symmCoreClient.markets.search(searchText, { chainId });
24939
- }
24940
- return symmCoreClient.markets.list({ pageSize: "1000", chainId });
24941
- },
24942
- enabled: internalEnabled && (params?.query?.enabled ?? true)
24943
- });
24944
- }
24945
- function useSymmHedgerMarketById(params) {
24946
- const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24947
- const { id } = params;
24948
- const chainId = params.chainId ?? ctxChainId;
24949
- const internalEnabled = !!symmCoreClient && id != null;
24950
- return useQuery({
24951
- ...params.query,
24952
- queryKey: symmKeys.hedgerMarketById(id, chainId),
24953
- queryFn: () => symmCoreClient.markets.getById(id, chainId),
24954
- enabled: internalEnabled && (params.query?.enabled ?? true)
24955
- });
24956
- }
24957
- function useSymmHedgerMarketBySymbol(params) {
25136
+ function useSymmPositions(params) {
24958
25137
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24959
- const { symbol } = params;
25138
+ const { accountAddress, address } = params;
25139
+ const resolvedAddress = accountAddress ? void 0 : address;
24960
25140
  const chainId = params.chainId ?? ctxChainId;
24961
- const internalEnabled = !!symmCoreClient && !!symbol;
25141
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25142
+ const enabled = internalEnabled && (params.query?.enabled ?? true);
24962
25143
  return useQuery({
24963
25144
  ...params.query,
24964
- queryKey: symmKeys.hedgerMarketBySymbol(symbol, chainId),
24965
- queryFn: () => symmCoreClient.markets.getBySymbol(symbol, chainId),
24966
- enabled: internalEnabled && (params.query?.enabled ?? true)
25145
+ queryKey: symmKeys.positions({
25146
+ accountAddress,
25147
+ address: resolvedAddress,
25148
+ chainId
25149
+ }),
25150
+ queryFn: () => symmCoreClient.positions.getOpen({
25151
+ accountAddress,
25152
+ address: resolvedAddress,
25153
+ chainId
25154
+ }),
25155
+ enabled
24967
25156
  });
24968
25157
  }
24969
- function useSymmLockedParams(params) {
25158
+ function useSymmOpenOrders(params) {
24970
25159
  const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
24971
- const { marketName, leverage } = params;
25160
+ const { accountAddress, address } = params;
25161
+ const resolvedAddress = accountAddress ? void 0 : address;
24972
25162
  const chainId = params.chainId ?? ctxChainId;
24973
- const internalEnabled = !!symmCoreClient && !!marketName && leverage != null;
25163
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
24974
25164
  return useQuery({
24975
25165
  ...params.query,
24976
- queryKey: symmKeys.lockedParams(marketName, leverage, chainId),
24977
- queryFn: () => symmCoreClient.markets.getLockedParams({
24978
- marketName,
24979
- leverage,
25166
+ queryKey: symmKeys.openOrders({
25167
+ accountAddress,
25168
+ address: resolvedAddress,
24980
25169
  chainId
24981
25170
  }),
24982
- 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
- async function fetch24hrTickers() {
25042
- const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/ticker/24hr`);
25043
- if (!response.ok) {
25044
- throw new Error(`Binance 24hr tickers failed: ${response.status}`);
25045
- }
25046
- const data = await response.json();
25047
- const result = {};
25048
- for (const item of data) {
25049
- result[item.symbol] = {
25050
- lastPrice: parseFloat(item.lastPrice),
25051
- prevClosePrice: parseFloat(item.prevClosePrice),
25052
- priceChangePercent: parseFloat(item.priceChangePercent)
25053
- };
25054
- }
25055
- return result;
25056
- }
25057
-
25058
- // src/utils/binance-symbol-map.ts
25059
- var SYMBOL_OVERRIDES = {
25060
- // Add overrides here as needed for SYMM markets that don't map 1:1 to Binance
25061
- // e.g., 'SOME_SYMM_SYMBOL': 'BINANCE_SYMBOL',
25062
- };
25063
- var UNSUPPORTED_SYMBOLS = /* @__PURE__ */ new Set([
25064
- // Add symbols here that have no Binance equivalent
25065
- ]);
25066
- function resolveBinanceSymbol(symmSymbol) {
25067
- if (!symmSymbol || !symmSymbol.trim()) {
25068
- return {
25069
- symmSymbol,
25070
- normalizedSymbol: "",
25071
- binanceSymbol: null,
25072
- supported: false,
25073
- reason: "missing_symbol"
25074
- };
25075
- }
25076
- const normalized = symmSymbol.toUpperCase().trim();
25077
- if (!/^[A-Z0-9]+$/.test(normalized)) {
25078
- return {
25079
- symmSymbol,
25080
- normalizedSymbol: normalized,
25081
- binanceSymbol: null,
25082
- supported: false,
25083
- reason: "invalid_symbol"
25084
- };
25085
- }
25086
- if (UNSUPPORTED_SYMBOLS.has(normalized)) {
25087
- return {
25088
- symmSymbol,
25089
- normalizedSymbol: normalized,
25090
- binanceSymbol: null,
25091
- supported: false,
25092
- reason: "unsupported_symbol"
25093
- };
25094
- }
25095
- const binanceSymbol = SYMBOL_OVERRIDES[normalized] ?? (normalized.endsWith("USDT") ? normalized : `${normalized}USDT`);
25096
- return {
25097
- symmSymbol,
25098
- normalizedSymbol: normalized,
25099
- binanceSymbol,
25100
- supported: true,
25101
- reason: null
25102
- };
25103
- }
25104
- function getUnsupportedBinanceSymbols(symbols) {
25105
- return symbols.filter((symbol) => !resolveBinanceSymbol(symbol).supported);
25106
- }
25107
- function toBinanceSymbol(symmSymbol) {
25108
- return resolveBinanceSymbol(symmSymbol).binanceSymbol;
25109
- }
25110
-
25111
- // src/utils/binance-ws.ts
25112
- var BINANCE_WS_URL = "wss://fstream.binance.com/ws";
25113
- var RECONNECT_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
25114
- var BinanceWsManager = class {
25115
- ws = null;
25116
- streams = /* @__PURE__ */ new Map();
25117
- reconnectAttempt = 0;
25118
- reconnectTimer = null;
25119
- intentionalClose = false;
25120
- pendingSubscribes = [];
25121
- idCounter = 0;
25122
- /**
25123
- * Subscribe to a kline stream. Returns an unsubscribe function.
25124
- */
25125
- subscribeKline(symbol, interval, cb) {
25126
- const streamName = `${symbol.toLowerCase()}@kline_${interval}`;
25127
- const id = this.generateId();
25128
- const wrappedCb = (raw) => {
25129
- const k = raw.k;
25130
- if (!k) return;
25131
- cb({
25132
- symbol: raw.s,
25133
- interval: k.i,
25134
- openTime: k.t,
25135
- closeTime: k.T,
25136
- open: parseFloat(k.o),
25137
- high: parseFloat(k.h),
25138
- low: parseFloat(k.l),
25139
- close: parseFloat(k.c),
25140
- volume: parseFloat(k.v),
25141
- isFinal: k.x
25142
- });
25143
- };
25144
- this.addStreamCallback(streamName, id, wrappedCb);
25145
- return () => this.removeStreamCallback(streamName, id);
25146
- }
25147
- /**
25148
- * Subscribe to a mark price stream (1s updates). Returns an unsubscribe function.
25149
- */
25150
- subscribeMarkPrice(symbol, cb) {
25151
- const streamName = `${symbol.toLowerCase()}@markPrice@1s`;
25152
- const id = this.generateId();
25153
- const wrappedCb = (raw) => {
25154
- cb({
25155
- symbol: raw.s,
25156
- markPrice: parseFloat(raw.p),
25157
- indexPrice: parseFloat(raw.i),
25158
- time: raw.E
25159
- });
25160
- };
25161
- this.addStreamCallback(streamName, id, wrappedCb);
25162
- return () => this.removeStreamCallback(streamName, id);
25163
- }
25164
- /**
25165
- * Destroy the manager and close the connection.
25166
- */
25167
- destroy() {
25168
- this.intentionalClose = true;
25169
- if (this.reconnectTimer) {
25170
- clearTimeout(this.reconnectTimer);
25171
- this.reconnectTimer = null;
25172
- }
25173
- if (this.ws) {
25174
- this.ws.close();
25175
- this.ws = null;
25176
- }
25177
- this.streams.clear();
25178
- }
25179
- // --- Private ---
25180
- generateId() {
25181
- return `sub_${++this.idCounter}_${Date.now()}`;
25182
- }
25183
- addStreamCallback(streamName, id, cb) {
25184
- let sub = this.streams.get(streamName);
25185
- const isNew = !sub;
25186
- if (!sub) {
25187
- sub = { callbacks: /* @__PURE__ */ new Map() };
25188
- this.streams.set(streamName, sub);
25189
- }
25190
- sub.callbacks.set(id, cb);
25191
- if (isNew) {
25192
- this.ensureConnected();
25193
- this.sendSubscribe([streamName]);
25194
- }
25195
- }
25196
- removeStreamCallback(streamName, id) {
25197
- const sub = this.streams.get(streamName);
25198
- if (!sub) return;
25199
- sub.callbacks.delete(id);
25200
- if (sub.callbacks.size === 0) {
25201
- this.streams.delete(streamName);
25202
- this.sendUnsubscribe([streamName]);
25203
- if (this.streams.size === 0 && this.ws) {
25204
- this.intentionalClose = true;
25205
- this.ws.close();
25206
- this.ws = null;
25207
- this.intentionalClose = false;
25171
+ queryFn: () => symmCoreClient.orders.list({
25172
+ address: accountAddress ?? resolvedAddress,
25173
+ chainId
25174
+ }),
25175
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25176
+ });
25177
+ }
25178
+ function useSymmTradeHistory(params) {
25179
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25180
+ const { accountAddress, address } = params;
25181
+ const resolvedAddress = accountAddress ? void 0 : address;
25182
+ const chainId = params.chainId ?? ctxChainId;
25183
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25184
+ return useQuery({
25185
+ ...params.query,
25186
+ queryKey: symmKeys.tradeHistory({
25187
+ accountAddress,
25188
+ address: resolvedAddress,
25189
+ chainId
25190
+ }),
25191
+ queryFn: () => {
25192
+ const request = {
25193
+ accountAddress,
25194
+ address: resolvedAddress,
25195
+ chainId
25196
+ };
25197
+ return symmCoreClient.positions.getTradeHistory(request);
25198
+ },
25199
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25200
+ });
25201
+ }
25202
+ function useSymmSetTpslMutation(options) {
25203
+ const { symmCoreClient } = useSymmContext();
25204
+ const queryClient = useQueryClient();
25205
+ return useMutation({
25206
+ ...withSymmMutationConfig(options?.mutation, {
25207
+ onSuccess: () => {
25208
+ invalidateOrders(queryClient);
25209
+ invalidatePositions(queryClient);
25208
25210
  }
25211
+ }),
25212
+ mutationFn: async (request) => {
25213
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25214
+ return symmCoreClient.orders.setTpsl(request);
25209
25215
  }
25210
- }
25211
- ensureConnected() {
25212
- if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
25213
- return;
25216
+ });
25217
+ }
25218
+ function useSymmCancelTpslMutation(options) {
25219
+ const { symmCoreClient } = useSymmContext();
25220
+ const queryClient = useQueryClient();
25221
+ return useMutation({
25222
+ ...withSymmMutationConfig(options?.mutation, {
25223
+ onSuccess: () => {
25224
+ invalidateOrders(queryClient);
25225
+ invalidatePositions(queryClient);
25226
+ }
25227
+ }),
25228
+ mutationFn: async (request) => {
25229
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25230
+ return symmCoreClient.orders.cancelTpsl(request);
25214
25231
  }
25215
- this.connect();
25216
- }
25217
- connect() {
25218
- if (typeof WebSocket === "undefined") return;
25219
- this.intentionalClose = false;
25220
- this.ws = new WebSocket(BINANCE_WS_URL);
25221
- this.ws.onopen = () => {
25222
- this.reconnectAttempt = 0;
25223
- const activeStreams = Array.from(this.streams.keys());
25224
- if (activeStreams.length > 0) {
25225
- this.sendSubscribe(activeStreams);
25232
+ });
25233
+ }
25234
+ function useSymmTpslOrders(params) {
25235
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25236
+ const { accountAddress, address, positionId, type, status } = params;
25237
+ const resolvedAddress = accountAddress ? void 0 : address;
25238
+ const chainId = params.chainId ?? ctxChainId;
25239
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress || positionId);
25240
+ const request = {
25241
+ address: accountAddress ?? resolvedAddress,
25242
+ positionId,
25243
+ type,
25244
+ status,
25245
+ chainId
25246
+ };
25247
+ return useQuery({
25248
+ ...params.query,
25249
+ queryKey: symmKeys.tpslOrdersList({
25250
+ accountAddress,
25251
+ address: resolvedAddress,
25252
+ positionId,
25253
+ type,
25254
+ status,
25255
+ chainId
25256
+ }),
25257
+ queryFn: () => symmCoreClient.orders.getTpslOrders(request),
25258
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25259
+ });
25260
+ }
25261
+ function useResolvedTwapParams(params) {
25262
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25263
+ const { accountAddress, address } = params;
25264
+ const resolvedAddress = accountAddress ? void 0 : address;
25265
+ const chainId = params.chainId ?? ctxChainId;
25266
+ return { symmCoreClient, accountAddress, resolvedAddress, chainId, query: params.query };
25267
+ }
25268
+ function useSymmTwapOrdersQuery(params) {
25269
+ const { symmCoreClient, accountAddress, resolvedAddress, chainId, query } = useResolvedTwapParams(params);
25270
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25271
+ return useQuery({
25272
+ ...query,
25273
+ queryKey: symmKeys.twapOrders(accountAddress ?? resolvedAddress, chainId),
25274
+ queryFn: () => symmCoreClient.orders.getTwapOrders({
25275
+ address: accountAddress ?? resolvedAddress,
25276
+ chainId
25277
+ }),
25278
+ enabled: internalEnabled && (query?.enabled ?? true)
25279
+ });
25280
+ }
25281
+ function useSymmCancelTwapOrderMutation(options) {
25282
+ const { symmCoreClient } = useSymmContext();
25283
+ const queryClient = useQueryClient();
25284
+ return useMutation({
25285
+ ...withSymmMutationConfig(options?.mutation, {
25286
+ onSuccess: () => {
25287
+ invalidateOrders(queryClient);
25226
25288
  }
25227
- if (this.pendingSubscribes.length > 0) {
25228
- this.sendSubscribe(this.pendingSubscribes);
25229
- this.pendingSubscribes = [];
25289
+ }),
25290
+ mutationFn: async (orderId) => {
25291
+ if (!symmCoreClient) throw new Error("symm-core client not available");
25292
+ return symmCoreClient.orders.cancelTwapOrder(orderId);
25293
+ }
25294
+ });
25295
+ }
25296
+ function useSymmTriggerConfigQuery(params) {
25297
+ const { symmCoreClient } = useSymmContext();
25298
+ const { orderId } = params;
25299
+ const internalEnabled = !!symmCoreClient && !!orderId;
25300
+ return useQuery({
25301
+ ...params.query,
25302
+ queryKey: symmKeys.triggerConfig(orderId),
25303
+ queryFn: () => symmCoreClient.orders.getTriggerConfig(orderId),
25304
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25305
+ });
25306
+ }
25307
+ function useSymmSetTriggerConfigMutation(params, options) {
25308
+ const { symmCoreClient } = useSymmContext();
25309
+ const { orderId } = params;
25310
+ const queryClient = useQueryClient();
25311
+ return useMutation({
25312
+ ...withSymmMutationConfig(options?.mutation, {
25313
+ onSuccess: () => {
25314
+ queryClient.invalidateQueries({
25315
+ queryKey: symmKeys.triggerConfig(orderId)
25316
+ });
25317
+ invalidateOrders(queryClient);
25230
25318
  }
25231
- };
25232
- this.ws.onmessage = (event) => {
25233
- try {
25234
- const data = JSON.parse(event.data);
25235
- this.handleMessage(data);
25236
- } catch {
25319
+ }),
25320
+ mutationFn: async (request) => {
25321
+ if (!symmCoreClient || !orderId) {
25322
+ throw new Error("symm-core client or orderId not available");
25237
25323
  }
25238
- };
25239
- this.ws.onclose = () => {
25240
- if (this.intentionalClose) return;
25241
- this.scheduleReconnect();
25242
- };
25243
- this.ws.onerror = () => {
25244
- };
25245
- }
25246
- handleMessage(data) {
25247
- if (data.e === "kline") {
25248
- const k = data.k;
25249
- const streamName = `${data.s.toLowerCase()}@kline_${k.i}`;
25250
- this.dispatchToStream(streamName, data);
25251
- } else if (data.e === "markPriceUpdate") {
25252
- const streamName = `${data.s.toLowerCase()}@markPrice@1s`;
25253
- this.dispatchToStream(streamName, data);
25324
+ return symmCoreClient.orders.setTriggerConfig(orderId, request);
25254
25325
  }
25255
- }
25256
- dispatchToStream(streamName, data) {
25257
- const sub = this.streams.get(streamName);
25258
- if (!sub) return;
25259
- sub.callbacks.forEach((cb) => {
25260
- try {
25261
- cb(data);
25262
- } catch {
25326
+ });
25327
+ }
25328
+ function useSymmClearTriggerConfigMutation(params, options) {
25329
+ const { symmCoreClient } = useSymmContext();
25330
+ const { orderId } = params;
25331
+ const queryClient = useQueryClient();
25332
+ return useMutation({
25333
+ ...withSymmMutationConfig(options?.mutation, {
25334
+ onSuccess: () => {
25335
+ queryClient.invalidateQueries({
25336
+ queryKey: symmKeys.triggerConfig(orderId)
25337
+ });
25338
+ invalidateOrders(queryClient);
25263
25339
  }
25264
- });
25265
- }
25266
- sendSubscribe(streams) {
25267
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
25268
- this.pendingSubscribes.push(...streams);
25269
- return;
25270
- }
25271
- this.ws.send(JSON.stringify({
25272
- method: "SUBSCRIBE",
25273
- params: streams,
25274
- id: Date.now()
25275
- }));
25276
- }
25277
- sendUnsubscribe(streams) {
25278
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
25279
- this.pendingSubscribes = this.pendingSubscribes.filter(
25280
- (s) => !streams.includes(s)
25281
- );
25282
- return;
25340
+ }),
25341
+ mutationFn: async () => {
25342
+ if (!symmCoreClient || !orderId) {
25343
+ throw new Error("symm-core client or orderId not available");
25344
+ }
25345
+ return symmCoreClient.orders.clearTriggerConfig(orderId);
25283
25346
  }
25284
- this.ws.send(JSON.stringify({
25285
- method: "UNSUBSCRIBE",
25286
- params: streams,
25287
- id: Date.now()
25288
- }));
25289
- }
25290
- scheduleReconnect() {
25291
- if (this.reconnectTimer) return;
25292
- if (this.streams.size === 0) return;
25293
- const delay = RECONNECT_DELAYS[Math.min(this.reconnectAttempt, RECONNECT_DELAYS.length - 1)];
25294
- this.reconnectAttempt++;
25295
- this.reconnectTimer = setTimeout(() => {
25296
- this.reconnectTimer = null;
25297
- this.connect();
25298
- }, delay);
25299
- }
25300
- };
25301
- var _instance = null;
25302
- function getBinanceWsManager() {
25303
- if (!_instance) {
25304
- _instance = new BinanceWsManager();
25305
- }
25306
- return _instance;
25347
+ });
25348
+ }
25349
+ function useSymmTriggerOrders(params) {
25350
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25351
+ const { accountAddress, address, status, limit, offset } = params;
25352
+ const resolvedAddress = accountAddress ? void 0 : address;
25353
+ const chainId = params.chainId ?? ctxChainId;
25354
+ const internalEnabled = !!symmCoreClient && !!(accountAddress || resolvedAddress);
25355
+ const request = {
25356
+ address: accountAddress ?? resolvedAddress,
25357
+ chainId,
25358
+ status,
25359
+ limit,
25360
+ offset
25361
+ };
25362
+ return useQuery({
25363
+ ...params.query,
25364
+ queryKey: symmKeys.triggerOrders({
25365
+ accountAddress,
25366
+ address: resolvedAddress,
25367
+ chainId,
25368
+ status,
25369
+ limit,
25370
+ offset
25371
+ }),
25372
+ queryFn: () => symmCoreClient.orders.listTriggerOrders(request),
25373
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25374
+ });
25307
25375
  }
25308
-
25309
- // src/react/stores/use-binance-mark-price-store.ts
25310
- var refCounts = /* @__PURE__ */ new Map();
25311
- var streamUnsubs = /* @__PURE__ */ new Map();
25312
- var streamSymbols = /* @__PURE__ */ new Map();
25313
- function normalizeBinanceSymbol(symbol) {
25314
- return symbol.toUpperCase().trim();
25376
+ function useSymmMarkets(params) {
25377
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25378
+ const chainId = params?.chainId ?? ctxChainId;
25379
+ const searchText = params?.searchText;
25380
+ const internalEnabled = !!symmCoreClient;
25381
+ return useQuery({
25382
+ ...params?.query,
25383
+ queryKey: symmKeys.markets(chainId, searchText),
25384
+ queryFn: () => {
25385
+ if (searchText) {
25386
+ return symmCoreClient.markets.search(searchText, { chainId });
25387
+ }
25388
+ return symmCoreClient.markets.list({ pageSize: "1000", chainId });
25389
+ },
25390
+ enabled: internalEnabled && (params?.query?.enabled ?? true)
25391
+ });
25315
25392
  }
25316
- function getNextRefCount(binanceSymbol) {
25317
- return (refCounts.get(binanceSymbol) ?? 0) + 1;
25393
+ function useSymmHedgerMarketById(params) {
25394
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25395
+ const { id } = params;
25396
+ const chainId = params.chainId ?? ctxChainId;
25397
+ const internalEnabled = !!symmCoreClient && id != null;
25398
+ return useQuery({
25399
+ ...params.query,
25400
+ queryKey: symmKeys.hedgerMarketById(id, chainId),
25401
+ queryFn: () => symmCoreClient.markets.getById(id, chainId),
25402
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25403
+ });
25318
25404
  }
25319
- function getPrevRefCount(binanceSymbol) {
25320
- return Math.max(0, (refCounts.get(binanceSymbol) ?? 0) - 1);
25405
+ function useSymmHedgerMarketBySymbol(params) {
25406
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25407
+ const { symbol } = params;
25408
+ const chainId = params.chainId ?? ctxChainId;
25409
+ const internalEnabled = !!symmCoreClient && !!symbol;
25410
+ return useQuery({
25411
+ ...params.query,
25412
+ queryKey: symmKeys.hedgerMarketBySymbol(symbol, chainId),
25413
+ queryFn: () => symmCoreClient.markets.getBySymbol(symbol, chainId),
25414
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25415
+ });
25321
25416
  }
25322
- var useBinanceMarkPriceStore = create((set) => ({
25323
- markPrices: {},
25324
- subscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
25325
- const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
25326
- const nextRefCount = getNextRefCount(binanceSymbol);
25327
- refCounts.set(binanceSymbol, nextRefCount);
25328
- const symbols = streamSymbols.get(binanceSymbol) ?? /* @__PURE__ */ new Set();
25329
- symbols.add(symmSymbol);
25330
- streamSymbols.set(binanceSymbol, symbols);
25331
- if (nextRefCount === 1) {
25332
- const wsManager = getBinanceWsManager();
25333
- const unsubscribe = wsManager.subscribeMarkPrice(binanceSymbol, (data) => {
25334
- const canonicalSymbol = normalizeBinanceSymbol(data.symbol);
25335
- const mappedSymbols = streamSymbols.get(canonicalSymbol);
25336
- if (!mappedSymbols || mappedSymbols.size === 0) return;
25337
- set((state) => {
25338
- const nextMarkPrices = { ...state.markPrices };
25339
- mappedSymbols.forEach((mappedSymbol) => {
25340
- nextMarkPrices[mappedSymbol] = data.markPrice;
25341
- });
25342
- return { markPrices: nextMarkPrices };
25343
- });
25344
- });
25345
- streamUnsubs.set(binanceSymbol, unsubscribe);
25346
- }
25347
- },
25348
- unsubscribeSymbol: (symmSymbol, rawBinanceSymbol) => {
25349
- const binanceSymbol = normalizeBinanceSymbol(rawBinanceSymbol);
25350
- const symbols = streamSymbols.get(binanceSymbol);
25351
- if (symbols) {
25352
- symbols.delete(symmSymbol);
25353
- if (symbols.size === 0) {
25354
- streamSymbols.delete(binanceSymbol);
25355
- } else {
25356
- streamSymbols.set(binanceSymbol, symbols);
25357
- }
25358
- }
25359
- const nextRefCount = getPrevRefCount(binanceSymbol);
25360
- if (nextRefCount === 0) {
25361
- const unsubscribe = streamUnsubs.get(binanceSymbol);
25362
- if (unsubscribe) unsubscribe();
25363
- streamUnsubs.delete(binanceSymbol);
25364
- refCounts.delete(binanceSymbol);
25365
- } else {
25366
- refCounts.set(binanceSymbol, nextRefCount);
25367
- }
25368
- set((state) => {
25369
- if (state.markPrices[symmSymbol] == null) return state;
25370
- const nextMarkPrices = { ...state.markPrices };
25371
- delete nextMarkPrices[symmSymbol];
25372
- return { markPrices: nextMarkPrices };
25373
- });
25417
+ function useSymmLockedParams(params) {
25418
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25419
+ const { marketName, leverage } = params;
25420
+ const chainId = params.chainId ?? ctxChainId;
25421
+ const internalEnabled = !!symmCoreClient && !!marketName && leverage != null;
25422
+ return useQuery({
25423
+ ...params.query,
25424
+ queryKey: symmKeys.lockedParams(marketName, leverage, chainId),
25425
+ queryFn: () => symmCoreClient.markets.getLockedParams({
25426
+ marketName,
25427
+ leverage,
25428
+ chainId
25429
+ }),
25430
+ enabled: internalEnabled && (params.query?.enabled ?? true)
25431
+ });
25432
+ }
25433
+ function useSymmHedgerMarkets(params) {
25434
+ const { symmCoreClient, chainId: ctxChainId } = useSymmContext();
25435
+ const chainId = params?.chainId ?? ctxChainId;
25436
+ const searchText = params?.searchText?.trim();
25437
+ const consumerEnabled = params?.query?.enabled ?? params?.enabled ?? true;
25438
+ const { enabled: _, query: __, ...restParams } = params ?? {};
25439
+ const request = {
25440
+ ...restParams,
25441
+ chainId,
25442
+ searchText: searchText || void 0
25443
+ };
25444
+ const internalEnabled = !!symmCoreClient;
25445
+ return useQuery({
25446
+ ...params?.query,
25447
+ queryKey: symmKeys.hedgerMarkets(request),
25448
+ queryFn: () => symmCoreClient.markets.listSymmHedger(request),
25449
+ enabled: internalEnabled && consumerEnabled
25450
+ });
25451
+ }
25452
+
25453
+ // src/utils/binance-api.ts
25454
+ var BINANCE_FAPI_BASE = "https://fapi.binance.com";
25455
+ async function fetchKlines(symbol, interval, startTime, endTime, limit = 1500) {
25456
+ const params = new URLSearchParams({
25457
+ symbol,
25458
+ interval,
25459
+ startTime: String(startTime),
25460
+ endTime: String(endTime),
25461
+ limit: String(Math.min(limit, 1500))
25462
+ });
25463
+ const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/klines?${params}`);
25464
+ if (!response.ok) {
25465
+ throw new Error(`Binance klines failed: ${response.status}`);
25374
25466
  }
25375
- }));
25467
+ const data = await response.json();
25468
+ return data.map((k) => ({
25469
+ openTime: Number(k[0]),
25470
+ open: parseFloat(k[1]),
25471
+ high: parseFloat(k[2]),
25472
+ low: parseFloat(k[3]),
25473
+ close: parseFloat(k[4]),
25474
+ volume: parseFloat(k[5]),
25475
+ closeTime: Number(k[6])
25476
+ }));
25477
+ }
25478
+ async function fetch24hrTicker(symbol) {
25479
+ const params = new URLSearchParams({ symbol });
25480
+ const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/ticker/24hr?${params}`);
25481
+ if (!response.ok) return null;
25482
+ const data = await response.json();
25483
+ return {
25484
+ lastPrice: parseFloat(data.lastPrice),
25485
+ openPrice: parseFloat(data.openPrice),
25486
+ priceChangePercent: parseFloat(data.priceChangePercent)
25487
+ };
25488
+ }
25489
+ async function fetch24hrTickers() {
25490
+ const response = await fetch(`${BINANCE_FAPI_BASE}/fapi/v1/ticker/24hr`);
25491
+ if (!response.ok) {
25492
+ throw new Error(`Binance 24hr tickers failed: ${response.status}`);
25493
+ }
25494
+ const data = await response.json();
25495
+ const result = {};
25496
+ for (const item of data) {
25497
+ result[item.symbol] = {
25498
+ lastPrice: parseFloat(item.lastPrice),
25499
+ openPrice: parseFloat(item.openPrice),
25500
+ priceChangePercent: parseFloat(item.priceChangePercent)
25501
+ };
25502
+ }
25503
+ return result;
25504
+ }
25376
25505
 
25377
25506
  // src/react/hooks/use-symm-token-selection-markets.ts
25378
25507
  function useSymmTokenSelectionMarkets(params) {
@@ -25390,19 +25519,7 @@ function useSymmTokenSelectionMarkets(params) {
25390
25519
  () => [...marketSymbols].sort().join(","),
25391
25520
  [marketSymbols]
25392
25521
  );
25393
- const symbolToBinanceMap = useMemo(
25394
- () => new Map(
25395
- marketSymbols.map((symbol) => {
25396
- const binanceSymbol = resolveBinanceSymbol(symbol).binanceSymbol;
25397
- if (!binanceSymbol) return null;
25398
- return [symbol, binanceSymbol];
25399
- }).filter((entry) => !!entry)
25400
- ),
25401
- [marketSymbols]
25402
- );
25403
25522
  const liveMarkPrices = useBinanceMarkPriceStore((state) => state.markPrices);
25404
- const subscribeSymbol = useBinanceMarkPriceStore((state) => state.subscribeSymbol);
25405
- const unsubscribeSymbol = useBinanceMarkPriceStore((state) => state.unsubscribeSymbol);
25406
25523
  const priceQuery = useQuery({
25407
25524
  queryKey: ["symm", "token-selection-markets-price", symbolsKey],
25408
25525
  queryFn: async () => {
@@ -25415,7 +25532,7 @@ function useSymmTokenSelectionMarkets(params) {
25415
25532
  return;
25416
25533
  }
25417
25534
  const ticker = allTickers[binanceSymbol];
25418
- tickerSnapshots[symbol] = ticker ? { prevClosePrice: ticker.prevClosePrice } : null;
25535
+ tickerSnapshots[symbol] = ticker ? { openPrice: ticker.openPrice } : null;
25419
25536
  });
25420
25537
  return { tickerSnapshots };
25421
25538
  },
@@ -25423,26 +25540,12 @@ function useSymmTokenSelectionMarkets(params) {
25423
25540
  staleTime: 6e4,
25424
25541
  gcTime: 5 * 6e4
25425
25542
  });
25426
- useEffect(() => {
25427
- if (!query.isSuccess || symbolToBinanceMap.size === 0) {
25428
- return;
25429
- }
25430
- const symbolEntries = Array.from(symbolToBinanceMap.entries());
25431
- symbolEntries.forEach(
25432
- ([symbol, binanceSymbol]) => subscribeSymbol(symbol, binanceSymbol)
25433
- );
25434
- return () => {
25435
- symbolEntries.forEach(
25436
- ([symbol, binanceSymbol]) => unsubscribeSymbol(symbol, binanceSymbol)
25437
- );
25438
- };
25439
- }, [query.isSuccess, symbolsKey, symbolToBinanceMap, subscribeSymbol, unsubscribeSymbol]);
25440
25543
  const markets = useMemo(() => {
25441
25544
  const snapshots = priceQuery.data?.tickerSnapshots ?? {};
25442
25545
  return baseMarkets.map((market) => {
25443
25546
  const symbol = market.symbol ?? "";
25444
25547
  const markPrice = symbol ? liveMarkPrices[symbol] ?? null : null;
25445
- const prevDayPrice = symbol ? snapshots[symbol]?.prevClosePrice ?? null : null;
25548
+ const prevDayPrice = symbol ? snapshots[symbol]?.openPrice ?? null : null;
25446
25549
  const priceChange24h = markPrice != null && prevDayPrice != null ? markPrice - prevDayPrice : null;
25447
25550
  const priceChange24hPercent = markPrice != null && prevDayPrice != null && prevDayPrice !== 0 ? (markPrice - prevDayPrice) / prevDayPrice * 100 : null;
25448
25551
  return {
@@ -25465,6 +25568,61 @@ function useSymmTokenSelectionMarkets(params) {
25465
25568
  () => new Map(markets.map((market) => [market.id, market])),
25466
25569
  [markets]
25467
25570
  );
25571
+ useEffect(() => {
25572
+ console.debug("[useSymmTokenSelectionMarkets] data flow", {
25573
+ params,
25574
+ query: {
25575
+ status: query.status,
25576
+ fetchStatus: query.fetchStatus,
25577
+ isLoading: query.isLoading,
25578
+ isSuccess: query.isSuccess,
25579
+ isError: query.isError,
25580
+ error: query.error
25581
+ },
25582
+ priceQuery: {
25583
+ status: priceQuery.status,
25584
+ fetchStatus: priceQuery.fetchStatus,
25585
+ isLoading: priceQuery.isLoading,
25586
+ isSuccess: priceQuery.isSuccess,
25587
+ isError: priceQuery.isError,
25588
+ error: priceQuery.error
25589
+ },
25590
+ marketSymbols,
25591
+ symbolsKey,
25592
+ liveMarkPrices,
25593
+ tickerSnapshots: priceQuery.data?.tickerSnapshots ?? {},
25594
+ result: {
25595
+ marketCount: markets.length,
25596
+ markets: markets.map((market) => ({
25597
+ id: market.id,
25598
+ symbol: market.symbol,
25599
+ markPrice: market.markPrice,
25600
+ prevDayPrice: market.prevDayPrice,
25601
+ priceChange24h: market.priceChange24h,
25602
+ priceChange24hPercent: market.priceChange24hPercent
25603
+ }))
25604
+ }
25605
+ });
25606
+ }, [
25607
+ liveMarkPrices,
25608
+ marketSymbols,
25609
+ markets,
25610
+ params,
25611
+ priceQuery.data,
25612
+ priceQuery.error,
25613
+ priceQuery.fetchStatus,
25614
+ priceQuery.isError,
25615
+ priceQuery.isLoading,
25616
+ priceQuery.isSuccess,
25617
+ priceQuery.status,
25618
+ query.error,
25619
+ query.fetchStatus,
25620
+ query.isError,
25621
+ query.isLoading,
25622
+ query.isSuccess,
25623
+ query.status,
25624
+ symbolsKey
25625
+ ]);
25468
25626
  return {
25469
25627
  query,
25470
25628
  priceQuery,
@@ -25479,17 +25637,6 @@ function useSymmTokenMarkPrice(symbol) {
25479
25637
  const normalizedSymbol = symbol?.trim().toUpperCase() || null;
25480
25638
  const resolution = resolveBinanceSymbol(normalizedSymbol ?? "");
25481
25639
  const liveMarkPrices = useBinanceMarkPriceStore((state) => state.markPrices);
25482
- const subscribeSymbol = useBinanceMarkPriceStore((state) => state.subscribeSymbol);
25483
- const unsubscribeSymbol = useBinanceMarkPriceStore((state) => state.unsubscribeSymbol);
25484
- useEffect(() => {
25485
- if (!normalizedSymbol || !resolution.binanceSymbol || !resolution.supported) {
25486
- return;
25487
- }
25488
- subscribeSymbol(normalizedSymbol, resolution.binanceSymbol);
25489
- return () => {
25490
- unsubscribeSymbol(normalizedSymbol, resolution.binanceSymbol);
25491
- };
25492
- }, [normalizedSymbol, resolution.binanceSymbol, resolution.supported, subscribeSymbol, unsubscribeSymbol]);
25493
25640
  const markPrice = useMemo(() => {
25494
25641
  if (!normalizedSymbol) return null;
25495
25642
  return liveMarkPrices[normalizedSymbol] ?? null;
@@ -26016,7 +26163,7 @@ async function fetchTickerSnapshot(symbol) {
26016
26163
  const ticker = await fetch24hrTicker(resolution.binanceSymbol);
26017
26164
  if (!ticker) return null;
26018
26165
  return {
26019
- prevClosePrice: ticker.prevClosePrice
26166
+ openPrice: ticker.openPrice
26020
26167
  };
26021
26168
  }
26022
26169
  function toSymmTokenMetadata(currentPrice, prevDayPrice) {
@@ -26042,17 +26189,7 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26042
26189
  const isUnsupported = unsupportedSymbols.length > 0;
26043
26190
  const unavailableReason = isUnsupported ? `Binance market data is unavailable for ${unsupportedSymbols.join(", ")}.` : null;
26044
26191
  const symbolsKey = [...selectedSymbols].sort().join(",");
26045
- const symbolToBinanceMap = useMemo(
26046
- () => new Map(
26047
- selectedSymbols.map(
26048
- (symbol) => [symbol, resolveBinanceSymbol(symbol).binanceSymbol]
26049
- ).filter((entry) => !!entry[1])
26050
- ),
26051
- [selectedSymbols]
26052
- );
26053
26192
  const liveMarkPrices = useBinanceMarkPriceStore((state) => state.markPrices);
26054
- const subscribeSymbol = useBinanceMarkPriceStore((state) => state.subscribeSymbol);
26055
- const unsubscribeSymbol = useBinanceMarkPriceStore((state) => state.unsubscribeSymbol);
26056
26193
  const query = useQuery({
26057
26194
  queryKey: ["symm", "chart-metadata", symbolsKey],
26058
26195
  queryFn: async () => {
@@ -26078,28 +26215,7 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26078
26215
  enabled: enabled && selectedSymbols.length > 0 && !isUnsupported,
26079
26216
  gcTime: 5 * 6e4
26080
26217
  });
26081
- useEffect(() => {
26082
- if (!enabled || isUnsupported || selectedSymbols.length === 0) {
26083
- return;
26084
- }
26085
- const symbolEntries = Array.from(symbolToBinanceMap.entries());
26086
- symbolEntries.forEach(
26087
- ([symbol, binanceSymbol]) => subscribeSymbol(symbol, binanceSymbol)
26088
- );
26089
- return () => {
26090
- symbolEntries.forEach(
26091
- ([symbol, binanceSymbol]) => unsubscribeSymbol(symbol, binanceSymbol)
26092
- );
26093
- };
26094
- }, [
26095
- enabled,
26096
- isUnsupported,
26097
- selectedSymbols.length,
26098
- symbolToBinanceMap,
26099
- subscribeSymbol,
26100
- unsubscribeSymbol
26101
- ]);
26102
- return useMemo(() => {
26218
+ const result = useMemo(() => {
26103
26219
  const tickerSnapshots = query.data?.tickerSnapshots ?? {};
26104
26220
  const longMeta = {};
26105
26221
  const shortMeta = {};
@@ -26107,12 +26223,12 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26107
26223
  for (const token of longTokens) {
26108
26224
  const currentPrice = liveMarkPrices[token.symbol];
26109
26225
  const ticker = tickerSnapshots[token.symbol];
26110
- longMeta[token.symbol] = currentPrice != null && ticker ? toSymmTokenMetadata(currentPrice, ticker.prevClosePrice) : null;
26226
+ longMeta[token.symbol] = currentPrice != null && ticker ? toSymmTokenMetadata(currentPrice, ticker.openPrice) : null;
26111
26227
  }
26112
26228
  for (const token of shortTokens) {
26113
26229
  const currentPrice = liveMarkPrices[token.symbol];
26114
26230
  const ticker = tickerSnapshots[token.symbol];
26115
- shortMeta[token.symbol] = currentPrice != null && ticker ? toSymmTokenMetadata(currentPrice, ticker.prevClosePrice) : null;
26231
+ shortMeta[token.symbol] = currentPrice != null && ticker ? toSymmTokenMetadata(currentPrice, ticker.openPrice) : null;
26116
26232
  }
26117
26233
  const allLongReady = longTokens.every(
26118
26234
  (t) => longMeta[t.symbol]?.currentPrice != null
@@ -26158,6 +26274,53 @@ function useSymmTokenSelectionMetadata(selection, options = {}) {
26158
26274
  selectedSymbols.length,
26159
26275
  liveMarkPrices
26160
26276
  ]);
26277
+ useEffect(() => {
26278
+ console.debug("[useSymmTokenSelectionMetadata] data flow", {
26279
+ selection,
26280
+ options,
26281
+ query: {
26282
+ status: query.status,
26283
+ fetchStatus: query.fetchStatus,
26284
+ isLoading: query.isLoading,
26285
+ isSuccess: query.isSuccess,
26286
+ isError: query.isError,
26287
+ error: query.error
26288
+ },
26289
+ selectedSymbols,
26290
+ unsupportedSymbols,
26291
+ unavailableReason,
26292
+ liveMarkPrices,
26293
+ tickerSnapshots: query.data?.tickerSnapshots ?? {},
26294
+ result: {
26295
+ isLoading: result.isLoading,
26296
+ isPriceDataReady: result.isPriceDataReady,
26297
+ isUnsupported: result.isUnsupported,
26298
+ longTokensMetadata: result.longTokensMetadata,
26299
+ shortTokensMetadata: result.shortTokensMetadata,
26300
+ weightedRatio: result.weightedRatio,
26301
+ weightedRatio24h: result.weightedRatio24h,
26302
+ priceRatio: result.priceRatio,
26303
+ priceRatio24h: result.priceRatio24h,
26304
+ sumNetFunding: result.sumNetFunding
26305
+ }
26306
+ });
26307
+ }, [
26308
+ liveMarkPrices,
26309
+ options,
26310
+ query.data,
26311
+ query.error,
26312
+ query.fetchStatus,
26313
+ query.isError,
26314
+ query.isLoading,
26315
+ query.isSuccess,
26316
+ query.status,
26317
+ result,
26318
+ selectedSymbols,
26319
+ selection,
26320
+ unavailableReason,
26321
+ unsupportedSymbols
26322
+ ]);
26323
+ return result;
26161
26324
  }
26162
26325
 
26163
26326
  // src/utils/binance-intervals.ts
@@ -26186,10 +26349,13 @@ function toBinanceInterval(interval) {
26186
26349
  }
26187
26350
 
26188
26351
  // src/react/hooks/use-symm-chart-candles.ts
26352
+ function areIntervalsEqual(currentInterval, nextInterval) {
26353
+ return currentInterval === toBinanceInterval(nextInterval);
26354
+ }
26189
26355
  async function fetchSymbolKlines(symbol, interval, start, end) {
26190
26356
  const binanceSymbol = toBinanceSymbol(symbol);
26191
26357
  if (!binanceSymbol) return [];
26192
- const klines = await fetchMarkPriceKlines(
26358
+ const klines = await fetchKlines(
26193
26359
  binanceSymbol,
26194
26360
  toBinanceInterval(interval),
26195
26361
  start,
@@ -26350,6 +26516,15 @@ function useSymmChartCandles(selection) {
26350
26516
  wsUnsubsRef.current.push(unsub);
26351
26517
  }
26352
26518
  }, [emitRealtimeBar, isUnsupported, selectedSymbols]);
26519
+ const setRealtimeInterval = useCallback((interval) => {
26520
+ if (areIntervalsEqual(activeIntervalRef.current, interval)) {
26521
+ return;
26522
+ }
26523
+ activeIntervalRef.current = toBinanceInterval(interval);
26524
+ if (listenersRef.current.size > 0) {
26525
+ setupWsSubscriptions();
26526
+ }
26527
+ }, [setupWsSubscriptions]);
26353
26528
  useEffect(() => {
26354
26529
  if (listenersRef.current.size > 0) {
26355
26530
  setupWsSubscriptions();
@@ -26361,6 +26536,7 @@ function useSymmChartCandles(selection) {
26361
26536
  }, [setupWsSubscriptions]);
26362
26537
  const fetchBasketCandles = useCallback(
26363
26538
  async (start, end, interval) => {
26539
+ setRealtimeInterval(interval);
26364
26540
  const longSymbol = longTokens[0]?.symbol;
26365
26541
  const shortSymbol = shortTokens[0]?.symbol;
26366
26542
  if (isUnsupported) {
@@ -26386,18 +26562,20 @@ function useSymmChartCandles(selection) {
26386
26562
  Object.fromEntries(entries)
26387
26563
  );
26388
26564
  },
26389
- [isUnsupported, longTokens, shortTokens]
26565
+ [isUnsupported, longTokens, setRealtimeInterval, shortTokens]
26390
26566
  );
26391
26567
  const fetchPerformanceCandles = useCallback(
26392
26568
  async (start, end, interval, symbol) => {
26569
+ setRealtimeInterval(interval);
26393
26570
  const parts = symbol.split(" ");
26394
26571
  const assetSymbol = parts.length >= 2 ? parts.slice(1).join(" ") : symbol;
26395
26572
  return fetchSymbolKlines(assetSymbol, interval, start, end);
26396
26573
  },
26397
- []
26574
+ [setRealtimeInterval]
26398
26575
  );
26399
26576
  const fetchOverallPerformanceCandles = useCallback(
26400
26577
  async (start, end, interval) => {
26578
+ setRealtimeInterval(interval);
26401
26579
  const longSymbol = longTokens[0]?.symbol;
26402
26580
  const shortSymbol = shortTokens[0]?.symbol;
26403
26581
  if (isUnsupported) return [];
@@ -26454,7 +26632,7 @@ function useSymmChartCandles(selection) {
26454
26632
  }
26455
26633
  return result;
26456
26634
  },
26457
- [isUnsupported, longTokens, shortTokens]
26635
+ [isUnsupported, longTokens, setRealtimeInterval, shortTokens]
26458
26636
  );
26459
26637
  const addRealtimeListener = useCallback((cb) => {
26460
26638
  const id = Math.random().toString(36).slice(2);