Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for anchorV1 idls #27

Merged
merged 6 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/dull-dolls-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solanafm/explorer-kit": minor
"@solanafm/explorer-kit-idls": minor
---

feat: add support for anchorV1 idls (0.3.0)
5 changes: 3 additions & 2 deletions packages/explorerkit-idls/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@
"@vitest/coverage-v8": "^0.34.2",
"eslint": "^8.47.0",
"eslint-config-explorerkit": "workspace:*",
"vitest": "^0.34.2"
"vitest": "^1.5.3"
},
"dependencies": {
"@coral-xyz/anchor": "^0.27.0",
"@coral-xyz/anchor": "^0.29.0",
"@coral-xyz/anchor-new": "npm:@coral-xyz/anchor@^0.30.0",
"@solanafm/kinobi-lite": "^0.12.0",
"axios": "^1.3.3"
}
Expand Down
67 changes: 39 additions & 28 deletions packages/explorerkit-idls/src/idls/IdlRepository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Idl as AnchorIdl } from "@coral-xyz/anchor";
import { Idl as AnchorV1Idl } from "@coral-xyz/anchor-new";
import { Idl as ShankIdl } from "@solanafm/kinobi-lite";

import { CHAIN_ID } from "../constants";
Expand All @@ -8,8 +9,8 @@ import { getLocalIdl, IdlTypes } from "./LocalIdlRepository";

