@kamino-finance/klend-sdk 5.11.2-beta.0 → 5.11.3-beta.0

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 (73) hide show
  1. package/dist/classes/action.d.ts +25 -35
  2. package/dist/classes/action.d.ts.map +1 -1
  3. package/dist/classes/action.js +92 -388
  4. package/dist/classes/action.js.map +1 -1
  5. package/dist/classes/lut_utils.d.ts +29 -0
  6. package/dist/classes/lut_utils.d.ts.map +1 -0
  7. package/dist/classes/lut_utils.js +62 -0
  8. package/dist/classes/lut_utils.js.map +1 -0
  9. package/dist/classes/manager.d.ts +1 -1
  10. package/dist/classes/manager.js +1 -1
  11. package/dist/classes/market.d.ts +3 -3
  12. package/dist/classes/market.d.ts.map +1 -1
  13. package/dist/classes/market.js +30 -16
  14. package/dist/classes/market.js.map +1 -1
  15. package/dist/classes/obligation.d.ts +3 -1
  16. package/dist/classes/obligation.d.ts.map +1 -1
  17. package/dist/classes/obligation.js +6 -1
  18. package/dist/classes/obligation.js.map +1 -1
  19. package/dist/classes/vault.js +6 -6
  20. package/dist/classes/vault.js.map +1 -1
  21. package/dist/lending_operations/repay_with_collateral_calcs.d.ts.map +1 -1
  22. package/dist/lending_operations/repay_with_collateral_calcs.js +5 -9
  23. package/dist/lending_operations/repay_with_collateral_calcs.js.map +1 -1
  24. package/dist/lending_operations/repay_with_collateral_operations.d.ts +4 -9
  25. package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
  26. package/dist/lending_operations/repay_with_collateral_operations.js +10 -33
  27. package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
  28. package/dist/lending_operations/swap_collateral_operations.d.ts +2 -2
  29. package/dist/lending_operations/swap_collateral_operations.d.ts.map +1 -1
  30. package/dist/lending_operations/swap_collateral_operations.js +11 -6
  31. package/dist/lending_operations/swap_collateral_operations.js.map +1 -1
  32. package/dist/leverage/operations.d.ts +9 -7
  33. package/dist/leverage/operations.d.ts.map +1 -1
  34. package/dist/leverage/operations.js +78 -66
  35. package/dist/leverage/operations.js.map +1 -1
  36. package/dist/leverage/types.d.ts +4 -4
  37. package/dist/leverage/types.d.ts.map +1 -1
  38. package/dist/utils/ObligationType.d.ts +1 -1
  39. package/dist/utils/ObligationType.d.ts.map +1 -1
  40. package/dist/utils/lookupTable.d.ts +0 -27
  41. package/dist/utils/lookupTable.d.ts.map +1 -1
  42. package/dist/utils/lookupTable.js +0 -58
  43. package/dist/utils/lookupTable.js.map +1 -1
  44. package/dist/utils/managerTypes.d.ts.map +1 -1
  45. package/dist/utils/managerTypes.js +7 -52
  46. package/dist/utils/managerTypes.js.map +1 -1
  47. package/dist/utils/oracle.d.ts +3 -3
  48. package/dist/utils/oracle.d.ts.map +1 -1
  49. package/dist/utils/seeds.d.ts +1 -11
  50. package/dist/utils/seeds.d.ts.map +1 -1
  51. package/dist/utils/seeds.js +3 -13
  52. package/dist/utils/seeds.js.map +1 -1
  53. package/dist/utils/userMetadata.js +6 -6
  54. package/dist/utils/userMetadata.js.map +1 -1
  55. package/package.json +2 -2
  56. package/src/classes/action.ts +116 -532
  57. package/src/classes/lut_utils.ts +63 -0
  58. package/src/classes/manager.ts +1 -1
  59. package/src/classes/market.ts +34 -25
  60. package/src/classes/obligation.ts +7 -1
  61. package/src/classes/vault.ts +1 -1
  62. package/src/client.ts +8 -3
  63. package/src/lending_operations/repay_with_collateral_calcs.ts +5 -14
  64. package/src/lending_operations/repay_with_collateral_operations.ts +33 -72
  65. package/src/lending_operations/swap_collateral_operations.ts +19 -7
  66. package/src/leverage/operations.ts +114 -66
  67. package/src/leverage/types.ts +4 -4
  68. package/src/utils/ObligationType.ts +1 -1
  69. package/src/utils/lookupTable.ts +0 -62
  70. package/src/utils/managerTypes.ts +10 -52
  71. package/src/utils/oracle.ts +2 -2
  72. package/src/utils/seeds.ts +4 -14
  73. package/src/utils/userMetadata.ts +14 -14
