@kasufinance/kasu-sdk 2.7.1 → 2.10.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 (88) hide show
  1. package/README.md +162 -7
  2. package/dist/bundle.cjs.js +2292 -449
  3. package/dist/bundle.esm.js +2253 -451
  4. package/dist/domain/batch-proof.d.ts +210 -0
  5. package/dist/domain/batch-proof.js +384 -0
  6. package/dist/domain/batch-proof.js.map +1 -0
  7. package/dist/domain/explorer-urls.d.ts +33 -0
  8. package/dist/domain/explorer-urls.js +38 -0
  9. package/dist/domain/explorer-urls.js.map +1 -0
  10. package/dist/domain/fixed-term.d.ts +74 -0
  11. package/dist/domain/fixed-term.js +86 -0
  12. package/dist/domain/fixed-term.js.map +1 -0
  13. package/dist/domain/index.d.ts +12 -2
  14. package/dist/domain/index.js +7 -2
  15. package/dist/domain/index.js.map +1 -1
  16. package/dist/domain/partners.d.ts +29 -0
  17. package/dist/domain/partners.js +20 -0
  18. package/dist/domain/partners.js.map +1 -1
  19. package/dist/domain/requests.d.ts +24 -0
  20. package/dist/domain/requests.js +33 -0
  21. package/dist/domain/requests.js.map +1 -1
  22. package/dist/domain/sponsored-calls.d.ts +74 -0
  23. package/dist/domain/sponsored-calls.js +156 -0
  24. package/dist/domain/sponsored-calls.js.map +1 -0
  25. package/dist/domain/yields.d.ts +121 -0
  26. package/dist/domain/yields.js +151 -0
  27. package/dist/domain/yields.js.map +1 -0
  28. package/dist/facade/calls.d.ts +59 -0
  29. package/dist/facade/calls.js +89 -0
  30. package/dist/facade/calls.js.map +1 -0
  31. package/dist/facade/chain-configs.js +5 -0
  32. package/dist/facade/chain-configs.js.map +1 -1
  33. package/dist/facade/flows.d.ts +40 -3
  34. package/dist/facade/flows.js +36 -3
  35. package/dist/facade/flows.js.map +1 -1
  36. package/dist/facade/index.d.ts +3 -1
  37. package/dist/facade/index.js +1 -0
  38. package/dist/facade/index.js.map +1 -1
  39. package/dist/facade/kasu.d.ts +8 -2
  40. package/dist/facade/kasu.js +17 -4
  41. package/dist/facade/kasu.js.map +1 -1
  42. package/dist/facade/strategies.d.ts +43 -2
  43. package/dist/facade/strategies.js +80 -4
  44. package/dist/facade/strategies.js.map +1 -1
  45. package/dist/facade/types.d.ts +18 -0
  46. package/dist/flows/batch-errors.d.ts +74 -0
  47. package/dist/flows/batch-errors.js +83 -0
  48. package/dist/flows/batch-errors.js.map +1 -0
  49. package/dist/flows/batch-submission.d.ts +223 -0
  50. package/dist/flows/batch-submission.js +321 -0
  51. package/dist/flows/batch-submission.js.map +1 -0
  52. package/dist/flows/deposit-flow.d.ts +128 -4
  53. package/dist/flows/deposit-flow.js +233 -4
  54. package/dist/flows/deposit-flow.js.map +1 -1
  55. package/dist/flows/index.d.ts +6 -2
  56. package/dist/flows/index.js +3 -1
  57. package/dist/flows/index.js.map +1 -1
  58. package/dist/flows/withdraw-flow.d.ts +95 -4
  59. package/dist/flows/withdraw-flow.js +107 -2
  60. package/dist/flows/withdraw-flow.js.map +1 -1
  61. package/dist/services/DataService/queries.js +1 -0
  62. package/dist/services/DataService/queries.js.map +1 -1
  63. package/dist/services/UserLending/user-lending.d.ts +13 -0
  64. package/dist/services/UserLending/user-lending.js +25 -4
  65. package/dist/services/UserLending/user-lending.js.map +1 -1
  66. package/package.json +1 -1
  67. package/src/domain/batch-proof.ts +576 -0
  68. package/src/domain/explorer-urls.ts +74 -0
  69. package/src/domain/fixed-term.ts +115 -0
  70. package/src/domain/index.ts +56 -0
  71. package/src/domain/partners.ts +35 -0
  72. package/src/domain/requests.ts +37 -0
  73. package/src/domain/sponsored-calls.ts +213 -0
  74. package/src/domain/yields.ts +189 -0
  75. package/src/facade/calls.ts +135 -0
  76. package/src/facade/chain-configs.ts +5 -0
  77. package/src/facade/flows.ts +35 -2
  78. package/src/facade/index.ts +3 -0
  79. package/src/facade/kasu.ts +32 -2
  80. package/src/facade/strategies.ts +98 -7
  81. package/src/facade/types.ts +19 -0
  82. package/src/flows/batch-errors.ts +118 -0
  83. package/src/flows/batch-submission.ts +511 -0
  84. package/src/flows/deposit-flow.ts +379 -7
  85. package/src/flows/index.ts +34 -1
  86. package/src/flows/withdraw-flow.ts +219 -10
  87. package/src/services/DataService/queries.ts +1 -0
  88. package/src/services/UserLending/user-lending.ts +38 -10
