@defisaver/positions-sdk 2.1.169-portfolio-api-dev → 2.1.169

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 (101) hide show
  1. package/cjs/aaveV3/index.d.ts +12 -2
  2. package/cjs/aaveV3/sgho.d.ts +16 -2
  3. package/cjs/aaveV4/lend.d.ts +43 -2
  4. package/cjs/claiming/aaveV3.d.ts +2 -2
  5. package/cjs/claiming/ethena.d.ts +1 -2
  6. package/cjs/claiming/king.d.ts +1 -2
  7. package/cjs/claiming/spark.d.ts +2 -2
  8. package/cjs/fluid/index.d.ts +49 -5
  9. package/cjs/liquity/index.d.ts +12 -2
  10. package/cjs/liquityV2/index.d.ts +23 -8
  11. package/cjs/liquityV2/index.js +19 -37
  12. package/cjs/markets/morphoBlue/index.d.ts +2 -0
  13. package/cjs/markets/morphoBlue/index.js +18 -1
  14. package/cjs/portfolio/index.d.ts +8 -16
  15. package/cjs/portfolio/index.js +520 -458
  16. package/cjs/savings/index.d.ts +2 -8
  17. package/cjs/savings/index.js +4 -33
  18. package/cjs/types/aave.d.ts +2 -98
  19. package/cjs/types/aaveV4.d.ts +0 -44
  20. package/cjs/types/compound.d.ts +2 -26
  21. package/cjs/types/curveUsd.d.ts +1 -9
  22. package/cjs/types/fluid.d.ts +0 -13
  23. package/cjs/types/index.d.ts +0 -1
  24. package/cjs/types/index.js +0 -1
  25. package/cjs/types/liquity.d.ts +0 -12
  26. package/cjs/types/liquityV2.d.ts +0 -24
  27. package/cjs/types/llamaLend.d.ts +1 -12
  28. package/cjs/types/morphoBlue.d.ts +1 -2
  29. package/cjs/types/morphoBlue.js +1 -0
  30. package/cjs/types/portfolio.d.ts +5 -125
  31. package/cjs/types/savings/index.d.ts +1 -9
  32. package/cjs/types/spark.d.ts +1 -21
  33. package/cjs/umbrella/index.d.ts +3 -2
  34. package/esm/aaveV3/index.d.ts +12 -2
  35. package/esm/aaveV3/sgho.d.ts +16 -2
  36. package/esm/aaveV4/lend.d.ts +43 -2
  37. package/esm/claiming/aaveV3.d.ts +2 -2
  38. package/esm/claiming/aaveV3.js +1 -1
  39. package/esm/claiming/ethena.d.ts +1 -2
  40. package/esm/claiming/king.d.ts +1 -2
  41. package/esm/claiming/spark.d.ts +2 -2
  42. package/esm/claiming/spark.js +1 -1
  43. package/esm/fluid/index.d.ts +49 -5
  44. package/esm/liquity/index.d.ts +12 -2
  45. package/esm/liquityV2/index.d.ts +23 -8
  46. package/esm/liquityV2/index.js +18 -35
  47. package/esm/markets/morphoBlue/index.d.ts +2 -0
  48. package/esm/markets/morphoBlue/index.js +16 -0
  49. package/esm/portfolio/index.d.ts +8 -16
  50. package/esm/portfolio/index.js +522 -458
  51. package/esm/savings/index.d.ts +2 -8
  52. package/esm/savings/index.js +3 -31
  53. package/esm/types/aave.d.ts +2 -98
  54. package/esm/types/aaveV4.d.ts +0 -44
  55. package/esm/types/compound.d.ts +2 -26
  56. package/esm/types/curveUsd.d.ts +1 -9
  57. package/esm/types/fluid.d.ts +0 -13
  58. package/esm/types/index.d.ts +0 -1
  59. package/esm/types/index.js +0 -1
  60. package/esm/types/liquity.d.ts +0 -12
  61. package/esm/types/liquityV2.d.ts +0 -24
  62. package/esm/types/llamaLend.d.ts +1 -12
  63. package/esm/types/morphoBlue.d.ts +1 -2
  64. package/esm/types/morphoBlue.js +1 -0
  65. package/esm/types/portfolio.d.ts +5 -125
  66. package/esm/types/savings/index.d.ts +1 -9
  67. package/esm/types/spark.d.ts +1 -21
  68. package/esm/umbrella/index.d.ts +3 -2
  69. package/package.json +2 -2
  70. package/src/aaveV3/index.ts +1 -2
  71. package/src/aaveV3/sgho.ts +17 -2
  72. package/src/aaveV4/lend.ts +48 -2
  73. package/src/claiming/aaveV3.ts +2 -4
  74. package/src/claiming/ethena.ts +5 -5
  75. package/src/claiming/king.ts +3 -3
  76. package/src/claiming/spark.ts +4 -6
  77. package/src/fluid/index.ts +3 -4
  78. package/src/liquity/index.ts +2 -2
  79. package/src/liquityV2/index.ts +22 -48
  80. package/src/markets/morphoBlue/index.ts +16 -0
  81. package/src/portfolio/index.ts +331 -306
  82. package/src/savings/index.ts +4 -41
  83. package/src/types/aave.ts +1 -104
  84. package/src/types/aaveV4.ts +0 -49
  85. package/src/types/compound.ts +2 -38
  86. package/src/types/curveUsd.ts +1 -17
  87. package/src/types/fluid.ts +0 -14
  88. package/src/types/index.ts +0 -1
  89. package/src/types/liquity.ts +1 -13
  90. package/src/types/liquityV2.ts +1 -26
  91. package/src/types/llamaLend.ts +1 -19
  92. package/src/types/morphoBlue.ts +1 -3
  93. package/src/types/portfolio.ts +5 -151
  94. package/src/types/savings/index.ts +1 -12
  95. package/src/types/spark.ts +1 -25
  96. package/src/umbrella/index.ts +1 -2
  97. package/cjs/portfolio/lendingProtocols.d.ts +0 -184
  98. package/cjs/portfolio/lendingProtocols.js +0 -196
  99. package/esm/portfolio/lendingProtocols.d.ts +0 -184
  100. package/esm/portfolio/lendingProtocols.js +0 -189
  101. package/src/portfolio/lendingProtocols.ts +0 -281
