Explorer: Add details page for address lookup table accounts (#27133)

This commit is contained in:
Justin Starry 2022-08-14 15:50:23 +01:00 committed by GitHub
parent 32903f2e8e
commit ba4a2d2241
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 198 additions and 7 deletions

View File

@ -0,0 +1,84 @@
import React from "react";
import { TableCardBody } from "components/common/TableCardBody";
import { SolBalance } from "utils";
import { Account, useFetchAccountInfo } from "providers/accounts";
import { Address } from "components/common/Address";
import { AddressLookupTableAccount } from "@solana/web3.js";
import { Slot } from "components/common/Slot";
export function AddressLookupTableAccountSection({
account,
data,
}: {
account: Account;
data: Uint8Array;
}) {
const lookupTableAccount = React.useMemo(() => {
return new AddressLookupTableAccount({
key: account.pubkey,
state: AddressLookupTableAccount.deserialize(data),
});
}, [account, data]);
const refresh = useFetchAccountInfo();
return (
<div className="card">
<div className="card-header">
<h3 className="card-header-title mb-0 d-flex align-items-center">
Address Lookup Table Account
</h3>
<button
className="btn btn-white btn-sm"
onClick={() => refresh(account.pubkey)}
>
<span className="fe fe-refresh-cw me-2"></span>
Refresh
</button>
</div>
<TableCardBody>
<tr>
<td>Address</td>
<td className="text-lg-end">
<Address pubkey={account.pubkey} alignRight raw />
</td>
</tr>
<tr>
<td>Balance (SOL)</td>
<td className="text-lg-end text-uppercase">
<SolBalance lamports={account.lamports || 0} />
</td>
</tr>
<tr>
<td>Activation Status</td>
<td className="text-lg-end text-uppercase">
{lookupTableAccount.isActive() ? "Active" : "Deactivated"}
</td>
</tr>
<tr>
<td>Last Extended Slot</td>
<td className="text-lg-end">
{lookupTableAccount.state.lastExtendedSlot === 0 ? (
"None (Empty)"
) : (
<Slot slot={lookupTableAccount.state.lastExtendedSlot} link />
)}
</td>
</tr>
<tr>
<td>Authority</td>
<td className="text-lg-end">
{lookupTableAccount.state.authority === undefined ? (
"None (Frozen)"
) : (
<Address
pubkey={lookupTableAccount.state.authority}
alignRight
link
/>
)}
</td>
</tr>
</TableCardBody>
</div>
);
}

View File

@ -0,0 +1,60 @@
import React from "react";
import { AddressLookupTableAccount, PublicKey } from "@solana/web3.js";
import { Address } from "components/common/Address";
export function LookupTableEntriesCard({
lookupTableAccountData,
}: {
lookupTableAccountData: Uint8Array;
}) {
const lookupTableState = React.useMemo(() => {
return AddressLookupTableAccount.deserialize(lookupTableAccountData);
}, [lookupTableAccountData]);
return (
<div className="card">
<div className="card-header">
<div className="row align-items-center">
<div className="col">
<h3 className="card-header-title">Lookup Table Entries</h3>
</div>
</div>
</div>
<div className="table-responsive mb-0">
<table className="table table-sm table-nowrap card-table">
<thead>
<tr>
<th className="w-1 text-muted">Index</th>
<th className="text-muted">Address</th>
</tr>
</thead>
<tbody className="list">
{lookupTableState.addresses.length > 0 &&
lookupTableState.addresses.map((entry: PublicKey, index) => {
return renderRow(entry, index);
})}
</tbody>
</table>
</div>
{lookupTableState.addresses.length === 0 && (
<div className="card-footer">
<div className="text-muted text-center">No entries found</div>
</div>
)}
</div>
);
}
const renderRow = (entry: PublicKey, index: number) => {
return (
<tr key={index}>
<td className="w-1 font-monospace">{index}</td>
<td className="font-monospace">
<Address pubkey={entry} link />
</td>
</tr>
);
};

View File

@ -0,0 +1,13 @@
import { PublicKey } from "@solana/web3.js";
const PROGRAM_ID: string = "AddressLookupTab1e1111111111111111111111111";
export function isAddressLookupTableAccount(
accountOwner: PublicKey,
accountData: Uint8Array
): boolean {
if (accountOwner.toBase58() !== PROGRAM_ID) return false;
if (!accountData || accountData.length === 0) return false;
const LOOKUP_TABLE_ACCOUNT_TYPE = 1;
return accountData[0] === LOOKUP_TABLE_ACCOUNT_TYPE;
}

View File

@ -1,8 +1,6 @@
import { TransactionInstruction } from "@solana/web3.js";
export const PROGRAM_IDS: string[] = [
"AddressLookupTab1e1111111111111111111111111",
];
const PROGRAM_ID: string = "AddressLookupTab1e1111111111111111111111111";
const INSTRUCTION_LOOKUP: { [key: number]: string } = {
0: "Create Lookup Table",
@ -15,7 +13,7 @@ const INSTRUCTION_LOOKUP: { [key: number]: string } = {
export function isAddressLookupTableInstruction(
instruction: TransactionInstruction
): boolean {
return PROGRAM_IDS.includes(instruction.programId.toBase58());
return PROGRAM_ID === instruction.programId.toBase58();
}
export function parseAddressLookupTableInstructionTitle(

View File

@ -44,6 +44,9 @@ import { SecurityCard } from "components/account/SecurityCard";
import { AnchorAccountCard } from "components/account/AnchorAccountCard";
import { AnchorProgramCard } from "components/account/AnchorProgramCard";
import { useAnchorProgram } from "providers/anchor";
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";
const IDENTICON_WIDTH = 64;
@ -124,6 +127,13 @@ const TABS_LOOKUP: { [id: string]: Tab[] } = {
path: "/security",
},
],
"address-lookup-table": [
{
slug: "entries",
title: "Table Entries",
path: "/entries",
},
],
};
const TOKEN_TABS_HIDDEN = [
@ -309,7 +319,8 @@ function DetailsSections({
}
function InfoSection({ account }: { account: Account }) {
const data = account?.details?.data;
const details = account?.details;
const data = details?.data;
if (data && data.program === "bpf-upgradeable-loader") {
return (
@ -342,6 +353,16 @@ function InfoSection({ account }: { account: Account }) {
return (
<ConfigAccountSection account={account} configAccount={data.parsed} />
);
} else if (
details?.rawData &&
isAddressLookupTableAccount(details.owner, details.rawData)
) {
return (
<AddressLookupTableAccountSection
account={account}
data={details.rawData}
/>
);
} else {
return <UnknownAccountCard account={account} />;
}
@ -374,7 +395,8 @@ export type MoreTabs =
| "domains"
| "security"
| "anchor-program"
| "anchor-account";
| "anchor-account"
| "entries";
function MoreSection({
account,
@ -386,7 +408,8 @@ function MoreSection({
tabs: (JSX.Element | null)[];
}) {
const pubkey = account.pubkey;
const data = account?.details?.data;
const details = account?.details;
const data = details?.data;
return (
<>
@ -456,6 +479,11 @@ function MoreSection({
<AnchorAccountCard account={account} />
</React.Suspense>
)}
{tab === "entries" &&
details?.rawData &&
isAddressLookupTableAccount(details.owner, details.rawData) && (
<LookupTableEntriesCard lookupTableAccountData={details?.rawData} />
)}
</>
);
}
@ -484,6 +512,14 @@ function getTabs(pubkey: PublicKey, account: Account): TabComponent[] {
tabs.push(...TABS_LOOKUP[programTypeKey]);
}
// 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"]);
}
// Add the key for Metaplex NFTs
if (
data &&