package/README.md CHANGED
@@ -100,13 +100,14 @@ a read-only `Kasu.create({ chain: 'plume' })` throws — pass your own provider
100
100
  read its history.
101
101
 
102
102
  Every entry lives in `CHAIN_CONFIGS`, including contract addresses, subgraph
103
- URLs, the stable asset and public RPC defaults:
103
+ URLs, the stable asset, public RPC defaults and the block explorer:
104
104
 
105
105
  ```ts
106
- import { CHAIN_CONFIGS } from '@kasufinance/kasu-sdk';
106
+ import { CHAIN_CONFIGS, explorerTxUrl } from '@kasufinance/kasu-sdk';
107
107
 
108
108
  CHAIN_CONFIGS.base.stableAsset; // { address, symbol, name, decimals, currencyCode }
109
109
  CHAIN_CONFIGS.xdc.rpcUrls; // starting preference only — override with your own
110
+ explorerTxUrl(CHAIN_CONFIGS.base, txHash); // https://basescan.org/tx/0x… — throws on a malformed hash
110
111
  ```
111
112
 
112
113
  You can also pass a whole `ChainConfigEntry` instead of a chain key.
@@ -115,10 +116,11 @@ You can also pass a whole `ChainConfigEntry` instead of a chain key.
115
116
 
116
117
  | Facade | Methods | Purpose |
117
118
  | ----------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
118
- | `kasu.strategies` | `getAll()`, `getVisible()`, `getById()`, `getPlatformStats()`, `getPerformanceFeePercent()`, `calculateDepositLimits()` | Browse pools, APY, capacity |
119
+ | `kasu.strategies` | `getAll()`, `getVisible()`, `getById()`, `getPlatformStats()`, `getPerformanceFeePercent()`, `calculateDepositLimits()`, `getRiskPerformance(poolId)`, `getOriginator(poolId)` | Browse pools, APY, capacity, risk, originator |
119
120
  | `kasu.deposits` | `deposit()`, `withdraw()`, `withdrawMax()`, `buildKycParams()`, `isClearingPending()` | Submit transactions |
120
121
  | `kasu.portfolio` | `getPositions()`, `getTransactionHistory()`, `getRequestStates()` | Lender balances, yield, history |
121
122
  | `kasu.flows` | `deposit(ports)`, `withdraw(ports?)` | Headless transaction pipelines |
123
+ | `kasu.calls` | `requestWithdrawal(params)`, `cancelDepositRequest(poolId, dNftId)`, `cancelWithdrawalRequest(poolId, wNftId)`, `withdrawAtExpiry(poolId, lockId)` | Encoded calls for a sponsored relay (read, then encode — never sends) |
122
124
 
123
125
  On the instance itself: `kasu.connect(signer)`, `kasu.isReadOnly`,
