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";
|
import { TransactionInstruction } from "@solana/web3.js";
|
||||||
|
|
||||||
export const PROGRAM_IDS: string[] = [
|
const PROGRAM_ID: string = "AddressLookupTab1e1111111111111111111111111";
|
||||||
"AddressLookupTab1e1111111111111111111111111",
|
|
||||||
];
|
|
||||||
|
|
||||||
const INSTRUCTION_LOOKUP: { [key: number]: string } = {
|
const INSTRUCTION_LOOKUP: { [key: number]: string } = {
|
||||||
0: "Create Lookup Table",
|
0: "Create Lookup Table",
|
||||||
|
@ -15,7 +13,7 @@ const INSTRUCTION_LOOKUP: { [key: number]: string } = {
|
||||||
export function isAddressLookupTableInstruction(
|
export function isAddressLookupTableInstruction(
|
||||||
instruction: TransactionInstruction
|
instruction: TransactionInstruction
|
||||||
): boolean {
|
): boolean {
|
||||||
return PROGRAM_IDS.includes(instruction.programId.toBase58());
|
return PROGRAM_ID === instruction.programId.toBase58();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseAddressLookupTableInstructionTitle(
|
export function parseAddressLookupTableInstructionTitle(
|
||||||
|
|
|
@ -44,6 +44,9 @@ import { SecurityCard } from "components/account/SecurityCard";
|
||||||
import { AnchorAccountCard } from "components/account/AnchorAccountCard";
|
import { AnchorAccountCard } from "components/account/AnchorAccountCard";
|
||||||
import { AnchorProgramCard } from "components/account/AnchorProgramCard";
|
import { AnchorProgramCard } from "components/account/AnchorProgramCard";
|
||||||
import { useAnchorProgram } from "providers/anchor";
|
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;
|
const IDENTICON_WIDTH = 64;
|
||||||
|
|
||||||
|
@ -124,6 +127,13 @@ const TABS_LOOKUP: { [id: string]: Tab[] } = {
|
||||||
path: "/security",
|
path: "/security",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"address-lookup-table": [
|
||||||
|
{
|
||||||
|
slug: "entries",
|
||||||
|
title: "Table Entries",
|
||||||
|
path: "/entries",
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const TOKEN_TABS_HIDDEN = [
|
const TOKEN_TABS_HIDDEN = [
|
||||||
|
@ -309,7 +319,8 @@ function DetailsSections({
|
||||||
}
|
}
|
||||||
|
|
||||||
function InfoSection({ account }: { account: Account }) {
|
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") {
|
if (data && data.program === "bpf-upgradeable-loader") {
|
||||||
return (
|
return (
|
||||||
|
@ -342,6 +353,16 @@ function InfoSection({ account }: { account: Account }) {
|
||||||
return (
|
return (
|
||||||
<ConfigAccountSection account={account} configAccount={data.parsed} />
|
<ConfigAccountSection account={account} configAccount={data.parsed} />
|
||||||
);
|
);
|
||||||
|
} else if (
|
||||||
|
details?.rawData &&
|
||||||
|
isAddressLookupTableAccount(details.owner, details.rawData)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<AddressLookupTableAccountSection
|
||||||
|
account={account}
|
||||||
|
data={details.rawData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return <UnknownAccountCard account={account} />;
|
return <UnknownAccountCard account={account} />;
|
||||||
}
|
}
|
||||||
|
@ -374,7 +395,8 @@ export type MoreTabs =
|
||||||
| "domains"
|
| "domains"
|
||||||
| "security"
|
| "security"
|
||||||
| "anchor-program"
|
| "anchor-program"
|
||||||
| "anchor-account";
|
| "anchor-account"
|
||||||
|
| "entries";
|
||||||
|
|
||||||
function MoreSection({
|
function MoreSection({
|
||||||
account,
|
account,
|
||||||
|
@ -386,7 +408,8 @@ function MoreSection({
|
||||||
tabs: (JSX.Element | null)[];
|
tabs: (JSX.Element | null)[];
|
||||||
}) {
|
}) {
|
||||||
const pubkey = account.pubkey;
|
const pubkey = account.pubkey;
|
||||||
const data = account?.details?.data;
|
const details = account?.details;
|
||||||
|
const data = details?.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -456,6 +479,11 @@ function MoreSection({
|
||||||
<AnchorAccountCard account={account} />
|
<AnchorAccountCard account={account} />
|
||||||
</React.Suspense>
|
</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]);
|
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
|
// Add the key for Metaplex NFTs
|
||||||
if (
|
if (
|
||||||
data &&
|
data &&
|
||||||
|
|
Loading…
Reference in New Issue