@kamino-finance/klend-sdk 7.3.2 → 7.3.3

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.
@@ -56,6 +56,7 @@ import { ObligationZP } from '../@codegen/klend/zero_padding';
56
56
  import { checkDefined } from '../utils/validations';
57
57
  import { Buffer } from 'buffer';
58
58
  import { ReserveStatus } from '../@codegen/klend/types';
59
+ import { fetchKaminoCdnData } from '../utils/readCdnData';
59
60
 
60
61
  export type KaminoMarketRpcApi = GetAccountInfoApi &
61
62
  GetMultipleAccountsApi &
@@ -479,7 +480,10 @@ export class KaminoMarket {
479
480
  state: reserveAccount,
480
481
  };
481
482
  });
482
- const reservesAndOracles = await getTokenOracleData(this.getRpc(), deserializedReserves, oracleAccounts);
483
+ const [reservesAndOracles, cdnResourcesData] = await Promise.all([
484
+ getTokenOracleData(this.getRpc(), deserializedReserves, oracleAccounts),
485
+ fetchKaminoCdnData(),
486
+ ]);
483
487
  const kaminoReserves = new Map<Address, KaminoReserve>();
484
488
  reservesAndOracles.forEach(([reserve, oracle], index) => {
485
489
  if (!oracle) {
@@ -494,7 +498,8 @@ export class KaminoMarket {
494
498
  reserve,
495
499
  oracle,
496
500
  this.rpc,
497
- this.recentSlotDurationMs
501
+ this.recentSlotDurationMs,
502
+ cdnResourcesData
498
503
  );
499
504
  kaminoReserves.set(kaminoReserve.address, kaminoReserve);
500
505
  });
@@ -1712,7 +1717,10 @@ export async function getReservesForMarket(
1712
1717
  state: reserveAccount,
1713
1718
  };
1714
1719
  });
1715
- const reservesAndOracles = await getTokenOracleData(rpc, deserializedReserves, oracleAccounts);
1720
+ const [reservesAndOracles, cdnResourcesData] = await Promise.all([
1721
+ getTokenOracleData(rpc, deserializedReserves, oracleAccounts),
1722
+ fetchKaminoCdnData(),
1723
+ ]);
1716
1724
  const reservesByAddress = new Map<Address, KaminoReserve>();
1717
1725
  reservesAndOracles.forEach(([reserve, oracle], index) => {
1718
1726
  if (reserve.config.status === ReserveStatus.Obsolete.discriminator) {
@@ -1725,7 +1733,14 @@ export async function getReservesForMarket(
1725
1733
  }) reserve in market ${reserve.lendingMarket}`
1726
1734
  );
1727
1735
  }
1728
- const kaminoReserve = KaminoReserve.initialize(reserves[index].pubkey, reserve, oracle, rpc, recentSlotDurationMs);
1736
+ const kaminoReserve = KaminoReserve.initialize(
1737
+ reserves[index].pubkey,
1738
+ reserve,
1739
+ oracle,
1740
+ rpc,
1741
+ recentSlotDurationMs,
1742
+ cdnResourcesData
1743
+ );
1729
1744
  reservesByAddress.set(kaminoReserve.address, kaminoReserve);
1730
1745
  });
1731
1746
  return reservesByAddress;
@@ -1743,7 +1758,10 @@ export async function getSingleReserve(
1743
1758
  if (reserve === null) {
1744
1759
  throw new Error(`Reserve account ${reservePk} does not exist`);
1745
1760
  }
1746
- const reservesAndOracles = await getTokenOracleData(rpc, [{ address: reservePk, state: reserve }], oracleAccounts);
1761
+ const [reservesAndOracles, cdnResourcesData] = await Promise.all([
1762
+ getTokenOracleData(rpc, [{ address: reservePk, state: reserve }], oracleAccounts),
1763
+ fetchKaminoCdnData(),
1764
+ ]);
1747
1765
  const [, oracle] = reservesAndOracles[0];
1748
1766
 
1749
1767
  if (!oracle) {
@@ -1753,7 +1771,7 @@ export async function getSingleReserve(
1753
1771
  }`
1754
1772
  );
1755
1773
  }
