@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
@@ -64,3 +64,284 @@ export interface PoolTvlHistoryEntry {
64
64
  /** `'clearing'` for a cycle boundary; `'current'` for the closing point. */
65
65
  kind: 'clearing' | 'current';
66
66
  }
67
+
68
+ /**
69
+ * One deployment's share of a strategy's history, for `combineTvlHistories`:
70
+ * that pool's `getPoolTvlHistoryWindow` result (`{ entries, opening }`) as it
71
+ * came back, and what one unit of the deployment's stable token is worth in
72
+ * USD (`1` for USDC; today's AUD→USD for AUDD). The SDK fetches no exchange
73
+ * rate: the caller passes it.
74
+ */
75
+ export interface TvlHistoryLeg {
76
+ /** At least one point: a real pool always returns its `current` point. */
77
+ entries: readonly PoolTvlHistoryEntry[];
78
+ /** USD per unit of the leg's stable token. Finite and above 0. */
79
+ usdRate: number;
80
+ /**
81
+ * The leg's step BEFORE its first entry: the newest clearing before the
82
+ * window, as `getPoolTvlHistoryWindow` read it at its own block. It
83
+ * counts from its own `timestamp` on, and the leg is 0 before that — no
84
+ * value is carried back to a time before it existed. A `'clearing'`
85
+ * point no later than the first entry; it is never drawn. `null` or
86
+ * absent: the pool has no clearing before the window (it started lending
87
+ * later, say) and counts 0 before its first entry.
88
+ */
89
+ opening?: PoolTvlHistoryEntry | null;
90
+ }
91
+
92
+ /**
93
+ * One point of a strategy's TVL summed across its deployments.
94
+ *
95
+ * There is no `epochId`: each chain clears at its own blocks and times, so a
96
+ * merged point belongs to no single epoch. Plot by `timestamp`.
97
+ */
98
+ export interface StrategyTvlHistoryEntry {
99
+ /** Unix SECONDS. Strictly ascending across the series. */
100
+ timestamp: number;
101
+ /** The sum over legs of each leg's TVL then, times its `usdRate`. */
102
+ tvlUsd: number;
103
+ /** `'clearing'` for a cycle boundary on any leg; `'current'` for the closing point. */
104
+ kind: 'clearing' | 'current';
105
+ }
106
+
107
+ /**
108
+ * What `combineTvlHistories` refuses, as a code:
109
+ *
110
+ * - `legs` — not a non-empty array (or one with holes);
111
+ * - `leg` — a leg that is not an object;
112
+ * - `usd-rate` — a rate that is not a finite number above 0;
113
+ * - `opening` — an opening that is not a well-formed `'clearing'` point at
114
+ * or before the leg's first entry (or `null`, or absent);
115
+ * - `entries` — a leg's `entries` is not an array;
116
+ * - `empty-leg` — a leg with no entries: pass only the legs you have;
117
+ * - `entry` — an entry that is not an object (or a hole);
118
+ * - `timestamp` / `tvl` — not a finite number at or above 0;
119
+ * - `kind` — neither `'clearing'` nor `'current'`;
120
+ * - `order` — timestamps going backwards within a leg;
121
+ * - `current` — a `current` point that is not the leg's last, or some legs
122
+ * ending in one and others not;
123
+ * - `overflow` — a sum too large to be a finite number.
124
+ */
125
+ export type TvlHistoryInputErrorCode =
126
+ | 'legs'
127
+ | 'leg'
128
+ | 'usd-rate'
129
+ | 'opening'
130
+ | 'entries'
131
+ | 'empty-leg'
132
+ | 'entry'
133
+ | 'timestamp'
134
+ | 'tvl'
135
+ | 'kind'
136
+ | 'order'
137
+ | 'current'
138
+ | 'overflow';
139
+
140
+ /**
141
+ * What `combineTvlHistories` throws instead of returning a number it cannot
142
+ * stand behind. `leg` and `entry` are the zero-based position of the
143
+ * offending input, when there is one.
144
+ */
145
+ export class TvlHistoryInputError extends Error {
146
+ readonly code: TvlHistoryInputErrorCode;
147
+ readonly leg: number | undefined;
148
+ readonly entry: number | undefined;
149
+
150
+ constructor(code: TvlHistoryInputErrorCode, leg?: number, entry?: number) {
151
+ const at = [
152
+ leg === undefined ? '' : ` at leg ${String(leg)}`,
153
+ entry === undefined ? '' : `, entry ${String(entry)}`,
154
+ ].join('');
155
+ super(`combineTvlHistories: input refused (${code})${at}`);
156
+ this.name = 'TvlHistoryInputError';
157
+ this.code = code;
158
+ this.leg = leg;
159
+ this.entry = entry;
160
+ Object.setPrototypeOf(this, TvlHistoryInputError.prototype);
161
+ }
162
+ }
163
+
164
+ function isNonNegativeFinite(value: unknown): value is number {
165
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
166
+ }
167
+
168
+ /**
169
+ * Throws unless the leg is well formed. Index loops, not `forEach`: a hole in
170
+ * a sparse array is skipped by the array methods and would surface later as
171
+ * a raw `TypeError` instead of a refusal.
172
+ */
173
+ function validateLeg(leg: unknown, legIndex: number): TvlHistoryLeg {
174
+ if (typeof leg !== 'object' || leg === null) {
175
+ throw new TvlHistoryInputError('leg', legIndex);
176
+ }
177
+ const { entries, usdRate, opening } = leg as Partial<TvlHistoryLeg>;
178
+ if (
179
+ typeof usdRate !== 'number' ||
180
+ !Number.isFinite(usdRate) ||
181
+ usdRate <= 0
182
+ ) {
183
+ throw new TvlHistoryInputError('usd-rate', legIndex);
184
+ }
185
+ if (!Array.isArray(entries as unknown)) {
186
+ throw new TvlHistoryInputError('entries', legIndex);
187
+ }
188
+ const list = entries as readonly unknown[];
189
+ if (list.length === 0) {
190
+ throw new TvlHistoryInputError('empty-leg', legIndex);
191
+ }
192
+ let previous = -Infinity;
193
+ for (let index = 0; index < list.length; index += 1) {
194
+ const entry = list[index];
195
+ if (typeof entry !== 'object' || entry === null) {
196
+ throw new TvlHistoryInputError('entry', legIndex, index);
197
+ }
198
+ const { timestamp, tvl, kind } = entry as Partial<PoolTvlHistoryEntry>;
199
+ if (!isNonNegativeFinite(timestamp)) {
200
+ throw new TvlHistoryInputError('timestamp', legIndex, index);
201
+ }
202
+ if (!isNonNegativeFinite(tvl)) {
203
+ throw new TvlHistoryInputError('tvl', legIndex, index);
204
+ }
205
+ if (kind !== 'clearing' && kind !== 'current') {
206
+ throw new TvlHistoryInputError('kind', legIndex, index);
207
+ }
208
+ if (kind === 'current' && index !== list.length - 1) {
209
+ throw new TvlHistoryInputError('current', legIndex, index);
210
+ }
211
+ if (timestamp < previous) {
212
+ throw new TvlHistoryInputError('order', legIndex, index);
213
+ }
214
+ previous = timestamp;
215
+ }
216
+ if (opening !== undefined && opening !== null) {
217
+ const first = list[0] as PoolTvlHistoryEntry;
218
+ const { timestamp, tvl, kind } = (
219
+ typeof opening === 'object' ? opening : {}
220
+ ) as Partial<PoolTvlHistoryEntry>;
221
+ if (
222
+ !isNonNegativeFinite(timestamp) ||
223
+ !isNonNegativeFinite(tvl) ||
224
+ kind !== 'clearing' ||
225
+ timestamp > first.timestamp
226
+ ) {
227
+ throw new TvlHistoryInputError('opening', legIndex);
228
+ }
229
+ }
230
+ return leg as TvlHistoryLeg;
231
+ }
232
+
233
+ /**
234
+ * A strategy's TVL in USD over time, from its deployments' histories — Tax
235
+ * Pay runs on Base (USDC) and on XDC (AUDD), and its total lent is both.
236
+ * Pure: the caller fetches each leg (`strategyDeployments` names them) and
237
+ * passes the exchange rates.
238
+ *
239
+ * A step merge by time. At every distinct timestamp where any leg has a
240
+ * clearing, the value is the sum over legs of that leg's latest point at or
241
+ * before then, times its `usdRate`. A leg's `opening` — what it held just
242
+ * before the window — is one more step: it counts from its own timestamp
243
+ * until the leg's first entry, and the leg is 0 before it (or throughout,
244
+ * before its first entry, without one).
245
+ * Points that share a timestamp — clearings run in one block, or two chains
246
+ * clearing in the same second — are one output point. The series ends with
247
+ * one `current` point at the latest of the legs' `current` timestamps, equal
248
+ * to the sum of their `current` values. Every leg must end in a `current`
249
+ * point, or none may.
250
+ *
251
+ * The window is the union of the legs' windows: the series starts at the
252
+ * earliest first entry of any leg. An `opening` is a value, never a point.
253
+ * Without it, two windows cut at offset clearing times would count the
254
+ * later-starting leg 0 at the left edge and draw a false step up; with it,
255
+ * only a genuine late start (`opening: null`) counts 0.
256
+ *
257
+ * Every leg and entry is validated first: a bad input — an empty leg
258
+ * included — throws `TvlHistoryInputError` (its `code` says why), so the
259
+ * result never holds `NaN` or `Infinity`. The inputs are not modified.
260
+ *
261
+ * ```ts
262
+ * const window = { count: 52 };
263
+ * const points = combineTvlHistories([
264
+ * { ...(await base.strategies.getPoolTvlHistoryWindow(basePool, window)), usdRate: 1 },
265
+ * { ...(await xdc.strategies.getPoolTvlHistoryWindow(xdcPool, window)), usdRate: audToUsd },
266
+ * ]);
267
+ * ```
268
+ */
269
+ export function combineTvlHistories(
270
+ legs: readonly TvlHistoryLeg[],
271
+ ): StrategyTvlHistoryEntry[] {
272
+ if (!Array.isArray(legs as unknown) || legs.length === 0) {
273
+ throw new TvlHistoryInputError('legs');
274
+ }
275
+ const valid: TvlHistoryLeg[] = [];
276
+ for (let index = 0; index < legs.length; index += 1) {
277
+ valid.push(validateLeg(legs[index], index));
278
+ }
279
+ const endsInCurrent = valid.map(
280
+ (leg) => leg.entries[leg.entries.length - 1].kind === 'current',
281
+ );
282
+ const odd = endsInCurrent.findIndex((ends) => ends !== endsInCurrent[0]);
283
+ if (odd !== -1) throw new TvlHistoryInputError('current', odd);
284
+
285
+ const finalTimestamp = endsInCurrent[0]
286
+ ? Math.max(
287
+ ...valid.map(
288
+ (leg) => leg.entries[leg.entries.length - 1].timestamp,
289
+ ),
290
+ )
291
+ : undefined;
292
+ const clearingTimes = [
293
+ ...new Set(
294
+ valid.flatMap((leg) =>
295
+ leg.entries
296
+ .filter((entry) => entry.kind === 'clearing')
297
+ .map((entry) => entry.timestamp),
298
+ ),
299
+ ),
300
+ ]
301
+ .filter((t) => finalTimestamp === undefined || t < finalTimestamp)
302
+ .sort((a, b) => a - b);
303
+
304
+ // One cursor per leg: the times ascend, so each leg is walked once. A
305
+ // leg's `current` point is a step like any other, which keeps a chain
306
+ // whose subgraph lags behind the other's last clearing at its own latest.
307
+ const cursors = valid.map(() => -1);
308
+ const sumAt = (t: number): number => {
309
+ let sum = 0;
310
+ valid.forEach((leg, index) => {
311
+ let cursor = cursors[index];
312
+ while (
313
+ cursor + 1 < leg.entries.length &&
314
+ leg.entries[cursor + 1].timestamp <= t
315
+ ) {
316
+ cursor += 1;
317
+ }
318
+ cursors[index] = cursor;
319
+ // Before the first entry, the opening is the step — from its
320
+ // own time on, never earlier.
321
+ const opening = leg.opening;
322
+ const value =
323
+ cursor >= 0
324
+ ? leg.entries[cursor].tvl
325
+ : opening && opening.timestamp <= t
326
+ ? opening.tvl
327
+ : 0;
328
+ sum += value * leg.usdRate;
329
+ });
330
+ if (!Number.isFinite(sum)) throw new TvlHistoryInputError('overflow');
331
+ return sum;
332
+ };
333
+
334
+ const combined: StrategyTvlHistoryEntry[] = clearingTimes.map((t) => ({
335
+ timestamp: t,
336
+ tvlUsd: sumAt(t),
337
+ kind: 'clearing' as const,
338
+ }));
339
+ if (finalTimestamp !== undefined) {
340
+ combined.push({
341
+ timestamp: finalTimestamp,
342
+ tvlUsd: sumAt(finalTimestamp),
343
+ kind: 'current',
344
+ });
345
+ }
346
+ return combined;
347
+ }