@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.
Files changed (38) hide show
  1. package/README.md +45 -45
  2. package/dist/{chunk-VMSZJPR5.mjs → chunk-APQGEW7S.mjs} +82 -11
  3. package/dist/chunk-APQGEW7S.mjs.map +1 -0
  4. package/dist/{chunk-3STZXVXD.mjs → chunk-JK47EFJT.mjs} +940 -381
  5. package/dist/chunk-JK47EFJT.mjs.map +1 -0
  6. package/dist/{chunk-QKVL45F6.mjs → chunk-TQRJYZNK.mjs} +5 -3
  7. package/dist/chunk-TQRJYZNK.mjs.map +1 -0
  8. package/dist/{chunk-JUZVCBAI.js → chunk-VA6LKXCQ.js} +85 -10
  9. package/dist/chunk-VA6LKXCQ.js.map +1 -0
  10. package/dist/{chunk-NQXK7PGX.js → chunk-VZ5Y6DIM.js} +19 -17
  11. package/dist/chunk-VZ5Y6DIM.js.map +1 -0
  12. package/dist/{chunk-ULUYX23Q.js → chunk-Z22V3D4E.js} +949 -388
  13. package/dist/chunk-Z22V3D4E.js.map +1 -0
  14. package/dist/compat.d.mts +1 -1
  15. package/dist/compat.d.ts +1 -1
  16. package/dist/headless.d.mts +99 -7
  17. package/dist/headless.d.ts +99 -7
  18. package/dist/headless.js +128 -112
  19. package/dist/headless.mjs +2 -2
  20. package/dist/index.d.mts +5 -4
  21. package/dist/index.d.ts +5 -4
  22. package/dist/index.js +163 -139
  23. package/dist/index.mjs +3 -3
  24. package/dist/react.d.mts +179 -13
  25. package/dist/react.d.ts +179 -13
  26. package/dist/react.js +36 -28
  27. package/dist/react.mjs +2 -2
  28. package/dist/{transaction-signer-D9d8nxwb.d.mts → transaction-signer-CpGEvp7S.d.mts} +1 -1
  29. package/dist/{transaction-signer-D9d8nxwb.d.ts → transaction-signer-CpGEvp7S.d.ts} +1 -1
  30. package/dist/{wallet-standard-shim--YcrQNRt.d.ts → wallet-standard-shim-D4CYG5sU.d.mts} +35 -6
  31. package/dist/{wallet-standard-shim-Dx7H8Ctf.d.mts → wallet-standard-shim-DiMvGjOk.d.ts} +35 -6
  32. package/package.json +1 -1
  33. package/dist/chunk-3STZXVXD.mjs.map +0 -1
  34. package/dist/chunk-JUZVCBAI.js.map +0 -1
  35. package/dist/chunk-NQXK7PGX.js.map +0 -1
  36. package/dist/chunk-QKVL45F6.mjs.map +0 -1
  37. package/dist/chunk-ULUYX23Q.js.map +0 -1
  38. package/dist/chunk-VMSZJPR5.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkJUZVCBAI_js = require('./chunk-JUZVCBAI.js');
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
- chunkJUZVCBAI_js.installPolyfills();
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 chunkJUZVCBAI_js.ConnectorClient(config), typeof window < "u" && (window.__connectorClient = clientRef.current), config?.debug && logger.info("Client initialized successfully");
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
- chunkJUZVCBAI_js.ConnectorErrorBoundary,
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 ? chunkJUZVCBAI_js.isMainnetCluster(cluster) : false, isDevnet = cluster ? chunkJUZVCBAI_js.isDevnetCluster(cluster) : false, isTestnet = cluster ? chunkJUZVCBAI_js.isTestnetCluster(cluster) : false, isLocal = cluster ? chunkJUZVCBAI_js.isLocalCluster(cluster) : false, explorerUrl = cluster ? chunkJUZVCBAI_js.getClusterExplorerUrl(cluster) : "", type = cluster ? chunkJUZVCBAI_js.getClusterType(cluster) : null;
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 ? chunkJUZVCBAI_js.formatAddress(selectedAccount) : "", [selectedAccount]), copy = React.useCallback(async () => selectedAccount ? (copyTimeoutRef.current && clearTimeout(copyTimeoutRef.current), await chunkJUZVCBAI_js.copyAddressToClipboard(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 : chunkJUZVCBAI_js.createTransactionSigner({
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 ? chunkJUZVCBAI_js.createKitTransactionSigner(connectorSigner) : null, [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 chunkJUZVCBAI_js.NetworkError("RPC_ERROR", "Solana client not available. Cannot prepare transaction.");
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 LAMPORTS_PER_SOL2 = 1000000000n;
309
- function formatSol(lamports, decimals = 4) {
310
- return (Number(lamports) / Number(LAMPORTS_PER_SOL2)).toLocaleString(void 0, {
311
- minimumFractionDigits: 0,
312
- maximumFractionDigits: decimals
313
- }) + " SOL";
314
- }
315
- function useBalance() {
316
- let { address, connected } = useAccount(); useCluster(); let client = useSolanaClient(), [lamports, setLamports] = React.useState(0n), [tokens, setTokens] = React.useState([]), [isLoading, setIsLoading] = React.useState(false), [error, setError] = React.useState(null), [lastUpdated, setLastUpdated] = React.useState(null), hasDataRef = React.useRef(false), rpcClient = client?.client ?? null, fetchBalance = React.useCallback(async () => {
317
- if (!connected || !address || !rpcClient) {
318
- setLamports(0n), setTokens([]), hasDataRef.current = false;
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
- setIsLoading(true), setError(null);
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
- let rpc = rpcClient.rpc, walletAddress = addresses.address(address), balanceResult = await rpc.getBalance(walletAddress).send();
324
- setLamports(balanceResult.value);
325
- try {
326
- let tokenProgramId = addresses.address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), tokenAccountsResult = await rpc.getTokenAccountsByOwner(walletAddress, { programId: tokenProgramId }, { encoding: "jsonParsed" }).send(), tokenBalances = [];
327
- for (let account of tokenAccountsResult.value) {
328
- let parsed = account.account.data;
329
- if (parsed?.parsed?.info) {
330
- let info = parsed.parsed.info, amount = BigInt(info.tokenAmount?.amount || "0"), decimals = info.tokenAmount?.decimals || 0, formatted = (Number(amount) / Math.pow(10, decimals)).toLocaleString(void 0, {
331
- minimumFractionDigits: 0,
332
- maximumFractionDigits: Math.min(decimals, 6)
333
- });
334
- amount > 0n && tokenBalances.push({
335
- mint: info.mint,
336
- amount,
337
- decimals,
338
- formatted
339
- });
340
- }
341
- }
342
- setTokens(tokenBalances);
343
- } catch (tokenError) {
344
- console.warn("Failed to fetch token balances:", tokenError), setTokens([]);
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
- hasDataRef.current = true, setLastUpdated(/* @__PURE__ */ new Date());
347
- } catch (err) {
348
- hasDataRef.current || (setError(err), console.error("Failed to fetch balance:", err));
349
- } finally {
350
- setIsLoading(false);
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
- }, [connected, address, rpcClient]);
353
- React.useEffect(() => {
354
- fetchBalance();
355
- }, [fetchBalance]), React.useEffect(() => {
356
- if (!connected) return;
357
- let interval = setInterval(fetchBalance, 3e4);
358
- return () => clearInterval(interval);
359
- }, [connected, fetchBalance]);
360
- let solBalance = React.useMemo(() => Number(lamports) / Number(LAMPORTS_PER_SOL2), [lamports]), formattedSol = React.useMemo(() => formatSol(lamports), [lamports]);
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: fetchBalance,
370
- lastUpdated
690
+ isLoading: isFetching,
691
+ error: visibleError,
692
+ refetch,
693
+ abort,
694
+ lastUpdated: updatedAt ? new Date(updatedAt) : null
371
695
  }),
372
- [solBalance, lamports, formattedSol, tokens, isLoading, error, fetchBalance, lastUpdated]
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() || balance.owner && balance.owner.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() || balance.owner && balance.owner.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
- var tokenMetadataCache = /* @__PURE__ */ new Map();
499
- function transformImageUrl(url, imageProxy) {
500
- if (url)
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
- async function fetchTokenMetadata(mints) {
504
- let results = /* @__PURE__ */ new Map();
505
- if (mints.length === 0) return results;
506
- let uncachedMints = [];
507
- for (let mint of mints) {
508
- let cached = tokenMetadataCache.get(mint);
509
- cached ? results.set(mint, cached) : uncachedMints.push(mint);
510
- }
511
- if (uncachedMints.length === 0) return results;
512
- try {
513
- let response = await fetch("https://token-list-api.solana.cloud/v1/mints?chainId=101", {
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 { limit = 10, autoRefresh = false, refreshInterval = 6e4, fetchDetails = true } = options, { address, connected } = useAccount(), { cluster } = useCluster(), client = useSolanaClient(), connectorClient = useConnectorClient(), [transactions, setTransactions] = React.useState([]), [isLoading, setIsLoading] = React.useState(false), [error, setError] = React.useState(null), [hasMore, setHasMore] = React.useState(true), [lastUpdated, setLastUpdated] = React.useState(null), beforeSignatureRef = React.useRef(void 0), prevDepsRef = React.useRef(
532
- null
533
- ), rpcClient = client?.client ?? null, imageProxy = connectorClient?.getConfig().imageProxy, parseTransaction = React.useCallback(
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((key) => key.trim() === walletAddress.trim());
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
- if (hasJupiter || hasOrca || hasRaydium)
563
- type = "swap";
564
- else if (hasStake)
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
- ), fetchTransactions = React.useCallback(
592
- async (loadMore = false) => {
593
- if (!connected || !address || !rpcClient || !cluster) {
594
- setTransactions([]);
595
- return;
596
- }
597
- setIsLoading(true), setError(null);
598
- try {
599
- let rpc = rpcClient.rpc, walletAddress = addresses.address(address), signaturesResult = await rpc.getSignaturesForAddress(walletAddress, {
600
- limit,
601
- ...loadMore && beforeSignatureRef.current ? { before: keys.signature(beforeSignatureRef.current) } : {}
602
- }).send(), newTransactions;
603
- if (fetchDetails && signaturesResult.length > 0) {
604
- let txPromises = signaturesResult.map(
605
- (s) => rpc.getTransaction(keys.signature(String(s.signature)), {
606
- encoding: "jsonParsed",
607
- maxSupportedTransactionVersion: 0
608
- }).send().catch(() => null)
609
- ), txDetails = await Promise.all(txPromises);
610
- newTransactions = signaturesResult.map((sig, idx) => {
611
- let blockTimeNum = sig.blockTime ? Number(sig.blockTime) : null, tx = txDetails[idx];
612
- return parseTransaction(
613
- tx,
614
- address,
615
- String(sig.signature),
616
- blockTimeNum,
617
- Number(sig.slot),
618
- sig.err,
619
- chunkJUZVCBAI_js.getTransactionUrl(String(sig.signature), cluster)
620
- );
621
- });
622
- } else
623
- newTransactions = signaturesResult.map((sig) => {
624
- let blockTimeNum = sig.blockTime ? Number(sig.blockTime) : null, { date, time } = formatDate(blockTimeNum);
625
- return {
626
- signature: String(sig.signature),
627
- blockTime: blockTimeNum,
628
- slot: Number(sig.slot),
629
- status: sig.err ? "failed" : "success",
630
- error: sig.err ? JSON.stringify(sig.err) : void 0,
631
- type: "unknown",
632
- formattedDate: date,
633
- formattedTime: time,
634
- explorerUrl: chunkJUZVCBAI_js.getTransactionUrl(String(sig.signature), cluster)
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
- setTransactions(loadMore ? (prev) => [...prev, ...newTransactions] : newTransactions);
638
- let mintsToFetch = [...new Set(newTransactions.filter((tx) => tx.tokenMint).map((tx) => tx.tokenMint))];
639
- if (mintsToFetch.length > 0) {
640
- let tokenMetadata = await fetchTokenMetadata(mintsToFetch);
641
- if (tokenMetadata.size > 0) {
642
- let enrichedTransactions = newTransactions.map((tx) => {
643
- if (tx.tokenMint && tokenMetadata.has(tx.tokenMint)) {
644
- let meta = tokenMetadata.get(tx.tokenMint);
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
- return tx;
654
- });
655
- setTransactions(loadMore ? (prev) => [...prev.slice(0, -newTransactions.length), ...enrichedTransactions] : enrichedTransactions);
1194
+ };
656
1195
  }
657
- }
658
- if (typeof newTransactions < "u" && Array.isArray(newTransactions)) {
659
- if (newTransactions.length > 0) {
660
- let newBeforeSignature = newTransactions[newTransactions.length - 1].signature;
661
- beforeSignatureRef.current = newBeforeSignature;
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
- setHasMore(newTransactions.length === limit);
664
- }
665
- setLastUpdated(/* @__PURE__ */ new Date());
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
- setError(err), console.error("Failed to fetch transactions:", err);
1249
+ console.error("Failed to load more transactions:", err);
668
1250
  } finally {
669
- setIsLoading(false);
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
- [connected, address, rpcClient, cluster, limit, fetchDetails, parseTransaction, imageProxy]
673
- ), refetch = React.useCallback(async () => {
674
- beforeSignatureRef.current = void 0, await fetchTransactions(false);
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
- lastUpdated
1268
+ abort,
1269
+ lastUpdated: updatedAt ? new Date(updatedAt) : null
694
1270
  }),
695
- [transactions, isLoading, error, hasMore, loadMoreFn, refetch, lastUpdated]
1271
+ [transactions, isLoading, visibleError, hasMore, loadMoreFn, refetch, abort, updatedAt]
696
1272
  );
697
1273
  }
698
- function createTimeoutSignal(ms) {
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. Returning cached/partial results.`
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, error instanceof DOMException && error.name === "AbortError" ? console.warn(
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
- let results = /* @__PURE__ */ new Map();
880
- if (mints.length === 0) return results;
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
- cached?.coingeckoId && !coingeckoIdToMint.has(cached.coingeckoId) && coingeckoIdToMint.set(cached.coingeckoId, mint);
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 prices = await fetchCoinGeckoPrices([...coingeckoIdToMint.keys()], coingeckoConfig);
895
- for (let [mint, meta] of solanaMetadata) {
896
- let coingeckoId = meta.extensions?.coingeckoId, usdPrice = coingeckoId ? prices.get(coingeckoId) : void 0, combined = {
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 === NATIVE_MINT ? "Solana" : meta.name,
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
- results.set(mint, combined), metadataCache.set(mint, combined);
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 = results.get(mint) ?? metadataCache.get(mint);
1440
+ let cached = metadataCache.get(mint);
909
1441
  if (cached) {
910
1442
  let usdPrice = prices.get(coingeckoId);
911
- usdPrice !== void 0 && (cached.usdPrice = usdPrice, results.set(mint, cached), metadataCache.set(mint, cached));
1443
+ usdPrice !== void 0 && cached.usdPrice !== usdPrice && (didUpdate = true, didUpdatePrices = true, cached.usdPrice = usdPrice, metadataCache.set(mint, cached));
912
1444
  }
913
1445
  }
914
- return results;
1446
+ return didUpdatePrices && options?.onUpdate?.(), didUpdate;
915
1447
  }
916
1448
  function formatBalance(amount, decimals) {
917
- return (Number(amount) / Math.pow(10, decimals)).toLocaleString(void 0, {
918
- minimumFractionDigits: 0,
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 (Number(amount) / Math.pow(10, decimals) * usdPrice).toLocaleString(void 0, {
924
- style: "currency",
925
- currency: "USD",
926
- minimumFractionDigits: 2,
927
- maximumFractionDigits: 2
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 transformImageUrl2(url, imageProxy) {
931
- if (!url) return;
932
- if (!imageProxy) return url;
933
- let encodedUrl = encodeURIComponent(url);
934
- return imageProxy.endsWith("/") ? imageProxy + encodedUrl : imageProxy + "/" + encodedUrl;
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
- } = options, { address, connected } = useAccount(), client = useSolanaClient(), connectorClient = useConnectorClient(), [tokens, setTokens] = React.useState([]), [isLoading, setIsLoading] = React.useState(false), [error, setError] = React.useState(null), [lastUpdated, setLastUpdated] = React.useState(null), [totalAccounts, setTotalAccounts] = React.useState(0), rpcClient = client?.client ?? null, connectorConfig = connectorClient?.getConfig(), imageProxy = connectorConfig?.imageProxy, coingeckoConfig = connectorConfig?.coingecko, fetchTokens = React.useCallback(async () => {
944
- if (!connected || !address || !rpcClient) {
945
- setTokens([]), setTotalAccounts(0);
946
- return;
947
- }
948
- setIsLoading(true), setError(null);
949
- try {
950
- let rpc = rpcClient.rpc, walletAddress = addresses.address(address), tokenProgramId = addresses.address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), [balanceResult, tokenAccountsResult] = await Promise.all([
951
- includeNativeSol ? rpc.getBalance(walletAddress).send() : Promise.resolve(null),
952
- rpc.getTokenAccountsByOwner(walletAddress, { programId: tokenProgramId }, { encoding: "jsonParsed" }).send()
953
- ]), tokenList = [], mints = [];
954
- if (includeNativeSol && balanceResult !== null) {
955
- let solBalance = balanceResult.value;
956
- (includeZeroBalance || solBalance > 0n) && (tokenList.push({
957
- mint: NATIVE_MINT,
958
- tokenAccount: address,
959
- // SOL uses wallet address
960
- amount: solBalance,
961
- decimals: 9,
962
- formatted: formatBalance(solBalance, 9),
963
- isFrozen: false,
964
- owner: address
965
- }), mints.push(NATIVE_MINT));
966
- }
967
- for (let account of tokenAccountsResult.value) {
968
- let parsed = account.account.data;
969
- if (parsed?.parsed?.info) {
970
- let info = parsed.parsed.info, amount = BigInt(info.tokenAmount?.amount || "0"), decimals = info.tokenAmount?.decimals || 0;
971
- if (!includeZeroBalance && amount === 0n)
972
- continue;
973
- tokenList.push({
974
- mint: info.mint,
975
- tokenAccount: account.pubkey,
976
- amount,
977
- decimals,
978
- formatted: formatBalance(amount, decimals),
979
- isFrozen: info.state === "frozen",
980
- owner: info.owner
981
- }), mints.push(info.mint);
982
- }
983
- }
984
- if (setTokens([...tokenList]), setTotalAccounts(tokenAccountsResult.value.length + (includeNativeSol ? 1 : 0)), setLastUpdated(/* @__PURE__ */ new Date()), fetchMetadata && mints.length > 0) {
985
- let metadata = await fetchTokenMetadataHybrid(mints, coingeckoConfig);
986
- for (let i = 0; i < tokenList.length; i++) {
987
- let meta = metadata.get(tokenList[i].mint);
988
- meta && (tokenList[i] = {
989
- ...tokenList[i],
990
- name: meta.name,
991
- symbol: meta.symbol,
992
- logo: transformImageUrl2(meta.logoURI, imageProxy),
993
- usdPrice: meta.usdPrice,
994
- formattedUsd: meta.usdPrice ? formatUsd(tokenList[i].amount, tokenList[i].decimals, meta.usdPrice) : void 0
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
- fetchTokens();
1012
- }, [fetchTokens]), React.useEffect(() => {
1013
- if (!connected || !autoRefresh) return;
1014
- let interval = setInterval(fetchTokens, refreshInterval);
1015
- return () => clearInterval(interval);
1016
- }, [connected, autoRefresh, refreshInterval, fetchTokens]), React.useEffect(() => (startCacheCleanup(), () => stopCacheCleanup()), []);
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
- return React.useEffect(() => {
1563
+ React.useEffect(() => {
1019
1564
  wasConnectedRef.current && !connected && clearTokenCaches(), wasConnectedRef.current = connected;
1020
- }, [connected]), React.useMemo(
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: fetchTokens,
1026
- lastUpdated,
1575
+ isLoading: isFetching,
1576
+ error: visibleError,
1577
+ refetch,
1578
+ abort,
1579
+ lastUpdated: updatedAt ? new Date(updatedAt) : null,
1027
1580
  totalAccounts
1028
1581
  }),
1029
- [tokens, isLoading, error, fetchTokens, lastUpdated, totalAccounts]
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({ limit });
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-ULUYX23Q.js.map
2213
- //# sourceMappingURL=chunk-ULUYX23Q.js.map
2773
+ //# sourceMappingURL=chunk-Z22V3D4E.js.map
2774
+ //# sourceMappingURL=chunk-Z22V3D4E.js.map