UNPKG

@dahlia-labs/stableswap-sdk

Version:
206 lines 10 kB
import { Fraction, ONE, TEN, TokenAmount, ZERO, } from "@dahlia-labs/token-utils"; import JSBI from "jsbi"; import mapValues from "lodash.mapvalues"; import { computeD, computeY } from "./curve"; export const normalizeAmount = (tokenAmount) => JSBI.multiply(tokenAmount.raw, JSBI.exponentiate(TEN, JSBI.BigInt(18 - tokenAmount.token.decimals))); export const denormalizeAmount = (tokenAmount) => new TokenAmount(tokenAmount.token, JSBI.divide(tokenAmount.raw, JSBI.exponentiate(TEN, JSBI.BigInt(18 - tokenAmount.token.decimals)))); /** * 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, normalizeAmount(exchange.reserves[0]), normalizeAmount(exchange.reserves[1])), 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].token) ? [exchange.reserves[0], exchange.reserves[1]] : [exchange.reserves[1], exchange.reserves[0]]; if (fromAmount.equalTo(0)) { const zero = new TokenAmount(toReserves.token, ZERO); return { outputAmountBeforeFees: zero, outputAmount: zero, fee: zero, lpFee: zero, adminFee: zero, }; } const amp = exchange.ampFactor; const amountBeforeFees = JSBI.subtract(normalizeAmount(toReserves), computeY(amp, JSBI.add(normalizeAmount(fromReserves), normalizeAmount(fromAmount)), computeD(amp, normalizeAmount(fromReserves), normalizeAmount(toReserves)))); const outputAmountBeforeFees = new TokenAmount(toReserves.token, amountBeforeFees); const fee = new TokenAmount(toReserves.token, exchange.fees.trade.asFraction.multiply(amountBeforeFees).quotient); const adminFee = new TokenAmount(toReserves.token, exchange.fees.admin.asFraction.multiply(fee.raw).quotient); const lpFee = fee.subtract(adminFee); const outputAmount = new TokenAmount(toReserves.token, JSBI.subtract(amountBeforeFees, fee.raw)); return { outputAmountBeforeFees: denormalizeAmount(outputAmountBeforeFees), outputAmount: denormalizeAmount(outputAmount), fee: denormalizeAmount(fee), lpFee: denormalizeAmount(lpFee), adminFee: denormalizeAmount(adminFee), }; }; const N_COINS = JSBI.BigInt(2); /** * 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 quantities = { withdrawAmount: ZERO, withdrawAmountBeforeFees: ZERO, swapFee: ZERO, withdrawFee: ZERO, lpSwapFee: ZERO, adminSwapFee: ZERO, }; return mapValues(quantities, (q) => new 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 ? normalizeAmount(withdrawReserves) : ZERO; const quoteReserves = nonWithdrawReserves ? normalizeAmount(nonWithdrawReserves) : ZERO; const d_0 = computeD(ampFactor, baseReserves, quoteReserves); const d_1 = JSBI.subtract(d_0, JSBI.divide(JSBI.multiply(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 = JSBI.subtract(JSBI.divide(JSBI.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.subtract(quoteReserves, JSBI.divide(JSBI.multiply(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, JSBI.BigInt(new_quote_amount.toFixed(0)), d_1).toString()); const dy_0 = JSBI.subtract(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.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 mapValues(quantities, (q) => new TokenAmount(withdrawToken, q.toFixed(0))); }; /** * Compute normalized fee for symmetric/asymmetric deposits/withdraws */ export const normalizedTradeFee = ({ trade }, n_coins, amount) => { const adjustedTradeFee = new Fraction(n_coins, JSBI.multiply(JSBI.subtract(n_coins, ONE), JSBI.BigInt(4))); 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.token, ZERO)); return { withdrawAmounts: zero, withdrawAmountsBeforeFees: zero, fees: zero, }; } const share = poolTokenAmount.divide(lpTotalSupply); const withdrawAmounts = reserves.map((amount) => { const baseAmount = share.multiply(normalizeAmount(amount)); const fee = baseAmount.multiply(fees.withdraw.asFraction); return [ new TokenAmount(amount.token, JSBI.BigInt(baseAmount.subtract(fee).toFixed(0))), { beforeFees: JSBI.BigInt(baseAmount.toFixed(0)), fee: JSBI.BigInt(fee.toFixed(0)), }, ]; }); return { withdrawAmountsBeforeFees: withdrawAmounts.map(([amt, { beforeFees }]) => denormalizeAmount(new TokenAmount(amt.token, beforeFees))), withdrawAmounts: [ denormalizeAmount(withdrawAmounts[0][0]), denormalizeAmount(withdrawAmounts[1][0]), ], fees: withdrawAmounts.map(([amt, { fee }]) => denormalizeAmount(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) => { const normalizedDepositAmountA = normalizeAmount(new TokenAmount(exchange.reserves[0].token, depositAmountA)); const normalizedDepositAmountB = normalizeAmount(new TokenAmount(exchange.reserves[1].token, depositAmountB)); if (JSBI.equal(depositAmountA, ZERO) && JSBI.equal(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, normalizeAmount(reserveA), normalizeAmount(reserveB)); const d1 = computeD(amp, JSBI.add(normalizeAmount(reserveA), normalizedDepositAmountA), JSBI.add(normalizeAmount(reserveB), normalizedDepositAmountB)); if (JSBI.lessThan(d1, d0)) { throw new Error("New D cannot be less than previous D"); } const oldBalances = exchange.reserves.map((r) => normalizeAmount(r)); const newBalances = [ JSBI.add(normalizeAmount(reserveA), normalizedDepositAmountA), JSBI.add(normalizeAmount(reserveB), normalizedDepositAmountB), ]; 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, JSBI.BigInt(diffAbs.toFixed(0))); return JSBI.subtract(newBalance, JSBI.BigInt(fee.toFixed(0))); }); const d2 = computeD(amp, adjustedBalances[0], adjustedBalances[1]); const lpSupply = exchange.lpTotalSupply; const mintAmountRaw = JSBI.divide(JSBI.multiply(lpSupply.raw, JSBI.subtract(d2, d0)), d0); const mintAmount = new TokenAmount(exchange.lpTotalSupply.token, mintAmountRaw); const mintAmountRawBeforeFees = JSBI.divide(JSBI.multiply(lpSupply.raw, JSBI.subtract(d1, d0)), d0); const fees = new TokenAmount(exchange.lpTotalSupply.token, JSBI.subtract(mintAmountRawBeforeFees, mintAmountRaw)); const mintAmountBeforeFees = new TokenAmount(exchange.lpTotalSupply.token, mintAmountRawBeforeFees); return { mintAmount, mintAmountBeforeFees, fees, }; }; //# sourceMappingURL=amounts.js.map