import alot from 'alot'; import { type TAbiItem } from '@dequanto/types/TAbi'; import { TAddress } from '@dequanto/models/TAddress'; import { env, File } from 'atma-io'; import { class_Uri } from 'atma-utils'; import { $abiType } from '@dequanto/utils/$abiType'; import { $date } from '@dequanto/utils/$date'; import { $path } from '@dequanto/utils/$path'; import { $abiUtils } from '@dequanto/utils/$abiUtils'; import { TPlatform } from '@dequanto/models/TPlatform'; import { $config } from '@dequanto/utils/$config'; import { $logger } from '@dequanto/utils/$logger'; import { GeneratorStorageReader } from './GeneratorStorageReader'; import { Web3Client } from '@dequanto/clients/Web3Client'; import { Str } from './utils/Str'; import { $gen } from './utils/$gen'; import { IConfigData } from '@dequanto/config/interface/IConfigData'; export class GeneratorFromAbi { static get Gen() { return Gen; } async generate(abiJson: TAbiItem[], opts: { target?: 'js' | 'mjs' | 'ts' | 'cjs' network: TPlatform name: string contractName: string address: TAddress output: string outputFileExt?: string implementation: TAddress saveAbi?: boolean saveSources?: boolean sources?: { [file: string]: { content: string } } sourceMain?: string /** Path to the compiled meta json file (with ABI and Bytecode) */ artifact?: string client?: Web3Client }) { let settings = $config.get ('settings.generate'); let target = opts.target ?? settings?.target ?? 'ts'; let outputSourceType = { mjs: 'js', cjs: 'js', }[target] ?? target; let outputModuleType = target === 'cjs' ? 'cjs' : 'esm'; let outputFileExt = opts?.outputFileExt ?? { mjs: 'mjs', cjs: 'js', ts: 'ts', js: 'js', }[target]; let methodsArr = alot(abiJson) .filter(x => x.type === 'function' || x.type === 'constructor') .groupBy(x => x.name) .map(group => { if (group.values.length === 1) { let item = group.values[0]; return Gen.serializeMethodTs(item); } if (group.values.length > 1) { let items = group.values; return Gen.serializeMethodTsOverloads(items); } return null; }) .filter(Boolean) .mapFull(arr => { return { ts: arr.map(x => Str.formatMethod(x.ts)), js: arr.map(x => Str.formatMethod(x.js)), types: arr.map(x => Str.formatMethod(x.types)), } }); let methodTypings = alot(abiJson) .filter(x => x.type === 'function') .groupBy(x => x.name) .map(group => { let item = group.values[0]; return Gen.serializeMethodTypingsTs(item.name, group.values); }) .filter(Boolean) .toArray(); let eventsArr = alot(abiJson) .filter(x => x.type === 'event') .map(x => Gen.serializeEvent(x)) .filter(Boolean) .mapFull(arr => { return { ts: arr.map(x => Str.formatMethod(x.ts)), js: arr.map(x => Str.formatMethod(x.js)), types: arr.map(x => Str.formatMethod(x.types)), } }) let eventTypes = alot(abiJson) .filter(x => x.type === 'event') .groupBy(x => x.name) .map(group => { let item = group.values[0]; return Gen.serializeEventType(item); }) .filter(Boolean) .toArray(); //let eventInterfacesAll = Gen.serializeEventsInterfacesAllTs(abiJson.filter(x => x.type === 'event')); let eventsExtractorsArr = alot(abiJson) .filter(x => x.type === 'event') .map(x => Gen.serializeEventExtractor(x)) .filter(Boolean) .mapFull(arr => { return { ts: arr.map(x => Str.formatMethod(x.ts)), js: arr.map(x => Str.formatMethod(x.js)), types: arr.map(x => Str.formatMethod(x.types)), } }); let eventsFetchersArr = alot(abiJson) .filter(x => x.type === 'event') .map(x => Gen.serializeEventFetcher(x)) .filter(Boolean) .mapFull(arr => { return { ts: arr.map(x => Str.formatMethod(x.ts)), js: arr.map(x => Str.formatMethod(x.js)), types: arr.map(x => Str.formatMethod(x.types)), } }); // let eventInterfaces = abiJson // .filter(x => x.type === 'event') // .map(x => Gen.serializeEventInterface(x)) // .filter(Boolean) // .map(Str.formatMethod); let methods = methodsArr[outputSourceType].join('\n\n'); let methodsTypings = methodsArr.types.join('\n\n'); let events = eventsArr[outputSourceType].join('\n\n'); let eventsTypings = eventsArr.types.join('\n\n'); let eventsExtractors = eventsExtractorsArr[outputSourceType].join('\n\n'); let eventsExtractorsTypings = eventsExtractorsArr.types.join('\n\n'); let eventsFetchers = eventsFetchersArr[outputSourceType].join('\n\n'); let eventsFetchersTypings = eventsFetchersArr.types.join('\n\n'); let name = opts.name; let contractTemplateSuffix = outputModuleType === 'cjs' ? 'cjs' : outputSourceType; let templatePath = $path.resolve(`/src/gen/templates/ContractTemplate.${ contractTemplateSuffix }.tmpl`); let template = await File.readAsync(templatePath, { skipHooks: true }); let templateDtsPath = $path.resolve(`/src/gen/templates/ContractTemplate.d.ts.tmpl`); let templateDts = await File.readAsync(templateDtsPath, { skipHooks: true }); let EtherscanStr; let EthWeb3ClientStr; let imports = []; let sourceUri: string; let Web3ClientOptions = ''; let EvmScanOptions = ''; let importPfx = true /* always */ ? 'dequanto' : '@dequanto'; switch (opts.network) { case 'hardhat': EtherscanStr = 'Etherscan'; EthWeb3ClientStr = 'HardhatWeb3Client'; imports = { esm: [ `import { Etherscan } from '${importPfx}/explorer/Etherscan';`, `import { HardhatWeb3Client } from '${importPfx}/hardhat/HardhatWeb3Client';`, ], cjs: [ `const { Etherscan } = require('${importPfx}/explorer/Etherscan');`, `const { HardhatWeb3Client } = require('${importPfx}/hardhat/HardhatWeb3Client');`, ], }[outputModuleType]; sourceUri = ``; break; default: { let web3Config = $config.get(`web3.${opts.network}`); if (web3Config) { EtherscanStr = 'Evmscan'; EthWeb3ClientStr = 'EvmWeb3Client'; imports = { esm: [ `import { Evmscan } from '${importPfx}/explorer/Evmscan';`, `import { EvmWeb3Client } from '${importPfx}/clients/EvmWeb3Client';`, ], cjs: [ `const { Evmscan } = require('${importPfx}/explorer/Evmscan');`, `const { EvmWeb3Client } = require('${importPfx}/clients/EvmWeb3Client');`, ] }[outputModuleType]; Web3ClientOptions = `{ platform: '${opts.network}' }`; EvmScanOptions = `{ platform: '${opts.network}' }`; sourceUri = ''; let evmscan = $config.get(`blockchainExplorer.${opts.network}`) if (evmscan?.www) { sourceUri = `${evmscan.www}/address/${opts.address}#code`; } break; } throw new Error(`Unknown network ${opts.network}, and no configuration found under "web3" field`); } } let outputDirectory = name; let outputFilename = /[^\\/]+$/.exec(name)[0]; let outputPath = $path.hasExt(opts.output) ? opts.output : class_Uri.combine(opts.output, outputDirectory, `${outputFilename}.${outputFileExt}`); let meta = { artifact: opts.artifact ? this.getRelativePath(opts.artifact) : void 0, source: opts.sourceMain ? this.getRelativePath(opts.sourceMain) : void 0, class: this.getRelativePath(outputPath) }; let metaProperty = `$meta = ${JSON.stringify(meta, null, 4)}`; let storageReaderInitializer = ''; let storageReaderProperty = ''; let storageReaderClass = ''; let storageReaderClassTypings = ''; try { let storageReaderGenerator = new GeneratorStorageReader(); let reader = await storageReaderGenerator.generate({ ...opts, target: outputSourceType }); if (reader.sourcePath != null && opts.address == null) { sourceUri = this.getRelativePath(reader.sourcePath); } if (reader.className) { storageReaderClass = reader.code; storageReaderClassTypings = reader.types; storageReaderProperty = outputSourceType === 'ts' ? `declare storage: ${reader.className}` : ''; storageReaderInitializer = `this.storage = new ${reader.className}(this.address, this.client, this.explorer);`; $logger.log(`green was generated`); } else { $logger.log(`yellow was not generated: ${reader.error?.message}`); } } catch (error) { $logger.error(`Storage Reader is skipped due to the error: ${error.message}`); } let className = $gen.toClassName(name); let code = template .replace(/\$Etherscan\$/g, EtherscanStr) .replace(/\$EthWeb3Client\$/g, EthWeb3ClientStr) .replace(/\$Web3ClientOptions\$/g, Web3ClientOptions) .replace(/\$EvmScanOptions\$/g, EvmScanOptions) .replace(`/* META_PROPERTY */`, () => Str.indent(metaProperty, ' ')) .replace(`/* IMPORTS */`, () => imports.join('\n')) .replace(`/* ERRORS */`, () => Gen.serializeErrors(className, abiJson)) .replace(/\$NAME\$/g, className) .replace(`$ADDRESS$`, opts.address ? `'${opts.address}'` : 'null') .replace(`/* METHODS */`, methods) .replace(`/* EVENTS */`, events) .replace(`/* EVENTS_EXTRACTORS */`, eventsExtractors) .replace(`/* EVENTS_FETCHERS */`, eventsFetchers) .replace(`$ABI$`, () => JSON.stringify(abiJson)) .replace(`$DATE$`, $date.format(new Date(), 'yyyy-MM-dd HH:mm')) .replace(`$EXPLORER_URL$`, sourceUri) //.replace(`/* $EVENT_INTERFACES$ */`, () => eventInterfaces.join('\n') + '\n\n' + eventInterfacesAll.code + '\n\n') .replace(`/* STORAGE_READER_INITIALIZER */`, storageReaderInitializer) .replace(`/* STORAGE_READER_PROPERTY */`, storageReaderProperty) .replace(`/* STORAGE_READER_CLASS */`, () => storageReaderClass) .replace(`/* TX_CALLER_METHODS */`, () => Gen.serializeTxCallerMethods(className, abiJson)[outputSourceType]) .replace(`/* TX_DATA_METHODS */`, () => Gen.serializeTxDataMethods(className, abiJson)[outputSourceType]) .replace(`/* $EVENT_TYPES$ */`, () => Str.indent(eventTypes.map(x => x.code).join('\n'), ' ')) .replace(`/* $METHOD_TYPES$ */`, () => Str.indent(methodTypings.map(x => x.code).join('\n'), ' ')) //.replace(`/* $METHOD_INTERFACES$ */`, () => methodInterfacesArr.map(x => x.code).join('\n\n') + '\n\n' + methodInterfacesAll.code + '\n\n') ; let codeDts = templateDts .replace(`/* IMPORTS */`, () => imports.join('\n')) .replace(`/* ERRORS */`, () => Gen.serializeErrors(className, abiJson)) .replace(/\$NAME\$/g, className) .replace(`$ADDRESS$`, opts.address ? `'${opts.address}'` : 'null') .replace(`/* METHODS */`, methodsTypings) .replace(`/* EVENTS */`, eventsTypings) .replace(`/* EVENTS_EXTRACTORS */`, eventsExtractorsTypings) .replace(`/* EVENTS_FETCHERS */`, eventsFetchersTypings) .replace(`$ABI$`, () => JSON.stringify(abiJson)) .replace(`$DATE$`, $date.format(new Date(), 'yyyy-MM-dd HH:mm')) .replace(`$EXPLORER_URL$`, sourceUri) //.replace(`/* $EVENT_INTERFACES$ */`, () => eventInterfaces.join('\n') + '\n\n' + eventInterfacesAll.code + '\n\n') .replace(`/* STORAGE_READER_INITIALIZER */`, storageReaderInitializer) .replace(`/* STORAGE_READER_PROPERTY */`, storageReaderProperty) .replace(`/* STORAGE_READER_CLASS */`, () => storageReaderClassTypings) .replace(`/* TX_CALLER_METHODS */`, () => Gen.serializeTxCallerMethods(className, abiJson)[outputSourceType]) .replace(`/* TX_DATA_METHODS */`, () => Gen.serializeTxDataMethods(className, abiJson)[outputSourceType]) .replace(`/* $EVENT_TYPES$ */`, () => Str.indent(eventTypes.map(x => x.code).join('\n'), ' ')) .replace(`/* $METHOD_TYPES$ */`, () => Str.indent(methodTypings.map(x => x.code).join('\n'), ' ')) ; await File.writeAsync(outputPath, code, { skipHooks: true }); if (outputSourceType === 'js') { let outputPathDts = outputPath.replace(/\.\w+$/, '.d.ts'); await File.writeAsync(outputPathDts, codeDts, { skipHooks: true }); } if (opts.saveAbi) { let path = class_Uri.combine(opts.output, outputDirectory, `${outputFilename}Abi.json`); await File.writeAsync(path, abiJson); } $logger.log(`bold> ABI wrapper class created: bold<${outputPath}>`); let sources = opts.sources; let sourceFiles = []; if (sources && opts.saveSources !== false) { sourceFiles = await alot.fromObject(sources).mapAsync(async entry => { let sourceFilename = /\/?([^/]+$)/.exec(entry.key)[1]; let sourcePath = entry.key; let cwd = env.currentDir.toString().toLowerCase(); if (sourcePath.toLowerCase().startsWith(cwd)) { sourcePath = sourcePath.substring(cwd.length + 1); } // Remove: solc should load deps also from the root directory // if (sourcePath.startsWith('@')) { // // handle @openzeppelin and other npm packages // sourcePath = `node_modules/${ sourcePath }`; // } let path = class_Uri.combine(opts.output, outputDirectory, outputFilename, sourcePath); await File.writeAsync(path, entry.value.content, { skipHooks: true }); $logger.log(`Source code saved: ${path}`); return path; }).toArrayAsync(); } return { main: outputPath, sources: sourceFiles, platform: opts.network, address: opts.address, implementation: opts.implementation, contractName: opts.contractName }; } private getRelativePath(path: string) { if (path == null) { return path; } let uri = new class_Uri(path); if (uri.isRelative()) { return path; } return uri.toRelativeString(process.cwd() + '/'); } } namespace Gen { export function serializeMethodTs(abi: TAbiItem) { if (abi.type === 'constructor') { return serializeConstructorMethodTs(abi); } let isRead = $abiUtils.isReadMethod(abi); if (isRead) { return serializeReadMethodTs(abi); } return serializeWriteMethodTs(abi); } export function serializeMethodTsOverloads(abis: TAbiItem[]) { let isRead = abis.every(abi => $abiUtils.isReadMethod(abi)); if (isRead) { return serializeReadMethodTsOverloads(abis); } return serializeWriteMethodTsOverloads(abis); } // abi.length > 1 if has method overloads export function serializeMethodInterfacesTs(name: string, abis: TAbiItem[]) { let args = abis.map(abi => { let { fnInputArgumentsTargets } = serializeArgumentsTs(abi); return `[ ${fnInputArgumentsTargets.ts} ]`; }).join(' | '); let iface = `IMethod${name[0].toUpperCase()}${name.substring(1)}`; let code = [ `interface ${iface} {`, ` method: "${name}"`, ` arguments: ${args}`, `}` ]; return { method: name, interface: iface, code: code.join('\n') }; } export function serializeMethodTypingsTs(name: string, abis: TAbiItem[]) { let args = abis.map(abi => { let { fnInputArgumentsTargets } = serializeArgumentsTs(abi); return `[ ${fnInputArgumentsTargets.ts} ]`; }).join(' | '); let iface = `IMethod${name[0].toUpperCase()}${name.substring(1)}`; let code = [ `${name}: {`, ` method: "${name}"`, ` arguments: ${args}`, `}`, ]; return { method: name, interface: iface, code: code.join('\n') }; } export function serializeMethodInterfacesAllTs(methods: { method: string, interface: string }[]) { let fields = methods.map(method => { return ` ${method.method}: ${method.interface}`; }); let code = [ `interface IMethods {`, ...fields, ` '*': { method: string, arguments: any[] } `, `}` ]; return { code: code.join('\n') }; } export function serializeEventsInterfacesAllTs(events: TAbiItem[]) { let fields = events.map(item => { return ` ${item.name}: TLog${item.name}Parameters`; }); let code = [ `interface IEvents {`, ...fields, ` '*': any[] `, `}` ]; return { code: code.join('\n') }; } export function serializeEventType(event: TAbiItem) { let { fnInputArgumentsTargets, callInputArguments, fnResult } = serializeArgumentsTs(event); let outputParams = `{ ${fnInputArgumentsTargets.ts} }`; let outputArgs = `[ ${fnInputArgumentsTargets.ts.replace('\n', '')} ]`; let code = [ `${event.name}: {`, ` outputParams: ${outputParams},`, ` outputArgs: ${outputArgs},`, `}` ]; return { code: code.join('\n') }; } export function serializeEvent(abi: TAbiItem) { let { fnInputArgumentsTargets, callInputArguments, fnResult } = serializeArgumentsTs(abi); return { ts: ` on${abi.name} (fn?: (event: TClientEventsStreamData>) => void): ClientEventsStream>> { return this.$onLog('${abi.name}', fn); } `, types: ` on${abi.name} (fn?: (event: TClientEventsStreamData>) => void): ClientEventsStream>> `, js: ` on${abi.name} (fn) { return this.$onLog('${abi.name}', fn); } ` }; } export function serializeEventExtractor(abi: TAbiItem) { return { ts: ` extractLogs${abi.name} (tx: TEth.TxReceipt): ITxLogItem>[] { let abi = this.$getAbiItem('event', '${abi.name}'); return this.$extractLogs(tx, abi) as any as ITxLogItem>[]; } `, types: ` extractLogs${abi.name} (tx: TEth.TxReceipt): ITxLogItem>[] `, js: ` extractLogs${abi.name} (tx) { let abi = this.$getAbiItem('event', '${abi.name}'); return this.$extractLogs(tx, abi); } ` }; } export function serializeEventFetcher(abi: TAbiItem) { let inputs = abi.inputs; let indexed = alot(inputs).takeWhile(x => x.indexed).toArray(); let indexedParams = indexed.map(param => `${param.name}?: ${$abiType.getTsType(param.type, param)}`) return { ts: ` async getPastLogs${abi.name} (options?: { fromBlock?: number | Date toBlock?: number | Date params?: { ${indexedParams} } }): Promise>[]> { return await this.$getPastLogsParsed('${abi.name}', options) as any; } `, types: ` getPastLogs${abi.name} (options?: { fromBlock?: number | Date toBlock?: number | Date params?: { ${indexedParams} } }): Promise>[]> `, js: ` async getPastLogs${abi.name} (options) { return await this.$getPastLogsParsed('${abi.name}', options); } ` } } // export function serializeEventInterface (abi: TAbiItem) { // let { fnInputArguments, callInputArguments, fnResult } = serializeArgumentsTs(abi); // return ` // type TLog${abi.name} = { // ${fnInputArguments} // }; // type TLog${abi.name}Parameters = [ ${fnInputArguments.replace('\n', '')} ]; // `; // } export function serializeMethodAbi(abi: TAbiItem, includeNames?: boolean) { let params = abi.inputs?.map(x => { let param = x.type; if (includeNames && x.name) { param += ' ' + x.name; } return param; }).join(', ') ?? ''; let returns = serializeMethodAbiReturns(abi.outputs); if (returns && abi.outputs.length > 1) { returns = `(${returns})`; } let returnsStr = returns ? `returns ${returns}` : ''; return `function ${abi.name}(${params}) ${returnsStr}`.trim(); } function serializeReadMethodTs(abi: TAbiItem) { let { fnInputArgumentsTargets, callInputArguments, fnResult } = serializeArgumentsTs(abi); if (callInputArguments) { callInputArguments = `, ${callInputArguments}`; } return { ts: ` // ${$abiUtils.getMethodSignature(abi)} async ${abi.name} (${fnInputArgumentsTargets.ts}): ${fnResult} { return this.$read(this.$getAbiItem('function', '${abi.name}')${callInputArguments}); } `, types: ` // ${$abiUtils.getMethodSignature(abi)} ${abi.name} (${fnInputArgumentsTargets.ts}): ${fnResult} `, js: ` // ${$abiUtils.getMethodSignature(abi)} async ${abi.name} (${fnInputArgumentsTargets.js}) { return this.$read(this.$getAbiItem('function', '${abi.name}')${callInputArguments}); } ` }; } function serializeReadMethodTsOverloads(abis: TAbiItem[]) { let overrides = abis.map(abi => { let { fnInputArgumentsTargets, fnResult } = serializeArgumentsTs(abi); return ` // ${$abiUtils.getMethodSignature(abi)} async ${abi.name} (${fnInputArgumentsTargets.ts}): ${fnResult} `; }).join('\n'); let abi = abis[0]; let { fnResult } = serializeArgumentsTs(abi); let sigs = abis.map(abi => serializeMethodAbi(abi)).map(x => `'${x}'`).join(', '); return { ts: ` ${overrides} async ${abi.name} (...args): ${fnResult} { let abi = this.$getAbiItemOverload('${abi.name}', args); return this.$read(abi, ...args); } `, types: ` ${overrides} ${abi.name} (...args): ${fnResult} `, js: ` async ${abi.name} (...args) { let abi = this.$getAbiItemOverload('${abi.name}', args); return this.$read(abi, ...args); } ` }; } function serializeWriteMethodTs(abi: TAbiItem) { let { fnInputArgumentsTargets, callInputArguments } = serializeArgumentsTs(abi); if (callInputArguments) { callInputArguments = `, ${callInputArguments}`; } return { ts: ` // ${$abiUtils.getMethodSignature(abi)} async ${abi.name} (sender: TSender, ${fnInputArgumentsTargets.ts}): Promise { return this.$write(this.$getAbiItem('function', '${abi.name}'), sender${callInputArguments}); } `, types: ` // ${$abiUtils.getMethodSignature(abi)} ${abi.name} (sender: TSender, ${fnInputArgumentsTargets.ts}): Promise `, js: ` // ${$abiUtils.getMethodSignature(abi)} async ${abi.name} (sender, ${fnInputArgumentsTargets.js}) { return this.$write(this.$getAbiItem('function', '${abi.name}'), sender${callInputArguments}); } ` }; } function serializeConstructorMethodTs(abi: TAbiItem) { let { fnInputArgumentsTargets, callInputArguments } = serializeArgumentsTs(abi); if (callInputArguments) { callInputArguments = `, ${callInputArguments}`; } return { ts: ` async $constructor (deployer: TSender, ${fnInputArgumentsTargets.ts}): Promise { throw new Error('Not implemented. Typing purpose. Use the ContractDeployer class to deploy the contract'); } `, types: ` $constructor (deployer: TSender, ${fnInputArgumentsTargets.ts}): Promise `, js: ` async $constructor (deployer, ${fnInputArgumentsTargets.js}) { throw new Error('Not implemented. Typing purpose. Use the ContractDeployer class to deploy the contract'); } ` }; } function serializeWriteMethodTsOverloads(abis: TAbiItem[]) { let overrides = abis.map(abi => { let { fnInputArgumentsTargets, fnResult } = serializeArgumentsTs(abi); return ` // ${$abiUtils.getMethodSignature(abi)} async ${abi.name} (sender: TSender, ${fnInputArgumentsTargets.ts}): Promise `; }).join('\n'); let abi = abis[0]; let sigs = abis.map(abi => serializeMethodAbi(abi)).map(x => `'${x}'`).join(', '); return { ts: ` ${overrides} async ${abi.name} (sender: TSender, ...args): Promise { let abi = this.$getAbiItemOverload([ ${sigs} ], args); return this.$write(abi, sender, ...args); } `, types: ` ${overrides} ${abi.name} (sender: TSender, ...args): Promise `, js: ` async ${abi.name} (sender, ...args) { let abi = this.$getAbiItemOverload([ ${sigs} ], args); return this.$write(abi, sender, ...args); } ` }; } function serializeArgumentsTs(abi: TAbiItem) { let inputs = abi.inputs.map((input, i) => { let result = { ...input }; if (result.name == null || result.name === '') { result.name = 'input' + i; } if (result.name === 'sender') { result.name = '_sender'; } return result; }); let fnInputArgumentsArr = inputs ?.map((input) => { let tsType = $abiType.getTsType(input.type, input); if (tsType == null) { throw new Error(`Unknown abi type in arguments: ${input.type}`); } return { name: input.name, type: tsType }; }) ?? []; let fnInputArgumentsTs = fnInputArgumentsArr .map((input) => { return `${input.name}: ${input.type}`; }) .join(', '); let fnInputArgumentsJs = fnInputArgumentsArr .map((input) => { return `${input.name}`; }) .join(', '); let callInputArguments = inputs ?.map(input => { return `${input.name}`; }) ?.join(', ') ?? ''; let fnResult = serializeMethodTsReturns(abi.outputs); return { fnInputArgumentsTargets: { ts: fnInputArgumentsTs, js: fnInputArgumentsJs, }, callInputArguments, fnResult }; } function isObjectParams(params: { name?, type }[]) { return params?.every(x => Boolean(x.name)); } function serializeMethodAbiReturns(params: { name?, type, components?}[], opts?: { asTuple?: boolean}) { if (params == null) { return ''; } // if (isObjectParams(params)) { // return params.map(x => serializeMethodAbiReturnsSingle(x)).join(','); // } return params?.map(x => serializeMethodAbiReturnsSingle(x, opts)).join(','); } function serializeMethodAbiReturnsSingle(param: { name?, type, components?}, opts?: { asTuple?: boolean}) { if (param == null) { return null; } if (param.components) { // tuple, tuple[] let fields = serializeMethodAbiReturns(param.components, { asTuple: true }); return `(${fields})${param.type === 'tuple[]' ? '[]' : ''}`; } if (opts?.asTuple && param.name && param.type) { return `${param.type} ${param.name}`; } return param.type; } function serializeMethodTsReturns(params: { name?, type }[]) { if (params == null || params.length === 0) { params = [{ name: '', type: 'uint256' }]; } let tsTypes = params.map(param => { let tsType = $abiType.getTsType(param.type, param); if (tsType == null) { throw new Error(`(gen) Unknown abi type in return: ${param.type}`); } return { name: param.name, type: tsType }; }); if (params.length > 1 && isObjectParams(params)) { let paramsStr = tsTypes.map(x => `${x.name}: ${x.type}`).join(', '); return `Promise<{ ${paramsStr} }>`; } let fnResult = tsTypes?.map(x => x.type).join(', '); if (tsTypes.length > 1) { fnResult = `[ ${fnResult} ]` } return `Promise<${fnResult}>`; } export function serializeErrors(className: string, abiJson: TAbiItem[]): string { let errors = abiJson.filter(x => (x as any).type === 'error'); if (errors.length === 0) { return ''; } let lines = [ `export namespace ${className}Errors {` ]; errors.forEach(error => { lines.push(` export interface ${error.name} {`); lines.push(` type: '${error.name}'`); lines.push(` params: {`); error.inputs.forEach(input => { lines.push(` ${input.name}: ${$abiType.getTsType(input.type, input)}`); }); lines.push(` }`); lines.push(` }`); }); lines.push(` export type Error = ${errors.map(x => x.name).join(' | ')}`); lines.push(`}`); return lines.join('\n'); } export function serializeTxCallerMethods(className: string, abiJson: TAbiItem[]) { let methods = abiJson.filter(x => x.type === 'function'); let writeMethods = methods.filter(abi => $abiUtils.isReadMethod(abi) === false); let lines = writeMethods.map(method => { return serializeMethodTs(method); }); return { ts: lines.map(x => x.ts).join('\n'), js: lines.map(x => x.js).join('\n'), }; function serializeMethodTs(abi: TAbiItem) { let { fnInputArgumentsTargets, callInputArguments, fnResult } = serializeArgumentsTs(abi); if (callInputArguments) { callInputArguments = `, ${callInputArguments}`; } return { ts: ` ${abi.name} (sender: TSender, ${fnInputArgumentsTargets.ts}): Promise<{ error?: Error & { data?: { type: string, params } }, result? }>`, js: ` ${abi.name} (sender, ${fnInputArgumentsTargets.js})`, }; } } export function serializeTxDataMethods(className: string, abiJson: TAbiItem[]) { let methods = abiJson.filter(x => x.type === 'function'); let writeMethods = methods.filter(abi => $abiUtils.isReadMethod(abi) === false); let lines = writeMethods.map(method => { return serializeInterfaceMethodTs(method); }); return { ts: lines.map(x => x.ts).join('\n'), js: lines.map(x => x.js).join('\n'), } function serializeInterfaceMethodTs(abi: TAbiItem) { let { fnInputArgumentsTargets, callInputArguments, fnResult } = serializeArgumentsTs(abi); if (callInputArguments) { callInputArguments = `, ${callInputArguments}`; } return { ts: ` ${abi.name} (sender: TSender, ${fnInputArgumentsTargets.ts}): Promise`, js: ` ${abi.name} (sender, ${fnInputArgumentsTargets.js})`, } } } }