UNPKG

@mercurial-finance/dynamic-amm-sdk

Version:

Mercurial Vaults SDK is a typescript library that allows you to interact with Mercurial v2's AMM.

323 lines 17.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StableSwap = void 0; const anchor_1 = require("@coral-xyz/anchor"); const anchor_2 = require("@project-serum/anchor"); const stable_swap_math_1 = require("./stable-swap-math"); const web3_js_1 = require("@solana/web3.js"); const _1 = require("."); const constants_1 = require("../constants"); const marinade_finance_json_1 = __importDefault(require("../marinade-finance.json")); const types_1 = require("../types"); // Precision for base pool virtual price const PRECISION = new anchor_1.BN(1_000_000); const BASE_CACHE_EXPIRE = new anchor_1.BN(60 * 10); const N_COINS = BigInt(2); class StableSwap { amp; tokenMultiplier; depeg; extraAccounts; onChainTime; stakePoolPubkey; constructor(amp, tokenMultiplier, depeg, extraAccounts, onChainTime, stakePoolPubkey) { this.amp = amp; this.tokenMultiplier = tokenMultiplier; this.depeg = depeg; this.extraAccounts = extraAccounts; this.onChainTime = onChainTime; this.stakePoolPubkey = stakePoolPubkey; } getBasePoolVirtualPrice(depegType) { if (depegType['marinade']) { const account = this.extraAccounts.get(constants_1.CURVE_TYPE_ACCOUNTS.marinade.toBase58()); const coder = new anchor_2.BorshCoder(marinade_finance_json_1.default); const stake = coder.accounts.decode('State', account.data); const msolPrice = stake.msolPrice; return msolPrice.mul(PRECISION).div(new anchor_1.BN(0x1_0000_0000)); } if (depegType['lido']) { const account = this.extraAccounts.get(constants_1.CURVE_TYPE_ACCOUNTS.lido.toBase58()); //https://github.com/mercurial-finance/mercurial-dynamic-amm/blob/main/programs/amm/tests/test_depeg_price.rs#L33 const stSolSupply = new anchor_1.BN(account.data.readBigInt64LE(73).toString()); const stSolBalance = new anchor_1.BN(account.data.readBigInt64LE(81).toString()); return stSolBalance.mul(PRECISION).div(stSolSupply); } if (depegType['splStake']) { const account = this.extraAccounts.get(this.stakePoolPubkey.toBase58()); const stakePool = types_1.StakePoolLayout.decode(account.data); return stakePool.totalLamports.mul(PRECISION).div(stakePool.poolTokenSupply); } throw new Error('UnsupportedBasePool'); } updateDepegInfoIfExpired() { if (!this.depeg.depegType['none']) { const expired = this.onChainTime.toNumber() > this.depeg.baseCacheUpdated.add(BASE_CACHE_EXPIRE).toNumber(); if (expired) { this.depeg.baseVirtualPrice = this.getBasePoolVirtualPrice(this.depeg.depegType); this.depeg.baseCacheUpdated = new anchor_1.BN(this.onChainTime); } } } upscaleTokenA(tokenAAmount) { const { tokenAMultiplier } = this.tokenMultiplier; const normalizedTokenAAmount = tokenAAmount.mul(tokenAMultiplier); if (!this.depeg.depegType['none']) { return normalizedTokenAAmount.mul(PRECISION); } return normalizedTokenAAmount; } downscaleTokenA(tokenAAmount) { const { tokenAMultiplier } = this.tokenMultiplier; const denormalizedTokenAAmount = tokenAAmount.div(tokenAMultiplier); if (!this.depeg.depegType['none']) { return denormalizedTokenAAmount.div(PRECISION); } return denormalizedTokenAAmount; } upscaleTokenB(tokenBAmount) { const { tokenBMultiplier } = this.tokenMultiplier; const normalizedTokenBAmount = tokenBAmount.mul(tokenBMultiplier); if (!this.depeg.depegType['none']) { return normalizedTokenBAmount.mul(this.depeg.baseVirtualPrice); } return normalizedTokenBAmount; } downscaleTokenB(tokenBAmount) { const { tokenBMultiplier } = this.tokenMultiplier; const denormalizedTokenBAmount = tokenBAmount.div(tokenBMultiplier); if (!this.depeg.depegType['none']) { return denormalizedTokenBAmount.div(this.depeg.baseVirtualPrice); } return denormalizedTokenBAmount; } computeOutAmountWithoutSlippage(sourceAmount, swapSourceAmount, swapDestinationAmount, invariantD) { const SIXTEEN = new anchor_1.BN(16); const FOUR = new anchor_1.BN(4); const TWO = new anchor_1.BN(2); const amp = new anchor_1.BN(this.amp); const a = amp.mul(SIXTEEN); const b = a; const c = invariantD.mul(FOUR).sub(invariantD.mul(amp).mul(SIXTEEN)); const numerator = TWO.mul(a) .mul(swapSourceAmount) .add(b.mul(swapDestinationAmount)) .add(c) .mul(swapDestinationAmount); const denominator = a.mul(swapSourceAmount).add(TWO.mul(b).mul(swapDestinationAmount).add(c)).mul(swapSourceAmount); return sourceAmount.mul(numerator).div(denominator); } computeOutAmount(sourceAmount, swapSourceAmount, swapDestinationAmount, tradeDirection) { this.updateDepegInfoIfExpired(); const [upscaledSourceAmount, upscaledSwapSourceAmount, upscaledSwapDestinationAmount] = tradeDirection == _1.TradeDirection.AToB ? [ this.upscaleTokenA(sourceAmount), this.upscaleTokenA(swapSourceAmount), this.upscaleTokenB(swapDestinationAmount), ] : [ this.upscaleTokenB(sourceAmount), this.upscaleTokenB(swapSourceAmount), this.upscaleTokenA(swapDestinationAmount), ]; const invariantD = (0, stable_swap_math_1.computeD)(BigInt(this.amp), BigInt(upscaledSwapSourceAmount.toString()), BigInt(upscaledSwapDestinationAmount.toString())); const newSwapSourceAmount = BigInt(upscaledSwapSourceAmount.toString()) + BigInt(upscaledSourceAmount.toString()); const newSwapDestinationAmount = (0, stable_swap_math_1.computeY)(BigInt(this.amp), newSwapSourceAmount, invariantD); let outAmount = upscaledSwapDestinationAmount.sub(new anchor_1.BN(newSwapDestinationAmount.toString())).sub(new anchor_1.BN(1)); let outAmountWithoutSlippage = this.computeOutAmountWithoutSlippage(upscaledSourceAmount, upscaledSwapSourceAmount, upscaledSwapDestinationAmount, new anchor_1.BN(invariantD.toString())); [outAmount, outAmountWithoutSlippage] = tradeDirection == _1.TradeDirection.AToB ? [this.downscaleTokenB(outAmount), this.downscaleTokenB(outAmountWithoutSlippage)] : [this.downscaleTokenA(outAmount), this.downscaleTokenA(outAmountWithoutSlippage)]; return { outAmount, priceImpact: (0, _1.getPriceImpact)(outAmount, outAmountWithoutSlippage), }; } computeD(tokenAAmount, tokenBAmount) { this.updateDepegInfoIfExpired(); const upscaledTokenAAmount = this.upscaleTokenA(tokenAAmount); const upscaledTokenBAmount = this.upscaleTokenB(tokenBAmount); const invariantD = new anchor_1.BN((0, stable_swap_math_1.computeD)(BigInt(this.amp), BigInt(upscaledTokenAAmount.toString()), BigInt(upscaledTokenBAmount.toString())).toString()); if (!this.depeg.depegType['none']) { return invariantD.div(PRECISION); } return invariantD; } computeInAmount(destAmount, swapSourceAmount, swapDestinationAmount, tradeDirection) { this.updateDepegInfoIfExpired(); const [upscaledDestAmount, upscaledSwapSourceAmount, upscaledSwapDestinationAmount] = tradeDirection == _1.TradeDirection.AToB ? [ this.upscaleTokenB(destAmount), this.upscaleTokenA(swapSourceAmount), this.upscaleTokenB(swapDestinationAmount), ] : [ this.upscaleTokenA(destAmount), this.upscaleTokenB(swapSourceAmount), this.upscaleTokenA(swapDestinationAmount), ]; const invariantD = (0, stable_swap_math_1.computeD)(BigInt(this.amp), BigInt(upscaledSwapSourceAmount.toString()), BigInt(upscaledSwapDestinationAmount.toString())); const newSwapDestAmount = BigInt(upscaledSwapDestinationAmount.toString()) - BigInt(upscaledDestAmount.toString()); const newSwapSourceAmount = (0, stable_swap_math_1.computeY)(BigInt(this.amp), newSwapDestAmount, invariantD); const inAmount = new anchor_1.BN(newSwapSourceAmount.toString()).sub(swapSourceAmount); return tradeDirection == _1.TradeDirection.AToB ? this.downscaleTokenA(inAmount) : this.downscaleTokenB(inAmount); } computeImbalanceDeposit(depositAAmount, depositBAmount, swapTokenAAmount, swapTokenBAmount, lpSupply, fees) { this.updateDepegInfoIfExpired(); const [upscaledDepositAAmount, upscaledDepositBAmount, upscaledSwapTokenAAmount, upscaledSwapTokenBAmount] = [ this.upscaleTokenA(depositAAmount), this.upscaleTokenB(depositBAmount), this.upscaleTokenA(swapTokenAAmount), this.upscaleTokenB(swapTokenBAmount), ]; const { mintAmount } = calculateEstimatedMintAmount(BigInt(this.amp), Helper.toFees(fees), BigInt(lpSupply.toString()), [BigInt(upscaledSwapTokenAAmount.toString()), BigInt(upscaledSwapTokenBAmount.toString())], BigInt(upscaledDepositAAmount.toString()), BigInt(upscaledDepositBAmount.toString())); return new anchor_1.BN(mintAmount.toString()); } computeWithdrawOne(lpAmount, lpSupply, swapTokenAAmount, swapTokenBAmount, fees, tradeDirection) { this.updateDepegInfoIfExpired(); const [upscaledSwapTokenAAmount, upscaledSwapTokenBAmount] = [ this.upscaleTokenA(swapTokenAAmount), this.upscaleTokenB(swapTokenBAmount), ]; const { withdrawAmountBeforeFees } = calculateEstimatedWithdrawOneAmount({ ampFactor: BigInt(this.amp), feeInfo: Helper.toFees(fees), lpTotalSupply: BigInt(lpSupply.toString()), poolTokenAmount: BigInt(lpAmount.toString()), reserves: [BigInt(upscaledSwapTokenAAmount.toString()), BigInt(upscaledSwapTokenBAmount.toString())], tradeDirection, }); // Before withdrawal fee return tradeDirection == _1.TradeDirection.AToB ? this.downscaleTokenB(new anchor_1.BN(withdrawAmountBeforeFees.toString())) : this.downscaleTokenA(new anchor_1.BN(withdrawAmountBeforeFees.toString())); } getRemainingAccounts() { let accounts = []; if ('marinade' in this.depeg.depegType) { accounts.push({ pubkey: constants_1.CURVE_TYPE_ACCOUNTS.marinade, isWritable: false, isSigner: false, }); } if ('lido' in this.depeg.depegType) { accounts.push({ pubkey: constants_1.CURVE_TYPE_ACCOUNTS.lido, isWritable: false, isSigner: false, }); } if (!this.stakePoolPubkey.equals(web3_js_1.PublicKey.default)) { accounts.push({ pubkey: this.stakePoolPubkey, isWritable: false, isSigner: false, }); } return accounts; } } exports.StableSwap = StableSwap; function calculateEstimatedWithdrawOneAmount({ ampFactor, feeInfo, lpTotalSupply, reserves, poolTokenAmount, tradeDirection, }) { if (poolTokenAmount == stable_swap_math_1.ZERO) { return { withdrawAmount: stable_swap_math_1.ZERO, withdrawAmountBeforeFees: stable_swap_math_1.ZERO, swapFee: stable_swap_math_1.ZERO, withdrawFee: stable_swap_math_1.ZERO, lpSwapFee: stable_swap_math_1.ZERO, lpWithdrawFee: stable_swap_math_1.ZERO, adminSwapFee: stable_swap_math_1.ZERO, adminWithdrawFee: stable_swap_math_1.ZERO, }; } const [baseReserves, quoteReserves] = tradeDirection == _1.TradeDirection.BToA ? [reserves[0], reserves[1]] : [reserves[1], reserves[0]]; const d_0 = (0, stable_swap_math_1.computeD)(ampFactor, baseReserves, quoteReserves); const d_1 = d_0 - poolTokenAmount * d_0 / lpTotalSupply; const new_y = (0, stable_swap_math_1.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 stable_swap_math_1.Fraction(baseReserves.toString(), 1).subtract((0, stable_swap_math_1.normalizedTradeFee)(feeInfo, N_COINS, expected_base_amount)); // new_quote_amount = swap_quote_amount - expected_quote_amount * fee / fee_denominator; const new_quote_amount = new stable_swap_math_1.Fraction(quoteReserves.toString(), 1).subtract((0, stable_swap_math_1.normalizedTradeFee)(feeInfo, N_COINS, expected_quote_amount)); const dy = new_base_amount.subtract((0, stable_swap_math_1.computeY)(ampFactor, BigInt(new_quote_amount.toFixed(0)), d_1).toString()); const dy_0 = baseReserves - new_y; // lp fees const swapFee = new stable_swap_math_1.Fraction(dy_0.toString(), 1).subtract(dy); const withdrawFee = dy.multiply(feeInfo.withdraw.asFraction); // admin fees const adminSwapFee = swapFee.multiply(feeInfo.adminTrade.asFraction); const adminWithdrawFee = withdrawFee.multiply(feeInfo.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: BigInt(withdrawAmount.toFixed(0)), withdrawAmountBeforeFees: BigInt(dy.toFixed(0)), swapFee: BigInt(swapFee.toFixed(0)), withdrawFee: BigInt(withdrawFee.toFixed(0)), lpSwapFee: BigInt(lpSwapFee.toFixed(0)), lpWithdrawFee: BigInt(lpWithdrawFee.toFixed(0)), adminSwapFee: BigInt(adminSwapFee.toFixed(0)), adminWithdrawFee: BigInt(adminWithdrawFee.toFixed(0)), }; } function calculateEstimatedMintAmount(ampFactor, feeInfo, lpTotalSupply, reserves, depositAmountA, depositAmountB) { if (depositAmountA == stable_swap_math_1.ZERO && depositAmountB == stable_swap_math_1.ZERO) { return { mintAmountBeforeFees: stable_swap_math_1.ZERO, mintAmount: stable_swap_math_1.ZERO, fees: stable_swap_math_1.ZERO, }; } const amp = ampFactor; const [reserveA, reserveB] = reserves; const d0 = (0, stable_swap_math_1.computeD)(amp, reserveA, reserveB); const d1 = (0, stable_swap_math_1.computeD)(amp, reserveA + depositAmountA, reserveB + depositAmountB); if (d1 < d0) { throw new Error('New D cannot be less than previous D'); } const oldBalances = reserves.map((r) => r); const newBalances = [reserveA + depositAmountA, reserveB + depositAmountB]; const adjustedBalances = newBalances.map((newBalance, i) => { const oldBalance = oldBalances[i]; const idealBalance = new stable_swap_math_1.Fraction(d1, d0).multiply(oldBalance); const difference = idealBalance.subtract(newBalance); const diffAbs = difference.greaterThan(0) ? difference : difference.multiply(-1); const fee = (0, stable_swap_math_1.normalizedTradeFee)(feeInfo, N_COINS, BigInt(diffAbs.toFixed(0))); return newBalance - BigInt(fee.toFixed(0)); }); const d2 = (0, stable_swap_math_1.computeD)(amp, adjustedBalances[0], adjustedBalances[1]); const lpSupply = lpTotalSupply; const mintAmountRaw = lpSupply * (d2 - d0) / d0; const mintAmountRawBeforeFees = lpSupply * (d1 - d0) / d0; const fees = mintAmountRawBeforeFees - mintAmountRaw; return { mintAmount: mintAmountRaw, mintAmountBeforeFees: mintAmountRawBeforeFees, fees, }; } // Helper class to convert the type to the type from saber stable calculator class Helper { static toFees(fees) { return { adminTrade: new stable_swap_math_1.Percent(fees.protocolTradeFeeNumerator, fees.protocolTradeFeeDenominator), trade: new stable_swap_math_1.Percent(fees.tradeFeeNumerator, fees.tradeFeeDenominator), adminWithdraw: new stable_swap_math_1.Percent(0, 100), withdraw: new stable_swap_math_1.Percent(0, 100), }; } } //# sourceMappingURL=stable-swap.js.map