@pear-protocol/symmio-client 0.2.27 → 0.2.28

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