@kamino-finance/klend-sdk 6.0.5-beta.2 → 6.0.5-beta.21

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.
Files changed (67) hide show
  1. package/dist/classes/action.d.ts +1 -1
  2. package/dist/classes/action.d.ts.map +1 -1
  3. package/dist/classes/action.js +32 -16
  4. package/dist/classes/action.js.map +1 -1
  5. package/dist/classes/manager.d.ts +29 -18
  6. package/dist/classes/manager.d.ts.map +1 -1
  7. package/dist/classes/manager.js +66 -49
  8. package/dist/classes/manager.js.map +1 -1
  9. package/dist/classes/market.d.ts +12 -11
  10. package/dist/classes/market.d.ts.map +1 -1
  11. package/dist/classes/market.js +77 -37
  12. package/dist/classes/market.js.map +1 -1
  13. package/dist/classes/obligation.d.ts +32 -2
  14. package/dist/classes/obligation.d.ts.map +1 -1
  15. package/dist/classes/obligation.js +150 -24
  16. package/dist/classes/obligation.js.map +1 -1
  17. package/dist/classes/vault.d.ts +5 -3
  18. package/dist/classes/vault.d.ts.map +1 -1
  19. package/dist/classes/vault.js +8 -6
  20. package/dist/classes/vault.js.map +1 -1
  21. package/dist/client_kamino_manager.d.ts.map +1 -1
  22. package/dist/client_kamino_manager.js +30 -22
  23. package/dist/client_kamino_manager.js.map +1 -1
  24. package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
  25. package/dist/lending_operations/repay_with_collateral_operations.js +36 -32
  26. package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
  27. package/dist/lending_operations/swap_collateral_operations.d.ts.map +1 -1
  28. package/dist/lending_operations/swap_collateral_operations.js +4 -4
  29. package/dist/lending_operations/swap_collateral_operations.js.map +1 -1
  30. package/dist/leverage/operations.d.ts +4 -3
  31. package/dist/leverage/operations.d.ts.map +1 -1
  32. package/dist/leverage/operations.js +186 -154
  33. package/dist/leverage/operations.js.map +1 -1
  34. package/dist/leverage/types.d.ts +1 -0
  35. package/dist/leverage/types.d.ts.map +1 -1
  36. package/dist/utils/managerTypes.d.ts +1 -2
  37. package/dist/utils/managerTypes.d.ts.map +1 -1
  38. package/dist/utils/managerTypes.js +9 -9
  39. package/dist/utils/managerTypes.js.map +1 -1
  40. package/dist/utils/obligations.d.ts +5 -0
  41. package/dist/utils/obligations.d.ts.map +1 -0
  42. package/dist/utils/obligations.js +53 -0
  43. package/dist/utils/obligations.js.map +1 -0
  44. package/dist/utils/oracle.d.ts +3 -3
  45. package/dist/utils/oracle.d.ts.map +1 -1
  46. package/dist/utils/oracle.js +2 -2
  47. package/dist/utils/oracle.js.map +1 -1
  48. package/dist/utils/pubkey.d.ts +1 -0
  49. package/dist/utils/pubkey.d.ts.map +1 -1
  50. package/dist/utils/pubkey.js +10 -0
  51. package/dist/utils/pubkey.js.map +1 -1
  52. package/package.json +3 -3
  53. package/src/classes/action.ts +32 -20
  54. package/src/classes/manager.ts +87 -54
  55. package/src/classes/market.ts +132 -52
  56. package/src/classes/obligation.ts +201 -36
  57. package/src/classes/vault.ts +17 -6
  58. package/src/client.ts +4 -4
  59. package/src/client_kamino_manager.ts +40 -35
  60. package/src/lending_operations/repay_with_collateral_operations.ts +76 -72
  61. package/src/lending_operations/swap_collateral_operations.ts +13 -11
  62. package/src/leverage/operations.ts +362 -328
  63. package/src/leverage/types.ts +1 -0
  64. package/src/utils/managerTypes.ts +1 -2
  65. package/src/utils/obligations.ts +69 -0
  66. package/src/utils/oracle.ts +5 -4
  67. package/src/utils/pubkey.ts +9 -0
