UNPKG

@dahlia-labs/stableswap-sdk

Version:
218 lines 12.5 kB
"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