@solana/connector 0.1.6 → 0.1.8

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 (50) hide show
  1. package/README.md +122 -44
  2. package/dist/{chunk-VMSZJPR5.mjs → chunk-6F6M6L7R.mjs} +152 -173
  3. package/dist/chunk-6F6M6L7R.mjs.map +1 -0
  4. package/dist/{chunk-NQXK7PGX.js → chunk-AOIXHVRH.js} +82 -235
  5. package/dist/chunk-AOIXHVRH.js.map +1 -0
  6. package/dist/chunk-DSUCH44G.js +678 -0
  7. package/dist/chunk-DSUCH44G.js.map +1 -0
  8. package/dist/{chunk-JUZVCBAI.js → chunk-FTXIXM43.js} +240 -271
  9. package/dist/chunk-FTXIXM43.js.map +1 -0
  10. package/dist/{chunk-QKVL45F6.mjs → chunk-G575OAT4.mjs} +73 -218
  11. package/dist/chunk-G575OAT4.mjs.map +1 -0
  12. package/dist/chunk-J7DHGLW6.mjs +638 -0
  13. package/dist/chunk-J7DHGLW6.mjs.map +1 -0
  14. package/dist/{chunk-ULUYX23Q.js → chunk-K3BNIGPX.js} +1023 -404
  15. package/dist/chunk-K3BNIGPX.js.map +1 -0
  16. package/dist/{chunk-3STZXVXD.mjs → chunk-TTOKQAPX.mjs} +998 -388
  17. package/dist/chunk-TTOKQAPX.mjs.map +1 -0
  18. package/dist/compat.d.mts +1 -1
  19. package/dist/compat.d.ts +1 -1
  20. package/dist/compat.js +40 -39
  21. package/dist/compat.js.map +1 -1
  22. package/dist/compat.mjs +39 -38
  23. package/dist/compat.mjs.map +1 -1
  24. package/dist/headless.d.mts +540 -152
  25. package/dist/headless.d.ts +540 -152
  26. package/dist/headless.js +226 -190
  27. package/dist/headless.mjs +3 -3
  28. package/dist/index.d.mts +8 -6
  29. package/dist/index.d.ts +8 -6
  30. package/dist/index.js +286 -218
  31. package/dist/index.mjs +4 -4
  32. package/dist/react.d.mts +283 -16
  33. package/dist/react.d.ts +283 -16
  34. package/dist/react.js +60 -28
  35. package/dist/react.mjs +2 -2
  36. package/dist/{wallet-standard-shim--YcrQNRt.d.ts → standard-shim-CT49DM5l.d.mts} +72 -252
  37. package/dist/{wallet-standard-shim-Dx7H8Ctf.d.mts → standard-shim-D9guL5fz.d.ts} +72 -252
  38. package/dist/{transaction-signer-D9d8nxwb.d.mts → transaction-signer-T-KVQFi8.d.mts} +2 -2
  39. package/dist/{transaction-signer-D9d8nxwb.d.ts → transaction-signer-T-KVQFi8.d.ts} +2 -2
  40. package/package.json +3 -3
  41. package/dist/chunk-3STZXVXD.mjs.map +0 -1
  42. package/dist/chunk-I64FD2EH.js +0 -312
  43. package/dist/chunk-I64FD2EH.js.map +0 -1
  44. package/dist/chunk-JUZVCBAI.js.map +0 -1
  45. package/dist/chunk-NQXK7PGX.js.map +0 -1
  46. package/dist/chunk-QKVL45F6.mjs.map +0 -1
  47. package/dist/chunk-QL3IT3TS.mjs +0 -299
  48. package/dist/chunk-QL3IT3TS.mjs.map +0 -1
  49. package/dist/chunk-ULUYX23Q.js.map +0 -1
  50. package/dist/chunk-VMSZJPR5.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkJUZVCBAI_js = require('./chunk-JUZVCBAI.js');
4
- var chunkI64FD2EH_js = require('./chunk-I64FD2EH.js');
3
+ var chunkFTXIXM43_js = require('./chunk-FTXIXM43.js');
4
+ var chunkDSUCH44G_js = require('./chunk-DSUCH44G.js');
5
5
  var React = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var addresses = require('@solana/addresses');
@@ -11,8 +11,8 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
11
 
12
12
  var React__default = /*#__PURE__*/_interopDefault(React);
13
13
 
