@strkfarm/sdk 1.1.70 → 2.0.0-dev.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 (53) hide show
  1. package/dist/cli.js +2 -2
  2. package/dist/cli.mjs +2 -2
  3. package/dist/index.browser.global.js +66861 -59746
  4. package/dist/index.browser.mjs +24970 -18579
  5. package/dist/index.d.ts +1969 -776
  6. package/dist/index.js +25264 -18850
  7. package/dist/index.mjs +25463 -19089
  8. package/package.json +80 -76
  9. package/src/data/extended-deposit.abi.json +3613 -0
  10. package/src/data/universal-vault.abi.json +135 -20
  11. package/src/dataTypes/address.ts +8 -1
  12. package/src/global.ts +240 -193
  13. package/src/interfaces/common.tsx +26 -2
  14. package/src/modules/ExtendedWrapperSDk/index.ts +62 -0
  15. package/src/modules/ExtendedWrapperSDk/types.ts +311 -0
  16. package/src/modules/ExtendedWrapperSDk/wrapper.ts +395 -0
  17. package/src/modules/avnu.ts +17 -4
  18. package/src/modules/ekubo-quoter.ts +98 -10
  19. package/src/modules/erc20.ts +67 -21
  20. package/src/modules/harvests.ts +16 -29
  21. package/src/modules/index.ts +5 -1
  22. package/src/modules/lst-apr.ts +36 -0
  23. package/src/modules/midas.ts +159 -0
  24. package/src/modules/pricer-from-api.ts +2 -2
  25. package/src/modules/pricer-lst.ts +1 -1
  26. package/src/modules/pricer.ts +3 -38
  27. package/src/modules/token-market-data.ts +202 -0
  28. package/src/node/deployer.ts +1 -36
  29. package/src/strategies/autoCompounderStrk.ts +1 -1
  30. package/src/strategies/base-strategy.ts +20 -3
  31. package/src/strategies/ekubo-cl-vault.tsx +123 -306
  32. package/src/strategies/index.ts +4 -1
  33. package/src/strategies/svk-strategy.ts +247 -0
  34. package/src/strategies/universal-adapters/adapter-optimizer.ts +65 -0
  35. package/src/strategies/universal-adapters/adapter-utils.ts +5 -1
  36. package/src/strategies/universal-adapters/avnu-adapter.ts +418 -0
  37. package/src/strategies/universal-adapters/baseAdapter.ts +181 -153
  38. package/src/strategies/universal-adapters/common-adapter.ts +98 -77
  39. package/src/strategies/universal-adapters/extended-adapter.ts +544 -0
  40. package/src/strategies/universal-adapters/index.ts +5 -1
  41. package/src/strategies/universal-adapters/unused-balance-adapter.ts +109 -0
  42. package/src/strategies/universal-adapters/vesu-adapter.ts +220 -218
  43. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +924 -0
  44. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +58 -51
  45. package/src/strategies/universal-lst-muliplier-strategy.tsx +707 -774
  46. package/src/strategies/universal-strategy.tsx +1098 -1180
  47. package/src/strategies/vesu-extended-strategy/services/operationService.ts +28 -0
  48. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +77 -0
  49. package/src/strategies/vesu-extended-strategy/utils/constants.ts +48 -0
  50. package/src/strategies/vesu-extended-strategy/utils/helper.ts +374 -0
  51. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +992 -0
  52. package/src/strategies/vesu-rebalance.tsx +16 -19
  53. package/src/utils/health-factor-math.ts +11 -5
