@defisaver/positions-sdk 2.0.13 → 2.0.14-dev-portfolio1

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 (117) hide show
  1. package/CLAUDE.md +32 -0
  2. package/cjs/aaveV2/index.js +1 -0
  3. package/cjs/aaveV3/index.d.ts +12 -0
  4. package/cjs/aaveV3/index.js +93 -1
  5. package/cjs/claiming/aaveV3.d.ts +9 -0
  6. package/cjs/claiming/aaveV3.js +148 -0
  7. package/cjs/claiming/compV3.d.ts +15 -0
  8. package/cjs/claiming/compV3.js +34 -0
  9. package/cjs/claiming/index.d.ts +6 -0
  10. package/cjs/claiming/index.js +46 -0
  11. package/cjs/claiming/king.d.ts +4 -0
  12. package/cjs/claiming/king.js +72 -0
  13. package/cjs/claiming/morphoBlue.d.ts +6 -0
  14. package/cjs/claiming/morphoBlue.js +113 -0
  15. package/cjs/claiming/spark.d.ts +6 -0
  16. package/cjs/claiming/spark.js +188 -0
  17. package/cjs/config/contracts.d.ts +2681 -0
  18. package/cjs/config/contracts.js +103 -2
  19. package/cjs/constants/index.d.ts +4 -0
  20. package/cjs/constants/index.js +6 -2
  21. package/cjs/contracts.d.ts +3027 -23
  22. package/cjs/contracts.js +10 -1
  23. package/cjs/index.d.ts +2 -1
  24. package/cjs/index.js +3 -1
  25. package/cjs/liquity/index.d.ts +11 -0
  26. package/cjs/liquity/index.js +39 -1
  27. package/cjs/liquityV2/index.d.ts +35 -0
  28. package/cjs/liquityV2/index.js +136 -1
  29. package/cjs/markets/aave/marketAssets.js +1 -1
  30. package/cjs/markets/compound/marketsAssets.js +2 -2
  31. package/cjs/markets/spark/marketAssets.js +1 -1
  32. package/cjs/morphoBlue/index.d.ts +5 -0
  33. package/cjs/morphoBlue/index.js +38 -4
  34. package/cjs/portfolio/index.d.ts +6 -1
  35. package/cjs/portfolio/index.js +295 -10
  36. package/cjs/services/utils.d.ts +5 -0
  37. package/cjs/services/utils.js +33 -1
  38. package/cjs/services/viem.d.ts +12 -12
  39. package/cjs/spark/index.js +1 -0
  40. package/cjs/staking/staking.js +3 -1
  41. package/cjs/types/claiming.d.ts +93 -0
  42. package/cjs/types/claiming.js +27 -0
  43. package/cjs/umbrella/index.d.ts +5 -0
  44. package/cjs/umbrella/index.js +50 -0
  45. package/cjs/umbrella/umbrellaUtils.d.ts +22 -0
  46. package/cjs/umbrella/umbrellaUtils.js +34 -0
  47. package/esm/aaveV2/index.js +1 -0
  48. package/esm/aaveV3/index.d.ts +12 -0
  49. package/esm/aaveV3/index.js +91 -1
  50. package/esm/claiming/aaveV3.d.ts +9 -0
  51. package/esm/claiming/aaveV3.js +139 -0
  52. package/esm/claiming/compV3.d.ts +15 -0
  53. package/esm/claiming/compV3.js +30 -0
  54. package/esm/claiming/index.d.ts +6 -0
  55. package/esm/claiming/index.js +6 -0
  56. package/esm/claiming/king.d.ts +4 -0
  57. package/esm/claiming/king.js +64 -0
  58. package/esm/claiming/morphoBlue.d.ts +6 -0
  59. package/esm/claiming/morphoBlue.js +104 -0
  60. package/esm/claiming/spark.d.ts +6 -0
  61. package/esm/claiming/spark.js +179 -0
  62. package/esm/config/contracts.d.ts +2681 -0
  63. package/esm/config/contracts.js +102 -1
  64. package/esm/constants/index.d.ts +4 -0
  65. package/esm/constants/index.js +5 -1
  66. package/esm/contracts.d.ts +3027 -23
  67. package/esm/contracts.js +9 -0
  68. package/esm/index.d.ts +2 -1
  69. package/esm/index.js +2 -1
  70. package/esm/liquity/index.d.ts +11 -0
  71. package/esm/liquity/index.js +38 -1
  72. package/esm/liquityV2/index.d.ts +35 -0
  73. package/esm/liquityV2/index.js +137 -4
  74. package/esm/markets/aave/marketAssets.js +1 -1
  75. package/esm/markets/compound/marketsAssets.js +2 -2
  76. package/esm/markets/spark/marketAssets.js +1 -1
  77. package/esm/morphoBlue/index.d.ts +5 -0
  78. package/esm/morphoBlue/index.js +38 -5
  79. package/esm/portfolio/index.d.ts +6 -1
  80. package/esm/portfolio/index.js +300 -15
  81. package/esm/services/utils.d.ts +5 -0
  82. package/esm/services/utils.js +31 -0
  83. package/esm/services/viem.d.ts +12 -12
  84. package/esm/spark/index.js +1 -0
  85. package/esm/staking/staking.js +3 -1
  86. package/esm/types/claiming.d.ts +93 -0
  87. package/esm/types/claiming.js +24 -0
  88. package/esm/umbrella/index.d.ts +5 -0
  89. package/esm/umbrella/index.js +46 -0
  90. package/esm/umbrella/umbrellaUtils.d.ts +22 -0
  91. package/esm/umbrella/umbrellaUtils.js +28 -0
  92. package/package.json +2 -2
  93. package/src/aaveV2/index.ts +2 -1
  94. package/src/aaveV3/index.ts +100 -2
  95. package/src/claiming/aaveV3.ts +163 -0
  96. package/src/claiming/compV3.ts +23 -0
  97. package/src/claiming/index.ts +13 -0
  98. package/src/claiming/king.ts +66 -0
  99. package/src/claiming/morphoBlue.ts +119 -0
  100. package/src/claiming/spark.ts +226 -0
  101. package/src/config/contracts.ts +105 -5
  102. package/src/constants/index.ts +5 -1
  103. package/src/contracts.ts +14 -1
  104. package/src/index.ts +2 -0
  105. package/src/liquity/index.ts +57 -2
  106. package/src/liquityV2/index.ts +241 -4
  107. package/src/markets/aave/marketAssets.ts +1 -1
  108. package/src/markets/compound/marketsAssets.ts +2 -2
  109. package/src/markets/spark/marketAssets.ts +1 -1
  110. package/src/morphoBlue/index.ts +43 -6
  111. package/src/portfolio/index.ts +302 -15
  112. package/src/services/utils.ts +37 -1
  113. package/src/spark/index.ts +2 -1
  114. package/src/staking/staking.ts +2 -1
  115. package/src/types/claiming.ts +109 -0
  116. package/src/umbrella/index.ts +70 -0
  117. package/src/umbrella/umbrellaUtils.ts +30 -0