@@ -0,0 +1,63 @@
1
+ import { AddressLookupTableProgram, Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
2
+
3
+ /**
4
+ * This method retuns an instruction that creates a lookup table, alongside the pubkey of the lookup table
5
+ * @param payer - the owner of the lookup table
6
+ * @param slot - the current slot
7
+ * @returns - the instruction to create the lookup table and its address
8
+ */
9
+ export function initLookupTableIx(payer: PublicKey, slot: number): [TransactionInstruction, PublicKey] {
10
+ const [ixn, address] = AddressLookupTableProgram.createLookupTable({
11
+ authority: payer,
12
+ payer,
13
+ recentSlot: slot,
14
+ });
15
+
16
+ return [ixn, address];
17
+ }
18
+
19
+ /**
20
+ * This method retuns an instruction that deactivates a lookup table, which is needed to close it
21
+ * @param payer - the owner of the lookup table
22
+ * @param lookupTable - the lookup table to deactivate
23
+ * @returns - the instruction to deactivate the lookup table
24
+ */
25
+ export function deactivateLookupTableIx(payer: PublicKey, lookupTable: PublicKey): TransactionInstruction {
26
+ const ixn = AddressLookupTableProgram.deactivateLookupTable({
27
+ authority: payer,
28
+ lookupTable: lookupTable,
29
+ });
30
+
31
+ return ixn;
32
+ }
33
+
34
+ /**
35
+ * This method returns an instruction that closes a lookup table. That lookup table needs to be disabled at least 500 blocks before closing it.
36
+ * @param payer - the owner of the lookup table
37
+ * @param lookupTable - the lookup table to close
38
+ * @returns - the instruction to close the lookup table
39
+ */
40
+ /// this require the LUT to be deactivated at least 500 blocks before
41
+ export function closeLookupTableIx(payer: PublicKey, lookupTable: PublicKey): TransactionInstruction {
42
+ const ixn = AddressLookupTableProgram.closeLookupTable({
43
+ authority: payer,
44
+ recipient: payer,
45
+ lookupTable: lookupTable,
46
+ });
47
+
48
+ return ixn;
49
+ }
50
+
51
+ /**
52
+ * Returns the accounts in a lookup table
53
+ * @param lookupTable - lookup table to get the accounts from
54
+ * @returns - an array of accounts in the lookup table
55
+ */
56
+ export async function getAccountsInLUT(connection: Connection, lookupTable: PublicKey): Promise<PublicKey[]> {
57
+ const lutState = await connection.getAddressLookupTable(lookupTable);
58
+ if (!lutState || !lutState.value) {
59
+ throw new Error(`Lookup table ${lookupTable} not found`);
60
+ }
61
+
62
+ return lutState.value.state.addresses;
63
+ }
@@ -949,7 +949,7 @@ export class KaminoManager {
949
949
  }
950
950
 
951
951
  /**
952
- * This retruns an array of scope oracle configs to be used to set the scope price and twap oracles for a reserve
952
+ * This returns an array of scope oracle configs to be used to set the scope price and twap oracles for a reserve
953
953
  * @param feed - scope feed to fetch prices from
954
954
  * @param cluster - cluster to fetch from, this should be left unchanged unless working on devnet or locally
955
955
  * @returns - an array of scope oracle configs
@@ -29,7 +29,7 @@ import Decimal from 'decimal.js';
29
29
  import { FarmState } from '@kamino-finance/farms-sdk';
30
30
  import { PROGRAM_ID } from '../idl_codegen/programId';
31
31
  import bs58 from 'bs58';
32
- import { OraclePrices, Scope } from '@kamino-finance/scope-sdk';
32
+ import { OraclePrices, Scope, U16_MAX } from '@kamino-finance/scope-sdk';
33
33
  import { Fraction } from './fraction';
34
34
  import { chunks, KaminoPrices, MintToPriceMap } from '@kamino-finance/kliquidity-sdk';
35
35
  import { parseTokenSymbol, parseZeroPaddedUtf8 } from './utils';
@@ -59,8 +59,6 @@ export class KaminoMarket {
59
59
 
60
60
  readonly programId: PublicKey;
61
61
 
62
- scope: Scope;
63
-
64
62
  private readonly recentSlotDurationMs: number;
65
63
 
66
64
  private constructor(
@@ -68,7 +66,6 @@ export class KaminoMarket {
68
66
  state: LendingMarket,
69
67
  marketAddress: string,
70
68
  reserves: Map<PublicKey, KaminoReserve>,
71
- scope: Scope,
72
69
  recentSlotDurationMs: number,
73
70
  programId: PublicKey = PROGRAM_ID
74
71
  ) {
@@ -78,7 +75,6 @@ export class KaminoMarket {
78
75
  this.reserves = reserves;
79
76
  this.reservesActive = getReservesActive(this.reserves);
80
77
  this.programId = programId;
81
- this.scope = scope;
82
78
  this.recentSlotDurationMs = recentSlotDurationMs;
83
79
  }
84
80
 
@@ -97,7 +93,6 @@ export class KaminoMarket {
97
93
  marketAddress: PublicKey,
98
94
  recentSlotDurationMs: number,
99
95
  programId: PublicKey = PROGRAM_ID,
100
- setupLocalTest: boolean = false,
101
96
  withReserves: boolean = true
102
97
  ) {
103
98
  const market = await LendingMarket.fetch(connection, marketAddress, programId);
@@ -105,26 +100,12 @@ export class KaminoMarket {
105
100
  if (market === null) {
106
101
  return null;
107
102
  }
108
- let scope: Scope;
109
- if (!setupLocalTest) {
110
- scope = new Scope('mainnet-beta', connection);
111
- } else {
112
- scope = new Scope('localnet', connection);
113
- }
114
103
 
115
104
  const reserves = withReserves
116
105
  ? await getReservesForMarket(marketAddress, connection, programId, recentSlotDurationMs)
117
106
  : new Map<PublicKey, KaminoReserve>();
118
107
 
119
- return new KaminoMarket(
120
- connection,
121
- market,
122
- marketAddress.toString(),
123
- reserves,
124
- scope,
125
- recentSlotDurationMs,
126
- programId
127
- );
108
+ return new KaminoMarket(connection, market, marketAddress.toString(), reserves, recentSlotDurationMs, programId);
128
109
  }
129
110
 
130
111
  async reload(): Promise<void> {
@@ -1186,9 +1167,9 @@ export class KaminoMarket {
1186
1167
  /**
1187
1168
  * Get all Scope prices used by all the market reserves
1188
1169
  */
1189
- async getAllScopePrices(oraclePrices?: OraclePrices): Promise<KaminoPrices> {
1170
+ async getAllScopePrices(scope: Scope, oraclePrices?: OraclePrices): Promise<KaminoPrices> {
1190
1171
  if (!oraclePrices) {
1191
- oraclePrices = await this.scope.getOraclePrices();
1172
+ oraclePrices = await scope.getOraclePrices();
1192
1173
  }
1193
1174
  const spot: MintToPriceMap = {};
1194
1175
  const twaps: MintToPriceMap = {};
@@ -1199,11 +1180,11 @@ export class KaminoMarket {
1199
1180
  const chain = reserve.state.config.tokenInfo.scopeConfiguration.priceChain;
1200
1181
  const twapChain = reserve.state.config.tokenInfo.scopeConfiguration.twapChain.filter((x) => x > 0);
1201
1182
  if (oracle && isNotNullPubkey(oracle) && chain && Scope.isScopeChainValid(chain)) {
1202
- const spotPrice = await this.scope.getPriceFromChain(chain, oraclePrices);
1183
+ const spotPrice = await scope.getPriceFromChain(chain, oraclePrices);
1203
1184
  spot[tokenMint] = { price: spotPrice.price, name: tokenName };
1204
1185
  }
1205
1186
  if (oracle && isNotNullPubkey(oracle) && twapChain && Scope.isScopeChainValid(twapChain)) {
1206
- const twap = await this.scope.getPriceFromChain(twapChain, oraclePrices);
1187
+ const twap = await scope.getPriceFromChain(twapChain, oraclePrices);
1207
1188
  twaps[tokenMint] = { price: twap.price, name: tokenName };
1208
1189
  }
1209
1190
  }
@@ -1527,6 +1508,34 @@ export function getReservesActive(reserves: Map<PublicKey, KaminoReserve>): Map<
1527
1508
  return reservesActive;
1528
1509
  }
1529
1510
 
1511
+ export function getTokenIdsForScopeRefresh(kaminoMarket: KaminoMarket, reserves: PublicKey[]): number[] {
1512
+ const tokenIds: number[] = [];
1513
+
1514
+ for (const reserveAddress of reserves) {
1515
+ const reserve = kaminoMarket.getReserveByAddress(reserveAddress);
1516
+ if (!reserve) {
1517
+ throw new Error(`Reserve not found for reserve ${reserveAddress.toBase58()}`);
1518
+ }
1519
+
1520
+ if (!reserve.state.config.tokenInfo.scopeConfiguration.priceFeed.equals(PublicKey.default)) {
1521
+ let x = 0;
1522
+
1523
+ while (reserve.state.config.tokenInfo.scopeConfiguration.priceChain[x] !== U16_MAX) {
1524
+ tokenIds.push(reserve.state.config.tokenInfo.scopeConfiguration.priceChain[x]);
1525
+ x++;
1526
+ }
1527
+
1528
+ x = 0;
1529
+ while (reserve.state.config.tokenInfo.scopeConfiguration.twapChain[x] !== U16_MAX) {
1530
+ tokenIds.push(reserve.state.config.tokenInfo.scopeConfiguration.twapChain[x]);
1531
+ x++;
1532
+ }
1533
+ }
1534
+ }
1535
+
1536
+ return tokenIds;
1537
+ }
1538
+
1530
1539
  export async function getReserveFromMintAndMarket(
1531
1540
  connection: Connection,
1532
1541
  market: KaminoMarket,
@@ -17,6 +17,7 @@ import {
17
17
  getObligationPdaWithArgs,
18
18
  getObligationType,
19
19
  isNotNullPubkey,
20
+ ObligationType,
20
21
  PubkeyHashMap,
21
22
  TOTAL_NUMBER_OF_IDS_TO_CHECK,
22
23
  U64_MAX,
@@ -266,7 +267,7 @@ export class KaminoObligation {
266
267
  /**
267
268
  * @returns total borrow power of the obligation, relative to max LTV of each asset's reserve
268
269
  */
269
- getMaxAllowedBorrowValue(): Decimal {
270
+ getAllowedBorrowValue(): Decimal {
270
271
  return new Fraction(this.state.allowedBorrowValueSf).toDecimal();
271
272
  }
272
273
 
@@ -1593,3 +1594,8 @@ export class KaminoObligation {
1593
1594
  }
1594
1595
  }
1595
1596
  }
1597
+
1598
+ // Create a function that checks if an obligation is of type obligation or obligationType
1599
+ export function isKaminoObligation(obligation: KaminoObligation | ObligationType): obligation is KaminoObligation {
1600
+ return 'obligationAddress' in obligation;
1601
+ }
@@ -82,7 +82,7 @@ import {
82
82
  import { batchFetch, collToLamportsDecimal, ZERO } from '@kamino-finance/kliquidity-sdk';
83
83
  import { FullBPSDecimal } from '@kamino-finance/kliquidity-sdk/dist/utils/CreationParameters';
84
84
  import { FarmState } from '@kamino-finance/farms-sdk/dist';
85
- import { getAccountsInLUT, initLookupTableIx } from '../utils/lookupTable';
85
+ import { getAccountsInLUT, initLookupTableIx } from './lut_utils';
86
86
  import {
87
87
  getFarmStakeIxs,
88
88
  getFarmUnstakeAndWithdrawIxs,
package/src/client.ts CHANGED
@@ -22,6 +22,7 @@ import { VanillaObligation } from './utils/ObligationType';
22
22
  import { parseTokenSymbol } from './classes/utils';
23
23
  import { Env, initEnv } from '../tests/runner/setup_utils';
24
24
  import { initializeFarmsForReserve } from '../tests/runner/farms/farms_operations';
25
+ import { Scope } from '@kamino-finance/scope-sdk';
25
26
 
26
27
  const STAGING_LENDING_MARKET = new PublicKey('6WVSwDQXrBZeQVnu6hpnsRZhodaJTZBUaC334SiiBKdb');
27
28
  const MAINNET_LENDING_MARKET = new PublicKey('7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF');
@@ -309,7 +310,8 @@ async function deposit(connection: Connection, wallet: Keypair, token: string, d
309
310
  kaminoMarket.getReserveBySymbol(token)!.getLiquidityMint(),
310
311
  wallet.publicKey,
311
312
  new VanillaObligation(STAGING_LENDING_MARKET),
312
- true
313
+ true,
314
+ { scope: new Scope('mainnet-beta', connection), scopeFeed: 'hubble' }
313
315
  );
314
316
  console.log('User obligation', kaminoAction.obligation!.obligationAddress.toString());
315
317
 
@@ -330,7 +332,8 @@ async function withdraw(connection: Connection, wallet: Keypair, token: string,
330
332
  kaminoMarket.getReserveBySymbol(token)!.getLiquidityMint(),
331
333
  wallet.publicKey,
332
334
  new VanillaObligation(new PublicKey(STAGING_LENDING_MARKET)),
333
- true
335
+ true,
336
+ { scope: new Scope('mainnet-beta', connection), scopeFeed: 'hubble' }
334
337
  );
335
338
  console.log('User obligation', kaminoAction.obligation!.obligationAddress.toString());
336
339
 
@@ -351,7 +354,8 @@ async function borrow(connection: Connection, wallet: Keypair, token: string, bo
351
354
  kaminoMarket.getReserveBySymbol(token)!.getLiquidityMint(),
352
355
  wallet.publicKey,
353
356
  new VanillaObligation(new PublicKey(STAGING_LENDING_MARKET)),
354
- true
357
+ true,
358
+ { scope: new Scope('mainnet-beta', connection), scopeFeed: 'hubble' }
355
359
  );
356
360
  console.log('User obligation', kaminoAction.obligation!.obligationAddress.toString());
357
361
 
@@ -373,6 +377,7 @@ async function repay(connection: Connection, wallet: Keypair, token: string, bor
373
377
  wallet.publicKey,
374
378
  new VanillaObligation(new PublicKey(STAGING_LENDING_MARKET)),
375
379
  true,
380
+ { scope: new Scope('mainnet-beta', connection), scopeFeed: 'hubble' },
376
381
  await connection.getSlot()
377
382
  );
378
383
  console.log('User obligation', kaminoAction.obligation!.obligationAddress.toString());
@@ -2,7 +2,6 @@ import Decimal from 'decimal.js';
2
2
  import { KaminoMarket, KaminoObligation, KaminoReserve, numberToLamportsDecimal } from '../classes';
3
3
  import { PublicKey } from '@solana/web3.js';
4
4
  import { lamportsToDecimal } from '../classes/utils';
5
- import { MaxWithdrawLtvCheck, getMaxWithdrawLtvCheck } from './repay_with_collateral_operations';
6
5
 
7
6
  export function calcRepayAmountWithSlippage(
8
7
  kaminoMarket: KaminoMarket,
@@ -103,7 +102,6 @@ export function calcMaxWithdrawCollateral(
103
102
  .filter((p) => !p.reserveAddress.equals(borrow.reserveAddress))
104
103
  .reduce((acc, b) => acc.add(b.marketValueRefreshed), new Decimal('0'));
105
104
  }
106
- const maxWithdrawLtvCheck = getMaxWithdrawLtvCheck(obligation);
107
105
 
108
106
  let remainingDepositsValueWithLtv = new Decimal('0');
109
107
  if (obligation.getDeposits().length > 1) {
@@ -111,13 +109,8 @@ export function calcMaxWithdrawCollateral(
111
109
  .getDeposits()
112
110
  .filter((p) => !p.reserveAddress.equals(deposit.reserveAddress))
113
111
  .reduce((acc, d) => {
114
- const { maxLtv, liquidationLtv } = obligation.getLtvForReserve(
115
- market,
116
- market.getReserveByAddress(d.reserveAddress)!
117
- );
118
- const maxWithdrawLtv =
119
- maxWithdrawLtvCheck === MaxWithdrawLtvCheck.LIQUIDATION_THRESHOLD ? liquidationLtv : maxLtv;
120
- return acc.add(d.marketValueRefreshed.mul(maxWithdrawLtv));
112
+ const { maxLtv } = obligation.getLtvForReserve(market, market.getReserveByAddress(d.reserveAddress)!);
113
+ return acc.add(d.marketValueRefreshed.mul(maxLtv));
121
114
  }, new Decimal('0'));
122
115
  }
123
116
 
@@ -130,18 +123,16 @@ export function calcMaxWithdrawCollateral(
130
123
  repayingAllDebt: repayAmountLamports.gte(borrow.amount),
131
124
  };
132
125
  } else {
133
- const { maxLtv: collMaxLtv, liquidationLtv: collLiquidationLtv } = obligation.getLtvForReserve(
126
+ const { maxLtv: collMaxLtv } = obligation.getLtvForReserve(
134
127
  market,
135
128
  market.getReserveByAddress(depositReserve.address)!
136
129
  );
137
- const maxWithdrawLtv =
138
- maxWithdrawLtvCheck === MaxWithdrawLtvCheck.LIQUIDATION_THRESHOLD ? collLiquidationLtv : collMaxLtv;
139
130
  const numerator = deposit.marketValueRefreshed
140
- .mul(maxWithdrawLtv)
131
+ .mul(collMaxLtv)
141
132
  .add(remainingDepositsValueWithLtv)
142
133
  .sub(remainingBorrowsValue);
143
134
 
144
- const denominator = depositReserve.getOracleMarketPrice().mul(maxWithdrawLtv);
135
+ const denominator = depositReserve.getOracleMarketPrice().mul(collMaxLtv);
145
136
  const maxCollWithdrawAmount = numerator.div(denominator);
146
137
  const withdrawableCollLamports = maxCollWithdrawAmount.mul(depositReserve.getMintFactor()).floor();
147
138
 
@@ -6,12 +6,13 @@ import {
6
6
  SwapIxs,
7
7
  SwapIxsProvider,
8
8
  SwapQuoteProvider,
9
+ getScopeRefreshIx,
9
10
  } from '../leverage';
10
11
  import {
11
12
  createAtasIdempotent,
12
13
  getComputeBudgetAndPriorityFeeIxns,
13
14
  removeBudgetAndAtaIxns,
14
- ScopeRefresh,
15
+ ScopePriceRefreshConfig,
15
16
  U64_MAX,
16
17
  uniqueAccounts,
17
18
  } from '../utils';
@@ -51,16 +52,11 @@ interface RepayWithCollSwapInputsProps<QuoteResponse> {
51
52
  repayAmount: Decimal;
52
53
  isClosingPosition: boolean;
53
54
  budgetAndPriorityFeeIxs?: TransactionInstruction[];
54
- scopeRefresh?: ScopeRefresh;
55
+ scopeRefreshConfig?: ScopePriceRefreshConfig;
55
56
  useV2Ixs: boolean;
56
57
  quoter: SwapQuoteProvider<QuoteResponse>;
57
58
  }
58
59
 
59
- export enum MaxWithdrawLtvCheck {
60
- MAX_LTV,
61
- LIQUIDATION_THRESHOLD,
62
- }
63
-
64
60
  export async function getRepayWithCollSwapInputs<QuoteResponse>({
65
61
  collTokenMint,
66
62
  currentSlot,
@@ -72,7 +68,7 @@ export async function getRepayWithCollSwapInputs<QuoteResponse>({
72
68
  repayAmount,
73
69
  isClosingPosition,
74
70
  budgetAndPriorityFeeIxs,
75
- scopeRefresh,
71
+ scopeRefreshConfig,
76
72
  useV2Ixs,
77
73
  }: RepayWithCollSwapInputsProps<QuoteResponse>): Promise<{
78
74
  swapInputs: SwapInputs;
@@ -131,7 +127,7 @@ export async function getRepayWithCollSwapInputs<QuoteResponse>({
131
127
  referrer,
132
128
  currentSlot,
133
129
  budgetAndPriorityFeeIxs,
134
- scopeRefresh,
130
+ scopeRefreshConfig,
135
131
  {
136
132
  preActionIxs: [],
137
133
  swapIxs: [],
@@ -196,7 +192,7 @@ export async function getRepayWithCollIxs<QuoteResponse>({
196
192
  quoter,
197
193
  swapper,
198
194
  referrer,
199
- scopeRefresh,
195
+ scopeRefreshConfig,
200
196
  useV2Ixs,
201
197
  logger = console.log,
202
198
  }: RepayWithCollIxsProps<QuoteResponse>): Promise<RepayWithCollIxsResponse<QuoteResponse>> {
@@ -211,23 +207,14 @@ export async function getRepayWithCollIxs<QuoteResponse>({
211
207
  repayAmount,
212
208
  isClosingPosition,
213
209
  budgetAndPriorityFeeIxs,
214
- scopeRefresh,
210
+ scopeRefreshConfig,
215
211
  useV2Ixs,
216
212
  });
217
213
  const { debtRepayAmountLamports, flashRepayAmountLamports, maxCollateralWithdrawLamports, swapQuote } = initialInputs;
218
214
  const { inputAmountLamports: collSwapInLamports } = swapInputs;
219
215
 
220
- const collReserve = kaminoMarket.getReserveByMint(collTokenMint);
221
-
222
- if (!collReserve) {
223
- throw new Error(`Collateral reserve with mint ${collTokenMint} not found in market ${kaminoMarket.getAddress()}`);
224
- }
225
-
226
- const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint);
227
-
228
- if (!debtReserve) {
229
- throw new Error(`Debt reserve with mint ${debtTokenMint} not found in market ${kaminoMarket.getAddress()}`);
230
- }
216
+ const collReserve = kaminoMarket.getReserveByMint(collTokenMint)!;
217
+ const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint)!;
231
218
 
232
219
  // the client should use these values to prevent this input, but the tx may succeed, so we don't want to fail
233
220
  // there is also a chance that the tx will consume debt token from the user's ata which they would not expect
@@ -258,7 +245,7 @@ export async function getRepayWithCollIxs<QuoteResponse>({
258
245
  referrer,
259
246
  currentSlot,
260
247
  budgetAndPriorityFeeIxs,
261
- scopeRefresh,
248
+ scopeRefreshConfig,
262
249
  swapResponse,
263
250
  isClosingPosition,
264
251
  debtRepayAmountLamports,
@@ -282,7 +269,7 @@ async function buildRepayWithCollateralIxs(
282
269
  referrer: PublicKey,
283
270
  currentSlot: number,
284
271
  budgetAndPriorityFeeIxs: TransactionInstruction[] | undefined,
285
- scopeRefresh: ScopeRefresh | undefined,
272
+ scopeRefreshConfig: ScopePriceRefreshConfig | undefined,
286
273
  swapQuoteIxs: SwapIxs,
287
274
  isClosingPosition: boolean,
288
275
  debtRepayAmountLamports: Decimal,
@@ -300,9 +287,11 @@ async function buildRepayWithCollateralIxs(
300
287
  const atasAndIxs = createAtasIdempotent(obligation.state.owner, atas);
301
288
  const [, { ata: debtTokenAta }] = atasAndIxs;
302
289
 
290
+ const scopeRefreshIxn = await getScopeRefreshIx(market, collReserve, debtReserve, obligation, scopeRefreshConfig);
291
+
303
292
  // 2. Flash borrow & repay the debt to repay amount needed
304
293
  const { flashBorrowIxn, flashRepayIxn } = getFlashLoanInstructions({
305
- borrowIxnIndex: budgetIxns.length + atasAndIxs.length,
294
+ borrowIxnIndex: budgetIxns.length + atasAndIxs.length + (scopeRefreshIxn.length > 0 ? 1 : 0),
306
295
  walletPublicKey: obligation.state.owner,
307
296
  lendingMarketAuthority: market.getLendingMarketAuthority(),
308
297
  lendingMarketAddress: market.getAddress(),
@@ -317,54 +306,32 @@ async function buildRepayWithCollateralIxs(
317
306
 
318
307
  const requestElevationGroup = !isClosingPosition && obligation.state.elevationGroup !== 0;
319
308
 
320
- const maxWithdrawLtvCheck = getMaxWithdrawLtvCheck(obligation);
321
-
322
309
  // 3. Repay using the flash borrowed funds & withdraw collateral to swap and pay the flash loan
323
- let repayAndWithdrawAction;
324
- if (maxWithdrawLtvCheck === MaxWithdrawLtvCheck.MAX_LTV) {
325
- repayAndWithdrawAction = await KaminoAction.buildRepayAndWithdrawTxns(
326
- market,
327
- isClosingPosition ? U64_MAX : debtRepayAmountLamports.toString(),
328
- debtReserve.getLiquidityMint(),
329
- isClosingPosition ? U64_MAX : collWithdrawLamports.toString(),
330
- collReserve.getLiquidityMint(),
331
- obligation.state.owner,
332
- currentSlot,
333
- obligation,
334
- useV2Ixs,
335
- 0,
336
- false,
337
- requestElevationGroup,
338
- undefined,
339
- undefined,
340
- referrer,
341
- scopeRefresh
342
- );
343
- } else {
344
- repayAndWithdrawAction = await KaminoAction.buildRepayAndWithdrawV2Txns(
345
- market,
346
- isClosingPosition ? U64_MAX : debtRepayAmountLamports.toString(),
347
- debtReserve.getLiquidityMint(),
348
- isClosingPosition ? U64_MAX : collWithdrawLamports.toString(),
349
- collReserve.getLiquidityMint(),
350
- obligation.state.owner,
351
- currentSlot,
352
- obligation,
353
- 0,
354
- false,
355
- requestElevationGroup,
356
- undefined,
357
- undefined,
358
- referrer,
359
- scopeRefresh
360
- );
361
- }
310
+ const repayAndWithdrawAction = await KaminoAction.buildRepayAndWithdrawTxns(
311
+ market,
312
+ isClosingPosition ? U64_MAX : debtRepayAmountLamports.toString(),
313
+ debtReserve.getLiquidityMint(),
314
+ isClosingPosition ? U64_MAX : collWithdrawLamports.toString(),
315
+ collReserve.getLiquidityMint(),
316
+ obligation.state.owner,
317
+ currentSlot,
318
+ obligation,
319
+ useV2Ixs,
320
+ undefined,
321
+ 0,
322
+ false,
323
+ requestElevationGroup,
324
+ undefined,
325
+ undefined,
326
+ referrer
327
+ );
362
328
 
363
329
  // 4. Swap collateral to debt to repay flash loan
364
330
  const { preActionIxs, swapIxs } = swapQuoteIxs;
365
331
  const swapInstructions = removeBudgetAndAtaIxns(swapIxs, []);
366
332
 
367
333
  return [
334
+ ...scopeRefreshIxn,
368
335
  ...budgetIxns,
369
336
  ...atasAndIxs.map((x) => x.createAtaIx),
370
337
  flashBorrowIxn,
@@ -374,9 +341,3 @@ async function buildRepayWithCollateralIxs(
374
341
  flashRepayIxn,
375
342
  ];
376
343
  }
377
-
378
- export const getMaxWithdrawLtvCheck = (obligation: KaminoObligation) => {
379
- return obligation.refreshedStats.userTotalBorrowBorrowFactorAdjusted.gte(obligation.refreshedStats.borrowLimit)
380
- ? MaxWithdrawLtvCheck.LIQUIDATION_THRESHOLD
381
- : MaxWithdrawLtvCheck.MAX_LTV;
382
- };
@@ -6,14 +6,14 @@ import {
6
6
  KaminoObligation,
7
7
  KaminoReserve,
8
8
  } from '../classes';
9
- import { getFlashLoanInstructions, SwapIxsProvider, SwapQuoteProvider } from '../leverage';
9
+ import { getFlashLoanInstructions, getScopeRefreshIx, SwapIxsProvider, SwapQuoteProvider } from '../leverage';
10
10
  import {
11
11
  createAtasIdempotent,
12
12
  DEFAULT_MAX_COMPUTE_UNITS,
13
13
  getAssociatedTokenAddress,
14
14
  getComputeBudgetAndPriorityFeeIxns,
15
15
  PublicKeySet,
16
- ScopeRefresh,
16
+ ScopePriceRefreshConfig,
17
17
  U64_MAX,
18
18
  uniqueAccounts,
19
19
  } from '../utils';
@@ -61,7 +61,7 @@ export interface SwapCollIxnsInputs<QuoteResponse> {
61
61
  referrer: PublicKey;
62
62
  currentSlot: number;
63
63
  budgetAndPriorityFeeIxns?: TransactionInstruction[];
64
- scopeRefresh?: ScopeRefresh;
64
+ scopeRefreshConfig?: ScopePriceRefreshConfig;
65
65
  useV2Ixs: boolean;
66
66
  quoter: SwapQuoteProvider<QuoteResponse>;
67
67
  swapper: SwapIxsProvider<QuoteResponse>;
@@ -205,7 +205,7 @@ type SwapCollContext<QuoteResponse> = {
205
205
  referrer: PublicKey;
206
206
  currentSlot: number;
207
207
  useV2Ixs: boolean;
208
- scopeRefresh: ScopeRefresh | undefined;
208
+ scopeRefreshConfig: ScopePriceRefreshConfig | undefined;
209
209
  logger: (msg: string, ...extra: any[]) => void;
210
210
  };
211
211
 
@@ -235,7 +235,7 @@ function extractArgsAndContext<QuoteResponse>(
235
235
  quoter: inputs.quoter,
236
236
  swapper: inputs.swapper,
237
237
  referrer: inputs.referrer,
238
- scopeRefresh: inputs.scopeRefresh,
238
+ scopeRefreshConfig: inputs.scopeRefreshConfig,
239
239
  currentSlot: inputs.currentSlot,
240
240
  useV2Ixs: inputs.useV2Ixs,
241
241
  },
@@ -264,6 +264,18 @@ async function getKlendIxns(
264
264
  const { ataCreationIxns, targetCollAta } = getAtaCreationIxns(context);
265
265
  const setupIxns = [...context.budgetAndPriorityFeeIxns, ...ataCreationIxns];
266
266
 
267
+ const scopeRefreshIxn = await getScopeRefreshIx(
268
+ context.market,
269
+ context.sourceCollReserve,
270
+ context.targetCollReserve,
271
+ context.obligation,
272
+ context.scopeRefreshConfig
273
+ );
274
+
275
+ if (scopeRefreshIxn) {
276
+ setupIxns.unshift(...scopeRefreshIxn);
277
+ }
278
+
267
279
  const targetCollFlashBorrowedAmount = calculateTargetCollFlashBorrowedAmount(targetCollSwapOutAmount, context);
268
280
  const { targetCollFlashBorrowIxn, targetCollFlashRepayIxn } = getTargetCollFlashLoanIxns(
269
281
  targetCollFlashBorrowedAmount,
@@ -380,6 +392,7 @@ async function getDepositTargetCollIxns(
380
392
  context.obligation.state.owner,
381
393
  context.obligation,
382
394
  context.useV2Ixs,
395
+ undefined, // we create the scope refresh ixn outside of KaminoAction
383
396
  0, // no extra compute budget
384
397
  false, // we do not need ATA ixns here (we construct and close them ourselves)
385
398
  removesElevationGroup, // we may need to (temporarily) remove the elevation group; the same or a different one will be set on withdraw, if requested
@@ -387,7 +400,6 @@ async function getDepositTargetCollIxns(
387
400
  false, // we do not need to create a lookup table, dealing with an existing obligation
388
401
  context.referrer,
389
402
  context.currentSlot,
390
- context.scopeRefresh,
391
403
  removesElevationGroup ? 0 : undefined // only applicable when removing the group
392
404
  );
393
405
  return {
@@ -430,6 +442,7 @@ async function getWithdrawSourceCollIxns(
430
442
  context.obligation.state.owner,
431
443
  context.obligation,
432
444
  context.useV2Ixs,
445
+ undefined, // we create the scope refresh ixn outside of KaminoAction
433
446
  0, // no extra compute budget
434
447
  false, // we do not need ATA ixns here (we construct and close them ourselves)
435
448
  requestedElevationGroup !== undefined, // the `elevationGroupIdToRequestAfterWithdraw()` has already decided on this
@@ -437,7 +450,6 @@ async function getWithdrawSourceCollIxns(
437
450
  false, // we do not need to create a lookup table, dealing with an existing obligation
438
451
  context.referrer,
439
452
  context.currentSlot,
440
- undefined, // we have refreshed scope already, during depositing
441
453
  requestedElevationGroup,
442
454
  context.obligation.deposits.has(context.targetCollReserve.address) // if our obligation already had the target coll...
443
455
  ? undefined // ... then we need no customizations here, but otherwise...