@solana/connector 0.1.4 → 0.1.6

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 (42) hide show
  1. package/README.md +301 -0
  2. package/dist/{chunk-U64YZRJL.mjs → chunk-3STZXVXD.mjs} +254 -53
  3. package/dist/chunk-3STZXVXD.mjs.map +1 -0
  4. package/dist/{chunk-RIQH5W7D.js → chunk-I64FD2EH.js} +4 -3
  5. package/dist/chunk-I64FD2EH.js.map +1 -0
  6. package/dist/{chunk-CLXM6UEE.js → chunk-JUZVCBAI.js} +91 -85
  7. package/dist/chunk-JUZVCBAI.js.map +1 -0
  8. package/dist/{chunk-D6PZY5G6.js → chunk-NQXK7PGX.js} +30 -26
  9. package/dist/chunk-NQXK7PGX.js.map +1 -0
  10. package/dist/{chunk-N3Q2J2FG.mjs → chunk-QKVL45F6.mjs} +10 -6
  11. package/dist/chunk-QKVL45F6.mjs.map +1 -0
  12. package/dist/{chunk-P5MWBDFG.mjs → chunk-QL3IT3TS.mjs} +4 -3
  13. package/dist/chunk-QL3IT3TS.mjs.map +1 -0
  14. package/dist/{chunk-LUZWUZ5N.js → chunk-ULUYX23Q.js} +268 -67
  15. package/dist/chunk-ULUYX23Q.js.map +1 -0
  16. package/dist/{chunk-YTCSTE3Q.mjs → chunk-VMSZJPR5.mjs} +10 -4
  17. package/dist/chunk-VMSZJPR5.mjs.map +1 -0
  18. package/dist/compat.js +3 -3
  19. package/dist/compat.mjs +1 -1
  20. package/dist/headless.d.mts +2 -2
  21. package/dist/headless.d.ts +2 -2
  22. package/dist/headless.js +120 -120
  23. package/dist/headless.mjs +3 -3
  24. package/dist/index.d.mts +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js +147 -147
  27. package/dist/index.mjs +4 -4
  28. package/dist/react.d.mts +4 -4
  29. package/dist/react.d.ts +4 -4
  30. package/dist/react.js +28 -28
  31. package/dist/react.mjs +2 -2
  32. package/dist/{wallet-standard-shim-DC_Z7DS-.d.ts → wallet-standard-shim--YcrQNRt.d.ts} +83 -0
  33. package/dist/{wallet-standard-shim-Cp4vF4oo.d.mts → wallet-standard-shim-Dx7H8Ctf.d.mts} +83 -0
  34. package/package.json +1 -1
  35. package/dist/chunk-CLXM6UEE.js.map +0 -1
  36. package/dist/chunk-D6PZY5G6.js.map +0 -1
  37. package/dist/chunk-LUZWUZ5N.js.map +0 -1
  38. package/dist/chunk-N3Q2J2FG.mjs.map +0 -1
  39. package/dist/chunk-P5MWBDFG.mjs.map +0 -1
  40. package/dist/chunk-RIQH5W7D.js.map +0 -1
  41. package/dist/chunk-U64YZRJL.mjs.map +0 -1
  42. package/dist/chunk-YTCSTE3Q.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkCLXM6UEE_js = require('./chunk-CLXM6UEE.js');
