@kasufinance/kasu-sdk 2.12.0 → 2.13.1
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 +26 -1
- package/dist/bundle.cjs.js +974 -83
- package/dist/bundle.esm.js +971 -85
- package/dist/domain/index.d.ts +2 -0
- package/dist/domain/index.js +1 -0
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/pool-tvl.d.ts +62 -0
- package/dist/domain/pool-tvl.js +22 -0
- package/dist/domain/pool-tvl.js.map +1 -0
- package/dist/domain/requests.d.ts +11 -10
- package/dist/facade/chain-configs.js +24 -0
- package/dist/facade/chain-configs.js.map +1 -1
- package/dist/facade/kasu.d.ts +2 -0
- package/dist/facade/kasu.js +13 -5
- package/dist/facade/kasu.js.map +1 -1
- package/dist/facade/strategies.d.ts +20 -0
- package/dist/facade/strategies.js +22 -0
- package/dist/facade/strategies.js.map +1 -1
- package/dist/facade/types.d.ts +20 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/kasu-sdk.d.ts +9 -1
- package/dist/kasu-sdk.js +2 -2
- package/dist/kasu-sdk.js.map +1 -1
- package/dist/sdk-config.d.ts +20 -0
- package/dist/sdk-config.js +3 -1
- package/dist/sdk-config.js.map +1 -1
- package/dist/services/DataService/data-service.d.ts +81 -1
- package/dist/services/DataService/data-service.js +211 -6
- package/dist/services/DataService/data-service.js.map +1 -1
- package/dist/services/DataService/tvl-history.d.ts +296 -0
- package/dist/services/DataService/tvl-history.js +558 -0
- package/dist/services/DataService/tvl-history.js.map +1 -0
- package/dist/services/Portfolio/yield-history.d.ts +1 -16
- package/dist/services/Portfolio/yield-history.js +2 -29
- package/dist/services/Portfolio/yield-history.js.map +1 -1
- package/dist/services/UserLending/subgraph-types.d.ts +2 -8
- package/dist/services/UserLending/types.d.ts +10 -0
- package/dist/services/UserLending/user-lending.d.ts +14 -0
- package/dist/services/shared.d.ts +16 -0
- package/dist/services/shared.js +30 -0
- package/dist/services/shared.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/index.ts +3 -0
- package/src/domain/pool-tvl.ts +66 -0
- package/src/domain/requests.ts +37 -23
- package/src/facade/chain-configs.ts +26 -0
- package/src/facade/kasu.ts +20 -2
- package/src/facade/strategies.ts +26 -0
- package/src/facade/types.ts +20 -0
- package/src/index.ts +9 -0
- package/src/kasu-sdk.ts +19 -2
- package/src/sdk-config.ts +22 -0
- package/src/services/DataService/data-service.ts +296 -6
- package/src/services/DataService/tvl-history.ts +853 -0
- package/src/services/Portfolio/yield-history.ts +2 -35
- package/src/services/UserLending/queries.ts +2 -7
- package/src/services/UserLending/subgraph-types.ts +2 -8
- package/src/services/UserLending/types.ts +10 -0
- package/src/services/UserLending/user-lending.ts +98 -40
- package/src/services/shared.ts +38 -1
|
@@ -537,38 +537,5 @@ export function buildYieldHistory(
|
|
|
537
537
|
);
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
* deployment's subgraph does not have. A network error, a timeout or an
|
|
543
|
-
* indexing error is not one.
|
|
544
|
-
*/
|
|
545
|
-
export function isMissingEntityError(error: unknown): boolean {
|
|
546
|
-
if (typeof error !== 'object' || error === null) return false;
|
|
547
|
-
const response = (error as { response?: { errors?: unknown } }).response;
|
|
548
|
-
const errors = response?.errors;
|
|
549
|
-
if (!Array.isArray(errors) || errors.length === 0) return false;
|
|
550
|
-
return errors.every((e: unknown) => {
|
|
551
|
-
const message = (e as { message?: unknown } | null)?.message;
|
|
552
|
-
return (
|
|
553
|
-
typeof message === 'string' &&
|
|
554
|
-
/has no field|cannot query field|unknown (type|field|argument)|no such (type|field)/i.test(
|
|
555
|
-
message,
|
|
556
|
-
)
|
|
557
|
-
);
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* The only failure `getYieldHistory` turns into `[]`: a missing-schema error
|
|
563
|
-
* on a LITE deployment, whose subgraph may predate the entities. On a full
|
|
564
|
-
* deployment the entities are part of the schema, so the same error means a
|
|
565
|
-
* broken query or subgraph and THROWS — an empty chart there would hide it.
|
|
566
|
-
* Everything else (network, timeout, indexing) throws on every deployment, so
|
|
567
|
-
* a consumer can tell "no history here" from "history unavailable".
|
|
568
|
-
*/
|
|
569
|
-
export function isEmptyHistoryError(
|
|
570
|
-
error: unknown,
|
|
571
|
-
liteDeployment: boolean,
|
|
572
|
-
): boolean {
|
|
573
|
-
return liteDeployment && isMissingEntityError(error);
|
|
574
|
-
}
|
|
540
|
+
// Shared with `getPoolTvlHistory`: one Lite-vs-full rule for every history.
|
|
541
|
+
export { isEmptyHistoryError, isMissingEntityError } from '../shared';
|
|
@@ -160,14 +160,13 @@ export const currentEpochFtdAmountQuery = gql`
|
|
|
160
160
|
user: $userId
|
|
161
161
|
lendingPool_in: $poolIds
|
|
162
162
|
epochId: $currentEpoch
|
|
163
|
+
type: DepositRequest
|
|
163
164
|
}
|
|
164
165
|
) {
|
|
165
166
|
fixedTermConfigId
|
|
166
167
|
amountRequested
|
|
167
168
|
tranche {
|
|
168
169
|
id
|
|
169
|
-
balance
|
|
170
|
-
shares
|
|
171
170
|
}
|
|
172
171
|
}
|
|
173
172
|
}
|
|
@@ -183,11 +182,7 @@ export const currentFtdBalanceQuery = gql`
|
|
|
183
182
|
configId
|
|
184
183
|
lendingPoolTranche {
|
|
185
184
|
id
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
lendingPool {
|
|
189
|
-
balance
|
|
190
|
-
tranches {
|
|
185
|
+
balance
|
|
191
186
|
shares
|
|
192
187
|
}
|
|
193
188
|
}
|
|
@@ -77,8 +77,6 @@ export interface CurrentEpochFtdAmountSubgraph {
|
|
|
77
77
|
amountRequested: string;
|
|
78
78
|
tranche: {
|
|
79
79
|
id: string;
|
|
80
|
-
balance: string;
|
|
81
|
-
shares: string;
|
|
82
80
|
};
|
|
83
81
|
}[];
|
|
84
82
|
}
|
|
@@ -161,13 +159,9 @@ export interface CurrentFtdBalanceSubgraph {
|
|
|
161
159
|
configId: string;
|
|
162
160
|
lendingPoolTranche: {
|
|
163
161
|
id: string;
|
|
164
|
-
|
|
165
|
-
};
|
|
166
|
-
lendingPool: {
|
|
167
|
-
balance: string;
|
|
168
|
-
tranches: {
|
|
162
|
+
balance: string;
|
|
169
163
|
shares: string;
|
|
170
|
-
}
|
|
164
|
+
};
|
|
171
165
|
};
|
|
172
166
|
}[];
|
|
173
167
|
}
|
|
@@ -39,8 +39,18 @@ export interface UserRequest {
|
|
|
39
39
|
requestType: 'Deposit' | 'Withdrawal';
|
|
40
40
|
trancheId: string;
|
|
41
41
|
trancheName: string;
|
|
42
|
+
/**
|
|
43
|
+
* The amount still OUTSTANDING on the request, in assets (the
|
|
44
|
+
* deployment's stable token, display units) — not the amount first asked
|
|
45
|
+
* for: the subgraph takes accepted and rejected amounts off it, and a
|
|
46
|
+
* cancellation zeroes it. A deposit's figure is the asset amount as the
|
|
47
|
+
* subgraph holds it (top-ups included); a withdrawal's is its remaining
|
|
48
|
+
* tranche shares at the tranche's current share price.
|
|
49
|
+
*/
|
|
42
50
|
requestedAmount: string;
|
|
51
|
+
/** Assets accepted so far — deposited, or paid out for a withdrawal. */
|
|
43
52
|
acceptedAmount: string;
|
|
53
|
+
/** Assets rejected and returned — deposits only; 0 for a withdrawal. */
|
|
44
54
|
rejectedAmount: string;
|
|
45
55
|
timestamp: EpochTimeStamp;
|
|
46
56
|
status: UserRequestStatus;
|
|
@@ -8,13 +8,7 @@ import {
|
|
|
8
8
|
ethers,
|
|
9
9
|
Signer,
|
|
10
10
|
} from 'ethers';
|
|
11
|
-
import {
|
|
12
|
-
defaultAbiCoder,
|
|
13
|
-
formatEther,
|
|
14
|
-
formatUnits,
|
|
15
|
-
parseEther,
|
|
16
|
-
parseUnits,
|
|
17
|
-
} from 'ethers/lib/utils';
|
|
11
|
+
import { defaultAbiCoder, formatUnits, parseUnits } from 'ethers/lib/utils';
|
|
18
12
|
import { GraphQLClient } from 'graphql-request';
|
|
19
13
|
|
|
20
14
|
import {
|
|
@@ -70,6 +64,20 @@ import {
|
|
|
70
64
|
UserTrancheBalance,
|
|
71
65
|
} from './types';
|
|
72
66
|
|
|
67
|
+
/** Thrown when the subgraph reports a request type with no known unit. */
|
|
68
|
+
export const UNKNOWN_REQUEST_TYPE_MESSAGE =
|
|
69
|
+
'UserLending: unknown request type, its amount unit is unknown';
|
|
70
|
+
|
|
71
|
+
const KNOWN_REQUEST_TYPES: readonly string[] = [
|
|
72
|
+
'DepositRequest',
|
|
73
|
+
'WithdrawalRequest',
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const isKnownRequestType = (
|
|
77
|
+
type: unknown,
|
|
78
|
+
): type is UserRequestsSubgraph['userRequests'][number]['type'] =>
|
|
79
|
+
typeof type === 'string' && KNOWN_REQUEST_TYPES.includes(type);
|
|
80
|
+
|
|
73
81
|
export class UserLending {
|
|
74
82
|
private readonly _graph: GraphQLClient;
|
|
75
83
|
|
|
@@ -406,6 +414,17 @@ export class UserLending {
|
|
|
406
414
|
const retn: UserRequest[] = [];
|
|
407
415
|
|
|
408
416
|
for (const userRequest of subgraphResult.userRequests) {
|
|
417
|
+
// A request type this SDK does not know has no known amount unit
|
|
418
|
+
// (the schema also declares types no live deployment emits yet).
|
|
419
|
+
// Skip that ONE row before anything is priced or read on chain,
|
|
420
|
+
// rather than failing — and blanking — the lender's whole list.
|
|
421
|
+
if (!isKnownRequestType(userRequest.type)) {
|
|
422
|
+
console.error(
|
|
423
|
+
`${UNKNOWN_REQUEST_TYPE_MESSAGE}: ${String(userRequest.type)} (request ${userRequest.id}); row skipped`,
|
|
424
|
+
);
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
409
428
|
const directusPool = directusResult.find(
|
|
410
429
|
({ id }) => id === userRequest.lendingPool.id,
|
|
411
430
|
);
|
|
@@ -552,13 +571,7 @@ export class UserLending {
|
|
|
552
571
|
: 'Withdrawal',
|
|
553
572
|
trancheName: trancheName,
|
|
554
573
|
trancheId: userRequest.tranche.id,
|
|
555
|
-
requestedAmount:
|
|
556
|
-
? userRequest.amountRequested
|
|
557
|
-
: this.convertSharesToAssets(
|
|
558
|
-
userRequest.amountRequested,
|
|
559
|
-
userRequest.tranche.balance,
|
|
560
|
-
userRequest.tranche.shares,
|
|
561
|
-
).toString(),
|
|
574
|
+
requestedAmount: this.requestedAmountInAssets(userRequest),
|
|
562
575
|
acceptedAmount: userRequest.amountAccepted,
|
|
563
576
|
rejectedAmount: userRequest.amountRejected,
|
|
564
577
|
status: userRequest.status,
|
|
@@ -606,15 +619,16 @@ export class UserLending {
|
|
|
606
619
|
const trancheID =
|
|
607
620
|
userFTD.lendingPoolTrancheFixedTermConfig.lendingPoolTranche.id.toLowerCase();
|
|
608
621
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
622
|
+
// The lock holds shares of ONE tranche, so they are worth that
|
|
623
|
+
// tranche's share price — not the pool's average across tranches,
|
|
624
|
+
// which differs whenever the tranches have earned differently.
|
|
625
|
+
const lockTranche =
|
|
626
|
+
userFTD.lendingPoolTrancheFixedTermConfig.lendingPoolTranche;
|
|
613
627
|
|
|
614
628
|
const amount = this.convertSharesToAssets(
|
|
615
629
|
userFTD.trancheShares,
|
|
616
|
-
|
|
617
|
-
|
|
630
|
+
lockTranche.balance,
|
|
631
|
+
lockTranche.shares,
|
|
618
632
|
);
|
|
619
633
|
|
|
620
634
|
const item = trancheMap.get(trancheID) ?? [];
|
|
@@ -643,33 +657,43 @@ export class UserLending {
|
|
|
643
657
|
currentEpoch: parseFloat(currentEpoch),
|
|
644
658
|
});
|
|
645
659
|
|
|
646
|
-
|
|
660
|
+
// Deposit requests only (the query filters on type): a deposit's
|
|
661
|
+
// `amountRequested` is already an ASSET amount, so it is summed as it
|
|
662
|
+
// is — never put through a share price. Summed, not assigned: a
|
|
663
|
+
// cancelled request and its replacement share a tranche and a config
|
|
664
|
+
// in one epoch, and the cancelled one's 0 must not overwrite the live
|
|
665
|
+
// amount.
|
|
666
|
+
const totals = new Map<string, Map<number, BigNumber>>();
|
|
647
667
|
|
|
648
668
|
for (const userRequest of currentEpochFtdAmountRes.userRequests) {
|
|
649
669
|
const ftdID = parseFloat(userRequest.fixedTermConfigId);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
670
|
+
if (!Number.isInteger(ftdID) || ftdID < 0) continue;
|
|
671
|
+
|
|
672
|
+
const perConfig =
|
|
673
|
+
totals.get(userRequest.tranche.id) ??
|
|
674
|
+
new Map<number, BigNumber>();
|
|
675
|
+
perConfig.set(
|
|
676
|
+
ftdID,
|
|
677
|
+
(perConfig.get(ftdID) ?? BigNumber.from(0)).add(
|
|
678
|
+
parseUnits(userRequest.amountRequested),
|
|
679
|
+
),
|
|
657
680
|
);
|
|
681
|
+
totals.set(userRequest.tranche.id, perConfig);
|
|
682
|
+
}
|
|
658
683
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
currentEpochFtdAmounts.set(userRequest.tranche.id, clonedItem);
|
|
684
|
+
// A slot whose total is zero is left out — `'0.0'` is a truthy
|
|
685
|
+
// string, and a caller testing the slot would read it as an amount.
|
|
686
|
+
// A tranche with no non-zero slot is left out entirely.
|
|
687
|
+
const currentEpochFtdAmounts = new Map<string, string[]>();
|
|
665
688
|
|
|
666
|
-
|
|
667
|
-
}
|
|
689
|
+
for (const [trancheId, perConfig] of totals) {
|
|
668
690
|
const ftdAmounts: string[] = [];
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
691
|
+
for (const [ftdID, total] of perConfig) {
|
|
692
|
+
if (total.gt(0)) ftdAmounts[ftdID] = formatUnits(total);
|
|
693
|
+
}
|
|
694
|
+
if (ftdAmounts.length) {
|
|
695
|
+
currentEpochFtdAmounts.set(trancheId, ftdAmounts);
|
|
696
|
+
}
|
|
673
697
|
}
|
|
674
698
|
|
|
675
699
|
return currentEpochFtdAmounts;
|
|
@@ -708,6 +732,40 @@ export class UserLending {
|
|
|
708
732
|
return amountMap;
|
|
709
733
|
}
|
|
710
734
|
|
|
735
|
+
/**
|
|
736
|
+
* A request's outstanding amount in ASSETS (the deployment's stable token).
|
|
737
|
+
*
|
|
738
|
+
* The subgraph's `UserRequest.amountRequested` is denominated by request
|
|
739
|
+
* type. A DEPOSIT request holds assets: the deposit NFT's `assetAmount`,
|
|
740
|
+
* top-ups added, accepted and rejected amounts taken off. A WITHDRAWAL
|
|
741
|
+
* request holds tranche SHARES: the withdrawal NFT's `sharesAmount`,
|
|
742
|
+
* accepted shares taken off. So only a withdrawal is converted, at the
|
|
743
|
+
* tranche's current share price; a deposit passes through unchanged —
|
|
744
|
+
* pricing it as shares overstated it by the share price.
|
|
745
|
+
*/
|
|
746
|
+
private requestedAmountInAssets(
|
|
747
|
+
userRequest: UserRequestsSubgraph['userRequests'][number],
|
|
748
|
+
): string {
|
|
749
|
+
switch (userRequest.type) {
|
|
750
|
+
case 'DepositRequest':
|
|
751
|
+
return userRequest.amountRequested;
|
|
752
|
+
case 'WithdrawalRequest':
|
|
753
|
+
return this.convertSharesToAssets(
|
|
754
|
+
userRequest.amountRequested,
|
|
755
|
+
userRequest.tranche.balance,
|
|
756
|
+
userRequest.tranche.shares,
|
|
757
|
+
);
|
|
758
|
+
default: {
|
|
759
|
+
// Fail closed: a type this SDK does not know has no known
|
|
760
|
+
// unit, and pricing it either way would print a wrong figure.
|
|
761
|
+
const unknownType: string = userRequest.type;
|
|
762
|
+
throw new Error(
|
|
763
|
+
`${UNKNOWN_REQUEST_TYPE_MESSAGE}: ${unknownType}`,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
711
769
|
convertSharesToAssets(
|
|
712
770
|
sharesAmount: string,
|
|
713
771
|
totalAssets: string,
|
package/src/services/shared.ts
CHANGED
|
@@ -8,4 +8,41 @@ export function filterArray<T>(array: T[], id_in?: string[]): T[] {
|
|
|
8
8
|
return id_in.includes(id);
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A GraphQL VALIDATION error naming an entity, field or argument this
|
|
15
|
+
* deployment's subgraph does not have. A network error, a timeout or an
|
|
16
|
+
* indexing error is not one.
|
|
17
|
+
*/
|
|
18
|
+
export function isMissingEntityError(error: unknown): boolean {
|
|
19
|
+
if (typeof error !== 'object' || error === null) return false;
|
|
20
|
+
const response = (error as { response?: { errors?: unknown } }).response;
|
|
21
|
+
const errors = response?.errors;
|
|
22
|
+
if (!Array.isArray(errors) || errors.length === 0) return false;
|
|
23
|
+
return errors.every((e: unknown) => {
|
|
24
|
+
const message = (e as { message?: unknown } | null)?.message;
|
|
25
|
+
return (
|
|
26
|
+
typeof message === 'string' &&
|
|
27
|
+
/has no field|cannot query field|unknown (type|field|argument)|no such (type|field)/i.test(
|
|
28
|
+
message,
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The only failure a history read (`getYieldHistory`, `getPoolTvlHistory`)
|
|
36
|
+
* turns into `[]`: a missing-schema error
|
|
37
|
+
* on a LITE deployment, whose subgraph may predate the entities. On a full
|
|
38
|
+
* deployment the entities are part of the schema, so the same error means a
|
|
39
|
+
* broken query or subgraph and THROWS — an empty chart there would hide it.
|
|
40
|
+
* Everything else (network, timeout, indexing) throws on every deployment, so
|
|
41
|
+
* a consumer can tell "no history here" from "history unavailable".
|
|
42
|
+
*/
|
|
43
|
+
export function isEmptyHistoryError(
|
|
44
|
+
error: unknown,
|
|
45
|
+
liteDeployment: boolean,
|
|
46
|
+
): boolean {
|
|
47
|
+
return liteDeployment && isMissingEntityError(error);
|
|
48
|
+
}
|