import { asyncPipe, type ConnectionConfig, type RequiredBy, } from "@alchemy/aa-core"; import { type Plugin } from "@wagmi/cli"; import dedent from "dedent"; import { createPublicClient, getContract, http, type Chain } from "viem"; import type { PluginGenConfig } from "../plugindefs/types.js"; import { IPluginAbi } from "../src/msca/abis/IPlugin.js"; import { ContractAbiGenPhase } from "./phases/contract-abi-gen.js"; import { ContractAddressesGenPhase } from "./phases/contract-addresses-gen.js"; import { ExecutionAbiGenPhase } from "./phases/execution-abi-gen.js"; import { PluginActionsGenPhase } from "./phases/plugin-actions/index.js"; import { PluginGeneratorPhase } from "./phases/plugin-generator/index.js"; import type { Phase, PhaseInput } from "./types.js"; // Add more phases here if needed const phases: Phase[] = [ ContractAddressesGenPhase, PluginGeneratorPhase, PluginActionsGenPhase, ExecutionAbiGenPhase, ContractAbiGenPhase, ]; export function plugingen({ chain, connectionConfig, config, }: { chain: Chain; connectionConfig: ConnectionConfig; config: PluginGenConfig; }): RequiredBy { return { name: "ERC6900PluginGen: This file is auto-generated by plugingen", run: async ({ contracts }) => { // Setup plugin generator const imports: Map< string, { types: Set; members: Set; } > = new Map(); const addImport: PhaseInput["addImport"] = (moduleName, member) => { if (!imports.has(moduleName)) { imports.set(moduleName, { types: new Set(), members: new Set(), }); } const module = imports.get(moduleName)!; if (member.isType) { module.types.add(member.name); } else { module.members.add(member.name); } }; const content: string[] = []; const types: Map = new Map(); const addType: PhaseInput["addType"] = (typeName, typeDef, isPublic) => { if (types.has(typeName)) { throw new Error(`Type ${typeName} already exists`); } types.set(typeName, { definition: typeDef.replace(";", ""), isPublic: isPublic ?? false, }); }; if (contracts.length !== 1) { throw new Error( "plugingen should be used with only one Contract per plugin invocation" ); } const rpcUrl = connectionConfig.rpcUrl == null ? `${chain.rpcUrls.alchemy.http[0]}/${ connectionConfig.apiKey ?? process.env.API_KEY }` : connectionConfig.rpcUrl; const client = createPublicClient({ chain, transport: http(rpcUrl, { fetchOptions: { headers: { Authorization: `Bearer ${connectionConfig.jwt}`, }, }, }), }); const contract = contracts[0]; // This is done to clear out the ABI generated by wagmi contract.content = ""; if (contract.address == null) { throw new Error("contract must have at least one address"); } const address = typeof contract.address === "string" ? { [chain.id]: contract.address } : contract.address; if (!(chain.id in address)) { throw new Error( `contract address missing for the reference chain ${chain.id}` ); } const plugin = getContract({ address: address[chain.id], abi: IPluginAbi, client, }); const result = await asyncPipe(...phases)({ addImport, addType, content, config, contract, plugin, }); // Aggregate Result of phase const finalContent = dedent` ${Array.from(types.entries()) .map( ([name, type]) => `${type.isPublic ? "export" : ""} type ${name} = ${ type.definition };` ) .join("\n\n")} ${result.content.join("\n\n")} `; const finalImports = Array.from(imports.entries()) .map(([moduleName, { members, types }]) => { return dedent` import { ${Array.from(members.values()).join(",")} ${ members.size > 0 ? "," : "" } ${Array.from(types.values()) .map((x) => `type ${x}`) .join(",")} } from "${moduleName}"; `; }) .join("\n"); return { imports: finalImports, content: finalContent, }; }, }; }