@kasufinance/kasu-sdk 2.5.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 +144 -7
- package/dist/bundle.cjs.js +1667 -21
- package/dist/bundle.esm.js +1630 -22
- package/dist/domain/au-minimum.d.ts +135 -0
- package/dist/domain/au-minimum.js +154 -0
- package/dist/domain/au-minimum.js.map +1 -0
- package/dist/domain/index.d.ts +18 -3
- package/dist/domain/index.js +13 -3
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/loan-contract.d.ts +174 -0
- package/dist/domain/loan-contract.js +160 -0
- package/dist/domain/loan-contract.js.map +1 -0
- package/dist/domain/requests.d.ts +219 -0
- package/dist/domain/requests.js +218 -0
- package/dist/domain/requests.js.map +1 -0
- package/dist/domain/settlement.d.ts +97 -0
- package/dist/domain/settlement.js +117 -0
- package/dist/domain/settlement.js.map +1 -0
- package/dist/domain/wallet-errors.d.ts +92 -0
- package/dist/domain/wallet-errors.js +155 -0
- package/dist/domain/wallet-errors.js.map +1 -0
- package/dist/facade/chain-configs.js +7 -1
- package/dist/facade/chain-configs.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/facade/user-portfolio.d.ts +18 -0
- package/dist/facade/user-portfolio.js +23 -0
- package/dist/facade/user-portfolio.js.map +1 -1
- 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 +2 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/services/DataService/data-service.js +3 -11
- package/dist/services/DataService/data-service.js.map +1 -1
- package/dist/services/DataService/directus-client.d.ts +26 -0
- package/dist/services/DataService/directus-client.js +38 -0
- package/dist/services/DataService/directus-client.js.map +1 -0
- package/dist/services/UserLending/user-lending.js +11 -7
- package/dist/services/UserLending/user-lending.js.map +1 -1
- package/package.json +8 -2
- package/src/domain/au-minimum.ts +192 -0
- package/src/domain/index.ts +73 -3
- package/src/domain/loan-contract.ts +275 -0
- package/src/domain/requests.ts +467 -0
- package/src/domain/settlement.ts +161 -0
- package/src/domain/wallet-errors.ts +173 -0
- package/src/facade/chain-configs.ts +7 -1
- 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/facade/user-portfolio.ts +24 -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 +9 -0
- package/src/services/DataService/data-service.ts +7 -25
- package/src/services/DataService/directus-client.ts +54 -0
- package/src/services/UserLending/user-lending.ts +17 -21
- 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/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/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/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 -201
- 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/deposit-bounds.test.ts +0 -200
- 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/tranche-display-name.test.ts +0 -96
- package/src/domain/tranches.test.ts +0 -300
- package/src/facade/config.test.ts +0 -265
- package/src/facade/facade.test.ts +0 -250
- package/src/services/Locking/calculate-apy.test.ts +0 -52
- package/src/tests/sample.test.ts +0 -63
package/dist/bundle.cjs.js
CHANGED
|
@@ -15721,6 +15721,43 @@ function filterArray(array, id_in) {
|
|
|
15721
15721
|
}
|
|
15722
15722
|
}
|
|
15723
15723
|
|
|
15724
|
+
/**
|
|
15725
|
+
* The error a CMS-only call raises on a deployment configured without
|
|
15726
|
+
* Directus. Named so a caller can match on it rather than on the message.
|
|
15727
|
+
*/
|
|
15728
|
+
const NO_DIRECTUS_URL_MESSAGE = 'Kasu: this call needs Directus, but the SDK was configured without a ' +
|
|
15729
|
+
'`directusUrl`. On-chain data (pools, tranches, positions, requests) ' +
|
|
15730
|
+
'works without one; CMS content does not.';
|
|
15731
|
+
/**
|
|
15732
|
+
* Build the Directus client, or a stand-in that refuses clearly.
|
|
15733
|
+
*
|
|
15734
|
+
* `directusUrl` is documented optional, and most of the SDK genuinely does not
|
|
15735
|
+
* need it — pools, tranches, positions and request history all come from the
|
|
15736
|
+
* subgraph and the chain. But `createDirectus('')` throws `Invalid URL` inside
|
|
15737
|
+
* the constructor, so omitting the URL used to make the whole SDK
|
|
15738
|
+
* unconstructable rather than merely CMS-less.
|
|
15739
|
+
*
|
|
15740
|
+
* With no URL, the services skip Directus where they can degrade (pool
|
|
15741
|
+
* descriptions, images and Directus pool names simply do not appear, and the
|
|
15742
|
+
* raw subgraph names are used instead), and a call that exists ONLY to read
|
|
15743
|
+
* CMS content rejects with `NO_DIRECTUS_URL_MESSAGE` — a sentence that says
|
|
15744
|
+
* what to configure, rather than a `null` dereference thrown from inside a
|
|
15745
|
+
* vendor SDK.
|
|
15746
|
+
*/
|
|
15747
|
+
function createDirectusClient(directusUrl) {
|
|
15748
|
+
if (directusUrl) {
|
|
15749
|
+
return le(directusUrl)
|
|
15750
|
+
.with(ne())
|
|
15751
|
+
.with(Zp());
|
|
15752
|
+
}
|
|
15753
|
+
const refuse = () => {
|
|
15754
|
+
throw new Error(NO_DIRECTUS_URL_MESSAGE);
|
|
15755
|
+
};
|
|
15756
|
+
// A stand-in, not a client: every entry point the services use goes
|
|
15757
|
+
// through `request`, so refusing there covers all of them.
|
|
15758
|
+
return { request: refuse };
|
|
15759
|
+
}
|
|
15760
|
+
|
|
15724
15761
|
const getPoolOverviewQuery = (ids) => gql `
|
|
15725
15762
|
query getAllPoolOverview($epochId: String!, $unusedPools: [String]!) {
|
|
15726
15763
|
lendingPools(
|
|
@@ -15876,15 +15913,7 @@ class DataService {
|
|
|
15876
15913
|
this._kasuConfig = _kasuConfig;
|
|
15877
15914
|
this._externalTvlAbi = KasuPoolExternalTVLAbi__factory.connect(_kasuConfig.contracts.ExternalTVL, signerOrProvider);
|
|
15878
15915
|
this._graph = new GraphQLClient(_kasuConfig.subgraphUrl);
|
|
15879
|
-
|
|
15880
|
-
this._directus = le(_kasuConfig.directusUrl)
|
|
15881
|
-
.with(ne())
|
|
15882
|
-
.with(Zp());
|
|
15883
|
-
}
|
|
15884
|
-
else {
|
|
15885
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
15886
|
-
this._directus = null;
|
|
15887
|
-
}
|
|
15916
|
+
this._directus = createDirectusClient(_kasuConfig.directusUrl);
|
|
15888
15917
|
}
|
|
15889
15918
|
getUrlFromFile(fileName) {
|
|
15890
15919
|
return `${this._kasuConfig.directusUrl}assets/${fileName}`;
|
|
@@ -16178,7 +16207,6 @@ class DataService {
|
|
|
16178
16207
|
return __awaiter(this, void 0, void 0, function* () {
|
|
16179
16208
|
var _a;
|
|
16180
16209
|
const poolDelegateProfileAndHistoryDirectus = (yield this._directus.request(bs('PoolDelegateProfileAndHistory', {
|
|
16181
|
-
// @ts-ignore Directus' deep-fields typing is fragile across versions
|
|
16182
16210
|
fields: ['*', { otherPools: ['*'] }],
|
|
16183
16211
|
})));
|
|
16184
16212
|
const [poolNames, directusPoolNames] = yield Promise.all([
|
|
@@ -17138,9 +17166,7 @@ class UserLending {
|
|
|
17138
17166
|
this._clearingCoordinatorAbi = IClearingCoordinatorAbi__factory.connect(_kasuConfig.contracts.ClearingCoordinator, signerOrProvider);
|
|
17139
17167
|
this._systemVariablesAbi = ISystemVariablesAbi__factory.connect(_kasuConfig.contracts.SystemVariables, signerOrProvider);
|
|
17140
17168
|
this._dataService = new DataService(_kasuConfig, signerOrProvider);
|
|
17141
|
-
this._directus =
|
|
17142
|
-
.with(ne())
|
|
17143
|
-
.with(Zp());
|
|
17169
|
+
this._directus = createDirectusClient(_kasuConfig.directusUrl);
|
|
17144
17170
|
}
|
|
17145
17171
|
getUserTotalPendingAndActiveDepositedAmount(user) {
|
|
17146
17172
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -17258,9 +17284,14 @@ class UserLending {
|
|
|
17258
17284
|
unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
|
|
17259
17285
|
epochId,
|
|
17260
17286
|
}),
|
|
17261
|
-
|
|
17262
|
-
|
|
17263
|
-
|
|
17287
|
+
// Directus supplies the display pool NAME only; without it the
|
|
17288
|
+
// raw subgraph name is used, which is what the fallback below
|
|
17289
|
+
// already does for a pool with no CMS entry.
|
|
17290
|
+
this._kasuConfig.directusUrl
|
|
17291
|
+
? this._directus.request(bs('PoolOverview', {
|
|
17292
|
+
fields: ['id', 'poolName', 'subheading'],
|
|
17293
|
+
}))
|
|
17294
|
+
: [],
|
|
17264
17295
|
]);
|
|
17265
17296
|
const retn = [];
|
|
17266
17297
|
for (const userRequest of subgraphResult.userRequests) {
|
|
@@ -24295,7 +24326,13 @@ const CHAIN_CONFIGS = {
|
|
|
24295
24326
|
ClearingCoordinator: '',
|
|
24296
24327
|
ExternalTVL: '',
|
|
24297
24328
|
},
|
|
24298
|
-
|
|
24329
|
+
// The frozen Plume history is indexed on the LEGACY Goldsky project,
|
|
24330
|
+
// not the one the live chains use: the same path under the current
|
|
24331
|
+
// project 404s. Verified 2026-09-05 — this URL answers
|
|
24332
|
+
// `{ lendingPools { id name } }` with the three Plume pools; the
|
|
24333
|
+
// current-project spelling returns HTTP 404. Note the `/gn` suffix,
|
|
24334
|
+
// which the current project's URLs do not carry.
|
|
24335
|
+
subgraphUrl: 'https://api.goldsky.com/api/public/project_cm9t3064xeuyn01tgctdo3c17/subgraphs/kasu-plume/prod/gn',
|
|
24299
24336
|
directusUrl: 'https://kasu-finance.directus.app/',
|
|
24300
24337
|
unusedPoolIds: [],
|
|
24301
24338
|
poolMetadataMapping: undefined,
|
|
@@ -24315,6 +24352,19 @@ const CHAIN_CONFIGS = {
|
|
|
24315
24352
|
},
|
|
24316
24353
|
};
|
|
24317
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
|
+
|
|
24318
24368
|
/**
|
|
24319
24369
|
* High-level facade for deposit and withdrawal operations.
|
|
24320
24370
|
*
|
|
@@ -24343,7 +24393,7 @@ class DepositsFacade {
|
|
|
24343
24393
|
*/
|
|
24344
24394
|
assertWritable() {
|
|
24345
24395
|
if (this._isReadOnly) {
|
|
24346
|
-
throw new Error(
|
|
24396
|
+
throw new Error(READ_ONLY_MESSAGE);
|
|
24347
24397
|
}
|
|
24348
24398
|
}
|
|
24349
24399
|
/**
|
|
@@ -24416,6 +24466,1024 @@ class DepositsFacade {
|
|
|
24416
24466
|
}
|
|
24417
24467
|
}
|
|
24418
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.
|
|
25409
|
+
*/
|
|
25410
|
+
_erc20,
|
|
25411
|
+
/** The read-only flag `Kasu` already computed — never re-derived here. */
|
|
25412
|
+
_isReadOnly,
|
|
25413
|
+
/**
|
|
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.
|
|
25419
|
+
*/
|
|
25420
|
+
_spender) {
|
|
25421
|
+
this._deposits = _deposits;
|
|
25422
|
+
this._erc20 = _erc20;
|
|
25423
|
+
this._isReadOnly = _isReadOnly;
|
|
25424
|
+
this._spender = _spender;
|
|
25425
|
+
}
|
|
25426
|
+
/**
|
|
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).
|
|
25431
|
+
*
|
|
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.
|
|
25437
|
+
*/
|
|
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,
|
|
25460
|
+
});
|
|
25461
|
+
}
|
|
25462
|
+
/**
|
|
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.
|
|
25466
|
+
*/
|
|
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)),
|
|
25478
|
+
});
|
|
25479
|
+
}
|
|
25480
|
+
_assertWritable() {
|
|
25481
|
+
if (this._isReadOnly) {
|
|
25482
|
+
throw new Error(READ_ONLY_MESSAGE);
|
|
25483
|
+
}
|
|
25484
|
+
}
|
|
25485
|
+
}
|
|
25486
|
+
|
|
24419
25487
|
/**
|
|
24420
25488
|
* Minimum remaining capacity (in stable-asset units) for a tranche to be
|
|
24421
25489
|
* selectable. Mirrors `MIN_CAPACITY = toBigNumber('1')` in kasu-fe-next's
|
|
@@ -24835,6 +25903,230 @@ class StrategiesFacade {
|
|
|
24835
25903
|
}
|
|
24836
25904
|
}
|
|
24837
25905
|
|
|
25906
|
+
var UserRequestStatus;
|
|
25907
|
+
(function (UserRequestStatus) {
|
|
25908
|
+
UserRequestStatus["REQUESTED"] = "Requested";
|
|
25909
|
+
UserRequestStatus["PROCESSING"] = "Processing";
|
|
25910
|
+
UserRequestStatus["PROCESSED"] = "Processed";
|
|
25911
|
+
})(UserRequestStatus || (UserRequestStatus = {}));
|
|
25912
|
+
|
|
25913
|
+
/**
|
|
25914
|
+
* Events that represent a lender SUBMISSION into the bundle. A dNFT position
|
|
25915
|
+
* aggregates every submission the lender made into the same pool/tranche this
|
|
25916
|
+
* cycle: the first is `Initiated`, each subsequent top-up is `Increased`.
|
|
25917
|
+
* Everything else on the timeline (Accepted / Rejected / Cancelled /
|
|
25918
|
+
* Reallocated / Forced) is an OUTCOME, not a request, and must not be counted.
|
|
25919
|
+
*/
|
|
25920
|
+
const SUBMISSION_EVENTS = new Set(['Initiated', 'Increased']);
|
|
25921
|
+
/**
|
|
25922
|
+
* The submissions bundled into one dNFT-aggregate request row, input order
|
|
25923
|
+
* preserved — one loan agreement per submission.
|
|
25924
|
+
*/
|
|
25925
|
+
function submissionEvents(events) {
|
|
25926
|
+
return events.filter((e) => SUBMISSION_EVENTS.has(e.requestType));
|
|
25927
|
+
}
|
|
25928
|
+
/** Count the submissions bundled into one dNFT-aggregate request row. */
|
|
25929
|
+
function countSubmissions(events) {
|
|
25930
|
+
return submissionEvents(events).length;
|
|
25931
|
+
}
|
|
25932
|
+
/**
|
|
25933
|
+
* Timestamp of the FIRST submission in the bundle. Falls back to `fallback`
|
|
25934
|
+
* when the `Initiated` event has not indexed yet — the caller decides what
|
|
25935
|
+
* that is (kasu-ui passes the request's own timestamp).
|
|
25936
|
+
*/
|
|
25937
|
+
function firstSubmissionTimestamp(events, fallback) {
|
|
25938
|
+
const submissions = submissionEvents(events);
|
|
25939
|
+
if (submissions.length === 0)
|
|
25940
|
+
return fallback;
|
|
25941
|
+
return submissions.reduce((min, e) => (e.timestamp < min ? e.timestamp : min), Infinity);
|
|
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
|
+
}
|
|
25953
|
+
/**
|
|
25954
|
+
* Has THIS request's cycle closed? — the single open/closed signal behind the
|
|
25955
|
+
* status vocabulary and behind Cancel.
|
|
25956
|
+
*
|
|
25957
|
+
* `request.canCancel` is the SDK's `isCancelable(status, poolId)` —
|
|
25958
|
+
* `status !== 'Processed' && !isLendingPoolClearingPending(pool)`. It is
|
|
25959
|
+
* per-POOL and reads the same condition the contract enforces on the cancel
|
|
25960
|
+
* call — but it reads it ONCE, when the request was fetched. Nothing about
|
|
25961
|
+
* this value is live, so a client that holds a request across a cycle close
|
|
25962
|
+
* must refetch before acting on it.
|
|
25963
|
+
*
|
|
25964
|
+
* The raw subgraph `status` must NOT feed this: `'Processing'` is a STICKY
|
|
25965
|
+
* historical marker set on the first partial fill and never reset, so gating
|
|
25966
|
+
* on it would freeze a partly-filled request in Processing forever. A global
|
|
25967
|
+
* settlement clock is equally wrong here — it is blind to whether THIS
|
|
25968
|
+
* request's pool is already clearing.
|
|
25969
|
+
*/
|
|
25970
|
+
function isCycleClosed(request) {
|
|
25971
|
+
return !request.canCancel;
|
|
25972
|
+
}
|
|
25973
|
+
const isCancelled = (events) => events.some((e) => e.requestType === 'Cancelled');
|
|
25974
|
+
const isForced = (events) => events.some((e) => e.requestType === 'Forced');
|
|
25975
|
+
const initiatedEvent = (events) => events.find((e) => e.requestType === 'Initiated');
|
|
25976
|
+
/**
|
|
25977
|
+
* Subgraph behaviour: when a request is cancelled, the on-request
|
|
25978
|
+
* `requestedAmount` field is reset to 0 (the lender's effective balance is
|
|
25979
|
+
* restored). The original amount survives on the `Initiated` event's
|
|
25980
|
+
* `assetAmount`. Recover from there so cancelled rows still carry the amount
|
|
25981
|
+
* the lender originally asked for.
|
|
25982
|
+
*/
|
|
25983
|
+
const initiatedAmountOf = (events) => {
|
|
25984
|
+
const initiated = initiatedEvent(events);
|
|
25985
|
+
return initiated ? Number(initiated.assetAmount || '0') : 0;
|
|
25986
|
+
};
|
|
25987
|
+
/**
|
|
25988
|
+
* A deposit is REALLOCATED when the timeline carries a `Reallocated` event, or
|
|
25989
|
+
* an `Accepted` event into a tranche other than the one requested.
|
|
25990
|
+
*/
|
|
25991
|
+
const findReallocation = (events, originalTrancheId) => events.find((e) => e.requestType === 'Reallocated' ||
|
|
25992
|
+
(e.requestType === 'Accepted' &&
|
|
25993
|
+
e.trancheId.toLowerCase() !==
|
|
25994
|
+
originalTrancheId.toLowerCase()));
|
|
25995
|
+
/** kasu-ui's `num`: an absent or unparseable figure reads as 0. */
|
|
25996
|
+
const num = (str) => {
|
|
25997
|
+
const n = Number(str !== null && str !== void 0 ? str : '0');
|
|
25998
|
+
return Number.isFinite(n) ? n : 0;
|
|
25999
|
+
};
|
|
26000
|
+
/** The same parse, keeping "the subgraph reported nothing" distinct from 0. */
|
|
26001
|
+
const numOrNull = (str) => {
|
|
26002
|
+
if (str === undefined || str === null || str.trim() === '')
|
|
26003
|
+
return null;
|
|
26004
|
+
const n = Number(str);
|
|
26005
|
+
return Number.isFinite(n) ? n : null;
|
|
26006
|
+
};
|
|
26007
|
+
/**
|
|
26008
|
+
* Convert a `UserRequest` into a `RequestState`. Pure — no clock, no network,
|
|
26009
|
+
* no copy.
|
|
26010
|
+
*
|
|
26011
|
+
* BRANCH ORDER (this IS the behaviour; it reproduces kasu-ui's
|
|
26012
|
+
* `deriveTransactionView` check for check):
|
|
26013
|
+
*
|
|
26014
|
+
* 1. a `Cancelled` event → `cancelled`, neutral, 0
|
|
26015
|
+
* 2. a withdrawal with a `Forced` event → `forced`, outflow, −accepted
|
|
26016
|
+
* 3. a reallocated deposit → `reallocated`, inflow, +accepted
|
|
26017
|
+
* 4. a withdrawal partly filled with a LIVE
|
|
26018
|
+
* remainder (cycle still open) → `partial`, outflow, −accepted
|
|
26019
|
+
* 5. resolved (`status === 'Processed'`):
|
|
26020
|
+
* withdrawal, partly filled → `partial`, outflow, −accepted
|
|
26021
|
+
* withdrawal, fully filled → `complete`, outflow, −accepted
|
|
26022
|
+
* deposit, nothing accepted → `rejected`, neutral, 0
|
|
26023
|
+
* deposit, part rejected → `partial`, inflow, +accepted
|
|
26024
|
+
* deposit, fully accepted → `complete`, inflow, +accepted
|
|
26025
|
+
* 6. otherwise → `pending`, ±requested
|
|
26026
|
+
*
|
|
26027
|
+
* `cancelled` is reachable ONLY from branch 1 — a Cancelled EVENT. No
|
|
26028
|
+
* processing or resolved state can derive it.
|
|
26029
|
+
*
|
|
26030
|
+
* Branch 4 is checked BEFORE the resolved branch and gates on `isCycleClosed`
|
|
26031
|
+
* (i.e. `canCancel`), never on the sticky raw status: a withdrawal that was
|
|
26032
|
+
* partly filled returns to the queue with a live Cancel, and reading the raw
|
|
26033
|
+
* status would strand it.
|
|
26034
|
+
*
|
|
26035
|
+
* WHAT THE APPLICATION STILL OWNS: the status word and the detail line beneath
|
|
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 —
|
|
26040
|
+
* `requestType === 'Deposit' && statusCode !== 'cancelled' && statusCode !== 'rejected'`
|
|
26041
|
+
* — because neither a cancelled nor a fully-rejected deposit ever issued one,
|
|
26042
|
+
* and withdrawals sign no agreement at all.
|
|
26043
|
+
*/
|
|
26044
|
+
function deriveRequestState(request) {
|
|
26045
|
+
var _a, _b, _c;
|
|
26046
|
+
const isWithdrawal = request.requestType === 'Withdrawal';
|
|
26047
|
+
const cancelled = isCancelled(request.events);
|
|
26048
|
+
// Cancelled requests have `requestedAmount` zeroed on the request itself;
|
|
26049
|
+
// pull the original value from the Initiated event so the row still
|
|
26050
|
+
// carries "100 cancelled" instead of "0 cancelled".
|
|
26051
|
+
const requested = cancelled
|
|
26052
|
+
? num(request.requestedAmount) || initiatedAmountOf(request.events)
|
|
26053
|
+
: num(request.requestedAmount);
|
|
26054
|
+
const accepted = num(request.acceptedAmount);
|
|
26055
|
+
const rejected = num(request.rejectedAmount);
|
|
26056
|
+
const forced = isWithdrawal && isForced(request.events);
|
|
26057
|
+
const reallocation = !isWithdrawal && findReallocation(request.events, request.trancheId);
|
|
26058
|
+
const initiated = initiatedEvent(request.events);
|
|
26059
|
+
const base = {
|
|
26060
|
+
id: request.id,
|
|
26061
|
+
contractId: (_a = initiated === null || initiated === void 0 ? void 0 : initiated.id) !== null && _a !== void 0 ? _a : '',
|
|
26062
|
+
poolId: request.lendingPool.id.toLowerCase(),
|
|
26063
|
+
poolName: request.lendingPool.name,
|
|
26064
|
+
trancheName: request.trancheName,
|
|
26065
|
+
trancheId: request.trancheId,
|
|
26066
|
+
fixedTermConfigId: (_c = (_b = request.fixedTermConfig) === null || _b === void 0 ? void 0 : _b.configId) !== null && _c !== void 0 ? _c : '0',
|
|
26067
|
+
requestType: request.requestType,
|
|
26068
|
+
rawStatus: request.status,
|
|
26069
|
+
requestedAmount: requested,
|
|
26070
|
+
acceptedAmount: numOrNull(request.acceptedAmount),
|
|
26071
|
+
initiatedAmount: initiated
|
|
26072
|
+
? initiatedAmountOf(request.events)
|
|
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),
|
|
26080
|
+
submissionCount: countSubmissions(request.events),
|
|
26081
|
+
// `firstSubmissionTimestamp` needs a fallback it will never use here:
|
|
26082
|
+
// the bundle is non-empty on every path that reaches the call.
|
|
26083
|
+
firstSubmissionTimestamp: countSubmissions(request.events) === 0
|
|
26084
|
+
? null
|
|
26085
|
+
: firstSubmissionTimestamp(request.events, 0),
|
|
26086
|
+
cycleClosed: isCycleClosed(request),
|
|
26087
|
+
canCancel: request.canCancel,
|
|
26088
|
+
};
|
|
26089
|
+
// 1. Cancelled wins over everything — the ONLY path to `cancelled`.
|
|
26090
|
+
if (cancelled) {
|
|
26091
|
+
return Object.assign(Object.assign({}, base), { kind: 'neutral', amount: 0, statusCode: 'cancelled' });
|
|
26092
|
+
}
|
|
26093
|
+
// 2. Forced withdrawal (credit originator returned funds early).
|
|
26094
|
+
if (forced) {
|
|
26095
|
+
return Object.assign(Object.assign({}, base), { kind: 'outflow', amount: -accepted, statusCode: 'forced' });
|
|
26096
|
+
}
|
|
26097
|
+
// 3. Reallocated deposit (accepted into a different lending option).
|
|
26098
|
+
if (reallocation) {
|
|
26099
|
+
return Object.assign(Object.assign({}, base), { kind: 'inflow', amount: accepted, statusCode: 'reallocated' });
|
|
26100
|
+
}
|
|
26101
|
+
// 4. Withdrawal partly filled with a LIVE remainder: the request returns
|
|
26102
|
+
// to the queue with a live Cancel. Checked BEFORE the resolved branch and
|
|
26103
|
+
// via `isCycleClosed`, never via the sticky raw status.
|
|
26104
|
+
if (isWithdrawal &&
|
|
26105
|
+
accepted > 0 &&
|
|
26106
|
+
accepted < requested &&
|
|
26107
|
+
!isCycleClosed(request)) {
|
|
26108
|
+
return Object.assign(Object.assign({}, base), { kind: 'outflow', amount: -accepted, statusCode: 'partial' });
|
|
26109
|
+
}
|
|
26110
|
+
// 5. Resolved — a rejection IS a resolution.
|
|
26111
|
+
if (request.status === UserRequestStatus.PROCESSED) {
|
|
26112
|
+
if (isWithdrawal) {
|
|
26113
|
+
const partly = accepted < requested;
|
|
26114
|
+
return Object.assign(Object.assign({}, base), { kind: 'outflow', amount: -accepted, statusCode: partly ? 'partial' : 'complete' });
|
|
26115
|
+
}
|
|
26116
|
+
// Deposit: full reject vs partial vs full accept.
|
|
26117
|
+
if (accepted === 0) {
|
|
26118
|
+
return Object.assign(Object.assign({}, base), { kind: 'neutral', amount: 0, statusCode: 'rejected' });
|
|
26119
|
+
}
|
|
26120
|
+
if (rejected > 0) {
|
|
26121
|
+
return Object.assign(Object.assign({}, base), { kind: 'inflow', amount: accepted, statusCode: 'partial' });
|
|
26122
|
+
}
|
|
26123
|
+
return Object.assign(Object.assign({}, base), { kind: 'inflow', amount: accepted, statusCode: 'complete' });
|
|
26124
|
+
}
|
|
26125
|
+
// 6. Unresolved. `cycleClosed` tells the caller whether to render its
|
|
26126
|
+
// "queued" or its "processing" word; the code is the same either way.
|
|
26127
|
+
return Object.assign(Object.assign({}, base), { kind: isWithdrawal ? 'outflow' : 'inflow', amount: isWithdrawal ? -requested : requested, statusCode: 'pending' });
|
|
26128
|
+
}
|
|
26129
|
+
|
|
24838
26130
|
/**
|
|
24839
26131
|
* High-level facade for querying a user's portfolio, positions, and history.
|
|
24840
26132
|
*
|
|
@@ -24877,13 +26169,36 @@ class PortfolioFacade {
|
|
|
24877
26169
|
return yield this._userLending.getUserRequests(userAddress, currentEpoch);
|
|
24878
26170
|
});
|
|
24879
26171
|
}
|
|
26172
|
+
/**
|
|
26173
|
+
* The same history, already derived into `RequestState` rows — status
|
|
26174
|
+
* code, kind, signed amount, the cancelled-amount recovery, the bundled
|
|
26175
|
+
* submission count and the cycle-open signal.
|
|
26176
|
+
*
|
|
26177
|
+
* ```ts
|
|
26178
|
+
* const rows = await kasu.portfolio.getRequestStates('0xUser...');
|
|
26179
|
+
* rows.filter((r) => r.statusCode === 'pending');
|
|
26180
|
+
* ```
|
|
26181
|
+
*
|
|
26182
|
+
* Every application derives this from `getTransactionHistory` anyway, and
|
|
26183
|
+
* the derivation is the part they were each getting subtly differently.
|
|
26184
|
+
* Words are still the caller's: render `statusCode` in your own
|
|
26185
|
+
* vocabulary, and call `getTrancheDisplayName` on `trancheName` at the
|
|
26186
|
+
* view boundary.
|
|
26187
|
+
*/
|
|
26188
|
+
getRequestStates(userAddress) {
|
|
26189
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26190
|
+
const requests = yield this.getTransactionHistory(userAddress);
|
|
26191
|
+
return requests.map(deriveRequestState);
|
|
26192
|
+
});
|
|
26193
|
+
}
|
|
24880
26194
|
}
|
|
24881
26195
|
|
|
24882
26196
|
/**
|
|
24883
26197
|
* High-level entry point for external integrators.
|
|
24884
26198
|
*
|
|
24885
|
-
* Provides
|
|
24886
|
-
* 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.
|
|
24887
26202
|
*
|
|
24888
26203
|
* ```ts
|
|
24889
26204
|
* import { Kasu } from '@kasufinance/kasu-sdk';
|
|
@@ -24907,8 +26222,13 @@ class Kasu {
|
|
|
24907
26222
|
this._signerOrProvider = signerOrProvider;
|
|
24908
26223
|
this._configOverrides = configOverrides;
|
|
24909
26224
|
this.strategies = new StrategiesFacade(sdk.DataService, sdk.UserLending);
|
|
24910
|
-
|
|
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);
|
|
24911
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);
|
|
24912
26232
|
}
|
|
24913
26233
|
/**
|
|
24914
26234
|
* Create a Kasu instance with built-in chain config.
|
|
@@ -25040,6 +26360,23 @@ function rpcUrlsOf(chainConfig) {
|
|
|
25040
26360
|
var _a;
|
|
25041
26361
|
return (_a = chainConfig.rpcUrls) !== null && _a !== void 0 ? _a : [];
|
|
25042
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
|
+
}
|
|
25043
26380
|
/**
|
|
25044
26381
|
* The read-only provider used when the caller passes no `signerOrProvider`.
|
|
25045
26382
|
*
|
|
@@ -25125,6 +26462,160 @@ function fetchUnusedPoolIds() {
|
|
|
25125
26462
|
});
|
|
25126
26463
|
}
|
|
25127
26464
|
|
|
26465
|
+
/**
|
|
26466
|
+
* The AU cumulative-lending minimum — the numeric half.
|
|
26467
|
+
*
|
|
26468
|
+
* RULE: a lender whose verified KYC country is Australia may only lend when
|
|
26469
|
+
* their existing deposited position on THIS deployment, plus the amount they
|
|
26470
|
+
* are asking for, reaches the deployment's threshold.
|
|
26471
|
+
*
|
|
26472
|
+
* This is UX PRE-VALIDATION ONLY. kasu-backend enforces the rule
|
|
26473
|
+
* authoritatively at contract generation and refuses with HTTP 403
|
|
26474
|
+
* `AU_WHOLESALE_MINIMUM_NOT_MET`; a lender who gets past this check is still
|
|
26475
|
+
* stopped there. What these helpers exist for is to raise the amount field's
|
|
26476
|
+
* minimum so the lender learns the rule while typing rather than at the end.
|
|
26477
|
+
*
|
|
26478
|
+
* FAIL-OPEN by design: an absent or unknown country is NOT restricted, and an
|
|
26479
|
+
* unlisted stable symbol has no threshold. An UNKNOWN existing position is
|
|
26480
|
+
* treated as 0 — strict, so the floor is never advertised lower than the
|
|
26481
|
+
* backend will accept, and it relaxes once the position loads.
|
|
26482
|
+
*
|
|
26483
|
+
* Arithmetic is integer bigint in minor units (10^decimals) so no two
|
|
26484
|
+
* consumers can disagree at the boundary. Parsing TRUNCATES, never rounds up —
|
|
26485
|
+
* a position can never be inflated into passing. `BigInt(...)` calls only, no
|
|
26486
|
+
* bigint literals, so consumers on an older target still compile.
|
|
26487
|
+
*
|
|
26488
|
+
* Lifted from kasu-ui's `features/lending/lib/au-lending-restriction.ts`. The
|
|
26489
|
+
* two toast strings that live there are copy and stay in the applications.
|
|
26490
|
+
*/
|
|
26491
|
+
const ZERO = BigInt(0);
|
|
26492
|
+
/** ISO 3166-1 alpha-3 for Australia — the collapsed KYC country field. */
|
|
26493
|
+
const AU_ALPHA3 = 'AUS';
|
|
26494
|
+
/**
|
|
26495
|
+
* Is this the Australian KYC country? Case-insensitive and
|
|
26496
|
+
* whitespace-tolerant. Anything that is not a string — including the
|
|
26497
|
+
* `undefined` of an unloaded KYC record — is not restricted (fail-open).
|
|
26498
|
+
*
|
|
26499
|
+
* The alpha-2 `'AU'` deliberately does NOT match: the KYC country reaching
|
|
26500
|
+
* this rule is normalised to alpha-3 upstream, and a bare two-letter code
|
|
26501
|
+
* here means something else went wrong.
|
|
26502
|
+
*/
|
|
26503
|
+
function isAustralianKyc(country) {
|
|
26504
|
+
if (typeof country !== 'string')
|
|
26505
|
+
return false;
|
|
26506
|
+
return country.trim().toUpperCase() === AU_ALPHA3;
|
|
26507
|
+
}
|
|
26508
|
+
/**
|
|
26509
|
+
* Minimum CUMULATIVE position, in WHOLE stable units, keyed on the
|
|
26510
|
+
* deployment's stable-asset symbol (a property of the currency, not the
|
|
26511
|
+
* chain). Must match kasu-backend's table exactly.
|
|
26512
|
+
*/
|
|
26513
|
+
const AU_MIN_CUMULATIVE_BY_STABLE = {
|
|
26514
|
+
USDC: 360000,
|
|
26515
|
+
AUDD: 500000,
|
|
26516
|
+
};
|
|
26517
|
+
/**
|
|
26518
|
+
* Threshold in whole units, or `undefined` for an unlisted stable — a
|
|
26519
|
+
* deployment whose currency has no configured minimum is unrestricted.
|
|
26520
|
+
*/
|
|
26521
|
+
function auThresholdFor(symbol) {
|
|
26522
|
+
if (typeof symbol !== 'string')
|
|
26523
|
+
return undefined;
|
|
26524
|
+
return AU_MIN_CUMULATIVE_BY_STABLE[symbol.trim().toUpperCase()];
|
|
26525
|
+
}
|
|
26526
|
+
/**
|
|
26527
|
+
* Is this wallet released from the minimum entirely, whatever its country,
|
|
26528
|
+
* stable or position says?
|
|
26529
|
+
*
|
|
26530
|
+
* `exemptAddresses` is DEPLOYMENT CONFIGURATION the caller supplies, not a
|
|
26531
|
+
* constant of this package. kasu-sdk is published publicly; the exempt list is
|
|
26532
|
+
* a compliance carve-out naming particular lender wallets, so it does not
|
|
26533
|
+
* belong in a public tarball. Read it from wherever the application keeps its
|
|
26534
|
+
* deployment configuration, and keep it identical to kasu-backend's
|
|
26535
|
+
* `AU_WHOLESALE_EXEMPT_ADDRESSES` — that is where the rule is actually
|
|
26536
|
+
* enforced. An address exempt here but not there sees no raised minimum in the
|
|
26537
|
+
* form and is refused at the end, which is worse than not exempting it at all.
|
|
26538
|
+
*
|
|
26539
|
+
* The address matched must be the CONNECTED wallet — the one that owns the KYC
|
|
26540
|
+
* record and signs the agreement request. kasu-backend verifies that signature
|
|
26541
|
+
* before the gate, so an exemption cannot be claimed by asserting someone
|
|
26542
|
+
* else's address; it takes their private key. Never match a "view as" address.
|
|
26543
|
+
*
|
|
26544
|
+
* Comparison is case-insensitive and whitespace-tolerant. Anything that is not
|
|
26545
|
+
* a string is NOT exempt (fail closed), and so is anything when the list is
|
|
26546
|
+
* absent.
|
|
26547
|
+
*/
|
|
26548
|
+
function isAuMinimumExempt(address, exemptAddresses) {
|
|
26549
|
+
if (typeof address !== 'string' || !exemptAddresses)
|
|
26550
|
+
return false;
|
|
26551
|
+
const needle = address.trim().toLowerCase();
|
|
26552
|
+
return exemptAddresses.some((a) => a.trim().toLowerCase() === needle);
|
|
26553
|
+
}
|
|
26554
|
+
const DECIMAL_RE = /^(\d+)?(?:\.(\d*))?$/;
|
|
26555
|
+
/**
|
|
26556
|
+
* Truncating, partial-input-tolerant decimal → minor-unit parse.
|
|
26557
|
+
* `'10.'` → `10000000n` · `'1.23456789'` → `1234567n` (truncated at 6dp).
|
|
26558
|
+
* `''` / `'abc'` / negative / null → `null`.
|
|
26559
|
+
*
|
|
26560
|
+
* Tolerant of mid-typing states because it runs on an amount field as the
|
|
26561
|
+
* lender types, and truncating because rounding up would let a position that
|
|
26562
|
+
* is a fraction short read as passing.
|
|
26563
|
+
*/
|
|
26564
|
+
function parseMinorUnits(value, decimals) {
|
|
26565
|
+
if (value === null || value === undefined)
|
|
26566
|
+
return null;
|
|
26567
|
+
const raw = typeof value === 'number' ? String(value) : value.trim();
|
|
26568
|
+
if (!raw)
|
|
26569
|
+
return null;
|
|
26570
|
+
const match = DECIMAL_RE.exec(raw);
|
|
26571
|
+
if (!match || (!match[1] && !match[2]))
|
|
26572
|
+
return null;
|
|
26573
|
+
// Both capture groups are optional, so either can be absent at runtime.
|
|
26574
|
+
// A fractional group that matched EMPTY ('10.') is not absent, and the
|
|
26575
|
+
// destructuring defaults leave it alone — only `undefined` takes them.
|
|
26576
|
+
const [, whole = '0', fracDigits = ''] = match;
|
|
26577
|
+
const frac = fracDigits.slice(0, decimals).padEnd(decimals, '0');
|
|
26578
|
+
return BigInt(whole + frac);
|
|
26579
|
+
}
|
|
26580
|
+
/**
|
|
26581
|
+
* The amount, in WHOLE stable units, an Australian lender still needs in order
|
|
26582
|
+
* to reach the deployment's cumulative minimum. Apply it as the amount field's
|
|
26583
|
+
* floor via `max(trancheMin, auMinimumRemaining(...))`.
|
|
26584
|
+
*
|
|
26585
|
+
* Returns 0 for everyone the rule does not restrict (fail-open), 0 for an
|
|
26586
|
+
* exempt address, and 0 once the lender's existing position already satisfies
|
|
26587
|
+
* the threshold.
|
|
26588
|
+
*
|
|
26589
|
+
* The exemption is tested FIRST and unconditionally: an exempt wallet has no
|
|
26590
|
+
* minimum whatever its country, stable or position says. kasu-backend
|
|
26591
|
+
* deliberately tests country first and the exemption second — the two orders
|
|
26592
|
+
* are not in conflict, both return "no minimum" for the same inputs. There,
|
|
26593
|
+
* the exemption sets a flag that drives a compliance log, so it must not fire
|
|
26594
|
+
* for a lender the rule never engaged for. Here nothing is logged, so
|
|
26595
|
+
* exemption-first is preferred: it makes this function right on its own,
|
|
26596
|
+
* whatever country a caller happens to pass.
|
|
26597
|
+
*/
|
|
26598
|
+
function auMinimumRemaining(input) {
|
|
26599
|
+
var _a, _b;
|
|
26600
|
+
if (isAuMinimumExempt(input.address, input.exemptAddresses))
|
|
26601
|
+
return 0;
|
|
26602
|
+
if (!isAustralianKyc(input.country))
|
|
26603
|
+
return 0;
|
|
26604
|
+
const threshold = auThresholdFor(input.stableSymbol);
|
|
26605
|
+
if (threshold === undefined)
|
|
26606
|
+
return 0;
|
|
26607
|
+
const { existingDeposited, decimals } = input;
|
|
26608
|
+
// A table threshold always parses; `?? ZERO` keeps the rule fail-open
|
|
26609
|
+
// rather than asserting, so an unparseable one yields no minimum at all.
|
|
26610
|
+
const thresholdMinor = (_a = parseMinorUnits(threshold, decimals)) !== null && _a !== void 0 ? _a : ZERO;
|
|
26611
|
+
const existingMinor = (_b = parseMinorUnits(existingDeposited, decimals)) !== null && _b !== void 0 ? _b : ZERO;
|
|
26612
|
+
const remaining = thresholdMinor - existingMinor;
|
|
26613
|
+
if (remaining <= ZERO)
|
|
26614
|
+
return 0;
|
|
26615
|
+
const base = BigInt('1' + '0'.repeat(decimals));
|
|
26616
|
+
return Number(remaining / base) + Number(remaining % base) / Number(base);
|
|
26617
|
+
}
|
|
26618
|
+
|
|
25128
26619
|
// Fallbacks if the selected tranche's on-chain min/max are 0 / NaN (some
|
|
25129
26620
|
// "coming soon" pools haven't been configured yet).
|
|
25130
26621
|
const MIN_LENDING_AMOUNT_FALLBACK = 500;
|
|
@@ -25252,6 +26743,123 @@ function getInstitutionalLender(originator) {
|
|
|
25252
26743
|
return originator.name === APXIUM.name ? RIXON_CAPITAL : null;
|
|
25253
26744
|
}
|
|
25254
26745
|
|
|
26746
|
+
/**
|
|
26747
|
+
* Cycles and the clearing window — the protocol's weekly clock, as numbers.
|
|
26748
|
+
*
|
|
26749
|
+
* Lifted verbatim from kasu-ui's `features/portfolio/lib/settlement-window.ts`
|
|
26750
|
+
* and `features/lending/lib/cycle-dates.ts`. The two formatters that live
|
|
26751
|
+
* beside `deriveCycleDates` there (`formatCycleDate`, `formatCycleCloseUtc`)
|
|
26752
|
+
* print words and stay in the applications; everything here is unix seconds in
|
|
26753
|
+
* and unix seconds out.
|
|
26754
|
+
*
|
|
26755
|
+
* The clearing window is the fixed 48 hours immediately preceding an epoch
|
|
26756
|
+
* end. Inside it, pending requests are being processed and cannot be modified,
|
|
26757
|
+
* and the countdown runs to the epoch end. Outside it, the countdown runs to
|
|
26758
|
+
* the next clearing-window start.
|
|
26759
|
+
*
|
|
26760
|
+
* The epoch end comes straight from the chain (`nextEpochStartTimestamp`, i.e.
|
|
26761
|
+
* the SDK's `getNextEpochDate`) — the same value the protocol's own
|
|
26762
|
+
* `getNextClearingPeriodDate` derives from — so the window always lines up
|
|
26763
|
+
* with the real weekly schedule (Tue 06:00 → Thu 06:00 UTC on Base) instead of
|
|
26764
|
+
* a projected subgraph timestamp that can drift off the grid.
|
|
26765
|
+
*/
|
|
26766
|
+
/**
|
|
26767
|
+
* Clearing-window length — a fixed 48h protocol constant. Exported so every
|
|
26768
|
+
* consumer derives the window from the same number this module runs on.
|
|
26769
|
+
*/
|
|
26770
|
+
const CLEARING_WINDOW_SECONDS = 48 * 60 * 60;
|
|
26771
|
+
/**
|
|
26772
|
+
* Weekly cadence — the epoch schedule is fixed weekly (Tue → Thu UTC on Base).
|
|
26773
|
+
* Used only to roll a cycle forward when a request lands inside a window that
|
|
26774
|
+
* has already closed.
|
|
26775
|
+
*/
|
|
26776
|
+
const WEEK_SECONDS = 7 * 24 * 60 * 60;
|
|
26777
|
+
/**
|
|
26778
|
+
* Which phase of the weekly cycle `nowSeconds` falls in, and how long is left
|
|
26779
|
+
* of it.
|
|
26780
|
+
*
|
|
26781
|
+
* `'unknown'` when no epoch boundary has been loaded yet, or when the one on
|
|
26782
|
+
* hand is stale (it has already elapsed — the on-chain value refetches to the
|
|
26783
|
+
* next boundary shortly after rollover). A caller must render its "no cycle
|
|
26784
|
+
* loaded" state there, never a zeroed countdown.
|
|
26785
|
+
*/
|
|
26786
|
+
function computeSettlementWindow({ nowSeconds, nextEpochStart, clearingWindowSeconds = CLEARING_WINDOW_SECONDS, }) {
|
|
26787
|
+
// No epoch boundary loaded yet, or a stale one that already elapsed.
|
|
26788
|
+
if (nextEpochStart <= 0 || nextEpochStart <= nowSeconds) {
|
|
26789
|
+
return { phase: 'unknown' };
|
|
26790
|
+
}
|
|
26791
|
+
const clearingStart = nextEpochStart - clearingWindowSeconds;
|
|
26792
|
+
if (nowSeconds < clearingStart) {
|
|
26793
|
+
return {
|
|
26794
|
+
phase: 'awaiting',
|
|
26795
|
+
secondsUntilClearing: clearingStart - nowSeconds,
|
|
26796
|
+
nextClearingStart: clearingStart,
|
|
26797
|
+
};
|
|
26798
|
+
}
|
|
26799
|
+
// nowSeconds is in [clearingStart, nextEpochStart) — inside the window.
|
|
26800
|
+
return {
|
|
26801
|
+
phase: 'clearing',
|
|
26802
|
+
secondsUntilEpochEnd: nextEpochStart - nowSeconds,
|
|
26803
|
+
epochEnd: nextEpochStart,
|
|
26804
|
+
};
|
|
26805
|
+
}
|
|
26806
|
+
/**
|
|
26807
|
+
* The next cycle boundary strictly after `nowSeconds`, in unix seconds — the
|
|
26808
|
+
* cycle close (`nextEpochStart − 48h`) while the window is still open, the
|
|
26809
|
+
* epoch end once we are inside it.
|
|
26810
|
+
*
|
|
26811
|
+
* `undefined` when there is no boundary left to wait for: no epoch boundary
|
|
26812
|
+
* loaded, or a cached one that has already elapsed — the same staleness rule
|
|
26813
|
+
* `computeSettlementWindow` applies before it reports `'unknown'`.
|
|
26814
|
+
*
|
|
26815
|
+
* Split out of the state machine because some consumers need the INSTANT
|
|
26816
|
+
* rather than the phase: one to flush the cycle-dependent caches when the
|
|
26817
|
+
* clock crosses it, one to move a pre-commit screen's snapshot clock at the
|
|
26818
|
+
* same moment.
|
|
26819
|
+
*/
|
|
26820
|
+
function nextCycleBoundary(nextEpochStart, nowSeconds, clearingWindowSeconds = CLEARING_WINDOW_SECONDS) {
|
|
26821
|
+
if (!nextEpochStart || nextEpochStart <= 0)
|
|
26822
|
+
return undefined;
|
|
26823
|
+
const clearingStart = nextEpochStart - clearingWindowSeconds;
|
|
26824
|
+
if (nowSeconds < clearingStart)
|
|
26825
|
+
return clearingStart;
|
|
26826
|
+
if (nowSeconds < nextEpochStart)
|
|
26827
|
+
return nextEpochStart;
|
|
26828
|
+
return undefined;
|
|
26829
|
+
}
|
|
26830
|
+
/**
|
|
26831
|
+
* The cycle-close and outcome dates for a request submitted `now`.
|
|
26832
|
+
*
|
|
26833
|
+
* - The cycle "closes" (stops accepting requests, starts processing) at the
|
|
26834
|
+
* start of the 48h clearing window, i.e. 48h before the epoch end.
|
|
26835
|
+
* - Processing takes up to 48h, so the outcome is confirmed by the epoch end
|
|
26836
|
+
* (close + 48h).
|
|
26837
|
+
*
|
|
26838
|
+
* Returns `null` when the epoch boundary is not available or is stale — a
|
|
26839
|
+
* caller then omits the dates entirely (omit, don't stub).
|
|
26840
|
+
*
|
|
26841
|
+
* The common case is a request submitted OUTSIDE the clearing window: the
|
|
26842
|
+
* close is `nextEpochStart − 48h` and the outcome is `nextEpochStart`. When
|
|
26843
|
+
* the request lands INSIDE the current clearing window (that close is already
|
|
26844
|
+
* in the past), it queues for the NEXT weekly cycle, so the close is advanced
|
|
26845
|
+
* by whole weeks until it is in the future.
|
|
26846
|
+
*/
|
|
26847
|
+
function deriveCycleDates(nextEpochStart, nowSeconds) {
|
|
26848
|
+
// No boundary loaded, or a stale one that already elapsed — the same
|
|
26849
|
+
// staleness rule as `computeSettlementWindow`.
|
|
26850
|
+
if (!nextEpochStart || nextEpochStart <= nowSeconds)
|
|
26851
|
+
return null;
|
|
26852
|
+
let close = nextEpochStart - CLEARING_WINDOW_SECONDS;
|
|
26853
|
+
let outcome = nextEpochStart;
|
|
26854
|
+
// Inside the current clearing window the close already passed; a request
|
|
26855
|
+
// now is queued for the next weekly cycle.
|
|
26856
|
+
while (close <= nowSeconds) {
|
|
26857
|
+
close += WEEK_SECONDS;
|
|
26858
|
+
outcome += WEEK_SECONDS;
|
|
26859
|
+
}
|
|
26860
|
+
return { close, outcome };
|
|
26861
|
+
}
|
|
26862
|
+
|
|
25255
26863
|
// Business rename (2026-07): Apxium strategies market their top retail tranche
|
|
25256
26864
|
// as "Upper Mezzanine" — the true senior position is held by the institutional
|
|
25257
26865
|
// lender (Rixon Capital), so retail lenders are never actually senior in the
|
|
@@ -25282,34 +26890,71 @@ function getTrancheDisplayName(trancheName, pool) {
|
|
|
25282
26890
|
}
|
|
25283
26891
|
|
|
25284
26892
|
exports.APXIUM = APXIUM;
|
|
26893
|
+
exports.AU_ALPHA3 = AU_ALPHA3;
|
|
26894
|
+
exports.AU_MIN_CUMULATIVE_BY_STABLE = AU_MIN_CUMULATIVE_BY_STABLE;
|
|
25285
26895
|
exports.CHAIN_CONFIGS = CHAIN_CONFIGS;
|
|
26896
|
+
exports.CLEARING_WINDOW_SECONDS = CLEARING_WINDOW_SECONDS;
|
|
26897
|
+
exports.CONTRACT_TTL_MS = CONTRACT_TTL_MS;
|
|
26898
|
+
exports.DepositFlow = DepositFlow;
|
|
25286
26899
|
exports.DepositsFacade = DepositsFacade;
|
|
25287
26900
|
exports.EPOCHS_IN_YEAR = EPOCHS_IN_YEAR;
|
|
26901
|
+
exports.Flow = Flow;
|
|
26902
|
+
exports.FlowStore = FlowStore;
|
|
26903
|
+
exports.FlowsFacade = FlowsFacade;
|
|
25288
26904
|
exports.INVOICEMATE = INVOICEMATE;
|
|
25289
26905
|
exports.Kasu = Kasu;
|
|
25290
26906
|
exports.KasuSdk = KasuSdk;
|
|
25291
26907
|
exports.MAX_LENDING_AMOUNT_FALLBACK = MAX_LENDING_AMOUNT_FALLBACK;
|
|
25292
26908
|
exports.MIN_LENDING_AMOUNT_FALLBACK = MIN_LENDING_AMOUNT_FALLBACK;
|
|
25293
26909
|
exports.MIN_TRANCHE_CAPACITY = MIN_TRANCHE_CAPACITY;
|
|
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;
|
|
25294
26913
|
exports.PortfolioFacade = PortfolioFacade;
|
|
26914
|
+
exports.READ_ONLY_MESSAGE = READ_ONLY_MESSAGE;
|
|
25295
26915
|
exports.RIXON_CAPITAL = RIXON_CAPITAL;
|
|
25296
26916
|
exports.SdkConfig = SdkConfig;
|
|
25297
26917
|
exports.StrategiesFacade = StrategiesFacade;
|
|
25298
26918
|
exports.UPPER_MEZZANINE = UPPER_MEZZANINE;
|
|
26919
|
+
exports.WithdrawFlow = WithdrawFlow;
|
|
25299
26920
|
exports.apyToEpochRate = apyToEpochRate;
|
|
26921
|
+
exports.asContractType = asContractType;
|
|
26922
|
+
exports.auMinimumRemaining = auMinimumRemaining;
|
|
26923
|
+
exports.auThresholdFor = auThresholdFor;
|
|
26924
|
+
exports.buildContractVersionType = buildContractVersionType;
|
|
26925
|
+
exports.buildFullNameRequestMessage = buildFullNameRequestMessage;
|
|
26926
|
+
exports.buildLegacyContractRequestMessage = buildLegacyContractRequestMessage;
|
|
26927
|
+
exports.buildLoanAgreementSignMessage = buildLoanAgreementSignMessage;
|
|
25300
26928
|
exports.ceilToCents = ceilToCents;
|
|
26929
|
+
exports.classifyWalletFailure = classifyWalletFailure;
|
|
25301
26930
|
exports.compareTrancheSeniority = compareTrancheSeniority;
|
|
26931
|
+
exports.computeSettlementWindow = computeSettlementWindow;
|
|
26932
|
+
exports.countSubmissions = countSubmissions;
|
|
26933
|
+
exports.deriveCycleDates = deriveCycleDates;
|
|
25302
26934
|
exports.derivePoolStatus = derivePoolStatus;
|
|
26935
|
+
exports.deriveRequestState = deriveRequestState;
|
|
26936
|
+
exports.encodeDepositData = encodeDepositData;
|
|
25303
26937
|
exports.epochRateToApy = epochRateToApy;
|
|
25304
26938
|
exports.fetchUnusedPoolIds = fetchUnusedPoolIds;
|
|
26939
|
+
exports.firstSubmissionTimestamp = firstSubmissionTimestamp;
|
|
25305
26940
|
exports.floorToCents = floorToCents;
|
|
26941
|
+
exports.formatSignTimestampUtc = formatSignTimestampUtc;
|
|
25306
26942
|
exports.getCreditOriginator = getCreditOriginator;
|
|
25307
26943
|
exports.getInstitutionalLender = getInstitutionalLender;
|
|
25308
26944
|
exports.getTrancheDisplayName = getTrancheDisplayName;
|
|
26945
|
+
exports.isAuMinimumExempt = isAuMinimumExempt;
|
|
26946
|
+
exports.isAustralianKyc = isAustralianKyc;
|
|
25309
26947
|
exports.isBelowMinimumCapacity = isBelowMinimumCapacity;
|
|
26948
|
+
exports.isCycleClosed = isCycleClosed;
|
|
26949
|
+
exports.isUnpredictableGas = isUnpredictableGas;
|
|
26950
|
+
exports.isUserRejected = isUserRejected;
|
|
26951
|
+
exports.lastEventTimestamp = lastEventTimestamp;
|
|
25310
26952
|
exports.maxNetRateCeiling = maxNetRateCeiling;
|
|
25311
26953
|
exports.netEffectiveApy = netEffectiveApy;
|
|
25312
26954
|
exports.netTrancheApyBounds = netTrancheApyBounds;
|
|
26955
|
+
exports.nextCycleBoundary = nextCycleBoundary;
|
|
26956
|
+
exports.parseFormattedMessage = parseFormattedMessage;
|
|
26957
|
+
exports.parseMinorUnits = parseMinorUnits;
|
|
25313
26958
|
exports.parseTrancheBound = parseTrancheBound;
|
|
25314
26959
|
exports.pickDefaultTrancheId = pickDefaultTrancheId;
|
|
25315
26960
|
exports.pickHighestYieldTranche = pickHighestYieldTranche;
|
|
@@ -25318,6 +26963,7 @@ exports.poolMaxApy = poolMaxApy;
|
|
|
25318
26963
|
exports.resolveBoundShortcuts = resolveBoundShortcuts;
|
|
25319
26964
|
exports.resolveDepositBounds = resolveDepositBounds;
|
|
25320
26965
|
exports.selectVisiblePools = selectVisiblePools;
|
|
26966
|
+
exports.submissionEvents = submissionEvents;
|
|
25321
26967
|
exports.trancheApyBounds = trancheApyBounds;
|
|
25322
26968
|
exports.trancheHasCapacity = trancheHasCapacity;
|
|
25323
26969
|
exports.trancheRiskRank = trancheRiskRank;
|