UNPKG

@kamino-finance/klend-sdk

Version:

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

206 lines 13 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMaxWithdrawLtvCheck = exports.MaxWithdrawLtvCheck = void 0; exports.getRepayWithCollSwapInputs = getRepayWithCollSwapInputs; exports.getRepayWithCollIxs = getRepayWithCollIxs; exports.getMaxCollateralFromRepayAmount = getMaxCollateralFromRepayAmount; const classes_1 = require("../classes"); const leverage_1 = require("../leverage"); const utils_1 = require("../utils"); const decimal_js_1 = __importDefault(require("decimal.js")); const repay_with_collateral_calcs_1 = require("./repay_with_collateral_calcs"); var MaxWithdrawLtvCheck; (function (MaxWithdrawLtvCheck) { MaxWithdrawLtvCheck[MaxWithdrawLtvCheck["MAX_LTV"] = 0] = "MAX_LTV"; MaxWithdrawLtvCheck[MaxWithdrawLtvCheck["LIQUIDATION_THRESHOLD"] = 1] = "LIQUIDATION_THRESHOLD"; })(MaxWithdrawLtvCheck || (exports.MaxWithdrawLtvCheck = MaxWithdrawLtvCheck = {})); async function getRepayWithCollSwapInputs({ collTokenMint, currentSlot, debtTokenMint, kaminoMarket, obligation, quoter, referrer, repayAmount, isClosingPosition, budgetAndPriorityFeeIxs, scopeRefreshConfig, useV2Ixs, }) { const collReserve = kaminoMarket.getExistingReserveByMint(collTokenMint); const debtReserve = kaminoMarket.getExistingReserveByMint(debtTokenMint); const { repayAmountLamports, flashRepayAmountLamports, repayAmount: finalRepayAmount, } = (0, repay_with_collateral_calcs_1.calcRepayAmountWithSlippage)(kaminoMarket, debtReserve, currentSlot, obligation, repayAmount, referrer); const debtPosition = obligation.getBorrowByReserve(debtReserve.address); const collPosition = obligation.getDepositByReserve(collReserve.address); if (!debtPosition) { throw new Error(`Debt position not found for ${debtReserve.stats.symbol} reserve ${debtReserve.address} in obligation ${obligation.obligationAddress}`); } if (!collPosition) { throw new Error(`Collateral position not found for ${collReserve.stats.symbol} reserve ${collReserve.address} in obligation ${obligation.obligationAddress}`); } const { maxWithdrawableCollLamports } = (0, repay_with_collateral_calcs_1.calcMaxWithdrawCollateral)(kaminoMarket, obligation, collReserve.address, debtReserve.address, repayAmountLamports); const maxCollNeededFromOracle = getMaxCollateralFromRepayAmount(finalRepayAmount, debtReserve, collReserve); const inputAmountLamports = decimal_js_1.default.min(maxWithdrawableCollLamports, maxCollNeededFromOracle); // Build the repay & withdraw collateral tx to get the number of accounts const klendIxs = await buildRepayWithCollateralIxs(kaminoMarket, debtReserve, collReserve, obligation, referrer, currentSlot, budgetAndPriorityFeeIxs, scopeRefreshConfig, { preActionIxs: [], swapIxs: [], lookupTables: [], quote: {}, }, isClosingPosition, repayAmountLamports, inputAmountLamports, useV2Ixs); const uniqueKlendAccounts = (0, utils_1.uniqueAccountsWithProgramIds)(klendIxs.instructions); const swapQuoteInputs = { inputAmountLamports, inputMint: collTokenMint, outputMint: debtTokenMint, amountDebtAtaBalance: undefined, // only used for kTokens }; const swapQuote = await quoter(swapQuoteInputs, uniqueKlendAccounts); const swapQuotePxDebtToColl = swapQuote.priceAInB; const collSwapInLamports = flashRepayAmountLamports .div(debtReserve.getMintFactor()) .div(swapQuotePxDebtToColl) .mul(collReserve.getMintFactor()) .ceil(); return { swapInputs: { inputAmountLamports: collSwapInLamports, minOutAmountLamports: flashRepayAmountLamports, inputMint: collTokenMint, outputMint: debtTokenMint, amountDebtAtaBalance: undefined, // only used for kTokens }, flashLoanInfo: klendIxs.flashLoanInfo, initialInputs: { debtRepayAmountLamports: repayAmountLamports, flashRepayAmountLamports, maxCollateralWithdrawLamports: maxWithdrawableCollLamports, swapQuote, currentSlot, klendAccounts: uniqueKlendAccounts, }, }; } async function getRepayWithCollIxs({ repayAmount, isClosingPosition, budgetAndPriorityFeeIxs, collTokenMint, currentSlot, debtTokenMint, kaminoMarket, obligation, quoter, swapper, referrer, scopeRefreshConfig, useV2Ixs, logger = console.log, }) { const { swapInputs, initialInputs } = await getRepayWithCollSwapInputs({ collTokenMint, currentSlot, debtTokenMint, kaminoMarket, obligation, quoter, referrer, repayAmount, isClosingPosition, budgetAndPriorityFeeIxs, scopeRefreshConfig, useV2Ixs, }); const { debtRepayAmountLamports, flashRepayAmountLamports, maxCollateralWithdrawLamports, swapQuote } = initialInputs; const { inputAmountLamports: collSwapInLamports } = swapInputs; const collReserve = kaminoMarket.getExistingReserveByMint(collTokenMint); const debtReserve = kaminoMarket.getExistingReserveByMint(debtTokenMint); // the client should use these values to prevent this input, but the tx may succeed, so we don't want to fail // there is also a chance that the tx will consume debt token from the user's ata which they would not expect if (collSwapInLamports.greaterThan(maxCollateralWithdrawLamports)) { logger(`Collateral swap in amount ${collSwapInLamports} exceeds max withdrawable collateral ${maxCollateralWithdrawLamports}, tx may fail with slippage`); swapInputs.inputAmountLamports = maxCollateralWithdrawLamports; } const actualSwapInLamports = decimal_js_1.default.min(collSwapInLamports, maxCollateralWithdrawLamports); logger(`Expected to swap in: ${actualSwapInLamports.div(collReserve.getMintFactor())} ${collReserve.symbol}, for: ${flashRepayAmountLamports.div(debtReserve.getMintFactor())} ${debtReserve.symbol}, quoter px: ${swapQuote.priceAInB} ${debtReserve.symbol}/${collReserve.symbol}, required px: ${flashRepayAmountLamports .div(debtReserve.getMintFactor()) .div(actualSwapInLamports.div(collReserve.getMintFactor()))} ${debtReserve.symbol}/${collReserve.symbol}`); const swapResponses = await swapper(swapInputs, initialInputs.klendAccounts, swapQuote); return Promise.all(swapResponses.map(async (swapResponse) => { const ixs = await buildRepayWithCollateralIxs(kaminoMarket, debtReserve, collReserve, obligation, referrer, currentSlot, budgetAndPriorityFeeIxs, scopeRefreshConfig, swapResponse, isClosingPosition, debtRepayAmountLamports, swapInputs.inputAmountLamports, useV2Ixs); return { ixs: ixs.instructions, lookupTables: swapResponse.lookupTables, swapInputs, flashLoanInfo: ixs.flashLoanInfo, initialInputs, quote: swapResponse.quote.quoteResponse, }; })); } async function buildRepayWithCollateralIxs(market, debtReserve, collReserve, obligation, referrer, currentSlot, budgetAndPriorityFeeIxs, scopeRefreshConfig, swapQuoteIxs, isClosingPosition, debtRepayAmountLamports, collWithdrawLamports, useV2Ixs) { // 1. Create atas & budget txns const budgetIxs = budgetAndPriorityFeeIxs || (0, utils_1.getComputeBudgetAndPriorityFeeIxs)(1_400_000); const atas = [ { mint: collReserve.getLiquidityMint(), tokenProgram: collReserve.getLiquidityTokenProgram() }, { mint: debtReserve.getLiquidityMint(), tokenProgram: debtReserve.getLiquidityTokenProgram() }, ]; const atasAndIxs = (0, utils_1.createAtasIdempotent)(obligation.state.owner, atas); const [, { ata: debtTokenAta }] = atasAndIxs; const scopeRefreshIxn = await (0, leverage_1.getScopeRefreshIx)(market, collReserve, debtReserve, obligation, scopeRefreshConfig); // 2. Flash borrow & repay the debt to repay amount needed const { flashBorrowIx, flashRepayIx } = (0, leverage_1.getFlashLoanInstructions)({ borrowIxIndex: budgetIxs.length + atasAndIxs.length + (scopeRefreshIxn.length > 0 ? 1 : 0), walletPublicKey: obligation.state.owner, lendingMarketAuthority: market.getLendingMarketAuthority(), lendingMarketAddress: market.getAddress(), reserve: debtReserve, amountLamports: debtRepayAmountLamports, destinationAta: debtTokenAta, // TODO(referrals): once we support referrals, we will have to replace the placeholder args below: referrerAccount: market.programId, referrerTokenState: market.programId, programId: market.programId, }); const requestElevationGroup = !isClosingPosition && obligation.state.elevationGroup !== 0; const maxWithdrawLtvCheck = (0, exports.getMaxWithdrawLtvCheck)(obligation, debtRepayAmountLamports, debtReserve, collWithdrawLamports, collReserve); // 3. Repay using the flash borrowed funds & withdraw collateral to swap and pay the flash loan let repayAndWithdrawAction; if (maxWithdrawLtvCheck === MaxWithdrawLtvCheck.MAX_LTV) { repayAndWithdrawAction = await classes_1.KaminoAction.buildRepayAndWithdrawTxns(market, isClosingPosition ? utils_1.U64_MAX : debtRepayAmountLamports.toString(), debtReserve.getLiquidityMint(), isClosingPosition ? utils_1.U64_MAX : collWithdrawLamports.toString(), collReserve.getLiquidityMint(), obligation.state.owner, currentSlot, obligation, useV2Ixs, undefined, 0, false, requestElevationGroup, undefined, referrer); } else { repayAndWithdrawAction = await classes_1.KaminoAction.buildRepayAndWithdrawV2Txns(market, isClosingPosition ? utils_1.U64_MAX : debtRepayAmountLamports.toString(), debtReserve.getLiquidityMint(), isClosingPosition ? utils_1.U64_MAX : collWithdrawLamports.toString(), collReserve.getLiquidityMint(), obligation.state.owner, currentSlot, obligation, undefined, 0, false, requestElevationGroup, undefined, referrer); } // 4. Swap collateral to debt to repay flash loan const { preActionIxs, swapIxs } = swapQuoteIxs; const swapInstructions = (0, utils_1.removeBudgetIxs)(swapIxs); const ixs = [ ...scopeRefreshIxn, ...budgetIxs, ...atasAndIxs.map((x) => x.createAtaIx), flashBorrowIx, ...preActionIxs, ...classes_1.KaminoAction.actionToIxs(repayAndWithdrawAction), ...swapInstructions, flashRepayIx, ]; const res = { flashLoanInfo: { flashBorrowReserve: debtReserve.address, flashLoanFee: debtReserve.getFlashLoanFee(), }, instructions: ixs, }; return res; } const getMaxWithdrawLtvCheck = (obligation, repayAmountLamports, debtReserve, collWithdrawAmount, collReserve) => { const [finalLtv, finalMaxLtv] = calculatePostOperationLtv(obligation, repayAmountLamports, debtReserve, collWithdrawAmount, collReserve); if (finalLtv.lte(finalMaxLtv)) { return MaxWithdrawLtvCheck.MAX_LTV; } return obligation.refreshedStats.userTotalBorrowBorrowFactorAdjusted.gte(obligation.refreshedStats.borrowLimit) ? MaxWithdrawLtvCheck.LIQUIDATION_THRESHOLD : MaxWithdrawLtvCheck.MAX_LTV; }; exports.getMaxWithdrawLtvCheck = getMaxWithdrawLtvCheck; function calculatePostOperationLtv(obligation, repayAmountLamports, debtReserve, collWithdrawAmount, collReserve) { const repayValue = repayAmountLamports .div(debtReserve.getMintFactor()) .mul(debtReserve.getOracleMarketPrice()) .mul(debtReserve.getBorrowFactor()); const collWithdrawValue = collWithdrawAmount.div(collReserve.getMintFactor()).mul(collReserve.getOracleMarketPrice()); // Calculate new borrow value and deposit value const newBorrowBfValue = decimal_js_1.default.max(new decimal_js_1.default(0), obligation.refreshedStats.userTotalBorrowBorrowFactorAdjusted.sub(repayValue)); const newDepositValue = decimal_js_1.default.max(new decimal_js_1.default(0), obligation.refreshedStats.userTotalDeposit.sub(collWithdrawValue)); const newMaxBorrowableValue = decimal_js_1.default.max(new decimal_js_1.default(0), obligation.refreshedStats.borrowLimit.sub(collWithdrawValue.mul(collReserve.stats.loanToValue))); const newMaxLtv = newMaxBorrowableValue.div(newDepositValue); // return final ltv and final max ltv return [newBorrowBfValue.div(newDepositValue), newMaxLtv]; } function getMaxCollateralFromRepayAmount(repayAmount, debtReserve, collReserve) { // sanity check: we have extra collateral to swap, but we want to ensure we don't quote for way more than needed and get a bad px return repayAmount .mul(debtReserve.getOracleMarketPrice()) .div(collReserve.getOracleMarketPrice()) .mul('1.1') .mul(collReserve.getMintFactor()) .ceil(); } //# sourceMappingURL=repay_with_collateral_operations.js.map