124
126
  `kasu.provider`, `kasu.chainConfig`, `kasu.isLiteDeployment`, and
@@ -142,7 +144,10 @@ application formats them in its own design system and language.
142
144
  | Tranches | `trancheHasCapacity`, `poolAllTranchesFull`, `derivePoolStatus`, `trancheRiskRank`, `compareTrancheSeniority`, `pickDefaultTrancheId`, `trancheApyBounds`, `netTrancheApyBounds`, `MIN_TRANCHE_CAPACITY` |
143
145
  | Deposit bounds | `resolveDepositBounds`, `resolveBoundShortcuts`, `isBelowMinimumCapacity`, `parseTrancheBound`, `floorToCents`, `ceilToCents` |
144
146
  | Pools | `selectVisiblePools`, `poolMaxApy`, `pickHighestYieldTranche`, `maxNetRateCeiling` |
145
- | Partners | `getCreditOriginator`, `getInstitutionalLender`, `APXIUM`, `INVOICEMATE`, `RIXON_CAPITAL` |
147
+ | Partners | `getCreditOriginator`, `getInstitutionalLender`, `getStrategyRegion`, `APXIUM`, `INVOICEMATE`, `RIXON_CAPITAL` (each with a `region`) |
148
+ | Fixed term | `availableFixedTermConfigs`, `isFixedTermConfigId`, `lockDurationDays`, `lockDurationMonths`, `DAYS_PER_EPOCH`, `DAYS_PER_MONTH`, `MIN_DAYS_FOR_MONTHS` |
149
+ | Yields | `weeklyInterest`, `dailyInterest`, `accruedInterest`, `projectedAnnualEarnings`, `weightedAverageApy`, `SECONDS_PER_WEEK` — NET of the performance fee |
150
+ | Batch proof | `validateDepositBatchCalls`, `proveDepositInReceipt`, `DepositBatchMismatchError`, `ENTRY_POINT_V07` |
146
151
  | Tranche display name | `getTrancheDisplayName`, `UPPER_MEZZANINE` |
147
152
  | Requests | `deriveRequestState`, `submissionEvents`, `countSubmissions`, `firstSubmissionTimestamp`, `isCycleClosed` |
148
153
  | Settlement | `computeSettlementWindow`, `nextCycleBoundary`, `deriveCycleDates`, `CLEARING_WINDOW_SECONDS` |
@@ -225,8 +230,103 @@ await flow.acceptContract(); // signs it and carries on
225
230
  // or: flow.declineContract(); → phase 'declined', nothing submitted
