@defisaver/positions-sdk 2.1.144-dev → 2.1.145

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 (96) hide show
  1. package/cjs/config/contracts.d.ts +249 -0
  2. package/cjs/config/contracts.js +11 -1
  3. package/cjs/constants/index.d.ts +1 -0
  4. package/cjs/constants/index.js +2 -1
  5. package/cjs/contracts.d.ts +1510 -0
  6. package/cjs/contracts.js +3 -2
  7. package/cjs/fluid/index.js +6 -2
  8. package/cjs/helpers/fluidHelpers/index.js +5 -4
  9. package/cjs/helpers/index.d.ts +1 -0
  10. package/cjs/helpers/index.js +2 -1
  11. package/cjs/helpers/morphoMidnightHelpers/headroom.d.ts +48 -0
  12. package/cjs/helpers/morphoMidnightHelpers/headroom.js +52 -0
  13. package/cjs/helpers/morphoMidnightHelpers/index.d.ts +113 -0
  14. package/cjs/helpers/morphoMidnightHelpers/index.js +298 -0
  15. package/cjs/helpers/morphoMidnightHelpers/rate.d.ts +15 -0
  16. package/cjs/helpers/morphoMidnightHelpers/rate.js +62 -0
  17. package/cjs/helpers/morphoMidnightHelpers/tenor.d.ts +129 -0
  18. package/cjs/helpers/morphoMidnightHelpers/tenor.js +254 -0
  19. package/cjs/index.d.ts +2 -1
  20. package/cjs/index.js +3 -1
  21. package/cjs/markets/index.d.ts +1 -0
  22. package/cjs/markets/index.js +6 -1
  23. package/cjs/markets/morphoMidnight/index.d.ts +60 -0
  24. package/cjs/markets/morphoMidnight/index.js +659 -0
  25. package/cjs/morphoMidnight/index.d.ts +14 -0
  26. package/cjs/morphoMidnight/index.js +261 -0
  27. package/cjs/portfolio/discovery.js +4 -0
  28. package/cjs/portfolio/index.js +44 -0
  29. package/cjs/services/viem.d.ts +11 -11
  30. package/cjs/staking/eligibility.d.ts +15 -1
  31. package/cjs/staking/eligibility.js +12 -1
  32. package/cjs/staking/staking.js +18 -22
  33. package/cjs/types/index.d.ts +1 -0
  34. package/cjs/types/index.js +1 -0
  35. package/cjs/types/morphoMidnight.d.ts +141 -0
  36. package/cjs/types/morphoMidnight.js +47 -0
  37. package/cjs/types/portfolio.d.ts +4 -0
  38. package/esm/config/contracts.d.ts +249 -0
  39. package/esm/config/contracts.js +9 -0
  40. package/esm/constants/index.d.ts +1 -0
  41. package/esm/constants/index.js +1 -0
  42. package/esm/contracts.d.ts +1510 -0
  43. package/esm/contracts.js +1 -0
  44. package/esm/fluid/index.js +6 -2
  45. package/esm/helpers/fluidHelpers/index.js +6 -5
  46. package/esm/helpers/index.d.ts +1 -0
  47. package/esm/helpers/index.js +1 -0
  48. package/esm/helpers/morphoMidnightHelpers/headroom.d.ts +48 -0
  49. package/esm/helpers/morphoMidnightHelpers/headroom.js +45 -0
  50. package/esm/helpers/morphoMidnightHelpers/index.d.ts +113 -0
  51. package/esm/helpers/morphoMidnightHelpers/index.js +271 -0
  52. package/esm/helpers/morphoMidnightHelpers/rate.d.ts +15 -0
  53. package/esm/helpers/morphoMidnightHelpers/rate.js +50 -0
  54. package/esm/helpers/morphoMidnightHelpers/tenor.d.ts +129 -0
  55. package/esm/helpers/morphoMidnightHelpers/tenor.js +238 -0
  56. package/esm/index.d.ts +2 -1
  57. package/esm/index.js +2 -1
  58. package/esm/markets/index.d.ts +1 -0
  59. package/esm/markets/index.js +1 -0
  60. package/esm/markets/morphoMidnight/index.d.ts +60 -0
  61. package/esm/markets/morphoMidnight/index.js +631 -0
  62. package/esm/morphoMidnight/index.d.ts +14 -0
  63. package/esm/morphoMidnight/index.js +248 -0
  64. package/esm/portfolio/discovery.js +5 -1
  65. package/esm/portfolio/index.js +45 -1
  66. package/esm/services/viem.d.ts +11 -11
  67. package/esm/staking/eligibility.d.ts +15 -1
  68. package/esm/staking/eligibility.js +10 -0
  69. package/esm/staking/staking.js +19 -23
  70. package/esm/types/index.d.ts +1 -0
  71. package/esm/types/index.js +1 -0
  72. package/esm/types/morphoMidnight.d.ts +141 -0
  73. package/esm/types/morphoMidnight.js +44 -0
  74. package/esm/types/portfolio.d.ts +4 -0
  75. package/package.json +1 -1
  76. package/src/config/contracts.ts +9 -0
  77. package/src/constants/index.ts +1 -0
  78. package/src/contracts.ts +1 -0
  79. package/src/fluid/index.ts +6 -2
  80. package/src/helpers/fluidHelpers/index.ts +6 -5
  81. package/src/helpers/index.ts +1 -0
  82. package/src/helpers/morphoMidnightHelpers/headroom.ts +82 -0
  83. package/src/helpers/morphoMidnightHelpers/index.ts +485 -0
  84. package/src/helpers/morphoMidnightHelpers/rate.ts +71 -0
  85. package/src/helpers/morphoMidnightHelpers/tenor.ts +407 -0
  86. package/src/index.ts +2 -0
  87. package/src/markets/index.ts +3 -0
  88. package/src/markets/morphoMidnight/index.ts +675 -0
  89. package/src/morphoMidnight/index.ts +276 -0
  90. package/src/portfolio/discovery.ts +6 -0
  91. package/src/portfolio/index.ts +44 -0
  92. package/src/staking/eligibility.ts +29 -2
  93. package/src/staking/staking.ts +20 -22
  94. package/src/types/index.ts +1 -0
  95. package/src/types/morphoMidnight.ts +163 -0
  96. package/src/types/portfolio.ts +4 -0
