@strkfarm/sdk 2.0.0-dev.5 → 2.0.0-dev.51
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 +190 -36
- package/dist/cli.mjs +188 -34
- package/dist/index.browser.global.js +118889 -92229
- package/dist/index.browser.mjs +13381 -11153
- package/dist/index.d.ts +2284 -1938
- package/dist/index.js +13794 -11360
- package/dist/index.mjs +14253 -11843
- package/package.json +59 -60
- package/src/data/avnu.abi.json +840 -0
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/data/redeem-request-nft.abi.json +752 -0
- package/src/data/universal-vault.abi.json +8 -7
- package/src/dataTypes/_bignumber.ts +13 -4
- package/src/dataTypes/bignumber.browser.ts +10 -1
- package/src/dataTypes/bignumber.node.ts +10 -1
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +280 -233
- package/src/index.browser.ts +2 -1
- package/src/interfaces/common.tsx +229 -6
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +21 -12
- package/src/modules/ekubo-pricer.ts +99 -0
- package/src/modules/ekubo-quoter.ts +48 -30
- package/src/modules/erc20.ts +17 -0
- package/src/modules/harvests.ts +43 -29
- package/src/modules/index.ts +2 -1
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-avnu-api.ts +114 -0
- package/src/modules/pricer-from-api.ts +159 -15
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer-quote-utils.ts +54 -0
- package/src/modules/pricer.ts +157 -54
- package/src/modules/pricerBase.ts +2 -1
- package/src/modules/zkLend.ts +3 -2
- package/src/node/deployer.ts +36 -1
- package/src/node/pricer-redis.ts +3 -1
- package/src/strategies/base-strategy.ts +168 -16
- package/src/strategies/constants.ts +8 -3
- package/src/strategies/ekubo-cl-vault.tsx +1048 -355
- package/src/strategies/factory.ts +199 -0
- package/src/strategies/index.ts +5 -3
- package/src/strategies/registry.ts +262 -0
- package/src/strategies/sensei.ts +354 -10
- package/src/strategies/svk-strategy.ts +292 -31
- package/src/strategies/token-boosted-xstrk-carry-strategy.tsx +1261 -0
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +4 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +196 -272
- package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
- package/src/strategies/universal-adapters/common-adapter.ts +206 -203
- package/src/strategies/universal-adapters/index.ts +10 -8
- package/src/strategies/universal-adapters/svk-troves-adapter.ts +511 -0
- package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
- package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +525 -0
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +866 -860
- package/src/strategies/universal-adapters/vesu-position-common.ts +258 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
- package/src/strategies/universal-lst-muliplier-strategy.tsx +895 -416
- package/src/strategies/universal-strategy.tsx +1332 -1173
- package/src/strategies/vesu-rebalance.tsx +254 -153
- package/src/strategies/yoloVault.ts +1096 -0
- package/src/utils/cacheClass.ts +11 -2
- package/src/utils/health-factor-math.ts +33 -1
- package/src/utils/index.ts +3 -1
- package/src/utils/logger.browser.ts +22 -4
- package/src/utils/logger.node.ts +259 -24
- package/src/utils/starknet-call-parser.ts +1036 -0
- package/src/utils/strategy-utils.ts +61 -0
- package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
- package/src/modules/ExtendedWrapperSDk/types.ts +0 -311
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -395
- package/src/strategies/universal-adapters/extended-adapter.ts +0 -662
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -34
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -77
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -49
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -372
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1140
|
@@ -1,51 +1,79 @@
|
|
|
1
|
-
import { FAQ, getMainnetConfig, getNoRiskTags, highlightTextWithLinks, IConfig, IStrategyMetadata, Protocols, RiskFactor, RiskType, TokenInfo, VaultPosition } from "@/interfaces";
|
|
1
|
+
import { FAQ, getMainnetConfig, getNoRiskTags, highlightTextWithLinks, IConfig, IStrategyMetadata, Protocols, RiskFactor, RiskType, TokenInfo, VaultPosition, StrategyTag, AuditStatus, SourceCodeType, AccessControlType, InstantWithdrawalVault, StrategyLiveStatus, StrategySettings, VaultType, RedemptionInfo } from "@/interfaces";
|
|
2
2
|
import { AUMTypes, getContractDetails, UNIVERSAL_MANAGE_IDS, UniversalManageCall, UniversalStrategySettings } from "./universal-strategy";
|
|
3
3
|
import { PricerBase } from "@/modules/pricerBase";
|
|
4
4
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
5
5
|
import { Global } from "@/global";
|
|
6
|
-
import { ApproveCallParams, APYType, AvnuSwapCallParams, BaseAdapterConfig, CommonAdapter, ManageCall, PositionInfo,
|
|
6
|
+
import { ApproveCallParams, APYType, AvnuSwapCallParams, BaseAdapterConfig, CommonAdapter, ManageCall, PositionInfo, TokenTransferAdapter, VesuModifyPositionAdapter, VesuMultiplyAdapter, VesuPools, VesuSupplyOnlyAdapter } from "./universal-adapters";
|
|
7
|
+
import { VesuAdapter } from "./universal-adapters/vesu-adapter";
|
|
7
8
|
import { AVNU_EXCHANGE } from "./universal-adapters/adapter-utils";
|
|
8
9
|
import { DepegRiskLevel, LiquidationRiskLevel, SmartContractRiskLevel, TechnicalRiskLevel } from "@/interfaces/risks";
|
|
9
|
-
import { AvnuWrapper, EkuboQuoter, ERC20, PricerFromApi, PricerLST } from "@/modules";
|
|
10
|
+
import { AvnuWrapper, EkuboQuoter, ERC20, LSTAPRService, PricerFromApi, PricerLST } from "@/modules";
|
|
10
11
|
import { assert, logger } from "@/utils";
|
|
11
|
-
import {
|
|
12
|
+
import { findMaxInputWithSlippage } from "@/utils/math-utils";
|
|
13
|
+
import { SingleActionAmount, SingleTokenInfo, UserPositionCard, UserPositionCardsInput } from "./base-strategy";
|
|
12
14
|
import { SVKStrategy } from "./svk-strategy";
|
|
13
|
-
import { Call, uint256 } from "starknet";
|
|
15
|
+
import { Call, Contract, num, uint256, BlockIdentifier } from "starknet";
|
|
14
16
|
import ERC4626Abi from "@/data/erc4626.abi.json";
|
|
15
17
|
import { AdapterOptimizer } from "./universal-adapters/adapter-optimizer";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
+
import { LSTPriceType } from "./types";
|
|
19
|
+
|
|
20
|
+
const VESU_MULTIPLY_ADAPTER_ID_PREFIX = "vesu_multiply_";
|
|
21
|
+
const VESU_MODIFY_ADAPTER_ID_PREFIX = "vesu_modify_";
|
|
18
22
|
|
|
19
23
|
export interface HyperLSTStrategySettings extends UniversalStrategySettings {
|
|
20
|
-
borrowable_assets: TokenInfo[];
|
|
24
|
+
borrowable_assets: { tokenInfo: TokenInfo, poolId: ContractAddr }[];
|
|
21
25
|
underlyingToken: TokenInfo;
|
|
22
26
|
quoteAmountToFetchPrice: Web3Number;
|
|
23
27
|
targetHealthFactor: number;
|
|
24
28
|
minHealthFactor: number;
|
|
25
29
|
aumOracle: ContractAddr;
|
|
30
|
+
adapterIds?: {
|
|
31
|
+
/** VesuMultiplyAdapter for underlying-matched debt (first matching borrowable pool) */
|
|
32
|
+
primaryMultiply: string;
|
|
33
|
+
multiply: Record<string, string>;
|
|
34
|
+
modify: Record<string, string>;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getHyperLSTAdapterKey(debtSymbol: string, poolId: ContractAddr): string {
|
|
39
|
+
return `${debtSymbol.toLowerCase()}_${poolId.shortString()}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getHyperLSTMultiplyAdapterId(
|
|
43
|
+
lstSymbol: string,
|
|
44
|
+
debtSymbol: string,
|
|
45
|
+
poolId: ContractAddr,
|
|
46
|
+
): string {
|
|
47
|
+
return `${VESU_MULTIPLY_ADAPTER_ID_PREFIX}${lstSymbol.toLowerCase()}_${getHyperLSTAdapterKey(debtSymbol, poolId)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getHyperLSTModifyAdapterId(
|
|
51
|
+
lstSymbol: string,
|
|
52
|
+
debtSymbol: string,
|
|
53
|
+
poolId: ContractAddr,
|
|
54
|
+
): string {
|
|
55
|
+
return `${VESU_MODIFY_ADAPTER_ID_PREFIX}${lstSymbol.toLowerCase()}_${getHyperLSTAdapterKey(debtSymbol, poolId)}`;
|
|
26
56
|
}
|
|
27
57
|
|
|
28
58
|
export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings> extends SVKStrategy<S> {
|
|
29
59
|
|
|
30
60
|
private quoteAmountToFetchPrice = new Web3Number(1, 18);
|
|
31
|
-
|
|
61
|
+
|
|
32
62
|
constructor(config: IConfig, pricer: PricerBase, metadata: IStrategyMetadata<S>) {
|
|
33
63
|
super(config, pricer, metadata);
|
|
34
64
|
const STRKToken = Global.getDefaultTokens().find(token => token.symbol === 'STRK')!;
|
|
35
65
|
const underlyingToken = this.getLSTUnderlyingTokenInfo();
|
|
36
|
-
if (
|
|
37
|
-
this.quoteAmountToFetchPrice
|
|
38
|
-
} else {
|
|
39
|
-
// else this BTC
|
|
40
|
-
this.quoteAmountToFetchPrice = new Web3Number(0.01, this.asset().decimals);
|
|
66
|
+
if (metadata.additionalInfo.quoteAmountToFetchPrice.isZero()) {
|
|
67
|
+
throw new Error(`${this.getTag()}::quoteAmountToFetchPrice is zero`);
|
|
41
68
|
}
|
|
69
|
+
this.quoteAmountToFetchPrice = metadata.additionalInfo.quoteAmountToFetchPrice;
|
|
42
70
|
|
|
43
71
|
this.metadata.additionalInfo.adapters.forEach(adapter => {
|
|
44
72
|
adapter.adapter.config.networkConfig = this.config;
|
|
45
73
|
adapter.adapter.config.pricer = this.pricer;
|
|
46
|
-
if ((adapter.adapter as
|
|
47
|
-
(adapter.adapter as
|
|
48
|
-
(adapter.adapter as
|
|
74
|
+
if ((adapter.adapter as any)._vesuAdapter) {
|
|
75
|
+
(adapter.adapter as any)._vesuAdapter.networkConfig = this.config;
|
|
76
|
+
(adapter.adapter as any)._vesuAdapter.pricer = this.pricer;
|
|
49
77
|
}
|
|
50
78
|
});
|
|
51
79
|
}
|
|
@@ -54,13 +82,36 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
54
82
|
return `${UniversalLstMultiplierStrategy.name}:${this.metadata.name}`;
|
|
55
83
|
}
|
|
56
84
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
85
|
+
private getAdapterById<T>(id: string): T {
|
|
86
|
+
const entry = this.metadata.additionalInfo.adapters.find((a) => a.id === id);
|
|
87
|
+
if (!entry) {
|
|
88
|
+
throw new Error(`${this.getTag()}::getAdapterById: adapter not found for id "${id}"`);
|
|
89
|
+
}
|
|
90
|
+
return entry.adapter as T;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getVesuMultiplyAdapterByKey(key: string): VesuMultiplyAdapter {
|
|
94
|
+
const id = this.metadata.additionalInfo.adapterIds?.multiply[key];
|
|
95
|
+
if (!id) {
|
|
96
|
+
throw new Error(`${this.getTag()}::getVesuMultiplyAdapterByKey: unknown key "${key}"`);
|
|
97
|
+
}
|
|
98
|
+
return this.getAdapterById<VesuMultiplyAdapter>(id);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Vesu multiply adapter whose debt matches the LST underlying (e.g. STRK for xSTRK)
|
|
102
|
+
getVesuSameTokenAdapter(): VesuMultiplyAdapter {
|
|
103
|
+
const primaryId = this.metadata.additionalInfo.adapterIds?.primaryMultiply;
|
|
104
|
+
if (primaryId) {
|
|
105
|
+
const adapter = this.getAdapterById<VesuMultiplyAdapter>(primaryId);
|
|
106
|
+
adapter.config.networkConfig = this.config;
|
|
107
|
+
adapter.config.pricer = this.pricer;
|
|
108
|
+
adapter._vesuAdapter.networkConfig = this.config;
|
|
109
|
+
adapter._vesuAdapter.pricer = this.pricer;
|
|
110
|
+
return adapter;
|
|
111
|
+
}
|
|
112
|
+
const baseAdapter = this.getVesuMultiplyAdapters().find((adapter) =>
|
|
113
|
+
adapter.config.debt.address.eq(this.metadata.additionalInfo.underlyingToken.address),
|
|
114
|
+
);
|
|
64
115
|
if (!baseAdapter) {
|
|
65
116
|
throw new Error(`${this.getTag()}::getVesuSameTokenAdapter: base adapter not found`);
|
|
66
117
|
}
|
|
@@ -71,11 +122,15 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
71
122
|
|
|
72
123
|
// only one leg is used
|
|
73
124
|
// todo support lending assets of underlying as well (like if xSTRK looping is not viable, simply supply STRK)
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
125
|
+
getVesuMultiplyAdapters() {
|
|
126
|
+
const multiplyAdapterIds = new Set(Object.values(this.metadata.additionalInfo.adapterIds?.multiply ?? {}));
|
|
127
|
+
const vesuMultipleAdapterEntries = this.metadata.additionalInfo.adapters
|
|
128
|
+
.filter((entry) =>
|
|
129
|
+
multiplyAdapterIds.size > 0
|
|
130
|
+
? multiplyAdapterIds.has(entry.id)
|
|
131
|
+
: entry.id.startsWith(VESU_MULTIPLY_ADAPTER_ID_PREFIX),
|
|
132
|
+
);
|
|
133
|
+
const vesuMultipleAdapters = vesuMultipleAdapterEntries.map((entry) => entry.adapter) as VesuMultiplyAdapter[];
|
|
79
134
|
for (const vesuAdapter of vesuMultipleAdapters) {
|
|
80
135
|
vesuAdapter.config.pricer = this.pricer;
|
|
81
136
|
vesuAdapter.config.networkConfig = this.config;
|
|
@@ -83,6 +138,82 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
83
138
|
return vesuMultipleAdapters;
|
|
84
139
|
}
|
|
85
140
|
|
|
141
|
+
async getRewardsAUM(_prevAum: Web3Number) {
|
|
142
|
+
const lstToken = this.asset();
|
|
143
|
+
// Defi spring rewards handling; currently returns 0 as SVKStrategy has no built-in rewards logic
|
|
144
|
+
return Web3Number.fromWei("0", lstToken.decimals);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async getLSTAvnuRate() {
|
|
148
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter();
|
|
149
|
+
const lstTokenInfo = vesuAdapter1._vesuAdapter.config.collateral;
|
|
150
|
+
const underlyingTokenInfo = vesuAdapter1._vesuAdapter.config.debt;
|
|
151
|
+
|
|
152
|
+
const avnuModule = new AvnuWrapper();
|
|
153
|
+
|
|
154
|
+
const sellAmount = (lstTokenInfo as any).priceCheckAmount
|
|
155
|
+
? new Web3Number(
|
|
156
|
+
(lstTokenInfo as any).priceCheckAmount,
|
|
157
|
+
underlyingTokenInfo.decimals,
|
|
158
|
+
)
|
|
159
|
+
: new Web3Number(1, underlyingTokenInfo.decimals);
|
|
160
|
+
|
|
161
|
+
const quote = await avnuModule.getQuotes(
|
|
162
|
+
underlyingTokenInfo.address.address,
|
|
163
|
+
lstTokenInfo.address.address,
|
|
164
|
+
sellAmount.toWei(),
|
|
165
|
+
this.metadata.additionalInfo.vaultAllocator.address,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const underlyingAmountNumber = sellAmount.toNumber();
|
|
169
|
+
const lstAmountNumber = Web3Number.fromWei(
|
|
170
|
+
quote.buyAmount.toString(),
|
|
171
|
+
lstTokenInfo.decimals,
|
|
172
|
+
).toNumber();
|
|
173
|
+
|
|
174
|
+
assert(lstAmountNumber > 0, "Avnu LST amount is zero");
|
|
175
|
+
|
|
176
|
+
const exchangeRate = underlyingAmountNumber / lstAmountNumber;
|
|
177
|
+
logger.verbose(
|
|
178
|
+
`${this.getTag()}:: LST Avnu Exchange Rate: ${exchangeRate}`,
|
|
179
|
+
);
|
|
180
|
+
return exchangeRate;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async getLSTExchangeRate() {
|
|
184
|
+
const vesuAdapter1 = this.getVesuSameTokenAdapter();
|
|
185
|
+
const lstTokenInfo = vesuAdapter1._vesuAdapter.config.collateral;
|
|
186
|
+
const lstABI = new Contract({
|
|
187
|
+
abi: ERC4626Abi,
|
|
188
|
+
address: lstTokenInfo.address.address,
|
|
189
|
+
providerOrAccount: this.config.provider,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const price: any = await lstABI.call("convert_to_assets", [
|
|
193
|
+
uint256.bnToUint256(new Web3Number(1, lstTokenInfo.decimals).toWei()),
|
|
194
|
+
]);
|
|
195
|
+
const exchangeRate =
|
|
196
|
+
Number(uint256.uint256ToBN(price).toString()) /
|
|
197
|
+
Math.pow(10, lstTokenInfo.decimals);
|
|
198
|
+
logger.verbose(`${this.getTag()}:: LST Exchange Rate: ${exchangeRate}`);
|
|
199
|
+
return exchangeRate;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private async _getMinOutputAmountLSTBuy(amountInUnderlying: Web3Number) {
|
|
203
|
+
const lstTruePrice = await this.getLSTExchangeRate();
|
|
204
|
+
const minOutputAmount = amountInUnderlying
|
|
205
|
+
.dividedBy(lstTruePrice)
|
|
206
|
+
.multipliedBy(0.99979);
|
|
207
|
+
return new Web3Number(minOutputAmount.toString(), this.asset().decimals);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private async _getMinOutputAmountLSTSell(amountInLST: Web3Number) {
|
|
211
|
+
const lstTruePrice = await this.getLSTExchangeRate();
|
|
212
|
+
const minOutputAmount = amountInLST
|
|
213
|
+
.multipliedBy(lstTruePrice)
|
|
214
|
+
.multipliedBy(0.995);
|
|
215
|
+
return minOutputAmount;
|
|
216
|
+
}
|
|
86
217
|
|
|
87
218
|
// async getAvnuSwapMultiplyCall(params: {
|
|
88
219
|
// isDeposit: boolean,
|
|
@@ -132,7 +263,7 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
132
263
|
|
|
133
264
|
// async _getAvnuDepositSwapLegCall(params: {
|
|
134
265
|
// isDeposit: boolean,
|
|
135
|
-
// leg1DepositAmount: Web3Number,
|
|
266
|
+
// leg1DepositAmount: Web3Number,
|
|
136
267
|
// minHF: number, // e.g. 1.01
|
|
137
268
|
// vesuAdapter: VesuAdapter
|
|
138
269
|
// }) {
|
|
@@ -144,18 +275,18 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
144
275
|
// // approve and swap strk
|
|
145
276
|
// // add collateral again
|
|
146
277
|
|
|
147
|
-
|
|
278
|
+
|
|
148
279
|
// const legLTV = await vesuAdapter.getLTVConfig(this.config);
|
|
149
280
|
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall legLTV: ${legLTV}`);
|
|
150
281
|
// const existingPositions = await vesuAdapter.getPositions(this.config);
|
|
151
282
|
// const collateralisation = await vesuAdapter.getCollateralization(this.config);
|
|
152
283
|
// const existingCollateralInfo = existingPositions[0];
|
|
153
284
|
// const existingDebtInfo = existingPositions[1];
|
|
154
|
-
// logger.debug(`${this.getTag()}::_getAvnuDepositSwapLegCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
285
|
+
// logger.debug(`${this.getTag()}::_getAvnuDepositSwapLegCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
155
286
|
// existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
156
287
|
|
|
157
288
|
// // - Prices as seen by Vesu contracts, ideal for HF math
|
|
158
|
-
// // Price 1 is ok as fallback bcz that would relatively price the
|
|
289
|
+
// // Price 1 is ok as fallback bcz that would relatively price the
|
|
159
290
|
// // collateral and debt as equal.
|
|
160
291
|
// const collateralPrice = collateralisation[0].usdValue > 0 ? collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() : 1;
|
|
161
292
|
// const debtPrice = collateralisation[1].usdValue > 0 ? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() : 1;
|
|
@@ -202,7 +333,7 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
202
333
|
|
|
203
334
|
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall debtAmount: ${debtAmount}`);
|
|
204
335
|
// if (debtAmount.lt(0)) {
|
|
205
|
-
// // this is to unwind the position to optimal HF.
|
|
336
|
+
// // this is to unwind the position to optimal HF.
|
|
206
337
|
// const lstDEXPrice = await this.getLSTDexPrice();
|
|
207
338
|
// const debtAmountInLST = debtAmount.abs().dividedBy(lstDEXPrice);
|
|
208
339
|
// const calls = await this.getVesuMultiplyCall({
|
|
@@ -228,7 +359,7 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
228
359
|
// }));
|
|
229
360
|
|
|
230
361
|
// console.log(`manageCall1`, manageCall1.call, debtAmount.toWei(), newDepositAmount.toWei());
|
|
231
|
-
|
|
362
|
+
|
|
232
363
|
// const proofIds: string[] = [STEP0, STEP1];
|
|
233
364
|
// const manageCalls: ManageCall[] = [manageCall0, manageCall1];
|
|
234
365
|
|
|
@@ -253,9 +384,9 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
253
384
|
// const minAmountWei = (minAmount).toWei();
|
|
254
385
|
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall minAmount: ${minAmount}`);
|
|
255
386
|
// const swapInfo = await avnuModule.getSwapInfo(
|
|
256
|
-
// quote,
|
|
257
|
-
// this.metadata.additionalInfo.vaultAllocator.address,
|
|
258
|
-
// 0,
|
|
387
|
+
// quote,
|
|
388
|
+
// this.metadata.additionalInfo.vaultAllocator.address,
|
|
389
|
+
// 0,
|
|
259
390
|
// this.address.address,
|
|
260
391
|
// minAmountWei
|
|
261
392
|
// );
|
|
@@ -271,7 +402,7 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
271
402
|
|
|
272
403
|
|
|
273
404
|
// // if the created debt, when added is collateral will put the total HF above min, but below (target + 0.05),
|
|
274
|
-
// // then lets close the looping cycle by adding this as collateral.
|
|
405
|
+
// // then lets close the looping cycle by adding this as collateral.
|
|
275
406
|
// const newCollateral = minAmount.plus(totalCollateral);
|
|
276
407
|
// const newHF = newCollateral.multipliedBy(collateralPrice).multipliedBy(legLTV).dividedBy(totalDebtAmount).dividedBy(debtPrice).toNumber();
|
|
277
408
|
// logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall newHF: ${newHF}`);
|
|
@@ -283,7 +414,7 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
283
414
|
// const manageCall4 = manage4Info.callConstructor({
|
|
284
415
|
// amount: minAmount
|
|
285
416
|
// });
|
|
286
|
-
|
|
417
|
+
|
|
287
418
|
// const STEP5 = getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, vesuAdapter.config.debt.symbol);
|
|
288
419
|
// const manage5Info = this.getProofs<VesuModifyPositionCallParams>(STEP5);
|
|
289
420
|
// const manageCall5 = manage5Info.callConstructor(VesuAdapter.getDefaultModifyPositionCallParams({
|
|
@@ -301,7 +432,7 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
301
432
|
// return manageCall;
|
|
302
433
|
// }
|
|
303
434
|
|
|
304
|
-
// todo unwind or not deposit when the yield is bad.
|
|
435
|
+
// todo unwind or not deposit when the yield is bad.
|
|
305
436
|
|
|
306
437
|
// async getLSTMultiplierRebalanceCall(): Promise<{ shouldRebalance: boolean, manageCalls: {vesuAdapter: VesuAdapter, manageCall: Call}[] }> {
|
|
307
438
|
// let shouldRebalance = false;
|
|
@@ -327,11 +458,11 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
327
458
|
// const healthFactor = await vesuAdapter.getHealthFactor();
|
|
328
459
|
|
|
329
460
|
// const collateralisation = await vesuAdapter.getCollateralization(this.config);
|
|
330
|
-
// logger.debug(`${this.getTag()}::getVesuMultiplyCall::${vesuAdapter.config.debt.symbol} existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
461
|
+
// logger.debug(`${this.getTag()}::getVesuMultiplyCall::${vesuAdapter.config.debt.symbol} existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
331
462
|
// existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
332
463
|
|
|
333
464
|
// // - Prices as seen by Vesu contracts, ideal for HF math
|
|
334
|
-
// // Price 1 is ok as fallback bcz that would relatively price the
|
|
465
|
+
// // Price 1 is ok as fallback bcz that would relatively price the
|
|
335
466
|
// // collateral and debt as equal.
|
|
336
467
|
// const collateralPrice = collateralisation[0].usdValue > 0 ? collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() : 1;
|
|
337
468
|
// const debtPrice = collateralisation[1].usdValue > 0 ? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() : 1;
|
|
@@ -341,11 +472,11 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
341
472
|
// const isHFTooLow = healthFactor < this.metadata.additionalInfo.minHealthFactor;
|
|
342
473
|
// const isHFTooHigh = healthFactor > this.metadata.additionalInfo.targetHealthFactor + 0.05;
|
|
343
474
|
// if (isHFTooLow || isHFTooHigh || 1) {
|
|
344
|
-
// // use unused collateral to target more.
|
|
475
|
+
// // use unused collateral to target more.
|
|
345
476
|
// const manageCall = await this._getAvnuDepositSwapLegCall({
|
|
346
477
|
// isDeposit: true,
|
|
347
478
|
// leg1DepositAmount: unusedBalance.amount,
|
|
348
|
-
// minHF: 1.02, // todo, shouldnt use this 1.02 HF, if there isn;t more looping left.
|
|
479
|
+
// minHF: 1.02, // todo, shouldnt use this 1.02 HF, if there isn;t more looping left.
|
|
349
480
|
// vesuAdapter
|
|
350
481
|
// })
|
|
351
482
|
// return { shouldRebalance: true, manageCall };
|
|
@@ -355,61 +486,17 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
355
486
|
// }
|
|
356
487
|
// }
|
|
357
488
|
|
|
358
|
-
// protected async getVesuAUM(adapter: VesuAdapter) {
|
|
359
|
-
// const legAUM = await adapter.getPositions(this.config);
|
|
360
|
-
// const underlying = this.asset();
|
|
361
|
-
// // assert its an LST of Endur
|
|
362
|
-
// assert(underlying.symbol.startsWith('x'), 'Underlying is not an LST of Endur');
|
|
363
|
-
|
|
364
|
-
// let vesuAum = Web3Number.fromWei("0", underlying.decimals);
|
|
365
|
-
|
|
366
|
-
// let tokenUnderlyingPrice = await this.getLSTExchangeRate();
|
|
367
|
-
// // to offset for usual DEX lag, we multiply by 0.998 (i.e. 0.2% loss)
|
|
368
|
-
// tokenUnderlyingPrice = tokenUnderlyingPrice * 0.998;
|
|
369
|
-
// logger.verbose(`${this.getTag()} tokenUnderlyingPrice: ${tokenUnderlyingPrice}`);
|
|
370
|
-
|
|
371
|
-
// // handle collateral
|
|
372
|
-
// if (legAUM[0].token.address.eq(underlying.address)) {
|
|
373
|
-
// vesuAum = vesuAum.plus(legAUM[0].amount);
|
|
374
|
-
// } else {
|
|
375
|
-
// vesuAum = vesuAum.plus(legAUM[0].amount.dividedBy(tokenUnderlyingPrice));
|
|
376
|
-
// }
|
|
377
489
|
|
|
378
|
-
// // handle debt
|
|
379
|
-
// if (legAUM[1].token.address.eq(underlying.address)) {
|
|
380
|
-
// vesuAum = vesuAum.minus(legAUM[1].amount);
|
|
381
|
-
// } else {
|
|
382
|
-
// vesuAum = vesuAum.minus(legAUM[1].amount.dividedBy(tokenUnderlyingPrice));
|
|
383
|
-
// };
|
|
384
|
-
|
|
385
|
-
// logger.verbose(`${this.getTag()} Vesu AUM: ${vesuAum}, legCollateral: ${legAUM[0].amount.toNumber()}, legDebt: ${legAUM[1].amount.toNumber()}`);
|
|
386
|
-
// return vesuAum;
|
|
387
|
-
// }
|
|
388
|
-
|
|
389
|
-
//
|
|
390
|
-
// private async _getMinOutputAmountLSTBuy(amountInUnderlying: Web3Number) {
|
|
391
|
-
// const lstTruePrice = await this.getLSTExchangeRate();
|
|
392
|
-
// // during buy, the purchase should always be <= true LST price.
|
|
393
|
-
// const minOutputAmount = amountInUnderlying.dividedBy(lstTruePrice).multipliedBy(0.99979); // minus 0.021% to account for avnu fees
|
|
394
|
-
// return new Web3Number(minOutputAmount.toString(), this.asset().decimals);
|
|
395
|
-
// }
|
|
396
|
-
|
|
397
|
-
// private async _getMinOutputAmountLSTSell(amountInLST: Web3Number) {
|
|
398
|
-
// const lstTruePrice = await this.getLSTExchangeRate();
|
|
399
|
-
// // during sell, the purchase should always be > 0.995 * true LST price.
|
|
400
|
-
// const minOutputAmount = amountInLST.multipliedBy(lstTruePrice).multipliedBy(0.995);
|
|
401
|
-
// return minOutputAmount;
|
|
402
|
-
// }
|
|
403
490
|
|
|
404
491
|
|
|
405
492
|
|
|
406
493
|
// todo add a function to findout max borrowable amount without fucking yield
|
|
407
|
-
// if the current net yield < LST yield, add a function to calculate how much to unwind.
|
|
494
|
+
// if the current net yield < LST yield, add a function to calculate how much to unwind.
|
|
408
495
|
|
|
409
496
|
/**
|
|
410
497
|
* Uses vesu's multiple call to create leverage on LST
|
|
411
498
|
* Deposit amount is in LST
|
|
412
|
-
* @param params
|
|
499
|
+
* @param params
|
|
413
500
|
*/
|
|
414
501
|
async getFundManagementCall(params: {
|
|
415
502
|
isDeposit: boolean,
|
|
@@ -418,268 +505,528 @@ export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings>
|
|
|
418
505
|
logger.verbose(`${this.getTag()}::getFundManagementCall params: ${JSON.stringify(params)}`);
|
|
419
506
|
// const legLTV = await vesuAdapter1.getLTVConfig(this.config);
|
|
420
507
|
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall legLTV: ${legLTV}`);
|
|
421
|
-
const
|
|
508
|
+
const multiplyAdapters = this.getVesuMultiplyAdapters();
|
|
422
509
|
if (!params.isDeposit) {
|
|
423
|
-
// try using unused balance to unwind.
|
|
424
|
-
// no need to unwind.
|
|
510
|
+
// try using unused balance to unwind.
|
|
511
|
+
// no need to unwind.
|
|
425
512
|
const unusedBalance = await this.getUnusedBalance();
|
|
426
513
|
logger.verbose(`${this.getTag()}::getVesuMultiplyCall unusedBalance: ${unusedBalance.amount.toString()}, required: ${params.leg1DepositAmount.toString()}`);
|
|
427
514
|
if (unusedBalance.amount.gte(params.leg1DepositAmount)) {
|
|
428
515
|
return null;
|
|
429
|
-
} else {
|
|
430
|
-
const adapters = await AdapterOptimizer.getAdapterToUse(allAdapters, false, params.leg1DepositAmount);
|
|
431
|
-
if (adapters.length > 0) {
|
|
432
|
-
const proofsInfo = adapters.map(adapter => adapter.getProofs(false, this.getMerkleTree()));
|
|
433
|
-
const calls: Call[] = [];
|
|
434
|
-
for (const info of proofsInfo) {
|
|
435
|
-
const proofGroups = info.proofs;
|
|
436
|
-
const call = this.getManageCall(proofGroups, await info.callConstructor({amount: params.leg1DepositAmount}));
|
|
437
|
-
calls.push(call);
|
|
438
|
-
}
|
|
439
|
-
return calls;
|
|
440
|
-
} else {
|
|
441
|
-
throw new Error(`${this.getTag()}::getVesuMultiplyCall: no adapters to use for unused balance: ${unusedBalance.amount.toString()}`);
|
|
442
|
-
}
|
|
443
516
|
}
|
|
444
517
|
}
|
|
445
518
|
|
|
446
|
-
const adapters = await AdapterOptimizer.getAdapterToUse(
|
|
519
|
+
const adapters = await AdapterOptimizer.getAdapterToUse(multiplyAdapters, params.isDeposit, params.leg1DepositAmount);
|
|
447
520
|
if (adapters.length > 0) {
|
|
448
|
-
const proofsInfo = adapters.map(adapter => adapter.getProofs(
|
|
521
|
+
const proofsInfo = adapters.map(adapter => adapter.getProofs(params.isDeposit, this.getMerkleTree()));
|
|
449
522
|
const calls: Call[] = [];
|
|
450
523
|
for (const info of proofsInfo) {
|
|
451
|
-
const
|
|
452
|
-
const call = this.getManageCall(
|
|
524
|
+
const manageCalls = await info.callConstructor({ amount: params.leg1DepositAmount });
|
|
525
|
+
const call = this.getManageCall(this.getProofGroupsForManageCalls(manageCalls), manageCalls);
|
|
453
526
|
calls.push(call);
|
|
454
527
|
}
|
|
455
528
|
return calls;
|
|
456
529
|
} else {
|
|
457
530
|
throw new Error(`${this.getTag()}::getVesuMultiplyCall: no adapters to use for deposit: ${params.leg1DepositAmount.toString()}`);
|
|
458
531
|
}
|
|
459
|
-
|
|
460
|
-
// const existingPositions = await vesuAdapter1.getPositions(this.config);
|
|
461
|
-
// const collateralisation = await vesuAdapter1.getCollateralization(this.config);
|
|
462
|
-
// const existingCollateralInfo = existingPositions[0];
|
|
463
|
-
// const existingDebtInfo = existingPositions[1];
|
|
464
|
-
// logger.debug(`${this.getTag()}::getVesuMultiplyCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
465
|
-
// existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
466
|
-
|
|
467
|
-
// // - Prices as seen by Vesu contracts, ideal for HF math
|
|
468
|
-
// // Price 1 is ok as fallback bcz that would relatively price the
|
|
469
|
-
// // collateral and debt as equal.
|
|
470
|
-
// const collateralPrice = collateralisation[0].usdValue > 0 ? collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() : 1;
|
|
471
|
-
// const debtPrice = collateralisation[1].usdValue > 0 ? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() : 1;
|
|
472
|
-
// logger.debug(`${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
|
|
473
|
-
|
|
474
|
-
// // - Prices as seen by actual swap price
|
|
475
|
-
// const dexPrice = await this.getLSTDexPrice();
|
|
476
|
-
|
|
477
|
-
// // compute optimal amount of collateral and debt post addition/removal
|
|
478
|
-
// // target hf = collateral * collateralPrice * ltv / debt * debtPrice
|
|
479
|
-
// // assuming X to be the usd amount of debt borrowed or repaied (negative).
|
|
480
|
-
// // target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv / (debt * debtPrice + X)
|
|
481
|
-
// // => X * target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv - (debt * debtPrice * target hf)
|
|
482
|
-
// // => X * (target hf - ltv)= ((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)
|
|
483
|
-
// // => X = (((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)) / (target hf - ltv)
|
|
484
|
-
// const addedCollateral = params.leg1DepositAmount
|
|
485
|
-
// .multipliedBy(params.isDeposit ? 1 : -1)
|
|
486
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall addedCollateral: ${addedCollateral}`);
|
|
487
|
-
// const numeratorPart1 = (existingCollateralInfo.amount.plus((addedCollateral))).multipliedBy(collateralPrice).multipliedBy(legLTV);
|
|
488
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}`);
|
|
489
|
-
// const numeratorPart2 = existingDebtInfo.amount.multipliedBy(debtPrice).multipliedBy(this.metadata.additionalInfo.targetHealthFactor);
|
|
490
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall numeratorPart2: ${numeratorPart2}`);
|
|
491
|
-
// const denominatorPart = this.metadata.additionalInfo.targetHealthFactor - (legLTV / dexPrice);
|
|
492
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall denominatorPart: ${denominatorPart}`);
|
|
493
|
-
// const x_debt_usd = numeratorPart1.minus(numeratorPart2).dividedBy(denominatorPart);
|
|
494
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall x_debt_usd: ${x_debt_usd}`);
|
|
495
|
-
// logger.debug(`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}, numeratorPart2: ${numeratorPart2}, denominatorPart: ${denominatorPart}`);
|
|
496
|
-
|
|
497
|
-
// // both in underlying
|
|
498
|
-
// const debtAmount = x_debt_usd.dividedBy(debtPrice);
|
|
499
|
-
// const marginAmount = addedCollateral;
|
|
500
|
-
// logger.verbose(`${this.getTag()}::getVesuMultiplyCall debtAmount: ${debtAmount}, marginAmount: ${marginAmount}`);
|
|
501
|
-
|
|
502
|
-
// // Cases of lever increase (within the scopr of this function)
|
|
503
|
-
// // 1. debtAmount > 0 and marginAmount > 0
|
|
504
|
-
// // 2. debtAmount > 0 and marginAmount < 0
|
|
505
|
-
|
|
506
|
-
// // Cases of lever decrease
|
|
507
|
-
// // 3. debtAmount < 0 and marginAmount > 0
|
|
508
|
-
// // 4. debtAmount < 0 and marginAmount < 0
|
|
509
|
-
// return this.getModifyLeverCall({
|
|
510
|
-
// marginAmount,
|
|
511
|
-
// debtAmount,
|
|
512
|
-
// lstDexPriceInUnderlying: dexPrice,
|
|
513
|
-
// isIncrease: debtAmount.greaterThan(0)
|
|
514
|
-
// });
|
|
515
532
|
}
|
|
516
533
|
|
|
517
|
-
|
|
518
534
|
getLSTUnderlyingTokenInfo() {
|
|
519
535
|
return this.metadata.additionalInfo.underlyingToken;
|
|
520
536
|
}
|
|
521
537
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
538
|
+
async getLSTAPR(_address: ContractAddr): Promise<number> {
|
|
539
|
+
try {
|
|
540
|
+
const apr = await LSTAPRService.getLSTAPR(this.getLSTUnderlyingTokenInfo().address);
|
|
541
|
+
if (!apr) {
|
|
542
|
+
throw new Error('Failed to get LST APR');
|
|
543
|
+
}
|
|
544
|
+
return apr;
|
|
545
|
+
} catch (error) {
|
|
546
|
+
logger.warn(`${this.getTag()}: Failed to get LST APR: ${error}`);
|
|
547
|
+
return 0;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
533
550
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
+
async netAPY(): Promise<{
|
|
552
|
+
net: number;
|
|
553
|
+
splits: { apy: number; id: string }[];
|
|
554
|
+
}> {
|
|
555
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
556
|
+
const maxNewDeposits = await this.maxNewDeposits({
|
|
557
|
+
isAPYComputation: true,
|
|
558
|
+
});
|
|
559
|
+
const lstAPY = await this.getLSTAPR(
|
|
560
|
+
this.getLSTUnderlyingTokenInfo().address,
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
// if unused balance is > max servicable from loan, we are limited by the max borrowing we can do
|
|
564
|
+
// we also allow accepting little higher deposits (1.5x) to have room for future looping when theres more liquidity or debt cap rises
|
|
565
|
+
if (maxNewDeposits * 1.5 < unusedBalance.amount.toNumber()) {
|
|
566
|
+
// we have excess, just use real APY
|
|
567
|
+
logger.verbose(
|
|
568
|
+
`${this.getTag()}::netAPY: unused balance is > max servicable from loan, lstAPY: ${lstAPY}`,
|
|
569
|
+
);
|
|
570
|
+
const { positions, baseAPYs, rewardAPYs } = await this.getVesuAPYs();
|
|
571
|
+
const unusedBalanceAPY = await this.getUnusedBalanceAPY();
|
|
572
|
+
baseAPYs.push(...[unusedBalanceAPY.apy]);
|
|
573
|
+
rewardAPYs.push(0);
|
|
574
|
+
|
|
575
|
+
const weights = positions.map((p: VaultPosition, index: number) => p.usdValue * (index % 2 == 0 ? 1 : -1));
|
|
576
|
+
weights.push(unusedBalanceAPY.weight);
|
|
577
|
+
|
|
578
|
+
const prevAUM = await this.getPrevAUM();
|
|
579
|
+
const price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
|
|
580
|
+
const prevAUMUSD = prevAUM.multipliedBy(price.price);
|
|
581
|
+
const output = await this.returnNetAPY(baseAPYs, rewardAPYs, weights, prevAUMUSD);
|
|
582
|
+
output.splits.push({ apy: lstAPY, id: "lst_apy" });
|
|
583
|
+
return output;
|
|
584
|
+
} else {
|
|
585
|
+
// we have little bit room to accept more deposits, we use theoretical max APY
|
|
586
|
+
logger.verbose(
|
|
587
|
+
`${this.getTag()}::netAPY: we can take more deposits, use theoretical max APY`,
|
|
588
|
+
);
|
|
589
|
+
const { positions, baseAPYs, rewardAPYs } = await this.getVesuAPYs();
|
|
590
|
+
const weights = positions.map(
|
|
591
|
+
(p: VaultPosition, index: number) => p.usdValue * (index % 2 == 0 ? 1 : -1),
|
|
592
|
+
);
|
|
593
|
+
const aum = weights.reduce((acc: number, curr: number) => acc + curr, 0);
|
|
594
|
+
const output = await this.returnNetAPY(
|
|
595
|
+
baseAPYs,
|
|
596
|
+
rewardAPYs,
|
|
597
|
+
weights,
|
|
598
|
+
new Web3Number(
|
|
599
|
+
aum.toFixed(9),
|
|
600
|
+
this.getLSTUnderlyingTokenInfo().decimals,
|
|
601
|
+
),
|
|
602
|
+
);
|
|
603
|
+
output.splits.push({ apy: lstAPY, id: "lst_apy" });
|
|
604
|
+
return output;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
551
607
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
608
|
+
async getVesuAPYs() {
|
|
609
|
+
const vesuAdapters = this.getVesuAdapters();
|
|
610
|
+
const allVesuPools = await VesuAdapter.getVesuPools();
|
|
611
|
+
const pools = vesuAdapters.map((vesuAdapter) => {
|
|
612
|
+
return allVesuPools.pools.find(p => vesuAdapter.config.poolId.eqString(num.getHexString(p.id)));
|
|
613
|
+
});
|
|
614
|
+
logger.verbose(`${this.metadata.name}::netAPY: vesu-pools: ${JSON.stringify(pools)}`);
|
|
615
|
+
if (pools.some(p => !p)) {
|
|
616
|
+
throw new Error('Pool not found');
|
|
617
|
+
}
|
|
618
|
+
const positions = await this.getVesuPositions();
|
|
619
|
+
logger.verbose(`${this.metadata.name}::netAPY: positions: ${JSON.stringify(positions)}`);
|
|
620
|
+
const baseAPYs: number[] = [];
|
|
621
|
+
const rewardAPYs: number[] = [];
|
|
622
|
+
|
|
623
|
+
for (const [index, pool] of pools.entries()) {
|
|
624
|
+
const vesuAdapter = vesuAdapters[index];
|
|
625
|
+
const collateralAsset = pool.assets.find((a: any) => a.symbol.toLowerCase() === vesuAdapter.config.collateral.symbol.toLowerCase())?.stats!;
|
|
626
|
+
const debtAsset = pool.assets.find((a: any) => a.symbol.toLowerCase() === vesuAdapter.config.debt.symbol.toLowerCase())?.stats!;
|
|
627
|
+
const supplyApy = Number(collateralAsset.supplyApy.value || 0) / 1e18;
|
|
628
|
+
|
|
629
|
+
const lstAPY = await this.getLSTAPR(vesuAdapter.config.collateral.address);
|
|
630
|
+
logger.verbose(`${this.metadata.name}::netAPY: ${vesuAdapter.config.collateral.symbol} LST APR from Endur: ${lstAPY}`);
|
|
631
|
+
|
|
632
|
+
baseAPYs.push(...[supplyApy + lstAPY, Number(debtAsset.borrowApr.value) / 1e18]);
|
|
633
|
+
rewardAPYs.push(...[Number(collateralAsset.defiSpringSupplyApr?.value || "0") / 1e18, 0]);
|
|
634
|
+
}
|
|
635
|
+
logger.verbose(`${this.metadata.name}::netAPY: baseAPYs: ${JSON.stringify(baseAPYs)}`);
|
|
636
|
+
logger.verbose(`${this.metadata.name}::netAPY: rewardAPYs: ${JSON.stringify(rewardAPYs)}`);
|
|
557
637
|
|
|
558
|
-
|
|
559
|
-
// // Dont compute precise max swappable for APY computation
|
|
560
|
-
// if (vesuAdapter.config.debt.address.eq(this.getLSTUnderlyingTokenInfo().address) || isAPYComputation) {
|
|
561
|
-
// return {amount: maxBorrowable, dexSwappableAmount: maxBorrowable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
|
|
562
|
-
// }
|
|
563
|
-
// // Want < 0.02% slippage
|
|
564
|
-
// try {
|
|
565
|
-
// const maxSwappable = await this.getMaxSwappableWithMaxSlippage(vesuAdapter.config.debt, this.getLSTUnderlyingTokenInfo(), 0.0002, maxBorrowable);
|
|
566
|
-
// return {amount: maxBorrowable.minimum(maxSwappable), dexSwappableAmount: maxSwappable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
|
|
567
|
-
// } catch (error) {
|
|
568
|
-
// logger.warn(`${this.getTag()}: Failed to get max swappable: ${error}`);
|
|
569
|
-
// const maxSwappable = Web3Number.fromWei("0", vesuAdapter.config.debt.decimals);
|
|
570
|
-
// return {amount: maxBorrowable.minimum(maxSwappable), dexSwappableAmount: maxSwappable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
|
|
571
|
-
// }
|
|
572
|
-
// }
|
|
638
|
+
assert(baseAPYs.length == positions.length, 'APYs and positions length mismatch');
|
|
573
639
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
// * @returns Promise<number> The LST APR (not divided by 1e18)
|
|
581
|
-
// */
|
|
582
|
-
// async getLSTAPR(_address: ContractAddr): Promise<number> {
|
|
583
|
-
// try {
|
|
584
|
-
// const vesuAdapter1 = this.getVesuSameTokenAdapter();
|
|
585
|
-
// const apr = await LSTAPRService.getLSTAPR(vesuAdapter1.config.debt.address);
|
|
586
|
-
// if (!apr) {
|
|
587
|
-
// throw new Error('Failed to get LST APR');
|
|
588
|
-
// }
|
|
589
|
-
// return apr;
|
|
590
|
-
// } catch (error) {
|
|
591
|
-
// logger.warn(`${this.getTag()}: Failed to get LST APR: ${error}`);
|
|
592
|
-
// return 0;
|
|
593
|
-
// }
|
|
594
|
-
// }
|
|
640
|
+
return {
|
|
641
|
+
baseAPYs,
|
|
642
|
+
rewardAPYs,
|
|
643
|
+
positions
|
|
644
|
+
}
|
|
645
|
+
}
|
|
595
646
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
647
|
+
protected async returnNetAPY(baseAPYs: number[], rewardAPYs: number[], weights: number[], prevAUMUSD: Web3Number) {
|
|
648
|
+
if (weights.every(p => p == 0)) {
|
|
649
|
+
return {
|
|
650
|
+
net: 0, splits: [{
|
|
651
|
+
apy: 0, id: 'base'
|
|
652
|
+
}, {
|
|
653
|
+
apy: 0, id: 'defispring'
|
|
654
|
+
}]
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const baseAPY = this.computeAPY(baseAPYs, weights, prevAUMUSD);
|
|
659
|
+
const rewardAPY = this.computeAPY(rewardAPYs, weights, prevAUMUSD);
|
|
660
|
+
const netAPY = baseAPY + rewardAPY;
|
|
661
|
+
logger.verbose(`${this.metadata.name}::netAPY: net: ${netAPY}, baseAPY: ${baseAPY}, rewardAPY: ${rewardAPY}`);
|
|
662
|
+
return {
|
|
663
|
+
net: netAPY, splits: [{
|
|
664
|
+
apy: baseAPY, id: 'base'
|
|
665
|
+
}, {
|
|
666
|
+
apy: rewardAPY, id: 'defispring'
|
|
667
|
+
}]
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
protected async getUnusedBalanceAPY() {
|
|
672
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
673
|
+
const vesuAdapter = this.getVesuSameTokenAdapter();
|
|
674
|
+
const underlying = vesuAdapter.config.debt;
|
|
675
|
+
const lstAPY = await this.getLSTAPR(underlying.address);
|
|
676
|
+
return {
|
|
677
|
+
apy: lstAPY,
|
|
678
|
+
weight: unusedBalance.usdValue
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
private computeAPY(apys: number[], weights: number[], currentAUM: Web3Number) {
|
|
683
|
+
assert(apys.length === weights.length, "APYs and weights length mismatch");
|
|
684
|
+
const weightedSum = apys.reduce((acc, apy, i) => acc + apy * weights[i], 0);
|
|
685
|
+
logger.verbose(`${this.getTag()} computeAPY: apys: ${JSON.stringify(apys)}, weights: ${JSON.stringify(weights)}, weightedSum: ${weightedSum}, currentAUM: ${currentAUM}`);
|
|
686
|
+
return weightedSum / currentAUM.toNumber();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
getVesuAdapters(): VesuAdapter[] {
|
|
690
|
+
const multiply = this.getVesuMultiplyAdapters();
|
|
691
|
+
return multiply.map(m => m._vesuAdapter);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
async getVesuPositions(blockNumber: BlockIdentifier = 'latest'): Promise<VaultPosition[]> {
|
|
695
|
+
const adapters = this.getVesuAdapters();
|
|
696
|
+
const positions: VaultPosition[] = [];
|
|
697
|
+
for (const adapter of adapters) {
|
|
698
|
+
positions.push(...await adapter.getPositions(this.config, blockNumber));
|
|
699
|
+
}
|
|
700
|
+
return positions;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async getUnusedBalance(): Promise<SingleTokenInfo> {
|
|
704
|
+
const balance = await (new ERC20(this.config)).balanceOf(this.asset().address, this.metadata.additionalInfo.vaultAllocator, this.asset().decimals);
|
|
705
|
+
const price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
|
|
706
|
+
const usdValue = Number(balance.toFixed(6)) * price.price;
|
|
707
|
+
return {
|
|
708
|
+
tokenInfo: this.asset(),
|
|
709
|
+
amount: balance,
|
|
710
|
+
usdValue
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async maxNewDeposits(
|
|
715
|
+
params: { isAPYComputation: boolean } = { isAPYComputation: false },
|
|
716
|
+
) {
|
|
717
|
+
const maxBorrowableAmounts = await this.getMaxBorrowableAmount(params);
|
|
718
|
+
let numerator = 0;
|
|
719
|
+
|
|
720
|
+
for (let adapter of this.getVesuAdapters()) {
|
|
721
|
+
const maxBorrowableAmountInfo = maxBorrowableAmounts.maxBorrowables.find(
|
|
722
|
+
(b) => b.borrowableAsset.address.eq(adapter.config.debt.address),
|
|
723
|
+
);
|
|
724
|
+
if (!maxBorrowableAmountInfo || !maxBorrowableAmountInfo?.amount) {
|
|
725
|
+
throw new Error(
|
|
726
|
+
`Max borrowable amount not found for adapter: ${adapter.config.debt.symbol}`,
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
numerator +=
|
|
731
|
+
(this.metadata.additionalInfo.targetHealthFactor *
|
|
732
|
+
maxBorrowableAmountInfo.amount.toNumber()) /
|
|
733
|
+
maxBorrowableAmountInfo.ltv;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return numerator - maxBorrowableAmounts.netMaxBorrowableAmount.toNumber();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async getMaxBorrowableAmount(
|
|
740
|
+
params: { isAPYComputation: boolean } = { isAPYComputation: false },
|
|
741
|
+
) {
|
|
742
|
+
const vesuAdapters = this.getVesuAdapters();
|
|
743
|
+
let netMaxBorrowableAmount = Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals);
|
|
744
|
+
const maxBorrowables: { amount: Web3Number, dexSwappableAmount: Web3Number, maxBorrowableAmount: Web3Number, borrowableAsset: TokenInfo, ltv: number, poolId: ContractAddr }[] = [];
|
|
745
|
+
for (const vesuAdapter of vesuAdapters) {
|
|
746
|
+
const output = await this.getMaxBorrowableAmountByVesuAdapter(
|
|
747
|
+
vesuAdapter,
|
|
748
|
+
params.isAPYComputation,
|
|
749
|
+
);
|
|
750
|
+
const ltv = await vesuAdapter.getLTVConfig(this.config);
|
|
751
|
+
maxBorrowables.push({ ...output, ltv, poolId: vesuAdapter.config.poolId });
|
|
752
|
+
}
|
|
753
|
+
maxBorrowables.sort((a, b) => b.amount.toNumber() - a.amount.toNumber());
|
|
754
|
+
netMaxBorrowableAmount = maxBorrowables.reduce(
|
|
755
|
+
(acc, curr) => acc.plus(curr.amount),
|
|
756
|
+
Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals),
|
|
757
|
+
);
|
|
758
|
+
return { netMaxBorrowableAmount, maxBorrowables };
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
async getMaxSwappableWithMaxSlippage(
|
|
762
|
+
fromToken: TokenInfo,
|
|
763
|
+
toToken: TokenInfo,
|
|
764
|
+
maxSlippage: number,
|
|
765
|
+
maxAmount: Web3Number,
|
|
766
|
+
) {
|
|
767
|
+
const output = await findMaxInputWithSlippage({
|
|
768
|
+
apiGetOutput: async (inputAmount: number): Promise<number> => {
|
|
769
|
+
const ekuboQuoter = new EkuboQuoter(this.config, this.pricer);
|
|
770
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // artificial delay, to avoid rate limit
|
|
771
|
+
const quote = await ekuboQuoter.getQuoteExactInput(
|
|
772
|
+
fromToken.address.address,
|
|
773
|
+
toToken.address.address,
|
|
774
|
+
new Web3Number(inputAmount.toFixed(9), fromToken.decimals),
|
|
775
|
+
);
|
|
776
|
+
return Web3Number.fromWei(
|
|
777
|
+
quote.total_calculated.toString(),
|
|
778
|
+
toToken.decimals,
|
|
779
|
+
).toNumber();
|
|
780
|
+
},
|
|
781
|
+
maxInput: maxAmount.toNumber(),
|
|
782
|
+
maxSlippagePercent: maxSlippage,
|
|
783
|
+
tolerance: 0.001,
|
|
784
|
+
referenceRate: 1,
|
|
785
|
+
});
|
|
786
|
+
return new Web3Number(output.optimalInput, fromToken.decimals);
|
|
787
|
+
}
|
|
619
788
|
|
|
620
|
-
async
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
789
|
+
async getMaxBorrowableAmountByVesuAdapter(
|
|
790
|
+
vesuAdapter: VesuAdapter,
|
|
791
|
+
isAPYComputation: boolean,
|
|
792
|
+
) {
|
|
793
|
+
const lstAPY = await this.getLSTAPR(
|
|
794
|
+
this.getLSTUnderlyingTokenInfo().address,
|
|
795
|
+
);
|
|
796
|
+
const maxInterestRate = lstAPY * 0.8;
|
|
797
|
+
const { maxDebtToHave: maxBorrowableAmount, currentDebt } =
|
|
798
|
+
await vesuAdapter.getMaxBorrowableByInterestRate(
|
|
799
|
+
this.config,
|
|
800
|
+
vesuAdapter.config.debt,
|
|
801
|
+
maxInterestRate,
|
|
802
|
+
);
|
|
803
|
+
const debtCap = await vesuAdapter.getDebtCap(this.config);
|
|
804
|
+
|
|
805
|
+
if (currentDebt.gte(debtCap)) {
|
|
806
|
+
return {
|
|
807
|
+
amount: Web3Number.fromWei("0", vesuAdapter.config.debt.decimals),
|
|
808
|
+
dexSwappableAmount: Web3Number.fromWei(
|
|
809
|
+
"0",
|
|
810
|
+
vesuAdapter.config.debt.decimals,
|
|
811
|
+
),
|
|
812
|
+
maxBorrowableAmount: Web3Number.fromWei(
|
|
813
|
+
"0",
|
|
814
|
+
vesuAdapter.config.debt.decimals,
|
|
815
|
+
),
|
|
816
|
+
borrowableAsset: vesuAdapter.config.debt,
|
|
817
|
+
};
|
|
625
818
|
}
|
|
626
819
|
|
|
627
|
-
const
|
|
628
|
-
|
|
820
|
+
const availableToBorrow = debtCap.minus(currentDebt);
|
|
821
|
+
|
|
822
|
+
const maxBorrowable = maxBorrowableAmount
|
|
823
|
+
.minimum(availableToBorrow)
|
|
824
|
+
.multipliedBy(0.999);
|
|
825
|
+
// Dont compute precise max swappable for APY computation
|
|
826
|
+
if (
|
|
827
|
+
vesuAdapter.config.debt.address.eq(
|
|
828
|
+
this.getLSTUnderlyingTokenInfo().address,
|
|
829
|
+
) ||
|
|
830
|
+
isAPYComputation
|
|
831
|
+
) {
|
|
832
|
+
return {
|
|
833
|
+
amount: maxBorrowable,
|
|
834
|
+
dexSwappableAmount: maxBorrowable,
|
|
835
|
+
maxBorrowableAmount: maxBorrowable,
|
|
836
|
+
borrowableAsset: vesuAdapter.config.debt,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
// Want < 0.02% slippage
|
|
840
|
+
try {
|
|
841
|
+
const maxSwappable = await this.getMaxSwappableWithMaxSlippage(
|
|
842
|
+
vesuAdapter.config.debt,
|
|
843
|
+
this.getLSTUnderlyingTokenInfo(),
|
|
844
|
+
0.0002,
|
|
845
|
+
maxBorrowable,
|
|
846
|
+
);
|
|
847
|
+
return {
|
|
848
|
+
amount: maxBorrowable.minimum(maxSwappable),
|
|
849
|
+
dexSwappableAmount: maxSwappable,
|
|
850
|
+
maxBorrowableAmount: maxBorrowable,
|
|
851
|
+
borrowableAsset: vesuAdapter.config.debt,
|
|
852
|
+
};
|
|
853
|
+
} catch (error) {
|
|
854
|
+
logger.warn(`${this.getTag()}: Failed to get max swappable: ${error}`);
|
|
855
|
+
const maxSwappable = Web3Number.fromWei(
|
|
856
|
+
"0",
|
|
857
|
+
vesuAdapter.config.debt.decimals,
|
|
858
|
+
);
|
|
859
|
+
return {
|
|
860
|
+
amount: maxBorrowable.minimum(maxSwappable),
|
|
861
|
+
dexSwappableAmount: maxSwappable,
|
|
862
|
+
maxBorrowableAmount: maxBorrowable,
|
|
863
|
+
borrowableAsset: vesuAdapter.config.debt,
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
async getAUM(unrealizedAUM: boolean = false): Promise<{ net: SingleTokenInfo, prevAum: Web3Number, splits: PositionInfo[] }> {
|
|
869
|
+
const underlying = this.asset();
|
|
870
|
+
assert(underlying.symbol.startsWith('x'), 'Underlying is not an LST of Endur');
|
|
871
|
+
|
|
872
|
+
// dynamic computation of aum based on price type
|
|
873
|
+
const priceType = unrealizedAUM ? LSTPriceType.ENDUR_PRICE : LSTPriceType.AVNU_PRICE;
|
|
874
|
+
|
|
875
|
+
// get dex rate
|
|
876
|
+
let tokenUnderlyingPrice: number;
|
|
877
|
+
const avnuRate = await this.getLSTAvnuRate();
|
|
878
|
+
if (avnuRate === 0) {
|
|
879
|
+
throw new Error(`${this.getTag()}::getAUM: tokenUnderlyingPrice (Avnu) is 0`);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// get token underlying price
|
|
883
|
+
if (priceType === LSTPriceType.ENDUR_PRICE) {
|
|
884
|
+
tokenUnderlyingPrice = await this.getLSTExchangeRate();
|
|
885
|
+
if (tokenUnderlyingPrice === 0) {
|
|
886
|
+
throw new Error(`${this.getTag()}::getAUM: tokenUnderlyingPrice (Endur) is 0`);
|
|
887
|
+
}
|
|
888
|
+
} else {
|
|
889
|
+
tokenUnderlyingPrice = avnuRate;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// check if endur and avnu prices differ by more than 2%
|
|
893
|
+
const diff = Math.abs(tokenUnderlyingPrice - avnuRate) / tokenUnderlyingPrice;
|
|
894
|
+
if (diff > 0.02) {
|
|
895
|
+
throw new Error(
|
|
896
|
+
`${this.getTag()}::getAUM: Endur and Avnu prices differ by more than 2% (Endur: ${tokenUnderlyingPrice}, Avnu: ${avnuRate})`,
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// get all positions
|
|
901
|
+
const allPositions = await this.getVaultPositions();
|
|
902
|
+
|
|
903
|
+
// compute net aum
|
|
904
|
+
let netAUM = new Web3Number(0, underlying.decimals);
|
|
629
905
|
for (let position of allPositions) {
|
|
630
|
-
if (position.
|
|
906
|
+
if (position.token.address.eq(underlying.address)) {
|
|
631
907
|
netAUM = netAUM.plus(position.amount);
|
|
908
|
+
} else if (position.token.address.eq(this.getLSTUnderlyingTokenInfo().address)) {
|
|
909
|
+
netAUM = netAUM.plus(position.amount.dividedBy(tokenUnderlyingPrice));
|
|
632
910
|
} else {
|
|
633
|
-
|
|
911
|
+
throw new Error(`${this.getTag()}::getAUM: unknown token: ${position.token.symbol}`);
|
|
634
912
|
}
|
|
635
913
|
}
|
|
636
914
|
|
|
915
|
+
const assetPrice = await this.pricer.getPrice(underlying.symbol);
|
|
637
916
|
const prevAum = await this.getPrevAUM();
|
|
917
|
+
const priceTypeLabel = priceType === LSTPriceType.ENDUR_PRICE ? "Endur Price" : "Avnu Price";
|
|
918
|
+
logger.verbose(`${this.getTag()} AUM (${priceTypeLabel}): ${netAUM}`);
|
|
919
|
+
|
|
638
920
|
const realAUM: PositionInfo = {
|
|
639
|
-
tokenInfo:
|
|
921
|
+
tokenInfo: underlying,
|
|
640
922
|
amount: netAUM,
|
|
641
923
|
usdValue: netAUM.toNumber() * assetPrice.price,
|
|
642
|
-
apy: {apy:
|
|
924
|
+
apy: { apy: 0, type: APYType.BASE }, // VT: Dont remember why this field exists here. FOr now, set it to 0.
|
|
643
925
|
remarks: AUMTypes.FINALISED,
|
|
644
|
-
protocol: Protocols.NONE
|
|
926
|
+
protocol: Protocols.NONE
|
|
645
927
|
};
|
|
646
928
|
|
|
647
929
|
const estimatedAUMDelta: PositionInfo = {
|
|
648
|
-
tokenInfo:
|
|
649
|
-
amount: Web3Number.fromWei('0',
|
|
930
|
+
tokenInfo: underlying,
|
|
931
|
+
amount: Web3Number.fromWei('0', underlying.decimals),
|
|
650
932
|
usdValue: 0,
|
|
651
|
-
apy: {apy: 0, type: APYType.BASE},
|
|
933
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
652
934
|
remarks: AUMTypes.DEFISPRING,
|
|
653
|
-
protocol: Protocols.NONE
|
|
935
|
+
protocol: Protocols.NONE
|
|
654
936
|
};
|
|
655
937
|
|
|
656
|
-
return {
|
|
938
|
+
return {
|
|
939
|
+
net: {
|
|
940
|
+
tokenInfo: underlying,
|
|
941
|
+
amount: netAUM,
|
|
942
|
+
usdValue: netAUM.toNumber() * assetPrice.price
|
|
943
|
+
}, prevAum: prevAum, splits: [realAUM, estimatedAUMDelta]
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
async getTVLUnrealized() {
|
|
948
|
+
return await this.getAUM(true);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
async getUserUnrealizedGains(user: ContractAddr) {
|
|
952
|
+
const tvl = await this.getTVL();
|
|
953
|
+
const unrealizedTVL = await this.getTVLUnrealized();
|
|
954
|
+
const unrealizedDiff = unrealizedTVL.net.amount.minus(tvl.amount);
|
|
955
|
+
const userTVL = await this.getUserTVL(user);
|
|
956
|
+
const userShare = userTVL.amount.dividedBy(tvl.amount);
|
|
957
|
+
const unrealizedGains = unrealizedDiff.multipliedBy(userShare);
|
|
958
|
+
|
|
959
|
+
return {
|
|
960
|
+
unrealizedGains,
|
|
961
|
+
userShare: userShare.toNumber(),
|
|
657
962
|
tokenInfo: this.asset(),
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
async getUserPositionCards(input: UserPositionCardsInput): Promise<UserPositionCard[]> {
|
|
967
|
+
const cards = await super.getUserPositionCards(input);
|
|
968
|
+
|
|
969
|
+
try {
|
|
970
|
+
const [unrealizedResult, userTVL, realizedApyRaw] = await Promise.all([
|
|
971
|
+
this.getUserUnrealizedGains(input.user),
|
|
972
|
+
this.getUserTVL(input.user),
|
|
973
|
+
this.getUserRealizedAPY().catch(() => null),
|
|
974
|
+
]);
|
|
975
|
+
const amount = unrealizedResult.unrealizedGains;
|
|
976
|
+
let usdValue = 0;
|
|
977
|
+
const userAmount = userTVL.amount.toNumber();
|
|
978
|
+
if (Number.isFinite(userAmount) && userAmount > 0) {
|
|
979
|
+
usdValue = (userTVL.usdValue / userAmount) * amount.toNumber();
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
cards.push({
|
|
983
|
+
title: "Unrealized Gains",
|
|
984
|
+
tooltip: "Unrealized gains based on current market prices vs Endur prices. If you withdraw now, you will forgo these gains.",
|
|
985
|
+
value: this.formatTokenAmountForCard(amount, unrealizedResult.tokenInfo),
|
|
986
|
+
subValue: `≈ ${this.formatUSDForCard(usdValue)}`,
|
|
987
|
+
subValueColor: this.getSubValueColorFromSignedNumber(usdValue),
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
const realizedApy = typeof realizedApyRaw === "number"
|
|
991
|
+
? this.formatPercentForCard(realizedApyRaw)
|
|
992
|
+
: "N/A";
|
|
993
|
+
cards.push({
|
|
994
|
+
title: "Realized APY",
|
|
995
|
+
tooltip: this.metadata.realizedApyMethodology || "Realized APY is based on past 14 days performance",
|
|
996
|
+
value: realizedApy,
|
|
997
|
+
});
|
|
998
|
+
} catch (error) {
|
|
999
|
+
logger.warn(`${this.getTag()}::getUserPositionCards unrealized gains fallback`, error);
|
|
1000
|
+
cards.push({
|
|
1001
|
+
title: "Unrealized Gains",
|
|
1002
|
+
tooltip: "Unrealized gains based on current market prices vs Endur prices. If you withdraw now, you will forgo these gains.",
|
|
1003
|
+
value: this.formatTokenAmountForCard(
|
|
1004
|
+
Web3Number.fromWei("0", this.asset().decimals),
|
|
1005
|
+
this.asset()
|
|
1006
|
+
),
|
|
1007
|
+
subValue: `≈ ${this.formatUSDForCard(0)}`,
|
|
1008
|
+
subValueColor: "default",
|
|
1009
|
+
});
|
|
1010
|
+
cards.push({
|
|
1011
|
+
title: "Realized APY",
|
|
1012
|
+
tooltip: this.metadata.realizedApyMethodology || "Realized APY is based on past 14 days performance",
|
|
1013
|
+
value: "N/A",
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (this.asset().symbol === "xSTRK") {
|
|
1018
|
+
const index = cards.findIndex((card) => card.title === "Lifetime Earnings");
|
|
1019
|
+
if (index >= 0) {
|
|
1020
|
+
cards[index] = {
|
|
1021
|
+
...cards[index],
|
|
1022
|
+
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.",
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
return cards;
|
|
661
1028
|
}
|
|
662
1029
|
|
|
663
|
-
/**
|
|
664
|
-
*
|
|
665
|
-
* @param params marginAmount is in LST, debtAmount is in underlying
|
|
666
|
-
*/
|
|
667
|
-
// async getModifyLeverCall(params: {
|
|
668
|
-
// marginAmount: Web3Number, // >0 during deposit
|
|
669
|
-
// debtAmount: Web3Number,
|
|
670
|
-
// lstDexPriceInUnderlying: number,
|
|
671
|
-
// isIncrease: boolean
|
|
672
|
-
// }): Promise<Call[]> {
|
|
673
|
-
// logger.verbose(`${this.getTag()}::getModifyLeverCall marginAmount: ${params.marginAmount}, debtAmount: ${params.debtAmount}, lstDexPriceInUnderlying: ${params.lstDexPriceInUnderlying}, isIncrease: ${params.isIncrease}`);
|
|
674
|
-
|
|
675
|
-
// const vesuAdapter = this.getVesuSameTokenAdapter();
|
|
676
|
-
// const manage0Info = vesuAdapter.
|
|
677
|
-
// const manageCall0 = manage0Info.callConstructor({
|
|
678
|
-
// amount: newDepositAmount
|
|
679
|
-
// });
|
|
680
|
-
// const manageCalls = await vesu
|
|
681
|
-
// return [this.getManageCall(proofsIDs, manageCalls)];
|
|
682
|
-
// }
|
|
683
1030
|
}
|
|
684
1031
|
|
|
685
1032
|
export default function VaultDescription(
|
|
@@ -689,7 +1036,6 @@ export default function VaultDescription(
|
|
|
689
1036
|
const containerStyle = {
|
|
690
1037
|
maxWidth: "800px",
|
|
691
1038
|
margin: "0 auto",
|
|
692
|
-
backgroundColor: "#111",
|
|
693
1039
|
color: "#eee",
|
|
694
1040
|
fontFamily: "Arial, sans-serif",
|
|
695
1041
|
borderRadius: "12px",
|
|
@@ -705,7 +1051,7 @@ export default function VaultDescription(
|
|
|
705
1051
|
</p>
|
|
706
1052
|
|
|
707
1053
|
<p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
|
|
708
|
-
This vault uses Vesu for lending and borrowing. The oracle used by this pool is a {highlightTextWithLinks("conversion rate oracle", [{highlight: "conversion rate oracle", link: "https://docs.pragma.build/starknet/development#conversion-rate"}])}
|
|
1054
|
+
This vault uses Vesu for lending and borrowing. The oracle used by this pool is a {highlightTextWithLinks("conversion rate oracle", [{ highlight: "conversion rate oracle", link: "https://docs.pragma.build/starknet/development#conversion-rate" }])}
|
|
709
1055
|
{" "}which is resilient to liquidity issues and price volatility, hence reducing 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.
|
|
710
1056
|
</p>
|
|
711
1057
|
|
|
@@ -716,7 +1062,7 @@ export default function VaultDescription(
|
|
|
716
1062
|
</div>
|
|
717
1063
|
<div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
|
|
718
1064
|
<p style={{ fontSize: "13px", color: "#ccc" }}>
|
|
719
|
-
<strong>Debt limits:</strong> Pools on Vesu have debt caps that are gradually increased over time. Until caps are raised, deposited
|
|
1065
|
+
<strong>Debt limits:</strong> Pools on Vesu have debt caps that are gradually increased over time. Until caps are raised, deposited LSTs remain in the vault, generating a shared net return for all depositors. There is no additional fee taken by Troves on LST APY, its only on added gain.
|
|
720
1066
|
</p>
|
|
721
1067
|
</div>
|
|
722
1068
|
{/* <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
|
|
@@ -733,69 +1079,82 @@ function getDescription(tokenSymbol: string, underlyingSymbol: string) {
|
|
|
733
1079
|
return VaultDescription(tokenSymbol, underlyingSymbol);
|
|
734
1080
|
}
|
|
735
1081
|
|
|
736
|
-
|
|
737
|
-
enum LST_MULTIPLIER_MANAGE_IDS {
|
|
738
|
-
MULTIPLE_APPROVE = 'multiple_approve',
|
|
739
|
-
MULTIPLY_VESU = 'multiply_vesu',
|
|
740
|
-
SWITCH_DELEGATION_ON = 'switch_delegation_on',
|
|
741
|
-
SWITCH_DELEGATION_OFF = 'switch_delegation_off',
|
|
742
|
-
AVNU_MULTIPLY_APPROVE_DEPOSIT = 'avnu_mul_approve_dep',
|
|
743
|
-
AVNU_MULTIPLY_SWAP_DEPOSIT = 'avnu_mul_swap_dep',
|
|
744
|
-
AVNU_MULTIPLY_APPROVE_WITHDRAW = 'avnu_mul_approve_withdr',
|
|
745
|
-
AVNU_MULTIPLY_SWAP_WITHDRAW = 'avnu_mul_swap_withdr',
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
function getAvnuManageIDs(baseID: LST_MULTIPLIER_MANAGE_IDS, debtTokenSymbol: string) {
|
|
749
|
-
return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
function getVesuLegId(baseID: string, debtTokenSymbol: string) {
|
|
753
|
-
return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
1082
|
function getLooperSettings(
|
|
757
1083
|
lstSymbol: string,
|
|
758
1084
|
underlyingSymbol: string,
|
|
759
1085
|
vaultSettings: HyperLSTStrategySettings,
|
|
760
|
-
pool1: ContractAddr,
|
|
761
1086
|
) {
|
|
762
1087
|
vaultSettings.leafAdapters = [];
|
|
1088
|
+
vaultSettings.adapters = [];
|
|
763
1089
|
|
|
764
1090
|
const lstToken = Global.getDefaultTokens().find(token => token.symbol === lstSymbol)!;
|
|
765
1091
|
const underlyingToken = Global.getDefaultTokens().find(token => token.symbol === underlyingSymbol)!;
|
|
766
1092
|
|
|
767
1093
|
const baseAdapterConfig: BaseAdapterConfig = {
|
|
768
1094
|
baseToken: lstToken,
|
|
769
|
-
supportedPositions: [{asset: lstToken, isDebt: false}],
|
|
1095
|
+
supportedPositions: [{ asset: lstToken, isDebt: false }, { asset: underlyingToken, isDebt: true }],
|
|
770
1096
|
networkConfig: getMainnetConfig(),
|
|
771
1097
|
pricer: new PricerFromApi(getMainnetConfig(), Global.getDefaultTokens()),
|
|
772
1098
|
vaultAllocator: vaultSettings.vaultAllocator,
|
|
773
1099
|
vaultAddress: vaultSettings.vaultAddress
|
|
774
1100
|
}
|
|
775
|
-
const vesuAdapterLST = new VesuSupplyOnlyAdapter({
|
|
776
|
-
// xWBTC vToken on re7 xBTC pool
|
|
777
|
-
vTokenContract: ContractAddr.from('0x062a162d0827db6f43ebb850cbef3c99fc7969e3070b83a2236c9f3713c89fd8'),
|
|
778
|
-
...baseAdapterConfig,
|
|
779
|
-
})
|
|
780
1101
|
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1102
|
+
const adapterIds: NonNullable<HyperLSTStrategySettings["adapterIds"]> = {
|
|
1103
|
+
primaryMultiply: "",
|
|
1104
|
+
multiply: {},
|
|
1105
|
+
modify: {},
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
const vesuMultiplyAdapters = vaultSettings.borrowable_assets.map((position) => {
|
|
1109
|
+
const key = getHyperLSTAdapterKey(position.tokenInfo.symbol, position.poolId);
|
|
1110
|
+
const id = getHyperLSTMultiplyAdapterId(lstSymbol, position.tokenInfo.symbol, position.poolId);
|
|
1111
|
+
adapterIds.multiply[key] = id;
|
|
1112
|
+
return {
|
|
1113
|
+
id,
|
|
1114
|
+
adapter: new VesuMultiplyAdapter({
|
|
1115
|
+
poolId: position.poolId,
|
|
1116
|
+
collateral: lstToken,
|
|
1117
|
+
debt: position.tokenInfo,
|
|
1118
|
+
marginToken: lstToken,
|
|
1119
|
+
targetHealthFactor: vaultSettings.targetHealthFactor,
|
|
1120
|
+
minHealthFactor: vaultSettings.minHealthFactor,
|
|
1121
|
+
quoteAmountToFetchPrice: vaultSettings.quoteAmountToFetchPrice,
|
|
1122
|
+
...baseAdapterConfig,
|
|
1123
|
+
minimumVesuMovementAmount: 0,
|
|
1124
|
+
}),
|
|
1125
|
+
};
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
const vesuModifyPositionAdapters = vaultSettings.borrowable_assets.map((position) => {
|
|
1129
|
+
const key = getHyperLSTAdapterKey(position.tokenInfo.symbol, position.poolId);
|
|
1130
|
+
const id = getHyperLSTModifyAdapterId(lstSymbol, position.tokenInfo.symbol, position.poolId);
|
|
1131
|
+
adapterIds.modify[key] = id;
|
|
1132
|
+
return {
|
|
1133
|
+
id,
|
|
1134
|
+
adapter: new VesuModifyPositionAdapter({
|
|
1135
|
+
poolId: position.poolId,
|
|
1136
|
+
collateral: lstToken,
|
|
1137
|
+
debt: position.tokenInfo,
|
|
1138
|
+
targetLtv: 0.8,
|
|
1139
|
+
maxLtv: 0.9,
|
|
1140
|
+
...baseAdapterConfig,
|
|
1141
|
+
}),
|
|
1142
|
+
};
|
|
794
1143
|
});
|
|
795
1144
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1145
|
+
if (!adapterIds.primaryMultiply && vesuMultiplyAdapters.length > 0) {
|
|
1146
|
+
adapterIds.primaryMultiply = vesuMultiplyAdapters[0].id;
|
|
1147
|
+
} else {
|
|
1148
|
+
throw new Error(`${lstSymbol}::getLooperSettings: primaryMultiply adapter not found`);
|
|
1149
|
+
}
|
|
1150
|
+
vaultSettings.adapterIds = adapterIds;
|
|
1151
|
+
|
|
1152
|
+
for (const entry of vesuMultiplyAdapters) {
|
|
1153
|
+
vaultSettings.adapters.push(entry);
|
|
1154
|
+
}
|
|
1155
|
+
for (const entry of vesuModifyPositionAdapters) {
|
|
1156
|
+
vaultSettings.adapters.push(entry);
|
|
1157
|
+
}
|
|
799
1158
|
|
|
800
1159
|
const commonAdapter = new CommonAdapter({
|
|
801
1160
|
id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN,
|
|
@@ -805,25 +1164,21 @@ function getLooperSettings(
|
|
|
805
1164
|
asset: lstToken.address
|
|
806
1165
|
})
|
|
807
1166
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1167
|
+
for (const entry of vesuMultiplyAdapters) {
|
|
1168
|
+
const adapter = entry.adapter;
|
|
1169
|
+
vaultSettings.leafAdapters.push(() => adapter.getDepositLeaf());
|
|
1170
|
+
vaultSettings.leafAdapters.push(() => adapter.getWithdrawLeaf());
|
|
1171
|
+
}
|
|
1172
|
+
for (const entry of vesuModifyPositionAdapters) {
|
|
1173
|
+
const adapter = entry.adapter;
|
|
1174
|
+
vaultSettings.leafAdapters.push(() => adapter.getDepositLeaf());
|
|
1175
|
+
vaultSettings.leafAdapters.push(() => adapter.getWithdrawLeaf());
|
|
1176
|
+
}
|
|
811
1177
|
|
|
812
|
-
// push vesu multiply adapter to leaf adapters
|
|
813
|
-
vesuMultiplyAdapters.map(adapter => vaultSettings.leafAdapters.push(() => adapter.getDepositLeaf()));
|
|
814
|
-
vesuMultiplyAdapters.map(adapter => vaultSettings.leafAdapters.push(() => adapter.getWithdrawLeaf()));
|
|
815
|
-
|
|
816
1178
|
// to bridge liquidity back to vault (used by bring_liquidity)
|
|
817
1179
|
vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(lstToken.address, vaultSettings.vaultAddress, UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY).bind(commonAdapter));
|
|
818
1180
|
vaultSettings.leafAdapters.push(commonAdapter.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY).bind(commonAdapter));
|
|
819
|
-
|
|
820
|
-
// claim rewards
|
|
821
|
-
// vaultSettings.leafAdapters.push(vesuAdapterLST.getDefispringRewardsAdapter(UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS).bind(vesuAdapterLST));
|
|
822
|
-
|
|
823
|
-
// // avnu swap for claims rewards
|
|
824
|
-
// const STRKToken = Global.getDefaultTokens().find(token => token.symbol === 'STRK')!;
|
|
825
|
-
// vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(STRKToken.address, AVNU_EXCHANGE, UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1).bind(commonAdapter));
|
|
826
|
-
// vabaseAdapterConfigultSettings.leafAdapters.push(commonAdapter.getAvnuAdapter(STRKToken.address, lstToken.address, UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS, false).bind(commonAdapter));
|
|
1181
|
+
|
|
827
1182
|
return vaultSettings;
|
|
828
1183
|
}
|
|
829
1184
|
|
|
@@ -889,13 +1244,9 @@ export const _riskFactor: RiskFactor[] = [
|
|
|
889
1244
|
{ type: RiskType.SMART_CONTRACT_RISK, value: SmartContractRiskLevel.WELL_AUDITED, weight: 25, reason: "Audited by Zellic" },
|
|
890
1245
|
{ type: RiskType.LIQUIDATION_RISK, value: LiquidationRiskLevel.VERY_LOW_PROBABILITY, weight: 25, reason: "The collateral and debt are highly correlated" },
|
|
891
1246
|
{ type: RiskType.TECHNICAL_RISK, value: TechnicalRiskLevel.STABLE_INFRASTRUCTURE, weight: 25, reason: "Liquidation can only happen if vault is left un-monitored for weeks, which is highly unlikely. We actively monitor all services on a daily basis." },
|
|
892
|
-
{type: RiskType.DEPEG_RISK, value: DepegRiskLevel.GENERALLY_STABLE, weight: 25, reason: "Generally stable pegged assets" },
|
|
1247
|
+
{ type: RiskType.DEPEG_RISK, value: DepegRiskLevel.GENERALLY_STABLE, weight: 25, reason: "Generally stable pegged assets" },
|
|
893
1248
|
];
|
|
894
1249
|
|
|
895
|
-
const borrowableAssets = [
|
|
896
|
-
'WBTC', 'tBTC', 'LBTC', 'solvBTC'
|
|
897
|
-
]
|
|
898
|
-
|
|
899
1250
|
const hyperxSTRK: HyperLSTStrategySettings = {
|
|
900
1251
|
vaultAddress: ContractAddr.from('0x46c7a54c82b1fe374353859f554a40b8bd31d3e30f742901579e7b57b1b5960'),
|
|
901
1252
|
manager: ContractAddr.from('0x5d499cd333757f461a0bedaca3dfc4d77320c773037e0aa299f22a6dbfdc03a'),
|
|
@@ -906,9 +1257,15 @@ const hyperxSTRK: HyperLSTStrategySettings = {
|
|
|
906
1257
|
adapters: [],
|
|
907
1258
|
targetHealthFactor: 1.1,
|
|
908
1259
|
minHealthFactor: 1.05,
|
|
909
|
-
borrowable_assets:
|
|
1260
|
+
borrowable_assets: [{
|
|
1261
|
+
tokenInfo: Global.getDefaultTokens().find(token => token.symbol === 'STRK')!,
|
|
1262
|
+
poolId: VesuPools.Re7STRK,
|
|
1263
|
+
}, {
|
|
1264
|
+
tokenInfo: Global.getDefaultTokens().find(token => token.symbol === 'STRK')!,
|
|
1265
|
+
poolId: VesuPools.Prime,
|
|
1266
|
+
}],
|
|
910
1267
|
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'STRK')!,
|
|
911
|
-
quoteAmountToFetchPrice: new Web3Number('
|
|
1268
|
+
quoteAmountToFetchPrice: new Web3Number('1000', Global.getDefaultTokens().find(token => token.symbol === 'STRK')!.decimals),
|
|
912
1269
|
}
|
|
913
1270
|
|
|
914
1271
|
const hyperxWBTC: HyperLSTStrategySettings = {
|
|
@@ -921,9 +1278,16 @@ const hyperxWBTC: HyperLSTStrategySettings = {
|
|
|
921
1278
|
adapters: [],
|
|
922
1279
|
targetHealthFactor: 1.1,
|
|
923
1280
|
minHealthFactor: 1.05,
|
|
924
|
-
borrowable_assets:
|
|
1281
|
+
borrowable_assets: [{
|
|
1282
|
+
tokenInfo: Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!,
|
|
1283
|
+
poolId: VesuPools.Re7xBTC,
|
|
1284
|
+
}, {
|
|
1285
|
+
tokenInfo: Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!,
|
|
1286
|
+
poolId: VesuPools.Prime,
|
|
1287
|
+
}],
|
|
925
1288
|
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!,
|
|
926
1289
|
quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!.decimals),
|
|
1290
|
+
redemptionRouter: ContractAddr.from('0x6ea649f402898f69baf775c1afdd08522c071c640b9c4460192070ec2b96417'),
|
|
927
1291
|
}
|
|
928
1292
|
|
|
929
1293
|
const hyperxtBTC: HyperLSTStrategySettings = {
|
|
@@ -936,9 +1300,16 @@ const hyperxtBTC: HyperLSTStrategySettings = {
|
|
|
936
1300
|
adapters: [],
|
|
937
1301
|
targetHealthFactor: 1.1,
|
|
938
1302
|
minHealthFactor: 1.05,
|
|
939
|
-
borrowable_assets:
|
|
1303
|
+
borrowable_assets: [{
|
|
1304
|
+
tokenInfo: Global.getDefaultTokens().find(token => token.symbol === 'tBTC')!,
|
|
1305
|
+
poolId: VesuPools.Re7xBTC,
|
|
1306
|
+
}, {
|
|
1307
|
+
tokenInfo: Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!,
|
|
1308
|
+
poolId: VesuPools.Re7xBTC,
|
|
1309
|
+
}],
|
|
940
1310
|
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'tBTC')!,
|
|
941
1311
|
quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'tBTC')!.decimals),
|
|
1312
|
+
redemptionRouter: ContractAddr.from('0x3de9c409d1e357e25778fb7a3e2e2393666956846a5c2caa607296fa8e76b5d'),
|
|
942
1313
|
}
|
|
943
1314
|
|
|
944
1315
|
const hyperxsBTC: HyperLSTStrategySettings = {
|
|
@@ -951,7 +1322,10 @@ const hyperxsBTC: HyperLSTStrategySettings = {
|
|
|
951
1322
|
adapters: [],
|
|
952
1323
|
targetHealthFactor: 1.1,
|
|
953
1324
|
minHealthFactor: 1.05,
|
|
954
|
-
borrowable_assets:
|
|
1325
|
+
borrowable_assets: [{
|
|
1326
|
+
tokenInfo: Global.getDefaultTokens().find(token => token.symbol === 'solvBTC')!,
|
|
1327
|
+
poolId: VesuPools.Re7xBTC,
|
|
1328
|
+
}],
|
|
955
1329
|
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'solvBTC')!,
|
|
956
1330
|
quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'solvBTC')!.decimals),
|
|
957
1331
|
}
|
|
@@ -966,44 +1340,47 @@ const hyperxLBTC: HyperLSTStrategySettings = {
|
|
|
966
1340
|
adapters: [],
|
|
967
1341
|
targetHealthFactor: 1.1,
|
|
968
1342
|
minHealthFactor: 1.05,
|
|
969
|
-
borrowable_assets:
|
|
1343
|
+
borrowable_assets: [{
|
|
1344
|
+
tokenInfo: Global.getDefaultTokens().find(token => token.symbol === 'LBTC')!,
|
|
1345
|
+
poolId: VesuPools.Re7xBTC,
|
|
1346
|
+
}],
|
|
970
1347
|
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'LBTC')!,
|
|
971
1348
|
quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'LBTC')!.decimals),
|
|
972
1349
|
}
|
|
973
1350
|
|
|
974
|
-
const hypermRe7BTC: HyperLSTStrategySettings = {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}
|
|
1351
|
+
// const hypermRe7BTC: HyperLSTStrategySettings = {
|
|
1352
|
+
// vaultAddress: ContractAddr.from('0x6c89b75d09de82477edb86b2c2918cfc1a5dc0177cfbdea46278386b6499645'),
|
|
1353
|
+
// manager: ContractAddr.from('0x7bc2d0535e13352d3ab78ea047616a3162068294297304943d2302122a150a1'),
|
|
1354
|
+
// vaultAllocator: ContractAddr.from('0x760f9cebca9d2ee624f4224591da6da8b3ea5fd2410590735709551bd4e7570'),
|
|
1355
|
+
// redeemRequestNFT: ContractAddr.from('0x5e045ae0160f7650f8a4dd7c826f25630a89fe62434db4441e7e0075608180f'),
|
|
1356
|
+
// aumOracle: ContractAddr.from('0x3958df341b838813c24efb9183c23bddd1c57d44b1b86c0dd57f67887b89fba'),
|
|
1357
|
+
// leafAdapters: [],
|
|
1358
|
+
// adapters: [],
|
|
1359
|
+
// targetHealthFactor: 1.1,
|
|
1360
|
+
// minHealthFactor: 1.05,
|
|
1361
|
+
// borrowable_assets: .map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
|
|
1362
|
+
// underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!,
|
|
1363
|
+
// quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'mRe7BTC')!.decimals),
|
|
1364
|
+
// }
|
|
988
1365
|
// Contract deployed: Vault, addr: 0x42797ab4eb1f72787442e91a73d63a39e3a141c1106470a946ecc328db6896c
|
|
989
1366
|
// Contract deployed: RedeemRequest, addr: 0x4bbb25c2568af07967342833f7db1aece1be1be2330798dab4ee585aa6c2c72
|
|
990
1367
|
// Contract deployed: VaultAllocator, addr: 0x456c4c6afca90512aeb5c735d84405fea6e51ab06d1851ac8cdb0a235e14f15
|
|
991
1368
|
// Contract deployed: Manager, addr: 0x435b45d40fbb406cf69ac84bb471e7b7a4ea2295d0893c05dd2db565295e77f
|
|
992
1369
|
|
|
993
|
-
const hypermRe7YIELD: HyperLSTStrategySettings = {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1370
|
+
// const hypermRe7YIELD: HyperLSTStrategySettings = {
|
|
1371
|
+
// vaultAddress: ContractAddr.from('0x42797ab4eb1f72787442e91a73d63a39e3a141c1106470a946ecc328db6896c'),
|
|
1372
|
+
// manager: ContractAddr.from('0x435b45d40fbb406cf69ac84bb471e7b7a4ea2295d0893c05dd2db565295e77f'),
|
|
1373
|
+
// vaultAllocator: ContractAddr.from('0x456c4c6afca90512aeb5c735d84405fea6e51ab06d1851ac8cdb0a235e14f15'),
|
|
1374
|
+
// redeemRequestNFT: ContractAddr.from('0x4bbb25c2568af07967342833f7db1aece1be1be2330798dab4ee585aa6c2c72'),
|
|
1375
|
+
// aumOracle: ContractAddr.from('0x3e1f2825158cafccc9b42a8165d17ceb6b8e966474d9c63587d338746888382'),
|
|
1376
|
+
// leafAdapters: [],
|
|
1377
|
+
// adapters: [],
|
|
1378
|
+
// targetHealthFactor: 1.1,
|
|
1379
|
+
// minHealthFactor: 1.05,
|
|
1380
|
+
// borrowable_assets: [Global.getDefaultTokens().find(token => token.symbol === 'USDC')!],
|
|
1381
|
+
// underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'USDC')!,
|
|
1382
|
+
// quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'mRe7BTC')!.decimals),
|
|
1383
|
+
// }
|
|
1007
1384
|
|
|
1008
1385
|
export function getInvestmentSteps(lstSymbol: string, underlyingSymbol: string) {
|
|
1009
1386
|
return [
|
|
@@ -1011,35 +1388,137 @@ export function getInvestmentSteps(lstSymbol: string, underlyingSymbol: string)
|
|
|
1011
1388
|
`The vault manager loops the ${underlyingSymbol} to buy ${lstSymbol}`,
|
|
1012
1389
|
`The vault manager collateralizes the ${lstSymbol} on Vesu`,
|
|
1013
1390
|
`The vault manager borrows more ${underlyingSymbol} to loop further`,
|
|
1014
|
-
`If required, adjust leverage or re-allocate assets within pool on Vesu to optimize yield`
|
|
1391
|
+
`If required, adjust leverage or re-allocate assets within LST pool on Vesu to optimize yield`
|
|
1015
1392
|
]
|
|
1016
1393
|
}
|
|
1017
1394
|
|
|
1018
|
-
function
|
|
1395
|
+
function createHyperLSTSettings(
|
|
1396
|
+
lstSymbol: string,
|
|
1397
|
+
underlyingSymbol: string,
|
|
1398
|
+
): StrategySettings {
|
|
1399
|
+
const depositToken = Global.getDefaultTokens().find(
|
|
1400
|
+
(token) => token.symbol === lstSymbol,
|
|
1401
|
+
)!;
|
|
1019
1402
|
return {
|
|
1403
|
+
isPaused: false,
|
|
1404
|
+
liveStatus: lstSymbol === 'xSTRK' ? StrategyLiveStatus.HOT : ((lstSymbol == 'xWBTC' || lstSymbol == 'xtBTC') ? StrategyLiveStatus.ACTIVE : StrategyLiveStatus.DEPRECATED),
|
|
1405
|
+
isAudited: true,
|
|
1406
|
+
isInstantWithdrawal: false,
|
|
1407
|
+
hideHarvestInfo: true,
|
|
1408
|
+
quoteToken: depositToken,
|
|
1409
|
+
showWithdrawalWarningModal: false,
|
|
1410
|
+
alerts: [
|
|
1411
|
+
{
|
|
1412
|
+
tab: "withdraw" as const,
|
|
1413
|
+
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.",
|
|
1414
|
+
type: "info" as const,
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
tab: "deposit" as const,
|
|
1418
|
+
text: (
|
|
1419
|
+
<>
|
|
1420
|
+
To acquire the LST, please visit{" "}
|
|
1421
|
+
<a href="https://app.endur.fi" target="_blank" rel="noopener noreferrer">endur.fi</a>
|
|
1422
|
+
</>
|
|
1423
|
+
),
|
|
1424
|
+
type: "info" as const,
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
tab: "deposit" as const,
|
|
1428
|
+
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.",
|
|
1429
|
+
type: "info" as const,
|
|
1430
|
+
},
|
|
1431
|
+
],
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
const HYPER_LST_SECURITY = {
|
|
1436
|
+
auditStatus: AuditStatus.AUDITED,
|
|
1437
|
+
sourceCode: {
|
|
1438
|
+
type: SourceCodeType.CLOSED_SOURCE,
|
|
1439
|
+
contractLink: "https://github.com/trovesfi/troves-contracts",
|
|
1440
|
+
},
|
|
1441
|
+
accessControl: {
|
|
1442
|
+
type: AccessControlType.STANDARD_ACCOUNT,
|
|
1443
|
+
addresses: [ContractAddr.from("0x03495dd1e4838aa06666aac236036d86e81a6553e222fc02e70c2cbc0062e8d0")],
|
|
1444
|
+
},
|
|
1445
|
+
};
|
|
1446
|
+
|
|
1447
|
+
const HYPER_LST_REDEMPTION_INFO: RedemptionInfo = {
|
|
1448
|
+
instantWithdrawalVault: InstantWithdrawalVault.NO,
|
|
1449
|
+
redemptionsInfo: [
|
|
1450
|
+
{
|
|
1451
|
+
title: "Typical Duration",
|
|
1452
|
+
description: "1-2 hours",
|
|
1453
|
+
},
|
|
1454
|
+
],
|
|
1455
|
+
alerts: [
|
|
1456
|
+
{
|
|
1457
|
+
type: "info",
|
|
1458
|
+
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.",
|
|
1459
|
+
tab: "withdraw",
|
|
1460
|
+
},
|
|
1461
|
+
],
|
|
1462
|
+
};
|
|
1463
|
+
|
|
1464
|
+
function getStrategySettings(
|
|
1465
|
+
lstSymbol: string,
|
|
1466
|
+
underlyingSymbol: string,
|
|
1467
|
+
settings: HyperLSTStrategySettings,
|
|
1468
|
+
isPreview: boolean = false,
|
|
1469
|
+
isLST: boolean,
|
|
1470
|
+
): IStrategyMetadata<HyperLSTStrategySettings> {
|
|
1471
|
+
return {
|
|
1472
|
+
id: `hyper_${lstSymbol.toLowerCase()}`,
|
|
1020
1473
|
name: `Hyper ${lstSymbol}`,
|
|
1021
1474
|
description: getDescription(lstSymbol, underlyingSymbol),
|
|
1022
|
-
address:
|
|
1475
|
+
address: settings.vaultAddress,
|
|
1023
1476
|
launchBlock: 0,
|
|
1024
1477
|
type: 'Other',
|
|
1478
|
+
vaultType: {
|
|
1479
|
+
type: VaultType.LOOPING,
|
|
1480
|
+
description: `Creates leveraged looping position on ${lstSymbol} by borrowing ${underlyingSymbol} to increase yield`,
|
|
1481
|
+
},
|
|
1025
1482
|
depositTokens: [Global.getDefaultTokens().find(token => token.symbol === lstSymbol)!],
|
|
1026
|
-
additionalInfo: getLooperSettings(lstSymbol, underlyingSymbol,
|
|
1483
|
+
additionalInfo: getLooperSettings(lstSymbol, underlyingSymbol, settings),
|
|
1027
1484
|
risk: {
|
|
1028
1485
|
riskFactor: _riskFactor,
|
|
1029
1486
|
netRisk:
|
|
1030
1487
|
_riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
|
|
1031
1488
|
_riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
|
|
1032
|
-
notARisks: getNoRiskTags(_riskFactor)
|
|
1489
|
+
notARisks: getNoRiskTags(_riskFactor),
|
|
1033
1490
|
},
|
|
1034
1491
|
auditUrl: AUDIT_URL,
|
|
1035
1492
|
protocols: [Protocols.ENDUR, Protocols.VESU],
|
|
1036
|
-
|
|
1037
|
-
|
|
1493
|
+
curator: {
|
|
1494
|
+
name: "Unwrap Labs",
|
|
1495
|
+
logo: "https://assets.troves.fi/integrations/unwraplabs/white.png",
|
|
1496
|
+
},
|
|
1497
|
+
settings: createHyperLSTSettings(lstSymbol, underlyingSymbol),
|
|
1498
|
+
contractDetails: getContractDetails(settings),
|
|
1038
1499
|
faqs: getFAQs(lstSymbol, underlyingSymbol, isLST),
|
|
1039
1500
|
investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
|
|
1040
1501
|
isPreview: isPreview,
|
|
1041
|
-
apyMethodology:
|
|
1042
|
-
|
|
1502
|
+
apyMethodology:
|
|
1503
|
+
"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.",
|
|
1504
|
+
realizedApyMethodology:
|
|
1505
|
+
"The realizedAPY is based on past 14 days performance by the vault",
|
|
1506
|
+
feeBps: {
|
|
1507
|
+
performanceFeeBps: 1000,
|
|
1508
|
+
},
|
|
1509
|
+
tags: lstSymbol.includes("BTC")
|
|
1510
|
+
? [StrategyTag.BTC, StrategyTag.LEVERED]
|
|
1511
|
+
: [StrategyTag.LEVERED],
|
|
1512
|
+
security: HYPER_LST_SECURITY,
|
|
1513
|
+
redemptionInfo: HYPER_LST_REDEMPTION_INFO,
|
|
1514
|
+
usualTimeToEarnings: '2 weeks',
|
|
1515
|
+
usualTimeToEarningsDescription: '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.',
|
|
1516
|
+
points: lstSymbol === 'xSTRK' ? [{
|
|
1517
|
+
multiplier: 4,
|
|
1518
|
+
logo: "https://endur.fi/favicon.ico",
|
|
1519
|
+
toolTip: "This strategy holds xSTRK. Earn 3-4x Endur points on your xSTRK due to the leverage. Points can be found on endur.fi.",
|
|
1520
|
+
}] : undefined,
|
|
1521
|
+
};
|
|
1043
1522
|
}
|
|
1044
1523
|
|
|
1045
1524
|
|
|
@@ -1062,10 +1541,10 @@ function getStrategySettings(lstSymbol: string, underlyingSymbol: string, addres
|
|
|
1062
1541
|
export const HyperLSTStrategies: IStrategyMetadata<HyperLSTStrategySettings>[] =
|
|
1063
1542
|
[
|
|
1064
1543
|
getStrategySettings('xSTRK', 'STRK', hyperxSTRK, false, true),
|
|
1065
|
-
getStrategySettings('xWBTC', 'WBTC', hyperxWBTC, false,
|
|
1066
|
-
getStrategySettings('xtBTC', 'tBTC', hyperxtBTC, false,
|
|
1067
|
-
getStrategySettings('xsBTC', 'solvBTC', hyperxsBTC, false,
|
|
1068
|
-
getStrategySettings('xLBTC', 'LBTC', hyperxLBTC, false,
|
|
1069
|
-
getStrategySettings('mRe7BTC', 'mRe7BTC', hypermRe7BTC, false, false),
|
|
1070
|
-
getStrategySettings('mRe7YIELD', 'mRe7YIELD', hypermRe7YIELD, false, false),
|
|
1544
|
+
getStrategySettings('xWBTC', 'WBTC', hyperxWBTC, false, true),
|
|
1545
|
+
getStrategySettings('xtBTC', 'tBTC', hyperxtBTC, false, true),
|
|
1546
|
+
getStrategySettings('xsBTC', 'solvBTC', hyperxsBTC, false, true),
|
|
1547
|
+
getStrategySettings('xLBTC', 'LBTC', hyperxLBTC, false, true),
|
|
1548
|
+
// getStrategySettings('mRe7BTC', 'mRe7BTC', hypermRe7BTC, false, false),
|
|
1549
|
+
// getStrategySettings('mRe7YIELD', 'mRe7YIELD', hypermRe7YIELD, false, false),
|
|
1071
1550
|
]
|