@evaafi/sdk 0.9.5 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/api/math.d.ts +16 -3
  2. package/dist/api/math.js +356 -33
  3. package/dist/api/parser.js +34 -12
  4. package/dist/constants/assets/assetId.d.ts +2 -0
  5. package/dist/constants/assets/assetId.js +3 -0
  6. package/dist/constants/assets/mainnet.d.ts +2 -0
  7. package/dist/constants/assets/mainnet.js +13 -1
  8. package/dist/constants/general/mainnet.d.ts +2 -0
  9. package/dist/constants/general/mainnet.js +4 -1
  10. package/dist/constants/pools/mainnet.d.ts +2 -0
  11. package/dist/constants/pools/mainnet.js +45 -1
  12. package/dist/constants/pools/testnet.d.ts +3 -2
  13. package/dist/constants/pools/testnet.js +16 -1
  14. package/dist/oracles/collectors/ClassicCollector.js +13 -18
  15. package/dist/oracles/collectors/FakeCollector.d.ts +30 -0
  16. package/dist/oracles/collectors/FakeCollector.js +128 -0
  17. package/dist/oracles/collectors/index.d.ts +1 -0
  18. package/dist/oracles/collectors/index.js +1 -0
  19. package/dist/types/Master.d.ts +8 -2
  20. package/dist/types/User.d.ts +7 -3
  21. package/package.json +1 -1
  22. package/src/api/math.ts +504 -40
  23. package/src/api/parser.ts +57 -12
  24. package/src/constants/assets/assetId.ts +4 -0
  25. package/src/constants/assets/mainnet.ts +24 -0
  26. package/src/constants/general/mainnet.ts +4 -0
  27. package/src/constants/pools/mainnet.ts +53 -1
  28. package/src/constants/pools/testnet.ts +30 -5
  29. package/src/oracles/collectors/ClassicCollector.ts +10 -14
  30. package/src/oracles/collectors/FakeCollector.ts +153 -0
  31. package/src/oracles/collectors/index.ts +1 -0
  32. package/src/types/Master.ts +9 -2
  33. package/src/types/User.ts +7 -3
