import { Contract } from 'web3-eth-contract' import { TransactionReceipt } from 'web3-core' import ContractHandler from '../ContractHandler' import { Instantiable, InstantiableConfig } from '../../Instantiable.abstract' export abstract class ContractBase extends Instantiable { protected static instance = null public contractName: string private contract: Contract = null get address() { return this.contract.options.address } constructor(contractName: string, private optional: boolean = false) { super() this.contractName = contractName } public async getEventData(eventName: string, options: any) { if (!this.contract.events[eventName]) { throw new Error( `Event "${eventName}" not found on contract "${this.contractName}"` ) } return this.contract.getPastEvents(eventName, options) } public getPastEvents(eventName: string, filter: { [key: string]: any }) { return this.getEventData(eventName, { filter, fromBlock: 0, toBlock: 'latest' }) } public getAddress(): string { return this.contract.options.address } public getSignatureOfMethod(methodName: string): string { const foundMethod = this.searchMethod(methodName) return foundMethod.signature } public getInputsOfMethod(methodName: string): any[] { const foundMethod = this.searchMethod(methodName) return foundMethod.inputs } protected async init(config: InstantiableConfig) { this.setInstanceConfig(config) const contractHandler = new ContractHandler(config) this.contract = await contractHandler.get(this.contractName, this.optional) } protected async getFromAddress(from?: string): Promise { if (!from) { from = (await this.web3.eth.getAccounts())[0] } return from } protected async sendFrom( name: string, args: any[], from?: string ): Promise { from = await this.getFromAddress(from) return this.send(name, from, args) } protected async send( name: string, from: string, args: any[] ): Promise { if (!this.contract.methods[name]) { throw new Error( `Method "${name}" is not part of contract "${this.contractName}"` ) } // Logger.log(name, args) const method = this.contract.methods[name] try { const methodInstance = method(...args) const estimatedGas = await methodInstance.estimateGas(args, { from }) const tx = methodInstance.send({ from, gas: estimatedGas }) return tx } catch (err) { const mappedArgs = this.searchMethod(name, args).inputs.map((input, i) => { return { name: input.name, value: args[i] } }) this.logger.error('-'.repeat(40)) this.logger.error( `Sending transaction "${name}" on contract "${this.contractName}" failed.` ) this.logger.error(`Error: ${err.message}`) this.logger.error(`From: ${from}`) this.logger.error(`Parameters: ${JSON.stringify(mappedArgs, null, 2)}`) this.logger.error('-'.repeat(40)) throw err } } protected async call( name: string, args: any[], from?: string ): Promise { if (!this.contract.methods[name]) { throw new Error(`Method ${name} is not part of contract ${this.contractName}`) } // Logger.log(name) try { const method = this.contract.methods[name](...args) return method.call(from ? { from } : null) } catch (err) { this.logger.error( `Calling method "${name}" on contract "${this.contractName}" failed. Args: ${args}`, err ) throw err } } protected getEvent(eventName: string, filter: { [key: string]: any }) { if (!this.contract.events[eventName]) { throw new Error( `Event ${eventName} is not part of contract ${this.contractName}` ) } return this.ocean.keeper.utils.eventHandler.getEvent(this, eventName, filter) } private searchMethod(methodName: string, args: any[] = []) { const methods = this.contract.options.jsonInterface .map((method) => ({ ...method, signature: (method as any).signature })) .filter((method: any) => method.name === methodName) const foundMethod = methods.find(({ inputs }) => inputs.length === args.length) || methods[0] if (!foundMethod) { throw new Error( `Method "${methodName}" is not part of contract "${this.contractName}"` ) } return foundMethod } } export default ContractBase