explorer: Display upgradeable program details (#15348)
This commit is contained in:
parent
53959b4bbc
commit
aaa44355b1
|
@ -0,0 +1,83 @@
|
||||||
|
import React from "react";
|
||||||
|
import { TableCardBody } from "components/common/TableCardBody";
|
||||||
|
import { lamportsToSolString } from "utils";
|
||||||
|
import { Account, useFetchAccountInfo } from "providers/accounts";
|
||||||
|
import { Address } from "components/common/Address";
|
||||||
|
import {
|
||||||
|
ProgramAccountInfo,
|
||||||
|
ProgramDataAccountInfo,
|
||||||
|
} from "validators/accounts/upgradeable-program";
|
||||||
|
import { Slot } from "components/common/Slot";
|
||||||
|
|
||||||
|
export function UpgradeableProgramSection({
|
||||||
|
account,
|
||||||
|
programAccount,
|
||||||
|
programData,
|
||||||
|
}: {
|
||||||
|
account: Account;
|
||||||
|
programAccount: ProgramAccountInfo;
|
||||||
|
programData: ProgramDataAccountInfo;
|
||||||
|
}) {
|
||||||
|
const refresh = useFetchAccountInfo();
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-header">
|
||||||
|
<h3 className="card-header-title mb-0 d-flex align-items-center">
|
||||||
|
Program Account
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
className="btn btn-white btn-sm"
|
||||||
|
onClick={() => refresh(account.pubkey)}
|
||||||
|
>
|
||||||
|
<span className="fe fe-refresh-cw mr-2"></span>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TableCardBody>
|
||||||
|
<tr>
|
||||||
|
<td>Address</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={account.pubkey} alignRight raw />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Balance (SOL)</td>
|
||||||
|
<td className="text-lg-right text-uppercase">
|
||||||
|
{lamportsToSolString(account.lamports || 0)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Executable</td>
|
||||||
|
<td className="text-lg-right">Yes</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Executable Data</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={programAccount.programData} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Upgradeable</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
{programData.authority !== null ? "Yes" : "No"}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Last Deployed Slot</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Slot slot={programData.slot} link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{programData.authority !== null && (
|
||||||
|
<tr>
|
||||||
|
<td>Upgrade Authority</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
<Address pubkey={programData.authority} alignRight link />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</TableCardBody>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import { StakeHistoryCard } from "components/account/StakeHistoryCard";
|
||||||
import { BlockhashesCard } from "components/account/BlockhashesCard";
|
import { BlockhashesCard } from "components/account/BlockhashesCard";
|
||||||
import { ConfigAccountSection } from "components/account/ConfigAccountSection";
|
import { ConfigAccountSection } from "components/account/ConfigAccountSection";
|
||||||
import { useFlaggedAccounts } from "providers/accounts/flagged-accounts";
|
import { useFlaggedAccounts } from "providers/accounts/flagged-accounts";
|
||||||
|
import { UpgradeableProgramSection } from "components/account/UpgradeableProgramSection";
|
||||||
|
|
||||||
const TABS_LOOKUP: { [id: string]: Tab } = {
|
const TABS_LOOKUP: { [id: string]: Tab } = {
|
||||||
"spl-token:mint": {
|
"spl-token:mint": {
|
||||||
|
@ -174,7 +175,15 @@ function DetailsSections({ pubkey, tab }: { pubkey: PublicKey; tab?: string }) {
|
||||||
function InfoSection({ account }: { account: Account }) {
|
function InfoSection({ account }: { account: Account }) {
|
||||||
const data = account?.details?.data;
|
const data = account?.details?.data;
|
||||||
|
|
||||||
if (data && data.program === "stake") {
|
if (data && data.program === "bpf-upgradeable-loader") {
|
||||||
|
return (
|
||||||
|
<UpgradeableProgramSection
|
||||||
|
account={account}
|
||||||
|
programAccount={data.programAccount}
|
||||||
|
programData={data.programData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (data && data.program === "stake") {
|
||||||
return (
|
return (
|
||||||
<StakeAccountSection
|
<StakeAccountSection
|
||||||
account={account}
|
account={account}
|
||||||
|
@ -290,7 +299,7 @@ function getTabs(data?: ProgramData): Tab[] {
|
||||||
];
|
];
|
||||||
|
|
||||||
let programTypeKey = "";
|
let programTypeKey = "";
|
||||||
if (data && "type" in data.parsed) {
|
if (data && "parsed" in data && "type" in data.parsed) {
|
||||||
programTypeKey = `${data.program}:${data.parsed.type}`;
|
programTypeKey = `${data.program}:${data.parsed.type}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,12 @@ import { NonceAccount } from "validators/accounts/nonce";
|
||||||
import { SysvarAccount } from "validators/accounts/sysvar";
|
import { SysvarAccount } from "validators/accounts/sysvar";
|
||||||
import { ConfigAccount } from "validators/accounts/config";
|
import { ConfigAccount } from "validators/accounts/config";
|
||||||
import { FlaggedAccountsProvider } from "./flagged-accounts";
|
import { FlaggedAccountsProvider } from "./flagged-accounts";
|
||||||
|
import {
|
||||||
|
ProgramAccount,
|
||||||
|
ProgramAccountInfo,
|
||||||
|
ProgramDataAccount,
|
||||||
|
ProgramDataAccountInfo,
|
||||||
|
} from "validators/accounts/upgradeable-program";
|
||||||
export { useAccountHistory } from "./history";
|
export { useAccountHistory } from "./history";
|
||||||
|
|
||||||
export type StakeProgramData = {
|
export type StakeProgramData = {
|
||||||
|
@ -27,6 +33,12 @@ export type StakeProgramData = {
|
||||||
activation?: StakeActivationData;
|
activation?: StakeActivationData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UpgradeableProgramAccountData = {
|
||||||
|
program: "bpf-upgradeable-loader";
|
||||||
|
programData: ProgramDataAccountInfo;
|
||||||
|
programAccount: ProgramAccountInfo;
|
||||||
|
};
|
||||||
|
|
||||||
export type TokenProgramData = {
|
export type TokenProgramData = {
|
||||||
program: "spl-token";
|
program: "spl-token";
|
||||||
parsed: TokenAccount;
|
parsed: TokenAccount;
|
||||||
|
@ -53,6 +65,7 @@ export type ConfigProgramData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProgramData =
|
export type ProgramData =
|
||||||
|
| UpgradeableProgramAccountData
|
||||||
| StakeProgramData
|
| StakeProgramData
|
||||||
| TokenProgramData
|
| TokenProgramData
|
||||||
| VoteProgramData
|
| VoteProgramData
|
||||||
|
@ -131,6 +144,8 @@ async function fetchAccountInfo(
|
||||||
let space;
|
let space;
|
||||||
if (!("parsed" in result.data)) {
|
if (!("parsed" in result.data)) {
|
||||||
space = result.data.length;
|
space = result.data.length;
|
||||||
|
} else {
|
||||||
|
space = result.data.space;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data: ProgramData | undefined;
|
let data: ProgramData | undefined;
|
||||||
|
@ -138,6 +153,40 @@ async function fetchAccountInfo(
|
||||||
try {
|
try {
|
||||||
const info = coerce(result.data.parsed, ParsedInfo);
|
const info = coerce(result.data.parsed, ParsedInfo);
|
||||||
switch (result.data.program) {
|
switch (result.data.program) {
|
||||||
|
case "bpf-upgradeable-loader": {
|
||||||
|
let programAccount: ProgramAccountInfo;
|
||||||
|
let programData: ProgramDataAccountInfo;
|
||||||
|
|
||||||
|
if (info.type === "programData") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = coerce(info, ProgramAccount);
|
||||||
|
programAccount = parsed.info;
|
||||||
|
const result = (
|
||||||
|
await connection.getParsedAccountInfo(parsed.info.programData)
|
||||||
|
).value;
|
||||||
|
if (
|
||||||
|
result &&
|
||||||
|
"parsed" in result.data &&
|
||||||
|
result.data.program === "bpf-upgradeable-loader"
|
||||||
|
) {
|
||||||
|
const info = coerce(result.data.parsed, ParsedInfo);
|
||||||
|
programData = coerce(info, ProgramDataAccount).info;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`invalid program data account for program: ${pubkey.toBase58()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
program: result.data.program,
|
||||||
|
programData,
|
||||||
|
programAccount,
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "stake": {
|
case "stake": {
|
||||||
const parsed = coerce(info, StakeAccount);
|
const parsed = coerce(info, StakeAccount);
|
||||||
const isDelegated = parsed.type === "delegated";
|
const isDelegated = parsed.type === "delegated";
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-redeclare */
|
||||||
|
|
||||||
|
import { StructType, pick, number, nullable, literal } from "superstruct";
|
||||||
|
import { Pubkey } from "validators/pubkey";
|
||||||
|
|
||||||
|
export type ProgramAccountInfo = StructType<typeof ProgramAccountInfo>;
|
||||||
|
export const ProgramAccountInfo = pick({
|
||||||
|
programData: Pubkey,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ProgramAccount = StructType<typeof ProgramDataAccount>;
|
||||||
|
export const ProgramAccount = pick({
|
||||||
|
type: literal("program"),
|
||||||
|
info: ProgramAccountInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ProgramDataAccountInfo = StructType<typeof ProgramDataAccountInfo>;
|
||||||
|
export const ProgramDataAccountInfo = pick({
|
||||||
|
authority: nullable(Pubkey),
|
||||||
|
// don't care about data yet
|
||||||
|
slot: number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ProgramDataAccount = StructType<typeof ProgramDataAccount>;
|
||||||
|
export const ProgramDataAccount = pick({
|
||||||
|
type: literal("programData"),
|
||||||
|
info: ProgramDataAccountInfo,
|
||||||
|
});
|
Loading…
Reference in New Issue