@sky-mavis/smart-order-router
Version:
Ronin Swap Smart Order Router
282 lines • 29.9 kB
JavaScript
"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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