@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.
@@ -0,0 +1,405 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { gql } from 'graphql-request';
11
+ import { epochTrancheYield, } from '../../domain/yield-history';
12
+ /** The subgraph returns at most this many rows per collection per request. */
13
+ export const YIELD_HISTORY_PAGE_SIZE = 1000;
14
+ /**
15
+ * A runaway guard, not a working limit: a collection this many pages long
16
+ * (`MAX × PAGE_SIZE` rows for one lender) is a broken response, not a history.
17
+ */
18
+ export const YIELD_HISTORY_MAX_PAGES = 100;
19
+ /**
20
+ * Throws `RangeError` on a malformed `fromEpoch` / `count`. `getYieldHistory`
21
+ * calls it BEFORE any network read.
22
+ */
23
+ export function validateYieldHistoryOptions(options) {
24
+ const { fromEpoch, count } = options;
25
+ if (fromEpoch !== undefined &&
26
+ (!Number.isSafeInteger(fromEpoch) || fromEpoch < 0)) {
27
+ throw new RangeError(`getYieldHistory: fromEpoch must be a non-negative integer, got ${String(fromEpoch)}`);
28
+ }
29
+ if (count !== undefined && (!Number.isSafeInteger(count) || count < 1)) {
30
+ throw new RangeError(`getYieldHistory: count must be a positive integer, got ${String(count)}`);
31
+ }
32
+ }
33
+ export function resolveYieldHistoryWindow(options, currentEpoch) {
34
+ validateYieldHistoryOptions(options);
35
+ const { fromEpoch, count } = options;
36
+ const newest = currentEpoch - 1;
37
+ if (newest < 0)
38
+ return null;
39
+ let from;
40
+ let to = newest;
41
+ if (fromEpoch !== undefined) {
42
+ from = fromEpoch;
43
+ if (count !== undefined)
44
+ to = Math.min(newest, fromEpoch + count - 1);
45
+ }
46
+ else {
47
+ from = count !== undefined ? Math.max(0, newest - count + 1) : 0;
48
+ }
49
+ return from > to ? null : { fromEpoch: from, toEpoch: to };
50
+ }
51
+ // ---------------------------------------------------------------------------
52
+ // Subgraph reads
53
+ // ---------------------------------------------------------------------------
54
+ /**
55
+ * Round trip 1: the lender's tranche positions and fixed-term locks. Same
56
+ * pool exclusion and the same nested-list size (`$first`) as
57
+ * `lendingPortfolioQuery`, so both read the same lock set.
58
+ */
59
+ export const yieldHistoryPositionsQuery = gql `
60
+ query YieldHistoryPositions(
61
+ $userAddress: ID!
62
+ $unusedPools: [String!]!
63
+ $first: Int!
64
+ ) {
65
+ user(id: $userAddress) {
66
+ lendingPoolUserDetails(
67
+ first: $first
68
+ where: { lendingPool_not_in: $unusedPools }
69
+ ) {
70
+ lendingPool {
71
+ id
72
+ }
73
+ lendingPoolTrancheUserDetails(first: $first) {
74
+ id
75
+ tranche {
76
+ id
77
+ balance
78
+ shares
79
+ }
80
+ userLendingPoolTrancheFixedTermDepositLocks(first: $first) {
81
+ id
82
+ epochLockStart
83
+ epochLockEnd
84
+ initialTrancheShares
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ `;
91
+ /**
92
+ * Round trip 2 onwards: the four per-epoch series, one page of each per
93
+ * request, cursor-paginated by `id`. A series that is exhausted is skipped with
94
+ * `@include`, so a history costs as many requests as its LONGEST series has
95
+ * pages — normally one — never one per epoch.
96
+ *
97
+ * Share snapshots are read up to the window's end with no lower bound: the
98
+ * value AS OF the first epoch may come from an update long before it.
99
+ */
100
+ export const yieldHistoryPageQuery = gql `
101
+ query YieldHistoryPage(
102
+ $tranches: [String!]!
103
+ $userTranches: [String!]!
104
+ $locks: [String!]!
105
+ $fromEpoch: BigInt!
106
+ $toEpoch: BigInt!
107
+ $interestCursor: ID!
108
+ $trancheSharesCursor: ID!
109
+ $userSharesCursor: ID!
110
+ $lockSharesCursor: ID!
111
+ $withInterest: Boolean!
112
+ $withTrancheShares: Boolean!
113
+ $withUserShares: Boolean!
114
+ $withLockShares: Boolean!
115
+ $first: Int!
116
+ ) {
117
+ interest: lendingPoolTrancheEpochInterests(
118
+ first: $first
119
+ orderBy: id
120
+ orderDirection: asc
121
+ where: {
122
+ id_gt: $interestCursor
123
+ lendingPoolTranche_in: $tranches
124
+ epochId_gte: $fromEpoch
125
+ epochId_lte: $toEpoch
126
+ }
127
+ ) @include(if: $withInterest) {
128
+ id
129
+ epochId
130
+ epochInterestAmount
131
+ lendingPoolTranche {
132
+ id
133
+ }
134
+ }
135
+ trancheShares: lendingPoolTrancheShareUpdates(
136
+ first: $first
137
+ orderBy: id
138
+ orderDirection: asc
139
+ where: {
140
+ id_gt: $trancheSharesCursor
141
+ lendingPoolTranche_in: $tranches
142
+ epochId_lte: $toEpoch
143
+ }
144
+ ) @include(if: $withTrancheShares) {
145
+ id
146
+ epochId
147
+ shareUpdatesIndex
148
+ shares
149
+ lendingPoolTranche {
150
+ id
151
+ }
152
+ }
153
+ userShares: lendingPoolTrancheUserEpochSharesUpdates(
154
+ first: $first
155
+ orderBy: id
156
+ orderDirection: asc
157
+ where: {
158
+ id_gt: $userSharesCursor
159
+ lendingPoolTrancheUserDetails_in: $userTranches
160
+ epochId_lte: $toEpoch
161
+ }
162
+ ) @include(if: $withUserShares) {
163
+ id
164
+ epochId
165
+ shareUpdatesIndex
166
+ shares
167
+ lendingPoolTrancheUserDetails {
168
+ id
169
+ }
170
+ }
171
+ lockShares: userLendingPoolTrancheFtdLockShareUpdates(
172
+ first: $first
173
+ orderBy: id
174
+ orderDirection: asc
175
+ where: {
176
+ id_gt: $lockSharesCursor
177
+ userLendingPoolTrancheFixedTermDepositLock_in: $locks
178
+ epochId_lte: $toEpoch
179
+ }
180
+ ) @include(if: $withLockShares) {
181
+ id
182
+ epochId
183
+ shareUpdatesIndex
184
+ shares
185
+ userLendingPoolTrancheFixedTermDepositLock {
186
+ id
187
+ }
188
+ }
189
+ }
190
+ `;
191
+ /**
192
+ * Fetch the four series for the window, paginating each by `id` until a short
193
+ * page. Returns the rows and how many requests it took.
194
+ */
195
+ export function fetchYieldHistorySeries(request, ids, window) {
196
+ return __awaiter(this, void 0, void 0, function* () {
197
+ var _a;
198
+ const rows = {
199
+ interest: [],
200
+ trancheShares: [],
201
+ userShares: [],
202
+ lockShares: [],
203
+ };
204
+ const series = [
205
+ { key: 'interest', flag: 'withInterest', cursor: 'interestCursor' },
206
+ {
207
+ key: 'trancheShares',
208
+ flag: 'withTrancheShares',
209
+ cursor: 'trancheSharesCursor',
210
+ },
211
+ {
212
+ key: 'userShares',
213
+ flag: 'withUserShares',
214
+ cursor: 'userSharesCursor',
215
+ },
216
+ {
217
+ key: 'lockShares',
218
+ flag: 'withLockShares',
219
+ cursor: 'lockSharesCursor',
220
+ },
221
+ ];
222
+ const cursors = {
223
+ interest: '',
224
+ trancheShares: '',
225
+ userShares: '',
226
+ lockShares: '',
227
+ };
228
+ const open = {
229
+ interest: ids.tranches.length > 0,
230
+ trancheShares: ids.tranches.length > 0,
231
+ userShares: ids.userTranches.length > 0,
232
+ lockShares: ids.locks.length > 0,
233
+ };
234
+ let requests = 0;
235
+ while (series.some(({ key }) => open[key])) {
236
+ if (requests >= YIELD_HISTORY_MAX_PAGES) {
237
+ throw new Error(`getYieldHistory: more than ${String(YIELD_HISTORY_MAX_PAGES)} pages for one lender`);
238
+ }
239
+ const variables = {
240
+ tranches: ids.tranches,
241
+ userTranches: ids.userTranches,
242
+ locks: ids.locks,
243
+ fromEpoch: String(window.fromEpoch),
244
+ toEpoch: String(window.toEpoch),
245
+ first: YIELD_HISTORY_PAGE_SIZE,
246
+ };
247
+ for (const { key, flag, cursor } of series) {
248
+ variables[flag] = open[key];
249
+ variables[cursor] = cursors[key];
250
+ }
251
+ const page = yield request(variables);
252
+ requests += 1;
253
+ for (const { key } of series) {
254
+ if (!open[key])
255
+ continue;
256
+ const got = (_a = page[key]) !== null && _a !== void 0 ? _a : [];
257
+ rows[key].push(...got);
258
+ const last = got.at(-1);
259
+ if (got.length < YIELD_HISTORY_PAGE_SIZE || last === undefined) {
260
+ open[key] = false;
261
+ continue;
262
+ }
263
+ if (last.id <= cursors[key]) {
264
+ throw new Error(`getYieldHistory: the ${key} cursor did not advance`);
265
+ }
266
+ cursors[key] = last.id;
267
+ }
268
+ }
269
+ return { rows, requests };
270
+ });
271
+ }
272
+ /**
273
+ * The snapshot in force at `epoch`: among updates with `epochId ≤ epoch`, the
274
+ * one with the highest `shareUpdatesIndex` — exactly what
275
+ * `lendingPortfolioQuery` selects with `orderBy: shareUpdatesIndex, desc,
276
+ * first: 1, where: { epochId_lt: epoch + 1 }`. `undefined` when none.
277
+ */
278
+ export function sharesAsOf(updates, epoch) {
279
+ let found;
280
+ for (const update of updates) {
281
+ if (Number(update.epochId) > epoch)
282
+ continue;
283
+ if (!found || update.shareUpdatesIndex > found.shareUpdatesIndex) {
284
+ found = update;
285
+ }
286
+ }
287
+ return found;
288
+ }
289
+ function groupBy(rows, key) {
290
+ const map = new Map();
291
+ for (const row of rows) {
292
+ const k = key(row);
293
+ const list = map.get(k);
294
+ if (list)
295
+ list.push(row);
296
+ else
297
+ map.set(k, [row]);
298
+ }
299
+ return map;
300
+ }
301
+ /**
302
+ * Pure: the fetched series → one entry per pool, tranche and settled epoch in
303
+ * which the lender held shares (or a lock earned a top-up). Each entry runs the
304
+ * same `epochTrancheYield` as the portfolio's last-epoch figure, with the same
305
+ * share selection, so the newest entry equals that figure.
306
+ *
307
+ * Sorted by `epochId`, then `poolId`, then `trancheId`.
308
+ */
309
+ export function buildYieldHistory(read, window, clock) {
310
+ var _a, _b, _c, _d, _e, _f;
311
+ const user = read.positions.user;
312
+ if (!user)
313
+ return [];
314
+ const interestByTranche = groupBy(read.interest, (row) => row.lendingPoolTranche.id);
315
+ const trancheSharesByTranche = groupBy(read.trancheShares, (row) => row.lendingPoolTranche.id);
316
+ const userSharesByPosition = groupBy(read.userShares, (row) => row.lendingPoolTrancheUserDetails.id);
317
+ const lockSharesByLock = groupBy(read.lockShares, (row) => row.userLendingPoolTrancheFixedTermDepositLock.id);
318
+ const entries = [];
319
+ for (const pool of user.lendingPoolUserDetails) {
320
+ for (const position of pool.lendingPoolTrancheUserDetails) {
321
+ const trancheId = position.tranche.id;
322
+ const trancheUpdates = (_a = trancheSharesByTranche.get(trancheId)) !== null && _a !== void 0 ? _a : [];
323
+ const userUpdates = (_b = userSharesByPosition.get(position.id)) !== null && _b !== void 0 ? _b : [];
324
+ const trancheBalanceNow = parseFloat(position.tranche.balance);
325
+ const trancheSharesNow = parseFloat(position.tranche.shares);
326
+ for (const record of (_c = interestByTranche.get(trancheId)) !== null && _c !== void 0 ? _c : []) {
327
+ const epoch = Number(record.epochId);
328
+ if (epoch < window.fromEpoch || epoch > window.toEpoch) {
329
+ continue;
330
+ }
331
+ const userSnapshot = sharesAsOf(userUpdates, epoch);
332
+ const userShares = parseFloat((_d = userSnapshot === null || userSnapshot === void 0 ? void 0 : userSnapshot.shares) !== null && _d !== void 0 ? _d : '0');
333
+ const locks = position.userLendingPoolTrancheFixedTermDepositLocks.map((lock) => {
334
+ var _a, _b, _c, _d, _e;
335
+ const lockUpdates = (_a = lockSharesByLock.get(lock.id)) !== null && _a !== void 0 ? _a : [];
336
+ return {
337
+ active: parseFloat(lock.epochLockStart) < epoch &&
338
+ parseFloat(lock.epochLockEnd) >= epoch &&
339
+ userSnapshot !== undefined,
340
+ // No snapshot yet = the lock's opening
341
+ // shares: a lock made after clearing E
342
+ // starts at E but its first update is
343
+ // tagged E + 1, and reading that gap as 0
344
+ // would book its principal as a top-up.
345
+ sharesAtEpoch: parseFloat((_c = (_b = sharesAsOf(lockUpdates, epoch)) === null || _b === void 0 ? void 0 : _b.shares) !== null && _c !== void 0 ? _c : lock.initialTrancheShares),
346
+ sharesBeforeEpoch: parseFloat((_e = (_d = sharesAsOf(lockUpdates, epoch - 1)) === null || _d === void 0 ? void 0 : _d.shares) !== null && _e !== void 0 ? _e : lock.initialTrancheShares),
347
+ };
348
+ });
349
+ const { total } = epochTrancheYield({
350
+ epochInterest: parseFloat(record.epochInterestAmount),
351
+ userShares,
352
+ trancheShares: parseFloat((_f = (_e = sharesAsOf(trancheUpdates, epoch)) === null || _e === void 0 ? void 0 : _e.shares) !== null && _f !== void 0 ? _f : '0'),
353
+ trancheBalanceNow,
354
+ trancheSharesNow,
355
+ locks,
356
+ });
357
+ // Nothing held and nothing topped up: not part of this
358
+ // lender's history (a new lender's history is empty).
359
+ if (userShares <= 0 && total === 0)
360
+ continue;
361
+ entries.push({
362
+ epochId: epoch,
363
+ epochEnd: clock.epochZeroStart +
364
+ (epoch + 1) * clock.epochDuration,
365
+ poolId: pool.lendingPool.id,
366
+ trancheId,
367
+ netYield: total,
368
+ });
369
+ }
370
+ }
371
+ }
372
+ return entries.sort((a, b) => a.epochId - b.epochId ||
373
+ a.poolId.localeCompare(b.poolId) ||
374
+ a.trancheId.localeCompare(b.trancheId));
375
+ }
376
+ /**
377
+ * A GraphQL VALIDATION error naming an entity, field or argument this
378
+ * deployment's subgraph does not have. A network error, a timeout or an
379
+ * indexing error is not one.
380
+ */
381
+ export function isMissingEntityError(error) {
382
+ if (typeof error !== 'object' || error === null)
383
+ return false;
384
+ const response = error.response;
385
+ const errors = response === null || response === void 0 ? void 0 : response.errors;
386
+ if (!Array.isArray(errors) || errors.length === 0)
387
+ return false;
388
+ return errors.every((e) => {
389
+ const message = e === null || e === void 0 ? void 0 : e.message;
390
+ return (typeof message === 'string' &&
391
+ /has no field|cannot query field|unknown (type|field|argument)|no such (type|field)/i.test(message));
392
+ });
393
+ }
394
+ /**
395
+ * The only failure `getYieldHistory` turns into `[]`: a missing-schema error
396
+ * on a LITE deployment, whose subgraph may predate the entities. On a full
397
+ * deployment the entities are part of the schema, so the same error means a
398
+ * broken query or subgraph and THROWS — an empty chart there would hide it.
399
+ * Everything else (network, timeout, indexing) throws on every deployment, so
400
+ * a consumer can tell "no history here" from "history unavailable".
401
+ */
402
+ export function isEmptyHistoryError(error, liteDeployment) {
403
+ return liteDeployment && isMissingEntityError(error);
404
+ }
405
+ //# sourceMappingURL=yield-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yield-history.js","sourceRoot":"","sources":["../../../src/services/Portfolio/yield-history.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kasufinance/kasu-sdk",
3
- "version": "2.11.0",
3
+ "version": "2.12.0",
4
4
  "description": "TypeScript SDK for the Kasu Finance protocol — pool data, portfolio analytics, and KYC-gated deposits and withdrawals on Base and XDC",
