@syncswap/sdk
Version:
SyncSwap TypeScript SDK for building DeFi applications
1,205 lines • 92.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.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