@strkfarm/sdk 2.0.0-dev.34 → 2.0.0-dev.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.global.js +1486 -64
- package/dist/index.browser.mjs +2433 -1010
- package/dist/index.d.ts +104 -2
- package/dist/index.js +1452 -26
- package/dist/index.mjs +2437 -1014
- package/package.json +1 -1
- package/src/data/redeem-request-nft.abi.json +752 -0
- package/src/strategies/index.ts +1 -0
- package/src/strategies/svk-strategy.ts +3 -2
- package/src/strategies/universal-adapters/adapter-utils.ts +2 -0
- package/src/strategies/universal-adapters/avnu-adapter.ts +3 -2
- package/src/strategies/universal-adapters/extended-adapter.ts +25 -0
- package/src/strategies/universal-adapters/svk-troves-adapter.ts +141 -7
- package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +69 -39
- package/src/strategies/usdc-boosted-strategy.tsx +693 -0
- package/src/strategies/vesu-extended-strategy/services/executionService.ts +1 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMainnetConfig,
|
|
3
|
+
IConfig,
|
|
4
|
+
IStrategyMetadata,
|
|
5
|
+
Protocols,
|
|
6
|
+
TokenInfo,
|
|
7
|
+
AuditStatus,
|
|
8
|
+
SourceCodeType,
|
|
9
|
+
AccessControlType,
|
|
10
|
+
InstantWithdrawalVault,
|
|
11
|
+
VaultType,
|
|
12
|
+
RedemptionInfo,
|
|
13
|
+
StrategyTag,
|
|
14
|
+
VaultPosition,
|
|
15
|
+
} from "@/interfaces";
|
|
16
|
+
import {
|
|
17
|
+
AUMTypes,
|
|
18
|
+
getContractDetails,
|
|
19
|
+
UNIVERSAL_MANAGE_IDS,
|
|
20
|
+
UniversalStrategySettings,
|
|
21
|
+
} from "./universal-strategy";
|
|
22
|
+
import { PricerBase } from "@/modules/pricerBase";
|
|
23
|
+
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
24
|
+
import { Global } from "@/global";
|
|
25
|
+
import {
|
|
26
|
+
APYType,
|
|
27
|
+
AvnuAdapter,
|
|
28
|
+
AvnuAdapterConfig,
|
|
29
|
+
BaseAdapterConfig,
|
|
30
|
+
CommonAdapter,
|
|
31
|
+
ManageCall,
|
|
32
|
+
PositionInfo,
|
|
33
|
+
TokenTransferAdapter,
|
|
34
|
+
VesuModifyPositionAdapter,
|
|
35
|
+
} from "./universal-adapters";
|
|
36
|
+
import { SvkTrovesAdapter } from "./universal-adapters/svk-troves-adapter";
|
|
37
|
+
import { VesuPools } from "./universal-adapters/vesu-adapter";
|
|
38
|
+
import {
|
|
39
|
+
AVNU_EXCHANGE,
|
|
40
|
+
AVNU_QUOTE_URL,
|
|
41
|
+
} from "./universal-adapters/adapter-utils";
|
|
42
|
+
import { PricerFromApi, ERC20 } from "@/modules";
|
|
43
|
+
import { assert, logger } from "@/utils";
|
|
44
|
+
import { SingleActionAmount, SingleTokenInfo } from "./base-strategy";
|
|
45
|
+
import { SVKStrategy } from "./svk-strategy";
|
|
46
|
+
import { BlockIdentifier, Call, uint256 } from "starknet";
|
|
47
|
+
import { AdapterOptimizer } from "./universal-adapters/adapter-optimizer";
|
|
48
|
+
|
|
49
|
+
export interface USDCBoostedStrategySettings extends UniversalStrategySettings {
|
|
50
|
+
vesuPoolId: ContractAddr;
|
|
51
|
+
collateralToken: TokenInfo; // USDC
|
|
52
|
+
debtToken: TokenInfo; // STRK
|
|
53
|
+
maxLTV: number; // e.g. 0.66
|
|
54
|
+
targetLTV: number;
|
|
55
|
+
hyperxSTRKVaultAddress: ContractAddr;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class USDCBoostedStrategy<
|
|
59
|
+
S extends USDCBoostedStrategySettings,
|
|
60
|
+
> extends SVKStrategy<S> {
|
|
61
|
+
constructor(
|
|
62
|
+
config: IConfig,
|
|
63
|
+
pricer: PricerBase,
|
|
64
|
+
metadata: IStrategyMetadata<S>,
|
|
65
|
+
) {
|
|
66
|
+
super(config, pricer, metadata);
|
|
67
|
+
|
|
68
|
+
this.metadata.additionalInfo.adapters.forEach((adapter) => {
|
|
69
|
+
adapter.adapter.config.networkConfig = this.config;
|
|
70
|
+
adapter.adapter.config.pricer = this.pricer;
|
|
71
|
+
if ((adapter.adapter as any)._vesuAdapter) {
|
|
72
|
+
(adapter.adapter as any)._vesuAdapter.networkConfig = this.config;
|
|
73
|
+
(adapter.adapter as any)._vesuAdapter.pricer = this.pricer;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getTag(): string {
|
|
79
|
+
return `${USDCBoostedStrategy.name}:${this.metadata.name}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private getAdapterById<T>(id: string): T {
|
|
83
|
+
const entry = this.metadata.additionalInfo.adapters.find(
|
|
84
|
+
(a) => a.id === id,
|
|
85
|
+
);
|
|
86
|
+
if (!entry) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`${this.getTag()}::getAdapterById: adapter not found for id "${id}"`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return entry.adapter as T;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async getVesuModifyPositionCall(params: {
|
|
95
|
+
isDeposit: boolean;
|
|
96
|
+
leg1DepositAmount: Web3Number;
|
|
97
|
+
debtAmount?: Web3Number;
|
|
98
|
+
}): Promise<Call> {
|
|
99
|
+
logger.verbose(
|
|
100
|
+
`${this.getTag()}::getVesuModifyPositionCall isDeposit=${params.isDeposit}, amount=${params.leg1DepositAmount}, debtAmount=${params.debtAmount?.toNumber()}`,
|
|
101
|
+
);
|
|
102
|
+
return this.buildManageCallFromAdapter(
|
|
103
|
+
this.getAdapterById<VesuModifyPositionAdapter>("vesu_usdc_strk"),
|
|
104
|
+
params.isDeposit,
|
|
105
|
+
params.leg1DepositAmount,
|
|
106
|
+
params.debtAmount ? { debtAmount: params.debtAmount } : undefined,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async getVesuPositions(): Promise<VaultPosition[]> {
|
|
111
|
+
const positions =
|
|
112
|
+
await this.getAdapterById<VesuModifyPositionAdapter>(
|
|
113
|
+
"vesu_usdc_strk",
|
|
114
|
+
).getPositions();
|
|
115
|
+
return positions.map((p) => ({
|
|
116
|
+
amount: p.amount,
|
|
117
|
+
usdValue: p.usdValue,
|
|
118
|
+
remarks: p.remarks ?? "",
|
|
119
|
+
token: p.tokenInfo,
|
|
120
|
+
protocol: p.protocol,
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async getVesuHealthFactor(
|
|
125
|
+
blockNumber: BlockIdentifier = "latest",
|
|
126
|
+
): Promise<number> {
|
|
127
|
+
const vesuAdapter =
|
|
128
|
+
this.getAdapterById<VesuModifyPositionAdapter>("vesu_usdc_strk");
|
|
129
|
+
return await vesuAdapter._vesuAdapter.getHealthFactor(blockNumber);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// TODO: will have to still modify for instant withdrawals as of now
|
|
133
|
+
async getModifyHyperPositionCall(params: {
|
|
134
|
+
isDeposit: boolean;
|
|
135
|
+
amount: Web3Number;
|
|
136
|
+
}): Promise<Call> {
|
|
137
|
+
logger.verbose(
|
|
138
|
+
`${this.getTag()}::getModifyHyperPositionCall isDeposit=${params.isDeposit}, amount=${params.amount}`,
|
|
139
|
+
);
|
|
140
|
+
return this.buildManageCallFromAdapter(
|
|
141
|
+
this.getAdapterById<SvkTrovesAdapter>("hyper_xstrk"),
|
|
142
|
+
params.isDeposit,
|
|
143
|
+
params.amount,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async getAvnuSwapCall(params: {
|
|
148
|
+
isDeposit: boolean;
|
|
149
|
+
amount: Web3Number;
|
|
150
|
+
}): Promise<Call> {
|
|
151
|
+
logger.verbose(
|
|
152
|
+
`${this.getTag()}::getAvnuSwapCall isDeposit=${params.isDeposit}, amount=${params.amount}`,
|
|
153
|
+
);
|
|
154
|
+
return this.buildManageCallFromAdapter(
|
|
155
|
+
this.getAdapterById<AvnuAdapter>("avnu_strk_xstrk"),
|
|
156
|
+
params.isDeposit,
|
|
157
|
+
params.amount,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Returns the USDC balance sitting idle inside the vault contract itself.
|
|
163
|
+
* This balance can offset the required liquidity during withdrawal processing.
|
|
164
|
+
*/
|
|
165
|
+
async getUnusedBalanceVault(): Promise<Web3Number> {
|
|
166
|
+
const collateralToken = this.metadata.additionalInfo.collateralToken;
|
|
167
|
+
return new ERC20(this.config).balanceOf(
|
|
168
|
+
collateralToken.address,
|
|
169
|
+
this.metadata.additionalInfo.vaultAddress,
|
|
170
|
+
collateralToken.decimals,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Returns the current STRK balance sitting unused in the vault allocator.
|
|
176
|
+
* This covers STRK from prior borrow cycles that hasn't been swapped yet.
|
|
177
|
+
*/
|
|
178
|
+
// Technically we can use this or we can even use the avnu-adapter to get the unused debt
|
|
179
|
+
async getUnusedDebt(): Promise<Web3Number> {
|
|
180
|
+
const debtToken = this.metadata.additionalInfo.debtToken;
|
|
181
|
+
return new ERC20(this.config).balanceOf(
|
|
182
|
+
debtToken.address,
|
|
183
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
184
|
+
debtToken.decimals,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Returns the xSTRK balance sitting in the vault allocator.
|
|
190
|
+
* This is non-zero when the previous cycle's request_redeem on the HyperPosition
|
|
191
|
+
* has been settled — the redeemed xSTRK lands here and is ready to be swapped.
|
|
192
|
+
*/
|
|
193
|
+
async getxSTRKInVault(): Promise<Web3Number> {
|
|
194
|
+
const xstrkToken = Global.getDefaultTokens().find(
|
|
195
|
+
(t) => t.symbol === "xSTRK",
|
|
196
|
+
);
|
|
197
|
+
if (!xstrkToken) {
|
|
198
|
+
throw new Error(`${this.getTag()}:: xSTRK token not found`);
|
|
199
|
+
}
|
|
200
|
+
return new ERC20(this.config).balanceOf(
|
|
201
|
+
xstrkToken.address.address,
|
|
202
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
203
|
+
xstrkToken.decimals,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Simulates depositing `depositAmount` USDC on Vesu and returns the
|
|
209
|
+
* incremental STRK that would be borrowed. Needed to size the AVNU swap
|
|
210
|
+
* call that is batched together with the Vesu call in the same transaction.
|
|
211
|
+
*/
|
|
212
|
+
async computeVesuDepositBorrowAmount(
|
|
213
|
+
depositAmount: Web3Number,
|
|
214
|
+
): Promise<Web3Number> {
|
|
215
|
+
return this.getAdapterById<VesuModifyPositionAdapter>(
|
|
216
|
+
"vesu_usdc_strk",
|
|
217
|
+
).getExpectedDepositDebtDelta(depositAmount);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async computeVesuWithdrawDebtDelta(
|
|
221
|
+
withdrawAmount: Web3Number,
|
|
222
|
+
): Promise<Web3Number> {
|
|
223
|
+
return this.getAdapterById<VesuModifyPositionAdapter>(
|
|
224
|
+
"vesu_usdc_strk",
|
|
225
|
+
).getExpectedWithdrawDebtDelta(withdrawAmount);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async getPendingHyperAssets(): Promise<Web3Number> {
|
|
229
|
+
const xstrkToken = Global.getDefaultTokens().find(
|
|
230
|
+
(t) => t.symbol === "xSTRK",
|
|
231
|
+
)!;
|
|
232
|
+
// TODO: hardcoding for now ( since this is temp method we will use due_assets_from_owner later on )
|
|
233
|
+
const hyperXstrkRedeemNFT = ContractAddr.from(
|
|
234
|
+
"0x51e40b839dc0c2feca923f863072673b94abfa2483345be3b30b457a90d095"
|
|
235
|
+
);
|
|
236
|
+
return this.getAdapterById<SvkTrovesAdapter>(
|
|
237
|
+
"hyper_xstrk",
|
|
238
|
+
).getPendingAssetsFromOwnerNFTMethod(
|
|
239
|
+
hyperXstrkRedeemNFT,
|
|
240
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
241
|
+
xstrkToken.decimals,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// TODO: rn we are just making these functions in the strategy itself but
|
|
246
|
+
// later on we will move them to the SVKStrategy for generalization
|
|
247
|
+
|
|
248
|
+
async getUserTVL(
|
|
249
|
+
user: ContractAddr,
|
|
250
|
+
blockIdentifier: BlockIdentifier = "latest",
|
|
251
|
+
) {
|
|
252
|
+
const shares: any = await this.contract.call("balanceOf", [user.address], {
|
|
253
|
+
blockIdentifier,
|
|
254
|
+
});
|
|
255
|
+
const assets: any = await this.contract.call(
|
|
256
|
+
"convert_to_assets",
|
|
257
|
+
[uint256.bnToUint256(shares)],
|
|
258
|
+
{ blockIdentifier },
|
|
259
|
+
);
|
|
260
|
+
const amount = Web3Number.fromWei(
|
|
261
|
+
assets.toString(),
|
|
262
|
+
this.metadata.depositTokens[0].decimals,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const blockNumber =
|
|
266
|
+
typeof blockIdentifier === "number" || typeof blockIdentifier === "bigint"
|
|
267
|
+
? Number(blockIdentifier)
|
|
268
|
+
: undefined;
|
|
269
|
+
|
|
270
|
+
const price = await this.pricer.getPrice(
|
|
271
|
+
this.metadata.depositTokens[0].symbol,
|
|
272
|
+
blockNumber,
|
|
273
|
+
);
|
|
274
|
+
const usdValue = Number(amount.toFixed(6)) * price.price;
|
|
275
|
+
return {
|
|
276
|
+
tokenInfo: this.asset(),
|
|
277
|
+
amount,
|
|
278
|
+
usdValue,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async getTVL() {
|
|
283
|
+
const assets = await this.contract.total_assets();
|
|
284
|
+
const amount = Web3Number.fromWei(
|
|
285
|
+
assets.toString(),
|
|
286
|
+
this.metadata.depositTokens[0].decimals,
|
|
287
|
+
);
|
|
288
|
+
const price = await this.pricer.getPrice(
|
|
289
|
+
this.metadata.depositTokens[0].symbol,
|
|
290
|
+
);
|
|
291
|
+
const usdValue = Number(amount.toFixed(6)) * price.price;
|
|
292
|
+
return {
|
|
293
|
+
tokenInfo: this.asset(),
|
|
294
|
+
amount,
|
|
295
|
+
usdValue,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async getAUM(): Promise<{
|
|
300
|
+
net: SingleTokenInfo;
|
|
301
|
+
prevAum: Web3Number;
|
|
302
|
+
splits: PositionInfo[];
|
|
303
|
+
}> {
|
|
304
|
+
const underlying = this.asset(); // USDC
|
|
305
|
+
const usdcPrice = await this.pricer.getPrice(underlying.symbol);
|
|
306
|
+
|
|
307
|
+
const allPositions: PositionInfo[] = [];
|
|
308
|
+
for (const adapter of this.metadata.additionalInfo.adapters) {
|
|
309
|
+
const positions = await adapter.adapter.getPositions();
|
|
310
|
+
allPositions.push(...positions);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let netAUM = new Web3Number(0, underlying.decimals);
|
|
314
|
+
for (const position of allPositions) {
|
|
315
|
+
if (position.tokenInfo.address.eq(underlying.address)) {
|
|
316
|
+
netAUM = netAUM.plus(position.amount);
|
|
317
|
+
} else {
|
|
318
|
+
const valueInUSDC = position.usdValue;
|
|
319
|
+
netAUM = netAUM.plus(valueInUSDC);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
324
|
+
logger.verbose(
|
|
325
|
+
`${this.getTag()} unused balance: ${unusedBalance.amount.toNumber()}`,
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// Since this is only in USDC -> we are taking directly the amount ( ow can use the usdValue as well )
|
|
329
|
+
netAUM = netAUM.plus(unusedBalance.amount);
|
|
330
|
+
|
|
331
|
+
const prevAum = await this.getPrevAUM();
|
|
332
|
+
logger.verbose(`${this.getTag()} AUM: ${netAUM}`);
|
|
333
|
+
|
|
334
|
+
const realAUM: PositionInfo = {
|
|
335
|
+
tokenInfo: underlying,
|
|
336
|
+
amount: netAUM,
|
|
337
|
+
usdValue: netAUM.toNumber() * usdcPrice.price,
|
|
338
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
339
|
+
remarks: AUMTypes.FINALISED,
|
|
340
|
+
protocol: Protocols.NONE,
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// TODO: we can completely remove it since this strategy doesnt include rewards
|
|
344
|
+
// but for now keeping it for the structure -> eitherways its 0
|
|
345
|
+
const estimatedAUMDelta: PositionInfo = {
|
|
346
|
+
tokenInfo: underlying,
|
|
347
|
+
amount: Web3Number.fromWei("0", underlying.decimals),
|
|
348
|
+
usdValue: 0,
|
|
349
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
350
|
+
remarks: AUMTypes.DEFISPRING,
|
|
351
|
+
protocol: Protocols.NONE,
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
net: {
|
|
356
|
+
tokenInfo: underlying,
|
|
357
|
+
amount: netAUM,
|
|
358
|
+
usdValue: netAUM.toNumber() * usdcPrice.price,
|
|
359
|
+
},
|
|
360
|
+
prevAum,
|
|
361
|
+
splits: [realAUM, estimatedAUMDelta],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// TODO: can refactor later but seems redundant since not being used ANYWHERE
|
|
366
|
+
// Most of the fund management done through adapters only
|
|
367
|
+
|
|
368
|
+
async getFundManagementCall(params: {
|
|
369
|
+
isDeposit: boolean;
|
|
370
|
+
leg1DepositAmount: Web3Number;
|
|
371
|
+
}) {
|
|
372
|
+
logger.verbose(
|
|
373
|
+
`${this.getTag()}::getFundManagementCall params: ${JSON.stringify(params)}`,
|
|
374
|
+
);
|
|
375
|
+
const allAdapters = this.metadata.additionalInfo.adapters.map(
|
|
376
|
+
(a) => a.adapter,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
if (!params.isDeposit) {
|
|
380
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
381
|
+
logger.verbose(
|
|
382
|
+
`${this.getTag()}::getFundManagementCall unusedBalance: ${unusedBalance.amount}, required: ${params.leg1DepositAmount}`,
|
|
383
|
+
);
|
|
384
|
+
if (unusedBalance.amount.gte(params.leg1DepositAmount)) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const adapters = await AdapterOptimizer.getAdapterToUse(
|
|
389
|
+
allAdapters,
|
|
390
|
+
false,
|
|
391
|
+
params.leg1DepositAmount,
|
|
392
|
+
);
|
|
393
|
+
if (adapters.length > 0) {
|
|
394
|
+
const proofsInfo = adapters.map((adapter) =>
|
|
395
|
+
adapter.getProofs(false, this.getMerkleTree()),
|
|
396
|
+
);
|
|
397
|
+
const calls: Call[] = [];
|
|
398
|
+
for (const info of proofsInfo) {
|
|
399
|
+
const manageCalls = await info.callConstructor({
|
|
400
|
+
amount: params.leg1DepositAmount,
|
|
401
|
+
});
|
|
402
|
+
const call = this.getManageCall(
|
|
403
|
+
this.getProofGroupsForManageCalls(manageCalls),
|
|
404
|
+
manageCalls,
|
|
405
|
+
);
|
|
406
|
+
calls.push(call);
|
|
407
|
+
}
|
|
408
|
+
return calls;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
throw new Error(
|
|
412
|
+
`${this.getTag()}::getFundManagementCall: no adapters for withdraw: ${unusedBalance.amount}`,
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const adapters = await AdapterOptimizer.getAdapterToUse(
|
|
417
|
+
allAdapters,
|
|
418
|
+
true,
|
|
419
|
+
params.leg1DepositAmount,
|
|
420
|
+
);
|
|
421
|
+
if (adapters.length > 0) {
|
|
422
|
+
const proofsInfo = adapters.map((adapter) =>
|
|
423
|
+
adapter.getProofs(true, this.getMerkleTree()),
|
|
424
|
+
);
|
|
425
|
+
const calls: Call[] = [];
|
|
426
|
+
for (const info of proofsInfo) {
|
|
427
|
+
const manageCalls = await info.callConstructor({
|
|
428
|
+
amount: params.leg1DepositAmount,
|
|
429
|
+
});
|
|
430
|
+
const call = this.getManageCall(
|
|
431
|
+
this.getProofGroupsForManageCalls(manageCalls),
|
|
432
|
+
manageCalls,
|
|
433
|
+
);
|
|
434
|
+
calls.push(call);
|
|
435
|
+
}
|
|
436
|
+
return calls;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
throw new Error(
|
|
440
|
+
`${this.getTag()}::getFundManagementCall: no adapters for deposit: ${params.leg1DepositAmount}`,
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function getUSDCBoostedSettings(vaultSettings: USDCBoostedStrategySettings) {
|
|
446
|
+
vaultSettings.leafAdapters = [];
|
|
447
|
+
|
|
448
|
+
const xSTRKToken = Global.getDefaultTokens().find(
|
|
449
|
+
(t) => t.symbol === "xSTRK",
|
|
450
|
+
)!;
|
|
451
|
+
|
|
452
|
+
// TODO: we are hardcoding for now since one strategy but we can change later on if req.
|
|
453
|
+
const USDCToken = Global.getDefaultTokens().find((t) => t.symbol === "USDC")!;
|
|
454
|
+
const STRKToken = Global.getDefaultTokens().find((t) => t.symbol === "STRK")!;
|
|
455
|
+
|
|
456
|
+
const baseAdapterConfig: BaseAdapterConfig = {
|
|
457
|
+
baseToken: USDCToken,
|
|
458
|
+
supportedPositions: [{ asset: USDCToken, isDebt: false }],
|
|
459
|
+
networkConfig: getMainnetConfig(),
|
|
460
|
+
pricer: new PricerFromApi(getMainnetConfig(), Global.getDefaultTokens()),
|
|
461
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
462
|
+
vaultAddress: vaultSettings.vaultAddress,
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// ── 1. VesuModifyPositionAdapter (USDC collateral / STRK debt) ──
|
|
466
|
+
|
|
467
|
+
const vesuModifyPositionAdapter = new VesuModifyPositionAdapter({
|
|
468
|
+
poolId: vaultSettings.vesuPoolId,
|
|
469
|
+
collateral: vaultSettings.collateralToken,
|
|
470
|
+
debt: vaultSettings.debtToken,
|
|
471
|
+
targetLtv: vaultSettings.targetLTV,
|
|
472
|
+
maxLtv: vaultSettings.maxLTV,
|
|
473
|
+
...baseAdapterConfig,
|
|
474
|
+
supportedPositions: [
|
|
475
|
+
{ asset: USDCToken, isDebt: false },
|
|
476
|
+
{ asset: STRKToken, isDebt: true },
|
|
477
|
+
],
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// ── 2. AvnuAdapter (STRK ↔ xSTRK swaps) ──
|
|
481
|
+
const avnuAdapter = new AvnuAdapter({
|
|
482
|
+
baseUrl: AVNU_QUOTE_URL,
|
|
483
|
+
avnuContract: AVNU_EXCHANGE,
|
|
484
|
+
slippage: 0.01,
|
|
485
|
+
minimumExtendedPriceDifferenceForSwapOpen: 0,
|
|
486
|
+
maximumExtendedPriceDifferenceForSwapClosing: 0,
|
|
487
|
+
...baseAdapterConfig,
|
|
488
|
+
baseToken: STRKToken,
|
|
489
|
+
supportedPositions: [
|
|
490
|
+
{ asset: STRKToken, isDebt: false },
|
|
491
|
+
{ asset: xSTRKToken, isDebt: false },
|
|
492
|
+
],
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// ── 3. SvkTrovesAdapter (deposit xSTRK into / withdraw from Hyper-xSTRK) ──
|
|
496
|
+
const svkTrovesAdapter = new SvkTrovesAdapter({
|
|
497
|
+
...baseAdapterConfig,
|
|
498
|
+
baseToken: xSTRKToken,
|
|
499
|
+
supportedPositions: [{ asset: xSTRKToken, isDebt: false }],
|
|
500
|
+
strategyVault: vaultSettings.hyperxSTRKVaultAddress,
|
|
501
|
+
trovesStrategyId: "hyper_xstrk",
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Used to track the funds in toAddress ( Vault )
|
|
505
|
+
// ── 4. TokenTransferAdapter ──
|
|
506
|
+
const usdcTransferAdapter = new TokenTransferAdapter({
|
|
507
|
+
...baseAdapterConfig,
|
|
508
|
+
fromAddress: vaultSettings.vaultAllocator,
|
|
509
|
+
toAddress: vaultSettings.vaultAddress,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// ── 5. CommonAdapter (approve + bring liquidity) ──
|
|
513
|
+
const commonAdapter = new CommonAdapter({
|
|
514
|
+
id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN,
|
|
515
|
+
vaultAddress: vaultSettings.vaultAddress,
|
|
516
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
517
|
+
manager: vaultSettings.manager,
|
|
518
|
+
asset: USDCToken.address,
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// ── Register adapters for position tracking ──
|
|
522
|
+
vaultSettings.adapters.push(
|
|
523
|
+
// TODO: generalize the ids
|
|
524
|
+
{ id: "vesu_usdc_strk", adapter: vesuModifyPositionAdapter },
|
|
525
|
+
// Used to track swapped funds in vaultAllocator
|
|
526
|
+
{ id: "avnu_strk_xstrk", adapter: avnuAdapter },
|
|
527
|
+
{ id: "hyper_xstrk", adapter: svkTrovesAdapter },
|
|
528
|
+
{ id: "usdc_transfer", adapter: usdcTransferAdapter },
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
// ── Register leaf adapters for merkle tree ──
|
|
532
|
+
// Vesu modify position
|
|
533
|
+
vaultSettings.leafAdapters.push(() =>
|
|
534
|
+
vesuModifyPositionAdapter.getDepositLeaf(),
|
|
535
|
+
);
|
|
536
|
+
vaultSettings.leafAdapters.push(() =>
|
|
537
|
+
vesuModifyPositionAdapter.getWithdrawLeaf(),
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
// Avnu swaps (STRK ↔ xSTRK)
|
|
541
|
+
vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
|
|
542
|
+
vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
|
|
543
|
+
|
|
544
|
+
// Hyper-xSTRK deposit / withdraw
|
|
545
|
+
vaultSettings.leafAdapters.push(() => svkTrovesAdapter.getDepositLeaf());
|
|
546
|
+
vaultSettings.leafAdapters.push(() => svkTrovesAdapter.getWithdrawLeaf());
|
|
547
|
+
|
|
548
|
+
// Bring liquidity back to vault
|
|
549
|
+
vaultSettings.leafAdapters.push(
|
|
550
|
+
commonAdapter
|
|
551
|
+
.getApproveAdapter(
|
|
552
|
+
USDCToken.address,
|
|
553
|
+
vaultSettings.vaultAddress,
|
|
554
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY,
|
|
555
|
+
)
|
|
556
|
+
.bind(commonAdapter),
|
|
557
|
+
);
|
|
558
|
+
vaultSettings.leafAdapters.push(
|
|
559
|
+
commonAdapter
|
|
560
|
+
.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
|
|
561
|
+
.bind(commonAdapter),
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
return vaultSettings;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const usdcBoostedSettings: USDCBoostedStrategySettings = {
|
|
568
|
+
vaultAddress: ContractAddr.from(
|
|
569
|
+
"0xcdb0e3b2e076a2cdc4ee958b726b47c066239ef91c5ac80c94cf814147b84",
|
|
570
|
+
),
|
|
571
|
+
manager: ContractAddr.from(
|
|
572
|
+
"0x72eea9bac9fa8cfffda637d3b990851446860c6fd8987d6cb50e659b01ee50f",
|
|
573
|
+
),
|
|
574
|
+
vaultAllocator: ContractAddr.from(
|
|
575
|
+
"0x6d3101cff7f821412a99ebe23bb31a1950f93276285102eb4313e3601f5f927",
|
|
576
|
+
),
|
|
577
|
+
redeemRequestNFT: ContractAddr.from(
|
|
578
|
+
"0x47dcc6889ca8db4e9eea8f55421e10f8ce7e356ccb45260a1c49a76f733c309",
|
|
579
|
+
),
|
|
580
|
+
// TODO: not applicable in our case -> remove later ( make it optional if needed)
|
|
581
|
+
aumOracle: ContractAddr.from("0x0"),
|
|
582
|
+
leafAdapters: [],
|
|
583
|
+
adapters: [],
|
|
584
|
+
// Calc using the maxLTV / targetLTV (0.5)
|
|
585
|
+
targetHealthFactor: 1.32,
|
|
586
|
+
// Calc using the maxLTV / maxAcceptableLTV (0.55)
|
|
587
|
+
minHealthFactor: 1.2,
|
|
588
|
+
vesuPoolId: VesuPools.Prime,
|
|
589
|
+
collateralToken: Global.getDefaultTokens().find((t) => t.symbol === "USDC")!,
|
|
590
|
+
debtToken: Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
|
|
591
|
+
maxLTV: 0.66,
|
|
592
|
+
targetLTV: 0.5,
|
|
593
|
+
hyperxSTRKVaultAddress: ContractAddr.from(
|
|
594
|
+
"0x46c7a54c82b1fe374353859f554a40b8bd31d3e30f742901579e7b57b1b5960",
|
|
595
|
+
),
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
function getStrategySettings(
|
|
599
|
+
settings: USDCBoostedStrategySettings,
|
|
600
|
+
): IStrategyMetadata<USDCBoostedStrategySettings> {
|
|
601
|
+
// TODO: we are hardcoding for now since one strategy but we can change later on
|
|
602
|
+
const USDCToken = Global.getDefaultTokens().find((t) => t.symbol === "USDC")!;
|
|
603
|
+
const STRKToken = Global.getDefaultTokens().find((t) => t.symbol === "STRK")!;
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
id: "usdc_boosted",
|
|
607
|
+
name: "USDC Boosted",
|
|
608
|
+
description:
|
|
609
|
+
"Deposits USDC as collateral on Vesu, borrows STRK, swaps to xSTRK, and deposits into Hyper-xSTRK for boosted yield",
|
|
610
|
+
address: settings.vaultAddress,
|
|
611
|
+
launchBlock: 8742931,
|
|
612
|
+
type: "ERC4626" as const,
|
|
613
|
+
vaultType: {
|
|
614
|
+
// TODO: can change as per need
|
|
615
|
+
type: VaultType.META_VAULT,
|
|
616
|
+
description:
|
|
617
|
+
"Deposits USDC as collateral on Vesu, borrows STRK, swaps to xSTRK, and deposits into Hyper-xSTRK for boosted yield",
|
|
618
|
+
},
|
|
619
|
+
depositTokens: [USDCToken],
|
|
620
|
+
additionalInfo: getUSDCBoostedSettings(settings),
|
|
621
|
+
// TODO: config lateron
|
|
622
|
+
risk: {
|
|
623
|
+
riskFactor: [],
|
|
624
|
+
netRisk: 0,
|
|
625
|
+
notARisks: [],
|
|
626
|
+
},
|
|
627
|
+
protocols: [Protocols.VESU, Protocols.TROVES],
|
|
628
|
+
curator: {
|
|
629
|
+
name: "Unwrap Labs",
|
|
630
|
+
logo: "https://assets.troves.fi/integrations/unwraplabs/white.png",
|
|
631
|
+
},
|
|
632
|
+
settings: {
|
|
633
|
+
maxTVL: Web3Number.fromWei(0, USDCToken.decimals),
|
|
634
|
+
isPaused: false,
|
|
635
|
+
isAudited: false,
|
|
636
|
+
isInstantWithdrawal: false,
|
|
637
|
+
hideHarvestInfo: true,
|
|
638
|
+
quoteToken: USDCToken,
|
|
639
|
+
alerts: [
|
|
640
|
+
{
|
|
641
|
+
tab: "withdraw" as const,
|
|
642
|
+
text: "On withdrawal, you will receive an NFT representing your withdrawal request. The funds will be automatically sent to your wallet (NFT owner) in 1-2 hours. You can monitor the status in transactions tab.",
|
|
643
|
+
type: "info" as const,
|
|
644
|
+
},
|
|
645
|
+
],
|
|
646
|
+
},
|
|
647
|
+
contractDetails: getContractDetails(settings),
|
|
648
|
+
// TODO: config later
|
|
649
|
+
faqs: [],
|
|
650
|
+
investmentSteps: [
|
|
651
|
+
"Deposit USDC into the vault",
|
|
652
|
+
"USDC is supplied as collateral on Vesu, STRK is borrowed",
|
|
653
|
+
"Borrowed STRK is swapped to xSTRK via Avnu",
|
|
654
|
+
"xSTRK is deposited into the Hyper-xSTRK vault for additional yield",
|
|
655
|
+
"On withdrawal, the pipeline reverses to return USDC",
|
|
656
|
+
],
|
|
657
|
+
// TODO: config later
|
|
658
|
+
tags: [StrategyTag.META_VAULT],
|
|
659
|
+
security: {
|
|
660
|
+
auditStatus: AuditStatus.AUDITED,
|
|
661
|
+
sourceCode: {
|
|
662
|
+
type: SourceCodeType.CLOSED_SOURCE,
|
|
663
|
+
contractLink: "https://github.com/trovesfi/troves-contracts",
|
|
664
|
+
},
|
|
665
|
+
accessControl: {
|
|
666
|
+
type: AccessControlType.STANDARD_ACCOUNT,
|
|
667
|
+
addresses: [ContractAddr.from("0x0")],
|
|
668
|
+
timeLock: "2 Days",
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
redemptionInfo: {
|
|
672
|
+
instantWithdrawalVault: InstantWithdrawalVault.NO,
|
|
673
|
+
redemptionsInfo: [
|
|
674
|
+
{
|
|
675
|
+
title: "Typical Duration",
|
|
676
|
+
description: "1-2 hours",
|
|
677
|
+
},
|
|
678
|
+
],
|
|
679
|
+
alerts: [
|
|
680
|
+
{
|
|
681
|
+
type: "info",
|
|
682
|
+
text: "Redemption times are estimates and may vary based on network conditions and liquidity requirements.",
|
|
683
|
+
tab: "withdraw",
|
|
684
|
+
},
|
|
685
|
+
],
|
|
686
|
+
},
|
|
687
|
+
usualTimeToEarnings: null,
|
|
688
|
+
usualTimeToEarningsDescription: null,
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export const USDCBoostedStrategies: IStrategyMetadata<USDCBoostedStrategySettings>[] =
|
|
693
|
+
[getStrategySettings(usdcBoostedSettings)];
|
|
@@ -1162,6 +1162,7 @@ export class ExecutionService {
|
|
|
1162
1162
|
);
|
|
1163
1163
|
const netExecutionPrice = this._getNetExecutionPrice(isIncrease);
|
|
1164
1164
|
|
|
1165
|
+
// todo doubt. I dont think its a good idea to extended short below ideal price, nor above in long case.
|
|
1165
1166
|
let executionPrice: number;
|
|
1166
1167
|
|
|
1167
1168
|
if (hasVesuSwapRoute) {
|