@defisaver/positions-sdk 2.1.168 → 2.1.169-portfolio-api-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.
- package/cjs/aaveV3/index.d.ts +2 -12
- package/cjs/aaveV3/sgho.d.ts +2 -16
- package/cjs/aaveV4/lend.d.ts +2 -43
- package/cjs/claiming/aaveV3.d.ts +2 -2
- package/cjs/claiming/ethena.d.ts +2 -1
- package/cjs/claiming/king.d.ts +2 -1
- package/cjs/claiming/spark.d.ts +2 -2
- package/cjs/fluid/index.d.ts +5 -49
- package/cjs/liquity/index.d.ts +2 -12
- package/cjs/liquityV2/index.d.ts +8 -23
- package/cjs/liquityV2/index.js +37 -19
- package/cjs/portfolio/index.d.ts +16 -8
- package/cjs/portfolio/index.js +458 -520
- package/cjs/portfolio/lendingProtocols.d.ts +184 -0
- package/cjs/portfolio/lendingProtocols.js +196 -0
- package/cjs/savings/index.d.ts +8 -2
- package/cjs/savings/index.js +33 -4
- package/cjs/types/aave.d.ts +98 -2
- package/cjs/types/aaveV4.d.ts +44 -0
- package/cjs/types/compound.d.ts +26 -2
- package/cjs/types/curveUsd.d.ts +9 -1
- package/cjs/types/fluid.d.ts +13 -0
- package/cjs/types/index.d.ts +1 -0
- package/cjs/types/index.js +1 -0
- package/cjs/types/liquity.d.ts +12 -0
- package/cjs/types/liquityV2.d.ts +24 -0
- package/cjs/types/llamaLend.d.ts +12 -1
- package/cjs/types/morphoBlue.d.ts +2 -0
- package/cjs/types/portfolio.d.ts +125 -5
- package/cjs/types/savings/index.d.ts +9 -1
- package/cjs/types/spark.d.ts +21 -1
- package/cjs/umbrella/index.d.ts +2 -3
- package/esm/aaveV3/index.d.ts +2 -12
- package/esm/aaveV3/sgho.d.ts +2 -16
- package/esm/aaveV4/lend.d.ts +2 -43
- package/esm/claiming/aaveV3.d.ts +2 -2
- package/esm/claiming/aaveV3.js +1 -1
- package/esm/claiming/ethena.d.ts +2 -1
- package/esm/claiming/king.d.ts +2 -1
- package/esm/claiming/spark.d.ts +2 -2
- package/esm/claiming/spark.js +1 -1
- package/esm/fluid/index.d.ts +5 -49
- package/esm/liquity/index.d.ts +2 -12
- package/esm/liquityV2/index.d.ts +8 -23
- package/esm/liquityV2/index.js +35 -18
- package/esm/portfolio/index.d.ts +16 -8
- package/esm/portfolio/index.js +458 -522
- package/esm/portfolio/lendingProtocols.d.ts +184 -0
- package/esm/portfolio/lendingProtocols.js +189 -0
- package/esm/savings/index.d.ts +8 -2
- package/esm/savings/index.js +31 -3
- package/esm/types/aave.d.ts +98 -2
- package/esm/types/aaveV4.d.ts +44 -0
- package/esm/types/compound.d.ts +26 -2
- package/esm/types/curveUsd.d.ts +9 -1
- package/esm/types/fluid.d.ts +13 -0
- package/esm/types/index.d.ts +1 -0
- package/esm/types/index.js +1 -0
- package/esm/types/liquity.d.ts +12 -0
- package/esm/types/liquityV2.d.ts +24 -0
- package/esm/types/llamaLend.d.ts +12 -1
- package/esm/types/morphoBlue.d.ts +2 -0
- package/esm/types/portfolio.d.ts +125 -5
- package/esm/types/savings/index.d.ts +9 -1
- package/esm/types/spark.d.ts +21 -1
- package/esm/umbrella/index.d.ts +2 -3
- package/package.json +1 -1
- package/src/aaveV3/index.ts +2 -1
- package/src/aaveV3/sgho.ts +2 -17
- package/src/aaveV4/lend.ts +2 -48
- package/src/claiming/aaveV3.ts +4 -2
- package/src/claiming/ethena.ts +5 -5
- package/src/claiming/king.ts +3 -3
- package/src/claiming/spark.ts +6 -4
- package/src/fluid/index.ts +4 -3
- package/src/liquity/index.ts +2 -2
- package/src/liquityV2/index.ts +48 -22
- package/src/portfolio/index.ts +306 -331
- package/src/portfolio/lendingProtocols.ts +281 -0
- package/src/savings/index.ts +41 -4
- package/src/types/aave.ts +104 -1
- package/src/types/aaveV4.ts +49 -0
- package/src/types/compound.ts +38 -2
- package/src/types/curveUsd.ts +17 -1
- package/src/types/fluid.ts +14 -0
- package/src/types/index.ts +1 -0
- package/src/types/liquity.ts +13 -1
- package/src/types/liquityV2.ts +26 -1
- package/src/types/llamaLend.ts +19 -1
- package/src/types/morphoBlue.ts +3 -0
- package/src/types/portfolio.ts +151 -5
- package/src/types/savings/index.ts +12 -1
- package/src/types/spark.ts +25 -1
- package/src/umbrella/index.ts +2 -1
package/src/portfolio/index.ts
CHANGED
|
@@ -9,137 +9,158 @@ import {
|
|
|
9
9
|
LlamaLendMarkets,
|
|
10
10
|
MakerActiveIlks,
|
|
11
11
|
MorphoBlueMarkets,
|
|
12
|
-
MorphoMidnightMarkets,
|
|
13
12
|
SparkMarkets,
|
|
14
13
|
} from '../markets';
|
|
15
14
|
import { _getMorphoBlueAccountData, _getMorphoBluePortfolioMarketData, getMorphoEarn } from '../morphoBlue';
|
|
16
|
-
import { _getMorphoMidnightAccountData, _getMorphoMidnightMarketData } from '../morphoMidnight';
|
|
17
15
|
import {
|
|
18
|
-
AaveV2MarketData,
|
|
19
|
-
AaveV3MarketData,
|
|
20
|
-
AaveV4SpokeData,
|
|
21
16
|
AaveVersions,
|
|
22
17
|
CdpInfo,
|
|
23
|
-
CompoundV2MarketsData,
|
|
24
|
-
CompoundV3MarketsData,
|
|
25
18
|
CompoundVersions,
|
|
26
|
-
CrvUSDGlobalMarketData,
|
|
27
19
|
LiquityV2MarketData,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
LiquityV2Versions,
|
|
21
|
+
PortfolioData,
|
|
22
|
+
PortfolioDataOptions,
|
|
23
|
+
PortfolioLiquityV2Troves,
|
|
31
24
|
PortfolioMarketsData,
|
|
25
|
+
PortfolioMarketsErrors,
|
|
26
|
+
PortfolioMarketsResult,
|
|
32
27
|
PortfolioPositionsData,
|
|
33
|
-
|
|
28
|
+
PortfolioPositionsDataForAddress,
|
|
29
|
+
PortfolioRewardsData,
|
|
30
|
+
PortfolioStakingPositionsData,
|
|
31
|
+
PortfolioUserData,
|
|
32
|
+
ShifterPortfolioMarketsData,
|
|
34
33
|
} from '../types';
|
|
35
34
|
import { _getCompoundV3AccountData, _getCompoundV3MarketsData } from '../compoundV3';
|
|
36
35
|
import { _getSparkAccountData, _getSparkMarketsData } from '../spark';
|
|
37
36
|
import { _getCurveUsdGlobalData, _getCurveUsdUserData } from '../curveUsd';
|
|
38
37
|
import { _getLlamaLendGlobalData, _getLlamaLendUserData } from '../llamaLend';
|
|
39
38
|
import { _getAaveV3AccountData, _getAaveV3MarketData, getStakeAaveData } from '../aaveV3';
|
|
39
|
+
import { _getAaveV4AccountData, _getAaveV4SpokeData } from '../aaveV4';
|
|
40
40
|
import { ZERO_ADDRESS } from '../constants';
|
|
41
41
|
import { _getMakerCdpData, _getMakerIlksData, _getUserCdps } from '../maker';
|
|
42
42
|
import { _getAaveV2AccountData, _getAaveV2MarketsData } from '../aaveV2';
|
|
43
43
|
import { _getCompoundV2AccountData, _getCompoundV2MarketsData } from '../compoundV2';
|
|
44
44
|
import { getViemProvider } from '../services/viem';
|
|
45
45
|
import { _getLiquityTroveInfo, getLiquityStakingData } from '../liquity';
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
_getLiquityV2MarketData, _getLiquityV2TroveData, _getLiquityV2UsersTroveIds, getLiquitySAndYBold, getLiquityV2Staking,
|
|
48
|
+
} from '../liquityV2';
|
|
47
49
|
import { _getAllFluidMarketDataPortfolio, _getAllUserEarnPositionsWithFTokens, _getUserPositionsPortfolio } from '../fluid';
|
|
48
50
|
import { getUmbrellaData } from '../umbrella';
|
|
49
51
|
import { getMerklUnclaimedRewards, getUnclaimedRewardsForAllMarkets } from '../claiming/aaveV3';
|
|
50
52
|
import { fetchSparkAirdropRewards, fetchSparkRewards } from '../claiming/spark';
|
|
51
53
|
import { getKingRewards } from '../claiming/king';
|
|
52
54
|
import { fetchEthenaAirdropRewards } from '../claiming/ethena';
|
|
53
|
-
import { _getAaveV4AccountData, _getAaveV4SpokeData } from '../aaveV4';
|
|
54
55
|
import { getUniswapRewards } from '../claiming/uniswap';
|
|
56
|
+
import {
|
|
57
|
+
aaveV3Lending, LENDING_PROTOCOLS, LendingKey, morphoBlueLending, requireMarket, sparkLending,
|
|
58
|
+
} from './lendingProtocols';
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
positions: PortfolioPositionsData;
|
|
58
|
-
stakingPositions: any;
|
|
59
|
-
rewardsData: any;
|
|
60
|
-
markets: any;
|
|
61
|
-
}> {
|
|
62
|
-
const isMainnet = network === NetworkNumber.Eth;
|
|
63
|
-
const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
|
|
64
|
-
|
|
65
|
-
const morphoMarkets = Object.values(MorphoBlueMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
66
|
-
const morphoMidnightMarkets = Object.values(MorphoMidnightMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
67
|
-
const compoundV3Markets = Object.values(CompoundMarkets(network)).filter((market) => market.chainIds.includes(network) && market.value !== CompoundVersions.CompoundV2);
|
|
68
|
-
const sparkMarkets = Object.values(SparkMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
69
|
-
const aaveV3Markets = [AaveVersions.AaveV3, AaveVersions.AaveV3Lido, AaveVersions.AaveV3Etherfi].map((version) => AaveMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
|
|
70
|
-
const aaveV2Markets = [AaveVersions.AaveV2].map((version) => AaveMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
|
|
71
|
-
const compoundV2Markets = [CompoundVersions.CompoundV2].map((version) => CompoundMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
|
|
72
|
-
const crvUsdMarkets = Object.values(CrvUsdMarkets(network)).filter((market) => market.chainIds.includes(network));
|
|
73
|
-
const llamaLendMarkets = [NetworkNumber.Eth, NetworkNumber.Arb].includes(network) ? Object.values(LlamaLendMarkets(network)).filter((market) => market.chainIds.includes(network)) : [];
|
|
74
|
-
const liquityV2Markets = [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)) : [];
|
|
75
|
-
const liquityV2MarketsStaking = [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)).filter(market => !market.isLegacy) : [];
|
|
76
|
-
const aaveV4Spokes = Object.values(AaveV4Spokes(network)).filter((market) => market.chainIds.includes(network));
|
|
60
|
+
type PortfolioClient = ReturnType<typeof getViemProvider>;
|
|
77
61
|
|
|
62
|
+
/** The markets of the protocols read by their own code below; LENDING_PROTOCOLS has the others'. */
|
|
63
|
+
const getPortfolioMarkets = (network: NetworkNumber) => ({
|
|
64
|
+
liquityV2Markets: [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)) : [],
|
|
65
|
+
liquityV2MarketsStaking: [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)).filter(market => !market.isLegacy) : [],
|
|
66
|
+
});
|
|
78
67
|
|
|
68
|
+
const getPortfolioClients = (provider: EthereumProvider, defaultProvider: EthereumProvider, network: NetworkNumber, isSim: boolean) => {
|
|
79
69
|
// batchSize is viem's cap on a batch's raw subcall calldata (bytes); the JSON-RPC body ends up
|
|
80
70
|
// ~4x larger (hex + aggregate3 ABI + JSON overhead) and RPC providers reject bodies over
|
|
81
71
|
// ~2.5MB with HTTP 413, so keep this small enough that no single body gets near that.
|
|
82
72
|
// 250k gives the largest body of around 1.26MB - if we bump, we can save maybe 2-3 rpc calls but scaling takes a hit
|
|
83
73
|
// (e.g. adding new markets may result in body size going over alchemy body size limit)
|
|
84
74
|
const args: [NetworkNumber, any?] = [network, { batch: { multicall: { batchSize: isSim ? 2_000 : 250_000 } } }];
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
return {
|
|
76
|
+
client: getViemProvider(provider, ...args),
|
|
77
|
+
defaultClient: getViemProvider(defaultProvider, ...args),
|
|
78
|
+
};
|
|
79
|
+
};
|
|
87
80
|
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
morphoMidnightMarketsData,
|
|
104
|
-
compoundV3MarketsData,
|
|
105
|
-
sparkMarketsData,
|
|
106
|
-
aaveV3MarketsData,
|
|
107
|
-
aaveV2MarketsData,
|
|
108
|
-
compoundV2MarketsData,
|
|
109
|
-
crvUsdMarketsData,
|
|
110
|
-
llamaLendMarketsData,
|
|
111
|
-
liquityV2MarketsData,
|
|
112
|
-
aaveV4SpokesData,
|
|
81
|
+
const _getPortfolioMarketsData = async (client: PortfolioClient, defaultClient: PortfolioClient, network: NetworkNumber): Promise<PortfolioMarketsResult> => {
|
|
82
|
+
const { liquityV2Markets } = getPortfolioMarkets(network);
|
|
83
|
+
|
|
84
|
+
const markets: PortfolioMarketsData = {
|
|
85
|
+
morphoMarketsData: {},
|
|
86
|
+
morphoMidnightMarketsData: {},
|
|
87
|
+
compoundV3MarketsData: {},
|
|
88
|
+
sparkMarketsData: {},
|
|
89
|
+
aaveV3MarketsData: {},
|
|
90
|
+
aaveV2MarketsData: {},
|
|
91
|
+
compoundV2MarketsData: {},
|
|
92
|
+
crvUsdMarketsData: {},
|
|
93
|
+
llamaLendMarketsData: {},
|
|
94
|
+
liquityV2MarketsData: {},
|
|
95
|
+
aaveV4SpokesData: {},
|
|
113
96
|
};
|
|
97
|
+
const liquityV2Failed: Record<string, string> = {};
|
|
98
|
+
|
|
99
|
+
const [lendingFailed] = await Promise.all([
|
|
100
|
+
Promise.all(LENDING_PROTOCOLS.map(async (protocol) => [protocol.marketsKey, await protocol.readMarkets({ client, defaultClient, network }, markets)] as const)),
|
|
101
|
+
// Liquity V2's markets: its troves are read by their own code below.
|
|
102
|
+
...liquityV2Markets.map(async (market) => {
|
|
103
|
+
try {
|
|
104
|
+
markets.liquityV2MarketsData[market.value] = await _getLiquityV2MarketData(client, network, market);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(`Error fetching liquityV2MarketsData for market ${market.value}:`, error);
|
|
107
|
+
liquityV2Failed[market.value] = `Error fetching liquityV2MarketsData for market ${market.value}`;
|
|
108
|
+
}
|
|
109
|
+
}),
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// A market that failed is left out of markets and named here, under its protocol's key.
|
|
113
|
+
const errors: PortfolioMarketsErrors = {};
|
|
114
|
+
for (const [key, failed] of [...lendingFailed, ['liquityV2MarketsData', liquityV2Failed] as const]) {
|
|
115
|
+
if (Object.keys(failed).length) errors[key] = failed;
|
|
116
|
+
}
|
|
117
|
+
return { markets, errors };
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const _getPortfolioUserData = async (
|
|
121
|
+
client: PortfolioClient,
|
|
122
|
+
network: NetworkNumber,
|
|
123
|
+
addresses: EthAddress[],
|
|
124
|
+
marketsData: Promise<PortfolioMarketsData>,
|
|
125
|
+
isSim: boolean,
|
|
126
|
+
options: PortfolioDataOptions,
|
|
127
|
+
): Promise<PortfolioUserData> => {
|
|
128
|
+
const isMainnet = network === NetworkNumber.Eth;
|
|
129
|
+
const includePositions = options.positions !== false;
|
|
130
|
+
const includeStaking = options.staking !== false;
|
|
131
|
+
const includeRewards = options.rewards !== false;
|
|
132
|
+
const includeLiquityV2 = includePositions && options.liquityV2 === true;
|
|
133
|
+
// The addresses positions, staking, rewards and Liquity V2 troves are read for: none when left out, so their fetches
|
|
134
|
+
// below make no calls.
|
|
135
|
+
const positionsAddresses = includePositions ? addresses : [];
|
|
136
|
+
const stakingAddresses = includeStaking ? addresses : [];
|
|
137
|
+
const rewardsAddresses = includeRewards ? addresses : [];
|
|
138
|
+
const liquityV2Addresses = includeLiquityV2 ? addresses : [];
|
|
139
|
+
const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
|
|
140
|
+
|
|
141
|
+
const { liquityV2Markets, liquityV2MarketsStaking } = getPortfolioMarkets(network);
|
|
114
142
|
|
|
115
143
|
const positions: PortfolioPositionsData = {};
|
|
116
|
-
const stakingPositions:
|
|
117
|
-
const rewardsData:
|
|
118
|
-
const
|
|
144
|
+
const stakingPositions: PortfolioStakingPositionsData = {};
|
|
145
|
+
const rewardsData: PortfolioRewardsData = {};
|
|
146
|
+
const makerCdpsErrors: Record<EthAddress, string> = {};
|
|
119
147
|
|
|
120
|
-
for (const address of
|
|
148
|
+
for (const address of positionsAddresses) {
|
|
121
149
|
positions[address.toLowerCase() as EthAddress] = {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
morphoBlue: {},
|
|
125
|
-
morphoMidnight: {},
|
|
126
|
-
compoundV3: {},
|
|
127
|
-
spark: {},
|
|
150
|
+
// Every registry protocol, keyed by market as its reads fill it in.
|
|
151
|
+
...Object.fromEntries(LENDING_PROTOCOLS.map((protocol) => [protocol.key, {}])) as Pick<PortfolioPositionsDataForAddress, LendingKey>,
|
|
128
152
|
maker: {},
|
|
129
|
-
aaveV2: {},
|
|
130
|
-
compoundV2: {},
|
|
131
153
|
liquity: {},
|
|
132
|
-
crvUsd: {},
|
|
133
|
-
llamaLend: {},
|
|
134
154
|
fluid: {
|
|
135
155
|
error: '',
|
|
136
156
|
data: {},
|
|
137
157
|
},
|
|
158
|
+
...(includeLiquityV2 ? { liquityV2: {} } : {}),
|
|
138
159
|
};
|
|
139
160
|
}
|
|
140
161
|
|
|
141
162
|
// TODO: check default values, probably needed when fetching portfolio on unsupported networks
|
|
142
|
-
for (const address of
|
|
163
|
+
for (const address of stakingAddresses) {
|
|
143
164
|
stakingPositions[address.toLowerCase() as EthAddress] = {
|
|
144
165
|
aaveV3: {},
|
|
145
166
|
morphoBlue: {},
|
|
@@ -153,72 +174,52 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
153
174
|
error: '',
|
|
154
175
|
data: {},
|
|
155
176
|
},
|
|
177
|
+
// Set by the sBOLD/yBOLD fetch below, whether it succeeds or fails.
|
|
178
|
+
liquityV2SBoldYBold: { error: '', data: null },
|
|
156
179
|
};
|
|
180
|
+
}
|
|
157
181
|
|
|
182
|
+
for (const address of rewardsAddresses) {
|
|
183
|
+
// merkl, spk, king and uniswap are set by their fetches below, whether they succeed or fail.
|
|
158
184
|
rewardsData[address.toLowerCase() as EthAddress] = {
|
|
159
|
-
merkl: {},
|
|
185
|
+
merkl: { error: '', data: null },
|
|
160
186
|
aaveV3: {},
|
|
161
187
|
spark: {},
|
|
162
|
-
spk: {},
|
|
163
|
-
king: {},
|
|
188
|
+
spk: { error: '', data: null },
|
|
189
|
+
king: { error: '', data: null },
|
|
164
190
|
ethena: {},
|
|
191
|
+
uniswap: { error: '', data: null },
|
|
165
192
|
};
|
|
166
193
|
}
|
|
167
194
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
...morphoMidnightMarkets.map(async (market) => {
|
|
175
|
-
const marketData = await _getMorphoMidnightMarketData(client, network, market);
|
|
176
|
-
morphoMidnightMarketsData[market.value] = marketData;
|
|
177
|
-
}),
|
|
178
|
-
...compoundV3Markets.map(async (market) => {
|
|
179
|
-
const marketData = await _getCompoundV3MarketsData(client, network, market, defaultClient);
|
|
180
|
-
compoundV3MarketsData[market.value] = marketData;
|
|
181
|
-
}),
|
|
182
|
-
...sparkMarkets.map(async (market) => {
|
|
183
|
-
const marketData = await _getSparkMarketsData(client, network, market);
|
|
184
|
-
sparkMarketsData[market.value] = marketData;
|
|
185
|
-
}),
|
|
186
|
-
...aaveV3Markets.map(async (market) => {
|
|
187
|
-
const marketData = await _getAaveV3MarketData(client, network, market);
|
|
188
|
-
aaveV3MarketsData[market.value] = marketData;
|
|
189
|
-
}),
|
|
190
|
-
...aaveV4Spokes.map(async (spoke) => {
|
|
191
|
-
const spokeData = await _getAaveV4SpokeData(client, network, spoke);
|
|
192
|
-
aaveV4SpokesData[spoke.value] = spokeData;
|
|
193
|
-
}),
|
|
194
|
-
...aaveV2Markets.map(async (market) => {
|
|
195
|
-
const marketData = await _getAaveV2MarketsData(client, network, market);
|
|
196
|
-
aaveV2MarketsData[market.value] = marketData;
|
|
197
|
-
}),
|
|
198
|
-
...compoundV2Markets.map(async (market) => {
|
|
199
|
-
const marketData = await _getCompoundV2MarketsData(client, network);
|
|
200
|
-
compoundV2MarketsData[market.value] = marketData;
|
|
201
|
-
}),
|
|
202
|
-
...crvUsdMarkets.map(async (market) => {
|
|
203
|
-
const marketData = await _getCurveUsdGlobalData(client, network, market);
|
|
204
|
-
crvUsdMarketsData[market.value] = marketData;
|
|
205
|
-
}),
|
|
206
|
-
...llamaLendMarkets.map(async (market) => {
|
|
207
|
-
const marketData = await _getLlamaLendGlobalData(client, network, market);
|
|
208
|
-
llamaLendMarketsData[market.value] = marketData;
|
|
209
|
-
}),
|
|
210
|
-
...liquityV2Markets.map(async (market) => {
|
|
211
|
-
const marketData = await _getLiquityV2MarketData(client, network, market);
|
|
212
|
-
liquityV2MarketsData[market.value] = marketData;
|
|
213
|
-
}),
|
|
214
|
-
|
|
215
|
-
// === INDEPENDENT USER DATA (doesn't depend on market data) ===
|
|
216
|
-
...addresses.map(async (address) => {
|
|
195
|
+
// Everything starts at once; a read that needs a market's data waits for marketsData.
|
|
196
|
+
await Promise.all([
|
|
197
|
+
// === POSITIONS THAT DO NOT NEED MARKET DATA ===
|
|
198
|
+
// Maker: the address's vault list, then each vault. A failed list is reported in errors.makerCdps: maker stays {},
|
|
199
|
+
// which would otherwise read as "no vaults".
|
|
200
|
+
...positionsAddresses.map(async (address) => {
|
|
217
201
|
if (!isMainnet) return; // Maker CDPs are only available on mainnet
|
|
218
|
-
|
|
219
|
-
|
|
202
|
+
let cdps: CdpInfo[];
|
|
203
|
+
try {
|
|
204
|
+
cdps = await _getUserCdps(client, network, address);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(`Error fetching Maker CDPs for address ${address}:`, error);
|
|
207
|
+
makerCdpsErrors[address.toLowerCase() as EthAddress] = `Error fetching Maker CDPs for address ${address}`;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
await Promise.all(cdps.map(async (cdpInfo) => {
|
|
211
|
+
try {
|
|
212
|
+
const cdpData = await _getMakerCdpData(client, network, cdpInfo);
|
|
213
|
+
if (cdpData) {
|
|
214
|
+
positions[address.toLowerCase() as EthAddress].maker[cdpInfo.id] = { error: '', data: cdpData };
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error(`Error fetching Maker CDP data for address ${address} with ID ${cdpInfo.id}:`, error);
|
|
218
|
+
positions[address.toLowerCase() as EthAddress].maker[cdpInfo.id] = { error: `Error fetching Maker CDP data for address ${address} with ID ${cdpInfo.id}`, data: null };
|
|
219
|
+
}
|
|
220
|
+
}));
|
|
220
221
|
}),
|
|
221
|
-
...
|
|
222
|
+
...positionsAddresses.map(async (address) => {
|
|
222
223
|
try {
|
|
223
224
|
if (!isFluidSupported) return; // Fluid is not available on Optimism
|
|
224
225
|
const userPositions = (await _getUserPositionsPortfolio(client, network, address));
|
|
@@ -235,71 +236,94 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
235
236
|
};
|
|
236
237
|
}
|
|
237
238
|
}),
|
|
239
|
+
...positionsAddresses.map(async (address) => {
|
|
240
|
+
try {
|
|
241
|
+
if (!isMainnet) return; // Liquity trove info is only available on mainnet
|
|
242
|
+
const troveInfo = await _getLiquityTroveInfo(client, network, address);
|
|
243
|
+
if (new Dec(troveInfo.collateral).gt(0)) positions[address.toLowerCase() as EthAddress].liquity = { error: '', data: troveInfo };
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error(`Error fetching Liquity trove info for address ${address}:`, error);
|
|
246
|
+
positions[address.toLowerCase() as EthAddress].liquity = { error: `Error fetching Liquity trove info for address ${address}`, data: null };
|
|
247
|
+
}
|
|
248
|
+
}),
|
|
238
249
|
|
|
239
|
-
// === STAKING DATA (independent of market data) ===
|
|
240
|
-
...
|
|
250
|
+
// === STAKING DATA (independent of market data; Morpho Earn is with the positions below) ===
|
|
251
|
+
...stakingAddresses.map(async (address) => {
|
|
241
252
|
try {
|
|
242
253
|
if (!isFluidSupported) return;
|
|
243
|
-
stakingPositions[address.toLowerCase()].fluid = await _getAllUserEarnPositionsWithFTokens(client, network, address);
|
|
254
|
+
stakingPositions[address.toLowerCase() as EthAddress].fluid = await _getAllUserEarnPositionsWithFTokens(client, network, address);
|
|
244
255
|
} catch (error) {
|
|
245
256
|
console.error(`Error fetching Fluid lend data for address ${address}:`, error);
|
|
246
|
-
stakingPositions[address.toLowerCase()].fluid = { error: `Error fetching Fluid lend data for address ${address}`, data: null };
|
|
257
|
+
stakingPositions[address.toLowerCase() as EthAddress].fluid = { error: `Error fetching Fluid lend data for address ${address}`, data: null };
|
|
247
258
|
}
|
|
248
259
|
}),
|
|
249
|
-
...
|
|
260
|
+
...stakingAddresses.map(async (address) => {
|
|
250
261
|
try {
|
|
251
262
|
if (!isMainnet) return;
|
|
252
|
-
stakingPositions[address.toLowerCase()].liquity = await getLiquityStakingData(client, network, address);
|
|
263
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquity = await getLiquityStakingData(client, network, address);
|
|
253
264
|
} catch (error) {
|
|
254
265
|
console.error(`Error fetching Liquity staking data for address ${address}:`, error);
|
|
255
|
-
stakingPositions[address.toLowerCase()].liquity = { error: `Error fetching Liquity staking data for address ${address}`, data: null };
|
|
266
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquity = { error: `Error fetching Liquity staking data for address ${address}`, data: null };
|
|
256
267
|
}
|
|
257
268
|
}),
|
|
258
|
-
...
|
|
269
|
+
...stakingAddresses.map(async (address) => {
|
|
259
270
|
try {
|
|
260
271
|
if (!isMainnet) return;
|
|
261
|
-
stakingPositions[address.toLowerCase()].aaveV3 = await getStakeAaveData(client, network, address);
|
|
272
|
+
stakingPositions[address.toLowerCase() as EthAddress].aaveV3 = await getStakeAaveData(client, network, address);
|
|
262
273
|
} catch (error) {
|
|
263
274
|
console.error(`Error fetching Aave V3 staking data for address ${address}:`, error);
|
|
264
|
-
stakingPositions[address.toLowerCase()].aaveV3 = { error: `Error fetching Aave V3 staking data for address ${address}`, data: null };
|
|
275
|
+
stakingPositions[address.toLowerCase() as EthAddress].aaveV3 = { error: `Error fetching Aave V3 staking data for address ${address}`, data: null };
|
|
265
276
|
}
|
|
266
277
|
}),
|
|
267
|
-
...
|
|
278
|
+
...stakingAddresses.map(async (address) => {
|
|
268
279
|
try {
|
|
269
280
|
if (!isMainnet) return;
|
|
270
|
-
stakingPositions[address.toLowerCase()].umbrella = await getUmbrellaData(client, network, address);
|
|
281
|
+
stakingPositions[address.toLowerCase() as EthAddress].umbrella = await getUmbrellaData(client, network, address);
|
|
271
282
|
} catch (error) {
|
|
272
283
|
console.error(`Error fetching Umbrella staking data for address ${address}:`, error);
|
|
273
|
-
stakingPositions[address.toLowerCase()].umbrella = { error: `Error fetching Umbrella staking data for address ${address}`, data: null };
|
|
284
|
+
stakingPositions[address.toLowerCase() as EthAddress].umbrella = { error: `Error fetching Umbrella staking data for address ${address}`, data: null };
|
|
274
285
|
}
|
|
275
286
|
}),
|
|
276
|
-
// Liquity V2
|
|
277
|
-
...
|
|
287
|
+
// Liquity V2 stability pools, then sBOLD/yBOLD, which reads them.
|
|
288
|
+
...stakingAddresses.map(async (address) => {
|
|
289
|
+
await Promise.all(liquityV2MarketsStaking.map(async (market) => {
|
|
290
|
+
try {
|
|
291
|
+
if (!isMainnet) {
|
|
292
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: '', data: null };
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const liquityV2StakingData = await getLiquityV2Staking(client, network, market.value, address);
|
|
296
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: '', data: liquityV2StakingData };
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error(`Error fetching Liquity V2 staking data for address ${address}, market ${market.value}:`, error);
|
|
299
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: `Error fetching Liquity V2 staking data for address ${address}`, data: null };
|
|
300
|
+
}
|
|
301
|
+
}));
|
|
278
302
|
try {
|
|
279
303
|
if (!isMainnet) {
|
|
280
|
-
stakingPositions[address.toLowerCase()].
|
|
304
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data: null };
|
|
281
305
|
return;
|
|
282
306
|
}
|
|
283
|
-
const
|
|
284
|
-
stakingPositions[address.toLowerCase()].
|
|
307
|
+
const data = await getLiquitySAndYBold(client, network, stakingPositions[address.toLowerCase() as EthAddress].liquityV2, address);
|
|
308
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data };
|
|
285
309
|
} catch (error) {
|
|
286
|
-
console.error(`Error fetching
|
|
287
|
-
stakingPositions[address.toLowerCase()].
|
|
310
|
+
console.error(`Error fetching SBold/YBold data for address ${address}:`, error);
|
|
311
|
+
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: `Error fetching sBold/yBold data for address ${address}`, data: null };
|
|
288
312
|
}
|
|
289
|
-
})
|
|
313
|
+
}),
|
|
290
314
|
|
|
291
315
|
// === REWARDS DATA (independent of market data) ===
|
|
292
316
|
// Batch King rewards
|
|
293
317
|
(async () => {
|
|
294
318
|
try {
|
|
295
319
|
if (!isMainnet) {
|
|
296
|
-
for (const address of
|
|
297
|
-
rewardsData[address.toLowerCase()].king = { error: '', data: [] };
|
|
320
|
+
for (const address of rewardsAddresses) {
|
|
321
|
+
rewardsData[address.toLowerCase() as EthAddress].king = { error: '', data: [] };
|
|
298
322
|
}
|
|
299
323
|
return;
|
|
300
324
|
}
|
|
301
|
-
const kingRewards = await getKingRewards(client, network,
|
|
302
|
-
for (const address of
|
|
325
|
+
const kingRewards = await getKingRewards(client, network, rewardsAddresses);
|
|
326
|
+
for (const address of rewardsAddresses) {
|
|
303
327
|
const lowerAddress = address.toLowerCase() as EthAddress;
|
|
304
328
|
rewardsData[lowerAddress].king = {
|
|
305
329
|
error: '',
|
|
@@ -308,7 +332,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
308
332
|
}
|
|
309
333
|
} catch (error) {
|
|
310
334
|
console.error('Error fetching King rewards data in batch:', error);
|
|
311
|
-
for (const address of
|
|
335
|
+
for (const address of rewardsAddresses) {
|
|
312
336
|
rewardsData[address.toLowerCase() as EthAddress].king = {
|
|
313
337
|
error: 'Error fetching King rewards data in batch',
|
|
314
338
|
data: null,
|
|
@@ -320,13 +344,13 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
320
344
|
(async () => {
|
|
321
345
|
try {
|
|
322
346
|
if (!isMainnet) {
|
|
323
|
-
for (const address of
|
|
324
|
-
rewardsData[address.toLowerCase()].uniswap = { error: '', data: [] };
|
|
347
|
+
for (const address of rewardsAddresses) {
|
|
348
|
+
rewardsData[address.toLowerCase() as EthAddress].uniswap = { error: '', data: [] };
|
|
325
349
|
}
|
|
326
350
|
return;
|
|
327
351
|
}
|
|
328
|
-
const uniswapRewards = await getUniswapRewards(client, network,
|
|
329
|
-
for (const address of
|
|
352
|
+
const uniswapRewards = await getUniswapRewards(client, network, rewardsAddresses);
|
|
353
|
+
for (const address of rewardsAddresses) {
|
|
330
354
|
const lowerAddress = address.toLowerCase() as EthAddress;
|
|
331
355
|
rewardsData[lowerAddress].uniswap = {
|
|
332
356
|
error: '',
|
|
@@ -335,7 +359,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
335
359
|
}
|
|
336
360
|
} catch (error) {
|
|
337
361
|
console.error('Error fetching Uniswap rewards data in batch:', error);
|
|
338
|
-
for (const address of
|
|
362
|
+
for (const address of rewardsAddresses) {
|
|
339
363
|
rewardsData[address.toLowerCase() as EthAddress].uniswap = {
|
|
340
364
|
error: 'Error fetching Uniswap rewards data in batch',
|
|
341
365
|
data: null,
|
|
@@ -343,10 +367,10 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
343
367
|
}
|
|
344
368
|
}
|
|
345
369
|
})(),
|
|
346
|
-
...
|
|
370
|
+
...sparkLending.markets(network).map((market) => rewardsAddresses.map(async address => {
|
|
347
371
|
try {
|
|
348
372
|
if (!isMainnet) {
|
|
349
|
-
rewardsData[address.toLowerCase()].spark[market.value] = { error: '', data: [] };
|
|
373
|
+
rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: '', data: [] };
|
|
350
374
|
return;
|
|
351
375
|
}
|
|
352
376
|
const sparkData = await fetchSparkRewards(client, network, address, market.providerAddress);
|
|
@@ -356,7 +380,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
356
380
|
rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: `Error fetching Spark rewards data for address ${address}`, data: null };
|
|
357
381
|
}
|
|
358
382
|
})).flat(),
|
|
359
|
-
...
|
|
383
|
+
...rewardsAddresses.map(async (address) => {
|
|
360
384
|
try {
|
|
361
385
|
const merklData = await getMerklUnclaimedRewards(address, network);
|
|
362
386
|
rewardsData[address.toLowerCase() as EthAddress].merkl = { error: '', data: merklData };
|
|
@@ -365,27 +389,28 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
365
389
|
rewardsData[address.toLowerCase() as EthAddress].merkl = { error: `Error fetching Merkl rewards data for address ${address}`, data: null };
|
|
366
390
|
}
|
|
367
391
|
}),
|
|
368
|
-
...
|
|
392
|
+
...aaveV3Lending.markets(network).map(market => rewardsAddresses.map(async (address) => {
|
|
369
393
|
try {
|
|
370
394
|
const aaveData = await getUnclaimedRewardsForAllMarkets(client, network, address, market.providerAddress);
|
|
371
395
|
rewardsData[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: '', data: aaveData };
|
|
372
396
|
} catch (error) {
|
|
373
397
|
console.error(`Error fetching Aave V3 Merit rewards data for address ${address}:`, error);
|
|
374
|
-
|
|
398
|
+
// This market only: the other markets keep their rewards.
|
|
399
|
+
rewardsData[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: `Error fetching Aave V3 rewards data for address ${address} on market ${market.value}`, data: null };
|
|
375
400
|
}
|
|
376
401
|
})).flat(),
|
|
377
402
|
// Batch Spark Airdrop rewards
|
|
378
403
|
(async () => {
|
|
379
404
|
try {
|
|
380
405
|
if (!isMainnet) {
|
|
381
|
-
for (const address of
|
|
382
|
-
rewardsData[address.toLowerCase()].spk = { error: '', data: [] };
|
|
406
|
+
for (const address of rewardsAddresses) {
|
|
407
|
+
rewardsData[address.toLowerCase() as EthAddress].spk = { error: '', data: [] };
|
|
383
408
|
}
|
|
384
409
|
return;
|
|
385
410
|
}
|
|
386
411
|
|
|
387
|
-
const sparkAirdropRewards = await fetchSparkAirdropRewards(client, network,
|
|
388
|
-
for (const address of
|
|
412
|
+
const sparkAirdropRewards = await fetchSparkAirdropRewards(client, network, rewardsAddresses);
|
|
413
|
+
for (const address of rewardsAddresses) {
|
|
389
414
|
const lowerAddress = address.toLowerCase() as EthAddress;
|
|
390
415
|
rewardsData[lowerAddress].spk = {
|
|
391
416
|
error: '',
|
|
@@ -394,7 +419,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
394
419
|
}
|
|
395
420
|
} catch (error) {
|
|
396
421
|
console.error('Error fetching Spark Airdrop rewards data in batch:', error);
|
|
397
|
-
for (const address of
|
|
422
|
+
for (const address of rewardsAddresses) {
|
|
398
423
|
rewardsData[address.toLowerCase() as EthAddress].spk = {
|
|
399
424
|
error: 'Error fetching Spark Airdrop rewards data in batch',
|
|
400
425
|
data: null,
|
|
@@ -408,8 +433,8 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
408
433
|
return;
|
|
409
434
|
}
|
|
410
435
|
|
|
411
|
-
const ethenaAirdropRewards = await fetchEthenaAirdropRewards(
|
|
412
|
-
for (const address of
|
|
436
|
+
const ethenaAirdropRewards = await fetchEthenaAirdropRewards(rewardsAddresses);
|
|
437
|
+
for (const address of rewardsAddresses) {
|
|
413
438
|
const lowerAddress = address.toLowerCase() as EthAddress;
|
|
414
439
|
rewardsData[lowerAddress].ethena = {
|
|
415
440
|
error: '',
|
|
@@ -418,7 +443,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
418
443
|
}
|
|
419
444
|
} catch (error) {
|
|
420
445
|
console.error('Error fetching Ethena Airdrop rewards data:', error);
|
|
421
|
-
for (const address of
|
|
446
|
+
for (const address of rewardsAddresses) {
|
|
422
447
|
rewardsData[address.toLowerCase() as EthAddress].ethena = {
|
|
423
448
|
error: 'Error fetching Ethena Airdrop rewards data in batch',
|
|
424
449
|
data: null,
|
|
@@ -426,162 +451,63 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
426
451
|
}
|
|
427
452
|
}
|
|
428
453
|
})(),
|
|
429
|
-
]);
|
|
430
454
|
|
|
431
|
-
|
|
432
|
-
...
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
})).flat(),
|
|
441
|
-
...aaveV4Spokes.map((spoke) => allAddresses.map(async (address) => {
|
|
442
|
-
try {
|
|
443
|
-
const accData = await _getAaveV4AccountData(client, network, aaveV4SpokesData[spoke.value], address);
|
|
444
|
-
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].aaveV4[spoke.value] = { error: '', data: accData };
|
|
445
|
-
} catch (error) {
|
|
446
|
-
console.error(`Error fetching AaveV4 account data for address ${address} on spoke ${spoke.value}:`, error);
|
|
447
|
-
positions[address.toLowerCase() as EthAddress].aaveV4[spoke.value] = { error: `Error fetching AaveV4 account data for address ${address} on spoke ${spoke.value}`, data: null };
|
|
448
|
-
}
|
|
449
|
-
})).flat(),
|
|
450
|
-
...morphoMarkets.map((market) => addresses.map(async (address) => {
|
|
451
|
-
try {
|
|
452
|
-
const [accDataPromise, earnDataPromise] = await Promise.allSettled([
|
|
453
|
-
_getMorphoBlueAccountData(client, network, address, market, morphoMarketsData[market.value]),
|
|
454
|
-
getMorphoEarn(client, network, address, market, morphoMarketsData[market.value]),
|
|
455
|
-
]);
|
|
456
|
-
if (accDataPromise.status === 'rejected') {
|
|
457
|
-
console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, accDataPromise.reason);
|
|
458
|
-
positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
|
|
459
|
-
}
|
|
460
|
-
if (earnDataPromise.status === 'rejected') {
|
|
461
|
-
console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, earnDataPromise.reason);
|
|
462
|
-
positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
|
|
463
|
-
}
|
|
464
|
-
if (accDataPromise.status !== 'rejected') {
|
|
465
|
-
const accData = accDataPromise.value;
|
|
466
|
-
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: '', data: accData };
|
|
467
|
-
}
|
|
468
|
-
if (earnDataPromise.status !== 'rejected') {
|
|
469
|
-
const earnData = earnDataPromise.value;
|
|
470
|
-
if (earnData && new Dec(earnData.amount).gt(0)) {
|
|
471
|
-
stakingPositions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = {
|
|
472
|
-
error: '',
|
|
473
|
-
data: earnData,
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
} catch (error) {
|
|
478
|
-
console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, error);
|
|
479
|
-
positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
|
|
480
|
-
}
|
|
481
|
-
})).flat(),
|
|
482
|
-
...morphoMidnightMarkets.map((market) => addresses.map(async (address) => {
|
|
483
|
-
try {
|
|
484
|
-
// Markets are created lazily on the first position, so an uncreated one can hold no position.
|
|
485
|
-
if (!morphoMidnightMarketsData[market.value]?.isCreated) return;
|
|
486
|
-
const accData = await _getMorphoMidnightAccountData(client, network, address, market, morphoMidnightMarketsData[market.value]);
|
|
487
|
-
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: '', data: accData };
|
|
488
|
-
} catch (error) {
|
|
489
|
-
console.error(`Error fetching MorphoMidnight account data for address ${address} on market ${market.value}:`, error);
|
|
490
|
-
positions[address.toLowerCase() as EthAddress].morphoMidnight[market.value] = { error: `Error fetching MorphoMidnight account data for address ${address} on market ${market.value}`, data: null };
|
|
491
|
-
}
|
|
492
|
-
})).flat(),
|
|
493
|
-
...compoundV3Markets.map((market) => addresses.map(async (address) => {
|
|
494
|
-
try {
|
|
495
|
-
const accData = await _getCompoundV3AccountData(client, network, address, ZERO_ADDRESS, { selectedMarket: market, assetsData: compoundV3MarketsData[market.value].assetsData });
|
|
496
|
-
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].compoundV3[market.value] = { error: '', data: accData };
|
|
497
|
-
} catch (error) {
|
|
498
|
-
console.error(`Error fetching CompoundV3 account data for address ${address} on market ${market.value}:`, error);
|
|
499
|
-
positions[address.toLowerCase() as EthAddress].compoundV3[market.value] = { error: `Error fetching CompoundV3 account data for address ${address} on market ${market.value}`, data: null };
|
|
500
|
-
}
|
|
501
|
-
})).flat(),
|
|
502
|
-
...sparkMarkets.map((market) => allAddresses.map(async (address) => {
|
|
503
|
-
try {
|
|
504
|
-
const accData = await _getSparkAccountData(client, network, address, { selectedMarket: market, assetsData: sparkMarketsData[market.value].assetsData, eModeCategoriesData: sparkMarketsData[market.value].eModeCategoriesData });
|
|
505
|
-
if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].spark[market.value] = { error: '', data: accData };
|
|
506
|
-
} catch (error) {
|
|
507
|
-
console.error(`Error fetching Spark account data for address ${address} on market ${market.value}:`, error);
|
|
508
|
-
positions[address.toLowerCase() as EthAddress].spark[market.value] = { error: `Error fetching Spark account data for address ${address} on market ${market.value}`, data: null };
|
|
509
|
-
}
|
|
510
|
-
})).flat(),
|
|
511
|
-
...addresses.map((address) => (makerCdps[address.toLowerCase() as EthAddress] || []).map(async (cdpInfo) => {
|
|
512
|
-
try {
|
|
513
|
-
const cdpData = await _getMakerCdpData(client, network, cdpInfo);
|
|
514
|
-
if (cdpData) {
|
|
515
|
-
positions[address.toLowerCase() as EthAddress].maker[cdpInfo.id] = { error: '', data: cdpData };
|
|
455
|
+
// === POSITIONS ON MARKETS (each waits for its market's data) ===
|
|
456
|
+
...LENDING_PROTOCOLS.flatMap((protocol) => protocol.readPositions({ client, network }, positionsAddresses, marketsData, positions)),
|
|
457
|
+
// Morpho Blue Earn: a supply without a borrow, kept as staking, read on the same markets as Morpho Blue's positions.
|
|
458
|
+
...morphoBlueLending.markets(network).flatMap((market) => stakingAddresses.map(async (address) => {
|
|
459
|
+
try {
|
|
460
|
+
const marketData = requireMarket((await marketsData).morphoMarketsData, market.value);
|
|
461
|
+
const earnData = await getMorphoEarn(client, network, address, market, marketData);
|
|
462
|
+
if (earnData && new Dec(earnData.amount).gt(0)) {
|
|
463
|
+
stakingPositions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: '', data: earnData };
|
|
516
464
|
}
|
|
517
465
|
} catch (error) {
|
|
518
|
-
console.error(`Error fetching
|
|
519
|
-
|
|
520
|
-
}
|
|
521
|
-
}))
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const accData = await _getLlamaLendUserData(client, network, address, market, llamaLendMarketsData[market.value]);
|
|
564
|
-
if (new Dec(accData.suppliedUsd).gt(0) || new Dec(accData.borrowedUsd).gt(0)) {
|
|
565
|
-
positions[address.toLowerCase() as EthAddress].llamaLend[market.value] = { error: '', data: { ...accData, borrowRate: llamaLendMarketsData[market.value].borrowRate } };
|
|
566
|
-
}
|
|
567
|
-
} catch (error) {
|
|
568
|
-
console.error(`Error fetching LlamaLend account data for address ${address} on market ${market.value}:`, error);
|
|
569
|
-
positions[address.toLowerCase() as EthAddress].llamaLend[market.value] = { error: `Error fetching LlamaLend account data for address ${address} on market ${market.value}`, data: null };
|
|
570
|
-
}
|
|
571
|
-
})).flat(),
|
|
572
|
-
// liquity sBold/yBold and staking options
|
|
573
|
-
...addresses.map(async (address) => {
|
|
574
|
-
try {
|
|
575
|
-
if (!isMainnet) {
|
|
576
|
-
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data: null };
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
const data = await getLiquitySAndYBold(client, network, stakingPositions[address.toLowerCase()].liquityV2, address);
|
|
580
|
-
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data };
|
|
581
|
-
} catch (error) {
|
|
582
|
-
console.error(`Error fetching SBold/YBold data for address ${address}:`, error);
|
|
583
|
-
stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: `Error fetching sBold/yBold data for address ${address}`, data: null };
|
|
584
|
-
}
|
|
466
|
+
console.error(`Error fetching MorphoBlue earn data for address ${address} on market ${market.value}:`, error);
|
|
467
|
+
stakingPositions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue earn data for address ${address} on market ${market.value}`, data: null };
|
|
468
|
+
}
|
|
469
|
+
})),
|
|
470
|
+
// Liquity V2 troves: one market's trove ids for every address at once, then each trove's data. A failure is the
|
|
471
|
+
// market's error; the troves that did load stay in its data.
|
|
472
|
+
...(liquityV2Addresses.length ? liquityV2Markets : []).map(async (market) => {
|
|
473
|
+
const setEntry = (address: EthAddress, entry: PortfolioLiquityV2Troves) => {
|
|
474
|
+
positions[address.toLowerCase() as EthAddress].liquityV2![market.value] = entry;
|
|
475
|
+
};
|
|
476
|
+
const failAll = (error: string) => liquityV2Addresses.forEach((address) => setEntry(address, { error, data: {} }));
|
|
477
|
+
|
|
478
|
+
const allMarketsData = (await marketsData).liquityV2MarketsData;
|
|
479
|
+
const marketData = allMarketsData[market.value];
|
|
480
|
+
if (!marketData) {
|
|
481
|
+
failAll(`Error fetching Liquity V2 market data for market ${market.value}`);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
let troveIds: Record<EthAddress, string[]>;
|
|
485
|
+
try {
|
|
486
|
+
troveIds = await _getLiquityV2UsersTroveIds(client, network, market, marketData.marketData.troveNFTAddress, isSim, liquityV2Addresses);
|
|
487
|
+
} catch (error) {
|
|
488
|
+
console.error(`Error fetching Liquity V2 trove ids on market ${market.value}:`, error);
|
|
489
|
+
failAll(`Error fetching Liquity V2 troves on market ${market.value}`);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
await Promise.all(liquityV2Addresses.map(async (address) => {
|
|
493
|
+
const entry: PortfolioLiquityV2Troves = { error: '', data: {} };
|
|
494
|
+
const failed: string[] = [];
|
|
495
|
+
await Promise.all((troveIds[address] ?? []).map(async (troveId) => {
|
|
496
|
+
try {
|
|
497
|
+
entry.data[troveId] = await _getLiquityV2TroveData(client, network, {
|
|
498
|
+
selectedMarket: market,
|
|
499
|
+
assetsData: marketData.assetsData,
|
|
500
|
+
troveId,
|
|
501
|
+
allMarketsData: allMarketsData as Record<LiquityV2Versions, LiquityV2MarketData>,
|
|
502
|
+
});
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.error(`Error fetching Liquity V2 trove ${troveId} for address ${address} on market ${market.value}:`, error);
|
|
505
|
+
failed.push(troveId);
|
|
506
|
+
}
|
|
507
|
+
}));
|
|
508
|
+
if (failed.length) entry.error = `Error fetching Liquity V2 troves ${failed.join(', ')} for address ${address} on market ${market.value}`;
|
|
509
|
+
setEntry(address, entry);
|
|
510
|
+
}));
|
|
585
511
|
}),
|
|
586
512
|
]);
|
|
587
513
|
|
|
@@ -589,14 +515,63 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
|
|
|
589
515
|
positions,
|
|
590
516
|
stakingPositions,
|
|
591
517
|
rewardsData,
|
|
518
|
+
errors: Object.keys(makerCdpsErrors).length ? { makerCdps: makerCdpsErrors } : {},
|
|
519
|
+
};
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Every market the portfolio reads on a network — the markets half of getPortfolioData, for a caller that keeps
|
|
524
|
+
* markets apart from what it reads per address (a cache shared by many addresses). A market that fails is left out
|
|
525
|
+
* of `markets` and named in `errors`.
|
|
526
|
+
*/
|
|
527
|
+
export async function getPortfolioMarketsData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, isSim = false): Promise<PortfolioMarketsResult> {
|
|
528
|
+
const { client, defaultClient } = getPortfolioClients(provider, defaultProvider, network, isSim);
|
|
529
|
+
return _getPortfolioMarketsData(client, defaultClient, network);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* What the portfolio reads per address — the other half of getPortfolioData — against `markets` from
|
|
534
|
+
* getPortfolioMarketsData. `markets` can be a promise: reads that do not need a market (Maker, Fluid, Liquity, staking,
|
|
535
|
+
* rewards) start at once, and each read on a market waits for it. A position on a market missing from `markets` is an
|
|
536
|
+
* entry with an error.
|
|
537
|
+
*/
|
|
538
|
+
export async function getPortfolioUserData(
|
|
539
|
+
provider: EthereumProvider,
|
|
540
|
+
network: NetworkNumber,
|
|
541
|
+
addresses: EthAddress[],
|
|
542
|
+
markets: PortfolioMarketsData | Promise<PortfolioMarketsData>,
|
|
543
|
+
isSim = false,
|
|
544
|
+
options: PortfolioDataOptions = {},
|
|
545
|
+
): Promise<PortfolioUserData> {
|
|
546
|
+
const { client } = getPortfolioClients(provider, provider, network, isSim);
|
|
547
|
+
return _getPortfolioUserData(client, network, addresses, Promise.resolve(markets), isSim, options);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export async function getPortfolioData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, addresses: EthAddress[], isSim = false, options: PortfolioDataOptions = {}): Promise<PortfolioData> {
|
|
551
|
+
// One pair of clients for both halves, so their calls batch together.
|
|
552
|
+
const { client, defaultClient } = getPortfolioClients(provider, defaultProvider, network, isSim);
|
|
553
|
+
const marketsResult = _getPortfolioMarketsData(client, defaultClient, network);
|
|
554
|
+
const [{ markets, errors: marketsErrors }, userData] = await Promise.all([
|
|
555
|
+
marketsResult,
|
|
556
|
+
_getPortfolioUserData(client, network, addresses, marketsResult.then((result) => result.markets), isSim, options),
|
|
557
|
+
]);
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
positions: userData.positions,
|
|
561
|
+
stakingPositions: userData.stakingPositions,
|
|
562
|
+
rewardsData: userData.rewardsData,
|
|
592
563
|
markets,
|
|
564
|
+
errors: {
|
|
565
|
+
...(Object.keys(marketsErrors).length ? { markets: marketsErrors } : {}),
|
|
566
|
+
...userData.errors,
|
|
567
|
+
},
|
|
593
568
|
};
|
|
594
569
|
}
|
|
595
570
|
|
|
596
571
|
|
|
597
572
|
export async function getShifterPortfolioData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, addresses: EthAddress[], isSim = false): Promise<{
|
|
598
573
|
positions: PortfolioPositionsData;
|
|
599
|
-
markets:
|
|
574
|
+
markets: ShifterPortfolioMarketsData;
|
|
600
575
|
}> {
|
|
601
576
|
const isMainnet = network === NetworkNumber.Eth;
|
|
602
577
|
const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
|
|
@@ -616,7 +591,7 @@ export async function getShifterPortfolioData(provider: EthereumProvider, networ
|
|
|
616
591
|
const client = getViemProvider(provider, ...args);
|
|
617
592
|
const defaultClient = getViemProvider(defaultProvider, ...args);
|
|
618
593
|
|
|
619
|
-
const markets:
|
|
594
|
+
const markets: ShifterPortfolioMarketsData = {
|
|
620
595
|
morphoMarketsData: {},
|
|
621
596
|
morphoMidnightMarketsData: {}, // Morpho Midnight is not fetched for the shifter; key kept so the payload shape is stable
|
|
622
597
|
compoundV3MarketsData: {},
|