@syncswap/sdk
Version:
SyncSwap TypeScript SDK for building DeFi applications
1,263 lines • 86.7 kB
JavaScript
import { BigNumber, Contract } from "ethers";
import { TokenRegistry, getWETHAddress } from "./tokens/tokenRegistry.js";
import { Numbers } from "./utils/numbers.js";
import { PoolTypes, RouteDirection, } from "./router/types.js";
import { ZERO, ZERO_ADDRESS, DECIMALS_2, DECIMALS_5, DECIMALS_6, ETHER, FOUR, ONE, THREE, TWO, UINT128_MAX, UINT256_MAX, DECIMALS_14, DECIMALS_16, DECIMALS_17, DECIMALS_18, DECIMALS_20, DECIMALS_21, DECIMALS_24, DECIMALS_26, DECIMALS_3, DECIMALS_33, DECIMALS_4, DECIMALS_9, Q_96, ZERO_FEE_DATA, } from "./utils/constants.js";
import ContractRegistry from "./contracts/contractRegistry.js";
import invariant from "tiny-invariant";
import { Token } from "./tokens/token.js";
import { defaultAbiCoder, splitSignature } from "ethers/lib/utils.js";
import { stateStore } from "./statestore/statestore.js";
import { utils } from "zksync-web3";
export const SOPHON_MAINNET_USDT = "0x6386da73545ae4e2b2e0393688fa8b65bb9a7169";
export const SOPHON_MAINNET_USDC = "0x9aa0f72392b5784ad86c6f3e899bcc053d00db4f";
export const ZKSYNC_TESTNET = "zkSyncTestnet";
export const ZKSYNC_MAINNET = "zkSyncMainnet";
export const SCROLL_MAINNET = "scrollMainnet";
export const SOPHON_MAINNET = "sophonMainnet";
export const SOPHON_TESTNET = "sophonTestnet";
export const ZKCANDY_MAINNET = "zkcandyMainnet";
export const LINEA_MAINNET = "lineaMainnet";
export const ZKSYNC_SEPOLIA_TESTNET = "zkSyncSepoliaTestnet";
const GAS_CLASSIC_STEP = BigNumber.from(4697000); // 4697727
const GAS_STABLE_STEP = BigNumber.from(4700000); // 4716100
const GAS_AQUA_STEP = BigNumber.from(5000000); // dummy
const GAS_EXTRA_PATH = BigNumber.from(4000000); // 4634420
const GAS_SWAP_BASE = BigNumber.from(7230000); // 7230698
const ETHER_x_4 = ETHER.mul(4);
const LIQUIDITY_MIN_RESERVE = BigNumber.from(100000);
const SOPHON_MAINNET_WSOPH = "0x2b1a859de6a55c553520d7780bc5805712b128f9";
const SOPHON_MAINNET_RTOKENS = [
SOPHON_MAINNET_WSOPH,
"0x72af9f169b619d85a47dfa8fefbcd39de55c567d", // ETH
SOPHON_MAINNET_USDC,
SOPHON_MAINNET_USDT,
];
export function getRouteTokens() {
const network = stateStore().network;
if (network === SOPHON_MAINNET) {
return SOPHON_MAINNET_RTOKENS;
}
return [getWETHAddress()];
}
export function sortTokens(tokenA, tokenB) {
return Number(tokenA) < Number(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA];
}
export function isETH(token) {
if (typeof token === 'string' || token instanceof String) {
return token.toLowerCase() === Token.ETHER.address;
}
else {
return token.address === Token.ETHER.address;
}
}
export function getPoolKey(address1, address2) {
if (address1 < address2) {
return `${address1}:${address2}`;
}
else {
return `${address2}:${address1}`;
}
}
export 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);
}
*/
export 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;
}
export 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.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.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 = BigNumber.from(100000); // 1e5
const TWO_MAX_FEE = MAX_FEE.mul(2); // 1e5
const MINIMUM_LIQUIDITY = BigNumber.from(1000);
const ZERO_STEP_AMOUNT_OUT = [ZERO, 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 === 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 === 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: BigNumber.from(pool.reserveA),
reserveB: 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(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(UINT256_MAX)) {
//console.log('getAmountOutClassic overflow 1');
throw Error("overflow");
}
const numerator = amountInWithFee.mul(params.reserveOut);
if (checkOverflow && numerator.gt(UINT256_MAX)) {
//console.log('getAmountOutClassic overflow 2');
throw Error("overflow");
}
const denominator = params.reserveIn.mul(MAX_FEE).add(amountInWithFee);
if (checkOverflow && denominator.gt(UINT256_MAX)) {
//console.log('getAmountOutClassic overflow 3');
throw Error("overflow");
}
return numerator.div(denominator);
}
const MAX_XP = UINT128_MAX; //BigNumber.from('3802571709128108338056982581425910818');
const DEFAULT_STABLE_POOL_A = BigNumber.from(1000);
export function getBlockTimestamp() {
return BigNumber.from(Math.floor(Date.now() / 1000));
}
function getY(A, x, d) {
const nA = A.mul(TWO);
const c = d.mul(d).div(x.mul(TWO)).mul(d).div(nA.mul(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(TWO).add(b).sub(d));
if (y.sub(yPrev).abs().lte(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;
}
export function sqrtBN(x) {
let z = x.add(ONE).div(TWO);
let y = x;
while (z.sub(y).isNegative()) {
y = z;
z = x.div(z).add(z).div(TWO);
}
return y;
}
export function computeInvariant(poolType, reserveA, reserveB, tokenAPrecisionMultiplier, tokenBPrecisionMultiplier, a, gamma) {
if (poolType === PoolTypes.STABLE) {
invariant(tokenAPrecisionMultiplier && tokenBPrecisionMultiplier && a, "multiplier");
const adjustedReserveA = reserveA.mul(tokenAPrecisionMultiplier);
const adjustedReserveB = reserveB.mul(tokenBPrecisionMultiplier);
return computeDFromAdjustedBalances(a, adjustedReserveA, adjustedReserveB, true);
}
if (poolType === PoolTypes.AQUA) {
invariant(tokenAPrecisionMultiplier && tokenBPrecisionMultiplier && a && gamma, "multiplier");
const adjustedReserveA = reserveA.mul(tokenAPrecisionMultiplier);
const adjustedReserveB = reserveB.mul(tokenBPrecisionMultiplier);
return cryptoMathComputeD(a, gamma, adjustedReserveA, adjustedReserveB);
}
if (poolType === PoolTypes.CLASSIC) {
if (reserveA.gt(UINT128_MAX) || reserveB.gt(UINT128_MAX)) {
throw Error("overflow");
}
return sqrtBN(reserveA.mul(reserveB));
}
return ZERO;
}
export function getUnbalancedMintFee(isV2, amountA, amountB, reserveA, reserveB, swapFeeAB, swapFeeBA) {
if (reserveA.isZero() || reserveB.isZero()) {
return [ZERO, 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 [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, ZERO];
}
}
export function getXCP(invariant, priceScale) {
return geometricMean(invariant.div(2), invariant.mul(ETHER).div(priceScale.mul(2)));
}
export function getCryptoFee(feeData, xp0, xp1) {
const gamma = feeData.gamma;
const minFee = BigNumber.from(feeData.minFee);
const maxFee = BigNumber.from(feeData.maxFee);
let f = xp0.add(xp1);
f = gamma
.mul(ETHER)
.div(gamma.add(ETHER).sub(ETHER_x_4.mul(xp1).mul(xp0).div(f).div(f)));
const fee = minFee
.mul(f)
.add(maxFee.mul(ETHER.sub(f)))
.div(ETHER);
return fee;
}
export function calculateCryptoLiquidityFee(feeData, amount0, amount1, xp0, xp1) {
const fee = getCryptoFee(feeData, xp0, xp1).div(2);
const sum = amount0.add(amount1);
if (sum.isZero()) {
return 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
export function computeLiquidity(isV2, poolType, amountA, amountB, reserveA, reserveB, swapFeeAB, swapFeeBA, totalSupply, isTokenA0, tokenAPrecisionMultiplier, tokenBPrecisionMultiplier, a, cryptoData) {
if (poolType === PoolTypes.UNISWAP_V3) {
return [ZERO, ZERO];
}
// Aqua Pools
if (poolType === PoolTypes.AQUA) {
const newReserveA = reserveA.add(amountA);
const newReserveB = reserveB.add(amountB);
if (newReserveA.gt(UINT128_MAX) || newReserveB.gt(UINT128_MAX)) {
throw Error("overflow");
}
invariant(tokenAPrecisionMultiplier && tokenBPrecisionMultiplier && a && cryptoData, "multiplier");
const priceScale = cryptoData.priceScale;
const xpA = isTokenA0
? newReserveA.mul(tokenAPrecisionMultiplier)
: newReserveA.mul(priceScale).mul(tokenAPrecisionMultiplier).div(ETHER);
//const priceScaleB = priceScale.mul(tokenBPrecisionMultiplier);
const xpB = isTokenA0
? newReserveB.mul(priceScale).mul(tokenBPrecisionMultiplier).div(ETHER)
: newReserveB.mul(tokenBPrecisionMultiplier);
const oldXpA = isTokenA0
? reserveA.mul(tokenAPrecisionMultiplier)
: reserveA.mul(priceScale).mul(tokenAPrecisionMultiplier).div(ETHER);
const oldXpB = isTokenA0
? reserveB.mul(priceScale).mul(tokenBPrecisionMultiplier).div(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
? ETHER.mul(xpA).div(newReserveB.mul(tokenBPrecisionMultiplier))
: 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()
? 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(DECIMALS_5).add(1);
if (feeAmount.lt(ZERO)) {
feeAmount = ZERO;
}
liquidity = liquidity.sub(feeAmount);
}
if (liquidity.lt(ZERO)) {
liquidity = ZERO;
}
if (liquidityWithoutFee.lt(ZERO)) {
liquidityWithoutFee = ZERO;
}
return [liquidity, liquidityWithoutFee];
}
// Classic / Stable Pools
// fees are added to old invariant
const [feeA, feeB] = getUnbalancedMintFee(isV2, amountA, amountB, reserveA, reserveB, BigNumber.from(swapFeeAB.maxFee), 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(UINT128_MAX) || newReserveB.gt(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 ZERO;
}
else {
let prevD;
let d = s;
const nA = A.mul(TWO);
for (let i = 0; i < MAX_LOOP_LIMIT; i++) {
const dSq = d.mul(d);
if (checkOverflow && dSq.gt(UINT256_MAX)) {
throw Error("overflow");
}
const d2 = dSq.div(xp0).mul(d);
if (checkOverflow && d2.gt(UINT256_MAX)) {
throw Error("overflow");
}
const dP = d2.div(xp1).div(FOUR);
prevD = d;
const d0 = nA.mul(s).add(dP.mul(TWO)).mul(d);
if (checkOverflow && d0.gt(UINT256_MAX)) {
throw Error("overflow");
}
d = d0.div(nA.sub(ONE).mul(d).add(dP.mul(THREE)));
if (d.sub(prevD).abs().lte(ONE)) {
return d;
}
}
return d;
}
}
const A_MULTIPLIER = BigNumber.from(10000);
const N_COINS = 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();
export function cryptoMathGetY(ANN, gamma, x0, x1, D, i) {
//invariant(D.gte(DECIMALS_17) && D.lte(DECIMALS_33), "unsafe values D");
if (!(D.gte(DECIMALS_17) && D.lte(DECIMALS_33))) {
//console.warn("cryptoMathGetY: unsafe values D", D.toString());
return 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(TWO).div(x_j.mul(N_COINS.pow(TWO)));
const K0_i = 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(DECIMALS_16.mul(N_COINS)) && K0_i.lte(DECIMALS_20.mul(N_COINS)))) {
//console.warn("cryptoMathGetY: unsafe values x[i]", K0_i.toString());
return ZERO;
}
const convergence_limit = max(max(x_j.div(DECIMALS_14), D.div(DECIMALS_14)), DECIMALS_2);
const __g1k0 = gamma.add(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 = DECIMALS_18.mul(D)
.div(gamma)
.mul(_g1k0)
.div(gamma)
.mul(_g1k0)
.mul(A_MULTIPLIER)
.div(ANN);
const mul2 = DECIMALS_18.add(TWO.mul(DECIMALS_18).mul(K0)).div(_g1k0);
let yfprime = DECIMALS_18.mul(y).add(S.mul(mul2)).add(mul1);
const _dyfprime = D.mul(mul2);
if (yfprime.lt(_dyfprime)) {
y = y_prev.div(TWO);
continue;
}
else {
yfprime = yfprime.sub(_dyfprime);
}
const fprime = yfprime.div(y);
const y_minus_temp = mul1.div(fprime);
const y_plus = yfprime
.add(ETHER.mul(D))
.div(fprime)
.add(y_minus_temp.mul(ETHER).div(K0));
const y_minus = y_minus_temp.add(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(DECIMALS_14)))) {
const frac = y.mul(DECIMALS_18).div(D);
//invariant(frac.gte(DECIMALS_16) && frac.lte(DECIMALS_20), "cryptoMathGetY: unsafe value for y");
if (!(frac.gte(DECIMALS_16) && frac.lte(DECIMALS_20))) {
//console.warn("cryptoMathGetY: unsafe values for y", frac.toString());
return 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 ZERO;
}
function hashComputeD(ANN, gamma, x0, x1) {
return (ANN.toHexString() +
gamma.toHexString() +
x0.toHexString() +
x1.toHexString());
}
let computeDCacheMap = new Map();
export 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 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(DECIMALS_9) && x[0].lte(DECIMALS_33))) {
//console.warn("cryptoMathComputeD: unsafe values x[0]", x[0].toString());
return ZERO;
}
if (!x[1].mul(DECIMALS_18).div(x[0]).gte(DECIMALS_14)) {
//console.warn("cryptoMathComputeD: unsafe values x[i] (input)", x[1].toString());
return 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(DECIMALS_18);
for (let i = 0; i < 255; i++) {
const D_prev = D;
//invariant(D.gt(ZERO), "Unsafe value D");
if (D.lte(ZERO)) {
//console.warn("Unsafe value D", D.toString());
return ZERO;
}
const K0 = 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 = DECIMALS_18.mul(D)
.div(gamma)
.mul(_g1k0)
.div(gamma)
.mul(_g1k0)
.mul(A_MULTIPLIER)
.div(ANN);
const mul2 = TWO.mul(DECIMALS_18).mul(N_COINS).mul(K0).div(_g1k0);
const neg_fprime = S.add(S.mul(mul2).div(DECIMALS_18))
.add(mul1.mul(N_COINS).div(K0))
.sub(mul2.mul(D).div(DECIMALS_18));
const D_plus = D.mul(neg_fprime.add(S)).div(neg_fprime);
let D_minus = D.mul(D).div(neg_fprime);
if (DECIMALS_18.gt(K0)) {
D_minus = D_minus.add(D.mul(mul1.div(neg_fprime))
.div(DECIMALS_18)
.mul(DECIMALS_18.sub(K0))
.div(K0));
}
else {
D_minus = D_minus.sub(D.mul(mul1.div(neg_fprime))
.div(DECIMALS_18)
.mul(K0.sub(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(DECIMALS_14).lt(max(DECIMALS_16, D))) {
const frac = x0.mul(DECIMALS_18).div(D);
if (!(frac.gte(DECIMALS_16) && frac.lte(DECIMALS_20))) {
//console.warn("cryptoMathComputeD: unsafe values x[i]-1", frac.toString());
return ZERO;
}
//nvariant(frac.gte(DECIMALS_16) && frac.lte(DECIMALS_20), "cryptoMathComputeD: unsafe values x[i]-1");
const frac1 = x1.mul(DECIMALS_18).div(D);
if (!(frac1.gte(DECIMALS_16) && frac1.lte(DECIMALS_20))) {
//console.warn("cryptoMathComputeD: unsafe values x[i]-2", frac1.toString());
return 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 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 = ETHER.mul(unadjustedInvariant).div(priceScale.mul(2));
const xcp = geometricMean(_xp0, _xp1);
const newVirtualPrice = 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 ZERO;
}
if (!params.a ||
!params.gamma ||
!params.invariantLast ||
!params.priceScale ||
!params.futureParamsTime ||
!params.totalSupply ||
!params.virtualPrice) {
console.warn("getAmountOutAqua: incomplete params", params);
return ZERO;
}
if (params.a?.isZero() ||
params.gamma?.isZero() ||
params.invariantLast?.isZero()) {
console.warn("getAmountOutAqua: invalid params", params);
return 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(ETHER);
}
else {
xpIn = balanceIn.mul(priceScale).mul(tokenInPrecisionMultiplier).div(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(ETHER);
invariant = cryptoMathComputeD(params.a, params.gamma, xpOut, oldXpIn);
}
}
else {
invariant = params.invariantLast;
}
if (checkOverflow && balanceIn.gt(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(ONE)) {
//console.log('getAmountOutAqua: zero out 1', xpIn.toString(), xpOut.toString(), invariant.toString());
return ZERO;
}
// modify xp out
newXpOut = xpOut.sub(yOut);
const intermediate = yOut.sub(ONE).mul(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(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(ETHER);
}
}
else {
const y = cryptoMathGetY(params.a, params.gamma, xpOut, xpIn, invariant, 0);
const yOut = xpOut.sub(y);
if (yOut.lte(ONE)) {
//console.log('getAmountOutAqua: zero out 0', xpOut.toString(), xpIn.toString(), invariant.toString());
return ZERO;
}
// modify xp out
newXpOut = xpOut.sub(yOut);
const intermediate = yOut.sub(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(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, ZERO, params.virtualPrice, priceScale, params.totalSupply, params.futureParamsTime);
}
else {
estimateTweakPriceAqua(params.a, params.gamma, newXpOut, xpIn, ZERO, params.virtualPrice, priceScale, params.totalSupply, params.futureParamsTime);
}
}
if (amountOutAfterFee.lte(ZERO)) {
getAmountOutAquaCacheMap.set(hash, {
amountOut: ZERO,
timestamp: Date.now(),
});
return 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: ZERO,
sqrtPriceLimitX96: ZERO,
};
}
if (params.reserveIn.isZero() || params.amount.isZero()) {
//console.log('getAmountOutAqua reserveIn or amount is zero');
return {
amountOut: ZERO,
sqrtPriceLimitX96: ZERO,
};
}
const amountIn = params.amount;
const balanceIn = params.reserveIn.add(amountIn);
if (checkOverflow && balanceIn.gt(UINT128_MAX)) {
//console.log('getAmountOutAqua overflow');
throw Error("overflow");
}
let sqrtPriceLimitX96;
if (params.swap0For1) {
sqrtPriceLimitX96 = ZERO; //BigNumber.from(params.sqrtPriceX96).sub(sqrtPriceX96Slippage);
}
else {
sqrtPriceLimitX96 = ZERO; //BigNumber.from(params.sqrtPriceX96).add(sqrtPriceX96Slippage);
}
let quoterContract;
let rangeFactoryAddress;
const network = stateStore().network;
if (cachedQuoterInfo && cachedQuoterInfo.network === network) {
quoterContract = cachedQuoterInfo.quoterContract;
rangeFactoryAddress = cachedQuoterInfo.rangeFactoryAddress;
}
else {
quoterContract = ContractRegistry.getContractByName("quoter");
rangeFactoryAddress = ContractRegistry.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: 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: ZERO,
sqrtPriceLimitX96: sqrtPriceLimitX96,
});
});
setTimeout(() => {
if (!resolved) {
resolve({
amountOut: ZERO,
sqrtPriceLimitX96: sqrtPriceLimitX96,
});
}
}, 20000); // 2s timeout
});
}
export async function calculateAmountOut(params, checkOverflow) {
//console.log('calculateAmountOut params', params);
if (params.amount.isZero()) {
return {
amountOut: ZERO,
};
}
try {
switch (params.poolType) {
case PoolTypes.CLASSIC:
return {
amountOut: getAmountOutClassic(params, checkOverflow),
};
case PoolTypes.STABLE:
return {
amountOut: getAmountOutStable(params, checkOverflow),
};
case PoolTypes.AQUA:
return {
amountOut: getAmountOutAqua(params, checkOverflow, true),
};
case 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: ZERO,
};
}
}
function getQuoteOutClassic(params, hasFee) {
if (params.reserveIn.isZero() || params.amount.isZero()) {
return ZERO;
}
const amountIn = params.amount;
const multiplier = DECIMALS_5;
const swapFee = hasFee ? params.swapFee.maxFee : 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 = 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 : ZERO_FEE_DATA,
tokenInPrecisionMultiplier: params.tokenInPrecisionMultiplier,
tokenOutPrecisionMultiplier: params.tokenOutPrecisionMultiplier,
a: params.a,
gamma: params.gamma,
priceScale: params.priceScale,
futureParamsTime: params.futureParamsTime,
invariantLast: params.invariantLast,
virtualPrice: params.virtualPrice,
totalSupply: params.totalSupply,
}, false, false).mul(scale);
return quoteOut;
}
function getQuoteOutUniswapV3(params, hasFee) {
const sqrtPriceX96 = params.sqrtPriceX96;
if (!sqrtPriceX96 || sqrtPriceX96.isZero()) {
return ZERO;
}
const squaredPrice = sqrtPriceX96.mul(sqrtPriceX96).div(Q_96);
let amountIn = params.amount;
if (hasFee) {
const amountFee = amountIn.mul(params.swapFee.maxFee).div(DECIMALS_5);
amountIn = amountIn.sub(amountFee);
}
if (params.swap0For1) {
return amountIn.mul(squaredPrice).div(Q_96);
}
else {
return amountIn.mul(Q_96).div(squaredPrice);
}
}
function getQuoteOutStable(params, hasFee) {
const multiplier = DECIMALS_4;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const adjustedReserveIn = params.reserveIn
.mul(params.tokenInPrecisionMultiplier)
.mul(multiplier);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const adjustedReserveOut = params.reserveOut
.mul(params.tokenOutPrecisionMultiplier)
.mul(multiplier);
const amountIn = params.amount;
const swapFee = hasFee ? params.swapFee.maxFee : ZERO;
const feeDeductedAmountIn = amountIn.sub(amountIn.mul(swapFee).div(MAX_FEE));
const a = params.a ?? DEFAULT_STABLE_POOL_A;
const d = computeDFromAdjustedBalances(a, adjustedReserveIn, adjustedReserveOut, false);
// 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
return dy.div(params.tokenOutPrecisionMultiplier);
}
export function calculateQuoteOut(params, hasFee) {
if (params.amount.isZero()) {
return ZERO;
}
let quoteOut;
switch (params.poolType) {
case PoolTypes.CLASSIC:
quoteOut = getQuoteOutClassic(params, hasFee);
break;
case PoolTypes.STABLE:
quoteOut = getQuoteOutStable(params, hasFee);
break;
case PoolTypes.AQUA:
quoteOut = getQuoteOutAqua(params, hasFee);
break;
case PoolTypes.UNISWAP_V3:
quoteOut = getQuoteOutUniswapV3(params, hasFee);
break;
default:
throw Error("Unsupported pool type");
}
return quoteOut;
}
async function calculateAmountOutForStep(step, amountIn, quoteIn
//updateReserves: boolean,
) {
// cache
const hash = hashAmountForStep(step, amountIn);
const cached = cacheAmountOutForStepMap.get(hash);
if (cached) {
//console.log('[calculateAmountOutForStep] hit cache, size', cacheAmountOutForStepMap.size);
return cached.amounts;
}
const isTokenAIn = step.pool.tokenA === step.tokenIn;
const [reserveIn, reserveOut] = isTokenAIn
? [step.pool.reserveA, step.pool.reserveB]
: [step.pool.reserveB, step.pool.reserveA];
//console.log('calculateAmountOutForStep tokenA', step.tokenA, 'tokenIn', step.tokenIn);
let tokenInPrecisionMultiplier;
let tokenOutPrecisionMultiplier;
const [tokenInAddress, tokenOutAddress] = isTokenAIn
? [step.pool.tokenA, step.pool.tokenB]
: [step.pool.tokenB, step.pool.tokenA];
// create multiplier for stable pools
const needMultiplers = step.pool.poolType === PoolTypes.STABLE ||
step.pool.poolType === PoolTypes.AQUA;
if (needMultiplers) {
const tokenIn = TokenRegistry.getTokenByAddress(tokenInAddress);
const tokenOut = TokenRegistry.getTokenByAddress(tok