@kamino-finance/klend-sdk 6.0.5-beta.10 → 6.0.5-beta.11

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.
@@ -23,6 +23,7 @@ import {
23
23
  PublicKeySet,
24
24
  DEPOSITS_LIMIT,
25
25
  setOrAppend,
26
+ AllOracleAccounts,
26
27
  } from '../utils';
27
28
  import base58 from 'bs58';
28
29
  import { BN } from '@coral-xyz/anchor';
@@ -32,7 +33,7 @@ import { PROGRAM_ID } from '../idl_codegen/programId';
32
33
  import bs58 from 'bs58';
33
34
  import { OraclePrices, Scope, U16_MAX } from '@kamino-finance/scope-sdk';
34
35
  import { Fraction } from './fraction';
35
- import { chunks, KaminoPrices, MintToPriceMap } from '@kamino-finance/kliquidity-sdk';
36
+ import { batchFetch, chunks, KaminoPrices, MintToPriceMap } from '@kamino-finance/kliquidity-sdk';
36
37
  import { parseTokenSymbol, parseZeroPaddedUtf8 } from './utils';
37
38
  import SwitchboardProgram from '@switchboard-xyz/sbv2-lite';
38
39
  import { ObligationZP } from '../idl_codegen/zero_padding';
@@ -73,6 +74,10 @@ export class KaminoMarket {
73
74
  recentSlotDurationMs: number,
74
75
  programId: PublicKey = PROGRAM_ID
75
76
  ) {
77
+ if (recentSlotDurationMs <= 0) {
78
+ throw new Error('Recent slot duration cannot be 0 or less');
79
+ }
80
+
76
81
  this.address = marketAddress;
77
82
  this.connection = connection;
78
83
  this.state = state;
@@ -109,10 +114,6 @@ export class KaminoMarket {
109
114
  return null;
110
115
  }
111
116
 
112
- if (recentSlotDurationMs <= 0) {
113
- throw new Error('Recent slot duration cannot be 0');
114
- }
115
-
116
117
  const reserves = withReserves
117
118
  ? await getReservesForMarket(marketAddress, connection, programId, recentSlotDurationMs)
118
119
  : new Map<PublicKey, KaminoReserve>();
@@ -131,6 +132,68 @@ export class KaminoMarket {
131
132
  return new KaminoMarket(connection, market, marketAddress.toString(), reserves, recentSlotDurationMs, programId);
132
133
  }
133
134
 
135
+ static async loadMultiple(
136
+ connection: Connection,
137
+ markets: PublicKey[],
138
+ recentSlotDurationMs: number,
139
+ programId: PublicKey = PROGRAM_ID,
140
+ withReserves: boolean = true,
141
+ oracleAccounts?: AllOracleAccounts
142
+ ) {
143
+ const marketStates = await batchFetch(markets, (market) =>
144
+ LendingMarket.fetchMultiple(connection, market, programId)
145
+ );
146
+ const kaminoMarkets = new PubkeyHashMap<PublicKey, KaminoMarket>();
147
+ for (let i = 0; i < markets.length; i++) {
148
+ const market = marketStates[i];
149
+ const marketAddress = markets[i];
150
+ if (market === null) {
151
+ throw Error(`Could not fetch LendingMarket account state for market ${marketAddress}`);
152
+ }
153
+
154
+ const marketReserves = withReserves
155
+ ? await getReservesForMarket(marketAddress, connection, programId, recentSlotDurationMs, oracleAccounts)
156
+ : new PubkeyHashMap<PublicKey, KaminoReserve>();
157
+
158
+ kaminoMarkets.set(
159
+ marketAddress,
160
+ new KaminoMarket(connection, market, marketAddress.toString(), marketReserves, recentSlotDurationMs, programId)
161
+ );
162
+ }
163
+ return kaminoMarkets;
164
+ }
165
+
166
+ static async loadMultipleWithReserves(
167
+ connection: Connection,
168
+ markets: PublicKey[],
169
+ reserves: PubkeyHashMap<PublicKey, PubkeyHashMap<PublicKey, KaminoReserve>>,
170
+ recentSlotDurationMs: number,
171
+ programId: PublicKey = PROGRAM_ID
172
+ ) {
173
+ const marketStates = await batchFetch(markets, (market) =>
174
+ LendingMarket.fetchMultiple(connection, market, programId)
175
+ );
176
+ const kaminoMarkets = new PubkeyHashMap<PublicKey, KaminoMarket>();
177
+ for (let i = 0; i < markets.length; i++) {
178
+ const market = marketStates[i];
179
+ const marketAddress = markets[i];
180
+ if (market === null) {
181
+ throw Error(`Could not fetch LendingMarket account state for market ${marketAddress}`);
182
+ }
183
+ const marketReserves = reserves.get(marketAddress);
184
+ if (!marketReserves) {
185
+ throw Error(
186
+ `Could not get reserves for market ${marketAddress} from the reserves map argument supplied to this method`
187
+ );
188
+ }
189
+ kaminoMarkets.set(
190
+ marketAddress,
191
+ new KaminoMarket(connection, market, marketAddress.toString(), marketReserves, recentSlotDurationMs, programId)
192
+ );
193
+ }
194
+ return kaminoMarkets;
195
+ }
196
+
134
197
  async reload(): Promise<void> {
135
198
  const market = await LendingMarket.fetch(this.connection, this.getAddress(), this.programId);
136
199
  if (market === null) {
@@ -383,7 +446,7 @@ export class KaminoMarket {
383
446
  : debtReserve.getMaxBorrowAmountWithCollReserve(this, collReserve, slot);
384
447
  }
385
448
 
386
- async loadReserves() {
449
+ async loadReserves(oracleAccounts?: AllOracleAccounts) {
387
450
  const addresses = [...this.reserves.keys()];
388
451
  const reserveAccounts = await this.connection.getMultipleAccountsInfo(addresses, 'processed');
389
452
  const deserializedReserves = reserveAccounts.map((reserve, i) => {
@@ -397,7 +460,7 @@ export class KaminoMarket {
397
460
  }
398
461
  return reserveAccount;
399
462
  });
400
- const reservesAndOracles = await getTokenOracleData(this.connection, deserializedReserves);
463
+ const reservesAndOracles = await getTokenOracleData(this.connection, deserializedReserves, oracleAccounts);
401
464
  const kaminoReserves = new PubkeyHashMap<PublicKey, KaminoReserve>();
402
465
  reservesAndOracles.forEach(([reserve, oracle], index) => {
403
466
  if (!oracle) {
@@ -849,9 +912,13 @@ export class KaminoMarket {
849
912
  return finalObligations;
850
913
  }
851
914
 
852
- async getAllUserObligations(user: PublicKey, commitment = this.connection.commitment): Promise<KaminoObligation[]> {
915
+ async getAllUserObligations(
916
+ user: PublicKey,
917
+ commitment = this.connection.commitment,
918
+ slot?: number
919
+ ): Promise<KaminoObligation[]> {
853
920
  const [currentSlot, obligations] = await Promise.all([
854
- this.connection.getSlot(),
921
+ slot !== undefined ? Promise.resolve(slot) : this.connection.getSlot(),
855
922
  this.connection.getProgramAccounts(this.programId, {
856
923
  filters: [
857
924
  {
@@ -1226,8 +1293,10 @@ export class KaminoMarket {
1226
1293
  /**
1227
1294
  * Get all Scope prices used by all the market reserves
1228
1295
  */
1229
- async getAllScopePrices(scope: Scope): Promise<KaminoPrices> {
1230
- const allOraclePrices = await this.getReserveOraclePrices(scope);
1296
+ async getAllScopePrices(
1297
+ scope: Scope,
1298
+ allOraclePrices: PubkeyHashMap<PublicKey, OraclePrices>
1299
+ ): Promise<KaminoPrices> {
1231
1300
  const spot: MintToPriceMap = {};
1232
1301
  const twaps: MintToPriceMap = {};
1233
1302
  for (const reserve of this.reserves.values()) {
@@ -1236,7 +1305,7 @@ export class KaminoMarket {
1236
1305
  const oracle = reserve.state.config.tokenInfo.scopeConfiguration.priceFeed;
1237
1306
  const chain = reserve.state.config.tokenInfo.scopeConfiguration.priceChain;
1238
1307
  const twapChain = reserve.state.config.tokenInfo.scopeConfiguration.twapChain.filter((x) => x > 0);
1239
- const oraclePrices = allOraclePrices.get(reserve.address);
1308
+ const oraclePrices = allOraclePrices.get(oracle);
1240
1309
  if (oraclePrices && oracle && isNotNullPubkey(oracle) && chain && Scope.isScopeChainValid(chain)) {
1241
1310
  const spotPrice = await scope.getPriceFromChain(chain, oraclePrices);
1242
1311
  spot[tokenMint] = { price: spotPrice.price, name: tokenName };
@@ -1252,16 +1321,18 @@ export class KaminoMarket {
1252
1321
  /**
1253
1322
  * Get all Scope/Pyth/Switchboard prices used by all the market reserves
1254
1323
  */
1255
- async getAllPrices(): Promise<KlendPrices> {
1324
+ async getAllPrices(oracleAccounts?: AllOracleAccounts): Promise<KlendPrices> {
1256
1325
  const klendPrices: KlendPrices = {
1257
1326
  scope: { spot: {}, twap: {} },
1258
1327
  pyth: { spot: {}, twap: {} },
1259
1328
  switchboard: { spot: {}, twap: {} },
1260
1329
  };
1261
- const allOracleAccounts = await getAllOracleAccounts(
1262
- this.connection,
1263
- this.getReserves().map((x) => x.state)
1264
- );
1330
+ const allOracleAccounts =
1331
+ oracleAccounts ??
1332
+ (await getAllOracleAccounts(
1333
+ this.connection,
1334
+ this.getReserves().map((x) => x.state)
1335
+ ));
1265
1336
  const pythCache = new PubkeyHashMap<PublicKey, PythPrices>();
1266
1337
  const switchboardCache = new PubkeyHashMap<PublicKey, CandidatePrice>();
1267
1338
  const scopeCache = new PubkeyHashMap<PublicKey, OraclePrices>();
@@ -1473,7 +1544,8 @@ export async function getReservesForMarket(
1473
1544
  marketAddress: PublicKey,
1474
1545
  connection: Connection,
1475
1546
  programId: PublicKey,
1476
- recentSlotDurationMs: number
1547
+ recentSlotDurationMs: number,
1548
+ oracleAccounts?: AllOracleAccounts
1477
1549
  ): Promise<Map<PublicKey, KaminoReserve>> {
1478
1550
  const reserves = await connection.getProgramAccounts(programId, {
1479
1551
  filters: [
@@ -1501,7 +1573,7 @@ export async function getReservesForMarket(
1501
1573
  return reserveAccount;
1502
1574
  });
1503
1575
  const allBuffers = reserves.map((reserve) => reserve.account);
1504
- const reservesAndOracles = await getTokenOracleData(connection, deserializedReserves);
1576
+ const reservesAndOracles = await getTokenOracleData(connection, deserializedReserves, oracleAccounts);
1505
1577
  const reservesByAddress = new PubkeyHashMap<PublicKey, KaminoReserve>();
1506
1578
  reservesAndOracles.forEach(([reserve, oracle], index) => {
1507
1579
  if (!oracle) {
@@ -1524,7 +1596,8 @@ export async function getSingleReserve(
1524
1596
  reservePk: PublicKey,
1525
1597
  connection: Connection,
1526
1598
  recentSlotDurationMs: number,
1527
- accountData?: AccountInfo<Buffer>
1599
+ accountData?: AccountInfo<Buffer>,
1600
+ oracleAccounts?: AllOracleAccounts
1528
1601
  ): Promise<KaminoReserve> {
1529
1602
  const reserve = accountData ? accountData : await connection.getAccountInfo(reservePk);
1530
1603
 
@@ -1538,7 +1611,7 @@ export async function getSingleReserve(
1538
1611
  throw Error(`Could not parse reserve ${reservePk.toBase58()}`);
1539
1612
  }
1540
1613
 
1541
- const reservesAndOracles = await getTokenOracleData(connection, [reserveAccount]);
1614
+ const reservesAndOracles = await getTokenOracleData(connection, [reserveAccount], oracleAccounts);
1542
1615
  const [reserveState, oracle] = reservesAndOracles[0];
1543
1616
 
1544
1617
  if (!oracle) {
@@ -28,6 +28,7 @@ import {
28
28
  PubkeyHashMap,
29
29
  Reserve,
30
30
  UserState,
31
+ AllOracleAccounts,
31
32
  } from '../lib';
32
33
  import {
33
34
  DepositAccounts,
@@ -2324,9 +2325,13 @@ export class KaminoVaultClient {
2324
2325
  /**
2325
2326
  * This will load the onchain state for all the reserves that the vaults have allocations for, deduplicating the reserves
2326
2327
  * @param vaults - the vault states to load reserves for
2328
+ * @param oracleAccounts
2327
2329
  * @returns a hashmap from each reserve pubkey to the reserve state
2328
2330
  */
2329
- async loadVaultsReserves(vaults: VaultState[]): Promise<PubkeyHashMap<PublicKey, KaminoReserve>> {
2331
+ async loadVaultsReserves(
2332
+ vaults: VaultState[],
2333
+ oracleAccounts?: AllOracleAccounts
2334
+ ): Promise<PubkeyHashMap<PublicKey, KaminoReserve>> {
2330
2335
  const vaultReservesAddressesSet = new PublicKeySet(vaults.flatMap((vault) => this.getVaultReserves(vault)));
2331
2336
  const vaultReservesAddresses = vaultReservesAddressesSet.toArray();
2332
2337
  const reserveAccounts = await this.getConnection().getMultipleAccountsInfo(vaultReservesAddresses, 'processed');
@@ -2343,7 +2348,7 @@ export class KaminoVaultClient {
2343
2348
  return reserveAccount;
2344
2349
  });
2345
2350
 
2346
- const reservesAndOracles = await getTokenOracleData(this.getConnection(), deserializedReserves);
2351
+ const reservesAndOracles = await getTokenOracleData(this.getConnection(), deserializedReserves, oracleAccounts);
2347
2352
 
2348
2353
  const kaminoReserves = new PubkeyHashMap<PublicKey, KaminoReserve>();
2349
2354
 
@@ -2371,13 +2376,15 @@ export class KaminoVaultClient {
2371
2376
  * @param [slot] - the slot for which to retrieve the vault collaterals for. Optional. If not provided the function will fetch the current slot
2372
2377
  * @param [vaultReservesMap] - hashmap from each reserve pubkey to the reserve state. Optional. If provided the function will be significantly faster as it will not have to fetch the reserves
2373
2378
  * @param [kaminoMarkets] - a list of all the kamino markets. Optional. If provided the function will be significantly faster as it will not have to fetch the markets
2379
+ * @param oracleAccounts
2374
2380
  * @returns a hashmap from each reserve pubkey to the market overview of the collaterals that can be used and the min and max loan to value ratio in that market
2375
2381
  */
2376
2382
  async getVaultCollaterals(
2377
2383
  vaultState: VaultState,
2378
2384
  slot: number,
2379
2385
  vaultReservesMap?: PubkeyHashMap<PublicKey, KaminoReserve>,
2380
- kaminoMarkets?: KaminoMarket[]
2386
+ kaminoMarkets?: KaminoMarket[],
2387
+ oracleAccounts?: AllOracleAccounts
2381
2388
  ): Promise<PubkeyHashMap<PublicKey, MarketOverview>> {
2382
2389
  const vaultReservesStateMap = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
2383
2390
  const vaultReservesState: KaminoReserve[] = [];
@@ -2401,7 +2408,11 @@ export class KaminoVaultClient {
2401
2408
  const missingReservesStates = (await Reserve.fetchMultiple(this.getConnection(), missingReserves.toArray())).filter(
2402
2409
  (reserve) => reserve !== null
2403
2410
  );
2404
- const missingReservesAndOracles = await getTokenOracleData(this.getConnection(), missingReservesStates);
2411
+ const missingReservesAndOracles = await getTokenOracleData(
2412
+ this.getConnection(),
2413
+ missingReservesStates,
2414
+ oracleAccounts
2415
+ );
2405
2416
  missingReservesAndOracles.forEach(([reserve, oracle], index) => {
2406
2417
  const fetchedReserve = new KaminoReserve(
2407
2418
  reserve,
@@ -107,9 +107,10 @@ export function getTokenOracleDataSync(
107
107
  // TODO: Add freshness of the latest price to match sc logic
108
108
  export async function getTokenOracleData(
109
109
  connection: Connection,
110
- reserves: Reserve[]
110
+ reserves: Reserve[],
111
+ oracleAccounts?: AllOracleAccounts
111
112
  ): Promise<Array<[Reserve, TokenOracleData | undefined]>> {
112
- const allOracleAccounts = await getAllOracleAccounts(connection, reserves);
113
+ const allOracleAccounts = oracleAccounts ?? (await getAllOracleAccounts(connection, reserves));
113
114
  const switchboardV2 = await SwitchboardProgram.loadMainnet(connection);
114
115
  return getTokenOracleDataSync(allOracleAccounts, switchboardV2, reserves);
115
116
  }