@defisaver/positions-sdk 2.1.161-portfolio-api-1-dev → 2.1.161-portfolio-api-2-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/cjs/aaveV3/index.d.ts +2 -12
  2. package/cjs/aaveV3/sgho.d.ts +2 -16
  3. package/cjs/claiming/aaveV3.d.ts +2 -2
  4. package/cjs/claiming/ethena.d.ts +2 -1
  5. package/cjs/claiming/king.d.ts +2 -1
  6. package/cjs/claiming/spark.d.ts +2 -2
  7. package/cjs/fluid/index.d.ts +5 -49
  8. package/cjs/liquity/index.d.ts +2 -12
  9. package/cjs/liquityV2/index.d.ts +3 -23
  10. package/cjs/portfolio/index.d.ts +4 -4
  11. package/cjs/portfolio/index.js +46 -33
  12. package/cjs/types/aave.d.ts +83 -0
  13. package/cjs/types/fluid.d.ts +13 -0
  14. package/cjs/types/index.d.ts +1 -0
  15. package/cjs/types/index.js +1 -0
  16. package/cjs/types/liquity.d.ts +12 -0
  17. package/cjs/types/liquityV2.d.ts +24 -0
  18. package/cjs/types/portfolio.d.ts +68 -5
  19. package/cjs/umbrella/index.d.ts +2 -3
  20. package/esm/aaveV3/index.d.ts +2 -12
  21. package/esm/aaveV3/sgho.d.ts +2 -16
  22. package/esm/claiming/aaveV3.d.ts +2 -2
  23. package/esm/claiming/aaveV3.js +1 -1
  24. package/esm/claiming/ethena.d.ts +2 -1
  25. package/esm/claiming/king.d.ts +2 -1
  26. package/esm/claiming/spark.d.ts +2 -2
  27. package/esm/claiming/spark.js +1 -1
  28. package/esm/fluid/index.d.ts +5 -49
  29. package/esm/liquity/index.d.ts +2 -12
  30. package/esm/liquityV2/index.d.ts +3 -23
  31. package/esm/portfolio/index.d.ts +4 -4
  32. package/esm/portfolio/index.js +46 -33
  33. package/esm/types/aave.d.ts +83 -0
  34. package/esm/types/fluid.d.ts +13 -0
  35. package/esm/types/index.d.ts +1 -0
  36. package/esm/types/index.js +1 -0
  37. package/esm/types/liquity.d.ts +12 -0
  38. package/esm/types/liquityV2.d.ts +24 -0
  39. package/esm/types/portfolio.d.ts +68 -5
  40. package/esm/umbrella/index.d.ts +2 -3
  41. package/package.json +1 -1
  42. package/src/aaveV3/index.ts +2 -1
  43. package/src/aaveV3/sgho.ts +2 -17
  44. package/src/claiming/aaveV3.ts +4 -2
  45. package/src/claiming/ethena.ts +5 -5
  46. package/src/claiming/king.ts +3 -3
  47. package/src/claiming/spark.ts +6 -4
  48. package/src/fluid/index.ts +4 -3
  49. package/src/liquity/index.ts +2 -2
  50. package/src/liquityV2/index.ts +3 -3
  51. package/src/portfolio/index.ts +69 -53
  52. package/src/types/aave.ts +86 -0
  53. package/src/types/fluid.ts +14 -0
  54. package/src/types/index.ts +2 -1
  55. package/src/types/liquity.ts +13 -1
  56. package/src/types/liquityV2.ts +26 -1
  57. package/src/types/portfolio.ts +86 -5
  58. package/src/umbrella/index.ts +2 -1
@@ -27,8 +27,11 @@ import {
27
27
  LlamaLendGlobalMarketData,
28
28
  MorphoBlueMarketInfo,
29
29
  MorphoMidnightMarketInfo,
30
+ PortfolioDataOptions,
30
31
  PortfolioMarketsData,
31
32
  PortfolioPositionsData,
33
+ PortfolioRewardsData,
34
+ PortfolioStakingPositionsData,
32
35
  SparkMarketsData,
33
36
  } from '../types';
34
37
  import { _getCompoundV3AccountData, _getCompoundV3MarketsData } from '../compoundV3';
@@ -52,13 +55,18 @@ import { fetchEthenaAirdropRewards } from '../claiming/ethena';
52
55
  import { _getAaveV4AccountData, _getAaveV4SpokeData } from '../aaveV4';
53
56
  import { getUniswapRewards } from '../claiming/uniswap';
54
57
 
