Skip to content

Commit

Permalink
feat: add support for optional account keys (#33)
Browse files Browse the repository at this point in the history
* feat: add support to respect 'isOptional' type in idl account

* docs(changeset): feat: add support for optional account keys
  • Loading branch information
doodoo-aihc authored May 29, 2024
1 parent 3d2d3ac commit d833b78
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 48 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-cameras-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solanafm/explorer-kit": patch
---

feat: add support for optional account keys
94 changes: 46 additions & 48 deletions packages/explorerkit-translator/src/helpers/idl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export type DataWithMappedType = {
data: any;
};

export type IdlAccountName = {
name: string;
optional?: boolean;
};

export const isDataWithMappedType = (data: any): data is DataWithMappedType => {
return data.type && data.data;
};
Expand All @@ -26,23 +31,47 @@ export const mapAccountKeysToName = (
[name: string]: string | DataWithMappedType;
} => {
if (idlIxAccounts && accountKeys) {
const names: (string | string[])[] = [];
const names: (IdlAccountName | IdlAccountName[])[] = [];

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

const flattenedArray = names.flat(5);
let nonOptionalAccountKeys = 0;
let optionalAccountKeys = 0;

flattenedArray.forEach((accountKey) => {
if (accountKey.optional) optionalAccountKeys++;
else nonOptionalAccountKeys++;
});

let optionalKeyCounter = 1;

let translatedAccountKeysObj: {
[name: string]: string;
} = {};

accountKeys.forEach((accountKey, index) => {
const objectKey = flattenedArray[index] ?? ("Unknown" as string);
const objectKey = flattenedArray[index] ?? {
name: "Unknown",
};

// If the account is optional, we will check if the accountKeys length is more than the idlIxAccounts length without optional accounts
if (objectKey.optional) {
optionalKeyCounter++;
// If the optional key counter is more than the optional account keys, we will return and not add the name
if (optionalKeyCounter > optionalAccountKeys) return;
if (accountKeys.length > nonOptionalAccountKeys + optionalKeyCounter) return;
}

const object: {
[name: string]: string | DataWithMappedType;
} = {
[objectKey]: mapTypes ? { data: accountKey, type: "publicKey" } : accountKey,
[objectKey.name]: mapTypes ? { data: accountKey, type: "publicKey" } : accountKey,
};

translatedAccountKeysObj = Object.assign(translatedAccountKeysObj, object);
Expand All @@ -54,64 +83,33 @@ export const mapAccountKeysToName = (
return {};
};

export const mapAccountKeysToNameV2 = (
accountKeys?: string[],
idlIxAccounts?: IdlAccountItem[] | IdlInstructionAccount[],
mapTypes?: boolean
): {
[name: string]: string | DataWithMappedType | (string | DataWithMappedType)[];
} => {
if (!idlIxAccounts || !accountKeys) return {};

const names: string[] = idlIxAccounts
.map((idlIxAccount) => extractIdlIxAccountName(idlIxAccount) ?? "Unknown")
.flat();
let translatedAccountKeysObj: {
[name: string]: string | DataWithMappedType | (string | DataWithMappedType)[];
} = {};

accountKeys.forEach((accountKey, index) => {
const objectKey = names[index] ?? "Unknown";
const newEntry = mapTypes ? ({ data: accountKey, type: "publicKey" } as DataWithMappedType) : accountKey;

// If objectKey already exists, we append the new accountKey to the existing array or object
if (objectKey in translatedAccountKeysObj) {
const existingEntry = translatedAccountKeysObj[objectKey];

if (Array.isArray(existingEntry)) {
(existingEntry as (string | DataWithMappedType)[]).push(newEntry);
} else {
translatedAccountKeysObj[objectKey] = [existingEntry ?? "", newEntry];
}
}
// If the objectKey doesn't exist in translatedAccountKeysObj, we just assign it like before
else {
translatedAccountKeysObj[objectKey] = newEntry;
}
});

return translatedAccountKeysObj;
};

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

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

return idlIxAccounts;
} else {
return idlAccount.name;
return {
name: idlAccount.name,
optional: idlAccount.isOptional,
};
}
}

Expand Down
38 changes: 38 additions & 0 deletions packages/explorerkit-translator/tests/v2/instruction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,44 @@ describe("createShankParserWithMappingSupport", () => {
});
});

describe("createAnchorParserWithOptionalAccountKeysSupport", () => {
it("should construct an shank instruction parser for a given valid IDL and map the correct number of account keys with 1 optional account keys", async () => {
const programId = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const idlItem = await getProgramIdl(programId);
const instructionData = "5xcHHuFELpsnhDZW9QA7Vjm";

if (idlItem) {
const parser = new SolanaFMParser(idlItem, programId);
const instructionParser = parser.createParser(ParserType.INSTRUCTION);

if (instructionParser && checkIfInstructionParser(instructionParser)) {
const decodedData = instructionParser.parseInstructions(instructionData, [
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"DSUvc5qf5LJHHV5e2tD184ixotSnCnwj7i4jJa4Xsrmt",
"5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1",
"38p42yoKFWgxw2LCbB96wAKa2LwAxiBArY3fc3eA9yWv",
"FBba2XsQVhkoQDMfbNLVmo7dsvssdT39BMzVc2eFfE21",
"GuXKCb9ibwSeRSdSYqaCL3dcxBZ7jJcj6Y7rDwzmUBu9",
"srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX",
"58sHobBa2KmyE3EKxCpgxn5KGuzudmGHsgqYgrfciyzd",
"EhboNaGqMiw2rqvh7uN6qEsfxpNoCJQBzPbiiSBNCXmW",
"CnsZUH9AUNFqkEE6oExCevcHkMamPJhPFvND6mLT4ikb",
"33L8Zi2bnkUX99NJeFmSEwYF6DaNknPXTB5EdsVBfb6e",
"Cr278bTbmgyvTbnt1jqCTsPdqUaB9WN3hbGMjRFontmM",
"EYXT9U31MHRsRSBJ8zafg9paUwYLmWZfJHbSwrJ8mNVb",
"B7af1ADihMVF1xE2243G2ggBkLRFTFgHT8hHbjWzqj1F",
"CTyFguG69kwYrzk24P3UuBvY1rR5atu9kf2S6XEwAU8X",
"F8K1h7dk5UGCv4u6w1b6u6WqtjyJfqHcQvN4DT8eYpM8",
"MfDuWeqSHEqTFVYZ7LoexgAK9dxk7cy4DFJWjWMGVWa",
]);
expect(decodedData).not.toBeNull();
expect(decodedData?.name).toBe("swapBaseIn");
expect(decodedData?.data.ammTargetOrders).toBeUndefined();
}
}
});
});

describe("createShankPhoenixParserWithMappingSupport", () => {
it("should construct an shank phoenix instruction parser for a given valid IDL and parse the instruction data with types properly mapped according to the idl", async () => {
const programId = "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY";
Expand Down

0 comments on commit d833b78

Please sign in to comment.