226
231
  ```
227
232
 
233
+ ### One atomic transaction: the `depositBatch` port
234
+
235
+ If your application can submit several calls as ONE atomic transaction (all
236
+ apply or none does), supply the optional `depositBatch` port. When the
237
+ allowance is short, the run then approves and deposits in a single batch
238
+ instead of two transactions. You decide how the batch is sent; the SDK decides
239
+ what is in it:
240
+
241
+ ```ts
242
+ import { BatchRevertedError, type BatchCall } from '@kasufinance/kasu-sdk';
243
+
244
+ const flow = kasu.flows.deposit({
245
+ signMessage,
246
+ generateContract,
247
+ getKycSignature,
248
+ depositBatch: async (calls: BatchCall[]) => {
249
+ // calls[0]: stableAsset.approve(LendingPoolManager, amount) — EXACT amount
250
+ // calls[1]: LendingPoolManager.requestDepositWithKyc(…) — value 0
251
+ const tx = await submitAtomically(calls); // your mechanism
252
+ return tx; // anything with wait()
253
+ // Known to revert? throw new BatchRevertedError('ClearingIsPending')
254
+ },
255
+ });
256
+
257
+ // …after acceptContract(), a batched run parks on `awaiting-authorise`:
258
+ flow.authoriseTransfer(); // the lender's explicit go-ahead
259
+ // or: flow.declineTransfer(); → error { step: 'request', reason: 'cancelled' }
260
+ ```
261
+
262
+ - The batch path is taken only when the allowance is short, the port is
263
+ supplied and the chain's stable token is known (`kasu.flows.deposit()` fills
264
+ it from the chain config). Otherwise the run is exactly the two-transaction
265
+ pipeline.
266
+ - A batch has no wallet prompt per call, so the run **parks for an explicit
267
+ authorisation** before any money moves. The agreement's TTL is checked again
268
+ after it, and the KYC signature is fetched only then.
269
+ - `state.batched` is `true` from the first state of a batched run. It never
270
+ enters `approve`, so it counts as a three-step run (`stepTotal` 3).
271
+ - Throw `BatchRevertedError(revertError)` when the batch is known to revert →
272
+ `{ step: 'request', reason: 'reverted', revertError }`. Throw a
273
+ `BatchSubmissionError` (or `BatchPendingError`) to report a submission
274
+ outcome: `pending` → `{ reason: 'pending' }`, `already-submitted` →
275
+ `{ reason: 'already-submitted' }`, `failed` → `{ reason: 'failed' }` — read
276
+ at its word, never through the revert decoder. Any other throw is read like a
277
+ direct deposit's: a wallet rejection is `cancelled`, everything else
278
+ `failed`.
279
+
280
+ ### Submitting a sponsored batch: `submitBatch` (2.9.0)
281
+
282
+ For a submitter that relays the batch and is asked for its status later (a
283
+ sponsored-gas relay, for one), `submitBatch` owns the polling policy, the
284
+ receipt proof and the outcome taxonomy. Your ports do the I/O; the SDK decides
285
+ what each answer means:
286
+
287
+ ```ts
288
+ import {
289
+ submitBatch,
290
+ validateDepositBatchCalls,
291
+ } from '@kasufinance/kasu-sdk';
292
+
293
+ depositBatch: (calls) => {
294
+ // Before signing: the batch is exactly this run's deposit, or it throws.
295
+ const { tranche, amount } = validateDepositBatchCalls(calls, {
296
+ stableAsset, spender, poolId, trancheId, amount, fixedTermConfigId,
297
+ });
298
+ return submitBatch(
299
+ // `pendingPool` is the strategy's pending pool (`ILendingPool.pendingPool()`):
300
+ // with it, only THAT contract's `DepositRequested` can prove the deposit.
301
+ { calls, expect: { lender, tranche, amount, pendingPool } },
302
+ {
303
+ post: (calls) => postToMyRelay(calls), // → { transactionId }
304
+ getStatus: (id) => readMyRelayStatus(id), // normalised BatchStatusRead
305
+ getReceipt: (hash) => provider.waitForTransaction(hash, 1, 120_000),
306
+ now: Date.now,
307
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
308
+ },
309
+ );
310
+ },
311
+ ```
312
+
313
+ - **An ambiguous result is never reported as failed.** Once the batch has been
314
+ posted only a revert proves nothing was deposited; a timeout, an unreadable
315
+ receipt, an unknown status or an unrecognised `post` error is a
316
+ `BatchPendingError`, and the run ends `{ reason: 'pending' }`. Tell the
317
+ lender it may still be processing — never "try again".
318
+ - `wait()` resolves only when `proveDepositInReceipt` finds THIS lender's
319
+ `DepositRequested` for the expected tranche AND amount inside their own user
320
+ operation — and, when `pendingPool` is given, emitted by that contract. A
321
+ handleOps receipt's `status` decides nothing.
322
+ - Polling: first read at once, then 3 s ×1.5 to a 10 s ceiling, for 10 minutes
323
+ (`DEFAULT_BATCH_POLLING_POLICY`; pass a partial policy to override).
324
+ - `checkBatchOutcome(transactionId, expect, ports)` is the one-read check for an
325
+ app's guard against lending twice after a `pending` run.
326
+
228
327
  **Phases** — `idle`, `generating-sign`, `generating-fetch`, `awaiting-accept`,
229
- `accepting-sign`, `approve`, `request-sign`, `request-confirm`, and the
328
+ `accepting-sign`, `approve`, `awaiting-authorise` (batch path only),
329
+ `request-sign`, `request-confirm`, and the
230
330
  terminal `success` / `declined` / `error`. Each maps to one of four steps
231
331
  (`generate`, `confirm`, `approve`, `request`); `stepIndex` and `stepTotal` are
232
332
  published because the approve step drops out of the sequence when the allowance
@@ -246,7 +346,12 @@ type DepositFailure =
246
346
  revertError: string; // e.g. 'ClearingIsPending'
247
347
  error: unknown;
248
348
  }
249
- | { step: 'request'; reason: 'contract-expired' }; // the 5-minute TTL
349
+ | { step: 'request'; reason: 'contract-expired' } // the 5-minute TTL
350
+ | {
351
+ step: 'request';
352
+ reason: 'pending' | 'already-submitted'; // batch path only — NOT failures
353
+ error: unknown;
354
+ };
250
355
  ```
