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