1756
- return KaminoReserve.initialize(reservePk, reserve, oracle, rpc, recentSlotDurationMs);
1774
+ return KaminoReserve.initialize(reservePk, reserve, oracle, rpc, recentSlotDurationMs, cdnResourcesData);
1757
1775
  }
1758
1776
 
1759
1777
  export function getReservesActive(reserves: Map<Address, KaminoReserve>): Map<Address, KaminoReserve> {
@@ -57,6 +57,7 @@ import { SYSVAR_RENT_ADDRESS } from '@solana/sysvars';
57
57
  import { noopSigner } from '../utils/signer';
58
58
  import { getRewardPerTimeUnitSecond } from './farm_utils';
59
59
  import { Scope, ScopeEntryMetadata } from '@kamino-finance/scope-sdk';
60
+ import { fetchKaminoCdnData, KaminoCdnData } from '../utils/readCdnData';
60
61
 
61
62
  export type KaminoReserveRpcApi = GetProgramAccountsApi & GetAccountInfoApi & GetMultipleAccountsApi;
62
63
 
@@ -97,10 +98,11 @@ export class KaminoReserve {
97
98
  state: Reserve,
98
99
  tokenOraclePrice: TokenOracleData,
99
100
  rpc: Rpc<KaminoReserveRpcApi>,
100
- recentSlotDurationMs: number
101
+ recentSlotDurationMs: number,
102
+ cdnResourcesData?: KaminoCdnData
101
103
  ): KaminoReserve {
102
104
  const reserve = new KaminoReserve(state, address, tokenOraclePrice, rpc, recentSlotDurationMs);
103
- reserve.stats = reserve.formatReserveData(state);
105
+ reserve.stats = reserve.formatReserveData(state, cdnResourcesData?.deprecatedAssets ?? []);
104
106
  return reserve;
105
107
  }
106
108
 
@@ -779,13 +781,16 @@ export class KaminoReserve {
779
781
  }
780
782
 
781
783
  async load(tokenOraclePrice: TokenOracleData) {
782
- const parsedData = await Reserve.fetch(this.rpc, this.address);
784
+ const [parsedData, cdnResourcesData] = await Promise.all([
785
+ Reserve.fetch(this.rpc, this.address),
786
+ fetchKaminoCdnData(),
787
+ ]);
783
788
  if (!parsedData) {
784
789
  throw Error(`Unable to parse data of reserve ${this.symbol}`);
785
790
  }
786
791
  this.state = parsedData;
787
792
  this.tokenOraclePrice = tokenOraclePrice;
788
- this.stats = this.formatReserveData(parsedData);
793
+ this.stats = this.formatReserveData(parsedData, cdnResourcesData?.deprecatedAssets ?? []);
789
794
  }
790
795
 
791
796
  totalSupplyAPY(currentSlot: Slot) {
@@ -874,7 +879,7 @@ export class KaminoReserve {
874
879
  return { apy: aprToApy(apr, 365), apr };
875
880
  }
876
881
 
877
- private formatReserveData(parsedData: ReserveFields): ReserveDataType {
882
+ private formatReserveData(parsedData: ReserveFields, deprecatedAssets: string[]): ReserveDataType {
878
883
  const mintTotalSupply = new Decimal(parsedData.collateral.mintTotalSupply.toString()).div(this.getMintFactor());
879
884
  let reserveStatus = ReserveStatus.Active;
880
885
  switch (parsedData.config.status) {
@@ -888,6 +893,8 @@ export class KaminoReserve {
888
893
  reserveStatus = ReserveStatus.Hidden;
889
894
  break;
890
895
  }
896
+ const reserveIsUIDeprecated =
897
+ deprecatedAssets.length > 0 ? deprecatedAssets.includes(this.address.toString()) : undefined;
891
898
  return {
892
899
  // Reserve config
893
900
 
@@ -910,6 +917,7 @@ export class KaminoReserve {
910
917
  depositLimitCrossedTimestamp: parsedData.liquidity.depositLimitCrossedTimestamp.toNumber(),
911
918
  borrowLimitCrossedTimestamp: parsedData.liquidity.borrowLimitCrossedTimestamp.toNumber(),
912
919
  borrowFactor: parsedData.config.borrowFactorPct.toNumber(),
920
+ isUIDeprecated: reserveIsUIDeprecated,
913
921
  };
914
922
  }
915
923
 
@@ -44,6 +44,7 @@ export type ReserveDataType = {
44
44
  accumulatedProtocolFees: Decimal;
45
45
  mintTotalSupply: Decimal;
46
46
  borrowFactor: number;
47
+ isUIDeprecated: boolean | undefined;
47
48
  };
48
49
 
49
50
  export type ReserveRewardYield = {
@@ -139,6 +139,7 @@ import { Farms } from '@kamino-finance/farms-sdk';
139
139
  import { getFarmIncentives } from '@kamino-finance/farms-sdk/dist/utils/apy';
140
140
  import { computeReservesAllocation } from '../utils/vaultAllocation';
141
141
  import { getReserveFarmRewardsAPY } from '../utils/farmUtils';
142
+ import { fetchKaminoCdnData } from '../utils/readCdnData';
142
143
 
143
144
  export const kaminoVaultId = address('KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd');
144
145
  export const kaminoVaultStagingId = address('stKvQfwRsQiKnLtMNVLHKS3exFJmZFsgfzBPWHECUYK');
@@ -2581,7 +2582,10 @@ export class KaminoVaultClient {
2581
2582
  const deserializedReserves = await batchFetch(vaultReservesAddresses, (chunk) =>
2582
2583
  this.loadReserializedReserves(chunk)
2583
2584
  );
2584
- const reservesAndOracles = await getTokenOracleData(this.getConnection(), deserializedReserves, oracleAccounts);
2585
+ const [reservesAndOracles, cdnResourcesData] = await Promise.all([
2586
+ getTokenOracleData(this.getConnection(), deserializedReserves, oracleAccounts),
2587
+ fetchKaminoCdnData(),
2588
+ ]);
2585
2589
  const kaminoReserves = new Map<Address, KaminoReserve>();
2586
2590
  reservesAndOracles.forEach(([reserve, oracle], index) => {
2587
2591
  if (!oracle) {
@@ -2596,7 +2600,8 @@ export class KaminoVaultClient {
2596
2600
  reserve,
2597
2601
  oracle,
2598
2602
  this.getConnection(),
2599
- this.recentSlotDurationMs
2603
+ this.recentSlotDurationMs,
2604
+ cdnResourcesData
2600
2605
  );
2601
2606
  kaminoReserves.set(kaminoReserve.address, kaminoReserve);
2602
2607
  });
@@ -0,0 +1,50 @@
1
+ import { CDN_ENDPOINT } from './constants';
2
+
3
+ /**
4
+ * CDN data structure containing Kamino resources for different networks
5
+ */
6
+ export interface AllKaminoCdnData {
7
+ 'mainnet-beta': KaminoCdnData;
8
+ devnet: KaminoCdnData;
9
+ }
10
+
11
+ /**
12
+ * Kamino CDN data structure
13
+ * This type can be extended with additional fields as needed
14
+ */
15
+ export interface KaminoCdnData {
16
+ /**
17
+ * List of deprecated reserve addresses (pubkeys as strings)
18
+ * Note: This field is named 'deprecatedAssets' in the CDN but represents deprecated reserves
19
+ */
20
+ deprecatedAssets: string[];
21
+ // Additional fields can be added here as they become relevant
22
+ [key: string]: unknown;
23
+ }
24
+
25
+ /**
26
+ * Fetches Kamino CDN data from the resources endpoint
27
+ * @returns Promise resolving to the CDN data for the specified cluster or undefined if fetching/parsing fails
28
+ */
29
+ export async function fetchKaminoCdnData(): Promise<KaminoCdnData | undefined> {
30
+ const url = `${CDN_ENDPOINT}/resources.json`;
31
+
32
+ try {
33
+ const response = await fetch(url);
34
+
35
+ if (!response.ok) {
36
+ throw new Error(`HTTP error! status: ${response.status}`);
37
+ }
38
+
39
+ const text = await response.text();
40
+
41
+ try {
42
+ const data: AllKaminoCdnData = JSON.parse(text);
43
+ return data['mainnet-beta'];
44
+ } catch (parseError) {
45
+ throw new Error('Invalid JSON in response');
46
+ }
47
+ } catch (error) {
48
+ return undefined;
49
+ }
50
+ }