251
356
 
252
357
  A wallet rejection is `cancelled`, not a failure: the lender changed their
@@ -328,7 +433,57 @@ const checked = kasu.flows.withdraw({
328
433
  ```
329
434
 
330
435
  `'max'` is a code you pass, not a balance you read: it routes to the all-shares
331
- call, which resolves the balance on chain at execution.
436
+ request, whose share count the SDK reads (`balanceOf`) at submission instead of
437
+ converting an asset figure — so no dust is stranded.
438
+
439
+ ### Sponsored withdraw and cancel (2.10.0)
440
+
441
+ `kasu.calls` encodes the single `LendingPoolManager` calls a gas-sponsoring
442
+ relay carries. Each returns `{ kind, call }`: post `kind` and `[call]` exactly
443
+ as given — the relay judges the call by its kind, so never map one yourself.
444
+
445
+ ```ts
446
+ import { submitSponsoredCall } from '@kasufinance/kasu-sdk';
447
+
448
+ const ports = {
449
+ post: ({ kind, call }) => postToMyRelay({ kind, calls: [call] }), // → { transactionId }
450
+ getStatus: (id) => readMyRelayStatus(id), // normalised BatchStatusRead
451
+ getReceipt: (hash) => provider.waitForTransaction(hash, 1, 120_000),
452
+ now: Date.now,
453
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
454
+ };
455
+
456
+ // Withdraw: the flow encodes the call (shares from convertToShares, or the
457
+ // whole balanceOf for 'max') and submits it through your port.
458
+ const flow = kasu.flows.withdraw({
459
+ submitCall: (call) =>
460
+ submitSponsoredCall({ call, expect: { sender: lender } }, ports),
461
+ });
462
+ flow.state.sponsored; // true, from the first state
463
+ await flow.start({ poolId, trancheId, amount: 'max', userAddress: lender });
464
+
465
+ // Cancel: encode, then submit the same way.
466
+ const cancel = kasu.calls.cancelDepositRequest(poolId, dNftId);
467
+ const submission = await submitSponsoredCall(
468
+ { call: cancel, expect: { sender: lender } },
469
+ ports,
470
+ );
471
+ await submission.wait(); // resolves only when the lender's operation executed
472
+ ```
473
+
474
+ - `submitSponsoredCall` is `submitBatch` for one call: the same polling policy
475
+ and outcome table, with `proveUserOperationInReceipt` as the proof — the
476
+ lender's own operation, from the EntryPoint, with `success` set. A reverted
477
+ operation is `BatchRevertedError`; anything unproven is `BatchPendingError`.
478
+ - On the sponsored path a withdrawal's failure can also be `{ reason:
479
+ 'reverted', revertError }` and `{ reason: 'pending' | 'already-submitted' }`.
480
+ A wallet rejection thrown by `submitCall` (the lender declined to sign) is
481
+ `cancelled`. Any other throw from `submitCall` or `wait()` the SDK does not
482
+ recognise is `pending` — it may have left, so never tell the lender to try
483
+ again. A throw while encoding (a share read, an empty position) is `failed`:
484
+ nothing was sent.
485
+ - `cancelDeadline(requestState, nextEpochStart, nowSeconds)` is the "cancel
486
+ until" instant for a pending request (the next cycle close), or `null`.
332
487
 
333
488
  ## Low-level `KasuSdk`
334
489