@kamino-finance/klend-sdk 5.0.1 → 5.0.2-fix

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.
@@ -362,6 +362,9 @@ export class KaminoObligation {
362
362
  collateralExchangeRates: Map<PublicKey, Decimal>
363
363
  ): ObligationCollateral[] {
364
364
  const newDeposits: ObligationCollateral[] = [];
365
+ const depositIndex = obligationDeposits.findIndex((deposit) => deposit.depositReserve.equals(changeReserve));
366
+
367
+ // Always copy the previous deposits and modify the changeReserve one if it exists
365
368
  for (let i = 0; i < obligationDeposits.length; i++) {
366
369
  if (obligationDeposits[i].depositReserve.equals(changeReserve)) {
367
370
  const coll: ObligationCollateralFields = { ...obligationDeposits[i] };
@@ -375,6 +378,25 @@ export class KaminoObligation {
375
378
  }
376
379
  }
377
380
 
381
+ if (depositIndex === -1) {
382
+ // If the reserve is not in the obligation, we add it
383
+ const firstBorrowIndexAvailable = obligationDeposits.findIndex((deposit) =>
384
+ deposit.depositReserve.equals(PublicKey.default)
385
+ );
386
+
387
+ if (firstBorrowIndexAvailable === -1) {
388
+ throw new Error('No available borrows to modify');
389
+ }
390
+
391
+ const coll: ObligationCollateralFields = { ...obligationDeposits[firstBorrowIndexAvailable] };
392
+ const exchangeRate = collateralExchangeRates.get(changeReserve)!;
393
+ const changeInCollateral = new Decimal(changeInLamports).mul(exchangeRate).toFixed(0);
394
+ coll.depositedAmount = new BN(positiveOrZero(new Decimal(changeInCollateral)).toString());
395
+ coll.depositReserve = changeReserve;
396
+
397
+ newDeposits[firstBorrowIndexAvailable] = new ObligationCollateral(coll);
398
+ }
399
+
378
400
  return newDeposits;
379
401
  }
380
402
 
@@ -387,17 +409,30 @@ export class KaminoObligation {
387
409
  const newBorrows: ObligationLiquidity[] = [];
388
410
  const borrowIndex = obligationBorrows.findIndex((borrow) => borrow.borrowReserve.equals(changeReserve));
389
411
 
390
- if (borrowIndex !== -1) {
391
- const borrow: ObligationLiquidityFields = { ...obligationBorrows[borrowIndex] };
392
- const newBorrowedAmount: Decimal = new Fraction(borrow.borrowedAmountSf).toDecimal().add(changeInLamports);
393
- const newBorrowedAmountSf = Fraction.fromDecimal(positiveOrZero(newBorrowedAmount)).getValue();
394
- borrow.borrowedAmountSf = newBorrowedAmountSf;
395
- newBorrows.push(new ObligationLiquidity(borrow));
396
- } else {
412
+ // Always copy the previous borrows and modify the changeReserve one if it exists
413
+ for (let i = 0; i < obligationBorrows.length; i++) {
414
+ if (obligationBorrows[i].borrowReserve.equals(changeReserve)) {
415
+ const borrow: ObligationLiquidityFields = { ...obligationBorrows[borrowIndex] };
416
+ const newBorrowedAmount: Decimal = new Fraction(borrow.borrowedAmountSf).toDecimal().add(changeInLamports);
417
+ const newBorrowedAmountSf = Fraction.fromDecimal(positiveOrZero(newBorrowedAmount)).getValue();
418
+ borrow.borrowedAmountSf = newBorrowedAmountSf;
419
+
420
+ newBorrows.push(new ObligationLiquidity(borrow));
421
+ } else {
422
+ newBorrows.push(obligationBorrows[i]);
423
+ }
424
+ }
425
+
426
+ if (borrowIndex === -1) {
427
+ // If the reserve is not in the obligation, we add it
397
428
  const firstBorrowIndexAvailable = obligationBorrows.findIndex((borrow) =>
398
429
  borrow.borrowReserve.equals(PublicKey.default)
399
430
  );
400
431
 
432
+ if (firstBorrowIndexAvailable === -1) {
433
+ throw new Error('No available borrows to modify');
434
+ }
435
+
401
436
  const borrow: ObligationLiquidityFields = { ...obligationBorrows[firstBorrowIndexAvailable] };
402
437
  borrow.borrowedAmountSf = Fraction.fromDecimal(new Decimal(changeInLamports)).getValue();
403
438
  borrow.borrowReserve = changeReserve;
@@ -405,7 +440,7 @@ export class KaminoObligation {
405
440
  padding: [],
406
441
  value: [Fraction.fromDecimal(cumulativeBorrowRate).getValue(), new BN(0), new BN(0), new BN(0)],
407
442
  };
408
- newBorrows.push(new ObligationLiquidity(borrow));
443
+ newBorrows[firstBorrowIndexAvailable] = new ObligationLiquidity(borrow);
409
444
  }
410
445
 
411
446
  return newBorrows;
@@ -433,7 +468,23 @@ export class KaminoObligation {
433
468
  const { amountCollateral, amountDebt, action, mintCollateral, mintDebt, market } = params;
434
469
  let newStats = { ...this.refreshedStats };
435
470
 
436
- const { collateralExchangeRates } = KaminoObligation.getRatesForObligation(market, this.state, params.slot);
471
+ const collateralReservePk = mintCollateral ? market.getReserveByMint(mintCollateral)!.address : undefined;
472
+ const debtReservePk = mintDebt ? market.getReserveByMint(mintDebt)!.address : undefined;
473
+
474
+ const additionalReserves = [];
475
+ if (collateralReservePk !== undefined) {
476
+ additionalReserves.push(collateralReservePk);
477
+ }
478
+ if (debtReservePk !== undefined) {
479
+ additionalReserves.push(debtReservePk);
480
+ }
481
+
482
+ const { collateralExchangeRates } = KaminoObligation.getRatesForObligation(
483
+ market,
484
+ this.state,
485
+ params.slot,
486
+ additionalReserves
487
+ );
437
488
 
438
489
  const elevationGroup = params.elevationGroupOverride ?? this.state.elevationGroup;
439
490
 
@@ -444,8 +495,6 @@ export class KaminoObligation {
444
495
  // so we have to recalculate the entire position, not just an updated deposit or borrow
445
496
  // as both LTVs and borrow factors can change, affecting all calcs
446
497
 
447
- const collateralReservePk = mintCollateral ? market.getReserveByMint(mintCollateral)!.address : undefined;
448
- const debtReservePk = mintDebt ? market.getReserveByMint(mintDebt)!.address : undefined;
449
498
  const debtReserveCumulativeBorrowRate = mintDebt
450
499
  ? market.getReserveByMint(mintDebt)!.getCumulativeBorrowRate()
451
500
  : undefined;
@@ -453,6 +502,14 @@ export class KaminoObligation {
453
502
  let newObligationDeposits = this.state.deposits;
454
503
  let newObligationBorrows = this.state.borrows;
455
504
 
505
+ // Print deposits and borrows before
506
+ for (const deposit of this.state.deposits) {
507
+ console.log(`Before Deposit: ${deposit.depositReserve.toBase58()} - ${deposit.depositedAmount}`);
508
+ }
509
+ for (const borrow of this.state.borrows) {
510
+ console.log(`Before Borrow: ${borrow.borrowReserve.toBase58()} - ${borrow.borrowedAmountSf}`);
511
+ }
512
+
456
513
  switch (action) {
457
514
  case 'deposit': {
458
515
  if (amountCollateral === undefined || mintCollateral === undefined) {
@@ -567,6 +624,14 @@ export class KaminoObligation {
567
624
  null
568
625
  );
569
626
 
627
+ // Print deposits and borrows after
628
+ for (const deposit of newObligationDeposits) {
629
+ console.log(`After Deposit: ${deposit.depositReserve.toBase58()} - ${deposit.depositedAmount}`);
630
+ }
631
+ for (const borrow of newObligationBorrows) {
632
+ console.log(`After Borrow: ${borrow.borrowReserve.toBase58()} - ${borrow.borrowedAmountSf}`);
633
+ }
634
+
570
635
  newStats = refreshedStats;
571
636
  newDeposits = deposits;
572
637
  newBorrows = borrows;
@@ -921,7 +986,11 @@ export class KaminoObligation {
921
986
  throw new Error('Reserve not found');
922
987
  }
923
988
 
924
- const liquidityAvailable = reserve.getLiquidityAvailableForDebtReserveGivenCaps(market, [elevationGroup])[0];
989
+ const liquidityAvailable = reserve.getLiquidityAvailableForDebtReserveGivenCaps(
990
+ market,
991
+ [elevationGroup],
992
+ Array.from(this.deposits.keys())
993
+ )[0];
925
994
  const maxBorrowAmount = this.getBorrowPower(market, liquidityMint, slot, elevationGroup);
926
995
 
927
996
  if (elevationGroup === this.state.elevationGroup) {
@@ -1213,7 +1282,8 @@ export class KaminoObligation {
1213
1282
  public static getRatesForObligation(
1214
1283
  kaminoMarket: KaminoMarket,
1215
1284
  obligation: Obligation,
1216
- slot: number
1285
+ slot: number,
1286
+ additionalReserves: PublicKey[] = []
1217
1287
  ): {
1218
1288
  collateralExchangeRates: Map<PublicKey, Decimal>;
1219
1289
  cumulativeBorrowRates: Map<PublicKey, Decimal>;
@@ -1221,12 +1291,14 @@ export class KaminoObligation {
1221
1291
  const collateralExchangeRates = KaminoObligation.getCollateralExchangeRatesForObligation(
1222
1292
  kaminoMarket,
1223
1293
  obligation,
1224
- slot
1294
+ slot,
1295
+ additionalReserves
1225
1296
  );
1226
1297
  const cumulativeBorrowRates = KaminoObligation.getCumulativeBorrowRatesForObligation(
1227
1298
  kaminoMarket,
1228
1299
  obligation,
1229
- slot
1300
+ slot,
1301
+ additionalReserves
1230
1302
  );
1231
1303
 
1232
1304
  return {
@@ -1249,20 +1321,35 @@ export class KaminoObligation {
1249
1321
  static getCollateralExchangeRatesForObligation(
1250
1322
  kaminoMarket: KaminoMarket,
1251
1323
  obligation: Obligation,
1252
- slot: number
1324
+ slot: number,
1325
+ additionalReserves: PublicKey[]
1253
1326
  ): Map<PublicKey, Decimal> {
1254
1327
  const collateralExchangeRates = new PubkeyHashMap<PublicKey, Decimal>();
1328
+
1329
+ // Create a set of all reserves coming from deposit plus additional reserves
1330
+ const allReserves = new Set<PublicKey>();
1255
1331
  for (let i = 0; i < obligation.deposits.length; i++) {
1256
1332
  const deposit = obligation.deposits[i];
1257
- if (isNotNullPubkey(deposit.depositReserve) && !collateralExchangeRates.has(deposit.depositReserve)) {
1258
- const reserve = kaminoMarket.getReserveByAddress(deposit.depositReserve)!;
1259
- const collateralExchangeRate = reserve.getEstimatedCollateralExchangeRate(
1260
- slot,
1261
- kaminoMarket.state.referralFeeBps
1262
- );
1263
- collateralExchangeRates.set(reserve.address, collateralExchangeRate);
1333
+ if (isNotNullPubkey(deposit.depositReserve)) {
1334
+ allReserves.add(deposit.depositReserve);
1335
+ }
1336
+ }
1337
+ for (let i = 0; i < additionalReserves.length; i++) {
1338
+ if (isNotNullPubkey(additionalReserves[i])) {
1339
+ allReserves.add(additionalReserves[i]);
1264
1340
  }
1265
1341
  }
1342
+
1343
+ // Run through all reserves and get the exchange rate
1344
+ for (const reserve of allReserves) {
1345
+ const reserveInstance = kaminoMarket.getReserveByAddress(reserve)!;
1346
+ const collateralExchangeRate = reserveInstance.getEstimatedCollateralExchangeRate(
1347
+ slot,
1348
+ kaminoMarket.state.referralFeeBps
1349
+ );
1350
+ collateralExchangeRates.set(reserve, collateralExchangeRate);
1351
+ }
1352
+
1266
1353
  return collateralExchangeRates;
1267
1354
  }
1268
1355
 
@@ -1285,16 +1372,39 @@ export class KaminoObligation {
1285
1372
  }
1286
1373
  }
1287
1374
 
1288
- static getCumulativeBorrowRatesForObligation(kaminoMarket: KaminoMarket, obligation: Obligation, slot: number) {
1289
- const cumulativeBorrowRates = new PubkeyHashMap<PublicKey, Decimal>();
1375
+ static getCumulativeBorrowRatesForObligation(
1376
+ kaminoMarket: KaminoMarket,
1377
+ obligation: Obligation,
1378
+ slot: number,
1379
+ additionalReserves: PublicKey[] = []
1380
+ ): Map<PublicKey, Decimal> {
1381
+ const allReserves = new Set<PublicKey>();
1290
1382
  for (let i = 0; i < obligation.borrows.length; i++) {
1291
1383
  const borrow = obligation.borrows[i];
1292
- if (isNotNullPubkey(borrow.borrowReserve) && !cumulativeBorrowRates.has(borrow.borrowReserve)) {
1293
- const reserve = kaminoMarket.getReserveByAddress(borrow.borrowReserve)!;
1294
- const cumulativeBorrowRate = reserve.getEstimatedCumulativeBorrowRate(slot, kaminoMarket.state.referralFeeBps);
1295
- cumulativeBorrowRates.set(reserve.address, cumulativeBorrowRate);
1384
+ if (isNotNullPubkey(borrow.borrowReserve)) {
1385
+ allReserves.add(borrow.borrowReserve);
1296
1386
  }
1297
1387
  }
1388
+
1389
+ // Add additional reserves
1390
+ for (let i = 0; i < additionalReserves.length; i++) {
1391
+ if (isNotNullPubkey(additionalReserves[i])) {
1392
+ allReserves.add(additionalReserves[i]);
1393
+ }
1394
+ }
1395
+
1396
+ const cumulativeBorrowRates = new PubkeyHashMap<PublicKey, Decimal>();
1397
+
1398
+ // Run through all reserves and get the cumulative borrow rate
1399
+ for (const reserve of allReserves) {
1400
+ const reserveInstance = kaminoMarket.getReserveByAddress(reserve)!;
1401
+ const cumulativeBorrowRate = reserveInstance.getEstimatedCumulativeBorrowRate(
1402
+ slot,
1403
+ kaminoMarket.state.referralFeeBps
1404
+ );
1405
+ cumulativeBorrowRates.set(reserve, cumulativeBorrowRate);
1406
+ }
1407
+
1298
1408
  return cumulativeBorrowRates;
1299
1409
  }
1300
1410
 
@@ -570,7 +570,7 @@ export class KaminoReserve {
570
570
  return Decimal.max(new Decimal(0), maxBorrowAmount);
571
571
  }
572
572
 
573
- calcSimulatedBorrowAPR(
573
+ calcSimulatedBorrowRate(
574
574
  amount: Decimal,
575
575
  action: ActionType,
576
576
  slot: number,
@@ -580,7 +580,20 @@ export class KaminoReserve {
580
580
  const slotAdjustmentFactor = this.slotAdjustmentFactor();
581
581
  const newUtilization = this.calcSimulatedUtilizationRatio(amount, action, slot, referralFeeBps, outflowAmount);
582
582
  const curve = truncateBorrowCurve(this.state.config.borrowRateCurve.points);
583
- return getBorrowRate(newUtilization, curve) * slotAdjustmentFactor + this.getFixedHostInterestRate().toNumber();
583
+ return getBorrowRate(newUtilization, curve) * slotAdjustmentFactor;
584
+ }
585
+
586
+ calcSimulatedBorrowAPR(
587
+ amount: Decimal,
588
+ action: ActionType,
589
+ slot: number,
590
+ referralFeeBps: number,
591
+ outflowAmount?: Decimal
592
+ ) {
593
+ return (
594
+ this.calcSimulatedBorrowRate(amount, action, slot, referralFeeBps, outflowAmount) +
595
+ this.getFixedHostInterestRate().toNumber()
596
+ );
584
597
  }
585
598
 
586
599
  calcSimulatedSupplyAPR(
@@ -591,7 +604,7 @@ export class KaminoReserve {
591
604
  outflowAmount?: Decimal
592
605
  ) {
593
606
  const newUtilization = this.calcSimulatedUtilizationRatio(amount, action, slot, referralFeeBps, outflowAmount);
594
- const simulatedBorrowAPR = this.calcSimulatedBorrowAPR(amount, action, slot, referralFeeBps, outflowAmount);
607
+ const simulatedBorrowAPR = this.calcSimulatedBorrowRate(amount, action, slot, referralFeeBps, outflowAmount);
595
608
  const protocolTakeRatePct = 1 - this.state.config.protocolTakeRatePct / 100;
596
609
 
597
610
  return newUtilization * simulatedBorrowAPR * protocolTakeRatePct;
@@ -1033,7 +1046,11 @@ export class KaminoReserve {
1033
1046
  }
1034
1047
 
1035
1048
  /* This takes into account all the caps */
1036
- getLiquidityAvailableForDebtReserveGivenCaps(market: KaminoMarket, elevationGroups: number[]): Decimal[] {
1049
+ getLiquidityAvailableForDebtReserveGivenCaps(
1050
+ market: KaminoMarket,
1051
+ elevationGroups: number[],
1052
+ collateralReserves: PublicKey[] = []
1053
+ ): Decimal[] {
1037
1054
  const caps = this.getBorrowCapForReserve(market);
1038
1055
 
1039
1056
  const liquidityAvailable = this.getLiquidityAvailableAmount();
@@ -1069,7 +1086,16 @@ export class KaminoReserve {
1069
1086
  (x) => x.elevationGroup === elevationGroup
1070
1087
  );
1071
1088
  if (capsGivenEgroup.length > 0) {
1072
- remainingInsideEmodeCaps = Decimal.min(...capsGivenEgroup.map((x) => x.maxDebt.minus(x.currentValue)));
1089
+ remainingInsideEmodeCaps = Decimal.min(
1090
+ ...capsGivenEgroup.map((x) => {
1091
+ // check reserve is part of collReserves array
1092
+ if (collateralReserves.find((collateralReserve) => collateralReserve.equals(x.collateralReserve))) {
1093
+ return x.maxDebt.minus(x.currentValue);
1094
+ } else {
1095
+ return new Decimal(U64_MAX);
1096
+ }
1097
+ })
1098
+ );
1073
1099
  }
1074
1100
  return Decimal.min(
1075
1101
  positiveOrZero(liquidityAvailable),
@@ -77,7 +77,11 @@ export function calcMaxWithdrawCollateral(
77
77
  const collPosition = obligation.getDepositByReserve(collReserve.address)!;
78
78
  const initialCollValue = collPosition.amount.floor().div(collReserve.getMintFactor()).mul(collOraclePx);
79
79
  const remainingDebtAmountLamports = debtPosition.amount.sub(repayAmountLamports);
80
- const remainingDebtBfWeightedValue = remainingDebtAmountLamports.ceil().div(debtReserve.getMintFactor()).mul(debtBorrowFactor).mul(debtOraclePx);
80
+ const remainingDebtBfWeightedValue = remainingDebtAmountLamports
81
+ .ceil()
82
+ .div(debtReserve.getMintFactor())
83
+ .mul(debtBorrowFactor)
84
+ .mul(debtOraclePx);
81
85
 
82
86
  let isClosingPosition = false;
83
87
  if (remainingDebtAmountLamports.lte(new Decimal(0)) && obligation.getBorrows().length === 1) {
@@ -132,7 +132,11 @@ export async function getRepayWithCollSwapInputs<QuoteResponse>({
132
132
  const swapQuote = await quoter(swapQuoteInputs, uniqueKlendAccounts);
133
133
 
134
134
  const swapQuotePxDebtToColl = swapQuote.priceAInB;
135
- const collSwapInLamports = flashRepayAmountLamports.div(debtReserve.getMintFactor()).div(swapQuotePxDebtToColl).mul(collReserve.getMintFactor()).ceil();
135
+ const collSwapInLamports = flashRepayAmountLamports
136
+ .div(debtReserve.getMintFactor())
137
+ .div(swapQuotePxDebtToColl)
138
+ .mul(collReserve.getMintFactor())
139
+ .ceil();
136
140
 
137
141
  return {
138
142
  swapInputs: {