4
- var chunkRIQH5W7D_js = require('./chunk-RIQH5W7D.js');
3
+ var chunkJUZVCBAI_js = require('./chunk-JUZVCBAI.js');
4
+ var chunkI64FD2EH_js = require('./chunk-I64FD2EH.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 = chunkRIQH5W7D_js.createLogger("ConnectorProvider");
15
- chunkCLXM6UEE_js.installPolyfills();
14
+ var logger = chunkI64FD2EH_js.createLogger("ConnectorProvider");
15
+ chunkJUZVCBAI_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 chunkCLXM6UEE_js.ConnectorClient(config), typeof window < "u" && (window.__connectorClient = clientRef.current), config?.debug && logger.info("Client initialized successfully");
26
+ clientRef.current = new chunkJUZVCBAI_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 });
@@ -83,7 +83,7 @@ function ConnectorProvider({
83
83
  }) {
84
84
  let errorBoundaryConfig = config?.errorBoundary;
85
85
  return errorBoundaryConfig?.enabled ? /* @__PURE__ */ jsxRuntime.jsx(
86
- chunkCLXM6UEE_js.ConnectorErrorBoundary,
86
+ chunkJUZVCBAI_js.ConnectorErrorBoundary,
87
87
  {
88
88
  maxRetries: errorBoundaryConfig.maxRetries ?? 3,
89
89
  onError: errorBoundaryConfig.onError,
@@ -140,7 +140,7 @@ function useCluster() {
140
140
  [client]
141
141
  );
142
142
  return React.useMemo(() => {
143
- let isMainnet = cluster ? chunkCLXM6UEE_js.isMainnetCluster(cluster) : false, isDevnet = cluster ? chunkCLXM6UEE_js.isDevnetCluster(cluster) : false, isTestnet = cluster ? chunkCLXM6UEE_js.isTestnetCluster(cluster) : false, isLocal = cluster ? chunkCLXM6UEE_js.isLocalCluster(cluster) : false, explorerUrl = cluster ? chunkCLXM6UEE_js.getClusterExplorerUrl(cluster) : "", type = cluster ? chunkCLXM6UEE_js.getClusterType(cluster) : null;
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
144
  return {
145
145
  cluster,
146
146
  clusters,
@@ -158,7 +158,7 @@ function useAccount() {
158
158
  let { selectedAccount, accounts, connected, selectAccount } = useConnector(), [copied, setCopied] = React.useState(false), copyTimeoutRef = React__default.default.useRef(void 0), account = React.useMemo(
159
159
  () => accounts.find((a) => a.address === selectedAccount) ?? null,
160
160
  [accounts, selectedAccount]
161
- ), formatted = React.useMemo(() => selectedAccount ? chunkCLXM6UEE_js.formatAddress(selectedAccount) : "", [selectedAccount]), copy = React.useCallback(async () => selectedAccount ? (copyTimeoutRef.current && clearTimeout(copyTimeoutRef.current), await chunkCLXM6UEE_js.copyAddressToClipboard(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
162
  onSuccess: () => {
163
163
  setCopied(true), copyTimeoutRef.current = setTimeout(() => setCopied(false), 2e3);
164
164
  }
@@ -224,7 +224,7 @@ function useTransactionSigner() {
224
224
  let { selectedWallet, selectedAccount, accounts, cluster, connected } = useConnector(), client = useConnectorClient(), account = React.useMemo(
225
225
  () => accounts.find((a) => a.address === selectedAccount)?.raw ?? null,
226
226
  [accounts, selectedAccount]
227
- ), signer = React.useMemo(() => !connected || !selectedWallet || !account ? null : chunkCLXM6UEE_js.createTransactionSigner({
227
+ ), signer = React.useMemo(() => !connected || !selectedWallet || !account ? null : chunkJUZVCBAI_js.createTransactionSigner({
228
228
  wallet: selectedWallet,
229
229
  account,
230
230
  cluster: cluster ?? void 0,
@@ -252,20 +252,20 @@ function useTransactionSigner() {
252
252
  function useKitTransactionSigner() {
253
253
  let { signer: connectorSigner, ready } = useTransactionSigner();
254
254
  return {
255
- signer: React.useMemo(() => connectorSigner ? chunkCLXM6UEE_js.createKitTransactionSigner(connectorSigner) : null, [connectorSigner]),
255
+ signer: React.useMemo(() => connectorSigner ? chunkJUZVCBAI_js.createKitTransactionSigner(connectorSigner) : null, [connectorSigner]),
256
256
  ready
257
257
  };
258
258
  }
259
259
  var useGillTransactionSigner = useKitTransactionSigner;
260
- var logger2 = chunkRIQH5W7D_js.createLogger("useSolanaClient");
260
+ var logger2 = chunkI64FD2EH_js.createLogger("useSolanaClient");
261
261
  function useSolanaClient() {
262
262
  let { type } = useCluster(), connectorClient = useConnectorClient(), client = React.useMemo(() => {
263
263
  if (!type || !connectorClient) return null;
264
264
  try {
265
265
  let rpcUrl = connectorClient.getRpcUrl();
266
- return rpcUrl ? chunkRIQH5W7D_js.createSolanaClient({
266
+ return rpcUrl ? chunkI64FD2EH_js.createSolanaClient({
267
267
  urlOrMoniker: rpcUrl
268
- }) : type !== "custom" ? chunkRIQH5W7D_js.createSolanaClient({
268
+ }) : type !== "custom" ? chunkI64FD2EH_js.createSolanaClient({
269
269
  urlOrMoniker: type
270
270
  }) : null;
271
271
  } catch (error) {
@@ -286,8 +286,8 @@ function useTransactionPreparer() {
286
286
  let { client, ready } = useSolanaClient(), prepare = React.useCallback(
287
287
  async (transaction, options = {}) => {
288
288
  if (!client)
289
- throw new chunkCLXM6UEE_js.NetworkError("RPC_ERROR", "Solana client not available. Cannot prepare transaction.");
290
- return chunkRIQH5W7D_js.prepareTransaction({
289
+ throw new chunkJUZVCBAI_js.NetworkError("RPC_ERROR", "Solana client not available. Cannot prepare transaction.");
290
+ return chunkI64FD2EH_js.prepareTransaction({
291
291
  transaction,
292
292
  rpc: client.rpc,
293
293
  computeUnitLimitMultiplier: options.computeUnitLimitMultiplier,
@@ -313,9 +313,9 @@ function formatSol(lamports, decimals = 4) {
313
313
  }) + " SOL";
314
314
  }
315
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), rpcClient = client?.client ?? null, fetchBalance = React.useCallback(async () => {
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
317
  if (!connected || !address || !rpcClient) {
318
- setLamports(0n), setTokens([]);
318
+ setLamports(0n), setTokens([]), hasDataRef.current = false;
319
319
  return;
320
320
  }
321
321
  setIsLoading(true), setError(null);
@@ -343,9 +343,9 @@ function useBalance() {
343
343
  } catch (tokenError) {
344
344
  console.warn("Failed to fetch token balances:", tokenError), setTokens([]);
345
345
  }
346
- setLastUpdated(/* @__PURE__ */ new Date());
346
+ hasDataRef.current = true, setLastUpdated(/* @__PURE__ */ new Date());
347
347
  } catch (err) {
348
- setError(err), console.error("Failed to fetch balance:", err);
348
+ hasDataRef.current || (setError(err), console.error("Failed to fetch balance:", err));
349
349
  } finally {
350
350
  setIsLoading(false);
351
351
  }
@@ -496,6 +496,10 @@ function formatAmount(tokenAmount, tokenDecimals, direction, solChange) {
496
496
  return `${solChange > 0 ? "+" : ""}${solChange.toFixed(4)} SOL`;
497
497
  }
498
498
  var tokenMetadataCache = /* @__PURE__ */ new Map();
499
+ function transformImageUrl(url, imageProxy) {
500
+ if (url)
501
+ return imageProxy ? `${imageProxy}${encodeURIComponent(url)}` : url;
502
+ }
499
503
  async function fetchTokenMetadata(mints) {
500
504
  let results = /* @__PURE__ */ new Map();
501
505
  if (mints.length === 0) return results;
@@ -506,16 +510,17 @@ async function fetchTokenMetadata(mints) {
506
510
  }
507
511
  if (uncachedMints.length === 0) return results;
508
512
  try {
509
- let url = new URL("https://lite-api.jup.ag/tokens/v2/search");
510
- url.searchParams.append("query", uncachedMints.join(","));
511
- let response = await fetch(url.toString(), {
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 }),
512
517
  signal: AbortSignal.timeout(5e3)
513
518
  });
514
519
  if (!response.ok) return results;
515
- let items = await response.json();
516
- for (let item of items) {
517
- let metadata = { symbol: item.symbol, icon: item.icon };
518
- results.set(item.id, metadata), tokenMetadataCache.set(item.id, metadata);
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);
519
524
  }
520
525
  } catch (error) {
521
526
  console.warn("[useTransactions] Failed to fetch token metadata:", error);
@@ -523,16 +528,19 @@ async function fetchTokenMetadata(mints) {
523
528
  return results;
524
529
  }
525
530
  function useTransactions(options = {}) {
526
- let { limit = 10, autoRefresh = false, refreshInterval = 6e4, fetchDetails = true } = options, { address, connected } = useAccount(), { cluster } = useCluster(), client = useSolanaClient(), [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(
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(
527
532
  null
528
- ), rpcClient = client?.client ?? null, parseTransaction = React.useCallback(
533
+ ), rpcClient = client?.client ?? null, imageProxy = connectorClient?.getConfig().imageProxy, parseTransaction = React.useCallback(
529
534
  (tx, walletAddress, sig, blockTime, slot, err, explorerUrl) => {
530
535
  let { date, time } = formatDate(blockTime), baseInfo = {
531
536
  signature: sig,
532
537
  blockTime,
533
538
  slot,
534
539
  status: err ? "failed" : "success",
535
- error: err ? JSON.stringify(err) : void 0,
540
+ error: err ? JSON.stringify(
541
+ err,
542
+ (_key, value) => typeof value == "bigint" ? value.toString() : value
543
+ ) : void 0,
536
544
  type: "unknown",
537
545
  formattedDate: date,
538
546
  formattedTime: time,
@@ -608,7 +616,7 @@ function useTransactions(options = {}) {
608
616
  blockTimeNum,
609
617
  Number(sig.slot),
610
618
  sig.err,
611
- chunkCLXM6UEE_js.getTransactionUrl(String(sig.signature), cluster)
619
+ chunkJUZVCBAI_js.getTransactionUrl(String(sig.signature), cluster)
612
620
  );
613
621
  });
614
622
  } else
@@ -623,7 +631,7 @@ function useTransactions(options = {}) {
623
631
  type: "unknown",
624
632
  formattedDate: date,
625
633
  formattedTime: time,
626
- explorerUrl: chunkCLXM6UEE_js.getTransactionUrl(String(sig.signature), cluster)
634
+ explorerUrl: chunkJUZVCBAI_js.getTransactionUrl(String(sig.signature), cluster)
627
635
  };
628
636
  });
629
637
  setTransactions(loadMore ? (prev) => [...prev, ...newTransactions] : newTransactions);
@@ -637,7 +645,7 @@ function useTransactions(options = {}) {
637
645
  return {
638
646
  ...tx,
639
647
  tokenSymbol: meta.symbol,
640
- tokenIcon: meta.icon,
648
+ tokenIcon: transformImageUrl(meta.icon, imageProxy),
641
649
  // Update formatted amount with symbol
642
650
  formattedAmount: tx.formattedAmount ? `${tx.formattedAmount} ${meta.symbol}` : tx.formattedAmount
643
651
  };
@@ -661,7 +669,7 @@ function useTransactions(options = {}) {
661
669
  setIsLoading(false);
662
670
  }
663
671
  },
664
- [connected, address, rpcClient, cluster, limit, fetchDetails, parseTransaction]
672
+ [connected, address, rpcClient, cluster, limit, fetchDetails, parseTransaction, imageProxy]
665
673
  ), refetch = React.useCallback(async () => {
666
674
  beforeSignatureRef.current = void 0, await fetchTransactions(false);
667
675
  }, [fetchTransactions]), loadMoreFn = React.useCallback(async () => {
@@ -687,38 +695,221 @@ function useTransactions(options = {}) {
687
695
  [transactions, isLoading, error, hasMore, loadMoreFn, refetch, lastUpdated]
688
696
  );
689
697
  }
690
- var NATIVE_MINT = "So11111111111111111111111111111111111111112", metadataCache = /* @__PURE__ */ new Map();
691
- async function fetchJupiterMetadata(mints) {
698
+ function createTimeoutSignal(ms) {
699
+ if (typeof AbortSignal.timeout == "function")
700
+ return { signal: AbortSignal.timeout(ms), cleanup: () => {
701
+ } };
702
+ let controller = new AbortController(), timeoutId = setTimeout(() => controller.abort(), ms);
703
+ return {
704
+ signal: controller.signal,
705
+ cleanup: () => clearTimeout(timeoutId)
706
+ };
707
+ }
708
+ var NATIVE_MINT = "So11111111111111111111111111111111111111112", CACHE_MAX_SIZE = 500, PRICE_CACHE_TTL = 6e4, STALE_CLEANUP_INTERVAL = 12e4, COINGECKO_DEFAULT_MAX_RETRIES = 3, COINGECKO_DEFAULT_BASE_DELAY = 1e3, COINGECKO_DEFAULT_MAX_TIMEOUT = 3e4, COINGECKO_API_BASE_URL = "https://api.coingecko.com/api/v3", LRUCache = class {
709
+ 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");
714
+ this.maxSize = maxSize, this.getTtl = options?.getTtl, this.getTimestamp = options?.getTimestamp;
715
+ }
716
+ get(key) {
717
+ let value = this.cache.get(key);
718
+ if (value !== void 0) {
719
+ if (this.getTtl && this.getTimestamp) {
720
+ let ttl = this.getTtl(value), timestamp = this.getTimestamp(value);
721
+ if (ttl !== void 0 && timestamp !== void 0 && Date.now() - timestamp >= ttl) {
722
+ this.cache.delete(key);
723
+ return;
724
+ }
725
+ }
726
+ return this.cache.delete(key), this.cache.set(key, value), value;
727
+ }
728
+ }
729
+ set(key, value) {
730
+ if (this.cache.has(key) && this.cache.delete(key), this.cache.size >= this.maxSize) {
731
+ let oldestKey = this.cache.keys().next().value;
732
+ oldestKey !== void 0 && this.cache.delete(oldestKey);
733
+ }
734
+ this.cache.set(key, value);
735
+ }
736
+ has(key) {
737
+ return this.cache.has(key);
738
+ }
739
+ delete(key) {
740
+ return this.cache.delete(key);
741
+ }
742
+ clear() {
743
+ this.cache.clear();
744
+ }
745
+ get size() {
746
+ return this.cache.size;
747
+ }
748
+ /**
749
+ * Prune stale entries based on TTL.
750
+ * Only works if getTtl and getTimestamp are provided.
751
+ */
752
+ pruneStale() {
753
+ if (!this.getTtl || !this.getTimestamp) return 0;
754
+ let now = Date.now(), pruned = 0;
755
+ for (let [key, value] of this.cache) {
756
+ let ttl = this.getTtl(value), timestamp = this.getTimestamp(value);
757
+ ttl !== void 0 && timestamp !== void 0 && now - timestamp >= ttl && (this.cache.delete(key), pruned++);
758
+ }
759
+ return pruned;
760
+ }
761
+ }, metadataCache = new LRUCache(CACHE_MAX_SIZE), priceCache = new LRUCache(CACHE_MAX_SIZE, {
762
+ getTtl: () => PRICE_CACHE_TTL,
763
+ getTimestamp: (entry) => entry.timestamp
764
+ }), cleanupIntervalId = null, cleanupRefCount = 0;
765
+ function startCacheCleanup() {
766
+ cleanupRefCount++, cleanupIntervalId === null && (cleanupIntervalId = setInterval(() => {
767
+ priceCache.pruneStale();
768
+ }, STALE_CLEANUP_INTERVAL));
769
+ }
770
+ function stopCacheCleanup() {
771
+ cleanupRefCount = Math.max(0, cleanupRefCount - 1), cleanupRefCount === 0 && cleanupIntervalId !== null && (clearInterval(cleanupIntervalId), cleanupIntervalId = null);
772
+ }
773
+ function clearTokenCaches() {
774
+ metadataCache.clear(), priceCache.clear();
775
+ }
776
+ async function fetchSolanaTokenMetadata(mints) {
692
777
  let results = /* @__PURE__ */ new Map();
693
778
  if (mints.length === 0) return results;
694
- let uncachedMints = [];
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
+ function calculateBackoffDelay(attempt, baseDelay, retryAfter) {
798
+ if (retryAfter !== void 0 && retryAfter > 0) {
799
+ let jitter2 = Math.random() * 500;
800
+ return retryAfter * 1e3 + jitter2;
801
+ }
802
+ let exponentialDelay = baseDelay * Math.pow(2, attempt), jitter = Math.random() * 500;
803
+ return exponentialDelay + jitter;
804
+ }
805
+ function parseRetryAfter(retryAfterHeader) {
806
+ if (!retryAfterHeader) return;
807
+ let seconds = parseInt(retryAfterHeader, 10);
808
+ if (!isNaN(seconds) && seconds >= 0)
809
+ return seconds;
810
+ let date = Date.parse(retryAfterHeader);
811
+ if (!isNaN(date)) {
812
+ let waitMs = date - Date.now();
813
+ return waitMs > 0 ? Math.ceil(waitMs / 1e3) : 0;
814
+ }
815
+ }
816
+ async function fetchCoinGeckoPrices(coingeckoIds, config) {
817
+ let results = /* @__PURE__ */ new Map();
818
+ if (coingeckoIds.length === 0) return results;
819
+ let now = Date.now(), uncachedIds = [];
820
+ for (let id of coingeckoIds) {
821
+ let cached = priceCache.get(id);
822
+ cached && now - cached.timestamp < PRICE_CACHE_TTL ? results.set(id, cached.price) : uncachedIds.push(id);
823
+ }
824
+ if (uncachedIds.length === 0) return results;
825
+ let maxRetries = config?.maxRetries ?? COINGECKO_DEFAULT_MAX_RETRIES, baseDelay = config?.baseDelay ?? COINGECKO_DEFAULT_BASE_DELAY, maxTimeout = config?.maxTimeout ?? COINGECKO_DEFAULT_MAX_TIMEOUT, apiKey = config?.apiKey, isPro = config?.isPro ?? false, url = `${COINGECKO_API_BASE_URL}/simple/price?ids=${uncachedIds.join(",")}&vs_currencies=usd`, headers = {};
826
+ apiKey && (headers[isPro ? "x-cg-pro-api-key" : "x-cg-demo-api-key"] = apiKey);
827
+ let startTime = Date.now(), attempt = 0, lastError = null;
828
+ for (; attempt <= maxRetries; ) {
829
+ let elapsedTime = Date.now() - startTime;
830
+ if (elapsedTime >= maxTimeout) {
831
+ console.warn(
832
+ `[useTokens] CoinGecko API: Total timeout (${maxTimeout}ms) exceeded after ${attempt} attempts. Returning cached/partial results.`
833
+ );
834
+ break;
835
+ }
836
+ let remainingTimeout = maxTimeout - elapsedTime, requestTimeout = Math.min(1e4, remainingTimeout), { signal, cleanup } = createTimeoutSignal(requestTimeout);
837
+ try {
838
+ let response = await fetch(url, {
839
+ headers,
840
+ signal
841
+ });
842
+ if (cleanup(), response.status === 429) {
843
+ 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
+ );
850
+ break;
851
+ }
852
+ await new Promise((resolve) => setTimeout(resolve, delay)), attempt++;
853
+ continue;
854
+ }
855
+ if (!response.ok)
856
+ throw new Error(`CoinGecko API error: ${response.status} ${response.statusText}`);
857
+ let data = await response.json(), fetchTime = Date.now();
858
+ for (let [id, priceData] of Object.entries(data))
859
+ priceData?.usd !== void 0 && (results.set(id, priceData.usd), priceCache.set(id, { price: priceData.usd, timestamp: fetchTime }));
860
+ return results;
861
+ } 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) {
868
+ let delay = calculateBackoffDelay(attempt, baseDelay);
869
+ Date.now() - startTime + delay < maxTimeout && await new Promise((resolve) => setTimeout(resolve, delay));
870
+ }
871
+ attempt++;
872
+ }
873
+ }
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;
877
+ }
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();
695
882
  for (let mint of mints) {
696
883
  let cached = metadataCache.get(mint);
697
- cached ? results.set(mint, cached) : uncachedMints.push(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);
698
885
  }
699
886
  if (uncachedMints.length === 0) return results;
700
- try {
701
- let url = new URL("https://lite-api.jup.ag/tokens/v2/search");
702
- url.searchParams.append("query", uncachedMints.join(","));
703
- let response = await fetch(url.toString(), {
704
- signal: AbortSignal.timeout(1e4)
705
- });
706
- if (!response.ok)
707
- throw new Error(`Jupiter API error: ${response.status}`);
708
- let items = await response.json();
709
- for (let item of items) {
710
- let metadata = {
711
- id: item.id,
712
- name: item.id === NATIVE_MINT ? "Solana" : item.name,
713
- symbol: item.symbol,
714
- decimals: item.decimals,
715
- icon: item.icon,
716
- usdPrice: item.usdPrice
717
- };
718
- results.set(item.id, metadata), metadataCache.set(item.id, metadata);
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);
890
+ for (let mint of mints) {
891
+ let cached = metadataCache.get(mint);
892
+ cached?.coingeckoId && !coingeckoIdToMint.has(cached.coingeckoId) && coingeckoIdToMint.set(cached.coingeckoId, mint);
893
+ }
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 = {
897
+ address: meta.address,
898
+ name: meta.address === NATIVE_MINT ? "Solana" : meta.name,
899
+ symbol: meta.symbol,
900
+ decimals: meta.decimals,
901
+ logoURI: meta.logoURI,
902
+ coingeckoId,
903
+ usdPrice
904
+ };
905
+ results.set(mint, combined), metadataCache.set(mint, combined);
906
+ }
907
+ for (let [coingeckoId, mint] of coingeckoIdToMint) {
908
+ let cached = results.get(mint) ?? metadataCache.get(mint);
909
+ if (cached) {
910
+ let usdPrice = prices.get(coingeckoId);
911
+ usdPrice !== void 0 && (cached.usdPrice = usdPrice, results.set(mint, cached), metadataCache.set(mint, cached));
719
912
  }
720
- } catch (error) {
721
- console.warn("[useTokens] Jupiter API failed:", error);
722
913
  }
723
914
  return results;
724
915
  }
@@ -736,6 +927,12 @@ function formatUsd(amount, decimals, usdPrice) {
736
927
  maximumFractionDigits: 2
737
928
  });
738
929
  }
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;
935
+ }
739
936
  function useTokens(options = {}) {
740
937
  let {
741
938
  includeZeroBalance = false,
@@ -743,7 +940,7 @@ function useTokens(options = {}) {
743
940
  refreshInterval = 6e4,
744
941
  fetchMetadata = true,
745
942
  includeNativeSol = true
746
- } = options, { address, connected } = useAccount(), client = useSolanaClient(), [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, fetchTokens = React.useCallback(async () => {
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 () => {
747
944
  if (!connected || !address || !rpcClient) {
748
945
  setTokens([]), setTotalAccounts(0);
749
946
  return;
@@ -785,16 +982,16 @@ function useTokens(options = {}) {
785
982
  }
786
983
  }
787
984
  if (setTokens([...tokenList]), setTotalAccounts(tokenAccountsResult.value.length + (includeNativeSol ? 1 : 0)), setLastUpdated(/* @__PURE__ */ new Date()), fetchMetadata && mints.length > 0) {
788
- let metadata = await fetchJupiterMetadata(mints);
985
+ let metadata = await fetchTokenMetadataHybrid(mints, coingeckoConfig);
789
986
  for (let i = 0; i < tokenList.length; i++) {
790
987
  let meta = metadata.get(tokenList[i].mint);
791
988
  meta && (tokenList[i] = {
792
989
  ...tokenList[i],
793
990
  name: meta.name,
794
991
  symbol: meta.symbol,
795
- logo: meta.icon,
992
+ logo: transformImageUrl2(meta.logoURI, imageProxy),
796
993
  usdPrice: meta.usdPrice,
797
- formattedUsd: formatUsd(tokenList[i].amount, tokenList[i].decimals, meta.usdPrice)
994
+ formattedUsd: meta.usdPrice ? formatUsd(tokenList[i].amount, tokenList[i].decimals, meta.usdPrice) : void 0
798
995
  });
799
996
  }
800
997
  tokenList.sort((a, b) => {
@@ -809,14 +1006,18 @@ function useTokens(options = {}) {
809
1006
  } finally {
810
1007
  setIsLoading(false);
811
1008
  }
812
- }, [connected, address, rpcClient, includeZeroBalance, fetchMetadata, includeNativeSol]);
813
- return React.useEffect(() => {
1009
+ }, [connected, address, rpcClient, includeZeroBalance, fetchMetadata, includeNativeSol, imageProxy, coingeckoConfig]);
1010
+ React.useEffect(() => {
814
1011
  fetchTokens();
815
1012
  }, [fetchTokens]), React.useEffect(() => {
816
1013
  if (!connected || !autoRefresh) return;
817
1014
  let interval = setInterval(fetchTokens, refreshInterval);
818
1015
  return () => clearInterval(interval);
819
- }, [connected, autoRefresh, refreshInterval, fetchTokens]), React.useMemo(
1016
+ }, [connected, autoRefresh, refreshInterval, fetchTokens]), React.useEffect(() => (startCacheCleanup(), () => stopCacheCleanup()), []);
1017
+ let wasConnectedRef = React.useRef(connected);
1018
+ return React.useEffect(() => {
1019
+ wasConnectedRef.current && !connected && clearTokenCaches(), wasConnectedRef.current = connected;
1020
+ }, [connected]), React.useMemo(
820
1021
  () => ({
821
1022
  tokens,
822
1023
  isLoading,
@@ -2008,5 +2209,5 @@ exports.useTransactionPreparer = useTransactionPreparer;
2008
2209
  exports.useTransactionSigner = useTransactionSigner;
2009
2210
  exports.useTransactions = useTransactions;
2010
2211
  exports.useWalletInfo = useWalletInfo;
2011
- //# sourceMappingURL=chunk-LUZWUZ5N.js.map
2012
- //# sourceMappingURL=chunk-LUZWUZ5N.js.map
2212
+ //# sourceMappingURL=chunk-ULUYX23Q.js.map
2213
+ //# sourceMappingURL=chunk-ULUYX23Q.js.map