"use strict";
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());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EthersMulticall = exports.isMulticallUnderlyingError = void 0;
const dataloader_1 = __importDefault(require("dataloader"));
const ethers_1 = require("ethers");
const utils_1 = require("ethers/lib/utils");
const multicall_contract_1 = require("./multicall.contract");
const isMulticallUnderlyingError = (err) => err.message.includes('Multicall call failed for');
exports.isMulticallUnderlyingError = isMulticallUnderlyingError;
const DEFAULT_DATALOADER_OPTIONS = { cache: false, maxBatchSize: 250 };
class EthersMulticall {
    constructor(multicall, dataLoaderOptions = DEFAULT_DATALOADER_OPTIONS, { beforeCallHook } = {}) {
        this.multicall = multicall;
        this.dataLoader = new dataloader_1.default(this.doCalls.bind(this), dataLoaderOptions);
        this.beforeCallHook = beforeCallHook;
    }
    get contract() {
        return this.multicall;
    }
    doCalls(calls) {
        return __awaiter(this, void 0, void 0, function* () {
            const callRequests = calls.map(call => ({
                target: call.address,
                callData: new utils_1.Interface([]).encodeFunctionData(call.fragment, call.params),
            }));
            if (this.beforeCallHook)
                this.beforeCallHook(calls, callRequests);
            const res = yield this.multicall.callStatic.aggregate(callRequests, false);
            if (res.returnData.length !== callRequests.length) {
                throw new Error(`Unexpected response length: received ${res.returnData.length}; expected ${callRequests.length}`);
            }
            const result = calls.map((call, i) => {
                const signature = utils_1.FunctionFragment.from(call.fragment).format();
                const callIdentifier = [call.address, signature].join(':');
                const [success, data] = res.returnData[i];
                if (!success) {
                    const error = new Error(`Multicall call failed for ${callIdentifier}`);
                    error.stack = call.stack;
                    return error;
                }
                try {
                    const outputs = call.fragment.outputs;
                    const result = new utils_1.Interface([]).decodeFunctionResult(call.fragment, data);
                    return outputs.length === 1 ? result[0] : result;
                }
                catch (err) {
                    const error = new Error(`Multicall call failed for ${callIdentifier}: ${err.message} (decode)`);
                    error.stack = call.stack;
                    return error;
                }
            });
            return result;
        });
    }
    wrap(contract) {
        var _a;
        const abi = contract.interface.fragments;
        const stack = (_a = new Error().stack) === null || _a === void 0 ? void 0 : _a.split('\n').slice(1).join('\n');
        const multicallContract = new multicall_contract_1.MulticallContract(contract.address, abi, stack);
        const funcs = abi.reduce((memo, frag) => {
            if (frag.type !== 'function')
                return memo;
            const funcFrag = frag;
            if (!['pure', 'view'].includes(funcFrag.stateMutability))
                return memo;
            const multicallFunc = multicallContract[funcFrag.name].bind(multicallContract);
            const newFunc = (...args) => {
                const contractCall = multicallFunc(...args);
                return this.dataLoader.load(contractCall);
            };
            memo[funcFrag.name] = newFunc;
            return memo;
        }, {});
        return Object.setPrototypeOf(Object.assign(Object.assign({}, contract), funcs), ethers_1.Contract.prototype);
    }
}
exports.EthersMulticall = EthersMulticall;
exports.default = EthersMulticall;
//# sourceMappingURL=multicall.ethers.js.map