@@ -9,158 +9,137 @@ import {
9
9
  LlamaLendMarkets,
10
10
  MakerActiveIlks,
11
11
  MorphoBlueMarkets,
12
+ MorphoMidnightMarkets,
12
13
  SparkMarkets,
13
14
  } from '../markets';
14
15
  import { _getMorphoBlueAccountData, _getMorphoBluePortfolioMarketData, getMorphoEarn } from '../morphoBlue';
16
+ import { _getMorphoMidnightAccountData, _getMorphoMidnightMarketData } from '../morphoMidnight';
15
17
  import {
18
+ AaveV2MarketData,
19
+ AaveV3MarketData,
20
+ AaveV4SpokeData,
16
21
  AaveVersions,
17
22
  CdpInfo,
23
+ CompoundV2MarketsData,
24
+ CompoundV3MarketsData,
18
25
  CompoundVersions,
26
+ CrvUSDGlobalMarketData,
19
27
  LiquityV2MarketData,
20
- LiquityV2Versions,
21
- PortfolioData,
22
- PortfolioDataOptions,
23
- PortfolioLiquityV2Troves,
28
+ LlamaLendGlobalMarketData,
29
+ MorphoBlueMarketInfo,
30
+ MorphoMidnightMarketInfo,
24
31
  PortfolioMarketsData,
25
- PortfolioMarketsErrors,
26
- PortfolioMarketsResult,
27
32
  PortfolioPositionsData,
28
- PortfolioPositionsDataForAddress,
29
- PortfolioRewardsData,
30
- PortfolioStakingPositionsData,
31
- PortfolioUserData,
32
- ShifterPortfolioMarketsData,
33
+ SparkMarketsData,
33
34
  } from '../types';
34
35
  import { _getCompoundV3AccountData, _getCompoundV3MarketsData } from '../compoundV3';
35
36
  import { _getSparkAccountData, _getSparkMarketsData } from '../spark';
36
37
  import { _getCurveUsdGlobalData, _getCurveUsdUserData } from '../curveUsd';
37
38
  import { _getLlamaLendGlobalData, _getLlamaLendUserData } from '../llamaLend';
