Explorer: Add details page for address lookup table accounts (#27133)
This commit is contained in:
parent
32903f2e8e
commit
ba4a2d2241
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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 &&
|
||||
|
|
Loading…
Reference in New Issue