@kamino-finance/klend-sdk 6.0.5-beta.20 → 6.0.5-beta.22

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.
@@ -39,6 +39,11 @@ export type Position = {
39
39
  marketValueRefreshed: Decimal;
40
40
  };
41
41
 
42
+ export type PositionChange = {
43
+ reserveAddress: PublicKey;
44
+ amountChangeLamports: Decimal;
45
+ };
46
+
42
47
  export type ObligationStats = {
43
48
  userTotalDeposit: Decimal;
44
49
  userTotalCollateralDeposit: Decimal;
@@ -491,19 +496,20 @@ export class KaminoObligation {
491
496
 
492
497
  simulateDepositChange(
493
498
  obligationDeposits: ObligationCollateral[],
494
- changeInLamports: number,
495
- changeReserve: PublicKey,
499
+ depositChange: PositionChange,
496
500
  collateralExchangeRates: Map<PublicKey, Decimal>
497
501
  ): ObligationCollateral[] {
498
502
  const newDeposits: ObligationCollateral[] = [];
499
- const depositIndex = obligationDeposits.findIndex((deposit) => deposit.depositReserve.equals(changeReserve));
503
+ const depositIndex = obligationDeposits.findIndex((deposit) =>
504
+ deposit.depositReserve.equals(depositChange.reserveAddress)
505
+ );
500
506
 
501
507
  // Always copy the previous deposits and modify the changeReserve one if it exists
502
508
  for (let i = 0; i < obligationDeposits.length; i++) {
503
- if (obligationDeposits[i].depositReserve.equals(changeReserve)) {
509
+ if (obligationDeposits[i].depositReserve.equals(depositChange.reserveAddress)) {
504
510
  const coll: ObligationCollateralFields = { ...obligationDeposits[i] };
505
- const exchangeRate = collateralExchangeRates.get(changeReserve)!;
506
- const changeInCollateral = new Decimal(changeInLamports).mul(exchangeRate).toFixed(0);
511
+ const exchangeRate = collateralExchangeRates.get(depositChange.reserveAddress)!;
512
+ const changeInCollateral = new Decimal(depositChange.amountChangeLamports).mul(exchangeRate).toFixed(0);
507
513
  const updatedDeposit = new Decimal(obligationDeposits[i].depositedAmount.toNumber()).add(changeInCollateral);
508
514
  coll.depositedAmount = new BN(positiveOrZero(updatedDeposit).toString());
509
515
  newDeposits.push(new ObligationCollateral(coll));
@@ -523,10 +529,10 @@ export class KaminoObligation {
523
529
  }
524
530
 
525
531
  const coll: ObligationCollateralFields = { ...obligationDeposits[firstBorrowIndexAvailable] };
526
- const exchangeRate = collateralExchangeRates.get(changeReserve)!;
527
- const changeInCollateral = new Decimal(changeInLamports).mul(exchangeRate).toFixed(0);
532
+ const exchangeRate = collateralExchangeRates.get(depositChange.reserveAddress)!;
533
+ const changeInCollateral = new Decimal(depositChange.amountChangeLamports).mul(exchangeRate).toFixed(0);
528
534
  coll.depositedAmount = new BN(positiveOrZero(new Decimal(changeInCollateral)).toString());
529
- coll.depositReserve = changeReserve;
535
+ coll.depositReserve = depositChange.reserveAddress;
530
536
 
531
537
  newDeposits[firstBorrowIndexAvailable] = new ObligationCollateral(coll);
532
538
  }
@@ -536,18 +542,21 @@ export class KaminoObligation {
536
542
 
537
543
  simulateBorrowChange(
538
544
  obligationBorrows: ObligationLiquidity[],
539
- changeInLamports: number,
540
- changeReserve: PublicKey,
545
+ borrowChange: PositionChange,
541
546
  cumulativeBorrowRate: Decimal
542
547
  ): ObligationLiquidity[] {
543
548
  const newBorrows: ObligationLiquidity[] = [];
544
- const borrowIndex = obligationBorrows.findIndex((borrow) => borrow.borrowReserve.equals(changeReserve));
549
+ const borrowIndex = obligationBorrows.findIndex((borrow) =>
550
+ borrow.borrowReserve.equals(borrowChange.reserveAddress)
551
+ );
545
552
 
546
553
  // Always copy the previous borrows and modify the changeReserve one if it exists
547
554
  for (let i = 0; i < obligationBorrows.length; i++) {
548
- if (obligationBorrows[i].borrowReserve.equals(changeReserve)) {
555
+ if (obligationBorrows[i].borrowReserve.equals(borrowChange.reserveAddress)) {
549
556
  const borrow: ObligationLiquidityFields = { ...obligationBorrows[borrowIndex] };
550
- const newBorrowedAmount: Decimal = new Fraction(borrow.borrowedAmountSf).toDecimal().add(changeInLamports);
557
+ const newBorrowedAmount: Decimal = new Fraction(borrow.borrowedAmountSf)
558
+ .toDecimal()
559
+ .add(borrowChange.amountChangeLamports);
551
560
  const newBorrowedAmountSf = Fraction.fromDecimal(positiveOrZero(newBorrowedAmount)).getValue();
552
561
  borrow.borrowedAmountSf = newBorrowedAmountSf;
553
562
 
@@ -568,8 +577,8 @@ export class KaminoObligation {
568
577
  }
569
578
 
570
579
  const borrow: ObligationLiquidityFields = { ...obligationBorrows[firstBorrowIndexAvailable] };
571
- borrow.borrowedAmountSf = Fraction.fromDecimal(new Decimal(changeInLamports)).getValue();
572
- borrow.borrowReserve = changeReserve;
580
+ borrow.borrowedAmountSf = Fraction.fromDecimal(new Decimal(borrowChange.amountChangeLamports)).getValue();
581
+ borrow.borrowReserve = borrowChange.reserveAddress;
573
582
  borrow.cumulativeBorrowRateBsf = {
574
583
  padding: [],
575
584
  value: [Fraction.fromDecimal(cumulativeBorrowRate).getValue(), new BN(0), new BN(0), new BN(0)],
@@ -643,8 +652,10 @@ export class KaminoObligation {
643
652
  }
644
653
  newObligationDeposits = this.simulateDepositChange(
645
654
  this.state.deposits,
646
- amountCollateral.toNumber(),
647
- collateralReservePk!,
655
+ {
656
+ reserveAddress: collateralReservePk!,
657
+ amountChangeLamports: amountCollateral,
658
+ },
648
659
  collateralExchangeRates
649
660
  );
650
661
  break;
@@ -656,8 +667,10 @@ export class KaminoObligation {
656
667
 
657
668
  newObligationBorrows = this.simulateBorrowChange(
658
669
  this.state.borrows,
659
- amountDebt.toNumber(),
660
- debtReservePk!,
670
+ {
671
+ reserveAddress: debtReservePk!,
672
+ amountChangeLamports: amountDebt,
673
+ },
661
674
  debtReserveCumulativeBorrowRate!
662
675
  );
663
676
  break;
@@ -669,8 +682,10 @@ export class KaminoObligation {
669
682
 
670
683
  newObligationBorrows = this.simulateBorrowChange(
671
684
  this.state.borrows,
672
- amountDebt.neg().toNumber(),
673
- debtReservePk!,
685
+ {
686
+ reserveAddress: debtReservePk!,
687
+ amountChangeLamports: amountDebt.neg(),
688
+ },
674
689
  debtReserveCumulativeBorrowRate!
675
690
  );
676
691
 
@@ -683,8 +698,10 @@ export class KaminoObligation {
683
698
  }
684
699
  newObligationDeposits = this.simulateDepositChange(
685
700
  this.state.deposits,
686
- amountCollateral.neg().toNumber(),
687
- collateralReservePk!,
701
+ {
702
+ reserveAddress: collateralReservePk!,
703
+ amountChangeLamports: amountCollateral.neg(),
704
+ },
688
705
  collateralExchangeRates
689
706
  );
690
707
  break;
@@ -700,15 +717,19 @@ export class KaminoObligation {
700
717
  }
701
718
  newObligationDeposits = this.simulateDepositChange(
702
719
  this.state.deposits,
703
- amountCollateral.toNumber(),
704
- collateralReservePk!,
720
+ {
721
+ reserveAddress: collateralReservePk!,
722
+ amountChangeLamports: amountCollateral,
723
+ },
705
724
  collateralExchangeRates
706
725
  );
707
726
 
708
727
  newObligationBorrows = this.simulateBorrowChange(
709
728
  this.state.borrows,
710
- amountDebt.toNumber(),
711
- debtReservePk!,
729
+ {
730
+ reserveAddress: debtReservePk!,
731
+ amountChangeLamports: amountDebt,
732
+ },
712
733
  debtReserveCumulativeBorrowRate!
713
734
  );
714
735
  break;
@@ -724,14 +745,18 @@ export class KaminoObligation {
724
745
  }
725
746
  newObligationDeposits = this.simulateDepositChange(
726
747
  this.state.deposits,
727
- amountCollateral.neg().toNumber(),
728
- collateralReservePk!,
748
+ {
749
+ reserveAddress: collateralReservePk!,
750
+ amountChangeLamports: amountCollateral.neg(),
751
+ },
729
752
  collateralExchangeRates
730
753
  );
731
754
  newObligationBorrows = this.simulateBorrowChange(
732
755
  this.state.borrows,
733
- amountDebt.neg().toNumber(),
734
- debtReservePk!,
756
+ {
757
+ reserveAddress: debtReservePk!,
758
+ amountChangeLamports: amountDebt.neg(),
759
+ },
735
760
  debtReserveCumulativeBorrowRate!
736
761
  );
737
762
  break;
@@ -803,14 +828,18 @@ export class KaminoObligation {
803
828
  let newObligationDeposits = this.state.deposits;
804
829
  newObligationDeposits = this.simulateDepositChange(
805
830
  newObligationDeposits,
806
- withdrawAmountLamports.neg().toNumber(),
807
- withdrawReserveAddress,
831
+ {
832
+ reserveAddress: withdrawReserveAddress,
833
+ amountChangeLamports: withdrawAmountLamports.neg(),
834
+ },
808
835
  collateralExchangeRates
809
836
  );
810
837
  newObligationDeposits = this.simulateDepositChange(
811
838
  newObligationDeposits,
812
- depositAmountLamports.toNumber(),
813
- depositReserveAddress,
839
+ {
840
+ reserveAddress: depositReserveAddress,
841
+ amountChangeLamports: depositAmountLamports,
842
+ },
814
843
  collateralExchangeRates
815
844
  );
816
845
 
@@ -947,6 +976,7 @@ export class KaminoObligation {
947
976
  } else {
948
977
  exchangeRate = reserve.getCollateralExchangeRate();
949
978
  }
979
+
950
980
  const supplyAmount = new Decimal(deposit.depositedAmount.toString()).div(exchangeRate);
951
981
 
952
982
  const depositValueUsd = supplyAmount.mul(getPx(reserve)).div(reserve.getMintFactor());
@@ -1074,6 +1104,73 @@ export class KaminoObligation {
1074
1104
  return borrowLimit.div(userTotalCollateralDeposit);
1075
1105
  }
1076
1106
 
1107
+ /**
1108
+ * Creates a new KaminoObligation with simulated position changes applied.
1109
+ * This allows you to model what the obligation would look like with deposits/borrows
1110
+ * without actually executing those transactions.
1111
+ *
1112
+ * @param market - The KaminoMarket instance
1113
+ * @param slot - The slot number for rate calculations
1114
+ * @param depositChanges - Optional array of deposit changes to apply
1115
+ * @param borrowChanges - Optional array of borrow changes to apply
1116
+ * @returns A new KaminoObligation instance with the changes applied
1117
+ */
1118
+ withPositionChanges(
1119
+ market: KaminoMarket,
1120
+ slot: number,
1121
+ depositChanges?: PositionChange[],
1122
+ borrowChanges?: PositionChange[]
1123
+ ): KaminoObligation {
1124
+ const reservesToRefresh: PublicKey[] = [];
1125
+
1126
+ if (depositChanges) {
1127
+ reservesToRefresh.push(...depositChanges.map((change) => change.reserveAddress));
1128
+ }
1129
+ if (borrowChanges) {
1130
+ reservesToRefresh.push(...borrowChanges.map((change) => change.reserveAddress));
1131
+ }
1132
+
1133
+ const { collateralExchangeRates, cumulativeBorrowRates } = KaminoObligation.getRatesForObligation(
1134
+ market,
1135
+ this.state,
1136
+ slot,
1137
+ reservesToRefresh
1138
+ );
1139
+
1140
+ let newDeposits: ObligationCollateral[] = this.state.deposits;
1141
+ if (depositChanges) {
1142
+ for (const depositChange of depositChanges) {
1143
+ newDeposits = this.simulateDepositChange(newDeposits, depositChange, collateralExchangeRates);
1144
+ }
1145
+ }
1146
+
1147
+ let newBorrows: ObligationLiquidity[] = this.state.borrows;
1148
+ if (borrowChanges) {
1149
+ for (const borrowChange of borrowChanges) {
1150
+ const reserve = market.getReserveByAddress(borrowChange.reserveAddress);
1151
+ if (!reserve) {
1152
+ throw new Error(`Reserve not found: ${borrowChange.reserveAddress}`);
1153
+ }
1154
+ newBorrows = this.simulateBorrowChange(newBorrows, borrowChange, reserve.getCumulativeBorrowRate());
1155
+ }
1156
+ }
1157
+
1158
+ // Create a deep copy of the obligation state and override deposits/borrows
1159
+ const newObligationState = new Obligation({
1160
+ ...this.state,
1161
+ deposits: newDeposits,
1162
+ borrows: newBorrows,
1163
+ });
1164
+
1165
+ return new KaminoObligation(
1166
+ market,
1167
+ this.obligationAddress,
1168
+ newObligationState,
1169
+ collateralExchangeRates,
1170
+ cumulativeBorrowRates
1171
+ );
1172
+ }
1173
+
1077
1174
  /*
1078
1175
  How much of a given token can a user borrow extra given an elevation group,
1079
1176
  regardless of caps and liquidity or assuming infinite liquidity and infinite caps,
@@ -1196,6 +1293,36 @@ export class KaminoObligation {
1196
1293
  }
1197
1294
  }
1198
1295
 
1296
+ /*
1297
+ Same as getMaxBorrowAmountV2 but assumes a deposit is made first, calculating
1298
+ the new borrow power after the deposit, without overriding the obligation itself.
1299
+
1300
+ * @param market - The KaminoMarket instance.
1301
+ * @param liquidityMint - The liquidity mint Address.
1302
+ * @param slot - The slot number.
1303
+ * @param elevationGroup - The elevation group number (default: this.state.elevationGroup).
1304
+ * @returns The maximum borrow amount as a Decimal.
1305
+ * @throws Error if the reserve is not found.
1306
+ */
1307
+ getMaxBorrowAmountV2WithDeposit(
1308
+ market: KaminoMarket,
1309
+ liquidityMint: PublicKey,
1310
+ slot: number,
1311
+ elevationGroup: number = this.state.elevationGroup,
1312
+ depositAmountLamports: Decimal,
1313
+ depositReserveAddress: PublicKey
1314
+ ): Decimal {
1315
+ const depositChanges = [
1316
+ {
1317
+ reserveAddress: depositReserveAddress,
1318
+ amountChangeLamports: depositAmountLamports,
1319
+ },
1320
+ ];
1321
+ const obligationWithDeposit = this.withPositionChanges(market, slot, depositChanges);
1322
+
1323
+ return obligationWithDeposit.getMaxBorrowAmountV2(market, liquidityMint, slot, elevationGroup);
1324
+ }
1325
+
1199
1326
  /*
1200
1327
  Returns true if the loan is eligible for the elevation group, including for the default one.
1201
1328
  * @param market - The KaminoMarket object representing the market.
@@ -1447,6 +1574,44 @@ export class KaminoObligation {
1447
1574
  );
1448
1575
  }
1449
1576
 
1577
+ /**
1578
+ * Same as getMaxWithdrawAmount but assumes a repay is made first, calculating
1579
+ * the new withdraw power after the repay, without overriding the obligation itself.
1580
+ *
1581
+ * @param market - The KaminoMarket instance.
1582
+ * @param tokenMint - The liquidity mint Address.
1583
+ * @param slot - The slot number.
1584
+ * @param repayAmountLamports - The amount to repay in lamports (use U64_MAX for full repay).
1585
+ * @param repayReserveAddress - The reserve address of the borrow being repaid.
1586
+ * @returns The maximum withdraw amount as a Decimal.
1587
+ * @throws Error if the reserve is not found.
1588
+ */
1589
+ getMaxWithdrawAmountWithRepay(
1590
+ market: KaminoMarket,
1591
+ tokenMint: PublicKey,
1592
+ slot: number,
1593
+ repayAmountLamports: Decimal,
1594
+ repayReserveAddress: PublicKey
1595
+ ): Decimal {
1596
+ const repayReserve = market.getReserveByAddress(repayReserveAddress);
1597
+ if (!repayReserve) {
1598
+ throw new Error('Reserve not found');
1599
+ }
1600
+
1601
+ const repayAmount = repayAmountLamports.equals(U64_MAX)
1602
+ ? this.getBorrowAmountByReserve(repayReserve)
1603
+ : repayAmountLamports;
1604
+ const borrowChanges = [
1605
+ {
1606
+ reserveAddress: repayReserveAddress,
1607
+ amountChangeLamports: repayAmount.neg(), // as it's a repay
1608
+ },
1609
+ ];
1610
+ const obligationWithRepay = this.withPositionChanges(market, slot, undefined, borrowChanges);
1611
+
1612
+ return obligationWithRepay.getMaxWithdrawAmount(market, tokenMint, slot);
1613
+ }
1614
+
1450
1615
  getObligationLiquidityByReserve(reserveAddress: PublicKey): ObligationLiquidity {
1451
1616
  const obligationLiquidity = this.state.borrows.find((borrow) => borrow.borrowReserve.equals(reserveAddress));
1452
1617
 
@@ -40,6 +40,7 @@ import {
40
40
  import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
41
41
  import { aprToApy, KaminoPrices } from '@kamino-finance/kliquidity-sdk';
42
42
  import { FarmState, RewardInfo } from '@kamino-finance/farms-sdk';
43
+ import { Scope, ScopeEntryMetadata } from '@kamino-finance/scope-sdk';
43
44
 
44
45
  export const DEFAULT_RECENT_SLOT_DURATION_MS = 450;
45
46
 
@@ -56,6 +57,8 @@ export class KaminoReserve {
56
57
  private connection: Connection;
57
58
  private readonly recentSlotDurationMs: number;
58
59
 
60
+ private metadata?: ScopeEntryMetadata[];
61
+
59
62
  constructor(
60
63
  state: Reserve,
61
64
  address: PublicKey,
@@ -96,6 +99,19 @@ export class KaminoReserve {
96
99
  return parseTokenSymbol(this.state.config.tokenInfo.name);
97
100
  }
98
101
 
102
+ /**
103
+ * @returns list of logo names and human readable oracle type descriptions
104
+ */
105
+ async getOracleMetadata(): Promise<[string, string][]> {
106
+ if (this.metadata === undefined) {
107
+ const scope = new Scope('mainnet-beta', this.connection);
108
+ const { priceFeed, priceChain } = this.state.config.tokenInfo.scopeConfiguration;
109
+ this.metadata = await scope.getChainMetadata({ prices: priceFeed }, priceChain);
110
+ }
111
+
112
+ return (this.metadata || []).map((chainMetadata) => [chainMetadata.provider, chainMetadata.name]);
113
+ }
114
+
99
115
  /**
100
116
  * @returns the total borrowed amount of the reserve in lamports
101
117
  */