@kamino-finance/klend-sdk 7.3.9 → 7.3.10-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.
@@ -76,11 +76,7 @@ import { Scope } from '@kamino-finance/scope-sdk';
76
76
  import { ObligationOrderAtIndex } from './obligationOrder';
77
77
  import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
78
78
  import { SYSVAR_INSTRUCTIONS_ADDRESS, SYSVAR_RENT_ADDRESS } from '@solana/sysvars';
79
- import {
80
- getCloseAccountInstruction,
81
- getSyncNativeInstruction,
82
- TOKEN_2022_PROGRAM_ADDRESS,
83
- } from '@solana-program/token-2022';
79
+ import { getCloseAccountInstruction, getSyncNativeInstruction } from '@solana-program/token-2022';
84
80
  import { getTransferSolInstruction, SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
85
81
  import { noopSigner } from '../utils/signer';
86
82
 
@@ -3293,13 +3289,8 @@ export class KaminoAction {
3293
3289
  action === 'mint' ||
3294
3290
  (action === 'liquidate' && this.mint === WRAPPED_SOL_MINT); // only sync WSOL amount if liquidator repays SOL which is secondaryMint
3295
3291
 
3296
- const isValidTokenAccount =
3297
- userWSOLAccountInfo.exists &&
3298
- (userWSOLAccountInfo.programAddress === TOKEN_PROGRAM_ADDRESS ||
3299
- userWSOLAccountInfo.programAddress === TOKEN_2022_PROGRAM_ADDRESS);
3300
-
3301
3292
  const transferLamportsIx = getTransferSolInstruction({
3302
- amount: (isValidTokenAccount ? 0n : rentExemptLamports) + (sendAction ? BigInt(safeRepay.toString()) : 0n),
3293
+ amount: (userWSOLAccountInfo.exists ? 0n : rentExemptLamports) + (sendAction ? BigInt(safeRepay.toString()) : 0n),
3303
3294
  source: this.owner,
3304
3295
  destination: userTokenAccountAddress,
3305
3296
  });
@@ -3321,20 +3312,29 @@ export class KaminoAction {
3321
3312
  },
3322
3313
  { programAddress: TOKEN_PROGRAM_ADDRESS }
3323
3314
  );
3324
-
3325
- const [, createUserWSOLAccountIx] = await createAssociatedTokenAccountIdempotentInstruction(
3326
- this.owner,
3327
- WRAPPED_SOL_MINT,
3328
- this.owner.address,
3329
- TOKEN_PROGRAM_ADDRESS,
3330
- userTokenAccountAddress
3331
- );
3332
- preIxs.push(createUserWSOLAccountIx);
3333
- preIxsLabels.push(`CreateUserAtaSOL[${userTokenAccountAddress}]`);
3334
- preIxs.push(syncIx);
3335
- preIxsLabels.push(`SyncUserAtaSOL[${userTokenAccountAddress}]`);
3336
- postIxs.push(closeWSOLAccountIx);
3337
- postIxsLabels.push(`CloseUserAtaSOL[${userTokenAccountAddress}]`);
3315
+ if (userWSOLAccountInfo.exists) {
3316
+ if (sendAction) {
3317
+ preIxs.push(syncIx);
3318
+ preIxsLabels.push(`SyncUserAtaSOL[${userTokenAccountAddress}]`);
3319
+ } else {
3320
+ postIxs.push(closeWSOLAccountIx);
3321
+ postIxsLabels.push(`CloseUserAtaSOL[${userTokenAccountAddress}]`);
3322
+ }
3323
+ } else {
3324
+ const [, createUserWSOLAccountIx] = await createAssociatedTokenAccountIdempotentInstruction(
3325
+ this.owner,
3326
+ WRAPPED_SOL_MINT,
3327
+ this.owner.address,
3328
+ TOKEN_PROGRAM_ADDRESS,
3329
+ userTokenAccountAddress
3330
+ );
3331
+ preIxs.push(createUserWSOLAccountIx);
3332
+ preIxsLabels.push(`CreateUserAtaSOL[${userTokenAccountAddress}]`);
3333
+ preIxs.push(syncIx);
3334
+ preIxsLabels.push(`SyncUserAtaSOL[${userTokenAccountAddress}]`);
3335
+ postIxs.push(closeWSOLAccountIx);
3336
+ postIxsLabels.push(`CloseUserAtaSOL[${userTokenAccountAddress}]`);
3337
+ }
3338
3338
 
3339
3339
  this.setupIxs.unshift(...preIxs);
3340
3340
  this.setupIxsLabels.unshift(...preIxsLabels);
@@ -1,6 +1,8 @@
1
1
  import { struct, Layout } from '@coral-xyz/borsh';
2
2
  import { blobEquals, orThrow, toJson } from './utils';
3
3
  import { Buffer } from 'buffer';
4
+ import { ReserveConfig } from '../lib';
5
+ import { UpdateConfigModeKind, UpdateConfigMode } from '../@codegen/klend/types';
4
6
 
5
7
  /**
6
8
  * An object literal specifying *all* "update mode" enum values (of type {@code M}) and their corresponding config items
@@ -151,7 +153,7 @@ export class ConfigItemUpdater<C, A> {
151
153
  * A resolver of config item changes.
152
154
  */
153
155
  export class ConfigUpdater<M extends BorshEnum, C> {
154
- private readonly itemUpdaters: Map<M['kind'], [M, ConfigItemUpdater<C, any>[]]>;
156
+ protected readonly itemUpdaters: Map<M['kind'], [M, ConfigItemUpdater<C, any>[]]>;
155
157
 
156
158
  /**
157
159
  * A resolving constructor.
@@ -235,6 +237,61 @@ export class ConfigUpdater<M extends BorshEnum, C> {
235
237
  }
236
238
  }
237
239
 
240
+ export class EntireReserveConfigUpdater extends ConfigUpdater<UpdateConfigModeKind, ReserveConfig> {
241
+ encodeAllUpdates(
242
+ currentConfig: ReserveConfig | undefined,
243
+ newConfig: ReserveConfig
244
+ ): EncodedConfigUpdate<UpdateConfigModeKind>[] {
245
+ const updates: EncodedConfigUpdate<UpdateConfigModeKind>[] = [];
246
+ let ltvUpdate: EncodedConfigUpdate<UpdateConfigModeKind> | undefined;
247
+ let liquidationThresholdUpdate: EncodedConfigUpdate<UpdateConfigModeKind> | undefined;
248
+
249
+ for (const [mode, itemUpdaters] of this.itemUpdaters.values()) {
250
+ for (const itemUpdater of itemUpdaters) {
251
+ const value = itemUpdater.encodeUpdatedItemFrom(currentConfig, newConfig);
252
+ if (value === undefined) {
253
+ continue;
254
+ }
255
+
256
+ const update = { mode, value };
257
+
258
+ if (mode.discriminator === UpdateConfigMode.UpdateLoanToValuePct.discriminator) {
259
+ ltvUpdate = update;
260
+ } else if (mode.discriminator === UpdateConfigMode.UpdateLiquidationThresholdPct.discriminator) {
261
+ liquidationThresholdUpdate = update;
262
+ // If reserve is already initialized, skip fields that require global admin signature on update
263
+ } else {
264
+ updates.push(update);
265
+ }
266
+ }
267
+ }
268
+
269
+ // Handle ordering of LTV and LiquidationThreshold updates
270
+ // ensure LiquidationThreshold >= LTV at all times
271
+ if (ltvUpdate && liquidationThresholdUpdate) {
272
+ const currentLiquidationThreshold = currentConfig ? currentConfig.liquidationThresholdPct : 0;
273
+ const newLiquidationThreshold = newConfig.liquidationThresholdPct;
274
+
275
+ if (newLiquidationThreshold > currentLiquidationThreshold) {
276
+ // Liquidation threshold is increasing, so it must be updated first
277
+ updates.push(liquidationThresholdUpdate);
278
+ updates.push(ltvUpdate);
279
+ } else {
280
+ // All other cases, it's safer to update the LTV first
281
+ updates.push(ltvUpdate);
282
+ updates.push(liquidationThresholdUpdate);
283
+ }
284
+ } else if (ltvUpdate) {
285
+ updates.push(ltvUpdate);
286
+ } else if (liquidationThresholdUpdate) {
287
+ updates.push(liquidationThresholdUpdate);
288
+ }
289
+
290
+ updates.sort((left, right) => priorityOf(left.mode) - priorityOf(right.mode));
291
+ return updates;
292
+ }
293
+ }
294
+
238
295
  /**
239
296
  * The update mode discriminator and the serialized value needed to construct an update ix.
240
297
  */
@@ -293,3 +350,44 @@ type Getter<C, A> = (config: C) => A;
293
350
  function toArray<T>(singleOrArray: SingleOrArray<T>): T[] {
294
351
  return Array.isArray(singleOrArray) ? singleOrArray : [singleOrArray];
295
352
  }
353
+
354
+ // Lowest priority gets updated first
355
+ function priorityOf(mode: UpdateConfigModeKind): number {
356
+ switch (mode.discriminator) {
357
+ case UpdateConfigMode.UpdateScopePriceFeed.discriminator:
358
+ return 0;
359
+ case UpdateConfigMode.UpdateTokenInfoScopeTwap.discriminator:
360
+ return 0;
361
+ case UpdateConfigMode.UpdateTokenInfoScopeChain.discriminator:
362
+ return 0;
363
+ case UpdateConfigMode.UpdateTokenInfoLowerHeuristic.discriminator:
364
+ return 0;
365
+ case UpdateConfigMode.UpdateTokenInfoUpperHeuristic.discriminator:
366
+ return 0;
367
+ case UpdateConfigMode.UpdateTokenInfoExpHeuristic.discriminator:
368
+ return 0;
369
+ case UpdateConfigMode.UpdateTokenInfoTwapDivergence.discriminator:
370
+ return 0;
371
+ case UpdateConfigMode.UpdateTokenInfoName.discriminator:
372
+ return 0;
373
+ case UpdateConfigMode.UpdateTokenInfoPriceMaxAge.discriminator:
374
+ return 0;
375
+ case UpdateConfigMode.UpdateTokenInfoTwapMaxAge.discriminator:
376
+ return 0;
377
+ case UpdateConfigMode.UpdateDeleveragingBonusIncreaseBpsPerDay.discriminator:
378
+ return priorityOf(new UpdateConfigMode.UpdateAutodeleverageEnabled()) - 1;
379
+ case UpdateConfigMode.UpdateDeleveragingMarginCallPeriod.discriminator:
380
+ return priorityOf(new UpdateConfigMode.UpdateAutodeleverageEnabled()) - 1;
381
+ case UpdateConfigMode.UpdateDeleveragingThresholdDecreaseBpsPerDay.discriminator:
382
+ return priorityOf(new UpdateConfigMode.UpdateAutodeleverageEnabled()) - 1;
383
+ case UpdateConfigMode.UpdateAutodeleverageEnabled.discriminator:
384
+ return 4;
385
+ // Always update last bc we cannot skip validation
386
+ case UpdateConfigMode.UpdateDepositLimit.discriminator:
387
+ return 63;
388
+ case UpdateConfigMode.UpdateBorrowLimit.discriminator:
389
+ return 63;
390
+ default:
391
+ return 10;
392
+ }
393
+ }
@@ -8,7 +8,6 @@ import {
8
8
  scaleDownWads,
9
9
  WAD,
10
10
  RewardInfo,
11
- RewardType,
12
11
  } from '@kamino-finance/farms-sdk';
13
12
  import {
14
13
  address,
@@ -175,7 +174,7 @@ export type UnstakeAndWithdrawFromFarmIxs = {
175
174
  withdrawIx: Instruction;
176
175
  };
177
176
 
178
- export function getRewardPerTimeUnitSecond(reward: RewardInfo, farmTotalStakeLamports: Decimal) {
177
+ export function getRewardPerTimeUnitSecond(reward: RewardInfo) {
179
178
  const now = new Decimal(new Date().getTime()).div(1000);
180
179
  let rewardPerTimeUnitSecond = new Decimal(0);
181
180
  for (let i = 0; i < reward.rewardScheduleCurve.points.length - 1; i++) {
@@ -197,11 +196,8 @@ export function getRewardPerTimeUnitSecond(reward: RewardInfo, farmTotalStakeLam
197
196
  const rewardTokenDecimals = reward.token.decimals.toNumber();
198
197
  const rewardAmountPerUnitDecimals = new Decimal(10).pow(reward.rewardsPerSecondDecimals.toString());
199
198
  const rewardAmountPerUnitLamports = new Decimal(10).pow(rewardTokenDecimals.toString());
200
- const constantRewardStakeAdjustment =
201
- reward.rewardType === RewardType.Constant.discriminator ? farmTotalStakeLamports : new Decimal(1);
202
199
 
203
200
  const rpsAdjusted = new Decimal(rewardPerTimeUnitSecond.toString())
204
- .mul(constantRewardStakeAdjustment)
205
201
  .div(rewardAmountPerUnitDecimals)
206
202
  .div(rewardAmountPerUnitLamports);
207
203
 
@@ -24,7 +24,6 @@ import {
24
24
  INITIAL_COLLATERAL_RATE,
25
25
  lendingMarketAuthPda,
26
26
  MarketWithAddress,
27
- MIN_INITIAL_DEPOSIT,
28
27
  ONE_HUNDRED_PCT_IN_BPS,
29
28
  reservePdas,
30
29
  SLOTS_PER_DAY,
@@ -37,7 +36,7 @@ import { FeeCalculation, Fees, ReserveDataType, ReserveFarmInfo, ReserveRewardYi
37
36
  import { Reserve, ReserveFields } from '../@codegen/klend/accounts';
38
37
  import { CurvePointFields, ReserveConfig, UpdateConfigMode, UpdateConfigModeKind } from '../@codegen/klend/types';
39
38
  import { calculateAPYFromAPR, getBorrowRate, lamportsToNumberDecimal, parseTokenSymbol, positiveOrZero } from './utils';
40
- import { CompositeConfigItem, encodeUsingLayout, ConfigUpdater } from './configItems';
39
+ import { CompositeConfigItem, encodeUsingLayout, EntireReserveConfigUpdater } from './configItems';
41
40
  import { bfToDecimal, Fraction } from './fraction';
42
41
  import { ActionType } from './action';
43
42
  import { BorrowCapsAndCounters, ElevationGroupDescription, KaminoMarket } from './market';
@@ -49,7 +48,7 @@ import {
49
48
  UpdateReserveConfigArgs,
50
49
  } from '../lib';
51
50
  import { aprToApy, KaminoPrices } from '@kamino-finance/kliquidity-sdk';
52
- import { FarmAndKey, FarmState, RewardInfo } from '@kamino-finance/farms-sdk';
51
+ import { FarmState, RewardInfo } from '@kamino-finance/farms-sdk';
53
52
  import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
54
53
  import { maxBigInt } from '../utils/bigint';
55
54
  import { getCreateAccountInstruction, SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
@@ -70,7 +69,7 @@ export class KaminoReserve {
70
69
 
71
70
  tokenOraclePrice: TokenOracleData;
72
71
  stats: ReserveDataType;
73
- private farmData: ReserveFarmInfo = { fetched: false, farms: [] };
72
+ private farmData: ReserveFarmInfo = { fetched: false, farmStates: [] };
74
73
 
75
74
  private rpc: Rpc<KaminoReserveRpcApi>;
76
75
  private readonly recentSlotDurationMs: number;
@@ -813,22 +812,22 @@ export class KaminoReserve {
813
812
 
814
813
  async loadFarmStates() {
815
814
  if (!this.farmData.fetched) {
816
- const farmStates: FarmAndKey[] = [];
815
+ const farmStates: FarmState[] = [];
817
816
  const debtFarmAddress = this.getDebtFarmAddress();
818
817
  if (isSome(debtFarmAddress)) {
819
818
  const farmState = await FarmState.fetch(this.rpc, debtFarmAddress.value);
820
819
  if (farmState !== null) {
821
- farmStates.push({ farmState, key: debtFarmAddress.value });
820
+ farmStates.push(farmState);
822
821
  }
823
822
  }
824
823
  const collateralFarmAddress = this.getCollateralFarmAddress();
825
824
  if (isSome(collateralFarmAddress)) {
826
825
  const farmState = await FarmState.fetch(this.rpc, collateralFarmAddress.value);
827
826
  if (farmState !== null) {
828
- farmStates.push({ farmState, key: collateralFarmAddress.value });
827
+ farmStates.push(farmState);
829
828
  }
830
829
  }
831
- this.farmData.farms = farmStates;
830
+ this.farmData.farmStates = farmStates;
832
831
  this.farmData.fetched = true;
833
832
  }
834
833
  }
@@ -839,19 +838,14 @@ export class KaminoReserve {
839
838
  throw Error('KaminoMarket must call loadReserves.');
840
839
  }
841
840
 
841
+ const isDebtReward = this.state.farmDebt === this.address;
842
842
  await this.loadFarmStates();
843
843
  const yields: ReserveRewardYield[] = [];
844
- for (const farmAndKey of this.farmData.farms) {
845
- const isDebtReward = this.state.farmDebt === farmAndKey.key;
846
- for (const rewardInfo of farmAndKey.farmState.rewardInfos.filter(
844
+ for (const farmState of this.farmData.farmStates) {
845
+ for (const rewardInfo of farmState.rewardInfos.filter(
847
846
  (x) => x.token.mint !== DEFAULT_PUBLIC_KEY && !x.rewardsAvailable.isZero()
848
847
  )) {
849
- const { apy, apr } = this.calculateRewardYield(
850
- prices,
851
- rewardInfo,
852
- isDebtReward,
853
- new Decimal(farmAndKey.farmState.totalActiveStakeScaled.toString())
854
- );
848
+ const { apy, apr } = this.calculateRewardYield(prices, rewardInfo, isDebtReward);
855
849
  if (apy.isZero() && apr.isZero()) {
856
850
  continue;
857
851
  }
@@ -861,14 +855,9 @@ export class KaminoReserve {
861
855
  return yields;
862
856
  }
863
857
 
864
- calculateRewardYield(
865
- prices: KaminoPrices,
866
- rewardInfo: RewardInfo,
867
- isDebtReward: boolean,
868
- farmTotalStakeLamports: Decimal
869
- ) {
858
+ private calculateRewardYield(prices: KaminoPrices, rewardInfo: RewardInfo, isDebtReward: boolean) {
870
859
  const mintAddress = this.getLiquidityMint();
871
- const rewardPerTimeUnitSecond = getRewardPerTimeUnitSecond(rewardInfo, farmTotalStakeLamports);
860
+ const rewardPerTimeUnitSecond = getRewardPerTimeUnitSecond(rewardInfo);
872
861
  const reserveToken = prices.spot[mintAddress.toString()];
873
862
  const rewardToken = prices.spot[rewardInfo.token.mint.toString()];
874
863
 
@@ -1250,69 +1239,74 @@ export async function updateReserveConfigIx(
1250
1239
  return updateReserveConfig(args, accounts, undefined, programId);
1251
1240
  }
1252
1241
 
1253
- export const RESERVE_CONFIG_UPDATER = new ConfigUpdater(UpdateConfigMode.fromDecoded, ReserveConfig, (config) => ({
1254
- [UpdateConfigMode.UpdateLoanToValuePct.kind]: config.loanToValuePct,
1255
- [UpdateConfigMode.UpdateMaxLiquidationBonusBps.kind]: config.maxLiquidationBonusBps,
1256
- [UpdateConfigMode.UpdateLiquidationThresholdPct.kind]: config.liquidationThresholdPct,
1257
- [UpdateConfigMode.UpdateProtocolLiquidationFee.kind]: config.protocolLiquidationFeePct,
1258
- [UpdateConfigMode.UpdateProtocolTakeRate.kind]: config.protocolTakeRatePct,
1259
- [UpdateConfigMode.UpdateFeesOriginationFee.kind]: config.fees.originationFeeSf,
1260
- [UpdateConfigMode.UpdateFeesFlashLoanFee.kind]: config.fees.flashLoanFeeSf,
1261
- [UpdateConfigMode.DeprecatedUpdateFeesReferralFeeBps.kind]: [], // deprecated
1262
- [UpdateConfigMode.UpdateDepositLimit.kind]: config.depositLimit,
1263
- [UpdateConfigMode.UpdateBorrowLimit.kind]: config.borrowLimit,
1264
- [UpdateConfigMode.UpdateTokenInfoLowerHeuristic.kind]: config.tokenInfo.heuristic.lower,
1265
- [UpdateConfigMode.UpdateTokenInfoUpperHeuristic.kind]: config.tokenInfo.heuristic.upper,
1266
- [UpdateConfigMode.UpdateTokenInfoExpHeuristic.kind]: config.tokenInfo.heuristic.exp,
1267
- [UpdateConfigMode.UpdateTokenInfoTwapDivergence.kind]: config.tokenInfo.maxTwapDivergenceBps,
1268
- [UpdateConfigMode.UpdateTokenInfoScopeTwap.kind]: config.tokenInfo.scopeConfiguration.twapChain,
1269
- [UpdateConfigMode.UpdateTokenInfoScopeChain.kind]: config.tokenInfo.scopeConfiguration.priceChain,
1270
- [UpdateConfigMode.UpdateTokenInfoName.kind]: config.tokenInfo.name,
1271
- [UpdateConfigMode.UpdateTokenInfoPriceMaxAge.kind]: config.tokenInfo.maxAgePriceSeconds,
1272
- [UpdateConfigMode.UpdateTokenInfoTwapMaxAge.kind]: config.tokenInfo.maxAgeTwapSeconds,
1273
- [UpdateConfigMode.UpdateScopePriceFeed.kind]: config.tokenInfo.scopeConfiguration.priceFeed,
1274
- [UpdateConfigMode.UpdatePythPrice.kind]: config.tokenInfo.pythConfiguration.price,
1275
- [UpdateConfigMode.UpdateSwitchboardFeed.kind]: config.tokenInfo.switchboardConfiguration.priceAggregator,
1276
- [UpdateConfigMode.UpdateSwitchboardTwapFeed.kind]: config.tokenInfo.switchboardConfiguration.twapAggregator,
1277
- [UpdateConfigMode.UpdateBorrowRateCurve.kind]: config.borrowRateCurve,
1278
- [UpdateConfigMode.UpdateEntireReserveConfig.kind]: [], // technically `config` would be a valid thing here, but we actually do NOT want entire config update among ixs produced for field-by-field updates
1279
- [UpdateConfigMode.UpdateDebtWithdrawalCap.kind]: new CompositeConfigItem(
1280
- config.debtWithdrawalCap.configCapacity,
1281
- config.debtWithdrawalCap.configIntervalLengthSeconds
1282
- ),
1283
- [UpdateConfigMode.UpdateDepositWithdrawalCap.kind]: new CompositeConfigItem(
1284
- config.depositWithdrawalCap.configCapacity,
1285
- config.depositWithdrawalCap.configIntervalLengthSeconds
1286
- ),
1287
- [UpdateConfigMode.DeprecatedUpdateDebtWithdrawalCapCurrentTotal.kind]: [], // deprecated
1288
- [UpdateConfigMode.DeprecatedUpdateDepositWithdrawalCapCurrentTotal.kind]: [], // deprecated
1289
- [UpdateConfigMode.UpdateBadDebtLiquidationBonusBps.kind]: config.badDebtLiquidationBonusBps,
1290
- [UpdateConfigMode.UpdateMinLiquidationBonusBps.kind]: config.minLiquidationBonusBps,
1291
- [UpdateConfigMode.UpdateDeleveragingMarginCallPeriod.kind]: config.deleveragingMarginCallPeriodSecs,
1292
- [UpdateConfigMode.UpdateBorrowFactor.kind]: config.borrowFactorPct,
1293
- [UpdateConfigMode.UpdateAssetTier.kind]: config.assetTier,
1294
- [UpdateConfigMode.UpdateElevationGroup.kind]: config.elevationGroups,
1295
- [UpdateConfigMode.UpdateDeleveragingThresholdDecreaseBpsPerDay.kind]: config.deleveragingThresholdDecreaseBpsPerDay,
1296
- [UpdateConfigMode.DeprecatedUpdateMultiplierSideBoost.kind]: [], // deprecated
1297
- [UpdateConfigMode.DeprecatedUpdateMultiplierTagBoost.kind]: [], // deprecated
1298
- [UpdateConfigMode.UpdateReserveStatus.kind]: config.status,
1299
- [UpdateConfigMode.UpdateFarmCollateral.kind]: [], // the farm fields live on the `Reserve` level...
1300
- [UpdateConfigMode.UpdateFarmDebt.kind]: [], // ...so we are not concerned with them in the `ReserveConfig`'s field-by-field update tx
1301
- [UpdateConfigMode.UpdateDisableUsageAsCollateralOutsideEmode.kind]: config.disableUsageAsCollOutsideEmode,
1302
- [UpdateConfigMode.UpdateBlockBorrowingAboveUtilizationPct.kind]: config.utilizationLimitBlockBorrowingAbovePct,
1303
- [UpdateConfigMode.UpdateBlockPriceUsage.kind]: config.tokenInfo.blockPriceUsage,
1304
- [UpdateConfigMode.UpdateBorrowLimitOutsideElevationGroup.kind]: config.borrowLimitOutsideElevationGroup,
1305
- [UpdateConfigMode.UpdateBorrowLimitsInElevationGroupAgainstThisReserve.kind]:
1306
- config.borrowLimitAgainstThisCollateralInElevationGroup,
1307
- [UpdateConfigMode.UpdateHostFixedInterestRateBps.kind]: config.hostFixedInterestRateBps,
1308
- [UpdateConfigMode.UpdateAutodeleverageEnabled.kind]: config.autodeleverageEnabled,
1309
- [UpdateConfigMode.UpdateDeleveragingBonusIncreaseBpsPerDay.kind]: config.deleveragingBonusIncreaseBpsPerDay,
1310
- [UpdateConfigMode.UpdateProtocolOrderExecutionFee.kind]: config.protocolOrderExecutionFeePct,
1311
- [UpdateConfigMode.UpdateProposerAuthorityLock.kind]: config.proposerAuthorityLocked,
1312
- [UpdateConfigMode.UpdateMinDeleveragingBonusBps.kind]: config.minDeleveragingBonusBps,
1313
- [UpdateConfigMode.UpdateBlockCTokenUsage.kind]: config.blockCtokenUsage,
1314
- }));
1315
-
1242
+ export const RESERVE_CONFIG_UPDATER = new EntireReserveConfigUpdater(
1243
+ UpdateConfigMode.fromDecoded,
1244
+ ReserveConfig,
1245
+ (config) => ({
1246
+ [UpdateConfigMode.UpdateLoanToValuePct.kind]: config.loanToValuePct,
1247
+ [UpdateConfigMode.UpdateMaxLiquidationBonusBps.kind]: config.maxLiquidationBonusBps,
1248
+ [UpdateConfigMode.UpdateLiquidationThresholdPct.kind]: config.liquidationThresholdPct,
1249
+ [UpdateConfigMode.UpdateProtocolLiquidationFee.kind]: config.protocolLiquidationFeePct,
1250
+ [UpdateConfigMode.UpdateProtocolTakeRate.kind]: config.protocolTakeRatePct,
1251
+ [UpdateConfigMode.UpdateFeesOriginationFee.kind]: config.fees.originationFeeSf,
1252
+ [UpdateConfigMode.UpdateFeesFlashLoanFee.kind]: config.fees.flashLoanFeeSf,
1253
+ [UpdateConfigMode.DeprecatedUpdateFeesReferralFeeBps.kind]: [], // deprecated
1254
+ [UpdateConfigMode.UpdateDepositLimit.kind]: config.depositLimit,
1255
+ [UpdateConfigMode.UpdateBorrowLimit.kind]: config.borrowLimit,
1256
+ [UpdateConfigMode.UpdateTokenInfoLowerHeuristic.kind]: config.tokenInfo.heuristic.lower,
1257
+ [UpdateConfigMode.UpdateTokenInfoUpperHeuristic.kind]: config.tokenInfo.heuristic.upper,
1258
+ [UpdateConfigMode.UpdateTokenInfoExpHeuristic.kind]: config.tokenInfo.heuristic.exp,
1259
+ [UpdateConfigMode.UpdateTokenInfoTwapDivergence.kind]: config.tokenInfo.maxTwapDivergenceBps,
1260
+ [UpdateConfigMode.UpdateTokenInfoScopeTwap.kind]: config.tokenInfo.scopeConfiguration.twapChain,
1261
+ [UpdateConfigMode.UpdateTokenInfoScopeChain.kind]: config.tokenInfo.scopeConfiguration.priceChain,
1262
+ [UpdateConfigMode.UpdateTokenInfoName.kind]: config.tokenInfo.name,
1263
+ [UpdateConfigMode.UpdateTokenInfoPriceMaxAge.kind]: config.tokenInfo.maxAgePriceSeconds,
1264
+ [UpdateConfigMode.UpdateTokenInfoTwapMaxAge.kind]: config.tokenInfo.maxAgeTwapSeconds,
1265
+ [UpdateConfigMode.UpdateScopePriceFeed.kind]: config.tokenInfo.scopeConfiguration.priceFeed,
1266
+ [UpdateConfigMode.UpdatePythPrice.kind]: config.tokenInfo.pythConfiguration.price,
1267
+ [UpdateConfigMode.UpdateSwitchboardFeed.kind]: config.tokenInfo.switchboardConfiguration.priceAggregator,
1268
+ [UpdateConfigMode.UpdateSwitchboardTwapFeed.kind]: config.tokenInfo.switchboardConfiguration.twapAggregator,
1269
+ [UpdateConfigMode.UpdateBorrowRateCurve.kind]: config.borrowRateCurve,
1270
+ [UpdateConfigMode.UpdateEntireReserveConfig.kind]: [], // technically `config` would be a valid thing here, but we actually do NOT want entire config update among ixs produced for field-by-field updates
1271
+ [UpdateConfigMode.UpdateDebtWithdrawalCap.kind]: new CompositeConfigItem(
1272
+ config.debtWithdrawalCap.configCapacity,
1273
+ config.debtWithdrawalCap.configIntervalLengthSeconds
1274
+ ),
1275
+ [UpdateConfigMode.UpdateDepositWithdrawalCap.kind]: new CompositeConfigItem(
1276
+ config.depositWithdrawalCap.configCapacity,
1277
+ config.depositWithdrawalCap.configIntervalLengthSeconds
1278
+ ),
1279
+ [UpdateConfigMode.DeprecatedUpdateDebtWithdrawalCapCurrentTotal.kind]: [], // deprecated
1280
+ [UpdateConfigMode.DeprecatedUpdateDepositWithdrawalCapCurrentTotal.kind]: [], // deprecated
1281
+ [UpdateConfigMode.UpdateBadDebtLiquidationBonusBps.kind]: config.badDebtLiquidationBonusBps,
1282
+ [UpdateConfigMode.UpdateMinLiquidationBonusBps.kind]: config.minLiquidationBonusBps,
1283
+ [UpdateConfigMode.UpdateDeleveragingMarginCallPeriod.kind]: config.deleveragingMarginCallPeriodSecs,
1284
+ [UpdateConfigMode.UpdateBorrowFactor.kind]: config.borrowFactorPct,
1285
+ [UpdateConfigMode.UpdateAssetTier.kind]: config.assetTier,
1286
+ [UpdateConfigMode.UpdateElevationGroup.kind]: config.elevationGroups,
1287
+ [UpdateConfigMode.UpdateDeleveragingThresholdDecreaseBpsPerDay.kind]: config.deleveragingThresholdDecreaseBpsPerDay,
1288
+ [UpdateConfigMode.DeprecatedUpdateMultiplierSideBoost.kind]: [], // deprecated
1289
+ [UpdateConfigMode.DeprecatedUpdateMultiplierTagBoost.kind]: [], // deprecated
1290
+ [UpdateConfigMode.UpdateReserveStatus.kind]: config.status,
1291
+ [UpdateConfigMode.UpdateFarmCollateral.kind]: [], // the farm fields live on the `Reserve` level...
1292
+ [UpdateConfigMode.UpdateFarmDebt.kind]: [], // ...so we are not concerned with them in the `ReserveConfig`'s field-by-field update tx
1293
+ [UpdateConfigMode.UpdateDisableUsageAsCollateralOutsideEmode.kind]: config.disableUsageAsCollOutsideEmode,
1294
+ [UpdateConfigMode.UpdateBlockBorrowingAboveUtilizationPct.kind]: config.utilizationLimitBlockBorrowingAbovePct,
1295
+ [UpdateConfigMode.UpdateBlockPriceUsage.kind]: config.tokenInfo.blockPriceUsage,
1296
+ [UpdateConfigMode.UpdateBorrowLimitOutsideElevationGroup.kind]: config.borrowLimitOutsideElevationGroup,
1297
+ [UpdateConfigMode.UpdateBorrowLimitsInElevationGroupAgainstThisReserve.kind]:
1298
+ config.borrowLimitAgainstThisCollateralInElevationGroup,
1299
+ [UpdateConfigMode.UpdateHostFixedInterestRateBps.kind]: config.hostFixedInterestRateBps,
1300
+ [UpdateConfigMode.UpdateAutodeleverageEnabled.kind]: config.autodeleverageEnabled,
1301
+ [UpdateConfigMode.UpdateDeleveragingBonusIncreaseBpsPerDay.kind]: config.deleveragingBonusIncreaseBpsPerDay,
1302
+ [UpdateConfigMode.UpdateProtocolOrderExecutionFee.kind]: config.protocolOrderExecutionFeePct,
1303
+ [UpdateConfigMode.UpdateProposerAuthorityLock.kind]: config.proposerAuthorityLocked,
1304
+ [UpdateConfigMode.UpdateMinDeleveragingBonusBps.kind]: config.minDeleveragingBonusBps,
1305
+ [UpdateConfigMode.UpdateBlockCTokenUsage.kind]: config.blockCtokenUsage,
1306
+ })
1307
+ );
1308
+
1309
+ // TODO : this needs to be deprecated
1316
1310
  export async function updateEntireReserveConfigIx(
1317
1311
  signer: TransactionSigner,
1318
1312
  marketAddress: Address,
@@ -1348,19 +1342,20 @@ export function parseForChangesReserveConfigAndGetIxs(
1348
1342
  lendingMarketOwner: TransactionSigner = noopSigner(marketWithAddress.state.lendingMarketOwner)
1349
1343
  ): Promise<Instruction[]> {
1350
1344
  const encodedConfigUpdates = RESERVE_CONFIG_UPDATER.encodeAllUpdates(reserve?.config, reserveConfig);
1351
- encodedConfigUpdates.sort((left, right) => priorityOf(left.mode) - priorityOf(right.mode));
1352
1345
  return Promise.all(
1353
- encodedConfigUpdates.map(async (encodedConfigUpdate) =>
1354
- updateReserveConfigIx(
1355
- lendingMarketOwner,
1356
- marketWithAddress.address,
1357
- reserveAddress,
1358
- encodedConfigUpdate.mode,
1359
- encodedConfigUpdate.value,
1360
- programId,
1361
- shouldSkipValidation(encodedConfigUpdate.mode, reserve)
1346
+ encodedConfigUpdates
1347
+ .filter((encodedConfigUpdate) => !isAdminOnly(encodedConfigUpdate.mode))
1348
+ .map(async (encodedConfigUpdate) =>
1349
+ updateReserveConfigIx(
1350
+ lendingMarketOwner,
1351
+ marketWithAddress.address,
1352
+ reserveAddress,
1353
+ encodedConfigUpdate.mode,
1354
+ encodedConfigUpdate.value,
1355
+ programId,
1356
+ shouldSkipValidation(encodedConfigUpdate.mode, reserve)
1357
+ )
1362
1358
  )
1363
- )
1364
1359
  );
1365
1360
  }
1366
1361
 
@@ -1369,30 +1364,39 @@ export type ReserveWithAddress = {
1369
1364
  state: Reserve;
1370
1365
  };
1371
1366
 
1372
- const NON_VALIDATED_DISCRIMINATORS = [
1373
- UpdateConfigMode.UpdateScopePriceFeed.discriminator,
1374
- UpdateConfigMode.UpdateTokenInfoScopeChain.discriminator,
1375
- UpdateConfigMode.UpdateTokenInfoScopeTwap.discriminator,
1376
- UpdateConfigMode.UpdateTokenInfoExpHeuristic.discriminator,
1377
- UpdateConfigMode.UpdateTokenInfoTwapDivergence.discriminator,
1378
- UpdateConfigMode.UpdateTokenInfoPriceMaxAge.discriminator,
1379
- UpdateConfigMode.UpdateTokenInfoTwapMaxAge.discriminator,
1367
+ // Updating the deposit/borrow limit will automatically unblock usage and force validation inside the smart contract
1368
+ const VALIDATED_DISCRIMINATORS = [
1369
+ UpdateConfigMode.UpdateDepositLimit.discriminator,
1370
+ UpdateConfigMode.UpdateBorrowLimit.discriminator,
1380
1371
  ];
1381
1372
 
1382
1373
  function shouldSkipValidation(mode: UpdateConfigModeKind, reserve: Reserve | undefined): boolean {
1383
- return (
1384
- NON_VALIDATED_DISCRIMINATORS.includes(mode.discriminator) &&
1385
- !reserve?.liquidity.availableAmount.gten(MIN_INITIAL_DEPOSIT)
1386
- );
1374
+ if (VALIDATED_DISCRIMINATORS.includes(mode.discriminator)) {
1375
+ return false;
1376
+ }
1377
+ if (reserve == undefined) {
1378
+ return true;
1379
+ }
1380
+ const is_usage_blocked = reserve.config.depositLimit.isZero() && reserve.config.borrowLimit.isZero();
1381
+ return is_usage_blocked;
1387
1382
  }
1388
1383
 
1389
- function priorityOf(mode: UpdateConfigModeKind): number {
1390
- switch (mode.discriminator) {
1391
- case UpdateConfigMode.UpdateScopePriceFeed.discriminator:
1392
- return 0;
1393
- case UpdateConfigMode.UpdateTokenInfoScopeChain.discriminator:
1394
- return 0;
1395
- default:
1396
- return 1;
1384
+ function isAdminOnly(mode: UpdateConfigModeKind): boolean {
1385
+ for (const adminOnlyMode of ADMIN_ONLY_MODES) {
1386
+ if (mode.discriminator === adminOnlyMode.discriminator) {
1387
+ return true;
1388
+ }
1397
1389
  }
1390
+ return false;
1398
1391
  }
1392
+
1393
+ // These need to be skipped when updating the entire reserve config
1394
+ const ADMIN_ONLY_MODES = [
1395
+ UpdateConfigMode.UpdateProtocolTakeRate,
1396
+ UpdateConfigMode.UpdateProtocolLiquidationFee,
1397
+ UpdateConfigMode.UpdateHostFixedInterestRateBps,
1398
+ UpdateConfigMode.UpdateProtocolOrderExecutionFee,
1399
+ UpdateConfigMode.UpdateFeesOriginationFee,
1400
+ UpdateConfigMode.UpdateFeesFlashLoanFee,
1401
+ UpdateConfigMode.UpdateBlockCTokenUsage,
1402
+ ];
@@ -1,6 +1,6 @@
1
1
  import { Address } from '@solana/kit';
2
2
  import Decimal from 'decimal.js';
3
- import { FarmAndKey, RewardInfo } from '@kamino-finance/farms-sdk';
3
+ import { FarmState, RewardInfo } from '@kamino-finance/farms-sdk';
4
4
 
5
5
  export type ConfigType = Array<MarketConfigType>;
6
6
 
@@ -55,7 +55,7 @@ export type ReserveRewardYield = {
55
55
 
56
56
  export type ReserveFarmInfo = {
57
57
  fetched: boolean;
58
- farms: FarmAndKey[];
58
+ farmStates: FarmState[];
59
59
  };
60
60
 
61
61
  export enum FeeCalculation {