14
- var logger = chunkI64FD2EH_js.createLogger("ConnectorProvider");
15
- chunkJUZVCBAI_js.installPolyfills();
14
+ var logger = chunkDSUCH44G_js.createLogger("ConnectorProvider");
15
+ chunkFTXIXM43_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 chunkFTXIXM43_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
+ chunkFTXIXM43_js.ConnectorErrorBoundary,
87
87
  {
88
88
  maxRetries: errorBoundaryConfig.maxRetries ?? 3,
89
89
  onError: errorBoundaryConfig.onError,
@@ -121,14 +121,15 @@ function useConnector() {
121
121
  function useConnectorClient() {
122
122
  return React.useContext(ConnectorContext);
123
123
  }
124
- function UnifiedProvider({ children, config, connectorConfig, mobile, providers = [] }) {
125
- let actualConnectorConfig = config?.connectorConfig ?? connectorConfig, actualMobile = config?.mobile ?? mobile, content = /* @__PURE__ */ jsxRuntime.jsx(ConnectorProvider, { config: actualConnectorConfig, mobile: actualMobile, children });
124
+ function AppProvider({ children, connectorConfig, mobile, providers = [] }) {
125
+ let content = /* @__PURE__ */ jsxRuntime.jsx(ConnectorProvider, { config: connectorConfig, mobile, children });
126
126
  for (let i = providers.length - 1; i >= 0; i--) {
127
127
  let { component: Provider, props = {} } = providers[i];
128
128
  content = /* @__PURE__ */ jsxRuntime.jsx(Provider, { ...props, children: content });
129
129
  }
130
130
  return content;
131
131
  }
132
+ var UnifiedProvider = AppProvider;
132
133
  function useCluster() {
133
134
  let { cluster, clusters } = useConnector(), client = useConnectorClient();
134
135
  if (!client)
@@ -140,7 +141,7 @@ function useCluster() {
140
141
  [client]
141
142
  );
142
143
  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;
144
+ let isMainnet = cluster ? chunkFTXIXM43_js.isMainnetCluster(cluster) : false, isDevnet = cluster ? chunkFTXIXM43_js.isDevnetCluster(cluster) : false, isTestnet = cluster ? chunkFTXIXM43_js.isTestnetCluster(cluster) : false, isLocal = cluster ? chunkFTXIXM43_js.isLocalCluster(cluster) : false, explorerUrl = cluster ? chunkFTXIXM43_js.getClusterExplorerUrl(cluster) : "", type = cluster ? chunkFTXIXM43_js.getClusterType(cluster) : null;
144
145
  return {
145
146
  cluster,
146
147
  clusters,
@@ -158,7 +159,7 @@ function useAccount() {
158
159
  let { selectedAccount, accounts, connected, selectAccount } = useConnector(), [copied, setCopied] = React.useState(false), copyTimeoutRef = React__default.default.useRef(void 0), account = React.useMemo(
159
160
  () => accounts.find((a) => a.address === selectedAccount) ?? null,
160
161
  [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, {
162
+ ), formatted = React.useMemo(() => selectedAccount ? chunkFTXIXM43_js.formatAddress(selectedAccount) : "", [selectedAccount]), copy = React.useCallback(async () => selectedAccount ? (copyTimeoutRef.current && clearTimeout(copyTimeoutRef.current), await chunkFTXIXM43_js.copyAddressToClipboard(selectedAccount, {
162
163
  onSuccess: () => {
163
164
  setCopied(true), copyTimeoutRef.current = setTimeout(() => setCopied(false), 2e3);
164
165
  }
@@ -224,7 +225,7 @@ function useTransactionSigner() {
224
225
  let { selectedWallet, selectedAccount, accounts, cluster, connected } = useConnector(), client = useConnectorClient(), account = React.useMemo(
225
226
  () => accounts.find((a) => a.address === selectedAccount)?.raw ?? null,
226
227
  [accounts, selectedAccount]
227
- ), signer = React.useMemo(() => !connected || !selectedWallet || !account ? null : chunkJUZVCBAI_js.createTransactionSigner({
228
+ ), signer = React.useMemo(() => !connected || !selectedWallet || !account ? null : chunkFTXIXM43_js.createTransactionSigner({
228
229
  wallet: selectedWallet,
229
230
  account,
230
231
  cluster: cluster ?? void 0,
@@ -252,20 +253,20 @@ function useTransactionSigner() {
252
253
  function useKitTransactionSigner() {
253
254
  let { signer: connectorSigner, ready } = useTransactionSigner();
254
255
  return {
255
- signer: React.useMemo(() => connectorSigner ? chunkJUZVCBAI_js.createKitTransactionSigner(connectorSigner) : null, [connectorSigner]),
256
+ signer: React.useMemo(() => connectorSigner ? chunkFTXIXM43_js.createKitTransactionSigner(connectorSigner) : null, [connectorSigner]),
256
257
  ready
257
258
  };
258
259
  }
259
260
  var useGillTransactionSigner = useKitTransactionSigner;
260
- var logger2 = chunkI64FD2EH_js.createLogger("useSolanaClient");
261
+ var logger2 = chunkDSUCH44G_js.createLogger("useSolanaClient");
261
262
  function useSolanaClient() {
262
263
  let { type } = useCluster(), connectorClient = useConnectorClient(), client = React.useMemo(() => {
263
264
  if (!type || !connectorClient) return null;
264
265
  try {
265
266
  let rpcUrl = connectorClient.getRpcUrl();
266
- return rpcUrl ? chunkI64FD2EH_js.createSolanaClient({
267
+ return rpcUrl ? chunkDSUCH44G_js.createSolanaClient({
267
268
  urlOrMoniker: rpcUrl
268
- }) : type !== "custom" ? chunkI64FD2EH_js.createSolanaClient({
269
+ }) : type !== "custom" ? chunkDSUCH44G_js.createSolanaClient({
269
270
  urlOrMoniker: type
270
271
  }) : null;
271
272
  } catch (error) {
@@ -286,8 +287,8 @@ function useTransactionPreparer() {
286
287
  let { client, ready } = useSolanaClient(), prepare = React.useCallback(
287
288
  async (transaction, options = {}) => {
288
289
  if (!client)
289
- throw new chunkJUZVCBAI_js.NetworkError("RPC_ERROR", "Solana client not available. Cannot prepare transaction.");
290
- return chunkI64FD2EH_js.prepareTransaction({
290
+ throw new chunkDSUCH44G_js.NetworkError("RPC_ERROR", "Solana client not available. Cannot prepare transaction.");
291
+ return chunkDSUCH44G_js.prepareTransaction({
291
292
  transaction,
292
293
  rpc: client.rpc,
293
294
  computeUnitLimitMultiplier: options.computeUnitLimitMultiplier,
@@ -305,73 +306,503 @@ function useTransactionPreparer() {
305
306
  [prepare, ready]
306
307
  );
307
308
  }
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;
309
+ var DEFAULT_CACHE_TIME_MS = 300 * 1e3, MAX_STORE_SIZE = 100, store = /* @__PURE__ */ new Map();
310
+ function evictStaleEntries() {
311
+ if (store.size <= MAX_STORE_SIZE) return;
312
+ let evictable = [];
313
+ for (let [key, entry] of store)
314
+ entry.subscribers.size === 0 && evictable.push([key, entry]);
315
+ evictable.sort((a, b) => a[1].lastAccessedAt - b[1].lastAccessedAt);
316
+ for (let [key, entry] of evictable) {
317
+ if (store.size <= MAX_STORE_SIZE) break;
318
+ entry.gcTimeoutId && clearTimeout(entry.gcTimeoutId), entry.intervalId && clearInterval(entry.intervalId), entry.abortController && entry.abortController.abort(), store.delete(key);
319
+ }
320
+ }
321
+ function getOrCreateEntry(key) {
322
+ let existing = store.get(key);
323
+ if (existing)
324
+ return existing.lastAccessedAt = Date.now(), existing;
325
+ let entry = {
326
+ snapshot: {
327
+ data: void 0,
328
+ error: null,
329
+ status: "idle",
330
+ updatedAt: null,
331
+ isFetching: false
332
+ },
333
+ subscribers: /* @__PURE__ */ new Set(),
334
+ promise: null,
335
+ abortController: null,
336
+ cacheTimeMs: DEFAULT_CACHE_TIME_MS,
337
+ gcTimeoutId: null,
338
+ queryFn: null,
339
+ intervalCounts: /* @__PURE__ */ new Map(),
340
+ activeIntervalMs: null,
341
+ intervalId: null,
342
+ lastAccessedAt: Date.now()
343
+ };
344
+ return store.set(key, entry), evictStaleEntries(), entry;
345
+ }
346
+ function emit(entry) {
347
+ for (let cb of entry.subscribers)
348
+ cb();
349
+ }
350
+ function setSnapshot(entry, next) {
351
+ let prev = entry.snapshot;
352
+ 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));
353
+ }
354
+ function getMinIntervalMs(entry) {
355
+ let min = null;
356
+ for (let [ms, count] of entry.intervalCounts)
357
+ count <= 0 || (min === null || ms < min) && (min = ms);
358
+ return min;
359
+ }
360
+ function startOrRestartInterval(key, entry) {
361
+ let nextMs = getMinIntervalMs(entry);
362
+ if (nextMs === null) {
363
+ entry.intervalId && clearInterval(entry.intervalId), entry.intervalId = null, entry.activeIntervalMs = null;
364
+ return;
365
+ }
366
+ entry.activeIntervalMs === nextMs && entry.intervalId || (entry.intervalId && clearInterval(entry.intervalId), entry.activeIntervalMs = nextMs, entry.intervalId = setInterval(() => {
367
+ let fn = entry.queryFn;
368
+ fn && fetchSharedQuery(key, fn, { force: true });
369
+ }, nextMs));
370
+ }
371
+ function subscribeSharedQuery(key, onChange, cacheTimeMs) {
372
+ let entry = getOrCreateEntry(key);
373
+ return entry.subscribers.add(onChange), typeof cacheTimeMs == "number" && (entry.cacheTimeMs = Math.max(entry.cacheTimeMs, cacheTimeMs)), entry.gcTimeoutId && (clearTimeout(entry.gcTimeoutId), entry.gcTimeoutId = null), () => {
374
+ if (entry.subscribers.delete(onChange), entry.subscribers.size > 0) return;
375
+ 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();
376
+ let gcDelayMs = entry.cacheTimeMs ?? DEFAULT_CACHE_TIME_MS;
377
+ entry.gcTimeoutId = setTimeout(() => {
378
+ entry.subscribers.size === 0 && store.delete(key);
379
+ }, gcDelayMs);
380
+ };
381
+ }
382
+ async function fetchSharedQuery(key, queryFn, options = {}) {
383
+ let entry = getOrCreateEntry(key);
384
+ entry.queryFn = queryFn;
385
+ let staleTimeMs = options.staleTimeMs ?? 0, now = Date.now();
386
+ if (options.force !== true && entry.snapshot.status === "success" && entry.snapshot.updatedAt !== null && now - entry.snapshot.updatedAt < staleTimeMs && entry.snapshot.data !== void 0)
387
+ return entry.snapshot.data;
388
+ if (entry.promise)
389
+ return entry.promise;
390
+ let controller = new AbortController();
391
+ entry.abortController = controller, options.signal && (options.signal.aborted ? controller.abort() : options.signal.addEventListener("abort", () => controller.abort(), { once: true }));
392
+ let isFirstLoad = entry.snapshot.status === "idle" && entry.snapshot.updatedAt === null;
393
+ setSnapshot(entry, {
394
+ ...entry.snapshot,
395
+ status: isFirstLoad ? "loading" : entry.snapshot.status,
396
+ isFetching: true,
397
+ error: null
398
+ });
399
+ let promise = (async () => {
400
+ try {
401
+ let data = await queryFn(controller.signal);
402
+ return setSnapshot(entry, {
403
+ data,
404
+ error: null,
405
+ status: "success",
406
+ updatedAt: Date.now(),
407
+ isFetching: false
408
+ }), data;
409
+ } catch (cause) {
410
+ if (controller.signal.aborted)
411
+ return setSnapshot(entry, {
412
+ data: entry.snapshot.data,
413
+ error: null,
414
+ status: entry.snapshot.status === "idle" ? "idle" : entry.snapshot.status,
415
+ updatedAt: entry.snapshot.updatedAt,
416
+ isFetching: false
417
+ }), entry.snapshot.data;
418
+ let error = cause instanceof Error ? cause : new Error(String(cause));
419
+ throw setSnapshot(entry, {
420
+ data: entry.snapshot.data,
421
+ error,
422
+ status: "error",
423
+ updatedAt: entry.snapshot.updatedAt,
424
+ isFetching: false
425
+ }), error;
426
+ } finally {
427
+ entry.promise = null, entry.abortController = null;
428
+ }
429
+ })();
430
+ return entry.promise = promise, promise;
431
+ }
432
+ function useSharedQuery(key, queryFn, options = {}) {
433
+ let {
434
+ enabled = true,
435
+ staleTimeMs = 0,
436
+ cacheTimeMs,
437
+ refetchOnMount = "stale",
438
+ refetchIntervalMs = false,
439
+ select
440
+ } = options, queryFnRef = React.useRef(queryFn);
441
+ queryFnRef.current = queryFn;
442
+ let stableQueryFn = React.useCallback((signal) => queryFnRef.current(signal), []), subscribe = React.useCallback(
443
+ (onChange) => key ? subscribeSharedQuery(key, onChange, cacheTimeMs) : () => {
444
+ },
445
+ [key, cacheTimeMs]
446
+ ), emptySnapshot = React.useMemo(
447
+ () => ({
448
+ data: void 0,
449
+ error: null,
450
+ status: "idle",
451
+ updatedAt: null,
452
+ isFetching: false
453
+ }),
454
+ []
455
+ ), 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);
456
+ React.useEffect(() => {
457
+ if (!key || !enabled) {
458
+ fetchedKeyRef.current = null;
319
459
  return;
320
460
  }
321
- setIsLoading(true), setError(null);
461
+ let entry = getOrCreateEntry(key);
462
+ if (entry.queryFn = stableQueryFn, fetchedKeyRef.current === key && entry.snapshot.status === "success")
463
+ return;
464
+ let current = entry.snapshot, isStale = current.updatedAt === null || Date.now() - current.updatedAt >= staleTimeMs;
465
+ refetchOnMount === true || current.status === "idle" || refetchOnMount === "stale" && isStale ? (fetchedKeyRef.current = key, fetchSharedQuery(key, stableQueryFn, {
466
+ staleTimeMs,
467
+ force: refetchOnMount === true
468
+ }).catch(() => {
469
+ })) : fetchedKeyRef.current = key;
470
+ }, [key, enabled, staleTimeMs, refetchOnMount, stableQueryFn]), React.useEffect(() => {
471
+ if (!key || !enabled || refetchIntervalMs === false) return;
472
+ let entry = getOrCreateEntry(key);
473
+ entry.queryFn = stableQueryFn;
474
+ let prev = entry.intervalCounts.get(refetchIntervalMs) ?? 0;
475
+ return entry.intervalCounts.set(refetchIntervalMs, prev + 1), startOrRestartInterval(key, entry), () => {
476
+ let count = entry.intervalCounts.get(refetchIntervalMs) ?? 0;
477
+ count <= 1 ? entry.intervalCounts.delete(refetchIntervalMs) : entry.intervalCounts.set(refetchIntervalMs, count - 1), startOrRestartInterval(key, entry);
478
+ };
479
+ }, [key, enabled, refetchIntervalMs, stableQueryFn]);
480
+ let refetch = React.useCallback(
481
+ async (refetchOptions) => {
482
+ if (key)
483
+ return fetchSharedQuery(key, stableQueryFn, {
484
+ force: true,
485
+ signal: refetchOptions?.signal
486
+ });
487
+ },
488
+ [key, stableQueryFn]
489
+ ), abort = React.useCallback(() => {
490
+ if (!key) return;
491
+ let entry = store.get(key);
492
+ entry?.abortController && entry.abortController.abort();
493
+ }, [key]);
494
+ return React.useMemo(
495
+ () => ({
496
+ data: stableSelectedData,
497
+ error: snapshot.error,
498
+ status: snapshot.status,
499
+ updatedAt: snapshot.updatedAt,
500
+ isFetching: snapshot.isFetching,
501
+ refetch,
502
+ abort
503
+ }),
504
+ [stableSelectedData, snapshot.error, snapshot.status, snapshot.updatedAt, snapshot.isFetching, refetch, abort]
505
+ );
506
+ }
507
+ function invalidateSharedQuery(key) {
508
+ let entry = store.get(key);
509
+ entry && setSnapshot(entry, {
510
+ ...entry.snapshot,
511
+ updatedAt: null
512
+ // Mark as stale
513
+ });
514
+ }
515
+ function clearSharedQueryCache() {
516
+ for (let [, entry] of store)
517
+ entry.intervalId && clearInterval(entry.intervalId), entry.gcTimeoutId && clearTimeout(entry.gcTimeoutId), entry.abortController && entry.abortController.abort();
518
+ store.clear();
519
+ }
520
+
521
+ // src/hooks/_internal/use-wallet-assets.ts
522
+ var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", NATIVE_SOL_MINT = "So11111111111111111111111111111111111111112";
523
+ function isRecord(value) {
524
+ return typeof value == "object" && value !== null;
525
+ }
526
+ function parseBigInt(value) {
527
+ if (typeof value == "bigint") return value;
528
+ if (typeof value == "number" && Number.isSafeInteger(value)) return BigInt(value);
529
+ if (typeof value == "string")
322
530
  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([]);
531
+ return BigInt(value);
532
+ } catch {
533
+ return 0n;
534
+ }
535
+ return 0n;
536
+ }
537
+ function getParsedTokenAccountInfo(data) {
538
+ if (!isRecord(data)) return null;
539
+ let parsed = data.parsed;
540
+ if (!isRecord(parsed)) return null;
541
+ let info = parsed.info;
542
+ return isRecord(info) ? info : null;
543
+ }
544
+ function parseTokenAccount(account, programId) {
545
+ let info = getParsedTokenAccountInfo(account.account.data);
546
+ if (!info) return null;
547
+ let mint = typeof info.mint == "string" ? info.mint : null, owner = typeof info.owner == "string" ? info.owner : null;
548
+ if (!mint || !owner) return null;
549
+ 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;
550
+ return {
551
+ pubkey: String(account.pubkey),
552
+ mint,
553
+ owner,
554
+ amount,
555
+ decimals,
556
+ isFrozen: state === "frozen",
557
+ programId
558
+ };
559
+ }
560
+ function getWalletAssetsQueryKey(rpcUrl, address) {
561
+ return !rpcUrl || !address ? null : JSON.stringify(["wallet-assets", rpcUrl, address]);
562
+ }
563
+ function useWalletAssets(options = {}) {
564
+ let {
565
+ enabled = true,
566
+ staleTimeMs = 0,
567
+ cacheTimeMs = 300 * 1e3,
568
+ // 5 minutes
569
+ refetchOnMount = "stale",
570
+ refetchIntervalMs = false,
571
+ client: clientOverride,
572
+ select
573
+ } = options, { address, connected } = useAccount(), { client: providerClient } = useSolanaClient(), rpcClient = clientOverride ?? providerClient, key = React.useMemo(() => {
574
+ if (!enabled || !connected || !address || !rpcClient) return null;
575
+ let rpcUrl = rpcClient.urlOrMoniker instanceof URL ? rpcClient.urlOrMoniker.toString() : String(rpcClient.urlOrMoniker);
576
+ return getWalletAssetsQueryKey(rpcUrl, address);
577
+ }, [enabled, connected, address, rpcClient]), queryFn = React.useCallback(
578
+ async (signal) => {
579
+ if (!connected || !address || !rpcClient)
580
+ return { lamports: 0n, tokenAccounts: [] };
581
+ if (signal.aborted)
582
+ throw new DOMException("Query aborted", "AbortError");
583
+ 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([
584
+ rpc.getBalance(walletAddress).send(),
585
+ rpc.getTokenAccountsByOwner(walletAddress, { programId: tokenProgramId }, { encoding: "jsonParsed" }).send(),
586
+ rpc.getTokenAccountsByOwner(
587
+ walletAddress,
588
+ { programId: token2022ProgramId },
589
+ { encoding: "jsonParsed" }
590
+ ).send()
591
+ ]);
592
+ if (signal.aborted)
593
+ throw new DOMException("Query aborted", "AbortError");
594
+ let tokenAccounts = [];
595
+ for (let account of tokenAccountsResult.value) {
596
+ let parsed = parseTokenAccount(account, "token");
597
+ parsed && tokenAccounts.push(parsed);
345
598
  }
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);
599
+ for (let account of token2022AccountsResult.value) {
600
+ let parsed = parseTokenAccount(account, "token-2022");
601
+ parsed && tokenAccounts.push(parsed);
602
+ }
603
+ return {
604
+ lamports: balanceResult.value,
605
+ tokenAccounts
606
+ };
607
+ },
608
+ [connected, address, rpcClient]
609
+ ), { data, error, status, updatedAt, isFetching, refetch, abort } = useSharedQuery(
610
+ key,
611
+ queryFn,
612
+ {
613
+ enabled,
614
+ staleTimeMs,
615
+ cacheTimeMs,
616
+ refetchOnMount,
617
+ refetchIntervalMs,
618
+ select
351
619
  }
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]);
620
+ ), isLoading = status === "loading" || status === "idle";
621
+ return React.useMemo(
622
+ () => ({
623
+ data,
624
+ isLoading,
625
+ isFetching,
626
+ error,
627
+ refetch,
628
+ abort,
629
+ updatedAt
630
+ }),
631
+ [data, isLoading, isFetching, error, refetch, abort, updatedAt]
632
+ );
633
+ }
634
+
635
+ // src/hooks/use-balance.ts
636
+ function getBalanceQueryKey(rpcUrl, address) {
637
+ return getWalletAssetsQueryKey(rpcUrl, address);
638
+ }
639
+ var LAMPORTS_PER_SOL2 = 1000000000n;
640
+ function formatTokenAccount(account) {
641
+ let formatted = chunkFTXIXM43_js.formatBigIntBalance(account.amount, account.decimals, {
642
+ maxDecimals: Math.min(account.decimals, 6)
643
+ });
644
+ return {
645
+ mint: account.mint,
646
+ amount: account.amount,
647
+ decimals: account.decimals,
648
+ formatted
649
+ };
650
+ }
651
+ function selectBalance(assets) {
652
+ if (!assets)
653
+ return { lamports: 0n, tokens: [] };
654
+ let tokens = assets.tokenAccounts.filter((account) => account.amount > 0n).map(formatTokenAccount);
655
+ return {
656
+ lamports: assets.lamports,
657
+ tokens
658
+ };
659
+ }
660
+ function useBalance(options = {}) {
661
+ let {
662
+ enabled = true,
663
+ autoRefresh = true,
664
+ refreshInterval = 3e4,
665
+ staleTimeMs = 0,
666
+ cacheTimeMs = 300 * 1e3,
667
+ // 5 minutes
668
+ refetchOnMount = "stale",
669
+ client: clientOverride
670
+ } = options, {
671
+ data,
672
+ error,
673
+ isFetching,
674
+ updatedAt,
675
+ refetch: sharedRefetch,
676
+ abort
677
+ } = useWalletAssets({
678
+ enabled,
679
+ staleTimeMs,
680
+ cacheTimeMs,
681
+ refetchOnMount,
682
+ refetchIntervalMs: autoRefresh ? refreshInterval : false,
683
+ client: clientOverride,
684
+ select: selectBalance
685
+ }), lamports = data?.lamports ?? 0n, tokens = data?.tokens ?? [], solBalance = React.useMemo(() => Number(lamports) / Number(LAMPORTS_PER_SOL2), [lamports]), formattedSol = React.useMemo(() => chunkFTXIXM43_js.formatLamportsToSolSafe(lamports, { maxDecimals: 4, suffix: true }), [lamports]), visibleError = updatedAt ? null : error, refetch = React.useCallback(
686
+ async (opts) => {
687
+ await sharedRefetch(opts);
688
+ },
689
+ [sharedRefetch]
690
+ );
361
691
  return React.useMemo(
362
692
  () => ({
363
693
  solBalance,
364
694
  lamports,
365
695
  formattedSol,
366
696
  tokens,
367
- isLoading,
368
- error,
369
- refetch: fetchBalance,
370
- lastUpdated
697
+ isLoading: isFetching,
698
+ error: visibleError,
699
+ refetch,
700
+ abort,
701
+ lastUpdated: updatedAt ? new Date(updatedAt) : null
371
702
  }),
372
- [solBalance, lamports, formattedSol, tokens, isLoading, error, fetchBalance, lastUpdated]
703
+ [solBalance, lamports, formattedSol, tokens, isFetching, visibleError, refetch, abort, updatedAt]
373
704
  );
374
705
  }
706
+
707
+ // src/utils/abort.ts
708
+ function createTimeoutSignal(ms) {
709
+ if (typeof AbortSignal.timeout == "function")
710
+ return { signal: AbortSignal.timeout(ms), cleanup: () => {
711
+ } };
712
+ let controller = new AbortController(), timeoutId = setTimeout(() => controller.abort(), ms);
713
+ return {
714
+ signal: controller.signal,
715
+ cleanup: () => clearTimeout(timeoutId)
716
+ };
717
+ }
718
+
719
+ // src/hooks/_internal/solana-token-list.ts
720
+ var CLUSTER_CHAIN_IDS = {
721
+ mainnet: 101,
722
+ testnet: 102,
723
+ devnet: 103,
724
+ localnet: 103,
725
+ // Use devnet tokens for localnet
726
+ custom: 101
727
+ // Default to mainnet for custom clusters
728
+ }, TOKEN_LIST_API_BASE_URL = "https://token-list-api.solana.cloud/v1/mints";
729
+ function getTokenListApiUrl(cluster = "mainnet") {
730
+ let chainId = CLUSTER_CHAIN_IDS[cluster];
731
+ return `${TOKEN_LIST_API_BASE_URL}?chainId=${chainId}`;
732
+ }
733
+ var DEFAULT_TIMEOUT_MS = 1e4, TOKEN_LIST_CACHE_MAX_SIZE = 1500, MAX_ADDRESSES_PER_REQUEST = 100, tokenListCache = /* @__PURE__ */ new Map();
734
+ function getCachedTokenListMetadata(mint) {
735
+ let value = tokenListCache.get(mint);
736
+ if (value)
737
+ return tokenListCache.delete(mint), tokenListCache.set(mint, value), value;
738
+ }
739
+ function setCachedTokenListMetadata(mint, value) {
740
+ if (tokenListCache.has(mint) && tokenListCache.delete(mint), tokenListCache.set(mint, value), tokenListCache.size > TOKEN_LIST_CACHE_MAX_SIZE) {
741
+ let oldestKey = tokenListCache.keys().next().value;
742
+ oldestKey && tokenListCache.delete(oldestKey);
743
+ }
744
+ }
745
+ function createLinkedSignal(externalSignal, timeoutMs) {
746
+ let controller = new AbortController(), onAbort = () => controller.abort();
747
+ externalSignal && (externalSignal.aborted ? controller.abort() : externalSignal.addEventListener("abort", onAbort, { once: true }));
748
+ let { signal: timeoutSignal, cleanup: cleanupTimeout } = createTimeoutSignal(timeoutMs), onTimeoutAbort = () => controller.abort();
749
+ return timeoutSignal.aborted ? controller.abort() : timeoutSignal.addEventListener("abort", onTimeoutAbort, { once: true }), {
750
+ signal: controller.signal,
751
+ cleanup: () => {
752
+ cleanupTimeout(), externalSignal && externalSignal.removeEventListener("abort", onAbort), timeoutSignal.removeEventListener("abort", onTimeoutAbort);
753
+ }
754
+ };
755
+ }
756
+ async function fetchSolanaTokenListMetadata(mints, options = {}) {
757
+ let results = /* @__PURE__ */ new Map();
758
+ if (!mints.length) return results;
759
+ let seen = /* @__PURE__ */ new Set(), uncached = [];
760
+ for (let mint of mints) {
761
+ let normalized = mint?.trim();
762
+ if (!normalized || seen.has(normalized)) continue;
763
+ seen.add(normalized);
764
+ let cached = getCachedTokenListMetadata(normalized);
765
+ cached ? results.set(normalized, cached) : uncached.push(normalized);
766
+ }
767
+ if (!uncached.length) return results;
768
+ let timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS, apiUrl = getTokenListApiUrl(options.cluster);
769
+ for (let i = 0; i < uncached.length && !options.signal?.aborted; i += MAX_ADDRESSES_PER_REQUEST) {
770
+ let batch = uncached.slice(i, i + MAX_ADDRESSES_PER_REQUEST), { signal, cleanup } = createLinkedSignal(options.signal, timeoutMs);
771
+ try {
772
+ let response = await fetch(apiUrl, {
773
+ method: "POST",
774
+ headers: { "Content-Type": "application/json" },
775
+ body: JSON.stringify({ addresses: batch }),
776
+ signal
777
+ });
778
+ if (!response.ok) {
779
+ console.warn("[token-list] Solana Token List API error:", response.status, response.statusText);
780
+ continue;
781
+ }
782
+ let data = await response.json();
783
+ if (!data?.content?.length) continue;
784
+ for (let item of data.content)
785
+ item?.address && (results.set(item.address, item), setCachedTokenListMetadata(item.address, item));
786
+ } catch (error) {
787
+ console.warn("[token-list] Solana Token List API failed:", error);
788
+ } finally {
789
+ cleanup();
790
+ }
791
+ }
792
+ return results;
793
+ }
794
+
795
+ // src/utils/image.ts
796
+ function transformImageUrl(url, imageProxy) {
797
+ if (url)
798
+ return imageProxy ? `${imageProxy}${encodeURIComponent(url)}` : url;
799
+ }
800
+
801
+ // src/hooks/use-transactions.ts
802
+ function getTransactionsQueryKey(options) {
803
+ let { rpcUrl, address, clusterId, limit = 20, fetchDetails = false } = options;
804
+ return !rpcUrl || !address || !clusterId ? null : JSON.stringify(["wallet-transactions", rpcUrl, address, clusterId, limit, fetchDetails]);
805
+ }
375
806
  function formatDate(timestamp) {
376
807
  if (!timestamp)
377
808
  return { date: "Unknown", time: "" };
@@ -383,6 +814,45 @@ function formatDate(timestamp) {
383
814
  });
384
815
  return { date: formattedDate, time: formattedTime };
385
816
  }
817
+ var KNOWN_PROGRAMS = {
818
+ "11111111111111111111111111111111": "System Program",
819
+ TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA: "Token Program",
820
+ ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL: "Associated Token",
821
+ JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4: "Jupiter",
822
+ whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc: "Orca Whirlpool",
823
+ "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8": "Raydium AMM",
824
+ Stake11111111111111111111111111111111111111: "Stake Program",
825
+ metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s: "Metaplex"
826
+ }, DEFAULT_IGNORED_PROGRAM_IDS = /* @__PURE__ */ new Set([
827
+ "11111111111111111111111111111111",
828
+ // System Program
829
+ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
830
+ // Token Program
831
+ "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
832
+ // Associated Token
833
+ ]);
834
+ function resolveProgramName(programId, programLabels) {
835
+ return programLabels?.[programId] ?? KNOWN_PROGRAMS[programId];
836
+ }
837
+ function pickPrimaryProgramId(programIds) {
838
+ for (let id of programIds)
839
+ if (!DEFAULT_IGNORED_PROGRAM_IDS.has(id)) return id;
840
+ return programIds.values().next().value;
841
+ }
842
+ function getParsedInstructionTypes(message) {
843
+ if (!Array.isArray(message.instructions)) return;
844
+ let types = [];
845
+ for (let ix of message.instructions) {
846
+ if (!ix || typeof ix != "object") continue;
847
+ let parsed = ix.parsed;
848
+ if (!parsed || typeof parsed != "object" || !("type" in parsed)) continue;
849
+ let t = parsed.type;
850
+ if (typeof t == "string" && (types.push(t), types.length >= 10))
851
+ break;
852
+ }
853
+ let unique = [...new Set(types)];
854
+ return unique.length ? unique : void 0;
855
+ }
386
856
  function isAccountKey(value) {
387
857
  return typeof value == "object" && value !== null && "pubkey" in value && typeof value.pubkey == "string";
388
858
  }
@@ -407,6 +877,13 @@ function isTransactionWithMeta(value) {
407
877
  function isTransactionMessage(value) {
408
878
  return typeof value == "object" && value !== null && "accountKeys" in value && Array.isArray(value.accountKeys);
409
879
  }
880
+ function coerceMaybeAddressString(value) {
881
+ if (typeof value == "string") return value;
882
+ if (value && typeof value == "object") {
883
+ let str = String(value);
884
+ if (str && str !== "[object Object]") return str;
885
+ }
886
+ }
410
887
  function getAccountKeys(message) {
411
888
  return Array.isArray(message.accountKeys) ? message.accountKeys.map((key) => typeof key == "string" ? key : isAccountKey(key) ? key.pubkey : "").filter(Boolean) : [];
412
889
  }
@@ -438,12 +915,12 @@ function parseTokenTransfers(meta, accountKeys, walletAddress) {
438
915
  return null;
439
916
  let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
440
917
  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();
918
+ let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
919
+ return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
443
920
  }), ourPostTokens = postTokenBalances.filter((balance) => {
444
921
  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();
922
+ let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
923
+ return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
447
924
  }), allMints = /* @__PURE__ */ new Set();
448
925
  for (let token of ourPreTokens)
449
926
  isTokenBalance(token) && allMints.add(token.mint);
@@ -487,6 +964,52 @@ function parseTokenTransfers(meta, accountKeys, walletAddress) {
487
964
  }
488
965
  return null;
489
966
  }
967
+ function parseTokenAccountClosure(meta, accountKeys, walletAddress) {
968
+ if (!isTransactionMeta(meta))
969
+ return null;
970
+ let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
971
+ if (!isTokenBalance(balance)) return false;
972
+ let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
973
+ return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
974
+ }), ourPostTokens = postTokenBalances.filter((balance) => {
975
+ if (!isTokenBalance(balance)) return false;
976
+ let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
977
+ return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
978
+ }), postKeys = /* @__PURE__ */ new Set();
979
+ for (let token of ourPostTokens)
980
+ isTokenBalance(token) && postKeys.add(`${token.accountIndex}:${token.mint}`);
981
+ for (let token of ourPreTokens) {
982
+ if (!isTokenBalance(token)) continue;
983
+ let key = `${token.accountIndex}:${token.mint}`;
984
+ if (!postKeys.has(key))
985
+ return { tokenMint: token.mint };
986
+ }
987
+ return null;
988
+ }
989
+ function parseSwapTokens(meta, accountKeys, walletAddress, solChange) {
990
+ if (!isTransactionMeta(meta))
991
+ return {};
992
+ let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
993
+ if (!isTokenBalance(balance)) return false;
994
+ let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
995
+ return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
996
+ }), ourPostTokens = postTokenBalances.filter((balance) => {
997
+ if (!isTokenBalance(balance)) return false;
998
+ let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
999
+ return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
1000
+ }), allMints = /* @__PURE__ */ new Set();
1001
+ for (let token of ourPreTokens)
1002
+ isTokenBalance(token) && allMints.add(token.mint);
1003
+ for (let token of ourPostTokens)
1004
+ isTokenBalance(token) && allMints.add(token.mint);
1005
+ let fromToken, toToken;
1006
+ for (let mint of allMints) {
1007
+ 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;
1008
+ change < 0 && !fromToken ? fromToken = { mint } : change > 0 && !toToken && (toToken = { mint });
1009
+ }
1010
+ let WRAPPED_SOL_MINT = "So11111111111111111111111111111111111111112";
1011
+ return solChange < -1e-3 && !fromToken ? fromToken = { mint: WRAPPED_SOL_MINT } : solChange > 1e-3 && !toToken && (toToken = { mint: WRAPPED_SOL_MINT }), { fromToken, toToken };
1012
+ }
490
1013
  function formatAmount(tokenAmount, tokenDecimals, direction, solChange) {
491
1014
  if (tokenAmount !== void 0 && tokenDecimals !== void 0 && direction !== void 0) {
492
1015
  let sign = direction === "in" ? "+" : "-", maxDecimals = Math.min(tokenDecimals, 6);
@@ -495,52 +1018,59 @@ function formatAmount(tokenAmount, tokenDecimals, direction, solChange) {
495
1018
  if (solChange !== 0)
496
1019
  return `${solChange > 0 ? "+" : ""}${solChange.toFixed(4)} SOL`;
497
1020
  }
498
- var tokenMetadataCache = /* @__PURE__ */ new Map();
499
- function transformImageUrl(url, imageProxy) {
500
- if (url)
501
- return imageProxy ? `${imageProxy}${encodeURIComponent(url)}` : url;
1021
+ function throwIfAborted(signal) {
1022
+ if (signal?.aborted)
1023
+ throw new DOMException("Query aborted", "AbortError");
502
1024
  }
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);
1025
+ function clampInt(value, min, max) {
1026
+ return Number.isFinite(value) ? Math.min(max, Math.max(min, Math.floor(value))) : min;
1027
+ }
1028
+ async function mapWithConcurrency(inputs, worker, options) {
1029
+ let concurrency = clampInt(options.concurrency, 1, 32), results = new Array(inputs.length), nextIndex = 0;
1030
+ async function run() {
1031
+ for (; ; ) {
1032
+ throwIfAborted(options.signal);
1033
+ let index = nextIndex;
1034
+ if (nextIndex += 1, index >= inputs.length) return;
1035
+ results[index] = await worker(inputs[index], index);
524
1036
  }
525
- } catch (error) {
526
- console.warn("[useTransactions] Failed to fetch token metadata:", error);
527
1037
  }
1038
+ let runners = Array.from({ length: Math.min(concurrency, inputs.length) }, () => run());
1039
+ return await Promise.all(runners), results;
1040
+ }
1041
+ async function fetchTransactionTokenMetadata(mints, options = {}) {
1042
+ let results = /* @__PURE__ */ new Map();
1043
+ if (!mints.length) return results;
1044
+ let tokenList = await fetchSolanaTokenListMetadata(mints, {
1045
+ timeoutMs: 5e3,
1046
+ signal: options.signal,
1047
+ cluster: options.cluster
1048
+ });
1049
+ for (let [mint, meta] of tokenList)
1050
+ results.set(mint, { symbol: meta.symbol, icon: meta.logoURI });
528
1051
  return results;
529
1052
  }
530
1053
  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(
1054
+ let {
1055
+ enabled = true,
1056
+ limit = 10,
1057
+ autoRefresh = false,
1058
+ refreshInterval = 6e4,
1059
+ fetchDetails = true,
1060
+ detailsConcurrency = 6,
1061
+ staleTimeMs = 0,
1062
+ cacheTimeMs = 300 * 1e3,
1063
+ // 5 minutes
1064
+ refetchOnMount = "stale",
1065
+ client: clientOverride
1066
+ } = 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
1067
  (tx, walletAddress, sig, blockTime, slot, err, explorerUrl) => {
535
1068
  let { date, time } = formatDate(blockTime), baseInfo = {
536
1069
  signature: sig,
537
1070
  blockTime,
538
1071
  slot,
539
1072
  status: err ? "failed" : "success",
540
- error: err ? JSON.stringify(
541
- err,
542
- (_key, value) => typeof value == "bigint" ? value.toString() : value
543
- ) : void 0,
1073
+ error: err ? JSON.stringify(err, (_key, value) => typeof value == "bigint" ? value.toString() : value) : void 0,
544
1074
  type: "unknown",
545
1075
  formattedDate: date,
546
1076
  formattedTime: time,
@@ -555,24 +1085,13 @@ function useTransactions(options = {}) {
555
1085
  let { message } = transaction;
556
1086
  if (!isTransactionMessage(message))
557
1087
  return baseInfo;
558
- let accountKeys = getAccountKeys(message), walletIndex = accountKeys.findIndex((key) => key.trim() === walletAddress.trim());
1088
+ let accountKeys = getAccountKeys(message), walletIndex = accountKeys.findIndex((key2) => key2.trim() === walletAddress.trim());
559
1089
  if (walletIndex === -1)
560
1090
  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");
1091
+ 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;
1092
+ 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(
1093
+ (key2, idx) => idx !== walletIndex && key2 !== "11111111111111111111111111111111"
1094
+ ))) : programIds.size > 0 && (type = "program");
576
1095
  let formattedAmount = formatAmount(tokenAmount, tokenDecimals, direction, solChange);
577
1096
  return {
578
1097
  ...baseInfo,
@@ -581,136 +1100,197 @@ function useTransactions(options = {}) {
581
1100
  amount: tokenAmount ?? Math.abs(solChange),
582
1101
  formattedAmount,
583
1102
  tokenMint,
584
- counterparty: counterparty ? `${counterparty.slice(0, 4)}...${counterparty.slice(-4)}` : void 0
1103
+ counterparty: counterparty ? `${counterparty.slice(0, 4)}...${counterparty.slice(-4)}` : void 0,
1104
+ swapFromToken,
1105
+ swapToToken,
1106
+ programId,
1107
+ programName,
1108
+ programIds: programIdsArray.length ? programIdsArray : void 0,
1109
+ instructionTypes,
1110
+ instructionCount
585
1111
  };
586
1112
  } catch (parseError) {
587
1113
  return console.warn("Failed to parse transaction:", parseError), baseInfo;
588
1114
  }
589
1115
  },
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)
1116
+ [programLabels]
1117
+ ), key = React.useMemo(() => {
1118
+ if (!enabled || !connected || !address || !rpcClient || !cluster) return null;
1119
+ let rpcUrl = rpcClient.urlOrMoniker instanceof URL ? rpcClient.urlOrMoniker.toString() : String(rpcClient.urlOrMoniker);
1120
+ return getTransactionsQueryKey({ rpcUrl, address, clusterId: cluster.id, limit, fetchDetails });
1121
+ }, [enabled, connected, address, rpcClient, cluster, limit, fetchDetails]);
1122
+ React.useEffect(() => {
1123
+ beforeSignatureRef.current = void 0, setPaginatedTransactions([]), setIsPaginationLoading(false), setHasMore(true);
1124
+ }, [key]);
1125
+ let fetchAndEnrichTransactions = React.useCallback(
1126
+ async (beforeSignature, currentCluster, signal) => {
1127
+ if (!rpcClient || !address)
1128
+ return { transactions: [], hasMore: false };
1129
+ throwIfAborted(signal);
1130
+ let rpc = rpcClient.rpc, walletAddress = addresses.address(address), signaturesResult = await rpc.getSignaturesForAddress(walletAddress, {
1131
+ limit,
1132
+ ...beforeSignature ? { before: keys.signature(beforeSignature) } : {}
1133
+ }).send();
1134
+ throwIfAborted(signal);
1135
+ let newTransactions;
1136
+ if (fetchDetails && signaturesResult.length > 0) {
1137
+ let txDetails = await mapWithConcurrency(
1138
+ signaturesResult,
1139
+ async (sig) => rpc.getTransaction(keys.signature(String(sig.signature)), {
1140
+ encoding: "jsonParsed",
1141
+ maxSupportedTransactionVersion: 0
1142
+ }).send().catch(() => null),
1143
+ { concurrency: detailsConcurrency, signal }
1144
+ );
1145
+ throwIfAborted(signal), newTransactions = signaturesResult.map((sig, idx) => {
1146
+ let blockTimeNum = sig.blockTime ? Number(sig.blockTime) : null, tx = txDetails[idx];
1147
+ return parseTransaction(
1148
+ tx,
1149
+ address,
1150
+ String(sig.signature),
1151
+ blockTimeNum,
1152
+ Number(sig.slot),
1153
+ sig.err,
1154
+ chunkFTXIXM43_js.getTransactionUrl(String(sig.signature), currentCluster)
1155
+ );
1156
+ });
1157
+ } else
1158
+ newTransactions = signaturesResult.map((sig) => {
1159
+ let blockTimeNum = sig.blockTime ? Number(sig.blockTime) : null, { date, time } = formatDate(blockTimeNum);
1160
+ return {
1161
+ signature: String(sig.signature),
1162
+ blockTime: blockTimeNum,
1163
+ slot: Number(sig.slot),
1164
+ status: sig.err ? "failed" : "success",
1165
+ error: sig.err ? JSON.stringify(sig.err) : void 0,
1166
+ type: "unknown",
1167
+ formattedDate: date,
1168
+ formattedTime: time,
1169
+ explorerUrl: chunkFTXIXM43_js.getTransactionUrl(String(sig.signature), currentCluster)
1170
+ };
1171
+ });
1172
+ let mintsToFetch = [
1173
+ .../* @__PURE__ */ new Set([
1174
+ ...newTransactions.filter((tx) => tx.tokenMint).map((tx) => tx.tokenMint),
1175
+ ...newTransactions.filter((tx) => tx.swapFromToken?.mint).map((tx) => tx.swapFromToken.mint),
1176
+ ...newTransactions.filter((tx) => tx.swapToToken?.mint).map((tx) => tx.swapToToken.mint)
1177
+ ])
1178
+ ];
1179
+ if (mintsToFetch.length > 0) {
1180
+ throwIfAborted(signal);
1181
+ let tokenMetadata = await fetchTransactionTokenMetadata(mintsToFetch, {
1182
+ signal,
1183
+ cluster: chunkFTXIXM43_js.getClusterType(currentCluster)
1184
+ });
1185
+ tokenMetadata.size > 0 && (newTransactions = newTransactions.map((tx) => {
1186
+ let enrichedTx = { ...tx };
1187
+ if (tx.tokenMint && tokenMetadata.has(tx.tokenMint)) {
1188
+ let meta = tokenMetadata.get(tx.tokenMint);
1189
+ enrichedTx = {
1190
+ ...enrichedTx,
1191
+ tokenSymbol: meta.symbol,
1192
+ tokenIcon: transformImageUrl(meta.icon, imageProxy),
1193
+ formattedAmount: tx.formattedAmount ? `${tx.formattedAmount} ${meta.symbol}` : tx.formattedAmount
635
1194
  };
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
- };
1195
+ }
1196
+ if (tx.swapFromToken?.mint && tokenMetadata.has(tx.swapFromToken.mint)) {
1197
+ let meta = tokenMetadata.get(tx.swapFromToken.mint);
1198
+ enrichedTx = {
1199
+ ...enrichedTx,
1200
+ swapFromToken: {
1201
+ ...tx.swapFromToken,
1202
+ symbol: meta.symbol,
1203
+ icon: transformImageUrl(meta.icon, imageProxy)
652
1204
  }
653
- return tx;
654
- });
655
- setTransactions(loadMore ? (prev) => [...prev.slice(0, -newTransactions.length), ...enrichedTransactions] : enrichedTransactions);
1205
+ };
656
1206
  }
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;
1207
+ if (tx.swapToToken?.mint && tokenMetadata.has(tx.swapToToken.mint)) {
1208
+ let meta = tokenMetadata.get(tx.swapToToken.mint);
1209
+ enrichedTx = {
1210
+ ...enrichedTx,
1211
+ swapToToken: {
1212
+ ...tx.swapToToken,
1213
+ symbol: meta.symbol,
1214
+ icon: transformImageUrl(meta.icon, imageProxy)
1215
+ }
1216
+ };
662
1217
  }
663
- setHasMore(newTransactions.length === limit);
664
- }
665
- setLastUpdated(/* @__PURE__ */ new Date());
1218
+ return enrichedTx;
1219
+ }));
1220
+ }
1221
+ return {
1222
+ transactions: newTransactions,
1223
+ hasMore: newTransactions.length === limit
1224
+ };
1225
+ },
1226
+ [rpcClient, address, limit, fetchDetails, detailsConcurrency, parseTransaction, imageProxy]
1227
+ ), queryFn = React.useCallback(
1228
+ async (signal) => {
1229
+ if (!connected || !address || !rpcClient || !cluster)
1230
+ return [];
1231
+ throwIfAborted(signal);
1232
+ let result = await fetchAndEnrichTransactions(void 0, cluster, signal);
1233
+ return throwIfAborted(signal), result.transactions;
1234
+ },
1235
+ [connected, address, rpcClient, cluster, fetchAndEnrichTransactions]
1236
+ ), {
1237
+ data: initialTransactions,
1238
+ error,
1239
+ isFetching: isInitialLoading,
1240
+ updatedAt,
1241
+ refetch: sharedRefetch,
1242
+ abort
1243
+ } = useSharedQuery(key, queryFn, {
1244
+ enabled,
1245
+ staleTimeMs,
1246
+ cacheTimeMs,
1247
+ refetchOnMount,
1248
+ refetchIntervalMs: autoRefresh ? refreshInterval : false
1249
+ });
1250
+ React.useEffect(() => {
1251
+ initialTransactions && (beforeSignatureRef.current = initialTransactions.length ? initialTransactions[initialTransactions.length - 1].signature : void 0, setHasMore(initialTransactions.length === limit), setPaginatedTransactions((prev) => prev.length ? [] : prev));
1252
+ }, [initialTransactions, limit]);
1253
+ let loadMoreFn = React.useCallback(async () => {
1254
+ if (!(!hasMore || isPaginationLoading || !cluster)) {
1255
+ setIsPaginationLoading(true);
1256
+ try {
1257
+ let result = await fetchAndEnrichTransactions(beforeSignatureRef.current, cluster);
1258
+ result.transactions.length > 0 && (beforeSignatureRef.current = result.transactions[result.transactions.length - 1].signature, setPaginatedTransactions((prev) => [...prev, ...result.transactions])), setHasMore(result.hasMore);
666
1259
  } catch (err) {
667
- setError(err), console.error("Failed to fetch transactions:", err);
1260
+ console.error("Failed to load more transactions:", err);
668
1261
  } finally {
669
- setIsLoading(false);
1262
+ setIsPaginationLoading(false);
670
1263
  }
1264
+ }
1265
+ }, [hasMore, isPaginationLoading, cluster, fetchAndEnrichTransactions]), refetch = React.useCallback(
1266
+ async (opts) => {
1267
+ beforeSignatureRef.current = void 0, setPaginatedTransactions([]), setHasMore(true), await sharedRefetch(opts);
671
1268
  },
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(
1269
+ [sharedRefetch]
1270
+ ), transactions = React.useMemo(() => [...initialTransactions ?? [], ...paginatedTransactions], [initialTransactions, paginatedTransactions]), isLoading = isInitialLoading || isPaginationLoading, visibleError = updatedAt ? null : error;
1271
+ return React.useMemo(
686
1272
  () => ({
687
1273
  transactions,
688
1274
  isLoading,
689
- error,
1275
+ error: visibleError,
690
1276
  hasMore,
691
1277
  loadMore: loadMoreFn,
692
1278
  refetch,
693
- lastUpdated
1279
+ abort,
1280
+ lastUpdated: updatedAt ? new Date(updatedAt) : null
694
1281
  }),
695
- [transactions, isLoading, error, hasMore, loadMoreFn, refetch, lastUpdated]
1282
+ [transactions, isLoading, visibleError, hasMore, loadMoreFn, refetch, abort, updatedAt]
696
1283
  );
697
1284
  }
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
- };
1285
+ function getTokensQueryKey(rpcUrl, address) {
1286
+ return getWalletAssetsQueryKey(rpcUrl, address);
707
1287
  }
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 {
1288
+ 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
1289
  constructor(maxSize, options) {
710
- chunkI64FD2EH_js.__publicField(this, "cache", /* @__PURE__ */ new Map());
711
- chunkI64FD2EH_js.__publicField(this, "maxSize");
712
- chunkI64FD2EH_js.__publicField(this, "getTtl");
713
- chunkI64FD2EH_js.__publicField(this, "getTimestamp");
1290
+ chunkDSUCH44G_js.__publicField(this, "cache", /* @__PURE__ */ new Map());
1291
+ chunkDSUCH44G_js.__publicField(this, "maxSize");
1292
+ chunkDSUCH44G_js.__publicField(this, "getTtl");
1293
+ chunkDSUCH44G_js.__publicField(this, "getTimestamp");
714
1294
  this.maxSize = maxSize, this.getTtl = options?.getTtl, this.getTimestamp = options?.getTimestamp;
715
1295
  }
716
1296
  get(key) {
@@ -745,10 +1325,6 @@ var NATIVE_MINT = "So11111111111111111111111111111111111111112", CACHE_MAX_SIZE
745
1325
  get size() {
746
1326
  return this.cache.size;
747
1327
  }
748
- /**
749
- * Prune stale entries based on TTL.
750
- * Only works if getTtl and getTimestamp are provided.
751
- */
752
1328
  pruneStale() {
753
1329
  if (!this.getTtl || !this.getTimestamp) return 0;
754
1330
  let now = Date.now(), pruned = 0;
@@ -773,27 +1349,6 @@ function stopCacheCleanup() {
773
1349
  function clearTokenCaches() {
774
1350
  metadataCache.clear(), priceCache.clear();
775
1351
  }
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
1352
  function calculateBackoffDelay(attempt, baseDelay, retryAfter) {
798
1353
  if (retryAfter !== void 0 && retryAfter > 0) {
799
1354
  let jitter2 = Math.random() * 500;
@@ -829,26 +1384,17 @@ async function fetchCoinGeckoPrices(coingeckoIds, config) {
829
1384
  let elapsedTime = Date.now() - startTime;
830
1385
  if (elapsedTime >= maxTimeout) {
831
1386
  console.warn(
832
- `[useTokens] CoinGecko API: Total timeout (${maxTimeout}ms) exceeded after ${attempt} attempts. Returning cached/partial results.`
1387
+ `[useTokens] CoinGecko API: Total timeout (${maxTimeout}ms) exceeded after ${attempt} attempts.`
833
1388
  );
834
1389
  break;
835
1390
  }
836
1391
  let remainingTimeout = maxTimeout - elapsedTime, requestTimeout = Math.min(1e4, remainingTimeout), { signal, cleanup } = createTimeoutSignal(requestTimeout);
837
1392
  try {
838
- let response = await fetch(url, {
839
- headers,
840
- signal
841
- });
1393
+ let response = await fetch(url, { headers, signal });
842
1394
  if (cleanup(), response.status === 429) {
843
1395
  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
- );
1396
+ if (console.warn(`[useTokens] CoinGecko API rate limited (429). Waiting ${Math.round(delay)}ms.`), Date.now() - startTime + delay >= maxTimeout)
850
1397
  break;
851
- }
852
1398
  await new Promise((resolve) => setTimeout(resolve, delay)), attempt++;
853
1399
  continue;
854
1400
  }
@@ -859,174 +1405,195 @@ async function fetchCoinGeckoPrices(coingeckoIds, config) {
859
1405
  priceData?.usd !== void 0 && (results.set(id, priceData.usd), priceCache.set(id, { price: priceData.usd, timestamp: fetchTime }));
860
1406
  return results;
861
1407
  } 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) {
1408
+ if (cleanup(), lastError = error, attempt < maxRetries) {
868
1409
  let delay = calculateBackoffDelay(attempt, baseDelay);
869
1410
  Date.now() - startTime + delay < maxTimeout && await new Promise((resolve) => setTimeout(resolve, delay));
870
1411
  }
871
1412
  attempt++;
872
1413
  }
873
1414
  }
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;
1415
+ return attempt > maxRetries && lastError && console.warn(`[useTokens] CoinGecko API: All attempts failed. Last error: ${lastError.message}`), results;
877
1416
  }
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);
1417
+ async function fetchTokenMetadataHybrid(mints, coingeckoConfig, options) {
1418
+ if (mints.length === 0) return false;
1419
+ let now = Date.now(), mintsNeedingTokenList = [], staleCoingeckoIdToMint = /* @__PURE__ */ new Map();
890
1420
  for (let mint of mints) {
891
1421
  let cached = metadataCache.get(mint);
892
- cached?.coingeckoId && !coingeckoIdToMint.has(cached.coingeckoId) && coingeckoIdToMint.set(cached.coingeckoId, mint);
1422
+ if (!cached) {
1423
+ mintsNeedingTokenList.push(mint);
1424
+ continue;
1425
+ }
1426
+ if (!cached.coingeckoId) continue;
1427
+ let priceEntry = priceCache.get(cached.coingeckoId);
1428
+ !!(priceEntry && now - priceEntry.timestamp < PRICE_CACHE_TTL) || staleCoingeckoIdToMint.set(cached.coingeckoId, mint);
893
1429
  }
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 = {
1430
+ let didUpdate = false, tokenListMetadata = await fetchSolanaTokenListMetadata(mintsNeedingTokenList, {
1431
+ timeoutMs: 1e4,
1432
+ cluster: options?.cluster
1433
+ });
1434
+ for (let [mint, meta] of tokenListMetadata) {
1435
+ let coingeckoId = meta.extensions?.coingeckoId, usdPrice = (coingeckoId ? priceCache.get(coingeckoId) : void 0)?.price, combined = {
897
1436
  address: meta.address,
898
- name: meta.address === NATIVE_MINT ? "Solana" : meta.name,
1437
+ name: meta.address === NATIVE_SOL_MINT ? "Solana" : meta.name,
899
1438
  symbol: meta.symbol,
900
1439
  decimals: meta.decimals,
901
1440
  logoURI: meta.logoURI,
902
1441
  coingeckoId,
903
1442
  usdPrice
904
- };
905
- results.set(mint, combined), metadataCache.set(mint, combined);
1443
+ }, existing = metadataCache.get(mint);
1444
+ (!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
1445
  }
1446
+ didUpdate && options?.onUpdate?.();
1447
+ let coingeckoIdToMint = new Map(staleCoingeckoIdToMint);
1448
+ for (let [mint, meta] of tokenListMetadata)
1449
+ meta.extensions?.coingeckoId && coingeckoIdToMint.set(meta.extensions.coingeckoId, mint);
1450
+ if (coingeckoIdToMint.size === 0)
1451
+ return didUpdate;
1452
+ let prices = await fetchCoinGeckoPrices([...coingeckoIdToMint.keys()], coingeckoConfig), didUpdatePrices = false;
907
1453
  for (let [coingeckoId, mint] of coingeckoIdToMint) {
908
- let cached = results.get(mint) ?? metadataCache.get(mint);
1454
+ let cached = metadataCache.get(mint);
909
1455
  if (cached) {
910
1456
  let usdPrice = prices.get(coingeckoId);
911
- usdPrice !== void 0 && (cached.usdPrice = usdPrice, results.set(mint, cached), metadataCache.set(mint, cached));
1457
+ usdPrice !== void 0 && cached.usdPrice !== usdPrice && (didUpdate = true, didUpdatePrices = true, cached.usdPrice = usdPrice, metadataCache.set(mint, cached));
912
1458
  }
913
1459
  }
914
- return results;
1460
+ return didUpdatePrices && options?.onUpdate?.(), didUpdate;
915
1461
  }
916
1462
  function formatBalance(amount, decimals) {
917
- return (Number(amount) / Math.pow(10, decimals)).toLocaleString(void 0, {
918
- minimumFractionDigits: 0,
919
- maximumFractionDigits: Math.min(decimals, 6)
1463
+ return chunkFTXIXM43_js.formatBigIntBalance(amount, decimals, {
1464
+ maxDecimals: Math.min(decimals, 6)
920
1465
  });
921
1466
  }
922
1467
  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
- });
1468
+ return chunkFTXIXM43_js.formatBigIntUsd(amount, decimals, usdPrice);
929
1469
  }
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;
1470
+ function selectTokens(assets, address) {
1471
+ return {
1472
+ lamports: assets?.lamports ?? 0n,
1473
+ tokenAccounts: assets?.tokenAccounts ?? [],
1474
+ address
1475
+ };
1476
+ }
1477
+ function sortByValueDesc(a, b) {
1478
+ let metadataSort = (b.logo ? 1 : 0) - (a.logo ? 1 : 0);
1479
+ if (metadataSort !== 0) return metadataSort;
1480
+ let aValue = Number(a.amount) / Math.pow(10, a.decimals) * (a.usdPrice ?? 0);
1481
+ return Number(b.amount) / Math.pow(10, b.decimals) * (b.usdPrice ?? 0) - aValue;
935
1482
  }
936
1483
  function useTokens(options = {}) {
937
1484
  let {
1485
+ enabled = true,
938
1486
  includeZeroBalance = false,
939
1487
  autoRefresh = false,
940
1488
  refreshInterval = 6e4,
941
1489
  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]);
1490
+ includeNativeSol = true,
1491
+ staleTimeMs = 0,
1492
+ cacheTimeMs = 300 * 1e3,
1493
+ // 5 minutes
1494
+ refetchOnMount = "stale",
1495
+ client: clientOverride
1496
+ } = options, { address, connected } = useAccount(), { type: clusterType } = useCluster(), connectorConfig = useConnectorClient()?.getConfig(), imageProxy = connectorConfig?.imageProxy, coingeckoConfig = connectorConfig?.coingecko, selectFn = React.useCallback(
1497
+ (assets) => selectTokens(assets, address ?? ""),
1498
+ [address]
1499
+ ), {
1500
+ data: selection,
1501
+ error,
1502
+ isFetching,
1503
+ updatedAt,
1504
+ refetch: sharedRefetch,
1505
+ abort
1506
+ } = useWalletAssets({
1507
+ enabled,
1508
+ staleTimeMs,
1509
+ cacheTimeMs,
1510
+ refetchOnMount,
1511
+ refetchIntervalMs: autoRefresh ? refreshInterval : false,
1512
+ client: clientOverride,
1513
+ select: selectFn
1514
+ }), lamports = selection?.lamports ?? 0n, tokenAccounts = selection?.tokenAccounts ?? [], walletAddress = selection?.address ?? "", baseTokens = React.useMemo(() => {
1515
+ let result = [];
1516
+ includeNativeSol && walletAddress && (includeZeroBalance || lamports > 0n) && result.push({
1517
+ mint: NATIVE_SOL_MINT,
1518
+ tokenAccount: walletAddress,
1519
+ amount: lamports,
1520
+ decimals: 9,
1521
+ formatted: formatBalance(lamports, 9),
1522
+ isFrozen: false,
1523
+ owner: walletAddress
1524
+ });
1525
+ for (let account of tokenAccounts)
1526
+ !includeZeroBalance && account.amount === 0n || result.push({
1527
+ mint: account.mint,
1528
+ tokenAccount: account.pubkey,
1529
+ amount: account.amount,
1530
+ decimals: account.decimals,
1531
+ formatted: formatBalance(account.amount, account.decimals),
1532
+ isFrozen: account.isFrozen,
1533
+ owner: account.owner,
1534
+ programId: account.programId
1535
+ });
1536
+ return result;
1537
+ }, [lamports, tokenAccounts, walletAddress, includeNativeSol, includeZeroBalance]), mints = React.useMemo(() => {
1538
+ let unique = /* @__PURE__ */ new Set();
1539
+ for (let token of baseTokens)
1540
+ unique.add(token.mint);
1541
+ return [...unique].sort();
1542
+ }, [baseTokens]), mintsKey = React.useMemo(() => mints.join(","), [mints]), [metadataVersion, setMetadataVersion] = React.useState(0);
1010
1543
  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()), []);
1544
+ if (!fetchMetadata || !mintsKey) return;
1545
+ let isMounted = true;
1546
+ return (async () => {
1547
+ try {
1548
+ let mintList = mintsKey.split(",");
1549
+ await fetchTokenMetadataHybrid(mintList, coingeckoConfig, {
1550
+ onUpdate: () => {
1551
+ isMounted && setMetadataVersion((v) => v + 1);
1552
+ },
1553
+ cluster: clusterType ?? void 0
1554
+ }), isMounted && setMetadataVersion((v) => v + 1);
1555
+ } catch (err) {
1556
+ console.error("[useTokens] Failed to fetch metadata:", err);
1557
+ }
1558
+ })(), () => {
1559
+ isMounted = false;
1560
+ };
1561
+ }, [mintsKey, fetchMetadata, coingeckoConfig, clusterType]);
1562
+ let tokens = React.useMemo(() => fetchMetadata ? baseTokens.map((token) => {
1563
+ let meta = metadataCache.get(token.mint);
1564
+ if (!meta) return token;
1565
+ let usdPrice = (meta.coingeckoId ? priceCache.get(meta.coingeckoId) : void 0)?.price ?? meta.usdPrice;
1566
+ return {
1567
+ ...token,
1568
+ name: meta.name,
1569
+ symbol: meta.symbol,
1570
+ logo: transformImageUrl(meta.logoURI, imageProxy),
1571
+ usdPrice,
1572
+ formattedUsd: usdPrice ? formatUsd(token.amount, token.decimals, usdPrice) : void 0
1573
+ };
1574
+ }).sort(sortByValueDesc) : baseTokens.slice().sort(sortByValueDesc), [baseTokens, fetchMetadata, imageProxy, metadataVersion]), totalAccounts = tokenAccounts.length + (includeNativeSol ? 1 : 0);
1575
+ React.useEffect(() => (startCacheCleanup(), () => stopCacheCleanup()), []);
1017
1576
  let wasConnectedRef = React.useRef(connected);
1018
- return React.useEffect(() => {
1577
+ React.useEffect(() => {
1019
1578
  wasConnectedRef.current && !connected && clearTokenCaches(), wasConnectedRef.current = connected;
1020
- }, [connected]), React.useMemo(
1579
+ }, [connected]);
1580
+ let visibleError = updatedAt ? null : error, refetch = React.useCallback(
1581
+ async (opts) => {
1582
+ await sharedRefetch(opts);
1583
+ },
1584
+ [sharedRefetch]
1585
+ );
1586
+ return React.useMemo(
1021
1587
  () => ({
1022
1588
  tokens,
1023
- isLoading,
1024
- error,
1025
- refetch: fetchTokens,
1026
- lastUpdated,
1589
+ isLoading: isFetching,
1590
+ error: visibleError,
1591
+ refetch,
1592
+ abort,
1593
+ lastUpdated: updatedAt ? new Date(updatedAt) : null,
1027
1594
  totalAccounts
1028
1595
  }),
1029
- [tokens, isLoading, error, fetchTokens, lastUpdated, totalAccounts]
1596
+ [tokens, isFetching, visibleError, refetch, abort, updatedAt, totalAccounts]
1030
1597
  );
1031
1598
  }
1032
1599
  function DisconnectElement({
@@ -1663,6 +2230,43 @@ function WalletListElement({
1663
2230
  );
1664
2231
  }
1665
2232
  WalletListElement.displayName = "WalletListElement";
2233
+ var shineStyles = `
2234
+ .ck-skeleton {
2235
+ position: relative;
2236
+ overflow: hidden;
2237
+ }
2238
+ .ck-skeleton-shine {
2239
+ position: absolute;
2240
+ top: 0;
2241
+ left: 0;
2242
+ width: 500%;
2243
+ height: 500%;
2244
+ background: linear-gradient(135deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.1) 50%, rgba(255,255,255,0) 100%);
2245
+ animation: ck-skeleton-slide 0.5s infinite;
2246
+ z-index: 1;
2247
+ pointer-events: none;
2248
+ }
2249
+ @keyframes ck-skeleton-slide {
2250
+ 0% { transform: translate(-100%, -100%); }
2251
+ 100% { transform: translate(100%, 100%); }
2252
+ }
2253
+ `, stylesInjected = false;
2254
+ function injectStyles() {
2255
+ if (!stylesInjected && typeof document < "u") {
2256
+ let styleId = "ck-skeleton-shine-styles";
2257
+ if (!document.getElementById(styleId)) {
2258
+ let style = document.createElement("style");
2259
+ style.id = styleId, style.textContent = shineStyles, document.head.appendChild(style);
2260
+ }
2261
+ stylesInjected = true;
2262
+ }
2263
+ }
2264
+ function SkeletonShine() {
2265
+ return React__default.default.useEffect(() => {
2266
+ injectStyles();
2267
+ }, []), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton-shine", "data-slot": "skeleton-shine" });
2268
+ }
2269
+ SkeletonShine.displayName = "SkeletonShine";
1666
2270
  function BalanceElement({
1667
2271
  showSol = true,
1668
2272
  showTokens = false,
@@ -1705,10 +2309,10 @@ function BalanceElement({
1705
2309
  "data-variant": variant,
1706
2310
  "data-loading": "true",
1707
2311
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ck-balance-block-skeleton", "data-slot": "balance-element-skeleton", children: [
1708
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton ck-skeleton--text" }),
2312
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton ck-skeleton--text", children: /* @__PURE__ */ jsxRuntime.jsx(SkeletonShine, {}) }),
1709
2313
  showTokens && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1710
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton ck-skeleton--text ck-skeleton--short" }),
1711
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton ck-skeleton--text ck-skeleton--short" })
2314
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton ck-skeleton--text ck-skeleton--short", children: /* @__PURE__ */ jsxRuntime.jsx(SkeletonShine, {}) }),
2315
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton ck-skeleton--text ck-skeleton--short", children: /* @__PURE__ */ jsxRuntime.jsx(SkeletonShine, {}) })
1712
2316
  ] })
1713
2317
  ] })
1714
2318
  }
@@ -1870,6 +2474,8 @@ function BalanceElement({
1870
2474
  BalanceElement.displayName = "BalanceElement";
1871
2475
  function TransactionHistoryElement({
1872
2476
  limit = 5,
2477
+ fetchDetails = true,
2478
+ detailsConcurrency,
1873
2479
  showStatus = true,
1874
2480
  showTime = true,
1875
2481
  className,
@@ -1879,7 +2485,11 @@ function TransactionHistoryElement({
1879
2485
  render,
1880
2486
  renderItem
1881
2487
  }) {
1882
- let { transactions, isLoading, error, hasMore, loadMore, refetch } = useTransactions({ limit });
2488
+ let { transactions, isLoading, error, hasMore, loadMore, refetch } = useTransactions({
2489
+ limit,
2490
+ fetchDetails,
2491
+ detailsConcurrency
2492
+ });
1883
2493
  if (render)
1884
2494
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: render({ transactions, isLoading, error, hasMore, loadMore, refetch }) });
1885
2495
  let statusIcon = (status) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -1929,7 +2539,7 @@ function TransactionHistoryElement({
1929
2539
  "data-slot": "tx-history-element",
1930
2540
  "data-variant": variant,
1931
2541
  "data-loading": "true",
1932
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-tx-history-skeleton", "data-slot": "tx-history-skeleton", children: Array.from({ length: Math.min(limit, 3) }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton ck-skeleton--tx" }, i)) })
2542
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-tx-history-skeleton", "data-slot": "tx-history-skeleton", children: Array.from({ length: Math.min(limit, 3) }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton ck-skeleton--tx", children: /* @__PURE__ */ jsxRuntime.jsx(SkeletonShine, {}) }, i)) })
1933
2543
  }
1934
2544
  );
1935
2545
  if (error)
@@ -2093,7 +2703,7 @@ function TokenListElement({
2093
2703
  "data-slot": "token-list-element",
2094
2704
  "data-variant": variant,
2095
2705
  "data-loading": "true",
2096
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-token-list-skeleton", "data-slot": "token-list-skeleton", children: Array.from({ length: Math.min(limit, 3) }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton ck-skeleton--token" }, i)) })
2706
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-token-list-skeleton", "data-slot": "token-list-skeleton", children: Array.from({ length: Math.min(limit, 3) }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ck-skeleton ck-skeleton--token", children: /* @__PURE__ */ jsxRuntime.jsx(SkeletonShine, {}) }, i)) })
2097
2707
  }
