@strkfarm/sdk 2.0.0-dev.9 → 2.0.0-staging.2
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/index.browser.global.js +111371 -93151
- package/dist/index.browser.mjs +27815 -32690
- package/dist/index.d.ts +1095 -2011
- package/dist/index.js +27425 -32309
- package/dist/index.mjs +27590 -32452
- package/package.json +6 -5
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/data/universal-vault.abi.json +20 -135
- package/src/dataTypes/address.ts +0 -7
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +296 -288
- package/src/index.browser.ts +6 -5
- package/src/interfaces/common.tsx +324 -184
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +4 -17
- package/src/modules/ekubo-pricer.ts +79 -0
- package/src/modules/ekubo-quoter.ts +11 -88
- package/src/modules/erc20.ts +21 -67
- package/src/modules/harvests.ts +26 -15
- package/src/modules/index.ts +11 -13
- package/src/modules/lst-apr.ts +0 -36
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-from-api.ts +150 -14
- package/src/modules/pricer.ts +2 -1
- package/src/modules/pricerBase.ts +2 -1
- package/src/node/deployer.ts +36 -1
- package/src/node/pricer-redis.ts +2 -1
- package/src/strategies/autoCompounderStrk.ts +1 -1
- package/src/strategies/base-strategy.ts +5 -22
- package/src/strategies/ekubo-cl-vault.tsx +2904 -2175
- package/src/strategies/factory.ts +165 -0
- package/src/strategies/index.ts +10 -11
- package/src/strategies/registry.ts +268 -0
- package/src/strategies/sensei.ts +416 -292
- package/src/strategies/universal-adapters/adapter-utils.ts +1 -5
- package/src/strategies/universal-adapters/baseAdapter.ts +153 -181
- package/src/strategies/universal-adapters/common-adapter.ts +77 -98
- package/src/strategies/universal-adapters/index.ts +1 -5
- package/src/strategies/universal-adapters/vesu-adapter.ts +218 -220
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +51 -58
- package/src/strategies/universal-lst-muliplier-strategy.tsx +1952 -992
- package/src/strategies/universal-strategy.tsx +1713 -1150
- package/src/strategies/vesu-rebalance.tsx +1189 -986
- package/src/utils/health-factor-math.ts +5 -11
- package/src/utils/index.ts +8 -9
- package/src/utils/strategy-utils.ts +57 -0
- package/src/data/extended-deposit.abi.json +0 -3613
- package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
- package/src/modules/ExtendedWrapperSDk/types.ts +0 -311
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -395
- package/src/modules/midas.ts +0 -159
- package/src/modules/token-market-data.ts +0 -202
- package/src/strategies/svk-strategy.ts +0 -247
- package/src/strategies/universal-adapters/adapter-optimizer.ts +0 -65
- package/src/strategies/universal-adapters/avnu-adapter.ts +0 -413
- package/src/strategies/universal-adapters/extended-adapter.ts +0 -972
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +0 -1306
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -34
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -77
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -49
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -370
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1379
|
@@ -1,1072 +1,2032 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
FAQ,
|
|
3
|
+
getNoRiskTags,
|
|
4
|
+
highlightTextWithLinks,
|
|
5
|
+
IConfig,
|
|
6
|
+
IStrategyMetadata,
|
|
7
|
+
Protocols,
|
|
8
|
+
RiskFactor,
|
|
9
|
+
RiskType,
|
|
10
|
+
StrategyCategory,
|
|
11
|
+
StrategyTag,
|
|
12
|
+
StrategyLiveStatus,
|
|
13
|
+
StrategySettings,
|
|
14
|
+
TokenInfo,
|
|
15
|
+
AuditStatus,
|
|
16
|
+
SourceCodeType,
|
|
17
|
+
AccessControlType,
|
|
18
|
+
InstantWithdrawalVault
|
|
19
|
+
} from "@/interfaces";
|
|
20
|
+
import {
|
|
21
|
+
AUMTypes,
|
|
22
|
+
getContractDetails,
|
|
23
|
+
UNIVERSAL_ADAPTERS,
|
|
24
|
+
UNIVERSAL_MANAGE_IDS,
|
|
25
|
+
UniversalManageCall,
|
|
26
|
+
UniversalStrategy,
|
|
27
|
+
UniversalStrategySettings
|
|
28
|
+
} from "./universal-strategy";
|
|
3
29
|
import { PricerBase } from "@/modules/pricerBase";
|
|
4
30
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
5
31
|
import { Global } from "@/global";
|
|
6
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
ApproveCallParams,
|
|
34
|
+
AvnuSwapCallParams,
|
|
35
|
+
CommonAdapter,
|
|
36
|
+
getVesuSingletonAddress,
|
|
37
|
+
ManageCall,
|
|
38
|
+
Swap,
|
|
39
|
+
VesuAdapter,
|
|
40
|
+
VesuModifyDelegationCallParams,
|
|
41
|
+
VesuModifyPositionCallParams,
|
|
42
|
+
VesuMultiplyCallParams,
|
|
43
|
+
VesuPools
|
|
44
|
+
} from "./universal-adapters";
|
|
7
45
|
import { AVNU_EXCHANGE } from "./universal-adapters/adapter-utils";
|
|
8
|
-
import {
|
|
9
|
-
|
|
46
|
+
import {
|
|
47
|
+
DepegRiskLevel,
|
|
48
|
+
LiquidationRiskLevel,
|
|
49
|
+
SmartContractRiskLevel,
|
|
50
|
+
TechnicalRiskLevel
|
|
51
|
+
} from "@/interfaces/risks";
|
|
52
|
+
import {
|
|
53
|
+
AvnuWrapper,
|
|
54
|
+
EkuboQuoter,
|
|
55
|
+
ERC20,
|
|
56
|
+
LSTAPRService,
|
|
57
|
+
PricerLST
|
|
58
|
+
} from "@/modules";
|
|
10
59
|
import { assert, logger } from "@/utils";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { Call, uint256 } from "starknet";
|
|
60
|
+
import { SingleTokenInfo } from "./base-strategy";
|
|
61
|
+
import { VaultPosition } from "@/interfaces";
|
|
62
|
+
import { Call, Contract, uint256 } from "starknet";
|
|
14
63
|
import ERC4626Abi from "@/data/erc4626.abi.json";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { VesuExtendedStrategySettings } from "./vesu-extended-strategy/vesu-extended-strategy";
|
|
64
|
+
import { HealthFactorMath } from "@/utils/health-factor-math";
|
|
65
|
+
import { findMaxInputWithSlippage } from "@/utils/math-utils";
|
|
18
66
|
|
|
19
67
|
export interface HyperLSTStrategySettings extends UniversalStrategySettings {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
quoteAmountToFetchPrice: Web3Number;
|
|
23
|
-
targetHealthFactor: number;
|
|
24
|
-
minHealthFactor: number;
|
|
25
|
-
aumOracle: ContractAddr;
|
|
68
|
+
borrowable_assets: TokenInfo[];
|
|
69
|
+
underlyingToken: TokenInfo;
|
|
26
70
|
}
|
|
27
71
|
|
|
28
|
-
export class UniversalLstMultiplierStrategy
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTStrategySettings> {
|
|
73
|
+
private quoteAmountToFetchPrice = new Web3Number(1, 18);
|
|
74
|
+
|
|
75
|
+
constructor(
|
|
76
|
+
config: IConfig,
|
|
77
|
+
pricer: PricerBase,
|
|
78
|
+
metadata: IStrategyMetadata<HyperLSTStrategySettings>
|
|
79
|
+
) {
|
|
80
|
+
super(config, pricer, metadata);
|
|
81
|
+
|
|
82
|
+
const STRKToken = Global.getDefaultTokens().find(
|
|
83
|
+
(token) => token.symbol === "STRK"
|
|
84
|
+
)!;
|
|
85
|
+
const underlyingToken = this.getLSTUnderlyingTokenInfo();
|
|
86
|
+
if (underlyingToken.address.eq(STRKToken.address)) {
|
|
87
|
+
this.quoteAmountToFetchPrice = new Web3Number(100, 18);
|
|
88
|
+
} else {
|
|
89
|
+
// else this BTC
|
|
90
|
+
this.quoteAmountToFetchPrice = new Web3Number(
|
|
91
|
+
0.01,
|
|
92
|
+
this.asset().decimals
|
|
93
|
+
);
|
|
94
|
+
}
|
|
41
95
|
}
|
|
42
96
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
97
|
+
asset() {
|
|
98
|
+
return this.getVesuSameTokenAdapter().config.collateral;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getTag() {
|
|
102
|
+
return `${UniversalLstMultiplierStrategy.name}:${this.metadata.name}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Vesu adapter with LST and base token match
|
|
106
|
+
getVesuSameTokenAdapter() {
|
|
107
|
+
const baseAdapter = this.getAdapter(
|
|
108
|
+
getVesuLegId(
|
|
109
|
+
UNIVERSAL_MANAGE_IDS.VESU_LEG1,
|
|
110
|
+
this.metadata.additionalInfo.underlyingToken.symbol
|
|
111
|
+
)
|
|
112
|
+
) as VesuAdapter;
|
|
113
|
+
baseAdapter.networkConfig = this.config;
|
|
114
|
+
baseAdapter.pricer = this.pricer;
|
|
115
|
+
return baseAdapter;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// only one leg is used
|
|
119
|
+
// todo support lending assets of underlying as well (like if xSTRK looping is not viable, simply supply STRK)
|
|
120
|
+
getVesuAdapters() {
|
|
121
|
+
const adapters: VesuAdapter[] = [];
|
|
122
|
+
const baseAdapter = this.getVesuSameTokenAdapter();
|
|
123
|
+
for (const asset of this.metadata.additionalInfo.borrowable_assets) {
|
|
124
|
+
const vesuAdapter1 = new VesuAdapter({
|
|
125
|
+
poolId: baseAdapter.config.poolId,
|
|
126
|
+
collateral: this.asset(),
|
|
127
|
+
debt: asset,
|
|
128
|
+
vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
|
|
129
|
+
id: ""
|
|
130
|
+
});
|
|
131
|
+
vesuAdapter1.pricer = this.pricer;
|
|
132
|
+
vesuAdapter1.networkConfig = this.config;
|
|
133
|
+
adapters.push(vesuAdapter1);
|
|
134
|
+
}
|
|
135
|
+
return adapters;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// not applicable for this strategy
|
|
139
|
+
// No rewards on collateral or borrowing of LST assets
|
|
140
|
+
protected async getRewardsAUM(prevAum: Web3Number): Promise<Web3Number> {
|
|
141
|
+
const lstToken = this.asset();
|
|
142
|
+
if (lstToken.symbol === "xSTRK") {
|
|
143
|
+
return super.getRewardsAUM(prevAum);
|
|
144
|
+
}
|
|
145
|
+
return Web3Number.fromWei("0", lstToken.decimals);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getLSTUnderlyingTokenInfo() {
|
|
149
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter();
|
|
150
|
+
return vesuAdapter1.config.debt;
|
|
66
151
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
152
|
+
|
|
153
|
+
async getLSTDexPrice() {
|
|
154
|
+
const ekuboQuoter = new EkuboQuoter(this.config);
|
|
155
|
+
const lstTokenInfo = this.asset();
|
|
156
|
+
const lstUnderlyingTokenInfo = this.getLSTUnderlyingTokenInfo();
|
|
157
|
+
const quote = await ekuboQuoter.getQuote(
|
|
158
|
+
lstTokenInfo.address.address,
|
|
159
|
+
lstUnderlyingTokenInfo.address.address,
|
|
160
|
+
this.quoteAmountToFetchPrice
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// in Underlying
|
|
164
|
+
const outputAmount = Web3Number.fromWei(
|
|
165
|
+
quote.total_calculated,
|
|
166
|
+
lstUnderlyingTokenInfo.decimals
|
|
167
|
+
);
|
|
168
|
+
const price =
|
|
169
|
+
outputAmount.toNumber() / this.quoteAmountToFetchPrice.toNumber();
|
|
170
|
+
logger.verbose(`${this.getTag()}:: LST Dex Price: ${price}`);
|
|
171
|
+
return price;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async getAvnuSwapMultiplyCall(params: {
|
|
175
|
+
isDeposit: boolean;
|
|
176
|
+
leg1DepositAmount: Web3Number;
|
|
177
|
+
}) {
|
|
178
|
+
assert(
|
|
179
|
+
params.isDeposit,
|
|
180
|
+
"Only deposit is supported in getAvnuSwapMultiplyCall"
|
|
181
|
+
);
|
|
182
|
+
// TODO use a varibale for 1.02
|
|
183
|
+
const maxBorrowableAmounts = await this.getMaxBorrowableAmount({
|
|
184
|
+
isAPYComputation: false
|
|
185
|
+
});
|
|
186
|
+
const allVesuAdapters = this.getVesuAdapters();
|
|
187
|
+
let remainingAmount = params.leg1DepositAmount;
|
|
188
|
+
const lstExRate = await this.getLSTExchangeRate();
|
|
189
|
+
const baseAssetPrice = await this.pricer.getPrice(
|
|
190
|
+
this.getLSTUnderlyingTokenInfo().symbol
|
|
191
|
+
);
|
|
192
|
+
const lstPrice = baseAssetPrice.price * lstExRate;
|
|
193
|
+
for (let i = 0; i < maxBorrowableAmounts.maxBorrowables.length; i++) {
|
|
194
|
+
const maxBorrowable = maxBorrowableAmounts.maxBorrowables[i];
|
|
195
|
+
const vesuAdapter = allVesuAdapters.find((adapter) =>
|
|
196
|
+
adapter.config.debt.address.eq(
|
|
197
|
+
maxBorrowable.borrowableAsset.address
|
|
198
|
+
)
|
|
199
|
+
);
|
|
200
|
+
if (!vesuAdapter) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`${this.getTag()}::getAvnuSwapMultiplyCall: vesuAdapter not found for borrowable asset: ${
|
|
203
|
+
maxBorrowable.borrowableAsset.symbol
|
|
204
|
+
}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const maxLTV = await vesuAdapter.getLTVConfig(this.config);
|
|
208
|
+
const debtPrice = await this.pricer.getPrice(
|
|
209
|
+
maxBorrowable.borrowableAsset.symbol
|
|
210
|
+
);
|
|
211
|
+
const maxAmountToDeposit =
|
|
212
|
+
HealthFactorMath.getMinCollateralRequiredOnLooping(
|
|
213
|
+
maxBorrowable.amount,
|
|
214
|
+
debtPrice.price,
|
|
215
|
+
this.metadata.additionalInfo.targetHealthFactor,
|
|
216
|
+
maxLTV,
|
|
217
|
+
lstPrice,
|
|
218
|
+
this.asset()
|
|
219
|
+
);
|
|
220
|
+
const amountToDeposit = remainingAmount.minimum(maxAmountToDeposit);
|
|
221
|
+
logger.verbose(
|
|
222
|
+
`${this.getTag()}::getAvnuSwapMultiplyCall::${
|
|
223
|
+
vesuAdapter.config.debt.symbol
|
|
224
|
+
}:: remainingAmount: ${remainingAmount}, amountToDeposit: ${amountToDeposit}, depositAmount: ${amountToDeposit}, maxBorrowable: ${
|
|
225
|
+
maxBorrowable.amount
|
|
226
|
+
}`
|
|
227
|
+
);
|
|
228
|
+
const call = await this._getAvnuDepositSwapLegCall({
|
|
229
|
+
isDeposit: params.isDeposit,
|
|
230
|
+
// adjust decimals of debt asset
|
|
231
|
+
leg1DepositAmount: amountToDeposit,
|
|
232
|
+
minHF: 1.1, // undo
|
|
233
|
+
vesuAdapter: vesuAdapter
|
|
234
|
+
});
|
|
235
|
+
remainingAmount = remainingAmount.minus(amountToDeposit);
|
|
236
|
+
|
|
237
|
+
// return the first possible call because computing all calls at a time
|
|
238
|
+
// is not efficinet for swaps
|
|
239
|
+
return { call, vesuAdapter };
|
|
240
|
+
}
|
|
241
|
+
throw new Error(
|
|
242
|
+
`${this.getTag()}::getAvnuSwapMultiplyCall: no calls found`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async _getAvnuDepositSwapLegCall(params: {
|
|
247
|
+
isDeposit: boolean;
|
|
248
|
+
leg1DepositAmount: Web3Number;
|
|
249
|
+
minHF: number; // e.g. 1.01
|
|
250
|
+
vesuAdapter: VesuAdapter;
|
|
251
|
+
}) {
|
|
252
|
+
const { vesuAdapter } = params;
|
|
253
|
+
logger.verbose(
|
|
254
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall params: ${JSON.stringify(
|
|
255
|
+
params
|
|
256
|
+
)}`
|
|
257
|
+
);
|
|
258
|
+
assert(
|
|
259
|
+
params.isDeposit,
|
|
260
|
+
"Only deposit is supported in _getAvnuDepositSwapLegCall"
|
|
261
|
+
);
|
|
262
|
+
// add collateral
|
|
263
|
+
// borrow STRK (e.g.)
|
|
264
|
+
// approve and swap strk
|
|
265
|
+
// add collateral again
|
|
266
|
+
|
|
267
|
+
const legLTV = await vesuAdapter.getLTVConfig(this.config);
|
|
268
|
+
logger.verbose(
|
|
269
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall legLTV: ${legLTV}`
|
|
270
|
+
);
|
|
271
|
+
const existingPositions = await vesuAdapter.getPositions(this.config);
|
|
272
|
+
const collateralisation = await vesuAdapter.getCollateralization(
|
|
273
|
+
this.config
|
|
274
|
+
);
|
|
275
|
+
const existingCollateralInfo = existingPositions[0];
|
|
276
|
+
const existingDebtInfo = existingPositions[1];
|
|
277
|
+
logger.debug(`${this.getTag()}::_getAvnuDepositSwapLegCall existingCollateralInfo: ${JSON.stringify(
|
|
278
|
+
existingCollateralInfo
|
|
279
|
+
)},
|
|
280
|
+
existingDebtInfo: ${JSON.stringify(
|
|
281
|
+
existingDebtInfo
|
|
282
|
+
)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
283
|
+
|
|
284
|
+
// - Prices as seen by Vesu contracts, ideal for HF math
|
|
285
|
+
// Price 1 is ok as fallback bcz that would relatively price the
|
|
286
|
+
// collateral and debt as equal.
|
|
287
|
+
const collateralPrice =
|
|
288
|
+
collateralisation[0].usdValue > 0
|
|
289
|
+
? collateralisation[0].usdValue /
|
|
290
|
+
existingCollateralInfo.amount.toNumber()
|
|
291
|
+
: 1;
|
|
292
|
+
const debtPrice =
|
|
293
|
+
collateralisation[1].usdValue > 0
|
|
294
|
+
? collateralisation[1].usdValue /
|
|
295
|
+
existingDebtInfo.amount.toNumber()
|
|
296
|
+
: 1;
|
|
297
|
+
logger.debug(
|
|
298
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const debtTokenInfo = vesuAdapter.config.debt;
|
|
302
|
+
let newDepositAmount = params.leg1DepositAmount;
|
|
303
|
+
const totalCollateral = existingCollateralInfo.amount.plus(
|
|
304
|
+
params.leg1DepositAmount
|
|
305
|
+
);
|
|
306
|
+
logger.verbose(
|
|
307
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall totalCollateral: ${totalCollateral}`
|
|
308
|
+
);
|
|
309
|
+
const totalDebtAmount = new Web3Number(
|
|
310
|
+
totalCollateral
|
|
311
|
+
.multipliedBy(collateralPrice)
|
|
312
|
+
.multipliedBy(legLTV)
|
|
313
|
+
.dividedBy(debtPrice)
|
|
314
|
+
.dividedBy(params.minHF)
|
|
315
|
+
.toString(),
|
|
316
|
+
debtTokenInfo.decimals
|
|
317
|
+
);
|
|
318
|
+
let debtAmount = totalDebtAmount.minus(existingDebtInfo.amount);
|
|
319
|
+
logger.verbose(
|
|
320
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall totalDebtAmount: ${totalDebtAmount}, initial computed debt: ${debtAmount}`
|
|
321
|
+
);
|
|
322
|
+
const maxBorrowable = await this.getMaxBorrowableAmountByVesuAdapter(
|
|
323
|
+
vesuAdapter,
|
|
324
|
+
false
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// if the debt amount is greater than 0 and the max borrowable amount is 0, skip
|
|
328
|
+
if (debtAmount.gt(0) && maxBorrowable.amount.eq(0)) {
|
|
329
|
+
logger.verbose(
|
|
330
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall maxBorrowable is 0, skipping`
|
|
331
|
+
);
|
|
332
|
+
return undefined;
|
|
333
|
+
} else if (debtAmount.gt(0) && maxBorrowable.amount.gt(0)) {
|
|
334
|
+
debtAmount = maxBorrowable.amount.minimum(debtAmount);
|
|
335
|
+
const newDebtUSDValue = debtAmount.multipliedBy(debtPrice);
|
|
336
|
+
const totalCollateralRequired =
|
|
337
|
+
HealthFactorMath.getCollateralRequired(
|
|
338
|
+
debtAmount.plus(existingDebtInfo.amount),
|
|
339
|
+
debtPrice,
|
|
340
|
+
params.minHF,
|
|
341
|
+
legLTV,
|
|
342
|
+
collateralPrice,
|
|
343
|
+
this.asset()
|
|
344
|
+
);
|
|
345
|
+
newDepositAmount = totalCollateralRequired.minus(
|
|
346
|
+
existingCollateralInfo.amount
|
|
347
|
+
);
|
|
348
|
+
if (newDepositAmount.lt(0)) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall newDepositAmount is less than 0, newDepositAmount: ${newDepositAmount}, totalCollateralRequired: ${totalCollateralRequired}, existingCollateralInfo.amount: ${
|
|
351
|
+
existingCollateralInfo.amount
|
|
352
|
+
}`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
if (newDebtUSDValue.toNumber() < 100) {
|
|
356
|
+
// too less debt, skip
|
|
357
|
+
logger.verbose(
|
|
358
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall newDebtUSDValue is less than 100, skipping`
|
|
359
|
+
);
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
logger.verbose(
|
|
365
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall debtAmount: ${debtAmount}`
|
|
366
|
+
);
|
|
367
|
+
if (debtAmount.lt(0)) {
|
|
368
|
+
// this is to unwind the position to optimal HF.
|
|
369
|
+
const lstDEXPrice = await this.getLSTDexPrice();
|
|
370
|
+
const debtAmountInLST = debtAmount.abs().dividedBy(lstDEXPrice);
|
|
371
|
+
const calls = await this.getVesuMultiplyCall({
|
|
372
|
+
isDeposit: false,
|
|
373
|
+
leg1DepositAmount: debtAmountInLST
|
|
374
|
+
});
|
|
375
|
+
assert(
|
|
376
|
+
calls.length == 1,
|
|
377
|
+
`Expected 1 call for unwind, got ${calls.length}`
|
|
378
|
+
);
|
|
379
|
+
return calls[0];
|
|
380
|
+
}
|
|
381
|
+
console.log(
|
|
382
|
+
`debtAmount`,
|
|
383
|
+
debtAmount.toWei(),
|
|
384
|
+
params.leg1DepositAmount.toWei()
|
|
385
|
+
);
|
|
386
|
+
const STEP0 = UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1;
|
|
387
|
+
const manage0Info = this.getProofs<ApproveCallParams>(STEP0);
|
|
388
|
+
const manageCall0 = manage0Info.callConstructor({
|
|
389
|
+
amount: newDepositAmount
|
|
390
|
+
});
|
|
391
|
+
const STEP1 = getVesuLegId(
|
|
392
|
+
UNIVERSAL_MANAGE_IDS.VESU_LEG1,
|
|
393
|
+
vesuAdapter.config.debt.symbol
|
|
394
|
+
);
|
|
395
|
+
const manage1Info = this.getProofs<VesuModifyPositionCallParams>(STEP1);
|
|
396
|
+
const manageCall1 = manage1Info.callConstructor(
|
|
397
|
+
VesuAdapter.getDefaultModifyPositionCallParams({
|
|
398
|
+
collateralAmount: newDepositAmount,
|
|
399
|
+
isAddCollateral: params.isDeposit,
|
|
400
|
+
debtAmount: debtAmount,
|
|
401
|
+
isBorrow: params.isDeposit
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
console.log(
|
|
406
|
+
`manageCall1`,
|
|
407
|
+
manageCall1.call,
|
|
408
|
+
debtAmount.toWei(),
|
|
409
|
+
newDepositAmount.toWei()
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
const proofIds: string[] = [STEP0, STEP1];
|
|
413
|
+
const manageCalls: ManageCall[] = [manageCall0, manageCall1];
|
|
414
|
+
|
|
415
|
+
// approve and swap to LST using avnu
|
|
416
|
+
if (debtAmount.gt(0)) {
|
|
417
|
+
const STEP2 = getAvnuManageIDs(
|
|
418
|
+
LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT,
|
|
419
|
+
vesuAdapter.config.debt.symbol
|
|
420
|
+
);
|
|
421
|
+
const manage2Info = this.getProofs<ApproveCallParams>(STEP2);
|
|
422
|
+
const manageCall2 = manage2Info.callConstructor({
|
|
423
|
+
amount: debtAmount
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const debtTokenInfo = vesuAdapter.config.debt;
|
|
427
|
+
const lstTokenInfo = this.asset();
|
|
428
|
+
const avnuModule = new AvnuWrapper();
|
|
429
|
+
const quote = await avnuModule.getQuotes(
|
|
430
|
+
debtTokenInfo.address.address,
|
|
431
|
+
lstTokenInfo.address.address,
|
|
432
|
+
debtAmount.toWei(),
|
|
433
|
+
this.metadata.additionalInfo.vaultAllocator.address
|
|
434
|
+
);
|
|
435
|
+
const minAmount = await this._getMinOutputAmountLSTBuy(debtAmount);
|
|
436
|
+
const minAmountWei = minAmount.toWei();
|
|
437
|
+
logger.verbose(
|
|
438
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall minAmount: ${minAmount}`
|
|
439
|
+
);
|
|
440
|
+
const swapInfo = await avnuModule.getSwapInfo(
|
|
441
|
+
quote,
|
|
442
|
+
this.metadata.additionalInfo.vaultAllocator.address,
|
|
443
|
+
0,
|
|
444
|
+
this.address.address,
|
|
445
|
+
minAmountWei
|
|
446
|
+
);
|
|
447
|
+
logger.verbose(
|
|
448
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall swapInfo: ${JSON.stringify(
|
|
449
|
+
swapInfo
|
|
450
|
+
)}`
|
|
451
|
+
);
|
|
452
|
+
const STEP3 = getAvnuManageIDs(
|
|
453
|
+
LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT,
|
|
454
|
+
vesuAdapter.config.debt.symbol
|
|
455
|
+
);
|
|
456
|
+
const manage3Info = this.getProofs<AvnuSwapCallParams>(STEP3);
|
|
457
|
+
const manageCall3 = manage3Info.callConstructor({
|
|
458
|
+
props: swapInfo
|
|
459
|
+
});
|
|
460
|
+
proofIds.push(STEP2);
|
|
461
|
+
proofIds.push(STEP3);
|
|
462
|
+
manageCalls.push(manageCall2, manageCall3);
|
|
463
|
+
|
|
464
|
+
// if the created debt, when added is collateral will put the total HF above min, but below (target + 0.05),
|
|
465
|
+
// then lets close the looping cycle by adding this as collateral.
|
|
466
|
+
const newCollateral = minAmount.plus(totalCollateral);
|
|
467
|
+
const newHF = newCollateral
|
|
468
|
+
.multipliedBy(collateralPrice)
|
|
469
|
+
.multipliedBy(legLTV)
|
|
470
|
+
.dividedBy(totalDebtAmount)
|
|
471
|
+
.dividedBy(debtPrice)
|
|
472
|
+
.toNumber();
|
|
473
|
+
logger.verbose(
|
|
474
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall newHF: ${newHF}`
|
|
475
|
+
);
|
|
476
|
+
if (
|
|
477
|
+
newHF > this.metadata.additionalInfo.minHealthFactor &&
|
|
478
|
+
newHF < this.metadata.additionalInfo.targetHealthFactor + 0.05
|
|
479
|
+
) {
|
|
480
|
+
logger.verbose(
|
|
481
|
+
`${this.getTag()}::_getAvnuDepositSwapLegCall newHF is above min and below target + 0.05, adding collateral on vesu`
|
|
482
|
+
);
|
|
483
|
+
// approve and add collateral on vesu (modify position)
|
|
484
|
+
const STEP4 = UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1;
|
|
485
|
+
const manage4Info = this.getProofs<ApproveCallParams>(STEP4);
|
|
486
|
+
const manageCall4 = manage4Info.callConstructor({
|
|
487
|
+
amount: minAmount
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const STEP5 = getVesuLegId(
|
|
491
|
+
UNIVERSAL_MANAGE_IDS.VESU_LEG1,
|
|
492
|
+
vesuAdapter.config.debt.symbol
|
|
493
|
+
);
|
|
494
|
+
const manage5Info =
|
|
495
|
+
this.getProofs<VesuModifyPositionCallParams>(STEP5);
|
|
496
|
+
const manageCall5 = manage5Info.callConstructor(
|
|
497
|
+
VesuAdapter.getDefaultModifyPositionCallParams({
|
|
498
|
+
collateralAmount: minAmount,
|
|
499
|
+
isAddCollateral: true,
|
|
500
|
+
debtAmount: Web3Number.fromWei(
|
|
501
|
+
"0",
|
|
502
|
+
this.asset().decimals
|
|
503
|
+
),
|
|
504
|
+
isBorrow: params.isDeposit
|
|
505
|
+
})
|
|
506
|
+
);
|
|
507
|
+
proofIds.push(STEP4, STEP5);
|
|
508
|
+
manageCalls.push(manageCall4, manageCall5);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const manageCall = this.getManageCall(proofIds, manageCalls);
|
|
513
|
+
return manageCall;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// todo unwind or not deposit when the yield is bad.
|
|
517
|
+
|
|
518
|
+
async getLSTMultiplierRebalanceCall(): Promise<{
|
|
519
|
+
shouldRebalance: boolean;
|
|
520
|
+
manageCalls: { vesuAdapter: VesuAdapter; manageCall: Call }[];
|
|
521
|
+
}> {
|
|
522
|
+
let shouldRebalance = false;
|
|
523
|
+
const calls: { vesuAdapter: VesuAdapter; manageCall: Call }[] = [];
|
|
524
|
+
// todo undo
|
|
525
|
+
const allVesuAdapters = this.getVesuAdapters().filter(
|
|
526
|
+
(vesuAdapter) => vesuAdapter.config.debt.symbol === "LBTC"
|
|
527
|
+
);
|
|
528
|
+
for (const vesuAdapter of allVesuAdapters) {
|
|
529
|
+
const call = await this._getLSTMultiplierRebalanceCall(vesuAdapter);
|
|
530
|
+
if (call.shouldRebalance && call.manageCall) {
|
|
531
|
+
shouldRebalance = true;
|
|
532
|
+
calls.push({ vesuAdapter, manageCall: call.manageCall });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return { shouldRebalance, manageCalls: calls };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async _getLSTMultiplierRebalanceCall(
|
|
539
|
+
vesuAdapter: VesuAdapter
|
|
540
|
+
): Promise<{ shouldRebalance: boolean; manageCall: Call | undefined }> {
|
|
541
|
+
const positions = await vesuAdapter.getPositions(this.config);
|
|
542
|
+
assert(
|
|
543
|
+
positions.length == 2,
|
|
544
|
+
"Rebalance call is only supported for 2 positions"
|
|
545
|
+
);
|
|
546
|
+
const existingCollateralInfo = positions[0];
|
|
547
|
+
const existingDebtInfo = positions[1];
|
|
548
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
549
|
+
const healthFactor = await vesuAdapter.getHealthFactor();
|
|
550
|
+
|
|
551
|
+
const collateralisation = await vesuAdapter.getCollateralization(
|
|
552
|
+
this.config
|
|
553
|
+
);
|
|
554
|
+
logger.debug(`${this.getTag()}::getVesuMultiplyCall::${
|
|
555
|
+
vesuAdapter.config.debt.symbol
|
|
556
|
+
} existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
557
|
+
existingDebtInfo: ${JSON.stringify(
|
|
558
|
+
existingDebtInfo
|
|
559
|
+
)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
560
|
+
|
|
561
|
+
// - Prices as seen by Vesu contracts, ideal for HF math
|
|
562
|
+
// Price 1 is ok as fallback bcz that would relatively price the
|
|
563
|
+
// collateral and debt as equal.
|
|
564
|
+
const collateralPrice =
|
|
565
|
+
collateralisation[0].usdValue > 0
|
|
566
|
+
? collateralisation[0].usdValue /
|
|
567
|
+
existingCollateralInfo.amount.toNumber()
|
|
568
|
+
: 1;
|
|
569
|
+
const debtPrice =
|
|
570
|
+
collateralisation[1].usdValue > 0
|
|
571
|
+
? collateralisation[1].usdValue /
|
|
572
|
+
existingDebtInfo.amount.toNumber()
|
|
573
|
+
: 1;
|
|
574
|
+
logger.debug(
|
|
575
|
+
`${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`
|
|
576
|
+
);
|
|
577
|
+
logger.debug(
|
|
578
|
+
`${this.getTag()}::getVesuMultiplyCall healthFactor: ${healthFactor}`
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
const isHFTooLow =
|
|
582
|
+
healthFactor < this.metadata.additionalInfo.minHealthFactor;
|
|
583
|
+
const isHFTooHigh =
|
|
584
|
+
healthFactor >
|
|
585
|
+
this.metadata.additionalInfo.targetHealthFactor + 0.05;
|
|
586
|
+
if (isHFTooLow || isHFTooHigh || 1) {
|
|
587
|
+
// use unused collateral to target more.
|
|
588
|
+
const manageCall = await this._getAvnuDepositSwapLegCall({
|
|
589
|
+
isDeposit: true,
|
|
590
|
+
leg1DepositAmount: unusedBalance.amount,
|
|
591
|
+
minHF: 1.02, // todo, shouldnt use this 1.02 HF, if there isn;t more looping left.
|
|
592
|
+
vesuAdapter
|
|
593
|
+
});
|
|
594
|
+
return { shouldRebalance: true, manageCall };
|
|
595
|
+
} else {
|
|
596
|
+
// do nothing
|
|
597
|
+
return { shouldRebalance: false, manageCall: undefined };
|
|
598
|
+
}
|
|
82
599
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// )
|
|
115
|
-
// const amountToDeposit = remainingAmount.minimum(maxAmountToDeposit);
|
|
116
|
-
// logger.verbose(`${this.getTag()}::getAvnuSwapMultiplyCall::${vesuAdapter.config.debt.symbol}:: remainingAmount: ${remainingAmount}, amountToDeposit: ${amountToDeposit}, depositAmount: ${amountToDeposit}, maxBorrowable: ${maxBorrowable.amount}`);
|
|
117
|
-
// const call = await this._getAvnuDepositSwapLegCall({
|
|
118
|
-
// isDeposit: params.isDeposit,
|
|
119
|
-
// // adjust decimals of debt asset
|
|
120
|
-
// leg1DepositAmount: amountToDeposit,
|
|
121
|
-
// minHF: 1.1, // undo
|
|
122
|
-
// vesuAdapter: vesuAdapter
|
|
123
|
-
// });
|
|
124
|
-
// remainingAmount = remainingAmount.minus(amountToDeposit);
|
|
125
|
-
|
|
126
|
-
// // return the first possible call because computing all calls at a time
|
|
127
|
-
// // is not efficinet for swaps
|
|
128
|
-
// return {call, vesuAdapter};
|
|
129
|
-
// }
|
|
130
|
-
// throw new Error(`${this.getTag()}::getAvnuSwapMultiplyCall: no calls found`);
|
|
131
|
-
// }
|
|
132
|
-
|
|
133
|
-
// async _getAvnuDepositSwapLegCall(params: {
|
|
134
|
-
// isDeposit: boolean,
|
|
135
|
-
// leg1DepositAmount: Web3Number,
|
|
136
|
-
// minHF: number, // e.g. 1.01
|
|
137
|
-
// vesuAdapter: VesuAdapter
|
|
138
|
-
// }) {
|
|
139
|
-
// const { vesuAdapter } = params;
|
|
140
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall params: ${JSON.stringify(params)}`);
|
|
141
|
-
// assert(params.isDeposit, 'Only deposit is supported in _getAvnuDepositSwapLegCall')
|
|
142
|
-
// // add collateral
|
|
143
|
-
// // borrow STRK (e.g.)
|
|
144
|
-
// // approve and swap strk
|
|
145
|
-
// // add collateral again
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// const legLTV = await vesuAdapter.getLTVConfig(this.config);
|
|
149
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall legLTV: ${legLTV}`);
|
|
150
|
-
// const existingPositions = await vesuAdapter.getPositions(this.config);
|
|
151
|
-
// const collateralisation = await vesuAdapter.getCollateralization(this.config);
|
|
152
|
-
// const existingCollateralInfo = existingPositions[0];
|
|
153
|
-
// const existingDebtInfo = existingPositions[1];
|
|
154
|
-
// logger.debug(`${this.getTag()}::_getAvnuDepositSwapLegCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
155
|
-
// existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
156
|
-
|
|
157
|
-
// // - Prices as seen by Vesu contracts, ideal for HF math
|
|
158
|
-
// // Price 1 is ok as fallback bcz that would relatively price the
|
|
159
|
-
// // collateral and debt as equal.
|
|
160
|
-
// const collateralPrice = collateralisation[0].usdValue > 0 ? collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() : 1;
|
|
161
|
-
// const debtPrice = collateralisation[1].usdValue > 0 ? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() : 1;
|
|
162
|
-
// logger.debug(`${this.getTag()}::_getAvnuDepositSwapLegCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// const debtTokenInfo = vesuAdapter.config.debt;
|
|
166
|
-
// let newDepositAmount = params.leg1DepositAmount;
|
|
167
|
-
// const totalCollateral = existingCollateralInfo.amount.plus(params.leg1DepositAmount);
|
|
168
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall totalCollateral: ${totalCollateral}`);
|
|
169
|
-
// const totalDebtAmount = new Web3Number(
|
|
170
|
-
// totalCollateral.multipliedBy(collateralPrice).multipliedBy(legLTV).dividedBy(debtPrice).dividedBy(params.minHF).toString(),
|
|
171
|
-
// debtTokenInfo.decimals
|
|
172
|
-
// );
|
|
173
|
-
// let debtAmount = totalDebtAmount.minus(existingDebtInfo.amount);
|
|
174
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall totalDebtAmount: ${totalDebtAmount}, initial computed debt: ${debtAmount}`);
|
|
175
|
-
// const maxBorrowable = await this.getMaxBorrowableAmountByVesuAdapter(vesuAdapter, false);
|
|
176
|
-
|
|
177
|
-
// // if the debt amount is greater than 0 and the max borrowable amount is 0, skip
|
|
178
|
-
// if (debtAmount.gt(0) && maxBorrowable.amount.eq(0)) {
|
|
179
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall maxBorrowable is 0, skipping`);
|
|
180
|
-
// return undefined;
|
|
181
|
-
// } else if (debtAmount.gt(0) && maxBorrowable.amount.gt(0)) {
|
|
182
|
-
// debtAmount = maxBorrowable.amount.minimum(debtAmount);
|
|
183
|
-
// const newDebtUSDValue = debtAmount.multipliedBy(debtPrice);
|
|
184
|
-
// const totalCollateralRequired = HealthFactorMath.getCollateralRequired(
|
|
185
|
-
// debtAmount.plus(existingDebtInfo.amount),
|
|
186
|
-
// debtPrice,
|
|
187
|
-
// params.minHF,
|
|
188
|
-
// legLTV,
|
|
189
|
-
// collateralPrice,
|
|
190
|
-
// this.asset()
|
|
191
|
-
// );
|
|
192
|
-
// newDepositAmount = totalCollateralRequired.minus(existingCollateralInfo.amount);
|
|
193
|
-
// if (newDepositAmount.lt(0)) {
|
|
194
|
-
// throw new Error(`${this.getTag()}::_getAvnuDepositSwapLegCall newDepositAmount is less than 0, newDepositAmount: ${newDepositAmount}, totalCollateralRequired: ${totalCollateralRequired}, existingCollateralInfo.amount: ${existingCollateralInfo.amount}`);
|
|
195
|
-
// }
|
|
196
|
-
// if (newDebtUSDValue.toNumber() < 100) {
|
|
197
|
-
// // too less debt, skip
|
|
198
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall newDebtUSDValue is less than 100, skipping`);
|
|
199
|
-
// return undefined;
|
|
200
|
-
// }
|
|
201
|
-
// }
|
|
202
|
-
|
|
203
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall debtAmount: ${debtAmount}`);
|
|
204
|
-
// if (debtAmount.lt(0)) {
|
|
205
|
-
// // this is to unwind the position to optimal HF.
|
|
206
|
-
// const lstDEXPrice = await this.getLSTDexPrice();
|
|
207
|
-
// const debtAmountInLST = debtAmount.abs().dividedBy(lstDEXPrice);
|
|
208
|
-
// const calls = await this.getVesuMultiplyCall({
|
|
209
|
-
// isDeposit: false,
|
|
210
|
-
// leg1DepositAmount: debtAmountInLST
|
|
211
|
-
// })
|
|
212
|
-
// assert(calls.length == 1, `Expected 1 call for unwind, got ${calls.length}`);
|
|
213
|
-
// return calls[0];
|
|
214
|
-
// }
|
|
215
|
-
// console.log(`debtAmount`, debtAmount.toWei(), params.leg1DepositAmount.toWei());
|
|
216
|
-
// const STEP0 = UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1;
|
|
217
|
-
// const manage0Info = this.getProofs<ApproveCallParams>(STEP0);
|
|
218
|
-
// const manageCall0 = manage0Info.callConstructor({
|
|
219
|
-
// amount: newDepositAmount
|
|
220
|
-
// });
|
|
221
|
-
// const STEP1 = getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, vesuAdapter.config.debt.symbol);
|
|
222
|
-
// const manage1Info = this.getProofs<VesuModifyPositionCallParams>(STEP1);
|
|
223
|
-
// const manageCall1 = manage1Info.callConstructor(VesuAdapter.getDefaultModifyPositionCallParams({
|
|
224
|
-
// collateralAmount: newDepositAmount,
|
|
225
|
-
// isAddCollateral: params.isDeposit,
|
|
226
|
-
// debtAmount: debtAmount,
|
|
227
|
-
// isBorrow: params.isDeposit
|
|
228
|
-
// }));
|
|
229
|
-
|
|
230
|
-
// console.log(`manageCall1`, manageCall1.call, debtAmount.toWei(), newDepositAmount.toWei());
|
|
231
|
-
|
|
232
|
-
// const proofIds: string[] = [STEP0, STEP1];
|
|
233
|
-
// const manageCalls: ManageCall[] = [manageCall0, manageCall1];
|
|
234
|
-
|
|
235
|
-
// // approve and swap to LST using avnu
|
|
236
|
-
// if (debtAmount.gt(0)) {
|
|
237
|
-
// const STEP2 = getAvnuManageIDs(LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT, vesuAdapter.config.debt.symbol);
|
|
238
|
-
// const manage2Info = this.getProofs<ApproveCallParams>(STEP2);
|
|
239
|
-
// const manageCall2 = manage2Info.callConstructor({
|
|
240
|
-
// amount: debtAmount
|
|
241
|
-
// });
|
|
242
|
-
|
|
243
|
-
// const debtTokenInfo = vesuAdapter.config.debt;
|
|
244
|
-
// const lstTokenInfo = this.asset();
|
|
245
|
-
// const avnuModule = new AvnuWrapper();
|
|
246
|
-
// const quote = await avnuModule.getQuotes(
|
|
247
|
-
// debtTokenInfo.address.address,
|
|
248
|
-
// lstTokenInfo.address.address,
|
|
249
|
-
// debtAmount.toWei(),
|
|
250
|
-
// this.metadata.additionalInfo.vaultAllocator.address
|
|
251
|
-
// );
|
|
252
|
-
// const minAmount = await this._getMinOutputAmountLSTBuy(debtAmount);
|
|
253
|
-
// const minAmountWei = (minAmount).toWei();
|
|
254
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall minAmount: ${minAmount}`);
|
|
255
|
-
// const swapInfo = await avnuModule.getSwapInfo(
|
|
256
|
-
// quote,
|
|
257
|
-
// this.metadata.additionalInfo.vaultAllocator.address,
|
|
258
|
-
// 0,
|
|
259
|
-
// this.address.address,
|
|
260
|
-
// minAmountWei
|
|
261
|
-
// );
|
|
262
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall swapInfo: ${JSON.stringify(swapInfo)}`);
|
|
263
|
-
// const STEP3 = getAvnuManageIDs(LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT, vesuAdapter.config.debt.symbol);
|
|
264
|
-
// const manage3Info = this.getProofs<AvnuSwapCallParams>(STEP3);
|
|
265
|
-
// const manageCall3 = manage3Info.callConstructor({
|
|
266
|
-
// props: swapInfo
|
|
267
|
-
// });
|
|
268
|
-
// proofIds.push(STEP2);
|
|
269
|
-
// proofIds.push(STEP3);
|
|
270
|
-
// manageCalls.push(manageCall2, manageCall3);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// // if the created debt, when added is collateral will put the total HF above min, but below (target + 0.05),
|
|
274
|
-
// // then lets close the looping cycle by adding this as collateral.
|
|
275
|
-
// const newCollateral = minAmount.plus(totalCollateral);
|
|
276
|
-
// const newHF = newCollateral.multipliedBy(collateralPrice).multipliedBy(legLTV).dividedBy(totalDebtAmount).dividedBy(debtPrice).toNumber();
|
|
277
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall newHF: ${newHF}`);
|
|
278
|
-
// if (newHF > this.metadata.additionalInfo.minHealthFactor && newHF < this.metadata.additionalInfo.targetHealthFactor + 0.05) {
|
|
279
|
-
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall newHF is above min and below target + 0.05, adding collateral on vesu`);
|
|
280
|
-
// // approve and add collateral on vesu (modify position)
|
|
281
|
-
// const STEP4 = UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1;
|
|
282
|
-
// const manage4Info = this.getProofs<ApproveCallParams>(STEP4);
|
|
283
|
-
// const manageCall4 = manage4Info.callConstructor({
|
|
284
|
-
// amount: minAmount
|
|
285
|
-
// });
|
|
286
|
-
|
|
287
|
-
// const STEP5 = getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, vesuAdapter.config.debt.symbol);
|
|
288
|
-
// const manage5Info = this.getProofs<VesuModifyPositionCallParams>(STEP5);
|
|
289
|
-
// const manageCall5 = manage5Info.callConstructor(VesuAdapter.getDefaultModifyPositionCallParams({
|
|
290
|
-
// collateralAmount: minAmount,
|
|
291
|
-
// isAddCollateral: true,
|
|
292
|
-
// debtAmount: Web3Number.fromWei('0', this.asset().decimals),
|
|
293
|
-
// isBorrow: params.isDeposit
|
|
294
|
-
// }));
|
|
295
|
-
// proofIds.push(STEP4, STEP5);
|
|
296
|
-
// manageCalls.push(manageCall4, manageCall5);
|
|
297
|
-
// }
|
|
298
|
-
// }
|
|
299
|
-
|
|
300
|
-
// const manageCall = this.getManageCall(proofIds, manageCalls);
|
|
301
|
-
// return manageCall;
|
|
302
|
-
// }
|
|
303
|
-
|
|
304
|
-
// todo unwind or not deposit when the yield is bad.
|
|
305
|
-
|
|
306
|
-
// async getLSTMultiplierRebalanceCall(): Promise<{ shouldRebalance: boolean, manageCalls: {vesuAdapter: VesuAdapter, manageCall: Call}[] }> {
|
|
307
|
-
// let shouldRebalance = false;
|
|
308
|
-
// const calls: {vesuAdapter: VesuAdapter, manageCall: Call}[] = [];
|
|
309
|
-
// // todo undo
|
|
310
|
-
// const allVesuAdapters = this.getVesuAdapters().filter(vesuAdapter => vesuAdapter.config.debt.symbol === 'LBTC');
|
|
311
|
-
// for (const vesuAdapter of allVesuAdapters) {
|
|
312
|
-
// const call = await this._getLSTMultiplierRebalanceCall(vesuAdapter);
|
|
313
|
-
// if (call.shouldRebalance && call.manageCall) {
|
|
314
|
-
// shouldRebalance = true;
|
|
315
|
-
// calls.push({vesuAdapter, manageCall: call.manageCall});
|
|
316
|
-
// }
|
|
317
|
-
// }
|
|
318
|
-
// return { shouldRebalance, manageCalls: calls };
|
|
319
|
-
// }
|
|
320
|
-
|
|
321
|
-
// async _getLSTMultiplierRebalanceCall(vesuAdapter: VesuAdapter): Promise<{ shouldRebalance: boolean, manageCall: Call | undefined }> {
|
|
322
|
-
// const positions = await vesuAdapter.getPositions(this.config);
|
|
323
|
-
// assert(positions.length == 2, 'Rebalance call is only supported for 2 positions');
|
|
324
|
-
// const existingCollateralInfo = positions[0];
|
|
325
|
-
// const existingDebtInfo = positions[1];
|
|
326
|
-
// const unusedBalance = await this.getUnusedBalance();
|
|
327
|
-
// const healthFactor = await vesuAdapter.getHealthFactor();
|
|
328
|
-
|
|
329
|
-
// const collateralisation = await vesuAdapter.getCollateralization(this.config);
|
|
330
|
-
// logger.debug(`${this.getTag()}::getVesuMultiplyCall::${vesuAdapter.config.debt.symbol} existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
331
|
-
// existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
332
|
-
|
|
333
|
-
// // - Prices as seen by Vesu contracts, ideal for HF math
|
|
334
|
-
// // Price 1 is ok as fallback bcz that would relatively price the
|
|
335
|
-
// // collateral and debt as equal.
|
|
336
|
-
// const collateralPrice = collateralisation[0].usdValue > 0 ? collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() : 1;
|
|
337
|
-
// const debtPrice = collateralisation[1].usdValue > 0 ? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() : 1;
|
|
338
|
-
// logger.debug(`${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
|
|
339
|
-
// logger.debug(`${this.getTag()}::getVesuMultiplyCall healthFactor: ${healthFactor}`);
|
|
340
|
-
|
|
341
|
-
// const isHFTooLow = healthFactor < this.metadata.additionalInfo.minHealthFactor;
|
|
342
|
-
// const isHFTooHigh = healthFactor > this.metadata.additionalInfo.targetHealthFactor + 0.05;
|
|
343
|
-
// if (isHFTooLow || isHFTooHigh || 1) {
|
|
344
|
-
// // use unused collateral to target more.
|
|
345
|
-
// const manageCall = await this._getAvnuDepositSwapLegCall({
|
|
346
|
-
// isDeposit: true,
|
|
347
|
-
// leg1DepositAmount: unusedBalance.amount,
|
|
348
|
-
// minHF: 1.02, // todo, shouldnt use this 1.02 HF, if there isn;t more looping left.
|
|
349
|
-
// vesuAdapter
|
|
350
|
-
// })
|
|
351
|
-
// return { shouldRebalance: true, manageCall };
|
|
352
|
-
// } else {
|
|
353
|
-
// // do nothing
|
|
354
|
-
// return { shouldRebalance: false, manageCall: undefined };
|
|
355
|
-
// }
|
|
356
|
-
// }
|
|
357
|
-
|
|
358
|
-
// protected async getVesuAUM(adapter: VesuAdapter) {
|
|
359
|
-
// const legAUM = await adapter.getPositions(this.config);
|
|
360
|
-
// const underlying = this.asset();
|
|
361
|
-
// // assert its an LST of Endur
|
|
362
|
-
// assert(underlying.symbol.startsWith('x'), 'Underlying is not an LST of Endur');
|
|
363
|
-
|
|
364
|
-
// let vesuAum = Web3Number.fromWei("0", underlying.decimals);
|
|
365
|
-
|
|
366
|
-
// let tokenUnderlyingPrice = await this.getLSTExchangeRate();
|
|
367
|
-
// // to offset for usual DEX lag, we multiply by 0.998 (i.e. 0.2% loss)
|
|
368
|
-
// tokenUnderlyingPrice = tokenUnderlyingPrice * 0.998;
|
|
369
|
-
// logger.verbose(`${this.getTag()} tokenUnderlyingPrice: ${tokenUnderlyingPrice}`);
|
|
370
|
-
|
|
371
|
-
// // handle collateral
|
|
372
|
-
// if (legAUM[0].token.address.eq(underlying.address)) {
|
|
373
|
-
// vesuAum = vesuAum.plus(legAUM[0].amount);
|
|
374
|
-
// } else {
|
|
375
|
-
// vesuAum = vesuAum.plus(legAUM[0].amount.dividedBy(tokenUnderlyingPrice));
|
|
376
|
-
// }
|
|
377
|
-
|
|
378
|
-
// // handle debt
|
|
379
|
-
// if (legAUM[1].token.address.eq(underlying.address)) {
|
|
380
|
-
// vesuAum = vesuAum.minus(legAUM[1].amount);
|
|
381
|
-
// } else {
|
|
382
|
-
// vesuAum = vesuAum.minus(legAUM[1].amount.dividedBy(tokenUnderlyingPrice));
|
|
383
|
-
// };
|
|
384
|
-
|
|
385
|
-
// logger.verbose(`${this.getTag()} Vesu AUM: ${vesuAum}, legCollateral: ${legAUM[0].amount.toNumber()}, legDebt: ${legAUM[1].amount.toNumber()}`);
|
|
386
|
-
// return vesuAum;
|
|
387
|
-
// }
|
|
388
|
-
|
|
389
|
-
//
|
|
390
|
-
// private async _getMinOutputAmountLSTBuy(amountInUnderlying: Web3Number) {
|
|
391
|
-
// const lstTruePrice = await this.getLSTExchangeRate();
|
|
392
|
-
// // during buy, the purchase should always be <= true LST price.
|
|
393
|
-
// const minOutputAmount = amountInUnderlying.dividedBy(lstTruePrice).multipliedBy(0.99979); // minus 0.021% to account for avnu fees
|
|
394
|
-
// return new Web3Number(minOutputAmount.toString(), this.asset().decimals);
|
|
395
|
-
// }
|
|
396
|
-
|
|
397
|
-
// private async _getMinOutputAmountLSTSell(amountInLST: Web3Number) {
|
|
398
|
-
// const lstTruePrice = await this.getLSTExchangeRate();
|
|
399
|
-
// // during sell, the purchase should always be > 0.995 * true LST price.
|
|
400
|
-
// const minOutputAmount = amountInLST.multipliedBy(lstTruePrice).multipliedBy(0.995);
|
|
401
|
-
// return minOutputAmount;
|
|
402
|
-
// }
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
// todo add a function to findout max borrowable amount without fucking yield
|
|
407
|
-
// if the current net yield < LST yield, add a function to calculate how much to unwind.
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Uses vesu's multiple call to create leverage on LST
|
|
411
|
-
* Deposit amount is in LST
|
|
412
|
-
* @param params
|
|
413
|
-
*/
|
|
414
|
-
async getFundManagementCall(params: {
|
|
415
|
-
isDeposit: boolean,
|
|
416
|
-
leg1DepositAmount: Web3Number
|
|
417
|
-
}) {
|
|
418
|
-
logger.verbose(`${this.getTag()}::getFundManagementCall params: ${JSON.stringify(params)}`);
|
|
419
|
-
// const legLTV = await vesuAdapter1.getLTVConfig(this.config);
|
|
420
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall legLTV: ${legLTV}`);
|
|
421
|
-
const allAdapters = this.metadata.additionalInfo.adapters.map(adapter => adapter.adapter);
|
|
422
|
-
if (!params.isDeposit) {
|
|
423
|
-
// try using unused balance to unwind.
|
|
424
|
-
// no need to unwind.
|
|
425
|
-
const unusedBalance = await this.getUnusedBalance();
|
|
426
|
-
logger.verbose(`${this.getTag()}::getVesuMultiplyCall unusedBalance: ${unusedBalance.amount.toString()}, required: ${params.leg1DepositAmount.toString()}`);
|
|
427
|
-
if (unusedBalance.amount.gte(params.leg1DepositAmount)) {
|
|
428
|
-
return null;
|
|
429
|
-
} else {
|
|
430
|
-
const adapters = await AdapterOptimizer.getAdapterToUse(allAdapters, false, params.leg1DepositAmount);
|
|
431
|
-
if (adapters.length > 0) {
|
|
432
|
-
const proofsInfo = adapters.map(adapter => adapter.getProofs(false, this.getMerkleTree()));
|
|
433
|
-
const calls: Call[] = [];
|
|
434
|
-
for (const info of proofsInfo) {
|
|
435
|
-
const proofGroups = info.proofs;
|
|
436
|
-
const call = this.getManageCall(proofGroups, await info.callConstructor({amount: params.leg1DepositAmount}));
|
|
437
|
-
calls.push(call);
|
|
438
|
-
}
|
|
439
|
-
return calls;
|
|
600
|
+
|
|
601
|
+
protected async getVesuAUM(adapter: VesuAdapter) {
|
|
602
|
+
const legAUM = await adapter.getPositions(this.config);
|
|
603
|
+
const underlying = this.asset();
|
|
604
|
+
// assert its an LST of Endur
|
|
605
|
+
assert(
|
|
606
|
+
underlying.symbol.startsWith("x"),
|
|
607
|
+
"Underlying is not an LST of Endur"
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
let vesuAum = Web3Number.fromWei("0", underlying.decimals);
|
|
611
|
+
|
|
612
|
+
let tokenUnderlyingPrice = await this.getLSTExchangeRate();
|
|
613
|
+
// to offset for usual DEX lag, we multiply by 0.998 (i.e. 0.2% loss)
|
|
614
|
+
tokenUnderlyingPrice = tokenUnderlyingPrice * 0.998;
|
|
615
|
+
logger.verbose(
|
|
616
|
+
`${this.getTag()} tokenUnderlyingPrice: ${tokenUnderlyingPrice}`
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
// handle collateral
|
|
620
|
+
if (legAUM[0].token.address.eq(underlying.address)) {
|
|
621
|
+
vesuAum = vesuAum.plus(legAUM[0].amount);
|
|
622
|
+
} else {
|
|
623
|
+
vesuAum = vesuAum.plus(
|
|
624
|
+
legAUM[0].amount.dividedBy(tokenUnderlyingPrice)
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// handle debt
|
|
629
|
+
if (legAUM[1].token.address.eq(underlying.address)) {
|
|
630
|
+
vesuAum = vesuAum.minus(legAUM[1].amount);
|
|
440
631
|
} else {
|
|
441
|
-
|
|
632
|
+
vesuAum = vesuAum.minus(
|
|
633
|
+
legAUM[1].amount.dividedBy(tokenUnderlyingPrice)
|
|
634
|
+
);
|
|
442
635
|
}
|
|
443
|
-
|
|
636
|
+
|
|
637
|
+
logger.verbose(
|
|
638
|
+
`${this.getTag()} Vesu AUM: ${vesuAum}, legCollateral: ${legAUM[0].amount.toNumber()}, legDebt: ${legAUM[1].amount.toNumber()}`
|
|
639
|
+
);
|
|
640
|
+
return vesuAum;
|
|
444
641
|
}
|
|
445
642
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
throw new Error(`${this.getTag()}::getVesuMultiplyCall: no adapters to use for deposit: ${params.leg1DepositAmount.toString()}`);
|
|
643
|
+
//
|
|
644
|
+
private async _getMinOutputAmountLSTBuy(amountInUnderlying: Web3Number) {
|
|
645
|
+
const lstTruePrice = await this.getLSTExchangeRate();
|
|
646
|
+
// during buy, the purchase should always be <= true LST price.
|
|
647
|
+
const minOutputAmount = amountInUnderlying
|
|
648
|
+
.dividedBy(lstTruePrice)
|
|
649
|
+
.multipliedBy(0.99979); // minus 0.021% to account for avnu fees
|
|
650
|
+
return new Web3Number(
|
|
651
|
+
minOutputAmount.toString(),
|
|
652
|
+
this.asset().decimals
|
|
653
|
+
);
|
|
458
654
|
}
|
|
459
655
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
// // - Prices as seen by Vesu contracts, ideal for HF math
|
|
468
|
-
// // Price 1 is ok as fallback bcz that would relatively price the
|
|
469
|
-
// // collateral and debt as equal.
|
|
470
|
-
// const collateralPrice = collateralisation[0].usdValue > 0 ? collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() : 1;
|
|
471
|
-
// const debtPrice = collateralisation[1].usdValue > 0 ? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() : 1;
|
|
472
|
-
// logger.debug(`${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
|
|
473
|
-
|
|
474
|
-
// // - Prices as seen by actual swap price
|
|
475
|
-
// const dexPrice = await this.getLSTDexPrice();
|
|
476
|
-
|
|
477
|
-
// // compute optimal amount of collateral and debt post addition/removal
|
|
478
|
-
// // target hf = collateral * collateralPrice * ltv / debt * debtPrice
|
|
479
|
-
// // assuming X to be the usd amount of debt borrowed or repaied (negative).
|
|
480
|
-
// // target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv / (debt * debtPrice + X)
|
|
481
|
-
// // => X * target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv - (debt * debtPrice * target hf)
|
|
482
|
-
// // => X * (target hf - ltv)= ((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)
|
|
483
|
-
// // => X = (((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)) / (target hf - ltv)
|
|
484
|
-
// const addedCollateral = params.leg1DepositAmount
|
|
485
|
-
// .multipliedBy(params.isDeposit ? 1 : -1)
|
|
486
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall addedCollateral: ${addedCollateral}`);
|
|
487
|
-
// const numeratorPart1 = (existingCollateralInfo.amount.plus((addedCollateral))).multipliedBy(collateralPrice).multipliedBy(legLTV);
|
|
488
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}`);
|
|
489
|
-
// const numeratorPart2 = existingDebtInfo.amount.multipliedBy(debtPrice).multipliedBy(this.metadata.additionalInfo.targetHealthFactor);
|
|
490
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall numeratorPart2: ${numeratorPart2}`);
|
|
491
|
-
// const denominatorPart = this.metadata.additionalInfo.targetHealthFactor - (legLTV / dexPrice);
|
|
492
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall denominatorPart: ${denominatorPart}`);
|
|
493
|
-
// const x_debt_usd = numeratorPart1.minus(numeratorPart2).dividedBy(denominatorPart);
|
|
494
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall x_debt_usd: ${x_debt_usd}`);
|
|
495
|
-
// logger.debug(`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}, numeratorPart2: ${numeratorPart2}, denominatorPart: ${denominatorPart}`);
|
|
496
|
-
|
|
497
|
-
// // both in underlying
|
|
498
|
-
// const debtAmount = x_debt_usd.dividedBy(debtPrice);
|
|
499
|
-
// const marginAmount = addedCollateral;
|
|
500
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall debtAmount: ${debtAmount}, marginAmount: ${marginAmount}`);
|
|
501
|
-
|
|
502
|
-
// // Cases of lever increase (within the scopr of this function)
|
|
503
|
-
// // 1. debtAmount > 0 and marginAmount > 0
|
|
504
|
-
// // 2. debtAmount > 0 and marginAmount < 0
|
|
505
|
-
|
|
506
|
-
// // Cases of lever decrease
|
|
507
|
-
// // 3. debtAmount < 0 and marginAmount > 0
|
|
508
|
-
// // 4. debtAmount < 0 and marginAmount < 0
|
|
509
|
-
// return this.getModifyLeverCall({
|
|
510
|
-
// marginAmount,
|
|
511
|
-
// debtAmount,
|
|
512
|
-
// lstDexPriceInUnderlying: dexPrice,
|
|
513
|
-
// isIncrease: debtAmount.greaterThan(0)
|
|
514
|
-
// });
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
getLSTUnderlyingTokenInfo() {
|
|
519
|
-
return this.metadata.additionalInfo.underlyingToken;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// async getMaxBorrowableAmount(params: { isAPYComputation: boolean } = { isAPYComputation: false }) {
|
|
523
|
-
// const vesuAdapters = this.getVesuAdapters();
|
|
524
|
-
// let netMaxBorrowableAmount = Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals);
|
|
525
|
-
// const maxBorrowables: {amount: Web3Number, dexSwappableAmount: Web3Number, maxBorrowableAmount: Web3Number, borrowableAsset: TokenInfo}[] = [];
|
|
526
|
-
// for (const vesuAdapter of vesuAdapters) {
|
|
527
|
-
// maxBorrowables.push(await this.getMaxBorrowableAmountByVesuAdapter(vesuAdapter, params.isAPYComputation));
|
|
528
|
-
// }
|
|
529
|
-
// maxBorrowables.sort((a, b) => b.amount.toNumber() - a.amount.toNumber());
|
|
530
|
-
// netMaxBorrowableAmount = maxBorrowables.reduce((acc, curr) => acc.plus(curr.amount), Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals));
|
|
531
|
-
// return {netMaxBorrowableAmount, maxBorrowables};
|
|
532
|
-
// }
|
|
533
|
-
|
|
534
|
-
// recursively, using binary search computes max swappable.
|
|
535
|
-
// @dev assumes 1 token of from == 1 token of to
|
|
536
|
-
// async getMaxSwappableWithMaxSlippage(fromToken: TokenInfo, toToken: TokenInfo, maxSlippage: number, maxAmount: Web3Number) {
|
|
537
|
-
// const output = await findMaxInputWithSlippage({
|
|
538
|
-
// apiGetOutput: async (inputAmount: number): Promise<number> => {
|
|
539
|
-
// const ekuboQuoter = new EkuboQuoter(this.config);
|
|
540
|
-
// await new Promise(resolve => setTimeout(resolve, 1000)); // artificial delay, to avoid rate limit
|
|
541
|
-
// const quote = await ekuboQuoter.getQuote(fromToken.address.address, toToken.address.address, new Web3Number(inputAmount.toFixed(9), fromToken.decimals));
|
|
542
|
-
// return Web3Number.fromWei(quote.total_calculated.toString(), toToken.decimals).toNumber();
|
|
543
|
-
// },
|
|
544
|
-
// maxInput: maxAmount.toNumber(),
|
|
545
|
-
// maxSlippagePercent: maxSlippage,
|
|
546
|
-
// tolerance: 0.001,
|
|
547
|
-
// referenceRate: 1,
|
|
548
|
-
// });
|
|
549
|
-
// return new Web3Number(output.optimalInput, fromToken.decimals);
|
|
550
|
-
// }
|
|
551
|
-
|
|
552
|
-
// async getMaxBorrowableAmountByVesuAdapter(vesuAdapter: VesuAdapter, isAPYComputation: boolean) {
|
|
553
|
-
// const lstAPY = await this.getLSTAPR(this.getLSTUnderlyingTokenInfo().address);
|
|
554
|
-
// const maxInterestRate = lstAPY * 0.8;
|
|
555
|
-
// const maxBorrowableAmount = await vesuAdapter.getMaxBorrowableByInterestRate(this.config, vesuAdapter.config.debt, maxInterestRate);
|
|
556
|
-
// const debtCap = await vesuAdapter.getDebtCap(this.config);
|
|
557
|
-
|
|
558
|
-
// const maxBorrowable = maxBorrowableAmount.minimum(debtCap).multipliedBy(0.999);
|
|
559
|
-
// // Dont compute precise max swappable for APY computation
|
|
560
|
-
// if (vesuAdapter.config.debt.address.eq(this.getLSTUnderlyingTokenInfo().address) || isAPYComputation) {
|
|
561
|
-
// return {amount: maxBorrowable, dexSwappableAmount: maxBorrowable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
|
|
562
|
-
// }
|
|
563
|
-
// // Want < 0.02% slippage
|
|
564
|
-
// try {
|
|
565
|
-
// const maxSwappable = await this.getMaxSwappableWithMaxSlippage(vesuAdapter.config.debt, this.getLSTUnderlyingTokenInfo(), 0.0002, maxBorrowable);
|
|
566
|
-
// return {amount: maxBorrowable.minimum(maxSwappable), dexSwappableAmount: maxSwappable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
|
|
567
|
-
// } catch (error) {
|
|
568
|
-
// logger.warn(`${this.getTag()}: Failed to get max swappable: ${error}`);
|
|
569
|
-
// const maxSwappable = Web3Number.fromWei("0", vesuAdapter.config.debt.decimals);
|
|
570
|
-
// return {amount: maxBorrowable.minimum(maxSwappable), dexSwappableAmount: maxSwappable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
|
|
571
|
-
// }
|
|
572
|
-
// }
|
|
573
|
-
|
|
574
|
-
// todo how much to unwind to get back healthy APY zone again
|
|
575
|
-
// if net APY < LST APR + 0.5%, we need to unwind to get back to LST APR + 1% atleast or 0 vesu position
|
|
576
|
-
// For xSTRK, simply deposit in Vesu if looping is not viable
|
|
577
|
-
|
|
578
|
-
// /**
|
|
579
|
-
// * Gets LST APR for the strategy's underlying asset from Endur API
|
|
580
|
-
// * @returns Promise<number> The LST APR (not divided by 1e18)
|
|
581
|
-
// */
|
|
582
|
-
// async getLSTAPR(_address: ContractAddr): Promise<number> {
|
|
583
|
-
// try {
|
|
584
|
-
// const vesuAdapter1 = this.getVesuSameTokenAdapter();
|
|
585
|
-
// const apr = await LSTAPRService.getLSTAPR(vesuAdapter1.config.debt.address);
|
|
586
|
-
// if (!apr) {
|
|
587
|
-
// throw new Error('Failed to get LST APR');
|
|
588
|
-
// }
|
|
589
|
-
// return apr;
|
|
590
|
-
// } catch (error) {
|
|
591
|
-
// logger.warn(`${this.getTag()}: Failed to get LST APR: ${error}`);
|
|
592
|
-
// return 0;
|
|
593
|
-
// }
|
|
594
|
-
// }
|
|
595
|
-
|
|
596
|
-
// async maxNewDeposits(params: { isAPYComputation: boolean } = { isAPYComputation: false }) {
|
|
597
|
-
// const maxBorrowableAmounts = await this.getMaxBorrowableAmount(params);
|
|
598
|
-
|
|
599
|
-
// let ltv: number | undefined = undefined;
|
|
600
|
-
// for (let adapter of this.getVesuAdapters()) {
|
|
601
|
-
// const maxBorrowableAmount = maxBorrowableAmounts.maxBorrowables.find(b => b.borrowableAsset.address.eq(adapter.config.debt.address))?.amount;
|
|
602
|
-
// if (!maxBorrowableAmount) {
|
|
603
|
-
// throw new Error(`Max borrowable amount not found for adapter: ${adapter.config.debt.symbol}`);
|
|
604
|
-
// }
|
|
605
|
-
// const maxLTV = await adapter.getLTVConfig(this.config);
|
|
606
|
-
// if (!ltv) {
|
|
607
|
-
// ltv = maxLTV;
|
|
608
|
-
// } else if (ltv != maxLTV) {
|
|
609
|
-
// throw new Error(`LTV mismatch for adapter: ${adapter.config.debt.symbol}`);
|
|
610
|
-
// }
|
|
611
|
-
// }
|
|
612
|
-
// if (!ltv) {
|
|
613
|
-
// throw new Error('LTV not found');
|
|
614
|
-
// }
|
|
615
|
-
// // for simplicity, we assume 1 underlying = 1 LST
|
|
616
|
-
// const numerator = this.metadata.additionalInfo.targetHealthFactor * maxBorrowableAmounts.netMaxBorrowableAmount.toNumber() / (ltv)
|
|
617
|
-
// return numerator - maxBorrowableAmounts.netMaxBorrowableAmount.toNumber();
|
|
618
|
-
// }
|
|
619
|
-
|
|
620
|
-
async getAUM(): Promise<{net: SingleTokenInfo, prevAum: Web3Number, splits: PositionInfo[]}> {
|
|
621
|
-
const allPositions: PositionInfo[] = [];
|
|
622
|
-
for (let adapter of this.metadata.additionalInfo.adapters) {
|
|
623
|
-
const positions = await adapter.adapter.getPositions();
|
|
624
|
-
allPositions.push(...positions);
|
|
656
|
+
private async _getMinOutputAmountLSTSell(amountInLST: Web3Number) {
|
|
657
|
+
const lstTruePrice = await this.getLSTExchangeRate();
|
|
658
|
+
// during sell, the purchase should always be > 0.995 * true LST price.
|
|
659
|
+
const minOutputAmount = amountInLST
|
|
660
|
+
.multipliedBy(lstTruePrice)
|
|
661
|
+
.multipliedBy(0.995);
|
|
662
|
+
return minOutputAmount;
|
|
625
663
|
}
|
|
626
664
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
665
|
+
// todo add a function to findout max borrowable amount without fucking yield
|
|
666
|
+
// if the current net yield < LST yield, add a function to calculate how much to unwind.
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Uses vesu's multiple call to create leverage on LST
|
|
670
|
+
* Deposit amount is in LST
|
|
671
|
+
* @param params
|
|
672
|
+
*/
|
|
673
|
+
async getVesuMultiplyCall(params: {
|
|
674
|
+
isDeposit: boolean;
|
|
675
|
+
leg1DepositAmount: Web3Number;
|
|
676
|
+
maxEkuboPriceImpact?: number;
|
|
677
|
+
}) {
|
|
678
|
+
const maxEkuboPriceImpact = params.maxEkuboPriceImpact || 0.01;
|
|
679
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter();
|
|
680
|
+
const legLTV = await vesuAdapter1.getLTVConfig(this.config);
|
|
681
|
+
logger.verbose(
|
|
682
|
+
`${this.getTag()}::getVesuMultiplyCall legLTV: ${legLTV}`
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
if (!params.isDeposit) {
|
|
686
|
+
// try using unused balance to unwind.
|
|
687
|
+
// no need to unwind.
|
|
688
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
689
|
+
logger.verbose(
|
|
690
|
+
`${this.getTag()}::getVesuMultiplyCall unusedBalance: ${unusedBalance.amount.toString()}, required: ${params.leg1DepositAmount.toString()}`
|
|
691
|
+
);
|
|
692
|
+
// undo
|
|
693
|
+
// if (unusedBalance.amount.gte(params.leg1DepositAmount)) {
|
|
694
|
+
// return [];
|
|
695
|
+
// }
|
|
696
|
+
// throw new Error('Unused balance is less than the amount to unwind');
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const existingPositions = await vesuAdapter1.getPositions(this.config);
|
|
700
|
+
const collateralisation = await vesuAdapter1.getCollateralization(
|
|
701
|
+
this.config
|
|
702
|
+
);
|
|
703
|
+
const existingCollateralInfo = existingPositions[0];
|
|
704
|
+
const existingDebtInfo = existingPositions[1];
|
|
705
|
+
logger.debug(`${this.getTag()}::getVesuMultiplyCall existingCollateralInfo: ${JSON.stringify(
|
|
706
|
+
existingCollateralInfo
|
|
707
|
+
)},
|
|
708
|
+
existingDebtInfo: ${JSON.stringify(
|
|
709
|
+
existingDebtInfo
|
|
710
|
+
)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
711
|
+
|
|
712
|
+
// - Prices as seen by Vesu contracts, ideal for HF math
|
|
713
|
+
// Price 1 is ok as fallback bcz that would relatively price the
|
|
714
|
+
// collateral and debt as equal.
|
|
715
|
+
const collateralPrice =
|
|
716
|
+
collateralisation[0].usdValue > 0
|
|
717
|
+
? collateralisation[0].usdValue /
|
|
718
|
+
existingCollateralInfo.amount.toNumber()
|
|
719
|
+
: 1;
|
|
720
|
+
const debtPrice =
|
|
721
|
+
collateralisation[1].usdValue > 0
|
|
722
|
+
? collateralisation[1].usdValue /
|
|
723
|
+
existingDebtInfo.amount.toNumber()
|
|
724
|
+
: 1;
|
|
725
|
+
logger.debug(
|
|
726
|
+
`${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
// - Prices as seen by actual swap price
|
|
730
|
+
const dexPrice = await this.getLSTDexPrice();
|
|
731
|
+
|
|
732
|
+
// compute optimal amount of collateral and debt post addition/removal
|
|
733
|
+
// target hf = collateral * collateralPrice * ltv / debt * debtPrice
|
|
734
|
+
// assuming X to be the usd amount of debt borrowed or repaied (negative).
|
|
735
|
+
// target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv / (debt * debtPrice + X)
|
|
736
|
+
// => X * target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv - (debt * debtPrice * target hf)
|
|
737
|
+
// => X * (target hf - ltv)= ((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)
|
|
738
|
+
// => X = (((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)) / (target hf - ltv)
|
|
739
|
+
const addedCollateral = params.leg1DepositAmount.multipliedBy(
|
|
740
|
+
params.isDeposit ? 1 : -1
|
|
741
|
+
);
|
|
742
|
+
logger.verbose(
|
|
743
|
+
`${this.getTag()}::getVesuMultiplyCall addedCollateral: ${addedCollateral}`
|
|
744
|
+
);
|
|
745
|
+
const numeratorPart1 = existingCollateralInfo.amount
|
|
746
|
+
.plus(addedCollateral)
|
|
747
|
+
.multipliedBy(collateralPrice)
|
|
748
|
+
.multipliedBy(legLTV);
|
|
749
|
+
logger.verbose(
|
|
750
|
+
`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}`
|
|
751
|
+
);
|
|
752
|
+
const numeratorPart2 = existingDebtInfo.amount
|
|
753
|
+
.multipliedBy(debtPrice)
|
|
754
|
+
.multipliedBy(this.metadata.additionalInfo.targetHealthFactor);
|
|
755
|
+
logger.verbose(
|
|
756
|
+
`${this.getTag()}::getVesuMultiplyCall numeratorPart2: ${numeratorPart2}`
|
|
757
|
+
);
|
|
758
|
+
const denominatorPart =
|
|
759
|
+
this.metadata.additionalInfo.targetHealthFactor - legLTV / dexPrice;
|
|
760
|
+
logger.verbose(
|
|
761
|
+
`${this.getTag()}::getVesuMultiplyCall denominatorPart: ${denominatorPart}`
|
|
762
|
+
);
|
|
763
|
+
const x_debt_usd = numeratorPart1
|
|
764
|
+
.minus(numeratorPart2)
|
|
765
|
+
.dividedBy(denominatorPart);
|
|
766
|
+
logger.verbose(
|
|
767
|
+
`${this.getTag()}::getVesuMultiplyCall x_debt_usd: ${x_debt_usd}`
|
|
768
|
+
);
|
|
769
|
+
logger.debug(
|
|
770
|
+
`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}, numeratorPart2: ${numeratorPart2}, denominatorPart: ${denominatorPart}`
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
// both in underlying
|
|
774
|
+
let debtAmount = x_debt_usd.dividedBy(debtPrice);
|
|
775
|
+
const marginAmount = addedCollateral;
|
|
776
|
+
logger.verbose(
|
|
777
|
+
`${this.getTag()}::getVesuMultiplyCall debtAmount: ${debtAmount}, marginAmount: ${marginAmount}`
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
if (marginAmount.lt(0) && debtAmount.gt(0)) {
|
|
781
|
+
// if we want to withdraw, but debt can go high, its conflicting between
|
|
782
|
+
// increasing and reducing lever. and in this case, the HF will go high,
|
|
783
|
+
// which is ok.
|
|
784
|
+
debtAmount = Web3Number.fromWei(0, this.asset().decimals);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Cases of lever increase (within the scopr of this function)
|
|
788
|
+
// 1. debtAmount > 0 and marginAmount > 0
|
|
789
|
+
// 2. debtAmount > 0 and marginAmount < 0
|
|
790
|
+
|
|
791
|
+
// Cases of lever decrease
|
|
792
|
+
// 3. debtAmount < 0 and marginAmount > 0
|
|
793
|
+
// 4. debtAmount < 0 and marginAmount < 0
|
|
794
|
+
return this.getModifyLeverCall({
|
|
795
|
+
marginAmount,
|
|
796
|
+
debtAmount,
|
|
797
|
+
lstDexPriceInUnderlying: dexPrice,
|
|
798
|
+
isIncrease: debtAmount.greaterThan(0),
|
|
799
|
+
maxEkuboPriceImpact
|
|
800
|
+
});
|
|
635
801
|
}
|
|
636
802
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
803
|
+
async getMaxBorrowableAmount(
|
|
804
|
+
params: { isAPYComputation: boolean } = { isAPYComputation: false }
|
|
805
|
+
) {
|
|
806
|
+
const vesuAdapters = this.getVesuAdapters();
|
|
807
|
+
let netMaxBorrowableAmount = Web3Number.fromWei(
|
|
808
|
+
"0",
|
|
809
|
+
this.getLSTUnderlyingTokenInfo().decimals
|
|
810
|
+
);
|
|
811
|
+
const maxBorrowables: {
|
|
812
|
+
amount: Web3Number;
|
|
813
|
+
dexSwappableAmount: Web3Number;
|
|
814
|
+
maxBorrowableAmount: Web3Number;
|
|
815
|
+
borrowableAsset: TokenInfo;
|
|
816
|
+
}[] = [];
|
|
817
|
+
for (const vesuAdapter of vesuAdapters) {
|
|
818
|
+
maxBorrowables.push(
|
|
819
|
+
await this.getMaxBorrowableAmountByVesuAdapter(
|
|
820
|
+
vesuAdapter,
|
|
821
|
+
params.isAPYComputation
|
|
822
|
+
)
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
maxBorrowables.sort(
|
|
826
|
+
(a, b) => b.amount.toNumber() - a.amount.toNumber()
|
|
827
|
+
);
|
|
828
|
+
netMaxBorrowableAmount = maxBorrowables.reduce(
|
|
829
|
+
(acc, curr) => acc.plus(curr.amount),
|
|
830
|
+
Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals)
|
|
831
|
+
);
|
|
832
|
+
return { netMaxBorrowableAmount, maxBorrowables };
|
|
833
|
+
}
|
|
646
834
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
835
|
+
// recursively, using binary search computes max swappable.
|
|
836
|
+
// @dev assumes 1 token of from == 1 token of to
|
|
837
|
+
async getMaxSwappableWithMaxSlippage(
|
|
838
|
+
fromToken: TokenInfo,
|
|
839
|
+
toToken: TokenInfo,
|
|
840
|
+
maxSlippage: number,
|
|
841
|
+
maxAmount: Web3Number
|
|
842
|
+
) {
|
|
843
|
+
const output = await findMaxInputWithSlippage({
|
|
844
|
+
apiGetOutput: async (inputAmount: number): Promise<number> => {
|
|
845
|
+
const ekuboQuoter = new EkuboQuoter(this.config);
|
|
846
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // artificial delay, to avoid rate limit
|
|
847
|
+
const quote = await ekuboQuoter.getQuote(
|
|
848
|
+
fromToken.address.address,
|
|
849
|
+
toToken.address.address,
|
|
850
|
+
new Web3Number(inputAmount.toFixed(9), fromToken.decimals)
|
|
851
|
+
);
|
|
852
|
+
return Web3Number.fromWei(
|
|
853
|
+
quote.total_calculated.toString(),
|
|
854
|
+
toToken.decimals
|
|
855
|
+
).toNumber();
|
|
856
|
+
},
|
|
857
|
+
maxInput: maxAmount.toNumber(),
|
|
858
|
+
maxSlippagePercent: maxSlippage,
|
|
859
|
+
tolerance: 0.001,
|
|
860
|
+
referenceRate: 1
|
|
861
|
+
});
|
|
862
|
+
return new Web3Number(output.optimalInput, fromToken.decimals);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async getMaxBorrowableAmountByVesuAdapter(
|
|
866
|
+
vesuAdapter: VesuAdapter,
|
|
867
|
+
isAPYComputation: boolean
|
|
868
|
+
) {
|
|
869
|
+
const lstAPY = await this.getLSTAPR(
|
|
870
|
+
this.getLSTUnderlyingTokenInfo().address
|
|
871
|
+
);
|
|
872
|
+
const maxInterestRate = lstAPY * 0.8;
|
|
873
|
+
const maxBorrowableAmount =
|
|
874
|
+
await vesuAdapter.getMaxBorrowableByInterestRate(
|
|
875
|
+
this.config,
|
|
876
|
+
vesuAdapter.config.debt,
|
|
877
|
+
maxInterestRate
|
|
878
|
+
);
|
|
879
|
+
const debtCap = await vesuAdapter.getDebtCap(this.config);
|
|
880
|
+
|
|
881
|
+
const maxBorrowable = maxBorrowableAmount
|
|
882
|
+
.minimum(debtCap)
|
|
883
|
+
.multipliedBy(0.999);
|
|
884
|
+
// Dont compute precise max swappable for APY computation
|
|
885
|
+
if (
|
|
886
|
+
vesuAdapter.config.debt.address.eq(
|
|
887
|
+
this.getLSTUnderlyingTokenInfo().address
|
|
888
|
+
) ||
|
|
889
|
+
isAPYComputation
|
|
890
|
+
) {
|
|
891
|
+
return {
|
|
892
|
+
amount: maxBorrowable,
|
|
893
|
+
dexSwappableAmount: maxBorrowable,
|
|
894
|
+
maxBorrowableAmount: maxBorrowable,
|
|
895
|
+
borrowableAsset: vesuAdapter.config.debt
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
// Want < 0.02% slippage
|
|
899
|
+
try {
|
|
900
|
+
const maxSwappable = await this.getMaxSwappableWithMaxSlippage(
|
|
901
|
+
vesuAdapter.config.debt,
|
|
902
|
+
this.getLSTUnderlyingTokenInfo(),
|
|
903
|
+
0.0002,
|
|
904
|
+
maxBorrowable
|
|
905
|
+
);
|
|
906
|
+
return {
|
|
907
|
+
amount: maxBorrowable.minimum(maxSwappable),
|
|
908
|
+
dexSwappableAmount: maxSwappable,
|
|
909
|
+
maxBorrowableAmount: maxBorrowable,
|
|
910
|
+
borrowableAsset: vesuAdapter.config.debt
|
|
911
|
+
};
|
|
912
|
+
} catch (error) {
|
|
913
|
+
logger.warn(
|
|
914
|
+
`${this.getTag()}: Failed to get max swappable: ${error}`
|
|
915
|
+
);
|
|
916
|
+
const maxSwappable = Web3Number.fromWei(
|
|
917
|
+
"0",
|
|
918
|
+
vesuAdapter.config.debt.decimals
|
|
919
|
+
);
|
|
920
|
+
return {
|
|
921
|
+
amount: maxBorrowable.minimum(maxSwappable),
|
|
922
|
+
dexSwappableAmount: maxSwappable,
|
|
923
|
+
maxBorrowableAmount: maxBorrowable,
|
|
924
|
+
borrowableAsset: vesuAdapter.config.debt
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// todo how much to unwind to get back healthy APY zone again
|
|
930
|
+
// if net APY < LST APR + 0.5%, we need to unwind to get back to LST APR + 1% atleast or 0 vesu position
|
|
931
|
+
// For xSTRK, simply deposit in Vesu if looping is not viable
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Gets LST APR for the strategy's underlying asset from Endur API
|
|
935
|
+
* @returns Promise<number> The LST APR (not divided by 1e18)
|
|
936
|
+
*/
|
|
937
|
+
async getLSTAPR(_address: ContractAddr): Promise<number> {
|
|
938
|
+
try {
|
|
939
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter();
|
|
940
|
+
const apr = await LSTAPRService.getLSTAPR(
|
|
941
|
+
vesuAdapter1.config.debt.address
|
|
942
|
+
);
|
|
943
|
+
if (!apr) {
|
|
944
|
+
throw new Error("Failed to get LST APR");
|
|
945
|
+
}
|
|
946
|
+
return apr;
|
|
947
|
+
} catch (error) {
|
|
948
|
+
logger.warn(`${this.getTag()}: Failed to get LST APR: ${error}`);
|
|
949
|
+
return 0;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// todo undo this
|
|
954
|
+
async netAPY(): Promise<{
|
|
955
|
+
net: number;
|
|
956
|
+
splits: { apy: number; id: string }[];
|
|
957
|
+
}> {
|
|
958
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
959
|
+
const maxNewDeposits = await this.maxNewDeposits({
|
|
960
|
+
isAPYComputation: true
|
|
961
|
+
});
|
|
962
|
+
const lstAPY = await this.getLSTAPR(
|
|
963
|
+
this.getLSTUnderlyingTokenInfo().address
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
// if unused balance is > max servicable from loan, we are limited by the max borrowing we can do
|
|
967
|
+
// we also allow accepting little higher deposits (1.5x) to have room for future looping when theres more liquidity or debt cap rises
|
|
968
|
+
if (maxNewDeposits * 1.5 < unusedBalance.amount.toNumber()) {
|
|
969
|
+
// we have excess, just use real APY
|
|
970
|
+
logger.verbose(
|
|
971
|
+
`${this.getTag()}::netAPY: unused balance is > max servicable from loan, lstAPY: ${lstAPY}`
|
|
972
|
+
);
|
|
973
|
+
const output = await super.netAPY();
|
|
974
|
+
output.splits.push({ apy: lstAPY, id: "lst_apy" });
|
|
975
|
+
return output;
|
|
976
|
+
} else {
|
|
977
|
+
// we have little bit room to accept more deposits, we use theoretical max APY
|
|
978
|
+
logger.verbose(
|
|
979
|
+
`${this.getTag()}::netAPY: we can take more deposits, use theoretical max APY`
|
|
980
|
+
);
|
|
981
|
+
const { positions, baseAPYs, rewardAPYs } =
|
|
982
|
+
await this.getVesuAPYs();
|
|
983
|
+
const weights = positions.map(
|
|
984
|
+
(p: VaultPosition, index: number) => p.usdValue * (index % 2 == 0 ? 1 : -1)
|
|
985
|
+
);
|
|
986
|
+
const aum = weights.reduce((acc: number, curr: number) => acc + curr, 0);
|
|
987
|
+
const output = await this.returnNetAPY(
|
|
988
|
+
baseAPYs,
|
|
989
|
+
rewardAPYs,
|
|
990
|
+
weights,
|
|
991
|
+
new Web3Number(
|
|
992
|
+
aum.toFixed(9),
|
|
993
|
+
this.getLSTUnderlyingTokenInfo().decimals
|
|
994
|
+
)
|
|
995
|
+
);
|
|
996
|
+
output.splits.push({ apy: lstAPY, id: "lst_apy" });
|
|
997
|
+
return output;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
async maxNewDeposits(
|
|
1002
|
+
params: { isAPYComputation: boolean } = { isAPYComputation: false }
|
|
1003
|
+
) {
|
|
1004
|
+
const maxBorrowableAmounts = await this.getMaxBorrowableAmount(params);
|
|
1005
|
+
|
|
1006
|
+
let ltv: number | undefined = undefined;
|
|
1007
|
+
for (let adapter of this.getVesuAdapters()) {
|
|
1008
|
+
const maxBorrowableAmount =
|
|
1009
|
+
maxBorrowableAmounts.maxBorrowables.find((b) =>
|
|
1010
|
+
b.borrowableAsset.address.eq(adapter.config.debt.address)
|
|
1011
|
+
)?.amount;
|
|
1012
|
+
if (!maxBorrowableAmount) {
|
|
1013
|
+
throw new Error(
|
|
1014
|
+
`Max borrowable amount not found for adapter: ${adapter.config.debt.symbol}`
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
const maxLTV = await adapter.getLTVConfig(this.config);
|
|
1018
|
+
if (!ltv) {
|
|
1019
|
+
ltv = maxLTV;
|
|
1020
|
+
} else if (ltv != maxLTV) {
|
|
1021
|
+
throw new Error(
|
|
1022
|
+
`LTV mismatch for adapter: ${adapter.config.debt.symbol}`
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (!ltv) {
|
|
1027
|
+
throw new Error("LTV not found");
|
|
1028
|
+
}
|
|
1029
|
+
// for simplicity, we assume 1 underlying = 1 LST
|
|
1030
|
+
const numerator =
|
|
1031
|
+
(this.metadata.additionalInfo.targetHealthFactor *
|
|
1032
|
+
maxBorrowableAmounts.netMaxBorrowableAmount.toNumber()) /
|
|
1033
|
+
ltv;
|
|
1034
|
+
return (
|
|
1035
|
+
numerator - maxBorrowableAmounts.netMaxBorrowableAmount.toNumber()
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
655
1038
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1039
|
+
/**
|
|
1040
|
+
* Gets Vesu APYs for all positions
|
|
1041
|
+
* @returns Object with positions, baseAPYs, and rewardAPYs
|
|
1042
|
+
*/
|
|
1043
|
+
async getVesuAPYs(): Promise<{
|
|
1044
|
+
positions: VaultPosition[];
|
|
1045
|
+
baseAPYs: number[];
|
|
1046
|
+
rewardAPYs: number[];
|
|
1047
|
+
}> {
|
|
1048
|
+
const positions = await this.getVesuPositions();
|
|
1049
|
+
const baseAPYs: number[] = [];
|
|
1050
|
+
const rewardAPYs: number[] = [];
|
|
1051
|
+
|
|
1052
|
+
// This is a stub implementation - needs to be properly implemented
|
|
1053
|
+
// to fetch actual APYs from Vesu adapters
|
|
1054
|
+
for (const position of positions) {
|
|
1055
|
+
baseAPYs.push(0);
|
|
1056
|
+
rewardAPYs.push(0);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
return { positions, baseAPYs, rewardAPYs };
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Returns net APY based on base APYs, reward APYs, weights, and AUM
|
|
1064
|
+
* @param baseAPYs Array of base APY values
|
|
1065
|
+
* @param rewardAPYs Array of reward APY values
|
|
1066
|
+
* @param weights Array of weights for each position
|
|
1067
|
+
* @param aum Total assets under management
|
|
1068
|
+
* @returns Object with net APY and splits
|
|
1069
|
+
*/
|
|
1070
|
+
protected async returnNetAPY(
|
|
1071
|
+
baseAPYs: number[],
|
|
1072
|
+
rewardAPYs: number[],
|
|
1073
|
+
weights: number[],
|
|
1074
|
+
aum: Web3Number
|
|
1075
|
+
): Promise<{
|
|
1076
|
+
net: number;
|
|
1077
|
+
splits: { apy: number; id: string }[];
|
|
1078
|
+
}> {
|
|
1079
|
+
// Calculate weighted average APY
|
|
1080
|
+
let totalWeightedAPY = 0;
|
|
1081
|
+
let totalWeight = 0;
|
|
1082
|
+
|
|
1083
|
+
for (let i = 0; i < baseAPYs.length; i++) {
|
|
1084
|
+
const weight = Math.abs(weights[i]);
|
|
1085
|
+
const totalAPY = baseAPYs[i] + rewardAPYs[i];
|
|
1086
|
+
totalWeightedAPY += totalAPY * weight;
|
|
1087
|
+
totalWeight += weight;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const net = totalWeight > 0 ? totalWeightedAPY / totalWeight : 0;
|
|
1091
|
+
|
|
1092
|
+
return {
|
|
1093
|
+
net,
|
|
1094
|
+
splits: []
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// todo revisit cases where 0th adapters is used
|
|
1099
|
+
protected async getUnusedBalanceAPY() {
|
|
1100
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
1101
|
+
const vesuAdapter = this.getVesuSameTokenAdapter();
|
|
1102
|
+
const underlying = vesuAdapter.config.debt;
|
|
1103
|
+
const lstAPY = await this.getLSTAPR(underlying.address);
|
|
1104
|
+
return {
|
|
1105
|
+
apy: lstAPY,
|
|
1106
|
+
weight: unusedBalance.usdValue
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
async getLSTExchangeRate() {
|
|
1111
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter();
|
|
1112
|
+
const lstTokenInfo = vesuAdapter1.config.collateral;
|
|
1113
|
+
const lstABI = new Contract({
|
|
1114
|
+
abi: ERC4626Abi,
|
|
1115
|
+
address: lstTokenInfo.address.address,
|
|
1116
|
+
providerOrAccount: this.config.provider
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
const price: any = await lstABI.call("convert_to_assets", [
|
|
1120
|
+
uint256.bnToUint256(
|
|
1121
|
+
new Web3Number(1, lstTokenInfo.decimals).toWei()
|
|
1122
|
+
)
|
|
1123
|
+
]);
|
|
1124
|
+
const exchangeRate =
|
|
1125
|
+
Number(uint256.uint256ToBN(price).toString()) /
|
|
1126
|
+
Math.pow(10, lstTokenInfo.decimals);
|
|
1127
|
+
logger.verbose(`${this.getTag()}:: LST Exchange Rate: ${exchangeRate}`);
|
|
1128
|
+
return exchangeRate;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
*
|
|
1133
|
+
* @param params marginAmount is in LST, debtAmount is in underlying
|
|
1134
|
+
*/
|
|
1135
|
+
async getModifyLeverCall(params: {
|
|
1136
|
+
marginAmount: Web3Number; // >0 during deposit
|
|
1137
|
+
debtAmount: Web3Number;
|
|
1138
|
+
lstDexPriceInUnderlying: number;
|
|
1139
|
+
isIncrease: boolean;
|
|
1140
|
+
maxEkuboPriceImpact: number;
|
|
1141
|
+
}): Promise<Call[]> {
|
|
1142
|
+
logger.verbose(
|
|
1143
|
+
`${this.getTag()}::getModifyLeverCall marginAmount: ${
|
|
1144
|
+
params.marginAmount
|
|
1145
|
+
}, debtAmount: ${params.debtAmount}, lstDexPriceInUnderlying: ${
|
|
1146
|
+
params.lstDexPriceInUnderlying
|
|
1147
|
+
}, isIncrease: ${params.isIncrease}`
|
|
1148
|
+
);
|
|
1149
|
+
assert(
|
|
1150
|
+
!params.marginAmount.isZero() || !params.debtAmount.isZero(),
|
|
1151
|
+
"Deposit/debt must be non-0"
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter();
|
|
1155
|
+
const lstTokenInfo = this.asset();
|
|
1156
|
+
const lstUnderlyingTokenInfo = vesuAdapter1.config.debt;
|
|
1157
|
+
|
|
1158
|
+
// todo make it more general
|
|
1159
|
+
// 500k STRK (~75k$) or 0.5 BTC (~60k$)
|
|
1160
|
+
const maxAmounts = lstTokenInfo.symbol == "xSTRK" ? 500000 : 0.5;
|
|
1161
|
+
if (params.marginAmount.greaterThan(maxAmounts)) {
|
|
1162
|
+
throw new Error(
|
|
1163
|
+
`Margin amount is greater than max amount: ${params.marginAmount.toNumber()} > ${maxAmounts}`
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const proofsIDs: string[] = [];
|
|
1168
|
+
const manageCalls: ManageCall[] = [];
|
|
1169
|
+
|
|
1170
|
+
// approve token
|
|
1171
|
+
if (params.marginAmount.greaterThan(0)) {
|
|
1172
|
+
const STEP1_ID = LST_MULTIPLIER_MANAGE_IDS.MULTIPLE_APPROVE;
|
|
1173
|
+
const manage1Info = this.getProofs<ApproveCallParams>(STEP1_ID);
|
|
1174
|
+
const depositAmount = params.marginAmount;
|
|
1175
|
+
const manageCall1 = manage1Info.callConstructor({
|
|
1176
|
+
amount: depositAmount
|
|
1177
|
+
});
|
|
1178
|
+
proofsIDs.push(STEP1_ID);
|
|
1179
|
+
manageCalls.push(manageCall1);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
const lstDexPriceInUnderlying = params.lstDexPriceInUnderlying;
|
|
1183
|
+
const lstTrueExchangeRate = await this.getLSTExchangeRate();
|
|
1184
|
+
const ekuboQuoter = new EkuboQuoter(this.config);
|
|
1185
|
+
|
|
1186
|
+
// compute quotes for lever swap
|
|
1187
|
+
const MAX_SLIPPAGE = 0.002;
|
|
1188
|
+
// when increasing, debt is swapped to collateral (LST)
|
|
1189
|
+
// when decreasing, collateral is swapped to debt (underlying)
|
|
1190
|
+
// but both cases, we denominate amount in underlying. negative for decrease (exact amount out)
|
|
1191
|
+
const fromToken = params.isIncrease
|
|
1192
|
+
? lstUnderlyingTokenInfo
|
|
1193
|
+
: lstTokenInfo;
|
|
1194
|
+
const toToken = params.isIncrease
|
|
1195
|
+
? lstTokenInfo
|
|
1196
|
+
: lstUnderlyingTokenInfo;
|
|
1197
|
+
const leverSwapQuote = await ekuboQuoter.getQuote(
|
|
1198
|
+
fromToken.address.address,
|
|
1199
|
+
toToken.address.address,
|
|
1200
|
+
params.debtAmount // negative for exact amount out
|
|
1201
|
+
);
|
|
1202
|
+
logger.verbose(
|
|
1203
|
+
`${this.getTag()}::getModifyLeverCall leverSwapQuote: ${JSON.stringify(
|
|
1204
|
+
leverSwapQuote
|
|
1205
|
+
)}`
|
|
1206
|
+
);
|
|
1207
|
+
// Ekubo's price impact can randomly show high numbers sometimes.
|
|
1208
|
+
assert(
|
|
1209
|
+
leverSwapQuote.price_impact <= params.maxEkuboPriceImpact,
|
|
1210
|
+
"getIncreaseLeverCall: Price impact is too high [Debt swap]"
|
|
1211
|
+
);
|
|
1212
|
+
const leverSwap = ekuboQuoter.getVesuMultiplyQuote(
|
|
1213
|
+
leverSwapQuote,
|
|
1214
|
+
fromToken,
|
|
1215
|
+
toToken
|
|
1216
|
+
);
|
|
1217
|
+
logger.verbose(
|
|
1218
|
+
`${this.getTag()}::getModifyLeverCall leverSwap: ${JSON.stringify(
|
|
1219
|
+
leverSwap
|
|
1220
|
+
)}`
|
|
1221
|
+
);
|
|
1222
|
+
|
|
1223
|
+
// todo double check this logic
|
|
1224
|
+
// is Deposit
|
|
1225
|
+
let minLSTReceived = params.debtAmount
|
|
1226
|
+
.dividedBy(lstDexPriceInUnderlying)
|
|
1227
|
+
.multipliedBy(1 - MAX_SLIPPAGE); // used for increase
|
|
1228
|
+
const minLSTReceivedAsPerTruePrice =
|
|
1229
|
+
params.debtAmount.dividedBy(lstTrueExchangeRate); // execution output to be <= True LST price
|
|
1230
|
+
// if (minLSTReceived < minLSTReceivedAsPerTruePrice) {
|
|
1231
|
+
// minLSTReceived = minLSTReceivedAsPerTruePrice; // the execution shouldn't be bad than True price logi
|
|
1232
|
+
// }
|
|
1233
|
+
minLSTReceived = minLSTReceivedAsPerTruePrice; // in any case, we are ok with this, bcz the BTC LST spread shouldnt be high
|
|
1234
|
+
logger.verbose(
|
|
1235
|
+
`${this.getTag()}::getModifyLeverCall minLSTReceivedAsPerTruePrice: ${minLSTReceivedAsPerTruePrice}, minLSTReceived: ${minLSTReceived}`
|
|
1236
|
+
);
|
|
1237
|
+
|
|
1238
|
+
// is withdraw
|
|
1239
|
+
let maxUsedCollateral = params.debtAmount
|
|
1240
|
+
.abs()
|
|
1241
|
+
.dividedBy(lstDexPriceInUnderlying)
|
|
1242
|
+
.multipliedBy(1 + MAX_SLIPPAGE); // +ve for exact amount out, used for decrease
|
|
1243
|
+
const maxUsedCollateralInLST = params.debtAmount
|
|
1244
|
+
.abs()
|
|
1245
|
+
.dividedBy(lstTrueExchangeRate)
|
|
1246
|
+
.multipliedBy(1.005); // 0.5% slippage, worst case based on true price
|
|
1247
|
+
logger.verbose(
|
|
1248
|
+
`${this.getTag()}::getModifyLeverCall maxUsedCollateralInLST: ${maxUsedCollateralInLST}, maxUsedCollateral: ${maxUsedCollateral}`
|
|
1249
|
+
);
|
|
1250
|
+
// if (maxUsedCollateralInLST > maxUsedCollateral) {
|
|
1251
|
+
// maxUsedCollateral = maxUsedCollateralInLST;
|
|
1252
|
+
// }
|
|
1253
|
+
maxUsedCollateral = maxUsedCollateralInLST; // in any case, we are ok with this, bcz the BTC LST spread shouldnt be high
|
|
1254
|
+
|
|
1255
|
+
const STEP2_ID = LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_ON;
|
|
1256
|
+
const manage2Info =
|
|
1257
|
+
this.getProofs<VesuModifyDelegationCallParams>(STEP2_ID);
|
|
1258
|
+
const manageCall2 = manage2Info.callConstructor({
|
|
1259
|
+
delegation: true
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
// deposit and borrow or repay and withdraw
|
|
1263
|
+
const STEP3_ID = getVesuLegId(
|
|
1264
|
+
LST_MULTIPLIER_MANAGE_IDS.MULTIPLY_VESU,
|
|
1265
|
+
vesuAdapter1.config.debt.symbol
|
|
1266
|
+
);
|
|
1267
|
+
const manage3Info = this.getProofs<VesuMultiplyCallParams>(STEP3_ID);
|
|
1268
|
+
const multiplyParams: VesuMultiplyCallParams = params.isIncrease
|
|
1269
|
+
? {
|
|
1270
|
+
isIncrease: true,
|
|
1271
|
+
increaseParams: {
|
|
1272
|
+
add_margin: params.marginAmount,
|
|
1273
|
+
margin_swap: [],
|
|
1274
|
+
margin_swap_limit_amount: Web3Number.fromWei(
|
|
1275
|
+
0,
|
|
1276
|
+
this.asset().decimals
|
|
1277
|
+
),
|
|
1278
|
+
lever_swap: leverSwap,
|
|
1279
|
+
lever_swap_limit_amount: minLSTReceived
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
: {
|
|
1283
|
+
isIncrease: false,
|
|
1284
|
+
decreaseParams: {
|
|
1285
|
+
sub_margin: params.marginAmount.multipliedBy(-1),
|
|
1286
|
+
lever_swap: leverSwap,
|
|
1287
|
+
lever_swap_limit_amount: maxUsedCollateral,
|
|
1288
|
+
// only required for close position
|
|
1289
|
+
lever_swap_weights: [],
|
|
1290
|
+
// no need to swap collateral to anything, and any residuals return our contract anyways.
|
|
1291
|
+
withdraw_swap: [],
|
|
1292
|
+
withdraw_swap_limit_amount: Web3Number.fromWei(
|
|
1293
|
+
0,
|
|
1294
|
+
this.asset().decimals
|
|
1295
|
+
),
|
|
1296
|
+
withdraw_swap_weights: [],
|
|
1297
|
+
close_position: false
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
const manageCall3 = manage3Info.callConstructor(multiplyParams);
|
|
1301
|
+
|
|
1302
|
+
// switch delegation off
|
|
1303
|
+
const STEP4_ID = LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_OFF;
|
|
1304
|
+
const manage4Info =
|
|
1305
|
+
this.getProofs<VesuModifyDelegationCallParams>(STEP4_ID);
|
|
1306
|
+
const manageCall4 = manage4Info.callConstructor({
|
|
1307
|
+
delegation: false
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
proofsIDs.push(STEP2_ID, STEP3_ID, STEP4_ID);
|
|
1311
|
+
manageCalls.push(manageCall2, manageCall3, manageCall4);
|
|
1312
|
+
|
|
1313
|
+
return [this.getManageCall(proofsIDs, manageCalls)];
|
|
1314
|
+
}
|
|
683
1315
|
}
|
|
684
1316
|
|
|
685
1317
|
export default function VaultDescription(
|
|
686
|
-
|
|
687
|
-
|
|
1318
|
+
lstSymbol: string,
|
|
1319
|
+
underlyingSymbol: string
|
|
688
1320
|
) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1321
|
+
const containerStyle = {
|
|
1322
|
+
maxWidth: "800px",
|
|
1323
|
+
margin: "0 auto",
|
|
1324
|
+
backgroundColor: "#111",
|
|
1325
|
+
color: "#eee",
|
|
1326
|
+
fontFamily: "Arial, sans-serif",
|
|
1327
|
+
borderRadius: "12px"
|
|
1328
|
+
};
|
|
1329
|
+
|
|
1330
|
+
return (
|
|
1331
|
+
<div style={containerStyle}>
|
|
1332
|
+
<h1 style={{ fontSize: "18px", marginBottom: "10px" }}>
|
|
1333
|
+
Liquidation risk managed leverged {lstSymbol} Vault
|
|
1334
|
+
</h1>
|
|
1335
|
+
<p
|
|
1336
|
+
style={{
|
|
1337
|
+
fontSize: "14px",
|
|
1338
|
+
lineHeight: "1.5",
|
|
1339
|
+
marginBottom: "16px"
|
|
1340
|
+
}}
|
|
1341
|
+
>
|
|
1342
|
+
This Levered Endur {lstSymbol} vault is a tokenized leveraged
|
|
1343
|
+
Vault, auto-compounding strategy that takes upto 5x leverage on{" "}
|
|
1344
|
+
{lstSymbol} by borrow {underlyingSymbol}. Borrowed amount is
|
|
1345
|
+
swapped to {lstSymbol} to create leverage. Depositors receive
|
|
1346
|
+
vault shares that represent a proportional claim on the
|
|
1347
|
+
underlying assets and accrued yield.
|
|
1348
|
+
</p>
|
|
1349
|
+
|
|
1350
|
+
<p
|
|
1351
|
+
style={{
|
|
1352
|
+
fontSize: "14px",
|
|
1353
|
+
lineHeight: "1.5",
|
|
1354
|
+
marginBottom: "16px"
|
|
1355
|
+
}}
|
|
1356
|
+
>
|
|
1357
|
+
This vault uses Vesu for lending and borrowing. The oracle used
|
|
1358
|
+
by this pool is a{" "}
|
|
1359
|
+
{highlightTextWithLinks("conversion rate oracle", [
|
|
1360
|
+
{
|
|
1361
|
+
highlight: "conversion rate oracle",
|
|
1362
|
+
link: "https://docs.pragma.build/starknet/development#conversion-rate"
|
|
1363
|
+
}
|
|
1364
|
+
])}{" "}
|
|
1365
|
+
which is resilient to liquidity issues and price volatility,
|
|
1366
|
+
hence reducing the risk of liquidation. However, overtime, if
|
|
1367
|
+
left un-monitored, debt can increase enough to trigger a
|
|
1368
|
+
liquidation. But no worries, our continuous monitoring systems
|
|
1369
|
+
look for situations with reduced health factor and balance
|
|
1370
|
+
collateral/debt to bring it back to safe levels. With Troves,
|
|
1371
|
+
you can have a peaceful sleep.
|
|
1372
|
+
</p>
|
|
1373
|
+
|
|
1374
|
+
<div
|
|
1375
|
+
style={{
|
|
1376
|
+
backgroundColor: "#222",
|
|
1377
|
+
padding: "10px",
|
|
1378
|
+
borderRadius: "8px",
|
|
1379
|
+
marginBottom: "20px",
|
|
1380
|
+
border: "1px solid #444"
|
|
1381
|
+
}}
|
|
1382
|
+
>
|
|
1383
|
+
<p style={{ fontSize: "13px", color: "#ccc" }}>
|
|
1384
|
+
<strong>Withdrawals:</strong> Requests can take up to{" "}
|
|
1385
|
+
<strong>1-2 hours</strong> to process as the vault unwinds
|
|
1386
|
+
and settles routing.
|
|
1387
|
+
</p>
|
|
1388
|
+
</div>
|
|
1389
|
+
<div
|
|
1390
|
+
style={{
|
|
1391
|
+
backgroundColor: "#222",
|
|
1392
|
+
padding: "10px",
|
|
1393
|
+
borderRadius: "8px",
|
|
1394
|
+
marginBottom: "20px",
|
|
1395
|
+
border: "1px solid #444"
|
|
1396
|
+
}}
|
|
1397
|
+
>
|
|
1398
|
+
<p style={{ fontSize: "13px", color: "#ccc" }}>
|
|
1399
|
+
<strong>Debt limits:</strong> Pools on Vesu have debt caps
|
|
1400
|
+
that are gradually increased over time. Until caps are
|
|
1401
|
+
raised, deposited LSTs remain in the vault, generating a
|
|
1402
|
+
shared net return for all depositors. There is no additional
|
|
1403
|
+
fee taken by Troves on LST APY, its only on added gain.
|
|
1404
|
+
</p>
|
|
1405
|
+
</div>
|
|
1406
|
+
{/* <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
|
|
723
1407
|
<p style={{ fontSize: "13px", color: "#ccc" }}>
|
|
724
1408
|
<strong>APY assumptions:</strong> APY shown is the max possible value given current LST and borrowing rates. True APY will be subject to the actual leverage, based on above point. More insights on exact APY will be added soon.
|
|
725
1409
|
</p>
|
|
726
1410
|
</div> */}
|
|
727
|
-
|
|
728
|
-
|
|
1411
|
+
</div>
|
|
1412
|
+
);
|
|
729
1413
|
}
|
|
730
1414
|
|
|
731
|
-
|
|
732
1415
|
function getDescription(tokenSymbol: string, underlyingSymbol: string) {
|
|
733
|
-
|
|
1416
|
+
return VaultDescription(tokenSymbol, underlyingSymbol);
|
|
734
1417
|
}
|
|
735
1418
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
AVNU_MULTIPLY_SWAP_WITHDRAW = 'avnu_mul_swap_withdr',
|
|
1419
|
+
enum LST_MULTIPLIER_MANAGE_IDS {
|
|
1420
|
+
MULTIPLE_APPROVE = "multiple_approve",
|
|
1421
|
+
MULTIPLY_VESU = "multiply_vesu",
|
|
1422
|
+
SWITCH_DELEGATION_ON = "switch_delegation_on",
|
|
1423
|
+
SWITCH_DELEGATION_OFF = "switch_delegation_off",
|
|
1424
|
+
AVNU_MULTIPLY_APPROVE_DEPOSIT = "avnu_mul_approve_dep",
|
|
1425
|
+
AVNU_MULTIPLY_SWAP_DEPOSIT = "avnu_mul_swap_dep",
|
|
1426
|
+
AVNU_MULTIPLY_APPROVE_WITHDRAW = "avnu_mul_approve_withdr",
|
|
1427
|
+
AVNU_MULTIPLY_SWAP_WITHDRAW = "avnu_mul_swap_withdr"
|
|
746
1428
|
}
|
|
747
1429
|
|
|
748
|
-
function getAvnuManageIDs(
|
|
749
|
-
|
|
1430
|
+
function getAvnuManageIDs(
|
|
1431
|
+
baseID: LST_MULTIPLIER_MANAGE_IDS,
|
|
1432
|
+
debtTokenSymbol: string
|
|
1433
|
+
) {
|
|
1434
|
+
return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
|
|
750
1435
|
}
|
|
751
1436
|
|
|
752
1437
|
function getVesuLegId(baseID: string, debtTokenSymbol: string) {
|
|
753
|
-
|
|
1438
|
+
return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
|
|
754
1439
|
}
|
|
755
1440
|
|
|
756
1441
|
function getLooperSettings(
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1442
|
+
lstSymbol: string,
|
|
1443
|
+
underlyingSymbol: string,
|
|
1444
|
+
vaultSettings: HyperLSTStrategySettings,
|
|
1445
|
+
pool1: ContractAddr
|
|
761
1446
|
) {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1447
|
+
vaultSettings.leafAdapters = [];
|
|
1448
|
+
|
|
1449
|
+
const lstToken = Global.getDefaultTokens().find(
|
|
1450
|
+
(token) => token.symbol === lstSymbol
|
|
1451
|
+
)!;
|
|
1452
|
+
const underlyingToken = Global.getDefaultTokens().find(
|
|
1453
|
+
(token) => token.symbol === underlyingSymbol
|
|
1454
|
+
)!;
|
|
1455
|
+
|
|
1456
|
+
const vesuAdapterLST = new VesuAdapter({
|
|
1457
|
+
poolId: pool1,
|
|
1458
|
+
collateral: lstToken,
|
|
1459
|
+
debt: underlyingToken,
|
|
1460
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
1461
|
+
id: getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, underlyingToken.symbol)
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
const commonAdapter = new CommonAdapter({
|
|
1465
|
+
manager: vaultSettings.manager,
|
|
1466
|
+
asset: lstToken.address,
|
|
1467
|
+
id: "",
|
|
1468
|
+
vaultAddress: vaultSettings.vaultAddress,
|
|
1469
|
+
vaultAllocator: vaultSettings.vaultAllocator
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
// Useful for returning adapter class objects that can compute
|
|
1473
|
+
// certain things for us (e.g. positions, hfs)
|
|
1474
|
+
vaultSettings.adapters.push(
|
|
1475
|
+
...[
|
|
1476
|
+
{
|
|
1477
|
+
id: getVesuLegId(
|
|
1478
|
+
UNIVERSAL_MANAGE_IDS.VESU_LEG1,
|
|
1479
|
+
underlyingToken.symbol
|
|
1480
|
+
),
|
|
1481
|
+
adapter: vesuAdapterLST
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
id: UNIVERSAL_ADAPTERS.COMMON,
|
|
1485
|
+
adapter: commonAdapter
|
|
1486
|
+
}
|
|
1487
|
+
]
|
|
1488
|
+
);
|
|
1489
|
+
|
|
1490
|
+
// avnu multiply
|
|
1491
|
+
const { isV2, addr: poolAddr } = getVesuSingletonAddress(pool1);
|
|
1492
|
+
// vesu multiply looping
|
|
1493
|
+
const VESU_MULTIPLY = isV2
|
|
1494
|
+
? vesuAdapterLST.VESU_MULTIPLY
|
|
1495
|
+
: vesuAdapterLST.VESU_MULTIPLY_V1;
|
|
1496
|
+
vaultSettings.leafAdapters.push(
|
|
1497
|
+
commonAdapter
|
|
1498
|
+
.getApproveAdapter(
|
|
1499
|
+
lstToken.address,
|
|
1500
|
+
VESU_MULTIPLY,
|
|
1501
|
+
LST_MULTIPLIER_MANAGE_IDS.MULTIPLE_APPROVE
|
|
1502
|
+
)
|
|
1503
|
+
.bind(commonAdapter)
|
|
1504
|
+
);
|
|
1505
|
+
vaultSettings.leafAdapters.push(
|
|
1506
|
+
vesuAdapterLST
|
|
1507
|
+
.getVesuModifyDelegationAdapter(
|
|
1508
|
+
LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_ON
|
|
1509
|
+
)
|
|
1510
|
+
.bind(vesuAdapterLST)
|
|
1511
|
+
);
|
|
1512
|
+
vaultSettings.leafAdapters.push(
|
|
1513
|
+
vesuAdapterLST
|
|
1514
|
+
.getVesuModifyDelegationAdapter(
|
|
1515
|
+
LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_OFF
|
|
1516
|
+
)
|
|
1517
|
+
.bind(vesuAdapterLST)
|
|
1518
|
+
);
|
|
1519
|
+
|
|
1520
|
+
// approve lst once to avnu
|
|
1521
|
+
vaultSettings.leafAdapters.push(
|
|
1522
|
+
commonAdapter
|
|
1523
|
+
.getApproveAdapter(
|
|
1524
|
+
lstToken.address,
|
|
1525
|
+
AVNU_EXCHANGE,
|
|
1526
|
+
LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_WITHDRAW
|
|
1527
|
+
)
|
|
1528
|
+
.bind(commonAdapter)
|
|
1529
|
+
);
|
|
1530
|
+
for (let borrowableAsset of vaultSettings.borrowable_assets) {
|
|
1531
|
+
// in-efficient avnu swap looping (but good with endur integration)
|
|
1532
|
+
const debtAsset = borrowableAsset;
|
|
1533
|
+
const approve_debt_token_id = getAvnuManageIDs(
|
|
1534
|
+
LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT,
|
|
1535
|
+
debtAsset.symbol
|
|
1536
|
+
);
|
|
1537
|
+
const swap_debt_token_id = getAvnuManageIDs(
|
|
1538
|
+
LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT,
|
|
1539
|
+
debtAsset.symbol
|
|
1540
|
+
);
|
|
1541
|
+
const swap_lst_token_id = getAvnuManageIDs(
|
|
1542
|
+
LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_WITHDRAW,
|
|
1543
|
+
debtAsset.symbol
|
|
1544
|
+
);
|
|
1545
|
+
vaultSettings.leafAdapters.push(
|
|
1546
|
+
commonAdapter
|
|
1547
|
+
.getApproveAdapter(
|
|
1548
|
+
debtAsset.address,
|
|
1549
|
+
AVNU_EXCHANGE,
|
|
1550
|
+
approve_debt_token_id
|
|
1551
|
+
)
|
|
1552
|
+
.bind(commonAdapter)
|
|
1553
|
+
);
|
|
1554
|
+
vaultSettings.leafAdapters.push(
|
|
1555
|
+
commonAdapter
|
|
1556
|
+
.getAvnuAdapter(
|
|
1557
|
+
debtAsset.address,
|
|
1558
|
+
lstToken.address,
|
|
1559
|
+
swap_debt_token_id,
|
|
1560
|
+
false
|
|
1561
|
+
)
|
|
1562
|
+
.bind(commonAdapter)
|
|
1563
|
+
);
|
|
1564
|
+
vaultSettings.leafAdapters.push(
|
|
1565
|
+
commonAdapter
|
|
1566
|
+
.getAvnuAdapter(
|
|
1567
|
+
lstToken.address,
|
|
1568
|
+
debtAsset.address,
|
|
1569
|
+
swap_lst_token_id,
|
|
1570
|
+
false
|
|
1571
|
+
)
|
|
1572
|
+
.bind(commonAdapter)
|
|
1573
|
+
);
|
|
1574
|
+
|
|
1575
|
+
// approve LST to add collateral
|
|
1576
|
+
const vesuAdapter = new VesuAdapter({
|
|
1577
|
+
poolId: pool1,
|
|
1578
|
+
collateral: lstToken,
|
|
1579
|
+
debt: debtAsset,
|
|
1580
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
1581
|
+
id: getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, debtAsset.symbol)
|
|
1582
|
+
});
|
|
1583
|
+
vaultSettings.leafAdapters.push(
|
|
1584
|
+
commonAdapter
|
|
1585
|
+
.getApproveAdapter(
|
|
1586
|
+
lstToken.address,
|
|
1587
|
+
poolAddr,
|
|
1588
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1
|
|
1589
|
+
)
|
|
1590
|
+
.bind(commonAdapter)
|
|
1591
|
+
);
|
|
1592
|
+
vaultSettings.leafAdapters.push(
|
|
1593
|
+
vesuAdapter.getModifyPosition.bind(vesuAdapter)
|
|
1594
|
+
);
|
|
1595
|
+
|
|
1596
|
+
// Vesu multiply
|
|
1597
|
+
const multiplID = getVesuLegId(
|
|
1598
|
+
LST_MULTIPLIER_MANAGE_IDS.MULTIPLY_VESU,
|
|
1599
|
+
debtAsset.symbol
|
|
1600
|
+
);
|
|
1601
|
+
vaultSettings.leafAdapters.push(
|
|
1602
|
+
vesuAdapter.getMultiplyAdapter(multiplID).bind(vesuAdapter)
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// to bridge liquidity back to vault (used by bring_liquidity)
|
|
1607
|
+
vaultSettings.leafAdapters.push(
|
|
1608
|
+
commonAdapter
|
|
1609
|
+
.getApproveAdapter(
|
|
1610
|
+
lstToken.address,
|
|
1611
|
+
vaultSettings.vaultAddress,
|
|
1612
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
|
|
1613
|
+
)
|
|
1614
|
+
.bind(commonAdapter)
|
|
1615
|
+
);
|
|
1616
|
+
vaultSettings.leafAdapters.push(
|
|
1617
|
+
commonAdapter
|
|
1618
|
+
.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
|
|
1619
|
+
.bind(commonAdapter)
|
|
1620
|
+
);
|
|
1621
|
+
|
|
1622
|
+
// claim rewards
|
|
1623
|
+
vaultSettings.leafAdapters.push(
|
|
1624
|
+
vesuAdapterLST
|
|
1625
|
+
.getDefispringRewardsAdapter(
|
|
1626
|
+
UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS
|
|
1627
|
+
)
|
|
1628
|
+
.bind(vesuAdapterLST)
|
|
1629
|
+
);
|
|
1630
|
+
|
|
1631
|
+
// avnu swap for claims rewards
|
|
1632
|
+
const STRKToken = Global.getDefaultTokens().find(
|
|
1633
|
+
(token) => token.symbol === "STRK"
|
|
1634
|
+
)!;
|
|
1635
|
+
vaultSettings.leafAdapters.push(
|
|
1636
|
+
commonAdapter
|
|
1637
|
+
.getApproveAdapter(
|
|
1638
|
+
STRKToken.address,
|
|
1639
|
+
AVNU_EXCHANGE,
|
|
1640
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1
|
|
1641
|
+
)
|
|
1642
|
+
.bind(commonAdapter)
|
|
1643
|
+
);
|
|
1644
|
+
vaultSettings.leafAdapters.push(
|
|
1645
|
+
commonAdapter
|
|
1646
|
+
.getAvnuAdapter(
|
|
1647
|
+
STRKToken.address,
|
|
1648
|
+
lstToken.address,
|
|
1649
|
+
UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS,
|
|
1650
|
+
false
|
|
1651
|
+
)
|
|
1652
|
+
.bind(commonAdapter)
|
|
1653
|
+
);
|
|
1654
|
+
return vaultSettings;
|
|
829
1655
|
}
|
|
830
1656
|
|
|
831
|
-
|
|
1657
|
+
const AUDIT_URL = "https://docs.troves.fi/p/security#starknet-vault-kit";
|
|
1658
|
+
|
|
1659
|
+
function getFAQs(lstSymbol: string, underlyingSymbol: string): FAQ[] {
|
|
1660
|
+
return [
|
|
1661
|
+
{
|
|
1662
|
+
question: `What is the Hyper ${lstSymbol} Vault?`,
|
|
1663
|
+
answer: `The Hyper ${lstSymbol} Vault is a tokenized strategy that automatically loops your ${lstSymbol} to create up to 5x leverage to hence yield in a very low risk manner.`
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
question: "How does yield allocation work?",
|
|
1667
|
+
answer: `The strategy uses deposited ${lstSymbol} to collateralize it on Vesu, borrow more ${underlyingSymbol} to loop further. Instead of manually doing this, using flash loan, this leverage is created in a single gas efficient step. Our continuous monitoring systems gauge current yield and available liquidity in real time to make sure yield is optimal. For instance, if the looping becomes in-efficient in future, the strategy will rebalance to reduce leverage or simply hold ${lstSymbol} to continue earning yield.`
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
question: "Which protocols/dApp are used??",
|
|
1671
|
+
answer: (
|
|
1672
|
+
<span>
|
|
1673
|
+
Currently, the LST is from <strong>Endur</strong> while{" "}
|
|
1674
|
+
<strong>Vesu</strong> is used to collateralize the looped
|
|
1675
|
+
position.
|
|
1676
|
+
</span>
|
|
1677
|
+
)
|
|
1678
|
+
},
|
|
1679
|
+
{
|
|
1680
|
+
question: "Can I get liquidated?",
|
|
1681
|
+
answer: "The strategy uses highly correlated assets which drastically reduces the risk of liquidation. However, overtime, if left un-monitored, debt can increase enough to trigger a liquidation. But no worries, our continuous monitoring systems look for situations with reduced health factor and balance collateral/debt to bring it back to safe levels. With Troves, you can have a peaceful sleep."
|
|
1682
|
+
},
|
|
1683
|
+
{
|
|
1684
|
+
question: "What do I receive when I deposit?",
|
|
1685
|
+
answer: "Depositors receive vault tokens representing their proportional share of the vault. These tokens entitle holders to both the principal and accrued yield."
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
question: "How long do withdrawals take?",
|
|
1689
|
+
answer: "Withdrawals may take up to 1-2 hours to process, as the vault unwinds and settles liquidity routing across integrated protocols. In case of large withdrawals, to avoid slippage, we may slowly unwind the position, which could make the withdrawals longer."
|
|
1690
|
+
},
|
|
1691
|
+
{
|
|
1692
|
+
question: "Is the Hyper xSTRK Vault non-custodial?",
|
|
1693
|
+
answer: "Yes. The Hyper xSTRK Vault operates entirely on-chain. Users always maintain control of their vault tokens, and the strategy is fully transparent."
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
question: "Is the Vault audited?",
|
|
1697
|
+
answer: "Yes. The Hyper xSTRK Vault is audited by Zellic. Look for safety icon beside the strategy name for more details."
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
question: "Are there any fees?",
|
|
1701
|
+
answer: "Troves charges a performance of 10% on the yield generated. The APY shown is net of this fee. This fee is only applied to the profits earned, ensuring that users retain their initial capital."
|
|
1702
|
+
}
|
|
1703
|
+
];
|
|
1704
|
+
}
|
|
832
1705
|
|
|
833
|
-
|
|
834
|
-
return [
|
|
835
|
-
{
|
|
836
|
-
question: `What is the Hyper ${lstSymbol} Vault?`,
|
|
837
|
-
answer:
|
|
838
|
-
`The Hyper ${lstSymbol} Vault is a tokenized strategy that automatically loops your ${lstSymbol} to create up to 5x leverage to hence yield in a very low risk manner.`,
|
|
839
|
-
},
|
|
840
|
-
{
|
|
841
|
-
question: "How does yield allocation work?",
|
|
842
|
-
answer:
|
|
843
|
-
`The strategy uses deposited ${lstSymbol} to collateralize it on Vesu, borrow more ${underlyingSymbol} to loop further. Instead of manually doing this, using flash loan, this leverage is created in a single gas efficient step. Our continuous monitoring systems gauge current yield and available liquidity in real time to make sure yield is optimal. For instance, if the looping becomes in-efficient in future, the strategy will rebalance to reduce leverage or simply hold ${lstSymbol} to continue earning yield.`,
|
|
844
|
-
},
|
|
845
|
-
{
|
|
846
|
-
question: "Which protocols/dApp are used??",
|
|
847
|
-
answer: isLST ? (
|
|
848
|
-
<span>
|
|
849
|
-
Currently, the LST is from <strong>Endur</strong> while <strong>Vesu</strong> is used to collateralize the looped position.
|
|
850
|
-
</span>
|
|
851
|
-
) : (
|
|
852
|
-
<span>
|
|
853
|
-
Currently, the Yield Token is from <strong>Re7 Labs (Midas)</strong> while <strong>Vesu</strong> is used to collateralize the looped position.
|
|
854
|
-
</span>
|
|
855
|
-
),
|
|
856
|
-
},
|
|
1706
|
+
const _riskFactor: RiskFactor[] = [
|
|
857
1707
|
{
|
|
858
|
-
|
|
859
|
-
|
|
1708
|
+
type: RiskType.SMART_CONTRACT_RISK,
|
|
1709
|
+
value: SmartContractRiskLevel.WELL_AUDITED,
|
|
1710
|
+
weight: 25,
|
|
1711
|
+
reason: "Audited by Zellic"
|
|
860
1712
|
},
|
|
861
1713
|
{
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1714
|
+
type: RiskType.LIQUIDATION_RISK,
|
|
1715
|
+
value: LiquidationRiskLevel.VERY_LOW_PROBABILITY,
|
|
1716
|
+
weight: 25,
|
|
1717
|
+
reason: "The collateral and debt are highly correlated"
|
|
865
1718
|
},
|
|
866
1719
|
{
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1720
|
+
type: RiskType.TECHNICAL_RISK,
|
|
1721
|
+
value: TechnicalRiskLevel.STABLE_INFRASTRUCTURE,
|
|
1722
|
+
weight: 25,
|
|
1723
|
+
reason: "Liquidation can only happen if vault is left un-monitored for weeks, which is highly unlikely. We actively monitor all services on a daily basis."
|
|
870
1724
|
},
|
|
871
1725
|
{
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
question: "Is the Vault audited?",
|
|
878
|
-
answer:
|
|
879
|
-
`Yes. The Hyper ${lstSymbol} Vault is audited by Zellic. Look for safety icon beside the strategy name for more details.`,
|
|
880
|
-
},
|
|
881
|
-
{
|
|
882
|
-
question: "Are there any fees?",
|
|
883
|
-
answer:
|
|
884
|
-
"Troves charges a performance of 10% on the yield generated. The APY shown is net of this fee. This fee is only applied to the profits earned, ensuring that users retain their initial capital.",
|
|
885
|
-
},
|
|
886
|
-
];
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
export const _riskFactor: RiskFactor[] = [
|
|
890
|
-
{ type: RiskType.SMART_CONTRACT_RISK, value: SmartContractRiskLevel.WELL_AUDITED, weight: 25, reason: "Audited by Zellic" },
|
|
891
|
-
{ type: RiskType.LIQUIDATION_RISK, value: LiquidationRiskLevel.VERY_LOW_PROBABILITY, weight: 25, reason: "The collateral and debt are highly correlated" },
|
|
892
|
-
{ type: RiskType.TECHNICAL_RISK, value: TechnicalRiskLevel.STABLE_INFRASTRUCTURE, weight: 25, reason: "Liquidation can only happen if vault is left un-monitored for weeks, which is highly unlikely. We actively monitor all services on a daily basis." },
|
|
893
|
-
{type: RiskType.DEPEG_RISK, value: DepegRiskLevel.GENERALLY_STABLE, weight: 25, reason: "Generally stable pegged assets" },
|
|
1726
|
+
type: RiskType.DEPEG_RISK,
|
|
1727
|
+
value: DepegRiskLevel.GENERALLY_STABLE,
|
|
1728
|
+
weight: 25,
|
|
1729
|
+
reason: "Generally stable pegged assets"
|
|
1730
|
+
}
|
|
894
1731
|
];
|
|
895
1732
|
|
|
896
|
-
const borrowableAssets = [
|
|
897
|
-
'WBTC', 'tBTC', 'LBTC', 'solvBTC'
|
|
898
|
-
]
|
|
1733
|
+
const borrowableAssets = ["WBTC", "tBTC", "LBTC", "solvBTC"];
|
|
899
1734
|
|
|
900
1735
|
const hyperxSTRK: HyperLSTStrategySettings = {
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1736
|
+
vaultAddress: ContractAddr.from(
|
|
1737
|
+
"0x46c7a54c82b1fe374353859f554a40b8bd31d3e30f742901579e7b57b1b5960"
|
|
1738
|
+
),
|
|
1739
|
+
manager: ContractAddr.from(
|
|
1740
|
+
"0x5d499cd333757f461a0bedaca3dfc4d77320c773037e0aa299f22a6dbfdc03a"
|
|
1741
|
+
),
|
|
1742
|
+
vaultAllocator: ContractAddr.from(
|
|
1743
|
+
"0x511d07953a09bc7c505970891507c5a2486d2ea22752601a14db092186d7caa"
|
|
1744
|
+
),
|
|
1745
|
+
redeemRequestNFT: ContractAddr.from(
|
|
1746
|
+
"0x51e40b839dc0c2feca923f863072673b94abfa2483345be3b30b457a90d095"
|
|
1747
|
+
),
|
|
1748
|
+
aumOracle: ContractAddr.from(
|
|
1749
|
+
"0x48cf709870a1a0d453d37de108e0c41b8b89819ef54f95abc0e2e1f98bbe937"
|
|
1750
|
+
),
|
|
1751
|
+
leafAdapters: [],
|
|
1752
|
+
adapters: [],
|
|
1753
|
+
targetHealthFactor: 1.1,
|
|
1754
|
+
minHealthFactor: 1.05,
|
|
1755
|
+
borrowable_assets: Global.getDefaultTokens().filter(
|
|
1756
|
+
(token) => token.symbol === "STRK"
|
|
1757
|
+
),
|
|
1758
|
+
underlyingToken: Global.getDefaultTokens().find(
|
|
1759
|
+
(token) => token.symbol === "STRK"
|
|
1760
|
+
)!
|
|
1761
|
+
};
|
|
914
1762
|
|
|
915
1763
|
const hyperxWBTC: HyperLSTStrategySettings = {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1764
|
+
vaultAddress: ContractAddr.from(
|
|
1765
|
+
"0x2da9d0f96a46b453f55604313785dc866424240b1c6811d13bef594343db818"
|
|
1766
|
+
),
|
|
1767
|
+
manager: ContractAddr.from(
|
|
1768
|
+
"0x75866db44c81e6986f06035206ee9c7d15833ddb22d6a22c016cfb5c866a491"
|
|
1769
|
+
),
|
|
1770
|
+
vaultAllocator: ContractAddr.from(
|
|
1771
|
+
"0x57b5c1bb457b5e840a2714ae53ada87d77be2f3fd33a59b4fe709ef20c020c1"
|
|
1772
|
+
),
|
|
1773
|
+
redeemRequestNFT: ContractAddr.from(
|
|
1774
|
+
"0x7a5dc288325456f05e70e9616e16bc02ffbe448f4b89f80b47c0970b989c7c"
|
|
1775
|
+
),
|
|
1776
|
+
aumOracle: ContractAddr.from(
|
|
1777
|
+
"0x258f8a0ca0d21f542e48ad89d00e92dc4d9db4999084f50ef9c22dfb1e83023"
|
|
1778
|
+
),
|
|
1779
|
+
leafAdapters: [],
|
|
1780
|
+
adapters: [],
|
|
1781
|
+
targetHealthFactor: 1.1,
|
|
1782
|
+
minHealthFactor: 1.05,
|
|
1783
|
+
borrowable_assets: borrowableAssets.map(
|
|
1784
|
+
(asset) =>
|
|
1785
|
+
Global.getDefaultTokens().find((token) => token.symbol === asset)!
|
|
1786
|
+
),
|
|
1787
|
+
underlyingToken: Global.getDefaultTokens().find(
|
|
1788
|
+
(token) => token.symbol === "WBTC"
|
|
1789
|
+
)!
|
|
1790
|
+
};
|
|
929
1791
|
|
|
930
1792
|
const hyperxtBTC: HyperLSTStrategySettings = {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1793
|
+
vaultAddress: ContractAddr.from(
|
|
1794
|
+
"0x47d5f68477e5637ce0e56436c6b5eee5a354e6828995dae106b11a48679328"
|
|
1795
|
+
),
|
|
1796
|
+
manager: ContractAddr.from(
|
|
1797
|
+
"0xc4cc3e08029a0ae076f5fdfca70575abb78d23c5cd1c49a957f7e697885401"
|
|
1798
|
+
),
|
|
1799
|
+
vaultAllocator: ContractAddr.from(
|
|
1800
|
+
"0x50bbd4fe69f841ecb13b2619fe50ebfa4e8944671b5d0ebf7868fd80c61b31e"
|
|
1801
|
+
),
|
|
1802
|
+
redeemRequestNFT: ContractAddr.from(
|
|
1803
|
+
"0xeac9032f02057779816e38a6cb9185d12d86b3aacc9949b96b36de359c1e3"
|
|
1804
|
+
),
|
|
1805
|
+
aumOracle: ContractAddr.from(
|
|
1806
|
+
"0x7e0d05cb7ba3f7db77a36c21c21583b5a524c2e685c08c24b3554911fb4a039"
|
|
1807
|
+
),
|
|
1808
|
+
leafAdapters: [],
|
|
1809
|
+
adapters: [],
|
|
1810
|
+
targetHealthFactor: 1.1,
|
|
1811
|
+
minHealthFactor: 1.05,
|
|
1812
|
+
borrowable_assets: Global.getDefaultTokens().filter(
|
|
1813
|
+
(token) => token.symbol === "tBTC" || token.symbol === "WBTC"
|
|
1814
|
+
),
|
|
1815
|
+
underlyingToken: Global.getDefaultTokens().find(
|
|
1816
|
+
(token) => token.symbol === "tBTC"
|
|
1817
|
+
)!
|
|
1818
|
+
};
|
|
944
1819
|
|
|
945
1820
|
const hyperxsBTC: HyperLSTStrategySettings = {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1821
|
+
vaultAddress: ContractAddr.from(
|
|
1822
|
+
"0x437ef1e7d0f100b2e070b7a65cafec0b2be31b0290776da8b4112f5473d8d9"
|
|
1823
|
+
),
|
|
1824
|
+
manager: ContractAddr.from(
|
|
1825
|
+
"0xc9ac023090625b0be3f6532ca353f086746f9c09f939dbc1b2613f09e5f821"
|
|
1826
|
+
),
|
|
1827
|
+
vaultAllocator: ContractAddr.from(
|
|
1828
|
+
"0x60c2d856936b975459a5b4eb28b8672d91f757bd76cebb6241f8d670185dc01"
|
|
1829
|
+
),
|
|
1830
|
+
redeemRequestNFT: ContractAddr.from(
|
|
1831
|
+
"0x429e8ee8bc7ecd1ade72630d350a2e0f10f9a2507c45f188ba17fe8f2ab4cf3"
|
|
1832
|
+
),
|
|
1833
|
+
aumOracle: ContractAddr.from(
|
|
1834
|
+
"0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345"
|
|
1835
|
+
),
|
|
1836
|
+
leafAdapters: [],
|
|
1837
|
+
adapters: [],
|
|
1838
|
+
targetHealthFactor: 1.1,
|
|
1839
|
+
minHealthFactor: 1.05,
|
|
1840
|
+
borrowable_assets: Global.getDefaultTokens().filter(
|
|
1841
|
+
(token) => token.symbol === "solvBTC"
|
|
1842
|
+
),
|
|
1843
|
+
underlyingToken: Global.getDefaultTokens().find(
|
|
1844
|
+
(token) => token.symbol === "solvBTC"
|
|
1845
|
+
)!
|
|
1846
|
+
};
|
|
959
1847
|
|
|
960
1848
|
const hyperxLBTC: HyperLSTStrategySettings = {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1849
|
+
vaultAddress: ContractAddr.from(
|
|
1850
|
+
"0x64cf24d4883fe569926419a0569ab34497c6956a1a308fa883257f7486d7030"
|
|
1851
|
+
),
|
|
1852
|
+
manager: ContractAddr.from(
|
|
1853
|
+
"0x203530a4022a99b8f4b406aaf33b0849d43ad7422c1d5cc14ff8c667abec6c0"
|
|
1854
|
+
),
|
|
1855
|
+
vaultAllocator: ContractAddr.from(
|
|
1856
|
+
"0x7dbc8ccd4eabce6ea6c19e0e5c9ccca3a93bd510303b9e071cbe25fc508546e"
|
|
1857
|
+
),
|
|
1858
|
+
redeemRequestNFT: ContractAddr.from(
|
|
1859
|
+
"0x5ee66a39af9aef3d0d48982b4a63e8bd2a5bad021916bd87fb0eae3a26800b8"
|
|
1860
|
+
),
|
|
1861
|
+
aumOracle: ContractAddr.from(
|
|
1862
|
+
"0x23d69e4391fa72d10e625e7575d8bddbb4aff96f04503f83fdde23123bf41d0"
|
|
1863
|
+
),
|
|
1864
|
+
leafAdapters: [],
|
|
1865
|
+
adapters: [],
|
|
1866
|
+
targetHealthFactor: 1.1,
|
|
1867
|
+
minHealthFactor: 1.05,
|
|
1868
|
+
borrowable_assets: Global.getDefaultTokens().filter(
|
|
1869
|
+
(token) => token.symbol === "LBTC"
|
|
1870
|
+
),
|
|
1871
|
+
underlyingToken: Global.getDefaultTokens().find(
|
|
1872
|
+
(token) => token.symbol === "LBTC"
|
|
1873
|
+
)!
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
function getInvestmentSteps(lstSymbol: string, underlyingSymbol: string) {
|
|
1877
|
+
return [
|
|
1878
|
+
`Deposit ${lstSymbol} into the vault`,
|
|
1879
|
+
`The vault manager loops the ${underlyingSymbol} to buy ${lstSymbol}`,
|
|
1880
|
+
`The vault manager collateralizes the ${lstSymbol} on Vesu`,
|
|
1881
|
+
`The vault manager borrows more ${underlyingSymbol} to loop further`,
|
|
1882
|
+
`If required, adjust leverage or re-allocate assets within LST pool on Vesu to optimize yield`
|
|
1883
|
+
];
|
|
973
1884
|
}
|
|
974
1885
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const hypermRe7YIELD: HyperLSTStrategySettings = {
|
|
995
|
-
vaultAddress: ContractAddr.from('0x42797ab4eb1f72787442e91a73d63a39e3a141c1106470a946ecc328db6896c'),
|
|
996
|
-
manager: ContractAddr.from('0x435b45d40fbb406cf69ac84bb471e7b7a4ea2295d0893c05dd2db565295e77f'),
|
|
997
|
-
vaultAllocator: ContractAddr.from('0x456c4c6afca90512aeb5c735d84405fea6e51ab06d1851ac8cdb0a235e14f15'),
|
|
998
|
-
redeemRequestNFT: ContractAddr.from('0x4bbb25c2568af07967342833f7db1aece1be1be2330798dab4ee585aa6c2c72'),
|
|
999
|
-
aumOracle: ContractAddr.from('0x3e1f2825158cafccc9b42a8165d17ceb6b8e966474d9c63587d338746888382'),
|
|
1000
|
-
leafAdapters: [],
|
|
1001
|
-
adapters: [],
|
|
1002
|
-
targetHealthFactor: 1.1,
|
|
1003
|
-
minHealthFactor: 1.05,
|
|
1004
|
-
borrowable_assets: [Global.getDefaultTokens().find(token => token.symbol === 'USDC')!],
|
|
1005
|
-
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'USDC')!,
|
|
1006
|
-
quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'mRe7BTC')!.decimals),
|
|
1886
|
+
// Helper to get maxTVL based on LST symbol (matching client values)
|
|
1887
|
+
function getMaxTVL(lstSymbol: string): Web3Number {
|
|
1888
|
+
const lstMaxTVLs: Record<string, number> = {
|
|
1889
|
+
xWBTC: 5,
|
|
1890
|
+
xLBTC: 5,
|
|
1891
|
+
xtBTC: 5,
|
|
1892
|
+
xsBTC: 5,
|
|
1893
|
+
xSTRK: 550000
|
|
1894
|
+
};
|
|
1895
|
+
|
|
1896
|
+
const maxTVLValue = lstMaxTVLs[lstSymbol] || 0;
|
|
1897
|
+
const token = Global.getDefaultTokens().find(
|
|
1898
|
+
(token) => token.symbol === lstSymbol
|
|
1899
|
+
);
|
|
1900
|
+
if (!token) {
|
|
1901
|
+
return Web3Number.fromWei(0, 18);
|
|
1902
|
+
}
|
|
1903
|
+
return Web3Number.fromWei(maxTVLValue, token.decimals);
|
|
1007
1904
|
}
|
|
1008
1905
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1906
|
+
// Helper to create Hyper LST strategy settings
|
|
1907
|
+
function createHyperLSTSettings(
|
|
1908
|
+
lstSymbol: string,
|
|
1909
|
+
underlyingSymbol: string
|
|
1910
|
+
): StrategySettings {
|
|
1911
|
+
const depositToken = Global.getDefaultTokens().find(
|
|
1912
|
+
(token) => token.symbol === lstSymbol
|
|
1913
|
+
)!;
|
|
1914
|
+
return {
|
|
1915
|
+
maxTVL: getMaxTVL(lstSymbol),
|
|
1916
|
+
isPaused: false,
|
|
1917
|
+
liveStatus: StrategyLiveStatus.HOT,
|
|
1918
|
+
isAudited: true,
|
|
1919
|
+
isInstantWithdrawal: false,
|
|
1920
|
+
hideHarvestInfo: true,
|
|
1921
|
+
quoteToken: depositToken,
|
|
1922
|
+
showWithdrawalWarningModal: false,
|
|
1923
|
+
alerts: [
|
|
1924
|
+
{
|
|
1925
|
+
tab: "withdraw" as const,
|
|
1926
|
+
text: "On withdrawal, you will receive an NFT representing your withdrawal request. The funds will be automatically sent to your wallet (NFT owner) in 24 hours (In this initial phase of Launch). You can monitor the status in transactions tab.",
|
|
1927
|
+
type: "info" as const
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
tab: "deposit" as const,
|
|
1931
|
+
text: (
|
|
1932
|
+
<>
|
|
1933
|
+
To acquire the LST, please visit{" "}
|
|
1934
|
+
<a
|
|
1935
|
+
href="https://app.endur.fi"
|
|
1936
|
+
target="_blank"
|
|
1937
|
+
rel="noopener noreferrer"
|
|
1938
|
+
>
|
|
1939
|
+
endur.fi
|
|
1940
|
+
</a>
|
|
1941
|
+
</>
|
|
1942
|
+
),
|
|
1943
|
+
type: "info" as const
|
|
1944
|
+
},
|
|
1945
|
+
{
|
|
1946
|
+
tab: "deposit" as const,
|
|
1947
|
+
text: "It may take up to one week for your deposit to appreciate in value. This delay occurs because the LST price is sourced from DEXes and liquidity is usually rebased once a week.",
|
|
1948
|
+
type: "info" as const
|
|
1949
|
+
}
|
|
1950
|
+
]
|
|
1951
|
+
};
|
|
1017
1952
|
}
|
|
1018
1953
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
launchBlock: 0,
|
|
1025
|
-
type: 'Other',
|
|
1026
|
-
depositTokens: [Global.getDefaultTokens().find(token => token.symbol === lstSymbol)!],
|
|
1027
|
-
additionalInfo: getLooperSettings(lstSymbol, underlyingSymbol, addresses, lstSymbol === 'xSTRK' ? VesuPools.Re7xSTRK : VesuPools.Re7xBTC),
|
|
1028
|
-
risk: {
|
|
1029
|
-
riskFactor: _riskFactor,
|
|
1030
|
-
netRisk:
|
|
1031
|
-
_riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
|
|
1032
|
-
_riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
|
|
1033
|
-
notARisks: getNoRiskTags(_riskFactor)
|
|
1954
|
+
const HYPER_LST_SECURITY = {
|
|
1955
|
+
auditStatus: AuditStatus.AUDITED,
|
|
1956
|
+
sourceCode: {
|
|
1957
|
+
type: SourceCodeType.CLOSED_SOURCE,
|
|
1958
|
+
contractLink: "https://github.com/trovesfi/troves-contracts"
|
|
1034
1959
|
},
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1960
|
+
accessControl: {
|
|
1961
|
+
type: AccessControlType.STANDARD_ACCOUNT,
|
|
1962
|
+
addresses: [ContractAddr.from("0x0")],
|
|
1963
|
+
timeLock: "2 Days"
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
|
|
1967
|
+
const HYPER_LST_REDEMPTION_INFO = {
|
|
1968
|
+
instantWithdrawalVault: InstantWithdrawalVault.NO,
|
|
1969
|
+
expectedRedemptionTime: {
|
|
1970
|
+
upto1M: "1-2hrs",
|
|
1971
|
+
upto10M: "24hrs",
|
|
1972
|
+
above10M: "2-3 Days"
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
|
|
1976
|
+
function getStrategySettings(
|
|
1977
|
+
lstSymbol: string,
|
|
1978
|
+
underlyingSymbol: string,
|
|
1979
|
+
addresses: HyperLSTStrategySettings,
|
|
1980
|
+
isPreview: boolean = false
|
|
1981
|
+
): IStrategyMetadata<HyperLSTStrategySettings> {
|
|
1982
|
+
return {
|
|
1983
|
+
id: `hyper_${lstSymbol.toLowerCase()}`,
|
|
1984
|
+
name: `Hyper ${lstSymbol}`,
|
|
1985
|
+
description: getDescription(lstSymbol, underlyingSymbol),
|
|
1986
|
+
address: addresses.vaultAddress,
|
|
1987
|
+
launchBlock: 0,
|
|
1988
|
+
type: "Other",
|
|
1989
|
+
depositTokens: [
|
|
1990
|
+
Global.getDefaultTokens().find(
|
|
1991
|
+
(token) => token.symbol === lstSymbol
|
|
1992
|
+
)!
|
|
1993
|
+
],
|
|
1994
|
+
additionalInfo: getLooperSettings(
|
|
1995
|
+
lstSymbol,
|
|
1996
|
+
underlyingSymbol,
|
|
1997
|
+
addresses,
|
|
1998
|
+
lstSymbol === "xSTRK" ? VesuPools.Re7xSTRK : VesuPools.Re7xBTC
|
|
1999
|
+
),
|
|
2000
|
+
risk: {
|
|
2001
|
+
riskFactor: _riskFactor,
|
|
2002
|
+
netRisk:
|
|
2003
|
+
_riskFactor.reduce(
|
|
2004
|
+
(acc, curr) => acc + curr.value * curr.weight,
|
|
2005
|
+
0
|
|
2006
|
+
) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
|
|
2007
|
+
notARisks: getNoRiskTags(_riskFactor)
|
|
2008
|
+
},
|
|
2009
|
+
auditUrl: AUDIT_URL,
|
|
2010
|
+
protocols: [Protocols.ENDUR, Protocols.VESU],
|
|
2011
|
+
settings: createHyperLSTSettings(lstSymbol, underlyingSymbol),
|
|
2012
|
+
contractDetails: getContractDetails(addresses),
|
|
2013
|
+
faqs: getFAQs(lstSymbol, underlyingSymbol),
|
|
2014
|
+
investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
|
|
2015
|
+
isPreview: isPreview,
|
|
2016
|
+
apyMethodology:
|
|
2017
|
+
"Current annualized APY in terms of base asset of the LST. There is no additional fee taken by Troves on LST APY. We charge a 10% performance fee on the additional gain which is already accounted in the APY shown.",
|
|
2018
|
+
category: StrategyCategory.META_VAULTS,
|
|
2019
|
+
tags: [StrategyTag.HYPER_LST],
|
|
2020
|
+
security: HYPER_LST_SECURITY,
|
|
2021
|
+
redemptionInfo: HYPER_LST_REDEMPTION_INFO
|
|
2022
|
+
};
|
|
1044
2023
|
}
|
|
1045
2024
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
// const hyperxWBTCTest: HyperLSTStrategySettings = {
|
|
1049
|
-
// vaultAddress: ContractAddr.from('0x5535e01a0d0438a888267ba6cd6519a40e653cdfb5dce4af475221d9cf11e63'),
|
|
1050
|
-
// manager: ContractAddr.from('0x3caa816a9d9b55a6621a47c1a7e6773141dd05fb3ca4ec4b774656f360b32a1'),
|
|
1051
|
-
// vaultAllocator: ContractAddr.from('0x3d4f82f8bfa8e5b2f0242c4d8ed87287c9ad5427be8b982a31a0393cf3075d1'),
|
|
1052
|
-
// redeemRequestNFT: ContractAddr.from('0x6190460d0f1fd5d142bd5378d7b3270e70253bdd652d6826175f6c0b1ad4f32'),
|
|
1053
|
-
// aumOracle: ContractAddr.from('0x43f5aa7c67b29b5e69ad03ab427f7613e43350d6ddede481e42e8184f49cb2f'),
|
|
1054
|
-
// leafAdapters: [],
|
|
1055
|
-
// adapters: [],
|
|
1056
|
-
// targetHealthFactor: 1.1,
|
|
1057
|
-
// minHealthFactor: 1.05,
|
|
1058
|
-
// borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
|
|
1059
|
-
// underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!,
|
|
1060
|
-
// quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!.decimals),
|
|
1061
|
-
// }
|
|
1062
|
-
|
|
1063
2025
|
export const HyperLSTStrategies: IStrategyMetadata<HyperLSTStrategySettings>[] =
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
getStrategySettings('mRe7YIELD', 'mRe7YIELD', hypermRe7YIELD, false, false),
|
|
1072
|
-
]
|
|
2026
|
+
[
|
|
2027
|
+
getStrategySettings("xSTRK", "STRK", hyperxSTRK, false),
|
|
2028
|
+
getStrategySettings("xWBTC", "WBTC", hyperxWBTC, false),
|
|
2029
|
+
getStrategySettings("xtBTC", "tBTC", hyperxtBTC, false),
|
|
2030
|
+
getStrategySettings("xsBTC", "solvBTC", hyperxsBTC, false),
|
|
2031
|
+
getStrategySettings("xLBTC", "LBTC", hyperxLBTC, false)
|
|
2032
|
+
];
|