diff --git a/explorer/src/components/account/address-lookup-table/AddressLookupTableAccountSection.tsx b/explorer/src/components/account/address-lookup-table/AddressLookupTableAccountSection.tsx
new file mode 100644
index 0000000000..bffec1fa8b
--- /dev/null
+++ b/explorer/src/components/account/address-lookup-table/AddressLookupTableAccountSection.tsx
@@ -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 (
+
+
+
+ Address Lookup Table Account
+
+
+
+
+
+
+ Address |
+
+
+ |
+
+
+ Balance (SOL) |
+
+
+ |
+
+
+ Activation Status |
+
+ {lookupTableAccount.isActive() ? "Active" : "Deactivated"}
+ |
+
+
+ Last Extended Slot |
+
+ {lookupTableAccount.state.lastExtendedSlot === 0 ? (
+ "None (Empty)"
+ ) : (
+
+ )}
+ |
+
+
+ Authority |
+
+ {lookupTableAccount.state.authority === undefined ? (
+ "None (Frozen)"
+ ) : (
+
+ )}
+ |
+
+
+
+ );
+}
diff --git a/explorer/src/components/account/address-lookup-table/LookupTableEntriesCard.tsx b/explorer/src/components/account/address-lookup-table/LookupTableEntriesCard.tsx
new file mode 100644
index 0000000000..f20ff53b21
--- /dev/null
+++ b/explorer/src/components/account/address-lookup-table/LookupTableEntriesCard.tsx
@@ -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 (
+
+
+
+
+
Lookup Table Entries
+
+
+
+
+
+
+
+
+ Index |
+ Address |
+
+
+
+ {lookupTableState.addresses.length > 0 &&
+ lookupTableState.addresses.map((entry: PublicKey, index) => {
+ return renderRow(entry, index);
+ })}
+
+
+
+
+ {lookupTableState.addresses.length === 0 && (
+
+ )}
+
+ );
+}
+
+const renderRow = (entry: PublicKey, index: number) => {
+ return (
+
+ {index} |
+
+
+ |
+
+ );
+};
diff --git a/explorer/src/components/account/address-lookup-table/types.ts b/explorer/src/components/account/address-lookup-table/types.ts
new file mode 100644
index 0000000000..136c412d50
--- /dev/null
+++ b/explorer/src/components/account/address-lookup-table/types.ts
@@ -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;
+}
diff --git a/explorer/src/components/instruction/address-lookup-table/types.ts b/explorer/src/components/instruction/address-lookup-table/types.ts
index 4b1f01b794..9032b63d31 100644
--- a/explorer/src/components/instruction/address-lookup-table/types.ts
+++ b/explorer/src/components/instruction/address-lookup-table/types.ts
@@ -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(
diff --git a/explorer/src/pages/AccountDetailsPage.tsx b/explorer/src/pages/AccountDetailsPage.tsx
index b9c0fadf76..1e69cc3d39 100644
--- a/explorer/src/pages/AccountDetailsPage.tsx
+++ b/explorer/src/pages/AccountDetailsPage.tsx
@@ -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 (
);
+ } else if (
+ details?.rawData &&
+ isAddressLookupTableAccount(details.owner, details.rawData)
+ ) {
+ return (
+
+ );
} else {
return ;
}
@@ -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({
)}
+ {tab === "entries" &&
+ details?.rawData &&
+ isAddressLookupTableAccount(details.owner, 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 &&