@kasufinance/kasu-sdk 2.7.0 → 2.8.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 CHANGED
@@ -147,7 +147,8 @@ application formats them in its own design system and language.
147
147
  | Requests | `deriveRequestState`, `submissionEvents`, `countSubmissions`, `firstSubmissionTimestamp`, `isCycleClosed` |
148
148
  | Settlement | `computeSettlementWindow`, `nextCycleBoundary`, `deriveCycleDates`, `CLEARING_WINDOW_SECONDS` |
149
149
  | Loan contract | `encodeDepositData`, `buildContractVersionType`, `buildLoanAgreementSignMessage`, `buildLegacyContractRequestMessage`, `buildFullNameRequestMessage`, `formatSignTimestampUtc`, `parseFormattedMessage`, `asContractType` |
150
- | Wallet errors | `isUserRejected`, `isUnpredictableGas` |
150
+ | Wallet errors | `isUserRejected`, `isUnpredictableGas`, `classifyWalletFailure` |
151
+ | Revert errors | `decodeRevert`, `extractRevertData` — the bytes a reverted call came back with, read as the custom error the contract named |
151
152
  | AU minimum | `auMinimumRemaining`, `isAustralianKyc`, `auThresholdFor`, `isAuMinimumExempt`, `parseMinorUnits`, `AU_ALPHA3`, `AU_MIN_CUMULATIVE_BY_STABLE` |
152
153
 
153
154
  Three rules worth knowing:
@@ -224,8 +225,52 @@ await flow.acceptContract(); // signs it and carries on
224
225
  // or: flow.declineContract(); → phase 'declined', nothing submitted
225
226
  ```
226
227
 
228
+ ### One atomic transaction: the `depositBatch` port
229
+
230
+ If your application can submit several calls as ONE atomic transaction (all
231
+ apply or none does), supply the optional `depositBatch` port. When the
232
+ allowance is short, the run then approves and deposits in a single batch
233
+ instead of two transactions. You decide how the batch is sent; the SDK decides
234
+ what is in it:
235
+
236
+ ```ts
237
+ import { BatchRevertedError, type BatchCall } from '@kasufinance/kasu-sdk';
238
+
239
+ const flow = kasu.flows.deposit({
240
+ signMessage,
241
+ generateContract,
242
+ getKycSignature,
243
+ depositBatch: async (calls: BatchCall[]) => {
244
+ // calls[0]: stableAsset.approve(LendingPoolManager, amount) — EXACT amount
245
+ // calls[1]: LendingPoolManager.requestDepositWithKyc(…) — value 0
246
+ const tx = await submitAtomically(calls); // your mechanism
247
+ return tx; // anything with wait()
248
+ // Known to revert? throw new BatchRevertedError('ClearingIsPending')
249
+ },
250
+ });
251
+
252
+ // …after acceptContract(), a batched run parks on `awaiting-authorise`:
253
+ flow.authoriseTransfer(); // the lender's explicit go-ahead
254
+ // or: flow.declineTransfer(); → error { step: 'request', reason: 'cancelled' }
255
+ ```
256
+
257
+ - The batch path is taken only when the allowance is short, the port is
258
+ supplied and the chain's stable token is known (`kasu.flows.deposit()` fills
259
+ it from the chain config). Otherwise the run is exactly the two-transaction
260
+ pipeline.
261
+ - A batch has no wallet prompt per call, so the run **parks for an explicit
262
+ authorisation** before any money moves. The agreement's TTL is checked again
263
+ after it, and the KYC signature is fetched only then.
264
+ - `state.batched` is `true` from the first state of a batched run. It never
265
+ enters `approve`, so it counts as a three-step run (`stepTotal` 3).
266
+ - Throw `BatchRevertedError(revertError)` when the batch is known to revert →
267
+ `{ step: 'request', reason: 'reverted', revertError }`. Any other throw is
268
+ read like a direct deposit's: a wallet rejection is `cancelled`, everything
269
+ else `failed`.
270
+
227
271
  **Phases** — `idle`, `generating-sign`, `generating-fetch`, `awaiting-accept`,
228
- `accepting-sign`, `approve`, `request-sign`, `request-confirm`, and the
272
+ `accepting-sign`, `approve`, `awaiting-authorise` (batch path only),
273
+ `request-sign`, `request-confirm`, and the
229
274
  terminal `success` / `declined` / `error`. Each maps to one of four steps
230
275
  (`generate`, `confirm`, `approve`, `request`); `stepIndex` and `stepTotal` are
231
276
  published because the approve step drops out of the sequence when the allowance
@@ -239,14 +284,51 @@ type DepositFailure =
239
284
  | { step: DepositStep; reason: 'cancelled' } // isUserRejected
240
285
  | { step: DepositStep; reason: 'failed'; error: unknown }
241
286
  | { step: 'request'; reason: 'insufficient-balance'; error: unknown }
287
+ | {
288
+ step: 'request';
289
+ reason: 'reverted';
290
+ revertError: string; // e.g. 'ClearingIsPending'
291
+ error: unknown;
292
+ }
242
293
  | { step: 'request'; reason: 'contract-expired' }; // the 5-minute TTL
243
294
  ```
244
295
 
245
296
  A wallet rejection is `cancelled`, not a failure: the lender changed their
246
- mind, and telling them something broke would be a lie. A reverted gas estimate
247
- on the request step is `insufficient-balance` — nothing was refused, so a retry
248
- would only reproduce it. Everything else is `failed` and carries the original
249
- error for your crash reporter.
297
+ mind, and telling them something broke would be a lie. Everything else is
298
+ `failed` and carries the original error for your crash reporter.
299
+
300
+ A revert on the request step is read for WHAT reverted. `requestDepositWithKyc`
301
+ reverts for a family of reasons the ABI declares — `LendingPoolIsStopped`,
302
+ `ClearingIsPending`, `InvalidTranche`, `UserNotKycd`, `UserBlocked`,
303
+ `UserNotInAllowList`, `BlockExpired` — and none of them is a shortfall. Those
304
+ come back as `reverted`, with `revertError` naming the contract error. Only a
305
+ token balance or allowance failure, or revert data nothing can decode, is
306
+ `insufficient-balance`.
307
+
308
+ **Render `reverted` with generic copy and special-case only the names you have
309
+ words for.** A contract upgrade can add an error, so treat the set as open: a
310
+ consumer that assumed it was closed has nothing to show for a new one.
311
+ `decodeRevert(err)` is exported if you want the same reading elsewhere.
312
+
313
+ ```ts
314
+ if (failure.reason === 'reverted') {
315
+ switch (failure.revertError) {
316
+ case 'ClearingIsPending':
317
+ return t('deposit.error.clearing');
318
+ case 'UserNotKycd':
319
+ return t('deposit.error.kyc');
320
+ default:
321
+ return t('deposit.error.rejectedOnChain'); // generic
322
+ }
323
+ }
324
+ ```
325
+
326
+ **Both personal signs are length-checked where they are taken.** A wallet is
327
+ not obliged to return 65 bytes and the ABI coder will not catch one that does
328
+ not — `0x` encodes into `depositData` as happily as a real signature does, and
329
+ the agreements service could then never verify the deposit. A malformed auth
330
+ signature fails the `generate` step and a malformed acceptance signature fails
331
+ `confirm`, both as `failed`, before anything is broadcast.
250
332
 
251
333
  Three guarantees worth knowing:
252
334