@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.
- package/LICENSE +21 -0
- package/README.md +189 -253
- package/dist/bundle.cjs.js +4775 -1794
- package/dist/bundle.esm.js +4764 -1816
- package/dist/domain/deposit-bounds.d.ts +62 -0
- package/dist/domain/deposit-bounds.js +84 -0
- package/dist/domain/deposit-bounds.js.map +1 -0
- package/dist/domain/deposit-bounds.test.js +135 -0
- package/dist/domain/deposit-bounds.test.js.map +1 -0
- package/dist/domain/index.d.ts +26 -0
- package/dist/domain/index.js +23 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/partners.d.ts +34 -0
- package/dist/domain/partners.js +43 -0
- package/dist/domain/partners.js.map +1 -0
- package/dist/domain/partners.test.d.ts +1 -0
- package/dist/domain/partners.test.js +53 -0
- package/dist/domain/partners.test.js.map +1 -0
- package/dist/domain/pools.d.ts +70 -0
- package/dist/domain/pools.js +134 -0
- package/dist/domain/pools.js.map +1 -0
- package/dist/domain/pools.test.d.ts +1 -0
- package/dist/domain/pools.test.js +184 -0
- package/dist/domain/pools.test.js.map +1 -0
- package/dist/domain/rates.d.ts +83 -0
- package/dist/domain/rates.js +120 -0
- package/dist/domain/rates.js.map +1 -0
- package/dist/domain/rates.test.d.ts +1 -0
- package/dist/domain/rates.test.js +181 -0
- package/dist/domain/rates.test.js.map +1 -0
- package/dist/domain/tranche-display-name.d.ts +17 -0
- package/dist/domain/tranche-display-name.js +30 -0
- package/dist/domain/tranche-display-name.js.map +1 -0
- package/dist/domain/tranche-display-name.test.d.ts +1 -0
- package/dist/domain/tranche-display-name.test.js +58 -0
- package/dist/domain/tranche-display-name.test.js.map +1 -0
- package/dist/domain/tranches.d.ts +89 -0
- package/dist/domain/tranches.js +150 -0
- package/dist/domain/tranches.js.map +1 -0
- package/dist/domain/tranches.test.d.ts +1 -0
- package/dist/domain/tranches.test.js +206 -0
- package/dist/domain/tranches.test.js.map +1 -0
- package/dist/facade/chain-configs.js +54 -0
- package/dist/facade/chain-configs.js.map +1 -1
- package/dist/facade/config.test.d.ts +1 -0
- package/dist/facade/config.test.js +216 -0
- package/dist/facade/config.test.js.map +1 -0
- package/dist/facade/deposits.d.ts +22 -1
- package/dist/facade/deposits.js +24 -1
- package/dist/facade/deposits.js.map +1 -1
- package/dist/facade/facade.test.d.ts +1 -0
- package/dist/{tests → facade}/facade.test.js +10 -5
- package/dist/facade/facade.test.js.map +1 -0
- package/dist/facade/index.d.ts +2 -1
- package/dist/facade/index.js +2 -0
- package/dist/facade/index.js.map +1 -1
- package/dist/facade/kasu.d.ts +49 -8
- package/dist/facade/kasu.js +117 -13
- package/dist/facade/kasu.js.map +1 -1
- package/dist/facade/strategies.d.ts +25 -0
- package/dist/facade/strategies.js +36 -0
- package/dist/facade/strategies.js.map +1 -1
- package/dist/facade/types.d.ts +57 -2
- package/dist/facade/unused-pool-ids.d.ts +31 -0
- package/dist/facade/unused-pool-ids.js +57 -0
- package/dist/facade/unused-pool-ids.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/sdk-config.d.ts +11 -0
- package/dist/sdk-config.js +8 -1
- package/dist/sdk-config.js.map +1 -1
- package/dist/services/DataService/data-service.d.ts +5 -0
- package/dist/services/DataService/data-service.js +7 -2
- package/dist/services/DataService/data-service.js.map +1 -1
- package/dist/services/Locking/calculate-apy.test.d.ts +1 -0
- package/dist/services/Locking/calculate-apy.test.js +41 -0
- package/dist/services/Locking/calculate-apy.test.js.map +1 -0
- package/dist/services/Locking/locking.d.ts +14 -0
- package/dist/services/Locking/locking.js +16 -2
- package/dist/services/Locking/locking.js.map +1 -1
- package/dist/tests/sample.test.js +46 -25
- package/dist/tests/sample.test.js.map +1 -1
- package/package.json +43 -12
- package/src/domain/deposit-bounds.test.ts +200 -0
- package/src/domain/deposit-bounds.ts +118 -0
- package/src/domain/index.ts +70 -0
- package/src/domain/partners.test.ts +83 -0
- package/src/domain/partners.ts +66 -0
- package/src/domain/pools.test.ts +260 -0
- package/src/domain/pools.ts +150 -0
- package/src/domain/rates.test.ts +254 -0
- package/src/domain/rates.ts +120 -0
- package/src/domain/tranche-display-name.test.ts +96 -0
- package/src/domain/tranche-display-name.ts +34 -0
- package/src/domain/tranches.test.ts +300 -0
- package/src/domain/tranches.ts +177 -0
- package/src/facade/chain-configs.ts +55 -0
- package/src/facade/config.test.ts +265 -0
- package/src/facade/deposits.ts +25 -0
- package/src/{tests → facade}/facade.test.ts +10 -5
- package/src/facade/index.ts +4 -0
- package/src/facade/kasu.ts +155 -12
- package/src/facade/strategies.ts +36 -0
- package/src/facade/types.ts +60 -2
- package/src/facade/unused-pool-ids.ts +59 -0
- package/src/index.ts +6 -0
- package/src/sdk-config.ts +19 -1
- package/src/services/DataService/data-service.ts +7 -2
- package/src/services/Locking/calculate-apy.test.ts +52 -0
- package/src/services/Locking/locking.ts +16 -2
- package/src/tests/sample.test.ts +54 -33
- package/.eslintignore +0 -3
- package/.eslintrc.json +0 -90
- package/.github/workflows/ci.yml +0 -39
- package/.github/workflows/gitleaks.yml +0 -22
- package/.github/workflows/main.yml +0 -41
- package/.gitleaksignore +0 -19
- package/.prettierignore +0 -3
- package/.prettierrc +0 -28
- package/CLAUDE.md +0 -410
- package/copy-abi.ps1 +0 -15
- package/dist/tests/facade.test.js.map +0 -1
- package/index.ts +0 -2
- package/jest-config.ts +0 -9
- package/jest.config.js +0 -8
- package/rollup.config.js +0 -45
- package/tsconfig.json +0 -27
- /package/dist/{tests/facade.test.d.ts → domain/deposit-bounds.test.d.ts} +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { TrancheData } from '../services/DataService/types';
|
|
2
|
+
export declare const MIN_LENDING_AMOUNT_FALLBACK = 500;
|
|
3
|
+
export declare const MAX_LENDING_AMOUNT_FALLBACK = 500000;
|
|
4
|
+
export declare function parseTrancheBound(raw: string | number | null | undefined): number | null;
|
|
5
|
+
export interface DepositBounds {
|
|
6
|
+
minDeposit: number;
|
|
7
|
+
maxDeposit: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Min + max lending amount (in whole stable units) for a tranche. The max is
|
|
11
|
+
* clamped by the tranche's *remaining* capacity: a tranche can fill mid-epoch
|
|
12
|
+
* while `maximumDeposit` stays at its configured value, so without the clamp
|
|
13
|
+
* a form would accept an amount the contract will reject. Mirrors
|
|
14
|
+
* kasu-fe-next's `calculateDepositMinMax` (`trancheMax = min(max, capacity)`).
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveDepositBounds(tranche: TrancheData | undefined): DepositBounds;
|
|
17
|
+
export interface BoundShortcuts {
|
|
18
|
+
min: number;
|
|
19
|
+
max: number;
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The values a lend form's clickable Min/Max labels write into the amount
|
|
24
|
+
* field. Derived from the validated bounds but snapped to whole cents
|
|
25
|
+
* CONSERVATIVELY — the min rounds UP, the max rounds DOWN — so a shortcut can
|
|
26
|
+
* never fill an amount the form's own `min ≤ amount ≤ max` check rejects, and
|
|
27
|
+
* never carries the SDK's float noise into the field (`targetDrawAmount -
|
|
28
|
+
* pendingDeposits` routinely yields `249999.99999999997`, which would make the
|
|
29
|
+
* label, the field's 2dp display and the signed request label all disagree).
|
|
30
|
+
*
|
|
31
|
+
* `enabled` is false when the snapped range is empty: a tranche at capacity
|
|
32
|
+
* (max clamps to 0 or to sub-cent dust), a remaining capacity below the
|
|
33
|
+
* tranche minimum, or a jurisdiction floor above the capacity. Nothing either
|
|
34
|
+
* shortcut could write would validate, so both render inert.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveBoundShortcuts(minDeposit: number, maxDeposit: number): BoundShortcuts;
|
|
37
|
+
/**
|
|
38
|
+
* True when no amount exists that satisfies both bounds — the minimum sits
|
|
39
|
+
* ABOVE the maximum, so the range a form would otherwise print ("Min 360,000 ·
|
|
40
|
+
* Max 25,285") is a contradiction, not an instruction. Happens when a
|
|
41
|
+
* tranche's remaining capacity falls under its own configured minimum, when it
|
|
42
|
+
* is at capacity (max 0 / sub-cent dust), or when the AU wholesale cumulative
|
|
43
|
+
* floor lands above the capacity that is left.
|
|
44
|
+
*
|
|
45
|
+
* Defined as the negation of `resolveBoundShortcuts().enabled` on purpose: the
|
|
46
|
+
* "there is nothing to click" and "there is nothing to type" states must never
|
|
47
|
+
* be able to disagree.
|
|
48
|
+
*
|
|
49
|
+
* This is UX only. The binding minimum is enforced by kasu-backend (403
|
|
50
|
+
* `AU_WHOLESALE_MINIMUM_NOT_MET`) and by the contract's own bounds; nothing
|
|
51
|
+
* here may be used to relax either.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isBelowMinimumCapacity(minDeposit: number, maxDeposit: number): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Largest whole-cent value ≤ `n`. Float multiplication can round `n * 100` UP
|
|
56
|
+
* onto an integer (`1.15 * 100 === 114.99999999999999` is the usual direction,
|
|
57
|
+
* but the other happens too), so the result is re-checked against `n` and
|
|
58
|
+
* stepped down a cent if it overshot — the contract is "never above `n`".
|
|
59
|
+
*/
|
|
60
|
+
export declare function floorToCents(n: number): number;
|
|
61
|
+
/** Smallest whole-cent value ≥ `n`. Mirror of `floorToCents`: never below `n`. */
|
|
62
|
+
export declare function ceilToCents(n: number): number;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Fallbacks if the selected tranche's on-chain min/max are 0 / NaN (some
|
|
2
|
+
// "coming soon" pools haven't been configured yet).
|
|
3
|
+
export const MIN_LENDING_AMOUNT_FALLBACK = 500;
|
|
4
|
+
export const MAX_LENDING_AMOUNT_FALLBACK = 500000;
|
|
5
|
+
export function parseTrancheBound(raw) {
|
|
6
|
+
if (raw == null)
|
|
7
|
+
return null;
|
|
8
|
+
const n = typeof raw === 'string' ? parseFloat(raw) : raw;
|
|
9
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Min + max lending amount (in whole stable units) for a tranche. The max is
|
|
13
|
+
* clamped by the tranche's *remaining* capacity: a tranche can fill mid-epoch
|
|
14
|
+
* while `maximumDeposit` stays at its configured value, so without the clamp
|
|
15
|
+
* a form would accept an amount the contract will reject. Mirrors
|
|
16
|
+
* kasu-fe-next's `calculateDepositMinMax` (`trancheMax = min(max, capacity)`).
|
|
17
|
+
*/
|
|
18
|
+
export function resolveDepositBounds(tranche) {
|
|
19
|
+
var _a, _b;
|
|
20
|
+
const minDeposit = (_a = parseTrancheBound(tranche === null || tranche === void 0 ? void 0 : tranche.minimumDeposit)) !== null && _a !== void 0 ? _a : MIN_LENDING_AMOUNT_FALLBACK;
|
|
21
|
+
const configuredMax = (_b = parseTrancheBound(tranche === null || tranche === void 0 ? void 0 : tranche.maximumDeposit)) !== null && _b !== void 0 ? _b : MAX_LENDING_AMOUNT_FALLBACK;
|
|
22
|
+
if (!tranche)
|
|
23
|
+
return { minDeposit, maxDeposit: configuredMax };
|
|
24
|
+
const remainingCapacity = Math.max(0, parseFloat(tranche.poolCapacity) || 0);
|
|
25
|
+
return { minDeposit, maxDeposit: Math.min(configuredMax, remainingCapacity) };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The values a lend form's clickable Min/Max labels write into the amount
|
|
29
|
+
* field. Derived from the validated bounds but snapped to whole cents
|
|
30
|
+
* CONSERVATIVELY — the min rounds UP, the max rounds DOWN — so a shortcut can
|
|
31
|
+
* never fill an amount the form's own `min ≤ amount ≤ max` check rejects, and
|
|
32
|
+
* never carries the SDK's float noise into the field (`targetDrawAmount -
|
|
33
|
+
* pendingDeposits` routinely yields `249999.99999999997`, which would make the
|
|
34
|
+
* label, the field's 2dp display and the signed request label all disagree).
|
|
35
|
+
*
|
|
36
|
+
* `enabled` is false when the snapped range is empty: a tranche at capacity
|
|
37
|
+
* (max clamps to 0 or to sub-cent dust), a remaining capacity below the
|
|
38
|
+
* tranche minimum, or a jurisdiction floor above the capacity. Nothing either
|
|
39
|
+
* shortcut could write would validate, so both render inert.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveBoundShortcuts(minDeposit, maxDeposit) {
|
|
42
|
+
const min = ceilToCents(minDeposit);
|
|
43
|
+
const max = floorToCents(maxDeposit);
|
|
44
|
+
const enabled = Number.isFinite(min) && Number.isFinite(max) && max > 0 && max >= min;
|
|
45
|
+
return { min, max, enabled };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* True when no amount exists that satisfies both bounds — the minimum sits
|
|
49
|
+
* ABOVE the maximum, so the range a form would otherwise print ("Min 360,000 ·
|
|
50
|
+
* Max 25,285") is a contradiction, not an instruction. Happens when a
|
|
51
|
+
* tranche's remaining capacity falls under its own configured minimum, when it
|
|
52
|
+
* is at capacity (max 0 / sub-cent dust), or when the AU wholesale cumulative
|
|
53
|
+
* floor lands above the capacity that is left.
|
|
54
|
+
*
|
|
55
|
+
* Defined as the negation of `resolveBoundShortcuts().enabled` on purpose: the
|
|
56
|
+
* "there is nothing to click" and "there is nothing to type" states must never
|
|
57
|
+
* be able to disagree.
|
|
58
|
+
*
|
|
59
|
+
* This is UX only. The binding minimum is enforced by kasu-backend (403
|
|
60
|
+
* `AU_WHOLESALE_MINIMUM_NOT_MET`) and by the contract's own bounds; nothing
|
|
61
|
+
* here may be used to relax either.
|
|
62
|
+
*/
|
|
63
|
+
export function isBelowMinimumCapacity(minDeposit, maxDeposit) {
|
|
64
|
+
return !resolveBoundShortcuts(minDeposit, maxDeposit).enabled;
|
|
65
|
+
}
|
|
66
|
+
const CENTS = 100;
|
|
67
|
+
/**
|
|
68
|
+
* Largest whole-cent value ≤ `n`. Float multiplication can round `n * 100` UP
|
|
69
|
+
* onto an integer (`1.15 * 100 === 114.99999999999999` is the usual direction,
|
|
70
|
+
* but the other happens too), so the result is re-checked against `n` and
|
|
71
|
+
* stepped down a cent if it overshot — the contract is "never above `n`".
|
|
72
|
+
*/
|
|
73
|
+
export function floorToCents(n) {
|
|
74
|
+
const k = Math.floor(n * CENTS);
|
|
75
|
+
const snapped = k / CENTS;
|
|
76
|
+
return snapped > n ? (k - 1) / CENTS : snapped;
|
|
77
|
+
}
|
|
78
|
+
/** Smallest whole-cent value ≥ `n`. Mirror of `floorToCents`: never below `n`. */
|
|
79
|
+
export function ceilToCents(n) {
|
|
80
|
+
const k = Math.ceil(n * CENTS);
|
|
81
|
+
const snapped = k / CENTS;
|
|
82
|
+
return snapped < n ? (k + 1) / CENTS : snapped;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=deposit-bounds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deposit-bounds.js","sourceRoot":"","sources":["../../src/domain/deposit-bounds.ts"],"names":[],"mappings":"AAEA,yEAAyE;AACzE,oDAAoD;AACpD,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAC/C,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAO,CAAC;AAEnD,MAAM,UAAU,iBAAiB,CAC7B,GAAuC;IAEvC,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7B,MAAM,CAAC,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC;AAOD;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAChC,OAAgC;;IAEhC,MAAM,UAAU,GACZ,MAAA,iBAAiB,CAAC,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,CAAC,mCAC1C,2BAA2B,CAAC;IAChC,MAAM,aAAa,GACf,MAAA,iBAAiB,CAAC,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,CAAC,mCAC1C,2BAA2B,CAAC;IAChC,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC;IAC/D,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAC9B,CAAC,EACD,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CACxC,CAAC;IACF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,iBAAiB,CAAC,EAAE,CAAC;AAClF,CAAC;AAQD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CACjC,UAAkB,EAClB,UAAkB;IAElB,MAAM,GAAG,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,OAAO,GACT,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC;IAC1E,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sBAAsB,CAClC,UAAkB,EAClB,UAAkB;IAElB,OAAO,CAAC,qBAAqB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,OAAO,CAAC;AAClE,CAAC;AAED,MAAM,KAAK,GAAG,GAAG,CAAC;AAElB;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IAClC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC;IAC1B,OAAO,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;AACnD,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,CAAS;IACjC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC;IAC1B,OAAO,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as fc from 'fast-check';
|
|
2
|
+
import { ceilToCents, floorToCents, isBelowMinimumCapacity, MAX_LENDING_AMOUNT_FALLBACK, MIN_LENDING_AMOUNT_FALLBACK, parseTrancheBound, resolveBoundShortcuts, resolveDepositBounds, } from './deposit-bounds';
|
|
3
|
+
/**
|
|
4
|
+
* Ported from kasu-ui `src/features/lending/lib/tranche-bounds.property.test.ts`,
|
|
5
|
+
* with the `resolveDepositBounds` / `parseTrancheBound` cases the property file
|
|
6
|
+
* did not cover.
|
|
7
|
+
*
|
|
8
|
+
* The Min/Max shortcuts (clickable bound labels on a lend form) must never
|
|
9
|
+
* write an amount the form rejects, so the snapping is checked as invariants
|
|
10
|
+
* over the whole input domain rather than at a few hand-picked values.
|
|
11
|
+
*/
|
|
12
|
+
function tranche(over) {
|
|
13
|
+
return Object.assign({ minimumDeposit: '500', maximumDeposit: '500000', poolCapacity: '250000' }, over);
|
|
14
|
+
}
|
|
15
|
+
const amountArb = fc.double({
|
|
16
|
+
min: 0,
|
|
17
|
+
max: 1e12,
|
|
18
|
+
noNaN: true,
|
|
19
|
+
noDefaultInfinity: true,
|
|
20
|
+
});
|
|
21
|
+
/** `String(k / 100)` prints at most two decimals for any integer `k` in range. */
|
|
22
|
+
const AT_MOST_TWO_DP = /^\d+(\.\d{1,2})?$/;
|
|
23
|
+
describe('parseTrancheBound', () => {
|
|
24
|
+
it('accepts a positive finite bound from a string or a number', () => {
|
|
25
|
+
expect(parseTrancheBound('500')).toBe(500);
|
|
26
|
+
expect(parseTrancheBound(500)).toBe(500);
|
|
27
|
+
});
|
|
28
|
+
it('rejects anything that is not a usable bound', () => {
|
|
29
|
+
expect(parseTrancheBound(null)).toBeNull();
|
|
30
|
+
expect(parseTrancheBound(undefined)).toBeNull();
|
|
31
|
+
expect(parseTrancheBound('')).toBeNull();
|
|
32
|
+
expect(parseTrancheBound('0')).toBeNull();
|
|
33
|
+
expect(parseTrancheBound('-1')).toBeNull();
|
|
34
|
+
expect(parseTrancheBound('not-a-number')).toBeNull();
|
|
35
|
+
expect(parseTrancheBound(Infinity)).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('resolveDepositBounds', () => {
|
|
39
|
+
it('clamps the max by the tranche’s REMAINING capacity', () => {
|
|
40
|
+
// A tranche can fill mid-epoch while `maximumDeposit` stays put.
|
|
41
|
+
expect(resolveDepositBounds(tranche({ maximumDeposit: '500000', poolCapacity: '25285.86' }))).toEqual({ minDeposit: 500, maxDeposit: 25285.86 });
|
|
42
|
+
});
|
|
43
|
+
it('keeps the configured max when capacity is larger', () => {
|
|
44
|
+
expect(resolveDepositBounds(tranche({ maximumDeposit: '500000', poolCapacity: '900000' }))).toEqual({ minDeposit: 500, maxDeposit: 500000 });
|
|
45
|
+
});
|
|
46
|
+
it('falls back when a "coming soon" pool has no bounds configured', () => {
|
|
47
|
+
expect(resolveDepositBounds(tranche({
|
|
48
|
+
minimumDeposit: '0',
|
|
49
|
+
maximumDeposit: '0',
|
|
50
|
+
poolCapacity: '900000',
|
|
51
|
+
}))).toEqual({
|
|
52
|
+
minDeposit: MIN_LENDING_AMOUNT_FALLBACK,
|
|
53
|
+
maxDeposit: MAX_LENDING_AMOUNT_FALLBACK,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
it('uses both fallbacks and skips the capacity clamp with no tranche at all', () => {
|
|
57
|
+
expect(resolveDepositBounds(undefined)).toEqual({
|
|
58
|
+
minDeposit: MIN_LENDING_AMOUNT_FALLBACK,
|
|
59
|
+
maxDeposit: MAX_LENDING_AMOUNT_FALLBACK,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
it('treats unparseable or negative capacity as zero', () => {
|
|
63
|
+
expect(resolveDepositBounds(tranche({ poolCapacity: 'not-a-number' }))
|
|
64
|
+
.maxDeposit).toBe(0);
|
|
65
|
+
expect(resolveDepositBounds(tranche({ poolCapacity: '-5' })).maxDeposit).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('cent snapping', () => {
|
|
69
|
+
it('floorToCents never exceeds its input, stays within a cent of it, and has ≤ 2dp', () => {
|
|
70
|
+
fc.assert(fc.property(amountArb, (n) => {
|
|
71
|
+
const snapped = floorToCents(n);
|
|
72
|
+
expect(snapped).toBeLessThanOrEqual(n);
|
|
73
|
+
expect(n - snapped).toBeLessThan(0.02);
|
|
74
|
+
expect(String(snapped)).toMatch(AT_MOST_TWO_DP);
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
77
|
+
it('ceilToCents never undershoots its input, stays within a cent of it, and has ≤ 2dp', () => {
|
|
78
|
+
fc.assert(fc.property(amountArb, (n) => {
|
|
79
|
+
const snapped = ceilToCents(n);
|
|
80
|
+
expect(snapped).toBeGreaterThanOrEqual(n);
|
|
81
|
+
expect(snapped - n).toBeLessThan(0.02);
|
|
82
|
+
expect(String(snapped)).toMatch(AT_MOST_TWO_DP);
|
|
83
|
+
}));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('resolveBoundShortcuts', () => {
|
|
87
|
+
it('keeps both shortcuts inside the validated range whenever they are enabled', () => {
|
|
88
|
+
fc.assert(fc.property(amountArb, amountArb, (minDeposit, maxDeposit) => {
|
|
89
|
+
const { min, max, enabled } = resolveBoundShortcuts(minDeposit, maxDeposit);
|
|
90
|
+
if (!enabled)
|
|
91
|
+
return;
|
|
92
|
+
expect(min).toBeGreaterThanOrEqual(minDeposit);
|
|
93
|
+
expect(max).toBeLessThanOrEqual(maxDeposit);
|
|
94
|
+
expect(max).toBeGreaterThanOrEqual(min);
|
|
95
|
+
expect(max).toBeGreaterThan(0);
|
|
96
|
+
}));
|
|
97
|
+
});
|
|
98
|
+
it('disables the pair whenever the snapped range is empty', () => {
|
|
99
|
+
// Tranche at capacity (max clamps to 0), sub-cent dust the SDK's float
|
|
100
|
+
// subtraction leaves behind, remaining capacity below the tranche
|
|
101
|
+
// minimum, and an AU floor above the capacity.
|
|
102
|
+
expect(resolveBoundShortcuts(500, 0).enabled).toBe(false);
|
|
103
|
+
expect(resolveBoundShortcuts(500, 1.4551915228366852e-11).enabled).toBe(false);
|
|
104
|
+
expect(resolveBoundShortcuts(500, 137).enabled).toBe(false);
|
|
105
|
+
expect(resolveBoundShortcuts(240000, 100000).enabled).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
it('snaps SDK float noise onto cents without crossing the bound', () => {
|
|
108
|
+
expect(resolveBoundShortcuts(500, 249999.99999999997)).toEqual({
|
|
109
|
+
min: 500,
|
|
110
|
+
max: 249999.99,
|
|
111
|
+
enabled: true,
|
|
112
|
+
});
|
|
113
|
+
expect(resolveBoundShortcuts(500, 25285.86)).toEqual({
|
|
114
|
+
min: 500,
|
|
115
|
+
max: 25285.86,
|
|
116
|
+
enabled: true,
|
|
117
|
+
});
|
|
118
|
+
// An AU remainder with more precision than the field shows rounds UP.
|
|
119
|
+
expect(resolveBoundShortcuts(358765.432109, 500000).min).toBe(358765.44);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('isBelowMinimumCapacity', () => {
|
|
123
|
+
it('is exactly the negation of the shortcuts being enabled', () => {
|
|
124
|
+
fc.assert(fc.property(amountArb, amountArb, (minDeposit, maxDeposit) => {
|
|
125
|
+
// The "nothing to click" and "nothing to type" states must
|
|
126
|
+
// never be able to disagree.
|
|
127
|
+
expect(isBelowMinimumCapacity(minDeposit, maxDeposit)).toBe(!resolveBoundShortcuts(minDeposit, maxDeposit).enabled);
|
|
128
|
+
}));
|
|
129
|
+
});
|
|
130
|
+
it('flags a capacity that has fallen under the tranche minimum', () => {
|
|
131
|
+
expect(isBelowMinimumCapacity(500, 137)).toBe(true);
|
|
132
|
+
expect(isBelowMinimumCapacity(500, 25285.86)).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=deposit-bounds.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deposit-bounds.test.js","sourceRoot":"","sources":["../../src/domain/deposit-bounds.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared domain layer: the rules every Kasu frontend needs to agree on,
|
|
3
|
+
* in one place instead of three.
|
|
4
|
+
*
|
|
5
|
+
* Two rules govern what may live here.
|
|
6
|
+
*
|
|
7
|
+
* 1. **Numbers and codes only.** No copy, no locale, no `Intl`, no string a
|
|
8
|
+
* user reads — `netEffectiveApy` returns `0.196`, never `'19.60% p.a.'`,
|
|
9
|
+
* and `derivePoolStatus` returns the code `'Full'`, not a sentence. The
|
|
10
|
+
* formatters that turn these into text stay in each app, where the design
|
|
11
|
+
* system and the visitor's locale are. The one exception is
|
|
12
|
+
* `getTrancheDisplayName`, which exists precisely because that rename must
|
|
13
|
+
* NOT drift between apps.
|
|
14
|
+
* 2. **Pure.** No network, no clock, no environment. Anything with I/O belongs
|
|
15
|
+
* in `facade/` (see `fetchUnusedPoolIds`).
|
|
16
|
+
*/
|
|
17
|
+
export { apyToEpochRate, epochRateToApy, EPOCHS_IN_YEAR, netEffectiveApy, } from './rates';
|
|
18
|
+
export { ceilToCents, floorToCents, isBelowMinimumCapacity, MAX_LENDING_AMOUNT_FALLBACK, MIN_LENDING_AMOUNT_FALLBACK, parseTrancheBound, resolveBoundShortcuts, resolveDepositBounds, } from './deposit-bounds';
|
|
19
|
+
export type { BoundShortcuts, DepositBounds } from './deposit-bounds';
|
|
20
|
+
export { APXIUM, getCreditOriginator, getInstitutionalLender, INVOICEMATE, RIXON_CAPITAL, } from './partners';
|
|
21
|
+
export type { PoolNameSignal, StrategyPartner } from './partners';
|
|
22
|
+
export { maxNetRateCeiling, pickHighestYieldTranche, poolMaxApy, selectVisiblePools, } from './pools';
|
|
23
|
+
export type { BestTranche } from './pools';
|
|
24
|
+
export { getTrancheDisplayName, UPPER_MEZZANINE } from './tranche-display-name';
|
|
25
|
+
export { compareTrancheSeniority, derivePoolStatus, MIN_TRANCHE_CAPACITY, netTrancheApyBounds, pickDefaultTrancheId, poolAllTranchesFull, trancheApyBounds, trancheHasCapacity, trancheRiskRank, } from './tranches';
|
|
26
|
+
export type { ApyBounds, PoolStatus, TrancheCapacitySignal, } from './tranches';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared domain layer: the rules every Kasu frontend needs to agree on,
|
|
3
|
+
* in one place instead of three.
|
|
4
|
+
*
|
|
5
|
+
* Two rules govern what may live here.
|
|
6
|
+
*
|
|
7
|
+
* 1. **Numbers and codes only.** No copy, no locale, no `Intl`, no string a
|
|
8
|
+
* user reads — `netEffectiveApy` returns `0.196`, never `'19.60% p.a.'`,
|
|
9
|
+
* and `derivePoolStatus` returns the code `'Full'`, not a sentence. The
|
|
10
|
+
* formatters that turn these into text stay in each app, where the design
|
|
11
|
+
* system and the visitor's locale are. The one exception is
|
|
12
|
+
* `getTrancheDisplayName`, which exists precisely because that rename must
|
|
13
|
+
* NOT drift between apps.
|
|
14
|
+
* 2. **Pure.** No network, no clock, no environment. Anything with I/O belongs
|
|
15
|
+
* in `facade/` (see `fetchUnusedPoolIds`).
|
|
16
|
+
*/
|
|
17
|
+
export { apyToEpochRate, epochRateToApy, EPOCHS_IN_YEAR, netEffectiveApy, } from './rates';
|
|
18
|
+
export { ceilToCents, floorToCents, isBelowMinimumCapacity, MAX_LENDING_AMOUNT_FALLBACK, MIN_LENDING_AMOUNT_FALLBACK, parseTrancheBound, resolveBoundShortcuts, resolveDepositBounds, } from './deposit-bounds';
|
|
19
|
+
export { APXIUM, getCreditOriginator, getInstitutionalLender, INVOICEMATE, RIXON_CAPITAL, } from './partners';
|
|
20
|
+
export { maxNetRateCeiling, pickHighestYieldTranche, poolMaxApy, selectVisiblePools, } from './pools';
|
|
21
|
+
export { getTrancheDisplayName, UPPER_MEZZANINE } from './tranche-display-name';
|
|
22
|
+
export { compareTrancheSeniority, derivePoolStatus, MIN_TRANCHE_CAPACITY, netTrancheApyBounds, pickDefaultTrancheId, poolAllTranchesFull, trancheApyBounds, trancheHasCapacity, trancheRiskRank, } from './tranches';
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/domain/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACH,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,GAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACH,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAC3B,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACH,MAAM,EACN,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,aAAa,GAChB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACH,iBAAiB,EACjB,uBAAuB,EACvB,UAAU,EACV,kBAAkB,GACrB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,EACH,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,GAClB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface StrategyPartner {
|
|
2
|
+
name: string;
|
|
3
|
+
url: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const APXIUM: StrategyPartner;
|
|
6
|
+
export declare const INVOICEMATE: StrategyPartner;
|
|
7
|
+
export declare const RIXON_CAPITAL: StrategyPartner;
|
|
8
|
+
/**
|
|
9
|
+
* Anything a pool name can arrive as. The raw `PoolOverview` carries
|
|
10
|
+
* `poolName`, the facade's `Strategy` carries `name`, and a caller who already
|
|
11
|
+
* has the string can pass it directly — one rule, whichever shape reaches it.
|
|
12
|
+
* `poolName` wins when an object carries both and it is set.
|
|
13
|
+
*/
|
|
14
|
+
export type PoolNameSignal = string | {
|
|
15
|
+
poolName?: string | null;
|
|
16
|
+
} | {
|
|
17
|
+
name?: string | null;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Credit-originator inference by pool NAME only. The sole InvoiceMate
|
|
21
|
+
* strategy is "Payment Finance (PayFi)" (matched by "payfi" / "payment
|
|
22
|
+
* finance"); every other pool — Whole Ledger / Taxation / Professional Fee
|
|
23
|
+
* Funding — is Apxium.
|
|
24
|
+
*
|
|
25
|
+
* We deliberately do NOT match on asset class / subheading: Apxium's pools
|
|
26
|
+
* are receivables/invoice financing, so their asset class contains
|
|
27
|
+
* "invoice" and would be misclassified as InvoiceMate.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getCreditOriginator(pool: PoolNameSignal): StrategyPartner;
|
|
30
|
+
/**
|
|
31
|
+
* Apxium pools carry an institutional senior lender (Rixon Capital);
|
|
32
|
+
* InvoiceMate pools have none.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getInstitutionalLender(originator: StrategyPartner): StrategyPartner | null;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const APXIUM = {
|
|
2
|
+
name: 'Apxium',
|
|
3
|
+
url: 'https://www.apxium.com/',
|
|
4
|
+
};
|
|
5
|
+
export const INVOICEMATE = {
|
|
6
|
+
name: 'InvoiceMate',
|
|
7
|
+
url: 'https://invoicemate.net/',
|
|
8
|
+
};
|
|
9
|
+
export const RIXON_CAPITAL = {
|
|
10
|
+
name: 'Rixon Capital',
|
|
11
|
+
url: 'https://rixon.capital/',
|
|
12
|
+
};
|
|
13
|
+
function poolNameOf(pool) {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
if (typeof pool === 'string')
|
|
16
|
+
return pool;
|
|
17
|
+
const signal = pool;
|
|
18
|
+
return (_b = (_a = signal.poolName) !== null && _a !== void 0 ? _a : signal.name) !== null && _b !== void 0 ? _b : '';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Credit-originator inference by pool NAME only. The sole InvoiceMate
|
|
22
|
+
* strategy is "Payment Finance (PayFi)" (matched by "payfi" / "payment
|
|
23
|
+
* finance"); every other pool — Whole Ledger / Taxation / Professional Fee
|
|
24
|
+
* Funding — is Apxium.
|
|
25
|
+
*
|
|
26
|
+
* We deliberately do NOT match on asset class / subheading: Apxium's pools
|
|
27
|
+
* are receivables/invoice financing, so their asset class contains
|
|
28
|
+
* "invoice" and would be misclassified as InvoiceMate.
|
|
29
|
+
*/
|
|
30
|
+
export function getCreditOriginator(pool) {
|
|
31
|
+
const name = poolNameOf(pool).toLowerCase();
|
|
32
|
+
if (/payfi|payment finance/.test(name))
|
|
33
|
+
return INVOICEMATE;
|
|
34
|
+
return APXIUM;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Apxium pools carry an institutional senior lender (Rixon Capital);
|
|
38
|
+
* InvoiceMate pools have none.
|
|
39
|
+
*/
|
|
40
|
+
export function getInstitutionalLender(originator) {
|
|
41
|
+
return originator.name === APXIUM.name ? RIXON_CAPITAL : null;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=partners.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partners.js","sourceRoot":"","sources":["../../src/domain/partners.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,MAAM,GAAoB;IACnC,IAAI,EAAE,QAAQ;IACd,GAAG,EAAE,yBAAyB;CACjC,CAAC;AACF,MAAM,CAAC,MAAM,WAAW,GAAoB;IACxC,IAAI,EAAE,aAAa;IACnB,GAAG,EAAE,0BAA0B;CAClC,CAAC;AACF,MAAM,CAAC,MAAM,aAAa,GAAoB;IAC1C,IAAI,EAAE,eAAe;IACrB,GAAG,EAAE,wBAAwB;CAChC,CAAC;AAaF,SAAS,UAAU,CAAC,IAAoB;;IACpC,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,MAAM,GAAuD,IAAI,CAAC;IACxE,OAAO,MAAA,MAAA,MAAM,CAAC,QAAQ,mCAAI,MAAM,CAAC,IAAI,mCAAI,EAAE,CAAC;AAChD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAoB;IACpD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,WAAW,CAAC;IAC3D,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAClC,UAA2B;IAE3B,OAAO,UAAU,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { APXIUM, getCreditOriginator, getInstitutionalLender, INVOICEMATE, RIXON_CAPITAL, } from './partners';
|
|
2
|
+
// Ported from kasu-ui `src/features/lending/lib/strategy-partners.test.ts`.
|
|
3
|
+
describe('getCreditOriginator', () => {
|
|
4
|
+
it('maps "Payment Finance (PayFi)" to InvoiceMate', () => {
|
|
5
|
+
expect(getCreditOriginator({ poolName: 'Payment Finance (PayFi)' })).toBe(INVOICEMATE);
|
|
6
|
+
});
|
|
7
|
+
it('keeps Apxium receivables pools as Apxium (regression: do not match "invoice")', () => {
|
|
8
|
+
// The bug: matching the asset class ("invoice financing") wrongly
|
|
9
|
+
// classified Apxium receivables pools as InvoiceMate. Name-only
|
|
10
|
+
// matching fixes it — none of these names contain payfi/payment
|
|
11
|
+
// finance.
|
|
12
|
+
expect(getCreditOriginator({ poolName: 'Professional Fee Funding' })).toBe(APXIUM);
|
|
13
|
+
expect(getCreditOriginator({ poolName: 'Whole Ledger Funding' })).toBe(APXIUM);
|
|
14
|
+
expect(getCreditOriginator({ poolName: 'Taxation Funding (Tax Pay)' })).toBe(APXIUM);
|
|
15
|
+
});
|
|
16
|
+
it('is case-insensitive and matches the "payfi" / "payment finance" name', () => {
|
|
17
|
+
expect(getCreditOriginator({ poolName: 'payment finance' })).toBe(INVOICEMATE);
|
|
18
|
+
expect(getCreditOriginator({ poolName: 'PAYFI Strategy' })).toBe(INVOICEMATE);
|
|
19
|
+
});
|
|
20
|
+
it('defaults to Apxium for empty / missing names', () => {
|
|
21
|
+
expect(getCreditOriginator({ poolName: '' })).toBe(APXIUM);
|
|
22
|
+
expect(getCreditOriginator({})).toBe(APXIUM);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('getCreditOriginator — the widened PoolNameSignal', () => {
|
|
26
|
+
it('reads the facade Strategy shape (`name`)', () => {
|
|
27
|
+
expect(getCreditOriginator({ name: 'Payment Finance (PayFi)' })).toBe(INVOICEMATE);
|
|
28
|
+
expect(getCreditOriginator({ name: 'Taxation Funding' })).toBe(APXIUM);
|
|
29
|
+
expect(getCreditOriginator({ name: null })).toBe(APXIUM);
|
|
30
|
+
});
|
|
31
|
+
it('reads a bare string', () => {
|
|
32
|
+
expect(getCreditOriginator('Payment Finance (PayFi)')).toBe(INVOICEMATE);
|
|
33
|
+
expect(getCreditOriginator('Whole Ledger Funding')).toBe(APXIUM);
|
|
34
|
+
expect(getCreditOriginator('')).toBe(APXIUM);
|
|
35
|
+
});
|
|
36
|
+
it('prefers poolName when an object carries both', () => {
|
|
37
|
+
const both = { poolName: 'Taxation Funding', name: 'Payment Finance' };
|
|
38
|
+
expect(getCreditOriginator(both)).toBe(APXIUM);
|
|
39
|
+
});
|
|
40
|
+
it('treats an explicitly null poolName as missing, not as a match', () => {
|
|
41
|
+
expect(getCreditOriginator({ poolName: null })).toBe(APXIUM);
|
|
42
|
+
expect(getCreditOriginator({ poolName: undefined })).toBe(APXIUM);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('getInstitutionalLender', () => {
|
|
46
|
+
it('gives Apxium pools Rixon Capital', () => {
|
|
47
|
+
expect(getInstitutionalLender(APXIUM)).toBe(RIXON_CAPITAL);
|
|
48
|
+
});
|
|
49
|
+
it('gives InvoiceMate pools no senior lender', () => {
|
|
50
|
+
expect(getInstitutionalLender(INVOICEMATE)).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=partners.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partners.test.js","sourceRoot":"","sources":["../../src/domain/partners.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { PoolOverview, TrancheData } from '../services/DataService/types';
|
|
2
|
+
/**
|
|
3
|
+
* The highest FTD-inclusive upper bound across a pool's tranches, or `0` when
|
|
4
|
+
* none is usable. The sort key behind `selectVisiblePools`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function poolMaxApy(pool: PoolOverview): number;
|
|
7
|
+
/**
|
|
8
|
+
* A lending grid's visible pool set: *active* and *not yet oversubscribed*
|
|
9
|
+
* pools, sorted with has-capacity strategies first, then by highest max APY.
|
|
10
|
+
*
|
|
11
|
+
* Shared so a server-rendered payload and a client re-fetch can never
|
|
12
|
+
* disagree — kasu-ui's SSR fetcher and client hook used to carry verbatim
|
|
13
|
+
* copies of this filter + sort, and any drift surfaced as an SSR→CSR content
|
|
14
|
+
* flip on the lending page.
|
|
15
|
+
*
|
|
16
|
+
* Pure: operates on a fresh array (`filter` output), so the in-place `sort`
|
|
17
|
+
* does not mutate the caller's input.
|
|
18
|
+
*/
|
|
19
|
+
export declare function selectVisiblePools(all: PoolOverview[]): PoolOverview[];
|
|
20
|
+
export interface BestTranche {
|
|
21
|
+
pool: PoolOverview;
|
|
22
|
+
tranche: TrancheData;
|
|
23
|
+
/** Base variable-rate APY as a decimal fraction (e.g. `0.12`). */
|
|
24
|
+
apy: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The highest-yielding tranche that still has capacity, across all pools.
|
|
28
|
+
*
|
|
29
|
+
* Compares `tranche.apy` — the base variable rate — NOT `minApy`/`maxApy`,
|
|
30
|
+
* which the data service widens with fixed-term-deposit variants. A headline
|
|
31
|
+
* naming one tranche should advertise the rate every depositor gets, not an
|
|
32
|
+
* FTD-locked uplift.
|
|
33
|
+
*
|
|
34
|
+
* Capacity is gated by the shared `trancheHasCapacity` (≥ 1 stable unit), so
|
|
35
|
+
* full tranches never win. Ties on APY break toward more remaining capacity.
|
|
36
|
+
* Returns `null` when no tranche qualifies (empty list, all full, or all rates
|
|
37
|
+
* unset) — callers fall back to static copy.
|
|
38
|
+
*/
|
|
39
|
+
export declare function pickHighestYieldTranche(pools?: PoolOverview[] | null): BestTranche | null;
|
|
40
|
+
/**
|
|
41
|
+
* The highest NET Effective Interest Rate on offer across the pools passed in
|
|
42
|
+
* — the ceiling behind an "up to …" claim. The numeric core of kasu-ui's
|
|
43
|
+
* `formatMaxNetRate`.
|
|
44
|
+
*
|
|
45
|
+
* `null` when there is nothing to quote, and callers must then DROP the claim
|
|
46
|
+
* rather than print a placeholder: this feeds text that gets posted publicly.
|
|
47
|
+
*
|
|
48
|
+
* Scope follows the caller's pool list, which is normally keyed to one chain —
|
|
49
|
+
* so a lender on XDC AUDD quotes the AUDD ceiling, not Base's. That is the
|
|
50
|
+
* intended reading: they are sharing the deployment they are on.
|
|
51
|
+
*
|
|
52
|
+
* Reads `maxApy` — the FTD-inclusive upper bound — unlike
|
|
53
|
+
* `pickHighestYieldTranche` above, which names one tranche's variable rate. A
|
|
54
|
+
* ceiling claim and a specific tranche's rate are different statements, and
|
|
55
|
+
* the ceiling must agree with the top of `netTrancheApyBounds`, which reads
|
|
56
|
+
* the same field. `apy` is taken into the comparison too: `maxApy` is derived
|
|
57
|
+
* from the base rate plus any fixed-term configs, so it can never be lower,
|
|
58
|
+
* and a tranche that carries no `maxApy` at all still contributes its base
|
|
59
|
+
* rate instead of dropping out.
|
|
60
|
+
*
|
|
61
|
+
* Capacity-gated by `trancheHasCapacity` — we never advertise a rate no lender
|
|
62
|
+
* could take up.
|
|
63
|
+
*
|
|
64
|
+
* FAIL CLOSED: an out-of-domain fee, or a net rate that is non-finite or ≤ 0,
|
|
65
|
+
* returns `null`. The gross figure is never a substitute, and a caller that
|
|
66
|
+
* does not yet know the fee must not pass `0`.
|
|
67
|
+
*
|
|
68
|
+
* @param feePercent the chain's performance fee, 0..100 — never a fraction.
|
|
69
|
+
*/
|
|
70
|
+
export declare function maxNetRateCeiling(pools: PoolOverview[] | undefined | null, feePercent: number): number | null;
|