@defisaver/positions-sdk 2.1.144 → 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.
- package/cjs/config/contracts.d.ts +249 -0
- package/cjs/config/contracts.js +11 -1
- package/cjs/constants/index.d.ts +1 -0
- package/cjs/constants/index.js +2 -1
- package/cjs/contracts.d.ts +1510 -0
- package/cjs/contracts.js +3 -2
- package/cjs/helpers/index.d.ts +1 -0
- package/cjs/helpers/index.js +2 -1
- package/cjs/helpers/morphoMidnightHelpers/headroom.d.ts +48 -0
- package/cjs/helpers/morphoMidnightHelpers/headroom.js +52 -0
- package/cjs/helpers/morphoMidnightHelpers/index.d.ts +113 -0
- package/cjs/helpers/morphoMidnightHelpers/index.js +298 -0
- package/cjs/helpers/morphoMidnightHelpers/rate.d.ts +15 -0
- package/cjs/helpers/morphoMidnightHelpers/rate.js +62 -0
- package/cjs/helpers/morphoMidnightHelpers/tenor.d.ts +129 -0
- package/cjs/helpers/morphoMidnightHelpers/tenor.js +254 -0
- package/cjs/index.d.ts +2 -1
- package/cjs/index.js +3 -1
- package/cjs/markets/index.d.ts +1 -0
- package/cjs/markets/index.js +6 -1
- package/cjs/markets/morphoMidnight/index.d.ts +60 -0
- package/cjs/markets/morphoMidnight/index.js +659 -0
- package/cjs/morphoMidnight/index.d.ts +14 -0
- package/cjs/morphoMidnight/index.js +261 -0
- package/cjs/portfolio/discovery.js +4 -0
- package/cjs/portfolio/index.js +44 -0
- package/cjs/services/viem.d.ts +11 -11
- package/cjs/types/index.d.ts +1 -0
- package/cjs/types/index.js +1 -0
- package/cjs/types/morphoMidnight.d.ts +141 -0
- package/cjs/types/morphoMidnight.js +47 -0
- package/cjs/types/portfolio.d.ts +4 -0
- package/esm/config/contracts.d.ts +249 -0
- package/esm/config/contracts.js +9 -0
- package/esm/constants/index.d.ts +1 -0
- package/esm/constants/index.js +1 -0
- package/esm/contracts.d.ts +1510 -0
- package/esm/contracts.js +1 -0
- package/esm/helpers/index.d.ts +1 -0
- package/esm/helpers/index.js +1 -0
- package/esm/helpers/morphoMidnightHelpers/headroom.d.ts +48 -0
- package/esm/helpers/morphoMidnightHelpers/headroom.js +45 -0
- package/esm/helpers/morphoMidnightHelpers/index.d.ts +113 -0
- package/esm/helpers/morphoMidnightHelpers/index.js +271 -0
- package/esm/helpers/morphoMidnightHelpers/rate.d.ts +15 -0
- package/esm/helpers/morphoMidnightHelpers/rate.js +50 -0
- package/esm/helpers/morphoMidnightHelpers/tenor.d.ts +129 -0
- package/esm/helpers/morphoMidnightHelpers/tenor.js +238 -0
- package/esm/index.d.ts +2 -1
- package/esm/index.js +2 -1
- package/esm/markets/index.d.ts +1 -0
- package/esm/markets/index.js +1 -0
- package/esm/markets/morphoMidnight/index.d.ts +60 -0
- package/esm/markets/morphoMidnight/index.js +631 -0
- package/esm/morphoMidnight/index.d.ts +14 -0
- package/esm/morphoMidnight/index.js +248 -0
- package/esm/portfolio/discovery.js +5 -1
- package/esm/portfolio/index.js +45 -1
- package/esm/services/viem.d.ts +11 -11
- package/esm/types/index.d.ts +1 -0
- package/esm/types/index.js +1 -0
- package/esm/types/morphoMidnight.d.ts +141 -0
- package/esm/types/morphoMidnight.js +44 -0
- package/esm/types/portfolio.d.ts +4 -0
- package/package.json +1 -1
- package/src/config/contracts.ts +9 -0
- package/src/constants/index.ts +1 -0
- package/src/contracts.ts +1 -0
- package/src/helpers/index.ts +1 -0
- package/src/helpers/morphoMidnightHelpers/headroom.ts +82 -0
- package/src/helpers/morphoMidnightHelpers/index.ts +485 -0
- package/src/helpers/morphoMidnightHelpers/rate.ts +71 -0
- package/src/helpers/morphoMidnightHelpers/tenor.ts +407 -0
- package/src/index.ts +2 -0
- package/src/markets/index.ts +3 -0
- package/src/markets/morphoMidnight/index.ts +675 -0
- package/src/morphoMidnight/index.ts +276 -0
- package/src/portfolio/discovery.ts +6 -0
- package/src/portfolio/index.ts +44 -0
- package/src/types/index.ts +1 -0
- package/src/types/morphoMidnight.ts +163 -0
- 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
|
}
|
package/src/portfolio/index.ts
CHANGED
|
@@ -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 });
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
// Tenor-hosted Midnight markets (same core, different order book)
|
|
16
|
+
MorphoMidnightTenorCbBTCUSDC_20260827_Base = 'morphomidnighttenorcbbtcusdc_20260827_base',
|
|
17
|
+
MorphoMidnightTenorCbBTCUSDC_20260924_Base = 'morphomidnighttenorcbbtcusdc_20260924_base',
|
|
18
|
+
MorphoMidnightTenorCbBTCUSDC_20261022_Base = 'morphomidnighttenorcbbtcusdc_20261022_base',
|
|
19
|
+
MorphoMidnightTenorCbBTCUSDC_20261119_Base = 'morphomidnighttenorcbbtcusdc_20261119_base',
|
|
20
|
+
MorphoMidnightTenorCbBTCUSDC_20261217_Base = 'morphomidnighttenorcbbtcusdc_20261217_base',
|
|
21
|
+
MorphoMidnightTenorWETHUSDC_20260827_Base = 'morphomidnighttenorwethusdc_20260827_base',
|
|
22
|
+
MorphoMidnightTenorWETHUSDC_20260924_Base = 'morphomidnighttenorwethusdc_20260924_base',
|
|
23
|
+
MorphoMidnightTenorWETHUSDC_20261022_Base = 'morphomidnighttenorwethusdc_20261022_base',
|
|
24
|
+
MorphoMidnightTenorWETHUSDC_20261119_Base = 'morphomidnighttenorwethusdc_20261119_base',
|
|
25
|
+
MorphoMidnightTenorWETHUSDC_20261217_Base = 'morphomidnighttenorwethusdc_20261217_base',
|
|
26
|
+
MorphoMidnightTenorCbETHWETH_20260827_Base = 'morphomidnighttenorcbethweth_20260827_base',
|
|
27
|
+
MorphoMidnightTenorCbETHWETH_20260924_Base = 'morphomidnighttenorcbethweth_20260924_base',
|
|
28
|
+
MorphoMidnightTenorCbETHWETH_20261022_Base = 'morphomidnighttenorcbethweth_20261022_base',
|
|
29
|
+
MorphoMidnightTenorCbETHWETH_20261119_Base = 'morphomidnighttenorcbethweth_20261119_base',
|
|
30
|
+
MorphoMidnightTenorCbETHWETH_20261217_Base = 'morphomidnighttenorcbethweth_20261217_base',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type MorphoMidnightCurator = 'Morpho' | 'Tenor';
|
|
34
|
+
|
|
35
|
+
export interface MorphoMidnightCollateralParams {
|
|
36
|
+
token: EthAddress,
|
|
37
|
+
lltv: number | string,
|
|
38
|
+
liquidationCursor: number | string,
|
|
39
|
+
oracle: EthAddress,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface MorphoMidnightMarketData {
|
|
43
|
+
chainIds: NetworkNumber[],
|
|
44
|
+
label: string,
|
|
45
|
+
shortLabel: string,
|
|
46
|
+
url: string,
|
|
47
|
+
value: MorphoMidnightVersions,
|
|
48
|
+
midnight: EthAddress,
|
|
49
|
+
loanToken: EthAddress,
|
|
50
|
+
collaterals: MorphoMidnightCollateralParams[],
|
|
51
|
+
/**
|
|
52
|
+
* Tenor's curated markets list the curator's own vault share token next to the real collateral.
|
|
53
|
+
*/
|
|
54
|
+
hiddenCollaterals?: MorphoMidnightCollateralParams[],
|
|
55
|
+
maturity: number, // unix timestamp (seconds)
|
|
56
|
+
rcfThreshold: number | string,
|
|
57
|
+
enterGate: EthAddress,
|
|
58
|
+
liquidatorGate: EthAddress,
|
|
59
|
+
marketId: string, // bytes32, precomputed off-chain (verify with MidnightView.toId)
|
|
60
|
+
protocolName: string,
|
|
61
|
+
curator: MorphoMidnightCurator,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface MorphoMidnightAssetData {
|
|
65
|
+
symbol: string,
|
|
66
|
+
address: string,
|
|
67
|
+
price: string,
|
|
68
|
+
supplyRate: string,
|
|
69
|
+
borrowRate: string,
|
|
70
|
+
supplyIncentives: IncentiveData[],
|
|
71
|
+
borrowIncentives: IncentiveData[],
|
|
72
|
+
totalSupply?: string,
|
|
73
|
+
totalBorrow?: string,
|
|
74
|
+
canBeSupplied?: boolean,
|
|
75
|
+
canBeBorrowed?: boolean,
|
|
76
|
+
lltv?: string,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type MorphoMidnightAssetsData = { [key: string]: MorphoMidnightAssetData };
|
|
80
|
+
|
|
81
|
+
export interface MorphoMidnightMarketInfo {
|
|
82
|
+
id: string,
|
|
83
|
+
loanToken: string,
|
|
84
|
+
collaterals: string[], // collateral symbols, index-aligned with the market's collateral set
|
|
85
|
+
maturity: number, // unix timestamp (seconds)
|
|
86
|
+
isMatured: boolean, // true once now >= maturity; no new debt can be opened
|
|
87
|
+
totalUnits: string, // face-value units on the market (= totalDebt + withdrawable)
|
|
88
|
+
withdrawable: string, // loan-token liquidity available for withdraw
|
|
89
|
+
totalDebt: string,
|
|
90
|
+
lossFactor: string, // bad-debt socialization factor applied to lender credit
|
|
91
|
+
tickSpacing: number, // orderbook price granularity (relevant to phase-2 rate math)
|
|
92
|
+
utillization: string,
|
|
93
|
+
assetsData: MorphoMidnightAssetsData,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Which half of the order book a caller is taking from: `bids` are the lend offers a borrower fills,
|
|
97
|
+
// `asks` the sell offers a repayer buys debt units from.
|
|
98
|
+
export type MorphoMidnightBookSide = 'bids' | 'asks';
|
|
99
|
+
|
|
100
|
+
// One resting offer on a market's order book, as an annualized rate rather than the API's raw WAD price.
|
|
101
|
+
export interface MorphoMidnightBookOffer {
|
|
102
|
+
rate: string, // fixed APY, percent
|
|
103
|
+
liquidity: string, // loan-token amount available at this rate
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface MorphoMidnightParsedBook {
|
|
107
|
+
bestRate: string, // best rate for the taker of this side (= offers[0].rate)
|
|
108
|
+
totalLiquidity: string, // Σ offers[].liquidity, loan-token units
|
|
109
|
+
offers: MorphoMidnightBookOffer[], // best-first: bids ascending by rate, asks descending
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface MorphoMidnightAggregatedPositionData {
|
|
113
|
+
suppliedUsd: string,
|
|
114
|
+
suppliedCollateralUsd: string,
|
|
115
|
+
borrowedUsd: string,
|
|
116
|
+
borrowLimitUsd: string,
|
|
117
|
+
liquidationLimitUsd: string,
|
|
118
|
+
leftToBorrowUsd: string,
|
|
119
|
+
leftToBorrow: string,
|
|
120
|
+
netApy: string,
|
|
121
|
+
incentiveUsd: string,
|
|
122
|
+
totalInterestUsd: string,
|
|
123
|
+
ltv: string,
|
|
124
|
+
ratio: string, // health ratio as a percentage (from MidnightView.ratio, 1e18-scaled)
|
|
125
|
+
healthRatio: string, // liquidationLimitUsd / borrowedUsd
|
|
126
|
+
leveragedType: LeverageType,
|
|
127
|
+
leveragedAsset?: string,
|
|
128
|
+
currentVolatilePairRatio?: string,
|
|
129
|
+
liquidationPrice?: string,
|
|
130
|
+
minCollRatio?: string,
|
|
131
|
+
collLiquidationRatio?: string,
|
|
132
|
+
exposure: string,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* How much weight `borrowRate` / `debtBase` / `debtInterest` carry on a given position. They fall back to
|
|
137
|
+
* `'0'` / `debt` / `'0'`, which is indistinguishable from a real 0%-interest position, so anything
|
|
138
|
+
* displaying them has to read this to know whether it is looking at a number or at a placeholder.
|
|
139
|
+
*
|
|
140
|
+
* - `Available` — reported and reconciled against the on-chain debt (also lenders and debt-free positions,
|
|
141
|
+
* which have nothing to report).
|
|
142
|
+
* - `Pending` — the indexer has not caught up with the chain yet: it does not know the position, or its
|
|
143
|
+
* split describes a different debt. Refetching resolves it; `getMorphoMidnightUserBorrowInfo` failing
|
|
144
|
+
* outright lands here too, since the next call may well succeed.
|
|
145
|
+
*/
|
|
146
|
+
export enum MorphoMidnightBorrowInfoStatus {
|
|
147
|
+
Available = 'available',
|
|
148
|
+
Pending = 'pending',
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Fixed-rate/YTM (derived from entry price + orderbook) is intentionally absent in MVP:
|
|
152
|
+
// MidnightView exposes no per-position rate, so a variable-MM-style APY would be misleading.
|
|
153
|
+
export interface MorphoMidnightPositionData extends MorphoMidnightAggregatedPositionData {
|
|
154
|
+
usedAssets: MMUsedAssets,
|
|
155
|
+
credit: string, // lender credit units, face value at maturity (with interest); 0 for borrowers
|
|
156
|
+
debt: string, // borrower debt, face value at maturity (with interest); 0 for lenders
|
|
157
|
+
borrowRate: string, // weighted-average borrow APY as a percent
|
|
158
|
+
debtBase: string, // base borrowed (principal), loan-token units
|
|
159
|
+
debtInterest: string, // debt − debtBase (fixed interest owed at maturity), loan-token units
|
|
160
|
+
borrowInfoStatus: MorphoMidnightBorrowInfoStatus,
|
|
161
|
+
maturity: number,
|
|
162
|
+
isMatured: boolean,
|
|
163
|
+
}
|
package/src/types/portfolio.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { LiquityV2TroveData, LiquityV2Versions } from './liquityV2';
|
|
|
9
9
|
import { LlamaLendUserData, LlamaLendVersionsType } from './llamaLend';
|
|
10
10
|
import { CdpData } from './maker';
|
|
11
11
|
import { MorphoBluePositionData, MorphoBlueVersions } from './morphoBlue';
|
|
12
|
+
import { MorphoMidnightPositionData, MorphoMidnightVersions } from './morphoMidnight';
|
|
12
13
|
import { SparkPositionData, SparkVersions } from './spark';
|
|
13
14
|
|
|
14
15
|
export interface PortfolioProtocolData<T> {
|
|
@@ -23,6 +24,9 @@ export interface PortfolioPositionsDataForAddress {
|
|
|
23
24
|
morphoBlue: {
|
|
24
25
|
[key in MorphoBlueVersions]?: PortfolioProtocolData<MorphoBluePositionData>;
|
|
25
26
|
};
|
|
27
|
+
morphoMidnight: {
|
|
28
|
+
[key in MorphoMidnightVersions]?: PortfolioProtocolData<MorphoMidnightPositionData>;
|
|
29
|
+
};
|
|
26
30
|
compoundV3: {
|
|
27
31
|
[key in CompoundVersions]?: PortfolioProtocolData<CompoundV3PositionData>;
|
|
28
32
|
};
|