@kamino-finance/klend-sdk 7.0.11 → 7.0.13

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.
@@ -17,7 +17,7 @@ import {
17
17
  Slot,
18
18
  } from '@solana/kit';
19
19
  import { KaminoObligation } from './obligation';
20
- import { KaminoReserve, KaminoReserveRpcApi } from './reserve';
20
+ import { KaminoReserve, KaminoReserveRpcApi, ReserveWithAddress } from './reserve';
21
21
  import { LendingMarket, Obligation, ReferrerTokenState, Reserve, UserMetadata } from '../@codegen/klend/accounts';
22
22
  import {
23
23
  AllOracleAccounts,
@@ -446,13 +446,13 @@ export class KaminoMarket {
446
446
  /**
447
447
  * @returns The max borrowable amount for leverage positions
448
448
  */
449
- async getMaxLeverageBorrowableAmount(
449
+ getMaxLeverageBorrowableAmount(
450
450
  collReserve: KaminoReserve,
451
451
  debtReserve: KaminoReserve,
452
452
  slot: Slot,
453
453
  requestElevationGroup: boolean,
454
454
  obligation?: KaminoObligation
455
- ): Promise<Decimal> {
455
+ ): Decimal {
456
456
  return obligation
457
457
  ? obligation.getMaxBorrowAmount(this, debtReserve.getLiquidityMint(), slot, requestElevationGroup)
458
458
  : debtReserve.getMaxBorrowAmountWithCollReserve(this, collReserve, slot);
@@ -463,7 +463,7 @@ export class KaminoMarket {
463
463
  const reserveAccounts = await this.rpc
464
464
  .getMultipleAccounts(addresses, { commitment: 'processed', encoding: 'base64' })
465
465
  .send();
466
- const deserializedReserves = reserveAccounts.value.map((reserve, i) => {
466
+ const deserializedReserves: ReserveWithAddress[] = reserveAccounts.value.map((reserve, i) => {
467
467
  if (reserve === null) {
468
468
  // maybe reuse old here
469
469
  throw new Error(`Reserve account ${addresses[i]} was not found`);
@@ -472,13 +472,20 @@ export class KaminoMarket {
472
472
  if (!reserveAccount) {
473
473
  throw Error(`Could not parse reserve ${addresses[i]}`);
474
474
  }
475
- return reserveAccount;
475
+ return {
476
+ address: addresses[i],
477
+ state: reserveAccount,
478
+ };
476
479
  });
477
480
  const reservesAndOracles = await getTokenOracleData(this.getRpc(), deserializedReserves, oracleAccounts);
478
481
  const kaminoReserves = new Map<Address, KaminoReserve>();
479
482
  reservesAndOracles.forEach(([reserve, oracle], index) => {
480
483
  if (!oracle) {
481
- throw Error(`Could not find oracle for ${parseTokenSymbol(reserve.config.tokenInfo.name)} reserve`);
484
+ throw Error(
485
+ `Could not find oracle for ${parseTokenSymbol(reserve.config.tokenInfo.name)} (${
486
+ addresses[index]
487
+ }) reserve in market ${reserve.lendingMarket}`
488
+ );
482
489
  }
483
490
  const kaminoReserve = KaminoReserve.initialize(
484
491
  addresses[index],
@@ -1020,7 +1027,7 @@ export class KaminoMarket {
1020
1027
  obligationAddresses.push(await new VanillaObligation(this.programId).toPda(this.getAddress(), user));
1021
1028
  const targetReserve = new Map<Address, KaminoReserve>(Array.from(this.reserves.entries())).get(reserve);
1022
1029
  if (!targetReserve) {
1023
- throw Error('Could not find reserve.');
1030
+ throw Error(`Could not find reserve ${reserve}`);
1024
1031
  }
1025
1032
  for (const [key, kaminoReserve] of this.reserves) {
1026
1033
  if (targetReserve.address === key) {
@@ -1087,7 +1094,7 @@ export class KaminoMarket {
1087
1094
  const obligation = await this.getObligationByAddress(vanillaObligationAddress);
1088
1095
 
1089
1096
  if (!obligation) {
1090
- throw new Error('Could not find vanilla obligation.');
1097
+ throw new Error(`Could not find vanilla obligation ${vanillaObligationAddress}`);
1091
1098
  }
1092
1099
 
1093
1100
  return obligation;
@@ -1594,7 +1601,7 @@ export async function getReservesForMarket(
1594
1601
  encoding: 'base64',
1595
1602
  })
1596
1603
  .send();
1597
- const deserializedReserves = reserves.map((reserve) => {
1604
+ const deserializedReserves: ReserveWithAddress[] = reserves.map((reserve) => {
1598
1605
  if (reserve.account === null) {
1599
1606
  throw new Error(`Reserve account ${reserve.pubkey} does not exist`);
1600
1607
  }
@@ -1604,13 +1611,20 @@ export async function getReservesForMarket(
1604
1611
  if (!reserveAccount) {
1605
1612
  throw Error(`Could not parse reserve ${reserve.pubkey}`);
1606
1613
  }
1607
- return reserveAccount;
1614
+ return {
1615
+ address: reserve.pubkey,
1616
+ state: reserveAccount,
1617
+ };
1608
1618
  });
1609
1619
  const reservesAndOracles = await getTokenOracleData(rpc, deserializedReserves, oracleAccounts);
1610
1620
  const reservesByAddress = new Map<Address, KaminoReserve>();
1611
1621
  reservesAndOracles.forEach(([reserve, oracle], index) => {
1612
1622
  if (!oracle) {
1613
- throw Error(`Could not find oracle for ${parseTokenSymbol(reserve.config.tokenInfo.name)} reserve`);
1623
+ throw Error(
1624
+ `Could not find oracle for ${parseTokenSymbol(reserve.config.tokenInfo.name)} (${
1625
+ reserves[index].pubkey
1626
+ }) reserve in market ${reserve.lendingMarket}`
1627
+ );
1614
1628
  }
1615
1629
  const kaminoReserve = KaminoReserve.initialize(reserves[index].pubkey, reserve, oracle, rpc, recentSlotDurationMs);
1616
1630
  reservesByAddress.set(kaminoReserve.address, kaminoReserve);
@@ -1630,11 +1644,15 @@ export async function getSingleReserve(
1630
1644
  if (reserve === null) {
1631
1645
  throw new Error(`Reserve account ${reservePk} does not exist`);
1632
1646
  }
1633
- const reservesAndOracles = await getTokenOracleData(rpc, [reserve], oracleAccounts);
1647
+ const reservesAndOracles = await getTokenOracleData(rpc, [{ address: reservePk, state: reserve }], oracleAccounts);
1634
1648
  const [, oracle] = reservesAndOracles[0];
1635
1649
 
1636
1650
  if (!oracle) {
1637
- throw Error(`Could not find oracle for ${parseTokenSymbol(reserve.config.tokenInfo.name)} reserve`);
1651
+ throw Error(
1652
+ `Could not find oracle for ${parseTokenSymbol(reserve.config.tokenInfo.name)} (${reservePk}) reserve in market ${
1653
+ reserve.lendingMarket
1654
+ }`
1655
+ );
1638
1656
  }
1639
1657
  return KaminoReserve.initialize(reservePk, reserve, oracle, rpc, recentSlotDurationMs);
1640
1658
  }
@@ -112,7 +112,11 @@ export class KaminoReserve {
112
112
  throw new Error(`Reserve account ${address} does not exist`);
113
113
  }
114
114
 
115
- const tokenOracleDataWithReserve = await getTokenOracleData(rpc, [reserve], oracleAccounts);
115
+ const tokenOracleDataWithReserve = await getTokenOracleData(
116
+ rpc,
117
+ [{ address: address, state: reserve }],
118
+ oracleAccounts
119
+ );
116
120
  if (!tokenOracleDataWithReserve[0]) {
117
121
  throw new Error('Token oracle data not found');
118
122
  }
@@ -2386,7 +2386,10 @@ export class KaminoVaultClient {
2386
2386
  if (!reserveAccount) {
2387
2387
  throw Error(`Could not parse reserve ${vaultReservesAddresses[i]}`);
2388
2388
  }
2389
- return reserveAccount;
2389
+ return {
2390
+ address: vaultReservesAddresses[i],
2391
+ state: reserveAccount,
2392
+ };
2390
2393
  });
2391
2394
 
2392
2395
  const reservesAndOracles = await getTokenOracleData(this.getConnection(), deserializedReserves, oracleAccounts);
@@ -2395,7 +2398,11 @@ export class KaminoVaultClient {
2395
2398
 
2396
2399
  reservesAndOracles.forEach(([reserve, oracle], index) => {
2397
2400
  if (!oracle) {
2398
- throw Error(`Could not find oracle for ${parseTokenSymbol(reserve.config.tokenInfo.name)} reserve`);
2401
+ throw Error(
2402
+ `Could not find oracle for ${parseTokenSymbol(reserve.config.tokenInfo.name)} (${
2403
+ vaultReservesAddresses[index]
2404
+ }) reserve in market ${reserve.lendingMarket}`
2405
+ );
2399
2406
  }
2400
2407
  const kaminoReserve = KaminoReserve.initialize(
2401
2408
  vaultReservesAddresses[index],
@@ -2445,9 +2452,18 @@ export class KaminoVaultClient {
2445
2452
  });
2446
2453
 
2447
2454
  // read missing reserves
2448
- const missingReservesStates = (await Reserve.fetchMultiple(this.getConnection(), [...missingReserves])).filter(
2449
- (reserve) => reserve !== null
2450
- );
2455
+ const missingReserveAddresses = [...missingReserves];
2456
+ const missingReservesStates = (await Reserve.fetchMultiple(this.getConnection(), missingReserveAddresses))
2457
+ .map((reserve, index) => {
2458
+ if (!reserve) {
2459
+ return null;
2460
+ }
2461
+ return {
2462
+ address: missingReserveAddresses[index],
2463
+ state: reserve,
2464
+ };
2465
+ })
2466
+ .filter((state) => state !== null);
2451
2467
  const missingReservesAndOracles = await getTokenOracleData(
2452
2468
  this.getConnection(),
2453
2469
  missingReservesStates,
@@ -2456,7 +2472,7 @@ export class KaminoVaultClient {
2456
2472
  missingReservesAndOracles.forEach(([reserve, oracle], index) => {
2457
2473
  const fetchedReserve = new KaminoReserve(
2458
2474
  reserve,
2459
- [...missingReserves][index]!, // Set maintains order
2475
+ missingReserveAddresses[index]!, // Set maintains order
2460
2476
  oracle!,
2461
2477
  this.getConnection(),
2462
2478
  this.recentSlotDurationMs
@@ -3,7 +3,7 @@ import Decimal from 'decimal.js';
3
3
  import { Scope } from '@kamino-finance/scope-sdk';
4
4
  import { OraclePrices } from '@kamino-finance/scope-sdk/dist/@codegen/scope/accounts/OraclePrices';
5
5
  import { isNotNullPubkey } from './pubkey';
6
- import { parseTokenSymbol } from '../classes';
6
+ import { parseTokenSymbol, ReserveWithAddress } from '../classes';
7
7
  import { Reserve } from '../lib';
8
8
  import { batchFetch } from '@kamino-finance/kliquidity-sdk';
9
9
  import BN from 'bn.js';
@@ -43,12 +43,12 @@ export type ScopePriceRefreshConfig = {
43
43
  scopeConfigurations: [Address, Configuration][];
44
44
  };
45
45
 
46
- export function getTokenOracleDataSync(allOracleAccounts: AllOracleAccounts, reserves: Reserve[]) {
46
+ export function getTokenOracleDataSync(allOracleAccounts: AllOracleAccounts, reserves: ReserveWithAddress[]) {
47
47
  const tokenOracleDataForReserves: Array<[Reserve, TokenOracleData | undefined]> = [];
48
48
  const pythCache = new Map<Address, PythPrices>();
49
49
  const switchboardCache = new Map<Address, CandidatePrice>();
50
50
  const scopeCache = new Map<Address, OraclePrices>();
51
- for (const reserve of reserves) {
51
+ for (const { address, state: reserve } of reserves) {
52
52
  let currentBest: CandidatePrice | undefined = undefined;
53
53
  const oracle = {
54
54
  pythAddress: reserve.config.tokenInfo.pythConfiguration.price,
@@ -86,7 +86,10 @@ export function getTokenOracleDataSync(allOracleAccounts: AllOracleAccounts, res
86
86
  }
87
87
 
88
88
  if (!currentBest) {
89
- console.error(`No price found for reserve: ${parseTokenSymbol(reserve.config.tokenInfo.name)}`);
89
+ const reserveSymbol = parseTokenSymbol(reserve.config.tokenInfo.name);
90
+ console.error(
91
+ `No price found for reserve: ${reserveSymbol ?? 'unknown'} (${address}) in market: ${reserve.lendingMarket}`
92
+ );
90
93
  tokenOracleDataForReserves.push([reserve, undefined]);
91
94
  continue;
92
95
  }
@@ -105,10 +108,15 @@ export function getTokenOracleDataSync(allOracleAccounts: AllOracleAccounts, res
105
108
  // TODO: Add freshness of the latest price to match sc logic
106
109
  export async function getTokenOracleData(
107
110
  rpc: Rpc<GetMultipleAccountsApi>,
108
- reserves: Reserve[],
111
+ reserves: ReserveWithAddress[],
109
112
  oracleAccounts?: AllOracleAccounts
110
113
  ): Promise<Array<[Reserve, TokenOracleData | undefined]>> {
111
- const allOracleAccounts = oracleAccounts ?? (await getAllOracleAccounts(rpc, reserves));
114
+ const allOracleAccounts =
115
+ oracleAccounts ??
116
+ (await getAllOracleAccounts(
117
+ rpc,
118
+ reserves.map((r) => r.state)
119
+ ));
112
120
  return getTokenOracleDataSync(allOracleAccounts, reserves);
113
121
  }
114
122