@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,6 +1,6 @@
1
- import { installPolyfills, ConnectorErrorBoundary, isMainnetCluster, isDevnetCluster, isTestnetCluster, isLocalCluster, getClusterExplorerUrl, getClusterType, formatAddress, copyAddressToClipboard, createTransactionSigner, createKitTransactionSigner, NetworkError, getTransactionUrl, ConnectorClient } from './chunk-YTCSTE3Q.mjs';
2
- import { createLogger, createSolanaClient, prepareTransaction } from './chunk-P5MWBDFG.mjs';
3
- import React, { createContext, useContext, useSyncExternalStore, useMemo, useState, useCallback, useEffect, useRef } from 'react';
1
+ import { installPolyfills, ConnectorErrorBoundary, isMainnetCluster, isDevnetCluster, isTestnetCluster, isLocalCluster, getClusterExplorerUrl, getClusterType, formatAddress, copyAddressToClipboard, createTransactionSigner, createKitTransactionSigner, NetworkError, getTransactionUrl, ConnectorClient } from './chunk-VMSZJPR5.mjs';
2
+ import { createLogger, __publicField, createSolanaClient, prepareTransaction } from './chunk-QL3IT3TS.mjs';
3
+ import React, { createContext, useContext, useSyncExternalStore, useMemo, useState, useCallback, useRef, useEffect } from 'react';
4
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
5
  import { address } from '@solana/addresses';
6
6
  import { signature } from '@solana/keys';
@@ -307,9 +307,9 @@ function formatSol(lamports, decimals = 4) {
307
307
  }) + " SOL";
308
308
  }
