@kamino-finance/klend-sdk 4.0.1 → 4.1.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.
Files changed (72) hide show
  1. package/README.md +2 -2
  2. package/dist/classes/action.d.ts +3 -1
  3. package/dist/classes/action.d.ts.map +1 -1
  4. package/dist/classes/action.js +62 -61
  5. package/dist/classes/action.js.map +1 -1
  6. package/dist/classes/market.d.ts +5 -5
  7. package/dist/classes/market.d.ts.map +1 -1
  8. package/dist/classes/market.js +14 -14
  9. package/dist/classes/market.js.map +1 -1
  10. package/dist/classes/obligation.d.ts +10 -1
  11. package/dist/classes/obligation.d.ts.map +1 -1
  12. package/dist/classes/obligation.js +48 -28
  13. package/dist/classes/obligation.js.map +1 -1
  14. package/dist/classes/reserve.d.ts +3 -1
  15. package/dist/classes/reserve.d.ts.map +1 -1
  16. package/dist/classes/reserve.js +39 -5
  17. package/dist/classes/reserve.js.map +1 -1
  18. package/dist/classes/shared.d.ts +8 -0
  19. package/dist/classes/shared.d.ts.map +1 -1
  20. package/dist/classes/shared.js +6 -1
  21. package/dist/classes/shared.js.map +1 -1
  22. package/dist/classes/vault.d.ts.map +1 -1
  23. package/dist/classes/vault.js +19 -5
  24. package/dist/classes/vault.js.map +1 -1
  25. package/dist/lending_operations/repay_with_collateral_calcs.d.ts +18 -1
  26. package/dist/lending_operations/repay_with_collateral_calcs.d.ts.map +1 -1
  27. package/dist/lending_operations/repay_with_collateral_calcs.js +62 -0
  28. package/dist/lending_operations/repay_with_collateral_calcs.js.map +1 -1
  29. package/dist/lending_operations/repay_with_collateral_operations.d.ts +27 -43
  30. package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
  31. package/dist/lending_operations/repay_with_collateral_operations.js +99 -152
  32. package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
  33. package/dist/leverage/operations.d.ts +14 -3
  34. package/dist/leverage/operations.d.ts.map +1 -1
  35. package/dist/leverage/operations.js +184 -128
  36. package/dist/leverage/operations.js.map +1 -1
  37. package/dist/utils/ata.d.ts +12 -3
  38. package/dist/utils/ata.d.ts.map +1 -1
  39. package/dist/utils/ata.js +26 -39
  40. package/dist/utils/ata.js.map +1 -1
  41. package/dist/utils/index.d.ts +0 -2
  42. package/dist/utils/index.d.ts.map +1 -1
  43. package/dist/utils/index.js +0 -2
  44. package/dist/utils/index.js.map +1 -1
  45. package/dist/utils/instruction.d.ts +1 -0
  46. package/dist/utils/instruction.d.ts.map +1 -1
  47. package/dist/utils/instruction.js +18 -0
  48. package/dist/utils/instruction.js.map +1 -1
  49. package/package.json +3 -3
  50. package/src/classes/action.ts +92 -98
  51. package/src/classes/market.ts +19 -20
  52. package/src/classes/obligation.ts +80 -38
  53. package/src/classes/reserve.ts +56 -8
  54. package/src/classes/shared.ts +10 -0
  55. package/src/classes/vault.ts +23 -19
  56. package/src/lending_operations/repay_with_collateral_calcs.ts +98 -1
  57. package/src/lending_operations/repay_with_collateral_operations.ts +233 -253
  58. package/src/leverage/operations.ts +227 -140
  59. package/src/utils/ata.ts +33 -56
  60. package/src/utils/index.ts +0 -2
  61. package/src/utils/instruction.ts +22 -0
  62. package/dist/utils/layout.d.ts +0 -14
  63. package/dist/utils/layout.d.ts.map +0 -1
  64. package/dist/utils/layout.js +0 -123
  65. package/dist/utils/layout.js.map +0 -1
  66. package/dist/utils/syncNative.d.ts +0 -11
  67. package/dist/utils/syncNative.d.ts.map +0 -1
  68. package/dist/utils/syncNative.js +0 -45
  69. package/dist/utils/syncNative.js.map +0 -1
  70. package/src/global.d.ts +0 -1
  71. package/src/utils/layout.ts +0 -118
  72. package/src/utils/syncNative.ts +0 -22
