@kasufinance/kasu-sdk 2.13.1 → 2.14.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.
@@ -15502,6 +15502,185 @@ KasuPoolExternalTVLAbi__factory.abi = _abi;
15502
15502
  function poolTvl(onChainBalance, offChainTvl) {
15503
15503
  return parseFloat(onChainBalance) + parseFloat(offChainTvl);
15504
15504
  }
15505
+ /**
15506
+ * What `combineTvlHistories` throws instead of returning a number it cannot
15507
+ * stand behind. `leg` and `entry` are the zero-based position of the
15508
+ * offending input, when there is one.
15509
+ */
15510
+ class TvlHistoryInputError extends Error {
15511
+ constructor(code, leg, entry) {
15512
+ const at = [
15513
+ leg === undefined ? '' : ` at leg ${String(leg)}`,
15514
+ entry === undefined ? '' : `, entry ${String(entry)}`,
15515
+ ].join('');
15516
+ super(`combineTvlHistories: input refused (${code})${at}`);
15517
+ this.name = 'TvlHistoryInputError';
15518
+ this.code = code;
15519
+ this.leg = leg;
15520
+ this.entry = entry;
15521
+ Object.setPrototypeOf(this, TvlHistoryInputError.prototype);
15522
+ }
15523
+ }
15524
+ function isNonNegativeFinite(value) {
15525
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
15526
+ }
15527
+ /**
15528
+ * Throws unless the leg is well formed. Index loops, not `forEach`: a hole in
15529
+ * a sparse array is skipped by the array methods and would surface later as
15530
+ * a raw `TypeError` instead of a refusal.
15531
+ */
15532
+ function validateLeg(leg, legIndex) {
15533
+ if (typeof leg !== 'object' || leg === null) {
15534
+ throw new TvlHistoryInputError('leg', legIndex);
15535
+ }
15536
+ const { entries, usdRate, opening } = leg;
15537
+ if (typeof usdRate !== 'number' ||
15538
+ !Number.isFinite(usdRate) ||
15539
+ usdRate <= 0) {
15540
+ throw new TvlHistoryInputError('usd-rate', legIndex);
15541
+ }
15542
+ if (!Array.isArray(entries)) {
15543
+ throw new TvlHistoryInputError('entries', legIndex);
15544
+ }
15545
+ const list = entries;
15546
+ if (list.length === 0) {
15547
+ throw new TvlHistoryInputError('empty-leg', legIndex);
15548
+ }
15549
+ let previous = -Infinity;
15550
+ for (let index = 0; index < list.length; index += 1) {
15551
+ const entry = list[index];
15552
+ if (typeof entry !== 'object' || entry === null) {
15553
+ throw new TvlHistoryInputError('entry', legIndex, index);
15554
+ }
15555
+ const { timestamp, tvl, kind } = entry;
15556
+ if (!isNonNegativeFinite(timestamp)) {
15557
+ throw new TvlHistoryInputError('timestamp', legIndex, index);
15558
+ }
15559
+ if (!isNonNegativeFinite(tvl)) {
15560
+ throw new TvlHistoryInputError('tvl', legIndex, index);
15561
+ }
15562
+ if (kind !== 'clearing' && kind !== 'current') {
15563
+ throw new TvlHistoryInputError('kind', legIndex, index);
15564
+ }
15565
+ if (kind === 'current' && index !== list.length - 1) {
15566
+ throw new TvlHistoryInputError('current', legIndex, index);
15567
+ }
15568
+ if (timestamp < previous) {
15569
+ throw new TvlHistoryInputError('order', legIndex, index);
15570
+ }
15571
+ previous = timestamp;
15572
+ }
15573
+ if (opening !== undefined && opening !== null) {
15574
+ const first = list[0];
15575
+ const { timestamp, tvl, kind } = (typeof opening === 'object' ? opening : {});
15576
+ if (!isNonNegativeFinite(timestamp) ||
15577
+ !isNonNegativeFinite(tvl) ||
15578
+ kind !== 'clearing' ||
15579
+ timestamp > first.timestamp) {
15580
+ throw new TvlHistoryInputError('opening', legIndex);
15581
+ }
15582
+ }
15583
+ return leg;
15584
+ }
15585
+ /**
15586
+ * A strategy's TVL in USD over time, from its deployments' histories — Tax
15587
+ * Pay runs on Base (USDC) and on XDC (AUDD), and its total lent is both.
15588
+ * Pure: the caller fetches each leg (`strategyDeployments` names them) and
15589
+ * passes the exchange rates.
15590
+ *
15591
+ * A step merge by time. At every distinct timestamp where any leg has a
15592
+ * clearing, the value is the sum over legs of that leg's latest point at or
15593
+ * before then, times its `usdRate`. A leg's `opening` — what it held just
15594
+ * before the window — is one more step: it counts from its own timestamp
15595
+ * until the leg's first entry, and the leg is 0 before it (or throughout,
15596
+ * before its first entry, without one).
15597
+ * Points that share a timestamp — clearings run in one block, or two chains
15598
+ * clearing in the same second — are one output point. The series ends with
15599
+ * one `current` point at the latest of the legs' `current` timestamps, equal
15600
+ * to the sum of their `current` values. Every leg must end in a `current`
15601
+ * point, or none may.
15602
+ *
15603
+ * The window is the union of the legs' windows: the series starts at the
15604
+ * earliest first entry of any leg. An `opening` is a value, never a point.
15605
+ * Without it, two windows cut at offset clearing times would count the
15606
+ * later-starting leg 0 at the left edge and draw a false step up; with it,
15607
+ * only a genuine late start (`opening: null`) counts 0.
15608
+ *
15609
+ * Every leg and entry is validated first: a bad input — an empty leg
15610
+ * included — throws `TvlHistoryInputError` (its `code` says why), so the
15611
+ * result never holds `NaN` or `Infinity`. The inputs are not modified.
15612
+ *
15613
+ * ```ts
15614
+ * const window = { count: 52 };
15615
+ * const points = combineTvlHistories([
15616
+ * { ...(await base.strategies.getPoolTvlHistoryWindow(basePool, window)), usdRate: 1 },
15617
+ * { ...(await xdc.strategies.getPoolTvlHistoryWindow(xdcPool, window)), usdRate: audToUsd },
15618
+ * ]);
15619
+ * ```
15620
+ */
15621
+ function combineTvlHistories(legs) {
15622
+ if (!Array.isArray(legs) || legs.length === 0) {
15623
+ throw new TvlHistoryInputError('legs');
15624
+ }
15625
+ const valid = [];
15626
+ for (let index = 0; index < legs.length; index += 1) {
15627
+ valid.push(validateLeg(legs[index], index));
15628
+ }
15629
+ const endsInCurrent = valid.map((leg) => leg.entries[leg.entries.length - 1].kind === 'current');
15630
+ const odd = endsInCurrent.findIndex((ends) => ends !== endsInCurrent[0]);
15631
+ if (odd !== -1)
15632
+ throw new TvlHistoryInputError('current', odd);
15633
+ const finalTimestamp = endsInCurrent[0]
15634
+ ? Math.max(...valid.map((leg) => leg.entries[leg.entries.length - 1].timestamp))
15635
+ : undefined;
15636
+ const clearingTimes = [
15637
+ ...new Set(valid.flatMap((leg) => leg.entries
15638
+ .filter((entry) => entry.kind === 'clearing')
15639
+ .map((entry) => entry.timestamp))),
15640
+ ]
15641
+ .filter((t) => finalTimestamp === undefined || t < finalTimestamp)
15642
+ .sort((a, b) => a - b);
15643
+ // One cursor per leg: the times ascend, so each leg is walked once. A
15644
+ // leg's `current` point is a step like any other, which keeps a chain
15645
+ // whose subgraph lags behind the other's last clearing at its own latest.
15646
+ const cursors = valid.map(() => -1);
15647
+ const sumAt = (t) => {
15648
+ let sum = 0;
15649
+ valid.forEach((leg, index) => {
15650
+ let cursor = cursors[index];
15651
+ while (cursor + 1 < leg.entries.length &&
15652
+ leg.entries[cursor + 1].timestamp <= t) {
15653
+ cursor += 1;
15654
+ }
15655
+ cursors[index] = cursor;
15656
+ // Before the first entry, the opening is the step — from its
15657
+ // own time on, never earlier.
15658
+ const opening = leg.opening;
15659
+ const value = cursor >= 0
15660
+ ? leg.entries[cursor].tvl
15661
+ : opening && opening.timestamp <= t
15662
+ ? opening.tvl
15663
+ : 0;
15664
+ sum += value * leg.usdRate;
15665
+ });
15666
+ if (!Number.isFinite(sum))
15667
+ throw new TvlHistoryInputError('overflow');
15668
+ return sum;
15669
+ };
15670
+ const combined = clearingTimes.map((t) => ({
15671
+ timestamp: t,
15672
+ tvlUsd: sumAt(t),
15673
+ kind: 'clearing',
15674
+ }));
15675
+ if (finalTimestamp !== undefined) {
15676
+ combined.push({
15677
+ timestamp: finalTimestamp,
15678
+ tvlUsd: sumAt(finalTimestamp),
15679
+ kind: 'current',
15680
+ });
15681
+ }
15682
+ return combined;
15683
+ }
15505
15684
 