309
309
  function useBalance() {
310
- let { address: address$1, connected } = useAccount(); useCluster(); let client = useSolanaClient(), [lamports, setLamports] = useState(0n), [tokens, setTokens] = useState([]), [isLoading, setIsLoading] = useState(false), [error, setError] = useState(null), [lastUpdated, setLastUpdated] = useState(null), rpcClient = client?.client ?? null, fetchBalance = useCallback(async () => {
310
+ let { address: address$1, connected } = useAccount(); useCluster(); let client = useSolanaClient(), [lamports, setLamports] = useState(0n), [tokens, setTokens] = useState([]), [isLoading, setIsLoading] = useState(false), [error, setError] = useState(null), [lastUpdated, setLastUpdated] = useState(null), hasDataRef = useRef(false), rpcClient = client?.client ?? null, fetchBalance = useCallback(async () => {
311
311
  if (!connected || !address$1 || !rpcClient) {
312
- setLamports(0n), setTokens([]);
312
+ setLamports(0n), setTokens([]), hasDataRef.current = false;
313
313
  return;
314
314
  }
315
315
  setIsLoading(true), setError(null);
@@ -337,9 +337,9 @@ function useBalance() {
337
337
  } catch (tokenError) {
338
338
  console.warn("Failed to fetch token balances:", tokenError), setTokens([]);
339
339
  }
340
- setLastUpdated(/* @__PURE__ */ new Date());
340
+ hasDataRef.current = true, setLastUpdated(/* @__PURE__ */ new Date());
341
341
  } catch (err) {
342
- setError(err), console.error("Failed to fetch balance:", err);
342
+ hasDataRef.current || (setError(err), console.error("Failed to fetch balance:", err));
343
343
  } finally {
344
344
  setIsLoading(false);
345
345
  }
@@ -490,6 +490,10 @@ function formatAmount(tokenAmount, tokenDecimals, direction, solChange) {
490
490
  return `${solChange > 0 ? "+" : ""}${solChange.toFixed(4)} SOL`;
491
491
  }
492
492
  var tokenMetadataCache = /* @__PURE__ */ new Map();
493
+ function transformImageUrl(url, imageProxy) {
494
+ if (url)
495
+ return imageProxy ? `${imageProxy}${encodeURIComponent(url)}` : url;
496
+ }
493
497
  async function fetchTokenMetadata(mints) {
494
498
  let results = /* @__PURE__ */ new Map();
495
499
  if (mints.length === 0) return results;
@@ -500,16 +504,17 @@ async function fetchTokenMetadata(mints) {
500
504
  }
501
505
  if (uncachedMints.length === 0) return results;
502
506
  try {
503
- let url = new URL("https://lite-api.jup.ag/tokens/v2/search");
504
- url.searchParams.append("query", uncachedMints.join(","));
505
- let response = await fetch(url.toString(), {
507
+ let response = await fetch("https://token-list-api.solana.cloud/v1/mints?chainId=101", {
508
+ method: "POST",
509
+ headers: { "Content-Type": "application/json" },
510
+ body: JSON.stringify({ addresses: uncachedMints }),
506
511
  signal: AbortSignal.timeout(5e3)
507
512
  });
508
513
  if (!response.ok) return results;
509
- let items = await response.json();
510
- for (let item of items) {
511
- let metadata = { symbol: item.symbol, icon: item.icon };
512
- results.set(item.id, metadata), tokenMetadataCache.set(item.id, metadata);
514
+ let data = await response.json();
515
+ for (let item of data.content) {
516
+ let metadata = { symbol: item.symbol, icon: item.logoURI };
517
+ results.set(item.address, metadata), tokenMetadataCache.set(item.address, metadata);
513
518
  }
514
519
  } catch (error) {
515
520
  console.warn("[useTransactions] Failed to fetch token metadata:", error);
@@ -517,16 +522,19 @@ async function fetchTokenMetadata(mints) {
517
522
  return results;
518
523
  }
519
524
  function useTransactions(options = {}) {
520
- let { limit = 10, autoRefresh = false, refreshInterval = 6e4, fetchDetails = true } = options, { address: address$1, connected } = useAccount(), { cluster } = useCluster(), client = useSolanaClient(), [transactions, setTransactions] = useState([]), [isLoading, setIsLoading] = useState(false), [error, setError] = useState(null), [hasMore, setHasMore] = useState(true), [lastUpdated, setLastUpdated] = useState(null), beforeSignatureRef = useRef(void 0), prevDepsRef = useRef(
525
+ let { limit = 10, autoRefresh = false, refreshInterval = 6e4, fetchDetails = true } = options, { address: address$1, connected } = useAccount(), { cluster } = useCluster(), client = useSolanaClient(), connectorClient = useConnectorClient(), [transactions, setTransactions] = useState([]), [isLoading, setIsLoading] = useState(false), [error, setError] = useState(null), [hasMore, setHasMore] = useState(true), [lastUpdated, setLastUpdated] = useState(null), beforeSignatureRef = useRef(void 0), prevDepsRef = useRef(
521
526
  null
522
- ), rpcClient = client?.client ?? null, parseTransaction = useCallback(
527
+ ), rpcClient = client?.client ?? null, imageProxy = connectorClient?.getConfig().imageProxy, parseTransaction = useCallback(
523
528
  (tx, walletAddress, sig, blockTime, slot, err, explorerUrl) => {
524
529
  let { date, time } = formatDate(blockTime), baseInfo = {
525
530
  signature: sig,
526
531
  blockTime,
527
532
  slot,
528
533
  status: err ? "failed" : "success",
529
- error: err ? JSON.stringify(err) : void 0,
534
+ error: err ? JSON.stringify(
535
+ err,
536
+ (_key, value) => typeof value == "bigint" ? value.toString() : value
537
+ ) : void 0,
530
538
  type: "unknown",
531
539
  formattedDate: date,
532
540
  formattedTime: time,
@@ -631,7 +639,7 @@ function useTransactions(options = {}) {
631
639
  return {
632
640
  ...tx,
633
641
  tokenSymbol: meta.symbol,
634
- tokenIcon: meta.icon,
642
+ tokenIcon: transformImageUrl(meta.icon, imageProxy),
635
643
  // Update formatted amount with symbol
636
644
  formattedAmount: tx.formattedAmount ? `${tx.formattedAmount} ${meta.symbol}` : tx.formattedAmount
637
645
  };
@@ -655,7 +663,7 @@ function useTransactions(options = {}) {
655
663
  setIsLoading(false);
656
664
  }
657
665
  },
658
- [connected, address$1, rpcClient, cluster, limit, fetchDetails, parseTransaction]
666
+ [connected, address$1, rpcClient, cluster, limit, fetchDetails, parseTransaction, imageProxy]
659
667
  ), refetch = useCallback(async () => {
660
668
  beforeSignatureRef.current = void 0, await fetchTransactions(false);
661
669
  }, [fetchTransactions]), loadMoreFn = useCallback(async () => {
@@ -681,38 +689,221 @@ function useTransactions(options = {}) {
681
689
  [transactions, isLoading, error, hasMore, loadMoreFn, refetch, lastUpdated]
682
690
  );
683
691
  }
684
- var NATIVE_MINT = "So11111111111111111111111111111111111111112", metadataCache = /* @__PURE__ */ new Map();
685
- async function fetchJupiterMetadata(mints) {
692
+ function createTimeoutSignal(ms) {
693
+ if (typeof AbortSignal.timeout == "function")
694
+ return { signal: AbortSignal.timeout(ms), cleanup: () => {
695
+ } };
696
+ let controller = new AbortController(), timeoutId = setTimeout(() => controller.abort(), ms);
697
+ return {
698
+ signal: controller.signal,
699
+ cleanup: () => clearTimeout(timeoutId)
700
+ };
701
+ }
702
+ 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 {
703
+ constructor(maxSize, options) {
704
+ __publicField(this, "cache", /* @__PURE__ */ new Map());
705
+ __publicField(this, "maxSize");
706
+ __publicField(this, "getTtl");
707
+ __publicField(this, "getTimestamp");
708
+ this.maxSize = maxSize, this.getTtl = options?.getTtl, this.getTimestamp = options?.getTimestamp;
709
+ }
710
+ get(key) {
711
+ let value = this.cache.get(key);
712
+ if (value !== void 0) {
713
+ if (this.getTtl && this.getTimestamp) {
714
+ let ttl = this.getTtl(value), timestamp = this.getTimestamp(value);
715
+ if (ttl !== void 0 && timestamp !== void 0 && Date.now() - timestamp >= ttl) {
716
+ this.cache.delete(key);
717
+ return;
718
+ }
719
+ }
720
+ return this.cache.delete(key), this.cache.set(key, value), value;
721
+ }
722
+ }
723
+ set(key, value) {
724
+ if (this.cache.has(key) && this.cache.delete(key), this.cache.size >= this.maxSize) {
725
+ let oldestKey = this.cache.keys().next().value;
726
+ oldestKey !== void 0 && this.cache.delete(oldestKey);
727
+ }
728
+ this.cache.set(key, value);
729
+ }
730
+ has(key) {
731
+ return this.cache.has(key);
732
+ }
733
+ delete(key) {
734
+ return this.cache.delete(key);
735
+ }
736
+ clear() {
737
+ this.cache.clear();
738
+ }
739
+ get size() {
740
+ return this.cache.size;
741
+ }
742
+ /**
743
+ * Prune stale entries based on TTL.
744
+ * Only works if getTtl and getTimestamp are provided.
745
+ */
746
+ pruneStale() {
747
+ if (!this.getTtl || !this.getTimestamp) return 0;
748
+ let now = Date.now(), pruned = 0;
749
+ for (let [key, value] of this.cache) {
750
+ let ttl = this.getTtl(value), timestamp = this.getTimestamp(value);
751
+ ttl !== void 0 && timestamp !== void 0 && now - timestamp >= ttl && (this.cache.delete(key), pruned++);
752
+ }
753
+ return pruned;
754
+ }
755
+ }, metadataCache = new LRUCache(CACHE_MAX_SIZE), priceCache = new LRUCache(CACHE_MAX_SIZE, {
756
+ getTtl: () => PRICE_CACHE_TTL,
757
+ getTimestamp: (entry) => entry.timestamp
758
+ }), cleanupIntervalId = null, cleanupRefCount = 0;
759
+ function startCacheCleanup() {
760
+ cleanupRefCount++, cleanupIntervalId === null && (cleanupIntervalId = setInterval(() => {
761
+ priceCache.pruneStale();
762
+ }, STALE_CLEANUP_INTERVAL));
763
+ }
764
+ function stopCacheCleanup() {
765
+ cleanupRefCount = Math.max(0, cleanupRefCount - 1), cleanupRefCount === 0 && cleanupIntervalId !== null && (clearInterval(cleanupIntervalId), cleanupIntervalId = null);
766
+ }
767
+ function clearTokenCaches() {
768
+ metadataCache.clear(), priceCache.clear();
769
+ }
770
+ async function fetchSolanaTokenMetadata(mints) {
686
771
  let results = /* @__PURE__ */ new Map();
687
772
  if (mints.length === 0) return results;
688
- let uncachedMints = [];
773
+ let { signal, cleanup } = createTimeoutSignal(1e4);
774
+ try {
775
+ let response = await fetch("https://token-list-api.solana.cloud/v1/mints?chainId=101", {
776
+ method: "POST",
777
+ headers: { "Content-Type": "application/json" },
778
+ body: JSON.stringify({ addresses: mints }),
779
+ signal
780
+ });
781
+ if (cleanup(), !response.ok)
782
+ throw new Error(`Solana Token List API error: ${response.status}`);
783
+ let data = await response.json();
784
+ for (let item of data.content)
785
+ results.set(item.address, item);
786
+ } catch (error) {
787
+ cleanup(), console.warn("[useTokens] Solana Token List API failed:", error);
788
+ }
789
+ return results;
790
+ }
791
+ function calculateBackoffDelay(attempt, baseDelay, retryAfter) {
792
+ if (retryAfter !== void 0 && retryAfter > 0) {
793
+ let jitter2 = Math.random() * 500;
794
+ return retryAfter * 1e3 + jitter2;
795
+ }
796
+ let exponentialDelay = baseDelay * Math.pow(2, attempt), jitter = Math.random() * 500;
797
+ return exponentialDelay + jitter;
798
+ }
799
+ function parseRetryAfter(retryAfterHeader) {
800
+ if (!retryAfterHeader) return;
801
+ let seconds = parseInt(retryAfterHeader, 10);
802
+ if (!isNaN(seconds) && seconds >= 0)
803
+ return seconds;
804
+ let date = Date.parse(retryAfterHeader);
805
+ if (!isNaN(date)) {
806
+ let waitMs = date - Date.now();
807
+ return waitMs > 0 ? Math.ceil(waitMs / 1e3) : 0;
808
+ }
809
+ }
810
+ async function fetchCoinGeckoPrices(coingeckoIds, config) {
811
+ let results = /* @__PURE__ */ new Map();
812
+ if (coingeckoIds.length === 0) return results;
813
+ let now = Date.now(), uncachedIds = [];
814
+ for (let id of coingeckoIds) {
815
+ let cached = priceCache.get(id);
816
+ cached && now - cached.timestamp < PRICE_CACHE_TTL ? results.set(id, cached.price) : uncachedIds.push(id);
817
+ }
818
+ if (uncachedIds.length === 0) return results;
819
+ 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 = {};
820
+ apiKey && (headers[isPro ? "x-cg-pro-api-key" : "x-cg-demo-api-key"] = apiKey);
821
+ let startTime = Date.now(), attempt = 0, lastError = null;
822
+ for (; attempt <= maxRetries; ) {
823
+ let elapsedTime = Date.now() - startTime;
824
+ if (elapsedTime >= maxTimeout) {
825
+ console.warn(
826
+ `[useTokens] CoinGecko API: Total timeout (${maxTimeout}ms) exceeded after ${attempt} attempts. Returning cached/partial results.`
827
+ );
828
+ break;
829
+ }
830
+ let remainingTimeout = maxTimeout - elapsedTime, requestTimeout = Math.min(1e4, remainingTimeout), { signal, cleanup } = createTimeoutSignal(requestTimeout);
831
+ try {
832
+ let response = await fetch(url, {
833
+ headers,
834
+ signal
835
+ });
836
+ if (cleanup(), response.status === 429) {
837
+ let retryAfter = parseRetryAfter(response.headers.get("Retry-After")), delay = calculateBackoffDelay(attempt, baseDelay, retryAfter);
838
+ if (console.warn(
839
+ `[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`
840
+ ), Date.now() - startTime + delay >= maxTimeout) {
841
+ console.warn(
842
+ `[useTokens] CoinGecko API: Skipping retry - would exceed total timeout (${maxTimeout}ms). Returning cached/partial results.`
843
+ );
844
+ break;
845
+ }
846
+ await new Promise((resolve) => setTimeout(resolve, delay)), attempt++;
847
+ continue;
848
+ }
849
+ if (!response.ok)
850
+ throw new Error(`CoinGecko API error: ${response.status} ${response.statusText}`);
851
+ let data = await response.json(), fetchTime = Date.now();
852
+ for (let [id, priceData] of Object.entries(data))
853
+ priceData?.usd !== void 0 && (results.set(id, priceData.usd), priceCache.set(id, { price: priceData.usd, timestamp: fetchTime }));
854
+ return results;
855
+ } catch (error) {
856
+ if (cleanup(), lastError = error, error instanceof DOMException && error.name === "AbortError" ? console.warn(
857
+ `[useTokens] CoinGecko API request timed out. Attempt ${attempt + 1}/${maxRetries + 1}.`
858
+ ) : console.warn(
859
+ `[useTokens] CoinGecko API request failed. Attempt ${attempt + 1}/${maxRetries + 1}:`,
860
+ error
861
+ ), attempt < maxRetries) {
862
+ let delay = calculateBackoffDelay(attempt, baseDelay);
863
+ Date.now() - startTime + delay < maxTimeout && await new Promise((resolve) => setTimeout(resolve, delay));
864
+ }
865
+ attempt++;
866
+ }
867
+ }
868
+ return attempt > maxRetries && console.warn(
869
+ `[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`
870
+ ), results;
871
+ }
872
+ async function fetchTokenMetadataHybrid(mints, coingeckoConfig) {
873
+ let results = /* @__PURE__ */ new Map();
874
+ if (mints.length === 0) return results;
875
+ let uncachedMints = [], now = Date.now();
689
876
  for (let mint of mints) {
690
877
  let cached = metadataCache.get(mint);
691
- cached ? results.set(mint, cached) : uncachedMints.push(mint);
878
+ 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);
692
879
  }
693
880
  if (uncachedMints.length === 0) return results;
694
- try {
695
- let url = new URL("https://lite-api.jup.ag/tokens/v2/search");
696
- url.searchParams.append("query", uncachedMints.join(","));
697
- let response = await fetch(url.toString(), {
698
- signal: AbortSignal.timeout(1e4)
699
- });
700
- if (!response.ok)
701
- throw new Error(`Jupiter API error: ${response.status}`);
702
- let items = await response.json();
703
- for (let item of items) {
704
- let metadata = {
705
- id: item.id,
706
- name: item.id === NATIVE_MINT ? "Solana" : item.name,
707
- symbol: item.symbol,
708
- decimals: item.decimals,
709
- icon: item.icon,
710
- usdPrice: item.usdPrice
711
- };
712
- results.set(item.id, metadata), metadataCache.set(item.id, metadata);
881
+ let solanaMetadata = await fetchSolanaTokenMetadata(uncachedMints), coingeckoIdToMint = /* @__PURE__ */ new Map();
882
+ for (let [mint, meta] of solanaMetadata)
883
+ meta.extensions?.coingeckoId && coingeckoIdToMint.set(meta.extensions.coingeckoId, mint);
884
+ for (let mint of mints) {
885
+ let cached = metadataCache.get(mint);
886
+ cached?.coingeckoId && !coingeckoIdToMint.has(cached.coingeckoId) && coingeckoIdToMint.set(cached.coingeckoId, mint);
887
+ }
888
+ let prices = await fetchCoinGeckoPrices([...coingeckoIdToMint.keys()], coingeckoConfig);
889
+ for (let [mint, meta] of solanaMetadata) {
890
+ let coingeckoId = meta.extensions?.coingeckoId, usdPrice = coingeckoId ? prices.get(coingeckoId) : void 0, combined = {
891
+ address: meta.address,
892
+ name: meta.address === NATIVE_MINT ? "Solana" : meta.name,
893
+ symbol: meta.symbol,
894
+ decimals: meta.decimals,
895
+ logoURI: meta.logoURI,
896
+ coingeckoId,
897
+ usdPrice
898
+ };
899
+ results.set(mint, combined), metadataCache.set(mint, combined);
900
+ }
901
+ for (let [coingeckoId, mint] of coingeckoIdToMint) {
902
+ let cached = results.get(mint) ?? metadataCache.get(mint);
903
+ if (cached) {
904
+ let usdPrice = prices.get(coingeckoId);
905
+ usdPrice !== void 0 && (cached.usdPrice = usdPrice, results.set(mint, cached), metadataCache.set(mint, cached));
713
906
  }
714
- } catch (error) {
715
- console.warn("[useTokens] Jupiter API failed:", error);
716
907
  }
717
908
  return results;
718
909
  }
@@ -730,6 +921,12 @@ function formatUsd(amount, decimals, usdPrice) {
730
921
  maximumFractionDigits: 2
731
922
  });
732
923
  }
924
+ function transformImageUrl2(url, imageProxy) {
925
+ if (!url) return;
926
+ if (!imageProxy) return url;
927
+ let encodedUrl = encodeURIComponent(url);
928
+ return imageProxy.endsWith("/") ? imageProxy + encodedUrl : imageProxy + "/" + encodedUrl;
929
+ }
733
930
  function useTokens(options = {}) {
734
931
  let {
735
932
  includeZeroBalance = false,
@@ -737,7 +934,7 @@ function useTokens(options = {}) {
737
934
  refreshInterval = 6e4,
738
935
  fetchMetadata = true,
739
936
  includeNativeSol = true
740
- } = options, { address: address$1, connected } = useAccount(), client = useSolanaClient(), [tokens, setTokens] = useState([]), [isLoading, setIsLoading] = useState(false), [error, setError] = useState(null), [lastUpdated, setLastUpdated] = useState(null), [totalAccounts, setTotalAccounts] = useState(0), rpcClient = client?.client ?? null, fetchTokens = useCallback(async () => {
937
+ } = options, { address: address$1, connected } = useAccount(), client = useSolanaClient(), connectorClient = useConnectorClient(), [tokens, setTokens] = useState([]), [isLoading, setIsLoading] = useState(false), [error, setError] = useState(null), [lastUpdated, setLastUpdated] = useState(null), [totalAccounts, setTotalAccounts] = useState(0), rpcClient = client?.client ?? null, connectorConfig = connectorClient?.getConfig(), imageProxy = connectorConfig?.imageProxy, coingeckoConfig = connectorConfig?.coingecko, fetchTokens = useCallback(async () => {
741
938
  if (!connected || !address$1 || !rpcClient) {
742
939
  setTokens([]), setTotalAccounts(0);
743
940
  return;
@@ -779,16 +976,16 @@ function useTokens(options = {}) {
779
976
  }
780
977
  }
781
978
  if (setTokens([...tokenList]), setTotalAccounts(tokenAccountsResult.value.length + (includeNativeSol ? 1 : 0)), setLastUpdated(/* @__PURE__ */ new Date()), fetchMetadata && mints.length > 0) {
782
- let metadata = await fetchJupiterMetadata(mints);
979
+ let metadata = await fetchTokenMetadataHybrid(mints, coingeckoConfig);
783
980
  for (let i = 0; i < tokenList.length; i++) {
784
981
  let meta = metadata.get(tokenList[i].mint);
785
982
  meta && (tokenList[i] = {
786
983
  ...tokenList[i],
787
984
  name: meta.name,
788
985
  symbol: meta.symbol,
789
- logo: meta.icon,
986
+ logo: transformImageUrl2(meta.logoURI, imageProxy),
790
987
  usdPrice: meta.usdPrice,
791
- formattedUsd: formatUsd(tokenList[i].amount, tokenList[i].decimals, meta.usdPrice)
988
+ formattedUsd: meta.usdPrice ? formatUsd(tokenList[i].amount, tokenList[i].decimals, meta.usdPrice) : void 0
792
989
  });
793
990
  }
794
991
  tokenList.sort((a, b) => {
@@ -803,14 +1000,18 @@ function useTokens(options = {}) {
803
1000
  } finally {
804
1001
  setIsLoading(false);
805
1002
  }
806
- }, [connected, address$1, rpcClient, includeZeroBalance, fetchMetadata, includeNativeSol]);
807
- return useEffect(() => {
1003
+ }, [connected, address$1, rpcClient, includeZeroBalance, fetchMetadata, includeNativeSol, imageProxy, coingeckoConfig]);
1004
+ useEffect(() => {
808
1005
  fetchTokens();
809
1006
  }, [fetchTokens]), useEffect(() => {
810
1007
  if (!connected || !autoRefresh) return;
811
1008
  let interval = setInterval(fetchTokens, refreshInterval);
812
1009
  return () => clearInterval(interval);
813
- }, [connected, autoRefresh, refreshInterval, fetchTokens]), useMemo(
1010
+ }, [connected, autoRefresh, refreshInterval, fetchTokens]), useEffect(() => (startCacheCleanup(), () => stopCacheCleanup()), []);
1011
+ let wasConnectedRef = useRef(connected);
1012
+ return useEffect(() => {
1013
+ wasConnectedRef.current && !connected && clearTokenCaches(), wasConnectedRef.current = connected;
1014
+ }, [connected]), useMemo(
814
1015
  () => ({
815
1016
  tokens,
816
1017
  isLoading,
@@ -1980,5 +2181,5 @@ function TokenListElement({
1980
2181
  TokenListElement.displayName = "TokenListElement";
1981
2182
 
1982
2183
  export { AccountElement, BalanceElement, ClusterElement, ConnectorProvider, DisconnectElement, TokenListElement, TransactionHistoryElement, UnifiedProvider, WalletListElement, useAccount, useBalance, useCluster, useConnector, useConnectorClient, useGillSolanaClient, useGillTransactionSigner, useKitTransactionSigner, useSolanaClient, useTokens, useTransactionPreparer, useTransactionSigner, useTransactions, useWalletInfo };
1983
- //# sourceMappingURL=chunk-U64YZRJL.mjs.map
1984
- //# sourceMappingURL=chunk-U64YZRJL.mjs.map
2184
+ //# sourceMappingURL=chunk-3STZXVXD.mjs.map
2185
+ //# sourceMappingURL=chunk-3STZXVXD.mjs.map