UNPKG

@kamino-finance/klend-sdk

Version:

Typescript SDK for interacting with the Kamino Lending (klend) protocol

342 lines 18.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.depositLeverageCalcs = exports.estimateDepositMode = exports.estimateAdjustMode = exports.estimateWithdrawMode = exports.calcBorrowAmount = exports.LeverageOption = void 0; exports.calculateMultiplyEffects = calculateMultiplyEffects; exports.calcWithdrawAmounts = calcWithdrawAmounts; exports.calcAdjustAmounts = calcAdjustAmounts; exports.withdrawLeverageCalcs = withdrawLeverageCalcs; exports.adjustDepositLeverageCalcs = adjustDepositLeverageCalcs; exports.adjustWithdrawLeverageCalcs = adjustWithdrawLeverageCalcs; const decimal_js_1 = __importDefault(require("decimal.js")); const classes_1 = require("../classes"); const utils_1 = require("../utils"); const closingPositionDiffTolerance = 0.0001; var LeverageOption; (function (LeverageOption) { LeverageOption["deposit"] = "Deposit"; LeverageOption["withdraw"] = "Withdraw"; LeverageOption["adjust"] = "Adjust"; LeverageOption["close"] = "Close"; })(LeverageOption || (exports.LeverageOption = LeverageOption = {})); async function calculateMultiplyEffects(getPriceByTokenMintDecimal, { depositAmount, withdrawAmount, deposited, borrowed, debtTokenMint, selectedTokenMint, collTokenMint, targetLeverage, activeLeverageOption, flashLoanFeeRatio, debtBorrowFactorPct, priceCollToDebt, priceDebtToColl, }, logEstimations = false) { // calculate estimations for deposit operation const { adjustDepositPosition: depositModeEstimatedDepositAmount, adjustBorrowPosition: depositModeEstimatedBorrowAmount, } = (0, exports.estimateDepositMode)({ priceCollToDebt, priceDebtToColl, amount: depositAmount, targetLeverage, selectedTokenMint, collTokenMint: collTokenMint, flashLoanFee: flashLoanFeeRatio, }); // calculate estimations for withdraw operation const { adjustDepositPosition: withdrawModeEstimatedDepositTokenWithdrawn, adjustBorrowPosition: withdrawModeEstimatedBorrowTokenWithdrawn, } = (0, exports.estimateWithdrawMode)({ priceCollToDebt: priceCollToDebt, collTokenMint, selectedTokenMint, amount: withdrawAmount, deposited: new decimal_js_1.default(deposited), borrowed: new decimal_js_1.default(borrowed), }); // calculate estimations for adjust operation const { adjustDepositPosition: adjustModeEstimatedDepositAmount, adjustBorrowPosition: adjustModeEstimateBorrowAmount, } = (0, exports.estimateAdjustMode)(priceCollToDebt, { targetLeverage, debtTokenMint, collTokenMint, totalDeposited: new decimal_js_1.default(deposited), totalBorrowed: new decimal_js_1.default(borrowed), flashLoanFee: flashLoanFeeRatio, // TODO: is this the right flash borrow? }); if (logEstimations) { console.log('Estimations', (0, classes_1.toJson)({ activeLeverageOption, depositModeEstimatedDepositAmount, depositModeEstimatedBorrowAmount, withdrawModeEstimatedDepositTokenWithdrawn, withdrawModeEstimatedBorrowTokenWithdrawn, adjustModeEstimatedDepositAmount, adjustModeEstimateBorrowAmount, })); } let [isClosingPosition, totalDeposited, totalBorrowed] = [false, new decimal_js_1.default(0), new decimal_js_1.default(0)]; switch (activeLeverageOption) { case LeverageOption.deposit: { // Deposit and Adjust never clos the position isClosingPosition = false; totalDeposited = deposited.add(depositModeEstimatedDepositAmount); totalBorrowed = borrowed.add(depositModeEstimatedBorrowAmount); break; } case LeverageOption.close: case LeverageOption.withdraw: { isClosingPosition = (withdrawModeEstimatedDepositTokenWithdrawn.gte(new decimal_js_1.default(deposited)) || withdrawModeEstimatedBorrowTokenWithdrawn.gte(new decimal_js_1.default(borrowed)) || (0, utils_1.fuzzyEqual)(withdrawModeEstimatedDepositTokenWithdrawn, new decimal_js_1.default(deposited), closingPositionDiffTolerance) || (0, utils_1.fuzzyEqual)(withdrawModeEstimatedBorrowTokenWithdrawn, new decimal_js_1.default(borrowed), closingPositionDiffTolerance)) && !(0, utils_1.fuzzyEqual)(withdrawModeEstimatedDepositTokenWithdrawn, new decimal_js_1.default(0), closingPositionDiffTolerance); totalDeposited = isClosingPosition ? new decimal_js_1.default(0) : deposited.sub(withdrawModeEstimatedDepositTokenWithdrawn); totalBorrowed = isClosingPosition ? new decimal_js_1.default(0) : borrowed.sub(withdrawModeEstimatedBorrowTokenWithdrawn); break; } case LeverageOption.adjust: { // Deposit and Adjust never clos the position isClosingPosition = false; totalDeposited = deposited.add(adjustModeEstimatedDepositAmount); totalBorrowed = borrowed.add(adjustModeEstimateBorrowAmount); break; } } const borrowTokenPrice = await getPriceByTokenMintDecimal(debtTokenMint); const depositTokenPrice = await getPriceByTokenMintDecimal(collTokenMint); const totalDepositedUsd = depositTokenPrice.mul(totalDeposited); const totalBorrowedUsd = borrowTokenPrice.mul(totalBorrowed); const netValueUsd = totalDepositedUsd.minus(totalBorrowedUsd); // TODO marius this is bad, do not convert to sol as we don't only do leveraged loops only const netValueSol = netValueUsd.div(borrowTokenPrice); const ltv = totalBorrowedUsd.mul(debtBorrowFactorPct.div(100)).div(totalDepositedUsd); return { earned: new decimal_js_1.default(0), totalDeposited, totalBorrowed, netValue: netValueSol, netValueUsd: netValueUsd, ltv, }; } /** * returns how much borrowToken will be borrowed to reach leverage given initial collateral amount * @param depositTokenAmount * @param leverage * @param priceAToB * @param flashBorrowFee */ const calcBorrowAmount = ({ depositTokenAmount, targetLeverage, priceCollToDebt, flashLoanFeeRatio, }) => { const initialCollAmountInCollToken = depositTokenAmount; const finalCollAmountInCollToken = initialCollAmountInCollToken.mul(targetLeverage); const finalDebtAmountInCollToken = finalCollAmountInCollToken.sub(initialCollAmountInCollToken); const finalDebtAmountInDebtToken = finalDebtAmountInCollToken.mul(priceCollToDebt); const flashFeeFactor = new decimal_js_1.default(1).add(flashLoanFeeRatio); const debtTokenToBorrow = finalDebtAmountInDebtToken.mul(flashFeeFactor); return debtTokenToBorrow; }; exports.calcBorrowAmount = calcBorrowAmount; const estimateWithdrawMode = (props) => { const { amount, collTokenMint, selectedTokenMint, deposited, borrowed, priceCollToDebt } = props; return calcWithdrawAmounts({ selectedTokenMint, collTokenMint, withdrawAmount: new decimal_js_1.default(amount), priceCollToDebt, currentBorrowPosition: borrowed, currentDepositPosition: deposited, }); }; exports.estimateWithdrawMode = estimateWithdrawMode; function calcWithdrawAmounts(params) { const { currentBorrowPosition, currentDepositPosition, priceCollToDebt, withdrawAmount, selectedTokenMint, collTokenMint, } = params; // MSOL/SOL const currentDepositInCollateralToken = currentDepositPosition; const currentDebtInCollateralToken = currentBorrowPosition.div(priceCollToDebt); const currentNetPositionInCollateralToken = currentDepositInCollateralToken.minus(currentDebtInCollateralToken); const targetLeverage = currentDepositInCollateralToken.div(currentNetPositionInCollateralToken); const initialDepositInCollateralToken = currentDepositPosition.minus(currentBorrowPosition.div(priceCollToDebt)); const amountToWithdrawDepositToken = selectedTokenMint === collTokenMint ? withdrawAmount : withdrawAmount.div(priceCollToDebt); const targetDeposit = initialDepositInCollateralToken.minus(amountToWithdrawDepositToken).mul(targetLeverage); const targetBorrow = (0, exports.calcBorrowAmount)({ depositTokenAmount: initialDepositInCollateralToken.minus(amountToWithdrawDepositToken), priceCollToDebt: new decimal_js_1.default(priceCollToDebt), targetLeverage: new decimal_js_1.default(targetLeverage), flashLoanFeeRatio: new decimal_js_1.default(0), }); const adjustDepositPosition = currentDepositPosition.minus(targetDeposit); const adjustBorrowPosition = currentBorrowPosition.minus(targetBorrow); // TODO: add flashLoan fee here in final values return { adjustDepositPosition, adjustBorrowPosition, }; } /** * Calculate how much token will be deposited or withdrawn in case of position adjustment * @param leverage * @param totalDeposited * @param totalBorrowed */ const estimateAdjustMode = (priceCollToDebt, { targetLeverage, totalDeposited, totalBorrowed, flashLoanFee }) => { return calcAdjustAmounts({ currentBorrowPosition: totalBorrowed, currentDepositPosition: totalDeposited, priceCollToDebt, targetLeverage, flashLoanFee, }); }; exports.estimateAdjustMode = estimateAdjustMode; /** * Calculates the amounts of tokenA to deposit/withdraw and tokenB to borrow/repay proportionally to adjust the leverage of a position. * * @param {AdjustLeverageParams} params - Parameters for the calculation * @param {number} params.targetLeverage - The target leverage for the position * @param {Decimal} params.currentPositionTokenA - The current amount of tokenA in the position * @param {Decimal} params.currentPositionTokenB - The current amount of borrowed tokenB in the position * @param {number} params.priceAtoB - The conversion rate from tokenA to tokenB (tokenA price = tokenB price * priceAtoB) * @returns {AdjustLeverageResult} An object containing the amounts of tokenA to deposit/withdraw and tokenB to borrow/repay */ function calcAdjustAmounts({ targetLeverage, currentBorrowPosition, currentDepositPosition, priceCollToDebt, flashLoanFee, }) { const initialDeposit = currentDepositPosition.minus(currentBorrowPosition.div(priceCollToDebt)); const targetDeposit = initialDeposit.mul(targetLeverage); const targetBorrow = (0, exports.calcBorrowAmount)({ depositTokenAmount: initialDeposit, priceCollToDebt: new decimal_js_1.default(priceCollToDebt), targetLeverage: new decimal_js_1.default(targetLeverage), flashLoanFeeRatio: flashLoanFee, }); const adjustDepositPosition = targetDeposit.minus(currentDepositPosition); const adjustBorrowPosition = targetBorrow.minus(currentBorrowPosition); return { adjustDepositPosition, adjustBorrowPosition, }; } // Given a deposit amount of Deposit|Borrow token // and a target leverage, calculate final { collateral, debt } value const estimateDepositMode = ({ priceCollToDebt, priceDebtToColl, amount, targetLeverage, selectedTokenMint, collTokenMint, flashLoanFee, slippagePct = new decimal_js_1.default(0), }) => { const isDepositingCollToken = selectedTokenMint === collTokenMint; const finalCollTokenAmount = isDepositingCollToken ? new decimal_js_1.default(amount).mul(targetLeverage).toNumber() : new decimal_js_1.default(amount).mul(priceDebtToColl).mul(targetLeverage).toNumber(); const depositCollTokenAmount = isDepositingCollToken ? amount : amount.mul(priceDebtToColl); const borrowAmount = (0, exports.calcBorrowAmount)({ depositTokenAmount: depositCollTokenAmount, targetLeverage: new decimal_js_1.default(targetLeverage), priceCollToDebt: new decimal_js_1.default(priceCollToDebt), flashLoanFeeRatio: new decimal_js_1.default(flashLoanFee), }); const slippageFactor = new decimal_js_1.default(1).add(slippagePct.div(new decimal_js_1.default(100))); const borrowAmountWithSlippage = borrowAmount.mul(slippageFactor); return { adjustDepositPosition: finalCollTokenAmount, adjustBorrowPosition: borrowAmountWithSlippage.toNumber(), }; }; exports.estimateDepositMode = estimateDepositMode; const depositLeverageCalcs = (props) => { // Initialize local variables from the props object const { depositAmount, depositTokenIsCollToken, depositTokenIsSol, priceDebtToColl, targetLeverage, slippagePct, flashLoanFee, } = props; const slippage = slippagePct.div('100'); const initDepositInSol = depositTokenIsSol ? depositAmount : new decimal_js_1.default(0); // Core logic if (depositTokenIsCollToken) { const y = targetLeverage.mul(priceDebtToColl); const x = flashLoanFee.add('1').mul(slippage.add('1')).div(priceDebtToColl); const finalColl = depositAmount.mul(x).div(x.sub(targetLeverage.sub('1').div(y))); const debt = finalColl.sub(depositAmount).mul(x); const flashBorrowColl = finalColl.sub(depositAmount).mul(flashLoanFee.add('1')); return { flashBorrowInCollToken: flashBorrowColl, initDepositInSol, debtTokenToBorrow: debt, collTokenToDeposit: finalColl, swapDebtTokenIn: debt, swapCollTokenExpectedOut: finalColl.sub(depositAmount), }; } else { const y = targetLeverage.mul(priceDebtToColl); const x = flashLoanFee.add('1').mul(slippage.add('1')).div(priceDebtToColl); const finalColl = depositAmount.div(x.sub(targetLeverage.sub('1').div(y))); const flashBorrowColl = finalColl.mul(flashLoanFee.add('1')); const debt = targetLeverage.sub('1').mul(finalColl).div(y); return { flashBorrowInCollToken: flashBorrowColl, initDepositInSol, debtTokenToBorrow: debt, collTokenToDeposit: finalColl, swapDebtTokenIn: debt.add(depositAmount), swapCollTokenExpectedOut: finalColl, }; } }; exports.depositLeverageCalcs = depositLeverageCalcs; function withdrawLeverageCalcs(market, collReserve, debtReserve, priceCollToDebt, withdrawAmount, deposited, borrowed, currentSlot, isClosingPosition, selectedTokenIsCollToken, selectedTokenMint, obligation, flashLoanFee, slippagePct) { // 1. Calculate coll_amount and debt_amount to repay such that we maintain leverage and we withdraw to // the wallet `amountInDepositTokenToWithdrawToWallet` amount of collateral token // We need to withdraw withdrawAmountInDepositToken coll tokens // and repay repayAmountInBorrowToken debt tokens const { adjustDepositPosition: withdrawAmountCalculated, adjustBorrowPosition: initialRepayAmount } = isClosingPosition ? { adjustDepositPosition: deposited, adjustBorrowPosition: borrowed } : calcWithdrawAmounts({ collTokenMint: collReserve.getLiquidityMint(), priceCollToDebt: new decimal_js_1.default(priceCollToDebt), currentDepositPosition: deposited, currentBorrowPosition: borrowed, withdrawAmount: new decimal_js_1.default(withdrawAmount), selectedTokenMint: selectedTokenMint, }); // Add slippage for the accrued interest rate amount const irSlippageBpsForDebt = obligation .estimateObligationInterestRate(market, debtReserve, obligation?.state.borrows[0], currentSlot) .toDecimalPlaces(debtReserve?.getMintDecimals(), decimal_js_1.default.ROUND_CEIL); // add 0.1 to irSlippageBpsForDebt because we don't want to estimate slightly less than SC and end up not repaying enough const repayAmount = initialRepayAmount .mul(irSlippageBpsForDebt.add('0.1').div('10_000').add('1')) .toDecimalPlaces(debtReserve?.getMintDecimals(), decimal_js_1.default.ROUND_CEIL); // 6. Get swap ixs // 5. Get swap estimations to understand how much we need to borrow from borrow reserve // prevent withdrawing more then deposited if we close position const depositTokenWithdrawAmount = !isClosingPosition ? withdrawAmountCalculated.mul(new decimal_js_1.default(1).plus(flashLoanFee)) : withdrawAmountCalculated; // We are swapping debt token // When withdrawing coll, it means we just need to swap enough to pay for the flash borrow const swapAmountIfWithdrawingColl = repayAmount .mul(new decimal_js_1.default(1).plus(flashLoanFee)) .mul(new decimal_js_1.default(1).plus(slippagePct.div(100))) .div(priceCollToDebt); // When withdrawing debt, it means we need to swap just the collateral we are withdrwaing // enough to cover the debt we are repaying, leaving the remaining in the wallet const swapAmountIfWithdrawingDebt = withdrawAmountCalculated; const collTokenSwapIn = selectedTokenIsCollToken ? swapAmountIfWithdrawingColl : swapAmountIfWithdrawingDebt; const debtTokenExpectedSwapOut = collTokenSwapIn.mul(priceCollToDebt).div(new decimal_js_1.default(1).add(slippagePct.div(100))); return { withdrawAmount: withdrawAmountCalculated, repayAmount, collTokenSwapIn, debtTokenExpectedSwapOut, depositTokenWithdrawAmount, }; } function adjustDepositLeverageCalcs(debtReserve, adjustDepositPosition, adjustBorrowPosition, priceDebtToColl, flashLoanFee, slippagePct) { const amountToFlashBorrowDebt = adjustDepositPosition .div(priceDebtToColl) .mul(new decimal_js_1.default(new decimal_js_1.default(1).add(slippagePct.div(100)))) .toDecimalPlaces(debtReserve.stats.decimals, decimal_js_1.default.ROUND_UP); const borrowAmount = adjustDepositPosition .mul(new decimal_js_1.default(1).plus(flashLoanFee)) .mul(new decimal_js_1.default(new decimal_js_1.default(1).add(slippagePct.div(100)))) .div(priceDebtToColl); return { adjustDepositPosition, adjustBorrowPosition, amountToFlashBorrowDebt, borrowAmount, withdrawAmountWithSlippageAndFlashLoanFee: new decimal_js_1.default(0), }; } function adjustWithdrawLeverageCalcs(adjustDepositPosition, adjustBorrowPosition, flashLoanFee, slippagePct) { const withdrawAmountWithSlippageAndFlashLoanFee = decimal_js_1.default.abs(adjustDepositPosition) .mul(new decimal_js_1.default(1).plus(flashLoanFee)) .mul(new decimal_js_1.default(1).add(slippagePct.div(100))); return { adjustDepositPosition, adjustBorrowPosition, amountToFlashBorrowDebt: new decimal_js_1.default(0), borrowAmount: new decimal_js_1.default(0), withdrawAmountWithSlippageAndFlashLoanFee, }; } //# sourceMappingURL=calcs.js.map