@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
@@ -1,5 +1,7 @@
1
1
  import { BigNumber, utils } from 'ethers';
2
2
 
3
+ import { IERC20MetadataAbi__factory } from '../contracts/factories/IERC20MetadataAbi__factory';
4
+ import { ILendingPoolManagerAbi__factory } from '../contracts/factories/ILendingPoolManagerAbi__factory';
3
5
  import {
4
6
  asContractType,
5
7
  buildLegacyContractRequestMessage,
@@ -14,6 +16,7 @@ import {
14
16
  } from '../domain/wallet-errors';
15
17
  import { DepositParams, KycParams } from '../facade/types';
16
18
 
19
+ import { BatchRevertedError, BatchSubmissionError } from './batch-errors';
17
20
  import { Flow } from './flow';
18
21
  import { WaitableTransaction } from './observable';
19
22
 
@@ -59,6 +62,20 @@ import { WaitableTransaction } from './observable';
59
62
  * 8. **`request-sign` / `request-confirm`** — KYC signature, then the deposit,
60
63
  * then the receipt.
61
64
  *
65
+ * ## The batch path
66
+ *
67
+ * A consumer that can submit several calls as ONE atomic transaction supplies
68
+ * the optional `depositBatch` port. When the allowance is short, that port is
69
+ * present and the stable token is known, steps 7 and 8 become one: the SDK
70
+ * encodes `approve(spender, amount)` and `requestDepositWithKyc(...)` itself
71
+ * and hands both to the port. Because a batch has no wallet prompt per call,
72
+ * the run first PARKS on `awaiting-authorise` until the consumer calls
73
+ * `authoriseTransfer()` (or `declineTransfer()`) — nothing moves money
74
+ * without an explicit authorisation. The TTL is checked again after it, and
75
+ * the KYC signature is fetched only then, because it carries a block
76
+ * expiration of its own. Without the port, or with the allowance already
77
+ * sufficient, the run is exactly the two-transaction pipeline above.
78
+ *
62
79
  * ## Failure codes
63
80
  *
64
81
  * A wallet rejection (`isUserRejected`) is `'cancelled'` — the lender changed
@@ -105,6 +122,11 @@ export type DepositPhase =
105
122
  | 'awaiting-accept'
106
123
  | 'accepting-sign'
107
124
  | 'approve'
125
+ /**
126
+ * Batch path only: the run is PARKED until `authoriseTransfer()` or
127
+ * `declineTransfer()`. Step `request`.
128
+ */
129
+ | 'awaiting-authorise'
108
130
  | 'request-sign'
109
131
  | 'request-confirm'
110
132
  | 'success'
@@ -146,7 +168,19 @@ export type DepositFailure =
146
168
  revertError: string;
147
169
  error: unknown;
148
170
  }
149
- | { step: 'request'; reason: 'contract-expired' };
171
+ | { step: 'request'; reason: 'contract-expired' }
172
+ | {
173
+ step: 'request';
174
+ /**
175
+ * Batch path only — a `BatchSubmissionError` whose outcome is not
176
+ * `failed`. `pending`: the batch was, or may have been, accepted and
177
+ * nothing proves whether it deposited — NEVER render it as a
178
+ * failure, which invites a second deposit. `already-submitted`: an
179
+ * earlier submission was proven, so this one was refused.
180
+ */
181
+ reason: 'pending' | 'already-submitted';
182
+ error: unknown;
183
+ };
150
184
 
151
185
  // ---------------------------------------------------------------------------
152
186
  // Ports
@@ -154,6 +188,19 @@ export type DepositFailure =
154
188
 
155
189
  export type { WaitableTransaction };
156
190
 
191
+ /**
192
+ * One call of an atomic batch: a target and its ABI-encoded calldata. No
193
+ * value — neither call the deposit batch makes sends the native token.
194
+ */
195
+ export interface BatchCall {
196
+ to: string;
197
+ data: string;
198
+ }
199
+
200
+ // `BatchRevertedError` lives in `./batch-errors` beside the submission
201
+ // errors, and is re-exported here so every existing import keeps working.
202
+ export { BatchRevertedError };
203
+
157
204
  /** What the KYC signing service hands back. */
