diff --git a/app/components/instruction/ed25519/Ed25519DetailsCard.tsx b/app/components/instruction/ed25519/Ed25519DetailsCard.tsx
index 0513215f1..81e005ebd 100644
--- a/app/components/instruction/ed25519/Ed25519DetailsCard.tsx
+++ b/app/components/instruction/ed25519/Ed25519DetailsCard.tsx
@@ -1,3 +1,6 @@
+import { Program } from '@coral-xyz/anchor';
+import { IdlType } from '@coral-xyz/anchor/dist/cjs/idl';
+import { sha256 } from '@noble/hashes/sha256';
import {
ParsedTransaction,
PartiallyDecodedInstruction,
@@ -6,7 +9,11 @@ import {
TransactionInstruction,
} from '@solana/web3.js';
import bs58 from 'bs58';
-import React from 'react';
+import React, { useMemo } from 'react';
+
+import { useAnchorProgram } from '@/app/providers/anchor';
+import { useCluster } from '@/app/providers/cluster';
+import { mapField } from '@/app/utils/anchor';
import { Address } from '../../common/Address';
import { Copyable } from '../../common/Copyable';
@@ -77,6 +84,178 @@ const extractData = (
}
};
+function decodeMessageFromAnchorProgram(
+ anchorProgram: Program,
+ message: Uint8Array
+): { name: string; data: any } | null {
+ const messageDisc = Buffer.from(message.slice(0, 8)).toString('hex');
+ const coder = anchorProgram.coder.types;
+ for (const [_, typeLayouts] of Object.entries(anchorProgram.coder.types)) {
+ for (const [name] of typeLayouts.entries()) {
+ try {
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
+ const disc = Buffer.from(sha256(`global:${capitalizedName}`).slice(0, 8)).toString('hex');
+
+ if (disc === messageDisc) {
+ const decoded = coder.decode(name, Buffer.from(message.slice(8)));
+ if (decoded) {
+ return { data: decoded, name };
+ }
+ }
+ } catch (e) {
+ console.log('Error decoding message with anchor program', e);
+ }
+ }
+ }
+ return null;
+}
+
+function SignatureDetails({
+ index,
+ offset,
+ signature,
+ pubkey,
+ message,
+ messageIx,
+}: {
+ index: number;
+ offset: Ed25519SignatureOffsets;
+ signature: Uint8Array | null;
+ pubkey: Uint8Array | null;
+ message: Uint8Array;
+ messageIx: PartiallyDecodedInstruction;
+}) {
+ const { url } = useCluster();
+ const anchorProgram = useAnchorProgram(messageIx.programId.toBase58(), url);
+
+ const decodedMessage = useMemo(() => {
+ if (!anchorProgram?.idl || !anchorProgram?.program) {
+ return null;
+ }
+ return decodeMessageFromAnchorProgram(anchorProgram.program, Buffer.from(message.toString(), 'hex'));
+ }, [anchorProgram, message]);
+
+ const messageRow = useMemo(() => {
+ if (!decodedMessage || !anchorProgram?.idl || !anchorProgram?.program) {
+ return (
+
+ | Message |
+
+
+ {Buffer.from(message).toString('base64')}
+
+ |
+
+ );
+ }
+
+ const name = decodedMessage.name;
+ const data = decodedMessage.data;
+
+ if (!name || !data) {
+ return null;
+ }
+
+ const type: IdlType = { defined: { name } };
+
+ return (
+
+
+ |
+ Message Payload
+ |
+
+ Payload Type
+ |
+
+ Value
+ |
+
+ {mapField(name, data, type, anchorProgram.program.idl, 'sigverify-message', 0)}
+
+ );
+ }, [decodedMessage, message, anchorProgram?.idl, anchorProgram?.program]);
+
+ return (
+
+
+ |
+ Signature #{index + 1}
+ |
+
+
+ | Signature Reference |
+
+ Instruction {offset.signatureInstructionIndex}, Offset {offset.signatureOffset}
+ |
+
+
+ | Signature |
+
+ {signature ? (
+
+ {Buffer.from(signature).toString('base64')}
+
+ ) : (
+ Invalid Reference
+ )}
+ |
+
+
+ | Public Key Reference |
+
+ Instruction {offset.publicKeyInstructionIndex}, Offset {offset.publicKeyOffset}
+ |
+
+
+ | Public Key |
+
+ {pubkey ? (
+
+ ) : (
+ Invalid Reference
+ )}
+ |
+
+
+ | Message Reference |
+
+ Instruction {offset.messageInstructionIndex}, Offset {offset.messageDataOffset}, Size{' '}
+ {offset.messageDataSize}
+ |
+
+
+ | Message Program |
+
+
+ |
+
+ {messageRow}
+
+ );
+}
+
export function Ed25519DetailsCard(props: DetailsProps) {
const { tx, ix, index, result, innerCards, childIndex } = props;
@@ -97,7 +276,6 @@ export function Ed25519DetailsCard(props: DetailsProps) {
-
{offsets.map((offset, index) => {
const signature = extractData(
tx,
@@ -109,100 +287,24 @@ export function Ed25519DetailsCard(props: DetailsProps) {
const pubkey = extractData(tx, offset.publicKeyInstructionIndex, ix.data, offset.publicKeyOffset, 32);
- const message = extractData(
- tx,
- offset.messageInstructionIndex,
- ix.data,
- offset.messageDataOffset,
- offset.messageDataSize
- );
+ const messageIx = tx.message.instructions[
+ offset.messageInstructionIndex
+ ] as PartiallyDecodedInstruction;
+
+ const message = bs58
+ .decode(messageIx.data)
+ .slice(offset.messageDataOffset, offset.messageDataOffset + offset.messageDataSize);
return (
-
-
- |
- Signature #{index + 1}
- |
-
-
- | Signature Reference |
-
- {offset.signatureInstructionIndex === ED25519_SELF_REFERENCE_INSTRUCTION_INDEX
- ? 'This instruction'
- : `Instruction ${offset.signatureInstructionIndex}`}
- {', '}
- Offset {offset.signatureOffset}
- |
-
-
- | Signature |
-
- {signature ? (
-
-
- {Buffer.from(signature).toString('base64')}
-
-
- ) : (
- 'Invalid reference'
- )}
- |
-
-
- | Public Key Reference |
-
- {offset.publicKeyInstructionIndex === ED25519_SELF_REFERENCE_INSTRUCTION_INDEX
- ? 'This instruction'
- : `Instruction ${offset.publicKeyInstructionIndex}`}
- {', '}
- Offset {offset.publicKeyOffset}
- |
-
-
- | Public Key |
-
- {pubkey ? (
-
- ) : (
- 'Invalid reference'
- )}
- |
-
-
- | Message Reference |
-
- {offset.messageInstructionIndex === ED25519_SELF_REFERENCE_INSTRUCTION_INDEX
- ? 'This instruction'
- : `Instruction ${offset.messageInstructionIndex}`}
- {', '}
- Offset {offset.messageDataOffset}, Size {offset.messageDataSize}
- |
-
-
- | Message |
-
- {message ? (
-
-
- {Buffer.from(message).toString('base64')}
-
-
- ) : (
- 'Invalid reference'
- )}
- |
-
-
+
);
})}
diff --git a/app/providers/anchor.tsx b/app/providers/anchor.tsx
index 1148fb10a..32bb9cbfe 100644
--- a/app/providers/anchor.tsx
+++ b/app/providers/anchor.tsx
@@ -4,15 +4,11 @@ import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import * as elfy from 'elfy';
import pako from 'pako';
import { useEffect, useMemo } from 'react';
+import useSWR from 'swr';
import { formatIdl } from '../utils/convertLegacyIdl';
import { useAccountInfo, useFetchAccountInfo } from './accounts';
-const cachedAnchorProgramPromises: Record<
- string,
- void | { __type: 'promise'; promise: Promise } | { __type: 'result'; result: Idl | null }
-> = {};
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function useIdlFromSolanaProgramBinary(programAddress: string): Idl | null {
const fetchAccountInfo = useFetchAccountInfo();
@@ -81,53 +77,46 @@ function getProvider(url: string) {
return new AnchorProvider(new Connection(url), new NodeWallet(Keypair.generate()), {});
}
-function useIdlFromAnchorProgramSeed(programAddress: string, url: string): Idl | null {
- const key = `${programAddress}-${url}`;
- const cacheEntry = cachedAnchorProgramPromises[key];
-
- if (cacheEntry === undefined) {
- 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: idl,
- };
- })
- .catch(_ => {
- cachedAnchorProgramPromises[key] = { __type: 'result', result: null };
- });
- cachedAnchorProgramPromises[key] = {
- __type: 'promise',
- promise,
- };
- throw promise;
- } else if (cacheEntry.__type === 'promise') {
- throw cacheEntry.promise;
+function getProgram(idl: Idl, programAddress: string, url: string) {
+ const provider = getProvider(url);
+
+ try {
+ try {
+ // Try using the uploaded IDL
+ return new Program(idl, provider);
+ } catch (e) {
+ // If raw IDL fails, try with formatted IDL
+ try {
+ const unprunedIdl = formatIdl(idl, programAddress, false);
+ return new Program(unprunedIdl, provider);
+ } catch (e) {
+ // Try again with types removed
+ const prunedIdl = formatIdl(idl, programAddress, true);
+ return new Program(prunedIdl, provider);
+ }
+ }
+ } catch (e) {
+ console.error('Error creating anchor program for', programAddress, e, { idl });
+ return null;
}
- return cacheEntry.result;
}
export function useAnchorProgram(programAddress: string, url: string): { program: Program | null; idl: Idl | null } {
- // TODO(ngundotra): Rewrite this to be more efficient
- // const idlFromBinary = useIdlFromSolanaProgramBinary(programAddress);
- const idlFromAnchorProgram = useIdlFromAnchorProgramSeed(programAddress, url);
- const idl = idlFromAnchorProgram;
- const program: Program | null = useMemo(() => {
- if (!idl) return null;
+ const { data } = useSWR([programAddress, url], async () => {
try {
- const program = new Program(formatIdl(idl, programAddress), getProvider(url));
- return program;
+ const programId = new PublicKey(programAddress);
+ const idl = await Program.fetchIdl(programId, getProvider(url));
+ if (!idl) {
+ throw new Error(`IDL not found for program: ${programAddress}`);
+ }
+ return { idl, program: getProgram(idl, programAddress, url) };
} catch (e) {
- console.error('Error creating anchor program for', programAddress, e, { idl });
+ console.error('Error fetching IDL:', e);
return null;
}
- }, [idl, programAddress, url]);
- return { idl, program };
+ });
+
+ return data ?? { idl: null, program: null };
}
export type AnchorAccount = {
diff --git a/app/utils/anchor.tsx b/app/utils/anchor.tsx
index 504f363a0..6ffacfd9a 100644
--- a/app/utils/anchor.tsx
+++ b/app/utils/anchor.tsx
@@ -155,7 +155,14 @@ export function mapAccountToRows(accountData: any, accountType: IdlTypeDef, idl:
});
}
-function mapField(key: string, value: any, type: IdlType, idl: Idl, keySuffix?: any, nestingLevel = 0): ReactNode {
+export function mapField(
+ key: string,
+ value: any,
+ type: IdlType,
+ idl: Idl,
+ keySuffix?: any,
+ nestingLevel = 0
+): ReactNode {
let itemKey = key;
if (/^-?\d+$/.test(keySuffix)) {
itemKey = `#${keySuffix}`;
diff --git a/app/utils/convertLegacyIdl.ts b/app/utils/convertLegacyIdl.ts
index b930b5dcd..ee688774a 100644
--- a/app/utils/convertLegacyIdl.ts
+++ b/app/utils/convertLegacyIdl.ts
@@ -192,8 +192,8 @@ function traverseIdlFields(fields: IdlDefinedFields, refs: Set) {
typeof field === 'string'
? traverseType(field, refs)
: typeof field === 'object' && 'type' in field
- ? traverseType(field.type, refs)
- : traverseType(field, refs)
+ ? traverseType(field.type, refs)
+ : traverseType(field, refs)
);
}
@@ -436,14 +436,19 @@ export function getIdlSpecType(idl: any): IdlSpec {
export type IdlSpec = '0.1.0' | 'legacy';
-export function formatIdl(idl: any, programAddress?: string): Idl {
+export function formatIdl(idl: any, programAddress?: string, removeTypes = true): Idl {
const spec = getIdlSpecType(idl);
switch (spec) {
case '0.1.0':
return idl as Idl;
- case 'legacy':
- return removeUnusedTypes(convertLegacyIdl(idl as LegacyIdl, programAddress));
+ case 'legacy': {
+ let converted = convertLegacyIdl(idl as LegacyIdl, programAddress);
+ if (removeTypes) {
+ converted = removeUnusedTypes(converted);
+ }
+ return converted;
+ }
default:
throw new Error(`IDL spec not supported: ${spec}`);
}