@kamino-finance/klend-sdk 5.1.10 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,43 +64,74 @@ export function calcMaxWithdrawCollateral(
64
64
  debtReserveAddr: PublicKey,
65
65
  obligation: KaminoObligation,
66
66
  repayAmountLamports: Decimal
67
- ) {
67
+ ): {
68
+ canWithdrawRemainingColl: boolean;
69
+ withdrawableCollLamports: Decimal;
70
+ } {
68
71
  const collReserve = kaminoMarket.getReserveByAddress(collReserveAddr)!;
69
- const debtReserve = kaminoMarket.getReserveByAddress(debtReserveAddr)!;
70
-
71
- const debtOraclePx = debtReserve.getOracleMarketPrice();
72
72
  const collOraclePx = collReserve.getOracleMarketPrice();
73
73
  const { maxLtv: collMaxLtv } = obligation.getLtvForReserve(kaminoMarket, collReserve);
74
- const debtBorrowFactor = debtReserve.getBorrowFactor();
75
74
 
76
- const debtPosition = obligation.getBorrowByReserve(debtReserve.address)!;
77
75
  const collPosition = obligation.getDepositByReserve(collReserve.address)!;
78
76
  const initialCollValue = collPosition.amount.floor().div(collReserve.getMintFactor()).mul(collOraclePx);
79
- const remainingDebtAmountLamports = debtPosition.amount.sub(repayAmountLamports);
80
- const remainingDebtBfWeightedValue = remainingDebtAmountLamports
81
- .ceil()
82
- .div(debtReserve.getMintFactor())
83
- .mul(debtBorrowFactor)
84
- .mul(debtOraclePx);
85
-
86
- let isClosingPosition = false;
87
- if (remainingDebtAmountLamports.lte(new Decimal(0)) && obligation.getBorrows().length === 1) {
88
- isClosingPosition = true;
77
+
78
+ let totalRemainingDebtValue = new Decimal(0);
79
+ const borrows = obligation.getBorrows();
80
+ for (const debtPosition of borrows) {
81
+ const debtReserve = kaminoMarket.getReserveByAddress(debtPosition.reserveAddress)!;
82
+ const debtOraclePx = debtReserve.getOracleMarketPrice();
83
+ const debtBorrowFactor = debtReserve.getBorrowFactor();
84
+ let remainingDebtAmountLamports = debtPosition.amount;
85
+ if (debtPosition.reserveAddress.equals(debtReserveAddr)) {
86
+ remainingDebtAmountLamports = remainingDebtAmountLamports.sub(repayAmountLamports);
87
+ }
88
+ const remainingDebtBfWeightedValue = remainingDebtAmountLamports
89
+ .ceil()
90
+ .div(debtReserve.getMintFactor())
91
+ .mul(debtBorrowFactor)
92
+ .mul(debtOraclePx);
93
+ totalRemainingDebtValue = totalRemainingDebtValue.add(remainingDebtBfWeightedValue);
94
+ }
95
+
96
+ let canWithdrawRemainingColl = false;
97
+ if (totalRemainingDebtValue.lte(new Decimal(0)) && borrows.length === 1) {
98
+ canWithdrawRemainingColl = true;
89
99
  }
90
- const numerator = initialCollValue.mul(collMaxLtv).sub(remainingDebtBfWeightedValue);
100
+
101
+ const deposits = obligation.getDeposits();
102
+ const otherCollDeposits = deposits.filter((deposit) => !deposit.reserveAddress.equals(collReserve.address));
103
+
104
+ let totalOtherCollateralValue = new Decimal(0);
105
+ for (const d of otherCollDeposits) {
106
+ const otherCollReserve = kaminoMarket.getReserveByAddress(d.reserveAddress)!;
107
+ const otherCollOraclePx = otherCollReserve.getOracleMarketPrice();
108
+ const otherCollMaxLtv = obligation.getLtvForReserve(kaminoMarket, otherCollReserve).maxLtv;
109
+ const otherCollValue = d.amount
110
+ .floor()
111
+ .div(otherCollReserve.getMintFactor())
112
+ .mul(otherCollOraclePx)
113
+ .mul(otherCollMaxLtv);
114
+ totalOtherCollateralValue = totalOtherCollateralValue.add(otherCollValue);
115
+ }
116
+
117
+ const numerator = initialCollValue.mul(collMaxLtv).add(totalOtherCollateralValue).sub(totalRemainingDebtValue);
118
+
119
+ // If all collateral cannot cover the remaining debt
120
+ if (numerator.lte('0')) {
121
+ return { canWithdrawRemainingColl: false, withdrawableCollLamports: new Decimal(0) };
122
+ }
123
+
91
124
  const denominator = collOraclePx.mul(collMaxLtv);
92
125
  const maxCollWithdrawAmount = numerator.div(denominator);
93
126
  const maxCollateralWithdrawalAmountLamports = maxCollWithdrawAmount.mul(collReserve.getMintFactor()).floor();
94
127
 
95
128
  let withdrawableCollLamports: Decimal;
96
- if (isClosingPosition) {
97
- // 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
98
- const maxSwapCollLamportsWithBuffer = maxCollateralWithdrawalAmountLamports.mul('1.1');
99
- withdrawableCollLamports = Decimal.min(maxSwapCollLamportsWithBuffer, collPosition.amount).floor();
129
+ if (canWithdrawRemainingColl) {
130
+ withdrawableCollLamports = Decimal.min(maxCollateralWithdrawalAmountLamports, collPosition.amount).floor();
100
131
  } else {
101
132
  withdrawableCollLamports = Decimal.max(new Decimal(0), maxCollateralWithdrawalAmountLamports);
102
133
  }
103
- return { isClosingPosition, withdrawableCollLamports };
134
+ return { canWithdrawRemainingColl, withdrawableCollLamports };
104
135
  }
105
136
 
106
137
  export function estimateDebtRepaymentWithColl(props: {
@@ -74,14 +74,11 @@ export async function getRepayWithCollSwapInputs<QuoteResponse>({
74
74
  throw new Error(`Debt reserve with mint ${debtReserve} not found in market ${kaminoMarket.getAddress()}`);
75
75
  }
76
76
 
77
- const { repayAmountLamports, flashRepayAmountLamports } = calcRepayAmountWithSlippage(
78
- kaminoMarket,
79
- debtReserve,
80
- currentSlot,
81
- obligation,
82
- repayAmount,
83
- referrer
84
- );
77
+ const {
78
+ repayAmountLamports,
79
+ flashRepayAmountLamports,
80
+ repayAmount: finalRepayAmount,
81
+ } = calcRepayAmountWithSlippage(kaminoMarket, debtReserve, currentSlot, obligation, repayAmount, referrer);
85
82
 
86
83
  const debtPosition = obligation.getBorrowByReserve(debtReserve.address);
87
84
  const collPosition = obligation.getDepositByReserve(collReserve.address);
@@ -103,8 +100,17 @@ export async function getRepayWithCollSwapInputs<QuoteResponse>({
103
100
  repayAmountLamports
104
101
  );
105
102
 
103
+ // 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
104
+ const maxCollNeededFromOracle = finalRepayAmount
105
+ .mul(debtReserve.getOracleMarketPrice())
106
+ .div(collReserve.getOracleMarketPrice())
107
+ .mul('1.1')
108
+ .mul(collReserve.getMintFactor())
109
+ .ceil();
110
+ const inputAmountLamports = Decimal.min(withdrawableCollLamports, maxCollNeededFromOracle);
111
+
106
112
  const swapQuoteInputs: SwapInputs = {
107
- inputAmountLamports: withdrawableCollLamports,
113
+ inputAmountLamports,
108
114
  inputMint: collTokenMint,
109
115
  outputMint: debtTokenMint,
110
116
  amountDebtAtaBalance: new Decimal(0), // only used for kTokens
@@ -127,7 +133,7 @@ export async function getRepayWithCollSwapInputs<QuoteResponse>({
127
133
  },
128
134
  isClosingPosition,
129
135
  repayAmountLamports,
130
- withdrawableCollLamports
136
+ inputAmountLamports
131
137
  );
132
138
  const uniqueKlendAccounts = uniqueAccounts(klendIxs);
133
139
  const swapQuote = await quoter(swapQuoteInputs, uniqueKlendAccounts);
package/src/utils/ata.ts CHANGED
@@ -154,7 +154,7 @@ export async function getTokenAccountBalanceDecimal(
154
154
  connection: Connection,
155
155
  mint: PublicKey,
156
156
  owner: PublicKey,
157
- tokenProgram: PublicKey = TOKEN_PROGRAM_ID,
157
+ tokenProgram: PublicKey = TOKEN_PROGRAM_ID
158
158
  ): Promise<Decimal> {
159
159
  const ata = getAssociatedTokenAddress(mint, owner, true, tokenProgram);
160
160
  const accInfo = await connection.getAccountInfo(ata);