@kasufinance/kasu-sdk 2.6.0 → 2.7.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/README.md +128 -3
- package/dist/bundle.cjs.js +1119 -254
- package/dist/bundle.esm.js +1109 -255
- package/dist/domain/index.d.ts +3 -2
- package/dist/domain/index.js +2 -2
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/requests.d.ts +42 -4
- package/dist/domain/requests.js +20 -4
- package/dist/domain/requests.js.map +1 -1
- package/dist/domain/wallet-errors.d.ts +63 -8
- package/dist/domain/wallet-errors.js +115 -16
- package/dist/domain/wallet-errors.js.map +1 -1
- package/dist/facade/deposits.js +2 -1
- package/dist/facade/deposits.js.map +1 -1
- package/dist/facade/flows.d.ts +95 -0
- package/dist/facade/flows.js +116 -0
- package/dist/facade/flows.js.map +1 -0
- package/dist/facade/index.d.ts +3 -0
- package/dist/facade/index.js +3 -0
- package/dist/facade/index.js.map +1 -1
- package/dist/facade/kasu.d.ts +6 -2
- package/dist/facade/kasu.js +28 -3
- package/dist/facade/kasu.js.map +1 -1
- package/dist/facade/read-only.d.ts +12 -0
- package/dist/facade/read-only.js +13 -0
- package/dist/facade/read-only.js.map +1 -0
- package/dist/flows/deposit-flow.d.ts +301 -0
- package/dist/flows/deposit-flow.js +358 -0
- package/dist/flows/deposit-flow.js.map +1 -0
- package/dist/flows/flow.d.ts +72 -0
- package/dist/flows/flow.js +110 -0
- package/dist/flows/flow.js.map +1 -0
- package/dist/flows/index.d.ts +19 -0
- package/dist/flows/index.js +17 -0
- package/dist/flows/index.js.map +1 -0
- package/dist/flows/observable.d.ts +59 -0
- package/dist/flows/observable.js +81 -0
- package/dist/flows/observable.js.map +1 -0
- package/dist/flows/withdraw-flow.d.ts +99 -0
- package/dist/flows/withdraw-flow.js +83 -0
- package/dist/flows/withdraw-flow.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/services/DataService/data-service.js +0 -1
- package/dist/services/DataService/data-service.js.map +1 -1
- package/package.json +8 -2
- package/src/domain/index.ts +7 -1
- package/src/domain/requests.ts +57 -4
- package/src/domain/wallet-errors.ts +135 -18
- package/src/facade/deposits.ts +2 -3
- package/src/facade/flows.ts +172 -0
- package/src/facade/index.ts +8 -0
- package/src/facade/kasu.ts +43 -3
- package/src/facade/read-only.ts +13 -0
- package/src/flows/deposit-flow.ts +775 -0
- package/src/flows/flow.ts +108 -0
- package/src/flows/index.ts +45 -0
- package/src/flows/observable.ts +97 -0
- package/src/flows/withdraw-flow.ts +210 -0
- package/src/index.ts +7 -0
- package/src/services/DataService/data-service.ts +0 -1
- package/dist/domain/au-minimum.test.d.ts +0 -1
- package/dist/domain/au-minimum.test.js +0 -202
- package/dist/domain/au-minimum.test.js.map +0 -1
- package/dist/domain/deposit-bounds.test.d.ts +0 -1
- package/dist/domain/deposit-bounds.test.js +0 -135
- package/dist/domain/deposit-bounds.test.js.map +0 -1
- package/dist/domain/loan-contract.test.d.ts +0 -1
- package/dist/domain/loan-contract.test.js +0 -255
- package/dist/domain/loan-contract.test.js.map +0 -1
- package/dist/domain/partners.test.d.ts +0 -1
- package/dist/domain/partners.test.js +0 -53
- package/dist/domain/partners.test.js.map +0 -1
- package/dist/domain/pools.test.d.ts +0 -1
- package/dist/domain/pools.test.js +0 -184
- package/dist/domain/pools.test.js.map +0 -1
- package/dist/domain/rates.test.d.ts +0 -1
- package/dist/domain/rates.test.js +0 -181
- package/dist/domain/rates.test.js.map +0 -1
- package/dist/domain/requests.test.d.ts +0 -1
- package/dist/domain/requests.test.js +0 -470
- package/dist/domain/requests.test.js.map +0 -1
- package/dist/domain/settlement.test.d.ts +0 -1
- package/dist/domain/settlement.test.js +0 -152
- package/dist/domain/settlement.test.js.map +0 -1
- package/dist/domain/tranche-display-name.test.d.ts +0 -1
- package/dist/domain/tranche-display-name.test.js +0 -58
- package/dist/domain/tranche-display-name.test.js.map +0 -1
- package/dist/domain/tranches.test.d.ts +0 -1
- package/dist/domain/tranches.test.js +0 -206
- package/dist/domain/tranches.test.js.map +0 -1
- package/dist/domain/wallet-errors.test.d.ts +0 -1
- package/dist/domain/wallet-errors.test.js +0 -71
- package/dist/domain/wallet-errors.test.js.map +0 -1
- package/dist/facade/config.test.d.ts +0 -1
- package/dist/facade/config.test.js +0 -216
- package/dist/facade/config.test.js.map +0 -1
- package/dist/facade/facade.test.d.ts +0 -1
- package/dist/facade/facade.test.js +0 -278
- package/dist/facade/facade.test.js.map +0 -1
- package/dist/services/Locking/calculate-apy.test.d.ts +0 -1
- package/dist/services/Locking/calculate-apy.test.js +0 -41
- package/dist/services/Locking/calculate-apy.test.js.map +0 -1
- package/dist/tests/sample.test.d.ts +0 -1
- package/dist/tests/sample.test.js +0 -59
- package/dist/tests/sample.test.js.map +0 -1
- package/src/domain/au-minimum.test.ts +0 -371
- package/src/domain/deposit-bounds.test.ts +0 -200
- package/src/domain/loan-contract.test.ts +0 -343
- package/src/domain/partners.test.ts +0 -83
- package/src/domain/pools.test.ts +0 -260
- package/src/domain/rates.test.ts +0 -254
- package/src/domain/requests.test.ts +0 -653
- package/src/domain/settlement.test.ts +0 -198
- package/src/domain/tranche-display-name.test.ts +0 -96
- package/src/domain/tranches.test.ts +0 -300
- package/src/domain/wallet-errors.test.ts +0 -100
- package/src/facade/config.test.ts +0 -265
- package/src/facade/facade.test.ts +0 -374
- package/src/services/Locking/calculate-apy.test.ts +0 -52
- package/src/tests/sample.test.ts +0 -63
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { AU_ALPHA3, AU_MIN_CUMULATIVE_BY_STABLE, auMinimumRemaining, auThresholdFor, isAuMinimumExempt, isAustralianKyc, parseMinorUnits, } from './au-minimum';
|
|
2
|
-
/**
|
|
3
|
-
* Ported from kasu-ui
|
|
4
|
-
* `src/features/lending/lib/au-lending-restriction.test.ts`, minus the two
|
|
5
|
-
* cases that pin toast copy — those strings stay in the applications.
|
|
6
|
-
*
|
|
7
|
-
* What these pin is the arithmetic: integer bigint minor units and a
|
|
8
|
-
* truncating parse, so no consumer can disagree with another at the
|
|
9
|
-
* 360,000 / 500,000 boundary.
|
|
10
|
-
*/
|
|
11
|
-
// 6dp on every current deployment (USDC on Base/XDC, AUDD on XDC).
|
|
12
|
-
const DP = 6;
|
|
13
|
-
const n = (v) => BigInt(v);
|
|
14
|
-
describe('isAustralianKyc', () => {
|
|
15
|
-
it('accepts the alpha-3 code in any casing, with surrounding whitespace', () => {
|
|
16
|
-
expect(isAustralianKyc('AUS')).toBe(true);
|
|
17
|
-
expect(isAustralianKyc('aus')).toBe(true);
|
|
18
|
-
expect(isAustralianKyc(' AUS ')).toBe(true);
|
|
19
|
-
expect(isAustralianKyc('Aus')).toBe(true);
|
|
20
|
-
});
|
|
21
|
-
it('rejects the alpha-2 code — the collapsed KYC field is alpha-3', () => {
|
|
22
|
-
// Deliberate: a bare 'AU' must NOT restrict. The KYC claim is
|
|
23
|
-
// normalised to alpha-3 before it ever reaches this rule.
|
|
24
|
-
expect(isAustralianKyc('AU')).toBe(false);
|
|
25
|
-
});
|
|
26
|
-
it('rejects near-misses and absent countries (fail-open)', () => {
|
|
27
|
-
expect(isAustralianKyc('AUT')).toBe(false);
|
|
28
|
-
expect(isAustralianKyc('')).toBe(false);
|
|
29
|
-
expect(isAustralianKyc(undefined)).toBe(false);
|
|
30
|
-
expect(isAustralianKyc(null)).toBe(false);
|
|
31
|
-
});
|
|
32
|
-
it('exports the alpha-3 constant it compares against', () => {
|
|
33
|
-
expect(AU_ALPHA3).toBe('AUS');
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
describe('auThresholdFor', () => {
|
|
37
|
-
it('returns the per-stable whole-unit minimum, case-insensitively', () => {
|
|
38
|
-
expect(auThresholdFor('USDC')).toBe(360000);
|
|
39
|
-
expect(auThresholdFor('usdc')).toBe(360000);
|
|
40
|
-
expect(auThresholdFor(' USDC ')).toBe(360000);
|
|
41
|
-
expect(auThresholdFor('AUDD')).toBe(500000);
|
|
42
|
-
expect(auThresholdFor('audd')).toBe(500000);
|
|
43
|
-
});
|
|
44
|
-
it('fails open for an unlisted stable or a missing symbol', () => {
|
|
45
|
-
expect(auThresholdFor('DAI')).toBeUndefined();
|
|
46
|
-
expect(auThresholdFor(undefined)).toBeUndefined();
|
|
47
|
-
expect(auThresholdFor(null)).toBeUndefined();
|
|
48
|
-
expect(auThresholdFor('')).toBeUndefined();
|
|
49
|
-
});
|
|
50
|
-
it('exposes the table kasu-backend’s minimums must match', () => {
|
|
51
|
-
expect(AU_MIN_CUMULATIVE_BY_STABLE).toEqual({
|
|
52
|
-
USDC: 360000,
|
|
53
|
-
AUDD: 500000,
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
describe('parseMinorUnits (6dp)', () => {
|
|
58
|
-
it('parses whole and partial decimal input', () => {
|
|
59
|
-
expect(parseMinorUnits('0', DP)).toBe(n('0'));
|
|
60
|
-
expect(parseMinorUnits('1', DP)).toBe(n('1000000'));
|
|
61
|
-
// Mid-typing states an amount field really produces.
|
|
62
|
-
expect(parseMinorUnits('10.', DP)).toBe(n('10000000'));
|
|
63
|
-
expect(parseMinorUnits('.5', DP)).toBe(n('500000'));
|
|
64
|
-
expect(parseMinorUnits('1.5', DP)).toBe(n('1500000'));
|
|
65
|
-
});
|
|
66
|
-
it('TRUNCATES excess precision — a position can never be inflated into passing', () => {
|
|
67
|
-
expect(parseMinorUnits('1.23456789', DP)).toBe(n('1234567'));
|
|
68
|
-
});
|
|
69
|
-
it('returns null for empty, non-numeric, negative and absent input', () => {
|
|
70
|
-
expect(parseMinorUnits('', DP)).toBeNull();
|
|
71
|
-
expect(parseMinorUnits(' ', DP)).toBeNull();
|
|
72
|
-
expect(parseMinorUnits('abc', DP)).toBeNull();
|
|
73
|
-
expect(parseMinorUnits('-5', DP)).toBeNull();
|
|
74
|
-
// A lone separator matches the shape but carries no digits.
|
|
75
|
-
expect(parseMinorUnits('.', DP)).toBeNull();
|
|
76
|
-
expect(parseMinorUnits(null, DP)).toBeNull();
|
|
77
|
-
expect(parseMinorUnits(undefined, DP)).toBeNull();
|
|
78
|
-
});
|
|
79
|
-
it('accepts numbers as well as strings', () => {
|
|
80
|
-
expect(parseMinorUnits(360000, DP)).toBe(n('360000000000'));
|
|
81
|
-
expect(parseMinorUnits(0, DP)).toBe(n('0'));
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
// `address` and `exemptAddresses` are required keys on AuMinimumInput (a
|
|
85
|
-
// refactor must not be able to drop either silently), so the shared fixture
|
|
86
|
-
// supplies the un-exempted defaults and the exemption cases override them.
|
|
87
|
-
const BASE = {
|
|
88
|
-
stableSymbol: 'USDC',
|
|
89
|
-
decimals: DP,
|
|
90
|
-
address: undefined,
|
|
91
|
-
exemptAddresses: undefined,
|
|
92
|
-
};
|
|
93
|
-
describe('auMinimumRemaining — fail-open cases return 0 (no raised minimum)', () => {
|
|
94
|
-
it('is 0 when the KYC country is absent', () => {
|
|
95
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: undefined, existingDeposited: '0' }))).toBe(0);
|
|
96
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: null, existingDeposited: '0' }))).toBe(0);
|
|
97
|
-
});
|
|
98
|
-
it('is 0 for a non-Australian lender', () => {
|
|
99
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'USA', existingDeposited: '0' }))).toBe(0);
|
|
100
|
-
});
|
|
101
|
-
it('is 0 on a deployment whose stable has no configured threshold', () => {
|
|
102
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { stableSymbol: 'DAI', country: 'AUS', existingDeposited: '0' }))).toBe(0);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
describe('auMinimumRemaining — Australian lender', () => {
|
|
106
|
-
it('demands the FULL threshold while the position is unknown (strict-until-loaded)', () => {
|
|
107
|
-
// `undefined` is not a zero position — it is an unread one. Treating
|
|
108
|
-
// it as 0 keeps the floor at its strictest until the read lands, so
|
|
109
|
-
// the field never advertises a minimum lower than the backend accepts.
|
|
110
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: undefined }))).toBe(360000);
|
|
111
|
-
});
|
|
112
|
-
it('is 0 once the existing position exactly meets the threshold (inclusive)', () => {
|
|
113
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: '360000' }))).toBe(0);
|
|
114
|
-
});
|
|
115
|
-
it('is 0 once the existing position exceeds the threshold', () => {
|
|
116
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: '400000' }))).toBe(0);
|
|
117
|
-
});
|
|
118
|
-
it('returns the remainder for a partial position', () => {
|
|
119
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: '120000' }))).toBe(240000);
|
|
120
|
-
});
|
|
121
|
-
it('keeps sub-unit precision — one minor unit short still demands one minor unit', () => {
|
|
122
|
-
// 359,999.999999 lent → 0.000001 USDC left to reach 360,000. The
|
|
123
|
-
// bigint arithmetic must not round that away into a 0 (unrestricted).
|
|
124
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: '359999.999999' }))).toBe(0.000001);
|
|
125
|
-
});
|
|
126
|
-
it('accepts a numeric existing position, not just a string', () => {
|
|
127
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: 120000 }))).toBe(240000);
|
|
128
|
-
});
|
|
129
|
-
it('uses the 500,000 threshold on AUDD deployments', () => {
|
|
130
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { stableSymbol: 'AUDD', country: 'AUS', existingDeposited: 0 }))).toBe(500000);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
// Deployment configuration the caller supplies — see `isAuMinimumExempt`. The
|
|
134
|
-
// real list is never shipped in this public package; these are stand-ins.
|
|
135
|
-
const EXEMPT_CHECKSUMMED = '0x1111111111111111111111111111111111111111';
|
|
136
|
-
const EXEMPT_LOWER = EXEMPT_CHECKSUMMED.toLowerCase();
|
|
137
|
-
const OTHER_EXEMPT = '0x2222222222222222222222222222222222222222';
|
|
138
|
-
const NOT_EXEMPT = '0x3333333333333333333333333333333333333333';
|
|
139
|
-
const LIST = [EXEMPT_LOWER, OTHER_EXEMPT.toLowerCase()];
|
|
140
|
-
describe('isAuMinimumExempt', () => {
|
|
141
|
-
it('matches a listed wallet in any casing, with surrounding whitespace', () => {
|
|
142
|
-
expect(isAuMinimumExempt(EXEMPT_CHECKSUMMED, LIST)).toBe(true);
|
|
143
|
-
expect(isAuMinimumExempt(EXEMPT_LOWER, LIST)).toBe(true);
|
|
144
|
-
expect(isAuMinimumExempt(EXEMPT_LOWER.toUpperCase(), LIST)).toBe(true);
|
|
145
|
-
expect(isAuMinimumExempt(` ${OTHER_EXEMPT} `, LIST)).toBe(true);
|
|
146
|
-
});
|
|
147
|
-
it('matches a list entry stored in mixed case too', () => {
|
|
148
|
-
// The rule normalises both sides, so a caller that reads its list
|
|
149
|
-
// straight out of configuration cannot be caught out by casing.
|
|
150
|
-
expect(isAuMinimumExempt(EXEMPT_LOWER, [EXEMPT_CHECKSUMMED.toUpperCase()])).toBe(true);
|
|
151
|
-
});
|
|
152
|
-
it('fails CLOSED on anything unlisted or not a string', () => {
|
|
153
|
-
expect(isAuMinimumExempt(NOT_EXEMPT, LIST)).toBe(false);
|
|
154
|
-
expect(isAuMinimumExempt('', LIST)).toBe(false);
|
|
155
|
-
expect(isAuMinimumExempt(' ', LIST)).toBe(false);
|
|
156
|
-
expect(isAuMinimumExempt(undefined, LIST)).toBe(false);
|
|
157
|
-
expect(isAuMinimumExempt(null, LIST)).toBe(false);
|
|
158
|
-
// A prefix of, or an extension to, a listed address is another wallet.
|
|
159
|
-
expect(isAuMinimumExempt(EXEMPT_LOWER.slice(0, -1), LIST)).toBe(false);
|
|
160
|
-
expect(isAuMinimumExempt(`${EXEMPT_LOWER}00`, LIST)).toBe(false);
|
|
161
|
-
});
|
|
162
|
-
it('fails CLOSED when no list was supplied', () => {
|
|
163
|
-
expect(isAuMinimumExempt(EXEMPT_LOWER, undefined)).toBe(false);
|
|
164
|
-
expect(isAuMinimumExempt(EXEMPT_LOWER, null)).toBe(false);
|
|
165
|
-
expect(isAuMinimumExempt(EXEMPT_LOWER, [])).toBe(false);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
describe('auMinimumRemaining — named-address exemption', () => {
|
|
169
|
-
it('is 0 for an exempt wallet that would otherwise owe the full threshold', () => {
|
|
170
|
-
for (const address of [
|
|
171
|
-
EXEMPT_CHECKSUMMED,
|
|
172
|
-
EXEMPT_LOWER,
|
|
173
|
-
OTHER_EXEMPT,
|
|
174
|
-
]) {
|
|
175
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', address, exemptAddresses: LIST, existingDeposited: 0 }))).toBe(0);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
it('is 0 on AUDD too, where the threshold is the larger 500,000', () => {
|
|
179
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { stableSymbol: 'AUDD', country: 'AUS', address: EXEMPT_LOWER, exemptAddresses: LIST, existingDeposited: 0 }))).toBe(0);
|
|
180
|
-
});
|
|
181
|
-
it('overrides an UNKNOWN position — the strict full-threshold case', () => {
|
|
182
|
-
// `undefined` is what a caller passes while the on-chain read is in
|
|
183
|
-
// flight, and it normally yields the whole threshold. An exemption
|
|
184
|
-
// outranks it, so an exempt lender never sees a floor that later drops.
|
|
185
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', address: EXEMPT_LOWER, exemptAddresses: LIST, existingDeposited: undefined }))).toBe(0);
|
|
186
|
-
});
|
|
187
|
-
it('leaves a non-exempt Australian on the full threshold', () => {
|
|
188
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', address: NOT_EXEMPT, exemptAddresses: LIST, existingDeposited: 0 }))).toBe(360000);
|
|
189
|
-
});
|
|
190
|
-
it('treats an absent address, or an absent list, as NOT exempt', () => {
|
|
191
|
-
// Both keys are required; either VALUE may be absent, and when it is
|
|
192
|
-
// the rule applies exactly as it did before exemptions existed.
|
|
193
|
-
for (const address of [undefined, null]) {
|
|
194
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', address, exemptAddresses: LIST, existingDeposited: 0 }))).toBe(360000);
|
|
195
|
-
}
|
|
196
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', address: EXEMPT_LOWER, exemptAddresses: undefined, existingDeposited: 0 }))).toBe(360000);
|
|
197
|
-
});
|
|
198
|
-
it('is 0 for a non-Australian at an exempt address, as it always was', () => {
|
|
199
|
-
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'FRA', address: EXEMPT_LOWER, exemptAddresses: LIST, existingDeposited: 0 }))).toBe(0);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
//# sourceMappingURL=au-minimum.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"au-minimum.test.js","sourceRoot":"","sources":["../../src/domain/au-minimum.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,135 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deposit-bounds.test.js","sourceRoot":"","sources":["../../src/domain/deposit-bounds.test.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAIjC,OAAO,EACH,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAC3B,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;GAQG;AAEH,SAAS,OAAO,CAAC,IAA0B;IACvC,OAAO,gBACH,cAAc,EAAE,KAAK,EACrB,cAAc,EAAE,QAAQ,EACxB,YAAY,EAAE,QAAQ,IACnB,IAAI,CACgB,CAAC;AAChC,CAAC;AAED,MAAM,SAAS,GAAG,EAAE,CAAC,MAAM,CAAC;IACxB,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,IAAI;IACT,KAAK,EAAE,IAAI;IACX,iBAAiB,EAAE,IAAI;CAC1B,CAAC,CAAC;AACH,kFAAkF;AAClF,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAE3C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC3C,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAChD,MAAM,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACzC,MAAM,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC1C,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC3C,MAAM,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACrD,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC1D,iEAAiE;QACjE,MAAM,CACF,oBAAoB,CAChB,OAAO,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,CAClE,CACJ,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QACxD,MAAM,CACF,oBAAoB,CAChB,OAAO,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,CAChE,CACJ,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACrE,MAAM,CACF,oBAAoB,CAChB,OAAO,CAAC;YACJ,cAAc,EAAE,GAAG;YACnB,cAAc,EAAE,GAAG;YACnB,YAAY,EAAE,QAAQ;SACzB,CAAC,CACL,CACJ,CAAC,OAAO,CAAC;YACN,UAAU,EAAE,2BAA2B;YACvC,UAAU,EAAE,2BAA2B;SAC1C,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QAC/E,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;YAC5C,UAAU,EAAE,2BAA2B;YACvC,UAAU,EAAE,2BAA2B;SAC1C,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACvD,MAAM,CACF,oBAAoB,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,CAAC;aAC1D,UAAU,CAClB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACV,MAAM,CACF,oBAAoB,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,CACnE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACtF,EAAE,CAAC,MAAM,CACL,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;YACzB,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,OAAO,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;YACvC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACpD,CAAC,CAAC,CACL,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mFAAmF,EAAE,GAAG,EAAE;QACzF,EAAE,CAAC,MAAM,CACL,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;YACzB,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,OAAO,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;YAC1C,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACpD,CAAC,CAAC,CACL,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACjF,EAAE,CAAC,MAAM,CACL,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE;YACzD,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,qBAAqB,CAC/C,UAAU,EACV,UAAU,CACb,CAAC;YACF,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrB,MAAM,CAAC,GAAG,CAAC,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC;YAC/C,MAAM,CAAC,GAAG,CAAC,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;YAC5C,MAAM,CAAC,GAAG,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACnC,CAAC,CAAC,CACL,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC7D,uEAAuE;QACvE,kEAAkE;QAClE,+CAA+C;QAC/C,MAAM,CAAC,qBAAqB,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,MAAM,CACF,qBAAqB,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC,OAAO,CAC7D,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,MAAM,CAAC,qBAAqB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,MAAM,CAAC,qBAAqB,CAAC,MAAO,EAAE,MAAO,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACnE,MAAM,CAAC,qBAAqB,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC;YAC3D,GAAG,EAAE,GAAG;YACR,GAAG,EAAE,SAAS;YACd,OAAO,EAAE,IAAI;SAChB,CAAC,CAAC;QACH,MAAM,CAAC,qBAAqB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;YACjD,GAAG,EAAE,GAAG;YACR,GAAG,EAAE,QAAQ;YACb,OAAO,EAAE,IAAI;SAChB,CAAC,CAAC;QACH,sEAAsE;QACtE,MAAM,CAAC,qBAAqB,CAAC,aAAa,EAAE,MAAO,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAC1D,SAAS,CACZ,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAC9D,EAAE,CAAC,MAAM,CACL,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE;YACzD,2DAA2D;YAC3D,6BAA6B;YAC7B,MAAM,CAAC,sBAAsB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CACvD,CAAC,qBAAqB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,OAAO,CACzD,CAAC;QACN,CAAC,CAAC,CACL,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpD,MAAM,CAAC,sBAAsB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,255 +0,0 @@
|
|
|
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
|