package/src/api/math.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Dictionary } from '@ton/core';
2
- import { UNDEFINED_ASSET } from '../constants/assets';
3
- import {
2
+ import type {
4
3
  AgregatedBalances,
5
4
  AssetApy,
6
5
  AssetConfig,
@@ -12,14 +11,18 @@ import {
12
11
  MasterConstants,
13
12
  PoolConfig,
14
13
  } from '../types/Master';
14
+ import { UNDEFINED_ASSET } from '../constants/assets';
15
15
  import {
16
16
  BalanceChangeType,
17
17
  BalanceType,
18
- HealthParamsArgs,
19
- LiquidationData,
20
- PredictAPYArgs,
21
- PredictHealthFactorArgs,
22
- UserBalance,
18
+ UserData,
19
+ UserDataActive,
20
+ UserLiteData,
21
+ type HealthParamsArgs,
22
+ type LiquidationData,
23
+ type PredictAPYArgs,
24
+ type PredictHealthFactorArgs,
25
+ type UserBalance,
23
26
  } from '../types/User';
24
27
  import { addReserve, isBadDebt } from './liquidation';
25
28
 
@@ -177,6 +180,309 @@ export function checkNotInDebtAtAll(principals: Dictionary<bigint, bigint>): boo
177
180
  return principals.values().every((x) => x >= 0n);
178
181
  }
179
182
 
183
+ export function exceedsStandardBorrowLimit(
184
+ principals: Dictionary<bigint, bigint>,
185
+ assetsConfig: ExtendedAssetsConfig,
186
+ assetsData: ExtendedAssetsData,
187
+ prices: Dictionary<bigint, bigint>,
188
+ masterConstants: MasterConstants,
189
+ ): boolean {
190
+ let standardBorrowLimit = 0n;
191
+ let totalBorrow = 0n;
192
+ for (const [assetId, principal] of principals) {
193
+ if (principal === 0n) continue;
194
+ const assetConfig = assetsConfig.get(assetId);
195
+ const assetData = assetsData.get(assetId);
196
+ if (!assetConfig || !assetData || !prices.has(assetId)) continue;
197
+ const price = prices.get(assetId)!;
198
+ const assetBalance = presentValue(assetData.sRate, assetData.bRate, principal, masterConstants);
199
+ const assetWorth = (assetBalance.amount * price) / 10n ** assetConfig.decimals;
200
+ if (assetBalance.type === BalanceType.supply) {
201
+ standardBorrowLimit += (assetWorth * assetConfig.collateralFactor) / masterConstants.ASSET_COEFFICIENT_SCALE;
202
+ } else if (assetBalance.type === BalanceType.borrow && assetConfig.dust < assetBalance.amount) {
203
+ totalBorrow += assetWorth;
204
+ }
205
+ }
206
+ return totalBorrow > standardBorrowLimit;
207
+ }
208
+
209
+ export function determineHeCategory(
210
+ assetsConfig: ExtendedAssetsConfig,
211
+ principals: Dictionary<bigint, bigint>,
212
+ poolConfig?: PoolConfig,
213
+ ): number {
214
+ const heCategoryByAssetId = new Map<bigint, number>();
215
+ if (poolConfig) {
216
+ for (const heConfig of poolConfig.poolAssetsHEConfig) {
217
+ for (const asset of heConfig.assets) {
218
+ heCategoryByAssetId.set(asset.assetId, heConfig.heCategory);
219
+ }
220
+ }
221
+ }
222
+
223
+ let heCategory = -1;
224
+
225
+ for (const [assetID, principal] of principals) {
226
+ if (principal >= 0n) {
227
+ continue;
228
+ }
229
+
230
+ const heCategoryForAsset = heCategoryByAssetId.get(assetID) ?? assetsConfig.get(assetID)?.heCategory ?? 0;
231
+ if (heCategoryForAsset <= 0) {
232
+ return -1;
233
+ }
234
+
235
+ if (heCategory === -1) {
236
+ heCategory = heCategoryForAsset;
237
+ continue;
238
+ }
239
+
240
+ if (heCategory !== heCategoryForAsset) {
241
+ return -1;
242
+ }
243
+ }
244
+
245
+ return heCategory > 0 ? heCategory : -1;
246
+ }
247
+
248
+ export function calculateRepayToExitEMode(
249
+ assetsConfig: ExtendedAssetsConfig,
250
+ assetsData: ExtendedAssetsData,
251
+ principals: Dictionary<bigint, bigint>,
252
+ prices: Dictionary<bigint, bigint>,
253
+ poolConfig: PoolConfig,
254
+ ): {
255
+ activeHeCategory: number;
256
+ requiredRepayInUsd: bigint;
257
+ repayAmounts: Dictionary<bigint, bigint>;
258
+ enoughPriceData: boolean;
259
+ } {
260
+ const repayAmounts = Dictionary.empty<bigint, bigint>();
261
+
262
+ const heCategoryRaw = determineHeCategory(assetsConfig, principals, poolConfig);
263
+ const activeHeCategory =
264
+ heCategoryRaw > 0 && exceedsStandardBorrowLimit(principals, assetsConfig, assetsData, prices, poolConfig.masterConstants)
265
+ ? heCategoryRaw
266
+ : -1;
267
+
268
+ if (activeHeCategory <= 0) {
269
+ return { activeHeCategory: -1, requiredRepayInUsd: 0n, repayAmounts, enoughPriceData: true };
270
+ }
271
+
272
+ const availableToBorrowStandard = getAvailableToBorrow(
273
+ assetsConfig,
274
+ assetsData,
275
+ principals,
276
+ prices,
277
+ poolConfig.masterConstants,
278
+ );
279
+ const requiredRepayInUsd = bigIntMax(0n, -availableToBorrowStandard);
280
+
281
+ if (requiredRepayInUsd === 0n) {
282
+ return {
283
+ activeHeCategory,
284
+ requiredRepayInUsd,
285
+ repayAmounts,
286
+ enoughPriceData: true,
287
+ };
288
+ }
289
+
290
+ let totalDebtWorth = 0n;
291
+ const debtEntries: Array<{
292
+ assetID: bigint;
293
+ debtAmount: bigint;
294
+ debtWorth: bigint;
295
+ decimals: bigint;
296
+ price: bigint;
297
+ }> = [];
298
+
299
+ for (const [assetID, principal] of principals) {
300
+ if (principal >= 0n) {
301
+ continue;
302
+ }
303
+
304
+ if (!prices.has(assetID)) {
305
+ return {
306
+ activeHeCategory,
307
+ requiredRepayInUsd,
308
+ repayAmounts: Dictionary.empty<bigint, bigint>(),
309
+ enoughPriceData: false,
310
+ };
311
+ }
312
+
313
+ const assetConfig = assetsConfig.get(assetID);
314
+ const assetData = assetsData.get(assetID);
315
+ if (!assetData || !assetConfig) {
316
+ continue;
317
+ }
318
+
319
+ const price = prices.get(assetID)!;
320
+ const debtAmount = calculatePresentValue(assetData.bRate, -principal, poolConfig.masterConstants);
321
+ const debtWorth = mulDiv(debtAmount, price, 10n ** assetConfig.decimals);
322
+
323
+ totalDebtWorth += debtWorth;
324
+ debtEntries.push({ assetID, debtAmount, debtWorth, decimals: assetConfig.decimals, price });
325
+ }
326
+
327
+ if (totalDebtWorth === 0n) {
328
+ return {
329
+ activeHeCategory,
330
+ requiredRepayInUsd,
331
+ repayAmounts,
332
+ enoughPriceData: true,
333
+ };
334
+ }
335
+
336
+ let distributedRepayWorth = 0n;
337
+ debtEntries.forEach((entry, index) => {
338
+ const repayWorth =
339
+ index === debtEntries.length - 1
340
+ ? requiredRepayInUsd - distributedRepayWorth
341
+ : mulDiv(requiredRepayInUsd, entry.debtWorth, totalDebtWorth);
342
+
343
+ distributedRepayWorth += repayWorth;
344
+
345
+ const repayAmount = bigIntMin(entry.debtAmount, mulDivC(repayWorth, 10n ** entry.decimals, entry.price));
346
+ if (repayAmount > 0n) {
347
+ repayAmounts.set(entry.assetID, repayAmount);
348
+ }
349
+ });
350
+
351
+ if (distributedRepayWorth < requiredRepayInUsd && debtEntries.length > 0) {
352
+ const lastEntry = debtEntries[debtEntries.length - 1];
353
+ const currentRepayAmount = repayAmounts.get(lastEntry.assetID) ?? 0n;
354
+ if (currentRepayAmount < lastEntry.debtAmount) {
355
+ repayAmounts.set(lastEntry.assetID, bigIntMin(lastEntry.debtAmount, currentRepayAmount + 1n));
356
+ }
357
+ }
358
+
359
+ return {
360
+ activeHeCategory,
361
+ requiredRepayInUsd,
362
+ repayAmounts,
363
+ enoughPriceData: true,
364
+ };
365
+ }
366
+
367
+ export function getAvailableToBorrowWithEMode(
368
+ assetsConfig: ExtendedAssetsConfig,
369
+ assetsData: ExtendedAssetsData,
370
+ principals: Dictionary<bigint, bigint>,
371
+ prices: Dictionary<bigint, bigint>,
372
+ masterConstants: MasterConstants,
373
+ poolConfig?: PoolConfig,
374
+ ): { availableToBorrow: bigint; heCategory: number } {
375
+ const heCategoryByAssetId = new Map<bigint, number>();
376
+ if (poolConfig) {
377
+ for (const heConfig of poolConfig.poolAssetsHEConfig) {
378
+ for (const asset of heConfig.assets) {
379
+ heCategoryByAssetId.set(asset.assetId, heConfig.heCategory);
380
+ }
381
+ }
382
+ }
383
+
384
+ const calculateForHeCategory = (heCategory: number): bigint => {
385
+ let borrowLimit = 0n;
386
+ let borrowAmount = 0n;
387
+
388
+ for (const assetID of principals.keys()) {
389
+ const principal = principals.get(assetID) as bigint;
390
+
391
+ if (principal == 0n) {
392
+ continue;
393
+ }
394
+
395
+ if (!prices.has(assetID)) {
396
+ return 0n;
397
+ }
398
+
399
+ const assetConfig = assetsConfig.get(assetID) as AssetConfig;
400
+ const assetData = assetsData.get(assetID) as ExtendedAssetData;
401
+ const price = prices.get(assetID) as bigint;
402
+
403
+ if (principal < 0n) {
404
+ borrowAmount += mulDivC(
405
+ calculatePresentValue(assetData.bRate, -principal, masterConstants),
406
+ price,
407
+ 10n ** assetConfig.decimals,
408
+ );
409
+ } else {
410
+ const suppliedAssetInDollars = mulDiv(
411
+ calculatePresentValue(assetData.sRate, principal, masterConstants),
412
+ price,
413
+ 10n ** assetConfig.decimals,
414
+ );
415
+
416
+ const heCategoryForAsset = heCategoryByAssetId.get(assetID) ?? assetConfig.heCategory;
417
+
418
+ const collateralFactor =
419
+ heCategory > 0 && heCategoryForAsset === heCategory
420
+ ? assetConfig.heCollateralFactor
421
+ : assetConfig.collateralFactor;
422
+
423
+ borrowLimit += mulDiv(
424
+ suppliedAssetInDollars,
425
+ collateralFactor,
426
+ masterConstants.ASSET_COEFFICIENT_SCALE,
427
+ );
428
+ }
429
+ }
430
+
431
+ return borrowLimit - borrowAmount;
432
+ };
433
+
434
+ const activeHeCategory = determineHeCategory(assetsConfig, principals, poolConfig);
435
+ if (activeHeCategory > 0) {
436
+ return {
437
+ availableToBorrow: calculateForHeCategory(activeHeCategory),
438
+ heCategory: activeHeCategory,
439
+ };
440
+ }
441
+
442
+ const availableToBorrowWithoutEmode = calculateForHeCategory(0);
443
+ if (!checkNotInDebtAtAll(principals)) {
444
+ return {
445
+ availableToBorrow: availableToBorrowWithoutEmode,
446
+ heCategory: 0,
447
+ };
448
+ }
449
+
450
+ const availableHeCategories = new Set<number>();
451
+ if (poolConfig && poolConfig.poolAssetsHEConfig.length > 0) {
452
+ for (const heConfig of poolConfig.poolAssetsHEConfig) {
453
+ const hasSupplyInCategory = heConfig.assets.some((asset) => (principals.get(asset.assetId) ?? 0n) > 0n);
454
+ if (hasSupplyInCategory && heConfig.heCategory > 0) {
455
+ availableHeCategories.add(heConfig.heCategory);
456
+ }
457
+ }
458
+ } else {
459
+ for (const [assetID, principal] of principals) {
460
+ if (principal <= 0n) {
461
+ continue;
462
+ }
463
+ const heCategory = (assetsConfig.get(assetID) as AssetConfig).heCategory;
464
+ if (heCategory > 0) {
465
+ availableHeCategories.add(heCategory);
466
+ }
467
+ }
468
+ }
469
+
470
+ let bestHeCategory = 0;
471
+ let bestAvailableToBorrow = availableToBorrowWithoutEmode;
472
+ for (const heCategory of availableHeCategories) {
473
+ const availableToBorrow = calculateForHeCategory(heCategory);
474
+ if (availableToBorrow > bestAvailableToBorrow) {
475
+ bestAvailableToBorrow = availableToBorrow;
476
+ bestHeCategory = heCategory;
477
+ }
478
+ }
479
+
480
+ return {
481
+ availableToBorrow: bestAvailableToBorrow,
482
+ heCategory: bestHeCategory,
483
+ };
484
+ }
485
+
180
486
  export function getAgregatedBalances(
181
487
  assetsData: ExtendedAssetsData,
182
488
  assetsConfig: ExtendedAssetsConfig,
@@ -215,7 +521,7 @@ export function calculateMaximumWithdrawAmount(
215
521
  assetsData: ExtendedAssetsData,
216
522
  principals: Dictionary<bigint, bigint>,
217
523
  prices: Dictionary<bigint, bigint>,
218
- masterConstants: MasterConstants,
524
+ poolConfig: PoolConfig,
219
525
  assetId: bigint,
220
526
  ): bigint {
221
527
  let withdrawAmountMax = 0n;
@@ -225,7 +531,12 @@ export function calculateMaximumWithdrawAmount(
225
531
  const oldPrincipal = principals.get(assetId) as bigint;
226
532
 
227
533
  if (oldPrincipal > assetConfig.dust) {
228
- const oldPresentValue = presentValue(assetData.sRate, assetData.bRate, oldPrincipal, masterConstants);
534
+ const oldPresentValue = presentValue(
535
+ assetData.sRate,
536
+ assetData.bRate,
537
+ oldPrincipal,
538
+ poolConfig.masterConstants,
539
+ );
229
540
  if (checkNotInDebtAtAll(principals)) {
230
541
  withdrawAmountMax = oldPresentValue.amount;
231
542
  } else {
@@ -233,7 +544,13 @@ export function calculateMaximumWithdrawAmount(
233
544
  return 0n;
234
545
  }
235
546
 
236
- const borrowable = getAvailableToBorrow(assetsConfig, assetsData, principals, prices, masterConstants);
547
+ const borrowable = getAvailableToBorrow(
548
+ assetsConfig,
549
+ assetsData,
550
+ principals,
551
+ prices,
552
+ poolConfig.masterConstants,
553
+ );
237
554
  const price = prices.get(assetId) as bigint;
238
555
 
239
556
  let maxAmountToReclaim = 0n;
@@ -241,14 +558,28 @@ export function calculateMaximumWithdrawAmount(
241
558
  if (assetConfig.collateralFactor == 0n) {
242
559
  maxAmountToReclaim = oldPresentValue.amount;
243
560
  } else if (price > 0) {
561
+ const { availableToBorrow: borrowable, heCategory } = getAvailableToBorrowWithEMode(
562
+ assetsConfig,
563
+ assetsData,
564
+ principals,
565
+ prices,
566
+ poolConfig.masterConstants,
567
+ poolConfig,
568
+ );
569
+
570
+ const collateralFactor =
571
+ heCategory > 0 && assetConfig.heCategory === heCategory
572
+ ? assetConfig.heCollateralFactor
573
+ : assetConfig.collateralFactor;
574
+
244
575
  maxAmountToReclaim = bigIntMax(
245
576
  0n,
246
577
  mulDiv(
247
- mulDiv(borrowable, masterConstants.ASSET_COEFFICIENT_SCALE, assetConfig.collateralFactor),
578
+ mulDiv(borrowable, poolConfig.masterConstants.ASSET_COEFFICIENT_SCALE, collateralFactor),
248
579
  10n ** assetConfig.decimals,
249
580
  price,
250
581
  ) -
251
- calculatePresentValue(assetData.sRate, assetConfig.dust, masterConstants) / 2n,
582
+ calculatePresentValue(assetData.sRate, assetConfig.dust, poolConfig.masterConstants) / 2n,
252
583
  );
253
584
  }
254
585
 
@@ -262,7 +593,14 @@ export function calculateMaximumWithdrawAmount(
262
593
  const price = prices.get(assetId) as bigint;
263
594
 
264
595
  return (
265
- (getAvailableToBorrow(assetsConfig, assetsData, principals, prices, masterConstants) *
596
+ (getAvailableToBorrowWithEMode(
597
+ assetsConfig,
598
+ assetsData,
599
+ principals,
600
+ prices,
601
+ poolConfig.masterConstants,
602
+ poolConfig,
603
+ ).availableToBorrow *
266
604
  10n ** assetConfig.decimals) /
267
605
  price
268
606
  );
@@ -357,11 +695,16 @@ export function calculateHealthParams(parameters: HealthParamsArgs) {
357
695
  const { principals, prices, assetsData, assetsConfig, poolConfig } = parameters;
358
696
 
359
697
  const { ASSET_LIQUIDATION_THRESHOLD_SCALE } = poolConfig.masterConstants;
698
+ let activeHeCategory = determineHeCategory(assetsConfig, principals, poolConfig);
360
699
 
361
700
  let totalSupply = 0n;
362
701
  let totalDebt = 0n;
363
702
  let totalLimit = 0n;
364
703
 
704
+ if (activeHeCategory > 0 && !exceedsStandardBorrowLimit(principals, assetsConfig, assetsData, prices, poolConfig.masterConstants)) {
705
+ activeHeCategory = -1;
706
+ }
707
+
365
708
  for (const asset of poolConfig.poolAssetsConfig) {
366
709
  if (!principals.has(asset.assetId)) continue;
367
710
  const assetPrincipal = principals.get(asset.assetId)!;
@@ -380,8 +723,13 @@ export function calculateHealthParams(parameters: HealthParamsArgs) {
380
723
  const assetBalance = presentValue(sRate, bRate, assetPrincipal, poolConfig.masterConstants);
381
724
  const assetWorth = (assetBalance.amount * assetPrice) / assetScale;
382
725
  if (assetBalance.type === BalanceType.supply) {
726
+ const liquidationThreshold =
727
+ activeHeCategory > 0 && assetConfig.heCategory === activeHeCategory
728
+ ? assetConfig.heLiquidationThreshold
729
+ : assetConfig.liquidationThreshold;
730
+
383
731
  totalSupply += assetWorth;
384
- totalLimit += (assetWorth * assetConfig.liquidationThreshold) / ASSET_LIQUIDATION_THRESHOLD_SCALE;
732
+ totalLimit += (assetWorth * liquidationThreshold) / ASSET_LIQUIDATION_THRESHOLD_SCALE;
385
733
  } else if (assetBalance.type === BalanceType.borrow && assetConfig.dust < assetBalance.amount) {
386
734
  totalDebt += assetWorth;
387
735
  }
@@ -399,6 +747,7 @@ export function calculateHealthParams(parameters: HealthParamsArgs) {
399
747
  totalDebt,
400
748
  totalLimit,
401
749
  totalSupply,
750
+ activeHeCategory,
402
751
  isLiquidatable: _isLiquidable,
403
752
  isBadDebt: _isBadDebt,
404
753
  };
@@ -427,6 +776,11 @@ export function calculateLiquidationData(
427
776
  let totalDebt = 0n;
428
777
  let totalLimit = 0n;
429
778
 
779
+ let activeHeCategory = determineHeCategory(assetsConfig, principals, poolConfig);
780
+ if (activeHeCategory > 0 && !exceedsStandardBorrowLimit(principals, assetsConfig, assetsData, prices, poolConfig.masterConstants)) {
781
+ activeHeCategory = -1;
782
+ }
783
+
430
784
  const { ASSET_SRATE_SCALE, ASSET_BRATE_SCALE, COLLATERAL_WORTH_THRESHOLD } = poolConfig.masterConstants;
431
785
 
432
786
  for (const asset of poolConfig.poolAssetsConfig) {
@@ -441,8 +795,12 @@ export function calculateLiquidationData(
441
795
 
442
796
  const assetWorth = (bigAbs(balance) * prices.get(asset.assetId)!) / 10n ** assetConfig.decimals;
443
797
  if (balance > 0) {
444
- totalLimit +=
445
- (assetWorth * assetConfig.liquidationThreshold) / poolConfig.masterConstants.ASSET_COEFFICIENT_SCALE;
798
+ const liquidationThreshold =
799
+ activeHeCategory > 0 && assetConfig.heCategory === activeHeCategory
800
+ ? assetConfig.heLiquidationThreshold
801
+ : assetConfig.liquidationThreshold;
802
+
803
+ totalLimit += (assetWorth * liquidationThreshold) / poolConfig.masterConstants.ASSET_COEFFICIENT_SCALE;
446
804
  // get the greatest collateral
447
805
  if (assetWorth > collateralValue) {
448
806
  collateralValue = assetWorth;
@@ -516,46 +874,152 @@ export function calculateLiquidationData(
516
874
  }
517
875
 
518
876
  export function predictHealthFactor(args: PredictHealthFactorArgs): number {
519
- const healthParams = calculateHealthParams(args);
877
+ const { principals, prices, assetsData, assetsConfig, poolConfig } = args;
520
878
  const assetId = args.asset.assetId;
879
+ const changeType = args.balanceChangeType;
880
+ const currentAmount = args.amount;
521
881
 
522
- const assetConfig = args.assetsConfig.get(assetId)!;
523
- const assetPrice = Number(args.prices.get(assetId)!);
882
+ const heCategoryByAssetId = new Map<bigint, number>();
883
+ for (const heConfig of poolConfig.poolAssetsHEConfig) {
884
+ for (const asset of heConfig.assets) {
885
+ heCategoryByAssetId.set(asset.assetId, heConfig.heCategory);
886
+ }
887
+ }
524
888
 
525
- let totalLimit = Number(healthParams.totalLimit);
526
- let totalBorrow = Number(healthParams.totalDebt);
889
+ const projectedBalances = new Map<bigint, bigint>();
527
890
 
528
- const currentAmount = args.amount;
891
+ for (const asset of poolConfig.poolAssetsConfig) {
892
+ if (!principals.has(asset.assetId)) {
893
+ continue;
894
+ }
529
895
 
530
- const decimals = Number(assetConfig.decimals);
896
+ const assetPrincipal = principals.get(asset.assetId)!;
897
+ const assetConfig = assetsConfig.get(asset.assetId)!;
898
+ const assetData = assetsData.get(asset.assetId)!;
899
+ const balance = presentValue(assetData.sRate, assetData.bRate, assetPrincipal, poolConfig.masterConstants);
531
900
 
532
- const currentBalance = (assetPrice * Number(currentAmount)) / Math.pow(10, decimals);
533
- const changeType = args.balanceChangeType;
901
+ if (balance.type === BalanceType.supply) {
902
+ projectedBalances.set(asset.assetId, balance.amount);
903
+ } else if (balance.type === BalanceType.borrow) {
904
+ projectedBalances.set(asset.assetId, -balance.amount);
905
+ }
906
+ }
534
907
 
535
908
  if (currentAmount != null && currentAmount != 0n) {
909
+ const currentSignedBalance = projectedBalances.get(assetId) ?? 0n;
910
+ const actionAssetConfig = assetsConfig.get(assetId)!;
911
+ let newSignedBalance = currentSignedBalance;
912
+
536
913
  if (changeType == BalanceChangeType.Borrow) {
537
- totalBorrow +=
538
- currentBalance *
539
- (1 +
540
- Number(assetConfig.originationFee) /
541
- Number(args.poolConfig.masterConstants.ASSET_ORIGINATION_FEE_SCALE));
914
+ const borrowWithFee =
915
+ currentAmount +
916
+ mulDivC(
917
+ currentAmount,
918
+ actionAssetConfig.originationFee,
919
+ poolConfig.masterConstants.ASSET_ORIGINATION_FEE_SCALE,
920
+ );
921
+ newSignedBalance -= borrowWithFee;
542
922
  } else if (changeType == BalanceChangeType.Repay) {
543
- totalBorrow -= currentBalance;
923
+ newSignedBalance += currentAmount;
544
924
  } else if (changeType == BalanceChangeType.Withdraw) {
545
- totalLimit -=
546
- (currentBalance * Number(assetConfig.liquidationThreshold)) /
547
- Number(args.poolConfig.masterConstants.ASSET_COEFFICIENT_SCALE);
925
+ newSignedBalance -= currentAmount;
548
926
  } else if (changeType == BalanceChangeType.Supply) {
549
- totalLimit +=
550
- (currentBalance * Number(assetConfig.liquidationThreshold)) /
551
- Number(args.poolConfig.masterConstants.ASSET_COEFFICIENT_SCALE);
927
+ newSignedBalance += currentAmount;
552
928
  }
929
+
930
+ projectedBalances.set(assetId, newSignedBalance);
553
931
  }
554
- if (Number(totalLimit) == 0) {
555
- return 1;
932
+
933
+ const getHeCategoryForAsset = (assetID: bigint, assetConfig: AssetConfig): number => {
934
+ return heCategoryByAssetId.get(assetID) ?? assetConfig.heCategory;
935
+ };
936
+
937
+ let projectedActiveHeCategory = -1;
938
+ for (const asset of poolConfig.poolAssetsConfig) {
939
+ const signedBalance = projectedBalances.get(asset.assetId) ?? 0n;
940
+ if (signedBalance >= 0n) {
941
+ continue;
942
+ }
943
+
944
+ const assetConfig = assetsConfig.get(asset.assetId)!;
945
+ const heCategory = getHeCategoryForAsset(asset.assetId, assetConfig);
946
+ if (heCategory <= 0) {
947
+ projectedActiveHeCategory = -1;
948
+ break;
949
+ }
950
+
951
+ if (projectedActiveHeCategory === -1) {
952
+ projectedActiveHeCategory = heCategory;
953
+ continue;
954
+ }
955
+
956
+ if (projectedActiveHeCategory !== heCategory) {
957
+ projectedActiveHeCategory = -1;
958
+ break;
959
+ }
556
960
  }
557
961
 
558
- return Math.min(Math.max(1 - totalBorrow / totalLimit, 0), 1); // let's limit a result to zero below and one above
962
+ const calculateProjectedHealthFactor = (heCategoryForCalculation: number): number => {
963
+ let totalLimit = 0n;
964
+ let totalBorrow = 0n;
965
+
966
+ for (const asset of poolConfig.poolAssetsConfig) {
967
+ const signedBalance = projectedBalances.get(asset.assetId) ?? 0n;
968
+ if (signedBalance === 0n) {
969
+ continue;
970
+ }
971
+
972
+ const assetConfig = assetsConfig.get(asset.assetId)!;
973
+ const price = prices.get(asset.assetId)!;
974
+ const assetWorth = (bigAbs(signedBalance) * price) / 10n ** assetConfig.decimals;
975
+
976
+ if (signedBalance > 0n) {
977
+ const heCategory = getHeCategoryForAsset(asset.assetId, assetConfig);
978
+ const liquidationThreshold =
979
+ heCategoryForCalculation > 0 && heCategory === heCategoryForCalculation
980
+ ? assetConfig.heLiquidationThreshold
981
+ : assetConfig.liquidationThreshold;
982
+ totalLimit +=
983
+ (assetWorth * liquidationThreshold) / poolConfig.masterConstants.ASSET_LIQUIDATION_THRESHOLD_SCALE;
984
+ continue;
985
+ }
986
+
987
+ const borrowAmount = -signedBalance;
988
+ if (borrowAmount > assetConfig.dust) {
989
+ totalBorrow += assetWorth;
990
+ }
991
+ }
992
+
993
+ if (totalLimit === 0n) {
994
+ return 1;
995
+ }
996
+
997
+ return Math.min(Math.max(1 - Number(totalBorrow) / Number(totalLimit), 0), 1);
998
+ };
999
+
1000
+ if (projectedActiveHeCategory > 0) {
1001
+ let standardBorrowLimit = 0n;
1002
+ let projectedTotalBorrow = 0n;
1003
+ for (const asset of poolConfig.poolAssetsConfig) {
1004
+ const signedBalance = projectedBalances.get(asset.assetId) ?? 0n;
1005
+ if (signedBalance === 0n) continue;
1006
+ const assetConfig = assetsConfig.get(asset.assetId)!;
1007
+ const price = prices.get(asset.assetId)!;
1008
+ const assetWorth = (bigAbs(signedBalance) * price) / 10n ** assetConfig.decimals;
1009
+ if (signedBalance > 0n) {
1010
+ standardBorrowLimit +=
1011
+ (assetWorth * assetConfig.collateralFactor) /
1012
+ poolConfig.masterConstants.ASSET_COEFFICIENT_SCALE;
1013
+ } else if (-signedBalance > assetConfig.dust) {
1014
+ projectedTotalBorrow += assetWorth;
1015
+ }
1016
+ }
1017
+
1018
+ if (projectedTotalBorrow > standardBorrowLimit) {
1019
+ return calculateProjectedHealthFactor(projectedActiveHeCategory);
1020
+ }
1021
+ }
1022
+ return calculateProjectedHealthFactor(-1);
559
1023
  }
560
1024
 
561
1025
  /**