38
39
  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 {
47
- _getLiquityV2MarketData, _getLiquityV2TroveData, _getLiquityV2UsersTroveIds, getLiquitySAndYBold, getLiquityV2Staking,
48
- } from '../liquityV2';
46
+ import { _getLiquityV2MarketData, getLiquitySAndYBold, getLiquityV2Staking } from '../liquityV2';
49
47
  import { _getAllFluidMarketDataPortfolio, _getAllUserEarnPositionsWithFTokens, _getUserPositionsPortfolio } from '../fluid';
50
48
  import { getUmbrellaData } from '../umbrella';
51
49
  import { getMerklUnclaimedRewards, getUnclaimedRewardsForAllMarkets } from '../claiming/aaveV3';
52
50
  import { fetchSparkAirdropRewards, fetchSparkRewards } from '../claiming/spark';
53
51
  import { getKingRewards } from '../claiming/king';
54
52
  import { fetchEthenaAirdropRewards } from '../claiming/ethena';
53
+ import { _getAaveV4AccountData, _getAaveV4SpokeData } from '../aaveV4';
55
54
  import { getUniswapRewards } from '../claiming/uniswap';
56
- import {
57
- aaveV3Lending, LENDING_PROTOCOLS, LendingKey, morphoBlueLending, requireMarket, sparkLending,
58
- } from './lendingProtocols';
59
55
 
60
- type PortfolioClient = ReturnType<typeof getViemProvider>;
56
+ export async function getPortfolioData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, addresses: EthAddress[], isSim = false): Promise<{
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));
61
77
 
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
- });
67
78
 
68
- const getPortfolioClients = (provider: EthereumProvider, defaultProvider: EthereumProvider, network: NetworkNumber, isSim: boolean) => {
69
79
  // batchSize is viem's cap on a batch's raw subcall calldata (bytes); the JSON-RPC body ends up
70
80
  // ~4x larger (hex + aggregate3 ABI + JSON overhead) and RPC providers reject bodies over
71
81
  // ~2.5MB with HTTP 413, so keep this small enough that no single body gets near that.
72
82
  // 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
73
83
  // (e.g. adding new markets may result in body size going over alchemy body size limit)
74
84
  const args: [NetworkNumber, any?] = [network, { batch: { multicall: { batchSize: isSim ? 2_000 : 250_000 } } }];
75
- return {
76
- client: getViemProvider(provider, ...args),
77
- defaultClient: getViemProvider(defaultProvider, ...args),
78
- };
79
- };
80
-
81
- const _getPortfolioMarketsData = async (client: PortfolioClient, defaultClient: PortfolioClient, network: NetworkNumber): Promise<PortfolioMarketsResult> => {
82
- const { liquityV2Markets } = getPortfolioMarkets(network);
85
+ const client = getViemProvider(provider, ...args);
86
+ const defaultClient = getViemProvider(defaultProvider, ...args);
83
87
 
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: {},
88
+ const morphoMarketsData: Record<string, MorphoBlueMarketInfo> = {};
89
+ const morphoMidnightMarketsData: Record<string, MorphoMidnightMarketInfo> = {};
90
+ const compoundV3MarketsData: Record<string, CompoundV3MarketsData> = {};
91
+ const sparkMarketsData: Record<string, SparkMarketsData> = {};
92
+ const aaveV3MarketsData: Record<string, AaveV3MarketData> = {};
93
+ const makerCdps: Record<string, CdpInfo[]> = {};
94
+ const aaveV2MarketsData: Record<string, AaveV2MarketData> = {};
95
+ const compoundV2MarketsData: Record<string, CompoundV2MarketsData> = {};
96
+ const crvUsdMarketsData: Record<string, CrvUSDGlobalMarketData> = {};
97
+ const llamaLendMarketsData: Record<string, LlamaLendGlobalMarketData> = {};
98
+ const liquityV2MarketsData: Record<string, LiquityV2MarketData> = {};
99
+ const aaveV4SpokesData: Record<string, AaveV4SpokeData> = {};
100
+
101
+ const markets = {
102
+ morphoMarketsData,
103
+ morphoMidnightMarketsData,
104
+ compoundV3MarketsData,
105
+ sparkMarketsData,
106
+ aaveV3MarketsData,
107
+ aaveV2MarketsData,
108
+ compoundV2MarketsData,
109
+ crvUsdMarketsData,
110
+ llamaLendMarketsData,
111
+ liquityV2MarketsData,
112
+ aaveV4SpokesData,
96
113
  };
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);
142
114
 
