@defisaver/positions-sdk 2.1.161-portfolio-api-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 +5 -5
  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 +88 -10
  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 +5 -5
  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 +88 -10
  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 +72 -55
  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 +111 -11
  58. package/src/umbrella/index.ts +2 -1
@@ -27,7 +27,11 @@ import {
27
27
  LlamaLendGlobalMarketData,
28
28
  MorphoBlueMarketInfo,
29
29
  MorphoMidnightMarketInfo,
30
+ PortfolioDataOptions,
31
+ PortfolioMarketsData,
30
32
  PortfolioPositionsData,
33
+ PortfolioRewardsData,
34
+ PortfolioStakingPositionsData,
31
35
  SparkMarketsData,
32
36
  } from '../types';
33
37
  import { _getCompoundV3AccountData, _getCompoundV3MarketsData } from '../compoundV3';
@@ -51,13 +55,18 @@ import { fetchEthenaAirdropRewards } from '../claiming/ethena';
51
55
  import { _getAaveV4AccountData, _getAaveV4SpokeData } from '../aaveV4';
52
56
  import { getUniswapRewards } from '../claiming/uniswap';
53
57
 
54
- 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<{
55
59
  positions: PortfolioPositionsData;
56
- stakingPositions: any;
57
- rewardsData: any;
58
- markets: any;
60
+ stakingPositions: PortfolioStakingPositionsData;
61
+ rewardsData: PortfolioRewardsData;
62
+ markets: PortfolioMarketsData;
59
63
  }> {
60
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 : [];
61
70
  const isFluidSupported = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Base, NetworkNumber.Plasma].includes(network);
62
71
 
63
72
  const morphoMarkets = Object.values(MorphoBlueMarkets(network)).filter((market) => market.chainIds.includes(network));
@@ -96,7 +105,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
96
105
  const liquityV2MarketsData: Record<string, LiquityV2MarketData> = {};
97
106
  const aaveV4SpokesData: Record<string, AaveV4SpokeData> = {};
98
107
 
99
- const markets = {
108
+ const markets: PortfolioMarketsData = {
100
109
  morphoMarketsData,
101
110
  morphoMidnightMarketsData,
102
111
  compoundV3MarketsData,
@@ -111,8 +120,8 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
111
120
  };
112
121
 
113
122
  const positions: PortfolioPositionsData = {};
114
- const stakingPositions: any = {};
115
- const rewardsData: any = {};
123
+ const stakingPositions: PortfolioStakingPositionsData = {};
124
+ const rewardsData: PortfolioRewardsData = {};
116
125
  const allAddresses = [...addresses];
117
126
 
118
127
  for (const address of allAddresses) {
@@ -137,7 +146,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
137
146
  }
138
147
 
139
148
  // TODO: check default values, probably needed when fetching portfolio on unsupported networks
140
- for (const address of addresses) {
149
+ for (const address of stakingAddresses) {
141
150
  stakingPositions[address.toLowerCase() as EthAddress] = {
142
151
  aaveV3: {},
143
152
  morphoBlue: {},
@@ -151,15 +160,21 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
151
160
  error: '',
152
161
  data: {},
153
162
  },
163
+ // Set by the sBOLD/yBOLD fetch below, whether it succeeds or fails.
164
+ liquityV2SBoldYBold: { error: '', data: null },
154
165
  };
166
+ }
155
167
 
168
+ for (const address of rewardsAddresses) {
169
+ // merkl, spk, king and uniswap are set by their fetches below, whether they succeed or fail.
156
170
  rewardsData[address.toLowerCase() as EthAddress] = {
157
- merkl: {},
171
+ merkl: { error: '', data: null },
158
172
  aaveV3: {},
159
173
  spark: {},
160
- spk: {},
161
- king: {},
174
+ spk: { error: '', data: null },
175
+ king: { error: '', data: null },
162
176
  ethena: {},
177
+ uniswap: { error: '', data: null },
163
178
  };
164
179
  }
165
180
 
@@ -235,54 +250,54 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
235
250
  }),
236
251
 
237
252
  // === STAKING DATA (independent of market data) ===