@@ -2,9 +2,15 @@ import Dec from 'decimal.js';
2
2
  import { EthAddress, EthereumProvider, NetworkNumber } from '../types/common';
3
3
  import {
4
4
  AaveMarkets,
5
- CompoundMarkets, CrvUsdMarkets, EulerV2Markets, LlamaLendMarkets, MorphoBlueMarkets, SparkMarkets,
5
+ CompoundMarkets,
6
+ CrvUsdMarkets,
7
+ EulerV2Markets,
8
+ LiquityV2Markets,
9
+ LlamaLendMarkets,
10
+ MorphoBlueMarkets,
11
+ SparkMarkets,
6
12
  } from '../markets';
7
- import { _getMorphoBlueAccountData, _getMorphoBlueMarketData } from '../morphoBlue';
13
+ import { _getMorphoBlueAccountData, _getMorphoBlueMarketData, getMorphoEarn } from '../morphoBlue';
8
14
  import {
9
15
  AaveV2MarketData,
10
16
  AaveV3MarketData,
@@ -15,6 +21,7 @@ import {
15
21
  CompoundVersions,
16
22
  CrvUSDGlobalMarketData,
17
23
  EulerV2FullMarketData,
24
+ LiquityV2MarketData,
18
25
  LlamaLendGlobalMarketData,
19
26
  MorphoBlueMarketInfo,
20
27
  PortfolioPositionsData,
@@ -25,18 +32,29 @@ import { _getSparkAccountData, _getSparkMarketsData } from '../spark';
25
32
  import { _getEulerV2AccountData, _getEulerV2MarketsData } from '../eulerV2';
26
33
  import { _getCurveUsdGlobalData, _getCurveUsdUserData } from '../curveUsd';
27
34
  import { _getLlamaLendGlobalData, _getLlamaLendUserData } from '../llamaLend';
28
- import { _getAaveV3AccountData, _getAaveV3MarketData } from '../aaveV3';
35
+ import { _getAaveV3AccountData, _getAaveV3MarketData, getStakeAaveData } from '../aaveV3';
29
36
  import { ZERO_ADDRESS } from '../constants';
30
37
  import { _getMakerCdpData, _getUserCdps } from '../maker';
31
38
  import { _getAaveV2AccountData, _getAaveV2MarketsData } from '../aaveV2';
32
39
  import { _getCompoundV2AccountData, _getCompoundV2MarketsData } from '../compoundV2';
33
40
  import { getViemProvider } from '../services/viem';
34
- import { _getLiquityTroveInfo } from '../liquity';
35
- import { _getLiquityV2MarketData, _getLiquityV2TroveData, _getLiquityV2UserTroveIds } from '../liquityV2';
36
- import { _getUserPositionsPortfolio } from '../fluid';
41
+ import { _getLiquityTroveInfo, getLiquityStakingData } from '../liquity';
42
+ import { getLiquityV2Staking, _getLiquityV2MarketData, getLiquitySAndYBold } from '../liquityV2';
43
+ import { _getAllUserEarnPositionsWithFTokens, _getUserPositionsPortfolio } from '../fluid';
37
44
  import { getEulerV2SubAccounts } from '../helpers/eulerHelpers';
45
+ import { getUmbrellaData } from '../umbrella';
46
+ import { getMeritUnclaimedRewards, getUnclaimedRewardsForAllMarkets } from '../claiming/aaveV3';
47
+ import { getCompoundV3Rewards } from '../claiming/compV3';
48
+ import { fetchSparkAirdropRewards, fetchSparkRewards } from '../claiming/spark';
49
+ import { fetchMorphoBlueRewards } from '../claiming/morphoBlue';
50
+ import { getKingRewards } from '../claiming/king';
38
51
 
39
- export async function getPortfolioData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, addresses: EthAddress[], summerFiAddresses: EthAddress[]): Promise<PortfolioPositionsData> {
52
+ export async function getPortfolioData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, addresses: EthAddress[], summerFiAddresses: EthAddress[]): Promise<{
53
+ positions: PortfolioPositionsData;
54
+ stakingPositions: any;
55
+ rewardsData: any;
56
+ markets: any;
57
+ }> {
40
58
  const isMainnet = network === NetworkNumber.Eth;
41
59
  const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base].includes(network);
42
60
 
@@ -49,6 +67,8 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
49
67
  const compoundV2Markets = [CompoundVersions.CompoundV2].map((version) => CompoundMarkets(network)[version]).filter((market) => market.chainIds.includes(network));
50
68
  const crvUsdMarkets = Object.values(CrvUsdMarkets(network)).filter((market) => market.chainIds.includes(network));
51
69
  const llamaLendMarkets = [NetworkNumber.Eth, NetworkNumber.Arb].includes(network) ? Object.values(LlamaLendMarkets(network)).filter((market) => market.chainIds.includes(network)) : [];
70
+ const liquityV2Markets = [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)) : [];
71
+ const liquityV2MarketsStaking = [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)).filter(market => !market.isLegacy) : [];
52
72
 