55
- export async function getPortfolioData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, addresses: EthAddress[], isSim = false): Promise<{
58
+ export async function getPortfolioData(provider: EthereumProvider, network: NetworkNumber, defaultProvider: EthereumProvider, addresses: EthAddress[], isSim = false, options: PortfolioDataOptions = {}): Promise<{
56
59
  positions: PortfolioPositionsData;
57
- stakingPositions: any;
58
- rewardsData: any;
60
+ stakingPositions: PortfolioStakingPositionsData;
61
+ rewardsData: PortfolioRewardsData;
59
62
  markets: PortfolioMarketsData;
60
63
  }> {
61
64
  const isMainnet = network === NetworkNumber.Eth;
65
+ const includeStaking = options.staking !== false;
66
+ const includeRewards = options.rewards !== false;
67
+ // The addresses staking and rewards are read for: none when left out, so their fetches below make no calls.
68
+ const stakingAddresses = includeStaking ? addresses : [];
69
+ const rewardsAddresses = includeRewards ? addresses : [];
62
70
  const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
63
71
 
64
72
  const morphoMarkets = Object.values(MorphoBlueMarkets(network)).filter((market) => market.chainIds.includes(network));
@@ -112,8 +120,8 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
112
120
  };
113
121
 
114
122
  const positions: PortfolioPositionsData = {};
115
- const stakingPositions: any = {};
116
- const rewardsData: any = {};
123
+ const stakingPositions: PortfolioStakingPositionsData = {};
124
+ const rewardsData: PortfolioRewardsData = {};
117
125
  const allAddresses = [...addresses];
118
126
 
119
127
  for (const address of allAddresses) {
@@ -138,7 +146,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
138
146
  }
139
147
 
140
148
  // TODO: check default values, probably needed when fetching portfolio on unsupported networks
141
- for (const address of addresses) {
149
+ for (const address of stakingAddresses) {
142
150
  stakingPositions[address.toLowerCase() as EthAddress] = {
143
151
  aaveV3: {},
144
152
  morphoBlue: {},
@@ -152,15 +160,21 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
152
160
  error: '',
153
161
  data: {},
154
162
  },
163
+ // Set by the sBOLD/yBOLD fetch below, whether it succeeds or fails.
164
+ liquityV2SBoldYBold: { error: '', data: null },
155
165
  };
166
+ }
156
167
 
168
+ for (const address of rewardsAddresses) {
169
+ // merkl, spk, king and uniswap are set by their fetches below, whether they succeed or fail.
157
170
  rewardsData[address.toLowerCase() as EthAddress] = {
158
- merkl: {},
171
+ merkl: { error: '', data: null },
159
172
  aaveV3: {},
160
173
  spark: {},
161
- spk: {},
162
- king: {},
174
+ spk: { error: '', data: null },
175
+ king: { error: '', data: null },
163
176
  ethena: {},
177
+ uniswap: { error: '', data: null },
164
178
  };
165
179
  }
166
180
 
@@ -236,54 +250,54 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
236
250
  }),
237
251
 
238
252
  // === STAKING DATA (independent of market data) ===
