@kasufinance/kasu-sdk 2.11.0 → 2.12.0

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.
@@ -1,8 +1,10 @@
1
1
  import { Provider } from '@ethersproject/providers';
2
2
 
3
3
  import { deriveRequestState, RequestState } from '../domain/requests';
4
+ import { YieldHistoryEntry } from '../domain/yield-history';
4
5
  import { DataService } from '../services/DataService/data-service';
5
6
  import { Portfolio } from '../services/Portfolio/portfolio';
7
+ import { YieldHistoryOptions } from '../services/Portfolio/yield-history';
6
8
  import { UserRequest } from '../services/UserLending/types';
7
9
  import { UserLending } from '../services/UserLending/user-lending';
8
10
 
@@ -39,9 +41,8 @@ export class PortfolioFacade {
39
41
  ): Promise<UserPositions> {
40
42
  const currentEpoch = await this._userLending.getCurrentEpoch();
41
43
 
42
- const poolOverviews = await this._dataService.getPoolOverview(
43
- currentEpoch,
44
- );
44
+ const poolOverviews =
45
+ await this._dataService.getPoolOverview(currentEpoch);
45
46
 
46
47
  const pools = await this._portfolio.getPortfolioLendingData(
47
48
  userAddress,
@@ -59,6 +60,42 @@ export class PortfolioFacade {
59
60
  return { pools, summary };
60
61
  }
61
62
 
63
+ /**
64
+ * The lender's settled yield per cycle, per pool and tranche: what each
65
+ * close credited them, NET of the performance fee (the chain credits it
66
+ * net), in display units of the deployment's own stable token (AUDD on
67
+ * XDC AUDD). `epochEnd` is unix seconds. Net only — no gross is offered.
68
+ *
69
+ * Validates `options` before any read (`RangeError` on a malformed one).
70
+ *
71
+ * ```ts
72
+ * const entries = await kasu.portfolio.getYieldHistory('0xUser...', { count: 52 });
73
+ * const allStrategies = aggregateYieldHistory(entries);
74
+ * const oneStrategy = aggregateYieldHistory(entries, { poolId });
75
+ * ```
76
+ *
77
+ * `getPositions`' `totalYieldEarningsLastEpoch` for a pool equals that
78
+ * pool's bar for the epoch of its most recently APPLIED interest — which
79
+ * is older than the last cycle for a pool that has not cleared since (the
80
+ * bar carries that older `epochId`). A lender who held nothing at that
81
+ * epoch has no bar there and a figure of 0; their newest bar is from
82
+ * when they last held shares.
83
+ *
84
+ * For a fixed-term lock holder the bars can sum to slightly more than the
85
+ * lifetime interest (up to about 0.2 %), because each top-up is valued at
86
+ * today's share price — show "earned to date" from the lifetime figure,
87
+ * never from the sum of the bars.
88
+ *
89
+ * `[]` for a lender with no history, and on a LITE deployment whose
90
+ * subgraph lacks the entities; on a full deployment that error throws.
91
+ */
92
+ async getYieldHistory(
93
+ userAddress: string,
94
+ options?: YieldHistoryOptions,
95
+ ): Promise<YieldHistoryEntry[]> {
96
+ return await this._portfolio.getYieldHistory(userAddress, options);
97
+ }
98
+
62
99
  /**
63
100
  * Fetch a user's full transaction history (deposits, withdrawals, cancellations).
64
101
  */
package/src/index.ts CHANGED
@@ -42,6 +42,8 @@ export type {
42
42
  PortfolioTrancheDepositDetails,
43
43
  } from './services/Portfolio/types';
44
44
 
45
+ export type { YieldHistoryOptions } from './services/Portfolio/yield-history';
46
+
45
47
  export type {
46
48
  UserLock,
47
49
  UserBonusData,
@@ -10,6 +10,10 @@ import {
10
10
  ISystemVariablesAbi__factory,
11
11
  IUserLoyaltyRewardsAbi__factory,
12
12
  } from '../../contracts';
13
+ import {
14
+ epochTrancheYield,
15
+ YieldHistoryEntry,
16
+ } from '../../domain/yield-history';
13
17
  import { SdkConfig } from '../../sdk-config';
14
18
  import { isLiteDeployment } from '../../utils/deployment-mode';
15
19
  import { DataService } from '../DataService/data-service';
@@ -43,6 +47,19 @@ import {
43
47
  PortfolioTrancheDepositDetails,
44
48
  UserLendingPoolTrancheFixedTermDepositLock,
45
49
  } from './types';
50
+ import {
51
+ buildYieldHistory,
52
+ fetchYieldHistorySeries,
53
+ isEmptyHistoryError,
54
+ resolveYieldHistoryWindow,
55
+ validateYieldHistoryOptions,
56
+ YIELD_HISTORY_PAGE_SIZE,
57
+ YieldHistoryOptions,
58
+ yieldHistoryPageQuery,
59
+ YieldHistoryPageResult,
60
+ yieldHistoryPositionsQuery,
61
+ YieldHistoryPositionsResult,
62
+ } from './yield-history';
46
63
 
47
64
  export class Portfolio {
48
65
  private _lockingService: KSULocking;
@@ -235,9 +252,7 @@ export class Portfolio {
235
252
  ecosystemFeeRate;
236
253
  }
237
254
 
238
- return (
239
- (totalWeeklyEcosystemFees * userRKsuAmount) / totalRKsuAmount
240
- );
255
+ return (totalWeeklyEcosystemFees * userRKsuAmount) / totalRKsuAmount;
241
256
  }
242
257
 
243
258
  /**
@@ -250,8 +265,12 @@ export class Portfolio {
250
265
  userDepositAmounts: [BigNumber, BigNumber];
251
266
  ksuEpochPrice: { price: BigNumber; decimals: number };
252
267
  }): Promise<number> {
253
- const { userAddress, userRksuAmount, userDepositAmounts, ksuEpochPrice } =
254
- params;
268
+ const {
269
+ userAddress,
270
+ userRksuAmount,
271
+ userDepositAmounts,
272
+ ksuEpochPrice,
273
+ } = params;
255
274
 
256
275
  const totalUserDeposits = userDepositAmounts[0].add(
257
276
  userDepositAmounts[1],
@@ -259,10 +278,11 @@ export class Portfolio {
259
278
 
260
279
  if (totalUserDeposits.isZero()) return 0;
261
280
 
262
- const rKSUtoStableRatio = await this._lockingService.getRKSUvsStableRatio(
263
- userRksuAmount,
264
- userAddress,
265
- );
281
+ const rKSUtoStableRatio =
282
+ await this._lockingService.getRKSUvsStableRatio(
283
+ userRksuAmount,
284
+ userAddress,
285
+ );
266
286
  const { loyaltyLevel } =
267
287
  this._lockingService.getLoyaltyLevelAndApyBonusFromRatio(
268
288
  rKSUtoStableRatio,
@@ -374,13 +394,14 @@ export class Portfolio {
374
394
  userRksuAmount,
375
395
  });
376
396
 
377
- const weeklyKsuBonusRewards =
378
- await this.calculateWeeklyKsuBonusRewards({
397
+ const weeklyKsuBonusRewards = await this.calculateWeeklyKsuBonusRewards(
398
+ {
379
399
  userAddress,
380
400
  userRksuAmount,
381
401
  userDepositAmounts,
382
402
  ksuEpochPrice,
383
- });
403
+ },
404
+ );
384
405
 
385
406
  return {
386
407
  current: {
@@ -440,6 +461,7 @@ export class Portfolio {
440
461
  epochId: parseFloat(currentEpoch),
441
462
  lastEpochId: previousEpoch.toNumber(),
442
463
  unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
464
+ first: YIELD_HISTORY_PAGE_SIZE,
443
465
  },
444
466
  ),
445
467
  this._userLendingService.getPortfolioUserTrancheBalances(
@@ -505,7 +527,6 @@ export class Portfolio {
505
527
  }
506
528
 
507
529
  let totalInvestments = BigNumber.from(0);
508
- let totalYieldEarnedLastEpoch = 0;
509
530
 
510
531
  for (const poolOverview of poolOverviews) {
511
532
  const userPoolBalance = userPoolBalances.find(
@@ -522,6 +543,8 @@ export class Portfolio {
522
543
  };
523
544
  })[] = [];
524
545
 
546
+ let poolYieldEarnedLastEpoch = 0;
547
+
525
548
  const lendingPoolUserDetails =
526
549
  lastEpochData.user.lendingPoolUserDetails.find(
527
550
  ({ lendingPool }) => lendingPool.id === poolOverview.id,
@@ -558,35 +581,66 @@ export class Portfolio {
558
581
  });
559
582
  continue;
560
583
  }
561
- const lastEpochTotalYield = parseFloat(
562
- lastEpochUserTrancheDetails.tranche
563
- .lendingPoolTrancheEpochInterest?.[0]
564
- ?.epochInterestAmount ?? '0',
565
- );
584
+ const lastEpoch = previousEpoch.toNumber();
566
585
 
567
- const lastEpochUserShares = parseFloat(
586
+ const userHasLastEpochShares = Boolean(
568
587
  lastEpochUserTrancheDetails
569
- .lendingPoolTrancheUserEpochSharesUpdates?.[0]
570
- ?.shares ?? '0',
588
+ .lendingPoolTrancheUserEpochSharesUpdates?.length,
571
589
  );
572
590
 
573
- const lastEpochTotalShares = parseFloat(
574
- lastEpochUserTrancheDetails.tranche
575
- .lendingPoolTrancheShareUpdates?.[0]?.shares ?? '0',
576
- );
577
-
578
- const currentTotalAmount = parseFloat(
579
- lastEpochUserTrancheDetails.tranche.balance,
580
- );
581
- const currentTotalShares = parseFloat(
582
- lastEpochUserTrancheDetails.tranche.shares,
583
- );
584
-
585
- const lastEpochTotalBaseYield =
586
- lastEpochTotalShares === 0
587
- ? 0
588
- : (lastEpochTotalYield * lastEpochUserShares) /
589
- lastEpochTotalShares;
591
+ const fixedTermDeposits =
592
+ lastEpochUserTrancheDetails.userLendingPoolTrancheFixedTermDepositLocks;
593
+
594
+ // The one per-epoch formula `getYieldHistory` runs too, with
595
+ // the same share selection — so the last bar of a history
596
+ // equals these figures.
597
+ const lastEpochYield = epochTrancheYield({
598
+ epochInterest: parseFloat(
599
+ lastEpochUserTrancheDetails.tranche
600
+ .lendingPoolTrancheEpochInterest?.[0]
601
+ ?.epochInterestAmount ?? '0',
602
+ ),
603
+ userShares: parseFloat(
604
+ lastEpochUserTrancheDetails
605
+ .lendingPoolTrancheUserEpochSharesUpdates?.[0]
606
+ ?.shares ?? '0',
607
+ ),
608
+ trancheShares: parseFloat(
609
+ lastEpochUserTrancheDetails.tranche
610
+ .lendingPoolTrancheShareUpdates?.[0]?.shares ?? '0',
611
+ ),
612
+ trancheBalanceNow: parseFloat(
613
+ lastEpochUserTrancheDetails.tranche.balance,
614
+ ),
615
+ trancheSharesNow: parseFloat(
616
+ lastEpochUserTrancheDetails.tranche.shares,
617
+ ),
618
+ locks: fixedTermDeposits.map((fixedTermDeposit) => ({
619
+ active:
620
+ parseFloat(fixedTermDeposit.epochLockStart) <
621
+ lastEpoch &&
622
+ parseFloat(fixedTermDeposit.epochLockEnd) >=
623
+ lastEpoch &&
624
+ userHasLastEpochShares,
625
+ // No snapshot = the lock's opening shares, exactly
626
+ // as `getYieldHistory` reads it: a lock made after
627
+ // clearing E starts at E with its first update
628
+ // tagged E + 1. Never the CURRENT shares, which
629
+ // would carry a top-up from a clearing that ran
630
+ // after the last settled epoch.
631
+ sharesAtEpoch: parseFloat(
632
+ fixedTermDeposit.lockSharesAtLastEpoch?.[0]
633
+ ?.shares ??
634
+ fixedTermDeposit.initialTrancheShares,
635
+ ),
636
+ sharesBeforeEpoch: parseFloat(
637
+ fixedTermDeposit
638
+ .userLendingPoolTrancheFixedTermDepositLockShareUpdate?.[0]
639
+ ?.shares ??
640
+ fixedTermDeposit.initialTrancheShares,
641
+ ),
642
+ })),
643
+ });
590
644
 
591
645
  const userTrancheBalance = {
592
646
  balance: '0',
@@ -609,129 +663,85 @@ export class Portfolio {
609
663
 
610
664
  let totalLifetimeFtdYield = 0;
611
665
 
612
- let lastEpochVariableBaseYield = lastEpochTotalBaseYield;
613
-
614
666
  const fixedLoans: PortfolioTranche['fixedLoans'] =
615
- lastEpochUserTrancheDetails.userLendingPoolTrancheFixedTermDepositLocks.map(
616
- (fixedTermDeposit) => {
617
- const fixedLoanDepositDetails =
618
- userRequest?.[
619
- fixedTermDeposit
620
- .lendingPoolTrancheFixedTermConfig
621
- .configId
622
- ] ?? [];
623
-
624
- let lastEpochYieldEarned = 0;
625
- let lifetimeYieldEarned = 0;
626
-
627
- if (
628
- parseFloat(fixedTermDeposit.epochLockStart) <
629
- previousEpoch.toNumber() &&
630
- parseFloat(fixedTermDeposit.epochLockEnd) >=
631
- previousEpoch.toNumber() &&
632
- Boolean(
633
- lastEpochUserTrancheDetails
634
- .lendingPoolTrancheUserEpochSharesUpdates
635
- ?.length,
636
- )
637
- ) {
638
- const currentEpochFtdShares = parseFloat(
667
+ fixedTermDeposits.map((fixedTermDeposit, lockIndex) => {
668
+ const fixedLoanDepositDetails =
669
+ userRequest?.[
670
+ fixedTermDeposit
671
+ .lendingPoolTrancheFixedTermConfig.configId
672
+ ] ?? [];
673
+
674
+ const lastEpochYieldEarned =
675
+ lastEpochYield.locks[lockIndex] ?? 0;
676
+
677
+ const lifetimeYieldEarned =
678
+ this.calculateFixedTermDepositEarnings(
679
+ fixedTermDeposit,
680
+ lastEpochUserTrancheDetails.tranche.balance,
681
+ lastEpochUserTrancheDetails.tranche.shares,
682
+ );
683
+
684
+ totalLifetimeFtdYield += lifetimeYieldEarned;
685
+
686
+ const lockDuration =
687
+ parseFloat(
688
+ fixedTermDeposit
689
+ .lendingPoolTrancheFixedTermConfig
690
+ .epochLockDuration,
691
+ ) * SECONDS_PER_EPOCH;
692
+
693
+ return {
694
+ lockId: fixedTermDeposit.lockId,
695
+ configId:
696
+ fixedTermDeposit
697
+ .lendingPoolTrancheFixedTermConfig.configId,
698
+ epochLockDuration:
699
+ fixedTermDeposit
700
+ .lendingPoolTrancheFixedTermConfig
701
+ .epochLockDuration,
702
+ epochLockEnd: fixedTermDeposit.epochLockEnd,
703
+ epochInterestRate:
704
+ fixedTermDeposit
705
+ .lendingPoolTrancheFixedTermConfig
706
+ .epochInterestRate,
707
+ epochLockStart: fixedTermDeposit.epochLockStart,
708
+ amount: this._userLendingService
709
+ .convertSharesToAssets(
639
710
  fixedTermDeposit.trancheShares,
640
- );
641
-
642
- const lastEpochFtdShares = parseFloat(
643
- fixedTermDeposit
644
- .userLendingPoolTrancheFixedTermDepositLockShareUpdate?.[0]
645
- ?.shares ?? '0',
646
- );
647
-
648
- const additionalFtdYield =
649
- ((currentEpochFtdShares -
650
- lastEpochFtdShares) *
651
- currentTotalAmount) /
652
- currentTotalShares;
653
-
654
- const lastEpochFtdBaseYield =
655
- (lastEpochTotalBaseYield *
656
- lastEpochFtdShares) /
657
- lastEpochUserShares;
658
-
659
- lastEpochVariableBaseYield -=
660
- lastEpochFtdBaseYield;
661
-
662
- lastEpochYieldEarned =
663
- lastEpochFtdBaseYield + additionalFtdYield;
664
- }
665
-
666
- lifetimeYieldEarned =
667
- this.calculateFixedTermDepositEarnings(
668
- fixedTermDeposit,
669
711
  lastEpochUserTrancheDetails.tranche.balance,
670
712
  lastEpochUserTrancheDetails.tranche.shares,
671
- );
672
-
673
- totalLifetimeFtdYield += lifetimeYieldEarned;
674
-
675
- totalYieldEarnedLastEpoch += lastEpochYieldEarned;
676
-
677
- const lockDuration =
678
- parseFloat(
679
- fixedTermDeposit
680
- .lendingPoolTrancheFixedTermConfig
681
- .epochLockDuration,
682
- ) * SECONDS_PER_EPOCH;
683
-
684
- return {
685
- lockId: fixedTermDeposit.lockId,
686
- configId:
687
- fixedTermDeposit
688
- .lendingPoolTrancheFixedTermConfig
689
- .configId,
690
- epochLockDuration:
691
- fixedTermDeposit
692
- .lendingPoolTrancheFixedTermConfig
693
- .epochLockDuration,
694
- epochLockEnd: fixedTermDeposit.epochLockEnd,
695
- epochInterestRate:
696
- fixedTermDeposit
697
- .lendingPoolTrancheFixedTermConfig
698
- .epochInterestRate,
699
- epochLockStart: fixedTermDeposit.epochLockStart,
700
- amount: this._userLendingService
701
- .convertSharesToAssets(
702
- fixedTermDeposit.trancheShares,
703
- lastEpochUserTrancheDetails.tranche
704
- .balance,
705
- lastEpochUserTrancheDetails.tranche
706
- .shares,
707
- )
708
- .toString(),
709
- endTime:
710
- parseInt(fixedTermDeposit.createdOn) +
711
- lockDuration,
712
- isLocked: fixedTermDeposit.isLocked,
713
- isWithdrawalRequested:
714
- fixedTermDeposit.isWithdrawalRequested,
715
- startTime: parseInt(fixedTermDeposit.createdOn),
716
- yieldEarnings: {
717
- lastEpoch: lastEpochYieldEarned.toString(),
718
- lifetime:
719
- this.precisionToString(
720
- lifetimeYieldEarned,
721
- ),
722
- },
723
- depositDetails: fixedLoanDepositDetails,
724
- };
725
- },
726
- );
713
+ )
714
+ .toString(),
715
+ endTime:
716
+ parseInt(fixedTermDeposit.createdOn) +
717
+ lockDuration,
718
+ isLocked: fixedTermDeposit.isLocked,
719
+ isWithdrawalRequested:
720
+ fixedTermDeposit.isWithdrawalRequested,
721
+ startTime: parseInt(fixedTermDeposit.createdOn),
722
+ yieldEarnings: {
723
+ lastEpoch:
724
+ this.precisionToString(
725
+ lastEpochYieldEarned,
726
+ ),
727
+ lifetime:
728
+ this.precisionToString(lifetimeYieldEarned),
729
+ },
730
+ depositDetails: fixedLoanDepositDetails,
731
+ };
732
+ });
727
733
 
728
- totalYieldEarnedLastEpoch += lastEpochTotalBaseYield;
734
+ // This POOL's last-epoch total: the tranche totals summed, the
735
+ // locks counted once (inside `total`).
736
+ poolYieldEarnedLastEpoch += lastEpochYield.total;
729
737
 
730
738
  tranches.push({
731
739
  ...tranche,
732
740
  investedAmount: userTrancheBalance,
733
741
  yieldEarnings: {
734
- lastEpoch: lastEpochVariableBaseYield.toString(),
742
+ lastEpoch: this.precisionToString(
743
+ lastEpochYield.variable,
744
+ ),
735
745
  lifetime: this.precisionToString(totalLifetimeFtdYield), // passing in total FTD yield to be subtracted by total trache lifetime later down
736
746
  },
737
747
  fixedLoans,
@@ -741,8 +751,9 @@ export class Portfolio {
741
751
 
742
752
  portfolioLendingPoolsPromise.push({
743
753
  ...poolOverview,
744
- totalYieldEarningsLastEpoch:
745
- totalYieldEarnedLastEpoch.toString(),
754
+ totalYieldEarningsLastEpoch: this.precisionToString(
755
+ poolYieldEarnedLastEpoch,
756
+ ),
746
757
  totalInvestedAmount: userPoolBalance.balance.toString(),
747
758
  totalYieldEarningsLifetime:
748
759
  userPoolBalance.yieldEarned.toString(),
@@ -796,7 +807,106 @@ export class Portfolio {
796
807
  );
797
808
  }
798
809
 
810
+ /**
811
+ * The lender's settled yield per cycle (epoch), per pool and tranche —
812
+ * what the chain credited at each close, NET of the performance fee
813
+ * (`domain/yield-history.ts`), in the deployment's own stable token. Each
814
+ * entry runs the same `epochTrancheYield` as `getPortfolioLendingData`'s
815
+ * last-epoch figures: a tranche's `yieldEarnings.lastEpoch` plus its
816
+ * locks' equals its entry for the epoch of the tranche's most recently
817
+ * APPLIED interest (older than the last cycle for a pool that has not
818
+ * cleared since; no entry, and a figure of 0, when the lender held
819
+ * nothing at that epoch).
820
+ *
821
+ * Sum entries with `aggregateYieldHistory` (all strategies, or one with
822
+ * `{ poolId }`). Only epochs in which the lender held shares appear — a
823
+ * new lender gets `[]`. Pools in `UNUSED_LENDING_POOL_IDS` are left out,
824
+ * as in `lendingPortfolioQuery`; Directus enablement is NOT applied.
825
+ *
826
+ * Cost: three parallel `SystemVariables` reads and the positions query in
827
+ * one round, then one subgraph request per page of the longest series
828
+ * (1000 rows a page) — normally two subgraph round trips in all, never one
829
+ * per epoch.
830
+ *
831
+ * On a LITE deployment, a subgraph that lacks the entities (a validation
832
+ * error naming them) yields `[]`; on a full deployment that error throws,
833
+ * like every other failure (`isEmptyHistoryError`).
834
+ *
835
+ * @throws RangeError on a malformed `fromEpoch` / `count`, before any
836
+ * network read.
837
+ */
838
+ async getYieldHistory(
839
+ userAddress: string,
840
+ options: YieldHistoryOptions = {},
841
+ ): Promise<YieldHistoryEntry[]> {
842
+ validateYieldHistoryOptions(options);
843
+
844
+ try {
845
+ const [currentEpoch, epochZeroStart, epochDuration, positions] =
846
+ await Promise.all([
847
+ this._systemVariablesAbi.currentEpochNumber(),
848
+ this._systemVariablesAbi.epochStartTimestamp(0),
849
+ this._systemVariablesAbi.epochDuration(),
850
+ this._graph.request<YieldHistoryPositionsResult>(
851
+ yieldHistoryPositionsQuery,
852
+ {
853
+ userAddress: userAddress.toLowerCase(),
854
+ unusedPools:
855
+ this._kasuConfig.UNUSED_LENDING_POOL_IDS,
856
+ first: YIELD_HISTORY_PAGE_SIZE,
857
+ },
858
+ ),
859
+ ]);
860
+
861
+ const window = resolveYieldHistoryWindow(
862
+ options,
863
+ currentEpoch.toNumber(),
864
+ );
865
+ if (!window || !positions.user) return [];
866
+
867
+ const userTranches: string[] = [];
868
+ const tranches = new Set<string>();
869
+ const locks: string[] = [];
870
+ for (const pool of positions.user.lendingPoolUserDetails) {
871
+ for (const position of pool.lendingPoolTrancheUserDetails) {
872
+ userTranches.push(position.id);
873
+ tranches.add(position.tranche.id);
874
+ for (const lock of position.userLendingPoolTrancheFixedTermDepositLocks) {
875
+ locks.push(lock.id);
876
+ }
877
+ }
878
+ }
879
+ if (userTranches.length === 0) return [];
880
+
881
+ const { rows } = await fetchYieldHistorySeries(
882
+ (variables) =>
883
+ this._graph.request<YieldHistoryPageResult>(
884
+ yieldHistoryPageQuery,
885
+ variables,
886
+ ),
887
+ { tranches: [...tranches], userTranches, locks },
888
+ window,
889
+ );
890
+
891
+ return buildYieldHistory({ positions, ...rows }, window, {
892
+ epochZeroStart: epochZeroStart.toNumber(),
893
+ epochDuration: epochDuration.toNumber(),
894
+ });
895
+ } catch (error) {
896
+ if (isEmptyHistoryError(error, this._isLiteDeployment)) return [];
897
+ throw error;
898
+ }
899
+ }
900
+
901
+ /**
902
+ * A money figure as a plain decimal string. Below a millionth of a token
903
+ * (the stable asset's own precision) is float noise, never money, so it
904
+ * is `'0'` — not `'1e-7'`, which `parseUnits` rejects, nor `'-3e-12'`. A
905
+ * small negative remainder (above −0.0001) is `'0'` as well.
906
+ */
799
907
  precisionToString(val: number): string {
908
+ if (Math.abs(val) < 1e-6) return '0';
909
+
800
910
  if (val < 0 && val > -0.0001) {
801
911
  return '0';
802
912
  }
@@ -67,9 +67,11 @@ export const lendingPortfolioQuery = gql`
67
67
  $epochId: BigInt!
68
68
  $lastEpochId: BigInt!
69
69
  $unusedPools: [String!]!
70
+ $first: Int!
70
71
  ) {
71
72
  user(id: $userAddress) {
72
73
  lendingPoolUserDetails(
74
+ first: $first
73
75
  where: { lendingPool_not_in: $unusedPools }
74
76
  ) {
75
77
  id
@@ -77,11 +79,11 @@ export const lendingPortfolioQuery = gql`
77
79
  id
78
80
  name
79
81
  balance
80
- tranches {
82
+ tranches(first: $first) {
81
83
  shares
82
84
  }
83
85
  }
84
- lendingPoolTrancheUserDetails {
86
+ lendingPoolTrancheUserDetails(first: $first) {
85
87
  shares
86
88
  lendingPoolTrancheUserEpochSharesUpdates(
87
89
  orderDirection: desc
@@ -91,7 +93,7 @@ export const lendingPortfolioQuery = gql`
91
93
  ) {
92
94
  shares
93
95
  }
94
- userLendingPoolTrancheFixedTermDepositLocks {
96
+ userLendingPoolTrancheFixedTermDepositLocks(first: $first) {
95
97
  isLocked
96
98
  unlockAmount
97
99
  initialAmount
@@ -115,6 +117,14 @@ export const lendingPortfolioQuery = gql`
115
117
  ) {
116
118
  shares
117
119
  }
120
+ lockSharesAtLastEpoch: userLendingPoolTrancheFixedTermDepositLockShareUpdate(
121
+ orderBy: shareUpdatesIndex
122
+ orderDirection: desc
123
+ first: 1
124
+ where: { epochId_lt: $epochId }
125
+ ) {
126
+ shares
127
+ }
118
128
  }
119
129
  tranche {
120
130
  id
@@ -141,6 +151,7 @@ export const lendingPortfolioQuery = gql`
141
151
  }
142
152
  }
143
153
  userRequests(
154
+ first: $first
144
155
  where: {
145
156
  user: $userAddress
146
157
  userRequestEvents_: {
@@ -156,6 +167,7 @@ export const lendingPortfolioQuery = gql`
156
167
  }
157
168
  fixedTermConfigId
158
169
  userRequestEvents(
170
+ first: $first
159
171
  where: { type_in: [DepositInitiated, DepositIncreased] }
160
172
  ) {
161
173
  id