@strkfarm/sdk 1.2.0 → 2.0.0-dev-strategy2.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 (60) hide show
  1. package/dist/index.browser.global.js +76556 -66640
  2. package/dist/index.browser.mjs +34235 -24392
  3. package/dist/index.d.ts +2372 -793
  4. package/dist/index.js +31967 -22084
  5. package/dist/index.mjs +25545 -15719
  6. package/package.json +86 -76
  7. package/readme.md +56 -1
  8. package/src/data/extended-deposit.abi.json +3613 -0
  9. package/src/data/universal-vault.abi.json +135 -20
  10. package/src/dataTypes/_bignumber.ts +11 -0
  11. package/src/dataTypes/address.ts +7 -0
  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 +448 -0
  17. package/src/modules/avnu.ts +17 -4
  18. package/src/modules/ekubo-quoter.ts +89 -10
  19. package/src/modules/erc20.ts +67 -21
  20. package/src/modules/harvests.ts +29 -43
  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/btc-vesu-extended-strategy/core-strategy.tsx +1486 -0
  32. package/src/strategies/btc-vesu-extended-strategy/services/operationService.ts +32 -0
  33. package/src/strategies/btc-vesu-extended-strategy/utils/constants.ts +3 -0
  34. package/src/strategies/btc-vesu-extended-strategy/utils/helper.ts +396 -0
  35. package/src/strategies/btc-vesu-extended-strategy/utils/types.ts +5 -0
  36. package/src/strategies/ekubo-cl-vault.tsx +123 -306
  37. package/src/strategies/index.ts +7 -1
  38. package/src/strategies/svk-strategy.ts +247 -0
  39. package/src/strategies/universal-adapters/adapter-optimizer.ts +65 -0
  40. package/src/strategies/universal-adapters/adapter-utils.ts +5 -1
  41. package/src/strategies/universal-adapters/avnu-adapter.ts +432 -0
  42. package/src/strategies/universal-adapters/baseAdapter.ts +181 -153
  43. package/src/strategies/universal-adapters/common-adapter.ts +98 -77
  44. package/src/strategies/universal-adapters/extended-adapter.ts +976 -0
  45. package/src/strategies/universal-adapters/index.ts +7 -1
  46. package/src/strategies/universal-adapters/unused-balance-adapter.ts +109 -0
  47. package/src/strategies/universal-adapters/vesu-adapter.ts +230 -230
  48. package/src/strategies/universal-adapters/vesu-borrow-adapter.ts +1247 -0
  49. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1306 -0
  50. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +58 -51
  51. package/src/strategies/universal-lst-muliplier-strategy.tsx +716 -844
  52. package/src/strategies/universal-strategy.tsx +1103 -1181
  53. package/src/strategies/vesu-extended-strategy/services/operationService.ts +34 -0
  54. package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +25 -0
  55. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +77 -0
  56. package/src/strategies/vesu-extended-strategy/utils/constants.ts +50 -0
  57. package/src/strategies/vesu-extended-strategy/utils/helper.ts +367 -0
  58. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +1420 -0
  59. package/src/strategies/vesu-rebalance.tsx +16 -20
  60. package/src/utils/health-factor-math.ts +11 -5
