import React from "react"; import { Link } from "react-router-dom"; import { PublicKey, StakeProgram, TokenAccountInfo } from "@solana/web3.js"; import { FetchStatus, useFetchAccountInfo, useAccountInfo, useAccountHistory, Account, } from "providers/accounts"; import { lamportsToSolString } from "utils"; import Copyable from "./Copyable"; import { displayAddress } from "utils/tx"; import { StakeAccountCards } from "components/account/StakeAccountCards"; import ErrorCard from "components/common/ErrorCard"; import LoadingCard from "components/common/LoadingCard"; import TableCardBody from "components/common/TableCardBody"; import { useFetchAccountHistory } from "providers/accounts/history"; import { useFetchAccountOwnedTokens, useAccountOwnedTokens, } from "providers/accounts/tokens"; import { useCluster, ClusterStatus } from "providers/cluster"; type Props = { address: string }; export default function AccountDetails({ address }: Props) { let pubkey: PublicKey | undefined; try { pubkey = new PublicKey(address); } catch (err) { console.error(err); // TODO handle bad addresses } return (
Details

Account

{pubkey && } {pubkey && } {pubkey && }
); } function AccountCards({ pubkey }: { pubkey: PublicKey }) { const fetchAccount = useFetchAccountInfo(); const address = pubkey.toBase58(); const info = useAccountInfo(address); const refresh = useFetchAccountInfo(); const { status } = useCluster(); // Fetch account on load React.useEffect(() => { if (pubkey && !info && status === ClusterStatus.Connected) fetchAccount(pubkey); }, [address, status]); // eslint-disable-line react-hooks/exhaustive-deps if (!info || info.status === FetchStatus.Fetching) { return ; } else if ( info.status === FetchStatus.FetchFailed || info.lamports === undefined ) { return refresh(pubkey)} text="Fetch Failed" />; } const owner = info.details?.owner; const data = info.details?.data; if (data && owner && owner.equals(StakeProgram.programId)) { return ; } else { return ; } } function UnknownAccountCard({ account }: { account: Account }) { const { details, lamports } = account; if (lamports === undefined) return null; return (

Overview

Address {displayAddress(account.pubkey.toBase58())} Balance (SOL) {lamportsToSolString(lamports)} {details && ( Data (Bytes) {details.space} )} {details && ( Owner {displayAddress(details.owner.toBase58())} )} {details && ( Executable {details.executable ? "Yes" : "No"} )}
); } function TokensCard({ pubkey }: { pubkey: PublicKey }) { const address = pubkey.toBase58(); const ownedTokens = useAccountOwnedTokens(address); const fetchAccountTokens = useFetchAccountOwnedTokens(); const refresh = () => fetchAccountTokens(pubkey); if (ownedTokens === undefined) { return null; } const { status, tokens } = ownedTokens; const fetching = status === FetchStatus.Fetching; if (fetching && (tokens === undefined || tokens.length === 0)) { return ; } else if (tokens === undefined) { return ; } if (tokens.length === 0) { return ( ); } const mappedTokens = new Map(); for (const token of tokens) { const mintAddress = token.mint.toBase58(); const tokenInfo = mappedTokens.get(mintAddress); if (tokenInfo) { tokenInfo.amount += token.amount; } else { mappedTokens.set(mintAddress, token); } } const detailsList: React.ReactNode[] = []; mappedTokens.forEach((tokenInfo, mintAddress) => { const balance = tokenInfo.amount; detailsList.push( {mintAddress} {balance} ({ ...location, pathname: "/account/" + mintAddress, })} className="btn btn-rounded-circle btn-white btn-sm" > ); }); return (

Tokens

{detailsList}
Token Address Balance Details
); } function HistoryCard({ pubkey }: { pubkey: PublicKey }) { const address = pubkey.toBase58(); const info = useAccountInfo(address); const history = useAccountHistory(address); const fetchAccountHistory = useFetchAccountHistory(); const refresh = () => fetchAccountHistory(pubkey, true); const loadMore = () => fetchAccountHistory(pubkey); if (!info || !history || info.lamports === undefined) { return null; } else if ( history.fetched === undefined || history.fetchedRange === undefined ) { if (history.status === FetchStatus.Fetching) { return ; } return ( ); } if (history.fetched.length === 0) { if (history.status === FetchStatus.Fetching) { return ; } return ( ); } const detailsList: React.ReactNode[] = []; const transactions = history.fetched; for (var i = 0; i < transactions.length; i++) { const slot = transactions[i].status.slot; const slotTransactions = [transactions[i]]; while (i + 1 < transactions.length) { const nextSlot = transactions[i + 1].status.slot; if (nextSlot !== slot) break; slotTransactions.push(transactions[++i]); } slotTransactions.forEach(({ signature, status }, index) => { let statusText; let statusClass; if (status.err) { statusClass = "warning"; statusText = "Failed"; } else { statusClass = "success"; statusText = "Success"; } detailsList.push( {index === 0 ? ( {slot} ) : ( )} {statusText} {signature} ({ ...location, pathname: "/tx/" + signature, })} className="btn btn-rounded-circle btn-white btn-sm" > ); }); } const fetching = history.status === FetchStatus.Fetching; return (

Transaction History

{detailsList}
Slot Result Transaction Signature Details
); }