@@ -20,7 +20,7 @@ import {
20
20
  TokenOracleData,
21
21
  U64_MAX,
22
22
  } from '../utils';
23
- import { ReserveDataType, ReserveFarmInfo, ReserveRewardYield, ReserveStatus } from './shared';
23
+ import { FeeCalculation, Fees, ReserveDataType, ReserveFarmInfo, ReserveRewardYield, ReserveStatus } from './shared';
24
24
  import { Reserve, ReserveFields } from '../idl_codegen/accounts';
25
25
  import { BorrowRateCurve, CurvePointFields, ReserveConfig, UpdateConfigMode } from '../idl_codegen/types';
26
26
  import { calculateAPYFromAPR, getBorrowRate, lamportsToNumberDecimal, parseTokenSymbol, positiveOrZero } from './utils';
@@ -642,6 +642,53 @@ export class KaminoReserve {
642
642
  return this.state.collateral.mintPubkey;
643
643
  }
644
644
 
645
+ calculateFees(
646
+ amountLamports: Decimal,
647
+ borrowFeeRate: Decimal,
648
+ feeCalculation: FeeCalculation,
649
+ referralFeeBps: number,
650
+ hasReferrer: boolean
651
+ ): Fees {
652
+ const referralFeeRate = new Decimal(referralFeeBps).div(ONE_HUNDRED_PCT_IN_BPS);
653
+ if (borrowFeeRate.gt('0') && amountLamports.gt('0')) {
654
+ const needToAssessReferralFee = referralFeeRate.gt('0') && hasReferrer;
655
+ const minimumFee = new Decimal('1'); // 1 token to market owner, nothing to referrer
656
+
657
+ let borrowFeeAmount: Decimal;
658
+ if (feeCalculation === FeeCalculation.Exclusive) {
659
+ borrowFeeAmount = amountLamports.mul(borrowFeeRate);
660
+ } else {
661
+ const borrowFeeFactor = borrowFeeRate.div(borrowFeeRate.add('1'));
662
+ borrowFeeAmount = amountLamports.mul(borrowFeeFactor);
663
+ }
664
+ const borrowFee = Decimal.max(borrowFeeAmount, minimumFee);
665
+ if (borrowFee.gte(amountLamports)) {
666
+ throw Error('Borrow amount is too small to receive liquidity after fees');
667
+ }
668
+ const referralFee = needToAssessReferralFee
669
+ ? referralFeeRate.eq(1)
670
+ ? borrowFee
671
+ : borrowFee.mul(referralFeeRate).floor()
672
+ : new Decimal(0);
673
+
674
+ const protocolFee = borrowFee.sub(referralFee);
675
+
676
+ return { protocolFees: protocolFee, referrerFees: referralFee };
677
+ } else {
678
+ return { protocolFees: new Decimal(0), referrerFees: new Decimal(0) };
679
+ }
680
+ }
681
+
682
+ calculateFlashLoanFees(flashLoanAmountLamports: Decimal, referralFeeBps: number, hasReferrer: boolean): Fees {
683
+ return this.calculateFees(
684
+ flashLoanAmountLamports,
685
+ this.getFlashLoanFee(),
686
+ FeeCalculation.Exclusive,
687
+ referralFeeBps,
688
+ hasReferrer
689
+ );
690
+ }
691
+
645
692
  setBuffer(buffer: AccountInfo<Buffer> | null) {
646
693
  this.buffer = buffer;
647
694
  }
@@ -935,9 +982,9 @@ export class KaminoReserve {
935
982
  currentValue: Decimal;
936
983
  }[] = market
