diff --git a/.changeset/fast-flies-decide.md b/.changeset/fast-flies-decide.md new file mode 100644 index 0000000..2de9b33 --- /dev/null +++ b/.changeset/fast-flies-decide.md @@ -0,0 +1,5 @@ +--- +"@solanafm/explorer-kit": minor +--- + +feat: 1.2.0 diff --git a/packages/explorerkit-idls/tests/idls.test.ts b/packages/explorerkit-idls/tests/idls.test.ts index b848fac..2fa4b83 100644 --- a/packages/explorerkit-idls/tests/idls.test.ts +++ b/packages/explorerkit-idls/tests/idls.test.ts @@ -4,9 +4,14 @@ import { addIdlToMap, getProgramIdl, IdlRepository } from "../src"; import { getMultipleProgramIdls } from "../src/idls/IdlRepository"; describe("getProgramIdl", () => { - it("should return null if the program does not have an idl", async () => { - const idl = await getProgramIdl("BRGovFm72qvoE8MwWP1ipgefSuUvp5y2Vs7PoRM74Sth"); - expect(idl).toBeNull(); + it("should return an error now if the program does not have an idl", async () => { + try { + await getProgramIdl("BRGovFm72qvoE8MwWP1ipgefSuUvp5y2Vs7PoRM74Sth"); + // If we reach this line, the test should fail because no error was thrown + expect.fail("Expected an error but none was thrown"); + } catch (error) { + expect(error.message).toBe("Error fetching IDL for BRGovFm72qvoE8MwWP1ipgefSuUvp5y2Vs7PoRM74Sth"); + } }); it("should return an idl from the cloud repository if the program has an idl", async () => { @@ -87,8 +92,13 @@ describe("getProgramIdl", () => { }); it("should returns undefined when program ID is not found in the IDL repository map", async () => { - const idl = await getProgramIdl("randomProgramID"); - expect(idl).toBeNull(); + try { + await getProgramIdl("randomProgramID"); + // If we reach this line, the test should fail because no error was thrown + expect.fail("Expected an error but none was thrown"); + } catch (error) { + expect(error.message).toBe("Error fetching IDL for randomProgramID"); + } }); it("should returns the latest IDL when no slot is specified", async () => { diff --git a/packages/explorerkit-translator/src/interfaces/EventParserInterface.ts b/packages/explorerkit-translator/src/interfaces/EventParserInterface.ts index eea7ef0..2c5f3ee 100644 --- a/packages/explorerkit-translator/src/interfaces/EventParserInterface.ts +++ b/packages/explorerkit-translator/src/interfaces/EventParserInterface.ts @@ -1,6 +1,7 @@ import { BorshEventCoder, BorshInstructionCoder } from "@coral-xyz/anchor"; +import { BorshEventCoder as V1BorshEventCoder } from "@coral-xyz/anchor-new"; -import { createAnchorEventParser, createShankEventParser } from "../parsers/v2/event"; +import { createAnchorEventParser, createAnchorV1EventParser, createShankEventParser } from "../parsers/v2/event"; import { createBubblegumEventParser } from "../parsers/v2/event/anchor/bubblegum"; import { createSPLCompEventParser } from "../parsers/v2/event/anchor/spl-compression"; import { createTCompEventParser } from "../parsers/v2/event/anchor/tcomp"; @@ -9,7 +10,11 @@ import { IdlItem } from "../types/IdlItem"; import { FMShankSerializer } from "../types/KinobiTreeGenerator"; import { ParserOutput } from "../types/Parsers"; -export type EventParsers = BorshInstructionCoder | BorshEventCoder | Map; +export type EventParsers = + | BorshInstructionCoder + | BorshEventCoder + | V1BorshEventCoder + | Map; export interface EventParserInterface { eventsLayout: EventParsers; @@ -33,6 +38,9 @@ export const createEventParser = (idlItem: IdlItem, programHash: string) => { return createAnchorEventParser(idlItem); } + case "anchorV1": + return createAnchorV1EventParser(idlItem); + case "kinobi": switch (programHash) { case "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY": diff --git a/packages/explorerkit-translator/src/parsers/v2/event/anchor-v1.ts b/packages/explorerkit-translator/src/parsers/v2/event/anchor-v1.ts new file mode 100644 index 0000000..7a58b20 --- /dev/null +++ b/packages/explorerkit-translator/src/parsers/v2/event/anchor-v1.ts @@ -0,0 +1,56 @@ +import { BorshEventCoder, Idl as AnchorIdl } from "@coral-xyz/anchor-new"; +import { IdlTypeDefTyStruct } from "@coral-xyz/anchor-new/dist/cjs/idl"; +import { convertBNToNumberInObject } from "@solanafm/utils"; + +import { mapNewAnchorDataTypeToName } from "../../../helpers/idl"; +import { EventParserInterface } from "../../../interfaces"; +import { IdlItem } from "../../../types/IdlItem"; +import { ParserOutput, ParserType } from "../../../types/Parsers"; + +export const createAnchorV1EventParser: (idlItem: IdlItem) => EventParserInterface = (idlItem: IdlItem) => { + const idl = idlItem.idl as AnchorIdl; + const eventsLayout = new BorshEventCoder(idl); + + const parseEvents = (eventData: string, mapTypes?: boolean): ParserOutput => { + try { + if (eventsLayout) { + const decodedEventData = eventsLayout.decode(eventData); + + if (decodedEventData) { + const filteredIdlEvent = idl.events?.filter((event) => event.name === decodedEventData.name) ?? []; + + if (mapTypes) { + if (filteredIdlEvent && filteredIdlEvent[0]) { + const eventName = filteredIdlEvent[0].name; + const dataFields = (idl.types?.filter((type) => type.name === eventName)[0]?.type as IdlTypeDefTyStruct) + .fields; + decodedEventData.data = mapNewAnchorDataTypeToName(decodedEventData.data, dataFields); + } + } + + decodedEventData.data = convertBNToNumberInObject(decodedEventData.data); + + return { + name: decodedEventData.name, + data: convertBNToNumberInObject(decodedEventData.data), + type: ParserType.EVENT, + }; + } + } + + return null; + } catch (error) { + throw new Error(`Error parsing event data - ${eventData}`, { + cause: { + decoderError: error, + programId: idlItem.programId, + }, + }); + } + }; + + return { + eventsLayout, + parseEvents, + }; +}; diff --git a/packages/explorerkit-translator/src/parsers/v2/event/index.ts b/packages/explorerkit-translator/src/parsers/v2/event/index.ts index d144310..9cf181c 100644 --- a/packages/explorerkit-translator/src/parsers/v2/event/index.ts +++ b/packages/explorerkit-translator/src/parsers/v2/event/index.ts @@ -1,2 +1,3 @@ export { createAnchorEventParser } from "./anchor"; +export { createAnchorV1EventParser } from "./anchor-v1"; export { createShankEventParser } from "./shank"; diff --git a/packages/explorerkit-translator/tests/v2/account.test.ts b/packages/explorerkit-translator/tests/v2/account.test.ts index 2662cac..2015f6e 100644 --- a/packages/explorerkit-translator/tests/v2/account.test.ts +++ b/packages/explorerkit-translator/tests/v2/account.test.ts @@ -48,7 +48,7 @@ describe("parseAnchorAccount", () => { expect(decodedData).not.toBeNull(); expect(decodedData?.type).toBe("account"); expect(decodedData?.name).toBe("Pool"); - expect(decodedData?.data["admin"].type).toBe("publicKey"); + expect(decodedData?.data["aVault"].type).toBe("publicKey"); } } }); diff --git a/packages/explorerkit-translator/tests/v2/event.test.ts b/packages/explorerkit-translator/tests/v2/event.test.ts index 0c9f670..6d594ef 100644 --- a/packages/explorerkit-translator/tests/v2/event.test.ts +++ b/packages/explorerkit-translator/tests/v2/event.test.ts @@ -113,3 +113,55 @@ describe("createShankEventParser", () => { } }); }); + +describe("createNewAnchorParser", () => { + it("should construct an anchor 0.3.0 event parser for a given valid IDL", async () => { + const programId = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"; + const idlItem = await getProgramIdl(programId); + + if (idlItem) { + const parser = new SolanaFMParser(idlItem, programId); + const eventParser = parser.createParser(ParserType.EVENT); + + expect(eventParser).not.toBeNull(); + expect(checkIfEventParser(eventParser)).toBe(true); + } + }); + + it("should construct an anchor 0.3.0 event parser for a given valid IDL and parses the event data", async () => { + const programId = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"; + const eventData = + "QMbN6CYIceKpKlqLTylZUoQlUKqT/VuVtazmqOuSDJOULkNpDCDscwabiFf+q4GE+2h/Y0YYwDXaxDncGus7VZig8AAAAAABG+xuAQAAAABuE+O/mSsnCUc4smt9N6Da8XiGrtyfTQYxWwx20dhGYdoM2iAAAAAA"; + const idlItem = await getProgramIdl(programId); + + if (idlItem) { + const parser = new SolanaFMParser(idlItem, programId); + const eventParser = parser.createParser(ParserType.EVENT); + if (eventParser && checkIfEventParser(eventParser)) { + const decodedData = eventParser.parseEvents(eventData); + expect(decodedData).not.toBeNull(); + expect(decodedData?.type).toBe("event"); + expect(decodedData?.name).toBe("SwapEvent"); + } + } + }); + + it("should construct an anchor 0.3.0 event parser for a given valid IDL, parses the event data and properly map the data type with the given IDL", async () => { + const programId = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"; + const eventData = + "QMbN6CYIceKpKlqLTylZUoQlUKqT/VuVtazmqOuSDJOULkNpDCDscwabiFf+q4GE+2h/Y0YYwDXaxDncGus7VZig8AAAAAABG+xuAQAAAABuE+O/mSsnCUc4smt9N6Da8XiGrtyfTQYxWwx20dhGYdoM2iAAAAAA"; + const idlItem = await getProgramIdl(programId); + + if (idlItem) { + const parser = new SolanaFMParser(idlItem, programId); + const eventParser = parser.createParser(ParserType.EVENT); + if (eventParser && checkIfEventParser(eventParser)) { + const decodedData = eventParser.parseEvents(eventData, true); + expect(decodedData).not.toBeNull(); + expect(decodedData?.type).toBe("event"); + expect(decodedData?.name).toBe("SwapEvent"); + expect(decodedData?.data["input_amount"].type).toBe("u64"); + } + } + }); +});