@@ -0,0 +1,276 @@
1
+ import Dec from 'decimal.js';
2
+ import { assetAmountInEth, getAssetInfoByAddress } from '@defisaver/tokens';
3
+ import { Client } from 'viem';
4
+ import {
5
+ Blockish, EthAddress, EthereumProvider, MMAssetsData, MMUsedAssets, NetworkNumber, PositionBalances,
6
+ } from '../types/common';
7
+ import {
8
+ DFSFeedRegistryContractViem, FeedRegistryContractViem, MorphoMidnightViewContractViem,
9
+ } from '../contracts';
10
+ import {
11
+ MorphoMidnightAssetsData, MorphoMidnightBorrowInfoStatus, MorphoMidnightMarketData, MorphoMidnightMarketInfo, MorphoMidnightPositionData,
12
+ } from '../types';
13
+ import { USD_QUOTE } from '../constants';
14
+ import { calculateNetApy } from '../staking';
15
+ import { isMainnetNetwork, wethToEth } from '../services/utils';
16
+ import { getMorphoMidnightAggregatedPositionData, getMorphoMidnightUserBorrowInfo } from '../helpers/morphoMidnightHelpers';
17
+ import { getChainlinkAssetAddress } from '../services/priceService';
18
+ import { getViemProvider, setViemBlockNumber } from '../services/viem';
19
+
20
+ const HARDCODED_USD_STABLE_PRICE = '100000000'; // $1 with 8 decimals
21
+ const nowInSeconds = () => Math.floor(Date.now() / 1000);
22
+
23
+ export async function _getMorphoMidnightMarketData(provider: Client, network: NetworkNumber, selectedMarket: MorphoMidnightMarketData): Promise<MorphoMidnightMarketInfo> {
24
+ const { loanToken, collaterals, marketId } = selectedMarket;
25
+ const loanTokenInfo = getAssetInfoByAddress(loanToken, network);
26
+ const loanSym = wethToEth(loanTokenInfo.symbol);
27
+ const loanTokenFeedAddress = getChainlinkAssetAddress(loanTokenInfo.symbol, network);
28
+
29
+ const viewContract = MorphoMidnightViewContractViem(provider, network);
30
+ const isHardcodedUsdStable = ['USDA', 'RLUSD'].includes(loanTokenInfo.symbol);
31
+
32
+ let marketInfo;
33
+ let loanTokenPrice;
34
+ if (isMainnetNetwork(network)) {
35
+ const feedRegistryContract = FeedRegistryContractViem(provider, NetworkNumber.Eth);
36
+ const [_loanTokenPrice, _marketInfo] = await Promise.all([
37
+ isHardcodedUsdStable ? Promise.resolve(HARDCODED_USD_STABLE_PRICE) : feedRegistryContract.read.latestAnswer([loanTokenFeedAddress, USD_QUOTE]),
38
+ viewContract.read.getMarketInfo([marketId as `0x${string}`]),
39
+ ]);
40
+ marketInfo = _marketInfo;
41
+ loanTokenPrice = _loanTokenPrice;
42
+ } else {
43
+ // Currently only Base is supported
44
+ const feedRegistryContract = DFSFeedRegistryContractViem(provider, network);
45
+ const [loanTokenPriceRound, _marketInfo] = await Promise.all([
46
+ isHardcodedUsdStable ? Promise.resolve([0, HARDCODED_USD_STABLE_PRICE]) : feedRegistryContract.read.latestRoundData([loanTokenFeedAddress, USD_QUOTE]),
47
+ viewContract.read.getMarketInfo([marketId as `0x${string}`]),
48
+ ]);
49
+ marketInfo = _marketInfo;
50
+ loanTokenPrice = loanTokenPriceRound[1].toString();
51
+ }
52
+
53
+ const loanTokenUsd = new Dec(loanTokenPrice).div(1e8).toString();
54
+
55
+ const totalUnits = marketInfo.totalUnits.toString();
56
+ const withdrawable = marketInfo.withdrawable.toString();
57
+ const totalDebt = Dec.max(new Dec(totalUnits).sub(withdrawable), 0).toString();
58
+
59
+ const assetsData: MorphoMidnightAssetsData = {};
60
+ assetsData[loanSym] = {
61
+ symbol: loanSym,
62
+ address: loanToken,
63
+ price: loanTokenUsd,
64
+ supplyRate: '0', // fixed rate is orderbook-derived, not exposed on-chain (MVP)
65
+ borrowRate: '0',
66
+ totalSupply: assetAmountInEth(totalUnits, loanSym),
67
+ totalBorrow: assetAmountInEth(totalDebt, loanSym),
68
+ canBeSupplied: false,
69
+ canBeBorrowed: true,
70
+ supplyIncentives: [],
71
+ borrowIncentives: [],
72
+ };
73
+
74
+ const collateralSymbols: string[] = [];
75
+ collaterals.forEach((coll, i) => {
76
+ const collInfo = getAssetInfoByAddress(coll.token, network);
77
+ const collSym = wethToEth(collInfo.symbol);
78
+ collateralSymbols.push(collSym);
79
+ // Oracle price is returned in loan-token terms, scaled by 10^(36 + loanDec - collDec)
80
+ const scale = new Dec(10).pow(new Dec(36).add(loanTokenInfo.decimals).sub(collInfo.decimals).toString()).toString();
81
+ const rawPrice = marketInfo.prices[i] ? marketInfo.prices[i].toString() : '0';
82
+ const oracleRate = new Dec(rawPrice).div(scale).toString();
83
+ assetsData[collSym] = {
84
+ symbol: collSym,
85
+ address: coll.token,
86
+ price: new Dec(loanTokenUsd).mul(oracleRate).toString(),
87
+ supplyRate: '0',
88
+ borrowRate: '0',
89
+ lltv: new Dec(coll.lltv).toString(),
90
+ canBeSupplied: true,
91
+ canBeBorrowed: false,
92
+ supplyIncentives: [],
93
+ borrowIncentives: [],
94
+ };
95
+ });
96
+
97
+ const utillization = new Dec(totalUnits).eq(0) ? '0' : new Dec(totalDebt).div(totalUnits).mul(100).toString();
98
+
99
+ return {
100
+ id: marketInfo.id,
101
+ loanToken: loanSym,
102
+ collaterals: collateralSymbols,
103
+ maturity: selectedMarket.maturity,
104
+ isMatured: nowInSeconds() >= selectedMarket.maturity,
105
+ totalUnits: assetAmountInEth(totalUnits, loanSym),
106
+ withdrawable: assetAmountInEth(withdrawable, loanSym),
107
+ totalDebt: assetAmountInEth(totalDebt, loanSym),
108
+ lossFactor: marketInfo.lossFactor.toString(),
109
+ tickSpacing: marketInfo.tickSpacing,
110
+ utillization,
111
+ assetsData,
112
+ };
113
+ }
114
+
115
+ export async function getMorphoMidnightMarketData(provider: EthereumProvider, network: NetworkNumber, selectedMarket: MorphoMidnightMarketData): Promise<MorphoMidnightMarketInfo> {
116
+ return _getMorphoMidnightMarketData(getViemProvider(provider, network), network, selectedMarket);
117
+ }
118
+
119
+ export async function _getMorphoMidnightAccountData(provider: Client, network: NetworkNumber, account: EthAddress, selectedMarket: MorphoMidnightMarketData, marketInfo: MorphoMidnightMarketInfo): Promise<MorphoMidnightPositionData> {
120
+ const { marketId, collaterals } = selectedMarket;
121
+ const viewContract = MorphoMidnightViewContractViem(provider, network);
122
+ const positionInfo = await viewContract.read.getPositionInfo([marketId as `0x${string}`, account]);
123
+
124
+ const usedAssets: MMUsedAssets = {};
125
+
126
+ const loanTokenData = marketInfo.assetsData[marketInfo.loanToken];
127
+ const credit = assetAmountInEth(positionInfo.credit.toString(), marketInfo.loanToken);
128
+ const debt = assetAmountInEth(positionInfo.debt.toString(), marketInfo.loanToken);
129
+ // A user holds either credit (lender) or debt (borrower) in a market, never both.
130
+ usedAssets[marketInfo.loanToken] = {
131
+ symbol: marketInfo.loanToken,
132
+ supplied: credit,
133
+ borrowed: debt,
134
+ isSupplied: new Dec(positionInfo.credit.toString()).gt(0),
135
+ isBorrowed: new Dec(positionInfo.debt.toString()).gt(0),
136
+ collateral: false,
137
+ suppliedUsd: new Dec(credit).mul(loanTokenData.price).toString(),
138
+ borrowedUsd: new Dec(debt).mul(loanTokenData.price).toString(),
139
+ };
140
+
141
+ // positionInfo.collateral is index-aligned with the market's collateral set (0 where unused).
142
+ collaterals.forEach((coll, i) => {
143
+ const collInfo = getAssetInfoByAddress(coll.token, network);
144
+ const collSym = wethToEth(collInfo.symbol);
145
+ const rawAmount = positionInfo.collateral[i] ? positionInfo.collateral[i].toString() : '0';
146
+ const supplied = assetAmountInEth(rawAmount, collSym);
147
+ const collData = marketInfo.assetsData[collSym];
148
+ usedAssets[collSym] = {
149
+ symbol: collSym,
150
+ supplied,
151
+ borrowed: '0',
152
+ isSupplied: new Dec(rawAmount).gt(0),
153
+ isBorrowed: false,
154
+ collateral: true,
155
+ suppliedUsd: new Dec(supplied).mul(collData?.price || 0).toString(),
156
+ borrowedUsd: '0',
157
+ };
158
+ });
159
+
160
+ // Enrich borrower positions with the orderbook-derived rate + principal/interest split (off-chain).
161
+ // MidnightView only stores `debt` (= face value at maturity), so we default to full-debt-as-principal and
162
+ // override from the transactions API when available. Never throw here — the position must still render.
163
+ let borrowRate = '0';
164
+ let debtBase = debt; // fallback: treat the full on-chain debt as principal until fill history is known
165
+ let debtInterest = '0';
166
+ let borrowInfoStatus = MorphoMidnightBorrowInfoStatus.Available;
167
+ let assetsDataForApy = marketInfo.assetsData;
168
+ if (new Dec(positionInfo.debt.toString()).gt(0)) {
169
+ try {
170
+ // Curator-agnostic: `/users/:account/positions` is indexed off the Midnight singleton, so it carries
171
+ // Tenor-curated markets too. Only the *book* differs by curator, and that is quoted elsewhere.
172
+ const borrowInfo = await getMorphoMidnightUserBorrowInfo(account, marketId, marketInfo.loanToken);
173
+ if (new Dec(borrowInfo.debtTotal).gt(0)) {
174
+ borrowRate = borrowInfo.borrowRate;
175
+ debtBase = borrowInfo.debtBase;
176
+ debtInterest = borrowInfo.debtInterest;
177
+ usedAssets[marketInfo.loanToken].borrowRate = borrowRate;
178
+ // Reflect the real borrow cost in netApy without mutating the shared marketInfo.assetsData.
179
+ assetsDataForApy = {
180
+ ...marketInfo.assetsData,
181
+ [marketInfo.loanToken]: { ...loanTokenData, borrowRate },
182
+ };
183
+ const describesChainDebt = new Dec(borrowInfo.debtTotal).eq(debt);
184
+ const ratePriced = new Dec(borrowRate).gt(0) || new Dec(debtInterest).lte(0);
185
+ borrowInfoStatus = describesChainDebt && ratePriced
186
+ ? MorphoMidnightBorrowInfoStatus.Available
187
+ : MorphoMidnightBorrowInfoStatus.Pending;
188
+ } else {
189
+ // No entry for the position: the API does not know it yet, rather than it having no principal.
190
+ borrowInfoStatus = MorphoMidnightBorrowInfoStatus.Pending;
191
+ }
192
+ } catch (err) {
193
+ // Positions API unreachable — the fallback above still renders, and the next call may succeed.
194
+ borrowInfoStatus = MorphoMidnightBorrowInfoStatus.Pending;
195
+ }
196
+ }
197
+
198
+ return {
199
+ usedAssets,
200
+ credit,
201
+ debt,
202
+ borrowRate,
203
+ debtBase,
204
+ debtInterest,
205
+ borrowInfoStatus,
206
+ maturity: marketInfo.maturity,
207
+ isMatured: marketInfo.isMatured,
208
+ ...getMorphoMidnightAggregatedPositionData({ usedAssets, assetsData: assetsDataForApy, marketInfo }),
209
+ };
210
+ }
211
+
212
+ export async function getMorphoMidnightAccountData(provider: EthereumProvider, network: NetworkNumber, account: EthAddress, selectedMarket: MorphoMidnightMarketData, marketInfo: MorphoMidnightMarketInfo): Promise<MorphoMidnightPositionData> {
213
+ return _getMorphoMidnightAccountData(getViemProvider(provider, network), network, account, selectedMarket, marketInfo);
214
+ }
215
+
216
+ export const _getMorphoMidnightAccountBalances = async (provider: Client, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress, selectedMarket: MorphoMidnightMarketData): Promise<PositionBalances> => {
217
+ const balances: PositionBalances = { collateral: {}, debt: {} };
218
+ if (!address) return balances;
219
+
220
+ const { marketId, loanToken, collaterals } = selectedMarket;
221
+ const viewContract = MorphoMidnightViewContractViem(provider, network, block);
222
+ const positionInfo = await viewContract.read.getPositionInfo([marketId as `0x${string}`, address], setViemBlockNumber(block));
223
+
224
+ const loanTokenInfo = getAssetInfoByAddress(loanToken, network);
225
+ balances.debt = {
226
+ [addressMapping ? loanTokenInfo.address.toLowerCase() : wethToEth(loanTokenInfo.symbol)]: assetAmountInEth(positionInfo.debt.toString(), wethToEth(loanTokenInfo.symbol)),
227
+ };
228
+
229
+ const collateral: Record<string, string> = {};
230
+ collaterals.forEach((coll, i) => {
231
+ const collInfo = getAssetInfoByAddress(coll.token, network);
232
+ const rawAmount = positionInfo.collateral[i] ? positionInfo.collateral[i].toString() : '0';
233
+ collateral[addressMapping ? collInfo.address.toLowerCase() : wethToEth(collInfo.symbol)] = assetAmountInEth(rawAmount, wethToEth(collInfo.symbol));
234
+ });
235
+ balances.collateral = collateral;
236
+
237
+ return balances;
238
+ };
239
+
240
+ export const getMorphoMidnightAccountBalances = async (
241
+ provider: EthereumProvider,
242
+ network: NetworkNumber,
243
+ block: Blockish,
244
+ addressMapping: boolean,
245
+ address: EthAddress,
246
+ selectedMarket: MorphoMidnightMarketData,
247
+ ): Promise<PositionBalances> => _getMorphoMidnightAccountBalances(getViemProvider(provider, network), network, block, addressMapping, address, selectedMarket);
248
+
249
+ // Lender-side (credit-only) supply position, mirroring getMorphoEarn. `amount` is the credit face value
250
+ // redeemable at maturity; `apy` is '0' in MVP because the fixed yield is orderbook-derived, not on-chain.
251
+ export async function getMorphoMidnightEarn(provider: Client, network: NetworkNumber, account: EthAddress, selectedMarket: MorphoMidnightMarketData, marketInfo: MorphoMidnightMarketInfo): Promise<{ apy: string, amount: string, amountUsd: string }> {
252
+ const { marketId } = selectedMarket;
253
+ const viewContract = MorphoMidnightViewContractViem(provider, network);
254
+ const positionInfo = await viewContract.read.getPositionInfo([marketId as `0x${string}`, account]);
255
+
256
+ const loanTokenData = marketInfo.assetsData[marketInfo.loanToken];
257
+ const credit = assetAmountInEth(positionInfo.credit.toString(), marketInfo.loanToken);
258
+ const creditUsd = new Dec(credit).mul(loanTokenData.price).toString();
259
+
260
+ const usedAssets: MMUsedAssets = {
261
+ [marketInfo.loanToken]: {
262
+ symbol: marketInfo.loanToken,
263
+ supplied: credit,
264
+ borrowed: '0',
265
+ isSupplied: new Dec(positionInfo.credit.toString()).gt(0),
266
+ isBorrowed: false,
267
+ collateral: false,
268
+ suppliedUsd: creditUsd,
269
+ borrowedUsd: '0',
270
+ },
271
+ };
272
+
273
+ const { netApy } = calculateNetApy({ usedAssets, assetsData: marketInfo.assetsData as unknown as MMAssetsData });
274
+
275
+ return { apy: netApy, amount: credit, amountUsd: creditUsd };
276
+ }
@@ -10,6 +10,7 @@ import {
10
10
  CrvUsdMarkets,
11
11
  LlamaLendMarkets,
12
12
  MorphoBlueMarkets,
13
+ MorphoMidnightMarkets,
13
14
  SparkMarkets,
14
15
  } from '../markets';
