@sky-mavis/smart-order-router
Version:
Ronin Swap Smart Order Router
155 lines • 16.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.V2HeuristicGasModelFactory = exports.COST_PER_EXTRA_HOP = exports.BASE_SWAP_COST = void 0;
const bignumber_1 = require("@ethersproject/bignumber");
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");
// Constant cost for doing any swap regardless of pools.
exports.BASE_SWAP_COST = bignumber_1.BigNumber.from(135000); // 115000, bumped up by 20_000 @eric 7/8/2022
// Constant per extra hop in the route.
exports.COST_PER_EXTRA_HOP = bignumber_1.BigNumber.from(50000); // 20000, bumped up by 30_000 @eric 7/8/2022
/**
* Computes a gas estimate for a V2 swap using heuristics.
* Considers number of hops in the route and the typical base cost for a swap.
*
* 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.
*
* Note, certain tokens e.g. rebasing/fee-on-transfer, may incur higher gas costs than
* what we estimate here. This is because they run extra logic on token transfer.
*
* @export
* @class V2HeuristicGasModelFactory
*/
class V2HeuristicGasModelFactory extends gas_model_1.IV2GasModelFactory {
async buildGasModel({ chainId, gasPriceWei, poolProvider, token, providerConfig, }) {
const usdPoolPromise = this.getHighestLiquidityUSDPool(chainId, poolProvider, providerConfig);
// Only fetch the native gasToken pool if specified by the config AND the gas token is not the native currency.
const nativeAndSpecifiedGasTokenPoolPromise = (providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.gasToken) && !(providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.gasToken.equals(util_1.WRAPPED_NATIVE_CURRENCY[chainId]))
? this.getEthPool(chainId, providerConfig.gasToken, poolProvider, providerConfig)
: Promise.resolve(null);
const [usdPool, nativeAndSpecifiedGasTokenPool] = await Promise.all([
usdPoolPromise,
nativeAndSpecifiedGasTokenPoolPromise,
]);
let ethPool = null;
if (!token.equals(util_1.WRAPPED_NATIVE_CURRENCY[chainId])) {
ethPool = await this.getEthPool(chainId, token, poolProvider, providerConfig);
}
const usdToken = usdPool.token0.address == util_1.WRAPPED_NATIVE_CURRENCY[chainId].address ? usdPool.token1 : usdPool.token0;
const calculateL1GasFees = async () => {
const nativePool = !token.equals(util_1.WRAPPED_NATIVE_CURRENCY[chainId])
? await (0, gas_factory_helpers_1.getV2NativePool)(token, poolProvider, providerConfig)
: null;
return await (0, gas_factory_helpers_1.calculateL1GasFeesHelper)(chainId, usdPool, token, nativePool);
};
return {
estimateGasCost: (routeWithValidQuote) => {
var _a;
const { gasCostInEth, gasUse } = this.estimateGas(routeWithValidQuote, gasPriceWei, chainId, providerConfig);
/** ------ MARK: USD logic -------- */
const gasCostInTermsOfUSD = (0, gas_model_1.getQuoteThroughNativePool)(chainId, gasCostInEth, usdPool);
/** ------ MARK: Conditional logic run if gasToken is specified -------- */
let gasCostInTermsOfGasToken = undefined;
if (nativeAndSpecifiedGasTokenPool) {
gasCostInTermsOfGasToken = (0, gas_model_1.getQuoteThroughNativePool)(chainId, gasCostInEth, nativeAndSpecifiedGasTokenPool);
}
// if the gasToken is the native currency, we can just use the gasCostInEth
else if ((_a = providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.gasToken) === null || _a === void 0 ? void 0 : _a.equals(util_1.WRAPPED_NATIVE_CURRENCY[chainId])) {
gasCostInTermsOfGasToken = gasCostInEth;
}
/** ------ MARK: return early if quoteToken is wrapped native currency ------- */
if (token.equals(util_1.WRAPPED_NATIVE_CURRENCY[chainId])) {
return {
gasEstimate: gasUse,
gasCostInToken: gasCostInEth,
gasCostInUSD: gasCostInTermsOfUSD,
gasCostInGasToken: gasCostInTermsOfGasToken,
};
}
// If the quote token is not WETH, we convert the gas cost to be in terms of the quote token.
// We do this by getting the highest liquidity <token>/ETH pool.
if (!ethPool) {
util_1.log.info('Unable to find ETH pool with the quote token to produce gas adjusted costs. Route will not account for gas.');
return {
gasEstimate: gasUse,
gasCostInToken: amounts_1.CurrencyAmount.fromRawAmount(token, 0),
gasCostInUSD: amounts_1.CurrencyAmount.fromRawAmount(usdToken, 0),
};
}
const gasCostInTermsOfQuoteToken = (0, gas_model_1.getQuoteThroughNativePool)(chainId, gasCostInEth, ethPool);
return {
gasEstimate: gasUse,
gasCostInToken: gasCostInTermsOfQuoteToken,
gasCostInUSD: gasCostInTermsOfUSD,
gasCostInGasToken: gasCostInTermsOfGasToken,
};
},
calculateL1GasFees,
};
}
estimateGas(routeWithValidQuote, gasPriceWei, chainId, providerConfig) {
const hops = routeWithValidQuote.route.pairs.length;
let gasUse = exports.BASE_SWAP_COST.add(exports.COST_PER_EXTRA_HOP.mul(hops - 1));
if (providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.additionalGasOverhead) {
gasUse = gasUse.add(providerConfig.additionalGasOverhead);
}
const totalGasCostWei = gasPriceWei.mul(gasUse);
const weth = util_1.WRAPPED_NATIVE_CURRENCY[chainId];
const gasCostInEth = amounts_1.CurrencyAmount.fromRawAmount(weth, totalGasCostWei.toString());
return { gasCostInEth, gasUse };
}
async getEthPool(chainId, token, poolProvider, providerConfig) {
const weth = util_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)) {
util_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 pool with ${token.symbol} for computing gas costs.`);
return null;
}
return pool;
}
async getHighestLiquidityUSDPool(chainId, poolProvider, providerConfig) {
const usdTokens = gas_model_1.USD_GAS_TOKEN_BY_CHAIN[chainId];
if (!usdTokens) {
throw new Error(`Could not find a USD token for computing gas costs on ${chainId}`);
}
const usdPools = lodash_1.default.map(usdTokens, usdToken => [usdToken, util_1.WRAPPED_NATIVE_CURRENCY[chainId]]);
const poolAccessor = await poolProvider.getPools(usdPools, providerConfig);
const poolsRaw = poolAccessor.getAllPools();
const pools = lodash_1.default.filter(poolsRaw, pool => pool.reserve0.greaterThan(0) &&
pool.reserve1.greaterThan(0) &&
// this case should never happen in production, but when we mock the pool provider it may return non native pairs
(pool.token0.equals(util_1.WRAPPED_NATIVE_CURRENCY[chainId]) || pool.token1.equals(util_1.WRAPPED_NATIVE_CURRENCY[chainId])));
if (pools.length == 0) {
util_1.log.error({ pools }, `Could not find a USD/WETH pool for computing gas costs.`);
throw new Error(`Can't find USD/WETH pool for computing gas costs.`);
}
const maxPool = lodash_1.default.maxBy(pools, pool => {
if (pool.token0.equals(util_1.WRAPPED_NATIVE_CURRENCY[chainId])) {
return parseFloat(pool.reserve0.toSignificant(2));
}
else {
return parseFloat(pool.reserve1.toSignificant(2));
}
});
return maxPool;
}
}
exports.V2HeuristicGasModelFactory = V2HeuristicGasModelFactory;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidjItaGV1cmlzdGljLWdhcy1tb2RlbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3NyYy9yb3V0ZXJzL2FscGhhLXJvdXRlci9nYXMtbW9kZWxzL3YyL3YyLWhldXJpc3RpYy1nYXMtbW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQ0Esd0RBQXFEO0FBR3JELG9EQUF1QjtBQUl2QiwyQ0FBZ0U7QUFDaEUsc0RBQTBEO0FBQzFELDhFQUFpRztBQUVqRyw0Q0FPc0I7QUFFdEIsd0RBQXdEO0FBQzNDLFFBQUEsY0FBYyxHQUFHLHFCQUFTLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsNkNBQTZDO0FBRW5HLHVDQUF1QztBQUMxQixRQUFBLGtCQUFrQixHQUFHLHFCQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsNENBQTRDO0FBRXJHOzs7Ozs7Ozs7Ozs7Ozs7O0dBZ0JHO0FBQ0gsTUFBYSwwQkFBMkIsU0FBUSw4QkFBa0I7SUFDekQsS0FBSyxDQUFDLGFBQWEsQ0FBQyxFQUN6QixPQUFPLEVBQ1AsV0FBVyxFQUNYLFlBQVksRUFDWixLQUFLLEVBQ0wsY0FBYyxHQUNhO1FBQzNCLE1BQU0sY0FBYyxHQUFrQixJQUFJLENBQUMsMEJBQTBCLENBQUMsT0FBTyxFQUFFLFlBQVksRUFBRSxjQUFjLENBQUMsQ0FBQztRQUU3RywrR0FBK0c7UUFDL0csTUFBTSxxQ0FBcUMsR0FDekMsQ0FBQSxjQUFjLGFBQWQsY0FBYyx1QkFBZCxjQUFjLENBQUUsUUFBUSxLQUFJLENBQUMsQ0FBQSxjQUFjLGFBQWQsY0FBYyx1QkFBZCxjQUFjLENBQUUsUUFBUSxDQUFDLE1BQU0sQ0FBQyw4QkFBdUIsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFBO1lBQzVGLENBQUMsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxjQUFjLENBQUMsUUFBUSxFQUFFLFlBQVksRUFB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