@defisaver/positions-sdk 2.1.126 → 2.1.127-midnight-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 (62) hide show
  1. package/cjs/config/contracts.d.ts +249 -0
  2. package/cjs/config/contracts.js +11 -1
  3. package/cjs/contracts.d.ts +1510 -0
  4. package/cjs/contracts.js +3 -2
  5. package/cjs/helpers/index.d.ts +1 -0
  6. package/cjs/helpers/index.js +2 -1
  7. package/cjs/helpers/morphoMidnightHelpers/index.d.ts +16 -0
  8. package/cjs/helpers/morphoMidnightHelpers/index.js +68 -0
  9. package/cjs/index.d.ts +2 -1
  10. package/cjs/index.js +3 -1
  11. package/cjs/markets/index.d.ts +1 -0
  12. package/cjs/markets/index.js +4 -1
  13. package/cjs/markets/morphoMidnight/index.d.ts +16 -0
  14. package/cjs/markets/morphoMidnight/index.js +159 -0
  15. package/cjs/morphoMidnight/index.d.ts +14 -0
  16. package/cjs/morphoMidnight/index.js +220 -0
  17. package/cjs/portfolio/discovery.js +4 -0
  18. package/cjs/portfolio/index.js +44 -0
  19. package/cjs/services/viem.d.ts +11 -11
  20. package/cjs/types/index.d.ts +1 -0
  21. package/cjs/types/index.js +1 -0
  22. package/cjs/types/morphoMidnight.d.ts +91 -0
  23. package/cjs/types/morphoMidnight.js +15 -0
  24. package/cjs/types/portfolio.d.ts +4 -0
  25. package/esm/config/contracts.d.ts +249 -0
  26. package/esm/config/contracts.js +9 -0
  27. package/esm/contracts.d.ts +1510 -0
  28. package/esm/contracts.js +1 -0
  29. package/esm/helpers/index.d.ts +1 -0
  30. package/esm/helpers/index.js +1 -0
  31. package/esm/helpers/morphoMidnightHelpers/index.d.ts +16 -0
  32. package/esm/helpers/morphoMidnightHelpers/index.js +61 -0
  33. package/esm/index.d.ts +2 -1
  34. package/esm/index.js +2 -1
  35. package/esm/markets/index.d.ts +1 -0
  36. package/esm/markets/index.js +1 -0
  37. package/esm/markets/morphoMidnight/index.d.ts +16 -0
  38. package/esm/markets/morphoMidnight/index.js +148 -0
  39. package/esm/morphoMidnight/index.d.ts +14 -0
  40. package/esm/morphoMidnight/index.js +207 -0
  41. package/esm/portfolio/discovery.js +5 -1
  42. package/esm/portfolio/index.js +45 -1
  43. package/esm/services/viem.d.ts +11 -11
  44. package/esm/types/index.d.ts +1 -0
  45. package/esm/types/index.js +1 -0
  46. package/esm/types/morphoMidnight.d.ts +91 -0
  47. package/esm/types/morphoMidnight.js +12 -0
  48. package/esm/types/portfolio.d.ts +4 -0
  49. package/package.json +1 -1
  50. package/src/config/contracts.ts +9 -0
  51. package/src/contracts.ts +1 -0
  52. package/src/helpers/index.ts +1 -0
  53. package/src/helpers/morphoMidnightHelpers/index.ts +86 -0
  54. package/src/index.ts +2 -0
  55. package/src/markets/index.ts +1 -0
  56. package/src/markets/morphoMidnight/index.ts +161 -0
  57. package/src/morphoMidnight/index.ts +234 -0
  58. package/src/portfolio/discovery.ts +6 -0
  59. package/src/portfolio/index.ts +44 -0
  60. package/src/types/index.ts +1 -0
  61. package/src/types/morphoMidnight.ts +104 -0
  62. package/src/types/portfolio.ts +4 -0
