@kasufinance/kasu-sdk 1.0.3 → 2.1.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.
Files changed (186) hide show
  1. package/.claude/settings.local.json +15 -0
  2. package/CLAUDE.md +410 -0
  3. package/README.md +179 -12
  4. package/dist/bundle.cjs.js +749 -99
  5. package/dist/bundle.esm.js +745 -101
  6. package/dist/contracts/IClearingCoordinatorAbi.js +1 -0
  7. package/dist/contracts/IClearingCoordinatorAbi.js.map +1 -0
  8. package/dist/contracts/IERC20MetadataAbi.js +1 -0
  9. package/dist/contracts/IERC20MetadataAbi.js.map +1 -0
  10. package/dist/contracts/IFeeManagerAbi.js +1 -0
  11. package/dist/contracts/IFeeManagerAbi.js.map +1 -0
  12. package/dist/contracts/IKSULockingAbi.js +1 -0
  13. package/dist/contracts/IKSULockingAbi.js.map +1 -0
  14. package/dist/contracts/IKasuAllowListAbi.js +1 -0
  15. package/dist/contracts/IKasuAllowListAbi.js.map +1 -0
  16. package/dist/contracts/IKasuControllerAbi.js +1 -0
  17. package/dist/contracts/IKasuControllerAbi.js.map +1 -0
  18. package/dist/contracts/IKasuNFTsAbi.js +1 -0
  19. package/dist/contracts/IKasuNFTsAbi.js.map +1 -0
  20. package/dist/contracts/IKsuPriceAbi.js +1 -0
  21. package/dist/contracts/IKsuPriceAbi.js.map +1 -0
  22. package/dist/contracts/ILendingPoolAbi.js +1 -0
  23. package/dist/contracts/ILendingPoolAbi.js.map +1 -0
  24. package/dist/contracts/ILendingPoolFactoryAbi.js +1 -0
  25. package/dist/contracts/ILendingPoolFactoryAbi.js.map +1 -0
  26. package/dist/contracts/ILendingPoolManagerAbi.js +1 -0
  27. package/dist/contracts/ILendingPoolManagerAbi.js.map +1 -0
  28. package/dist/contracts/ILendingPoolTrancheAbi.js +1 -0
  29. package/dist/contracts/ILendingPoolTrancheAbi.js.map +1 -0
  30. package/dist/contracts/ISystemVariablesAbi.js +1 -0
  31. package/dist/contracts/ISystemVariablesAbi.js.map +1 -0
  32. package/dist/contracts/IUserLoyaltyRewardsAbi.js +1 -0
  33. package/dist/contracts/IUserLoyaltyRewardsAbi.js.map +1 -0
  34. package/dist/contracts/IUserManagerAbi.js +1 -0
  35. package/dist/contracts/IUserManagerAbi.js.map +1 -0
  36. package/dist/contracts/KSULockBonusAbi.js +1 -0
  37. package/dist/contracts/KSULockBonusAbi.js.map +1 -0
  38. package/dist/contracts/KasuPoolExternalTVLAbi.js +1 -0
  39. package/dist/contracts/KasuPoolExternalTVLAbi.js.map +1 -0
  40. package/dist/contracts/SwapperAbi.js +1 -0
  41. package/dist/contracts/SwapperAbi.js.map +1 -0
  42. package/dist/contracts/common.js +1 -0
  43. package/dist/contracts/common.js.map +1 -0
  44. package/dist/contracts/factories/IClearingCoordinatorAbi__factory.js +1 -0
  45. package/dist/contracts/factories/IClearingCoordinatorAbi__factory.js.map +1 -0
  46. package/dist/contracts/factories/IERC20MetadataAbi__factory.js +1 -0
  47. package/dist/contracts/factories/IERC20MetadataAbi__factory.js.map +1 -0
  48. package/dist/contracts/factories/IFeeManagerAbi__factory.js +1 -0
  49. package/dist/contracts/factories/IFeeManagerAbi__factory.js.map +1 -0
  50. package/dist/contracts/factories/IKSULockingAbi__factory.js +1 -0
  51. package/dist/contracts/factories/IKSULockingAbi__factory.js.map +1 -0
  52. package/dist/contracts/factories/IKasuAllowListAbi__factory.js +1 -0
  53. package/dist/contracts/factories/IKasuAllowListAbi__factory.js.map +1 -0
  54. package/dist/contracts/factories/IKasuControllerAbi__factory.js +1 -0
  55. package/dist/contracts/factories/IKasuControllerAbi__factory.js.map +1 -0
  56. package/dist/contracts/factories/IKasuNFTsAbi__factory.js +1 -0
  57. package/dist/contracts/factories/IKasuNFTsAbi__factory.js.map +1 -0
  58. package/dist/contracts/factories/IKsuPriceAbi__factory.js +1 -0
  59. package/dist/contracts/factories/IKsuPriceAbi__factory.js.map +1 -0
  60. package/dist/contracts/factories/ILendingPoolAbi__factory.js +1 -0
  61. package/dist/contracts/factories/ILendingPoolAbi__factory.js.map +1 -0
  62. package/dist/contracts/factories/ILendingPoolFactoryAbi__factory.js +1 -0
  63. package/dist/contracts/factories/ILendingPoolFactoryAbi__factory.js.map +1 -0
  64. package/dist/contracts/factories/ILendingPoolManagerAbi__factory.js +1 -0
  65. package/dist/contracts/factories/ILendingPoolManagerAbi__factory.js.map +1 -0
  66. package/dist/contracts/factories/ILendingPoolTrancheAbi__factory.js +1 -0
  67. package/dist/contracts/factories/ILendingPoolTrancheAbi__factory.js.map +1 -0
  68. package/dist/contracts/factories/ISystemVariablesAbi__factory.js +1 -0
  69. package/dist/contracts/factories/ISystemVariablesAbi__factory.js.map +1 -0
  70. package/dist/contracts/factories/IUserLoyaltyRewardsAbi__factory.js +1 -0
  71. package/dist/contracts/factories/IUserLoyaltyRewardsAbi__factory.js.map +1 -0
  72. package/dist/contracts/factories/IUserManagerAbi__factory.js +1 -0
  73. package/dist/contracts/factories/IUserManagerAbi__factory.js.map +1 -0
  74. package/dist/contracts/factories/KSULockBonusAbi__factory.js +1 -0
  75. package/dist/contracts/factories/KSULockBonusAbi__factory.js.map +1 -0
  76. package/dist/contracts/factories/KasuPoolExternalTVLAbi__factory.js +1 -0
  77. package/dist/contracts/factories/KasuPoolExternalTVLAbi__factory.js.map +1 -0
  78. package/dist/contracts/factories/SwapperAbi__factory.js +1 -0
  79. package/dist/contracts/factories/SwapperAbi__factory.js.map +1 -0
  80. package/dist/contracts/factories/index.js +1 -0
  81. package/dist/contracts/factories/index.js.map +1 -0
  82. package/dist/contracts/index.js +1 -0
  83. package/dist/contracts/index.js.map +1 -0
  84. package/dist/facade/chain-configs.d.ts +11 -0
  85. package/dist/facade/chain-configs.js +84 -0
  86. package/dist/facade/chain-configs.js.map +1 -0
  87. package/dist/facade/deposits.d.ts +56 -0
  88. package/dist/facade/deposits.js +87 -0
  89. package/dist/facade/deposits.js.map +1 -0
  90. package/dist/facade/index.d.ts +6 -0
  91. package/dist/facade/index.js +9 -0
  92. package/dist/facade/index.js.map +1 -0
  93. package/dist/facade/kasu.d.ts +71 -0
  94. package/dist/facade/kasu.js +101 -0
  95. package/dist/facade/kasu.js.map +1 -0
  96. package/dist/facade/strategies.d.ts +37 -0
  97. package/dist/facade/strategies.js +112 -0
  98. package/dist/facade/strategies.js.map +1 -0
  99. package/dist/facade/types.d.ts +127 -0
  100. package/dist/facade/types.js +2 -0
  101. package/dist/facade/types.js.map +1 -0
  102. package/dist/facade/user-portfolio.d.ts +35 -0
  103. package/dist/facade/user-portfolio.js +53 -0
  104. package/dist/facade/user-portfolio.js.map +1 -0
  105. package/dist/index.d.ts +9 -17
  106. package/dist/index.js +10 -16
  107. package/dist/index.js.map +1 -0
  108. package/dist/kasu-sdk.d.ts +17 -0
  109. package/dist/kasu-sdk.js +17 -0
  110. package/dist/kasu-sdk.js.map +1 -0
  111. package/dist/sdk-config.d.ts +28 -4
  112. package/dist/sdk-config.js +9 -6
  113. package/dist/sdk-config.js.map +1 -0
  114. package/dist/services/DataService/data-service.d.ts +9 -2
  115. package/dist/services/DataService/data-service.js +91 -52
  116. package/dist/services/DataService/data-service.js.map +1 -0
  117. package/dist/services/DataService/directus-types.js +1 -0
  118. package/dist/services/DataService/directus-types.js.map +1 -0
  119. package/dist/services/DataService/queries.d.ts +0 -1
  120. package/dist/services/DataService/queries.js +1 -8
  121. package/dist/services/DataService/queries.js.map +1 -0
  122. package/dist/services/DataService/subgraph-types.d.ts +0 -6
  123. package/dist/services/DataService/subgraph-types.js +1 -0
  124. package/dist/services/DataService/subgraph-types.js.map +1 -0
  125. package/dist/services/DataService/types.d.ts +0 -1
  126. package/dist/services/DataService/types.js +1 -0
  127. package/dist/services/DataService/types.js.map +1 -0
  128. package/dist/services/Locking/locking.d.ts +4 -2
  129. package/dist/services/Locking/locking.js +90 -7
  130. package/dist/services/Locking/locking.js.map +1 -0
  131. package/dist/services/Locking/queries.js +1 -0
  132. package/dist/services/Locking/queries.js.map +1 -0
  133. package/dist/services/Locking/types.js +1 -0
  134. package/dist/services/Locking/types.js.map +1 -0
  135. package/dist/services/Portfolio/portfolio.d.ts +41 -2
  136. package/dist/services/Portfolio/portfolio.js +146 -34
  137. package/dist/services/Portfolio/portfolio.js.map +1 -0
  138. package/dist/services/Portfolio/queries.js +1 -0
  139. package/dist/services/Portfolio/queries.js.map +1 -0
  140. package/dist/services/Portfolio/types.js +1 -0
  141. package/dist/services/Portfolio/types.js.map +1 -0
  142. package/dist/services/Swapper/swapper.js +1 -0
  143. package/dist/services/Swapper/swapper.js.map +1 -0
  144. package/dist/services/Swapper/types.js +1 -0
  145. package/dist/services/Swapper/types.js.map +1 -0
  146. package/dist/services/UserLending/helper.js +1 -0
  147. package/dist/services/UserLending/helper.js.map +1 -0
  148. package/dist/services/UserLending/queries.js +1 -0
  149. package/dist/services/UserLending/queries.js.map +1 -0
  150. package/dist/services/UserLending/subgraph-types.js +1 -0
  151. package/dist/services/UserLending/subgraph-types.js.map +1 -0
  152. package/dist/services/UserLending/types.js +1 -0
  153. package/dist/services/UserLending/types.js.map +1 -0
  154. package/dist/services/UserLending/user-lending.js +1 -0
  155. package/dist/services/UserLending/user-lending.js.map +1 -0
  156. package/dist/services/shared.js +1 -0
  157. package/dist/services/shared.js.map +1 -0
  158. package/dist/tests/facade.test.d.ts +1 -0
  159. package/dist/tests/facade.test.js +196 -0
  160. package/dist/tests/facade.test.js.map +1 -0
  161. package/dist/tests/sample.test.js +20 -14
  162. package/dist/tests/sample.test.js.map +1 -0
  163. package/dist/utils/deployment-mode.d.ts +6 -0
  164. package/dist/utils/deployment-mode.js +8 -0
  165. package/dist/utils/deployment-mode.js.map +1 -0
  166. package/package.json +3 -2
  167. package/src/facade/chain-configs.ts +93 -0
  168. package/src/facade/deposits.ts +102 -0
  169. package/src/facade/index.ts +26 -0
  170. package/src/facade/kasu.ts +144 -0
  171. package/src/facade/strategies.ts +119 -0
  172. package/src/facade/types.ts +168 -0
  173. package/src/facade/user-portfolio.ts +73 -0
  174. package/src/index.ts +59 -25
  175. package/src/kasu-sdk.ts +27 -0
  176. package/src/sdk-config.ts +36 -15
  177. package/src/services/DataService/data-service.ts +105 -65
  178. package/src/services/DataService/queries.ts +0 -9
  179. package/src/services/DataService/subgraph-types.ts +0 -7
  180. package/src/services/DataService/types.ts +0 -1
  181. package/src/services/Locking/locking.ts +113 -12
  182. package/src/services/Portfolio/portfolio.ts +276 -50
  183. package/src/tests/facade.test.ts +245 -0
  184. package/src/tests/sample.test.ts +7 -9
  185. package/src/utils/deployment-mode.ts +9 -0
  186. package/tsconfig.json +1 -1
