@kasufinance/kasu-sdk 2.11.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +37 -2
  2. package/dist/bundle.cjs.js +1516 -71
  3. package/dist/bundle.esm.js +1511 -73
  4. package/dist/domain/index.d.ts +6 -2
  5. package/dist/domain/index.js +2 -0
  6. package/dist/domain/index.js.map +1 -1
  7. package/dist/domain/pool-tvl.d.ts +62 -0
  8. package/dist/domain/pool-tvl.js +22 -0
  9. package/dist/domain/pool-tvl.js.map +1 -0
  10. package/dist/domain/yield-history.d.ts +122 -0
  11. package/dist/domain/yield-history.js +95 -0
  12. package/dist/domain/yield-history.js.map +1 -0
  13. package/dist/facade/chain-configs.js +24 -0
  14. package/dist/facade/chain-configs.js.map +1 -1
  15. package/dist/facade/kasu.d.ts +2 -0
  16. package/dist/facade/kasu.js +13 -5
  17. package/dist/facade/kasu.js.map +1 -1
  18. package/dist/facade/strategies.d.ts +20 -0
  19. package/dist/facade/strategies.js +22 -0
  20. package/dist/facade/strategies.js.map +1 -1
  21. package/dist/facade/types.d.ts +20 -0
  22. package/dist/facade/user-portfolio.d.ts +32 -0
  23. package/dist/facade/user-portfolio.js +34 -0
  24. package/dist/facade/user-portfolio.js.map +1 -1
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/kasu-sdk.d.ts +9 -1
  29. package/dist/kasu-sdk.js +2 -2
  30. package/dist/kasu-sdk.js.map +1 -1
  31. package/dist/sdk-config.d.ts +20 -0
  32. package/dist/sdk-config.js +3 -1
  33. package/dist/sdk-config.js.map +1 -1
  34. package/dist/services/DataService/data-service.d.ts +81 -1
  35. package/dist/services/DataService/data-service.js +211 -6
  36. package/dist/services/DataService/data-service.js.map +1 -1
  37. package/dist/services/DataService/tvl-history.d.ts +296 -0
  38. package/dist/services/DataService/tvl-history.js +558 -0
  39. package/dist/services/DataService/tvl-history.js.map +1 -0
  40. package/dist/services/Portfolio/portfolio.d.ts +37 -0
  41. package/dist/services/Portfolio/portfolio.js +136 -56
  42. package/dist/services/Portfolio/portfolio.js.map +1 -1
  43. package/dist/services/Portfolio/queries.js +15 -3
  44. package/dist/services/Portfolio/queries.js.map +1 -1
  45. package/dist/services/Portfolio/types.d.ts +8 -0
  46. package/dist/services/Portfolio/yield-history.d.ts +162 -0
  47. package/dist/services/Portfolio/yield-history.js +378 -0
  48. package/dist/services/Portfolio/yield-history.js.map +1 -0
  49. package/dist/services/shared.d.ts +16 -0
  50. package/dist/services/shared.js +30 -0
  51. package/dist/services/shared.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/domain/index.ts +15 -10
  54. package/src/domain/pool-tvl.ts +66 -0
  55. package/src/domain/yield-history.ts +194 -0
  56. package/src/facade/chain-configs.ts +26 -0
  57. package/src/facade/kasu.ts +20 -2
  58. package/src/facade/strategies.ts +26 -0
  59. package/src/facade/types.ts +20 -0
  60. package/src/facade/user-portfolio.ts +40 -3
  61. package/src/index.ts +11 -0
  62. package/src/kasu-sdk.ts +19 -2
  63. package/src/sdk-config.ts +22 -0
  64. package/src/services/DataService/data-service.ts +296 -6
  65. package/src/services/DataService/tvl-history.ts +853 -0
  66. package/src/services/Portfolio/portfolio.ts +263 -153
  67. package/src/services/Portfolio/queries.ts +15 -3
  68. package/src/services/Portfolio/types.ts +10 -0
  69. package/src/services/Portfolio/yield-history.ts +541 -0
  70. package/src/services/shared.ts +38 -1
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Settled interest per cycle (epoch): what a lender was credited at each close,
3
+ * per pool and tranche, and the one formula that turns a tranche's credited
4
+ * interest into one lender's share of it.
5
+ *
6
+ * **The figures are NET, and only net.** `LendingPool._applyTrancheInterest`
7
+ * takes the performance fee off first and emits `InterestApplied` with the
8
+ * lender side only (`interest × (1 − performanceFee)`); a fixed-term top-up
9
+ * (`_applyFixedRateInterests`) is net of the fee the same way. So the figure
10
+ * this module computes from those records IS what the lender was credited —
11
+ * never take the fee off it again. No gross is offered: the subgraph keeps
12
+ * only TODAY's fee, so a historical gross would be a guess.
13
+ *
14
+ * Units: amounts are display units of the DEPLOYMENT'S OWN stable token (USDC
15
+ * on Base and XDC USDC, AUDD on XDC AUDD) — never converted to USD here;
16
+ * shares are display units of tranche shares, as the subgraph's BigDecimals.
17
+ */
18
+
19
+ /** One fixed-term lock inside a tranche position, at one epoch. */
20
+ export interface EpochLockShares {
21
+ /**
22
+ * The lock earns in this epoch: it started BEFORE the epoch, ends at or
23
+ * after it, and the lender has a share snapshot for the tranche at it.
24
+ */
25
+ active: boolean;
26
+ /** The lock's tranche shares as of the epoch (latest update at or before it). */
27
+ sharesAtEpoch: number;
28
+ /** The lock's tranche shares as of the epoch BEFORE it. */
29
+ sharesBeforeEpoch: number;
30
+ }
31
+
32
+ /** Everything one lender's share of one tranche's credited interest needs. */
33
+ export interface EpochTrancheYieldInput {
34
+ /** The tranche's interest credited at this epoch's close (net of the fee). */
35
+ epochInterest: number;
36
+ /** The lender's tranche shares as of the epoch (fixed-term locks included). */
37
+ userShares: number;
38
+ /** The tranche's total shares as of the epoch. */
39
+ trancheShares: number;
40
+ /**
41
+ * The tranche's CURRENT balance and shares: the price a fixed-term top-up
42
+ * (extra shares) is valued at. The subgraph keeps no per-epoch tranche
43
+ * balance, so an older top-up is valued at today's price — a second-order
44
+ * difference, because the top-up is only the fixed rate's excess over the
45
+ * variable one.
46
+ */
47
+ trancheBalanceNow: number;
48
+ trancheSharesNow: number;
49
+ locks: readonly EpochLockShares[];
50
+ }
51
+
52
+ export interface EpochTrancheYield {
53
+ /** `variable + Σ locks` — the lender's whole yield in the tranche. */
54
+ total: number;
55
+ /** The variable residual: the share-weighted base yield minus the locks' part of it. */
56
+ variable: number;
57
+ /** Each lock's yield, in the order of `input.locks`: its base part plus its top-up. */
58
+ locks: number[];
59
+ }
60
+
61
+ /**
62
+ * The ONE per-epoch formula. `Portfolio.getPortfolioLendingData` runs it for
63
+ * the last settled epoch and `getYieldHistory` for every epoch of a window, so
64
+ * the two figures cannot drift apart.
65
+ *
66
+ * ```
67
+ * base = epochInterest × userShares / trancheShares
68
+ * lockBase = base × lock.sharesBeforeEpoch / userShares (active locks)
69
+ * topUp = (lock.sharesAtEpoch − lock.sharesBeforeEpoch) × trancheBalanceNow / trancheSharesNow
70
+ * variable = base − Σ lockBase
71
+ * total = variable + Σ (lockBase + topUp)
72
+ * ```
73
+ *
74
+ * A zero denominator contributes 0 (a tranche or a lender with no shares
75
+ * earned nothing at that close), never `NaN`.
76
+ */
77
+ export function epochTrancheYield(
78
+ input: EpochTrancheYieldInput,
79
+ ): EpochTrancheYield {
80
+ const base =
81
+ input.trancheShares === 0
82
+ ? 0
83
+ : (input.epochInterest * input.userShares) / input.trancheShares;
84
+
85
+ let variable = base;
86
+ const locks = input.locks.map((lock) => {
87
+ if (!lock.active) return 0;
88
+
89
+ const topUp =
90
+ input.trancheSharesNow === 0
91
+ ? 0
92
+ : ((lock.sharesAtEpoch - lock.sharesBeforeEpoch) *
93
+ input.trancheBalanceNow) /
94
+ input.trancheSharesNow;
95
+
96
+ const lockBase =
97
+ input.userShares === 0
98
+ ? 0
99
+ : (base * lock.sharesBeforeEpoch) / input.userShares;
100
+
101
+ variable -= lockBase;
102
+
103
+ return lockBase + topUp;
104
+ });
105
+
106
+ const total = locks.reduce((sum, lockYield) => sum + lockYield, variable);
107
+
108
+ return { total, variable, locks };
109
+ }
110
+
111
+ /** One lender's settled yield in one tranche at one epoch. */
112
+ export interface YieldHistoryEntry {
113
+ /** The epoch (cycle) number, in the chain's numbering. */
114
+ epochId: number;
115
+ /**
116
+ * When the epoch ended — the next epoch's start — in unix SECONDS (not
117
+ * milliseconds).
118
+ */
119
+ epochEnd: number;
120
+ /** Lowercased lending pool address. */
121
+ poolId: string;
122
+ /** Lowercased tranche address. */
123
+ trancheId: string;
124
+ /**
125
+ * What the close credited the lender in this tranche, AFTER the
126
+ * performance fee, in display units of the deployment's own stable token
127
+ * (AUDD on XDC AUDD — convert before formatting it as USD).
128
+ */
129
+ netYield: number;
130
+ }
131
+
132
+ /** One epoch's yield summed over the entries an aggregation kept. */
133
+ export interface YieldHistoryPoint {
134
+ /** The epoch (cycle) number. */
135
+ epochId: number;
136
+ /** The epoch's end, in unix SECONDS. */
137
+ epochEnd: number;
138
+ /** Net yield summed, in the deployment's own stable token. */
139
+ netYield: number;
140
+ }
141
+
142
+ export interface YieldHistoryFilter {
143
+ /** Keep only this pool (case-insensitive). */
144
+ poolId?: string;
145
+ /** Keep only this tranche (case-insensitive). */
146
+ trancheId?: string;
147
+ }
148
+
149
+ /**
150
+ * Pool/tranche entries → one point per epoch, ascending by `epochId`. With no
151
+ * filter it sums every strategy (the Lend tab's bars); with `poolId` it is one
152
+ * strategy (a strategy page's bars).
153
+ *
154
+ * An epoch appears only if a kept entry has it — there is no zero padding, so
155
+ * an empty input (a new lender) is an empty output.
156
+ *
157
+ * For a fixed-term lock holder the bars can sum to slightly MORE than the
158
+ * lender's lifetime interest (up to about 0.2 %): each top-up is valued at the
159
+ * tranche's CURRENT share price, which has grown since. Show "earned to date"
160
+ * from the lifetime figure, never from this sum.
161
+ */
162
+ export function aggregateYieldHistory(
163
+ entries: readonly YieldHistoryEntry[],
164
+ filter: YieldHistoryFilter = {},
165
+ ): YieldHistoryPoint[] {
166
+ const poolId = filter.poolId?.toLowerCase();
167
+ const trancheId = filter.trancheId?.toLowerCase();
168
+ const byEpoch = new Map<number, YieldHistoryPoint>();
169
+
170
+ for (const entry of entries) {
171
+ if (poolId !== undefined && entry.poolId.toLowerCase() !== poolId) {
172
+ continue;
173
+ }
174
+ if (
175
+ trancheId !== undefined &&
176
+ entry.trancheId.toLowerCase() !== trancheId
177
+ ) {
178
+ continue;
179
+ }
180
+
181
+ const point = byEpoch.get(entry.epochId);
182
+ if (point) {
183
+ point.netYield += entry.netYield;
184
+ continue;
185
+ }
186
+ byEpoch.set(entry.epochId, {
187
+ epochId: entry.epochId,
188
+ epochEnd: entry.epochEnd,
189
+ netYield: entry.netYield,
190
+ });
191
+ }
192
+
193
+ return [...byEpoch.values()].sort((a, b) => a.epochId - b.epochId);
194
+ }
@@ -15,6 +15,29 @@ const XDC_RPC_URLS: string[] = [
15
15
  'https://rpc.primenumbers.xyz/',
16
16
  ];