@@ -0,0 +1,924 @@
1
+ import { ContractAddr, Web3Number } from "@/dataTypes";
2
+ import { IConfig, Protocols, TokenInfo } from "@/interfaces";
3
+ import { PricerBase } from "@/modules/pricerBase";
4
+ import { BaseAdapter, BaseAdapterConfig, SupportedPosition, PositionInfo, PositionAPY, APYType, ManageCall, AdapterLeafType, GenerateCallFn, DepositParams, WithdrawParams, PositionAmount } from "./baseAdapter";
5
+ import { SIMPLE_SANITIZER, SIMPLE_SANITIZER_V2, toBigInt, VESU_SINGLETON, VESU_V2_MODIFY_POSITION_SANITIZER } from "./adapter-utils";
6
+ import { hash, uint256, Contract, CairoCustomEnum, num } from "starknet";
7
+ import { VesuAdapter, VesuMultiplyCallParams, VesuModifyDelegationCallParams, getVesuSingletonAddress, VesuPools, Swap, IncreaseLeverParams, DecreaseLeverParams } from "./vesu-adapter";
8
+ import { logger } from "@/utils";
9
+ import { WALLET_ADDRESS } from "../vesu-extended-strategy/utils/constants";
10
+ import VesuMultiplyAbi from '@/data/vesu-multiple.abi.json';
11
+ import VesuSingletonAbi from '../../data/vesu-singleton.abi.json';
12
+ import VesuPoolV2Abi from '@/data/vesu-pool-v2.abi.json';
13
+ import { EkuboQuoter, TokenMarketData } from "@/modules";
14
+ import { calculateDebtReductionAmountForWithdrawal } from "../vesu-extended-strategy/utils/helper";
15
+ import { HealthFactorMath } from "@/utils/health-factor-math";
16
+ import { MAX_LIQUIDATION_RATIO } from "../vesu-extended-strategy/utils/constants";
17
+
18
+ export interface VesuMultiplyAdapterConfig extends BaseAdapterConfig {
19
+ poolId: ContractAddr;
20
+ collateral: TokenInfo;
21
+ debt: TokenInfo;
22
+ targetHealthFactor: number;
23
+ minHealthFactor: number;
24
+ quoteAmountToFetchPrice: Web3Number;
25
+ }
26
+
27
+ export class VesuMultiplyAdapter extends BaseAdapter<DepositParams, WithdrawParams> {
28
+ readonly config: VesuMultiplyAdapterConfig;
29
+ readonly vesuAdapter: VesuAdapter;
30
+ readonly tokenMarketData: TokenMarketData;
31
+
32
+ constructor(config: VesuMultiplyAdapterConfig) {
33
+ super(config, VesuMultiplyAdapter.name, Protocols.VESU);
34
+ this.config = config;
35
+ this.vesuAdapter = new VesuAdapter({
36
+ poolId: config.poolId,
37
+ collateral: config.collateral,
38
+ debt: config.debt,
39
+ vaultAllocator: config.vaultAllocator,
40
+ id: ''
41
+ });
42
+ this.tokenMarketData = new TokenMarketData(this.config.pricer, this.config.networkConfig);
43
+ }
44
+
45
+ protected async getAPY(supportedPosition: SupportedPosition): Promise<PositionAPY> {
46
+ const CACHE_KEY = `apy_${this.config.poolId.address}_${supportedPosition.asset.symbol}`;
47
+ const cacheData = this.getCache<PositionAPY>(CACHE_KEY);
48
+ console.log(`${VesuMultiplyAdapter.name}::getAPY cacheData: ${JSON.stringify(cacheData)}`, this.vesuAdapter.config.poolId.shortString(), this.vesuAdapter.config.collateral.symbol, this.vesuAdapter.config.debt.symbol);
49
+ if (cacheData) {
50
+ return cacheData;
51
+ }
52
+ try {
53
+ // Get Vesu pools to find APY for the asset
54
+ const allVesuPools = await VesuAdapter.getVesuPools();
55
+ const asset = supportedPosition.asset;
56
+ const pool = allVesuPools.pools.find(p => this.vesuAdapter.config.poolId.eqString(num.getHexString(p.id)));
57
+ if (!pool) {
58
+ logger.warn(`VesuMultiplyAdapter: Pool not found for token ${asset.symbol}`);
59
+ return {
60
+ apy: 0,
61
+ type: APYType.BASE
62
+ };
63
+ }
64
+ // Find the asset stats for our token
65
+ const assetStats = pool.assets.find((a: any) =>
66
+ a.symbol.toLowerCase() === asset.symbol.toLowerCase()
67
+ )?.stats;
68
+
69
+ if (!assetStats) {
70
+ logger.warn(`VesuMultiplyAdapter: Asset stats not found for token ${asset.symbol}`);
71
+ return {
72
+ apy: 0,
73
+ type: APYType.BASE
74
+ };
75
+ }
76
+ // Get appropriate APY based on position type
77
+ let apy = 0;
78
+ if (supportedPosition.isDebt) {
79
+ // For debt positions, use borrow APY
80
+ apy = Number(assetStats.borrowApr?.value || 0) / 1e18;
81
+
82
+ // todo
83
+ // Account for rewards on debt token
84
+ } else {
85
+ // For collateral positions, use supply APY
86
+ const isAssetBTC = asset.symbol.toLowerCase().includes("btc");
87
+ const baseAPY = Number(isAssetBTC ? assetStats.btcFiSupplyApr?.value + assetStats.supplyApy?.value : assetStats.supplyApy?.value || 0) / 1e18;
88
+
89
+ // account for reward yield (like STRK rewards)
90
+ const rewardAPY = Number(assetStats.defiSpringSupplyApr?.value || "0") / 1e18;
91
+
92
+ // account for base yield of LST
93
+ const isSupported = this.tokenMarketData.isAPYSupported(asset);
94
+ apy = baseAPY + rewardAPY;
95
+ if (isSupported) {
96
+ const tokenAPY = await this.tokenMarketData.getAPY(asset);
97
+ apy += tokenAPY;
98
+ }
99
+ }
100
+ const result = {
101
+ apy,
102
+ type: supportedPosition.isDebt ? APYType.BASE : APYType.BASE
103
+ };
104
+
105
+ this.setCache(CACHE_KEY, result, 300000); // Cache for 5 minutes
106
+ return result;
107
+ } catch (error) {
108
+ logger.error(`VesuMultiplyAdapter: Error getting APY for ${supportedPosition.asset.symbol}:`, error);
109
+ // return {
110
+ // apy: 0,
111
+ // type: APYType.BASE
112
+ // };
113
+ throw error;
114
+ }
115
+ }
116
+
117
+ protected async getPosition(supportedPosition: SupportedPosition): Promise<PositionAmount> {
118
+ const CACHE_KEY = `position_${this.config.poolId.address}_${supportedPosition.asset.symbol}`;
119
+ const cacheData = this.getCache<PositionAmount>(CACHE_KEY);
120
+ if (cacheData) {
121
+ return cacheData;
122
+ }
123
+
124
+ try {
125
+ // Use VesuAdapter to get positions
126
+ this.vesuAdapter.networkConfig = this.config.networkConfig;
127
+ this.vesuAdapter.pricer = this.config.pricer;
128
+
129
+ const positions = await this.vesuAdapter.getPositions(this.config.networkConfig);
130
+
131
+ // Find the position for our asset
132
+ let position = positions.find(p =>
133
+ p.token.address.eq(supportedPosition.asset.address)
134
+ );
135
+
136
+ if (!position) {
137
+ logger.warn(`VesuMultiplyAdapter: Position not found for token ${supportedPosition.asset.symbol}`);
138
+ return {
139
+ amount: new Web3Number('0', supportedPosition.asset.decimals),
140
+ remarks: "Position not found"
141
+ };
142
+ }
143
+
144
+ if (supportedPosition.isDebt) {
145
+ position.amount = position.amount.multipliedBy(-1);
146
+ position.usdValue = position.usdValue * -1;
147
+ }
148
+
149
+ this.setCache(CACHE_KEY, position, 60000); // Cache for 1 minute
150
+ return position;
151
+ } catch (error) {
152
+ logger.error(`VesuMultiplyAdapter: Error getting position for ${supportedPosition.asset.symbol}:`, error);
153
+ // return new Web3Number('0', supportedPosition.asset.decimals);
154
+ throw error;
155
+ }
156
+ }
157
+
158
+ async maxBorrowableAPY(): Promise<number> {
159
+ // get collateral APY
160
+ const collateralAPY = await this.getAPY({ asset: this.config.collateral, isDebt: false });
161
+ const apy = collateralAPY.apy * 0.8;
162
+ return apy;
163
+ }
164
+
165
+ async maxDeposit(amount?: Web3Number): Promise<PositionInfo> {
166
+ const collateral = this.config.collateral;
167
+ const debt = this.config.debt;
168
+
169
+ try {
170
+ // Get current positions
171
+ this.vesuAdapter.networkConfig = this.config.networkConfig;
172
+ this.vesuAdapter.pricer = this.config.pricer;
173
+
174
+ const positions = await this.vesuAdapter.getPositions(this.config.networkConfig);
175
+ const collateralPosition = positions.find(p => p.token.address.eq(collateral.address));
176
+ const debtPosition = positions.find(p => p.token.address.eq(debt.address));
177
+
178
+ if (!collateralPosition || !debtPosition) {
179
+ throw new Error('Could not find current positions');
180
+ }
181
+
182
+ // Calculate max borrowable amount
183
+ const maxBorrowableAPY = await this.maxBorrowableAPY();
184
+ const maxBorrowable = await this.vesuAdapter.getMaxBorrowableByInterestRate(
185
+ this.config.networkConfig,
186
+ debt,
187
+ maxBorrowableAPY
188
+ );
189
+ logger.verbose(`VesuMultiplyAdapter: Max borrowable: ${maxBorrowable.toNumber()}`);
190
+ const debtCap = await this.vesuAdapter.getDebtCap(this.config.networkConfig);
191
+ logger.verbose(`VesuMultiplyAdapter: Debt cap: ${debtCap.toNumber()}`);
192
+ const actualMaxBorrowable = maxBorrowable.minimum(debtCap);
193
+ logger.verbose(`VesuMultiplyAdapter: Actual max borrowable: ${actualMaxBorrowable.toNumber()}`);
194
+
195
+ // Calculate max collateral that can be deposited based on LTV
196
+ const maxLTV = await this.vesuAdapter.getLTVConfig(this.config.networkConfig);
197
+ const collateralPrice = await this.config.pricer.getPrice(collateral.symbol);
198
+ if (collateralPrice.price === 0) {
199
+ throw new Error('Collateral price is 0');
200
+ }
201
+ const debtPrice = await this.config.pricer.getPrice(debt.symbol);
202
+ if (debtPrice.price === 0) {
203
+ throw new Error('Debt price is 0');
204
+ }
205
+ const maxCollateralFromDebt = HealthFactorMath.getMinCollateralRequiredOnLooping(
206
+ actualMaxBorrowable,
207
+ debtPrice.price,
208
+ this.config.targetHealthFactor,
209
+ maxLTV,
210
+ collateralPrice.price,
211
+ collateral
212
+ )
213
+
214
+ const maxDepositAmount = amount ? amount.minimum(maxCollateralFromDebt) : maxCollateralFromDebt;
215
+ const usdValue = await this.getUSDValue(collateral, maxDepositAmount);
216
+ logger.verbose(`VesuMultiplyAdapter: Max deposit::USD value: ${usdValue}, amount: ${maxDepositAmount.toNumber()}`);
217
+ const apys = await Promise.all([this.getAPY({ asset: collateral, isDebt: false }), this.getAPY({ asset: debt, isDebt: true })]);
218
+ logger.verbose(`VesuMultiplyAdapter: Apys: ${apys[0].apy}, ${apys[1].apy}`);
219
+
220
+ const borrowAmountUSD = actualMaxBorrowable.multipliedBy(debtPrice.price);
221
+ logger.verbose(`VesuMultiplyAdapter: Borrow amount: ${actualMaxBorrowable.toNumber()}, borrow amount USD: ${borrowAmountUSD.toNumber()}`);
222
+ const netCollateralUSD = usdValue + borrowAmountUSD.toNumber();
223
+ const netAPY = (apys[0].apy * netCollateralUSD + apys[1].apy * borrowAmountUSD.toNumber()) / (usdValue);
224
+ logger.verbose(`VesuMultiplyAdapter: Max deposit amount: ${maxDepositAmount.toNumber()}, netAPY: ${netAPY}`);
225
+ return {
226
+ tokenInfo: collateral,
227
+ amount: maxDepositAmount,
228
+ usdValue,
229
+ remarks: "Max deposit based on available debt capacity",
230
+ apy: {
231
+ apy: netAPY,
232
+ type: APYType.BASE
233
+ },
234
+ protocol: this.protocol
235
+ };
236
+ } catch (error) {
237
+ logger.error(`VesuMultiplyAdapter: Error calculating max deposit:`, error);
238
+ throw error;
239
+ }
240
+ }
241
+
242
+ async maxWithdraw(): Promise<PositionInfo> {
243
+ const collateral = this.config.collateral;
244
+ const debt = this.config.debt;
245
+
246
+ try {
247
+ // Calculate how much can be withdrawn without affecting health factor too much
248
+ this.vesuAdapter.networkConfig = this.config.networkConfig;
249
+ this.vesuAdapter.pricer = this.config.pricer;
250
+
251
+ const positions = await this.vesuAdapter.getPositions(this.config.networkConfig);
252
+ const collateralPosition = positions.find(p => p.token.address.eq(collateral.address));
253
+ const debtPosition = positions.find(p => p.token.address.eq(this.config.debt.address));
254
+
255
+ if (!collateralPosition || !debtPosition) {
256
+ throw new Error('Could not find current positions');
257
+ }
258
+
259
+ // Calculate max withdrawable (conservative approach)
260
+ const collateralPrice = collateralPosition.usdValue / collateralPosition.amount.toNumber();
261
+ const debtInCollateral = debtPosition.usdValue / collateralPrice;
262
+ const maxWithdrawable = collateralPosition.amount.minus(debtInCollateral);
263
+
264
+ const result = maxWithdrawable.greaterThan(0) ? maxWithdrawable : new Web3Number('0', collateral.decimals);
265
+ const usdValue = await this.getUSDValue(collateral, result);
266
+ const debtUSD = debtPosition.usdValue;
267
+ logger.verbose(`VesuMultiplyAdapter: Debt USD: ${debtUSD}, collateral USD: ${usdValue}`);
268
+ const apys = await Promise.all([this.getAPY({ asset: collateral, isDebt: false }), this.getAPY({ asset: debt, isDebt: true })]);
269
+ logger.verbose(`VesuMultiplyAdapter: Apys: ${apys[0].apy}, ${apys[1].apy}`);
270
+ const netAPY = (usdValue - debtUSD) > 0 ? (apys[0].apy * usdValue + apys[1].apy * debtUSD) / (usdValue - debtUSD) : 0;
271
+ logger.verbose(`VesuMultiplyAdapter: Max withdraw amount: ${result.toNumber()}, netAPY: ${netAPY}`);
272
+ return {
273
+ tokenInfo: collateral,
274
+ amount: result,
275
+ usdValue,
276
+ remarks: "Max withdraw based on health factor",
277
+ apy: {
278
+ apy: netAPY,
279
+ type: APYType.BASE
280
+ },
281
+ protocol: this.protocol
282
+ };
283
+ } catch (error) {
284
+ logger.error(`VesuMultiplyAdapter: Error calculating max withdraw:`, error);
285
+ throw error;
286
+ }
287
+ }
288
+
289
+ protected _getDepositLeaf(): {
290
+ target: ContractAddr,
291
+ method: string,
292
+ packedArguments: bigint[],
293
+ sanitizer: ContractAddr,
294
+ id: string
295
+ }[] {
296
+ const collateral = this.config.collateral;
297
+ const debt = this.config.debt;
298
+ const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(this.config.poolId);
299
+ const vesuMultiply = isV2 ? this.vesuAdapter.VESU_MULTIPLY : this.vesuAdapter.VESU_MULTIPLY_V1;
300
+
301
+ return [
302
+ // Approval step for collateral
303
+ {
304
+ target: collateral.address,
305
+ method: 'approve',
306
+ packedArguments: [
307
+ vesuMultiply.toBigInt(), // spender
308
+ ],
309
+ sanitizer: SIMPLE_SANITIZER,
310
+ // amc = approve multiply collateral
311
+ id: `amc_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
312
+ },
313
+ // Switch delegation on
314
+ {
315
+ target: vesuSingleton,
316
+ method: 'modify_delegation',
317
+ packedArguments: isV2 ? [
318
+ vesuMultiply.toBigInt(), // delegatee
319
+ ] : [
320
+ this.config.poolId.toBigInt(),
321
+ vesuMultiply.toBigInt(), // delegatee
322
+ ],
323
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
324
+ // sd1 = switch delegation on
325
+ id: `sd1_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
326
+ },
327
+ // Vesu multiply call
328
+ {
329
+ target: vesuMultiply,
330
+ method: 'modify_lever',
331
+ packedArguments: [
332
+ this.config.poolId.toBigInt(),
333
+ collateral.address.toBigInt(),
334
+ this.config.debt.address.toBigInt(),
335
+ this.config.vaultAllocator.toBigInt(),
336
+ ],
337
+ sanitizer: SIMPLE_SANITIZER_V2,
338
+ // vm = vesu multiply
339
+ id: `vm_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
340
+ },
341
+ // Switch delegation off
342
+ {
343
+ target: vesuSingleton,
344
+ method: 'modify_delegation',
345
+ packedArguments: isV2 ? [
346
+ vesuMultiply.toBigInt(), // delegatee
347
+ ] : [
348
+ this.config.poolId.toBigInt(),
349
+ vesuMultiply.toBigInt(), // delegatee
350
+ ],
351
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
352
+ // sd2 = switch delegation off
353
+ id: `sd2_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
354
+ }
355
+ ];
356
+ }
357
+
358
+ protected _getWithdrawLeaf(): {
359
+ target: ContractAddr,
360
+ method: string,
361
+ packedArguments: bigint[],
362
+ sanitizer: ContractAddr,
363
+ id: string
364
+ }[] {
365
+ const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(this.config.poolId);
366
+ const vesuMultiply = isV2 ? this.vesuAdapter.VESU_MULTIPLY : this.vesuAdapter.VESU_MULTIPLY_V1;
367
+ const collateral = this.config.collateral;
368
+ const debt = this.config.debt;
369
+
370
+ return [
371
+ // Switch delegation on
372
+ {
373
+ target: vesuSingleton,
374
+ method: 'modify_delegation',
375
+ packedArguments: isV2 ? [
376
+ vesuMultiply.toBigInt(), // delegatee
377
+ ] : [
378
+ this.config.poolId.toBigInt(),
379
+ vesuMultiply.toBigInt(), // delegatee
380
+ ],
381
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
382
+ // sdow = switch delegation on withdraw
383
+ id: `sdow_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
384
+ },
385
+ // Vesu multiply call
386
+ {
387
+ target: vesuMultiply,
388
+ method: 'modify_lever',
389
+ packedArguments: [
390
+ this.config.poolId.toBigInt(),
391
+ this.config.collateral.address.toBigInt(),
392
+ this.config.debt.address.toBigInt(),
393
+ this.config.vaultAllocator.toBigInt(),
394
+ ],
395
+ sanitizer: SIMPLE_SANITIZER_V2,
396
+ // vmw = vesu multiply withdraw
397
+ id: `vmw_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
398
+ },
399
+ // Switch delegation off
400
+ {
401
+ target: vesuSingleton,
402
+ method: 'modify_delegation',
403
+ packedArguments: isV2 ? [
404
+ vesuMultiply.toBigInt(), // delegatee
405
+ ] : [
406
+ this.config.poolId.toBigInt(),
407
+ vesuMultiply.toBigInt(), // delegatee
408
+ ],
409
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
410
+ // sdofw = switch delegation off withdraw
411
+ id: `sdofw_${this.config.poolId.shortString()}_${collateral.symbol}_${debt.symbol}`
412
+ }
413
+ ];
414
+ }
415
+
416
+ getDepositAdapter(): AdapterLeafType<DepositParams> {
417
+ const leafConfigs = this._getDepositLeaf();
418
+ const leaves = leafConfigs.map(config => {
419
+ const { target, method, packedArguments, sanitizer, id } = config;
420
+ const leaf = this.constructSimpleLeafData({
421
+ id: id,
422
+ target,
423
+ method,
424
+ packedArguments
425
+ }, sanitizer);
426
+ return leaf;
427
+ });
428
+ return { leaves, callConstructor: this.getDepositCall.bind(this) as unknown as GenerateCallFn<DepositParams> };
429
+ }
430
+
431
+ getWithdrawAdapter(): AdapterLeafType<WithdrawParams> {
432
+ const leafConfigs = this._getWithdrawLeaf();
433
+ const leaves = leafConfigs.map(config => {
434
+ const { target, method, packedArguments, sanitizer, id } = config;
435
+ const leaf = this.constructSimpleLeafData({
436
+ id: id,
437
+ target,
438
+ method,
439
+ packedArguments
440
+ }, sanitizer);
441
+ return leaf;
442
+ });
443
+ return { leaves, callConstructor: this.getWithdrawCall.bind(this) as unknown as GenerateCallFn<WithdrawParams> };
444
+ }
445
+
446
+ async getDepositCall(params: DepositParams): Promise<ManageCall[]> {
447
+ const collateral = this.config.collateral;
448
+ const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(this.config.poolId);
449
+ const vesuMultiply = isV2 ? this.vesuAdapter.VESU_MULTIPLY : this.vesuAdapter.VESU_MULTIPLY_V1;
450
+
451
+ const uint256MarginAmount = uint256.bnToUint256(params.amount.toWei());
452
+
453
+ return [
454
+ // Approval call
455
+ {
456
+ sanitizer: SIMPLE_SANITIZER,
457
+ call: {
458
+ contractAddress: collateral.address,
459
+ selector: hash.getSelectorFromName('approve'),
460
+ calldata: [
461
+ vesuMultiply.toBigInt(), // spender
462
+ toBigInt(uint256MarginAmount.low.toString()), // amount low
463
+ toBigInt(uint256MarginAmount.high.toString()), // amount high
464
+ ]
465
+ }
466
+ },
467
+ // Switch delegation on
468
+ {
469
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
470
+ call: {
471
+ contractAddress: vesuSingleton,
472
+ selector: hash.getSelectorFromName('modify_delegation'),
473
+ calldata: isV2 ? [
474
+ vesuMultiply.toBigInt(), // delegatee
475
+ BigInt(1), // delegation: true
476
+ ] : [
477
+ this.config.poolId.toBigInt(),
478
+ vesuMultiply.toBigInt(), // delegatee
479
+ BigInt(1), // delegation: true
480
+ ]
481
+ }
482
+ },
483
+ // Vesu multiply call
484
+ {
485
+ sanitizer: SIMPLE_SANITIZER_V2,
486
+ call: {
487
+ contractAddress: vesuMultiply,
488
+ selector: hash.getSelectorFromName('modify_lever'),
489
+ calldata: await this.getMultiplyCallCalldata(params, true)
490
+ }
491
+ },
492
+ // Switch delegation off
493
+ {
494
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
495
+ call: {
496
+ contractAddress: vesuSingleton,
497
+ selector: hash.getSelectorFromName('modify_delegation'),
498
+ calldata: isV2 ? [
499
+ vesuMultiply.toBigInt(), // delegatee
500
+ BigInt(0), // delegation: false
501
+ ] : [
502
+ this.config.poolId.toBigInt(),
503
+ vesuMultiply.toBigInt(), // delegatee
504
+ BigInt(0), // delegation: false
505
+ ]
506
+ }
507
+ }
508
+ ];
509
+ }
510
+
511
+ async getWithdrawCall(params: WithdrawParams): Promise<ManageCall[]> {
512
+ const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(this.config.poolId);
513
+ const vesuMultiply = isV2 ? this.vesuAdapter.VESU_MULTIPLY : this.vesuAdapter.VESU_MULTIPLY_V1;
514
+
515
+ return [
516
+ // Switch delegation on
517
+ {
518
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
519
+ call: {
520
+ contractAddress: vesuSingleton,
521
+ selector: hash.getSelectorFromName('modify_delegation'),
522
+ calldata: isV2 ? [
523
+ vesuMultiply.toBigInt(), // delegatee
524
+ BigInt(1), // delegation: true
525
+ ] : [
526
+ this.config.poolId.toBigInt(),
527
+ vesuMultiply.toBigInt(), // delegatee
528
+ BigInt(1), // delegation: true
529
+ ]
530
+ }
531
+ },
532
+ // Vesu multiply call
533
+ {
534
+ sanitizer: SIMPLE_SANITIZER_V2,
535
+ call: {
536
+ contractAddress: vesuMultiply,
537
+ selector: hash.getSelectorFromName('modify_lever'),
538
+ calldata: await this.getWithdrawalCalldata(params)
539
+ }
540
+ },
541
+ // Switch delegation off
542
+ {
543
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
544
+ call: {
545
+ contractAddress: vesuSingleton,
546
+ selector: hash.getSelectorFromName('modify_delegation'),
547
+ calldata: isV2 ? [
548
+ vesuMultiply.toBigInt(), // delegatee
549
+ BigInt(0), // delegation: false
550
+ ] : [
551
+ this.config.poolId.toBigInt(),
552
+ vesuMultiply.toBigInt(), // delegatee
553
+ BigInt(0), // delegation: false
554
+ ]
555
+ }
556
+ }
557
+ ];
558
+ }
559
+
560
+ private async getMultiplyCallCalldata(params: DepositParams | WithdrawParams, isDeposit: boolean): Promise<bigint[]> {
561
+ logger.verbose(`${VesuMultiplyAdapter.name}::getMultiplyCallCalldata params: ${JSON.stringify(params)}, isDeposit: ${isDeposit}, collateral: ${this.config.collateral.symbol}, debt: ${this.config.debt.symbol}`);
562
+ const { isV2 } = getVesuSingletonAddress(this.config.poolId);
563
+ const vesuMultiply = isV2 ? this.vesuAdapter.VESU_MULTIPLY : this.vesuAdapter.VESU_MULTIPLY_V1;
564
+
565
+ // Create a temporary contract instance to populate the call
566
+ const multiplyContract = new Contract({
567
+ abi: VesuMultiplyAbi,
568
+ address: vesuMultiply.address,
569
+ providerOrAccount: this.config.networkConfig.provider
570
+ });
571
+
572
+ // Configure swaps based on the operation
573
+ let leverSwap: Swap[] = [];
574
+ let leverSwapLimitAmount = Web3Number.fromWei(0, this.config.debt.decimals);
575
+
576
+ const existingPositions = await this.vesuAdapter.getPositions(this.config.networkConfig);
577
+ const collateralisation = await this.vesuAdapter.getCollateralization(this.config.networkConfig);
578
+ const existingCollateralInfo = existingPositions[0];
579
+ const existingDebtInfo = existingPositions[1];
580
+ const isDexPriceRequired = existingDebtInfo.token.symbol !== "USDC";
581
+ logger.debug(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
582
+ existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
583
+
584
+ // - Prices as seen by Vesu contracts, ideal for HF math
585
+ // Price 1 is ok as fallback bcz that would relatively price the
586
+ // collateral and debt as equal.
587
+ const collateralPrice = collateralisation[0].usdValue > 0 ?
588
+ collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() :
589
+ (await this.config.pricer.getPrice(this.config.collateral.symbol)).price;
590
+ const debtPrice = collateralisation[1].usdValue > 0 ?
591
+ collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() :
592
+ (await this.config.pricer.getPrice(this.config.debt.symbol)).price;
593
+ logger.debug(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
594
+
595
+ const legLTV = await this.vesuAdapter.getLTVConfig(this.config.networkConfig);
596
+ const ekuboQuoter = new EkuboQuoter(this.config.networkConfig, this.config.pricer);
597
+ const dexPrice = isDexPriceRequired ? await ekuboQuoter.getDexPrice(this.config.collateral, this.config.debt, this.config.quoteAmountToFetchPrice) : 1;
598
+ logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall dexPrice: ${dexPrice}, ltv: ${legLTV}`);
599
+
600
+ // compute optimal amount of collateral and debt post addition/removal
601
+ // target hf = collateral * collateralPrice * ltv / debt * debtPrice
602
+ // assuming X to be the usd amount of debt borrowed or repaied (negative).
603
+ // target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv / (debt * debtPrice + X)
604
+ // => X * target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv - (debt * debtPrice * target hf)
605
+ // => X * (target hf - ltv)= ((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)
606
+ // => X = (((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)) / (target hf - ltv)
607
+
608
+ const addedCollateral = params.amount
609
+ .multipliedBy(isDeposit ? 1 : -1)
610
+ logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall addedCollateral: ${addedCollateral}`);
611
+ const numeratorPart1 = (existingCollateralInfo.amount.plus((addedCollateral))).multipliedBy(collateralPrice).multipliedBy(legLTV);
612
+ logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}`);
613
+ const numeratorPart2 = existingDebtInfo.amount.multipliedBy(debtPrice).multipliedBy(this.config.targetHealthFactor);
614
+ logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall numeratorPart2: ${numeratorPart2}`);
615
+ const denominatorPart = this.config.targetHealthFactor - (legLTV / dexPrice); // TODO Write reason for this. this dexPrice is some custom thing. this dexPrice is probably exchange rate (1 xWBTC in WBTC terms)
616
+ logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall denominatorPart: ${denominatorPart}`);
617
+ const x_debt_usd = numeratorPart1.minus(numeratorPart2).dividedBy(denominatorPart);
618
+ logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall x_debt_usd: ${x_debt_usd}`);
619
+ logger.debug(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}, numeratorPart2: ${numeratorPart2}, denominatorPart: ${denominatorPart}`);
620
+
621
+ // both in underlying
622
+ // debtAmount in debt units
623
+ let debtAmount = new Web3Number(x_debt_usd.dividedBy(debtPrice).toFixed(this.config.debt.decimals), this.config.debt.decimals);
624
+ const marginAmount = addedCollateral;
625
+ const collateralToken = this.config.collateral;
626
+ const debtToken = this.config.debt;
627
+ const debtAmountInCollateralUnits = new Web3Number(debtAmount.multipliedBy(debtPrice).dividedBy(collateralPrice).multipliedBy(10 ** collateralToken.decimals).toFixed(0), collateralToken.decimals);
628
+
629
+ // increase multiply lever or not
630
+ const isIncrease = debtAmount.greaterThanOrEqualTo(0);
631
+
632
+
633
+ // due to directional limitations in multiply contract
634
+ if (isIncrease && debtAmount.lessThan(0)) {
635
+ // we are increasing lever but math says reduce debt
636
+ // - this is ok
637
+ } else if (!isIncrease && debtAmount.greaterThan(0)) {
638
+ // we are decreasing level but math says increase debt
639
+ // - such actions must be done with zero margin amount
640
+ // - so just set debt 0
641
+ debtAmount = Web3Number.fromWei(0, this.config.debt.decimals);
642
+ }
643
+ logger.verbose(`${VesuMultiplyAdapter.name}::getVesuMultiplyCall debtAmount: ${debtAmount}, marginAmount: ${marginAmount}`);
644
+ if (!debtAmount.isZero()) {
645
+ // Get swap quote for leverage operation
646
+ // Determine swap direction based on operation type
647
+
648
+ try {
649
+ const swapQuote = await ekuboQuoter.getQuote(
650
+ collateralToken.address.address,
651
+ debtToken.address.address,
652
+ debtAmountInCollateralUnits.multipliedBy(-1)// negative for exact amount out
653
+ );
654
+
655
+ // todo add better slip checks
656
+ // Check price impact
657
+ if (swapQuote.price_impact < 0.01) { // 1% max price impact
658
+ // from and toToken param position reversed, to fetch the required quote and keep things generalised
659
+ leverSwap = ekuboQuoter.getVesuMultiplyQuote(swapQuote, debtToken, collateralToken);
660
+ //console.log("leverSwap", leverSwap[-1].token_amount);
661
+ //console.log(JSON.stringify(leverSwap));
662
+ // Calculate limit amount with slippage protection
663
+ const MAX_SLIPPAGE = 0.002; // 0.2% slippage
664
+ if (debtAmount.greaterThan(0)) {
665
+ // For increase: minimum amount of collateral received
666
+ // from debt token to collateral token
667
+ console.log("debtAmountInCollateralUnits", debtAmountInCollateralUnits.toNumber());
668
+ leverSwapLimitAmount = await ekuboQuoter.getSwapLimitAmount(debtToken, collateralToken, debtAmount, MAX_SLIPPAGE);
669
+ const anotherleverSwapLimitAmount = debtAmount.multipliedBy(1 + MAX_SLIPPAGE);
670
+ console.log("anotherleverSwapLimitAmount", anotherleverSwapLimitAmount, leverSwapLimitAmount);
671
+ } else if (debtAmount.lessThan(0)) {
672
+ // For decrease: maximum amount of collateral used
673
+ // from collateral token to debt token
674
+ leverSwapLimitAmount = await ekuboQuoter.getSwapLimitAmount(collateralToken, debtToken, debtAmountInCollateralUnits.multipliedBy(-1), MAX_SLIPPAGE);
675
+ const anotherleverSwapLimitAmount = debtAmount.abs().multipliedBy(1 - MAX_SLIPPAGE);
676
+ console.log("anotherleverSwapLimitAmount", anotherleverSwapLimitAmount, leverSwapLimitAmount);
677
+ } else {
678
+ leverSwapLimitAmount = Web3Number.fromWei(0, this.config.debt.decimals);
679
+ }
680
+ await new Promise((resolve) => setTimeout(resolve, 10000));
681
+ console.log("leverSwapLimitAmount", leverSwapLimitAmount);
682
+ } else {
683
+ throw new Error(`VesuMultiplyAdapter: Price impact too high (${swapQuote.price_impact}), skipping swap`);
684
+ }
685
+ } catch (error) {
686
+ throw new Error(`VesuMultiplyAdapter: Failed to get swap quote: ${error}`);
687
+ }
688
+ }
689
+
690
+ const multiplyParams = await this.getLeverParams(isIncrease, params, leverSwap, leverSwapLimitAmount);
691
+ const call = multiplyContract.populate('modify_lever', {
692
+ modify_lever_params: this.formatMultiplyParams(isIncrease, multiplyParams)
693
+ });
694
+
695
+ return call.calldata as bigint[];
696
+ }
697
+
698
+ private async getLeverParams(isIncrease: boolean, params: DepositParams | WithdrawParams, leverSwap: Swap[], leverSwapLimitAmount: Web3Number): Promise<IncreaseLeverParams | DecreaseLeverParams> {
699
+ const multiplyParams: IncreaseLeverParams | DecreaseLeverParams = isIncrease ? {
700
+ user: this.config.vaultAllocator,
701
+ pool_id: this.config.poolId,
702
+ collateral_asset: this.config.collateral.address,
703
+ debt_asset: this.config.debt.address,
704
+ recipient: this.config.vaultAllocator,
705
+ add_margin: params.amount, // multiplied by collateral decimals in format
706
+ margin_swap: [],
707
+ margin_swap_limit_amount: Web3Number.fromWei(0, this.config.collateral.decimals),
708
+ lever_swap: leverSwap,
709
+ lever_swap_limit_amount: leverSwapLimitAmount
710
+ } : {
711
+ user: this.config.vaultAllocator,
712
+ pool_id: this.config.poolId,
713
+ collateral_asset: this.config.collateral.address,
714
+ debt_asset: this.config.debt.address,
715
+ recipient: this.config.vaultAllocator,
716
+ sub_margin: params.amount,
717
+ lever_swap: leverSwap,
718
+ lever_swap_limit_amount: leverSwapLimitAmount,
719
+ lever_swap_weights: [],
720
+ withdraw_swap: [],
721
+ withdraw_swap_limit_amount: Web3Number.fromWei(0, this.config.collateral.decimals),
722
+ withdraw_swap_weights: [],
723
+ close_position: false
724
+ };
725
+ return multiplyParams;
726
+ }
727
+
728
+ private async getWithdrawalCalldata(params: WithdrawParams): Promise<bigint[]> {
729
+ //params.amount must be in btc here
730
+ const { isV2 } = getVesuSingletonAddress(this.config.poolId);
731
+ const vesuMultiply = isV2 ? this.vesuAdapter.VESU_MULTIPLY : this.vesuAdapter.VESU_MULTIPLY_V1;
732
+ const multiplyContract = new Contract({
733
+ abi: VesuMultiplyAbi,
734
+ address: vesuMultiply.address,
735
+ providerOrAccount: this.config.networkConfig.provider
736
+ });
737
+ let leverSwap: Swap[] = [];
738
+ let leverSwapLimitAmount = Web3Number.fromWei(0, this.config.debt.decimals);
739
+ const existingPositions = await this.vesuAdapter.getPositions(this.config.networkConfig);
740
+ const existingCollateralInfo = existingPositions[0];
741
+ const existingDebtInfo = existingPositions[1];
742
+ const collateralToken = this.config.collateral;
743
+ const debtToken = this.config.debt;
744
+ const collateralPrice = await this.config.pricer.getPrice(collateralToken.symbol);
745
+ const debtPrice = await this.config.pricer.getPrice(debtToken.symbol);
746
+ // the debt amount is negative as we are reducing debt to withdraw
747
+ const { deltadebtAmountUnits: debtAmountToRepay } =
748
+ calculateDebtReductionAmountForWithdrawal(
749
+ existingDebtInfo.amount,
750
+ existingCollateralInfo.amount,
751
+ MAX_LIQUIDATION_RATIO,
752
+ params.amount,
753
+ collateralPrice.price,
754
+ debtPrice.price,
755
+ debtToken.decimals
756
+ );
757
+ console.log("debtAmountToRepay", debtAmountToRepay);
758
+ if(!debtAmountToRepay) {
759
+ throw new Error("error calculating debt amount to repay");
760
+ }
761
+ const ekuboQuoter = new EkuboQuoter(this.config.networkConfig, this.config.pricer);
762
+ const debtInDebtUnits = new Web3Number(debtAmountToRepay, debtToken.decimals).dividedBy(debtPrice.price).multipliedBy(10 ** debtToken.decimals);
763
+ const debtInCollateralUnits = new Web3Number(debtAmountToRepay, debtToken.decimals).dividedBy(collateralPrice.price).multipliedBy(10 ** collateralToken.decimals);
764
+ const swapQuote = await ekuboQuoter.getQuote(
765
+ debtToken.address.address,
766
+ collateralToken.address.address,
767
+ debtInDebtUnits
768
+ )
769
+ const MAX_SLIPPAGE = 0.002; // 0.2% slippag
770
+ if(swapQuote.price_impact < 0.025) {
771
+ leverSwap = ekuboQuoter.getVesuMultiplyQuote(swapQuote, debtToken, collateralToken);
772
+ } else {
773
+ logger.error(`VesuMultiplyAdapter: Price impact too high (${swapQuote.price_impact}), skipping swap`);
774
+ }
775
+ const anotherLeverSwapLimitAmount = await ekuboQuoter.getSwapLimitAmount(collateralToken, debtToken, debtInDebtUnits, MAX_SLIPPAGE);
776
+ leverSwapLimitAmount = await ekuboQuoter.getSwapLimitAmount(debtToken, collateralToken, debtInCollateralUnits, MAX_SLIPPAGE);
777
+ const multiplyParams = await this.getLeverParams(false, params, leverSwap, leverSwapLimitAmount);
778
+ const call = multiplyContract.populate('modify_lever', {
779
+ modify_lever_params: this.formatMultiplyParams(false, multiplyParams)
780
+ });
781
+ return call.calldata as bigint[];
782
+ }
783
+
784
+ formatMultiplyParams(isIncrease: boolean, params: IncreaseLeverParams | DecreaseLeverParams) {
785
+ if (isIncrease) {
786
+ const _params = params as IncreaseLeverParams;
787
+ return {
788
+ action: new CairoCustomEnum({ IncreaseLever: {
789
+ pool_id: _params.pool_id.toBigInt(),
790
+ collateral_asset: _params.collateral_asset.toBigInt(),
791
+ debt_asset: _params.debt_asset.toBigInt(),
792
+ user: _params.user.toBigInt(),
793
+ add_margin: BigInt(_params.add_margin.toWei()),
794
+ margin_swap: _params.margin_swap.map(swap => ({
795
+ route: swap.route.map(route => ({
796
+ pool_key: {
797
+ token0: route.pool_key.token0.toBigInt(),
798
+ token1: route.pool_key.token1.toBigInt(),
799
+ fee: route.pool_key.fee,
800
+ tick_spacing: route.pool_key.tick_spacing,
801
+ extension: BigInt(num.hexToDecimalString(route.pool_key.extension)),
802
+ },
803
+ sqrt_ratio_limit: uint256.bnToUint256(route.sqrt_ratio_limit.toWei()),
804
+ skip_ahead: BigInt(100)
805
+ })),
806
+ token_amount: {
807
+ token: swap.token_amount.token.toBigInt(),
808
+ amount: swap.token_amount.amount.toI129()
809
+ }
810
+ })),
811
+ margin_swap_limit_amount: BigInt(_params.margin_swap_limit_amount.toWei()),
812
+ lever_swap: _params.lever_swap.map(swap => ({
813
+ route: swap.route.map(route => ({
814
+ pool_key: {
815
+ token0: route.pool_key.token0.toBigInt(),
816
+ token1: route.pool_key.token1.toBigInt(),
817
+ fee: route.pool_key.fee,
818
+ tick_spacing: route.pool_key.tick_spacing,
819
+ extension: BigInt(num.hexToDecimalString(route.pool_key.extension)),
820
+ },
821
+ sqrt_ratio_limit: uint256.bnToUint256(route.sqrt_ratio_limit.toWei()),
822
+ skip_ahead: BigInt(0)
823
+ })),
824
+ token_amount: {
825
+ token: swap.token_amount.token.toBigInt(),
826
+ amount: swap.token_amount.amount.toI129()
827
+ }
828
+ })),
829
+ lever_swap_limit_amount: BigInt(_params.lever_swap_limit_amount.toWei()),
830
+ } }),
831
+ }
832
+ }
833
+
834
+ const _params = params as DecreaseLeverParams;
835
+ return {
836
+ action: new CairoCustomEnum({ DecreaseLever: {
837
+ pool_id: _params.pool_id.toBigInt(),
838
+ collateral_asset: _params.collateral_asset.toBigInt(),
839
+ debt_asset: _params.debt_asset.toBigInt(),
840
+ user: _params.user.toBigInt(),
841
+ sub_margin: BigInt(_params.sub_margin.toWei()),
842
+ recipient: _params.recipient.toBigInt(),
843
+ lever_swap: _params.lever_swap.map(swap => ({
844
+ route: swap.route.map(route => ({
845
+ pool_key: {
846
+ token0: route.pool_key.token0.toBigInt(),
847
+ token1: route.pool_key.token1.toBigInt(),
848
+ fee: route.pool_key.fee,
849
+ tick_spacing: route.pool_key.tick_spacing,
850
+ extension: ContractAddr.from(route.pool_key.extension).toBigInt(),
851
+ },
852
+ sqrt_ratio_limit: uint256.bnToUint256(route.sqrt_ratio_limit.toWei()),
853
+ skip_ahead: BigInt(route.skip_ahead.toWei())
854
+ })),
855
+ token_amount: {
856
+ token: swap.token_amount.token.toBigInt(),
857
+ amount: swap.token_amount.amount.toI129()
858
+ }
859
+ })),
860
+ lever_swap_limit_amount: BigInt(_params.lever_swap_limit_amount.toWei()),
861
+ lever_swap_weights: _params.lever_swap_weights.map(weight => BigInt(weight.toWei())),
862
+ withdraw_swap: _params.withdraw_swap.map(swap => ({
863
+ route: swap.route.map(route => ({
864
+ pool_key: {
865
+ token0: route.pool_key.token0.toBigInt(),
866
+ token1: route.pool_key.token1.toBigInt(),
867
+ fee: route.pool_key.fee,
868
+ tick_spacing: route.pool_key.tick_spacing,
869
+ extension: ContractAddr.from(route.pool_key.extension).toBigInt(),
870
+ },
871
+ sqrt_ratio_limit: uint256.bnToUint256(route.sqrt_ratio_limit.toWei()),
872
+ skip_ahead: BigInt(route.skip_ahead.toWei())
873
+ })),
874
+ token_amount: {
875
+ token: swap.token_amount.token.toBigInt(),
876
+ amount: swap.token_amount.amount.toI129()
877
+ }
878
+ })),
879
+ withdraw_swap_limit_amount: BigInt(_params.withdraw_swap_limit_amount.toWei()),
880
+ withdraw_swap_weights: _params.withdraw_swap_weights.map(weight => BigInt(weight.toWei())),
881
+ close_position: _params.close_position,
882
+ }}),
883
+ }
884
+ }
885
+
886
+ async getHealthFactor(): Promise<number> {
887
+ const healthFactor = await this.vesuAdapter.getHealthFactor();
888
+ return healthFactor;
889
+ }
890
+
891
+ async getNetAPY(): Promise<number> {
892
+ const positions = await this.getPositions();
893
+ logger.verbose(`${this.name}::getNetAPY: positions: ${JSON.stringify(positions)}`);
894
+ const allZero = positions.every(p => p.usdValue === 0);
895
+
896
+ // in case of zero positions, apy will come zero/NaN
897
+ // bcz of net 0 zero weights
898
+ if (allZero) {
899
+ // use approx dummy usd values to compute netAPY
900
+ const collateralUSD = 1000;
901
+ const maxLTV = await this.vesuAdapter.getLTVConfig(this.config.networkConfig);
902
+ const targetHF = this.config.targetHealthFactor;
903
+ const maxDebt = HealthFactorMath.getMaxDebtAmountOnLooping(
904
+ new Web3Number(collateralUSD, this.config.collateral.decimals),
905
+ 1, // assume price 1 for simplicity
906
+ maxLTV,
907
+ targetHF,
908
+ 1, // assume price 1 for simplicity
909
+ this.config.debt
910
+ )
911
+
912
+ // debt is also added to collateral bcz, we assume debt is swapped to collateral
913
+ const debtUSD = maxDebt.multipliedBy(1); // assume price 1 for simplicity
914
+ const netAPY = (positions[0].apy.apy * (collateralUSD + debtUSD.toNumber()) + positions[1].apy.apy * debtUSD.toNumber()) / (collateralUSD);
915
+ return netAPY;
916
+ }
917
+
918
+ // Return true APY
919
+ const netAmount = positions.reduce((acc, curr) => acc + curr.usdValue, 0);
920
+ const netAPY = positions.reduce((acc, curr) => acc + curr.apy.apy * curr.usdValue, 0) / netAmount;
921
+ logger.verbose(`${this.name}::getNetAPY: netAPY: ${netAPY}`);
922
+ return netAPY;
923
+ }
924
+ }