@kasufinance/kasu-sdk 2.15.0 → 2.16.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.
@@ -15,6 +15,24 @@
15
15
  * on Base and XDC USDC, AUDD on XDC AUDD) — never converted to USD here;
16
16
  * shares are display units of tranche shares, as the subgraph's BigDecimals.
17
17
  */
18
+ /**
19
+ * The largest change of a lock's shares, as a fraction of its shares before
20
+ * the epoch, that one clearing's fixed-term top-up can make. The top-up is
21
+ * the fixed rate's difference from the variable one on one week's balance
22
+ * (`LendingPool._applyFixedRateInterests`) — a fraction of a percent at any
23
+ * real rate — so 5 % is far above every genuine top-up and far below any
24
+ * accounting fault. A lock-share change outside it is NOT interest: it is
25
+ * left out of the yield (a share movement is never booked as yield). The one
26
+ * seen on chain is a lock whose recorded shares were set to zero at a
27
+ * clearing while its shares stayed in the fixed-term contract.
28
+ */
29
+ export declare const MAX_TOP_UP_SHARE_FRACTION = 0.05;
30
+ /**
31
+ * True when `sharesBefore → sharesAt` is a change one clearing's fixed-term
32
+ * top-up can make (`MAX_TOP_UP_SHARE_FRACTION`). A lock with no shares
33
+ * before has no top-up at all.
34
+ */
35
+ export declare function isPlausibleTopUp(sharesBefore: number, sharesAt: number): boolean;
18
36
  /** One fixed-term lock inside a tranche position, at one epoch. */
19
37
  export interface EpochLockShares {
20
38
  /**
@@ -68,7 +86,13 @@ export interface EpochTrancheYield {
68
86
  * ```
69
87
  *
70
88
  * A zero denominator contributes 0 (a tranche or a lender with no shares
71
- * earned nothing at that close), never `NaN`.
89
+ * earned nothing at that close), never `NaN`. So does a NEGATIVE share count:
90
+ * the subgraph's per-lender shares are a ledger of accepted deposits and
91
+ * withdrawals, and a wallet whose shares arrived some other way (moved to it
92
+ * from another wallet) withdraws more than the ledger ever credited it. That
93
+ * position is not one the ledger can describe, and its negative count is not
94
+ * a negative yield. A lock-share change no top-up can make
95
+ * (`isPlausibleTopUp`) contributes no top-up.
72
96
  */
73
97
  export declare function epochTrancheYield(input: EpochTrancheYieldInput): EpochTrancheYield;
74
98
  /** One lender's settled yield in one tranche at one epoch. */
@@ -15,6 +15,29 @@
15
15
  * on Base and XDC USDC, AUDD on XDC AUDD) — never converted to USD here;
16
16
  * shares are display units of tranche shares, as the subgraph's BigDecimals.
17
17
  */
18
+ /**
19
+ * The largest change of a lock's shares, as a fraction of its shares before
20
+ * the epoch, that one clearing's fixed-term top-up can make. The top-up is
21
+ * the fixed rate's difference from the variable one on one week's balance
22
+ * (`LendingPool._applyFixedRateInterests`) — a fraction of a percent at any
23
+ * real rate — so 5 % is far above every genuine top-up and far below any
24
+ * accounting fault. A lock-share change outside it is NOT interest: it is
25
+ * left out of the yield (a share movement is never booked as yield). The one
26
+ * seen on chain is a lock whose recorded shares were set to zero at a
27
+ * clearing while its shares stayed in the fixed-term contract.
28
+ */
29
+ export const MAX_TOP_UP_SHARE_FRACTION = 0.05;
30
+ /**
31
+ * True when `sharesBefore → sharesAt` is a change one clearing's fixed-term
32
+ * top-up can make (`MAX_TOP_UP_SHARE_FRACTION`). A lock with no shares
33
+ * before has no top-up at all.
34
+ */
35
+ export function isPlausibleTopUp(sharesBefore, sharesAt) {
36
+ if (!(sharesBefore > 0))
37
+ return sharesAt === sharesBefore;
38
+ return (Math.abs(sharesAt - sharesBefore) <=
39
+ sharesBefore * MAX_TOP_UP_SHARE_FRACTION);
40
+ }
18
41
  /**
19
42
  * The ONE per-epoch formula. `Portfolio.getPortfolioLendingData` runs it for
20
43
  * the last settled epoch and `getYieldHistory` for every epoch of a window, so
@@ -29,24 +52,31 @@
29
52
  * ```
30
53
  *
31
54
  * A zero denominator contributes 0 (a tranche or a lender with no shares
32
- * earned nothing at that close), never `NaN`.
55
+ * earned nothing at that close), never `NaN`. So does a NEGATIVE share count:
56
+ * the subgraph's per-lender shares are a ledger of accepted deposits and
57
+ * withdrawals, and a wallet whose shares arrived some other way (moved to it
58
+ * from another wallet) withdraws more than the ledger ever credited it. That
59
+ * position is not one the ledger can describe, and its negative count is not
60
+ * a negative yield. A lock-share change no top-up can make
61
+ * (`isPlausibleTopUp`) contributes no top-up.
33
62
  */
