@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
@@ -15,6 +15,7 @@ import {
15
15
  NATIVE_MINT,
16
16
  TOKEN_PROGRAM_ID,
17
17
  createCloseAccountInstruction,
18
+ createSyncNativeInstruction,
18
19
  } from '@solana/spl-token';
19
20
  import BN from 'bn.js';
20
21
  import Decimal from 'decimal.js';
@@ -47,7 +48,6 @@ import {
47
48
  buildComputeBudgetIx,
48
49
  createAssociatedTokenAccountIdempotentInstruction,
49
50
  ObligationType,
50
- syncNative,
51
51
  U64_MAX,
52
52
  referrerTokenStatePda,
53
53
  userMetadataPda,
@@ -1043,14 +1043,14 @@ export class KaminoAction {
1043
1043
  kaminoMarket: KaminoMarket,
1044
1044
  currentSlot: number = 0
1045
1045
  ) {
1046
- const { axn, createAtasIxns } = await KaminoAction.initializeWithdrawReferrerFees(
1046
+ const { axn, createAtaIxs } = await KaminoAction.initializeWithdrawReferrerFees(
1047
1047
  tokenMint,
1048
1048
  owner,
1049
1049
  kaminoMarket,
1050
1050
  currentSlot
1051
1051
  );
1052
1052
 
1053
- axn.preTxnIxs.push(...createAtasIxns);
1053
+ axn.preTxnIxs.push(...createAtaIxs);
1054
1054
  axn.preTxnIxsLabels.push(`createAtasIxs[${axn.userTokenAccountAddress.toString()}]`);
1055
1055
 
1056
1056
  axn.addRefreshReserveIxs([axn.reserve.address]);
@@ -2445,70 +2445,57 @@ export class KaminoAction {
2445
2445
  }
2446
2446
 
2447
2447
  if ((action === 'withdraw' || action === 'borrow' || action === 'redeem') && !this.mint.equals(WRAPPED_SOL_MINT)) {
2448
- const userTokenAccountInfo = await this.kaminoMarket.getConnection().getAccountInfo(this.userTokenAccountAddress);
2449
-
2450
- if (!userTokenAccountInfo) {
2451
- const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2452
- this.owner,
2453
- this.reserve.getLiquidityMint(),
2454
- this.owner,
2455
- this.reserve.getLiquidityTokenProgram(),
2456
- this.userTokenAccountAddress
2457
- );
2448
+ const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2449
+ this.owner,
2450
+ this.reserve.getLiquidityMint(),
2451
+ this.owner,
2452
+ this.reserve.getLiquidityTokenProgram(),
2453
+ this.userTokenAccountAddress
2454
+ );
2458
2455
 
2459
- if (this.positions === POSITION_LIMIT) {
2460
- this.preTxnIxs.push(createUserTokenAccountIx);
2461
- this.preTxnIxsLabels.push(`CreateLiquidityUserAta[${this.owner}]`);
2462
- } else {
2463
- this.setupIxs.unshift(createUserTokenAccountIx);
2464
- this.setupIxsLabels.unshift(`CreateLiquidityUserAta[${this.owner}]`);
2465
- }
2456
+ if (this.positions === POSITION_LIMIT) {
2457
+ this.preTxnIxs.push(createUserTokenAccountIx);
2458
+ this.preTxnIxsLabels.push(`CreateLiquidityUserAta[${this.owner}]`);
2459
+ } else {
2460
+ this.setupIxs.unshift(createUserTokenAccountIx);
2461
+ this.setupIxsLabels.unshift(`CreateLiquidityUserAta[${this.owner}]`);
2466
2462
  }
2467
2463
  }
2468
2464
 
2469
2465
  if (action === 'liquidate') {
2470
- const userTokenAccountInfo = await this.kaminoMarket.getConnection().getAccountInfo(this.userTokenAccountAddress);
2471
-
2472
2466
  if (!this.outflowReserve) {
2473
2467
  throw new Error(`Outflow reserve state not found ${this.mint}`);
2474
2468
  }
2475
2469
 
2476
- if (!userTokenAccountInfo) {
2477
- const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2478
- this.owner,
2479
- this.outflowReserve.getLiquidityMint(),
2480
- this.owner,
2481
- this.outflowReserve.getLiquidityTokenProgram(),
2482
- this.userTokenAccountAddress
2483
- );
2484
- if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2485
- this.preTxnIxs.push(createUserTokenAccountIx);
2486
- this.preTxnIxsLabels.push(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2487
- } else {
2488
- this.setupIxs.unshift(createUserTokenAccountIx);
2489
- this.setupIxsLabels.unshift(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2490
- }
2470
+ const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2471
+ this.owner,
2472
+ this.outflowReserve.getLiquidityMint(),
2473
+ this.owner,
2474
+ this.outflowReserve.getLiquidityTokenProgram(),
2475
+ this.userTokenAccountAddress
2476
+ );
2477
+ if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2478
+ this.preTxnIxs.push(createUserTokenAccountIx);
2479
+ this.preTxnIxsLabels.push(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2480
+ } else {
2481
+ this.setupIxs.unshift(createUserTokenAccountIx);
2482
+ this.setupIxsLabels.unshift(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2491
2483
  }
2492
2484
 
2493
- const userCollateralAccountInfo = await this.kaminoMarket
2494
- .getConnection()
2495
- .getAccountInfo(this.userCollateralAccountAddress);
2496
- if (!userCollateralAccountInfo) {
2497
- const [, createUserCollateralAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2498
- this.owner,
2499
- this.outflowReserve.getCTokenMint(),
2500
- this.owner,
2501
- TOKEN_PROGRAM_ID,
2502
- this.userCollateralAccountAddress
2503
- );
2485
+ const [, createUserCollateralAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2486
+ this.owner,
2487
+ this.outflowReserve.getCTokenMint(),
2488
+ this.owner,
2489
+ TOKEN_PROGRAM_ID,
2490
+ this.userCollateralAccountAddress
2491
+ );
2504
2492
 
2505
- if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2506
- this.preTxnIxs.push(createUserCollateralAccountIx);
2507
- this.preTxnIxsLabels.push(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2508
- } else {
2509
- this.setupIxs.unshift(createUserCollateralAccountIx);
2510
- this.setupIxsLabels.unshift(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2511
- }
2493
+ if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2494
+ this.preTxnIxs.push(createUserCollateralAccountIx);
2495
+ this.preTxnIxsLabels.push(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2496
+ } else {
2497
+ this.setupIxs.unshift(createUserCollateralAccountIx);
2498
+ this.setupIxsLabels.unshift(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2512
2499
  }
2513
2500
 
2514
2501
  if (!this.additionalTokenAccountAddress) {
@@ -2547,43 +2534,32 @@ export class KaminoAction {
2547
2534
  }
2548
2535
 
2549
2536
  if (action === 'withdraw' || action === 'mint' || action === 'deposit' || action === 'repayAndWithdraw') {
2550
- const userTokenAccountInfo = await this.kaminoMarket.getConnection().getAccountInfo(this.userTokenAccountAddress);
2551
-
2552
- // TODO: Might need to remove this
2553
- if (!userTokenAccountInfo) {
2554
- const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2555
- this.owner,
2556
- this.reserve.getLiquidityMint(),
2557
- this.owner,
2558
- this.reserve.getLiquidityTokenProgram(),
2559
- this.userTokenAccountAddress
2560
- );
2561
- this.preTxnIxs.push(createUserTokenAccountIx);
2562
- this.preTxnIxsLabels.push(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2563
- }
2537
+ const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2538
+ this.owner,
2539
+ this.reserve.getLiquidityMint(),
2540
+ this.owner,
2541
+ this.reserve.getLiquidityTokenProgram(),
2542
+ this.userTokenAccountAddress
2543
+ );
2544
+ this.preTxnIxs.push(createUserTokenAccountIx);
2545
+ this.preTxnIxsLabels.push(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2564
2546
  }
2565
2547
  if (action === 'mint') {
2566
- const userCollateralAccountInfo = await this.kaminoMarket
2567
- .getConnection()
2568
- .getAccountInfo(this.userCollateralAccountAddress);
2569
-
2570
- if (!userCollateralAccountInfo) {
2571
- const collateralMintPubkey = this.reserve.getCTokenMint();
2572
- const [, createUserCollateralAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2573
- this.owner,
2574
- collateralMintPubkey,
2575
- this.owner,
2576
- TOKEN_PROGRAM_ID,
2577
- this.userCollateralAccountAddress
2578
- );
2548
+ const collateralMintPubkey = this.reserve.getCTokenMint();
2549
+ const [, createUserCollateralAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2550
+ this.owner,
2551
+ collateralMintPubkey,
2552
+ this.owner,
2553
+ TOKEN_PROGRAM_ID,
2554
+ this.userCollateralAccountAddress
2555
+ );
2579
2556
 
2580
- if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2581
- this.preTxnIxs.push(createUserCollateralAccountIx);
2582
- this.preTxnIxsLabels.push(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2583
- } else {
2584
- this.setupIxs.unshift(createUserCollateralAccountIx);
2585
- this.setupIxsLabels.unshift(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2586
- }
2557
+ if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2558
+ this.preTxnIxs.push(createUserCollateralAccountIx);
2559
+ this.preTxnIxsLabels.push(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2560
+ } else {
2561
+ this.setupIxs.unshift(createUserCollateralAccountIx);
2562
+ this.setupIxsLabels.unshift(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2587
2563
  }
2588
2564
  }
2589
2565
  }
@@ -2661,7 +2637,7 @@ export class KaminoAction {
2661
2637
  TOKEN_PROGRAM_ID
2662
2638
  );
2663
2639
 
2664
- const syncIx = syncNative(userTokenAccountAddress);
2640
+ const syncIx = createSyncNativeInstruction(userTokenAccountAddress);
2665
2641
  if (userWSOLAccountInfo) {
2666
2642
  if (sendAction) {
2667
2643
  preIxs.push(syncIx);
@@ -2801,12 +2777,12 @@ export class KaminoAction {
2801
2777
  throw new Error(`Reserve ${mint} not found in market ${kaminoMarket.getAddress().toBase58()}`);
2802
2778
  }
2803
2779
 
2804
- const { atas, createAtasIxns } = await getAtasWithCreateIxnsIfMissing(
2805
- kaminoMarket.getConnection(),
2806
- owner,
2807
- [reserve.getLiquidityMint()],
2808
- [reserve.getLiquidityTokenProgram()]
2809
- );
2780
+ const { atas, createAtaIxs } = await getAtasWithCreateIxnsIfMissing(kaminoMarket.getConnection(), owner, [
2781
+ {
2782
+ mint: reserve.getLiquidityMint(),
2783
+ tokenProgram: reserve.getLiquidityTokenProgram(),
2784
+ },
2785
+ ]);
2810
2786
 
2811
2787
  const userTokenAccountAddress = atas[0];
2812
2788
 
@@ -2830,7 +2806,7 @@ export class KaminoAction {
2830
2806
  undefined,
2831
2807
  undefined
2832
2808
  ),
2833
- createAtasIxns,
2809
+ createAtaIxs,
2834
2810
  };
2835
2811
  }
2836
2812
 
@@ -2867,7 +2843,7 @@ export class KaminoAction {
2867
2843
  owner: PublicKey,
2868
2844
  kaminoObligation: KaminoObligation | null,
2869
2845
  referrer: PublicKey
2870
- ) {
2846
+ ): Promise<PublicKey> {
2871
2847
  let referrerKey = referrer;
2872
2848
  if (!referrer || referrer.equals(PublicKey.default)) {
2873
2849
  if (kaminoObligation === null) {
@@ -2881,4 +2857,22 @@ export class KaminoAction {
2881
2857
  }
2882
2858
  return referrerKey;
2883
2859
  }
2860
+
2861
+ public static actionToIxs(action: KaminoAction): Array<TransactionInstruction> {
2862
+ const ixs: TransactionInstruction[] = [...action.setupIxs];
2863
+ ixs.push(...KaminoAction.actionToLendingIxs(action));
2864
+ ixs.push(...action.cleanupIxs);
2865
+ return ixs;
2866
+ }
2867
+
2868
+ public static actionToLendingIxs(action: KaminoAction): Array<TransactionInstruction> {
2869
+ const ixs: TransactionInstruction[] = [];
2870
+ for (let i = 0; i < action.lendingIxs.length; i++) {
2871
+ ixs.push(action.lendingIxs[i]);
2872
+ if (i !== action.lendingIxs.length - 1) {
2873
+ ixs.push(...action.inBetweenIxs);
2874
+ }
2875
+ }
2876
+ return ixs;
2877
+ }
2884
2878
  }
@@ -20,6 +20,7 @@ import {
20
20
  cacheOrGetSwitchboardPrice,
21
21
  PubkeyHashMap,
22
22
  CandidatePrice,
23
+ PublicKeySet,
23
24
  } from '../utils';
24
25
  import base58 from 'bs58';
25
26
  import { BN } from '@coral-xyz/anchor';
@@ -1208,10 +1209,10 @@ export class KaminoMarket {
1208
1209
  continue;
1209
1210
  }
1210
1211
  elevationGroups.push({
1211
- collateralReserves: [],
1212
- collateralLiquidityMints: [],
1213
- debtReserve: elevationGroup.debtReserve.toString(),
1214
- debtLiquidityMint: '',
1212
+ collateralReserves: new PublicKeySet<PublicKey>([]),
1213
+ collateralLiquidityMints: new PublicKeySet<PublicKey>([]),
1214
+ debtReserve: elevationGroup.debtReserve,
1215
+ debtLiquidityMint: PublicKey.default,
1215
1216
  elevationGroup: elevationGroup.id,
1216
1217
  });
1217
1218
  }
@@ -1219,7 +1220,7 @@ export class KaminoMarket {
1219
1220
  // Fill the remaining
1220
1221
  for (const reserve of this.reserves.values()) {
1221
1222
  const reserveLiquidityMint = reserve.getLiquidityMint();
1222
- const reserveAddress = reserve.address.toString();
1223
+ const reserveAddress = reserve.address;
1223
1224
  const reserveElevationGroups = reserve.state.config.elevationGroups;
1224
1225
  for (const elevationGroupId of reserveElevationGroups) {
1225
1226
  if (elevationGroupId === 0) {
@@ -1228,14 +1229,14 @@ export class KaminoMarket {
1228
1229
 
1229
1230
  const elevationGroupDescription = elevationGroups[elevationGroupId - 1];
1230
1231
  if (elevationGroupDescription) {
1231
- if (reserveAddress === elevationGroupDescription.debtReserve) {
1232
- elevationGroups[elevationGroupId - 1].debtLiquidityMint = reserveLiquidityMint.toString();
1232
+ if (reserveAddress.equals(elevationGroupDescription.debtReserve)) {
1233
+ elevationGroups[elevationGroupId - 1].debtLiquidityMint = reserveLiquidityMint;
1233
1234
  } else {
1234
- elevationGroups[elevationGroupId - 1].collateralReserves.push(reserveAddress);
1235
- elevationGroups[elevationGroupId - 1].collateralLiquidityMints.push(reserveLiquidityMint.toString());
1235
+ elevationGroups[elevationGroupId - 1].collateralReserves.add(reserveAddress);
1236
+ elevationGroups[elevationGroupId - 1].collateralLiquidityMints.add(reserveLiquidityMint);
1236
1237
  }
1237
1238
  } else {
1238
- throw new Error(`Invalid elevation group id ${elevationGroupId} at reserve ${reserveAddress}`);
1239
+ throw new Error(`Invalid elevation group id ${elevationGroupId} at reserve ${reserveAddress.toString()}`);
1239
1240
  }
1240
1241
  }
1241
1242
  }
@@ -1252,10 +1253,8 @@ export class KaminoMarket {
1252
1253
 
1253
1254
  return allElevationGroups.filter((elevationGroupDescription) => {
1254
1255
  return (
1255
- collLiquidityMints.every((mint) =>
1256
- elevationGroupDescription.collateralLiquidityMints.includes(mint.toString())
1257
- ) &&
1258
- (debtLiquidityMint == undefined || debtLiquidityMint.toString() === elevationGroupDescription.debtLiquidityMint)
1256
+ collLiquidityMints.every((mint) => elevationGroupDescription.collateralLiquidityMints.contains(mint)) &&
1257
+ (debtLiquidityMint == undefined || debtLiquidityMint.equals(elevationGroupDescription.debtLiquidityMint))
1259
1258
  );
1260
1259
  });
1261
1260
  }
@@ -1269,8 +1268,8 @@ export class KaminoMarket {
1269
1268
 
1270
1269
  return allElevationGroups.filter((elevationGroupDescription) => {
1271
1270
  return (
1272
- collReserves.every((mint) => elevationGroupDescription.collateralReserves.includes(mint.toString())) &&
1273
- (debtReserve == undefined || debtReserve.toString() === elevationGroupDescription.debtReserve)
1271
+ collReserves.every((mint) => elevationGroupDescription.collateralReserves.contains(mint)) &&
1272
+ (debtReserve == undefined || debtReserve.equals(elevationGroupDescription.debtReserve))
1274
1273
  );
1275
1274
  });
1276
1275
  }
@@ -1305,10 +1304,10 @@ export type BorrowCapsAndCounters = {
1305
1304
  };
1306
1305
 
1307
1306
  export type ElevationGroupDescription = {
1308
- collateralReserves: string[];
1309
- collateralLiquidityMints: string[];
1310
- debtReserve: string;
1311
- debtLiquidityMint: string;
1307
+ collateralReserves: PublicKeySet<PublicKey>;
1308
+ collateralLiquidityMints: PublicKeySet<PublicKey>;
1309
+ debtReserve: PublicKey;
1310
+ debtLiquidityMint: PublicKey;
1312
1311
  elevationGroup: number;
1313
1312
  };
1314
1313
 
@@ -305,6 +305,15 @@ export class KaminoObligation {
305
305
  return this.refreshedStats.netAccountValue;
306
306
  }
307
307
 
308
+ /**
309
+ * Get the loan to value and liquidation loan to value for a collateral token reserve as ratios, accounting for the obligation elevation group if it is active
310
+ * @param market
311
+ * @param reserve
312
+ */
313
+ public getLtvForReserve(market: KaminoMarket, reserve: KaminoReserve): { maxLtv: Decimal; liquidationLtv: Decimal } {
314
+ return KaminoObligation.getLtvForReserve(market, reserve, this.state.elevationGroup);
315
+ }
316
+
308
317
  /**
309
318
  * @returns the potential elevation groups the obligation qualifies for
310
319
  */
@@ -372,19 +381,31 @@ export class KaminoObligation {
372
381
  simulateBorrowChange(
373
382
  obligationBorrows: ObligationLiquidity[],
374
383
  changeInLamports: number,
375
- changeReserve: PublicKey
384
+ changeReserve: PublicKey,
385
+ cumulativeBorrowRate: Decimal
376
386
  ): ObligationLiquidity[] {
377
387
  const newBorrows: ObligationLiquidity[] = [];
378
- for (let i = 0; i < obligationBorrows.length; i++) {
379
- if (obligationBorrows[i].borrowReserve.equals(changeReserve)) {
380
- const borrow: ObligationLiquidityFields = { ...obligationBorrows[i] };
381
- const newBorrowedAmount: Decimal = new Fraction(borrow.borrowedAmountSf).toDecimal().add(changeInLamports);
382
- const newBorrowedAmountSf = Fraction.fromDecimal(positiveOrZero(newBorrowedAmount)).getValue();
383
- borrow.borrowedAmountSf = newBorrowedAmountSf;
384
- newBorrows.push(new ObligationLiquidity(borrow));
385
- } else {
386
- newBorrows.push(obligationBorrows[i]);
387
- }
388
+ const borrowIndex = obligationBorrows.findIndex((borrow) => borrow.borrowReserve.equals(changeReserve));
389
+
390
+ if (borrowIndex !== -1) {
391
+ const borrow: ObligationLiquidityFields = { ...obligationBorrows[borrowIndex] };
392
+ const newBorrowedAmount: Decimal = new Fraction(borrow.borrowedAmountSf).toDecimal().add(changeInLamports);
393
+ const newBorrowedAmountSf = Fraction.fromDecimal(positiveOrZero(newBorrowedAmount)).getValue();
394
+ borrow.borrowedAmountSf = newBorrowedAmountSf;
395
+ newBorrows.push(new ObligationLiquidity(borrow));
396
+ } else {
397
+ const firstBorrowIndexAvailable = obligationBorrows.findIndex((borrow) =>
398
+ borrow.borrowReserve.equals(PublicKey.default)
399
+ );
400
+
401
+ const borrow: ObligationLiquidityFields = { ...obligationBorrows[firstBorrowIndexAvailable] };
402
+ borrow.borrowedAmountSf = Fraction.fromDecimal(new Decimal(changeInLamports)).getValue();
403
+ borrow.borrowReserve = changeReserve;
404
+ borrow.cumulativeBorrowRateBsf = {
405
+ padding: [],
406
+ value: [Fraction.fromDecimal(cumulativeBorrowRate).getValue(), new BN(0), new BN(0), new BN(0)],
407
+ };
408
+ newBorrows.push(new ObligationLiquidity(borrow));
388
409
  }
389
410
 
390
411
  return newBorrows;
@@ -412,11 +433,7 @@ export class KaminoObligation {
412
433
  const { amountCollateral, amountDebt, action, mintCollateral, mintDebt, market } = params;
413
434
  let newStats = { ...this.refreshedStats };
414
435
 
415
- const { collateralExchangeRates, cumulativeBorrowRates } = KaminoObligation.getRatesForObligation(
416
- market,
417
- this.state,
418
- params.slot
419
- );
436
+ const { collateralExchangeRates } = KaminoObligation.getRatesForObligation(market, this.state, params.slot);
420
437
 
421
438
  const elevationGroup = params.elevationGroupOverride ?? this.state.elevationGroup;
422
439
 
@@ -427,8 +444,11 @@ export class KaminoObligation {
427
444
  // so we have to recalculate the entire position, not just an updated deposit or borrow
428
445
  // as both LTVs and borrow factors can change, affecting all calcs
429
446
 
430
- const collateralReserve = mintCollateral ? market.getReserveByMint(mintCollateral)!.address : undefined;
431
- const debtReserve = mintDebt ? market.getReserveByMint(mintDebt)!.address : undefined;
447
+ const collateralReservePk = mintCollateral ? market.getReserveByMint(mintCollateral)!.address : undefined;
448
+ const debtReservePk = mintDebt ? market.getReserveByMint(mintDebt)!.address : undefined;
449
+ const debtReserveCumulativeBorrowRate = mintDebt
450
+ ? market.getReserveByMint(mintDebt)!.getCumulativeBorrowRate()
451
+ : undefined;
432
452
 
433
453
  let newObligationDeposits = this.state.deposits;
434
454
  let newObligationBorrows = this.state.borrows;
@@ -441,7 +461,7 @@ export class KaminoObligation {
441
461
  newObligationDeposits = this.simulateDepositChange(
442
462
  this.state.deposits,
443
463
  amountCollateral.toNumber(),
444
- collateralReserve!,
464
+ collateralReservePk!,
445
465
  collateralExchangeRates
446
466
  );
447
467
  break;
@@ -451,14 +471,25 @@ export class KaminoObligation {
451
471
  throw Error('amountDebt & mintDebt are required for borrow action');
452
472
  }
453
473
 
454
- newObligationBorrows = this.simulateBorrowChange(this.state.borrows, amountDebt.toNumber(), debtReserve!);
474
+ newObligationBorrows = this.simulateBorrowChange(
475
+ this.state.borrows,
476
+ amountDebt.toNumber(),
477
+ debtReservePk!,
478
+ debtReserveCumulativeBorrowRate!
479
+ );
455
480
  break;
456
481
  }
457
482
  case 'repay': {
458
483
  if (amountDebt === undefined || mintDebt === undefined) {
459
484
  throw Error('amountDebt & mintDebt are required for repay action');
460
485
  }
461
- newObligationBorrows = this.simulateBorrowChange(this.state.borrows, amountDebt.neg().toNumber(), debtReserve!);
486
+
487
+ newObligationBorrows = this.simulateBorrowChange(
488
+ this.state.borrows,
489
+ amountDebt.neg().toNumber(),
490
+ debtReservePk!,
491
+ debtReserveCumulativeBorrowRate!
492
+ );
462
493
 
463
494
  break;
464
495
  }
@@ -470,7 +501,7 @@ export class KaminoObligation {
470
501
  newObligationDeposits = this.simulateDepositChange(
471
502
  this.state.deposits,
472
503
  amountCollateral.neg().toNumber(),
473
- collateralReserve!,
504
+ collateralReservePk!,
474
505
  collateralExchangeRates
475
506
  );
476
507
  break;
@@ -487,10 +518,16 @@ export class KaminoObligation {
487
518
  newObligationDeposits = this.simulateDepositChange(
488
519
  this.state.deposits,
489
520
  amountCollateral.toNumber(),
490
- collateralReserve!,
521
+ collateralReservePk!,
491
522
  collateralExchangeRates
492
523
  );
493
- newObligationBorrows = this.simulateBorrowChange(this.state.borrows, amountDebt.toNumber(), debtReserve!);
524
+
525
+ newObligationBorrows = this.simulateBorrowChange(
526
+ this.state.borrows,
527
+ amountDebt.toNumber(),
528
+ debtReservePk!,
529
+ debtReserveCumulativeBorrowRate!
530
+ );
494
531
  break;
495
532
  }
496
533
  case 'repayAndWithdraw': {
@@ -505,10 +542,15 @@ export class KaminoObligation {
505
542
  newObligationDeposits = this.simulateDepositChange(
506
543
  this.state.deposits,
507
544
  amountCollateral.neg().toNumber(),
508
- collateralReserve!,
545
+ collateralReservePk!,
509
546
  collateralExchangeRates
510
547
  );
511
- newObligationBorrows = this.simulateBorrowChange(this.state.borrows, amountDebt.neg().toNumber(), debtReserve!);
548
+ newObligationBorrows = this.simulateBorrowChange(
549
+ this.state.borrows,
550
+ amountDebt.neg().toNumber(),
551
+ debtReservePk!,
552
+ debtReserveCumulativeBorrowRate!
553
+ );
512
554
  break;
513
555
  }
514
556
  default: {
@@ -522,7 +564,7 @@ export class KaminoObligation {
522
564
  newObligationBorrows,
523
565
  elevationGroup,
524
566
  collateralExchangeRates,
525
- cumulativeBorrowRates
567
+ null
526
568
  );
527
569
 
528
570
  newStats = refreshedStats;
@@ -568,7 +610,7 @@ export class KaminoObligation {
568
610
  obligationBorrows: ObligationLiquidity[],
569
611
  elevationGroup: number,
570
612
  collateralExchangeRates: Map<PublicKey, Decimal>,
571
- cumulativeBorrowRates: Map<PublicKey, Decimal>
613
+ cumulativeBorrowRates: Map<PublicKey, Decimal> | null
572
614
  ): {
573
615
  borrows: Map<PublicKey, Position>;
574
616
  deposits: Map<PublicKey, Position>;
@@ -774,12 +816,12 @@ export class KaminoObligation {
774
816
  return borrowLimit.div(userTotalDeposit);
775
817
  }
776
818
 
777
- /*
778
- How much of a given token can a user borrow extra given an elevation group,
819
+ /*
820
+ How much of a given token can a user borrow extra given an elevation group,
779
821
  regardless of caps and liquidity or assuming infinite liquidity and infinite caps,
780
822
  until it hits max LTV.
781
823
 
782
- This is purely a function about the borrow power of an obligation,
824
+ This is purely a function about the borrow power of an obligation,
783
825
  not a reserve-specific, caps-specific, liquidity-specific function.
784
826
 
785
827
  * @param market - The KaminoMarket instance.
@@ -857,7 +899,7 @@ export class KaminoObligation {
857
899
  return Decimal.max(new Decimal(0), maxBorrowAmount);
858
900
  }
859
901
 
860
- /*
902
+ /*
861
903
  How much of a given token can a user borrow extra given an elevation group,
862
904
  and a specific reserve, until it hits max LTV and given available liquidity and caps.
863
905
 
@@ -891,7 +933,7 @@ export class KaminoObligation {
891
933
  }
892
934
  }
893
935
 
894
- /*
936
+ /*
895
937
  Returns true if the loan is eligible for the elevation group, including for the default one.
896
938
  * @param market - The KaminoMarket object representing the market.
897
939
  * @param slot - The slot number of the loan.
@@ -904,8 +946,8 @@ export class KaminoObligation {
904
946
  // - [x] due to collateral / debt reserves combination
905
947
  // - [x] due to LTV, etc
906
948
 
907
- const reserveDeposits: string[] = Array.from(this.deposits.keys()).map((x) => x.toString());
908
- const reserveBorrows: string[] = Array.from(this.borrows.keys()).map((x) => x.toString());
949
+ const reserveDeposits: PublicKey[] = Array.from(this.deposits.keys());
950
+ const reserveBorrows: PublicKey[] = Array.from(this.borrows.keys());
909
951
 
910
952
  if (reserveBorrows.length > 1) {
911
953
  return false;
@@ -918,11 +960,11 @@ export class KaminoObligation {
918
960
 
919
961
  // Has to be a subset
920
962
  const allCollsIncluded = reserveDeposits.every((reserve) =>
921
- elevationGroupDescription.collateralReserves.includes(reserve)
963
+ elevationGroupDescription.collateralReserves.contains(reserve)
922
964
  );
923
965
  const allDebtsIncluded =
924
966
  reserveBorrows.length === 0 ||
925
- (reserveBorrows.length === 1 && elevationGroupDescription.debtReserve === reserveBorrows[0]);
967
+ (reserveBorrows.length === 1 && elevationGroupDescription.debtReserve.equals(reserveBorrows[0]));
926
968
 
927
969
  if (!allCollsIncluded || !allDebtsIncluded) {
928
970
  return false;
@@ -946,7 +988,7 @@ export class KaminoObligation {
946
988
  return isEligibleBasedOnLtv;
947
989
  }
948
990
 
949
- /*
991
+ /*
950
992
  Returns all elevation groups for a given obligation, except the default one
951
993
  * @param market - The KaminoMarket instance.
952
994
  * @returns An array of ElevationGroupDescription objects representing the elevation groups for the obligation.