@kamino-finance/klend-sdk 7.4.0-beta.3 → 7.4.0-beta.4

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.
@@ -40,8 +40,10 @@ import {
40
40
  MultiplyObligation,
41
41
  MultiplyObligationFixedRate,
42
42
  ObligationType,
43
+ FloatRateReserveKind,
43
44
  PythPrices,
44
45
  referrerTokenStatePda,
46
+ ReserveKind,
45
47
  setOrAppend,
46
48
  userMetadataPda,
47
49
  VanillaObligation,
@@ -571,6 +573,12 @@ export class KaminoMarket {
571
573
  return checkDefined(this.getReserveByAddress(address), `${description} reserve ${address} not found`);
572
574
  }
573
575
 
576
+ /**
577
+ * Returns all reserves for the given mint address (both float rate and fixed rate).
578
+ *
579
+ * @param mint The liquidity mint address
580
+ * @returns Array of all reserves for this mint
581
+ */
574
582
  getReservesByMint(address: Address): KaminoReserve[] {
575
583
  const reserves: KaminoReserve[] = [];
576
584
  for (const reserve of this.reserves.values()) {
@@ -581,14 +589,97 @@ export class KaminoMarket {
581
589
  return reserves;
582
590
  }
583
591
 
584
- /**
585
- * Returns this market's reserve of the given mint address, or throws an error (including the given description) if
586
- * such reserve does not exist.
587
- */
588
592
  getExistingReservesByMint(address: Address, description: string = 'Requested'): KaminoReserve[] {
589
593
  return checkArrayNotEmpty(this.getReservesByMint(address), `${description} reserve with mint ${address} not found`);
590
594
  }
591
595
 
596
+ /**
597
+ * Returns this market's reserve matching the given mint address and reserve kind.
598
+ * Since a market can have multiple reserves for the same mint (with different terms),
599
+ * the reserve kind specifies which reserve to select.
600
+ *
601
+ * Example for fixed-term reserves:
602
+ * const fixedReserveKind = new FixedReserveKind(new BN(30 * 24 * 60 * 60), 500); // 30 days, 5% borrow rate
603
+ * const fixedReserve = market.getReserveByMintAndKind(tokenMint, fixedReserveKind)!;
604
+
605
+ * @param mint The liquidity mint address
606
+ * @param reserveKind The reserve kind to match (e.g., FloatRateReserveKind or FixedRateReserveKind)
607
+ */
608
+ getReserveByMintAndKind(mint: Address, reserveKind: ReserveKind): KaminoReserve | undefined {
609
+ for (const reserve of this.reserves.values()) {
610
+ if (reserve.getLiquidityMint() === mint && reserveKind.matches(reserve)) {
611
+ return reserve;
612
+ }
613
+ }
614
+ return undefined;
615
+ }
616
+
617
+ /**
618
+ * Returns this market's reserve matching the given mint address and reserve kind,
619
+ * or throws an error if not found.
620
+ *
621
+ * @param mint The liquidity mint address
622
+ * @param reserveKind The reserve kind to match
623
+ * @param description Optional description for the error message
624
+ */
625
+ getExistingReserveByMintAndKind(
626
+ mint: Address,
627
+ reserveKind: ReserveKind,
628
+ description: string = 'Requested'
629
+ ): KaminoReserve {
630
+ return checkDefined(
631
+ this.getReserveByMintAndKind(mint, reserveKind),
632
+ `${description} reserve with mint ${mint} and kind ${reserveKind.toString()} not found`
633
+ );
634
+ }
635
+
636
+ /**
637
+ * Returns this market's float rate reserve for the given mint address.
638
+ * Float rate reserves are reserves without a fixed term (debtTermSeconds = 0).
639
+ *
640
+ * @param mint The liquidity mint address
641
+ * @returns The float rate reserve, or undefined if not found
642
+ */
643
+ getFloatRateReserveByMint(mint: Address): KaminoReserve | undefined {
644
+ return this.getReserveByMintAndKind(mint, new FloatRateReserveKind());
645
+ }
646
+
647
+ /**
648
+ * Returns this market's float rate reserve for the given mint address,
649
+ * or throws an error if not found.
650
+ *
651
+ * @param mint The liquidity mint address
652
+ * @param description Optional description for the error message
653
+ * @returns The float rate reserve
654
+ */
655
+ getExistingFloatRateReserveByMint(mint: Address, description: string = 'Requested'): KaminoReserve {
656
+ return checkDefined(this.getFloatRateReserveByMint(mint), `${description} float rate reserve with mint ${mint} not found`);
657
+ }
658
+
659
+ /**
660
+ * Returns all fixed rate reserves for the given mint address.
661
+ * Fixed rate reserves have a non-zero debt term (debtTermSeconds > 0).
662
+ *
663
+ * @param mint The liquidity mint address
664
+ * @returns Array of all fixed rate reserves for this mint
665
+ */
666
+ getFixedRateReservesByMint(mint: Address): KaminoReserve[] {
667
+ const fixedRateReserves: KaminoReserve[] = [];
668
+ const floatRateReserveKind = new FloatRateReserveKind();
669
+ for (const reserve of this.reserves.values()) {
670
+ if (reserve.getLiquidityMint() === mint && !floatRateReserveKind.matches(reserve)) {
671
+ fixedRateReserves.push(reserve);
672
+ }
673
+ }
674
+ return fixedRateReserves;
675
+ }
676
+
677
+ /**
678
+ * Returns all reserves for the given symbol (both float rate and fixed rate).
679
+ *
680
+ * @param symbol The reserve symbol
681
+ * @returns Array of all reserves for this symbol
682
+ */
592
683
  getReservesBySymbol(symbol: string): KaminoReserve[] {
593
684
  const reserves: KaminoReserve[] = [];
594
685
  for (const reserve of this.reserves.values()) {
@@ -599,10 +690,6 @@ export class KaminoMarket {
599
690
  return reserves;
600
691
  }
601
692
 
602
- /**
603
- * Returns this market's reserve of the given symbol, or throws an error (including the given description) if
604
- * such reserve does not exist.
605
- */
606
693
  getExistingReservesBySymbol(symbol: string, description: string = 'Requested'): KaminoReserve[] {
607
694
  return checkArrayNotEmpty(
608
695
  this.getReservesBySymbol(symbol),
@@ -610,6 +697,94 @@ export class KaminoMarket {
610
697
  );
611
698
  }
612
699
 
700
+ /**
701
+ * Returns this market's reserve matching the given symbol and reserve kind.
702
+ * Since a market can have multiple reserves for the same symbol (with different terms),
703
+ * the reserve kind specifies which reserve to select.
704
+ *
705
+ * @param symbol The reserve symbol
706
+ * @param reserveKind The reserve kind to match (e.g., FloatRateReserveKind or FixedRateReserveKind)
707
+ */
708
+ getReserveBySymbolAndKind(symbol: string, reserveKind: ReserveKind): KaminoReserve | undefined {
709
+ for (const reserve of this.reserves.values()) {
710
+ if (reserve.symbol === symbol && reserveKind.matches(reserve)) {
711
+ return reserve;
712
+ }
713
+ }
714
+ return undefined;
715
+ }
716
+
717
+ /**
718
+ * Returns this market's reserve matching the given symbol and reserve kind,
719
+ * or throws an error if not found.
720
+ *
721
+ * @param symbol The reserve symbol
722
+ * @param reserveKind The reserve kind to match
723
+ * @param description Optional description for the error message
724
+ */
725
+ getExistingReserveBySymbolAndKind(
726
+ symbol: string,
727
+ reserveKind: ReserveKind,
728
+ description: string = 'Requested'
729
+ ): KaminoReserve {
730
+ return checkDefined(
731
+ this.getReserveBySymbolAndKind(symbol, reserveKind),
732
+ `${description} reserve with symbol ${symbol} and kind ${reserveKind.toString()} not found`
733
+ );
734
+ }
735
+
736
+ /**
737
+ * Returns this market's float rate reserve for the given symbol.
738
+ * Float rate reserves are reserves without a fixed term (debtTermSeconds = 0).
739
+ *
740
+ * @param symbol The reserve symbol
741
+ * @returns The float rate reserve, or undefined if not found
742
+ */
743
+ getFloatRateReserveBySymbol(symbol: string): KaminoReserve | undefined {
744
+ return this.getReserveBySymbolAndKind(symbol, new FloatRateReserveKind());
745
+ }
746
+
747
+ /**
748
+ * Returns this market's float rate reserve for the given symbol,
749
+ * or throws an error if not found.
750
+ *
751
+ * @param symbol The reserve symbol
752
+ * @param description Optional description for the error message
753
+ * @returns The float rate reserve
754
+ */
755
+ getExistingFloatRateReserveBySymbol(symbol: string, description: string = 'Requested'): KaminoReserve {
756
+ return checkDefined(
757
+ this.getFloatRateReserveBySymbol(symbol),
758
+ `${description} float rate reserve with symbol ${symbol} not found`
759
+ );
760
+ }
761
+
762
+ /**
763
+ * Returns all fixed rate reserves for the given symbol.
764
+ * Fixed rate reserves have a non-zero debt term (debtTermSeconds > 0).
765
+ *
766
+ * @param symbol The reserve symbol
767
+ * @returns Array of all fixed rate reserves for this symbol
768
+ */
769
+ getFixedRateReservesBySymbol(symbol: string): KaminoReserve[] {
770
+ const fixedRateReserves: KaminoReserve[] = [];
771
+ const floatRateReserveKind = new FloatRateReserveKind();
772
+ for (const reserve of this.reserves.values()) {
773
+ if (reserve.symbol === symbol && !floatRateReserveKind.matches(reserve)) {
774
+ fixedRateReserves.push(reserve);
775
+ }
776
+ }
777
+ return fixedRateReserves;
778
+ }
779
+
780
+ getReserveMintBySymbol(symbol: string) {
781
+ const reserves = this.getReservesBySymbol(symbol);
782
+ if (reserves.length === 0) {
783
+ throw new Error(`No reserves found for symbol ${symbol}`);
784
+ }
785
+ return reserves[0].getLiquidityMint();
786
+ }
787
+
613
788
  async getReserveFarmInfo(
614
789
  reserveAddress: Address,
615
790
  getRewardPrice: (mint: Address) => Promise<number>
@@ -19,6 +19,7 @@ import Decimal from 'decimal.js';
19
19
  import {
20
20
  AllOracleAccounts,
21
21
  DEFAULT_PUBLIC_KEY,
22
+ FixedRateReserveKind,
22
23
  getTokenOracleData,
23
24
  globalConfigPda,
24
25
  INITIAL_COLLATERAL_RATE,
@@ -26,7 +27,9 @@ import {
26
27
  MarketWithAddress,
27
28
  MIN_INITIAL_DEPOSIT,
28
29
  ONE_HUNDRED_PCT_IN_BPS,
30
+ FloatRateReserveKind,
29
31
  reservePdas,
32
+ ReserveKind,
30
33
  SLOTS_PER_DAY,
31
34
  SLOTS_PER_SECOND,
32
35
  SLOTS_PER_YEAR,
@@ -76,6 +79,7 @@ export class KaminoReserve {
76
79
  private readonly recentSlotDurationMs: number;
77
80
 
78
81
  private metadata?: ScopeEntryMetadata[];
82
+ private reserveKind: ReserveKind;
79
83
 
80
84
  constructor(
81
85
  state: Reserve,
@@ -91,6 +95,7 @@ export class KaminoReserve {
91
95
  this.rpc = connection;
92
96
  this.symbol = parseTokenSymbol(state.config.tokenInfo.name);
93
97
  this.recentSlotDurationMs = recentSlotDurationMs;
98
+ this.reserveKind = KaminoReserve.createReserveKind(state);
94
99
  }
95
100
 
96
101
  static initialize(
@@ -130,6 +135,16 @@ export class KaminoReserve {
130
135
  return new KaminoReserve(reserve, address, tokenOracleData, rpc, recentSlotDurationMs);
131
136
  }
132
137
 
138
+ static createReserveKind(state: Reserve): ReserveKind {
139
+ const debtTermSeconds = state.config.debtTermSeconds;
140
+ if (debtTermSeconds.eqn(0)) {
141
+ return new FloatRateReserveKind();
142
+ } else {
143
+ const borrowRateBps = state.config.borrowRateCurve.points[0]?.borrowRateBps || 0;
144
+ return new FixedRateReserveKind(debtTermSeconds, borrowRateBps);
145
+ }
146
+ }
147
+
133
148
  /// GETTERS
134
149
 
135
150
  /**
@@ -733,6 +748,15 @@ export class KaminoReserve {
733
748
  return this.state.collateral.mintPubkey;
734
749
  }
735
750
 
751
+ /**
752
+ * Returns the reserve kind (FloatRateReserveKind or FixedRateReserveKind) for this reserve.
753
+ *
754
+ * @returns The reserve kind instance
755
+ */
756
+ getKind(): ReserveKind {
757
+ return this.reserveKind;
758
+ }
759
+
736
760
  calculateFees(
737
761
  amountLamports: Decimal,
738
762
  borrowFeeRate: Decimal,
@@ -0,0 +1,94 @@
1
+ import BN from 'bn.js';
2
+ import { KaminoReserve } from '../classes/reserve';
3
+
4
+ /**
5
+ * Base class for reserve kind identification.
6
+ * Since a market can have multiple reserves for the same mint (with different terms),
7
+ * this kind specifies which reserve to select.
8
+ */
9
+ export abstract class ReserveKind {
10
+ /**
11
+ * Checks if a reserve matches this reserve kind's criteria
12
+ */
13
+ abstract matches(reserve: KaminoReserve): boolean;
14
+
15
+ /**
16
+ * Returns a human-readable string representation
17
+ */
18
+ abstract toString(): string;
19
+
20
+ /**
21
+ * Type check: returns true if this is an open-term reserve
22
+ */
23
+ abstract isFloatRate(): boolean;
24
+
25
+ /**
26
+ * Type check: returns true if this is a fixed-term reserve
27
+ */
28
+ abstract isFixedRate(): boolean;
29
+ }
30
+
31
+ /**
32
+ * Represents an open-term reserve with no fixed maturity.
33
+ */
34
+ export class FloatRateReserveKind extends ReserveKind {
35
+ /**
36
+ * Checks if a reserve matches this open-term reserve kind (debtTermSeconds == 0)
37
+ */
38
+ matches(reserve: KaminoReserve): boolean {
39
+ return reserve.state.config.debtTermSeconds.eq(new BN(0));
40
+ }
41
+
42
+ toString(): string {
43
+ return 'FloatRate';
44
+ }
45
+
46
+ isFloatRate(): boolean {
47
+ return true;
48
+ }
49
+
50
+ isFixedRate(): boolean {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Represents a fixed-term reserve with specific loan parameters.
57
+ */
58
+ export class FixedRateReserveKind extends ReserveKind {
59
+ /**
60
+ * @param debtTermSeconds The debt term in seconds (e.g., new BN(30 * 24 * 60 * 60) for 30 days)
61
+ * @param borrowRateBps The maximum borrow rate in basis points (e.g., 500 for 5%)
62
+ */
63
+ constructor(public readonly debtTermSeconds: BN, public readonly borrowRateBps: number) {
64
+ super();
65
+ }
66
+
67
+ /**
68
+ * Checks if a reserve matches this fixed-term reserve kind
69
+ */
70
+ matches(reserve: KaminoReserve): boolean {
71
+ // Check debt term matches
72
+ if (!reserve.state.config.debtTermSeconds.eq(this.debtTermSeconds)) {
73
+ return false;
74
+ }
75
+
76
+ // For fixed-term reserves, all points on the borrow rate curve should have the same rate
77
+ const curvePoints = reserve.state.config.borrowRateCurve.points;
78
+
79
+ // Check if all points have the expected borrow rate
80
+ return curvePoints.every((point) => point.borrowRateBps === this.borrowRateBps);
81
+ }
82
+
83
+ toString(): string {
84
+ return `FixedRate(term=${this.debtTermSeconds.toString()}s, rate=${this.borrowRateBps}bps)`;
85
+ }
86
+
87
+ isFloatRate(): boolean {
88
+ return false;
89
+ }
90
+
91
+ isFixedRate(): boolean {
92
+ return true;
93
+ }
94
+ }
@@ -5,6 +5,7 @@ export * from './constants';
5
5
  export * from './idl';
6
6
  export * from './instruction';
7
7
  export * from './ObligationType';
8
+ export * from './ReserveKind';
8
9
  export * from './seeds';
9
10
  export * from './slots';
10
11
  export * from './userMetadata';