UNPKG

@sky-mavis/smart-order-router

Version:
282 lines 29.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateL1GasFeesHelper = exports.initSwapRouteFromExisting = exports.calculateGasUsed = exports.calculateOptimismToL1FeeFromCalldata = exports.getGasCostInNativeCurrency = exports.getHighestLiquidityV3USDPool = exports.getHighestLiquidityV3NativePool = exports.getV2NativePool = void 0; const sdk_1 = require("@eth-optimism/sdk"); const bignumber_1 = require("@ethersproject/bignumber"); const router_sdk_1 = require("@uniswap/router-sdk"); const sdk_core_1 = require("@uniswap/sdk-core"); const v3_sdk_1 = require("@uniswap/v3-sdk"); const jsbi_1 = __importDefault(require("jsbi")); const lodash_1 = __importDefault(require("lodash")); const routers_1 = require("../routers"); const _1 = require("."); const methodParameters_1 = require("./methodParameters"); async function getV2NativePool(token, poolProvider, providerConfig) { const chainId = token.chainId; const weth = _1.WRAPPED_NATIVE_CURRENCY[chainId]; const poolAccessor = await poolProvider.getPools([[weth, token]], providerConfig); const pool = poolAccessor.getPool(weth, token); if (!pool || pool.reserve0.equalTo(0) || pool.reserve1.equalTo(0)) { _1.log.error({ weth, token, reserve0: pool === null || pool === void 0 ? void 0 : pool.reserve0.toExact(), reserve1: pool === null || pool === void 0 ? void 0 : pool.reserve1.toExact(), }, `Could not find a valid WETH V2 pool with ${token.symbol} for computing gas costs.`); return null; } return pool; } exports.getV2NativePool = getV2NativePool; async function getHighestLiquidityV3NativePool(token, poolProvider, providerConfig) { const nativeCurrency = _1.WRAPPED_NATIVE_CURRENCY[token.chainId]; const nativePools = (0, lodash_1.default)([v3_sdk_1.FeeAmount.HIGH, v3_sdk_1.FeeAmount.MEDIUM, v3_sdk_1.FeeAmount.LOW, v3_sdk_1.FeeAmount.LOWEST]) .map(feeAmount => { return [nativeCurrency, token, feeAmount]; }) .value(); const poolAccessor = await poolProvider.getPools(nativePools, providerConfig); const pools = (0, lodash_1.default)([v3_sdk_1.FeeAmount.HIGH, v3_sdk_1.FeeAmount.MEDIUM, v3_sdk_1.FeeAmount.LOW, v3_sdk_1.FeeAmount.LOWEST]) .map(feeAmount => { return poolAccessor.getPool(nativeCurrency, token, feeAmount); }) .compact() .value(); if (pools.length == 0) { _1.log.error({ pools }, `Could not find a ${nativeCurrency.symbol} pool with ${token.symbol} for computing gas costs.`); return null; } const maxPool = pools.reduce((prev, current) => { return jsbi_1.default.greaterThan(prev.liquidity, current.liquidity) ? prev : current; }); return maxPool; } exports.getHighestLiquidityV3NativePool = getHighestLiquidityV3NativePool; async function getHighestLiquidityV3USDPool(chainId, poolProvider, providerConfig) { const usdTokens = routers_1.USD_GAS_TOKEN_BY_CHAIN[chainId]; const wrappedCurrency = _1.WRAPPED_NATIVE_CURRENCY[chainId]; if (!usdTokens) { throw new Error(`Could not find a USD token for computing gas costs on ${chainId}`); } const usdPools = (0, lodash_1.default)([v3_sdk_1.FeeAmount.HIGH, v3_sdk_1.FeeAmount.MEDIUM, v3_sdk_1.FeeAmount.LOW, v3_sdk_1.FeeAmount.LOWEST]) .flatMap(feeAmount => { return lodash_1.default.map(usdTokens, usdToken => [wrappedCurrency, usdToken, feeAmount]); }) .value(); const poolAccessor = await poolProvider.getPools(usdPools, providerConfig); const pools = (0, lodash_1.default)([v3_sdk_1.FeeAmount.HIGH, v3_sdk_1.FeeAmount.MEDIUM, v3_sdk_1.FeeAmount.LOW, v3_sdk_1.FeeAmount.LOWEST]) .flatMap(feeAmount => { const pools = []; for (const usdToken of usdTokens) { const pool = poolAccessor.getPool(wrappedCurrency, usdToken, feeAmount); if (pool) { pools.push(pool); } } return pools; }) .compact() .value(); if (pools.length == 0) { const message = `Could not find a USD/${wrappedCurrency.symbol} pool for computing gas costs.`; _1.log.error({ pools }, message); throw new Error(message); } const maxPool = pools.reduce((prev, current) => { return jsbi_1.default.greaterThan(prev.liquidity, current.liquidity) ? prev : current; }); return maxPool; } exports.getHighestLiquidityV3USDPool = getHighestLiquidityV3USDPool; function getGasCostInNativeCurrency(nativeCurrency, gasCostInWei) { // wrap fee to native currency const costNativeCurrency = _1.CurrencyAmount.fromRawAmount(nativeCurrency, gasCostInWei.toString()); return costNativeCurrency; } exports.getGasCostInNativeCurrency = getGasCostInNativeCurrency; async function calculateOptimismToL1FeeFromCalldata(calldata, chainId, provider) { const tx = { data: calldata, chainId: chainId, type: 2, // sign the transaction as EIP-1559, otherwise it will fail at maxFeePerGas }; const [l1GasUsed, l1GasCost] = await Promise.all([(0, sdk_1.estimateL1Gas)(provider, tx), (0, sdk_1.estimateL1GasCost)(provider, tx)]); return [l1GasUsed, l1GasCost]; } exports.calculateOptimismToL1FeeFromCalldata = calculateOptimismToL1FeeFromCalldata; async function calculateGasUsed(chainId, route, simulatedGasUsed, v2PoolProvider, v3PoolProvider, providerConfig) { const quoteToken = route.quote.currency.wrapped; const gasPriceWei = route.gasPriceWei; const gasCostInWei = gasPriceWei.mul(simulatedGasUsed); const nativeCurrency = _1.WRAPPED_NATIVE_CURRENCY[chainId]; const costNativeCurrency = getGasCostInNativeCurrency(nativeCurrency, gasCostInWei); const usdPool = await getHighestLiquidityV3USDPool(chainId, v3PoolProvider, providerConfig); /** ------ MARK: USD logic -------- */ const gasCostUSD = (0, routers_1.getQuoteThroughNativePool)(chainId, costNativeCurrency, usdPool); /** ------ MARK: Conditional logic run if gasToken is specified -------- */ let gasCostInTermsOfGasToken = undefined; if (providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.gasToken) { if (providerConfig.gasToken.equals(nativeCurrency)) { gasCostInTermsOfGasToken = costNativeCurrency; } else { const nativeAndSpecifiedGasTokenPool = await getHighestLiquidityV3NativePool(providerConfig.gasToken, v3PoolProvider, providerConfig); if (nativeAndSpecifiedGasTokenPool) { gasCostInTermsOfGasToken = (0, routers_1.getQuoteThroughNativePool)(chainId, costNativeCurrency, nativeAndSpecifiedGasTokenPool); } else { _1.log.info(`Could not find a V3 pool for gas token ${providerConfig.gasToken.symbol}`); } } } /** ------ MARK: Main gas logic in terms of quote token -------- */ let gasCostQuoteToken = undefined; // shortcut if quote token is native currency if (quoteToken.equals(nativeCurrency)) { gasCostQuoteToken = costNativeCurrency; } // get fee in terms of quote token else { const nativePools = await Promise.all([ getHighestLiquidityV3NativePool(quoteToken, v3PoolProvider, providerConfig), getV2NativePool(quoteToken, v2PoolProvider, providerConfig), ]); const nativePool = nativePools.find(pool => pool !== null); if (!nativePool) { _1.log.info('Could not find any V2 or V3 pools to convert the cost into the quote token'); gasCostQuoteToken = _1.CurrencyAmount.fromRawAmount(quoteToken, 0); } else { gasCostQuoteToken = (0, routers_1.getQuoteThroughNativePool)(chainId, costNativeCurrency, nativePool); } } // Adjust quote for gas fees let quoteGasAdjusted; if (route.trade.tradeType == sdk_core_1.TradeType.EXACT_OUTPUT) { // Exact output - need more of tokenIn to get the desired amount of tokenOut quoteGasAdjusted = route.quote.add(gasCostQuoteToken); } else { // Exact input - can get less of tokenOut due to fees quoteGasAdjusted = route.quote.subtract(gasCostQuoteToken); } return { estimatedGasUsedUSD: gasCostUSD, estimatedGasUsedQuoteToken: gasCostQuoteToken, estimatedGasUsedGasToken: gasCostInTermsOfGasToken, quoteGasAdjusted: quoteGasAdjusted, }; } exports.calculateGasUsed = calculateGasUsed; function initSwapRouteFromExisting(swapRoute, v2PoolProvider, v3PoolProvider, portionProvider, quoteGasAdjusted, estimatedGasUsed, estimatedGasUsedQuoteToken, estimatedGasUsedUSD, swapOptions, estimatedGasUsedGasToken) { const currencyIn = swapRoute.trade.inputAmount.currency; const currencyOut = swapRoute.trade.outputAmount.currency; const tradeType = swapRoute.trade.tradeType.valueOf() ? sdk_core_1.TradeType.EXACT_OUTPUT : sdk_core_1.TradeType.EXACT_INPUT; const routesWithValidQuote = swapRoute.route.map(route => { switch (route.protocol) { case router_sdk_1.Protocol.V3: return new routers_1.V3RouteWithValidQuote({ amount: _1.CurrencyAmount.fromFractionalAmount(route.amount.currency, route.amount.numerator, route.amount.denominator), rawQuote: bignumber_1.BigNumber.from(route.rawQuote), sqrtPriceX96AfterList: route.sqrtPriceX96AfterList.map(num => bignumber_1.BigNumber.from(num)), initializedTicksCrossedList: [...route.initializedTicksCrossedList], quoterGasEstimate: bignumber_1.BigNumber.from(route.gasEstimate), percent: route.percent, route: route.route, gasModel: route.gasModel, quoteToken: new sdk_core_1.Token(currencyIn.chainId, route.quoteToken.address, route.quoteToken.decimals, route.quoteToken.symbol, route.quoteToken.name), tradeType: tradeType, v3PoolProvider: v3PoolProvider, }); case router_sdk_1.Protocol.V2: return new routers_1.V2RouteWithValidQuote({ amount: _1.CurrencyAmount.fromFractionalAmount(route.amount.currency, route.amount.numerator, route.amount.denominator), rawQuote: bignumber_1.BigNumber.from(route.rawQuote), percent: route.percent, route: route.route, gasModel: route.gasModel, quoteToken: new sdk_core_1.Token(currencyIn.chainId, route.quoteToken.address, route.quoteToken.decimals, route.quoteToken.symbol, route.quoteToken.name), tradeType: tradeType, v2PoolProvider: v2PoolProvider, }); case router_sdk_1.Protocol.MIXED: return new routers_1.MixedRouteWithValidQuote({ amount: _1.CurrencyAmount.fromFractionalAmount(route.amount.currency, route.amount.numerator, route.amount.denominator), rawQuote: bignumber_1.BigNumber.from(route.rawQuote), sqrtPriceX96AfterList: route.sqrtPriceX96AfterList.map(num => bignumber_1.BigNumber.from(num)), initializedTicksCrossedList: [...route.initializedTicksCrossedList], quoterGasEstimate: bignumber_1.BigNumber.from(route.gasEstimate), percent: route.percent, route: route.route, mixedRouteGasModel: route.gasModel, v2PoolProvider, quoteToken: new sdk_core_1.Token(currencyIn.chainId, route.quoteToken.address, route.quoteToken.decimals, route.quoteToken.symbol, route.quoteToken.name), tradeType: tradeType, v3PoolProvider: v3PoolProvider, }); } }); const trade = (0, methodParameters_1.buildTrade)(currencyIn, currencyOut, tradeType, routesWithValidQuote); const quoteGasAndPortionAdjusted = swapRoute.portionAmount ? portionProvider.getQuoteGasAndPortionAdjusted(swapRoute.trade.tradeType, quoteGasAdjusted, swapRoute.portionAmount) : undefined; const routesWithValidQuotePortionAdjusted = portionProvider.getRouteWithQuotePortionAdjusted(swapRoute.trade.tradeType, routesWithValidQuote, swapOptions); return { quote: swapRoute.quote, quoteGasAdjusted, quoteGasAndPortionAdjusted, estimatedGasUsed, estimatedGasUsedQuoteToken, estimatedGasUsedGasToken, estimatedGasUsedUSD, gasPriceWei: bignumber_1.BigNumber.from(swapRoute.gasPriceWei), trade, route: routesWithValidQuotePortionAdjusted, blockNumber: bignumber_1.BigNumber.from(swapRoute.blockNumber), methodParameters: swapRoute.methodParameters ? { calldata: swapRoute.methodParameters.calldata, value: swapRoute.methodParameters.value, to: swapRoute.methodParameters.to, } : undefined, simulationStatus: swapRoute.simulationStatus, portionAmount: swapRoute.portionAmount, }; } exports.initSwapRouteFromExisting = initSwapRouteFromExisting; const calculateL1GasFeesHelper = async (chainId, usdPool, quoteToken, nativePool) => { const mainnetGasUsed = bignumber_1.BigNumber.from(0); const mainnetFeeInWei = bignumber_1.BigNumber.from(0); const gasUsedL1OnL2 = bignumber_1.BigNumber.from(0); // wrap fee to native currency const nativeCurrency = _1.WRAPPED_NATIVE_CURRENCY[chainId]; const costNativeCurrency = _1.CurrencyAmount.fromRawAmount(nativeCurrency, mainnetFeeInWei.toString()); // convert fee into usd const gasCostL1USD = (0, routers_1.getQuoteThroughNativePool)(chainId, costNativeCurrency, usdPool); let gasCostL1QuoteToken = costNativeCurrency; // if the inputted token is not in the native currency, quote a native/quote token pool to get the gas cost in terms of the quote token if (!quoteToken.equals(nativeCurrency)) { if (!nativePool) { _1.log.info('Could not find a pool to convert the cost into the quote token'); gasCostL1QuoteToken = _1.CurrencyAmount.fromRawAmount(quoteToken, 0); } else { const nativeTokenPrice = nativePool.token0.address == nativeCurrency.address ? nativePool.token0Price : nativePool.token1Price; gasCostL1QuoteToken = nativeTokenPrice.quote(costNativeCurrency); } } // gasUsedL1 is the gas units used calculated from the bytes of the calldata // gasCostL1USD and gasCostL1QuoteToken is the cost of gas in each of those tokens return { gasUsedL1: mainnetGasUsed, gasUsedL1OnL2, gasCostL1USD, gasCostL1QuoteToken, }; }; exports.calculateL1GasFeesHelper = calculateL1GasFeesHelper; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2FzLWZhY3RvcnktaGVscGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy91dGlsL2dhcy1mYWN0b3J5LWhlbHBlcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQ0EsMkNBQXFFO0FBQ3JFLHdEQUFxRDtBQUVyRCxvREFBK0M7QUFDL0MsZ0RBQXFEO0FBRXJELDRDQUFrRDtBQUNsRCxnREFBd0I7QUFDeEIsb0RBQXVCO0FBS3ZCLHdDQVVvQjtBQUNwQix3QkFBaUU7QUFDakUseURBQWdEO0FBRXpDLEtBQUssVUFBVSxlQUFlLENBQ25DLEtBQVksRUFDWixZQUE2QixFQUM3QixjQUF1QztJQUV2QyxNQUFNLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBa0IsQ0FBQztJQUN6QyxNQUFNLElBQUksR0FBRywwQkFBdUIsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUU5QyxNQUFNLFlBQVksR0FBRyxNQUFNLFlBQVksQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQyxFQUFFLGNBQWMsQ0FBQyxDQUFDO0lBQ2xGLE1BQU0sSUFBSSxHQUFHLFlBQVksQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBRS9DLElBQUksQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUU7UUFDakUsTUFBRyxDQUFDLEtBQUssQ0FDUDtZQUNFLElBQUk7WUFDSixLQUFLO1lBQ0wsUUFBUSxFQUFFLElBQUksYUFBSixJQUFJLHVCQUFKLElBQUksQ0FBRSxRQUFRLENBQUMsT0FBTyxFQUFFO1lBQ2xDLFFBQVEsRUFBRSxJQUFJLGFBQUosSUFBSSx1QkFBSixJQUFJLENBQUUsUUFBUSxDQUFDLE9BQU8sRUFBRTtTQUNuQyxFQUNELDRDQUE0QyxL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