@solana/connector 0.1.6 → 0.1.7
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 +45 -45
- package/dist/{chunk-VMSZJPR5.mjs → chunk-APQGEW7S.mjs} +82 -11
- package/dist/chunk-APQGEW7S.mjs.map +1 -0
- package/dist/{chunk-3STZXVXD.mjs → chunk-JK47EFJT.mjs} +940 -381
- package/dist/chunk-JK47EFJT.mjs.map +1 -0
- package/dist/{chunk-QKVL45F6.mjs → chunk-TQRJYZNK.mjs} +5 -3
- package/dist/chunk-TQRJYZNK.mjs.map +1 -0
- package/dist/{chunk-JUZVCBAI.js → chunk-VA6LKXCQ.js} +85 -10
- package/dist/chunk-VA6LKXCQ.js.map +1 -0
- package/dist/{chunk-NQXK7PGX.js → chunk-VZ5Y6DIM.js} +19 -17
- package/dist/chunk-VZ5Y6DIM.js.map +1 -0
- package/dist/{chunk-ULUYX23Q.js → chunk-Z22V3D4E.js} +949 -388
- package/dist/chunk-Z22V3D4E.js.map +1 -0
- package/dist/compat.d.mts +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/headless.d.mts +99 -7
- package/dist/headless.d.ts +99 -7
- package/dist/headless.js +128 -112
- package/dist/headless.mjs +2 -2
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +163 -139
- package/dist/index.mjs +3 -3
- package/dist/react.d.mts +179 -13
- package/dist/react.d.ts +179 -13
- package/dist/react.js +36 -28
- package/dist/react.mjs +2 -2
- package/dist/{transaction-signer-D9d8nxwb.d.mts → transaction-signer-CpGEvp7S.d.mts} +1 -1
- package/dist/{transaction-signer-D9d8nxwb.d.ts → transaction-signer-CpGEvp7S.d.ts} +1 -1
- package/dist/{wallet-standard-shim--YcrQNRt.d.ts → wallet-standard-shim-D4CYG5sU.d.mts} +35 -6
- package/dist/{wallet-standard-shim-Dx7H8Ctf.d.mts → wallet-standard-shim-DiMvGjOk.d.ts} +35 -6
- package/package.json +1 -1
- package/dist/chunk-3STZXVXD.mjs.map +0 -1
- package/dist/chunk-JUZVCBAI.js.map +0 -1
- package/dist/chunk-NQXK7PGX.js.map +0 -1
- package/dist/chunk-QKVL45F6.mjs.map +0 -1
- package/dist/chunk-ULUYX23Q.js.map +0 -1
- package/dist/chunk-VMSZJPR5.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { installPolyfills, ConnectorErrorBoundary, isMainnetCluster, isDevnetCluster, isTestnetCluster, isLocalCluster, getClusterExplorerUrl, getClusterType, formatAddress, copyAddressToClipboard, createTransactionSigner, createKitTransactionSigner, NetworkError, getTransactionUrl, ConnectorClient } from './chunk-
|
|
1
|
+
import { installPolyfills, ConnectorErrorBoundary, isMainnetCluster, isDevnetCluster, isTestnetCluster, isLocalCluster, getClusterExplorerUrl, getClusterType, formatAddress, copyAddressToClipboard, createTransactionSigner, createKitTransactionSigner, NetworkError, formatLamportsToSolSafe, getTransactionUrl, ConnectorClient, formatBigIntBalance, formatBigIntUsd } from './chunk-APQGEW7S.mjs';
|
|
2
2
|
import { createLogger, __publicField, createSolanaClient, prepareTransaction } from './chunk-QL3IT3TS.mjs';
|
|
3
3
|
import React, { createContext, useContext, useSyncExternalStore, useMemo, useState, useCallback, useRef, useEffect } from 'react';
|
|
4
4
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
@@ -54,15 +54,15 @@ function ConnectorProviderInternal({
|
|
|
54
54
|
"solana:mainnet",
|
|
55
55
|
"solana:devnet",
|
|
56
56
|
"solana:testnet"
|
|
57
|
-
]
|
|
58
|
-
registerMwa({
|
|
57
|
+
], mwaConfig = {
|
|
59
58
|
appIdentity: mobile.appIdentity,
|
|
60
59
|
authorizationCache: mobile.authorizationCache ?? createDefaultAuthorizationCache(),
|
|
61
60
|
chains: mobile.chains ?? defaultChains,
|
|
62
61
|
chainSelector: mobile.chainSelector ?? createDefaultChainSelector(),
|
|
63
62
|
remoteHostAuthority: mobile.remoteHostAuthority,
|
|
64
63
|
onWalletNotFound: mobile.onWalletNotFound ?? createDefaultWalletNotFoundHandler()
|
|
65
|
-
}
|
|
64
|
+
};
|
|
65
|
+
registerMwa(mwaConfig);
|
|
66
66
|
} catch {
|
|
67
67
|
}
|
|
68
68
|
})(), () => {
|
|
@@ -299,73 +299,493 @@ function useTransactionPreparer() {
|
|
|
299
299
|
[prepare, ready]
|
|
300
300
|
);
|
|
301
301
|
}
|
|
302
|
-
var
|
|
303
|
-
function
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
302
|
+
var DEFAULT_CACHE_TIME_MS = 300 * 1e3, MAX_STORE_SIZE = 100, store = /* @__PURE__ */ new Map();
|
|
303
|
+
function evictStaleEntries() {
|
|
304
|
+
if (store.size <= MAX_STORE_SIZE) return;
|
|
305
|
+
let evictable = [];
|
|
306
|
+
for (let [key, entry] of store)
|
|
307
|
+
entry.subscribers.size === 0 && evictable.push([key, entry]);
|
|
308
|
+
evictable.sort((a, b) => a[1].lastAccessedAt - b[1].lastAccessedAt);
|
|
309
|
+
for (let [key, entry] of evictable) {
|
|
310
|
+
if (store.size <= MAX_STORE_SIZE) break;
|
|
311
|
+
entry.gcTimeoutId && clearTimeout(entry.gcTimeoutId), entry.intervalId && clearInterval(entry.intervalId), entry.abortController && entry.abortController.abort(), store.delete(key);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function getOrCreateEntry(key) {
|
|
315
|
+
let existing = store.get(key);
|
|
316
|
+
if (existing)
|
|
317
|
+
return existing.lastAccessedAt = Date.now(), existing;
|
|
318
|
+
let entry = {
|
|
319
|
+
snapshot: {
|
|
320
|
+
data: void 0,
|
|
321
|
+
error: null,
|
|
322
|
+
status: "idle",
|
|
323
|
+
updatedAt: null,
|
|
324
|
+
isFetching: false
|
|
325
|
+
},
|
|
326
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
327
|
+
promise: null,
|
|
328
|
+
abortController: null,
|
|
329
|
+
cacheTimeMs: DEFAULT_CACHE_TIME_MS,
|
|
330
|
+
gcTimeoutId: null,
|
|
331
|
+
queryFn: null,
|
|
332
|
+
intervalCounts: /* @__PURE__ */ new Map(),
|
|
333
|
+
activeIntervalMs: null,
|
|
334
|
+
intervalId: null,
|
|
335
|
+
lastAccessedAt: Date.now()
|
|
336
|
+
};
|
|
337
|
+
return store.set(key, entry), evictStaleEntries(), entry;
|
|
338
|
+
}
|
|
339
|
+
function emit(entry) {
|
|
340
|
+
for (let cb of entry.subscribers)
|
|
341
|
+
cb();
|
|
342
|
+
}
|
|
343
|
+
function setSnapshot(entry, next) {
|
|
344
|
+
let prev = entry.snapshot;
|
|
345
|
+
prev.status === next.status && prev.isFetching === next.isFetching && prev.updatedAt === next.updatedAt && prev.error === next.error && prev.data === next.data || (entry.snapshot = next, emit(entry));
|
|
346
|
+
}
|
|
347
|
+
function getMinIntervalMs(entry) {
|
|
348
|
+
let min = null;
|
|
349
|
+
for (let [ms, count] of entry.intervalCounts)
|
|
350
|
+
count <= 0 || (min === null || ms < min) && (min = ms);
|
|
351
|
+
return min;
|
|
352
|
+
}
|
|
353
|
+
function startOrRestartInterval(key, entry) {
|
|
354
|
+
let nextMs = getMinIntervalMs(entry);
|
|
355
|
+
if (nextMs === null) {
|
|
356
|
+
entry.intervalId && clearInterval(entry.intervalId), entry.intervalId = null, entry.activeIntervalMs = null;
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
entry.activeIntervalMs === nextMs && entry.intervalId || (entry.intervalId && clearInterval(entry.intervalId), entry.activeIntervalMs = nextMs, entry.intervalId = setInterval(() => {
|
|
360
|
+
let fn = entry.queryFn;
|
|
361
|
+
fn && fetchSharedQuery(key, fn, { force: true });
|
|
362
|
+
}, nextMs));
|
|
363
|
+
}
|
|
364
|
+
function subscribeSharedQuery(key, onChange, cacheTimeMs) {
|
|
365
|
+
let entry = getOrCreateEntry(key);
|
|
366
|
+
return entry.subscribers.add(onChange), typeof cacheTimeMs == "number" && (entry.cacheTimeMs = Math.max(entry.cacheTimeMs, cacheTimeMs)), entry.gcTimeoutId && (clearTimeout(entry.gcTimeoutId), entry.gcTimeoutId = null), () => {
|
|
367
|
+
if (entry.subscribers.delete(onChange), entry.subscribers.size > 0) return;
|
|
368
|
+
entry.abortController && entry.abortController.abort(), entry.abortController = null, entry.promise = null, entry.intervalId && clearInterval(entry.intervalId), entry.intervalId = null, entry.activeIntervalMs = null, entry.intervalCounts.clear();
|
|
369
|
+
let gcDelayMs = entry.cacheTimeMs ?? DEFAULT_CACHE_TIME_MS;
|
|
370
|
+
entry.gcTimeoutId = setTimeout(() => {
|
|
371
|
+
entry.subscribers.size === 0 && store.delete(key);
|
|
372
|
+
}, gcDelayMs);
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
async function fetchSharedQuery(key, queryFn, options = {}) {
|
|
376
|
+
let entry = getOrCreateEntry(key);
|
|
377
|
+
entry.queryFn = queryFn;
|
|
378
|
+
let staleTimeMs = options.staleTimeMs ?? 0, now = Date.now();
|
|
379
|
+
if (options.force !== true && entry.snapshot.status === "success" && entry.snapshot.updatedAt !== null && now - entry.snapshot.updatedAt < staleTimeMs && entry.snapshot.data !== void 0)
|
|
380
|
+
return entry.snapshot.data;
|
|
381
|
+
if (entry.promise)
|
|
382
|
+
return entry.promise;
|
|
383
|
+
let controller = new AbortController();
|
|
384
|
+
entry.abortController = controller, options.signal && (options.signal.aborted ? controller.abort() : options.signal.addEventListener("abort", () => controller.abort(), { once: true }));
|
|
385
|
+
let isFirstLoad = entry.snapshot.status === "idle" && entry.snapshot.updatedAt === null;
|
|
386
|
+
setSnapshot(entry, {
|
|
387
|
+
...entry.snapshot,
|
|
388
|
+
status: isFirstLoad ? "loading" : entry.snapshot.status,
|
|
389
|
+
isFetching: true,
|
|
390
|
+
error: null
|
|
391
|
+
});
|
|
392
|
+
let promise = (async () => {
|
|
393
|
+
try {
|
|
394
|
+
let data = await queryFn(controller.signal);
|
|
395
|
+
return setSnapshot(entry, {
|
|
396
|
+
data,
|
|
397
|
+
error: null,
|
|
398
|
+
status: "success",
|
|
399
|
+
updatedAt: Date.now(),
|
|
400
|
+
isFetching: false
|
|
401
|
+
}), data;
|
|
402
|
+
} catch (cause) {
|
|
403
|
+
if (controller.signal.aborted)
|
|
404
|
+
return setSnapshot(entry, {
|
|
405
|
+
data: entry.snapshot.data,
|
|
406
|
+
error: null,
|
|
407
|
+
status: entry.snapshot.status === "idle" ? "idle" : entry.snapshot.status,
|
|
408
|
+
updatedAt: entry.snapshot.updatedAt,
|
|
409
|
+
isFetching: false
|
|
410
|
+
}), entry.snapshot.data;
|
|
411
|
+
let error = cause instanceof Error ? cause : new Error(String(cause));
|
|
412
|
+
throw setSnapshot(entry, {
|
|
413
|
+
data: entry.snapshot.data,
|
|
414
|
+
error,
|
|
415
|
+
status: "error",
|
|
416
|
+
updatedAt: entry.snapshot.updatedAt,
|
|
417
|
+
isFetching: false
|
|
418
|
+
}), error;
|
|
419
|
+
} finally {
|
|
420
|
+
entry.promise = null, entry.abortController = null;
|
|
421
|
+
}
|
|
422
|
+
})();
|
|
423
|
+
return entry.promise = promise, promise;
|
|
424
|
+
}
|
|
425
|
+
function useSharedQuery(key, queryFn, options = {}) {
|
|
426
|
+
let {
|
|
427
|
+
enabled = true,
|
|
428
|
+
staleTimeMs = 0,
|
|
429
|
+
cacheTimeMs,
|
|
430
|
+
refetchOnMount = "stale",
|
|
431
|
+
refetchIntervalMs = false,
|
|
432
|
+
select
|
|
433
|
+
} = options, queryFnRef = useRef(queryFn);
|
|
434
|
+
queryFnRef.current = queryFn;
|
|
435
|
+
let stableQueryFn = useCallback((signal) => queryFnRef.current(signal), []), subscribe = useCallback(
|
|
436
|
+
(onChange) => key ? subscribeSharedQuery(key, onChange, cacheTimeMs) : () => {
|
|
437
|
+
},
|
|
438
|
+
[key, cacheTimeMs]
|
|
439
|
+
), emptySnapshot = useMemo(
|
|
440
|
+
() => ({
|
|
441
|
+
data: void 0,
|
|
442
|
+
error: null,
|
|
443
|
+
status: "idle",
|
|
444
|
+
updatedAt: null,
|
|
445
|
+
isFetching: false
|
|
446
|
+
}),
|
|
447
|
+
[]
|
|
448
|
+
), getSnapshot = useCallback(() => key ? getOrCreateEntry(key).snapshot : emptySnapshot, [key, emptySnapshot]), snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot), selectedData = useMemo(() => select ? select(snapshot.data) : snapshot.data, [snapshot.data, select]), prevSelectedRef = useRef(selectedData), stableSelectedData = useMemo(() => Object.is(prevSelectedRef.current, selectedData) ? prevSelectedRef.current : (prevSelectedRef.current = selectedData, selectedData), [selectedData]), fetchedKeyRef = useRef(null);
|
|
449
|
+
useEffect(() => {
|
|
450
|
+
if (!key || !enabled) {
|
|
451
|
+
fetchedKeyRef.current = null;
|
|
313
452
|
return;
|
|
314
453
|
}
|
|
315
|
-
|
|
454
|
+
let entry = getOrCreateEntry(key);
|
|
455
|
+
if (entry.queryFn = stableQueryFn, fetchedKeyRef.current === key && entry.snapshot.status === "success")
|
|
456
|
+
return;
|
|
457
|
+
let current = entry.snapshot, isStale = current.updatedAt === null || Date.now() - current.updatedAt >= staleTimeMs;
|
|
458
|
+
refetchOnMount === true || current.status === "idle" || refetchOnMount === "stale" && isStale ? (fetchedKeyRef.current = key, fetchSharedQuery(key, stableQueryFn, {
|
|
459
|
+
staleTimeMs,
|
|
460
|
+
force: refetchOnMount === true
|
|
461
|
+
}).catch(() => {
|
|
462
|
+
})) : fetchedKeyRef.current = key;
|
|
463
|
+
}, [key, enabled, staleTimeMs, refetchOnMount, stableQueryFn]), useEffect(() => {
|
|
464
|
+
if (!key || !enabled || refetchIntervalMs === false) return;
|
|
465
|
+
let entry = getOrCreateEntry(key);
|
|
466
|
+
entry.queryFn = stableQueryFn;
|
|
467
|
+
let prev = entry.intervalCounts.get(refetchIntervalMs) ?? 0;
|
|
468
|
+
return entry.intervalCounts.set(refetchIntervalMs, prev + 1), startOrRestartInterval(key, entry), () => {
|
|
469
|
+
let count = entry.intervalCounts.get(refetchIntervalMs) ?? 0;
|
|
470
|
+
count <= 1 ? entry.intervalCounts.delete(refetchIntervalMs) : entry.intervalCounts.set(refetchIntervalMs, count - 1), startOrRestartInterval(key, entry);
|
|
471
|
+
};
|
|
472
|
+
}, [key, enabled, refetchIntervalMs, stableQueryFn]);
|
|
473
|
+
let refetch = useCallback(
|
|
474
|
+
async (refetchOptions) => {
|
|
475
|
+
if (key)
|
|
476
|
+
return fetchSharedQuery(key, stableQueryFn, {
|
|
477
|
+
force: true,
|
|
478
|
+
signal: refetchOptions?.signal
|
|
479
|
+
});
|
|
480
|
+
},
|
|
481
|
+
[key, stableQueryFn]
|
|
482
|
+
), abort = useCallback(() => {
|
|
483
|
+
if (!key) return;
|
|
484
|
+
let entry = store.get(key);
|
|
485
|
+
entry?.abortController && entry.abortController.abort();
|
|
486
|
+
}, [key]);
|
|
487
|
+
return useMemo(
|
|
488
|
+
() => ({
|
|
489
|
+
data: stableSelectedData,
|
|
490
|
+
error: snapshot.error,
|
|
491
|
+
status: snapshot.status,
|
|
492
|
+
updatedAt: snapshot.updatedAt,
|
|
493
|
+
isFetching: snapshot.isFetching,
|
|
494
|
+
refetch,
|
|
495
|
+
abort
|
|
496
|
+
}),
|
|
497
|
+
[stableSelectedData, snapshot.error, snapshot.status, snapshot.updatedAt, snapshot.isFetching, refetch, abort]
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
function invalidateSharedQuery(key) {
|
|
501
|
+
let entry = store.get(key);
|
|
502
|
+
entry && setSnapshot(entry, {
|
|
503
|
+
...entry.snapshot,
|
|
504
|
+
updatedAt: null
|
|
505
|
+
// Mark as stale
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
function clearSharedQueryCache() {
|
|
509
|
+
for (let [, entry] of store)
|
|
510
|
+
entry.intervalId && clearInterval(entry.intervalId), entry.gcTimeoutId && clearTimeout(entry.gcTimeoutId), entry.abortController && entry.abortController.abort();
|
|
511
|
+
store.clear();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/hooks/_internal/use-wallet-assets.ts
|
|
515
|
+
var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", NATIVE_SOL_MINT = "So11111111111111111111111111111111111111112";
|
|
516
|
+
function isRecord(value) {
|
|
517
|
+
return typeof value == "object" && value !== null;
|
|
518
|
+
}
|
|
519
|
+
function parseBigInt(value) {
|
|
520
|
+
if (typeof value == "bigint") return value;
|
|
521
|
+
if (typeof value == "number" && Number.isSafeInteger(value)) return BigInt(value);
|
|
522
|
+
if (typeof value == "string")
|
|
316
523
|
try {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
524
|
+
return BigInt(value);
|
|
525
|
+
} catch {
|
|
526
|
+
return 0n;
|
|
527
|
+
}
|
|
528
|
+
return 0n;
|
|
529
|
+
}
|
|
530
|
+
function getParsedTokenAccountInfo(data) {
|
|
531
|
+
if (!isRecord(data)) return null;
|
|
532
|
+
let parsed = data.parsed;
|
|
533
|
+
if (!isRecord(parsed)) return null;
|
|
534
|
+
let info = parsed.info;
|
|
535
|
+
return isRecord(info) ? info : null;
|
|
536
|
+
}
|
|
537
|
+
function parseTokenAccount(account, programId) {
|
|
538
|
+
let info = getParsedTokenAccountInfo(account.account.data);
|
|
539
|
+
if (!info) return null;
|
|
540
|
+
let mint = typeof info.mint == "string" ? info.mint : null, owner = typeof info.owner == "string" ? info.owner : null;
|
|
541
|
+
if (!mint || !owner) return null;
|
|
542
|
+
let tokenAmount = isRecord(info.tokenAmount) ? info.tokenAmount : null, amount = parseBigInt(tokenAmount?.amount), decimals = typeof tokenAmount?.decimals == "number" ? tokenAmount.decimals : 0, state = typeof info.state == "string" ? info.state : void 0;
|
|
543
|
+
return {
|
|
544
|
+
pubkey: String(account.pubkey),
|
|
545
|
+
mint,
|
|
546
|
+
owner,
|
|
547
|
+
amount,
|
|
548
|
+
decimals,
|
|
549
|
+
isFrozen: state === "frozen",
|
|
550
|
+
programId
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function useWalletAssets(options = {}) {
|
|
554
|
+
let {
|
|
555
|
+
enabled = true,
|
|
556
|
+
staleTimeMs = 0,
|
|
557
|
+
cacheTimeMs = 300 * 1e3,
|
|
558
|
+
// 5 minutes
|
|
559
|
+
refetchOnMount = "stale",
|
|
560
|
+
refetchIntervalMs = false,
|
|
561
|
+
client: clientOverride,
|
|
562
|
+
select
|
|
563
|
+
} = options, { address: address$1, connected } = useAccount(), { client: providerClient } = useSolanaClient(), rpcClient = clientOverride ?? providerClient, key = useMemo(() => {
|
|
564
|
+
if (!enabled || !connected || !address$1 || !rpcClient) return null;
|
|
565
|
+
let rpcUrl = rpcClient.urlOrMoniker instanceof URL ? rpcClient.urlOrMoniker.toString() : String(rpcClient.urlOrMoniker);
|
|
566
|
+
return JSON.stringify(["wallet-assets", rpcUrl, address$1]);
|
|
567
|
+
}, [enabled, connected, address$1, rpcClient]), queryFn = useCallback(
|
|
568
|
+
async (signal) => {
|
|
569
|
+
if (!connected || !address$1 || !rpcClient)
|
|
570
|
+
return { lamports: 0n, tokenAccounts: [] };
|
|
571
|
+
if (signal.aborted)
|
|
572
|
+
throw new DOMException("Query aborted", "AbortError");
|
|
573
|
+
let rpc = rpcClient.rpc, walletAddress = address(address$1), tokenProgramId = address(TOKEN_PROGRAM_ID), token2022ProgramId = address(TOKEN_2022_PROGRAM_ID), [balanceResult, tokenAccountsResult, token2022AccountsResult] = await Promise.all([
|
|
574
|
+
rpc.getBalance(walletAddress).send(),
|
|
575
|
+
rpc.getTokenAccountsByOwner(walletAddress, { programId: tokenProgramId }, { encoding: "jsonParsed" }).send(),
|
|
576
|
+
rpc.getTokenAccountsByOwner(
|
|
577
|
+
walletAddress,
|
|
578
|
+
{ programId: token2022ProgramId },
|
|
579
|
+
{ encoding: "jsonParsed" }
|
|
580
|
+
).send()
|
|
581
|
+
]);
|
|
582
|
+
if (signal.aborted)
|
|
583
|
+
throw new DOMException("Query aborted", "AbortError");
|
|
584
|
+
let tokenAccounts = [];
|
|
585
|
+
for (let account of tokenAccountsResult.value) {
|
|
586
|
+
let parsed = parseTokenAccount(account, "token");
|
|
587
|
+
parsed && tokenAccounts.push(parsed);
|
|
339
588
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
589
|
+
for (let account of token2022AccountsResult.value) {
|
|
590
|
+
let parsed = parseTokenAccount(account, "token-2022");
|
|
591
|
+
parsed && tokenAccounts.push(parsed);
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
lamports: balanceResult.value,
|
|
595
|
+
tokenAccounts
|
|
596
|
+
};
|
|
597
|
+
},
|
|
598
|
+
[connected, address$1, rpcClient]
|
|
599
|
+
), { data, error, status, updatedAt, isFetching, refetch, abort } = useSharedQuery(
|
|
600
|
+
key,
|
|
601
|
+
queryFn,
|
|
602
|
+
{
|
|
603
|
+
enabled,
|
|
604
|
+
staleTimeMs,
|
|
605
|
+
cacheTimeMs,
|
|
606
|
+
refetchOnMount,
|
|
607
|
+
refetchIntervalMs,
|
|
608
|
+
select
|
|
345
609
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
610
|
+
), isLoading = status === "loading" || status === "idle";
|
|
611
|
+
return useMemo(
|
|
612
|
+
() => ({
|
|
613
|
+
data,
|
|
614
|
+
isLoading,
|
|
615
|
+
isFetching,
|
|
616
|
+
error,
|
|
617
|
+
refetch,
|
|
618
|
+
abort,
|
|
619
|
+
updatedAt
|
|
620
|
+
}),
|
|
621
|
+
[data, isLoading, isFetching, error, refetch, abort, updatedAt]
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// src/hooks/use-balance.ts
|
|
626
|
+
var LAMPORTS_PER_SOL2 = 1000000000n;
|
|
627
|
+
function formatTokenAccount(account) {
|
|
628
|
+
let formatted = formatBigIntBalance(account.amount, account.decimals, {
|
|
629
|
+
maxDecimals: Math.min(account.decimals, 6)
|
|
630
|
+
});
|
|
631
|
+
return {
|
|
632
|
+
mint: account.mint,
|
|
633
|
+
amount: account.amount,
|
|
634
|
+
decimals: account.decimals,
|
|
635
|
+
formatted
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function selectBalance(assets) {
|
|
639
|
+
if (!assets)
|
|
640
|
+
return { lamports: 0n, tokens: [] };
|
|
641
|
+
let tokens = assets.tokenAccounts.filter((account) => account.amount > 0n).map(formatTokenAccount);
|
|
642
|
+
return {
|
|
643
|
+
lamports: assets.lamports,
|
|
644
|
+
tokens
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function useBalance(options = {}) {
|
|
648
|
+
let {
|
|
649
|
+
enabled = true,
|
|
650
|
+
autoRefresh = true,
|
|
651
|
+
refreshInterval = 3e4,
|
|
652
|
+
staleTimeMs = 0,
|
|
653
|
+
cacheTimeMs = 300 * 1e3,
|
|
654
|
+
// 5 minutes
|
|
655
|
+
refetchOnMount = "stale",
|
|
656
|
+
client: clientOverride
|
|
657
|
+
} = options, {
|
|
658
|
+
data,
|
|
659
|
+
error,
|
|
660
|
+
isFetching,
|
|
661
|
+
updatedAt,
|
|
662
|
+
refetch: sharedRefetch,
|
|
663
|
+
abort
|
|
664
|
+
} = useWalletAssets({
|
|
665
|
+
enabled,
|
|
666
|
+
staleTimeMs,
|
|
667
|
+
cacheTimeMs,
|
|
668
|
+
refetchOnMount,
|
|
669
|
+
refetchIntervalMs: autoRefresh ? refreshInterval : false,
|
|
670
|
+
client: clientOverride,
|
|
671
|
+
select: selectBalance
|
|
672
|
+
}), lamports = data?.lamports ?? 0n, tokens = data?.tokens ?? [], solBalance = useMemo(() => Number(lamports) / Number(LAMPORTS_PER_SOL2), [lamports]), formattedSol = useMemo(() => formatLamportsToSolSafe(lamports, { maxDecimals: 4, suffix: true }), [lamports]), visibleError = updatedAt ? null : error, refetch = useCallback(
|
|
673
|
+
async (opts) => {
|
|
674
|
+
await sharedRefetch(opts);
|
|
675
|
+
},
|
|
676
|
+
[sharedRefetch]
|
|
677
|
+
);
|
|
355
678
|
return useMemo(
|
|
356
679
|
() => ({
|
|
357
680
|
solBalance,
|
|
358
681
|
lamports,
|
|
359
682
|
formattedSol,
|
|
360
683
|
tokens,
|
|
361
|
-
isLoading,
|
|
362
|
-
error,
|
|
363
|
-
refetch
|
|
364
|
-
|
|
684
|
+
isLoading: isFetching,
|
|
685
|
+
error: visibleError,
|
|
686
|
+
refetch,
|
|
687
|
+
abort,
|
|
688
|
+
lastUpdated: updatedAt ? new Date(updatedAt) : null
|
|
365
689
|
}),
|
|
366
|
-
[solBalance, lamports, formattedSol, tokens,
|
|
690
|
+
[solBalance, lamports, formattedSol, tokens, isFetching, visibleError, refetch, abort, updatedAt]
|
|
367
691
|
);
|
|
368
692
|
}
|
|
693
|
+
|
|
694
|
+
// src/utils/abort.ts
|
|
695
|
+
function createTimeoutSignal(ms) {
|
|
696
|
+
if (typeof AbortSignal.timeout == "function")
|
|
697
|
+
return { signal: AbortSignal.timeout(ms), cleanup: () => {
|
|
698
|
+
} };
|
|
699
|
+
let controller = new AbortController(), timeoutId = setTimeout(() => controller.abort(), ms);
|
|
700
|
+
return {
|
|
701
|
+
signal: controller.signal,
|
|
702
|
+
cleanup: () => clearTimeout(timeoutId)
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/hooks/_internal/solana-token-list.ts
|
|
707
|
+
var CLUSTER_CHAIN_IDS = {
|
|
708
|
+
mainnet: 101,
|
|
709
|
+
testnet: 102,
|
|
710
|
+
devnet: 103,
|
|
711
|
+
localnet: 103,
|
|
712
|
+
// Use devnet tokens for localnet
|
|
713
|
+
custom: 101
|
|
714
|
+
// Default to mainnet for custom clusters
|
|
715
|
+
}, TOKEN_LIST_API_BASE_URL = "https://token-list-api.solana.cloud/v1/mints";
|
|
716
|
+
function getTokenListApiUrl(cluster = "mainnet") {
|
|
717
|
+
let chainId = CLUSTER_CHAIN_IDS[cluster];
|
|
718
|
+
return `${TOKEN_LIST_API_BASE_URL}?chainId=${chainId}`;
|
|
719
|
+
}
|
|
720
|
+
var DEFAULT_TIMEOUT_MS = 1e4, TOKEN_LIST_CACHE_MAX_SIZE = 1500, MAX_ADDRESSES_PER_REQUEST = 100, tokenListCache = /* @__PURE__ */ new Map();
|
|
721
|
+
function getCachedTokenListMetadata(mint) {
|
|
722
|
+
let value = tokenListCache.get(mint);
|
|
723
|
+
if (value)
|
|
724
|
+
return tokenListCache.delete(mint), tokenListCache.set(mint, value), value;
|
|
725
|
+
}
|
|
726
|
+
function setCachedTokenListMetadata(mint, value) {
|
|
727
|
+
if (tokenListCache.has(mint) && tokenListCache.delete(mint), tokenListCache.set(mint, value), tokenListCache.size > TOKEN_LIST_CACHE_MAX_SIZE) {
|
|
728
|
+
let oldestKey = tokenListCache.keys().next().value;
|
|
729
|
+
oldestKey && tokenListCache.delete(oldestKey);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function createLinkedSignal(externalSignal, timeoutMs) {
|
|
733
|
+
let controller = new AbortController(), onAbort = () => controller.abort();
|
|
734
|
+
externalSignal && (externalSignal.aborted ? controller.abort() : externalSignal.addEventListener("abort", onAbort, { once: true }));
|
|
735
|
+
let { signal: timeoutSignal, cleanup: cleanupTimeout } = createTimeoutSignal(timeoutMs), onTimeoutAbort = () => controller.abort();
|
|
736
|
+
return timeoutSignal.aborted ? controller.abort() : timeoutSignal.addEventListener("abort", onTimeoutAbort, { once: true }), {
|
|
737
|
+
signal: controller.signal,
|
|
738
|
+
cleanup: () => {
|
|
739
|
+
cleanupTimeout(), externalSignal && externalSignal.removeEventListener("abort", onAbort), timeoutSignal.removeEventListener("abort", onTimeoutAbort);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
async function fetchSolanaTokenListMetadata(mints, options = {}) {
|
|
744
|
+
let results = /* @__PURE__ */ new Map();
|
|
745
|
+
if (!mints.length) return results;
|
|
746
|
+
let seen = /* @__PURE__ */ new Set(), uncached = [];
|
|
747
|
+
for (let mint of mints) {
|
|
748
|
+
let normalized = mint?.trim();
|
|
749
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
750
|
+
seen.add(normalized);
|
|
751
|
+
let cached = getCachedTokenListMetadata(normalized);
|
|
752
|
+
cached ? results.set(normalized, cached) : uncached.push(normalized);
|
|
753
|
+
}
|
|
754
|
+
if (!uncached.length) return results;
|
|
755
|
+
let timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS, apiUrl = getTokenListApiUrl(options.cluster);
|
|
756
|
+
for (let i = 0; i < uncached.length && !options.signal?.aborted; i += MAX_ADDRESSES_PER_REQUEST) {
|
|
757
|
+
let batch = uncached.slice(i, i + MAX_ADDRESSES_PER_REQUEST), { signal, cleanup } = createLinkedSignal(options.signal, timeoutMs);
|
|
758
|
+
try {
|
|
759
|
+
let response = await fetch(apiUrl, {
|
|
760
|
+
method: "POST",
|
|
761
|
+
headers: { "Content-Type": "application/json" },
|
|
762
|
+
body: JSON.stringify({ addresses: batch }),
|
|
763
|
+
signal
|
|
764
|
+
});
|
|
765
|
+
if (!response.ok) {
|
|
766
|
+
console.warn("[token-list] Solana Token List API error:", response.status, response.statusText);
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
let data = await response.json();
|
|
770
|
+
if (!data?.content?.length) continue;
|
|
771
|
+
for (let item of data.content)
|
|
772
|
+
item?.address && (results.set(item.address, item), setCachedTokenListMetadata(item.address, item));
|
|
773
|
+
} catch (error) {
|
|
774
|
+
console.warn("[token-list] Solana Token List API failed:", error);
|
|
775
|
+
} finally {
|
|
776
|
+
cleanup();
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return results;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// src/utils/image.ts
|
|
783
|
+
function transformImageUrl(url, imageProxy) {
|
|
784
|
+
if (url)
|
|
785
|
+
return imageProxy ? `${imageProxy}${encodeURIComponent(url)}` : url;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/hooks/use-transactions.ts
|
|
369
789
|
function formatDate(timestamp) {
|
|
370
790
|
if (!timestamp)
|
|
371
791
|
return { date: "Unknown", time: "" };
|
|
@@ -377,6 +797,45 @@ function formatDate(timestamp) {
|
|
|
377
797
|
});
|
|
378
798
|
return { date: formattedDate, time: formattedTime };
|
|
379
799
|
}
|
|
800
|
+
var KNOWN_PROGRAMS = {
|
|
801
|
+
"11111111111111111111111111111111": "System Program",
|
|
802
|
+
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA: "Token Program",
|
|
803
|
+
ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL: "Associated Token",
|
|
804
|
+
JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4: "Jupiter",
|
|
805
|
+
whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc: "Orca Whirlpool",
|
|
806
|
+
"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8": "Raydium AMM",
|
|
807
|
+
Stake11111111111111111111111111111111111111: "Stake Program",
|
|
808
|
+
metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s: "Metaplex"
|
|
809
|
+
}, DEFAULT_IGNORED_PROGRAM_IDS = /* @__PURE__ */ new Set([
|
|
810
|
+
"11111111111111111111111111111111",
|
|
811
|
+
// System Program
|
|
812
|
+
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
|
813
|
+
// Token Program
|
|
814
|
+
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
|
815
|
+
// Associated Token
|
|
816
|
+
]);
|
|
817
|
+
function resolveProgramName(programId, programLabels) {
|
|
818
|
+
return programLabels?.[programId] ?? KNOWN_PROGRAMS[programId];
|
|
819
|
+
}
|
|
820
|
+
function pickPrimaryProgramId(programIds) {
|
|
821
|
+
for (let id of programIds)
|
|
822
|
+
if (!DEFAULT_IGNORED_PROGRAM_IDS.has(id)) return id;
|
|
823
|
+
return programIds.values().next().value;
|
|
824
|
+
}
|
|
825
|
+
function getParsedInstructionTypes(message) {
|
|
826
|
+
if (!Array.isArray(message.instructions)) return;
|
|
827
|
+
let types = [];
|
|
828
|
+
for (let ix of message.instructions) {
|
|
829
|
+
if (!ix || typeof ix != "object") continue;
|
|
830
|
+
let parsed = ix.parsed;
|
|
831
|
+
if (!parsed || typeof parsed != "object" || !("type" in parsed)) continue;
|
|
832
|
+
let t = parsed.type;
|
|
833
|
+
if (typeof t == "string" && (types.push(t), types.length >= 10))
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
let unique = [...new Set(types)];
|
|
837
|
+
return unique.length ? unique : void 0;
|
|
838
|
+
}
|
|
380
839
|
function isAccountKey(value) {
|
|
381
840
|
return typeof value == "object" && value !== null && "pubkey" in value && typeof value.pubkey == "string";
|
|
382
841
|
}
|
|
@@ -401,6 +860,13 @@ function isTransactionWithMeta(value) {
|
|
|
401
860
|
function isTransactionMessage(value) {
|
|
402
861
|
return typeof value == "object" && value !== null && "accountKeys" in value && Array.isArray(value.accountKeys);
|
|
403
862
|
}
|
|
863
|
+
function coerceMaybeAddressString(value) {
|
|
864
|
+
if (typeof value == "string") return value;
|
|
865
|
+
if (value && typeof value == "object") {
|
|
866
|
+
let str = String(value);
|
|
867
|
+
if (str && str !== "[object Object]") return str;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
404
870
|
function getAccountKeys(message) {
|
|
405
871
|
return Array.isArray(message.accountKeys) ? message.accountKeys.map((key) => typeof key == "string" ? key : isAccountKey(key) ? key.pubkey : "").filter(Boolean) : [];
|
|
406
872
|
}
|
|
@@ -432,12 +898,12 @@ function parseTokenTransfers(meta, accountKeys, walletAddress) {
|
|
|
432
898
|
return null;
|
|
433
899
|
let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
|
|
434
900
|
if (!isTokenBalance(balance)) return false;
|
|
435
|
-
let accountKey = accountKeys[balance.accountIndex];
|
|
436
|
-
return accountKey && accountKey.trim() === walletAddress.trim() ||
|
|
901
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
902
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
437
903
|
}), ourPostTokens = postTokenBalances.filter((balance) => {
|
|
438
904
|
if (!isTokenBalance(balance)) return false;
|
|
439
|
-
let accountKey = accountKeys[balance.accountIndex];
|
|
440
|
-
return accountKey && accountKey.trim() === walletAddress.trim() ||
|
|
905
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
906
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
441
907
|
}), allMints = /* @__PURE__ */ new Set();
|
|
442
908
|
for (let token of ourPreTokens)
|
|
443
909
|
isTokenBalance(token) && allMints.add(token.mint);
|
|
@@ -481,6 +947,52 @@ function parseTokenTransfers(meta, accountKeys, walletAddress) {
|
|
|
481
947
|
}
|
|
482
948
|
return null;
|
|
483
949
|
}
|
|
950
|
+
function parseTokenAccountClosure(meta, accountKeys, walletAddress) {
|
|
951
|
+
if (!isTransactionMeta(meta))
|
|
952
|
+
return null;
|
|
953
|
+
let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
|
|
954
|
+
if (!isTokenBalance(balance)) return false;
|
|
955
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
956
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
957
|
+
}), ourPostTokens = postTokenBalances.filter((balance) => {
|
|
958
|
+
if (!isTokenBalance(balance)) return false;
|
|
959
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
960
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
961
|
+
}), postKeys = /* @__PURE__ */ new Set();
|
|
962
|
+
for (let token of ourPostTokens)
|
|
963
|
+
isTokenBalance(token) && postKeys.add(`${token.accountIndex}:${token.mint}`);
|
|
964
|
+
for (let token of ourPreTokens) {
|
|
965
|
+
if (!isTokenBalance(token)) continue;
|
|
966
|
+
let key = `${token.accountIndex}:${token.mint}`;
|
|
967
|
+
if (!postKeys.has(key))
|
|
968
|
+
return { tokenMint: token.mint };
|
|
969
|
+
}
|
|
970
|
+
return null;
|
|
971
|
+
}
|
|
972
|
+
function parseSwapTokens(meta, accountKeys, walletAddress, solChange) {
|
|
973
|
+
if (!isTransactionMeta(meta))
|
|
974
|
+
return {};
|
|
975
|
+
let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
|
|
976
|
+
if (!isTokenBalance(balance)) return false;
|
|
977
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
978
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
979
|
+
}), ourPostTokens = postTokenBalances.filter((balance) => {
|
|
980
|
+
if (!isTokenBalance(balance)) return false;
|
|
981
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
982
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
983
|
+
}), allMints = /* @__PURE__ */ new Set();
|
|
984
|
+
for (let token of ourPreTokens)
|
|
985
|
+
isTokenBalance(token) && allMints.add(token.mint);
|
|
986
|
+
for (let token of ourPostTokens)
|
|
987
|
+
isTokenBalance(token) && allMints.add(token.mint);
|
|
988
|
+
let fromToken, toToken;
|
|
989
|
+
for (let mint of allMints) {
|
|
990
|
+
let preBal = ourPreTokens.find((b) => isTokenBalance(b) && b.mint === mint), postBal = ourPostTokens.find((b) => isTokenBalance(b) && b.mint === mint), preAmount = isTokenBalance(preBal) && isUiTokenAmount(preBal.uiTokenAmount) ? Number(preBal.uiTokenAmount.amount) : 0, change = (isTokenBalance(postBal) && isUiTokenAmount(postBal.uiTokenAmount) ? Number(postBal.uiTokenAmount.amount) : 0) - preAmount;
|
|
991
|
+
change < 0 && !fromToken ? fromToken = { mint } : change > 0 && !toToken && (toToken = { mint });
|
|
992
|
+
}
|
|
993
|
+
let WRAPPED_SOL_MINT = "So11111111111111111111111111111111111111112";
|
|
994
|
+
return solChange < -1e-3 && !fromToken ? fromToken = { mint: WRAPPED_SOL_MINT } : solChange > 1e-3 && !toToken && (toToken = { mint: WRAPPED_SOL_MINT }), { fromToken, toToken };
|
|
995
|
+
}
|
|
484
996
|
function formatAmount(tokenAmount, tokenDecimals, direction, solChange) {
|
|
485
997
|
if (tokenAmount !== void 0 && tokenDecimals !== void 0 && direction !== void 0) {
|
|
486
998
|
let sign = direction === "in" ? "+" : "-", maxDecimals = Math.min(tokenDecimals, 6);
|
|
@@ -489,52 +1001,59 @@ function formatAmount(tokenAmount, tokenDecimals, direction, solChange) {
|
|
|
489
1001
|
if (solChange !== 0)
|
|
490
1002
|
return `${solChange > 0 ? "+" : ""}${solChange.toFixed(4)} SOL`;
|
|
491
1003
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
return imageProxy ? `${imageProxy}${encodeURIComponent(url)}` : url;
|
|
1004
|
+
function throwIfAborted(signal) {
|
|
1005
|
+
if (signal?.aborted)
|
|
1006
|
+
throw new DOMException("Query aborted", "AbortError");
|
|
496
1007
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
method: "POST",
|
|
509
|
-
headers: { "Content-Type": "application/json" },
|
|
510
|
-
body: JSON.stringify({ addresses: uncachedMints }),
|
|
511
|
-
signal: AbortSignal.timeout(5e3)
|
|
512
|
-
});
|
|
513
|
-
if (!response.ok) return results;
|
|
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);
|
|
1008
|
+
function clampInt(value, min, max) {
|
|
1009
|
+
return Number.isFinite(value) ? Math.min(max, Math.max(min, Math.floor(value))) : min;
|
|
1010
|
+
}
|
|
1011
|
+
async function mapWithConcurrency(inputs, worker, options) {
|
|
1012
|
+
let concurrency = clampInt(options.concurrency, 1, 32), results = new Array(inputs.length), nextIndex = 0;
|
|
1013
|
+
async function run() {
|
|
1014
|
+
for (; ; ) {
|
|
1015
|
+
throwIfAborted(options.signal);
|
|
1016
|
+
let index = nextIndex;
|
|
1017
|
+
if (nextIndex += 1, index >= inputs.length) return;
|
|
1018
|
+
results[index] = await worker(inputs[index], index);
|
|
518
1019
|
}
|
|
519
|
-
} catch (error) {
|
|
520
|
-
console.warn("[useTransactions] Failed to fetch token metadata:", error);
|
|
521
1020
|
}
|
|
1021
|
+
let runners = Array.from({ length: Math.min(concurrency, inputs.length) }, () => run());
|
|
1022
|
+
return await Promise.all(runners), results;
|
|
1023
|
+
}
|
|
1024
|
+
async function fetchTransactionTokenMetadata(mints, options = {}) {
|
|
1025
|
+
let results = /* @__PURE__ */ new Map();
|
|
1026
|
+
if (!mints.length) return results;
|
|
1027
|
+
let tokenList = await fetchSolanaTokenListMetadata(mints, {
|
|
1028
|
+
timeoutMs: 5e3,
|
|
1029
|
+
signal: options.signal,
|
|
1030
|
+
cluster: options.cluster
|
|
1031
|
+
});
|
|
1032
|
+
for (let [mint, meta] of tokenList)
|
|
1033
|
+
results.set(mint, { symbol: meta.symbol, icon: meta.logoURI });
|
|
522
1034
|
return results;
|
|
523
1035
|
}
|
|
524
1036
|
function useTransactions(options = {}) {
|
|
525
|
-
let {
|
|
526
|
-
|
|
527
|
-
|
|
1037
|
+
let {
|
|
1038
|
+
enabled = true,
|
|
1039
|
+
limit = 10,
|
|
1040
|
+
autoRefresh = false,
|
|
1041
|
+
refreshInterval = 6e4,
|
|
1042
|
+
fetchDetails = true,
|
|
1043
|
+
detailsConcurrency = 6,
|
|
1044
|
+
staleTimeMs = 0,
|
|
1045
|
+
cacheTimeMs = 300 * 1e3,
|
|
1046
|
+
// 5 minutes
|
|
1047
|
+
refetchOnMount = "stale",
|
|
1048
|
+
client: clientOverride
|
|
1049
|
+
} = options, { address: address$1, connected } = useAccount(), { cluster } = useCluster(), { client: providerClient } = useSolanaClient(), connectorClient = useConnectorClient(), [paginatedTransactions, setPaginatedTransactions] = useState([]), [isPaginationLoading, setIsPaginationLoading] = useState(false), [hasMore, setHasMore] = useState(true), beforeSignatureRef = useRef(void 0), rpcClient = clientOverride ?? providerClient, connectorConfig = connectorClient?.getConfig(), imageProxy = connectorConfig?.imageProxy, programLabels = connectorConfig?.programLabels, parseTransaction = useCallback(
|
|
528
1050
|
(tx, walletAddress, sig, blockTime, slot, err, explorerUrl) => {
|
|
529
1051
|
let { date, time } = formatDate(blockTime), baseInfo = {
|
|
530
1052
|
signature: sig,
|
|
531
1053
|
blockTime,
|
|
532
1054
|
slot,
|
|
533
1055
|
status: err ? "failed" : "success",
|
|
534
|
-
error: err ? JSON.stringify(
|
|
535
|
-
err,
|
|
536
|
-
(_key, value) => typeof value == "bigint" ? value.toString() : value
|
|
537
|
-
) : void 0,
|
|
1056
|
+
error: err ? JSON.stringify(err, (_key, value) => typeof value == "bigint" ? value.toString() : value) : void 0,
|
|
538
1057
|
type: "unknown",
|
|
539
1058
|
formattedDate: date,
|
|
540
1059
|
formattedTime: time,
|
|
@@ -549,24 +1068,13 @@ function useTransactions(options = {}) {
|
|
|
549
1068
|
let { message } = transaction;
|
|
550
1069
|
if (!isTransactionMessage(message))
|
|
551
1070
|
return baseInfo;
|
|
552
|
-
let accountKeys = getAccountKeys(message), walletIndex = accountKeys.findIndex((
|
|
1071
|
+
let accountKeys = getAccountKeys(message), walletIndex = accountKeys.findIndex((key2) => key2.trim() === walletAddress.trim());
|
|
553
1072
|
if (walletIndex === -1)
|
|
554
1073
|
return baseInfo;
|
|
555
|
-
let { balanceChange, solChange } = parseSolChange(meta, walletIndex), programIds = detectProgramIds(message, accountKeys), hasJupiter = programIds.has("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"), hasOrca = programIds.has("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"), hasRaydium = programIds.has("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"), hasStake = programIds.has("Stake11111111111111111111111111111111111111"), hasMetaplex = programIds.has("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), hasSystemProgram = programIds.has("11111111111111111111111111111111"), hasTokenProgram = programIds.has("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), type = "unknown", direction, counterparty, tokenMint, tokenAmount, tokenDecimals;
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
type = "stake";
|
|
560
|
-
else if (hasMetaplex)
|
|
561
|
-
type = "nft";
|
|
562
|
-
else if (hasSystemProgram && Math.abs(balanceChange) > 0)
|
|
563
|
-
type = balanceChange > 0 ? "received" : "sent", direction = balanceChange > 0 ? "in" : "out", tokenMint = "So11111111111111111111111111111111111111112", accountKeys.length >= 2 && (counterparty = accountKeys.find(
|
|
564
|
-
(key, idx) => idx !== walletIndex && key !== "11111111111111111111111111111111"
|
|
565
|
-
));
|
|
566
|
-
else if (hasTokenProgram) {
|
|
567
|
-
let tokenTransfer = parseTokenTransfers(meta, accountKeys, walletAddress);
|
|
568
|
-
tokenTransfer && (type = tokenTransfer.type, direction = tokenTransfer.direction, tokenMint = tokenTransfer.tokenMint, tokenAmount = tokenTransfer.tokenAmount, tokenDecimals = tokenTransfer.tokenDecimals);
|
|
569
|
-
} else programIds.size > 0 && (type = "program");
|
|
1074
|
+
let { balanceChange, solChange } = parseSolChange(meta, walletIndex), programIds = detectProgramIds(message, accountKeys), hasJupiter = programIds.has("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"), hasOrca = programIds.has("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"), hasRaydium = programIds.has("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"), hasStake = programIds.has("Stake11111111111111111111111111111111111111"), hasMetaplex = programIds.has("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"), hasSystemProgram = programIds.has("11111111111111111111111111111111"), hasTokenProgram = programIds.has("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), tokenTransfer = hasTokenProgram ? parseTokenTransfers(meta, accountKeys, walletAddress) : null, tokenAccountClosure = hasTokenProgram ? parseTokenAccountClosure(meta, accountKeys, walletAddress) : null, inferredSwapTokens = parseSwapTokens(meta, accountKeys, walletAddress, solChange), inferredSwapFromMint = inferredSwapTokens.fromToken?.mint, inferredSwapToMint = inferredSwapTokens.toToken?.mint, hasNonTrivialProgram = [...programIds].some((id) => !DEFAULT_IGNORED_PROGRAM_IDS.has(id)), hasInferredSwap = !!(inferredSwapFromMint && inferredSwapToMint) && inferredSwapFromMint !== inferredSwapToMint && hasNonTrivialProgram && !tokenAccountClosure, programId = pickPrimaryProgramId(programIds), programName = programId ? resolveProgramName(programId, programLabels) : void 0, programIdsArray = [...programIds], instructionTypes = getParsedInstructionTypes(message), instructionCount = Array.isArray(message.instructions) ? message.instructions.length : void 0, type = "unknown", direction, counterparty, tokenMint, tokenAmount, tokenDecimals, swapFromToken, swapToToken;
|
|
1075
|
+
hasJupiter || hasOrca || hasRaydium ? (type = "swap", inferredSwapTokens.fromToken && (swapFromToken = { mint: inferredSwapTokens.fromToken.mint }), inferredSwapTokens.toToken && (swapToToken = { mint: inferredSwapTokens.toToken.mint })) : hasStake ? type = "stake" : hasMetaplex ? type = "nft" : hasInferredSwap ? (type = "swap", swapFromToken = { mint: inferredSwapFromMint }, swapToToken = { mint: inferredSwapToMint }) : tokenTransfer ? (type = tokenTransfer.type, direction = tokenTransfer.direction, tokenMint = tokenTransfer.tokenMint, tokenAmount = tokenTransfer.tokenAmount, tokenDecimals = tokenTransfer.tokenDecimals) : tokenAccountClosure ? (type = "tokenAccountClosed", tokenMint = tokenAccountClosure.tokenMint, direction = solChange > 0 ? "in" : void 0) : hasSystemProgram && Math.abs(balanceChange) > 0 ? (type = balanceChange > 0 ? "received" : "sent", direction = balanceChange > 0 ? "in" : "out", tokenMint = "So11111111111111111111111111111111111111112", accountKeys.length >= 2 && (counterparty = accountKeys.find(
|
|
1076
|
+
(key2, idx) => idx !== walletIndex && key2 !== "11111111111111111111111111111111"
|
|
1077
|
+
))) : programIds.size > 0 && (type = "program");
|
|
570
1078
|
let formattedAmount = formatAmount(tokenAmount, tokenDecimals, direction, solChange);
|
|
571
1079
|
return {
|
|
572
1080
|
...baseInfo,
|
|
@@ -575,131 +1083,189 @@ function useTransactions(options = {}) {
|
|
|
575
1083
|
amount: tokenAmount ?? Math.abs(solChange),
|
|
576
1084
|
formattedAmount,
|
|
577
1085
|
tokenMint,
|
|
578
|
-
counterparty: counterparty ? `${counterparty.slice(0, 4)}...${counterparty.slice(-4)}` : void 0
|
|
1086
|
+
counterparty: counterparty ? `${counterparty.slice(0, 4)}...${counterparty.slice(-4)}` : void 0,
|
|
1087
|
+
swapFromToken,
|
|
1088
|
+
swapToToken,
|
|
1089
|
+
programId,
|
|
1090
|
+
programName,
|
|
1091
|
+
programIds: programIdsArray.length ? programIdsArray : void 0,
|
|
1092
|
+
instructionTypes,
|
|
1093
|
+
instructionCount
|
|
579
1094
|
};
|
|
580
1095
|
} catch (parseError) {
|
|
581
1096
|
return console.warn("Failed to parse transaction:", parseError), baseInfo;
|
|
582
1097
|
}
|
|
583
1098
|
},
|
|
584
|
-
[]
|
|
585
|
-
),
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
1099
|
+
[programLabels]
|
|
1100
|
+
), key = useMemo(() => {
|
|
1101
|
+
if (!enabled || !connected || !address$1 || !rpcClient || !cluster) return null;
|
|
1102
|
+
let rpcUrl = rpcClient.urlOrMoniker instanceof URL ? rpcClient.urlOrMoniker.toString() : String(rpcClient.urlOrMoniker);
|
|
1103
|
+
return JSON.stringify(["wallet-transactions", rpcUrl, address$1, cluster.id, limit, fetchDetails]);
|
|
1104
|
+
}, [enabled, connected, address$1, rpcClient, cluster, limit, fetchDetails]);
|
|
1105
|
+
useEffect(() => {
|
|
1106
|
+
beforeSignatureRef.current = void 0, setPaginatedTransactions([]), setIsPaginationLoading(false), setHasMore(true);
|
|
1107
|
+
}, [key]);
|
|
1108
|
+
let fetchAndEnrichTransactions = useCallback(
|
|
1109
|
+
async (beforeSignature, currentCluster, signal) => {
|
|
1110
|
+
if (!rpcClient || !address$1)
|
|
1111
|
+
return { transactions: [], hasMore: false };
|
|
1112
|
+
throwIfAborted(signal);
|
|
1113
|
+
let rpc = rpcClient.rpc, walletAddress = address(address$1), signaturesResult = await rpc.getSignaturesForAddress(walletAddress, {
|
|
1114
|
+
limit,
|
|
1115
|
+
...beforeSignature ? { before: signature(beforeSignature) } : {}
|
|
1116
|
+
}).send();
|
|
1117
|
+
throwIfAborted(signal);
|
|
1118
|
+
let newTransactions;
|
|
1119
|
+
if (fetchDetails && signaturesResult.length > 0) {
|
|
1120
|
+
let txDetails = await mapWithConcurrency(
|
|
1121
|
+
signaturesResult,
|
|
1122
|
+
async (sig) => rpc.getTransaction(signature(String(sig.signature)), {
|
|
1123
|
+
encoding: "jsonParsed",
|
|
1124
|
+
maxSupportedTransactionVersion: 0
|
|
1125
|
+
}).send().catch(() => null),
|
|
1126
|
+
{ concurrency: detailsConcurrency, signal }
|
|
1127
|
+
);
|
|
1128
|
+
throwIfAborted(signal), newTransactions = signaturesResult.map((sig, idx) => {
|
|
1129
|
+
let blockTimeNum = sig.blockTime ? Number(sig.blockTime) : null, tx = txDetails[idx];
|
|
1130
|
+
return parseTransaction(
|
|
1131
|
+
tx,
|
|
1132
|
+
address$1,
|
|
1133
|
+
String(sig.signature),
|
|
1134
|
+
blockTimeNum,
|
|
1135
|
+
Number(sig.slot),
|
|
1136
|
+
sig.err,
|
|
1137
|
+
getTransactionUrl(String(sig.signature), currentCluster)
|
|
1138
|
+
);
|
|
1139
|
+
});
|
|
1140
|
+
} else
|
|
1141
|
+
newTransactions = signaturesResult.map((sig) => {
|
|
1142
|
+
let blockTimeNum = sig.blockTime ? Number(sig.blockTime) : null, { date, time } = formatDate(blockTimeNum);
|
|
1143
|
+
return {
|
|
1144
|
+
signature: String(sig.signature),
|
|
1145
|
+
blockTime: blockTimeNum,
|
|
1146
|
+
slot: Number(sig.slot),
|
|
1147
|
+
status: sig.err ? "failed" : "success",
|
|
1148
|
+
error: sig.err ? JSON.stringify(sig.err) : void 0,
|
|
1149
|
+
type: "unknown",
|
|
1150
|
+
formattedDate: date,
|
|
1151
|
+
formattedTime: time,
|
|
1152
|
+
explorerUrl: getTransactionUrl(String(sig.signature), currentCluster)
|
|
1153
|
+
};
|
|
1154
|
+
});
|
|
1155
|
+
let mintsToFetch = [
|
|
1156
|
+
.../* @__PURE__ */ new Set([
|
|
1157
|
+
...newTransactions.filter((tx) => tx.tokenMint).map((tx) => tx.tokenMint),
|
|
1158
|
+
...newTransactions.filter((tx) => tx.swapFromToken?.mint).map((tx) => tx.swapFromToken.mint),
|
|
1159
|
+
...newTransactions.filter((tx) => tx.swapToToken?.mint).map((tx) => tx.swapToToken.mint)
|
|
1160
|
+
])
|
|
1161
|
+
];
|
|
1162
|
+
if (mintsToFetch.length > 0) {
|
|
1163
|
+
throwIfAborted(signal);
|
|
1164
|
+
let tokenMetadata = await fetchTransactionTokenMetadata(mintsToFetch, {
|
|
1165
|
+
signal,
|
|
1166
|
+
cluster: getClusterType(currentCluster)
|
|
1167
|
+
});
|
|
1168
|
+
tokenMetadata.size > 0 && (newTransactions = newTransactions.map((tx) => {
|
|
1169
|
+
let enrichedTx = { ...tx };
|
|
1170
|
+
if (tx.tokenMint && tokenMetadata.has(tx.tokenMint)) {
|
|
1171
|
+
let meta = tokenMetadata.get(tx.tokenMint);
|
|
1172
|
+
enrichedTx = {
|
|
1173
|
+
...enrichedTx,
|
|
1174
|
+
tokenSymbol: meta.symbol,
|
|
1175
|
+
tokenIcon: transformImageUrl(meta.icon, imageProxy),
|
|
1176
|
+
formattedAmount: tx.formattedAmount ? `${tx.formattedAmount} ${meta.symbol}` : tx.formattedAmount
|
|
629
1177
|
};
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
return {
|
|
640
|
-
...tx,
|
|
641
|
-
tokenSymbol: meta.symbol,
|
|
642
|
-
tokenIcon: transformImageUrl(meta.icon, imageProxy),
|
|
643
|
-
// Update formatted amount with symbol
|
|
644
|
-
formattedAmount: tx.formattedAmount ? `${tx.formattedAmount} ${meta.symbol}` : tx.formattedAmount
|
|
645
|
-
};
|
|
1178
|
+
}
|
|
1179
|
+
if (tx.swapFromToken?.mint && tokenMetadata.has(tx.swapFromToken.mint)) {
|
|
1180
|
+
let meta = tokenMetadata.get(tx.swapFromToken.mint);
|
|
1181
|
+
enrichedTx = {
|
|
1182
|
+
...enrichedTx,
|
|
1183
|
+
swapFromToken: {
|
|
1184
|
+
...tx.swapFromToken,
|
|
1185
|
+
symbol: meta.symbol,
|
|
1186
|
+
icon: transformImageUrl(meta.icon, imageProxy)
|
|
646
1187
|
}
|
|
647
|
-
|
|
648
|
-
});
|
|
649
|
-
setTransactions(loadMore ? (prev) => [...prev.slice(0, -newTransactions.length), ...enrichedTransactions] : enrichedTransactions);
|
|
1188
|
+
};
|
|
650
1189
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1190
|
+
if (tx.swapToToken?.mint && tokenMetadata.has(tx.swapToToken.mint)) {
|
|
1191
|
+
let meta = tokenMetadata.get(tx.swapToToken.mint);
|
|
1192
|
+
enrichedTx = {
|
|
1193
|
+
...enrichedTx,
|
|
1194
|
+
swapToToken: {
|
|
1195
|
+
...tx.swapToToken,
|
|
1196
|
+
symbol: meta.symbol,
|
|
1197
|
+
icon: transformImageUrl(meta.icon, imageProxy)
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
656
1200
|
}
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
|
|
1201
|
+
return enrichedTx;
|
|
1202
|
+
}));
|
|
1203
|
+
}
|
|
1204
|
+
return {
|
|
1205
|
+
transactions: newTransactions,
|
|
1206
|
+
hasMore: newTransactions.length === limit
|
|
1207
|
+
};
|
|
1208
|
+
},
|
|
1209
|
+
[rpcClient, address$1, limit, fetchDetails, detailsConcurrency, parseTransaction, imageProxy]
|
|
1210
|
+
), queryFn = useCallback(
|
|
1211
|
+
async (signal) => {
|
|
1212
|
+
if (!connected || !address$1 || !rpcClient || !cluster)
|
|
1213
|
+
return [];
|
|
1214
|
+
throwIfAborted(signal);
|
|
1215
|
+
let result = await fetchAndEnrichTransactions(void 0, cluster, signal);
|
|
1216
|
+
return throwIfAborted(signal), result.transactions;
|
|
1217
|
+
},
|
|
1218
|
+
[connected, address$1, rpcClient, cluster, fetchAndEnrichTransactions]
|
|
1219
|
+
), {
|
|
1220
|
+
data: initialTransactions,
|
|
1221
|
+
error,
|
|
1222
|
+
isFetching: isInitialLoading,
|
|
1223
|
+
updatedAt,
|
|
1224
|
+
refetch: sharedRefetch,
|
|
1225
|
+
abort
|
|
1226
|
+
} = useSharedQuery(key, queryFn, {
|
|
1227
|
+
enabled,
|
|
1228
|
+
staleTimeMs,
|
|
1229
|
+
cacheTimeMs,
|
|
1230
|
+
refetchOnMount,
|
|
1231
|
+
refetchIntervalMs: autoRefresh ? refreshInterval : false
|
|
1232
|
+
});
|
|
1233
|
+
useEffect(() => {
|
|
1234
|
+
initialTransactions && (beforeSignatureRef.current = initialTransactions.length ? initialTransactions[initialTransactions.length - 1].signature : void 0, setHasMore(initialTransactions.length === limit), setPaginatedTransactions((prev) => prev.length ? [] : prev));
|
|
1235
|
+
}, [initialTransactions, limit]);
|
|
1236
|
+
let loadMoreFn = useCallback(async () => {
|
|
1237
|
+
if (!(!hasMore || isPaginationLoading || !cluster)) {
|
|
1238
|
+
setIsPaginationLoading(true);
|
|
1239
|
+
try {
|
|
1240
|
+
let result = await fetchAndEnrichTransactions(beforeSignatureRef.current, cluster);
|
|
1241
|
+
result.transactions.length > 0 && (beforeSignatureRef.current = result.transactions[result.transactions.length - 1].signature, setPaginatedTransactions((prev) => [...prev, ...result.transactions])), setHasMore(result.hasMore);
|
|
660
1242
|
} catch (err) {
|
|
661
|
-
|
|
1243
|
+
console.error("Failed to load more transactions:", err);
|
|
662
1244
|
} finally {
|
|
663
|
-
|
|
1245
|
+
setIsPaginationLoading(false);
|
|
664
1246
|
}
|
|
1247
|
+
}
|
|
1248
|
+
}, [hasMore, isPaginationLoading, cluster, fetchAndEnrichTransactions]), refetch = useCallback(
|
|
1249
|
+
async (opts) => {
|
|
1250
|
+
beforeSignatureRef.current = void 0, setPaginatedTransactions([]), setHasMore(true), await sharedRefetch(opts);
|
|
665
1251
|
},
|
|
666
|
-
[
|
|
667
|
-
),
|
|
668
|
-
|
|
669
|
-
}, [fetchTransactions]), loadMoreFn = useCallback(async () => {
|
|
670
|
-
hasMore && !isLoading && await fetchTransactions(true);
|
|
671
|
-
}, [hasMore, isLoading, fetchTransactions]);
|
|
672
|
-
return useEffect(() => {
|
|
673
|
-
let prevDeps = prevDepsRef.current, currentDeps = { connected, address: address$1, cluster };
|
|
674
|
-
(!prevDeps || prevDeps.connected !== connected || prevDeps.address !== address$1 || prevDeps.cluster !== cluster) && (prevDepsRef.current = currentDeps, beforeSignatureRef.current = void 0, fetchTransactions(false));
|
|
675
|
-
}, [connected, address$1, cluster, fetchTransactions]), useEffect(() => {
|
|
676
|
-
if (!connected || !autoRefresh) return;
|
|
677
|
-
let interval = setInterval(refetch, refreshInterval);
|
|
678
|
-
return () => clearInterval(interval);
|
|
679
|
-
}, [connected, autoRefresh, refreshInterval, refetch]), useMemo(
|
|
1252
|
+
[sharedRefetch]
|
|
1253
|
+
), transactions = useMemo(() => [...initialTransactions ?? [], ...paginatedTransactions], [initialTransactions, paginatedTransactions]), isLoading = isInitialLoading || isPaginationLoading, visibleError = updatedAt ? null : error;
|
|
1254
|
+
return useMemo(
|
|
680
1255
|
() => ({
|
|
681
1256
|
transactions,
|
|
682
1257
|
isLoading,
|
|
683
|
-
error,
|
|
1258
|
+
error: visibleError,
|
|
684
1259
|
hasMore,
|
|
685
1260
|
loadMore: loadMoreFn,
|
|
686
1261
|
refetch,
|
|
687
|
-
|
|
1262
|
+
abort,
|
|
1263
|
+
lastUpdated: updatedAt ? new Date(updatedAt) : null
|
|
688
1264
|
}),
|
|
689
|
-
[transactions, isLoading,
|
|
1265
|
+
[transactions, isLoading, visibleError, hasMore, loadMoreFn, refetch, abort, updatedAt]
|
|
690
1266
|
);
|
|
691
1267
|
}
|
|
692
|
-
|
|
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 {
|
|
1268
|
+
var CACHE_MAX_SIZE = 500, PRICE_CACHE_TTL = 6e4, STALE_CLEANUP_INTERVAL = 12e4, COINGECKO_DEFAULT_MAX_RETRIES = 3, COINGECKO_DEFAULT_BASE_DELAY = 1e3, COINGECKO_DEFAULT_MAX_TIMEOUT = 3e4, COINGECKO_API_BASE_URL = "https://api.coingecko.com/api/v3", LRUCache = class {
|
|
703
1269
|
constructor(maxSize, options) {
|
|
704
1270
|
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
705
1271
|
__publicField(this, "maxSize");
|
|
@@ -739,10 +1305,6 @@ var NATIVE_MINT = "So11111111111111111111111111111111111111112", CACHE_MAX_SIZE
|
|
|
739
1305
|
get size() {
|
|
740
1306
|
return this.cache.size;
|
|
741
1307
|
}
|
|
742
|
-
/**
|
|
743
|
-
* Prune stale entries based on TTL.
|
|
744
|
-
* Only works if getTtl and getTimestamp are provided.
|
|
745
|
-
*/
|
|
746
1308
|
pruneStale() {
|
|
747
1309
|
if (!this.getTtl || !this.getTimestamp) return 0;
|
|
748
1310
|
let now = Date.now(), pruned = 0;
|
|
@@ -767,27 +1329,6 @@ function stopCacheCleanup() {
|
|
|
767
1329
|
function clearTokenCaches() {
|
|
768
1330
|
metadataCache.clear(), priceCache.clear();
|
|
769
1331
|
}
|
|
770
|
-
async function fetchSolanaTokenMetadata(mints) {
|
|
771
|
-
let results = /* @__PURE__ */ new Map();
|
|
772
|
-
if (mints.length === 0) return results;
|
|
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
1332
|
function calculateBackoffDelay(attempt, baseDelay, retryAfter) {
|
|
792
1333
|
if (retryAfter !== void 0 && retryAfter > 0) {
|
|
793
1334
|
let jitter2 = Math.random() * 500;
|
|
@@ -823,26 +1364,17 @@ async function fetchCoinGeckoPrices(coingeckoIds, config) {
|
|
|
823
1364
|
let elapsedTime = Date.now() - startTime;
|
|
824
1365
|
if (elapsedTime >= maxTimeout) {
|
|
825
1366
|
console.warn(
|
|
826
|
-
`[useTokens] CoinGecko API: Total timeout (${maxTimeout}ms) exceeded after ${attempt} attempts
|
|
1367
|
+
`[useTokens] CoinGecko API: Total timeout (${maxTimeout}ms) exceeded after ${attempt} attempts.`
|
|
827
1368
|
);
|
|
828
1369
|
break;
|
|
829
1370
|
}
|
|
830
1371
|
let remainingTimeout = maxTimeout - elapsedTime, requestTimeout = Math.min(1e4, remainingTimeout), { signal, cleanup } = createTimeoutSignal(requestTimeout);
|
|
831
1372
|
try {
|
|
832
|
-
let response = await fetch(url, {
|
|
833
|
-
headers,
|
|
834
|
-
signal
|
|
835
|
-
});
|
|
1373
|
+
let response = await fetch(url, { headers, signal });
|
|
836
1374
|
if (cleanup(), response.status === 429) {
|
|
837
1375
|
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
|
-
);
|
|
1376
|
+
if (console.warn(`[useTokens] CoinGecko API rate limited (429). Waiting ${Math.round(delay)}ms.`), Date.now() - startTime + delay >= maxTimeout)
|
|
844
1377
|
break;
|
|
845
|
-
}
|
|
846
1378
|
await new Promise((resolve) => setTimeout(resolve, delay)), attempt++;
|
|
847
1379
|
continue;
|
|
848
1380
|
}
|
|
@@ -853,174 +1385,195 @@ async function fetchCoinGeckoPrices(coingeckoIds, config) {
|
|
|
853
1385
|
priceData?.usd !== void 0 && (results.set(id, priceData.usd), priceCache.set(id, { price: priceData.usd, timestamp: fetchTime }));
|
|
854
1386
|
return results;
|
|
855
1387
|
} catch (error) {
|
|
856
|
-
if (cleanup(), lastError = error,
|
|
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) {
|
|
1388
|
+
if (cleanup(), lastError = error, attempt < maxRetries) {
|
|
862
1389
|
let delay = calculateBackoffDelay(attempt, baseDelay);
|
|
863
1390
|
Date.now() - startTime + delay < maxTimeout && await new Promise((resolve) => setTimeout(resolve, delay));
|
|
864
1391
|
}
|
|
865
1392
|
attempt++;
|
|
866
1393
|
}
|
|
867
1394
|
}
|
|
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;
|
|
1395
|
+
return attempt > maxRetries && lastError && console.warn(`[useTokens] CoinGecko API: All attempts failed. Last error: ${lastError.message}`), results;
|
|
871
1396
|
}
|
|
872
|
-
async function fetchTokenMetadataHybrid(mints, coingeckoConfig) {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
let uncachedMints = [], now = Date.now();
|
|
876
|
-
for (let mint of mints) {
|
|
877
|
-
let cached = metadataCache.get(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);
|
|
879
|
-
}
|
|
880
|
-
if (uncachedMints.length === 0) return results;
|
|
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);
|
|
1397
|
+
async function fetchTokenMetadataHybrid(mints, coingeckoConfig, options) {
|
|
1398
|
+
if (mints.length === 0) return false;
|
|
1399
|
+
let now = Date.now(), mintsNeedingTokenList = [], staleCoingeckoIdToMint = /* @__PURE__ */ new Map();
|
|
884
1400
|
for (let mint of mints) {
|
|
885
1401
|
let cached = metadataCache.get(mint);
|
|
886
|
-
|
|
1402
|
+
if (!cached) {
|
|
1403
|
+
mintsNeedingTokenList.push(mint);
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
if (!cached.coingeckoId) continue;
|
|
1407
|
+
let priceEntry = priceCache.get(cached.coingeckoId);
|
|
1408
|
+
!!(priceEntry && now - priceEntry.timestamp < PRICE_CACHE_TTL) || staleCoingeckoIdToMint.set(cached.coingeckoId, mint);
|
|
887
1409
|
}
|
|
888
|
-
let
|
|
889
|
-
|
|
890
|
-
|
|
1410
|
+
let didUpdate = false, tokenListMetadata = await fetchSolanaTokenListMetadata(mintsNeedingTokenList, {
|
|
1411
|
+
timeoutMs: 1e4,
|
|
1412
|
+
cluster: options?.cluster
|
|
1413
|
+
});
|
|
1414
|
+
for (let [mint, meta] of tokenListMetadata) {
|
|
1415
|
+
let coingeckoId = meta.extensions?.coingeckoId, usdPrice = (coingeckoId ? priceCache.get(coingeckoId) : void 0)?.price, combined = {
|
|
891
1416
|
address: meta.address,
|
|
892
|
-
name: meta.address ===
|
|
1417
|
+
name: meta.address === NATIVE_SOL_MINT ? "Solana" : meta.name,
|
|
893
1418
|
symbol: meta.symbol,
|
|
894
1419
|
decimals: meta.decimals,
|
|
895
1420
|
logoURI: meta.logoURI,
|
|
896
1421
|
coingeckoId,
|
|
897
1422
|
usdPrice
|
|
898
|
-
};
|
|
899
|
-
|
|
1423
|
+
}, existing = metadataCache.get(mint);
|
|
1424
|
+
(!existing || existing.name !== combined.name || existing.symbol !== combined.symbol || existing.decimals !== combined.decimals || existing.logoURI !== combined.logoURI || existing.coingeckoId !== combined.coingeckoId || existing.usdPrice !== combined.usdPrice) && (didUpdate = true, metadataCache.set(mint, combined));
|
|
900
1425
|
}
|
|
1426
|
+
didUpdate && options?.onUpdate?.();
|
|
1427
|
+
let coingeckoIdToMint = new Map(staleCoingeckoIdToMint);
|
|
1428
|
+
for (let [mint, meta] of tokenListMetadata)
|
|
1429
|
+
meta.extensions?.coingeckoId && coingeckoIdToMint.set(meta.extensions.coingeckoId, mint);
|
|
1430
|
+
if (coingeckoIdToMint.size === 0)
|
|
1431
|
+
return didUpdate;
|
|
1432
|
+
let prices = await fetchCoinGeckoPrices([...coingeckoIdToMint.keys()], coingeckoConfig), didUpdatePrices = false;
|
|
901
1433
|
for (let [coingeckoId, mint] of coingeckoIdToMint) {
|
|
902
|
-
let cached =
|
|
1434
|
+
let cached = metadataCache.get(mint);
|
|
903
1435
|
if (cached) {
|
|
904
1436
|
let usdPrice = prices.get(coingeckoId);
|
|
905
|
-
usdPrice !== void 0 &&
|
|
1437
|
+
usdPrice !== void 0 && cached.usdPrice !== usdPrice && (didUpdate = true, didUpdatePrices = true, cached.usdPrice = usdPrice, metadataCache.set(mint, cached));
|
|
906
1438
|
}
|
|
907
1439
|
}
|
|
908
|
-
return
|
|
1440
|
+
return didUpdatePrices && options?.onUpdate?.(), didUpdate;
|
|
909
1441
|
}
|
|
910
1442
|
function formatBalance(amount, decimals) {
|
|
911
|
-
return (
|
|
912
|
-
|
|
913
|
-
maximumFractionDigits: Math.min(decimals, 6)
|
|
1443
|
+
return formatBigIntBalance(amount, decimals, {
|
|
1444
|
+
maxDecimals: Math.min(decimals, 6)
|
|
914
1445
|
});
|
|
915
1446
|
}
|
|
916
1447
|
function formatUsd(amount, decimals, usdPrice) {
|
|
917
|
-
return (
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1448
|
+
return formatBigIntUsd(amount, decimals, usdPrice);
|
|
1449
|
+
}
|
|
1450
|
+
function selectTokens(assets, address) {
|
|
1451
|
+
return {
|
|
1452
|
+
lamports: assets?.lamports ?? 0n,
|
|
1453
|
+
tokenAccounts: assets?.tokenAccounts ?? [],
|
|
1454
|
+
address
|
|
1455
|
+
};
|
|
923
1456
|
}
|
|
924
|
-
function
|
|
925
|
-
|
|
926
|
-
if (
|
|
927
|
-
let
|
|
928
|
-
return
|
|
1457
|
+
function sortByValueDesc(a, b) {
|
|
1458
|
+
let metadataSort = (b.logo ? 1 : 0) - (a.logo ? 1 : 0);
|
|
1459
|
+
if (metadataSort !== 0) return metadataSort;
|
|
1460
|
+
let aValue = Number(a.amount) / Math.pow(10, a.decimals) * (a.usdPrice ?? 0);
|
|
1461
|
+
return Number(b.amount) / Math.pow(10, b.decimals) * (b.usdPrice ?? 0) - aValue;
|
|
929
1462
|
}
|
|
930
1463
|
function useTokens(options = {}) {
|
|
931
1464
|
let {
|
|
1465
|
+
enabled = true,
|
|
932
1466
|
includeZeroBalance = false,
|
|
933
1467
|
autoRefresh = false,
|
|
934
1468
|
refreshInterval = 6e4,
|
|
935
1469
|
fetchMetadata = true,
|
|
936
|
-
includeNativeSol = true
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
tokenList.sort((a, b) => {
|
|
992
|
-
let metadataSort = (b.logo ? 1 : 0) - (a.logo ? 1 : 0);
|
|
993
|
-
if (metadataSort !== 0) return metadataSort;
|
|
994
|
-
let aValue = Number(a.amount) / Math.pow(10, a.decimals) * (a.usdPrice ?? 0);
|
|
995
|
-
return Number(b.amount) / Math.pow(10, b.decimals) * (b.usdPrice ?? 0) - aValue;
|
|
996
|
-
}), setTokens([...tokenList]);
|
|
997
|
-
}
|
|
998
|
-
} catch (err) {
|
|
999
|
-
setError(err), console.error("[useTokens] Failed to fetch tokens:", err);
|
|
1000
|
-
} finally {
|
|
1001
|
-
setIsLoading(false);
|
|
1002
|
-
}
|
|
1003
|
-
}, [connected, address$1, rpcClient, includeZeroBalance, fetchMetadata, includeNativeSol, imageProxy, coingeckoConfig]);
|
|
1470
|
+
includeNativeSol = true,
|
|
1471
|
+
staleTimeMs = 0,
|
|
1472
|
+
cacheTimeMs = 300 * 1e3,
|
|
1473
|
+
// 5 minutes
|
|
1474
|
+
refetchOnMount = "stale",
|
|
1475
|
+
client: clientOverride
|
|
1476
|
+
} = options, { address, connected } = useAccount(), { type: clusterType } = useCluster(), connectorConfig = useConnectorClient()?.getConfig(), imageProxy = connectorConfig?.imageProxy, coingeckoConfig = connectorConfig?.coingecko, selectFn = useCallback(
|
|
1477
|
+
(assets) => selectTokens(assets, address ?? ""),
|
|
1478
|
+
[address]
|
|
1479
|
+
), {
|
|
1480
|
+
data: selection,
|
|
1481
|
+
error,
|
|
1482
|
+
isFetching,
|
|
1483
|
+
updatedAt,
|
|
1484
|
+
refetch: sharedRefetch,
|
|
1485
|
+
abort
|
|
1486
|
+
} = useWalletAssets({
|
|
1487
|
+
enabled,
|
|
1488
|
+
staleTimeMs,
|
|
1489
|
+
cacheTimeMs,
|
|
1490
|
+
refetchOnMount,
|
|
1491
|
+
refetchIntervalMs: autoRefresh ? refreshInterval : false,
|
|
1492
|
+
client: clientOverride,
|
|
1493
|
+
select: selectFn
|
|
1494
|
+
}), lamports = selection?.lamports ?? 0n, tokenAccounts = selection?.tokenAccounts ?? [], walletAddress = selection?.address ?? "", baseTokens = useMemo(() => {
|
|
1495
|
+
let result = [];
|
|
1496
|
+
includeNativeSol && walletAddress && (includeZeroBalance || lamports > 0n) && result.push({
|
|
1497
|
+
mint: NATIVE_SOL_MINT,
|
|
1498
|
+
tokenAccount: walletAddress,
|
|
1499
|
+
amount: lamports,
|
|
1500
|
+
decimals: 9,
|
|
1501
|
+
formatted: formatBalance(lamports, 9),
|
|
1502
|
+
isFrozen: false,
|
|
1503
|
+
owner: walletAddress
|
|
1504
|
+
});
|
|
1505
|
+
for (let account of tokenAccounts)
|
|
1506
|
+
!includeZeroBalance && account.amount === 0n || result.push({
|
|
1507
|
+
mint: account.mint,
|
|
1508
|
+
tokenAccount: account.pubkey,
|
|
1509
|
+
amount: account.amount,
|
|
1510
|
+
decimals: account.decimals,
|
|
1511
|
+
formatted: formatBalance(account.amount, account.decimals),
|
|
1512
|
+
isFrozen: account.isFrozen,
|
|
1513
|
+
owner: account.owner,
|
|
1514
|
+
programId: account.programId
|
|
1515
|
+
});
|
|
1516
|
+
return result;
|
|
1517
|
+
}, [lamports, tokenAccounts, walletAddress, includeNativeSol, includeZeroBalance]), mints = useMemo(() => {
|
|
1518
|
+
let unique = /* @__PURE__ */ new Set();
|
|
1519
|
+
for (let token of baseTokens)
|
|
1520
|
+
unique.add(token.mint);
|
|
1521
|
+
return [...unique].sort();
|
|
1522
|
+
}, [baseTokens]), mintsKey = useMemo(() => mints.join(","), [mints]), [metadataVersion, setMetadataVersion] = useState(0);
|
|
1004
1523
|
useEffect(() => {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1524
|
+
if (!fetchMetadata || !mintsKey) return;
|
|
1525
|
+
let isMounted = true;
|
|
1526
|
+
return (async () => {
|
|
1527
|
+
try {
|
|
1528
|
+
let mintList = mintsKey.split(",");
|
|
1529
|
+
await fetchTokenMetadataHybrid(mintList, coingeckoConfig, {
|
|
1530
|
+
onUpdate: () => {
|
|
1531
|
+
isMounted && setMetadataVersion((v) => v + 1);
|
|
1532
|
+
},
|
|
1533
|
+
cluster: clusterType ?? void 0
|
|
1534
|
+
}), isMounted && setMetadataVersion((v) => v + 1);
|
|
1535
|
+
} catch (err) {
|
|
1536
|
+
console.error("[useTokens] Failed to fetch metadata:", err);
|
|
1537
|
+
}
|
|
1538
|
+
})(), () => {
|
|
1539
|
+
isMounted = false;
|
|
1540
|
+
};
|
|
1541
|
+
}, [mintsKey, fetchMetadata, coingeckoConfig, clusterType]);
|
|
1542
|
+
let tokens = useMemo(() => fetchMetadata ? baseTokens.map((token) => {
|
|
1543
|
+
let meta = metadataCache.get(token.mint);
|
|
1544
|
+
if (!meta) return token;
|
|
1545
|
+
let usdPrice = (meta.coingeckoId ? priceCache.get(meta.coingeckoId) : void 0)?.price ?? meta.usdPrice;
|
|
1546
|
+
return {
|
|
1547
|
+
...token,
|
|
1548
|
+
name: meta.name,
|
|
1549
|
+
symbol: meta.symbol,
|
|
1550
|
+
logo: transformImageUrl(meta.logoURI, imageProxy),
|
|
1551
|
+
usdPrice,
|
|
1552
|
+
formattedUsd: usdPrice ? formatUsd(token.amount, token.decimals, usdPrice) : void 0
|
|
1553
|
+
};
|
|
1554
|
+
}).sort(sortByValueDesc) : baseTokens.slice().sort(sortByValueDesc), [baseTokens, fetchMetadata, imageProxy, metadataVersion]), totalAccounts = tokenAccounts.length + (includeNativeSol ? 1 : 0);
|
|
1555
|
+
useEffect(() => (startCacheCleanup(), () => stopCacheCleanup()), []);
|
|
1011
1556
|
let wasConnectedRef = useRef(connected);
|
|
1012
|
-
|
|
1557
|
+
useEffect(() => {
|
|
1013
1558
|
wasConnectedRef.current && !connected && clearTokenCaches(), wasConnectedRef.current = connected;
|
|
1014
|
-
}, [connected])
|
|
1559
|
+
}, [connected]);
|
|
1560
|
+
let visibleError = updatedAt ? null : error, refetch = useCallback(
|
|
1561
|
+
async (opts) => {
|
|
1562
|
+
await sharedRefetch(opts);
|
|
1563
|
+
},
|
|
1564
|
+
[sharedRefetch]
|
|
1565
|
+
);
|
|
1566
|
+
return useMemo(
|
|
1015
1567
|
() => ({
|
|
1016
1568
|
tokens,
|
|
1017
|
-
isLoading,
|
|
1018
|
-
error,
|
|
1019
|
-
refetch
|
|
1020
|
-
|
|
1569
|
+
isLoading: isFetching,
|
|
1570
|
+
error: visibleError,
|
|
1571
|
+
refetch,
|
|
1572
|
+
abort,
|
|
1573
|
+
lastUpdated: updatedAt ? new Date(updatedAt) : null,
|
|
1021
1574
|
totalAccounts
|
|
1022
1575
|
}),
|
|
1023
|
-
[tokens,
|
|
1576
|
+
[tokens, isFetching, visibleError, refetch, abort, updatedAt, totalAccounts]
|
|
1024
1577
|
);
|
|
1025
1578
|
}
|
|
1026
1579
|
function DisconnectElement({
|
|
@@ -1864,6 +2417,8 @@ function BalanceElement({
|
|
|
1864
2417
|
BalanceElement.displayName = "BalanceElement";
|
|
1865
2418
|
function TransactionHistoryElement({
|
|
1866
2419
|
limit = 5,
|
|
2420
|
+
fetchDetails = true,
|
|
2421
|
+
detailsConcurrency,
|
|
1867
2422
|
showStatus = true,
|
|
1868
2423
|
showTime = true,
|
|
1869
2424
|
className,
|
|
@@ -1873,7 +2428,11 @@ function TransactionHistoryElement({
|
|
|
1873
2428
|
render,
|
|
1874
2429
|
renderItem
|
|
1875
2430
|
}) {
|
|
1876
|
-
let { transactions, isLoading, error, hasMore, loadMore, refetch } = useTransactions({
|
|
2431
|
+
let { transactions, isLoading, error, hasMore, loadMore, refetch } = useTransactions({
|
|
2432
|
+
limit,
|
|
2433
|
+
fetchDetails,
|
|
2434
|
+
detailsConcurrency
|
|
2435
|
+
});
|
|
1877
2436
|
if (render)
|
|
1878
2437
|
return /* @__PURE__ */ jsx(Fragment, { children: render({ transactions, isLoading, error, hasMore, loadMore, refetch }) });
|
|
1879
2438
|
let statusIcon = (status) => /* @__PURE__ */ jsx(
|
|
@@ -2180,6 +2739,6 @@ function TokenListElement({
|
|
|
2180
2739
|
}
|
|
2181
2740
|
TokenListElement.displayName = "TokenListElement";
|
|
2182
2741
|
|
|
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 };
|
|
2184
|
-
//# sourceMappingURL=chunk-
|
|
2185
|
-
//# sourceMappingURL=chunk-
|
|
2742
|
+
export { AccountElement, BalanceElement, ClusterElement, ConnectorProvider, DisconnectElement, TokenListElement, TransactionHistoryElement, UnifiedProvider, WalletListElement, clearSharedQueryCache, invalidateSharedQuery, useAccount, useBalance, useCluster, useConnector, useConnectorClient, useGillSolanaClient, useGillTransactionSigner, useKitTransactionSigner, useSolanaClient, useTokens, useTransactionPreparer, useTransactionSigner, useTransactions, useWalletInfo };
|
|
2743
|
+
//# sourceMappingURL=chunk-JK47EFJT.mjs.map
|
|
2744
|
+
//# sourceMappingURL=chunk-JK47EFJT.mjs.map
|