2020-08-08 08:02:01 -07:00
|
|
|
import { PublicKey } from "@solana/web3.js";
|
2022-08-26 09:49:37 -07:00
|
|
|
import { AnchorAccountCard } from "components/account/AnchorAccountCard";
|
|
|
|
import { AnchorProgramCard } from "components/account/AnchorProgramCard";
|
|
|
|
import { BlockhashesCard } from "components/account/BlockhashesCard";
|
|
|
|
import { ConfigAccountSection } from "components/account/ConfigAccountSection";
|
|
|
|
import { DomainsCard } from "components/account/DomainsCard";
|
|
|
|
import { TokenInstructionsCard } from "components/account/history/TokenInstructionsCard";
|
|
|
|
import { TokenTransfersCard } from "components/account/history/TokenTransfersCard";
|
|
|
|
import { TransactionHistoryCard } from "components/account/history/TransactionHistoryCard";
|
|
|
|
import { MetaplexMetadataCard } from "components/account/MetaplexMetadataCard";
|
|
|
|
import { MetaplexNFTAttributesCard } from "components/account/MetaplexNFTAttributesCard";
|
|
|
|
import { MetaplexNFTHeader } from "components/account/MetaplexNFTHeader";
|
|
|
|
import { NonceAccountSection } from "components/account/NonceAccountSection";
|
|
|
|
import { OwnedTokensCard } from "components/account/OwnedTokensCard";
|
|
|
|
import { RewardsCard } from "components/account/RewardsCard";
|
|
|
|
import { SecurityCard } from "components/account/SecurityCard";
|
|
|
|
import { SlotHashesCard } from "components/account/SlotHashesCard";
|
2020-08-07 23:45:57 -07:00
|
|
|
import { StakeAccountSection } from "components/account/StakeAccountSection";
|
2022-08-26 09:49:37 -07:00
|
|
|
import { StakeHistoryCard } from "components/account/StakeHistoryCard";
|
|
|
|
import { SysvarAccountSection } from "components/account/SysvarAccountSection";
|
2020-08-08 08:02:01 -07:00
|
|
|
import { TokenAccountSection } from "components/account/TokenAccountSection";
|
2020-08-08 09:29:42 -07:00
|
|
|
import { TokenHistoryCard } from "components/account/TokenHistoryCard";
|
2020-08-12 10:31:21 -07:00
|
|
|
import { TokenLargestAccountsCard } from "components/account/TokenLargestAccountsCard";
|
2022-08-26 09:49:37 -07:00
|
|
|
import { UnknownAccountCard } from "components/account/UnknownAccountCard";
|
|
|
|
import { UpgradeableLoaderAccountSection } from "components/account/UpgradeableLoaderAccountSection";
|
2020-10-10 01:03:45 -07:00
|
|
|
import { VoteAccountSection } from "components/account/VoteAccountSection";
|
|
|
|
import { VotesCard } from "components/account/VotesCard";
|
2022-08-26 09:49:37 -07:00
|
|
|
import { ErrorCard } from "components/common/ErrorCard";
|
2021-04-12 11:25:51 -07:00
|
|
|
import { Identicon } from "components/common/Identicon";
|
2022-08-26 09:49:37 -07:00
|
|
|
import { LoadingCard } from "components/common/LoadingCard";
|
|
|
|
import {
|
|
|
|
Account,
|
|
|
|
TokenProgramData,
|
|
|
|
useAccountInfo,
|
|
|
|
useFetchAccountInfo,
|
|
|
|
useMintAccountInfo,
|
|
|
|
} from "providers/accounts";
|
|
|
|
import { useFlaggedAccounts } from "providers/accounts/flagged-accounts";
|
2021-11-01 21:30:26 -07:00
|
|
|
import isMetaplexNFT from "providers/accounts/utils/isMetaplexNFT";
|
2022-04-06 10:22:49 -07:00
|
|
|
import { useAnchorProgram } from "providers/anchor";
|
2022-08-26 09:49:37 -07:00
|
|
|
import { CacheEntry, FetchStatus } from "providers/cache";
|
|
|
|
import { ClusterStatus, useCluster } from "providers/cluster";
|
|
|
|
import { useTokenRegistry } from "providers/mints/token-registry";
|
|
|
|
import React, { Suspense } from "react";
|
|
|
|
import { NavLink, Redirect, useLocation } from "react-router-dom";
|
|
|
|
import { clusterPath } from "utils/url";
|
|
|
|
import { NFTokenAccountHeader } from "../components/account/nftoken/NFTokenAccountHeader";
|
|
|
|
import { NFTokenAccountSection } from "../components/account/nftoken/NFTokenAccountSection";
|
|
|
|
import { NFTokenCollectionNFTGrid } from "../components/account/nftoken/NFTokenCollectionNFTGrid";
|
|
|
|
import { NFTOKEN_ADDRESS } from "../components/account/nftoken/nftoken";
|
|
|
|
import {
|
|
|
|
isNFTokenAccount,
|
|
|
|
parseNFTokenCollectionAccount,
|
|
|
|
} from "../components/account/nftoken/isNFTokenAccount";
|
2022-08-14 07:50:23 -07:00
|
|
|
import { isAddressLookupTableAccount } from "components/account/address-lookup-table/types";
|
|
|
|
import { AddressLookupTableAccountSection } from "components/account/address-lookup-table/AddressLookupTableAccountSection";
|
|
|
|
import { LookupTableEntriesCard } from "components/account/address-lookup-table/LookupTableEntriesCard";
|
2020-10-10 01:03:45 -07:00
|
|
|
|
2021-04-12 11:25:51 -07:00
|
|
|
const IDENTICON_WIDTH = 64;
|
2021-04-14 16:22:40 -07:00
|
|
|
|
|
|
|
const TABS_LOOKUP: { [id: string]: Tab[] } = {
|
|
|
|
"spl-token:mint": [
|
|
|
|
{
|
|
|
|
slug: "transfers",
|
|
|
|
title: "Transfers",
|
|
|
|
path: "/transfers",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
slug: "instructions",
|
|
|
|
title: "Instructions",
|
|
|
|
path: "/instructions",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
slug: "largest",
|
|
|
|
title: "Distribution",
|
|
|
|
path: "/largest",
|
|
|
|
},
|
|
|
|
],
|
2021-10-08 13:17:32 -07:00
|
|
|
"spl-token:mint:metaplexNFT": [
|
|
|
|
{
|
|
|
|
slug: "metadata",
|
|
|
|
title: "Metadata",
|
|
|
|
path: "/metadata",
|
|
|
|
},
|
2022-04-22 05:06:00 -07:00
|
|
|
{
|
|
|
|
slug: "attributes",
|
|
|
|
title: "Attributes",
|
|
|
|
path: "/attributes",
|
|
|
|
},
|
2021-10-08 13:17:32 -07:00
|
|
|
],
|
2021-05-20 14:00:48 -07:00
|
|
|
stake: [
|
|
|
|
{
|
|
|
|
slug: "rewards",
|
|
|
|
title: "Rewards",
|
|
|
|
path: "/rewards",
|
|
|
|
},
|
|
|
|
],
|
2021-04-14 16:22:40 -07:00
|
|
|
vote: [
|
|
|
|
{
|
|
|
|
slug: "vote-history",
|
|
|
|
title: "Vote History",
|
|
|
|
path: "/vote-history",
|
|
|
|
},
|
2021-05-20 14:00:48 -07:00
|
|
|
{
|
|
|
|
slug: "rewards",
|
|
|
|
title: "Rewards",
|
|
|
|
path: "/rewards",
|
|
|
|
},
|
2021-04-14 16:22:40 -07:00
|
|
|
],
|
|
|
|
"sysvar:recentBlockhashes": [
|
|
|
|
{
|
|
|
|
slug: "blockhashes",
|
|
|
|
title: "Blockhashes",
|
|
|
|
path: "/blockhashes",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
"sysvar:slotHashes": [
|
|
|
|
{
|
|
|
|
slug: "slot-hashes",
|
|
|
|
title: "Slot Hashes",
|
|
|
|
path: "/slot-hashes",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
"sysvar:stakeHistory": [
|
|
|
|
{
|
|
|
|
slug: "stake-history",
|
|
|
|
title: "Stake History",
|
|
|
|
path: "/stake-history",
|
|
|
|
},
|
|
|
|
],
|
2022-03-31 02:23:32 -07:00
|
|
|
"bpf-upgradeable-loader": [
|
|
|
|
{
|
|
|
|
slug: "security",
|
|
|
|
title: "Security",
|
|
|
|
path: "/security",
|
|
|
|
},
|
|
|
|
],
|
2022-08-26 09:49:37 -07:00
|
|
|
"nftoken:collection": [
|
|
|
|
{
|
|
|
|
slug: "nftoken-collection-nfts",
|
|
|
|
title: "NFTs",
|
|
|
|
path: "/nfts",
|
|
|
|
},
|
|
|
|
],
|
2022-08-14 07:50:23 -07:00
|
|
|
"address-lookup-table": [
|
|
|
|
{
|
|
|
|
slug: "entries",
|
|
|
|
title: "Table Entries",
|
|
|
|
path: "/entries",
|
|
|
|
},
|
|
|
|
],
|
2020-10-10 01:03:45 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
const TOKEN_TABS_HIDDEN = [
|
|
|
|
"spl-token:mint",
|
|
|
|
"config",
|
|
|
|
"vote",
|
|
|
|
"sysvar",
|
|
|
|
"config",
|
|
|
|
];
|
2020-08-07 23:45:57 -07:00
|
|
|
|
|
|
|
type Props = { address: string; tab?: string };
|
|
|
|
export function AccountDetailsPage({ address, tab }: Props) {
|
2021-04-12 11:25:51 -07:00
|
|
|
const fetchAccount = useFetchAccountInfo();
|
|
|
|
const { status } = useCluster();
|
|
|
|
const info = useAccountInfo(address);
|
2020-08-07 23:45:57 -07:00
|
|
|
let pubkey: PublicKey | undefined;
|
2020-08-12 10:31:21 -07:00
|
|
|
|
2020-08-07 23:45:57 -07:00
|
|
|
try {
|
|
|
|
pubkey = new PublicKey(address);
|
2020-08-12 10:31:21 -07:00
|
|
|
} catch (err) {}
|
2020-08-07 23:45:57 -07:00
|
|
|
|
2021-04-12 11:25:51 -07:00
|
|
|
// Fetch account on load
|
|
|
|
React.useEffect(() => {
|
|
|
|
if (!info && status === ClusterStatus.Connected && pubkey) {
|
|
|
|
fetchAccount(pubkey);
|
|
|
|
}
|
|
|
|
}, [address, status]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
|
2020-08-07 23:45:57 -07:00
|
|
|
return (
|
|
|
|
<div className="container mt-n3">
|
|
|
|
<div className="header">
|
|
|
|
<div className="header-body">
|
2021-04-12 11:25:51 -07:00
|
|
|
<AccountHeader address={address} info={info} />
|
2020-08-07 23:45:57 -07:00
|
|
|
</div>
|
|
|
|
</div>
|
2020-08-12 10:31:21 -07:00
|
|
|
{!pubkey ? (
|
|
|
|
<ErrorCard text={`Address "${address}" is not valid`} />
|
|
|
|
) : (
|
2021-04-12 11:25:51 -07:00
|
|
|
<DetailsSections pubkey={pubkey} tab={tab} info={info} />
|
2020-08-12 10:31:21 -07:00
|
|
|
)}
|
2020-08-07 23:45:57 -07:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-04-12 11:25:51 -07:00
|
|
|
export function AccountHeader({
|
|
|
|
address,
|
|
|
|
info,
|
|
|
|
}: {
|
|
|
|
address: string;
|
|
|
|
info?: CacheEntry<Account>;
|
|
|
|
}) {
|
2021-02-24 12:30:55 -08:00
|
|
|
const { tokenRegistry } = useTokenRegistry();
|
|
|
|
const tokenDetails = tokenRegistry.get(address);
|
2021-11-01 21:30:26 -07:00
|
|
|
const mintInfo = useMintAccountInfo(address);
|
2021-04-12 11:25:51 -07:00
|
|
|
const account = info?.data;
|
|
|
|
const data = account?.details?.data;
|
|
|
|
const isToken = data?.program === "spl-token" && data?.parsed.type === "mint";
|
2021-10-05 11:30:05 -07:00
|
|
|
|
2021-11-03 12:52:04 -07:00
|
|
|
if (isMetaplexNFT(data, mintInfo)) {
|
2021-11-01 21:30:26 -07:00
|
|
|
return (
|
2022-08-26 09:49:37 -07:00
|
|
|
<MetaplexNFTHeader
|
2021-11-01 21:30:26 -07:00
|
|
|
nftData={(data as TokenProgramData).nftData!}
|
|
|
|
address={address}
|
|
|
|
/>
|
|
|
|
);
|
2021-10-05 11:30:05 -07:00
|
|
|
}
|
2021-04-12 11:25:51 -07:00
|
|
|
|
2022-08-26 09:49:37 -07:00
|
|
|
const nftokenNFT = account && isNFTokenAccount(account);
|
|
|
|
if (nftokenNFT && account) {
|
|
|
|
return <NFTokenAccountHeader account={account} />;
|
|
|
|
}
|
|
|
|
|
2022-04-22 00:55:46 -07:00
|
|
|
if (isToken) {
|
|
|
|
let token;
|
|
|
|
let unverified = false;
|
|
|
|
|
2022-07-26 20:28:05 -07:00
|
|
|
// Fall back to legacy token list when there is stub metadata (blank uri), updatable by default by the mint authority
|
|
|
|
if (!data?.nftData?.metadata.data.uri && tokenDetails) {
|
|
|
|
token = tokenDetails;
|
|
|
|
} else if (data?.nftData) {
|
2022-04-22 00:55:46 -07:00
|
|
|
token = {
|
|
|
|
logoURI: data?.nftData?.json?.image,
|
2022-07-26 20:28:05 -07:00
|
|
|
name: data?.nftData?.json?.name ?? data?.nftData.metadata.data.name,
|
2022-04-22 00:55:46 -07:00
|
|
|
};
|
|
|
|
unverified = true;
|
2022-07-25 14:47:50 -07:00
|
|
|
} else if (tokenDetails) {
|
|
|
|
token = tokenDetails;
|
2022-04-22 00:55:46 -07:00
|
|
|
}
|
|
|
|
|
2020-08-13 07:57:53 -07:00
|
|
|
return (
|
|
|
|
<div className="row align-items-end">
|
2022-04-22 00:55:46 -07:00
|
|
|
{unverified && (
|
2022-04-27 09:37:38 -07:00
|
|
|
<div className="alert alert-warning alert-scam" role="alert">
|
|
|
|
Warning! Token names and logos are not unique. This token may have
|
|
|
|
spoofed its name and logo to look like another token. Verify the
|
|
|
|
token's mint address to ensure it is correct.
|
2022-04-22 00:55:46 -07:00
|
|
|
</div>
|
|
|
|
)}
|
2021-04-12 11:25:51 -07:00
|
|
|
<div className="col-auto">
|
|
|
|
<div className="avatar avatar-lg header-avatar-top">
|
2022-04-22 00:55:46 -07:00
|
|
|
{token?.logoURI ? (
|
2020-08-29 22:00:07 -07:00
|
|
|
<img
|
2022-04-22 00:55:46 -07:00
|
|
|
src={token.logoURI}
|
2020-08-29 22:00:07 -07:00
|
|
|
alt="token logo"
|
|
|
|
className="avatar-img rounded-circle border border-4 border-body"
|
|
|
|
/>
|
2021-04-12 11:25:51 -07:00
|
|
|
) : (
|
|
|
|
<Identicon
|
|
|
|
address={address}
|
|
|
|
className="avatar-img rounded-circle border border-body identicon-wrapper"
|
|
|
|
style={{ width: IDENTICON_WIDTH }}
|
|
|
|
/>
|
|
|
|
)}
|
2020-08-13 07:57:53 -07:00
|
|
|
</div>
|
2021-04-12 11:25:51 -07:00
|
|
|
</div>
|
2020-08-13 07:57:53 -07:00
|
|
|
|
2021-11-28 12:49:22 -08:00
|
|
|
<div className="col mb-3 ms-n3 ms-md-n2">
|
2020-08-13 07:57:53 -07:00
|
|
|
<h6 className="header-pretitle">Token</h6>
|
2022-04-22 00:55:46 -07:00
|
|
|
<h2 className="header-title">{token?.name || "Unknown Token"}</h2>
|
2020-08-13 07:57:53 -07:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<h6 className="header-pretitle">Details</h6>
|
|
|
|
<h2 className="header-title">Account</h2>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-04-12 11:25:51 -07:00
|
|
|
function DetailsSections({
|
|
|
|
pubkey,
|
|
|
|
tab,
|
|
|
|
info,
|
|
|
|
}: {
|
|
|
|
pubkey: PublicKey;
|
|
|
|
tab?: string;
|
|
|
|
info?: CacheEntry<Account>;
|
|
|
|
}) {
|
2020-08-07 23:45:57 -07:00
|
|
|
const fetchAccount = useFetchAccountInfo();
|
|
|
|
const address = pubkey.toBase58();
|
2020-08-12 10:31:21 -07:00
|
|
|
const location = useLocation();
|
2021-02-08 12:54:36 -08:00
|
|
|
const { flaggedAccounts } = useFlaggedAccounts();
|
2020-08-07 23:45:57 -07:00
|
|
|
|
|
|
|
if (!info || info.status === FetchStatus.Fetching) {
|
|
|
|
return <LoadingCard />;
|
|
|
|
} else if (
|
|
|
|
info.status === FetchStatus.FetchFailed ||
|
2020-08-12 07:41:04 -07:00
|
|
|
info.data?.lamports === undefined
|
2020-08-07 23:45:57 -07:00
|
|
|
) {
|
2020-08-12 10:31:21 -07:00
|
|
|
return <ErrorCard retry={() => fetchAccount(pubkey)} text="Fetch Failed" />;
|
2020-08-07 23:45:57 -07:00
|
|
|
}
|
|
|
|
|
2020-08-12 07:41:04 -07:00
|
|
|
const account = info.data;
|
2022-04-06 10:22:49 -07:00
|
|
|
const tabComponents = getTabs(pubkey, account).concat(
|
|
|
|
getAnchorTabs(pubkey, account)
|
|
|
|
);
|
2020-08-12 10:31:21 -07:00
|
|
|
|
|
|
|
let moreTab: MoreTabs = "history";
|
2022-04-06 10:22:49 -07:00
|
|
|
if (
|
|
|
|
tab &&
|
|
|
|
tabComponents.filter((tabComponent) => tabComponent.tab.slug === tab)
|
|
|
|
.length === 0
|
|
|
|
) {
|
2020-08-12 10:31:21 -07:00
|
|
|
return <Redirect to={{ ...location, pathname: `/address/${address}` }} />;
|
|
|
|
} else if (tab) {
|
|
|
|
moreTab = tab as MoreTabs;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2021-02-08 12:54:36 -08:00
|
|
|
{flaggedAccounts.has(address) && (
|
2021-01-27 13:50:02 -08:00
|
|
|
<div className="alert alert-danger alert-scam" role="alert">
|
2021-02-23 21:27:35 -08:00
|
|
|
Warning! This account has been flagged by the community as a scam
|
|
|
|
account. Please be cautious sending SOL to this account.
|
2021-01-27 13:50:02 -08:00
|
|
|
</div>
|
|
|
|
)}
|
2022-04-13 12:38:59 -07:00
|
|
|
<InfoSection account={account} />
|
2022-04-06 10:22:49 -07:00
|
|
|
<MoreSection
|
|
|
|
account={account}
|
|
|
|
tab={moreTab}
|
|
|
|
tabs={tabComponents.map(({ component }) => component)}
|
|
|
|
/>
|
2020-08-12 10:31:21 -07:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function InfoSection({ account }: { account: Account }) {
|
2022-08-14 07:50:23 -07:00
|
|
|
const details = account?.details;
|
|
|
|
const data = details?.data;
|
2020-09-21 22:41:39 -07:00
|
|
|
|
2021-02-16 02:30:02 -08:00
|
|
|
if (data && data.program === "bpf-upgradeable-loader") {
|
|
|
|
return (
|
2021-03-13 09:11:59 -08:00
|
|
|
<UpgradeableLoaderAccountSection
|
2021-02-16 02:30:02 -08:00
|
|
|
account={account}
|
2021-03-13 09:11:59 -08:00
|
|
|
parsedData={data.parsed}
|
2021-02-16 02:30:02 -08:00
|
|
|
programData={data.programData}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
} else if (data && data.program === "stake") {
|
2020-08-08 06:06:24 -07:00
|
|
|
return (
|
|
|
|
<StakeAccountSection
|
2020-08-12 07:41:04 -07:00
|
|
|
account={account}
|
2020-10-28 20:37:55 -07:00
|
|
|
stakeAccount={data.parsed.info}
|
2020-09-21 22:41:39 -07:00
|
|
|
activation={data.activation}
|
2020-10-28 20:37:55 -07:00
|
|
|
stakeAccountType={data.parsed.type}
|
2020-08-08 06:06:24 -07:00
|
|
|
/>
|
|
|
|
);
|
2022-08-26 09:49:37 -07:00
|
|
|
} else if (account.details?.owner.toBase58() === NFTOKEN_ADDRESS) {
|
|
|
|
return <NFTokenAccountSection account={account} />;
|
2020-08-29 05:50:45 -07:00
|
|
|
} else if (data && data.program === "spl-token") {
|
2020-08-12 07:41:04 -07:00
|
|
|
return <TokenAccountSection account={account} tokenAccount={data.parsed} />;
|
2020-10-10 01:03:45 -07:00
|
|
|
} else if (data && data.program === "nonce") {
|
|
|
|
return <NonceAccountSection account={account} nonceAccount={data.parsed} />;
|
|
|
|
} else if (data && data.program === "vote") {
|
|
|
|
return <VoteAccountSection account={account} voteAccount={data.parsed} />;
|
|
|
|
} else if (data && data.program === "sysvar") {
|
|
|
|
return (
|
|
|
|
<SysvarAccountSection account={account} sysvarAccount={data.parsed} />
|
|
|
|
);
|
|
|
|
} else if (data && data.program === "config") {
|
|
|
|
return (
|
|
|
|
<ConfigAccountSection account={account} configAccount={data.parsed} />
|
|
|
|
);
|
2022-09-29 06:08:01 -07:00
|
|
|
} else if (
|
|
|
|
data &&
|
|
|
|
data.program === "address-lookup-table" &&
|
|
|
|
data.parsed.type === "lookupTable"
|
|
|
|
) {
|
|
|
|
return (
|
|
|
|
<AddressLookupTableAccountSection
|
|
|
|
account={account}
|
|
|
|
lookupTableAccount={data.parsed.info}
|
|
|
|
/>
|
|
|
|
);
|
2022-08-14 07:50:23 -07:00
|
|
|
} else if (
|
|
|
|
details?.rawData &&
|
|
|
|
isAddressLookupTableAccount(details.owner, details.rawData)
|
|
|
|
) {
|
|
|
|
return (
|
|
|
|
<AddressLookupTableAccountSection
|
|
|
|
account={account}
|
|
|
|
data={details.rawData}
|
|
|
|
/>
|
|
|
|
);
|
2020-08-07 23:45:57 -07:00
|
|
|
} else {
|
2020-08-12 07:41:04 -07:00
|
|
|
return <UnknownAccountCard account={account} />;
|
2020-08-07 23:45:57 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-12 10:31:21 -07:00
|
|
|
type Tab = {
|
|
|
|
slug: MoreTabs;
|
|
|
|
title: string;
|
|
|
|
path: string;
|
|
|
|
};
|
|
|
|
|
2022-04-06 10:22:49 -07:00
|
|
|
type TabComponent = {
|
|
|
|
tab: Tab;
|
|
|
|
component: JSX.Element | null;
|
|
|
|
};
|
|
|
|
|
2021-04-14 16:22:40 -07:00
|
|
|
export type MoreTabs =
|
2020-10-10 01:03:45 -07:00
|
|
|
| "history"
|
|
|
|
| "tokens"
|
2022-08-26 09:49:37 -07:00
|
|
|
| "nftoken-collection-nfts"
|
2020-10-10 01:03:45 -07:00
|
|
|
| "largest"
|
|
|
|
| "vote-history"
|
|
|
|
| "slot-hashes"
|
|
|
|
| "stake-history"
|
2021-04-14 16:22:40 -07:00
|
|
|
| "blockhashes"
|
|
|
|
| "transfers"
|
2021-05-20 14:00:48 -07:00
|
|
|
| "instructions"
|
2021-10-08 13:17:32 -07:00
|
|
|
| "rewards"
|
2021-10-25 19:14:24 -07:00
|
|
|
| "metadata"
|
2022-04-22 05:06:00 -07:00
|
|
|
| "attributes"
|
2022-03-31 02:23:32 -07:00
|
|
|
| "domains"
|
2022-04-06 10:22:49 -07:00
|
|
|
| "security"
|
|
|
|
| "anchor-program"
|
2022-08-14 07:50:23 -07:00
|
|
|
| "anchor-account"
|
|
|
|
| "entries";
|
2020-10-10 01:03:45 -07:00
|
|
|
|
2020-08-12 10:31:21 -07:00
|
|
|
function MoreSection({
|
|
|
|
account,
|
|
|
|
tab,
|
|
|
|
tabs,
|
|
|
|
}: {
|
|
|
|
account: Account;
|
|
|
|
tab: MoreTabs;
|
2022-04-06 10:22:49 -07:00
|
|
|
tabs: (JSX.Element | null)[];
|
2020-08-12 10:31:21 -07:00
|
|
|
}) {
|
|
|
|
const pubkey = account.pubkey;
|
2022-08-14 07:50:23 -07:00
|
|
|
const details = account?.details;
|
|
|
|
const data = details?.data;
|
2022-04-06 10:22:49 -07:00
|
|
|
|
2020-08-07 23:45:57 -07:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div className="container">
|
|
|
|
<div className="header">
|
|
|
|
<div className="header-body pt-0">
|
2022-04-06 10:22:49 -07:00
|
|
|
<ul className="nav nav-tabs nav-overflow header-tabs">{tabs}</ul>
|
2020-08-07 23:45:57 -07:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-08-08 09:29:42 -07:00
|
|
|
{tab === "tokens" && (
|
|
|
|
<>
|
|
|
|
<OwnedTokensCard pubkey={pubkey} />
|
|
|
|
<TokenHistoryCard pubkey={pubkey} />
|
|
|
|
</>
|
|
|
|
)}
|
2020-08-07 23:45:57 -07:00
|
|
|
{tab === "history" && <TransactionHistoryCard pubkey={pubkey} />}
|
2021-04-14 16:22:40 -07:00
|
|
|
{tab === "transfers" && <TokenTransfersCard pubkey={pubkey} />}
|
|
|
|
{tab === "instructions" && <TokenInstructionsCard pubkey={pubkey} />}
|
2020-08-13 07:57:53 -07:00
|
|
|
{tab === "largest" && <TokenLargestAccountsCard pubkey={pubkey} />}
|
2021-05-20 14:00:48 -07:00
|
|
|
{tab === "rewards" && <RewardsCard pubkey={pubkey} />}
|
2020-10-10 01:03:45 -07:00
|
|
|
{tab === "vote-history" && data?.program === "vote" && (
|
|
|
|
<VotesCard voteAccount={data.parsed} />
|
|
|
|
)}
|
|
|
|
{tab === "slot-hashes" &&
|
|
|
|
data?.program === "sysvar" &&
|
|
|
|
data.parsed.type === "slotHashes" && (
|
|
|
|
<SlotHashesCard sysvarAccount={data.parsed} />
|
|
|
|
)}
|
|
|
|
{tab === "stake-history" &&
|
|
|
|
data?.program === "sysvar" &&
|
|
|
|
data.parsed.type === "stakeHistory" && (
|
|
|
|
<StakeHistoryCard sysvarAccount={data.parsed} />
|
|
|
|
)}
|
|
|
|
{tab === "blockhashes" &&
|
|
|
|
data?.program === "sysvar" &&
|
|
|
|
data.parsed.type === "recentBlockhashes" && (
|
|
|
|
<BlockhashesCard blockhashes={data.parsed.info} />
|
|
|
|
)}
|
2021-10-08 13:17:32 -07:00
|
|
|
{tab === "metadata" && (
|
|
|
|
<MetaplexMetadataCard
|
|
|
|
nftData={(account.details?.data as TokenProgramData).nftData!}
|
|
|
|
/>
|
|
|
|
)}
|
2022-08-26 09:49:37 -07:00
|
|
|
{tab === "nftoken-collection-nfts" && (
|
|
|
|
<Suspense
|
|
|
|
fallback={<LoadingCard message="Loading NFTs for collection." />}
|
|
|
|
>
|
|
|
|
<NFTokenCollectionNFTGrid collection={account.pubkey.toBase58()} />
|
|
|
|
</Suspense>
|
|
|
|
)}
|
2022-04-22 05:06:00 -07:00
|
|
|
{tab === "attributes" && (
|
|
|
|
<MetaplexNFTAttributesCard
|
|
|
|
nftData={(account.details?.data as TokenProgramData).nftData!}
|
|
|
|
/>
|
|
|
|
)}
|
2021-10-25 19:14:24 -07:00
|
|
|
{tab === "domains" && <DomainsCard pubkey={pubkey} />}
|
2022-03-31 02:23:32 -07:00
|
|
|
{tab === "security" && data?.program === "bpf-upgradeable-loader" && (
|
|
|
|
<SecurityCard data={data} />
|
|
|
|
)}
|
2022-04-06 10:22:49 -07:00
|
|
|
{tab === "anchor-program" && (
|
|
|
|
<React.Suspense
|
|
|
|
fallback={<LoadingCard message="Loading anchor program IDL" />}
|
|
|
|
>
|
|
|
|
<AnchorProgramCard programId={pubkey} />
|
|
|
|
</React.Suspense>
|
|
|
|
)}
|
|
|
|
{tab === "anchor-account" && (
|
|
|
|
<React.Suspense
|
|
|
|
fallback={
|
|
|
|
<LoadingCard message="Decoding account data using anchor interface" />
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<AnchorAccountCard account={account} />
|
|
|
|
</React.Suspense>
|
|
|
|
)}
|
2022-08-14 07:50:23 -07:00
|
|
|
{tab === "entries" &&
|
|
|
|
details?.rawData &&
|
|
|
|
isAddressLookupTableAccount(details.owner, details.rawData) && (
|
|
|
|
<LookupTableEntriesCard lookupTableAccountData={details?.rawData} />
|
|
|
|
)}
|
2022-09-29 06:08:01 -07:00
|
|
|
{tab === "entries" &&
|
|
|
|
data?.program === "address-lookup-table" &&
|
|
|
|
data.parsed.type === "lookupTable" && (
|
|
|
|
<LookupTableEntriesCard parsedLookupTable={data.parsed.info} />
|
|
|
|
)}
|
2020-08-07 23:45:57 -07:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
2020-10-10 01:03:45 -07:00
|
|
|
|
2022-04-06 10:22:49 -07:00
|
|
|
function getTabs(pubkey: PublicKey, account: Account): TabComponent[] {
|
|
|
|
const address = pubkey.toBase58();
|
|
|
|
const data = account.details?.data;
|
2020-10-10 01:03:45 -07:00
|
|
|
const tabs: Tab[] = [
|
|
|
|
{
|
|
|
|
slug: "history",
|
|
|
|
title: "History",
|
|
|
|
path: "",
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
let programTypeKey = "";
|
2021-02-16 02:30:02 -08:00
|
|
|
if (data && "parsed" in data && "type" in data.parsed) {
|
2020-10-10 01:03:45 -07:00
|
|
|
programTypeKey = `${data.program}:${data.parsed.type}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data && data.program in TABS_LOOKUP) {
|
2021-04-14 16:22:40 -07:00
|
|
|
tabs.push(...TABS_LOOKUP[data.program]);
|
2020-10-10 01:03:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (data && programTypeKey in TABS_LOOKUP) {
|
2021-04-14 16:22:40 -07:00
|
|
|
tabs.push(...TABS_LOOKUP[programTypeKey]);
|
2020-10-10 01:03:45 -07:00
|
|
|
}
|
|
|
|
|
2022-08-14 07:50:23 -07:00
|
|
|
// Add the key for address lookup tables
|
|
|
|
if (
|
|
|
|
account.details?.rawData &&
|
|
|
|
isAddressLookupTableAccount(account.details.owner, account.details.rawData)
|
|
|
|
) {
|
|
|
|
tabs.push(...TABS_LOOKUP["address-lookup-table"]);
|
|
|
|
}
|
|
|
|
|
2021-10-08 13:17:32 -07:00
|
|
|
// Add the key for Metaplex NFTs
|
|
|
|
if (
|
|
|
|
data &&
|
|
|
|
programTypeKey === "spl-token:mint" &&
|
|
|
|
(data as TokenProgramData).nftData
|
|
|
|
) {
|
|
|
|
tabs.push(...TABS_LOOKUP[`${programTypeKey}:metaplexNFT`]);
|
|
|
|
}
|
|
|
|
|
2022-08-26 09:49:37 -07:00
|
|
|
const isNFToken = account && isNFTokenAccount(account);
|
|
|
|
if (isNFToken) {
|
|
|
|
const collection = parseNFTokenCollectionAccount(account);
|
|
|
|
if (collection) {
|
|
|
|
tabs.push({
|
|
|
|
slug: "nftoken-collection-nfts",
|
|
|
|
title: "NFTs",
|
|
|
|
path: "/nftoken-collection-nfts",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-10 01:03:45 -07:00
|
|
|
if (
|
2022-08-26 09:49:37 -07:00
|
|
|
!isNFToken &&
|
|
|
|
(!data ||
|
|
|
|
!(
|
|
|
|
TOKEN_TABS_HIDDEN.includes(data.program) ||
|
|
|
|
TOKEN_TABS_HIDDEN.includes(programTypeKey)
|
|
|
|
))
|
2020-10-10 01:03:45 -07:00
|
|
|
) {
|
|
|
|
tabs.push({
|
|
|
|
slug: "tokens",
|
|
|
|
title: "Tokens",
|
|
|
|
path: "/tokens",
|
|
|
|
});
|
2021-10-25 19:14:24 -07:00
|
|
|
tabs.push({
|
|
|
|
slug: "domains",
|
|
|
|
title: "Domains",
|
|
|
|
path: "/domains",
|
|
|
|
});
|
2020-10-10 01:03:45 -07:00
|
|
|
}
|
|
|
|
|
2022-04-06 10:22:49 -07:00
|
|
|
return tabs.map((tab) => {
|
|
|
|
return {
|
|
|
|
tab,
|
|
|
|
component: (
|
|
|
|
<li key={tab.slug} className="nav-item">
|
|
|
|
<NavLink
|
|
|
|
className="nav-link"
|
|
|
|
to={clusterPath(`/address/${address}${tab.path}`)}
|
|
|
|
exact
|
|
|
|
>
|
|
|
|
{tab.title}
|
|
|
|
</NavLink>
|
|
|
|
</li>
|
|
|
|
),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getAnchorTabs(pubkey: PublicKey, account: Account) {
|
|
|
|
const tabComponents = [];
|
|
|
|
const anchorProgramTab: Tab = {
|
|
|
|
slug: "anchor-program",
|
|
|
|
title: "Anchor Program IDL",
|
|
|
|
path: "/anchor-program",
|
|
|
|
};
|
|
|
|
tabComponents.push({
|
|
|
|
tab: anchorProgramTab,
|
|
|
|
component: (
|
|
|
|
<React.Suspense key={anchorProgramTab.slug} fallback={<></>}>
|
|
|
|
<AnchorProgramLink
|
|
|
|
tab={anchorProgramTab}
|
|
|
|
address={pubkey.toString()}
|
|
|
|
pubkey={pubkey}
|
|
|
|
/>
|
|
|
|
</React.Suspense>
|
|
|
|
),
|
|
|
|
});
|
|
|
|
|
2022-04-13 12:38:59 -07:00
|
|
|
const accountDataTab: Tab = {
|
2022-04-06 10:22:49 -07:00
|
|
|
slug: "anchor-account",
|
2022-04-13 12:38:59 -07:00
|
|
|
title: "Anchor Data",
|
2022-04-06 10:22:49 -07:00
|
|
|
path: "/anchor-account",
|
|
|
|
};
|
|
|
|
tabComponents.push({
|
2022-04-13 12:38:59 -07:00
|
|
|
tab: accountDataTab,
|
2022-04-06 10:22:49 -07:00
|
|
|
component: (
|
2022-04-13 12:38:59 -07:00
|
|
|
<React.Suspense key={accountDataTab.slug} fallback={<></>}>
|
|
|
|
<AccountDataLink
|
|
|
|
tab={accountDataTab}
|
2022-04-06 10:22:49 -07:00
|
|
|
address={pubkey.toString()}
|
|
|
|
programId={account.details?.owner}
|
|
|
|
/>
|
|
|
|
</React.Suspense>
|
|
|
|
),
|
|
|
|
});
|
|
|
|
|
|
|
|
return tabComponents;
|
|
|
|
}
|
|
|
|
|
|
|
|
function AnchorProgramLink({
|
|
|
|
tab,
|
|
|
|
address,
|
|
|
|
pubkey,
|
|
|
|
}: {
|
|
|
|
tab: Tab;
|
|
|
|
address: string;
|
|
|
|
pubkey: PublicKey;
|
|
|
|
}) {
|
|
|
|
const { url } = useCluster();
|
|
|
|
const anchorProgram = useAnchorProgram(pubkey.toString() ?? "", url);
|
|
|
|
|
|
|
|
if (!anchorProgram) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<li key={tab.slug} className="nav-item">
|
|
|
|
<NavLink
|
|
|
|
className="nav-link"
|
|
|
|
to={clusterPath(`/address/${address}${tab.path}`)}
|
|
|
|
exact
|
|
|
|
>
|
|
|
|
{tab.title}
|
|
|
|
</NavLink>
|
|
|
|
</li>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-04-13 12:38:59 -07:00
|
|
|
function AccountDataLink({
|
2022-04-06 10:22:49 -07:00
|
|
|
address,
|
|
|
|
tab,
|
|
|
|
programId,
|
|
|
|
}: {
|
|
|
|
address: string;
|
|
|
|
tab: Tab;
|
|
|
|
programId: PublicKey | undefined;
|
|
|
|
}) {
|
|
|
|
const { url } = useCluster();
|
|
|
|
const accountAnchorProgram = useAnchorProgram(
|
|
|
|
programId?.toString() ?? "",
|
|
|
|
url
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!accountAnchorProgram) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<li key={tab.slug} className="nav-item">
|
|
|
|
<NavLink
|
|
|
|
className="nav-link"
|
|
|
|
to={clusterPath(`/address/${address}${tab.path}`)}
|
|
|
|
exact
|
|
|
|
>
|
|
|
|
{tab.title}
|
|
|
|
</NavLink>
|
|
|
|
</li>
|
|
|
|
);
|
2020-10-10 01:03:45 -07:00
|
|
|
}
|