Skip to content

Commit

Permalink
fix: properly map out the optional and non optional account keys from…
Browse files Browse the repository at this point in the history
… an instruction (#36)

* feat: add support to respect 'isOptional' type in idl account

* docs(changeset): feat: add support for optional account keys

* fix: properly map out the optional account keys without skipping them
  • Loading branch information
doodoo-aihc authored May 29, 2024
1 parent f6a9f16 commit b1ca99f
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 28 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
107 changes: 80 additions & 27 deletions packages/explorerkit-translator/src/helpers/idl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,49 +40,102 @@ export const mapAccountKeysToName = (
}
);
});
const flattenedArray = names.flat(5);
let nonOptionalAccountKeys = 0;
let optionalAccountKeys = 0;

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

let optionalKeyCounter = 1;
const flattenedArray = names.flat(5);
const nonOptionalFlattenArray = flattenedArray.filter((accountKey) => !accountKey.optional);

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

accountKeys.forEach((accountKey, index) => {
const objectKey = flattenedArray[index] ?? {
name: "Unknown",
};
// If there are lesser accountKeys than optionalAccountKeys, we will just return all the accountKeys as non-optional
// If not, we will treat it as there is a mix of optional and non-optional accounts
if (nonOptionalFlattenArray.length >= accountKeys.length) {
accountKeys.forEach((accountKey, index) => {
let objectKey = nonOptionalFlattenArray[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.name]: mapTypes ? { data: accountKey, type: "publicKey" } : accountKey,
};

const object: {
[name: string]: string | DataWithMappedType;
} = {
[objectKey.name]: mapTypes ? { data: accountKey, type: "publicKey" } : accountKey,
};
translatedAccountKeysObj = Object.assign(translatedAccountKeysObj, object);
});
} else {
let namesIndex = 0;
let optionalKeyCounter = 1;

translatedAccountKeysObj = Object.assign(translatedAccountKeysObj, object);
});
accountKeys.forEach((accountKey) => {
let objectKey = flattenedArray[namesIndex] ?? {
name: "Unknown",
};

if (objectKey.optional) {
// If the accountKeys are lesser than the totalKeys including optional ones, we will treat the current accountKey as a non-optional key and go to the next one
if (accountKeys.length < nonOptionalFlattenArray.length + optionalKeyCounter) {
const nonOptionalIdlAccount = getNonOptionalIdlAccount(namesIndex, flattenedArray);

if (nonOptionalIdlAccount) {
objectKey = nonOptionalIdlAccount.idlAccount ?? {
name: "Unknown",
};
namesIndex = nonOptionalIdlAccount.index;
}
}

optionalKeyCounter++;
}

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

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

return translatedAccountKeysObj;
}

return {};
};

export const getNonOptionalIdlAccount: (
nameIndex: number,
idlAccounts: IdlAccountName[]
) =>
| {
idlAccount: IdlAccountName | undefined;
index: number;
}
| undefined = (nameIndex: number, idlAccounts: IdlAccountName[]) => {
let index = nameIndex;
let idlAccount = idlAccounts[index] ?? {
name: "Unknown",
};

if (idlAccount) {
if (idlAccount?.optional) {
// Recursively find the next non-optional account
index++;
return getNonOptionalIdlAccount(index, idlAccounts);
}

return {
idlAccount,
index,
};
}

return undefined;
};

export const extractIdlIxAccountName: (IdlAccount?: IdlAccountItem) => IdlAccountName | IdlAccountName[] | undefined = (
idlAccount
) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/explorerkit-translator/tests/v2/instruction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ describe("createShankParserWithMappingSupport", () => {
});
});

describe("createAnchorParserWithOptionalAccountKeysSupport", () => {
describe("createShankParserWithOptionalAccountKeysSupport", () => {
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);
Expand Down Expand Up @@ -263,6 +263,7 @@ describe("createAnchorParserWithOptionalAccountKeysSupport", () => {
expect(decodedData).not.toBeNull();
expect(decodedData?.name).toBe("swapBaseIn");
expect(decodedData?.data.ammTargetOrders).toBeUndefined();
expect(decodedData?.data.poolCoinTokenAccount).toBe("FBba2XsQVhkoQDMfbNLVmo7dsvssdT39BMzVc2eFfE21");
}
}
});
Expand Down

0 comments on commit b1ca99f

Please sign in to comment.