@@ -8,11 +8,29 @@ import {
8
8
  IKasuNFTsAbi__factory,
9
9
  ISystemVariablesAbi,
10
10
  ISystemVariablesAbi__factory,
11
+ IUserLoyaltyRewardsAbi__factory,
11
12
  } from '../../contracts';
12
13
  import { SdkConfig } from '../../sdk-config';
14
+ import { isLiteDeployment } from '../../utils/deployment-mode';
13
15
  import { DataService } from '../DataService/data-service';
16
+ import {
17
+ getAllTrancheConfigurationsQuery,
18
+ getAllTranchesQuery,
19
+ } from '../DataService/queries';
20
+ import {
21
+ TrancheConfigurationSubgraphResult,
22
+ TrancheSubgraphResult,
23
+ } from '../DataService/subgraph-types';
14
24
  import { PoolOverview } from '../DataService/types';
15
25
  import { KSULocking } from '../Locking/locking';
26
+ import {
27
+ getSystemVariablesQuery,
28
+ lockingSummariesQuery,
29
+ } from '../Locking/queries';
30
+ import {
31
+ LockingSummarySubgraphResult,
32
+ SystemVariables,
33
+ } from '../Locking/types';
16
34
  import { UserLending } from '../UserLending/user-lending';
17
35
 
