"use strict";
/*
This file is part of web3.js.

web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with web3.js.  If not, see <http://www.gnu.org/licenses/>.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Contract = void 0;
const web3_core_1 = require("web3-core");
const web3_errors_1 = require("web3-errors");
const web3_eth_1 = require("web3-eth");
const web3_eth_abi_1 = require("web3-eth-abi");
const web3_types_1 = require("web3-types");
const web3_utils_1 = require("web3-utils");
const web3_validator_1 = require("web3-validator");
const encoding_js_1 = require("./encoding.js");
const contract_log_subscription_js_1 = require("./contract_log_subscription.js");
const utils_js_1 = require("./utils.js");
// eslint-disable-next-line import/no-cycle
const contract_deployer_method_class_js_1 = require("./contract-deployer-method-class.js");
// eslint-disable-next-line import/no-cycle
const contract_subscription_manager_js_1 = require("./contract-subscription-manager.js");
const contractSubscriptions = {
    logs: contract_log_subscription_js_1.ContractLogsSubscription,
    newHeads: web3_eth_1.NewHeadsSubscription,
    newBlockHeaders: web3_eth_1.NewHeadsSubscription,
};
/**
 * The `web3.eth.Contract` makes it easy to interact with smart contracts on the ethereum blockchain.
 * For using contract package, first install Web3 package using: `npm i web3` or `yarn add web3` based on your package manager, after that contracts features can be used as mentioned in following snippet.
 * ```ts
 *
 * import { Web3 } from 'web3';
 *
 * const web3 = new Web3('https://127.0.0.1:4545');
 * const abi = [...] as const; // your contract ABI
 *
 * let contract = new web3.eth.Contract(abi,'0xdAC17F958D2ee523a2206206994597C13D831ec7');
 * await contract.methods.balanceOf('0xdAC17F958D2ee523a2206206994597C13D831ec7').call();
 * ```
 * For using individual package install `web3-eth-contract` and `web3-core` packages using: `npm i web3-eth-contract web3-core` or `yarn add web3-eth-contract web3-core`. This is more efficient approach for building lightweight applications.
 * ```ts
 *
 * import { Web3Context } from 'web3-core';
 * import { Contract } from 'web3-eth-contract';
 *
 * const abi = [...] as const; // your contract ABI
 *
 * let contract = new web3.eth.Contract(
 * 	abi,
 * 	'0xdAC17F958D2ee523a2206206994597C13D831ec7'
 * 	 new Web3Context('http://127.0.0.1:8545'));
 *
 * await contract.methods.balanceOf('0xdAC17F958D2ee523a2206206994597C13D831ec7').call();
 * ```
 * ## Generated Methods
 * Following methods are generated by web3.js contract object for each of contract functions by using its ABI.
 *
 * ### send
 * This is used to send a transaction to the smart contract and execute its method. Note this can alter the smart contract state.
 *
 * #### Parameters
 * options?: PayableTxOptions | NonPayableTxOptions
 *
 * #### Returns
 * [Web3PromiEvent](/api/web3/namespace/core#Web3PromiEvent) : Web3 Promi Event
 *
 * ```ts
 * // using the promise
 * myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'})
 * 	.then(function(receipt){
 * 		// other parts of code to use receipt
 * 	});
 *
 *
 * // using the event emitter
 * myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'})
 * 	.on('transactionHash', function(hash){
 * 		// ...
 * 	})
 * 	.on('confirmation', function(confirmationNumber, receipt){
 * 		// ...
 * 	})
 * 	.on('receipt', function(receipt){
 * 		// ...
 * 	})
 * 	.on('error', function(error, receipt) {
 * 		// ...
 * 	});
 *
 * ```
 *
 * ### call
 * This will execute smart contract method in the EVM without sending any transaction. Note calling cannot alter the smart contract state.
 *
 * #### Parameters
 * options?: PayableCallOptions | NonPayableCallOptions,
 * block?: BlockNumberOrTag,
 *
 * #### Returns
 * Promise : having results of call
 *
 * ```ts
 *
 * let myContract = new web3.eth.Contract(abi, address);
 *
 * myContract.methods.myFunction().call()
 * .then(console.log);
 *
 * ```
 * ### estimateGas
 * Returns the amount of gas consumed by executing the method in EVM without creating a new transaction on the blockchain. The returned amount can be used as a gas estimate for executing the transaction publicly. The actual gas used can be different when sending the transaction later, as the state of the smart contract can be different at that time.
 *
 * #### Parameters
 * options?: PayableCallOptions,
 * returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat,
 *
 * #### Returns
 * Promise: The gas amount estimated.
 *
 * ```ts
 * const estimatedGas = await contract.methods.approve('0xdAC17F958D2ee523a2206206994597C13D831ec7', 300)
 *     .estimateGas();
 *
 * ```
 *
 * ### encodeABI
 * Encodes the ABI for this method. The resulting hex string is 32-bit function signature hash plus the passed parameters in Solidity tightly packed format. This can be used to send a transaction, call a method, or pass it into another smart contract’s method as arguments. Set the data field on web3.eth.sendTransaction options as the encodeABI() result and it is the same as calling the contract method with contract.myMethod.send().
 *
 * Some use cases for encodeABI() include: preparing a smart contract transaction for a multisignature wallet, working with offline wallets and cold storage and creating transaction payload for complex smart contract proxy calls.
 *
 * #### Parameters
 * None
 *
 * #### Returns
 * String: The encoded ABI.
 *
 * ```ts
 * const encodedABI = await contract.methods.approve('0xdAC17F958D2ee523a2206206994597C13D831ec7', 300)
 *     .encodeABI();
 *
 * ```
 *

 * ### decodeMethodData
 * Decodes the given ABI-encoded data, revealing both the method name and the parameters used in the smart contract call.
 * This function reverses the encoding process happens at the method `encodeABI`.
 * It's particularly useful for debugging and understanding the interactions with and between smart contracts.
 *
 * #### Parameters
 *
 * - `data` **HexString**: The string of ABI-encoded data that needs to be decoded. This should include the method signature and the encoded parameters.
 *
 * #### Returns
 *
 * - **Object**: This object combines both the decoded parameters and the method name in a readable format. Specifically, the returned object contains:
 *   - `__method__` **String**: The name of the contract method, reconstructed from the ABI.
 *   - `__length__` **Number**: The number of parameters decoded.
 *   - Additional properties representing each parameter by name, as well as their position and values.
 *
 * #### Example
 *
 * Given an ABI-encoded string from a transaction, you can decode this data to identify the method called and the parameters passed.
 * Here's a simplified example:
 *
 *
 * ```typescript
 * const GreeterAbi = [
 * 	{
 * 		inputs: [
 * 			{
 * 				internalType: 'string',
 * 				name: '_greeting',
 * 				type: 'string',
 * 			},
 * 		],
 * 		name: 'setGreeting',
 * 		outputs: [],
 * 		type: 'function',
 * 	},
 * ];
 * const contract = new Contract(GreeterAbi); // Initialize with your contract's ABI
 *
 * // The ABI-encoded data string for "setGreeting('Hello World')"
 * const encodedData =
 * 	'0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000';
 *
 * try {
 * 	const decoded = contract.decodeMethodData(encodedData);
 * 	console.log(decoded.__method__); // Outputs: "setGreeting(string)"
 * 	console.log(decoded); // Outputs the detailed parameter data
 * 	// This tells that the method called was `setGreeting` with a single string parameter "Hello World":
 * 	// {
 * 	//   __method__: 'setGreeting(string)',
 * 	//   __length__: 1,
 * 	//   '0': 'Hello World',
 * 	//   _greeting: 'Hello World'
 * 	// }
 * } catch (error) {
 * 	console.error(error);
 * }
 * ```
 *

 * ### createAccessList
 * This will create an access list a method execution will access when executed in the EVM.
 * Note: You must specify a from address and gas if it’s not specified in options when instantiating parent contract object.
 *
 * #### Parameters
 * options?: PayableCallOptions | NonPayableCallOptions,
 * block?: BlockNumberOrTag,
 *
 * #### Returns
 * Promise: The generated access list for transaction.
 *
 * ```ts
 * const accessList = await contract.methods.approve('0xbEe634C21c16F05B03B704BaE071536121e6cFeA', 300)
 *     .createAccessList({
 *         from: "0x9992695e1053bb737d3cfae4743dcfc4b94f203d"
 *    });
 * ```
 *
 */
