UNPKG

@sky-mavis/smart-order-router

Version:
137 lines 13.8 kB
import { BigNumber } from '@ethersproject/bignumber'; import { partitionMixedRouteByProtocol } from '@uniswap/router-sdk'; import { Pair } from '@uniswap/v2-sdk'; import { Pool } from '@uniswap/v3-sdk'; import JSBI from 'jsbi'; import _ from 'lodash'; import { WRAPPED_NATIVE_CURRENCY } from '../../../..'; import { log } from '../../../../util'; import { CurrencyAmount } from '../../../../util/amounts'; import { getV2NativePool } from '../../../../util/gas-factory-helpers'; import { getQuoteThroughNativePool, IOnChainGasModelFactory, } from '../gas-model'; import { BASE_SWAP_COST as BASE_SWAP_COST_V2, COST_PER_EXTRA_HOP as COST_PER_EXTRA_HOP_V2, } from '../v2/v2-heuristic-gas-model'; import { BASE_SWAP_COST, COST_PER_HOP, COST_PER_INIT_TICK, COST_PER_UNINIT_TICK } from '../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 */ export class MixedRouteHeuristicGasModelFactory extends IOnChainGasModelFactory { constructor() { super(); } async buildGasModel({ chainId, gasPriceWei, pools, quoteToken, v2poolProvider: V2poolProvider, providerConfig, }) { const nativeCurrency = WRAPPED_NATIVE_CURRENCY[chainId]; const usdPool = pools.usdPool; const usdToken = usdPool.token0.equals(nativeCurrency) ? usdPool.token1 : usdPool.token0; let nativeV2Pool; // Avoid fetching for a (WETH,WETH) pool here, we handle the quoteToken = wrapped native case in estimateGasCost if (!quoteToken.equals(nativeCurrency) && V2poolProvider) { /// MixedRoutes nativeV2Pool = await getV2NativePool(quoteToken, V2poolProvider, providerConfig); } const estimateGasCost = (routeWithValidQuote) => { var _a; const { totalGasCostNativeCurrency, baseGasUse } = this.estimateGas(routeWithValidQuote, gasPriceWei, chainId, providerConfig); /** ------ MARK: USD Logic -------- */ const gasCostInTermsOfUSD = getQuoteThroughNativePool(chainId, totalGasCostNativeCurrency, usdPool); /** ------ MARK: Conditional logic run if gasToken is specified -------- */ const nativeAndSpecifiedGasTokenPool = pools.nativeAndSpecifiedGasTokenV3Pool; let gasCostInTermsOfGasToken = undefined; if (nativeAndSpecifiedGasTokenPool) { gasCostInTermsOfGasToken = getQuoteThroughNativePool(chainId, totalGasCostNativeCurrency, nativeAndSpecifiedGasTokenPool); } // if the gasToken is the native currency, we can just use the totalGasCostNativeCurrency else if ((_a = providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.gasToken) === null || _a === void 0 ? void 0 : _a.equals(nativeCurrency)) { gasCostInTermsOfGasToken = totalGasCostNativeCurrency; } /** ------ MARK: return early if quoteToken is wrapped native currency ------- */ if (quoteToken.equals(nativeCurrency)) { return { gasEstimate: baseGasUse, gasCostInToken: totalGasCostNativeCurrency, gasCostInUSD: gasCostInTermsOfUSD, gasCostInGasToken: gasCostInTermsOfGasToken, }; } /** ------ MARK: Main gas logic in terms of quote token -------- */ // 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 = pools.nativeAndQuoteTokenV3Pool; if (!nativeV3Pool && !nativeV2Pool) { 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: CurrencyAmount.fromRawAmount(quoteToken, 0), gasCostInUSD: CurrencyAmount.fromRawAmount(usdToken, 0), }; } /// we will use nativeV2Pool for fallback if nativeV3 does not exist or has 0 liquidity /// can use ! here because we return above if v3Pool and v2Pool are null const nativePool = (!nativeV3Pool || JSBI.equal(nativeV3Pool.liquidity, JSBI.BigInt(0))) && nativeV2Pool ? nativeV2Pool : nativeV3Pool; const gasCostInTermsOfQuoteToken = getQuoteThroughNativePool(chainId, totalGasCostNativeCurrency, nativePool); return { gasEstimate: baseGasUse, gasCostInToken: gasCostInTermsOfQuoteToken, gasCostInUSD: gasCostInTermsOfUSD, gasCostInGasToken: gasCostInTermsOfGasToken, }; }; return { estimateGasCost: estimateGasCost.bind(this), }; } estimateGas(routeWithValidQuote, gasPriceWei, chainId, providerConfig) { const totalInitializedTicksCrossed = BigNumber.from(Math.max(1, _.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.from(0); const route = routeWithValidQuote.route; const res = partitionMixedRouteByProtocol(route); res.map((section) => { if (section.every(pool => pool instanceof Pool)) { baseGasUse = baseGasUse.add(BASE_SWAP_COST); baseGasUse = baseGasUse.add(COST_PER_HOP.mul(section.length)); } else if (section.every(pool => pool instanceof Pair)) { baseGasUse = baseGasUse.add(BASE_SWAP_COST_V2); baseGasUse = baseGasUse.add( /// same behavior in v2 heuristic gas model factory COST_PER_EXTRA_HOP_V2.mul(section.length - 1)); } }); const tickGasUse = COST_PER_INIT_TICK.mul(totalInitializedTicksCrossed); const uninitializedTickGasUse = 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); if (providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.additionalGasOverhead) { baseGasUse = baseGasUse.add(providerConfig.additionalGasOverhead); } const baseGasCostWei = gasPriceWei.mul(baseGasUse); const wrappedCurrency = WRAPPED_NATIVE_CURRENCY[chainId]; const totalGasCostNativeCurrency = CurrencyAmount.fromRawAmount(wrappedCurrency, baseGasCostWei.toString()); return { totalGasCostNativeCurrency, totalInitializedTicksCrossed, baseGasUse, }; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWl4ZWQtcm91dGUtaGV1cmlzdGljLWdhcy1tb2RlbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3NyYy9yb3V0ZXJzL2FscGhhLXJvdXRlci9nYXMtbW9kZWxzL21peGVkUm91dGUvbWl4ZWQtcm91dGUtaGV1cmlzdGljLWdhcy1tb2RlbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFckQsT0FBTyxFQUFFLDZCQUE2QixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDcEUsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3ZDLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN2QyxPQUFPLElBQUksTUFBTSxNQUFNLENBQUM7QUFDeEIsT0FBTyxDQUFDLE1BQU0sUUFBUSxDQUFDO0FBRXZCLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUN0RCxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDdkMsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQzFELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQUV2RSxPQUFPLEVBR0wseUJBQXlCLEVBRXpCLHVCQUF1QixHQUN4QixNQUFNLGNBQWMsQ0FBQztBQUN0QixPQUFPLEVBQ0wsY0FBYyxJQUFJLGlCQUFpQixFQUNuQyxrQkFBa0IsSUFBSSxxQkFBcUIsR0FDNUMsTUFBTSw4QkFBOEIsQ0FBQztBQUN0QyxPQUFPLEVBQUUsY0FBYyxFQUFFLFlBQVksRUFBRSxrQkFBa0IsRUFBRSxvQkFBb0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRXpHOzs7Ozs7Ozs7Ozs7Ozs7OztHQWlCRztBQUNILE1BQU0sT0FBTyxrQ0FBbUMsU0FBUSx1QkFBdUI7SUFDN0U7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