@kasufinance/kasu-sdk 2.10.3 → 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.
Files changed (41) hide show
  1. package/README.md +28 -1
  2. package/dist/bundle.cjs.js +752 -59
  3. package/dist/bundle.esm.js +750 -60
  4. package/dist/domain/index.d.ts +10 -2
  5. package/dist/domain/index.js +6 -0
  6. package/dist/domain/index.js.map +1 -1
  7. package/dist/domain/strategy-summary.d.ts +47 -0
  8. package/dist/domain/strategy-summary.js +76 -0
  9. package/dist/domain/strategy-summary.js.map +1 -0
  10. package/dist/domain/yield-history.d.ts +122 -0
  11. package/dist/domain/yield-history.js +95 -0
  12. package/dist/domain/yield-history.js.map +1 -0
  13. package/dist/facade/strategies.js +2 -0
  14. package/dist/facade/strategies.js.map +1 -1
  15. package/dist/facade/types.d.ts +13 -0
  16. package/dist/facade/user-portfolio.d.ts +32 -0
  17. package/dist/facade/user-portfolio.js +34 -0
  18. package/dist/facade/user-portfolio.js.map +1 -1
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/services/Portfolio/portfolio.d.ts +37 -0
  22. package/dist/services/Portfolio/portfolio.js +136 -56
  23. package/dist/services/Portfolio/portfolio.js.map +1 -1
  24. package/dist/services/Portfolio/queries.js +15 -3
  25. package/dist/services/Portfolio/queries.js.map +1 -1
  26. package/dist/services/Portfolio/types.d.ts +8 -0
  27. package/dist/services/Portfolio/yield-history.d.ts +177 -0
  28. package/dist/services/Portfolio/yield-history.js +405 -0
  29. package/dist/services/Portfolio/yield-history.js.map +1 -0
  30. package/package.json +2 -1
  31. package/src/domain/index.ts +19 -10
  32. package/src/domain/strategy-summary.ts +88 -0
  33. package/src/domain/yield-history.ts +194 -0
  34. package/src/facade/strategies.ts +2 -0
  35. package/src/facade/types.ts +13 -0
  36. package/src/facade/user-portfolio.ts +40 -3
  37. package/src/index.ts +2 -0
  38. package/src/services/Portfolio/portfolio.ts +263 -153
  39. package/src/services/Portfolio/queries.ts +15 -3
  40. package/src/services/Portfolio/types.ts +10 -0
  41. package/src/services/Portfolio/yield-history.ts +574 -0
@@ -16943,6 +16943,101 @@ class KSULocking {
16943
16943
  }
16944
16944
  }
16945
16945
 