34
63
  export function epochTrancheYield(input) {
35
- const base = input.trancheShares === 0
36
- ? 0
37
- : (input.epochInterest * input.userShares) / input.trancheShares;
64
+ const base = input.trancheShares > 0 && input.userShares > 0
65
+ ? (input.epochInterest * input.userShares) / input.trancheShares
66
+ : 0;
38
67
  let variable = base;
39
68
  const locks = input.locks.map((lock) => {
40
69
  if (!lock.active)
41
70
  return 0;
42
- const topUp = input.trancheSharesNow === 0
43
- ? 0
44
- : ((lock.sharesAtEpoch - lock.sharesBeforeEpoch) *
71
+ const topUp = input.trancheSharesNow > 0 &&
72
+ isPlausibleTopUp(lock.sharesBeforeEpoch, lock.sharesAtEpoch)
73
+ ? ((lock.sharesAtEpoch - lock.sharesBeforeEpoch) *
45
74
  input.trancheBalanceNow) /
46
- input.trancheSharesNow;
47
- const lockBase = input.userShares === 0
48
- ? 0
49
- : (base * lock.sharesBeforeEpoch) / input.userShares;
75
+ input.trancheSharesNow
76
+ : 0;
77
+ const lockBase = input.userShares > 0
78
+ ? (base * lock.sharesBeforeEpoch) / input.userShares
79
+ : 0;
50
80
  variable -= lockBase;
51
81
  return lockBase + topUp;
52
82
  });
@@ -1 +1 @@
1
- {"version":3,"file":"yield-history.js","sourceRoot":"","sources":["../../src/domain/yield-history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AA4CH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAC7B,KAA6B;IAE7B,MAAM,IAAI,GACN,KAAK,CAAC,aAAa,KAAK,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,aAAa,CAAC;IAEzE,IAAI,QAAQ,GAAG,IAAI,CAAC;IACpB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACnC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,CAAC;QAE3B,MAAM,KAAK,GACP,KAAK,CAAC,gBAAgB,KAAK,CAAC;YACxB,CAAC,CAAC,CAAC;YACH,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,iBAAiB,CAAC;gBAC1C,KAAK,CAAC,iBAAiB,CAAC;gBAC5B,KAAK,CAAC,gBAAgB,CAAC;QAEjC,MAAM,QAAQ,GACV,KAAK,CAAC,UAAU,KAAK,CAAC;YAClB,CAAC,CAAC,CAAC;YACH,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC;QAE7D,QAAQ,IAAI,QAAQ,CAAC;QAErB,OAAO,QAAQ,GAAG,KAAK,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,GAAG,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC;IAE1E,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AACtC,CAAC;AAwCD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,qBAAqB,CACjC,OAAqC,EACrC,SAA6B,EAAE;;IAE/B,MAAM,MAAM,GAAG,MAAA,MAAM,CAAC,MAAM,0CAAE,WAAW,EAAE,CAAC;IAC5C,MAAM,SAAS,GAAG,MAAA,MAAM,CAAC,SAAS,0CAAE,WAAW,EAAE,CAAC;IAClD,MAAM,OAAO,GAAG,IAAI,GAAG,EAA6B,CAAC;IAErD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;YAChE,SAAS;QACb,CAAC;QACD,IACI,SAAS,KAAK,SAAS;YACvB,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,SAAS,EAC7C,CAAC;YACC,SAAS;QACb,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,KAAK,EAAE,CAAC;YACR,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC;YACjC,SAAS;QACb,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE;YACvB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SAC3B,CAAC,CAAC;IACP,CAAC;IAED,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;AACvE,CAAC"}
1
+ {"version":3,"file":"yield-history.js","sourceRoot":"","sources":["../../src/domain/yield-history.ts"],"names":[],"mappings":"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"}
@@ -77,7 +77,12 @@ export declare class Portfolio {
77
77
  * Cost: three parallel `SystemVariables` reads and the positions query in
78
78
  * one round, then one subgraph request per page of the longest series
79
79
  * (1000 rows a page) — normally two subgraph round trips in all, never one
80
- * per epoch.
80
+ * per epoch — alongside one `LendingPool.userBalance` call per pool at
81
+ * the subgraph's block. A pool whose ledger credits more than the wallet
82
+ * holds at that block (shares moved away outside a withdrawal) is left
83
+ * out; if the node says it cannot serve that block, `latest` is read
84
+ * and nothing is. Any other failure of those reads (a rate limit, a
85
+ * network error) THROWS: the history needs a working RPC.
81
86
  *
82
87
  * On a LITE deployment, a subgraph that lacks the entities (a validation
83
88
  * error naming them) yields `[]`; on a full deployment that error throws,
@@ -87,6 +92,54 @@ export declare class Portfolio {
87
92
  * network read.
88
93
  */
89
94
  getYieldHistory(userAddress: string, options?: YieldHistoryOptions): Promise<YieldHistoryEntry[]>;
95
+ /**
96
+ * Read the chain at the subgraph's block, so the chain and the ledger
97
+ * describe the same state: after every clearing the subgraph runs a few
98
+ * blocks behind, and a `latest` read would then disagree with an
99
+ * honest ledger (interest moved every balance, deposits were accepted)
100
+ * and book that difference as a ledger fault.
101
+ *
102
+ * Only when the node says it cannot serve that block (pruned, or behind
103
+ * the subgraph: `isBlockUnavailableError`) does it fall back to `latest`,
104
+ * and it says so (`blockTag: undefined`): unpinned figures are never
105
+ * compared with the ledger, and `getPortfolioLendingData` marks the pools
106
+ * `chainPinned: false`. Any other failure — a rate limit, a timeout, a
107
+ * network error — is thrown, never turned into an unpinned read.
108
+ */
109
+ private readChainAt;
110
+ /**
111
+ * One pool's balances: the ledger's (the subgraph counts a lender's
112
+ * shares as accepted deposits − accepted withdrawals ± top-ups) checked
113
+ * against the chain's `LendingPool.userBalance` at the SAME block.
114
+ *
115
+ * The ledger does not follow shares that move between wallets any other
116
+ * way (an administrative move of a position is the case seen on chain):
117
+ * the receiving wallet goes negative as it withdraws, the sending one
118
+ * keeps shares it no longer holds. Where the two disagree at that block —
119
+ * or any ledger balance is negative — the tranche balances are read from
120
+ * the chain at that block (`userActiveAssets`, one call per tranche), the
121
+ * tranche's ledger `yieldEarned` moves by the same difference, and the
122
+ * pool carries `ledgerMismatch`. Unpinned (the node could not serve the
123
+ * block), only a negative ledger balance is corrected. A pool that agrees
124
+ * — every ordinary lender — costs nothing extra.
125
+ *
126
+ * Lifetime figures stay ledger figures: they cannot tell moved principal
127
+ * from interest.
128
+ */
129
+ private reconcilePool;
130
+ /**
131
+ * The last-epoch figures of positions whose newest interest is OLDER than
132
+ * the recent window. Pools clear independently, and some clear only when
133
+ * the originator repays, so a pool's last cycle can be weeks back: it is
134
+ * priced at THAT epoch, from a history read of just those positions over
135
+ * just those epochs, through the same `positionEpochYield` — so it equals
136
+ * the history's newest bar.
137
+ *
138
+ * A lender who has since left the tranche (no ledger shares now) gets no
139
+ * figure: an old pool's last cycle is not theirs to show. Costs nothing
140
+ * when no position qualifies.
141
+ */
142
+ private staleLastEpochYields;
90
143
  /**
91
144
  * A money figure as a plain decimal string. Below a millionth of a token
92
145
  * (the stable asset's own precision) is float noise, never money, so it