@kasufinance/kasu-sdk 2.11.0 → 2.13.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 (70) hide show
  1. package/README.md +37 -2
  2. package/dist/bundle.cjs.js +1516 -71
  3. package/dist/bundle.esm.js +1511 -73
  4. package/dist/domain/index.d.ts +6 -2
  5. package/dist/domain/index.js +2 -0
  6. package/dist/domain/index.js.map +1 -1
  7. package/dist/domain/pool-tvl.d.ts +62 -0
  8. package/dist/domain/pool-tvl.js +22 -0
  9. package/dist/domain/pool-tvl.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/chain-configs.js +24 -0
  14. package/dist/facade/chain-configs.js.map +1 -1
  15. package/dist/facade/kasu.d.ts +2 -0
  16. package/dist/facade/kasu.js +13 -5
  17. package/dist/facade/kasu.js.map +1 -1
  18. package/dist/facade/strategies.d.ts +20 -0
  19. package/dist/facade/strategies.js +22 -0
  20. package/dist/facade/strategies.js.map +1 -1
  21. package/dist/facade/types.d.ts +20 -0
  22. package/dist/facade/user-portfolio.d.ts +32 -0
  23. package/dist/facade/user-portfolio.js +34 -0
  24. package/dist/facade/user-portfolio.js.map +1 -1
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/kasu-sdk.d.ts +9 -1
  29. package/dist/kasu-sdk.js +2 -2
  30. package/dist/kasu-sdk.js.map +1 -1
  31. package/dist/sdk-config.d.ts +20 -0
  32. package/dist/sdk-config.js +3 -1
  33. package/dist/sdk-config.js.map +1 -1
  34. package/dist/services/DataService/data-service.d.ts +81 -1
  35. package/dist/services/DataService/data-service.js +211 -6
  36. package/dist/services/DataService/data-service.js.map +1 -1
  37. package/dist/services/DataService/tvl-history.d.ts +296 -0
  38. package/dist/services/DataService/tvl-history.js +558 -0
  39. package/dist/services/DataService/tvl-history.js.map +1 -0
  40. package/dist/services/Portfolio/portfolio.d.ts +37 -0
  41. package/dist/services/Portfolio/portfolio.js +136 -56
  42. package/dist/services/Portfolio/portfolio.js.map +1 -1
  43. package/dist/services/Portfolio/queries.js +15 -3
  44. package/dist/services/Portfolio/queries.js.map +1 -1
  45. package/dist/services/Portfolio/types.d.ts +8 -0
  46. package/dist/services/Portfolio/yield-history.d.ts +162 -0
  47. package/dist/services/Portfolio/yield-history.js +378 -0
  48. package/dist/services/Portfolio/yield-history.js.map +1 -0
  49. package/dist/services/shared.d.ts +16 -0
  50. package/dist/services/shared.js +30 -0
  51. package/dist/services/shared.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/domain/index.ts +15 -10
  54. package/src/domain/pool-tvl.ts +66 -0
  55. package/src/domain/yield-history.ts +194 -0
  56. package/src/facade/chain-configs.ts +26 -0
  57. package/src/facade/kasu.ts +20 -2
  58. package/src/facade/strategies.ts +26 -0
  59. package/src/facade/types.ts +20 -0
  60. package/src/facade/user-portfolio.ts +40 -3
  61. package/src/index.ts +11 -0
  62. package/src/kasu-sdk.ts +19 -2
  63. package/src/sdk-config.ts +22 -0
  64. package/src/services/DataService/data-service.ts +296 -6
  65. package/src/services/DataService/tvl-history.ts +853 -0
  66. package/src/services/Portfolio/portfolio.ts +263 -153
  67. package/src/services/Portfolio/queries.ts +15 -3
  68. package/src/services/Portfolio/types.ts +10 -0
  69. package/src/services/Portfolio/yield-history.ts +541 -0
  70. package/src/services/shared.ts +38 -1
@@ -1,5 +1,5 @@
1
- import { formatUnits, parseUnits, formatEther, defaultAbiCoder, parseEther } from 'ethers/lib/utils';
2
- import { utils as utils$2, Contract, BigNumber, ethers, constants, Signer, providers } from 'ethers';
1
+ import { utils as utils$2, Contract, BigNumber, providers, ethers, constants, Signer } from 'ethers';
2
+ import { Interface, formatUnits, isAddress, parseUnits, formatEther, defaultAbiCoder, parseEther } from 'ethers/lib/utils';
3
3
 
4
4
  const defaultJsonSerializer = JSON;
5
5
 
@@ -15479,6 +15479,28 @@ class KasuPoolExternalTVLAbi__factory {
15479
15479
  }
15480
15480
  KasuPoolExternalTVLAbi__factory.abi = _abi;
15481
15481
 
15482
+ /**
15483
+ * A pool's TVL — the total lent into it by everyone — and the shape of its
15484
+ * history. Pure: the reads live in `services/DataService/tvl-history.ts`.
15485
+ */
15486
+ /**
15487
+ * The ONE definition of a pool's TVL: what lenders have lent on chain
15488
+ * (`lendingPool.balance` in the subgraph) plus the off-chain amount the
15489
+ * pool's `ExternalTVL` contract records (`externalTVLOfPool`).
15490
+ *
15491
+ * Both inputs, and the result, are in DISPLAY units of the deployment's own
15492
+ * stable token (USDC on Base and XDC USDC, AUDD on XDC AUDD) — `'1.5'` is
15493
+ * one and a half tokens, not 1.5 minor units. Convert before formatting as
15494
+ * USD.
15495
+ *
15496
+ * `getPoolOverview` (its `totalValueLocked.total`) and `getPoolTvlHistory`
15497
+ * (every point) both call this, so the chart and the figure above it cannot
15498
+ * be computed two ways.
15499
+ */
15500
+ function poolTvl(onChainBalance, offChainTvl) {
15501
+ return parseFloat(onChainBalance) + parseFloat(offChainTvl);
15502
+ }
15503
+
15482
15504
  /**
15483
15505
  * Epochs in a year — the single source of truth for every rate conversion in
15484
15506
  * the SDK and in every consumer that undoes one.
@@ -15718,6 +15740,36 @@ function filterArray(array, id_in) {
15718
15740
  });
15719
15741
  }
15720
15742
  }
15743
+ /**
15744
+ * A GraphQL VALIDATION error naming an entity, field or argument this
15745
+ * deployment's subgraph does not have. A network error, a timeout or an
15746
+ * indexing error is not one.
15747
+ */
15748
+ function isMissingEntityError(error) {
15749
+ if (typeof error !== 'object' || error === null)
15750
+ return false;
15751
+ const response = error.response;
15752
+ const errors = response === null || response === void 0 ? void 0 : response.errors;
15753
+ if (!Array.isArray(errors) || errors.length === 0)
15754
+ return false;
15755
+ return errors.every((e) => {
15756
+ const message = e === null || e === void 0 ? void 0 : e.message;
15757
+ return (typeof message === 'string' &&
15758
+ /has no field|cannot query field|unknown (type|field|argument)|no such (type|field)/i.test(message));
15759
+ });
15760
+ }
15761
+ /**
15762
+ * The only failure a history read (`getYieldHistory`, `getPoolTvlHistory`)
15763
+ * turns into `[]`: a missing-schema error
15764
+ * on a LITE deployment, whose subgraph may predate the entities. On a full
15765
+ * deployment the entities are part of the schema, so the same error means a
15766
+ * broken query or subgraph and THROWS — an empty chart there would hide it.
15767
+ * Everything else (network, timeout, indexing) throws on every deployment, so
15768
+ * a consumer can tell "no history here" from "history unavailable".
15769
+ */
15770
+ function isEmptyHistoryError(error, liteDeployment) {
15771
+ return liteDeployment && isMissingEntityError(error);
15772
+ }
15721
15773
 
