@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.
Files changed (122) hide show
  1. package/README.md +128 -3
  2. package/dist/bundle.cjs.js +1119 -254
  3. package/dist/bundle.esm.js +1109 -255
  4. package/dist/domain/index.d.ts +3 -2
  5. package/dist/domain/index.js +2 -2
  6. package/dist/domain/index.js.map +1 -1
  7. package/dist/domain/requests.d.ts +42 -4
  8. package/dist/domain/requests.js +20 -4
  9. package/dist/domain/requests.js.map +1 -1
  10. package/dist/domain/wallet-errors.d.ts +63 -8
  11. package/dist/domain/wallet-errors.js +115 -16
  12. package/dist/domain/wallet-errors.js.map +1 -1
  13. package/dist/facade/deposits.js +2 -1
  14. package/dist/facade/deposits.js.map +1 -1
  15. package/dist/facade/flows.d.ts +95 -0
  16. package/dist/facade/flows.js +116 -0
  17. package/dist/facade/flows.js.map +1 -0
  18. package/dist/facade/index.d.ts +3 -0
  19. package/dist/facade/index.js +3 -0
  20. package/dist/facade/index.js.map +1 -1
  21. package/dist/facade/kasu.d.ts +6 -2
  22. package/dist/facade/kasu.js +28 -3
  23. package/dist/facade/kasu.js.map +1 -1
  24. package/dist/facade/read-only.d.ts +12 -0
  25. package/dist/facade/read-only.js +13 -0
  26. package/dist/facade/read-only.js.map +1 -0
  27. package/dist/flows/deposit-flow.d.ts +301 -0
  28. package/dist/flows/deposit-flow.js +358 -0
  29. package/dist/flows/deposit-flow.js.map +1 -0
  30. package/dist/flows/flow.d.ts +72 -0
  31. package/dist/flows/flow.js +110 -0
  32. package/dist/flows/flow.js.map +1 -0
  33. package/dist/flows/index.d.ts +19 -0
  34. package/dist/flows/index.js +17 -0
  35. package/dist/flows/index.js.map +1 -0
  36. package/dist/flows/observable.d.ts +59 -0
  37. package/dist/flows/observable.js +81 -0
  38. package/dist/flows/observable.js.map +1 -0
  39. package/dist/flows/withdraw-flow.d.ts +99 -0
  40. package/dist/flows/withdraw-flow.js +83 -0
  41. package/dist/flows/withdraw-flow.js.map +1 -0
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +6 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/services/DataService/data-service.js +0 -1
  46. package/dist/services/DataService/data-service.js.map +1 -1
  47. package/package.json +8 -2
  48. package/src/domain/index.ts +7 -1
  49. package/src/domain/requests.ts +57 -4
  50. package/src/domain/wallet-errors.ts +135 -18
  51. package/src/facade/deposits.ts +2 -3
  52. package/src/facade/flows.ts +172 -0
  53. package/src/facade/index.ts +8 -0
  54. package/src/facade/kasu.ts +43 -3
  55. package/src/facade/read-only.ts +13 -0
  56. package/src/flows/deposit-flow.ts +775 -0
  57. package/src/flows/flow.ts +108 -0
  58. package/src/flows/index.ts +45 -0
  59. package/src/flows/observable.ts +97 -0
  60. package/src/flows/withdraw-flow.ts +210 -0
  61. package/src/index.ts +7 -0
  62. package/src/services/DataService/data-service.ts +0 -1
  63. package/dist/domain/au-minimum.test.d.ts +0 -1
  64. package/dist/domain/au-minimum.test.js +0 -202
  65. package/dist/domain/au-minimum.test.js.map +0 -1
  66. package/dist/domain/deposit-bounds.test.d.ts +0 -1
  67. package/dist/domain/deposit-bounds.test.js +0 -135
  68. package/dist/domain/deposit-bounds.test.js.map +0 -1
  69. package/dist/domain/loan-contract.test.d.ts +0 -1
  70. package/dist/domain/loan-contract.test.js +0 -255
  71. package/dist/domain/loan-contract.test.js.map +0 -1
  72. package/dist/domain/partners.test.d.ts +0 -1
  73. package/dist/domain/partners.test.js +0 -53
  74. package/dist/domain/partners.test.js.map +0 -1
  75. package/dist/domain/pools.test.d.ts +0 -1
  76. package/dist/domain/pools.test.js +0 -184
  77. package/dist/domain/pools.test.js.map +0 -1
  78. package/dist/domain/rates.test.d.ts +0 -1
  79. package/dist/domain/rates.test.js +0 -181
  80. package/dist/domain/rates.test.js.map +0 -1
  81. package/dist/domain/requests.test.d.ts +0 -1
  82. package/dist/domain/requests.test.js +0 -470
  83. package/dist/domain/requests.test.js.map +0 -1
  84. package/dist/domain/settlement.test.d.ts +0 -1
  85. package/dist/domain/settlement.test.js +0 -152
  86. package/dist/domain/settlement.test.js.map +0 -1
  87. package/dist/domain/tranche-display-name.test.d.ts +0 -1
  88. package/dist/domain/tranche-display-name.test.js +0 -58
  89. package/dist/domain/tranche-display-name.test.js.map +0 -1
  90. package/dist/domain/tranches.test.d.ts +0 -1
  91. package/dist/domain/tranches.test.js +0 -206
  92. package/dist/domain/tranches.test.js.map +0 -1
  93. package/dist/domain/wallet-errors.test.d.ts +0 -1
  94. package/dist/domain/wallet-errors.test.js +0 -71
  95. package/dist/domain/wallet-errors.test.js.map +0 -1
  96. package/dist/facade/config.test.d.ts +0 -1
  97. package/dist/facade/config.test.js +0 -216
  98. package/dist/facade/config.test.js.map +0 -1
  99. package/dist/facade/facade.test.d.ts +0 -1
  100. package/dist/facade/facade.test.js +0 -278
  101. package/dist/facade/facade.test.js.map +0 -1
  102. package/dist/services/Locking/calculate-apy.test.d.ts +0 -1
  103. package/dist/services/Locking/calculate-apy.test.js +0 -41
  104. package/dist/services/Locking/calculate-apy.test.js.map +0 -1
  105. package/dist/tests/sample.test.d.ts +0 -1
  106. package/dist/tests/sample.test.js +0 -59
  107. package/dist/tests/sample.test.js.map +0 -1
  108. package/src/domain/au-minimum.test.ts +0 -371
  109. package/src/domain/deposit-bounds.test.ts +0 -200
  110. package/src/domain/loan-contract.test.ts +0 -343
  111. package/src/domain/partners.test.ts +0 -83
  112. package/src/domain/pools.test.ts +0 -260
  113. package/src/domain/rates.test.ts +0 -254
  114. package/src/domain/requests.test.ts +0 -653
  115. package/src/domain/settlement.test.ts +0 -198
  116. package/src/domain/tranche-display-name.test.ts +0 -96
  117. package/src/domain/tranches.test.ts +0 -300
  118. package/src/domain/wallet-errors.test.ts +0 -100
  119. package/src/facade/config.test.ts +0 -265
  120. package/src/facade/facade.test.ts +0 -374
  121. package/src/services/Locking/calculate-apy.test.ts +0 -52
  122. 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":"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"}
@@ -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