@kasufinance/kasu-sdk 2.13.0 → 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.
- package/README.md +40 -1
- package/dist/bundle.cjs.js +580 -60
- package/dist/bundle.esm.js +577 -61
- package/dist/domain/index.d.ts +2 -2
- package/dist/domain/pool-tvl.d.ts +104 -0
- package/dist/domain/requests.d.ts +11 -10
- package/dist/facade/index.d.ts +2 -0
- package/dist/facade/strategies.d.ts +17 -1
- package/dist/facade/strategy-deployments.d.ts +65 -0
- package/dist/index.d.ts +1 -1
- package/dist/services/DataService/data-service.d.ts +29 -1
- package/dist/services/DataService/tvl-history.d.ts +49 -0
- package/dist/services/UserLending/subgraph-types.d.ts +2 -8
- package/dist/services/UserLending/types.d.ts +10 -0
- package/dist/services/UserLending/user-lending.d.ts +14 -0
- package/package.json +1 -1
- package/src/domain/index.ts +7 -2
- package/src/domain/pool-tvl.ts +281 -0
- package/src/domain/requests.ts +37 -23
- package/src/facade/index.ts +10 -0
- package/src/facade/strategies.ts +26 -1
- package/src/facade/strategy-deployments.ts +161 -0
- package/src/index.ts +4 -1
- package/src/services/DataService/data-service.ts +103 -7
- package/src/services/DataService/tvl-history.ts +131 -0
- package/src/services/UserLending/queries.ts +2 -7
- package/src/services/UserLending/subgraph-types.ts +2 -8
- package/src/services/UserLending/types.ts +10 -0
- package/src/services/UserLending/user-lending.ts +98 -40
package/dist/bundle.cjs.js
CHANGED
|
@@ -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
|
-
? {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -18015,14 +18354,13 @@ const currentEpochFtdAmountQuery = gql `
|
|
|
18015
18354
|
user: $userId
|
|
18016
18355
|
lendingPool_in: $poolIds
|
|
18017
18356
|
epochId: $currentEpoch
|
|
18357
|
+
type: DepositRequest
|
|
18018
18358
|
}
|
|
18019
18359
|
) {
|
|
18020
18360
|
fixedTermConfigId
|
|
18021
18361
|
amountRequested
|
|
18022
18362
|
tranche {
|
|
18023
18363
|
id
|
|
18024
|
-
balance
|
|
18025
|
-
shares
|
|
18026
18364
|
}
|
|
18027
18365
|
}
|
|
18028
18366
|
}
|
|
@@ -18037,11 +18375,7 @@ const currentFtdBalanceQuery = gql `
|
|
|
18037
18375
|
configId
|
|
18038
18376
|
lendingPoolTranche {
|
|
18039
18377
|
id
|
|
18040
|
-
|
|
18041
|
-
}
|
|
18042
|
-
lendingPool {
|
|
18043
|
-
balance
|
|
18044
|
-
tranches {
|
|
18378
|
+
balance
|
|
18045
18379
|
shares
|
|
18046
18380
|
}
|
|
18047
18381
|
}
|
|
@@ -18049,6 +18383,13 @@ const currentFtdBalanceQuery = gql `
|
|
|
18049
18383
|
}
|
|
18050
18384
|
`;
|
|
18051
18385
|
|
|
18386
|
+
/** Thrown when the subgraph reports a request type with no known unit. */
|
|
18387
|
+
const UNKNOWN_REQUEST_TYPE_MESSAGE = 'UserLending: unknown request type, its amount unit is unknown';
|
|
18388
|
+
const KNOWN_REQUEST_TYPES = [
|
|
18389
|
+
'DepositRequest',
|
|
18390
|
+
'WithdrawalRequest',
|
|
18391
|
+
];
|
|
18392
|
+
const isKnownRequestType = (type) => typeof type === 'string' && KNOWN_REQUEST_TYPES.includes(type);
|
|
18052
18393
|
class UserLending {
|
|
18053
18394
|
constructor(_kasuConfig, signerOrProvider) {
|
|
18054
18395
|
this._kasuConfig = _kasuConfig;
|
|
@@ -18210,6 +18551,14 @@ class UserLending {
|
|
|
18210
18551
|
]);
|
|
18211
18552
|
const retn = [];
|
|
18212
18553
|
for (const userRequest of subgraphResult.userRequests) {
|
|
18554
|
+
// A request type this SDK does not know has no known amount unit
|
|
18555
|
+
// (the schema also declares types no live deployment emits yet).
|
|
18556
|
+
// Skip that ONE row before anything is priced or read on chain,
|
|
18557
|
+
// rather than failing — and blanking — the lender's whole list.
|
|
18558
|
+
if (!isKnownRequestType(userRequest.type)) {
|
|
18559
|
+
console.error(`${UNKNOWN_REQUEST_TYPE_MESSAGE}: ${String(userRequest.type)} (request ${userRequest.id}); row skipped`);
|
|
18560
|
+
continue;
|
|
18561
|
+
}
|
|
18213
18562
|
const directusPool = directusResult.find(({ id }) => id === userRequest.lendingPool.id);
|
|
18214
18563
|
const trancheName = trancheNames[userRequest.lendingPool.tranches.length - 1][parseInt(userRequest.tranche.orderId)];
|
|
18215
18564
|
const events = [];
|
|
@@ -18305,9 +18654,7 @@ class UserLending {
|
|
|
18305
18654
|
: 'Withdrawal',
|
|
18306
18655
|
trancheName: trancheName,
|
|
18307
18656
|
trancheId: userRequest.tranche.id,
|
|
18308
|
-
requestedAmount:
|
|
18309
|
-
? userRequest.amountRequested
|
|
18310
|
-
: this.convertSharesToAssets(userRequest.amountRequested, userRequest.tranche.balance, userRequest.tranche.shares).toString(),
|
|
18657
|
+
requestedAmount: this.requestedAmountInAssets(userRequest),
|
|
18311
18658
|
acceptedAmount: userRequest.amountAccepted,
|
|
18312
18659
|
rejectedAmount: userRequest.amountRejected,
|
|
18313
18660
|
status: userRequest.status,
|
|
@@ -18340,8 +18687,11 @@ class UserLending {
|
|
|
18340
18687
|
for (const userFTD of currentFtdBalance.userLendingPoolTrancheFixedTermDepositLocks) {
|
|
18341
18688
|
const ftdID = parseFloat(userFTD.lendingPoolTrancheFixedTermConfig.configId);
|
|
18342
18689
|
const trancheID = userFTD.lendingPoolTrancheFixedTermConfig.lendingPoolTranche.id.toLowerCase();
|
|
18343
|
-
|
|
18344
|
-
|
|
18690
|
+
// The lock holds shares of ONE tranche, so they are worth that
|
|
18691
|
+
// tranche's share price — not the pool's average across tranches,
|
|
18692
|
+
// which differs whenever the tranches have earned differently.
|
|
18693
|
+
const lockTranche = userFTD.lendingPoolTrancheFixedTermConfig.lendingPoolTranche;
|
|
18694
|
+
const amount = this.convertSharesToAssets(userFTD.trancheShares, lockTranche.balance, lockTranche.shares);
|
|
18345
18695
|
const item = (_a = trancheMap.get(trancheID)) !== null && _a !== void 0 ? _a : [];
|
|
18346
18696
|
if (item[ftdID]) {
|
|
18347
18697
|
item[ftdID] += parseFloat(amount);
|
|
@@ -18356,25 +18706,40 @@ class UserLending {
|
|
|
18356
18706
|
}
|
|
18357
18707
|
getCurrentEpochFtdAmount(poolIds, userId, currentEpoch) {
|
|
18358
18708
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18709
|
+
var _a, _b;
|
|
18359
18710
|
const currentEpochFtdAmountRes = yield this._graph.request(currentEpochFtdAmountQuery, {
|
|
18360
18711
|
userId: userId.toLowerCase(),
|
|
18361
18712
|
poolIds: poolIds.map((id) => id.toLowerCase()),
|
|
18362
18713
|
currentEpoch: parseFloat(currentEpoch),
|
|
18363
18714
|
});
|
|
18364
|
-
|
|
18715
|
+
// Deposit requests only (the query filters on type): a deposit's
|
|
18716
|
+
// `amountRequested` is already an ASSET amount, so it is summed as it
|
|
18717
|
+
// is — never put through a share price. Summed, not assigned: a
|
|
18718
|
+
// cancelled request and its replacement share a tranche and a config
|
|
18719
|
+
// in one epoch, and the cancelled one's 0 must not overwrite the live
|
|
18720
|
+
// amount.
|
|
18721
|
+
const totals = new Map();
|
|
18365
18722
|
for (const userRequest of currentEpochFtdAmountRes.userRequests) {
|
|
18366
18723
|
const ftdID = parseFloat(userRequest.fixedTermConfigId);
|
|
18367
|
-
|
|
18368
|
-
const amountRequested = this.convertSharesToAssets(userRequest.amountRequested, userRequest.tranche.balance, userRequest.tranche.shares);
|
|
18369
|
-
if (item) {
|
|
18370
|
-
const clonedItem = [...item];
|
|
18371
|
-
clonedItem[ftdID] = amountRequested;
|
|
18372
|
-
currentEpochFtdAmounts.set(userRequest.tranche.id, clonedItem);
|
|
18724
|
+
if (!Number.isInteger(ftdID) || ftdID < 0)
|
|
18373
18725
|
continue;
|
|
18374
|
-
|
|
18726
|
+
const perConfig = (_a = totals.get(userRequest.tranche.id)) !== null && _a !== void 0 ? _a : new Map();
|
|
18727
|
+
perConfig.set(ftdID, ((_b = perConfig.get(ftdID)) !== null && _b !== void 0 ? _b : ethers.BigNumber.from(0)).add(utils$2.parseUnits(userRequest.amountRequested)));
|
|
18728
|
+
totals.set(userRequest.tranche.id, perConfig);
|
|
18729
|
+
}
|
|
18730
|
+
// A slot whose total is zero is left out — `'0.0'` is a truthy
|
|
18731
|
+
// string, and a caller testing the slot would read it as an amount.
|
|
18732
|
+
// A tranche with no non-zero slot is left out entirely.
|
|
18733
|
+
const currentEpochFtdAmounts = new Map();
|
|
18734
|
+
for (const [trancheId, perConfig] of totals) {
|
|
18375
18735
|
const ftdAmounts = [];
|
|
18376
|
-
|
|
18377
|
-
|
|
18736
|
+
for (const [ftdID, total] of perConfig) {
|
|
18737
|
+
if (total.gt(0))
|
|
18738
|
+
ftdAmounts[ftdID] = utils$2.formatUnits(total);
|
|
18739
|
+
}
|
|
18740
|
+
if (ftdAmounts.length) {
|
|
18741
|
+
currentEpochFtdAmounts.set(trancheId, ftdAmounts);
|
|
18742
|
+
}
|
|
18378
18743
|
}
|
|
18379
18744
|
return currentEpochFtdAmounts;
|
|
18380
18745
|
});
|
|
@@ -18395,6 +18760,31 @@ class UserLending {
|
|
|
18395
18760
|
return amountMap;
|
|
18396
18761
|
});
|
|
18397
18762
|
}
|
|
18763
|
+
/**
|
|
18764
|
+
* A request's outstanding amount in ASSETS (the deployment's stable token).
|
|
18765
|
+
*
|
|
18766
|
+
* The subgraph's `UserRequest.amountRequested` is denominated by request
|
|
18767
|
+
* type. A DEPOSIT request holds assets: the deposit NFT's `assetAmount`,
|
|
18768
|
+
* top-ups added, accepted and rejected amounts taken off. A WITHDRAWAL
|
|
18769
|
+
* request holds tranche SHARES: the withdrawal NFT's `sharesAmount`,
|
|
18770
|
+
* accepted shares taken off. So only a withdrawal is converted, at the
|
|
18771
|
+
* tranche's current share price; a deposit passes through unchanged —
|
|
18772
|
+
* pricing it as shares overstated it by the share price.
|
|
18773
|
+
*/
|
|
18774
|
+
requestedAmountInAssets(userRequest) {
|
|
18775
|
+
switch (userRequest.type) {
|
|
18776
|
+
case 'DepositRequest':
|
|
18777
|
+
return userRequest.amountRequested;
|
|
18778
|
+
case 'WithdrawalRequest':
|
|
18779
|
+
return this.convertSharesToAssets(userRequest.amountRequested, userRequest.tranche.balance, userRequest.tranche.shares);
|
|
18780
|
+
default: {
|
|
18781
|
+
// Fail closed: a type this SDK does not know has no known
|
|
18782
|
+
// unit, and pricing it either way would print a wrong figure.
|
|
18783
|
+
const unknownType = userRequest.type;
|
|
18784
|
+
throw new Error(`${UNKNOWN_REQUEST_TYPE_MESSAGE}: ${unknownType}`);
|
|
18785
|
+
}
|
|
18786
|
+
}
|
|
18787
|
+
}
|
|
18398
18788
|
convertSharesToAssets(sharesAmount, totalAssets, totalSupply) {
|
|
18399
18789
|
if (utils$2.parseUnits(totalSupply).isZero())
|
|
18400
18790
|
return '0';
|
|
@@ -25648,9 +26038,9 @@ class SponsoredCallInputError extends Error {
|
|
|
25648
26038
|
Object.setPrototypeOf(this, SponsoredCallInputError.prototype);
|
|
25649
26039
|
}
|
|
25650
26040
|
}
|
|
25651
|
-
const ADDRESS_RE$
|
|
26041
|
+
const ADDRESS_RE$3 = /^0x[0-9a-fA-F]{40}$/;
|
|
25652
26042
|
function assertAddress(value, code) {
|
|
25653
|
-
if (typeof value !== 'string' || !ADDRESS_RE$
|
|
26043
|
+
if (typeof value !== 'string' || !ADDRESS_RE$3.test(value)) {
|
|
25654
26044
|
throw new SponsoredCallInputError(code);
|
|
25655
26045
|
}
|
|
25656
26046
|
}
|
|
@@ -25779,7 +26169,7 @@ function encodeStableTransfer(input) {
|
|
|
25779
26169
|
};
|
|
25780
26170
|
}
|
|
25781
26171
|
|
|
25782
|
-
const ADDRESS_RE$
|
|
26172
|
+
const ADDRESS_RE$2 = /^0x[0-9a-fA-F]{40}$/;
|
|
25783
26173
|
/**
|
|
25784
26174
|
* Encoded, ready-to-sponsor `LendingPoolManager` calls — read, then encode,
|
|
25785
26175
|
* NEVER send. Each returns `{ kind, call }`: post `kind` and `[call]` to a
|
|
@@ -25816,13 +26206,13 @@ class CallsFacade {
|
|
|
25816
26206
|
requestWithdrawal(params) {
|
|
25817
26207
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25818
26208
|
const { poolId, trancheId, amount, userAddress } = params;
|
|
25819
|
-
if (typeof userAddress !== 'string' || !ADDRESS_RE$
|
|
26209
|
+
if (typeof userAddress !== 'string' || !ADDRESS_RE$2.test(userAddress)) {
|
|
25820
26210
|
throw new SponsoredCallInputError('user');
|
|
25821
26211
|
}
|
|
25822
|
-
if (typeof poolId !== 'string' || !ADDRESS_RE$
|
|
26212
|
+
if (typeof poolId !== 'string' || !ADDRESS_RE$2.test(poolId)) {
|
|
25823
26213
|
throw new SponsoredCallInputError('pool');
|
|
25824
26214
|
}
|
|
25825
|
-
if (typeof trancheId !== 'string' || !ADDRESS_RE$
|
|
26215
|
+
if (typeof trancheId !== 'string' || !ADDRESS_RE$2.test(trancheId)) {
|
|
25826
26216
|
throw new SponsoredCallInputError('tranche');
|
|
25827
26217
|
}
|
|
25828
26218
|
let shares;
|
|
@@ -26474,7 +26864,7 @@ const BEFORE_EXECUTION = ENTRY_POINT_EVENTS.getEventTopic('BeforeExecution');
|
|
|
26474
26864
|
const DEPOSIT_REQUESTED = PENDING_POOL_EVENTS.getEventTopic('DepositRequested');
|
|
26475
26865
|
const ERC20$1 = IERC20MetadataAbi__factory.createInterface();
|
|
26476
26866
|
const LENDING_POOL_MANAGER$1 = ILendingPoolManagerAbi__factory.createInterface();
|
|
26477
|
-
const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
26867
|
+
const ADDRESS_RE$1 = /^0x[0-9a-fA-F]{40}$/;
|
|
26478
26868
|
/**
|
|
26479
26869
|
* Case-insensitive address equality; `false` for anything that is not a
|
|
26480
26870
|
* 20-byte hex address. Checksums are not enforced — an all-lowercase address
|
|
@@ -26483,8 +26873,8 @@ const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
|
26483
26873
|
function sameAddress(a, b) {
|
|
26484
26874
|
return (typeof a === 'string' &&
|
|
26485
26875
|
typeof b === 'string' &&
|
|
26486
|
-
ADDRESS_RE.test(a) &&
|
|
26487
|
-
ADDRESS_RE.test(b) &&
|
|
26876
|
+
ADDRESS_RE$1.test(a) &&
|
|
26877
|
+
ADDRESS_RE$1.test(b) &&
|
|
26488
26878
|
a.toLowerCase() === b.toLowerCase());
|
|
26489
26879
|
}
|
|
26490
26880
|
function isHexData(value) {
|
|
@@ -26510,7 +26900,7 @@ function isCallShape(call) {
|
|
|
26510
26900
|
if (call === null || typeof call !== 'object')
|
|
26511
26901
|
return false;
|
|
26512
26902
|
const { to, data } = call;
|
|
26513
|
-
return typeof to === 'string' && ADDRESS_RE.test(to) && isHexData(data);
|
|
26903
|
+
return typeof to === 'string' && ADDRESS_RE$1.test(to) && isHexData(data);
|
|
26514
26904
|
}
|
|
26515
26905
|
function isZeroValue(value) {
|
|
26516
26906
|
if (value === undefined)
|
|
@@ -29139,6 +29529,26 @@ class StrategiesFacade {
|
|
|
29139
29529
|
return yield this._dataService.getPoolTvlHistory(poolId, options);
|
|
29140
29530
|
});
|
|
29141
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
|
+
}
|
|
29142
29552
|
/**
|
|
29143
29553
|
* Fetch a single strategy by pool ID. Returns `null` if not found.
|
|
29144
29554
|
*/
|
|
@@ -29463,17 +29873,24 @@ function cancelDeadline(request, nextEpochStart, nowSeconds) {
|
|
|
29463
29873
|
const isCancelled = (events) => events.some((e) => e.requestType === 'Cancelled');
|
|
29464
29874
|
const isForced = (events) => events.some((e) => e.requestType === 'Forced');
|
|
29465
29875
|
const initiatedEvent = (events) => events.find((e) => e.requestType === 'Initiated');
|
|
29466
|
-
/**
|
|
29467
|
-
* Subgraph behaviour: when a request is cancelled, the on-request
|
|
29468
|
-
* `requestedAmount` field is reset to 0 (the lender's effective balance is
|
|
29469
|
-
* restored). The original amount survives on the `Initiated` event's
|
|
29470
|
-
* `assetAmount`. Recover from there so cancelled rows still carry the amount
|
|
29471
|
-
* the lender originally asked for.
|
|
29472
|
-
*/
|
|
29876
|
+
/** `assetAmount` of the `Initiated` event, or 0 when there is none. */
|
|
29473
29877
|
const initiatedAmountOf = (events) => {
|
|
29474
29878
|
const initiated = initiatedEvent(events);
|
|
29475
29879
|
return initiated ? Number(initiated.assetAmount || '0') : 0;
|
|
29476
29880
|
};
|
|
29881
|
+
/**
|
|
29882
|
+
* Subgraph behaviour: when a request is cancelled, the on-request
|
|
29883
|
+
* `requestedAmount` field is reset to 0 (the lender's effective balance is
|
|
29884
|
+
* restored). The amount cancelled survives on the `Cancelled` event's
|
|
29885
|
+
* `assetAmount` — everything still outstanding at the cancel, top-ups
|
|
29886
|
+
* included. Only when that event carries no figure does this fall back to the
|
|
29887
|
+
* `Initiated` event, which holds the FIRST submission alone and so misses
|
|
29888
|
+
* every top-up.
|
|
29889
|
+
*/
|
|
29890
|
+
const cancelledAmountOf = (events) => {
|
|
29891
|
+
const cancelledEvent = events.find((e) => e.requestType === 'Cancelled');
|
|
29892
|
+
return num(cancelledEvent === null || cancelledEvent === void 0 ? void 0 : cancelledEvent.assetAmount) || initiatedAmountOf(events);
|
|
29893
|
+
};
|
|
29477
29894
|
/**
|
|
29478
29895
|
* A deposit is REALLOCATED when the timeline carries a `Reallocated` event, or
|
|
29479
29896
|
* an `Accepted` event into a tranche other than the one requested.
|
|
@@ -29498,17 +29915,15 @@ const numOrNull = (str) => {
|
|
|
29498
29915
|
* Convert a `UserRequest` into a `RequestState`. Pure — no clock, no network,
|
|
29499
29916
|
* no copy.
|
|
29500
29917
|
*
|
|
29501
|
-
* BRANCH ORDER (this IS the behaviour
|
|
29502
|
-
* `deriveTransactionView` check for check):
|
|
29918
|
+
* BRANCH ORDER (this IS the behaviour):
|
|
29503
29919
|
*
|
|
29504
29920
|
* 1. a `Cancelled` event → `cancelled`, neutral, 0
|
|
29505
29921
|
* 2. a withdrawal with a `Forced` event → `forced`, outflow, −accepted
|
|
29506
29922
|
* 3. a reallocated deposit → `reallocated`, inflow, +accepted
|
|
29507
|
-
* 4. a withdrawal partly filled
|
|
29508
|
-
* remainder (cycle
|
|
29923
|
+
* 4. a withdrawal partly filled (paid > 0) with
|
|
29924
|
+
* a LIVE remainder (queued > 0, cycle open) → `partial`, outflow, −accepted
|
|
29509
29925
|
* 5. resolved (`status === 'Processed'`):
|
|
29510
|
-
* withdrawal
|
|
29511
|
-
* withdrawal, fully filled → `complete`, outflow, −accepted
|
|
29926
|
+
* withdrawal (nothing left queued) → `complete`, outflow, −accepted
|
|
29512
29927
|
* deposit, nothing accepted → `rejected`, neutral, 0
|
|
29513
29928
|
* deposit, part rejected → `partial`, inflow, +accepted
|
|
29514
29929
|
* deposit, fully accepted → `complete`, inflow, +accepted
|
|
@@ -29536,10 +29951,10 @@ function deriveRequestState(request) {
|
|
|
29536
29951
|
const isWithdrawal = request.requestType === 'Withdrawal';
|
|
29537
29952
|
const cancelled = isCancelled(request.events);
|
|
29538
29953
|
// Cancelled requests have `requestedAmount` zeroed on the request itself;
|
|
29539
|
-
//
|
|
29540
|
-
//
|
|
29954
|
+
// take the amount from the Cancelled event so the row still carries
|
|
29955
|
+
// "1500 cancelled" (1000 + a 500 top-up) instead of "0 cancelled".
|
|
29541
29956
|
const requested = cancelled
|
|
29542
|
-
? num(request.requestedAmount) ||
|
|
29957
|
+
? num(request.requestedAmount) || cancelledAmountOf(request.events)
|
|
29543
29958
|
: num(request.requestedAmount);
|
|
29544
29959
|
const accepted = num(request.acceptedAmount);
|
|
29545
29960
|
const rejected = num(request.rejectedAmount);
|
|
@@ -29590,18 +30005,23 @@ function deriveRequestState(request) {
|
|
|
29590
30005
|
}
|
|
29591
30006
|
// 4. Withdrawal partly filled with a LIVE remainder: the request returns
|
|
29592
30007
|
// to the queue with a live Cancel. Checked BEFORE the resolved branch and
|
|
29593
|
-
// via `isCycleClosed`, never via the sticky raw status.
|
|
30008
|
+
// via `isCycleClosed`, never via the sticky raw status. `requested` is
|
|
30009
|
+
// what is still OUTSTANDING, so "partly filled" is "something paid AND
|
|
30010
|
+
// something still queued" — never a comparison of the two, which would
|
|
30011
|
+
// turn on whether more was paid than is left.
|
|
29594
30012
|
if (isWithdrawal &&
|
|
29595
30013
|
accepted > 0 &&
|
|
29596
|
-
|
|
30014
|
+
requested > 0 &&
|
|
29597
30015
|
!isCycleClosed(request)) {
|
|
29598
30016
|
return Object.assign(Object.assign({}, base), { kind: 'outflow', amount: -accepted, statusCode: 'partial' });
|
|
29599
30017
|
}
|
|
29600
30018
|
// 5. Resolved — a rejection IS a resolution.
|
|
29601
30019
|
if (request.status === UserRequestStatus.PROCESSED) {
|
|
30020
|
+
// A withdrawal is resolved only once nothing is left queued — a
|
|
30021
|
+
// partly filled one returns to the queue (branch 4), and one cancelled
|
|
30022
|
+
// with a remainder is branch 1 — so a resolved withdrawal is complete.
|
|
29602
30023
|
if (isWithdrawal) {
|
|
29603
|
-
|
|
29604
|
-
return Object.assign(Object.assign({}, base), { kind: 'outflow', amount: -accepted, statusCode: partly ? 'partial' : 'complete' });
|
|
30024
|
+
return Object.assign(Object.assign({}, base), { kind: 'outflow', amount: -accepted, statusCode: 'complete' });
|
|
29605
30025
|
}
|
|
29606
30026
|
// Deposit: full reject vs partial vs full accept.
|
|
29607
30027
|
if (accepted === 0) {
|
|
@@ -29958,6 +30378,102 @@ function normaliseMapping(mapping) {
|
|
|
29958
30378
|
return out;
|
|
29959
30379
|
}
|
|
29960
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
|
+
|
|
29961
30477
|
/** Directus instance holding Kasu's pool editorial content. */
|
|
29962
30478
|
const DEFAULT_DIRECTUS_URL = 'https://kasu-finance.directus.app/';
|
|
29963
30479
|
/**
|
|
@@ -30658,6 +31174,8 @@ exports.SPONSORED_CALL_SELECTORS = SPONSORED_CALL_SELECTORS;
|
|
|
30658
31174
|
exports.SdkConfig = SdkConfig;
|
|
30659
31175
|
exports.SponsoredCallInputError = SponsoredCallInputError;
|
|
30660
31176
|
exports.StrategiesFacade = StrategiesFacade;
|
|
31177
|
+
exports.StrategyDeploymentInputError = StrategyDeploymentInputError;
|
|
31178
|
+
exports.TvlHistoryInputError = TvlHistoryInputError;
|
|
30661
31179
|
exports.UPPER_MEZZANINE = UPPER_MEZZANINE;
|
|
30662
31180
|
exports.WithdrawFlow = WithdrawFlow;
|
|
30663
31181
|
exports.accruedInterest = accruedInterest;
|
|
@@ -30677,6 +31195,7 @@ exports.ceilToCents = ceilToCents;
|
|
|
30677
31195
|
exports.checkBatchOutcome = checkBatchOutcome;
|
|
30678
31196
|
exports.checkSponsoredCallOutcome = checkSponsoredCallOutcome;
|
|
30679
31197
|
exports.classifyWalletFailure = classifyWalletFailure;
|
|
31198
|
+
exports.combineTvlHistories = combineTvlHistories;
|
|
30680
31199
|
exports.compareTrancheSeniority = compareTrancheSeniority;
|
|
30681
31200
|
exports.computeSettlementWindow = computeSettlementWindow;
|
|
30682
31201
|
exports.countSubmissions = countSubmissions;
|
|
@@ -30734,6 +31253,7 @@ exports.proveUserOperationInReceipt = proveUserOperationInReceipt;
|
|
|
30734
31253
|
exports.resolveBoundShortcuts = resolveBoundShortcuts;
|
|
30735
31254
|
exports.resolveDepositBounds = resolveDepositBounds;
|
|
30736
31255
|
exports.selectVisiblePools = selectVisiblePools;
|
|
31256
|
+
exports.strategyDeployments = strategyDeployments;
|
|
30737
31257
|
exports.submissionEvents = submissionEvents;
|
|
30738
31258
|
exports.submitBatch = submitBatch;
|
|
30739
31259
|
exports.submitSponsoredCall = submitSponsoredCall;
|