Skip to content

Commit

Permalink
Add support for anchor 0.30.1 (#374)
Browse files Browse the repository at this point in the history
- ports `anchor idl convert` to TS
- uses new anchor 0.30.1 idl type everywhere
- removes old anchor package

Still doesn't work for State Compression transactions:
-
http://localhost:3000/tx/TYgsD3iqLCK2KMeSPp7cyAboGhDZqgSCQUGwAdDWXKe1QeLWmBS7wpLBsp1Ma9z6rTYHRqvAwbnZA8qtAnwHCyD

Works for new Anchor IDLs:
-
http://localhost:3000/tx/52kfChUoUQLR5XkCm2Bx9c6oayeRZm9tno62ugHkPcBieojoGWZwEa1NpXaQFga83HtxTWu5ARqi544D65HTm4tB

Works for some old Anchor IDLs:
-
http://localhost:3000/tx/2iofwLXfEDjEqvGZNMuCzZF8TEGyvfogCVf4oDnAATLpGkUbSfPe7ZBuqNoNXzaV2Xb33AT7PRYd9HztdbTvF3GR?cluster=devnet

---------

Co-authored-by: Noah Gundotra <[email protected]>
  • Loading branch information
ngundotra and Noah Gundotra authored Sep 11, 2024
1 parent 8e2c735 commit 61083ea
Show file tree
Hide file tree
Showing 7 changed files with 720 additions and 269 deletions.
16 changes: 8 additions & 8 deletions app/components/account/AnchorAccountCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ErrorCard } from '@components/common/ErrorCard';
import { BorshAccountsCoder } from '@project-serum/anchor';
import { IdlTypeDef } from '@project-serum/anchor/dist/cjs/idl';
import { BorshAccountsCoder } from '@coral-xyz/anchor';
import { IdlTypeDef } from '@coral-xyz/anchor/dist/cjs/idl';
import { Account } from '@providers/accounts';
import { useAnchorProgram } from '@providers/anchor';
import { useCluster } from '@providers/cluster';
Expand All @@ -19,13 +19,13 @@ export function AnchorAccountCard({ account }: { account: Account }) {
let accountDef: IdlTypeDef | undefined = undefined;
if (anchorProgram && rawData) {
const coder = new BorshAccountsCoder(anchorProgram.idl);
const accountDefTmp = anchorProgram.idl.accounts?.find((accountType: any) =>
(rawData as Buffer).slice(0, 8).equals(BorshAccountsCoder.accountDiscriminator(accountType.name))
const account = anchorProgram.idl.accounts?.find((accountType: any) =>
(rawData as Buffer).slice(0, 8).equals(coder.accountDiscriminator(accountType.name))
);
if (accountDefTmp) {
accountDef = accountDefTmp;
if (account) {
accountDef = anchorProgram.idl.types?.find((type: any) => type.name === account.name);
try {
decodedAccountData = coder.decode(accountDef.name, rawData);
decodedAccountData = coder.decode(account.name, rawData);
} catch (err) {
console.log(err);
}
Expand All @@ -51,7 +51,7 @@ export function AnchorAccountCard({ account }: { account: Account }) {
<div className="row align-items-center">
<div className="col">
<h3 className="card-header-title">
{programName}: {accountDef.name}
{programName}: {accountDef.name.charAt(0).toUpperCase() + accountDef.name.slice(1)}
</h3>
</div>
</div>
Expand Down
14 changes: 10 additions & 4 deletions app/components/instruction/AnchorDetailsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Address } from '@components/common/Address';
import { BorshEventCoder, BorshInstructionCoder, Idl, Instruction, Program } from '@project-serum/anchor';
import { IdlEvent, IdlInstruction } from '@project-serum/anchor/dist/cjs/idl';
import { BorshEventCoder, BorshInstructionCoder, Idl, Instruction, Program } from '@coral-xyz/anchor';
import { IdlEvent, IdlField, IdlInstruction, IdlTypeDefTyStruct } from '@coral-xyz/anchor/dist/cjs/idl';
import { SignatureResult, TransactionInstruction } from '@solana/web3.js';
import {
getAnchorAccountsFromInstruction,
Expand Down Expand Up @@ -57,8 +57,14 @@ function AnchorDetails({ ix, anchorProgram }: { ix: TransactionInstruction; anch
ixDef => ixDef.name === decodedIxData?.name
) as IdlEvent;

// Remap the event definition to an instruction definition
ixDef = { ...ixEventDef, accounts: [], args: ixEventDef.fields };
const ixEventFields = anchorProgram.idl.types?.find((type: any) => type.name === ixEventDef.name);

// Remap the event definition to an instruction definition by force casting to struct fields
ixDef = {
...ixEventDef,
accounts: [],
args: ((ixEventFields?.type as IdlTypeDefTyStruct).fields as IdlField[]) ?? [],
};

// Self-CPI instructions have 1 account called the eventAuthority
// https://github.com/coral-xyz/anchor/blob/04985802587c693091f836e0083e4412148c0ca6/lang/attribute/event/src/lib.rs#L165
Expand Down
11 changes: 7 additions & 4 deletions app/providers/anchor.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { NodeWallet } from '@metaplex/js';
import { Idl, Program, Provider } from '@project-serum/anchor';
import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor';
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import * as elfy from 'elfy';
import pako from 'pako';
import { useEffect, useMemo } from 'react';

import { formatIdl } from '../utils/convertLegacyIdl';
import { useAccountInfo, useFetchAccountInfo } from './accounts';

const cachedAnchorProgramPromises: Record<
Expand Down Expand Up @@ -76,7 +77,7 @@ function parseIdlFromElf(elfBuffer: any) {
}

function getProvider(url: string) {
return new Provider(new Connection(url), new NodeWallet(Keypair.generate()), {});
return new AnchorProvider(new Connection(url), new NodeWallet(Keypair.generate()), {});
}

function useIdlFromAnchorProgramSeed(programAddress: string, url: string): Idl | null {
Expand Down Expand Up @@ -117,8 +118,10 @@ export function useAnchorProgram(programAddress: string, url: string): { program
const program: Program<Idl> | null = useMemo(() => {
if (!idl) return null;
try {
return new Program(idl, new PublicKey(programAddress), getProvider(url));
const program = new Program(formatIdl(idl, programAddress), getProvider(url));
return program;
} catch (e) {
console.error('Error creating anchor program', e, { idl });
return null;
}
}, [idl, programAddress, url]);
Expand Down
49 changes: 34 additions & 15 deletions app/utils/anchor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Address } from '@components/common/Address';
import { BorshInstructionCoder, Idl, Program } from '@project-serum/anchor';
import { IdlField, IdlInstruction, IdlType, IdlTypeDef } from '@project-serum/anchor/dist/cjs/idl';
import { BorshInstructionCoder, Idl, Program } from '@coral-xyz/anchor';
import { IdlDefinedFields } from '@coral-xyz/anchor/dist/cjs/idl';
import { IdlField, IdlInstruction, IdlType, IdlTypeDef } from '@coral-xyz/anchor/dist/cjs/idl';
import { useAnchorProgram } from '@providers/anchor';
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
import { Cluster } from '@utils/cluster';
Expand All @@ -18,7 +19,7 @@ export function instructionIsSelfCPI(ixData: Buffer): boolean {
}

export function getAnchorProgramName(program: Program | null): string | undefined {
return program && 'name' in program.idl ? snakeToTitleCase(program.idl.name) : undefined;
return program && 'name' in program.idl.metadata ? snakeToTitleCase(program.idl.metadata.name) : undefined;
}

export function AnchorProgramName({
Expand Down Expand Up @@ -112,17 +113,29 @@ export function mapIxArgsToRows(ixArgs: any, ixType: IdlInstruction, idl: Idl) {
});
}

function getFieldDef(fields: IdlDefinedFields | undefined, key: string, index: number): IdlType | undefined {
if (!fields || fields.length === 0) {
return undefined;
}
if (typeof fields[0] === 'string') {
return (fields as IdlType[]).find((ixDefArg, argIndex) => argIndex === index);
} else {
return (fields as IdlField[]).find(ixDefArg => ixDefArg.name === key)?.type;
}
}

export function mapAccountToRows(accountData: any, accountType: IdlTypeDef, idl: Idl) {
return Object.entries(accountData).map(([key, value]) => {
return Object.entries(accountData).map(([key, value], index) => {
try {
if (accountType.type.kind !== 'struct') {
throw Error(`Account ${accountType.name} is of type ${accountType.type.kind} (expected: 'struct')`);
}
const fieldDef = accountType.type.fields.find(ixDefArg => ixDefArg.name === key);

const fieldDef: IdlType | undefined = getFieldDef(accountType.type.fields, key, index);
if (!fieldDef) {
throw Error(`Could not find expected ${key} field on account type definition for ${accountType.name}`);
}
return mapField(key, value as any, fieldDef.type, idl);
return mapField(key, value as any, fieldDef, idl);
} catch (error: any) {
console.log('Error while displaying IDL-based account data', error);
return (
Expand Down Expand Up @@ -172,7 +185,9 @@ function mapField(key: string, value: any, type: IdlType, idl: Idl, keySuffix?:
type === 'i64' ||
type === 'f64' ||
type === 'u128' ||
type === 'i128'
type === 'i128' ||
type === 'u256' ||
type === 'i256'
) {
return (
<SimpleRow
Expand All @@ -197,7 +212,7 @@ function mapField(key: string, value: any, type: IdlType, idl: Idl, keySuffix?:
<div>{value.toString()}</div>
</SimpleRow>
);
} else if (type === 'publicKey') {
} else if (type === 'pubkey') {
return (
<SimpleRow
key={keySuffix ? `${key}-${keySuffix}` : key}
Expand All @@ -210,7 +225,7 @@ function mapField(key: string, value: any, type: IdlType, idl: Idl, keySuffix?:
</SimpleRow>
);
} else if ('defined' in type) {
const fieldType = idl.types?.find(t => t.name === type.defined);
const fieldType = idl.types?.find(t => t.name === type.defined.name);
if (!fieldType) {
throw Error(`Could not type definition for ${type.defined} field in IDL`);
}
Expand All @@ -225,18 +240,18 @@ function mapField(key: string, value: any, type: IdlType, idl: Idl, keySuffix?:
>
<Fragment key={keySuffix ? `${key}-${keySuffix}` : key}>
{Object.entries(value).map(([innerKey, innerValue]: [string, any]) => {
const innerFieldType = structFields.find(t => t.name === innerKey);
const innerFieldType = getFieldDef(structFields, innerKey, 0);
if (!innerFieldType) {
throw Error(
`Could not type definition for ${innerKey} field in user-defined struct ${fieldType.name}`
);
}
return mapField(innerKey, innerValue, innerFieldType?.type, idl, key, nestingLevel + 1);
return mapField(innerKey, innerValue, innerFieldType, idl, key, nestingLevel + 1);
})}
</Fragment>
</ExpandableRow>
);
} else {
} else if (fieldType.type.kind === 'enum') {
const enumVariantName = Object.keys(value)[0];
const variant = fieldType.type.variants.find(
val => val.name.toLocaleLowerCase() === enumVariantName.toLocaleLowerCase()
Expand Down Expand Up @@ -274,13 +289,15 @@ function mapField(key: string, value: any, type: IdlType, idl: Idl, keySuffix?:
<SimpleRow
key={keySuffix ? `${key}-${keySuffix}` : key}
rawKey={key}
type={{ enum: type.defined }}
type={{ enum: type.defined.name }}
keySuffix={keySuffix}
nestingLevel={nestingLevel}
>
{camelToTitleCase(enumVariantName)}
</SimpleRow>
);
} else {
throw Error('Unsupported type kind: ' + fieldType.type.kind);
}
} else if ('option' in type) {
if (value === null) {
Expand Down Expand Up @@ -445,14 +462,16 @@ function typeDisplayName(
case 'f64':
case 'u128':
case 'i128':
case 'i256':
case 'u256':
case 'bytes':
case 'string':
return type.toString();
case 'publicKey':
case 'pubkey':
return 'PublicKey';
default:
if ('enum' in type) return `${type.enum} (enum)`;
if ('defined' in type) return type.defined;
if ('defined' in type) return type.defined.name;
if ('option' in type) return `${typeDisplayName(type.option)} (optional)`;
if ('vec' in type) return `${typeDisplayName(type.vec)}[]`;
if ('array' in type) return `${typeDisplayName(type.array[0])}[${type.array[1]}]`;
Expand Down
Loading

0 comments on commit 61083ea

Please sign in to comment.