@kasufinance/kasu-sdk 2.15.0 → 2.17.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 (42) hide show
  1. package/README.md +17 -1
  2. package/dist/bundle.cjs.js +1144 -221
  3. package/dist/bundle.esm.js +1141 -222
  4. package/dist/domain/index.d.ts +2 -2
  5. package/dist/domain/index.js +1 -1
  6. package/dist/domain/index.js.map +1 -1
  7. package/dist/domain/yield-history.d.ts +25 -1
  8. package/dist/domain/yield-history.js +41 -11
  9. package/dist/domain/yield-history.js.map +1 -1
  10. package/dist/domain/yields.d.ts +200 -15
  11. package/dist/domain/yields.js +211 -14
  12. package/dist/domain/yields.js.map +1 -1
  13. package/dist/services/Locking/locking.d.ts +12 -0
  14. package/dist/services/Locking/locking.js +16 -0
  15. package/dist/services/Locking/locking.js.map +1 -1
  16. package/dist/services/Portfolio/portfolio.d.ts +54 -1
  17. package/dist/services/Portfolio/portfolio.js +357 -80
  18. package/dist/services/Portfolio/portfolio.js.map +1 -1
  19. package/dist/services/Portfolio/queries.d.ts +16 -0
  20. package/dist/services/Portfolio/queries.js +119 -2
  21. package/dist/services/Portfolio/queries.js.map +1 -1
  22. package/dist/services/Portfolio/types.d.ts +94 -6
  23. package/dist/services/Portfolio/yield-history.d.ts +148 -10
  24. package/dist/services/Portfolio/yield-history.js +241 -31
  25. package/dist/services/Portfolio/yield-history.js.map +1 -1
  26. package/dist/services/UserLending/user-lending.d.ts +16 -0
  27. package/dist/services/UserLending/user-lending.js +24 -0
  28. package/dist/services/UserLending/user-lending.js.map +1 -1
  29. package/dist/services/shared.d.ts +7 -0
  30. package/dist/services/shared.js +48 -0
  31. package/dist/services/shared.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/domain/index.ts +5 -1
  34. package/src/domain/yield-history.ts +47 -11
  35. package/src/domain/yields.ts +257 -15
  36. package/src/services/Locking/locking.ts +17 -0
  37. package/src/services/Portfolio/portfolio.ts +559 -126
  38. package/src/services/Portfolio/queries.ts +119 -2
  39. package/src/services/Portfolio/types.ts +91 -6
  40. package/src/services/Portfolio/yield-history.ts +405 -56
  41. package/src/services/UserLending/user-lending.ts +46 -0
  42. package/src/services/shared.ts +48 -0
@@ -64,5 +64,5 @@ export { combineTvlHistories, poolTvl, TvlHistoryInputError } from './pool-tvl';
64
64
  export type { PoolTvlHistoryEntry, StrategyTvlHistoryEntry, TvlHistoryInputErrorCode, TvlHistoryLeg, } from './pool-tvl';
65
65
  export { aggregateYieldHistory, epochTrancheYield } from './yield-history';
66
66
  export type { EpochLockShares, EpochTrancheYield, EpochTrancheYieldInput, YieldHistoryEntry, YieldHistoryFilter, YieldHistoryPoint, } from './yield-history';
