@kasufinance/kasu-sdk 2.13.1 → 2.15.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 (56) hide show
  1. package/README.md +68 -5
  2. package/dist/bundle.cjs.js +1038 -29
  3. package/dist/bundle.esm.js +1026 -30
  4. package/dist/domain/index.d.ts +8 -3
  5. package/dist/domain/index.js +6 -2
  6. package/dist/domain/index.js.map +1 -1
  7. package/dist/domain/loan-contract.d.ts +16 -5
  8. package/dist/domain/loan-contract.js +16 -5
  9. package/dist/domain/loan-contract.js.map +1 -1
  10. package/dist/domain/pool-tvl.d.ts +104 -0
  11. package/dist/domain/pool-tvl.js +179 -0
  12. package/dist/domain/pool-tvl.js.map +1 -1
  13. package/dist/domain/requests.js +29 -19
  14. package/dist/domain/requests.js.map +1 -1
  15. package/dist/domain/strategy-details.d.ts +262 -0
  16. package/dist/domain/strategy-details.js +375 -0
  17. package/dist/domain/strategy-details.js.map +1 -0
  18. package/dist/facade/index.d.ts +3 -1
  19. package/dist/facade/index.js +2 -0
  20. package/dist/facade/index.js.map +1 -1
  21. package/dist/facade/strategies.d.ts +102 -4
  22. package/dist/facade/strategies.js +182 -6
  23. package/dist/facade/strategies.js.map +1 -1
  24. package/dist/facade/strategy-deployments.d.ts +65 -0
  25. package/dist/facade/strategy-deployments.js +97 -0
  26. package/dist/facade/strategy-deployments.js.map +1 -0
  27. package/dist/facade/types.d.ts +82 -0
  28. package/dist/flows/deposit-flow.d.ts +19 -5
  29. package/dist/flows/deposit-flow.js.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/services/DataService/data-service.d.ts +29 -1
  33. package/dist/services/DataService/data-service.js +80 -9
  34. package/dist/services/DataService/data-service.js.map +1 -1
  35. package/dist/services/DataService/tvl-history.d.ts +49 -0
  36. package/dist/services/DataService/tvl-history.js +89 -0
  37. package/dist/services/DataService/tvl-history.js.map +1 -1
  38. package/dist/services/UserLending/queries.js +2 -7
  39. package/dist/services/UserLending/queries.js.map +1 -1
  40. package/dist/services/UserLending/subgraph-types.js.map +1 -1
  41. package/dist/services/UserLending/types.js.map +1 -1
  42. package/dist/services/UserLending/user-lending.js +72 -16
  43. package/dist/services/UserLending/user-lending.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/domain/index.ts +34 -3
  46. package/src/domain/loan-contract.ts +16 -5
  47. package/src/domain/pool-tvl.ts +281 -0
  48. package/src/domain/strategy-details.ts +528 -0
  49. package/src/facade/index.ts +13 -0
  50. package/src/facade/strategies.ts +231 -11
  51. package/src/facade/strategy-deployments.ts +161 -0
  52. package/src/facade/types.ts +85 -0
  53. package/src/flows/deposit-flow.ts +19 -5
  54. package/src/index.ts +4 -1
  55. package/src/services/DataService/data-service.ts +103 -7
  56. package/src/services/DataService/tvl-history.ts +131 -0
@@ -15500,6 +15500,185 @@ KasuPoolExternalTVLAbi__factory.abi = _abi;
15500
15500
  function poolTvl(onChainBalance, offChainTvl) {
15501
15501
  return parseFloat(onChainBalance) + parseFloat(offChainTvl);
15502
15502
  }
15503
+ /**
15504
+ * What `combineTvlHistories` throws instead of returning a number it cannot
15505
+ * stand behind. `leg` and `entry` are the zero-based position of the
15506
+ * offending input, when there is one.
15507
+ */
15508
+ class TvlHistoryInputError extends Error {
15509
+ constructor(code, leg, entry) {
15510
+ const at = [
15511
+ leg === undefined ? '' : ` at leg ${String(leg)}`,
15512
+ entry === undefined ? '' : `, entry ${String(entry)}`,
15513
+ ].join('');
15514
+ super(`combineTvlHistories: input refused (${code})${at}`);
15515
+ this.name = 'TvlHistoryInputError';
15516
+ this.code = code;
15517
+ this.leg = leg;
15518
+ this.entry = entry;
15519
+ Object.setPrototypeOf(this, TvlHistoryInputError.prototype);
15520
+ }
15521
+ }
15522
+ function isNonNegativeFinite(value) {
15523
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
15524
+ }
15525
+ /**
15526
+ * Throws unless the leg is well formed. Index loops, not `forEach`: a hole in
15527
+ * a sparse array is skipped by the array methods and would surface later as
15528
+ * a raw `TypeError` instead of a refusal.
15529
+ */
15530
+ function validateLeg(leg, legIndex) {
15531
+ if (typeof leg !== 'object' || leg === null) {
15532
+ throw new TvlHistoryInputError('leg', legIndex);
15533
+ }
15534
+ const { entries, usdRate, opening } = leg;
15535
+ if (typeof usdRate !== 'number' ||
15536
+ !Number.isFinite(usdRate) ||
15537
+ usdRate <= 0) {
15538
+ throw new TvlHistoryInputError('usd-rate', legIndex);
15539
+ }
15540
+ if (!Array.isArray(entries)) {
15541
+ throw new TvlHistoryInputError('entries', legIndex);
15542
+ }
15543
+ const list = entries;
15544
+ if (list.length === 0) {
15545
+ throw new TvlHistoryInputError('empty-leg', legIndex);
15546
+ }
15547
+ let previous = -Infinity;
15548
+ for (let index = 0; index < list.length; index += 1) {
15549
+ const entry = list[index];
15550
+ if (typeof entry !== 'object' || entry === null) {
15551
+ throw new TvlHistoryInputError('entry', legIndex, index);
15552
+ }
15553
+ const { timestamp, tvl, kind } = entry;
15554
+ if (!isNonNegativeFinite(timestamp)) {
15555
+ throw new TvlHistoryInputError('timestamp', legIndex, index);
15556
+ }
15557
+ if (!isNonNegativeFinite(tvl)) {
15558
+ throw new TvlHistoryInputError('tvl', legIndex, index);
15559
+ }
15560
+ if (kind !== 'clearing' && kind !== 'current') {
15561
+ throw new TvlHistoryInputError('kind', legIndex, index);
15562
+ }
15563
+ if (kind === 'current' && index !== list.length - 1) {
15564
+ throw new TvlHistoryInputError('current', legIndex, index);
15565
+ }
15566
+ if (timestamp < previous) {
15567
+ throw new TvlHistoryInputError('order', legIndex, index);
15568
+ }
15569
+ previous = timestamp;
15570
+ }
15571
+ if (opening !== undefined && opening !== null) {
15572
+ const first = list[0];
15573
+ const { timestamp, tvl, kind } = (typeof opening === 'object' ? opening : {});
15574
+ if (!isNonNegativeFinite(timestamp) ||
15575
+ !isNonNegativeFinite(tvl) ||
15576
+ kind !== 'clearing' ||
15577
+ timestamp > first.timestamp) {
15578
+ throw new TvlHistoryInputError('opening', legIndex);
15579
+ }
15580
+ }
15581
+ return leg;
15582
+ }
15583
+ /**
15584
+ * A strategy's TVL in USD over time, from its deployments' histories — Tax
15585
+ * Pay runs on Base (USDC) and on XDC (AUDD), and its total lent is both.
15586
+ * Pure: the caller fetches each leg (`strategyDeployments` names them) and
15587
+ * passes the exchange rates.
15588
+ *
15589
+ * A step merge by time. At every distinct timestamp where any leg has a
15590
+ * clearing, the value is the sum over legs of that leg's latest point at or
15591
+ * before then, times its `usdRate`. A leg's `opening` — what it held just
15592
+ * before the window — is one more step: it counts from its own timestamp
15593
+ * until the leg's first entry, and the leg is 0 before it (or throughout,
15594
+ * before its first entry, without one).
15595
+ * Points that share a timestamp — clearings run in one block, or two chains
15596
+ * clearing in the same second — are one output point. The series ends with
15597
+ * one `current` point at the latest of the legs' `current` timestamps, equal
15598
+ * to the sum of their `current` values. Every leg must end in a `current`
15599
+ * point, or none may.
15600
+ *
15601
+ * The window is the union of the legs' windows: the series starts at the
15602
+ * earliest first entry of any leg. An `opening` is a value, never a point.
15603
+ * Without it, two windows cut at offset clearing times would count the
15604
+ * later-starting leg 0 at the left edge and draw a false step up; with it,
15605
+ * only a genuine late start (`opening: null`) counts 0.
15606
+ *
15607
+ * Every leg and entry is validated first: a bad input — an empty leg
15608
+ * included — throws `TvlHistoryInputError` (its `code` says why), so the
15609
+ * result never holds `NaN` or `Infinity`. The inputs are not modified.
15610
+ *
15611
+ * ```ts
15612
+ * const window = { count: 52 };
15613
+ * const points = combineTvlHistories([
15614
+ * { ...(await base.strategies.getPoolTvlHistoryWindow(basePool, window)), usdRate: 1 },
15615
+ * { ...(await xdc.strategies.getPoolTvlHistoryWindow(xdcPool, window)), usdRate: audToUsd },
15616
+ * ]);
15617
+ * ```
15618
+ */
15619
+ function combineTvlHistories(legs) {
15620
+ if (!Array.isArray(legs) || legs.length === 0) {
15621
+ throw new TvlHistoryInputError('legs');
15622
+ }
15623
+ const valid = [];
15624
+ for (let index = 0; index < legs.length; index += 1) {
15625
+ valid.push(validateLeg(legs[index], index));
15626
+ }
15627
+ const endsInCurrent = valid.map((leg) => leg.entries[leg.entries.length - 1].kind === 'current');
15628
+ const odd = endsInCurrent.findIndex((ends) => ends !== endsInCurrent[0]);
15629
+ if (odd !== -1)
15630
+ throw new TvlHistoryInputError('current', odd);
15631
+ const finalTimestamp = endsInCurrent[0]
15632
+ ? Math.max(...valid.map((leg) => leg.entries[leg.entries.length - 1].timestamp))
15633
+ : undefined;
15634
+ const clearingTimes = [
15635
+ ...new Set(valid.flatMap((leg) => leg.entries
15636
+ .filter((entry) => entry.kind === 'clearing')
15637
+ .map((entry) => entry.timestamp))),
15638
+ ]
15639
+ .filter((t) => finalTimestamp === undefined || t < finalTimestamp)
15640
+ .sort((a, b) => a - b);
15641
+ // One cursor per leg: the times ascend, so each leg is walked once. A
15642
+ // leg's `current` point is a step like any other, which keeps a chain
15643
+ // whose subgraph lags behind the other's last clearing at its own latest.
15644
+ const cursors = valid.map(() => -1);
15645
+ const sumAt = (t) => {
15646
+ let sum = 0;
15647
+ valid.forEach((leg, index) => {
15648
+ let cursor = cursors[index];
15649
+ while (cursor + 1 < leg.entries.length &&
15650
+ leg.entries[cursor + 1].timestamp <= t) {
15651
+ cursor += 1;
15652
+ }
15653
+ cursors[index] = cursor;
15654
+ // Before the first entry, the opening is the step — from its
15655
+ // own time on, never earlier.
15656
+ const opening = leg.opening;
15657
+ const value = cursor >= 0
15658
+ ? leg.entries[cursor].tvl
15659
+ : opening && opening.timestamp <= t
15660
+ ? opening.tvl
15661
+ : 0;
15662
+ sum += value * leg.usdRate;
15663
+ });
15664
+ if (!Number.isFinite(sum))
15665
+ throw new TvlHistoryInputError('overflow');
15666
+ return sum;
15667
+ };
15668
+ const combined = clearingTimes.map((t) => ({
15669
+ timestamp: t,
15670
+ tvlUsd: sumAt(t),
15671
+ kind: 'clearing',
15672
+ }));
15673
+ if (finalTimestamp !== undefined) {
15674
+ combined.push({
15675
+ timestamp: finalTimestamp,
15676
+ tvlUsd: sumAt(finalTimestamp),
15677
+ kind: 'current',
15678
+ });
15679
+ }
15680
+ return combined;
15681
+ }
15503
15682
 
