@kamino-finance/klend-sdk 7.3.7-beta.0 → 7.3.8

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,7 +76,11 @@ 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 { getCloseAccountInstruction, getSyncNativeInstruction } from '@solana-program/token-2022';
79
+ import {
80
+ getCloseAccountInstruction,
81
+ getSyncNativeInstruction,
82
+ TOKEN_2022_PROGRAM_ADDRESS,
83
+ } from '@solana-program/token-2022';
80
84
  import { getTransferSolInstruction, SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
81
85
  import { noopSigner } from '../utils/signer';
82
86
 
@@ -3289,8 +3293,13 @@ export class KaminoAction {
3289
3293
  action === 'mint' ||
3290
3294
  (action === 'liquidate' && this.mint === WRAPPED_SOL_MINT); // only sync WSOL amount if liquidator repays SOL which is secondaryMint
3291
3295
 
3296
+ const isValidTokenAccount =
3297
+ userWSOLAccountInfo.exists &&
3298
+ (userWSOLAccountInfo.programAddress === TOKEN_PROGRAM_ADDRESS ||
3299
+ userWSOLAccountInfo.programAddress === TOKEN_2022_PROGRAM_ADDRESS);
3300
+
3292
3301
  const transferLamportsIx = getTransferSolInstruction({
3293
- amount: (userWSOLAccountInfo.exists ? 0n : rentExemptLamports) + (sendAction ? BigInt(safeRepay.toString()) : 0n),
3302
+ amount: (isValidTokenAccount ? 0n : rentExemptLamports) + (sendAction ? BigInt(safeRepay.toString()) : 0n),
3294
3303
  source: this.owner,
3295
3304
  destination: userTokenAccountAddress,
3296
3305
  });
@@ -3312,29 +3321,20 @@ export class KaminoAction {
3312
3321
  },
3313
3322
  { programAddress: TOKEN_PROGRAM_ADDRESS }
3314
3323
  );
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
- }
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}]`);
3338
3338
 
3339
3339
  this.setupIxs.unshift(...preIxs);
3340
3340
  this.setupIxsLabels.unshift(...preIxsLabels);
@@ -191,49 +191,15 @@ export class ConfigUpdater<M extends BorshEnum, C> {
191
191
  */
192
192
  encodeAllUpdates(currentConfig: C | undefined, newConfig: C): EncodedConfigUpdate<M>[] {
193
193
  const updates: EncodedConfigUpdate<M>[] = [];
194
- let ltvUpdate: EncodedConfigUpdate<M> | undefined;
195
- let liquidationThresholdUpdate: EncodedConfigUpdate<M> | undefined;
196
-
197
194
  for (const [mode, itemUpdaters] of this.itemUpdaters.values()) {
198
195
  for (const itemUpdater of itemUpdaters) {
199
196
  const value = itemUpdater.encodeUpdatedItemFrom(currentConfig, newConfig);
200
197
  if (value === undefined) {
201
198
  continue;
202
199
  }
203
-
204
- const update = { mode, value };
205
-
206
- if (mode.kind === 'UpdateLoanToValuePct') {
207
- ltvUpdate = update;
208
- } else if (mode.kind === 'UpdateLiquidationThresholdPct') {
209
- liquidationThresholdUpdate = update;
210
- } else {
211
- updates.push(update);
212
- }
200
+ updates.push({ mode, value });
213
201
  }
214
202
  }
215
-
216
- // Handle ordering of LTV and LiquidationThreshold updates
217
- // ensure LiquidationThreshold >= LTV at all times
218
- if (ltvUpdate && liquidationThresholdUpdate) {
219
- const currentLiquidationThreshold = currentConfig ? (currentConfig as any).liquidationThresholdPct : 0;
220
- const newLiquidationThreshold = (newConfig as any).liquidationThresholdPct;
221
-
222
- if (newLiquidationThreshold > currentLiquidationThreshold) {
223
- // Liquidation threshold is increasing, so it must be updated first
224
- updates.push(liquidationThresholdUpdate);
225
- updates.push(ltvUpdate);
226
- } else {
227
- // All other cases, it's safer to update the LTV first
228
- updates.push(ltvUpdate);
229
- updates.push(liquidationThresholdUpdate);
230
- }
231
- } else if (ltvUpdate) {
232
- updates.push(ltvUpdate);
233
- } else if (liquidationThresholdUpdate) {
234
- updates.push(liquidationThresholdUpdate);
235
- }
236
-
237
203
  return updates;
238
204
  }
239
205
 
@@ -8,6 +8,7 @@ import {
8
8
  scaleDownWads,
9
9
  WAD,
10
10
  RewardInfo,
11
+ RewardType,
11
12
  } from '@kamino-finance/farms-sdk';
12
13
  import {
13
14
  address,
@@ -174,7 +175,7 @@ export type UnstakeAndWithdrawFromFarmIxs = {
174
175
  withdrawIx: Instruction;
175
176
  };
176
177
 
177
- export function getRewardPerTimeUnitSecond(reward: RewardInfo) {
178
+ export function getRewardPerTimeUnitSecond(reward: RewardInfo, farmTotalStakeLamports: Decimal) {
178
179
  const now = new Decimal(new Date().getTime()).div(1000);
179
180
  let rewardPerTimeUnitSecond = new Decimal(0);
180
181
  for (let i = 0; i < reward.rewardScheduleCurve.points.length - 1; i++) {
@@ -196,8 +197,11 @@ export function getRewardPerTimeUnitSecond(reward: RewardInfo) {
196
197
  const rewardTokenDecimals = reward.token.decimals.toNumber();
197
198
  const rewardAmountPerUnitDecimals = new Decimal(10).pow(reward.rewardsPerSecondDecimals.toString());
198
199
  const rewardAmountPerUnitLamports = new Decimal(10).pow(rewardTokenDecimals.toString());
200
+ const constantRewardStakeAdjustment =
201
+ reward.rewardType === RewardType.Constant.discriminator ? farmTotalStakeLamports : new Decimal(1);
199
202
 
200
203
  const rpsAdjusted = new Decimal(rewardPerTimeUnitSecond.toString())
204
+ .mul(constantRewardStakeAdjustment)
201
205
  .div(rewardAmountPerUnitDecimals)
202
206
  .div(rewardAmountPerUnitLamports);
203
207
 
@@ -24,6 +24,7 @@ import {
24
24
  INITIAL_COLLATERAL_RATE,
25
25
  lendingMarketAuthPda,
26
26
  MarketWithAddress,
27
+ MIN_INITIAL_DEPOSIT,
27
28
  ONE_HUNDRED_PCT_IN_BPS,
28
29
  reservePdas,
29
30
  SLOTS_PER_DAY,
@@ -48,7 +49,7 @@ import {
48
49
  UpdateReserveConfigArgs,
49
50
  } from '../lib';
50
51
  import { aprToApy, KaminoPrices } from '@kamino-finance/kliquidity-sdk';
51
- import { FarmState, RewardInfo } from '@kamino-finance/farms-sdk';
52
+ import { FarmAndKey, FarmState, RewardInfo } from '@kamino-finance/farms-sdk';
52
53
  import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
53
54
  import { maxBigInt } from '../utils/bigint';
54
55
  import { getCreateAccountInstruction, SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
@@ -69,7 +70,7 @@ export class KaminoReserve {
69
70
 
70
71
  tokenOraclePrice: TokenOracleData;
71
72
  stats: ReserveDataType;
72
- private farmData: ReserveFarmInfo = { fetched: false, farmStates: [] };
73
+ private farmData: ReserveFarmInfo = { fetched: false, farms: [] };
73
74
 
74
75
  private rpc: Rpc<KaminoReserveRpcApi>;
75
76
  private readonly recentSlotDurationMs: number;
@@ -812,22 +813,22 @@ export class KaminoReserve {
812
813
 
813
814
  async loadFarmStates() {
814
815
  if (!this.farmData.fetched) {
815
- const farmStates: FarmState[] = [];
816
+ const farmStates: FarmAndKey[] = [];
816
817
  const debtFarmAddress = this.getDebtFarmAddress();
817
818
  if (isSome(debtFarmAddress)) {
818
819
  const farmState = await FarmState.fetch(this.rpc, debtFarmAddress.value);
819
820
  if (farmState !== null) {
820
- farmStates.push(farmState);
821
+ farmStates.push({ farmState, key: debtFarmAddress.value });
821
822
  }
822
823
  }
823
824
  const collateralFarmAddress = this.getCollateralFarmAddress();
824
825
  if (isSome(collateralFarmAddress)) {
825
826
  const farmState = await FarmState.fetch(this.rpc, collateralFarmAddress.value);
826
827
  if (farmState !== null) {
827
- farmStates.push(farmState);
828
+ farmStates.push({ farmState, key: collateralFarmAddress.value });
828
829
  }
829
830
  }
830
- this.farmData.farmStates = farmStates;
831
+ this.farmData.farms = farmStates;
831
832
  this.farmData.fetched = true;
832
833
  }
833
834
  }
@@ -838,14 +839,19 @@ export class KaminoReserve {
838
839
  throw Error('KaminoMarket must call loadReserves.');
839
840
  }
840
841
 
841
- const isDebtReward = this.state.farmDebt === this.address;
842
842
  await this.loadFarmStates();
843
843
  const yields: ReserveRewardYield[] = [];
844
- for (const farmState of this.farmData.farmStates) {
845
- for (const rewardInfo of farmState.rewardInfos.filter(
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(
846
847
  (x) => x.token.mint !== DEFAULT_PUBLIC_KEY && !x.rewardsAvailable.isZero()
847
848
  )) {
848
- const { apy, apr } = this.calculateRewardYield(prices, rewardInfo, isDebtReward);
849
+ const { apy, apr } = this.calculateRewardYield(
850
+ prices,
851
+ rewardInfo,
852
+ isDebtReward,
853
+ new Decimal(farmAndKey.farmState.totalActiveStakeScaled.toString())
854
+ );
849
855
  if (apy.isZero() && apr.isZero()) {
850
856
  continue;
851
857
  }
@@ -855,9 +861,14 @@ export class KaminoReserve {
855
861
  return yields;
856
862
  }
857
863
 
858
- private calculateRewardYield(prices: KaminoPrices, rewardInfo: RewardInfo, isDebtReward: boolean) {
864
+ calculateRewardYield(
865
+ prices: KaminoPrices,
866
+ rewardInfo: RewardInfo,
867
+ isDebtReward: boolean,
868
+ farmTotalStakeLamports: Decimal
869
+ ) {
859
870
  const mintAddress = this.getLiquidityMint();
860
- const rewardPerTimeUnitSecond = getRewardPerTimeUnitSecond(rewardInfo);
871
+ const rewardPerTimeUnitSecond = getRewardPerTimeUnitSecond(rewardInfo, farmTotalStakeLamports);
861
872
  const reserveToken = prices.spot[mintAddress.toString()];
862
873
  const rewardToken = prices.spot[rewardInfo.token.mint.toString()];
863
874
 
@@ -1302,7 +1313,6 @@ export const RESERVE_CONFIG_UPDATER = new ConfigUpdater(UpdateConfigMode.fromDec
1302
1313
  [UpdateConfigMode.UpdateBlockCTokenUsage.kind]: config.blockCtokenUsage,
1303
1314
  }));
1304
1315
 
1305
- // TODO : this needs to be deprecated
1306
1316
  export async function updateEntireReserveConfigIx(
1307
1317
  signer: TransactionSigner,
1308
1318
  marketAddress: Address,
@@ -1359,60 +1369,30 @@ export type ReserveWithAddress = {
1359
1369
  state: Reserve;
1360
1370
  };
1361
1371
 
1362
- // Updating the deposit/borrow limit will automatically unblock usage and force validation inside the smart contract
1363
- const VALIDATED_DISCRIMINATORS = [
1364
- UpdateConfigMode.UpdateDepositLimit.discriminator,
1365
- UpdateConfigMode.UpdateBorrowLimit.discriminator,
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,
1366
1380
  ];
1367
1381
 
1368
1382
  function shouldSkipValidation(mode: UpdateConfigModeKind, reserve: Reserve | undefined): boolean {
1369
- if (VALIDATED_DISCRIMINATORS.includes(mode.discriminator)) {
1370
- return false;
1371
- }
1372
- if (reserve == undefined) {
1373
- return true;
1374
- }
1375
- const is_usage_blocked = reserve.config.depositLimit.isZero() && reserve.config.borrowLimit.isZero();
1376
- return is_usage_blocked;
1383
+ return (
1384
+ NON_VALIDATED_DISCRIMINATORS.includes(mode.discriminator) &&
1385
+ !reserve?.liquidity.availableAmount.gten(MIN_INITIAL_DEPOSIT)
1386
+ );
1377
1387
  }
1378
1388
 
1379
- // Lowest priority gets updated first
1380
1389
  function priorityOf(mode: UpdateConfigModeKind): number {
1381
1390
  switch (mode.discriminator) {
1382
1391
  case UpdateConfigMode.UpdateScopePriceFeed.discriminator:
1383
1392
  return 0;
1384
- case UpdateConfigMode.UpdateTokenInfoScopeTwap.discriminator:
1385
- return 0;
1386
1393
  case UpdateConfigMode.UpdateTokenInfoScopeChain.discriminator:
1387
1394
  return 0;
1388
- case UpdateConfigMode.UpdateTokenInfoLowerHeuristic.discriminator:
1389
- return 0;
1390
- case UpdateConfigMode.UpdateTokenInfoUpperHeuristic.discriminator:
1391
- return 0;
1392
- case UpdateConfigMode.UpdateTokenInfoExpHeuristic.discriminator:
1393
- return 0;
1394
- case UpdateConfigMode.UpdateTokenInfoTwapDivergence.discriminator:
1395
- return 0;
1396
- case UpdateConfigMode.UpdateTokenInfoName.discriminator:
1397
- return 0;
1398
- case UpdateConfigMode.UpdateTokenInfoPriceMaxAge.discriminator:
1399
- return 0;
1400
- case UpdateConfigMode.UpdateTokenInfoTwapMaxAge.discriminator:
1401
- return 0;
1402
- case UpdateConfigMode.UpdateDeleveragingBonusIncreaseBpsPerDay.discriminator:
1403
- return priorityOf(new UpdateConfigMode.UpdateAutodeleverageEnabled()) - 1;
1404
- case UpdateConfigMode.UpdateDeleveragingMarginCallPeriod.discriminator:
1405
- return priorityOf(new UpdateConfigMode.UpdateAutodeleverageEnabled()) - 1;
1406
- case UpdateConfigMode.UpdateDeleveragingThresholdDecreaseBpsPerDay.discriminator:
1407
- return priorityOf(new UpdateConfigMode.UpdateAutodeleverageEnabled()) - 1;
1408
- case UpdateConfigMode.UpdateAutodeleverageEnabled.discriminator:
1409
- return 4;
1410
- // Always update last bc we cannot skip validation
1411
- case UpdateConfigMode.UpdateDepositLimit.discriminator:
1412
- return 63;
1413
- case UpdateConfigMode.UpdateBorrowLimit.discriminator:
1414
- return 63;
1415
1395
  default:
1416
- return 10;
1396
+ return 1;
1417
1397
  }
1418
1398
  }
@@ -1,6 +1,6 @@
1
1
  import { Address } from '@solana/kit';
2
2
  import Decimal from 'decimal.js';
3
- import { FarmState, RewardInfo } from '@kamino-finance/farms-sdk';
3
+ import { FarmAndKey, 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
- farmStates: FarmState[];
58
+ farms: FarmAndKey[];
59
59
  };
60
60
 
61
61
  export enum FeeCalculation {