158
205
  export interface KycSignature {
159
206
  signature: string;
@@ -257,11 +304,35 @@ export interface DepositPorts {
257
304
  approve(spender: string, amount: BigNumber): Promise<WaitableTransaction>;
258
305
  /** `requestDepositWithKyc`. Defaults to `kasu.deposits.deposit`. */
259
306
  deposit(params: DepositParams): Promise<WaitableTransaction>;
307
+ /**
308
+ * OPTIONAL, and with no default: submit several calls as ONE atomic
309
+ * transaction — all of them apply, or none does. How is the consumer's
310
+ * business; the SDK owns what goes in and in which order.
311
+ *
312
+ * When supplied, the allowance is short and the flow knows the stable
313
+ * token (`DepositFlowOptions.stableAsset`), the run takes the batch path:
314
+ * it parks on `awaiting-authorise`, then hands this port exactly two calls
315
+ * —
316
+ *
317
+ * 0. `stableAsset.approve(spender, amount)` — the EXACT deposit amount,
318
+ * never `MaxUint256`;
319
+ * 1. `spender.requestDepositWithKyc(pool, tranche, amount, '0x',
320
+ * fixedTermConfigId, depositData, { blockExpiration, signature })` —
321
+ * the call the default `deposit` port makes.
322
+ *
323
+ * and neither `approve` nor `deposit` is called. Resolve with something
324
+ * whose `wait()` settles when the batch is mined. Throw
325
+ * `BatchRevertedError` when the batch is known to revert.
326
+ */
327
+ depositBatch?(calls: BatchCall[]): Promise<WaitableTransaction>;
260
328
  /** ms-epoch clock. Defaults to `Date.now`; injected so the TTL is testable. */
261
329
  now?(): number;
262
330
  }
263
331
 
264
- /** Construction options. `kasu.flows.deposit()` fills `spender` in. */
332
+ /**
333
+ * Construction options. `kasu.flows.deposit()` fills `spender` and
334
+ * `stableAsset` in.
335
+ */
265
336
  export interface DepositFlowOptions {
266
337
  /** Agreement validity window; defaults to `CONTRACT_TTL_MS`. */
267
338
  contractTtlMs?: number;
@@ -272,6 +343,17 @@ export interface DepositFlowOptions {
272
343
  * deposit port calls.
273
344
  */
274
345
  spender?: string;
346
+ /**
347
+ * The ERC-20 the deposit is paid in — the target of the batch path's
348
+ * approve call. `kasu.flows.deposit()` passes this chain's
349
+ * `stableAsset.address`.
350
+ *
351
+ * Without it the batch path is never taken, even when `depositBatch` is
352
+ * supplied: an approve encoded against a guessed token is an approval
353
+ * granted on the wrong contract inside a transaction that then reverts.
354
+ * The run falls back to the `approve` and `deposit` ports instead.
355
+ */
356
+ stableAsset?: string;
275
357
  }
276
358
 
277
359
  // ---------------------------------------------------------------------------
@@ -315,10 +397,24 @@ export interface DepositState {
315
397
  step: DepositStep | null;
316
398
  /** 1-based badge position of `step`; `0` while idle. */
317
399
  stepIndex: number;
318
- /** `4`, or `3` when the allowance already covers the deposit. */
400
+ /**
401
+ * `4`, or `3` when the allowance already covers the deposit or the run
402
+ * is batched.
403
+ */
319
404
  stepTotal: number;
320
- /** Whether the approve step is in scope for this run. */
405
+ /**
406
+ * Whether the separate approve STEP is in scope for this run. `false`
407
+ * on a batched run too: its approval rides inside the request step's
408
+ * single transaction, so it never enters `approve` and counts as a
409
+ * three-step run on every badge.
410
+ */
321
411
  approvalRequired: boolean;
412
+ /**
413
+ * Whether this run approves and deposits as ONE atomic batch through the
414
+ * `depositBatch` port. Decided by the allowance pre-check, before the
415
+ * first signature, and `false` on every two-transaction run.
416
+ */
417
+ batched: boolean;
322
418
  /** The generated agreement, from `generating-fetch` onwards. */
323
419
  contract: GenerateContractResponse | null;
324
420
  /** Set with `phase: 'error'`, cleared by `reset()`. */
@@ -359,6 +455,7 @@ const INITIAL: DepositState = {
359
455
  stepIndex: 0,
360
456
  stepTotal: 4,
361
457
  approvalRequired: true,
458
+ batched: false,
362
459
  contract: null,
363
460
  failure: null,
364
461
  };
@@ -384,6 +481,56 @@ type AcceptOutcome =
384
481
  /** `reset()` unparked it. The run's token check drops everything after. */
385
482
  | { kind: 'abandoned' };
386
483
 
484
+ /** How the batch path's authorisation park ends. Same rules as `AcceptOutcome`. */
485
+ type AuthoriseOutcome =
486
+ | { kind: 'authorised' }
487
+ | { kind: 'declined' }
488
+ | { kind: 'abandoned' };
489
+
490
+ const ERC20 = IERC20MetadataAbi__factory.createInterface();
491
+ const LENDING_POOL_MANAGER = ILendingPoolManagerAbi__factory.createInterface();
492
+
493
+ /**
494
+ * The batch path's two calls, encoded by the SDK so no consumer has to: the
495
+ * EXACT-amount approve, then the very `requestDepositWithKyc` the default
496
+ * `deposit` port sends — `swapData` `'0x'`, no value. Both read `amount` from
497
+ * the same variable, so the batch cannot approve one figure and deposit
498
+ * another.
499
+ */
500
+ function encodeDepositBatch(args: {
501
+ stableAsset: string;
502
+ spender: string;
503
+ input: DepositFlowInput;
504
+ depositData: string;
505
+ kyc: KycSignature;
506
+ }): BatchCall[] {
507
+ const { stableAsset, spender, input, depositData, kyc } = args;
508
+ return [
509
+ {
510
+ to: stableAsset,
511
+ data: ERC20.encodeFunctionData('approve', [spender, input.amount]),
512
+ },
513
+ {
514
+ to: spender,
515
+ data: LENDING_POOL_MANAGER.encodeFunctionData(
516
+ 'requestDepositWithKyc',
517
+ [
518
+ input.poolId,
519
+ input.trancheId,
520
+ input.amount,
521
+ '0x',
522
+ input.fixedTermConfigId,
523
+ depositData,
524
+ {
525
+ blockExpiration: kyc.blockExpiration,
526
+ signature: kyc.signature,
527
+ },
528
+ ],
529
+ ),
530
+ },
531
+ ];
532
+ }
533
+
387
534
  /** 1-based badge position, with `approve` dropped when it is out of scope. */
388
535
  function stepIndexOf(step: DepositStep, approvalRequired: boolean): number {
389
536
  const order: DepositStep[] = approvalRequired
@@ -435,6 +582,33 @@ function classifyRequest(err: unknown): DepositFailure {
435
582
  return classifyWalletFailure('request', err);
436
583
  }
437
584
 
585
+ /**
586
+ * A `depositBatch` rejection. A submitter that KNOWS the batch reverts says so
587
+ * with `BatchRevertedError`; anything else is read exactly as a direct
588
+ * deposit's throw is, so a non-wallet error can never become `cancelled`.
589
+ */
590
+ function classifyBatch(err: unknown): DepositFailure {
591
+ // A submission outcome is read FIRST and taken at its word. It must never
592
+ // reach the revert-data walk below: that walk follows `cause`, and an
593
+ // ambiguous outcome whose cause happens to carry revert bytes would read
594
+ // as `reverted` or `insufficient-balance` — "nothing was deposited, try
595
+ // again" — on a batch that may have moved the money.
596
+ if (err instanceof BatchSubmissionError) {
597
+ return err.outcome === 'failed'
598
+ ? { step: 'request', reason: 'failed', error: err }
599
+ : { step: 'request', reason: err.outcome, error: err };
600
+ }
601
+ if (err instanceof BatchRevertedError) {
602
+ return {
603
+ step: 'request',
604
+ reason: 'reverted',
605
+ revertError: err.revertError,
606
+ error: err,
607
+ };
608
+ }
609
+ return classifyRequest(err);
610
+ }
611
+
438
612
  // ---------------------------------------------------------------------------
439
613
  // The flow
440
614
  // ---------------------------------------------------------------------------
@@ -442,6 +616,7 @@ function classifyRequest(err: unknown): DepositFailure {
442
616
  export class DepositFlow extends Flow<DepositState, DepositFlowInput> {
443
617
  private readonly _ttlMs: number;
444
618
  private readonly _defaultSpender: string | undefined;
619
+ private readonly _stableAsset: string | undefined;
445
620
  private readonly _now: () => number;
446
621
 
447
622
  /**
@@ -464,6 +639,15 @@ export class DepositFlow extends Flow<DepositState, DepositFlowInput> {
464
639
  */
465
640
  private _acceptingFor: number | null = null;
466
641
 
642
+ /**
643
+ * The batch path's authorisation handshake. Same shape and lifetime as
644
+ * `_accept`: the run parks on it, `authoriseTransfer`, `declineTransfer`
645
+ * and `reset` settle it, and it is cleared the moment it settles.
646
+ */
647
+ private _authorise: {
648
+ settle: (outcome: AuthoriseOutcome) => void;
649
+ } | null = null;
650
+
467
651
  constructor(
468
652
  private readonly _ports: DepositPorts,
469
653
  opts?: DepositFlowOptions,
@@ -471,6 +655,7 @@ export class DepositFlow extends Flow<DepositState, DepositFlowInput> {
471
655
  super(INITIAL);
472
656
  this._ttlMs = opts?.contractTtlMs ?? CONTRACT_TTL_MS;
473
657
  this._defaultSpender = opts?.spender;
658
+ this._stableAsset = opts?.stableAsset;
474
659
  // Called through the ports object, never captured off it: a consumer
475
660
  // whose clock is a method on its own object keeps its `this`.
476
661
  this._now = (): number => _ports.now?.() ?? Date.now();
@@ -536,6 +721,33 @@ export class DepositFlow extends Flow<DepositState, DepositFlowInput> {
536
721
  bridge.settle({ kind: 'declined' });
537
722
  }
538
723
 
724
+ /**
725
+ * Batch path: authorise the single transaction that approves and deposits,
726
+ * and resume the parked run. A no-op when nothing is parked on
727
+ * `awaiting-authorise`, so a double tap cannot submit twice.
728
+ *
729
+ * The batch has no wallet prompt per call, so this is the lender's
730
+ * explicit consent to the transfer — call it only from their action.
731
+ */
732
+ authoriseTransfer(): void {
733
+ const bridge = this._authorise;
734
+ if (!bridge) return;
735
+ this._authorise = null;
736
+ bridge.settle({ kind: 'authorised' });
737
+ }
738
+
739
+ /**
740
+ * Batch path: refuse the transfer. The run ends on `error` with `{ step:
741
+ * 'request', reason: 'cancelled' }` and nothing is submitted. A no-op when
742
+ * nothing is parked on `awaiting-authorise`.
743
+ */
744
+ declineTransfer(): void {
745
+ const bridge = this._authorise;
746
+ if (!bridge) return;
747
+ this._authorise = null;
748
+ bridge.settle({ kind: 'declined' });
749
+ }
750
+
539
751
  /** True only while THIS generation is waiting on the acceptance signature. */
540
752
  private _isAccepting(): boolean {
541
753
  return (
@@ -550,6 +762,9 @@ export class DepositFlow extends Flow<DepositState, DepositFlowInput> {
550
762
  this._accept = null;
551
763
  this._acceptingFor = null;
552
764
  bridge?.settle({ kind: 'abandoned' });
765
+ const authorise = this._authorise;
766
+ this._authorise = null;
767
+ authorise?.settle({ kind: 'abandoned' });
553
768
  }
554
769
 
555
770
  // -----------------------------------------------------------------------
@@ -560,6 +775,7 @@ export class DepositFlow extends Flow<DepositState, DepositFlowInput> {
560
775
  ): Promise<void> {
561
776
  this._accept = null;
562
777
  this._acceptingFor = null;
778
+ this._authorise = null;
563
779
  const ports = this._ports;
564
780
  const owner = input.userAddress.toLowerCase();
565
781
 
@@ -582,22 +798,35 @@ export class DepositFlow extends Flow<DepositState, DepositFlowInput> {
582
798
  // live, never cached: an exact-amount approval is fully consumed by
583
799
  // the deposit it paid for, so a stale allowance is exactly the value
584
800
  // that would wrongly skip the approve and revert the deposit.
585
- let approvalRequired = true;
801
+ let allowanceShort = true;
586
802
  try {
587
803
  const allowance = await ports.readAllowance(owner, spender);
588
- approvalRequired = allowance.lt(input.amount);
804
+ allowanceShort = allowance.lt(input.amount);
589
805
  } catch {
590
806
  // A read failure is not a reason to skip an approval. Assume one is
591
807
  // needed: the cost is a redundant approve, the alternative is a
592
808
  // reverted deposit.
593
- approvalRequired = true;
809
+ allowanceShort = true;
594
810
  }
595
811
  if (!this._store.isCurrent(token)) return;
596
812
 
813
+ // The path is chosen HERE, with the allowance, so the badge is right
814
+ // from the first state the lender sees. Batch only when there is an
815
+ // approval to fold in, a port to submit it, and a known token to
816
+ // approve on — never a guessed one. A batched run never enters the
817
+ // `approve` step, so every badge counts it as a three-step run.
818
+ const stableAsset = this._stableAsset;
819
+ const batched =
820
+ allowanceShort &&
821
+ typeof ports.depositBatch === 'function' &&
822
+ !!stableAsset;
823
+ const approvalRequired = allowanceShort && !batched;
824
+
597
825
  // 2. Generate — sign the auth message.
598
826
  this._store.patch(
599
827
  {
600
828
  approvalRequired,
829
+ batched,
601
830
  phase: 'generating-sign',
602
831
  ...badgeFor('generate', approvalRequired),
603
832
  },
@@ -736,6 +965,19 @@ export class DepositFlow extends Flow<DepositState, DepositFlowInput> {
736
965
  return;
737
966
  }
738
967
 
968
+ if (batched && stableAsset) {
969
+ await this._runBatch({
970
+ token,
971
+ input,
972
+ owner,
973
+ spender,
974
+ stableAsset,
975
+ depositData,
976
+ contract,
977
+ });
978
+ return;
979
+ }
980
+
739
981
  // 6. Approve — the EXACT amount, never `MaxUint256`. House rule: an
740
982
  // unlimited allowance outlives the deposit it was granted for, and a
741
983
  // later exploit of the spender would drain a wallet that has long
@@ -818,6 +1060,136 @@ export class DepositFlow extends Flow<DepositState, DepositFlowInput> {
818
1060
  );
819
1061
  }
820
1062
 
1063
+ /**
1064
+ * The batch path, from the accepted agreement to the receipt.
1065
+ *
1066
+ * 1. PARK on `awaiting-authorise`. A batch puts no wallet prompt in front
1067
+ * of each call, so the flow waits for an explicit authorisation before
1068
+ * any money moves. `declineTransfer()` ends it as `cancelled`.
1069
+ * 2. Re-check the TTL: the lender may have idled on the authorisation as
1070
+ * long as they did on the agreement.
1071
+ * 3. Only now fetch the KYC signature — it has a block expiration of its
1072
+ * own, and an authorisation left open must not spend it.
1073
+ * 4. Encode both calls and hand them to `depositBatch`; then the receipt.
1074
+ */
1075
+ private async _runBatch(args: {
1076
+ token: number;
1077
+ input: DepositFlowInput;
1078
+ owner: string;
1079
+ spender: string;
1080
+ stableAsset: string;
1081
+ depositData: string;
1082
+ contract: GenerateContractResponse;
1083
+ }): Promise<void> {
1084
+ const { token, input, owner, spender, stableAsset, depositData } =
1085
+ args;
1086
+ const ports = this._ports;
1087
+ // A batched run is a three-step run on every badge.
1088
+ const approvalRequired = false;
1089
+
1090
+ // Every settler clears the handshake before settling it, so there is
1091
+ // nothing to clear here — and clearing it anyway could drop the
1092
+ // handshake of a NEWER run that `reset(); start()` already parked.
1093
+ const outcome = await new Promise<AuthoriseOutcome>((resolve) => {
1094
+ this._authorise = { settle: resolve };
1095
+ this._store.patch(
1096
+ {
1097
+ phase: 'awaiting-authorise',
1098
+ ...badgeFor('request', approvalRequired),
1099
+ },
1100
+ token,
1101
+ );
1102
+ });
1103
+ if (!this._store.isCurrent(token)) return;
1104
+ if (outcome.kind === 'declined') {
1105
+ this._fail(token, approvalRequired, {
1106
+ step: 'request',
1107
+ reason: 'cancelled',
1108
+ });
1109
+ return;
1110
+ }
1111
+ if (outcome.kind !== 'authorised') return;
1112
+
1113
+ if (this._now() > args.contract.timestamp + this._ttlMs) {
1114
+ this._fail(token, approvalRequired, {
1115
+ step: 'request',
1116
+ reason: 'contract-expired',
1117
+ });
1118
+ return;
1119
+ }
1120
+
1121
+ this._store.patch(
1122
+ { phase: 'request-sign', ...badgeFor('request', approvalRequired) },
1123
+ token,
1124
+ );
1125
+
1126
+ // The consumer's backend, so `failed` — never `cancelled` — exactly as
1127
+ // on the two-transaction path.
1128
+ let kyc: KycSignature;
1129
+ try {
1130
+ const kycParams = await ports.buildKycParams(
1131
+ owner as `0x${string}`,
1132
+ );
1133
+ kyc = await ports.getKycSignature(kycParams);
1134
+ } catch (err) {
1135
+ this._fail(token, approvalRequired, {
1136
+ step: 'request',
1137
+ reason: 'failed',
1138
+ error: err,
1139
+ });
1140
+ return;
1141
+ }
1142
+ if (!this._store.isCurrent(token)) return;
1143
+
1144
+ // Encoded from values a backend returned (the KYC signature), so a
1145
+ // shape the coder refuses is a failure the consumer can render, not a
1146
+ // rejection out of a `start()` that promises it has none.
1147
+ let calls: BatchCall[];
1148
+ try {
1149
+ calls = encodeDepositBatch({
1150
+ stableAsset,
1151
+ spender,
1152
+ input,
1153
+ depositData,
1154
+ kyc,
1155
+ });
1156
+ } catch (err) {
1157
+ this._fail(token, approvalRequired, {
1158
+ step: 'request',
1159
+ reason: 'failed',
1160
+ error: err,
1161
+ });
1162
+ return;
1163
+ }
1164
+
1165
+ try {
1166
+ // Called through the ports object, so a port that is a method
1167
+ // keeps its `this`. Its presence decided the path; a consumer
1168
+ // that removed it since gets a `failed`, not a crash.
1169
+ if (typeof ports.depositBatch !== 'function') {
1170
+ throw new Error('DepositFlow: the depositBatch port is gone');
1171
+ }
1172
+ const tx = await ports.depositBatch(calls);
1173
+ this._store.patch(
1174
+ {
1175
+ phase: 'request-confirm',
1176
+ ...badgeFor('request', approvalRequired),
1177
+ },
1178
+ token,
1179
+ );
1180
+ await tx.wait();
1181
+ } catch (err) {
1182
+ this._fail(token, approvalRequired, classifyBatch(err));
1183
+ return;
1184
+ }
1185
+ if (!this._store.isCurrent(token)) return;
1186
+
1187
+ this._store.patch(
1188
+ { phase: 'success', ...badgeFor('request', approvalRequired) },
1189
+ token,
1190
+ );
1191
+ }
1192
+
821
1193
  private _fail(
822
1194
  token: number,
823
1195
  approvalRequired: boolean,
@@ -10,6 +10,33 @@
10
10
  * Every application drove its own copy of the deposit pipeline before this, and
11
11
  * the copies had already started to differ.
12
12
  */
13
+ export {
14
+ BatchPendingError,
15
+ BatchRevertedError,
16
+ batchOutcomeOf,
17
+ BatchSubmissionError,
18
+ isDefinitivelyNotSubmitted,
19
+ } from './batch-errors';
20
+ export type { BatchSubmissionOutcome } from './batch-errors';
21
+
22
+ export {
23
+ checkBatchOutcome,
24
+ DEFAULT_BATCH_POLLING_POLICY,
25
+ submitBatch,
26
+ submitSponsoredCall,
27
+ } from './batch-submission';
28
+ export type {
29
+ BatchPollingPolicy,
30
+ BatchStatusRead,
31
+ BatchSubmission,
32
+ BatchSubmissionPorts,
33
+ ExecutedCall,
34
+ ProvenBatch,
35
+ SponsoredCallSubmission,
36
+ SponsoredCallSubmissionPorts,
37
+ SubmitBatchCall,
38
+ } from './batch-submission';
39
+
13
40
  export { Flow } from './flow';
14
41
  export { FlowStore } from './observable';
15
42
  export type { WaitableTransaction } from './observable';
@@ -22,6 +49,7 @@ export {
22
49
  NO_SPENDER_MESSAGE,
23
50
  } from './deposit-flow';
24
51
  export type {
52
+ BatchCall,
25
53
  ContractMessageRequest,
26
54
  DepositFailure,
27
55
  DepositFlowInput,
@@ -36,7 +64,12 @@ export type {
36
64
  LoanAgreementRequest,
37
65
  } from './deposit-flow';
38
66
 
39
- export { NO_KYC_PARAMS_MESSAGE, WithdrawFlow } from './withdraw-flow';
67
+ export {
68
+ NO_KYC_PARAMS_MESSAGE,
69
+ NO_WITHDRAWAL_ENCODER_MESSAGE,
70
+ NOT_A_WITHDRAWAL_CALL_MESSAGE,
71
+ WithdrawFlow,
72
+ } from './withdraw-flow';
40
73
  export type {
41
74
  WithdrawFailure,
42
75
  WithdrawFlowInput,