5
5
  "keywords": [
6
6
  "kasu",
@@ -134,11 +134,7 @@ export {
134
134
  lastEventTimestamp,
135
135
  submissionEvents,
136
136
  } from './requests';
137
- export type {
138
- RequestKind,
139
- RequestState,
140
- RequestStatusCode,
141
- } from './requests';
137
+ export type { RequestKind, RequestState, RequestStatusCode } from './requests';
142
138
 
143
139
  export { decodeRevert, extractRevertData } from './revert-errors';
144
140
  export type { DecodedRevert, RevertFamily } from './revert-errors';
@@ -183,11 +179,7 @@ export {
183
179
  trancheHasCapacity,
184
180
  trancheRiskRank,
185
181
  } from './tranches';
186
- export type {
187
- ApyBounds,
188
- PoolStatus,
189
- TrancheCapacitySignal,
190
- } from './tranches';
182
+ export type { ApyBounds, PoolStatus, TrancheCapacitySignal } from './tranches';
191
183
 
192
184
  export {
193
185
  classifyWalletFailure,
@@ -196,6 +188,16 @@ export {
196
188
  } from './wallet-errors';
197
189
  export type { WalletFailure } from './wallet-errors';
198
190
 
191
+ export { aggregateYieldHistory, epochTrancheYield } from './yield-history';
192
+ export type {
193
+ EpochLockShares,
194
+ EpochTrancheYield,
195
+ EpochTrancheYieldInput,
196
+ YieldHistoryEntry,
197
+ YieldHistoryFilter,
198
+ YieldHistoryPoint,
199
+ } from './yield-history';
200
+
199
201
  export {
200
202
  accruedInterest,
201
203
  dailyInterest,
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Settled interest per cycle (epoch): what a lender was credited at each close,
3
+ * per pool and tranche, and the one formula that turns a tranche's credited
4
+ * interest into one lender's share of it.
5
+ *
6
+ * **The figures are NET, and only net.** `LendingPool._applyTrancheInterest`
7
+ * takes the performance fee off first and emits `InterestApplied` with the
8
+ * lender side only (`interest × (1 − performanceFee)`); a fixed-term top-up
9
+ * (`_applyFixedRateInterests`) is net of the fee the same way. So the figure
10
+ * this module computes from those records IS what the lender was credited —
11
+ * never take the fee off it again. No gross is offered: the subgraph keeps
12
+ * only TODAY's fee, so a historical gross would be a guess.
13
+ *
14
+ * Units: amounts are display units of the DEPLOYMENT'S OWN stable token (USDC
15
+ * on Base and XDC USDC, AUDD on XDC AUDD) — never converted to USD here;
16
+ * shares are display units of tranche shares, as the subgraph's BigDecimals.
17
+ */
18
+
19
+ /** One fixed-term lock inside a tranche position, at one epoch. */
20
+ export interface EpochLockShares {
21
+ /**
22
+ * The lock earns in this epoch: it started BEFORE the epoch, ends at or
23
+ * after it, and the lender has a share snapshot for the tranche at it.
24
+ */
25
+ active: boolean;
26
+ /** The lock's tranche shares as of the epoch (latest update at or before it). */
27
+ sharesAtEpoch: number;
28
+ /** The lock's tranche shares as of the epoch BEFORE it. */
29
+ sharesBeforeEpoch: number;
30
+ }
31
+
32
+ /** Everything one lender's share of one tranche's credited interest needs. */
33
+ export interface EpochTrancheYieldInput {
34
+ /** The tranche's interest credited at this epoch's close (net of the fee). */
35
+ epochInterest: number;
36
+ /** The lender's tranche shares as of the epoch (fixed-term locks included). */
37
+ userShares: number;
38
+ /** The tranche's total shares as of the epoch. */
39
+ trancheShares: number;
40
+ /**
41
+ * The tranche's CURRENT balance and shares: the price a fixed-term top-up
42
+ * (extra shares) is valued at. The subgraph keeps no per-epoch tranche
43
+ * balance, so an older top-up is valued at today's price — a second-order
44
+ * difference, because the top-up is only the fixed rate's excess over the
45
+ * variable one.
46
+ */
47
+ trancheBalanceNow: number;
48
+ trancheSharesNow: number;
49
+ locks: readonly EpochLockShares[];
50
+ }
51
+
52
+ export interface EpochTrancheYield {
53
+ /** `variable + Σ locks` — the lender's whole yield in the tranche. */
54
+ total: number;
55
+ /** The variable residual: the share-weighted base yield minus the locks' part of it. */
56
+ variable: number;
57
+ /** Each lock's yield, in the order of `input.locks`: its base part plus its top-up. */
58
+ locks: number[];
59
+ }
60
+
61
+ /**
62
+ * The ONE per-epoch formula. `Portfolio.getPortfolioLendingData` runs it for
63
+ * the last settled epoch and `getYieldHistory` for every epoch of a window, so
64
+ * the two figures cannot drift apart.
65
+ *
66
+ * ```
67
+ * base = epochInterest × userShares / trancheShares
68
+ * lockBase = base × lock.sharesBeforeEpoch / userShares (active locks)
69
+ * topUp = (lock.sharesAtEpoch − lock.sharesBeforeEpoch) × trancheBalanceNow / trancheSharesNow
70
+ * variable = base − Σ lockBase
71
+ * total = variable + Σ (lockBase + topUp)
72
+ * ```
73
+ *
74
+ * A zero denominator contributes 0 (a tranche or a lender with no shares
75
+ * earned nothing at that close), never `NaN`.
76
+ */
77
+ export function epochTrancheYield(
78
+ input: EpochTrancheYieldInput,
79
+ ): EpochTrancheYield {
80
+ const base =
81
+ input.trancheShares === 0
82
+ ? 0
83
+ : (input.epochInterest * input.userShares) / input.trancheShares;
84
+
85
+ let variable = base;
86
+ const locks = input.locks.map((lock) => {
87
+ if (!lock.active) return 0;
88
+
89
+ const topUp =
90
+ input.trancheSharesNow === 0
91
+ ? 0
92
+ : ((lock.sharesAtEpoch - lock.sharesBeforeEpoch) *
93
+ input.trancheBalanceNow) /
94
+ input.trancheSharesNow;
95
+
96
+ const lockBase =
97
+ input.userShares === 0
98
+ ? 0
99
+ : (base * lock.sharesBeforeEpoch) / input.userShares;
100
+
101
+ variable -= lockBase;
102
+
103
+ return lockBase + topUp;
104
+ });
105
+
106
+ const total = locks.reduce((sum, lockYield) => sum + lockYield, variable);
107
+
108
+ return { total, variable, locks };
109
+ }
110
+
111
+ /** One lender's settled yield in one tranche at one epoch. */
112
+ export interface YieldHistoryEntry {
113
+ /** The epoch (cycle) number, in the chain's numbering. */
114
+ epochId: number;
115
+ /**
116
+ * When the epoch ended — the next epoch's start — in unix SECONDS (not
117
+ * milliseconds).
118
+ */
119
+ epochEnd: number;
120
+ /** Lowercased lending pool address. */
121
+ poolId: string;
122
+ /** Lowercased tranche address. */
123
+ trancheId: string;
124
+ /**
125
+ * What the close credited the lender in this tranche, AFTER the
126
+ * performance fee, in display units of the deployment's own stable token
127
+ * (AUDD on XDC AUDD — convert before formatting it as USD).
128
+ */
129
+ netYield: number;
130
+ }
131
+
132
+ /** One epoch's yield summed over the entries an aggregation kept. */
133
+ export interface YieldHistoryPoint {
134
+ /** The epoch (cycle) number. */
135
+ epochId: number;
136
+ /** The epoch's end, in unix SECONDS. */
137
+ epochEnd: number;
138
+ /** Net yield summed, in the deployment's own stable token. */
139
+ netYield: number;
140
+ }
141
+
142
+ export interface YieldHistoryFilter {
143
+ /** Keep only this pool (case-insensitive). */
144
+ poolId?: string;
145
+ /** Keep only this tranche (case-insensitive). */
146
+ trancheId?: string;
147
+ }
148
+
149
+ /**
150
+ * Pool/tranche entries → one point per epoch, ascending by `epochId`. With no
151
+ * filter it sums every strategy (the Lend tab's bars); with `poolId` it is one
152
+ * strategy (a strategy page's bars).
153
+ *
154
+ * An epoch appears only if a kept entry has it — there is no zero padding, so
155
+ * an empty input (a new lender) is an empty output.
156
+ *
157
+ * For a fixed-term lock holder the bars can sum to slightly MORE than the
158
+ * lender's lifetime interest (up to about 0.2 %): each top-up is valued at the
159
+ * tranche's CURRENT share price, which has grown since. Show "earned to date"
160
+ * from the lifetime figure, never from this sum.
161
+ */
162
+ export function aggregateYieldHistory(
163
+ entries: readonly YieldHistoryEntry[],
164
+ filter: YieldHistoryFilter = {},
165
+ ): YieldHistoryPoint[] {
166
+ const poolId = filter.poolId?.toLowerCase();
167
+ const trancheId = filter.trancheId?.toLowerCase();
168
+ const byEpoch = new Map<number, YieldHistoryPoint>();
169
+
170
+ for (const entry of entries) {
171
+ if (poolId !== undefined && entry.poolId.toLowerCase() !== poolId) {
172
+ continue;
173
+ }
174
+ if (
175
+ trancheId !== undefined &&
176
+ entry.trancheId.toLowerCase() !== trancheId
177
+ ) {
178
+ continue;
179
+ }
180
+
181
+ const point = byEpoch.get(entry.epochId);
182
+ if (point) {
183
+ point.netYield += entry.netYield;
184
+ continue;
185
+ }
186
+ byEpoch.set(entry.epochId, {
187
+ epochId: entry.epochId,
188
+ epochEnd: entry.epochEnd,
189
+ netYield: entry.netYield,
190
+ });
191
+ }
192
+
193
+ return [...byEpoch.values()].sort((a, b) => a.epochId - b.epochId);
194
+ }