18
36
  import { lendingPortfolioQuery } from './queries';
@@ -33,8 +51,12 @@ export class Portfolio {
33
51
  private readonly _graph: GraphQLClient;
34
52
 
35
53
  private _systemVariablesAbi: ISystemVariablesAbi;
36
- private _kasuNftContract: IKasuNFTsAbi;
54
+ private _userLoyaltyRewardsAbi: ReturnType<
55
+ typeof IUserLoyaltyRewardsAbi__factory.connect
56
+ >;
57
+ private _kasuNftContract: IKasuNFTsAbi | null;
37
58
  readonly _signerOrProvider: Signer | Provider;
59
+ private _isLiteDeployment: boolean;
38
60
 
39
61
  constructor(
40
62
  private _kasuConfig: SdkConfig,
@@ -47,60 +69,35 @@ export class Portfolio {
47
69
  _kasuConfig.contracts.SystemVariables,
48
70
  signerOrProvider,
49
71
  );
50
- this._userLendingService = new UserLending(
51
- _kasuConfig,
72
+ this._userLoyaltyRewardsAbi = IUserLoyaltyRewardsAbi__factory.connect(
73
+ _kasuConfig.contracts.UserLoyaltyRewards,
52
74
  signerOrProvider,
53
75
  );
54
- this._kasuNftContract = IKasuNFTsAbi__factory.connect(
55
- _kasuConfig.contracts.KasuNFTs,
76
+ this._userLendingService = new UserLending(
77
+ _kasuConfig,
56
78
  signerOrProvider,
57
79
  );
80
+ this._isLiteDeployment = isLiteDeployment(_kasuConfig);
81
+ this._kasuNftContract = _kasuConfig.contracts.KasuNFTs
82
+ ? IKasuNFTsAbi__factory.connect(
83
+ _kasuConfig.contracts.KasuNFTs,
84
+ signerOrProvider,
85
+ )
86
+ : null;
58
87
  this._graph = new GraphQLClient(_kasuConfig.subgraphUrl);
59
88
  }
60
89
 
61
- async getPortfolioRewards(userAddress: string): Promise<PortfolioRewards> {
62
- const userLockingData =
63
- await this._lockingService.getUserBonusData(userAddress);
64
- const lockingRewards =
65
- await this._lockingService.getLockingRewards(userAddress);
66
- const ksuLaunchBonus =
67
- await this._lockingService.getUserTotalBonusAmount(userAddress);
68
- return {
69
- bonusYieldEarnings: {
70
- claimableBalance: {
71
- ksuAmount: userLockingData.ksuBonusAndRewards,
72
- },
73
- lifeTime: {
74
- ksuAmount: userLockingData.ksuBonusAndRewardsLifetime,
75
- },
76
- },
77
- protocolFees: {
78
- claimableBalance: {
79
- usdcAmount: lockingRewards.claimableRewards,
80
- },
81
- lifeTime: { usdcAmount: lockingRewards.lifeTimeRewards },
82
- },
83
- ksuLaunchBonus: { lifeTime: { ksuAmount: ksuLaunchBonus } },
84
- };
85
- }
86
-
87
- async getUserNfts(userAddress: string): Promise<number[]> {
88
- const usernfts = await this._kasuNftContract.tokensOfOwner(userAddress);
89
-
90
- return usernfts.map((nft) => nft.toNumber());
91
- }
92
-
93
- async getPortfolioSummary(
94
- userAddress: string,
90
+ private computeYieldMetrics(
95
91
  portfolioLendingPools: PortfolioLendingPool[],
96
- ): Promise<PortfolioSummary> {
97
- const userLockingData =
98
- await this._lockingService.getUserBonusData(userAddress);
92
+ ): {
93
+ totalInvestments: number;
94
+ totalYieldEarned: number;
95
+ weightedApy: number;
96
+ weeklyYieldEarnings: number;
97
+ } {
99
98
  let totalInvestments = 0;
100
-
101
- let weightedApy = 0;
102
99
  let totalYieldEarned = 0;
103
-
100
+ let weightedApy = 0;
104
101
  let weeklyYieldEarnings = 0;
105
102
 
106
103
  for (const pool of portfolioLendingPools) {
@@ -139,6 +136,238 @@ export class Portfolio {
139
136
  }
140
137
  }
141
138
 
139
+ return {
140
+ totalInvestments,
141
+ totalYieldEarned,
142
+ weightedApy,
143
+ weeklyYieldEarnings,
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Calculate aggregate yield metrics for a set of portfolio lending pools.
149
+ * Accepts pre-fetched pool data and returns totals without performing any network calls.
150
+ */
151
+ public calculateYieldMetrics(
152
+ portfolioLendingPools: PortfolioLendingPool[],
153
+ ): {
154
+ totalInvestments: number;
155
+ totalYieldEarned: number;
156
+ weightedApy: number;
157
+ weeklyYieldEarnings: number;
158
+ } {
159
+ return this.computeYieldMetrics(portfolioLendingPools);
160
+ }
161
+
162
+ /**
163
+ * Calculate the user's weekly protocol fee share (USDC) given tranche balances/configs,
164
+ * system variables and locking summary. All inputs are expected to be pre-fetched.
165
+ */
166
+ public calculateWeeklyProtocolFees(params: {
167
+ trancheBalances: TrancheSubgraphResult;
168
+ trancheConfigurations: TrancheConfigurationSubgraphResult;
169
+ systemVariables: SystemVariables;
170
+ lockingSummary: LockingSummarySubgraphResult;
171
+ userRksuAmount: string;
172
+ }): number {
173
+ const {
174
+ trancheBalances,
175
+ trancheConfigurations,
176
+ systemVariables,
177
+ lockingSummary,
178
+ userRksuAmount,
179
+ } = params;
180
+
181
+ const totalRKsuAmount = parseFloat(
182
+ lockingSummary.lockingSummaries[0]?.totalRKsuAmount ?? '0',
183
+ );
184
+ const userRKsuAmount = parseFloat(userRksuAmount || '0');
185
+
186
+ const performanceFee =
187
+ parseFloat(systemVariables.systemVariables.performanceFee) / 100;
188
+ const ecosystemFeeRate =
189
+ parseFloat(systemVariables.systemVariables.ecosystemFeeRate) / 100;
190
+
191
+ if (
192
+ totalRKsuAmount <= 0 ||
193
+ userRKsuAmount <= 0 ||
194
+ performanceFee <= 0 ||
195
+ ecosystemFeeRate <= 0
196
+ ) {
197
+ return 0;
198
+ }
199
+
200
+ let totalWeeklyEcosystemFees = 0;
201
+
202
+ for (const tranche of trancheBalances.lendingPoolTranches) {
203
+ const trancheConfig =
204
+ trancheConfigurations.lendingPoolTrancheConfigurations.find(
205
+ (config) => config.id === tranche.id,
206
+ );
207
+
208
+ if (!trancheConfig) continue;
209
+
210
+ const interestUpdates =
211
+ trancheConfig.lendingPoolTrancheInterestRateUpdates;
212
+
213
+ const interestRate = interestUpdates.length
214
+ ? interestUpdates[0].epochInterestRate
215
+ : trancheConfig.interestRate;
216
+
217
+ totalWeeklyEcosystemFees +=
218
+ parseFloat(tranche.balance) *
219
+ parseFloat(interestRate) *
220
+ performanceFee *
221
+ ecosystemFeeRate;
222
+ }
223
+
224
+ return (
225
+ (totalWeeklyEcosystemFees * userRKsuAmount) / totalRKsuAmount
226
+ );
227
+ }
228
+
229
+ /**
230
+ * Calculate the user's weekly KASU bonus rewards (KASU) based on current deposits,
231
+ * loyalty level, reward rate, and KSU epoch price. Inputs should be pre-fetched.
232
+ */
233
+ public async calculateWeeklyKsuBonusRewards(params: {
234
+ userAddress: string;
235
+ userRksuAmount: string;
236
+ userDepositAmounts: [BigNumber, BigNumber];
237
+ ksuEpochPrice: { price: BigNumber; decimals: number };
238
+ }): Promise<number> {
239
+ const { userAddress, userRksuAmount, userDepositAmounts, ksuEpochPrice } =
240
+ params;
241
+
242
+ const totalUserDeposits = userDepositAmounts[0].add(
243
+ userDepositAmounts[1],
244
+ );
245
+
246
+ if (totalUserDeposits.isZero()) return 0;
247
+
248
+ const rKSUtoUSDCRatio = await this._lockingService.getRKSUvsUSDCRatio(
249
+ userRksuAmount,
250
+ userAddress,
251
+ );
252
+ const { loyaltyLevel } =
253
+ this._lockingService.getLoyaltyLevelAndApyBonusFromRatio(
254
+ rKSUtoUSDCRatio,
255
+ );
256
+
257
+ const loyaltyRewardRate =
258
+ await this._userLoyaltyRewardsAbi.loyaltyEpochRewardRates(
259
+ loyaltyLevel,
260
+ );
261
+
262
+ if (loyaltyRewardRate.isZero() || ksuEpochPrice.price.isZero()) {
263
+ return 0;
264
+ }
265
+
266
+ const reward = totalUserDeposits
267
+ .mul(loyaltyRewardRate)
268
+ .mul(BigNumber.from(10).pow(12))
269
+ .div(ksuEpochPrice.price);
270
+
271
+ return parseFloat(formatEther(reward));
272
+ }
273
+
274
+ async getPortfolioRewards(userAddress: string): Promise<PortfolioRewards> {
275
+ const userLockingData =
276
+ await this._lockingService.getUserBonusData(userAddress);
277
+ const lockingRewards =
278
+ await this._lockingService.getLockingRewards(userAddress);
279
+ const ksuLaunchBonus =
280
+ await this._lockingService.getUserTotalBonusAmount(userAddress);
281
+ return {
282
+ bonusYieldEarnings: {
283
+ claimableBalance: {
284
+ ksuAmount: userLockingData.ksuBonusAndRewards,
285
+ },
286
+ lifeTime: {
287
+ ksuAmount: userLockingData.ksuBonusAndRewardsLifetime,
288
+ },
289
+ },
290
+ protocolFees: {
291
+ claimableBalance: {
292
+ usdcAmount: lockingRewards.claimableRewards,
293
+ },
294
+ lifeTime: { usdcAmount: lockingRewards.lifeTimeRewards },
295
+ },
296
+ ksuLaunchBonus: { lifeTime: { ksuAmount: ksuLaunchBonus } },
297
+ };
298
+ }
299
+
300
+ async getUserNfts(userAddress: string): Promise<number[]> {
301
+ // NFTs are not available on Lite deployments
302
+ if (this._isLiteDeployment || !this._kasuNftContract) {
303
+ return [];
304
+ }
305
+
306
+ const usernfts = await this._kasuNftContract.tokensOfOwner(userAddress);
307
+
308
+ return usernfts.map((nft) => nft.toNumber());
309
+ }
310
+
311
+ async getPortfolioSummary(
312
+ userAddress: string,
313
+ portfolioLendingPools: PortfolioLendingPool[],
314
+ currentEpoch: string,
315
+ ): Promise<PortfolioSummary> {
316
+ const epochId = currentEpoch.toString();
317
+ const [
318
+ userLockingData,
319
+ userRksuAmount,
320
+ lockingSummary,
321
+ systemVariables,
322
+ trancheBalances,
323
+ trancheConfigurations,
324
+ userDepositAmounts,
325
+ ksuEpochPrice,
326
+ ] = await Promise.all([
327
+ this._lockingService.getUserBonusData(userAddress),
328
+ this._lockingService.getUserEarnedrKsu(userAddress),
329
+ this._graph.request<LockingSummarySubgraphResult>(
330
+ lockingSummariesQuery,
331
+ ),
332
+ this._graph.request<SystemVariables>(getSystemVariablesQuery),
333
+ this._graph.request<TrancheSubgraphResult>(getAllTranchesQuery, {
334
+ unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
335
+ }),
336
+ this._graph.request<TrancheConfigurationSubgraphResult>(
337
+ getAllTrancheConfigurationsQuery,
338
+ {
339
+ unusedPools: this._kasuConfig.UNUSED_LENDING_POOL_IDS,
340
+ epochId,
341
+ },
342
+ ),
343
+ this._userLendingService.getUserTotalPendingAndActiveDepositedAmountForCurrentEpoch(
344
+ userAddress,
345
+ ),
346
+ this._lockingService.getKasuEpochTokenPrice(),
347
+ ]);
348
+ const {
349
+ totalInvestments,
350
+ totalYieldEarned,
351
+ weightedApy,
352
+ weeklyYieldEarnings,
353
+ } = this.computeYieldMetrics(portfolioLendingPools);
354
+
355
+ const weeklyProtocolFeesEarned = this.calculateWeeklyProtocolFees({
356
+ trancheBalances,
357
+ trancheConfigurations,
358
+ systemVariables,
359
+ lockingSummary,
360
+ userRksuAmount,
361
+ });
362
+
363
+ const weeklyKsuBonusRewards =
364
+ await this.calculateWeeklyKsuBonusRewards({
365
+ userAddress,
366
+ userRksuAmount,
367
+ userDepositAmounts,
368
+ ksuEpochPrice,
369
+ });
370
+
142
371
  return {
143
372
  current: {
144
373
  totalKsuLocked: userLockingData.totalLockedAmount,
@@ -153,8 +382,8 @@ export class Portfolio {
153
382
  },
154
383
  weekly: {
155
384
  yieldEarnings: weeklyYieldEarnings.toString(),
156
- protocolFeesEarned: '0',
157
- ksuBonusRewards: '0',
385
+ protocolFeesEarned: weeklyProtocolFeesEarned.toString(),
386
+ ksuBonusRewards: weeklyKsuBonusRewards.toString(),
158
387
  },
159
388
  lifetime: {
160
389
  yieldEarnings: totalYieldEarned.toString(),
@@ -269,9 +498,6 @@ export class Portfolio {
269
498
  ({ poolId }) => poolId === poolOverview.id,
270
499
  );
271
500
  if (!userPoolBalance) {
272
- console.warn(
273
- `Could not find user pool balance for ${poolOverview.id}`,
274
- );
275
501
  continue;
276
502
  }
277
503
  totalInvestments = userPoolBalance.balance.add(totalInvestments);
@@ -0,0 +1,245 @@
1
+ import { CHAIN_CONFIGS } from '../facade/chain-configs';
2
+ import { DepositsFacade } from '../facade/deposits';
3
+ import { Kasu } from '../facade/kasu';
4
+ import { StrategiesFacade } from '../facade/strategies';
5
+ import { PortfolioFacade } from '../facade/user-portfolio';
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Chain configs
9
+ // ---------------------------------------------------------------------------
10
+
11
+ describe('CHAIN_CONFIGS', () => {
12
+ it('should have base, xdc, and plume entries', () => {
13
+ expect(CHAIN_CONFIGS).toHaveProperty('base');
14
+ expect(CHAIN_CONFIGS).toHaveProperty('xdc');
15
+ expect(CHAIN_CONFIGS).toHaveProperty('plume');
16
+ });
17
+
18
+ it('base should be a full deployment', () => {
19
+ const base = CHAIN_CONFIGS.base;
20
+ expect(base.chainId).toBe(8453);
21
+ expect(base.isLiteDeployment).toBe(false);
22
+ expect(base.contracts.KSUToken).toBeDefined();
23
+ expect(base.contracts.KasuNFTs).toBeDefined();
24
+ });
25
+
26
+ it('xdc should be a lite deployment without KSU token', () => {
27
+ const xdc = CHAIN_CONFIGS.xdc;
28
+ expect(xdc.chainId).toBe(50);
29
+ expect(xdc.isLiteDeployment).toBe(true);
30
+ expect(xdc.contracts.KSUToken).toBeUndefined();
31
+ expect(xdc.contracts.KasuNFTs).toBeUndefined();
32
+ });
33
+
34
+ it('plume should be a lite deployment', () => {
35
+ const plume = CHAIN_CONFIGS.plume;
36
+ expect(plume.chainId).toBe(98866);
37
+ expect(plume.isLiteDeployment).toBe(true);
38
+ });
39
+
40
+ it('all chains should have required contract addresses', () => {
41
+ for (const [, config] of Object.entries(CHAIN_CONFIGS)) {
42
+ expect(config.contracts.LendingPoolManager).toBeDefined();
43
+ expect(config.contracts.UserManager).toBeDefined();
44
+ expect(config.contracts.KasuAllowList).toBeDefined();
45
+ expect(config.contracts.SystemVariables).toBeDefined();
46
+ expect(config.subgraphUrl).toContain('goldsky.com');
47
+ }
48
+ });
49
+
50
+ it('xdc should have poolMetadataMapping', () => {
51
+ const mapping = CHAIN_CONFIGS.xdc.poolMetadataMapping;
52
+ expect(mapping).toBeDefined();
53
+ expect(Object.keys(mapping ?? {}).length).toBeGreaterThan(0);
54
+ });
55
+ });
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Kasu.create()
59
+ // ---------------------------------------------------------------------------
60
+
61
+ describe('Kasu.create()', () => {
62
+ // Mock provider — enough to construct the SDK without network calls
63
+ const mockProvider = {
64
+ getNetwork: jest.fn().mockResolvedValue({ chainId: 8453, name: 'base' }),
65
+ call: jest.fn(),
66
+ estimateGas: jest.fn(),
67
+ getBlockNumber: jest.fn(),
68
+ getBlock: jest.fn(),
69
+ getTransaction: jest.fn(),
70
+ getTransactionReceipt: jest.fn(),
71
+ getLogs: jest.fn(),
72
+ on: jest.fn(),
73
+ once: jest.fn(),
74
+ off: jest.fn(),
75
+ removeAllListeners: jest.fn(),
76
+ _isProvider: true,
77
+ };
78
+
79
+ it('should create an instance from a chain name', () => {
80
+ const kasu = Kasu.create({
81
+ chain: 'base',
82
+ signerOrProvider: mockProvider as never,
83
+ });
84
+
85
+ expect(kasu).toBeInstanceOf(Kasu);
86
+ expect(kasu.strategies).toBeInstanceOf(StrategiesFacade);
87
+ expect(kasu.deposits).toBeInstanceOf(DepositsFacade);
88
+ expect(kasu.portfolio).toBeInstanceOf(PortfolioFacade);
89
+ });
90
+
91
+ it('should expose chainConfig for the selected chain', () => {
92
+ const kasu = Kasu.create({
93
+ chain: 'xdc',
94
+ signerOrProvider: mockProvider as never,
95
+ });
96
+
97
+ expect(kasu.chainConfig.chainId).toBe(50);
98
+ expect(kasu.isLiteDeployment).toBe(true);
99
+ });
100
+
101
+ it('should accept a custom ChainConfigEntry', () => {
102
+ const custom = {
103
+ ...CHAIN_CONFIGS.base,
104
+ chainId: 999,
105
+ name: 'CustomChain',
106
+ };
107
+
108
+ const kasu = Kasu.create({
109
+ chain: custom,
110
+ signerOrProvider: mockProvider as never,
111
+ });
112
+
113
+ expect(kasu.chainConfig.chainId).toBe(999);
114
+ expect(kasu.chainConfig.name).toBe('CustomChain');
115
+ });
116
+
117
+ it('should accept configOverrides', () => {
118
+ const kasu = Kasu.create({
119
+ chain: 'base',
120
+ signerOrProvider: mockProvider as never,
121
+ configOverrides: {
122
+ UNUSED_LENDING_POOL_IDS: ['0xdead'],
123
+ },
124
+ });
125
+
126
+ expect(kasu).toBeInstanceOf(Kasu);
127
+ });
128
+
129
+ it('should expose the underlying KasuSdk via .services', () => {
130
+ const kasu = Kasu.create({
131
+ chain: 'base',
132
+ signerOrProvider: mockProvider as never,
133
+ });
134
+
135
+ expect(kasu.services).toBeDefined();
136
+ expect(kasu.services.DataService).toBeDefined();
137
+ expect(kasu.services.UserLending).toBeDefined();
138
+ expect(kasu.services.Locking).toBeDefined();
139
+ expect(kasu.services.Portfolio).toBeDefined();
140
+ });
141
+ });
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // StrategiesFacade.calculateDepositLimits()
145
+ // ---------------------------------------------------------------------------
146
+
147
+ describe('StrategiesFacade.calculateDepositLimits()', () => {
148
+ // We can test this pure function without network calls by constructing
149
+ // a StrategiesFacade with null services and calling calculateDepositLimits directly.
150
+ const facade = new StrategiesFacade(null as never, null as never);
151
+
152
+ it('should enforce minimum of 1 USDC', () => {
153
+ const tranche = makeTranche({
154
+ minimumDeposit: '0.5',
155
+ maximumDeposit: '10000',
156
+ poolCapacity: '50000',
157
+ });
158
+
159
+ const limits = facade.calculateDepositLimits(tranche);
160
+ expect(limits.min).toBe('1');
161
+ expect(limits.max).toBe('10000');
162
+ expect(limits.availableCapacity).toBe('50000');
163
+ });
164
+
165
+ it('should pass through minimum when >= 1', () => {
166
+ const tranche = makeTranche({
167
+ minimumDeposit: '100',
168
+ maximumDeposit: '10000',
169
+ poolCapacity: '50000',
170
+ });
171
+
172
+ const limits = facade.calculateDepositLimits(tranche);
173
+ expect(limits.min).toBe('100');
174
+ });
175
+
176
+ it('should cap max at pool capacity when capacity is lower', () => {
177
+ const tranche = makeTranche({
178
+ minimumDeposit: '100',
179
+ maximumDeposit: '50000',
180
+ poolCapacity: '10000',
181
+ });
182
+
183
+ const limits = facade.calculateDepositLimits(tranche);
184
+ expect(limits.max).toBe('10000');
185
+ });
186
+
187
+ it('should use maximumDeposit when capacity is higher', () => {
188
+ const tranche = makeTranche({
189
+ minimumDeposit: '100',
190
+ maximumDeposit: '5000',
191
+ poolCapacity: '50000',
192
+ });
193
+
194
+ const limits = facade.calculateDepositLimits(tranche);
195
+ expect(limits.max).toBe('5000');
196
+ });
197
+ });
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Type exports — verify key facade types are importable
201
+ // ---------------------------------------------------------------------------
202
+
203
+ describe('Type re-exports', () => {
204
+ it('should export facade classes from the main entry', () => {
205
+ // Verify the barrel exports resolve — these would fail at compile time
206
+ // if the re-exports were broken.
207
+ expect(Kasu).toBeDefined();
208
+ expect(CHAIN_CONFIGS).toBeDefined();
209
+ expect(StrategiesFacade).toBeDefined();
210
+ expect(DepositsFacade).toBeDefined();
211
+ expect(PortfolioFacade).toBeDefined();
212
+ });
213
+ });
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // Helpers
217
+ // ---------------------------------------------------------------------------
218
+
219
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
220
+ function makeTranche(raw: {
221
+ minimumDeposit: string;
222
+ maximumDeposit: string;
223
+ poolCapacity: string;
224
+ }) {
225
+ return {
226
+ id: '0xtest',
227
+ name: 'Test Tranche',
228
+ apy: 0.08,
229
+ minApy: 0.06,
230
+ maxApy: 0.10,
231
+ minimumDeposit: raw.minimumDeposit,
232
+ maximumDeposit: raw.maximumDeposit,
233
+ availableCapacity: raw.poolCapacity,
234
+ fixedTermOptions: [],
235
+ _raw: {
236
+ ...raw,
237
+ id: '0xtest',
238
+ name: 'Test Tranche',
239
+ apy: '0.08',
240
+ minApy: '0.06',
241
+ maxApy: '0.10',
242
+ fixedTermConfig: [],
243
+ },
244
+ } as never;
245
+ }
@@ -12,10 +12,10 @@ test('subgraphCall', async () => {
12
12
  'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
13
13
  provider,
14
14
  );
15
- const config = new SdkConfig(
16
- 'https://api.studio.thegraph.com/query/63245/kasu-wip-sepolia/version/latest',
17
- 'https://api.goldsky.com/api/public/project_cm9t3064xeuyn01tgctdo3c17/subgraphs/kasu-plume/prod/gn',
18
- {
15
+ const config = new SdkConfig({
16
+ subgraphUrl:
17
+ 'https://api.studio.thegraph.com/query/63245/kasu-wip-sepolia/version/latest',
18
+ contracts: {
19
19
  IKSULocking: '0x529A81c11ab6176c5E88670d293BB771800066a2',
20
20
  IKSULockBonus: '0xBBfFd5F744156FFc526df12F5e09dC7b208Be740',
21
21
  KSUToken: '0xa0f698Feb9Bc2BeA6E85eb071D5E3F59dc5bC56b',
@@ -29,11 +29,9 @@ test('subgraphCall', async () => {
29
29
  KasuNFTs: '0x0000000000000000000000000000000000000000',
30
30
  ExternalTVL: '0x0000000000000000000000000000000000000000',
31
31
  },
32
-
33
- 'https://kasu-finance.directus.app',
34
-
35
- [''],
36
- );
32
+ directusUrl: 'https://kasu-finance.directus.app',
33
+ UNUSED_LENDING_POOL_IDS: [''],
34
+ });
37
35
  const sdk = new KasuSdk(config, wallet);
38
36
 
39
37
  console.log(
@@ -0,0 +1,9 @@
1
+ import { SdkConfig } from '../sdk-config';
2
+
3
+ /**
4
+ * Check if the SDK is configured for a Lite deployment.
5
+ * Lite deployments don't have KSU token, locking, or loyalty features.
6
+ */
7
+ export function isLiteDeployment(config: SdkConfig): boolean {
8
+ return config.isLiteDeployment;
9
+ }
package/tsconfig.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "target": "es6",
7
7
  "module": "esnext",
8
8
  "lib": ["es6", "dom", "dom.iterable", "esnext", "es2020"],
9
- "sourceMap": false,
9
+ "sourceMap": true,
10
10
  "allowJs": true,
11
11
  "outDir": "./dist",
12
12
  "rootDir": "src",