@kasufinance/kasu-sdk 2.4.2 → 2.5.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 (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +189 -253
  3. package/dist/bundle.cjs.js +4775 -1794
  4. package/dist/bundle.esm.js +4764 -1816
  5. package/dist/domain/deposit-bounds.d.ts +62 -0
  6. package/dist/domain/deposit-bounds.js +84 -0
  7. package/dist/domain/deposit-bounds.js.map +1 -0
  8. package/dist/domain/deposit-bounds.test.js +135 -0
  9. package/dist/domain/deposit-bounds.test.js.map +1 -0
  10. package/dist/domain/index.d.ts +26 -0
  11. package/dist/domain/index.js +23 -0
  12. package/dist/domain/index.js.map +1 -0
  13. package/dist/domain/partners.d.ts +34 -0
  14. package/dist/domain/partners.js +43 -0
  15. package/dist/domain/partners.js.map +1 -0
  16. package/dist/domain/partners.test.d.ts +1 -0
  17. package/dist/domain/partners.test.js +53 -0
  18. package/dist/domain/partners.test.js.map +1 -0
  19. package/dist/domain/pools.d.ts +70 -0
  20. package/dist/domain/pools.js +134 -0
  21. package/dist/domain/pools.js.map +1 -0
  22. package/dist/domain/pools.test.d.ts +1 -0
  23. package/dist/domain/pools.test.js +184 -0
  24. package/dist/domain/pools.test.js.map +1 -0
  25. package/dist/domain/rates.d.ts +83 -0
  26. package/dist/domain/rates.js +120 -0
  27. package/dist/domain/rates.js.map +1 -0
  28. package/dist/domain/rates.test.d.ts +1 -0
  29. package/dist/domain/rates.test.js +181 -0
  30. package/dist/domain/rates.test.js.map +1 -0
  31. package/dist/domain/tranche-display-name.d.ts +17 -0
  32. package/dist/domain/tranche-display-name.js +30 -0
  33. package/dist/domain/tranche-display-name.js.map +1 -0
  34. package/dist/domain/tranche-display-name.test.d.ts +1 -0
  35. package/dist/domain/tranche-display-name.test.js +58 -0
  36. package/dist/domain/tranche-display-name.test.js.map +1 -0
  37. package/dist/domain/tranches.d.ts +89 -0
  38. package/dist/domain/tranches.js +150 -0
  39. package/dist/domain/tranches.js.map +1 -0
  40. package/dist/domain/tranches.test.d.ts +1 -0
  41. package/dist/domain/tranches.test.js +206 -0
  42. package/dist/domain/tranches.test.js.map +1 -0
  43. package/dist/facade/chain-configs.js +54 -0
  44. package/dist/facade/chain-configs.js.map +1 -1
  45. package/dist/facade/config.test.d.ts +1 -0
  46. package/dist/facade/config.test.js +216 -0
  47. package/dist/facade/config.test.js.map +1 -0
  48. package/dist/facade/deposits.d.ts +22 -1
  49. package/dist/facade/deposits.js +24 -1
  50. package/dist/facade/deposits.js.map +1 -1
  51. package/dist/facade/facade.test.d.ts +1 -0
  52. package/dist/{tests → facade}/facade.test.js +10 -5
  53. package/dist/facade/facade.test.js.map +1 -0
  54. package/dist/facade/index.d.ts +2 -1
  55. package/dist/facade/index.js +2 -0
  56. package/dist/facade/index.js.map +1 -1
  57. package/dist/facade/kasu.d.ts +49 -8
  58. package/dist/facade/kasu.js +117 -13
  59. package/dist/facade/kasu.js.map +1 -1
  60. package/dist/facade/strategies.d.ts +25 -0
  61. package/dist/facade/strategies.js +36 -0
  62. package/dist/facade/strategies.js.map +1 -1
  63. package/dist/facade/types.d.ts +57 -2
  64. package/dist/facade/unused-pool-ids.d.ts +31 -0
  65. package/dist/facade/unused-pool-ids.js +57 -0
  66. package/dist/facade/unused-pool-ids.js.map +1 -0
  67. package/dist/index.d.ts +1 -0
  68. package/dist/index.js +5 -0
  69. package/dist/index.js.map +1 -1
  70. package/dist/sdk-config.d.ts +11 -0
  71. package/dist/sdk-config.js +8 -1
  72. package/dist/sdk-config.js.map +1 -1
  73. package/dist/services/DataService/data-service.d.ts +5 -0
  74. package/dist/services/DataService/data-service.js +7 -2
  75. package/dist/services/DataService/data-service.js.map +1 -1
  76. package/dist/services/Locking/calculate-apy.test.d.ts +1 -0
  77. package/dist/services/Locking/calculate-apy.test.js +41 -0
  78. package/dist/services/Locking/calculate-apy.test.js.map +1 -0
  79. package/dist/services/Locking/locking.d.ts +14 -0
  80. package/dist/services/Locking/locking.js +16 -2
  81. package/dist/services/Locking/locking.js.map +1 -1
  82. package/dist/tests/sample.test.js +46 -25
  83. package/dist/tests/sample.test.js.map +1 -1
  84. package/package.json +43 -12
  85. package/src/domain/deposit-bounds.test.ts +200 -0
  86. package/src/domain/deposit-bounds.ts +118 -0
  87. package/src/domain/index.ts +70 -0
  88. package/src/domain/partners.test.ts +83 -0
  89. package/src/domain/partners.ts +66 -0
  90. package/src/domain/pools.test.ts +260 -0
  91. package/src/domain/pools.ts +150 -0
  92. package/src/domain/rates.test.ts +254 -0
  93. package/src/domain/rates.ts +120 -0
  94. package/src/domain/tranche-display-name.test.ts +96 -0
  95. package/src/domain/tranche-display-name.ts +34 -0
  96. package/src/domain/tranches.test.ts +300 -0
  97. package/src/domain/tranches.ts +177 -0
  98. package/src/facade/chain-configs.ts +55 -0
  99. package/src/facade/config.test.ts +265 -0
  100. package/src/facade/deposits.ts +25 -0
  101. package/src/{tests → facade}/facade.test.ts +10 -5
  102. package/src/facade/index.ts +4 -0
  103. package/src/facade/kasu.ts +155 -12
  104. package/src/facade/strategies.ts +36 -0
  105. package/src/facade/types.ts +60 -2
  106. package/src/facade/unused-pool-ids.ts +59 -0
  107. package/src/index.ts +6 -0
  108. package/src/sdk-config.ts +19 -1
  109. package/src/services/DataService/data-service.ts +7 -2
  110. package/src/services/Locking/calculate-apy.test.ts +52 -0
  111. package/src/services/Locking/locking.ts +16 -2
  112. package/src/tests/sample.test.ts +54 -33
  113. package/.eslintignore +0 -3
  114. package/.eslintrc.json +0 -90
  115. package/.github/workflows/ci.yml +0 -39
  116. package/.github/workflows/gitleaks.yml +0 -22
  117. package/.github/workflows/main.yml +0 -41
  118. package/.gitleaksignore +0 -19
  119. package/.prettierignore +0 -3
  120. package/.prettierrc +0 -28
  121. package/CLAUDE.md +0 -410
  122. package/copy-abi.ps1 +0 -15
  123. package/dist/tests/facade.test.js.map +0 -1
  124. package/index.ts +0 -2
  125. package/jest-config.ts +0 -9
  126. package/jest.config.js +0 -8
  127. package/rollup.config.js +0 -45
  128. package/tsconfig.json +0 -27
  129. /package/dist/{tests/facade.test.d.ts → domain/deposit-bounds.test.d.ts} +0 -0
@@ -0,0 +1,181 @@
1
+ import * as fc from 'fast-check';
2
+ import { apyToEpochRate, epochRateToApy, EPOCHS_IN_YEAR, netEffectiveApy, } from './rates';
3
+ /**
4
+ * Ported from kasu-ui `src/features/lending/lib/interest-rate.test.ts`. The
5
+ * `formatEffectiveRate` cases stay there — they assert copy and locale, which
6
+ * this layer does not own.
7
+ */
8
+ /** The live performance fee on all deployments (subgraph units: 10 = ten percent). */
9
+ const LIVE_FEE_PERCENT = 10;
10
+ /**
11
+ * Spec §9 — every distinct per-epoch rate configured on Base, read on-chain
12
+ * 2026-09-01, with the NET Effective Interest Rate each must display at the
13
+ * live 10% fee. This table IS the review sanity check: a correct
14
+ * implementation reproduces the whole column, including the 30% crossover row
15
+ * (26.30% gross nominal → 26.64% net APY) which moves UP while every other row
16
+ * moves down, because the two errors in the old display — no fee deducted, and
17
+ * a spurious de-compounding — pull in opposite directions and cross over near
18
+ * 28%.
19
+ */
20
+ const GROUND_TRUTH = [
21
+ { grossApy: 0.1, netPercent: 8.96 },
22
+ { grossApy: 0.105, netPercent: 9.4 },
23
+ { grossApy: 0.12, netPercent: 10.74 },
24
+ { grossApy: 0.14, netPercent: 12.52 },
25
+ { grossApy: 0.15, netPercent: 13.41 },
26
+ { grossApy: 0.16, netPercent: 14.29 },
27
+ { grossApy: 0.2, netPercent: 17.84 },
28
+ { grossApy: 0.22, netPercent: 19.6 },
29
+ { grossApy: 0.3, netPercent: 26.64 },
30
+ ];
31
+ describe('netEffectiveApy — spec §9 ground truth', () => {
32
+ it.each(GROUND_TRUTH)('gross $grossApy at a 10% fee is $netPercent% net', ({ grossApy, netPercent }) => {
33
+ expect(netEffectiveApy(grossApy, LIVE_FEE_PERCENT) * 100).toBeCloseTo(netPercent, 2);
34
+ });
35
+ it('reproduces the 30% crossover — the net APY is ABOVE the old gross nominal figure', () => {
36
+ // Old display: 52.17857 · ((1.30)^(1/52.17857) − 1) = 26.30%. New: 26.64%.
37
+ const oldGrossNominal = EPOCHS_IN_YEAR * (Math.pow(1.3, (1 / EPOCHS_IN_YEAR)) - 1);
38
+ expect(oldGrossNominal * 100).toBeCloseTo(26.3, 1);
39
+ expect(netEffectiveApy(0.3, LIVE_FEE_PERCENT)).toBeGreaterThan(oldGrossNominal);
40
+ });
41
+ });
42
+ describe('netEffectiveApy — units (spec §2.3)', () => {
43
+ it('reads feePercent as a PERCENTAGE: 22% gross at fee 10 is ≈19.602%', () => {
44
+ expect(netEffectiveApy(0.22, 10)).toBeCloseTo(0.19602, 5);
45
+ });
46
+ it('does not silently accept the 0..1 fraction — 0.1 is a 0.1% fee, not 10%', () => {
47
+ const wrongUnit = netEffectiveApy(0.22, 0.1);
48
+ const correct = netEffectiveApy(0.22, 10);
49
+ // It is a finite, in-range number (we cannot reject it — 0.1% is a
50
+ // legal fee), so the guard cannot catch this. What protects us is that
51
+ // it is MATERIALLY different: ~2.37pp too high, not a plausible
52
+ // near-miss that could sit on screen unnoticed.
53
+ expect(wrongUnit).toBeCloseTo(0.21976, 5);
54
+ expect(wrongUnit - correct).toBeGreaterThan(0.02);
55
+ });
56
+ it('rejects the RAW subgraph integer (1000 = 10%, pre-integerToPercentage2)', () => {
57
+ expect(netEffectiveApy(0.22, 1000)).toBeNaN();
58
+ });
59
+ it('rejects a fee outside 0..100', () => {
60
+ expect(netEffectiveApy(0.22, 101)).toBeNaN();
61
+ expect(netEffectiveApy(0.22, -1)).toBeNaN();
62
+ expect(netEffectiveApy(0.22, NaN)).toBeNaN();
63
+ expect(netEffectiveApy(0.22, Infinity)).toBeNaN();
64
+ });
65
+ it('accepts the domain edges', () => {
66
+ expect(netEffectiveApy(0.22, 0)).toBe(0.22);
67
+ // A 100% fee leaves the lender nothing — 0, not NaN: the fee is
68
+ // knowable and the answer is "you earn zero", which consumers render
69
+ // as "—".
70
+ expect(netEffectiveApy(0.22, 100)).toBe(0);
71
+ });
72
+ it('returns NaN for a non-finite or negative gross APY (callers render "—")', () => {
73
+ expect(netEffectiveApy(NaN, 10)).toBeNaN();
74
+ expect(netEffectiveApy(Infinity, 10)).toBeNaN();
75
+ expect(netEffectiveApy(-Infinity, 10)).toBeNaN();
76
+ expect(netEffectiveApy(-0.01, 10)).toBeNaN();
77
+ });
78
+ });
79
+ // The FULL in-domain range, down to and including zero and the denormals. The
80
+ // invariants below are the reason `netEffectiveApy` is written with
81
+ // `log1p`/`expm1`: the direct `(1 + x) ** n - 1` form breaks the `net <= gross`
82
+ // invariant for tiny rates, and the property runner finds that in a few hundred
83
+ // cases. Narrowing this arb would hide it.
84
+ const grossApyArb = fc.double({
85
+ min: 0,
86
+ max: 5,
87
+ noNaN: true,
88
+ noDefaultInfinity: true,
89
+ });
90
+ const feePercentArb = fc.double({
91
+ min: 0,
92
+ max: 100,
93
+ noNaN: true,
94
+ noDefaultInfinity: true,
95
+ });
96
+ /**
97
+ * A fee as the subgraph can actually express one: `integerToPercentage2`
98
+ * divides a uint by 100, so every reachable value is a 2dp multiple of 0.01 in
99
+ * 0..100. Used only for the STRICT-decrease property, which cannot hold for a
100
+ * fee of 1e-300 percent — `1 − feePercent/100` rounds to exactly 1 and the net
101
+ * rate is the gross one. That is arithmetic, not a defect, and no such fee
102
+ * exists.
103
+ */
104
+ const realisticFeePercentArb = fc
105
+ .integer({ min: 1, max: 10000 })
106
+ .map((n) => n / 100);
107
+ describe('netEffectiveApy — invariants', () => {
108
+ it('never returns more than the gross APY it came from', () => {
109
+ fc.assert(fc.property(grossApyArb, feePercentArb, (grossApy, feePercent) => {
110
+ expect(netEffectiveApy(grossApy, feePercent)).toBeLessThanOrEqual(grossApy);
111
+ }));
112
+ });
113
+ it('equals the gross APY EXACTLY when the fee is zero', () => {
114
+ fc.assert(
115
+ // Exact equality, not "close to": if the fee is ever set to zero
116
+ // on-chain, the rate must be the SDK's own APY, with no float drift
117
+ // from the de-compound/re-compound round trip.
118
+ fc.property(grossApyArb, (grossApy) => {
119
+ expect(netEffectiveApy(grossApy, 0)).toBe(grossApy);
120
+ }));
121
+ });
122
+ it('is STRICTLY below the gross APY for any real (2dp) non-zero fee', () => {
123
+ fc.assert(fc.property(grossApyArb, realisticFeePercentArb, (grossApy, feePercent) => {
124
+ if (grossApy === 0)
125
+ return;
126
+ expect(netEffectiveApy(grossApy, feePercent)).toBeLessThan(grossApy);
127
+ }));
128
+ });
129
+ it('decreases monotonically as the fee rises', () => {
130
+ fc.assert(fc.property(grossApyArb, feePercentArb, feePercentArb, (grossApy, a, b) => {
131
+ const [lo, hi] = a <= b ? [a, b] : [b, a];
132
+ expect(netEffectiveApy(grossApy, hi)).toBeLessThanOrEqual(netEffectiveApy(grossApy, lo));
133
+ }));
134
+ });
135
+ it('increases monotonically as the gross APY rises', () => {
136
+ fc.assert(fc.property(grossApyArb, grossApyArb, feePercentArb, (a, b, feePercent) => {
137
+ const [lo, hi] = a <= b ? [a, b] : [b, a];
138
+ expect(netEffectiveApy(hi, feePercent)).toBeGreaterThanOrEqual(netEffectiveApy(lo, feePercent));
139
+ }));
140
+ });
141
+ it('is never negative for any in-domain input', () => {
142
+ fc.assert(fc.property(grossApyArb, feePercentArb, (grossApy, feePercent) => {
143
+ expect(netEffectiveApy(grossApy, feePercent)).toBeGreaterThanOrEqual(0);
144
+ }));
145
+ });
146
+ });
147
+ describe('epochRateToApy / apyToEpochRate', () => {
148
+ it('compounds a per-epoch rate exactly as the data service always has', () => {
149
+ // The literal expression `calculateApyForTranche` carried inline.
150
+ for (const r of [0, 0.001, 0.0025, 0.003, 0.005, 0.01]) {
151
+ expect(epochRateToApy(r)).toBe(Math.pow((1 + r), EPOCHS_IN_YEAR) - 1);
152
+ }
153
+ });
154
+ it('round-trips within 1e-12 for realistic per-epoch rates', () => {
155
+ fc.assert(fc.property(
156
+ // 0.0001..0.02 per epoch ≈ 0.5%..190% APY — wider than any
157
+ // rate the platform has configured.
158
+ fc.double({
159
+ min: 0.0001,
160
+ max: 0.02,
161
+ noNaN: true,
162
+ noDefaultInfinity: true,
163
+ }), (r) => {
164
+ expect(apyToEpochRate(epochRateToApy(r))).toBeCloseTo(r, 12);
165
+ }));
166
+ });
167
+ it('inverts the APYs the platform actually quotes', () => {
168
+ for (const apy of [0.1, 0.12, 0.14, 0.16, 0.22, 0.3]) {
169
+ expect(epochRateToApy(apyToEpochRate(apy))).toBeCloseTo(apy, 12);
170
+ }
171
+ });
172
+ it('is the same de-compounding netEffectiveApy uses at a zero-ish fee', () => {
173
+ // A fee of exactly 0 short-circuits; the smallest fee that does not
174
+ // must still agree with the round trip to well under a basis point.
175
+ const gross = 0.22;
176
+ const net = netEffectiveApy(gross, 0.01);
177
+ expect(net).toBeLessThan(gross);
178
+ expect(gross - net).toBeLessThan(0.0005);
179
+ });
180
+ });
181
+ //# sourceMappingURL=rates.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rates.test.js","sourceRoot":"","sources":["../../src/domain/rates.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { PoolNameSignal } from './partners';
2
+ export declare const UPPER_MEZZANINE = "Upper Mezzanine";
3
+ /**
4
+ * User-facing display name for a tranche. Maps `Senior` → `Upper Mezzanine`
5
+ * on Apxium pools (identified by pool name, see `partners.ts`); every other
6
+ * tranche/pool combination passes through unchanged.
7
+ *
8
+ * Call this at the view-model boundary (options, cards, transaction views),
9
+ * NEVER in matching/sorting logic — `compareTrancheSeniority` and
10
+ * `pickDefaultTrancheId` rank by the raw subgraph name and must keep doing so.
11
+ * This is the one function in `domain/` that returns a user-visible string,
12
+ * and it is here rather than in each frontend so all three cannot drift.
13
+ *
14
+ * `pool` accepts the raw `PoolOverview` (`poolName`), the facade `Strategy`
15
+ * (`name`), or the name itself.
16
+ */
17
+ export declare function getTrancheDisplayName(trancheName: string, pool: PoolNameSignal): string;
@@ -0,0 +1,30 @@
1
+ import { APXIUM, getCreditOriginator } from './partners';
2
+ // Business rename (2026-07): Apxium strategies market their top retail tranche
3
+ // as "Upper Mezzanine" — the true senior position is held by the institutional
4
+ // lender (Rixon Capital), so retail lenders are never actually senior in the
5
+ // waterfall. Display-only: subgraph/SDK tranche names, ids and any
6
+ // seniority-ranking logic keep the on-chain "Senior" name.
7
+ export const UPPER_MEZZANINE = 'Upper Mezzanine';
8
+ const SENIOR = 'senior';
9
+ /**
10
+ * User-facing display name for a tranche. Maps `Senior` → `Upper Mezzanine`
11
+ * on Apxium pools (identified by pool name, see `partners.ts`); every other
12
+ * tranche/pool combination passes through unchanged.
13
+ *
14
+ * Call this at the view-model boundary (options, cards, transaction views),
15
+ * NEVER in matching/sorting logic — `compareTrancheSeniority` and
16
+ * `pickDefaultTrancheId` rank by the raw subgraph name and must keep doing so.
17
+ * This is the one function in `domain/` that returns a user-visible string,
18
+ * and it is here rather than in each frontend so all three cannot drift.
19
+ *
20
+ * `pool` accepts the raw `PoolOverview` (`poolName`), the facade `Strategy`
21
+ * (`name`), or the name itself.
22
+ */
23
+ export function getTrancheDisplayName(trancheName, pool) {
24
+ if (trancheName.trim().toLowerCase() !== SENIOR)
25
+ return trancheName;
26
+ return getCreditOriginator(pool).name === APXIUM.name
27
+ ? UPPER_MEZZANINE
28
+ : trancheName;
29
+ }
30
+ //# sourceMappingURL=tranche-display-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tranche-display-name.js","sourceRoot":"","sources":["../../src/domain/tranche-display-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAkB,MAAM,YAAY,CAAC;AAEzE,+EAA+E;AAC/E,+EAA+E;AAC/E,6EAA6E;AAC7E,mEAAmE;AACnE,2DAA2D;AAC3D,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD,MAAM,MAAM,GAAG,QAAQ,CAAC;AAExB;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CACjC,WAAmB,EACnB,IAAoB;IAEpB,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM;QAAE,OAAO,WAAW,CAAC;IACpE,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI;QACjD,CAAC,CAAC,eAAe;QACjB,CAAC,CAAC,WAAW,CAAC;AACtB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ import { getTrancheDisplayName, UPPER_MEZZANINE } from './tranche-display-name';
2
+ // Ported from kasu-ui `src/features/lending/lib/tranche-display-name.test.ts`.
3
+ describe('getTrancheDisplayName', () => {
4
+ it('renames Senior to Upper Mezzanine on Apxium pools', () => {
5
+ expect(getTrancheDisplayName('Senior', {
6
+ poolName: 'Taxation Funding (Tax Pay)',
7
+ })).toBe(UPPER_MEZZANINE);
8
+ expect(getTrancheDisplayName('Senior', {
9
+ poolName: 'Professional Fee Funding',
10
+ })).toBe(UPPER_MEZZANINE);
11
+ expect(getTrancheDisplayName('Senior', { poolName: 'Whole Ledger Funding' })).toBe(UPPER_MEZZANINE);
12
+ });
13
+ it('keeps Senior on non-Apxium (InvoiceMate) pools', () => {
14
+ expect(getTrancheDisplayName('Senior', {
15
+ poolName: 'Payment Finance (PayFi) - Clearing Houses',
16
+ })).toBe('Senior');
17
+ });
18
+ it('never touches other tranche names, on any pool', () => {
19
+ expect(getTrancheDisplayName('Mezzanine', { poolName: 'Taxation Funding' })).toBe('Mezzanine');
20
+ expect(getTrancheDisplayName('Junior', { poolName: 'Taxation Funding' })).toBe('Junior');
21
+ expect(getTrancheDisplayName('Mezzanine', {
22
+ poolName: 'Payment Finance (PayFi)',
23
+ })).toBe('Mezzanine');
24
+ });
25
+ it('matches the Senior name case-insensitively but preserves unknown names verbatim', () => {
26
+ expect(getTrancheDisplayName('SENIOR', { poolName: 'Taxation Funding' })).toBe(UPPER_MEZZANINE);
27
+ expect(getTrancheDisplayName('Senior Plus', { poolName: 'Taxation Funding' })).toBe('Senior Plus');
28
+ });
29
+ it('treats missing pool names as Apxium (matches getCreditOriginator default)', () => {
30
+ // getCreditOriginator defaults unknown pools to Apxium; the display
31
+ // name must follow the same rule or the two surfaces would disagree.
32
+ expect(getTrancheDisplayName('Senior', { poolName: '' })).toBe(UPPER_MEZZANINE);
33
+ expect(getTrancheDisplayName('Senior', {})).toBe(UPPER_MEZZANINE);
34
+ });
35
+ });
36
+ describe('getTrancheDisplayName — the widened PoolNameSignal', () => {
37
+ it('accepts the facade Strategy shape (`name`)', () => {
38
+ expect(getTrancheDisplayName('Senior', { name: 'Taxation Funding' })).toBe(UPPER_MEZZANINE);
39
+ expect(getTrancheDisplayName('Senior', {
40
+ name: 'Payment Finance (PayFi)',
41
+ })).toBe('Senior');
42
+ });
43
+ it('accepts a bare pool name', () => {
44
+ expect(getTrancheDisplayName('Senior', 'Whole Ledger Funding')).toBe(UPPER_MEZZANINE);
45
+ expect(getTrancheDisplayName('Senior', 'Payment Finance (PayFi)')).toBe('Senior');
46
+ });
47
+ it('gives the same answer for the raw and facade shapes of one pool', () => {
48
+ for (const poolName of [
49
+ 'Taxation Funding (Tax Pay)',
50
+ 'Payment Finance (PayFi)',
51
+ 'Whole Ledger Funding',
52
+ '',
53
+ ]) {
54
+ expect(getTrancheDisplayName('Senior', { poolName })).toBe(getTrancheDisplayName('Senior', { name: poolName }));
55
+ }
56
+ });
57
+ });
58
+ //# sourceMappingURL=tranche-display-name.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tranche-display-name.test.js","sourceRoot":"","sources":["../../src/domain/tranche-display-name.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { PoolOverview, TrancheData } from '../services/DataService/types';
2
+ /**
3
+ * Minimum remaining capacity (in stable-asset units) for a tranche to be
4
+ * selectable. Mirrors `MIN_CAPACITY = toBigNumber('1')` in kasu-fe-next's
5
+ * lending modal after its `formatEther`. Anything below this is rounding
6
+ * noise — the SDK's own capacity arithmetic routinely leaves sub-cent dust
7
+ * behind.
8
+ */
9
+ export declare const MIN_TRANCHE_CAPACITY = 1;
10
+ /**
11
+ * Remaining capacity as it reaches this layer. `TrancheData` (raw, from the
12
+ * data service) calls it `poolCapacity`; the facade's `StrategyTranche` calls
13
+ * the same number `availableCapacity`. Accepting both keeps ONE capacity gate
14
+ * for the whole SDK rather than a duplicate per shape.
15
+ */
16
+ export type TrancheCapacitySignal = {
17
+ poolCapacity: string;
18
+ } | {
19
+ availableCapacity: string;
20
+ };
21
+ export declare function trancheHasCapacity(tranche: TrancheCapacitySignal): boolean;
22
+ /**
23
+ * Whole-strategy capacity gate: true when the pool has tranches but none of
24
+ * them has remaining capacity. Drives a lend form's full-capacity message and
25
+ * its step-counter suppression.
26
+ */
27
+ export declare function poolAllTranchesFull(pool: PoolOverview): boolean;
28
+ export type PoolStatus = 'Live' | 'Full' | 'Coming soon';
29
+ /**
30
+ * The status CODE for a pool. A code, not copy: consumers map it to their own
31
+ * wording and locale.
32
+ */
33
+ export declare function derivePoolStatus(pool: PoolOverview): PoolStatus;
34
+ export declare function trancheRiskRank(tranche: TrancheData): number;
35
+ /**
36
+ * Comparator that orders tranches SAFEST-FIRST (Senior → Mezzanine → Junior)
37
+ * by the same name-based risk rank the default-pick uses. Ranks by the RAW
38
+ * subgraph name, so the Apxium "Upper Mezzanine" display rename never reaches
39
+ * here. Ties (incl. unrecognised names, which both rank last) keep their input
40
+ * order under a stable sort. Used by a lend dropdown to list the least-risky
41
+ * option first.
42
+ */
43
+ export declare function compareTrancheSeniority(a: TrancheData, b: TrancheData): number;
44
+ /**
45
+ * Capacity-aware default tranche pick: the LOWEST-risk tranche that still has
46
+ * capacity, so a new lender lands on the safest available option. Riskier
47
+ * tranches remain a deliberate opt-in — we must never pre-select the
48
+ * highest-risk Junior (audit 3.2). When every tranche is full we fall back to
49
+ * the lowest-risk one regardless, so a form still mounts in a known state
50
+ * (the dropdown marks it full and the submit gate blocks progress).
51
+ */
52
+ export declare function pickDefaultTrancheId(pool: PoolOverview): string;
53
+ /** A closed APY range, both bounds as 0..1 fractions. */
54
+ export interface ApyBounds {
55
+ min: number;
56
+ max: number;
57
+ }
58
+ /**
59
+ * GROSS APY range across a pool's tranches — the numeric core of kasu-ui's
60
+ * `formatTrancheApyRange`. Each `TrancheData` carries `minApy`/`maxApy`
61
+ * derived from the base rate plus any fixed-term configs (`data-service.ts`).
62
+ *
63
+ * Zero and negative values are skipped — an unset/missing rate (e.g. a tranche
64
+ * with no fixed-term config) must not drag the range down to `0–10.5%`.
65
+ * `null` when nothing usable remains, which is the caller's cue to render its
66
+ * "no rate" state rather than a confident zero.
67
+ *
68
+ * The min is taken over `minApy` and the max over `maxApy`, so a single
69
+ * unusable side collapses the whole range to `null` rather than half a range.
70
+ *
71
+ * Formatting is NOT here. The rule that two bounds printing the same figure
72
+ * collapse to one is decided on the rendered digits, so it belongs with the
73
+ * formatter that produces them (kasu-ui `format-tranche-apy.ts`).
74
+ */
75
+ export declare function trancheApyBounds(tranches: TrancheData[]): ApyBounds | null;
76
+ /**
77
+ * The same range as NET Effective Interest Rates — each bound through
78
+ * `netEffectiveApy`.
79
+ *
80
+ * FAIL CLOSED: `null` when either bound comes back non-finite or ≤ 0, so an
81
+ * out-of-domain fee or a rate-less pool can never be rendered as a confident
82
+ * figure. A caller that does not yet know the fee must not substitute `0` —
83
+ * a fee-less rate overstates the figure by up to 3.4pp with nothing on screen
84
+ * saying so; it should skip the call and render its unavailable state.
85
+ *
86
+ * @param feePercent the chain's performance fee, 0..100 — never a fraction.
87
+ * See `netEffectiveApy` for why the units matter.
88
+ */
89
+ export declare function netTrancheApyBounds(tranches: TrancheData[], feePercent: number): ApyBounds | null;
@@ -0,0 +1,150 @@
1
+ import { netEffectiveApy } from './rates';
2
+ /**
3
+ * Minimum remaining capacity (in stable-asset units) for a tranche to be
4
+ * selectable. Mirrors `MIN_CAPACITY = toBigNumber('1')` in kasu-fe-next's
5
+ * lending modal after its `formatEther`. Anything below this is rounding
6
+ * noise — the SDK's own capacity arithmetic routinely leaves sub-cent dust
7
+ * behind.
8
+ */
9
+ export const MIN_TRANCHE_CAPACITY = 1;
10
+ export function trancheHasCapacity(tranche) {
11
+ const raw = 'poolCapacity' in tranche
12
+ ? tranche.poolCapacity
13
+ : tranche.availableCapacity;
14
+ const remaining = parseFloat(raw);
15
+ return Number.isFinite(remaining) && remaining >= MIN_TRANCHE_CAPACITY;
16
+ }
17
+ /**
18
+ * Whole-strategy capacity gate: true when the pool has tranches but none of
19
+ * them has remaining capacity. Drives a lend form's full-capacity message and
20
+ * its step-counter suppression.
21
+ */
22
+ export function poolAllTranchesFull(pool) {
23
+ return pool.tranches.length > 0 && !pool.tranches.some(trancheHasCapacity);
24
+ }
25
+ /**
26
+ * The status CODE for a pool. A code, not copy: consumers map it to their own
27
+ * wording and locale.
28
+ */
29
+ export function derivePoolStatus(pool) {
30
+ if (!pool.enabled)
31
+ return 'Coming soon';
32
+ if (poolAllTranchesFull(pool))
33
+ return 'Full';
34
+ return 'Live';
35
+ }
36
+ /**
37
+ * Seniority rank of a tranche by name — LOWER is less risky. The credit
38
+ * waterfall is Senior (repaid first, lowest risk) → Mezzanine → Junior
39
+ * (repaid last, highest risk). APY is NOT a reliable proxy (a pool can price
40
+ * its Senior tranche above its Mezzanine), so risk is ranked by name.
41
+ * Unrecognised names sort last, so a known-safe tranche always wins the
42
+ * default. Ranks by the RAW subgraph name — the Apxium "Upper Mezzanine"
43
+ * rename is display-only and must never reach here (see
44
+ * `tranche-display-name.ts`).
45
+ */
46
+ const TRANCHE_RISK_RANK = {
47
+ senior: 0,
48
+ mezzanine: 1,
49
+ junior: 2,
50
+ };
51
+ // `name` is typed `string` on `TrancheData` but arrives from the subgraph, so
52
+ // it is taken as possibly missing here: an unnamed tranche ranks last rather
53
+ // than throwing on `.trim()`.
54
+ function riskRankOfName(name) {
55
+ var _a, _b;
56
+ return ((_b = TRANCHE_RISK_RANK[(_a = name === null || name === void 0 ? void 0 : name.trim().toLowerCase()) !== null && _a !== void 0 ? _a : '']) !== null && _b !== void 0 ? _b : Number.POSITIVE_INFINITY);
57
+ }
58
+ export function trancheRiskRank(tranche) {
59
+ return riskRankOfName(tranche.name);
60
+ }
61
+ /**
62
+ * Comparator that orders tranches SAFEST-FIRST (Senior → Mezzanine → Junior)
63
+ * by the same name-based risk rank the default-pick uses. Ranks by the RAW
64
+ * subgraph name, so the Apxium "Upper Mezzanine" display rename never reaches
65
+ * here. Ties (incl. unrecognised names, which both rank last) keep their input
66
+ * order under a stable sort. Used by a lend dropdown to list the least-risky
67
+ * option first.
68
+ */
69
+ export function compareTrancheSeniority(a, b) {
70
+ const ra = trancheRiskRank(a);
71
+ const rb = trancheRiskRank(b);
72
+ if (ra === rb)
73
+ return 0;
74
+ return ra < rb ? -1 : 1;
75
+ }
76
+ /**
77
+ * Capacity-aware default tranche pick: the LOWEST-risk tranche that still has
78
+ * capacity, so a new lender lands on the safest available option. Riskier
79
+ * tranches remain a deliberate opt-in — we must never pre-select the
80
+ * highest-risk Junior (audit 3.2). When every tranche is full we fall back to
81
+ * the lowest-risk one regardless, so a form still mounts in a known state
82
+ * (the dropdown marks it full and the submit gate blocks progress).
83
+ */
84
+ export function pickDefaultTrancheId(pool) {
85
+ const withCapacity = pool.tranches.filter(trancheHasCapacity);
86
+ const candidates = withCapacity.length > 0 ? withCapacity : pool.tranches;
87
+ if (candidates.length === 0)
88
+ return '';
89
+ return candidates.reduce((best, t) => trancheRiskRank(t) < trancheRiskRank(best) ? t : best).id;
90
+ }
91
+ /**
92
+ * GROSS APY range across a pool's tranches — the numeric core of kasu-ui's
93
+ * `formatTrancheApyRange`. Each `TrancheData` carries `minApy`/`maxApy`
94
+ * derived from the base rate plus any fixed-term configs (`data-service.ts`).
95
+ *
96
+ * Zero and negative values are skipped — an unset/missing rate (e.g. a tranche
97
+ * with no fixed-term config) must not drag the range down to `0–10.5%`.
98
+ * `null` when nothing usable remains, which is the caller's cue to render its
99
+ * "no rate" state rather than a confident zero.
100
+ *
101
+ * The min is taken over `minApy` and the max over `maxApy`, so a single
102
+ * unusable side collapses the whole range to `null` rather than half a range.
103
+ *
104
+ * Formatting is NOT here. The rule that two bounds printing the same figure
105
+ * collapse to one is decided on the rendered digits, so it belongs with the
106
+ * formatter that produces them (kasu-ui `format-tranche-apy.ts`).
107
+ */
108
+ export function trancheApyBounds(tranches) {
109
+ if (tranches.length === 0)
110
+ return null;
111
+ let lo = Number.POSITIVE_INFINITY;
112
+ let hi = Number.NEGATIVE_INFINITY;
113
+ for (const t of tranches) {
114
+ const min = parseFloat(t.minApy);
115
+ const max = parseFloat(t.maxApy);
116
+ if (Number.isFinite(min) && min > 0)
117
+ lo = Math.min(lo, min);
118
+ if (Number.isFinite(max) && max > 0)
119
+ hi = Math.max(hi, max);
120
+ }
121
+ if (!Number.isFinite(lo) || !Number.isFinite(hi))
122
+ return null;
123
+ return { min: lo, max: hi };
124
+ }
125
+ /**
126
+ * The same range as NET Effective Interest Rates — each bound through
127
+ * `netEffectiveApy`.
128
+ *
129
+ * FAIL CLOSED: `null` when either bound comes back non-finite or ≤ 0, so an
130
+ * out-of-domain fee or a rate-less pool can never be rendered as a confident
131
+ * figure. A caller that does not yet know the fee must not substitute `0` —
132
+ * a fee-less rate overstates the figure by up to 3.4pp with nothing on screen
133
+ * saying so; it should skip the call and render its unavailable state.
134
+ *
135
+ * @param feePercent the chain's performance fee, 0..100 — never a fraction.
136
+ * See `netEffectiveApy` for why the units matter.
137
+ */
138
+ export function netTrancheApyBounds(tranches, feePercent) {
139
+ const gross = trancheApyBounds(tranches);
140
+ if (!gross)
141
+ return null;
142
+ const min = netEffectiveApy(gross.min, feePercent);
143
+ const max = netEffectiveApy(gross.max, feePercent);
144
+ if (!Number.isFinite(min) || min <= 0)
145
+ return null;
146
+ if (!Number.isFinite(max) || max <= 0)
147
+ return null;
148
+ return { min, max };
149
+ }
150
+ //# sourceMappingURL=tranches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tranches.js","sourceRoot":"","sources":["../../src/domain/tranches.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};