@kasufinance/kasu-sdk 2.5.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 (128) hide show
  1. package/README.md +144 -7
  2. package/dist/bundle.cjs.js +1667 -21
  3. package/dist/bundle.esm.js +1630 -22
  4. package/dist/domain/au-minimum.d.ts +135 -0
  5. package/dist/domain/au-minimum.js +154 -0
  6. package/dist/domain/au-minimum.js.map +1 -0
  7. package/dist/domain/index.d.ts +18 -3
  8. package/dist/domain/index.js +13 -3
  9. package/dist/domain/index.js.map +1 -1
  10. package/dist/domain/loan-contract.d.ts +174 -0
  11. package/dist/domain/loan-contract.js +160 -0
  12. package/dist/domain/loan-contract.js.map +1 -0
  13. package/dist/domain/requests.d.ts +219 -0
  14. package/dist/domain/requests.js +218 -0
  15. package/dist/domain/requests.js.map +1 -0
  16. package/dist/domain/settlement.d.ts +97 -0
  17. package/dist/domain/settlement.js +117 -0
  18. package/dist/domain/settlement.js.map +1 -0
  19. package/dist/domain/wallet-errors.d.ts +92 -0
  20. package/dist/domain/wallet-errors.js +155 -0
  21. package/dist/domain/wallet-errors.js.map +1 -0
  22. package/dist/facade/chain-configs.js +7 -1
  23. package/dist/facade/chain-configs.js.map +1 -1
  24. package/dist/facade/deposits.js +2 -1
  25. package/dist/facade/deposits.js.map +1 -1
  26. package/dist/facade/flows.d.ts +95 -0
  27. package/dist/facade/flows.js +116 -0
  28. package/dist/facade/flows.js.map +1 -0
  29. package/dist/facade/index.d.ts +3 -0
  30. package/dist/facade/index.js +3 -0
  31. package/dist/facade/index.js.map +1 -1
  32. package/dist/facade/kasu.d.ts +6 -2
  33. package/dist/facade/kasu.js +28 -3
  34. package/dist/facade/kasu.js.map +1 -1
  35. package/dist/facade/read-only.d.ts +12 -0
  36. package/dist/facade/read-only.js +13 -0
  37. package/dist/facade/read-only.js.map +1 -0
  38. package/dist/facade/user-portfolio.d.ts +18 -0
  39. package/dist/facade/user-portfolio.js +23 -0
  40. package/dist/facade/user-portfolio.js.map +1 -1
  41. package/dist/flows/deposit-flow.d.ts +301 -0
  42. package/dist/flows/deposit-flow.js +358 -0
  43. package/dist/flows/deposit-flow.js.map +1 -0
  44. package/dist/flows/flow.d.ts +72 -0
  45. package/dist/flows/flow.js +110 -0
  46. package/dist/flows/flow.js.map +1 -0
  47. package/dist/flows/index.d.ts +19 -0
  48. package/dist/flows/index.js +17 -0
  49. package/dist/flows/index.js.map +1 -0
  50. package/dist/flows/observable.d.ts +59 -0
  51. package/dist/flows/observable.js +81 -0
  52. package/dist/flows/observable.js.map +1 -0
  53. package/dist/flows/withdraw-flow.d.ts +99 -0
  54. package/dist/flows/withdraw-flow.js +83 -0
  55. package/dist/flows/withdraw-flow.js.map +1 -0
  56. package/dist/index.d.ts +2 -0
  57. package/dist/index.js +7 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/services/DataService/data-service.js +3 -11
  60. package/dist/services/DataService/data-service.js.map +1 -1
  61. package/dist/services/DataService/directus-client.d.ts +26 -0
  62. package/dist/services/DataService/directus-client.js +38 -0
  63. package/dist/services/DataService/directus-client.js.map +1 -0
  64. package/dist/services/UserLending/user-lending.js +11 -7
  65. package/dist/services/UserLending/user-lending.js.map +1 -1
  66. package/package.json +8 -2
  67. package/src/domain/au-minimum.ts +192 -0
  68. package/src/domain/index.ts +73 -3
  69. package/src/domain/loan-contract.ts +275 -0
  70. package/src/domain/requests.ts +467 -0
  71. package/src/domain/settlement.ts +161 -0
  72. package/src/domain/wallet-errors.ts +173 -0
  73. package/src/facade/chain-configs.ts +7 -1
  74. package/src/facade/deposits.ts +2 -3
  75. package/src/facade/flows.ts +172 -0
  76. package/src/facade/index.ts +8 -0
  77. package/src/facade/kasu.ts +43 -3
  78. package/src/facade/read-only.ts +13 -0
  79. package/src/facade/user-portfolio.ts +24 -0
  80. package/src/flows/deposit-flow.ts +775 -0
  81. package/src/flows/flow.ts +108 -0
  82. package/src/flows/index.ts +45 -0
  83. package/src/flows/observable.ts +97 -0
  84. package/src/flows/withdraw-flow.ts +210 -0
  85. package/src/index.ts +9 -0
  86. package/src/services/DataService/data-service.ts +7 -25
  87. package/src/services/DataService/directus-client.ts +54 -0
  88. package/src/services/UserLending/user-lending.ts +17 -21
  89. package/dist/domain/deposit-bounds.test.d.ts +0 -1
  90. package/dist/domain/deposit-bounds.test.js +0 -135
  91. package/dist/domain/deposit-bounds.test.js.map +0 -1
  92. package/dist/domain/partners.test.d.ts +0 -1
  93. package/dist/domain/partners.test.js +0 -53
  94. package/dist/domain/partners.test.js.map +0 -1
  95. package/dist/domain/pools.test.d.ts +0 -1
  96. package/dist/domain/pools.test.js +0 -184
  97. package/dist/domain/pools.test.js.map +0 -1
  98. package/dist/domain/rates.test.d.ts +0 -1
  99. package/dist/domain/rates.test.js +0 -181
  100. package/dist/domain/rates.test.js.map +0 -1
  101. package/dist/domain/tranche-display-name.test.d.ts +0 -1
  102. package/dist/domain/tranche-display-name.test.js +0 -58
  103. package/dist/domain/tranche-display-name.test.js.map +0 -1
  104. package/dist/domain/tranches.test.d.ts +0 -1
  105. package/dist/domain/tranches.test.js +0 -206
  106. package/dist/domain/tranches.test.js.map +0 -1
  107. package/dist/facade/config.test.d.ts +0 -1
  108. package/dist/facade/config.test.js +0 -216
  109. package/dist/facade/config.test.js.map +0 -1
  110. package/dist/facade/facade.test.d.ts +0 -1
  111. package/dist/facade/facade.test.js +0 -201
  112. package/dist/facade/facade.test.js.map +0 -1
  113. package/dist/services/Locking/calculate-apy.test.d.ts +0 -1
  114. package/dist/services/Locking/calculate-apy.test.js +0 -41
  115. package/dist/services/Locking/calculate-apy.test.js.map +0 -1
  116. package/dist/tests/sample.test.d.ts +0 -1
  117. package/dist/tests/sample.test.js +0 -59
  118. package/dist/tests/sample.test.js.map +0 -1
  119. package/src/domain/deposit-bounds.test.ts +0 -200
  120. package/src/domain/partners.test.ts +0 -83
  121. package/src/domain/pools.test.ts +0 -260
  122. package/src/domain/rates.test.ts +0 -254
  123. package/src/domain/tranche-display-name.test.ts +0 -96
  124. package/src/domain/tranches.test.ts +0 -300
  125. package/src/facade/config.test.ts +0 -265
  126. package/src/facade/facade.test.ts +0 -250
  127. package/src/services/Locking/calculate-apy.test.ts +0 -52
  128. package/src/tests/sample.test.ts +0 -63