class Contract extends web3_core_1.Web3Context {
    get subscriptionManager() {
        return this._subscriptionManager;
    }
    constructor(jsonInterface, addressOrOptionsOrContext, optionsOrContextOrReturnFormat, contextOrReturnFormat, returnFormat) {
        var _a, _b, _c;
        // eslint-disable-next-line no-nested-ternary
        const options = (0, web3_utils_1.isContractInitOptions)(addressOrOptionsOrContext)
            ? addressOrOptionsOrContext
            : (0, web3_utils_1.isContractInitOptions)(optionsOrContextOrReturnFormat)
                ? optionsOrContextOrReturnFormat
                : undefined;
        let contractContext;
        if ((0, utils_js_1.isWeb3ContractContext)(addressOrOptionsOrContext)) {
            contractContext = addressOrOptionsOrContext;
        }
        else if ((0, utils_js_1.isWeb3ContractContext)(optionsOrContextOrReturnFormat)) {
            contractContext = optionsOrContextOrReturnFormat;
        }
        else {
            contractContext = contextOrReturnFormat;
        }
        let provider;
        if (typeof addressOrOptionsOrContext === 'object' &&
            'provider' in addressOrOptionsOrContext) {
            provider = addressOrOptionsOrContext.provider;
        }
        else if (typeof optionsOrContextOrReturnFormat === 'object' &&
            'provider' in optionsOrContextOrReturnFormat) {
            provider = optionsOrContextOrReturnFormat.provider;
        }
        else if (typeof contextOrReturnFormat === 'object' &&
            'provider' in contextOrReturnFormat) {
            provider = contextOrReturnFormat.provider;
        }
        else {
            provider = Contract.givenProvider;
        }
        super(Object.assign(Object.assign({}, contractContext), { provider, registeredSubscriptions: contractSubscriptions }));
        /**
         * Set to true if you want contracts' defaults to sync with global defaults.
         */
        this.syncWithContext = false;
        this._functions = {};
        this._subscriptionManager = new contract_subscription_manager_js_1.ContractSubscriptionManager(super.subscriptionManager, this);
        // Init protected properties
        if (contractContext === null || contractContext === void 0 ? void 0 : contractContext.wallet) {
            this._wallet = contractContext.wallet;
        }
        if (contractContext === null || contractContext === void 0 ? void 0 : contractContext.accountProvider) {
            this._accountProvider = contractContext.accountProvider;
        }
        if (!(0, web3_validator_1.isNullish)(options) &&
            !(0, web3_validator_1.isNullish)(options.data) &&
            !(0, web3_validator_1.isNullish)(options.input) &&
            this.config.contractDataInputFill !== 'both')
            throw new web3_errors_1.ContractTransactionDataAndInputError({
                data: options.data,
                input: options.input,
            });
        this._overloadedMethodAbis = new Map();
        // eslint-disable-next-line no-nested-ternary
        const returnDataFormat = (0, web3_utils_1.isDataFormat)(contextOrReturnFormat)
            ? contextOrReturnFormat
            : (0, web3_utils_1.isDataFormat)(optionsOrContextOrReturnFormat)
                ? optionsOrContextOrReturnFormat
                : returnFormat !== null && returnFormat !== void 0 ? returnFormat : this.defaultReturnFormat;
        const address = typeof addressOrOptionsOrContext === 'string' ? addressOrOptionsOrContext : undefined;
        this.config.contractDataInputFill =
            (_a = options === null || options === void 0 ? void 0 : options.dataInputFill) !== null && _a !== void 0 ? _a : this.config.contractDataInputFill;
        this._parseAndSetJsonInterface(jsonInterface, returnDataFormat);
        if (this.defaultReturnFormat !== returnDataFormat) {
            this.defaultReturnFormat = returnDataFormat;
        }
        if (!(0, web3_validator_1.isNullish)(address)) {
            this._parseAndSetAddress(address, returnDataFormat);
        }
        this.options = {
            address,
            jsonInterface: this._jsonInterface,
            gas: (_b = options === null || options === void 0 ? void 0 : options.gas) !== null && _b !== void 0 ? _b : options === null || options === void 0 ? void 0 : options.gasLimit,
            gasPrice: options === null || options === void 0 ? void 0 : options.gasPrice,
            from: options === null || options === void 0 ? void 0 : options.from,
            input: options === null || options === void 0 ? void 0 : options.input,
            data: options === null || options === void 0 ? void 0 : options.data,
        };
        this.syncWithContext = (_c = options === null || options === void 0 ? void 0 : options.syncWithContext) !== null && _c !== void 0 ? _c : false;
        if (contractContext instanceof web3_core_1.Web3Context) {
            this.subscribeToContextEvents(contractContext);
        }
        Object.defineProperty(this.options, 'address', {
            set: (value) => this._parseAndSetAddress(value, returnDataFormat),
            get: () => this._address,
        });
        Object.defineProperty(this.options, 'jsonInterface', {
            set: (value) => this._parseAndSetJsonInterface(value, returnDataFormat),
            get: () => this._jsonInterface,
        });
        if (contractContext instanceof web3_core_1.Web3Context) {
            contractContext.on(web3_core_1.Web3ConfigEvent.CONFIG_CHANGE, event => {
                // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
                this.setConfig({ [event.name]: event.newValue });
            });
        }
    }
    setTransactionMiddleware(transactionMiddleware) {
        this.transactionMiddleware = transactionMiddleware;
    }
    getTransactionMiddleware() {
        return this.transactionMiddleware;
    }
    /**
     * Subscribe to an event.
     *
     * ```ts
     * await myContract.events.MyEvent([options])
     * ```
     *
     * There is a special event `allEvents` that can be used to subscribe all events.
     *
     * ```ts
     * await myContract.events.allEvents([options])
     * ```
     *
     * @returns - When individual event is accessed will returns {@link ContractBoundEvent} object
     */
    get events() {
        return this._events;
    }
    /**
     * Creates a transaction object for that method, which then can be `called`, `send`, `estimated`, `createAccessList` , or `ABI encoded`.
     *
     * The methods of this smart contract are available through:
     *
     * The name: `myContract.methods.myMethod(123)`
     * The name with parameters: `myContract.methods['myMethod(uint256)'](123)`
     * The signature `myContract.methods['0x58cf5f10'](123)`
     *
     * This allows calling functions with same name but different parameters from the JavaScript contract object.
     *
     * \> The method signature does not provide a type safe interface, so we recommend to use method `name` instead.
     *
     * ```ts
     * // calling a method
     * const result = await myContract.methods.myMethod(123).call({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'});
     *
     * // or sending and using a promise
     * const receipt = await myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'});
     *
     * // or sending and using the events
     * const sendObject = myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'});
     * sendObject.on('transactionHash', function(hash){
     *   ...
     * });
     * sendObject.on('receipt', function(receipt){
     *   ...
     * });
     * sendObject.on('confirmation', function(confirmationNumber, receipt){
     *   ...
     * });
     * sendObject.on('error', function(error, receipt) {
     *   ...
     * });
     * ```
     *
     * @returns - Either returns {@link PayableMethodObject} or {@link NonPayableMethodObject} based on the definitions of the ABI of that contract.
     */
    get methods() {
        return this._methods;
    }
    /**
     * Clones the current contract instance. This doesn't deploy contract on blockchain and only creates a local clone.
     *
     * @returns - The new contract instance.
     *
     * ```ts
     * const contract1 = new web3.eth.Contract(abi, address, {gasPrice: '12345678', from: fromAddress});
     *
     * const contract2 = contract1.clone();
     * contract2.options.address = '0xdAC17F958D2ee523a2206206994597C13D831ec7';
     *
     * (contract1.options.address !== contract2.options.address);
     * > true
     * ```
     */
    clone() {
        let newContract;
        if (this.options.address) {
            newContract = new Contract([...this._jsonInterface, ...this._errorsInterface], this.options.address, {
                gas: this.options.gas,
                gasPrice: this.options.gasPrice,
                from: this.options.from,
                input: this.options.input,
                data: this.options.data,
                provider: this.currentProvider,
                syncWithContext: this.syncWithContext,
                dataInputFill: this.config.contractDataInputFill,
            }, this.getContextObject());
        }
        else {
            newContract = new Contract([...this._jsonInterface, ...this._errorsInterface], {
                gas: this.options.gas,
                gasPrice: this.options.gasPrice,
                from: this.options.from,
                input: this.options.input,
                data: this.options.data,
                provider: this.currentProvider,
                syncWithContext: this.syncWithContext,
                dataInputFill: this.config.contractDataInputFill,
            }, this.getContextObject());
        }
        if (this.context)
            newContract.subscribeToContextEvents(this.context);
        return newContract;
    }
    /**
     * Call this function to deploy the contract to the blockchain. After successful deployment the promise will resolve with a new contract instance.
     *
     * ```ts
     * myContract.deploy({
     *   input: '0x12345...', // data keyword can be used, too.
     *   arguments: [123, 'My String']
     * })
     * .send({
     *   from: '0x1234567890123456789012345678901234567891',
     *   gas: 1500000,
     *   gasPrice: '30000000000000'
     * }, function(error, transactionHash){ ... })
     * .on('error', function(error){ ... })
     * .on('transactionHash', function(transactionHash){ ... })
     * .on('receipt', function(receipt){
     *  console.log(receipt.contractAddress) // contains the new contract address
     * })
     * .on('confirmation', function(confirmationNumber, receipt){ ... })
     * .then(function(newContractInstance){
     *   console.log(newContractInstance.options.address) // instance with the new contract address
     * });
     *
     *
     * // When the data is already set as an option to the contract itself
     * myContract.options.data = '0x12345...';
     *
     * myContract.deploy({
     *   arguments: [123, 'My String']
     * })
     * .send({
     *   from: '0x1234567890123456789012345678901234567891',
     *   gas: 1500000,
     *   gasPrice: '30000000000000'
     * })
     * .then(function(newContractInstance){
     *   console.log(newContractInstance.options.address) // instance with the new contract address
     * });
     *
     *
     * // Simply encoding
     * myContract.deploy({
     *   input: '0x12345...',
     *   arguments: [123, 'My String']
     * })
     * .encodeABI();
     * > '0x12345...0000012345678765432'
     *
     *
     * // decoding
     * myContract.deploy({
     *   input: '0x12345...',
     *   // arguments: [123, 'My Greeting'] if you just need to decode the data, you can skip the arguments
     * })
     * .decodeData('0x12345...0000012345678765432');
     * > {
     *      __method__: 'constructor',
     *      __length__: 2,
     *      '0': '123',
     *      _id: '123',
     *      '1': 'My Greeting',
     *      _greeting: 'My Greeting',
     *   }
     *
     *
     * // Gas estimation
     * myContract.deploy({
     *   input: '0x12345...',
     *   arguments: [123, 'My String']
     * })
     * .estimateGas(function(err, gas){
     *   console.log(gas);
     * });
     * ```
     *
     * @returns - The transaction object
     */
    deploy(deployOptions) {
        return new contract_deployer_method_class_js_1.DeployerMethodClass(this, deployOptions);
    }
    getPastEvents(param1, param2, param3) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const eventName = typeof param1 === 'string' ? param1 : web3_eth_1.ALL_EVENTS;
            const options = 
            // eslint-disable-next-line no-nested-ternary
            typeof param1 !== 'string' && !(0, web3_utils_1.isDataFormat)(param1)
                ? param1
                : !(0, web3_utils_1.isDataFormat)(param2)
                    ? param2
                    : {};
            // eslint-disable-next-line no-nested-ternary
            const returnFormat = (0, web3_utils_1.isDataFormat)(param1)
                ? param1
                : (0, web3_utils_1.isDataFormat)(param2)
                    ? param2
                    : param3 !== null && param3 !== void 0 ? param3 : this.defaultReturnFormat;
            const abi = eventName === 'allEvents' || eventName === web3_eth_1.ALL_EVENTS
                ? web3_eth_1.ALL_EVENTS_ABI
                : this._jsonInterface.find(j => 'name' in j && j.name === eventName);
            if (!abi) {
                throw new web3_errors_1.Web3ContractError(`Event ${String(eventName)} not found.`);
            }
            const { fromBlock, toBlock, topics, address } = (0, encoding_js_1.encodeEventABI)(this.options, abi, options !== null && options !== void 0 ? options : {});
            const logs = yield (0, web3_eth_1.getLogs)(this, { fromBlock, toBlock, topics, address }, returnFormat);
            const decodedLogs = logs
                ? logs.map(log => typeof log === 'string'
                    ? log
                    : (0, web3_eth_1.decodeEventABI)(abi, log, this._jsonInterface, returnFormat))
                : [];
            const filter = (_a = options === null || options === void 0 ? void 0 : options.filter) !== null && _a !== void 0 ? _a : {};
            const filterKeys = Object.keys(filter);
            if (filterKeys.length > 0) {
                return decodedLogs.filter(log => {
                    if (typeof log === 'string')
                        return true;
                    return filterKeys.every((key) => {
                        var _a;
                        if (Array.isArray(filter[key])) {
                            return filter[key].some((v) => String(log.returnValues[key]).toUpperCase() ===
                                String(v).toUpperCase());
                        }
                        const inputAbi = (_a = abi.inputs) === null || _a === void 0 ? void 0 : _a.filter(input => input.name === key)[0];
                        if ((inputAbi === null || inputAbi === void 0 ? void 0 : inputAbi.indexed) && inputAbi.type === 'string') {
                            const hashedIndexedString = (0, web3_utils_1.keccak256)(filter[key]);
                            if (hashedIndexedString === String(log.returnValues[key]))
                                return true;
                        }
                        return (String(log.returnValues[key]).toUpperCase() ===
                            String(filter[key]).toUpperCase());
                    });
                });
            }
            return decodedLogs;
        });
    }
    _parseAndSetAddress(value, returnFormat = this.defaultReturnFormat) {
        this._address = value
            ? (0, web3_utils_1.toChecksumAddress)((0, web3_utils_1.format)({ format: 'address' }, value, returnFormat))
            : value;
    }
    decodeMethodData(data) {
        const methodSignature = data.slice(0, 10);
        const functionsAbis = this._jsonInterface.filter(j => j.type !== 'error');
        const abi = functionsAbis.find(a => methodSignature === (0, web3_eth_abi_1.encodeFunctionSignature)((0, web3_eth_abi_1.jsonInterfaceMethodToString)(a)));
        if (!abi) {
            throw new web3_errors_1.Web3ContractError(`The ABI for the provided method signature ${methodSignature} was not found.`);
        }
        return (0, web3_eth_abi_1.decodeFunctionCall)(abi, data);
    }
    _parseAndSetJsonInterface(abis, returnFormat = this.defaultReturnFormat) {
        var _a, _b;
        this._functions = {};
        this._methods = {};
        this._events = {};
        let result = [];
        const functionsAbi = abis.filter(abi => abi.type !== 'error');
        const errorsAbi = abis.filter(abi => (0, web3_eth_abi_1.isAbiErrorFragment)(abi));
        for (const a of functionsAbi) {
            const abi = Object.assign(Object.assign({}, a), { signature: '' });
            if ((0, web3_eth_abi_1.isAbiFunctionFragment)(abi)) {
                const methodName = (0, web3_eth_abi_1.jsonInterfaceMethodToString)(abi);
                const methodSignature = (0, web3_eth_abi_1.encodeFunctionSignature)(methodName);
                abi.methodNameWithInputs = methodName;
                abi.signature = methodSignature;
                // make constant and payable backwards compatible
                abi.constant =
                    abi.stateMutability === 'view' ||
                        abi.stateMutability === 'pure' ||
                        abi.constant;
                abi.payable = abi.stateMutability === 'payable' || abi.payable;
                this._overloadedMethodAbis.set(abi.name, [
                    ...((_a = this._overloadedMethodAbis.get(abi.name)) !== null && _a !== void 0 ? _a : []),
                    abi,
                ]);
                const abiFragment = (_b = this._overloadedMethodAbis.get(abi.name)) !== null && _b !== void 0 ? _b : [];
                const contractMethod = this._createContractMethod(abiFragment, errorsAbi);
                const exactContractMethod = this._createContractMethod(abiFragment, errorsAbi, true);
                this._functions[methodName] = {
                    signature: methodSignature,
                    method: exactContractMethod,
                };
                // We don't know a particular type of the Abi method so can't type check
                this._methods[abi.name] =
                    contractMethod;
                // We don't know a particular type of the Abi method so can't type check
                this._methods[methodName] =
                    exactContractMethod;
                // We don't know a particular type of the Abi method so can't type check
                this._methods[methodSignature] =
                    exactContractMethod;
            }
            else if ((0, web3_eth_abi_1.isAbiEventFragment)(abi)) {
                const eventName = (0, web3_eth_abi_1.jsonInterfaceMethodToString)(abi);
                const eventSignature = (0, web3_eth_abi_1.encodeEventSignature)(eventName);
                const event = this._createContractEvent(abi, returnFormat);
                abi.signature = eventSignature;
                if (!(eventName in this._events) || abi.name === 'bound') {
                    // It's a private type and we don't want to expose it and no need to check
                    this._events[eventName] = event;
                }
                // It's a private type and we don't want to expose it and no need to check
                this._events[abi.name] = event;
                // It's a private type and we don't want to expose it and no need to check
                this._events[eventSignature] = event;
            }
            result = [...result, abi];
        }
        this._events.allEvents = this._createContractEvent(web3_eth_1.ALL_EVENTS_ABI, returnFormat);
        this._jsonInterface = [...result];
        this._errorsInterface = errorsAbi;
    }
    // eslint-disable-next-line class-methods-use-this
    _getAbiParams(abi, params) {
        var _a;
        try {
            return web3_validator_1.utils.transformJsonDataToAbiFormat((_a = abi.inputs) !== null && _a !== void 0 ? _a : [], params);
        }
        catch (error) {
            throw new web3_errors_1.Web3ContractError(`Invalid parameters for method ${abi.name}: ${error.message}`);
        }
    }
    _createContractMethod(abiArr, errorsAbis, exact = false) {
        const abi = abiArr[abiArr.length - 1];
        return (...params) => {
            var _a, _b, _c;
            let abiParams;
            const abis = (_b = (exact
                ? (_a = this._overloadedMethodAbis
                    .get(abi.name)) === null || _a === void 0 ? void 0 : _a.filter(_abi => _abi.signature === abi.signature)
                : this._overloadedMethodAbis.get(abi.name))) !== null && _b !== void 0 ? _b : [];
            let methodAbi = abis[0];
            const internalErrorsAbis = errorsAbis;
            const arrayOfAbis = abis.filter(_abi => { var _a; return ((_a = _abi.inputs) !== null && _a !== void 0 ? _a : []).length === params.length; });
            if (abis.length === 1 || arrayOfAbis.length === 0) {
                abiParams = this._getAbiParams(methodAbi, params);
                web3_validator_1.validator.validate((_c = abi.inputs) !== null && _c !== void 0 ? _c : [], abiParams);
            }
            else {
                const errors = [];
                // all the methods that have is valid for the given inputs
                const applicableMethodAbi = [];
                for (const _abi of arrayOfAbis) {
                    try {
                        abiParams = this._getAbiParams(_abi, params);
                        web3_validator_1.validator.validate(_abi.inputs, abiParams);
                        applicableMethodAbi.push(_abi);
                    }
                    catch (e) {
                        errors.push(e);
                    }
                }
                if (applicableMethodAbi.length === 1) {
                    [methodAbi] = applicableMethodAbi; // take the first item that is the only item in the array
                }
                else if (applicableMethodAbi.length > 1) {
                    [methodAbi] = applicableMethodAbi; // take the first item in the array
                    console.warn(`Multiple methods found that is compatible with the given inputs.\n\tFound ${applicableMethodAbi.length} compatible methods: ${JSON.stringify(applicableMethodAbi.map(m => `${m.methodNameWithInputs} (signature: ${m.signature})`))} \n\tThe first one will be used: ${methodAbi.methodNameWithInputs}`);
                    // TODO: 5.x Should throw a new error with the list of methods found.
                    // Related issue: https://github.com/web3/web3.js/issues/6923
                    // This is in order to provide an error message when there is more than one method found that fits the inputs.
                    // To do that, replace the pervious line of code with something like the following line:
                    // throw new Web3ValidatorError({ message: 'Multiple methods found',  ... list of applicable methods }));
                }
                if (errors.length === arrayOfAbis.length) {
                    throw new web3_validator_1.Web3ValidatorError(errors);
                }
            }
            const methods = {
                arguments: abiParams,
                call: (options, block) => __awaiter(this, void 0, void 0, function* () {
                    return this._contractMethodCall(methodAbi, abiParams, internalErrorsAbis, options, block);
                }),
                send: (options) => this._contractMethodSend(methodAbi, abiParams, internalErrorsAbis, options),
                populateTransaction: (options, contractOptions) => {
                    var _a, _b;
                    let modifiedContractOptions = contractOptions !== null && contractOptions !== void 0 ? contractOptions : this.options;
                    modifiedContractOptions = Object.assign(Object.assign({}, modifiedContractOptions), { input: undefined, from: (_b = (_a = modifiedContractOptions === null || modifiedContractOptions === void 0 ? void 0 : modifiedContractOptions.from) !== null && _a !== void 0 ? _a : this.defaultAccount) !== null && _b !== void 0 ? _b : undefined });
                    const tx = (0, utils_js_1.getSendTxParams)({
                        abi,
                        params,
                        options: Object.assign(Object.assign({}, options), { dataInputFill: this.config.contractDataInputFill }),
                        contractOptions: modifiedContractOptions,
                    });
                    // @ts-expect-error remove unnecessary field
                    if (tx.dataInputFill) {
                        // @ts-expect-error remove unnecessary field
                        delete tx.dataInputFill;
                    }
                    return tx;
                },
                estimateGas: (options_1, ...args_1) => __awaiter(this, [options_1, ...args_1], void 0, function* (options, returnFormat = this
                    .defaultReturnFormat) {
                    return this.contractMethodEstimateGas({
                        abi: methodAbi,
                        params: abiParams,
                        returnFormat,
                        options,
                    });
                }),
                encodeABI: () => (0, encoding_js_1.encodeMethodABI)(methodAbi, abiParams),
                decodeData: (data) => (0, web3_eth_abi_1.decodeFunctionCall)(methodAbi, data),
                createAccessList: (options, block) => __awaiter(this, void 0, void 0, function* () {
                    return this._contractMethodCreateAccessList(methodAbi, abiParams, internalErrorsAbis, options, block);
                }),
            };
            if (methodAbi.stateMutability === 'payable') {
                return methods;
            }
            return methods;
        };
    }
    _contractMethodCall(abi, params, errorsAbi, options, block) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const tx = (0, utils_js_1.getEthTxCallParams)({
                abi,
                params,
                options: Object.assign(Object.assign({}, options), { dataInputFill: this.config.contractDataInputFill }),
                contractOptions: Object.assign(Object.assign({}, this.options), { from: (_a = this.options.from) !== null && _a !== void 0 ? _a : this.config.defaultAccount }),
            });
            try {
                const result = yield (0, web3_eth_1.call)(this, tx, block, this.defaultReturnFormat);
                return (0, web3_eth_abi_1.decodeFunctionReturn)(abi, result);
            }
            catch (error) {
                if (error instanceof web3_errors_1.ContractExecutionError) {
                    // this will parse the error data by trying to decode the ABI error inputs according to EIP-838
                    (0, web3_eth_abi_1.decodeContractErrorData)(errorsAbi, error.cause);
                }
                throw error;
            }
        });
    }
    _contractMethodCreateAccessList(abi, params, errorsAbi, options, block) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const tx = (0, utils_js_1.getCreateAccessListParams)({
                abi,
                params,
                options: Object.assign(Object.assign({}, options), { dataInputFill: this.config.contractDataInputFill }),
                contractOptions: Object.assign(Object.assign({}, this.options), { from: (_a = this.options.from) !== null && _a !== void 0 ? _a : this.config.defaultAccount }),
            });
            try {
                return (0, web3_eth_1.createAccessList)(this, tx, block, this.defaultReturnFormat);
            }
            catch (error) {
                if (error instanceof web3_errors_1.ContractExecutionError) {
                    // this will parse the error data by trying to decode the ABI error inputs according to EIP-838
                    (0, web3_eth_abi_1.decodeContractErrorData)(errorsAbi, error.cause);
                }
                throw error;
            }
        });
    }
    _contractMethodSend(abi, params, errorsAbi, options, contractOptions) {
        var _a, _b;
        let modifiedContractOptions = contractOptions !== null && contractOptions !== void 0 ? contractOptions : this.options;
        modifiedContractOptions = Object.assign(Object.assign({}, modifiedContractOptions), { input: undefined, from: (_b = (_a = modifiedContractOptions.from) !== null && _a !== void 0 ? _a : this.defaultAccount) !== null && _b !== void 0 ? _b : undefined });
        const tx = (0, utils_js_1.getSendTxParams)({
            abi,
            params,
            options: Object.assign(Object.assign({}, options), { dataInputFill: this.config.contractDataInputFill }),
            contractOptions: modifiedContractOptions,
        });
        const transactionToSend = (0, web3_validator_1.isNullish)(this.transactionMiddleware)
            ? (0, web3_eth_1.sendTransaction)(this, tx, this.defaultReturnFormat, {
                // TODO Should make this configurable by the user
                checkRevertBeforeSending: false,
                contractAbi: this._jsonInterface, // explicitly not passing middleware so if some one is using old eth package it will not break
            })
            : (0, web3_eth_1.sendTransaction)(this, tx, this.defaultReturnFormat, {
                // TODO Should make this configurable by the user
                checkRevertBeforeSending: false,
                contractAbi: this._jsonInterface,
            }, this.transactionMiddleware);
        // eslint-disable-next-line no-void
        void transactionToSend.on('error', (error) => {
            if (error instanceof web3_errors_1.ContractExecutionError) {
                // this will parse the error data by trying to decode the ABI error inputs according to EIP-838
                (0, web3_eth_abi_1.decodeContractErrorData)(errorsAbi, error.cause);
            }
        });
        return transactionToSend;
    }
    contractMethodEstimateGas(_a) {
        return __awaiter(this, arguments, void 0, function* ({ abi, params, returnFormat, options, contractOptions, }) {
            const tx = (0, utils_js_1.getEstimateGasParams)({
                abi,
                params,
                options: Object.assign(Object.assign({}, options), { dataInputFill: this.config.contractDataInputFill }),
                contractOptions: contractOptions !== null && contractOptions !== void 0 ? contractOptions : this.options,
            });
            return (0, web3_eth_1.estimateGas)(this, tx, web3_types_1.BlockTags.LATEST, returnFormat !== null && returnFormat !== void 0 ? returnFormat : this.defaultReturnFormat);
        });
    }
    // eslint-disable-next-line class-methods-use-this
    _createContractEvent(abi, returnFormat = this.defaultReturnFormat) {
        return (...params) => {
            var _a;
            const { topics, fromBlock } = (0, encoding_js_1.encodeEventABI)(this.options, abi, params[0]);
            const sub = new contract_log_subscription_js_1.ContractLogsSubscription({
                address: this.options.address,
                topics,
                abi,
                jsonInterface: this._jsonInterface,
            }, {
                subscriptionManager: this.subscriptionManager,
                returnFormat,
            });
            if (!(0, web3_validator_1.isNullish)(fromBlock)) {
                // emit past events when fromBlock is defined
                this.getPastEvents(abi.name, { fromBlock, topics }, returnFormat)
                    .then(logs => {
                    if (logs) {
                        logs.forEach(log => sub.emit('data', log));
                    }
                })
                    .catch((error) => {
                    sub.emit('error', new web3_errors_1.SubscriptionError('Failed to get past events.', error));
                });
            }
            (_a = this.subscriptionManager) === null || _a === void 0 ? void 0 : _a.addSubscription(sub).catch((error) => {
                sub.emit('error', new web3_errors_1.SubscriptionError('Failed to subscribe.', error));
            });
            return sub;
        };
    }
    subscribeToContextEvents(context) {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const contractThis = this;
        this.context = context;
        if (contractThis.syncWithContext) {
            context.on(web3_core_1.Web3ConfigEvent.CONFIG_CHANGE, event => {
                contractThis.setConfig({ [event.name]: event.newValue });
            });
        }
    }
}
exports.Contract = Contract;
//# sourceMappingURL=contract.js.map