@solana-developers/helpers
Version:
Solana helper functions
311 lines • 9.63 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertLegacyIdl = convertLegacyIdl;
exports.getIdlSpecType = getIdlSpecType;
exports.formatIdl = formatIdl;
const sha256_1 = require("@noble/hashes/sha256");
// Remove the dynamic import
function getSnakeCase(str) {
return str
.replace(/([A-Z])/g, "_$1")
.toLowerCase()
.replace(/^_/, "");
}
function convertLegacyIdl(legacyIdl, programAddress) {
const address = programAddress ?? legacyIdl.metadata?.address;
if (!address) {
throw new Error("Program id missing in `idl.metadata.address` field");
}
return {
accounts: (legacyIdl.accounts || []).map(convertAccount),
address: address,
constants: (legacyIdl.constants || []).map(convertConst),
errors: legacyIdl.errors?.map(convertErrorCode) || [],
events: legacyIdl.events?.map(convertEvent) || [],
instructions: legacyIdl.instructions.map(convertInstruction),
metadata: {
name: legacyIdl.name,
version: legacyIdl.version,
},
types: [
...(legacyIdl.types || []).map(convertTypeDef),
...(legacyIdl.accounts || []).map(convertTypeDef),
...(legacyIdl.events || []).map(convertEventToTypeDef),
],
};
}
function traverseType(type, refs) {
if (typeof type === "string") {
// skip
}
else if ("vec" in type) {
traverseType(type.vec, refs);
}
else if ("option" in type) {
traverseType(type.option, refs);
}
else if ("defined" in type) {
refs.add(type.defined.name);
}
else if ("array" in type) {
traverseType(type.array[0], refs);
}
else if ("generic" in type) {
refs.add(type.generic);
}
else if ("coption" in type) {
traverseType(type.coption, refs);
}
}
function traverseIdlFields(fields, refs) {
fields.forEach((field) => typeof field === "string"
? traverseType(field, refs)
: typeof field === "object" && "type" in field
? traverseType(field.type, refs)
: traverseType(field, refs));
}
function traverseTypeDef(type, refs) {
switch (type.kind) {
case "struct":
traverseIdlFields(type.fields ?? [], refs);
return;
case "enum":
type.variants.forEach((variant) => traverseIdlFields(variant.fields ?? [], refs));
return;
case "type":
traverseType(type.alias, refs);
return;
}
}
function getTypeReferences(idl) {
const refs = new Set();
idl.constants?.forEach((constant) => traverseType(constant.type, refs));
idl.accounts?.forEach((account) => refs.add(account.name));
idl.instructions?.forEach((instruction) => instruction.args.forEach((arg) => traverseType(arg.type, refs)));
idl.events?.forEach((event) => refs.add(event.name));
// Build up recursive type references in breadth-first manner.
// Very inefficient since we traverse same types multiple times.
// But it works. Open to contributions that do proper graph traversal
let prevSize = refs.size;
let sizeDiff = 1;
while (sizeDiff > 0) {
for (const idlType of idl.types ?? []) {
if (refs.has(idlType.name)) {
traverseTypeDef(idlType.type, refs);
}
}
sizeDiff = refs.size - prevSize;
prevSize = refs.size;
}
return refs;
}
// Remove types that are not used in definition of instructions, accounts, events, or constants
function removeUnusedTypes(idl) {
const usedElsewhere = getTypeReferences(idl);
return {
...idl,
types: (idl.types ?? []).filter((type) => usedElsewhere.has(type.name)),
};
}
function getDisc(prefix, name) {
const hash = (0, sha256_1.sha256)(`${prefix}:${name}`);
return Array.from(hash.slice(0, 8));
}
function convertInstruction(instruction) {
const name = getSnakeCase(instruction.name);
return {
accounts: instruction.accounts.map(convertInstructionAccount),
args: instruction.args.map(convertField),
discriminator: getDisc("global", name),
name,
returns: instruction.returns ? convertType(instruction.returns) : undefined,
};
}
function convertAccount(account) {
return {
discriminator: getDisc("account", account.name),
name: account.name,
};
}
function convertTypeDef(typeDef) {
return {
name: typeDef.name,
type: convertTypeDefTy(typeDef.type),
};
}
function convertTypeDefTy(type) {
switch (type.kind) {
case "struct":
return {
fields: type.fields.map(convertField),
kind: "struct",
};
case "enum":
return {
kind: "enum",
variants: type.variants.map(convertEnumVariant),
};
case "alias":
return {
alias: convertType(type.value),
kind: "type",
};
}
}
function convertField(field) {
return {
name: getSnakeCase(field.name),
type: convertType(field.type),
};
}
function convertEnumVariant(variant) {
return {
fields: variant.fields ? convertEnumFields(variant.fields) : undefined,
name: variant.name,
};
}
function convertEnumFields(fields) {
if (Array.isArray(fields) &&
fields.length > 0 &&
typeof fields[0] === "object" &&
"type" in fields[0]) {
return fields.map(convertField);
}
else {
return fields.map((type) => convertType(type));
}
}
function convertEvent(event) {
return {
discriminator: getDisc("event", event.name),
name: event.name,
};
}
function convertErrorCode(error) {
return {
code: error.code,
msg: error.msg,
name: error.name,
};
}
function convertConst(constant) {
return {
name: constant.name,
type: convertType(constant.type),
value: constant.value,
};
}
function convertInstructionAccount(account) {
if ("accounts" in account) {
return convertInstructionAccounts(account);
}
else {
return {
docs: account.docs || [],
name: getSnakeCase(account.name),
optional: account.isOptional || false,
pda: account.pda ? convertPda(account.pda) : undefined,
relations: account.relations || [],
signer: account.isSigner || false,
writable: account.isMut || false,
};
}
}
function convertInstructionAccounts(accounts) {
return {
accounts: accounts.accounts.map(convertInstructionAccount),
name: getSnakeCase(accounts.name),
};
}
function convertPda(pda) {
return {
...(pda.programId ? { programId: convertSeed(pda.programId) } : {}),
seeds: pda.seeds.map(convertSeed),
};
}
function convertSeed(seed) {
switch (seed.kind) {
case "const":
return { kind: "const", type: convertType(seed.type), value: seed.value };
case "arg":
return { kind: "arg", path: seed.path, type: convertType(seed.type) };
case "account":
return {
...(seed.account ? { account: seed.account } : {}),
kind: "account",
path: seed.path,
type: convertType(seed.type),
};
}
}
function convertEventToTypeDef(event) {
return {
name: event.name,
type: {
fields: event.fields.map((field) => ({
name: getSnakeCase(field.name),
type: convertType(field.type),
})),
kind: "struct",
},
};
}
function convertType(type) {
if (typeof type === "string") {
return type === "publicKey" ? "pubkey" : type;
}
else if ("vec" in type) {
return { vec: convertType(type.vec) };
}
else if ("option" in type) {
return { option: convertType(type.option) };
}
else if ("defined" in type) {
return { defined: { generics: [], name: type.defined } };
}
else if ("array" in type) {
return { array: [convertType(type.array[0]), type.array[1]] };
}
else if ("generic" in type) {
return type;
}
else if ("definedWithTypeArgs" in type) {
return {
defined: {
generics: type.definedWithTypeArgs.args.map(convertDefinedTypeArg),
name: type.definedWithTypeArgs.name,
},
};
}
throw new Error(`Unsupported type: ${JSON.stringify(type)}`);
}
function convertDefinedTypeArg(arg) {
if ("generic" in arg) {
return { generic: arg.generic };
}
else if ("value" in arg) {
return { value: arg.value };
}
else if ("type" in arg) {
return { type: convertType(arg.type) };
}
throw new Error(`Unsupported defined type arg: ${JSON.stringify(arg)}`);
}
function getIdlSpecType(idl) {
return idl.metadata?.spec ?? "legacy";
}
function formatIdl(idl, programAddress) {
const spec = getIdlSpecType(idl);
switch (spec) {
case "0.1.0":
return idl;
case "legacy":
if (!programAddress) {
throw new Error("Program address is required for legacy IDL");
}
return removeUnusedTypes(convertLegacyIdl(idl, programAddress));
default:
throw new Error(`IDL spec not supported: ${spec}`);
}
}
//# sourceMappingURL=convertLegacyIdl.js.map
;