@strkfarm/sdk 2.0.0-staging.4 → 2.0.0-staging.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +10 -6
- package/dist/cli.mjs +10 -6
- package/dist/index.browser.global.js +29277 -26655
- package/dist/index.browser.mjs +4037 -1224
- package/dist/index.d.ts +287 -45
- package/dist/index.js +4316 -1489
- package/dist/index.mjs +4256 -1437
- package/package.json +4 -4
- package/src/data/yoloVault.abi.json +1109 -0
- 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 +27 -0
- package/src/interfaces/common.tsx +68 -25
- package/src/modules/avnu.ts +1 -1
- package/src/modules/erc20.ts +18 -2
- package/src/strategies/base-strategy.ts +216 -6
- package/src/strategies/constants.ts +2 -2
- package/src/strategies/ekubo-cl-vault.tsx +210 -105
- 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 +156 -11
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +48 -27
- package/src/strategies/universal-lst-muliplier-strategy.tsx +1473 -574
- package/src/strategies/universal-strategy.tsx +141 -69
- package/src/strategies/vesu-rebalance.tsx +27 -11
- package/src/strategies/yoloVault.ts +747 -0
- package/src/utils/logger.node.ts +11 -4
- 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,154 @@ 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 getUserPositionCards(input: UserPositionCardsInput) {
|
|
681
|
+
const cards = await super.getUserPositionCards(input);
|
|
682
|
+
|
|
683
|
+
try {
|
|
684
|
+
const [unrealizedResult, userTVL, realizedApyRaw] = await Promise.all([
|
|
685
|
+
this.getUserUnrealizedGains(input.user),
|
|
686
|
+
this.getUserTVL(input.user),
|
|
687
|
+
this.getUserRealizedAPY().catch(() => null),
|
|
688
|
+
]);
|
|
689
|
+
const amount = unrealizedResult.unrealizedGains;
|
|
690
|
+
let usdValue = 0;
|
|
691
|
+
const userAmount = userTVL.amount.toNumber();
|
|
692
|
+
if (Number.isFinite(userAmount) && userAmount > 0) {
|
|
693
|
+
usdValue = (userTVL.usdValue / userAmount) * amount.toNumber();
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const unrealizedCard = {
|
|
697
|
+
title: "Unrealized Gains",
|
|
698
|
+
tooltip: "Unrealized gains based on current market prices vs realized prices",
|
|
699
|
+
value: this.formatTokenAmountForCard(amount, unrealizedResult.tokenInfo),
|
|
700
|
+
subValue: `≈ ${this.formatUSDForCard(usdValue)}`,
|
|
701
|
+
subValueColor: this.getSubValueColorFromSignedNumber(usdValue),
|
|
702
|
+
};
|
|
703
|
+
cards.push(unrealizedCard);
|
|
704
|
+
|
|
705
|
+
// add realised apy card
|
|
706
|
+
const realizedApy = typeof realizedApyRaw === "number"
|
|
707
|
+
? this.formatPercentForCard(realizedApyRaw)
|
|
708
|
+
: "N/A";
|
|
709
|
+
cards.push({
|
|
710
|
+
title: "Realized APY",
|
|
711
|
+
tooltip: this.metadata.realizedApyMethodology || "Realized APY is based on past 14 days performance",
|
|
712
|
+
value: realizedApy,
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
} catch (error) {
|
|
716
|
+
logger.warn(`${this.getTag()}::getUserPositionCards unrealized gains fallback`, error);
|
|
717
|
+
cards.push({
|
|
718
|
+
title: "Unrealized Gains",
|
|
719
|
+
tooltip: "Unrealized gains based on current market prices vs realized prices",
|
|
720
|
+
value: this.formatTokenAmountForCard(
|
|
721
|
+
Web3Number.fromWei("0", this.asset().decimals),
|
|
722
|
+
this.asset()
|
|
723
|
+
),
|
|
724
|
+
subValue: `≈ ${this.formatUSDForCard(0)}`,
|
|
725
|
+
subValueColor: "default",
|
|
726
|
+
});
|
|
727
|
+
cards.push({
|
|
728
|
+
title: "Realized APY",
|
|
729
|
+
tooltip: this.metadata.realizedApyMethodology || "Realized APY is based on past 14 days performance",
|
|
730
|
+
value: "N/A",
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// if xstrk vault, update lifetype earnings to specific title.
|
|
735
|
+
if (this.asset().symbol === "xSTRK") {
|
|
736
|
+
const index = cards.findIndex((card) => card.title === "Lifetime Earnings");
|
|
737
|
+
if (index >= 0) {
|
|
738
|
+
cards[index] = {
|
|
739
|
+
title: "Lifetime Earnings (Read this)",
|
|
740
|
+
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.",
|
|
741
|
+
value: this.formatTokenAmountForCard(
|
|
742
|
+
Web3Number.fromWei("0", this.asset().decimals),
|
|
743
|
+
this.asset()
|
|
744
|
+
),
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return cards;
|
|
750
|
+
}
|
|
751
|
+
|
|
410
752
|
//
|
|
411
753
|
private async _getMinOutputAmountLSTBuy(amountInUnderlying: Web3Number) {
|
|
412
754
|
const lstTruePrice = await this.getLSTExchangeRate();
|
|
413
|
-
// during buy, the purchase should always be <= true LST price.
|
|
414
|
-
const minOutputAmount = amountInUnderlying
|
|
755
|
+
// during buy, the purchase should always be <= true LST price.
|
|
756
|
+
const minOutputAmount = amountInUnderlying
|
|
757
|
+
.dividedBy(lstTruePrice)
|
|
758
|
+
.multipliedBy(0.99979); // minus 0.021% to account for avnu fees
|
|
415
759
|
return new Web3Number(minOutputAmount.toString(), this.asset().decimals);
|
|
416
760
|
}
|
|
417
761
|
|
|
418
762
|
private async _getMinOutputAmountLSTSell(amountInLST: Web3Number) {
|
|
419
763
|
const lstTruePrice = await this.getLSTExchangeRate();
|
|
420
|
-
// during sell, the purchase should always be > 0.995 * true LST price.
|
|
421
|
-
const minOutputAmount = amountInLST
|
|
764
|
+
// during sell, the purchase should always be > 0.995 * true LST price.
|
|
765
|
+
const minOutputAmount = amountInLST
|
|
766
|
+
.multipliedBy(lstTruePrice)
|
|
767
|
+
.multipliedBy(0.995);
|
|
422
768
|
return minOutputAmount;
|
|
423
769
|
}
|
|
424
770
|
|
|
425
771
|
// 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.
|
|
772
|
+
// if the current net yield < LST yield, add a function to calculate how much to unwind.
|
|
427
773
|
|
|
428
774
|
/**
|
|
429
775
|
* Uses vesu's multiple call to create leverage on LST
|
|
430
776
|
* Deposit amount is in LST
|
|
431
|
-
* @param params
|
|
777
|
+
* @param params
|
|
432
778
|
*/
|
|
433
779
|
async getVesuMultiplyCall(params: {
|
|
434
|
-
isDeposit: boolean
|
|
435
|
-
leg1DepositAmount: Web3Number
|
|
436
|
-
maxEkuboPriceImpact?: number
|
|
437
|
-
poolId: ContractAddr
|
|
780
|
+
isDeposit: boolean;
|
|
781
|
+
leg1DepositAmount: Web3Number;
|
|
782
|
+
maxEkuboPriceImpact?: number;
|
|
783
|
+
poolId: ContractAddr;
|
|
438
784
|
}) {
|
|
439
785
|
const maxEkuboPriceImpact = params.maxEkuboPriceImpact || 0.01;
|
|
440
786
|
const vesuAdapter1 = this.getVesuSameTokenAdapter(params.poolId);
|
|
@@ -442,10 +788,12 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
442
788
|
logger.verbose(`${this.getTag()}::getVesuMultiplyCall legLTV: ${legLTV}`);
|
|
443
789
|
|
|
444
790
|
if (!params.isDeposit) {
|
|
445
|
-
// try using unused balance to unwind.
|
|
446
|
-
// no need to unwind.
|
|
791
|
+
// try using unused balance to unwind.
|
|
792
|
+
// no need to unwind.
|
|
447
793
|
const unusedBalance = await this.getUnusedBalance();
|
|
448
|
-
logger.verbose(
|
|
794
|
+
logger.verbose(
|
|
795
|
+
`${this.getTag()}::getVesuMultiplyCall unusedBalance: ${unusedBalance.amount.toString()}, required: ${params.leg1DepositAmount.toString()}`,
|
|
796
|
+
);
|
|
449
797
|
// undo
|
|
450
798
|
// if (unusedBalance.amount.gte(params.leg1DepositAmount)) {
|
|
451
799
|
// return [];
|
|
@@ -454,51 +802,85 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
454
802
|
}
|
|
455
803
|
|
|
456
804
|
const existingPositions = await vesuAdapter1.getPositions(this.config);
|
|
457
|
-
const collateralisation = await vesuAdapter1.getCollateralization(
|
|
805
|
+
const collateralisation = await vesuAdapter1.getCollateralization(
|
|
806
|
+
this.config,
|
|
807
|
+
);
|
|
458
808
|
const existingCollateralInfo = existingPositions[0];
|
|
459
809
|
const existingDebtInfo = existingPositions[1];
|
|
460
810
|
logger.debug(`${this.getTag()}::getVesuMultiplyCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
461
811
|
existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
462
812
|
|
|
463
813
|
// - Prices as seen by Vesu contracts, ideal for HF math
|
|
464
|
-
// Price 1 is ok as fallback bcz that would relatively price the
|
|
814
|
+
// Price 1 is ok as fallback bcz that would relatively price the
|
|
465
815
|
// collateral and debt as equal.
|
|
466
|
-
const collateralPrice =
|
|
467
|
-
|
|
468
|
-
|
|
816
|
+
const collateralPrice =
|
|
817
|
+
collateralisation[0].usdValue > 0
|
|
818
|
+
? collateralisation[0].usdValue /
|
|
819
|
+
existingCollateralInfo.amount.toNumber()
|
|
820
|
+
: 1;
|
|
821
|
+
const debtPrice =
|
|
822
|
+
collateralisation[1].usdValue > 0
|
|
823
|
+
? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber()
|
|
824
|
+
: 1;
|
|
825
|
+
logger.debug(
|
|
826
|
+
`${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`,
|
|
827
|
+
);
|
|
469
828
|
|
|
470
829
|
// - Prices as seen by actual swap price
|
|
471
830
|
const dexPrice = await this.getLSTDexPrice();
|
|
472
831
|
|
|
473
832
|
// compute optimal amount of collateral and debt post addition/removal
|
|
474
833
|
// target hf = collateral * collateralPrice * ltv / debt * debtPrice
|
|
475
|
-
// assuming X to be the usd amount of debt borrowed or repaied (negative).
|
|
834
|
+
// assuming X to be the usd amount of debt borrowed or repaied (negative).
|
|
476
835
|
// target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv / (debt * debtPrice + X)
|
|
477
836
|
// => X * target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv - (debt * debtPrice * target hf)
|
|
478
837
|
// => X * (target hf - ltv)= ((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)
|
|
479
838
|
// => 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
|
-
|
|
839
|
+
const addedCollateral = params.leg1DepositAmount.multipliedBy(
|
|
840
|
+
params.isDeposit ? 1 : -1,
|
|
841
|
+
);
|
|
842
|
+
logger.verbose(
|
|
843
|
+
`${this.getTag()}::getVesuMultiplyCall addedCollateral: ${addedCollateral}`,
|
|
844
|
+
);
|
|
845
|
+
const numeratorPart1 = existingCollateralInfo.amount
|
|
846
|
+
.plus(addedCollateral)
|
|
847
|
+
.multipliedBy(collateralPrice)
|
|
848
|
+
.multipliedBy(legLTV);
|
|
849
|
+
logger.verbose(
|
|
850
|
+
`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}`,
|
|
851
|
+
);
|
|
852
|
+
const numeratorPart2 = existingDebtInfo.amount
|
|
853
|
+
.multipliedBy(debtPrice)
|
|
854
|
+
.multipliedBy(this.metadata.additionalInfo.targetHealthFactor);
|
|
855
|
+
logger.verbose(
|
|
856
|
+
`${this.getTag()}::getVesuMultiplyCall numeratorPart2: ${numeratorPart2}`,
|
|
857
|
+
);
|
|
858
|
+
const denominatorPart =
|
|
859
|
+
this.metadata.additionalInfo.targetHealthFactor - legLTV / dexPrice;
|
|
860
|
+
logger.verbose(
|
|
861
|
+
`${this.getTag()}::getVesuMultiplyCall denominatorPart: ${denominatorPart}`,
|
|
862
|
+
);
|
|
863
|
+
const x_debt_usd = numeratorPart1
|
|
864
|
+
.minus(numeratorPart2)
|
|
865
|
+
.dividedBy(denominatorPart);
|
|
866
|
+
logger.verbose(
|
|
867
|
+
`${this.getTag()}::getVesuMultiplyCall x_debt_usd: ${x_debt_usd}`,
|
|
868
|
+
);
|
|
869
|
+
logger.debug(
|
|
870
|
+
`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}, numeratorPart2: ${numeratorPart2}, denominatorPart: ${denominatorPart}`,
|
|
871
|
+
);
|
|
492
872
|
|
|
493
873
|
// both in underlying
|
|
494
874
|
let debtAmount = x_debt_usd.dividedBy(debtPrice);
|
|
495
875
|
const marginAmount = addedCollateral;
|
|
496
|
-
logger.verbose(
|
|
876
|
+
logger.verbose(
|
|
877
|
+
`${this.getTag()}::getVesuMultiplyCall debtAmount: ${debtAmount}, marginAmount: ${marginAmount}`,
|
|
878
|
+
);
|
|
497
879
|
|
|
498
880
|
if (marginAmount.lt(0) && debtAmount.gt(0)) {
|
|
499
881
|
// 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.
|
|
882
|
+
// increasing and reducing lever. and in this case, the HF will go high,
|
|
883
|
+
// which is ok.
|
|
502
884
|
debtAmount = Web3Number.fromWei(0, this.asset().decimals);
|
|
503
885
|
}
|
|
504
886
|
|
|
@@ -515,38 +897,60 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
515
897
|
lstDexPriceInUnderlying: dexPrice,
|
|
516
898
|
isIncrease: debtAmount.greaterThan(0),
|
|
517
899
|
maxEkuboPriceImpact,
|
|
518
|
-
poolId: params.poolId
|
|
900
|
+
poolId: params.poolId,
|
|
519
901
|
});
|
|
520
902
|
}
|
|
521
903
|
|
|
522
904
|
getLSTUnderlyingTokenInfo() {
|
|
523
|
-
const vesuAdapter1 = this.getVesuSameTokenAdapter(
|
|
905
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter(
|
|
906
|
+
this.metadata.additionalInfo.defaultPoolId,
|
|
907
|
+
);
|
|
524
908
|
return vesuAdapter1.config.debt;
|
|
525
909
|
}
|
|
526
910
|
|
|
527
|
-
async getMaxBorrowableAmount(
|
|
911
|
+
async getMaxBorrowableAmount(
|
|
912
|
+
params: { isAPYComputation: boolean } = { isAPYComputation: false },
|
|
913
|
+
) {
|
|
528
914
|
const vesuAdapters = this.getVesuAdapters();
|
|
529
915
|
let netMaxBorrowableAmount = Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals);
|
|
530
|
-
const maxBorrowables: {amount: Web3Number, dexSwappableAmount: Web3Number, maxBorrowableAmount: Web3Number, borrowableAsset: TokenInfo, ltv: number}[] = [];
|
|
916
|
+
const maxBorrowables: {amount: Web3Number, dexSwappableAmount: Web3Number, maxBorrowableAmount: Web3Number, borrowableAsset: TokenInfo, ltv: number, poolId: ContractAddr}[] = [];
|
|
531
917
|
for (const vesuAdapter of vesuAdapters) {
|
|
532
|
-
const output = await this.getMaxBorrowableAmountByVesuAdapter(
|
|
918
|
+
const output = await this.getMaxBorrowableAmountByVesuAdapter(
|
|
919
|
+
vesuAdapter,
|
|
920
|
+
params.isAPYComputation,
|
|
921
|
+
);
|
|
533
922
|
const ltv = await vesuAdapter.getLTVConfig(this.config);
|
|
534
|
-
maxBorrowables.push({...output, ltv});
|
|
923
|
+
maxBorrowables.push({...output, ltv,poolId: vesuAdapter.config.poolId});
|
|
535
924
|
}
|
|
536
925
|
maxBorrowables.sort((a, b) => b.amount.toNumber() - a.amount.toNumber());
|
|
537
|
-
netMaxBorrowableAmount = maxBorrowables.reduce(
|
|
538
|
-
|
|
926
|
+
netMaxBorrowableAmount = maxBorrowables.reduce(
|
|
927
|
+
(acc, curr) => acc.plus(curr.amount),
|
|
928
|
+
Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals),
|
|
929
|
+
);
|
|
930
|
+
return { netMaxBorrowableAmount, maxBorrowables };
|
|
539
931
|
}
|
|
540
932
|
|
|
541
|
-
// recursively, using binary search computes max swappable.
|
|
933
|
+
// recursively, using binary search computes max swappable.
|
|
542
934
|
// @dev assumes 1 token of from == 1 token of to
|
|
543
|
-
async getMaxSwappableWithMaxSlippage(
|
|
935
|
+
async getMaxSwappableWithMaxSlippage(
|
|
936
|
+
fromToken: TokenInfo,
|
|
937
|
+
toToken: TokenInfo,
|
|
938
|
+
maxSlippage: number,
|
|
939
|
+
maxAmount: Web3Number,
|
|
940
|
+
) {
|
|
544
941
|
const output = await findMaxInputWithSlippage({
|
|
545
942
|
apiGetOutput: async (inputAmount: number): Promise<number> => {
|
|
546
943
|
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
|
-
|
|
944
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // artificial delay, to avoid rate limit
|
|
945
|
+
const quote = await ekuboQuoter.getQuote(
|
|
946
|
+
fromToken.address.address,
|
|
947
|
+
toToken.address.address,
|
|
948
|
+
new Web3Number(inputAmount.toFixed(9), fromToken.decimals),
|
|
949
|
+
);
|
|
950
|
+
return Web3Number.fromWei(
|
|
951
|
+
quote.total_calculated.toString(),
|
|
952
|
+
toToken.decimals,
|
|
953
|
+
).toNumber();
|
|
550
954
|
},
|
|
551
955
|
maxInput: maxAmount.toNumber(),
|
|
552
956
|
maxSlippagePercent: maxSlippage,
|
|
@@ -556,48 +960,103 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
556
960
|
return new Web3Number(output.optimalInput, fromToken.decimals);
|
|
557
961
|
}
|
|
558
962
|
|
|
559
|
-
async getMaxBorrowableAmountByVesuAdapter(
|
|
560
|
-
|
|
963
|
+
async getMaxBorrowableAmountByVesuAdapter(
|
|
964
|
+
vesuAdapter: VesuAdapter,
|
|
965
|
+
isAPYComputation: boolean,
|
|
966
|
+
) {
|
|
967
|
+
const lstAPY = await this.getLSTAPR(
|
|
968
|
+
this.getLSTUnderlyingTokenInfo().address,
|
|
969
|
+
);
|
|
561
970
|
const maxInterestRate = lstAPY * 0.8;
|
|
562
|
-
const {maxDebtToHave: maxBorrowableAmount, currentDebt} =
|
|
971
|
+
const { maxDebtToHave: maxBorrowableAmount, currentDebt } =
|
|
972
|
+
await vesuAdapter.getMaxBorrowableByInterestRate(
|
|
973
|
+
this.config,
|
|
974
|
+
vesuAdapter.config.debt,
|
|
975
|
+
maxInterestRate,
|
|
976
|
+
);
|
|
563
977
|
const debtCap = await vesuAdapter.getDebtCap(this.config);
|
|
564
978
|
|
|
565
979
|
if (currentDebt.gte(debtCap)) {
|
|
566
|
-
return {
|
|
980
|
+
return {
|
|
981
|
+
amount: Web3Number.fromWei("0", vesuAdapter.config.debt.decimals),
|
|
982
|
+
dexSwappableAmount: Web3Number.fromWei(
|
|
983
|
+
"0",
|
|
984
|
+
vesuAdapter.config.debt.decimals,
|
|
985
|
+
),
|
|
986
|
+
maxBorrowableAmount: Web3Number.fromWei(
|
|
987
|
+
"0",
|
|
988
|
+
vesuAdapter.config.debt.decimals,
|
|
989
|
+
),
|
|
990
|
+
borrowableAsset: vesuAdapter.config.debt,
|
|
991
|
+
};
|
|
567
992
|
}
|
|
568
993
|
|
|
569
994
|
const availableToBorrow = debtCap.minus(currentDebt);
|
|
570
995
|
|
|
571
|
-
const maxBorrowable = maxBorrowableAmount
|
|
996
|
+
const maxBorrowable = maxBorrowableAmount
|
|
997
|
+
.minimum(availableToBorrow)
|
|
998
|
+
.multipliedBy(0.999);
|
|
572
999
|
// Dont compute precise max swappable for APY computation
|
|
573
|
-
if (
|
|
574
|
-
|
|
1000
|
+
if (
|
|
1001
|
+
vesuAdapter.config.debt.address.eq(
|
|
1002
|
+
this.getLSTUnderlyingTokenInfo().address,
|
|
1003
|
+
) ||
|
|
1004
|
+
isAPYComputation
|
|
1005
|
+
) {
|
|
1006
|
+
return {
|
|
1007
|
+
amount: maxBorrowable,
|
|
1008
|
+
dexSwappableAmount: maxBorrowable,
|
|
1009
|
+
maxBorrowableAmount: maxBorrowable,
|
|
1010
|
+
borrowableAsset: vesuAdapter.config.debt,
|
|
1011
|
+
};
|
|
575
1012
|
}
|
|
576
1013
|
// Want < 0.02% slippage
|
|
577
1014
|
try {
|
|
578
|
-
const maxSwappable = await this.getMaxSwappableWithMaxSlippage(
|
|
579
|
-
|
|
1015
|
+
const maxSwappable = await this.getMaxSwappableWithMaxSlippage(
|
|
1016
|
+
vesuAdapter.config.debt,
|
|
1017
|
+
this.getLSTUnderlyingTokenInfo(),
|
|
1018
|
+
0.0002,
|
|
1019
|
+
maxBorrowable,
|
|
1020
|
+
);
|
|
1021
|
+
return {
|
|
1022
|
+
amount: maxBorrowable.minimum(maxSwappable),
|
|
1023
|
+
dexSwappableAmount: maxSwappable,
|
|
1024
|
+
maxBorrowableAmount: maxBorrowable,
|
|
1025
|
+
borrowableAsset: vesuAdapter.config.debt,
|
|
1026
|
+
};
|
|
580
1027
|
} catch (error) {
|
|
581
1028
|
logger.warn(`${this.getTag()}: Failed to get max swappable: ${error}`);
|
|
582
|
-
const maxSwappable = Web3Number.fromWei(
|
|
583
|
-
|
|
584
|
-
|
|
1029
|
+
const maxSwappable = Web3Number.fromWei(
|
|
1030
|
+
"0",
|
|
1031
|
+
vesuAdapter.config.debt.decimals,
|
|
1032
|
+
);
|
|
1033
|
+
return {
|
|
1034
|
+
amount: maxBorrowable.minimum(maxSwappable),
|
|
1035
|
+
dexSwappableAmount: maxSwappable,
|
|
1036
|
+
maxBorrowableAmount: maxBorrowable,
|
|
1037
|
+
borrowableAsset: vesuAdapter.config.debt,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
585
1040
|
}
|
|
586
1041
|
|
|
587
1042
|
// todo how much to unwind to get back healthy APY zone again
|
|
588
1043
|
// 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
|
-
|
|
1044
|
+
// For xSTRK, simply deposit in Vesu if looping is not viable
|
|
1045
|
+
|
|
591
1046
|
/**
|
|
592
1047
|
* Gets LST APR for the strategy's underlying asset from Endur API
|
|
593
1048
|
* @returns Promise<number> The LST APR (not divided by 1e18)
|
|
594
1049
|
*/
|
|
595
1050
|
async getLSTAPR(_address: ContractAddr): Promise<number> {
|
|
596
1051
|
try {
|
|
597
|
-
const vesuAdapter1 = this.getVesuSameTokenAdapter(
|
|
598
|
-
|
|
1052
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter(
|
|
1053
|
+
this.metadata.additionalInfo.defaultPoolId,
|
|
1054
|
+
);
|
|
1055
|
+
const apr = await LSTAPRService.getLSTAPR(
|
|
1056
|
+
vesuAdapter1.config.debt.address,
|
|
1057
|
+
);
|
|
599
1058
|
if (!apr) {
|
|
600
|
-
throw new Error(
|
|
1059
|
+
throw new Error("Failed to get LST APR");
|
|
601
1060
|
}
|
|
602
1061
|
return apr;
|
|
603
1062
|
} catch (error) {
|
|
@@ -607,88 +1066,170 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
607
1066
|
}
|
|
608
1067
|
|
|
609
1068
|
// todo undo this
|
|
610
|
-
async netAPY(): Promise<{
|
|
1069
|
+
async netAPY(): Promise<{
|
|
1070
|
+
net: number;
|
|
1071
|
+
splits: { apy: number; id: string }[];
|
|
1072
|
+
}> {
|
|
611
1073
|
const unusedBalance = await this.getUnusedBalance();
|
|
612
|
-
const maxNewDeposits = await this.maxNewDeposits({
|
|
613
|
-
|
|
1074
|
+
const maxNewDeposits = await this.maxNewDeposits({
|
|
1075
|
+
isAPYComputation: true,
|
|
1076
|
+
});
|
|
1077
|
+
const lstAPY = await this.getLSTAPR(
|
|
1078
|
+
this.getLSTUnderlyingTokenInfo().address,
|
|
1079
|
+
);
|
|
614
1080
|
|
|
615
1081
|
// if unused balance is > max servicable from loan, we are limited by the max borrowing we can do
|
|
616
1082
|
// we also allow accepting little higher deposits (1.5x) to have room for future looping when theres more liquidity or debt cap rises
|
|
617
1083
|
if (maxNewDeposits * 1.5 < unusedBalance.amount.toNumber()) {
|
|
618
1084
|
// we have excess, just use real APY
|
|
619
|
-
logger.verbose(
|
|
1085
|
+
logger.verbose(
|
|
1086
|
+
`${this.getTag()}::netAPY: unused balance is > max servicable from loan, lstAPY: ${lstAPY}`,
|
|
1087
|
+
);
|
|
620
1088
|
const output = await super.netAPY();
|
|
621
|
-
output.splits.push({apy: lstAPY, id:
|
|
1089
|
+
output.splits.push({ apy: lstAPY, id: "lst_apy" });
|
|
622
1090
|
return output;
|
|
623
1091
|
} else {
|
|
624
1092
|
// we have little bit room to accept more deposits, we use theoretical max APY
|
|
625
|
-
logger.verbose(
|
|
1093
|
+
logger.verbose(
|
|
1094
|
+
`${this.getTag()}::netAPY: we can take more deposits, use theoretical max APY`,
|
|
1095
|
+
);
|
|
626
1096
|
const { positions, baseAPYs, rewardAPYs } = await this.getVesuAPYs();
|
|
627
|
-
const weights = positions.map(
|
|
1097
|
+
const weights = positions.map(
|
|
1098
|
+
(p, index) => p.usdValue * (index % 2 == 0 ? 1 : -1),
|
|
1099
|
+
);
|
|
628
1100
|
const aum = weights.reduce((acc, curr) => acc + curr, 0);
|
|
629
|
-
const output = await this.returnNetAPY(
|
|
630
|
-
|
|
1101
|
+
const output = await this.returnNetAPY(
|
|
1102
|
+
baseAPYs,
|
|
1103
|
+
rewardAPYs,
|
|
1104
|
+
weights,
|
|
1105
|
+
new Web3Number(
|
|
1106
|
+
aum.toFixed(9),
|
|
1107
|
+
this.getLSTUnderlyingTokenInfo().decimals,
|
|
1108
|
+
),
|
|
1109
|
+
);
|
|
1110
|
+
output.splits.push({ apy: lstAPY, id: "lst_apy" });
|
|
631
1111
|
return output;
|
|
632
1112
|
}
|
|
633
1113
|
}
|
|
634
1114
|
|
|
635
|
-
async maxNewDeposits(
|
|
1115
|
+
async maxNewDeposits(
|
|
1116
|
+
params: { isAPYComputation: boolean } = { isAPYComputation: false },
|
|
1117
|
+
) {
|
|
636
1118
|
const maxBorrowableAmounts = await this.getMaxBorrowableAmount(params);
|
|
637
1119
|
let numerator = 0;
|
|
638
1120
|
|
|
639
1121
|
let ltv: number | undefined = undefined;
|
|
640
1122
|
for (let adapter of this.getVesuAdapters()) {
|
|
641
|
-
const maxBorrowableAmountInfo = maxBorrowableAmounts.maxBorrowables.find(
|
|
1123
|
+
const maxBorrowableAmountInfo = maxBorrowableAmounts.maxBorrowables.find(
|
|
1124
|
+
(b) => b.borrowableAsset.address.eq(adapter.config.debt.address),
|
|
1125
|
+
);
|
|
642
1126
|
if (!maxBorrowableAmountInfo || !maxBorrowableAmountInfo?.amount) {
|
|
643
|
-
throw new Error(
|
|
1127
|
+
throw new Error(
|
|
1128
|
+
`Max borrowable amount not found for adapter: ${adapter.config.debt.symbol}`,
|
|
1129
|
+
);
|
|
644
1130
|
}
|
|
645
1131
|
|
|
646
|
-
numerator +=
|
|
1132
|
+
numerator +=
|
|
1133
|
+
(this.metadata.additionalInfo.targetHealthFactor *
|
|
1134
|
+
maxBorrowableAmountInfo.amount.toNumber()) /
|
|
1135
|
+
maxBorrowableAmountInfo.ltv;
|
|
647
1136
|
}
|
|
648
1137
|
|
|
649
|
-
return
|
|
1138
|
+
return numerator - maxBorrowableAmounts.netMaxBorrowableAmount.toNumber();
|
|
650
1139
|
}
|
|
651
1140
|
|
|
652
1141
|
// todo revisit cases where 0th adapters is used
|
|
653
1142
|
protected async getUnusedBalanceAPY() {
|
|
654
1143
|
const unusedBalance = await this.getUnusedBalance();
|
|
655
|
-
const vesuAdapter = this.getVesuSameTokenAdapter(
|
|
1144
|
+
const vesuAdapter = this.getVesuSameTokenAdapter(
|
|
1145
|
+
this.metadata.additionalInfo.defaultPoolId,
|
|
1146
|
+
);
|
|
656
1147
|
const underlying = vesuAdapter.config.debt;
|
|
657
1148
|
const lstAPY = await this.getLSTAPR(underlying.address);
|
|
658
1149
|
return {
|
|
659
|
-
apy: lstAPY,
|
|
660
|
-
|
|
1150
|
+
apy: lstAPY,
|
|
1151
|
+
weight: unusedBalance.usdValue,
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async getLSTAvnuRate() {
|
|
1156
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter(
|
|
1157
|
+
this.metadata.additionalInfo.defaultPoolId,
|
|
1158
|
+
);
|
|
1159
|
+
const lstTokenInfo = vesuAdapter1.config.collateral;
|
|
1160
|
+
const underlyingTokenInfo = vesuAdapter1.config.debt;
|
|
1161
|
+
|
|
1162
|
+
const avnuModule = new AvnuWrapper();
|
|
1163
|
+
|
|
1164
|
+
const sellAmount = lstTokenInfo.priceCheckAmount
|
|
1165
|
+
? new Web3Number(
|
|
1166
|
+
lstTokenInfo.priceCheckAmount,
|
|
1167
|
+
underlyingTokenInfo.decimals,
|
|
1168
|
+
)
|
|
1169
|
+
: new Web3Number(1, underlyingTokenInfo.decimals);
|
|
1170
|
+
|
|
1171
|
+
const quote = await avnuModule.getQuotes(
|
|
1172
|
+
underlyingTokenInfo.address.address,
|
|
1173
|
+
lstTokenInfo.address.address,
|
|
1174
|
+
sellAmount.toWei(),
|
|
1175
|
+
this.metadata.additionalInfo.vaultAllocator.address,
|
|
1176
|
+
);
|
|
1177
|
+
|
|
1178
|
+
const underlyingAmountNumber = sellAmount.toNumber();
|
|
1179
|
+
const lstAmountNumber = Web3Number.fromWei(
|
|
1180
|
+
quote.buyAmount.toString(),
|
|
1181
|
+
lstTokenInfo.decimals,
|
|
1182
|
+
).toNumber();
|
|
1183
|
+
|
|
1184
|
+
assert(lstAmountNumber > 0, "Avnu LST amount is zero");
|
|
1185
|
+
|
|
1186
|
+
const exchangeRate = underlyingAmountNumber / lstAmountNumber;
|
|
1187
|
+
logger.verbose(
|
|
1188
|
+
`${this.getTag()}:: LST Avnu Exchange Rate: ${exchangeRate}`,
|
|
1189
|
+
);
|
|
1190
|
+
return exchangeRate;
|
|
661
1191
|
}
|
|
662
1192
|
|
|
663
1193
|
async getLSTExchangeRate() {
|
|
664
|
-
const vesuAdapter1 = this.getVesuSameTokenAdapter(
|
|
1194
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter(
|
|
1195
|
+
this.metadata.additionalInfo.defaultPoolId,
|
|
1196
|
+
);
|
|
665
1197
|
const lstTokenInfo = vesuAdapter1.config.collateral;
|
|
666
1198
|
const lstABI = new Contract({
|
|
667
1199
|
abi: ERC4626Abi,
|
|
668
1200
|
address: lstTokenInfo.address.address,
|
|
669
|
-
providerOrAccount: this.config.provider
|
|
1201
|
+
providerOrAccount: this.config.provider,
|
|
670
1202
|
});
|
|
671
1203
|
|
|
672
|
-
const price: any = await lstABI.call(
|
|
673
|
-
|
|
1204
|
+
const price: any = await lstABI.call("convert_to_assets", [
|
|
1205
|
+
uint256.bnToUint256(new Web3Number(1, lstTokenInfo.decimals).toWei()),
|
|
1206
|
+
]);
|
|
1207
|
+
const exchangeRate =
|
|
1208
|
+
Number(uint256.uint256ToBN(price).toString()) /
|
|
1209
|
+
Math.pow(10, lstTokenInfo.decimals);
|
|
674
1210
|
logger.verbose(`${this.getTag()}:: LST Exchange Rate: ${exchangeRate}`);
|
|
675
1211
|
return exchangeRate;
|
|
676
|
-
|
|
1212
|
+
}
|
|
677
1213
|
|
|
678
1214
|
/**
|
|
679
|
-
*
|
|
1215
|
+
*
|
|
680
1216
|
* @param params marginAmount is in LST, debtAmount is in underlying
|
|
681
1217
|
*/
|
|
682
1218
|
async getModifyLeverCall(params: {
|
|
683
|
-
marginAmount: Web3Number
|
|
684
|
-
debtAmount: Web3Number
|
|
685
|
-
lstDexPriceInUnderlying: number
|
|
686
|
-
isIncrease: boolean
|
|
687
|
-
maxEkuboPriceImpact: number
|
|
688
|
-
poolId: ContractAddr
|
|
1219
|
+
marginAmount: Web3Number; // >0 during deposit
|
|
1220
|
+
debtAmount: Web3Number;
|
|
1221
|
+
lstDexPriceInUnderlying: number;
|
|
1222
|
+
isIncrease: boolean;
|
|
1223
|
+
maxEkuboPriceImpact: number;
|
|
1224
|
+
poolId: ContractAddr;
|
|
689
1225
|
}): Promise<Call[]> {
|
|
690
|
-
logger.verbose(
|
|
691
|
-
|
|
1226
|
+
logger.verbose(
|
|
1227
|
+
`${this.getTag()}::getModifyLeverCall marginAmount: ${params.marginAmount}, debtAmount: ${params.debtAmount}, lstDexPriceInUnderlying: ${params.lstDexPriceInUnderlying}, isIncrease: ${params.isIncrease}`,
|
|
1228
|
+
);
|
|
1229
|
+
assert(
|
|
1230
|
+
!params.marginAmount.isZero() || !params.debtAmount.isZero(),
|
|
1231
|
+
"Deposit/debt must be non-0",
|
|
1232
|
+
);
|
|
692
1233
|
|
|
693
1234
|
const vesuAdapter1 = this.getVesuSameTokenAdapter(params.poolId);
|
|
694
1235
|
const lstTokenInfo = this.asset();
|
|
@@ -696,9 +1237,11 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
696
1237
|
|
|
697
1238
|
// todo make it more general
|
|
698
1239
|
// 500k STRK (~75k$) or 0.5 BTC (~60k$)
|
|
699
|
-
const maxAmounts = lstTokenInfo.symbol ==
|
|
1240
|
+
const maxAmounts = lstTokenInfo.symbol == "xSTRK" ? 500000 : 0.5;
|
|
700
1241
|
if (params.marginAmount.greaterThan(maxAmounts)) {
|
|
701
|
-
throw new Error(
|
|
1242
|
+
throw new Error(
|
|
1243
|
+
`Margin amount is greater than max amount: ${params.marginAmount.toNumber()} > ${maxAmounts}`,
|
|
1244
|
+
);
|
|
702
1245
|
}
|
|
703
1246
|
|
|
704
1247
|
const proofsIDs: string[] = [];
|
|
@@ -706,12 +1249,15 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
706
1249
|
|
|
707
1250
|
// approve token
|
|
708
1251
|
if (params.marginAmount.greaterThan(0)) {
|
|
709
|
-
const STEP1_ID = getVesuGenericLegId(
|
|
1252
|
+
const STEP1_ID = getVesuGenericLegId(
|
|
1253
|
+
params.poolId.toString(),
|
|
1254
|
+
LST_MULTIPLIER_MANAGE_IDS.MULTIPLE_APPROVE,
|
|
1255
|
+
);
|
|
710
1256
|
const manage1Info = this.getProofs<ApproveCallParams>(STEP1_ID);
|
|
711
1257
|
const depositAmount = params.marginAmount;
|
|
712
1258
|
const manageCall1 = manage1Info.callConstructor({
|
|
713
|
-
|
|
714
|
-
})
|
|
1259
|
+
amount: depositAmount,
|
|
1260
|
+
});
|
|
715
1261
|
proofsIDs.push(STEP1_ID);
|
|
716
1262
|
manageCalls.push(manageCall1);
|
|
717
1263
|
}
|
|
@@ -728,75 +1274,119 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
728
1274
|
const fromToken = params.isIncrease ? lstUnderlyingTokenInfo : lstTokenInfo;
|
|
729
1275
|
const toToken = params.isIncrease ? lstTokenInfo : lstUnderlyingTokenInfo;
|
|
730
1276
|
const leverSwapQuote = await ekuboQuoter.getQuote(
|
|
731
|
-
fromToken.address.address,
|
|
732
|
-
toToken.address.address,
|
|
733
|
-
params.debtAmount // negative for exact amount out
|
|
1277
|
+
fromToken.address.address,
|
|
1278
|
+
toToken.address.address,
|
|
1279
|
+
params.debtAmount, // negative for exact amount out
|
|
1280
|
+
);
|
|
1281
|
+
logger.verbose(
|
|
1282
|
+
`${this.getTag()}::getModifyLeverCall leverSwapQuote: ${JSON.stringify(leverSwapQuote)}`,
|
|
1283
|
+
);
|
|
1284
|
+
// Ekubo's price impact can randomly show high numbers sometimes.
|
|
1285
|
+
assert(
|
|
1286
|
+
leverSwapQuote.price_impact <= params.maxEkuboPriceImpact,
|
|
1287
|
+
"getIncreaseLeverCall: Price impact is too high [Debt swap]",
|
|
1288
|
+
);
|
|
1289
|
+
const leverSwap = ekuboQuoter.getVesuMultiplyQuote(
|
|
1290
|
+
leverSwapQuote,
|
|
1291
|
+
fromToken,
|
|
1292
|
+
toToken,
|
|
1293
|
+
);
|
|
1294
|
+
logger.verbose(
|
|
1295
|
+
`${this.getTag()}::getModifyLeverCall leverSwap: ${JSON.stringify(leverSwap)}`,
|
|
734
1296
|
);
|
|
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
1297
|
|
|
741
1298
|
// todo double check this logic
|
|
742
1299
|
// is Deposit
|
|
743
|
-
let minLSTReceived = params.debtAmount
|
|
744
|
-
|
|
1300
|
+
let minLSTReceived = params.debtAmount
|
|
1301
|
+
.dividedBy(lstDexPriceInUnderlying)
|
|
1302
|
+
.multipliedBy(1 - MAX_SLIPPAGE); // used for increase
|
|
1303
|
+
const minLSTReceivedAsPerTruePrice =
|
|
1304
|
+
params.debtAmount.dividedBy(lstTrueExchangeRate); // execution output to be <= True LST price
|
|
745
1305
|
// if (minLSTReceived < minLSTReceivedAsPerTruePrice) {
|
|
746
1306
|
// minLSTReceived = minLSTReceivedAsPerTruePrice; // the execution shouldn't be bad than True price logi
|
|
747
1307
|
// }
|
|
748
1308
|
minLSTReceived = minLSTReceivedAsPerTruePrice; // in any case, we are ok with this, bcz the BTC LST spread shouldnt be high
|
|
749
|
-
logger.verbose(
|
|
1309
|
+
logger.verbose(
|
|
1310
|
+
`${this.getTag()}::getModifyLeverCall minLSTReceivedAsPerTruePrice: ${minLSTReceivedAsPerTruePrice}, minLSTReceived: ${minLSTReceived}`,
|
|
1311
|
+
);
|
|
750
1312
|
|
|
751
1313
|
// is withdraw
|
|
752
|
-
let maxUsedCollateral = params.debtAmount
|
|
753
|
-
|
|
754
|
-
|
|
1314
|
+
let maxUsedCollateral = params.debtAmount
|
|
1315
|
+
.abs()
|
|
1316
|
+
.dividedBy(lstDexPriceInUnderlying)
|
|
1317
|
+
.multipliedBy(1 + MAX_SLIPPAGE); // +ve for exact amount out, used for decrease
|
|
1318
|
+
const maxUsedCollateralInLST = params.debtAmount
|
|
1319
|
+
.abs()
|
|
1320
|
+
.dividedBy(lstTrueExchangeRate)
|
|
1321
|
+
.multipliedBy(1.005); // 0.5% slippage, worst case based on true price
|
|
1322
|
+
logger.verbose(
|
|
1323
|
+
`${this.getTag()}::getModifyLeverCall maxUsedCollateralInLST: ${maxUsedCollateralInLST}, maxUsedCollateral: ${maxUsedCollateral}`,
|
|
1324
|
+
);
|
|
755
1325
|
// if (maxUsedCollateralInLST > maxUsedCollateral) {
|
|
756
1326
|
// maxUsedCollateral = maxUsedCollateralInLST;
|
|
757
1327
|
// }
|
|
758
1328
|
maxUsedCollateral = maxUsedCollateralInLST; // in any case, we are ok with this, bcz the BTC LST spread shouldnt be high
|
|
759
1329
|
|
|
760
|
-
const STEP2_ID = getVesuGenericLegId(
|
|
761
|
-
|
|
1330
|
+
const STEP2_ID = getVesuGenericLegId(
|
|
1331
|
+
params.poolId.toString(),
|
|
1332
|
+
LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_ON,
|
|
1333
|
+
);
|
|
1334
|
+
const manage2Info =
|
|
1335
|
+
this.getProofs<VesuModifyDelegationCallParams>(STEP2_ID);
|
|
762
1336
|
const manageCall2 = manage2Info.callConstructor({
|
|
763
|
-
delegation: true
|
|
1337
|
+
delegation: true,
|
|
764
1338
|
});
|
|
765
1339
|
|
|
766
1340
|
// deposit and borrow or repay and withdraw
|
|
767
|
-
const STEP3_ID = getVesuLegId(
|
|
1341
|
+
const STEP3_ID = getVesuLegId(
|
|
1342
|
+
LST_MULTIPLIER_MANAGE_IDS.MULTIPLY_VESU,
|
|
1343
|
+
vesuAdapter1.config.debt.symbol,
|
|
1344
|
+
vesuAdapter1.config.poolId.toString(),
|
|
1345
|
+
);
|
|
768
1346
|
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
|
-
|
|
1347
|
+
const multiplyParams: VesuMultiplyCallParams = params.isIncrease
|
|
1348
|
+
? {
|
|
1349
|
+
isIncrease: true,
|
|
1350
|
+
increaseParams: {
|
|
1351
|
+
add_margin: params.marginAmount,
|
|
1352
|
+
margin_swap: [],
|
|
1353
|
+
margin_swap_limit_amount: Web3Number.fromWei(
|
|
1354
|
+
0,
|
|
1355
|
+
this.asset().decimals,
|
|
1356
|
+
),
|
|
1357
|
+
lever_swap: leverSwap,
|
|
1358
|
+
lever_swap_limit_amount: minLSTReceived,
|
|
1359
|
+
},
|
|
1360
|
+
}
|
|
1361
|
+
: {
|
|
1362
|
+
isIncrease: false,
|
|
1363
|
+
decreaseParams: {
|
|
1364
|
+
sub_margin: params.marginAmount.multipliedBy(-1),
|
|
1365
|
+
lever_swap: leverSwap,
|
|
1366
|
+
lever_swap_limit_amount: maxUsedCollateral,
|
|
1367
|
+
// only required for close position
|
|
1368
|
+
lever_swap_weights: [],
|
|
1369
|
+
// no need to swap collateral to anything, and any residuals return our contract anyways.
|
|
1370
|
+
withdraw_swap: [],
|
|
1371
|
+
withdraw_swap_limit_amount: Web3Number.fromWei(
|
|
1372
|
+
0,
|
|
1373
|
+
this.asset().decimals,
|
|
1374
|
+
),
|
|
1375
|
+
withdraw_swap_weights: [],
|
|
1376
|
+
close_position: false,
|
|
1377
|
+
},
|
|
1378
|
+
};
|
|
793
1379
|
const manageCall3 = manage3Info.callConstructor(multiplyParams);
|
|
794
1380
|
|
|
795
1381
|
// switch delegation off
|
|
796
|
-
const STEP4_ID = getVesuGenericLegId(
|
|
797
|
-
|
|
1382
|
+
const STEP4_ID = getVesuGenericLegId(
|
|
1383
|
+
params.poolId.toString(),
|
|
1384
|
+
LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_OFF,
|
|
1385
|
+
);
|
|
1386
|
+
const manage4Info =
|
|
1387
|
+
this.getProofs<VesuModifyDelegationCallParams>(STEP4_ID);
|
|
798
1388
|
const manageCall4 = manage4Info.callConstructor({
|
|
799
|
-
delegation: false
|
|
1389
|
+
delegation: false,
|
|
800
1390
|
});
|
|
801
1391
|
|
|
802
1392
|
proofsIDs.push(STEP2_ID, STEP3_ID, STEP4_ID);
|
|
@@ -808,12 +1398,11 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
808
1398
|
|
|
809
1399
|
export default function VaultDescription(
|
|
810
1400
|
lstSymbol: string,
|
|
811
|
-
underlyingSymbol: string
|
|
1401
|
+
underlyingSymbol: string,
|
|
812
1402
|
) {
|
|
813
1403
|
const containerStyle = {
|
|
814
1404
|
maxWidth: "800px",
|
|
815
1405
|
margin: "0 auto",
|
|
816
|
-
backgroundColor: "#111",
|
|
817
1406
|
color: "#eee",
|
|
818
1407
|
fontFamily: "Arial, sans-serif",
|
|
819
1408
|
borderRadius: "12px",
|
|
@@ -821,26 +1410,64 @@ export default function VaultDescription(
|
|
|
821
1410
|
|
|
822
1411
|
return (
|
|
823
1412
|
<div style={containerStyle}>
|
|
824
|
-
<h1 style={{ fontSize: "18px", marginBottom: "10px" }}>
|
|
1413
|
+
<h1 style={{ fontSize: "18px", marginBottom: "10px" }}>
|
|
1414
|
+
Liquidation risk managed leverged {lstSymbol} Vault
|
|
1415
|
+
</h1>
|
|
825
1416
|
<p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
|
|
826
|
-
This Levered Endur {lstSymbol} vault is a tokenized leveraged Vault,
|
|
827
|
-
|
|
828
|
-
|
|
1417
|
+
This Levered Endur {lstSymbol} vault is a tokenized leveraged Vault,
|
|
1418
|
+
auto-compounding strategy that takes upto 5x leverage on {lstSymbol} by
|
|
1419
|
+
borrow {underlyingSymbol}. Borrowed amount is swapped to {lstSymbol} to
|
|
1420
|
+
create leverage. Depositors receive vault shares that represent a
|
|
1421
|
+
proportional claim on the underlying assets and accrued yield.
|
|
829
1422
|
</p>
|
|
830
1423
|
|
|
831
1424
|
<p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
|
|
832
|
-
This vault uses Vesu for lending and borrowing. The oracle used by this
|
|
833
|
-
{" "}
|
|
1425
|
+
This vault uses Vesu for lending and borrowing. The oracle used by this
|
|
1426
|
+
pool is a{" "}
|
|
1427
|
+
{highlightTextWithLinks("conversion rate oracle", [
|
|
1428
|
+
{
|
|
1429
|
+
highlight: "conversion rate oracle",
|
|
1430
|
+
link: "https://docs.pragma.build/starknet/development#conversion-rate",
|
|
1431
|
+
},
|
|
1432
|
+
])}{" "}
|
|
1433
|
+
which is resilient to liquidity issues and price volatility, hence
|
|
1434
|
+
reducing the risk of liquidation. However, overtime, if left
|
|
1435
|
+
un-monitored, debt can increase enough to trigger a liquidation. But no
|
|
1436
|
+
worries, our continuous monitoring systems look for situations with
|
|
1437
|
+
reduced health factor and balance collateral/debt to bring it back to
|
|
1438
|
+
safe levels. With Troves, you can have a peaceful sleep.
|
|
834
1439
|
</p>
|
|
835
1440
|
|
|
836
|
-
<div
|
|
1441
|
+
<div
|
|
1442
|
+
style={{
|
|
1443
|
+
backgroundColor: "#222",
|
|
1444
|
+
padding: "10px",
|
|
1445
|
+
borderRadius: "8px",
|
|
1446
|
+
marginBottom: "20px",
|
|
1447
|
+
border: "1px solid #444",
|
|
1448
|
+
}}
|
|
1449
|
+
>
|
|
837
1450
|
<p style={{ fontSize: "13px", color: "#ccc" }}>
|
|
838
|
-
<strong>Withdrawals:</strong> Requests can take up to
|
|
1451
|
+
<strong>Withdrawals:</strong> Requests can take up to{" "}
|
|
1452
|
+
<strong>1-2 hours</strong> to process as the vault unwinds and settles
|
|
1453
|
+
routing.
|
|
839
1454
|
</p>
|
|
840
1455
|
</div>
|
|
841
|
-
<div
|
|
1456
|
+
<div
|
|
1457
|
+
style={{
|
|
1458
|
+
backgroundColor: "#222",
|
|
1459
|
+
padding: "10px",
|
|
1460
|
+
borderRadius: "8px",
|
|
1461
|
+
marginBottom: "20px",
|
|
1462
|
+
border: "1px solid #444",
|
|
1463
|
+
}}
|
|
1464
|
+
>
|
|
842
1465
|
<p style={{ fontSize: "13px", color: "#ccc" }}>
|
|
843
|
-
<strong>Debt limits:</strong> Pools on Vesu have debt caps that are
|
|
1466
|
+
<strong>Debt limits:</strong> Pools on Vesu have debt caps that are
|
|
1467
|
+
gradually increased over time. Until caps are raised, deposited LSTs
|
|
1468
|
+
remain in the vault, generating a shared net return for all
|
|
1469
|
+
depositors. There is no additional fee taken by Troves on LST APY, its
|
|
1470
|
+
only on added gain.
|
|
844
1471
|
</p>
|
|
845
1472
|
</div>
|
|
846
1473
|
{/* <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
|
|
@@ -852,24 +1479,25 @@ export default function VaultDescription(
|
|
|
852
1479
|
);
|
|
853
1480
|
}
|
|
854
1481
|
|
|
855
|
-
|
|
856
1482
|
function getDescription(tokenSymbol: string, underlyingSymbol: string) {
|
|
857
1483
|
return VaultDescription(tokenSymbol, underlyingSymbol);
|
|
858
1484
|
}
|
|
859
1485
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
AVNU_MULTIPLY_SWAP_WITHDRAW = 'avnu_mul_swap_withdr',
|
|
1486
|
+
enum LST_MULTIPLIER_MANAGE_IDS {
|
|
1487
|
+
MULTIPLE_APPROVE = "multiple_approve",
|
|
1488
|
+
MULTIPLY_VESU = "multiply_vesu",
|
|
1489
|
+
SWITCH_DELEGATION_ON = "switch_delegation_on",
|
|
1490
|
+
SWITCH_DELEGATION_OFF = "switch_delegation_off",
|
|
1491
|
+
AVNU_MULTIPLY_APPROVE_DEPOSIT = "avnu_mul_approve_dep",
|
|
1492
|
+
AVNU_MULTIPLY_SWAP_DEPOSIT = "avnu_mul_swap_dep",
|
|
1493
|
+
AVNU_MULTIPLY_APPROVE_WITHDRAW = "avnu_mul_approve_withdr",
|
|
1494
|
+
AVNU_MULTIPLY_SWAP_WITHDRAW = "avnu_mul_swap_withdr",
|
|
870
1495
|
}
|
|
871
1496
|
|
|
872
|
-
function getAvnuManageIDs(
|
|
1497
|
+
function getAvnuManageIDs(
|
|
1498
|
+
baseID: LST_MULTIPLIER_MANAGE_IDS,
|
|
1499
|
+
debtTokenSymbol: string,
|
|
1500
|
+
) {
|
|
873
1501
|
return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
|
|
874
1502
|
}
|
|
875
1503
|
|
|
@@ -881,6 +1509,7 @@ function getVesuLegId(baseID: string, debtTokenSymbol: string, poolId: string) {
|
|
|
881
1509
|
return `${baseID}_${debtTokenSymbol.toLowerCase()}_${poolId.slice(-4).toLowerCase()}`;
|
|
882
1510
|
}
|
|
883
1511
|
|
|
1512
|
+
|
|
884
1513
|
function addVesuLeaves(
|
|
885
1514
|
poolId: ContractAddr,
|
|
886
1515
|
lstSymbol: string,
|
|
@@ -888,42 +1517,100 @@ function addVesuLeaves(
|
|
|
888
1517
|
vaultSettings: HyperLSTStrategySettings,
|
|
889
1518
|
commonAdapter: CommonAdapter,
|
|
890
1519
|
) {
|
|
891
|
-
const lstToken = Global.getDefaultTokens().find(
|
|
892
|
-
|
|
1520
|
+
const lstToken = Global.getDefaultTokens().find(
|
|
1521
|
+
(token) => token.symbol === lstSymbol,
|
|
1522
|
+
)!;
|
|
1523
|
+
const underlyingToken = Global.getDefaultTokens().find(
|
|
1524
|
+
(token) => token.symbol === underlyingSymbol,
|
|
1525
|
+
)!;
|
|
893
1526
|
|
|
894
1527
|
const vesuAdapterLST = new VesuAdapter({
|
|
895
1528
|
poolId: poolId,
|
|
896
1529
|
collateral: lstToken,
|
|
897
1530
|
debt: underlyingToken,
|
|
898
1531
|
vaultAllocator: vaultSettings.vaultAllocator,
|
|
899
|
-
id: getVesuLegId(
|
|
1532
|
+
id: getVesuLegId(
|
|
1533
|
+
UNIVERSAL_MANAGE_IDS.VESU_LEG1,
|
|
1534
|
+
underlyingToken.symbol,
|
|
1535
|
+
poolId.toString(),
|
|
1536
|
+
),
|
|
900
1537
|
});
|
|
901
1538
|
|
|
902
1539
|
// Useful for returning adapter class objects that can compute
|
|
903
1540
|
// certain things for us (e.g. positions, hfs)
|
|
904
|
-
vaultSettings.adapters.push(
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1541
|
+
vaultSettings.adapters.push(
|
|
1542
|
+
...[
|
|
1543
|
+
{
|
|
1544
|
+
id: getVesuLegId(
|
|
1545
|
+
UNIVERSAL_MANAGE_IDS.VESU_LEG1,
|
|
1546
|
+
underlyingToken.symbol,
|
|
1547
|
+
poolId.toString(),
|
|
1548
|
+
),
|
|
1549
|
+
adapter: vesuAdapterLST,
|
|
1550
|
+
},
|
|
1551
|
+
],
|
|
1552
|
+
);
|
|
908
1553
|
|
|
909
1554
|
// avnu multiply
|
|
910
|
-
const { isV2, addr:
|
|
1555
|
+
const { isV2, addr: poolAddr } = getVesuSingletonAddress(poolId);
|
|
911
1556
|
|
|
912
1557
|
// vesu multiply looping
|
|
913
|
-
const VESU_MULTIPLY = isV2
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1558
|
+
const VESU_MULTIPLY = isV2
|
|
1559
|
+
? vesuAdapterLST.VESU_MULTIPLY
|
|
1560
|
+
: vesuAdapterLST.VESU_MULTIPLY_V1;
|
|
1561
|
+
|
|
1562
|
+
const leafIdApprove = getVesuGenericLegId(
|
|
1563
|
+
poolId.toString(),
|
|
1564
|
+
LST_MULTIPLIER_MANAGE_IDS.MULTIPLE_APPROVE,
|
|
1565
|
+
);
|
|
1566
|
+
vaultSettings.leafAdapters.push(
|
|
1567
|
+
commonAdapter
|
|
1568
|
+
.getApproveAdapter(lstToken.address, VESU_MULTIPLY, leafIdApprove)
|
|
1569
|
+
.bind(commonAdapter),
|
|
1570
|
+
);
|
|
1571
|
+
|
|
1572
|
+
const leafIdDelegationOn = getVesuGenericLegId(
|
|
1573
|
+
poolId.toString(),
|
|
1574
|
+
LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_ON,
|
|
1575
|
+
);
|
|
1576
|
+
vaultSettings.leafAdapters.push(
|
|
1577
|
+
vesuAdapterLST
|
|
1578
|
+
.getVesuModifyDelegationAdapter(leafIdDelegationOn, VESU_MULTIPLY)
|
|
1579
|
+
.bind(vesuAdapterLST),
|
|
1580
|
+
);
|
|
1581
|
+
|
|
1582
|
+
const leafIdDelegationOff = getVesuGenericLegId(
|
|
1583
|
+
poolId.toString(),
|
|
1584
|
+
LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_OFF,
|
|
1585
|
+
);
|
|
1586
|
+
vaultSettings.leafAdapters.push(
|
|
1587
|
+
vesuAdapterLST
|
|
1588
|
+
.getVesuModifyDelegationAdapter(leafIdDelegationOff, VESU_MULTIPLY)
|
|
1589
|
+
.bind(vesuAdapterLST),
|
|
1590
|
+
);
|
|
1591
|
+
|
|
1592
|
+
const multiplID = getVesuLegId(
|
|
1593
|
+
LST_MULTIPLIER_MANAGE_IDS.MULTIPLY_VESU,
|
|
1594
|
+
underlyingToken.symbol,
|
|
1595
|
+
poolId.toString(),
|
|
1596
|
+
);
|
|
1597
|
+
vaultSettings.leafAdapters.push(
|
|
1598
|
+
vesuAdapterLST.getMultiplyAdapter(multiplID).bind(vesuAdapterLST),
|
|
1599
|
+
);
|
|
1600
|
+
|
|
1601
|
+
// direct modify position stuff
|
|
1602
|
+
vaultSettings.leafAdapters.push(
|
|
1603
|
+
commonAdapter
|
|
1604
|
+
.getApproveAdapter(
|
|
1605
|
+
lstToken.address,
|
|
1606
|
+
poolAddr,
|
|
1607
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1,
|
|
1608
|
+
)
|
|
1609
|
+
.bind(commonAdapter),
|
|
1610
|
+
);
|
|
1611
|
+
vaultSettings.leafAdapters.push(
|
|
1612
|
+
vesuAdapterLST.getModifyPosition.bind(vesuAdapterLST),
|
|
1613
|
+
);
|
|
927
1614
|
return vesuAdapterLST;
|
|
928
1615
|
}
|
|
929
1616
|
|
|
@@ -932,7 +1619,6 @@ function getLooperSettings(
|
|
|
932
1619
|
underlyingSymbol: string,
|
|
933
1620
|
vaultSettings: HyperLSTStrategySettings,
|
|
934
1621
|
defaultPoolId: ContractAddr,
|
|
935
|
-
altSupportedPoolIds: ContractAddr[] = []
|
|
936
1622
|
) {
|
|
937
1623
|
vaultSettings.leafAdapters = [];
|
|
938
1624
|
const pool1 = vaultSettings.defaultPoolId;
|
|
@@ -940,99 +1626,179 @@ function getLooperSettings(
|
|
|
940
1626
|
throw new Error(`Dont include default pool id in supported pool ids`);
|
|
941
1627
|
}
|
|
942
1628
|
|
|
943
|
-
const lstToken = Global.getDefaultTokens().find(
|
|
944
|
-
|
|
1629
|
+
const lstToken = Global.getDefaultTokens().find(
|
|
1630
|
+
(token) => token.symbol === lstSymbol,
|
|
1631
|
+
)!;
|
|
1632
|
+
const underlyingToken = Global.getDefaultTokens().find(
|
|
1633
|
+
(token) => token.symbol === underlyingSymbol,
|
|
1634
|
+
)!;
|
|
945
1635
|
|
|
946
1636
|
const commonAdapter = new CommonAdapter({
|
|
947
1637
|
manager: vaultSettings.manager,
|
|
948
1638
|
asset: lstToken.address,
|
|
949
|
-
id:
|
|
1639
|
+
id: "",
|
|
950
1640
|
vaultAddress: vaultSettings.vaultAddress,
|
|
951
1641
|
vaultAllocator: vaultSettings.vaultAllocator,
|
|
952
1642
|
});
|
|
953
1643
|
|
|
954
1644
|
// add common adapter to adapters
|
|
955
|
-
vaultSettings.adapters.push(
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1645
|
+
vaultSettings.adapters.push(
|
|
1646
|
+
...[
|
|
1647
|
+
{
|
|
1648
|
+
id: UNIVERSAL_ADAPTERS.COMMON,
|
|
1649
|
+
adapter: commonAdapter,
|
|
1650
|
+
},
|
|
1651
|
+
],
|
|
1652
|
+
);
|
|
959
1653
|
|
|
960
|
-
// add vesu leaves for
|
|
961
|
-
|
|
1654
|
+
// add vesu leaves for all supported pool ids
|
|
1655
|
+
vaultSettings.borrowable_assets.map((borrowableAsset) =>
|
|
1656
|
+
addVesuLeaves(
|
|
1657
|
+
borrowableAsset.poolId,
|
|
1658
|
+
lstSymbol,
|
|
1659
|
+
underlyingSymbol,
|
|
1660
|
+
vaultSettings,
|
|
1661
|
+
commonAdapter,
|
|
1662
|
+
),
|
|
1663
|
+
);
|
|
962
1664
|
|
|
963
|
-
// add vesu leaves for alt supported pool ids
|
|
964
|
-
altSupportedPoolIds.map(poolId => addVesuLeaves(poolId, lstSymbol, underlyingSymbol, vaultSettings, commonAdapter));
|
|
965
|
-
|
|
966
1665
|
// approve lst once to avnu
|
|
967
|
-
vaultSettings.leafAdapters.push(
|
|
968
|
-
|
|
1666
|
+
vaultSettings.leafAdapters.push(
|
|
1667
|
+
commonAdapter
|
|
1668
|
+
.getApproveAdapter(
|
|
1669
|
+
lstToken.address,
|
|
1670
|
+
AVNU_EXCHANGE,
|
|
1671
|
+
LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_WITHDRAW,
|
|
1672
|
+
)
|
|
1673
|
+
.bind(commonAdapter),
|
|
1674
|
+
);
|
|
1675
|
+
|
|
1676
|
+
const uniqueBorrowableAssets = [
|
|
1677
|
+
...new Set(
|
|
1678
|
+
vaultSettings.borrowable_assets.map(
|
|
1679
|
+
(borrowableAsset) => borrowableAsset.token.symbol,
|
|
1680
|
+
),
|
|
1681
|
+
),
|
|
1682
|
+
];
|
|
1683
|
+
for (let borrowableAssetSymbol of uniqueBorrowableAssets) {
|
|
1684
|
+
const borrowableAsset = Global.getDefaultTokens().find(
|
|
1685
|
+
(token) => token.symbol === borrowableAssetSymbol,
|
|
1686
|
+
)!;
|
|
969
1687
|
// in-efficient avnu swap looping (but good with endur integration)
|
|
970
1688
|
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
|
-
|
|
1689
|
+
const approve_debt_token_id = getAvnuManageIDs(
|
|
1690
|
+
LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT,
|
|
1691
|
+
debtAsset.symbol,
|
|
1692
|
+
);
|
|
1693
|
+
const swap_debt_token_id = getAvnuManageIDs(
|
|
1694
|
+
LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT,
|
|
1695
|
+
debtAsset.symbol,
|
|
1696
|
+
);
|
|
1697
|
+
const swap_lst_token_id = getAvnuManageIDs(
|
|
1698
|
+
LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_WITHDRAW,
|
|
1699
|
+
debtAsset.symbol,
|
|
1700
|
+
);
|
|
1701
|
+
vaultSettings.leafAdapters.push(
|
|
1702
|
+
commonAdapter
|
|
1703
|
+
.getApproveAdapter(
|
|
1704
|
+
debtAsset.address,
|
|
1705
|
+
AVNU_EXCHANGE,
|
|
1706
|
+
approve_debt_token_id,
|
|
1707
|
+
)
|
|
1708
|
+
.bind(commonAdapter),
|
|
1709
|
+
);
|
|
1710
|
+
vaultSettings.leafAdapters.push(
|
|
1711
|
+
commonAdapter
|
|
1712
|
+
.getAvnuAdapter(
|
|
1713
|
+
debtAsset.address,
|
|
1714
|
+
lstToken.address,
|
|
1715
|
+
swap_debt_token_id,
|
|
1716
|
+
false,
|
|
1717
|
+
)
|
|
1718
|
+
.bind(commonAdapter),
|
|
1719
|
+
);
|
|
1720
|
+
vaultSettings.leafAdapters.push(
|
|
1721
|
+
commonAdapter
|
|
1722
|
+
.getAvnuAdapter(
|
|
1723
|
+
lstToken.address,
|
|
1724
|
+
debtAsset.address,
|
|
1725
|
+
swap_lst_token_id,
|
|
1726
|
+
false,
|
|
1727
|
+
)
|
|
1728
|
+
.bind(commonAdapter),
|
|
1729
|
+
);
|
|
994
1730
|
}
|
|
995
1731
|
|
|
996
|
-
|
|
997
1732
|
// to bridge liquidity back to vault (used by bring_liquidity)
|
|
998
|
-
vaultSettings.leafAdapters.push(
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1733
|
+
vaultSettings.leafAdapters.push(
|
|
1734
|
+
commonAdapter
|
|
1735
|
+
.getApproveAdapter(
|
|
1736
|
+
lstToken.address,
|
|
1737
|
+
vaultSettings.vaultAddress,
|
|
1738
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY,
|
|
1739
|
+
)
|
|
1740
|
+
.bind(commonAdapter),
|
|
1741
|
+
);
|
|
1742
|
+
vaultSettings.leafAdapters.push(
|
|
1743
|
+
commonAdapter
|
|
1744
|
+
.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
|
|
1745
|
+
.bind(commonAdapter),
|
|
1746
|
+
);
|
|
1747
|
+
|
|
1748
|
+
// claim rewards (defi spring ended)
|
|
1749
|
+
// vaultSettings.leafAdapters.push(vesuAdapterLST.getDefispringRewardsAdapter(UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS).bind(vesuAdapterLST));
|
|
1003
1750
|
|
|
1004
1751
|
// avnu swap for claims rewards
|
|
1005
|
-
const STRKToken = Global.getDefaultTokens().find(
|
|
1006
|
-
|
|
1007
|
-
|
|
1752
|
+
const STRKToken = Global.getDefaultTokens().find(
|
|
1753
|
+
(token) => token.symbol === "STRK",
|
|
1754
|
+
)!;
|
|
1755
|
+
vaultSettings.leafAdapters.push(
|
|
1756
|
+
commonAdapter
|
|
1757
|
+
.getApproveAdapter(
|
|
1758
|
+
STRKToken.address,
|
|
1759
|
+
AVNU_EXCHANGE,
|
|
1760
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1,
|
|
1761
|
+
)
|
|
1762
|
+
.bind(commonAdapter),
|
|
1763
|
+
);
|
|
1764
|
+
vaultSettings.leafAdapters.push(
|
|
1765
|
+
commonAdapter
|
|
1766
|
+
.getAvnuAdapter(
|
|
1767
|
+
STRKToken.address,
|
|
1768
|
+
lstToken.address,
|
|
1769
|
+
UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS,
|
|
1770
|
+
false,
|
|
1771
|
+
)
|
|
1772
|
+
.bind(commonAdapter),
|
|
1773
|
+
);
|
|
1008
1774
|
return vaultSettings;
|
|
1009
1775
|
}
|
|
1010
1776
|
|
|
1011
|
-
const AUDIT_URL =
|
|
1777
|
+
const AUDIT_URL = "https://docs.troves.fi/p/security#starknet-vault-kit";
|
|
1012
1778
|
|
|
1013
1779
|
function getFAQs(lstSymbol: string, underlyingSymbol: string): FAQ[] {
|
|
1014
1780
|
return [
|
|
1015
1781
|
{
|
|
1016
1782
|
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.`,
|
|
1783
|
+
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
1784
|
},
|
|
1020
1785
|
{
|
|
1021
1786
|
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.`,
|
|
1787
|
+
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
1788
|
},
|
|
1025
1789
|
{
|
|
1026
1790
|
question: "Which protocols/dApp are used??",
|
|
1027
1791
|
answer: (
|
|
1028
1792
|
<span>
|
|
1029
|
-
Currently, the LST is from <strong>Endur</strong> while
|
|
1793
|
+
Currently, the LST is from <strong>Endur</strong> while{" "}
|
|
1794
|
+
<strong>Vesu</strong> is used to collateralize the looped position.
|
|
1030
1795
|
</span>
|
|
1031
1796
|
),
|
|
1032
1797
|
},
|
|
1033
1798
|
{
|
|
1034
1799
|
question: "Can I get liquidated?",
|
|
1035
|
-
answer:
|
|
1800
|
+
answer:
|
|
1801
|
+
"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
1802
|
},
|
|
1037
1803
|
{
|
|
1038
1804
|
question: "What do I receive when I deposit?",
|
|
@@ -1063,96 +1829,207 @@ function getFAQs(lstSymbol: string, underlyingSymbol: string): FAQ[] {
|
|
|
1063
1829
|
}
|
|
1064
1830
|
|
|
1065
1831
|
const _riskFactor: RiskFactor[] = [
|
|
1066
|
-
{
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1832
|
+
{
|
|
1833
|
+
type: RiskType.SMART_CONTRACT_RISK,
|
|
1834
|
+
value: SmartContractRiskLevel.WELL_AUDITED,
|
|
1835
|
+
weight: 25,
|
|
1836
|
+
reason: "Audited by Zellic",
|
|
1837
|
+
},
|
|
1838
|
+
{
|
|
1839
|
+
type: RiskType.LIQUIDATION_RISK,
|
|
1840
|
+
value: LiquidationRiskLevel.VERY_LOW_PROBABILITY,
|
|
1841
|
+
weight: 25,
|
|
1842
|
+
reason: "The collateral and debt are highly correlated",
|
|
1843
|
+
},
|
|
1844
|
+
{
|
|
1845
|
+
type: RiskType.TECHNICAL_RISK,
|
|
1846
|
+
value: TechnicalRiskLevel.STABLE_INFRASTRUCTURE,
|
|
1847
|
+
weight: 25,
|
|
1848
|
+
reason:
|
|
1849
|
+
"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.",
|
|
1850
|
+
},
|
|
1851
|
+
{
|
|
1852
|
+
type: RiskType.DEPEG_RISK,
|
|
1853
|
+
value: DepegRiskLevel.GENERALLY_STABLE,
|
|
1854
|
+
weight: 25,
|
|
1855
|
+
reason: "Generally stable pegged assets",
|
|
1856
|
+
},
|
|
1070
1857
|
];
|
|
1071
1858
|
|
|
1072
|
-
const
|
|
1073
|
-
'WBTC', 'tBTC', 'LBTC', 'solvBTC'
|
|
1074
|
-
]
|
|
1859
|
+
const btcBorrowableAssets = ["WBTC", "tBTC", "LBTC", "solvBTC"];
|
|
1075
1860
|
|
|
1076
1861
|
const hyperxSTRK: HyperLSTStrategySettings = {
|
|
1077
|
-
vaultAddress: ContractAddr.from(
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1862
|
+
vaultAddress: ContractAddr.from(
|
|
1863
|
+
"0x46c7a54c82b1fe374353859f554a40b8bd31d3e30f742901579e7b57b1b5960",
|
|
1864
|
+
),
|
|
1865
|
+
manager: ContractAddr.from(
|
|
1866
|
+
"0x5d499cd333757f461a0bedaca3dfc4d77320c773037e0aa299f22a6dbfdc03a",
|
|
1867
|
+
),
|
|
1868
|
+
vaultAllocator: ContractAddr.from(
|
|
1869
|
+
"0x511d07953a09bc7c505970891507c5a2486d2ea22752601a14db092186d7caa",
|
|
1870
|
+
),
|
|
1871
|
+
redeemRequestNFT: ContractAddr.from(
|
|
1872
|
+
"0x51e40b839dc0c2feca923f863072673b94abfa2483345be3b30b457a90d095",
|
|
1873
|
+
),
|
|
1874
|
+
aumOracle: ContractAddr.from(
|
|
1875
|
+
"0x48cf709870a1a0d453d37de108e0c41b8b89819ef54f95abc0e2e1f98bbe937",
|
|
1876
|
+
),
|
|
1082
1877
|
leafAdapters: [],
|
|
1083
1878
|
adapters: [],
|
|
1084
1879
|
targetHealthFactor: 1.1,
|
|
1085
1880
|
minHealthFactor: 1.05,
|
|
1086
|
-
borrowable_assets:
|
|
1881
|
+
borrowable_assets: [
|
|
1882
|
+
...Global.getDefaultTokens()
|
|
1883
|
+
.filter((token) => token.symbol === "STRK")
|
|
1884
|
+
.map((token) => ({ token, poolId: VesuPools.Re7xSTRK })),
|
|
1885
|
+
...Global.getDefaultTokens()
|
|
1886
|
+
.filter((token) => token.symbol === "STRK")
|
|
1887
|
+
.map((token) => ({ token, poolId: VesuPools.Prime })),
|
|
1888
|
+
...Global.getDefaultTokens()
|
|
1889
|
+
.filter((token) => token.symbol === "STRK")
|
|
1890
|
+
.map((token) => ({ token, poolId: VesuPools.Re7STRK })),
|
|
1891
|
+
],
|
|
1087
1892
|
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'STRK')!,
|
|
1088
|
-
defaultPoolId: VesuPools.
|
|
1089
|
-
altSupportedPoolIds: [VesuPools.Prime],
|
|
1893
|
+
defaultPoolId: VesuPools.Re7STRK,
|
|
1090
1894
|
}
|
|
1091
1895
|
|
|
1092
1896
|
const hyperxWBTC: HyperLSTStrategySettings = {
|
|
1093
|
-
vaultAddress: ContractAddr.from(
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1897
|
+
vaultAddress: ContractAddr.from(
|
|
1898
|
+
"0x2da9d0f96a46b453f55604313785dc866424240b1c6811d13bef594343db818",
|
|
1899
|
+
),
|
|
1900
|
+
manager: ContractAddr.from(
|
|
1901
|
+
"0x75866db44c81e6986f06035206ee9c7d15833ddb22d6a22c016cfb5c866a491",
|
|
1902
|
+
),
|
|
1903
|
+
vaultAllocator: ContractAddr.from(
|
|
1904
|
+
"0x57b5c1bb457b5e840a2714ae53ada87d77be2f3fd33a59b4fe709ef20c020c1",
|
|
1905
|
+
),
|
|
1906
|
+
redeemRequestNFT: ContractAddr.from(
|
|
1907
|
+
"0x7a5dc288325456f05e70e9616e16bc02ffbe448f4b89f80b47c0970b989c7c",
|
|
1908
|
+
),
|
|
1909
|
+
aumOracle: ContractAddr.from(
|
|
1910
|
+
"0x258f8a0ca0d21f542e48ad89d00e92dc4d9db4999084f50ef9c22dfb1e83023",
|
|
1911
|
+
),
|
|
1098
1912
|
leafAdapters: [],
|
|
1099
1913
|
adapters: [],
|
|
1100
1914
|
targetHealthFactor: 1.1,
|
|
1101
1915
|
minHealthFactor: 1.05,
|
|
1102
|
-
borrowable_assets:
|
|
1103
|
-
|
|
1916
|
+
borrowable_assets: [
|
|
1917
|
+
// allow all BTC flavours borrowing on Re7xBTC pool
|
|
1918
|
+
...btcBorrowableAssets
|
|
1919
|
+
.map(
|
|
1920
|
+
(asset) =>
|
|
1921
|
+
Global.getDefaultTokens().find((token) => token.symbol === asset)!,
|
|
1922
|
+
)
|
|
1923
|
+
.map((token) => ({ token, poolId: VesuPools.Re7xBTC })),
|
|
1924
|
+
// allow only WBTC borrowing on Prime pool
|
|
1925
|
+
...Global.getDefaultTokens()
|
|
1926
|
+
.filter((token) => token.symbol === "WBTC")
|
|
1927
|
+
.map((token) => ({ token, poolId: VesuPools.Prime })),
|
|
1928
|
+
],
|
|
1929
|
+
underlyingToken: Global.getDefaultTokens().find(
|
|
1930
|
+
(token) => token.symbol === "WBTC",
|
|
1931
|
+
)!,
|
|
1104
1932
|
defaultPoolId: VesuPools.Re7xBTC,
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1933
|
+
redemptionRouter: ContractAddr.from(
|
|
1934
|
+
"0x6ea649f402898f69baf775c1afdd08522c071c640b9c4460192070ec2b96417",
|
|
1935
|
+
),
|
|
1936
|
+
};
|
|
1108
1937
|
|
|
1109
1938
|
const hyperxtBTC: HyperLSTStrategySettings = {
|
|
1110
|
-
vaultAddress: ContractAddr.from(
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1939
|
+
vaultAddress: ContractAddr.from(
|
|
1940
|
+
"0x47d5f68477e5637ce0e56436c6b5eee5a354e6828995dae106b11a48679328",
|
|
1941
|
+
),
|
|
1942
|
+
manager: ContractAddr.from(
|
|
1943
|
+
"0xc4cc3e08029a0ae076f5fdfca70575abb78d23c5cd1c49a957f7e697885401",
|
|
1944
|
+
),
|
|
1945
|
+
vaultAllocator: ContractAddr.from(
|
|
1946
|
+
"0x50bbd4fe69f841ecb13b2619fe50ebfa4e8944671b5d0ebf7868fd80c61b31e",
|
|
1947
|
+
),
|
|
1948
|
+
redeemRequestNFT: ContractAddr.from(
|
|
1949
|
+
"0xeac9032f02057779816e38a6cb9185d12d86b3aacc9949b96b36de359c1e3",
|
|
1950
|
+
),
|
|
1951
|
+
aumOracle: ContractAddr.from(
|
|
1952
|
+
"0x7e0d05cb7ba3f7db77a36c21c21583b5a524c2e685c08c24b3554911fb4a039",
|
|
1953
|
+
),
|
|
1115
1954
|
leafAdapters: [],
|
|
1116
1955
|
adapters: [],
|
|
1117
1956
|
targetHealthFactor: 1.1,
|
|
1118
1957
|
minHealthFactor: 1.05,
|
|
1119
|
-
borrowable_assets:
|
|
1120
|
-
|
|
1958
|
+
borrowable_assets: [
|
|
1959
|
+
...Global.getDefaultTokens()
|
|
1960
|
+
.filter((token) => token.symbol === "tBTC" || token.symbol === "WBTC")
|
|
1961
|
+
.map((token) => ({ token, poolId: VesuPools.Re7xBTC })),
|
|
1962
|
+
],
|
|
1963
|
+
underlyingToken: Global.getDefaultTokens().find(
|
|
1964
|
+
(token) => token.symbol === "tBTC",
|
|
1965
|
+
)!,
|
|
1121
1966
|
defaultPoolId: VesuPools.Re7xBTC,
|
|
1122
|
-
|
|
1123
|
-
|
|
1967
|
+
redemptionRouter: ContractAddr.from(
|
|
1968
|
+
"0x3de9c409d1e357e25778fb7a3e2e2393666956846a5c2caa607296fa8e76b5d",
|
|
1969
|
+
),
|
|
1970
|
+
};
|
|
1124
1971
|
|
|
1125
1972
|
const hyperxsBTC: HyperLSTStrategySettings = {
|
|
1126
|
-
vaultAddress: ContractAddr.from(
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1973
|
+
vaultAddress: ContractAddr.from(
|
|
1974
|
+
"0x437ef1e7d0f100b2e070b7a65cafec0b2be31b0290776da8b4112f5473d8d9",
|
|
1975
|
+
),
|
|
1976
|
+
manager: ContractAddr.from(
|
|
1977
|
+
"0xc9ac023090625b0be3f6532ca353f086746f9c09f939dbc1b2613f09e5f821",
|
|
1978
|
+
),
|
|
1979
|
+
vaultAllocator: ContractAddr.from(
|
|
1980
|
+
"0x60c2d856936b975459a5b4eb28b8672d91f757bd76cebb6241f8d670185dc01",
|
|
1981
|
+
),
|
|
1982
|
+
redeemRequestNFT: ContractAddr.from(
|
|
1983
|
+
"0x429e8ee8bc7ecd1ade72630d350a2e0f10f9a2507c45f188ba17fe8f2ab4cf3",
|
|
1984
|
+
),
|
|
1985
|
+
aumOracle: ContractAddr.from(
|
|
1986
|
+
"0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345",
|
|
1987
|
+
),
|
|
1131
1988
|
leafAdapters: [],
|
|
1132
1989
|
adapters: [],
|
|
1133
1990
|
targetHealthFactor: 1.1,
|
|
1134
1991
|
minHealthFactor: 1.05,
|
|
1135
|
-
borrowable_assets:
|
|
1136
|
-
|
|
1992
|
+
borrowable_assets: [
|
|
1993
|
+
...Global.getDefaultTokens()
|
|
1994
|
+
.filter((token) => token.symbol === "solvBTC")
|
|
1995
|
+
.map((token) => ({ token, poolId: VesuPools.Re7xBTC })),
|
|
1996
|
+
],
|
|
1997
|
+
underlyingToken: Global.getDefaultTokens().find(
|
|
1998
|
+
(token) => token.symbol === "solvBTC",
|
|
1999
|
+
)!,
|
|
1137
2000
|
defaultPoolId: VesuPools.Re7xBTC,
|
|
1138
|
-
|
|
1139
|
-
}
|
|
2001
|
+
};
|
|
1140
2002
|
|
|
1141
2003
|
const hyperxLBTC: HyperLSTStrategySettings = {
|
|
1142
|
-
vaultAddress: ContractAddr.from(
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
2004
|
+
vaultAddress: ContractAddr.from(
|
|
2005
|
+
"0x64cf24d4883fe569926419a0569ab34497c6956a1a308fa883257f7486d7030",
|
|
2006
|
+
),
|
|
2007
|
+
manager: ContractAddr.from(
|
|
2008
|
+
"0x203530a4022a99b8f4b406aaf33b0849d43ad7422c1d5cc14ff8c667abec6c0",
|
|
2009
|
+
),
|
|
2010
|
+
vaultAllocator: ContractAddr.from(
|
|
2011
|
+
"0x7dbc8ccd4eabce6ea6c19e0e5c9ccca3a93bd510303b9e071cbe25fc508546e",
|
|
2012
|
+
),
|
|
2013
|
+
redeemRequestNFT: ContractAddr.from(
|
|
2014
|
+
"0x5ee66a39af9aef3d0d48982b4a63e8bd2a5bad021916bd87fb0eae3a26800b8",
|
|
2015
|
+
),
|
|
2016
|
+
aumOracle: ContractAddr.from(
|
|
2017
|
+
"0x23d69e4391fa72d10e625e7575d8bddbb4aff96f04503f83fdde23123bf41d0",
|
|
2018
|
+
),
|
|
1147
2019
|
leafAdapters: [],
|
|
1148
2020
|
adapters: [],
|
|
1149
2021
|
targetHealthFactor: 1.1,
|
|
1150
2022
|
minHealthFactor: 1.05,
|
|
1151
|
-
borrowable_assets:
|
|
1152
|
-
|
|
2023
|
+
borrowable_assets: [
|
|
2024
|
+
...Global.getDefaultTokens()
|
|
2025
|
+
.filter((token) => token.symbol === "LBTC")
|
|
2026
|
+
.map((token) => ({ token, poolId: VesuPools.Re7xBTC })),
|
|
2027
|
+
],
|
|
2028
|
+
underlyingToken: Global.getDefaultTokens().find(
|
|
2029
|
+
(token) => token.symbol === "LBTC",
|
|
2030
|
+
)!,
|
|
1153
2031
|
defaultPoolId: VesuPools.Re7xBTC,
|
|
1154
|
-
|
|
1155
|
-
}
|
|
2032
|
+
};
|
|
1156
2033
|
|
|
1157
2034
|
function getInvestmentSteps(lstSymbol: string, underlyingSymbol: string) {
|
|
1158
2035
|
return [
|
|
@@ -1160,154 +2037,176 @@ function getInvestmentSteps(lstSymbol: string, underlyingSymbol: string) {
|
|
|
1160
2037
|
`The vault manager loops the ${underlyingSymbol} to buy ${lstSymbol}`,
|
|
1161
2038
|
`The vault manager collateralizes the ${lstSymbol} on Vesu`,
|
|
1162
2039
|
`The vault manager borrows more ${underlyingSymbol} to loop further`,
|
|
1163
|
-
`If required, adjust leverage or re-allocate assets within LST pool on Vesu to optimize yield
|
|
1164
|
-
]
|
|
2040
|
+
`If required, adjust leverage or re-allocate assets within LST pool on Vesu to optimize yield`,
|
|
2041
|
+
];
|
|
1165
2042
|
}
|
|
1166
2043
|
|
|
1167
2044
|
// Helper to get maxTVL based on LST symbol (matching client values)
|
|
1168
2045
|
function getMaxTVL(lstSymbol: string): Web3Number {
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
2046
|
+
const lstMaxTVLs: Record<string, number> = {
|
|
2047
|
+
xWBTC: 5,
|
|
2048
|
+
xLBTC: 5,
|
|
2049
|
+
xtBTC: 5,
|
|
2050
|
+
xsBTC: 5,
|
|
2051
|
+
xSTRK: 7000000,
|
|
2052
|
+
};
|
|
1176
2053
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
2054
|
+
const maxTVLValue = lstMaxTVLs[lstSymbol] || 0;
|
|
2055
|
+
const token = Global.getDefaultTokens().find(
|
|
2056
|
+
(token) => token.symbol === lstSymbol,
|
|
2057
|
+
);
|
|
2058
|
+
if (!token) {
|
|
2059
|
+
return Web3Number.fromWei(0, 18);
|
|
2060
|
+
}
|
|
2061
|
+
return Web3Number.fromWei(maxTVLValue, token.decimals);
|
|
1185
2062
|
}
|
|
1186
2063
|
|
|
1187
2064
|
// Helper to create Hyper LST strategy settings
|
|
1188
2065
|
function createHyperLSTSettings(
|
|
1189
|
-
|
|
1190
|
-
|
|
2066
|
+
lstSymbol: string,
|
|
2067
|
+
underlyingSymbol: string,
|
|
1191
2068
|
): StrategySettings {
|
|
1192
|
-
|
|
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
|
-
|
|
2069
|
+
const depositToken = Global.getDefaultTokens().find(
|
|
2070
|
+
(token) => token.symbol === lstSymbol,
|
|
2071
|
+
)!;
|
|
2072
|
+
return {
|
|
2073
|
+
maxTVL: getMaxTVL(lstSymbol),
|
|
2074
|
+
isPaused: false,
|
|
2075
|
+
liveStatus: StrategyLiveStatus.HOT,
|
|
2076
|
+
isAudited: true,
|
|
2077
|
+
isInstantWithdrawal: false,
|
|
2078
|
+
hideHarvestInfo: true,
|
|
2079
|
+
quoteToken: depositToken,
|
|
2080
|
+
showWithdrawalWarningModal: false,
|
|
2081
|
+
alerts: [
|
|
2082
|
+
{
|
|
2083
|
+
tab: "withdraw" as const,
|
|
2084
|
+
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.",
|
|
2085
|
+
type: "info" as const,
|
|
2086
|
+
},
|
|
2087
|
+
{
|
|
2088
|
+
tab: "deposit" as const,
|
|
2089
|
+
text: (
|
|
2090
|
+
<>
|
|
2091
|
+
To acquire the LST, please visit{" "}
|
|
2092
|
+
<a
|
|
2093
|
+
href="https://app.endur.fi"
|
|
2094
|
+
target="_blank"
|
|
2095
|
+
rel="noopener noreferrer"
|
|
2096
|
+
>
|
|
2097
|
+
endur.fi
|
|
2098
|
+
</a>
|
|
2099
|
+
</>
|
|
2100
|
+
),
|
|
2101
|
+
type: "info" as const,
|
|
2102
|
+
},
|
|
2103
|
+
{
|
|
2104
|
+
tab: "deposit" as const,
|
|
2105
|
+
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.",
|
|
2106
|
+
type: "info" as const,
|
|
2107
|
+
},
|
|
2108
|
+
],
|
|
2109
|
+
};
|
|
1233
2110
|
}
|
|
1234
2111
|
|
|
1235
2112
|
const HYPER_LST_SECURITY = {
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
}
|
|
2113
|
+
auditStatus: AuditStatus.AUDITED,
|
|
2114
|
+
sourceCode: {
|
|
2115
|
+
type: SourceCodeType.CLOSED_SOURCE,
|
|
2116
|
+
contractLink: "https://github.com/trovesfi/troves-contracts",
|
|
2117
|
+
},
|
|
2118
|
+
accessControl: {
|
|
2119
|
+
type: AccessControlType.STANDARD_ACCOUNT,
|
|
2120
|
+
addresses: [ContractAddr.from("0x03495DD1e4838aa06666aac236036D86E81A6553e222FC02e70C2Cbc0062e8d0")],
|
|
2121
|
+
},
|
|
1246
2122
|
};
|
|
1247
2123
|
|
|
1248
|
-
const HYPER_LST_REDEMPTION_INFO = {
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
}
|
|
2124
|
+
const HYPER_LST_REDEMPTION_INFO: RedemptionInfo = {
|
|
2125
|
+
instantWithdrawalVault: InstantWithdrawalVault.NO,
|
|
2126
|
+
redemptionsInfo: [
|
|
2127
|
+
{
|
|
2128
|
+
title: "Typical Duration",
|
|
2129
|
+
description: "1-2 hours",
|
|
2130
|
+
},
|
|
2131
|
+
],
|
|
2132
|
+
alerts: [
|
|
2133
|
+
{
|
|
2134
|
+
type: "info",
|
|
2135
|
+
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.",
|
|
2136
|
+
tab: "withdraw",
|
|
2137
|
+
},
|
|
2138
|
+
],
|
|
1255
2139
|
};
|
|
1256
2140
|
|
|
1257
2141
|
function getStrategySettings(
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
2142
|
+
lstSymbol: string,
|
|
2143
|
+
underlyingSymbol: string,
|
|
2144
|
+
settings: HyperLSTStrategySettings,
|
|
2145
|
+
isPreview: boolean = false,
|
|
1262
2146
|
): IStrategyMetadata<HyperLSTStrategySettings> {
|
|
1263
|
-
|
|
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
|
-
|
|
2147
|
+
return {
|
|
2148
|
+
id: `hyper_${lstSymbol.toLowerCase()}`,
|
|
2149
|
+
name: `Hyper ${lstSymbol}`,
|
|
2150
|
+
description: getDescription(lstSymbol, underlyingSymbol),
|
|
2151
|
+
address: settings.vaultAddress,
|
|
2152
|
+
launchBlock: 0,
|
|
2153
|
+
type: "Other",
|
|
2154
|
+
vaultType: {
|
|
2155
|
+
type: VaultType.LOOPING,
|
|
2156
|
+
description: `Creates leveraged looping position on ${lstSymbol} by borrowing ${underlyingSymbol} to increase yield`,
|
|
2157
|
+
},
|
|
2158
|
+
depositTokens: [
|
|
2159
|
+
Global.getDefaultTokens().find((token) => token.symbol === lstSymbol)!,
|
|
2160
|
+
],
|
|
2161
|
+
additionalInfo: getLooperSettings(
|
|
2162
|
+
lstSymbol,
|
|
2163
|
+
underlyingSymbol,
|
|
2164
|
+
settings,
|
|
2165
|
+
settings.defaultPoolId,
|
|
2166
|
+
),
|
|
2167
|
+
risk: {
|
|
2168
|
+
riskFactor: _riskFactor,
|
|
2169
|
+
netRisk:
|
|
2170
|
+
_riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
|
|
2171
|
+
_riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
|
|
2172
|
+
notARisks: getNoRiskTags(_riskFactor),
|
|
2173
|
+
},
|
|
2174
|
+
auditUrl: AUDIT_URL,
|
|
2175
|
+
protocols: [Protocols.ENDUR, Protocols.VESU],
|
|
2176
|
+
curator: UnwrapLabsCurator,
|
|
2177
|
+
settings: createHyperLSTSettings(lstSymbol, underlyingSymbol),
|
|
2178
|
+
contractDetails: getContractDetails(settings),
|
|
2179
|
+
faqs: getFAQs(lstSymbol, underlyingSymbol),
|
|
2180
|
+
investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
|
|
2181
|
+
isPreview: isPreview,
|
|
2182
|
+
apyMethodology:
|
|
2183
|
+
"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.",
|
|
2184
|
+
realizedApyMethodology:
|
|
2185
|
+
"The realizedAPY is based on past 14 days performance by the vault",
|
|
2186
|
+
tags: lstSymbol.includes("BTC")
|
|
2187
|
+
? [StrategyTag.BTC, StrategyTag.LEVERED]
|
|
2188
|
+
: [StrategyTag.LEVERED],
|
|
2189
|
+
security: HYPER_LST_SECURITY,
|
|
2190
|
+
redemptionInfo: HYPER_LST_REDEMPTION_INFO,
|
|
2191
|
+
usualTimeToEarnings: "2 weeks",
|
|
2192
|
+
usualTimeToEarningsDescription:
|
|
2193
|
+
"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 hours. This is when you realise your earnings.",
|
|
2194
|
+
points: [
|
|
2195
|
+
{
|
|
2196
|
+
multiplier: 4,
|
|
2197
|
+
logo: "https://endur.fi/favicon.ico",
|
|
2198
|
+
toolTip:
|
|
2199
|
+
"This strategy holds xSTRK. Earn 3-4x Endur points on your xSTRK due to the leverage. Points can be found on endur.fi.",
|
|
2200
|
+
},
|
|
2201
|
+
],
|
|
2202
|
+
};
|
|
1304
2203
|
}
|
|
1305
2204
|
|
|
1306
2205
|
export const HyperLSTStrategies: IStrategyMetadata<HyperLSTStrategySettings>[] =
|
|
1307
2206
|
[
|
|
1308
|
-
getStrategySettings(
|
|
1309
|
-
getStrategySettings(
|
|
1310
|
-
getStrategySettings(
|
|
1311
|
-
getStrategySettings(
|
|
1312
|
-
getStrategySettings(
|
|
1313
|
-
]
|
|
2207
|
+
getStrategySettings("xSTRK", "STRK", hyperxSTRK, false),
|
|
2208
|
+
getStrategySettings("xWBTC", "WBTC", hyperxWBTC, false),
|
|
2209
|
+
getStrategySettings("xtBTC", "tBTC", hyperxtBTC, false),
|
|
2210
|
+
getStrategySettings("xsBTC", "solvBTC", hyperxsBTC, false),
|
|
2211
|
+
getStrategySettings("xLBTC", "LBTC", hyperxLBTC, false),
|
|
2212
|
+
];
|