143
115
  const positions: PortfolioPositionsData = {};
144
- const stakingPositions: PortfolioStakingPositionsData = {};
145
- const rewardsData: PortfolioRewardsData = {};
146
- const makerCdpsErrors: Record<EthAddress, string> = {};
116
+ const stakingPositions: any = {};
117
+ const rewardsData: any = {};
118
+ const allAddresses = [...addresses];
147
119
 
148
- for (const address of positionsAddresses) {
120
+ for (const address of allAddresses) {
149
121
  positions[address.toLowerCase() as EthAddress] = {
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>,
122
+ aaveV3: {},
123
+ aaveV4: {},
124
+ morphoBlue: {},
125
+ morphoMidnight: {},
126
+ compoundV3: {},
127
+ spark: {},
152
128
  maker: {},
129
+ aaveV2: {},
130
+ compoundV2: {},
153
131
  liquity: {},
132
+ crvUsd: {},
133
+ llamaLend: {},
154
134
  fluid: {
155
135
  error: '',
156
136
  data: {},
157
137
  },
158
- ...(includeLiquityV2 ? { liquityV2: {} } : {}),
159
138
  };
160
139
  }
161
140
 
162
141
  // TODO: check default values, probably needed when fetching portfolio on unsupported networks
163
- for (const address of stakingAddresses) {
142
+ for (const address of addresses) {
164
143
  stakingPositions[address.toLowerCase() as EthAddress] = {
165
144
  aaveV3: {},
166
145
  morphoBlue: {},
@@ -174,52 +153,72 @@ const _getPortfolioUserData = async (
174
153
  error: '',
175
154
  data: {},
176
155
  },
177
- // Set by the sBOLD/yBOLD fetch below, whether it succeeds or fails.
178
- liquityV2SBoldYBold: { error: '', data: null },
179
156
  };
180
- }
181
157
 
182
- for (const address of rewardsAddresses) {
183
- // merkl, spk, king and uniswap are set by their fetches below, whether they succeed or fail.
184
158
  rewardsData[address.toLowerCase() as EthAddress] = {
185
- merkl: { error: '', data: null },
159
+ merkl: {},
186
160
  aaveV3: {},
187
161
  spark: {},
188
- spk: { error: '', data: null },
189
- king: { error: '', data: null },
162
+ spk: {},
163
+ king: {},
190
164
  ethena: {},
191
- uniswap: { error: '', data: null },
192
165
  };
193
166
  }
194
167
 
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) => {
168
+ await Promise.allSettled([
169
+ // === MARKET DATA (needs to be fetched first) ===
170
+ ...morphoMarkets.map(async (market) => {
171
+ const marketData = await _getMorphoBluePortfolioMarketData(client, network, market);
172
+ morphoMarketsData[market.value] = marketData;
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) => {
201
217
  if (!isMainnet) return; // Maker CDPs are only available on mainnet
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
- }));
218
+ const makerCdp = await _getUserCdps(client, network, address);
219
+ makerCdps[address.toLowerCase() as EthAddress] = makerCdp;
221
220
  }),
222
- ...positionsAddresses.map(async (address) => {
221
+ ...addresses.map(async (address) => {
223
222
  try {
224
223
  if (!isFluidSupported) return; // Fluid is not available on Optimism
225
224
  const userPositions = (await _getUserPositionsPortfolio(client, network, address));
@@ -236,94 +235,71 @@ const _getPortfolioUserData = async (
236
235
  };
237
236
  }
238
237
  }),
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
- }),
249
238
 
250
- // === STAKING DATA (independent of market data; Morpho Earn is with the positions below) ===
251
- ...stakingAddresses.map(async (address) => {
239
+ // === STAKING DATA (independent of market data) ===
240
+ ...addresses.map(async (address) => {
252
241
  try {
253
242
  if (!isFluidSupported) return;
254
- stakingPositions[address.toLowerCase() as EthAddress].fluid = await _getAllUserEarnPositionsWithFTokens(client, network, address);
243
+ stakingPositions[address.toLowerCase()].fluid = await _getAllUserEarnPositionsWithFTokens(client, network, address);
255
244
  } catch (error) {
256
245
  console.error(`Error fetching Fluid lend data for address ${address}:`, error);
257
- stakingPositions[address.toLowerCase() as EthAddress].fluid = { error: `Error fetching Fluid lend data for address ${address}`, data: null };
246
+ stakingPositions[address.toLowerCase()].fluid = { error: `Error fetching Fluid lend data for address ${address}`, data: null };
258
247
  }
259
248
  }),
260
- ...stakingAddresses.map(async (address) => {
249
+ ...addresses.map(async (address) => {
261
250
  try {
262
251
  if (!isMainnet) return;
263
- stakingPositions[address.toLowerCase() as EthAddress].liquity = await getLiquityStakingData(client, network, address);
252
+ stakingPositions[address.toLowerCase()].liquity = await getLiquityStakingData(client, network, address);
264
253
  } catch (error) {
265
254
  console.error(`Error fetching Liquity staking data for address ${address}:`, error);
266
- stakingPositions[address.toLowerCase() as EthAddress].liquity = { error: `Error fetching Liquity staking data for address ${address}`, data: null };
255
+ stakingPositions[address.toLowerCase()].liquity = { error: `Error fetching Liquity staking data for address ${address}`, data: null };
267
256
  }
268
257
  }),
269
- ...stakingAddresses.map(async (address) => {
258
+ ...addresses.map(async (address) => {
270
259
  try {
271
260
  if (!isMainnet) return;
272
- stakingPositions[address.toLowerCase() as EthAddress].aaveV3 = await getStakeAaveData(client, network, address);
261
+ stakingPositions[address.toLowerCase()].aaveV3 = await getStakeAaveData(client, network, address);
273
262
  } catch (error) {
274
263
  console.error(`Error fetching Aave V3 staking data for address ${address}:`, error);
275
- stakingPositions[address.toLowerCase() as EthAddress].aaveV3 = { error: `Error fetching Aave V3 staking data for address ${address}`, data: null };
264
+ stakingPositions[address.toLowerCase()].aaveV3 = { error: `Error fetching Aave V3 staking data for address ${address}`, data: null };
276
265
  }
277
266
  }),
278
- ...stakingAddresses.map(async (address) => {
267
+ ...addresses.map(async (address) => {
279
268
  try {
280
269
  if (!isMainnet) return;
281
- stakingPositions[address.toLowerCase() as EthAddress].umbrella = await getUmbrellaData(client, network, address);
270
+ stakingPositions[address.toLowerCase()].umbrella = await getUmbrellaData(client, network, address);
282
271
  } catch (error) {
283
272
  console.error(`Error fetching Umbrella staking data for address ${address}:`, error);
284
- stakingPositions[address.toLowerCase() as EthAddress].umbrella = { error: `Error fetching Umbrella staking data for address ${address}`, data: null };
273
+ stakingPositions[address.toLowerCase()].umbrella = { error: `Error fetching Umbrella staking data for address ${address}`, data: null };
285
274
  }
286
275
  }),
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
- }));
276
+ // Liquity V2 staking
277
+ ...liquityV2MarketsStaking.map(market => addresses.map(async (address) => {
302
278
  try {
303
279
  if (!isMainnet) {
304
- stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data: null };
280
+ stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: '', data: null };
305
281
  return;
306
282
  }
307
- const data = await getLiquitySAndYBold(client, network, stakingPositions[address.toLowerCase() as EthAddress].liquityV2, address);
308
- stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data };
283
+ const liquityV2StakingData = await getLiquityV2Staking(client, network, market.value, address);
284
+ stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: '', data: liquityV2StakingData };
309
285
  } catch (error) {
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 };
286
+ console.error(`Error fetching Liquity V2 staking data for address ${address}, market ${market.value}:`, error);
287
+ stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: `Error fetching Liquity V2 staking data for address ${address}`, data: null };
312
288
  }
313
- }),
289
+ })).flat(),
314
290
 
