From 48f3a9b1a7c5161e3df13946abf1fd810a8be8c3 Mon Sep 17 00:00:00 2001 From: ngundotra Date: Mon, 17 Jun 2024 07:21:04 -0400 Subject: [PATCH] refactor anchor program IDL page to load, even if it cannot be validated --- app/address/[address]/layout.tsx | 10 ++--- app/components/account/AnchorAccountCard.tsx | 2 +- app/components/account/AnchorProgramCard.tsx | 6 +-- .../transaction/InstructionsSection.tsx | 2 +- app/providers/anchor.tsx | 43 ++++++++++++------- app/utils/anchor.tsx | 2 +- 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/app/address/[address]/layout.tsx b/app/address/[address]/layout.tsx index 0a8f2930..16a91894 100644 --- a/app/address/[address]/layout.tsx +++ b/app/address/[address]/layout.tsx @@ -630,7 +630,7 @@ function getAnchorTabs(pubkey: PublicKey, account: Account) { tabComponents.push({ component: ( }> - + ), tab: anchorProgramTab, @@ -653,13 +653,13 @@ function getAnchorTabs(pubkey: PublicKey, account: Account) { return tabComponents; } -function AnchorProgramLink({ tab, address, pubkey }: { tab: Tab; address: string; pubkey: PublicKey }) { +function AnchorProgramIdlLink({ tab, address, pubkey }: { tab: Tab; address: string; pubkey: PublicKey }) { const { url } = useCluster(); - const anchorProgram = useAnchorProgram(pubkey.toString(), url); + const { idl } = useAnchorProgram(pubkey.toString(), url); const anchorProgramPath = useClusterPath({ pathname: `/address/${address}/${tab.path}` }); const selectedLayoutSegment = useSelectedLayoutSegment(); const isActive = selectedLayoutSegment === tab.path; - if (!anchorProgram) { + if (!idl) { return null; } @@ -674,7 +674,7 @@ function AnchorProgramLink({ tab, address, pubkey }: { tab: Tab; address: string function AccountDataLink({ address, tab, programId }: { address: string; tab: Tab; programId: PublicKey }) { const { url } = useCluster(); - const accountAnchorProgram = useAnchorProgram(programId.toString(), url); + const { program: accountAnchorProgram } = useAnchorProgram(programId.toString(), url); const accountDataPath = useClusterPath({ pathname: `/address/${address}/${tab.path}` }); const selectedLayoutSegment = useSelectedLayoutSegment(); const isActive = selectedLayoutSegment === tab.path; diff --git a/app/components/account/AnchorAccountCard.tsx b/app/components/account/AnchorAccountCard.tsx index 2970e448..6eac8345 100644 --- a/app/components/account/AnchorAccountCard.tsx +++ b/app/components/account/AnchorAccountCard.tsx @@ -10,7 +10,7 @@ import React, { useMemo } from 'react'; export function AnchorAccountCard({ account }: { account: Account }) { const { lamports } = account; const { url } = useCluster(); - const anchorProgram = useAnchorProgram(account.owner.toString(), url); + const { program: anchorProgram } = useAnchorProgram(account.owner.toString(), url); const rawData = account.data.raw; const programName = getAnchorProgramName(anchorProgram) || 'Unknown Program'; diff --git a/app/components/account/AnchorProgramCard.tsx b/app/components/account/AnchorProgramCard.tsx index 20eedefd..d8bdd24e 100644 --- a/app/components/account/AnchorProgramCard.tsx +++ b/app/components/account/AnchorProgramCard.tsx @@ -6,9 +6,9 @@ import ReactJson from 'react-json-view'; export function AnchorProgramCard({ programId }: { programId: string }) { const { url } = useCluster(); - const program = useAnchorProgram(programId, url); + const { idl } = useAnchorProgram(programId, url); - if (!program) { + if (!idl) { return null; } @@ -24,7 +24,7 @@ export function AnchorProgramCard({ programId }: { programId: string }) {
- +
diff --git a/app/components/transaction/InstructionsSection.tsx b/app/components/transaction/InstructionsSection.tsx index 680f69c0..88488c6d 100644 --- a/app/components/transaction/InstructionsSection.tsx +++ b/app/components/transaction/InstructionsSection.tsx @@ -162,7 +162,7 @@ function InstructionCard({ url: string; }) { const key = `${index}-${childIndex}`; - const anchorProgram = useAnchorProgram(ix.programId.toString(), url); + const { program: anchorProgram } = useAnchorProgram(ix.programId.toString(), url); if ('parsed' in ix) { const props = { diff --git a/app/providers/anchor.tsx b/app/providers/anchor.tsx index c8711e05..f74735a7 100644 --- a/app/providers/anchor.tsx +++ b/app/providers/anchor.tsx @@ -6,15 +6,13 @@ import pako from 'pako'; import { useEffect, useMemo } from 'react'; import { useAccountInfo, useFetchAccountInfo } from './accounts'; -import { useCluster } from './cluster'; const cachedAnchorProgramPromises: Record< string, - void | { __type: 'promise'; promise: Promise } | { __type: 'result'; result: Program | null } + void | { __type: 'promise'; promise: Promise } | { __type: 'result'; result: Idl | null } > = {}; -function useProgramElf(programAddress: string) { - const { url } = useCluster(); +function useIdlFromSolanaProgramBinary(programAddress: string): Idl | null { const fetchAccountInfo = useFetchAccountInfo(); const programInfo = useAccountInfo(programAddress); const programDataAddress: string | undefined = programInfo?.data?.data.parsed?.parsed.info['programData']; @@ -39,14 +37,13 @@ function useProgramElf(programAddress: string) { const raw = Buffer.from(programDataInfo.data.data.raw.slice(offset)); try { - const idl = parseIdlFromElf(raw); - return new Program(idl, programAddress, getProvider(url)); - } catch (_e) { + return parseIdlFromElf(raw); + } catch (e) { return null; } } return null; - }, [programDataInfo, programInfo, programAddress, url]); + }, [programDataInfo, programInfo]); return param; } @@ -82,16 +79,21 @@ function getProvider(url: string) { return new Provider(new Connection(url), new NodeWallet(Keypair.generate()), {}); } -function useAnchorIdlAccount(programAddress: string, url: string): Program | null { +function useIdlFromAnchorProgramSeed(programAddress: string, url: string): Idl | null { const key = `${programAddress}-${url}`; const cacheEntry = cachedAnchorProgramPromises[key]; if (cacheEntry === undefined) { - const promise = Program.at(programAddress, getProvider(url)) - .then(program => { + const programId = new PublicKey(programAddress); + const promise = Program.fetchIdl(programId, getProvider(url)) + .then(idl => { + if (!idl) { + throw new Error(`IDL not found for program: ${programAddress.toString()}`); + } + cachedAnchorProgramPromises[key] = { __type: 'result', - result: program, + result: idl, }; }) .catch(_ => { @@ -108,10 +110,19 @@ function useAnchorIdlAccount(programAddress: string, url: string): Program | nul return cacheEntry.result; } -export function useAnchorProgram(programAddress: string, url: string): Program | null { - const idlFromBinary = useProgramElf(programAddress); - const idlFromAccount = useAnchorIdlAccount(programAddress, url); - return idlFromBinary ?? idlFromAccount; +export function useAnchorProgram(programAddress: string, url: string): { program: Program | null; idl: Idl | null } { + const idlFromBinary = useIdlFromSolanaProgramBinary(programAddress); + const idlFromAnchorProgram = useIdlFromAnchorProgramSeed(programAddress, url); + const idl = idlFromBinary ?? idlFromAnchorProgram; + const program: Program | null = useMemo(() => { + if (!idl) return null; + try { + return new Program(idl, new PublicKey(programAddress), getProvider(url)); + } catch (e) { + return null; + } + }, [idl, programAddress, url]); + return { idl, program }; } export type AnchorAccount = { diff --git a/app/utils/anchor.tsx b/app/utils/anchor.tsx index ecb692fb..5d3e54f2 100644 --- a/app/utils/anchor.tsx +++ b/app/utils/anchor.tsx @@ -23,7 +23,7 @@ export function AnchorProgramName({ url: string; defaultName?: string; }) { - const program = useAnchorProgram(programId.toString(), url); + const { program } = useAnchorProgram(programId.toString(), url); const programName = getAnchorProgramName(program) || defaultName; return <>{programName}; }