@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
206 lines • 13 kB
JavaScript
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
;