@dahlia-labs/stableswap-sdk
Version:
SDK for Mobius's StableSwap program.
218 lines • 12.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateEstimatedMintAmount = exports.calculateEstimatedWithdrawAmount = exports.normalizedTradeFee = exports.calculateEstimatedWithdrawOneAmount = exports.calculateEstimatedSwapOutputAmount = exports.calculateVirtualPrice = exports.denormalizeAmount = exports.normalizeAmount = void 0;
const tslib_1 = require("tslib");
const token_utils_1 = require("@dahlia-labs/token-utils");
const jsbi_1 = tslib_1.__importDefault(require("jsbi"));
const lodash_mapvalues_1 = tslib_1.__importDefault(require("lodash.mapvalues"));
const curve_1 = require("./curve");
const normalizeAmount = (tokenAmount) => jsbi_1.default.multiply(tokenAmount.raw, jsbi_1.default.exponentiate(token_utils_1.TEN, jsbi_1.default.BigInt(18 - tokenAmount.token.decimals)));
exports.normalizeAmount = normalizeAmount;
const denormalizeAmount = (tokenAmount) => new token_utils_1.TokenAmount(tokenAmount.token, jsbi_1.default.divide(tokenAmount.raw, jsbi_1.default.exponentiate(token_utils_1.TEN, jsbi_1.default.BigInt(18 - tokenAmount.token.decimals))));
exports.denormalizeAmount = denormalizeAmount;
/**
* Calculates the current virtual price of the exchange.
* @param exchange
* @returns
*/
const calculateVirtualPrice = (exchange) => {
const amount = exchange.lpTotalSupply;
if (amount === undefined || amount.equalTo(0)) {
// pool has no tokens
return null;
}
const price = new token_utils_1.Fraction((0, curve_1.computeD)(exchange.ampFactor, (0, exports.normalizeAmount)(exchange.reserves[0]), (0, exports.normalizeAmount)(exchange.reserves[1])), amount.raw);
return price;
};
exports.calculateVirtualPrice = calculateVirtualPrice;
/**
* Calculates the estimated output amount of a swap.
* @param exchange
* @param fromAmount
* @returns
*/
const calculateEstimatedSwapOutputAmount = (exchange, fromAmount) => {
const [fromReserves, toReserves] = fromAmount.token.equals(exchange.reserves[0].token)
? [exchange.reserves[0], exchange.reserves[1]]
: [exchange.reserves[1], exchange.reserves[0]];
if (fromAmount.equalTo(0)) {
const zero = new token_utils_1.TokenAmount(toReserves.token, token_utils_1.ZERO);
return {
outputAmountBeforeFees: zero,
outputAmount: zero,
fee: zero,
lpFee: zero,
adminFee: zero,
};
}
const amp = exchange.ampFactor;
const amountBeforeFees = jsbi_1.default.subtract((0, exports.normalizeAmount)(toReserves), (0, curve_1.computeY)(amp, jsbi_1.default.add((0, exports.normalizeAmount)(fromReserves), (0, exports.normalizeAmount)(fromAmount)), (0, curve_1.computeD)(amp, (0, exports.normalizeAmount)(fromReserves), (0, exports.normalizeAmount)(toReserves))));
const outputAmountBeforeFees = new token_utils_1.TokenAmount(toReserves.token, amountBeforeFees);
const fee = new token_utils_1.TokenAmount(toReserves.token, exchange.fees.trade.asFraction.multiply(amountBeforeFees).quotient);
const adminFee = new token_utils_1.TokenAmount(toReserves.token, exchange.fees.admin.asFraction.multiply(fee.raw).quotient);
const lpFee = fee.subtract(adminFee);
const outputAmount = new token_utils_1.TokenAmount(toReserves.token, jsbi_1.default.subtract(amountBeforeFees, fee.raw));
return {
outputAmountBeforeFees: (0, exports.denormalizeAmount)(outputAmountBeforeFees),
outputAmount: (0, exports.denormalizeAmount)(outputAmount),
fee: (0, exports.denormalizeAmount)(fee),
lpFee: (0, exports.denormalizeAmount)(lpFee),
adminFee: (0, exports.denormalizeAmount)(adminFee),
};
};
exports.calculateEstimatedSwapOutputAmount = calculateEstimatedSwapOutputAmount;
const N_COINS = jsbi_1.default.BigInt(2);
/**
* Calculates the amount of tokens withdrawn if only withdrawing one token.
* @returns
*/
const calculateEstimatedWithdrawOneAmount = ({ exchange, poolTokenAmount, withdrawToken, }) => {
if (poolTokenAmount.equalTo(0)) {
// final quantities
const quantities = {
withdrawAmount: token_utils_1.ZERO,
withdrawAmountBeforeFees: token_utils_1.ZERO,
swapFee: token_utils_1.ZERO,
withdrawFee: token_utils_1.ZERO,
lpSwapFee: token_utils_1.ZERO,
adminSwapFee: token_utils_1.ZERO,
};
return (0, lodash_mapvalues_1.default)(quantities, (q) => new token_utils_1.TokenAmount(withdrawToken, q));
}
const { ampFactor, fees } = exchange;
const withdrawReserves = exchange.reserves.find((r) => r.token.equals(withdrawToken));
const nonWithdrawReserves = exchange.reserves.find((r) => !r.token.equals(withdrawToken));
const baseReserves = withdrawReserves
? (0, exports.normalizeAmount)(withdrawReserves)
: token_utils_1.ZERO;
const quoteReserves = nonWithdrawReserves
? (0, exports.normalizeAmount)(nonWithdrawReserves)
: token_utils_1.ZERO;
const d_0 = (0, curve_1.computeD)(ampFactor, baseReserves, quoteReserves);
const d_1 = jsbi_1.default.subtract(d_0, jsbi_1.default.divide(jsbi_1.default.multiply(poolTokenAmount.raw, d_0), exchange.lpTotalSupply.raw));
const new_y = (0, curve_1.computeY)(ampFactor, quoteReserves, d_1);
// expected_base_amount = swap_base_amount * d_1 / d_0 - new_y;
const expected_base_amount = jsbi_1.default.subtract(jsbi_1.default.divide(jsbi_1.default.multiply(baseReserves, d_1), d_0), new_y);
// expected_quote_amount = swap_quote_amount - swap_quote_amount * d_1 / d_0;
const expected_quote_amount = jsbi_1.default.subtract(quoteReserves, jsbi_1.default.divide(jsbi_1.default.multiply(quoteReserves, d_1), d_0));
// new_base_amount = swap_base_amount - expected_base_amount * fee / fee_denominator;
const new_base_amount = new token_utils_1.Fraction(baseReserves.toString(), 1).subtract((0, exports.normalizedTradeFee)(fees, N_COINS, expected_base_amount));
// new_quote_amount = swap_quote_amount - expected_quote_amount * fee / fee_denominator;
const new_quote_amount = new token_utils_1.Fraction(quoteReserves.toString(), 1).subtract((0, exports.normalizedTradeFee)(fees, N_COINS, expected_quote_amount));
const dy = new_base_amount.subtract((0, curve_1.computeY)(ampFactor, jsbi_1.default.BigInt(new_quote_amount.toFixed(0)), d_1).toString());
const dy_0 = jsbi_1.default.subtract(baseReserves, new_y);
// lp fees
const swapFee = new token_utils_1.Fraction(dy_0.toString(), 1).subtract(dy);
const withdrawFee = dy.multiply(fees.withdraw.asFraction);
// admin fees
const adminSwapFee = swapFee.multiply(fees.admin.asFraction);
// final LP fees
const lpSwapFee = swapFee.subtract(adminSwapFee);
// final withdraw amount
const withdrawAmount = dy.subtract(withdrawFee).subtract(swapFee);
// final quantities
const quantities = {
withdrawAmount,
withdrawAmountBeforeFees: dy,
swapFee,
withdrawFee,
lpSwapFee,
adminSwapFee,
};
return (0, lodash_mapvalues_1.default)(quantities, (q) => new token_utils_1.TokenAmount(withdrawToken, q.toFixed(0)));
};
exports.calculateEstimatedWithdrawOneAmount = calculateEstimatedWithdrawOneAmount;
/**
* Compute normalized fee for symmetric/asymmetric deposits/withdraws
*/
const normalizedTradeFee = ({ trade }, n_coins, amount) => {
const adjustedTradeFee = new token_utils_1.Fraction(n_coins, jsbi_1.default.multiply(jsbi_1.default.subtract(n_coins, token_utils_1.ONE), jsbi_1.default.BigInt(4)));
return new token_utils_1.Fraction(amount, 1).multiply(trade).multiply(adjustedTradeFee);
};
exports.normalizedTradeFee = normalizedTradeFee;
const calculateEstimatedWithdrawAmount = ({ poolTokenAmount, reserves, fees, lpTotalSupply, }) => {
if (lpTotalSupply.equalTo(0)) {
const zero = reserves.map((r) => new token_utils_1.TokenAmount(r.token, token_utils_1.ZERO));
return {
withdrawAmounts: zero,
withdrawAmountsBeforeFees: zero,
fees: zero,
};
}
const share = poolTokenAmount.divide(lpTotalSupply);
const withdrawAmounts = reserves.map((amount) => {
const baseAmount = share.multiply((0, exports.normalizeAmount)(amount));
const fee = baseAmount.multiply(fees.withdraw.asFraction);
return [
new token_utils_1.TokenAmount(amount.token, jsbi_1.default.BigInt(baseAmount.subtract(fee).toFixed(0))),
{
beforeFees: jsbi_1.default.BigInt(baseAmount.toFixed(0)),
fee: jsbi_1.default.BigInt(fee.toFixed(0)),
},
];
});
return {
withdrawAmountsBeforeFees: withdrawAmounts.map(([amt, { beforeFees }]) => (0, exports.denormalizeAmount)(new token_utils_1.TokenAmount(amt.token, beforeFees))),
withdrawAmounts: [
(0, exports.denormalizeAmount)(withdrawAmounts[0][0]),
(0, exports.denormalizeAmount)(withdrawAmounts[1][0]),
],
fees: withdrawAmounts.map(([amt, { fee }]) => (0, exports.denormalizeAmount)(new token_utils_1.TokenAmount(amt.token, fee))),
};
};
exports.calculateEstimatedWithdrawAmount = calculateEstimatedWithdrawAmount;
/**
* Calculate the estimated amount of LP tokens minted after a deposit.
* @param exchange
* @param depositAmountA
* @param depositAmountB
* @returns
*/
const calculateEstimatedMintAmount = (exchange, depositAmountA, depositAmountB) => {
const normalizedDepositAmountA = (0, exports.normalizeAmount)(new token_utils_1.TokenAmount(exchange.reserves[0].token, depositAmountA));
const normalizedDepositAmountB = (0, exports.normalizeAmount)(new token_utils_1.TokenAmount(exchange.reserves[1].token, depositAmountB));
if (jsbi_1.default.equal(depositAmountA, token_utils_1.ZERO) && jsbi_1.default.equal(depositAmountB, token_utils_1.ZERO)) {
const zero = new token_utils_1.TokenAmount(exchange.lpTotalSupply.token, token_utils_1.ZERO);
return {
mintAmountBeforeFees: zero,
mintAmount: zero,
fees: zero,
};
}
const amp = exchange.ampFactor;
const [reserveA, reserveB] = exchange.reserves;
const d0 = (0, curve_1.computeD)(amp, (0, exports.normalizeAmount)(reserveA), (0, exports.normalizeAmount)(reserveB));
const d1 = (0, curve_1.computeD)(amp, jsbi_1.default.add((0, exports.normalizeAmount)(reserveA), normalizedDepositAmountA), jsbi_1.default.add((0, exports.normalizeAmount)(reserveB), normalizedDepositAmountB));
if (jsbi_1.default.lessThan(d1, d0)) {
throw new Error("New D cannot be less than previous D");
}
const oldBalances = exchange.reserves.map((r) => (0, exports.normalizeAmount)(r));
const newBalances = [
jsbi_1.default.add((0, exports.normalizeAmount)(reserveA), normalizedDepositAmountA),
jsbi_1.default.add((0, exports.normalizeAmount)(reserveB), normalizedDepositAmountB),
];
const adjustedBalances = newBalances.map((newBalance, i) => {
const oldBalance = oldBalances[i];
const idealBalance = new token_utils_1.Fraction(d1, d0).multiply(oldBalance);
const difference = idealBalance.subtract(newBalance);
const diffAbs = difference.greaterThan(0)
? difference
: difference.multiply(-1);
const fee = (0, exports.normalizedTradeFee)(exchange.fees, N_COINS, jsbi_1.default.BigInt(diffAbs.toFixed(0)));
return jsbi_1.default.subtract(newBalance, jsbi_1.default.BigInt(fee.toFixed(0)));
});
const d2 = (0, curve_1.computeD)(amp, adjustedBalances[0], adjustedBalances[1]);
const lpSupply = exchange.lpTotalSupply;
const mintAmountRaw = jsbi_1.default.divide(jsbi_1.default.multiply(lpSupply.raw, jsbi_1.default.subtract(d2, d0)), d0);
const mintAmount = new token_utils_1.TokenAmount(exchange.lpTotalSupply.token, mintAmountRaw);
const mintAmountRawBeforeFees = jsbi_1.default.divide(jsbi_1.default.multiply(lpSupply.raw, jsbi_1.default.subtract(d1, d0)), d0);
const fees = new token_utils_1.TokenAmount(exchange.lpTotalSupply.token, jsbi_1.default.subtract(mintAmountRawBeforeFees, mintAmountRaw));
const mintAmountBeforeFees = new token_utils_1.TokenAmount(exchange.lpTotalSupply.token, mintAmountRawBeforeFees);
return {
mintAmount,
mintAmountBeforeFees,
fees,
};
};
exports.calculateEstimatedMintAmount = calculateEstimatedMintAmount;
//# sourceMappingURL=amounts.js.map