17
17
 
18
+ /**
19
+ * XDC RPCs that serve PAST state, for `getPoolTvlHistory` (each verified
20
+ * with `externalTVLOfPool` at old blocks, CORS `*`), in fallback order. Not
21
+ * `rpc.xdcrpc.com`, which refuses older blocks, and never `rpc.xdc.org` /
22
+ * `erpc.xdc.org`. Public and rate-limited.
23
+ */
24
+ const XDC_HISTORY_RPC_URLS: string[] = [
25
+ 'https://rpc.primenumbers.xyz/',
26
+ 'https://rpc.ankr.com/xdc',
27
+ ];
28
+
29
+ /**
30
+ * Base RPCs that serve PAST state, in fallback order (each verified with
31
+ * `externalTVLOfPool` at old blocks; all answer CORS). Not
32
+ * `base-rpc.publicnode.com`, the default read RPC, which refuses old state.
33
+ * Public and rate-limited.
34
+ */
35
+ const BASE_HISTORY_RPC_URLS: string[] = [
36
+ 'https://mainnet.base.org',
37
+ 'https://base.drpc.org',
38
+ 'https://1rpc.io/base',
39
+ ];
40
+
18
41
  /**
19
42
  * Built-in chain configurations for Kasu-supported networks.
20
43
  *
@@ -59,6 +82,7 @@ export const CHAIN_CONFIGS: Record<'base' | 'xdc' | 'xdc-usdc' | 'plume', ChainC
59
82
  'https://base-rpc.publicnode.com',
60
83
  'https://mainnet.base.org',
61
84
  ],
85
+ historyRpcUrls: BASE_HISTORY_RPC_URLS,
62
86
  blockExplorerUrl: 'https://basescan.org',
63
87
  },
64
88
 
@@ -100,6 +124,7 @@ export const CHAIN_CONFIGS: Record<'base' | 'xdc' | 'xdc-usdc' | 'plume', ChainC
100
124
  currencyCode: 'AUD',
101
125
  },
102
126
  rpcUrls: XDC_RPC_URLS,
127
+ historyRpcUrls: XDC_HISTORY_RPC_URLS,
103
128
  blockExplorerUrl: 'https://xdcscan.com',
104
129
  },
105
130
 
@@ -134,6 +159,7 @@ export const CHAIN_CONFIGS: Record<'base' | 'xdc' | 'xdc-usdc' | 'plume', ChainC
134
159
  currencyCode: 'USD',
135
160
  },
136
161
  rpcUrls: XDC_RPC_URLS,
162
+ historyRpcUrls: XDC_HISTORY_RPC_URLS,
137
163
  blockExplorerUrl: 'https://xdcscan.com',
138
164
  },
139
165
 
@@ -65,17 +65,21 @@ export class Kasu {
65
65
  * of pools than the read-only one it came from.
66
66
  */
