@defisaver/positions-sdk 2.1.161-portfolio-api-2-dev → 2.1.161-portfolio-api-3-dev

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 (166) hide show
  1. package/cjs/aaveV2/index.js +2 -0
  2. package/cjs/aaveV3/index.d.ts +39 -0
  3. package/cjs/aaveV3/index.js +237 -2
  4. package/cjs/aaveV4/lend.d.ts +2 -43
  5. package/cjs/curveUsd/index.d.ts +5 -2
  6. package/cjs/curveUsd/index.js +30 -15
  7. package/cjs/fluid/index.d.ts +5 -0
  8. package/cjs/fluid/index.js +36 -1
  9. package/cjs/helpers/aaveHelpers/index.d.ts +9 -1
  10. package/cjs/helpers/aaveHelpers/index.js +26 -6
  11. package/cjs/helpers/aaveV4Helpers/index.js +1 -0
  12. package/cjs/helpers/compoundHelpers/index.js +2 -0
  13. package/cjs/helpers/curveUsdHelpers/index.d.ts +3 -1
  14. package/cjs/helpers/curveUsdHelpers/index.js +15 -2
  15. package/cjs/helpers/fluidHelpers/index.js +1 -0
  16. package/cjs/helpers/liquityV2Helpers/index.js +1 -0
  17. package/cjs/helpers/llamaLendHelpers/index.js +1 -0
  18. package/cjs/helpers/makerHelpers/index.d.ts +1 -1
  19. package/cjs/helpers/makerHelpers/index.js +2 -2
  20. package/cjs/helpers/morphoBlueHelpers/index.js +2 -0
  21. package/cjs/helpers/morphoMidnightHelpers/index.js +2 -0
  22. package/cjs/helpers/sparkHelpers/index.js +1 -0
  23. package/cjs/liquity/index.js +15 -2
  24. package/cjs/liquityV2/index.d.ts +5 -0
  25. package/cjs/liquityV2/index.js +37 -19
  26. package/cjs/maker/index.d.ts +7 -2
  27. package/cjs/maker/index.js +44 -12
  28. package/cjs/markets/index.d.ts +1 -0
  29. package/cjs/markets/index.js +3 -1
  30. package/cjs/markets/maker/index.d.ts +1 -0
  31. package/cjs/markets/maker/index.js +13 -0
  32. package/cjs/moneymarket/moneymarketCommonService.d.ts +9 -0
  33. package/cjs/moneymarket/moneymarketCommonService.js +13 -1
  34. package/cjs/portfolio/index.d.ts +17 -5
  35. package/cjs/portfolio/index.js +583 -394
  36. package/cjs/portfolio/lendingProtocols.d.ts +184 -0
  37. package/cjs/portfolio/lendingProtocols.js +196 -0
  38. package/cjs/savings/index.d.ts +8 -2
  39. package/cjs/savings/index.js +37 -5
  40. package/cjs/savings/morphoVaults/index.d.ts +1 -1
  41. package/cjs/savings/morphoVaults/index.js +56 -18
  42. package/cjs/savings/morphoVaults/options.d.ts +2 -0
  43. package/cjs/savings/morphoVaults/options.js +61 -1
  44. package/cjs/spark/index.js +1 -0
  45. package/cjs/staking/staking.d.ts +20 -0
  46. package/cjs/staking/staking.js +19 -1
  47. package/cjs/types/aave.d.ts +3 -0
  48. package/cjs/types/aaveV4.d.ts +45 -0
  49. package/cjs/types/compound.d.ts +2 -0
  50. package/cjs/types/curveUsd.d.ts +6 -0
  51. package/cjs/types/fluid.d.ts +1 -0
  52. package/cjs/types/liquity.d.ts +4 -0
  53. package/cjs/types/liquityV2.d.ts +2 -0
  54. package/cjs/types/maker.d.ts +4 -0
  55. package/cjs/types/morphoBlue.d.ts +2 -0
  56. package/cjs/types/morphoMidnight.d.ts +1 -0
  57. package/cjs/types/portfolio.d.ts +64 -6
  58. package/cjs/types/savings/index.d.ts +9 -1
  59. package/cjs/types/savings/morphoVaults.d.ts +10 -2
  60. package/cjs/types/savings/morphoVaults.js +9 -1
  61. package/cjs/types/spark.d.ts +2 -0
  62. package/esm/aaveV2/index.js +2 -0
  63. package/esm/aaveV3/index.d.ts +39 -0
  64. package/esm/aaveV3/index.js +231 -2
  65. package/esm/aaveV4/lend.d.ts +2 -43
  66. package/esm/curveUsd/index.d.ts +5 -2
  67. package/esm/curveUsd/index.js +30 -15
  68. package/esm/fluid/index.d.ts +5 -0
  69. package/esm/fluid/index.js +33 -0
  70. package/esm/helpers/aaveHelpers/index.d.ts +9 -1
  71. package/esm/helpers/aaveHelpers/index.js +23 -5
  72. package/esm/helpers/aaveV4Helpers/index.js +1 -0
  73. package/esm/helpers/compoundHelpers/index.js +2 -0
  74. package/esm/helpers/curveUsdHelpers/index.d.ts +3 -1
  75. package/esm/helpers/curveUsdHelpers/index.js +16 -3
  76. package/esm/helpers/fluidHelpers/index.js +1 -0
  77. package/esm/helpers/liquityV2Helpers/index.js +1 -0
  78. package/esm/helpers/llamaLendHelpers/index.js +2 -1
  79. package/esm/helpers/makerHelpers/index.d.ts +1 -1
  80. package/esm/helpers/makerHelpers/index.js +2 -2
  81. package/esm/helpers/morphoBlueHelpers/index.js +2 -0
  82. package/esm/helpers/morphoMidnightHelpers/index.js +2 -0
  83. package/esm/helpers/sparkHelpers/index.js +1 -0
  84. package/esm/liquity/index.js +16 -3
  85. package/esm/liquityV2/index.d.ts +5 -0
  86. package/esm/liquityV2/index.js +35 -18
  87. package/esm/maker/index.d.ts +7 -2
  88. package/esm/maker/index.js +44 -14
  89. package/esm/markets/index.d.ts +1 -0
  90. package/esm/markets/index.js +1 -0
  91. package/esm/markets/maker/index.d.ts +1 -0
  92. package/esm/markets/maker/index.js +10 -0
  93. package/esm/moneymarket/moneymarketCommonService.d.ts +9 -0
  94. package/esm/moneymarket/moneymarketCommonService.js +11 -0
  95. package/esm/portfolio/index.d.ts +17 -5
  96. package/esm/portfolio/index.js +584 -398
  97. package/esm/portfolio/lendingProtocols.d.ts +184 -0
  98. package/esm/portfolio/lendingProtocols.js +189 -0
  99. package/esm/savings/index.d.ts +8 -2
  100. package/esm/savings/index.js +35 -4
  101. package/esm/savings/morphoVaults/index.d.ts +1 -1
  102. package/esm/savings/morphoVaults/index.js +57 -19
  103. package/esm/savings/morphoVaults/options.d.ts +2 -0
  104. package/esm/savings/morphoVaults/options.js +61 -1
  105. package/esm/spark/index.js +1 -0
  106. package/esm/staking/staking.d.ts +20 -0
  107. package/esm/staking/staking.js +17 -0
  108. package/esm/types/aave.d.ts +3 -0
  109. package/esm/types/aaveV4.d.ts +45 -0
  110. package/esm/types/compound.d.ts +2 -0
  111. package/esm/types/curveUsd.d.ts +6 -0
  112. package/esm/types/fluid.d.ts +1 -0
  113. package/esm/types/liquity.d.ts +4 -0
  114. package/esm/types/liquityV2.d.ts +2 -0
  115. package/esm/types/maker.d.ts +4 -0
  116. package/esm/types/morphoBlue.d.ts +2 -0
  117. package/esm/types/morphoMidnight.d.ts +1 -0
  118. package/esm/types/portfolio.d.ts +64 -6
  119. package/esm/types/savings/index.d.ts +9 -1
  120. package/esm/types/savings/morphoVaults.d.ts +10 -2
  121. package/esm/types/savings/morphoVaults.js +8 -0
  122. package/esm/types/spark.d.ts +2 -0
  123. package/package.json +3 -2
  124. package/src/aaveV2/index.ts +2 -0
  125. package/src/aaveV3/index.ts +291 -1
  126. package/src/aaveV4/lend.ts +2 -48
  127. package/src/curveUsd/index.ts +33 -16
  128. package/src/fluid/index.ts +40 -0
  129. package/src/helpers/aaveHelpers/index.ts +27 -7
  130. package/src/helpers/aaveV4Helpers/index.ts +1 -0
  131. package/src/helpers/compoundHelpers/index.ts +2 -0
  132. package/src/helpers/curveUsdHelpers/index.ts +23 -3
  133. package/src/helpers/fluidHelpers/index.ts +1 -0
  134. package/src/helpers/liquityV2Helpers/index.ts +1 -0
  135. package/src/helpers/llamaLendHelpers/index.ts +2 -1
  136. package/src/helpers/makerHelpers/index.ts +2 -1
  137. package/src/helpers/morphoBlueHelpers/index.ts +2 -0
  138. package/src/helpers/morphoMidnightHelpers/index.ts +2 -0
  139. package/src/helpers/sparkHelpers/index.ts +1 -0
  140. package/src/liquity/index.ts +17 -3
  141. package/src/liquityV2/index.ts +45 -19
  142. package/src/maker/index.ts +77 -30
  143. package/src/markets/index.ts +2 -1
  144. package/src/markets/maker/index.ts +10 -0
  145. package/src/moneymarket/moneymarketCommonService.ts +15 -0
  146. package/src/portfolio/index.ts +428 -215
  147. package/src/portfolio/lendingProtocols.ts +281 -0
  148. package/src/savings/index.ts +45 -5
  149. package/src/savings/morphoVaults/index.ts +73 -21
  150. package/src/savings/morphoVaults/options.ts +67 -1
  151. package/src/spark/index.ts +1 -0
  152. package/src/staking/staking.ts +28 -0
  153. package/src/types/aave.ts +4 -0
  154. package/src/types/aaveV4.ts +50 -0
  155. package/src/types/compound.ts +2 -0
  156. package/src/types/curveUsd.ts +7 -0
  157. package/src/types/fluid.ts +1 -0
  158. package/src/types/liquity.ts +6 -0
  159. package/src/types/liquityV2.ts +2 -0
  160. package/src/types/maker.ts +6 -0
  161. package/src/types/morphoBlue.ts +2 -0
  162. package/src/types/morphoMidnight.ts +1 -0
  163. package/src/types/portfolio.ts +71 -6
  164. package/src/types/savings/index.ts +12 -1
  165. package/src/types/savings/morphoVaults.ts +11 -1
  166. package/src/types/spark.ts +2 -0
