Skip to content

Commit

Permalink
Explorer: Add details page for address lookup table accounts (solana-…
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry authored and xiangzhu70 committed Aug 17, 2022
1 parent 4d7fd2d commit 716577f
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -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>
);
}
Original file line number Diff line number Diff line change
@@ -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>
);
};
13 changes: 13 additions & 0 deletions explorer/src/components/account/address-lookup-table/types.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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(
Expand Down
42 changes: 39 additions & 3 deletions explorer/src/pages/AccountDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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} />;
}
Expand Down Expand Up @@ -374,7 +395,8 @@ export type MoreTabs =
| "domains"
| "security"
| "anchor-program"
| "anchor-account";
| "anchor-account"
| "entries";

function MoreSection({
account,
Expand All @@ -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 (
<>
Expand Down Expand Up @@ -456,6 +479,11 @@ function MoreSection({
<AnchorAccountCard account={account} />
</React.Suspense>
)}
{tab === "entries" &&
details?.rawData &&
isAddressLookupTableAccount(details.owner, details.rawData) && (
<LookupTableEntriesCard lookupTableAccountData={details?.rawData} />
)}
</>
);
}
Expand Down Expand Up @@ -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 &&
Expand Down

0 comments on commit 716577f

Please sign in to comment.