67
67
  private readonly _configOverrides: Partial<SdkConfigOptions>;
68
+ /** The caller's own history provider, kept so `connect` keeps it too. */
69
+ private readonly _historyProvider: Provider | undefined;
68
70
 
69
71
  private constructor(
70
72
  sdk: KasuSdk,
71
73
  chainConfig: ChainConfigEntry,
72
74
  signerOrProvider: Provider | Signer,
73
75
  configOverrides: Partial<SdkConfigOptions>,
76
+ historyProvider: Provider | undefined,
74
77
  ) {
75
78
  this._sdk = sdk;
76
79
  this._chainConfig = chainConfig;
77
80
  this._signerOrProvider = signerOrProvider;
78
81
  this._configOverrides = configOverrides;
82
+ this._historyProvider = historyProvider;
79
83
 
80
84
  this.strategies = new StrategiesFacade(
81
85
  sdk.DataService,
@@ -168,6 +172,9 @@ export class Kasu {
168
172
  stableAssetDecimals:
169
173
  overrides.stableAssetDecimals ??
170
174
  stableAssetOf(chainConfig)?.decimals,
175
+ historyRpcUrls:
176
+ overrides.historyRpcUrls ?? chainConfig.historyRpcUrls,
177
+ chainId: overrides.chainId ?? chainConfig.chainId,
171
178
  });
172
179
 
173
180
  const signerOrProvider =
@@ -179,8 +186,18 @@ export class Kasu {
179
186
  : chainConfig.name,
180
187
  );
181
188
 
182
- const sdk = new KasuSdk(sdkConfig, signerOrProvider);
183
- return new Kasu(sdk, chainConfig, signerOrProvider, overrides);
189
+ // An explicit history provider wins; otherwise DataService builds
190
+ // them from `historyRpcUrls`, pinned to `chainId` (the one place).
191
+ const sdk = new KasuSdk(sdkConfig, signerOrProvider, {
192
+ historyProvider: options.historyProvider,
193
+ });
194
+ return new Kasu(
195
+ sdk,
196
+ chainConfig,
197
+ signerOrProvider,
198
+ overrides,
199
+ options.historyProvider,
200
+ );
184
201
  }