15506
15685
  /**
15507
15686
  * Epochs in a year — the single source of truth for every rate conversion in
@@ -16250,6 +16429,95 @@ function fetchPoolClearings(request, poolId, scope, cancellation) {
16250
16429
  return { clearings, snapshot, requests };
16251
16430
  });
16252
16431
  }
16432
+ /**
16433
+ * Pure: the opening clearing — the newest of `before` (finished clearings the
16434
+ * window left out, older by epoch) that finished STRICTLY before the first
16435
+ * in-window clearing's block. Newest by block, then epoch, so a clearing
16436
+ * numbered out of order (XDC AUDD's epoch 1, a week before epoch 88) is
16437
+ * placed by when it happened. A clearing in the first in-window block itself
16438
+ * (a catch-up block that finished several epochs at once) is not an opening:
16439
+ * it holds the first entry's own value, not the one before it. `null` when
16440
+ * there is none — see `needsClearingBeforeBlock`.
16441
+ */
16442
+ function pickOpeningClearing(before, inWindow) {
16443
+ const firstBlock = Math.min(...inWindow.map((c) => c.block));
16444
+ let opening = null;
16445
+ for (const clearing of before) {
16446
+ if (clearing.block >= firstBlock)
16447
+ continue;
16448
+ if (opening === null ||
16449
+ clearing.block > opening.block ||
16450
+ (clearing.block === opening.block &&
16451
+ clearing.epochId > opening.epochId)) {
16452
+ opening = clearing;
16453
+ }
16454
+ }
16455
+ return opening;
16456
+ }
16457
+ /**
16458
+ * Pure: whether the opening needs `fetchClearingBefore`'s by-block read.
16459
+ * Only when the window left clearings out but every one of them finished in
16460
+ * or after the first in-window block — the extra rows sat in a catch-up
16461
+ * block — so an older one, if any, was not fetched. Otherwise the opening
16462
+ * (or its absence) is already known and nothing more is read.
16463
+ */
16464
+ function needsClearingBeforeBlock(before, opening) {
16465
+ return opening === null && before.length > 0;
16466
+ }
16467
+ /**
16468
+ * The newest finished clearing in a block below `blockLt`, by block: the
16469
+ * opening when every clearing the window left out shares its first block.
16470
+ * ONE extra request for one row, no snapshot. Clearings that share that
16471
+ * older block share its time and balance, so which one comes back does not
16472
+ * change the value.
16473
+ */
16474
+ const poolTvlClearingBeforeBlockQuery = gql `
16475
+ query PoolTvlClearingBeforeBlock($pool: String!, $blockLt: BigInt!) {
16476
+ clearings: lendingPoolClearings(
16477
+ first: 1
16478
+ orderBy: endBlockNumber
16479
+ orderDirection: desc
16480
+ where: {
16481
+ lendingPool: $pool
16482
+ isFinished: true
16483
+ endBlockNumber_lt: $blockLt
16484
+ }
16485
+ ) {
16486
+ epochId
16487
+ endBlockNumber
16488
+ endTimestamp
16489
+ }
16490
+ }
16491
+ `;
16492
+ function fetchClearingBeforeBlock(request, poolId, blockLt, cancellation) {
16493
+ return __awaiter(this, void 0, void 0, function* () {
16494
+ cancellation.throwIfCancelled();
16495
+ const page = yield request(poolTvlClearingBeforeBlockQuery, { pool: poolId, blockLt: String(blockLt) }, cancellation.signal);
16496
+ const row = page.clearings.at(0);
16497
+ return row === undefined ? null : parseClearing(row);
16498
+ });
16499
+ }
16500
+ /**
16501
+ * The newest finished clearing numbered below `beforeEpoch`: the opening
16502
+ * candidate of a `fromEpoch` window, whose own fetch never reads that far
16503
+ * back. ONE extra request for one row, no snapshot.
16504
+ */
16505
+ function fetchClearingBefore(request, poolId, beforeEpoch, cancellation) {
16506
+ return __awaiter(this, void 0, void 0, function* () {
16507
+ cancellation.throwIfCancelled();
16508
+ const page = yield request(poolTvlClearingsQuery, {
16509
+ poolId,
16510
+ pool: poolId,
16511
+ epochGte: '0',
16512
+ epochLt: String(beforeEpoch),
16513
+ direction: 'desc',
16514
+ first: 1,
16515
+ withSnapshot: false,
16516
+ }, cancellation.signal);
16517
+ const row = page.clearings.at(0);
16518
+ return row === undefined ? null : parseClearing(row);
16519
+ });
16520
+ }
16253
16521
  /**
16254
16522
  * The pool's balance (display units, the subgraph's decimal string) at the end
16255
16523
  * of each block, `TVL_HISTORY_BLOCKS_PER_REQUEST` blocks per request. A block
@@ -16653,6 +16921,42 @@ class DataService {
16653
16921
  */
