@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,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
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 =
|
|
15
|
-
|
|
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
|
|
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
|
-
|
|
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 ?
|
|
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 ?
|
|
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 :
|
|
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 ?
|
|
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 =
|
|
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 ?
|
|
266
|
+
return rpcUrl ? chunkI64FD2EH_js.createSolanaClient({
|
|
267
267
|
urlOrMoniker: rpcUrl
|
|
268
|
-
}) : type !== "custom" ?
|
|
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
|
|
290
|
-
return
|
|
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
|
|
510
|
-
|
|
511
|
-
|
|
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
|
|
516
|
-
for (let item of
|
|
517
|
-
let metadata = { symbol: item.symbol, icon: item.
|
|
518
|
-
results.set(item.
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
691
|
-
|
|
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
|
|
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
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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-
|
|
2012
|
-
//# sourceMappingURL=chunk-
|
|
2212
|
+
//# sourceMappingURL=chunk-ULUYX23Q.js.map
|
|
2213
|
+
//# sourceMappingURL=chunk-ULUYX23Q.js.map
|