185
202
 
186
203
  /**
@@ -201,6 +218,7 @@ export class Kasu {
201
218
  chain: this._chainConfig,
202
219
  signerOrProvider: signer,
203
220
  configOverrides: this._configOverrides,
221
+ historyProvider: this._historyProvider,
204
222
  });
205
223
  }
206
224
 
@@ -1,6 +1,8 @@
1
+ import { PoolTvlHistoryEntry } from '../domain/pool-tvl';
1
2
  import { selectVisiblePools } from '../domain/pools';
2
3
  import { pickShortDescription } from '../domain/strategy-summary';
3
4
  import { DataService } from '../services/DataService/data-service';
5
+ import { PoolTvlHistoryOptions } from '../services/DataService/tvl-history';
4
6
  import {
5
7
  PoolDelegateProfileAndHistory,
6
8
  PoolOverview,
@@ -149,6 +151,30 @@ export class StrategiesFacade {
149
151
  return await this._dataService.getPerformanceFee();
150
152
  }
151
153
 
154
+ /**
155
+ * The strategy's TVL (total lent by everyone) at every cycle boundary,
156
+ * then as of now — the data behind a TVL chart. Ascending in time; the
157
+ * last point (`kind: 'current'`) equals `strategy.tvl.total` when both
158
+ * calls' reads succeed (see `PoolTvlHistoryEntry`).
159
+ *
160
+ * `tvl` is in display units of the deployment's OWN stable token (USDC on
161
+ * Base and XDC USDC, AUDD on XDC AUDD); `timestamp` is unix SECONDS.
162
+ * `{ count }` = the newest `count` epochs, `{ fromEpoch }` = from that
163
+ * epoch on; `timeoutMs` (default 30 s) and `signal` bound the call. A
164
+ * malformed option throws before any network read. See
165
+ * `DataService.getPoolTvlHistory` for the reads, the cost and the errors.
166
+ *
167
+ * ```ts
168
+ * const points = await kasu.strategies.getPoolTvlHistory(poolId, { count: 52 });
169
+ * ```
170
+ */
171
+ async getPoolTvlHistory(
172
+ poolId: string,
173
+ options: PoolTvlHistoryOptions = {},
174
+ ): Promise<PoolTvlHistoryEntry[]> {
175
+ return await this._dataService.getPoolTvlHistory(poolId, options);
176
+ }
177
+
152
178
  /**
153
179
  * Fetch a single strategy by pool ID. Returns `null` if not found.
154
180
  */