15504
15683
  /**
15505
15684
  * Epochs in a year — the single source of truth for every rate conversion in
@@ -16248,6 +16427,95 @@ function fetchPoolClearings(request, poolId, scope, cancellation) {
16248
16427
  return { clearings, snapshot, requests };
16249
16428
  });
16250
16429
  }
16430
+ /**
16431
+ * Pure: the opening clearing — the newest of `before` (finished clearings the
16432
+ * window left out, older by epoch) that finished STRICTLY before the first
16433
+ * in-window clearing's block. Newest by block, then epoch, so a clearing
16434
+ * numbered out of order (XDC AUDD's epoch 1, a week before epoch 88) is
16435
+ * placed by when it happened. A clearing in the first in-window block itself
16436
+ * (a catch-up block that finished several epochs at once) is not an opening:
16437
+ * it holds the first entry's own value, not the one before it. `null` when
16438
+ * there is none — see `needsClearingBeforeBlock`.
16439
+ */
16440
+ function pickOpeningClearing(before, inWindow) {
16441
+ const firstBlock = Math.min(...inWindow.map((c) => c.block));
16442
+ let opening = null;
16443
+ for (const clearing of before) {
16444
+ if (clearing.block >= firstBlock)
16445
+ continue;
16446
+ if (opening === null ||
16447
+ clearing.block > opening.block ||
16448
+ (clearing.block === opening.block &&
16449
+ clearing.epochId > opening.epochId)) {
16450
+ opening = clearing;
16451
+ }
16452
+ }
16453
+ return opening;
16454
+ }
16455
+ /**
16456
+ * Pure: whether the opening needs `fetchClearingBefore`'s by-block read.
16457
+ * Only when the window left clearings out but every one of them finished in
16458
+ * or after the first in-window block — the extra rows sat in a catch-up
16459
+ * block — so an older one, if any, was not fetched. Otherwise the opening
16460
+ * (or its absence) is already known and nothing more is read.
16461
+ */
16462
+ function needsClearingBeforeBlock(before, opening) {
16463
+ return opening === null && before.length > 0;
16464
+ }
16465
+ /**
16466
+ * The newest finished clearing in a block below `blockLt`, by block: the
16467
+ * opening when every clearing the window left out shares its first block.
16468
+ * ONE extra request for one row, no snapshot. Clearings that share that
16469
+ * older block share its time and balance, so which one comes back does not
16470
+ * change the value.
16471
+ */
16472
+ const poolTvlClearingBeforeBlockQuery = gql `
16473
+ query PoolTvlClearingBeforeBlock($pool: String!, $blockLt: BigInt!) {
16474
+ clearings: lendingPoolClearings(
16475
+ first: 1
16476
+ orderBy: endBlockNumber
16477
+ orderDirection: desc
16478
+ where: {
16479
+ lendingPool: $pool
16480
+ isFinished: true
16481
+ endBlockNumber_lt: $blockLt
16482
+ }
16483
+ ) {
16484
+ epochId
16485
+ endBlockNumber
16486
+ endTimestamp
16487
+ }
16488
+ }
16489
+ `;
16490
+ function fetchClearingBeforeBlock(request, poolId, blockLt, cancellation) {
16491
+ return __awaiter(this, void 0, void 0, function* () {
16492
+ cancellation.throwIfCancelled();
16493
+ const page = yield request(poolTvlClearingBeforeBlockQuery, { pool: poolId, blockLt: String(blockLt) }, cancellation.signal);
16494
+ const row = page.clearings.at(0);
16495
+ return row === undefined ? null : parseClearing(row);
16496
+ });
16497
+ }
16498
+ /**
16499
+ * The newest finished clearing numbered below `beforeEpoch`: the opening
16500
+ * candidate of a `fromEpoch` window, whose own fetch never reads that far
16501
+ * back. ONE extra request for one row, no snapshot.
16502
+ */
16503
+ function fetchClearingBefore(request, poolId, beforeEpoch, cancellation) {
16504
+ return __awaiter(this, void 0, void 0, function* () {
16505
+ cancellation.throwIfCancelled();
16506
+ const page = yield request(poolTvlClearingsQuery, {
16507
+ poolId,
16508
+ pool: poolId,
16509
+ epochGte: '0',
16510
+ epochLt: String(beforeEpoch),
16511
+ direction: 'desc',
16512
+ first: 1,
16513
+ withSnapshot: false,
16514
+ }, cancellation.signal);
16515
+ const row = page.clearings.at(0);
16516
+ return row === undefined ? null : parseClearing(row);
16517
+ });
16518
+ }
16251
16519
  /**
16252
16520
  * The pool's balance (display units, the subgraph's decimal string) at the end
16253
16521
  * of each block, `TVL_HISTORY_BLOCKS_PER_REQUEST` blocks per request. A block
@@ -16651,6 +16919,42 @@ class DataService {
16651
16919
  */
