@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
134 lines • 8.75 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.calcFlashRepayAmount = void 0;
exports.calcRepayAmountWithSlippage = calcRepayAmountWithSlippage;
exports.calcMaxWithdrawCollateral = calcMaxWithdrawCollateral;
exports.estimateDebtRepaymentWithColl = estimateDebtRepaymentWithColl;
exports.estimateCollNeededForDebtRepayment = estimateCollNeededForDebtRepayment;
const decimal_js_1 = __importDefault(require("decimal.js"));
const classes_1 = require("../classes");
const web3_js_1 = require("@solana/web3.js");
const utils_1 = require("../classes/utils");
const repay_with_collateral_operations_1 = require("./repay_with_collateral_operations");
function calcRepayAmountWithSlippage(kaminoMarket, debtReserve, currentSlot, obligation, amount, referrer) {
const interestRateAccrued = obligation
.estimateObligationInterestRate(kaminoMarket, debtReserve, obligation.state.borrows.find((borrow) => borrow.borrowReserve.equals(debtReserve.address)), currentSlot)
.toDecimalPlaces(debtReserve.state.liquidity.mintDecimals.toNumber(), decimal_js_1.default.ROUND_CEIL);
// add 0.1% to interestRateAccrued because we don't want to estimate slightly less than SC and end up not repaying enough
const repayAmountIrAdjusted = amount
.mul(interestRateAccrued.mul(new decimal_js_1.default('1.001')))
.toDecimalPlaces(debtReserve.state.liquidity.mintDecimals.toNumber(), decimal_js_1.default.ROUND_CEIL);
let repayAmount;
// Ensure when repaying close to the full amount, we repay the full amount as otherwise we might end up having a small amount left
if (repayAmountIrAdjusted.greaterThanOrEqualTo((0, utils_1.lamportsToDecimal)(obligation.getBorrowByReserve(debtReserve.address)?.amount || new decimal_js_1.default(0), debtReserve.stats.decimals))) {
repayAmount = repayAmountIrAdjusted;
}
else {
repayAmount = amount;
}
const repayAmountLamports = (0, classes_1.numberToLamportsDecimal)(repayAmount, debtReserve.stats.decimals);
const { flashRepayAmountLamports } = (0, exports.calcFlashRepayAmount)({
reserve: debtReserve,
referralFeeBps: kaminoMarket.state.referralFeeBps,
hasReferral: !referrer.equals(web3_js_1.PublicKey.default),
flashBorrowAmountLamports: repayAmountLamports,
});
return { repayAmount, repayAmountLamports, flashRepayAmountLamports };
}
const calcFlashRepayAmount = (props) => {
const { reserve, referralFeeBps, hasReferral, flashBorrowAmountLamports } = props;
const { referrerFees, protocolFees } = reserve.calculateFlashLoanFees(flashBorrowAmountLamports, referralFeeBps, hasReferral);
const flashRepayAmountLamports = flashBorrowAmountLamports.add(referrerFees).add(protocolFees);
return {
flashRepayAmountLamports,
};
};
exports.calcFlashRepayAmount = calcFlashRepayAmount;
function calcMaxWithdrawCollateral(market, obligation, collReserveAddr, debtReserveAddr, repayAmountLamports) {
const deposit = obligation.getDepositByReserve(collReserveAddr);
const borrow = obligation.getBorrowByReserve(debtReserveAddr);
const depositReserve = market.getReserveByAddress(deposit.reserveAddress);
const debtReserve = market.getReserveByAddress(borrow.reserveAddress);
const depositTotalLamports = deposit.amount.floor(); // TODO: can remove floor, we have lamports only for deposits
// Calculate the market value of the remaining debt after repaying
const remainingBorrowLamports = borrow.amount.sub(repayAmountLamports).ceil();
const remainingBorrowAmount = remainingBorrowLamports.div(debtReserve.getMintFactor());
let remainingBorrowsValue = remainingBorrowAmount.mul(debtReserve.getOracleMarketPrice());
if (obligation.getBorrows().length > 1) {
remainingBorrowsValue = obligation
.getBorrows()
.filter((p) => !p.reserveAddress.equals(borrow.reserveAddress))
.reduce((acc, b) => acc.add(b.marketValueRefreshed), new decimal_js_1.default('0'));
}
const hypotheticalWithdrawLamports = (0, repay_with_collateral_operations_1.getMaxCollateralFromRepayAmount)(repayAmountLamports.div(debtReserve.getMintFactor()), debtReserve, depositReserve);
// Calculate the max withdraw ltv we can withdraw up to
const maxWithdrawLtvCheck = (0, repay_with_collateral_operations_1.getMaxWithdrawLtvCheck)(obligation, repayAmountLamports, debtReserve, hypotheticalWithdrawLamports, depositReserve);
// Calculate the max borrowable value remaining against deposits
let maxBorrowableValueRemainingAgainstDeposits = new decimal_js_1.default('0');
if (obligation.getDeposits().length > 1) {
maxBorrowableValueRemainingAgainstDeposits = obligation
.getDeposits()
.filter((p) => !p.reserveAddress.equals(deposit.reserveAddress))
.reduce((acc, d) => {
const { maxLtv, liquidationLtv } = obligation.getLtvForReserve(market, d.reserveAddress);
const maxWithdrawLtv = maxWithdrawLtvCheck === repay_with_collateral_operations_1.MaxWithdrawLtvCheck.LIQUIDATION_THRESHOLD ? liquidationLtv : maxLtv;
return acc.add(d.marketValueRefreshed.mul(maxWithdrawLtv));
}, new decimal_js_1.default('0'));
}
// if the remaining borrow value is less than the
// this means that the user's ltv is less or equal to the max ltv
if (maxBorrowableValueRemainingAgainstDeposits.gte(remainingBorrowsValue)) {
return {
maxWithdrawableCollLamports: depositTotalLamports,
canWithdrawAllColl: true,
repayingAllDebt: repayAmountLamports.gte(borrow.amount),
};
}
else {
const { maxLtv: collMaxLtv, liquidationLtv: collLiquidationLtv } = obligation.getLtvForReserve(market, depositReserve.address);
const maxWithdrawLtv = maxWithdrawLtvCheck === repay_with_collateral_operations_1.MaxWithdrawLtvCheck.LIQUIDATION_THRESHOLD ? collLiquidationLtv : collMaxLtv;
const numerator = deposit.marketValueRefreshed
.mul(maxWithdrawLtv)
.add(maxBorrowableValueRemainingAgainstDeposits)
.sub(remainingBorrowsValue);
const denominator = depositReserve.getOracleMarketPrice().mul(maxWithdrawLtv);
const maxCollWithdrawAmount = numerator.div(denominator);
const maxWithdrawableCollLamports = maxCollWithdrawAmount.mul(depositReserve.getMintFactor()).floor();
return {
maxWithdrawableCollLamports,
canWithdrawAllColl: false,
repayingAllDebt: repayAmountLamports.gte(borrow.amount),
};
}
}
function estimateDebtRepaymentWithColl(props) {
const { collAmount, priceDebtToColl, slippagePct, flashLoanFeePct, kaminoMarket, debtTokenMint, obligation, currentSlot, } = props;
const slippageMultiplier = new decimal_js_1.default(1.0).add(slippagePct.div('100'));
const flashLoanFeeMultiplier = new decimal_js_1.default(1.0).add(flashLoanFeePct.div('100'));
const debtReserve = kaminoMarket.getExistingReserveByMint(debtTokenMint);
const debtAfterSwap = collAmount.div(slippageMultiplier).div(priceDebtToColl);
const debtAfterFlashLoanRepay = debtAfterSwap.div(flashLoanFeeMultiplier);
const accruedInterestRate = obligation
.estimateObligationInterestRate(kaminoMarket, debtReserve, obligation.getObligationLiquidityByReserve(debtReserve.address), currentSlot)
.toDecimalPlaces(debtReserve.state.liquidity.mintDecimals.toNumber(), decimal_js_1.default.ROUND_CEIL);
// Estimate slightly more, by adding 1% to IR in order to avoid the case where UI users can repay the max we allow them
const debtIrAdjusted = debtAfterFlashLoanRepay
.div(accruedInterestRate.mul(new decimal_js_1.default('1.01')))
.toDecimalPlaces(debtReserve.state.liquidity.mintDecimals.toNumber(), decimal_js_1.default.ROUND_CEIL);
return debtIrAdjusted;
}
function estimateCollNeededForDebtRepayment(props) {
const { debtAmount, // in decimals
priceDebtToColl, slippagePct, flashLoanFeePct, } = props;
const slippageRatio = slippagePct.div('100');
const flashLoanFeeRatio = flashLoanFeePct.div('100');
const slippageMultiplier = new decimal_js_1.default(1.0).add(slippageRatio);
const flashLoanFeeMultiplier = new decimal_js_1.default(1.0).add(flashLoanFeeRatio);
const debtFlashLoanRepay = debtAmount.mul(flashLoanFeeMultiplier);
const collToSwap = debtFlashLoanRepay.mul(slippageMultiplier).mul(priceDebtToColl);
return collToSwap;
}
//# sourceMappingURL=repay_with_collateral_calcs.js.map