53
73
  const client = getViemProvider(provider, network, {
54
74
  batch: {
@@ -75,9 +95,26 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
75
95
  const compoundV2MarketsData: Record<string, CompoundV2MarketsData> = {};
76
96
  const crvUsdMarketsData: Record<string, CrvUSDGlobalMarketData> = {};
77
97
  const llamaLendMarketsData: Record<string, LlamaLendGlobalMarketData> = {};
98
+ const liquityV2MarketsData: Record<string, LiquityV2MarketData> = {};
99
+
100
+ const markets = {
101
+ morphoMarketsData,
102
+ compoundV3MarketsData,
103
+ sparkMarketsData,
104
+ eulerV2MarketsData,
105
+ aaveV3MarketsData,
106
+ aaveV2MarketsData,
107
+ compoundV2MarketsData,
108
+ crvUsdMarketsData,
109
+ llamaLendMarketsData,
110
+ liquityV2MarketsData,
111
+ };
78
112
 
79
113
  const positions: PortfolioPositionsData = {};
114
+ const stakingPositions: any = {};
115
+ const rewardsData: any = {};
80
116
  const allAddresses = [...addresses, ...summerFiAddresses];
117
+
81
118
  for (const address of allAddresses) {
82
119
  positions[address.toLowerCase() as EthAddress] = {
83
120
  aaveV3: {},
@@ -98,7 +135,36 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
98
135
  };
99
136
  }
100
137
 
138
+ // TODO: check default values, probably needed when fetching portfolio on unsupported networks
139
+ for (const address of addresses) {
140
+ stakingPositions[address.toLowerCase() as EthAddress] = {
141
+ aaveV3: {},
142
+ morphoBlue: {},
143
+ compoundV3: {},
144
+ spark: {},
145
+ aaveV2: {},
146
+ compoundV2: {},
147
+ liquity: {},
148
+ liquityV2: {},
149
+ fluid: {
150
+ error: '',
151
+ data: {},
152
+ },
153
+ };
154
+
155
+ rewardsData[address.toLowerCase() as EthAddress] = {
156
+ aaveV3merit: {},
157
+ aaveV3: {},
158
+ compV3: {},
159
+ spark: {},
160
+ spk: {},
161
+ king: {},
162
+ morpho: {},
163
+ };
164
+ }
165
+
101
166
  await Promise.allSettled([
167
+ // === MARKET DATA (needs to be fetched first) ===
102
168
  ...morphoMarkets.map(async (market) => {
103
169
  const marketData = await _getMorphoBlueMarketData(client, network, market);
104
170
  morphoMarketsData[market.value] = marketData;
@@ -119,11 +185,6 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
119
185
  const marketData = await _getAaveV3MarketData(client, network, market);
120
186
  aaveV3MarketsData[market.value] = marketData;
121
187
  }),
122
- ...addresses.map(async (address) => {
123
- if (!isMainnet) return; // Maker CDPs are only available on mainnet
124
- const makerCdp = await _getUserCdps(client, network, address);
125
- makerCdps[address.toLowerCase() as EthAddress] = makerCdp;
126
- }),
127
188
  ...aaveV2Markets.map(async (market) => {
128
189
  const marketData = await _getAaveV2MarketsData(client, network, market);
129
190
  aaveV2MarketsData[market.value] = marketData;
@@ -140,6 +201,17 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
140
201
  const marketData = await _getLlamaLendGlobalData(client, network, market);
141
202
  llamaLendMarketsData[market.value] = marketData;
142
203
  }),
204
+ ...liquityV2Markets.map(async (market) => {
205
+ const marketData = await _getLiquityV2MarketData(client, network, market);
206
+ liquityV2MarketsData[market.value] = marketData;
207
+ }),
208
+
209
+ // === INDEPENDENT USER DATA (doesn't depend on market data) ===
210
+ ...addresses.map(async (address) => {
211
+ if (!isMainnet) return; // Maker CDPs are only available on mainnet
212
+ const makerCdp = await _getUserCdps(client, network, address);
213
+ makerCdps[address.toLowerCase() as EthAddress] = makerCdp;
214
+ }),
143
215
  ...addresses.map(async (address) => {
144
216
  try {
145
217
  if (!isFluidSupported) return; // Fluid is not available on Optimism
@@ -157,6 +229,180 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
157
229
  };
158
230
  }
159
231
  }),
232
+
233
+ // === STAKING DATA (independent of market data) ===
234
+ ...addresses.map(async (address) => {
235
+ try {
236
+ if (!isFluidSupported) return;
237
+ const fluidLendData = await _getAllUserEarnPositionsWithFTokens(client, network, address);
238
+ stakingPositions[address.toLowerCase()].fluid = fluidLendData;
239
+ } catch (error) {
240
+ console.error(`Error fetching Fluid lend data for address ${address}:`, error);
241
+ stakingPositions[address.toLowerCase()].fluid = { error: `Error fetching Fluid lend data for address ${address}`, data: null };
242
+ }
243
+ }),
244
+ ...addresses.map(async (address) => {
245
+ try {
246
+ if (!isMainnet) return;
247
+ const liquityStakingData = await getLiquityStakingData(client, network, address);
248
+ stakingPositions[address.toLowerCase()].liquity = liquityStakingData;
249
+ } catch (error) {
250
+ console.error(`Error fetching Liquity staking data for address ${address}:`, error);
251
+ stakingPositions[address.toLowerCase()].liquity = { error: `Error fetching Liquity staking data for address ${address}`, data: null };
252
+ }
253
+ }),
254
+ ...addresses.map(async (address) => {
255
+ try {
256
+ if (!isMainnet) return;
257
+ stakingPositions[address.toLowerCase()].aaveV3 = await getStakeAaveData(client, network, address);
258
+ } catch (error) {
259
+ console.error(`Error fetching Aave V3 staking data for address ${address}:`, error);
260
+ stakingPositions[address.toLowerCase()].aaveV3 = { error: `Error fetching Aave V3 staking data for address ${address}`, data: null };
261
+ }
262
+ }),
263
+ ...addresses.map(async (address) => {
264
+ try {
265
+ if (!isMainnet) return;
266
+ const umbrellaStakingData = await getUmbrellaData(client, network, address);
267
+ stakingPositions[address.toLowerCase()].umbrella = umbrellaStakingData;
268
+ } catch (error) {
269
+ console.error(`Error fetching Umbrella staking data for address ${address}:`, error);
270
+ stakingPositions[address.toLowerCase()].umbrella = { error: `Error fetching Umbrella staking data for address ${address}`, data: null };
271
+ }
272
+ }),
273
+ // Liquity V2 staking
274
+ ...liquityV2MarketsStaking.map(market => addresses.map(async (address) => {
275
+ try {
276
+ if (!isMainnet) {
277
+ stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: '', data: null };
278
+ return;
279
+ }
280
+ const liquityV2StakingData = await getLiquityV2Staking(client, network, market.value, address);
281
+ stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: '', data: liquityV2StakingData };
282
+ } catch (error) {
283
+ console.error(`Error fetching Liquity V2 staking data for address ${address}, market ${market.value}:`, error);
284
+ stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: `Error fetching Liquity V2 staking data for address ${address}`, data: null };
285
+ }
286
+ })).flat(),
287
+
288
+ // === REWARDS DATA (independent of market data) ===
289
+ // Batch King rewards
290
+ (async () => {
291
+ try {
292
+ if (!isMainnet) {
293
+ for (const address of addresses) {
294
+ rewardsData[address.toLowerCase()].king = { error: '', data: [] };
295
+ }
296
+ return;
297
+ }
298
+ const kingRewards = await getKingRewards(client, network, addresses);
299
+ for (const address of addresses) {
300
+ const lowerAddress = address.toLowerCase() as EthAddress;
301
+ rewardsData[lowerAddress].king = {
302
+ error: '',
303
+ data: kingRewards[lowerAddress] || [],
304
+ };
305
+ }
306
+ } catch (error) {
307
+ console.error('Error fetching King rewards data in batch:', error);
308
+ for (const address of addresses) {
309
+ rewardsData[address.toLowerCase() as EthAddress].king = {
310
+ error: 'Error fetching King rewards data in batch',
311
+ data: null,
312
+ };
313
+ }
314
+ }
315
+ })(),
316
+ ...sparkMarkets.map((market) => addresses.map(async address => {
317
+ try {
318
+ if (!isMainnet) {
319
+ rewardsData[address.toLowerCase()].spark[market.value] = { error: '', data: [] };
320
+ return;
321
+ }
322
+ const sparkData = await fetchSparkRewards(client, network, address, market.providerAddress);
323
+ rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: '', data: sparkData };
324
+ } catch (error) {
325
+ console.error(`Error fetching Spark rewards data for address ${address}, market ${market.value}:`, error);
326
+ rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: `Error fetching Spark rewards data for address ${address}`, data: null };
327
+ }
328
+ })).flat(),
329
+ // CompV3 rewards
330
+ ...compoundV3Markets.map(market => addresses.map(async (address) => {
331
+ try {
332
+ const compV3Rewards = await getCompoundV3Rewards(client, network, address, market.baseMarketAddress);
333
+ rewardsData[address.toLowerCase() as EthAddress].compV3[market.value] = { error: '', data: compV3Rewards };
334
+ } catch (error) {
335
+ console.error(`Error fetching Compound V3 rewards data for address ${address}:`, error);
336
+ rewardsData[address.toLowerCase() as EthAddress].compV3[market.value] = { error: `Error fetching Compound V3 rewards data for address ${address}`, data: null };
337
+ }
338
+ })).flat(),
339
+ ...addresses.map(async (address) => {
340
+ try {
341
+ const aaveMeritData = await getMeritUnclaimedRewards(address, network);
342
+ rewardsData[address.toLowerCase() as EthAddress].aaveV3merit = { error: '', data: aaveMeritData };
343
+ } catch (error) {
344
+ console.error(`Error fetching Aave V3 Merit rewards data for address ${address}:`, error);
345
+ rewardsData[address.toLowerCase() as EthAddress].aaveV3merit = { error: `Error fetching Aave V3 Merit rewards data for address ${address}`, data: null };
346
+ }
347
+ }),
348
+ ...aaveV3Markets.map(market => addresses.map(async (address) => {
349
+ try {
350
+ const aaveData = await getUnclaimedRewardsForAllMarkets(client, network, address, market.providerAddress);
351
+ rewardsData[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: '', data: aaveData };
352
+ } catch (error) {
353
+ console.error(`Error fetching Aave V3 Merit rewards data for address ${address}:`, error);
354
+ rewardsData[address.toLowerCase() as EthAddress].aaveV3 = { error: `Error fetching Aave V3 rewards data for address ${address}`, data: null };
355
+ }
356
+ })).flat(),
357
+ // Batch Morpho Blue rewards
358
+ (async () => {
359
+ try {
360
+ const morphoRewards = await fetchMorphoBlueRewards(client, network, addresses);
361
+ for (const address of addresses) {
362
+ const lowerAddress = address.toLowerCase() as EthAddress;
363
+ rewardsData[lowerAddress].morpho = {
364
+ error: '',
365
+ data: morphoRewards[lowerAddress] || [],
366
+ };
367
+ }
368
+ } catch (error) {
369
+ console.error('Error fetching Morpho Blue rewards data in batch:', error);
370
+ for (const address of addresses) {
371
+ rewardsData[address.toLowerCase() as EthAddress].morpho = {
372
+ error: 'Error fetching Morpho Blue rewards data in batch',
373
+ data: null,
374
+ };
375
+ }
376
+ }
377
+ })(),
378
+ // Batch Spark Airdrop rewards
379
+ (async () => {
380
+ try {
381
+ if (!isMainnet) {
382
+ for (const address of addresses) {
383
+ rewardsData[address.toLowerCase()].king = { error: '', data: [] };
384
+ }
385
+ return;
386
+ }
387
+
388
+ const sparkAirdropRewards = await fetchSparkAirdropRewards(client, network, addresses);
389
+ for (const address of addresses) {
390
+ const lowerAddress = address.toLowerCase() as EthAddress;
391
+ rewardsData[lowerAddress].spk = {
392
+ error: '',
393
+ data: sparkAirdropRewards[lowerAddress] || [],
394
+ };
395
+ }
396
+ } catch (error) {
397
+ console.error('Error fetching Spark Airdrop rewards data in batch:', error);
398
+ for (const address of addresses) {
399
+ rewardsData[address.toLowerCase() as EthAddress].spk = {
400
+ error: 'Error fetching Spark Airdrop rewards data in batch',
401
+ data: null,
402
+ };
403
+ }
404
+ }
405
+ })(),
160
406
  ]);
