@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.
- package/README.md +301 -0
- package/dist/{chunk-U64YZRJL.mjs → chunk-3STZXVXD.mjs} +254 -53
- package/dist/chunk-3STZXVXD.mjs.map +1 -0
- package/dist/{chunk-RIQH5W7D.js → chunk-I64FD2EH.js} +4 -3
- package/dist/chunk-I64FD2EH.js.map +1 -0
- package/dist/{chunk-CLXM6UEE.js → chunk-JUZVCBAI.js} +91 -85
- package/dist/chunk-JUZVCBAI.js.map +1 -0
- package/dist/{chunk-D6PZY5G6.js → chunk-NQXK7PGX.js} +30 -26
- package/dist/chunk-NQXK7PGX.js.map +1 -0
- package/dist/{chunk-N3Q2J2FG.mjs → chunk-QKVL45F6.mjs} +10 -6
- package/dist/chunk-QKVL45F6.mjs.map +1 -0
- package/dist/{chunk-P5MWBDFG.mjs → chunk-QL3IT3TS.mjs} +4 -3
- package/dist/chunk-QL3IT3TS.mjs.map +1 -0
- package/dist/{chunk-LUZWUZ5N.js → chunk-ULUYX23Q.js} +268 -67
- package/dist/chunk-ULUYX23Q.js.map +1 -0
- package/dist/{chunk-YTCSTE3Q.mjs → chunk-VMSZJPR5.mjs} +10 -4
- package/dist/chunk-VMSZJPR5.mjs.map +1 -0
- package/dist/compat.js +3 -3
- package/dist/compat.mjs +1 -1
- package/dist/headless.d.mts +2 -2
- package/dist/headless.d.ts +2 -2
- package/dist/headless.js +120 -120
- package/dist/headless.mjs +3 -3
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +147 -147
- package/dist/index.mjs +4 -4
- package/dist/react.d.mts +4 -4
- package/dist/react.d.ts +4 -4
- package/dist/react.js +28 -28
- package/dist/react.mjs +2 -2
- package/dist/{wallet-standard-shim-DC_Z7DS-.d.ts → wallet-standard-shim--YcrQNRt.d.ts} +83 -0
- package/dist/{wallet-standard-shim-Cp4vF4oo.d.mts → wallet-standard-shim-Dx7H8Ctf.d.mts} +83 -0
- package/package.json +1 -1
- package/dist/chunk-CLXM6UEE.js.map +0 -1
- package/dist/chunk-D6PZY5G6.js.map +0 -1
- package/dist/chunk-LUZWUZ5N.js.map +0 -1
- package/dist/chunk-N3Q2J2FG.mjs.map +0 -1
- package/dist/chunk-P5MWBDFG.mjs.map +0 -1
- package/dist/chunk-RIQH5W7D.js.map +0 -1
- package/dist/chunk-U64YZRJL.mjs.map +0 -1
- 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-
|
|
2
|
-
import { createLogger, createSolanaClient, prepareTransaction } from './chunk-
|
|
3
|
-
import React, { createContext, useContext, useSyncExternalStore, useMemo, useState, useCallback,
|
|
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
|
|
504
|
-
|
|
505
|
-
|
|
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
|
|
510
|
-
for (let item of
|
|
511
|
-
let metadata = { symbol: item.symbol, icon: item.
|
|
512
|
-
results.set(item.
|
|
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(
|
|
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
|
-
|
|
685
|
-
|
|
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
|
|
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
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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]),
|
|
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-
|
|
1984
|
-
//# sourceMappingURL=chunk-
|
|
2184
|
+
//# sourceMappingURL=chunk-3STZXVXD.mjs.map
|
|
2185
|
+
//# sourceMappingURL=chunk-3STZXVXD.mjs.map
|