"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PricerLogic = void 0;
const ethers_1 = require("ethers");
const math_1 = require("../utils/math");
const logger_1 = require("../utils/logger");
const helpers_1 = require("../utils/helpers");
const types_1 = require("../types");
/**
 * Represents a utility for pricing assets, evaluating orders, and assessing deals for profitability.
 */
class PricerLogic {
    config;
    constructor(_config) {
        this.config = _config;
    }
    /**
     * Calculates the price of asset A in terms of asset B, and ensures that the result
     * always has a fractional part of exactly 18 decimals.
     *
     * @param {BigNumber} priceA
     * @param {BigNumber} priceB
     * @return {BigNumber}
     */
    static calculatePriceAinBOn18Decimals(priceA, priceB, decimals = 18) {
        if (priceB.isZero()) {
            return ethers_1.BigNumber.from(0);
        }
        // Calculate the price of A in B, ensuring precision.
        const priceAinB = priceA.mul(ethers_1.BigNumber.from(10).pow(decimals)).div(priceB);
        const priceAinBAsFloat = (0, math_1.BNtoFloat)(priceAinB);
        return this.parsePriceStringToBigNumberOn18Decimals(priceAinBAsFloat.toFixed(18));
    }
    /**
     * Takes a string representation of a price and converts it into a BigNumber format,
     * ensuring that the fractional part is extended or truncated to exactly 18 decimals.
     *
     * @param {string} price
     * @param {number} decimals - The number of decimals to use for the fractional part.
     * @return {BigNumber}
     */
    static parsePriceStringToBigNumberOn18Decimals(price, decimals = 18) {
        return (0, math_1.parsePriceStringToBigNumberOn18DecimalsExt)(price, decimals);
    }
    parsePriceStringToBigNumberOn18DecimalsInfallible(price) {
        try {
            return PricerLogic.parsePriceStringToBigNumberOn18Decimals(price);
        }
        catch (error) {
            logger_1.logger.error({ error }, 'Failed to parse price string to BigNumber');
            return ethers_1.BigNumber.from(0);
        }
    }
    /**
     * Fetches the price of an asset in USD from the network.
     */
    static getAssetPriceAinB(priceAssetToSpend, priceAssetToReceive) {
        const priceAinB = PricerLogic.calculatePriceAinBOn18Decimals(PricerLogic.parsePriceStringToBigNumberOn18Decimals(priceAssetToSpend), PricerLogic.parsePriceStringToBigNumberOn18Decimals(priceAssetToReceive));
        return { priceAinB, priceAssetToSpend, priceAssetToReceive };
    }
    async estimateGasLimitsForLifecycle(remoteOrderContract, networkId, lifecycle, amount, assetAddress) {
        const latestTimestamp = Math.floor(Date.now() / 1000) - 5 * 60;
        const address = ethers_1.ethers.constants.AddressZero;
        const orderId = (0, helpers_1.generateOrderId)(networkId, address, latestTimestamp);
        const txParams = {};
        switch (lifecycle) {
            case types_1.OrderLifecycleCost.OrderCreatedNative:
                return await this.estimateOrderCreateGasLimit(remoteOrderContract, networkId, amount, ethers_1.ethers.constants.AddressZero, address, txParams);
            case types_1.OrderLifecycleCost.OrderCreatedERC20:
                return await this.estimateOrderCreateGasLimit(remoteOrderContract, networkId, amount, assetAddress, // FixMe: pass the correct example ERC20 asset for each network
                address, txParams);
            case types_1.OrderLifecycleCost.ConfirmedNative:
                return await this.estimateTxConfirmationGasLimit(remoteOrderContract, networkId, amount, ethers_1.ethers.constants.AddressZero, address, orderId, latestTimestamp, address, networkId, txParams);
            case types_1.OrderLifecycleCost.ConfirmedERC20:
                return await this.estimateTxConfirmationGasLimit(remoteOrderContract, networkId, amount, assetAddress, address, orderId, latestTimestamp, address, networkId, txParams);
            case types_1.OrderLifecycleCost.ClaimedERC20:
                return this.estimateOrderClaimedOrRefundedGasLimit(ethers_1.ethers.constants.AddressZero);
            case types_1.OrderLifecycleCost.ClaimedNative:
                return this.estimateOrderClaimedOrRefundedGasLimit(assetAddress);
            default:
                logger_1.logger.error({ lifecycle, networkId }, `Unrecognized lifecycle: ${lifecycle}. Assigning default gas limit.`);
                return ethers_1.BigNumber.from(this.config.pricer.ethTransferGasLimit);
        }
    }
    async estimateOrderCreateGasLimit(remoteOrderContract, networkId, amount, assetAddress, address, txParams) {
        let gasLimit;
        const defaultGasLimit = assetAddress === ethers_1.ethers.constants.AddressZero
            ? this.config.pricer.ethTransferGasLimit
            : this.config.pricer.erc20TransferGasLimit;
        if (this.config.pricer.useDefaultGasLimitNoEstimate) {
            (0, logger_1.batchLogger)({}, '🍌⛽ Using default gas limit for OrderCreated.', 10);
            return ethers_1.BigNumber.from(defaultGasLimit);
        }
        try {
            const maxReward = amount;
            const insurance = ethers_1.BigNumber.from(0);
            const targetAccount = `0x000000000000000000000000${address.slice(2)}`;
            gasLimit = await remoteOrderContract[networkId].estimateGas.order((0, helpers_1.encodeNetworkId)(networkId), assetAddress, targetAccount, amount, assetAddress, insurance, maxReward, txParams);
        }
        catch (error) {
            const defaultGasLimit = assetAddress === ethers_1.ethers.constants.AddressZero
                ? this.config.pricer.ethTransferGasLimit
                : this.config.pricer.erc20TransferGasLimit;
            logger_1.logger.warn({ error: error.message, defaultGasLimit, networkId, lifecycle: 'OrderL3' }, '🍌⛽ Could not retrieve OrderL3 Gas Limit. Assigning default.');
            gasLimit = ethers_1.BigNumber.from(defaultGasLimit);
        }
        return gasLimit;
    }
    estimateOrderClaimedOrRefundedGasLimit(assetAddress) {
        return assetAddress === ethers_1.ethers.constants.AddressZero
            ? ethers_1.BigNumber.from(this.config.pricer.ethClaimGasLimit.toString())
            : ethers_1.BigNumber.from(this.config.pricer.erc20ClaimGasLimit.toString());
    }
    async estimateTxConfirmationGasLimit(remoteOrderContract, networkId, amount, assetAddress, address, orderId, nonce, sourceAccount, source, txParams) {
        let gasLimit;
        const defaultGasLimit = assetAddress === ethers_1.ethers.constants.AddressZero
            ? this.config.pricer.ethTransferGasLimit
            : this.config.pricer.erc20TransferGasLimit;
        if (this.config.pricer.useDefaultGasLimitNoEstimate) {
            (0, logger_1.batchLogger)({}, '🍌⛽ Using default gas limit for ConfirmOrderV3.', 10);
            return ethers_1.BigNumber.from(defaultGasLimit);
        }
        try {
            gasLimit = await remoteOrderContract[networkId].estimateGas.confirmOrderV3(orderId, address, amount, assetAddress, nonce, sourceAccount, (0, helpers_1.encodeNetworkId)(source), txParams);
        }
        catch (error) {
            logger_1.logger.warn({ error: error.message, defaultGasLimit, networkId, lifecycle: 'Confirmation' }, '🍌⛽ Could not retrieve Confirmation Gas Limit. Assigning default.');
            gasLimit = ethers_1.BigNumber.from(defaultGasLimit);
        }
        return gasLimit;
    }
    static estimateReceivedAmount(rewardAmountWei, priceOfRewardInAsset, decimals) {
        return rewardAmountWei.mul(priceOfRewardInAsset).div(ethers_1.BigNumber.from(10).pow(decimals));
    }
    static estimateSpentAmount(rewardAmountWei, priceOfRewardInAsset, decimals) {
        return rewardAmountWei.mul(ethers_1.BigNumber.from(10).pow(decimals)).div(priceOfRewardInAsset);
    }
    /**
     * Calculates the bonuses and fees for a transaction and returns the final estimation result.
     */
    static convertBonusesAndFeesToUSD(initialAmountWei, estimatedReceivedAmountWei, totalCostInRewardWei, sourceGasFeeInRewardWei, executorFeeInRewardWei, protocolFeeInRewardWei, destinationGasFeeInFromRewardWei, fromAssetPriceUSD, toAssetPriceUSD, BRNorTRNPrice, currentBonusL3, isMainnet, avgOrderValueLastDayEth, priceOfNativeAssetSource, decimals = 18) {
        // total costs
        const totalCostWei = totalCostInRewardWei;
        // total costs broken down
        const gasFeeWei = sourceGasFeeInRewardWei;
        const executorFeeWei = executorFeeInRewardWei;
        const protocolFeeWei = protocolFeeInRewardWei;
        const initialAmountEth = ethers_1.ethers.utils.formatEther(initialAmountWei);
        let isHighValueOrder = false;
        // is order max reward > last day average value?
        if (parseFloat(initialAmountEth) > avgOrderValueLastDayEth && avgOrderValueLastDayEth > 0) {
            isHighValueOrder = true;
        }
        // Increase BRN bonus by 50% if high value order
        if (isHighValueOrder) {
            currentBonusL3 = currentBonusL3.add(currentBonusL3.div(2));
        }
        const BonusUSD = (0, math_1.BNtoFloat)(PricerLogic.parsePriceStringToBigNumberOn18Decimals(BRNorTRNPrice)
            .mul(currentBonusL3)
            .div(ethers_1.BigNumber.from(10).pow(decimals)));
        const TRNBonusWei = isMainnet ? currentBonusL3 : ethers_1.BigNumber.from(0);
        const BRNBonusWei = !isMainnet ? currentBonusL3 : ethers_1.BigNumber.from(0);
        const TRNBonusUSD = isMainnet ? BonusUSD : 0;
        const BRNBonusUSD = !isMainnet ? BonusUSD : 0;
        const totalRabateWei = TRNBonusWei.add(BRNBonusWei);
        const totalRabateUSD = TRNBonusUSD + BRNBonusUSD;
        const totalCostUSD = PricerLogic.calculateUSD(totalCostWei, fromAssetPriceUSD, decimals);
        const netFeeWei = totalCostWei.sub(totalRabateWei);
        const netFeeUSD = totalCostUSD - totalRabateUSD;
        const estimatedReceivedAmountUSD = PricerLogic.calculateUSD(estimatedReceivedAmountWei, toAssetPriceUSD, decimals);
        const executorFeeUSD = this.calculateUSD(executorFeeWei, fromAssetPriceUSD, decimals);
        const protocolFeeUSD = PricerLogic.calculateUSD(protocolFeeWei, fromAssetPriceUSD, decimals);
        const initialAmountUSD = PricerLogic.calculateUSD(initialAmountWei, fromAssetPriceUSD, decimals);
        const gasFeeUSD = PricerLogic.calculateUSD(gasFeeWei, priceOfNativeAssetSource, decimals);
        const gasFeeWeiDestinationUSD = PricerLogic.calculateUSD(destinationGasFeeInFromRewardWei, fromAssetPriceUSD, decimals);
        // Assume executor fee wei already covers the destination delivery costs + %
        // Bridge Fee being the total cost - submission gas cost
        const bridgeFeeWei = protocolFeeWei.add(executorFeeWei);
        const bridgeFeeUSD = protocolFeeUSD + executorFeeUSD + gasFeeWeiDestinationUSD;
        return {
            initialAmountWei,
            initialAmountWeiFormatted: ethers_1.ethers.utils.formatEther(initialAmountWei),
            initialAmountUSD: initialAmountUSD.toFixed(18),
            estimatedReceivedAmountWei,
            estimatedReceivedAmountWeiFormatted: ethers_1.ethers.utils.formatEther(estimatedReceivedAmountWei),
            estimatedReceivedAmountUSD: estimatedReceivedAmountUSD.toFixed(18),
            totalCostUSD: totalCostUSD.toFixed(18),
            totalCostWei,
            totalCostWeiFormatted: ethers_1.ethers.utils.formatEther(totalCostWei),
            totalRabateWei,
            gasFeeWei,
            gasFeeWeiFormatted: ethers_1.ethers.utils.formatEther(gasFeeWei),
            gasFeeWeiUSD: gasFeeUSD.toFixed(18),
            netFeeWei,
            netFeeWeiFormatted: ethers_1.ethers.utils.formatEther(netFeeWei),
            netFeeUSD: netFeeUSD.toFixed(18),
            executorFeeWei,
            executorFeeWeiFormatted: ethers_1.ethers.utils.formatEther(executorFeeWei),
            executorFeeUSD: executorFeeUSD.toFixed(18),
            bridgeFeeWei,
            bridgeFeeWeiFormatted: ethers_1.ethers.utils.formatEther(bridgeFeeWei),
            bridgeFeeUSD: bridgeFeeUSD.toFixed(18),
            protocolFeeWei,
            protocolFeeWeiFormatted: ethers_1.ethers.utils.formatEther(protocolFeeWei),
            protocolFeeUSD: protocolFeeUSD.toFixed(18),
            BRNBonusWei,
            BRNBonusUSD: BRNBonusUSD.toFixed(18),
            TRNBonusWei,
            TRNBonusUSD: TRNBonusUSD.toFixed(18),
            isHighValueOrder,
            gasFeeWeiDestination: destinationGasFeeInFromRewardWei,
            gasFeeWeiDestinationUSD: gasFeeWeiDestinationUSD.toFixed(18),
            gasFeeWeiDestinationFormatted: ethers_1.ethers.utils.formatEther(destinationGasFeeInFromRewardWei),
        };
    }
    static calculateUSD(valueWei, priceUSD, decimals) {
        const priceFloat = parseFloat(priceUSD); // Don't convert to BigNumber
        const valueEth = Number(valueWei.toString()) / 10 ** decimals; // Convert wei to ETH
        return valueEth * priceFloat; // USD
    }
    static calculateFromUSD(valueUSD, priceUSD, decimals) {
        const valueBN = (0, math_1.parsePriceStringToBigNumberOn18DecimalsExt)(valueUSD.toFixed(18), decimals);
        const priceBN = (0, math_1.parsePriceStringToBigNumberOn18DecimalsExt)(priceUSD, decimals);
        // Ensure that the value is greater than 0 to avoid BigNumber casting decimal
        if (valueUSD <= 0) {
            logger_1.logger.debug({ valueUSD, priceUSD }, '👾 Value in USD is less than or equal to 0');
            return ethers_1.BigNumber.from(0);
        }
        return valueBN.mul(ethers_1.BigNumber.from(10).pow(decimals)).div(priceBN);
    }
    // Calculates the total fees for a transaction.
    // Assumes the common denominator is a single asset price (max reward asset in /estimate)
    static sumUpTotalFees(destinationExecutionGasFeeWei, executorFeeWei = ethers_1.BigNumber.from(0), protocolFeeWei = ethers_1.BigNumber.from(0), gasPriceSpikeFactorPercent = 0) {
        return executorFeeWei
            .add(destinationExecutionGasFeeWei)
            .add(protocolFeeWei)
            .mul(gasPriceSpikeFactorPercent + 100)
            .div(100);
    }
    static calculateEIP1559FeeInWei({ gasPrice, lastBaseFeePerGas, maxFeePerGas, maxPriorityFeePerGas, gasUsed, txType, }) {
        if (txType === 0) {
            return gasPrice.mul(gasUsed);
        }
        const lastBase10PercentAddOn = lastBaseFeePerGas.div(10); // 10% of the last base fee
        const totalFeePerGas = lastBaseFeePerGas.add(lastBase10PercentAddOn);
        const result = totalFeePerGas.mul(gasUsed);
        logger_1.logger.trace({
            lastBaseFeePerGas: ethers_1.ethers.utils.formatUnits(lastBaseFeePerGas, 'gwei'),
            maxFeePerGas: ethers_1.ethers.utils.formatUnits(maxFeePerGas, 'gwei'),
            maxPriorityFeePerGas: ethers_1.ethers.utils.formatUnits(maxPriorityFeePerGas, 'gwei'),
            gasUsed: gasUsed.toString(),
            totalFeePerGas: ethers_1.ethers.utils.formatUnits(totalFeePerGas, 'gwei'),
            result: ethers_1.ethers.utils.formatUnits(result, 'gwei'),
        }, 'Calculated EIP-1559 Fee in Wei');
        return result;
    }
    /**
     * Calculates the cost of a transaction in terms of a given asset.
     * The cost is calculated by summing the gas fee and the executor fee (if available)
     * Returns the cost in Wei, Eth, Usd, and the asset, but also returns the gas fee and the executor fee separately.
     *
     * @param {BigNumber} amountWei
     * @param {string} asset
     * @param {BigNumber} priceAsset
     * @param {BigNumber} priceNative
     * @param totalGasFeeWei
     * @param protocolFeeWei
     * @param executorFeeSpreadOnAmount
     * @param tipAdjustmentWei
     * @returns {CostResult}
     */
    static calculateCostInAsset(amountWei, asset, priceAsset, priceNative, totalGasFeeWei, protocolFeeWei, executorFeeSpreadOnAmount, tipAdjustmentWei, // Flat tip adding to the transaction cost and profits of executor
    gasPriceSpikeFactorPercent = 32, decimals = 18) {
        const gasFeeWei = totalGasFeeWei;
        // Calculate the executor fee
        const executorFeeBps = Math.round(executorFeeSpreadOnAmount * 100);
        const executorFeeOptionalSpreadAddOn = executorFeeBps > 0 ? amountWei.mul(executorFeeBps).div(10_000) : ethers_1.BigNumber.from(0);
        const executorFeeWei = executorFeeOptionalSpreadAddOn.add(tipAdjustmentWei);
        // Final formula
        // Total costs = gas fee + executor fee + protocol fee
        //  Up total cost by the configured % in order to sustain potential gas price spikes during the tx confirmation
        const totalCostInWei = executorFeeWei
            .add(gasFeeWei)
            .add(protocolFeeWei)
            // .mul(this.config.pricer.gasPriceSpikeFactorPercent + 100)
            .mul(gasPriceSpikeFactorPercent + 100)
            .div(100);
        const priceOfNativeInAsset = PricerLogic.calculatePriceAinBOn18Decimals(priceNative, priceAsset);
        const costInAsset = (0, math_1.BNtoFloat)(priceOfNativeInAsset) * (0, math_1.BNtoFloat)(totalCostInWei);
        const costInUsd = (0, math_1.BNtoFloat)(priceNative) * (0, math_1.BNtoFloat)(totalCostInWei);
        logger_1.logger.debug({
            totalCostInWei: ethers_1.ethers.utils.formatEther(totalCostInWei),
            priceOfNativeInAsset: priceOfNativeInAsset.toString(),
            costInAsset: costInAsset.toString(),
            costInUsd: costInUsd.toString(),
        }, 'Calculated costs');
        return {
            costInWei: totalCostInWei,
            costInUsd,
            costInAsset: PricerLogic.parsePriceStringToBigNumberOn18Decimals(costInAsset.toFixed(decimals)),
            executorFeeWei,
            executorFeeOptionalSpreadAddOn,
            gasFeeWei,
            protocolFeeWei,
            asset,
        };
    }
}
exports.PricerLogic = PricerLogic;
//# sourceMappingURL=pricer.logic.service.js.map