@@ -0,0 +1,1306 @@
1
+ import { ContractAddr, Web3Number } from "@/dataTypes";
2
+ import { IConfig, Protocols, TokenInfo } from "@/interfaces";
3
+ import { PricerBase } from "@/modules/pricerBase";
4
+ import {
5
+ BaseAdapter,
6
+ BaseAdapterConfig,
7
+ SupportedPosition,
8
+ PositionInfo,
9
+ PositionAPY,
10
+ APYType,
11
+ ManageCall,
12
+ AdapterLeafType,
13
+ GenerateCallFn,
14
+ DepositParams,
15
+ WithdrawParams,
16
+ PositionAmount,
17
+ } from "./baseAdapter";
18
+ import {
19
+ SIMPLE_SANITIZER,
20
+ SIMPLE_SANITIZER_V2,
21
+ toBigInt,
22
+ VESU_SINGLETON,
23
+ VESU_V2_MODIFY_POSITION_SANITIZER,
24
+ } from "./adapter-utils";
25
+ import { hash, uint256, Contract, CairoCustomEnum, num } from "starknet";
26
+ import {
27
+ VesuAdapter,
28
+ VesuMultiplyCallParams,
29
+ VesuModifyDelegationCallParams,
30
+ getVesuSingletonAddress,
31
+ VesuPools,
32
+ Swap,
33
+ IncreaseLeverParams,
34
+ DecreaseLeverParams,
35
+ } from "./vesu-adapter";
36
+ import { logger } from "@/utils";
37
+ import { WALLET_ADDRESS, WBTC_TOKEN_DECIMALS } from "../vesu-extended-strategy/utils/constants";
38
+ import VesuMultiplyAbi from "@/data/vesu-multiple.abi.json";
39
+ import VesuSingletonAbi from "../../data/vesu-singleton.abi.json";
40
+ import VesuPoolV2Abi from "@/data/vesu-pool-v2.abi.json";
41
+ import { EkuboQuoter, TokenMarketData } from "@/modules";
42
+ import { calculateDebtReductionAmountForWithdrawal } from "../vesu-extended-strategy/utils/helper";
43
+ import { HealthFactorMath } from "@/utils/health-factor-math";
44
+ import { MAX_LIQUIDATION_RATIO } from "../vesu-extended-strategy/utils/constants";
45
+
46
+ export interface VesuMultiplyAdapterConfig extends BaseAdapterConfig {
47
+ poolId: ContractAddr;
48
+ collateral: TokenInfo;
49
+ debt: TokenInfo;
50
+ targetHealthFactor: number;
51
+ minHealthFactor: number;
52
+ quoteAmountToFetchPrice: Web3Number;
53
+ minimumVesuMovementAmount: number;
54
+ }
55
+
56
+ export class VesuMultiplyAdapter extends BaseAdapter<
57
+ DepositParams,
58
+ WithdrawParams
59
+ > {
60
+ readonly config: VesuMultiplyAdapterConfig;
61
+ readonly vesuAdapter: VesuAdapter;
62
+ readonly tokenMarketData: TokenMarketData;
63
+ readonly minimumVesuMovementAmount: number;
64
+ constructor(config: VesuMultiplyAdapterConfig) {
65
+ super(config, VesuMultiplyAdapter.name, Protocols.VESU);
66
+ this.config = config;
67
+ this.vesuAdapter = new VesuAdapter({
68
+ poolId: config.poolId,
69
+ collateral: config.collateral,
70
+ debt: config.debt,
71
+ vaultAllocator: config.vaultAllocator,
72
+ id: "",
73
+ });
74
+ this.minimumVesuMovementAmount = config.minimumVesuMovementAmount ?? 5; //5 usdc
75
+ this.tokenMarketData = new TokenMarketData(
76
+ this.config.pricer,
77
+ this.config.networkConfig
78
+ );
79
+ }
80
+
81
+ protected async getAPY(
82
+ supportedPosition: SupportedPosition
83
+ ): Promise<PositionAPY> {
84
+ const CACHE_KEY = `apy_${this.config.poolId.address}_${supportedPosition.asset.symbol}`;
85
+ const cacheData = this.getCache<PositionAPY>(CACHE_KEY);
86
+ console.log(
87
+ `${VesuMultiplyAdapter.name}::getAPY cacheData: ${JSON.stringify(
88
+ cacheData
89
+ )}`,
90
+ this.vesuAdapter.config.poolId.shortString(),
91
+ this.vesuAdapter.config.collateral.symbol,
92
+ this.vesuAdapter.config.debt.symbol
93
+ );
94
+ if (cacheData) {
95
+ return cacheData;
96
+ }
97
+ try {
98
+ // Get Vesu pools to find APY for the asset
99
+ const allVesuPools = await VesuAdapter.getVesuPools();
100
+ const asset = supportedPosition.asset;
101
+ const pool = allVesuPools.pools.find((p) =>
102
+ this.vesuAdapter.config.poolId.eqString(num.getHexString(p.id))
103
+ );
104
+ if (!pool) {
105
+ logger.warn(
106
+ `VesuMultiplyAdapter: Pool not found for token ${asset.symbol}`
107
+ );
108
+ return {
109
+ apy: 0,
110
+ type: APYType.BASE,
111
+ };
112
+ }
113
+ // Find the asset stats for our token
114
+ const assetStats = pool.assets.find(
115
+ (a: any) => a.symbol.toLowerCase() === asset.symbol.toLowerCase()
116
+ )?.stats;
117
+
118
+ if (!assetStats) {
119
+ logger.warn(
120
+ `VesuMultiplyAdapter: Asset stats not found for token ${asset.symbol}`
121
+ );
122
+ return {
123
+ apy: 0,
124
+ type: APYType.BASE,
125
+ };
126
+ }
127
+ // Get appropriate APY based on position type
128
+ let apy = 0;
129
+ if (supportedPosition.isDebt) {
130
+ // For debt positions, use borrow APY
131
+ apy = Number(assetStats.borrowApr?.value || 0) / 1e18;
132
+
133
+ // todo
134
+ // Account for rewards on debt token
135
+ } else {
136
+ // For collateral positions, use supply APY
137
+ const isAssetBTC = asset.symbol.toLowerCase().includes("btc");
138
+ const baseAPY =
139
+ Number(
140
+ isAssetBTC
141
+ ? assetStats.btcFiSupplyApr?.value + assetStats.supplyApy?.value
142
+ : assetStats.supplyApy?.value || 0
143
+ ) / 1e18;
144
+
145
+ // account for reward yield (like STRK rewards)
146
+ const rewardAPY =
147
+ Number(assetStats.defiSpringSupplyApr?.value || "0") / 1e18;
148
+
149
+ // account for base yield of LST
150
+ const isSupported = this.tokenMarketData.isAPYSupported(asset);
151
+ apy = baseAPY + rewardAPY;
152
+ if (isSupported) {
153
+ const tokenAPY = await this.tokenMarketData.getAPY(asset);
154
+ apy += tokenAPY;
155
+ }
156
+ }
157
+ const result = {
158
+ apy,
159
+ type: supportedPosition.isDebt ? APYType.BASE : APYType.BASE,
160
+ };
161
+
162
+ this.setCache(CACHE_KEY, result, 300000); // Cache for 5 minutes
163
+ return result;
164
+ } catch (error) {
165
+ logger.error(
166
+ `VesuMultiplyAdapter: Error getting APY for ${supportedPosition.asset.symbol}:`,
167
+ error
168
+ );
169
+ // return {
170
+ // apy: 0,
171
+ // type: APYType.BASE
172
+ // };
173
+ throw error;
174
+ }
175
+ }
176
+
177
+ protected async getPosition(
178
+ supportedPosition: SupportedPosition
179
+ ): Promise<PositionAmount> {
180
+ const CACHE_KEY = `position_${this.config.poolId.address}_${supportedPosition.asset.symbol}`;
181
+ const cacheData = this.getCache<PositionAmount>(CACHE_KEY);
182
+ if (cacheData) {
183
+ return cacheData;
184
+ }
185
+
186
+ try {
187
+ // Use VesuAdapter to get positions
188
+ this.vesuAdapter.networkConfig = this.config.networkConfig;
189
+ this.vesuAdapter.pricer = this.config.pricer;
190
+
191
+ const positions = await this.vesuAdapter.getPositions(
192
+ this.config.networkConfig
193
+ );
194
+
195
+ // Find the position for our asset
196
+ let position = positions.find((p) =>
197
+ p.token.address.eq(supportedPosition.asset.address)
198
+ );
199
+
200
+ if (!position) {
201
+ logger.warn(
202
+ `VesuMultiplyAdapter: Position not found for token ${supportedPosition.asset.symbol}`
203
+ );
204
+ return {
205
+ amount: new Web3Number("0", supportedPosition.asset.decimals),
206
+ remarks: "Position not found",
207
+ };
208
+ }
209
+
210
+ if (supportedPosition.isDebt) {
211
+ position.amount = position.amount.multipliedBy(-1);
212
+ position.usdValue = position.usdValue * -1;
213
+ }
214
+
215
+ this.setCache(CACHE_KEY, position, 60000); // Cache for 1 minute
216
+ return position;
217
+ } catch (error) {
218
+ logger.error(
219
+ `VesuMultiplyAdapter: Error getting position for ${supportedPosition.asset.symbol}:`,
220
+ error
221
+ );
222
+ // return new Web3Number('0', supportedPosition.asset.decimals);
223
+ throw error;
224
+ }
225
+ }
226
+
227
+ async maxBorrowableAPY(): Promise<number> {
228
+ // get collateral APY
229
+ const collateralAPY = await this.getAPY({
230
+ asset: this.config.collateral,
231
+ isDebt: false,
232
+ });
233
+ const apy = collateralAPY.apy * 0.8;
234
+ return apy;
235
+ }
236
+
237
+ async maxDeposit(amount?: Web3Number): Promise<PositionInfo> {
238
+ const collateral = this.config.collateral;
239
+ const debt = this.config.debt;
240
+
241
+ try {
242
+ // Get current positions
243
+ this.vesuAdapter.networkConfig = this.config.networkConfig;
244
+ this.vesuAdapter.pricer = this.config.pricer;
245
+
246
+ const positions = await this.vesuAdapter.getPositions(
247
+ this.config.networkConfig
248
+ );
249
+ const collateralPosition = positions.find((p) =>
250
+ p.token.address.eq(collateral.address)
251
+ );
252
+ const debtPosition = positions.find((p) =>
253
+ p.token.address.eq(debt.address)
254
+ );
255
+
256
+ if (!collateralPosition || !debtPosition) {
257
+ throw new Error("Could not find current positions");
258
+ }
259
+
260
+ // Calculate max borrowable amount
261
+ const maxBorrowableAPY = await this.maxBorrowableAPY();
262
+ const maxBorrowable =
263
+ await this.vesuAdapter.getMaxBorrowableByInterestRate(
264
+ this.config.networkConfig,
265
+ debt,
266
+ maxBorrowableAPY
267
+ );
268
+ logger.verbose(
269
+ `VesuMultiplyAdapter: Max borrowable: ${maxBorrowable.toNumber()}`
270
+ );
271
+ const debtCap = await this.vesuAdapter.getDebtCap(
272
+ this.config.networkConfig
273
+ );
274
+ logger.verbose(`VesuMultiplyAdapter: Debt cap: ${debtCap.toNumber()}`);
275
+ const actualMaxBorrowable = maxBorrowable.minimum(debtCap);
276
+ logger.verbose(
277
+ `VesuMultiplyAdapter: Actual max borrowable: ${actualMaxBorrowable.toNumber()}`
278
+ );
279
+
280
+ // Calculate max collateral that can be deposited based on LTV
281
+ const maxLTV = await this.vesuAdapter.getLTVConfig(
282
+ this.config.networkConfig
283
+ );
284
+ const collateralPrice = await this.config.pricer.getPrice(
285
+ collateral.symbol
286
+ );
287
+ if (collateralPrice.price === 0) {
288
+ throw new Error("Collateral price is 0");
289
+ }
290
+ const debtPrice = await this.config.pricer.getPrice(debt.symbol);
291
+ if (debtPrice.price === 0) {
292
+ throw new Error("Debt price is 0");
293
+ }
294
+ const maxCollateralFromDebt =
295
+ HealthFactorMath.getMinCollateralRequiredOnLooping(
296
+ actualMaxBorrowable,
297
+ debtPrice.price,
298
+ this.config.targetHealthFactor,
299
+ maxLTV,
300
+ collateralPrice.price,
301
+ collateral
302
+ );
303
+
304
+ const maxDepositAmount = amount
305
+ ? amount.minimum(maxCollateralFromDebt)
306
+ : maxCollateralFromDebt;
307
+ const usdValue = await this.getUSDValue(collateral, maxDepositAmount);
308
+ logger.verbose(
309
+ `VesuMultiplyAdapter: Max deposit::USD value: ${usdValue}, amount: ${maxDepositAmount.toNumber()}`
310
+ );
311
+ const apys = await Promise.all([
312
+ this.getAPY({ asset: collateral, isDebt: false }),
313
+ this.getAPY({ asset: debt, isDebt: true }),
314
+ ]);
315
+ logger.verbose(
316
+ `VesuMultiplyAdapter: Apys: ${apys[0].apy}, ${apys[1].apy}`
317
+ );
318
+
319
+ const borrowAmountUSD = actualMaxBorrowable.multipliedBy(debtPrice.price);
320
+ logger.verbose(
321
+ `VesuMultiplyAdapter: Borrow amount: ${actualMaxBorrowable.toNumber()}, borrow amount USD: ${borrowAmountUSD.toNumber()}`
322
+ );
323
+ const netCollateralUSD = usdValue + borrowAmountUSD.toNumber();
324
+ const netAPY =
325
+ (apys[0].apy * netCollateralUSD +
326
+ apys[1].apy * borrowAmountUSD.toNumber()) /
327
+ usdValue;
328
+ logger.verbose(
329
+ `VesuMultiplyAdapter: Max deposit amount: ${maxDepositAmount.toNumber()}, netAPY: ${netAPY}`
330
+ );
331
+ return {
332
+ tokenInfo: collateral,
333
+ amount: maxDepositAmount,
334
+ usdValue,
335
+ remarks: "Max deposit based on available debt capacity",
336
+ apy: {
337
+ apy: netAPY,
338
+ type: APYType.BASE,
339
+ },
340
+ protocol: this.protocol,
341
+ };
342
+ } catch (error) {
343
+ logger.error(
344
+ `VesuMultiplyAdapter: Error calculating max deposit:`,
345
+ error
346
+ );
347
+ throw error;
348
+ }
349
+ }
350
+
351
+ async maxWithdraw(): Promise<PositionInfo> {
352
+ const collateral = this.config.collateral;
353
+ const debt = this.config.debt;
354
+
355
+ try {
356
+ // Calculate how much can be withdrawn without affecting health factor too much
357
+ this.vesuAdapter.networkConfig = this.config.networkConfig;
358
+ this.vesuAdapter.pricer = this.config.pricer;
359
+
360
+ const positions = await this.vesuAdapter.getPositions(
361
+ this.config.networkConfig
362
+ );
363
+ const collateralPosition = positions.find((p) =>
364
+ p.token.address.eq(collateral.address)
365
+ );
366
+ const debtPosition = positions.find((p) =>
367
+ p.token.address.eq(this.config.debt.address)
368
+ );
369
+
370
+ if (!collateralPosition || !debtPosition) {
371
+ throw new Error("Could not find current positions");
372
+ }
373
+
374
+ // Calculate max withdrawable (conservative approach)
375
+ const collateralPrice =
376
+ collateralPosition.usdValue / collateralPosition.amount.toNumber();
377
+ const debtInCollateral = debtPosition.usdValue / collateralPrice;
378
+ const maxWithdrawable = collateralPosition.amount.minus(debtInCollateral);
379
+
380
+ const result = maxWithdrawable.greaterThan(0)
381
+ ? maxWithdrawable
382
+ : new Web3Number("0", collateral.decimals);
383
+ const usdValue = await this.getUSDValue(collateral, result);
384
+ const debtUSD = debtPosition.usdValue;
385
+ logger.verbose(
386
+ `VesuMultiplyAdapter: Debt USD: ${debtUSD}, collateral USD: ${usdValue}`
387
+ );
388
+ const apys = await Promise.all([
389
+ this.getAPY({ asset: collateral, isDebt: false }),
390
+ this.getAPY({ asset: debt, isDebt: true }),
391
+ ]);
392
+ logger.verbose(
393
+ `VesuMultiplyAdapter: Apys: ${apys[0].apy}, ${apys[1].apy}`
394
+ );
395
+ const netAPY =
396
+ usdValue - debtUSD > 0
397
+ ? (apys[0].apy * usdValue + apys[1].apy * debtUSD) /
398
+ (usdValue - debtUSD)
399
+ : 0;
400
+ logger.verbose(
401
+ `VesuMultiplyAdapter: Max withdraw amount: ${result.toNumber()}, netAPY: ${netAPY}`
402
+ );
403
+ return {
404
+ tokenInfo: collateral,
405
+ amount: result,
406
+ usdValue,
407
+ remarks: "Max withdraw based on health factor",
408
+ apy: {
409
+ apy: netAPY,
410
+ type: APYType.BASE,
411
+ },
412
+ protocol: this.protocol,
413
+ };
414
+ } catch (error) {
415
+ logger.error(
416
+ `VesuMultiplyAdapter: Error calculating max withdraw:`,
417
+ error
418
+ );
419
+ throw error;
420
+ }
421
+ }
422
+
423
+ protected _getDepositLeaf(): {
424
+ target: ContractAddr;
425
+ method: string;
426
+ packedArguments: bigint[];
427
+ sanitizer: ContractAddr;
428
+ id: string;
429
+ }[] {
430
+ const collateral = this.config.collateral;
431
+ const debt = this.config.debt;
432
+ const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(
433
+ this.config.poolId
434
+ );
435
+ const vesuMultiply = isV2
436
+ ? this.vesuAdapter.VESU_MULTIPLY
437
+ : this.vesuAdapter.VESU_MULTIPLY_V1;
438
+
439
+ return [
440
+ // Approval step for collateral
441
+ {
442
+ target: collateral.address,
443
+ method: "approve",
444
+ packedArguments: [
445
+ vesuMultiply.toBigInt(), // spender
446
+ ],
447
+ sanitizer: SIMPLE_SANITIZER,
448
+ // amc = approve multiply collateral
449
+ id: `amc_${this.config.poolId.shortString()}_${collateral.symbol}_${
450
+ debt.symbol
451
+ }`,
452
+ },
453
+ // Switch delegation on
454
+ {
455
+ target: vesuSingleton,
456
+ method: "modify_delegation",
457
+ packedArguments: isV2
458
+ ? [
459
+ vesuMultiply.toBigInt(), // delegatee
460
+ ]
461
+ : [
462
+ this.config.poolId.toBigInt(),
463
+ vesuMultiply.toBigInt(), // delegatee
464
+ ],
465
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
466
+ // sd1 = switch delegation on
467
+ id: `sd1_${this.config.poolId.shortString()}_${collateral.symbol}_${
468
+ debt.symbol
469
+ }`,
470
+ },
471
+ // Vesu multiply call
472
+ {
473
+ target: vesuMultiply,
474
+ method: "modify_lever",
475
+ packedArguments: [
476
+ this.config.poolId.toBigInt(),
477
+ collateral.address.toBigInt(),
478
+ this.config.debt.address.toBigInt(),
479
+ this.config.vaultAllocator.toBigInt(),
480
+ ],
481
+ sanitizer: SIMPLE_SANITIZER_V2,
482
+ // vm = vesu multiply
483
+ id: `vm_${this.config.poolId.shortString()}_${collateral.symbol}_${
484
+ debt.symbol
485
+ }`,
486
+ },
487
+ // Switch delegation off
488
+ {
489
+ target: vesuSingleton,
490
+ method: "modify_delegation",
491
+ packedArguments: isV2
492
+ ? [
493
+ vesuMultiply.toBigInt(), // delegatee
494
+ ]
495
+ : [
496
+ this.config.poolId.toBigInt(),
497
+ vesuMultiply.toBigInt(), // delegatee
498
+ ],
499
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
500
+ // sd2 = switch delegation off
501
+ id: `sd2_${this.config.poolId.shortString()}_${collateral.symbol}_${
502
+ debt.symbol
503
+ }`,
504
+ },
505
+ ];
506
+ }
507
+
508
+ protected _getWithdrawLeaf(): {
509
+ target: ContractAddr;
510
+ method: string;
511
+ packedArguments: bigint[];
512
+ sanitizer: ContractAddr;
513
+ id: string;
514
+ }[] {
515
+ const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(
516
+ this.config.poolId
517
+ );
518
+ const vesuMultiply = isV2
519
+ ? this.vesuAdapter.VESU_MULTIPLY
520
+ : this.vesuAdapter.VESU_MULTIPLY_V1;
521
+ const collateral = this.config.collateral;
522
+ const debt = this.config.debt;
523
+
524
+ return [
525
+ // Switch delegation on
526
+ {
527
+ target: vesuSingleton,
528
+ method: "modify_delegation",
529
+ packedArguments: isV2
530
+ ? [
531
+ vesuMultiply.toBigInt(), // delegatee
532
+ ]
533
+ : [
534
+ this.config.poolId.toBigInt(),
535
+ vesuMultiply.toBigInt(), // delegatee
536
+ ],
537
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
538
+ // sdow = switch delegation on withdraw
539
+ id: `sdow_${this.config.poolId.shortString()}_${collateral.symbol}_${
540
+ debt.symbol
541
+ }`,
542
+ },
543
+ // Vesu multiply call
544
+ {
545
+ target: vesuMultiply,
546
+ method: "modify_lever",
547
+ packedArguments: [
548
+ this.config.poolId.toBigInt(),
549
+ this.config.collateral.address.toBigInt(),
550
+ this.config.debt.address.toBigInt(),
551
+ this.config.vaultAllocator.toBigInt(),
552
+ ],
553
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
554
+ // vmw = vesu multiply withdraw
555
+ id: `vmw_${this.config.poolId.shortString()}_${collateral.symbol}_${
556
+ debt.symbol
557
+ }`,
558
+ },
559
+ // Switch delegation off
560
+ {
561
+ target: vesuSingleton,
562
+ method: "modify_delegation",
563
+ packedArguments: isV2
564
+ ? [
565
+ vesuMultiply.toBigInt(), // delegatee
566
+ ]
567
+ : [
568
+ this.config.poolId.toBigInt(),
569
+ vesuMultiply.toBigInt(), // delegatee
570
+ ],
571
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
572
+ // sdofw = switch delegation off withdraw
573
+ id: `sdofw_${this.config.poolId.shortString()}_${collateral.symbol}_${
574
+ debt.symbol
575
+ }`,
576
+ },
577
+ ];
578
+ }
579
+
580
+ getDepositAdapter(): AdapterLeafType<DepositParams> {
581
+ const leafConfigs = this._getDepositLeaf();
582
+ const leaves = leafConfigs.map((config) => {
583
+ const { target, method, packedArguments, sanitizer, id } = config;
584
+ const leaf = this.constructSimpleLeafData(
585
+ {
586
+ id: id,
587
+ target,
588
+ method,
589
+ packedArguments,
590
+ },
591
+ sanitizer
592
+ );
593
+ return leaf;
594
+ });
595
+ return {
596
+ leaves,
597
+ callConstructor: this.getDepositCall.bind(
598
+ this
599
+ ) as unknown as GenerateCallFn<DepositParams>,
600
+ };
601
+ }
602
+
603
+ getWithdrawAdapter(): AdapterLeafType<WithdrawParams> {
604
+ const leafConfigs = this._getWithdrawLeaf();
605
+ const leaves = leafConfigs.map((config) => {
606
+ const { target, method, packedArguments, sanitizer, id } = config;
607
+ const leaf = this.constructSimpleLeafData(
608
+ {
609
+ id: id,
610
+ target,
611
+ method,
612
+ packedArguments,
613
+ },
614
+ sanitizer
615
+ );
616
+ return leaf;
617
+ });
618
+ return {
619
+ leaves,
620
+ callConstructor: this.getWithdrawCall.bind(
621
+ this
622
+ ) as unknown as GenerateCallFn<WithdrawParams>,
623
+ };
624
+ }
625
+
626
+ async getDepositCall(params: DepositParams): Promise<ManageCall[]> {
627
+ const collateral = this.config.collateral;
628
+ const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(
629
+ this.config.poolId
630
+ );
631
+ const vesuMultiply = isV2
632
+ ? this.vesuAdapter.VESU_MULTIPLY
633
+ : this.vesuAdapter.VESU_MULTIPLY_V1;
634
+
635
+ const uint256MarginAmount = uint256.bnToUint256(params.amount.toWei());
636
+
637
+ return [
638
+ // Approval call
639
+ {
640
+ sanitizer: SIMPLE_SANITIZER,
641
+ call: {
642
+ contractAddress: collateral.address,
643
+ selector: hash.getSelectorFromName("approve"),
644
+ calldata: [
645
+ vesuMultiply.toBigInt(), // spender
646
+ toBigInt(uint256MarginAmount.low.toString()), // amount low
647
+ toBigInt(uint256MarginAmount.high.toString()), // amount high
648
+ ],
649
+ },
650
+ },
651
+ // Switch delegation on
652
+ {
653
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
654
+ call: {
655
+ contractAddress: vesuSingleton,
656
+ selector: hash.getSelectorFromName("modify_delegation"),
657
+ calldata: isV2
658
+ ? [
659
+ vesuMultiply.toBigInt(), // delegatee
660
+ BigInt(1), // delegation: true
661
+ ]
662
+ : [
663
+ this.config.poolId.toBigInt(),
664
+ vesuMultiply.toBigInt(), // delegatee
665
+ BigInt(1), // delegation: true
666
+ ],
667
+ },
668
+ },
669
+ // Vesu multiply call
670
+ {
671
+ sanitizer: SIMPLE_SANITIZER_V2,
672
+ call: {
673
+ contractAddress: vesuMultiply,
674
+ selector: hash.getSelectorFromName("modify_lever"),
675
+ calldata: await this.getMultiplyCallCalldata(params, true),
676
+ },
677
+ },
678
+ // Switch delegation off
679
+ {
680
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
681
+ call: {
682
+ contractAddress: vesuSingleton,
683
+ selector: hash.getSelectorFromName("modify_delegation"),
684
+ calldata: isV2
685
+ ? [
686
+ vesuMultiply.toBigInt(), // delegatee
687
+ BigInt(0), // delegation: false
688
+ ]
689
+ : [
690
+ this.config.poolId.toBigInt(),
691
+ vesuMultiply.toBigInt(), // delegatee
692
+ BigInt(0), // delegation: false
693
+ ],
694
+ },
695
+ },
696
+ ];
697
+ }
698
+
699
+ async getWithdrawCall(params: WithdrawParams): Promise<ManageCall[]> {
700
+ const { addr: vesuSingleton, isV2 } = getVesuSingletonAddress(
701
+ this.config.poolId
702
+ );
703
+ const vesuMultiply = isV2
704
+ ? this.vesuAdapter.VESU_MULTIPLY
705
+ : this.vesuAdapter.VESU_MULTIPLY_V1;
706
+
707
+ return [
708
+ // Switch delegation on
709
+ {
710
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
711
+ call: {
712
+ contractAddress: vesuSingleton,
713
+ selector: hash.getSelectorFromName("modify_delegation"),
714
+ calldata: isV2
715
+ ? [
716
+ vesuMultiply.toBigInt(), // delegatee
717
+ BigInt(1), // delegation: true
718
+ ]
719
+ : [
720
+ this.config.poolId.toBigInt(),
721
+ vesuMultiply.toBigInt(), // delegatee
722
+ BigInt(1), // delegation: true
723
+ ],
724
+ },
725
+ },
726
+ // Vesu multiply call
727
+ {
728
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
729
+ call: {
730
+ contractAddress: vesuMultiply,
731
+ selector: hash.getSelectorFromName("modify_lever"),
732
+ calldata: await this.getWithdrawalCalldata(params),
733
+ },
734
+ },
735
+ // Switch delegation off
736
+ {
737
+ sanitizer: isV2 ? SIMPLE_SANITIZER_V2 : SIMPLE_SANITIZER,
738
+ call: {
739
+ contractAddress: vesuSingleton,
740
+ selector: hash.getSelectorFromName("modify_delegation"),
741
+ calldata: isV2
742
+ ? [
743
+ vesuMultiply.toBigInt(), // delegatee
744
+ BigInt(0), // delegation: false
745
+ ]
746
+ : [
747
+ this.config.poolId.toBigInt(),
748
+ vesuMultiply.toBigInt(), // delegatee
749
+ BigInt(0), // delegation: false
750
+ ],
751
+ },
752
+ },
753
+ ];
754
+ }
755
+
756
+ private async getMultiplyCallCalldata(
757
+ params: DepositParams | WithdrawParams,
758
+ isDeposit: boolean
759
+ ): Promise<bigint[]> {
760
+ logger.verbose(
761
+ `${
762
+ VesuMultiplyAdapter.name
763
+ }::getMultiplyCallCalldata params: ${JSON.stringify(
764
+ params
765
+ )}, isDeposit: ${isDeposit}, collateral: ${
766
+ this.config.collateral.symbol
767
+ }, debt: ${this.config.debt.symbol}`
768
+ );
769
+ const { isV2 } = getVesuSingletonAddress(this.config.poolId);
770
+ const vesuMultiply = isV2
771
+ ? this.vesuAdapter.VESU_MULTIPLY
772
+ : this.vesuAdapter.VESU_MULTIPLY_V1;
773
+
774
+ // Create a temporary contract instance to populate the call
775
+ const multiplyContract = new Contract({
776
+ abi: VesuMultiplyAbi,
777
+ address: vesuMultiply.address,
778
+ providerOrAccount: this.config.networkConfig.provider,
779
+ });
780
+
781
+ // Configure swaps based on the operation
782
+ let leverSwap: Swap[] = [];
783
+ let leverSwapLimitAmount = Web3Number.fromWei(0, this.config.debt.decimals);
784
+
785
+ const existingPositions = await this.vesuAdapter.getPositions(
786
+ this.config.networkConfig
787
+ );
788
+ const collateralisation = await this.vesuAdapter.getCollateralization(
789
+ this.config.networkConfig
790
+ );
791
+ const existingCollateralInfo = existingPositions[0];
792
+ const existingDebtInfo = existingPositions[1];
793
+ const isDexPriceRequired = existingDebtInfo.token.symbol !== "USDC";
794
+ logger.debug(`${
795
+ VesuMultiplyAdapter.name
796
+ }::getVesuMultiplyCall existingCollateralInfo: ${JSON.stringify(
797
+ existingCollateralInfo
798
+ )},
799
+ existingDebtInfo: ${JSON.stringify(
800
+ existingDebtInfo
801
+ )}, collateralisation: ${JSON.stringify(collateralisation)}`);
802
+
803
+ // - Prices as seen by Vesu contracts, ideal for HF math
804
+ // Price 1 is ok as fallback bcz that would relatively price the
805
+ // collateral and debt as equal.
806
+ const collateralPrice =(await this.config.pricer.getPrice(this.config.collateral.symbol))
807
+ .price;
808
+ const debtPrice = (await this.config.pricer.getPrice(this.config.debt.symbol)).price;
809
+ logger.debug(
810
+ `${VesuMultiplyAdapter.name}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`
811
+ );
812
+
813
+ const legLTV = await this.vesuAdapter.getLTVConfig(
814
+ this.config.networkConfig
815
+ );
816
+ const ekuboQuoter = new EkuboQuoter(
817
+ this.config.networkConfig,
818
+ this.config.pricer
819
+ );
820
+ const dexPrice = isDexPriceRequired
821
+ ? await ekuboQuoter.getDexPrice(
822
+ this.config.collateral,
823
+ this.config.debt,
824
+ this.config.quoteAmountToFetchPrice
825
+ )
826
+ : 1;
827
+ logger.verbose(
828
+ `${VesuMultiplyAdapter.name}::getVesuMultiplyCall dexPrice: ${dexPrice}, ltv: ${legLTV}`
829
+ );
830
+
831
+ // compute optimal amount of collateral and debt post addition/removal
832
+ // target hf = collateral * collateralPrice * ltv / debt * debtPrice
833
+ // assuming X to be the usd amount of debt borrowed or repaied (negative).
834
+ // target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv / (debt * debtPrice + X)
835
+ // => X * target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv - (debt * debtPrice * target hf)
836
+ // => X * (target hf - ltv)= ((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)
837
+ // => X = (((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)) / (target hf - ltv)
838
+
839
+ const addedCollateral = params.amount.multipliedBy(isDeposit ? 1 : -1);
840
+ logger.verbose(
841
+ `${VesuMultiplyAdapter.name}::getVesuMultiplyCall addedCollateral: ${addedCollateral}`
842
+ );
843
+ const numeratorPart1 = existingCollateralInfo.amount
844
+ .plus(addedCollateral)
845
+ .multipliedBy(collateralPrice)
846
+ .multipliedBy(legLTV);
847
+ logger.verbose(
848
+ `${VesuMultiplyAdapter.name}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}`
849
+ );
850
+ const numeratorPart2 = existingDebtInfo.amount
851
+ .multipliedBy(debtPrice)
852
+ .multipliedBy(this.config.targetHealthFactor);
853
+ logger.verbose(
854
+ `${VesuMultiplyAdapter.name}::getVesuMultiplyCall numeratorPart2: ${numeratorPart2}`
855
+ );
856
+ 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)
857
+ logger.verbose(
858
+ `${VesuMultiplyAdapter.name}::getVesuMultiplyCall denominatorPart: ${denominatorPart}`
859
+ );
860
+ const x_debt_usd = numeratorPart1
861
+ .minus(numeratorPart2)
862
+ .dividedBy(denominatorPart);
863
+ logger.verbose(
864
+ `${VesuMultiplyAdapter.name}::getVesuMultiplyCall x_debt_usd: ${x_debt_usd}`
865
+ );
866
+ logger.debug(
867
+ `${VesuMultiplyAdapter.name}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}, numeratorPart2: ${numeratorPart2}, denominatorPart: ${denominatorPart}`
868
+ );
869
+
870
+ // both in underlying
871
+ // debtAmount in debt units
872
+ let debtAmount = new Web3Number(
873
+ x_debt_usd.dividedBy(debtPrice).toFixed(this.config.debt.decimals),
874
+ this.config.debt.decimals
875
+ );
876
+ const marginAmount = addedCollateral;
877
+ const collateralToken = this.config.collateral;
878
+ const debtToken = this.config.debt;
879
+ const debtAmountInCollateralUnits = new Web3Number(
880
+ debtAmount
881
+ .multipliedBy(debtPrice)
882
+ .dividedBy(collateralPrice)
883
+ .multipliedBy(10 ** collateralToken.decimals)
884
+ .toFixed(0),
885
+ collateralToken.decimals
886
+ );
887
+
888
+ // increase multiply lever or not
889
+ const isIncrease = debtAmount.greaterThanOrEqualTo(0);
890
+
891
+ // due to directional limitations in multiply contract
892
+ if (isIncrease && debtAmount.lessThan(0)) {
893
+ // we are increasing lever but math says reduce debt
894
+ // - this is ok
895
+ } else if (!isIncrease && debtAmount.greaterThan(0)) {
896
+ // we are decreasing level but math says increase debt
897
+ // - such actions must be done with zero margin amount
898
+ // - so just set debt 0
899
+ debtAmount = Web3Number.fromWei(0, this.config.debt.decimals);
900
+ }
901
+ logger.verbose(
902
+ `${VesuMultiplyAdapter.name}::getVesuMultiplyCall debtAmount: ${debtAmount}, marginAmount: ${marginAmount}`
903
+ );
904
+ if (!debtAmount.isZero()) {
905
+ // Get swap quote for leverage operation
906
+ // Determine swap direction based on operation type
907
+
908
+ try {
909
+ const swapQuote = await ekuboQuoter.getQuote(
910
+ collateralToken.address.address,
911
+ debtToken.address.address,
912
+ debtAmountInCollateralUnits.multipliedBy(-1) // negative for exact amount out
913
+ );
914
+
915
+ // todo add better slip checks
916
+ // Check price impact
917
+ if (swapQuote.price_impact < 0.01) {
918
+ // 1% max price impact
919
+ // from and toToken param position reversed, to fetch the required quote and keep things generalised
920
+
921
+ leverSwap = debtAmount.isNegative()
922
+ ? ekuboQuoter.getVesuMultiplyQuote(
923
+ swapQuote,
924
+ collateralToken,
925
+ debtToken
926
+ )
927
+ : ekuboQuoter.getVesuMultiplyQuote(
928
+ swapQuote,
929
+ debtToken,
930
+ collateralToken
931
+ );
932
+ //console.log("leverSwap", leverSwap[-1].token_amount);
933
+ //console.log(JSON.stringify(leverSwap));
934
+ // Calculate limit amount with slippage protection
935
+ const MAX_SLIPPAGE = 0.002; // 0.2% slippage
936
+ if (debtAmount.greaterThan(0)) {
937
+ // For increase: minimum amount of collateral received
938
+ // from debt token to collateral token
939
+ //console.log("debtAmountInCollateralUnits", debtAmountInCollateralUnits.toNumber());
940
+ //leverSwapLimitAmount = await ekuboQuoter.getSwapLimitAmount(debtToken, collateralToken, debtAmount, MAX_SLIPPAGE);
941
+ leverSwapLimitAmount = debtAmount.multipliedBy(1 + MAX_SLIPPAGE);
942
+ //console.log("anotherleverSwapLimitAmount", anotherleverSwapLimitAmount, leverSwapLimitAmount);
943
+ } else if (debtAmount.lessThan(0)) {
944
+ // For decrease: maximum amount of collateral used
945
+ // from collateral token to debt token
946
+ //leverSwapLimitAmount = await ekuboQuoter.getSwapLimitAmount(collateralToken, debtToken, debtAmountInCollateralUnits.multipliedBy(-1), MAX_SLIPPAGE);
947
+ leverSwapLimitAmount = debtAmount
948
+ .abs()
949
+ .multipliedBy(1 - MAX_SLIPPAGE);
950
+ //console.log("anotherleverSwapLimitAmount", anotherleverSwapLimitAmount, leverSwapLimitAmount);
951
+ } else {
952
+ leverSwapLimitAmount = Web3Number.fromWei(
953
+ 0,
954
+ this.config.debt.decimals
955
+ );
956
+ }
957
+ await new Promise((resolve) => setTimeout(resolve, 10000));
958
+ //console.log("leverSwapLimitAmount", leverSwapLimitAmount);
959
+ } else {
960
+ throw new Error(
961
+ `VesuMultiplyAdapter: Price impact too high (${swapQuote.price_impact}), skipping swap`
962
+ );
963
+ }
964
+ } catch (error) {
965
+ throw new Error(
966
+ `VesuMultiplyAdapter: Failed to get swap quote: ${error}`
967
+ );
968
+ }
969
+ }
970
+
971
+ const multiplyParams = await this.getLeverParams(
972
+ isIncrease,
973
+ params,
974
+ leverSwap,
975
+ leverSwapLimitAmount
976
+ );
977
+ const call = multiplyContract.populate("modify_lever", {
978
+ modify_lever_params: this.formatMultiplyParams(
979
+ isIncrease,
980
+ multiplyParams
981
+ ),
982
+ });
983
+
984
+ return call.calldata as bigint[];
985
+ }
986
+
987
+ private async getLeverParams(
988
+ isIncrease: boolean,
989
+ params: DepositParams | WithdrawParams,
990
+ leverSwap: Swap[],
991
+ leverSwapLimitAmount: Web3Number,
992
+ closePosition?: boolean
993
+ ): Promise<IncreaseLeverParams | DecreaseLeverParams> {
994
+ const multiplyParams: IncreaseLeverParams | DecreaseLeverParams = isIncrease
995
+ ? {
996
+ user: this.config.vaultAllocator,
997
+ pool_id: this.config.poolId,
998
+ collateral_asset: this.config.collateral.address,
999
+ debt_asset: this.config.debt.address,
1000
+ recipient: this.config.vaultAllocator,
1001
+ add_margin: params.amount, // multiplied by collateral decimals in format
1002
+ margin_swap: [],
1003
+ margin_swap_limit_amount: Web3Number.fromWei(
1004
+ 0,
1005
+ this.config.collateral.decimals
1006
+ ),
1007
+ lever_swap: leverSwap,
1008
+ lever_swap_limit_amount: leverSwapLimitAmount,
1009
+ }
1010
+ : {
1011
+ user: this.config.vaultAllocator,
1012
+ pool_id: this.config.poolId,
1013
+ collateral_asset: this.config.collateral.address,
1014
+ debt_asset: this.config.debt.address,
1015
+ recipient: this.config.vaultAllocator,
1016
+ sub_margin: params.amount,
1017
+ lever_swap: leverSwap,
1018
+ lever_swap_limit_amount: leverSwapLimitAmount,
1019
+ lever_swap_weights: [], // add weights for closing position
1020
+ withdraw_swap: [],
1021
+ withdraw_swap_limit_amount: Web3Number.fromWei(
1022
+ 0,
1023
+ this.config.collateral.decimals
1024
+ ),
1025
+ withdraw_swap_weights: [],
1026
+ close_position: closePosition ?? false,
1027
+ };
1028
+ return multiplyParams;
1029
+ }
1030
+
1031
+ private async getWithdrawalCalldata(
1032
+ params: WithdrawParams
1033
+ ): Promise<bigint[]> {
1034
+ //params.amount must be in btc here
1035
+ const { isV2 } = getVesuSingletonAddress(this.config.poolId);
1036
+ const vesuMultiply = isV2
1037
+ ? this.vesuAdapter.VESU_MULTIPLY
1038
+ : this.vesuAdapter.VESU_MULTIPLY_V1;
1039
+ const multiplyContract = new Contract({
1040
+ abi: VesuMultiplyAbi,
1041
+ address: vesuMultiply.address,
1042
+ providerOrAccount: this.config.networkConfig.provider,
1043
+ });
1044
+ let leverSwap: Swap[] = [];
1045
+ let leverSwapLimitAmount = Web3Number.fromWei(0, this.config.debt.decimals);
1046
+ const existingPositions = await this.vesuAdapter.getPositions(
1047
+ this.config.networkConfig
1048
+ );
1049
+ const existingCollateralInfo = existingPositions[0];
1050
+ const existingDebtInfo = existingPositions[1];
1051
+ const collateralToken = this.config.collateral;
1052
+ const debtToken = this.config.debt;
1053
+ const collateralPrice = await this.config.pricer.getPrice(
1054
+ collateralToken.symbol
1055
+ );
1056
+ const debtPrice = await this.config.pricer.getPrice(debtToken.symbol);
1057
+ // the debt amount is negative as we are reducing debt to withdraw
1058
+ const { deltadebtAmountUnits: debtAmountToRepay } =
1059
+ calculateDebtReductionAmountForWithdrawal(
1060
+ existingDebtInfo.amount,
1061
+ existingCollateralInfo.amount,
1062
+ MAX_LIQUIDATION_RATIO,
1063
+ params.amount,
1064
+ collateralPrice.price,
1065
+ debtPrice.price,
1066
+ debtToken.decimals
1067
+ );
1068
+ //console.log("debtAmountToRepay", debtAmountToRepay);
1069
+ if (!debtAmountToRepay) {
1070
+ throw new Error("error calculating debt amount to repay");
1071
+ }
1072
+ const ekuboQuoter = new EkuboQuoter(
1073
+ this.config.networkConfig,
1074
+ this.config.pricer
1075
+ );
1076
+ const debtInDebtUnits = new Web3Number(
1077
+ debtAmountToRepay,
1078
+ debtToken.decimals
1079
+ )
1080
+ .dividedBy(debtPrice.price)
1081
+ .multipliedBy(10 ** debtToken.decimals);
1082
+ const swapQuote = await ekuboQuoter.getQuote(
1083
+ debtToken.address.address,
1084
+ collateralToken.address.address,
1085
+ debtInDebtUnits
1086
+ );
1087
+ const MAX_SLIPPAGE = 0.002; // 0.2% slippag
1088
+ // const debtInCollateralUnits = new Web3Number(existingDebtInfo.amount.dividedBy(collateralPrice.price).multipliedBy(debtPrice.price).toFixed(WBTC_TOKEN_DECIMALS), collateralToken.decimals);
1089
+ // console.log("debtInCollateralUnits", debtInCollateralUnits, existingDebtInfo.amount, collateralPrice.price, debtPrice.price, WBTC_TOKEN_DECIMALS);
1090
+ if (swapQuote.price_impact < 0.0025) {
1091
+ leverSwap = ekuboQuoter.getVesuMultiplyQuote(
1092
+ swapQuote,
1093
+ collateralToken,
1094
+ debtToken
1095
+ );
1096
+ } else {
1097
+ logger.error(
1098
+ `VesuMultiplyAdapter: Price impact too high (${swapQuote.price_impact}), skipping swap`
1099
+ );
1100
+ }
1101
+
1102
+ leverSwapLimitAmount = new Web3Number(debtAmountToRepay, debtToken.decimals)
1103
+ .abs()
1104
+ .multipliedBy(1 + MAX_SLIPPAGE);
1105
+
1106
+ // leverSwapLimitAmount = debtInCollateralUnits; this is used when closing position
1107
+ console.log("leverSwapLimitAmount", leverSwapLimitAmount);
1108
+ //leverSwapLimitAmount = await ekuboQuoter.getSwapLimitAmount(debtToken, collateralToken, debtInCollateralUnits, MAX_SLIPPAGE);
1109
+ const multiplyParams = await this.getLeverParams(
1110
+ false,
1111
+ params,
1112
+ leverSwap,
1113
+ leverSwapLimitAmount
1114
+ );
1115
+ const call = multiplyContract.populate("modify_lever", {
1116
+ modify_lever_params: this.formatMultiplyParams(false, multiplyParams),
1117
+ });
1118
+ console.log("call", call.calldata);
1119
+ return call.calldata as bigint[];
1120
+ }
1121
+
1122
+ formatMultiplyParams(
1123
+ isIncrease: boolean,
1124
+ params: IncreaseLeverParams | DecreaseLeverParams
1125
+ ) {
1126
+ if (isIncrease) {
1127
+ const _params = params as IncreaseLeverParams;
1128
+ return {
1129
+ action: new CairoCustomEnum({
1130
+ IncreaseLever: {
1131
+ pool_id: _params.pool_id.toBigInt(),
1132
+ collateral_asset: _params.collateral_asset.toBigInt(),
1133
+ debt_asset: _params.debt_asset.toBigInt(),
1134
+ user: _params.user.toBigInt(),
1135
+ add_margin: BigInt(_params.add_margin.toWei()),
1136
+ margin_swap: _params.margin_swap.map((swap) => ({
1137
+ route: swap.route.map((route) => ({
1138
+ pool_key: {
1139
+ token0: route.pool_key.token0.toBigInt(),
1140
+ token1: route.pool_key.token1.toBigInt(),
1141
+ fee: route.pool_key.fee,
1142
+ tick_spacing: route.pool_key.tick_spacing,
1143
+ extension: BigInt(
1144
+ num.hexToDecimalString(route.pool_key.extension)
1145
+ ),
1146
+ },
1147
+ sqrt_ratio_limit: uint256.bnToUint256(
1148
+ route.sqrt_ratio_limit.toWei()
1149
+ ),
1150
+ skip_ahead: BigInt(100),
1151
+ })),
1152
+ token_amount: {
1153
+ token: swap.token_amount.token.toBigInt(),
1154
+ amount: swap.token_amount.amount.toI129(),
1155
+ },
1156
+ })),
1157
+ margin_swap_limit_amount: BigInt(
1158
+ _params.margin_swap_limit_amount.toWei()
1159
+ ),
1160
+ lever_swap: _params.lever_swap.map((swap) => ({
1161
+ route: swap.route.map((route) => ({
1162
+ pool_key: {
1163
+ token0: route.pool_key.token0.toBigInt(),
1164
+ token1: route.pool_key.token1.toBigInt(),
1165
+ fee: route.pool_key.fee,
1166
+ tick_spacing: route.pool_key.tick_spacing,
1167
+ extension: BigInt(
1168
+ num.hexToDecimalString(route.pool_key.extension)
1169
+ ),
1170
+ },
1171
+ sqrt_ratio_limit: uint256.bnToUint256(
1172
+ route.sqrt_ratio_limit.toWei()
1173
+ ),
1174
+ skip_ahead: BigInt(0),
1175
+ })),
1176
+ token_amount: {
1177
+ token: swap.token_amount.token.toBigInt(),
1178
+ amount: swap.token_amount.amount.toI129(),
1179
+ },
1180
+ })),
1181
+ lever_swap_limit_amount: BigInt(
1182
+ _params.lever_swap_limit_amount.toWei()
1183
+ ),
1184
+ },
1185
+ }),
1186
+ };
1187
+ }
1188
+
1189
+ const _params = params as DecreaseLeverParams;
1190
+ return {
1191
+ action: new CairoCustomEnum({
1192
+ DecreaseLever: {
1193
+ pool_id: _params.pool_id.toBigInt(),
1194
+ collateral_asset: _params.collateral_asset.toBigInt(),
1195
+ debt_asset: _params.debt_asset.toBigInt(),
1196
+ user: _params.user.toBigInt(),
1197
+ sub_margin: BigInt(_params.sub_margin.toWei()),
1198
+ recipient: _params.recipient.toBigInt(),
1199
+ lever_swap: _params.lever_swap.map((swap) => ({
1200
+ route: swap.route.map((route) => ({
1201
+ pool_key: {
1202
+ token0: route.pool_key.token0.toBigInt(),
1203
+ token1: route.pool_key.token1.toBigInt(),
1204
+ fee: route.pool_key.fee,
1205
+ tick_spacing: route.pool_key.tick_spacing,
1206
+ extension: ContractAddr.from(
1207
+ route.pool_key.extension
1208
+ ).toBigInt(),
1209
+ },
1210
+ sqrt_ratio_limit: uint256.bnToUint256(
1211
+ route.sqrt_ratio_limit.toWei()
1212
+ ),
1213
+ skip_ahead: BigInt(route.skip_ahead.toWei()),
1214
+ })),
1215
+ token_amount: {
1216
+ token: swap.token_amount.token.toBigInt(),
1217
+ amount: swap.token_amount.amount.toI129(),
1218
+ },
1219
+ })),
1220
+ lever_swap_limit_amount: BigInt(
1221
+ _params.lever_swap_limit_amount.toWei()
1222
+ ),
1223
+ lever_swap_weights: _params.lever_swap_weights.map((weight) =>
1224
+ BigInt(weight.toWei())
1225
+ ),
1226
+ withdraw_swap: _params.withdraw_swap.map((swap) => ({
1227
+ route: swap.route.map((route) => ({
1228
+ pool_key: {
1229
+ token0: route.pool_key.token0.toBigInt(),
1230
+ token1: route.pool_key.token1.toBigInt(),
1231
+ fee: route.pool_key.fee,
1232
+ tick_spacing: route.pool_key.tick_spacing,
1233
+ extension: ContractAddr.from(
1234
+ route.pool_key.extension
1235
+ ).toBigInt(),
1236
+ },
1237
+ sqrt_ratio_limit: uint256.bnToUint256(
1238
+ route.sqrt_ratio_limit.toWei()
1239
+ ),
1240
+ skip_ahead: BigInt(route.skip_ahead.toWei()),
1241
+ })),
1242
+ token_amount: {
1243
+ token: swap.token_amount.token.toBigInt(),
1244
+ amount: swap.token_amount.amount.toI129(),
1245
+ },
1246
+ })),
1247
+ withdraw_swap_limit_amount: BigInt(
1248
+ _params.withdraw_swap_limit_amount.toWei()
1249
+ ),
1250
+ withdraw_swap_weights: _params.withdraw_swap_weights.map((weight) =>
1251
+ BigInt(weight.toWei())
1252
+ ),
1253
+ close_position: _params.close_position,
1254
+ },
1255
+ }),
1256
+ };
1257
+ }
1258
+
1259
+ async getHealthFactor(): Promise<number> {
1260
+ const healthFactor = await this.vesuAdapter.getHealthFactor();
1261
+ return healthFactor;
1262
+ }
1263
+
1264
+ async getNetAPY(): Promise<number> {
1265
+ const positions = await this.getPositions();
1266
+ logger.verbose(
1267
+ `${this.name}::getNetAPY: positions: ${JSON.stringify(positions)}`
1268
+ );
1269
+ const allZero = positions.every((p) => p.usdValue === 0);
1270
+
1271
+ // in case of zero positions, apy will come zero/NaN
1272
+ // bcz of net 0 zero weights
1273
+ if (allZero) {
1274
+ // use approx dummy usd values to compute netAPY
1275
+ const collateralUSD = 1000;
1276
+ const maxLTV = await this.vesuAdapter.getLTVConfig(
1277
+ this.config.networkConfig
1278
+ );
1279
+ const targetHF = this.config.targetHealthFactor;
1280
+ const maxDebt = HealthFactorMath.getMaxDebtAmountOnLooping(
1281
+ new Web3Number(collateralUSD, this.config.collateral.decimals),
1282
+ 1, // assume price 1 for simplicity
1283
+ maxLTV,
1284
+ targetHF,
1285
+ 1, // assume price 1 for simplicity
1286
+ this.config.debt
1287
+ );
1288
+
1289
+ // debt is also added to collateral bcz, we assume debt is swapped to collateral
1290
+ const debtUSD = maxDebt.multipliedBy(1); // assume price 1 for simplicity
1291
+ const netAPY =
1292
+ (positions[0].apy.apy * (collateralUSD + debtUSD.toNumber()) +
1293
+ positions[1].apy.apy * debtUSD.toNumber()) /
1294
+ collateralUSD;
1295
+ return netAPY;
1296
+ }
1297
+
1298
+ // Return true APY
1299
+ const netAmount = positions.reduce((acc, curr) => acc + curr.usdValue, 0);
1300
+ const netAPY =
1301
+ positions.reduce((acc, curr) => acc + curr.apy.apy * curr.usdValue, 0) /
1302
+ netAmount;
1303
+ logger.verbose(`${this.name}::getNetAPY: netAPY: ${netAPY}`);
1304
+ return netAPY;
1305
+ }
1306
+ }