161
407
 
162
408
 
@@ -172,8 +418,31 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
172
418
  })).flat(),
173
419
  ...morphoMarkets.map((market) => addresses.map(async (address) => {
174
420
  try {
175
- const accData = await _getMorphoBlueAccountData(client, network, address, market, morphoMarketsData[market.value]);
176
- if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: '', data: accData };
421
+ const [accDataPromise, earnDataPromise] = await Promise.allSettled([
422
+ _getMorphoBlueAccountData(client, network, address, market, morphoMarketsData[market.value]),
423
+ getMorphoEarn(client, network, address, market, morphoMarketsData[market.value]),
424
+ ]);
425
+ if (accDataPromise.status === 'rejected') {
426
+ console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, accDataPromise.reason);
427
+ positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
428
+ }
429
+ if (earnDataPromise.status === 'rejected') {
430
+ console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, earnDataPromise.reason);
431
+ positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
432
+ }
433
+ if (accDataPromise.status !== 'rejected') {
434
+ const accData = accDataPromise.value;
435
+ if (new Dec(accData.suppliedUsd).gt(0)) positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: '', data: accData };
436
+ }
437
+ if (earnDataPromise.status !== 'rejected') {
438
+ const earnData = earnDataPromise.value;
439
+ if (earnData && new Dec(earnData.amount).gt(0)) {
440
+ stakingPositions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = {
441
+ error: '',
442
+ data: earnData,
443
+ };
444
+ }
445
+ }
177
446
  } catch (error) {
178
447
  console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, error);
