UNPKG

hardhat-abi-exporter

Version:

Export Ethereum smart contract ABIs on compilation

92 lines (91 loc) 4.01 kB
import pkg from '../package.json'; import { FormatTypes, Interface } from '@ethersproject/abi'; import deleteEmpty from 'delete-empty'; import fs from 'fs'; import { HardhatPluginError } from 'hardhat/plugins'; import path from 'path'; const TS_TAG = `// this file was automatically generated by ${pkg.name} - do not modify`; export const clearAbiGroup = async (context, config) => { const outputDirectory = path.resolve(context.config.paths.root, config.path); if (!fs.existsSync(outputDirectory)) { return; } const files = (await fs.promises.readdir(outputDirectory, { recursive: true, withFileTypes: true, })) .filter((dirent) => dirent.isFile()) .map((dirent) => path.resolve(dirent.parentPath, dirent.name)); await Promise.all(files.map(async (file) => { const contents = await fs.promises.readFile(file, 'utf-8'); if (path.extname(file) === '.json') { try { // attempt to parse ABI from file contents new Interface(contents); } catch (e) { // file is not an ABI - do not delete return; } } else if (path.extname(file) === '.ts') { if (!contents.includes(TS_TAG)) { // file was not generated by plugin - do not delete return; } } else { // ABIs must be stored as JSON or TS return; } await fs.promises.rm(file); })); await deleteEmpty(outputDirectory); }; export const exportAbiGroup = async (context, config) => { const outputDirectory = path.resolve(context.config.paths.root, config.path); if (outputDirectory === context.config.paths.root) { throw new HardhatPluginError(pkg.name, 'resolved path must not be root directory'); } const outputData = []; const fullNames = Array.from(await context.artifacts.getAllFullyQualifiedNames()); await Promise.all(fullNames.map(async (fullName) => { if (config.only.length && !config.only.some((m) => fullName.match(m))) return; if (config.except.length && config.except.some((m) => fullName.match(m))) return; let { abi, sourceName, contractName } = await context.artifacts.readArtifact(fullName); if (!abi.length) return; abi = abi.filter((element, index, array) => config.filter(element, index, array, fullName)); // format ABI using ethers presets const formatType = FormatTypes[config.format] ?? 'json'; abi = [new Interface(abi).format(formatType)].flat(); let contents = JSON.stringify(abi, null, config.spacing); if (config.format === 'typescript') { contents = `${TS_TAG}\nexport default ${contents} as const;\n`; } if (!['json', 'minimal', 'full', 'typescript'].includes(config.format)) { throw new HardhatPluginError(pkg.name, `Unknown format: ${config.format}`); } const extension = config.format === 'typescript' ? '.ts' : '.json'; const destination = path.resolve(outputDirectory, config.rename(sourceName, contractName)) + extension; outputData.push({ destination, contents }); })); outputData.reduce((acc, { 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; }, {}); if (config.clear) { await clearAbiGroup(context, config); } await Promise.all(outputData.map(async ({ destination, contents }) => { await fs.promises.mkdir(path.dirname(destination), { recursive: true }); await fs.promises.writeFile(destination, contents, { flag: 'w' }); })); };