UNPKG

@xspswap/smart-order-router

Version:
281 lines 26.8 kB
import { BigNumber } from '@ethersproject/bignumber'; import { Protocol } from '@x-swap-protocol/router-sdk'; import { CurrencyAmount, Token, TradeType, WETH9, } from '@x-swap-protocol/sdk-core'; import { FeeAmount } from '@x-swap-protocol/v3-sdk'; import _ from 'lodash'; import { MixedRouteWithValidQuote, usdGasTokensByChain, V2RouteWithValidQuote, V3RouteWithValidQuote, } from '../routers'; import { log } from '../util'; import { buildTrade } from './methodParameters'; export async function getV2NativePool(token, poolProvider) { const chainId = token.chainId; const weth = WETH9[chainId]; const poolAccessor = await poolProvider.getPools([[weth, token]]); const pool = poolAccessor.getPool(weth, token); if (!pool || pool.reserve0.equalTo(0) || pool.reserve1.equalTo(0)) { 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; } export async function getHighestLiquidityV3NativePool(token, poolProvider) { const nativeCurrency = WETH9[token.chainId]; const nativePools = _([ FeeAmount.HIGH, FeeAmount.MEDIUM, FeeAmount.LOW, FeeAmount.LOWEST, ]) .map((feeAmount) => { return [nativeCurrency, token, feeAmount]; }) .value(); const poolAccessor = await poolProvider.getPools(nativePools); const pools = _([ FeeAmount.HIGH, FeeAmount.MEDIUM, FeeAmount.LOW, FeeAmount.LOWEST, ]) .map((feeAmount) => { return poolAccessor.getPool(nativeCurrency, token, feeAmount); }) .compact() .value(); if (pools.length == 0) { log.error({ pools }, `Could not find a ${nativeCurrency.symbol} pool with ${token.symbol} for computing gas costs.`); return null; } const maxPool = _.maxBy(pools, (pool) => pool.liquidity); return maxPool; } export async function getHighestLiquidityV3USDPool(chainId, poolProvider) { const usdTokens = usdGasTokensByChain[chainId]; const wrappedCurrency = WETH9[chainId]; if (!usdTokens) { throw new Error(`Could not find a USD token for computing gas costs on ${chainId}`); } const usdPools = _([ FeeAmount.HIGH, FeeAmount.MEDIUM, FeeAmount.LOW, FeeAmount.LOWEST, ]) .flatMap((feeAmount) => { return _.map(usdTokens, (usdToken) => [ wrappedCurrency, usdToken, feeAmount, ]); }) .value(); const poolAccessor = await poolProvider.getPools(usdPools); const pools = _([ FeeAmount.HIGH, FeeAmount.MEDIUM, FeeAmount.LOW, 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.`; log.error({ pools }, message); throw new Error(message); } const maxPool = _.maxBy(pools, (pool) => pool.liquidity); return maxPool; } export function getGasCostInUSD(usdPool, costNativeCurrency) { const nativeCurrency = costNativeCurrency.currency; // convert fee into usd const nativeTokenPrice = usdPool.token0.address == nativeCurrency.address ? usdPool.token0Price : usdPool.token1Price; const gasCostUSD = nativeTokenPrice.quote(costNativeCurrency); return gasCostUSD; } export function getGasCostInNativeCurrency(nativeCurrency, gasCostInWei) { // wrap fee to native currency const costNativeCurrency = CurrencyAmount.fromRawAmount(nativeCurrency, gasCostInWei.toString()); return costNativeCurrency; } export async function getGasCostInQuoteToken(quoteToken, nativePool, costNativeCurrency) { const nativeTokenPrice = nativePool.token0.address == quoteToken.address ? nativePool.token1Price : nativePool.token0Price; const gasCostQuoteToken = nativeTokenPrice.quote(costNativeCurrency); return gasCostQuoteToken; } export function calculateArbitrumToL1FeeFromCalldata(calldata, gasData) { const { perL2TxFee, perL1CalldataFee } = gasData; // calculates gas amounts based on bytes of calldata, use 0 as overhead. const l1GasUsed = getL2ToL1GasUsed(calldata, BigNumber.from(0)); // multiply by the fee per calldata and add the flat l2 fee let l1Fee = l1GasUsed.mul(perL1CalldataFee); l1Fee = l1Fee.add(perL2TxFee); return [l1GasUsed, l1Fee]; } export function calculateOptimismToL1FeeFromCalldata(calldata, gasData) { const { l1BaseFee, scalar, decimals, overhead } = gasData; const l1GasUsed = getL2ToL1GasUsed(calldata, overhead); // l1BaseFee is L1 Gas Price on etherscan const l1Fee = l1GasUsed.mul(l1BaseFee); const unscaled = l1Fee.mul(scalar); // scaled = unscaled / (10 ** decimals) const scaledConversion = BigNumber.from(10).pow(decimals); const scaled = unscaled.div(scaledConversion); return [l1GasUsed, scaled]; } // based on the code from the optimism OVM_GasPriceOracle contract export function getL2ToL1GasUsed(data, overhead) { // data is hex encoded const dataArr = data.slice(2).match(/.{1,2}/g); const numBytes = dataArr.length; let count = 0; for (let i = 0; i < numBytes; i += 1) { const byte = parseInt(dataArr[i], 16); if (byte == 0) { count += 4; } else { count += 16; } } const unsigned = overhead.add(count); const signedConversion = 68 * 16; return unsigned.add(signedConversion); } export async function calculateGasUsed(chainId, route, simulatedGasUsed, v2PoolProvider, v3PoolProvider) { const quoteToken = route.quote.currency.wrapped; const gasPriceWei = route.gasPriceWei; // calculate L2 to L1 security fee if relevant let l2toL1FeeInWei = BigNumber.from(0); // add l2 to l1 fee and wrap fee to native currency const gasCostInWei = gasPriceWei.mul(simulatedGasUsed).add(l2toL1FeeInWei); const nativeCurrency = WETH9[chainId]; const costNativeCurrency = getGasCostInNativeCurrency(nativeCurrency, gasCostInWei); const usdPool = await getHighestLiquidityV3USDPool(chainId, v3PoolProvider); const gasCostUSD = await getGasCostInUSD(usdPool, costNativeCurrency); let gasCostQuoteToken = costNativeCurrency; // get fee in terms of quote token if (!quoteToken.equals(nativeCurrency)) { const nativePools = await Promise.all([ getHighestLiquidityV3NativePool(quoteToken, v3PoolProvider), getV2NativePool(quoteToken, v2PoolProvider), ]); const nativePool = nativePools.find((pool) => pool !== null); if (!nativePool) { log.info('Could not find any V2 or V3 pools to convert the cost into the quote token'); gasCostQuoteToken = CurrencyAmount.fromRawAmount(quoteToken, 0); } else { gasCostQuoteToken = await getGasCostInQuoteToken(quoteToken, nativePool, costNativeCurrency); } } // Adjust quote for gas fees let quoteGasAdjusted; if (route.trade.tradeType == 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, quoteGasAdjusted: quoteGasAdjusted, }; } export function initSwapRouteFromExisting(swapRoute, v2PoolProvider, v3PoolProvider, quoteGasAdjusted, estimatedGasUsed, estimatedGasUsedQuoteToken, estimatedGasUsedUSD) { const currencyIn = swapRoute.trade.inputAmount.currency; const currencyOut = swapRoute.trade.outputAmount.currency; const tradeType = swapRoute.trade.tradeType.valueOf() ? TradeType.EXACT_OUTPUT : TradeType.EXACT_INPUT; const routesWithValidQuote = swapRoute.route.map((route) => { switch (route.protocol) { case Protocol.V3: case Protocol.UNI_V3: return new V3RouteWithValidQuote({ amount: CurrencyAmount.fromFractionalAmount(route.amount.currency, route.amount.numerator, route.amount.denominator), rawQuote: BigNumber.from(route.rawQuote), sqrtPriceX96AfterList: route.sqrtPriceX96AfterList.map((num) => BigNumber.from(num)), initializedTicksCrossedList: [...route.initializedTicksCrossedList], quoterGasEstimate: BigNumber.from(route.gasEstimate), percent: route.percent, route: route.route, gasModel: route.gasModel, quoteToken: new Token(currencyIn.chainId, route.quoteToken.address, route.quoteToken.decimals, route.quoteToken.symbol, route.quoteToken.name), tradeType: tradeType, v3PoolProvider: v3PoolProvider, protocol: route.protocol, }); case Protocol.V2: case Protocol.FATHOM: return new V2RouteWithValidQuote({ amount: CurrencyAmount.fromFractionalAmount(route.amount.currency, route.amount.numerator, route.amount.denominator), rawQuote: BigNumber.from(route.rawQuote), percent: route.percent, route: route.route, gasModel: route.gasModel, quoteToken: new Token(currencyIn.chainId, route.quoteToken.address, route.quoteToken.decimals, route.quoteToken.symbol, route.quoteToken.name), tradeType: tradeType, v2PoolProvider: v2PoolProvider, protocol: route.protocol, }); case Protocol.MIXED: return new MixedRouteWithValidQuote({ amount: CurrencyAmount.fromFractionalAmount(route.amount.currency, route.amount.numerator, route.amount.denominator), rawQuote: BigNumber.from(route.rawQuote), sqrtPriceX96AfterList: route.sqrtPriceX96AfterList.map((num) => BigNumber.from(num)), initializedTicksCrossedList: [...route.initializedTicksCrossedList], quoterGasEstimate: BigNumber.from(route.gasEstimate), percent: route.percent, route: route.route, mixedRouteGasModel: route.gasModel, v2PoolProvider, quoteToken: new Token(currencyIn.chainId, route.quoteToken.address, route.quoteToken.decimals, route.quoteToken.symbol, route.quoteToken.name), tradeType: tradeType, v3PoolProvider: v3PoolProvider, }); } }); const trade = buildTrade(currencyIn, currencyOut, tradeType, routesWithValidQuote); return { quote: swapRoute.quote, quoteGasAdjusted, estimatedGasUsed, estimatedGasUsedQuoteToken, estimatedGasUsedUSD, gasPriceWei: BigNumber.from(swapRoute.gasPriceWei), trade, route: routesWithValidQuote, blockNumber: BigNumber.from(swapRoute.blockNumber), methodParameters: swapRoute.methodParameters ? { calldata: swapRoute.methodParameters.calldata, value: swapRoute.methodParameters.value, to: swapRoute.methodParameters.to, } : undefined, simulationStatus: swapRoute.simulationStatus, }; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2FzLWZhY3RvcnktaGVscGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy91dGlsL2dhcy1mYWN0b3J5LWhlbHBlcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3JELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUN2RCxPQUFPLEVBR0wsY0FBYyxFQUNkLEtBQUssRUFDTCxTQUFTLEVBQ1QsS0FBSyxHQUNOLE1BQU0sMkJBQTJCLENBQUM7QUFFbkMsT0FBTyxFQUFFLFNBQVMsRUFBUSxNQUFNLHlCQUF5QixDQUFDO0FBQzFELE9BQU8sQ0FBQyxNQUFNLFFBQVEsQ0FBQztBQVF2QixPQUFPLEVBRUwsd0JBQXdCLEVBRXhCLG1CQUFtQixFQUNuQixxQkFBcUIsRUFDckIscUJBQXFCLEdBQ3RCLE1BQU0sWUFBWSxDQUFDO0FBQ3BCLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFFOUIsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRWhELE1BQU0sQ0FBQyxLQUFLLFVBQVUsZUFBZSxDQUNuQyxLQUFZLEVBQ1osWUFBNkI7SUFFN0IsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLE9BQWtCLENBQUM7SUFDekMsTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBRTVCLE1BQU0sWUFBWSxHQUFHLE1BQU0sWUFBWSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNsRSxNQUFNLElBQUksR0FBRyxZQUFZLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztJQUUvQyxJQUFJLENBQUMsSUFBSSxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FB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