diff --git a/app/components/account/VerifiedBuildCard.tsx b/app/components/account/VerifiedBuildCard.tsx
index 1928d518..a752c251 100644
--- a/app/components/account/VerifiedBuildCard.tsx
+++ b/app/components/account/VerifiedBuildCard.tsx
@@ -6,11 +6,13 @@ import { ExternalLink } from 'react-feather';
import { OsecRegistryInfo, useVerifiedProgramRegistry } from '@/app/utils/verified-builds';
+import { Copyable } from '../common/Copyable';
import { LoadingCard } from '../common/LoadingCard';
export function VerifiedBuildCard({ data, pubkey }: { data: UpgradeableLoaderAccountData; pubkey: PublicKey }) {
const { data: registryInfo, isLoading } = useVerifiedProgramRegistry({
options: { suspense: true },
+ programAuthority: data.programData?.authority ? new PublicKey(data.programData.authority) : null,
programId: pubkey,
});
if (!data.programData) {
@@ -31,6 +33,18 @@ export function VerifiedBuildCard({ data, pubkey }: { data: UpgradeableLoaderAcc
Verified Build
Information provided by osec.io
+
+ Verified builds indicate that the onchain build was built from the source code that is publicly
+ available, but this does not imply a security audit. For more details, refer to the{' '}
+
+ Verified Builds Docs
+
+ .
+
{ROWS.filter(x => x.key in registryInfo).map((x, idx) => {
return (
@@ -50,6 +64,7 @@ enum DisplayType {
String,
URL,
Date,
+ LongString,
}
type TableRow = {
@@ -84,6 +99,11 @@ const ROWS: TableRow[] = [
key: 'last_verified_at',
type: DisplayType.Date,
},
+ {
+ display: 'Verify Command',
+ key: 'verify_command',
+ type: DisplayType.LongString,
+ },
{
display: 'Repository URL',
key: 'repo_url',
@@ -100,7 +120,32 @@ function RenderEntry({ value, type }: { value: OsecRegistryInfo[keyof OsecRegist
);
case DisplayType.String:
- return {value && (value as string).length > 1 ? value : '-'} | ;
+ return (
+
+ {value && (value as string).length > 1 ? value : '-'}
+ |
+ );
+ case DisplayType.LongString:
+ return (
+
+ {value && (value as string).length > 1 ? (
+ <>
+
+ {value}
+ >
+ ) : (
+ '-'
+ )}
+ |
+ );
case DisplayType.URL:
if (isValidLink(value as string)) {
return (
@@ -120,7 +165,11 @@ function RenderEntry({ value, type }: { value: OsecRegistryInfo[keyof OsecRegist
);
case DisplayType.Date:
- return {value && (value as string).length > 1 ? new Date(value as string).toUTCString() : '-'} | ;
+ return (
+
+ {value && (value as string).length > 1 ? new Date(value as string).toUTCString() : '-'}
+ |
+ );
default:
break;
}
diff --git a/app/components/common/VerifiedProgramBadge.tsx b/app/components/common/VerifiedProgramBadge.tsx
index f683bfae..89ab2799 100644
--- a/app/components/common/VerifiedProgramBadge.tsx
+++ b/app/components/common/VerifiedProgramBadge.tsx
@@ -12,7 +12,7 @@ export function VerifiedProgramBadge({
programData: ProgramDataAccountInfo;
pubkey: PublicKey;
}) {
- const { isLoading, data: registryInfo } = useVerifiedProgramRegistry({ programId: pubkey });
+ const { isLoading, data: registryInfo } = useVerifiedProgramRegistry({ programAuthority: programData.authority ? new PublicKey(programData.authority) : null, programId: pubkey });
const verifiedBuildTabPath = useClusterPath({ pathname: `/address/${pubkey.toBase58()}/verified-build` });
const hash = hashProgramData(programData);
diff --git a/app/utils/verified-builds.tsx b/app/utils/verified-builds.tsx
index 1eb84958..b8ba4ada 100644
--- a/app/utils/verified-builds.tsx
+++ b/app/utils/verified-builds.tsx
@@ -1,10 +1,14 @@
import { sha256 } from '@noble/hashes/sha256';
-import { PublicKey } from '@solana/web3.js';
+import { Connection, PublicKey } from '@solana/web3.js';
import useSWRImmutable from 'swr/immutable';
+import { useAnchorProgram } from '../providers/anchor';
+import { useCluster } from '../providers/cluster';
import { ProgramDataAccountInfo } from '../validators/accounts/upgradeable-program';
+import { Cluster } from './cluster';
const OSEC_REGISTRY_URL = 'https://verify.osec.io';
+const VERIFY_PROGRAM_ID = 'verifycLy8mB96wd9wqq3WDXQwM4oU6r42Th37Db9fC';
export type OsecRegistryInfo = {
is_verified: boolean;
@@ -13,29 +17,111 @@ export type OsecRegistryInfo = {
executable_hash: string;
last_verified_at: string | null;
repo_url: string;
-};
-
-export type CheckedOsecRegistryInfo = {
- explorer_hash: string;
+ verify_command: string;
};
export function useVerifiedProgramRegistry({
programId,
+ programAuthority,
options,
}: {
programId: PublicKey;
+ programAuthority: PublicKey | null;
options?: { suspense: boolean };
}) {
- const { data, error, isLoading } = useSWRImmutable(
+ const { url: clusterUrl, cluster: cluster } = useCluster();
+ const connection = new Connection(clusterUrl);
+
+ const {
+ data: registryData,
+ error: registryError,
+ isLoading: isRegistryLoading,
+ } = useSWRImmutable(
`${programId.toBase58()}`,
async (programId: string) => {
- return fetch(`${OSEC_REGISTRY_URL}/status/${programId}`).then(response => response.json());
+ const response = await fetch(`${OSEC_REGISTRY_URL}/status/${programId}`);
+ return response.json();
+ },
+ { suspense: options?.suspense }
+ );
+
+ const { program: accountAnchorProgram } = useAnchorProgram(VERIFY_PROGRAM_ID, connection.rpcEndpoint);
+
+ // Fetch the PDA derived from the program upgrade authority
+ // TODO: Add getting verifier pubkey from the security.txt as second option once implemented
+ const {
+ data: pdaData,
+ error: pdaError,
+ isLoading: isPdaLoading,
+ } = useSWRImmutable(
+ programAuthority && accountAnchorProgram ? `pda-${programId.toBase58()}` : null,
+ async () => {
+ const [pda] = PublicKey.findProgramAddressSync(
+ [Buffer.from('otter_verify'), programAuthority!.toBuffer(), programId.toBuffer()],
+ new PublicKey(VERIFY_PROGRAM_ID)
+ );
+ const pdaAccountInfo = await connection.getAccountInfo(pda);
+ if (!pdaAccountInfo || !pdaAccountInfo.data) {
+ console.log('PDA account info not found');
+ return null;
+ }
+ return accountAnchorProgram?.coder.accounts.decode('buildParams', pdaAccountInfo.data);
},
{ suspense: options?.suspense }
);
- return { data: error ? null : (data as OsecRegistryInfo), isLoading };
+
+ const isLoading = isRegistryLoading || isPdaLoading;
+
+ if (registryError || pdaError) {
+ return { data: null, error: registryError || pdaError, isLoading };
+ }
+
+ // Create command from the args of the verify PDA
+ if (registryData && pdaData && !isLoading) {
+ const verifiedData = registryData as OsecRegistryInfo;
+ verifiedData.verify_command = `solana-verify verify-from-repo -um --program-id ${programId.toBase58()} ${
+ pdaData.gitUrl
+ } --commit-hash ${pdaData.commit}`;
+
+ // Add additional args if available, for example mount-path and --library-name
+ if (pdaData.args && pdaData.args.length > 0) {
+ const filteredArgs = [];
+
+ for (let i = 0; i < pdaData.args.length; i++) {
+ const arg = pdaData.args[i];
+
+ if (arg === '-b' || arg === '--base-image') {
+ i++; // Also skip the parameter
+ continue;
+ }
+ filteredArgs.push(arg);
+ }
+
+ if (filteredArgs.length > 0) {
+ const argsString = filteredArgs.join(' ');
+ verifiedData.verify_command += ` ${argsString}`;
+ }
+ }
+
+ return { data: verifiedData, isLoading };
+ }
+ if (registryData && pdaData == null && !isLoading) {
+ const verifiedData = registryData as OsecRegistryInfo;
+
+ verifiedData.verify_command = isMainnet(cluster)
+ ? 'Program does not have a verify PDA uploaded.'
+ : 'Verify command only available on mainnet.';
+ return { data: verifiedData, isLoading };
+ }
+
+ return { data: null, isLoading };
+}
+
+function isMainnet(currentCluster: Cluster): boolean {
+ return currentCluster == Cluster.MainnetBeta;
}
+// Helper function to hash program data
export function hashProgramData(programData: ProgramDataAccountInfo): string {
const buffer = Buffer.from(programData.data[0], 'base64');
// Truncate null bytes at the end of the buffer