@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.
Files changed (62) hide show
  1. package/README.md +26 -1
  2. package/dist/bundle.cjs.js +974 -83
  3. package/dist/bundle.esm.js +971 -85
  4. package/dist/domain/index.d.ts +2 -0
  5. package/dist/domain/index.js +1 -0
  6. package/dist/domain/index.js.map +1 -1
  7. package/dist/domain/pool-tvl.d.ts +62 -0
  8. package/dist/domain/pool-tvl.js +22 -0
  9. package/dist/domain/pool-tvl.js.map +1 -0
  10. package/dist/domain/requests.d.ts +11 -10
  11. package/dist/facade/chain-configs.js +24 -0
  12. package/dist/facade/chain-configs.js.map +1 -1
  13. package/dist/facade/kasu.d.ts +2 -0
  14. package/dist/facade/kasu.js +13 -5
  15. package/dist/facade/kasu.js.map +1 -1
  16. package/dist/facade/strategies.d.ts +20 -0
  17. package/dist/facade/strategies.js +22 -0
  18. package/dist/facade/strategies.js.map +1 -1
  19. package/dist/facade/types.d.ts +20 -0
  20. package/dist/index.d.ts +4 -0
  21. package/dist/index.js +2 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/kasu-sdk.d.ts +9 -1
  24. package/dist/kasu-sdk.js +2 -2
  25. package/dist/kasu-sdk.js.map +1 -1
  26. package/dist/sdk-config.d.ts +20 -0
  27. package/dist/sdk-config.js +3 -1
  28. package/dist/sdk-config.js.map +1 -1
  29. package/dist/services/DataService/data-service.d.ts +81 -1
  30. package/dist/services/DataService/data-service.js +211 -6
  31. package/dist/services/DataService/data-service.js.map +1 -1
  32. package/dist/services/DataService/tvl-history.d.ts +296 -0
  33. package/dist/services/DataService/tvl-history.js +558 -0
  34. package/dist/services/DataService/tvl-history.js.map +1 -0
  35. package/dist/services/Portfolio/yield-history.d.ts +1 -16
  36. package/dist/services/Portfolio/yield-history.js +2 -29
  37. package/dist/services/Portfolio/yield-history.js.map +1 -1
  38. package/dist/services/UserLending/subgraph-types.d.ts +2 -8
  39. package/dist/services/UserLending/types.d.ts +10 -0
  40. package/dist/services/UserLending/user-lending.d.ts +14 -0
  41. package/dist/services/shared.d.ts +16 -0
  42. package/dist/services/shared.js +30 -0
  43. package/dist/services/shared.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/domain/index.ts +3 -0
  46. package/src/domain/pool-tvl.ts +66 -0
  47. package/src/domain/requests.ts +37 -23
  48. package/src/facade/chain-configs.ts +26 -0
  49. package/src/facade/kasu.ts +20 -2
  50. package/src/facade/strategies.ts +26 -0
  51. package/src/facade/types.ts +20 -0
  52. package/src/index.ts +9 -0
  53. package/src/kasu-sdk.ts +19 -2
  54. package/src/sdk-config.ts +22 -0
  55. package/src/services/DataService/data-service.ts +296 -6
  56. package/src/services/DataService/tvl-history.ts +853 -0
  57. package/src/services/Portfolio/yield-history.ts +2 -35
  58. package/src/services/UserLending/queries.ts +2 -7
  59. package/src/services/UserLending/subgraph-types.ts +2 -8
  60. package/src/services/UserLending/types.ts +10 -0
  61. package/src/services/UserLending/user-lending.ts +98 -40
  62. package/src/services/shared.ts +38 -1
@@ -537,38 +537,5 @@ export function buildYieldHistory(
537
537
  );
538
538
  }
539
539
 
540
- /**
541
- * A GraphQL VALIDATION error naming an entity, field or argument this
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: parseUnits(userRequest.tranche.shares).isZero()
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
- const totalSupply = userFTD.lendingPool.tranches.reduce(
610
- (total, cur) => total.add(parseEther(cur.shares)),
611
- BigNumber.from(0),
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
- userFTD.lendingPool.balance,
617
- formatEther(totalSupply),
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
- const currentEpochFtdAmounts = new Map<string, string[]>();
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
- const item = currentEpochFtdAmounts.get(userRequest.tranche.id);
652
-
653
- const amountRequested = this.convertSharesToAssets(
654
- userRequest.amountRequested,
655
- userRequest.tranche.balance,
656
- userRequest.tranche.shares,
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
- if (item) {
660
- const clonedItem = [...item];
661
-
662
- clonedItem[ftdID] = amountRequested;
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
- continue;
667
- }
689
+ for (const [trancheId, perConfig] of totals) {
668
690
  const ftdAmounts: string[] = [];
669
-
670
- ftdAmounts[ftdID] = amountRequested;
671
-
672
- currentEpochFtdAmounts.set(userRequest.tranche.id, ftdAmounts);
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,
@@ -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
+ }