diff --git a/src/components/AccountTransactions.tsx b/src/components/AccountTransactions.tsx index 46f61c1..6f83c84 100644 --- a/src/components/AccountTransactions.tsx +++ b/src/components/AccountTransactions.tsx @@ -20,7 +20,8 @@ import { Hash } from "./Hash"; import { PageLink } from "./PageLink"; interface Tx { - txId: string; + txId?: string; // Legacy field + id?: string; // New field wrapperId: string; kind: string; data: string; @@ -156,7 +157,7 @@ export const AccountTransactions = ({ address }: AccountTransactionsProps) => { if (error) { return ( - + Error Failed to load transactions. Please try again. @@ -179,6 +180,7 @@ export const AccountTransactions = ({ address }: AccountTransactionsProps) => { Hash Type + Direction Status Block Amount @@ -193,7 +195,7 @@ export const AccountTransactions = ({ address }: AccountTransactionsProps) => { const tokenSymbol = token ? chainAssetsMap[token]?.symbol : ""; return ( { { + {tx.kind || "-"} + + + + } + content={} /> { + const navigate = useNavigate(); + const { data: chainAssetsMap } = useChainAssetsMap(); + const blockInfo = useBlockInfo((inner as any).blockHeight); + + // Parse token and amount for transfer types + const getTransferInfo = () => { + if (!inner.data || !inner.kind) return { token: null, amount: null }; + + const transferKinds = [ + "transparentTransfer", + "shieldingTransfer", + "unshieldingTransfer", + "ibcShieldingTransfer", + "ibcUnshieldingTransfer", + "ibcTransparentTransfer", + ]; + + if (!transferKinds.includes(inner.kind)) return { token: null, amount: null }; + + try { + const parsedData = typeof inner.data === "string" ? JSON.parse(inner.data) : inner.data; + let token: string | null = null; + let amount: string | null = null; + + if (Array.isArray(parsedData)) { + const sourceSection = parsedData.find((section: any) => section.sources); + const targetSection = parsedData.find((section: any) => section.targets); + if (sourceSection?.sources?.[0]) { + token = sourceSection.sources[0].token; + amount = sourceSection.sources[0].amount; + } + if (targetSection?.targets?.[0] && !amount) { + token = targetSection.targets[0].token; + amount = targetSection.targets[0].amount; + } + } else if (parsedData?.sources?.[0] || parsedData?.targets?.[0]) { + if (parsedData.sources?.[0]) { + token = parsedData.sources[0].token; + amount = parsedData.sources[0].amount; + } + if (parsedData.targets?.[0] && !amount) { + token = parsedData.targets[0].token; + amount = parsedData.targets[0].amount; + } + } + + return { token, amount }; + } catch { + return { token: null, amount: null }; + } + }; + + const { token, amount } = getTransferInfo(); + const tokenAsset = token ? chainAssetsMap?.[token] : null; + + const transferKinds = [ + "transparentTransfer", + "shieldingTransfer", + "unshieldingTransfer", + "ibcShieldingTransfer", + "ibcUnshieldingTransfer", + "ibcTransparentTransfer", + ]; + const isTransferType = inner.kind && transferKinds.includes(inner.kind); + + return ( + navigate(transactionUrl(inner.id))} + > + + + + {getAgeFromTimestamp(blockInfo.data.timestamp)} + + + + + {formatTimestamp(parseInt(blockInfo.data.timestamp, 10))} + + + + ) : ( + - + ) + } + /> + + {(inner as any).blockHeight ? (inner as any).blockHeight.toLocaleString() : "-"} + + } + /> + + + {inner.id ? `${inner.id.slice(0, 6)}...${inner.id.slice(-6)}` : "-"} + + + } + /> + + {formatNumberWithCommasAndDecimals(toDisplayAmount( + tokenAsset as Asset, + new BigNumber(amount), + ))} + + ) : amount ? ( + {amount} + ) : ( + - + ) + ) : ( + - + ) + } + /> + + {tokenAsset.symbol} + + ) : token ? ( + + ) : ( + - + ) + ) : ( + - + ) + } + /> + + {inner.kind || "-"} + + } + /> + } + /> + + ); +}; + + diff --git a/src/components/Search.tsx b/src/components/Search.tsx index 7463271..cd89beb 100644 --- a/src/components/Search.tsx +++ b/src/components/Search.tsx @@ -43,9 +43,7 @@ export const Search = () => { const [isFocused, setIsFocused] = useState(false); const [debouncedSearch] = useDebounce(searchValue, 500); const search = useSearchValue(debouncedSearch.toLowerCase()); - const { matches: matchedValidators } = useValidatorNameMatchesFuzzy( - debouncedSearch, - ); + const { matches: matchedValidators } = useValidatorNameMatchesFuzzy(debouncedSearch); const emptyResults = search.isSuccess && search.data.transactions.length + diff --git a/src/components/TransactionCard.tsx b/src/components/TransactionCard.tsx index 431215c..f76de6c 100644 --- a/src/components/TransactionCard.tsx +++ b/src/components/TransactionCard.tsx @@ -1,11 +1,12 @@ import { useTransaction } from "../queries/useTransaction"; -import { Box, Grid, Skeleton, VStack } from "@chakra-ui/react"; +import { Box, Grid, Skeleton, VStack, Text, Tooltip } from "@chakra-ui/react"; import { Data } from "./Data"; -import { Hash } from "./Hash"; import { AccountLink } from "./AccountLink"; import { useNavigate } from "react-router"; import { TransactionStatusBadge } from "./TransactionStatusBadge"; import { transactionUrl } from "../routes"; +import { getAgeFromTimestamp, formatTimestamp } from "../utils"; +import { useBlockInfo } from "../queries/useBlockInfo"; import type { InnerTransaction } from "../types"; type TransactionListProps = { @@ -29,6 +30,7 @@ const allInnerTxApplied = (innerTransactions: InnerTransaction[]): string => { export const TransactionCard = ({ hash }: TransactionListProps) => { const navigate = useNavigate(); const transaction = useTransaction(hash); + const blockInfo = useBlockInfo(transaction.data?.blockHeight); if (transaction.isLoading) { return ; @@ -46,16 +48,49 @@ export const TransactionCard = ({ hash }: TransactionListProps) => { py={4} px={6} rounded="sm" - templateColumns="3fr 1fr 1fr 1fr 1fr" + templateColumns="1fr 1fr 2fr 1fr 1fr 1fr 2fr" cursor="pointer" _hover={{ bg: "gray.700" }} onClick={() => navigate(transactionUrl(hash))} > + + + + {getAgeFromTimestamp(blockInfo.data.timestamp)} + + + + + {formatTimestamp(parseInt(blockInfo.data.timestamp, 10))} + + + + ) : ( + - + ) + } + /> + + {transaction.data?.blockHeight ? transaction.data.blockHeight.toLocaleString() : "-"} + + } + /> - + + {(transaction.data?.txId || transaction.data?.id) ? + `${(transaction.data?.txId || transaction.data?.id)!.slice(0, 6)}...${(transaction.data?.txId || transaction.data?.id)!.slice(-6)}` : + "-"} + } /> diff --git a/src/components/TransactionDetailsData.tsx b/src/components/TransactionDetailsData.tsx index fbd80db..0839477 100644 --- a/src/components/TransactionDetailsData.tsx +++ b/src/components/TransactionDetailsData.tsx @@ -33,7 +33,7 @@ type WrapperTxContext = { // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type const createValueMap = (wrapperContext?: WrapperTxContext): Record => { const { data: chainAssetsMap } = useChainAssetsMap(); - const { data: validators } = useAllValidators(); + const { data: validators } = useAllValidators({ refetchInterval: undefined }); return { validator: (address: string) => { diff --git a/src/pages/Account.tsx b/src/pages/Account.tsx index c8079ae..d093717 100644 --- a/src/pages/Account.tsx +++ b/src/pages/Account.tsx @@ -64,7 +64,7 @@ export const Account = () => { useDelegations(address!); const { data: transactionsData } = useAccountTransactions(address ?? ""); - const allValidators = useAllValidators(); + const allValidators = useAllValidators({ refetchInterval: undefined }); const matchingValidator: Validator | undefined = useMemo(() => { const list = (allValidators.data as Validator[]) || []; return list.find((v: Validator) => v.address === address); @@ -123,7 +123,7 @@ export const Account = () => { if (accountError) { return ( - + Error Failed to load account details. Please check the address and try diff --git a/src/pages/MaspTransactions.tsx b/src/pages/MaspTransactions.tsx index d35b00e..b5d7994 100644 --- a/src/pages/MaspTransactions.tsx +++ b/src/pages/MaspTransactions.tsx @@ -5,7 +5,6 @@ import { useNavigate, useSearchParams } from "react-router"; import { FaShieldAlt } from "react-icons/fa"; import { useRecentTransactions } from "../queries/useRecentTransactions"; import { useChainAssetsMap } from "../queries/useChainAssetsMap"; -import { Pagination } from "../components/Pagination"; import { Hash } from "../components/Hash"; import { AccountLink } from "../components/AccountLink"; import { transactionUrl } from "../routes"; @@ -65,75 +64,85 @@ export const MaspTransactions = () => { ? selectedTokensArray.join(",") : undefined; - const { data: recentPage, isLoading, error } = useRecentTransactions( - currentPage, + const perBatch = 30; + const offset = (currentPage - 1) * perBatch; + + const { data: wrappers, isLoading, error } = useRecentTransactions( + offset, maspKindsParam, tokenParam, ); const { data: chainAssetsMap } = useChainAssetsMap(); // Transform API results into enriched MASP transaction rows (parse data for token/amount/source/target) - const baseTransactions = (recentPage?.results || []).map((inner) => { - let source: string | undefined; - let target: string | undefined; - let amount: string | undefined; - let token: string | undefined; - - if (inner.kind === "shieldedTransfer") { - source = "MASP"; - target = "MASP"; - } else if (inner.data) { - try { - const parsedData = typeof inner.data === "string" ? JSON.parse(inner.data) : inner.data; - if (Array.isArray(parsedData)) { - const sourceSection = parsedData.find((section: any) => section.sources); - const targetSection = parsedData.find((section: any) => section.targets); - if (sourceSection?.sources?.[0]) { - source = sourceSection.sources[0].owner; - amount = sourceSection.sources[0].amount; - token = sourceSection.sources[0].token; - } - if (targetSection?.targets?.[0]) { - target = targetSection.targets[0].owner; - if (!amount) amount = targetSection.targets[0].amount; - if (!token) token = targetSection.targets[0].token; - } - } else if (parsedData?.sources?.[0] || parsedData?.targets?.[0]) { - if (parsedData.sources?.[0]) { - source = parsedData.sources[0].owner; - amount = parsedData.sources[0].amount; - token = parsedData.sources[0].token; - } - if (parsedData.targets?.[0]) { - target = parsedData.targets[0].owner; - if (!amount) amount = parsedData.targets[0].amount; - if (!token) token = parsedData.targets[0].token; - } + const baseTransactions = (wrappers || []).flatMap((wrapper) => { + const blockHeight = wrapper.blockHeight; + return (wrapper.innerTransactions || []) + .filter((inner) => selectedKindsArray.includes(inner.kind)) + .map((inner) => { + let source: string | undefined; + let target: string | undefined; + let amount: string | undefined; + let token: string | undefined; + + if (inner.kind === "shieldedTransfer") { + source = "MASP"; + target = "MASP"; + } else if (inner.data) { + try { + const parsedData = typeof inner.data === "string" ? JSON.parse(inner.data) : inner.data; + if (Array.isArray(parsedData)) { + const sourceSection = parsedData.find((section: any) => section.sources); + const targetSection = parsedData.find((section: any) => section.targets); + if (sourceSection?.sources?.[0]) { + source = sourceSection.sources[0].owner; + amount = sourceSection.sources[0].amount; + token = sourceSection.sources[0].token; + } + if (targetSection?.targets?.[0]) { + target = targetSection.targets[0].owner; + if (!amount) amount = targetSection.targets[0].amount; + if (!token) token = targetSection.targets[0].token; + } + } else if (parsedData?.sources?.[0] || parsedData?.targets?.[0]) { + if (parsedData.sources?.[0]) { + source = parsedData.sources[0].owner; + amount = parsedData.sources[0].amount; + token = parsedData.sources[0].token; + } + if (parsedData.targets?.[0]) { + target = parsedData.targets[0].owner; + if (!amount) amount = parsedData.targets[0].amount; + if (!token) token = parsedData.targets[0].token; + } + } + } catch {} } - } catch {} - } - return { - txId: inner.txId, // inner tx id - innerTxId: inner.txId, - blockHeight: inner.blockHeight, - kind: inner.kind, - exitCode: inner.exitCode, - source, - target, - amount, - token, - } as { - txId: string; - innerTxId: string; - blockHeight: number; - kind: string; - exitCode: string; - source?: string; - target?: string; - amount?: string; - token?: string; - }; + return { + txId: inner.id, + id: inner.id, + innerTxId: inner.id, + blockHeight, + kind: inner.kind, + exitCode: inner.exitCode, + source, + target, + amount, + token, + } as { + txId: string; + id: string; + innerTxId: string; + blockHeight: number; + kind: string; + exitCode: string; + source?: string; + target?: string; + amount?: string; + token?: string; + }; + }); }); // Fetch unique block timestamps for the current page @@ -153,9 +162,9 @@ export const MaspTransactions = () => { const heightToTimestamp = new Map((blocksForPage || []).map((b) => [b.height, b.timestamp])); const transactions = baseTransactions.map((t) => ({ ...t, timestamp: heightToTimestamp.get(t.blockHeight) })); - const totalItems = recentPage?.pagination?.totalItems || 0; - const perPage = recentPage?.pagination?.perPage || 30; - const totalPages = recentPage?.pagination?.totalPages || 0; + const totalInBatch = baseTransactions.length; + const hasPrev = currentPage > 1; + const hasNext = (wrappers?.length || 0) === perBatch; // Update URL when page changes useEffect(() => { @@ -250,7 +259,7 @@ export const MaspTransactions = () => { MASP Transactions - + Error Failed to load MASP transactions. Please try again. @@ -270,7 +279,7 @@ export const MaspTransactions = () => { - Showing {((currentPage - 1) * perPage) + 1} - {Math.min(currentPage * perPage, totalItems)} of {totalItems} MASP transactions + Showing {totalInBatch} MASP transactions from {offset + 1}-{offset + (wrappers?.length || 0)} most recent wrappers {hideIbcShieldingRejections && ( ({transactions.length - filteredTransactions.length} hidden) )} @@ -382,11 +391,11 @@ export const MaspTransactions = () => { return ( navigate(transactionUrl(tx.innerTxId || tx.txId))} + onClick={() => navigate(transactionUrl(tx.innerTxId || tx.txId || tx.id))} > {tx.timestamp ? ( @@ -486,7 +495,7 @@ export const MaspTransactions = () => { {(() => { - const hash = tx.innerTxId || tx.txId; + const hash = tx.innerTxId || tx.txId || tx.id; return hash ? `${hash.slice(0, 6)}...${hash.slice(-6)}` : "-"; })()} @@ -499,16 +508,39 @@ export const MaspTransactions = () => { )} - {totalPages > 1 && ( - - setCurrentPage(page)} - /> - - )} + + {!hasNext && (wrappers?.length || 0) > 0 && ( + + No additional matching transactions + + )} + + + + + )} diff --git a/src/pages/TransactionDetails.tsx b/src/pages/TransactionDetails.tsx index 41dbde7..1487f3d 100644 --- a/src/pages/TransactionDetails.tsx +++ b/src/pages/TransactionDetails.tsx @@ -36,7 +36,7 @@ export const TransactionDetails = () => { if (transaction.isError) { return ( - + Error Failed to load transaction. Please check the address and try again. @@ -135,7 +135,7 @@ export const TransactionDetails = () => { {txData.innerTransactions.map( (innerTransaction: InnerTransaction, index: number) => ( { - const { data, isLoading } = useTransaction(txId); - - if (isLoading || !data) { - return <>; - } +import { useMemo, useState } from "react"; +import { useRecentTransactions } from "../queries/useRecentTransactions"; +import { TransactionCard } from "../components/TransactionCard"; +import { InnerTransactionListCard } from "../components/InnerTransactionListCard"; +import { useChainAssetsMap } from "../queries/useChainAssetsMap"; - return ( - - - - {shortenHashOrAddress(data.txId, 15)} - - } - /> - - {formatNumberWithCommas(data.blockHeight)} - - } - /> - } - /> - - - ); -}; +export const Transactions = () => { + const [currentPage, setCurrentPage] = useState(1); + const perBatch = 30; + const offset = (currentPage - 1) * perBatch; -const BlockTransactions = ({ blockHeight }: { blockHeight: number }) => { - const block = useBlockInfo(blockHeight); - if (block.isLoading || !block.data) { - return <>; - } - return ( - <> - {block.data.transactions.map((txHash: string) => ( - - ))} - - ); -}; + // Filters (apply only in inner view) + const [selectedTokens, setSelectedTokens] = useState>(new Set()); + const [selectedKinds, setSelectedKinds] = useState>(new Set()); + const [viewMode, setViewMode] = useState<"wrapper" | "inner">("wrapper"); + + const selectedKindsArray = selectedKinds.size > 0 ? Array.from(selectedKinds) : []; + const selectedTokensArray = selectedTokens.size > 0 ? Array.from(selectedTokens) : []; + const kindParam = viewMode === "inner" && selectedKindsArray.length > 0 ? selectedKindsArray.join(",") : undefined; + const tokenParam = viewMode === "inner" && selectedTokensArray.length > 0 ? selectedTokensArray.join(",") : undefined; + + const { data: wrappers, isLoading, isError } = useRecentTransactions(offset, kindParam, tokenParam, 10000); + const { data: chainAssetsMap } = useChainAssetsMap(); + + const innerWithWrapper = useMemo(() => { + return (wrappers || []).flatMap((w) => (w.innerTransactions || []).map((inner) => ({ + id: inner.id, + kind: inner.kind, + exitCode: inner.exitCode, + data: inner.data, + blockHeight: w.blockHeight, + wrapperId: w.id, + }))); + }, [wrappers]); + + const hasPrev = currentPage > 1; + const hasNext = (wrappers?.length || 0) === perBatch; + + // Build token list from asset map + const assetEntries = Object.entries(chainAssetsMap || {}); + const assetList = assetEntries + .map(([address, asset]) => ({ address, symbol: (asset as any)?.symbol || (asset as any)?.name || address })) + .sort((a, b) => a.symbol.localeCompare(b.symbol)); + + // Known kinds (baseline), augmented with kinds present in current batch + const baseKinds = [ + "bond", + "unbond", + "transparentTransfer", + "shieldingTransfer", + "unshieldingTransfer", + "shieldedTransfer", + "ibcMsgTransfer", + "ibcUnshieldingTransfer", + "ibcShieldingTransfer", + "ibcTransparentTransfer", + "claimRewards", + ]; + const kindsFromBatch = Array.from(new Set((wrappers || []).flatMap((w) => (w.innerTransactions || []).map((i) => i.kind)))); + const transactionKinds = Array.from(new Set([...baseKinds, ...kindsFromBatch])); + + // Transfer kinds that can be filtered by token + const transferKinds = [ + "transparentTransfer", + "shieldingTransfer", + "unshieldingTransfer", + "ibcShieldingTransfer", + "ibcUnshieldingTransfer", + "ibcTransparentTransfer", + ]; + + const hasTokenFilter = selectedTokens.size > 0; -const LatestBlockTransactions = ({ - lastBlockNumber, -}: { - lastBlockNumber: number; -}) => { - const blocksAmount = 20; - const renderBlocks = (page: number) => { - const startBlock = lastBlockNumber - (page - 1) * blocksAmount; - const endBlock = Math.max(startBlock - blocksAmount + 1, 1); - const blocks = []; - for (let n = startBlock; n >= endBlock; n--) { - blocks.push(); + const toggleToken = (token: string) => { + const next = new Set(selectedTokens); + if (next.has(token)) { + next.delete(token); + } else { + next.add(token); } - return blocks; + setSelectedTokens(next); + setCurrentPage(1); + }; + + const toggleKind = (kind: string) => { + const next = new Set(selectedKinds); + if (next.has(kind)) { + next.delete(kind); + } else { + next.add(kind); + } + setSelectedKinds(next); + setCurrentPage(1); + }; + + const resetTokenFilters = () => { + setSelectedTokens(new Set()); + setCurrentPage(1); + }; + + const resetKindFilters = () => { + setSelectedKinds(new Set()); + setCurrentPage(1); }; - return ( - - {renderBlocks(1)} - - ); -}; -export const Transactions = () => { - const latestBlock = useLatestBlock(); return ( @@ -90,10 +113,183 @@ export const Transactions = () => { Latest transactions - {latestBlock.isLoading ? ( - + + + + + + + + + + {viewMode === "wrapper" + ? ( + <>Showing {offset + 1}-{offset + (wrappers?.length || 0)} most recent wrapper txs + ) : ( + <>Showing {innerWithWrapper.length} inner transactions from {offset + 1}-{offset + (wrappers?.length || 0)} most recent wrappers + )} + + + + + + + + + + + + {assetList.map(({ address, symbol }) => ( + toggleToken(address)}> + + + + {symbol} + + ))} + + + + + + + + + + + + + + {transactionKinds.map((kind) => { + const isTransferKind = transferKinds.includes(kind); + const isDisabled = hasTokenFilter && !isTransferKind; + const isSelected = selectedKinds.has(kind); + + return ( + !isDisabled && toggleKind(kind)} + opacity={isDisabled ? 0.5 : 1} + > + + + + + {kind} + + + ); + })} + + + + + + + + + {isLoading ? ( + + + Loading transactions... + + ) : isError ? ( + + Error + Failed to load transactions. Please try again. + ) : ( - + <> + {(viewMode === "wrapper" && (!wrappers || wrappers.length === 0)) || (viewMode === "inner" && innerWithWrapper.length === 0) ? ( + + No transactions + + ) : ( + + {viewMode === "wrapper" && wrappers?.map((w) => ( + + ))} + {viewMode === "inner" && innerWithWrapper.map((itx) => ( + + ))} + + )} + + + {!hasNext && (wrappers?.length || 0) > 0 && ( + No additional {viewMode === "wrapper" ? "transactions" : "inner transactions"} + )} + + + + + + )} ); diff --git a/src/pages/ValidatorDetail.tsx b/src/pages/ValidatorDetail.tsx index 8cac8e9..f1a0f23 100644 --- a/src/pages/ValidatorDetail.tsx +++ b/src/pages/ValidatorDetail.tsx @@ -38,7 +38,7 @@ export const ValidatorDetail = () => { const { address } = useParams<{ address: string }>(); // We currently don't have an endpoint to fetch a single validator by address, - const validators = useAllValidators(); + const validators = useAllValidators({ refetchInterval: undefined }); const validator = useMemo( () => validators.data?.find((v: Validator) => v.address === address), @@ -83,7 +83,7 @@ export const ValidatorDetail = () => { if (bondsError || unbondsError) { return ( - + Error Failed to load validator bonds data. Please try again. diff --git a/src/pages/Validators.tsx b/src/pages/Validators.tsx index edd107c..8ed924d 100644 --- a/src/pages/Validators.tsx +++ b/src/pages/Validators.tsx @@ -127,7 +127,7 @@ export const Validators = () => { if (error) { return ( - + Error Failed to load validators. Please try again. diff --git a/src/queries/useAccount.ts b/src/queries/useAccount.ts index abe3d36..c53d856 100644 --- a/src/queries/useAccount.ts +++ b/src/queries/useAccount.ts @@ -1,9 +1,22 @@ -import { useSimpleGet } from "./useSimpleGet"; +import { useQuery } from "@tanstack/react-query"; +import { get } from "../http/query"; export const useAccount = (address: string) => { - return useSimpleGet( - `account-${address}`, - `/account/${address}`, - undefined // Don't auto-refetch account data - ); + return useQuery({ + queryKey: ["account", address], + queryFn: async () => { + const data = await get(`/account/${address}`); + if (Array.isArray(data)) { + return data.map((item: any) => ({ + ...item, + tokenAddress: + typeof item?.tokenAddress === "string" + ? item.tokenAddress + : item?.tokenAddress?.address ?? "", + })); + } + return data; + }, + refetchInterval: undefined, + }); }; \ No newline at end of file diff --git a/src/queries/useAllValidators.tsx b/src/queries/useAllValidators.tsx index 4020172..c8f0754 100644 --- a/src/queries/useAllValidators.tsx +++ b/src/queries/useAllValidators.tsx @@ -3,13 +3,23 @@ import Fuse from "fuse.js"; import { useMemo } from "react"; import type { Validator } from "../types"; -export const useAllValidators = () => { - return useSimpleGet("all-validators", `/pos/validator/all`); +export const useAllValidators = (options?: { + enabled?: boolean; + refetchInterval?: number | undefined; +}) => { + return useSimpleGet( + "all-validators", + `/pos/validator/all`, + options?.refetchInterval, + options?.enabled ?? true, + ); }; // Fuzzy search hook for validator names using Fuse.js export const useValidatorNameMatchesFuzzy = (query: string) => { - const all = useAllValidators(); + const trimmed = query.trim(); + const shouldFetch = trimmed.length >= 3; + const all = useAllValidators({ enabled: shouldFetch, refetchInterval: undefined }); const index = useMemo(() => { if (!all.data) return null; diff --git a/src/queries/useRecentTransactions.ts b/src/queries/useRecentTransactions.ts index 16e4ae1..2e69966 100644 --- a/src/queries/useRecentTransactions.ts +++ b/src/queries/useRecentTransactions.ts @@ -1,14 +1,16 @@ import { useQuery } from "@tanstack/react-query"; import { get } from "../http/query"; -import type { RecentTransactionsResponse } from "../types"; +import type { WrapperTransaction } from "../types"; export const useRecentTransactions = ( - page = 1, + offset = 0, kind?: string, - token?: string + token?: string, + refetchInterval?: number, ) => { const queryParams = new URLSearchParams(); - queryParams.append("page", page.toString()); + queryParams.append("offset", offset.toString()); + queryParams.append("size", "30"); if (kind) { queryParams.append("kind", kind); } @@ -16,16 +18,16 @@ export const useRecentTransactions = ( queryParams.append("token", token); } - const url = `/chain/recent-inner?${queryParams.toString()}`; - const queryId = `recent-transactions-${page}-${kind || "all"}-${token || "all"}`; + const url = `/chain/wrapper/recent?${queryParams.toString()}`; + const queryId = `recent-wrappers-${offset}-${kind || "all"}-${token || "all"}`; - return useQuery({ + return useQuery({ queryKey: [queryId, url], queryFn: async () => { return get(url); }, staleTime: Infinity, gcTime: Infinity, - refetchInterval: false, // Disable automatic refetching + refetchInterval: refetchInterval ?? false, // Disable automatic refetching }); }; diff --git a/src/queries/useSimpleGet.ts b/src/queries/useSimpleGet.ts index 3851f47..b072a3a 100644 --- a/src/queries/useSimpleGet.ts +++ b/src/queries/useSimpleGet.ts @@ -4,7 +4,8 @@ import { useQuery } from "@tanstack/react-query"; export const useSimpleGet = ( id: string, url: string, - refetchInterval: number | undefined = 10000 + refetchInterval: number | undefined = 10000, + enabled: boolean = true ) => { return useQuery({ queryKey: [id, url], @@ -12,5 +13,6 @@ export const useSimpleGet = ( return get(url); }, refetchInterval, + enabled, }); }; diff --git a/src/types.ts b/src/types.ts index b96a3ca..6b3de5d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ export type InnerTransaction = { - txId: string; + txId?: string; // Legacy field + id?: string; // New field kind: string; data: string; memo: string; @@ -142,7 +143,8 @@ export type RegistryDenomUnit = { } export type InnerTransactionWithHeight = { - txId: string; + txId?: string; // Legacy field + id?: string; // New field kind: string; data: string; memo: string; @@ -161,3 +163,24 @@ export type RecentTransactionsResponse = { results: InnerTransactionWithHeight[]; pagination: Pagination; }; + +export type WrapperInnerTransaction = { + id: string; + kind: string; + data?: string; + memo?: string; + exitCode: string; +}; + +export type WrapperTransaction = { + id: string; + feePayer?: string; + feeToken?: { address: string } | string; + gasLimit?: string; + gasUsed?: number; + amountPerGasUnit?: number; + blockHeight: number; + innerTransactions: WrapperInnerTransaction[]; + exitCode: string; + atomic?: boolean; +};