@@ -0,0 +1,161 @@
1
+ import { MorphoMidnightMarketData, MorphoMidnightVersions, NetworkNumber } from '../../types';
2
+
3
+ // Morpho Midnight core contract on Base (same for every market).
4
+ const MIDNIGHT_BASE = '0xAdedD8ab6dE832766Fedf0FaC4992E5C4D3EA18A';
5
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
6
+
7
+ // Curated Morpho Midnight markets. Each market is fixed-term: it is uniquely identified on-chain by
8
+ // `marketId` (bytes32), derived from the static struct below via MidnightView.toId. Because markets
9
+ // churn as maturities roll, this list is hand-maintained for the pairs/maturities the app supports.
10
+ // Sourced from the official listing at https://markets.morpho.org/fixed/base (see its sitemap.xml) —
11
+ // currently a single USDC/cbBTC pair offered on a monthly maturity ladder; new maturities are added
12
+ // there progressively. Every `marketId` here is verified against MidnightView.toId(marketStruct) in
13
+ // tests/morphoMidnight.ts.
14
+
15
+ // BASE — USDC/cbBTC, 86% LLTV, monthly maturity ladder
16
+
17
+ export const MORPHO_MIDNIGHT_CBBTC_USDC_860_20260731 = (networkId = NetworkNumber.Base): MorphoMidnightMarketData => ({
18
+ chainIds: [NetworkNumber.Base],
19
+ label: 'Morpho Midnight cbBTC/USDC',
20
+ shortLabel: 'cbBTC/USDC',
21
+ url: 'cbbtc-usdc-20260731',
22
+ value: MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20260731_Base,
23
+ midnight: MIDNIGHT_BASE,
24
+ loanToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
25
+ collaterals: [{
26
+ token: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', // cbBTC
27
+ lltv: 0.86,
28
+ liquidationCursor: '300000000000000000',
29
+ oracle: '0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9',
30
+ }],
31
+ maturity: 1785510000, // 2026-07-31T15:00:00Z
32
+ rcfThreshold: '3000000000',
33
+ enterGate: ZERO_ADDRESS,
34
+ liquidatorGate: ZERO_ADDRESS,
35
+ marketId: '0x168e31250e0008b50d2255a5ab85e0265acd6c12e4f9a1336134b36a65a47937',
36
+ protocolName: 'morpho-midnight',
37
+ });
38
+
39
+ export const MORPHO_MIDNIGHT_CBBTC_USDC_860_20260828 = (networkId = NetworkNumber.Base): MorphoMidnightMarketData => ({
40
+ chainIds: [NetworkNumber.Base],
41
+ label: 'Morpho Midnight cbBTC/USDC',
42
+ shortLabel: 'cbBTC/USDC',
43
+ url: 'cbbtc-usdc-20260828',
44
+ value: MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20260828_Base,
45
+ midnight: MIDNIGHT_BASE,
46
+ loanToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
47
+ collaterals: [{
48
+ token: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', // cbBTC
49
+ lltv: 0.86,
50
+ liquidationCursor: '300000000000000000',
51
+ oracle: '0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9',
52
+ }],
53
+ maturity: 1787929200, // 2026-08-28T15:00:00Z
54
+ rcfThreshold: '3000000000',
55
+ enterGate: ZERO_ADDRESS,
56
+ liquidatorGate: ZERO_ADDRESS,
57
+ marketId: '0x05959752fdeff325962b9d263edb421efc6e2186a49360dba6c32e86ebf6c84c',
58
+ protocolName: 'morpho-midnight',
59
+ });
60
+
61
+ export const MORPHO_MIDNIGHT_CBBTC_USDC_860_20260925 = (networkId = NetworkNumber.Base): MorphoMidnightMarketData => ({
62
+ chainIds: [NetworkNumber.Base],
63
+ label: 'Morpho Midnight cbBTC/USDC',
64
+ shortLabel: 'cbBTC/USDC',
65
+ url: 'cbbtc-usdc-20260925',
66
+ value: MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20260925_Base,
67
+ midnight: MIDNIGHT_BASE,
68
+ loanToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
69
+ collaterals: [{
70
+ token: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', // cbBTC
71
+ lltv: 0.86,
72
+ liquidationCursor: '300000000000000000',
73
+ oracle: '0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9',
74
+ }],
75
+ maturity: 1790348400, // 2026-09-25T15:00:00Z
76
+ rcfThreshold: '3000000000',
77
+ enterGate: ZERO_ADDRESS,
78
+ liquidatorGate: ZERO_ADDRESS,
79
+ marketId: '0x549cd072daf99328554f3a6d2d4d6f4a07f1c59369e891e6391946f9cf75f221',
80
+ protocolName: 'morpho-midnight',
81
+ });
82
+
83
+ export const MORPHO_MIDNIGHT_CBBTC_USDC_860_20261030 = (networkId = NetworkNumber.Base): MorphoMidnightMarketData => ({
84
+ chainIds: [NetworkNumber.Base],
85
+ label: 'Morpho Midnight cbBTC/USDC',
86
+ shortLabel: 'cbBTC/USDC',
87
+ url: 'cbbtc-usdc-20261030',
88
+ value: MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20261030_Base,
89
+ midnight: MIDNIGHT_BASE,
90
+ loanToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
91
+ collaterals: [{
92
+ token: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', // cbBTC
93
+ lltv: 0.86,
94
+ liquidationCursor: '300000000000000000',
95
+ oracle: '0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9',
96
+ }],
97
+ maturity: 1793372400, // 2026-10-30T15:00:00Z
98
+ rcfThreshold: '3000000000',
99
+ enterGate: ZERO_ADDRESS,
100
+ liquidatorGate: ZERO_ADDRESS,
101
+ marketId: '0x43d6120738c57b2bc5835901f8250fdf7fc8054efbb006c6ccba61ec898e5ed9',
102
+ protocolName: 'morpho-midnight',
103
+ });
104
+
105
+ export const MORPHO_MIDNIGHT_CBBTC_USDC_860_20261127 = (networkId = NetworkNumber.Base): MorphoMidnightMarketData => ({
106
+ chainIds: [NetworkNumber.Base],
107
+ label: 'Morpho Midnight cbBTC/USDC',
108
+ shortLabel: 'cbBTC/USDC',
109
+ url: 'cbbtc-usdc-20261127',
110
+ value: MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20261127_Base,
111
+ midnight: MIDNIGHT_BASE,
112
+ loanToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
113
+ collaterals: [{
114
+ token: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', // cbBTC
115
+ lltv: 0.86,
116
+ liquidationCursor: '300000000000000000',
117
+ oracle: '0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9',
118
+ }],
119
+ maturity: 1795791600, // 2026-11-27T15:00:00Z
120
+ rcfThreshold: '3000000000',
121
+ enterGate: ZERO_ADDRESS,
122
+ liquidatorGate: ZERO_ADDRESS,
123
+ marketId: '0xe1878eec035b601f301484e63a49a428f8e008e2bf57a2fd88a3fc3a4c1b1acd',
124
+ protocolName: 'morpho-midnight',
125
+ });
126
+
127
+ export const MORPHO_MIDNIGHT_CBBTC_USDC_860_20261225 = (networkId = NetworkNumber.Base): MorphoMidnightMarketData => ({
128
+ chainIds: [NetworkNumber.Base],
129
+ label: 'Morpho Midnight cbBTC/USDC',
130
+ shortLabel: 'cbBTC/USDC',
131
+ url: 'cbbtc-usdc-20261225',
132
+ value: MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20261225_Base,
133
+ midnight: MIDNIGHT_BASE,
134
+ loanToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
135
+ collaterals: [{
136
+ token: '0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf', // cbBTC
137
+ lltv: 0.86,
138
+ liquidationCursor: '300000000000000000',
139
+ oracle: '0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9',
140
+ }],
141
+ maturity: 1798210800, // 2026-12-25T15:00:00Z
142
+ rcfThreshold: '3000000000',
143
+ enterGate: ZERO_ADDRESS,
144
+ liquidatorGate: ZERO_ADDRESS,
145
+ marketId: '0x9593c3a6dba45b6106af8dc8b45ba8c505d90d3d68a3d33f7c278dd921b637da',
146
+ protocolName: 'morpho-midnight',
147
+ });
148
+
149
+ export const MorphoMidnightMarkets = (networkId: NetworkNumber) => ({
150
+ // BASE
151
+ [MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20260731_Base]: MORPHO_MIDNIGHT_CBBTC_USDC_860_20260731(networkId),
152
+ [MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20260828_Base]: MORPHO_MIDNIGHT_CBBTC_USDC_860_20260828(networkId),
153
+ [MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20260925_Base]: MORPHO_MIDNIGHT_CBBTC_USDC_860_20260925(networkId),
154
+ [MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20261030_Base]: MORPHO_MIDNIGHT_CBBTC_USDC_860_20261030(networkId),
155
+ [MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20261127_Base]: MORPHO_MIDNIGHT_CBBTC_USDC_860_20261127(networkId),
156
+ [MorphoMidnightVersions.MorphoMidnightCbBTCUSDC_860_20261225_Base]: MORPHO_MIDNIGHT_CBBTC_USDC_860_20261225(networkId),
157
+ }) as const;
158
+
159
+ export const findMorphoMidnightMarket = (marketId: string, network: NetworkNumber = NetworkNumber.Base): MorphoMidnightMarketData | undefined => Object.values(MorphoMidnightMarkets(network)).find(
160
+ (market) => market.marketId.toLowerCase() === marketId.toLowerCase(),
161
+ );
@@ -0,0 +1,234 @@
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, 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 } 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: true,
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
+ return {
161
+ usedAssets,
162
+ credit,
163
+ debt,
164
+ maturity: marketInfo.maturity,
165
+ isMatured: marketInfo.isMatured,
166
+ ...getMorphoMidnightAggregatedPositionData({ usedAssets, assetsData: marketInfo.assetsData, marketInfo }),
167
+ };
168
+ }
169
+
170
+ export async function getMorphoMidnightAccountData(provider: EthereumProvider, network: NetworkNumber, account: EthAddress, selectedMarket: MorphoMidnightMarketData, marketInfo: MorphoMidnightMarketInfo): Promise<MorphoMidnightPositionData> {
171
+ return _getMorphoMidnightAccountData(getViemProvider(provider, network), network, account, selectedMarket, marketInfo);
172
+ }
173
+
174
+ export const _getMorphoMidnightAccountBalances = async (provider: Client, network: NetworkNumber, block: Blockish, addressMapping: boolean, address: EthAddress, selectedMarket: MorphoMidnightMarketData): Promise<PositionBalances> => {
175
+ const balances: PositionBalances = { collateral: {}, debt: {} };
176
+ if (!address) return balances;
177
+
178
+ const { marketId, loanToken, collaterals } = selectedMarket;
179
+ const viewContract = MorphoMidnightViewContractViem(provider, network, block);
180
+ const positionInfo = await viewContract.read.getPositionInfo([marketId as `0x${string}`, address], setViemBlockNumber(block));
181
+
182
+ const loanTokenInfo = getAssetInfoByAddress(loanToken, network);
183
+ balances.debt = {
184
+ [addressMapping ? loanTokenInfo.address.toLowerCase() : wethToEth(loanTokenInfo.symbol)]: assetAmountInEth(positionInfo.debt.toString(), wethToEth(loanTokenInfo.symbol)),
185
+ };
186
+
187
+ const collateral: Record<string, string> = {};
188
+ collaterals.forEach((coll, i) => {
189
+ const collInfo = getAssetInfoByAddress(coll.token, network);
190
+ const rawAmount = positionInfo.collateral[i] ? positionInfo.collateral[i].toString() : '0';
191
+ collateral[addressMapping ? collInfo.address.toLowerCase() : wethToEth(collInfo.symbol)] = assetAmountInEth(rawAmount, wethToEth(collInfo.symbol));
192
+ });
193
+ balances.collateral = collateral;
194
+
195
+ return balances;
196
+ };
197
+
198
+ export const getMorphoMidnightAccountBalances = async (
199
+ provider: EthereumProvider,
200
+ network: NetworkNumber,
201
+ block: Blockish,
202
+ addressMapping: boolean,
203
+ address: EthAddress,
204
+ selectedMarket: MorphoMidnightMarketData,
205
+ ): Promise<PositionBalances> => _getMorphoMidnightAccountBalances(getViemProvider(provider, network), network, block, addressMapping, address, selectedMarket);
206
+
207
+ // Lender-side (credit-only) supply position, mirroring getMorphoEarn. `amount` is the credit face value
208
+ // redeemable at maturity; `apy` is '0' in MVP because the fixed yield is orderbook-derived, not on-chain.
209
+ export async function getMorphoMidnightEarn(provider: Client, network: NetworkNumber, account: EthAddress, selectedMarket: MorphoMidnightMarketData, marketInfo: MorphoMidnightMarketInfo): Promise<{ apy: string, amount: string, amountUsd: string }> {
210
+ const { marketId } = selectedMarket;
211
+ const viewContract = MorphoMidnightViewContractViem(provider, network);
212
+ const positionInfo = await viewContract.read.getPositionInfo([marketId as `0x${string}`, account]);
213
+
214
+ const loanTokenData = marketInfo.assetsData[marketInfo.loanToken];
215
+ const credit = assetAmountInEth(positionInfo.credit.toString(), marketInfo.loanToken);
216
+ const creditUsd = new Dec(credit).mul(loanTokenData.price).toString();
217
+
218
+ const usedAssets: MMUsedAssets = {
219
+ [marketInfo.loanToken]: {
220
+ symbol: marketInfo.loanToken,
221
+ supplied: credit,
222
+ borrowed: '0',
223
+ isSupplied: new Dec(positionInfo.credit.toString()).gt(0),
224
+ isBorrowed: false,
225
+ collateral: false,
226
+ suppliedUsd: creditUsd,
227
+ borrowedUsd: '0',
228
+ },
229
+ };
230
+
231
+ const { netApy } = calculateNetApy({ usedAssets, assetsData: marketInfo.assetsData as unknown as MMAssetsData });
232
+
233
+ return { apy: netApy, amount: credit, amountUsd: creditUsd };
234
+ }
@@ -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
  }