@@ -6,6 +6,7 @@ import {
6
6
  AaveIncentivesControllerViem,
7
7
  AaveV3ViewContractViem,
8
8
  createViemContractFromConfigFunc,
9
+ getConfigContractAbi,
9
10
  StkAAVEViem,
10
11
  } from '../contracts';
11
12
  import { aaveAnyGetAggregatedPositionData, aaveV3IsInIsolationMode, aaveV3IsInSiloedMode } from '../helpers/aaveHelpers';
@@ -62,7 +63,10 @@ export const aaveV3EmodeCategoriesMapping = (extractedState: any, usedAssets: Aa
62
63
  ...extractedState, usedAssets, eModeCategory: e.id,
63
64
  });
64
65
  const willStayOverCollateralized = new Dec(afterEnteringCategory.ratio === 'NaN' ? '0' : afterEnteringCategory.ratio).eq(0) || new Dec(afterEnteringCategory.ratio).gt(afterEnteringCategory.liqPercent);
65
- const enteringTerms = [borrowingOnlyFromCategory, willStayOverCollateralized];
66
+ const collateralOnlyFromCategory = e.id === 0 || !e.isolated
67
+ ? true
68
+ : !usedAssetsValues.filter(u => u.isSupplied && u.collateral && !e.collateralAssets.includes(u.symbol)).length;
69
+ const enteringTerms = [borrowingOnlyFromCategory, willStayOverCollateralized, collateralOnlyFromCategory];
66
70
  categoriesMapping[e.id] = {
67
71
  enteringTerms,
68
72
  canEnterCategory: !enteringTerms.includes(false),
@@ -119,6 +123,7 @@ export async function _getAaveV3MarketData(provider: Client, network: NetworkNum
119
123
  borrowableBitmap: eModesInfo[i].borrowableBitmap.toString(),
120
124
  collateralBitmap: eModesInfo[i].collateralBitmap.toString(),
121
125
  ltvzeroBitmap: eModesInfo[i].ltvzeroBitmap.toString(),
126
+ isolated: eModesInfo[i].isolated ?? false,
122
127
  borrowAssets: [],
123
128
  collateralAssets: [],
124
129
  ltvZeroAssets: [],
@@ -346,6 +351,7 @@ export async function _getAaveV3MarketData(provider: Client, network: NetworkNum
346
351
  collateralAssets: assetsData.map((a) => a.symbol),
347
352
  borrowAssets: assetsData.map((a) => a.symbol),
348
353
  ltvZeroAssets: [],
354
+ isolated: false,
349
355
  };
350
356
 
351
357
  return { assetsData: payload, eModeCategoriesData };
@@ -431,6 +437,290 @@ export const _getAaveV3AccountBalances = async (provider: Client, network: Netwo
431
437
 
432
438
  export const getAaveV3AccountBalances = async (provider: EthereumProvider, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress): Promise<PositionBalances> => _getAaveV3AccountBalances(getViemProvider(provider, network), network, block, addressMapping, address);
433
439
 
440
+ /**
441
+ * Historical net-balance helpers that bypass the AaveV3View contract.
442
+ *
443
+ * The View contract (and therefore `getAaveV3AccountData` / `getAaveV3AccountBalances`) can only be
444
+ * queried from its deployment block onwards, so it cannot read balances for positions older than that.
445
+ * The aTokens/debt tokens, the ProtocolDataProvider and the Aave price oracle all exist from Aave v3
446
+ * launch, so reading `balanceOf` on those tokens + the oracle price directly reaches much further back
447
+ * and costs ~1 multicall per point. Used to build a position balance-history chart.
448
+ */
449
+
450
+ // matches morphoVaults' batchSize; stays under the 500k portfolio discovery allows on simulation fork RPCs
451
+ const HISTORICAL_MULTICALL_BATCH_SIZE = 250_000;
452
+
453
+ // Minimal Aave price oracle ABI (getAssetPrice returns the asset price in the market base currency).
454
+ const AAVE_ORACLE_ABI = [
455
+ {
456
+ inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
457
+ name: 'getAssetPrice',
458
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
459
+ stateMutability: 'view',
460
+ type: 'function',
461
+ },
462
+ ] as const;
463
+
464
+ export interface AaveV3ReserveTokenAddresses {
465
+ [symbol: string]: {
466
+ symbol: string,
467
+ underlyingAddress: EthAddress,
468
+ aTokenAddress: EthAddress,
469
+ stableDebtTokenAddress: EthAddress,
470
+ variableDebtTokenAddress: EthAddress,
471
+ };
472
+ }
473
+
474
+ export interface AaveV3HistoricalBalance {
475
+ block: number,
476
+ suppliedUsd: string,
477
+ borrowedUsd: string,
478
+ netUsd: string,
479
+ }
480
+
481
+ /**
482
+ * Fetches the aToken / stable-debt / variable-debt token addresses for every asset in the market.
483
+ * These are effectively immutable per reserve, so fetch once and reuse across all history points.
484
+ */
485
+ export const _getAaveV3ReserveTokenAddresses = async (provider: Client, network: NetworkNumber, market: AaveMarketInfo): Promise<AaveV3ReserveTokenAddresses> => {
486
+ const symbols = market.assets;
487
+ const underlyingAddresses = symbols.map((a: string) => getAssetInfo(getWrappedNativeAssetFromUnwrapped(a), network).address as EthAddress);
488
+ // @ts-ignore market.protocolData is a valid config key at runtime
489
+ const dataProviderAbi = getConfigContractAbi(market.protocolData, network);
490
+
491
+ const contracts = underlyingAddresses.map((underlying) => ({
492
+ address: market.protocolDataAddress as EthAddress,
493
+ abi: dataProviderAbi,
494
+ functionName: 'getReserveTokensAddresses',
495
+ args: [underlying],
496
+ }));
497
+
498
+ // @ts-ignore
499
+ const results = await provider.multicall({ contracts, allowFailure: true, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE });
500
+
501
+ const mapping: AaveV3ReserveTokenAddresses = {};
502
+ results.forEach((res: any, i: number) => {
503
+ if (res.status !== 'success' || !res.result) return;
504
+ // outputs order: [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress]
505
+ const [aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress] = res.result as [EthAddress, EthAddress, EthAddress];
506
+ mapping[symbols[i]] = {
507
+ symbol: symbols[i],
508
+ underlyingAddress: underlyingAddresses[i],
509
+ aTokenAddress,
510
+ stableDebtTokenAddress,
511
+ variableDebtTokenAddress,
512
+ };
513
+ });
514
+
515
+ return mapping;
516
+ };
517
+
518
+ export const getAaveV3ReserveTokenAddresses = async (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo): Promise<AaveV3ReserveTokenAddresses> => _getAaveV3ReserveTokenAddresses(getViemProvider(provider, network, { batch: { multicall: true } }), network, market);
519
+
520
+ /**
521
+ * Resolves the price oracle the market's addresses provider points at (at `block`, or latest).
522
+ * Aave can rotate the oracle over time, so a caller hoisting one address across many historical
523
+ * points must resolve it at both ends of its block range and only reuse it when the two agree —
524
+ * otherwise leave `oracleAddress` unset on _getAaveV3HistoricalBalance and it resolves per block.
525
+ */
526
+ export const _getAaveV3OracleAddress = async (provider: Client, network: NetworkNumber, market: AaveMarketInfo, block: Blockish = 'latest'): Promise<EthAddress> => {
527
+ // @ts-ignore market.provider is a valid config key at runtime
528
+ const providerAbi = getConfigContractAbi(market.provider, network);
529
+ // @ts-ignore readContract exists on the public client returned by getViemProvider
530
+ const oracleAddress = await provider.readContract({
531
+ address: market.providerAddress as EthAddress,
532
+ abi: providerAbi as any,
533
+ functionName: 'getPriceOracle',
534
+ ...setViemBlockNumber(block),
535
+ });
536
+ if (!oracleAddress) throw new Error(`AaveV3 oracle unavailable at block ${block}`);
537
+ return oracleAddress as EthAddress;
538
+ };
539
+
540
+ export const getAaveV3OracleAddress = async (provider: EthereumProvider, network: NetworkNumber, market: AaveMarketInfo, block: Blockish = 'latest'): Promise<EthAddress> => _getAaveV3OracleAddress(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, block);
541
+
542
+ type AaveV3ReserveTokenEntry = AaveV3ReserveTokenAddresses[string];
543
+ type AaveV3ActiveHistoricalAsset = AaveV3ReserveTokenEntry & { supplied: string, debt: string };
544
+ // raw getAssetPrice multicall result per asset symbol
545
+ type AaveV3PriceResultsBySymbol = Record<string, any>;
546
+
547
+ // supply = aToken.balanceOf, debt = variableDebtToken.balanceOf + stableDebtToken.balanceOf — three reads per reserve
548
+ const getHistoricalBalanceContracts = (entries: AaveV3ReserveTokenEntry[], address: EthAddress) => {
549
+ const erc20Abi = getConfigContractAbi('Erc20');
550
+ return entries.flatMap((e) => ([
551
+ {
552
+ address: e.aTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
553
+ },
554
+ {
555
+ address: e.variableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
556
+ },
557
+ {
558
+ address: e.stableDebtTokenAddress, abi: erc20Abi, functionName: 'balanceOf', args: [address],
559
+ },
560
+ ]));
561
+ };
562
+
563
+ const getAssetPriceContract = (oracle: EthAddress, underlyingAddress: EthAddress) => ({
564
+ address: oracle,
565
+ abi: AAVE_ORACLE_ABI,
566
+ functionName: 'getAssetPrice',
567
+ args: [underlyingAddress],
568
+ });
569
+
570
+ /**
571
+ * Pairs each reserve with its three balance reads and keeps the assets the wallet actually held.
572
+ * Throws when every read failed — a totally failed fetch must not masquerade as a real $0 balance,
573
+ * so the caller can distinguish "fetch failed" (gap) from "position was empty" (genuine 0).
574
+ */
575
+ const toActiveHistoricalAssets = (entries: AaveV3ReserveTokenEntry[], balanceResults: any[], block: number): AaveV3ActiveHistoricalAsset[] => {
576
+ const anyBalanceRead = balanceResults.some((r) => r?.status === 'success');
577
+ if (!anyBalanceRead) throw new Error(`AaveV3 historical balance: all balance reads failed at block ${block}`);
578
+
579
+ return entries.map((e, i) => {
580
+ const supplyRes = balanceResults[i * 3];
581
+ const varDebtRes = balanceResults[(i * 3) + 1];
582
+ const stableDebtRes = balanceResults[(i * 3) + 2];
583
+ const supplied = supplyRes?.status === 'success' ? (supplyRes.result as bigint).toString() : '0';
584
+ const varDebt = varDebtRes?.status === 'success' ? (varDebtRes.result as bigint).toString() : '0';
585
+ const stableDebt = stableDebtRes?.status === 'success' ? (stableDebtRes.result as bigint).toString() : '0';
586
+ const debt = new Dec(varDebt).add(stableDebt).toString();
587
+ return { ...e, supplied, debt };
588
+ }).filter((a) => a.supplied !== '0' || a.debt !== '0');
589
+ };
590
+
591
+ /**
592
+ * Sums the active assets into the point's USD totals. Throws when every price read failed — the
593
+ * oracle couldn't serve this block (pruned state, or a hoisted oracle that wasn't deployed yet back
594
+ * then), and totalling it up anyway would return a fake near-$0 balance. A single asset's price
595
+ * failure still just skips that asset, matching the live data path.
596
+ */
597
+ const sumHistoricalUsd = (activeAssets: AaveV3ActiveHistoricalAsset[], priceResBySymbol: AaveV3PriceResultsBySymbol, block: number): AaveV3HistoricalBalance => {
598
+ const anyPriceRead = activeAssets.some((a) => priceResBySymbol[a.symbol]?.status === 'success');
599
+ if (!anyPriceRead) throw new Error(`AaveV3 historical balance: all price reads failed at block ${block}`);
600
+
601
+ let suppliedUsd = new Dec(0);
602
+ let borrowedUsd = new Dec(0);
603
+ activeAssets.forEach((a) => {
604
+ const priceRes = priceResBySymbol[a.symbol];
605
+ if (priceRes?.status !== 'success') return;
606
+ const priceUsd = new Dec((priceRes.result as bigint).toString()).div(1e8); // Aave v3 base currency is USD with 8 decimals
607
+ if (a.supplied !== '0') suppliedUsd = suppliedUsd.add(new Dec(assetAmountInEth(a.supplied, a.symbol)).mul(priceUsd));
608
+ if (a.debt !== '0') borrowedUsd = borrowedUsd.add(new Dec(assetAmountInEth(a.debt, a.symbol)).mul(priceUsd));
609
+ });
610
+
611
+ return {
612
+ block,
613
+ suppliedUsd: suppliedUsd.toString(),
614
+ borrowedUsd: borrowedUsd.toString(),
615
+ netUsd: suppliedUsd.minus(borrowedUsd).toString(),
616
+ };
617
+ };
618
+
619
+ /**
620
+ * Fast path, ONE multicall: balances plus a price read for every reserve through the caller-hoisted
621
+ * oracle. Prices are read for all reserves because which assets are active isn't known until the
622
+ * balances come back — extra reads inside an already-paid multicall are nearly free.
623
+ */
624
+ const fetchHistoricalPointWithOracle = async (provider: Client, entries: AaveV3ReserveTokenEntry[], address: EthAddress, block: number, oracleAddress: EthAddress) => {
625
+ const balanceContracts = getHistoricalBalanceContracts(entries, address);
626
+ const priceContracts = entries.map((e) => getAssetPriceContract(oracleAddress, e.underlyingAddress));
627
+
628
+ // @ts-ignore
629
+ const results = await provider.multicall({
630
+ contracts: [...balanceContracts, ...priceContracts], allowFailure: true, blockNumber: BigInt(block), batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
631
+ });
632
+
633
+ const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
634
+ const priceResBySymbol: AaveV3PriceResultsBySymbol = {};
635
+ results.slice(balanceContracts.length).forEach((res: any, i: number) => {
636
+ priceResBySymbol[entries[i].symbol] = res;
637
+ });
638
+ return { activeAssets, priceResBySymbol };
639
+ };
640
+
641
+ /**
642
+ * Fallback path, TWO multicalls: balances plus getPriceOracle (resolving the oracle that was active
643
+ * AT the block — Aave can rotate it over time), then prices for just the active assets. The oracle
644
+ * read has no fallback: if it fails (bad archive node, rate limit, ...) the failure must surface
645
+ * rather than silently returning $0 — the caller renders a gap instead of a misleading zero.
646
+ */
647
+ const fetchHistoricalPointResolvingOracle = async (provider: Client, network: NetworkNumber, market: AaveMarketInfo, entries: AaveV3ReserveTokenEntry[], address: EthAddress, block: number) => {
648
+ const balanceContracts = getHistoricalBalanceContracts(entries, address);
649
+ // @ts-ignore market.provider is a valid config key at runtime
650
+ const providerAbi = getConfigContractAbi(market.provider, network);
651
+ const oracleContract = {
652
+ address: market.providerAddress as EthAddress,
653
+ abi: providerAbi,
654
+ functionName: 'getPriceOracle',
655
+ };
656
+ const blockNumber = BigInt(block);
657
+
658
+ // @ts-ignore
659
+ const results = await provider.multicall({
660
+ contracts: [...balanceContracts, oracleContract], allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
661
+ });
662
+ const oracleRes = results[balanceContracts.length];
663
+ if (oracleRes?.status !== 'success' || !oracleRes.result) throw new Error(`AaveV3 historical balance: oracle unavailable at block ${block}`);
664
+
665
+ const activeAssets = toActiveHistoricalAssets(entries, results.slice(0, balanceContracts.length), block);
666
+ if (!activeAssets.length) return { activeAssets, priceResBySymbol: {} };
667
+
668
+ const priceContracts = activeAssets.map((a) => getAssetPriceContract(oracleRes.result as EthAddress, a.underlyingAddress));
669
+ // @ts-ignore
670
+ const priceResults = await provider.multicall({
671
+ contracts: priceContracts, allowFailure: true, blockNumber, batchSize: HISTORICAL_MULTICALL_BATCH_SIZE,
672
+ });
673
+ const priceResBySymbol: AaveV3PriceResultsBySymbol = {};
674
+ priceResults.forEach((res: any, i: number) => {
675
+ priceResBySymbol[activeAssets[i].symbol] = res;
676
+ });
677
+ return { activeAssets, priceResBySymbol };
678
+ };
679
+
680
+ /**
681
+ * Computes a user's Aave v3 net USD balance (supplied collateral - borrowed debt) at a historical block,
682
+ * without touching the AaveV3View contract. Pass `reserveTokens` (from getAaveV3ReserveTokenAddresses)
683
+ * to avoid refetching token addresses for every point, and `oracleAddress` (from getAaveV3OracleAddress,
684
+ * verified identical at both ends of the sampled range) to collapse the point into a single multicall —
685
+ * with it the balances and all prices ride one request; without it the oracle is resolved at the block
686
+ * inside the balance multicall and the active assets are priced in a second one.
687
+ */
688
+ export const _getAaveV3HistoricalBalance = async (
689
+ provider: Client,
690
+ network: NetworkNumber,
691
+ market: AaveMarketInfo,
692
+ address: EthAddress,
693
+ block: number,
694
+ reserveTokens?: AaveV3ReserveTokenAddresses,
695
+ oracleAddress?: EthAddress,
696
+ ): Promise<AaveV3HistoricalBalance> => {
697
+ const empty: AaveV3HistoricalBalance = {
698
+ block, suppliedUsd: '0', borrowedUsd: '0', netUsd: '0',
699
+ };
700
+ if (!address) return empty;
701
+
702
+ const tokens = reserveTokens || await _getAaveV3ReserveTokenAddresses(provider, network, market);
703
+ const entries = Object.values(tokens);
704
+ if (!entries.length) return empty;
705
+
706
+ const { activeAssets, priceResBySymbol } = oracleAddress
707
+ ? await fetchHistoricalPointWithOracle(provider, entries, address, block, oracleAddress)
708
+ : await fetchHistoricalPointResolvingOracle(provider, network, market, entries, address, block);
709
+
710
+ if (!activeAssets.length) return empty;
711
+ return sumHistoricalUsd(activeAssets, priceResBySymbol, block);
712
+ };
713
+
714
+ export const getAaveV3HistoricalBalance = async (
715
+ provider: EthereumProvider,
716
+ network: NetworkNumber,
717
+ market: AaveMarketInfo,
718
+ address: EthAddress,
719
+ block: number,
720
+ reserveTokens?: AaveV3ReserveTokenAddresses,
721
+ oracleAddress?: EthAddress,
722
+ ): Promise<AaveV3HistoricalBalance> => _getAaveV3HistoricalBalance(getViemProvider(provider, network, { batch: { multicall: true } }), network, market, address, block, reserveTokens, oracleAddress);
723
+
434
724
  export const _getAaveV3AccountData = async (provider: Client, network: NetworkNumber, address: EthAddress, extractedState: any, blockNumber: 'latest' | number = 'latest'): Promise<AaveV3PositionData> => {
435
725
  const {
436
726
  selectedMarket: market, assetsData, eModeCategoriesData,
@@ -7,26 +7,9 @@ import {
7
7
  EthereumProvider,
8
8
  NetworkNumber,
9
9
  } from '../types/common';
10
+ import { AaveV4TokenizationSpokeDataParsed } from '../types/aaveV4';
10
11
 
11
- export interface AaveV4TokenizationSpokeData {
12
- underlyingAsset: EthAddress;
13
- assetId: string;
14
- decimals: number;
15
- spoke: EthAddress;
16
- spokeActive: boolean;
17
- spokeHalted: boolean;
18
- spokeDepositCap: string;
19
- spokeTotalAssets: string;
20
- spokeTotalShares: string;
21
- hub: EthAddress;
22
- hubLiquidity: string;
23
- hubDrawnRate: string;
24
- convertToShares: string;
25
- convertToAssets: string;
26
- user: EthAddress;
27
- userSuppliedAssets: string;
28
- userSuppliedShares: string;
29
- }
12
+ export type { AaveV4TokenizationSpokeData, AaveV4TokenizationSpokeDataParsed } from '../types/aaveV4';
30
13
 
31
14
  export const AAVE_V4_TOKENIZED_SPOKES: Record<string, EthAddress> = {
32
15
  EURC_CORE: '0x6D9e2Cdd61CaF69af99b275704B6e272C41c6718',
@@ -102,34 +85,6 @@ export const aaveV4GetTokenizedVaultAddress = (
102
85
  return AAVE_V4_TOKENIZED_SPOKES[key];
103
86
  };
104
87
 
105
- /** Parsed tokenization spoke data with human-readable supplied amounts for display */
106
- export interface AaveV4TokenizationSpokeDataParsed {
107
- vaultAddress: EthAddress;
108
- key: string | null;
109
- symbol: string;
110
- hubKey: string;
111
- // ---- Spoke ----
112
- spokeActive: boolean;
113
- spokeHalted: boolean;
114
- /** Deposit cap in asset units (wei string) */
115
- spokeDepositCap: string;
116
- /** Total assets currently in spoke in asset units (wei string) */
117
- spokeTotalAssets: string;
118
- // ---- Hub ----
119
- /** Available hub liquidity in asset units (wei string) */
120
- hubLiquidity: string;
121
- /** The conversion rate from assets to shares expressed in asset units. */
122
- convertToShares: string;
123
- // ---- User ----
124
- userSuppliedAssetsEth: string;
125
- userSuppliedSharesEth: string;
126
- userSuppliedAssets: string;
127
- userSuppliedShares: string;
128
- underlyingAsset: EthAddress;
129
- spoke: EthAddress;
130
- decimals: number;
131
- }
132
-
133
88
  const AAVE_V4_TOKENIZED_SPOKE_ADDRESS_TO_KEY: Record<string, string> = Object.entries(
134
89
  AAVE_V4_TOKENIZED_SPOKES,
135
90
  ).reduce((acc, [k, v]) => {
@@ -189,4 +144,3 @@ export async function getAaveV4TokenizationSpokesData(
189
144
  }).filter(item => item != null);
190
145
  }
191
146
 
192
-
@@ -14,6 +14,13 @@ import { getCrvUsdAggregatedData } from '../helpers/curveUsdHelpers';
14
14
  import { CrvUsdMarkets } from '../markets';
15
15
  import { wethToEth } from '../services/utils';
16
16
  import { getViemProvider, setViemBlockNumber } from '../services/viem';
17
+ import { getStakingApy, STAKING_ASSETS } from '../staking';
18
+
19
+ /** Converts a per-second AMM rate (wei, 18 decimals) into a yearly compounded borrow APY in %. */
20
+ const ammRateToBorrowApy = (rateWei: string) => {
21
+ const exponent = new Dec(assetAmountInEth(rateWei)).mul(365).mul(86400);
22
+ return new Dec(new Dec(2.718281828459).pow(exponent).minus(1)).mul(100).toString();
23
+ };
17
24
 
18
25
  const getAndFormatBands = async (provider: Client, network: NetworkNumber, selectedMarket: CrvUSDMarketData, _minBand: string, _maxBand: string) => {
19
26
  const contract = CrvUSDViewContractViem(provider, network);
@@ -69,15 +76,8 @@ export const _getCurveUsdGlobalData = async (provider: Client, network: NetworkN
69
76
  const totalDebt = assetAmountInEth(data.totalDebt.toString(), debtAsset);
70
77
  const ammPrice = assetAmountInEth(data.ammPrice.toString(), debtAsset);
71
78
 
72
- const rate = assetAmountInEth(data.ammRate.toString());
73
- const futureRate = assetAmountInEth(data.monetaryPolicyRate.toString());
74
-
75
- const exponentRate = new Dec(rate).mul(365).mul(86400);
76
- const exponentFutureRate = new Dec(futureRate).mul(365).mul(86400);
77
- const borrowRate = new Dec(new Dec(2.718281828459).pow(exponentRate).minus(1)).mul(100)
78
- .toString();
79
- const futureBorrowRate = new Dec(new Dec(2.718281828459).pow(exponentFutureRate).minus(1)).mul(100)
80
- .toString();
79
+ const borrowRate = ammRateToBorrowApy(data.ammRate.toString());
80
+ const futureBorrowRate = ammRateToBorrowApy(data.monetaryPolicyRate.toString());
81
81
 
82
82
  const bandsData = await getAndFormatBands(provider, network, selectedMarket, data.minBand.toString(), data.maxBand.toString());
83
83
 
@@ -164,13 +164,22 @@ export const getCrvUsdAccountBalances = async (
164
164
  controllerAddress: EthAddress,
165
165
  ): Promise<PositionBalances> => _getCrvUsdAccountBalances(getViemProvider(provider, network), network, block, addressMapping, address, controllerAddress);
166
166
 
167
- export const _getCurveUsdUserData = async (provider: Client, network: NetworkNumber, address: EthAddress, selectedMarket: CrvUSDMarketData, activeBand: string): Promise<CrvUSDUserData> => {
167
+ /**
168
+ * @param borrowRate market borrow APY in % (`CrvUSDGlobalMarketData.borrowRate`); when omitted it is read from the view contract
169
+ */
170
+ export const _getCurveUsdUserData = async (provider: Client, network: NetworkNumber, address: EthAddress, selectedMarket: CrvUSDMarketData, activeBand: string, borrowRate?: string): Promise<CrvUSDUserData> => {
168
171
  const contract = CrvUSDViewContractViem(provider, network);
169
-
170
- const data = await contract.read.userData([selectedMarket.controllerAddress, address]);
171
172
  const collAsset = selectedMarket.collAsset;
172
173
  const debtAsset = selectedMarket.baseAsset;
173
174
 
175
+ const [data, collStakingApy, marketBorrowRate] = await Promise.all([
176
+ contract.read.userData([selectedMarket.controllerAddress, address]),
177
+ STAKING_ASSETS.includes(collAsset) ? getStakingApy(collAsset) : Promise.resolve('0'),
178
+ borrowRate !== undefined
179
+ ? Promise.resolve(borrowRate)
180
+ : contract.read.globalData([selectedMarket.controllerAddress]).then((globalData) => ammRateToBorrowApy(globalData.ammRate.toString())),
181
+ ]);
182
+
174
183
  const health = assetAmountInEth(data.health.toString());
175
184
  const healthPercent = new Dec(health).mul(100).toString();
176
185
  const collPrice = assetAmountInEth(data.collateralPrice.toString(), debtAsset);
@@ -202,7 +211,7 @@ export const _getCurveUsdUserData = async (provider: Client, network: NetworkNum
202
211
  isBorrowed: new Dec(debtBorrowed).gt('0'),
203
212
  symbol: 'crvUSD',
204
213
  price: '1',
205
- interestRate: '0',
214
+ interestRate: marketBorrowRate,
206
215
  },
207
216
  } : {};
208
217
 
@@ -232,8 +241,15 @@ export const _getCurveUsdUserData = async (provider: Client, network: NetworkNum
232
241
  numOfBands: data.N.toString(),
233
242
  usedAssets,
234
243
  status,
244
+ borrowRate: marketBorrowRate,
235
245
  ...getCrvUsdAggregatedData({
236
- loanExists: data.loanExists, usedAssets, network: NetworkNumber.Eth, selectedMarket, numOfBands: data.N.toString(),
246
+ loanExists: data.loanExists,
247
+ usedAssets,
248
+ network: NetworkNumber.Eth,
249
+ selectedMarket,
250
+ numOfBands: data.N.toString(),
251
+ borrowRate: marketBorrowRate,
252
+ collStakingApy,
237
253
  }),
238
254
  userBands,
239
255
  };
@@ -245,10 +261,11 @@ export const getCurveUsdUserData = async (
245
261
  address: EthAddress,
246
262
  selectedMarket: CrvUSDMarketData,
247
263
  activeBand: string,
248
- ): Promise<CrvUSDUserData> => _getCurveUsdUserData(getViemProvider(provider, network), network, address, selectedMarket, activeBand);
264
+ borrowRate?: string,
265
+ ): Promise<CrvUSDUserData> => _getCurveUsdUserData(getViemProvider(provider, network), network, address, selectedMarket, activeBand, borrowRate);
249
266
 
250
267
  export const getCurveUsdFullPositionData = async (provider: EthereumProvider, network: NetworkNumber, address: EthAddress, selectedMarket: CrvUSDMarketData): Promise<CrvUSDUserData> => {
251
268
  const marketData = await getCurveUsdGlobalData(provider, network, selectedMarket);
252
- const positionData = await getCurveUsdUserData(provider, network, address, selectedMarket, marketData.activeBand);
269
+ const positionData = await getCurveUsdUserData(provider, network, address, selectedMarket, marketData.activeBand, marketData.borrowRate);
253
270
  return positionData;
254
271
  };
@@ -1831,3 +1831,43 @@ export const _getUserPositionsPortfolio = async (provider: PublicClient, network
1831
1831
  userData: userData[i],
1832
1832
  })).filter(md => md.marketData !== undefined);
1833
1833
  };
1834
+
1835
+
1836
+ export const _getAllFluidMarketDataPortfolio = async (provider: PublicClient, network: NetworkNumber): Promise<Record<string, FluidMarketData>> => {
1837
+ const versions = getFluidVersionsDataForNetwork(network);
1838
+ if (versions.length === 0) return {};
1839
+
1840
+ const view = FluidViewContractViem(provider, network);
1841
+ const vaultsData = await Promise.all(versions.map((version) => view.read.getVaultData([version.marketAddress])));
1842
+
1843
+ const tokens = Array.from(new Set(vaultsData.map((vaultData) => {
1844
+ const vaultTokens = [getAssetInfoByAddress(vaultData.supplyToken0, network).symbol, getAssetInfoByAddress(vaultData.borrowToken0, network).symbol];
1845
+ if (vaultData.supplyToken1 && !compareAddresses(ZERO_ADDRESS, vaultData.supplyToken1)) vaultTokens.push(getAssetInfoByAddress(vaultData.supplyToken1, network).symbol);
1846
+ if (vaultData.borrowToken1 && !compareAddresses(ZERO_ADDRESS, vaultData.borrowToken1)) vaultTokens.push(getAssetInfoByAddress(vaultData.borrowToken1, network).symbol);
1847
+ return vaultTokens;
1848
+ }).flat()));
1849
+
1850
+ // ETH and WBTC needed for other tokens prices
1851
+ if (!tokens.includes('ETH')) tokens.push('ETH');
1852
+ if (!tokens.includes('WBTC')) tokens.push('WBTC');
1853
+
1854
+ const [tokenPrices, merklCampaigns] = await Promise.all([
1855
+ getTokensPricesForPortfolio(tokens, provider, network),
1856
+ getFluidMerklCampaigns(network),
1857
+ ]);
1858
+
1859
+ const parsedMarketsData = await Promise.all(vaultsData.map(async (vaultData) => parseMarketData(provider, vaultData, network, tokenPrices)));
1860
+
1861
+ const marketsData: Record<string, FluidMarketData> = {};
1862
+ parsedMarketsData.forEach((marketData, i) => {
1863
+ if (!marketData) return;
1864
+ marketsData[versions[i].value] = attachFluidMerklIncentives(marketData, merklCampaigns);
1865
+ });
1866
+
1867
+ return marketsData;
1868
+ };
1869
+
1870
+ export const getAllFluidMarketDataPortfolio = async (
1871
+ provider: EthereumProvider,
1872
+ network: NetworkNumber,
1873
+ ): Promise<Record<string, FluidMarketData>> => _getAllFluidMarketDataPortfolio(getViemProvider(provider, network, { batch: { multicall: true } }), network);
@@ -2,7 +2,7 @@ import Dec from 'decimal.js';
2
2
  import { assetAmountInWei, getAssetInfo, getAssetInfoByAddress } from '@defisaver/tokens';
3
3
  import { Client } from 'viem';
4
4
  import {
5
- AaveAssetData, AaveHelperCommon, AaveMarketInfo, AaveV3AggregatedPositionData, AaveV3AssetsData, AaveV3UsedAsset, AaveV3UsedAssets, AaveVersions,
5
+ AaveAssetData, AaveHelperCommon, AaveMarketInfo, AaveV3AggregatedPositionData, AaveV3AssetsData, AaveV3UsedAsset, AaveV3UsedAssets, AaveVersions, EModeCategoriesData,
6
6
  } from '../../types';
7
7
  import { getNativeAssetFromWrapped, getWrappedNativeAssetFromUnwrapped } from '../../services/utils';
8
8
  import {
@@ -24,6 +24,13 @@ export const isAaveV3 = ({ selectedMarket }: { selectedMarket: Partial<AaveMarke
24
24
  export const aaveV3IsInIsolationMode = ({ usedAssets, assetsData }: { usedAssets: AaveV3UsedAssets, assetsData: AaveV3AssetsData }) => Object.values(usedAssets).some(({ symbol, collateral }) => collateral && assetsData[symbol].isIsolated);
25
25
  export const aaveV3IsInSiloedMode = ({ usedAssets, assetsData }: { usedAssets: AaveV3UsedAssets, assetsData: AaveV3AssetsData }) => Object.values(usedAssets).some(({ symbol, debt }) => debt && assetsData[symbol].isSiloed);
26
26
 
27
+ export const aaveV3IsEmodeIsolated = ({ eModeCategory, eModeCategoriesData }: { eModeCategory: number, eModeCategoriesData?: EModeCategoriesData }) => eModeCategory !== 0 && !!eModeCategoriesData?.[eModeCategory]?.isolated;
28
+
29
+ export const aaveV3CanBeCollateralInEmode = ({ eModeCategory, eModeCategoriesData }: { eModeCategory: number, eModeCategoriesData?: EModeCategoriesData }, _asset: string) => {
30
+ if (!aaveV3IsEmodeIsolated({ eModeCategory, eModeCategoriesData })) return true;
31
+ return eModeCategoriesData![eModeCategory].collateralAssets.includes(getNativeAssetFromWrapped(_asset));
32
+ };
33
+
27
34
  export const aaveAnyGetCollSuppliedAssets = ({ usedAssets }: { usedAssets: AaveV3UsedAssets }) => Object.values(usedAssets)
28
35
  .filter(({ isSupplied, collateral }: { isSupplied: boolean, collateral: boolean }) => isSupplied && collateral);
29
36
 
@@ -37,14 +44,18 @@ export const aaveAnyGetSuppliableAssets = ({
37
44
  const collAccountAssets = aaveAnyGetCollSuppliedAssets(data);
38
45
  const marketAssets = Object.values(assetsData) as AaveAssetData[];
39
46
 
40
- if (collAccountAssets.length === 0 || !isAaveV3(data)) return marketAssets.filter(({ canBeSupplied }) => canBeSupplied).map(({ symbol }) => ({ symbol, canBeCollateral: true }));
47
+ if (!isAaveV3(data)) return marketAssets.filter(({ canBeSupplied }) => canBeSupplied).map(({ symbol }) => ({ symbol, canBeCollateral: true }));
48
+
49
+ const canBeCollateralInEmode = (symbol: string) => aaveV3CanBeCollateralInEmode(data, symbol);
50
+
51
+ if (collAccountAssets.length === 0) return marketAssets.filter(({ canBeSupplied }) => canBeSupplied).map(({ symbol }) => ({ symbol, canBeCollateral: canBeCollateralInEmode(symbol) }));
41
52
 
42
53
  if (aaveV3IsInIsolationMode(data)) {
43
54
  const collAsset = collAccountAssets[0].symbol;
44
- return marketAssets.filter(d => d.canBeSupplied).map(({ symbol }) => ({ symbol, canBeCollateral: symbol === collAsset }));
55
+ return marketAssets.filter(d => d.canBeSupplied).map(({ symbol }) => ({ symbol, canBeCollateral: symbol === collAsset && canBeCollateralInEmode(symbol) }));
45
56
  }
46
57
 
47
- return marketAssets.filter(d => d.canBeSupplied).map(({ symbol, isIsolated }) => ({ symbol, canBeCollateral: !isIsolated }));
58
+ return marketAssets.filter(d => d.canBeSupplied).map(({ symbol, isIsolated }) => ({ symbol, canBeCollateral: !isIsolated && canBeCollateralInEmode(symbol) }));
48
59
  };
49
60
 
50
61
  export const aaveAnyGetSuppliableAsCollAssets = ({
@@ -62,8 +73,8 @@ export const aaveAnyGetEmodeMutableProps = (
62
73
  const asset = getNativeAssetFromWrapped(_asset);
63
74
 
64
75
  const assetData = assetsData[asset];
65
- const eModeCategoryData: { collateralAssets: string[], collateralFactor: string, liquidationRatio: string, ltvZeroAssets: string[] } = eModeCategoriesData?.[eModeCategory] || {
66
- collateralAssets: [], ltvZeroAssets: [], collateralFactor: '0', liquidationRatio: '0',
76
+ const eModeCategoryData: { collateralAssets: string[], collateralFactor: string, liquidationRatio: string, ltvZeroAssets: string[], isolated?: boolean } = eModeCategoriesData?.[eModeCategory] || {
77
+ collateralAssets: [], ltvZeroAssets: [], collateralFactor: '0', liquidationRatio: '0', isolated: false,
67
78
  };
68
79
 
69
80
  if (
@@ -72,6 +83,9 @@ export const aaveAnyGetEmodeMutableProps = (
72
83
  || new Dec(eModeCategoryData.collateralFactor || 0).eq(0)
73
84
  ) {
74
85
  const { liquidationRatio, collateralFactor } = assetData;
86
+ if (eModeCategory !== 0 && eModeCategoryData.isolated && !eModeCategoryData.collateralAssets.includes(asset)) {
87
+ return ({ liquidationRatio, collateralFactor: '0' });
88
+ }
75
89
  return ({ liquidationRatio, collateralFactor });
76
90
  }
77
91
  if (eModeCategoryData.ltvZeroAssets.includes(asset)) return ({ liquidationRatio: '0', collateralFactor: '0' });
@@ -118,7 +132,10 @@ export const aaveAnyGetUserReserveLtvAndLltv = (
118
132
  || !eModeCategoryData.collateralAssets.includes(asset)
119
133
  || new Dec(eModeCategoryData.collateralFactor || 0).eq(0)
120
134
  ) {
121
- return { ltv: assetData.collateralFactor, lltv: assetData.liquidationRatio };
135
+ const ltv = eModeCategory !== 0 && eModeCategoryData?.isolated && !eModeCategoryData.collateralAssets.includes(asset)
136
+ ? '0'
137
+ : assetData.collateralFactor;
138
+ return { ltv, lltv: assetData.liquidationRatio };
122
139
  }
123
140
 
124
141
  const ltv = eModeCategoryData.ltvZeroAssets.includes(asset) ? '0' : eModeCategoryData.collateralFactor;
@@ -169,6 +186,9 @@ export const aaveAnyGetAggregatedPositionData = ({
169
186
  },
170
187
  );
171
188
  payload.safetyRatioWithLtvZeroFallback = +payload.suppliedUsd ? new Dec(payload.borrowLimitWithLtvZeroFallbackUsd).div(payload.borrowedUsd).mul(100).toString() : '0';
189
+ // Normalised safety ratio (100 = liquidation on every protocol): the automation ratio above, falling back
190
+ // to the regular ratio when the fallback carries its '0' placeholder.
191
+ payload.safetyRatio = +payload.safetyRatioWithLtvZeroFallback ? payload.safetyRatioWithLtvZeroFallback : payload.ratio;
172
192
  payload.liqRatio = new Dec(payload.borrowLimitUsd).div(payload.liquidationLimitUsd).toString();
173
193
  payload.liqPercent = new Dec(payload.borrowLimitUsd).div(payload.liquidationLimitUsd).mul(100).toString();
174
194
  const { leveragedType, leveragedAsset } = isLeveragedPos(usedAssets);
@@ -270,6 +270,7 @@ export const aaveV4GetAggregatedPositionData = ({
270
270
  payload.ratio = +payload.suppliedUsd ? new Dec(payload.borrowLimitUsd).div(payload.borrowedUsd).mul(100).toString() : '0';
271
271
  payload.minRatio = '100';
272
272
  payload.collRatio = +payload.suppliedUsd ? new Dec(payload.suppliedCollateralUsd).div(payload.borrowedUsd).mul(100).toString() : '0';
273
+ payload.safetyRatio = payload.ratio;
273
274
  payload.liqRatio = new Dec(payload.borrowLimitUsd).div(payload.liquidationLimitUsd).toString();
274
275
  payload.liqPercent = new Dec(payload.borrowLimitUsd).div(payload.liquidationLimitUsd).mul(100).toString();
275
276
  const { leveragedType, leveragedAsset } = isLeveragedPosAaveV4(usedAssets);
@@ -139,6 +139,7 @@ export const getCompoundV2AggregatedData = ({
139
139
  payload.collRatio = payload.borrowedUsd && payload.borrowedUsd !== '0'
140
140
  ? new Dec(payload.suppliedCollateralUsd).div(payload.borrowedUsd).mul(100).toString()
141
141
  : '0';
142
+ payload.safetyRatio = payload.ratio;
142
143
 
143
144
  // Calculate borrow limits per asset
144
145
  Object.values(usedAssets).forEach((item) => {
@@ -185,6 +186,7 @@ export const getCompoundV3AggregatedData = ({
185
186
  payload.incentiveUsd = incentiveUsd;
186
187
  payload.totalInterestUsd = totalInterestUsd;
187
188
  payload.minRatio = '100';
189
+ payload.safetyRatio = payload.ratio;
188
190
  payload.liqRatio = new Dec(payload.borrowLimitUsd).div(payload.liquidationLimitUsd).toString();
189
191
  payload.liqPercent = new Dec(payload.borrowLimitUsd).div(payload.liquidationLimitUsd).mul(100).toString();
190
192
  payload.minDebt = assetsData[selectedMarket.baseAsset].minDebt;