@kamino-finance/klend-sdk 5.10.6 → 5.10.7

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 (56) hide show
  1. package/dist/classes/action.d.ts +7 -3
  2. package/dist/classes/action.d.ts.map +1 -1
  3. package/dist/classes/action.js +28 -13
  4. package/dist/classes/action.js.map +1 -1
  5. package/dist/classes/market.d.ts +11 -0
  6. package/dist/classes/market.d.ts.map +1 -1
  7. package/dist/classes/market.js +26 -0
  8. package/dist/classes/market.js.map +1 -1
  9. package/dist/classes/obligation.d.ts +14 -0
  10. package/dist/classes/obligation.d.ts.map +1 -1
  11. package/dist/classes/obligation.js +25 -0
  12. package/dist/classes/obligation.js.map +1 -1
  13. package/dist/lending_operations/index.d.ts +1 -0
  14. package/dist/lending_operations/index.d.ts.map +1 -1
  15. package/dist/lending_operations/index.js +1 -0
  16. package/dist/lending_operations/index.js.map +1 -1
  17. package/dist/lending_operations/repay_with_collateral_operations.d.ts +5 -5
  18. package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
  19. package/dist/lending_operations/repay_with_collateral_operations.js +3 -2
  20. package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
  21. package/dist/lending_operations/swap_collateral_operations.d.ts +102 -0
  22. package/dist/lending_operations/swap_collateral_operations.d.ts.map +1 -0
  23. package/dist/lending_operations/swap_collateral_operations.js +306 -0
  24. package/dist/lending_operations/swap_collateral_operations.js.map +1 -0
  25. package/dist/leverage/operations.d.ts +2 -2
  26. package/dist/leverage/operations.d.ts.map +1 -1
  27. package/dist/leverage/operations.js +4 -0
  28. package/dist/leverage/operations.js.map +1 -1
  29. package/dist/leverage/types.d.ts +5 -5
  30. package/dist/leverage/types.d.ts.map +1 -1
  31. package/dist/leverage/utils.d.ts +5 -5
  32. package/dist/leverage/utils.d.ts.map +1 -1
  33. package/dist/leverage/utils.js.map +1 -1
  34. package/dist/utils/constants.d.ts +1 -0
  35. package/dist/utils/constants.d.ts.map +1 -1
  36. package/dist/utils/constants.js +2 -1
  37. package/dist/utils/constants.js.map +1 -1
  38. package/dist/utils/pubkey.d.ts +3 -0
  39. package/dist/utils/pubkey.d.ts.map +1 -1
  40. package/dist/utils/pubkey.js +16 -2
  41. package/dist/utils/pubkey.js.map +1 -1
  42. package/dist/utils/seeds.d.ts +1 -1
  43. package/dist/utils/seeds.js +1 -1
  44. package/package.json +4 -4
  45. package/src/classes/action.ts +37 -19
  46. package/src/classes/market.ts +35 -1
  47. package/src/classes/obligation.ts +75 -0
  48. package/src/lending_operations/index.ts +1 -0
  49. package/src/lending_operations/repay_with_collateral_operations.ts +10 -9
  50. package/src/lending_operations/swap_collateral_operations.ts +586 -0
  51. package/src/leverage/operations.ts +14 -10
  52. package/src/leverage/types.ts +6 -6
  53. package/src/leverage/utils.ts +8 -8
  54. package/src/utils/constants.ts +2 -0
  55. package/src/utils/pubkey.ts +19 -2
  56. package/src/utils/seeds.ts +1 -1