15
16
  import { AaveVersions, CompoundVersions } from '../types';
@@ -19,6 +20,7 @@ import { _getCompoundV3AccountBalances } from '../compoundV3';
19
20
  import { _getCrvUsdAccountBalances } from '../curveUsd';
20
21
  import { _getLlamaLendAccountBalances } from '../llamaLend';
21
22
  import { _getMorphoBlueAccountBalances } from '../morphoBlue';
23
+ import { _getMorphoMidnightAccountBalances } from '../morphoMidnight';
22
24
  import { createViemContractFromConfigFunc } from '../contracts';
23
25
 
24
26
  const hasAnyBalance = (balances: PositionBalances): boolean => (
@@ -109,6 +111,10 @@ export async function getUserPositionsExistence(
109
111
  tasks.push(balanceTask(market.value, () => _getMorphoBlueAccountBalances(client, network, block, false, address, market)));
110
112
  });
111
113
 
114
+ Object.values(MorphoMidnightMarkets(network)).filter((market) => market.chainIds.includes(network)).forEach((market) => {
115
+ tasks.push(balanceTask(market.value, () => _getMorphoMidnightAccountBalances(client, network, block, false, address, market)));
116
+ });
117
+
112
118
  await Promise.all(tasks);
113
119
  return existence;
114
120
  }
