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 (
{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
Token Address |
Balance |
Details |
{detailsList}
);
}
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
Slot |
Result |
Transaction Signature |
Details |
{detailsList}
);
}