@@ -411,7 +411,7 @@ export class KaminoAction {
411
411
  obligation: KaminoObligation | ObligationType,
412
412
  extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
413
413
  includeAtaIxns: boolean = true, // if true it includes create and close wsol and token atas,
414
- requestElevationGroup: boolean = false,
414
+ requestElevationGroup: boolean = false, // to be requested *before* the deposit
415
415
  includeUserMetadata: boolean = true, // if true it includes user metadata
416
416
  referrer: PublicKey = PublicKey.default,
417
417
  currentSlot: number = 0,
@@ -855,11 +855,17 @@ export class KaminoAction {
855
855
  obligation: KaminoObligation | ObligationType,
856
856
  extraComputeBudget: number = 1_000_000, // if > 0 then adds the ixn
857
857
  includeAtaIxns: boolean = true, // if true it includes create and close wsol and token atas,
858
- requestElevationGroup: boolean = false,
858
+ requestElevationGroup: boolean = false, // to be requested *after* the withdraw
859
859
  includeUserMetadata: boolean = true, // if true it includes user metadata
860
860
  referrer: PublicKey = PublicKey.default,
861
861
  currentSlot: number = 0,
862
- scopeRefresh: ScopeRefresh = { includeScopeRefresh: false, scopeFeed: 'hubble' }
862
+ scopeRefresh: ScopeRefresh | undefined = undefined,
863
+ overrideElevationGroupRequest?: number,
864
+ // Optional customizations which may be needed if the obligation was mutated by some previous ixn.
865
+ obligationCustomizations?: {
866
+ // Any newly-added deposit reserves.
867
+ addedDepositReserves?: PublicKey[];
868
+ }
863
869
  ) {
864
870
  const axn = await KaminoAction.initialize(
865
871
  'withdraw',
@@ -877,6 +883,8 @@ export class KaminoAction {
877
883
  axn.addComputeBudgetIxn(extraComputeBudget);
878
884
  }
879
885
 
886
+ axn.depositReserves.push(...(obligationCustomizations?.addedDepositReserves || []));
887
+
880
888
  const allReserves = new PublicKeySet<PublicKey>([
881
889
  ...axn.depositReserves,
882
890
  ...axn.borrowReserves,
@@ -884,7 +892,7 @@ export class KaminoAction {
884
892
  ]).toArray();
885
893
  const tokenIds = axn.getTokenIdsForScopeRefresh(kaminoMarket, allReserves);
886
894
 
887
- if (tokenIds.length > 0 && scopeRefresh.includeScopeRefresh) {
895
+ if (tokenIds.length > 0 && scopeRefresh && scopeRefresh.includeScopeRefresh) {
888
896
  await axn.addScopeRefreshIxs(tokenIds, scopeRefresh.scopeFeed);
889
897
  }
890
898
 
@@ -893,7 +901,9 @@ export class KaminoAction {
893
901
  includeAtaIxns,
894
902
  requestElevationGroup,
895
903
  includeUserMetadata,
896
- addInitObligationForFarm
904
+ addInitObligationForFarm,
905
+ false,
906
+ overrideElevationGroupRequest
897
907
  );
898
908
  await axn.addWithdrawIx();
899
909
  axn.addRefreshFarmsCleanupTxnIxsToCleanupIxs();
@@ -1827,6 +1837,16 @@ export class KaminoAction {
1827
1837
  this.addRefreshReserveIxs(allReservesExcludingCurrent, addAsSupportIx);
1828
1838
  this.addRefreshReserveIxs(currentReserveAddresses.toArray(), addAsSupportIx);
1829
1839
  this.addRefreshObligationIx(addAsSupportIx);
1840
+ } else if (
1841
+ action === 'withdraw' &&
1842
+ overrideElevationGroupRequest !== undefined
1843
+ // Note: contrary to the 'deposit' case above, we allow requesting the same group as in the [stale, cached] obligation state, since our current use-case is "deposit X, withdraw Y"
1844
+ ) {
1845
+ console.log('Withdraw: Requesting elevation group', overrideElevationGroupRequest);
1846
+ // Skip the withdrawn reserve if we are in the process of closing it:
1847
+ const skipReserveIfClosing = this.amount.eq(new BN(U64_MAX)) ? [this.reserve.address] : [];
1848
+ this.addRefreshObligationIx('cleanup', skipReserveIfClosing);
1849
+ this.addRequestElevationIx(overrideElevationGroupRequest, 'cleanup', skipReserveIfClosing);
1830
1850
  }
1831
1851
  }
1832
1852
 
@@ -1996,7 +2016,7 @@ export class KaminoAction {
1996
2016
  });
1997
2017
  }
1998
2018
 
1999
- private addRefreshObligationIx(addAsSupportIx: AuxiliaryIx = 'setup', borrowReservesToSkip: PublicKey[] = []) {
2019
+ private addRefreshObligationIx(addAsSupportIx: AuxiliaryIx = 'setup', skipReserves: PublicKey[] = []) {
2000
2020
  const marketAddress = this.kaminoMarket.getAddress();
2001
2021
  const obligationPda = this.getObligationPda();
2002
2022
  const refreshObligationIx = refreshObligation(
@@ -2007,15 +2027,16 @@ export class KaminoAction {
2007
2027
  this.kaminoMarket.programId
2008
2028
  );
2009
2029
 
2010
- const depositReservesList = this.getAdditionalDepositReservesList();
2030
+ const skipReservesSet = new PublicKeySet(skipReserves);
2011
2031
 
2032
+ const depositReservesList = this.getAdditionalDepositReservesList().filter(
2033
+ (reserve) => !skipReservesSet.contains(reserve)
2034
+ );
2012
2035
  const depositReserveAccountMetas = depositReservesList.map((reserve) => {
2013
2036
  return { pubkey: reserve, isSigner: false, isWritable: true };
2014
2037
  });
2015
2038
 
2016
- const skipBorrowsSet = new PublicKeySet(borrowReservesToSkip);
2017
- const borrowReservesList = this.borrowReserves.filter((reserve) => !skipBorrowsSet.contains(reserve));
2018
-
2039
+ const borrowReservesList = this.borrowReserves.filter((reserve) => !skipReservesSet.contains(reserve));
2019
2040
  const borrowReserveAccountMetas = borrowReservesList.map((reserve) => {
2020
2041
  return { pubkey: reserve, isSigner: false, isWritable: true };
2021
2042
  });
@@ -2048,11 +2069,7 @@ export class KaminoAction {
2048
2069
  }
2049
2070
  }
2050
2071
 
2051
- private addRequestElevationIx(
2052
- elevationGroup: number,
2053
- addAsSupportIx: AuxiliaryIx,
2054
- skipBorrowReserves: PublicKey[] = []
2055
- ) {
2072
+ private addRequestElevationIx(elevationGroup: number, addAsSupportIx: AuxiliaryIx, skipReserves: PublicKey[] = []) {
2056
2073
  const obligationPda = this.getObligationPda();
2057
2074
  const args: RequestElevationGroupArgs = {
2058
2075
  elevationGroup,
@@ -2065,15 +2082,16 @@ export class KaminoAction {
2065
2082
 
2066
2083
  const requestElevationGroupIx = requestElevationGroup(args, accounts, this.kaminoMarket.programId);
2067
2084
 
2068
- const depositReservesList = this.getAdditionalDepositReservesList();
2085
+ const skipReservesSet = new PublicKeySet<PublicKey>(skipReserves);
2069
2086
 
2087
+ const depositReservesList = this.getAdditionalDepositReservesList().filter(
2088
+ (reserve) => !skipReservesSet.contains(reserve)
2089
+ );
2070
2090
  const depositReserveAccountMetas = depositReservesList.map((reserve) => {
2071
2091
  return { pubkey: reserve, isSigner: false, isWritable: true };
2072
2092
  });
2073
2093
 
2074
- const skipBorrowReservesSet = new PublicKeySet<PublicKey>(skipBorrowReserves);
2075
- const borrowReservesList = this.borrowReserves.filter((reserve) => !skipBorrowReservesSet.contains(reserve));
2076
-
2094
+ const borrowReservesList = this.borrowReserves.filter((reserve) => !skipReservesSet.contains(reserve));
2077
2095
  const borrowReserveAccountMetas = borrowReservesList.map((reserve) => {
2078
2096
  return { pubkey: reserve, isSigner: false, isWritable: true };
2079
2097
  });
@@ -169,6 +169,26 @@ export class KaminoMarket {
169
169
  return this.state.elevationGroups[elevationGroup - 1];
170
170
  }
171
171
 
172
+ /**
173
+ * Returns this market's elevation group of the given ID, or `null` for the default group `0`, or throws an error
174
+ * (including the given description) if the requested group does not exist.
175
+ */
176
+ getExistingElevationGroup(
177
+ elevationGroupId: number,
178
+ description: string = 'Requested'
179
+ ): ElevationGroupDescription | null {
180
+ if (elevationGroupId === 0) {
181
+ return null;
182
+ }
183
+ const elevationGroup = this.getMarketElevationGroupDescriptions().find(
184
+ (candidate) => candidate.elevationGroup === elevationGroupId
185
+ );
186
+ if (elevationGroup === undefined) {
187
+ throw new Error(`${description} elevation group ${elevationGroupId} not found in market ${this.getAddress()}`);
188
+ }
189
+ return elevationGroup;
190
+ }
191
+
172
192
  getMinNetValueObligation(): Decimal {
173
193
  return new Fraction(this.state.minNetValueInObligationSf).toDecimal();
174
194
  }
@@ -399,7 +419,7 @@ export class KaminoMarket {
399
419
  return this.reserves.get(address);
400
420
  }
401
421
 
402
- getReserveByMint(address: PublicKey) {
422
+ getReserveByMint(address: PublicKey): KaminoReserve | undefined {
403
423
  for (const reserve of this.reserves.values()) {
404
424
  if (reserve.getLiquidityMint().equals(address)) {
405
425
  return reserve;
@@ -408,6 +428,18 @@ export class KaminoMarket {
408
428
  return undefined;
409
429
  }
410
430
 
431
+ /**
432
+ * Returns this market's reserve of the given mint address, or throws an error (including the given description) if
433
+ * such reserve does not exist.
434
+ */
435
+ getExistingReserveByMint(address: PublicKey, description: string = 'Requested'): KaminoReserve {
436
+ const reserve = this.getReserveByMint(address);
437
+ if (!reserve) {
438
+ throw new Error(`${description} reserve with mint ${address} not found in market ${this.getAddress()}`);
439
+ }
440
+ return reserve;
441
+ }
442
+
411
443
  getReserveBySymbol(symbol: string) {
412
444
  for (const reserve of this.reserves.values()) {
413
445
  if (reserve.symbol === symbol) {
@@ -1297,6 +1329,7 @@ export class KaminoMarket {
1297
1329
  debtReserve: elevationGroup.debtReserve,
1298
1330
  debtLiquidityMint: PublicKey.default,
1299
1331
  elevationGroup: elevationGroup.id,
1332
+ maxReservesAsCollateral: elevationGroup.maxReservesAsCollateral,
1300
1333
  });
1301
1334
  }
1302
1335
 
@@ -1392,6 +1425,7 @@ export type ElevationGroupDescription = {
1392
1425
  debtReserve: PublicKey;
1393
1426
  debtLiquidityMint: PublicKey;
1394
1427
  elevationGroup: number;
1428
+ maxReservesAsCollateral: number;
1395
1429
  };
1396
1430
 
1397
1431
  export type KlendPrices = {
@@ -262,6 +262,11 @@ export class KaminoObligation {
262
262
  return undefined;
263
263
  }
264
264
 
265
+ getBorrowAmountByReserve(reserve: KaminoReserve): Decimal {
266
+ const amountLamports = this.getBorrowByMint(reserve.getLiquidityMint())?.amount ?? new Decimal(0);
267
+ return amountLamports.div(reserve.getMintFactor());
268
+ }
269
+
265
270
  getDepositByMint(mint: PublicKey): Position | undefined {
266
271
  for (const value of this.deposits.values()) {
267
272
  if (value.mintAddress.equals(mint)) {
@@ -271,6 +276,11 @@ export class KaminoObligation {
271
276
  return undefined;
272
277
  }
273
278
 
279
+ getDepositAmountByReserve(reserve: KaminoReserve): Decimal {
280
+ const amountLamports = this.getDepositByMint(reserve.getLiquidityMint())?.amount ?? new Decimal(0);
281
+ return amountLamports.div(reserve.getMintFactor());
282
+ }
283
+
274
284
  /**
275
285
  *
276
286
  * @returns Market value of the borrow in the specified obligation liquidity/borrow asset (USD) (no borrow factor weighting)
@@ -652,6 +662,71 @@ export class KaminoObligation {
652
662
  };
653
663
  }
654
664
 
665
+ /**
666
+ * Calculates the stats of the obligation after a hypothetical collateral swap.
667
+ */
668
+ getPostSwapCollObligationStats(params: {
669
+ withdrawAmountLamports: Decimal;
670
+ withdrawReserveAddress: PublicKey;
671
+ depositAmountLamports: Decimal;
672
+ depositReserveAddress: PublicKey;
673
+ newElevationGroup: number;
674
+ market: KaminoMarket;
675
+ slot: number;
676
+ }): ObligationStats {
677
+ const {
678
+ withdrawAmountLamports,
679
+ withdrawReserveAddress,
680
+ depositAmountLamports,
681
+ depositReserveAddress,
682
+ newElevationGroup,
683
+ market,
684
+ slot,
685
+ } = params;
686
+
687
+ const additionalReserves = [withdrawReserveAddress, depositReserveAddress].filter(
688
+ (reserveAddress) => !market.isReserveInObligation(this, reserveAddress)
689
+ );
690
+
691
+ const { collateralExchangeRates } = KaminoObligation.getRatesForObligation(
692
+ market,
693
+ this.state,
694
+ slot,
695
+ additionalReserves
696
+ );
697
+
698
+ let newObligationDeposits = this.state.deposits;
699
+ newObligationDeposits = this.simulateDepositChange(
700
+ newObligationDeposits,
701
+ withdrawAmountLamports.neg().toNumber(),
702
+ withdrawReserveAddress,
703
+ collateralExchangeRates
704
+ );
705
+ newObligationDeposits = this.simulateDepositChange(
706
+ newObligationDeposits,
707
+ depositAmountLamports.toNumber(),
708
+ depositReserveAddress,
709
+ collateralExchangeRates
710
+ );
711
+
712
+ const { refreshedStats } = this.calculatePositions(
713
+ market,
714
+ newObligationDeposits,
715
+ this.state.borrows,
716
+ newElevationGroup,
717
+ collateralExchangeRates,
718
+ null
719
+ );
720
+
721
+ const newStats = refreshedStats;
722
+ newStats.netAccountValue = newStats.userTotalDeposit.minus(newStats.userTotalBorrow);
723
+ newStats.loanToValue = valueOrZero(
724
+ newStats.userTotalBorrowBorrowFactorAdjusted.dividedBy(newStats.userTotalCollateralDeposit)
725
+ );
726
+ newStats.leverage = valueOrZero(newStats.userTotalDeposit.dividedBy(newStats.netAccountValue));
727
+ return newStats;
728
+ }
729
+
655
730
  estimateObligationInterestRate = (
656
731
  market: KaminoMarket,
657
732
  reserve: KaminoReserve,
@@ -1,2 +1,3 @@
1
1
  export * from './repay_with_collateral_operations';
2
+ export * from './swap_collateral_operations';
2
3
  export * from './repay_with_collateral_calcs';
@@ -3,8 +3,8 @@ import {
3
3
  getFlashLoanInstructions,
4
4
  SwapInputs,
5
5
  SwapQuote,
6
- SwapQuoteIxs,
7
- SwapQuoteIxsProvider,
6
+ SwapIxs,
7
+ SwapIxsProvider,
8
8
  SwapQuoteProvider,
9
9
  } from '../leverage';
10
10
  import {
@@ -23,10 +23,10 @@ export type RepayWithCollIxsResponse<QuoteResponse> = {
23
23
  ixs: TransactionInstruction[];
24
24
  lookupTables: AddressLookupTableAccount[];
25
25
  swapInputs: SwapInputs;
26
- initialInputs: InitialInputs<QuoteResponse>;
26
+ initialInputs: RepayWithCollInitialInputs<QuoteResponse>;
27
27
  };
28
28
 
29
- export type InitialInputs<QuoteResponse> = {
29
+ export type RepayWithCollInitialInputs<QuoteResponse> = {
30
30
  debtRepayAmountLamports: Decimal;
31
31
  flashRepayAmountLamports: Decimal;
32
32
  /**
@@ -69,15 +69,15 @@ export async function getRepayWithCollSwapInputs<QuoteResponse>({
69
69
  scopeRefresh,
70
70
  }: RepayWithCollSwapInputsProps<QuoteResponse>): Promise<{
71
71
  swapInputs: SwapInputs;
72
- initialInputs: InitialInputs<QuoteResponse>;
72
+ initialInputs: RepayWithCollInitialInputs<QuoteResponse>;
73
73
  }> {
74
74
  const collReserve = kaminoMarket.getReserveByMint(collTokenMint);
75
75
  const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint);
76
76
  if (!collReserve) {
77
- throw new Error(`Collateral reserve with mint ${collReserve} not found in market ${kaminoMarket.getAddress()}`);
77
+ throw new Error(`Collateral reserve with mint ${collTokenMint} not found in market ${kaminoMarket.getAddress()}`);
78
78
  }
79
79
  if (!debtReserve) {
80
- throw new Error(`Debt reserve with mint ${debtReserve} not found in market ${kaminoMarket.getAddress()}`);
80
+ throw new Error(`Debt reserve with mint ${debtTokenMint} not found in market ${kaminoMarket.getAddress()}`);
81
81
  }
82
82
 
83
83
  const {
@@ -172,7 +172,7 @@ export async function getRepayWithCollSwapInputs<QuoteResponse>({
172
172
  }
173
173
 
174
174
  interface RepayWithCollIxsProps<QuoteResponse> extends RepayWithCollSwapInputsProps<QuoteResponse> {
175
- swapper: SwapQuoteIxsProvider<QuoteResponse>;
175
+ swapper: SwapIxsProvider<QuoteResponse>;
176
176
  logger?: (msg: string, ...extra: any[]) => void;
177
177
  }
178
178
 
@@ -263,7 +263,7 @@ async function buildRepayWithCollateralIxs(
263
263
  currentSlot: number,
264
264
  budgetAndPriorityFeeIxs: TransactionInstruction[] | undefined,
265
265
  scopeRefresh: ScopeRefresh | undefined,
266
- swapQuoteIxs: SwapQuoteIxs,
266
+ swapQuoteIxs: SwapIxs,
267
267
  isClosingPosition: boolean,
268
268
  debtRepayAmountLamports: Decimal,
269
269
  collWithdrawLamports: Decimal
@@ -288,6 +288,7 @@ async function buildRepayWithCollateralIxs(
288
288
  reserve: debtReserve,
289
289
  amountLamports: debtRepayAmountLamports,
290
290
  destinationAta: debtTokenAta,
291
+ // TODO(referrals): once we support referrals, we will have to replace the placeholder args below:
291
292
  referrerAccount: market.programId,
292
293
  referrerTokenState: market.programId,
293
294
  programId: market.programId,