67
- export { accruedInterest, dailyInterest, projectedAnnualEarnings, SECONDS_PER_WEEK, weeklyInterest, weightedAverageApy, } from './yields';
68
- export type { InterestSlice, PoolAccrual } from './yields';
67
+ export { accruedInterest, accruedInterestSince, dailyInterest, epochAccrual, epochEndTimestamp, MAX_ACCRUAL_EPOCHS, projectedAnnualEarnings, SECONDS_PER_WEEK, weeklyInterest, weightedAverageApy, } from './yields';
68
+ export type { EpochAccrual, InterestSlice, PoolAccrual } from './yields';
@@ -46,5 +46,5 @@ export { compareTrancheSeniority, derivePoolStatus, MIN_TRANCHE_CAPACITY, netTra
46
46
  export { classifyWalletFailure, isUnpredictableGas, isUserRejected, } from './wallet-errors';
47
47
  export { combineTvlHistories, poolTvl, TvlHistoryInputError } from './pool-tvl';
48
48
  export { aggregateYieldHistory, epochTrancheYield } from './yield-history';
49
- export { accruedInterest, dailyInterest, projectedAnnualEarnings, SECONDS_PER_WEEK, weeklyInterest, weightedAverageApy, } from './yields';
49
+ export { accruedInterest, accruedInterestSince, dailyInterest, epochAccrual, epochEndTimestamp, MAX_ACCRUAL_EPOCHS, projectedAnnualEarnings, SECONDS_PER_WEEK, weeklyInterest, weightedAverageApy, } from './yields';
50
50
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/domain/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/domain/index.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,24 @@
15
15
  * on Base and XDC USDC, AUDD on XDC AUDD) — never converted to USD here;
16
16
  * shares are display units of tranche shares, as the subgraph's BigDecimals.
17
17
  */
18
+ /**
19
+ * The largest change of a lock's shares, as a fraction of its shares before
20
+ * the epoch, that one clearing's fixed-term top-up can make. The top-up is
21
+ * the fixed rate's difference from the variable one on one week's balance
22
+ * (`LendingPool._applyFixedRateInterests`) — a fraction of a percent at any
23
+ * real rate — so 5 % is far above every genuine top-up and far below any
24
+ * accounting fault. A lock-share change outside it is NOT interest: it is
25
+ * left out of the yield (a share movement is never booked as yield). The one
26
+ * seen on chain is a lock whose recorded shares were set to zero at a
27
+ * clearing while its shares stayed in the fixed-term contract.
28
+ */
29
+ export declare const MAX_TOP_UP_SHARE_FRACTION = 0.05;
30
+ /**
31
+ * True when `sharesBefore → sharesAt` is a change one clearing's fixed-term
32
+ * top-up can make (`MAX_TOP_UP_SHARE_FRACTION`). A lock with no shares
33
+ * before has no top-up at all.
34
+ */
35
+ export declare function isPlausibleTopUp(sharesBefore: number, sharesAt: number): boolean;
18
36
  /** One fixed-term lock inside a tranche position, at one epoch. */
19
37
  export interface EpochLockShares {
20
38
  /**
@@ -68,7 +86,13 @@ export interface EpochTrancheYield {
68
86
  * ```
69
87
  *
70
88
  * A zero denominator contributes 0 (a tranche or a lender with no shares
71
- * earned nothing at that close), never `NaN`.
89
+ * earned nothing at that close), never `NaN`. So does a NEGATIVE share count:
90
+ * the subgraph's per-lender shares are a ledger of accepted deposits and
91
+ * withdrawals, and a wallet whose shares arrived some other way (moved to it
92
+ * from another wallet) withdraws more than the ledger ever credited it. That
93
+ * position is not one the ledger can describe, and its negative count is not
94
+ * a negative yield. A lock-share change no top-up can make
95
+ * (`isPlausibleTopUp`) contributes no top-up.
72
96
  */
73
97
  export declare function epochTrancheYield(input: EpochTrancheYieldInput): EpochTrancheYield;
74
98
  /** One lender's settled yield in one tranche at one epoch. */
@@ -15,6 +15,29 @@
15
15
  * on Base and XDC USDC, AUDD on XDC AUDD) — never converted to USD here;
16
16
  * shares are display units of tranche shares, as the subgraph's BigDecimals.
17
17
  */