export interface IdlItem {
programId: string;
idl: AnchorIdl | ShankIdl | string;
idlType: "anchor" | "shank" | "kinobi";
idl: AnchorIdl | AnchorV1Idl | ShankIdl | string;
idlType: "anchor" | "anchorV1" | "shank" | "kinobi";
idlSlotVersion?: number;
chainId?: CHAIN_ID;
}
Expand All @@ -21,13 +22,15 @@ export type FetchProgramIdlOptions = {

/**
* Checks if the given IDL is an Anchor IDL.
* @param {AnchorIdl | ShankIdl | string} idl - The IDL to check.
* @param {AnchorIdl | AnchorV1Idl | ShankIdl | string} idl - The IDL to check.
* @returns {idl is AnchorIdl} - True if the IDL is an Anchor IDL, false otherwise.
*/
export const checkIdlIsAnchor = (idl: AnchorIdl | ShankIdl | string): idl is AnchorIdl => {
export const checkIdlIsAnchor = (idl: AnchorIdl | AnchorV1Idl | ShankIdl | string): idl is AnchorIdl => {
const anchorIdl = idl as AnchorIdl;

if (anchorIdl.metadata !== undefined && anchorIdl.metadata.origin === "shank") return false;
// you can differentiate IDLs from their `idl.metadata.spec` field (legacy/old if non-existent)
if (anchorIdl.metadata !== undefined && anchorIdl.metadata.spec) return false;

if (anchorIdl.instructions !== undefined) {
return anchorIdl.instructions.every((instruction: any) => {
Expand All @@ -42,21 +45,33 @@ export const checkIdlIsAnchor = (idl: AnchorIdl | ShankIdl | string): idl is Anc
return false;
};

/**
* Checks if the given IDL is an Anchor 0.3.0 IDL.
* @param {AnchorIdl | AnchorV1Idl | ShankIdl | string} idl - The IDL to check.
* @returns {idl is AnchorIdl} - True if the IDL is an Anchor IDL, false otherwise.
*/
export const checkIdlIsAnchorV1 = (idl: AnchorIdl | AnchorV1Idl | ShankIdl | string): idl is AnchorV1Idl => {
const anchorIdl = idl as AnchorV1Idl;
// you can differentiate IDLs from their `idl.metadata.spec` field (legacy/old if non-existent)
if (anchorIdl.metadata && anchorIdl.metadata.spec) return true;
else return false;
};

/**
* Checks if the given IDL is an Shank IDL.
* @param {AnchorIdl | ShankIdl | string} idl - The IDL to check.
* @param {AnchorIdl | AnchorV1Idl | ShankIdl | string} idl - The IDL to check.
* @returns {idl is ShankIdl} - True if the IDL is an Shank IDL, false otherwise.
*/
export const checkIdlIsShank = (idl: AnchorIdl | ShankIdl | string): idl is ShankIdl => {
export const checkIdlIsShank = (idl: AnchorIdl | AnchorV1Idl | ShankIdl | string): idl is ShankIdl => {
return (idl as ShankIdl).instructions !== undefined && (idl as ShankIdl).metadata !== undefined;
};

/**
* Checks if the given IDL is a string.
* @param {AnchorIdl | ShankIdl | string} idl - The IDL to check.
* @param {AnchorIdl | AnchorV1Idl | ShankIdl | string} idl - The IDL to check.
* @returns {idl is string} - True if the IDL is a string, false otherwise.
*/
export const checkIdlIsString = (idl: AnchorIdl | ShankIdl | string): idl is string => {
export const checkIdlIsString = (idl: AnchorIdl | AnchorV1Idl | ShankIdl | string): idl is string => {
return typeof idl === "string";
};

Expand All @@ -78,28 +93,19 @@ export const getProgramIdl = async (
): Promise<IdlItem | null> => {
const localIdl = getLocalIdl(programHash, options?.slotContext, idlRepoMap);
if (localIdl.idl) {
if (checkIdlIsAnchor(localIdl.idl)) {
return {
programId: programHash,
idl: localIdl.idl,
idlType: "anchor",
idlSlotVersion: localIdl?.slotDeployed,
chainId: options?.chainId,
};
} else if (checkIdlIsShank(localIdl.idl)) {
return {
programId: programHash,
idl: localIdl.idl,
idlType: "shank",
idlSlotVersion: localIdl?.slotDeployed,
chainId: options?.chainId,
};
} else if (checkIdlIsString(localIdl.idl)) {
let idlType: "anchor" | "anchorV1" | "shank" | "kinobi" | null = null;

if (checkIdlIsAnchor(localIdl.idl)) idlType = "anchor";
else if (checkIdlIsAnchorV1(localIdl.idl)) idlType = "anchorV1";
else if (checkIdlIsShank(localIdl.idl)) idlType = "shank";
else if (checkIdlIsString(localIdl.idl)) idlType = "kinobi";

if (idlType) {
return {
programId: programHash,
idl: localIdl.idl,
idlType: "kinobi",
idlSlotVersion: localIdl?.slotDeployed,
idlType: idlType,
idlSlotVersion: localIdl.slotDeployed,
chainId: options?.chainId,
};
}
Expand All @@ -118,10 +124,15 @@ export const getProgramIdl = async (
if (idlMetaResponse.idlInformation) {
if (idlMetaResponse.idlInformation.idl) {
if (idlMetaResponse.idlInformation.idlType === "anchor") {
// Since there is two version of anchor IDLs now, we will check if it's an IDL from version 30 and above
// If not, we will assume it's an IDL from version 29 and below
let idlType: "anchor" | "anchorV1" = "anchor";
if (checkIdlIsAnchorV1(idlMetaResponse.idlInformation.idl)) idlType = "anchorV1";

return {
programId: idlMetaResponse.programHash,
idl: idlMetaResponse.idlInformation.idl,
idlType: "anchor",
idlType: idlType,
idlSlotVersion: idlMetaResponse.idlInformation.slotDeployed,
chainId: options?.chainId,
};
Expand Down
3 changes: 2 additions & 1 deletion packages/explorerkit-idls/src/idls/LocalIdlRepository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Idl as AnchorIdl } from "@coral-xyz/anchor";
import { Idl as AnchorV1Idl } from "@coral-xyz/anchor-new";
import { Idl as ShankIdl } from "@solanafm/kinobi-lite";

import {
Expand Down Expand Up @@ -42,7 +43,7 @@ import {
TokenMetadataV1130IDL,
} from "./shank/others/token-metadata";

export type IdlTypes = ShankIdl | AnchorIdl | string;
export type IdlTypes = ShankIdl | AnchorIdl | AnchorV1Idl | string;

/**
* A map of program IDs to a map of slot numbers to IDL types.
Expand Down
6 changes: 6 additions & 0 deletions packages/explorerkit-idls/tests/idls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ describe("getProgramIdl", () => {
expect(idl).not.toBeNull();
expect(idl?.idlType).toBe("anchor");
});

it("should return a anchor 0.3.0 idl from the cloud repository", async () => {
const idl = await getProgramIdl("wns1gDLt8fgLcGhWi5MqAqgXpwEP1JftKE9eZnXS1HM");
expect(idl).not.toBeNull();
expect(idl?.idlType).toBe("anchorV1");
});
});

describe("getMultipleProgramIdls", () => {
Expand Down
5 changes: 3 additions & 2 deletions packages/explorerkit-translator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"@solanafm/explorer-kit-idls": "workspace:*",
"eslint-config-explorerkit": "workspace:*",
"tsconfig": "workspace:*",
"vitest": "^0.34.2"
"vitest": "^1.5.3"
},
"homepage": "https://github.com/solana-fm/explorer-kit#readme",
"repository": {
Expand All @@ -46,7 +46,8 @@
"directory": "packages/explorerkit-translator"
},
"dependencies": {
"@coral-xyz/anchor": "^0.28.1-beta.2",
"@coral-xyz/anchor": "^0.29.0",
"@coral-xyz/anchor-new": "npm:@coral-xyz/anchor@^0.30.0",
"@metaplex-foundation/umi": "^0.8.6",
"@metaplex-foundation/umi-serializers": "^0.8.5",
"@solana/spl-type-length-value": "^0.1.0",
Expand Down
40 changes: 39 additions & 1 deletion packages/explorerkit-translator/src/helpers/idl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
} from "@solanafm/kinobi-lite";

import { IdlAccountItem, IdlField, IdlType as AnchorIdlType, isIdlAccounts } from "../types/AnchorTypes";
import { checkIfIdlDefinedFieldsNamed, IdlDefinedFields, IdlType as AnchorV1IdlType } from "../types/NewAnchorTypes";

export type DataWithMappedType = {
type: ShankIdlType | AnchorIdlType;
type: ShankIdlType | AnchorIdlType | AnchorV1IdlType;
data: any;
};

Expand Down Expand Up @@ -160,3 +161,40 @@ export const mapDataTypeToName = (

return Object.keys(mappedDataType).length > 0 ? mappedDataType : data;
};

export const mapNewAnchorDataTypeToName = (data: Record<string, any>, idlFields?: IdlDefinedFields) => {
const dataKeys = Object.keys(data);
const mappedDataType: Record<string, DataWithMappedType> = {};
dataKeys.forEach((keyName) => {
// TODO: Still need to map the individual types to a "string"
// TODO: Key names have to be standarized with camel case since I did not standarize the field naming in the IDL
// TODO: All will be camel case like authorityAddress etc etc.
if (idlFields && idlFields.length > 0) {
if (checkIfIdlDefinedFieldsNamed(idlFields)) {
const filteredIdlField = idlFields.find((idlField) => idlField.name === keyName);
if (filteredIdlField) {
// defined type mapper
// TODO: Finish Defined Type Mapper in the future xD
// if (typeof filteredIdlField === "object") {
// // Since we already know filteredIdlField is a object type, we can safely cast it to Omit<IdlType, IdlTypeLeaf>
// const idlType: Omit<IdlType, IdlTypeLeaf> = filteredIdlField.type as Omit<IdlType, IdlTypeLeaf>;
// if ("defined" in idlType && idlTypes && idlTypes.length > 0) {
// const idlDefinedType = idlTypes.find((type) => type.name === idlType.defined);
// }
// }
mappedDataType[keyName] = {
type: filteredIdlField.type as AnchorV1IdlType,
data: data[keyName],
};
} else {
mappedDataType[keyName] = {
data: data[keyName],
type: "Unknown Type" as AnchorV1IdlType,
};
}
}
}
});

return Object.keys(mappedDataType).length > 0 ? mappedDataType : data;
};
65 changes: 65 additions & 0 deletions packages/explorerkit-translator/src/helpers/new-idl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { IdlInstructionAccountItem, isIdlAccounts } from "../types/NewAnchorTypes";
import { DataWithMappedType } from "./idl";

export const extractIdlIxAccountName: (IdlAccount?: IdlInstructionAccountItem) => string | string[] | undefined = (
idlAccount
) => {
if (idlAccount) {
if (isIdlAccounts(idlAccount)) {
const idlIxAccounts: string[] = [];

idlAccount.accounts.forEach((account) => {
let extractedName = extractIdlIxAccountName(account);
if (extractedName) {
if (Array.isArray(extractedName)) {
extractedName = extractIdlIxAccountName(account);
} else {
idlIxAccounts.push(idlAccount.name + "." + extractedName);
}
}
});

return idlIxAccounts;
} else {
return idlAccount.name;
}
}

return undefined;
};

export const mapNewAnchorAccountKeysToName = (
accountKeys?: string[],
idlIxAccounts?: IdlInstructionAccountItem[],
mapTypes?: boolean
): {
[name: string]: string | DataWithMappedType;
} => {
if (idlIxAccounts && accountKeys) {
const names: (string | string[])[] = [];

idlIxAccounts.forEach((idlIxAccount) => {
names.push(extractIdlIxAccountName(idlIxAccount) ?? "Unknown");
});

const flattenedArray = names.flat(5);
let translatedAccountKeysObj: {
[name: string]: string;
} = {};

accountKeys.forEach((accountKey, index) => {
const objectKey = flattenedArray[index] ?? ("Unknown" as string);
const object: {
[name: string]: string | DataWithMappedType;
} = {
[objectKey]: mapTypes ? { data: accountKey, type: "publicKey" } : accountKey,
};

translatedAccountKeysObj = Object.assign(translatedAccountKeysObj, object);
});

return translatedAccountKeysObj;
}

return {};
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { BorshAccountsCoder } from "@coral-xyz/anchor";
import { BorshAccountsCoder as NewBorshAccountsCoder } from "@coral-xyz/anchor-new";

import {
createAnchorAccountParser,
createAnchorV1AccountParser,
createShankAccountParser,
createShankConfigAccount,
createShankNameServiceAccount,
Expand All @@ -15,7 +17,7 @@ import { IdlItem } from "../types/IdlItem";
import { FMShankSerializer } from "../types/KinobiTreeGenerator";
import { ParserOutput } from "../types/Parsers";

export type AccountParsers = BorshAccountsCoder | Map<number | string, FMShankSerializer>;
export type AccountParsers = BorshAccountsCoder | NewBorshAccountsCoder | Map<number | string, FMShankSerializer>;

export interface AccountParserInterface {
accountLayouts: AccountParsers;
Expand All @@ -28,6 +30,9 @@ export const createAccountParser = (idlItem: IdlItem, programHash: string, accou
case "anchor":
return createAnchorAccountParser(idlItem);

case "anchorV1":
return createAnchorV1AccountParser(idlItem);

case "shank":
const localSysvars = [
"SysvarC1ock11111111111111111111111111111111",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { BorshInstructionCoder } from "@coral-xyz/anchor";
import { BorshInstructionCoder as NewBorshInstructionCoder } from "@coral-xyz/anchor-new";

import {
createAnchorInstructionParser,
createAnchorV1InstructionParser,
createShankInstructionParser,
createShankIxConfig,
createShankIxMemo,
Expand All @@ -14,7 +16,10 @@ import { IdlItem } from "../types/IdlItem";
import { FMShankSerializer } from "../types/KinobiTreeGenerator";
import { ParserOutput } from "../types/Parsers";

export type InstructionParsers = BorshInstructionCoder | Map<number | string, FMShankSerializer>;
export type InstructionParsers =
| BorshInstructionCoder
| NewBorshInstructionCoder
| Map<number | string, FMShankSerializer>;

export interface InstructionParserInterface {
instructionsLayout: InstructionParsers;
Expand All @@ -27,6 +32,9 @@ export const createInstructionParser = (idlItem: IdlItem, programHash: string) =
case "anchor":
return createAnchorInstructionParser(idlItem);

case "anchorV1":
return createAnchorV1InstructionParser(idlItem);

case "shank":
switch (programHash) {
case "Config1111111111111111111111111111111111111":
Expand Down
Loading
Loading