16654
16922
  getPoolTvlHistory(poolId_1) {
16655
16923
  return __awaiter(this, arguments, void 0, function* (poolId, options = {}) {
16924
+ return (yield this._poolTvlHistory(poolId, options, false)).entries;
16925
+ });
16926
+ }
16927
+ /**
16928
+ * `getPoolTvlHistory` — the same options, errors and `entries` — plus
16929
+ * `opening`: the pool's value just before the window, so a strategy
16930
+ * summed across deployments (`combineTvlHistories`) knows what each leg
16931
+ * held before its first point instead of counting it 0.
16932
+ *
16933
+ * `opening` is the newest finished clearing STRICTLY before the window's
16934
+ * first block (by `pickOpeningClearing`), read at its own block exactly
16935
+ * like every other point and through the same past-block cache; `null`
16936
+ * when the pool has no clearing before the window. Extra cost over
16937
+ * `getPoolTvlHistory`:
16938
+ *
16939
+ * - `count` only: one more clearing row in the same request (`count + 1`,
16940
+ * which always reaches past the window when older history exists);
16941
+ * - `fromEpoch` above 0: one more one-row subgraph request, in parallel
16942
+ * with the clearings;
16943
+ * - only when every left-out clearing finished in the window's first
16944
+ * block (a catch-up block that closed several epochs at once): one
16945
+ * more one-row request by block (`fetchClearingBeforeBlock`), after
16946
+ * the clearings — the common case never makes it;
16947
+ * - either way, the opening's block in the same batched time-travel read
16948
+ * and one more `externalTVLOfPool` read (cached once final).
16949
+ *
16950
+ * `{ entries: [], opening: null }` wherever `getPoolTvlHistory` returns
16951
+ * `[]`.
16952
+ */
16953
+ getPoolTvlHistoryWindow(poolId_1) {
16954
+ return __awaiter(this, arguments, void 0, function* (poolId, options = {}) {
16955
+ return yield this._poolTvlHistory(poolId, options, true);
16956
+ });
16957
+ }
16958
+ _poolTvlHistory(poolId, options, withOpening) {
16959
+ return __awaiter(this, void 0, void 0, function* () {
16656
16960
  var _a;
16657
16961
  validatePoolTvlHistoryOptions(options);
16658
16962
  if (!utils$2.isAddress(poolId)) {
@@ -16671,33 +16975,56 @@ class DataService {
16671
16975
  rpcs,
16672
16976
  cancellation,
16673
16977
  })
16674
- : null, externalTvl !== null && externalTvl !== void 0 ? externalTvl : '', cancellation));
16978
+ : null, externalTvl !== null && externalTvl !== void 0 ? externalTvl : '', cancellation, withOpening));
16675
16979
  });
