"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RelayFeeCalculator = exports.getDefaultRelayer = exports.DEFAULT_LOGGER = exports.expectedCapitalCostsKeys = void 0;
var tslib_1 = require("tslib");
var assert_1 = tslib_1.__importDefault(require("assert"));
var constants_1 = require("../constants");
var utils_1 = require("../utils");
exports.expectedCapitalCostsKeys = ["lowerBound", "upperBound", "cutoff", "decimals"];
exports.DEFAULT_LOGGER = {
    debug: function () {
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        return console.debug(args);
    },
    info: function () {
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        return console.info(args);
    },
    warn: function () {
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        return console.warn(args);
    },
    error: function () {
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        return console.error(args);
    },
};
function getDefaultRelayer(chainId) {
    return (0, utils_1.isDefined)(chainId) && (0, utils_1.chainIsSvm)(chainId)
        ? utils_1.SvmAddress.from(constants_1.DEFAULT_SIMULATED_RELAYER_ADDRESS_SVM)
        : utils_1.EvmAddress.from(constants_1.DEFAULT_SIMULATED_RELAYER_ADDRESS);
}
exports.getDefaultRelayer = getDefaultRelayer;
var safeOutputAmount = (0, utils_1.toBN)(100);
var RelayFeeCalculator = (function () {
    function RelayFeeCalculator(config, logger, destinationChainId) {
        (0, assert_1.default)(config, "config must be provided");
        if ("queries" in config) {
            this.queries = config.queries;
        }
        else {
            (0, assert_1.default)(destinationChainId !== undefined, "destinationChainId must be provided if queriesMap is provided");
            (0, assert_1.default)(config.queriesMap[destinationChainId], "No queries provided for destination chain");
            this.queries = config.queriesMap[destinationChainId];
        }
        this.gasDiscountPercent = config.gasDiscountPercent || 0;
        this.capitalDiscountPercent = config.capitalDiscountPercent || 0;
        this.feeLimitPercent = config.feeLimitPercent || 0;
        this.nativeTokenDecimals = config.nativeTokenDecimals || 18;
        (0, assert_1.default)(this.gasDiscountPercent >= 0 && this.gasDiscountPercent <= 100, "gasDiscountPercent must be between 0 and 100 percent");
        (0, assert_1.default)(this.capitalDiscountPercent >= 0 && this.capitalDiscountPercent <= 100, "capitalDiscountPercent must be between 0 and 100 percent");
        (0, assert_1.default)(this.feeLimitPercent >= 0 && this.feeLimitPercent <= 100, "feeLimitPercent must be between 0 and 100 percent");
        this.capitalCostsConfig = Object.fromEntries(Object.entries(config.capitalCostsConfig).map(function (_a) {
            var token = _a[0], capitalCosts = _a[1];
            return [token.toUpperCase(), RelayFeeCalculator.validateAndTransformCapitalCostsConfigOverride(capitalCosts)];
        }));
        (0, assert_1.default)(Object.keys(this.capitalCostsConfig).length > 0, "capitalCostsConfig must have at least one entry");
        this.logger = logger || exports.DEFAULT_LOGGER;
        this.checkAllConfigConflicts();
    }
    RelayFeeCalculator.capitalCostConfigIsOverride = function (config) {
        return config.default !== undefined;
    };
    RelayFeeCalculator.validateAndTransformCapitalCostsConfigOverride = function (capitalCosts) {
        var config = this.capitalCostConfigIsOverride(capitalCosts)
            ? capitalCosts
            : { default: capitalCosts };
        this.validateCapitalCostsConfig(config.default);
        for (var _i = 0, _a = Object.values(config.routeOverrides || {}); _i < _a.length; _i++) {
            var toChainIdRoutes = _a[_i];
            Object.values(toChainIdRoutes).forEach(this.validateCapitalCostsConfig);
        }
        Object.values(config.originChainOverrides || {}).forEach(this.validateCapitalCostsConfig);
        Object.values(config.destinationChainOverrides || {}).forEach(this.validateCapitalCostsConfig);
        return config;
    };
    RelayFeeCalculator.validateCapitalCostsConfig = function (capitalCosts) {
        (0, assert_1.default)((0, utils_1.toBN)(capitalCosts.upperBound).lt((0, utils_1.toBNWei)("1")), "upper bound must be < 100%");
        (0, assert_1.default)(capitalCosts.decimals > 0 && capitalCosts.decimals <= 18, "invalid decimals");
    };
    RelayFeeCalculator.prototype.getTokenPrice = function (tokenSymbol) {
        return this.queries.getTokenPrice(tokenSymbol);
    };
    RelayFeeCalculator.prototype.gasFeePercent = function (deposit, outputAmount, simulateZeroFill, relayerAddress, _tokenPrice, tokenMapping, gasPrice, gasLimit, _tokenGasCost, transport) {
        if (simulateZeroFill === void 0) { simulateZeroFill = false; }
        if (relayerAddress === void 0) { relayerAddress = getDefaultRelayer(deposit.destinationChainId); }
        if (tokenMapping === void 0) { tokenMapping = constants_1.TOKEN_SYMBOLS_MAP; }
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var inputToken, destinationChainId, originChainId, destinationChainTokenDetails, outputToken, outputTokenInfo, inputTokenInfo, simulatedAmount, getGasCosts, _a, tokenGasCost, tokenPrice, gasFeesInToken;
            var _this = this;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        if ((0, utils_1.toBN)(outputAmount).eq(utils_1.bnZero))
                            return [2, utils_1.MAX_BIG_INT];
                        inputToken = deposit.inputToken, destinationChainId = deposit.destinationChainId, originChainId = deposit.originChainId;
                        destinationChainTokenDetails = Object.values(tokenMapping).find(function (details) {
                            return (0, utils_1.compareAddressesSimple)(details.addresses[originChainId], inputToken.toNative()) &&
                                (0, utils_1.isDefined)(details.addresses[destinationChainId]);
                        });
                        outputToken = deposit.outputToken.isZeroAddress()
                            ? (0, utils_1.toAddressType)(destinationChainTokenDetails.addresses[destinationChainId], destinationChainId)
                            : deposit.outputToken;
                        outputTokenInfo = (0, utils_1.getTokenInfo)(outputToken, destinationChainId, tokenMapping);
                        inputTokenInfo = (0, utils_1.getTokenInfo)(inputToken, originChainId, tokenMapping);
                        if (!(0, utils_1.isDefined)(outputTokenInfo) || !(0, utils_1.isDefined)(inputTokenInfo)) {
                            throw new Error("Could not find token information for ".concat(inputToken, " or ").concat(outputToken));
                        }
                        simulatedAmount = simulateZeroFill ? safeOutputAmount : (0, utils_1.toBN)(outputAmount);
                        deposit = tslib_1.__assign(tslib_1.__assign({}, deposit), { outputAmount: simulatedAmount });
                        getGasCosts = this.queries
                            .getGasCosts(deposit, relayerAddress, { gasPrice: gasPrice, gasUnits: gasLimit, transport: transport })
                            .then(function (_a) {
                            var tokenGasCost = _a.tokenGasCost;
                            return tokenGasCost;
                        })
                            .catch(function (error) {
                            _this.logger.error({
                                at: "sdk/gasFeePercent",
                                message: "Error while fetching gas costs",
                                error: error,
                                simulateZeroFill: simulateZeroFill,
                                deposit: deposit,
                            });
                            throw error;
                        });
                        return [4, Promise.all([
                                _tokenGasCost ? Promise.resolve(_tokenGasCost) : getGasCosts,
                                _tokenPrice !== null && _tokenPrice !== void 0 ? _tokenPrice : this.queries.getTokenPrice(outputTokenInfo.symbol).catch(function (error) {
                                    _this.logger.error({
                                        at: "sdk/gasFeePercent",
                                        message: "Error while fetching token price",
                                        error: error,
                                        destinationChainId: deposit.destinationChainId,
                                        inputToken: inputToken,
                                    });
                                    throw error;
                                }),
                            ])];
                    case 1:
                        _a = _b.sent(), tokenGasCost = _a[0], tokenPrice = _a[1];
                        gasFeesInToken = (0, utils_1.nativeToToken)(tokenGasCost, tokenPrice, outputTokenInfo.decimals, this.nativeTokenDecimals);
                        return [2, (0, utils_1.percent)(gasFeesInToken, outputAmount.toString())];
                }
            });
        });
    };
    RelayFeeCalculator.prototype.capitalFeePercent = function (_outputAmount, _tokenSymbol, _originRoute, _destinationRoute) {
        var _a, _b, _c, _d, _e, _f;
        if ((0, utils_1.toBN)(_outputAmount).eq((0, utils_1.toBN)(0)))
            return utils_1.MAX_BIG_INT;
        var tokenCostConfig = this.capitalCostsConfig[_tokenSymbol.toUpperCase()];
        if (!(0, utils_1.isDefined)(tokenCostConfig)) {
            this.logger.error({
                at: "sdk/capitalFeePercent",
                message: "No capital fee available for token ".concat(_tokenSymbol),
            });
            throw new Error("No capital cost config available for token ".concat(_tokenSymbol));
        }
        else {
            var routeOverride = (_b = (_a = tokenCostConfig === null || tokenCostConfig === void 0 ? void 0 : tokenCostConfig.routeOverrides) === null || _a === void 0 ? void 0 : _a[_originRoute || ""]) === null || _b === void 0 ? void 0 : _b[_destinationRoute || ""];
            var destinationChainOverride = (_c = tokenCostConfig === null || tokenCostConfig === void 0 ? void 0 : tokenCostConfig.destinationChainOverrides) === null || _c === void 0 ? void 0 : _c[_destinationRoute || ""];
            var originChainOverride = (_d = tokenCostConfig === null || tokenCostConfig === void 0 ? void 0 : tokenCostConfig.originChainOverrides) === null || _d === void 0 ? void 0 : _d[_originRoute || ""];
            var config = (_f = (_e = routeOverride !== null && routeOverride !== void 0 ? routeOverride : destinationChainOverride) !== null && _e !== void 0 ? _e : originChainOverride) !== null && _f !== void 0 ? _f : tokenCostConfig.default;
            this.warnIfConfigConflicts(_tokenSymbol, _originRoute || "", _destinationRoute || "", routeOverride, destinationChainOverride, originChainOverride);
            var y = (0, utils_1.toBN)(_outputAmount).mul((0, utils_1.toBNWei)("1", 18 - config.decimals));
            var minCharge = (0, utils_1.toBN)(config.lowerBound).mul(y).div(utils_1.fixedPointAdjustment);
            var yTriangle = (0, utils_1.min)(config.cutoff, y);
            var triangleSlope = (0, utils_1.toBN)(config.cutoff).eq((0, utils_1.toBN)(0))
                ? (0, utils_1.toBN)(0)
                : (0, utils_1.toBN)(config.upperBound).sub(config.lowerBound).mul(utils_1.fixedPointAdjustment).div(config.cutoff);
            var triangleHeight = triangleSlope.mul(yTriangle).div(utils_1.fixedPointAdjustment);
            var triangleCharge = triangleHeight.mul(yTriangle).div((0, utils_1.toBNWei)(2));
            var yRemainder = (0, utils_1.max)((0, utils_1.toBN)(0), y.sub(config.cutoff));
            var remainderCharge = yRemainder.mul((0, utils_1.toBN)(config.upperBound).sub(config.lowerBound)).div(utils_1.fixedPointAdjustment);
            return minCharge.add(triangleCharge).add(remainderCharge).mul(utils_1.fixedPointAdjustment).div(y);
        }
    };
    RelayFeeCalculator.prototype.checkAllConfigConflicts = function () {
        var _a, _b, _c, _d;
        var _loop_1 = function (tokenSymbol, tokenConfig) {
            var originChains = new Set(Object.keys(tokenConfig.originChainOverrides || {}));
            var destChains = new Set(Object.keys(tokenConfig.destinationChainOverrides || {}));
            if (tokenConfig.routeOverrides) {
                Object.keys(tokenConfig.routeOverrides).forEach(function (originChain) {
                    originChains.add(originChain);
                    Object.keys(tokenConfig.routeOverrides[originChain]).forEach(function (destChain) {
                        destChains.add(destChain);
                    });
                });
            }
            if (originChains.size === 0 && destChains.size === 0) {
                return "continue";
            }
            for (var _g = 0, _h = Array.from(originChains); _g < _h.length; _g++) {
                var originChain = _h[_g];
                for (var _j = 0, _k = Array.from(destChains); _j < _k.length; _j++) {
                    var destChain = _k[_j];
                    var routeOverride = (_b = (_a = tokenConfig.routeOverrides) === null || _a === void 0 ? void 0 : _a[originChain]) === null || _b === void 0 ? void 0 : _b[destChain];
                    var destinationChainOverride = (_c = tokenConfig.destinationChainOverrides) === null || _c === void 0 ? void 0 : _c[destChain];
                    var originChainOverride = (_d = tokenConfig.originChainOverrides) === null || _d === void 0 ? void 0 : _d[originChain];
                    this_1.warnIfConfigConflicts(tokenSymbol, originChain, destChain, routeOverride, destinationChainOverride, originChainOverride);
                }
            }
        };
        var this_1 = this;
        for (var _i = 0, _e = Object.entries(this.capitalCostsConfig); _i < _e.length; _i++) {
            var _f = _e[_i], tokenSymbol = _f[0], tokenConfig = _f[1];
            _loop_1(tokenSymbol, tokenConfig);
        }
    };
    RelayFeeCalculator.prototype.warnIfConfigConflicts = function (tokenSymbol, originChain, destChain, routeOverride, destinationChainOverride, originChainOverride) {
        var overrideCount = [routeOverride, destinationChainOverride, originChainOverride].filter(Boolean).length;
        if (overrideCount > 1) {
            var configUsed = routeOverride
                ? "route override"
                : destinationChainOverride
                    ? "destination chain override"
                    : originChainOverride
                        ? "origin chain override"
                        : "default override";
            this.logger.warn({
                at: "RelayFeeCalculator",
                message: "Multiple configurations found for token ".concat(tokenSymbol, " from chain ").concat(originChain, " to chain ").concat(destChain),
                configUsed: configUsed,
                routeOverride: routeOverride,
                destinationChainOverride: destinationChainOverride,
                originChainOverride: originChainOverride,
            });
        }
    };
    RelayFeeCalculator.prototype.relayerFeeDetails = function (deposit, outputAmount, simulateZeroFill, relayerAddress, _tokenPrice, gasPrice, gasUnits, tokenGasCost) {
        if (simulateZeroFill === void 0) { simulateZeroFill = false; }
        if (relayerAddress === void 0) { relayerAddress = getDefaultRelayer(deposit.destinationChainId); }
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var inputToken, originChainId, outputToken, destinationChainId, inputTokenInfo, outputTokenInfo, gasFeePercent, outToInDecimals, gasFeeTotal, capitalFeePercent, capitalFeeTotal, relayFeePercent, relayFeeTotal, maxGasFeePercent, minDeposit, isAmountTooLow;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        outputAmount !== null && outputAmount !== void 0 ? outputAmount : (outputAmount = deposit.outputAmount);
                        inputToken = deposit.inputToken, originChainId = deposit.originChainId, outputToken = deposit.outputToken, destinationChainId = deposit.destinationChainId;
                        inputTokenInfo = (0, utils_1.getTokenInfo)(inputToken, originChainId);
                        outputTokenInfo = (0, utils_1.getTokenInfo)(outputToken, destinationChainId);
                        if (!(0, utils_1.isDefined)(inputTokenInfo) || !(0, utils_1.isDefined)(outputTokenInfo)) {
                            throw new Error("Could not find token information for ".concat(inputToken, " or ").concat(outputToken));
                        }
                        return [4, this.gasFeePercent(deposit, outputAmount, simulateZeroFill, relayerAddress, _tokenPrice, undefined, gasPrice, gasUnits, tokenGasCost)];
                    case 1:
                        gasFeePercent = _a.sent();
                        outToInDecimals = (0, utils_1.ConvertDecimals)(outputTokenInfo.decimals, inputTokenInfo.decimals);
                        gasFeeTotal = gasFeePercent.mul(outToInDecimals(outputAmount.toString())).div(utils_1.fixedPointAdjustment);
                        capitalFeePercent = this.capitalFeePercent(outputAmount, inputTokenInfo.symbol, deposit.originChainId.toString(), deposit.destinationChainId.toString());
                        capitalFeeTotal = capitalFeePercent.mul(outToInDecimals(outputAmount.toString())).div(utils_1.fixedPointAdjustment);
                        relayFeePercent = gasFeePercent.add(capitalFeePercent);
                        relayFeeTotal = gasFeeTotal.add(capitalFeeTotal);
                        maxGasFeePercent = (0, utils_1.max)((0, utils_1.toBNWei)(this.feeLimitPercent / 100).sub(capitalFeePercent), (0, utils_1.toBN)(0));
                        if (maxGasFeePercent.eq((0, utils_1.toBN)(0))) {
                            minDeposit = utils_1.MAX_BIG_INT;
                            isAmountTooLow = true;
                        }
                        else {
                            minDeposit = gasFeeTotal.mul(utils_1.fixedPointAdjustment).div(maxGasFeePercent);
                            isAmountTooLow = (0, utils_1.toBN)(outputAmount).lt(minDeposit);
                        }
                        return [2, {
                                amountToRelay: outputAmount.toString(),
                                tokenSymbol: inputTokenInfo.symbol,
                                gasFeePercent: gasFeePercent.toString(),
                                gasFeeTotal: gasFeeTotal.toString(),
                                gasDiscountPercent: this.gasDiscountPercent,
                                capitalFeePercent: capitalFeePercent.toString(),
                                capitalFeeTotal: capitalFeeTotal.toString(),
                                capitalDiscountPercent: this.capitalDiscountPercent,
                                relayFeePercent: relayFeePercent.toString(),
                                relayFeeTotal: relayFeeTotal.toString(),
                                feeLimitPercent: this.feeLimitPercent,
                                maxGasFeePercent: maxGasFeePercent.toString(),
                                minDeposit: minDeposit.toString(),
                                isAmountTooLow: isAmountTooLow,
                            }];
                }
            });
        });
    };
    return RelayFeeCalculator;
}());
exports.RelayFeeCalculator = RelayFeeCalculator;
//# sourceMappingURL=relayFeeCalculator.js.map