@@ -8,9 +8,11 @@ import {
8
8
  LiquityV2Markets,
9
9
  LlamaLendMarkets,
10
10
  MorphoBlueMarkets,
11
+ MorphoMidnightMarkets,
11
12
  SparkMarkets,
12
13
  } from '../markets';
13
14
  import { _getMorphoBlueAccountData, _getMorphoBluePortfolioMarketData, getMorphoEarn } from '../morphoBlue';
15
+ import { _getMorphoMidnightAccountData, _getMorphoMidnightMarketData, getMorphoMidnightEarn } from '../morphoMidnight';
14
16
  import {
15
17
  AaveV2MarketData,
16
18
  AaveV3MarketData,
@@ -24,6 +26,7 @@ import {
24
26
  LiquityV2MarketData,
25
27
  LlamaLendGlobalMarketData,
26
28
  MorphoBlueMarketInfo,
29
+ MorphoMidnightMarketInfo,
27
30
  PortfolioPositionsData,
28
31
  SparkMarketsData,
29
32
  } from '../types';
@@ -59,6 +62,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
59
62
  const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
60
63
 
61
64
  const morphoMarkets = Object.values(MorphoBlueMarkets(network)).filter((market) => market.chainIds.includes(network));
65
+ const morphoMidnightMarkets = Object.values(MorphoMidnightMarkets(network)).filter((market) => market.chainIds.includes(network));
62
66
  const compoundV3Markets = Object.values(CompoundMarkets(network)).filter((market) => market.chainIds.includes(network) && market.value !== CompoundVersions.CompoundV2);
63
67
  const sparkMarkets = Object.values(SparkMarkets(network)).filter((market) => market.chainIds.includes(network));
64
68
  const aaveV3Markets = [AaveVersions.AaveV3, AaveVersions.AaveV3Lido, AaveVersions.AaveV3Etherfi].map((version) => AaveMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
@@ -76,6 +80,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
76
80
  const defaultClient = getViemProvider(defaultProvider, ...args);
77
81
 
78
82
  const morphoMarketsData: Record<string, MorphoBlueMarketInfo> = {};
83
+ const morphoMidnightMarketsData: Record<string, MorphoMidnightMarketInfo> = {};
79
84
  const compoundV3MarketsData: Record<string, CompoundV3MarketsData> = {};
80
85
  const sparkMarketsData: Record<string, SparkMarketsData> = {};
81
86
  const aaveV3MarketsData: Record<string, AaveV3MarketData> = {};
@@ -89,6 +94,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
89
94
 
90
95
  const markets = {
91
96
  morphoMarketsData,
97
+ morphoMidnightMarketsData,
92
98
  compoundV3MarketsData,
93
99
  sparkMarketsData,
94
100
  aaveV3MarketsData,
@@ -110,6 +116,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
110
116
  aaveV3: {},
111
117
  aaveV4: {},
112
118
  morphoBlue: {},
119
+ morphoMidnight: {},
113
120
  compoundV3: {},
114
121
  spark: {},
115
122
  maker: {},
@@ -130,6 +137,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
130
137
  stakingPositions[address.toLowerCase() as EthAddress] = {
131
138
  aaveV3: {},
132
139
  morphoBlue: {},
140
+ morphoMidnight: {},
133
141
  compoundV3: {},
134
142
  spark: {},
135
143
  aaveV2: {},
@@ -159,6 +167,10 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
159
167
  const marketData = await _getMorphoBluePortfolioMarketData(client, network, market);
160
168
  morphoMarketsData[market.value] = marketData;
161
169
  }),
170
+ ...morphoMidnightMarkets.map(async (market) => {
171
+ const marketData = await _getMorphoMidnightMarketData(client, network, market);
172
+ morphoMidnightMarketsData[market.value] = marketData;
173
+ }),
162
174
  ...compoundV3Markets.map(async (market) => {
163
175
  const marketData = await _getCompoundV3MarketsData(client, network, market, defaultClient);
164
176
  compoundV3MarketsData[market.value] = marketData;
@@ -473,6 +485,38 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
473
485
  positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
474
486
  }
475
487
  })).flat(),
