UNPKG

@solana-developers/helpers

Version:
311 lines 9.63 kB
"use strict"; 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