@@ -9,9 +9,11 @@ import {
9
9
  LiquityV2Markets,
10
10
  LlamaLendMarkets,
11
11
  MorphoBlueMarkets,
12
+ MorphoMidnightMarkets,
12
13
  SparkMarkets,
13
14
  } from '../markets';
14
15
  import { _getMorphoBlueAccountData, _getMorphoBlueMarketData, getMorphoEarn } from '../morphoBlue';
16
+ import { _getMorphoMidnightAccountData, _getMorphoMidnightMarketData, getMorphoMidnightEarn } from '../morphoMidnight';
15
17
  import {
16
18
  AaveV2MarketData,
17
19
  AaveV3MarketData,
@@ -26,6 +28,7 @@ import {
26
28
  LiquityV2MarketData,
27
29
  LlamaLendGlobalMarketData,
28
30
  MorphoBlueMarketInfo,
31
+ MorphoMidnightMarketInfo,
29
32
  PortfolioPositionsData,
30
33
  SparkMarketsData,
31
34
  } from '../types';
@@ -62,6 +65,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
62
65
  const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
63
66
 
64
67
  const morphoMarkets = Object.values(MorphoBlueMarkets(network)).filter((market) => market.chainIds.includes(network));
68
+ const morphoMidnightMarkets = Object.values(MorphoMidnightMarkets(network)).filter((market) => market.chainIds.includes(network));
65
69
  const compoundV3Markets = Object.values(CompoundMarkets(network)).filter((market) => market.chainIds.includes(network) && market.value !== CompoundVersions.CompoundV2);
66
70
  const sparkMarkets = Object.values(SparkMarkets(network)).filter((market) => market.chainIds.includes(network));
67
71
  const eulerV2Markets = Object.values(EulerV2Markets(network)).filter((market) => market.chainIds.includes(network));
@@ -79,6 +83,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
79
83
  const defaultClient = getViemProvider(defaultProvider, ...args);
80
84
 
81
85
  const morphoMarketsData: Record<string, MorphoBlueMarketInfo> = {};
86
+ const morphoMidnightMarketsData: Record<string, MorphoMidnightMarketInfo> = {};
82
87
  const compoundV3MarketsData: Record<string, CompoundV3MarketsData> = {};
83
88
  const sparkMarketsData: Record<string, SparkMarketsData> = {};
84
89
  const eulerV2MarketsData: Record<string, EulerV2FullMarketData> = {};
@@ -93,6 +98,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
93
98
 
94
99
  const markets = {
95
100
  morphoMarketsData,
101
+ morphoMidnightMarketsData,
96
102
  compoundV3MarketsData,
97
103
  sparkMarketsData,
98
104
  eulerV2MarketsData,
@@ -115,6 +121,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
115
121
  aaveV3: {},
116
122
  aaveV4: {},
117
123
  morphoBlue: {},
124
+ morphoMidnight: {},
118
125
  compoundV3: {},
119
126
  spark: {},
120
127
  eulerV2: {},
@@ -136,6 +143,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
136
143
  stakingPositions[address.toLowerCase() as EthAddress] = {
137
144
  aaveV3: {},
138
145
  morphoBlue: {},
146
+ morphoMidnight: {},
139
147
  compoundV3: {},
140
148
  spark: {},
141
149
  aaveV2: {},
@@ -165,6 +173,10 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
165
173
  const marketData = await _getMorphoBlueMarketData(client, network, market);
166
174
  morphoMarketsData[market.value] = marketData;
167
175
  }),
176
+ ...morphoMidnightMarkets.map(async (market) => {
177
+ const marketData = await _getMorphoMidnightMarketData(client, network, market);
178
+ morphoMidnightMarketsData[market.value] = marketData;
179
+ }),
168
180
  ...compoundV3Markets.map(async (market) => {
169
181
  const marketData = await _getCompoundV3MarketsData(client, network, market, defaultClient);
170
182
  compoundV3MarketsData[market.value] = marketData;
@@ -456,6 +468,38 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
456
468
  positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
457
469
  }
458
470
  })).flat(),
