@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.
- package/README.md +162 -7
- package/dist/bundle.cjs.js +2292 -449
- package/dist/bundle.esm.js +2253 -451
- package/dist/domain/batch-proof.d.ts +210 -0
- package/dist/domain/batch-proof.js +384 -0
- package/dist/domain/batch-proof.js.map +1 -0
- package/dist/domain/explorer-urls.d.ts +33 -0
- package/dist/domain/explorer-urls.js +38 -0
- package/dist/domain/explorer-urls.js.map +1 -0
- package/dist/domain/fixed-term.d.ts +74 -0
- package/dist/domain/fixed-term.js +86 -0
- package/dist/domain/fixed-term.js.map +1 -0
- package/dist/domain/index.d.ts +12 -2
- package/dist/domain/index.js +7 -2
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/partners.d.ts +29 -0
- package/dist/domain/partners.js +20 -0
- package/dist/domain/partners.js.map +1 -1
- package/dist/domain/requests.d.ts +24 -0
- package/dist/domain/requests.js +33 -0
- package/dist/domain/requests.js.map +1 -1
- package/dist/domain/sponsored-calls.d.ts +74 -0
- package/dist/domain/sponsored-calls.js +156 -0
- package/dist/domain/sponsored-calls.js.map +1 -0
- package/dist/domain/yields.d.ts +121 -0
- package/dist/domain/yields.js +151 -0
- package/dist/domain/yields.js.map +1 -0
- package/dist/facade/calls.d.ts +59 -0
- package/dist/facade/calls.js +89 -0
- package/dist/facade/calls.js.map +1 -0
- package/dist/facade/chain-configs.js +5 -0
- package/dist/facade/chain-configs.js.map +1 -1
- package/dist/facade/flows.d.ts +40 -3
- package/dist/facade/flows.js +36 -3
- package/dist/facade/flows.js.map +1 -1
- package/dist/facade/index.d.ts +3 -1
- package/dist/facade/index.js +1 -0
- package/dist/facade/index.js.map +1 -1
- package/dist/facade/kasu.d.ts +8 -2
- package/dist/facade/kasu.js +17 -4
- package/dist/facade/kasu.js.map +1 -1
- package/dist/facade/strategies.d.ts +43 -2
- package/dist/facade/strategies.js +80 -4
- package/dist/facade/strategies.js.map +1 -1
- package/dist/facade/types.d.ts +18 -0
- package/dist/flows/batch-errors.d.ts +74 -0
- package/dist/flows/batch-errors.js +83 -0
- package/dist/flows/batch-errors.js.map +1 -0
- package/dist/flows/batch-submission.d.ts +223 -0
- package/dist/flows/batch-submission.js +321 -0
- package/dist/flows/batch-submission.js.map +1 -0
- package/dist/flows/deposit-flow.d.ts +128 -4
- package/dist/flows/deposit-flow.js +233 -4
- package/dist/flows/deposit-flow.js.map +1 -1
- package/dist/flows/index.d.ts +6 -2
- package/dist/flows/index.js +3 -1
- package/dist/flows/index.js.map +1 -1
- package/dist/flows/withdraw-flow.d.ts +95 -4
- package/dist/flows/withdraw-flow.js +107 -2
- package/dist/flows/withdraw-flow.js.map +1 -1
- package/dist/services/DataService/queries.js +1 -0
- package/dist/services/DataService/queries.js.map +1 -1
- package/dist/services/UserLending/user-lending.d.ts +13 -0
- package/dist/services/UserLending/user-lending.js +25 -4
- package/dist/services/UserLending/user-lending.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/batch-proof.ts +576 -0
- package/src/domain/explorer-urls.ts +74 -0
- package/src/domain/fixed-term.ts +115 -0
- package/src/domain/index.ts +56 -0
- package/src/domain/partners.ts +35 -0
- package/src/domain/requests.ts +37 -0
- package/src/domain/sponsored-calls.ts +213 -0
- package/src/domain/yields.ts +189 -0
- package/src/facade/calls.ts +135 -0
- package/src/facade/chain-configs.ts +5 -0
- package/src/facade/flows.ts +35 -2
- package/src/facade/index.ts +3 -0
- package/src/facade/kasu.ts +32 -2
- package/src/facade/strategies.ts +98 -7
- package/src/facade/types.ts +19 -0
- package/src/flows/batch-errors.ts +118 -0
- package/src/flows/batch-submission.ts +511 -0
- package/src/flows/deposit-flow.ts +379 -7
- package/src/flows/index.ts +34 -1
- package/src/flows/withdraw-flow.ts +219 -10
- package/src/services/DataService/queries.ts +1 -0
- 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
|
-
/**
|
|
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
|
-
/**
|
|
400
|
+
/**
|
|
401
|
+
* `4`, or `3` when the allowance already covers the deposit or the run
|
|
402
|
+
* is batched.
|
|
403
|
+
*/
|
|
319
404
|
stepTotal: number;
|
|
320
|
-
/**
|
|
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
|
|
801
|
+
let allowanceShort = true;
|
|
586
802
|
try {
|
|
587
803
|
const allowance = await ports.readAllowance(owner, spender);
|
|
588
|
-
|
|
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
|
-
|
|
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,
|
package/src/flows/index.ts
CHANGED
|
@@ -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 {
|
|
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,
|