@@ -70,6 +70,16 @@ export interface ChainConfigEntry {
70
70
  * Empty on a retired deployment, which has no read-only default.
71
71
  */
72
72
  rpcUrls: string[];
73
+ /**
74
+ * RPCs that SERVE PAST STATE (archive nodes), tried IN ORDER on failure,
75
+ * used only by `getPoolTvlHistory` to read the off-chain TVL at old
76
+ * blocks. Pruned nodes refuse those reads — some answer as if the call
77
+ * reverted — so these are never `rpcUrls[0]` by assumption. Public and
78
+ * RATE-LIMITED: an integrator should pass its own archive
79
+ * `KasuOptions.historyProvider`, which wins over them. Absent on a
80
+ * retired deployment.
81
+ */
82
+ historyRpcUrls?: string[];
73
83
  /**
74
84
  * Origin of the chain's block explorer, `https://…` with NO trailing
75
85
  * slash (`https://basescan.org`). Never append a path to it by hand: build
@@ -101,6 +111,16 @@ export interface KasuOptions {
101
111
  | import('@ethersproject/providers').Provider;
102
112
  /** Override any default config value. */
103
113
  configOverrides?: Partial<SdkConfigOptions>;
114
+ /**
115
+ * A provider that SERVES PAST STATE (an archive RPC), for
116
+ * `kasu.strategies.getPoolTvlHistory`. OPTIONAL: without one, the SDK
117
+ * builds one `StaticJsonRpcProvider` per
118
+ * `configOverrides.historyRpcUrls ?? chainConfig.historyRpcUrls` entry
119
+ * (tried in order, 10 s per call). The built-in URLs are public and
120
+ * rate-limited, so pass your own archive provider in production. Kept
121
+ * across `connect(signer)`.
122
+ */
123
+ historyProvider?: import('@ethersproject/providers').Provider;
104
124
  }
