UNPKG

@syncswap/sdk

Version:

SyncSwap TypeScript SDK for building DeFi applications

1,205 lines 92.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getWETHAddress = exports.GAS_SPONSOR_NFT_MAP = exports.NFTs = exports.ZKSYNC_SEPOLIA_TESTNET = exports.LINEA_MAINNET = exports.ZKCANDY_MAINNET = exports.SOPHON_TESTNET = exports.SOPHON_MAINNET = exports.SCROLL_MAINNET = exports.ZKSYNC_MAINNET = exports.ZKSYNC_TESTNET = exports.SOPHON_MAINNET_USDC = exports.SOPHON_MAINNET_USDT = void 0; exports.getRouteTokens = getRouteTokens; exports.sortTokens = sortTokens; exports.isETH = isETH; exports.getPoolKey = getPoolKey; exports.createPath = createPath; exports.isUSDStablecoin = isUSDStablecoin; exports.hasLiquidity = hasLiquidity; exports.getBlockTimestamp = getBlockTimestamp; exports.sqrtBN = sqrtBN; exports.computeInvariant = computeInvariant; exports.getUnbalancedMintFee = getUnbalancedMintFee; exports.getXCP = getXCP; exports.getCryptoFee = getCryptoFee; exports.calculateCryptoLiquidityFee = calculateCryptoLiquidityFee; exports.computeLiquidity = computeLiquidity; exports.cryptoMathGetY = cryptoMathGetY; exports.cryptoMathComputeD = cryptoMathComputeD; exports.calculateAmountOut = calculateAmountOut; exports.calculateQuoteOut = calculateQuoteOut; exports.calculatePathAmountsByInput = calculatePathAmountsByInput; exports.splitAmount = splitAmount; exports.calculateGroupAmounts = calculateGroupAmounts; exports.computeGasFee = computeGasFee; exports.calculateMinAmount = calculateMinAmount; exports.getAmountMin = getAmountMin; exports.shouldUseV2RouterInterface = shouldUseV2RouterInterface; exports.generateSplitPermitParams = generateSplitPermitParams; exports.createTimeoutPromise = createTimeoutPromise; exports.useGasPrice = useGasPrice; exports.isNFTPaymasterAvailable = isNFTPaymasterAvailable; exports.detectNFTHolder = detectNFTHolder; exports.detectSupportedNFTHolding = detectSupportedNFTHolding; exports.getPayMasterParams = getPayMasterParams; exports.createOverrides = createOverrides; exports.useSlippage = useSlippage; const ethers_1 = require("ethers"); const tokenRegistry_1 = require("./tokens/tokenRegistry"); const numbers_1 = require("./utils/numbers"); const types_1 = require("./router/types"); const constants_1 = require("./utils/constants"); const contractRegistry_1 = __importDefault(require("./contracts/contractRegistry")); const tiny_invariant_1 = __importDefault(require("tiny-invariant")); const token_1 = require("./tokens/token"); const utils_1 = require("ethers/lib/utils"); const statestore_1 = require("./statestore/statestore"); const zksync_web3_1 = require("zksync-web3"); exports.SOPHON_MAINNET_USDT = "0x6386da73545ae4e2b2e0393688fa8b65bb9a7169"; exports.SOPHON_MAINNET_USDC = "0x9aa0f72392b5784ad86c6f3e899bcc053d00db4f"; exports.ZKSYNC_TESTNET = "zkSyncTestnet"; exports.ZKSYNC_MAINNET = "zkSyncMainnet"; exports.SCROLL_MAINNET = "scrollMainnet"; exports.SOPHON_MAINNET = "sophonMainnet"; exports.SOPHON_TESTNET = "sophonTestnet"; exports.ZKCANDY_MAINNET = "zkcandyMainnet"; exports.LINEA_MAINNET = "lineaMainnet"; exports.ZKSYNC_SEPOLIA_TESTNET = "zkSyncSepoliaTestnet"; const GAS_CLASSIC_STEP = ethers_1.BigNumber.from(4697000); // 4697727 const GAS_STABLE_STEP = ethers_1.BigNumber.from(4700000); // 4716100 const GAS_AQUA_STEP = ethers_1.BigNumber.from(5000000); // dummy const GAS_EXTRA_PATH = ethers_1.BigNumber.from(4000000); // 4634420 const GAS_SWAP_BASE = ethers_1.BigNumber.from(7230000); // 7230698 const ETHER_x_4 = constants_1.ETHER.mul(4); const LIQUIDITY_MIN_RESERVE = ethers_1.BigNumber.from(100000); const SOPHON_MAINNET_WSOPH = "0x2b1a859de6a55c553520d7780bc5805712b128f9"; const SOPHON_MAINNET_RTOKENS = [ SOPHON_MAINNET_WSOPH, "0x72af9f169b619d85a47dfa8fefbcd39de55c567d", // ETH exports.SOPHON_MAINNET_USDC, exports.SOPHON_MAINNET_USDT, ]; function getRouteTokens() { const network = (0, statestore_1.stateStore)().network; if (network === exports.SOPHON_MAINNET) { return SOPHON_MAINNET_RTOKENS; } return [(0, tokenRegistry_1.getWETHAddress)()]; } function sortTokens(tokenA, tokenB) { return Number(tokenA) < Number(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]; } function isETH(token) { if (typeof token === 'string' || token instanceof String) { return token.toLowerCase() === token_1.Token.ETHER.address; } else { return token.address === token_1.Token.ETHER.address; } } function getPoolKey(address1, address2) { if (address1 < address2) { return `${address1}:${address2}`; } else { return `${address2}:${address1}`; } } function createPath(stepDataArray) { const steps = []; for (let i = 0; i < stepDataArray.length; i++) { const data = stepDataArray[i]; const pool = data.pool; //console.log('createPath, pool', pool, data); const tokenOut = data.tokenIn === pool.tokenA ? pool.tokenB : pool.tokenA; //console.log('createPath, tokenOut', tokenOut, 'data.tokenIn', data.tokenIn, 'pool.tokenA', pool.tokenA, 'tokenOut', tokenOut, 'zeroForOne', data.tokenIn < tokenOut); const step = { pool: pool, tokenIn: data.tokenIn, swapFee: data.tokenIn === pool.tokenA ? pool.swapFeeAB : pool.swapFeeBA, tokenOut: tokenOut, zeroForOne: data.tokenIn < tokenOut, }; steps.push(step); } return steps; } const EXTRA_USD_STABLECOIN_SYMBOLS = [ // tokens with 'USD' in name are handled automatically. "USDC.e", "USDC", "USDT", "DAI", "FRAX", "MAI", "DOLA", "MIM", "ONEZ", "USX", //"wTBT", "GRAI", "axlUSDC", "axlUSDT", "wDAI", "USDM", "wUSDM", //"MIMATIC", //BOB //HAY //BEAN //"USX", //"FEI". //"RSV", //"XAI", //"Hue", ]; /* function hasKeywordOnToken(token: Token, keyword: string): boolean { return token.name.includes(keyword) || token.symbol.includes(keyword); } */ function isUSDStablecoin(token) { if (!token) { return false; } // check symbol if (token.symbol) { if (token.symbol === "USDC" || token.symbol === "USDT" || token.symbol === "USDC.e" || token.symbol === "DAI" || token.symbol === "USN") { return true; } // wrapped tokens if (token.symbol === "hsUSDC" || token.symbol === "sDAI") { return false; } // wrapped tokens by symbol if (token.symbol.startsWith("w")) { return false; } if (token.symbol.includes("USD")) { return true; } if (EXTRA_USD_STABLECOIN_SYMBOLS.indexOf(token.symbol) !== -1) { return true; } } if (token.name) { if (token.name.includes("rapped")) { // Wrapped return false; } if (token.name.includes("USD")) { return true; } const name = token.name.toLowerCase(); return name.includes("dollar") || name.includes("stable"); } return false; } function hasLiquidity(data, tokenIn, amountIn) { if (tokenIn && amountIn) { const reserveIn = data.tokenA.toLowerCase() === tokenIn.toLowerCase() ? data.reserveA : data.reserveB; return reserveIn.gt(amountIn.div(10)); } else { if (tokenIn) { const isTokenAIn = data.tokenA.toLowerCase() === tokenIn.toLowerCase(); const tokenInData = tokenRegistry_1.TokenRegistry.getTokenByAddress(isTokenAIn ? data.tokenA : data.tokenB); if (tokenInData && !tokenInData.symbol.includes("USN") && isUSDStablecoin(tokenInData)) { const tokenInReserves = isTokenAIn ? data.reserveA : data.reserveB; return tokenInReserves.gte(numbers_1.Numbers.pow(tokenInData.decimals).mul(100)); } } return (data.reserveA.gte(LIQUIDITY_MIN_RESERVE) && data.reserveB.gte(LIQUIDITY_MIN_RESERVE)); } } const MAX_LOOP_LIMIT = 256; const MAX_FEE = ethers_1.BigNumber.from(100000); // 1e5 const TWO_MAX_FEE = MAX_FEE.mul(2); // 1e5 const MINIMUM_LIQUIDITY = ethers_1.BigNumber.from(1000); const ZERO_STEP_AMOUNT_OUT = [constants_1.ZERO, constants_1.ZERO, null, null]; let cacheAmountOutForStepMap = new Map(); let pathAmountsCacheMapIn = new Map(); // copy a swap step and its pool data function copyStep(step) { const pool = step.pool; const cryptoPoolData = []; if (pool.poolType === types_1.PoolTypes.AQUA && pool.cryptoPoolData) { const data = pool.cryptoPoolData[0]; cryptoPoolData.push({ gamma: data.gamma, futureParamsTime: data.futureParamsTime, invariantLast: data.invariantLast, priceScale: data.priceScale, virtualPrice: data.virtualPrice, totalSupply: data.totalSupply, }); } const rangePoolData = []; if (pool.poolType === types_1.PoolTypes.UNISWAP_V3 && pool.rangePoolData) { const data = pool.rangePoolData[0]; rangePoolData.push({ ...data, }); } return { pool: { isV2: pool.isV2, isV3: pool.isV3, isUniswap: pool.isUniswap, //vault: pool.vault, pool: pool.pool, tokenA: pool.tokenA, tokenB: pool.tokenB, poolType: pool.poolType, reserveA: ethers_1.BigNumber.from(pool.reserveA), reserveB: ethers_1.BigNumber.from(pool.reserveB), swapFeeAB: pool.swapFeeAB, swapFeeBA: pool.swapFeeBA, // aqua pool params a: pool.a, cryptoPoolData: cryptoPoolData, // uniswap v3 pool params rangePoolData: rangePoolData, }, //tokenA: step.pool.tokenA, //tokenB: step.pool.tokenB, tokenIn: step.tokenIn, swapFee: step.swapFee, tokenOut: step.tokenOut, zeroForOne: step.zeroForOne, }; } function hashAmountForStep(step, amountIn) { let hash = `${step.pool.reserveA?.toHexString()}:${step.tokenIn}:${amountIn?.toHexString()}`; if (step.pool.cryptoPoolData && step.pool.cryptoPoolData.length > 0) { hash += step.pool.cryptoPoolData[0]?.priceScale?.toHexString(); } if (step.pool.rangePoolData && step.pool.rangePoolData.length > 0) { hash += step.pool.rangePoolData[0]?.sqrtPriceX96?.toHexString(); } return hash; } function getAmountOutClassic(params, checkOverflow) { const amountIn = params.amount; const reserveIn = params.reserveIn; if (checkOverflow && reserveIn.add(amountIn).gt(constants_1.UINT128_MAX)) { //console.log('getAmountOutClassic overflow 0'); throw Error("overflow"); } const swapFee = params.swapFee.maxFee; const amountInWithFee = amountIn.mul(MAX_FEE.sub(swapFee)); if (checkOverflow && amountInWithFee.gt(constants_1.UINT256_MAX)) { //console.log('getAmountOutClassic overflow 1'); throw Error("overflow"); } const numerator = amountInWithFee.mul(params.reserveOut); if (checkOverflow && numerator.gt(constants_1.UINT256_MAX)) { //console.log('getAmountOutClassic overflow 2'); throw Error("overflow"); } const denominator = params.reserveIn.mul(MAX_FEE).add(amountInWithFee); if (checkOverflow && denominator.gt(constants_1.UINT256_MAX)) { //console.log('getAmountOutClassic overflow 3'); throw Error("overflow"); } return numerator.div(denominator); } const MAX_XP = constants_1.UINT128_MAX; //BigNumber.from('3802571709128108338056982581425910818'); const DEFAULT_STABLE_POOL_A = ethers_1.BigNumber.from(1000); function getBlockTimestamp() { return ethers_1.BigNumber.from(Math.floor(Date.now() / 1000)); } function getY(A, x, d) { const nA = A.mul(constants_1.TWO); const c = d.mul(d).div(x.mul(constants_1.TWO)).mul(d).div(nA.mul(constants_1.TWO)); const b = d.div(nA).add(x); let yPrev; let y = d; for (let i = 0; i < MAX_LOOP_LIMIT; i++) { yPrev = y; y = y.mul(y).add(c).div(y.mul(constants_1.TWO).add(b).sub(d)); if (y.sub(yPrev).abs().lte(constants_1.ONE)) { break; } } return y; } function getAmountOutStable(params, checkOverflow) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const adjustedReserveIn = params.reserveIn.mul(params.tokenInPrecisionMultiplier); if (checkOverflow && adjustedReserveIn.gt(MAX_XP)) { throw Error("overflow"); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const adjustedReserveOut = params.reserveOut.mul(params.tokenOutPrecisionMultiplier); if (checkOverflow && adjustedReserveOut.gt(MAX_XP)) { throw Error("overflow"); } const amountIn = params.amount; const swapFee = params.swapFee.maxFee; const feeDeductedAmountIn = amountIn.sub(amountIn.mul(swapFee).div(MAX_FEE)); const a = params.a ?? DEFAULT_STABLE_POOL_A; const d = computeDFromAdjustedBalances(a, adjustedReserveIn, adjustedReserveOut, checkOverflow); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const x = adjustedReserveIn.add(feeDeductedAmountIn.mul(params.tokenInPrecisionMultiplier)); const y = getY(a, x, d); const dy = adjustedReserveOut.sub(y).sub(1); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const amountOut = dy.div(params.tokenOutPrecisionMultiplier); //console.log('getAmountOutStable: amountOut', amountOut.toString(), 'amountIn', amountIn.toString(), 'reserveIn', params.reserveIn.toString(), 'reserveOut', params.reserveOut.toString(), 'tokenInPrecisionMultiplier', params.tokenInPrecisionMultiplier?.toString(), 'tokenOutPrecisionMultiplier', params.tokenOutPrecisionMultiplier?.toString()); return amountOut; } function sqrtBN(x) { let z = x.add(constants_1.ONE).div(constants_1.TWO); let y = x; while (z.sub(y).isNegative()) { y = z; z = x.div(z).add(z).div(constants_1.TWO); } return y; } function computeInvariant(poolType, reserveA, reserveB, tokenAPrecisionMultiplier, tokenBPrecisionMultiplier, a, gamma) { if (poolType === types_1.PoolTypes.STABLE) { (0, tiny_invariant_1.default)(tokenAPrecisionMultiplier && tokenBPrecisionMultiplier && a, "multiplier"); const adjustedReserveA = reserveA.mul(tokenAPrecisionMultiplier); const adjustedReserveB = reserveB.mul(tokenBPrecisionMultiplier); return computeDFromAdjustedBalances(a, adjustedReserveA, adjustedReserveB, true); } if (poolType === types_1.PoolTypes.AQUA) { (0, tiny_invariant_1.default)(tokenAPrecisionMultiplier && tokenBPrecisionMultiplier && a && gamma, "multiplier"); const adjustedReserveA = reserveA.mul(tokenAPrecisionMultiplier); const adjustedReserveB = reserveB.mul(tokenBPrecisionMultiplier); return cryptoMathComputeD(a, gamma, adjustedReserveA, adjustedReserveB); } if (poolType === types_1.PoolTypes.CLASSIC) { if (reserveA.gt(constants_1.UINT128_MAX) || reserveB.gt(constants_1.UINT128_MAX)) { throw Error("overflow"); } return sqrtBN(reserveA.mul(reserveB)); } return constants_1.ZERO; } function getUnbalancedMintFee(isV2, amountA, amountB, reserveA, reserveB, swapFeeAB, swapFeeBA) { if (reserveA.isZero() || reserveB.isZero()) { return [constants_1.ZERO, constants_1.ZERO]; } const amountBOptimal = amountA.mul(reserveB).div(reserveA); if (amountB.gte(amountBOptimal)) { // swap B for A const tokenBFee = swapFeeBA .mul(amountB.sub(amountBOptimal)) .div(isV2 ? MAX_FEE : TWO_MAX_FEE); return [constants_1.ZERO, tokenBFee]; } else { const amountAOptimal = amountB.mul(reserveA).div(reserveB); const tokenAFee = swapFeeAB .mul(amountA.sub(amountAOptimal)) .div(isV2 ? MAX_FEE : TWO_MAX_FEE); return [tokenAFee, constants_1.ZERO]; } } function getXCP(invariant, priceScale) { return geometricMean(invariant.div(2), invariant.mul(constants_1.ETHER).div(priceScale.mul(2))); } function getCryptoFee(feeData, xp0, xp1) { const gamma = feeData.gamma; const minFee = ethers_1.BigNumber.from(feeData.minFee); const maxFee = ethers_1.BigNumber.from(feeData.maxFee); let f = xp0.add(xp1); f = gamma .mul(constants_1.ETHER) .div(gamma.add(constants_1.ETHER).sub(ETHER_x_4.mul(xp1).mul(xp0).div(f).div(f))); const fee = minFee .mul(f) .add(maxFee.mul(constants_1.ETHER.sub(f))) .div(constants_1.ETHER); return fee; } function calculateCryptoLiquidityFee(feeData, amount0, amount1, xp0, xp1) { const fee = getCryptoFee(feeData, xp0, xp1).div(2); const sum = amount0.add(amount1); if (sum.isZero()) { return constants_1.ZERO; } const avg = sum.div(2); let sdiff; if (amount0.gt(avg)) { sdiff = amount0.sub(avg); } else { sdiff = avg.sub(amount0); } if (amount1.gt(avg)) { sdiff = sdiff.add(amount1.sub(avg)); } else { sdiff = sdiff.add(avg.sub(amount1)); } return fee.mul(sdiff).div(sum).add(1); } // compute liquidity amount by pool states and input amounts, returns expected and quote function computeLiquidity(isV2, poolType, amountA, amountB, reserveA, reserveB, swapFeeAB, swapFeeBA, totalSupply, isTokenA0, tokenAPrecisionMultiplier, tokenBPrecisionMultiplier, a, cryptoData) { if (poolType === types_1.PoolTypes.UNISWAP_V3) { return [constants_1.ZERO, constants_1.ZERO]; } // Aqua Pools if (poolType === types_1.PoolTypes.AQUA) { const newReserveA = reserveA.add(amountA); const newReserveB = reserveB.add(amountB); if (newReserveA.gt(constants_1.UINT128_MAX) || newReserveB.gt(constants_1.UINT128_MAX)) { throw Error("overflow"); } (0, tiny_invariant_1.default)(tokenAPrecisionMultiplier && tokenBPrecisionMultiplier && a && cryptoData, "multiplier"); const priceScale = cryptoData.priceScale; const xpA = isTokenA0 ? newReserveA.mul(tokenAPrecisionMultiplier) : newReserveA.mul(priceScale).mul(tokenAPrecisionMultiplier).div(constants_1.ETHER); //const priceScaleB = priceScale.mul(tokenBPrecisionMultiplier); const xpB = isTokenA0 ? newReserveB.mul(priceScale).mul(tokenBPrecisionMultiplier).div(constants_1.ETHER) : newReserveB.mul(tokenBPrecisionMultiplier); const oldXpA = isTokenA0 ? reserveA.mul(tokenAPrecisionMultiplier) : reserveA.mul(priceScale).mul(tokenAPrecisionMultiplier).div(constants_1.ETHER); const oldXpB = isTokenA0 ? reserveB.mul(priceScale).mul(tokenBPrecisionMultiplier).div(constants_1.ETHER) : reserveB.mul(tokenBPrecisionMultiplier); let oldInvariant; if (cryptoData.futureParamsTime.gt(getBlockTimestamp())) { oldInvariant = cryptoMathComputeD(a, cryptoData.gamma, oldXpA, oldXpB); } else { oldInvariant = cryptoData.invariantLast; } const newInvariant = cryptoMathComputeD(a, cryptoData.gamma, xpA, xpB); let liquidity; if (!totalSupply.isZero() && !oldInvariant.isZero()) { liquidity = totalSupply .mul(newInvariant) .div(oldInvariant) .sub(totalSupply); //console.log("computeLiquidity: oldInvariant is not zero, totalSupply", totalSupply.toString(), "newInvariant", newInvariant.toString(), "totalSupply.mul(newInvariant)", totalSupply.mul(newInvariant).toString()); } else { liquidity = getXCP(newInvariant, priceScale); if (!newReserveB.isZero() && !newReserveA.isZero()) { const newPriceScale = isTokenA0 ? constants_1.ETHER.mul(xpA).div(newReserveB.mul(tokenBPrecisionMultiplier)) : constants_1.ETHER.mul(xpB).div(newReserveA.mul(tokenAPrecisionMultiplier)); if (newPriceScale.gt(priceScale.mul(2)) || newPriceScale.lt(priceScale.div(2))) { console.log("loss newPriceScale", newPriceScale.toString(), "priceScale", priceScale.toString(), "isTokenA0", isTokenA0, "xpA", xpA.toString(), "xpB", xpB.toString()); throw Error("loss"); } } } //console.log("computeLiquidity: newInvariant", newInvariant.toString(), "oldInvariant", oldInvariant.toString(), "liquidity", liquidity.toString(), "cryptoData.priceScale", cryptoData.priceScale.toString(), "cryptoData.invariantLast", cryptoData.invariantLast.toString()); let liquidityWithoutFee = liquidity; let mintFee; const amountBOptimal = reserveA.isZero() ? constants_1.ZERO : amountA.mul(reserveB).div(reserveA); if (amountB.lt(amountBOptimal)) { mintFee = swapFeeAB; } else { mintFee = swapFeeBA; } if (!oldInvariant.isZero()) { const swapFee = calculateCryptoLiquidityFee(mintFee, xpA.sub(oldXpA), xpB.sub(oldXpB), xpA, xpB); let feeAmount = liquidity.mul(swapFee).div(constants_1.DECIMALS_5).add(1); if (feeAmount.lt(constants_1.ZERO)) { feeAmount = constants_1.ZERO; } liquidity = liquidity.sub(feeAmount); } if (liquidity.lt(constants_1.ZERO)) { liquidity = constants_1.ZERO; } if (liquidityWithoutFee.lt(constants_1.ZERO)) { liquidityWithoutFee = constants_1.ZERO; } return [liquidity, liquidityWithoutFee]; } // Classic / Stable Pools // fees are added to old invariant const [feeA, feeB] = getUnbalancedMintFee(isV2, amountA, amountB, reserveA, reserveB, ethers_1.BigNumber.from(swapFeeAB.maxFee), ethers_1.BigNumber.from(swapFeeBA.maxFee)); // add fees onto old reserves const reserveAWithFee = reserveA.add(feeA); const reserveBWithFee = reserveB.add(feeB); // compute old invariant const oldInvariant = computeInvariant(poolType, reserveAWithFee, reserveBWithFee, tokenAPrecisionMultiplier, tokenBPrecisionMultiplier, a, cryptoData?.gamma); const oldInvariantWithoutFee = computeInvariant(poolType, reserveA, reserveB, tokenAPrecisionMultiplier, tokenBPrecisionMultiplier, a, cryptoData?.gamma); // compute new invariant const newReserveA = reserveA.add(amountA); const newReserveB = reserveB.add(amountB); if (newReserveA.gt(constants_1.UINT128_MAX) || newReserveB.gt(constants_1.UINT128_MAX)) { throw Error("overflow"); } const newInvariant = computeInvariant(poolType, newReserveA, newReserveB, tokenAPrecisionMultiplier, tokenBPrecisionMultiplier, a, cryptoData?.gamma); if (totalSupply.isZero()) { // sub min liquidity from first mint const liquidity = newInvariant.sub(MINIMUM_LIQUIDITY); return [liquidity, liquidity]; } else { // calculate liquidity with invariant delta const liquidity = newInvariant .sub(oldInvariant) .mul(totalSupply) .div(oldInvariant); const liquidityWithoutFee = newInvariant .sub(oldInvariantWithoutFee) .mul(totalSupply) .div(oldInvariantWithoutFee); return [liquidity, liquidityWithoutFee]; } } function computeDFromAdjustedBalances(A, xp0, xp1, checkOverflow) { const s = xp0.add(xp1); if (s.isZero()) { return constants_1.ZERO; } else { let prevD; let d = s; const nA = A.mul(constants_1.TWO); for (let i = 0; i < MAX_LOOP_LIMIT; i++) { const dSq = d.mul(d); if (checkOverflow && dSq.gt(constants_1.UINT256_MAX)) { throw Error("overflow"); } const d2 = dSq.div(xp0).mul(d); if (checkOverflow && d2.gt(constants_1.UINT256_MAX)) { throw Error("overflow"); } const dP = d2.div(xp1).div(constants_1.FOUR); prevD = d; const d0 = nA.mul(s).add(dP.mul(constants_1.TWO)).mul(d); if (checkOverflow && d0.gt(constants_1.UINT256_MAX)) { throw Error("overflow"); } d = d0.div(nA.sub(constants_1.ONE).mul(d).add(dP.mul(constants_1.THREE))); if (d.sub(prevD).abs().lte(constants_1.ONE)) { return d; } } return d; } } const A_MULTIPLIER = ethers_1.BigNumber.from(10000); const N_COINS = ethers_1.BigNumber.from(2); function hashGetY(ANN, gamma, x0, x1, D, i) { return (ANN.toHexString() + gamma.toHexString() + x0.toHexString() + x1.toHexString() + D.toHexString() + i); } let getYCacheMap = new Map(); function cryptoMathGetY(ANN, gamma, x0, x1, D, i) { //invariant(D.gte(DECIMALS_17) && D.lte(DECIMALS_33), "unsafe values D"); if (!(D.gte(constants_1.DECIMALS_17) && D.lte(constants_1.DECIMALS_33))) { //console.warn("cryptoMathGetY: unsafe values D", D.toString()); return constants_1.ZERO; } // cache const hash = hashGetY(ANN, gamma, x0, x1, D, i); const cached = getYCacheMap.get(hash); if (cached !== undefined) { //console.log('[cryptoMathGetY] hit cache, size', getYCacheMap.size); return cached; } let x_j = x0; if (i === 0) { x_j = x1; } let y = D.pow(constants_1.TWO).div(x_j.mul(N_COINS.pow(constants_1.TWO))); const K0_i = constants_1.ETHER.mul(N_COINS).mul(x_j).div(D); //invariant(K0_i.gte(DECIMALS_16.mul(N_COINS)) && K0_i.lte(DECIMALS_20.mul(N_COINS)), "cryptoMathGetY: unsafe values x[i]"); if (!(K0_i.gte(constants_1.DECIMALS_16.mul(N_COINS)) && K0_i.lte(constants_1.DECIMALS_20.mul(N_COINS)))) { //console.warn("cryptoMathGetY: unsafe values x[i]", K0_i.toString()); return constants_1.ZERO; } const convergence_limit = max(max(x_j.div(constants_1.DECIMALS_14), D.div(constants_1.DECIMALS_14)), constants_1.DECIMALS_2); const __g1k0 = gamma.add(constants_1.ETHER); for (let j = 0; j < 255; j++) { const y_prev = y; const K0 = K0_i.mul(y).mul(N_COINS).div(D); const S = x_j.add(y); let _g1k0 = __g1k0; if (_g1k0.gt(K0)) { _g1k0 = _g1k0.sub(K0).add(1); } else { _g1k0 = K0.sub(_g1k0).add(1); } const mul1 = constants_1.DECIMALS_18.mul(D) .div(gamma) .mul(_g1k0) .div(gamma) .mul(_g1k0) .mul(A_MULTIPLIER) .div(ANN); const mul2 = constants_1.DECIMALS_18.add(constants_1.TWO.mul(constants_1.DECIMALS_18).mul(K0)).div(_g1k0); let yfprime = constants_1.DECIMALS_18.mul(y).add(S.mul(mul2)).add(mul1); const _dyfprime = D.mul(mul2); if (yfprime.lt(_dyfprime)) { y = y_prev.div(constants_1.TWO); continue; } else { yfprime = yfprime.sub(_dyfprime); } const fprime = yfprime.div(y); const y_minus_temp = mul1.div(fprime); const y_plus = yfprime .add(constants_1.ETHER.mul(D)) .div(fprime) .add(y_minus_temp.mul(constants_1.ETHER).div(K0)); const y_minus = y_minus_temp.add(constants_1.DECIMALS_18.mul(S).div(fprime)); if (y_plus.lt(y_minus)) { y = y_prev.div(2); } else { y = y_plus.sub(y_minus); } let diff; if (y.gt(y_prev)) { diff = y.sub(y_prev); } else { diff = y_prev.sub(y); } if (diff.lt(max(convergence_limit, y.div(constants_1.DECIMALS_14)))) { const frac = y.mul(constants_1.DECIMALS_18).div(D); //invariant(frac.gte(DECIMALS_16) && frac.lte(DECIMALS_20), "cryptoMathGetY: unsafe value for y"); if (!(frac.gte(constants_1.DECIMALS_16) && frac.lte(constants_1.DECIMALS_20))) { //console.warn("cryptoMathGetY: unsafe values for y", frac.toString()); return constants_1.ZERO; } if (getYCacheMap.size > 256) { getYCacheMap = new Map(); } getYCacheMap.set(hash, y); return y; } } //throw new Error("Did not converge"); console.warn("Did not converge"); return constants_1.ZERO; } function hashComputeD(ANN, gamma, x0, x1) { return (ANN.toHexString() + gamma.toHexString() + x0.toHexString() + x1.toHexString()); } let computeDCacheMap = new Map(); function cryptoMathComputeD(ANN, gamma, x0, x1) { //console.log('cryptoMathComputeD ANN', ANN.toString(), 'gamma', gamma.toString(), 'x0', x0.toString(), 'x1', x1.toString()); if (x0.isZero() || x1.isZero()) { return constants_1.ZERO; } // Initial value of invariant D is that for constant-product invariant let x; //[x0, x1].sort((a, b) => b.sub(a).isNegative() ? -1 : 1); if (x0.lt(x1)) { x = [x1, x0]; } else { x = [x0, x1]; } if (!(x[0].gte(constants_1.DECIMALS_9) && x[0].lte(constants_1.DECIMALS_33))) { //console.warn("cryptoMathComputeD: unsafe values x[0]", x[0].toString()); return constants_1.ZERO; } if (!x[1].mul(constants_1.DECIMALS_18).div(x[0]).gte(constants_1.DECIMALS_14)) { //console.warn("cryptoMathComputeD: unsafe values x[i] (input)", x[1].toString()); return constants_1.ZERO; } //invariant(x[0].gte(DECIMALS_9) && x[0].lte(DECIMALS_33), "cryptoMathComputeD: unsafe values x[0]"); //invariant(x[1].mul(DECIMALS_18).div(x[0]).gte(DECIMALS_14), "cryptoMathComputeD: unsafe values x[i] (input)"); // cache const hash = hashComputeD(ANN, gamma, x0, x1); const cached = computeDCacheMap.get(hash); if (cached !== undefined) { //console.log('[cryptoMathComputeD] hit cache, size', computeDCacheMap.size); return cached; } let D = N_COINS.mul(geometricMean(x0, x1)); const S = x0.add(x1); const __g1k0 = gamma.add(constants_1.DECIMALS_18); for (let i = 0; i < 255; i++) { const D_prev = D; //invariant(D.gt(ZERO), "Unsafe value D"); if (D.lte(constants_1.ZERO)) { //console.warn("Unsafe value D", D.toString()); return constants_1.ZERO; } const K0 = constants_1.DECIMALS_18.mul(N_COINS.pow(2)) .mul(x[0]) .div(D) .mul(x[1]) .div(D); let _g1k0 = __g1k0; if (_g1k0.gt(K0)) { _g1k0 = _g1k0.sub(K0).add(1); } else { _g1k0 = K0.sub(_g1k0).add(1); } const mul1 = constants_1.DECIMALS_18.mul(D) .div(gamma) .mul(_g1k0) .div(gamma) .mul(_g1k0) .mul(A_MULTIPLIER) .div(ANN); const mul2 = constants_1.TWO.mul(constants_1.DECIMALS_18).mul(N_COINS).mul(K0).div(_g1k0); const neg_fprime = S.add(S.mul(mul2).div(constants_1.DECIMALS_18)) .add(mul1.mul(N_COINS).div(K0)) .sub(mul2.mul(D).div(constants_1.DECIMALS_18)); const D_plus = D.mul(neg_fprime.add(S)).div(neg_fprime); let D_minus = D.mul(D).div(neg_fprime); if (constants_1.DECIMALS_18.gt(K0)) { D_minus = D_minus.add(D.mul(mul1.div(neg_fprime)) .div(constants_1.DECIMALS_18) .mul(constants_1.DECIMALS_18.sub(K0)) .div(K0)); } else { D_minus = D_minus.sub(D.mul(mul1.div(neg_fprime)) .div(constants_1.DECIMALS_18) .mul(K0.sub(constants_1.DECIMALS_18)) .div(K0)); } if (D_plus.gt(D_minus)) { D = D_plus.sub(D_minus); } else { D = D_minus.sub(D_plus).div(2); } const diff = D.gt(D_prev) ? D.sub(D_prev) : D_prev.sub(D); if (diff.mul(constants_1.DECIMALS_14).lt(max(constants_1.DECIMALS_16, D))) { const frac = x0.mul(constants_1.DECIMALS_18).div(D); if (!(frac.gte(constants_1.DECIMALS_16) && frac.lte(constants_1.DECIMALS_20))) { //console.warn("cryptoMathComputeD: unsafe values x[i]-1", frac.toString()); return constants_1.ZERO; } //nvariant(frac.gte(DECIMALS_16) && frac.lte(DECIMALS_20), "cryptoMathComputeD: unsafe values x[i]-1"); const frac1 = x1.mul(constants_1.DECIMALS_18).div(D); if (!(frac1.gte(constants_1.DECIMALS_16) && frac1.lte(constants_1.DECIMALS_20))) { //console.warn("cryptoMathComputeD: unsafe values x[i]-2", frac1.toString()); return constants_1.ZERO; } //invariant(frac1.gte(DECIMALS_16) && frac1.lte(DECIMALS_20), "cryptoMathComputeD: unsafe values x[i]-2"); // cache if (computeDCacheMap.size > 256) { computeDCacheMap = new Map(); } computeDCacheMap.set(hash, D); return D; } } //throw new Error("Did not converge"); console.warn("Did not converge"); return constants_1.ZERO; } function geometricMean(x0, x1) { // Assuming ethers has sqrt method for BigNumber (This may be an oversimplification, and a more detailed algorithm might be needed for accurate square root computation) return sqrtBN(x0.mul(x1)); } function max(x, y) { return x.gt(y) ? x : y; } function estimateTweakPriceAqua(a, gamma, xp0, xp1, newInvariant, oldVirtualPrice, priceScale, totalSupply, futureParamsTime) { //console.log('estimateTweakPriceAqua', a, gamma, xp0, xp1, newInvariant, oldVirtualPrice, priceScale, totalSupply, futureParamsTime); //console.log('estimateTweakPriceAqua futureParamsTime', futureParamsTime.toString(), 'oldVirtualPrice', oldVirtualPrice.toString()); let unadjustedInvariant; if (newInvariant.isZero()) { unadjustedInvariant = cryptoMathComputeD(a, gamma, xp0, xp1); } else { unadjustedInvariant = newInvariant; } if (!oldVirtualPrice.isZero()) { const _xp0 = unadjustedInvariant.div(2); const _xp1 = constants_1.ETHER.mul(unadjustedInvariant).div(priceScale.mul(2)); const xcp = geometricMean(_xp0, _xp1); const newVirtualPrice = constants_1.ETHER.mul(xcp).div(totalSupply); if (futureParamsTime.lt(getBlockTimestamp())) { if (newVirtualPrice.lt(oldVirtualPrice)) { console.warn("estimateTweakPriceAqua: loss, newVirtualPrice", newVirtualPrice.toString(), "oldVirtualPrice", oldVirtualPrice.toString(), "xcp", xcp.toString(), "newXp0", _xp0.toString(), "newXp1", _xp1.toString(), "priceScale", priceScale.toString()); throw Error("loss"); } } } } function hashGetAmountOutAquaParams(params) { return `${params.tokenIn}_${params.tokenOut}_${params.amount.toHexString()}_${params.reserveIn?.toHexString()}_${params.reserveOut?.toHexString()}_${params.priceScale?.toHexString()}_${params.virtualPrice?.toHexString()}_${params.invariantLast?.toHexString()}_${params.gamma?.toHexString()}`; } let getAmountOutAquaCacheMap = new Map(); function getCachedAmountOutAquaResult(hash) { const cachedResult = getAmountOutAquaCacheMap.get(hash); if (cachedResult) { if (Date.now() - cachedResult.timestamp <= 60000) { //console.log('[debug] getCachedAmountOutAquaResult: hit cache, size', getAmountOutAquaCacheMap.size); return cachedResult.amountOut; } else { //console.log('[debug] getCachedAmountOutAquaResult: found expired'); } } if (getAmountOutAquaCacheMap.size > 256) { //console.log('[debug] cache cleared'); getAmountOutAquaCacheMap = new Map(); } return null; } function getAmountOutAqua(params, checkOverflow, shouldEstimateTweakPrice) { //console.log('getAmountOutAqua', params, checkOverflow); if (params.reserveIn.isZero() || params.amount.isZero()) { //console.log('getAmountOutAqua reserveIn or amount is zero'); return constants_1.ZERO; } if (!params.a || !params.gamma || !params.invariantLast || !params.priceScale || !params.futureParamsTime || !params.totalSupply || !params.virtualPrice) { console.warn("getAmountOutAqua: incomplete params", params); return constants_1.ZERO; } if (params.a?.isZero() || params.gamma?.isZero() || params.invariantLast?.isZero()) { console.warn("getAmountOutAqua: invalid params", params); return constants_1.ZERO; } const hash = hashGetAmountOutAquaParams(params); const cachedResult = getCachedAmountOutAquaResult(hash); if (cachedResult) { return cachedResult; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const tokenInPrecisionMultiplier = params.tokenInPrecisionMultiplier; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const tokenOutPrecisionMultiplier = params.tokenOutPrecisionMultiplier; // Get XPs. const amountIn = params.amount; const balanceIn = params.reserveIn.add(amountIn); const balanceOut = params.reserveOut; const priceScale = params.priceScale; /* params.swapFee = { gamma: BigNumber.from(10000000000), minFee: 100, maxFee: 100, }; console.log('getAmountOutAqua params', params, 'swapFee', params.swapFee); */ let xpIn; let xpOut; if (params.swap0For1) { xpIn = balanceIn.mul(tokenInPrecisionMultiplier); xpOut = balanceOut .mul(priceScale) .mul(tokenOutPrecisionMultiplier) .div(constants_1.ETHER); } else { xpIn = balanceIn.mul(priceScale).mul(tokenInPrecisionMultiplier).div(constants_1.ETHER); xpOut = balanceOut.mul(tokenOutPrecisionMultiplier); } let invariant; if (params.futureParamsTime.gt(getBlockTimestamp())) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion let oldXpIn; if (params.swap0For1) { oldXpIn = params.reserveIn.mul(tokenInPrecisionMultiplier); invariant = cryptoMathComputeD(params.a, params.gamma, oldXpIn, xpOut); } else { oldXpIn = params.reserveIn .mul(priceScale) .mul(tokenInPrecisionMultiplier) .div(constants_1.ETHER); invariant = cryptoMathComputeD(params.a, params.gamma, xpOut, oldXpIn); } } else { invariant = params.invariantLast; } if (checkOverflow && balanceIn.gt(constants_1.UINT128_MAX)) { //console.log('getAmountOutAqua overflow'); throw Error("overflow"); } let amountOutAfterFee; let newXpOut; // used for estimateTweakPriceAqua //console.log('getAmountOutAqua: a', params.a.toString(), 'gamma', params.gamma.toString(), 'futureParamsTime', params.futureParamsTime.toString(), 'now', SwapUtils.blockTimestamp().toString()); if (params.swap0For1) { const y = cryptoMathGetY(params.a, params.gamma, xpIn, xpOut, invariant, 1); const yOut = xpOut.sub(y); if (yOut.lte(constants_1.ONE)) { //console.log('getAmountOutAqua: zero out 1', xpIn.toString(), xpOut.toString(), invariant.toString()); return constants_1.ZERO; } // modify xp out newXpOut = xpOut.sub(yOut); const intermediate = yOut.sub(constants_1.ONE).mul(constants_1.ETHER); const priceScale_x_precision = priceScale.mul(tokenOutPrecisionMultiplier); //const amountOut = yOut.sub(ONE).mul(ETHER).div(priceScale.mul(tokenOutPrecisionMultiplier)); const amountOut = intermediate.div(priceScale_x_precision); const swapFee = getCryptoFee(params.swapFee, xpIn, newXpOut); const amountFee = intermediate .mul(swapFee) .div(priceScale_x_precision) .div(constants_1.DECIMALS_5); amountOutAfterFee = amountOut.sub(amountFee); //console.log('getAmountOutAqua: swapFee', swapFee.toString(), 'amountFee', amountFee.toString(), '0For1'); if (shouldEstimateTweakPrice) { // update xp out newXpOut = params.reserveOut .sub(amountOutAfterFee) .mul(priceScale_x_precision) .div(constants_1.ETHER); } } else { const y = cryptoMathGetY(params.a, params.gamma, xpOut, xpIn, invariant, 0); const yOut = xpOut.sub(y); if (yOut.lte(constants_1.ONE)) { //console.log('getAmountOutAqua: zero out 0', xpOut.toString(), xpIn.toString(), invariant.toString()); return constants_1.ZERO; } // modify xp out newXpOut = xpOut.sub(yOut); const intermediate = yOut.sub(constants_1.ONE); //const amountOut = yOut.sub(ONE).div(tokenOutPrecisionMultiplier); const amountOut = intermediate.div(tokenOutPrecisionMultiplier); const swapFee = getCryptoFee(params.swapFee, newXpOut, xpIn); const amountFee = intermediate .mul(swapFee) .div(tokenOutPrecisionMultiplier) .div(constants_1.DECIMALS_5); amountOutAfterFee = amountOut.sub(amountFee); //console.log('getAmountOutAqua: swapFee', swapFee.toString(), 'amountFee', amountFee.toString(), '1For0'); if (shouldEstimateTweakPrice) { // update xp out newXpOut = params.reserveOut .sub(amountOutAfterFee) .mul(tokenOutPrecisionMultiplier); } } if (shouldEstimateTweakPrice) { if (params.swap0For1) { estimateTweakPriceAqua(params.a, params.gamma, xpIn, newXpOut, constants_1.ZERO, params.virtualPrice, priceScale, params.totalSupply, params.futureParamsTime); } else { estimateTweakPriceAqua(params.a, params.gamma, newXpOut, xpIn, constants_1.ZERO, params.virtualPrice, priceScale, params.totalSupply, params.futureParamsTime); } } if (amountOutAfterFee.lte(constants_1.ZERO)) { getAmountOutAquaCacheMap.set(hash, { amountOut: constants_1.ZERO, timestamp: Date.now(), }); return constants_1.ZERO; } else { getAmountOutAquaCacheMap.set(hash, { amountOut: amountOutAfterFee, timestamp: Date.now(), }); return amountOutAfterFee; } } let cachedQuoterInfo = null; let getAmountOutUniswapV3CacheMap = new Map(); function hashGetAmountOutUniswapV3Params(params) { return `${params.tokenIn}_${params.tokenOut}_${params.amount.toHexString()}_${params.tickSpacing}`; } function getCachedAmountOutUniswapV3Result(hash) { const cachedResult = getAmountOutUniswapV3CacheMap.get(hash); if (cachedResult) { if (Date.now() - cachedResult.timestamp <= 10000) { //console.log('[debug] getCachedAmountOutUniswapV3Result: hit cache, size', getAmountOutUniswapV3CacheMap.size); return cachedResult.amountOut; } else { //console.log('[debug] getCachedAmountOutUniswapV3Result: found expired'); } } if (getAmountOutUniswapV3CacheMap.size > 256) { //console.log('[debug] cache cleared'); getAmountOutUniswapV3CacheMap = new Map(); } return null; } async function getAmountOutUniswapV3(params, checkOverflow, shouldEstimateTweakPrice) { if (!params.sqrtPriceX96 || params.sqrtPriceX96.isZero()) { return { amountOut: constants_1.ZERO, sqrtPriceLimitX96: constants_1.ZERO, }; } if (params.reserveIn.isZero() || params.amount.isZero()) { //console.log('getAmountOutAqua reserveIn or amount is zero'); return { amountOut: constants_1.ZERO, sqrtPriceLimitX96: constants_1.ZERO, }; } const amountIn = params.amount; const balanceIn = params.reserveIn.add(amountIn); if (checkOverflow && balanceIn.gt(constants_1.UINT128_MAX)) { //console.log('getAmountOutAqua overflow'); throw Error("overflow"); } let sqrtPriceLimitX96; if (params.swap0For1) { sqrtPriceLimitX96 = constants_1.ZERO; //BigNumber.from(params.sqrtPriceX96).sub(sqrtPriceX96Slippage); } else { sqrtPriceLimitX96 = constants_1.ZERO; //BigNumber.from(params.sqrtPriceX96).add(sqrtPriceX96Slippage); } let quoterContract; let rangeFactoryAddress; const network = (0, statestore_1.stateStore)().network; if (cachedQuoterInfo && cachedQuoterInfo.network === network) { quoterContract = cachedQuoterInfo.quoterContract; rangeFactoryAddress = cachedQuoterInfo.rangeFactoryAddress; } else { quoterContract = contractRegistry_1.default.getContractByName("quoter"); rangeFactoryAddress = contractRegistry_1.default.getAddressByName("ranged_factory"); // cache quoter contract cachedQuoterInfo = { network: network, quoterContract: quoterContract, rangeFactoryAddress: rangeFactoryAddress, }; } let resolved = false; return new Promise((resolve) => { const hash = hashGetAmountOutUniswapV3Params(params); const cachedAmountOut = getCachedAmountOutUniswapV3Result(hash); if (cachedAmountOut) { resolve({ amountOut: cachedAmountOut, sqrtPriceLimitX96: sqrtPriceLimitX96, }); return; } quoterContract.callStatic .quoteExactInputSingle({ factory: rangeFactoryAddress, tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: params.amount, tickSpacing: params.tickSpacing, sqrtPriceLimitX96: constants_1.ZERO, }) .then((quoteResults) => { resolved = true; resolve({ amountOut: quoteResults.amountOut, sqrtPriceLimitX96: sqrtPriceLimitX96, }); getAmountOutUniswapV3CacheMap.set(hash, { amountOut: quoteResults.amountOut, timestamp: Date.now(), }); }) .catch((e) => { console.warn("Range quoter failed", e); resolved = true; resolve({ amountOut: constants_1.ZERO, sqrtPriceLimitX96: sqrtPriceLimitX96, }); }); setTimeout(() => { if (!resolved) { resolve({ amountOut: constants_1.ZERO, sqrtPriceLimitX96: sqrtPriceLimitX96, }); } }, 20000); // 2s timeout }); } async function calculateAmountOut(params, checkOverflow) { //console.log('calculateAmountOut params', params); if (params.amount.isZero()) { return { amountOut: constants_1.ZERO, }; } try { switch (params.poolType) { case types_1.PoolTypes.CLASSIC: return { amountOut: getAmountOutClassic(params, checkOverflow), }; case types_1.PoolTypes.STABLE: return { amountOut: getAmountOutStable(params, checkOverflow), }; case types_1.PoolTypes.AQUA: return { amountOut: getAmountOutAqua(params, checkOverflow, true), }; case types_1.PoolTypes.UNISWAP_V3: return getAmountOutUniswapV3(params, checkOverflow, true); default: throw Error("Unsupported pool type"); } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { if (error.message !== "overflow") { console.log(params.poolType, "error", error); } return { amountOut: constants_1.ZERO, }; } } function getQuoteOutClassic(params, hasFee) { if (params.reserveIn.isZero() || params.amount.isZero()) { return constants_1.ZERO; } const amountIn = params.amount; const multiplier = constants_1.DECIMALS_5; const swapFee = hasFee ? params.swapFee.maxFee : constants_1.ZERO; const amountInWithFee = amountIn.mul(MAX_FEE.sub(swapFee)); return amountInWithFee .mul(params.reserveOut.mul(multiplier)) .div(params.reserveIn.mul(multiplier).mul(MAX_FEE)); } function getQuoteOutAqua(params, hasFee) { const scale = constants_1.DECIMALS_3; const adjustAmount = params.amount.div(scale); const quoteOut = getAmountOutAqua({ tokenIn: params.tokenIn, tokenOut: params.tokenOut, poolType: params.poolType, swap0For1: params.swap0For1, amount: adjustAmount, reserve0: params.reserve0, reserve1: params.reserve1, reserveIn: params.reserveIn, reserveOut: params.reserveOut, swapFee: hasFee ? params.swapFee : constants_1.ZERO_FEE_DATA, tokenInPrecisionMultiplier: params.tokenInPrecisionMultiplier, tokenOutPrecisi