16946
+ /**
16947
+ * Settled interest per cycle (epoch): what a lender was credited at each close,
16948
+ * per pool and tranche, and the one formula that turns a tranche's credited
16949
+ * interest into one lender's share of it.
16950
+ *
16951
+ * **The figures are NET, and only net.** `LendingPool._applyTrancheInterest`
16952
+ * takes the performance fee off first and emits `InterestApplied` with the
16953
+ * lender side only (`interest × (1 − performanceFee)`); a fixed-term top-up
16954
+ * (`_applyFixedRateInterests`) is net of the fee the same way. So the figure
16955
+ * this module computes from those records IS what the lender was credited —
16956
+ * never take the fee off it again. No gross is offered: the subgraph keeps
16957
+ * only TODAY's fee, so a historical gross would be a guess.
16958
+ *
16959
+ * Units: amounts are display units of the DEPLOYMENT'S OWN stable token (USDC
16960
+ * on Base and XDC USDC, AUDD on XDC AUDD) — never converted to USD here;
16961
+ * shares are display units of tranche shares, as the subgraph's BigDecimals.
16962
+ */
16963
+ /**
16964
+ * The ONE per-epoch formula. `Portfolio.getPortfolioLendingData` runs it for
16965
+ * the last settled epoch and `getYieldHistory` for every epoch of a window, so
16966
+ * the two figures cannot drift apart.
16967
+ *
16968
+ * ```
16969
+ * base = epochInterest × userShares / trancheShares
16970
+ * lockBase = base × lock.sharesBeforeEpoch / userShares (active locks)
16971
+ * topUp = (lock.sharesAtEpoch − lock.sharesBeforeEpoch) × trancheBalanceNow / trancheSharesNow
16972
+ * variable = base − Σ lockBase
16973
+ * total = variable + Σ (lockBase + topUp)
16974
+ * ```
16975
+ *
16976
+ * A zero denominator contributes 0 (a tranche or a lender with no shares
16977
+ * earned nothing at that close), never `NaN`.
16978
+ */
16979
+ function epochTrancheYield(input) {
16980
+ const base = input.trancheShares === 0
16981
+ ? 0
16982
+ : (input.epochInterest * input.userShares) / input.trancheShares;
16983
+ let variable = base;
16984
+ const locks = input.locks.map((lock) => {
16985
+ if (!lock.active)
16986
+ return 0;
16987
+ const topUp = input.trancheSharesNow === 0
16988
+ ? 0
16989
+ : ((lock.sharesAtEpoch - lock.sharesBeforeEpoch) *
16990
+ input.trancheBalanceNow) /
16991
+ input.trancheSharesNow;
16992
+ const lockBase = input.userShares === 0
16993
+ ? 0
16994
+ : (base * lock.sharesBeforeEpoch) / input.userShares;
16995
+ variable -= lockBase;
16996
+ return lockBase + topUp;
16997
+ });
16998
+ const total = locks.reduce((sum, lockYield) => sum + lockYield, variable);
16999
+ return { total, variable, locks };
17000
+ }
17001
+ /**
17002
+ * Pool/tranche entries → one point per epoch, ascending by `epochId`. With no
17003
+ * filter it sums every strategy (the Lend tab's bars); with `poolId` it is one
17004
+ * strategy (a strategy page's bars).
17005
+ *
17006
+ * An epoch appears only if a kept entry has it — there is no zero padding, so
17007
+ * an empty input (a new lender) is an empty output.
17008
+ *
17009
+ * For a fixed-term lock holder the bars can sum to slightly MORE than the
17010
+ * lender's lifetime interest (up to about 0.2 %): each top-up is valued at the
17011
+ * tranche's CURRENT share price, which has grown since. Show "earned to date"
17012
+ * from the lifetime figure, never from this sum.
17013
+ */
17014
+ function aggregateYieldHistory(entries, filter = {}) {
17015
+ var _a, _b;
17016
+ const poolId = (_a = filter.poolId) === null || _a === void 0 ? void 0 : _a.toLowerCase();
17017
+ const trancheId = (_b = filter.trancheId) === null || _b === void 0 ? void 0 : _b.toLowerCase();
17018
+ const byEpoch = new Map();
17019
+ for (const entry of entries) {
17020
+ if (poolId !== undefined && entry.poolId.toLowerCase() !== poolId) {
17021
+ continue;
17022
+ }
17023
+ if (trancheId !== undefined &&
17024
+ entry.trancheId.toLowerCase() !== trancheId) {
17025
+ continue;
17026
+ }
17027
+ const point = byEpoch.get(entry.epochId);
17028
+ if (point) {
17029
+ point.netYield += entry.netYield;
17030
+ continue;
17031
+ }
17032
+ byEpoch.set(entry.epochId, {
17033
+ epochId: entry.epochId,
17034
+ epochEnd: entry.epochEnd,
17035
+ netYield: entry.netYield,
17036
+ });
17037
+ }
17038
+ return [...byEpoch.values()].sort((a, b) => a.epochId - b.epochId);
17039
+ }
17040
+
16946
17041
  /**
16947
17042
  * Check if the SDK is configured for a Lite deployment.
16948
17043
  * Lite deployments don't have KSU token, locking, or loyalty features.
@@ -17726,9 +17821,11 @@ const lendingPortfolioQuery = gql `
17726
17821
  $epochId: BigInt!
17727
17822
  $lastEpochId: BigInt!
17728
17823
  $unusedPools: [String!]!
17824
+ $first: Int!
17729
17825
  ) {
17730
17826
  user(id: $userAddress) {
17731
17827
  lendingPoolUserDetails(
17828
+ first: $first
17732
17829
  where: { lendingPool_not_in: $unusedPools }
17733
17830
  ) {
17734
17831
  id
@@ -17736,11 +17833,11 @@ const lendingPortfolioQuery = gql `
17736
17833
  id
17737
17834
  name
17738
17835
  balance
17739
- tranches {
17836
+ tranches(first: $first) {
17740
17837
  shares
17741
17838
  }
17742
17839
  }
17743
- lendingPoolTrancheUserDetails {
17840
+ lendingPoolTrancheUserDetails(first: $first) {
17744
17841
  shares
17745
17842
  lendingPoolTrancheUserEpochSharesUpdates(
17746
17843
  orderDirection: desc
@@ -17750,7 +17847,7 @@ const lendingPortfolioQuery = gql `
17750
17847
  ) {
17751
17848
  shares
17752
17849
  }
17753
- userLendingPoolTrancheFixedTermDepositLocks {
17850
+ userLendingPoolTrancheFixedTermDepositLocks(first: $first) {
17754
17851
  isLocked
17755
17852
  unlockAmount
17756
17853
  initialAmount
@@ -17774,6 +17871,14 @@ const lendingPortfolioQuery = gql `
17774
17871
  ) {
17775
17872
  shares
17776
17873
  }
17874
+ lockSharesAtLastEpoch: userLendingPoolTrancheFixedTermDepositLockShareUpdate(
17875
+ orderBy: shareUpdatesIndex
17876
+ orderDirection: desc
17877
+ first: 1
17878
+ where: { epochId_lt: $epochId }
17879
+ ) {
17880
+ shares
17881
+ }
17777
17882
  }
17778
17883
  tranche {
17779
17884
  id
@@ -17800,6 +17905,7 @@ const lendingPortfolioQuery = gql `
17800
17905
  }
17801
17906
  }
17802
17907
  userRequests(
17908
+ first: $first
17803
17909
  where: {
17804
17910
  user: $userAddress
17805
17911
  userRequestEvents_: {
@@ -17815,6 +17921,7 @@ const lendingPortfolioQuery = gql `
17815
17921
  }
17816
17922
  fixedTermConfigId
17817
17923
  userRequestEvents(
17924
+ first: $first
17818
17925
  where: { type_in: [DepositInitiated, DepositIncreased] }
17819
17926
  ) {
17820
17927
  id
@@ -17824,6 +17931,400 @@ const lendingPortfolioQuery = gql `
17824
17931
  }
17825
17932
  `;
17826
17933
 
17934
+ /** The subgraph returns at most this many rows per collection per request. */
17935
+ const YIELD_HISTORY_PAGE_SIZE = 1000;
17936
+ /**
17937
+ * A runaway guard, not a working limit: a collection this many pages long
17938
+ * (`MAX × PAGE_SIZE` rows for one lender) is a broken response, not a history.
17939
+ */
17940
+ const YIELD_HISTORY_MAX_PAGES = 100;
17941
+ /**
17942
+ * Throws `RangeError` on a malformed `fromEpoch` / `count`. `getYieldHistory`
17943
+ * calls it BEFORE any network read.
17944
+ */
17945
+ function validateYieldHistoryOptions(options) {
17946
+ const { fromEpoch, count } = options;
17947
+ if (fromEpoch !== undefined &&
17948
+ (!Number.isSafeInteger(fromEpoch) || fromEpoch < 0)) {
17949
+ throw new RangeError(`getYieldHistory: fromEpoch must be a non-negative integer, got ${String(fromEpoch)}`);
17950
+ }
17951
+ if (count !== undefined && (!Number.isSafeInteger(count) || count < 1)) {
17952
+ throw new RangeError(`getYieldHistory: count must be a positive integer, got ${String(count)}`);
17953
+ }
17954
+ }
17955
+ function resolveYieldHistoryWindow(options, currentEpoch) {
17956
+ validateYieldHistoryOptions(options);
17957
+ const { fromEpoch, count } = options;
17958
+ const newest = currentEpoch - 1;
17959
+ if (newest < 0)
17960
+ return null;
17961
+ let from;
17962
+ let to = newest;
17963
+ if (fromEpoch !== undefined) {
17964
+ from = fromEpoch;
17965
+ if (count !== undefined)
17966
+ to = Math.min(newest, fromEpoch + count - 1);
17967
+ }
17968
+ else {
17969
+ from = count !== undefined ? Math.max(0, newest - count + 1) : 0;
17970
+ }
17971
+ return from > to ? null : { fromEpoch: from, toEpoch: to };
17972
+ }
17973
+ // ---------------------------------------------------------------------------
17974
+ // Subgraph reads
17975
+ // ---------------------------------------------------------------------------
17976
+ /**
17977
+ * Round trip 1: the lender's tranche positions and fixed-term locks. Same
17978
+ * pool exclusion and the same nested-list size (`$first`) as
17979
+ * `lendingPortfolioQuery`, so both read the same lock set.
17980
+ */
17981
+ const yieldHistoryPositionsQuery = gql `
17982
+ query YieldHistoryPositions(
17983
+ $userAddress: ID!
17984
+ $unusedPools: [String!]!
17985
+ $first: Int!
17986
+ ) {
17987
+ user(id: $userAddress) {
17988
+ lendingPoolUserDetails(
17989
+ first: $first
17990
+ where: { lendingPool_not_in: $unusedPools }
17991
+ ) {
17992
+ lendingPool {
17993
+ id
17994
+ }
17995
+ lendingPoolTrancheUserDetails(first: $first) {
17996
+ id
17997
+ tranche {
17998
+ id
17999
+ balance
18000
+ shares
18001
+ }
18002
+ userLendingPoolTrancheFixedTermDepositLocks(first: $first) {
18003
+ id
18004
+ epochLockStart
18005
+ epochLockEnd
18006
+ initialTrancheShares
18007
+ }
18008
+ }
18009
+ }
18010
+ }
18011
+ }
18012
+ `;
18013
+ /**
18014
+ * Round trip 2 onwards: the four per-epoch series, one page of each per
18015
+ * request, cursor-paginated by `id`. A series that is exhausted is skipped with
18016
+ * `@include`, so a history costs as many requests as its LONGEST series has
18017
+ * pages — normally one — never one per epoch.
18018
+ *
18019
+ * Share snapshots are read up to the window's end with no lower bound: the
18020
+ * value AS OF the first epoch may come from an update long before it.
18021
+ */
18022
+ const yieldHistoryPageQuery = gql `
18023
+ query YieldHistoryPage(
18024
+ $tranches: [String!]!
18025
+ $userTranches: [String!]!
18026
+ $locks: [String!]!
18027
+ $fromEpoch: BigInt!
18028
+ $toEpoch: BigInt!
18029
+ $interestCursor: ID!
18030
+ $trancheSharesCursor: ID!
18031
+ $userSharesCursor: ID!
18032
+ $lockSharesCursor: ID!
18033
+ $withInterest: Boolean!
18034
+ $withTrancheShares: Boolean!
18035
+ $withUserShares: Boolean!
18036
+ $withLockShares: Boolean!
18037
+ $first: Int!
18038
+ ) {
18039
+ interest: lendingPoolTrancheEpochInterests(
18040
+ first: $first
18041
+ orderBy: id
18042
+ orderDirection: asc
18043
+ where: {
18044
+ id_gt: $interestCursor
18045
+ lendingPoolTranche_in: $tranches
18046
+ epochId_gte: $fromEpoch
18047
+ epochId_lte: $toEpoch
18048
+ }
18049
+ ) @include(if: $withInterest) {
18050
+ id
18051
+ epochId
18052
+ epochInterestAmount
18053
+ lendingPoolTranche {
18054
+ id
18055
+ }
18056
+ }
18057
+ trancheShares: lendingPoolTrancheShareUpdates(
18058
+ first: $first
18059
+ orderBy: id
18060
+ orderDirection: asc
18061
+ where: {
18062
+ id_gt: $trancheSharesCursor
18063
+ lendingPoolTranche_in: $tranches
18064
+ epochId_lte: $toEpoch
18065
+ }
18066
+ ) @include(if: $withTrancheShares) {
18067
+ id
18068
+ epochId
18069
+ shareUpdatesIndex
18070
+ shares
18071
+ lendingPoolTranche {
18072
+ id
18073
+ }
18074
+ }
18075
+ userShares: lendingPoolTrancheUserEpochSharesUpdates(
18076
+ first: $first
18077
+ orderBy: id
18078
+ orderDirection: asc
18079
+ where: {
18080
+ id_gt: $userSharesCursor
18081
+ lendingPoolTrancheUserDetails_in: $userTranches
18082
+ epochId_lte: $toEpoch
18083
+ }
18084
+ ) @include(if: $withUserShares) {
18085
+ id
18086
+ epochId
18087
+ shareUpdatesIndex
18088
+ shares
18089
+ lendingPoolTrancheUserDetails {
18090
+ id
18091
+ }
18092
+ }
18093
+ lockShares: userLendingPoolTrancheFtdLockShareUpdates(
18094
+ first: $first
18095
+ orderBy: id
18096
+ orderDirection: asc
18097
+ where: {
18098
+ id_gt: $lockSharesCursor
18099
+ userLendingPoolTrancheFixedTermDepositLock_in: $locks
18100
+ epochId_lte: $toEpoch
18101
+ }
18102
+ ) @include(if: $withLockShares) {
18103
+ id
18104
+ epochId
18105
+ shareUpdatesIndex
18106
+ shares
18107
+ userLendingPoolTrancheFixedTermDepositLock {
18108
+ id
18109
+ }
18110
+ }
18111
+ }
18112
+ `;
18113
+ /**
18114
+ * Fetch the four series for the window, paginating each by `id` until a short
18115
+ * page. Returns the rows and how many requests it took.
18116
+ */
18117
+ function fetchYieldHistorySeries(request, ids, window) {
18118
+ return __awaiter(this, void 0, void 0, function* () {
18119
+ var _a;
18120
+ const rows = {
18121
+ interest: [],
18122
+ trancheShares: [],
18123
+ userShares: [],
18124
+ lockShares: [],
18125
+ };
18126
+ const series = [
18127
+ { key: 'interest', flag: 'withInterest', cursor: 'interestCursor' },
18128
+ {
18129
+ key: 'trancheShares',
18130
+ flag: 'withTrancheShares',
18131
+ cursor: 'trancheSharesCursor',
18132
+ },
18133
+ {
18134
+ key: 'userShares',
18135
+ flag: 'withUserShares',
18136
+ cursor: 'userSharesCursor',
18137
+ },
18138
+ {
18139
+ key: 'lockShares',
18140
+ flag: 'withLockShares',
18141
+ cursor: 'lockSharesCursor',
18142
+ },
18143
+ ];
18144
+ const cursors = {
18145
+ interest: '',
18146
+ trancheShares: '',
18147
+ userShares: '',
18148
+ lockShares: '',
18149
+ };
18150
+ const open = {
18151
+ interest: ids.tranches.length > 0,
18152
+ trancheShares: ids.tranches.length > 0,
18153
+ userShares: ids.userTranches.length > 0,
18154
+ lockShares: ids.locks.length > 0,
18155
+ };
18156
+ let requests = 0;
18157
+ while (series.some(({ key }) => open[key])) {
18158
+ if (requests >= YIELD_HISTORY_MAX_PAGES) {
18159
+ throw new Error(`getYieldHistory: more than ${String(YIELD_HISTORY_MAX_PAGES)} pages for one lender`);
18160
+ }
18161
+ const variables = {
18162
+ tranches: ids.tranches,
18163
+ userTranches: ids.userTranches,
18164
+ locks: ids.locks,
18165
+ fromEpoch: String(window.fromEpoch),
18166
+ toEpoch: String(window.toEpoch),
18167
+ first: YIELD_HISTORY_PAGE_SIZE,
18168
+ };
18169
+ for (const { key, flag, cursor } of series) {
18170
+ variables[flag] = open[key];
18171
+ variables[cursor] = cursors[key];
18172
+ }
18173
+ const page = yield request(variables);
18174
+ requests += 1;
18175
+ for (const { key } of series) {
18176
+ if (!open[key])
18177
+ continue;
18178
+ const got = (_a = page[key]) !== null && _a !== void 0 ? _a : [];
18179
+ rows[key].push(...got);
18180
+ const last = got.at(-1);
18181
+ if (got.length < YIELD_HISTORY_PAGE_SIZE || last === undefined) {
18182
+ open[key] = false;
18183
+ continue;
18184
+ }
18185
+ if (last.id <= cursors[key]) {
18186
+ throw new Error(`getYieldHistory: the ${key} cursor did not advance`);
18187
+ }
18188
+ cursors[key] = last.id;
18189
+ }
18190
+ }
18191
+ return { rows, requests };
18192
+ });
18193
+ }
18194
+ /**
18195
+ * The snapshot in force at `epoch`: among updates with `epochId ≤ epoch`, the
18196
+ * one with the highest `shareUpdatesIndex` — exactly what
18197
+ * `lendingPortfolioQuery` selects with `orderBy: shareUpdatesIndex, desc,
18198
+ * first: 1, where: { epochId_lt: epoch + 1 }`. `undefined` when none.
18199
+ */
18200
+ function sharesAsOf(updates, epoch) {
18201
+ let found;
18202
+ for (const update of updates) {
18203
+ if (Number(update.epochId) > epoch)
18204
+ continue;
18205
+ if (!found || update.shareUpdatesIndex > found.shareUpdatesIndex) {
18206
+ found = update;
18207
+ }
18208
+ }
18209
+ return found;
18210
+ }
18211
+ function groupBy(rows, key) {
18212
+ const map = new Map();
18213
+ for (const row of rows) {
18214
+ const k = key(row);
18215
+ const list = map.get(k);
18216
+ if (list)
18217
+ list.push(row);
18218
+ else
18219
+ map.set(k, [row]);
18220
+ }
18221
+ return map;
18222
+ }
18223
+ /**
18224
+ * Pure: the fetched series → one entry per pool, tranche and settled epoch in
18225
+ * which the lender held shares (or a lock earned a top-up). Each entry runs the
18226
+ * same `epochTrancheYield` as the portfolio's last-epoch figure, with the same
18227
+ * share selection, so the newest entry equals that figure.
18228
+ *
18229
+ * Sorted by `epochId`, then `poolId`, then `trancheId`.
18230
+ */
18231
+ function buildYieldHistory(read, window, clock) {
18232
+ var _a, _b, _c, _d, _e, _f;
18233
+ const user = read.positions.user;
18234
+ if (!user)
18235
+ return [];
18236
+ const interestByTranche = groupBy(read.interest, (row) => row.lendingPoolTranche.id);
18237
+ const trancheSharesByTranche = groupBy(read.trancheShares, (row) => row.lendingPoolTranche.id);
18238
+ const userSharesByPosition = groupBy(read.userShares, (row) => row.lendingPoolTrancheUserDetails.id);
18239
+ const lockSharesByLock = groupBy(read.lockShares, (row) => row.userLendingPoolTrancheFixedTermDepositLock.id);
18240
+ const entries = [];
18241
+ for (const pool of user.lendingPoolUserDetails) {
18242
+ for (const position of pool.lendingPoolTrancheUserDetails) {
18243
+ const trancheId = position.tranche.id;
18244
+ const trancheUpdates = (_a = trancheSharesByTranche.get(trancheId)) !== null && _a !== void 0 ? _a : [];
18245
+ const userUpdates = (_b = userSharesByPosition.get(position.id)) !== null && _b !== void 0 ? _b : [];
18246
+ const trancheBalanceNow = parseFloat(position.tranche.balance);
18247
+ const trancheSharesNow = parseFloat(position.tranche.shares);
18248
+ for (const record of (_c = interestByTranche.get(trancheId)) !== null && _c !== void 0 ? _c : []) {
18249
+ const epoch = Number(record.epochId);
18250
+ if (epoch < window.fromEpoch || epoch > window.toEpoch) {
18251
+ continue;
18252
+ }
18253
+ const userSnapshot = sharesAsOf(userUpdates, epoch);
18254
+ const userShares = parseFloat((_d = userSnapshot === null || userSnapshot === void 0 ? void 0 : userSnapshot.shares) !== null && _d !== void 0 ? _d : '0');
18255
+ const locks = position.userLendingPoolTrancheFixedTermDepositLocks.map((lock) => {
18256
+ var _a, _b, _c, _d, _e;
18257
+ const lockUpdates = (_a = lockSharesByLock.get(lock.id)) !== null && _a !== void 0 ? _a : [];
18258
+ return {
18259
+ active: parseFloat(lock.epochLockStart) < epoch &&
18260
+ parseFloat(lock.epochLockEnd) >= epoch &&
18261
+ userSnapshot !== undefined,
18262
+ // No snapshot yet = the lock's opening
18263
+ // shares: a lock made after clearing E
18264
+ // starts at E but its first update is
18265
+ // tagged E + 1, and reading that gap as 0
18266
+ // would book its principal as a top-up.
18267
+ sharesAtEpoch: parseFloat((_c = (_b = sharesAsOf(lockUpdates, epoch)) === null || _b === void 0 ? void 0 : _b.shares) !== null && _c !== void 0 ? _c : lock.initialTrancheShares),
18268
+ sharesBeforeEpoch: parseFloat((_e = (_d = sharesAsOf(lockUpdates, epoch - 1)) === null || _d === void 0 ? void 0 : _d.shares) !== null && _e !== void 0 ? _e : lock.initialTrancheShares),
18269
+ };
18270
+ });
18271
+ const { total } = epochTrancheYield({
18272
+ epochInterest: parseFloat(record.epochInterestAmount),
18273
+ userShares,
18274
+ trancheShares: parseFloat((_f = (_e = sharesAsOf(trancheUpdates, epoch)) === null || _e === void 0 ? void 0 : _e.shares) !== null && _f !== void 0 ? _f : '0'),
18275
+ trancheBalanceNow,
18276
+ trancheSharesNow,
18277
+ locks,
18278
+ });
18279
+ // Nothing held and nothing topped up: not part of this
18280
+ // lender's history (a new lender's history is empty).
18281
+ if (userShares <= 0 && total === 0)
18282
+ continue;
18283
+ entries.push({
18284
+ epochId: epoch,
18285
+ epochEnd: clock.epochZeroStart +
18286
+ (epoch + 1) * clock.epochDuration,
18287
+ poolId: pool.lendingPool.id,
18288
+ trancheId,
18289
+ netYield: total,
18290
+ });
18291
+ }
18292
+ }
18293
+ }
18294
+ return entries.sort((a, b) => a.epochId - b.epochId ||
18295
+ a.poolId.localeCompare(b.poolId) ||
18296
+ a.trancheId.localeCompare(b.trancheId));
18297
+ }
18298
+ /**
18299
+ * A GraphQL VALIDATION error naming an entity, field or argument this
18300
+ * deployment's subgraph does not have. A network error, a timeout or an
18301
+ * indexing error is not one.
18302
+ */
18303
+ function isMissingEntityError(error) {
18304
+ if (typeof error !== 'object' || error === null)
18305
+ return false;
18306
+ const response = error.response;
18307
+ const errors = response === null || response === void 0 ? void 0 : response.errors;
18308
+ if (!Array.isArray(errors) || errors.length === 0)
18309
+ return false;
18310
+ return errors.every((e) => {
18311
+ const message = e === null || e === void 0 ? void 0 : e.message;
18312
+ return (typeof message === 'string' &&
18313
+ /has no field|cannot query field|unknown (type|field|argument)|no such (type|field)/i.test(message));
18314
+ });
18315
+ }
18316
+ /**
18317
+ * The only failure `getYieldHistory` turns into `[]`: a missing-schema error
18318
+ * on a LITE deployment, whose subgraph may predate the entities. On a full
18319
+ * deployment the entities are part of the schema, so the same error means a
18320
+ * broken query or subgraph and THROWS — an empty chart there would hide it.
18321
+ * Everything else (network, timeout, indexing) throws on every deployment, so
18322
+ * a consumer can tell "no history here" from "history unavailable".
18323
+ */
18324
+ function isEmptyHistoryError(error, liteDeployment) {
18325
+ return liteDeployment && isMissingEntityError(error);
18326
+ }
18327
+
17827
18328
  class Portfolio {
17828
18329
  constructor(_kasuConfig, signerOrProvider) {
17829
18330
  this._kasuConfig = _kasuConfig;
@@ -17924,7 +18425,7 @@ class Portfolio {
17924
18425
  performanceFee *
17925
18426
  ecosystemFeeRate;
17926
18427
  }
17927
- return ((totalWeeklyEcosystemFees * userRKsuAmount) / totalRKsuAmount);
18428
+ return (totalWeeklyEcosystemFees * userRKsuAmount) / totalRKsuAmount;
17928
18429
  }
17929
18430
  /**
17930
18431
  * Calculate the user's weekly KASU bonus rewards (KASU) based on current deposits,
@@ -17932,7 +18433,7 @@ class Portfolio {
17932
18433
  */
17933
18434
  calculateWeeklyKsuBonusRewards(params) {
17934
18435
  return __awaiter(this, void 0, void 0, function* () {
17935
- const { userAddress, userRksuAmount, userDepositAmounts, ksuEpochPrice } = params;
18436
+ const { userAddress, userRksuAmount, userDepositAmounts, ksuEpochPrice, } = params;
17936
18437
  const totalUserDeposits = userDepositAmounts[0].add(userDepositAmounts[1]);
17937
18438
  if (totalUserDeposits.isZero())
17938
18439
  return 0;
@@ -18041,7 +18542,7 @@ class Portfolio {
18041
18542
  }
18042
18543
  getPortfolioLendingData(userAddress, poolOverviews, currentEpoch, provider) {
18043
18544
  return __awaiter(this, void 0, void 0, function* () {
18044
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
18545
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
18045
18546
  const SECONDS_PER_EPOCH = 604800; // seconds in a week
18046
18547
  const portfolioLendingPoolsPromise = [];
18047
18548
  const previousEpoch = ethers.BigNumber.from(currentEpoch).sub(ethers.BigNumber.from(1));
@@ -18051,6 +18552,7 @@ class Portfolio {
18051
18552
  epochId: parseFloat(currentEpoch),
18052
18553
  lastEpochId: previousEpoch.toNumber(),
18053
18554
  unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
18555
+ first: YIELD_HISTORY_PAGE_SIZE,
18054
18556
  }),
18055
18557
  this._userLendingService.getPortfolioUserTrancheBalances(userAddress),
18056
18558
  this._userLendingService.getUserPoolBalance(userAddress, poolOverviews.map(({ id }) => id), provider),
@@ -18088,7 +18590,6 @@ class Portfolio {
18088
18590
  }
18089
18591
  }
18090
18592
  let totalInvestments = ethers.BigNumber.from(0);
18091
- let totalYieldEarnedLastEpoch = 0;
18092
18593
  for (const poolOverview of poolOverviews) {
18093
18594
  const userPoolBalance = userPoolBalances.find(({ poolId }) => poolId === poolOverview.id);
18094
18595
  if (!userPoolBalance) {
@@ -18096,6 +18597,7 @@ class Portfolio {
18096
18597
  }
18097
18598
  totalInvestments = userPoolBalance.balance.add(totalInvestments);
18098
18599
  const tranches = [];
18600
+ let poolYieldEarnedLastEpoch = 0;
18099
18601
  const lendingPoolUserDetails = lastEpochData.user.lendingPoolUserDetails.find(({ lendingPool }) => lendingPool.id === poolOverview.id);
18100
18602
  if (!lendingPoolUserDetails)
18101
18603
  continue;
@@ -18114,18 +18616,42 @@ class Portfolio {
18114
18616
  }, depositDetails, fixedLoans: [] }));
18115
18617
  continue;
18116
18618
  }
18117
- const lastEpochTotalYield = parseFloat((_e = (_d = (_c = lastEpochUserTrancheDetails.tranche
18118
- .lendingPoolTrancheEpochInterest) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.epochInterestAmount) !== null && _e !== void 0 ? _e : '0');
18119
- const lastEpochUserShares = parseFloat((_h = (_g = (_f = lastEpochUserTrancheDetails
18120
- .lendingPoolTrancheUserEpochSharesUpdates) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.shares) !== null && _h !== void 0 ? _h : '0');
18121
- const lastEpochTotalShares = parseFloat((_l = (_k = (_j = lastEpochUserTrancheDetails.tranche
18122
- .lendingPoolTrancheShareUpdates) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.shares) !== null && _l !== void 0 ? _l : '0');
18123
- const currentTotalAmount = parseFloat(lastEpochUserTrancheDetails.tranche.balance);
18124
- const currentTotalShares = parseFloat(lastEpochUserTrancheDetails.tranche.shares);
18125
- const lastEpochTotalBaseYield = lastEpochTotalShares === 0
18126
- ? 0
18127
- : (lastEpochTotalYield * lastEpochUserShares) /
18128
- lastEpochTotalShares;
18619
+ const lastEpoch = previousEpoch.toNumber();
18620
+ const userHasLastEpochShares = Boolean((_c = lastEpochUserTrancheDetails
18621
+ .lendingPoolTrancheUserEpochSharesUpdates) === null || _c === void 0 ? void 0 : _c.length);
18622
+ const fixedTermDeposits = lastEpochUserTrancheDetails.userLendingPoolTrancheFixedTermDepositLocks;
18623
+ // The one per-epoch formula `getYieldHistory` runs too, with
18624
+ // the same share selection — so the last bar of a history
18625
+ // equals these figures.
18626
+ const lastEpochYield = epochTrancheYield({
18627
+ epochInterest: parseFloat((_f = (_e = (_d = lastEpochUserTrancheDetails.tranche
18628
+ .lendingPoolTrancheEpochInterest) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.epochInterestAmount) !== null && _f !== void 0 ? _f : '0'),
18629
+ userShares: parseFloat((_j = (_h = (_g = lastEpochUserTrancheDetails
18630
+ .lendingPoolTrancheUserEpochSharesUpdates) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.shares) !== null && _j !== void 0 ? _j : '0'),
18631
+ trancheShares: parseFloat((_m = (_l = (_k = lastEpochUserTrancheDetails.tranche
18632
+ .lendingPoolTrancheShareUpdates) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.shares) !== null && _m !== void 0 ? _m : '0'),
18633
+ trancheBalanceNow: parseFloat(lastEpochUserTrancheDetails.tranche.balance),
18634
+ trancheSharesNow: parseFloat(lastEpochUserTrancheDetails.tranche.shares),
18635
+ locks: fixedTermDeposits.map((fixedTermDeposit) => {
18636
+ var _a, _b, _c, _d, _e, _f;
18637
+ return ({
18638
+ active: parseFloat(fixedTermDeposit.epochLockStart) <
18639
+ lastEpoch &&
18640
+ parseFloat(fixedTermDeposit.epochLockEnd) >=
18641
+ lastEpoch &&
18642
+ userHasLastEpochShares,
18643
+ // No snapshot = the lock's opening shares, exactly
18644
+ // as `getYieldHistory` reads it: a lock made after
18645
+ // clearing E starts at E with its first update
18646
+ // tagged E + 1. Never the CURRENT shares, which
18647
+ // would carry a top-up from a clearing that ran
18648
+ // after the last settled epoch.
18649
+ sharesAtEpoch: parseFloat((_c = (_b = (_a = fixedTermDeposit.lockSharesAtLastEpoch) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.shares) !== null && _c !== void 0 ? _c : fixedTermDeposit.initialTrancheShares),
18650
+ sharesBeforeEpoch: parseFloat((_f = (_e = (_d = fixedTermDeposit
18651
+ .userLendingPoolTrancheFixedTermDepositLockShareUpdate) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.shares) !== null && _f !== void 0 ? _f : fixedTermDeposit.initialTrancheShares),
18652
+ });
18653
+ }),
18654
+ });
18129
18655
  const userTrancheBalance = {
18130
18656
  balance: '0',
18131
18657
  yieldEarned: 0,
@@ -18140,47 +18666,20 @@ class Portfolio {
18140
18666
  }
18141
18667
  }
18142
18668
  let totalLifetimeFtdYield = 0;
18143
- let lastEpochVariableBaseYield = lastEpochTotalBaseYield;
18144
- const fixedLoans = lastEpochUserTrancheDetails.userLendingPoolTrancheFixedTermDepositLocks.map((fixedTermDeposit) => {
18145
- var _a, _b, _c, _d, _e;
18669
+ const fixedLoans = fixedTermDeposits.map((fixedTermDeposit, lockIndex) => {
18670
+ var _a, _b;
18146
18671
  const fixedLoanDepositDetails = (_a = userRequest === null || userRequest === void 0 ? void 0 : userRequest[fixedTermDeposit
18147
- .lendingPoolTrancheFixedTermConfig
18148
- .configId]) !== null && _a !== void 0 ? _a : [];
18149
- let lastEpochYieldEarned = 0;
18150
- let lifetimeYieldEarned = 0;
18151
- if (parseFloat(fixedTermDeposit.epochLockStart) <
18152
- previousEpoch.toNumber() &&
18153
- parseFloat(fixedTermDeposit.epochLockEnd) >=
18154
- previousEpoch.toNumber() &&
18155
- Boolean((_b = lastEpochUserTrancheDetails
18156
- .lendingPoolTrancheUserEpochSharesUpdates) === null || _b === void 0 ? void 0 : _b.length)) {
18157
- const currentEpochFtdShares = parseFloat(fixedTermDeposit.trancheShares);
18158
- const lastEpochFtdShares = parseFloat((_e = (_d = (_c = fixedTermDeposit
18159
- .userLendingPoolTrancheFixedTermDepositLockShareUpdate) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.shares) !== null && _e !== void 0 ? _e : '0');
18160
- const additionalFtdYield = ((currentEpochFtdShares -
18161
- lastEpochFtdShares) *
18162
- currentTotalAmount) /
18163
- currentTotalShares;
18164
- const lastEpochFtdBaseYield = (lastEpochTotalBaseYield *
18165
- lastEpochFtdShares) /
18166
- lastEpochUserShares;
18167
- lastEpochVariableBaseYield -=
18168
- lastEpochFtdBaseYield;
18169
- lastEpochYieldEarned =
18170
- lastEpochFtdBaseYield + additionalFtdYield;
18171
- }
18172
- lifetimeYieldEarned =
18173
- this.calculateFixedTermDepositEarnings(fixedTermDeposit, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares);
18672
+ .lendingPoolTrancheFixedTermConfig.configId]) !== null && _a !== void 0 ? _a : [];
18673
+ const lastEpochYieldEarned = (_b = lastEpochYield.locks[lockIndex]) !== null && _b !== void 0 ? _b : 0;
18674
+ const lifetimeYieldEarned = this.calculateFixedTermDepositEarnings(fixedTermDeposit, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares);
18174
18675
  totalLifetimeFtdYield += lifetimeYieldEarned;
18175
- totalYieldEarnedLastEpoch += lastEpochYieldEarned;
18176
18676
  const lockDuration = parseFloat(fixedTermDeposit
18177
18677
  .lendingPoolTrancheFixedTermConfig
18178
18678
  .epochLockDuration) * SECONDS_PER_EPOCH;
18179
18679
  return {
18180
18680
  lockId: fixedTermDeposit.lockId,
18181
18681
  configId: fixedTermDeposit
18182
- .lendingPoolTrancheFixedTermConfig
18183
- .configId,
18682
+ .lendingPoolTrancheFixedTermConfig.configId,
18184
18683
  epochLockDuration: fixedTermDeposit
18185
18684
  .lendingPoolTrancheFixedTermConfig
18186
18685
  .epochLockDuration,
@@ -18190,9 +18689,7 @@ class Portfolio {
18190
18689
  .epochInterestRate,
18191
18690
  epochLockStart: fixedTermDeposit.epochLockStart,
18192
18691
  amount: this._userLendingService
18193
- .convertSharesToAssets(fixedTermDeposit.trancheShares, lastEpochUserTrancheDetails.tranche
18194
- .balance, lastEpochUserTrancheDetails.tranche
18195
- .shares)
18692
+ .convertSharesToAssets(fixedTermDeposit.trancheShares, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares)
18196
18693
  .toString(),
18197
18694
  endTime: parseInt(fixedTermDeposit.createdOn) +
18198
18695
  lockDuration,
@@ -18200,20 +18697,22 @@ class Portfolio {
18200
18697
  isWithdrawalRequested: fixedTermDeposit.isWithdrawalRequested,
18201
18698
  startTime: parseInt(fixedTermDeposit.createdOn),
18202
18699
  yieldEarnings: {
18203
- lastEpoch: lastEpochYieldEarned.toString(),
18700
+ lastEpoch: this.precisionToString(lastEpochYieldEarned),
18204
18701
  lifetime: this.precisionToString(lifetimeYieldEarned),
18205
18702
  },
18206
18703
  depositDetails: fixedLoanDepositDetails,
18207
18704
  };
18208
18705
  });
18209
- totalYieldEarnedLastEpoch += lastEpochTotalBaseYield;
18706
+ // This POOL's last-epoch total: the tranche totals summed, the
18707
+ // locks counted once (inside `total`).
18708
+ poolYieldEarnedLastEpoch += lastEpochYield.total;
18210
18709
  tranches.push(Object.assign(Object.assign({}, tranche), { investedAmount: userTrancheBalance, yieldEarnings: {
18211
- lastEpoch: lastEpochVariableBaseYield.toString(),
18710
+ lastEpoch: this.precisionToString(lastEpochYield.variable),
18212
18711
  lifetime: this.precisionToString(totalLifetimeFtdYield), // passing in total FTD yield to be subtracted by total trache lifetime later down
18213
18712
  }, fixedLoans,
18214
18713
  depositDetails }));
18215
18714
  }
18216
- portfolioLendingPoolsPromise.push(Object.assign(Object.assign({}, poolOverview), { totalYieldEarningsLastEpoch: totalYieldEarnedLastEpoch.toString(), totalInvestedAmount: userPoolBalance.balance.toString(), totalYieldEarningsLifetime: userPoolBalance.yieldEarned.toString(), tranches }));
18715
+ portfolioLendingPoolsPromise.push(Object.assign(Object.assign({}, poolOverview), { totalYieldEarningsLastEpoch: this.precisionToString(poolYieldEarnedLastEpoch), totalInvestedAmount: userPoolBalance.balance.toString(), totalYieldEarningsLifetime: userPoolBalance.yieldEarned.toString(), tranches }));
18217
18716
  }
18218
18717
  const portfolioLendingPools = portfolioLendingPoolsPromise.map((pool) => (Object.assign(Object.assign({}, pool), { tranches: pool.tranches.map((tranche) => {
18219
18718
  const trancheBalance = tranche.investedAmount;
@@ -18231,7 +18730,87 @@ class Portfolio {
18231
18730
  !utils$2.parseUnits(pool.totalYieldEarningsLifetime).isZero());
18232
18731
  });
18233
18732
  }
18733
+ /**
18734
+ * The lender's settled yield per cycle (epoch), per pool and tranche —
18735
+ * what the chain credited at each close, NET of the performance fee
18736
+ * (`domain/yield-history.ts`), in the deployment's own stable token. Each
18737
+ * entry runs the same `epochTrancheYield` as `getPortfolioLendingData`'s
18738
+ * last-epoch figures: a tranche's `yieldEarnings.lastEpoch` plus its
18739
+ * locks' equals its entry for the epoch of the tranche's most recently
18740
+ * APPLIED interest (older than the last cycle for a pool that has not
18741
+ * cleared since; no entry, and a figure of 0, when the lender held
18742
+ * nothing at that epoch).
18743
+ *
18744
+ * Sum entries with `aggregateYieldHistory` (all strategies, or one with
18745
+ * `{ poolId }`). Only epochs in which the lender held shares appear — a
18746
+ * new lender gets `[]`. Pools in `UNUSED_LENDING_POOL_IDS` are left out,
18747
+ * as in `lendingPortfolioQuery`; Directus enablement is NOT applied.
18748
+ *
18749
+ * Cost: three parallel `SystemVariables` reads and the positions query in
18750
+ * one round, then one subgraph request per page of the longest series
18751
+ * (1000 rows a page) — normally two subgraph round trips in all, never one
18752
+ * per epoch.
18753
+ *
18754
+ * On a LITE deployment, a subgraph that lacks the entities (a validation
18755
+ * error naming them) yields `[]`; on a full deployment that error throws,
18756
+ * like every other failure (`isEmptyHistoryError`).
18757
+ *
18758
+ * @throws RangeError on a malformed `fromEpoch` / `count`, before any
18759
+ * network read.
18760
+ */
18761
+ getYieldHistory(userAddress_1) {
18762
+ return __awaiter(this, arguments, void 0, function* (userAddress, options = {}) {
18763
+ validateYieldHistoryOptions(options);
18764
+ try {
18765
+ const [currentEpoch, epochZeroStart, epochDuration, positions] = yield Promise.all([
18766
+ this._systemVariablesAbi.currentEpochNumber(),
18767
+ this._systemVariablesAbi.epochStartTimestamp(0),
18768
+ this._systemVariablesAbi.epochDuration(),
18769
+ this._graph.request(yieldHistoryPositionsQuery, {
18770
+ userAddress: userAddress.toLowerCase(),
18771
+ unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
18772
+ first: YIELD_HISTORY_PAGE_SIZE,
18773
+ }),
18774
+ ]);
18775
+ const window = resolveYieldHistoryWindow(options, currentEpoch.toNumber());
18776
+ if (!window || !positions.user)
18777
+ return [];
18778
+ const userTranches = [];
18779
+ const tranches = new Set();
18780
+ const locks = [];
18781
+ for (const pool of positions.user.lendingPoolUserDetails) {
18782
+ for (const position of pool.lendingPoolTrancheUserDetails) {
18783
+ userTranches.push(position.id);
18784
+ tranches.add(position.tranche.id);
18785
+ for (const lock of position.userLendingPoolTrancheFixedTermDepositLocks) {
18786
+ locks.push(lock.id);
18787
+ }
18788
+ }
18789
+ }
18790
+ if (userTranches.length === 0)
18791
+ return [];
18792
+ const { rows } = yield fetchYieldHistorySeries((variables) => this._graph.request(yieldHistoryPageQuery, variables), { tranches: [...tranches], userTranches, locks }, window);
18793
+ return buildYieldHistory(Object.assign({ positions }, rows), window, {
18794
+ epochZeroStart: epochZeroStart.toNumber(),
18795
+ epochDuration: epochDuration.toNumber(),
18796
+ });
18797
+ }
18798
+ catch (error) {
18799
+ if (isEmptyHistoryError(error, this._isLiteDeployment))
18800
+ return [];
18801
+ throw error;
18802
+ }
18803
+ });
18804
+ }
18805
+ /**
18806
+ * A money figure as a plain decimal string. Below a millionth of a token
18807
+ * (the stable asset's own precision) is float noise, never money, so it
18808
+ * is `'0'` — not `'1e-7'`, which `parseUnits` rejects, nor `'-3e-12'`. A
18809
+ * small negative remainder (above −0.0001) is `'0'` as well.
18810
+ */
18234
18811
  precisionToString(val) {
18812
+ if (Math.abs(val) < 1e-6)
18813
+ return '0';
18235
18814
  if (val < 0 && val > -1e-4) {
18236
18815
  return '0';
18237
18816
  }
@@ -27545,6 +28124,82 @@ function maxNetRateCeiling(pools, feePercent) {
27545
28124
  return rate;
27546
28125
  }
27547
28126
 
28127
+ /**
28128
+ * A strategy's SHORT blurb, as plain text: the CMS `liteDescription` first,
28129
+ * else the first paragraph of the long `description`, else nothing.
28130
+ *
28131
+ * Lifted from kasu-ui (`pickShortDescription`, its deposit dialog and landing
28132
+ * strategy card) so every app shows the same line, and pinned to its output
28133
+ * for every live CMS row. The rules are facts about how the CMS is authored,
28134
+ * not formatting preferences:
28135
+ *
28136
+ * - `liteDescription` IS THE SHORT ONE. `description` is the credit
28137
+ * originator's several-paragraph profile; on a card it is a wall.
28138
+ * - `"Not Available"` IS A PLACEHOLDER, not editorial. Several rows carry it
28139
+ * verbatim, and it must read as missing, not print. Matched on the whole
28140
+ * trimmed value, case-insensitively. It applies to `liteDescription` only.
28141
+ * - EMPTY OR BLANK IS MISSING. The service maps a pool with no CMS record to
28142
+ * `''`.
28143
+ * - THE FIELDS HOLD MARKDOWN. `liteDescription` can open with a `**bold**`
28144
+ * lead, and the surfaces this feeds render plain text, so the markers are
28145
+ * stripped (bold, then italic, then inline code).
28146
+ * - AND HARD NEWLINES. Every run of whitespace becomes one space, so a
28147
+ * paragraph break inside the value is not a gap in a one-line slot.
28148
+ * - NOT SANITISED. "Plain text" means text to render AS text. HTML tags,
28149
+ * entities and link syntax (`<script>`, `&lt;`, `[a](javascript:…)`) pass
28150
+ * through unchanged, and removing markers can join fragments into a tag
28151
+ * (`<scr**i**pt>` becomes `<script>`). Render it as a text node, never as
28152
+ * HTML or markdown.
28153
+ *
28154
+ * Two refinements over the kasu-ui original, neither reachable by any live
28155
+ * row: the cleaned text is trimmed (a CRLF value no longer ends in a space),
28156
+ * and a `liteDescription` that cleans to nothing (`** **`) counts as missing,
28157
+ * so the result is never a blank string.
28158
+ *
28159
+ * This authors no string of its own. It selects and cleans editorial the CMS
28160
+ * already holds, and an absent blurb is `undefined`, never a fallback sentence:
28161
+ * what "no blurb" means is the render site's decision.
28162
+ *
28163
+ * Pure. A value that is not a string reads as missing.
28164
+ */
28165
+ function pickShortDescription(source) {
28166
+ var _a;
28167
+ const lite = asTrimmed(source.liteDescription);
28168
+ if (lite && !NOT_AVAILABLE.test(lite)) {
28169
+ const summary = toOneLine(lite);
28170
+ if (summary)
28171
+ return summary;
28172
+ }
28173
+ const long = asTrimmed(source.description);
28174
+ if (long) {
28175
+ const firstParagraph = (_a = long.split(PARAGRAPH_BREAK, 1)[0]) !== null && _a !== void 0 ? _a : long;
28176
+ return toOneLine(firstParagraph) || undefined;
28177
+ }
28178
+ return undefined;
28179
+ }
28180
+ const NOT_AVAILABLE = /^not available$/i;
28181
+ const PARAGRAPH_BREAK = /\n\s*\n/;
28182
+ function asTrimmed(value) {
28183
+ return typeof value === 'string' ? value.trim() : '';
28184
+ }
28185
+ /** Markers off, whitespace runs to one space, ends trimmed. `''` when nothing is left. */
28186
+ function toOneLine(text) {
28187
+ return stripInlineMarkdown(text).replace(/\s+/gu, ' ').trim();
28188
+ }
28189
+ /**
28190
+ * Inline markdown markers off, in the order bold → italic → code. The bold
28191
+ * passes consume every `**` / `__` pair first, so the italic patterns can match
28192
+ * a lone `*` / `_` without lookarounds.
28193
+ */
28194
+ function stripInlineMarkdown(input) {
28195
+ return input
28196
+ .replace(/\*\*(.+?)\*\*/g, '$1')
28197
+ .replace(/__(.+?)__/g, '$1')
28198
+ .replace(/\*(.+?)\*/g, '$1')
28199
+ .replace(/_(.+?)_/g, '$1')
28200
+ .replace(/`([^`]+)`/g, '$1');
28201
+ }
28202
+
27548
28203
  /**
27549
28204
  * High-level facade for browsing Kasu lending strategies (pools).
27550
28205
  *
@@ -27731,6 +28386,7 @@ class StrategiesFacade {
27731
28386
  subheading: nonEmptyOrUndefined(pool.subheading),
27732
28387
  description: pool.description,
27733
28388
  liteDescription: nonEmptyOrUndefined(pool.liteDescription),
28389
+ summary: pickShortDescription(pool),
27734
28390
  isActive: pool.isActive,
27735
28391
  apy: pool.apy,
27736
28392
  tvl: pool.totalValueLocked,
@@ -28177,6 +28833,40 @@ class PortfolioFacade {
28177
28833
  return { pools, summary };
28178
28834
  });
28179
28835
  }
28836
+ /**
28837
+ * The lender's settled yield per cycle, per pool and tranche: what each
28838
+ * close credited them, NET of the performance fee (the chain credits it
28839
+ * net), in display units of the deployment's own stable token (AUDD on
28840
+ * XDC AUDD). `epochEnd` is unix seconds. Net only — no gross is offered.
28841
+ *
28842
+ * Validates `options` before any read (`RangeError` on a malformed one).
28843
+ *
28844
+ * ```ts
28845
+ * const entries = await kasu.portfolio.getYieldHistory('0xUser...', { count: 52 });
28846
+ * const allStrategies = aggregateYieldHistory(entries);
28847
+ * const oneStrategy = aggregateYieldHistory(entries, { poolId });
28848
+ * ```
28849
+ *
28850
+ * `getPositions`' `totalYieldEarningsLastEpoch` for a pool equals that
28851
+ * pool's bar for the epoch of its most recently APPLIED interest — which
28852
+ * is older than the last cycle for a pool that has not cleared since (the
28853
+ * bar carries that older `epochId`). A lender who held nothing at that
28854
+ * epoch has no bar there and a figure of 0; their newest bar is from
28855
+ * when they last held shares.
28856
+ *
28857
+ * For a fixed-term lock holder the bars can sum to slightly more than the
28858
+ * lifetime interest (up to about 0.2 %), because each top-up is valued at
28859
+ * today's share price — show "earned to date" from the lifetime figure,
28860
+ * never from the sum of the bars.
28861
+ *
28862
+ * `[]` for a lender with no history, and on a LITE deployment whose
28863
+ * subgraph lacks the entities; on a full deployment that error throws.
28864
+ */
28865
+ getYieldHistory(userAddress, options) {
28866
+ return __awaiter(this, void 0, void 0, function* () {
28867
+ return yield this._portfolio.getYieldHistory(userAddress, options);
28868
+ });
28869
+ }
28180
28870
  /**
28181
28871
  * Fetch a user's full transaction history (deposits, withdrawals, cancellations).
28182
28872
  */
@@ -29142,6 +29832,7 @@ exports.StrategiesFacade = StrategiesFacade;
29142
29832
  exports.UPPER_MEZZANINE = UPPER_MEZZANINE;
29143
29833
  exports.WithdrawFlow = WithdrawFlow;
29144
29834
  exports.accruedInterest = accruedInterest;
29835
+ exports.aggregateYieldHistory = aggregateYieldHistory;
29145
29836
  exports.apyToEpochRate = apyToEpochRate;
29146
29837
  exports.asContractType = asContractType;
29147
29838
  exports.auMinimumRemaining = auMinimumRemaining;
@@ -29172,6 +29863,7 @@ exports.encodeRequestWithdrawalCall = encodeRequestWithdrawalCall;
29172
29863
  exports.encodeStableTransfer = encodeStableTransfer;
29173
29864
  exports.encodeWithdrawAtExpiryCall = encodeWithdrawAtExpiryCall;
29174
29865
  exports.epochRateToApy = epochRateToApy;
29866
+ exports.epochTrancheYield = epochTrancheYield;
29175
29867
  exports.explorerAddressUrl = explorerAddressUrl;
29176
29868
  exports.explorerTxUrl = explorerTxUrl;
29177
29869
  exports.extractRevertData = extractRevertData;
@@ -29203,6 +29895,7 @@ exports.parseMinorUnits = parseMinorUnits;
29203
29895
  exports.parseTrancheBound = parseTrancheBound;
29204
29896
  exports.pickDefaultTrancheId = pickDefaultTrancheId;
29205
29897
  exports.pickHighestYieldTranche = pickHighestYieldTranche;
29898
+ exports.pickShortDescription = pickShortDescription;
29206
29899
  exports.poolAllTranchesFull = poolAllTranchesFull;
29207
29900
  exports.poolMaxApy = poolMaxApy;
29208
29901
  exports.projectedAnnualEarnings = projectedAnnualEarnings;