179
448
  positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
@@ -279,7 +548,25 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
279
548
  positions[address.toLowerCase() as EthAddress].llamaLend[market.value] = { error: `Error fetching LlamaLend account data for address ${address} on market ${market.value}`, data: null };
280
549
  }
281
550
  })).flat(),
551
+ // liquity sBold/yBold and staking options
552
+ ...addresses.map(async (address) => {
553
+ try {
554
+ if (!isMainnet) {
555
+ stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data: null };
556
+ return;
557
+ }
558
+ const data = await getLiquitySAndYBold(client, network, stakingPositions[address.toLowerCase()].liquityV2, address);
559
+ stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data };
560
+ } catch (error) {
561
+ console.error(`Error fetching SBold/YBold data for address ${address}:`, error);
562
+ }
563
+ }),
282
564
  ]);
283
565
 
284
- return positions;
566
+ return {
567
+ positions,
568
+ stakingPositions,
569
+ rewardsData,
570
+ markets,
571
+ };
285
572
  }
@@ -61,4 +61,40 @@ export const isMaxuint = (amount: string) => compareAddresses(MAXUINT, amount);
61
61
 
62
62
  export const isMainnetNetwork = (network: NetworkNumber) => network === NetworkNumber.Eth;
63
63
 
64
- export const DEFAULT_TIMEOUT = 2000; // 2 seconds
64
+ export const DEFAULT_TIMEOUT = 2000; // 2 seconds
65
+
66
+ /**
67
+ * Converts web3 hybrid response (that can be used as objects and arrays, so has duplicated values) to objects.
68
+ * @param value
69
+ */
70
+ export const convertHybridArraysToObjects = (value: any): any => {
71
+ if (Array.isArray(value)) {
72
+ const keys = Object.keys(value);
73
+ const hasNamedKeys = keys.some((key) => Number.isNaN(Number(key)));
74
+
75
+ // If array has named keys, treat it as an object and keep only those
76
+ if (hasNamedKeys) {
77
+ const result: Record<string, any> = {};
78
+ for (const [key, val] of Object.entries(value)) {
79
+ if (Number.isNaN(Number(key))) {
80
+ result[key] = convertHybridArraysToObjects(val);
81
+ }
82
+ }
83
+ return result;
84
+ }
85
+
86
+ // Else, treat as a regular array
87
+ return value.map(convertHybridArraysToObjects);
88
+ }
89
+
90
+ // If it's an object (but not an array), recurse into its values
91
+ if (value && typeof value === 'object') {
92
+ const result: Record<string, any> = {};
93
+ for (const [key, val] of Object.entries(value)) {
94
+ result[key] = convertHybridArraysToObjects(val);
95
+ }
96
+ return result;
97
+ }
98
+
99
+ return value;
100
+ };
@@ -286,7 +286,8 @@ export const _getSparkAccountBalances = async (provider: Client, network: Networ
286
286
  // @ts-ignore
287
287
  const protocolDataProviderContract = createViemContractFromConfigFunc(market.protocolData, market.protocolDataAddress)(provider, network);
288
288
 
289
- const reserveTokens = await protocolDataProviderContract.read.getAllReservesTokens(setViemBlockNumber(block));
289
+ // @ts-ignore
290
+ const reserveTokens = await protocolDataProviderContract.read.getAllReservesTokens(setViemBlockNumber(block)) as { symbol: string, tokenAddress: EthAddress }[];
290
291
  const symbols = reserveTokens.map(({ symbol }: { symbol: string }) => symbol);
291
292
  const _addresses = reserveTokens.map(({ tokenAddress }: { tokenAddress: EthAddress }) => tokenAddress);
292
293
 
@@ -54,7 +54,7 @@ const getApyFromDfsApi = async (asset: string) => {
54
54
  }
55
55
  };
