@saberhq/stableswap-sdk
Version:
Solana SDK for Saber's StableSwap program.
201 lines • 9.19 kB
JavaScript
import { Fraction, ONE, TokenAmount, ZERO } from "@saberhq/token-utils";
import { computeD, computeY } from "./curve.js";
/**
* Calculates the current virtual price of the exchange.
* @param exchange
* @returns
*/
export const calculateVirtualPrice = (exchange) => {
const amount = exchange.lpTotalSupply;
if (amount === undefined || amount.equalTo(0)) {
// pool has no tokens
return null;
}
const price = new Fraction(computeD(exchange.ampFactor, exchange.reserves[0].amount.raw, exchange.reserves[1].amount.raw), amount.raw);
return price;
};
/**
* Calculates the estimated output amount of a swap.
* @param exchange
* @param fromAmount
* @returns
*/
export const calculateEstimatedSwapOutputAmount = (exchange, fromAmount) => {
const [fromReserves, toReserves] = fromAmount.token.equals(exchange.reserves[0].amount.token)
? [exchange.reserves[0], exchange.reserves[1]]
: [exchange.reserves[1], exchange.reserves[0]];
if (fromAmount.equalTo(0)) {
const zero = new TokenAmount(toReserves.amount.token, ZERO);
return {
outputAmountBeforeFees: zero,
outputAmount: zero,
fee: zero,
lpFee: zero,
adminFee: zero,
};
}
const amp = exchange.ampFactor;
const amountBeforeFees = toReserves.amount.raw -
computeY(amp, fromReserves.amount.raw + fromAmount.raw, computeD(amp, fromReserves.amount.raw, toReserves.amount.raw));
const outputAmountBeforeFees = new TokenAmount(toReserves.amount.token, amountBeforeFees);
const fee = new TokenAmount(toReserves.amount.token, exchange.fees.trade.asFraction.multiply(amountBeforeFees).toFixed(0));
const adminFee = new TokenAmount(toReserves.amount.token, exchange.fees.adminTrade.asFraction.multiply(fee.raw).toFixed(0));
const lpFee = fee.subtract(adminFee);
const outputAmount = new TokenAmount(toReserves.amount.token, amountBeforeFees - fee.raw);
return {
outputAmountBeforeFees,
outputAmount,
fee: fee,
lpFee,
adminFee,
};
};
const N_COINS = 2n;
/**
* Calculates the amount of tokens withdrawn if only withdrawing one token.
* @returns
*/
export const calculateEstimatedWithdrawOneAmount = ({ exchange, poolTokenAmount, withdrawToken, }) => {
if (poolTokenAmount.equalTo(0)) {
// final quantities
const zeroQuantity = new TokenAmount(withdrawToken, ZERO);
return {
withdrawAmount: zeroQuantity,
withdrawAmountBeforeFees: zeroQuantity,
swapFee: zeroQuantity,
withdrawFee: zeroQuantity,
lpSwapFee: zeroQuantity,
lpWithdrawFee: zeroQuantity,
adminSwapFee: zeroQuantity,
adminWithdrawFee: zeroQuantity,
};
}
const { ampFactor, fees } = exchange;
const [baseReserves, quoteReserves] = [
exchange.reserves.find((r) => r.amount.token.equals(withdrawToken))?.amount
.raw ?? ZERO,
exchange.reserves.find((r) => !r.amount.token.equals(withdrawToken))?.amount
.raw ?? ZERO,
];
const d_0 = computeD(ampFactor, baseReserves, quoteReserves);
const d_1 = d_0 - (poolTokenAmount.raw * d_0) / exchange.lpTotalSupply.raw;
const new_y = computeY(ampFactor, quoteReserves, d_1);
// expected_base_amount = swap_base_amount * d_1 / d_0 - new_y;
const expected_base_amount = (baseReserves * d_1) / d_0 - new_y;
// expected_quote_amount = swap_quote_amount - swap_quote_amount * d_1 / d_0;
const expected_quote_amount = quoteReserves - (quoteReserves * d_1) / d_0;
// new_base_amount = swap_base_amount - expected_base_amount * fee / fee_denominator;
const new_base_amount = new Fraction(baseReserves.toString(), 1).subtract(normalizedTradeFee(fees, N_COINS, expected_base_amount));
// new_quote_amount = swap_quote_amount - expected_quote_amount * fee / fee_denominator;
const new_quote_amount = new Fraction(quoteReserves.toString(), 1).subtract(normalizedTradeFee(fees, N_COINS, expected_quote_amount));
const dy = new_base_amount.subtract(computeY(ampFactor, BigInt(new_quote_amount.toFixed(0)), d_1).toString());
const dy_0 = baseReserves - new_y;
// lp fees
const swapFee = new Fraction(dy_0.toString(), 1).subtract(dy);
const withdrawFee = dy.multiply(fees.withdraw.asFraction);
// admin fees
const adminSwapFee = swapFee.multiply(fees.adminTrade.asFraction);
const adminWithdrawFee = withdrawFee.multiply(fees.adminWithdraw.asFraction);
// final LP fees
const lpSwapFee = swapFee.subtract(adminSwapFee);
const lpWithdrawFee = withdrawFee.subtract(adminWithdrawFee);
// final withdraw amount
const withdrawAmount = dy.subtract(withdrawFee).subtract(swapFee);
// final quantities
return {
withdrawAmount: new TokenAmount(withdrawToken, withdrawAmount.toFixed(0)),
withdrawAmountBeforeFees: new TokenAmount(withdrawToken, dy.toFixed(0)),
swapFee: new TokenAmount(withdrawToken, swapFee.toFixed(0)),
withdrawFee: new TokenAmount(withdrawToken, withdrawFee.toFixed(0)),
lpSwapFee: new TokenAmount(withdrawToken, lpSwapFee.toFixed(0)),
lpWithdrawFee: new TokenAmount(withdrawToken, lpWithdrawFee.toFixed(0)),
adminSwapFee: new TokenAmount(withdrawToken, adminSwapFee.toFixed(0)),
adminWithdrawFee: new TokenAmount(withdrawToken, adminWithdrawFee.toFixed(0)),
};
};
/**
* Compute normalized fee for symmetric/asymmetric deposits/withdraws
*/
export const normalizedTradeFee = ({ trade }, n_coins, amount) => {
const adjustedTradeFee = new Fraction(n_coins, (n_coins - ONE) * 4n);
return new Fraction(amount, 1).multiply(trade).multiply(adjustedTradeFee);
};
export const calculateEstimatedWithdrawAmount = ({ poolTokenAmount, reserves, fees, lpTotalSupply, }) => {
if (lpTotalSupply.equalTo(0)) {
const zero = reserves.map((r) => new TokenAmount(r.amount.token, ZERO));
return {
withdrawAmounts: zero,
withdrawAmountsBeforeFees: zero,
fees: zero,
};
}
const share = poolTokenAmount.divide(lpTotalSupply);
const withdrawAmounts = reserves.map(({ amount }) => {
const baseAmount = share.multiply(amount.raw);
const fee = baseAmount.multiply(fees.withdraw.asFraction);
return [
new TokenAmount(amount.token, BigInt(baseAmount.subtract(fee).toFixed(0))),
{
beforeFees: BigInt(baseAmount.toFixed(0)),
fee: BigInt(fee.toFixed(0)),
},
];
});
return {
withdrawAmountsBeforeFees: withdrawAmounts.map(([amt, { beforeFees }]) => new TokenAmount(amt.token, beforeFees)),
withdrawAmounts: [withdrawAmounts[0][0], withdrawAmounts[1][0]],
fees: withdrawAmounts.map(([amt, { fee }]) => new TokenAmount(amt.token, fee)),
};
};
/**
* Calculate the estimated amount of LP tokens minted after a deposit.
* @param exchange
* @param depositAmountA
* @param depositAmountB
* @returns
*/
export const calculateEstimatedMintAmount = (exchange, depositAmountA, depositAmountB) => {
if (depositAmountA === ZERO && depositAmountB === ZERO) {
const zero = new TokenAmount(exchange.lpTotalSupply.token, ZERO);
return {
mintAmountBeforeFees: zero,
mintAmount: zero,
fees: zero,
};
}
const amp = exchange.ampFactor;
const [reserveA, reserveB] = exchange.reserves;
const d0 = computeD(amp, reserveA.amount.raw, reserveB.amount.raw);
const d1 = computeD(amp, reserveA.amount.raw + depositAmountA, reserveB.amount.raw + depositAmountB);
if (d1 < d0) {
throw new Error("New D cannot be less than previous D");
}
const oldBalances = exchange.reserves.map((r) => r.amount.raw);
const newBalances = [
reserveA.amount.raw + depositAmountA,
reserveB.amount.raw + depositAmountB,
];
const adjustedBalances = newBalances.map((newBalance, i) => {
const oldBalance = oldBalances[i];
const idealBalance = new Fraction(d1, d0).multiply(oldBalance);
const difference = idealBalance.subtract(newBalance);
const diffAbs = difference.greaterThan(0)
? difference
: difference.multiply(-1);
const fee = normalizedTradeFee(exchange.fees, N_COINS, BigInt(diffAbs.toFixed(0)));
return newBalance - BigInt(fee.toFixed(0));
});
const d2 = computeD(amp, adjustedBalances[0], adjustedBalances[1]);
const lpSupply = exchange.lpTotalSupply;
const mintAmountRaw = (lpSupply.raw * (d2 - d0)) / d0;
const mintAmount = new TokenAmount(exchange.lpTotalSupply.token, mintAmountRaw);
const mintAmountRawBeforeFees = (lpSupply.raw * (d1 - d0)) / d0;
const fees = new TokenAmount(exchange.lpTotalSupply.token, mintAmountRawBeforeFees - mintAmountRaw);
const mintAmountBeforeFees = new TokenAmount(exchange.lpTotalSupply.token, mintAmountRawBeforeFees);
return {
mintAmount,
mintAmountBeforeFees,
fees,
};
};
//# sourceMappingURL=amounts.js.map