18
+ /**
19
+ * The largest change of a lock's shares, as a fraction of its shares before
20
+ * the epoch, that one clearing's fixed-term top-up can make. The top-up is
21
+ * the fixed rate's difference from the variable one on one week's balance
22
+ * (`LendingPool._applyFixedRateInterests`) — a fraction of a percent at any
23
+ * real rate — so 5 % is far above every genuine top-up and far below any
24
+ * accounting fault. A lock-share change outside it is NOT interest: it is
25
+ * left out of the yield (a share movement is never booked as yield). The one
26
+ * seen on chain is a lock whose recorded shares were set to zero at a
27
+ * clearing while its shares stayed in the fixed-term contract.
28
+ */
29
+ export const MAX_TOP_UP_SHARE_FRACTION = 0.05;
30
+ /**
31
+ * True when `sharesBefore → sharesAt` is a change one clearing's fixed-term
32
+ * top-up can make (`MAX_TOP_UP_SHARE_FRACTION`). A lock with no shares
33
+ * before has no top-up at all.
34
+ */
35
+ export function isPlausibleTopUp(sharesBefore, sharesAt) {
36
+ if (!(sharesBefore > 0))
37
+ return sharesAt === sharesBefore;
38
+ return (Math.abs(sharesAt - sharesBefore) <=
39
+ sharesBefore * MAX_TOP_UP_SHARE_FRACTION);
40
+ }
18
41
  /**
19
42
  * The ONE per-epoch formula. `Portfolio.getPortfolioLendingData` runs it for
20
43
  * the last settled epoch and `getYieldHistory` for every epoch of a window, so
@@ -29,24 +52,31 @@
29
52
  * ```
30
53
  *
31
54
  * A zero denominator contributes 0 (a tranche or a lender with no shares
32
- * earned nothing at that close), never `NaN`.
55
+ * earned nothing at that close), never `NaN`. So does a NEGATIVE share count:
56
+ * the subgraph's per-lender shares are a ledger of accepted deposits and
57
+ * withdrawals, and a wallet whose shares arrived some other way (moved to it
58
+ * from another wallet) withdraws more than the ledger ever credited it. That
59
+ * position is not one the ledger can describe, and its negative count is not
60
+ * a negative yield. A lock-share change no top-up can make
61
+ * (`isPlausibleTopUp`) contributes no top-up.
33
62
  */