@@ -0,0 +1,275 @@
1
+ import { ethers } from 'ethers';
2
+
3
+ /**
4
+ * Loan-contract protocol: the strings and the bytes kasu-backend verifies.
5
+ *
6
+ * ⚠️ THE ONE EXCEPTION TO "NUMBERS AND CODES ONLY". Everything this module
7
+ * returns as a string is a PROTOCOL string, not copy. The backend reconstructs
8
+ * each of these messages byte-for-byte and verifies the lender's signature
9
+ * against it; a changed word, separator, line break or date format does not
10
+ * read differently — it stops every signature verifying. They are here, in the
11
+ * shared layer, for exactly the reason `getTrancheDisplayName` is: so the
12
+ * applications cannot drift apart on them.
13
+ *
14
+ * Any change to a builder below is a coordinated multi-repo change that has to
15
+ * land in kasu-backend at the same moment. Do not "tidy" this file.
16
+ *
17
+ * Lifted from kasu-ui's `features/lending/lib/{contract-types, sign-message}.ts`
18
+ * and `encode-deposit-data.ts` — the last rewritten from viem to ethers v5
19
+ * (kasu-mobile already runs that port; the tests pin the two byte-identical).
20
+ */
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // The `/contract/generate` and `/contract/resolve` payload
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /** Recursive list nesting: `list-N-list`, etc. — indexed by string keys. */
27
+ export type ContractListItem = {
28
+ label?: string;
29
+ description?: string;
30
+ } & Record<string, unknown>;
31
+
32
+ export type ContractSection = {
33
+ title?: string;
34
+ description?: string;
35
+ } & Record<`list-${number}`, ContractListItem | undefined> &
36
+ Record<string, unknown>;
37
+
38
+ export type RetailLoanContract = {
39
+ important?: { title?: string; description?: string };
40
+ intro?: string;
41
+ between?: string;
42
+ parties?: ContractSection;
43
+ background?: ContractSection;
44
+ witnesses?: ContractSection;
45
+ } & Record<`subheader-${number}`, ContractSection | undefined> &
46
+ Record<string, unknown>;
47
+
48
+ /**
49
+ * The exempt (wholesale) contract renders from the same tree as the retail
50
+ * one; the templates differ, the SHAPE does not. Kept as its own name because
51
+ * `contractType` distinguishes them everywhere else.
52
+ */
53
+ export type ExemptLoanContract = RetailLoanContract;
54
+
55
+ /** Either contract, parsed. Both are the same tree. */
56
+ export type LoanContractFormatted = RetailLoanContract;
57
+
58
+ /**
59
+ * `contractType` arrives over the wire as a free string; these are the two the
60
+ * version byte encodes.
61
+ */
62
+ export type ContractType = 'retail' | 'exempt';
63
+
64
+ export interface GenerateContractResponse {
65
+ fullName: string;
66
+ /** The plaintext the lender signs (EIP-191). */
67
+ contractMessage: string;
68
+ /** Returned as a JSON-encoded string; parse before rendering. */
69
+ formattedMessage: string;
70
+ contractType: ContractType;
71
+ /** Template version (>= 1). */
72
+ contractVersion: number;
73
+ /** ms-epoch; feeds the on-chain `depositData` and the TTL guard. */
74
+ timestamp: number;
75
+ }
76
+
77
+ export type ResolvedContractResponse = GenerateContractResponse & {
78
+ isValid: boolean;
79
+ };
80
+
81
+ /** Narrow the backend's loose `contractType` string to the encoded union. */
82
+ export function asContractType(raw: string): ContractType {
83
+ return raw === 'exempt' ? 'exempt' : 'retail';
84
+ }
85
+
86
+ /**
87
+ * Parse the server's JSON-string `formattedMessage` into a tree. Returns
88
+ * `null` on parse failure so a renderer can fall back to the plaintext.
89
+ */
90
+ export function parseFormattedMessage(
91
+ raw: string,
92
+ ): LoanContractFormatted | null {
93
+ try {
94
+ const parsed = JSON.parse(raw) as unknown;
95
+ if (parsed && typeof parsed === 'object')
96
+ return parsed as LoanContractFormatted;
97
+ return null;
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // The on-chain `depositData` blob
105
+ // ---------------------------------------------------------------------------
106
+
107
+ /**
108
+ * Pack the contract version and type into the `versionType` word.
109
+ *
110
+ * ```
111
+ * high byte = contract version (>= 1)
112
+ * low byte = 0 for retail, 1 for exempt
113
+ * ```
114
+ */
115
+ export function buildContractVersionType(
116
+ contractVersion: number,
117
+ contractType: ContractType,
118
+ ): number {
119
+ return (contractVersion << 8) + (contractType === 'retail' ? 0 : 1);
120
+ }
121
+
122
+ /**
123
+ * Build the on-chain `depositData` blob that `requestDepositWithKyc` expects.
124
+ *
125
+ * The KasuController decodes the bytes as
126
+ * `(bytes signature, uint256 timestamp, uint256 versionType)` and uses the
127
+ * embedded acceptance signature to verify — retrospectively, via the
128
+ * agreements service `/contract/resolve` — that the lender signed the
129
+ * loan-contract text. The ABI tuple and the packing are consensus-critical:
130
+ * these bytes go on chain.
131
+ *
132
+ * kasu-ui encodes this with viem, kasu-mobile with ethers v5 (viem is not
133
+ * available on Expo). This is the ethers v5 implementation, and
134
+ * `loan-contract.test.ts` pins its output byte-for-byte against fixtures
135
+ * produced by the viem version, so the two apps can never diverge here.
136
+ *
137
+ * @param args.signature EIP-191 signature from the lender accepting
138
+ * `contractMessage`, as a 0x-prefixed hex string.
139
+ * @param args.timestamp ms-epoch from the contract response.
140
+ */
141
+ export function encodeDepositData(args: {
142
+ signature: string;
143
+ timestamp: number;
144
+ contractVersion: number;
145
+ contractType: ContractType;
146
+ }): string {
147
+ const versionType = buildContractVersionType(
148
+ args.contractVersion,
149
+ args.contractType,
150
+ );
151
+ return ethers.utils.defaultAbiCoder.encode(
152
+ ['bytes', 'uint256', 'uint256'],
153
+ [
154
+ args.signature,
155
+ ethers.BigNumber.from(args.timestamp),
156
+ ethers.BigNumber.from(versionType),
157
+ ],
158
+ );
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // The signed messages
163
+ // ---------------------------------------------------------------------------
164
+
165
+ const MONTH_NAMES = [
166
+ 'January',
167
+ 'February',
168
+ 'March',
169
+ 'April',
170
+ 'May',
171
+ 'June',
172
+ 'July',
173
+ 'August',
174
+ 'September',
175
+ 'October',
176
+ 'November',
177
+ 'December',
178
+ ];
179
+
180
+ /**
181
+ * Format a unix timestamp as `{day} {MonthName} {yyyy}, {HH}:{mm}` in UTC.
182
+ * Day is non-padded; hour and minute are zero-padded to two digits (24h). A
183
+ * timestamp with >= 13 digits is treated as milliseconds, otherwise as seconds
184
+ * — the same auto-detection kasu-backend applies.
185
+ *
186
+ * Deliberately a manual formatter with English month names: no locale, no
187
+ * `Intl`, so the output is byte-identical across runtimes and time zones. This
188
+ * is not a display date. It goes inside a signed message.
189
+ *
190
+ * e.g. 1785313320000 → `"29 July 2026, 08:22"`
191
+ */
192
+ export function formatSignTimestampUtc(timestamp: number): string {
193
+ const ms = timestamp.toString().length >= 13 ? timestamp : timestamp * 1000;
194
+ const date = new Date(ms);
195
+ const day = date.getUTCDate();
196
+ const month = MONTH_NAMES[date.getUTCMonth()];
197
+ const year = date.getUTCFullYear();
198
+ const hours = String(date.getUTCHours()).padStart(2, '0');
199
+ const minutes = String(date.getUTCMinutes()).padStart(2, '0');
200
+ return `${day} ${month} ${year}, ${hours}:${minutes}`;
201
+ }
202
+
203
+ /**
204
+ * The 4-line human-readable message a lender signs to generate their loan
205
+ * agreement for review — `POST /contract/generate`.
206
+ *
207
+ * ⚠️ BYTE-EXACT PROTOCOL STRING. kasu-backend rebuilds this string from the
208
+ * request body and verifies the signature against it, so the wording,
209
+ * ordering, separators, line breaks and date format are all part of the wire
210
+ * contract. The separator between the line-2 fields is a MIDDLE DOT U+00B7
211
+ * (·) with a single space on each side; the four lines are joined with `\n`.
212
+ *
213
+ * The backend takes this format only when all four display fields are present
214
+ * and non-empty, and it cross-checks `amountLabel`'s leading number against
215
+ * the `depositAmount` it was sent (thousands separators stripped) — a message
216
+ * that states an amount other than the one being executed is refused.
217
+ */
218
+ export function buildLoanAgreementSignMessage(p: {
219
+ strategyName: string;
220
+ region: string;
221
+ optionName: string;
222
+ amountLabel: string;
223
+ timestamp: number;
224
+ }): string {
225
+ return [
226
+ 'Generate my Loan Agreement for review:',
227
+ `${p.strategyName} · ${p.region} · ${p.optionName} · ${p.amountLabel}.`,
228
+ `Request made ${formatSignTimestampUtc(p.timestamp)} UTC.`,
229
+ 'This request does not commit me to lend.',
230
+ ].join('\n');
231
+ }
232
+
233
+ /**
234
+ * The legacy `/contract/generate` and `/contract/resolve` message.
235
+ *
236
+ * ⚠️ BYTE-EXACT PROTOCOL STRING. kasu-backend rebuilds it as
237
+ * `` `I request contract content for ${address} at ${timestamp}.` `` from the
238
+ * `address` and `timestamp` fields of the request body — so the string signed
239
+ * and the body sent must agree exactly, INCLUDING the address casing. This
240
+ * builder lowercases, and the request body must carry the same lowercased
241
+ * address; that is what both apps signing this format do today.
242
+ *
243
+ * The backend takes this path whenever the four human-readable display fields
244
+ * are absent, and documents it as permanent until the legacy app is
245
+ * decommissioned. `/contract/resolve` has no other format — every consumer
246
+ * signs this one to retrieve an existing agreement.
247
+ *
248
+ * @param timestampMs ms-epoch, and the same value sent as the body's
249
+ * `timestamp`.
250
+ */
251
+ export function buildLegacyContractRequestMessage(
252
+ address: string,
253
+ timestampMs: number,
254
+ ): string {
255
+ return `I request contract content for ${address.toLowerCase()} at ${timestampMs}.`;
256
+ }
257
+
258
+ /**
259
+ * The `POST /contract/fullname` message.
260
+ *
261
+ * ⚠️ BYTE-EXACT PROTOCOL STRING, on the same terms as
262
+ * `buildLegacyContractRequestMessage`: kasu-backend rebuilds
263
+ * `` `I request my full name for ${address} at ${timestamp}.` `` from the
264
+ * request body and verifies the signature against it, so the body must carry
265
+ * the same lowercased address this builder signs.
266
+ *
267
+ * @param timestampMs ms-epoch, and the same value sent as the body's
268
+ * `timestamp`.
269
+ */
270
+ export function buildFullNameRequestMessage(
271
+ address: string,
272
+ timestampMs: number,
273
+ ): string {
274
+ return `I request my full name for ${address.toLowerCase()} at ${timestampMs}.`;
275
+ }