56
56
 
57
- export const STAKING_ASSETS = ['cbETH', 'wstETH', 'cbETH', 'rETH', 'sDAI', 'weETH', 'sUSDe', 'osETH', 'ezETH', 'ETHx', 'rsETH', 'pufETH', 'wrsETH', 'wsuperOETHb', 'sUSDS', 'tETH', 'PT sUSDe Sep', 'PT USDe Sep', 'PT sUSDe Nov'];
57
+ export const STAKING_ASSETS = ['cbETH', 'wstETH', 'cbETH', 'rETH', 'sDAI', 'weETH', 'sUSDe', 'osETH', 'ezETH', 'ETHx', 'rsETH', 'pufETH', 'wrsETH', 'wsuperOETHb', 'sUSDS', 'tETH', 'PT sUSDe Sep', 'PT USDe Sep', 'PT sUSDe Nov', 'PT USDe Nov'];
58
58
 
59
59
  export const getStakingApy = memoize(async (asset: string) => {
60
60
  try {
@@ -80,6 +80,7 @@ export const getStakingApy = memoize(async (asset: string) => {
80
80
  if (asset === 'tETH') return await getApyFromDfsApi('tETH');
81
81
  if (asset === 'USDe') return await getApyFromDfsApi('USDe');
82
82
  if (asset === 'PT sUSDe Nov') return await getApyFromDfsApi('PT sUSDe Nov');
83
+ if (asset === 'PT USDe Nov') return await getApyFromDfsApi('PT USDe Nov');
83
84
  } catch (e) {
84
85
  console.error(`Failed to fetch APY for ${asset}`);
85
86
  }
@@ -0,0 +1,109 @@
1
+ import { EthAddress } from './common';
2
+
3
+ export enum ClaimType {
4
+ /** Rewards from AAVE - various tokens & aTokens (supplied to protocol) */
5
+ AAVE_REWARDS = 'AAVE_REWARDS',
6
+ /** Merit rewards from AAVE (various tokens) */
7
+ AAVE_MERIT_REWARDS = 'AAVE_MERIT_REWARDS',
8
+ /** Rewards from Compound V3 (only in COMP) */
9
+ COMPOUND_V3_COMP = 'COMPOUND_V3_COMP',
10
+ /** Rewards from Spark (wstETH only for now) */
11
+ SPARK_REWARDS = 'SPARK_REWARDS',
12
+ /** Rewards from Morpho */
13
+ MORPHO = 'MORPHO',
14
+ /** Rewards from King (prev LTR^2 - received for weETH holding) */
15
+ KING_REWARDS = 'KING_REWARDS',
16
+ /** Spark Airdrop */
17
+ SPARK_AIRDROP = 'SPARK_AIRDROP',
18
+ /** Spark Airdrop */
19
+ SPARK_WST_ETH_REWARDS = 'SPARK_WST_ETH_REWARDS', // TODO: This will be removed once we fully refactor spark rewards
20
+ }
21
+
22
+ type _ClaimableTokenPartial = {
23
+ symbol: string;
24
+ underlyingSymbol: string;
25
+ tokenAddress: EthAddress;
26
+ amount: string;
27
+ walletAddress: EthAddress;
28
+ label: string;
29
+ };
30
+ export type AaveRewardsClaimableToken = _ClaimableTokenPartial & {
31
+ claimType: ClaimType.AAVE_REWARDS;
32
+ additionalClaimFields: {
33
+ marketAddress: EthAddress;
34
+ aTokenAddresses: string[];
35
+ isAaveToken: boolean;
36
+ };
37
+ };
38
+ export type AaveMeritRewardsClaimableToken = _ClaimableTokenPartial & {
39
+ claimType: ClaimType.AAVE_MERIT_REWARDS;
40
+ additionalClaimFields: {
41
+ accumulated: string;
42
+ proof: string[];
43
+ decimals: string;
44
+ unclaimed: string;
45
+ };
46
+ };
47
+
48
+ export type SparkRewardsClaimableToken = _ClaimableTokenPartial & {
49
+ claimType: ClaimType.SPARK_REWARDS;
50
+ additionalClaimFields: {
51
+ sparkAssetAddresses: string[];
52
+ };
53
+ };
54
+
55
+ export type KingRewardsClaimableToken = _ClaimableTokenPartial & {
56
+ claimType: ClaimType.KING_REWARDS;
57
+ additionalClaimFields: {
58
+ allRewardsAmount: string;
59
+ merkleRoot: string;
60
+ merkleProofs: string[];
61
+ };
62
+ };
63
+
64
+ export type MorphoClaimableToken = _ClaimableTokenPartial & {
65
+ claimType: ClaimType.MORPHO,
66
+ additionalClaimFields: {
67
+ originalAmount: string,
68
+ merkleProofs: string[];
69
+ distributor: EthAddress;
70
+ isLegacy: boolean;
71
+ txData: string;
72
+ }
73
+ };
74
+
75
+
76
+ export type CompoundV3CompClaimableToken = _ClaimableTokenPartial & {
77
+ claimType: ClaimType.COMPOUND_V3_COMP,
78
+ additionalClaimFields: {
79
+ marketAddress: EthAddress;
80
+ }
81
+ };
82
+
83
+ export enum SparkAirdropType {
84
+ SPARK_IGNITION = 'spark-ignition',
85
+ PRE_FARMING_AND_SOCIAL = 'pre-farming-and-social',
86
+ }
87
+
88
+ export type SparkAirdropClaimableToken = _ClaimableTokenPartial & {
89
+ claimType: ClaimType.SPARK_AIRDROP,
90
+ additionalClaimFields: {
91
+ merkleRoot: string;
92
+ merkleProofs: string[];
93
+ epoch: number;
94
+ rewardType: SparkAirdropType;
95
+ allRewardsAmount: string;
96
+ };
97
+ };
98
+
99
+ export type SparkWstEthRewardsClaimableToken = _ClaimableTokenPartial & { claimType: ClaimType.SPARK_WST_ETH_REWARDS };
100
+
101
+ export type ClaimableToken =
102
+ AaveRewardsClaimableToken
103
+ | AaveMeritRewardsClaimableToken
104
+ | CompoundV3CompClaimableToken
105
+ | MorphoClaimableToken
106
+ | SparkRewardsClaimableToken
107
+ | KingRewardsClaimableToken
108
+ | SparkAirdropClaimableToken
109
+ | SparkWstEthRewardsClaimableToken;
@@ -0,0 +1,70 @@
1
+ import { Client } from 'viem';
2
+ import { AaveUmbrellaViewViem, AaveV3ViewContractViem } from '../contracts';
3
+ import { EthAddress, NetworkNumber } from '../types/common';
4
+ import { compareAddresses, convertHybridArraysToObjects, getEthAmountForDecimals } from '../services/utils';
5
+ import { findMatching, tokenEntries } from './umbrellaUtils';
6
+
7
+ const umbrella = '0xD400fc38ED4732893174325693a63C30ee3881a8';
8
+ const aaveOracle = '0x54586bE62E3c3580375aE3723C145253060Ca0C2';
9
+
10
+ export const getUmbrellaData = async (provider: Client, network: NetworkNumber, address: EthAddress) => {
11
+ const umbrellaView = AaveUmbrellaViewViem(provider, network);
12
+ const aaveV3View = AaveV3ViewContractViem(provider, network);
13
+ const [tokensAggregatedData, additionalUmbrellaStakingData, userAggregatedData] = await Promise.all([
14
+ umbrellaView.read.getTokensAggregatedData([umbrella, aaveOracle]),
15
+ aaveV3View.read.getAdditionalUmbrellaStakingData([umbrella, address]),
16
+ umbrellaView.read.getUserAggregatedData([umbrella, address]),
17
+ ]);
18
+
19
+ return Object.fromEntries(
20
+ tokenEntries.map(([symbol, tokenAddr]) => {
21
+ // @ts-ignore
22
+ const fromTokens = convertHybridArraysToObjects(findMatching(tokensAggregatedData, tokenAddr));
23
+ // @ts-ignore
24
+ const fromUser = convertHybridArraysToObjects(findMatching(userAggregatedData, tokenAddr));
25
+ // @ts-ignore
26
+ const fromAdditionalData = convertHybridArraysToObjects(findMatching(additionalUmbrellaStakingData, tokenAddr));
27
+ const data = {
28
+ ...fromTokens,
29
+ ...fromUser,
30
+ ...fromAdditionalData,
31
+ account: {
32
+ stakeUserBalance: fromUser?.stakeUserBalance || '0',
33
+ rewardsTokenUserData: convertHybridArraysToObjects(fromUser?.rewardsTokenUserData) || [],
34
+ userCooldownAmount: fromAdditionalData?.userCooldownAmount || '0',
35
+ userEndOfCooldown: fromAdditionalData?.userEndOfCooldown || '0',
36
+ userWithdrawalWindow: fromAdditionalData?.userWithdrawalWindow || '0',
37
+ },
38
+ };
39
+
40
+ const stakeTokenData = data.stakeTokenData;
41
+
42
+ return [symbol, {
43
+ ...data,
44
+ stakeTokenData: { ...stakeTokenData, price: getEthAmountForDecimals(stakeTokenData.price, 8) },
45
+ stkTokenToWaTokenRate: getEthAmountForDecimals(data.stkTokenToWaTokenRate, stakeTokenData.decimals),
46
+ waTokenToATokenRate: getEthAmountForDecimals(data.waTokenToATokenRate, stakeTokenData.decimals),
47
+ totalShares: getEthAmountForDecimals(data.totalShares, stakeTokenData.decimals),
48
+ totalAssets: getEthAmountForDecimals(data.totalAssets, stakeTokenData.decimals),
49
+ targetLiquidity: getEthAmountForDecimals(data.targetLiquidity, stakeTokenData.decimals),
50
+ rewardsTokenData: data.rewardsTokenData.map((tokenData: { rewardTokenData: { price: string | number; }; }) => ({ ...tokenData, rewardTokenData: { ...tokenData.rewardTokenData, price: getEthAmountForDecimals(tokenData.rewardTokenData.price, 8) } })),
51
+ rewardsEmissionRates: data.rewardsEmissionRates.map((rate: string, i: number) => {
52
+ const tokenData = data.rewardsTokenData[i].rewardTokenData;
53
+ return getEthAmountForDecimals(rate, tokenData.decimals);
54
+ }),
55
+ account: {
56
+ ...data.account,
57
+ stakeUserBalance: getEthAmountForDecimals(data.account.stakeUserBalance, stakeTokenData.decimals),
58
+ userCooldownAmount: getEthAmountForDecimals(data.account.userCooldownAmount, stakeTokenData.decimals),
59
+ rewardsTokenUserData: data.account.rewardsTokenUserData.map((reward: any) => {
60
+ const tokenData = data.rewardsTokenData.find((tknData: { rewardTokenData: { token: string | undefined; }; }) => compareAddresses(tknData.rewardTokenData.token, reward.reward));
61
+ return ({
62
+ ...reward,
63
+ currentReward: getEthAmountForDecimals(reward.currentReward, tokenData.rewardTokenData.decimals),
64
+ });
65
+ }),
66
+ },
67
+ }];
68
+ }),
69
+ );
70
+ };