import { readFileSync } from 'fs' ; import { compact , uniqBy } from 'lodash' ; import { basename , join , resolve } from 'path' ; import { Dictionary } from 'ts-essentials' ; import { BytecodeWithLinkReferences , CodegenConfig , Config , Contract , extractAbi , extractBytecode , extractDocumentation , FileDescription , getFileExtension , getFilename , normalizeName , parse , TypeChainTarget , } from 'typechain' ; import { codegenAbstractContractFactory , codegenContractFactory , codegenContractTypings } from './codegen' ; import { generateHardhatHelper } from './codegen/hardhat' ; import { FACTORY_POSTFIX } from './common' ; export interface IEthersCfg { outDir ? : string ; } const DEFAULT_OUT_PATH = './types/ethers-contracts/' ; export default class Ethers extends TypeChainTarget { name = 'Ethers' ; private readonly allContracts : string [ ] ; private readonly outDirAbs : string ; private readonly contractCache : Dictionary < { abi : any ; contract : Contract ; } | undefined > = { } ; private readonly bytecodeCache : Dictionary < BytecodeWithLinkReferences | undefined > = { } ; constructor ( config ) { super ( config ) ; const { cwd , outDir , allFiles } = config ; this . outDirAbs = resolve ( cwd , outDir || DEFAULT_OUT_PATH ) ; this . allContracts = allFiles . map ( ( fp ) => normalizeName ( getFilename ( fp ) ) ) ; } transformFile ( file ) { const fileExt = getFileExtension ( file . path ) ; if ( fileExt === '.bin' ) { return this . transformBinFile ( file ) ; } return this . transformAbiOrFullJsonFile ( file ) ; } transformBinFile ( file ) { const name = getFilename ( file . path ) ; const bytecode = extractBytecode ( file . contents ) ; if ( ! bytecode ) { return ; } if ( this . contractCache [ name ] ) { const { contract , abi } = this . contractCache [ name ] ! ; delete this . contractCache [ name ] ; return [ this . genContractFactoryFile ( contract , abi , bytecode ) ] ; } else { this . bytecodeCache [ name ] = bytecode ; } } transformAbiOrFullJsonFile ( file ) { const name = getFilename ( file . path ) ; const abi = extractAbi ( file . contents ) ; if ( abi . length === 0 ) { return ; } const documentation = extractDocumentation ( file . contents ) ; const contract = parse ( abi , name , documentation ) ; const bytecode = extractBytecode ( file . contents ) || this . bytecodeCache [ name ] ; if ( bytecode ) { return [ this . genContractTypingsFile ( contract , this . cfg . flags ) , this . genContractFactoryFile ( contract , abi , bytecode ) , ] ; } else { this . contractCache [ name ] = { abi , contract } ; return [ this . genContractTypingsFile ( contract , this . cfg . flags ) ] ; } } genContractTypingsFile ( contract , codegenConfig ) { return { path : join ( this . outDirAbs , `${ contract . name }.ts` ) , contents : codegenContractTypings ( contract , codegenConfig ) , } ; } genContractFactoryFile ( contract , abi , bytecode ? ) { return { path : join ( this . outDirAbs , 'factories' , `${ contract . name }${ FACTORY_POSTFIX }.ts` ) , contents : codegenContractFactory ( contract , abi , bytecode ) , } ; } afterRun ( ) { const abstractFactoryFiles = Object . keys ( this . contractCache ) . map ( ( contractName ) => { const { contract , abi } = this . contractCache [ contractName ] ! ; return { path : join ( this . outDirAbs , 'factories' , `${ contract . name }${ FACTORY_POSTFIX }.ts` ) , contents : codegenAbstractContractFactory ( contract , abi ) , } ; } ) ; const hardhatHelper = this . cfg . flags . environment === 'hardhat' ? { path : join ( this . outDirAbs , 'hardhat.d.ts' ) , contents : generateHardhatHelper ( this . allContracts ) } : undefined ; const allFiles = compact ( [ ... abstractFactoryFiles , { path : join ( this . outDirAbs , 'common.ts' ) , contents : readFileSync ( join ( __dirname , '../static/common.ts' ) , 'utf-8' ) , } , { path : join ( this . outDirAbs , 'index.ts' ) , contents : this . genReExports ( ) , } , hardhatHelper , ] ) ; return allFiles ; } private genReExports ( ) { const codegen = [ ] ; const allContractsNoDuplicates = uniqBy ( this . allContracts , ( c ) => basename ( c ) ) ; for ( const fileName of allContractsNoDuplicates ) { const desiredSymbol = fileName ; codegen . push ( `export type { ${ desiredSymbol } } from './${ desiredSymbol }'` ) ; } codegen . push ( '\n' ) ; for ( const fileName of allContractsNoDuplicates ) { const desiredSymbol = fileName + '__factory' ; codegen . push ( `export { ${ desiredSymbol } } from './factories/${ desiredSymbol }'` ) ; } return codegen . join ( '\n' ) ; } }