@kasufinance/kasu-sdk 2.6.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.
- package/README.md +128 -3
- package/dist/bundle.cjs.js +1119 -254
- package/dist/bundle.esm.js +1109 -255
- package/dist/domain/index.d.ts +3 -2
- package/dist/domain/index.js +2 -2
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/requests.d.ts +42 -4
- package/dist/domain/requests.js +20 -4
- package/dist/domain/requests.js.map +1 -1
- package/dist/domain/wallet-errors.d.ts +63 -8
- package/dist/domain/wallet-errors.js +115 -16
- package/dist/domain/wallet-errors.js.map +1 -1
- package/dist/facade/deposits.js +2 -1
- package/dist/facade/deposits.js.map +1 -1
- package/dist/facade/flows.d.ts +95 -0
- package/dist/facade/flows.js +116 -0
- package/dist/facade/flows.js.map +1 -0
- package/dist/facade/index.d.ts +3 -0
- package/dist/facade/index.js +3 -0
- package/dist/facade/index.js.map +1 -1
- package/dist/facade/kasu.d.ts +6 -2
- package/dist/facade/kasu.js +28 -3
- package/dist/facade/kasu.js.map +1 -1
- package/dist/facade/read-only.d.ts +12 -0
- package/dist/facade/read-only.js +13 -0
- package/dist/facade/read-only.js.map +1 -0
- package/dist/flows/deposit-flow.d.ts +301 -0
- package/dist/flows/deposit-flow.js +358 -0
- package/dist/flows/deposit-flow.js.map +1 -0
- package/dist/flows/flow.d.ts +72 -0
- package/dist/flows/flow.js +110 -0
- package/dist/flows/flow.js.map +1 -0
- package/dist/flows/index.d.ts +19 -0
- package/dist/flows/index.js +17 -0
- package/dist/flows/index.js.map +1 -0
- package/dist/flows/observable.d.ts +59 -0
- package/dist/flows/observable.js +81 -0
- package/dist/flows/observable.js.map +1 -0
- package/dist/flows/withdraw-flow.d.ts +99 -0
- package/dist/flows/withdraw-flow.js +83 -0
- package/dist/flows/withdraw-flow.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/services/DataService/data-service.js +0 -1
- package/dist/services/DataService/data-service.js.map +1 -1
- package/package.json +8 -2
- package/src/domain/index.ts +7 -1
- package/src/domain/requests.ts +57 -4
- package/src/domain/wallet-errors.ts +135 -18
- package/src/facade/deposits.ts +2 -3
- package/src/facade/flows.ts +172 -0
- package/src/facade/index.ts +8 -0
- package/src/facade/kasu.ts +43 -3
- package/src/facade/read-only.ts +13 -0
- package/src/flows/deposit-flow.ts +775 -0
- package/src/flows/flow.ts +108 -0
- package/src/flows/index.ts +45 -0
- package/src/flows/observable.ts +97 -0
- package/src/flows/withdraw-flow.ts +210 -0
- package/src/index.ts +7 -0
- package/src/services/DataService/data-service.ts +0 -1
- package/dist/domain/au-minimum.test.d.ts +0 -1
- package/dist/domain/au-minimum.test.js +0 -202
- package/dist/domain/au-minimum.test.js.map +0 -1
- package/dist/domain/deposit-bounds.test.d.ts +0 -1
- package/dist/domain/deposit-bounds.test.js +0 -135
- package/dist/domain/deposit-bounds.test.js.map +0 -1
- package/dist/domain/loan-contract.test.d.ts +0 -1
- package/dist/domain/loan-contract.test.js +0 -255
- package/dist/domain/loan-contract.test.js.map +0 -1
- package/dist/domain/partners.test.d.ts +0 -1
- package/dist/domain/partners.test.js +0 -53
- package/dist/domain/partners.test.js.map +0 -1
- package/dist/domain/pools.test.d.ts +0 -1
- package/dist/domain/pools.test.js +0 -184
- package/dist/domain/pools.test.js.map +0 -1
- package/dist/domain/rates.test.d.ts +0 -1
- package/dist/domain/rates.test.js +0 -181
- package/dist/domain/rates.test.js.map +0 -1
- package/dist/domain/requests.test.d.ts +0 -1
- package/dist/domain/requests.test.js +0 -470
- package/dist/domain/requests.test.js.map +0 -1
- package/dist/domain/settlement.test.d.ts +0 -1
- package/dist/domain/settlement.test.js +0 -152
- package/dist/domain/settlement.test.js.map +0 -1
- package/dist/domain/tranche-display-name.test.d.ts +0 -1
- package/dist/domain/tranche-display-name.test.js +0 -58
- package/dist/domain/tranche-display-name.test.js.map +0 -1
- package/dist/domain/tranches.test.d.ts +0 -1
- package/dist/domain/tranches.test.js +0 -206
- package/dist/domain/tranches.test.js.map +0 -1
- package/dist/domain/wallet-errors.test.d.ts +0 -1
- package/dist/domain/wallet-errors.test.js +0 -71
- package/dist/domain/wallet-errors.test.js.map +0 -1
- package/dist/facade/config.test.d.ts +0 -1
- package/dist/facade/config.test.js +0 -216
- package/dist/facade/config.test.js.map +0 -1
- package/dist/facade/facade.test.d.ts +0 -1
- package/dist/facade/facade.test.js +0 -278
- package/dist/facade/facade.test.js.map +0 -1
- package/dist/services/Locking/calculate-apy.test.d.ts +0 -1
- package/dist/services/Locking/calculate-apy.test.js +0 -41
- package/dist/services/Locking/calculate-apy.test.js.map +0 -1
- package/dist/tests/sample.test.d.ts +0 -1
- package/dist/tests/sample.test.js +0 -59
- package/dist/tests/sample.test.js.map +0 -1
- package/src/domain/au-minimum.test.ts +0 -371
- package/src/domain/deposit-bounds.test.ts +0 -200
- package/src/domain/loan-contract.test.ts +0 -343
- package/src/domain/partners.test.ts +0 -83
- package/src/domain/pools.test.ts +0 -260
- package/src/domain/rates.test.ts +0 -254
- package/src/domain/requests.test.ts +0 -653
- package/src/domain/settlement.test.ts +0 -198
- package/src/domain/tranche-display-name.test.ts +0 -96
- package/src/domain/tranches.test.ts +0 -300
- package/src/domain/wallet-errors.test.ts +0 -100
- package/src/facade/config.test.ts +0 -265
- package/src/facade/facade.test.ts +0 -374
- package/src/services/Locking/calculate-apy.test.ts +0 -52
- package/src/tests/sample.test.ts +0 -63
package/dist/bundle.cjs.js
CHANGED
|
@@ -16207,7 +16207,6 @@ class DataService {
|
|
|
16207
16207
|
return __awaiter(this, void 0, void 0, function* () {
|
|
16208
16208
|
var _a;
|
|
16209
16209
|
const poolDelegateProfileAndHistoryDirectus = (yield this._directus.request(bs('PoolDelegateProfileAndHistory', {
|
|
16210
|
-
// @ts-ignore Directus' deep-fields typing is fragile across versions
|
|
16211
16210
|
fields: ['*', { otherPools: ['*'] }],
|
|
16212
16211
|
})));
|
|
16213
16212
|
const [poolNames, directusPoolNames] = yield Promise.all([
|
|
@@ -24353,6 +24352,19 @@ const CHAIN_CONFIGS = {
|
|
|
24353
24352
|
},
|
|
24354
24353
|
};
|
|
24355
24354
|
|
|
24355
|
+
/**
|
|
24356
|
+
* The refusal every write path shares when the instance holds a Provider
|
|
24357
|
+
* rather than a Signer.
|
|
24358
|
+
*
|
|
24359
|
+
* One constant, because the message's job is to name the fix — `kasu.connect`
|
|
24360
|
+
* — and three near-identical wordings across the facades would be three
|
|
24361
|
+
* different pieces of advice for one problem. ethers' own
|
|
24362
|
+
* "sending a transaction requires a signer" arrives several frames deeper,
|
|
24363
|
+
* after the params have been encoded, and says nothing about how to get a
|
|
24364
|
+
* signer onto THIS object.
|
|
24365
|
+
*/
|
|
24366
|
+
const READ_ONLY_MESSAGE = 'Kasu: this instance is read-only; call kasu.connect(signer) first';
|
|
24367
|
+
|
|
24356
24368
|
/**
|
|
24357
24369
|
* High-level facade for deposit and withdrawal operations.
|
|
24358
24370
|
*
|
|
@@ -24381,7 +24393,7 @@ class DepositsFacade {
|
|
|
24381
24393
|
*/
|
|
24382
24394
|
assertWritable() {
|
|
24383
24395
|
if (this._isReadOnly) {
|
|
24384
|
-
throw new Error(
|
|
24396
|
+
throw new Error(READ_ONLY_MESSAGE);
|
|
24385
24397
|
}
|
|
24386
24398
|
}
|
|
24387
24399
|
/**
|
|
@@ -24409,49 +24421,1067 @@ class DepositsFacade {
|
|
|
24409
24421
|
});
|
|
24410
24422
|
}
|
|
24411
24423
|
/**
|
|
24412
|
-
* Submit a withdrawal request for a specific stable asset amount.
|
|
24424
|
+
* Submit a withdrawal request for a specific stable asset amount.
|
|
24425
|
+
*/
|
|
24426
|
+
withdraw(params) {
|
|
24427
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24428
|
+
this.assertWritable();
|
|
24429
|
+
return yield this._userLending.requestWithdrawalInAsset(params.poolId, params.trancheId, params.amount);
|
|
24430
|
+
});
|
|
24431
|
+
}
|
|
24432
|
+
/**
|
|
24433
|
+
* Withdraw the entire balance from a tranche.
|
|
24434
|
+
*/
|
|
24435
|
+
withdrawMax(poolId, trancheId, userAddress) {
|
|
24436
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24437
|
+
this.assertWritable();
|
|
24438
|
+
return yield this._userLending.requestWithdrawalMax(poolId, trancheId, userAddress);
|
|
24439
|
+
});
|
|
24440
|
+
}
|
|
24441
|
+
/**
|
|
24442
|
+
* Build KYC signature parameters for the Nexera verification flow.
|
|
24443
|
+
*
|
|
24444
|
+
* The integrator is responsible for sending these to their backend,
|
|
24445
|
+
* which obtains the signature from Nexera's signing service.
|
|
24446
|
+
*/
|
|
24447
|
+
buildKycParams(userAddress) {
|
|
24448
|
+
return this._userLending.buildKycSignatureParams(userAddress, this._chainId);
|
|
24449
|
+
}
|
|
24450
|
+
/**
|
|
24451
|
+
* Check whether the pool is currently in a clearing period
|
|
24452
|
+
* (deposits/withdrawals are temporarily paused).
|
|
24453
|
+
*/
|
|
24454
|
+
isClearingPending(poolId) {
|
|
24455
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24456
|
+
return yield this._userLending.isClearingPending(poolId);
|
|
24457
|
+
});
|
|
24458
|
+
}
|
|
24459
|
+
/**
|
|
24460
|
+
* Get the current epoch number.
|
|
24461
|
+
*/
|
|
24462
|
+
getCurrentEpoch() {
|
|
24463
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24464
|
+
return yield this._userLending.getCurrentEpoch();
|
|
24465
|
+
});
|
|
24466
|
+
}
|
|
24467
|
+
}
|
|
24468
|
+
|
|
24469
|
+
/**
|
|
24470
|
+
* The tiny observable-state kernel both flows are built on.
|
|
24471
|
+
*
|
|
24472
|
+
* Deliberately not a framework store: no React, no signals, no dependency.
|
|
24473
|
+
* A flow is a long asynchronous run that has to publish every transition to
|
|
24474
|
+
* whatever is rendering it, and has to be able to abandon a run that the
|
|
24475
|
+
* consumer walked away from. That is the whole requirement, and it is 60 lines.
|
|
24476
|
+
*
|
|
24477
|
+
* The `generation` counter is the abandonment mechanism. `reset()` bumps it;
|
|
24478
|
+
* an in-flight run captured the previous value, so every later `patch` it
|
|
24479
|
+
* attempts is dropped and every `isCurrent` checkpoint tells it to stop. It is
|
|
24480
|
+
* what makes `reset()` safe to call mid-flight — the run cannot resurrect a
|
|
24481
|
+
* dead state or, worse, drive the UI back into a phase the consumer has left.
|
|
24482
|
+
*/
|
|
24483
|
+
class FlowStore {
|
|
24484
|
+
constructor(_initial) {
|
|
24485
|
+
this._initial = _initial;
|
|
24486
|
+
this._generation = 0;
|
|
24487
|
+
this._listeners = new Set();
|
|
24488
|
+
this._state = _initial;
|
|
24489
|
+
}
|
|
24490
|
+
get state() {
|
|
24491
|
+
return this._state;
|
|
24492
|
+
}
|
|
24493
|
+
/** The run token an in-flight pipeline captures at `start()`. */
|
|
24494
|
+
get generation() {
|
|
24495
|
+
return this._generation;
|
|
24496
|
+
}
|
|
24497
|
+
/** False once `reset()` (or a later `start()`) has superseded `token`. */
|
|
24498
|
+
isCurrent(token) {
|
|
24499
|
+
return this._generation === token;
|
|
24500
|
+
}
|
|
24501
|
+
/**
|
|
24502
|
+
* Subscribe to every transition. Returns the unsubscribe function; calling
|
|
24503
|
+
* it twice is harmless.
|
|
24504
|
+
*
|
|
24505
|
+
* Listeners are NOT called on subscribe — read `state` for the current
|
|
24506
|
+
* value. A listener that throws is not allowed to break the pipeline or
|
|
24507
|
+
* starve the listeners registered after it, so throws are swallowed.
|
|
24508
|
+
*/
|
|
24509
|
+
subscribe(listener) {
|
|
24510
|
+
this._listeners.add(listener);
|
|
24511
|
+
return () => {
|
|
24512
|
+
this._listeners.delete(listener);
|
|
24513
|
+
};
|
|
24514
|
+
}
|
|
24515
|
+
/**
|
|
24516
|
+
* Merge `next` into the state and notify, unless `token` names a
|
|
24517
|
+
* superseded run.
|
|
24518
|
+
*/
|
|
24519
|
+
patch(next, token) {
|
|
24520
|
+
if (token !== undefined && !this.isCurrent(token))
|
|
24521
|
+
return;
|
|
24522
|
+
this._state = Object.assign(Object.assign({}, this._state), next);
|
|
24523
|
+
for (const listener of [...this._listeners]) {
|
|
24524
|
+
try {
|
|
24525
|
+
listener(this._state);
|
|
24526
|
+
}
|
|
24527
|
+
catch (_a) {
|
|
24528
|
+
// A consumer's rendering fault is its own problem; the pipeline
|
|
24529
|
+
// owns real money and keeps going.
|
|
24530
|
+
}
|
|
24531
|
+
}
|
|
24532
|
+
}
|
|
24533
|
+
/**
|
|
24534
|
+
* Back to the initial state, abandoning any in-flight run.
|
|
24535
|
+
*
|
|
24536
|
+
* Subscribers are kept: the consumer that was watching the last run is the
|
|
24537
|
+
* one that will watch the next.
|
|
24538
|
+
*/
|
|
24539
|
+
reset() {
|
|
24540
|
+
this._generation += 1;
|
|
24541
|
+
this.patch(this._initial);
|
|
24542
|
+
}
|
|
24543
|
+
/** Claim the next run token. Any earlier run is abandoned. */
|
|
24544
|
+
beginRun() {
|
|
24545
|
+
this._generation += 1;
|
|
24546
|
+
return this._generation;
|
|
24547
|
+
}
|
|
24548
|
+
}
|
|
24549
|
+
|
|
24550
|
+
/**
|
|
24551
|
+
* The run lifecycle both flows share: one run at a time, every transition
|
|
24552
|
+
* published, and a `reset()` that is safe to call in the middle of one.
|
|
24553
|
+
*
|
|
24554
|
+
* `DepositFlow` and `WithdrawFlow` each carried their own copy of this — the
|
|
24555
|
+
* re-entrancy guard, the `try/finally` around the run, `state`, `isRunning`,
|
|
24556
|
+
* `subscribe`, `reset`. Two copies of a guard is two places for a guard to be
|
|
24557
|
+
* wrong, on the money path, which is the same reason the pipelines themselves
|
|
24558
|
+
* moved out of the applications.
|
|
24559
|
+
*
|
|
24560
|
+
* ## The guard and the run token are separate, and both matter
|
|
24561
|
+
*
|
|
24562
|
+
* `_runToken` is the re-entrancy guard: claimed synchronously, before the
|
|
24563
|
+
* first `await`, so a double tap cannot launch two pipelines. The store's
|
|
24564
|
+
* `generation` is the abandonment mechanism: an in-flight run holds the value
|
|
24565
|
+
* it claimed, and every `patch` and `isCurrent` checkpoint it makes after
|
|
24566
|
+
* `reset()` is dropped.
|
|
24567
|
+
*
|
|
24568
|
+
* `reset()` releases the guard IMMEDIATELY rather than waiting for the
|
|
24569
|
+
* abandoned run to notice. It has to: a run parked on a wallet prompt that
|
|
24570
|
+
* never answers is a promise that may never settle, and a consumer that has
|
|
24571
|
+
* left that screen must be able to start a new run in the same tick. The
|
|
24572
|
+
* abandoned run's `finally` therefore releases the guard only if it still
|
|
24573
|
+
* holds it — by then a NEW run may own it, and clearing it there would let a
|
|
24574
|
+
* third run start alongside the second.
|
|
24575
|
+
*/
|
|
24576
|
+
class Flow {
|
|
24577
|
+
constructor(initial) {
|
|
24578
|
+
/**
|
|
24579
|
+
* The token of the run currently holding the guard, or `null` when none
|
|
24580
|
+
* does. Not a boolean: releasing the guard has to be attributable to the
|
|
24581
|
+
* run that claimed it.
|
|
24582
|
+
*/
|
|
24583
|
+
this._runToken = null;
|
|
24584
|
+
this._store = new FlowStore(initial);
|
|
24585
|
+
}
|
|
24586
|
+
/** The current state. Every transition is also published to `subscribe`. */
|
|
24587
|
+
get state() {
|
|
24588
|
+
return this._store.state;
|
|
24589
|
+
}
|
|
24590
|
+
/** True while a run is in flight, including while parked. */
|
|
24591
|
+
get isRunning() {
|
|
24592
|
+
return this._runToken !== null;
|
|
24593
|
+
}
|
|
24594
|
+
/**
|
|
24595
|
+
* Observe every transition. Returns the unsubscribe function.
|
|
24596
|
+
*
|
|
24597
|
+
* The listener is not called on subscribe; read `state` for the value it
|
|
24598
|
+
* starts from.
|
|
24599
|
+
*/
|
|
24600
|
+
subscribe(listener) {
|
|
24601
|
+
return this._store.subscribe(listener);
|
|
24602
|
+
}
|
|
24603
|
+
/**
|
|
24604
|
+
* Run the pipeline. Resolves when it reaches a terminal phase — it does
|
|
24605
|
+
* not reject, because every outcome a consumer can act on is in `state`.
|
|
24606
|
+
*
|
|
24607
|
+
* A second call while one is in flight is a no-op: one submission per
|
|
24608
|
+
* intent, however many times the button is pressed.
|
|
24609
|
+
*/
|
|
24610
|
+
start(input) {
|
|
24611
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24612
|
+
if (this._runToken !== null)
|
|
24613
|
+
return;
|
|
24614
|
+
this._store.reset();
|
|
24615
|
+
const token = this._store.beginRun();
|
|
24616
|
+
this._runToken = token;
|
|
24617
|
+
try {
|
|
24618
|
+
yield this._run(input, token);
|
|
24619
|
+
}
|
|
24620
|
+
finally {
|
|
24621
|
+
if (this._runToken === token)
|
|
24622
|
+
this._runToken = null;
|
|
24623
|
+
}
|
|
24624
|
+
});
|
|
24625
|
+
}
|
|
24626
|
+
/**
|
|
24627
|
+
* Back to `idle`, abandoning any run in flight: its remaining transitions
|
|
24628
|
+
* are dropped, whatever it is parked on is unparked, and the guard is
|
|
24629
|
+
* released at once so `start()` is accepted in the SAME tick. Subscribers
|
|
24630
|
+
* are kept — the consumer watching the last run is the one watching the
|
|
24631
|
+
* next.
|
|
24632
|
+
*/
|
|
24633
|
+
reset() {
|
|
24634
|
+
this._runToken = null;
|
|
24635
|
+
// Bump the generation BEFORE unparking, so the abandoned run's next
|
|
24636
|
+
// checkpoint already reads as superseded.
|
|
24637
|
+
this._store.reset();
|
|
24638
|
+
this._onAbandon();
|
|
24639
|
+
}
|
|
24640
|
+
/**
|
|
24641
|
+
* Hook for a flow that parks: unpark it. Called by `reset()` after the
|
|
24642
|
+
* generation has been bumped, so anything the abandoned run does with the
|
|
24643
|
+
* result is dropped.
|
|
24644
|
+
*/
|
|
24645
|
+
_onAbandon() {
|
|
24646
|
+
// A flow with nothing parked has nothing to unpark.
|
|
24647
|
+
}
|
|
24648
|
+
}
|
|
24649
|
+
|
|
24650
|
+
/** Narrow the backend's loose `contractType` string to the encoded union. */
|
|
24651
|
+
function asContractType(raw) {
|
|
24652
|
+
return raw === 'exempt' ? 'exempt' : 'retail';
|
|
24653
|
+
}
|
|
24654
|
+
/**
|
|
24655
|
+
* Parse the server's JSON-string `formattedMessage` into a tree. Returns
|
|
24656
|
+
* `null` on parse failure so a renderer can fall back to the plaintext.
|
|
24657
|
+
*/
|
|
24658
|
+
function parseFormattedMessage(raw) {
|
|
24659
|
+
try {
|
|
24660
|
+
const parsed = JSON.parse(raw);
|
|
24661
|
+
if (parsed && typeof parsed === 'object')
|
|
24662
|
+
return parsed;
|
|
24663
|
+
return null;
|
|
24664
|
+
}
|
|
24665
|
+
catch (_a) {
|
|
24666
|
+
return null;
|
|
24667
|
+
}
|
|
24668
|
+
}
|
|
24669
|
+
// ---------------------------------------------------------------------------
|
|
24670
|
+
// The on-chain `depositData` blob
|
|
24671
|
+
// ---------------------------------------------------------------------------
|
|
24672
|
+
/**
|
|
24673
|
+
* Pack the contract version and type into the `versionType` word.
|
|
24674
|
+
*
|
|
24675
|
+
* ```
|
|
24676
|
+
* high byte = contract version (>= 1)
|
|
24677
|
+
* low byte = 0 for retail, 1 for exempt
|
|
24678
|
+
* ```
|
|
24679
|
+
*/
|
|
24680
|
+
function buildContractVersionType(contractVersion, contractType) {
|
|
24681
|
+
return (contractVersion << 8) + (contractType === 'retail' ? 0 : 1);
|
|
24682
|
+
}
|
|
24683
|
+
/**
|
|
24684
|
+
* Build the on-chain `depositData` blob that `requestDepositWithKyc` expects.
|
|
24685
|
+
*
|
|
24686
|
+
* The KasuController decodes the bytes as
|
|
24687
|
+
* `(bytes signature, uint256 timestamp, uint256 versionType)` and uses the
|
|
24688
|
+
* embedded acceptance signature to verify — retrospectively, via the
|
|
24689
|
+
* agreements service `/contract/resolve` — that the lender signed the
|
|
24690
|
+
* loan-contract text. The ABI tuple and the packing are consensus-critical:
|
|
24691
|
+
* these bytes go on chain.
|
|
24692
|
+
*
|
|
24693
|
+
* kasu-ui encodes this with viem, kasu-mobile with ethers v5 (viem is not
|
|
24694
|
+
* available on Expo). This is the ethers v5 implementation, and
|
|
24695
|
+
* `loan-contract.test.ts` pins its output byte-for-byte against fixtures
|
|
24696
|
+
* produced by the viem version, so the two apps can never diverge here.
|
|
24697
|
+
*
|
|
24698
|
+
* @param args.signature EIP-191 signature from the lender accepting
|
|
24699
|
+
* `contractMessage`, as a 0x-prefixed hex string.
|
|
24700
|
+
* @param args.timestamp ms-epoch from the contract response.
|
|
24701
|
+
*/
|
|
24702
|
+
function encodeDepositData(args) {
|
|
24703
|
+
const versionType = buildContractVersionType(args.contractVersion, args.contractType);
|
|
24704
|
+
return ethers.ethers.utils.defaultAbiCoder.encode(['bytes', 'uint256', 'uint256'], [
|
|
24705
|
+
args.signature,
|
|
24706
|
+
ethers.ethers.BigNumber.from(args.timestamp),
|
|
24707
|
+
ethers.ethers.BigNumber.from(versionType),
|
|
24708
|
+
]);
|
|
24709
|
+
}
|
|
24710
|
+
// ---------------------------------------------------------------------------
|
|
24711
|
+
// The signed messages
|
|
24712
|
+
// ---------------------------------------------------------------------------
|
|
24713
|
+
const MONTH_NAMES = [
|
|
24714
|
+
'January',
|
|
24715
|
+
'February',
|
|
24716
|
+
'March',
|
|
24717
|
+
'April',
|
|
24718
|
+
'May',
|
|
24719
|
+
'June',
|
|
24720
|
+
'July',
|
|
24721
|
+
'August',
|
|
24722
|
+
'September',
|
|
24723
|
+
'October',
|
|
24724
|
+
'November',
|
|
24725
|
+
'December',
|
|
24726
|
+
];
|
|
24727
|
+
/**
|
|
24728
|
+
* Format a unix timestamp as `{day} {MonthName} {yyyy}, {HH}:{mm}` in UTC.
|
|
24729
|
+
* Day is non-padded; hour and minute are zero-padded to two digits (24h). A
|
|
24730
|
+
* timestamp with >= 13 digits is treated as milliseconds, otherwise as seconds
|
|
24731
|
+
* — the same auto-detection kasu-backend applies.
|
|
24732
|
+
*
|
|
24733
|
+
* Deliberately a manual formatter with English month names: no locale, no
|
|
24734
|
+
* `Intl`, so the output is byte-identical across runtimes and time zones. This
|
|
24735
|
+
* is not a display date. It goes inside a signed message.
|
|
24736
|
+
*
|
|
24737
|
+
* e.g. 1785313320000 → `"29 July 2026, 08:22"`
|
|
24738
|
+
*/
|
|
24739
|
+
function formatSignTimestampUtc(timestamp) {
|
|
24740
|
+
const ms = timestamp.toString().length >= 13 ? timestamp : timestamp * 1000;
|
|
24741
|
+
const date = new Date(ms);
|
|
24742
|
+
const day = date.getUTCDate();
|
|
24743
|
+
const month = MONTH_NAMES[date.getUTCMonth()];
|
|
24744
|
+
const year = date.getUTCFullYear();
|
|
24745
|
+
const hours = String(date.getUTCHours()).padStart(2, '0');
|
|
24746
|
+
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
|
24747
|
+
return `${day} ${month} ${year}, ${hours}:${minutes}`;
|
|
24748
|
+
}
|
|
24749
|
+
/**
|
|
24750
|
+
* The 4-line human-readable message a lender signs to generate their loan
|
|
24751
|
+
* agreement for review — `POST /contract/generate`.
|
|
24752
|
+
*
|
|
24753
|
+
* ⚠️ BYTE-EXACT PROTOCOL STRING. kasu-backend rebuilds this string from the
|
|
24754
|
+
* request body and verifies the signature against it, so the wording,
|
|
24755
|
+
* ordering, separators, line breaks and date format are all part of the wire
|
|
24756
|
+
* contract. The separator between the line-2 fields is a MIDDLE DOT U+00B7
|
|
24757
|
+
* (·) with a single space on each side; the four lines are joined with `\n`.
|
|
24758
|
+
*
|
|
24759
|
+
* The backend takes this format only when all four display fields are present
|
|
24760
|
+
* and non-empty, and it cross-checks `amountLabel`'s leading number against
|
|
24761
|
+
* the `depositAmount` it was sent (thousands separators stripped) — a message
|
|
24762
|
+
* that states an amount other than the one being executed is refused.
|
|
24763
|
+
*/
|
|
24764
|
+
function buildLoanAgreementSignMessage(p) {
|
|
24765
|
+
return [
|
|
24766
|
+
'Generate my Loan Agreement for review:',
|
|
24767
|
+
`${p.strategyName} · ${p.region} · ${p.optionName} · ${p.amountLabel}.`,
|
|
24768
|
+
`Request made ${formatSignTimestampUtc(p.timestamp)} UTC.`,
|
|
24769
|
+
'This request does not commit me to lend.',
|
|
24770
|
+
].join('\n');
|
|
24771
|
+
}
|
|
24772
|
+
/**
|
|
24773
|
+
* The legacy `/contract/generate` and `/contract/resolve` message.
|
|
24774
|
+
*
|
|
24775
|
+
* ⚠️ BYTE-EXACT PROTOCOL STRING. kasu-backend rebuilds it as
|
|
24776
|
+
* `` `I request contract content for ${address} at ${timestamp}.` `` from the
|
|
24777
|
+
* `address` and `timestamp` fields of the request body — so the string signed
|
|
24778
|
+
* and the body sent must agree exactly, INCLUDING the address casing. This
|
|
24779
|
+
* builder lowercases, and the request body must carry the same lowercased
|
|
24780
|
+
* address; that is what both apps signing this format do today.
|
|
24781
|
+
*
|
|
24782
|
+
* The backend takes this path whenever the four human-readable display fields
|
|
24783
|
+
* are absent, and documents it as permanent until the legacy app is
|
|
24784
|
+
* decommissioned. `/contract/resolve` has no other format — every consumer
|
|
24785
|
+
* signs this one to retrieve an existing agreement.
|
|
24786
|
+
*
|
|
24787
|
+
* @param timestampMs ms-epoch, and the same value sent as the body's
|
|
24788
|
+
* `timestamp`.
|
|
24789
|
+
*/
|
|
24790
|
+
function buildLegacyContractRequestMessage(address, timestampMs) {
|
|
24791
|
+
return `I request contract content for ${address.toLowerCase()} at ${timestampMs}.`;
|
|
24792
|
+
}
|
|
24793
|
+
/**
|
|
24794
|
+
* The `POST /contract/fullname` message.
|
|
24795
|
+
*
|
|
24796
|
+
* ⚠️ BYTE-EXACT PROTOCOL STRING, on the same terms as
|
|
24797
|
+
* `buildLegacyContractRequestMessage`: kasu-backend rebuilds
|
|
24798
|
+
* `` `I request my full name for ${address} at ${timestamp}.` `` from the
|
|
24799
|
+
* request body and verifies the signature against it, so the body must carry
|
|
24800
|
+
* the same lowercased address this builder signs.
|
|
24801
|
+
*
|
|
24802
|
+
* @param timestampMs ms-epoch, and the same value sent as the body's
|
|
24803
|
+
* `timestamp`.
|
|
24804
|
+
*/
|
|
24805
|
+
function buildFullNameRequestMessage(address, timestampMs) {
|
|
24806
|
+
return `I request my full name for ${address.toLowerCase()} at ${timestampMs}.`;
|
|
24807
|
+
}
|
|
24808
|
+
|
|
24809
|
+
/**
|
|
24810
|
+
* Wallet and RPC error predicates — pure functions over `unknown`.
|
|
24811
|
+
*
|
|
24812
|
+
* Every consumer has to tell three things apart when a write fails: the lender
|
|
24813
|
+
* changed their mind, the call would revert, and everything else. The first
|
|
24814
|
+
* two must never be reported as a failure the lender should retry or contact
|
|
24815
|
+
* support about, and each wallet spells them differently, so the shapes are
|
|
24816
|
+
* enumerated once here.
|
|
24817
|
+
*
|
|
24818
|
+
* Lifted from kasu-ui's `src/lib/web3/is-user-rejected.ts` and kasu-mobile's
|
|
24819
|
+
* `src/features/lending/lib/errors.ts` — and now the UNION of the two, so
|
|
24820
|
+
* neither app has to keep a wrapper on top of this one.
|
|
24821
|
+
*/
|
|
24822
|
+
/**
|
|
24823
|
+
* Did the lender reject the request in their wallet?
|
|
24824
|
+
*
|
|
24825
|
+
* Providers surface a rejection in different shapes, and the union of them is
|
|
24826
|
+
* the point of this function existing once:
|
|
24827
|
+
* - MetaMask and most EIP-1193 wallets: `code: 4001`
|
|
24828
|
+
* - Coinbase Wallet and ethers v5: `code: 'ACTION_REJECTED'`
|
|
24829
|
+
* - viem: a `UserRejectedRequestError` name
|
|
24830
|
+
* - WalletConnect and some Privy paths: `Error('User rejected the request')`
|
|
24831
|
+
* - a provider error WRAPPED by another layer, carrying the real code and
|
|
24832
|
+
* message on a nested `error` — the shape Privy's embedded wallet
|
|
24833
|
+
* surfaces on Expo, where the outer object says nothing useful
|
|
24834
|
+
* - ethers' own `reason` field, which is often the only place the text lands
|
|
24835
|
+
* - a PLAIN OBJECT carrying `message`, which `String(err)` would read as
|
|
24836
|
+
* `"[object Object]"`
|
|
24837
|
+
*
|
|
24838
|
+
* Previously kasu-ui's implementation verbatim, with kasu-mobile keeping its
|
|
24839
|
+
* own superset on top. That is precisely the drift this layer exists to stop —
|
|
24840
|
+
* a rejection kasu-mobile recognised and kasu-ui did not was reported to the
|
|
24841
|
+
* same lender as a failure on one app and a cancellation on the other. The
|
|
24842
|
+
* union lives here; the mobile wrapper goes.
|
|
24843
|
+
*
|
|
24844
|
+
* ## The text is read for a SUBJECT, not for a keyword
|
|
24845
|
+
*
|
|
24846
|
+
* The words alone are not the signal. "Declined" and "request rejected" are
|
|
24847
|
+
* also what a rate limiter, a risk engine and a KYC decision say, and those
|
|
24848
|
+
* arrive wrapped in exactly the same envelope a wallet error does — ethers'
|
|
24849
|
+
* `SERVER_ERROR` around `-32603` carries the upstream body on a nested
|
|
24850
|
+
* `error.message`, so `{ error: { message: 'request rejected: rate limit
|
|
24851
|
+
* exceeded' } }` is an RPC refusing to serve us, not a lender refusing to
|
|
24852
|
+
* sign. Reporting one as the other tells a lender they cancelled something
|
|
24853
|
+
* they never saw, and DROPS the real error on the floor.
|
|
24854
|
+
*
|
|
24855
|
+
* So a rejection is a machine-readable wallet code, or a sentence that names
|
|
24856
|
+
* the party who did it: "user rejected", "declined by the user", "cancelled by
|
|
24857
|
+
* the wallet". A bare "declined" is not a rejection, whatever else is on the
|
|
24858
|
+
* envelope.
|
|
24859
|
+
*
|
|
24860
|
+
* Within that rule, being generous is the safe direction: calling a genuine
|
|
24861
|
+
* fault a cancellation costs a lender one retry, while calling a deliberate
|
|
24862
|
+
* rejection a failure sends them to support to report a bug that does not
|
|
24863
|
+
* exist.
|
|
24864
|
+
*/
|
|
24865
|
+
function isUserRejected(err) {
|
|
24866
|
+
var _a;
|
|
24867
|
+
if (!err)
|
|
24868
|
+
return false;
|
|
24869
|
+
if (typeof err === 'object') {
|
|
24870
|
+
const e = err;
|
|
24871
|
+
if (isRejectionCode(e.code))
|
|
24872
|
+
return true;
|
|
24873
|
+
// A wrapped provider error: the outer layer's code is its own, the
|
|
24874
|
+
// inner one is the wallet's.
|
|
24875
|
+
if (isRejectionCode((_a = e.error) === null || _a === void 0 ? void 0 : _a.code))
|
|
24876
|
+
return true;
|
|
24877
|
+
}
|
|
24878
|
+
const lower = rejectionText(err).toLowerCase();
|
|
24879
|
+
return USER_REJECTION_PATTERNS.some((pattern) => pattern.test(lower));
|
|
24880
|
+
}
|
|
24881
|
+
function isRejectionCode(code) {
|
|
24882
|
+
return code === 4001 || code === 'ACTION_REJECTED';
|
|
24883
|
+
}
|
|
24884
|
+
/**
|
|
24885
|
+
* The wordings that name the wallet or the person at it.
|
|
24886
|
+
*
|
|
24887
|
+
* Each one carries a SUBJECT — the identifier a wallet library uses, or the
|
|
24888
|
+
* verb with the party who performed it. That is what separates a lender
|
|
24889
|
+
* pressing Reject from a server declining to answer, and it is why none of
|
|
24890
|
+
* these is a bare "declined" or "rejected".
|
|
24891
|
+
*/
|
|
24892
|
+
const USER_REJECTION_PATTERNS = [
|
|
24893
|
+
// Machine-readable markers that only a wallet layer emits, arriving as
|
|
24894
|
+
// text because something in between stringified the error.
|
|
24895
|
+
/action_rejected/,
|
|
24896
|
+
/user_rejected/,
|
|
24897
|
+
/userrejectedrequest/,
|
|
24898
|
+
// "the user did it", in the orders the wallets write it.
|
|
24899
|
+
/user\s+(?:has\s+)?(?:rejected|denied|declined|refused|cancell?ed)/,
|
|
24900
|
+
/(?:rejected|denied|declined|refused|cancell?ed)\s+by\s+(?:the\s+)?(?:user|wallet|signer|owner)/,
|
|
24901
|
+
// The wallet as the subject, which is how some embedded wallets word it.
|
|
24902
|
+
/(?:wallet|signer)\s+(?:rejected|denied|declined|refused)/,
|
|
24903
|
+
];
|
|
24904
|
+
/**
|
|
24905
|
+
* Every place a wallet might have put the words: the message, ethers' `reason`,
|
|
24906
|
+
* the error's `name` (viem puts the whole signal there), and a wrapped error's
|
|
24907
|
+
* own three. Joined rather than picked, because which one carries the text
|
|
24908
|
+
* depends on how many layers wrapped it.
|
|
24909
|
+
*
|
|
24910
|
+
* A separator is used rather than a bare space: two fields must not be able to
|
|
24911
|
+
* form a phrase across the join that neither of them said.
|
|
24912
|
+
*/
|
|
24913
|
+
function rejectionText(err) {
|
|
24914
|
+
var _a, _b, _c;
|
|
24915
|
+
if (typeof err !== 'object' || err === null)
|
|
24916
|
+
return String(err);
|
|
24917
|
+
const e = err;
|
|
24918
|
+
return [
|
|
24919
|
+
e.message,
|
|
24920
|
+
e.reason,
|
|
24921
|
+
e.name,
|
|
24922
|
+
(_a = e.error) === null || _a === void 0 ? void 0 : _a.message,
|
|
24923
|
+
(_b = e.error) === null || _b === void 0 ? void 0 : _b.reason,
|
|
24924
|
+
(_c = e.error) === null || _c === void 0 ? void 0 : _c.name,
|
|
24925
|
+
]
|
|
24926
|
+
.filter((part) => typeof part === 'string')
|
|
24927
|
+
.join(' | ');
|
|
24928
|
+
}
|
|
24929
|
+
/**
|
|
24930
|
+
* Did the wallet or RPC signal that the on-chain call would revert?
|
|
24931
|
+
*
|
|
24932
|
+
* ethers v5 raises `UNPREDICTABLE_GAS_LIMIT` when gas estimation reverts —
|
|
24933
|
+
* most often an underlying `transferFrom` failing on an insufficient balance
|
|
24934
|
+
* or allowance. Distinct from a rejection: nothing was refused by the lender,
|
|
24935
|
+
* the transaction simply cannot succeed as composed, so the caller should
|
|
24936
|
+
* re-check its preconditions rather than invite a retry.
|
|
24937
|
+
*/
|
|
24938
|
+
function isUnpredictableGas(err) {
|
|
24939
|
+
if (!err || typeof err !== 'object')
|
|
24940
|
+
return false;
|
|
24941
|
+
return err.code === 'UNPREDICTABLE_GAS_LIMIT';
|
|
24942
|
+
}
|
|
24943
|
+
/**
|
|
24944
|
+
* The `cancelled` / `failed` split, for a WALLET call on any step of any flow.
|
|
24945
|
+
*
|
|
24946
|
+
* A lender who pressed Reject is not a fault. Reporting one as the other is
|
|
24947
|
+
* how a support queue fills with people who did exactly what they meant to.
|
|
24948
|
+
*
|
|
24949
|
+
* Generic in the step so both flows share one implementation — it was
|
|
24950
|
+
* duplicated byte-for-byte in each of them, which is the same drift this
|
|
24951
|
+
* layer exists to stop, one level up.
|
|
24952
|
+
*
|
|
24953
|
+
* Only pass it an error a WALLET produced. An HTTP port's throw is always a
|
|
24954
|
+
* `failed`: the lender's wallet was not involved in it, so a backend that
|
|
24955
|
+
* happens to word a refusal "declined" must never be shown to them as
|
|
24956
|
+
* something they did.
|
|
24957
|
+
*/
|
|
24958
|
+
function classifyWalletFailure(step, err) {
|
|
24959
|
+
return isUserRejected(err)
|
|
24960
|
+
? { step, reason: 'cancelled' }
|
|
24961
|
+
: { step, reason: 'failed', error: err };
|
|
24962
|
+
}
|
|
24963
|
+
|
|
24964
|
+
/** Generated agreements are valid for five minutes upstream. */
|
|
24965
|
+
const CONTRACT_TTL_MS = 5 * 60 * 1000;
|
|
24966
|
+
/** What a run without a spender, from either source, fails with. */
|
|
24967
|
+
const NO_SPENDER_MESSAGE = 'DepositFlow: no ERC-20 spender; build the flow with kasu.flows.deposit() or pass `spender` on the input';
|
|
24968
|
+
const INITIAL$1 = {
|
|
24969
|
+
phase: 'idle',
|
|
24970
|
+
step: null,
|
|
24971
|
+
stepIndex: 0,
|
|
24972
|
+
stepTotal: 4,
|
|
24973
|
+
approvalRequired: true,
|
|
24974
|
+
contract: null,
|
|
24975
|
+
failure: null,
|
|
24976
|
+
};
|
|
24977
|
+
/** 1-based badge position, with `approve` dropped when it is out of scope. */
|
|
24978
|
+
function stepIndexOf(step, approvalRequired) {
|
|
24979
|
+
const order = approvalRequired
|
|
24980
|
+
? ['generate', 'confirm', 'approve', 'request']
|
|
24981
|
+
: ['generate', 'confirm', 'request'];
|
|
24982
|
+
return order.indexOf(step) + 1;
|
|
24983
|
+
}
|
|
24984
|
+
function badgeFor(step, approvalRequired) {
|
|
24985
|
+
return {
|
|
24986
|
+
step,
|
|
24987
|
+
stepIndex: stepIndexOf(step, approvalRequired),
|
|
24988
|
+
stepTotal: approvalRequired ? 4 : 3,
|
|
24989
|
+
};
|
|
24990
|
+
}
|
|
24991
|
+
/**
|
|
24992
|
+
* The request step has a third outcome. `UNPREDICTABLE_GAS_LIMIT` here is
|
|
24993
|
+
* almost always `transferFrom` reverting on a balance that cannot cover the
|
|
24994
|
+
* deposit, and it takes precedence: nothing was refused by the lender, so
|
|
24995
|
+
* inviting a retry would just reproduce it.
|
|
24996
|
+
*/
|
|
24997
|
+
function classifyRequest(err) {
|
|
24998
|
+
if (isUnpredictableGas(err)) {
|
|
24999
|
+
return { step: 'request', reason: 'insufficient-balance', error: err };
|
|
25000
|
+
}
|
|
25001
|
+
return classifyWalletFailure('request', err);
|
|
25002
|
+
}
|
|
25003
|
+
// ---------------------------------------------------------------------------
|
|
25004
|
+
// The flow
|
|
25005
|
+
// ---------------------------------------------------------------------------
|
|
25006
|
+
class DepositFlow extends Flow {
|
|
25007
|
+
constructor(_ports, opts) {
|
|
25008
|
+
var _a;
|
|
25009
|
+
super(INITIAL$1);
|
|
25010
|
+
this._ports = _ports;
|
|
25011
|
+
/**
|
|
25012
|
+
* The accept handshake. The run parks on this promise; `acceptContract`,
|
|
25013
|
+
* `declineContract` and `reset` each settle it with an `AcceptOutcome`.
|
|
25014
|
+
* Cleared the moment it settles so a stale resolver from an abandoned run
|
|
25015
|
+
* can never leak into the next one.
|
|
25016
|
+
*/
|
|
25017
|
+
this._accept = null;
|
|
25018
|
+
/**
|
|
25019
|
+
* The run token that is between `acceptContract()` and the wallet
|
|
25020
|
+
* settling, or `null`.
|
|
25021
|
+
*
|
|
25022
|
+
* A token rather than a boolean, because the flag has to belong to the RUN
|
|
25023
|
+
* that set it: after `reset()` out of a wallet prompt that never answers,
|
|
25024
|
+
* the abandoned run's `finally` may not arrive for minutes, and a boolean
|
|
25025
|
+
* left standing refuses both Accept and Decline on every run after it.
|
|
25026
|
+
* A stale token simply is not the current generation.
|
|
25027
|
+
*/
|
|
25028
|
+
this._acceptingFor = null;
|
|
25029
|
+
this._ttlMs = (_a = opts === null || opts === void 0 ? void 0 : opts.contractTtlMs) !== null && _a !== void 0 ? _a : CONTRACT_TTL_MS;
|
|
25030
|
+
this._defaultSpender = opts === null || opts === void 0 ? void 0 : opts.spender;
|
|
25031
|
+
// Called through the ports object, never captured off it: a consumer
|
|
25032
|
+
// whose clock is a method on its own object keeps its `this`.
|
|
25033
|
+
this._now = () => { var _a, _b; return (_b = (_a = _ports.now) === null || _a === void 0 ? void 0 : _a.call(_ports)) !== null && _b !== void 0 ? _b : Date.now(); };
|
|
25034
|
+
}
|
|
25035
|
+
/**
|
|
25036
|
+
* Sign the agreement and resume the parked run. A no-op when nothing is
|
|
25037
|
+
* parked, so a double tap cannot sign twice.
|
|
25038
|
+
*/
|
|
25039
|
+
acceptContract() {
|
|
25040
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25041
|
+
const bridge = this._accept;
|
|
25042
|
+
const contract = this._store.state.contract;
|
|
25043
|
+
if (!bridge || !contract || this._isAccepting())
|
|
25044
|
+
return;
|
|
25045
|
+
const token = this._store.generation;
|
|
25046
|
+
this._acceptingFor = token;
|
|
25047
|
+
this._store.patch(Object.assign({ phase: 'accepting-sign' }, badgeFor('confirm', this._store.state.approvalRequired)), token);
|
|
25048
|
+
try {
|
|
25049
|
+
const signature = yield this._ports.signMessage(contract.contractMessage);
|
|
25050
|
+
bridge.settle({ kind: 'accepted', signature });
|
|
25051
|
+
}
|
|
25052
|
+
catch (err) {
|
|
25053
|
+
// A WALLET error, and the only one this flow classifies as a
|
|
25054
|
+
// possible cancellation on the confirm step.
|
|
25055
|
+
bridge.settle({ kind: 'failed', error: err });
|
|
25056
|
+
}
|
|
25057
|
+
finally {
|
|
25058
|
+
// Only if this run still holds it: a `reset()` during the prompt
|
|
25059
|
+
// may have started another one, and that one's flag is its own.
|
|
25060
|
+
if (this._acceptingFor === token)
|
|
25061
|
+
this._acceptingFor = null;
|
|
25062
|
+
}
|
|
25063
|
+
});
|
|
25064
|
+
}
|
|
25065
|
+
/**
|
|
25066
|
+
* Back out of the agreement. The run ends on `declined` — a legitimate
|
|
25067
|
+
* choice, not a failure, and `state.failure` stays null.
|
|
25068
|
+
*
|
|
25069
|
+
* Ignored once `acceptContract()` has opened the wallet: an agreement in
|
|
25070
|
+
* the middle of being signed cannot also be refused. `reset()` is the way
|
|
25071
|
+
* out of a prompt that never answers.
|
|
25072
|
+
*/
|
|
25073
|
+
declineContract() {
|
|
25074
|
+
const bridge = this._accept;
|
|
25075
|
+
if (!bridge || this._isAccepting())
|
|
25076
|
+
return;
|
|
25077
|
+
this._accept = null;
|
|
25078
|
+
bridge.settle({ kind: 'declined' });
|
|
25079
|
+
}
|
|
25080
|
+
/** True only while THIS generation is waiting on the acceptance signature. */
|
|
25081
|
+
_isAccepting() {
|
|
25082
|
+
return (this._acceptingFor !== null &&
|
|
25083
|
+
this._store.isCurrent(this._acceptingFor));
|
|
25084
|
+
}
|
|
25085
|
+
/** `reset()`: unpark the abandoned run and drop its handshake. */
|
|
25086
|
+
_onAbandon() {
|
|
25087
|
+
const bridge = this._accept;
|
|
25088
|
+
this._accept = null;
|
|
25089
|
+
this._acceptingFor = null;
|
|
25090
|
+
bridge === null || bridge === void 0 ? void 0 : bridge.settle({ kind: 'abandoned' });
|
|
25091
|
+
}
|
|
25092
|
+
// -----------------------------------------------------------------------
|
|
25093
|
+
_run(input, token) {
|
|
25094
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25095
|
+
var _a;
|
|
25096
|
+
this._accept = null;
|
|
25097
|
+
this._acceptingFor = null;
|
|
25098
|
+
const ports = this._ports;
|
|
25099
|
+
const owner = input.userAddress.toLowerCase();
|
|
25100
|
+
// 0. The spender, from the input or from the chain config the facade
|
|
25101
|
+
// built this flow with. Without one there is nothing to read an
|
|
25102
|
+
// allowance against and nothing to approve — and guessing would
|
|
25103
|
+
// grant an approval to the wrong contract.
|
|
25104
|
+
const spender = (_a = input.spender) !== null && _a !== void 0 ? _a : this._defaultSpender;
|
|
25105
|
+
if (!spender) {
|
|
25106
|
+
this._fail(token, true, {
|
|
25107
|
+
step: 'generate',
|
|
25108
|
+
reason: 'failed',
|
|
25109
|
+
error: new Error(NO_SPENDER_MESSAGE),
|
|
25110
|
+
});
|
|
25111
|
+
return;
|
|
25112
|
+
}
|
|
25113
|
+
// 1. Allowance pre-check. Decides `approvalRequired` — and therefore
|
|
25114
|
+
// the badge total — before the lender is shown a single step. Read
|
|
25115
|
+
// live, never cached: an exact-amount approval is fully consumed by
|
|
25116
|
+
// the deposit it paid for, so a stale allowance is exactly the value
|
|
25117
|
+
// that would wrongly skip the approve and revert the deposit.
|
|
25118
|
+
let approvalRequired = true;
|
|
25119
|
+
try {
|
|
25120
|
+
const allowance = yield ports.readAllowance(owner, spender);
|
|
25121
|
+
approvalRequired = allowance.lt(input.amount);
|
|
25122
|
+
}
|
|
25123
|
+
catch (_b) {
|
|
25124
|
+
// A read failure is not a reason to skip an approval. Assume one is
|
|
25125
|
+
// needed: the cost is a redundant approve, the alternative is a
|
|
25126
|
+
// reverted deposit.
|
|
25127
|
+
approvalRequired = true;
|
|
25128
|
+
}
|
|
25129
|
+
if (!this._store.isCurrent(token))
|
|
25130
|
+
return;
|
|
25131
|
+
// 2. Generate — sign the auth message.
|
|
25132
|
+
this._store.patch(Object.assign({ approvalRequired, phase: 'generating-sign' }, badgeFor('generate', approvalRequired)), token);
|
|
25133
|
+
const timestamp = this._now();
|
|
25134
|
+
const signedMessage = buildAuthMessage(input, owner, timestamp);
|
|
25135
|
+
let signature;
|
|
25136
|
+
try {
|
|
25137
|
+
signature = yield ports.signMessage(signedMessage);
|
|
25138
|
+
}
|
|
25139
|
+
catch (err) {
|
|
25140
|
+
this._fail(token, approvalRequired, classifyWalletFailure('generate', err));
|
|
25141
|
+
return;
|
|
25142
|
+
}
|
|
25143
|
+
if (!this._store.isCurrent(token))
|
|
25144
|
+
return;
|
|
25145
|
+
// 3. Generate — POST the request.
|
|
25146
|
+
this._store.patch(Object.assign({ phase: 'generating-fetch' }, badgeFor('generate', approvalRequired)), token);
|
|
25147
|
+
let contract;
|
|
25148
|
+
try {
|
|
25149
|
+
contract = yield ports.generateContract(Object.assign({ address: owner, signature,
|
|
25150
|
+
timestamp,
|
|
25151
|
+
signedMessage, poolId: input.poolId, trancheId: input.trancheId, fixedTermConfigId: input.fixedTermConfigId, depositAmount: input.depositAmount }, displayFieldsOf(input.contractMessage)));
|
|
25152
|
+
}
|
|
25153
|
+
catch (err) {
|
|
25154
|
+
// Never a wallet rejection: this step is an HTTP call, and the
|
|
25155
|
+
// lender's wallet was not involved in it. A backend that happened
|
|
25156
|
+
// to echo the words "user rejected" must not be reported to them as
|
|
25157
|
+
// something they did.
|
|
25158
|
+
this._fail(token, approvalRequired, {
|
|
25159
|
+
step: 'generate',
|
|
25160
|
+
reason: 'failed',
|
|
25161
|
+
error: err,
|
|
25162
|
+
});
|
|
25163
|
+
return;
|
|
25164
|
+
}
|
|
25165
|
+
if (!this._store.isCurrent(token))
|
|
25166
|
+
return;
|
|
25167
|
+
// 4. Park on the agreement until the consumer accepts or declines.
|
|
25168
|
+
const outcome = yield new Promise((resolve) => {
|
|
25169
|
+
this._accept = { settle: resolve };
|
|
25170
|
+
this._store.patch(Object.assign({ phase: 'awaiting-accept', contract }, badgeFor('confirm', approvalRequired)), token);
|
|
25171
|
+
});
|
|
25172
|
+
this._accept = null;
|
|
25173
|
+
// An abandoned run lands here too — `reset()` unparks it. The token
|
|
25174
|
+
// check is what tells the two apart.
|
|
25175
|
+
if (!this._store.isCurrent(token))
|
|
25176
|
+
return;
|
|
25177
|
+
if (outcome.kind === 'declined') {
|
|
25178
|
+
this._store.patch(Object.assign({ phase: 'declined' }, badgeFor('confirm', approvalRequired)), token);
|
|
25179
|
+
return;
|
|
25180
|
+
}
|
|
25181
|
+
if (outcome.kind !== 'accepted') {
|
|
25182
|
+
if (outcome.kind === 'failed') {
|
|
25183
|
+
this._fail(token, approvalRequired, classifyWalletFailure('confirm', outcome.error));
|
|
25184
|
+
}
|
|
25185
|
+
return;
|
|
25186
|
+
}
|
|
25187
|
+
// 5. TTL guard. Checked here because this is where the idling happens:
|
|
25188
|
+
// the lender has just spent as long as they wanted reading. An
|
|
25189
|
+
// expired agreement is refused rather than broadcast — the on-chain
|
|
25190
|
+
// call would revert, after a wallet prompt and a gas estimate, with
|
|
25191
|
+
// nothing on screen explaining why.
|
|
25192
|
+
if (this._now() > contract.timestamp + this._ttlMs) {
|
|
25193
|
+
this._fail(token, approvalRequired, {
|
|
25194
|
+
step: 'request',
|
|
25195
|
+
reason: 'contract-expired',
|
|
25196
|
+
});
|
|
25197
|
+
return;
|
|
25198
|
+
}
|
|
25199
|
+
const depositData = encodeDepositData({
|
|
25200
|
+
signature: outcome.signature,
|
|
25201
|
+
timestamp: contract.timestamp,
|
|
25202
|
+
contractVersion: contract.contractVersion,
|
|
25203
|
+
contractType: asContractType(contract.contractType),
|
|
25204
|
+
});
|
|
25205
|
+
// 6. Approve — the EXACT amount, never `MaxUint256`. House rule: an
|
|
25206
|
+
// unlimited allowance outlives the deposit it was granted for, and a
|
|
25207
|
+
// later exploit of the spender would drain a wallet that has long
|
|
25208
|
+
// since stopped lending.
|
|
25209
|
+
if (approvalRequired) {
|
|
25210
|
+
this._store.patch(Object.assign({ phase: 'approve' }, badgeFor('approve', approvalRequired)), token);
|
|
25211
|
+
try {
|
|
25212
|
+
const tx = yield ports.approve(spender, input.amount);
|
|
25213
|
+
yield tx.wait();
|
|
25214
|
+
}
|
|
25215
|
+
catch (err) {
|
|
25216
|
+
this._fail(token, approvalRequired, classifyWalletFailure('approve', err));
|
|
25217
|
+
return;
|
|
25218
|
+
}
|
|
25219
|
+
if (!this._store.isCurrent(token))
|
|
25220
|
+
return;
|
|
25221
|
+
}
|
|
25222
|
+
// 7. Request — KYC signature, then the deposit and its receipt.
|
|
25223
|
+
this._store.patch(Object.assign({ phase: 'request-sign' }, badgeFor('request', approvalRequired)), token);
|
|
25224
|
+
// The two KYC ports reach the consumer's own backend, so they fail the
|
|
25225
|
+
// way the generate step does: `failed`, with the error kept. Running
|
|
25226
|
+
// them through the rejection classifier would let a backend wording —
|
|
25227
|
+
// "Declined", "request rejected" — end a run as "you cancelled in your
|
|
25228
|
+
// wallet", with the real error discarded and nothing to report.
|
|
25229
|
+
let kyc;
|
|
25230
|
+
try {
|
|
25231
|
+
const kycParams = yield ports.buildKycParams(owner);
|
|
25232
|
+
kyc = yield ports.getKycSignature(kycParams);
|
|
25233
|
+
}
|
|
25234
|
+
catch (err) {
|
|
25235
|
+
this._fail(token, approvalRequired, {
|
|
25236
|
+
step: 'request',
|
|
25237
|
+
reason: 'failed',
|
|
25238
|
+
error: err,
|
|
25239
|
+
});
|
|
25240
|
+
return;
|
|
25241
|
+
}
|
|
25242
|
+
if (!this._store.isCurrent(token))
|
|
25243
|
+
return;
|
|
25244
|
+
try {
|
|
25245
|
+
const tx = yield ports.deposit({
|
|
25246
|
+
poolId: input.poolId,
|
|
25247
|
+
trancheId: input.trancheId,
|
|
25248
|
+
amount: input.amount,
|
|
25249
|
+
kycSignature: {
|
|
25250
|
+
blockExpiration: kyc.blockExpiration,
|
|
25251
|
+
signature: kyc.signature,
|
|
25252
|
+
},
|
|
25253
|
+
depositData,
|
|
25254
|
+
fixedTermConfigId: input.fixedTermConfigId,
|
|
25255
|
+
});
|
|
25256
|
+
this._store.patch(Object.assign({ phase: 'request-confirm' }, badgeFor('request', approvalRequired)), token);
|
|
25257
|
+
yield tx.wait();
|
|
25258
|
+
}
|
|
25259
|
+
catch (err) {
|
|
25260
|
+
this._fail(token, approvalRequired, classifyRequest(err));
|
|
25261
|
+
return;
|
|
25262
|
+
}
|
|
25263
|
+
if (!this._store.isCurrent(token))
|
|
25264
|
+
return;
|
|
25265
|
+
this._store.patch(Object.assign({ phase: 'success' }, badgeFor('request', approvalRequired)), token);
|
|
25266
|
+
});
|
|
25267
|
+
}
|
|
25268
|
+
_fail(token, approvalRequired, failure) {
|
|
25269
|
+
this._store.patch(Object.assign({ phase: 'error', failure }, badgeFor(failure.step, approvalRequired)), token);
|
|
25270
|
+
}
|
|
25271
|
+
}
|
|
25272
|
+
// ---------------------------------------------------------------------------
|
|
25273
|
+
// Message building
|
|
25274
|
+
// ---------------------------------------------------------------------------
|
|
25275
|
+
/**
|
|
25276
|
+
* The auth message, in whichever format the consumer asked for. Both builders
|
|
25277
|
+
* are the byte-exact protocol strings from `domain/loan-contract` — the string
|
|
25278
|
+
* signed here and the body posted from `_run` state the SAME timestamp and the
|
|
25279
|
+
* SAME lowercased address, because kasu-backend rebuilds one from the other.
|
|
25280
|
+
*/
|
|
25281
|
+
function buildAuthMessage(input, owner, timestamp) {
|
|
25282
|
+
if (input.contractMessage.format === 'legacy') {
|
|
25283
|
+
return buildLegacyContractRequestMessage(owner, timestamp);
|
|
25284
|
+
}
|
|
25285
|
+
const { strategyName, region, optionName, amountLabel } = input.contractMessage;
|
|
25286
|
+
return buildLoanAgreementSignMessage({
|
|
25287
|
+
strategyName,
|
|
25288
|
+
region,
|
|
25289
|
+
optionName,
|
|
25290
|
+
amountLabel,
|
|
25291
|
+
timestamp,
|
|
25292
|
+
});
|
|
25293
|
+
}
|
|
25294
|
+
/**
|
|
25295
|
+
* The four display fields, present only on the new format. The backend picks
|
|
25296
|
+
* its verification path on their presence: all four → the human-readable
|
|
25297
|
+
* format, any missing → the legacy string.
|
|
25298
|
+
*/
|
|
25299
|
+
function displayFieldsOf(message) {
|
|
25300
|
+
if (message.format === 'legacy')
|
|
25301
|
+
return {};
|
|
25302
|
+
return {
|
|
25303
|
+
strategyName: message.strategyName,
|
|
25304
|
+
region: message.region,
|
|
25305
|
+
optionName: message.optionName,
|
|
25306
|
+
amountLabel: message.amountLabel,
|
|
25307
|
+
};
|
|
25308
|
+
}
|
|
25309
|
+
|
|
25310
|
+
const INITIAL = {
|
|
25311
|
+
phase: 'idle',
|
|
25312
|
+
step: null,
|
|
25313
|
+
isMax: false,
|
|
25314
|
+
failure: null,
|
|
25315
|
+
};
|
|
25316
|
+
/** What a KYC pre-check with no `buildKycParams` to call fails with. */
|
|
25317
|
+
const NO_KYC_PARAMS_MESSAGE = 'WithdrawFlow: getKycSignature was supplied without buildKycParams; build the flow with kasu.flows.withdraw() or pass both';
|
|
25318
|
+
// ---------------------------------------------------------------------------
|
|
25319
|
+
// The flow
|
|
25320
|
+
// ---------------------------------------------------------------------------
|
|
25321
|
+
class WithdrawFlow extends Flow {
|
|
25322
|
+
constructor(_ports) {
|
|
25323
|
+
super(INITIAL);
|
|
25324
|
+
this._ports = _ports;
|
|
25325
|
+
}
|
|
25326
|
+
_run(input, token) {
|
|
25327
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25328
|
+
const ports = this._ports;
|
|
25329
|
+
const isMax = input.amount === 'max';
|
|
25330
|
+
// 1. The optional KYC pre-check: build the params, exchange them for a
|
|
25331
|
+
// signature. Both ports reach the application's own backend, so a
|
|
25332
|
+
// throw here is ALWAYS `failed` — running it through the wallet
|
|
25333
|
+
// rejection classifier would let a backend's "Declined" be reported
|
|
25334
|
+
// to a lender as something they did, with the real error dropped.
|
|
25335
|
+
if (ports.getKycSignature) {
|
|
25336
|
+
this._store.patch({ phase: 'checking-kyc', step: 'kyc', isMax }, token);
|
|
25337
|
+
try {
|
|
25338
|
+
if (!ports.buildKycParams) {
|
|
25339
|
+
throw new Error(NO_KYC_PARAMS_MESSAGE);
|
|
25340
|
+
}
|
|
25341
|
+
const params = yield ports.buildKycParams(input.userAddress);
|
|
25342
|
+
yield ports.getKycSignature(params);
|
|
25343
|
+
}
|
|
25344
|
+
catch (err) {
|
|
25345
|
+
this._store.patch({
|
|
25346
|
+
phase: 'error',
|
|
25347
|
+
failure: { step: 'kyc', reason: 'failed', error: err },
|
|
25348
|
+
}, token);
|
|
25349
|
+
return;
|
|
25350
|
+
}
|
|
25351
|
+
if (!this._store.isCurrent(token))
|
|
25352
|
+
return;
|
|
25353
|
+
}
|
|
25354
|
+
// 2. The submission. This one IS a wallet call, so it keeps the
|
|
25355
|
+
// `cancelled` / `failed` split.
|
|
25356
|
+
this._store.patch({ phase: 'request-sign', step: 'request', isMax }, token);
|
|
25357
|
+
try {
|
|
25358
|
+
const tx = input.amount === 'max'
|
|
25359
|
+
? yield ports.withdrawMax(input.poolId, input.trancheId, input.userAddress.toLowerCase())
|
|
25360
|
+
: yield ports.withdraw({
|
|
25361
|
+
poolId: input.poolId,
|
|
25362
|
+
trancheId: input.trancheId,
|
|
25363
|
+
amount: input.amount,
|
|
25364
|
+
});
|
|
25365
|
+
this._store.patch({ phase: 'request-confirm' }, token);
|
|
25366
|
+
yield tx.wait();
|
|
25367
|
+
}
|
|
25368
|
+
catch (err) {
|
|
25369
|
+
this._store.patch({
|
|
25370
|
+
phase: 'error',
|
|
25371
|
+
failure: classifyWalletFailure('request', err),
|
|
25372
|
+
}, token);
|
|
25373
|
+
return;
|
|
25374
|
+
}
|
|
25375
|
+
if (!this._store.isCurrent(token))
|
|
25376
|
+
return;
|
|
25377
|
+
this._store.patch({ phase: 'success' }, token);
|
|
25378
|
+
});
|
|
25379
|
+
}
|
|
25380
|
+
}
|
|
25381
|
+
|
|
25382
|
+
/**
|
|
25383
|
+
* Builds `DepositFlow` / `WithdrawFlow` instances wired to THIS Kasu instance:
|
|
25384
|
+
* its chain's stable token, its `LendingPoolManager`, its signer.
|
|
25385
|
+
*
|
|
25386
|
+
* ```ts
|
|
25387
|
+
* const flow = kasu.connect(signer).flows.deposit({
|
|
25388
|
+
* signMessage: (m) => signer.signMessage(m),
|
|
25389
|
+
* generateContract: (req) => postToMyProxy(req),
|
|
25390
|
+
* getKycSignature: (p) => postToMyBackend(p),
|
|
25391
|
+
* });
|
|
25392
|
+
* ```
|
|
25393
|
+
*
|
|
25394
|
+
* A flow built from a read-only instance constructs fine and reads fine — the
|
|
25395
|
+
* write ports throw `READ_ONLY_MESSAGE` when the run reaches them, exactly as
|
|
25396
|
+
* `kasu.deposits.deposit` does. Constructing is not the mistake; submitting is.
|
|
25397
|
+
*
|
|
25398
|
+
* It holds the `DepositsFacade` and NOTHING the facade already owns: no
|
|
25399
|
+
* `UserLending`, no chain id, no signer. Two paths to one behaviour is how the
|
|
25400
|
+
* KYC params a flow built came to differ from the ones `kasu.deposits`
|
|
25401
|
+
* built — the same class of drift the flows themselves exist to end.
|
|
25402
|
+
*/
|
|
25403
|
+
class FlowsFacade {
|
|
25404
|
+
constructor(_deposits,
|
|
25405
|
+
/**
|
|
25406
|
+
* The chain's stable token, bound to whatever the Kasu instance holds.
|
|
25407
|
+
* A factory rather than a contract: it is one `new Contract`, and a
|
|
25408
|
+
* cached binding would outlive the config it was built from.
|
|
24413
25409
|
*/
|
|
24414
|
-
|
|
24415
|
-
|
|
24416
|
-
|
|
24417
|
-
return yield this._userLending.requestWithdrawalInAsset(params.poolId, params.trancheId, params.amount);
|
|
24418
|
-
});
|
|
24419
|
-
}
|
|
25410
|
+
_erc20,
|
|
25411
|
+
/** The read-only flag `Kasu` already computed — never re-derived here. */
|
|
25412
|
+
_isReadOnly,
|
|
24420
25413
|
/**
|
|
24421
|
-
*
|
|
25414
|
+
* The ERC-20 spender every deposit run approves: this chain's
|
|
25415
|
+
* `LendingPoolManager`, which is the only contract the default deposit
|
|
25416
|
+
* port calls. Passed to the flow so a consumer never has to hand-wire
|
|
25417
|
+
* an address whose only wrong value grants an approval to the wrong
|
|
25418
|
+
* contract.
|
|
24422
25419
|
*/
|
|
24423
|
-
|
|
24424
|
-
|
|
24425
|
-
|
|
24426
|
-
|
|
24427
|
-
|
|
25420
|
+
_spender) {
|
|
25421
|
+
this._deposits = _deposits;
|
|
25422
|
+
this._erc20 = _erc20;
|
|
25423
|
+
this._isReadOnly = _isReadOnly;
|
|
25424
|
+
this._spender = _spender;
|
|
24428
25425
|
}
|
|
24429
25426
|
/**
|
|
24430
|
-
*
|
|
25427
|
+
* A deposit pipeline. `readAllowance`, `approve`, `deposit` and
|
|
25428
|
+
* `buildKycParams` default to the SDK's own implementations; pass any of
|
|
25429
|
+
* them to override (kasu-ui approves through its sponsored-gas path, for
|
|
25430
|
+
* one).
|
|
24431
25431
|
*
|
|
24432
|
-
*
|
|
24433
|
-
*
|
|
24434
|
-
|
|
24435
|
-
|
|
24436
|
-
|
|
24437
|
-
}
|
|
24438
|
-
/**
|
|
24439
|
-
* Check whether the pool is currently in a clearing period
|
|
24440
|
-
* (deposits/withdrawals are temporarily paused).
|
|
25432
|
+
* Each default is applied per key with `??`, not by spreading `ports` over
|
|
25433
|
+
* them: `{ ...defaults, ...ports }` lets an EXPLICITLY undefined value
|
|
25434
|
+
* delete the default it was meant to keep, and
|
|
25435
|
+
* `approve: sponsoredGas ? sponsoredApprove : undefined` is exactly how a
|
|
25436
|
+
* consumer writes a conditional override.
|
|
24441
25437
|
*/
|
|
24442
|
-
|
|
24443
|
-
|
|
24444
|
-
|
|
25438
|
+
deposit(ports, opts) {
|
|
25439
|
+
var _a, _b, _c, _d, _e;
|
|
25440
|
+
return new DepositFlow({
|
|
25441
|
+
signMessage: ports.signMessage,
|
|
25442
|
+
generateContract: ports.generateContract,
|
|
25443
|
+
getKycSignature: ports.getKycSignature,
|
|
25444
|
+
buildKycParams: (_a = ports.buildKycParams) !== null && _a !== void 0 ? _a : ((userAddress) => this._deposits.buildKycParams(userAddress)),
|
|
25445
|
+
readAllowance: (_b = ports.readAllowance) !== null && _b !== void 0 ? _b : ((owner, spender) => this._erc20().allowance(owner, spender)),
|
|
25446
|
+
approve: (_c = ports.approve) !== null && _c !== void 0 ? _c : ((spender, amount) => __awaiter(this, void 0, void 0, function* () {
|
|
25447
|
+
this._assertWritable();
|
|
25448
|
+
// The EXACT amount the flow asked for. Nothing here
|
|
25449
|
+
// rounds it up, and nothing here substitutes
|
|
25450
|
+
// MaxUint256.
|
|
25451
|
+
return yield this._erc20().approve(spender, amount);
|
|
25452
|
+
})),
|
|
25453
|
+
deposit: (_d = ports.deposit) !== null && _d !== void 0 ? _d : ((params) => this._deposits.deposit(params)),
|
|
25454
|
+
now: ports.now,
|
|
25455
|
+
}, {
|
|
25456
|
+
contractTtlMs: opts === null || opts === void 0 ? void 0 : opts.contractTtlMs,
|
|
25457
|
+
// Per key here too, for the same reason the ports are: an
|
|
25458
|
+
// explicit `spender: undefined` must not delete the default.
|
|
25459
|
+
spender: (_e = opts === null || opts === void 0 ? void 0 : opts.spender) !== null && _e !== void 0 ? _e : this._spender,
|
|
24445
25460
|
});
|
|
24446
25461
|
}
|
|
24447
25462
|
/**
|
|
24448
|
-
*
|
|
25463
|
+
* A withdrawal pipeline. Both write ports and `buildKycParams` default to
|
|
25464
|
+
* this instance; supplying `getKycSignature` turns the KYC pre-check on.
|
|
25465
|
+
* Defaults are applied per key, for the reason `deposit()` gives.
|
|
24449
25466
|
*/
|
|
24450
|
-
|
|
24451
|
-
|
|
24452
|
-
|
|
25467
|
+
withdraw(ports = {}) {
|
|
25468
|
+
var _a, _b, _c;
|
|
25469
|
+
return new WithdrawFlow({
|
|
25470
|
+
buildKycParams: (_a = ports.buildKycParams) !== null && _a !== void 0 ? _a : ((userAddress) => this._deposits.buildKycParams(userAddress)),
|
|
25471
|
+
getKycSignature: ports.getKycSignature,
|
|
25472
|
+
withdraw: (_b = ports.withdraw) !== null && _b !== void 0 ? _b : ((params) => this._deposits.withdraw({
|
|
25473
|
+
poolId: params.poolId,
|
|
25474
|
+
trancheId: params.trancheId,
|
|
25475
|
+
amount: params.amount,
|
|
25476
|
+
})),
|
|
25477
|
+
withdrawMax: (_c = ports.withdrawMax) !== null && _c !== void 0 ? _c : ((poolId, trancheId, userAddress) => this._deposits.withdrawMax(poolId, trancheId, userAddress)),
|
|
24453
25478
|
});
|
|
24454
25479
|
}
|
|
25480
|
+
_assertWritable() {
|
|
25481
|
+
if (this._isReadOnly) {
|
|
25482
|
+
throw new Error(READ_ONLY_MESSAGE);
|
|
25483
|
+
}
|
|
25484
|
+
}
|
|
24455
25485
|
}
|
|
24456
25486
|
|
|
24457
25487
|
/**
|
|
@@ -24910,6 +25940,16 @@ function firstSubmissionTimestamp(events, fallback) {
|
|
|
24910
25940
|
return fallback;
|
|
24911
25941
|
return submissions.reduce((min, e) => (e.timestamp < min ? e.timestamp : min), Infinity);
|
|
24912
25942
|
}
|
|
25943
|
+
/**
|
|
25944
|
+
* The LATEST event timestamp, or `fallback` when the timeline is empty — or
|
|
25945
|
+
* when every event predates it, because `fallback` seeds the reduction. That
|
|
25946
|
+
* seeding is deliberate and is kasu-ui's behaviour: `request.timestamp` is a
|
|
25947
|
+
* fact about the request, and an event indexed with an earlier clock must not
|
|
25948
|
+
* make the row look older than the request itself.
|
|
25949
|
+
*/
|
|
25950
|
+
function lastEventTimestamp(events, fallback) {
|
|
25951
|
+
return events.reduce((max, e) => (e.timestamp > max ? e.timestamp : max), fallback);
|
|
25952
|
+
}
|
|
24913
25953
|
/**
|
|
24914
25954
|
* Has THIS request's cycle closed? — the single open/closed signal behind the
|
|
24915
25955
|
* status vocabulary and behind Cancel.
|
|
@@ -24993,10 +26033,10 @@ const numOrNull = (str) => {
|
|
|
24993
26033
|
* status would strand it.
|
|
24994
26034
|
*
|
|
24995
26035
|
* WHAT THE APPLICATION STILL OWNS: the status word and the detail line beneath
|
|
24996
|
-
* it; the tranche display rename (`getTrancheDisplayName` on `trancheName
|
|
24997
|
-
* and on
|
|
24998
|
-
* split; the amount format. The "view loan agreement" affordance is
|
|
24999
|
-
* it follows from two fields already here —
|
|
26036
|
+
* it; the tranche display rename (`getTrancheDisplayName` on `trancheName`
|
|
26037
|
+
* and on `reallocationTargetTrancheName`, both of which are RAW here); the
|
|
26038
|
+
* pool-name split; the amount format. The "view loan agreement" affordance is
|
|
26039
|
+
* a fact, and it follows from two fields already here —
|
|
25000
26040
|
* `requestType === 'Deposit' && statusCode !== 'cancelled' && statusCode !== 'rejected'`
|
|
25001
26041
|
* — because neither a cancelled nor a fully-rejected deposit ever issued one,
|
|
25002
26042
|
* and withdrawals sign no agreement at all.
|
|
@@ -25031,6 +26071,12 @@ function deriveRequestState(request) {
|
|
|
25031
26071
|
initiatedAmount: initiated
|
|
25032
26072
|
? initiatedAmountOf(request.events)
|
|
25033
26073
|
: null,
|
|
26074
|
+
rejectedAmount: rejected,
|
|
26075
|
+
reallocatedOutAmount: reallocation ? num(reallocation.assetAmount) : 0,
|
|
26076
|
+
reallocationTargetTrancheName: reallocation
|
|
26077
|
+
? reallocation.trancheName
|
|
26078
|
+
: null,
|
|
26079
|
+
lastTimestamp: lastEventTimestamp(request.events, request.timestamp),
|
|
25034
26080
|
submissionCount: countSubmissions(request.events),
|
|
25035
26081
|
// `firstSubmissionTimestamp` needs a fallback it will never use here:
|
|
25036
26082
|
// the bundle is non-empty on every path that reaches the call.
|
|
@@ -25150,8 +26196,9 @@ class PortfolioFacade {
|
|
|
25150
26196
|
/**
|
|
25151
26197
|
* High-level entry point for external integrators.
|
|
25152
26198
|
*
|
|
25153
|
-
* Provides
|
|
25154
|
-
* exposes the underlying `KasuSdk` services via `.services` for
|
|
26199
|
+
* Provides four domain facades — `strategies`, `deposits`, `portfolio`,
|
|
26200
|
+
* `flows` — and exposes the underlying `KasuSdk` services via `.services` for
|
|
26201
|
+
* power-users.
|
|
25155
26202
|
*
|
|
25156
26203
|
* ```ts
|
|
25157
26204
|
* import { Kasu } from '@kasufinance/kasu-sdk';
|
|
@@ -25175,8 +26222,13 @@ class Kasu {
|
|
|
25175
26222
|
this._signerOrProvider = signerOrProvider;
|
|
25176
26223
|
this._configOverrides = configOverrides;
|
|
25177
26224
|
this.strategies = new StrategiesFacade(sdk.DataService, sdk.UserLending);
|
|
25178
|
-
|
|
26225
|
+
// Derived ONCE and handed to every facade that needs it. Each of them
|
|
26226
|
+
// re-deriving `Signer.isSigner` is how two facades come to disagree
|
|
26227
|
+
// about whether the same instance can write.
|
|
26228
|
+
const isReadOnly = !ethers.Signer.isSigner(signerOrProvider);
|
|
26229
|
+
this.deposits = new DepositsFacade(sdk.UserLending, chainConfig.chainId.toString(), isReadOnly);
|
|
25179
26230
|
this.portfolio = new PortfolioFacade(sdk.DataService, sdk.UserLending, sdk.Portfolio);
|
|
26231
|
+
this.flows = new FlowsFacade(this.deposits, () => erc20Of(chainConfig, signerOrProvider), isReadOnly, chainConfig.contracts.LendingPoolManager);
|
|
25180
26232
|
}
|
|
25181
26233
|
/**
|
|
25182
26234
|
* Create a Kasu instance with built-in chain config.
|
|
@@ -25308,6 +26360,23 @@ function rpcUrlsOf(chainConfig) {
|
|
|
25308
26360
|
var _a;
|
|
25309
26361
|
return (_a = chainConfig.rpcUrls) !== null && _a !== void 0 ? _a : [];
|
|
25310
26362
|
}
|
|
26363
|
+
/**
|
|
26364
|
+
* The chain's stable token, bound to whatever this instance holds.
|
|
26365
|
+
*
|
|
26366
|
+
* Built per call rather than cached: it is one `new Contract`, and a cached
|
|
26367
|
+
* binding would outlive the signer a `connect()` replaced. A config with no
|
|
26368
|
+
* `stableAsset` (a hand-written entry from before 2.5.0) throws here rather
|
|
26369
|
+
* than inside ethers, naming the two ports that make the flow work without
|
|
26370
|
+
* one.
|
|
26371
|
+
*/
|
|
26372
|
+
function erc20Of(chainConfig, signerOrProvider) {
|
|
26373
|
+
var _a;
|
|
26374
|
+
const address = (_a = stableAssetOf(chainConfig)) === null || _a === void 0 ? void 0 : _a.address;
|
|
26375
|
+
if (!address) {
|
|
26376
|
+
throw new Error('Kasu: this chain config has no stableAsset; pass readAllowance and approve ports explicitly');
|
|
26377
|
+
}
|
|
26378
|
+
return IERC20MetadataAbi__factory.connect(address, signerOrProvider);
|
|
26379
|
+
}
|
|
25311
26380
|
/**
|
|
25312
26381
|
* The read-only provider used when the caller passes no `signerOrProvider`.
|
|
25313
26382
|
*
|
|
@@ -25674,165 +26743,6 @@ function getInstitutionalLender(originator) {
|
|
|
25674
26743
|
return originator.name === APXIUM.name ? RIXON_CAPITAL : null;
|
|
25675
26744
|
}
|
|
25676
26745
|
|
|
25677
|
-
/** Narrow the backend's loose `contractType` string to the encoded union. */
|
|
25678
|
-
function asContractType(raw) {
|
|
25679
|
-
return raw === 'exempt' ? 'exempt' : 'retail';
|
|
25680
|
-
}
|
|
25681
|
-
/**
|
|
25682
|
-
* Parse the server's JSON-string `formattedMessage` into a tree. Returns
|
|
25683
|
-
* `null` on parse failure so a renderer can fall back to the plaintext.
|
|
25684
|
-
*/
|
|
25685
|
-
function parseFormattedMessage(raw) {
|
|
25686
|
-
try {
|
|
25687
|
-
const parsed = JSON.parse(raw);
|
|
25688
|
-
if (parsed && typeof parsed === 'object')
|
|
25689
|
-
return parsed;
|
|
25690
|
-
return null;
|
|
25691
|
-
}
|
|
25692
|
-
catch (_a) {
|
|
25693
|
-
return null;
|
|
25694
|
-
}
|
|
25695
|
-
}
|
|
25696
|
-
// ---------------------------------------------------------------------------
|
|
25697
|
-
// The on-chain `depositData` blob
|
|
25698
|
-
// ---------------------------------------------------------------------------
|
|
25699
|
-
/**
|
|
25700
|
-
* Pack the contract version and type into the `versionType` word.
|
|
25701
|
-
*
|
|
25702
|
-
* ```
|
|
25703
|
-
* high byte = contract version (>= 1)
|
|
25704
|
-
* low byte = 0 for retail, 1 for exempt
|
|
25705
|
-
* ```
|
|
25706
|
-
*/
|
|
25707
|
-
function buildContractVersionType(contractVersion, contractType) {
|
|
25708
|
-
return (contractVersion << 8) + (contractType === 'retail' ? 0 : 1);
|
|
25709
|
-
}
|
|
25710
|
-
/**
|
|
25711
|
-
* Build the on-chain `depositData` blob that `requestDepositWithKyc` expects.
|
|
25712
|
-
*
|
|
25713
|
-
* The KasuController decodes the bytes as
|
|
25714
|
-
* `(bytes signature, uint256 timestamp, uint256 versionType)` and uses the
|
|
25715
|
-
* embedded acceptance signature to verify — retrospectively, via the
|
|
25716
|
-
* agreements service `/contract/resolve` — that the lender signed the
|
|
25717
|
-
* loan-contract text. The ABI tuple and the packing are consensus-critical:
|
|
25718
|
-
* these bytes go on chain.
|
|
25719
|
-
*
|
|
25720
|
-
* kasu-ui encodes this with viem, kasu-mobile with ethers v5 (viem is not
|
|
25721
|
-
* available on Expo). This is the ethers v5 implementation, and
|
|
25722
|
-
* `loan-contract.test.ts` pins its output byte-for-byte against fixtures
|
|
25723
|
-
* produced by the viem version, so the two apps can never diverge here.
|
|
25724
|
-
*
|
|
25725
|
-
* @param args.signature EIP-191 signature from the lender accepting
|
|
25726
|
-
* `contractMessage`, as a 0x-prefixed hex string.
|
|
25727
|
-
* @param args.timestamp ms-epoch from the contract response.
|
|
25728
|
-
*/
|
|
25729
|
-
function encodeDepositData(args) {
|
|
25730
|
-
const versionType = buildContractVersionType(args.contractVersion, args.contractType);
|
|
25731
|
-
return ethers.ethers.utils.defaultAbiCoder.encode(['bytes', 'uint256', 'uint256'], [
|
|
25732
|
-
args.signature,
|
|
25733
|
-
ethers.ethers.BigNumber.from(args.timestamp),
|
|
25734
|
-
ethers.ethers.BigNumber.from(versionType),
|
|
25735
|
-
]);
|
|
25736
|
-
}
|
|
25737
|
-
// ---------------------------------------------------------------------------
|
|
25738
|
-
// The signed messages
|
|
25739
|
-
// ---------------------------------------------------------------------------
|
|
25740
|
-
const MONTH_NAMES = [
|
|
25741
|
-
'January',
|
|
25742
|
-
'February',
|
|
25743
|
-
'March',
|
|
25744
|
-
'April',
|
|
25745
|
-
'May',
|
|
25746
|
-
'June',
|
|
25747
|
-
'July',
|
|
25748
|
-
'August',
|
|
25749
|
-
'September',
|
|
25750
|
-
'October',
|
|
25751
|
-
'November',
|
|
25752
|
-
'December',
|
|
25753
|
-
];
|
|
25754
|
-
/**
|
|
25755
|
-
* Format a unix timestamp as `{day} {MonthName} {yyyy}, {HH}:{mm}` in UTC.
|
|
25756
|
-
* Day is non-padded; hour and minute are zero-padded to two digits (24h). A
|
|
25757
|
-
* timestamp with >= 13 digits is treated as milliseconds, otherwise as seconds
|
|
25758
|
-
* — the same auto-detection kasu-backend applies.
|
|
25759
|
-
*
|
|
25760
|
-
* Deliberately a manual formatter with English month names: no locale, no
|
|
25761
|
-
* `Intl`, so the output is byte-identical across runtimes and time zones. This
|
|
25762
|
-
* is not a display date. It goes inside a signed message.
|
|
25763
|
-
*
|
|
25764
|
-
* e.g. 1785313320000 → `"29 July 2026, 08:22"`
|
|
25765
|
-
*/
|
|
25766
|
-
function formatSignTimestampUtc(timestamp) {
|
|
25767
|
-
const ms = timestamp.toString().length >= 13 ? timestamp : timestamp * 1000;
|
|
25768
|
-
const date = new Date(ms);
|
|
25769
|
-
const day = date.getUTCDate();
|
|
25770
|
-
const month = MONTH_NAMES[date.getUTCMonth()];
|
|
25771
|
-
const year = date.getUTCFullYear();
|
|
25772
|
-
const hours = String(date.getUTCHours()).padStart(2, '0');
|
|
25773
|
-
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
|
25774
|
-
return `${day} ${month} ${year}, ${hours}:${minutes}`;
|
|
25775
|
-
}
|
|
25776
|
-
/**
|
|
25777
|
-
* The 4-line human-readable message a lender signs to generate their loan
|
|
25778
|
-
* agreement for review — `POST /contract/generate`.
|
|
25779
|
-
*
|
|
25780
|
-
* ⚠️ BYTE-EXACT PROTOCOL STRING. kasu-backend rebuilds this string from the
|
|
25781
|
-
* request body and verifies the signature against it, so the wording,
|
|
25782
|
-
* ordering, separators, line breaks and date format are all part of the wire
|
|
25783
|
-
* contract. The separator between the line-2 fields is a MIDDLE DOT U+00B7
|
|
25784
|
-
* (·) with a single space on each side; the four lines are joined with `\n`.
|
|
25785
|
-
*
|
|
25786
|
-
* The backend takes this format only when all four display fields are present
|
|
25787
|
-
* and non-empty, and it cross-checks `amountLabel`'s leading number against
|
|
25788
|
-
* the `depositAmount` it was sent (thousands separators stripped) — a message
|
|
25789
|
-
* that states an amount other than the one being executed is refused.
|
|
25790
|
-
*/
|
|
25791
|
-
function buildLoanAgreementSignMessage(p) {
|
|
25792
|
-
return [
|
|
25793
|
-
'Generate my Loan Agreement for review:',
|
|
25794
|
-
`${p.strategyName} · ${p.region} · ${p.optionName} · ${p.amountLabel}.`,
|
|
25795
|
-
`Request made ${formatSignTimestampUtc(p.timestamp)} UTC.`,
|
|
25796
|
-
'This request does not commit me to lend.',
|
|
25797
|
-
].join('\n');
|
|
25798
|
-
}
|
|
25799
|
-
/**
|
|
25800
|
-
* The legacy `/contract/generate` and `/contract/resolve` message.
|
|
25801
|
-
*
|
|
25802
|
-
* ⚠️ BYTE-EXACT PROTOCOL STRING. kasu-backend rebuilds it as
|
|
25803
|
-
* `` `I request contract content for ${address} at ${timestamp}.` `` from the
|
|
25804
|
-
* `address` and `timestamp` fields of the request body — so the string signed
|
|
25805
|
-
* and the body sent must agree exactly, INCLUDING the address casing. This
|
|
25806
|
-
* builder lowercases, and the request body must carry the same lowercased
|
|
25807
|
-
* address; that is what both apps signing this format do today.
|
|
25808
|
-
*
|
|
25809
|
-
* The backend takes this path whenever the four human-readable display fields
|
|
25810
|
-
* are absent, and documents it as permanent until the legacy app is
|
|
25811
|
-
* decommissioned. `/contract/resolve` has no other format — every consumer
|
|
25812
|
-
* signs this one to retrieve an existing agreement.
|
|
25813
|
-
*
|
|
25814
|
-
* @param timestampMs ms-epoch, and the same value sent as the body's
|
|
25815
|
-
* `timestamp`.
|
|
25816
|
-
*/
|
|
25817
|
-
function buildLegacyContractRequestMessage(address, timestampMs) {
|
|
25818
|
-
return `I request contract content for ${address.toLowerCase()} at ${timestampMs}.`;
|
|
25819
|
-
}
|
|
25820
|
-
/**
|
|
25821
|
-
* The `POST /contract/fullname` message.
|
|
25822
|
-
*
|
|
25823
|
-
* ⚠️ BYTE-EXACT PROTOCOL STRING, on the same terms as
|
|
25824
|
-
* `buildLegacyContractRequestMessage`: kasu-backend rebuilds
|
|
25825
|
-
* `` `I request my full name for ${address} at ${timestamp}.` `` from the
|
|
25826
|
-
* request body and verifies the signature against it, so the body must carry
|
|
25827
|
-
* the same lowercased address this builder signs.
|
|
25828
|
-
*
|
|
25829
|
-
* @param timestampMs ms-epoch, and the same value sent as the body's
|
|
25830
|
-
* `timestamp`.
|
|
25831
|
-
*/
|
|
25832
|
-
function buildFullNameRequestMessage(address, timestampMs) {
|
|
25833
|
-
return `I request my full name for ${address.toLowerCase()} at ${timestampMs}.`;
|
|
25834
|
-
}
|
|
25835
|
-
|
|
25836
26746
|
/**
|
|
25837
26747
|
* Cycles and the clearing window — the protocol's weekly clock, as numbers.
|
|
25838
26748
|
*
|
|
@@ -25979,69 +26889,18 @@ function getTrancheDisplayName(trancheName, pool) {
|
|
|
25979
26889
|
: trancheName;
|
|
25980
26890
|
}
|
|
25981
26891
|
|
|
25982
|
-
/**
|
|
25983
|
-
* Wallet and RPC error predicates — pure functions over `unknown`.
|
|
25984
|
-
*
|
|
25985
|
-
* Every consumer has to tell three things apart when a write fails: the lender
|
|
25986
|
-
* changed their mind, the call would revert, and everything else. The first
|
|
25987
|
-
* two must never be reported as a failure the lender should retry or contact
|
|
25988
|
-
* support about, and each wallet spells them differently, so the shapes are
|
|
25989
|
-
* enumerated once here.
|
|
25990
|
-
*
|
|
25991
|
-
* Lifted from kasu-ui's `src/lib/web3/is-user-rejected.ts` and kasu-mobile's
|
|
25992
|
-
* `src/features/lending/lib/errors.ts`.
|
|
25993
|
-
*/
|
|
25994
|
-
/**
|
|
25995
|
-
* Did the lender reject the request in their wallet?
|
|
25996
|
-
*
|
|
25997
|
-
* Providers surface a rejection in different shapes:
|
|
25998
|
-
* - MetaMask and most EIP-1193 wallets: `code: 4001`
|
|
25999
|
-
* - WalletConnect and some Privy paths: `Error('User rejected the request')`
|
|
26000
|
-
* - ethers v5 wraps it as `ACTION_REJECTED`
|
|
26001
|
-
* - Coinbase Wallet sometimes: `code: 'ACTION_REJECTED'` as a string
|
|
26002
|
-
*
|
|
26003
|
-
* This is kasu-ui's implementation verbatim. kasu-mobile's copy additionally
|
|
26004
|
-
* reads a nested `error.code` / `error.message`, an ethers `reason`, and the
|
|
26005
|
-
* words "request rejected" / "declined"; a consumer that needs those shapes
|
|
26006
|
-
* should keep its own check on top rather than assume they are covered here.
|
|
26007
|
-
*/
|
|
26008
|
-
function isUserRejected(err) {
|
|
26009
|
-
if (!err)
|
|
26010
|
-
return false;
|
|
26011
|
-
if (typeof err === 'object') {
|
|
26012
|
-
const code = err.code;
|
|
26013
|
-
if (code === 4001 || code === 'ACTION_REJECTED')
|
|
26014
|
-
return true;
|
|
26015
|
-
}
|
|
26016
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
26017
|
-
const lower = msg.toLowerCase();
|
|
26018
|
-
return (lower.includes('user rejected') ||
|
|
26019
|
-
lower.includes('user denied') ||
|
|
26020
|
-
lower.includes('rejected the request') ||
|
|
26021
|
-
lower.includes('action_rejected'));
|
|
26022
|
-
}
|
|
26023
|
-
/**
|
|
26024
|
-
* Did the wallet or RPC signal that the on-chain call would revert?
|
|
26025
|
-
*
|
|
26026
|
-
* ethers v5 raises `UNPREDICTABLE_GAS_LIMIT` when gas estimation reverts —
|
|
26027
|
-
* most often an underlying `transferFrom` failing on an insufficient balance
|
|
26028
|
-
* or allowance. Distinct from a rejection: nothing was refused by the lender,
|
|
26029
|
-
* the transaction simply cannot succeed as composed, so the caller should
|
|
26030
|
-
* re-check its preconditions rather than invite a retry.
|
|
26031
|
-
*/
|
|
26032
|
-
function isUnpredictableGas(err) {
|
|
26033
|
-
if (!err || typeof err !== 'object')
|
|
26034
|
-
return false;
|
|
26035
|
-
return err.code === 'UNPREDICTABLE_GAS_LIMIT';
|
|
26036
|
-
}
|
|
26037
|
-
|
|
26038
26892
|
exports.APXIUM = APXIUM;
|
|
26039
26893
|
exports.AU_ALPHA3 = AU_ALPHA3;
|
|
26040
26894
|
exports.AU_MIN_CUMULATIVE_BY_STABLE = AU_MIN_CUMULATIVE_BY_STABLE;
|
|
26041
26895
|
exports.CHAIN_CONFIGS = CHAIN_CONFIGS;
|
|
26042
26896
|
exports.CLEARING_WINDOW_SECONDS = CLEARING_WINDOW_SECONDS;
|
|
26897
|
+
exports.CONTRACT_TTL_MS = CONTRACT_TTL_MS;
|
|
26898
|
+
exports.DepositFlow = DepositFlow;
|
|
26043
26899
|
exports.DepositsFacade = DepositsFacade;
|
|
26044
26900
|
exports.EPOCHS_IN_YEAR = EPOCHS_IN_YEAR;
|
|
26901
|
+
exports.Flow = Flow;
|
|
26902
|
+
exports.FlowStore = FlowStore;
|
|
26903
|
+
exports.FlowsFacade = FlowsFacade;
|
|
26045
26904
|
exports.INVOICEMATE = INVOICEMATE;
|
|
26046
26905
|
exports.Kasu = Kasu;
|
|
26047
26906
|
exports.KasuSdk = KasuSdk;
|
|
@@ -26049,11 +26908,15 @@ exports.MAX_LENDING_AMOUNT_FALLBACK = MAX_LENDING_AMOUNT_FALLBACK;
|
|
|
26049
26908
|
exports.MIN_LENDING_AMOUNT_FALLBACK = MIN_LENDING_AMOUNT_FALLBACK;
|
|
26050
26909
|
exports.MIN_TRANCHE_CAPACITY = MIN_TRANCHE_CAPACITY;
|
|
26051
26910
|
exports.NO_DIRECTUS_URL_MESSAGE = NO_DIRECTUS_URL_MESSAGE;
|
|
26911
|
+
exports.NO_KYC_PARAMS_MESSAGE = NO_KYC_PARAMS_MESSAGE;
|
|
26912
|
+
exports.NO_SPENDER_MESSAGE = NO_SPENDER_MESSAGE;
|
|
26052
26913
|
exports.PortfolioFacade = PortfolioFacade;
|
|
26914
|
+
exports.READ_ONLY_MESSAGE = READ_ONLY_MESSAGE;
|
|
26053
26915
|
exports.RIXON_CAPITAL = RIXON_CAPITAL;
|
|
26054
26916
|
exports.SdkConfig = SdkConfig;
|
|
26055
26917
|
exports.StrategiesFacade = StrategiesFacade;
|
|
26056
26918
|
exports.UPPER_MEZZANINE = UPPER_MEZZANINE;
|
|
26919
|
+
exports.WithdrawFlow = WithdrawFlow;
|
|
26057
26920
|
exports.apyToEpochRate = apyToEpochRate;
|
|
26058
26921
|
exports.asContractType = asContractType;
|
|
26059
26922
|
exports.auMinimumRemaining = auMinimumRemaining;
|
|
@@ -26063,6 +26926,7 @@ exports.buildFullNameRequestMessage = buildFullNameRequestMessage;
|
|
|
26063
26926
|
exports.buildLegacyContractRequestMessage = buildLegacyContractRequestMessage;
|
|
26064
26927
|
exports.buildLoanAgreementSignMessage = buildLoanAgreementSignMessage;
|
|
26065
26928
|
exports.ceilToCents = ceilToCents;
|
|
26929
|
+
exports.classifyWalletFailure = classifyWalletFailure;
|
|
26066
26930
|
exports.compareTrancheSeniority = compareTrancheSeniority;
|
|
26067
26931
|
exports.computeSettlementWindow = computeSettlementWindow;
|
|
26068
26932
|
exports.countSubmissions = countSubmissions;
|
|
@@ -26084,6 +26948,7 @@ exports.isBelowMinimumCapacity = isBelowMinimumCapacity;
|
|
|
26084
26948
|
exports.isCycleClosed = isCycleClosed;
|
|
26085
26949
|
exports.isUnpredictableGas = isUnpredictableGas;
|
|
26086
26950
|
exports.isUserRejected = isUserRejected;
|
|
26951
|
+
exports.lastEventTimestamp = lastEventTimestamp;
|
|
26087
26952
|
exports.maxNetRateCeiling = maxNetRateCeiling;
|
|
26088
26953
|
exports.netEffectiveApy = netEffectiveApy;
|
|
26089
26954
|
exports.netTrancheApyBounds = netTrancheApyBounds;
|