315
291
  // === REWARDS DATA (independent of market data) ===
316
292
  // Batch King rewards
317
293
  (async () => {
318
294
  try {
319
295
  if (!isMainnet) {
320
- for (const address of rewardsAddresses) {
321
- rewardsData[address.toLowerCase() as EthAddress].king = { error: '', data: [] };
296
+ for (const address of addresses) {
297
+ rewardsData[address.toLowerCase()].king = { error: '', data: [] };
322
298
  }
323
299
  return;
324
300
  }
325
- const kingRewards = await getKingRewards(client, network, rewardsAddresses);
326
- for (const address of rewardsAddresses) {
301
+ const kingRewards = await getKingRewards(client, network, addresses);
302
+ for (const address of addresses) {
327
303
  const lowerAddress = address.toLowerCase() as EthAddress;
328
304
  rewardsData[lowerAddress].king = {
329
305
  error: '',
@@ -332,7 +308,7 @@ const _getPortfolioUserData = async (
332
308
  }
333
309
  } catch (error) {
334
310
  console.error('Error fetching King rewards data in batch:', error);
335
- for (const address of rewardsAddresses) {
311
+ for (const address of addresses) {
336
312
  rewardsData[address.toLowerCase() as EthAddress].king = {
337
313
  error: 'Error fetching King rewards data in batch',
338
314
  data: null,
@@ -344,13 +320,13 @@ const _getPortfolioUserData = async (
344
320
  (async () => {
345
321
  try {
346
322
  if (!isMainnet) {
347
- for (const address of rewardsAddresses) {
348
- rewardsData[address.toLowerCase() as EthAddress].uniswap = { error: '', data: [] };
323
+ for (const address of addresses) {
324
+ rewardsData[address.toLowerCase()].uniswap = { error: '', data: [] };
349
325
  }
350
326
  return;
351
327
  }
352
- const uniswapRewards = await getUniswapRewards(client, network, rewardsAddresses);
353
- for (const address of rewardsAddresses) {
328
+ const uniswapRewards = await getUniswapRewards(client, network, addresses);
329
+ for (const address of addresses) {
354
330
  const lowerAddress = address.toLowerCase() as EthAddress;
355
331
  rewardsData[lowerAddress].uniswap = {
356
332
  error: '',
@@ -359,7 +335,7 @@ const _getPortfolioUserData = async (
359
335
  }
360
336
  } catch (error) {
361
337
  console.error('Error fetching Uniswap rewards data in batch:', error);
362
- for (const address of rewardsAddresses) {
338
+ for (const address of addresses) {
363
339
  rewardsData[address.toLowerCase() as EthAddress].uniswap = {
364
340
  error: 'Error fetching Uniswap rewards data in batch',
365
341
  data: null,
@@ -367,10 +343,10 @@ const _getPortfolioUserData = async (
367
343
  }
368
344
  }
369
345
  })(),
370
- ...sparkLending.markets(network).map((market) => rewardsAddresses.map(async address => {
346
+ ...sparkMarkets.map((market) => addresses.map(async address => {
371
347
  try {
372
348
  if (!isMainnet) {
373
- rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: '', data: [] };
349
+ rewardsData[address.toLowerCase()].spark[market.value] = { error: '', data: [] };
374
350
  return;
375
351
  }
376
352
  const sparkData = await fetchSparkRewards(client, network, address, market.providerAddress);
@@ -380,7 +356,7 @@ const _getPortfolioUserData = async (
380
356
  rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: `Error fetching Spark rewards data for address ${address}`, data: null };
381
357
  }
382
358
  })).flat(),
383
- ...rewardsAddresses.map(async (address) => {
359
+ ...addresses.map(async (address) => {
384
360
  try {
385
361
  const merklData = await getMerklUnclaimedRewards(address, network);
386
362
  rewardsData[address.toLowerCase() as EthAddress].merkl = { error: '', data: merklData };
@@ -389,28 +365,27 @@ const _getPortfolioUserData = async (
389
365
  rewardsData[address.toLowerCase() as EthAddress].merkl = { error: `Error fetching Merkl rewards data for address ${address}`, data: null };
390
366
  }
391
367
  }),
392
- ...aaveV3Lending.markets(network).map(market => rewardsAddresses.map(async (address) => {
368
+ ...aaveV3Markets.map(market => addresses.map(async (address) => {
393
369
  try {
394
370
  const aaveData = await getUnclaimedRewardsForAllMarkets(client, network, address, market.providerAddress);
395
371
  rewardsData[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: '', data: aaveData };
396
372
  } catch (error) {
397
373
  console.error(`Error fetching Aave V3 Merit rewards data for address ${address}:`, error);
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 };
374
+ rewardsData[address.toLowerCase() as EthAddress].aaveV3 = { error: `Error fetching Aave V3 rewards data for address ${address}`, data: null };
400
375
  }
401
376
  })).flat(),
402
377
  // Batch Spark Airdrop rewards
403
378
  (async () => {
404
379
  try {
405
380
  if (!isMainnet) {
406
- for (const address of rewardsAddresses) {
407
- rewardsData[address.toLowerCase() as EthAddress].spk = { error: '', data: [] };
381
+ for (const address of addresses) {
382
+ rewardsData[address.toLowerCase()].spk = { error: '', data: [] };
408
383
  }
409
384
  return;
410
385
  }
411
386
 
412
- const sparkAirdropRewards = await fetchSparkAirdropRewards(client, network, rewardsAddresses);
413
- for (const address of rewardsAddresses) {
387
+ const sparkAirdropRewards = await fetchSparkAirdropRewards(client, network, addresses);
388
+ for (const address of addresses) {
414
389
  const lowerAddress = address.toLowerCase() as EthAddress;
415
390
  rewardsData[lowerAddress].spk = {
416
391
  error: '',
@@ -419,7 +394,7 @@ const _getPortfolioUserData = async (
419
394
  }
420
395
  } catch (error) {
421
396
  console.error('Error fetching Spark Airdrop rewards data in batch:', error);
422
- for (const address of rewardsAddresses) {
397
+ for (const address of addresses) {
423
398
  rewardsData[address.toLowerCase() as EthAddress].spk = {
424
399
  error: 'Error fetching Spark Airdrop rewards data in batch',
425
400
  data: null,
@@ -433,8 +408,8 @@ const _getPortfolioUserData = async (
433
408
  return;
434
409
  }
435
410
 
436
- const ethenaAirdropRewards = await fetchEthenaAirdropRewards(rewardsAddresses);
437
- for (const address of rewardsAddresses) {
411
+ const ethenaAirdropRewards = await fetchEthenaAirdropRewards(addresses);
412
+ for (const address of addresses) {
438
413
  const lowerAddress = address.toLowerCase() as EthAddress;
439
414
  rewardsData[lowerAddress].ethena = {
440
415
  error: '',
@@ -443,7 +418,7 @@ const _getPortfolioUserData = async (
443
418
  }
444
419
  } catch (error) {
445
420
  console.error('Error fetching Ethena Airdrop rewards data:', error);
446
- for (const address of rewardsAddresses) {
421
+ for (const address of addresses) {
447
422
  rewardsData[address.toLowerCase() as EthAddress].ethena = {
448
423
  error: 'Error fetching Ethena Airdrop rewards data in batch',
449
424
  data: null,
@@ -451,63 +426,162 @@ const _getPortfolioUserData = async (
451
426
  }
452
427
  }
453
428
  })(),
429
+ ]);
454
430
 
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 };
464
- }
431
+ await Promise.all([
432
+ ...aaveV3Markets.map((market) => allAddresses.map(async (address) => {
433
+ try {
434
+ const accData = await _getAaveV3AccountData(client, network, address, { selectedMarket: market, ...aaveV3MarketsData[market.value] });
435
+ if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: '', data: accData };
465
436
  } catch (error) {
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);
437
+ console.error(`Error fetching AaveV3 account data for address ${address} on market ${market.value}:`, error);
438
+ positions[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: `Error fetching AaveV3 account data for address ${address} on market ${market.value}`, data: null };
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
+ };
506
475
  }
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
- }));
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 };
516
+ }
517
+ } catch (error) {
518
+ console.error(`Error fetching Maker CDP data for address ${address} with ID ${cdpInfo.id}:`, error);
519
+ positions[address.toLowerCase() as EthAddress].maker[cdpInfo.id] = { error: `Error fetching Maker CDP data for address ${address} with ID ${cdpInfo.id}`, data: null };
520
+ }
521
+ })).flat(),
522
+ ...aaveV2Markets.map((market) => addresses.map(async (address) => {
523
+ try {
524
+ const accData = await _getAaveV2AccountData(client, network, address, aaveV2MarketsData[market.value].assetsData, market);
525
+ if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].aaveV2[market.value] = { error: '', data: accData };
526
+ } catch (error) {
527
+ console.error(`Error fetching AaveV2 account data for address ${address}:`, error);
528
+ positions[address.toLowerCase() as EthAddress].aaveV2[market.value] = { error: `Error fetching AaveV2 account data for address ${address}`, data: null };
529
+ }
530
+ })).flat(),
531
+ ...compoundV2Markets.map((market) => addresses.map(async (address) => {
532
+ try {
533
+ const accData = await _getCompoundV2AccountData(client, network, address, compoundV2MarketsData[market.value].assetsData);
534
+ if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].compoundV2[market.value] = { error: '', data: accData };
535
+ } catch (error) {
536
+ console.error(`Error fetching CompoundV2 account data for address ${address}:`, error);
537
+ positions[address.toLowerCase() as EthAddress].compoundV2[market.value] = { error: `Error fetching CompoundV2 account data for address ${address}`, data: null };
538
+ }
539
+ })).flat(),
540
+ ...addresses.map(async (address) => {
541
+ try {
542
+ if (!isMainnet) return; // Liquity trove info is only available on mainnet
543
+ const troveInfo = await _getLiquityTroveInfo(client, network, address);
544
+ if (new Dec(troveInfo.collateral).gt(0)) positions[address.toLowerCase() as EthAddress].liquity = { error: '', data: troveInfo };
545
+ } catch (error) {
546
+ console.error(`Error fetching Liquity trove info for address ${address}:`, error);
547
+ positions[address.toLowerCase() as EthAddress].liquity = { error: `Error fetching Liquity trove info for address ${address}`, data: null };
548
+ }
549
+ }),
550
+ ...crvUsdMarkets.map((market) => addresses.map(async (address) => {
551
+ try {
552
+ const accData = await _getCurveUsdUserData(client, network, address, market, crvUsdMarketsData[market.value].activeBand, crvUsdMarketsData[market.value].borrowRate);
553
+ if (new Dec(accData.suppliedUsd).gt(0) || new Dec(accData.borrowedUsd).gt(0)) {
554
+ positions[address.toLowerCase() as EthAddress].crvUsd[market.value] = { error: '', data: accData };
555
+ }
556
+ } catch (error) {
557
+ console.error(`Error fetching Curve USD account data for address ${address} on market ${market.value}:`, error);
558
+ positions[address.toLowerCase() as EthAddress].crvUsd[market.value] = { error: `Error fetching Curve USD account data for address ${address} on market ${market.value}`, data: null };
559
+ }
560
+ })).flat(),
561
+ ...llamaLendMarkets.map((market) => addresses.map(async (address) => {
562
+ try {
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
+ }
511
585
  }),
512
586
  ]);
513
587
 
@@ -515,63 +589,14 @@ const _getPortfolioUserData = async (
515
589
  positions,
516
590
  stakingPositions,
517
591
  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,
563
592
  markets,
564
- errors: {
565
- ...(Object.keys(marketsErrors).length ? { markets: marketsErrors } : {}),
566
- ...userData.errors,
567
- },
568
593
  };
569
594
  }
570
595
 
571
596
 
572
597
  export async function getShifterPortfolioData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, addresses: EthAddress[], isSim = false): Promise<{
573
598
  positions: PortfolioPositionsData;
574
- markets: ShifterPortfolioMarketsData;
599
+ markets: PortfolioMarketsData;
575
600
  }> {
576
601
  const isMainnet = network === NetworkNumber.Eth;
577
602
  const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
@@ -591,7 +616,7 @@ export async function getShifterPortfolioData(provider: EthereumProvider, networ
591
616
  const client = getViemProvider(provider, ...args);
592
617
  const defaultClient = getViemProvider(defaultProvider, ...args);
593
618
 
594
- const markets: ShifterPortfolioMarketsData = {
619
+ const markets: PortfolioMarketsData = {
595
620
  morphoMarketsData: {},
596
621
  morphoMidnightMarketsData: {}, // Morpho Midnight is not fetched for the shifter; key kept so the payload shape is stable
597
622
  compoundV3MarketsData: {},