@kasufinance/kasu-sdk 2.4.2 → 2.6.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 +201 -253
- package/dist/bundle.cjs.js +5472 -1710
- package/dist/bundle.esm.js +5415 -1713
- package/dist/domain/au-minimum.d.ts +135 -0
- package/dist/domain/au-minimum.js +154 -0
- package/dist/domain/au-minimum.js.map +1 -0
- package/dist/domain/au-minimum.test.js +202 -0
- package/dist/domain/au-minimum.test.js.map +1 -0
- 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.d.ts +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 +40 -0
- package/dist/domain/index.js +33 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/loan-contract.d.ts +174 -0
- package/dist/domain/loan-contract.js +160 -0
- package/dist/domain/loan-contract.js.map +1 -0
- package/dist/domain/loan-contract.test.d.ts +1 -0
- package/dist/domain/loan-contract.test.js +255 -0
- package/dist/domain/loan-contract.test.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/requests.d.ts +181 -0
- package/dist/domain/requests.js +202 -0
- package/dist/domain/requests.js.map +1 -0
- package/dist/domain/requests.test.d.ts +1 -0
- package/dist/domain/requests.test.js +470 -0
- package/dist/domain/requests.test.js.map +1 -0
- package/dist/domain/settlement.d.ts +97 -0
- package/dist/domain/settlement.js +117 -0
- package/dist/domain/settlement.js.map +1 -0
- package/dist/domain/settlement.test.d.ts +1 -0
- package/dist/domain/settlement.test.js +152 -0
- package/dist/domain/settlement.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/domain/wallet-errors.d.ts +37 -0
- package/dist/domain/wallet-errors.js +56 -0
- package/dist/domain/wallet-errors.js.map +1 -0
- package/dist/domain/wallet-errors.test.d.ts +1 -0
- package/dist/domain/wallet-errors.test.js +71 -0
- package/dist/domain/wallet-errors.test.js.map +1 -0
- package/dist/facade/chain-configs.js +61 -1
- 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 +87 -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/facade/user-portfolio.d.ts +18 -0
- package/dist/facade/user-portfolio.js +23 -0
- package/dist/facade/user-portfolio.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -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 +10 -12
- package/dist/services/DataService/data-service.js.map +1 -1
- package/dist/services/DataService/directus-client.d.ts +26 -0
- package/dist/services/DataService/directus-client.js +38 -0
- package/dist/services/DataService/directus-client.js.map +1 -0
- 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/services/UserLending/user-lending.js +11 -7
- package/dist/services/UserLending/user-lending.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/au-minimum.test.ts +371 -0
- package/src/domain/au-minimum.ts +192 -0
- package/src/domain/deposit-bounds.test.ts +200 -0
- package/src/domain/deposit-bounds.ts +118 -0
- package/src/domain/index.ts +134 -0
- package/src/domain/loan-contract.test.ts +343 -0
- package/src/domain/loan-contract.ts +275 -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/requests.test.ts +653 -0
- package/src/domain/requests.ts +414 -0
- package/src/domain/settlement.test.ts +198 -0
- package/src/domain/settlement.ts +161 -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/domain/wallet-errors.test.ts +100 -0
- package/src/domain/wallet-errors.ts +56 -0
- package/src/facade/chain-configs.ts +62 -1
- package/src/facade/config.test.ts +265 -0
- package/src/facade/deposits.ts +25 -0
- package/src/{tests → facade}/facade.test.ts +134 -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/facade/user-portfolio.ts +24 -0
- package/src/index.ts +8 -0
- package/src/sdk-config.ts +19 -1
- package/src/services/DataService/data-service.ts +14 -26
- package/src/services/DataService/directus-client.ts +54 -0
- package/src/services/Locking/calculate-apy.test.ts +52 -0
- package/src/services/Locking/locking.ts +16 -2
- package/src/services/UserLending/user-lending.ts +17 -21
- 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/au-minimum.test.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loan-contract.js","sourceRoot":"","sources":["../../src/domain/loan-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
import { asContractType, buildContractVersionType, buildFullNameRequestMessage, buildLegacyContractRequestMessage, buildLoanAgreementSignMessage, encodeDepositData, formatSignTimestampUtc, parseFormattedMessage, } from './loan-contract';
|
|
3
|
+
/**
|
|
4
|
+
* Ported from kasu-ui `src/features/lending/lib/encode-deposit-data.test.ts`
|
|
5
|
+
* and `sign-message.test.ts`.
|
|
6
|
+
*
|
|
7
|
+
* PINNED CROSS-REPO CONTRACT. The expected strings and byte strings here are
|
|
8
|
+
* what kasu-backend reconstructs and verifies against. A diff in this file
|
|
9
|
+
* without a matching kasu-backend diff means signatures stop verifying in
|
|
10
|
+
* production.
|
|
11
|
+
*/
|
|
12
|
+
describe('buildContractVersionType', () => {
|
|
13
|
+
it('packs version 1 retail as 0x0100 (256)', () => {
|
|
14
|
+
expect(buildContractVersionType(1, 'retail')).toBe(256);
|
|
15
|
+
});
|
|
16
|
+
it('packs version 1 exempt as 0x0101 (257)', () => {
|
|
17
|
+
expect(buildContractVersionType(1, 'exempt')).toBe(257);
|
|
18
|
+
});
|
|
19
|
+
it('packs version 0 retail as 0', () => {
|
|
20
|
+
expect(buildContractVersionType(0, 'retail')).toBe(0);
|
|
21
|
+
});
|
|
22
|
+
it('packs version 2 exempt as 0x0201 (513)', () => {
|
|
23
|
+
expect(buildContractVersionType(2, 'exempt')).toBe(513);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
const SIG_65 = '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' +
|
|
27
|
+
'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef1c';
|
|
28
|
+
const SIG_65_B = '0x4a1e2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8' +
|
|
29
|
+
'4a1e2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f81b';
|
|
30
|
+
/**
|
|
31
|
+
* BYTE FIXTURES — produced by the viem implementation kasu-ui runs
|
|
32
|
+
* (`encodeAbiParameters(parseAbiParameters('bytes, uint256, uint256'), …)`)
|
|
33
|
+
* and pasted here verbatim. This is the whole point of the port: the ethers v5
|
|
34
|
+
* encoder in `loan-contract.ts` must emit exactly these bytes, because kasu-ui
|
|
35
|
+
* and kasu-mobile write the same `depositData` field on the same contract.
|
|
36
|
+
*
|
|
37
|
+
* Covers both contract types, a contract version >= 2, a short signature and
|
|
38
|
+
* the empty-signature/zero-timestamp floor.
|
|
39
|
+
*/
|
|
40
|
+
const VIEM_FIXTURES = [
|
|
41
|
+
{
|
|
42
|
+
name: 'v1 retail, ms timestamp',
|
|
43
|
+
signature: SIG_65,
|
|
44
|
+
timestamp: 1700000000000,
|
|
45
|
+
contractVersion: 1,
|
|
46
|
+
contractType: 'retail',
|
|
47
|
+
expected: '0x00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000018bcfe5680000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000041deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef1c00000000000000000000000000000000000000000000000000000000000000',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'v1 exempt, ms timestamp',
|
|
51
|
+
signature: SIG_65,
|
|
52
|
+
timestamp: 1700000000000,
|
|
53
|
+
contractVersion: 1,
|
|
54
|
+
contractType: 'exempt',
|
|
55
|
+
expected: '0x00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000018bcfe5680000000000000000000000000000000000000000000000000000000000000001010000000000000000000000000000000000000000000000000000000000000041deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef1c00000000000000000000000000000000000000000000000000000000000000',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'v2 retail, deck timestamp',
|
|
59
|
+
signature: SIG_65_B,
|
|
60
|
+
timestamp: 1785313320000,
|
|
61
|
+
contractVersion: 2,
|
|
62
|
+
contractType: 'retail',
|
|
63
|
+
expected: '0x00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000019facf75c40000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000414a1e2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f84a1e2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f81b00000000000000000000000000000000000000000000000000000000000000',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'v2 exempt, deck timestamp',
|
|
67
|
+
signature: SIG_65_B,
|
|
68
|
+
timestamp: 1785313320000,
|
|
69
|
+
contractVersion: 2,
|
|
70
|
+
contractType: 'exempt',
|
|
71
|
+
expected: '0x00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000019facf75c40000000000000000000000000000000000000000000000000000000000000020100000000000000000000000000000000000000000000000000000000000000414a1e2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f84a1e2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f81b00000000000000000000000000000000000000000000000000000000000000',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'short signature, v3 exempt, timestamp 1',
|
|
75
|
+
signature: '0x1234',
|
|
76
|
+
timestamp: 1,
|
|
77
|
+
contractVersion: 3,
|
|
78
|
+
contractType: 'exempt',
|
|
79
|
+
expected: '0x00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000030100000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'empty signature, v0 retail, timestamp 0',
|
|
83
|
+
signature: '0x',
|
|
84
|
+
timestamp: 0,
|
|
85
|
+
contractVersion: 0,
|
|
86
|
+
contractType: 'retail',
|
|
87
|
+
expected: '0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
describe('encodeDepositData — byte-identical to the viem implementation', () => {
|
|
91
|
+
for (const fixture of VIEM_FIXTURES) {
|
|
92
|
+
it(`matches the viem fixture: ${fixture.name}`, () => {
|
|
93
|
+
expect(encodeDepositData({
|
|
94
|
+
signature: fixture.signature,
|
|
95
|
+
timestamp: fixture.timestamp,
|
|
96
|
+
contractVersion: fixture.contractVersion,
|
|
97
|
+
contractType: fixture.contractType,
|
|
98
|
+
})).toBe(fixture.expected);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
it('produces a 0x-prefixed hex string', () => {
|
|
102
|
+
const out = encodeDepositData({
|
|
103
|
+
signature: SIG_65,
|
|
104
|
+
timestamp: 1700000000000,
|
|
105
|
+
contractVersion: 1,
|
|
106
|
+
contractType: 'retail',
|
|
107
|
+
});
|
|
108
|
+
expect(out).toMatch(/^0x[0-9a-f]+$/i);
|
|
109
|
+
});
|
|
110
|
+
it('round-trips through the same ABI tuple', () => {
|
|
111
|
+
const out = encodeDepositData({
|
|
112
|
+
signature: SIG_65,
|
|
113
|
+
timestamp: 1700000000000,
|
|
114
|
+
contractVersion: 1,
|
|
115
|
+
contractType: 'exempt',
|
|
116
|
+
});
|
|
117
|
+
const [sigOut, tsOut, vtOut] = ethers.utils.defaultAbiCoder.decode(['bytes', 'uint256', 'uint256'], out);
|
|
118
|
+
expect(sigOut).toBe(SIG_65);
|
|
119
|
+
expect(tsOut.toString()).toBe('1700000000000');
|
|
120
|
+
expect(vtOut.toString()).toBe('257'); // version 1 exempt
|
|
121
|
+
});
|
|
122
|
+
it('encodes retail with low-byte 0 and exempt with low-byte 1', () => {
|
|
123
|
+
const vt = (contractType) => {
|
|
124
|
+
const [, , v] = ethers.utils.defaultAbiCoder.decode(['bytes', 'uint256', 'uint256'], encodeDepositData({
|
|
125
|
+
signature: SIG_65,
|
|
126
|
+
timestamp: 1,
|
|
127
|
+
contractVersion: 1,
|
|
128
|
+
contractType,
|
|
129
|
+
}));
|
|
130
|
+
return v.toString();
|
|
131
|
+
};
|
|
132
|
+
expect(vt('retail')).toBe('256');
|
|
133
|
+
expect(vt('exempt')).toBe('257');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
describe('formatSignTimestampUtc', () => {
|
|
137
|
+
// 29 July 2026, 08:22:00 UTC.
|
|
138
|
+
const ms = 1785313320000; // 13 digits → milliseconds
|
|
139
|
+
const seconds = 1785313320; // 10 digits → seconds
|
|
140
|
+
it('formats a millisecond timestamp (>= 13 digits)', () => {
|
|
141
|
+
expect(formatSignTimestampUtc(ms)).toBe('29 July 2026, 08:22');
|
|
142
|
+
});
|
|
143
|
+
it('formats a second timestamp (< 13 digits) to the same instant', () => {
|
|
144
|
+
expect(formatSignTimestampUtc(seconds)).toBe('29 July 2026, 08:22');
|
|
145
|
+
});
|
|
146
|
+
it('pads hour/minute but not the day, at a month-boundary midnight', () => {
|
|
147
|
+
// 1 August 2026, 00:05:00 UTC — day non-padded ("1"), minute padded.
|
|
148
|
+
const midnight = Date.UTC(2026, 7, 1, 0, 5, 0);
|
|
149
|
+
expect(formatSignTimestampUtc(midnight)).toBe('1 August 2026, 00:05');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
describe('buildLoanAgreementSignMessage', () => {
|
|
153
|
+
it('builds the exact 4-line message from a millisecond timestamp', () => {
|
|
154
|
+
expect(buildLoanAgreementSignMessage({
|
|
155
|
+
strategyName: 'Taxation Funding (Tax Pay)',
|
|
156
|
+
region: 'Australia',
|
|
157
|
+
optionName: 'Upper Mezzanine',
|
|
158
|
+
amountLabel: '500 AUDD',
|
|
159
|
+
timestamp: 1785313320000,
|
|
160
|
+
})).toBe('Generate my Loan Agreement for review:\n' +
|
|
161
|
+
'Taxation Funding (Tax Pay) · Australia · Upper Mezzanine · 500 AUDD.\n' +
|
|
162
|
+
'Request made 29 July 2026, 08:22 UTC.\n' +
|
|
163
|
+
'This request does not commit me to lend.');
|
|
164
|
+
});
|
|
165
|
+
it('builds the identical message from the equivalent second timestamp', () => {
|
|
166
|
+
expect(buildLoanAgreementSignMessage({
|
|
167
|
+
strategyName: 'Whole Ledger Funding',
|
|
168
|
+
region: 'Australia',
|
|
169
|
+
optionName: 'Mezzanine',
|
|
170
|
+
amountLabel: '1,260.37 USDC',
|
|
171
|
+
timestamp: 1785313320, // seconds form of the same instant
|
|
172
|
+
})).toBe('Generate my Loan Agreement for review:\n' +
|
|
173
|
+
'Whole Ledger Funding · Australia · Mezzanine · 1,260.37 USDC.\n' +
|
|
174
|
+
'Request made 29 July 2026, 08:22 UTC.\n' +
|
|
175
|
+
'This request does not commit me to lend.');
|
|
176
|
+
});
|
|
177
|
+
it('formats a month-boundary/midnight request line', () => {
|
|
178
|
+
expect(buildLoanAgreementSignMessage({
|
|
179
|
+
strategyName: 'Professional Fee Funding',
|
|
180
|
+
region: 'Australia',
|
|
181
|
+
optionName: 'Junior',
|
|
182
|
+
amountLabel: '10 USDC',
|
|
183
|
+
timestamp: Date.UTC(2026, 7, 1, 0, 5, 0),
|
|
184
|
+
})).toBe('Generate my Loan Agreement for review:\n' +
|
|
185
|
+
'Professional Fee Funding · Australia · Junior · 10 USDC.\n' +
|
|
186
|
+
'Request made 1 August 2026, 00:05 UTC.\n' +
|
|
187
|
+
'This request does not commit me to lend.');
|
|
188
|
+
});
|
|
189
|
+
it('separates the line-2 fields with U+00B7 surrounded by single spaces', () => {
|
|
190
|
+
const message = buildLoanAgreementSignMessage({
|
|
191
|
+
strategyName: 'A',
|
|
192
|
+
region: 'B',
|
|
193
|
+
optionName: 'C',
|
|
194
|
+
amountLabel: 'D',
|
|
195
|
+
timestamp: 1785313320000,
|
|
196
|
+
});
|
|
197
|
+
expect(message.split('\n')[1]).toBe('A · B · C · D.');
|
|
198
|
+
expect(message.split('\n')).toHaveLength(4);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
describe('buildLegacyContractRequestMessage', () => {
|
|
202
|
+
const ADDRESS = '0x4c0d92e9c862B58b0FFeAAD031004A049d2c360D';
|
|
203
|
+
it('builds the exact legacy template with a lowercased address', () => {
|
|
204
|
+
expect(buildLegacyContractRequestMessage(ADDRESS, 1785313320000)).toBe('I request contract content for ' +
|
|
205
|
+
'0x4c0d92e9c862b58b0ffeaad031004a049d2c360d at 1785313320000.');
|
|
206
|
+
});
|
|
207
|
+
it('is insensitive to the casing the caller hands over', () => {
|
|
208
|
+
expect(buildLegacyContractRequestMessage(ADDRESS, 1)).toBe(buildLegacyContractRequestMessage(ADDRESS.toLowerCase(), 1));
|
|
209
|
+
});
|
|
210
|
+
it('prints the timestamp verbatim — no seconds/ms normalisation', () => {
|
|
211
|
+
// The backend echoes the body's `timestamp` into the string it
|
|
212
|
+
// verifies, so whatever is signed must be what is sent.
|
|
213
|
+
expect(buildLegacyContractRequestMessage(ADDRESS, 1785313320)).toBe('I request contract content for ' +
|
|
214
|
+
'0x4c0d92e9c862b58b0ffeaad031004a049d2c360d at 1785313320.');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
describe('buildFullNameRequestMessage', () => {
|
|
218
|
+
const ADDRESS = '0x26b5b8060A704b0420734d5Ccd657384fb1366C2';
|
|
219
|
+
it('builds the exact full-name template with a lowercased address', () => {
|
|
220
|
+
expect(buildFullNameRequestMessage(ADDRESS, 1785313320000)).toBe('I request my full name for ' +
|
|
221
|
+
'0x26b5b8060a704b0420734d5ccd657384fb1366c2 at 1785313320000.');
|
|
222
|
+
});
|
|
223
|
+
it('is a different message from the contract-content one', () => {
|
|
224
|
+
expect(buildFullNameRequestMessage(ADDRESS, 1)).not.toBe(buildLegacyContractRequestMessage(ADDRESS, 1));
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
describe('parseFormattedMessage', () => {
|
|
228
|
+
it('parses the server’s JSON-encoded tree', () => {
|
|
229
|
+
const parsed = parseFormattedMessage(JSON.stringify({ intro: 'x', 'subheader-1': { title: 't' } }));
|
|
230
|
+
expect(parsed === null || parsed === void 0 ? void 0 : parsed.intro).toBe('x');
|
|
231
|
+
});
|
|
232
|
+
it('returns null on malformed JSON so a renderer can fall back', () => {
|
|
233
|
+
expect(parseFormattedMessage('not json')).toBeNull();
|
|
234
|
+
expect(parseFormattedMessage('')).toBeNull();
|
|
235
|
+
});
|
|
236
|
+
it('returns null for a JSON scalar, which is not a contract tree', () => {
|
|
237
|
+
expect(parseFormattedMessage('42')).toBeNull();
|
|
238
|
+
expect(parseFormattedMessage('null')).toBeNull();
|
|
239
|
+
expect(parseFormattedMessage('"a string"')).toBeNull();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
describe('asContractType', () => {
|
|
243
|
+
it('narrows the wire string to the two encoded types', () => {
|
|
244
|
+
expect(asContractType('exempt')).toBe('exempt');
|
|
245
|
+
expect(asContractType('retail')).toBe('retail');
|
|
246
|
+
});
|
|
247
|
+
it('falls back to retail for anything else', () => {
|
|
248
|
+
// Retail is the stricter contract; an unrecognised type must not be
|
|
249
|
+
// silently treated as the exempt one.
|
|
250
|
+
expect(asContractType('')).toBe('retail');
|
|
251
|
+
expect(asContractType('Exempt')).toBe('retail');
|
|
252
|
+
expect(asContractType('whatever')).toBe('retail');
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
//# sourceMappingURL=loan-contract.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loan-contract.test.js","sourceRoot":"","sources":["../../src/domain/loan-contract.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { netEffectiveApy } from './rates';
|
|
2
|
+
import { trancheHasCapacity } from './tranches';
|
|
3
|
+
/**
|
|
4
|
+
* The highest FTD-inclusive upper bound across a pool's tranches, or `0` when
|
|
5
|
+
* none is usable. The sort key behind `selectVisiblePools`.
|
|
6
|
+
*/
|
|
7
|
+
export function poolMaxApy(pool) {
|
|
8
|
+
let hi = 0;
|
|
9
|
+
for (const t of pool.tranches) {
|
|
10
|
+
const v = parseFloat(t.maxApy);
|
|
11
|
+
if (Number.isFinite(v) && v > hi)
|
|
12
|
+
hi = v;
|
|
13
|
+
}
|
|
14
|
+
return hi;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A lending grid's visible pool set: *active* and *not yet oversubscribed*
|
|
18
|
+
* pools, sorted with has-capacity strategies first, then by highest max APY.
|
|
19
|
+
*
|
|
20
|
+
* Shared so a server-rendered payload and a client re-fetch can never
|
|
21
|
+
* disagree — kasu-ui's SSR fetcher and client hook used to carry verbatim
|
|
22
|
+
* copies of this filter + sort, and any drift surfaced as an SSR→CSR content
|
|
23
|
+
* flip on the lending page.
|
|
24
|
+
*
|
|
25
|
+
* Pure: operates on a fresh array (`filter` output), so the in-place `sort`
|
|
26
|
+
* does not mutate the caller's input.
|
|
27
|
+
*/
|
|
28
|
+
export function selectVisiblePools(all) {
|
|
29
|
+
const active = all.filter((p) => p.isActive && !p.isOversubscribed);
|
|
30
|
+
return active.sort((a, b) => {
|
|
31
|
+
const aHas = a.tranches.some(trancheHasCapacity) ? 1 : 0;
|
|
32
|
+
const bHas = b.tranches.some(trancheHasCapacity) ? 1 : 0;
|
|
33
|
+
if (aHas !== bHas)
|
|
34
|
+
return bHas - aHas;
|
|
35
|
+
return poolMaxApy(b) - poolMaxApy(a);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// `tranches` is typed as a required array but arrives from the subgraph, so
|
|
39
|
+
// both scanners below read it as possibly missing — a pool with no tranche
|
|
40
|
+
// list contributes nothing instead of throwing. Mirrors the `?? []` the
|
|
41
|
+
// kasu-ui originals carried.
|
|
42
|
+
function tranchesOf(tranches) {
|
|
43
|
+
return tranches !== null && tranches !== void 0 ? tranches : [];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The highest-yielding tranche that still has capacity, across all pools.
|
|
47
|
+
*
|
|
48
|
+
* Compares `tranche.apy` — the base variable rate — NOT `minApy`/`maxApy`,
|
|
49
|
+
* which the data service widens with fixed-term-deposit variants. A headline
|
|
50
|
+
* naming one tranche should advertise the rate every depositor gets, not an
|
|
51
|
+
* FTD-locked uplift.
|
|
52
|
+
*
|
|
53
|
+
* Capacity is gated by the shared `trancheHasCapacity` (≥ 1 stable unit), so
|
|
54
|
+
* full tranches never win. Ties on APY break toward more remaining capacity.
|
|
55
|
+
* Returns `null` when no tranche qualifies (empty list, all full, or all rates
|
|
56
|
+
* unset) — callers fall back to static copy.
|
|
57
|
+
*/
|
|
58
|
+
export function pickHighestYieldTranche(pools) {
|
|
59
|
+
if (!(pools === null || pools === void 0 ? void 0 : pools.length))
|
|
60
|
+
return null;
|
|
61
|
+
let best = null;
|
|
62
|
+
for (const pool of pools) {
|
|
63
|
+
for (const tranche of tranchesOf(pool.tranches)) {
|
|
64
|
+
if (!trancheHasCapacity(tranche))
|
|
65
|
+
continue;
|
|
66
|
+
const apy = parseFloat(tranche.apy);
|
|
67
|
+
if (!Number.isFinite(apy) || apy <= 0)
|
|
68
|
+
continue;
|
|
69
|
+
if (!best) {
|
|
70
|
+
best = { pool, tranche, apy };
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const capacity = parseFloat(tranche.poolCapacity);
|
|
74
|
+
const bestCapacity = parseFloat(best.tranche.poolCapacity);
|
|
75
|
+
if (apy > best.apy || (apy === best.apy && capacity > bestCapacity)) {
|
|
76
|
+
best = { pool, tranche, apy };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return best;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The highest NET Effective Interest Rate on offer across the pools passed in
|
|
84
|
+
* — the ceiling behind an "up to …" claim. The numeric core of kasu-ui's
|
|
85
|
+
* `formatMaxNetRate`.
|
|
86
|
+
*
|
|
87
|
+
* `null` when there is nothing to quote, and callers must then DROP the claim
|
|
88
|
+
* rather than print a placeholder: this feeds text that gets posted publicly.
|
|
89
|
+
*
|
|
90
|
+
* Scope follows the caller's pool list, which is normally keyed to one chain —
|
|
91
|
+
* so a lender on XDC AUDD quotes the AUDD ceiling, not Base's. That is the
|
|
92
|
+
* intended reading: they are sharing the deployment they are on.
|
|
93
|
+
*
|
|
94
|
+
* Reads `maxApy` — the FTD-inclusive upper bound — unlike
|
|
95
|
+
* `pickHighestYieldTranche` above, which names one tranche's variable rate. A
|
|
96
|
+
* ceiling claim and a specific tranche's rate are different statements, and
|
|
97
|
+
* the ceiling must agree with the top of `netTrancheApyBounds`, which reads
|
|
98
|
+
* the same field. `apy` is taken into the comparison too: `maxApy` is derived
|
|
99
|
+
* from the base rate plus any fixed-term configs, so it can never be lower,
|
|
100
|
+
* and a tranche that carries no `maxApy` at all still contributes its base
|
|
101
|
+
* rate instead of dropping out.
|
|
102
|
+
*
|
|
103
|
+
* Capacity-gated by `trancheHasCapacity` — we never advertise a rate no lender
|
|
104
|
+
* could take up.
|
|
105
|
+
*
|
|
106
|
+
* FAIL CLOSED: an out-of-domain fee, or a net rate that is non-finite or ≤ 0,
|
|
107
|
+
* returns `null`. The gross figure is never a substitute, and a caller that
|
|
108
|
+
* does not yet know the fee must not pass `0`.
|
|
109
|
+
*
|
|
110
|
+
* @param feePercent the chain's performance fee, 0..100 — never a fraction.
|
|
111
|
+
*/
|
|
112
|
+
export function maxNetRateCeiling(pools, feePercent) {
|
|
113
|
+
if (!(pools === null || pools === void 0 ? void 0 : pools.length))
|
|
114
|
+
return null;
|
|
115
|
+
let ceiling = 0;
|
|
116
|
+
for (const pool of pools) {
|
|
117
|
+
for (const tranche of tranchesOf(pool.tranches)) {
|
|
118
|
+
if (!trancheHasCapacity(tranche))
|
|
119
|
+
continue;
|
|
120
|
+
for (const raw of [tranche.maxApy, tranche.apy]) {
|
|
121
|
+
const apy = parseFloat(raw);
|
|
122
|
+
if (Number.isFinite(apy) && apy > ceiling)
|
|
123
|
+
ceiling = apy;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (ceiling <= 0)
|
|
128
|
+
return null;
|
|
129
|
+
const rate = netEffectiveApy(ceiling, feePercent);
|
|
130
|
+
if (!Number.isFinite(rate) || rate <= 0)
|
|
131
|
+
return null;
|
|
132
|
+
return rate;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=pools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pools.js","sourceRoot":"","sources":["../../src/domain/pools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|