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