238
- ...addresses.map(async (address) => {
253
+ ...stakingAddresses.map(async (address) => {
239
254
  try {
240
255
  if (!isFluidSupported) return;
241
- stakingPositions[address.toLowerCase()].fluid = await _getAllUserEarnPositionsWithFTokens(client, network, address);
256
+ stakingPositions[address.toLowerCase() as EthAddress].fluid = await _getAllUserEarnPositionsWithFTokens(client, network, address);
242
257
  } catch (error) {
243
258
  console.error(`Error fetching Fluid lend data for address ${address}:`, error);
244
- 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 };
245
260
  }
246
261
  }),
247
- ...addresses.map(async (address) => {
262
+ ...stakingAddresses.map(async (address) => {
248
263
  try {
249
264
  if (!isMainnet) return;
250
- stakingPositions[address.toLowerCase()].liquity = await getLiquityStakingData(client, network, address);
265
+ stakingPositions[address.toLowerCase() as EthAddress].liquity = await getLiquityStakingData(client, network, address);
251
266
  } catch (error) {
252
267
  console.error(`Error fetching Liquity staking data for address ${address}:`, error);
253
- 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 };
254
269
  }
255
270
  }),
256
- ...addresses.map(async (address) => {
271
+ ...stakingAddresses.map(async (address) => {
257
272
  try {
258
273
  if (!isMainnet) return;
259
- stakingPositions[address.toLowerCase()].aaveV3 = await getStakeAaveData(client, network, address);
274
+ stakingPositions[address.toLowerCase() as EthAddress].aaveV3 = await getStakeAaveData(client, network, address);
260
275
  } catch (error) {
261
276
  console.error(`Error fetching Aave V3 staking data for address ${address}:`, error);
262
- 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 };
263
278
  }
264
279
  }),
265
- ...addresses.map(async (address) => {
280
+ ...stakingAddresses.map(async (address) => {
266
281
  try {
267
282
  if (!isMainnet) return;
268
- stakingPositions[address.toLowerCase()].umbrella = await getUmbrellaData(client, network, address);
283
+ stakingPositions[address.toLowerCase() as EthAddress].umbrella = await getUmbrellaData(client, network, address);
269
284
  } catch (error) {
270
285
  console.error(`Error fetching Umbrella staking data for address ${address}:`, error);
271
- 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 };
272
287
  }
273
288
  }),
274
289
  // Liquity V2 staking
275
- ...liquityV2MarketsStaking.map(market => addresses.map(async (address) => {
290
+ ...liquityV2MarketsStaking.map(market => stakingAddresses.map(async (address) => {
276
291
  try {
277
292
  if (!isMainnet) {
278
- stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: '', data: null };
293
+ stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: '', data: null };
279
294
  return;
280
295
  }
281
296
  const liquityV2StakingData = await getLiquityV2Staking(client, network, market.value, address);
282
- stakingPositions[address.toLowerCase()].liquityV2[market.value] = { error: '', data: liquityV2StakingData };
297
+ stakingPositions[address.toLowerCase() as EthAddress].liquityV2[market.value] = { error: '', data: liquityV2StakingData };
283
298
  } catch (error) {
284
299
  console.error(`Error fetching Liquity V2 staking data for address ${address}, market ${market.value}:`, error);
285
- 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 };
286
301
  }
287
302
  })).flat(),
288
303
 
@@ -291,13 +306,13 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
291
306
  (async () => {
292
307
  try {
293
308
  if (!isMainnet) {
294
- for (const address of addresses) {
295
- rewardsData[address.toLowerCase()].king = { error: '', data: [] };
309
+ for (const address of rewardsAddresses) {
310
+ rewardsData[address.toLowerCase() as EthAddress].king = { error: '', data: [] };
296
311
  }
297
312
  return;
298
313
  }
299
- const kingRewards = await getKingRewards(client, network, addresses);
300
- for (const address of addresses) {
314
+ const kingRewards = await getKingRewards(client, network, rewardsAddresses);
315
+ for (const address of rewardsAddresses) {
301
316
  const lowerAddress = address.toLowerCase() as EthAddress;
302
317
  rewardsData[lowerAddress].king = {
303
318
  error: '',
@@ -306,7 +321,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
306
321
  }
307
322
  } catch (error) {
308
323
  console.error('Error fetching King rewards data in batch:', error);
309
- for (const address of addresses) {
324
+ for (const address of rewardsAddresses) {
310
325
  rewardsData[address.toLowerCase() as EthAddress].king = {
311
326
  error: 'Error fetching King rewards data in batch',
312
327
  data: null,
@@ -318,13 +333,13 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
318
333
  (async () => {
319
334
  try {
320
335
  if (!isMainnet) {
321
- for (const address of addresses) {
322
- rewardsData[address.toLowerCase()].uniswap = { error: '', data: [] };
336
+ for (const address of rewardsAddresses) {
337
+ rewardsData[address.toLowerCase() as EthAddress].uniswap = { error: '', data: [] };
323
338
  }
324
339
  return;
325
340
  }
326
- const uniswapRewards = await getUniswapRewards(client, network, addresses);
327
- for (const address of addresses) {
341
+ const uniswapRewards = await getUniswapRewards(client, network, rewardsAddresses);
342
+ for (const address of rewardsAddresses) {
328
343
  const lowerAddress = address.toLowerCase() as EthAddress;
329
344
  rewardsData[lowerAddress].uniswap = {
330
345
  error: '',
@@ -333,7 +348,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
333
348
  }
334
349
  } catch (error) {
335
350
  console.error('Error fetching Uniswap rewards data in batch:', error);
336
- for (const address of addresses) {
351
+ for (const address of rewardsAddresses) {
337
352
  rewardsData[address.toLowerCase() as EthAddress].uniswap = {
338
353
  error: 'Error fetching Uniswap rewards data in batch',
339
354
  data: null,
@@ -341,10 +356,10 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
341
356
  }
342
357
  }
343
358
  })(),
344
- ...sparkMarkets.map((market) => addresses.map(async address => {
359
+ ...sparkMarkets.map((market) => rewardsAddresses.map(async address => {
345
360
  try {
346
361
  if (!isMainnet) {
347
- rewardsData[address.toLowerCase()].spark[market.value] = { error: '', data: [] };
362
+ rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: '', data: [] };
348
363
  return;
349
364
  }
350
365
  const sparkData = await fetchSparkRewards(client, network, address, market.providerAddress);
@@ -354,7 +369,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
354
369
  rewardsData[address.toLowerCase() as EthAddress].spark[market.value] = { error: `Error fetching Spark rewards data for address ${address}`, data: null };
355
370
  }
356
371
  })).flat(),
357
- ...addresses.map(async (address) => {
372
+ ...rewardsAddresses.map(async (address) => {
358
373
  try {
359
374
  const merklData = await getMerklUnclaimedRewards(address, network);
360
375
  rewardsData[address.toLowerCase() as EthAddress].merkl = { error: '', data: merklData };
@@ -363,27 +378,28 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
363
378
  rewardsData[address.toLowerCase() as EthAddress].merkl = { error: `Error fetching Merkl rewards data for address ${address}`, data: null };
364
379
  }
365
380
  }),
366
- ...aaveV3Markets.map(market => addresses.map(async (address) => {
381
+ ...aaveV3Markets.map(market => rewardsAddresses.map(async (address) => {
367
382
  try {
368
383
  const aaveData = await getUnclaimedRewardsForAllMarkets(client, network, address, market.providerAddress);
369
384
  rewardsData[address.toLowerCase() as EthAddress].aaveV3[market.value] = { error: '', data: aaveData };
370
385
  } catch (error) {
371
386
  console.error(`Error fetching Aave V3 Merit rewards data for address ${address}:`, error);
372
- 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 };
373
389
  }
374
390
  })).flat(),
375
391
  // Batch Spark Airdrop rewards
376
392
  (async () => {
377
393
  try {
378
394
  if (!isMainnet) {
379
- for (const address of addresses) {
380
- rewardsData[address.toLowerCase()].spk = { error: '', data: [] };
395
+ for (const address of rewardsAddresses) {
396
+ rewardsData[address.toLowerCase() as EthAddress].spk = { error: '', data: [] };
381
397
  }
382
398
  return;
383
399
  }
384
400
 
385
- const sparkAirdropRewards = await fetchSparkAirdropRewards(client, network, addresses);
386
- for (const address of addresses) {
401
+ const sparkAirdropRewards = await fetchSparkAirdropRewards(client, network, rewardsAddresses);
402
+ for (const address of rewardsAddresses) {
387
403
  const lowerAddress = address.toLowerCase() as EthAddress;
388
404
  rewardsData[lowerAddress].spk = {
389
405
  error: '',
@@ -392,7 +408,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
392
408
  }
393
409
  } catch (error) {
394
410
  console.error('Error fetching Spark Airdrop rewards data in batch:', error);
395
- for (const address of addresses) {
411
+ for (const address of rewardsAddresses) {
396
412
  rewardsData[address.toLowerCase() as EthAddress].spk = {
397
413
  error: 'Error fetching Spark Airdrop rewards data in batch',
398
414
  data: null,
@@ -406,8 +422,8 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
406
422
  return;
407
423
  }
408
424
 
409
- const ethenaAirdropRewards = await fetchEthenaAirdropRewards(addresses);
410
- for (const address of addresses) {
425
+ const ethenaAirdropRewards = await fetchEthenaAirdropRewards(rewardsAddresses);
426
+ for (const address of rewardsAddresses) {
411
427
  const lowerAddress = address.toLowerCase() as EthAddress;
412
428
  rewardsData[lowerAddress].ethena = {
413
429
  error: '',
@@ -416,7 +432,7 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
416
432
  }
417
433
  } catch (error) {
418
434
  console.error('Error fetching Ethena Airdrop rewards data:', error);
419
- for (const address of addresses) {
435
+ for (const address of rewardsAddresses) {
420
436
  rewardsData[address.toLowerCase() as EthAddress].ethena = {
421
437
  error: 'Error fetching Ethena Airdrop rewards data in batch',
422
438
  data: null,
@@ -449,15 +465,16 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
449
465
  try {
450
466
  const [accDataPromise, earnDataPromise] = await Promise.allSettled([
451
467
  _getMorphoBlueAccountData(client, network, address, market, morphoMarketsData[market.value]),
452
- getMorphoEarn(client, network, address, market, morphoMarketsData[market.value]),
468
+ includeStaking ? getMorphoEarn(client, network, address, market, morphoMarketsData[market.value]) : null,
453
469
  ]);
454
470
  if (accDataPromise.status === 'rejected') {
455
471
  console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, accDataPromise.reason);
456
472
  positions[address.toLowerCase() as EthAddress].morphoBlue[market.value] = { error: `Error fetching MorphoBlue account data for address ${address} on market ${market.value}`, data: null };
457
473
  }
458
474
  if (earnDataPromise.status === 'rejected') {
459
- console.error(`Error fetching MorphoBlue account data for address ${address} on market ${market.value}:`, earnDataPromise.reason);
460
- 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 };
461
478
  }
462
479
  if (accDataPromise.status !== 'rejected') {
463
480
  const accData = accDataPromise.value;
@@ -570,13 +587,13 @@ export async function getPortfolioData(provider: EthereumProvider, network: Netw
570
587
  }
571
588
  })).flat(),
572
589
  // liquity sBold/yBold and staking options
573
- ...addresses.map(async (address) => {
590
+ ...stakingAddresses.map(async (address) => {
574
591
  try {
575
592
  if (!isMainnet) {
576
593
  stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data: null };
577
594
  return;
578
595
  }
579
- 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);
580
597
  stakingPositions[address.toLowerCase() as EthAddress].liquityV2SBoldYBold = { error: '', data };
581
598
  } catch (error) {
582
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,22 +1,53 @@
1
- import { AaveV2PositionData, AaveV3PositionData, AaveVersions } from './aave';
2
- import { AaveV4AccountData, AaveV4SpokesType } from './aaveV4';
1
+ import {
2
+ AaveV2MarketData, AaveV2PositionData, AaveV3MarketData, AaveV3PositionData, AaveVersions, StakeAaveData, UmbrellaStakingData,
3
+ } from './aave';
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';
3
14
  import { EthAddress } from './common';
4
- import { CompoundV2PositionData, CompoundV3PositionData, CompoundVersions } from './compound';
5
- import { CrvUSDUserData, CrvUSDVersions } from './curveUsd';
6
- import { FluidVaultData } from './fluid';
7
- import { LiquityTroveInfo } from './liquity';
8
- import { LiquityV2TroveData, LiquityV2Versions } from './liquityV2';
9
- import { LlamaLendUserData, LlamaLendVersionsType } from './llamaLend';
15
+ import {
16
+ CompoundV2MarketsData, CompoundV2PositionData, CompoundV3MarketsData, CompoundV3PositionData, CompoundVersions,
17
+ } from './compound';
18
+ import { CrvUSDGlobalMarketData, CrvUSDUserData, CrvUSDVersions } from './curveUsd';
19
+ import { FluidEarnPositionData, FluidVaultData } from './fluid';
20
+ import { LiquityStakingData, LiquityTroveInfo } from './liquity';
21
+ import {
22
+ LiquityV2MarketData, LiquityV2SBoldYBoldData, LiquityV2StakingData, LiquityV2TroveData, LiquityV2Versions,
23
+ } from './liquityV2';
24
+ import { LlamaLendGlobalMarketData, LlamaLendUserData, LlamaLendVersionsType } from './llamaLend';
10
25
  import { CdpData } from './maker';
11
- import { MorphoBluePositionData, MorphoBlueVersions } from './morphoBlue';
12
- import { MorphoMidnightPositionData, MorphoMidnightVersions } from './morphoMidnight';
13
- import { SparkPositionData, SparkVersions } from './spark';
26
+ import {
27
+ MorphoBlueEarnData, MorphoBlueMarketInfo, MorphoBluePositionData, MorphoBlueVersions,
28
+ } from './morphoBlue';
29
+ import { MorphoMidnightMarketInfo, MorphoMidnightPositionData, MorphoMidnightVersions } from './morphoMidnight';
30
+ import { SparkMarketsData, SparkPositionData, SparkVersions } from './spark';
14
31
 
15
32
  export interface PortfolioProtocolData<T> {
16
33
  error: string,
17
34
  data: T | null,
18
35
  }
19
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
+
20
51
  export interface PortfolioPositionsDataForAddress {
21
52
  aaveV3: {
22
53
  [key in AaveVersions]?: PortfolioProtocolData<AaveV3PositionData>;
@@ -62,4 +93,73 @@ export interface PortfolioPositionsDataForAddress {
62
93
 
63
94
  export interface PortfolioPositionsData {
64
95
  [key: EthAddress]: PortfolioPositionsDataForAddress;
96
+ }
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
+
152
+ /** The `markets` getPortfolioData returns: every market it read, by protocol, keyed by market or spoke. */
153
+ export interface PortfolioMarketsData {
154
+ morphoMarketsData: Record<string, MorphoBlueMarketInfo>;
155
+ morphoMidnightMarketsData: Record<string, MorphoMidnightMarketInfo>;
156
+ compoundV3MarketsData: Record<string, CompoundV3MarketsData>;
157
+ sparkMarketsData: Record<string, SparkMarketsData>;
158
+ aaveV3MarketsData: Record<string, AaveV3MarketData>;
159
+ aaveV2MarketsData: Record<string, AaveV2MarketData>;
160
+ compoundV2MarketsData: Record<string, CompoundV2MarketsData>;
161
+ crvUsdMarketsData: Record<string, CrvUSDGlobalMarketData>;
162
+ llamaLendMarketsData: Record<string, LlamaLendGlobalMarketData>;
163
+ liquityV2MarketsData: Record<string, LiquityV2MarketData>;
164
+ aaveV4SpokesData: Record<string, AaveV4SpokeData>;
65
165
  }
@@ -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([