import pkg from '../../package.json' with { type: 'json' }; import type { AbiExporterConfig, AbiExporterConfigEntry } from '../types.js'; import { clearAbi } from './clear_abi.js'; import { FormatTypes, Interface } from '@ethersproject/abi'; import { writeUtf8File } from '@nomicfoundation/hardhat-utils/fs'; import { readArtifacts } from '@solidstate/hardhat-solidstate-utils/filter'; import { HardhatPluginError } from 'hardhat/plugins'; import type { HookContext } from 'hardhat/types/hooks'; import path from 'node:path'; const TS_TAG = `// this file was automatically generated by ${pkg.name} - do not modify`; export const exportAbi = async ( context: HookContext, configEntries: AbiExporterConfig, ) => { await clearAbi(context, configEntries); await Promise.all( configEntries.map((entry) => exportAbiGroup(context, entry)), ); }; const exportAbiGroup = async ( context: HookContext, config: AbiExporterConfigEntry, ) => { const outputDirectory = path.resolve(context.config.paths.root, config.path); // validate that the output directory is not the Hardhat root directory to prevent accidental file deletion if (outputDirectory === context.config.paths.root) { throw new HardhatPluginError( pkg.name, 'resolved path must not be root directory', ); } // get contract artifacts, filtered according to configuration const artifacts = await readArtifacts(context, config); // filter out 0-length ABIs and generate export file contents const outputData = artifacts .filter(({ abi }) => abi.length) .map((artifact) => { const { sourceName, contractName } = artifact; let { abi } = artifact; abi = abi.filter((element, index, array) => config.filter(element, index, array, sourceName, contractName), ); // format ABI using ethers presets const formatType = FormatTypes[config.format] ?? 'full'; let contents = new Interface(abi).format(formatType); if (formatType === 'json') { contents = JSON.parse(contents as string); } contents = JSON.stringify(contents, null, config.spacing); if (config.format === 'typescript') { contents = `${TS_TAG}\nexport default ${contents} as const;`; } contents = `${contents}\n`; const extension = config.format === 'typescript' ? '.ts' : '.json'; const destination = path.resolve(outputDirectory, config.rename(sourceName, contractName)) + extension; return { destination, contents }; }); // check for filename clashes among exported files outputData.reduce( (acc: { [destination: string]: string }, { destination, contents }) => { const previousContents = acc[destination]; if (previousContents && previousContents !== contents) { throw new HardhatPluginError( pkg.name, `multiple distinct contracts share same output destination: ${destination}`, ); } acc[destination] = contents; return acc; }, {}, ); // write export files to disk await Promise.all( outputData.map(({ destination, contents }) => writeUtf8File(destination, contents), ), ); };