@@ -22,6 +22,8 @@ import {
22
22
  CandidatePrice,
23
23
  PublicKeySet,
24
24
  DEPOSITS_LIMIT,
25
+ setOrAppend,
26
+ AllOracleAccounts,
25
27
  } from '../utils';
26
28
  import base58 from 'bs58';
27
29
  import { BN } from '@coral-xyz/anchor';
@@ -31,7 +33,7 @@ import { PROGRAM_ID } from '../idl_codegen/programId';
31
33
  import bs58 from 'bs58';
32
34
  import { OraclePrices, Scope, U16_MAX } from '@kamino-finance/scope-sdk';
33
35
  import { Fraction } from './fraction';
34
- import { chunks, KaminoPrices, MintToPriceMap } from '@kamino-finance/kliquidity-sdk';
36
+ import { batchFetch, chunks, KaminoPrices, MintToPriceMap } from '@kamino-finance/kliquidity-sdk';
35
37
  import { parseTokenSymbol, parseZeroPaddedUtf8 } from './utils';
36
38
  import SwitchboardProgram from '@switchboard-xyz/sbv2-lite';
37
39
  import { ObligationZP } from '../idl_codegen/zero_padding';
@@ -62,9 +64,7 @@ export class KaminoMarket {
62
64
 
63
65
  private readonly recentSlotDurationMs: number;
64
66
 
65
- // key = scope feed (oracle prices) pubkey
66
- // value = reserve pubkey
67
- private readonly reserveScopeFeeds: PubkeyHashMap<PublicKey, PublicKey>;
67
+ readonly scopeFeeds: PublicKeySet<PublicKey>;
68
68
 
69
69
  private constructor(
70
70
  connection: Connection,
@@ -74,6 +74,10 @@ export class KaminoMarket {
74
74
  recentSlotDurationMs: number,
75
75
  programId: PublicKey = PROGRAM_ID
76
76
  ) {
77
+ if (recentSlotDurationMs <= 0) {
78
+ throw new Error('Recent slot duration cannot be 0 or less');
79
+ }
80
+
77
81
  this.address = marketAddress;
78
82
  this.connection = connection;
79
83
  this.state = state;
@@ -81,10 +85,10 @@ export class KaminoMarket {
81
85
  this.reservesActive = getReservesActive(this.reserves);
82
86
  this.programId = programId;
83
87
  this.recentSlotDurationMs = recentSlotDurationMs;
84
- this.reserveScopeFeeds = new PubkeyHashMap(
88
+ this.scopeFeeds = new PublicKeySet(
85
89
  Array.from(this.reserves.values())
86
90
  .filter((r) => isNotNullPubkey(r.state.config.tokenInfo.scopeConfiguration.priceFeed))
87
- .map((r) => [r.address, r.state.config.tokenInfo.scopeConfiguration.priceFeed])
91
+ .map((r) => r.state.config.tokenInfo.scopeConfiguration.priceFeed)
88
92
  );
89
93
  }
90
94
 
@@ -95,7 +99,6 @@ export class KaminoMarket {
95
99
  * @param recentSlotDurationMs
96
100
  * @param programId
97
101
  * @param withReserves
98
- * @param setupLocalTest
99
102
  * @param withReserves
100
103
  */
101
104
  static async load(
@@ -111,10 +114,6 @@ export class KaminoMarket {
111
114
  return null;
112
115
  }
113
116
 
114
- if (recentSlotDurationMs <= 0) {
115
- throw new Error('Recent slot duration cannot be 0');
116
- }
117
-
118
117
  const reserves = withReserves
119
118
  ? await getReservesForMarket(marketAddress, connection, programId, recentSlotDurationMs)
120
119
  : new Map<PublicKey, KaminoReserve>();
@@ -133,6 +132,68 @@ export class KaminoMarket {
133
132
  return new KaminoMarket(connection, market, marketAddress.toString(), reserves, recentSlotDurationMs, programId);
134
133
  }
135
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
+
136
197
  async reload(): Promise<void> {
137
198
  const market = await LendingMarket.fetch(this.connection, this.getAddress(), this.programId);
138
199
  if (market === null) {
@@ -373,19 +434,19 @@ export class KaminoMarket {
373
434
  /**
374
435
  * @returns The max borrowable amount for leverage positions
375
436
  */
376
- async getMaxLeverageBorrowableAmount(
437
+ getMaxLeverageBorrowableAmount(
377
438
  collReserve: KaminoReserve,
378
439
  debtReserve: KaminoReserve,
379
440
  slot: number,
380
441
  requestElevationGroup: boolean,
381
442
  obligation?: KaminoObligation
382
- ): Promise<Decimal> {
443
+ ): Decimal {
383
444
  return obligation
384
445
  ? obligation.getMaxBorrowAmount(this, debtReserve.getLiquidityMint(), slot, requestElevationGroup)
385
446
  : debtReserve.getMaxBorrowAmountWithCollReserve(this, collReserve, slot);
386
447
  }
387
448
 
388
- async loadReserves() {
449
+ async loadReserves(oracleAccounts?: AllOracleAccounts) {
389
450
  const addresses = [...this.reserves.keys()];
390
451
  const reserveAccounts = await this.connection.getMultipleAccountsInfo(addresses, 'processed');
391
452
  const deserializedReserves = reserveAccounts.map((reserve, i) => {
@@ -399,7 +460,7 @@ export class KaminoMarket {
399
460
  }
400
461
  return reserveAccount;
401
462
  });
402
- const reservesAndOracles = await getTokenOracleData(this.connection, deserializedReserves);
463
+ const reservesAndOracles = await getTokenOracleData(this.connection, deserializedReserves, oracleAccounts);
403
464
  const kaminoReserves = new PubkeyHashMap<PublicKey, KaminoReserve>();
404
465
  reservesAndOracles.forEach(([reserve, oracle], index) => {
405
466
  if (!oracle) {
@@ -851,9 +912,13 @@ export class KaminoMarket {
851
912
  return finalObligations;
852
913
  }
853
914
 
854
- 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[]> {
855
920
  const [currentSlot, obligations] = await Promise.all([
856
- this.connection.getSlot(),
921
+ slot !== undefined ? Promise.resolve(slot) : this.connection.getSlot(),
857
922
  this.connection.getProgramAccounts(this.programId, {
858
923
  filters: [
859
924
  {
@@ -1211,12 +1276,16 @@ export class KaminoMarket {
1211
1276
  */
1212
1277
  async getReserveOraclePrices(scope: Scope): Promise<PubkeyHashMap<PublicKey, OraclePrices>> {
1213
1278
  const reserveOraclePrices: PubkeyHashMap<PublicKey, OraclePrices> = new PubkeyHashMap();
1214
- const oraclePrices = await scope.getMultipleOraclePrices(Array.from(this.reserveScopeFeeds.values()));
1215
- for (const [feed, oraclePricesAccount] of oraclePrices) {
1216
- const reserve = this.reserveScopeFeeds.get(feed);
1217
- if (reserve) {
1218
- reserveOraclePrices.set(reserve, oraclePricesAccount);
1219
- }
1279
+ const oraclePrices = await scope.getMultipleOraclePrices(this.scopeFeeds.toArray());
1280
+ const oraclePriceMap = new PubkeyHashMap<PublicKey, OraclePrices>();
1281
+ for (const [feed, account] of oraclePrices) {
1282
+ oraclePriceMap.set(feed, account);
1283
+ }
1284
+ for (const [reserveAddress, reserve] of this.reserves) {
1285
+ reserveOraclePrices.set(
1286
+ reserveAddress,
1287
+ oraclePriceMap.get(reserve.state.config.tokenInfo.scopeConfiguration.priceFeed)!
1288
+ );
1220
1289
  }
1221
1290
  return reserveOraclePrices;
1222
1291
  }
@@ -1224,8 +1293,10 @@ export class KaminoMarket {
1224
1293
  /**
1225
1294
  * Get all Scope prices used by all the market reserves
1226
1295
  */
1227
- async getAllScopePrices(scope: Scope): Promise<KaminoPrices> {
1228
- const allOraclePrices = await this.getReserveOraclePrices(scope);
1296
+ async getAllScopePrices(
1297
+ scope: Scope,
1298
+ allOraclePrices: PubkeyHashMap<PublicKey, OraclePrices>
1299
+ ): Promise<KaminoPrices> {
1229
1300
  const spot: MintToPriceMap = {};
1230
1301
  const twaps: MintToPriceMap = {};
1231
1302
  for (const reserve of this.reserves.values()) {
@@ -1234,7 +1305,7 @@ export class KaminoMarket {
1234
1305
  const oracle = reserve.state.config.tokenInfo.scopeConfiguration.priceFeed;
1235
1306
  const chain = reserve.state.config.tokenInfo.scopeConfiguration.priceChain;
1236
1307
  const twapChain = reserve.state.config.tokenInfo.scopeConfiguration.twapChain.filter((x) => x > 0);
1237
- const oraclePrices = allOraclePrices.get(reserve.address);
1308
+ const oraclePrices = allOraclePrices.get(oracle);
1238
1309
  if (oraclePrices && oracle && isNotNullPubkey(oracle) && chain && Scope.isScopeChainValid(chain)) {
1239
1310
  const spotPrice = await scope.getPriceFromChain(chain, oraclePrices);
1240
1311
  spot[tokenMint] = { price: spotPrice.price, name: tokenName };
@@ -1250,16 +1321,18 @@ export class KaminoMarket {
1250
1321
  /**
1251
1322
  * Get all Scope/Pyth/Switchboard prices used by all the market reserves
1252
1323
  */
1253
- async getAllPrices(): Promise<KlendPrices> {
1324
+ async getAllPrices(oracleAccounts?: AllOracleAccounts): Promise<KlendPrices> {
1254
1325
  const klendPrices: KlendPrices = {
1255
1326
  scope: { spot: {}, twap: {} },
1256
1327
  pyth: { spot: {}, twap: {} },
1257
1328
  switchboard: { spot: {}, twap: {} },
1258
1329
  };
1259
- const allOracleAccounts = await getAllOracleAccounts(
1260
- this.connection,
1261
- this.getReserves().map((x) => x.state)
1262
- );
1330
+ const allOracleAccounts =
1331
+ oracleAccounts ??
1332
+ (await getAllOracleAccounts(
1333
+ this.connection,
1334
+ this.getReserves().map((x) => x.state)
1335
+ ));
1263
1336
  const pythCache = new PubkeyHashMap<PublicKey, PythPrices>();
1264
1337
  const switchboardCache = new PubkeyHashMap<PublicKey, CandidatePrice>();
1265
1338
  const scopeCache = new PubkeyHashMap<PublicKey, OraclePrices>();
@@ -1471,7 +1544,8 @@ export async function getReservesForMarket(
1471
1544
  marketAddress: PublicKey,
1472
1545
  connection: Connection,
1473
1546
  programId: PublicKey,
1474
- recentSlotDurationMs: number
1547
+ recentSlotDurationMs: number,
1548
+ oracleAccounts?: AllOracleAccounts
1475
1549
  ): Promise<Map<PublicKey, KaminoReserve>> {
1476
1550
  const reserves = await connection.getProgramAccounts(programId, {
1477
1551
  filters: [
@@ -1499,7 +1573,7 @@ export async function getReservesForMarket(
1499
1573
  return reserveAccount;
1500
1574
  });
1501
1575
  const allBuffers = reserves.map((reserve) => reserve.account);
1502
- const reservesAndOracles = await getTokenOracleData(connection, deserializedReserves);
1576
+ const reservesAndOracles = await getTokenOracleData(connection, deserializedReserves, oracleAccounts);
1503
1577
  const reservesByAddress = new PubkeyHashMap<PublicKey, KaminoReserve>();
1504
1578
  reservesAndOracles.forEach(([reserve, oracle], index) => {
1505
1579
  if (!oracle) {
@@ -1522,7 +1596,8 @@ export async function getSingleReserve(
1522
1596
  reservePk: PublicKey,
1523
1597
  connection: Connection,
1524
1598
  recentSlotDurationMs: number,
1525
- accountData?: AccountInfo<Buffer>
1599
+ accountData?: AccountInfo<Buffer>,
1600
+ oracleAccounts?: AllOracleAccounts
1526
1601
  ): Promise<KaminoReserve> {
1527
1602
  const reserve = accountData ? accountData : await connection.getAccountInfo(reservePk);
1528
1603
 
@@ -1536,22 +1611,13 @@ export async function getSingleReserve(
1536
1611
  throw Error(`Could not parse reserve ${reservePk.toBase58()}`);
1537
1612
  }
1538
1613
 
1539
- const reservesAndOracles = await getTokenOracleData(connection, [reserveAccount]);
1614
+ const reservesAndOracles = await getTokenOracleData(connection, [reserveAccount], oracleAccounts);
1540
1615
  const [reserveState, oracle] = reservesAndOracles[0];
1541
1616
 
1542
1617
  if (!oracle) {
1543
1618
  throw Error(`Could not find oracle for ${parseTokenSymbol(reserveState.config.tokenInfo.name)} reserve`);
1544
1619
  }
1545
- const kaminoReserve = KaminoReserve.initialize(
1546
- reserve,
1547
- reservePk,
1548
- reserveState,
1549
- oracle,
1550
- connection,
1551
- recentSlotDurationMs
1552
- );
1553
-
1554
- return kaminoReserve;
1620
+ return KaminoReserve.initialize(reserve, reservePk, reserveState, oracle, connection, recentSlotDurationMs);
1555
1621
  }
1556
1622
 
1557
1623
  export function getReservesActive(reserves: Map<PublicKey, KaminoReserve>): Map<PublicKey, KaminoReserve> {
@@ -1564,8 +1630,11 @@ export function getReservesActive(reserves: Map<PublicKey, KaminoReserve>): Map<
1564
1630
  return reservesActive;
1565
1631
  }
1566
1632
 
1567
- export function getTokenIdsForScopeRefresh(kaminoMarket: KaminoMarket, reserves: PublicKey[]): number[] {
1568
- const tokenIds: number[] = [];
1633
+ export function getTokenIdsForScopeRefresh(
1634
+ kaminoMarket: KaminoMarket,
1635
+ reserves: PublicKey[]
1636
+ ): PubkeyHashMap<PublicKey, number[]> {
1637
+ const tokenIds = new PubkeyHashMap<PublicKey, number[]>();
1569
1638
 
1570
1639
  for (const reserveAddress of reserves) {
1571
1640
  const reserve = kaminoMarket.getReserveByAddress(reserveAddress);
@@ -1573,22 +1642,33 @@ export function getTokenIdsForScopeRefresh(kaminoMarket: KaminoMarket, reserves:
1573
1642
  throw new Error(`Reserve not found for reserve ${reserveAddress.toBase58()}`);
1574
1643
  }
1575
1644
 
1576
- if (!reserve.state.config.tokenInfo.scopeConfiguration.priceFeed.equals(PublicKey.default)) {
1645
+ const { scopeConfiguration } = reserve.state.config.tokenInfo;
1646
+ if (!scopeConfiguration.priceFeed.equals(PublicKey.default)) {
1577
1647
  let x = 0;
1578
1648
 
1579
- while (reserve.state.config.tokenInfo.scopeConfiguration.priceChain[x] !== U16_MAX) {
1580
- tokenIds.push(reserve.state.config.tokenInfo.scopeConfiguration.priceChain[x]);
1649
+ while (scopeConfiguration.priceChain[x] !== U16_MAX) {
1650
+ setOrAppend(tokenIds, scopeConfiguration.priceFeed, scopeConfiguration.priceChain[x]);
1581
1651
  x++;
1582
1652
  }
1583
1653
 
1584
1654
  x = 0;
1585
- while (reserve.state.config.tokenInfo.scopeConfiguration.twapChain[x] !== U16_MAX) {
1586
- tokenIds.push(reserve.state.config.tokenInfo.scopeConfiguration.twapChain[x]);
1655
+ while (scopeConfiguration.twapChain[x] !== U16_MAX) {
1656
+ setOrAppend(tokenIds, scopeConfiguration.priceFeed, scopeConfiguration.twapChain[x]);
1587
1657
  x++;
1588
1658
  }
1589
1659
  }
1590
1660
  }
1591
1661
 
1662
+ //TODO: remove code below
1663
+ // - currently Scope program does not allow multiple refreshPricesList instructions in 1 ix
1664
+ // - temporary fix is to only refresh one scope feed at this time
1665
+ const firstFeed = tokenIds.entries().next();
1666
+ tokenIds.clear();
1667
+ if (!firstFeed.done) {
1668
+ const [key, value] = firstFeed.value;
1669
+ tokenIds.set(key, value);
1670
+ }
1671
+
1592
1672
  return tokenIds;
1593
1673
  }
1594
1674