Explorer: Refactor account provider data types (#28390)

This commit is contained in:
Justin Starry 2022-10-14 14:41:32 +08:00 committed by GitHub
parent d40875fbec
commit c38bca9932
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 243 additions and 232 deletions

View File

@ -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"
);

View File

@ -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(() => {

View File

@ -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();

View File

@ -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>

View File

@ -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}
/>
);

View File

@ -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>
);

View File

@ -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>
);

View File

@ -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>

View File

@ -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,

View File

@ -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;

View File

@ -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>
);

View File

@ -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({

View File

@ -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)
);
}

View File

@ -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[]) => {