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