@solana/connector 0.1.6 → 0.1.7
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/README.md +45 -45
- package/dist/{chunk-VMSZJPR5.mjs → chunk-APQGEW7S.mjs} +82 -11
- package/dist/chunk-APQGEW7S.mjs.map +1 -0
- package/dist/{chunk-3STZXVXD.mjs → chunk-JK47EFJT.mjs} +940 -381
- package/dist/chunk-JK47EFJT.mjs.map +1 -0
- package/dist/{chunk-QKVL45F6.mjs → chunk-TQRJYZNK.mjs} +5 -3
- package/dist/chunk-TQRJYZNK.mjs.map +1 -0
- package/dist/{chunk-JUZVCBAI.js → chunk-VA6LKXCQ.js} +85 -10
- package/dist/chunk-VA6LKXCQ.js.map +1 -0
- package/dist/{chunk-NQXK7PGX.js → chunk-VZ5Y6DIM.js} +19 -17
- package/dist/chunk-VZ5Y6DIM.js.map +1 -0
- package/dist/{chunk-ULUYX23Q.js → chunk-Z22V3D4E.js} +949 -388
- package/dist/chunk-Z22V3D4E.js.map +1 -0
- package/dist/compat.d.mts +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/headless.d.mts +99 -7
- package/dist/headless.d.ts +99 -7
- package/dist/headless.js +128 -112
- package/dist/headless.mjs +2 -2
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +163 -139
- package/dist/index.mjs +3 -3
- package/dist/react.d.mts +179 -13
- package/dist/react.d.ts +179 -13
- package/dist/react.js +36 -28
- package/dist/react.mjs +2 -2
- package/dist/{transaction-signer-D9d8nxwb.d.mts → transaction-signer-CpGEvp7S.d.mts} +1 -1
- package/dist/{transaction-signer-D9d8nxwb.d.ts → transaction-signer-CpGEvp7S.d.ts} +1 -1
- package/dist/{wallet-standard-shim--YcrQNRt.d.ts → wallet-standard-shim-D4CYG5sU.d.mts} +35 -6
- package/dist/{wallet-standard-shim-Dx7H8Ctf.d.mts → wallet-standard-shim-DiMvGjOk.d.ts} +35 -6
- package/package.json +1 -1
- package/dist/chunk-3STZXVXD.mjs.map +0 -1
- package/dist/chunk-JUZVCBAI.js.map +0 -1
- package/dist/chunk-NQXK7PGX.js.map +0 -1
- package/dist/chunk-QKVL45F6.mjs.map +0 -1
- package/dist/chunk-ULUYX23Q.js.map +0 -1
- package/dist/chunk-VMSZJPR5.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkVA6LKXCQ_js = require('./chunk-VA6LKXCQ.js');
|
|
4
4
|
var chunkI64FD2EH_js = require('./chunk-I64FD2EH.js');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
@@ -12,7 +12,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
12
12
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
13
13
|
|
|
14
14
|
var logger = chunkI64FD2EH_js.createLogger("ConnectorProvider");
|
|
15
|
-
|
|
15
|
+
chunkVA6LKXCQ_js.installPolyfills();
|
|
16
16
|
var ConnectorContext = React.createContext(null);
|
|
17
17
|
ConnectorContext.displayName = "ConnectorContext";
|
|
18
18
|
function ConnectorProviderInternal({
|
|
@@ -23,7 +23,7 @@ function ConnectorProviderInternal({
|
|
|
23
23
|
let clientRef = React.useRef(null), client = React__default.default.useCallback(() => {
|
|
24
24
|
if (!clientRef.current)
|
|
25
25
|
try {
|
|
26
|
-
clientRef.current = new
|
|
26
|
+
clientRef.current = new chunkVA6LKXCQ_js.ConnectorClient(config), typeof window < "u" && (window.__connectorClient = clientRef.current), config?.debug && logger.info("Client initialized successfully");
|
|
27
27
|
} catch (error) {
|
|
28
28
|
let err = error;
|
|
29
29
|
logger.error("Failed to initialize client", { error: err });
|
|
@@ -60,15 +60,15 @@ function ConnectorProviderInternal({
|
|
|
60
60
|
"solana:mainnet",
|
|
61
61
|
"solana:devnet",
|
|
62
62
|
"solana:testnet"
|
|
63
|
-
]
|
|
64
|
-
registerMwa({
|
|
63
|
+
], mwaConfig = {
|
|
65
64
|
appIdentity: mobile.appIdentity,
|
|
66
65
|
authorizationCache: mobile.authorizationCache ?? createDefaultAuthorizationCache(),
|
|
67
66
|
chains: mobile.chains ?? defaultChains,
|
|
68
67
|
chainSelector: mobile.chainSelector ?? createDefaultChainSelector(),
|
|
69
68
|
remoteHostAuthority: mobile.remoteHostAuthority,
|
|
70
69
|
onWalletNotFound: mobile.onWalletNotFound ?? createDefaultWalletNotFoundHandler()
|
|
71
|
-
}
|
|
70
|
+
};
|
|
71
|
+
registerMwa(mwaConfig);
|
|
72
72
|
} catch {
|
|
73
73
|
}
|
|
74
74
|
})(), () => {
|
|
@@ -83,7 +83,7 @@ function ConnectorProvider({
|
|
|
83
83
|
}) {
|
|
84
84
|
let errorBoundaryConfig = config?.errorBoundary;
|
|
85
85
|
return errorBoundaryConfig?.enabled ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
86
|
-
|
|
86
|
+
chunkVA6LKXCQ_js.ConnectorErrorBoundary,
|
|
87
87
|
{
|
|
88
88
|
maxRetries: errorBoundaryConfig.maxRetries ?? 3,
|
|
89
89
|
onError: errorBoundaryConfig.onError,
|
|
@@ -140,7 +140,7 @@ function useCluster() {
|
|
|
140
140
|
[client]
|
|
141
141
|
);
|
|
142
142
|
return React.useMemo(() => {
|
|
143
|
-
let isMainnet = cluster ?
|
|
143
|
+
let isMainnet = cluster ? chunkVA6LKXCQ_js.isMainnetCluster(cluster) : false, isDevnet = cluster ? chunkVA6LKXCQ_js.isDevnetCluster(cluster) : false, isTestnet = cluster ? chunkVA6LKXCQ_js.isTestnetCluster(cluster) : false, isLocal = cluster ? chunkVA6LKXCQ_js.isLocalCluster(cluster) : false, explorerUrl = cluster ? chunkVA6LKXCQ_js.getClusterExplorerUrl(cluster) : "", type = cluster ? chunkVA6LKXCQ_js.getClusterType(cluster) : null;
|
|
144
144
|
return {
|
|
145
145
|
cluster,
|
|
146
146
|
clusters,
|
|
@@ -158,7 +158,7 @@ function useAccount() {
|
|
|
158
158
|
let { selectedAccount, accounts, connected, selectAccount } = useConnector(), [copied, setCopied] = React.useState(false), copyTimeoutRef = React__default.default.useRef(void 0), account = React.useMemo(
|
|
159
159
|
() => accounts.find((a) => a.address === selectedAccount) ?? null,
|
|
160
160
|
[accounts, selectedAccount]
|
|
161
|
-
), formatted = React.useMemo(() => selectedAccount ?
|
|
161
|
+
), formatted = React.useMemo(() => selectedAccount ? chunkVA6LKXCQ_js.formatAddress(selectedAccount) : "", [selectedAccount]), copy = React.useCallback(async () => selectedAccount ? (copyTimeoutRef.current && clearTimeout(copyTimeoutRef.current), await chunkVA6LKXCQ_js.copyAddressToClipboard(selectedAccount, {
|
|
162
162
|
onSuccess: () => {
|
|
163
163
|
setCopied(true), copyTimeoutRef.current = setTimeout(() => setCopied(false), 2e3);
|
|
164
164
|
}
|
|
@@ -224,7 +224,7 @@ function useTransactionSigner() {
|
|
|
224
224
|
let { selectedWallet, selectedAccount, accounts, cluster, connected } = useConnector(), client = useConnectorClient(), account = React.useMemo(
|
|
225
225
|
() => accounts.find((a) => a.address === selectedAccount)?.raw ?? null,
|
|
226
226
|
[accounts, selectedAccount]
|
|
227
|
-
), signer = React.useMemo(() => !connected || !selectedWallet || !account ? null :
|
|
227
|
+
), signer = React.useMemo(() => !connected || !selectedWallet || !account ? null : chunkVA6LKXCQ_js.createTransactionSigner({
|
|
228
228
|
wallet: selectedWallet,
|
|
229
229
|
account,
|
|
230
230
|
cluster: cluster ?? void 0,
|
|
@@ -252,7 +252,7 @@ function useTransactionSigner() {
|
|
|
252
252
|
function useKitTransactionSigner() {
|
|
253
253
|
let { signer: connectorSigner, ready } = useTransactionSigner();
|
|
254
254
|
return {
|
|
255
|
-
signer: React.useMemo(() => connectorSigner ?
|
|
255
|
+
signer: React.useMemo(() => connectorSigner ? chunkVA6LKXCQ_js.createKitTransactionSigner(connectorSigner) : null, [connectorSigner]),
|
|
256
256
|
ready
|
|
257
257
|
};
|
|
258
258
|
}
|
|
@@ -286,7 +286,7 @@ function useTransactionPreparer() {
|
|
|
286
286
|
let { client, ready } = useSolanaClient(), prepare = React.useCallback(
|
|
287
287
|
async (transaction, options = {}) => {
|
|
288
288
|
if (!client)
|
|
289
|
-
throw new
|
|
289
|
+
throw new chunkVA6LKXCQ_js.NetworkError("RPC_ERROR", "Solana client not available. Cannot prepare transaction.");
|
|
290
290
|
return chunkI64FD2EH_js.prepareTransaction({
|
|
291
291
|
transaction,
|
|
292
292
|
rpc: client.rpc,
|
|
@@ -305,73 +305,493 @@ function useTransactionPreparer() {
|
|
|
305
305
|
[prepare, ready]
|
|
306
306
|
);
|
|
307
307
|
}
|
|
308
|
-
var
|
|
309
|
-
function
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
308
|
+
var DEFAULT_CACHE_TIME_MS = 300 * 1e3, MAX_STORE_SIZE = 100, store = /* @__PURE__ */ new Map();
|
|
309
|
+
function evictStaleEntries() {
|
|
310
|
+
if (store.size <= MAX_STORE_SIZE) return;
|
|
311
|
+
let evictable = [];
|
|
312
|
+
for (let [key, entry] of store)
|
|
313
|
+
entry.subscribers.size === 0 && evictable.push([key, entry]);
|
|
314
|
+
evictable.sort((a, b) => a[1].lastAccessedAt - b[1].lastAccessedAt);
|
|
315
|
+
for (let [key, entry] of evictable) {
|
|
316
|
+
if (store.size <= MAX_STORE_SIZE) break;
|
|
317
|
+
entry.gcTimeoutId && clearTimeout(entry.gcTimeoutId), entry.intervalId && clearInterval(entry.intervalId), entry.abortController && entry.abortController.abort(), store.delete(key);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function getOrCreateEntry(key) {
|
|
321
|
+
let existing = store.get(key);
|
|
322
|
+
if (existing)
|
|
323
|
+
return existing.lastAccessedAt = Date.now(), existing;
|
|
324
|
+
let entry = {
|
|
325
|
+
snapshot: {
|
|
326
|
+
data: void 0,
|
|
327
|
+
error: null,
|
|
328
|
+
status: "idle",
|
|
329
|
+
updatedAt: null,
|
|
330
|
+
isFetching: false
|
|
331
|
+
},
|
|
332
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
333
|
+
promise: null,
|
|
334
|
+
abortController: null,
|
|
335
|
+
cacheTimeMs: DEFAULT_CACHE_TIME_MS,
|
|
336
|
+
gcTimeoutId: null,
|
|
337
|
+
queryFn: null,
|
|
338
|
+
intervalCounts: /* @__PURE__ */ new Map(),
|
|
339
|
+
activeIntervalMs: null,
|
|
340
|
+
intervalId: null,
|
|
341
|
+
lastAccessedAt: Date.now()
|
|
342
|
+
};
|
|
343
|
+
return store.set(key, entry), evictStaleEntries(), entry;
|
|
344
|
+
}
|
|
345
|
+
function emit(entry) {
|
|
346
|
+
for (let cb of entry.subscribers)
|
|
347
|
+
cb();
|
|
348
|
+
}
|
|
349
|
+
function setSnapshot(entry, next) {
|
|
350
|
+
let prev = entry.snapshot;
|
|
351
|
+
prev.status === next.status && prev.isFetching === next.isFetching && prev.updatedAt === next.updatedAt && prev.error === next.error && prev.data === next.data || (entry.snapshot = next, emit(entry));
|
|
352
|
+
}
|
|
353
|
+
function getMinIntervalMs(entry) {
|
|
354
|
+
let min = null;
|
|
355
|
+
for (let [ms, count] of entry.intervalCounts)
|
|
356
|
+
count <= 0 || (min === null || ms < min) && (min = ms);
|
|
357
|
+
return min;
|
|
358
|
+
}
|
|
359
|
+
function startOrRestartInterval(key, entry) {
|
|
360
|
+
let nextMs = getMinIntervalMs(entry);
|
|
361
|
+
if (nextMs === null) {
|
|
362
|
+
entry.intervalId && clearInterval(entry.intervalId), entry.intervalId = null, entry.activeIntervalMs = null;
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
entry.activeIntervalMs === nextMs && entry.intervalId || (entry.intervalId && clearInterval(entry.intervalId), entry.activeIntervalMs = nextMs, entry.intervalId = setInterval(() => {
|
|
366
|
+
let fn = entry.queryFn;
|
|
367
|
+
fn && fetchSharedQuery(key, fn, { force: true });
|
|
368
|
+
}, nextMs));
|
|
369
|
+
}
|
|
370
|
+
function subscribeSharedQuery(key, onChange, cacheTimeMs) {
|
|
371
|
+
let entry = getOrCreateEntry(key);
|
|
372
|
+
return entry.subscribers.add(onChange), typeof cacheTimeMs == "number" && (entry.cacheTimeMs = Math.max(entry.cacheTimeMs, cacheTimeMs)), entry.gcTimeoutId && (clearTimeout(entry.gcTimeoutId), entry.gcTimeoutId = null), () => {
|
|
373
|
+
if (entry.subscribers.delete(onChange), entry.subscribers.size > 0) return;
|
|
374
|
+
entry.abortController && entry.abortController.abort(), entry.abortController = null, entry.promise = null, entry.intervalId && clearInterval(entry.intervalId), entry.intervalId = null, entry.activeIntervalMs = null, entry.intervalCounts.clear();
|
|
375
|
+
let gcDelayMs = entry.cacheTimeMs ?? DEFAULT_CACHE_TIME_MS;
|
|
376
|
+
entry.gcTimeoutId = setTimeout(() => {
|
|
377
|
+
entry.subscribers.size === 0 && store.delete(key);
|
|
378
|
+
}, gcDelayMs);
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
async function fetchSharedQuery(key, queryFn, options = {}) {
|
|
382
|
+
let entry = getOrCreateEntry(key);
|
|
383
|
+
entry.queryFn = queryFn;
|
|
384
|
+
let staleTimeMs = options.staleTimeMs ?? 0, now = Date.now();
|
|
385
|
+
if (options.force !== true && entry.snapshot.status === "success" && entry.snapshot.updatedAt !== null && now - entry.snapshot.updatedAt < staleTimeMs && entry.snapshot.data !== void 0)
|
|
386
|
+
return entry.snapshot.data;
|
|
387
|
+
if (entry.promise)
|
|
388
|
+
return entry.promise;
|
|
389
|
+
let controller = new AbortController();
|
|
390
|
+
entry.abortController = controller, options.signal && (options.signal.aborted ? controller.abort() : options.signal.addEventListener("abort", () => controller.abort(), { once: true }));
|
|
391
|
+
let isFirstLoad = entry.snapshot.status === "idle" && entry.snapshot.updatedAt === null;
|
|
392
|
+
setSnapshot(entry, {
|
|
393
|
+
...entry.snapshot,
|
|
394
|
+
status: isFirstLoad ? "loading" : entry.snapshot.status,
|
|
395
|
+
isFetching: true,
|
|
396
|
+
error: null
|
|
397
|
+
});
|
|
398
|
+
let promise = (async () => {
|
|
399
|
+
try {
|
|
400
|
+
let data = await queryFn(controller.signal);
|
|
401
|
+
return setSnapshot(entry, {
|
|
402
|
+
data,
|
|
403
|
+
error: null,
|
|
404
|
+
status: "success",
|
|
405
|
+
updatedAt: Date.now(),
|
|
406
|
+
isFetching: false
|
|
407
|
+
}), data;
|
|
408
|
+
} catch (cause) {
|
|
409
|
+
if (controller.signal.aborted)
|
|
410
|
+
return setSnapshot(entry, {
|
|
411
|
+
data: entry.snapshot.data,
|
|
412
|
+
error: null,
|
|
413
|
+
status: entry.snapshot.status === "idle" ? "idle" : entry.snapshot.status,
|
|
414
|
+
updatedAt: entry.snapshot.updatedAt,
|
|
415
|
+
isFetching: false
|
|
416
|
+
}), entry.snapshot.data;
|
|
417
|
+
let error = cause instanceof Error ? cause : new Error(String(cause));
|
|
418
|
+
throw setSnapshot(entry, {
|
|
419
|
+
data: entry.snapshot.data,
|
|
420
|
+
error,
|
|
421
|
+
status: "error",
|
|
422
|
+
updatedAt: entry.snapshot.updatedAt,
|
|
423
|
+
isFetching: false
|
|
424
|
+
}), error;
|
|
425
|
+
} finally {
|
|
426
|
+
entry.promise = null, entry.abortController = null;
|
|
427
|
+
}
|
|
428
|
+
})();
|
|
429
|
+
return entry.promise = promise, promise;
|
|
430
|
+
}
|
|
431
|
+
function useSharedQuery(key, queryFn, options = {}) {
|
|
432
|
+
let {
|
|
433
|
+
enabled = true,
|
|
434
|
+
staleTimeMs = 0,
|
|
435
|
+
cacheTimeMs,
|
|
436
|
+
refetchOnMount = "stale",
|
|
437
|
+
refetchIntervalMs = false,
|
|
438
|
+
select
|
|
439
|
+
} = options, queryFnRef = React.useRef(queryFn);
|
|
440
|
+
queryFnRef.current = queryFn;
|
|
441
|
+
let stableQueryFn = React.useCallback((signal) => queryFnRef.current(signal), []), subscribe = React.useCallback(
|
|
442
|
+
(onChange) => key ? subscribeSharedQuery(key, onChange, cacheTimeMs) : () => {
|
|
443
|
+
},
|
|
444
|
+
[key, cacheTimeMs]
|
|
445
|
+
), emptySnapshot = React.useMemo(
|
|
446
|
+
() => ({
|
|
447
|
+
data: void 0,
|
|
448
|
+
error: null,
|
|
449
|
+
status: "idle",
|
|
450
|
+
updatedAt: null,
|
|
451
|
+
isFetching: false
|
|
452
|
+
}),
|
|
453
|
+
[]
|
|
454
|
+
), getSnapshot = React.useCallback(() => key ? getOrCreateEntry(key).snapshot : emptySnapshot, [key, emptySnapshot]), snapshot = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot), selectedData = React.useMemo(() => select ? select(snapshot.data) : snapshot.data, [snapshot.data, select]), prevSelectedRef = React.useRef(selectedData), stableSelectedData = React.useMemo(() => Object.is(prevSelectedRef.current, selectedData) ? prevSelectedRef.current : (prevSelectedRef.current = selectedData, selectedData), [selectedData]), fetchedKeyRef = React.useRef(null);
|
|
455
|
+
React.useEffect(() => {
|
|
456
|
+
if (!key || !enabled) {
|
|
457
|
+
fetchedKeyRef.current = null;
|
|
319
458
|
return;
|
|
320
459
|
}
|
|
321
|
-
|
|
460
|
+
let entry = getOrCreateEntry(key);
|
|
461
|
+
if (entry.queryFn = stableQueryFn, fetchedKeyRef.current === key && entry.snapshot.status === "success")
|
|
462
|
+
return;
|
|
463
|
+
let current = entry.snapshot, isStale = current.updatedAt === null || Date.now() - current.updatedAt >= staleTimeMs;
|
|
464
|
+
refetchOnMount === true || current.status === "idle" || refetchOnMount === "stale" && isStale ? (fetchedKeyRef.current = key, fetchSharedQuery(key, stableQueryFn, {
|
|
465
|
+
staleTimeMs,
|
|
466
|
+
force: refetchOnMount === true
|
|
467
|
+
}).catch(() => {
|
|
468
|
+
})) : fetchedKeyRef.current = key;
|
|
469
|
+
}, [key, enabled, staleTimeMs, refetchOnMount, stableQueryFn]), React.useEffect(() => {
|
|
470
|
+
if (!key || !enabled || refetchIntervalMs === false) return;
|
|
471
|
+
let entry = getOrCreateEntry(key);
|
|
472
|
+
entry.queryFn = stableQueryFn;
|
|
473
|
+
let prev = entry.intervalCounts.get(refetchIntervalMs) ?? 0;
|
|
474
|
+
return entry.intervalCounts.set(refetchIntervalMs, prev + 1), startOrRestartInterval(key, entry), () => {
|
|
475
|
+
let count = entry.intervalCounts.get(refetchIntervalMs) ?? 0;
|
|
476
|
+
count <= 1 ? entry.intervalCounts.delete(refetchIntervalMs) : entry.intervalCounts.set(refetchIntervalMs, count - 1), startOrRestartInterval(key, entry);
|
|
477
|
+
};
|
|
478
|
+
}, [key, enabled, refetchIntervalMs, stableQueryFn]);
|
|
479
|
+
let refetch = React.useCallback(
|
|
480
|
+
async (refetchOptions) => {
|
|
481
|
+
if (key)
|
|
482
|
+
return fetchSharedQuery(key, stableQueryFn, {
|
|
483
|
+
force: true,
|
|
484
|
+
signal: refetchOptions?.signal
|
|
485
|
+
});
|
|
486
|
+
},
|
|
487
|
+
[key, stableQueryFn]
|
|
488
|
+
), abort = React.useCallback(() => {
|
|
489
|
+
if (!key) return;
|
|
490
|
+
let entry = store.get(key);
|
|
491
|
+
entry?.abortController && entry.abortController.abort();
|
|
492
|
+
}, [key]);
|
|
493
|
+
return React.useMemo(
|
|
494
|
+
() => ({
|
|
495
|
+
data: stableSelectedData,
|
|
496
|
+
error: snapshot.error,
|
|
497
|
+
status: snapshot.status,
|
|
498
|
+
updatedAt: snapshot.updatedAt,
|
|
499
|
+
isFetching: snapshot.isFetching,
|
|
500
|
+
refetch,
|
|
501
|
+
abort
|
|
502
|
+
}),
|
|
503
|
+
[stableSelectedData, snapshot.error, snapshot.status, snapshot.updatedAt, snapshot.isFetching, refetch, abort]
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
function invalidateSharedQuery(key) {
|
|
507
|
+
let entry = store.get(key);
|
|
508
|
+
entry && setSnapshot(entry, {
|
|
509
|
+
...entry.snapshot,
|
|
510
|
+
updatedAt: null
|
|
511
|
+
// Mark as stale
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
function clearSharedQueryCache() {
|
|
515
|
+
for (let [, entry] of store)
|
|
516
|
+
entry.intervalId && clearInterval(entry.intervalId), entry.gcTimeoutId && clearTimeout(entry.gcTimeoutId), entry.abortController && entry.abortController.abort();
|
|
517
|
+
store.clear();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/hooks/_internal/use-wallet-assets.ts
|
|
521
|
+
var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", NATIVE_SOL_MINT = "So11111111111111111111111111111111111111112";
|
|
522
|
+
function isRecord(value) {
|
|
523
|
+
return typeof value == "object" && value !== null;
|
|
524
|
+
}
|
|
525
|
+
function parseBigInt(value) {
|
|
526
|
+
if (typeof value == "bigint") return value;
|
|
527
|
+
if (typeof value == "number" && Number.isSafeInteger(value)) return BigInt(value);
|
|
528
|
+
if (typeof value == "string")
|
|
322
529
|
try {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
530
|
+
return BigInt(value);
|
|
531
|
+
} catch {
|
|
532
|
+
return 0n;
|
|
533
|
+
}
|
|
534
|
+
return 0n;
|
|
535
|
+
}
|
|
536
|
+
function getParsedTokenAccountInfo(data) {
|
|
537
|
+
if (!isRecord(data)) return null;
|
|
538
|
+
let parsed = data.parsed;
|
|
539
|
+
if (!isRecord(parsed)) return null;
|
|
540
|
+
let info = parsed.info;
|
|
541
|
+
return isRecord(info) ? info : null;
|
|
542
|
+
}
|
|
543
|
+
function parseTokenAccount(account, programId) {
|
|
544
|
+
let info = getParsedTokenAccountInfo(account.account.data);
|
|
545
|
+
if (!info) return null;
|
|
546
|
+
let mint = typeof info.mint == "string" ? info.mint : null, owner = typeof info.owner == "string" ? info.owner : null;
|
|
547
|
+
if (!mint || !owner) return null;
|
|
548
|
+
let tokenAmount = isRecord(info.tokenAmount) ? info.tokenAmount : null, amount = parseBigInt(tokenAmount?.amount), decimals = typeof tokenAmount?.decimals == "number" ? tokenAmount.decimals : 0, state = typeof info.state == "string" ? info.state : void 0;
|
|
549
|
+
return {
|
|
550
|
+
pubkey: String(account.pubkey),
|
|
551
|
+
mint,
|
|
552
|
+
owner,
|
|
553
|
+
amount,
|
|
554
|
+
decimals,
|
|
555
|
+
isFrozen: state === "frozen",
|
|
556
|
+
programId
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function useWalletAssets(options = {}) {
|
|
560
|
+
let {
|
|
561
|
+
enabled = true,
|
|
562
|
+
staleTimeMs = 0,
|
|
563
|
+
cacheTimeMs = 300 * 1e3,
|
|
564
|
+
// 5 minutes
|
|
565
|
+
refetchOnMount = "stale",
|
|
566
|
+
refetchIntervalMs = false,
|
|
567
|
+
client: clientOverride,
|
|
568
|
+
select
|
|
569
|
+
} = options, { address, connected } = useAccount(), { client: providerClient } = useSolanaClient(), rpcClient = clientOverride ?? providerClient, key = React.useMemo(() => {
|
|
570
|
+
if (!enabled || !connected || !address || !rpcClient) return null;
|
|
571
|
+
let rpcUrl = rpcClient.urlOrMoniker instanceof URL ? rpcClient.urlOrMoniker.toString() : String(rpcClient.urlOrMoniker);
|
|
572
|
+
return JSON.stringify(["wallet-assets", rpcUrl, address]);
|
|
573
|
+
}, [enabled, connected, address, rpcClient]), queryFn = React.useCallback(
|
|
574
|
+
async (signal) => {
|
|
575
|
+
if (!connected || !address || !rpcClient)
|
|
576
|
+
return { lamports: 0n, tokenAccounts: [] };
|
|
577
|
+
if (signal.aborted)
|
|
578
|
+
throw new DOMException("Query aborted", "AbortError");
|
|
579
|
+
let rpc = rpcClient.rpc, walletAddress = addresses.address(address), tokenProgramId = addresses.address(TOKEN_PROGRAM_ID), token2022ProgramId = addresses.address(TOKEN_2022_PROGRAM_ID), [balanceResult, tokenAccountsResult, token2022AccountsResult] = await Promise.all([
|
|
580
|
+
rpc.getBalance(walletAddress).send(),
|
|
581
|
+
rpc.getTokenAccountsByOwner(walletAddress, { programId: tokenProgramId }, { encoding: "jsonParsed" }).send(),
|
|
582
|
+
rpc.getTokenAccountsByOwner(
|
|
583
|
+
walletAddress,
|
|
584
|
+
{ programId: token2022ProgramId },
|
|
585
|
+
{ encoding: "jsonParsed" }
|
|
586
|
+
).send()
|
|
587
|
+
]);
|
|
588
|
+
if (signal.aborted)
|
|
589
|
+
throw new DOMException("Query aborted", "AbortError");
|
|
590
|
+
let tokenAccounts = [];
|
|
591
|
+
for (let account of tokenAccountsResult.value) {
|
|
592
|
+
let parsed = parseTokenAccount(account, "token");
|
|
593
|
+
parsed && tokenAccounts.push(parsed);
|
|
345
594
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
595
|
+
for (let account of token2022AccountsResult.value) {
|
|
596
|
+
let parsed = parseTokenAccount(account, "token-2022");
|
|
597
|
+
parsed && tokenAccounts.push(parsed);
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
lamports: balanceResult.value,
|
|
601
|
+
tokenAccounts
|
|
602
|
+
};
|
|
603
|
+
},
|
|
604
|
+
[connected, address, rpcClient]
|
|
605
|
+
), { data, error, status, updatedAt, isFetching, refetch, abort } = useSharedQuery(
|
|
606
|
+
key,
|
|
607
|
+
queryFn,
|
|
608
|
+
{
|
|
609
|
+
enabled,
|
|
610
|
+
staleTimeMs,
|
|
611
|
+
cacheTimeMs,
|
|
612
|
+
refetchOnMount,
|
|
613
|
+
refetchIntervalMs,
|
|
614
|
+
select
|
|
351
615
|
}
|
|
352
|
-
|
|
353
|
-
React.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
616
|
+
), isLoading = status === "loading" || status === "idle";
|
|
617
|
+
return React.useMemo(
|
|
618
|
+
() => ({
|
|
619
|
+
data,
|
|
620
|
+
isLoading,
|
|
621
|
+
isFetching,
|
|
622
|
+
error,
|
|
623
|
+
refetch,
|
|
624
|
+
abort,
|
|
625
|
+
updatedAt
|
|
626
|
+
}),
|
|
627
|
+
[data, isLoading, isFetching, error, refetch, abort, updatedAt]
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/hooks/use-balance.ts
|
|
632
|
+
var LAMPORTS_PER_SOL2 = 1000000000n;
|
|
633
|
+
function formatTokenAccount(account) {
|
|
634
|
+
let formatted = chunkVA6LKXCQ_js.formatBigIntBalance(account.amount, account.decimals, {
|
|
635
|
+
maxDecimals: Math.min(account.decimals, 6)
|
|
636
|
+
});
|
|
637
|
+
return {
|
|
638
|
+
mint: account.mint,
|
|
639
|
+
amount: account.amount,
|
|
640
|
+
decimals: account.decimals,
|
|
641
|
+
formatted
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
function selectBalance(assets) {
|
|
645
|
+
if (!assets)
|
|
646
|
+
return { lamports: 0n, tokens: [] };
|
|
647
|
+
let tokens = assets.tokenAccounts.filter((account) => account.amount > 0n).map(formatTokenAccount);
|
|
648
|
+
return {
|
|
649
|
+
lamports: assets.lamports,
|
|
650
|
+
tokens
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function useBalance(options = {}) {
|
|
654
|
+
let {
|
|
655
|
+
enabled = true,
|
|
656
|
+
autoRefresh = true,
|
|
657
|
+
refreshInterval = 3e4,
|
|
658
|
+
staleTimeMs = 0,
|
|
659
|
+
cacheTimeMs = 300 * 1e3,
|
|
660
|
+
// 5 minutes
|
|
661
|
+
refetchOnMount = "stale",
|
|
662
|
+
client: clientOverride
|
|
663
|
+
} = options, {
|
|
664
|
+
data,
|
|
665
|
+
error,
|
|
666
|
+
isFetching,
|
|
667
|
+
updatedAt,
|
|
668
|
+
refetch: sharedRefetch,
|
|
669
|
+
abort
|
|
670
|
+
} = useWalletAssets({
|
|
671
|
+
enabled,
|
|
672
|
+
staleTimeMs,
|
|
673
|
+
cacheTimeMs,
|
|
674
|
+
refetchOnMount,
|
|
675
|
+
refetchIntervalMs: autoRefresh ? refreshInterval : false,
|
|
676
|
+
client: clientOverride,
|
|
677
|
+
select: selectBalance
|
|
678
|
+
}), lamports = data?.lamports ?? 0n, tokens = data?.tokens ?? [], solBalance = React.useMemo(() => Number(lamports) / Number(LAMPORTS_PER_SOL2), [lamports]), formattedSol = React.useMemo(() => chunkVA6LKXCQ_js.formatLamportsToSolSafe(lamports, { maxDecimals: 4, suffix: true }), [lamports]), visibleError = updatedAt ? null : error, refetch = React.useCallback(
|
|
679
|
+
async (opts) => {
|
|
680
|
+
await sharedRefetch(opts);
|
|
681
|
+
},
|
|
682
|
+
[sharedRefetch]
|
|
683
|
+
);
|
|
361
684
|
return React.useMemo(
|
|
362
685
|
() => ({
|
|
363
686
|
solBalance,
|
|
364
687
|
lamports,
|
|
365
688
|
formattedSol,
|
|
366
689
|
tokens,
|
|
367
|
-
isLoading,
|
|
368
|
-
error,
|
|
369
|
-
refetch
|
|
370
|
-
|
|
690
|
+
isLoading: isFetching,
|
|
691
|
+
error: visibleError,
|
|
692
|
+
refetch,
|
|
693
|
+
abort,
|
|
694
|
+
lastUpdated: updatedAt ? new Date(updatedAt) : null
|
|
371
695
|
}),
|
|
372
|
-
[solBalance, lamports, formattedSol, tokens,
|
|
696
|
+
[solBalance, lamports, formattedSol, tokens, isFetching, visibleError, refetch, abort, updatedAt]
|
|
373
697
|
);
|
|
374
698
|
}
|
|
699
|
+
|
|
700
|
+
// src/utils/abort.ts
|
|
701
|
+
function createTimeoutSignal(ms) {
|
|
702
|
+
if (typeof AbortSignal.timeout == "function")
|
|
703
|
+
return { signal: AbortSignal.timeout(ms), cleanup: () => {
|
|
704
|
+
} };
|
|
705
|
+
let controller = new AbortController(), timeoutId = setTimeout(() => controller.abort(), ms);
|
|
706
|
+
return {
|
|
707
|
+
signal: controller.signal,
|
|
708
|
+
cleanup: () => clearTimeout(timeoutId)
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/hooks/_internal/solana-token-list.ts
|
|
713
|
+
var CLUSTER_CHAIN_IDS = {
|
|
714
|
+
mainnet: 101,
|
|
715
|
+
testnet: 102,
|
|
716
|
+
devnet: 103,
|
|
717
|
+
localnet: 103,
|
|
718
|
+
// Use devnet tokens for localnet
|
|
719
|
+
custom: 101
|
|
720
|
+
// Default to mainnet for custom clusters
|
|
721
|
+
}, TOKEN_LIST_API_BASE_URL = "https://token-list-api.solana.cloud/v1/mints";
|
|
722
|
+
function getTokenListApiUrl(cluster = "mainnet") {
|
|
723
|
+
let chainId = CLUSTER_CHAIN_IDS[cluster];
|
|
724
|
+
return `${TOKEN_LIST_API_BASE_URL}?chainId=${chainId}`;
|
|
725
|
+
}
|
|
726
|
+
var DEFAULT_TIMEOUT_MS = 1e4, TOKEN_LIST_CACHE_MAX_SIZE = 1500, MAX_ADDRESSES_PER_REQUEST = 100, tokenListCache = /* @__PURE__ */ new Map();
|
|
727
|
+
function getCachedTokenListMetadata(mint) {
|
|
728
|
+
let value = tokenListCache.get(mint);
|
|
729
|
+
if (value)
|
|
730
|
+
return tokenListCache.delete(mint), tokenListCache.set(mint, value), value;
|
|
731
|
+
}
|
|
732
|
+
function setCachedTokenListMetadata(mint, value) {
|
|
733
|
+
if (tokenListCache.has(mint) && tokenListCache.delete(mint), tokenListCache.set(mint, value), tokenListCache.size > TOKEN_LIST_CACHE_MAX_SIZE) {
|
|
734
|
+
let oldestKey = tokenListCache.keys().next().value;
|
|
735
|
+
oldestKey && tokenListCache.delete(oldestKey);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function createLinkedSignal(externalSignal, timeoutMs) {
|
|
739
|
+
let controller = new AbortController(), onAbort = () => controller.abort();
|
|
740
|
+
externalSignal && (externalSignal.aborted ? controller.abort() : externalSignal.addEventListener("abort", onAbort, { once: true }));
|
|
741
|
+
let { signal: timeoutSignal, cleanup: cleanupTimeout } = createTimeoutSignal(timeoutMs), onTimeoutAbort = () => controller.abort();
|
|
742
|
+
return timeoutSignal.aborted ? controller.abort() : timeoutSignal.addEventListener("abort", onTimeoutAbort, { once: true }), {
|
|
743
|
+
signal: controller.signal,
|
|
744
|
+
cleanup: () => {
|
|
745
|
+
cleanupTimeout(), externalSignal && externalSignal.removeEventListener("abort", onAbort), timeoutSignal.removeEventListener("abort", onTimeoutAbort);
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
async function fetchSolanaTokenListMetadata(mints, options = {}) {
|
|
750
|
+
let results = /* @__PURE__ */ new Map();
|
|
751
|
+
if (!mints.length) return results;
|
|
752
|
+
let seen = /* @__PURE__ */ new Set(), uncached = [];
|
|
753
|
+
for (let mint of mints) {
|
|
754
|
+
let normalized = mint?.trim();
|
|
755
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
756
|
+
seen.add(normalized);
|
|
757
|
+
let cached = getCachedTokenListMetadata(normalized);
|
|
758
|
+
cached ? results.set(normalized, cached) : uncached.push(normalized);
|
|
759
|
+
}
|
|
760
|
+
if (!uncached.length) return results;
|
|
761
|
+
let timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS, apiUrl = getTokenListApiUrl(options.cluster);
|
|
762
|
+
for (let i = 0; i < uncached.length && !options.signal?.aborted; i += MAX_ADDRESSES_PER_REQUEST) {
|
|
763
|
+
let batch = uncached.slice(i, i + MAX_ADDRESSES_PER_REQUEST), { signal, cleanup } = createLinkedSignal(options.signal, timeoutMs);
|
|
764
|
+
try {
|
|
765
|
+
let response = await fetch(apiUrl, {
|
|
766
|
+
method: "POST",
|
|
767
|
+
headers: { "Content-Type": "application/json" },
|
|
768
|
+
body: JSON.stringify({ addresses: batch }),
|
|
769
|
+
signal
|
|
770
|
+
});
|
|
771
|
+
if (!response.ok) {
|
|
772
|
+
console.warn("[token-list] Solana Token List API error:", response.status, response.statusText);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
let data = await response.json();
|
|
776
|
+
if (!data?.content?.length) continue;
|
|
777
|
+
for (let item of data.content)
|
|
778
|
+
item?.address && (results.set(item.address, item), setCachedTokenListMetadata(item.address, item));
|
|
779
|
+
} catch (error) {
|
|
780
|
+
console.warn("[token-list] Solana Token List API failed:", error);
|
|
781
|
+
} finally {
|
|
782
|
+
cleanup();
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return results;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/utils/image.ts
|
|
789
|
+
function transformImageUrl(url, imageProxy) {
|
|
790
|
+
if (url)
|
|
791
|
+
return imageProxy ? `${imageProxy}${encodeURIComponent(url)}` : url;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/hooks/use-transactions.ts
|
|
375
795
|
function formatDate(timestamp) {
|
|
376
796
|
if (!timestamp)
|
|
377
797
|
return { date: "Unknown", time: "" };
|
|
@@ -383,6 +803,45 @@ function formatDate(timestamp) {
|
|
|
383
803
|
});
|
|
384
804
|
return { date: formattedDate, time: formattedTime };
|
|
385
805
|
}
|
|
806
|
+
var KNOWN_PROGRAMS = {
|
|
807
|
+
"11111111111111111111111111111111": "System Program",
|
|
808
|
+
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA: "Token Program",
|
|
809
|
+
ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL: "Associated Token",
|
|
810
|
+
JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4: "Jupiter",
|
|
811
|
+
whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc: "Orca Whirlpool",
|
|
812
|
+
"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8": "Raydium AMM",
|
|
813
|
+
Stake11111111111111111111111111111111111111: "Stake Program",
|
|
814
|
+
metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s: "Metaplex"
|
|
815
|
+
}, DEFAULT_IGNORED_PROGRAM_IDS = /* @__PURE__ */ new Set([
|
|
816
|
+
"11111111111111111111111111111111",
|
|
817
|
+
// System Program
|
|
818
|
+
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
|
819
|
+
// Token Program
|
|
820
|
+
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
|
821
|
+
// Associated Token
|
|
822
|
+
]);
|
|
823
|
+
function resolveProgramName(programId, programLabels) {
|
|
824
|
+
return programLabels?.[programId] ?? KNOWN_PROGRAMS[programId];
|
|
825
|
+
}
|
|
826
|
+
function pickPrimaryProgramId(programIds) {
|
|
827
|
+
for (let id of programIds)
|
|
828
|
+
if (!DEFAULT_IGNORED_PROGRAM_IDS.has(id)) return id;
|
|
829
|
+
return programIds.values().next().value;
|
|
830
|
+
}
|
|
831
|
+
function getParsedInstructionTypes(message) {
|
|
832
|
+
if (!Array.isArray(message.instructions)) return;
|
|
833
|
+
let types = [];
|
|
834
|
+
for (let ix of message.instructions) {
|
|
835
|
+
if (!ix || typeof ix != "object") continue;
|
|
836
|
+
let parsed = ix.parsed;
|
|
837
|
+
if (!parsed || typeof parsed != "object" || !("type" in parsed)) continue;
|
|
838
|
+
let t = parsed.type;
|
|
839
|
+
if (typeof t == "string" && (types.push(t), types.length >= 10))
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
let unique = [...new Set(types)];
|
|
843
|
+
return unique.length ? unique : void 0;
|
|
844
|
+
}
|
|
386
845
|
function isAccountKey(value) {
|
|
387
846
|
return typeof value == "object" && value !== null && "pubkey" in value && typeof value.pubkey == "string";
|
|
388
847
|
}
|
|
@@ -407,6 +866,13 @@ function isTransactionWithMeta(value) {
|
|
|
407
866
|
function isTransactionMessage(value) {
|
|
408
867
|
return typeof value == "object" && value !== null && "accountKeys" in value && Array.isArray(value.accountKeys);
|
|
409
868
|
}
|
|
869
|
+
function coerceMaybeAddressString(value) {
|
|
870
|
+
if (typeof value == "string") return value;
|
|
871
|
+
if (value && typeof value == "object") {
|
|
872
|
+
let str = String(value);
|
|
873
|
+
if (str && str !== "[object Object]") return str;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
410
876
|
function getAccountKeys(message) {
|
|
411
877
|
return Array.isArray(message.accountKeys) ? message.accountKeys.map((key) => typeof key == "string" ? key : isAccountKey(key) ? key.pubkey : "").filter(Boolean) : [];
|
|
412
878
|
}
|
|
@@ -438,12 +904,12 @@ function parseTokenTransfers(meta, accountKeys, walletAddress) {
|
|
|
438
904
|
return null;
|
|
439
905
|
let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
|
|
440
906
|
if (!isTokenBalance(balance)) return false;
|
|
441
|
-
let accountKey = accountKeys[balance.accountIndex];
|
|
442
|
-
return accountKey && accountKey.trim() === walletAddress.trim() ||
|
|
907
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
908
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
443
909
|
}), ourPostTokens = postTokenBalances.filter((balance) => {
|
|
444
910
|
if (!isTokenBalance(balance)) return false;
|
|
445
|
-
let accountKey = accountKeys[balance.accountIndex];
|
|
446
|
-
return accountKey && accountKey.trim() === walletAddress.trim() ||
|
|
911
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
912
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
447
913
|
}), allMints = /* @__PURE__ */ new Set();
|
|
448
914
|
for (let token of ourPreTokens)
|
|
449
915
|
isTokenBalance(token) && allMints.add(token.mint);
|
|
@@ -487,6 +953,52 @@ function parseTokenTransfers(meta, accountKeys, walletAddress) {
|
|
|
487
953
|
}
|
|
488
954
|
return null;
|
|
489
955
|
}
|
|
956
|
+
function parseTokenAccountClosure(meta, accountKeys, walletAddress) {
|
|
957
|
+
if (!isTransactionMeta(meta))
|
|
958
|
+
return null;
|
|
959
|
+
let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
|
|
960
|
+
if (!isTokenBalance(balance)) return false;
|
|
961
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
962
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
963
|
+
}), ourPostTokens = postTokenBalances.filter((balance) => {
|
|
964
|
+
if (!isTokenBalance(balance)) return false;
|
|
965
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
966
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
967
|
+
}), postKeys = /* @__PURE__ */ new Set();
|
|
968
|
+
for (let token of ourPostTokens)
|
|
969
|
+
isTokenBalance(token) && postKeys.add(`${token.accountIndex}:${token.mint}`);
|
|
970
|
+
for (let token of ourPreTokens) {
|
|
971
|
+
if (!isTokenBalance(token)) continue;
|
|
972
|
+
let key = `${token.accountIndex}:${token.mint}`;
|
|
973
|
+
if (!postKeys.has(key))
|
|
974
|
+
return { tokenMint: token.mint };
|
|
975
|
+
}
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
function parseSwapTokens(meta, accountKeys, walletAddress, solChange) {
|
|
979
|
+
if (!isTransactionMeta(meta))
|
|
980
|
+
return {};
|
|
981
|
+
let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
|
|
982
|
+
if (!isTokenBalance(balance)) return false;
|
|
983
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
984
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
985
|
+
}), ourPostTokens = postTokenBalances.filter((balance) => {
|
|
986
|
+
if (!isTokenBalance(balance)) return false;
|
|
987
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
988
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
989
|
+
}), allMints = /* @__PURE__ */ new Set();
|
|
990
|
+
for (let token of ourPreTokens)
|
|
991
|
+
isTokenBalance(token) && allMints.add(token.mint);
|
|
992
|
+
for (let token of ourPostTokens)
|
|
993
|
+
isTokenBalance(token) && allMints.add(token.mint);
|
|
994
|
+
let fromToken, toToken;
|
|
995
|
+
for (let mint of allMints) {
|
|
996
|
+
let preBal = ourPreTokens.find((b) => isTokenBalance(b) && b.mint === mint), postBal = ourPostTokens.find((b) => isTokenBalance(b) && b.mint === mint), preAmount = isTokenBalance(preBal) && isUiTokenAmount(preBal.uiTokenAmount) ? Number(preBal.uiTokenAmount.amount) : 0, change = (isTokenBalance(postBal) && isUiTokenAmount(postBal.uiTokenAmount) ? Number(postBal.uiTokenAmount.amount) : 0) - preAmount;
|
|
997
|
+
change < 0 && !fromToken ? fromToken = { mint } : change > 0 && !toToken && (toToken = { mint });
|
|
998
|
+
}
|
|
999
|
+
let WRAPPED_SOL_MINT = "So11111111111111111111111111111111111111112";
|
|
1000
|
+
return solChange < -1e-3 && !fromToken ? fromToken = { mint: WRAPPED_SOL_MINT } : solChange > 1e-3 && !toToken && (toToken = { mint: WRAPPED_SOL_MINT }), { fromToken, toToken };
|
|
1001
|
+
}
|
|
490
1002
|
function formatAmount(tokenAmount, tokenDecimals, direction, solChange) {
|
|
491
1003
|
if (tokenAmount !== void 0 && tokenDecimals !== void 0 && direction !== void 0) {
|
|
492
1004
|
let sign = direction === "in" ? "+" : "-", maxDecimals = Math.min(tokenDecimals, 6);
|
|
@@ -495,52 +1007,59 @@ function formatAmount(tokenAmount, tokenDecimals, direction, solChange) {
|
|
|
495
1007
|
if (solChange !== 0)
|
|
496
1008
|
return `${solChange > 0 ? "+" : ""}${solChange.toFixed(4)} SOL`;
|
|
497
1009
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
return imageProxy ? `${imageProxy}${encodeURIComponent(url)}` : url;
|
|
1010
|
+
function throwIfAborted(signal) {
|
|
1011
|
+
if (signal?.aborted)
|
|
1012
|
+
throw new DOMException("Query aborted", "AbortError");
|
|
502
1013
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
method: "POST",
|
|
515
|
-
headers: { "Content-Type": "application/json" },
|
|
516
|
-
body: JSON.stringify({ addresses: uncachedMints }),
|
|
517
|
-
signal: AbortSignal.timeout(5e3)
|
|
518
|
-
});
|
|
519
|
-
if (!response.ok) return results;
|
|
520
|
-
let data = await response.json();
|
|
521
|
-
for (let item of data.content) {
|
|
522
|
-
let metadata = { symbol: item.symbol, icon: item.logoURI };
|
|
523
|
-
results.set(item.address, metadata), tokenMetadataCache.set(item.address, metadata);
|
|
1014
|
+
function clampInt(value, min, max) {
|
|
1015
|
+
return Number.isFinite(value) ? Math.min(max, Math.max(min, Math.floor(value))) : min;
|
|
1016
|
+
}
|
|
1017
|
+
async function mapWithConcurrency(inputs, worker, options) {
|
|
1018
|
+
let concurrency = clampInt(options.concurrency, 1, 32), results = new Array(inputs.length), nextIndex = 0;
|
|
1019
|
+
async function run() {
|
|
1020
|
+
for (; ; ) {
|
|
1021
|
+
throwIfAborted(options.signal);
|
|
1022
|
+
let index = nextIndex;
|
|
1023
|
+
if (nextIndex += 1, index >= inputs.length) return;
|
|
1024
|
+
results[index] = await worker(inputs[index], index);
|
|
524
1025
|
}
|
|
525
|
-
} catch (error) {
|
|
526
|
-
console.warn("[useTransactions] Failed to fetch token metadata:", error);
|
|
527
1026
|
}
|
|
1027
|
+
let runners = Array.from({ length: Math.min(concurrency, inputs.length) }, () => run());
|
|
1028
|
+
return await Promise.all(runners), results;
|
|
1029
|
+
}
|
|
1030
|
+
async function fetchTransactionTokenMetadata(mints, options = {}) {
|
|
1031
|
+
let results = /* @__PURE__ */ new Map();
|
|
1032
|
+
if (!mints.length) return results;
|
|
1033
|
+
let tokenList = await fetchSolanaTokenListMetadata(mints, {
|
|
1034
|
+
timeoutMs: 5e3,
|
|
1035
|
+
signal: options.signal,
|
|
1036
|
+
cluster: options.cluster
|
|
1037
|
+
});
|
|
1038
|
+
for (let [mint, meta] of tokenList)
|
|
1039
|
+
results.set(mint, { symbol: meta.symbol, icon: meta.logoURI });
|
|
528
1040
|
return results;
|
|
529
1041
|
}
|
|
530
1042
|
function useTransactions(options = {}) {
|
|
531
|
-
let {
|
|
532
|
-
|
|
533
|
-
|
|
1043
|
+
let {
|
|
1044
|
+
enabled = true,
|
|
1045
|
+
limit = 10,
|
|
1046
|
+
autoRefresh = false,
|
|
1047
|
+
refreshInterval = 6e4,
|
|
1048
|
+
fetchDetails = true,
|
|
1049
|
+
detailsConcurrency = 6,
|
|
1050
|
+
staleTimeMs = 0,
|
|
1051
|
+
cacheTimeMs = 300 * 1e3,
|
|
1052
|
+
// 5 minutes
|
|
1053
|
+
refetchOnMount = "stale",
|
|
1054
|
+
client: clientOverride
|
|
1055
|
+
} = options, { address, connected } = useAccount(), { cluster } = useCluster(), { client: providerClient } = useSolanaClient(), connectorClient = useConnectorClient(), [paginatedTransactions, setPaginatedTransactions] = React.useState([]), [isPaginationLoading, setIsPaginationLoading] = React.useState(false), [hasMore, setHasMore] = React.useState(true), beforeSignatureRef = React.useRef(void 0), rpcClient = clientOverride ?? providerClient, connectorConfig = connectorClient?.getConfig(), imageProxy = connectorConfig?.imageProxy, programLabels = connectorConfig?.programLabels, parseTransaction = React.useCallback(
|
|
534
1056
|
(tx, walletAddress, sig, blockTime, slot, err, explorerUrl) => {
|
|
535
1057
|
let { date, time } = formatDate(blockTime), baseInfo = {
|
|
536
1058
|
signature: sig,
|
|
537
1059
|
blockTime,
|
|
538
1060
|
slot,
|
|
539
1061
|
status: err ? "failed" : "success",
|
|
540
|
-
error: err ? JSON.stringify(
|
|
541
|
-
err,
|
|
542
|
-
(_key, value) => typeof value == "bigint" ? value.toString() : value
|
|
543
|
-
) : void 0,
|
|
1062
|
+
error: err ? JSON.stringify(err, (_key, value) => typeof value == "bigint" ? value.toString() : value) : void 0,
|
|
544
1063
|
type: "unknown",
|
|
545
1064
|
formattedDate: date,
|
|
546
1065
|
formattedTime: time,
|
|
@@ -555,24 +1074,13 @@ function useTransactions(options = {}) {
|
|
|
555
1074
|
let { message } = transaction;
|
|
556
1075
|
if (!isTransactionMessage(message))
|
|
557
1076
|
return baseInfo;
|
|
558
|
-
let accountKeys = getAccountKeys(message), walletIndex = accountKeys.findIndex((
|
|
1077
|
+
let accountKeys = getAccountKeys(message), walletIndex = accountKeys.findIndex((key2) => key2.trim() === walletAddress.trim());
|
|
559
1078
|
if (walletIndex === -1)
|
|
560
1079
|
return baseInfo;
|
|
561
|
-
let { balanceChange, solChange } = parseSolChange(meta, walletIndex), programIds = detectProgramIds(message, accountKeys), hasJupiter = programIds.has("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"), hasOrca = programIds.has("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"), hasRaydium = programIds.has("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"), hasStake = programIds.has("Stake11111111111111111111111111111111111111"), hasMetaplex = programIds.has("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), hasSystemProgram = programIds.has("11111111111111111111111111111111"), hasTokenProgram = programIds.has("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), type = "unknown", direction, counterparty, tokenMint, tokenAmount, tokenDecimals;
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
type = "stake";
|
|
566
|
-
else if (hasMetaplex)
|
|
567
|
-
type = "nft";
|
|
568
|
-
else if (hasSystemProgram && Math.abs(balanceChange) > 0)
|
|
569
|
-
type = balanceChange > 0 ? "received" : "sent", direction = balanceChange > 0 ? "in" : "out", tokenMint = "So11111111111111111111111111111111111111112", accountKeys.length >= 2 && (counterparty = accountKeys.find(
|
|
570
|
-
(key, idx) => idx !== walletIndex && key !== "11111111111111111111111111111111"
|
|
571
|
-
));
|
|
572
|
-
else if (hasTokenProgram) {
|
|
573
|
-
let tokenTransfer = parseTokenTransfers(meta, accountKeys, walletAddress);
|
|
574
|
-
tokenTransfer && (type = tokenTransfer.type, direction = tokenTransfer.direction, tokenMint = tokenTransfer.tokenMint, tokenAmount = tokenTransfer.tokenAmount, tokenDecimals = tokenTransfer.tokenDecimals);
|
|
575
|
-
} else programIds.size > 0 && (type = "program");
|
|
1080
|
+
let { balanceChange, solChange } = parseSolChange(meta, walletIndex), programIds = detectProgramIds(message, accountKeys), hasJupiter = programIds.has("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"), hasOrca = programIds.has("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"), hasRaydium = programIds.has("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"), hasStake = programIds.has("Stake11111111111111111111111111111111111111"), hasMetaplex = programIds.has("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), hasSystemProgram = programIds.has("11111111111111111111111111111111"), hasTokenProgram = programIds.has("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), tokenTransfer = hasTokenProgram ? parseTokenTransfers(meta, accountKeys, walletAddress) : null, tokenAccountClosure = hasTokenProgram ? parseTokenAccountClosure(meta, accountKeys, walletAddress) : null, inferredSwapTokens = parseSwapTokens(meta, accountKeys, walletAddress, solChange), inferredSwapFromMint = inferredSwapTokens.fromToken?.mint, inferredSwapToMint = inferredSwapTokens.toToken?.mint, hasNonTrivialProgram = [...programIds].some((id) => !DEFAULT_IGNORED_PROGRAM_IDS.has(id)), hasInferredSwap = !!(inferredSwapFromMint && inferredSwapToMint) && inferredSwapFromMint !== inferredSwapToMint && hasNonTrivialProgram && !tokenAccountClosure, programId = pickPrimaryProgramId(programIds), programName = programId ? resolveProgramName(programId, programLabels) : void 0, programIdsArray = [...programIds], instructionTypes = getParsedInstructionTypes(message), instructionCount = Array.isArray(message.instructions) ? message.instructions.length : void 0, type = "unknown", direction, counterparty, tokenMint, tokenAmount, tokenDecimals, swapFromToken, swapToToken;
|
|
1081
|
+
hasJupiter || hasOrca || hasRaydium ? (type = "swap", inferredSwapTokens.fromToken && (swapFromToken = { mint: inferredSwapTokens.fromToken.mint }), inferredSwapTokens.toToken && (swapToToken = { mint: inferredSwapTokens.toToken.mint })) : hasStake ? type = "stake" : hasMetaplex ? type = "nft" : hasInferredSwap ? (type = "swap", swapFromToken = { mint: inferredSwapFromMint }, swapToToken = { mint: inferredSwapToMint }) : tokenTransfer ? (type = tokenTransfer.type, direction = tokenTransfer.direction, tokenMint = tokenTransfer.tokenMint, tokenAmount = tokenTransfer.tokenAmount, tokenDecimals = tokenTransfer.tokenDecimals) : tokenAccountClosure ? (type = "tokenAccountClosed", tokenMint = tokenAccountClosure.tokenMint, direction = solChange > 0 ? "in" : void 0) : hasSystemProgram && Math.abs(balanceChange) > 0 ? (type = balanceChange > 0 ? "received" : "sent", direction = balanceChange > 0 ? "in" : "out", tokenMint = "So11111111111111111111111111111111111111112", accountKeys.length >= 2 && (counterparty = accountKeys.find(
|
|
1082
|
+
(key2, idx) => idx !== walletIndex && key2 !== "11111111111111111111111111111111"
|
|
1083
|
+
))) : programIds.size > 0 && (type = "program");
|
|
576
1084
|
let formattedAmount = formatAmount(tokenAmount, tokenDecimals, direction, solChange);
|
|
577
1085
|
return {
|
|
578
1086
|
...baseInfo,
|
|
@@ -581,131 +1089,189 @@ function useTransactions(options = {}) {
|
|
|
581
1089
|
amount: tokenAmount ?? Math.abs(solChange),
|
|
582
1090
|
formattedAmount,
|
|
583
1091
|
tokenMint,
|
|
584
|
-
counterparty: counterparty ? `${counterparty.slice(0, 4)}...${counterparty.slice(-4)}` : void 0
|
|
1092
|
+
counterparty: counterparty ? `${counterparty.slice(0, 4)}...${counterparty.slice(-4)}` : void 0,
|
|
1093
|
+
swapFromToken,
|
|
1094
|
+
swapToToken,
|
|
1095
|
+
programId,
|
|
1096
|
+
programName,
|
|
1097
|
+
programIds: programIdsArray.length ? programIdsArray : void 0,
|
|
1098
|
+
instructionTypes,
|
|
1099
|
+
instructionCount
|
|
585
1100
|
};
|
|
586
1101
|
} catch (parseError) {
|
|
587
1102
|
return console.warn("Failed to parse transaction:", parseError), baseInfo;
|
|
588
1103
|
}
|
|
589
1104
|
},
|
|
590
|
-
[]
|
|
591
|
-
),
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
1105
|
+
[programLabels]
|
|
1106
|
+
), key = React.useMemo(() => {
|
|
1107
|
+
if (!enabled || !connected || !address || !rpcClient || !cluster) return null;
|
|
1108
|
+
let rpcUrl = rpcClient.urlOrMoniker instanceof URL ? rpcClient.urlOrMoniker.toString() : String(rpcClient.urlOrMoniker);
|
|
1109
|
+
return JSON.stringify(["wallet-transactions", rpcUrl, address, cluster.id, limit, fetchDetails]);
|
|
1110
|
+
}, [enabled, connected, address, rpcClient, cluster, limit, fetchDetails]);
|
|
1111
|
+
React.useEffect(() => {
|
|
1112
|
+
beforeSignatureRef.current = void 0, setPaginatedTransactions([]), setIsPaginationLoading(false), setHasMore(true);
|
|
1113
|
+
}, [key]);
|
|
1114
|
+
let fetchAndEnrichTransactions = React.useCallback(
|
|
1115
|
+
async (beforeSignature, currentCluster, signal) => {
|
|
1116
|
+
if (!rpcClient || !address)
|
|
1117
|
+
return { transactions: [], hasMore: false };
|
|
1118
|
+
throwIfAborted(signal);
|
|
1119
|
+
let rpc = rpcClient.rpc, walletAddress = addresses.address(address), signaturesResult = await rpc.getSignaturesForAddress(walletAddress, {
|
|
1120
|
+
limit,
|
|
1121
|
+
...beforeSignature ? { before: keys.signature(beforeSignature) } : {}
|
|
1122
|
+
}).send();
|
|
1123
|
+
throwIfAborted(signal);
|
|
1124
|
+
let newTransactions;
|
|
1125
|
+
if (fetchDetails && signaturesResult.length > 0) {
|
|
1126
|
+
let txDetails = await mapWithConcurrency(
|
|
1127
|
+
signaturesResult,
|
|
1128
|
+
async (sig) => rpc.getTransaction(keys.signature(String(sig.signature)), {
|
|
1129
|
+
encoding: "jsonParsed",
|
|
1130
|
+
maxSupportedTransactionVersion: 0
|
|
1131
|
+
}).send().catch(() => null),
|
|
1132
|
+
{ concurrency: detailsConcurrency, signal }
|
|
1133
|
+
);
|
|
1134
|
+
throwIfAborted(signal), newTransactions = signaturesResult.map((sig, idx) => {
|
|
1135
|
+
let blockTimeNum = sig.blockTime ? Number(sig.blockTime) : null, tx = txDetails[idx];
|
|
1136
|
+
return parseTransaction(
|
|
1137
|
+
tx,
|
|
1138
|
+
address,
|
|
1139
|
+
String(sig.signature),
|
|
1140
|
+
blockTimeNum,
|
|
1141
|
+
Number(sig.slot),
|
|
1142
|
+
sig.err,
|
|
1143
|
+
chunkVA6LKXCQ_js.getTransactionUrl(String(sig.signature), currentCluster)
|
|
1144
|
+
);
|
|
1145
|
+
});
|
|
1146
|
+
} else
|
|
1147
|
+
newTransactions = signaturesResult.map((sig) => {
|
|
1148
|
+
let blockTimeNum = sig.blockTime ? Number(sig.blockTime) : null, { date, time } = formatDate(blockTimeNum);
|
|
1149
|
+
return {
|
|
1150
|
+
signature: String(sig.signature),
|
|
1151
|
+
blockTime: blockTimeNum,
|
|
1152
|
+
slot: Number(sig.slot),
|
|
1153
|
+
status: sig.err ? "failed" : "success",
|
|
1154
|
+
error: sig.err ? JSON.stringify(sig.err) : void 0,
|
|
1155
|
+
type: "unknown",
|
|
1156
|
+
formattedDate: date,
|
|
1157
|
+
formattedTime: time,
|
|
1158
|
+
explorerUrl: chunkVA6LKXCQ_js.getTransactionUrl(String(sig.signature), currentCluster)
|
|
1159
|
+
};
|
|
1160
|
+
});
|
|
1161
|
+
let mintsToFetch = [
|
|
1162
|
+
.../* @__PURE__ */ new Set([
|
|
1163
|
+
...newTransactions.filter((tx) => tx.tokenMint).map((tx) => tx.tokenMint),
|
|
1164
|
+
...newTransactions.filter((tx) => tx.swapFromToken?.mint).map((tx) => tx.swapFromToken.mint),
|
|
1165
|
+
...newTransactions.filter((tx) => tx.swapToToken?.mint).map((tx) => tx.swapToToken.mint)
|
|
1166
|
+
])
|
|
1167
|
+
];
|
|
1168
|
+
if (mintsToFetch.length > 0) {
|
|
1169
|
+
throwIfAborted(signal);
|
|
1170
|
+
let tokenMetadata = await fetchTransactionTokenMetadata(mintsToFetch, {
|
|
1171
|
+
signal,
|
|
1172
|
+
cluster: chunkVA6LKXCQ_js.getClusterType(currentCluster)
|
|
1173
|
+
});
|
|
1174
|
+
tokenMetadata.size > 0 && (newTransactions = newTransactions.map((tx) => {
|
|
1175
|
+
let enrichedTx = { ...tx };
|
|
1176
|
+
if (tx.tokenMint && tokenMetadata.has(tx.tokenMint)) {
|
|
1177
|
+
let meta = tokenMetadata.get(tx.tokenMint);
|
|
1178
|
+
enrichedTx = {
|
|
1179
|
+
...enrichedTx,
|
|
1180
|
+
tokenSymbol: meta.symbol,
|
|
1181
|
+
tokenIcon: transformImageUrl(meta.icon, imageProxy),
|
|
1182
|
+
formattedAmount: tx.formattedAmount ? `${tx.formattedAmount} ${meta.symbol}` : tx.formattedAmount
|
|
635
1183
|
};
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
return {
|
|
646
|
-
...tx,
|
|
647
|
-
tokenSymbol: meta.symbol,
|
|
648
|
-
tokenIcon: transformImageUrl(meta.icon, imageProxy),
|
|
649
|
-
// Update formatted amount with symbol
|
|
650
|
-
formattedAmount: tx.formattedAmount ? `${tx.formattedAmount} ${meta.symbol}` : tx.formattedAmount
|
|
651
|
-
};
|
|
1184
|
+
}
|
|
1185
|
+
if (tx.swapFromToken?.mint && tokenMetadata.has(tx.swapFromToken.mint)) {
|
|
1186
|
+
let meta = tokenMetadata.get(tx.swapFromToken.mint);
|
|
1187
|
+
enrichedTx = {
|
|
1188
|
+
...enrichedTx,
|
|
1189
|
+
swapFromToken: {
|
|
1190
|
+
...tx.swapFromToken,
|
|
1191
|
+
symbol: meta.symbol,
|
|
1192
|
+
icon: transformImageUrl(meta.icon, imageProxy)
|
|
652
1193
|
}
|
|
653
|
-
|
|
654
|
-
});
|
|
655
|
-
setTransactions(loadMore ? (prev) => [...prev.slice(0, -newTransactions.length), ...enrichedTransactions] : enrichedTransactions);
|
|
1194
|
+
};
|
|
656
1195
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
1196
|
+
if (tx.swapToToken?.mint && tokenMetadata.has(tx.swapToToken.mint)) {
|
|
1197
|
+
let meta = tokenMetadata.get(tx.swapToToken.mint);
|
|
1198
|
+
enrichedTx = {
|
|
1199
|
+
...enrichedTx,
|
|
1200
|
+
swapToToken: {
|
|
1201
|
+
...tx.swapToToken,
|
|
1202
|
+
symbol: meta.symbol,
|
|
1203
|
+
icon: transformImageUrl(meta.icon, imageProxy)
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
662
1206
|
}
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
|
|
1207
|
+
return enrichedTx;
|
|
1208
|
+
}));
|
|
1209
|
+
}
|
|
1210
|
+
return {
|
|
1211
|
+
transactions: newTransactions,
|
|
1212
|
+
hasMore: newTransactions.length === limit
|
|
1213
|
+
};
|
|
1214
|
+
},
|
|
1215
|
+
[rpcClient, address, limit, fetchDetails, detailsConcurrency, parseTransaction, imageProxy]
|
|
1216
|
+
), queryFn = React.useCallback(
|
|
1217
|
+
async (signal) => {
|
|
1218
|
+
if (!connected || !address || !rpcClient || !cluster)
|
|
1219
|
+
return [];
|
|
1220
|
+
throwIfAborted(signal);
|
|
1221
|
+
let result = await fetchAndEnrichTransactions(void 0, cluster, signal);
|
|
1222
|
+
return throwIfAborted(signal), result.transactions;
|
|
1223
|
+
},
|
|
1224
|
+
[connected, address, rpcClient, cluster, fetchAndEnrichTransactions]
|
|
1225
|
+
), {
|
|
1226
|
+
data: initialTransactions,
|
|
1227
|
+
error,
|
|
1228
|
+
isFetching: isInitialLoading,
|
|
1229
|
+
updatedAt,
|
|
1230
|
+
refetch: sharedRefetch,
|
|
1231
|
+
abort
|
|
1232
|
+
} = useSharedQuery(key, queryFn, {
|
|
1233
|
+
enabled,
|
|
1234
|
+
staleTimeMs,
|
|
1235
|
+
cacheTimeMs,
|
|
1236
|
+
refetchOnMount,
|
|
1237
|
+
refetchIntervalMs: autoRefresh ? refreshInterval : false
|
|
1238
|
+
});
|
|
1239
|
+
React.useEffect(() => {
|
|
1240
|
+
initialTransactions && (beforeSignatureRef.current = initialTransactions.length ? initialTransactions[initialTransactions.length - 1].signature : void 0, setHasMore(initialTransactions.length === limit), setPaginatedTransactions((prev) => prev.length ? [] : prev));
|
|
1241
|
+
}, [initialTransactions, limit]);
|
|
1242
|
+
let loadMoreFn = React.useCallback(async () => {
|
|
1243
|
+
if (!(!hasMore || isPaginationLoading || !cluster)) {
|
|
1244
|
+
setIsPaginationLoading(true);
|
|
1245
|
+
try {
|
|
1246
|
+
let result = await fetchAndEnrichTransactions(beforeSignatureRef.current, cluster);
|
|
1247
|
+
result.transactions.length > 0 && (beforeSignatureRef.current = result.transactions[result.transactions.length - 1].signature, setPaginatedTransactions((prev) => [...prev, ...result.transactions])), setHasMore(result.hasMore);
|
|
666
1248
|
} catch (err) {
|
|
667
|
-
|
|
1249
|
+
console.error("Failed to load more transactions:", err);
|
|
668
1250
|
} finally {
|
|
669
|
-
|
|
1251
|
+
setIsPaginationLoading(false);
|
|
670
1252
|
}
|
|
1253
|
+
}
|
|
1254
|
+
}, [hasMore, isPaginationLoading, cluster, fetchAndEnrichTransactions]), refetch = React.useCallback(
|
|
1255
|
+
async (opts) => {
|
|
1256
|
+
beforeSignatureRef.current = void 0, setPaginatedTransactions([]), setHasMore(true), await sharedRefetch(opts);
|
|
671
1257
|
},
|
|
672
|
-
[
|
|
673
|
-
),
|
|
674
|
-
|
|
675
|
-
}, [fetchTransactions]), loadMoreFn = React.useCallback(async () => {
|
|
676
|
-
hasMore && !isLoading && await fetchTransactions(true);
|
|
677
|
-
}, [hasMore, isLoading, fetchTransactions]);
|
|
678
|
-
return React.useEffect(() => {
|
|
679
|
-
let prevDeps = prevDepsRef.current, currentDeps = { connected, address, cluster };
|
|
680
|
-
(!prevDeps || prevDeps.connected !== connected || prevDeps.address !== address || prevDeps.cluster !== cluster) && (prevDepsRef.current = currentDeps, beforeSignatureRef.current = void 0, fetchTransactions(false));
|
|
681
|
-
}, [connected, address, cluster, fetchTransactions]), React.useEffect(() => {
|
|
682
|
-
if (!connected || !autoRefresh) return;
|
|
683
|
-
let interval = setInterval(refetch, refreshInterval);
|
|
684
|
-
return () => clearInterval(interval);
|
|
685
|
-
}, [connected, autoRefresh, refreshInterval, refetch]), React.useMemo(
|
|
1258
|
+
[sharedRefetch]
|
|
1259
|
+
), transactions = React.useMemo(() => [...initialTransactions ?? [], ...paginatedTransactions], [initialTransactions, paginatedTransactions]), isLoading = isInitialLoading || isPaginationLoading, visibleError = updatedAt ? null : error;
|
|
1260
|
+
return React.useMemo(
|
|
686
1261
|
() => ({
|
|
687
1262
|
transactions,
|
|
688
1263
|
isLoading,
|
|
689
|
-
error,
|
|
1264
|
+
error: visibleError,
|
|
690
1265
|
hasMore,
|
|
691
1266
|
loadMore: loadMoreFn,
|
|
692
1267
|
refetch,
|
|
693
|
-
|
|
1268
|
+
abort,
|
|
1269
|
+
lastUpdated: updatedAt ? new Date(updatedAt) : null
|
|
694
1270
|
}),
|
|
695
|
-
[transactions, isLoading,
|
|
1271
|
+
[transactions, isLoading, visibleError, hasMore, loadMoreFn, refetch, abort, updatedAt]
|
|
696
1272
|
);
|
|
697
1273
|
}
|
|
698
|
-
|
|
699
|
-
if (typeof AbortSignal.timeout == "function")
|
|
700
|
-
return { signal: AbortSignal.timeout(ms), cleanup: () => {
|
|
701
|
-
} };
|
|
702
|
-
let controller = new AbortController(), timeoutId = setTimeout(() => controller.abort(), ms);
|
|
703
|
-
return {
|
|
704
|
-
signal: controller.signal,
|
|
705
|
-
cleanup: () => clearTimeout(timeoutId)
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
var NATIVE_MINT = "So11111111111111111111111111111111111111112", CACHE_MAX_SIZE = 500, PRICE_CACHE_TTL = 6e4, STALE_CLEANUP_INTERVAL = 12e4, COINGECKO_DEFAULT_MAX_RETRIES = 3, COINGECKO_DEFAULT_BASE_DELAY = 1e3, COINGECKO_DEFAULT_MAX_TIMEOUT = 3e4, COINGECKO_API_BASE_URL = "https://api.coingecko.com/api/v3", LRUCache = class {
|
|
1274
|
+
var CACHE_MAX_SIZE = 500, PRICE_CACHE_TTL = 6e4, STALE_CLEANUP_INTERVAL = 12e4, COINGECKO_DEFAULT_MAX_RETRIES = 3, COINGECKO_DEFAULT_BASE_DELAY = 1e3, COINGECKO_DEFAULT_MAX_TIMEOUT = 3e4, COINGECKO_API_BASE_URL = "https://api.coingecko.com/api/v3", LRUCache = class {
|
|
709
1275
|
constructor(maxSize, options) {
|
|
710
1276
|
chunkI64FD2EH_js.__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
711
1277
|
chunkI64FD2EH_js.__publicField(this, "maxSize");
|
|
@@ -745,10 +1311,6 @@ var NATIVE_MINT = "So11111111111111111111111111111111111111112", CACHE_MAX_SIZE
|
|
|
745
1311
|
get size() {
|
|
746
1312
|
return this.cache.size;
|
|
747
1313
|
}
|
|
748
|
-
/**
|
|
749
|
-
* Prune stale entries based on TTL.
|
|
750
|
-
* Only works if getTtl and getTimestamp are provided.
|
|
751
|
-
*/
|
|
752
1314
|
pruneStale() {
|
|
753
1315
|
if (!this.getTtl || !this.getTimestamp) return 0;
|
|
754
1316
|
let now = Date.now(), pruned = 0;
|
|
@@ -773,27 +1335,6 @@ function stopCacheCleanup() {
|
|
|
773
1335
|
function clearTokenCaches() {
|
|
774
1336
|
metadataCache.clear(), priceCache.clear();
|
|
775
1337
|
}
|
|
776
|
-
async function fetchSolanaTokenMetadata(mints) {
|
|
777
|
-
let results = /* @__PURE__ */ new Map();
|
|
778
|
-
if (mints.length === 0) return results;
|
|
779
|
-
let { signal, cleanup } = createTimeoutSignal(1e4);
|
|
780
|
-
try {
|
|
781
|
-
let response = await fetch("https://token-list-api.solana.cloud/v1/mints?chainId=101", {
|
|
782
|
-
method: "POST",
|
|
783
|
-
headers: { "Content-Type": "application/json" },
|
|
784
|
-
body: JSON.stringify({ addresses: mints }),
|
|
785
|
-
signal
|
|
786
|
-
});
|
|
787
|
-
if (cleanup(), !response.ok)
|
|
788
|
-
throw new Error(`Solana Token List API error: ${response.status}`);
|
|
789
|
-
let data = await response.json();
|
|
790
|
-
for (let item of data.content)
|
|
791
|
-
results.set(item.address, item);
|
|
792
|
-
} catch (error) {
|
|
793
|
-
cleanup(), console.warn("[useTokens] Solana Token List API failed:", error);
|
|
794
|
-
}
|
|
795
|
-
return results;
|
|
796
|
-
}
|
|
797
1338
|
function calculateBackoffDelay(attempt, baseDelay, retryAfter) {
|
|
798
1339
|
if (retryAfter !== void 0 && retryAfter > 0) {
|
|
799
1340
|
let jitter2 = Math.random() * 500;
|
|
@@ -829,26 +1370,17 @@ async function fetchCoinGeckoPrices(coingeckoIds, config) {
|
|
|
829
1370
|
let elapsedTime = Date.now() - startTime;
|
|
830
1371
|
if (elapsedTime >= maxTimeout) {
|
|
831
1372
|
console.warn(
|
|
832
|
-
`[useTokens] CoinGecko API: Total timeout (${maxTimeout}ms) exceeded after ${attempt} attempts
|
|
1373
|
+
`[useTokens] CoinGecko API: Total timeout (${maxTimeout}ms) exceeded after ${attempt} attempts.`
|
|
833
1374
|
);
|
|
834
1375
|
break;
|
|
835
1376
|
}
|
|
836
1377
|
let remainingTimeout = maxTimeout - elapsedTime, requestTimeout = Math.min(1e4, remainingTimeout), { signal, cleanup } = createTimeoutSignal(requestTimeout);
|
|
837
1378
|
try {
|
|
838
|
-
let response = await fetch(url, {
|
|
839
|
-
headers,
|
|
840
|
-
signal
|
|
841
|
-
});
|
|
1379
|
+
let response = await fetch(url, { headers, signal });
|
|
842
1380
|
if (cleanup(), response.status === 429) {
|
|
843
1381
|
let retryAfter = parseRetryAfter(response.headers.get("Retry-After")), delay = calculateBackoffDelay(attempt, baseDelay, retryAfter);
|
|
844
|
-
if (console.warn(
|
|
845
|
-
`[useTokens] CoinGecko API rate limited (429). Attempt ${attempt + 1}/${maxRetries + 1}. Retry-After: ${retryAfter ?? "not specified"}s. Waiting ${Math.round(delay)}ms before retry. Consider adding an API key for higher limits: https://www.coingecko.com/en/api/pricing`
|
|
846
|
-
), Date.now() - startTime + delay >= maxTimeout) {
|
|
847
|
-
console.warn(
|
|
848
|
-
`[useTokens] CoinGecko API: Skipping retry - would exceed total timeout (${maxTimeout}ms). Returning cached/partial results.`
|
|
849
|
-
);
|
|
1382
|
+
if (console.warn(`[useTokens] CoinGecko API rate limited (429). Waiting ${Math.round(delay)}ms.`), Date.now() - startTime + delay >= maxTimeout)
|
|
850
1383
|
break;
|
|
851
|
-
}
|
|
852
1384
|
await new Promise((resolve) => setTimeout(resolve, delay)), attempt++;
|
|
853
1385
|
continue;
|
|
854
1386
|
}
|
|
@@ -859,174 +1391,195 @@ async function fetchCoinGeckoPrices(coingeckoIds, config) {
|
|
|
859
1391
|
priceData?.usd !== void 0 && (results.set(id, priceData.usd), priceCache.set(id, { price: priceData.usd, timestamp: fetchTime }));
|
|
860
1392
|
return results;
|
|
861
1393
|
} catch (error) {
|
|
862
|
-
if (cleanup(), lastError = error,
|
|
863
|
-
`[useTokens] CoinGecko API request timed out. Attempt ${attempt + 1}/${maxRetries + 1}.`
|
|
864
|
-
) : console.warn(
|
|
865
|
-
`[useTokens] CoinGecko API request failed. Attempt ${attempt + 1}/${maxRetries + 1}:`,
|
|
866
|
-
error
|
|
867
|
-
), attempt < maxRetries) {
|
|
1394
|
+
if (cleanup(), lastError = error, attempt < maxRetries) {
|
|
868
1395
|
let delay = calculateBackoffDelay(attempt, baseDelay);
|
|
869
1396
|
Date.now() - startTime + delay < maxTimeout && await new Promise((resolve) => setTimeout(resolve, delay));
|
|
870
1397
|
}
|
|
871
1398
|
attempt++;
|
|
872
1399
|
}
|
|
873
1400
|
}
|
|
874
|
-
return attempt > maxRetries && console.warn(
|
|
875
|
-
`[useTokens] CoinGecko API: All ${maxRetries + 1} attempts failed. Returning cached/partial results. Last error: ${lastError?.message ?? "Unknown error"}. If you are frequently rate limited, consider adding an API key: https://www.coingecko.com/en/api/pricing`
|
|
876
|
-
), results;
|
|
1401
|
+
return attempt > maxRetries && lastError && console.warn(`[useTokens] CoinGecko API: All attempts failed. Last error: ${lastError.message}`), results;
|
|
877
1402
|
}
|
|
878
|
-
async function fetchTokenMetadataHybrid(mints, coingeckoConfig) {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
let uncachedMints = [], now = Date.now();
|
|
882
|
-
for (let mint of mints) {
|
|
883
|
-
let cached = metadataCache.get(mint);
|
|
884
|
-
cached ? (cached.coingeckoId && (!priceCache.get(cached.coingeckoId) || now - (priceCache.get(cached.coingeckoId)?.timestamp ?? 0) >= PRICE_CACHE_TTL) && cached.coingeckoId && uncachedMints.push(mint), results.set(mint, cached)) : uncachedMints.push(mint);
|
|
885
|
-
}
|
|
886
|
-
if (uncachedMints.length === 0) return results;
|
|
887
|
-
let solanaMetadata = await fetchSolanaTokenMetadata(uncachedMints), coingeckoIdToMint = /* @__PURE__ */ new Map();
|
|
888
|
-
for (let [mint, meta] of solanaMetadata)
|
|
889
|
-
meta.extensions?.coingeckoId && coingeckoIdToMint.set(meta.extensions.coingeckoId, mint);
|
|
1403
|
+
async function fetchTokenMetadataHybrid(mints, coingeckoConfig, options) {
|
|
1404
|
+
if (mints.length === 0) return false;
|
|
1405
|
+
let now = Date.now(), mintsNeedingTokenList = [], staleCoingeckoIdToMint = /* @__PURE__ */ new Map();
|
|
890
1406
|
for (let mint of mints) {
|
|
891
1407
|
let cached = metadataCache.get(mint);
|
|
892
|
-
|
|
1408
|
+
if (!cached) {
|
|
1409
|
+
mintsNeedingTokenList.push(mint);
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1412
|
+
if (!cached.coingeckoId) continue;
|
|
1413
|
+
let priceEntry = priceCache.get(cached.coingeckoId);
|
|
1414
|
+
!!(priceEntry && now - priceEntry.timestamp < PRICE_CACHE_TTL) || staleCoingeckoIdToMint.set(cached.coingeckoId, mint);
|
|
893
1415
|
}
|
|
894
|
-
let
|
|
895
|
-
|
|
896
|
-
|
|
1416
|
+
let didUpdate = false, tokenListMetadata = await fetchSolanaTokenListMetadata(mintsNeedingTokenList, {
|
|
1417
|
+
timeoutMs: 1e4,
|
|
1418
|
+
cluster: options?.cluster
|
|
1419
|
+
});
|
|
1420
|
+
for (let [mint, meta] of tokenListMetadata) {
|
|
1421
|
+
let coingeckoId = meta.extensions?.coingeckoId, usdPrice = (coingeckoId ? priceCache.get(coingeckoId) : void 0)?.price, combined = {
|
|
897
1422
|
address: meta.address,
|
|
898
|
-
name: meta.address ===
|
|
1423
|
+
name: meta.address === NATIVE_SOL_MINT ? "Solana" : meta.name,
|
|
899
1424
|
symbol: meta.symbol,
|
|
900
1425
|
decimals: meta.decimals,
|
|
901
1426
|
logoURI: meta.logoURI,
|
|
902
1427
|
coingeckoId,
|
|
903
1428
|
usdPrice
|
|
904
|
-
};
|
|
905
|
-
|
|
1429
|
+
}, existing = metadataCache.get(mint);
|
|
1430
|
+
(!existing || existing.name !== combined.name || existing.symbol !== combined.symbol || existing.decimals !== combined.decimals || existing.logoURI !== combined.logoURI || existing.coingeckoId !== combined.coingeckoId || existing.usdPrice !== combined.usdPrice) && (didUpdate = true, metadataCache.set(mint, combined));
|
|
906
1431
|
}
|
|
1432
|
+
didUpdate && options?.onUpdate?.();
|
|
1433
|
+
let coingeckoIdToMint = new Map(staleCoingeckoIdToMint);
|
|
1434
|
+
for (let [mint, meta] of tokenListMetadata)
|
|
1435
|
+
meta.extensions?.coingeckoId && coingeckoIdToMint.set(meta.extensions.coingeckoId, mint);
|
|
1436
|
+
if (coingeckoIdToMint.size === 0)
|
|
1437
|
+
return didUpdate;
|
|
1438
|
+
let prices = await fetchCoinGeckoPrices([...coingeckoIdToMint.keys()], coingeckoConfig), didUpdatePrices = false;
|
|
907
1439
|
for (let [coingeckoId, mint] of coingeckoIdToMint) {
|
|
908
|
-
let cached =
|
|
1440
|
+
let cached = metadataCache.get(mint);
|
|
909
1441
|
if (cached) {
|
|
910
1442
|
let usdPrice = prices.get(coingeckoId);
|
|
911
|
-
usdPrice !== void 0 &&
|
|
1443
|
+
usdPrice !== void 0 && cached.usdPrice !== usdPrice && (didUpdate = true, didUpdatePrices = true, cached.usdPrice = usdPrice, metadataCache.set(mint, cached));
|
|
912
1444
|
}
|
|
913
1445
|
}
|
|
914
|
-
return
|
|
1446
|
+
return didUpdatePrices && options?.onUpdate?.(), didUpdate;
|
|
915
1447
|
}
|
|
916
1448
|
function formatBalance(amount, decimals) {
|
|
917
|
-
return (
|
|
918
|
-
|
|
919
|
-
maximumFractionDigits: Math.min(decimals, 6)
|
|
1449
|
+
return chunkVA6LKXCQ_js.formatBigIntBalance(amount, decimals, {
|
|
1450
|
+
maxDecimals: Math.min(decimals, 6)
|
|
920
1451
|
});
|
|
921
1452
|
}
|
|
922
1453
|
function formatUsd(amount, decimals, usdPrice) {
|
|
923
|
-
return (
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1454
|
+
return chunkVA6LKXCQ_js.formatBigIntUsd(amount, decimals, usdPrice);
|
|
1455
|
+
}
|
|
1456
|
+
function selectTokens(assets, address) {
|
|
1457
|
+
return {
|
|
1458
|
+
lamports: assets?.lamports ?? 0n,
|
|
1459
|
+
tokenAccounts: assets?.tokenAccounts ?? [],
|
|
1460
|
+
address
|
|
1461
|
+
};
|
|
929
1462
|
}
|
|
930
|
-
function
|
|
931
|
-
|
|
932
|
-
if (
|
|
933
|
-
let
|
|
934
|
-
return
|
|
1463
|
+
function sortByValueDesc(a, b) {
|
|
1464
|
+
let metadataSort = (b.logo ? 1 : 0) - (a.logo ? 1 : 0);
|
|
1465
|
+
if (metadataSort !== 0) return metadataSort;
|
|
1466
|
+
let aValue = Number(a.amount) / Math.pow(10, a.decimals) * (a.usdPrice ?? 0);
|
|
1467
|
+
return Number(b.amount) / Math.pow(10, b.decimals) * (b.usdPrice ?? 0) - aValue;
|
|
935
1468
|
}
|
|
936
1469
|
function useTokens(options = {}) {
|
|
937
1470
|
let {
|
|
1471
|
+
enabled = true,
|
|
938
1472
|
includeZeroBalance = false,
|
|
939
1473
|
autoRefresh = false,
|
|
940
1474
|
refreshInterval = 6e4,
|
|
941
1475
|
fetchMetadata = true,
|
|
942
|
-
includeNativeSol = true
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
tokenList.sort((a, b) => {
|
|
998
|
-
let metadataSort = (b.logo ? 1 : 0) - (a.logo ? 1 : 0);
|
|
999
|
-
if (metadataSort !== 0) return metadataSort;
|
|
1000
|
-
let aValue = Number(a.amount) / Math.pow(10, a.decimals) * (a.usdPrice ?? 0);
|
|
1001
|
-
return Number(b.amount) / Math.pow(10, b.decimals) * (b.usdPrice ?? 0) - aValue;
|
|
1002
|
-
}), setTokens([...tokenList]);
|
|
1003
|
-
}
|
|
1004
|
-
} catch (err) {
|
|
1005
|
-
setError(err), console.error("[useTokens] Failed to fetch tokens:", err);
|
|
1006
|
-
} finally {
|
|
1007
|
-
setIsLoading(false);
|
|
1008
|
-
}
|
|
1009
|
-
}, [connected, address, rpcClient, includeZeroBalance, fetchMetadata, includeNativeSol, imageProxy, coingeckoConfig]);
|
|
1476
|
+
includeNativeSol = true,
|
|
1477
|
+
staleTimeMs = 0,
|
|
1478
|
+
cacheTimeMs = 300 * 1e3,
|
|
1479
|
+
// 5 minutes
|
|
1480
|
+
refetchOnMount = "stale",
|
|
1481
|
+
client: clientOverride
|
|
1482
|
+
} = options, { address, connected } = useAccount(), { type: clusterType } = useCluster(), connectorConfig = useConnectorClient()?.getConfig(), imageProxy = connectorConfig?.imageProxy, coingeckoConfig = connectorConfig?.coingecko, selectFn = React.useCallback(
|
|
1483
|
+
(assets) => selectTokens(assets, address ?? ""),
|
|
1484
|
+
[address]
|
|
1485
|
+
), {
|
|
1486
|
+
data: selection,
|
|
1487
|
+
error,
|
|
1488
|
+
isFetching,
|
|
1489
|
+
updatedAt,
|
|
1490
|
+
refetch: sharedRefetch,
|
|
1491
|
+
abort
|
|
1492
|
+
} = useWalletAssets({
|
|
1493
|
+
enabled,
|
|
1494
|
+
staleTimeMs,
|
|
1495
|
+
cacheTimeMs,
|
|
1496
|
+
refetchOnMount,
|
|
1497
|
+
refetchIntervalMs: autoRefresh ? refreshInterval : false,
|
|
1498
|
+
client: clientOverride,
|
|
1499
|
+
select: selectFn
|
|
1500
|
+
}), lamports = selection?.lamports ?? 0n, tokenAccounts = selection?.tokenAccounts ?? [], walletAddress = selection?.address ?? "", baseTokens = React.useMemo(() => {
|
|
1501
|
+
let result = [];
|
|
1502
|
+
includeNativeSol && walletAddress && (includeZeroBalance || lamports > 0n) && result.push({
|
|
1503
|
+
mint: NATIVE_SOL_MINT,
|
|
1504
|
+
tokenAccount: walletAddress,
|
|
1505
|
+
amount: lamports,
|
|
1506
|
+
decimals: 9,
|
|
1507
|
+
formatted: formatBalance(lamports, 9),
|
|
1508
|
+
isFrozen: false,
|
|
1509
|
+
owner: walletAddress
|
|
1510
|
+
});
|
|
1511
|
+
for (let account of tokenAccounts)
|
|
1512
|
+
!includeZeroBalance && account.amount === 0n || result.push({
|
|
1513
|
+
mint: account.mint,
|
|
1514
|
+
tokenAccount: account.pubkey,
|
|
1515
|
+
amount: account.amount,
|
|
1516
|
+
decimals: account.decimals,
|
|
1517
|
+
formatted: formatBalance(account.amount, account.decimals),
|
|
1518
|
+
isFrozen: account.isFrozen,
|
|
1519
|
+
owner: account.owner,
|
|
1520
|
+
programId: account.programId
|
|
1521
|
+
});
|
|
1522
|
+
return result;
|
|
1523
|
+
}, [lamports, tokenAccounts, walletAddress, includeNativeSol, includeZeroBalance]), mints = React.useMemo(() => {
|
|
1524
|
+
let unique = /* @__PURE__ */ new Set();
|
|
1525
|
+
for (let token of baseTokens)
|
|
1526
|
+
unique.add(token.mint);
|
|
1527
|
+
return [...unique].sort();
|
|
1528
|
+
}, [baseTokens]), mintsKey = React.useMemo(() => mints.join(","), [mints]), [metadataVersion, setMetadataVersion] = React.useState(0);
|
|
1010
1529
|
React.useEffect(() => {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1530
|
+
if (!fetchMetadata || !mintsKey) return;
|
|
1531
|
+
let isMounted = true;
|
|
1532
|
+
return (async () => {
|
|
1533
|
+
try {
|
|
1534
|
+
let mintList = mintsKey.split(",");
|
|
1535
|
+
await fetchTokenMetadataHybrid(mintList, coingeckoConfig, {
|
|
1536
|
+
onUpdate: () => {
|
|
1537
|
+
isMounted && setMetadataVersion((v) => v + 1);
|
|
1538
|
+
},
|
|
1539
|
+
cluster: clusterType ?? void 0
|
|
1540
|
+
}), isMounted && setMetadataVersion((v) => v + 1);
|
|
1541
|
+
} catch (err) {
|
|
1542
|
+
console.error("[useTokens] Failed to fetch metadata:", err);
|
|
1543
|
+
}
|
|
1544
|
+
})(), () => {
|
|
1545
|
+
isMounted = false;
|
|
1546
|
+
};
|
|
1547
|
+
}, [mintsKey, fetchMetadata, coingeckoConfig, clusterType]);
|
|
1548
|
+
let tokens = React.useMemo(() => fetchMetadata ? baseTokens.map((token) => {
|
|
1549
|
+
let meta = metadataCache.get(token.mint);
|
|
1550
|
+
if (!meta) return token;
|
|
1551
|
+
let usdPrice = (meta.coingeckoId ? priceCache.get(meta.coingeckoId) : void 0)?.price ?? meta.usdPrice;
|
|
1552
|
+
return {
|
|
1553
|
+
...token,
|
|
1554
|
+
name: meta.name,
|
|
1555
|
+
symbol: meta.symbol,
|
|
1556
|
+
logo: transformImageUrl(meta.logoURI, imageProxy),
|
|
1557
|
+
usdPrice,
|
|
1558
|
+
formattedUsd: usdPrice ? formatUsd(token.amount, token.decimals, usdPrice) : void 0
|
|
1559
|
+
};
|
|
1560
|
+
}).sort(sortByValueDesc) : baseTokens.slice().sort(sortByValueDesc), [baseTokens, fetchMetadata, imageProxy, metadataVersion]), totalAccounts = tokenAccounts.length + (includeNativeSol ? 1 : 0);
|
|
1561
|
+
React.useEffect(() => (startCacheCleanup(), () => stopCacheCleanup()), []);
|
|
1017
1562
|
let wasConnectedRef = React.useRef(connected);
|
|
1018
|
-
|
|
1563
|
+
React.useEffect(() => {
|
|
1019
1564
|
wasConnectedRef.current && !connected && clearTokenCaches(), wasConnectedRef.current = connected;
|
|
1020
|
-
}, [connected])
|
|
1565
|
+
}, [connected]);
|
|
1566
|
+
let visibleError = updatedAt ? null : error, refetch = React.useCallback(
|
|
1567
|
+
async (opts) => {
|
|
1568
|
+
await sharedRefetch(opts);
|
|
1569
|
+
},
|
|
1570
|
+
[sharedRefetch]
|
|
1571
|
+
);
|
|
1572
|
+
return React.useMemo(
|
|
1021
1573
|
() => ({
|
|
1022
1574
|
tokens,
|
|
1023
|
-
isLoading,
|
|
1024
|
-
error,
|
|
1025
|
-
refetch
|
|
1026
|
-
|
|
1575
|
+
isLoading: isFetching,
|
|
1576
|
+
error: visibleError,
|
|
1577
|
+
refetch,
|
|
1578
|
+
abort,
|
|
1579
|
+
lastUpdated: updatedAt ? new Date(updatedAt) : null,
|
|
1027
1580
|
totalAccounts
|
|
1028
1581
|
}),
|
|
1029
|
-
[tokens,
|
|
1582
|
+
[tokens, isFetching, visibleError, refetch, abort, updatedAt, totalAccounts]
|
|
1030
1583
|
);
|
|
1031
1584
|
}
|
|
1032
1585
|
function DisconnectElement({
|
|
@@ -1870,6 +2423,8 @@ function BalanceElement({
|
|
|
1870
2423
|
BalanceElement.displayName = "BalanceElement";
|
|
1871
2424
|
function TransactionHistoryElement({
|
|
1872
2425
|
limit = 5,
|
|
2426
|
+
fetchDetails = true,
|
|
2427
|
+
detailsConcurrency,
|
|
1873
2428
|
showStatus = true,
|
|
1874
2429
|
showTime = true,
|
|
1875
2430
|
className,
|
|
@@ -1879,7 +2434,11 @@ function TransactionHistoryElement({
|
|
|
1879
2434
|
render,
|
|
1880
2435
|
renderItem
|
|
1881
2436
|
}) {
|
|
1882
|
-
let { transactions, isLoading, error, hasMore, loadMore, refetch } = useTransactions({
|
|
2437
|
+
let { transactions, isLoading, error, hasMore, loadMore, refetch } = useTransactions({
|
|
2438
|
+
limit,
|
|
2439
|
+
fetchDetails,
|
|
2440
|
+
detailsConcurrency
|
|
2441
|
+
});
|
|
1883
2442
|
if (render)
|
|
1884
2443
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: render({ transactions, isLoading, error, hasMore, loadMore, refetch }) });
|
|
1885
2444
|
let statusIcon = (status) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2195,6 +2754,8 @@ exports.TokenListElement = TokenListElement;
|
|
|
2195
2754
|
exports.TransactionHistoryElement = TransactionHistoryElement;
|
|
2196
2755
|
exports.UnifiedProvider = UnifiedProvider;
|
|
2197
2756
|
exports.WalletListElement = WalletListElement;
|
|
2757
|
+
exports.clearSharedQueryCache = clearSharedQueryCache;
|
|
2758
|
+
exports.invalidateSharedQuery = invalidateSharedQuery;
|
|
2198
2759
|
exports.useAccount = useAccount;
|
|
2199
2760
|
exports.useBalance = useBalance;
|
|
2200
2761
|
exports.useCluster = useCluster;
|
|
@@ -2209,5 +2770,5 @@ exports.useTransactionPreparer = useTransactionPreparer;
|
|
|
2209
2770
|
exports.useTransactionSigner = useTransactionSigner;
|
|
2210
2771
|
exports.useTransactions = useTransactions;
|
|
2211
2772
|
exports.useWalletInfo = useWalletInfo;
|
|
2212
|
-
//# sourceMappingURL=chunk-
|
|
2213
|
-
//# sourceMappingURL=chunk-
|
|
2773
|
+
//# sourceMappingURL=chunk-Z22V3D4E.js.map
|
|
2774
|
+
//# sourceMappingURL=chunk-Z22V3D4E.js.map
|