diff --git a/explorer/src/components/account/UpgradeableLoaderAccountSection.tsx b/explorer/src/components/account/UpgradeableLoaderAccountSection.tsx index 4e5ec1ca4d..37e1ef4a36 100644 --- a/explorer/src/components/account/UpgradeableLoaderAccountSection.tsx +++ b/explorer/src/components/account/UpgradeableLoaderAccountSection.tsx @@ -15,6 +15,9 @@ import { useCluster } from "providers/cluster"; import { ErrorCard } from "components/common/ErrorCard"; import { UnknownAccountCard } from "components/account/UnknownAccountCard"; import { Downloadable } from "components/common/Downloadable"; +import { CheckingBadge, VerifiedBadge } from "components/common/VerifiedBadge"; +import { InfoTooltip } from "components/common/InfoTooltip"; +import { useVerifiableBuilds } from "utils/program-verification"; export function UpgradeableLoaderAccountSection({ account, @@ -72,6 +75,7 @@ export function UpgradeableProgramSection({ const refresh = useFetchAccountInfo(); const { cluster } = useCluster(); const label = addressLabel(account.pubkey.toBase58(), cluster); + const { loading, verifiableBuilds } = useVerifiableBuilds(account.pubkey); return (
@@ -122,6 +126,22 @@ export function UpgradeableProgramSection({ {programData.authority !== null ? "Yes" : "No"} + + + + + + {loading ? ( + + ) : ( + <> + {verifiableBuilds.map((b) => ( + + ))} + + )} + + Last Deployed Slot @@ -141,6 +161,14 @@ export function UpgradeableProgramSection({ ); } +function LastVerifiedBuildLabel() { + return ( + + Verifiable Build Status + + ); +} + export function UpgradeableProgramDataSection({ account, programData, diff --git a/explorer/src/components/common/VerifiedBadge.tsx b/explorer/src/components/common/VerifiedBadge.tsx new file mode 100644 index 0000000000..331ebfe63a --- /dev/null +++ b/explorer/src/components/common/VerifiedBadge.tsx @@ -0,0 +1,38 @@ +import { VerifiableBuild } from "utils/program-verification"; + +export function VerifiedBadge({ + verifiableBuild, +}: { + verifiableBuild: VerifiableBuild; +}) { + if (verifiableBuild && verifiableBuild.verified_slot) { + return ( +

+ + {verifiableBuild.label}: Verified + +

+ ); + } else { + return ( +

+ + {verifiableBuild.label}: Unverified + +

+ ); + } +} + +export function CheckingBadge() { + return ( +

+ Checking +

+ ); +} diff --git a/explorer/src/utils/program-verification.tsx b/explorer/src/utils/program-verification.tsx new file mode 100644 index 0000000000..a4e0fe90af --- /dev/null +++ b/explorer/src/utils/program-verification.tsx @@ -0,0 +1,103 @@ +import { PublicKey } from "@solana/web3.js"; +import { useEffect, useState } from "react"; + +export type VerifiableBuild = + | { + label: string; + id: number; + verified_slot: number; + url: string; + } + | { + label: string; + verified_slot: null; + }; + +export function useVerifiableBuilds(programAddress: PublicKey) { + const { loading: loadingAnchor, verifiableBuild: verifiedBuildAnchor } = + useAnchorVerifiableBuild(programAddress); + + return { + loading: loadingAnchor, + verifiableBuilds: [verifiedBuildAnchor], + }; +} + +// ANCHOR + +const defaultAnchorBuild = { + label: "Anchor", + verified_slot: null, +}; + +export function useAnchorVerifiableBuild(programAddress: PublicKey) { + const [loading, setLoading] = useState(true); + const [verifiableBuild, setVerifiableBuild] = + useState(defaultAnchorBuild); + + useEffect(() => { + setLoading(true); + getAnchorVerifiableBuild(programAddress) + .then(setVerifiableBuild) + .catch((error) => { + console.log(error); + setVerifiableBuild(defaultAnchorBuild); + }) + .finally(() => setLoading(false)); + }, [programAddress, setVerifiableBuild, setLoading]); + + return { + loading, + verifiableBuild, + }; +} + +export interface AnchorBuild { + aborted: boolean; + address: string; + created_at: string; + updated_at: string; + descriptor: string[]; + docker: string; + id: number; + name: string; + sha256: string; + upgrade_authority: string; + verified: string; + verified_slot: number; + state: string; +} + +/** + * Returns a verified build from the anchor registry. null if no such + * verified build exists, e.g., if the program has been upgraded since the + * last verified build. + */ +export async function getAnchorVerifiableBuild( + programId: PublicKey, + limit: number = 5 +): Promise { + const url = `https://anchor.projectserum.com/api/v0/program/${programId.toString()}/latest?limit=${limit}`; + const latestBuildsResp = await fetch(url); + + // Filter out all non successful builds. + const latestBuilds = (await latestBuildsResp.json()).filter( + (b: AnchorBuild) => + !b.aborted && b.state === "Built" && b.verified === "Verified" + ) as AnchorBuild[]; + + if (latestBuilds.length === 0) { + return defaultAnchorBuild; + } + + // Get the latest build. + const { verified_slot, id } = latestBuilds[0]; + return { + ...defaultAnchorBuild, + verified_slot, + id, + url: `https://anchor.projectserum.com/build/${id}`, + }; +} + +// END ANCHOR