16652
16920
  getPoolTvlHistory(poolId_1) {
16653
16921
  return __awaiter(this, arguments, void 0, function* (poolId, options = {}) {
16922
+ return (yield this._poolTvlHistory(poolId, options, false)).entries;
16923
+ });
16924
+ }
16925
+ /**
16926
+ * `getPoolTvlHistory` — the same options, errors and `entries` — plus
16927
+ * `opening`: the pool's value just before the window, so a strategy
16928
+ * summed across deployments (`combineTvlHistories`) knows what each leg
16929
+ * held before its first point instead of counting it 0.
16930
+ *
16931
+ * `opening` is the newest finished clearing STRICTLY before the window's
16932
+ * first block (by `pickOpeningClearing`), read at its own block exactly
16933
+ * like every other point and through the same past-block cache; `null`
16934
+ * when the pool has no clearing before the window. Extra cost over
16935
+ * `getPoolTvlHistory`:
16936
+ *
16937
+ * - `count` only: one more clearing row in the same request (`count + 1`,
16938
+ * which always reaches past the window when older history exists);
16939
+ * - `fromEpoch` above 0: one more one-row subgraph request, in parallel
16940
+ * with the clearings;
16941
+ * - only when every left-out clearing finished in the window's first
16942
+ * block (a catch-up block that closed several epochs at once): one
16943
+ * more one-row request by block (`fetchClearingBeforeBlock`), after
16944
+ * the clearings — the common case never makes it;
16945
+ * - either way, the opening's block in the same batched time-travel read
16946
+ * and one more `externalTVLOfPool` read (cached once final).
16947
+ *
16948
+ * `{ entries: [], opening: null }` wherever `getPoolTvlHistory` returns
16949
+ * `[]`.
16950
+ */
16951
+ getPoolTvlHistoryWindow(poolId_1) {
16952
+ return __awaiter(this, arguments, void 0, function* (poolId, options = {}) {
16953
+ return yield this._poolTvlHistory(poolId, options, true);
16954
+ });
16955
+ }
16956
+ _poolTvlHistory(poolId, options, withOpening) {
16957
+ return __awaiter(this, void 0, void 0, function* () {
16654
16958
  var _a;
16655
16959
  validatePoolTvlHistoryOptions(options);
16656
16960
  if (!isAddress(poolId)) {
@@ -16669,33 +16973,56 @@ class DataService {
16669
16973
  rpcs,
16670
16974
  cancellation,
16671
16975
  })
16672
- : null, externalTvl !== null && externalTvl !== void 0 ? externalTvl : '', cancellation));
16976
+ : null, externalTvl !== null && externalTvl !== void 0 ? externalTvl : '', cancellation, withOpening));
16673
16977
  });
16674
16978
  }