937
984
  .getMarketElevationGroupDescriptions()
938
- .filter((x) => x.debtReserve === this.address.toString())
985
+ .filter((x) => x.debtReserve.equals(this.address))
939
986
  .map((elevationGroupDescription: ElevationGroupDescription) =>
940
- elevationGroupDescription.collateralReserves.map((collateralReserveAddress) => {
987
+ elevationGroupDescription.collateralReserves.toArray().map((collateralReserveAddress) => {
941
988
  const collRes = market.reserves.get(new PublicKey(collateralReserveAddress))!;
942
989
 
943
990
  const debtLimitAgainstThisCollInGroup =
@@ -1015,14 +1062,15 @@ export class KaminoReserve {
1015
1062
  positiveOrZero(remainingGlobalCap),
1016
1063
  positiveOrZero(liquidityGivenUtilizationCap)
1017
1064
  );
1018
-
1019
1065
  return availableInCrossMode;
1020
1066
  } else {
1021
- const remainingInsideEmodeCaps = Decimal.min(
1022
- ...caps.debtAgainstCollateralReserveCaps
1023
- .filter((x) => x.elevationGroup === elevationGroup)
1024
- .map((x) => x.maxDebt.minus(x.currentValue))
1067
+ let remainingInsideEmodeCaps = new Decimal(0);
1068
+ const capsGivenEgroup = caps.debtAgainstCollateralReserveCaps.filter(
1069
+ (x) => x.elevationGroup === elevationGroup
1025
1070
  );
1071
+ if (capsGivenEgroup.length > 0) {
1072
+ remainingInsideEmodeCaps = Decimal.min(...capsGivenEgroup.map((x) => x.maxDebt.minus(x.currentValue)));
1073
+ }
1026
1074
  return Decimal.min(
1027
1075
  positiveOrZero(liquidityAvailable),
1028
1076
  positiveOrZero(remainingInsideEmodeCaps),
@@ -55,3 +55,13 @@ export type ReserveFarmInfo = {
55
55
  fetched: boolean;
56
56
  farmStates: FarmState[];
57
57
  };
58
+
59
+ export enum FeeCalculation {
60
+ Inclusive = 'Inclusive',
61
+ Exclusive = 'Exclusive',
62
+ }
63
+
64
+ export type Fees = {
65
+ protocolFees: Decimal;
66
+ referrerFees: Decimal;
67
+ };
@@ -185,11 +185,16 @@ export class KaminoVaultClient {
185
185
  const createAtasIxns: TransactionInstruction[] = [];
186
186
  const closeAtasIxns: TransactionInstruction[] = [];
187
187
  if (vaultState.tokenMint.equals(WRAPPED_SOL_MINT)) {
188
- const {
189
- atas: wsolAta,
190
- createAtasIxns: createWsolAtaIxns,
191
- closeAtasIxns: closeWsolAtaIxns,
192
- } = await getAtasWithCreateIxnsIfMissing(this._connection, user, [WRAPPED_SOL_MINT], [TOKEN_PROGRAM_ID]);
188
+ const { atas: wsolAta, createAtaIxs: createWsolAtaIxns } = await getAtasWithCreateIxnsIfMissing(
189
+ this._connection,
190
+ user,
191
+ [
192
+ {
193
+ mint: WRAPPED_SOL_MINT,
194
+ tokenProgram: TOKEN_PROGRAM_ID,
195
+ },
196
+ ]
197
+ );
193
198
  createAtasIxns.push(...createWsolAtaIxns);
194
199
  const depositWsolixn = getDepositWsolIxns(
195
200
  user,
@@ -197,15 +202,14 @@ export class KaminoVaultClient {
197
202
  numberToLamportsDecimal(tokenAmount, vaultState.tokenMintDecimals.toNumber()).ceil()
198
203
  );
199
204
  createAtasIxns.push(...depositWsolixn);
200
- closeAtasIxns.push(...closeWsolAtaIxns);
201
205
  }
202
206
 
203
- const { atas, createAtasIxns: createSharesAtaIxns } = await getAtasWithCreateIxnsIfMissing(
204
- this._connection,
205
- user,
206
- [vaultState.sharesMint],
207
- [TOKEN_PROGRAM_ID]
208
- );
207
+ const { atas, createAtaIxs: createSharesAtaIxns } = await getAtasWithCreateIxnsIfMissing(this._connection, user, [
208
+ {
209
+ mint: vaultState.sharesMint,
210
+ tokenProgram: TOKEN_PROGRAM_ID,
211
+ },
212
+ ]);
209
213
  createAtasIxns.push(...createSharesAtaIxns);
210
214
 
211
215
  const userSharesAta = atas[0];
@@ -256,12 +260,12 @@ export class KaminoVaultClient {
256
260
  const vaultState = await vault.getState(this._connection);
257
261
 
258
262
  const userSharesAta = getAssociatedTokenAddress(vaultState.sharesMint, user);
259
- const { atas, createAtasIxns } = await getAtasWithCreateIxnsIfMissing(
260
- this._connection,
261
- user,
262
- [vaultState.tokenMint],
263
- [TOKEN_PROGRAM_ID]
264
- );
263
+ const { atas, createAtaIxs } = await getAtasWithCreateIxnsIfMissing(this._connection, user, [
264
+ {
265
+ mint: vaultState.tokenMint,
266
+ tokenProgram: TOKEN_PROGRAM_ID,
267
+ },
268
+ ]);
265
269
  const userTokenAta = atas[0];
266
270
 
267
271
  const tokensToWithdraw = shareAmount.div(await this.getTokensPerShareSingleVault(vault, slot));
@@ -329,7 +333,7 @@ export class KaminoVaultClient {
329
333
  })
330
334
  );
331
335
 
332
- return [...createAtasIxns, ...withdrawIxns];
336
+ return [...createAtaIxs, ...withdrawIxns];
333
337
  }
334
338
 
335
339
  /**
@@ -1,7 +1,104 @@
1
1
  import Decimal from 'decimal.js';
2
- import { KaminoMarket, KaminoObligation } from '../classes';
2
+ import { KaminoMarket, KaminoObligation, KaminoReserve, numberToLamportsDecimal } from '../classes';
3
3
  import { PublicKey } from '@solana/web3.js';
4
4
 
5
+ export function calcRepayAmountWithSlippage(
6
+ kaminoMarket: KaminoMarket,
7
+ debtReserve: KaminoReserve,
8
+ currentSlot: number,
9
+ obligation: KaminoObligation,
10
+ amount: Decimal,
11
+ referrer: PublicKey
12
+ ): {
13
+ repayAmount: Decimal;
14
+ repayAmountLamports: Decimal;
15
+ flashRepayAmountLamports: Decimal;
16
+ } {
17
+ const irSlippageBpsForDebt = obligation
18
+ .estimateObligationInterestRate(
19
+ kaminoMarket,
20
+ debtReserve,
21
+ obligation.state.borrows.find((borrow) => borrow.borrowReserve.equals(debtReserve.address))!,
22
+ currentSlot
23
+ )
24
+ .toDecimalPlaces(debtReserve.state.liquidity.mintDecimals.toNumber(), Decimal.ROUND_CEIL);
25
+ // add 0.1% to irSlippageBpsForDebt because we don't want to estimate slightly less than SC and end up not reapying enough
26
+ const repayAmount = amount
27
+ .mul(irSlippageBpsForDebt.mul(new Decimal('1.001')))
28
+ .toDecimalPlaces(debtReserve.state.liquidity.mintDecimals.toNumber(), Decimal.ROUND_CEIL);
29
+ const repayAmountLamports = numberToLamportsDecimal(repayAmount, debtReserve.stats.decimals);
30
+
31
+ const { flashRepayAmountLamports } = calcFlashRepayAmount({
32
+ reserve: debtReserve,
33
+ referralFeeBps: kaminoMarket.state.referralFeeBps,
34
+ hasReferral: !referrer.equals(PublicKey.default),
35
+ flashBorrowAmountLamports: repayAmountLamports,
36
+ });
37
+ return { repayAmount, repayAmountLamports, flashRepayAmountLamports };
38
+ }
39
+
40
+ export const calcFlashRepayAmount = (props: {
41
+ reserve: KaminoReserve;
42
+ referralFeeBps: number;
43
+ hasReferral: boolean;
44
+ flashBorrowAmountLamports: Decimal;
45
+ }): {
46
+ flashRepayAmountLamports: Decimal;
47
+ } => {
48
+ const { reserve, referralFeeBps, hasReferral, flashBorrowAmountLamports } = props;
49
+ const { referrerFees, protocolFees } = reserve.calculateFlashLoanFees(
50
+ flashBorrowAmountLamports,
51
+ referralFeeBps,
52
+ hasReferral
53
+ );
54
+ const flashRepayAmountLamports = flashBorrowAmountLamports.add(referrerFees).add(protocolFees);
55
+
56
+ return {
57
+ flashRepayAmountLamports,
58
+ };
59
+ };
60
+
61
+ export function calcMaxWithdrawCollateral(
62
+ kaminoMarket: KaminoMarket,
63
+ collReserveAddr: PublicKey,
64
+ debtReserveAddr: PublicKey,
65
+ obligation: KaminoObligation,
66
+ repayAmountLamports: Decimal
67
+ ) {
68
+ const collReserve = kaminoMarket.getReserveByAddress(collReserveAddr)!;
69
+ const debtReserve = kaminoMarket.getReserveByAddress(debtReserveAddr)!;
70
+
71
+ const debtOraclePx = debtReserve.getOracleMarketPrice();
72
+ const collOraclePx = collReserve.getOracleMarketPrice();
73
+ const { maxLtv: collMaxLtv } = obligation.getLtvForReserve(kaminoMarket, collReserve);
74
+ const debtBorrowFactor = debtReserve.getBorrowFactor();
75
+
76
+ const debtPosition = obligation.getBorrowByReserve(debtReserve.address)!;
77
+ const collPosition = obligation.getDepositByReserve(collReserve.address)!;
78
+ const initialCollValue = collPosition.amount.floor().div(collReserve.getMintFactor()).mul(collOraclePx);
79
+ const remainingDebtAmountLamports = debtPosition.amount.sub(repayAmountLamports);
80
+ const remainingDebtBfWeightedValue = remainingDebtAmountLamports.ceil().div(debtReserve.getMintFactor()).mul(debtBorrowFactor).mul(debtOraclePx);
81
+
82
+ let isClosingPosition = false;
83
+ if (remainingDebtAmountLamports.lte(new Decimal(0)) && obligation.getBorrows().length === 1) {
84
+ isClosingPosition = true;
85
+ }
86
+ const numerator = initialCollValue.mul(collMaxLtv).sub(remainingDebtBfWeightedValue);
87
+ const denominator = collOraclePx.mul(collMaxLtv);
88
+ const maxCollWithdrawAmount = numerator.div(denominator);
89
+ const maxCollateralWithdrawalAmountLamports = maxCollWithdrawAmount.mul(collReserve.getMintFactor()).floor();
90
+
91
+ let withdrawableCollLamports: Decimal;
92
+ if (isClosingPosition) {
93
+ // 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
94
+ const maxSwapCollLamportsWithBuffer = maxCollateralWithdrawalAmountLamports.mul('1.1');
95
+ withdrawableCollLamports = Decimal.min(maxSwapCollLamportsWithBuffer, collPosition.amount).floor();
96
+ } else {
97
+ withdrawableCollLamports = Decimal.max(new Decimal(0), maxCollateralWithdrawalAmountLamports);
98
+ }
99
+ return { isClosingPosition, withdrawableCollLamports };
100
+ }
101
+
5
102
  export function estimateDebtRepaymentWithColl(props: {
6
103
  collAmount: Decimal; // in decimals
7
104
  priceDebtToColl: Decimal;