@strkfarm/sdk 1.1.70 → 2.0.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/index.browser.global.js +67016 -59681
- package/dist/index.browser.mjs +29832 -23221
- package/dist/index.d.ts +2006 -787
- package/dist/index.js +25403 -18769
- package/dist/index.mjs +25333 -18739
- package/package.json +80 -76
- package/src/data/extended-deposit.abi.json +3613 -0
- package/src/data/universal-vault.abi.json +135 -20
- package/src/dataTypes/address.ts +7 -0
- package/src/global.ts +240 -193
- package/src/interfaces/common.tsx +26 -2
- package/src/modules/ExtendedWrapperSDk/index.ts +62 -0
- package/src/modules/ExtendedWrapperSDk/types.ts +311 -0
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +395 -0
- package/src/modules/avnu.ts +17 -4
- package/src/modules/ekubo-quoter.ts +99 -10
- package/src/modules/erc20.ts +67 -21
- package/src/modules/harvests.ts +16 -29
- package/src/modules/index.ts +5 -1
- package/src/modules/lst-apr.ts +36 -0
- package/src/modules/midas.ts +159 -0
- package/src/modules/pricer-from-api.ts +2 -2
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +3 -38
- package/src/modules/token-market-data.ts +202 -0
- package/src/node/deployer.ts +1 -36
- package/src/strategies/autoCompounderStrk.ts +1 -1
- package/src/strategies/base-strategy.ts +20 -3
- package/src/strategies/ekubo-cl-vault.tsx +123 -306
- package/src/strategies/index.ts +4 -1
- package/src/strategies/svk-strategy.ts +247 -0
- package/src/strategies/universal-adapters/adapter-optimizer.ts +65 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +5 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +411 -0
- package/src/strategies/universal-adapters/baseAdapter.ts +181 -153
- package/src/strategies/universal-adapters/common-adapter.ts +98 -77
- package/src/strategies/universal-adapters/extended-adapter.ts +661 -0
- package/src/strategies/universal-adapters/index.ts +5 -1
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +109 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +220 -218
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +924 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +58 -51
- package/src/strategies/universal-lst-muliplier-strategy.tsx +707 -774
- package/src/strategies/universal-strategy.tsx +1098 -1180
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +34 -0
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +77 -0
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +49 -0
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +376 -0
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +1134 -0
- package/src/strategies/vesu-rebalance.tsx +16 -19
- package/src/utils/health-factor-math.ts +11 -5
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMainnetConfig,
|
|
3
|
+
IConfig,
|
|
4
|
+
IStrategyMetadata,
|
|
5
|
+
TokenInfo,
|
|
6
|
+
} from "@/interfaces";
|
|
7
|
+
import {
|
|
8
|
+
UNIVERSAL_MANAGE_IDS,
|
|
9
|
+
UniversalStrategySettings,
|
|
10
|
+
} from "../universal-strategy";
|
|
11
|
+
import { calculateExtendedLevergae } from "./utils/helper";
|
|
12
|
+
import { logger } from "@/utils";
|
|
13
|
+
import { AUDIT_URL } from "../universal-lst-muliplier-strategy";
|
|
14
|
+
import { getNoRiskTags } from "@/interfaces";
|
|
15
|
+
import { _riskFactor } from "../universal-lst-muliplier-strategy";
|
|
16
|
+
import { BUFFER_USDC_IN_WITHDRAWAL, EXTENDED_QTY_PRECISION, LIMIT_BALANCE, MAX_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED, MIN_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED, MINIMUM_EXTENDED_POSITION_SIZE, USDC_TOKEN_DECIMALS, WALLET_ADDRESS, WBTC_TOKEN_DECIMALS } from "./utils/constants";
|
|
17
|
+
import { PricerBase } from "@/modules/pricerBase";
|
|
18
|
+
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
19
|
+
import { Global } from "@/global";
|
|
20
|
+
import { ERC20 } from "@/modules";
|
|
21
|
+
import { Balance, OrderSide } from "@/modules/ExtendedWrapperSDk";
|
|
22
|
+
import { Protocols } from "@/interfaces";
|
|
23
|
+
import { MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP } from "./utils/constants";
|
|
24
|
+
import { getInvestmentSteps, getFAQs } from "../universal-lst-muliplier-strategy";
|
|
25
|
+
import { getContractDetails } from "../universal-strategy";
|
|
26
|
+
import { highlightTextWithLinks } from "@/interfaces";
|
|
27
|
+
import { PositionInfo } from "../universal-adapters";
|
|
28
|
+
import { APYType } from "../universal-adapters";
|
|
29
|
+
import { AUMTypes } from "../universal-strategy";
|
|
30
|
+
import { VesuPools } from "../universal-adapters";
|
|
31
|
+
import {
|
|
32
|
+
BaseAdapterConfig,
|
|
33
|
+
CommonAdapter,
|
|
34
|
+
UnusedBalanceAdapter,
|
|
35
|
+
VesuMultiplyAdapter,
|
|
36
|
+
} from "../universal-adapters";
|
|
37
|
+
import { Operations } from "./services/operationService";
|
|
38
|
+
import {
|
|
39
|
+
AVNU_EXCHANGE,
|
|
40
|
+
AVNU_QUOTE_URL,
|
|
41
|
+
AVNU_MIDDLEWARE,
|
|
42
|
+
EXTENDED_CONTRACT,
|
|
43
|
+
} from "../universal-adapters/adapter-utils";
|
|
44
|
+
import { PricerFromApi } from "@/modules";
|
|
45
|
+
import { ExtendedAdapter } from "../universal-adapters/extended-adapter";
|
|
46
|
+
import { SVKStrategy } from "../svk-strategy";
|
|
47
|
+
import { AvnuAdapter } from "../universal-adapters/avnu-adapter";
|
|
48
|
+
import {
|
|
49
|
+
calculateAmountDistribution,
|
|
50
|
+
calculateAmountDistributionForWithdrawal,
|
|
51
|
+
calculateVesuLeverage,
|
|
52
|
+
calculateVesUPositionSizeGivenExtended
|
|
53
|
+
} from "./utils/helper";
|
|
54
|
+
import { SingleTokenInfo } from "../base-strategy";
|
|
55
|
+
import { Call } from "starknet";
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
export interface VesuExtendedStrategySettings
|
|
59
|
+
extends UniversalStrategySettings {
|
|
60
|
+
underlyingToken: TokenInfo;
|
|
61
|
+
borrowable_assets: TokenInfo[];
|
|
62
|
+
targetHealthFactor: number;
|
|
63
|
+
quoteAmountToFetchPrice: Web3Number;
|
|
64
|
+
minHealthFactor: number;
|
|
65
|
+
aumOracle: ContractAddr;
|
|
66
|
+
minimumWBTCDifferenceForAvnuSwap: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class VesuExtendedMultiplierStrategy<
|
|
70
|
+
S extends VesuExtendedStrategySettings
|
|
71
|
+
>
|
|
72
|
+
extends SVKStrategy<S>
|
|
73
|
+
implements Operations {
|
|
74
|
+
|
|
75
|
+
constructor(
|
|
76
|
+
config: IConfig,
|
|
77
|
+
pricer: PricerBase,
|
|
78
|
+
metadata: IStrategyMetadata<S>
|
|
79
|
+
) {
|
|
80
|
+
super(config, pricer, metadata);
|
|
81
|
+
this.metadata.additionalInfo.adapters.forEach((adapter) => {
|
|
82
|
+
adapter.adapter.config.networkConfig = this.config;
|
|
83
|
+
adapter.adapter.config.pricer = this.pricer;
|
|
84
|
+
if ((adapter.adapter as VesuMultiplyAdapter).vesuAdapter) {
|
|
85
|
+
(adapter.adapter as VesuMultiplyAdapter).vesuAdapter.networkConfig =
|
|
86
|
+
this.config;
|
|
87
|
+
(adapter.adapter as VesuMultiplyAdapter).vesuAdapter.pricer =
|
|
88
|
+
this.pricer;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getTag() {
|
|
94
|
+
return `${VesuExtendedMultiplierStrategy.name}:${this.metadata.name}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getAssetPrices() {
|
|
98
|
+
const wbtcToken = Global.getDefaultTokens().find(
|
|
99
|
+
(token) => token.symbol === "WBTC"
|
|
100
|
+
)!;
|
|
101
|
+
const usdcToken = Global.getDefaultTokens().find(
|
|
102
|
+
(token) => token.symbol === "USDC"
|
|
103
|
+
)!;
|
|
104
|
+
const collateralPrice = await this.pricer.getPrice(wbtcToken.symbol);
|
|
105
|
+
const debtPrice = await this.pricer.getPrice(usdcToken.symbol);
|
|
106
|
+
return {
|
|
107
|
+
collateralPrice,
|
|
108
|
+
debtPrice,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getUnusedBalanceUSDCE(): Promise<SingleTokenInfo> {
|
|
113
|
+
const usdceToken = Global.getDefaultTokens().find(
|
|
114
|
+
(token) => token.symbol === "USDCe"
|
|
115
|
+
)!;
|
|
116
|
+
const balance = await new ERC20(this.config).balanceOf(
|
|
117
|
+
usdceToken.address,
|
|
118
|
+
WALLET_ADDRESS,
|
|
119
|
+
usdceToken.decimals
|
|
120
|
+
);
|
|
121
|
+
const price = await this.pricer.getPrice(usdceToken.symbol);
|
|
122
|
+
const usdValue =
|
|
123
|
+
Number(balance.toFixed(usdceToken.decimals)) * price.price;
|
|
124
|
+
return {
|
|
125
|
+
tokenInfo: usdceToken,
|
|
126
|
+
amount: balance,
|
|
127
|
+
usdValue,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async getUnusedBalanceWBTC(): Promise<SingleTokenInfo> {
|
|
133
|
+
const collateralToken = this.metadata.additionalInfo.borrowable_assets[0]!;
|
|
134
|
+
const balance = await new ERC20(this.config).balanceOf(
|
|
135
|
+
collateralToken.address,
|
|
136
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
137
|
+
collateralToken.decimals
|
|
138
|
+
);
|
|
139
|
+
const price = await this.pricer.getPrice(collateralToken.symbol);
|
|
140
|
+
const usdValue =
|
|
141
|
+
Number(balance.toFixed(collateralToken.decimals)) * price.price;
|
|
142
|
+
return {
|
|
143
|
+
tokenInfo: collateralToken,
|
|
144
|
+
amount: balance,
|
|
145
|
+
usdValue,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async getVesuAdapter(): Promise<VesuMultiplyAdapter | null> {
|
|
150
|
+
const vesuAdapter = this.metadata.additionalInfo.adapters.find(
|
|
151
|
+
(adapter) => adapter.adapter.name === VesuMultiplyAdapter.name
|
|
152
|
+
);
|
|
153
|
+
if (!vesuAdapter) {
|
|
154
|
+
logger.error("vesu adapter not found");
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return vesuAdapter.adapter as VesuMultiplyAdapter;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async getAvnuAdapter(): Promise<AvnuAdapter | null> {
|
|
161
|
+
const avnuAdapter = this.metadata.additionalInfo.adapters.find(
|
|
162
|
+
(adapter) => adapter.adapter.name === AvnuAdapter.name
|
|
163
|
+
);
|
|
164
|
+
if (!avnuAdapter) {
|
|
165
|
+
logger.error("avnu adapter not found");
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
return avnuAdapter.adapter as AvnuAdapter;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async getExtendedAdapter(): Promise<ExtendedAdapter | null> {
|
|
172
|
+
const extendedAdapter = this.metadata.additionalInfo.adapters.find(
|
|
173
|
+
(adapter) => adapter.adapter.name === ExtendedAdapter.name
|
|
174
|
+
);
|
|
175
|
+
if (!extendedAdapter) {
|
|
176
|
+
logger.error("extended adapter not found");
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return extendedAdapter.adapter as ExtendedAdapter;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async moveAssetsToVaultAllocator(amount: Web3Number, extendedAdapter: ExtendedAdapter): Promise<Call[]> {
|
|
183
|
+
try {
|
|
184
|
+
const usdceToken = Global.getDefaultTokens().find(
|
|
185
|
+
(token) => token.symbol === "USDCe"
|
|
186
|
+
)!;
|
|
187
|
+
const approveCall = new ERC20(this.config).approve(
|
|
188
|
+
usdceToken.address,
|
|
189
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
190
|
+
amount
|
|
191
|
+
);
|
|
192
|
+
const transferCall = new ERC20(this.config).transfer(
|
|
193
|
+
usdceToken.address,
|
|
194
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
195
|
+
amount
|
|
196
|
+
);
|
|
197
|
+
const proofsInfo = extendedAdapter.getProofsForFromLegacySwap(
|
|
198
|
+
this.getMerkleTree()
|
|
199
|
+
);
|
|
200
|
+
const proofGroups = proofsInfo.proofs;
|
|
201
|
+
const call = this.getManageCall(
|
|
202
|
+
proofGroups,
|
|
203
|
+
await proofsInfo.callConstructor({ amount: amount })
|
|
204
|
+
);
|
|
205
|
+
return [approveCall, transferCall, call];
|
|
206
|
+
} catch (err) {
|
|
207
|
+
logger.error(`error moving assets to vault allocator: ${err}`);
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async shouldInvest(): Promise<{
|
|
213
|
+
shouldInvest: boolean;
|
|
214
|
+
vesuAmount: Web3Number;
|
|
215
|
+
extendedAmount: Web3Number;
|
|
216
|
+
extendedLeverage: number;
|
|
217
|
+
collateralPrice: number;
|
|
218
|
+
debtPrice: number;
|
|
219
|
+
vesuLeverage: number;
|
|
220
|
+
}> {
|
|
221
|
+
try {
|
|
222
|
+
const vesuAdapter = await this.getVesuAdapter();
|
|
223
|
+
const extendedAdapter = await this.getExtendedAdapter();
|
|
224
|
+
if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
|
|
225
|
+
logger.error(
|
|
226
|
+
`vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
|
|
227
|
+
);
|
|
228
|
+
return {
|
|
229
|
+
shouldInvest: false,
|
|
230
|
+
vesuAmount: new Web3Number(0, 0),
|
|
231
|
+
extendedAmount: new Web3Number(0, 0),
|
|
232
|
+
extendedLeverage: 0,
|
|
233
|
+
collateralPrice: 0,
|
|
234
|
+
debtPrice: 0,
|
|
235
|
+
vesuLeverage: 0,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const balance = await this.getUnusedBalance();
|
|
239
|
+
const usdcBalanceOnExtended = await extendedAdapter.getExtendedDepositAmount();
|
|
240
|
+
/** The LIMIT_BALANCE is the bffer amount to keep in the investing Cycle */
|
|
241
|
+
const amountToInvest = balance.amount.plus(usdcBalanceOnExtended?.availableForWithdrawal ?? 0).minus(LIMIT_BALANCE);
|
|
242
|
+
|
|
243
|
+
if (amountToInvest.lessThan(0)) {
|
|
244
|
+
return {
|
|
245
|
+
shouldInvest: false,
|
|
246
|
+
vesuAmount: new Web3Number(0, 0),
|
|
247
|
+
extendedAmount: new Web3Number(0, 0),
|
|
248
|
+
extendedLeverage: 0,
|
|
249
|
+
collateralPrice: 0,
|
|
250
|
+
debtPrice: 0,
|
|
251
|
+
vesuLeverage: 0,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const extendedPositon = await extendedAdapter.getAllOpenPositions();
|
|
256
|
+
if (!extendedPositon) {
|
|
257
|
+
logger.error("error getting extended position to decide move assets");
|
|
258
|
+
return {
|
|
259
|
+
shouldInvest: false,
|
|
260
|
+
vesuAmount: new Web3Number(0, 0),
|
|
261
|
+
extendedAmount: new Web3Number(0, 0),
|
|
262
|
+
extendedLeverage: 0,
|
|
263
|
+
collateralPrice: 0,
|
|
264
|
+
debtPrice: 0,
|
|
265
|
+
vesuLeverage: 0,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const { collateralTokenAmount } =
|
|
269
|
+
await vesuAdapter.vesuAdapter.getAssetPrices();
|
|
270
|
+
|
|
271
|
+
const {
|
|
272
|
+
collateralPrice,
|
|
273
|
+
debtPrice
|
|
274
|
+
} = await this.getAssetPrices();
|
|
275
|
+
const { vesu_amount, extended_amount, extended_leverage, vesu_leverage } =
|
|
276
|
+
await calculateAmountDistribution(
|
|
277
|
+
amountToInvest.toNumber(),
|
|
278
|
+
extendedAdapter.client,
|
|
279
|
+
extendedAdapter.config.extendedMarketName,
|
|
280
|
+
collateralPrice.price,
|
|
281
|
+
debtPrice.price,
|
|
282
|
+
collateralTokenAmount,
|
|
283
|
+
extendedPositon
|
|
284
|
+
);
|
|
285
|
+
if (
|
|
286
|
+
!vesu_amount ||
|
|
287
|
+
!extended_amount ||
|
|
288
|
+
!extended_leverage ||
|
|
289
|
+
!vesu_leverage
|
|
290
|
+
) {
|
|
291
|
+
logger.error(
|
|
292
|
+
`Not enough amount to invest: vesu_amount=${vesu_amount}, extended_amount=${extended_amount}`
|
|
293
|
+
);
|
|
294
|
+
return {
|
|
295
|
+
shouldInvest: false,
|
|
296
|
+
vesuAmount: new Web3Number(0, 0),
|
|
297
|
+
extendedAmount: new Web3Number(0, 0),
|
|
298
|
+
extendedLeverage: 0,
|
|
299
|
+
collateralPrice: 0,
|
|
300
|
+
debtPrice: 0,
|
|
301
|
+
vesuLeverage: 0,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
shouldInvest: true,
|
|
306
|
+
vesuAmount: vesu_amount,
|
|
307
|
+
extendedAmount: extended_amount,
|
|
308
|
+
extendedLeverage: extended_leverage,
|
|
309
|
+
vesuLeverage: vesu_leverage,
|
|
310
|
+
collateralPrice: collateralPrice.price,
|
|
311
|
+
debtPrice: debtPrice.price,
|
|
312
|
+
};
|
|
313
|
+
} catch (err) {
|
|
314
|
+
logger.error(`error deciding invest: ${err}`);
|
|
315
|
+
return {
|
|
316
|
+
shouldInvest: false,
|
|
317
|
+
vesuAmount: new Web3Number(0, 0),
|
|
318
|
+
extendedAmount: new Web3Number(0, 0),
|
|
319
|
+
extendedLeverage: 0,
|
|
320
|
+
collateralPrice: 0,
|
|
321
|
+
debtPrice: 0,
|
|
322
|
+
vesuLeverage: 0,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async shouldMoveAssets(extendedAmount: Web3Number, vesuAmount: Web3Number): Promise<Call[]> {
|
|
328
|
+
try {
|
|
329
|
+
const vesuAdapter = await this.getVesuAdapter();
|
|
330
|
+
const extendedAdapter = await this.getExtendedAdapter();
|
|
331
|
+
let calls: Call[] = [];
|
|
332
|
+
if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
|
|
333
|
+
logger.error(
|
|
334
|
+
`vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
|
|
335
|
+
);
|
|
336
|
+
return calls;
|
|
337
|
+
}
|
|
338
|
+
if (extendedAmount.lessThan(0)) {
|
|
339
|
+
try {
|
|
340
|
+
const {calls: extendedCalls, status: extendedStatus} = await this.moveAssets(
|
|
341
|
+
{
|
|
342
|
+
to: Protocols.VAULT.name,
|
|
343
|
+
from: Protocols.EXTENDED.name,
|
|
344
|
+
amount: extendedAmount.abs(),
|
|
345
|
+
},
|
|
346
|
+
extendedAdapter,
|
|
347
|
+
vesuAdapter
|
|
348
|
+
);
|
|
349
|
+
//If withdrawal succesfull, then do further
|
|
350
|
+
if(extendedStatus){
|
|
351
|
+
calls.push(...extendedCalls);
|
|
352
|
+
}else{
|
|
353
|
+
return [];
|
|
354
|
+
}
|
|
355
|
+
} catch (err) {
|
|
356
|
+
logger.error(`Failed moving assets to vault: ${err}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (vesuAmount.lessThan(0)) {
|
|
361
|
+
try {
|
|
362
|
+
const {calls: vesuCalls, status: vesuStatus} = await this.moveAssets(
|
|
363
|
+
{
|
|
364
|
+
to: Protocols.EXTENDED.name,
|
|
365
|
+
from: Protocols.VESU.name,
|
|
366
|
+
amount: vesuAmount.abs(),
|
|
367
|
+
},
|
|
368
|
+
extendedAdapter,
|
|
369
|
+
vesuAdapter
|
|
370
|
+
);
|
|
371
|
+
calls.push(...vesuCalls);
|
|
372
|
+
if(!vesuStatus){
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
} catch (err) {
|
|
376
|
+
logger.error(`Failed moving assets to vault: ${err}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
|
|
381
|
+
if (!extendedHoldings) {
|
|
382
|
+
logger.error(`error getting extended holdings: ${extendedHoldings}`);
|
|
383
|
+
return calls;
|
|
384
|
+
}
|
|
385
|
+
const usdcAmountInWallet = (await this.getUnusedBalance()).amount;
|
|
386
|
+
const usdcAmountOnExtended = parseFloat(
|
|
387
|
+
extendedHoldings.availableForWithdrawal
|
|
388
|
+
);
|
|
389
|
+
if (extendedAmount.minus(usdcAmountOnExtended).greaterThan(0)) {
|
|
390
|
+
//move assets to extended
|
|
391
|
+
try {
|
|
392
|
+
const {calls: extendedCalls} = await this.moveAssets(
|
|
393
|
+
{
|
|
394
|
+
to: Protocols.EXTENDED.name,
|
|
395
|
+
from: Protocols.VAULT.name,
|
|
396
|
+
amount: extendedAmount.minus(usdcAmountOnExtended),
|
|
397
|
+
},
|
|
398
|
+
extendedAdapter,
|
|
399
|
+
vesuAdapter
|
|
400
|
+
);
|
|
401
|
+
calls.push(...extendedCalls);
|
|
402
|
+
} catch (err) {
|
|
403
|
+
logger.error(`Failed moving assets to extended: ${err}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (vesuAmount.minus(usdcAmountInWallet).greaterThan(0)) {
|
|
407
|
+
//move assets to vesu
|
|
408
|
+
try {
|
|
409
|
+
const {calls: vesuCalls, status: vesuStatus} = await this.moveAssets(
|
|
410
|
+
{
|
|
411
|
+
to: Protocols.VAULT.name,
|
|
412
|
+
from: Protocols.EXTENDED.name,
|
|
413
|
+
amount: vesuAmount.minus(usdcAmountInWallet),
|
|
414
|
+
},
|
|
415
|
+
extendedAdapter,
|
|
416
|
+
vesuAdapter
|
|
417
|
+
);
|
|
418
|
+
if(!vesuStatus){
|
|
419
|
+
return [];
|
|
420
|
+
}
|
|
421
|
+
calls.push(...vesuCalls);
|
|
422
|
+
} catch (err) {
|
|
423
|
+
logger.error(`Failed moving assets to vault: ${err}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return calls;
|
|
427
|
+
} catch (err) {
|
|
428
|
+
logger.error(`Failed moving assets to vesu: ${err}`);
|
|
429
|
+
return [];
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async moveAssets(
|
|
434
|
+
params: {
|
|
435
|
+
amount: Web3Number;
|
|
436
|
+
from: string;
|
|
437
|
+
to: string;
|
|
438
|
+
},
|
|
439
|
+
extendedAdapter: ExtendedAdapter,
|
|
440
|
+
vesuAdapter: VesuMultiplyAdapter
|
|
441
|
+
): Promise<{
|
|
442
|
+
calls: Call[];
|
|
443
|
+
status: boolean;
|
|
444
|
+
}> {
|
|
445
|
+
try {
|
|
446
|
+
const avnuAdapter = await this.getAvnuAdapter();
|
|
447
|
+
if (!avnuAdapter) {
|
|
448
|
+
logger.error(`avnu adapter not found: ${avnuAdapter}`);
|
|
449
|
+
return {
|
|
450
|
+
calls: [],
|
|
451
|
+
status: false
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
logger.info(`moveAssets params, ${JSON.stringify(params)}`);
|
|
455
|
+
const collateralToken = vesuAdapter.config.supportedPositions[0].asset;
|
|
456
|
+
const {
|
|
457
|
+
collateralPrice,
|
|
458
|
+
} = await this.getAssetPrices();
|
|
459
|
+
|
|
460
|
+
if (params.to === Protocols.EXTENDED.name && params.from === Protocols.VAULT.name) {
|
|
461
|
+
const proofsInfo = extendedAdapter.getProofs(
|
|
462
|
+
true,
|
|
463
|
+
this.getMerkleTree()
|
|
464
|
+
);
|
|
465
|
+
const calls = [];
|
|
466
|
+
const proofGroups = proofsInfo.proofs;
|
|
467
|
+
const call = this.getManageCall(
|
|
468
|
+
proofGroups,
|
|
469
|
+
await proofsInfo.callConstructor({ amount: params.amount })
|
|
470
|
+
);
|
|
471
|
+
calls.push(call);
|
|
472
|
+
return {
|
|
473
|
+
calls: [call],
|
|
474
|
+
status: true
|
|
475
|
+
};
|
|
476
|
+
} else if (params.to === Protocols.VAULT.name && params.from === Protocols.EXTENDED.name) {
|
|
477
|
+
const extendedLeverage = calculateExtendedLevergae();
|
|
478
|
+
const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
|
|
479
|
+
if (!extendedHoldings) {
|
|
480
|
+
logger.error(`error getting extended holdings: ${extendedHoldings}`);
|
|
481
|
+
return {
|
|
482
|
+
calls: [],
|
|
483
|
+
status: false
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
const extendedHoldingAmount = new Web3Number(
|
|
487
|
+
extendedHoldings.availableForWithdrawal,
|
|
488
|
+
USDC_TOKEN_DECIMALS
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
const leftAmountAfterWithdrawalAmountInAccount = params.amount.minus(extendedHoldingAmount);
|
|
492
|
+
const btcAmount = leftAmountAfterWithdrawalAmountInAccount.dividedBy(collateralPrice.price);
|
|
493
|
+
const openLongPosition = await extendedAdapter.createOrder(
|
|
494
|
+
extendedLeverage.toString(),
|
|
495
|
+
btcAmount.toNumber(),
|
|
496
|
+
OrderSide.BUY
|
|
497
|
+
)
|
|
498
|
+
if (!openLongPosition) {
|
|
499
|
+
logger.error(`error opening long position: ${openLongPosition}`);
|
|
500
|
+
return {
|
|
501
|
+
calls: [],
|
|
502
|
+
status: false
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
506
|
+
const withdrawalFromExtended =
|
|
507
|
+
await extendedAdapter.withdrawFromExtended(params.amount);
|
|
508
|
+
if (withdrawalFromExtended) {
|
|
509
|
+
/**
|
|
510
|
+
* We need to move assets from my wallet back to vault contract
|
|
511
|
+
*/
|
|
512
|
+
const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
|
|
513
|
+
logger.info(`extendedHoldings after withdrawal ${extendedHoldings}`);
|
|
514
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
515
|
+
const calls = await this.moveAssetsToVaultAllocator(params.amount, extendedAdapter);
|
|
516
|
+
if (calls.length > 0) {
|
|
517
|
+
return {
|
|
518
|
+
calls: calls,
|
|
519
|
+
status: true
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
} else {
|
|
523
|
+
logger.error("withdrawal from extended failed");
|
|
524
|
+
return {
|
|
525
|
+
calls: [],
|
|
526
|
+
status: false
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
} else if (params.to === Protocols.VAULT.name && params.from === Protocols.VESU.name) {
|
|
530
|
+
//withdraw from vesu
|
|
531
|
+
const vesuAmountInBTC = new Web3Number(
|
|
532
|
+
params.amount.dividedBy(collateralPrice.price).toNumber(),
|
|
533
|
+
collateralToken.decimals
|
|
534
|
+
);
|
|
535
|
+
const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
|
|
536
|
+
const calls = [];
|
|
537
|
+
const proofGroups = proofsInfo.proofs;
|
|
538
|
+
const call = this.getManageCall(
|
|
539
|
+
proofGroups,
|
|
540
|
+
await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
|
|
541
|
+
);
|
|
542
|
+
calls.push(call);
|
|
543
|
+
const swapProofsInfo = avnuAdapter.getProofs(false, this.getMerkleTree());
|
|
544
|
+
const swapProofGroups = swapProofsInfo.proofs;
|
|
545
|
+
const swapCall = this.getManageCall(
|
|
546
|
+
swapProofGroups,
|
|
547
|
+
await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
|
|
548
|
+
);
|
|
549
|
+
calls.push(swapCall);
|
|
550
|
+
return {
|
|
551
|
+
calls: calls,
|
|
552
|
+
status: true
|
|
553
|
+
};
|
|
554
|
+
} else if (params.to === Protocols.EXTENDED.name && params.from === Protocols.VESU.name) {
|
|
555
|
+
const vesuAmountInBTC = new Web3Number(
|
|
556
|
+
params.amount.dividedBy(collateralPrice.price).toNumber(),
|
|
557
|
+
collateralToken.decimals
|
|
558
|
+
);
|
|
559
|
+
const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
|
|
560
|
+
const calls = [];
|
|
561
|
+
const proofGroups = proofsInfo.proofs;
|
|
562
|
+
const call = this.getManageCall(
|
|
563
|
+
proofGroups,
|
|
564
|
+
await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
|
|
565
|
+
);
|
|
566
|
+
calls.push(call);
|
|
567
|
+
const swapProofsInfo = avnuAdapter.getProofs(false, this.getMerkleTree());
|
|
568
|
+
const swapProofGroups = swapProofsInfo.proofs;
|
|
569
|
+
const swapCall = this.getManageCall(
|
|
570
|
+
swapProofGroups,
|
|
571
|
+
await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
|
|
572
|
+
);
|
|
573
|
+
calls.push(swapCall);
|
|
574
|
+
const proofsInfoDeposit = extendedAdapter.getProofs(
|
|
575
|
+
true,
|
|
576
|
+
this.getMerkleTree()
|
|
577
|
+
);
|
|
578
|
+
//Deposit Amount would still be in usdc
|
|
579
|
+
const proofGroupsDeposit = proofsInfoDeposit.proofs;
|
|
580
|
+
const callDeposit = this.getManageCall(
|
|
581
|
+
proofGroupsDeposit,
|
|
582
|
+
await proofsInfoDeposit.callConstructor({ amount: params.amount })
|
|
583
|
+
);
|
|
584
|
+
calls.push(callDeposit);
|
|
585
|
+
return {
|
|
586
|
+
calls: calls,
|
|
587
|
+
status: true
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
calls: [],
|
|
592
|
+
status: false
|
|
593
|
+
};
|
|
594
|
+
} catch (err) {
|
|
595
|
+
logger.error(`error moving assets: ${err}`);
|
|
596
|
+
return {
|
|
597
|
+
calls: [],
|
|
598
|
+
status: false
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async handleDeposit(): Promise<{
|
|
604
|
+
extendedAmountInBTC: Web3Number,
|
|
605
|
+
calls: Call[]
|
|
606
|
+
}> {
|
|
607
|
+
try {
|
|
608
|
+
const vesuAdapter = await this.getVesuAdapter();
|
|
609
|
+
const extendedAdapter = await this.getExtendedAdapter();
|
|
610
|
+
const avnuAdapter = await this.getAvnuAdapter();
|
|
611
|
+
if (
|
|
612
|
+
!vesuAdapter ||
|
|
613
|
+
!extendedAdapter ||
|
|
614
|
+
!extendedAdapter.client ||
|
|
615
|
+
!avnuAdapter
|
|
616
|
+
) {
|
|
617
|
+
logger.error(
|
|
618
|
+
"vesu or extended adapter not found",
|
|
619
|
+
vesuAdapter,
|
|
620
|
+
extendedAdapter
|
|
621
|
+
);
|
|
622
|
+
return {
|
|
623
|
+
extendedAmountInBTC: new Web3Number(0, 0),
|
|
624
|
+
calls: [],
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
const extendedLeverage = calculateExtendedLevergae();
|
|
628
|
+
const isPriceDifferenceBetweenAvnuAndExtended = await this.checkPriceDifferenceBetweenAvnuAndExtended(extendedAdapter, vesuAdapter, avnuAdapter);
|
|
629
|
+
if (!isPriceDifferenceBetweenAvnuAndExtended) {
|
|
630
|
+
logger.error("price difference between avnu and extended doesn't fit the range");
|
|
631
|
+
return {
|
|
632
|
+
extendedAmountInBTC: new Web3Number(0, 0),
|
|
633
|
+
calls: [],
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
const position = await extendedAdapter.getAllOpenPositions();
|
|
637
|
+
if (!position) {
|
|
638
|
+
logger.error("error getting extended position", position);
|
|
639
|
+
return {
|
|
640
|
+
extendedAmountInBTC: new Web3Number(0, 0),
|
|
641
|
+
calls: [],
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
const extendedPositionValue = position.length > 0 ? parseFloat(position[0].value) : 0;
|
|
645
|
+
const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
|
|
646
|
+
if (!extendedHoldings) {
|
|
647
|
+
logger.error(`error getting extended holdings: ${extendedHoldings}`);
|
|
648
|
+
return {
|
|
649
|
+
extendedAmountInBTC: new Web3Number(0, 0),
|
|
650
|
+
calls: [],
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
const extendedHoldingAmount = new Web3Number(
|
|
654
|
+
extendedHoldings.availableForWithdrawal,
|
|
655
|
+
USDC_TOKEN_DECIMALS
|
|
656
|
+
);
|
|
657
|
+
const {
|
|
658
|
+
collateralTokenAmount,
|
|
659
|
+
} = await vesuAdapter.vesuAdapter.getAssetPrices();
|
|
660
|
+
const { collateralPrice } = await this.getAssetPrices();
|
|
661
|
+
const { vesuAmountInBTC, extendedAmountInBTC } = calculateVesUPositionSizeGivenExtended(
|
|
662
|
+
extendedPositionValue,
|
|
663
|
+
extendedHoldingAmount,
|
|
664
|
+
collateralTokenAmount,
|
|
665
|
+
collateralPrice.price
|
|
666
|
+
);
|
|
667
|
+
logger.info(`vesuAmountInBTC ${vesuAmountInBTC}, extendedAmountInBTC ${extendedAmountInBTC}`);
|
|
668
|
+
|
|
669
|
+
let calls: Call[] = [];
|
|
670
|
+
if (vesuAmountInBTC.greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)) {
|
|
671
|
+
const proofsInfo = vesuAdapter.getProofs(true, this.getMerkleTree());
|
|
672
|
+
const proofGroups = proofsInfo.proofs;
|
|
673
|
+
const call = this.getManageCall(
|
|
674
|
+
proofGroups,
|
|
675
|
+
await proofsInfo.callConstructor({
|
|
676
|
+
amount: vesuAmountInBTC,
|
|
677
|
+
})
|
|
678
|
+
);
|
|
679
|
+
const { amount: wbtcAmountInVaultAllocator } = await this.getUnusedBalanceWBTC();
|
|
680
|
+
if (wbtcAmountInVaultAllocator.lessThan(vesuAmountInBTC)) {
|
|
681
|
+
logger.info(`WBTC amount in vault allocator is less than vesu amount required in WBTC thus swapping, wbtcAmountInVaultAllocator: ${wbtcAmountInVaultAllocator}, vesuAmountInBTC: ${vesuAmountInBTC}`);
|
|
682
|
+
const swapProofsInfo = avnuAdapter.getProofs(true, this.getMerkleTree());
|
|
683
|
+
const swapProofGroups = swapProofsInfo.proofs;
|
|
684
|
+
const swapCall = this.getManageCall(
|
|
685
|
+
swapProofGroups,
|
|
686
|
+
await swapProofsInfo.callConstructor({
|
|
687
|
+
amount: vesuAmountInBTC,
|
|
688
|
+
})
|
|
689
|
+
);
|
|
690
|
+
calls.push(swapCall);
|
|
691
|
+
}
|
|
692
|
+
calls.push(call);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const shortPosition = extendedAmountInBTC.multipliedBy(3).abs().greaterThan(MINIMUM_EXTENDED_POSITION_SIZE) ? await extendedAdapter.createOrder(
|
|
696
|
+
extendedLeverage.toString(),
|
|
697
|
+
extendedAmountInBTC.toNumber(),
|
|
698
|
+
OrderSide.SELL
|
|
699
|
+
) : null;
|
|
700
|
+
if (!shortPosition && extendedAmountInBTC.multipliedBy(3).abs().greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)) {
|
|
701
|
+
logger.error(`error creating short position thus no position to be opened on vesu: ${shortPosition}`);
|
|
702
|
+
return {
|
|
703
|
+
extendedAmountInBTC: new Web3Number(0, 0),
|
|
704
|
+
calls: [],
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
extendedAmountInBTC: extendedAmountInBTC,
|
|
709
|
+
calls: calls,
|
|
710
|
+
};
|
|
711
|
+
} catch (err) {
|
|
712
|
+
logger.error(`error handling deposit: ${err}`);
|
|
713
|
+
return {
|
|
714
|
+
extendedAmountInBTC: new Web3Number(0, 0),
|
|
715
|
+
calls: [],
|
|
716
|
+
};;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
async checkPriceDifferenceBetweenAvnuAndExtended(extendedAdapter: ExtendedAdapter, vesuAdapter: VesuMultiplyAdapter, avnuAdapter: AvnuAdapter): Promise<boolean> {
|
|
721
|
+
const {
|
|
722
|
+
ask, bid
|
|
723
|
+
} = await extendedAdapter.fetchOrderBookBTCUSDC();
|
|
724
|
+
const price = ask.plus(bid).dividedBy(2);
|
|
725
|
+
const btcToken = vesuAdapter.config.supportedPositions[0].asset;
|
|
726
|
+
const btcPriceAvnu = await avnuAdapter.getPriceOfToken(btcToken.address.toString());
|
|
727
|
+
if (!btcPriceAvnu) {
|
|
728
|
+
logger.error(`error getting btc price avnu: ${btcPriceAvnu}`);
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
const priceDifference = price.minus(btcPriceAvnu).toNumber();
|
|
732
|
+
if (priceDifference < MAX_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED && priceDifference > MIN_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED) {
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
logger.error(`price difference between avnu and extended doesn't fit the range, priceDifference: ${priceDifference}`);
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async handleWithdraw(amount: Web3Number): Promise<{calls: Call[], status: boolean}> {
|
|
740
|
+
try {
|
|
741
|
+
const usdcBalanceVaultAllocator = await this.getUnusedBalance()
|
|
742
|
+
const usdcBalanceDifference = amount.plus(BUFFER_USDC_IN_WITHDRAWAL).minus(usdcBalanceVaultAllocator.usdValue);
|
|
743
|
+
logger.info(`usdcBalanceDifference, ${usdcBalanceDifference.toNumber()}`);
|
|
744
|
+
let calls: Call[] = [];
|
|
745
|
+
let status: boolean = true;
|
|
746
|
+
if (usdcBalanceDifference.lessThan(0)) {
|
|
747
|
+
const withdrawCall = await this.getBringLiquidityCall({
|
|
748
|
+
amount: usdcBalanceVaultAllocator.amount
|
|
749
|
+
})
|
|
750
|
+
logger.info("withdraw call", withdrawCall);
|
|
751
|
+
calls.push(withdrawCall);
|
|
752
|
+
return {
|
|
753
|
+
calls:calls,
|
|
754
|
+
status:true
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
const vesuAdapter = await this.getVesuAdapter();
|
|
758
|
+
const extendedAdapter = await this.getExtendedAdapter();
|
|
759
|
+
if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
|
|
760
|
+
status = false;
|
|
761
|
+
logger.error(
|
|
762
|
+
`vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
|
|
763
|
+
);
|
|
764
|
+
return {
|
|
765
|
+
calls:calls,
|
|
766
|
+
status:status
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
const { collateralTokenAmount } =
|
|
770
|
+
await vesuAdapter.vesuAdapter.getAssetPrices();
|
|
771
|
+
const {
|
|
772
|
+
collateralPrice
|
|
773
|
+
} = await this.getAssetPrices();
|
|
774
|
+
const extendedPositon = await extendedAdapter.getAllOpenPositions();
|
|
775
|
+
if(!extendedPositon) {
|
|
776
|
+
status = false;
|
|
777
|
+
logger.error("error getting extended position", extendedPositon);
|
|
778
|
+
return {
|
|
779
|
+
calls:calls,
|
|
780
|
+
status:status
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
const amountDistributionForWithdrawal =
|
|
784
|
+
await calculateAmountDistributionForWithdrawal(
|
|
785
|
+
usdcBalanceDifference,
|
|
786
|
+
collateralPrice.price,
|
|
787
|
+
collateralTokenAmount,
|
|
788
|
+
extendedPositon
|
|
789
|
+
);
|
|
790
|
+
if (!amountDistributionForWithdrawal) {
|
|
791
|
+
status = false;
|
|
792
|
+
logger.error(
|
|
793
|
+
`error calculating amount distribution for withdrawal: ${amountDistributionForWithdrawal}`
|
|
794
|
+
);
|
|
795
|
+
return {
|
|
796
|
+
calls:calls,
|
|
797
|
+
status:status
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
const { vesu_amount, extended_amount } = amountDistributionForWithdrawal;
|
|
801
|
+
|
|
802
|
+
if (status && vesu_amount.greaterThan(0)) {
|
|
803
|
+
const {calls: vesuCalls, status: vesuStatus} = await this.moveAssets(
|
|
804
|
+
{
|
|
805
|
+
amount: vesu_amount,
|
|
806
|
+
from: Protocols.VESU.name,
|
|
807
|
+
to: Protocols.VAULT.name,
|
|
808
|
+
},
|
|
809
|
+
extendedAdapter,
|
|
810
|
+
vesuAdapter
|
|
811
|
+
);
|
|
812
|
+
status = vesuStatus;
|
|
813
|
+
calls.push(...vesuCalls);
|
|
814
|
+
}
|
|
815
|
+
if (status && extended_amount.greaterThan(0)) {
|
|
816
|
+
const {calls: extendedCalls, status: extendedStatus} = await this.moveAssets(
|
|
817
|
+
{
|
|
818
|
+
amount: extended_amount,
|
|
819
|
+
from: Protocols.EXTENDED.name,
|
|
820
|
+
to: Protocols.VAULT.name,
|
|
821
|
+
},
|
|
822
|
+
extendedAdapter,
|
|
823
|
+
vesuAdapter
|
|
824
|
+
);
|
|
825
|
+
status = extendedStatus;
|
|
826
|
+
if(status) {
|
|
827
|
+
calls.push(...extendedCalls);
|
|
828
|
+
}else{
|
|
829
|
+
logger.error("error moving assets to vault: extendedStatus: ${extendedStatus}");
|
|
830
|
+
return {
|
|
831
|
+
calls: [],
|
|
832
|
+
status: status
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
const withdrawCall = await this.getBringLiquidityCall({
|
|
837
|
+
amount: amount
|
|
838
|
+
})
|
|
839
|
+
logger.info("withdraw call", withdrawCall);
|
|
840
|
+
calls.push(withdrawCall);
|
|
841
|
+
return {
|
|
842
|
+
calls: calls,
|
|
843
|
+
status: status
|
|
844
|
+
};
|
|
845
|
+
} catch (err) {
|
|
846
|
+
logger.error(`error handling withdrawal: ${err}`);
|
|
847
|
+
return {
|
|
848
|
+
calls: [],
|
|
849
|
+
status: false
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
async getAUM(): Promise<{ net: SingleTokenInfo, prevAum: Web3Number, splits: PositionInfo[] }> {
|
|
855
|
+
const allPositions: PositionInfo[] = [];
|
|
856
|
+
for (let adapter of this.metadata.additionalInfo.adapters) {
|
|
857
|
+
const positions = await adapter.adapter.getPositions();
|
|
858
|
+
allPositions.push(...positions);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const assetPrice = await this.pricer.getPrice(this.asset().symbol);
|
|
862
|
+
let netAUM = new Web3Number(0, this.asset().decimals);
|
|
863
|
+
for (let position of allPositions) {
|
|
864
|
+
if (position.tokenInfo.address.eq(this.asset().address)) {
|
|
865
|
+
netAUM = netAUM.plus(position.amount);
|
|
866
|
+
} else {
|
|
867
|
+
netAUM = netAUM.plus(position.usdValue / assetPrice.price);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const prevAum = await this.getPrevAUM();
|
|
872
|
+
const realAUM: PositionInfo = {
|
|
873
|
+
tokenInfo: this.asset(),
|
|
874
|
+
amount: netAUM,
|
|
875
|
+
usdValue: netAUM.toNumber() * assetPrice.price,
|
|
876
|
+
apy: { apy: netAUM.toNumber() * assetPrice.price, type: APYType.BASE },
|
|
877
|
+
remarks: AUMTypes.FINALISED,
|
|
878
|
+
protocol: Protocols.NONE // just placeholder
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
const estimatedAUMDelta: PositionInfo = {
|
|
882
|
+
tokenInfo: this.asset(),
|
|
883
|
+
amount: Web3Number.fromWei('0', this.asset().decimals),
|
|
884
|
+
usdValue: 0,
|
|
885
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
886
|
+
remarks: AUMTypes.DEFISPRING,
|
|
887
|
+
protocol: Protocols.NONE // just placeholder
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
return {
|
|
891
|
+
net: {
|
|
892
|
+
tokenInfo: this.asset(),
|
|
893
|
+
amount: netAUM,
|
|
894
|
+
usdValue: netAUM.toNumber() * assetPrice.price
|
|
895
|
+
}, prevAum: prevAum, splits: [realAUM, estimatedAUMDelta]
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function getLooperSettings(
|
|
902
|
+
lstSymbol: string,
|
|
903
|
+
underlyingSymbol: string,
|
|
904
|
+
vaultSettings: VesuExtendedStrategySettings,
|
|
905
|
+
pool1: ContractAddr,
|
|
906
|
+
extendedBackendUrl: string,
|
|
907
|
+
extendedApiKey: string,
|
|
908
|
+
vaultIdExtended: number,
|
|
909
|
+
) {
|
|
910
|
+
vaultSettings.leafAdapters = [];
|
|
911
|
+
|
|
912
|
+
const wbtcToken = Global.getDefaultTokens().find(
|
|
913
|
+
(token) => token.symbol === lstSymbol
|
|
914
|
+
)!;
|
|
915
|
+
const usdcToken = Global.getDefaultTokens().find(
|
|
916
|
+
(token) => token.symbol === underlyingSymbol
|
|
917
|
+
)!;
|
|
918
|
+
|
|
919
|
+
const baseAdapterConfig: BaseAdapterConfig = {
|
|
920
|
+
baseToken: wbtcToken,
|
|
921
|
+
supportedPositions: [
|
|
922
|
+
{ asset: usdcToken, isDebt: true },
|
|
923
|
+
{ asset: wbtcToken, isDebt: false },
|
|
924
|
+
],
|
|
925
|
+
//Since we open 2 positions, we need to add both positions, one is debt another is collateral
|
|
926
|
+
networkConfig: getMainnetConfig(),
|
|
927
|
+
pricer: new PricerFromApi(getMainnetConfig(), Global.getDefaultTokens()),
|
|
928
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
929
|
+
vaultAddress: vaultSettings.vaultAddress,
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
const avnuAdapter = new AvnuAdapter({
|
|
933
|
+
...baseAdapterConfig,
|
|
934
|
+
avnuContract: AVNU_MIDDLEWARE,
|
|
935
|
+
slippage: 0.01,
|
|
936
|
+
baseUrl: AVNU_QUOTE_URL,
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
const extendedAdapter = new ExtendedAdapter({
|
|
940
|
+
...baseAdapterConfig,
|
|
941
|
+
supportedPositions: [
|
|
942
|
+
{ asset: usdcToken, isDebt: true },
|
|
943
|
+
],
|
|
944
|
+
vaultIdExtended: vaultIdExtended,
|
|
945
|
+
extendedContract: EXTENDED_CONTRACT,
|
|
946
|
+
extendedBackendUrl: extendedBackendUrl,
|
|
947
|
+
extendedApiKey: extendedApiKey,
|
|
948
|
+
extendedTimeout: 30000,
|
|
949
|
+
extendedRetries: 3,
|
|
950
|
+
extendedBaseUrl: "https://api.starknet.extended.exchange",
|
|
951
|
+
extendedMarketName: "BTC-USD",
|
|
952
|
+
extendedPrecision: 5,
|
|
953
|
+
avnuAdapter: avnuAdapter,
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
const vesuMultiplyAdapter = new VesuMultiplyAdapter({
|
|
957
|
+
poolId: pool1,
|
|
958
|
+
collateral: wbtcToken,
|
|
959
|
+
debt: usdcToken,
|
|
960
|
+
targetHealthFactor: vaultSettings.targetHealthFactor,
|
|
961
|
+
minHealthFactor: vaultSettings.minHealthFactor,
|
|
962
|
+
quoteAmountToFetchPrice: vaultSettings.quoteAmountToFetchPrice,
|
|
963
|
+
...baseAdapterConfig,
|
|
964
|
+
supportedPositions: [
|
|
965
|
+
{ asset: wbtcToken, isDebt: false },
|
|
966
|
+
{ asset: usdcToken, isDebt: true },
|
|
967
|
+
],
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
const unusedBalanceAdapter = new UnusedBalanceAdapter({
|
|
971
|
+
...baseAdapterConfig,
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
vaultSettings.adapters.push({
|
|
975
|
+
id: `${vesuMultiplyAdapter.name}_${wbtcToken.symbol}_${usdcToken.symbol}`,
|
|
976
|
+
adapter: vesuMultiplyAdapter,
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
vaultSettings.adapters.push({
|
|
980
|
+
id: `${unusedBalanceAdapter.name}_${wbtcToken.symbol}`,
|
|
981
|
+
adapter: unusedBalanceAdapter,
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
vaultSettings.adapters.push({
|
|
985
|
+
id: `${extendedAdapter.name}_${wbtcToken.symbol}`,
|
|
986
|
+
adapter: extendedAdapter,
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
vaultSettings.adapters.push({
|
|
990
|
+
id: `${avnuAdapter.name}_${wbtcToken.symbol}`,
|
|
991
|
+
adapter: avnuAdapter,
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
const commonAdapter = new CommonAdapter({
|
|
995
|
+
id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN,
|
|
996
|
+
vaultAddress: vaultSettings.vaultAddress,
|
|
997
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
998
|
+
manager: vaultSettings.manager,
|
|
999
|
+
asset: wbtcToken.address,
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getDepositLeaf());
|
|
1003
|
+
vaultSettings.leafAdapters.push(() =>
|
|
1004
|
+
vesuMultiplyAdapter.getWithdrawLeaf()
|
|
1005
|
+
);
|
|
1006
|
+
vaultSettings.leafAdapters.push(() => extendedAdapter.getDepositLeaf());
|
|
1007
|
+
vaultSettings.leafAdapters.push(() => extendedAdapter.getSwapFromLegacyLeaf());
|
|
1008
|
+
vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
|
|
1009
|
+
vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
|
|
1010
|
+
// Doubt here, should this be usdcToken.address, or wbtcToken.address?
|
|
1011
|
+
vaultSettings.leafAdapters.push(
|
|
1012
|
+
commonAdapter
|
|
1013
|
+
.getApproveAdapter(
|
|
1014
|
+
usdcToken.address,
|
|
1015
|
+
vaultSettings.vaultAddress,
|
|
1016
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
|
|
1017
|
+
)
|
|
1018
|
+
.bind(commonAdapter)
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
vaultSettings.leafAdapters.push(
|
|
1022
|
+
commonAdapter
|
|
1023
|
+
.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
|
|
1024
|
+
.bind(commonAdapter)
|
|
1025
|
+
);
|
|
1026
|
+
return vaultSettings;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
function getDescription(tokenSymbol: string, underlyingSymbol: string) {
|
|
1031
|
+
return VaultDescription(tokenSymbol, underlyingSymbol);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
export default function VaultDescription(
|
|
1035
|
+
lstSymbol: string,
|
|
1036
|
+
underlyingSymbol: string
|
|
1037
|
+
) {
|
|
1038
|
+
const containerStyle = {
|
|
1039
|
+
maxWidth: "800px",
|
|
1040
|
+
margin: "0 auto",
|
|
1041
|
+
backgroundColor: "#111",
|
|
1042
|
+
color: "#eee",
|
|
1043
|
+
fontFamily: "Arial, sans-serif",
|
|
1044
|
+
borderRadius: "12px",
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
return (
|
|
1048
|
+
<div style={containerStyle}>
|
|
1049
|
+
<h1 style={{ fontSize: "18px", marginBottom: "10px" }}>Liquidation risk managed leverged {lstSymbol} Vault</h1>
|
|
1050
|
+
<p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
|
|
1051
|
+
This Levered Endur {lstSymbol} vault is a tokenized leveraged Vault, auto-compounding strategy that takes upto 5x leverage on {lstSymbol} by borrow {underlyingSymbol}. Borrowed amount
|
|
1052
|
+
is swapped to {lstSymbol} to create leverage. Depositors receive vault shares that
|
|
1053
|
+
represent a proportional claim on the underlying assets and accrued yield.
|
|
1054
|
+
</p>
|
|
1055
|
+
|
|
1056
|
+
<p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
|
|
1057
|
+
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" }])}
|
|
1058
|
+
{" "}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.
|
|
1059
|
+
</p>
|
|
1060
|
+
|
|
1061
|
+
<div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
|
|
1062
|
+
<p style={{ fontSize: "13px", color: "#ccc" }}>
|
|
1063
|
+
<strong>Withdrawals:</strong> Requests can take up to <strong>1-2 hours</strong> to process as the vault unwinds and settles routing.
|
|
1064
|
+
</p>
|
|
1065
|
+
</div>
|
|
1066
|
+
<div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
|
|
1067
|
+
<p style={{ fontSize: "13px", color: "#ccc" }}>
|
|
1068
|
+
<strong>Debt limits:</strong> Pools on Vesu have debt caps that are gradually increased over time. Until caps are raised, deposited Tokens remain in the vault, generating a shared net return for all depositors. There is no additional fee taken by Troves on Yield token's APY, its only on added gain.
|
|
1069
|
+
</p>
|
|
1070
|
+
</div>
|
|
1071
|
+
{/* <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
|
|
1072
|
+
<p style={{ fontSize: "13px", color: "#ccc" }}>
|
|
1073
|
+
<strong>APY assumptions:</strong> APY shown is the max possible value given current LST and borrowing rates. True APY will be subject to the actual leverage, based on above point. More insights on exact APY will be added soon.
|
|
1074
|
+
</p>
|
|
1075
|
+
</div> */}
|
|
1076
|
+
</div>
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const re7UsdcPrimeDevansh: VesuExtendedStrategySettings = {
|
|
1081
|
+
vaultAddress: ContractAddr.from("0x520a2e945dd0762e5284fc1b012f62ca04e238e105eb362d5e0ce208928729d"),
|
|
1082
|
+
manager: ContractAddr.from("0x6abe24d31cbc16d7c4acb2421a826f96273e225b4a2d168d91e65123da0bfb9"),
|
|
1083
|
+
vaultAllocator: ContractAddr.from("0x6e4f716e22efb164ee4a831233e513f45396ad3be4cff3c07386be0226febed"),
|
|
1084
|
+
redeemRequestNFT: ContractAddr.from("0x66060e1874e05506b18e6029188ec49bf231a411ad57642311bbdf3cb22e5f"),
|
|
1085
|
+
aumOracle: ContractAddr.from("0x301d883b9b45c76132638e39326b3f464c492599623263d405ec0df991e27ab"),
|
|
1086
|
+
leafAdapters: [],
|
|
1087
|
+
adapters: [],
|
|
1088
|
+
targetHealthFactor: 1.4,
|
|
1089
|
+
minHealthFactor: 1.05,
|
|
1090
|
+
underlyingToken: Global.getDefaultTokens().find(
|
|
1091
|
+
(token) => token.symbol === "USDC"
|
|
1092
|
+
)!,
|
|
1093
|
+
quoteAmountToFetchPrice: new Web3Number(
|
|
1094
|
+
"0.001",
|
|
1095
|
+
Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!.decimals
|
|
1096
|
+
),
|
|
1097
|
+
borrowable_assets: [Global.getDefaultTokens().find(token => token.symbol === "WBTC")!],
|
|
1098
|
+
minimumWBTCDifferenceForAvnuSwap: MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP,
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
export const VesuExtendedTestStrategies = (extendedBackendUrl: string, extendedApiKey: string, vaultIdExtended: number): IStrategyMetadata<VesuExtendedStrategySettings>[] => {
|
|
1102
|
+
return [
|
|
1103
|
+
getStrategySettingsVesuExtended('WBTC', 'USDC', re7UsdcPrimeDevansh, false, false, extendedBackendUrl, extendedApiKey, vaultIdExtended),
|
|
1104
|
+
]
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
function getStrategySettingsVesuExtended(lstSymbol: string, underlyingSymbol: string, addresses: VesuExtendedStrategySettings, isPreview: boolean = false, isLST: boolean, extendedBackendUrl: string, extendedApiKey: string, vaultIdExtended: number): IStrategyMetadata<VesuExtendedStrategySettings> {
|
|
1110
|
+
return {
|
|
1111
|
+
name: `Extended Test ${underlyingSymbol}`,
|
|
1112
|
+
description: getDescription(lstSymbol, underlyingSymbol),
|
|
1113
|
+
address: addresses.vaultAddress,
|
|
1114
|
+
launchBlock: 0,
|
|
1115
|
+
type: 'Other',
|
|
1116
|
+
depositTokens: [Global.getDefaultTokens().find(token => token.symbol === underlyingSymbol)!],
|
|
1117
|
+
additionalInfo: getLooperSettings(lstSymbol, underlyingSymbol, addresses, VesuPools.Re7USDCPrime, extendedBackendUrl, extendedApiKey, vaultIdExtended),
|
|
1118
|
+
risk: {
|
|
1119
|
+
riskFactor: _riskFactor,
|
|
1120
|
+
netRisk:
|
|
1121
|
+
_riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
|
|
1122
|
+
_riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
|
|
1123
|
+
notARisks: getNoRiskTags(_riskFactor)
|
|
1124
|
+
},
|
|
1125
|
+
auditUrl: AUDIT_URL,
|
|
1126
|
+
protocols: [Protocols.ENDUR, Protocols.VESU],
|
|
1127
|
+
maxTVL: Web3Number.fromWei(0, 18),
|
|
1128
|
+
contractDetails: getContractDetails(addresses),
|
|
1129
|
+
faqs: getFAQs(lstSymbol, underlyingSymbol, isLST),
|
|
1130
|
+
investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
|
|
1131
|
+
isPreview: isPreview,
|
|
1132
|
+
apyMethodology: isLST ? '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.' : 'Current annualized APY in terms of base asset of the Yield Token. There is no additional fee taken by Troves on yield token APY. We charge a 10% performance fee on the additional gain which is already accounted in the APY shown.'
|
|
1133
|
+
}
|
|
1134
|
+
}
|