471
+ ...morphoMidnightMarkets.map((market) => addresses.map(async (address) => {
472
+ try {
473
+ const [accDataPromise, earnDataPromise] = await Promise.allSettled([
474
+ _getMorphoMidnightAccountData(client, network, address, market, morphoMidnightMarketsData[market.value]),
475
+ getMorphoMidnightEarn(client, network, address, market, morphoMidnightMarketsData[market.value]),
476
+ ]);
477
+ if (accDataPromise.status === 'rejected') {
478
+ console.error(`Error fetching MorphoMidnight account data for address ${address} on market ${market.value}:`, accDataPromise.reason);
479
+ positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: `Error fetching MorphoMidnight account data for address ${address} on market ${market.value}`, data: null };
480
+ }
481
+ if (earnDataPromise.status === 'rejected') {
482
+ console.error(`Error fetching MorphoMidnight account data for address ${address} on market ${market.value}:`, earnDataPromise.reason);
483
+ positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: `Error fetching MorphoMidnight account data for address ${address} on market ${market.value}`, data: null };
484
+ }
485
+ if (accDataPromise.status !== 'rejected') {
486
+ const accData = accDataPromise.value;
487
+ if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: '', data: accData };
488
+ }
489
+ if (earnDataPromise.status !== 'rejected') {
490
+ const earnData = earnDataPromise.value;
491
+ if (earnData && new Dec(earnData.amount).gt(0)) {
492
+ stakingPositions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = {
493
+ error: '',
494
+ data: earnData,
495
+ };
496
+ }
497
+ }
498
+ } catch (error) {
499
+ console.error(`Error fetching MorphoMidnight account data for address ${address} on market ${market.value}:`, error);
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
+ })).flat(),
459
503
  ...compoundV3Markets.map((market) => addresses.map(async (address) => {
460
504
  try {
461
505
  const accData = await _getCompoundV3AccountData(client, network, address, ZERO_ADDRESS, { selectedMarket: market, assetsData: compoundV3MarketsData[market.value].assetsData });
@@ -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 './euler';
11
12
  export * from './fluid';
@@ -0,0 +1,104 @@
1
+ import {
2
+ EthAddress, IncentiveData, LeverageType, MMUsedAssets, NetworkNumber,
3
+ } from './common';
4
+
5
+ export enum MorphoMidnightVersions {
6
+ // BASE
7
+ // Fixed-term markets are disambiguated by maturity (YYYYMMDD), so the same pair recurs across dates.
8
+ // Sourced from the official listing at https://markets.morpho.org/fixed/base (see sitemap.xml).
9
+ MorphoMidnightCbBTCUSDC_860_20260731_Base = 'morphomidnightcbbtcusdc_860_20260731_base',
10
+ MorphoMidnightCbBTCUSDC_860_20260828_Base = 'morphomidnightcbbtcusdc_860_20260828_base',
11
+ MorphoMidnightCbBTCUSDC_860_20260925_Base = 'morphomidnightcbbtcusdc_860_20260925_base',
12
+ MorphoMidnightCbBTCUSDC_860_20261030_Base = 'morphomidnightcbbtcusdc_860_20261030_base',
13
+ MorphoMidnightCbBTCUSDC_860_20261127_Base = 'morphomidnightcbbtcusdc_860_20261127_base',
14
+ MorphoMidnightCbBTCUSDC_860_20261225_Base = 'morphomidnightcbbtcusdc_860_20261225_base',
15
+ }
16
+
17
+ export interface MorphoMidnightCollateralParams {
18
+ token: EthAddress,
19
+ lltv: number | string,
20
+ liquidationCursor: number | string,
21
+ oracle: EthAddress,
22
+ }
23
+
24
+ export interface MorphoMidnightMarketData {
25
+ chainIds: NetworkNumber[],
26
+ label: string,
27
+ shortLabel: string,
28
+ url: string,
29
+ value: MorphoMidnightVersions,
30
+ midnight: EthAddress,
31
+ loanToken: EthAddress,
32
+ collaterals: MorphoMidnightCollateralParams[],
33
+ maturity: number, // unix timestamp (seconds)
34
+ rcfThreshold: number | string,
35
+ enterGate: EthAddress,
36
+ liquidatorGate: EthAddress,
37
+ marketId: string, // bytes32, precomputed off-chain (verify with MidnightView.toId)
38
+ protocolName: string,
39
+ }
40
+
41
+ export interface MorphoMidnightAssetData {
42
+ symbol: string,
43
+ address: string,
44
+ price: string,
45
+ supplyRate: string,
46
+ borrowRate: string,
47
+ supplyIncentives: IncentiveData[],
48
+ borrowIncentives: IncentiveData[],
49
+ totalSupply?: string,
50
+ totalBorrow?: string,
51
+ canBeSupplied?: boolean,
52
+ canBeBorrowed?: boolean,
53
+ lltv?: string,
54
+ }
55
+
56
+ export type MorphoMidnightAssetsData = { [key: string]: MorphoMidnightAssetData };
57
+
58
+ export interface MorphoMidnightMarketInfo {
59
+ id: string,
60
+ loanToken: string,
61
+ collaterals: string[], // collateral symbols, index-aligned with the market's collateral set
62
+ maturity: number, // unix timestamp (seconds)
63
+ isMatured: boolean, // true once now >= maturity; no new debt can be opened
64
+ totalUnits: string, // face-value units on the market (= totalDebt + withdrawable)
65
+ withdrawable: string, // loan-token liquidity available for withdraw
66
+ totalDebt: string,
67
+ lossFactor: string, // bad-debt socialization factor applied to lender credit
68
+ tickSpacing: number, // orderbook price granularity (relevant to phase-2 rate math)
69
+ utillization: string,
70
+ assetsData: MorphoMidnightAssetsData,
71
+ }
72
+
73
+ export interface MorphoMidnightAggregatedPositionData {
74
+ suppliedUsd: string,
75
+ suppliedCollateralUsd: string,
76
+ borrowedUsd: string,
77
+ borrowLimitUsd: string,
78
+ liquidationLimitUsd: string,
79
+ leftToBorrowUsd: string,
80
+ leftToBorrow: string,
81
+ netApy: string,
82
+ incentiveUsd: string,
83
+ totalInterestUsd: string,
84
+ ltv: string,
85
+ ratio: string, // health ratio as a percentage (from MidnightView.ratio, 1e18-scaled)
86
+ healthRatio: string, // liquidationLimitUsd / borrowedUsd
87
+ leveragedType: LeverageType,
88
+ leveragedAsset?: string,
89
+ currentVolatilePairRatio?: string,
90
+ liquidationPrice?: string,
91
+ minCollRatio?: string,
92
+ collLiquidationRatio?: string,
93
+ exposure: string,
94
+ }
95
+
96
+ // Fixed-rate/YTM (derived from entry price + orderbook) is intentionally absent in MVP:
97
+ // MidnightView exposes no per-position rate, so a variable-MM-style APY would be misleading.
98
+ export interface MorphoMidnightPositionData extends MorphoMidnightAggregatedPositionData {
99
+ usedAssets: MMUsedAssets,
100
+ credit: string, // lender credit units, face value at maturity (with interest); 0 for borrowers
101
+ debt: string, // borrower debt, face value at maturity (with interest); 0 for lenders
102
+ maturity: number,
103
+ isMatured: boolean,
104
+ }
@@ -10,6 +10,7 @@ import { LiquityV2TroveData, LiquityV2Versions } from './liquityV2';
10
10
  import { LlamaLendUserData, LlamaLendVersionsType } from './llamaLend';
11
11
  import { CdpData } from './maker';
12
12
  import { MorphoBluePositionData, MorphoBlueVersions } from './morphoBlue';
13
+ import { MorphoMidnightPositionData, MorphoMidnightVersions } from './morphoMidnight';
13
14
  import { SparkPositionData, SparkVersions } from './spark';
14
15
 
15
16
  export interface PortfolioProtocolData<T> {
@@ -24,6 +25,9 @@ export interface PortfolioPositionsDataForAddress {
24
25
  morphoBlue: {
25
26
  [key in MorphoBlueVersions]?: PortfolioProtocolData<MorphoBluePositionData>;
26
27
  };
28
+ morphoMidnight: {
29
+ [key in MorphoMidnightVersions]?: PortfolioProtocolData<MorphoMidnightPositionData>;
30
+ };
27
31
  compoundV3: {
28
32
  [key in CompoundVersions]?: PortfolioProtocolData<CompoundV3PositionData>;
29
33
  };