UNPKG

@xspswap/smart-order-router

Version:
171 lines 15.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MixedRouteHeuristicGasModelFactory = void 0; const bignumber_1 = require("@ethersproject/bignumber"); const router_sdk_1 = require("@x-swap-protocol/router-sdk"); const sdk_core_1 = require("@x-swap-protocol/sdk-core"); const v2_sdk_1 = require("@x-swap-protocol/v2-sdk"); const v3_sdk_1 = require("@x-swap-protocol/v3-sdk"); const lodash_1 = __importDefault(require("lodash")); const util_1 = require("../../../../util"); const amounts_1 = require("../../../../util/amounts"); const gas_factory_helpers_1 = require("../../../../util/gas-factory-helpers"); const gas_model_1 = require("../gas-model"); const v2_heuristic_gas_model_1 = require("../v2/v2-heuristic-gas-model"); const gas_costs_1 = require("../v3/gas-costs"); /** * Computes a gas estimate for a mixed route swap using heuristics. * Considers number of hops in the route, number of ticks crossed * and the typical base cost for a swap. * * We get the number of ticks crossed in a swap from the MixedRouteQuoterV1 * contract. * * We compute gas estimates off-chain because * 1/ Calling eth_estimateGas for a swaps requires the caller to have * the full balance token being swapped, and approvals. * 2/ Tracking gas used using a wrapper contract is not accurate with Multicall * due to EIP-2929. We would have to make a request for every swap we wanted to estimate. * 3/ For V2 we simulate all our swaps off-chain so have no way to track gas used. * * @export * @class MixedRouteHeuristicGasModelFactory */ class MixedRouteHeuristicGasModelFactory extends gas_model_1.IOnChainGasModelFactory { constructor() { super(); } async buildGasModel({ chainId, gasPriceWei, v3poolProvider: V3poolProvider, quoteToken, v2poolProvider: V2poolProvider, }) { const usdPool = await (0, gas_factory_helpers_1.getHighestLiquidityV3USDPool)(chainId, V3poolProvider); // If our quote token is WETH, we don't need to convert our gas use to be in terms // of the quote token in order to produce a gas adjusted amount. // We do return a gas use in USD however, so we still convert to usd. const nativeCurrency = sdk_core_1.WETH9[chainId]; if (quoteToken.equals(nativeCurrency)) { const estimateGasCost = (routeWithValidQuote) => { const { totalGasCostNativeCurrency, baseGasUse } = this.estimateGas(routeWithValidQuote, gasPriceWei, chainId); const token0 = usdPool.token0.address == nativeCurrency.address; const nativeTokenPrice = token0 ? usdPool.token0Price : usdPool.token1Price; const gasCostInTermsOfUSD = nativeTokenPrice.quote(totalGasCostNativeCurrency); return { gasEstimate: baseGasUse, gasCostInToken: totalGasCostNativeCurrency, gasCostInUSD: gasCostInTermsOfUSD, }; }; return { estimateGasCost, }; } // If the quote token is not in the native currency, we convert the gas cost to be in terms of the quote token. // We do this by getting the highest liquidity <quoteToken>/<nativeCurrency> pool. eg. <quoteToken>/ETH pool. const nativeV3Pool = await (0, gas_factory_helpers_1.getHighestLiquidityV3NativePool)(quoteToken, V3poolProvider); let nativeV2Pool; if (V2poolProvider) { /// MixedRoutes nativeV2Pool = await (0, gas_factory_helpers_1.getV2NativePool)(quoteToken, V2poolProvider); } const usdToken = usdPool.token0.address == nativeCurrency.address ? usdPool.token1 : usdPool.token0; const estimateGasCost = (routeWithValidQuote) => { const { totalGasCostNativeCurrency, baseGasUse } = this.estimateGas(routeWithValidQuote, gasPriceWei, chainId); if (!nativeV3Pool && !nativeV2Pool) { util_1.log.info(`Unable to find ${nativeCurrency.symbol} pool with the quote token, ${quoteToken.symbol} to produce gas adjusted costs. Route will not account for gas.`); return { gasEstimate: baseGasUse, gasCostInToken: amounts_1.CurrencyAmount.fromRawAmount(quoteToken, 0), gasCostInUSD: amounts_1.CurrencyAmount.fromRawAmount(usdToken, 0), }; } /// we will use nativeV2Pool for fallback if nativeV3 does not exist /// can use ! here because we return above if v3Pool and v2Pool are null const nativePool = !nativeV3Pool && nativeV2Pool ? nativeV2Pool : nativeV3Pool; const token0 = nativePool.token0.address == nativeCurrency.address; // returns mid price in terms of the native currency (the ratio of quoteToken/nativeToken) const nativeTokenPrice = token0 ? nativePool.token0Price : nativePool.token1Price; let gasCostInTermsOfQuoteToken; try { // native token is base currency gasCostInTermsOfQuoteToken = nativeTokenPrice.quote(totalGasCostNativeCurrency); } catch (err) { util_1.log.info({ nativeTokenPriceBase: nativeTokenPrice.baseCurrency, nativeTokenPriceQuote: nativeTokenPrice.quoteCurrency, gasCostInEth: totalGasCostNativeCurrency.currency, }, 'Debug eth price token issue'); throw err; } // true if token0 is the native currency const token0USDPool = usdPool.token0.address == nativeCurrency.address; // gets the mid price of the pool in terms of the native token const nativeTokenPriceUSDPool = token0USDPool ? usdPool.token0Price : usdPool.token1Price; let gasCostInTermsOfUSD; try { gasCostInTermsOfUSD = nativeTokenPriceUSDPool.quote(totalGasCostNativeCurrency); } catch (err) { util_1.log.info({ usdT1: usdPool.token0.symbol, usdT2: usdPool.token1.symbol, gasCostInNativeToken: totalGasCostNativeCurrency.currency.symbol, }, 'Failed to compute USD gas price'); throw err; } return { gasEstimate: baseGasUse, gasCostInToken: gasCostInTermsOfQuoteToken, gasCostInUSD: gasCostInTermsOfUSD, }; }; return { estimateGasCost: estimateGasCost.bind(this), }; } estimateGas(routeWithValidQuote, gasPriceWei, chainId) { const totalInitializedTicksCrossed = bignumber_1.BigNumber.from(Math.max(1, lodash_1.default.sum(routeWithValidQuote.initializedTicksCrossedList))); /** * Since we must make a separate call to multicall for each v3 and v2 section, we will have to * add the BASE_SWAP_COST to each section. */ let baseGasUse = bignumber_1.BigNumber.from(0); const route = routeWithValidQuote.route; const res = (0, router_sdk_1.partitionMixedRouteByProtocol)(route); res.map((section) => { if (section.every((pool) => pool instanceof v3_sdk_1.Pool)) { baseGasUse = baseGasUse.add((0, gas_costs_1.BASE_SWAP_COST)(chainId)); baseGasUse = baseGasUse.add((0, gas_costs_1.COST_PER_HOP)(chainId).mul(section.length)); } else if (section.every((pool) => pool instanceof v2_sdk_1.Pair)) { baseGasUse = baseGasUse.add(v2_heuristic_gas_model_1.BASE_SWAP_COST); baseGasUse = baseGasUse.add( /// same behavior in v2 heuristic gas model factory v2_heuristic_gas_model_1.COST_PER_EXTRA_HOP.mul(section.length - 1)); } }); const tickGasUse = (0, gas_costs_1.COST_PER_INIT_TICK)(chainId).mul(totalInitializedTicksCrossed); const uninitializedTickGasUse = gas_costs_1.COST_PER_UNINIT_TICK.mul(0); // base estimate gas used based on chainId estimates for hops and ticks gas useage baseGasUse = baseGasUse.add(tickGasUse).add(uninitializedTickGasUse); const baseGasCostWei = gasPriceWei.mul(baseGasUse); const wrappedCurrency = sdk_core_1.WETH9[chainId]; const totalGasCostNativeCurrency = amounts_1.CurrencyAmount.fromRawAmount(wrappedCurrency, baseGasCostWei.toString()); return { totalGasCostNativeCurrency, totalInitializedTicksCrossed, baseGasUse, }; } } exports.MixedRouteHeuristicGasModelFactory = MixedRouteHeuristicGasModelFactory; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWl4ZWQtcm91dGUtaGV1cmlzdGljLWdhcy1tb2RlbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3NyYy9yb3V0ZXJzL2FscGhhLXJvdXRlci9nYXMtbW9kZWxzL21peGVkUm91dGUvbWl4ZWQtcm91dGUtaGV1cmlzdGljLWdhcy1tb2RlbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQSx3REFBcUQ7QUFDckQsNERBQTRFO0FBQzVFLHdEQUEyRDtBQUMzRCxvREFBK0M7QUFDL0Msb0RBQStDO0FBQy9DLG9EQUF1QjtBQUV2QiwyQ0FBdUM7QUFDdkMsc0RBQTBEO0FBQzFELDhFQUk4QztBQUU5Qyw0Q0FJc0I7QUFDdEIseUVBR3NDO0FBQ3RDLCtDQUt5QjtBQUV6Qjs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpQkc7QUFDSCxNQUFhLGtDQUFtQyxTQUFRLG1DQUF1QjtJQUM3RTtRQUNFLEtBQUssRUFBRSxDQUFDO0lBQ1YsQ0FBQztJQUVNLEtBQUssQ0FBQyxhQUFhLENBQUMsRUFDekIsT0FBTyxFQUNQLFdBQVcsRUFDWCxjQUFjLEVBQUUsY0FBYyxFQUM5QixVQUFVLEVBQ1YsY0FBYyxFQUFFLGNBQWMsR0FDRTtRQUdoQyxNQUFNLE9BQU8sR0FBUyxNQUFNLElBQUEsa0RBQTRCLEVBQ3RELE9BQU8sRUFDUCxjQUFjLENBQ2YsQ0FBQztRQUVGLGtGQUFrRjtRQUNsRixnRUFBZ0U7UUFDaEUscUVBQXFFO1FBQ3JFLE1BQU0sY0FBYyxHQUFHLGdCQUFLLENBQUMsT0FBTyxDQUFFLENBQUM7UUFDdkMsSUFBSSxVQUFVLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxFQUFFO1lBQ3JDLE1BQU0sZUFBZSxHQUFHLENBQ3RCLG1CQUE2QyxFQUs3QyxFQUFFO2dCQUNGLE1BQU0sRUFBRSwwQkFBMEIsRUFBRSxVQUFVLEVBQUUsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUNqRSxtQkFBbUIsRUFDbkIsV0FBVyxFQUNYLE9B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