15722
15774
  /**
15723
15775
  * The error a CMS-only call raises on a deployment configured without
@@ -15907,11 +15959,569 @@ const getLendingPoolWithdrawalAndDepositsQuery = gql `
15907
15959
  }
15908
15960
  `;
15909
15961
 
15962
+ /** Clearings per subgraph page (the subgraph's own row cap). */
15963
+ const TVL_HISTORY_PAGE_SIZE = 1000;
15964
+ /**
15965
+ * A runaway guard on the clearings pagination, not a working limit: a pool
15966
+ * clears once per epoch, so 20 pages would be twenty thousand cycles.
15967
+ */
15968
+ const TVL_HISTORY_MAX_PAGES = 20;
15969
+ /**
15970
+ * How many past blocks one time-travel request reads, as GraphQL aliases. A
15971
+ * pool with ~120 clearings costs two such requests.
15972
+ */
15973
+ const TVL_HISTORY_BLOCKS_PER_REQUEST = 100;
15974
+ /** At most this many past-state RPC reads are in flight at once. */
15975
+ const TVL_HISTORY_RPC_CONCURRENCY = 8;
15976
+ /** Attempts per past-state RPC read (each over every history RPC in turn). */
15977
+ const TVL_HISTORY_RPC_ATTEMPTS = 3;
15978
+ /** Back-off before the second attempt; doubled for each one after. */
15979
+ const TVL_HISTORY_RPC_RETRY_DELAY_MS = 500;
15980
+ /** Per-call timeout of the history providers the SDK builds itself. */
15981
+ const TVL_HISTORY_RPC_TIMEOUT_MS = 10000;
15982
+ /** Default `timeoutMs` of a whole `getPoolTvlHistory` call. */
15983
+ const TVL_HISTORY_DEFAULT_TIMEOUT_MS = 30000;
15984
+ /**
15985
+ * Blocks at least this far below the subgraph's head are treated as final
15986
+ * and their reads are cached; nearer ones are read again every time.
15987
+ */
15988
+ const TVL_HISTORY_CACHE_DEPTH = 64;
15989
+ /** Entries per SDK instance before the cache starts again from empty. */
15990
+ const TVL_HISTORY_CACHE_LIMIT = 20000;
15991
+ /** `getPoolTvlHistory` took longer than its `timeoutMs`. */
15992
+ class PoolTvlHistoryTimeoutError extends Error {
15993
+ constructor(timeoutMs) {
15994
+ super(`getPoolTvlHistory: timed out after ${String(timeoutMs)} ms`);
15995
+ this.timeoutMs = timeoutMs;
15996
+ this.name = 'PoolTvlHistoryTimeoutError';
15997
+ }
15998
+ }
15999
+ /** `getPoolTvlHistory` was abandoned through its `signal`. */
16000
+ class PoolTvlHistoryAbortedError extends Error {
16001
+ constructor() {
16002
+ super('getPoolTvlHistory: aborted');
16003
+ this.name = 'PoolTvlHistoryAbortedError';
16004
+ }
16005
+ }
16006
+ /**
16007
+ * A history provider answered a past block as if nothing were deployed — no
16008
+ * code even for the pool, which cleared in that very block. A pruned node
16009
+ * does that; trusting it would draw the off-chain TVL as 0.
16010
+ */
16011
+ class PastStateUnavailableError extends Error {
16012
+ constructor(block) {
16013
+ super(`getPoolTvlHistory: history provider does not serve past state (no code for the pool at block ${String(block)}, where it cleared)`);
16014
+ this.block = block;
16015
+ this.name = 'PastStateUnavailableError';
16016
+ }
16017
+ }
16018
+ /**
16019
+ * Throws `RangeError` / `TypeError` on a malformed option.
16020
+ * `getPoolTvlHistory` calls it ONCE, BEFORE any network read;
16021
+ * `resolvePoolTvlHistoryWindow` assumes it has run.
16022
+ */
16023
+ function validatePoolTvlHistoryOptions(options) {
16024
+ const { fromEpoch, count, timeoutMs, signal } = options;
16025
+ if (fromEpoch !== undefined &&
16026
+ (!Number.isSafeInteger(fromEpoch) || fromEpoch < 0)) {
16027
+ throw new RangeError(`getPoolTvlHistory: fromEpoch must be a non-negative integer, got ${String(fromEpoch)}`);
16028
+ }
16029
+ if (count !== undefined && (!Number.isSafeInteger(count) || count < 1)) {
16030
+ throw new RangeError(`getPoolTvlHistory: count must be a positive integer, got ${String(count)}`);
16031
+ }
16032
+ if (timeoutMs !== undefined &&
16033
+ (typeof timeoutMs !== 'number' ||
16034
+ !Number.isFinite(timeoutMs) ||
16035
+ timeoutMs <= 0)) {
16036
+ throw new RangeError(`getPoolTvlHistory: timeoutMs must be a positive number, got ${String(timeoutMs)}`);
16037
+ }
16038
+ if (signal !== undefined &&
16039
+ (typeof signal !== 'object' ||
16040
+ typeof signal.aborted !== 'boolean')) {
16041
+ throw new TypeError('getPoolTvlHistory: signal must be an AbortSignal');
16042
+ }
16043
+ }
16044
+ /** The epoch window. Options must already have passed validation. */
16045
+ function resolvePoolTvlHistoryWindow(options, currentEpoch) {
16046
+ const { fromEpoch, count } = options;
16047
+ let from;
16048
+ let to = currentEpoch;
16049
+ if (fromEpoch !== undefined) {
16050
+ from = fromEpoch;
16051
+ if (count !== undefined) {
16052
+ to = Math.min(currentEpoch, fromEpoch + count - 1);
16053
+ }
16054
+ }
16055
+ else {
16056
+ from = count !== undefined ? Math.max(0, currentEpoch - count + 1) : 0;
16057
+ }
16058
+ return from > to ? null : { fromEpoch: from, toEpoch: to };
16059
+ }
16060
+ /**
16061
+ * Runs `work` under a deadline and an optional caller signal. Whichever comes
16062
+ * first rejects the returned promise with its typed error at once, aborts
16063
+ * `cancellation.signal` (cancelling in-flight subgraph requests) and makes
16064
+ * every later `throwIfCancelled()` throw, so no new read starts. An RPC call
16065
+ * already sent cannot be recalled; its answer is dropped.
16066
+ */
16067
+ function runCancellable(timeoutMs, external, work) {
16068
+ return __awaiter(this, void 0, void 0, function* () {
16069
+ if (external === null || external === void 0 ? void 0 : external.aborted)
16070
+ throw new PoolTvlHistoryAbortedError();
16071
+ const controller = new AbortController();
16072
+ let reason;
16073
+ let rejectCancelled = () => undefined;
16074
+ const cancelled = new Promise((_, reject) => {
16075
+ rejectCancelled = reject;
16076
+ });
16077
+ const cancel = (error) => {
16078
+ if (reason)
16079
+ return;
16080
+ reason = error;
16081
+ controller.abort();
16082
+ rejectCancelled(error);
16083
+ };
16084
+ const timer = setTimeout(() => {
16085
+ cancel(new PoolTvlHistoryTimeoutError(timeoutMs));
16086
+ }, timeoutMs);
16087
+ const onAbort = () => {
16088
+ cancel(new PoolTvlHistoryAbortedError());
16089
+ };
16090
+ external === null || external === void 0 ? void 0 : external.addEventListener('abort', onAbort);
16091
+ const cancellation = {
16092
+ signal: controller.signal,
16093
+ throwIfCancelled: () => {
16094
+ if (reason)
16095
+ throw reason;
16096
+ },
16097
+ };
16098
+ try {
16099
+ const running = work(cancellation);
16100
+ // The losing side must never surface as an unhandled rejection.
16101
+ running.catch(() => undefined);
16102
+ return yield Promise.race([running, cancelled]);
16103
+ }
16104
+ finally {
16105
+ clearTimeout(timer);
16106
+ external === null || external === void 0 ? void 0 : external.removeEventListener('abort', onAbort);
16107
+ }
16108
+ });
16109
+ }
16110
+ // ---------------------------------------------------------------------------
16111
+ // Subgraph reads
16112
+ // ---------------------------------------------------------------------------
16113
+ /** Above any epoch number: "no upper bound" for the clearings query. */
16114
+ const NO_EPOCH_BOUND = '1000000000000';
16115
+ /**
16116
+ * The pool's FINISHED clearings, plus — on the first page only — its balance
16117
+ * now and the subgraph's latest block.
16118
+ *
16119
+ * Two modes over one document: ascending from `$epochGte` (a `fromEpoch`
16120
+ * window, or everything), or descending below `$epochLt` (the newest N, for
16121
+ * a `count`-only window, so it never pages through the whole history).
16122
+ */
16123
+ const poolTvlClearingsQuery = gql `
16124
+ query PoolTvlClearings(
16125
+ $poolId: ID!
16126
+ $pool: String!
16127
+ $epochGte: BigInt!
16128
+ $epochLt: BigInt!
16129
+ $direction: OrderDirection!
16130
+ $first: Int!
16131
+ $withSnapshot: Boolean!
16132
+ ) {
16133
+ clearings: lendingPoolClearings(
16134
+ first: $first
16135
+ orderBy: epochId
16136
+ orderDirection: $direction
16137
+ where: {
16138
+ lendingPool: $pool
16139
+ isFinished: true
16140
+ epochId_gte: $epochGte
16141
+ epochId_lt: $epochLt
16142
+ }
16143
+ ) {
16144
+ epochId
16145
+ endBlockNumber
16146
+ endTimestamp
16147
+ }
16148
+ pool: lendingPool(id: $poolId) @include(if: $withSnapshot) {
16149
+ balance
16150
+ }
16151
+ meta: _meta @include(if: $withSnapshot) {
16152
+ block {
16153
+ number
16154
+ timestamp
16155
+ }
16156
+ }
16157
+ }
16158
+ `;
16159
+ /** The time-travel alias for one block: `b<block>`. */
16160
+ function blockAlias(block) {
16161
+ return `b${String(block)}`;
16162
+ }
16163
+ /**
16164
+ * One time-travel request: the pool's balance at the END of each block, one
16165
+ * alias per block. Block numbers are written into the document, so each must
16166
+ * be a positive safe integer — anything else throws rather than reaching the
16167
+ * query text. The pool id travels as a variable.
16168
+ */
16169
+ function poolBalancesAtBlocksQuery(blocks) {
16170
+ const fields = blocks.map((block) => {
16171
+ if (!Number.isSafeInteger(block) || block <= 0) {
16172
+ throw new Error(`getPoolTvlHistory: refusing block number ${String(block)}`);
16173
+ }
16174
+ return `${blockAlias(block)}: lendingPool(id: $poolId, block: { number: ${String(block)} }) { balance }`;
16175
+ });
16176
+ return `query PoolTvlAtBlocks($poolId: ID!) { ${fields.join(' ')} }`;
16177
+ }
16178
+ function parseClearing(row) {
16179
+ const clearing = {
16180
+ epochId: Number(row.epochId),
16181
+ block: Number(row.endBlockNumber),
16182
+ timestamp: Number(row.endTimestamp),
16183
+ };
16184
+ if (!Number.isSafeInteger(clearing.epochId) ||
16185
+ clearing.epochId < 0 ||
16186
+ !Number.isSafeInteger(clearing.block) ||
16187
+ clearing.block <= 0 ||
16188
+ !Number.isSafeInteger(clearing.timestamp) ||
16189
+ clearing.timestamp <= 0) {
16190
+ throw new Error(`getPoolTvlHistory: malformed clearing row ${JSON.stringify(row)}`);
16191
+ }
16192
+ return clearing;
16193
+ }
16194
+ /**
16195
+ * The scope's finished clearings, ascending by epoch, and the snapshot from
16196
+ * the first page. Normally ONE request.
16197
+ */
16198
+ function fetchPoolClearings(request, poolId, scope, cancellation) {
16199
+ return __awaiter(this, void 0, void 0, function* () {
16200
+ const clearings = [];
16201
+ let snapshot = {};
16202
+ let gte = scope.kind === 'from' ? scope.fromEpoch : 0;
16203
+ let lt;
16204
+ let requests = 0;
16205
+ for (;;) {
16206
+ cancellation.throwIfCancelled();
16207
+ if (requests >= TVL_HISTORY_MAX_PAGES) {
16208
+ throw new Error(`getPoolTvlHistory: more than ${String(TVL_HISTORY_MAX_PAGES)} pages of clearings for one pool`);
16209
+ }
16210
+ const first = requests === 0;
16211
+ const remaining = scope.kind === 'newest'
16212
+ ? scope.count - clearings.length
16213
+ : TVL_HISTORY_PAGE_SIZE;
16214
+ const pageSize = Math.min(TVL_HISTORY_PAGE_SIZE, remaining);
16215
+ const page = yield request(poolTvlClearingsQuery, {
16216
+ poolId,
16217
+ pool: poolId,
16218
+ epochGte: String(gte),
16219
+ epochLt: lt === undefined ? NO_EPOCH_BOUND : String(lt),
16220
+ direction: scope.kind === 'newest' ? 'desc' : 'asc',
16221
+ first: pageSize,
16222
+ withSnapshot: first,
16223
+ }, cancellation.signal);
16224
+ requests += 1;
16225
+ if (first)
16226
+ snapshot = { pool: page.pool, meta: page.meta };
16227
+ const rows = page.clearings.map(parseClearing);
16228
+ clearings.push(...rows);
16229
+ const last = rows.at(-1);
16230
+ if (rows.length < pageSize || last === undefined)
16231
+ break;
16232
+ if (scope.kind === 'newest') {
16233
+ if (clearings.length >= scope.count)
16234
+ break;
16235
+ if (lt !== undefined && last.epochId >= lt) {
16236
+ throw new Error('getPoolTvlHistory: the clearings cursor did not advance');
16237
+ }
16238
+ lt = last.epochId;
16239
+ }
16240
+ else {
16241
+ if (last.epochId < gte) {
16242
+ throw new Error('getPoolTvlHistory: the clearings cursor did not advance');
16243
+ }
16244
+ gte = last.epochId + 1;
16245
+ }
16246
+ }
16247
+ clearings.sort((a, b) => a.epochId - b.epochId);
16248
+ return { clearings, snapshot, requests };
16249
+ });
16250
+ }
16251
+ /**
16252
+ * The pool's balance (display units, the subgraph's decimal string) at the end
16253
+ * of each block, `TVL_HISTORY_BLOCKS_PER_REQUEST` blocks per request. A block
16254
+ * at which the subgraph has no such pool THROWS: a finished clearing of the
16255
+ * pool happened in it, so a missing pool means a broken read, not a zero.
16256
+ */
16257
+ function fetchPoolBalancesAtBlocks(request, poolId, blocks, cancellation) {
16258
+ return __awaiter(this, void 0, void 0, function* () {
16259
+ const chunks = [];
16260
+ for (let i = 0; i < blocks.length; i += TVL_HISTORY_BLOCKS_PER_REQUEST) {
16261
+ chunks.push(blocks.slice(i, i + TVL_HISTORY_BLOCKS_PER_REQUEST));
16262
+ }
16263
+ cancellation.throwIfCancelled();
16264
+ const pages = yield Promise.all(chunks.map((chunk) => request(poolBalancesAtBlocksQuery(chunk), { poolId }, cancellation.signal).then((page) => ({ chunk, page }))));
16265
+ const balances = new Map();
16266
+ for (const { chunk, page } of pages) {
16267
+ for (const block of chunk) {
16268
+ const state = page[blockAlias(block)];
16269
+ if (!state) {
16270
+ throw new Error(`getPoolTvlHistory: the subgraph has no state for the pool at block ${String(block)}`);
16271
+ }
16272
+ balances.set(block, state.balance);
16273
+ }
16274
+ }
16275
+ return { balances, requests: chunks.length };
16276
+ });
16277
+ }
16278
+ const EXTERNAL_TVL_INTERFACE = new Interface(KasuPoolExternalTVLAbi__factory.abi);
16279
+ function hasCode(code) {
16280
+ if (typeof code !== 'string' || !/^0x[0-9a-fA-F]*$/.test(code)) {
16281
+ throw new Error(`getPoolTvlHistory: getCode returned ${String(code)}, not hex`);
16282
+ }
16283
+ return code !== '0x';
16284
+ }
16285
+ /**
16286
+ * The real reader: each read goes to the history RPCs IN ORDER until one
16287
+ * answers, and the whole chain is retried `attempts` times with back-off.
16288
+ * After the last attempt the last error propagates — never a value. Each
16289
+ * attempt first checks the cancellation, so a timed-out call stops here.
16290
+ */
16291
+ function createOffChainTvlReader({ externalTvl, pool, rpcs, cancellation, attempts = TVL_HISTORY_RPC_ATTEMPTS, delayMs = TVL_HISTORY_RPC_RETRY_DELAY_MS, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), }) {
16292
+ if (rpcs.length === 0) {
16293
+ throw new Error('getPoolTvlHistory: no history RPC to read from');
16294
+ }
16295
+ const data = EXTERNAL_TVL_INTERFACE.encodeFunctionData('externalTVLOfPool', [pool]);
16296
+ const read = (op) => __awaiter(this, void 0, void 0, function* () {
16297
+ let lastError;
16298
+ for (let attempt = 1; attempt <= attempts; attempt++) {
16299
+ for (const rpc of rpcs) {
16300
+ cancellation.throwIfCancelled();
16301
+ try {
16302
+ return yield op(rpc);
16303
+ }
16304
+ catch (error) {
16305
+ lastError = error;
16306
+ }
16307
+ }
16308
+ if (attempt < attempts) {
16309
+ yield sleep(delayMs * Math.pow(2, (attempt - 1)));
16310
+ }
16311
+ }
16312
+ cancellation.throwIfCancelled();
16313
+ throw lastError;
16314
+ });
16315
+ return {
16316
+ readAt: (block) => read((rpc) => __awaiter(this, void 0, void 0, function* () {
16317
+ const result = yield rpc.call({ to: externalTvl, data }, block);
16318
+ const [value] = EXTERNAL_TVL_INTERFACE.decodeFunctionResult('externalTVLOfPool', result);
16319
+ return value;
16320
+ })),
16321
+ hasCodeAt: (block) => read((rpc) => __awaiter(this, void 0, void 0, function* () { return hasCode(yield rpc.getCode(externalTvl, block)); })),
16322
+ poolHasCodeAt: (block) => read((rpc) => __awaiter(this, void 0, void 0, function* () { return hasCode(yield rpc.getCode(pool, block)); })),
16323
+ };
16324
+ }
16325
+ /**
16326
+ * Index of the first block (of `blocks`, ascending) at which the contract has
16327
+ * code; `blocks.length` when it has code at none. A deployed contract keeps
16328
+ * its code (it is a proxy, never self-destructed), so the answer is a single
16329
+ * boundary and a binary search finds it: ONE `getCode` when the contract
16330
+ * already existed at the oldest clearing, about log2(n) otherwise.
16331
+ */
16332
+ function firstBlockWithCode(blocks, hasCodeAt) {
16333
+ return __awaiter(this, void 0, void 0, function* () {
16334
+ if (blocks.length === 0)
16335
+ return 0;
16336
+ if (yield hasCodeAt(blocks[0]))
16337
+ return 0;
16338
+ const last = blocks.length - 1;
16339
+ if (!(yield hasCodeAt(blocks[last])))
16340
+ return blocks.length;
16341
+ let lo = 0; // no code
16342
+ let hi = last; // code
16343
+ while (hi - lo > 1) {
16344
+ const mid = Math.floor((lo + hi) / 2);
16345
+ if (yield hasCodeAt(blocks[mid]))
16346
+ hi = mid;
16347
+ else
16348
+ lo = mid;
16349
+ }
16350
+ return hi;
16351
+ });
16352
+ }
16353
+ /**
16354
+ * `fn` over `items`, at most `limit` in flight; rejects on the first failure
16355
+ * and starts nothing after it (or after `beforeEach` throws).
16356
+ */
16357
+ function mapWithConcurrency(items_1, limit_1, fn_1) {
16358
+ return __awaiter(this, arguments, void 0, function* (items, limit, fn, beforeEach = () => undefined) {
16359
+ const results = new Array(items.length);
16360
+ let next = 0;
16361
+ let failed = false;
16362
+ const worker = () => __awaiter(this, void 0, void 0, function* () {
16363
+ while (!failed && next < items.length) {
16364
+ const index = next++;
16365
+ try {
16366
+ beforeEach();
16367
+ results[index] = yield fn(items[index]);
16368
+ }
16369
+ catch (error) {
16370
+ failed = true;
16371
+ throw error;
16372
+ }
16373
+ }
16374
+ });
16375
+ yield Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
16376
+ return results;
16377
+ });
16378
+ }
16379
+ /**
16380
+ * Immutable past-block reads, per SDK instance. Keys carry the contract (or
16381
+ * subgraph), the pool and the block, so two deployments never collide.
16382
+ */
16383
+ class PastBlockCache {
16384
+ constructor() {
16385
+ this._entries = new Map();
16386
+ }
16387
+ get(key) {
16388
+ return this._entries.get(key);
16389
+ }
16390
+ set(key, value) {
16391
+ if (this._entries.size >= TVL_HISTORY_CACHE_LIMIT) {
16392
+ this._entries.clear();
16393
+ }
16394
+ this._entries.set(key, value);
16395
+ }
16396
+ get size() {
16397
+ return this._entries.size;
16398
+ }
16399
+ }
16400
+ /**
16401
+ * The off-chain TVL (BASE units) at each block, and — with `withLatest` — at
16402
+ * the latest block, all inside ONE bounded pool of reads.
16403
+ *
16404
+ * Before the contract existed there was nothing to record, so those blocks
16405
+ * are 0 — decided by `getCode`, never by catching a revert: an RPC that
16406
+ * refuses past state can surface that refusal as a "call exception". And a
16407
+ * "no code" answer is believed only if the same provider DOES show code for
16408
+ * the pool at the oldest and newest such block (it cleared there); otherwise
16409
+ * `PastStateUnavailableError`. Every other failed read throws.
16410
+ *
16411
+ * `cached` blocks (final, below the head) are answered from `cache` and
16412
+ * stored there after a successful read.
16413
+ */
16414
+ function fetchOffChainTvlAtBlocks(reader_1, blocks_1, _a) {
16415
+ return __awaiter(this, arguments, void 0, function* (reader, blocks, { withLatest = false, cancellation, cache, cacheKey, cacheable = () => false, }) {
16416
+ let rpcReads = 0;
16417
+ const cached = (kind, block, load) => {
16418
+ const key = cache && cacheKey && cacheable(block)
16419
+ ? cacheKey(kind, block)
16420
+ : undefined;
16421
+ const hit = key === undefined ? undefined : cache === null || cache === void 0 ? void 0 : cache.get(key);
16422
+ if (hit !== undefined)
16423
+ return Promise.resolve(hit);
16424
+ cancellation.throwIfCancelled();
16425
+ rpcReads += 1;
16426
+ return load().then((value) => {
16427
+ if (key !== undefined)
16428
+ cache === null || cache === void 0 ? void 0 : cache.set(key, value);
16429
+ return value;
16430
+ });
16431
+ };
16432
+ const deployedFrom = yield firstBlockWithCode(blocks, (block) => cached('code', block, () => reader.hasCodeAt(block)));
16433
+ if (deployedFrom > 0) {
16434
+ for (const block of new Set([blocks[0], blocks[deployedFrom - 1]])) {
16435
+ const poolDeployed = yield cached('pool', block, () => reader.poolHasCodeAt(block));
16436
+ if (!poolDeployed)
16437
+ throw new PastStateUnavailableError(block);
16438
+ }
16439
+ }
16440
+ const offChain = new Map();
16441
+ for (const block of blocks.slice(0, deployedFrom)) {
16442
+ offChain.set(block, BigNumber.from(0));
16443
+ }
16444
+ const targets = [...blocks.slice(deployedFrom)];
16445
+ if (withLatest)
16446
+ targets.push('latest');
16447
+ const values = yield mapWithConcurrency(targets, TVL_HISTORY_RPC_CONCURRENCY, (block) => {
16448
+ if (block !== 'latest') {
16449
+ return cached('tvl', block, () => reader.readAt(block));
16450
+ }
16451
+ // Never cached: "latest" moves.
16452
+ rpcReads += 1;
16453
+ return reader.readAt('latest');
16454
+ }, () => {
16455
+ cancellation.throwIfCancelled();
16456
+ });
16457
+ let latest = null;
16458
+ targets.forEach((block, i) => {
16459
+ if (block === 'latest')
16460
+ latest = values[i];
16461
+ else
16462
+ offChain.set(block, values[i]);
16463
+ });
16464
+ return { offChain, latest, rpcReads };
16465
+ });
16466
+ }
16467
+ /**
16468
+ * Pure: the reads → the history. One `clearing` point per clearing, ordered by
16469
+ * the block it finished in (then by epoch, for clearings that shared a block),
16470
+ * so the series is ascending in TIME even where epoch numbers jump; then the
16471
+ * `current` point. Each point is `poolTvl(onChain, offChain)` — the same
16472
+ * function `getPoolOverview` uses for `totalValueLocked.total`.
16473
+ */
16474
+ function buildPoolTvlHistory(read) {
16475
+ const offChainDisplay = (value) => formatUnits(value, read.decimals);
16476
+ const entries = [...read.clearings]
16477
+ .sort((a, b) => a.block - b.block || a.epochId - b.epochId)
16478
+ .map((clearing) => {
16479
+ const balance = read.balances.get(clearing.block);
16480
+ const offChain = read.offChain.get(clearing.block);
16481
+ if (balance === undefined || offChain === undefined) {
16482
+ throw new Error(`getPoolTvlHistory: no reading at block ${String(clearing.block)}`);
16483
+ }
16484
+ return {
16485
+ epochId: clearing.epochId,
16486
+ timestamp: clearing.timestamp,
16487
+ tvl: poolTvl(balance, offChainDisplay(offChain)),
16488
+ kind: 'clearing',
16489
+ };
16490
+ });
16491
+ if (read.current) {
16492
+ entries.push({
16493
+ epochId: read.current.epochId,
16494
+ timestamp: read.current.timestamp,
16495
+ tvl: poolTvl(read.current.balance, offChainDisplay(read.current.offChain)),
16496
+ kind: 'current',
16497
+ });
16498
+ }
16499
+ return entries;
16500
+ }
16501
+ /** Ascending, de-duplicated clearing blocks: each past block is read ONCE. */
16502
+ function distinctBlocks(clearings) {
16503
+ return [...new Set(clearings.map((c) => c.block))].sort((a, b) => a - b);
16504
+ }
16505
+
16506
+ /** Thrown by `getPoolTvlHistory` when no provider that serves past state is configured. */
16507
+ const NO_HISTORY_PROVIDER_MESSAGE = 'getPoolTvlHistory: no history provider; pass historyProvider or set historyRpcUrls to RPCs that serve past state';
15910
16508
  class DataService {
15911
- constructor(_kasuConfig, signerOrProvider) {
16509
+ /**
16510
+ * @param historyProvider - a provider that SERVES PAST STATE (an archive
16511
+ * RPC), used only by `getPoolTvlHistory` for the off-chain TVL at old
16512
+ * blocks. Without one, providers are built on `SdkConfig.historyRpcUrls`
16513
+ * (tried in order); with neither, that one call throws
16514
+ * `NO_HISTORY_PROVIDER_MESSAGE`. Never the default provider: a pruned
16515
+ * node refuses old blocks.
16516
+ */
16517
+ constructor(_kasuConfig, signerOrProvider, historyProvider) {
15912
16518
  this._kasuConfig = _kasuConfig;
16519
+ /** Immutable past-block reads, kept for the life of this instance. */
16520
+ this._tvlHistoryCache = new PastBlockCache();
15913
16521
  this._externalTvlAbi = KasuPoolExternalTVLAbi__factory.connect(_kasuConfig.contracts.ExternalTVL, signerOrProvider);
16522
+ this._systemVariablesAbi = ISystemVariablesAbi__factory.connect(_kasuConfig.contracts.SystemVariables, signerOrProvider);
15914
16523
  this._graph = new GraphQLClient(_kasuConfig.subgraphUrl);
16524
+ this._explicitHistoryProvider = historyProvider;
15915
16525
  this._directus = createDirectusClient(_kasuConfig.directusUrl);
15916
16526
  }
15917
16527
  getUrlFromFile(fileName) {
@@ -15991,6 +16601,194 @@ class DataService {
15991
16601
  return tvlMap;
15992
16602
  });
15993
16603
  }
16604
+ /**
16605
+ * The pool's TVL — the total lent into it by everyone — at every cycle
16606
+ * boundary, then as of now: the data behind a TVL chart.
16607
+ *
16608
+ * One `clearing` point per FINISHED clearing, read at the block it
16609
+ * finished in (`LendingPoolClearing.endBlockNumber`), then one `current`
16610
+ * point. Every point is `poolTvl(onChain, offChain)`, the definition
16611
+ * `getPoolOverview` uses for `totalValueLocked.total`:
16612
+ *
16613
+ * - on-chain: `lendingPool.balance` from the subgraph, read with
16614
+ * time-travel queries (`block: { number }`);
16615
+ * - off-chain: `ExternalTVL.externalTVLOfPool(pool)` at the same block,
16616
+ * through the HISTORY providers (see the constructor). 0 before the
16617
+ * contract was deployed (decided by `getCode`, and only if the same
16618
+ * provider shows the pool's own code there); any failed read throws.
16619
+ *
16620
+ * The `current` point mixes two sources: the subgraph's latest balance
16621
+ * (and its latest block time) and `externalTVLOfPool` at `latest` on the
16622
+ * history provider. `getPoolOverview` reads the same two, the off-chain
16623
+ * one on the default provider, so the last point equals
16624
+ * `totalValueLocked.total` when both reads succeed. They can differ when
16625
+ * `getPoolOverview`'s off-chain read FAILS — it then falls back to 0 —
16626
+ * or when the figure moves between the two calls.
16627
+ *
16628
+ * Units: `tvl` in display units of the deployment's own stable token
16629
+ * (USDC on Base and XDC USDC, AUDD on XDC AUDD); `timestamp` in unix
16630
+ * seconds. Ascending in time. See `PoolTvlHistoryEntry`.
16631
+ *
16632
+ * Cost: one subgraph request for the clearings (per thousand), one per
16633
+ * hundred distinct uncached clearing blocks, a `currentEpochNumber` read;
16634
+ * then, on the history providers, one `externalTVLOfPool` per distinct
16635
+ * uncached block plus the latest, and one to about log2(n) `getCode`s —
16636
+ * at most `TVL_HISTORY_RPC_CONCURRENCY` in flight. Final blocks are
16637
+ * cached on this instance, so a second call reads only what is new.
16638
+ * `timeoutMs` (default 30 s) bounds the whole call.
16639
+ *
16640
+ * `[]` for a pool the subgraph does not know, for a window with no epoch
16641
+ * in it, and on a LITE deployment whose subgraph lacks the entities. On a
16642
+ * full deployment that schema error throws, like every network, indexing
16643
+ * or past-state failure.
16644
+ *
16645
+ * @throws RangeError / TypeError on a malformed option or a pool id that
16646
+ * is not an address, and `NO_HISTORY_PROVIDER_MESSAGE` when the
16647
+ * deployment has an `ExternalTVL` contract but no history provider — all
16648
+ * before any network read. `PoolTvlHistoryTimeoutError` /
16649
+ * `PoolTvlHistoryAbortedError` on the deadline or the signal,
16650
+ * `PastStateUnavailableError` from a pruned history provider.
16651
+ */
16652
+ getPoolTvlHistory(poolId_1) {
16653
+ return __awaiter(this, arguments, void 0, function* (poolId, options = {}) {
16654
+ var _a;
16655
+ validatePoolTvlHistoryOptions(options);
16656
+ if (!isAddress(poolId)) {
16657
+ throw new TypeError(`getPoolTvlHistory: poolId must be an address, got ${String(poolId)}`);
16658
+ }
16659
+ const pool = poolId.toLowerCase();
16660
+ const externalTvl = this._externalTvlAddress();
16661
+ const rpcs = externalTvl ? this._historyRpcs() : [];
16662
+ if (externalTvl && rpcs.length === 0) {
16663
+ throw new Error(NO_HISTORY_PROVIDER_MESSAGE);
16664
+ }
16665
+ return yield runCancellable((_a = options.timeoutMs) !== null && _a !== void 0 ? _a : TVL_HISTORY_DEFAULT_TIMEOUT_MS, options.signal, (cancellation) => this._readPoolTvlHistory(pool, options, externalTvl
16666
+ ? this._createOffChainTvlReader({
16667
+ externalTvl,
16668
+ pool,
16669
+ rpcs,
16670
+ cancellation,
16671
+ })
16672
+ : null, externalTvl !== null && externalTvl !== void 0 ? externalTvl : '', cancellation));
16673
+ });
16674
+ }
16675
+ _readPoolTvlHistory(pool, options, reader, externalTvl, cancellation) {
16676
+ return __awaiter(this, void 0, void 0, function* () {
16677
+ var _a, _b, _c;
16678
+ const graph = (document, variables, signal) => this._graph.request({ document, variables, signal });
16679
+ const scope = options.fromEpoch === undefined && options.count !== undefined
16680
+ ? { kind: 'newest', count: options.count }
16681
+ : { kind: 'from', fromEpoch: (_a = options.fromEpoch) !== null && _a !== void 0 ? _a : 0 };
16682
+ try {
16683
+ const [currentEpochNumber, { clearings, snapshot }] = yield Promise.all([
16684
+ this._systemVariablesAbi.currentEpochNumber(),
16685
+ fetchPoolClearings(graph, pool, scope, cancellation),
16686
+ ]);
16687
+ const currentEpoch = currentEpochNumber.toNumber();
16688
+ const window = resolvePoolTvlHistoryWindow(options, currentEpoch);
16689
+ const poolNow = snapshot.pool;
16690
+ if (!window || !poolNow)
16691
+ return [];
16692
+ const head = (_b = snapshot.meta) === null || _b === void 0 ? void 0 : _b.block;
16693
+ if (!head || typeof head.timestamp !== 'number') {
16694
+ throw new Error('getPoolTvlHistory: the subgraph returned no latest block time');
16695
+ }
16696
+ const inWindow = clearings.filter((c) => c.epochId >= window.fromEpoch &&
16697
+ c.epochId <= window.toEpoch);
16698
+ const blocks = distinctBlocks(inWindow);
16699
+ const withCurrent = window.toEpoch >= currentEpoch;
16700
+ const cacheable = (block) => block <= head.number - TVL_HISTORY_CACHE_DEPTH;
16701
+ const [balances, offChainRead] = yield Promise.all([
16702
+ this._balancesAtBlocks(graph, pool, blocks, cacheable, cancellation),
16703
+ reader
16704
+ ? fetchOffChainTvlAtBlocks(reader, blocks, {
16705
+ withLatest: withCurrent,
16706
+ cancellation,
16707
+ cache: this._tvlHistoryCache,
16708
+ cacheKey: (kind, block) => `${kind}|${externalTvl}|${pool}|${String(block)}`,
16709
+ cacheable,
16710
+ })
16711
+ : {
16712
+ offChain: new Map(blocks.map((b) => [b, BigNumber.from(0)])),
16713
+ latest: null,
16714
+ },
16715
+ ]);
16716
+ return buildPoolTvlHistory({
16717
+ clearings: inWindow,
16718
+ balances,
16719
+ offChain: offChainRead.offChain,
16720
+ current: withCurrent
16721
+ ? {
16722
+ epochId: currentEpoch,
16723
+ timestamp: head.timestamp,
16724
+ balance: poolNow.balance,
16725
+ offChain: (_c = offChainRead.latest) !== null && _c !== void 0 ? _c : BigNumber.from(0),
16726
+ }
16727
+ : null,
16728
+ decimals: this._kasuConfig.stableAssetDecimals,
16729
+ });
16730
+ }
16731
+ catch (error) {
16732
+ if (isEmptyHistoryError(error, this._kasuConfig.isLiteDeployment)) {
16733
+ return [];
16734
+ }
16735
+ throw error;
16736
+ }
16737
+ });
16738
+ }
16739
+ /** The pool's balance at each block: cached final blocks, then one batch. */
16740
+ _balancesAtBlocks(graph, pool, blocks, cacheable, cancellation) {
16741
+ return __awaiter(this, void 0, void 0, function* () {
16742
+ const key = (block) => `balance|${this._kasuConfig.subgraphUrl}|${pool}|${String(block)}`;
16743
+ const balances = new Map();
16744
+ const missing = [];
16745
+ for (const block of blocks) {
16746
+ const hit = cacheable(block)
16747
+ ? this._tvlHistoryCache.get(key(block))
16748
+ : undefined;
16749
+ if (hit === undefined)
16750
+ missing.push(block);
16751
+ else
16752
+ balances.set(block, hit);
16753
+ }
16754
+ if (missing.length > 0) {
16755
+ const read = yield fetchPoolBalancesAtBlocks(graph, pool, missing, cancellation);
16756
+ for (const [block, balance] of read.balances) {
16757
+ balances.set(block, balance);
16758
+ if (cacheable(block))
16759
+ this._tvlHistoryCache.set(key(block), balance);
16760
+ }
16761
+ }
16762
+ return balances;
16763
+ });
16764
+ }
16765
+ /** `null` when the deployment has no `ExternalTVL` contract. */
16766
+ _externalTvlAddress() {
16767
+ const address = this._kasuConfig.contracts.ExternalTVL;
16768
+ if (!address || /^0x0{40}$/i.test(address))
16769
+ return null;
16770
+ return address.toLowerCase();
16771
+ }
16772
+ /** Seam for tests: the real reader over the history RPCs. */
16773
+ _createOffChainTvlReader(args) {
16774
+ return createOffChainTvlReader(args);
16775
+ }
16776
+ /**
16777
+ * The history RPCs, in fallback order: the explicit provider, else ONE
16778
+ * provider per `historyRpcUrls` entry — built here and nowhere else,
16779
+ * pinned to `SdkConfig.chainId` when known, each with a per-call timeout.
16780
+ * Built once per instance.
16781
+ */
16782
+ _historyRpcs() {
16783
+ if (this._explicitHistoryProvider)
16784
+ return [this._explicitHistoryProvider];
16785
+ if (!this._builtHistoryProviders) {
16786
+ // Through the `ethers` namespace, so the consumer's own build
16787
+ // (Node or browser) is used — see `defaultProvider` in the facade.
16788
+ this._builtHistoryProviders = this._kasuConfig.historyRpcUrls.map((url) => new providers.StaticJsonRpcProvider({ url, timeout: TVL_HISTORY_RPC_TIMEOUT_MS }, this._kasuConfig.chainId));
16789
+ }
16790
+ return this._builtHistoryProviders;
16791
+ }
15994
16792
  getPoolOverview(epochId, id_in) {
15995
16793
  return __awaiter(this, void 0, void 0, function* () {
15996
16794
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
@@ -16131,8 +16929,8 @@ class DataService {
16131
16929
  subheading: lendingPoolDirectus === null || lendingPoolDirectus === void 0 ? void 0 : lendingPoolDirectus.subheading,
16132
16930
  operatingSince: lendingPoolDirectus === null || lendingPoolDirectus === void 0 ? void 0 : lendingPoolDirectus.operatingSince,
16133
16931
  totalValueLocked: {
16134
- total: (parseFloat(lendingPoolSubgraph.balance) +
16135
- parseFloat(offChainTvl)).toString(),
16932
+ // The one TVL definition, shared with getPoolTvlHistory.
16933
+ total: poolTvl(lendingPoolSubgraph.balance, offChainTvl).toString(),
16136
16934
  offchain: offChainTvl,
16137
16935
  },
16138
16936
  loansUnderManagement: (_q = lendingPoolDirectus === null || lendingPoolDirectus === void 0 ? void 0 : lendingPoolDirectus.loansUnderManagement) !== null && _q !== void 0 ? _q : '0',
@@ -16941,6 +17739,101 @@ class KSULocking {
16941
17739
  }
16942
17740
  }
16943
17741
 
17742
+ /**
17743
+ * Settled interest per cycle (epoch): what a lender was credited at each close,
17744
+ * per pool and tranche, and the one formula that turns a tranche's credited
17745
+ * interest into one lender's share of it.
17746
+ *
17747
+ * **The figures are NET, and only net.** `LendingPool._applyTrancheInterest`
17748
+ * takes the performance fee off first and emits `InterestApplied` with the
17749
+ * lender side only (`interest × (1 − performanceFee)`); a fixed-term top-up
17750
+ * (`_applyFixedRateInterests`) is net of the fee the same way. So the figure
17751
+ * this module computes from those records IS what the lender was credited —
17752
+ * never take the fee off it again. No gross is offered: the subgraph keeps
17753
+ * only TODAY's fee, so a historical gross would be a guess.
17754
+ *
17755
+ * Units: amounts are display units of the DEPLOYMENT'S OWN stable token (USDC
17756
+ * on Base and XDC USDC, AUDD on XDC AUDD) — never converted to USD here;
17757
+ * shares are display units of tranche shares, as the subgraph's BigDecimals.
17758
+ */
17759
+ /**
17760
+ * The ONE per-epoch formula. `Portfolio.getPortfolioLendingData` runs it for
17761
+ * the last settled epoch and `getYieldHistory` for every epoch of a window, so
17762
+ * the two figures cannot drift apart.
17763
+ *
17764
+ * ```
17765
+ * base = epochInterest × userShares / trancheShares
17766
+ * lockBase = base × lock.sharesBeforeEpoch / userShares (active locks)
17767
+ * topUp = (lock.sharesAtEpoch − lock.sharesBeforeEpoch) × trancheBalanceNow / trancheSharesNow
17768
+ * variable = base − Σ lockBase
17769
+ * total = variable + Σ (lockBase + topUp)
17770
+ * ```
17771
+ *
17772
+ * A zero denominator contributes 0 (a tranche or a lender with no shares
17773
+ * earned nothing at that close), never `NaN`.
17774
+ */
17775
+ function epochTrancheYield(input) {
17776
+ const base = input.trancheShares === 0
17777
+ ? 0
17778
+ : (input.epochInterest * input.userShares) / input.trancheShares;
17779
+ let variable = base;
17780
+ const locks = input.locks.map((lock) => {
17781
+ if (!lock.active)
17782
+ return 0;
17783
+ const topUp = input.trancheSharesNow === 0
17784
+ ? 0
17785
+ : ((lock.sharesAtEpoch - lock.sharesBeforeEpoch) *
17786
+ input.trancheBalanceNow) /
17787
+ input.trancheSharesNow;
17788
+ const lockBase = input.userShares === 0
17789
+ ? 0
17790
+ : (base * lock.sharesBeforeEpoch) / input.userShares;
17791
+ variable -= lockBase;
17792
+ return lockBase + topUp;
17793
+ });
17794
+ const total = locks.reduce((sum, lockYield) => sum + lockYield, variable);
17795
+ return { total, variable, locks };
17796
+ }
17797
+ /**
17798
+ * Pool/tranche entries → one point per epoch, ascending by `epochId`. With no
17799
+ * filter it sums every strategy (the Lend tab's bars); with `poolId` it is one
17800
+ * strategy (a strategy page's bars).
17801
+ *
17802
+ * An epoch appears only if a kept entry has it — there is no zero padding, so
17803
+ * an empty input (a new lender) is an empty output.
17804
+ *
17805
+ * For a fixed-term lock holder the bars can sum to slightly MORE than the
17806
+ * lender's lifetime interest (up to about 0.2 %): each top-up is valued at the
17807
+ * tranche's CURRENT share price, which has grown since. Show "earned to date"
17808
+ * from the lifetime figure, never from this sum.
17809
+ */
17810
+ function aggregateYieldHistory(entries, filter = {}) {
17811
+ var _a, _b;
17812
+ const poolId = (_a = filter.poolId) === null || _a === void 0 ? void 0 : _a.toLowerCase();
17813
+ const trancheId = (_b = filter.trancheId) === null || _b === void 0 ? void 0 : _b.toLowerCase();
17814
+ const byEpoch = new Map();
17815
+ for (const entry of entries) {
17816
+ if (poolId !== undefined && entry.poolId.toLowerCase() !== poolId) {
17817
+ continue;
17818
+ }
17819
+ if (trancheId !== undefined &&
17820
+ entry.trancheId.toLowerCase() !== trancheId) {
17821
+ continue;
17822
+ }
17823
+ const point = byEpoch.get(entry.epochId);
17824
+ if (point) {
17825
+ point.netYield += entry.netYield;
17826
+ continue;
17827
+ }
17828
+ byEpoch.set(entry.epochId, {
17829
+ epochId: entry.epochId,
17830
+ epochEnd: entry.epochEnd,
17831
+ netYield: entry.netYield,
17832
+ });
17833
+ }
17834
+ return [...byEpoch.values()].sort((a, b) => a.epochId - b.epochId);
17835
+ }
17836
+
16944
17837
  /**
16945
17838
  * Check if the SDK is configured for a Lite deployment.
16946
17839
  * Lite deployments don't have KSU token, locking, or loyalty features.
@@ -17724,9 +18617,11 @@ const lendingPortfolioQuery = gql `
17724
18617
  $epochId: BigInt!
17725
18618
  $lastEpochId: BigInt!
17726
18619
  $unusedPools: [String!]!
18620
+ $first: Int!
17727
18621
  ) {
17728
18622
  user(id: $userAddress) {
17729
18623
  lendingPoolUserDetails(
18624
+ first: $first
17730
18625
  where: { lendingPool_not_in: $unusedPools }
17731
18626
  ) {
17732
18627
  id
@@ -17734,11 +18629,11 @@ const lendingPortfolioQuery = gql `
17734
18629
  id
17735
18630
  name
17736
18631
  balance
17737
- tranches {
18632
+ tranches(first: $first) {
17738
18633
  shares
17739
18634
  }
17740
18635
  }
17741
- lendingPoolTrancheUserDetails {
18636
+ lendingPoolTrancheUserDetails(first: $first) {
17742
18637
  shares
17743
18638
  lendingPoolTrancheUserEpochSharesUpdates(
17744
18639
  orderDirection: desc
@@ -17748,7 +18643,7 @@ const lendingPortfolioQuery = gql `
17748
18643
  ) {
17749
18644
  shares
17750
18645
  }
17751
- userLendingPoolTrancheFixedTermDepositLocks {
18646
+ userLendingPoolTrancheFixedTermDepositLocks(first: $first) {
17752
18647
  isLocked
17753
18648
  unlockAmount
17754
18649
  initialAmount
@@ -17772,6 +18667,14 @@ const lendingPortfolioQuery = gql `
17772
18667
  ) {
17773
18668
  shares
17774
18669
  }
18670
+ lockSharesAtLastEpoch: userLendingPoolTrancheFixedTermDepositLockShareUpdate(
18671
+ orderBy: shareUpdatesIndex
18672
+ orderDirection: desc
18673
+ first: 1
18674
+ where: { epochId_lt: $epochId }
18675
+ ) {
18676
+ shares
18677
+ }
17775
18678
  }
17776
18679
  tranche {
17777
18680
  id
@@ -17798,6 +18701,7 @@ const lendingPortfolioQuery = gql `
17798
18701
  }
17799
18702
  }
17800
18703
  userRequests(
18704
+ first: $first
17801
18705
  where: {
17802
18706
  user: $userAddress
17803
18707
  userRequestEvents_: {
@@ -17813,6 +18717,7 @@ const lendingPortfolioQuery = gql `
17813
18717
  }
17814
18718
  fixedTermConfigId
17815
18719
  userRequestEvents(
18720
+ first: $first
17816
18721
  where: { type_in: [DepositInitiated, DepositIncreased] }
17817
18722
  ) {
17818
18723
  id
@@ -17822,6 +18727,371 @@ const lendingPortfolioQuery = gql `
17822
18727
  }
17823
18728
  `;
17824
18729
 
18730
+ /** The subgraph returns at most this many rows per collection per request. */
18731
+ const YIELD_HISTORY_PAGE_SIZE = 1000;
18732
+ /**
18733
+ * A runaway guard, not a working limit: a collection this many pages long
18734
+ * (`MAX × PAGE_SIZE` rows for one lender) is a broken response, not a history.
18735
+ */
18736
+ const YIELD_HISTORY_MAX_PAGES = 100;
18737
+ /**
18738
+ * Throws `RangeError` on a malformed `fromEpoch` / `count`. `getYieldHistory`
18739
+ * calls it BEFORE any network read.
18740
+ */
18741
+ function validateYieldHistoryOptions(options) {
18742
+ const { fromEpoch, count } = options;
18743
+ if (fromEpoch !== undefined &&
18744
+ (!Number.isSafeInteger(fromEpoch) || fromEpoch < 0)) {
18745
+ throw new RangeError(`getYieldHistory: fromEpoch must be a non-negative integer, got ${String(fromEpoch)}`);
18746
+ }
18747
+ if (count !== undefined && (!Number.isSafeInteger(count) || count < 1)) {
18748
+ throw new RangeError(`getYieldHistory: count must be a positive integer, got ${String(count)}`);
18749
+ }
18750
+ }
18751
+ function resolveYieldHistoryWindow(options, currentEpoch) {
18752
+ validateYieldHistoryOptions(options);
18753
+ const { fromEpoch, count } = options;
18754
+ const newest = currentEpoch - 1;
18755
+ if (newest < 0)
18756
+ return null;
18757
+ let from;
18758
+ let to = newest;
18759
+ if (fromEpoch !== undefined) {
18760
+ from = fromEpoch;
18761
+ if (count !== undefined)
18762
+ to = Math.min(newest, fromEpoch + count - 1);
18763
+ }
18764
+ else {
18765
+ from = count !== undefined ? Math.max(0, newest - count + 1) : 0;
18766
+ }
18767
+ return from > to ? null : { fromEpoch: from, toEpoch: to };
18768
+ }
18769
+ // ---------------------------------------------------------------------------
18770
+ // Subgraph reads
18771
+ // ---------------------------------------------------------------------------
18772
+ /**
18773
+ * Round trip 1: the lender's tranche positions and fixed-term locks. Same
18774
+ * pool exclusion and the same nested-list size (`$first`) as
18775
+ * `lendingPortfolioQuery`, so both read the same lock set.
18776
+ */
18777
+ const yieldHistoryPositionsQuery = gql `
18778
+ query YieldHistoryPositions(
18779
+ $userAddress: ID!
18780
+ $unusedPools: [String!]!
18781
+ $first: Int!
18782
+ ) {
18783
+ user(id: $userAddress) {
18784
+ lendingPoolUserDetails(
18785
+ first: $first
18786
+ where: { lendingPool_not_in: $unusedPools }
18787
+ ) {
18788
+ lendingPool {
18789
+ id
18790
+ }
18791
+ lendingPoolTrancheUserDetails(first: $first) {
18792
+ id
18793
+ tranche {
18794
+ id
18795
+ balance
18796
+ shares
18797
+ }
18798
+ userLendingPoolTrancheFixedTermDepositLocks(first: $first) {
18799
+ id
18800
+ epochLockStart
18801
+ epochLockEnd
18802
+ initialTrancheShares
18803
+ }
18804
+ }
18805
+ }
18806
+ }
18807
+ }
18808
+ `;
18809
+ /**
18810
+ * Round trip 2 onwards: the four per-epoch series, one page of each per
18811
+ * request, cursor-paginated by `id`. A series that is exhausted is skipped with
18812
+ * `@include`, so a history costs as many requests as its LONGEST series has
18813
+ * pages — normally one — never one per epoch.
18814
+ *
18815
+ * Share snapshots are read up to the window's end with no lower bound: the
18816
+ * value AS OF the first epoch may come from an update long before it.
18817
+ */
18818
+ const yieldHistoryPageQuery = gql `
18819
+ query YieldHistoryPage(
18820
+ $tranches: [String!]!
18821
+ $userTranches: [String!]!
18822
+ $locks: [String!]!
18823
+ $fromEpoch: BigInt!
18824
+ $toEpoch: BigInt!
18825
+ $interestCursor: ID!
18826
+ $trancheSharesCursor: ID!
18827
+ $userSharesCursor: ID!
18828
+ $lockSharesCursor: ID!
18829
+ $withInterest: Boolean!
18830
+ $withTrancheShares: Boolean!
18831
+ $withUserShares: Boolean!
18832
+ $withLockShares: Boolean!
18833
+ $first: Int!
18834
+ ) {
18835
+ interest: lendingPoolTrancheEpochInterests(
18836
+ first: $first
18837
+ orderBy: id
18838
+ orderDirection: asc
18839
+ where: {
18840
+ id_gt: $interestCursor
18841
+ lendingPoolTranche_in: $tranches
18842
+ epochId_gte: $fromEpoch
18843
+ epochId_lte: $toEpoch
18844
+ }
18845
+ ) @include(if: $withInterest) {
18846
+ id
18847
+ epochId
18848
+ epochInterestAmount
18849
+ lendingPoolTranche {
18850
+ id
18851
+ }
18852
+ }
18853
+ trancheShares: lendingPoolTrancheShareUpdates(
18854
+ first: $first
18855
+ orderBy: id
18856
+ orderDirection: asc
18857
+ where: {
18858
+ id_gt: $trancheSharesCursor
18859
+ lendingPoolTranche_in: $tranches
18860
+ epochId_lte: $toEpoch
18861
+ }
18862
+ ) @include(if: $withTrancheShares) {
18863
+ id
18864
+ epochId
18865
+ shareUpdatesIndex
18866
+ shares
18867
+ lendingPoolTranche {
18868
+ id
18869
+ }
18870
+ }
18871
+ userShares: lendingPoolTrancheUserEpochSharesUpdates(
18872
+ first: $first
18873
+ orderBy: id
18874
+ orderDirection: asc
18875
+ where: {
18876
+ id_gt: $userSharesCursor
18877
+ lendingPoolTrancheUserDetails_in: $userTranches
18878
+ epochId_lte: $toEpoch
18879
+ }
18880
+ ) @include(if: $withUserShares) {
18881
+ id
18882
+ epochId
18883
+ shareUpdatesIndex
18884
+ shares
18885
+ lendingPoolTrancheUserDetails {
18886
+ id
18887
+ }
18888
+ }
18889
+ lockShares: userLendingPoolTrancheFtdLockShareUpdates(
18890
+ first: $first
18891
+ orderBy: id
18892
+ orderDirection: asc
18893
+ where: {
18894
+ id_gt: $lockSharesCursor
18895
+ userLendingPoolTrancheFixedTermDepositLock_in: $locks
18896
+ epochId_lte: $toEpoch
18897
+ }
18898
+ ) @include(if: $withLockShares) {
18899
+ id
18900
+ epochId
18901
+ shareUpdatesIndex
18902
+ shares
18903
+ userLendingPoolTrancheFixedTermDepositLock {
18904
+ id
18905
+ }
18906
+ }
18907
+ }
18908
+ `;
18909
+ /**
18910
+ * Fetch the four series for the window, paginating each by `id` until a short
18911
+ * page. Returns the rows and how many requests it took.
18912
+ */
18913
+ function fetchYieldHistorySeries(request, ids, window) {
18914
+ return __awaiter(this, void 0, void 0, function* () {
18915
+ var _a;
18916
+ const rows = {
18917
+ interest: [],
18918
+ trancheShares: [],
18919
+ userShares: [],
18920
+ lockShares: [],
18921
+ };
18922
+ const series = [
18923
+ { key: 'interest', flag: 'withInterest', cursor: 'interestCursor' },
18924
+ {
18925
+ key: 'trancheShares',
18926
+ flag: 'withTrancheShares',
18927
+ cursor: 'trancheSharesCursor',
18928
+ },
18929
+ {
18930
+ key: 'userShares',
18931
+ flag: 'withUserShares',
18932
+ cursor: 'userSharesCursor',
18933
+ },
18934
+ {
18935
+ key: 'lockShares',
18936
+ flag: 'withLockShares',
18937
+ cursor: 'lockSharesCursor',
18938
+ },
18939
+ ];
18940
+ const cursors = {
18941
+ interest: '',
18942
+ trancheShares: '',
18943
+ userShares: '',
18944
+ lockShares: '',
18945
+ };
18946
+ const open = {
18947
+ interest: ids.tranches.length > 0,
18948
+ trancheShares: ids.tranches.length > 0,
18949
+ userShares: ids.userTranches.length > 0,
18950
+ lockShares: ids.locks.length > 0,
18951
+ };
18952
+ let requests = 0;
18953
+ while (series.some(({ key }) => open[key])) {
18954
+ if (requests >= YIELD_HISTORY_MAX_PAGES) {
18955
+ throw new Error(`getYieldHistory: more than ${String(YIELD_HISTORY_MAX_PAGES)} pages for one lender`);
18956
+ }
18957
+ const variables = {
18958
+ tranches: ids.tranches,
18959
+ userTranches: ids.userTranches,
18960
+ locks: ids.locks,
18961
+ fromEpoch: String(window.fromEpoch),
18962
+ toEpoch: String(window.toEpoch),
18963
+ first: YIELD_HISTORY_PAGE_SIZE,
18964
+ };
18965
+ for (const { key, flag, cursor } of series) {
18966
+ variables[flag] = open[key];
18967
+ variables[cursor] = cursors[key];
18968
+ }
18969
+ const page = yield request(variables);
18970
+ requests += 1;
18971
+ for (const { key } of series) {
18972
+ if (!open[key])
18973
+ continue;
18974
+ const got = (_a = page[key]) !== null && _a !== void 0 ? _a : [];
18975
+ rows[key].push(...got);
18976
+ const last = got.at(-1);
18977
+ if (got.length < YIELD_HISTORY_PAGE_SIZE || last === undefined) {
18978
+ open[key] = false;
18979
+ continue;
18980
+ }
18981
+ if (last.id <= cursors[key]) {
18982
+ throw new Error(`getYieldHistory: the ${key} cursor did not advance`);
18983
+ }
18984
+ cursors[key] = last.id;
18985
+ }
18986
+ }
18987
+ return { rows, requests };
18988
+ });
18989
+ }
18990
+ /**
18991
+ * The snapshot in force at `epoch`: among updates with `epochId ≤ epoch`, the
18992
+ * one with the highest `shareUpdatesIndex` — exactly what
18993
+ * `lendingPortfolioQuery` selects with `orderBy: shareUpdatesIndex, desc,
18994
+ * first: 1, where: { epochId_lt: epoch + 1 }`. `undefined` when none.
18995
+ */
18996
+ function sharesAsOf(updates, epoch) {
18997
+ let found;
18998
+ for (const update of updates) {
18999
+ if (Number(update.epochId) > epoch)
19000
+ continue;
19001
+ if (!found || update.shareUpdatesIndex > found.shareUpdatesIndex) {
19002
+ found = update;
19003
+ }
19004
+ }
19005
+ return found;
19006
+ }
19007
+ function groupBy(rows, key) {
19008
+ const map = new Map();
19009
+ for (const row of rows) {
19010
+ const k = key(row);
19011
+ const list = map.get(k);
19012
+ if (list)
19013
+ list.push(row);
19014
+ else
19015
+ map.set(k, [row]);
19016
+ }
19017
+ return map;
19018
+ }
19019
+ /**
19020
+ * Pure: the fetched series → one entry per pool, tranche and settled epoch in
19021
+ * which the lender held shares (or a lock earned a top-up). Each entry runs the
19022
+ * same `epochTrancheYield` as the portfolio's last-epoch figure, with the same
19023
+ * share selection, so the newest entry equals that figure.
19024
+ *
19025
+ * Sorted by `epochId`, then `poolId`, then `trancheId`.
19026
+ */
19027
+ function buildYieldHistory(read, window, clock) {
19028
+ var _a, _b, _c, _d, _e, _f;
19029
+ const user = read.positions.user;
19030
+ if (!user)
19031
+ return [];
19032
+ const interestByTranche = groupBy(read.interest, (row) => row.lendingPoolTranche.id);
19033
+ const trancheSharesByTranche = groupBy(read.trancheShares, (row) => row.lendingPoolTranche.id);
19034
+ const userSharesByPosition = groupBy(read.userShares, (row) => row.lendingPoolTrancheUserDetails.id);
19035
+ const lockSharesByLock = groupBy(read.lockShares, (row) => row.userLendingPoolTrancheFixedTermDepositLock.id);
19036
+ const entries = [];
19037
+ for (const pool of user.lendingPoolUserDetails) {
19038
+ for (const position of pool.lendingPoolTrancheUserDetails) {
19039
+ const trancheId = position.tranche.id;
19040
+ const trancheUpdates = (_a = trancheSharesByTranche.get(trancheId)) !== null && _a !== void 0 ? _a : [];
19041
+ const userUpdates = (_b = userSharesByPosition.get(position.id)) !== null && _b !== void 0 ? _b : [];
19042
+ const trancheBalanceNow = parseFloat(position.tranche.balance);
19043
+ const trancheSharesNow = parseFloat(position.tranche.shares);
19044
+ for (const record of (_c = interestByTranche.get(trancheId)) !== null && _c !== void 0 ? _c : []) {
19045
+ const epoch = Number(record.epochId);
19046
+ if (epoch < window.fromEpoch || epoch > window.toEpoch) {
19047
+ continue;
19048
+ }
19049
+ const userSnapshot = sharesAsOf(userUpdates, epoch);
19050
+ const userShares = parseFloat((_d = userSnapshot === null || userSnapshot === void 0 ? void 0 : userSnapshot.shares) !== null && _d !== void 0 ? _d : '0');
19051
+ const locks = position.userLendingPoolTrancheFixedTermDepositLocks.map((lock) => {
19052
+ var _a, _b, _c, _d, _e;
19053
+ const lockUpdates = (_a = lockSharesByLock.get(lock.id)) !== null && _a !== void 0 ? _a : [];
19054
+ return {
19055
+ active: parseFloat(lock.epochLockStart) < epoch &&
19056
+ parseFloat(lock.epochLockEnd) >= epoch &&
19057
+ userSnapshot !== undefined,
19058
+ // No snapshot yet = the lock's opening
19059
+ // shares: a lock made after clearing E
19060
+ // starts at E but its first update is
19061
+ // tagged E + 1, and reading that gap as 0
19062
+ // would book its principal as a top-up.
19063
+ sharesAtEpoch: parseFloat((_c = (_b = sharesAsOf(lockUpdates, epoch)) === null || _b === void 0 ? void 0 : _b.shares) !== null && _c !== void 0 ? _c : lock.initialTrancheShares),
19064
+ sharesBeforeEpoch: parseFloat((_e = (_d = sharesAsOf(lockUpdates, epoch - 1)) === null || _d === void 0 ? void 0 : _d.shares) !== null && _e !== void 0 ? _e : lock.initialTrancheShares),
19065
+ };
19066
+ });
19067
+ const { total } = epochTrancheYield({
19068
+ epochInterest: parseFloat(record.epochInterestAmount),
19069
+ userShares,
19070
+ trancheShares: parseFloat((_f = (_e = sharesAsOf(trancheUpdates, epoch)) === null || _e === void 0 ? void 0 : _e.shares) !== null && _f !== void 0 ? _f : '0'),
19071
+ trancheBalanceNow,
19072
+ trancheSharesNow,
19073
+ locks,
19074
+ });
19075
+ // Nothing held and nothing topped up: not part of this
19076
+ // lender's history (a new lender's history is empty).
19077
+ if (userShares <= 0 && total === 0)
19078
+ continue;
19079
+ entries.push({
19080
+ epochId: epoch,
19081
+ epochEnd: clock.epochZeroStart +
19082
+ (epoch + 1) * clock.epochDuration,
19083
+ poolId: pool.lendingPool.id,
19084
+ trancheId,
19085
+ netYield: total,
19086
+ });
19087
+ }
19088
+ }
19089
+ }
19090
+ return entries.sort((a, b) => a.epochId - b.epochId ||
19091
+ a.poolId.localeCompare(b.poolId) ||
19092
+ a.trancheId.localeCompare(b.trancheId));
19093
+ }
19094
+
17825
19095
  class Portfolio {
17826
19096
  constructor(_kasuConfig, signerOrProvider) {
17827
19097
  this._kasuConfig = _kasuConfig;
@@ -17922,7 +19192,7 @@ class Portfolio {
17922
19192
  performanceFee *
17923
19193
  ecosystemFeeRate;
17924
19194
  }
17925
- return ((totalWeeklyEcosystemFees * userRKsuAmount) / totalRKsuAmount);
19195
+ return (totalWeeklyEcosystemFees * userRKsuAmount) / totalRKsuAmount;
17926
19196
  }
17927
19197
  /**
17928
19198
  * Calculate the user's weekly KASU bonus rewards (KASU) based on current deposits,
@@ -17930,7 +19200,7 @@ class Portfolio {
17930
19200
  */
17931
19201
  calculateWeeklyKsuBonusRewards(params) {
17932
19202
  return __awaiter(this, void 0, void 0, function* () {
17933
- const { userAddress, userRksuAmount, userDepositAmounts, ksuEpochPrice } = params;
19203
+ const { userAddress, userRksuAmount, userDepositAmounts, ksuEpochPrice, } = params;
17934
19204
  const totalUserDeposits = userDepositAmounts[0].add(userDepositAmounts[1]);
17935
19205
  if (totalUserDeposits.isZero())
17936
19206
  return 0;
@@ -18039,7 +19309,7 @@ class Portfolio {
18039
19309
  }
18040
19310
  getPortfolioLendingData(userAddress, poolOverviews, currentEpoch, provider) {
18041
19311
  return __awaiter(this, void 0, void 0, function* () {
18042
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
19312
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
18043
19313
  const SECONDS_PER_EPOCH = 604800; // seconds in a week
18044
19314
  const portfolioLendingPoolsPromise = [];
18045
19315
  const previousEpoch = BigNumber.from(currentEpoch).sub(BigNumber.from(1));
@@ -18049,6 +19319,7 @@ class Portfolio {
18049
19319
  epochId: parseFloat(currentEpoch),
18050
19320
  lastEpochId: previousEpoch.toNumber(),
18051
19321
  unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
19322
+ first: YIELD_HISTORY_PAGE_SIZE,
18052
19323
  }),
18053
19324
  this._userLendingService.getPortfolioUserTrancheBalances(userAddress),
18054
19325
  this._userLendingService.getUserPoolBalance(userAddress, poolOverviews.map(({ id }) => id), provider),
@@ -18086,7 +19357,6 @@ class Portfolio {
18086
19357
  }
18087
19358
  }
18088
19359
  let totalInvestments = BigNumber.from(0);
18089
- let totalYieldEarnedLastEpoch = 0;
18090
19360
  for (const poolOverview of poolOverviews) {
18091
19361
  const userPoolBalance = userPoolBalances.find(({ poolId }) => poolId === poolOverview.id);
18092
19362
  if (!userPoolBalance) {
@@ -18094,6 +19364,7 @@ class Portfolio {
18094
19364
  }
18095
19365
  totalInvestments = userPoolBalance.balance.add(totalInvestments);
18096
19366
  const tranches = [];
19367
+ let poolYieldEarnedLastEpoch = 0;
18097
19368
  const lendingPoolUserDetails = lastEpochData.user.lendingPoolUserDetails.find(({ lendingPool }) => lendingPool.id === poolOverview.id);
18098
19369
  if (!lendingPoolUserDetails)
18099
19370
  continue;
@@ -18112,18 +19383,42 @@ class Portfolio {
18112
19383
  }, depositDetails, fixedLoans: [] }));
18113
19384
  continue;
18114
19385
  }
18115
- const lastEpochTotalYield = parseFloat((_e = (_d = (_c = lastEpochUserTrancheDetails.tranche
18116
- .lendingPoolTrancheEpochInterest) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.epochInterestAmount) !== null && _e !== void 0 ? _e : '0');
18117
- const lastEpochUserShares = parseFloat((_h = (_g = (_f = lastEpochUserTrancheDetails
18118
- .lendingPoolTrancheUserEpochSharesUpdates) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.shares) !== null && _h !== void 0 ? _h : '0');
18119
- const lastEpochTotalShares = parseFloat((_l = (_k = (_j = lastEpochUserTrancheDetails.tranche
18120
- .lendingPoolTrancheShareUpdates) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.shares) !== null && _l !== void 0 ? _l : '0');
18121
- const currentTotalAmount = parseFloat(lastEpochUserTrancheDetails.tranche.balance);
18122
- const currentTotalShares = parseFloat(lastEpochUserTrancheDetails.tranche.shares);
18123
- const lastEpochTotalBaseYield = lastEpochTotalShares === 0
18124
- ? 0
18125
- : (lastEpochTotalYield * lastEpochUserShares) /
18126
- lastEpochTotalShares;
19386
+ const lastEpoch = previousEpoch.toNumber();
19387
+ const userHasLastEpochShares = Boolean((_c = lastEpochUserTrancheDetails
19388
+ .lendingPoolTrancheUserEpochSharesUpdates) === null || _c === void 0 ? void 0 : _c.length);
19389
+ const fixedTermDeposits = lastEpochUserTrancheDetails.userLendingPoolTrancheFixedTermDepositLocks;
19390
+ // The one per-epoch formula `getYieldHistory` runs too, with
19391
+ // the same share selection — so the last bar of a history
19392
+ // equals these figures.
19393
+ const lastEpochYield = epochTrancheYield({
19394
+ epochInterest: parseFloat((_f = (_e = (_d = lastEpochUserTrancheDetails.tranche
19395
+ .lendingPoolTrancheEpochInterest) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.epochInterestAmount) !== null && _f !== void 0 ? _f : '0'),
19396
+ userShares: parseFloat((_j = (_h = (_g = lastEpochUserTrancheDetails
19397
+ .lendingPoolTrancheUserEpochSharesUpdates) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.shares) !== null && _j !== void 0 ? _j : '0'),
19398
+ trancheShares: parseFloat((_m = (_l = (_k = lastEpochUserTrancheDetails.tranche
19399
+ .lendingPoolTrancheShareUpdates) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.shares) !== null && _m !== void 0 ? _m : '0'),
19400
+ trancheBalanceNow: parseFloat(lastEpochUserTrancheDetails.tranche.balance),
19401
+ trancheSharesNow: parseFloat(lastEpochUserTrancheDetails.tranche.shares),
19402
+ locks: fixedTermDeposits.map((fixedTermDeposit) => {
19403
+ var _a, _b, _c, _d, _e, _f;
19404
+ return ({
19405
+ active: parseFloat(fixedTermDeposit.epochLockStart) <
19406
+ lastEpoch &&
19407
+ parseFloat(fixedTermDeposit.epochLockEnd) >=
19408
+ lastEpoch &&
19409
+ userHasLastEpochShares,
19410
+ // No snapshot = the lock's opening shares, exactly
19411
+ // as `getYieldHistory` reads it: a lock made after
19412
+ // clearing E starts at E with its first update
19413
+ // tagged E + 1. Never the CURRENT shares, which
19414
+ // would carry a top-up from a clearing that ran
19415
+ // after the last settled epoch.
19416
+ 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),
19417
+ sharesBeforeEpoch: parseFloat((_f = (_e = (_d = fixedTermDeposit
19418
+ .userLendingPoolTrancheFixedTermDepositLockShareUpdate) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.shares) !== null && _f !== void 0 ? _f : fixedTermDeposit.initialTrancheShares),
19419
+ });
19420
+ }),
19421
+ });
18127
19422
  const userTrancheBalance = {
18128
19423
  balance: '0',
18129
19424
  yieldEarned: 0,
@@ -18138,47 +19433,20 @@ class Portfolio {
18138
19433
  }
18139
19434
  }
18140
19435
  let totalLifetimeFtdYield = 0;
18141
- let lastEpochVariableBaseYield = lastEpochTotalBaseYield;
18142
- const fixedLoans = lastEpochUserTrancheDetails.userLendingPoolTrancheFixedTermDepositLocks.map((fixedTermDeposit) => {
18143
- var _a, _b, _c, _d, _e;
19436
+ const fixedLoans = fixedTermDeposits.map((fixedTermDeposit, lockIndex) => {
19437
+ var _a, _b;
18144
19438
  const fixedLoanDepositDetails = (_a = userRequest === null || userRequest === void 0 ? void 0 : userRequest[fixedTermDeposit
18145
- .lendingPoolTrancheFixedTermConfig
18146
- .configId]) !== null && _a !== void 0 ? _a : [];
18147
- let lastEpochYieldEarned = 0;
18148
- let lifetimeYieldEarned = 0;
18149
- if (parseFloat(fixedTermDeposit.epochLockStart) <
18150
- previousEpoch.toNumber() &&
18151
- parseFloat(fixedTermDeposit.epochLockEnd) >=
18152
- previousEpoch.toNumber() &&
18153
- Boolean((_b = lastEpochUserTrancheDetails
18154
- .lendingPoolTrancheUserEpochSharesUpdates) === null || _b === void 0 ? void 0 : _b.length)) {
18155
- const currentEpochFtdShares = parseFloat(fixedTermDeposit.trancheShares);
18156
- const lastEpochFtdShares = parseFloat((_e = (_d = (_c = fixedTermDeposit
18157
- .userLendingPoolTrancheFixedTermDepositLockShareUpdate) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.shares) !== null && _e !== void 0 ? _e : '0');
18158
- const additionalFtdYield = ((currentEpochFtdShares -
18159
- lastEpochFtdShares) *
18160
- currentTotalAmount) /
18161
- currentTotalShares;
18162
- const lastEpochFtdBaseYield = (lastEpochTotalBaseYield *
18163
- lastEpochFtdShares) /
18164
- lastEpochUserShares;
18165
- lastEpochVariableBaseYield -=
18166
- lastEpochFtdBaseYield;
18167
- lastEpochYieldEarned =
18168
- lastEpochFtdBaseYield + additionalFtdYield;
18169
- }
18170
- lifetimeYieldEarned =
18171
- this.calculateFixedTermDepositEarnings(fixedTermDeposit, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares);
19439
+ .lendingPoolTrancheFixedTermConfig.configId]) !== null && _a !== void 0 ? _a : [];
19440
+ const lastEpochYieldEarned = (_b = lastEpochYield.locks[lockIndex]) !== null && _b !== void 0 ? _b : 0;
19441
+ const lifetimeYieldEarned = this.calculateFixedTermDepositEarnings(fixedTermDeposit, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares);
18172
19442
  totalLifetimeFtdYield += lifetimeYieldEarned;
18173
- totalYieldEarnedLastEpoch += lastEpochYieldEarned;
18174
19443
  const lockDuration = parseFloat(fixedTermDeposit
18175
19444
  .lendingPoolTrancheFixedTermConfig
18176
19445
  .epochLockDuration) * SECONDS_PER_EPOCH;
18177
19446
  return {
18178
19447
  lockId: fixedTermDeposit.lockId,
18179
19448
  configId: fixedTermDeposit
18180
- .lendingPoolTrancheFixedTermConfig
18181
- .configId,
19449
+ .lendingPoolTrancheFixedTermConfig.configId,
18182
19450
  epochLockDuration: fixedTermDeposit
18183
19451
  .lendingPoolTrancheFixedTermConfig
18184
19452
  .epochLockDuration,
@@ -18188,9 +19456,7 @@ class Portfolio {
18188
19456
  .epochInterestRate,
18189
19457
  epochLockStart: fixedTermDeposit.epochLockStart,
18190
19458
  amount: this._userLendingService
18191
- .convertSharesToAssets(fixedTermDeposit.trancheShares, lastEpochUserTrancheDetails.tranche
18192
- .balance, lastEpochUserTrancheDetails.tranche
18193
- .shares)
19459
+ .convertSharesToAssets(fixedTermDeposit.trancheShares, lastEpochUserTrancheDetails.tranche.balance, lastEpochUserTrancheDetails.tranche.shares)
18194
19460
  .toString(),
18195
19461
  endTime: parseInt(fixedTermDeposit.createdOn) +
18196
19462
  lockDuration,
@@ -18198,20 +19464,22 @@ class Portfolio {
18198
19464
  isWithdrawalRequested: fixedTermDeposit.isWithdrawalRequested,
18199
19465
  startTime: parseInt(fixedTermDeposit.createdOn),
18200
19466
  yieldEarnings: {
18201
- lastEpoch: lastEpochYieldEarned.toString(),
19467
+ lastEpoch: this.precisionToString(lastEpochYieldEarned),
18202
19468
  lifetime: this.precisionToString(lifetimeYieldEarned),
18203
19469
  },
18204
19470
  depositDetails: fixedLoanDepositDetails,
18205
19471
  };
18206
19472
  });
18207
- totalYieldEarnedLastEpoch += lastEpochTotalBaseYield;
19473
+ // This POOL's last-epoch total: the tranche totals summed, the
19474
+ // locks counted once (inside `total`).
19475
+ poolYieldEarnedLastEpoch += lastEpochYield.total;
18208
19476
  tranches.push(Object.assign(Object.assign({}, tranche), { investedAmount: userTrancheBalance, yieldEarnings: {
18209
- lastEpoch: lastEpochVariableBaseYield.toString(),
19477
+ lastEpoch: this.precisionToString(lastEpochYield.variable),
18210
19478
  lifetime: this.precisionToString(totalLifetimeFtdYield), // passing in total FTD yield to be subtracted by total trache lifetime later down
18211
19479
  }, fixedLoans,
18212
19480
  depositDetails }));
18213
19481
  }
18214
- portfolioLendingPoolsPromise.push(Object.assign(Object.assign({}, poolOverview), { totalYieldEarningsLastEpoch: totalYieldEarnedLastEpoch.toString(), totalInvestedAmount: userPoolBalance.balance.toString(), totalYieldEarningsLifetime: userPoolBalance.yieldEarned.toString(), tranches }));
19482
+ portfolioLendingPoolsPromise.push(Object.assign(Object.assign({}, poolOverview), { totalYieldEarningsLastEpoch: this.precisionToString(poolYieldEarnedLastEpoch), totalInvestedAmount: userPoolBalance.balance.toString(), totalYieldEarningsLifetime: userPoolBalance.yieldEarned.toString(), tranches }));
18215
19483
  }
18216
19484
  const portfolioLendingPools = portfolioLendingPoolsPromise.map((pool) => (Object.assign(Object.assign({}, pool), { tranches: pool.tranches.map((tranche) => {
18217
19485
  const trancheBalance = tranche.investedAmount;
@@ -18229,7 +19497,87 @@ class Portfolio {
18229
19497
  !parseUnits(pool.totalYieldEarningsLifetime).isZero());
18230
19498
  });
18231
19499
  }
19500
+ /**
19501
+ * The lender's settled yield per cycle (epoch), per pool and tranche —
19502
+ * what the chain credited at each close, NET of the performance fee
19503
+ * (`domain/yield-history.ts`), in the deployment's own stable token. Each
19504
+ * entry runs the same `epochTrancheYield` as `getPortfolioLendingData`'s
19505
+ * last-epoch figures: a tranche's `yieldEarnings.lastEpoch` plus its
19506
+ * locks' equals its entry for the epoch of the tranche's most recently
19507
+ * APPLIED interest (older than the last cycle for a pool that has not
19508
+ * cleared since; no entry, and a figure of 0, when the lender held
19509
+ * nothing at that epoch).
19510
+ *
19511
+ * Sum entries with `aggregateYieldHistory` (all strategies, or one with
19512
+ * `{ poolId }`). Only epochs in which the lender held shares appear — a
19513
+ * new lender gets `[]`. Pools in `UNUSED_LENDING_POOL_IDS` are left out,
19514
+ * as in `lendingPortfolioQuery`; Directus enablement is NOT applied.
19515
+ *
19516
+ * Cost: three parallel `SystemVariables` reads and the positions query in
19517
+ * one round, then one subgraph request per page of the longest series
19518
+ * (1000 rows a page) — normally two subgraph round trips in all, never one
19519
+ * per epoch.
19520
+ *
19521
+ * On a LITE deployment, a subgraph that lacks the entities (a validation
19522
+ * error naming them) yields `[]`; on a full deployment that error throws,
19523
+ * like every other failure (`isEmptyHistoryError`).
19524
+ *
19525
+ * @throws RangeError on a malformed `fromEpoch` / `count`, before any
19526
+ * network read.
19527
+ */
19528
+ getYieldHistory(userAddress_1) {
19529
+ return __awaiter(this, arguments, void 0, function* (userAddress, options = {}) {
19530
+ validateYieldHistoryOptions(options);
19531
+ try {
19532
+ const [currentEpoch, epochZeroStart, epochDuration, positions] = yield Promise.all([
19533
+ this._systemVariablesAbi.currentEpochNumber(),
19534
+ this._systemVariablesAbi.epochStartTimestamp(0),
19535
+ this._systemVariablesAbi.epochDuration(),
19536
+ this._graph.request(yieldHistoryPositionsQuery, {
19537
+ userAddress: userAddress.toLowerCase(),
19538
+ unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
19539
+ first: YIELD_HISTORY_PAGE_SIZE,
19540
+ }),
19541
+ ]);
19542
+ const window = resolveYieldHistoryWindow(options, currentEpoch.toNumber());
19543
+ if (!window || !positions.user)
19544
+ return [];
19545
+ const userTranches = [];
19546
+ const tranches = new Set();
19547
+ const locks = [];
19548
+ for (const pool of positions.user.lendingPoolUserDetails) {
19549
+ for (const position of pool.lendingPoolTrancheUserDetails) {
19550
+ userTranches.push(position.id);
19551
+ tranches.add(position.tranche.id);
19552
+ for (const lock of position.userLendingPoolTrancheFixedTermDepositLocks) {
19553
+ locks.push(lock.id);
19554
+ }
19555
+ }
19556
+ }
19557
+ if (userTranches.length === 0)
19558
+ return [];
19559
+ const { rows } = yield fetchYieldHistorySeries((variables) => this._graph.request(yieldHistoryPageQuery, variables), { tranches: [...tranches], userTranches, locks }, window);
19560
+ return buildYieldHistory(Object.assign({ positions }, rows), window, {
19561
+ epochZeroStart: epochZeroStart.toNumber(),
19562
+ epochDuration: epochDuration.toNumber(),
19563
+ });
19564
+ }
19565
+ catch (error) {
19566
+ if (isEmptyHistoryError(error, this._isLiteDeployment))
19567
+ return [];
19568
+ throw error;
19569
+ }
19570
+ });
19571
+ }
19572
+ /**
19573
+ * A money figure as a plain decimal string. Below a millionth of a token
19574
+ * (the stable asset's own precision) is float noise, never money, so it
19575
+ * is `'0'` — not `'1e-7'`, which `parseUnits` rejects, nor `'-3e-12'`. A
19576
+ * small negative remainder (above −0.0001) is `'0'` as well.
19577
+ */
18232
19578
  precisionToString(val) {
19579
+ if (Math.abs(val) < 1e-6)
19580
+ return '0';
18233
19581
  if (val < 0 && val > -1e-4) {
18234
19582
  return '0';
18235
19583
  }
@@ -24186,11 +25534,11 @@ class Swapper {
24186
25534
  }
24187
25535
 
24188
25536
  class KasuSdk {
24189
- constructor(config, signerOrProvider) {
25537
+ constructor(config, signerOrProvider, options = {}) {
24190
25538
  this._graphClient = new GraphQLClient(config.subgraphUrl);
24191
25539
  this.Locking = new KSULocking(config, signerOrProvider);
24192
25540
  this.UserLending = new UserLending(config, signerOrProvider);
24193
- this.DataService = new DataService(config, signerOrProvider);
25541
+ this.DataService = new DataService(config, signerOrProvider, options.historyProvider);
24194
25542
  this.Portfolio = new Portfolio(config, signerOrProvider);
24195
25543
  this.Swapper = new Swapper();
24196
25544
  }
@@ -24198,7 +25546,7 @@ class KasuSdk {
24198
25546
 
24199
25547
  class SdkConfig {
24200
25548
  constructor(options) {
24201
- var _a, _b, _c, _d;
25549
+ var _a, _b, _c, _d, _e;
24202
25550
  this.subgraphUrl = options.subgraphUrl;
24203
25551
  this.contracts = options.contracts;
24204
25552
  this.directusUrl = (_a = options.directusUrl) !== null && _a !== void 0 ? _a : '';
@@ -24213,6 +25561,8 @@ class SdkConfig {
24213
25561
  this.isLiteDeployment = (_b = options.isLiteDeployment) !== null && _b !== void 0 ? _b : false;
24214
25562
  this.poolMetadataMapping = (_c = options.poolMetadataMapping) !== null && _c !== void 0 ? _c : {};
24215
25563
  this.stableAssetDecimals = (_d = options.stableAssetDecimals) !== null && _d !== void 0 ? _d : 6;
25564
+ this.historyRpcUrls = [...((_e = options.historyRpcUrls) !== null && _e !== void 0 ? _e : [])];
25565
+ this.chainId = options.chainId;
24216
25566
  }
24217
25567
  }
24218
25568
 
@@ -24519,6 +25869,27 @@ const XDC_RPC_URLS = [
24519
25869
  'https://rpc.xinfin.network',
24520
25870
  'https://rpc.primenumbers.xyz/',
24521
25871
  ];
25872
+ /**
25873
+ * XDC RPCs that serve PAST state, for `getPoolTvlHistory` (each verified
25874
+ * with `externalTVLOfPool` at old blocks, CORS `*`), in fallback order. Not
25875
+ * `rpc.xdcrpc.com`, which refuses older blocks, and never `rpc.xdc.org` /
25876
+ * `erpc.xdc.org`. Public and rate-limited.
25877
+ */
25878
+ const XDC_HISTORY_RPC_URLS = [
25879
+ 'https://rpc.primenumbers.xyz/',
25880
+ 'https://rpc.ankr.com/xdc',
25881
+ ];
25882
+ /**
25883
+ * Base RPCs that serve PAST state, in fallback order (each verified with
25884
+ * `externalTVLOfPool` at old blocks; all answer CORS). Not
25885
+ * `base-rpc.publicnode.com`, the default read RPC, which refuses old state.
25886
+ * Public and rate-limited.
25887
+ */
25888
+ const BASE_HISTORY_RPC_URLS = [
25889
+ 'https://mainnet.base.org',
25890
+ 'https://base.drpc.org',
25891
+ 'https://1rpc.io/base',
25892
+ ];
24522
25893
  /**
24523
25894
  * Built-in chain configurations for Kasu-supported networks.
24524
25895
  *
@@ -24562,6 +25933,7 @@ const CHAIN_CONFIGS = {
24562
25933
  'https://base-rpc.publicnode.com',
24563
25934
  'https://mainnet.base.org',
24564
25935
  ],
25936
+ historyRpcUrls: BASE_HISTORY_RPC_URLS,
24565
25937
  blockExplorerUrl: 'https://basescan.org',
24566
25938
  },
24567
25939
  xdc: {
@@ -24598,6 +25970,7 @@ const CHAIN_CONFIGS = {
24598
25970
  currencyCode: 'AUD',
24599
25971
  },
24600
25972
  rpcUrls: XDC_RPC_URLS,
25973
+ historyRpcUrls: XDC_HISTORY_RPC_URLS,
24601
25974
  blockExplorerUrl: 'https://xdcscan.com',
24602
25975
  },
24603
25976
  'xdc-usdc': {
@@ -24630,6 +26003,7 @@ const CHAIN_CONFIGS = {
24630
26003
  currencyCode: 'USD',
24631
26004
  },
24632
26005
  rpcUrls: XDC_RPC_URLS,
26006
+ historyRpcUrls: XDC_HISTORY_RPC_URLS,
24633
26007
  blockExplorerUrl: 'https://xdcscan.com',
24634
26008
  },
24635
26009
  plume: {
@@ -27741,6 +29115,28 @@ class StrategiesFacade {
27741
29115
  return yield this._dataService.getPerformanceFee();
27742
29116
  });
27743
29117
  }
29118
+ /**
29119
+ * The strategy's TVL (total lent by everyone) at every cycle boundary,
29120
+ * then as of now — the data behind a TVL chart. Ascending in time; the
29121
+ * last point (`kind: 'current'`) equals `strategy.tvl.total` when both
29122
+ * calls' reads succeed (see `PoolTvlHistoryEntry`).
29123
+ *
29124
+ * `tvl` is in display units of the deployment's OWN stable token (USDC on
29125
+ * Base and XDC USDC, AUDD on XDC AUDD); `timestamp` is unix SECONDS.
29126
+ * `{ count }` = the newest `count` epochs, `{ fromEpoch }` = from that
29127
+ * epoch on; `timeoutMs` (default 30 s) and `signal` bound the call. A
29128
+ * malformed option throws before any network read. See
29129
+ * `DataService.getPoolTvlHistory` for the reads, the cost and the errors.
29130
+ *
29131
+ * ```ts
29132
+ * const points = await kasu.strategies.getPoolTvlHistory(poolId, { count: 52 });
29133
+ * ```
29134
+ */
29135
+ getPoolTvlHistory(poolId_1) {
29136
+ return __awaiter(this, arguments, void 0, function* (poolId, options = {}) {
29137
+ return yield this._dataService.getPoolTvlHistory(poolId, options);
29138
+ });
29139
+ }
27744
29140
  /**
27745
29141
  * Fetch a single strategy by pool ID. Returns `null` if not found.
27746
29142
  */
@@ -28252,6 +29648,40 @@ class PortfolioFacade {
28252
29648
  return { pools, summary };
28253
29649
  });
28254
29650
  }
29651
+ /**
29652
+ * The lender's settled yield per cycle, per pool and tranche: what each
29653
+ * close credited them, NET of the performance fee (the chain credits it
29654
+ * net), in display units of the deployment's own stable token (AUDD on
29655
+ * XDC AUDD). `epochEnd` is unix seconds. Net only — no gross is offered.
29656
+ *
29657
+ * Validates `options` before any read (`RangeError` on a malformed one).
29658
+ *
29659
+ * ```ts
29660
+ * const entries = await kasu.portfolio.getYieldHistory('0xUser...', { count: 52 });
29661
+ * const allStrategies = aggregateYieldHistory(entries);
29662
+ * const oneStrategy = aggregateYieldHistory(entries, { poolId });
29663
+ * ```
29664
+ *
29665
+ * `getPositions`' `totalYieldEarningsLastEpoch` for a pool equals that
29666
+ * pool's bar for the epoch of its most recently APPLIED interest — which
29667
+ * is older than the last cycle for a pool that has not cleared since (the
29668
+ * bar carries that older `epochId`). A lender who held nothing at that
29669
+ * epoch has no bar there and a figure of 0; their newest bar is from
29670
+ * when they last held shares.
29671
+ *
29672
+ * For a fixed-term lock holder the bars can sum to slightly more than the
29673
+ * lifetime interest (up to about 0.2 %), because each top-up is valued at
29674
+ * today's share price — show "earned to date" from the lifetime figure,
29675
+ * never from the sum of the bars.
29676
+ *
29677
+ * `[]` for a lender with no history, and on a LITE deployment whose
29678
+ * subgraph lacks the entities; on a full deployment that error throws.
29679
+ */
29680
+ getYieldHistory(userAddress, options) {
29681
+ return __awaiter(this, void 0, void 0, function* () {
29682
+ return yield this._portfolio.getYieldHistory(userAddress, options);
29683
+ });
29684
+ }
28255
29685
  /**
28256
29686
  * Fetch a user's full transaction history (deposits, withdrawals, cancellations).
28257
29687
  */
@@ -28308,12 +29738,13 @@ class PortfolioFacade {
28308
29738
  * ```
28309
29739
  */
28310
29740
  class Kasu {
28311
- constructor(sdk, chainConfig, signerOrProvider, configOverrides) {
29741
+ constructor(sdk, chainConfig, signerOrProvider, configOverrides, historyProvider) {
28312
29742
  var _a, _b;
28313
29743
  this._sdk = sdk;
28314
29744
  this._chainConfig = chainConfig;
28315
29745
  this._signerOrProvider = signerOrProvider;
28316
29746
  this._configOverrides = configOverrides;
29747
+ this._historyProvider = historyProvider;
28317
29748
  this.strategies = new StrategiesFacade(sdk.DataService, sdk.UserLending, normaliseMapping((_a = configOverrides.poolMetadataMapping) !== null && _a !== void 0 ? _a : chainConfig.poolMetadataMapping));
28318
29749
  // Derived ONCE and handed to every facade that needs it. Each of them
28319
29750
  // re-deriving `Signer.isSigner` is how two facades come to disagree
@@ -28351,7 +29782,7 @@ class Kasu {
28351
29782
  * ```
28352
29783
  */
28353
29784
  static create(options) {
28354
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
29785
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
28355
29786
  const chainConfig = resolveChainConfig(options.chain);
28356
29787
  const overrides = (_a = options.configOverrides) !== null && _a !== void 0 ? _a : {};
28357
29788
  const sdkConfig = new SdkConfig({
@@ -28366,12 +29797,18 @@ class Kasu {
28366
29797
  // of 6. Every live deployment happens to be 6dp today, so the
28367
29798
  // default was right by luck; the next one need not be.
28368
29799
  stableAssetDecimals: (_h = overrides.stableAssetDecimals) !== null && _h !== void 0 ? _h : (_j = stableAssetOf(chainConfig)) === null || _j === void 0 ? void 0 : _j.decimals,
29800
+ historyRpcUrls: (_k = overrides.historyRpcUrls) !== null && _k !== void 0 ? _k : chainConfig.historyRpcUrls,
29801
+ chainId: (_l = overrides.chainId) !== null && _l !== void 0 ? _l : chainConfig.chainId,
28369
29802
  });
28370
- const signerOrProvider = (_k = options.signerOrProvider) !== null && _k !== void 0 ? _k : defaultProvider(chainConfig, typeof options.chain === 'string'
29803
+ const signerOrProvider = (_m = options.signerOrProvider) !== null && _m !== void 0 ? _m : defaultProvider(chainConfig, typeof options.chain === 'string'
28371
29804
  ? options.chain
28372
29805
  : chainConfig.name);
28373
- const sdk = new KasuSdk(sdkConfig, signerOrProvider);
28374
- return new Kasu(sdk, chainConfig, signerOrProvider, overrides);
29806
+ // An explicit history provider wins; otherwise DataService builds
29807
+ // them from `historyRpcUrls`, pinned to `chainId` (the one place).
29808
+ const sdk = new KasuSdk(sdkConfig, signerOrProvider, {
29809
+ historyProvider: options.historyProvider,
29810
+ });
29811
+ return new Kasu(sdk, chainConfig, signerOrProvider, overrides, options.historyProvider);
28375
29812
  }
28376
29813
  /**
28377
29814
  * A NEW instance bound to `signer`, with the same chain config and the same
@@ -28391,6 +29828,7 @@ class Kasu {
28391
29828
  chain: this._chainConfig,
28392
29829
  signerOrProvider: signer,
28393
29830
  configOverrides: this._configOverrides,
29831
+ historyProvider: this._historyProvider,
28394
29832
  });
28395
29833
  }
28396
29834
  /**
@@ -29170,4 +30608,4 @@ function weightedAverageApy(slices) {
29170
30608
  return weighted / totalBalance;
29171
30609
  }
29172
30610
 
29173
- export { APXIUM, AU_ALPHA3, AU_MIN_CUMULATIVE_BY_STABLE, BatchPendingError, BatchRevertedError, BatchSubmissionError, CHAIN_CONFIGS, CLEARING_WINDOW_SECONDS, CONTRACT_TTL_MS, CallsFacade, DAYS_PER_EPOCH, DAYS_PER_MONTH, DEFAULT_BATCH_POLLING_POLICY, DepositBatchMismatchError, DepositFlow, DepositsFacade, ENTRY_POINT_V07, EPOCHS_IN_YEAR, Flow, FlowStore, FlowsFacade, INVALID_ACCEPTANCE_SIGNATURE_MESSAGE, INVALID_AUTH_SIGNATURE_MESSAGE, INVOICEMATE, Kasu, KasuSdk, MAX_LENDING_AMOUNT_FALLBACK, MIN_DAYS_FOR_MONTHS, MIN_LENDING_AMOUNT_FALLBACK, MIN_TRANCHE_CAPACITY, NOT_A_WITHDRAWAL_CALL_MESSAGE, NO_DIRECTUS_URL_MESSAGE, NO_KYC_PARAMS_MESSAGE, NO_SPENDER_MESSAGE, NO_WITHDRAWAL_ENCODER_MESSAGE, PortfolioFacade, READ_ONLY_MESSAGE, RIXON_CAPITAL, SECONDS_PER_WEEK, SPONSORED_CALL_KINDS, SPONSORED_CALL_SELECTORS, SdkConfig, SponsoredCallInputError, StrategiesFacade, UPPER_MEZZANINE, WithdrawFlow, accruedInterest, apyToEpochRate, asContractType, auMinimumRemaining, auThresholdFor, availableFixedTermConfigs, batchOutcomeOf, buildContractVersionType, buildFullNameRequestMessage, buildLegacyContractRequestMessage, buildLoanAgreementSignMessage, cancelDeadline, ceilToCents, checkBatchOutcome, checkSponsoredCallOutcome, classifyWalletFailure, compareTrancheSeniority, computeSettlementWindow, countSubmissions, dailyInterest, decodeRevert, deriveCycleDates, derivePoolStatus, deriveRequestState, encodeCancelDepositCall, encodeCancelWithdrawalCall, encodeDepositData, encodeRequestWithdrawalCall, encodeStableTransfer, encodeWithdrawAtExpiryCall, epochRateToApy, explorerAddressUrl, explorerTxUrl, extractRevertData, fetchUnusedPoolIds, firstSubmissionTimestamp, floorToCents, formatSignTimestampUtc, getCreditOriginator, getInstitutionalLender, getStrategyRegion, getTrancheDisplayName, isAuMinimumExempt, isAustralianKyc, isBelowMinimumCapacity, isCycleClosed, isDefinitivelyNotSubmitted, isFixedTermConfigId, isUnpredictableGas, isUserRejected, lastEventTimestamp, lockDurationDays, lockDurationMonths, maxNetRateCeiling, netEffectiveApy, netTrancheApyBounds, nextCycleBoundary, parseFormattedMessage, parseMinorUnits, parseTrancheBound, pickDefaultTrancheId, pickHighestYieldTranche, pickShortDescription, poolAllTranchesFull, poolMaxApy, projectedAnnualEarnings, proveDepositInReceipt, proveUserOperationInReceipt, resolveBoundShortcuts, resolveDepositBounds, selectVisiblePools, submissionEvents, submitBatch, submitSponsoredCall, trancheApyBounds, trancheHasCapacity, trancheRiskRank, validateDepositBatchCalls, weeklyInterest, weightedAverageApy };
30611
+ export { APXIUM, AU_ALPHA3, AU_MIN_CUMULATIVE_BY_STABLE, BatchPendingError, BatchRevertedError, BatchSubmissionError, CHAIN_CONFIGS, CLEARING_WINDOW_SECONDS, CONTRACT_TTL_MS, CallsFacade, DAYS_PER_EPOCH, DAYS_PER_MONTH, DEFAULT_BATCH_POLLING_POLICY, DepositBatchMismatchError, DepositFlow, DepositsFacade, ENTRY_POINT_V07, EPOCHS_IN_YEAR, Flow, FlowStore, FlowsFacade, INVALID_ACCEPTANCE_SIGNATURE_MESSAGE, INVALID_AUTH_SIGNATURE_MESSAGE, INVOICEMATE, Kasu, KasuSdk, MAX_LENDING_AMOUNT_FALLBACK, MIN_DAYS_FOR_MONTHS, MIN_LENDING_AMOUNT_FALLBACK, MIN_TRANCHE_CAPACITY, NOT_A_WITHDRAWAL_CALL_MESSAGE, NO_DIRECTUS_URL_MESSAGE, NO_HISTORY_PROVIDER_MESSAGE, NO_KYC_PARAMS_MESSAGE, NO_SPENDER_MESSAGE, NO_WITHDRAWAL_ENCODER_MESSAGE, PastStateUnavailableError, PoolTvlHistoryAbortedError, PoolTvlHistoryTimeoutError, PortfolioFacade, READ_ONLY_MESSAGE, RIXON_CAPITAL, SECONDS_PER_WEEK, SPONSORED_CALL_KINDS, SPONSORED_CALL_SELECTORS, SdkConfig, SponsoredCallInputError, StrategiesFacade, UPPER_MEZZANINE, WithdrawFlow, accruedInterest, aggregateYieldHistory, apyToEpochRate, asContractType, auMinimumRemaining, auThresholdFor, availableFixedTermConfigs, batchOutcomeOf, buildContractVersionType, buildFullNameRequestMessage, buildLegacyContractRequestMessage, buildLoanAgreementSignMessage, cancelDeadline, ceilToCents, checkBatchOutcome, checkSponsoredCallOutcome, classifyWalletFailure, compareTrancheSeniority, computeSettlementWindow, countSubmissions, dailyInterest, decodeRevert, deriveCycleDates, derivePoolStatus, deriveRequestState, encodeCancelDepositCall, encodeCancelWithdrawalCall, encodeDepositData, encodeRequestWithdrawalCall, encodeStableTransfer, encodeWithdrawAtExpiryCall, epochRateToApy, epochTrancheYield, explorerAddressUrl, explorerTxUrl, extractRevertData, fetchUnusedPoolIds, firstSubmissionTimestamp, floorToCents, formatSignTimestampUtc, getCreditOriginator, getInstitutionalLender, getStrategyRegion, getTrancheDisplayName, isAuMinimumExempt, isAustralianKyc, isBelowMinimumCapacity, isCycleClosed, isDefinitivelyNotSubmitted, isFixedTermConfigId, isUnpredictableGas, isUserRejected, lastEventTimestamp, lockDurationDays, lockDurationMonths, maxNetRateCeiling, netEffectiveApy, netTrancheApyBounds, nextCycleBoundary, parseFormattedMessage, parseMinorUnits, parseTrancheBound, pickDefaultTrancheId, pickHighestYieldTranche, pickShortDescription, poolAllTranchesFull, poolMaxApy, poolTvl, projectedAnnualEarnings, proveDepositInReceipt, proveUserOperationInReceipt, resolveBoundShortcuts, resolveDepositBounds, selectVisiblePools, submissionEvents, submitBatch, submitSponsoredCall, trancheApyBounds, trancheHasCapacity, trancheRiskRank, validateDepositBatchCalls, weeklyInterest, weightedAverageApy };