239
- ...addresses.map(async (address) => {
253
+ ...stakingAddresses.map(async (address) => {
240
254
  try {
241
255
  if (!isFluidSupported) return;
242
- stakingPositions[address.toLowerCase()].fluid = await _getAllUserEarnPositionsWithFTokens(client, network, address);
256
+ stakingPositions[address.toLowerCase() as EthAddress].fluid = await _getAllUserEarnPositionsWithFTokens(client, network, address);
243
257
  } catch (error) {
244
258
  console.error(`Error fetching Fluid lend data for address ${address}:`, error);
245
- stakingPositions[address.toLowerCase()].fluid = { error: `Error fetching Fluid lend data for address ${address}`, data: null };
259
+ stakingPositions[address.toLowerCase() as EthAddress].fluid = { error: `Error fetching Fluid lend data for address ${address}`, data: null };
246
260
  }
247
261
  }),
248
- ...addresses.map(async (address) => {
262
+ ...stakingAddresses.map(async (address) => {
249
263
  try {
250
264
  if (!isMainnet) return;
251
- stakingPositions[address.toLowerCase()].liquity = await getLiquityStakingData(client, network, address);
265
+ stakingPositions[address.toLowerCase() as EthAddress].liquity = await getLiquityStakingData(client, network, address);
252
266
  } catch (error) {
253
267
  console.error(`Error fetching Liquity staking data for address ${address}:`, error);
254
- stakingPositions[address.toLowerCase()].liquity = { error: `Error fetching Liquity staking data for address ${address}`, data: null };
268
+ stakingPositions[address.toLowerCase() as EthAddress].liquity = { error: `Error fetching Liquity staking data for address ${address}`, data: null };
255
269
  }
256
270
  }),
257
- ...addresses.map(async (address) => {
271
+ ...stakingAddresses.map(async (address) => {
258
272
  try {
259
273
  if (!isMainnet) return;
260
- stakingPositions[address.toLowerCase()].aaveV3 = await getStakeAaveData(client, network, address);
274
+ stakingPositions[address.toLowerCase() as EthAddress].aaveV3 = await getStakeAaveData(client, network, address);
261
275
  } catch (error) {
262
276
  console.error(`Error fetching Aave V3 staking data for address ${address}:`, error);
263
- stakingPositions[address.toLowerCase()].aaveV3 = { error: `Error fetching Aave V3 staking data for address ${address}`, data: null };
277
+ stakingPositions[address.toLowerCase() as EthAddress].aaveV3 = { error: `Error fetching Aave V3 staking data for address ${address}`, data: null };
264
278
  }
265
279
  }),
266
- ...addresses.map(async (address) => {
280
+ ...stakingAddresses.map(async (address) => {
267
281
  try {
268
282
  if (!isMainnet) return;
269
- stakingPositions[address.toLowerCase()].umbrella = await getUmbrellaData(client, network, address);
283
+ stakingPositions[address.toLowerCase() as EthAddress].umbrella = await getUmbrellaData(client, network, address);
270
284
  } catch (error) {
271
285
  console.error(`Error fetching Umbrella staking data for address ${address}:`, error);
272
- stakingPositions[address.toLowerCase()].umbrella = { error: `Error fetching Umbrella staking data for address ${address}`, data: null };
286
+ stakingPositions[address.toLowerCase() as EthAddress].umbrella = { error: `Error fetching Umbrella staking data for address ${address}`, data: null };
273
287
  }
274
288
  }),
275
289
  // Liquity V2 staking
276
- ...liquityV2MarketsStaking.map(market => addresses.map(async (address) => {
290
+ ...liquityV2MarketsStaking.map(market => stakingAddresses.map(async (address) => {
277
291
  try {
278
292
  if (!isMainnet) {
279
- stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: '', data: null };
293
+ stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: '', data: null };
280
294
  return;
281
295
  }
282
296
  const liquityV2StakingData = await getLiquityV2Staking(client, network, market.value, address);
283
- stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: '', data: liquityV2StakingData };
297
+ stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: '', data: liquityV2StakingData };
284
298
  } catch (error) {
285
299
  console.error(`Error fetching Liquity V2 staking data for address ${address}, market ${market.value}:`, error);
286
- stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: `Error fetching Liquity V2 staking data for address ${address}`, data: null };
300
+ stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: `Error fetching Liquity V2 staking data for address ${address}`, data: null };
287
301
  }
288
302
  })).flat(),
289
303
 
@@ -292,13 +306,13 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
292
306
  (async () => {
293
307
  try {
294
308
  if (!isMainnet) {
295
- for (const address of addresses) {
296
- rewardsData[address.toLowerCase()].king = { error: '', data: [] };
309
+ for (const address of rewardsAddresses) {
310
+ rewardsData[address.toLowerCase() as EthAddress].king = { error: '', data: [] };
297
311
  }
298
312
  return;
299
313
  }
300
- const kingRewards = await getKingRewards(client, network, addresses);
301
- for (const address of addresses) {
314
+ const kingRewards = await getKingRewards(client, network, rewardsAddresses);
315
+ for (const address of rewardsAddresses) {
302
316
  const lowerAddress = address.toLowerCase() as EthAddress;
303
317
  rewardsData[lowerAddress].king = {
304
318
  error: '',
@@ -307,7 +321,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
307
321
  }
308
322
  } catch (error) {
309
323
  console.error('Error fetching King rewards data in batch:', error);
310
- for (const address of addresses) {
324
+ for (const address of rewardsAddresses) {
311
325
  rewardsData[address.toLowerCase() as EthAddress].king = {
312
326
  error: 'Error fetching King rewards data in batch',
313
327
  data: null,
@@ -319,13 +333,13 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
319
333
  (async () => {
320
334
  try {
321
335
  if (!isMainnet) {
322
- for (const address of addresses) {
323
- rewardsData[address.toLowerCase()].uniswap = { error: '', data: [] };
336
+ for (const address of rewardsAddresses) {
337
+ rewardsData[address.toLowerCase() as EthAddress].uniswap = { error: '', data: [] };
324
338
  }
325
339
  return;
326
340
  }
327
- const uniswapRewards = await getUniswapRewards(client, network, addresses);
328
- for (const address of addresses) {
341
+ const uniswapRewards = await getUniswapRewards(client, network, rewardsAddresses);
342
+ for (const address of rewardsAddresses) {
329
343
  const lowerAddress = address.toLowerCase() as EthAddress;
330
344
  rewardsData[lowerAddress].uniswap = {
331
345
  error: '',
@@ -334,7 +348,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
334
348
  }
335
349
  } catch (error) {
336
350
  console.error('Error fetching Uniswap rewards data in batch:', error);
337
- for (const address of addresses) {
351
+ for (const address of rewardsAddresses) {
338
352
  rewardsData[address.toLowerCase() as EthAddress].uniswap = {
339
353
  error: 'Error fetching Uniswap rewards data in batch',
340
354
  data: null,
@@ -342,10 +356,10 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
342
356
  }
343
357
  }
344
358
  })(),
345
- ...sparkMarkets.map((market) => addresses.map(async address => {
359
+ ...sparkMarkets.map((market) => rewardsAddresses.map(async address => {
346
360
  try {
347
361
  if (!isMainnet) {
348
- rewardsData[address.toLowerCase()].spark[market.value] = { error: '', data: [] };
362
+ rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: '', data: [] };
349
363
  return;
350
364
  }
351
365
  const sparkData = await fetchSparkRewards(client, network, address, market.providerAddress);
@@ -355,7 +369,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
355
369
  rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: `Error fetching Spark rewards data for address ${address}`, data: null };
356
370
  }
357
371
  })).flat(),
358
- ...addresses.map(async (address) => {
372
+ ...rewardsAddresses.map(async (address) => {
359
373
  try {
360
374
  const merklData = await getMerklUnclaimedRewards(address, network);
361
375
  rewardsData[address.toLowerCase() as EthAddress].merkl = { error: '', data: merklData };
@@ -364,27 +378,28 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
364
378
  rewardsData[address.toLowerCase() as EthAddress].merkl = { error: `Error fetching Merkl rewards data for address ${address}`, data: null };
365
379
  }
366
380
  }),
367
- ...aaveV3Markets.map(market => addresses.map(async (address) => {
381
+ ...aaveV3Markets.map(market => rewardsAddresses.map(async (address) => {
368
382
  try {
369
383
  const aaveData = await getUnclaimedRewardsForAllMarkets(client, network, address, market.providerAddress);
370
384
  rewardsData[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: '', data: aaveData };
371
385
  } catch (error) {
372
386
  console.error(`Error fetching Aave V3 Merit rewards data for address ${address}:`, error);
373
- rewardsData[address.toLowerCase() as EthAddress].aaveV3 = { error: `Error fetching Aave V3 rewards data for address ${address}`, data: null };
387
+ // This market only: the other markets keep their rewards.
388
+ 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
389
  }
375
390
  })).flat(),
376
391
  // Batch Spark Airdrop rewards
377
392
  (async () => {
378
393
  try {
379
394
  if (!isMainnet) {
380
- for (const address of addresses) {
381
- rewardsData[address.toLowerCase()].spk = { error: '', data: [] };
395
+ for (const address of rewardsAddresses) {
396
+ rewardsData[address.toLowerCase() as EthAddress].spk = { error: '', data: [] };
382
397
  }
383
398
  return;
384
399
  }
385
400
 
386
- const sparkAirdropRewards = await fetchSparkAirdropRewards(client, network, addresses);
387
- for (const address of addresses) {
401
+ const sparkAirdropRewards = await fetchSparkAirdropRewards(client, network, rewardsAddresses);
402
+ for (const address of rewardsAddresses) {
388
403
  const lowerAddress = address.toLowerCase() as EthAddress;
389
404
  rewardsData[lowerAddress].spk = {
390
405
  error: '',
@@ -393,7 +408,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
393
408
  }
394
409
  } catch (error) {
395
410
  console.error('Error fetching Spark Airdrop rewards data in batch:', error);
396
- for (const address of addresses) {
411
+ for (const address of rewardsAddresses) {
397
412
  rewardsData[address.toLowerCase() as EthAddress].spk = {
398
413
  error: 'Error fetching Spark Airdrop rewards data in batch',
399
414
  data: null,
@@ -407,8 +422,8 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
407
422
  return;
408
423
  }
409
424
 
410
- const ethenaAirdropRewards = await fetchEthenaAirdropRewards(addresses);
411
- for (const address of addresses) {
425
+ const ethenaAirdropRewards = await fetchEthenaAirdropRewards(rewardsAddresses);
426
+ for (const address of rewardsAddresses) {
412
427
  const lowerAddress = address.toLowerCase() as EthAddress;
413
428
  rewardsData[lowerAddress].ethena = {
414
429
  error: '',
@@ -417,7 +432,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
417
432
  }
418
433
  } catch (error) {
419
434
  console.error('Error fetching Ethena Airdrop rewards data:', error);
420
- for (const address of addresses) {
435
+ for (const address of rewardsAddresses) {
421
436
  rewardsData[address.toLowerCase() as EthAddress].ethena = {
422
437
  error: 'Error fetching Ethena Airdrop rewards data in batch',
423
438
  data: null,
@@ -450,15 +465,16 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
450
465
  try {
451
466
  const [accDataPromise, earnDataPromise] = await Promise.allSettled([
452
467
  _getMorphoBlueAccountData(client, network, address, market, morphoMarketsData[market.value]),
453
- getMorphoEarn(client, network, address, market, morphoMarketsData[market.value]),
468
+ includeStaking ? getMorphoEarn(client, network, address, market, morphoMarketsData[market.value]) : null,
454
469
  ]);
455
470
  if (accDataPromise.status === 'rejected') {
456
471
  console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, accDataPromise.reason);
457
472
  positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
458
473
  }
459
474
  if (earnDataPromise.status === 'rejected') {
460
- console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, earnDataPromise.reason);
461
- positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
475
+ // An Earn failure belongs to the staking entry: the lending position is not the one that failed.
476
+ console.error(`Error fetching MorphoBlue earn data for address ${address} on market ${market.value}:`, earnDataPromise.reason);
477
+ stakingPositions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue earn data for address ${address} on market ${market.value}`, data: null };
462
478
  }
463
479
  if (accDataPromise.status !== 'rejected') {
464
480
  const accData = accDataPromise.value;
@@ -571,13 +587,13 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
571
587
  }
572
588
  })).flat(),
573
589
  // liquity sBold/yBold and staking options
574
- ...addresses.map(async (address) => {
590
+ ...stakingAddresses.map(async (address) => {
575
591
  try {
576
592
  if (!isMainnet) {
577
593
  stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data: null };
578
594
  return;
579
595
  }
580
- const data = await getLiquitySAndYBold(client, network, stakingPositions[address.toLowerCase()].liquityV2, address);
596
+ const data = await getLiquitySAndYBold(client, network, stakingPositions[address.toLowerCase() as EthAddress].liquityV2, address);
581
597
  stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data };
582
598
  } catch (error) {
583
599
  console.error(`Error fetching SBold/YBold data for address ${address}:`, error);
package/src/types/aave.ts CHANGED
@@ -219,3 +219,89 @@ export interface AaveHelperCommon {
219
219
  selectedMarket: Partial<AaveMarketInfo>,
220
220
  network?: NetworkNumber,
221
221
  }
222
+
223
+ export interface SghoUserData {
224
+ shares: string;
225
+ balance: string;
226
+ maxDeposit: string;
227
+ maxWithdraw: string;
228
+ underlyingBalance: string;
229
+ }
230
+
231
+ export interface SghoData {
232
+ totalAssets: string;
233
+ totalSupply: string;
234
+ supplyCap: string;
235
+ /** Target savings rate as an APY percent (e.g. "4.25" for 4.25%). */
236
+ targetRate: string;
237
+ paused: boolean;
238
+ user: SghoUserData;
239
+ }
240
+
241
+ /** stkAAVE, stkGHO and sGHO of an address, from getStakeAaveData. */
242
+ export interface StakeAaveData {
243
+ activatedCooldown: string,
244
+ activatedCooldownAmount: string,
245
+ stkAaveRewardsBalance: string,
246
+ aaveRewardsBalance: string,
247
+ stkAaveBalance: string,
248
+ stkGhoBalance: string,
249
+ ghoMeritApy: string,
250
+ stkAaveApy: string,
251
+ sgho: SghoData,
252
+ }
253
+
254
+ export interface UmbrellaTokenInfo {
255
+ token: EthAddress,
256
+ /** USD, converted from the oracle's 8 decimals. */
257
+ price: string,
258
+ name: string,
259
+ symbol: string,
260
+ decimals: number,
261
+ }
262
+
263
+ export interface UmbrellaRewardTokenData {
264
+ rewardTokenData: UmbrellaTokenInfo,
265
+ maxEmissionPerSecond: bigint,
266
+ distributionEnd: bigint,
267
+ }
268
+
269
+ export interface UmbrellaStakingAccountData {
270
+ stakeUserBalance: string,
271
+ rewardsTokenUserData: { reward: EthAddress, currentReward: string }[],
272
+ userCooldownAmount: string,
273
+ // Passed through unconverted: the view's bigint, or '0' when the view returns none.
274
+ userEndOfCooldown: bigint | string,
275
+ userWithdrawalWindow: bigint | string,
276
+ }
277
+
278
+ /**
279
+ * One Umbrella stake token of an address, from getUmbrellaData: the view contracts' structs merged, with the amounts
280
+ * the app reads converted to decimal strings. The bigint fields are the views' raw values, as the SDK passes them.
281
+ */
282
+ export interface UmbrellaStakingTokenData {
283
+ stakeTokenData: UmbrellaTokenInfo,
284
+ totalAssets: string,
285
+ targetLiquidity: string,
286
+ isStakeConfigured: boolean,
287
+ rewardsTokenData: UmbrellaRewardTokenData[],
288
+ stakeToken: EthAddress,
289
+ stakeUserBalance: bigint,
290
+ rewardsTokenUserData: { reward: EthAddress, currentReward: bigint }[],
291
+ stkToken: EthAddress,
292
+ totalShares: string,
293
+ stkUnderlyingToken: EthAddress,
294
+ aToken: EthAddress,
295
+ cooldownPeriod: bigint,
296
+ unstakeWindow: bigint,
297
+ stkTokenToWaTokenRate: string,
298
+ waTokenToATokenRate: string,
299
+ rewardsEmissionRates: string[],
300
+ userCooldownAmount: bigint,
301
+ userEndOfCooldown: bigint,
302
+ userWithdrawalWindow: bigint,
303
+ account: UmbrellaStakingAccountData,
304
+ }
305
+
306
+ /** Keyed by Umbrella stake (UmbrellaGHO, UmbrellaUSDC, UmbrellaUSDT, UmbrellaETH). */
307
+ export type UmbrellaStakingData = Record<string, UmbrellaStakingTokenData>;
@@ -487,3 +487,17 @@ export interface FluidFTokenDataStructOutput {
487
487
  rewardsRate: bigint;
488
488
  withdrawable: bigint;
489
489
  }
490
+
491
+ /** One Fluid lending (fToken) deposit of an address, from getAllUserEarnPositionsWithFTokens and getFluidDepositData. */
492
+ export interface FluidEarnPositionData {
493
+ fTokenAddress: string | undefined,
494
+ fTokenSymbol: string,
495
+ decimals: string,
496
+ totalDeposited: string,
497
+ withdrawable: string,
498
+ apy: string,
499
+ depositRate: string,
500
+ withdrawRate: string,
501
+ deposited: string,
502
+ depositedShares: string,
503
+ }
@@ -14,4 +14,5 @@ export * from './merit';
14
14
  export * from './merkl';
15
15
  export * from './savings';
16
16
  export * from './common';
17
- export * from './aaveV4';
17
+ export * from './aaveV4';
18
+ export * from './claiming';
@@ -29,4 +29,16 @@ export interface LiquityTroveInfo {
29
29
  priceForRecovery: string,
30
30
  debtInFront: string,
31
31
  exposure: string,
32
- }
32
+ }
33
+ /** LQTY staking and the LUSD stability pool deposit of an address, from getLiquityStakingData. */
34
+ export interface LiquityStakingData {
35
+ totalLUSDDeposited: string,
36
+ totalLQTYStaked: string,
37
+ stakedLQTY: string,
38
+ stakedLUSDBalance: string,
39
+ rewardETH: string,
40
+ rewardLUSD: string,
41
+ stabilityRewardETH: string,
42
+ stabilityRewardLQTY: string,
43
+ showStakingBalances: boolean,
44
+ }
@@ -131,4 +131,29 @@ export interface LiquityV2TroveData {
131
131
  debtInFront: string,
132
132
  lastInterestRateAdjTime: string,
133
133
  exposure: string,
134
- }
134
+ }
135
+ /** The stability pool deposit of an address in one market, from getLiquityV2Staking. */
136
+ export interface LiquityV2StakingData {
137
+ totalBOLDDeposited: string,
138
+ stakedBOLDBalance: string,
139
+ stabilityRewardColl: string,
140
+ stabilityRewardYield: string,
141
+ showStakingBalances: boolean,
142
+ debtTokenBalance: string,
143
+ stabilityPoolApy: string,
144
+ }
145
+
146
+ /** sBOLD, yBOLD and stYBOLD of an address, from getLiquitySAndYBold. */
147
+ export interface LiquityV2SBoldYBoldData {
148
+ spApy: string,
149
+ yBoldApy: string,
150
+ totalBoldDepositedSBold: string,
151
+ boldRateSBold: string,
152
+ maxWithdrawSBold: string,
153
+ totalBoldDepositedYBold: string,
154
+ boldRateYBold: string,
155
+ maxWithdrawYBold: string,
156
+ sBoldBalance: string,
157
+ yBoldBalance: string,
158
+ stYBoldBalance: string,
159
+ }
@@ -1,18 +1,31 @@
1
1
  import {
2
- AaveV2MarketData, AaveV2PositionData, AaveV3MarketData, AaveV3PositionData, AaveVersions,
2
+ AaveV2MarketData, AaveV2PositionData, AaveV3MarketData, AaveV3PositionData, AaveVersions, StakeAaveData, UmbrellaStakingData,
3
3
  } from './aave';
4
4
  import { AaveV4AccountData, AaveV4SpokeData, AaveV4SpokesType } from './aaveV4';
5
+ import {
6
+ AaveRewardsClaimableToken,
7
+ EthenaAirdropClaimableToken,
8
+ KingRewardsClaimableToken,
9
+ MerklRewardsClaimableToken,
10
+ SparkAirdropClaimableToken,
11
+ SparkRewardsClaimableToken,
12
+ UniswapAirdropClaimableToken,
13
+ } from './claiming';
5
14
  import { EthAddress } from './common';
6
15
  import {
7
16
  CompoundV2MarketsData, CompoundV2PositionData, CompoundV3MarketsData, CompoundV3PositionData, CompoundVersions,
8
17
  } from './compound';
9
18
  import { CrvUSDGlobalMarketData, CrvUSDUserData, CrvUSDVersions } from './curveUsd';
10
- import { FluidVaultData } from './fluid';
11
- import { LiquityTroveInfo } from './liquity';
12
- import { LiquityV2MarketData, LiquityV2TroveData, LiquityV2Versions } from './liquityV2';
19
+ import { FluidEarnPositionData, FluidVaultData } from './fluid';
20
+ import { LiquityStakingData, LiquityTroveInfo } from './liquity';
21
+ import {
22
+ LiquityV2MarketData, LiquityV2SBoldYBoldData, LiquityV2StakingData, LiquityV2TroveData, LiquityV2Versions,
23
+ } from './liquityV2';
13
24
  import { LlamaLendGlobalMarketData, LlamaLendUserData, LlamaLendVersionsType } from './llamaLend';
14
25
  import { CdpData } from './maker';
15
- import { MorphoBlueMarketInfo, MorphoBluePositionData, MorphoBlueVersions } from './morphoBlue';
26
+ import {
27
+ MorphoBlueEarnData, MorphoBlueMarketInfo, MorphoBluePositionData, MorphoBlueVersions,
28
+ } from './morphoBlue';
16
29
  import { MorphoMidnightMarketInfo, MorphoMidnightPositionData, MorphoMidnightVersions } from './morphoMidnight';
17
30
  import { SparkMarketsData, SparkPositionData, SparkVersions } from './spark';
18
31
 
@@ -21,6 +34,20 @@ export interface PortfolioProtocolData<T> {
21
34
  data: T | null,
22
35
  }
23
36
 
37
+ /** A failed fetch of a protocol that, when it succeeds, getPortfolioData returns unwrapped rather than as `{ error, data }`. */
38
+ export interface PortfolioProtocolError {
39
+ error: string,
40
+ data: null,
41
+ }
42
+
43
+ /** What getPortfolioData reads besides positions and markets. Both default to true. */
44
+ export interface PortfolioDataOptions {
45
+ /** stakingPositions; with false it comes back as {} and no staking source is read. */
46
+ staking?: boolean,
47
+ /** rewardsData; with false it comes back as {} and no rewards source is read. */
48
+ rewards?: boolean,
49
+ }
50
+
24
51
  export interface PortfolioPositionsDataForAddress {
25
52
  aaveV3: {
26
53
  [key in AaveVersions]?: PortfolioProtocolData<AaveV3PositionData>;
@@ -68,6 +95,60 @@ export interface PortfolioPositionsData {
68
95
  [key: EthAddress]: PortfolioPositionsDataForAddress;
69
96
  }
70
97
 
98
+ /**
99
+ * The staking positions getPortfolioData returns for an address. The shapes are uneven, and the DeFi Saver app
100
+ * parses them as they are: a protocol read once per address comes back unwrapped on success, as
101
+ * `{ error, data: null }` on failure, and as {} (or Fluid's `{ error: '', data: {} }`) on a network it is not read on.
102
+ */
103
+ export interface PortfolioStakingPositionsDataForAddress {
104
+ /** stkAAVE, stkGHO and sGHO; mainnet only. */
105
+ aaveV3: StakeAaveData | PortfolioProtocolError | {};
106
+ /** Morpho Blue supply without a borrow (Earn), by market; only markets with a deposit or an error. */
107
+ morphoBlue: {
108
+ [key in MorphoBlueVersions]?: PortfolioProtocolData<MorphoBlueEarnData>;
109
+ };
110
+ // Never filled; kept so the shape does not change under the app.
111
+ compoundV3: Record<string, never>;
112
+ spark: Record<string, never>;
113
+ aaveV2: Record<string, never>;
114
+ compoundV2: Record<string, never>;
115
+ /** LQTY staking and the LUSD stability pool; mainnet only. */
116
+ liquity: LiquityStakingData | PortfolioProtocolError | {};
117
+ /** Stability pool deposits, by market; mainnet only, legacy markets left out. */
118
+ liquityV2: {
119
+ [key in LiquityV2Versions]?: PortfolioProtocolData<LiquityV2StakingData>;
120
+ };
121
+ /** Fluid lending (fToken) deposits; `{ error: '', data: {} }` on a network without Fluid. */
122
+ fluid: FluidEarnPositionData[] | PortfolioProtocolError | { error: string, data: Record<string, never> };
123
+ /** Umbrella stakes; absent off mainnet. */
124
+ umbrella?: UmbrellaStakingData | PortfolioProtocolError;
125
+ /** sBOLD, yBOLD and stYBOLD; data is null off mainnet. */
126
+ liquityV2SBoldYBold: PortfolioProtocolData<LiquityV2SBoldYBoldData>;
127
+ }
128
+
129
+ // A Record rather than an interface: the app casts getPortfolioData's result to its own Record<string, …> type, and an
130
+ // interface with an address index signature is not comparable to one.
131
+ export type PortfolioStakingPositionsData = Record<EthAddress, PortfolioStakingPositionsDataForAddress>;
132
+
133
+ /** The claimable rewards getPortfolioData returns for an address. Off mainnet only Merkl and Aave V3 are read. */
134
+ export interface PortfolioRewardsDataForAddress {
135
+ merkl: PortfolioProtocolData<MerklRewardsClaimableToken[]>;
136
+ aaveV3: {
137
+ [key in AaveVersions]?: PortfolioProtocolData<AaveRewardsClaimableToken[]>;
138
+ };
139
+ spark: {
140
+ [key in SparkVersions]?: PortfolioProtocolData<SparkRewardsClaimableToken[]>;
141
+ };
142
+ /** The SPK airdrop. */
143
+ spk: PortfolioProtocolData<SparkAirdropClaimableToken[]>;
144
+ king: PortfolioProtocolData<KingRewardsClaimableToken[]>;
145
+ /** {} off mainnet. */
146
+ ethena: PortfolioProtocolData<EthenaAirdropClaimableToken[]> | {};
147
+ uniswap: PortfolioProtocolData<UniswapAirdropClaimableToken[]>;
148
+ }
149
+
150
+ export type PortfolioRewardsData = Record<EthAddress, PortfolioRewardsDataForAddress>;
151
+
71
152
  /** The `markets` getPortfolioData returns: every market it read, by protocol, keyed by market or spoke. */
72
153
  export interface PortfolioMarketsData {
73
154
  morphoMarketsData: Record<string, MorphoBlueMarketInfo>;
@@ -1,13 +1,14 @@
1
1
  import { Client } from 'viem';
2
2
  import { AaveUmbrellaViewViem, AaveV3ViewContractViem } from '../contracts';
3
3
  import { EthAddress, NetworkNumber } from '../types/common';
4
+ import { UmbrellaStakingData } from '../types/aave';
4
5
  import { compareAddresses, convertHybridArraysToObjects, getEthAmountForDecimals } from '../services/utils';
5
6
  import { findMatching, tokenEntries } from './umbrellaUtils';
6
7
 
7
8
  const umbrella = '0xD400fc38ED4732893174325693a63C30ee3881a8';
8
9
  const aaveOracle = '0x54586bE62E3c3580375aE3723C145253060Ca0C2';
9
10
 
10
- export const getUmbrellaData = async (provider: Client, network: NetworkNumber, address: EthAddress) => {
11
+ export const getUmbrellaData = async (provider: Client, network: NetworkNumber, address: EthAddress): Promise<UmbrellaStakingData> => {
11
12
  const umbrellaView = AaveUmbrellaViewViem(provider, network);
12
13
  const aaveV3View = AaveV3ViewContractViem(provider, network);
13
14
  const [tokensAggregatedData, additionalUmbrellaStakingData, userAggregatedData] = await Promise.all([