@strkfarm/sdk 2.0.0-dev.5 → 2.0.0-dev.50

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