UNPKG

@xspswap/smart-order-router

Version:
168 lines 15.5 kB
"use strict"; 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 sdk_core_1 = require("@x-swap-protocol/sdk-core"); const lodash_1 = __importDefault(require("lodash")); const util_1 = require("../../../../util"); const amounts_1 = require("../../../../util/amounts"); const gas_model_1 = require("../gas-model"); // Constant cost for doing any swap regardless of pools. exports.BASE_SWAP_COST = bignumber_1.BigNumber.from(100000); // Reduced for low gas networks // Constant per extra hop in the route. exports.COST_PER_EXTRA_HOP = bignumber_1.BigNumber.from(35000); // Reduced for low gas networks /** * 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 { constructor() { super(); } async buildGasModel({ chainId, gasPriceWei, poolProvider, token, }) { if (token.equals(sdk_core_1.WETH9[chainId])) { const usdPool = await this.getHighestLiquidityUSDPool(chainId, poolProvider); return { estimateGasCost: (routeWithValidQuote) => { const { gasCostInEth, gasUse } = this.estimateGas(routeWithValidQuote, gasPriceWei, chainId); const ethToken0 = usdPool.token0.address == sdk_core_1.WETH9[chainId].address; const ethTokenPrice = ethToken0 ? usdPool.token0Price : usdPool.token1Price; const gasCostInTermsOfUSD = ethTokenPrice.quote(gasCostInEth); return { gasEstimate: gasUse, gasCostInToken: gasCostInEth, gasCostInUSD: gasCostInTermsOfUSD, }; }, }; } // 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. const ethPool = await this.getEthPool(chainId, token, poolProvider); 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.'); } const usdPool = await this.getHighestLiquidityUSDPool(chainId, poolProvider); return { estimateGasCost: (routeWithValidQuote) => { const usdToken = usdPool.token0.address == sdk_core_1.WETH9[chainId].address ? usdPool.token1 : usdPool.token0; const { gasCostInEth, gasUse } = this.estimateGas(routeWithValidQuote, gasPriceWei, chainId); if (!ethPool) { return { gasEstimate: gasUse, gasCostInToken: amounts_1.CurrencyAmount.fromRawAmount(token, 0), gasCostInUSD: amounts_1.CurrencyAmount.fromRawAmount(usdToken, 0), }; } const ethToken0 = ethPool.token0.address == sdk_core_1.WETH9[chainId].address; const ethTokenPrice = ethToken0 ? ethPool.token0Price : ethPool.token1Price; let gasCostInTermsOfQuoteToken; try { gasCostInTermsOfQuoteToken = ethTokenPrice.quote(gasCostInEth); } catch (err) { util_1.log.error({ ethTokenPriceBase: ethTokenPrice.baseCurrency, ethTokenPriceQuote: ethTokenPrice.quoteCurrency, gasCostInEth: gasCostInEth.currency, }, 'Debug eth price token issue'); throw err; } const ethToken0USDPool = usdPool.token0.address == sdk_core_1.WETH9[chainId].address; const ethTokenPriceUSDPool = ethToken0USDPool ? usdPool.token0Price : usdPool.token1Price; let gasCostInTermsOfUSD; try { gasCostInTermsOfUSD = ethTokenPriceUSDPool.quote(gasCostInEth); } catch (err) { util_1.log.error({ usdT1: usdPool.token0.symbol, usdT2: usdPool.token1.symbol, gasCostInEthToken: gasCostInEth.currency.symbol, }, 'Failed to compute USD gas price'); throw err; } return { gasEstimate: gasUse, gasCostInToken: gasCostInTermsOfQuoteToken, gasCostInUSD: gasCostInTermsOfUSD, }; }, }; } estimateGas(routeWithValidQuote, gasPriceWei, chainId) { const hops = routeWithValidQuote.route.pairs.length; const gasUse = exports.BASE_SWAP_COST.add(exports.COST_PER_EXTRA_HOP.mul(hops - 1)); const totalGasCostWei = gasPriceWei.mul(gasUse); const weth = sdk_core_1.WETH9[chainId]; const gasCostInEth = amounts_1.CurrencyAmount.fromRawAmount(weth, totalGasCostWei.toString()); return { gasCostInEth, gasUse }; } async getEthPool(chainId, token, poolProvider) { const weth = sdk_core_1.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)) { 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) { const usdTokens = gas_model_1.usdGasTokensByChain[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, sdk_core_1.WETH9[chainId], ]); const poolAccessor = await poolProvider.getPools(usdPools); const poolsRaw = poolAccessor.getAllPools(); const pools = lodash_1.default.filter(poolsRaw, (pool) => pool.reserve0.greaterThan(0) && pool.reserve1.greaterThan(0)); 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(sdk_core_1.WETH9[chainId])) { return parseFloat(pool.reserve0.toSignificant(2)); } else { return parseFloat(pool.reserve1.toSignificant(2)); } }); return maxPool; } } exports.V2HeuristicGasModelFactory = V2HeuristicGasModelFactory; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidjItaGV1cmlzdGljLWdhcy1tb2RlbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3NyYy9yb3V0ZXJzL2FscGhhLXJvdXRlci9nYXMtbW9kZWxzL3YyL3YyLWhldXJpc3RpYy1nYXMtbW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsd0RBQXFEO0FBQ3JELHdEQUFrRTtBQUVsRSxvREFBdUI7QUFHdkIsMkNBQXVDO0FBQ3ZDLHNEQUEwRDtBQUUxRCw0Q0FLc0I7QUFFdEIsd0RBQXdEO0FBQzNDLFFBQUEsY0FBYyxHQUFHLHFCQUFTLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsK0JBQStCO0FBRXJGLHVDQUF1QztBQUMxQixRQUFBLGtCQUFrQixHQUFHLHFCQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsK0JBQStCO0FBRXhGOzs7Ozs7Ozs7Ozs7Ozs7O0dBZ0JHO0FBQ0gsTUFBYSwwQkFBMkIsU0FBUSw4QkFBa0I7SUFDaEU7UUFDRSxLQUFLLEVBQUUsQ0FBQztJQUNWLENBQUM7SUFFTSxLQUFLLENBQUMsYUFBYSxDQUFDLEVBQ3pCLE9BQU8sRUFDUCxXQUFXLEVBQ1gsWUFBWSxFQUNaLEtBQUssR0FDc0I7UUFDM0IsSUFBSSxLQUFLLENBQUMsTUFBTSxDQUFDLGdCQUFLLENBQUMsT0FBTyxDQUFFLENBQUMsRUFBRTtZQUNqQyxNQUFNLE9BQU8sR0FBUyxNQUFNLElBQUksQ0FBQywwQkFBMEIsQ0FDekQsT0FBTyxFQUNQLFlBQVksQ0FDYixDQUFDO1lBRUYsT0FBTztnQkFDTCxlQUFlLEVBQUUsQ0FBQyxtQkFBMEMsRUFBRSxFQUFFO29CQUM5RCxNQUFNLEVBQUUsWUFBWSxFQUFFLE1BQU0sRUFBRSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQy9DLG1CQUFtQixFQUNuQixXQUFXLEVBQ1gsT0FBTyxDQUNSLENBQUM7b0JBRUYsTUFBTSxTQUFTLEdB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