16675
- _readPoolTvlHistory(pool, options, reader, externalTvl, cancellation) {
16979
+ _readPoolTvlHistory(pool, options, reader, externalTvl, cancellation, withOpening) {
16676
16980
  return __awaiter(this, void 0, void 0, function* () {
16677
16981
  var _a, _b, _c;
16982
+ const nothing = { entries: [], opening: null };
16678
16983
  const graph = (document, variables, signal) => this._graph.request({ document, variables, signal });
16679
16984
  const scope = options.fromEpoch === undefined && options.count !== undefined
16680
- ? { kind: 'newest', count: options.count }
16985
+ ? {
16986
+ kind: 'newest',
16987
+ // One past the window: the opening, when there is one.
16988
+ count: options.count + (withOpening ? 1 : 0),
16989
+ }
16681
16990
  : { kind: 'from', fromEpoch: (_a = options.fromEpoch) !== null && _a !== void 0 ? _a : 0 };
16682
16991
  try {
16683
- const [currentEpochNumber, { clearings, snapshot }] = yield Promise.all([
16992
+ const [currentEpochNumber, { clearings, snapshot }, before] = yield Promise.all([
16684
16993
  this._systemVariablesAbi.currentEpochNumber(),
16685
16994
  fetchPoolClearings(graph, pool, scope, cancellation),
16995
+ withOpening && scope.kind === 'from' && scope.fromEpoch > 0
16996
+ ? fetchClearingBefore(graph, pool, scope.fromEpoch, cancellation)
16997
+ : Promise.resolve(null),
16686
16998
  ]);
16687
16999
  const currentEpoch = currentEpochNumber.toNumber();
16688
17000
  const window = resolvePoolTvlHistoryWindow(options, currentEpoch);
16689
17001
  const poolNow = snapshot.pool;
16690
17002
  if (!window || !poolNow)
16691
- return [];
17003
+ return nothing;
16692
17004
  const head = (_b = snapshot.meta) === null || _b === void 0 ? void 0 : _b.block;
16693
17005
  if (!head || typeof head.timestamp !== 'number') {
16694
17006
  throw new Error('getPoolTvlHistory: the subgraph returned no latest block time');
16695
17007
  }
16696
17008
  const inWindow = clearings.filter((c) => c.epochId >= window.fromEpoch &&
16697
17009
  c.epochId <= window.toEpoch);
16698
- const blocks = distinctBlocks(inWindow);
17010
+ // Clearings the window left out, older by epoch: the extra row of
17011
+ // a count fetch, or the one-row read below `fromEpoch`.
17012
+ const older = scope.kind === 'newest'
17013
+ ? clearings.filter((c) => c.epochId < window.fromEpoch)
17014
+ : before === null
17015
+ ? []
17016
+ : [before];
17017
+ let opening = withOpening
17018
+ ? pickOpeningClearing(older, inWindow)
17019
+ : null;
17020
+ // The left-out clearings all sat in the first in-window block (a
17021
+ // catch-up block): ask for the newest one before it, by block.
17022
+ if (withOpening && needsClearingBeforeBlock(older, opening)) {
17023
+ opening = yield fetchClearingBeforeBlock(graph, pool, Math.min(...inWindow.map((c) => c.block)), cancellation);
17024
+ }
17025
+ const blocks = distinctBlocks(opening ? [...inWindow, opening] : inWindow);
16699
17026
  const withCurrent = window.toEpoch >= currentEpoch;
16700
17027
  const cacheable = (block) => block <= head.number - TVL_HISTORY_CACHE_DEPTH;
16701
17028
  const [balances, offChainRead] = yield Promise.all([
@@ -16713,7 +17040,7 @@ class DataService {
16713
17040
  latest: null,
16714
17041
  },
16715
17042
  ]);
16716
- return buildPoolTvlHistory({
17043
+ const entries = buildPoolTvlHistory({
16717
17044
  clearings: inWindow,
16718
17045
  balances,
16719
17046
  offChain: offChainRead.offChain,
@@ -16727,10 +17054,22 @@ class DataService {
16727
17054
  : null,
16728
17055
  decimals: this._kasuConfig.stableAssetDecimals,
16729
17056
  });
17057
+ // The same function as every other point, so the opening cannot
17058
+ // be computed another way.
17059
+ const [openingEntry] = opening
17060
+ ? buildPoolTvlHistory({
17061
+ clearings: [opening],
17062
+ balances,
17063
+ offChain: offChainRead.offChain,
17064
+ current: null,
17065
+ decimals: this._kasuConfig.stableAssetDecimals,
17066
+ })
17067
+ : [null];
17068
+ return { entries, opening: openingEntry };
16730
17069
  }
16731
17070
  catch (error) {
16732
17071
  if (isEmptyHistoryError(error, this._kasuConfig.isLiteDeployment)) {
16733
- return [];
17072
+ return nothing;
16734
17073
  }
16735
17074
  throw error;
16736
17075
  }
@@ -25697,9 +26036,9 @@ class SponsoredCallInputError extends Error {
25697
26036
  Object.setPrototypeOf(this, SponsoredCallInputError.prototype);
25698
26037
  }
25699
26038
  }
25700
- const ADDRESS_RE$2 = /^0x[0-9a-fA-F]{40}$/;
26039
+ const ADDRESS_RE$3 = /^0x[0-9a-fA-F]{40}$/;
25701
26040
  function assertAddress(value, code) {
25702
- if (typeof value !== 'string' || !ADDRESS_RE$2.test(value)) {
26041
+ if (typeof value !== 'string' || !ADDRESS_RE$3.test(value)) {
25703
26042
  throw new SponsoredCallInputError(code);
25704
26043
  }
25705
26044
  }
@@ -25828,7 +26167,7 @@ function encodeStableTransfer(input) {
25828
26167
  };
25829
26168
  }
25830
26169
 
25831
- const ADDRESS_RE$1 = /^0x[0-9a-fA-F]{40}$/;
26170
+ const ADDRESS_RE$2 = /^0x[0-9a-fA-F]{40}$/;
25832
26171
  /**
25833
26172
  * Encoded, ready-to-sponsor `LendingPoolManager` calls — read, then encode,
25834
26173
  * NEVER send. Each returns `{ kind, call }`: post `kind` and `[call]` to a
@@ -25865,13 +26204,13 @@ class CallsFacade {
25865
26204
  requestWithdrawal(params) {
25866
26205
  return __awaiter(this, void 0, void 0, function* () {
25867
26206
  const { poolId, trancheId, amount, userAddress } = params;
25868
- if (typeof userAddress !== 'string' || !ADDRESS_RE$1.test(userAddress)) {
26207
+ if (typeof userAddress !== 'string' || !ADDRESS_RE$2.test(userAddress)) {
25869
26208
  throw new SponsoredCallInputError('user');
25870
26209
  }
25871
- if (typeof poolId !== 'string' || !ADDRESS_RE$1.test(poolId)) {
26210
+ if (typeof poolId !== 'string' || !ADDRESS_RE$2.test(poolId)) {
25872
26211
  throw new SponsoredCallInputError('pool');
25873
26212
  }
25874
- if (typeof trancheId !== 'string' || !ADDRESS_RE$1.test(trancheId)) {
26213
+ if (typeof trancheId !== 'string' || !ADDRESS_RE$2.test(trancheId)) {
25875
26214
  throw new SponsoredCallInputError('tranche');
25876
26215
  }
25877
26216
  let shares;
@@ -26523,7 +26862,7 @@ const BEFORE_EXECUTION = ENTRY_POINT_EVENTS.getEventTopic('BeforeExecution');
26523
26862
  const DEPOSIT_REQUESTED = PENDING_POOL_EVENTS.getEventTopic('DepositRequested');
26524
26863
  const ERC20$1 = IERC20MetadataAbi__factory.createInterface();
26525
26864
  const LENDING_POOL_MANAGER$1 = ILendingPoolManagerAbi__factory.createInterface();
26526
- const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
26865
+ const ADDRESS_RE$1 = /^0x[0-9a-fA-F]{40}$/;
26527
26866
  /**
26528
26867
  * Case-insensitive address equality; `false` for anything that is not a
26529
26868
  * 20-byte hex address. Checksums are not enforced — an all-lowercase address
@@ -26532,8 +26871,8 @@ const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
26532
26871
  function sameAddress(a, b) {
26533
26872
  return (typeof a === 'string' &&
26534
26873
  typeof b === 'string' &&
26535
- ADDRESS_RE.test(a) &&
26536
- ADDRESS_RE.test(b) &&
26874
+ ADDRESS_RE$1.test(a) &&
26875
+ ADDRESS_RE$1.test(b) &&
26537
26876
  a.toLowerCase() === b.toLowerCase());
26538
26877
  }
26539
26878
  function isHexData(value) {
@@ -26559,7 +26898,7 @@ function isCallShape(call) {
26559
26898
  if (call === null || typeof call !== 'object')
26560
26899
  return false;
26561
26900
  const { to, data } = call;
26562
- return typeof to === 'string' && ADDRESS_RE.test(to) && isHexData(data);
26901
+ return typeof to === 'string' && ADDRESS_RE$1.test(to) && isHexData(data);
26563
26902
  }
26564
26903
  function isZeroValue(value) {
26565
26904
  if (value === undefined)
@@ -27537,19 +27876,30 @@ function buildLoanAgreementSignMessage(p) {
27537
27876
  ].join('\n');
27538
27877
  }
27539
27878
  /**
27540
- * The legacy `/contract/generate` and `/contract/resolve` message.
27879
+ * The legacy `/contract/generate` message.
27541
27880
  *
27542
27881
  * ⚠️ BYTE-EXACT PROTOCOL STRING. kasu-backend rebuilds it as
27543
27882
  * `` `I request contract content for ${address} at ${timestamp}.` `` from the
27544
27883
  * `address` and `timestamp` fields of the request body — so the string signed
27545
27884
  * and the body sent must agree exactly, INCLUDING the address casing. This
27546
27885
  * builder lowercases, and the request body must carry the same lowercased
27547
- * address; that is what both apps signing this format do today.
27886
+ * address.
27548
27887
  *
27549
27888
  * The backend takes this path whenever the four human-readable display fields
27550
- * are absent, and documents it as permanent until the legacy app is
27551
- * decommissioned. `/contract/resolve` has no other format — every consumer
27552
- * signs this one to retrieve an existing agreement.
27889
+ * are absent. Only the retired kasu-fe-next app (retired 2026-11-05) and
27890
+ * kasu-mobile builds from before 2026-09-06 signed it; every current consumer
27891
+ * signs `buildLoanAgreementSignMessage`. The backend keeps accepting it until
27892
+ * no such mobile build is left in use.
27893
+ *
27894
+ * It is NOT the `/contract/resolve` message. Resolve has a message of its own
27895
+ * (`Requesting contract content for {address}/{id} at {timestamp}.`), which
27896
+ * kasu-backend rebuilds and verifies, so it does not depend on this format.
27897
+ *
27898
+ * @deprecated Sign the human-readable loan-agreement message
27899
+ * (`buildLoanAgreementSignMessage`, `contractMessage.format:
27900
+ * 'loan-agreement'`) instead. Only the retired kasu-fe-next app and
27901
+ * pre-2026-09-06 kasu-mobile builds used this format; it will be removed in
27902
+ * the next major version.
27553
27903
  *
27554
27904
  * @param timestampMs ms-epoch, and the same value sent as the body's
27555
27905
  * `timestamp`.
@@ -28968,6 +29318,381 @@ function maxNetRateCeiling(pools, feePercent) {
28968
29318
  return rate;
28969
29319
  }
28970
29320
 
29321
+ /**
29322
+ * The CMS-authored figures and text a strategy's Details screen shows, read
29323
+ * the way kasu-ui's Details tab reads them, so every app shows the same
29324
+ * thing.
29325
+ *
29326
+ * Lifted from kasu-ui (`lib/details-fields.ts`, `lib/directus-text.ts` and
29327
+ * the parsers inside `deposit-dialog/details-tab.tsx`). The rules are facts
29328
+ * about how the CMS is authored, not presentation:
29329
+ *
29330
+ * - EMPTY IS UNAUTHORED. The CMS stores an emptied field as `''`, and its
29331
+ * "numeric" fields arrive as a number, a numeric string, `''` or `null`.
29332
+ * Anything that is not real data reads as `undefined` — never a `0`, a
29333
+ * placeholder or a sentence of the SDK's own. A field the caller cannot
29334
+ * read is a row the caller omits.
29335
+ * - A START DATE MUST DATE. A value that is not an ISO date, or a real
29336
+ * calendar date, or is before 2000 or in the future, is refused: rendered
29337
+ * as elapsed time it would claim a history nobody authored.
29338
+ * - SOME FIELDS CARRY TWO LINES IN ONE STRING. Business Types and Facility
29339
+ * Type are a primary value and a qualifying clause; risk-item
29340
+ * descriptions can be numbered enumerations. Splitting them is the same
29341
+ * everywhere, so it lives here.
29342
+ *
29343
+ * Like `pickShortDescription`, this authors no string of its own: it selects,
29344
+ * splits and trims text the CMS already holds. None of it is sanitised —
29345
+ * render every string as text, never as HTML or markdown.
29346
+ *
29347
+ * Pure. No clock: the date reader takes `now` from its caller.
29348
+ */
29349
+ // ---------------------------------------------------------------------------
29350
+ // Start dates
29351
+ // ---------------------------------------------------------------------------
29352
+ /**
29353
+ * `YYYY-MM-DD`, optionally followed by a time part — the two shapes a CMS
29354
+ * date column serialises to. Checked BEFORE any parse: `new Date('Jun 2024')`
29355
+ * is a valid date in V8, so an editorial string would otherwise become a
29356
+ * confident duration.
29357
+ */
29358
+ const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})(?:[T ].*)?$/;
29359
+ /** Nothing on Kasu has a lending history from before this year. */
29360
+ const EARLIEST_YEAR = 2000;
29361
+ /**
29362
+ * A CMS start date as its `YYYY-MM-DD` date head, or `undefined`.
29363
+ *
29364
+ * Accepts an ISO date, optionally with a time part (only the date head is
29365
+ * kept — a time changes nothing at year/month granularity, and converting it
29366
+ * through a timezone could move it a day). Refused, as `undefined`:
29367
+ *
29368
+ * - anything that is not a string (or, with `unixSeconds`, a number);
29369
+ * - an empty or blank string (unauthored);
29370
+ * - a string that is not ISO-shaped (`'Jun 2024'`);
29371
+ * - a date that does not exist (`'2024-02-30'`, `'2024-13-01'`) — never
29372
+ * rolled over into the next month;
29373
+ * - a year before 2000;
29374
+ * - a date after `now` (a typo such as 2062 for 2026 — no history has
29375
+ * begun in the future).
29376
+ *
29377
+ * `now` is the caller's clock; the facade passes the current time.
29378
+ */
29379
+ function readCmsStartDate(value, now, options = {}) {
29380
+ let head;
29381
+ if (typeof value === 'number' && options.unixSeconds) {
29382
+ // `0` is formatDuration's "unset" sentinel; negatives are pre-1970.
29383
+ if (!Number.isFinite(value) || value <= 0)
29384
+ return undefined;
29385
+ const date = new Date(value * 1000);
29386
+ if (Number.isNaN(date.getTime()))
29387
+ return undefined;
29388
+ head = date.toISOString().slice(0, 10);
29389
+ }
29390
+ else if (typeof value === 'string') {
29391
+ const trimmed = value.trim();
29392
+ if (!ISO_DATE.test(trimmed))
29393
+ return undefined;
29394
+ head = trimmed.slice(0, 10);
29395
+ }
29396
+ else {
29397
+ return undefined;
29398
+ }
29399
+ const match = ISO_DATE.exec(head);
29400
+ if (!match)
29401
+ return undefined;
29402
+ const year = Number(match[1]);
29403
+ const month = Number(match[2]);
29404
+ const day = Number(match[3]);
29405
+ // Round-trip through UTC: a day or month out of range comes back as a
29406
+ // different date, which is how a non-existent date is caught.
29407
+ const utc = new Date(Date.UTC(year, month - 1, day));
29408
+ if (utc.getUTCFullYear() !== year ||
29409
+ utc.getUTCMonth() !== month - 1 ||
29410
+ utc.getUTCDate() !== day) {
29411
+ return undefined;
29412
+ }
29413
+ if (year < EARLIEST_YEAR)
29414
+ return undefined;
29415
+ if (utc.getTime() > now.getTime())
29416
+ return undefined;
29417
+ return head;
29418
+ }
29419
+ /**
29420
+ * Business Types (the CMS `industryExposure`), split into the value and its
29421
+ * supporting clause on the FIRST newline, else the FIRST parenthesised
29422
+ * segment (a trailing `)` dropped):
29423
+ *
29424
+ * `'SMEs & Large Businesses (diversified industries – exclusions apply)'`
29425
+ * → `{ primary: 'SMEs & Large Businesses', detail: 'diversified industries – exclusions apply' }`
29426
+ *
29427
+ * A value with neither delimiter is `{ primary }` alone. `undefined` for a
29428
+ * missing, non-string, empty or blank value.
29429
+ *
29430
+ * REFINED over kasu-ui: a value that OPENS with `(` would split to an empty
29431
+ * primary there; here the whole value is the primary instead, so `primary`
29432
+ * is never empty. No live record has that shape.
29433
+ */
29434
+ function splitBusinessTypes(raw) {
29435
+ const value = typeof raw === 'string' ? raw.trim() : '';
29436
+ if (!value)
29437
+ return undefined;
29438
+ let primary = value;
29439
+ let detail = '';
29440
+ const newlineAt = value.indexOf('\n');
29441
+ if (newlineAt !== -1) {
29442
+ primary = value.slice(0, newlineAt).trim();
29443
+ detail = value.slice(newlineAt + 1).trim();
29444
+ }
29445
+ else {
29446
+ const parenAt = value.indexOf('(');
29447
+ if (parenAt !== -1) {
29448
+ primary = value.slice(0, parenAt).trim();
29449
+ detail = value.slice(parenAt + 1).trim();
29450
+ if (detail.endsWith(')'))
29451
+ detail = detail.slice(0, -1).trim();
29452
+ }
29453
+ }
29454
+ if (!primary)
29455
+ return { primary: value };
29456
+ return detail ? { primary, detail } : { primary };
29457
+ }
29458
+ /**
29459
+ * The start of the label, or a character that is neither a word character
29460
+ * nor a hyphen, before `revolv` — not `\b`, which treats a hyphen as a
29461
+ * boundary, so "Non-Revolving" would match. kasu-ui writes the same rule as
29462
+ * the lookbehind `(?<![\w-])`; this form needs no lookbehind support from the
29463
+ * JavaScript engine.
29464
+ */
29465
+ const REVOLVING_FACILITY = /(?:^|[^\w-])revolv\w*\b/i;
29466
+ /**
29467
+ * Facility Type (the CMS `loanStructure`): the label on the FIRST line, and
29468
+ * everything after the first newline as its clause, so a clause with line
29469
+ * breaks of its own stays whole.
29470
+ *
29471
+ * `'Revolving Instalment Facility\n12-24 months instalment loan, subject to annual review'`
29472
+ * → `{ label: 'Revolving Instalment Facility', detail: '12-24 months …', revolving: true }`
29473
+ *
29474
+ * `undefined` for a missing, non-string, empty or blank value. The value is
29475
+ * trimmed first, so a leading blank line still yields a label.
29476
+ */
29477
+ function parseFacility(raw) {
29478
+ const value = typeof raw === 'string' ? raw.trim() : '';
29479
+ if (!value)
29480
+ return undefined;
29481
+ const breakAt = value.indexOf('\n');
29482
+ const label = (breakAt === -1 ? value : value.slice(0, breakAt)).trim();
29483
+ const detail = breakAt === -1 ? '' : value.slice(breakAt + 1).trim();
29484
+ const revolving = REVOLVING_FACILITY.test(label);
29485
+ return detail ? { label, detail, revolving } : { label, revolving };
29486
+ }
29487
+ // ---------------------------------------------------------------------------
29488
+ // Risk Management
29489
+ // ---------------------------------------------------------------------------
29490
+ /**
29491
+ * The order the Risk Management groups read in, by group name (compared
29492
+ * trimmed, case-insensitively). The CMS returns items in authoring order,
29493
+ * which is not this. A group not listed still appears — after these, in CMS
29494
+ * order.
29495
+ */
29496
+ const RISK_GROUP_ORDER = Object.freeze([
29497
+ 'Security & Recourse',
29498
+ 'Structural Controls',
29499
+ 'Financial Covenants',
29500
+ 'Reporting Covenants',
29501
+ 'Key Eligibility Criteria',
29502
+ ]);
29503
+ /** Zero-width paste artifacts, which `\s` does not match. */
29504
+ const ZERO_WIDTH = /[​-‍]/g;
29505
+ /** An `N)` marker at the start, or after a `;` / `; and,` separator. */
29506
+ const HAS_ENUMERATION = /(?:^\d+\)|;\s*(?:and,?\s*)?\d+\))/;
29507
+ const ENUMERATION_SPLIT = /(?:^|\s*;\s*(?:and,?\s*)?)\d+\)\s*/;
29508
+ /** kasu-ui's stub for an unauthored description; never a clause. */
29509
+ const EMPTY_DESCRIPTION = '—';
29510
+ /**
29511
+ * A risk-item description as the lines beneath the item's name.
29512
+ *
29513
+ * A numbered enumeration (`'1) Foo; and, 2) Bar'`, or one that omits its
29514
+ * leading `1)`: `'Foo; 2) Bar'`) is one line per clause; anything else is
29515
+ * one line. Zero-width characters are stripped first. An unauthored
29516
+ * description is NO lines (`[]`), never a dash.
29517
+ */
29518
+ function splitNumberedClauses(description) {
29519
+ const raw = cleanDescription(description);
29520
+ if (!raw)
29521
+ return [];
29522
+ const clauses = HAS_ENUMERATION.test(raw)
29523
+ ? raw
29524
+ .split(ENUMERATION_SPLIT)
29525
+ .map((part) => part.trim())
29526
+ .filter((part) => part.length > 0)
29527
+ : [raw];
29528
+ const lines = clauses.length > 0 ? clauses : [raw];
29529
+ return lines.filter((line) => line !== EMPTY_DESCRIPTION);
29530
+ }
29531
+ /** A description with zero-width characters stripped, trimmed; `''` when not a string. */
29532
+ function cleanDescription(description) {
29533
+ return typeof description === 'string'
29534
+ ? description.replace(ZERO_WIDTH, '').trim()
29535
+ : '';
29536
+ }
29537
+ /**
29538
+ * Risk-management items grouped by their group, the groups in
29539
+ * `RISK_GROUP_ORDER` and, within a group, the items in CMS order.
29540
+ *
29541
+ * An item with no name, or no group, is left out: there is nothing to head
29542
+ * its lines with, or no group to place it in. (The CMS joins items to groups
29543
+ * by name, so neither occurs in live data.) No group is ever empty, so an
29544
+ * item list with nothing usable gives `[]`.
29545
+ */
29546
+ function groupRiskManagementItems(items) {
29547
+ const groups = new Map();
29548
+ for (const source of items) {
29549
+ const name = trimmedOrUndefined(source.name);
29550
+ const group = trimmedOrUndefined(source.group);
29551
+ if (!name || !group)
29552
+ continue;
29553
+ const clauses = splitNumberedClauses(source.description);
29554
+ // The same cleaned text the clauses come from, and only when they
29555
+ // say something: a description of "—" or of zero-width characters
29556
+ // alone is unauthored, so it is absent here too, never a stray dash.
29557
+ const description = clauses.length > 0 ? cleanDescription(source.description) : '';
29558
+ const tooltip = trimmedOrUndefined(source.tooltip);
29559
+ const item = Object.assign(Object.assign(Object.assign({ name }, (description ? { description } : {})), { clauses }), (tooltip ? { tooltip } : {}));
29560
+ const existing = groups.get(group);
29561
+ if (existing)
29562
+ existing.items.push(item);
29563
+ else
29564
+ groups.set(group, { group, items: [item] });
29565
+ }
29566
+ // Array.prototype.sort is stable, so unlisted groups keep CMS order.
29567
+ return [...groups.values()].sort((a, b) => riskGroupRank(a.group) - riskGroupRank(b.group));
29568
+ }
29569
+ const RISK_GROUP_RANK = new Map(RISK_GROUP_ORDER.map((group, index) => [group.toLowerCase(), index]));
29570
+ function riskGroupRank(group) {
29571
+ var _a;
29572
+ return ((_a = RISK_GROUP_RANK.get(group.trim().toLowerCase())) !== null && _a !== void 0 ? _a : RISK_GROUP_ORDER.length);
29573
+ }
29574
+ /**
29575
+ * The pool's security tags (the CMS `security` list), trimmed, blanks
29576
+ * dropped. `undefined` when none remain.
29577
+ *
29578
+ * kasu-ui shows these under "Security Structure over End Borrowers" ONLY
29579
+ * when the pool has no risk-management items.
29580
+ */
29581
+ function securityStructureOf(raw) {
29582
+ if (!Array.isArray(raw))
29583
+ return undefined;
29584
+ const tags = raw
29585
+ .map((tag) => trimmedOrUndefined(tag))
29586
+ .filter((tag) => tag !== undefined);
29587
+ return tags.length > 0 ? tags : undefined;
29588
+ }
29589
+ // ---------------------------------------------------------------------------
29590
+ // Numbers
29591
+ // ---------------------------------------------------------------------------
29592
+ /**
29593
+ * A CMS or subgraph "numeric" value as a finite number, or `undefined`. An
29594
+ * empty or blank string is UNAUTHORED, not zero; a genuine `0` survives.
29595
+ */
29596
+ function finiteOrUndefined(value) {
29597
+ if (typeof value === 'number') {
29598
+ return Number.isFinite(value) ? value : undefined;
29599
+ }
29600
+ if (typeof value === 'string') {
29601
+ if (value.trim() === '')
29602
+ return undefined;
29603
+ const numeric = Number(value);
29604
+ return Number.isFinite(numeric) ? numeric : undefined;
29605
+ }
29606
+ return undefined;
29607
+ }
29608
+ /**
29609
+ * A leg `strategyTotalsUsd` cannot place: two legs for one deployment
29610
+ * (`duplicate-leg`, which would double-count it), or a leg for a deployment
29611
+ * not in `expectedDeployments` (`unexpected-leg`, a pool of some other
29612
+ * strategy). Thrown, because both are caller bugs, not data states.
29613
+ */
29614
+ class StrategyTotalsInputError extends Error {
29615
+ constructor(code, deployment) {
29616
+ super(code === 'duplicate-leg'
29617
+ ? `strategyTotalsUsd: two legs for deployment "${deployment}"`
29618
+ : `strategyTotalsUsd: leg for deployment "${deployment}" is not in expectedDeployments`);
29619
+ this.code = code;
29620
+ this.deployment = deployment;
29621
+ this.name = 'StrategyTotalsInputError';
29622
+ }
29623
+ }
29624
+ /**
29625
+ * Total Active Lending and Lender Interest Earned for a strategy across the
29626
+ * deployments it lends on, in USD: each leg's native amount times that leg's
29627
+ * own `usdRate`, summed. Tax Pay lends on Base (USDC) and XDC (AUDD);
29628
+ * 2,567,901 AUDD is not US$2,567,901.
29629
+ *
29630
+ * `expectedDeployments` is every deployment the strategy has a pool on —
29631
+ * `strategyDeployments(poolId, chain).map((d) => d.chain)` — and `legs` the
29632
+ * ones whose pool the caller actually loaded. What is guaranteed:
29633
+ *
29634
+ * - **`null` — WITHHELD** when there are no legs, or ANY leg's rate is
29635
+ * missing or unusable (not a finite number above 0). The total is never
29636
+ * built on a guessed rate, and never silently drops a loaded leg; the
29637
+ * caller shows the held pool's own figure, or nothing.
29638
+ * - **Otherwise the sum over `legs`**, with `isPartial` /
29639
+ * `missingDeployments` stating exactly which expected deployments it
29640
+ * does NOT cover. `isPartial: false` is the only case in which the
29641
+ * figures are the whole strategy.
29642
+ * - An amount that is absent or malformed contributes nothing (a real `0`
29643
+ * is still `0`).
29644
+ * - Throws `StrategyTotalsInputError` for a duplicated leg or one not in
29645
+ * `expectedDeployments`, before summing anything.
29646
+ *
29647
+ * Only these two figures are summed. The CMS-authored ones — every
29648
+ * originator figure, `operatingSince`, the loss rates — are shared by a
29649
+ * strategy's pools and already strategy-wide: summing them double-counts.
29650
+ *
29651
+ * Mirrors the money half of kasu-ui's `aggregateStrategyPools` and its
29652
+ * withhold gate.
29653
+ */
29654
+ function strategyTotalsUsd(legs, expectedDeployments) {
29655
+ var _a, _b;
29656
+ const expected = new Set(expectedDeployments);
29657
+ const seen = new Set();
29658
+ for (const leg of legs) {
29659
+ if (seen.has(leg.deployment)) {
29660
+ throw new StrategyTotalsInputError('duplicate-leg', leg.deployment);
29661
+ }
29662
+ if (!expected.has(leg.deployment)) {
29663
+ throw new StrategyTotalsInputError('unexpected-leg', leg.deployment);
29664
+ }
29665
+ seen.add(leg.deployment);
29666
+ }
29667
+ if (legs.length === 0)
29668
+ return null;
29669
+ let totalActiveLendingUsd = 0;
29670
+ let lenderInterestEarnedUsd = 0;
29671
+ for (const leg of legs) {
29672
+ const rate = leg.usdRate;
29673
+ if (typeof rate !== 'number' || !Number.isFinite(rate) || rate <= 0) {
29674
+ return null;
29675
+ }
29676
+ totalActiveLendingUsd +=
29677
+ ((_a = finiteOrUndefined(leg.totalActiveLending)) !== null && _a !== void 0 ? _a : 0) * rate;
29678
+ lenderInterestEarnedUsd +=
29679
+ ((_b = finiteOrUndefined(leg.lenderInterestEarned)) !== null && _b !== void 0 ? _b : 0) * rate;
29680
+ }
29681
+ const missingDeployments = [...expected].filter((d) => !seen.has(d));
29682
+ return {
29683
+ totalActiveLendingUsd,
29684
+ lenderInterestEarnedUsd,
29685
+ isPartial: missingDeployments.length > 0,
29686
+ missingDeployments,
29687
+ };
29688
+ }
29689
+ function trimmedOrUndefined(value) {
29690
+ if (typeof value !== 'string')
29691
+ return undefined;
29692
+ const trimmed = value.trim();
29693
+ return trimmed.length > 0 ? trimmed : undefined;
29694
+ }
29695
+
28971
29696
  /**
28972
29697
  * A strategy's SHORT blurb, as plain text: the CMS `liteDescription` first,
28973
29698
  * else the first paragraph of the long `description`, else nothing.
@@ -29055,10 +29780,55 @@ class StrategiesFacade {
29055
29780
  * Chain pool id → the pool id its CMS record is keyed by (XDC pools
29056
29781
  * share their Base counterpart's editorial record). Lowercase keys.
29057
29782
  */
29058
- _poolMetadataMapping = {}) {
29783
+ _poolMetadataMapping = {},
29784
+ /**
29785
+ * The clock the CMS start dates are checked against (a date after it
29786
+ * is refused). Injected for tests; the current time otherwise.
29787
+ */
29788
+ _now = () => new Date(),
29789
+ /**
29790
+ * Milliseconds on a clock that only moves forward, for the shared
29791
+ * CMS reads' 30-second lifetime — separate from `_now`, so a pinned
29792
+ * date clock cannot keep a read alive for ever. Injected for tests;
29793
+ * `Date.now` otherwise.
29794
+ */
29795
+ _memoClockMs = () => Date.now()) {
29059
29796
  this._dataService = _dataService;
29060
29797
  this._userLending = _userLending;
29061
29798
  this._poolMetadataMapping = _poolMetadataMapping;
29799
+ this._now = _now;
29800
+ this._memoClockMs = _memoClockMs;
29801
+ /**
29802
+ * Short-lived, per-instance memo of the two CMS-wide reads the pool
29803
+ * getters share, so a Details screen asking for the loss rate, the risk
29804
+ * groups and the originator together reads each collection once. Keyed
29805
+ * by read; holds the in-flight promise, so concurrent callers share it.
29806
+ * A REJECTED read is dropped at once, never served again — the next call
29807
+ * retries.
29808
+ */
29809
+ this._cmsReads = new Map();
29810
+ }
29811
+ _memoCmsRead(key, read) {
29812
+ const nowMs = this._memoClockMs();
29813
+ const cached = this._cmsReads.get(key);
29814
+ if (cached && cached.expiresAt > nowMs) {
29815
+ return cached.promise;
29816
+ }
29817
+ const promise = read();
29818
+ const entry = { promise, expiresAt: nowMs + CMS_READ_TTL_MS };
29819
+ this._cmsReads.set(key, entry);
29820
+ promise.catch(() => {
29821
+ // Only our own entry: a newer read may have replaced it.
29822
+ if (this._cmsReads.get(key) === entry)
29823
+ this._cmsReads.delete(key);
29824
+ });
29825
+ return promise;
29826
+ }
29827
+ _riskManagementRecords() {
29828
+ return this._memoCmsRead('riskManagement', () => this._dataService.getRiskManagement());
29829
+ }
29830
+ _originatorProfiles() {
29831
+ return this._memoCmsRead('originatorProfiles', () => this._dataService.getPoolDelegateProfileAndHistory());
29062
29832
  }
29063
29833
  /**
29064
29834
  * The pool's loss rate and first-loss capital, from its CMS
@@ -29071,12 +29841,14 @@ class StrategiesFacade {
29071
29841
  * 0..1 fraction.
29072
29842
  *
29073
29843
  * Requires `directusUrl`; without it the call rejects with
29074
- * `NO_DIRECTUS_URL_MESSAGE`, like every CMS-only read.
29844
+ * `NO_DIRECTUS_URL_MESSAGE`, like every CMS-only read. Shares its CMS
29845
+ * read with `getRiskManagementGroups` for 30 seconds per instance; a
29846
+ * failed read is never reused.
29075
29847
  */
29076
29848
  getRiskPerformance(poolId) {
29077
29849
  return __awaiter(this, void 0, void 0, function* () {
29078
29850
  const metadataId = this._metadataIdOf(poolId);
29079
- const records = yield this._dataService.getRiskManagement();
29851
+ const records = yield this._riskManagementRecords();
29080
29852
  const record = records.find((r) => String(r.poolIdFK).toLowerCase() === metadataId);
29081
29853
  if (!record)
29082
29854
  return null;
@@ -29100,16 +29872,95 @@ class StrategiesFacade {
29100
29872
  * deployment every profile lists no pools and this resolves `null` —
29101
29873
  * the same answer kasu-ui's own lookup gives there today. The metadata
29102
29874
  * candidate becomes live once the service resolves the mapping.
29875
+ *
29876
+ * Shares its reads with `getOriginatorHistory` for 30 seconds per
29877
+ * instance; a failed read is never reused. Each call returns its own
29878
+ * copy of the profile, so mutating it changes nothing shared.
29103
29879
  */
29104
29880
  getOriginator(poolId) {
29105
29881
  return __awaiter(this, void 0, void 0, function* () {
29106
- var _a;
29107
29882
  const candidates = new Set([
29108
29883
  poolId.toLowerCase(),
29109
29884
  this._metadataIdOf(poolId),
29110
29885
  ]);
29111
- const profiles = yield this._dataService.getPoolDelegateProfileAndHistory();
29112
- return ((_a = profiles.find((profile) => profile.otherKASUPools.some((pool) => candidates.has(String(pool.id).toLowerCase())))) !== null && _a !== void 0 ? _a : null);
29886
+ const profiles = yield this._originatorProfiles();
29887
+ const profile = profiles.find((candidate) => candidate.otherKASUPools.some((pool) => candidates.has(String(pool.id).toLowerCase())));
29888
+ // A copy: the profiles are shared between calls (see
29889
+ // `_memoCmsRead`), so a caller sorting or editing its result must
29890
+ // not change what the next call returns.
29891
+ return profile
29892
+ ? Object.assign(Object.assign({}, profile), { otherKASUPools: profile.otherKASUPools.map((pool) => (Object.assign({}, pool))) }) : null;
29893
+ });
29894
+ }
29895
+ /**
29896
+ * The credit originator's ENTIRE lending history for a pool — since when,
29897
+ * total originated, loans outstanding, loss rate — read from the profile
29898
+ * `getOriginator` finds, each figure validated. `null` when no profile
29899
+ * lists the pool, or the profile authors none of the four.
29900
+ *
29901
+ * kasu-ui's Details tab shows these as its Credit Originator strip, and
29902
+ * hides the strip when there is no profile. The money figures are
29903
+ * editorial USD on every deployment: never FX-convert them. An XDC pool
29904
+ * resolves `null` today, as it does in kasu-ui (see `getOriginator`).
29905
+ *
29906
+ * Requires `directusUrl`, like every CMS-only read; rejects when the
29907
+ * read fails. Shares its reads with `getOriginator` for 30 seconds per
29908
+ * instance; a failed read is never reused.
29909
+ */
29910
+ getOriginatorHistory(poolId) {
29911
+ return __awaiter(this, void 0, void 0, function* () {
29912
+ const profile = yield this.getOriginator(poolId);
29913
+ if (!profile)
29914
+ return null;
29915
+ const history = {};
29916
+ const since = readCmsStartDate(profile.delegateLendingHistory, this._now(), { unixSeconds: true });
29917
+ if (since !== undefined)
29918
+ history.since = since;
29919
+ const totalOriginated = finiteOrUndefined(profile.totalLoanFundsOriginated);
29920
+ if (totalOriginated !== undefined) {
29921
+ history.totalOriginated = totalOriginated;
29922
+ }
29923
+ const loansOutstanding = finiteOrUndefined(profile.loansUnderManagement);
29924
+ if (loansOutstanding !== undefined) {
29925
+ history.loansOutstanding = loansOutstanding;
29926
+ }
29927
+ const lossRate = finiteOrUndefined(profile.historicLossRate);
29928
+ if (lossRate !== undefined)
29929
+ history.lossRate = lossRate;
29930
+ return Object.keys(history).length > 0 ? history : null;
29931
+ });
29932
+ }
29933
+ /**
29934
+ * The pool's Risk Management groups, from its CMS risk-management record:
29935
+ * each group with its items (name, the pool's description of it split
29936
+ * into lines, the shared tooltip), the groups in `RISK_GROUP_ORDER`
29937
+ * (Security & Recourse, Structural Controls, Financial Covenants,
29938
+ * Reporting Covenants, Key Eligibility Criteria, then any other in CMS
29939
+ * order). See `groupRiskManagementItems`.
29940
+ *
29941
+ * `null` when the pool has no record, or the record has no usable item —
29942
+ * then kasu-ui falls back to `Strategy.securityStructure`. XDC pools
29943
+ * resolve through the metadata mapping to their Base record, like
29944
+ * `getRiskPerformance`.
29945
+ *
29946
+ * REJECTS when the CMS read fails (an outage, or no `directusUrl` —
29947
+ * `NO_DIRECTUS_URL_MESSAGE`), so a failure stays visible rather than
29948
+ * reading as "no items". For what to SHOW, treat a rejection like `null`:
29949
+ * fall back to `Strategy.securityStructure`, which arrives with the
29950
+ * strategy and needs no second read.
29951
+ *
29952
+ * Shares its CMS read with `getRiskPerformance` for 30 seconds per
29953
+ * instance; a failed read is never reused.
29954
+ */
29955
+ getRiskManagementGroups(poolId) {
29956
+ return __awaiter(this, void 0, void 0, function* () {
29957
+ const metadataId = this._metadataIdOf(poolId);
29958
+ const records = yield this._riskManagementRecords();
29959
+ const record = records.find((r) => String(r.poolIdFK).toLowerCase() === metadataId);
29960
+ if (!record || !Array.isArray(record.items))
29961
+ return null;
29962
+ const groups = groupRiskManagementItems(record.items);
29963
+ return groups.length > 0 ? groups : null;
29113
29964
  });
29114
29965
  }
29115
29966
  _metadataIdOf(poolId) {
@@ -29188,6 +30039,26 @@ class StrategiesFacade {
29188
30039
  return yield this._dataService.getPoolTvlHistory(poolId, options);
29189
30040
  });
29190
30041
  }
30042
+ /**
30043
+ * `getPoolTvlHistory` plus `opening` — the newest clearing before the
30044
+ * window, read at its own block like every other point, or `null` when
30045
+ * the pool has none — from the same read. Pass the result to
30046
+ * `combineTvlHistories` as a leg with its `usdRate`: the opening is what
30047
+ * the leg held before its first point, so a window cut at a different
30048
+ * time on another chain does not count it 0. Same options and errors as
30049
+ * `getPoolTvlHistory`; the extra cost is one clearing row (or, for
30050
+ * `fromEpoch`, one small request) and one block.
30051
+ *
30052
+ * ```ts
30053
+ * const leg = await kasu.strategies.getPoolTvlHistoryWindow(poolId, { count: 52 });
30054
+ * // { entries: PoolTvlHistoryEntry[], opening: PoolTvlHistoryEntry | null }
30055
+ * ```
30056
+ */
30057
+ getPoolTvlHistoryWindow(poolId_1) {
30058
+ return __awaiter(this, arguments, void 0, function* (poolId, options = {}) {
30059
+ return yield this._dataService.getPoolTvlHistoryWindow(poolId, options);
30060
+ });
30061
+ }
29191
30062
  /**
29192
30063
  * Fetch a single strategy by pool ID. Returns `null` if not found.
29193
30064
  */
@@ -29263,10 +30134,39 @@ class StrategiesFacade {
29263
30134
  apyStructure: pool.poolApyStructure,
29264
30135
  thumbnailUrl: pool.thumbnailImageUrl,
29265
30136
  bannerUrl: pool.bannerImageUrl,
30137
+ strategyHistory: strategyHistoryOf(pool, this._now()),
30138
+ endBorrowers: endBorrowersOf(pool),
30139
+ securityStructure: securityStructureOf(pool.security),
29266
30140
  _raw: pool,
29267
30141
  };
29268
30142
  }
29269
30143
  }
30144
+ /**
30145
+ * How long a CMS-wide read is shared between the pool getters. Long enough
30146
+ * for one screen's worth of calls, short enough that a CMS edit shows on the
30147
+ * next visit; apps cache further on their own terms.
30148
+ */
30149
+ const CMS_READ_TTL_MS = 30000;
30150
+ /** `Strategy.strategyHistory`: an absent figure is an absent key. */
30151
+ function strategyHistoryOf(pool, now) {
30152
+ const history = {};
30153
+ const since = readCmsStartDate(pool.operatingSince, now);
30154
+ if (since !== undefined)
30155
+ history.since = since;
30156
+ const lenderInterestEarned = finiteOrUndefined(pool.yieldEarned);
30157
+ if (lenderInterestEarned !== undefined) {
30158
+ history.lenderInterestEarned = lenderInterestEarned;
30159
+ }
30160
+ return history;
30161
+ }
30162
+ /** Business Types and Facility Type, or `undefined` when neither is authored. */
30163
+ function endBorrowersOf(pool) {
30164
+ const businessTypes = splitBusinessTypes(pool.industryExposure);
30165
+ const facility = parseFacility(pool.loanStructure);
30166
+ if (!businessTypes && !facility)
30167
+ return undefined;
30168
+ return Object.assign(Object.assign({}, (businessTypes ? { businessTypes } : {})), (facility ? { facility } : {}));
30169
+ }
29270
30170
  /**
29271
30171
  * A CMS text field as a string, or `undefined` when it is absent, not a
29272
30172
  * string, or empty (blank included) — the CMS stores an emptied field as `''`, and an empty
@@ -30017,6 +30917,102 @@ function normaliseMapping(mapping) {
30017
30917
  return out;
30018
30918
  }
30019
30919
 
30920
+ /** What `strategyDeployments` throws for an id it cannot place. */
30921
+ class StrategyDeploymentInputError extends Error {
30922
+ constructor(code) {
30923
+ super(`strategyDeployments: input refused (${code})`);
30924
+ this.name = 'StrategyDeploymentInputError';
30925
+ this.code = code;
30926
+ Object.setPrototypeOf(this, StrategyDeploymentInputError.prototype);
30927
+ }
30928
+ }
30929
+ /** The canonical deployment: its pool ids ARE the strategies' ids. */
30930
+ const CANONICAL_CHAIN = 'base';
30931
+ const ADDRESS_RE = /^0x[0-9a-f]{40}$/;
30932
+ /** The deployment's mapping with both sides lowercased; Base never has one. */
30933
+ function mappingOf(configs, chain) {
30934
+ var _a;
30935
+ if (chain === CANONICAL_CHAIN)
30936
+ return [];
30937
+ return Object.entries((_a = configs[chain].poolMetadataMapping) !== null && _a !== void 0 ? _a : {}).map(([local, canonical]) => [local.toLowerCase(), canonical.toLowerCase()]);
30938
+ }
30939
+ /**
30940
+ * `strategyDeployments` over any configs — the export reads `CHAIN_CONFIGS`;
30941
+ * the spec passes its own to cover shapes the built-in mapping lacks.
30942
+ */
30943
+ function resolveStrategyDeployments(configs, poolId, chain) {
30944
+ const id = typeof poolId === 'string' ? poolId.trim().toLowerCase() : '';
30945
+ if (!ADDRESS_RE.test(id)) {
30946
+ throw new StrategyDeploymentInputError('pool-id');
30947
+ }
30948
+ if (typeof chain !== 'string' ||
30949
+ !Object.prototype.hasOwnProperty.call(configs, chain)) {
30950
+ throw new StrategyDeploymentInputError('chain');
30951
+ }
30952
+ const chains = Object.keys(configs);
30953
+ const mappings = chains.map((c) => [c, mappingOf(configs, c)]);
30954
+ const isLocalElsewhere = mappings.some(([c, mapping]) => c !== chain && mapping.some(([local]) => local === id));
30955
+ let canonical;
30956
+ if (chain === CANONICAL_CHAIN) {
30957
+ // A Base id is its own Base pool — unless the mapping says it is
30958
+ // another deployment's chain-local id, which names no Base pool.
30959
+ if (isLocalElsewhere) {
30960
+ throw new StrategyDeploymentInputError('wrong-chain');
30961
+ }
30962
+ canonical = id;
30963
+ }
30964
+ else {
30965
+ const own = mappingOf(configs, chain).find(([local]) => local === id);
30966
+ if (own) {
30967
+ canonical = own[1];
30968
+ }
30969
+ else {
30970
+ const isCanonical = mappings.some(([, mapping]) => mapping.some(([, target]) => target === id));
30971
+ if (isLocalElsewhere || isCanonical) {
30972
+ throw new StrategyDeploymentInputError('wrong-chain');
30973
+ }
30974
+ // Unmapped off Base: a strategy of one on its own chain (every
30975
+ // XDC USDC pool today). Claiming a Base counterpart would read
30976
+ // Base with an XDC address.
30977
+ return [{ chain, poolId: id }];
30978
+ }
30979
+ }
30980
+ const found = [
30981
+ { chain: CANONICAL_CHAIN, poolId: canonical },
30982
+ ];
30983
+ for (const [c, mapping] of mappings) {
30984
+ for (const [local, target] of mapping) {
30985
+ if (target === canonical)
30986
+ found.push({ chain: c, poolId: local });
30987
+ }
30988
+ }
30989
+ return found;
30990
+ }
30991
+ /**
30992
+ * Every deployment a strategy has a pool on, canonical (Base) first, then the
30993
+ * others in `CHAIN_CONFIGS` order — the legs to fetch and pass to
30994
+ * `combineTvlHistories`. One entry for a strategy that lives on one chain
30995
+ * only (PayFi, on Base).
30996
+ *
30997
+ * `chain` is the deployment `poolId` was read on — required, because an
30998
+ * address alone does not say which chain it belongs to. From either side of
30999
+ * a mapping the answer is the same set: Tax Pay's Base id on `'base'` and
31000
+ * its XDC id on `'xdc'` both return both pools. An id the mapping does not
31001
+ * know is a strategy of one on its own chain. Ids are compared
31002
+ * case-insensitively and returned lowercase.
31003
+ *
31004
+ * Throws `StrategyDeploymentInputError` for a malformed id, an unknown chain,
31005
+ * or an id the mapping places on another deployment (`'wrong-chain'`).
31006
+ *
31007
+ * ```ts
31008
+ * strategyDeployments(strategy.id, 'base');
31009
+ * // [{ chain: 'base', poolId: '0x…' }, { chain: 'xdc', poolId: '0x…' }]
31010
+ * ```
31011
+ */
31012
+ function strategyDeployments(poolId, chain) {
31013
+ return resolveStrategyDeployments(CHAIN_CONFIGS, poolId, chain);
31014
+ }
31015
+
30020
31016
  /** Directus instance holding Kasu's pool editorial content. */
30021
31017
  const DEFAULT_DIRECTUS_URL = 'https://kasu-finance.directus.app/';
30022
31018
  /**
@@ -30669,4 +31665,4 @@ function weightedAverageApy(slices) {
30669
31665
  return weighted / totalBalance;
30670
31666
  }
30671
31667
 
30672
- 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 };
31668
+ 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, RISK_GROUP_ORDER, RIXON_CAPITAL, SECONDS_PER_WEEK, SPONSORED_CALL_KINDS, SPONSORED_CALL_SELECTORS, SdkConfig, SponsoredCallInputError, StrategiesFacade, StrategyDeploymentInputError, StrategyTotalsInputError, TvlHistoryInputError, UPPER_MEZZANINE, WithdrawFlow, accruedInterest, aggregateYieldHistory, apyToEpochRate, asContractType, auMinimumRemaining, auThresholdFor, availableFixedTermConfigs, batchOutcomeOf, buildContractVersionType, buildFullNameRequestMessage, buildLegacyContractRequestMessage, buildLoanAgreementSignMessage, cancelDeadline, ceilToCents, checkBatchOutcome, checkSponsoredCallOutcome, classifyWalletFailure, combineTvlHistories, 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, groupRiskManagementItems, isAuMinimumExempt, isAustralianKyc, isBelowMinimumCapacity, isCycleClosed, isDefinitivelyNotSubmitted, isFixedTermConfigId, isUnpredictableGas, isUserRejected, lastEventTimestamp, lockDurationDays, lockDurationMonths, maxNetRateCeiling, netEffectiveApy, netTrancheApyBounds, nextCycleBoundary, parseFacility, parseFormattedMessage, parseMinorUnits, parseTrancheBound, pickDefaultTrancheId, pickHighestYieldTranche, pickShortDescription, poolAllTranchesFull, poolMaxApy, poolTvl, projectedAnnualEarnings, proveDepositInReceipt, proveUserOperationInReceipt, readCmsStartDate, resolveBoundShortcuts, resolveDepositBounds, securityStructureOf, selectVisiblePools, splitBusinessTypes, splitNumberedClauses, strategyDeployments, strategyTotalsUsd, submissionEvents, submitBatch, submitSponsoredCall, trancheApyBounds, trancheHasCapacity, trancheRiskRank, validateDepositBatchCalls, weeklyInterest, weightedAverageApy };