488
+ ...morphoMidnightMarkets.map((market) => addresses.map(async (address) => {
489
+ try {
490
+ const [accDataPromise, earnDataPromise] = await Promise.allSettled([
491
+ _getMorphoMidnightAccountData(client, network, address, market, morphoMidnightMarketsData[market.value]),
492
+ getMorphoMidnightEarn(client, network, address, market, morphoMidnightMarketsData[market.value]),
493
+ ]);
494
+ if (accDataPromise.status === 'rejected') {
495
+ console.error(`Error fetching MorphoMidnight account data for address ${address} on market ${market.value}:`, accDataPromise.reason);
496
+ positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: `Error fetching MorphoMidnight account data for address ${address} on market ${market.value}`, data: null };
497
+ }
498
+ if (earnDataPromise.status === 'rejected') {
499
+ console.error(`Error fetching MorphoMidnight account data for address ${address} on market ${market.value}:`, earnDataPromise.reason);
500
+ positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: `Error fetching MorphoMidnight account data for address ${address} on market ${market.value}`, data: null };
501
+ }
502
+ if (accDataPromise.status !== 'rejected') {
503
+ const accData = accDataPromise.value;
504
+ if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: '', data: accData };
505
+ }
506
+ if (earnDataPromise.status !== 'rejected') {
507
+ const earnData = earnDataPromise.value;
508
+ if (earnData && new Dec(earnData.amount).gt(0)) {
509
+ stakingPositions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = {
510
+ error: '',
511
+ data: earnData,
512
+ };
513
+ }
514
+ }
515
+ } catch (error) {
516
+ console.error(`Error fetching MorphoMidnight account data for address ${address} on market ${market.value}:`, error);
517
+ positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: `Error fetching MorphoMidnight account data for address ${address} on market ${market.value}`, data: null };
518
+ }
519
+ })).flat(),
476
520
  ...compoundV3Markets.map((market) => addresses.map(async (address) => {
477
521
  try {
478
522
  const accData = await _getCompoundV3AccountData(client, network, address, ZERO_ADDRESS, { selectedMarket: market, assetsData: compoundV3MarketsData[market.value].assetsData });
@@ -1,5 +1,5 @@
1
1
  import Dec from 'decimal.js';
2
- import { IncentiveEligibilityId, MMUsedAssets } from '../types/common';
2
+ import { IncentiveData, IncentiveEligibilityId, MMUsedAssets } from '../types/common';
3
3
 
4
4
  type EthenaPairEligibilityConfig = {
5
5
  baseSymbol: string;
@@ -113,4 +113,31 @@ export const EligibilityMapping: { [key in IncentiveEligibilityId]: (usedAssets:
113
113
  [IncentiveEligibilityId.AaveV3EthenaLiquidLeveragePlasma]: isEligibleForEthenaUSDeRewards,
114
114
  [IncentiveEligibilityId.AaveV3EthenaLiquidLeveragePlasmaGHO]: isEligibleForEthenaGHORewards,
115
115
  [IncentiveEligibilityId.CompoundV3PufEthWethBorrow]: isEligibleForCompoundV3PufEthWethBorrow,
116
- };
116
+ };
117
+
118
+ export type IncentiveEligibilityResult = {
119
+ isEligible: boolean;
120
+ eligibleUSDAmount: string;
121
+ };
122
+
123
+ /**
124
+ * Resolves an incentive's position-specific eligibility and rewarded USD principal. Incentives
125
+ * without a curated eligibility rule apply to the full side amount supplied by the caller.
126
+ */
127
+ export const getIncentiveEligibility = ({
128
+ incentive,
129
+ usedAssets,
130
+ defaultEligibleUSDAmount,
131
+ optionalData,
132
+ }: {
133
+ incentive: Pick<IncentiveData, 'eligibilityId'>;
134
+ usedAssets: MMUsedAssets;
135
+ defaultEligibleUSDAmount: string;
136
+ optionalData?: any;
137
+ }): IncentiveEligibilityResult => {
138
+ const eligibilityCheck = incentive.eligibilityId ? EligibilityMapping[incentive.eligibilityId] : null;
139
+
140
+ return eligibilityCheck
141
+ ? eligibilityCheck(usedAssets, optionalData)
142
+ : { isEligible: true, eligibleUSDAmount: defaultEligibleUSDAmount };
143
+ };
@@ -7,7 +7,7 @@ import {
7
7
  } from '../types/common';
8
8
  import { BLOCKS_IN_A_YEAR } from '../constants';
9
9
  import { DEFAULT_TIMEOUT } from '../services/utils';
10
- import { EligibilityMapping } from './eligibility';
10
+ import { getIncentiveEligibility } from './eligibility';
11
11
 
12
12
  const getSsrApy = async () => {
13
13
  try {
@@ -153,16 +153,15 @@ export const calculateNetApy = ({
153
153
 
154
154
  if (assetData.supplyIncentives) {
155
155
  for (const supplyIncentive of assetData.supplyIncentives) {
156
- const { apy, eligibilityId } = supplyIncentive;
157
- const eligibilityCheck = eligibilityId ? EligibilityMapping[eligibilityId] : null;
158
- if (eligibilityCheck) {
159
- const { isEligible, eligibleUSDAmount } = eligibilityCheck(usedAssets, optionalData);
160
- const incentiveInterest = isEligible ? calculateInterestEarned(eligibleUSDAmount, apy, 'year', true) : '0';
161
- acc.incentiveUsd = new Dec(acc.incentiveUsd).add(incentiveInterest).toString();
162
- } else {
163
- const incentiveInterest = calculateInterestEarned(amount, apy, 'year', true);
164
- acc.incentiveUsd = new Dec(acc.incentiveUsd).add(incentiveInterest).toString();
165
- }
156
+ const { apy } = supplyIncentive;
157
+ const { isEligible, eligibleUSDAmount } = getIncentiveEligibility({
158
+ incentive: supplyIncentive,
159
+ usedAssets,
160
+ defaultEligibleUSDAmount: amount,
161
+ optionalData,
162
+ });
163
+ const incentiveInterest = isEligible ? calculateInterestEarned(eligibleUSDAmount, apy, 'year', true) : '0';
164
+ acc.incentiveUsd = new Dec(acc.incentiveUsd).add(incentiveInterest).toString();
166
165
  }
167
166
  }
168
167
  }
@@ -176,16 +175,15 @@ export const calculateNetApy = ({
176
175
 
177
176
  if (assetData.borrowIncentives) {
178
177
  for (const borrowIncentive of assetData.borrowIncentives) {
179
- const { apy, eligibilityId } = borrowIncentive;
180
- const eligibilityCheck = eligibilityId ? EligibilityMapping[eligibilityId] : null;
181
- if (eligibilityCheck) {
182
- const { isEligible, eligibleUSDAmount } = eligibilityCheck(usedAssets, optionalData);
183
- const incentiveInterest = isEligible ? calculateInterestEarned(eligibleUSDAmount, apy, 'year', true) : '0';
184
- acc.incentiveUsd = new Dec(acc.incentiveUsd).add(incentiveInterest).toString();
185
- } else {
186
- const incentiveInterest = calculateInterestEarned(amount, apy, 'year', true);
187
- acc.incentiveUsd = new Dec(acc.incentiveUsd).add(incentiveInterest).toString();
188
- }
178
+ const { apy } = borrowIncentive;
179
+ const { isEligible, eligibleUSDAmount } = getIncentiveEligibility({
180
+ incentive: borrowIncentive,
181
+ usedAssets,
182
+ defaultEligibleUSDAmount: amount,
183
+ optionalData,
184
+ });
185
+ const incentiveInterest = isEligible ? calculateInterestEarned(eligibleUSDAmount, apy, 'year', true) : '0';
186
+ acc.incentiveUsd = new Dec(acc.incentiveUsd).add(incentiveInterest).toString();
189
187
  }
190
188
  }
191
189
  }
@@ -204,4 +202,4 @@ export const calculateNetApy = ({
204
202
  const netApy = new Dec(totalInterestUsd).div(balance).times(100).toString();
205
203
 
206
204
  return { netApy, totalInterestUsd, incentiveUsd };
207
- };
205
+ };
@@ -6,6 +6,7 @@ export * from './liquity';
6
6
  export * from './liquityV2';
7
7
  export * from './maker';
8
8
  export * from './morphoBlue';
9
+ export * from './morphoMidnight';
9
10
  export * from './llamaLend';
10
11
  export * from './fluid';
11
12
  export * from './portfolio';