105
125
 
106
126
  // ---------------------------------------------------------------------------
@@ -1,8 +1,10 @@
1
1
  import { Provider } from '@ethersproject/providers';
2
2
 
3
3
  import { deriveRequestState, RequestState } from '../domain/requests';
4
+ import { YieldHistoryEntry } from '../domain/yield-history';
4
5
  import { DataService } from '../services/DataService/data-service';
5
6
  import { Portfolio } from '../services/Portfolio/portfolio';
7
+ import { YieldHistoryOptions } from '../services/Portfolio/yield-history';
6
8
  import { UserRequest } from '../services/UserLending/types';
7
9
  import { UserLending } from '../services/UserLending/user-lending';
8
10
 
@@ -39,9 +41,8 @@ export class PortfolioFacade {
39
41
  ): Promise<UserPositions> {
40
42
  const currentEpoch = await this._userLending.getCurrentEpoch();
41
43
 
42
- const poolOverviews = await this._dataService.getPoolOverview(
43
- currentEpoch,
44
- );
44
+ const poolOverviews =
45
+ await this._dataService.getPoolOverview(currentEpoch);
45
46
 
46
47
  const pools = await this._portfolio.getPortfolioLendingData(
47
48
  userAddress,
@@ -59,6 +60,42 @@ export class PortfolioFacade {
59
60
  return { pools, summary };
60
61
  }
61
62
 
63
+ /**
64
+ * The lender's settled yield per cycle, per pool and tranche: what each
65
+ * close credited them, NET of the performance fee (the chain credits it
66
+ * net), in display units of the deployment's own stable token (AUDD on
67
+ * XDC AUDD). `epochEnd` is unix seconds. Net only — no gross is offered.
68
+ *
69
+ * Validates `options` before any read (`RangeError` on a malformed one).
70
+ *
71
+ * ```ts
72
+ * const entries = await kasu.portfolio.getYieldHistory('0xUser...', { count: 52 });
73
+ * const allStrategies = aggregateYieldHistory(entries);
74
+ * const oneStrategy = aggregateYieldHistory(entries, { poolId });
75
+ * ```
76
+ *
77
+ * `getPositions`' `totalYieldEarningsLastEpoch` for a pool equals that
78
+ * pool's bar for the epoch of its most recently APPLIED interest — which
79
+ * is older than the last cycle for a pool that has not cleared since (the
80
+ * bar carries that older `epochId`). A lender who held nothing at that
81
+ * epoch has no bar there and a figure of 0; their newest bar is from
82
+ * when they last held shares.
83
+ *
84
+ * For a fixed-term lock holder the bars can sum to slightly more than the
85
+ * lifetime interest (up to about 0.2 %), because each top-up is valued at
86
+ * today's share price — show "earned to date" from the lifetime figure,
87
+ * never from the sum of the bars.
88
+ *
89
+ * `[]` for a lender with no history, and on a LITE deployment whose
90
+ * subgraph lacks the entities; on a full deployment that error throws.
91
+ */
92
+ async getYieldHistory(
93
+ userAddress: string,
94
+ options?: YieldHistoryOptions,
95
+ ): Promise<YieldHistoryEntry[]> {
96
+ return await this._portfolio.getYieldHistory(userAddress, options);
97
+ }
98
+
62
99
  /**
63
100
  * Fetch a user's full transaction history (deposits, withdrawals, cancellations).
64
101
  */
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  // Core SDK class (low-level service access)
3
3
  // ---------------------------------------------------------------------------
4
4
  export { KasuSdk } from './kasu-sdk';
5
+ export type { KasuSdkOptions } from './kasu-sdk';
5
6
 
6
7
  // ---------------------------------------------------------------------------
7
8
  // Re-export config types
@@ -42,6 +43,16 @@ export type {
42
43
  PortfolioTrancheDepositDetails,
43
44
  } from './services/Portfolio/types';
44
45
 
46
+ export type { YieldHistoryOptions } from './services/Portfolio/yield-history';
47
+
48
+ export type { PoolTvlHistoryOptions } from './services/DataService/tvl-history';
49
+ export {
50
+ PastStateUnavailableError,
51
+ PoolTvlHistoryAbortedError,
52
+ PoolTvlHistoryTimeoutError,
53
+ } from './services/DataService/tvl-history';
54
+ export { NO_HISTORY_PROVIDER_MESSAGE } from './services/DataService/data-service';
55
+
45
56
  export type {
46
57
  UserLock,
47
58
  UserBonusData,
package/src/kasu-sdk.ts CHANGED
@@ -9,6 +9,15 @@ import { Portfolio } from './services/Portfolio/portfolio';
9
9
  import { Swapper } from './services/Swapper/swapper';
10
10
  import { UserLending } from './services/UserLending/user-lending';
11
11
 
12
+ /** Optional providers beyond the main signer-or-provider. */
13
+ export interface KasuSdkOptions {
14
+ /**
15
+ * A provider that SERVES PAST STATE (an archive RPC), for
16
+ * `DataService.getPoolTvlHistory`. Wins over `SdkConfig.historyRpcUrls`.
17
+ */
18
+ historyProvider?: Provider;
19
+ }
20
+
12
21
  export class KasuSdk {
13
22
  private readonly _graphClient: GraphQLClient;
14
23
  public readonly Locking: KSULocking;
@@ -16,11 +25,19 @@ export class KasuSdk {
16
25
  public readonly UserLending: UserLending;
17
26
  public readonly Portfolio: Portfolio;
18
27
  public readonly Swapper: Swapper;
19
- constructor(config: SdkConfig, signerOrProvider: Provider | Signer) {
28
+ constructor(
29
+ config: SdkConfig,
30
+ signerOrProvider: Provider | Signer,
31
+ options: KasuSdkOptions = {},
32
+ ) {
20
33
  this._graphClient = new GraphQLClient(config.subgraphUrl);
21
34
  this.Locking = new KSULocking(config, signerOrProvider);
22
35
  this.UserLending = new UserLending(config, signerOrProvider);
23
- this.DataService = new DataService(config, signerOrProvider);
36
+ this.DataService = new DataService(
37
+ config,
38
+ signerOrProvider,
39
+ options.historyProvider,
40
+ );
24
41
  this.Portfolio = new Portfolio(config, signerOrProvider);
25
42
  this.Swapper = new Swapper();
26
43
  }
package/src/sdk-config.ts CHANGED
@@ -50,6 +50,22 @@ export interface SdkConfigOptions {
50
50
  * @default 6
51
51
  */
52
52
  stableAssetDecimals?: number;
53
+ /**
54
+ * RPC URLs that SERVE PAST STATE (archive nodes), tried IN ORDER on
55
+ * failure, used only by `DataService.getPoolTvlHistory` to read the
56
+ * off-chain TVL at old blocks. A pruned node refuses those reads (some
57
+ * answer as if the call reverted), so these are never the default read
58
+ * RPC. An explicit `historyProvider` passed to `KasuSdk` / `Kasu.create`
59
+ * wins over them. The public defaults are rate-limited: an integrator
60
+ * should pass its own archive provider.
61
+ * @default [] — none
62
+ */
63
+ historyRpcUrls?: string[];
64
+ /**
65
+ * The chain id the history providers are pinned to (no `eth_chainId`
66
+ * round trip). `Kasu.create` sets it from the chain config.
67
+ */
68
+ chainId?: number;
53
69
  }
54
70
 
55
71
  export class SdkConfig {
@@ -61,6 +77,10 @@ export class SdkConfig {
61
77
  isLiteDeployment: boolean;
62
78
  poolMetadataMapping: Record<string, string>;
63
79
  stableAssetDecimals: number;
80
+ /** See `SdkConfigOptions.historyRpcUrls`. `[]` when none. */
81
+ historyRpcUrls: string[];
82
+ /** See `SdkConfigOptions.chainId`. */
83
+ chainId: number | undefined;
64
84
 
65
85
  constructor(options: SdkConfigOptions) {
66
86
  this.subgraphUrl = options.subgraphUrl;
@@ -77,5 +97,7 @@ export class SdkConfig {
77
97
  this.isLiteDeployment = options.isLiteDeployment ?? false;
78
98
  this.poolMetadataMapping = options.poolMetadataMapping ?? {};
79
99
  this.stableAssetDecimals = options.stableAssetDecimals ?? 6;
100
+ this.historyRpcUrls = [...(options.historyRpcUrls ?? [])];
101
+ this.chainId = options.chainId;
80
102
  }
81
103
  }