34
63
  export function epochTrancheYield(input) {
35
- const base = input.trancheShares === 0
36
- ? 0
37
- : (input.epochInterest * input.userShares) / input.trancheShares;
64
+ const base = input.trancheShares > 0 && input.userShares > 0
65
+ ? (input.epochInterest * input.userShares) / input.trancheShares
66
+ : 0;
38
67
  let variable = base;
39
68
  const locks = input.locks.map((lock) => {
40
69
  if (!lock.active)
41
70
  return 0;
42
- const topUp = input.trancheSharesNow === 0
43
- ? 0
44
- : ((lock.sharesAtEpoch - lock.sharesBeforeEpoch) *
71
+ const topUp = input.trancheSharesNow > 0 &&
72
+ isPlausibleTopUp(lock.sharesBeforeEpoch, lock.sharesAtEpoch)
73
+ ? ((lock.sharesAtEpoch - lock.sharesBeforeEpoch) *
45
74
  input.trancheBalanceNow) /
46
- input.trancheSharesNow;
47
- const lockBase = input.userShares === 0
48
- ? 0
49
- : (base * lock.sharesBeforeEpoch) / input.userShares;
75
+ input.trancheSharesNow
76
+ : 0;
77
+ const lockBase = input.userShares > 0
78
+ ? (base * lock.sharesBeforeEpoch) / input.userShares
79
+ : 0;
50
80
  variable -= lockBase;
51
81
  return lockBase + topUp;
52
82
  });
@@ -1 +1 @@
1
- {"version":3,"file":"yield-history.js","sourceRoot":"","sources":["../../src/domain/yield-history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AA4CH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAC7B,KAA6B;IAE7B,MAAM,IAAI,GACN,KAAK,CAAC,aAAa,KAAK,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,aAAa,CAAC;IAEzE,IAAI,QAAQ,GAAG,IAAI,CAAC;IACpB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACnC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,CAAC;QAE3B,MAAM,KAAK,GACP,KAAK,CAAC,gBAAgB,KAAK,CAAC;YACxB,CAAC,CAAC,CAAC;YACH,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,iBAAiB,CAAC;gBAC1C,KAAK,CAAC,iBAAiB,CAAC;gBAC5B,KAAK,CAAC,gBAAgB,CAAC;QAEjC,MAAM,QAAQ,GACV,KAAK,CAAC,UAAU,KAAK,CAAC;YAClB,CAAC,CAAC,CAAC;YACH,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC;QAE7D,QAAQ,IAAI,QAAQ,CAAC;QAErB,OAAO,QAAQ,GAAG,KAAK,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,GAAG,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC;IAE1E,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AACtC,CAAC;AAwCD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,qBAAqB,CACjC,OAAqC,EACrC,SAA6B,EAAE;;IAE/B,MAAM,MAAM,GAAG,MAAA,MAAM,CAAC,MAAM,0CAAE,WAAW,EAAE,CAAC;IAC5C,MAAM,SAAS,GAAG,MAAA,MAAM,CAAC,SAAS,0CAAE,WAAW,EAAE,CAAC;IAClD,MAAM,OAAO,GAAG,IAAI,GAAG,EAA6B,CAAC;IAErD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;YAChE,SAAS;QACb,CAAC;QACD,IACI,SAAS,KAAK,SAAS;YACvB,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,SAAS,EAC7C,CAAC;YACC,SAAS;QACb,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,KAAK,EAAE,CAAC;YACR,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC;YACjC,SAAS;QACb,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE;YACvB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SAC3B,CAAC,CAAC;IACP,CAAC;IAED,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;AACvE,CAAC"}
1
+ {"version":3,"file":"yield-history.js","sourceRoot":"","sources":["../../src/domain/yield-history.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,16 @@
18
18
  * window, and the accrual spreads an epoch's interest across it.
19
19
  */
20
20
  export declare const SECONDS_PER_WEEK = 604800;
21
+ /**
22
+ * The most unpaid interest {@link accruedInterestSince} shows for one pool:
23
+ * this many epochs of its weekly figure (owner ruling 2026-11-07).
24
+ *
25
+ * A pool can stop being cleared without being flagged stopped. On Base, one
26
+ * pool is more than six epochs behind and another more than a hundred.
27
+ * Uncapped, the figure would show interest that may never be credited. Four
28
+ * epochs covers any ordinary late clearing.
29
+ */
30
+ export declare const MAX_ACCRUAL_EPOCHS = 4;
21
31
  /** One slice of a position that earns at a single rate. */
22
32
  export interface InterestSlice {
23
33
  /** The lender's current balance in this slice, in stable display units. */
@@ -34,13 +44,41 @@ export interface InterestSlice {
34
44
  */
35
45
  epochRate: number;
36
46
  }
37
- /** One pool's contribution to the current cycle's accrual. */
47
+ /**
48
+ * One pool's contribution to the current cycle's accrual, anchored on its
49
+ * last clearing TRANSACTION.
50
+ *
51
+ * @deprecated Use {@link EpochAccrual} with {@link accruedInterestSince}. A
52
+ * clearing transaction's timestamp is the wrong anchor: see `accruedInterest`.
53
+ */
38
54
  export interface PoolAccrual {
39
55
  /** That pool's projected net interest for one epoch (`weeklyInterest`). */
40
56
  weeklyInterest: number;
41
57
  /** Unix SECONDS at which THAT pool last cleared a cycle. */
42
58
  clearingTimestamp: number;
43
59
  }
60
+ /**
61
+ * One pool's contribution to the unpaid interest — the input of
62
+ * {@link accruedInterestSince}. Build it with {@link epochAccrual}, which
63
+ * validates every field and derives the two timestamps; do not assemble one
64
+ * by hand.
65
+ */
66
+ export interface EpochAccrual {
67
+ /** That pool's projected net interest for one epoch (`weeklyInterest`). */
68
+ weeklyInterest: number;
69
+ /**
70
+ * Unix SECONDS from which the pool's unpaid interest accrues: the earlier
71
+ * of the moment its last clearing STARTED and `epochEnd`. Never after
72
+ * `epochEnd`.
73
+ */
74
+ accruesFrom: number;
75
+ /**
76
+ * Unix SECONDS at which the pool's last cleared epoch ENDS
77
+ * (`epochEndTimestamp(initialEpochStart, lastClearedEpoch)`). The accrual
78
+ * holds from `accruesFrom + 604 800` until `epochEnd + 604 800`.
79
+ */
80
+ epochEnd: number;
81
+ }
44
82
  /**
45
83
  * Interest the slices accrue over ONE epoch, net of the fee.
46
84
  *
@@ -72,30 +110,177 @@ export declare function weeklyInterest(slices: readonly InterestSlice[], feePerc
72
110
  */
73
111
  export declare function dailyInterest(weekly: number | null): number | null;
74
112
  /**
75
- * Interest accrued since each pool's OWN last cycle close — the moving part
76
- * of lifetime interest (lifetime = settled on chain + this).
113
+ * Interest accrued since each pool's OWN last clearing transaction, capped at
114
+ * one epoch per pool.
77
115
  *
78
116
  * ```
79
117
  * accrued = Σ_pool weekly_pool × clamp(now − clearing_pool, 0, 604 800) / 604 800
80
118
  * ```
81
119
  *
82
- * **Per pool, because pools clear independently.** One shared anchor accrues
83
- * a stale pool's share from a close it never had, and when another pool
84
- * clears it drops the total by the stale pool's whole weekly share. Anchored
85
- * per pool, the total is monotonic across any single pool's clearing: that
86
- * pool swaps its accrued share for a capitalised one, and no other term moves.
120
+ * Per pool, because pools clear independently: one shared anchor accrues a
121
+ * stale pool's share from a close it never had, and when another pool clears
122
+ * it drops the total by the stale pool's whole weekly share.
87
123
  *
88
- * **The one-epoch cap is load-bearing.** Clearings are not exactly weekly,
89
- * and `weeklyInterest` is per EPOCH, so extrapolating past seven days would
90
- * promise interest the close will not credit; a late clearing under-reports
91
- * instead. The low clamp makes a close ahead of the local clock zero, never
92
- * negative.
124
+ * **The one-epoch cap rested on a wrong premise.** It assumed a late
125
+ * clearing credits only one epoch, so accruing past seven days would promise
126
+ * interest no close pays. The contracts say otherwise. `ClearingCoordinator`
127
+ * states that clearing "must be executed once per epoch for each lending
128
+ * pool" and that "clearing epochs must be executed in order"; `doClearing`
129
+ * reverts unless the target epoch is the pool's `nextLendingPoolClearingEpoch`,
130
+ * and it calls `LendingPool.applyInterests(targetEpoch)` on every clearing,
131
+ * late or not ("if clearing for the epoch is executed only after the clearing
132
+ * period, only yield will be applied"). Every missed epoch's interest is
133
+ * therefore credited when the clearings catch up — on Base, one pool cleared
134
+ * epochs 116 to 119 in the same block, four weeks credited at once.
135
+ * Capped, the figure freezes as soon as a clearing is a week late; anchored
136
+ * on the transaction, it also DIPS on a catch-up (elapsed resets to zero
137
+ * while only one week is credited per clearing).
93
138
  *
139
+ * The low clamp makes a close ahead of the local clock zero, never negative.
94
140
  * A non-finite term contributes nothing; a non-finite `nowSeconds` accrues
95
- * nothing. Callers fail closed upstream by not offering an accrual for a pool
96
- * whose weekly interest or anchor is unknown.
141
+ * nothing.
142
+ *
143
+ * @deprecated Use {@link accruedInterestSince} with {@link epochAccrual}:
144
+ * identical while clearings run on time, and it keeps accruing, up to four
145
+ * epochs, when they run late. Kept, behaviour-identical, for existing
146
+ * callers.
97
147
  */
98
148
  export declare function accruedInterest(accruals: readonly PoolAccrual[], nowSeconds: number): number;
149
+ /**
150
+ * Unix SECONDS at which `epoch` ENDS — the start of the next one:
151
+ *
152
+ * ```
153
+ * epochEnd(e) = initialEpochStart + (e + 1) × 604 800
154
+ * ```
155
+ *
156
+ * `SystemVariables.epochStartTimestamp(e)` is `initial + e × EPOCH_DURATION`,
157
+ * and the initial start is written once, at initialisation, so the end of any
158
+ * epoch follows from `epochStartTimestamp(0)` with no further read.
159
+ *
160
+ * FAILS CLOSED with `NaN`: a non-integer, non-finite or negative epoch, an
161
+ * initial start that is not a positive integer, or a result past
162
+ * `Number.MAX_SAFE_INTEGER`.
163
+ *
164
+ * @param initialEpochStart unix seconds, `epochStartTimestamp(0)`.
165
+ * @param epoch the epoch number, 0-based.
166
+ */
167
+ export declare function epochEndTimestamp(initialEpochStart: number, epoch: number): number;
168
+ /**
169
+ * Builds one pool's {@link EpochAccrual} from what the chain and the subgraph
170
+ * say about its last clearing:
171
+ *
172
+ * ```
173
+ * epochEnd = epochEndTimestamp(initialEpochStart, lastClearedEpoch)
174
+ * accruesFrom = min(clearingStartedAt, epochEnd)
175
+ * ```
176
+ *
177
+ * - **On time** (the clearing started inside its window, before its epoch
178
+ * ended): `accruesFrom` is the clearing's start. Clearing applies the
179
+ * epoch's interest first and only then accepts requests, so today's
180
+ * balance — which includes a deposit that clearing accepted — is never
181
+ * charged for time before it: a new lender starts at exactly 0.
182
+ * - **Late** (it started at or after its epoch end): `accruesFrom` is the
183
+ * epoch end. A late clearing accepts no requests and credits exactly one
184
+ * epoch, so the time it did not credit is kept.
185
+ *
186
+ * `lastClearedEpoch` is the newest epoch whose clearing has STARTED (the
187
+ * subgraph has a `LendingPoolClearing` for it), not the newest that has
188
+ * finished, and `clearingStartedAt` is that clearing's `startTimestamp`.
189
+ * `ClearingCoordinator.doClearing` mints the epoch's interest in its FIRST
190
+ * transaction (`applyInterests`), while `nextLendingPoolClearingEpoch` only
191
+ * advances when the clearing ENDS, so it is the wrong source.
192
+ *
193
+ * FAILS CLOSED with `null`: whatever {@link epochEndTimestamp} rejects, a
194
+ * `clearingStartedAt` that is not a finite positive number, or a
195
+ * `weeklyInterest` that is not finite or is negative. The caller then offers
196
+ * no term for that pool.
197
+ *
198
+ * @param input.weeklyInterest the pool's net interest for one epoch.
199
+ * @param input.initialEpochStart unix seconds, `epochStartTimestamp(0)`.
200
+ * @param input.lastClearedEpoch the newest epoch whose clearing has started.
201
+ * @param input.clearingStartedAt unix seconds, that clearing's start.
202
+ */
203
+ export declare function epochAccrual(input: {
204
+ weeklyInterest: number;
205
+ initialEpochStart: number;
206
+ lastClearedEpoch: number;
207
+ clearingStartedAt: number;
208
+ }): EpochAccrual | null;
209
+ /**
210
+ * Interest accrued and not yet credited — the moving part of lifetime
211
+ * interest (lifetime = settled on chain + this). Per term, with
212
+ * `W` = 604 800 s:
213
+ *
214
+ * ```
215
+ * accrued = min( weekly × ( min(max(now − accruesFrom, 0), W)
216
+ * + max(now − (epochEnd + W), 0) ) / W,
217
+ * MAX_ACCRUAL_EPOCHS × weekly )
218
+ * ```
219
+ *
220
+ * Read along the clock, for one pool:
221
+ * 1. from `accruesFrom`, one epoch of interest accrues linearly;
222
+ * 2. it then HOLDS from `accruesFrom + W` until `epochEnd + W`, the end of
223
+ * the next epoch — at most 48 h after an on-time clearing (its clearing
224
+ * window), no time at all after a late one (`accruesFrom == epochEnd`);
225
+ * 3. then it resumes, up to {@link MAX_ACCRUAL_EPOCHS} (4) epochs of
226
+ * `weekly` in all, and stops there.
227
+ *
228
+ * **Why it keeps accruing past one epoch.** Clearing must run once per
229
+ * epoch for each pool, in order (`ClearingCoordinator.doClearing` reverts
230
+ * unless the target epoch is the pool's `nextLendingPoolClearingEpoch`), and
231
+ * every clearing, on time or late, starts with
232
+ * `LendingPool.applyInterests(targetEpoch)`. A missed epoch is therefore
233
+ * credited when the clearings catch up (on Base, one pool cleared epochs 116
234
+ * to 119 in the same block).
235
+ *
236
+ * **Why it stops at four epochs** (owner ruling 2026-11-07). A pool can stop
237
+ * being cleared without being flagged stopped. On Base today one pool is
238
+ * more than six epochs behind and another more than a hundred. Past four
239
+ * epochs the figure would show interest that may never be credited. Four
240
+ * covers any ordinary late clearing.
241
+ *
242
+ * **Why the hold.** Without it an on-time anchor accrues past one epoch
243
+ * while the next on-time clearing — which credits exactly one — is still to
244
+ * come, and the total steps DOWN when it lands. With it, at a constant
245
+ * balance, `settled + accrued` never steps down at a clearing (`t` = the
246
+ * NEW clearing's start, `E` = the end of the epoch it clears):
247
+ * - on time after on time: identical to the deprecated `accruedInterest`
248
+ * at every instant. With `gap` = `t` − the previous clearing's start, it
249
+ * steps UP by `weekly × (W − gap) / W` when `gap < W` (at most 2/7 of one
250
+ * weekly, as before), and not at all when `gap ≥ W`;
251
+ * - late after on time, and late after late: exactly continuous;
252
+ * - on time after late: steps UP by `weekly × (E − t) / W`, at most 2/7 of
253
+ * one weekly.
254
+ * A deposit or top-up accepted at an on-time clearing starts at 0, so it
255
+ * never dips the total either.
256
+ *
257
+ * **At the cap.** All of the above holds for a pool below the cap, where
258
+ * the cap changes nothing. A pool AT the cap shows `4 × weekly` before a
259
+ * catch-up clearing; after it, one more `weekly` is settled and the new term
260
+ * shows `min(elapsed′, 4 × weekly)`, so `after = min(elapsed′ + weekly,
261
+ * 5 × weekly) ≥ before`. Each clearing steps the total UP by at most one
262
+ * weekly until the pool is back under the cap. Never down, and never above
263
+ * settled plus `4 × weekly` per pool.
264
+ *
265
+ * **Never negative**, and non-decreasing in `now`. A term is skipped — it
266
+ * contributes nothing — when `weeklyInterest` is not finite or is negative,
267
+ * when `accruesFrom` or `epochEnd` is not finite, when `accruesFrom >
268
+ * epochEnd` (inconsistent: the anchor is never after the epoch end), or when
269
+ * an elapsed time overflows. A non-finite `nowSeconds` accrues nothing.
270
+ *
271
+ * **Per pool, because pools clear independently.** One shared anchor accrues
272
+ * a stale pool's share from a close it never had.
273
+ *
274
+ * **Accepted approximation.** `weekly` is today's balance at today's epoch
275
+ * rate. Over a multi-week gap the chain compounds weekly and may apply other
276
+ * epochs' rates, so a long accrual reads slightly LOW. It is a ticking
277
+ * estimate, not a ledger.
278
+ *
279
+ * Callers fail closed upstream: no term for a pool whose weekly interest or
280
+ * clearing is unknown (`epochAccrual` returns `null`), and none for a pool
281
+ * that has stopped — it will never clear again.
282
+ */
283
+ export declare function accruedInterestSince(accruals: readonly EpochAccrual[], nowSeconds: number): number;
99
284
  /**
100
285
  * What `amount` earns in a year at a NET rate — the "≈ $Y a year" line.
101
286
  * Feed it `netEffectiveApy(...)`, never a gross APY: the result is a lender's