2098
2708
  );
2099
2709
  if (error)
@@ -2187,14 +2797,23 @@ function TokenListElement({
2187
2797
  TokenListElement.displayName = "TokenListElement";
2188
2798
 
2189
2799
  exports.AccountElement = AccountElement;
2800
+ exports.AppProvider = AppProvider;
2190
2801
  exports.BalanceElement = BalanceElement;
2191
2802
  exports.ClusterElement = ClusterElement;
2192
2803
  exports.ConnectorProvider = ConnectorProvider;
2193
2804
  exports.DisconnectElement = DisconnectElement;
2805
+ exports.SkeletonShine = SkeletonShine;
2194
2806
  exports.TokenListElement = TokenListElement;
2195
2807
  exports.TransactionHistoryElement = TransactionHistoryElement;
2196
2808
  exports.UnifiedProvider = UnifiedProvider;
2197
2809
  exports.WalletListElement = WalletListElement;
2810
+ exports.clearSharedQueryCache = clearSharedQueryCache;
2811
+ exports.clearTokenCaches = clearTokenCaches;
2812
+ exports.getBalanceQueryKey = getBalanceQueryKey;
2813
+ exports.getTokensQueryKey = getTokensQueryKey;
2814
+ exports.getTransactionsQueryKey = getTransactionsQueryKey;
2815
+ exports.getWalletAssetsQueryKey = getWalletAssetsQueryKey;
2816
+ exports.invalidateSharedQuery = invalidateSharedQuery;
2198
2817
  exports.useAccount = useAccount;
2199
2818
  exports.useBalance = useBalance;
2200
2819
  exports.useCluster = useCluster;
@@ -2209,5 +2828,5 @@ exports.useTransactionPreparer = useTransactionPreparer;
2209
2828
  exports.useTransactionSigner = useTransactionSigner;
2210
2829
  exports.useTransactions = useTransactions;
2211
2830
  exports.useWalletInfo = useWalletInfo;
2212
- //# sourceMappingURL=chunk-ULUYX23Q.js.map
2213
- //# sourceMappingURL=chunk-ULUYX23Q.js.map
2831
+ //# sourceMappingURL=chunk-K3BNIGPX.js.map
2832
+ //# sourceMappingURL=chunk-K3BNIGPX.js.map