Explorer: Refactor account provider data types (#28390)
This commit is contained in:
parent
d40875fbec
commit
c38bca9932
|
@ -23,8 +23,12 @@ describe("parseNFTokenAccounts", () => {
|
|||
]);
|
||||
const nftAccount = parseNFTokenNFTAccount({
|
||||
pubkey: new PublicKey("FagABcRBhZH27JDtu6A1Jo9woXyoznP28QujLkxkN9Hj"),
|
||||
details: { rawData: buffer, owner: new PublicKey(NFTOKEN_ADDRESS) },
|
||||
} as any);
|
||||
space: buffer.length,
|
||||
lamports: 1,
|
||||
executable: false,
|
||||
data: { raw: buffer as Buffer },
|
||||
owner: new PublicKey(NFTOKEN_ADDRESS),
|
||||
});
|
||||
expect(nftAccount!.metadata_url).to.eq(
|
||||
"https://cdn.glow.app/n/88/78ef17c1-2b5a-468e-ae8f-7403856e9f00.json"
|
||||
);
|
||||
|
|
|
@ -10,11 +10,8 @@ import { useAnchorProgram } from "providers/anchor";
|
|||
export function AnchorAccountCard({ account }: { account: Account }) {
|
||||
const { lamports } = account;
|
||||
const { url } = useCluster();
|
||||
const anchorProgram = useAnchorProgram(
|
||||
account.details?.owner.toString() || "",
|
||||
url
|
||||
);
|
||||
const rawData = account?.details?.rawData;
|
||||
const anchorProgram = useAnchorProgram(account.owner.toString(), url);
|
||||
const rawData = account.data.raw;
|
||||
const programName = getAnchorProgramName(anchorProgram) || "Unknown Program";
|
||||
|
||||
const { decodedAccountData, accountDef } = useMemo(() => {
|
||||
|
|
|
@ -7,22 +7,27 @@ import { ErrorCard } from "components/common/ErrorCard";
|
|||
import { Slot } from "components/common/Slot";
|
||||
import { lamportsToSolString } from "utils";
|
||||
import { useAccountInfo } from "providers/accounts";
|
||||
import BN from "bn.js";
|
||||
import { Epoch } from "components/common/Epoch";
|
||||
|
||||
const MAX_EPOCH = new BN(2).pow(new BN(64)).sub(new BN(1));
|
||||
const U64_MAX = BigInt("0xffffffffffffffff");
|
||||
|
||||
export function RewardsCard({ pubkey }: { pubkey: PublicKey }) {
|
||||
const address = React.useMemo(() => pubkey.toBase58(), [pubkey]);
|
||||
const info = useAccountInfo(address);
|
||||
const account = info?.data;
|
||||
const data = account?.details?.data?.parsed.info;
|
||||
const parsedData = account?.data.parsed;
|
||||
|
||||
const highestEpoch = React.useMemo(() => {
|
||||
if (data.stake && !data.stake.delegation.deactivationEpoch.eq(MAX_EPOCH)) {
|
||||
return data.stake.delegation.deactivationEpoch.toNumber();
|
||||
if (!parsedData) return;
|
||||
if (parsedData.program !== "stake") return;
|
||||
const stakeInfo = parsedData.parsed.info.stake;
|
||||
if (
|
||||
stakeInfo !== null &&
|
||||
stakeInfo.delegation.deactivationEpoch !== U64_MAX
|
||||
) {
|
||||
return Number(stakeInfo.delegation.deactivationEpoch);
|
||||
}
|
||||
}, [data]);
|
||||
}, [parsedData]);
|
||||
|
||||
const rewards = useRewards(address);
|
||||
const fetchRewards = useFetchRewards();
|
||||
|
|
|
@ -125,7 +125,7 @@ function OverviewCard({
|
|||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-end text-uppercase">
|
||||
<SolBalance lamports={account.lamports || 0} />
|
||||
<SolBalance lamports={account.lamports} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -54,11 +54,11 @@ export function TokenAccountSection({
|
|||
case "mint": {
|
||||
const info = create(tokenAccount.info, MintAccountInfo);
|
||||
|
||||
if (isMetaplexNFT(account.details?.data, info)) {
|
||||
if (isMetaplexNFT(account.data.parsed, info)) {
|
||||
return (
|
||||
<NonFungibleTokenMintAccountCard
|
||||
account={account}
|
||||
nftData={(account.details!.data as TokenProgramData).nftData!}
|
||||
nftData={(account.data.parsed as TokenProgramData).nftData!}
|
||||
mintInfo={info}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -8,10 +8,8 @@ import { useCluster } from "providers/cluster";
|
|||
import { useTokenRegistry } from "providers/mints/token-registry";
|
||||
|
||||
export function UnknownAccountCard({ account }: { account: Account }) {
|
||||
const { details, lamports } = account;
|
||||
const { cluster } = useCluster();
|
||||
const { tokenRegistry } = useTokenRegistry();
|
||||
if (lamports === undefined) return null;
|
||||
|
||||
const label = addressLabel(account.pubkey.toBase58(), cluster, tokenRegistry);
|
||||
return (
|
||||
|
@ -36,32 +34,26 @@ export function UnknownAccountCard({ account }: { account: Account }) {
|
|||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-end">
|
||||
<SolBalance lamports={lamports} />
|
||||
<SolBalance lamports={account.lamports} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{details?.space !== undefined && (
|
||||
<tr>
|
||||
<td>Allocated Data Size</td>
|
||||
<td className="text-lg-end">{details.space} byte(s)</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>Allocated Data Size</td>
|
||||
<td className="text-lg-end">{account.space} byte(s)</td>
|
||||
</tr>
|
||||
|
||||
{details && (
|
||||
<tr>
|
||||
<td>Assigned Program Id</td>
|
||||
<td className="text-lg-end">
|
||||
<Address pubkey={details.owner} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>Assigned Program Id</td>
|
||||
<td className="text-lg-end">
|
||||
<Address pubkey={account.owner} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{details && (
|
||||
<tr>
|
||||
<td>Executable</td>
|
||||
<td className="text-lg-end">{details.executable ? "Yes" : "No"}</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>Executable</td>
|
||||
<td className="text-lg-end">{account.executable ? "Yes" : "No"}</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -104,7 +104,7 @@ export function UpgradeableProgramSection({
|
|||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-end text-uppercase">
|
||||
<SolBalance lamports={account.lamports || 0} />
|
||||
<SolBalance lamports={account.lamports} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -235,22 +235,20 @@ export function UpgradeableProgramDataSection({
|
|||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-end text-uppercase">
|
||||
<SolBalance lamports={account.lamports || 0} />
|
||||
<SolBalance lamports={account.lamports} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Data Size (Bytes)</td>
|
||||
<td className="text-lg-end">
|
||||
<Downloadable
|
||||
data={programData.data[0]}
|
||||
filename={`${account.pubkey.toString()}.bin`}
|
||||
>
|
||||
<span className="me-2">{account.space}</span>
|
||||
</Downloadable>
|
||||
</td>
|
||||
</tr>
|
||||
{account.details?.space !== undefined && (
|
||||
<tr>
|
||||
<td>Data (Bytes)</td>
|
||||
<td className="text-lg-end">
|
||||
<Downloadable
|
||||
data={programData.data[0]}
|
||||
filename={`${account.pubkey.toString()}.bin`}
|
||||
>
|
||||
<span className="me-2">{account.details.space}</span>
|
||||
</Downloadable>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>Upgradeable</td>
|
||||
<td className="text-lg-end">
|
||||
|
@ -309,15 +307,13 @@ export function UpgradeableProgramBufferSection({
|
|||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-end text-uppercase">
|
||||
<SolBalance lamports={account.lamports || 0} />
|
||||
<SolBalance lamports={account.lamports} />
|
||||
</td>
|
||||
</tr>
|
||||
{account.details?.space !== undefined && (
|
||||
<tr>
|
||||
<td>Data (Bytes)</td>
|
||||
<td className="text-lg-end">{account.details.space}</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>Data Size (Bytes)</td>
|
||||
<td className="text-lg-end">{account.space}</td>
|
||||
</tr>
|
||||
{programBuffer.authority !== null && (
|
||||
<tr>
|
||||
<td>Deploy Authority</td>
|
||||
|
@ -326,14 +322,12 @@ export function UpgradeableProgramBufferSection({
|
|||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{account.details && (
|
||||
<tr>
|
||||
<td>Owner</td>
|
||||
<td className="text-lg-end">
|
||||
<Address pubkey={account.details.owner} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>Owner</td>
|
||||
<td className="text-lg-end">
|
||||
<Address pubkey={account.owner} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -56,7 +56,7 @@ export function AddressLookupTableAccountSection(
|
|||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-end text-uppercase">
|
||||
<SolBalance lamports={account.lamports || 0} />
|
||||
<SolBalance lamports={account.lamports} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -5,8 +5,7 @@ import { NftokenTypes } from "./nftoken-types";
|
|||
|
||||
export function isNFTokenAccount(account: Account): boolean {
|
||||
return Boolean(
|
||||
account.details?.owner.toBase58() === NFTOKEN_ADDRESS &&
|
||||
account.details.rawData
|
||||
account.owner.toBase58() === NFTOKEN_ADDRESS && account.data.raw
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -20,9 +19,7 @@ export const parseNFTokenNFTAccount = (
|
|||
}
|
||||
|
||||
try {
|
||||
const parsed = NftokenTypes.nftAccountLayout.decode(
|
||||
account!.details!.rawData!
|
||||
);
|
||||
const parsed = NftokenTypes.nftAccountLayout.decode(account.data.raw!);
|
||||
|
||||
if (!parsed) {
|
||||
return null;
|
||||
|
@ -36,7 +33,7 @@ export const parseNFTokenNFTAccount = (
|
|||
}
|
||||
|
||||
return {
|
||||
address: account!.pubkey.toBase58(),
|
||||
address: account.pubkey.toBase58(),
|
||||
holder: new PublicKey(parsed.holder).toBase58(),
|
||||
authority: new PublicKey(parsed.authority).toBase58(),
|
||||
authority_can_update: Boolean(parsed.authority_can_update),
|
||||
|
@ -62,7 +59,7 @@ export const parseNFTokenCollectionAccount = (
|
|||
|
||||
try {
|
||||
const parsed = NftokenTypes.collectionAccountLayout.decode(
|
||||
account!.details!.rawData!
|
||||
account.data.raw!
|
||||
);
|
||||
|
||||
if (!parsed) {
|
||||
|
@ -76,7 +73,7 @@ export const parseNFTokenCollectionAccount = (
|
|||
}
|
||||
|
||||
return {
|
||||
address: account!.pubkey.toBase58(),
|
||||
address: account.pubkey.toBase58(),
|
||||
authority: parsed.authority,
|
||||
authority_can_update: Boolean(parsed.authority_can_update),
|
||||
metadata_url: parsed.metadata_url?.replace(/\0/g, "") ?? null,
|
||||
|
|
|
@ -181,7 +181,7 @@ export function AccountDetailsPage({ address, tab }: Props) {
|
|||
<div className="container mt-n3">
|
||||
<div className="header">
|
||||
<div className="header-body">
|
||||
<AccountHeader address={address} info={info} />
|
||||
<AccountHeader address={address} account={info?.data} />
|
||||
</div>
|
||||
</div>
|
||||
{!pubkey ? (
|
||||
|
@ -195,22 +195,22 @@ export function AccountDetailsPage({ address, tab }: Props) {
|
|||
|
||||
export function AccountHeader({
|
||||
address,
|
||||
info,
|
||||
account,
|
||||
}: {
|
||||
address: string;
|
||||
info?: CacheEntry<Account>;
|
||||
account?: Account;
|
||||
}) {
|
||||
const { tokenRegistry } = useTokenRegistry();
|
||||
const tokenDetails = tokenRegistry.get(address);
|
||||
const mintInfo = useMintAccountInfo(address);
|
||||
const account = info?.data;
|
||||
const data = account?.details?.data;
|
||||
const isToken = data?.program === "spl-token" && data?.parsed.type === "mint";
|
||||
const parsedData = account?.data.parsed;
|
||||
const isToken =
|
||||
parsedData?.program === "spl-token" && parsedData?.parsed.type === "mint";
|
||||
|
||||
if (isMetaplexNFT(data, mintInfo)) {
|
||||
if (isMetaplexNFT(parsedData, mintInfo)) {
|
||||
return (
|
||||
<MetaplexNFTHeader
|
||||
nftData={(data as TokenProgramData).nftData!}
|
||||
nftData={(parsedData as TokenProgramData).nftData!}
|
||||
address={address}
|
||||
/>
|
||||
);
|
||||
|
@ -226,12 +226,14 @@ export function AccountHeader({
|
|||
let unverified = false;
|
||||
|
||||
// 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) {
|
||||
if (!parsedData?.nftData?.metadata.data.uri && tokenDetails) {
|
||||
token = tokenDetails;
|
||||
} else if (data?.nftData) {
|
||||
} else if (parsedData?.nftData) {
|
||||
token = {
|
||||
logoURI: data?.nftData?.json?.image,
|
||||
name: data?.nftData?.json?.name ?? data?.nftData.metadata.data.name,
|
||||
logoURI: parsedData?.nftData?.json?.image,
|
||||
name:
|
||||
parsedData?.nftData?.json?.name ??
|
||||
parsedData?.nftData.metadata.data.name,
|
||||
};
|
||||
unverified = true;
|
||||
} else if (tokenDetails) {
|
||||
|
@ -339,62 +341,68 @@ function DetailsSections({
|
|||
}
|
||||
|
||||
function InfoSection({ account }: { account: Account }) {
|
||||
const details = account?.details;
|
||||
const data = details?.data;
|
||||
const parsedData = account.data.parsed;
|
||||
const rawData = account.data.raw;
|
||||
|
||||
if (data && data.program === "bpf-upgradeable-loader") {
|
||||
if (parsedData && parsedData.program === "bpf-upgradeable-loader") {
|
||||
return (
|
||||
<UpgradeableLoaderAccountSection
|
||||
account={account}
|
||||
parsedData={data.parsed}
|
||||
programData={data.programData}
|
||||
parsedData={parsedData.parsed}
|
||||
programData={parsedData.programData}
|
||||
/>
|
||||
);
|
||||
} else if (data && data.program === "stake") {
|
||||
} else if (parsedData && parsedData.program === "stake") {
|
||||
return (
|
||||
<StakeAccountSection
|
||||
account={account}
|
||||
stakeAccount={data.parsed.info}
|
||||
activation={data.activation}
|
||||
stakeAccountType={data.parsed.type}
|
||||
stakeAccount={parsedData.parsed.info}
|
||||
activation={parsedData.activation}
|
||||
stakeAccountType={parsedData.parsed.type}
|
||||
/>
|
||||
);
|
||||
} else if (account.details?.owner.toBase58() === NFTOKEN_ADDRESS) {
|
||||
} else if (account.owner.toBase58() === NFTOKEN_ADDRESS) {
|
||||
return <NFTokenAccountSection account={account} />;
|
||||
} else if (data && data.program === "spl-token") {
|
||||
return <TokenAccountSection account={account} tokenAccount={data.parsed} />;
|
||||
} 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") {
|
||||
} else if (parsedData && parsedData.program === "spl-token") {
|
||||
return (
|
||||
<SysvarAccountSection account={account} sysvarAccount={data.parsed} />
|
||||
<TokenAccountSection account={account} tokenAccount={parsedData.parsed} />
|
||||
);
|
||||
} else if (data && data.program === "config") {
|
||||
} else if (parsedData && parsedData.program === "nonce") {
|
||||
return (
|
||||
<ConfigAccountSection account={account} configAccount={data.parsed} />
|
||||
<NonceAccountSection account={account} nonceAccount={parsedData.parsed} />
|
||||
);
|
||||
} else if (parsedData && parsedData.program === "vote") {
|
||||
return (
|
||||
<VoteAccountSection account={account} voteAccount={parsedData.parsed} />
|
||||
);
|
||||
} else if (parsedData && parsedData.program === "sysvar") {
|
||||
return (
|
||||
<SysvarAccountSection
|
||||
account={account}
|
||||
sysvarAccount={parsedData.parsed}
|
||||
/>
|
||||
);
|
||||
} else if (parsedData && parsedData.program === "config") {
|
||||
return (
|
||||
<ConfigAccountSection
|
||||
account={account}
|
||||
configAccount={parsedData.parsed}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
data &&
|
||||
data.program === "address-lookup-table" &&
|
||||
data.parsed.type === "lookupTable"
|
||||
parsedData &&
|
||||
parsedData.program === "address-lookup-table" &&
|
||||
parsedData.parsed.type === "lookupTable"
|
||||
) {
|
||||
return (
|
||||
<AddressLookupTableAccountSection
|
||||
account={account}
|
||||
lookupTableAccount={data.parsed.info}
|
||||
lookupTableAccount={parsedData.parsed.info}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
details?.rawData &&
|
||||
isAddressLookupTableAccount(details.owner, details.rawData)
|
||||
) {
|
||||
} else if (rawData && isAddressLookupTableAccount(account.owner, rawData)) {
|
||||
return (
|
||||
<AddressLookupTableAccountSection
|
||||
account={account}
|
||||
data={details.rawData}
|
||||
/>
|
||||
<AddressLookupTableAccountSection account={account} data={rawData} />
|
||||
);
|
||||
} else {
|
||||
return <UnknownAccountCard account={account} />;
|
||||
|
@ -442,8 +450,8 @@ function MoreSection({
|
|||
tabs: (JSX.Element | null)[];
|
||||
}) {
|
||||
const pubkey = account.pubkey;
|
||||
const details = account?.details;
|
||||
const data = details?.data;
|
||||
const parsedData = account.data.parsed;
|
||||
const rawData = account.data.raw;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -465,27 +473,27 @@ function MoreSection({
|
|||
{tab === "instructions" && <TokenInstructionsCard pubkey={pubkey} />}
|
||||
{tab === "largest" && <TokenLargestAccountsCard pubkey={pubkey} />}
|
||||
{tab === "rewards" && <RewardsCard pubkey={pubkey} />}
|
||||
{tab === "vote-history" && data?.program === "vote" && (
|
||||
<VotesCard voteAccount={data.parsed} />
|
||||
{tab === "vote-history" && parsedData?.program === "vote" && (
|
||||
<VotesCard voteAccount={parsedData.parsed} />
|
||||
)}
|
||||
{tab === "slot-hashes" &&
|
||||
data?.program === "sysvar" &&
|
||||
data.parsed.type === "slotHashes" && (
|
||||
<SlotHashesCard sysvarAccount={data.parsed} />
|
||||
parsedData?.program === "sysvar" &&
|
||||
parsedData.parsed.type === "slotHashes" && (
|
||||
<SlotHashesCard sysvarAccount={parsedData.parsed} />
|
||||
)}
|
||||
{tab === "stake-history" &&
|
||||
data?.program === "sysvar" &&
|
||||
data.parsed.type === "stakeHistory" && (
|
||||
<StakeHistoryCard sysvarAccount={data.parsed} />
|
||||
parsedData?.program === "sysvar" &&
|
||||
parsedData.parsed.type === "stakeHistory" && (
|
||||
<StakeHistoryCard sysvarAccount={parsedData.parsed} />
|
||||
)}
|
||||
{tab === "blockhashes" &&
|
||||
data?.program === "sysvar" &&
|
||||
data.parsed.type === "recentBlockhashes" && (
|
||||
<BlockhashesCard blockhashes={data.parsed.info} />
|
||||
parsedData?.program === "sysvar" &&
|
||||
parsedData.parsed.type === "recentBlockhashes" && (
|
||||
<BlockhashesCard blockhashes={parsedData.parsed.info} />
|
||||
)}
|
||||
{tab === "metadata" && (
|
||||
<MetaplexMetadataCard
|
||||
nftData={(account.details?.data as TokenProgramData).nftData!}
|
||||
nftData={(account.data.parsed as TokenProgramData).nftData!}
|
||||
/>
|
||||
)}
|
||||
{tab === "nftoken-collection-nfts" && (
|
||||
|
@ -497,13 +505,14 @@ function MoreSection({
|
|||
)}
|
||||
{tab === "attributes" && (
|
||||
<MetaplexNFTAttributesCard
|
||||
nftData={(account.details?.data as TokenProgramData).nftData!}
|
||||
nftData={(account.data.parsed as TokenProgramData).nftData!}
|
||||
/>
|
||||
)}
|
||||
{tab === "domains" && <DomainsCard pubkey={pubkey} />}
|
||||
{tab === "security" && data?.program === "bpf-upgradeable-loader" && (
|
||||
<SecurityCard data={data} />
|
||||
)}
|
||||
{tab === "security" &&
|
||||
parsedData?.program === "bpf-upgradeable-loader" && (
|
||||
<SecurityCard data={parsedData} />
|
||||
)}
|
||||
{tab === "anchor-program" && (
|
||||
<React.Suspense
|
||||
fallback={<LoadingCard message="Loading anchor program IDL" />}
|
||||
|
@ -521,14 +530,14 @@ function MoreSection({
|
|||
</React.Suspense>
|
||||
)}
|
||||
{tab === "entries" &&
|
||||
details?.rawData &&
|
||||
isAddressLookupTableAccount(details.owner, details.rawData) && (
|
||||
<LookupTableEntriesCard lookupTableAccountData={details?.rawData} />
|
||||
rawData &&
|
||||
isAddressLookupTableAccount(account.owner, rawData) && (
|
||||
<LookupTableEntriesCard lookupTableAccountData={rawData} />
|
||||
)}
|
||||
{tab === "entries" &&
|
||||
data?.program === "address-lookup-table" &&
|
||||
data.parsed.type === "lookupTable" && (
|
||||
<LookupTableEntriesCard parsedLookupTable={data.parsed.info} />
|
||||
parsedData?.program === "address-lookup-table" &&
|
||||
parsedData.parsed.type === "lookupTable" && (
|
||||
<LookupTableEntriesCard parsedLookupTable={parsedData.parsed.info} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -536,7 +545,7 @@ function MoreSection({
|
|||
|
||||
function getTabs(pubkey: PublicKey, account: Account): TabComponent[] {
|
||||
const address = pubkey.toBase58();
|
||||
const data = account.details?.data;
|
||||
const parsedData = account.data.parsed;
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
slug: "history",
|
||||
|
@ -546,31 +555,31 @@ function getTabs(pubkey: PublicKey, account: Account): TabComponent[] {
|
|||
];
|
||||
|
||||
let programTypeKey = "";
|
||||
if (data && "parsed" in data && "type" in data.parsed) {
|
||||
programTypeKey = `${data.program}:${data.parsed.type}`;
|
||||
if (parsedData) {
|
||||
programTypeKey = `${parsedData.program}:${parsedData.parsed.type}`;
|
||||
}
|
||||
|
||||
if (data && data.program in TABS_LOOKUP) {
|
||||
tabs.push(...TABS_LOOKUP[data.program]);
|
||||
if (parsedData && parsedData.program in TABS_LOOKUP) {
|
||||
tabs.push(...TABS_LOOKUP[parsedData.program]);
|
||||
}
|
||||
|
||||
if (data && programTypeKey in TABS_LOOKUP) {
|
||||
if (parsedData && programTypeKey in TABS_LOOKUP) {
|
||||
tabs.push(...TABS_LOOKUP[programTypeKey]);
|
||||
}
|
||||
|
||||
// Add the key for address lookup tables
|
||||
if (
|
||||
account.details?.rawData &&
|
||||
isAddressLookupTableAccount(account.details.owner, account.details.rawData)
|
||||
account.data.raw &&
|
||||
isAddressLookupTableAccount(account.owner, account.data.raw)
|
||||
) {
|
||||
tabs.push(...TABS_LOOKUP["address-lookup-table"]);
|
||||
}
|
||||
|
||||
// Add the key for Metaplex NFTs
|
||||
if (
|
||||
data &&
|
||||
parsedData &&
|
||||
programTypeKey === "spl-token:mint" &&
|
||||
(data as TokenProgramData).nftData
|
||||
(parsedData as TokenProgramData).nftData
|
||||
) {
|
||||
tabs.push(...TABS_LOOKUP[`${programTypeKey}:metaplexNFT`]);
|
||||
}
|
||||
|
@ -589,9 +598,9 @@ function getTabs(pubkey: PublicKey, account: Account): TabComponent[] {
|
|||
|
||||
if (
|
||||
!isNFToken &&
|
||||
(!data ||
|
||||
(!parsedData ||
|
||||
!(
|
||||
TOKEN_TABS_HIDDEN.includes(data.program) ||
|
||||
TOKEN_TABS_HIDDEN.includes(parsedData.program) ||
|
||||
TOKEN_TABS_HIDDEN.includes(programTypeKey)
|
||||
))
|
||||
) {
|
||||
|
@ -657,7 +666,7 @@ function getAnchorTabs(pubkey: PublicKey, account: Account) {
|
|||
<AccountDataLink
|
||||
tab={accountDataTab}
|
||||
address={pubkey.toString()}
|
||||
programId={account.details?.owner}
|
||||
programId={account.owner}
|
||||
/>
|
||||
</React.Suspense>
|
||||
),
|
||||
|
@ -676,7 +685,7 @@ function AnchorProgramLink({
|
|||
pubkey: PublicKey;
|
||||
}) {
|
||||
const { url } = useCluster();
|
||||
const anchorProgram = useAnchorProgram(pubkey.toString() ?? "", url);
|
||||
const anchorProgram = useAnchorProgram(pubkey.toString(), url);
|
||||
|
||||
if (!anchorProgram) {
|
||||
return null;
|
||||
|
@ -702,13 +711,10 @@ function AccountDataLink({
|
|||
}: {
|
||||
address: string;
|
||||
tab: Tab;
|
||||
programId: PublicKey | undefined;
|
||||
programId: PublicKey;
|
||||
}) {
|
||||
const { url } = useCluster();
|
||||
const accountAnchorProgram = useAnchorProgram(
|
||||
programId?.toString() ?? "",
|
||||
url
|
||||
);
|
||||
const accountAnchorProgram = useAnchorProgram(programId.toString(), url);
|
||||
|
||||
if (!accountAnchorProgram) {
|
||||
return null;
|
||||
|
|
|
@ -17,12 +17,11 @@ export const createFeePayerValidator = (
|
|||
feeLamports: number
|
||||
): AccountValidator => {
|
||||
return (account: Account): string | undefined => {
|
||||
if (account.details === undefined) return "Account doesn't exist";
|
||||
if (!account.details.owner.equals(SystemProgram.programId))
|
||||
if (account.lamports === 0) return "Account doesn't exist";
|
||||
if (!account.owner.equals(SystemProgram.programId))
|
||||
return "Only system-owned accounts can pay fees";
|
||||
// TODO: Actually nonce accounts can pay fees too
|
||||
if (account.details.space > 0)
|
||||
return "Only unallocated accounts can pay fees";
|
||||
if (account.space > 0) return "Only unallocated accounts can pay fees";
|
||||
if (account.lamports < feeLamports) {
|
||||
return "Insufficient funds for fees";
|
||||
}
|
||||
|
@ -31,9 +30,8 @@ export const createFeePayerValidator = (
|
|||
};
|
||||
|
||||
export const programValidator = (account: Account): string | undefined => {
|
||||
if (account.details === undefined) return "Account doesn't exist";
|
||||
if (!account.details.executable)
|
||||
return "Only executable accounts can be invoked";
|
||||
if (account.lamports === 0) return "Account doesn't exist";
|
||||
if (!account.executable) return "Only executable accounts can be invoked";
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -108,7 +106,8 @@ function AccountInfo({
|
|||
}
|
||||
}, [address, status]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
if (!info?.data)
|
||||
const account = info?.data;
|
||||
if (!account)
|
||||
return (
|
||||
<span className="text-muted">
|
||||
<span className="spinner-grow spinner-grow-sm me-2"></span>
|
||||
|
@ -116,23 +115,22 @@ function AccountInfo({
|
|||
</span>
|
||||
);
|
||||
|
||||
const errorMessage = validator && validator(info.data);
|
||||
const errorMessage = validator && validator(account);
|
||||
if (errorMessage) return <span className="text-warning">{errorMessage}</span>;
|
||||
|
||||
if (!info.data.details) {
|
||||
if (account.lamports === 0) {
|
||||
return <span className="text-muted">Account doesn't exist</span>;
|
||||
}
|
||||
|
||||
const owner = info.data.details.owner;
|
||||
const ownerAddress = owner.toBase58();
|
||||
const ownerAddress = account.owner.toBase58();
|
||||
const ownerLabel = addressLabel(ownerAddress, cluster);
|
||||
|
||||
return (
|
||||
<span className="text-muted">
|
||||
{`Owned by ${ownerLabel || ownerAddress}.`}
|
||||
{` Balance is ${lamportsToSolString(info.data.lamports)} SOL.`}
|
||||
{` Balance is ${lamportsToSolString(account.lamports)} SOL.`}
|
||||
{` Size is ${new Intl.NumberFormat("en-US").format(
|
||||
info.data.details.space
|
||||
account.space
|
||||
)} byte(s).`}
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
StakeActivationData,
|
||||
AddressLookupTableAccount,
|
||||
AddressLookupTableProgram,
|
||||
SystemProgram,
|
||||
} from "@solana/web3.js";
|
||||
import { useCluster, Cluster } from "../cluster";
|
||||
import { HistoryProvider } from "./history";
|
||||
|
@ -88,7 +89,7 @@ export type AddressLookupTableProgramData = {
|
|||
parsed: ParsedAddressLookupTableAccount;
|
||||
};
|
||||
|
||||
export type ProgramData =
|
||||
export type ParsedData =
|
||||
| UpgradeableLoaderAccountData
|
||||
| StakeProgramData
|
||||
| TokenProgramData
|
||||
|
@ -98,18 +99,18 @@ export type ProgramData =
|
|||
| ConfigProgramData
|
||||
| AddressLookupTableProgramData;
|
||||
|
||||
export interface Details {
|
||||
executable: boolean;
|
||||
owner: PublicKey;
|
||||
space: number;
|
||||
data?: ProgramData;
|
||||
rawData?: Buffer;
|
||||
export interface AccountData {
|
||||
parsed?: ParsedData;
|
||||
raw?: Buffer;
|
||||
}
|
||||
|
||||
export interface Account {
|
||||
pubkey: PublicKey;
|
||||
lamports: number;
|
||||
details?: Details;
|
||||
executable: boolean;
|
||||
owner: PublicKey;
|
||||
space: number;
|
||||
data: AccountData;
|
||||
}
|
||||
|
||||
type State = Cache.State<Account>;
|
||||
|
@ -162,12 +163,17 @@ async function fetchAccountInfo(
|
|||
const connection = new Connection(url, "confirmed");
|
||||
const result = (await connection.getParsedAccountInfo(pubkey)).value;
|
||||
|
||||
let lamports, details;
|
||||
let account: Account;
|
||||
if (result === null) {
|
||||
lamports = 0;
|
||||
account = {
|
||||
pubkey,
|
||||
lamports: 0,
|
||||
owner: SystemProgram.programId,
|
||||
space: 0,
|
||||
executable: false,
|
||||
data: { raw: Buffer.alloc(0) },
|
||||
};
|
||||
} else {
|
||||
lamports = result.lamports;
|
||||
|
||||
// Only save data in memory if we can decode it
|
||||
let space: number;
|
||||
if (!("parsed" in result.data)) {
|
||||
|
@ -176,7 +182,7 @@ async function fetchAccountInfo(
|
|||
space = result.data.space;
|
||||
}
|
||||
|
||||
let data: ProgramData | undefined;
|
||||
let parsedData: ParsedData | undefined;
|
||||
if ("parsed" in result.data) {
|
||||
try {
|
||||
const info = create(result.data.parsed, ParsedInfo);
|
||||
|
@ -200,7 +206,7 @@ async function fetchAccountInfo(
|
|||
}
|
||||
}
|
||||
|
||||
data = {
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed,
|
||||
programData,
|
||||
|
@ -215,7 +221,7 @@ async function fetchAccountInfo(
|
|||
? await connection.getStakeActivation(pubkey)
|
||||
: undefined;
|
||||
|
||||
data = {
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed,
|
||||
activation,
|
||||
|
@ -223,25 +229,25 @@ async function fetchAccountInfo(
|
|||
break;
|
||||
}
|
||||
case "vote":
|
||||
data = {
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed: create(info, VoteAccount),
|
||||
};
|
||||
break;
|
||||
case "nonce":
|
||||
data = {
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed: create(info, NonceAccount),
|
||||
};
|
||||
break;
|
||||
case "sysvar":
|
||||
data = {
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed: create(info, SysvarAccount),
|
||||
};
|
||||
break;
|
||||
case "config":
|
||||
data = {
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed: create(info, ConfigAccount),
|
||||
};
|
||||
|
@ -250,7 +256,7 @@ async function fetchAccountInfo(
|
|||
case "address-lookup-table": {
|
||||
const parsed = create(info, ParsedAddressLookupTableAccount);
|
||||
|
||||
data = {
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed,
|
||||
};
|
||||
|
@ -291,14 +297,14 @@ async function fetchAccountInfo(
|
|||
// unable to find NFT metadata account
|
||||
}
|
||||
|
||||
data = {
|
||||
parsedData = {
|
||||
program: result.data.program,
|
||||
parsed,
|
||||
nftData,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
data = undefined;
|
||||
parsedData = undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
reportError(error, { url, address: pubkey.toBase58() });
|
||||
|
@ -308,19 +314,23 @@ async function fetchAccountInfo(
|
|||
// If we cannot parse account layout as native spl account
|
||||
// then keep raw data for other components to decode
|
||||
let rawData: Buffer | undefined;
|
||||
if (!data && !("parsed" in result.data)) {
|
||||
if (!parsedData && !("parsed" in result.data)) {
|
||||
rawData = result.data;
|
||||
}
|
||||
|
||||
details = {
|
||||
account = {
|
||||
pubkey,
|
||||
lamports: result.lamports,
|
||||
space,
|
||||
executable: result.executable,
|
||||
owner: result.owner,
|
||||
data,
|
||||
rawData,
|
||||
data: {
|
||||
parsed: parsedData,
|
||||
raw: rawData,
|
||||
},
|
||||
};
|
||||
}
|
||||
data = { pubkey, lamports, details };
|
||||
data = account;
|
||||
fetchStatus = FetchStatus.Fetched;
|
||||
} catch (error) {
|
||||
if (cluster !== Cluster.Custom) {
|
||||
|
@ -413,16 +423,20 @@ export function useMintAccountInfo(
|
|||
): MintAccountInfo | undefined {
|
||||
const accountInfo = useAccountInfo(address);
|
||||
return React.useMemo(() => {
|
||||
if (address === undefined) return;
|
||||
if (address === undefined || accountInfo?.data === undefined) return;
|
||||
const account = accountInfo.data;
|
||||
|
||||
try {
|
||||
const data = accountInfo?.data?.details?.data;
|
||||
if (!data) return;
|
||||
if (data.program !== "spl-token" || data.parsed.type !== "mint") {
|
||||
const parsedData = account.data.parsed;
|
||||
if (!parsedData) return;
|
||||
if (
|
||||
parsedData.program !== "spl-token" ||
|
||||
parsedData.parsed.type !== "mint"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return create(data.parsed.info, MintAccountInfo);
|
||||
return create(parsedData.parsed.info, MintAccountInfo);
|
||||
} catch (err) {
|
||||
reportError(err, { address });
|
||||
}
|
||||
|
@ -433,16 +447,20 @@ export function useTokenAccountInfo(
|
|||
address: string | undefined
|
||||
): TokenAccountInfo | undefined {
|
||||
const accountInfo = useAccountInfo(address);
|
||||
if (address === undefined) return;
|
||||
if (address === undefined || accountInfo?.data === undefined) return;
|
||||
const account = accountInfo.data;
|
||||
|
||||
try {
|
||||
const data = accountInfo?.data?.details?.data;
|
||||
if (!data) return;
|
||||
if (data.program !== "spl-token" || data.parsed.type !== "account") {
|
||||
const parsedData = account.data.parsed;
|
||||
if (!parsedData) return;
|
||||
if (
|
||||
parsedData.program !== "spl-token" ||
|
||||
parsedData.parsed.type !== "account"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return create(data.parsed.info, TokenAccountInfo);
|
||||
return create(parsedData.parsed.info, TokenAccountInfo);
|
||||
} catch (err) {
|
||||
reportError(err, { address });
|
||||
}
|
||||
|
@ -452,24 +470,24 @@ export function useAddressLookupTable(
|
|||
address: string | undefined
|
||||
): AddressLookupTableAccount | undefined | string {
|
||||
const accountInfo = useAccountInfo(address);
|
||||
if (address === undefined) return;
|
||||
if (accountInfo?.data?.details === undefined) return;
|
||||
if (accountInfo.data.lamports === 0) return "Lookup Table Not Found";
|
||||
const { data, rawData } = accountInfo.data.details;
|
||||
if (address === undefined || accountInfo?.data === undefined) return;
|
||||
const account = accountInfo.data;
|
||||
if (account.lamports === 0) return "Lookup Table Not Found";
|
||||
const { parsed: parsedData, raw: rawData } = account.data;
|
||||
|
||||
const key = new PublicKey(address);
|
||||
if (data && data.program === "address-lookup-table") {
|
||||
if (data.parsed.type === "lookupTable") {
|
||||
if (parsedData && parsedData.program === "address-lookup-table") {
|
||||
if (parsedData.parsed.type === "lookupTable") {
|
||||
return new AddressLookupTableAccount({
|
||||
key,
|
||||
state: data.parsed.info,
|
||||
state: parsedData.parsed.info,
|
||||
});
|
||||
} else if (data.parsed.type === "uninitialized") {
|
||||
} else if (parsedData.parsed.type === "uninitialized") {
|
||||
return "Lookup Table Uninitialized";
|
||||
}
|
||||
} else if (
|
||||
rawData &&
|
||||
accountInfo.data.details.owner.equals(AddressLookupTableProgram.programId)
|
||||
account.owner.equals(AddressLookupTableProgram.programId)
|
||||
) {
|
||||
try {
|
||||
return new AddressLookupTableAccount({
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { MintAccountInfo } from "validators/accounts/token";
|
||||
import { ProgramData } from "..";
|
||||
import { ParsedData } from "..";
|
||||
|
||||
export default function isMetaplexNFT(
|
||||
data?: ProgramData,
|
||||
parsedData?: ParsedData,
|
||||
mintInfo?: MintAccountInfo
|
||||
) {
|
||||
return (
|
||||
data?.program === "spl-token" &&
|
||||
data?.parsed.type === "mint" &&
|
||||
data?.nftData &&
|
||||
parsedData?.program === "spl-token" &&
|
||||
parsedData?.parsed.type === "mint" &&
|
||||
parsedData?.nftData &&
|
||||
mintInfo?.decimals === 0 &&
|
||||
(parseInt(mintInfo.supply) === 1 ||
|
||||
data?.nftData?.metadata?.tokenStandard === 1)
|
||||
parsedData?.nftData?.metadata?.tokenStandard === 1)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -169,7 +169,7 @@ export function abbreviatedNumber(value: number, fixed = 1) {
|
|||
}
|
||||
|
||||
export const pubkeyToString = (key: PublicKey | string = "") => {
|
||||
return typeof key === "string" ? key : key?.toBase58() || "";
|
||||
return typeof key === "string" ? key : key.toBase58();
|
||||
};
|
||||
|
||||
export const getLast = (arr: string[]) => {
|
||||
|
|
Loading…
Reference in New Issue