16676
16980
  }
16677
- _readPoolTvlHistory(pool, options, reader, externalTvl, cancellation) {
16981
+ _readPoolTvlHistory(pool, options, reader, externalTvl, cancellation, withOpening) {
16678
16982
  return __awaiter(this, void 0, void 0, function* () {
16679
16983
  var _a, _b, _c;
16984
+ const nothing = { entries: [], opening: null };
16680
16985
  const graph = (document, variables, signal) => this._graph.request({ document, variables, signal });
16681
16986
  const scope = options.fromEpoch === undefined && options.count !== undefined
16682
- ? { kind: 'newest', count: options.count }
16987
+ ? {
16988
+ kind: 'newest',
16989
+ // One past the window: the opening, when there is one.
16990
+ count: options.count + (withOpening ? 1 : 0),
16991
+ }
16683
16992
  : { kind: 'from', fromEpoch: (_a = options.fromEpoch) !== null && _a !== void 0 ? _a : 0 };
16684
16993
  try {
16685
- const [currentEpochNumber, { clearings, snapshot }] = yield Promise.all([
16994
+ const [currentEpochNumber, { clearings, snapshot }, before] = yield Promise.all([
16686
16995
  this._systemVariablesAbi.currentEpochNumber(),
16687
16996
  fetchPoolClearings(graph, pool, scope, cancellation),
16997
+ withOpening && scope.kind === 'from' && scope.fromEpoch > 0
16998
+ ? fetchClearingBefore(graph, pool, scope.fromEpoch, cancellation)
16999
+ : Promise.resolve(null),
16688
17000
  ]);
16689
17001
  const currentEpoch = currentEpochNumber.toNumber();
16690
17002
  const window = resolvePoolTvlHistoryWindow(options, currentEpoch);
16691
17003
  const poolNow = snapshot.pool;
16692
17004
  if (!window || !poolNow)
16693
- return [];
17005
+ return nothing;
16694
17006
  const head = (_b = snapshot.meta) === null || _b === void 0 ? void 0 : _b.block;
16695
17007
  if (!head || typeof head.timestamp !== 'number') {
16696
17008
  throw new Error('getPoolTvlHistory: the subgraph returned no latest block time');
16697
17009
  }
16698
17010
  const inWindow = clearings.filter((c) => c.epochId >= window.fromEpoch &&
16699
17011
  c.epochId <= window.toEpoch);
16700
- const blocks = distinctBlocks(inWindow);
17012
+ // Clearings the window left out, older by epoch: the extra row of
17013
+ // a count fetch, or the one-row read below `fromEpoch`.
17014
+ const older = scope.kind === 'newest'
17015
+ ? clearings.filter((c) => c.epochId < window.fromEpoch)
17016
+ : before === null
17017
+ ? []
17018
+ : [before];
17019
+ let opening = withOpening
17020
+ ? pickOpeningClearing(older, inWindow)
17021
+ : null;
17022
+ // The left-out clearings all sat in the first in-window block (a
17023
+ // catch-up block): ask for the newest one before it, by block.
17024
+ if (withOpening && needsClearingBeforeBlock(older, opening)) {
17025
+ opening = yield fetchClearingBeforeBlock(graph, pool, Math.min(...inWindow.map((c) => c.block)), cancellation);
17026
+ }
17027
+ const blocks = distinctBlocks(opening ? [...inWindow, opening] : inWindow);
16701
17028
  const withCurrent = window.toEpoch >= currentEpoch;
16702
17029
  const cacheable = (block) => block <= head.number - TVL_HISTORY_CACHE_DEPTH;
16703
17030
  const [balances, offChainRead] = yield Promise.all([
@@ -16715,7 +17042,7 @@ class DataService {
16715
17042
  latest: null,
16716
17043
  },
16717
17044
  ]);
16718
- return buildPoolTvlHistory({
17045
+ const entries = buildPoolTvlHistory({
16719
17046
  clearings: inWindow,
16720
17047
  balances,
16721
17048
  offChain: offChainRead.offChain,
@@ -16729,10 +17056,22 @@ class DataService {
16729
17056
  : null,
16730
17057
  decimals: this._kasuConfig.stableAssetDecimals,
16731
17058
  });
17059
+ // The same function as every other point, so the opening cannot
17060
+ // be computed another way.
17061
+ const [openingEntry] = opening
17062
+ ? buildPoolTvlHistory({
17063
+ clearings: [opening],
17064
+ balances,
17065
+ offChain: offChainRead.offChain,
17066
+ current: null,
17067
+ decimals: this._kasuConfig.stableAssetDecimals,
17068
+ })
17069
+ : [null];
17070
+ return { entries, opening: openingEntry };
16732
17071
  }
16733
17072
  catch (error) {
16734
17073
  if (isEmptyHistoryError(error, this._kasuConfig.isLiteDeployment)) {
16735
- return [];
17074
+ return nothing;
16736
17075
  }
16737
17076
  throw error;
16738
17077
  }
@@ -25699,9 +26038,9 @@ class SponsoredCallInputError extends Error {
25699
26038
  Object.setPrototypeOf(this, SponsoredCallInputError.prototype);
25700
26039
  }
25701
26040
  }
25702
- const ADDRESS_RE$2 = /^0x[0-9a-fA-F]{40}$/;
26041
+ const ADDRESS_RE$3 = /^0x[0-9a-fA-F]{40}$/;
25703
26042
  function assertAddress(value, code) {
25704
- if (typeof value !== 'string' || !ADDRESS_RE$2.test(value)) {
26043
+ if (typeof value !== 'string' || !ADDRESS_RE$3.test(value)) {
25705
26044
  throw new SponsoredCallInputError(code);
25706
26045
  }
25707
26046
  }
@@ -25830,7 +26169,7 @@ function encodeStableTransfer(input) {
25830
26169
  };
25831
26170
  }
25832
26171
 
25833
- const ADDRESS_RE$1 = /^0x[0-9a-fA-F]{40}$/;
26172
+ const ADDRESS_RE$2 = /^0x[0-9a-fA-F]{40}$/;
25834
26173
  /**
25835
26174
  * Encoded, ready-to-sponsor `LendingPoolManager` calls — read, then encode,
25836
26175
  * NEVER send. Each returns `{ kind, call }`: post `kind` and `[call]` to a
@@ -25867,13 +26206,13 @@ class CallsFacade {
25867
26206
  requestWithdrawal(params) {
25868
26207
  return __awaiter(this, void 0, void 0, function* () {
25869
26208
  const { poolId, trancheId, amount, userAddress } = params;
25870
- if (typeof userAddress !== 'string' || !ADDRESS_RE$1.test(userAddress)) {
26209
+ if (typeof userAddress !== 'string' || !ADDRESS_RE$2.test(userAddress)) {
25871
26210
  throw new SponsoredCallInputError('user');
25872
26211
  }
25873
- if (typeof poolId !== 'string' || !ADDRESS_RE$1.test(poolId)) {
26212
+ if (typeof poolId !== 'string' || !ADDRESS_RE$2.test(poolId)) {
25874
26213
  throw new SponsoredCallInputError('pool');
25875
26214
  }
25876
- if (typeof trancheId !== 'string' || !ADDRESS_RE$1.test(trancheId)) {
26215
+ if (typeof trancheId !== 'string' || !ADDRESS_RE$2.test(trancheId)) {
25877
26216
  throw new SponsoredCallInputError('tranche');
25878
26217
  }
25879
26218
  let shares;
@@ -26525,7 +26864,7 @@ const BEFORE_EXECUTION = ENTRY_POINT_EVENTS.getEventTopic('BeforeExecution');
26525
26864
  const DEPOSIT_REQUESTED = PENDING_POOL_EVENTS.getEventTopic('DepositRequested');
26526
26865
  const ERC20$1 = IERC20MetadataAbi__factory.createInterface();
26527
26866
  const LENDING_POOL_MANAGER$1 = ILendingPoolManagerAbi__factory.createInterface();
26528
- const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
26867
+ const ADDRESS_RE$1 = /^0x[0-9a-fA-F]{40}$/;
26529
26868
  /**
26530
26869
  * Case-insensitive address equality; `false` for anything that is not a
26531
26870
  * 20-byte hex address. Checksums are not enforced — an all-lowercase address
@@ -26534,8 +26873,8 @@ const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
26534
26873
  function sameAddress(a, b) {
26535
26874
  return (typeof a === 'string' &&
26536
26875
  typeof b === 'string' &&
26537
- ADDRESS_RE.test(a) &&
26538
- ADDRESS_RE.test(b) &&
26876
+ ADDRESS_RE$1.test(a) &&
26877
+ ADDRESS_RE$1.test(b) &&
26539
26878
  a.toLowerCase() === b.toLowerCase());
26540
26879
  }
26541
26880
  function isHexData(value) {
@@ -26561,7 +26900,7 @@ function isCallShape(call) {
26561
26900
  if (call === null || typeof call !== 'object')
26562
26901
  return false;
26563
26902
  const { to, data } = call;
26564
- return typeof to === 'string' && ADDRESS_RE.test(to) && isHexData(data);
26903
+ return typeof to === 'string' && ADDRESS_RE$1.test(to) && isHexData(data);
26565
26904
  }
26566
26905
  function isZeroValue(value) {
26567
26906
  if (value === undefined)
@@ -29190,6 +29529,26 @@ class StrategiesFacade {
29190
29529
  return yield this._dataService.getPoolTvlHistory(poolId, options);
29191
29530
  });
29192
29531
  }
29532
+ /**
29533
+ * `getPoolTvlHistory` plus `opening` — the newest clearing before the
29534
+ * window, read at its own block like every other point, or `null` when
29535
+ * the pool has none — from the same read. Pass the result to
29536
+ * `combineTvlHistories` as a leg with its `usdRate`: the opening is what
29537
+ * the leg held before its first point, so a window cut at a different
29538
+ * time on another chain does not count it 0. Same options and errors as
29539
+ * `getPoolTvlHistory`; the extra cost is one clearing row (or, for
29540
+ * `fromEpoch`, one small request) and one block.
29541
+ *
29542
+ * ```ts
29543
+ * const leg = await kasu.strategies.getPoolTvlHistoryWindow(poolId, { count: 52 });
29544
+ * // { entries: PoolTvlHistoryEntry[], opening: PoolTvlHistoryEntry | null }
29545
+ * ```
29546
+ */
29547
+ getPoolTvlHistoryWindow(poolId_1) {
29548
+ return __awaiter(this, arguments, void 0, function* (poolId, options = {}) {
29549
+ return yield this._dataService.getPoolTvlHistoryWindow(poolId, options);
29550
+ });
29551
+ }
29193
29552
  /**
29194
29553
  * Fetch a single strategy by pool ID. Returns `null` if not found.
29195
29554
  */
@@ -30019,6 +30378,102 @@ function normaliseMapping(mapping) {
30019
30378
  return out;
30020
30379
  }
30021
30380
 
30381
+ /** What `strategyDeployments` throws for an id it cannot place. */
30382
+ class StrategyDeploymentInputError extends Error {
30383
+ constructor(code) {
30384
+ super(`strategyDeployments: input refused (${code})`);
30385
+ this.name = 'StrategyDeploymentInputError';
30386
+ this.code = code;
30387
+ Object.setPrototypeOf(this, StrategyDeploymentInputError.prototype);
30388
+ }
30389
+ }
30390
+ /** The canonical deployment: its pool ids ARE the strategies' ids. */
30391
+ const CANONICAL_CHAIN = 'base';
30392
+ const ADDRESS_RE = /^0x[0-9a-f]{40}$/;
30393
+ /** The deployment's mapping with both sides lowercased; Base never has one. */
30394
+ function mappingOf(configs, chain) {
30395
+ var _a;
30396
+ if (chain === CANONICAL_CHAIN)
30397
+ return [];
30398
+ return Object.entries((_a = configs[chain].poolMetadataMapping) !== null && _a !== void 0 ? _a : {}).map(([local, canonical]) => [local.toLowerCase(), canonical.toLowerCase()]);
30399
+ }
30400
+ /**
30401
+ * `strategyDeployments` over any configs — the export reads `CHAIN_CONFIGS`;
30402
+ * the spec passes its own to cover shapes the built-in mapping lacks.
30403
+ */
30404
+ function resolveStrategyDeployments(configs, poolId, chain) {
30405
+ const id = typeof poolId === 'string' ? poolId.trim().toLowerCase() : '';
30406
+ if (!ADDRESS_RE.test(id)) {
30407
+ throw new StrategyDeploymentInputError('pool-id');
30408
+ }
30409
+ if (typeof chain !== 'string' ||
30410
+ !Object.prototype.hasOwnProperty.call(configs, chain)) {
30411
+ throw new StrategyDeploymentInputError('chain');
30412
+ }
30413
+ const chains = Object.keys(configs);
30414
+ const mappings = chains.map((c) => [c, mappingOf(configs, c)]);
30415
+ const isLocalElsewhere = mappings.some(([c, mapping]) => c !== chain && mapping.some(([local]) => local === id));
30416
+ let canonical;
30417
+ if (chain === CANONICAL_CHAIN) {
30418
+ // A Base id is its own Base pool — unless the mapping says it is
30419
+ // another deployment's chain-local id, which names no Base pool.
30420
+ if (isLocalElsewhere) {
30421
+ throw new StrategyDeploymentInputError('wrong-chain');
30422
+ }
30423
+ canonical = id;
30424
+ }
30425
+ else {
30426
+ const own = mappingOf(configs, chain).find(([local]) => local === id);
30427
+ if (own) {
30428
+ canonical = own[1];
30429
+ }
30430
+ else {
30431
+ const isCanonical = mappings.some(([, mapping]) => mapping.some(([, target]) => target === id));
30432
+ if (isLocalElsewhere || isCanonical) {
30433
+ throw new StrategyDeploymentInputError('wrong-chain');
30434
+ }
30435
+ // Unmapped off Base: a strategy of one on its own chain (every
30436
+ // XDC USDC pool today). Claiming a Base counterpart would read
30437
+ // Base with an XDC address.
30438
+ return [{ chain, poolId: id }];
30439
+ }
30440
+ }
30441
+ const found = [
30442
+ { chain: CANONICAL_CHAIN, poolId: canonical },
30443
+ ];
30444
+ for (const [c, mapping] of mappings) {
30445
+ for (const [local, target] of mapping) {
30446
+ if (target === canonical)
30447
+ found.push({ chain: c, poolId: local });
30448
+ }
30449
+ }
30450
+ return found;
30451
+ }
30452
+ /**
30453
+ * Every deployment a strategy has a pool on, canonical (Base) first, then the
30454
+ * others in `CHAIN_CONFIGS` order — the legs to fetch and pass to
30455
+ * `combineTvlHistories`. One entry for a strategy that lives on one chain
30456
+ * only (PayFi, on Base).
30457
+ *
30458
+ * `chain` is the deployment `poolId` was read on — required, because an
30459
+ * address alone does not say which chain it belongs to. From either side of
30460
+ * a mapping the answer is the same set: Tax Pay's Base id on `'base'` and
30461
+ * its XDC id on `'xdc'` both return both pools. An id the mapping does not
30462
+ * know is a strategy of one on its own chain. Ids are compared
30463
+ * case-insensitively and returned lowercase.
30464
+ *
30465
+ * Throws `StrategyDeploymentInputError` for a malformed id, an unknown chain,
30466
+ * or an id the mapping places on another deployment (`'wrong-chain'`).
30467
+ *
30468
+ * ```ts
30469
+ * strategyDeployments(strategy.id, 'base');
30470
+ * // [{ chain: 'base', poolId: '0x…' }, { chain: 'xdc', poolId: '0x…' }]
30471
+ * ```
30472
+ */
30473
+ function strategyDeployments(poolId, chain) {
30474
+ return resolveStrategyDeployments(CHAIN_CONFIGS, poolId, chain);
30475
+ }
30476
+
30022
30477
  /** Directus instance holding Kasu's pool editorial content. */
30023
30478
  const DEFAULT_DIRECTUS_URL = 'https://kasu-finance.directus.app/';
30024
30479
  /**
@@ -30719,6 +31174,8 @@ exports.SPONSORED_CALL_SELECTORS = SPONSORED_CALL_SELECTORS;
30719
31174
  exports.SdkConfig = SdkConfig;
30720
31175
  exports.SponsoredCallInputError = SponsoredCallInputError;
30721
31176
  exports.StrategiesFacade = StrategiesFacade;
31177
+ exports.StrategyDeploymentInputError = StrategyDeploymentInputError;
31178
+ exports.TvlHistoryInputError = TvlHistoryInputError;
30722
31179
  exports.UPPER_MEZZANINE = UPPER_MEZZANINE;
30723
31180
  exports.WithdrawFlow = WithdrawFlow;
30724
31181
  exports.accruedInterest = accruedInterest;
@@ -30738,6 +31195,7 @@ exports.ceilToCents = ceilToCents;
30738
31195
  exports.checkBatchOutcome = checkBatchOutcome;
30739
31196
  exports.checkSponsoredCallOutcome = checkSponsoredCallOutcome;
30740
31197
  exports.classifyWalletFailure = classifyWalletFailure;
31198
+ exports.combineTvlHistories = combineTvlHistories;
30741
31199
  exports.compareTrancheSeniority = compareTrancheSeniority;
30742
31200
  exports.computeSettlementWindow = computeSettlementWindow;
30743
31201
  exports.countSubmissions = countSubmissions;
@@ -30795,6 +31253,7 @@ exports.proveUserOperationInReceipt = proveUserOperationInReceipt;
30795
31253
  exports.resolveBoundShortcuts = resolveBoundShortcuts;
30796
31254
  exports.resolveDepositBounds = resolveDepositBounds;
30797
31255
  exports.selectVisiblePools = selectVisiblePools;
31256
+ exports.strategyDeployments = strategyDeployments;
30798
31257
  exports.submissionEvents = submissionEvents;
30799
31258
  exports.submitBatch = submitBatch;
30800
31259
  exports.submitSponsoredCall = submitSponsoredCall;