@solana/connector 0.1.6 → 0.1.8
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 +122 -44
- package/dist/{chunk-VMSZJPR5.mjs → chunk-6F6M6L7R.mjs} +152 -173
- package/dist/chunk-6F6M6L7R.mjs.map +1 -0
- package/dist/{chunk-NQXK7PGX.js → chunk-AOIXHVRH.js} +82 -235
- package/dist/chunk-AOIXHVRH.js.map +1 -0
- package/dist/chunk-DSUCH44G.js +678 -0
- package/dist/chunk-DSUCH44G.js.map +1 -0
- package/dist/{chunk-JUZVCBAI.js → chunk-FTXIXM43.js} +240 -271
- package/dist/chunk-FTXIXM43.js.map +1 -0
- package/dist/{chunk-QKVL45F6.mjs → chunk-G575OAT4.mjs} +73 -218
- package/dist/chunk-G575OAT4.mjs.map +1 -0
- package/dist/chunk-J7DHGLW6.mjs +638 -0
- package/dist/chunk-J7DHGLW6.mjs.map +1 -0
- package/dist/{chunk-ULUYX23Q.js → chunk-K3BNIGPX.js} +1023 -404
- package/dist/chunk-K3BNIGPX.js.map +1 -0
- package/dist/{chunk-3STZXVXD.mjs → chunk-TTOKQAPX.mjs} +998 -388
- package/dist/chunk-TTOKQAPX.mjs.map +1 -0
- package/dist/compat.d.mts +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/compat.js +40 -39
- package/dist/compat.js.map +1 -1
- package/dist/compat.mjs +39 -38
- package/dist/compat.mjs.map +1 -1
- package/dist/headless.d.mts +540 -152
- package/dist/headless.d.ts +540 -152
- package/dist/headless.js +226 -190
- package/dist/headless.mjs +3 -3
- package/dist/index.d.mts +8 -6
- package/dist/index.d.ts +8 -6
- package/dist/index.js +286 -218
- package/dist/index.mjs +4 -4
- package/dist/react.d.mts +283 -16
- package/dist/react.d.ts +283 -16
- package/dist/react.js +60 -28
- package/dist/react.mjs +2 -2
- package/dist/{wallet-standard-shim--YcrQNRt.d.ts → standard-shim-CT49DM5l.d.mts} +72 -252
- package/dist/{wallet-standard-shim-Dx7H8Ctf.d.mts → standard-shim-D9guL5fz.d.ts} +72 -252
- package/dist/{transaction-signer-D9d8nxwb.d.mts → transaction-signer-T-KVQFi8.d.mts} +2 -2
- package/dist/{transaction-signer-D9d8nxwb.d.ts → transaction-signer-T-KVQFi8.d.ts} +2 -2
- package/package.json +3 -3
- package/dist/chunk-3STZXVXD.mjs.map +0 -1
- package/dist/chunk-I64FD2EH.js +0 -312
- package/dist/chunk-I64FD2EH.js.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-QL3IT3TS.mjs +0 -299
- package/dist/chunk-QL3IT3TS.mjs.map +0 -1
- package/dist/chunk-ULUYX23Q.js.map +0 -1
- package/dist/chunk-VMSZJPR5.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { installPolyfills, ConnectorErrorBoundary, isMainnetCluster, isDevnetCluster, isTestnetCluster, isLocalCluster, getClusterExplorerUrl, getClusterType, formatAddress, copyAddressToClipboard, createTransactionSigner, createKitTransactionSigner,
|
|
2
|
-
import { createLogger, __publicField, createSolanaClient, prepareTransaction } from './chunk-
|
|
1
|
+
import { installPolyfills, ConnectorErrorBoundary, isMainnetCluster, isDevnetCluster, isTestnetCluster, isLocalCluster, getClusterExplorerUrl, getClusterType, formatAddress, copyAddressToClipboard, createTransactionSigner, createKitTransactionSigner, formatLamportsToSolSafe, getTransactionUrl, ConnectorClient, formatBigIntBalance, formatBigIntUsd } from './chunk-6F6M6L7R.mjs';
|
|
2
|
+
import { createLogger, __publicField, createSolanaClient, NetworkError, prepareTransaction } from './chunk-J7DHGLW6.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';
|
|
5
5
|
import { address } from '@solana/addresses';
|
|
@@ -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
|
})(), () => {
|
|
@@ -115,14 +115,15 @@ function useConnector() {
|
|
|
115
115
|
function useConnectorClient() {
|
|
116
116
|
return useContext(ConnectorContext);
|
|
117
117
|
}
|
|
118
|
-
function
|
|
119
|
-
let
|
|
118
|
+
function AppProvider({ children, connectorConfig, mobile, providers = [] }) {
|
|
119
|
+
let content = /* @__PURE__ */ jsx(ConnectorProvider, { config: connectorConfig, mobile, children });
|
|
120
120
|
for (let i = providers.length - 1; i >= 0; i--) {
|
|
121
121
|
let { component: Provider, props = {} } = providers[i];
|
|
122
122
|
content = /* @__PURE__ */ jsx(Provider, { ...props, children: content });
|
|
123
123
|
}
|
|
124
124
|
return content;
|
|
125
125
|
}
|
|
126
|
+
var UnifiedProvider = AppProvider;
|
|
126
127
|
function useCluster() {
|
|
127
128
|
let { cluster, clusters } = useConnector(), client = useConnectorClient();
|
|
128
129
|
if (!client)
|
|
@@ -299,73 +300,503 @@ function useTransactionPreparer() {
|
|
|
299
300
|
[prepare, ready]
|
|
300
301
|
);
|
|
301
302
|
}
|
|
302
|
-
var
|
|
303
|
-
function
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
303
|
+
var DEFAULT_CACHE_TIME_MS = 300 * 1e3, MAX_STORE_SIZE = 100, store = /* @__PURE__ */ new Map();
|
|
304
|
+
function evictStaleEntries() {
|
|
305
|
+
if (store.size <= MAX_STORE_SIZE) return;
|
|
306
|
+
let evictable = [];
|
|
307
|
+
for (let [key, entry] of store)
|
|
308
|
+
entry.subscribers.size === 0 && evictable.push([key, entry]);
|
|
309
|
+
evictable.sort((a, b) => a[1].lastAccessedAt - b[1].lastAccessedAt);
|
|
310
|
+
for (let [key, entry] of evictable) {
|
|
311
|
+
if (store.size <= MAX_STORE_SIZE) break;
|
|
312
|
+
entry.gcTimeoutId && clearTimeout(entry.gcTimeoutId), entry.intervalId && clearInterval(entry.intervalId), entry.abortController && entry.abortController.abort(), store.delete(key);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function getOrCreateEntry(key) {
|
|
316
|
+
let existing = store.get(key);
|
|
317
|
+
if (existing)
|
|
318
|
+
return existing.lastAccessedAt = Date.now(), existing;
|
|
319
|
+
let entry = {
|
|
320
|
+
snapshot: {
|
|
321
|
+
data: void 0,
|
|
322
|
+
error: null,
|
|
323
|
+
status: "idle",
|
|
324
|
+
updatedAt: null,
|
|
325
|
+
isFetching: false
|
|
326
|
+
},
|
|
327
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
328
|
+
promise: null,
|
|
329
|
+
abortController: null,
|
|
330
|
+
cacheTimeMs: DEFAULT_CACHE_TIME_MS,
|
|
331
|
+
gcTimeoutId: null,
|
|
332
|
+
queryFn: null,
|
|
333
|
+
intervalCounts: /* @__PURE__ */ new Map(),
|
|
334
|
+
activeIntervalMs: null,
|
|
335
|
+
intervalId: null,
|
|
336
|
+
lastAccessedAt: Date.now()
|
|
337
|
+
};
|
|
338
|
+
return store.set(key, entry), evictStaleEntries(), entry;
|
|
339
|
+
}
|
|
340
|
+
function emit(entry) {
|
|
341
|
+
for (let cb of entry.subscribers)
|
|
342
|
+
cb();
|
|
343
|
+
}
|
|
344
|
+
function setSnapshot(entry, next) {
|
|
345
|
+
let prev = entry.snapshot;
|
|
346
|
+
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));
|
|
347
|
+
}
|
|
348
|
+
function getMinIntervalMs(entry) {
|
|
349
|
+
let min = null;
|
|
350
|
+
for (let [ms, count] of entry.intervalCounts)
|
|
351
|
+
count <= 0 || (min === null || ms < min) && (min = ms);
|
|
352
|
+
return min;
|
|
353
|
+
}
|
|
354
|
+
function startOrRestartInterval(key, entry) {
|
|
355
|
+
let nextMs = getMinIntervalMs(entry);
|
|
356
|
+
if (nextMs === null) {
|
|
357
|
+
entry.intervalId && clearInterval(entry.intervalId), entry.intervalId = null, entry.activeIntervalMs = null;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
entry.activeIntervalMs === nextMs && entry.intervalId || (entry.intervalId && clearInterval(entry.intervalId), entry.activeIntervalMs = nextMs, entry.intervalId = setInterval(() => {
|
|
361
|
+
let fn = entry.queryFn;
|
|
362
|
+
fn && fetchSharedQuery(key, fn, { force: true });
|
|
363
|
+
}, nextMs));
|
|
364
|
+
}
|
|
365
|
+
function subscribeSharedQuery(key, onChange, cacheTimeMs) {
|
|
366
|
+
let entry = getOrCreateEntry(key);
|
|
367
|
+
return entry.subscribers.add(onChange), typeof cacheTimeMs == "number" && (entry.cacheTimeMs = Math.max(entry.cacheTimeMs, cacheTimeMs)), entry.gcTimeoutId && (clearTimeout(entry.gcTimeoutId), entry.gcTimeoutId = null), () => {
|
|
368
|
+
if (entry.subscribers.delete(onChange), entry.subscribers.size > 0) return;
|
|
369
|
+
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();
|
|
370
|
+
let gcDelayMs = entry.cacheTimeMs ?? DEFAULT_CACHE_TIME_MS;
|
|
371
|
+
entry.gcTimeoutId = setTimeout(() => {
|
|
372
|
+
entry.subscribers.size === 0 && store.delete(key);
|
|
373
|
+
}, gcDelayMs);
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
async function fetchSharedQuery(key, queryFn, options = {}) {
|
|
377
|
+
let entry = getOrCreateEntry(key);
|
|
378
|
+
entry.queryFn = queryFn;
|
|
379
|
+
let staleTimeMs = options.staleTimeMs ?? 0, now = Date.now();
|
|
380
|
+
if (options.force !== true && entry.snapshot.status === "success" && entry.snapshot.updatedAt !== null && now - entry.snapshot.updatedAt < staleTimeMs && entry.snapshot.data !== void 0)
|
|
381
|
+
return entry.snapshot.data;
|
|
382
|
+
if (entry.promise)
|
|
383
|
+
return entry.promise;
|
|
384
|
+
let controller = new AbortController();
|
|
385
|
+
entry.abortController = controller, options.signal && (options.signal.aborted ? controller.abort() : options.signal.addEventListener("abort", () => controller.abort(), { once: true }));
|
|
386
|
+
let isFirstLoad = entry.snapshot.status === "idle" && entry.snapshot.updatedAt === null;
|
|
387
|
+
setSnapshot(entry, {
|
|
388
|
+
...entry.snapshot,
|
|
389
|
+
status: isFirstLoad ? "loading" : entry.snapshot.status,
|
|
390
|
+
isFetching: true,
|
|
391
|
+
error: null
|
|
392
|
+
});
|
|
393
|
+
let promise = (async () => {
|
|
394
|
+
try {
|
|
395
|
+
let data = await queryFn(controller.signal);
|
|
396
|
+
return setSnapshot(entry, {
|
|
397
|
+
data,
|
|
398
|
+
error: null,
|
|
399
|
+
status: "success",
|
|
400
|
+
updatedAt: Date.now(),
|
|
401
|
+
isFetching: false
|
|
402
|
+
}), data;
|
|
403
|
+
} catch (cause) {
|
|
404
|
+
if (controller.signal.aborted)
|
|
405
|
+
return setSnapshot(entry, {
|
|
406
|
+
data: entry.snapshot.data,
|
|
407
|
+
error: null,
|
|
408
|
+
status: entry.snapshot.status === "idle" ? "idle" : entry.snapshot.status,
|
|
409
|
+
updatedAt: entry.snapshot.updatedAt,
|
|
410
|
+
isFetching: false
|
|
411
|
+
}), entry.snapshot.data;
|
|
412
|
+
let error = cause instanceof Error ? cause : new Error(String(cause));
|
|
413
|
+
throw setSnapshot(entry, {
|
|
414
|
+
data: entry.snapshot.data,
|
|
415
|
+
error,
|
|
416
|
+
status: "error",
|
|
417
|
+
updatedAt: entry.snapshot.updatedAt,
|
|
418
|
+
isFetching: false
|
|
419
|
+
}), error;
|
|
420
|
+
} finally {
|
|
421
|
+
entry.promise = null, entry.abortController = null;
|
|
422
|
+
}
|
|
423
|
+
})();
|
|
424
|
+
return entry.promise = promise, promise;
|
|
425
|
+
}
|
|
426
|
+
function useSharedQuery(key, queryFn, options = {}) {
|
|
427
|
+
let {
|
|
428
|
+
enabled = true,
|
|
429
|
+
staleTimeMs = 0,
|
|
430
|
+
cacheTimeMs,
|
|
431
|
+
refetchOnMount = "stale",
|
|
432
|
+
refetchIntervalMs = false,
|
|
433
|
+
select
|
|
434
|
+
} = options, queryFnRef = useRef(queryFn);
|
|
435
|
+
queryFnRef.current = queryFn;
|
|
436
|
+
let stableQueryFn = useCallback((signal) => queryFnRef.current(signal), []), subscribe = useCallback(
|
|
437
|
+
(onChange) => key ? subscribeSharedQuery(key, onChange, cacheTimeMs) : () => {
|
|
438
|
+
},
|
|
439
|
+
[key, cacheTimeMs]
|
|
440
|
+
), emptySnapshot = useMemo(
|
|
441
|
+
() => ({
|
|
442
|
+
data: void 0,
|
|
443
|
+
error: null,
|
|
444
|
+
status: "idle",
|
|
445
|
+
updatedAt: null,
|
|
446
|
+
isFetching: false
|
|
447
|
+
}),
|
|
448
|
+
[]
|
|
449
|
+
), 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);
|
|
450
|
+
useEffect(() => {
|
|
451
|
+
if (!key || !enabled) {
|
|
452
|
+
fetchedKeyRef.current = null;
|
|
313
453
|
return;
|
|
314
454
|
}
|
|
315
|
-
|
|
455
|
+
let entry = getOrCreateEntry(key);
|
|
456
|
+
if (entry.queryFn = stableQueryFn, fetchedKeyRef.current === key && entry.snapshot.status === "success")
|
|
457
|
+
return;
|
|
458
|
+
let current = entry.snapshot, isStale = current.updatedAt === null || Date.now() - current.updatedAt >= staleTimeMs;
|
|
459
|
+
refetchOnMount === true || current.status === "idle" || refetchOnMount === "stale" && isStale ? (fetchedKeyRef.current = key, fetchSharedQuery(key, stableQueryFn, {
|
|
460
|
+
staleTimeMs,
|
|
461
|
+
force: refetchOnMount === true
|
|
462
|
+
}).catch(() => {
|
|
463
|
+
})) : fetchedKeyRef.current = key;
|
|
464
|
+
}, [key, enabled, staleTimeMs, refetchOnMount, stableQueryFn]), useEffect(() => {
|
|
465
|
+
if (!key || !enabled || refetchIntervalMs === false) return;
|
|
466
|
+
let entry = getOrCreateEntry(key);
|
|
467
|
+
entry.queryFn = stableQueryFn;
|
|
468
|
+
let prev = entry.intervalCounts.get(refetchIntervalMs) ?? 0;
|
|
469
|
+
return entry.intervalCounts.set(refetchIntervalMs, prev + 1), startOrRestartInterval(key, entry), () => {
|
|
470
|
+
let count = entry.intervalCounts.get(refetchIntervalMs) ?? 0;
|
|
471
|
+
count <= 1 ? entry.intervalCounts.delete(refetchIntervalMs) : entry.intervalCounts.set(refetchIntervalMs, count - 1), startOrRestartInterval(key, entry);
|
|
472
|
+
};
|
|
473
|
+
}, [key, enabled, refetchIntervalMs, stableQueryFn]);
|
|
474
|
+
let refetch = useCallback(
|
|
475
|
+
async (refetchOptions) => {
|
|
476
|
+
if (key)
|
|
477
|
+
return fetchSharedQuery(key, stableQueryFn, {
|
|
478
|
+
force: true,
|
|
479
|
+
signal: refetchOptions?.signal
|
|
480
|
+
});
|
|
481
|
+
},
|
|
482
|
+
[key, stableQueryFn]
|
|
483
|
+
), abort = useCallback(() => {
|
|
484
|
+
if (!key) return;
|
|
485
|
+
let entry = store.get(key);
|
|
486
|
+
entry?.abortController && entry.abortController.abort();
|
|
487
|
+
}, [key]);
|
|
488
|
+
return useMemo(
|
|
489
|
+
() => ({
|
|
490
|
+
data: stableSelectedData,
|
|
491
|
+
error: snapshot.error,
|
|
492
|
+
status: snapshot.status,
|
|
493
|
+
updatedAt: snapshot.updatedAt,
|
|
494
|
+
isFetching: snapshot.isFetching,
|
|
495
|
+
refetch,
|
|
496
|
+
abort
|
|
497
|
+
}),
|
|
498
|
+
[stableSelectedData, snapshot.error, snapshot.status, snapshot.updatedAt, snapshot.isFetching, refetch, abort]
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
function invalidateSharedQuery(key) {
|
|
502
|
+
let entry = store.get(key);
|
|
503
|
+
entry && setSnapshot(entry, {
|
|
504
|
+
...entry.snapshot,
|
|
505
|
+
updatedAt: null
|
|
506
|
+
// Mark as stale
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
function clearSharedQueryCache() {
|
|
510
|
+
for (let [, entry] of store)
|
|
511
|
+
entry.intervalId && clearInterval(entry.intervalId), entry.gcTimeoutId && clearTimeout(entry.gcTimeoutId), entry.abortController && entry.abortController.abort();
|
|
512
|
+
store.clear();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/hooks/_internal/use-wallet-assets.ts
|
|
516
|
+
var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", NATIVE_SOL_MINT = "So11111111111111111111111111111111111111112";
|
|
517
|
+
function isRecord(value) {
|
|
518
|
+
return typeof value == "object" && value !== null;
|
|
519
|
+
}
|
|
520
|
+
function parseBigInt(value) {
|
|
521
|
+
if (typeof value == "bigint") return value;
|
|
522
|
+
if (typeof value == "number" && Number.isSafeInteger(value)) return BigInt(value);
|
|
523
|
+
if (typeof value == "string")
|
|
316
524
|
try {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
525
|
+
return BigInt(value);
|
|
526
|
+
} catch {
|
|
527
|
+
return 0n;
|
|
528
|
+
}
|
|
529
|
+
return 0n;
|
|
530
|
+
}
|
|
531
|
+
function getParsedTokenAccountInfo(data) {
|
|
532
|
+
if (!isRecord(data)) return null;
|
|
533
|
+
let parsed = data.parsed;
|
|
534
|
+
if (!isRecord(parsed)) return null;
|
|
535
|
+
let info = parsed.info;
|
|
536
|
+
return isRecord(info) ? info : null;
|
|
537
|
+
}
|
|
538
|
+
function parseTokenAccount(account, programId) {
|
|
539
|
+
let info = getParsedTokenAccountInfo(account.account.data);
|
|
540
|
+
if (!info) return null;
|
|
541
|
+
let mint = typeof info.mint == "string" ? info.mint : null, owner = typeof info.owner == "string" ? info.owner : null;
|
|
542
|
+
if (!mint || !owner) return null;
|
|
543
|
+
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;
|
|
544
|
+
return {
|
|
545
|
+
pubkey: String(account.pubkey),
|
|
546
|
+
mint,
|
|
547
|
+
owner,
|
|
548
|
+
amount,
|
|
549
|
+
decimals,
|
|
550
|
+
isFrozen: state === "frozen",
|
|
551
|
+
programId
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function getWalletAssetsQueryKey(rpcUrl, address) {
|
|
555
|
+
return !rpcUrl || !address ? null : JSON.stringify(["wallet-assets", rpcUrl, address]);
|
|
556
|
+
}
|
|
557
|
+
function useWalletAssets(options = {}) {
|
|
558
|
+
let {
|
|
559
|
+
enabled = true,
|
|
560
|
+
staleTimeMs = 0,
|
|
561
|
+
cacheTimeMs = 300 * 1e3,
|
|
562
|
+
// 5 minutes
|
|
563
|
+
refetchOnMount = "stale",
|
|
564
|
+
refetchIntervalMs = false,
|
|
565
|
+
client: clientOverride,
|
|
566
|
+
select
|
|
567
|
+
} = options, { address: address$1, connected } = useAccount(), { client: providerClient } = useSolanaClient(), rpcClient = clientOverride ?? providerClient, key = useMemo(() => {
|
|
568
|
+
if (!enabled || !connected || !address$1 || !rpcClient) return null;
|
|
569
|
+
let rpcUrl = rpcClient.urlOrMoniker instanceof URL ? rpcClient.urlOrMoniker.toString() : String(rpcClient.urlOrMoniker);
|
|
570
|
+
return getWalletAssetsQueryKey(rpcUrl, address$1);
|
|
571
|
+
}, [enabled, connected, address$1, rpcClient]), queryFn = useCallback(
|
|
572
|
+
async (signal) => {
|
|
573
|
+
if (!connected || !address$1 || !rpcClient)
|
|
574
|
+
return { lamports: 0n, tokenAccounts: [] };
|
|
575
|
+
if (signal.aborted)
|
|
576
|
+
throw new DOMException("Query aborted", "AbortError");
|
|
577
|
+
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([
|
|
578
|
+
rpc.getBalance(walletAddress).send(),
|
|
579
|
+
rpc.getTokenAccountsByOwner(walletAddress, { programId: tokenProgramId }, { encoding: "jsonParsed" }).send(),
|
|
580
|
+
rpc.getTokenAccountsByOwner(
|
|
581
|
+
walletAddress,
|
|
582
|
+
{ programId: token2022ProgramId },
|
|
583
|
+
{ encoding: "jsonParsed" }
|
|
584
|
+
).send()
|
|
585
|
+
]);
|
|
586
|
+
if (signal.aborted)
|
|
587
|
+
throw new DOMException("Query aborted", "AbortError");
|
|
588
|
+
let tokenAccounts = [];
|
|
589
|
+
for (let account of tokenAccountsResult.value) {
|
|
590
|
+
let parsed = parseTokenAccount(account, "token");
|
|
591
|
+
parsed && tokenAccounts.push(parsed);
|
|
339
592
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
593
|
+
for (let account of token2022AccountsResult.value) {
|
|
594
|
+
let parsed = parseTokenAccount(account, "token-2022");
|
|
595
|
+
parsed && tokenAccounts.push(parsed);
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
lamports: balanceResult.value,
|
|
599
|
+
tokenAccounts
|
|
600
|
+
};
|
|
601
|
+
},
|
|
602
|
+
[connected, address$1, rpcClient]
|
|
603
|
+
), { data, error, status, updatedAt, isFetching, refetch, abort } = useSharedQuery(
|
|
604
|
+
key,
|
|
605
|
+
queryFn,
|
|
606
|
+
{
|
|
607
|
+
enabled,
|
|
608
|
+
staleTimeMs,
|
|
609
|
+
cacheTimeMs,
|
|
610
|
+
refetchOnMount,
|
|
611
|
+
refetchIntervalMs,
|
|
612
|
+
select
|
|
345
613
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
614
|
+
), isLoading = status === "loading" || status === "idle";
|
|
615
|
+
return useMemo(
|
|
616
|
+
() => ({
|
|
617
|
+
data,
|
|
618
|
+
isLoading,
|
|
619
|
+
isFetching,
|
|
620
|
+
error,
|
|
621
|
+
refetch,
|
|
622
|
+
abort,
|
|
623
|
+
updatedAt
|
|
624
|
+
}),
|
|
625
|
+
[data, isLoading, isFetching, error, refetch, abort, updatedAt]
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/hooks/use-balance.ts
|
|
630
|
+
function getBalanceQueryKey(rpcUrl, address) {
|
|
631
|
+
return getWalletAssetsQueryKey(rpcUrl, address);
|
|
632
|
+
}
|
|
633
|
+
var LAMPORTS_PER_SOL2 = 1000000000n;
|
|
634
|
+
function formatTokenAccount(account) {
|
|
635
|
+
let formatted = formatBigIntBalance(account.amount, account.decimals, {
|
|
636
|
+
maxDecimals: Math.min(account.decimals, 6)
|
|
637
|
+
});
|
|
638
|
+
return {
|
|
639
|
+
mint: account.mint,
|
|
640
|
+
amount: account.amount,
|
|
641
|
+
decimals: account.decimals,
|
|
642
|
+
formatted
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function selectBalance(assets) {
|
|
646
|
+
if (!assets)
|
|
647
|
+
return { lamports: 0n, tokens: [] };
|
|
648
|
+
let tokens = assets.tokenAccounts.filter((account) => account.amount > 0n).map(formatTokenAccount);
|
|
649
|
+
return {
|
|
650
|
+
lamports: assets.lamports,
|
|
651
|
+
tokens
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function useBalance(options = {}) {
|
|
655
|
+
let {
|
|
656
|
+
enabled = true,
|
|
657
|
+
autoRefresh = true,
|
|
658
|
+
refreshInterval = 3e4,
|
|
659
|
+
staleTimeMs = 0,
|
|
660
|
+
cacheTimeMs = 300 * 1e3,
|
|
661
|
+
// 5 minutes
|
|
662
|
+
refetchOnMount = "stale",
|
|
663
|
+
client: clientOverride
|
|
664
|
+
} = options, {
|
|
665
|
+
data,
|
|
666
|
+
error,
|
|
667
|
+
isFetching,
|
|
668
|
+
updatedAt,
|
|
669
|
+
refetch: sharedRefetch,
|
|
670
|
+
abort
|
|
671
|
+
} = useWalletAssets({
|
|
672
|
+
enabled,
|
|
673
|
+
staleTimeMs,
|
|
674
|
+
cacheTimeMs,
|
|
675
|
+
refetchOnMount,
|
|
676
|
+
refetchIntervalMs: autoRefresh ? refreshInterval : false,
|
|
677
|
+
client: clientOverride,
|
|
678
|
+
select: selectBalance
|
|
679
|
+
}), 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(
|
|
680
|
+
async (opts) => {
|
|
681
|
+
await sharedRefetch(opts);
|
|
682
|
+
},
|
|
683
|
+
[sharedRefetch]
|
|
684
|
+
);
|
|
355
685
|
return useMemo(
|
|
356
686
|
() => ({
|
|
357
687
|
solBalance,
|
|
358
688
|
lamports,
|
|
359
689
|
formattedSol,
|
|
360
690
|
tokens,
|
|
361
|
-
isLoading,
|
|
362
|
-
error,
|
|
363
|
-
refetch
|
|
364
|
-
|
|
691
|
+
isLoading: isFetching,
|
|
692
|
+
error: visibleError,
|
|
693
|
+
refetch,
|
|
694
|
+
abort,
|
|
695
|
+
lastUpdated: updatedAt ? new Date(updatedAt) : null
|
|
365
696
|
}),
|
|
366
|
-
[solBalance, lamports, formattedSol, tokens,
|
|
697
|
+
[solBalance, lamports, formattedSol, tokens, isFetching, visibleError, refetch, abort, updatedAt]
|
|
367
698
|
);
|
|
368
699
|
}
|
|
700
|
+
|
|
701
|
+
// src/utils/abort.ts
|
|
702
|
+
function createTimeoutSignal(ms) {
|
|
703
|
+
if (typeof AbortSignal.timeout == "function")
|
|
704
|
+
return { signal: AbortSignal.timeout(ms), cleanup: () => {
|
|
705
|
+
} };
|
|
706
|
+
let controller = new AbortController(), timeoutId = setTimeout(() => controller.abort(), ms);
|
|
707
|
+
return {
|
|
708
|
+
signal: controller.signal,
|
|
709
|
+
cleanup: () => clearTimeout(timeoutId)
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/hooks/_internal/solana-token-list.ts
|
|
714
|
+
var CLUSTER_CHAIN_IDS = {
|
|
715
|
+
mainnet: 101,
|
|
716
|
+
testnet: 102,
|
|
717
|
+
devnet: 103,
|
|
718
|
+
localnet: 103,
|
|
719
|
+
// Use devnet tokens for localnet
|
|
720
|
+
custom: 101
|
|
721
|
+
// Default to mainnet for custom clusters
|
|
722
|
+
}, TOKEN_LIST_API_BASE_URL = "https://token-list-api.solana.cloud/v1/mints";
|
|
723
|
+
function getTokenListApiUrl(cluster = "mainnet") {
|
|
724
|
+
let chainId = CLUSTER_CHAIN_IDS[cluster];
|
|
725
|
+
return `${TOKEN_LIST_API_BASE_URL}?chainId=${chainId}`;
|
|
726
|
+
}
|
|
727
|
+
var DEFAULT_TIMEOUT_MS = 1e4, TOKEN_LIST_CACHE_MAX_SIZE = 1500, MAX_ADDRESSES_PER_REQUEST = 100, tokenListCache = /* @__PURE__ */ new Map();
|
|
728
|
+
function getCachedTokenListMetadata(mint) {
|
|
729
|
+
let value = tokenListCache.get(mint);
|
|
730
|
+
if (value)
|
|
731
|
+
return tokenListCache.delete(mint), tokenListCache.set(mint, value), value;
|
|
732
|
+
}
|
|
733
|
+
function setCachedTokenListMetadata(mint, value) {
|
|
734
|
+
if (tokenListCache.has(mint) && tokenListCache.delete(mint), tokenListCache.set(mint, value), tokenListCache.size > TOKEN_LIST_CACHE_MAX_SIZE) {
|
|
735
|
+
let oldestKey = tokenListCache.keys().next().value;
|
|
736
|
+
oldestKey && tokenListCache.delete(oldestKey);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function createLinkedSignal(externalSignal, timeoutMs) {
|
|
740
|
+
let controller = new AbortController(), onAbort = () => controller.abort();
|
|
741
|
+
externalSignal && (externalSignal.aborted ? controller.abort() : externalSignal.addEventListener("abort", onAbort, { once: true }));
|
|
742
|
+
let { signal: timeoutSignal, cleanup: cleanupTimeout } = createTimeoutSignal(timeoutMs), onTimeoutAbort = () => controller.abort();
|
|
743
|
+
return timeoutSignal.aborted ? controller.abort() : timeoutSignal.addEventListener("abort", onTimeoutAbort, { once: true }), {
|
|
744
|
+
signal: controller.signal,
|
|
745
|
+
cleanup: () => {
|
|
746
|
+
cleanupTimeout(), externalSignal && externalSignal.removeEventListener("abort", onAbort), timeoutSignal.removeEventListener("abort", onTimeoutAbort);
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
async function fetchSolanaTokenListMetadata(mints, options = {}) {
|
|
751
|
+
let results = /* @__PURE__ */ new Map();
|
|
752
|
+
if (!mints.length) return results;
|
|
753
|
+
let seen = /* @__PURE__ */ new Set(), uncached = [];
|
|
754
|
+
for (let mint of mints) {
|
|
755
|
+
let normalized = mint?.trim();
|
|
756
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
757
|
+
seen.add(normalized);
|
|
758
|
+
let cached = getCachedTokenListMetadata(normalized);
|
|
759
|
+
cached ? results.set(normalized, cached) : uncached.push(normalized);
|
|
760
|
+
}
|
|
761
|
+
if (!uncached.length) return results;
|
|
762
|
+
let timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS, apiUrl = getTokenListApiUrl(options.cluster);
|
|
763
|
+
for (let i = 0; i < uncached.length && !options.signal?.aborted; i += MAX_ADDRESSES_PER_REQUEST) {
|
|
764
|
+
let batch = uncached.slice(i, i + MAX_ADDRESSES_PER_REQUEST), { signal, cleanup } = createLinkedSignal(options.signal, timeoutMs);
|
|
765
|
+
try {
|
|
766
|
+
let response = await fetch(apiUrl, {
|
|
767
|
+
method: "POST",
|
|
768
|
+
headers: { "Content-Type": "application/json" },
|
|
769
|
+
body: JSON.stringify({ addresses: batch }),
|
|
770
|
+
signal
|
|
771
|
+
});
|
|
772
|
+
if (!response.ok) {
|
|
773
|
+
console.warn("[token-list] Solana Token List API error:", response.status, response.statusText);
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
let data = await response.json();
|
|
777
|
+
if (!data?.content?.length) continue;
|
|
778
|
+
for (let item of data.content)
|
|
779
|
+
item?.address && (results.set(item.address, item), setCachedTokenListMetadata(item.address, item));
|
|
780
|
+
} catch (error) {
|
|
781
|
+
console.warn("[token-list] Solana Token List API failed:", error);
|
|
782
|
+
} finally {
|
|
783
|
+
cleanup();
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return results;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// src/utils/image.ts
|
|
790
|
+
function transformImageUrl(url, imageProxy) {
|
|
791
|
+
if (url)
|
|
792
|
+
return imageProxy ? `${imageProxy}${encodeURIComponent(url)}` : url;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/hooks/use-transactions.ts
|
|
796
|
+
function getTransactionsQueryKey(options) {
|
|
797
|
+
let { rpcUrl, address, clusterId, limit = 20, fetchDetails = false } = options;
|
|
798
|
+
return !rpcUrl || !address || !clusterId ? null : JSON.stringify(["wallet-transactions", rpcUrl, address, clusterId, limit, fetchDetails]);
|
|
799
|
+
}
|
|
369
800
|
function formatDate(timestamp) {
|
|
370
801
|
if (!timestamp)
|
|
371
802
|
return { date: "Unknown", time: "" };
|
|
@@ -377,6 +808,45 @@ function formatDate(timestamp) {
|
|
|
377
808
|
});
|
|
378
809
|
return { date: formattedDate, time: formattedTime };
|
|
379
810
|
}
|
|
811
|
+
var KNOWN_PROGRAMS = {
|
|
812
|
+
"11111111111111111111111111111111": "System Program",
|
|
813
|
+
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA: "Token Program",
|
|
814
|
+
ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL: "Associated Token",
|
|
815
|
+
JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4: "Jupiter",
|
|
816
|
+
whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc: "Orca Whirlpool",
|
|
817
|
+
"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8": "Raydium AMM",
|
|
818
|
+
Stake11111111111111111111111111111111111111: "Stake Program",
|
|
819
|
+
metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s: "Metaplex"
|
|
820
|
+
}, DEFAULT_IGNORED_PROGRAM_IDS = /* @__PURE__ */ new Set([
|
|
821
|
+
"11111111111111111111111111111111",
|
|
822
|
+
// System Program
|
|
823
|
+
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
|
824
|
+
// Token Program
|
|
825
|
+
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
|
826
|
+
// Associated Token
|
|
827
|
+
]);
|
|
828
|
+
function resolveProgramName(programId, programLabels) {
|
|
829
|
+
return programLabels?.[programId] ?? KNOWN_PROGRAMS[programId];
|
|
830
|
+
}
|
|
831
|
+
function pickPrimaryProgramId(programIds) {
|
|
832
|
+
for (let id of programIds)
|
|
833
|
+
if (!DEFAULT_IGNORED_PROGRAM_IDS.has(id)) return id;
|
|
834
|
+
return programIds.values().next().value;
|
|
835
|
+
}
|
|
836
|
+
function getParsedInstructionTypes(message) {
|
|
837
|
+
if (!Array.isArray(message.instructions)) return;
|
|
838
|
+
let types = [];
|
|
839
|
+
for (let ix of message.instructions) {
|
|
840
|
+
if (!ix || typeof ix != "object") continue;
|
|
841
|
+
let parsed = ix.parsed;
|
|
842
|
+
if (!parsed || typeof parsed != "object" || !("type" in parsed)) continue;
|
|
843
|
+
let t = parsed.type;
|
|
844
|
+
if (typeof t == "string" && (types.push(t), types.length >= 10))
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
let unique = [...new Set(types)];
|
|
848
|
+
return unique.length ? unique : void 0;
|
|
849
|
+
}
|
|
380
850
|
function isAccountKey(value) {
|
|
381
851
|
return typeof value == "object" && value !== null && "pubkey" in value && typeof value.pubkey == "string";
|
|
382
852
|
}
|
|
@@ -401,6 +871,13 @@ function isTransactionWithMeta(value) {
|
|
|
401
871
|
function isTransactionMessage(value) {
|
|
402
872
|
return typeof value == "object" && value !== null && "accountKeys" in value && Array.isArray(value.accountKeys);
|
|
403
873
|
}
|
|
874
|
+
function coerceMaybeAddressString(value) {
|
|
875
|
+
if (typeof value == "string") return value;
|
|
876
|
+
if (value && typeof value == "object") {
|
|
877
|
+
let str = String(value);
|
|
878
|
+
if (str && str !== "[object Object]") return str;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
404
881
|
function getAccountKeys(message) {
|
|
405
882
|
return Array.isArray(message.accountKeys) ? message.accountKeys.map((key) => typeof key == "string" ? key : isAccountKey(key) ? key.pubkey : "").filter(Boolean) : [];
|
|
406
883
|
}
|
|
@@ -432,12 +909,12 @@ function parseTokenTransfers(meta, accountKeys, walletAddress) {
|
|
|
432
909
|
return null;
|
|
433
910
|
let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
|
|
434
911
|
if (!isTokenBalance(balance)) return false;
|
|
435
|
-
let accountKey = accountKeys[balance.accountIndex];
|
|
436
|
-
return accountKey && accountKey.trim() === walletAddress.trim() ||
|
|
912
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
913
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
437
914
|
}), ourPostTokens = postTokenBalances.filter((balance) => {
|
|
438
915
|
if (!isTokenBalance(balance)) return false;
|
|
439
|
-
let accountKey = accountKeys[balance.accountIndex];
|
|
440
|
-
return accountKey && accountKey.trim() === walletAddress.trim() ||
|
|
916
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
917
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
441
918
|
}), allMints = /* @__PURE__ */ new Set();
|
|
442
919
|
for (let token of ourPreTokens)
|
|
443
920
|
isTokenBalance(token) && allMints.add(token.mint);
|
|
@@ -481,6 +958,52 @@ function parseTokenTransfers(meta, accountKeys, walletAddress) {
|
|
|
481
958
|
}
|
|
482
959
|
return null;
|
|
483
960
|
}
|
|
961
|
+
function parseTokenAccountClosure(meta, accountKeys, walletAddress) {
|
|
962
|
+
if (!isTransactionMeta(meta))
|
|
963
|
+
return null;
|
|
964
|
+
let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
|
|
965
|
+
if (!isTokenBalance(balance)) return false;
|
|
966
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
967
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
968
|
+
}), ourPostTokens = postTokenBalances.filter((balance) => {
|
|
969
|
+
if (!isTokenBalance(balance)) return false;
|
|
970
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
971
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
972
|
+
}), postKeys = /* @__PURE__ */ new Set();
|
|
973
|
+
for (let token of ourPostTokens)
|
|
974
|
+
isTokenBalance(token) && postKeys.add(`${token.accountIndex}:${token.mint}`);
|
|
975
|
+
for (let token of ourPreTokens) {
|
|
976
|
+
if (!isTokenBalance(token)) continue;
|
|
977
|
+
let key = `${token.accountIndex}:${token.mint}`;
|
|
978
|
+
if (!postKeys.has(key))
|
|
979
|
+
return { tokenMint: token.mint };
|
|
980
|
+
}
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
function parseSwapTokens(meta, accountKeys, walletAddress, solChange) {
|
|
984
|
+
if (!isTransactionMeta(meta))
|
|
985
|
+
return {};
|
|
986
|
+
let preTokenBalances = Array.isArray(meta.preTokenBalances) ? meta.preTokenBalances : [], postTokenBalances = Array.isArray(meta.postTokenBalances) ? meta.postTokenBalances : [], ourPreTokens = preTokenBalances.filter((balance) => {
|
|
987
|
+
if (!isTokenBalance(balance)) return false;
|
|
988
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
989
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
990
|
+
}), ourPostTokens = postTokenBalances.filter((balance) => {
|
|
991
|
+
if (!isTokenBalance(balance)) return false;
|
|
992
|
+
let accountKey = accountKeys[balance.accountIndex], owner = coerceMaybeAddressString(balance.owner);
|
|
993
|
+
return accountKey && accountKey.trim() === walletAddress.trim() || owner && owner.trim() === walletAddress.trim();
|
|
994
|
+
}), allMints = /* @__PURE__ */ new Set();
|
|
995
|
+
for (let token of ourPreTokens)
|
|
996
|
+
isTokenBalance(token) && allMints.add(token.mint);
|
|
997
|
+
for (let token of ourPostTokens)
|
|
998
|
+
isTokenBalance(token) && allMints.add(token.mint);
|
|
999
|
+
let fromToken, toToken;
|
|
1000
|
+
for (let mint of allMints) {
|
|
1001
|
+
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;
|
|
1002
|
+
change < 0 && !fromToken ? fromToken = { mint } : change > 0 && !toToken && (toToken = { mint });
|
|
1003
|
+
}
|
|
1004
|
+
let WRAPPED_SOL_MINT = "So11111111111111111111111111111111111111112";
|
|
1005
|
+
return solChange < -1e-3 && !fromToken ? fromToken = { mint: WRAPPED_SOL_MINT } : solChange > 1e-3 && !toToken && (toToken = { mint: WRAPPED_SOL_MINT }), { fromToken, toToken };
|
|
1006
|
+
}
|
|
484
1007
|
function formatAmount(tokenAmount, tokenDecimals, direction, solChange) {
|
|
485
1008
|
if (tokenAmount !== void 0 && tokenDecimals !== void 0 && direction !== void 0) {
|
|
486
1009
|
let sign = direction === "in" ? "+" : "-", maxDecimals = Math.min(tokenDecimals, 6);
|
|
@@ -489,52 +1012,59 @@ function formatAmount(tokenAmount, tokenDecimals, direction, solChange) {
|
|
|
489
1012
|
if (solChange !== 0)
|
|
490
1013
|
return `${solChange > 0 ? "+" : ""}${solChange.toFixed(4)} SOL`;
|
|
491
1014
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
return imageProxy ? `${imageProxy}${encodeURIComponent(url)}` : url;
|
|
1015
|
+
function throwIfAborted(signal) {
|
|
1016
|
+
if (signal?.aborted)
|
|
1017
|
+
throw new DOMException("Query aborted", "AbortError");
|
|
496
1018
|
}
|
|
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);
|
|
1019
|
+
function clampInt(value, min, max) {
|
|
1020
|
+
return Number.isFinite(value) ? Math.min(max, Math.max(min, Math.floor(value))) : min;
|
|
1021
|
+
}
|
|
1022
|
+
async function mapWithConcurrency(inputs, worker, options) {
|
|
1023
|
+
let concurrency = clampInt(options.concurrency, 1, 32), results = new Array(inputs.length), nextIndex = 0;
|
|
1024
|
+
async function run() {
|
|
1025
|
+
for (; ; ) {
|
|
1026
|
+
throwIfAborted(options.signal);
|
|
1027
|
+
let index = nextIndex;
|
|
1028
|
+
if (nextIndex += 1, index >= inputs.length) return;
|
|
1029
|
+
results[index] = await worker(inputs[index], index);
|
|
518
1030
|
}
|
|
519
|
-
} catch (error) {
|
|
520
|
-
console.warn("[useTransactions] Failed to fetch token metadata:", error);
|
|
521
1031
|
}
|
|
1032
|
+
let runners = Array.from({ length: Math.min(concurrency, inputs.length) }, () => run());
|
|
1033
|
+
return await Promise.all(runners), results;
|
|
1034
|
+
}
|
|
1035
|
+
async function fetchTransactionTokenMetadata(mints, options = {}) {
|
|
1036
|
+
let results = /* @__PURE__ */ new Map();
|
|
1037
|
+
if (!mints.length) return results;
|
|
1038
|
+
let tokenList = await fetchSolanaTokenListMetadata(mints, {
|
|
1039
|
+
timeoutMs: 5e3,
|
|
1040
|
+
signal: options.signal,
|
|
1041
|
+
cluster: options.cluster
|
|
1042
|
+
});
|
|
1043
|
+
for (let [mint, meta] of tokenList)
|
|
1044
|
+
results.set(mint, { symbol: meta.symbol, icon: meta.logoURI });
|
|
522
1045
|
return results;
|
|
523
1046
|
}
|
|
524
1047
|
function useTransactions(options = {}) {
|
|
525
|
-
let {
|
|
526
|
-
|
|
527
|
-
|
|
1048
|
+
let {
|
|
1049
|
+
enabled = true,
|
|
1050
|
+
limit = 10,
|
|
1051
|
+
autoRefresh = false,
|
|
1052
|
+
refreshInterval = 6e4,
|
|
1053
|
+
fetchDetails = true,
|
|
1054
|
+
detailsConcurrency = 6,
|
|
1055
|
+
staleTimeMs = 0,
|
|
1056
|
+
cacheTimeMs = 300 * 1e3,
|
|
1057
|
+
// 5 minutes
|
|
1058
|
+
refetchOnMount = "stale",
|
|
1059
|
+
client: clientOverride
|
|
1060
|
+
} = 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
1061
|
(tx, walletAddress, sig, blockTime, slot, err, explorerUrl) => {
|
|
529
1062
|
let { date, time } = formatDate(blockTime), baseInfo = {
|
|
530
1063
|
signature: sig,
|
|
531
1064
|
blockTime,
|
|
532
1065
|
slot,
|
|
533
1066
|
status: err ? "failed" : "success",
|
|
534
|
-
error: err ? JSON.stringify(
|
|
535
|
-
err,
|
|
536
|
-
(_key, value) => typeof value == "bigint" ? value.toString() : value
|
|
537
|
-
) : void 0,
|
|
1067
|
+
error: err ? JSON.stringify(err, (_key, value) => typeof value == "bigint" ? value.toString() : value) : void 0,
|
|
538
1068
|
type: "unknown",
|
|
539
1069
|
formattedDate: date,
|
|
540
1070
|
formattedTime: time,
|
|
@@ -549,24 +1079,13 @@ function useTransactions(options = {}) {
|
|
|
549
1079
|
let { message } = transaction;
|
|
550
1080
|
if (!isTransactionMessage(message))
|
|
551
1081
|
return baseInfo;
|
|
552
|
-
let accountKeys = getAccountKeys(message), walletIndex = accountKeys.findIndex((
|
|
1082
|
+
let accountKeys = getAccountKeys(message), walletIndex = accountKeys.findIndex((key2) => key2.trim() === walletAddress.trim());
|
|
553
1083
|
if (walletIndex === -1)
|
|
554
1084
|
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");
|
|
1085
|
+
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;
|
|
1086
|
+
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(
|
|
1087
|
+
(key2, idx) => idx !== walletIndex && key2 !== "11111111111111111111111111111111"
|
|
1088
|
+
))) : programIds.size > 0 && (type = "program");
|
|
570
1089
|
let formattedAmount = formatAmount(tokenAmount, tokenDecimals, direction, solChange);
|
|
571
1090
|
return {
|
|
572
1091
|
...baseInfo,
|
|
@@ -575,131 +1094,192 @@ function useTransactions(options = {}) {
|
|
|
575
1094
|
amount: tokenAmount ?? Math.abs(solChange),
|
|
576
1095
|
formattedAmount,
|
|
577
1096
|
tokenMint,
|
|
578
|
-
counterparty: counterparty ? `${counterparty.slice(0, 4)}...${counterparty.slice(-4)}` : void 0
|
|
1097
|
+
counterparty: counterparty ? `${counterparty.slice(0, 4)}...${counterparty.slice(-4)}` : void 0,
|
|
1098
|
+
swapFromToken,
|
|
1099
|
+
swapToToken,
|
|
1100
|
+
programId,
|
|
1101
|
+
programName,
|
|
1102
|
+
programIds: programIdsArray.length ? programIdsArray : void 0,
|
|
1103
|
+
instructionTypes,
|
|
1104
|
+
instructionCount
|
|
579
1105
|
};
|
|
580
1106
|
} catch (parseError) {
|
|
581
1107
|
return console.warn("Failed to parse transaction:", parseError), baseInfo;
|
|
582
1108
|
}
|
|
583
1109
|
},
|
|
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
|
-
|
|
1110
|
+
[programLabels]
|
|
1111
|
+
), key = useMemo(() => {
|
|
1112
|
+
if (!enabled || !connected || !address$1 || !rpcClient || !cluster) return null;
|
|
1113
|
+
let rpcUrl = rpcClient.urlOrMoniker instanceof URL ? rpcClient.urlOrMoniker.toString() : String(rpcClient.urlOrMoniker);
|
|
1114
|
+
return getTransactionsQueryKey({ rpcUrl, address: address$1, clusterId: cluster.id, limit, fetchDetails });
|
|
1115
|
+
}, [enabled, connected, address$1, rpcClient, cluster, limit, fetchDetails]);
|
|
1116
|
+
useEffect(() => {
|
|
1117
|
+
beforeSignatureRef.current = void 0, setPaginatedTransactions([]), setIsPaginationLoading(false), setHasMore(true);
|
|
1118
|
+
}, [key]);
|
|
1119
|
+
let fetchAndEnrichTransactions = useCallback(
|
|
1120
|
+
async (beforeSignature, currentCluster, signal) => {
|
|
1121
|
+
if (!rpcClient || !address$1)
|
|
1122
|
+
return { transactions: [], hasMore: false };
|
|
1123
|
+
throwIfAborted(signal);
|
|
1124
|
+
let rpc = rpcClient.rpc, walletAddress = address(address$1), signaturesResult = await rpc.getSignaturesForAddress(walletAddress, {
|
|
1125
|
+
limit,
|
|
1126
|
+
...beforeSignature ? { before: signature(beforeSignature) } : {}
|
|
1127
|
+
}).send();
|
|
1128
|
+
throwIfAborted(signal);
|
|
1129
|
+
let newTransactions;
|
|
1130
|
+
if (fetchDetails && signaturesResult.length > 0) {
|
|
1131
|
+
let txDetails = await mapWithConcurrency(
|
|
1132
|
+
signaturesResult,
|
|
1133
|
+
async (sig) => rpc.getTransaction(signature(String(sig.signature)), {
|
|
1134
|
+
encoding: "jsonParsed",
|
|
1135
|
+
maxSupportedTransactionVersion: 0
|
|
1136
|
+
}).send().catch(() => null),
|
|
1137
|
+
{ concurrency: detailsConcurrency, signal }
|
|
1138
|
+
);
|
|
1139
|
+
throwIfAborted(signal), newTransactions = signaturesResult.map((sig, idx) => {
|
|
1140
|
+
let blockTimeNum = sig.blockTime ? Number(sig.blockTime) : null, tx = txDetails[idx];
|
|
1141
|
+
return parseTransaction(
|
|
1142
|
+
tx,
|
|
1143
|
+
address$1,
|
|
1144
|
+
String(sig.signature),
|
|
1145
|
+
blockTimeNum,
|
|
1146
|
+
Number(sig.slot),
|
|
1147
|
+
sig.err,
|
|
1148
|
+
getTransactionUrl(String(sig.signature), currentCluster)
|
|
1149
|
+
);
|
|
1150
|
+
});
|
|
1151
|
+
} else
|
|
1152
|
+
newTransactions = signaturesResult.map((sig) => {
|
|
1153
|
+
let blockTimeNum = sig.blockTime ? Number(sig.blockTime) : null, { date, time } = formatDate(blockTimeNum);
|
|
1154
|
+
return {
|
|
1155
|
+
signature: String(sig.signature),
|
|
1156
|
+
blockTime: blockTimeNum,
|
|
1157
|
+
slot: Number(sig.slot),
|
|
1158
|
+
status: sig.err ? "failed" : "success",
|
|
1159
|
+
error: sig.err ? JSON.stringify(sig.err) : void 0,
|
|
1160
|
+
type: "unknown",
|
|
1161
|
+
formattedDate: date,
|
|
1162
|
+
formattedTime: time,
|
|
1163
|
+
explorerUrl: getTransactionUrl(String(sig.signature), currentCluster)
|
|
1164
|
+
};
|
|
1165
|
+
});
|
|
1166
|
+
let mintsToFetch = [
|
|
1167
|
+
.../* @__PURE__ */ new Set([
|
|
1168
|
+
...newTransactions.filter((tx) => tx.tokenMint).map((tx) => tx.tokenMint),
|
|
1169
|
+
...newTransactions.filter((tx) => tx.swapFromToken?.mint).map((tx) => tx.swapFromToken.mint),
|
|
1170
|
+
...newTransactions.filter((tx) => tx.swapToToken?.mint).map((tx) => tx.swapToToken.mint)
|
|
1171
|
+
])
|
|
1172
|
+
];
|
|
1173
|
+
if (mintsToFetch.length > 0) {
|
|
1174
|
+
throwIfAborted(signal);
|
|
1175
|
+
let tokenMetadata = await fetchTransactionTokenMetadata(mintsToFetch, {
|
|
1176
|
+
signal,
|
|
1177
|
+
cluster: getClusterType(currentCluster)
|
|
1178
|
+
});
|
|
1179
|
+
tokenMetadata.size > 0 && (newTransactions = newTransactions.map((tx) => {
|
|
1180
|
+
let enrichedTx = { ...tx };
|
|
1181
|
+
if (tx.tokenMint && tokenMetadata.has(tx.tokenMint)) {
|
|
1182
|
+
let meta = tokenMetadata.get(tx.tokenMint);
|
|
1183
|
+
enrichedTx = {
|
|
1184
|
+
...enrichedTx,
|
|
1185
|
+
tokenSymbol: meta.symbol,
|
|
1186
|
+
tokenIcon: transformImageUrl(meta.icon, imageProxy),
|
|
1187
|
+
formattedAmount: tx.formattedAmount ? `${tx.formattedAmount} ${meta.symbol}` : tx.formattedAmount
|
|
629
1188
|
};
|
|
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
|
-
};
|
|
1189
|
+
}
|
|
1190
|
+
if (tx.swapFromToken?.mint && tokenMetadata.has(tx.swapFromToken.mint)) {
|
|
1191
|
+
let meta = tokenMetadata.get(tx.swapFromToken.mint);
|
|
1192
|
+
enrichedTx = {
|
|
1193
|
+
...enrichedTx,
|
|
1194
|
+
swapFromToken: {
|
|
1195
|
+
...tx.swapFromToken,
|
|
1196
|
+
symbol: meta.symbol,
|
|
1197
|
+
icon: transformImageUrl(meta.icon, imageProxy)
|
|
646
1198
|
}
|
|
647
|
-
|
|
648
|
-
});
|
|
649
|
-
setTransactions(loadMore ? (prev) => [...prev.slice(0, -newTransactions.length), ...enrichedTransactions] : enrichedTransactions);
|
|
1199
|
+
};
|
|
650
1200
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1201
|
+
if (tx.swapToToken?.mint && tokenMetadata.has(tx.swapToToken.mint)) {
|
|
1202
|
+
let meta = tokenMetadata.get(tx.swapToToken.mint);
|
|
1203
|
+
enrichedTx = {
|
|
1204
|
+
...enrichedTx,
|
|
1205
|
+
swapToToken: {
|
|
1206
|
+
...tx.swapToToken,
|
|
1207
|
+
symbol: meta.symbol,
|
|
1208
|
+
icon: transformImageUrl(meta.icon, imageProxy)
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
656
1211
|
}
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
|
|
1212
|
+
return enrichedTx;
|
|
1213
|
+
}));
|
|
1214
|
+
}
|
|
1215
|
+
return {
|
|
1216
|
+
transactions: newTransactions,
|
|
1217
|
+
hasMore: newTransactions.length === limit
|
|
1218
|
+
};
|
|
1219
|
+
},
|
|
1220
|
+
[rpcClient, address$1, limit, fetchDetails, detailsConcurrency, parseTransaction, imageProxy]
|
|
1221
|
+
), queryFn = useCallback(
|
|
1222
|
+
async (signal) => {
|
|
1223
|
+
if (!connected || !address$1 || !rpcClient || !cluster)
|
|
1224
|
+
return [];
|
|
1225
|
+
throwIfAborted(signal);
|
|
1226
|
+
let result = await fetchAndEnrichTransactions(void 0, cluster, signal);
|
|
1227
|
+
return throwIfAborted(signal), result.transactions;
|
|
1228
|
+
},
|
|
1229
|
+
[connected, address$1, rpcClient, cluster, fetchAndEnrichTransactions]
|
|
1230
|
+
), {
|
|
1231
|
+
data: initialTransactions,
|
|
1232
|
+
error,
|
|
1233
|
+
isFetching: isInitialLoading,
|
|
1234
|
+
updatedAt,
|
|
1235
|
+
refetch: sharedRefetch,
|
|
1236
|
+
abort
|
|
1237
|
+
} = useSharedQuery(key, queryFn, {
|
|
1238
|
+
enabled,
|
|
1239
|
+
staleTimeMs,
|
|
1240
|
+
cacheTimeMs,
|
|
1241
|
+
refetchOnMount,
|
|
1242
|
+
refetchIntervalMs: autoRefresh ? refreshInterval : false
|
|
1243
|
+
});
|
|
1244
|
+
useEffect(() => {
|
|
1245
|
+
initialTransactions && (beforeSignatureRef.current = initialTransactions.length ? initialTransactions[initialTransactions.length - 1].signature : void 0, setHasMore(initialTransactions.length === limit), setPaginatedTransactions((prev) => prev.length ? [] : prev));
|
|
1246
|
+
}, [initialTransactions, limit]);
|
|
1247
|
+
let loadMoreFn = useCallback(async () => {
|
|
1248
|
+
if (!(!hasMore || isPaginationLoading || !cluster)) {
|
|
1249
|
+
setIsPaginationLoading(true);
|
|
1250
|
+
try {
|
|
1251
|
+
let result = await fetchAndEnrichTransactions(beforeSignatureRef.current, cluster);
|
|
1252
|
+
result.transactions.length > 0 && (beforeSignatureRef.current = result.transactions[result.transactions.length - 1].signature, setPaginatedTransactions((prev) => [...prev, ...result.transactions])), setHasMore(result.hasMore);
|
|
660
1253
|
} catch (err) {
|
|
661
|
-
|
|
1254
|
+
console.error("Failed to load more transactions:", err);
|
|
662
1255
|
} finally {
|
|
663
|
-
|
|
1256
|
+
setIsPaginationLoading(false);
|
|
664
1257
|
}
|
|
1258
|
+
}
|
|
1259
|
+
}, [hasMore, isPaginationLoading, cluster, fetchAndEnrichTransactions]), refetch = useCallback(
|
|
1260
|
+
async (opts) => {
|
|
1261
|
+
beforeSignatureRef.current = void 0, setPaginatedTransactions([]), setHasMore(true), await sharedRefetch(opts);
|
|
665
1262
|
},
|
|
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(
|
|
1263
|
+
[sharedRefetch]
|
|
1264
|
+
), transactions = useMemo(() => [...initialTransactions ?? [], ...paginatedTransactions], [initialTransactions, paginatedTransactions]), isLoading = isInitialLoading || isPaginationLoading, visibleError = updatedAt ? null : error;
|
|
1265
|
+
return useMemo(
|
|
680
1266
|
() => ({
|
|
681
1267
|
transactions,
|
|
682
1268
|
isLoading,
|
|
683
|
-
error,
|
|
1269
|
+
error: visibleError,
|
|
684
1270
|
hasMore,
|
|
685
1271
|
loadMore: loadMoreFn,
|
|
686
1272
|
refetch,
|
|
687
|
-
|
|
1273
|
+
abort,
|
|
1274
|
+
lastUpdated: updatedAt ? new Date(updatedAt) : null
|
|
688
1275
|
}),
|
|
689
|
-
[transactions, isLoading,
|
|
1276
|
+
[transactions, isLoading, visibleError, hasMore, loadMoreFn, refetch, abort, updatedAt]
|
|
690
1277
|
);
|
|
691
1278
|
}
|
|
692
|
-
function
|
|
693
|
-
|
|
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
|
-
};
|
|
1279
|
+
function getTokensQueryKey(rpcUrl, address) {
|
|
1280
|
+
return getWalletAssetsQueryKey(rpcUrl, address);
|
|
701
1281
|
}
|
|
702
|
-
var
|
|
1282
|
+
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
1283
|
constructor(maxSize, options) {
|
|
704
1284
|
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
705
1285
|
__publicField(this, "maxSize");
|
|
@@ -739,10 +1319,6 @@ var NATIVE_MINT = "So11111111111111111111111111111111111111112", CACHE_MAX_SIZE
|
|
|
739
1319
|
get size() {
|
|
740
1320
|
return this.cache.size;
|
|
741
1321
|
}
|
|
742
|
-
/**
|
|
743
|
-
* Prune stale entries based on TTL.
|
|
744
|
-
* Only works if getTtl and getTimestamp are provided.
|
|
745
|
-
*/
|
|
746
1322
|
pruneStale() {
|
|
747
1323
|
if (!this.getTtl || !this.getTimestamp) return 0;
|
|
748
1324
|
let now = Date.now(), pruned = 0;
|
|
@@ -767,27 +1343,6 @@ function stopCacheCleanup() {
|
|
|
767
1343
|
function clearTokenCaches() {
|
|
768
1344
|
metadataCache.clear(), priceCache.clear();
|
|
769
1345
|
}
|
|
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
1346
|
function calculateBackoffDelay(attempt, baseDelay, retryAfter) {
|
|
792
1347
|
if (retryAfter !== void 0 && retryAfter > 0) {
|
|
793
1348
|
let jitter2 = Math.random() * 500;
|
|
@@ -823,26 +1378,17 @@ async function fetchCoinGeckoPrices(coingeckoIds, config) {
|
|
|
823
1378
|
let elapsedTime = Date.now() - startTime;
|
|
824
1379
|
if (elapsedTime >= maxTimeout) {
|
|
825
1380
|
console.warn(
|
|
826
|
-
`[useTokens] CoinGecko API: Total timeout (${maxTimeout}ms) exceeded after ${attempt} attempts
|
|
1381
|
+
`[useTokens] CoinGecko API: Total timeout (${maxTimeout}ms) exceeded after ${attempt} attempts.`
|
|
827
1382
|
);
|
|
828
1383
|
break;
|
|
829
1384
|
}
|
|
830
1385
|
let remainingTimeout = maxTimeout - elapsedTime, requestTimeout = Math.min(1e4, remainingTimeout), { signal, cleanup } = createTimeoutSignal(requestTimeout);
|
|
831
1386
|
try {
|
|
832
|
-
let response = await fetch(url, {
|
|
833
|
-
headers,
|
|
834
|
-
signal
|
|
835
|
-
});
|
|
1387
|
+
let response = await fetch(url, { headers, signal });
|
|
836
1388
|
if (cleanup(), response.status === 429) {
|
|
837
1389
|
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
|
-
);
|
|
1390
|
+
if (console.warn(`[useTokens] CoinGecko API rate limited (429). Waiting ${Math.round(delay)}ms.`), Date.now() - startTime + delay >= maxTimeout)
|
|
844
1391
|
break;
|
|
845
|
-
}
|
|
846
1392
|
await new Promise((resolve) => setTimeout(resolve, delay)), attempt++;
|
|
847
1393
|
continue;
|
|
848
1394
|
}
|
|
@@ -853,174 +1399,195 @@ async function fetchCoinGeckoPrices(coingeckoIds, config) {
|
|
|
853
1399
|
priceData?.usd !== void 0 && (results.set(id, priceData.usd), priceCache.set(id, { price: priceData.usd, timestamp: fetchTime }));
|
|
854
1400
|
return results;
|
|
855
1401
|
} 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) {
|
|
1402
|
+
if (cleanup(), lastError = error, attempt < maxRetries) {
|
|
862
1403
|
let delay = calculateBackoffDelay(attempt, baseDelay);
|
|
863
1404
|
Date.now() - startTime + delay < maxTimeout && await new Promise((resolve) => setTimeout(resolve, delay));
|
|
864
1405
|
}
|
|
865
1406
|
attempt++;
|
|
866
1407
|
}
|
|
867
1408
|
}
|
|
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;
|
|
1409
|
+
return attempt > maxRetries && lastError && console.warn(`[useTokens] CoinGecko API: All attempts failed. Last error: ${lastError.message}`), results;
|
|
871
1410
|
}
|
|
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);
|
|
1411
|
+
async function fetchTokenMetadataHybrid(mints, coingeckoConfig, options) {
|
|
1412
|
+
if (mints.length === 0) return false;
|
|
1413
|
+
let now = Date.now(), mintsNeedingTokenList = [], staleCoingeckoIdToMint = /* @__PURE__ */ new Map();
|
|
884
1414
|
for (let mint of mints) {
|
|
885
1415
|
let cached = metadataCache.get(mint);
|
|
886
|
-
|
|
1416
|
+
if (!cached) {
|
|
1417
|
+
mintsNeedingTokenList.push(mint);
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
if (!cached.coingeckoId) continue;
|
|
1421
|
+
let priceEntry = priceCache.get(cached.coingeckoId);
|
|
1422
|
+
!!(priceEntry && now - priceEntry.timestamp < PRICE_CACHE_TTL) || staleCoingeckoIdToMint.set(cached.coingeckoId, mint);
|
|
887
1423
|
}
|
|
888
|
-
let
|
|
889
|
-
|
|
890
|
-
|
|
1424
|
+
let didUpdate = false, tokenListMetadata = await fetchSolanaTokenListMetadata(mintsNeedingTokenList, {
|
|
1425
|
+
timeoutMs: 1e4,
|
|
1426
|
+
cluster: options?.cluster
|
|
1427
|
+
});
|
|
1428
|
+
for (let [mint, meta] of tokenListMetadata) {
|
|
1429
|
+
let coingeckoId = meta.extensions?.coingeckoId, usdPrice = (coingeckoId ? priceCache.get(coingeckoId) : void 0)?.price, combined = {
|
|
891
1430
|
address: meta.address,
|
|
892
|
-
name: meta.address ===
|
|
1431
|
+
name: meta.address === NATIVE_SOL_MINT ? "Solana" : meta.name,
|
|
893
1432
|
symbol: meta.symbol,
|
|
894
1433
|
decimals: meta.decimals,
|
|
895
1434
|
logoURI: meta.logoURI,
|
|
896
1435
|
coingeckoId,
|
|
897
1436
|
usdPrice
|
|
898
|
-
};
|
|
899
|
-
|
|
1437
|
+
}, existing = metadataCache.get(mint);
|
|
1438
|
+
(!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
1439
|
}
|
|
1440
|
+
didUpdate && options?.onUpdate?.();
|
|
1441
|
+
let coingeckoIdToMint = new Map(staleCoingeckoIdToMint);
|
|
1442
|
+
for (let [mint, meta] of tokenListMetadata)
|
|
1443
|
+
meta.extensions?.coingeckoId && coingeckoIdToMint.set(meta.extensions.coingeckoId, mint);
|
|
1444
|
+
if (coingeckoIdToMint.size === 0)
|
|
1445
|
+
return didUpdate;
|
|
1446
|
+
let prices = await fetchCoinGeckoPrices([...coingeckoIdToMint.keys()], coingeckoConfig), didUpdatePrices = false;
|
|
901
1447
|
for (let [coingeckoId, mint] of coingeckoIdToMint) {
|
|
902
|
-
let cached =
|
|
1448
|
+
let cached = metadataCache.get(mint);
|
|
903
1449
|
if (cached) {
|
|
904
1450
|
let usdPrice = prices.get(coingeckoId);
|
|
905
|
-
usdPrice !== void 0 &&
|
|
1451
|
+
usdPrice !== void 0 && cached.usdPrice !== usdPrice && (didUpdate = true, didUpdatePrices = true, cached.usdPrice = usdPrice, metadataCache.set(mint, cached));
|
|
906
1452
|
}
|
|
907
1453
|
}
|
|
908
|
-
return
|
|
1454
|
+
return didUpdatePrices && options?.onUpdate?.(), didUpdate;
|
|
909
1455
|
}
|
|
910
1456
|
function formatBalance(amount, decimals) {
|
|
911
|
-
return (
|
|
912
|
-
|
|
913
|
-
maximumFractionDigits: Math.min(decimals, 6)
|
|
1457
|
+
return formatBigIntBalance(amount, decimals, {
|
|
1458
|
+
maxDecimals: Math.min(decimals, 6)
|
|
914
1459
|
});
|
|
915
1460
|
}
|
|
916
1461
|
function formatUsd(amount, decimals, usdPrice) {
|
|
917
|
-
return (
|
|
918
|
-
style: "currency",
|
|
919
|
-
currency: "USD",
|
|
920
|
-
minimumFractionDigits: 2,
|
|
921
|
-
maximumFractionDigits: 2
|
|
922
|
-
});
|
|
1462
|
+
return formatBigIntUsd(amount, decimals, usdPrice);
|
|
923
1463
|
}
|
|
924
|
-
function
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1464
|
+
function selectTokens(assets, address) {
|
|
1465
|
+
return {
|
|
1466
|
+
lamports: assets?.lamports ?? 0n,
|
|
1467
|
+
tokenAccounts: assets?.tokenAccounts ?? [],
|
|
1468
|
+
address
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
function sortByValueDesc(a, b) {
|
|
1472
|
+
let metadataSort = (b.logo ? 1 : 0) - (a.logo ? 1 : 0);
|
|
1473
|
+
if (metadataSort !== 0) return metadataSort;
|
|
1474
|
+
let aValue = Number(a.amount) / Math.pow(10, a.decimals) * (a.usdPrice ?? 0);
|
|
1475
|
+
return Number(b.amount) / Math.pow(10, b.decimals) * (b.usdPrice ?? 0) - aValue;
|
|
929
1476
|
}
|
|
930
1477
|
function useTokens(options = {}) {
|
|
931
1478
|
let {
|
|
1479
|
+
enabled = true,
|
|
932
1480
|
includeZeroBalance = false,
|
|
933
1481
|
autoRefresh = false,
|
|
934
1482
|
refreshInterval = 6e4,
|
|
935
1483
|
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]);
|
|
1484
|
+
includeNativeSol = true,
|
|
1485
|
+
staleTimeMs = 0,
|
|
1486
|
+
cacheTimeMs = 300 * 1e3,
|
|
1487
|
+
// 5 minutes
|
|
1488
|
+
refetchOnMount = "stale",
|
|
1489
|
+
client: clientOverride
|
|
1490
|
+
} = options, { address, connected } = useAccount(), { type: clusterType } = useCluster(), connectorConfig = useConnectorClient()?.getConfig(), imageProxy = connectorConfig?.imageProxy, coingeckoConfig = connectorConfig?.coingecko, selectFn = useCallback(
|
|
1491
|
+
(assets) => selectTokens(assets, address ?? ""),
|
|
1492
|
+
[address]
|
|
1493
|
+
), {
|
|
1494
|
+
data: selection,
|
|
1495
|
+
error,
|
|
1496
|
+
isFetching,
|
|
1497
|
+
updatedAt,
|
|
1498
|
+
refetch: sharedRefetch,
|
|
1499
|
+
abort
|
|
1500
|
+
} = useWalletAssets({
|
|
1501
|
+
enabled,
|
|
1502
|
+
staleTimeMs,
|
|
1503
|
+
cacheTimeMs,
|
|
1504
|
+
refetchOnMount,
|
|
1505
|
+
refetchIntervalMs: autoRefresh ? refreshInterval : false,
|
|
1506
|
+
client: clientOverride,
|
|
1507
|
+
select: selectFn
|
|
1508
|
+
}), lamports = selection?.lamports ?? 0n, tokenAccounts = selection?.tokenAccounts ?? [], walletAddress = selection?.address ?? "", baseTokens = useMemo(() => {
|
|
1509
|
+
let result = [];
|
|
1510
|
+
includeNativeSol && walletAddress && (includeZeroBalance || lamports > 0n) && result.push({
|
|
1511
|
+
mint: NATIVE_SOL_MINT,
|
|
1512
|
+
tokenAccount: walletAddress,
|
|
1513
|
+
amount: lamports,
|
|
1514
|
+
decimals: 9,
|
|
1515
|
+
formatted: formatBalance(lamports, 9),
|
|
1516
|
+
isFrozen: false,
|
|
1517
|
+
owner: walletAddress
|
|
1518
|
+
});
|
|
1519
|
+
for (let account of tokenAccounts)
|
|
1520
|
+
!includeZeroBalance && account.amount === 0n || result.push({
|
|
1521
|
+
mint: account.mint,
|
|
1522
|
+
tokenAccount: account.pubkey,
|
|
1523
|
+
amount: account.amount,
|
|
1524
|
+
decimals: account.decimals,
|
|
1525
|
+
formatted: formatBalance(account.amount, account.decimals),
|
|
1526
|
+
isFrozen: account.isFrozen,
|
|
1527
|
+
owner: account.owner,
|
|
1528
|
+
programId: account.programId
|
|
1529
|
+
});
|
|
1530
|
+
return result;
|
|
1531
|
+
}, [lamports, tokenAccounts, walletAddress, includeNativeSol, includeZeroBalance]), mints = useMemo(() => {
|
|
1532
|
+
let unique = /* @__PURE__ */ new Set();
|
|
1533
|
+
for (let token of baseTokens)
|
|
1534
|
+
unique.add(token.mint);
|
|
1535
|
+
return [...unique].sort();
|
|
1536
|
+
}, [baseTokens]), mintsKey = useMemo(() => mints.join(","), [mints]), [metadataVersion, setMetadataVersion] = useState(0);
|
|
1004
1537
|
useEffect(() => {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1538
|
+
if (!fetchMetadata || !mintsKey) return;
|
|
1539
|
+
let isMounted = true;
|
|
1540
|
+
return (async () => {
|
|
1541
|
+
try {
|
|
1542
|
+
let mintList = mintsKey.split(",");
|
|
1543
|
+
await fetchTokenMetadataHybrid(mintList, coingeckoConfig, {
|
|
1544
|
+
onUpdate: () => {
|
|
1545
|
+
isMounted && setMetadataVersion((v) => v + 1);
|
|
1546
|
+
},
|
|
1547
|
+
cluster: clusterType ?? void 0
|
|
1548
|
+
}), isMounted && setMetadataVersion((v) => v + 1);
|
|
1549
|
+
} catch (err) {
|
|
1550
|
+
console.error("[useTokens] Failed to fetch metadata:", err);
|
|
1551
|
+
}
|
|
1552
|
+
})(), () => {
|
|
1553
|
+
isMounted = false;
|
|
1554
|
+
};
|
|
1555
|
+
}, [mintsKey, fetchMetadata, coingeckoConfig, clusterType]);
|
|
1556
|
+
let tokens = useMemo(() => fetchMetadata ? baseTokens.map((token) => {
|
|
1557
|
+
let meta = metadataCache.get(token.mint);
|
|
1558
|
+
if (!meta) return token;
|
|
1559
|
+
let usdPrice = (meta.coingeckoId ? priceCache.get(meta.coingeckoId) : void 0)?.price ?? meta.usdPrice;
|
|
1560
|
+
return {
|
|
1561
|
+
...token,
|
|
1562
|
+
name: meta.name,
|
|
1563
|
+
symbol: meta.symbol,
|
|
1564
|
+
logo: transformImageUrl(meta.logoURI, imageProxy),
|
|
1565
|
+
usdPrice,
|
|
1566
|
+
formattedUsd: usdPrice ? formatUsd(token.amount, token.decimals, usdPrice) : void 0
|
|
1567
|
+
};
|
|
1568
|
+
}).sort(sortByValueDesc) : baseTokens.slice().sort(sortByValueDesc), [baseTokens, fetchMetadata, imageProxy, metadataVersion]), totalAccounts = tokenAccounts.length + (includeNativeSol ? 1 : 0);
|
|
1569
|
+
useEffect(() => (startCacheCleanup(), () => stopCacheCleanup()), []);
|
|
1011
1570
|
let wasConnectedRef = useRef(connected);
|
|
1012
|
-
|
|
1571
|
+
useEffect(() => {
|
|
1013
1572
|
wasConnectedRef.current && !connected && clearTokenCaches(), wasConnectedRef.current = connected;
|
|
1014
|
-
}, [connected])
|
|
1573
|
+
}, [connected]);
|
|
1574
|
+
let visibleError = updatedAt ? null : error, refetch = useCallback(
|
|
1575
|
+
async (opts) => {
|
|
1576
|
+
await sharedRefetch(opts);
|
|
1577
|
+
},
|
|
1578
|
+
[sharedRefetch]
|
|
1579
|
+
);
|
|
1580
|
+
return useMemo(
|
|
1015
1581
|
() => ({
|
|
1016
1582
|
tokens,
|
|
1017
|
-
isLoading,
|
|
1018
|
-
error,
|
|
1019
|
-
refetch
|
|
1020
|
-
|
|
1583
|
+
isLoading: isFetching,
|
|
1584
|
+
error: visibleError,
|
|
1585
|
+
refetch,
|
|
1586
|
+
abort,
|
|
1587
|
+
lastUpdated: updatedAt ? new Date(updatedAt) : null,
|
|
1021
1588
|
totalAccounts
|
|
1022
1589
|
}),
|
|
1023
|
-
[tokens,
|
|
1590
|
+
[tokens, isFetching, visibleError, refetch, abort, updatedAt, totalAccounts]
|
|
1024
1591
|
);
|
|
1025
1592
|
}
|
|
1026
1593
|
function DisconnectElement({
|
|
@@ -1657,6 +2224,43 @@ function WalletListElement({
|
|
|
1657
2224
|
);
|
|
1658
2225
|
}
|
|
1659
2226
|
WalletListElement.displayName = "WalletListElement";
|
|
2227
|
+
var shineStyles = `
|
|
2228
|
+
.ck-skeleton {
|
|
2229
|
+
position: relative;
|
|
2230
|
+
overflow: hidden;
|
|
2231
|
+
}
|
|
2232
|
+
.ck-skeleton-shine {
|
|
2233
|
+
position: absolute;
|
|
2234
|
+
top: 0;
|
|
2235
|
+
left: 0;
|
|
2236
|
+
width: 500%;
|
|
2237
|
+
height: 500%;
|
|
2238
|
+
background: linear-gradient(135deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.1) 50%, rgba(255,255,255,0) 100%);
|
|
2239
|
+
animation: ck-skeleton-slide 0.5s infinite;
|
|
2240
|
+
z-index: 1;
|
|
2241
|
+
pointer-events: none;
|
|
2242
|
+
}
|
|
2243
|
+
@keyframes ck-skeleton-slide {
|
|
2244
|
+
0% { transform: translate(-100%, -100%); }
|
|
2245
|
+
100% { transform: translate(100%, 100%); }
|
|
2246
|
+
}
|
|
2247
|
+
`, stylesInjected = false;
|
|
2248
|
+
function injectStyles() {
|
|
2249
|
+
if (!stylesInjected && typeof document < "u") {
|
|
2250
|
+
let styleId = "ck-skeleton-shine-styles";
|
|
2251
|
+
if (!document.getElementById(styleId)) {
|
|
2252
|
+
let style = document.createElement("style");
|
|
2253
|
+
style.id = styleId, style.textContent = shineStyles, document.head.appendChild(style);
|
|
2254
|
+
}
|
|
2255
|
+
stylesInjected = true;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
function SkeletonShine() {
|
|
2259
|
+
return React.useEffect(() => {
|
|
2260
|
+
injectStyles();
|
|
2261
|
+
}, []), /* @__PURE__ */ jsx("div", { className: "ck-skeleton-shine", "data-slot": "skeleton-shine" });
|
|
2262
|
+
}
|
|
2263
|
+
SkeletonShine.displayName = "SkeletonShine";
|
|
1660
2264
|
function BalanceElement({
|
|
1661
2265
|
showSol = true,
|
|
1662
2266
|
showTokens = false,
|
|
@@ -1699,10 +2303,10 @@ function BalanceElement({
|
|
|
1699
2303
|
"data-variant": variant,
|
|
1700
2304
|
"data-loading": "true",
|
|
1701
2305
|
children: /* @__PURE__ */ jsxs("div", { className: "ck-balance-block-skeleton", "data-slot": "balance-element-skeleton", children: [
|
|
1702
|
-
/* @__PURE__ */ jsx("div", { className: "ck-skeleton ck-skeleton--text" }),
|
|
2306
|
+
/* @__PURE__ */ jsx("div", { className: "ck-skeleton ck-skeleton--text", children: /* @__PURE__ */ jsx(SkeletonShine, {}) }),
|
|
1703
2307
|
showTokens && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1704
|
-
/* @__PURE__ */ jsx("div", { className: "ck-skeleton ck-skeleton--text ck-skeleton--short" }),
|
|
1705
|
-
/* @__PURE__ */ jsx("div", { className: "ck-skeleton ck-skeleton--text ck-skeleton--short" })
|
|
2308
|
+
/* @__PURE__ */ jsx("div", { className: "ck-skeleton ck-skeleton--text ck-skeleton--short", children: /* @__PURE__ */ jsx(SkeletonShine, {}) }),
|
|
2309
|
+
/* @__PURE__ */ jsx("div", { className: "ck-skeleton ck-skeleton--text ck-skeleton--short", children: /* @__PURE__ */ jsx(SkeletonShine, {}) })
|
|
1706
2310
|
] })
|
|
1707
2311
|
] })
|
|
1708
2312
|
}
|
|
@@ -1864,6 +2468,8 @@ function BalanceElement({
|
|
|
1864
2468
|
BalanceElement.displayName = "BalanceElement";
|
|
1865
2469
|
function TransactionHistoryElement({
|
|
1866
2470
|
limit = 5,
|
|
2471
|
+
fetchDetails = true,
|
|
2472
|
+
detailsConcurrency,
|
|
1867
2473
|
showStatus = true,
|
|
1868
2474
|
showTime = true,
|
|
1869
2475
|
className,
|
|
@@ -1873,7 +2479,11 @@ function TransactionHistoryElement({
|
|
|
1873
2479
|
render,
|
|
1874
2480
|
renderItem
|
|
1875
2481
|
}) {
|
|
1876
|
-
let { transactions, isLoading, error, hasMore, loadMore, refetch } = useTransactions({
|
|
2482
|
+
let { transactions, isLoading, error, hasMore, loadMore, refetch } = useTransactions({
|
|
2483
|
+
limit,
|
|
2484
|
+
fetchDetails,
|
|
2485
|
+
detailsConcurrency
|
|
2486
|
+
});
|
|
1877
2487
|
if (render)
|
|
1878
2488
|
return /* @__PURE__ */ jsx(Fragment, { children: render({ transactions, isLoading, error, hasMore, loadMore, refetch }) });
|
|
1879
2489
|
let statusIcon = (status) => /* @__PURE__ */ jsx(
|
|
@@ -1923,7 +2533,7 @@ function TransactionHistoryElement({
|
|
|
1923
2533
|
"data-slot": "tx-history-element",
|
|
1924
2534
|
"data-variant": variant,
|
|
1925
2535
|
"data-loading": "true",
|
|
1926
|
-
children: /* @__PURE__ */ jsx("div", { className: "ck-tx-history-skeleton", "data-slot": "tx-history-skeleton", children: Array.from({ length: Math.min(limit, 3) }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "ck-skeleton ck-skeleton--tx" }, i)) })
|
|
2536
|
+
children: /* @__PURE__ */ jsx("div", { className: "ck-tx-history-skeleton", "data-slot": "tx-history-skeleton", children: Array.from({ length: Math.min(limit, 3) }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "ck-skeleton ck-skeleton--tx", children: /* @__PURE__ */ jsx(SkeletonShine, {}) }, i)) })
|
|
1927
2537
|
}
|
|
1928
2538
|
);
|
|
1929
2539
|
if (error)
|
|
@@ -2087,7 +2697,7 @@ function TokenListElement({
|
|
|
2087
2697
|
"data-slot": "token-list-element",
|
|
2088
2698
|
"data-variant": variant,
|
|
2089
2699
|
"data-loading": "true",
|
|
2090
|
-
children: /* @__PURE__ */ jsx("div", { className: "ck-token-list-skeleton", "data-slot": "token-list-skeleton", children: Array.from({ length: Math.min(limit, 3) }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "ck-skeleton ck-skeleton--token" }, i)) })
|
|
2700
|
+
children: /* @__PURE__ */ jsx("div", { className: "ck-token-list-skeleton", "data-slot": "token-list-skeleton", children: Array.from({ length: Math.min(limit, 3) }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "ck-skeleton ck-skeleton--token", children: /* @__PURE__ */ jsx(SkeletonShine, {}) }, i)) })
|
|
2091
2701
|
}
|
|
2092
2702
|
);
|
|
2093
2703
|
if (error)
|
|
@@ -2180,6 +2790,6 @@ function TokenListElement({
|
|
|
2180
2790
|
}
|
|
2181
2791
|
TokenListElement.displayName = "TokenListElement";
|
|
2182
2792
|
|
|
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-
|
|
2793
|
+
export { AccountElement, AppProvider, BalanceElement, ClusterElement, ConnectorProvider, DisconnectElement, SkeletonShine, TokenListElement, TransactionHistoryElement, UnifiedProvider, WalletListElement, clearSharedQueryCache, clearTokenCaches, getBalanceQueryKey, getTokensQueryKey, getTransactionsQueryKey, getWalletAssetsQueryKey, invalidateSharedQuery, useAccount, useBalance, useCluster, useConnector, useConnectorClient, useGillSolanaClient, useGillTransactionSigner, useKitTransactionSigner, useSolanaClient, useTokens, useTransactionPreparer, useTransactionSigner, useTransactions, useWalletInfo };
|
|
2794
|
+
//# sourceMappingURL=chunk-TTOKQAPX.mjs.map
|
|
2795
|
+
//# sourceMappingURL=chunk-TTOKQAPX.mjs.map
|