import { __assign, __awaiter, __generator } from "tslib";
import { isL2Provider as isOptimismL2Provider } from "@eth-optimism/sdk/dist/l2-provider";
import { VoidSigner } from "ethers";
import { Coingecko } from "../../coingecko";
import { CHAIN_IDs } from "../../constants";
import { SpokePool__factory } from "../../typechain";
import { populateV3Relay } from "../../arch/evm";
import { BigNumber, toBNWei, bnZero, chainIsOPStack, fixedPointAdjustment, } from "../../utils";
import assert from "assert";
import { getDefaultRelayer } from "../relayFeeCalculator";
import { getGasPriceEstimate } from "../../gasPriceOracle";
/**
 * A unified QueryBase for querying gas costs, token prices, and decimals of various tokens
 * on a blockchain.
 */
var QueryBase = /** @class */ (function () {
    /**
     * Instantiates a QueryBase instance
     * @param provider A valid Ethers.js provider
     * @param symbolMapping A mapping to valid ERC20 tokens and their respective characteristics
     * @param spokePoolAddress The valid address of the Spoke Pool deployment
     * @param simulatedRelayerAddress The address that these queries will reference as the sender. Note: This address must be approved for USDC
     * @param logger A logging utility to report logs
     * @param coingeckoProApiKey An optional CoinGecko API key that links to a PRO account
     * @param fixedGasPrice Overrides the gas price with a fixed value. Note: primarily used for the Boba blockchain
     * @param coingeckoBaseCurrency The basis currency that CoinGecko will use to resolve pricing
     */
    function QueryBase(provider, symbolMapping, spokePoolAddress, simulatedRelayerAddress, logger, coingeckoProApiKey, fixedGasPrice, coingeckoBaseCurrency) {
        if (coingeckoBaseCurrency === void 0) { coingeckoBaseCurrency = "eth"; }
        this.provider = provider;
        this.symbolMapping = symbolMapping;
        this.spokePoolAddress = spokePoolAddress;
        this.simulatedRelayerAddress = simulatedRelayerAddress;
        this.logger = logger;
        this.coingeckoProApiKey = coingeckoProApiKey;
        this.fixedGasPrice = fixedGasPrice;
        this.coingeckoBaseCurrency = coingeckoBaseCurrency;
        this.spokePool = SpokePool__factory.connect(spokePoolAddress, provider);
    }
    /**
     * Retrieves the current gas costs of performing a fillRelay contract at the referenced SpokePool.
     * @param relayData RelayData instance, supplemented with destinationChainId
     * @param relayerAddress Relayer address to simulate with.
     * @param options
     * @param options.gasPrice Optional gas price to use for the simulation.
     * @param options.gasUnits Optional gas units to use for the simulation.
     * @param options.transport Optional transport object for custom gas price retrieval.
     * @returns The gas estimate for this function call (multiplied with the optional buffer).
     */
    QueryBase.prototype.getGasCosts = function (relayData, relayer, options) {
        if (relayer === void 0) { relayer = getDefaultRelayer(relayData.destinationChainId); }
        if (options === void 0) { options = {}; }
        return __awaiter(this, void 0, void 0, function () {
            var _a, gasPrice, gasUnits, baseFeeMultiplier, priorityFeeMultiplier, opStackL1GasCostMultiplier, transport, recipient, outputToken, exclusiveRelayer, tx, _b, nativeGasCost, tokenGasCost, impliedGasPrice, opStackL1GasCost;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        _a = options.gasPrice, gasPrice = _a === void 0 ? this.fixedGasPrice : _a, gasUnits = options.gasUnits, baseFeeMultiplier = options.baseFeeMultiplier, priorityFeeMultiplier = options.priorityFeeMultiplier, opStackL1GasCostMultiplier = options.opStackL1GasCostMultiplier, transport = options.transport;
                        recipient = relayData.recipient, outputToken = relayData.outputToken, exclusiveRelayer = relayData.exclusiveRelayer;
                        assert(recipient.isEVM(), "getGasCosts: recipient not an EVM address (".concat(recipient, ")"));
                        assert(outputToken.isEVM(), "getGasCosts: outputToken not an EVM address (".concat(outputToken, ")"));
                        assert(exclusiveRelayer.isEVM(), "getGasCosts: exclusiveRelayer not an EVM address (".concat(exclusiveRelayer, ")"));
                        return [4 /*yield*/, this.getUnsignedTxFromDeposit(__assign(__assign({}, relayData), { recipient: recipient, outputToken: outputToken, exclusiveRelayer: exclusiveRelayer }), relayer)];
                    case 1:
                        tx = _c.sent();
                        return [4 /*yield*/, this.estimateGas(tx, relayer, this.provider, {
                                gasPrice: gasPrice,
                                gasUnits: gasUnits,
                                baseFeeMultiplier: baseFeeMultiplier,
                                priorityFeeMultiplier: priorityFeeMultiplier,
                                opStackL1GasCostMultiplier: opStackL1GasCostMultiplier,
                                transport: transport,
                            })];
                    case 2:
                        _b = _c.sent(), nativeGasCost = _b.nativeGasCost, tokenGasCost = _b.tokenGasCost, impliedGasPrice = _b.gasPrice, opStackL1GasCost = _b.opStackL1GasCost;
                        return [2 /*return*/, {
                                nativeGasCost: nativeGasCost,
                                tokenGasCost: tokenGasCost,
                                gasPrice: impliedGasPrice,
                                opStackL1GasCost: opStackL1GasCost,
                            }];
                }
            });
        });
    };
    /**
     * @notice Return ethers.PopulatedTransaction for a fill based on input deposit args
     * @param relayData RelayData instance, supplemented with destinationChainId
     * @param relayer Sender of PopulatedTransaction
     * @returns PopulatedTransaction
     */
    QueryBase.prototype.getUnsignedTxFromDeposit = function (relayData, relayer) {
        if (relayer === void 0) { relayer = getDefaultRelayer(relayData.destinationChainId); }
        return populateV3Relay(this.spokePool, relayData, relayer);
    };
    /**
     * @notice Return the gas cost of a simulated transaction
     * @param relayData RelayData instance, supplemented with destinationChainId
     * @param relayer Sender of PopulatedTransaction
     * @returns Estimated gas cost based on ethers.VoidSigner's gas estimation
     */
    QueryBase.prototype.getNativeGasCost = function (relayData, relayer) {
        if (relayer === void 0) { relayer = getDefaultRelayer(relayData.destinationChainId); }
        return __awaiter(this, void 0, void 0, function () {
            var recipient, outputToken, exclusiveRelayer, unsignedTx, voidSigner;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        recipient = relayData.recipient, outputToken = relayData.outputToken, exclusiveRelayer = relayData.exclusiveRelayer;
                        assert(recipient.isEVM(), "getNativeGasCost: recipient not an EVM address (".concat(recipient, ")"));
                        assert(outputToken.isEVM(), "getNativeGasCost: outputToken not an EVM address (".concat(outputToken, ")"));
                        assert(exclusiveRelayer.isEVM(), "getNativeGasCost: exclusiveRelayer not an EVM address (".concat(exclusiveRelayer, ")"));
                        return [4 /*yield*/, this.getUnsignedTxFromDeposit(__assign(__assign({}, relayData), { recipient: recipient, outputToken: outputToken, exclusiveRelayer: exclusiveRelayer }), relayer)];
                    case 1:
                        unsignedTx = _a.sent();
                        voidSigner = new VoidSigner(relayer.toEvmAddress(), this.provider);
                        return [2 /*return*/, voidSigner.estimateGas(unsignedTx)];
                }
            });
        });
    };
    /**
     * @notice Return L1 data fee for OP stack L2 transaction, which is based on L2 calldata.
     * @dev https://docs.optimism.io/stack/transactions/fees#l1-data-fee
     * @param unsignedTx L2 transaction that you want L1 data fee for
     * @param relayer Sender of unsignedTx
     * @param options Specify gas units to avoid additional gas estimation call and multiplier for L1 data fee
     * @returns BigNumber L1 data fee in gas units
     */
    QueryBase.prototype.getOpStackL1DataFee = function (unsignedTx, relayer, options) {
        if (relayer === void 0) { relayer = getDefaultRelayer(unsignedTx.chainId); }
        return __awaiter(this, void 0, void 0, function () {
            var _a, opStackL2GasUnits, _b, opStackL1DataFeeMultiplier, chainId, voidSigner, populatedTransaction, l1DataFee;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        _a = options || {}, opStackL2GasUnits = _a.opStackL2GasUnits, _b = _a.opStackL1DataFeeMultiplier, opStackL1DataFeeMultiplier = _b === void 0 ? toBNWei("1") : _b;
                        return [4 /*yield*/, this.provider.getNetwork()];
                    case 1:
                        chainId = (_c.sent()).chainId;
                        assert(isOptimismL2Provider(this.provider), "Unexpected provider for chain ID ".concat(chainId, "."));
                        voidSigner = new VoidSigner(relayer.toEvmAddress(), this.provider);
                        return [4 /*yield*/, voidSigner.populateTransaction(__assign(__assign({}, unsignedTx), { gasLimit: opStackL2GasUnits }))];
                    case 2:
                        populatedTransaction = _c.sent();
                        return [4 /*yield*/, this.provider.estimateL1GasCost(populatedTransaction)];
                    case 3:
                        l1DataFee = _c.sent();
                        return [2 /*return*/, l1DataFee.mul(opStackL1DataFeeMultiplier).div(fixedPointAdjustment)];
                }
            });
        });
    };
    /**
     * Estimates the total gas cost required to submit an unsigned (populated) transaction on-chain.
     * @param unsignedTx The unsigned transaction that this function will estimate.
     * @param senderAddress The address that the transaction will be submitted from.
     * @param provider A valid ethers provider - will be used to reason the gas price.
     * @param options
     * @param options.gasPrice A manually provided gas price - if set, this function will not resolve the current gas price.
     * @param options.gasUnits A manually provided gas units - if set, this function will not estimate the gas units.
     * @param options.transport A custom transport object for custom gas price retrieval.
     * @returns Estimated cost in units of gas and the underlying gas token (gasPrice * estimatedGasUnits).
     */
    QueryBase.prototype.estimateGas = function (unsignedTx, senderAddress, provider, options) {
        if (options === void 0) { options = {}; }
        return __awaiter(this, void 0, void 0, function () {
            var _a, _gasPrice, gasUnits, _b, baseFeeMultiplier, _c, priorityFeeMultiplier, _d, opStackL1GasCostMultiplier, transport, chainId, voidSigner, queries, _e, nativeGasCost, gasPriceEstimate, gasPrice, tokenGasCost, opStackL1GasCost, l2GasCost;
            return __generator(this, function (_f) {
                switch (_f.label) {
                    case 0:
                        _a = options || {}, _gasPrice = _a.gasPrice, gasUnits = _a.gasUnits, _b = _a.baseFeeMultiplier, baseFeeMultiplier = _b === void 0 ? toBNWei("1") : _b, _c = _a.priorityFeeMultiplier, priorityFeeMultiplier = _c === void 0 ? toBNWei("1") : _c, _d = _a.opStackL1GasCostMultiplier, opStackL1GasCostMultiplier = _d === void 0 ? toBNWei("1") : _d, transport = _a.transport;
                        return [4 /*yield*/, provider.getNetwork()];
                    case 1:
                        chainId = (_f.sent()).chainId;
                        voidSigner = new VoidSigner(senderAddress.toEvmAddress(), provider);
                        queries = [
                            gasUnits ? Promise.resolve(BigNumber.from(gasUnits)) : voidSigner.estimateGas(unsignedTx),
                            _gasPrice
                                ? Promise.resolve({ maxFeePerGas: _gasPrice })
                                : getGasPriceEstimate(provider, { chainId: chainId, baseFeeMultiplier: baseFeeMultiplier, priorityFeeMultiplier: priorityFeeMultiplier, transport: transport, unsignedTx: unsignedTx }),
                        ];
                        return [4 /*yield*/, Promise.all(queries)];
                    case 2:
                        _e = _f.sent(), nativeGasCost = _e[0], gasPriceEstimate = _e[1];
                        gasPrice = gasPriceEstimate.maxFeePerGas;
                        assert(nativeGasCost.gt(bnZero), "Gas cost should not be 0");
                        if (!chainIsOPStack(chainId)) return [3 /*break*/, 4];
                        return [4 /*yield*/, this.getOpStackL1DataFee(unsignedTx, senderAddress, {
                                opStackL2GasUnits: nativeGasCost,
                                opStackL1DataFeeMultiplier: opStackL1GasCostMultiplier,
                            })];
                    case 3:
                        opStackL1GasCost = _f.sent();
                        l2GasCost = nativeGasCost.mul(gasPrice);
                        tokenGasCost = opStackL1GasCost.add(l2GasCost);
                        return [3 /*break*/, 5];
                    case 4:
                        tokenGasCost = nativeGasCost.mul(gasPrice);
                        _f.label = 5;
                    case 5: return [2 /*return*/, {
                            nativeGasCost: nativeGasCost,
                            tokenGasCost: tokenGasCost,
                            gasPrice: BigNumber.from(gasPrice.toString()),
                            opStackL1GasCost: opStackL1GasCost,
                        }];
                }
            });
        });
    };
    /**
     * Retrieves the current price of a token
     * @param tokenSymbol A valid [CoinGecko-ID](https://api.coingecko.com/api/v3/coins/list)
     * @returns The resolved token price within the specified coingeckoBaseCurrency
     */
    QueryBase.prototype.getTokenPrice = function (tokenSymbol) {
        return __awaiter(this, void 0, void 0, function () {
            var coingeckoInstance, _a, price;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        if (!this.symbolMapping[tokenSymbol])
                            throw new Error("".concat(tokenSymbol, " does not exist in mapping"));
                        coingeckoInstance = Coingecko.get(this.logger, this.coingeckoProApiKey);
                        return [4 /*yield*/, coingeckoInstance.getCurrentPriceByContract(this.symbolMapping[tokenSymbol].addresses[CHAIN_IDs.MAINNET], this.coingeckoBaseCurrency)];
                    case 1:
                        _a = _b.sent(), price = _a[1];
                        return [2 /*return*/, price];
                }
            });
        });
    };
    return QueryBase;
}());
export { QueryBase };
//# sourceMappingURL=baseQuery.js.map