0xweb
Version:
Contract package manager and other web3 tools
899 lines (808 loc) • 36.5 kB
text/typescript
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<IConfigData['settings']['generate']> ('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<string>(templatePath, { skipHooks: true });
let templateDtsPath = $path.resolve(`/src/gen/templates/ContractTemplate.d.ts.tmpl`);
let templateDts = await File.readAsync<string>(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<StorageReader> was generated`);
} else {
$logger.log(`yellow<StorageReader> 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<green<${className}>> 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<TEventArguments<'${abi.name}'>>) => void): ClientEventsStream<TClientEventsStreamData<TEventArguments<'${abi.name}'>>> {
return this.$onLog('${abi.name}', fn);
}
`,
types: `
on${abi.name} (fn?: (event: TClientEventsStreamData<TEventArguments<'${abi.name}'>>) => void): ClientEventsStream<TClientEventsStreamData<TEventArguments<'${abi.name}'>>>
`,
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<TEventParams<'${abi.name}'>>[] {
let abi = this.$getAbiItem('event', '${abi.name}');
return this.$extractLogs(tx, abi) as any as ITxLogItem<TEventParams<'${abi.name}'>>[];
}
`,
types: `
extractLogs${abi.name} (tx: TEth.TxReceipt): ITxLogItem<TEventParams<'${abi.name}'>>[]
`,
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<ITxLogItem<TEventParams<'${abi.name}'>>[]> {
return await this.$getPastLogsParsed('${abi.name}', options) as any;
}
`,
types: `
getPastLogs${abi.name} (options?: {
fromBlock?: number | Date
toBlock?: number | Date
params?: { ${indexedParams} }
}): Promise<ITxLogItem<TEventParams<'${abi.name}'>>[]>
`,
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([ ${sigs} ], args);
return this.$read(abi, ...args);
}
`,
types: `
${overrides}
${abi.name} (...args): ${fnResult}
`,
js: `
async ${abi.name} (...args) {
let abi = this.$getAbiItemOverload([ ${sigs} ], 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<TxWriter> {
return this.$write(this.$getAbiItem('function', '${abi.name}'), sender${callInputArguments});
}
`,
types: `
// ${$abiUtils.getMethodSignature(abi)}
${abi.name} (sender: TSender, ${fnInputArgumentsTargets.ts}): Promise<TxWriter>
`,
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<TxWriter> {
throw new Error('Not implemented. Typing purpose. Use the ContractDeployer class to deploy the contract');
}
`,
types: `
$constructor (deployer: TSender, ${fnInputArgumentsTargets.ts}): Promise<TxWriter>
`,
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<TxWriter>
`;
}).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<TxWriter> {
let abi = this.$getAbiItemOverload([ ${sigs} ], args);
return this.$write(abi, sender, ...args);
}
`,
types: `
${overrides}
${abi.name} (sender: TSender, ...args): Promise<TxWriter>
`,
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?}[]) {
if (params == null) {
return '';
}
// if (isObjectParams(params)) {
// return params.map(x => serializeMethodAbiReturnsSingle(x)).join(',');
// }
return params?.map(x => serializeMethodAbiReturnsSingle(x)).join(',');
}
function serializeMethodAbiReturnsSingle(param: { name?, type, components?}) {
if (param == null) {
return null;
}
if (param.components) {
// tuple, tuple[]
let fields = serializeMethodAbiReturns(param.components);
return `[${fields}]${param.type === 'tuple[]' ? '[]' : ''}`;
}
// if (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<TEth.TxLike>`,
js: ` ${abi.name} (sender, ${fnInputArgumentsTargets.js})`,
}
}
}
}