@strkfarm/sdk 1.0.55 → 1.0.56

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.
@@ -0,0 +1,543 @@
1
+ import { ContractAddr, Web3Number } from "@/dataTypes";
2
+ import { BaseStrategy, SingleActionAmount, SingleTokenInfo } from "./base-strategy";
3
+ import { PricerBase } from "@/modules/pricerBase";
4
+ import { getNoRiskTags, IConfig, IStrategyMetadata, Protocols, RiskFactor, RiskType, VaultPosition } from "@/interfaces";
5
+ import { Call, CallData, Contract, num, uint256 } from "starknet";
6
+ import { VesuRebalanceSettings } from "./vesu-rebalance";
7
+ import { assert, LeafData, logger, StandardMerkleTree } from "@/utils";
8
+ import UniversalVaultAbi from '../data/universal-vault.abi.json';
9
+ import ManagerAbi from '../data/vault-manager.abi.json';
10
+ import { ApproveCallParams, BaseAdapter, CommonAdapter, FlashloanCallParams, GenerateCallFn, LeafAdapterFn, ManageCall, VesuAdapter, VesuModifyPositionCallParams, VesuPools } from "./universal-adapters";
11
+ import { Global } from "@/global";
12
+
13
+ export interface UniversalStrategySettings {
14
+ manager: ContractAddr,
15
+ vaultAllocator: ContractAddr,
16
+ redeemRequestNFT: ContractAddr,
17
+ leafAdapters: LeafAdapterFn<any>[],
18
+ adapters: {id: string, adapter: BaseAdapter}[]
19
+ }
20
+
21
+
22
+ export class UniversalStrategy<
23
+ S extends UniversalStrategySettings
24
+ > extends BaseStrategy<
25
+ SingleTokenInfo,
26
+ SingleActionAmount
27
+ > {
28
+
29
+ /** Contract address of the strategy */
30
+ readonly address: ContractAddr;
31
+ /** Pricer instance for token price calculations */
32
+ readonly pricer: PricerBase;
33
+ /** Metadata containing strategy information */
34
+ readonly metadata: IStrategyMetadata<S>;
35
+ /** Contract instance for interacting with the strategy */
36
+ readonly contract: Contract;
37
+ readonly managerContract: Contract;
38
+ merkleTree: StandardMerkleTree | undefined;
39
+
40
+ constructor(
41
+ config: IConfig,
42
+ pricer: PricerBase,
43
+ metadata: IStrategyMetadata<S>
44
+ ) {
45
+ super(config);
46
+ this.pricer = pricer;
47
+
48
+ assert(
49
+ metadata.depositTokens.length === 1,
50
+ "VesuRebalance only supports 1 deposit token"
51
+ );
52
+ this.metadata = metadata;
53
+ this.address = metadata.address;
54
+
55
+ this.contract = new Contract(
56
+ UniversalVaultAbi,
57
+ this.address.address,
58
+ this.config.provider
59
+ );
60
+ this.managerContract = new Contract(
61
+ ManagerAbi,
62
+ this.metadata.additionalInfo.manager.address,
63
+ this.config.provider
64
+ );
65
+ }
66
+
67
+ getMerkleTree() {
68
+ if (this.merkleTree) return this.merkleTree;
69
+ const leaves = this.metadata.additionalInfo.leafAdapters.map((adapter, index) => {
70
+ return adapter()
71
+ });
72
+ const standardTree = StandardMerkleTree.of(leaves.map(l => l.leaf));
73
+ this.merkleTree = standardTree;
74
+ return standardTree;
75
+ }
76
+
77
+ getMerkleRoot() {
78
+ return this.getMerkleTree().root;
79
+ }
80
+
81
+ getProofs<T>(id: string): { proofs: string[], callConstructor: GenerateCallFn<T> } {
82
+ const tree = this.getMerkleTree();
83
+ let proofs: string[] = [];
84
+ for (const [i, v] of tree.entries()) {
85
+ if (v.readableId == id) {
86
+ proofs = tree.getProof(i);
87
+ }
88
+ }
89
+ if (proofs.length === 0) {
90
+ throw new Error(`Proof not found for ID: ${id}`);
91
+ }
92
+
93
+ // find leaf adapter
94
+ const leafAdapter = this.metadata.additionalInfo.leafAdapters.find(adapter => adapter().leaf.readableId === id);
95
+ if (!leafAdapter) {
96
+ throw new Error(`Leaf adapter not found for ID: ${id}`);
97
+ }
98
+ const leafInfo = leafAdapter();
99
+ return {
100
+ proofs,
101
+ callConstructor: leafInfo.callConstructor.bind(leafInfo),
102
+ };
103
+ }
104
+
105
+ getAdapter(id: string): BaseAdapter {
106
+ const adapter = this.metadata.additionalInfo.adapters.find(adapter => adapter.id === id);
107
+ if (!adapter) {
108
+ throw new Error(`Adapter not found for ID: ${id}`);
109
+ }
110
+ return adapter.adapter;
111
+ }
112
+
113
+ asset() {
114
+ return this.metadata.depositTokens[0];
115
+ }
116
+
117
+ async depositCall(amountInfo: SingleActionAmount, receiver: ContractAddr): Promise<Call[]> {
118
+ // Technically its not erc4626 abi, but we just need approve call
119
+ // so, its ok to use it
120
+ assert(
121
+ amountInfo.tokenInfo.address.eq(this.asset().address),
122
+ "Deposit token mismatch"
123
+ );
124
+ const assetContract = new Contract(
125
+ UniversalVaultAbi,
126
+ this.asset().address.address,
127
+ this.config.provider
128
+ );
129
+ const call1 = assetContract.populate("approve", [
130
+ this.address.address,
131
+ uint256.bnToUint256(amountInfo.amount.toWei())
132
+ ]);
133
+ const call2 = this.contract.populate("deposit", [
134
+ uint256.bnToUint256(amountInfo.amount.toWei()),
135
+ receiver.address
136
+ ]);
137
+ return [call1, call2];
138
+ }
139
+
140
+ /**
141
+ * Calculates the Total Value Locked (TVL) for a specific user.
142
+ * @param user - Address of the user
143
+ * @returns Object containing the amount in token units and USD value
144
+ */
145
+ async getUserTVL(user: ContractAddr) {
146
+ const shares = await this.contract.balanceOf(user.address);
147
+ const assets = await this.contract.convert_to_assets(
148
+ uint256.bnToUint256(shares)
149
+ );
150
+ const amount = Web3Number.fromWei(
151
+ assets.toString(),
152
+ this.metadata.depositTokens[0].decimals
153
+ );
154
+ let price = await this.pricer.getPrice(
155
+ this.metadata.depositTokens[0].symbol
156
+ );
157
+ const usdValue = Number(amount.toFixed(6)) * price.price;
158
+ return {
159
+ tokenInfo: this.asset(),
160
+ amount,
161
+ usdValue
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Calculates the weighted average APY across all pools based on USD value.
167
+ * @returns {Promise<number>} The weighted average APY across all pools
168
+ */
169
+ async netAPY(): Promise<number> {
170
+ const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
171
+ const pools = await VesuAdapter.getVesuPools();
172
+ const pool1 = pools.pools.find(p => vesuAdapter1.config.poolId.eqString(num.getHexString(p.id)));
173
+ const pool2 = pools.pools.find(p => vesuAdapter2.config.poolId.eqString(num.getHexString(p.id)));
174
+ if (!pool1 || !pool2) {
175
+ throw new Error('Pool not found');
176
+ };
177
+ const collateralAsset1 = pool1.assets.find((a: any) => a.symbol === vesuAdapter1.config.collateral.symbol)?.stats!;
178
+ const debtAsset1 = pool1.assets.find((a: any) => a.symbol === vesuAdapter1.config.debt.symbol)?.stats!;
179
+ const collateralAsset2 = pool2.assets.find((a: any) => a.symbol === vesuAdapter2.config.collateral.symbol)?.stats!;
180
+ const debtAsset2 = pool2.assets.find((a: any) => a.symbol === vesuAdapter2.config.debt.symbol)?.stats!;
181
+
182
+ // supplyApy: { value: '8057256029163289', decimals: 18 },
183
+ // defiSpringSupplyApr: { value: '46856062629264560', decimals: 18 },
184
+ // borrowApr: { value: '12167825982336000', decimals: 18 },
185
+ const collateral1APY = Number(collateralAsset1.supplyApy.value) / 1e18 + Number(collateralAsset1.defiSpringSupplyApr.value) / 1e18;
186
+ const debt1APY = Number(debtAsset1.borrowApr.value) / 1e18;
187
+ const collateral2APY = Number(collateralAsset2.supplyApy.value) / 1e18 + Number(collateralAsset2.defiSpringSupplyApr.value) / 1e18;
188
+ const debt2APY = Number(debtAsset2.borrowApr.value) / 1e18;
189
+
190
+ const apys = [collateral1APY, debt1APY, collateral2APY, debt2APY];
191
+ const positions = await this.getVaultPositions();
192
+ assert(positions.length == apys.length, "Positions and APYs length mismatch");
193
+ const weightedAPYs = positions.map((pos, i) => {
194
+ return pos.usdValue * apys[i] * (i % 2 == 0 ? 1 : -1);
195
+ });
196
+ const netPosition = positions.reduce((acc, val, index) => acc + val.usdValue * (index % 2 == 0 ? 1 : -1), 0);
197
+ const apy = weightedAPYs.reduce((acc, val) => acc + val, 0) / netPosition;
198
+ return apy;
199
+ }
200
+
201
+ /**
202
+ * Calculates the total TVL of the strategy.
203
+ * @returns Object containing the total amount in token units and USD value
204
+ */
205
+ async getTVL() {
206
+ const assets = await this.contract.total_assets();
207
+ const amount = Web3Number.fromWei(
208
+ assets.toString(),
209
+ this.metadata.depositTokens[0].decimals
210
+ );
211
+ let price = await this.pricer.getPrice(
212
+ this.metadata.depositTokens[0].symbol
213
+ );
214
+ const usdValue = Number(amount.toFixed(6)) * price.price;
215
+ return {
216
+ tokenInfo: this.asset(),
217
+ amount,
218
+ usdValue
219
+ };
220
+ }
221
+
222
+ async getAUM(): Promise<SingleTokenInfo> {
223
+ const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
224
+ const leg1AUM = await vesuAdapter1.getPositions(this.config);
225
+ const leg2AUM = await vesuAdapter2.getPositions(this.config);
226
+
227
+ const token1Price = await this.pricer.getPrice(vesuAdapter1.config.collateral.symbol);
228
+
229
+ const aumToken = leg1AUM[0].amount
230
+ .plus(leg2AUM[0].usdValue / token1Price.price)
231
+ .minus(leg1AUM[1].usdValue / token1Price.price)
232
+ .minus(leg2AUM[1].amount);
233
+
234
+ return {
235
+ tokenInfo: this.asset(),
236
+ amount: aumToken,
237
+ usdValue: aumToken.multipliedBy(token1Price.price).toNumber()
238
+ }
239
+ }
240
+
241
+ getVesuAdapters() {
242
+ const vesuAdapter1 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG1) as VesuAdapter;
243
+ const vesuAdapter2 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG2) as VesuAdapter;
244
+ vesuAdapter1.pricer = this.pricer;
245
+ vesuAdapter2.pricer = this.pricer;
246
+
247
+ return [vesuAdapter1, vesuAdapter2];
248
+ }
249
+
250
+ async getVaultPositions(): Promise<VaultPosition[]> {
251
+ const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
252
+ const leg1Positions = await vesuAdapter1.getPositions(this.config);
253
+ const leg2Positions = await vesuAdapter2.getPositions(this.config);
254
+ return [...leg1Positions, ...leg2Positions];
255
+ }
256
+
257
+ getSetManagerCall(strategist: ContractAddr, root = this.getMerkleRoot()) {
258
+ return this.managerContract.populate('set_manage_root', [strategist.address, num.getHexString(root)]);
259
+ }
260
+
261
+ getManageCall(proofIds: string[], manageCalls: ManageCall[]) {
262
+ assert(proofIds.length == manageCalls.length, 'Proof IDs and Manage Calls length mismatch');
263
+ return this.managerContract.populate('manage_vault_with_merkle_verification', {
264
+ proofs: proofIds.map(id => this.getProofs(id).proofs),
265
+ decoder_and_sanitizers: manageCalls.map(call => call.sanitizer.address),
266
+ targets: manageCalls.map(call => call.call.contractAddress.address),
267
+ selectors: manageCalls.map(call => call.call.selector),
268
+ calldatas: manageCalls.map(call => call.call.calldata), // Calldata[]
269
+ });
270
+ }
271
+
272
+ getVesuModifyPositionCalls(params: {
273
+ isLeg1: boolean,
274
+ isDeposit: boolean,
275
+ depositAmount: Web3Number,
276
+ debtAmount: Web3Number
277
+ }) {
278
+ assert(params.depositAmount.gt(0) || params.debtAmount.gt(0), 'Either deposit or debt amount must be greater than 0');
279
+ // approve token
280
+ const isToken1 = params.isLeg1 == params.isDeposit; // XOR
281
+ const STEP1_ID = isToken1 ? UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1 :UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN2;
282
+ const manage4Info = this.getProofs<ApproveCallParams>(STEP1_ID);
283
+ const approveAmount = params.isDeposit ? params.depositAmount : params.debtAmount;
284
+ const manageCall4 = manage4Info.callConstructor({
285
+ amount: approveAmount
286
+ })
287
+
288
+ // deposit and borrow or repay and withdraw
289
+ const STEP2_ID = params.isLeg1 ? UNIVERSAL_MANAGE_IDS.VESU_LEG1 : UNIVERSAL_MANAGE_IDS.VESU_LEG2;
290
+ const manage5Info = this.getProofs<VesuModifyPositionCallParams>(STEP2_ID);
291
+ const manageCall5 = manage5Info.callConstructor(VesuAdapter.getDefaultModifyPositionCallParams({
292
+ collateralAmount: params.depositAmount,
293
+ isAddCollateral: params.isDeposit,
294
+ debtAmount: params.debtAmount,
295
+ isBorrow: params.isDeposit
296
+ }))
297
+
298
+ const output = [{
299
+ proofs: manage5Info.proofs,
300
+ manageCall: manageCall5
301
+ }];
302
+ if (approveAmount.gt(0)) {
303
+ output.unshift({
304
+ proofs: manage4Info.proofs,
305
+ manageCall: manageCall4
306
+ })
307
+ }
308
+ return output;
309
+ }
310
+
311
+ getTag() {
312
+ return `${UniversalStrategy.name}:${this.metadata.name}`;
313
+ }
314
+
315
+ async getVesuMultiplyCall(params: {
316
+ isDeposit: boolean,
317
+ leg1DepositAmount: Web3Number
318
+ }) {
319
+ const vesuAdapter1 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG1) as VesuAdapter;
320
+ const vesuAdapter2 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG2) as VesuAdapter;
321
+ const leg1LTV = await vesuAdapter1.getLTVConfig(this.config);
322
+ const leg2LTV = await vesuAdapter2.getLTVConfig(this.config);
323
+ logger.verbose(`${this.getTag()}: LTVLeg1: ${leg1LTV}`);
324
+ logger.verbose(`${this.getTag()}: LTVLeg2: ${leg2LTV}`);
325
+
326
+ const token1Price = await this.pricer.getPrice(vesuAdapter1.config.collateral.symbol);
327
+ const token2Price = await this.pricer.getPrice(vesuAdapter2.config.collateral.symbol);
328
+ logger.verbose(`${this.getTag()}: Price${vesuAdapter1.config.collateral.symbol}: ${token1Price.price}`);
329
+ logger.verbose(`${this.getTag()}: Price${vesuAdapter2.config.collateral.symbol}: ${token2Price.price}`);
330
+
331
+ const TARGET_HF = 1.3;
332
+
333
+ const k1 = token1Price.price * leg1LTV / token2Price.price / TARGET_HF;
334
+ const k2 = token1Price.price * TARGET_HF / token2Price.price / leg2LTV;
335
+
336
+ const borrow2Amount = new Web3Number(
337
+ params.leg1DepositAmount.multipliedBy(k1.toFixed(6)).dividedBy(k2 - k1).toFixed(6),
338
+ vesuAdapter2.config.debt.decimals
339
+ );
340
+ const borrow1Amount = new Web3Number(
341
+ borrow2Amount.multipliedBy(k2).toFixed(6),
342
+ vesuAdapter1.config.debt.decimals
343
+ );
344
+ logger.verbose(`${this.getTag()}:: leg1DepositAmount: ${params.leg1DepositAmount.toString()} ${vesuAdapter1.config.collateral.symbol}`);
345
+ logger.verbose(`${this.getTag()}:: borrow1Amount: ${borrow1Amount.toString()} ${vesuAdapter1.config.debt.symbol}`);
346
+ logger.verbose(`${this.getTag()}:: borrow2Amount: ${borrow2Amount.toString()} ${vesuAdapter2.config.debt.symbol}`);
347
+
348
+ const callSet1 = this.getVesuModifyPositionCalls({
349
+ isLeg1: true,
350
+ isDeposit: params.isDeposit,
351
+ depositAmount: params.leg1DepositAmount.plus(borrow2Amount),
352
+ debtAmount: borrow1Amount
353
+ });
354
+
355
+ const callSet2 = this.getVesuModifyPositionCalls({
356
+ isLeg1: false,
357
+ isDeposit: params.isDeposit,
358
+ depositAmount: borrow1Amount,
359
+ debtAmount: borrow2Amount
360
+ });
361
+
362
+ const allActions = [...callSet1.map(i => i.manageCall), ...callSet2.map(i => i.manageCall)];
363
+ const flashloanCalldata = CallData.compile([
364
+ [...callSet1.map(i => i.proofs), ...callSet2.map(i => i.proofs)],
365
+ allActions.map(i => i.sanitizer.address),
366
+ allActions.map(i => i.call.contractAddress.address),
367
+ allActions.map(i => i.call.selector),
368
+ allActions.map(i => i.call.calldata)
369
+ ])
370
+
371
+ // flash loan
372
+ const STEP1_ID = UNIVERSAL_MANAGE_IDS.FLASH_LOAN;
373
+ const manage1Info = this.getProofs<FlashloanCallParams>(STEP1_ID);
374
+ const manageCall1 = manage1Info.callConstructor({
375
+ amount: borrow2Amount,
376
+ data: flashloanCalldata.map(i => BigInt(i))
377
+ })
378
+ const manageCall = this.getManageCall([UNIVERSAL_MANAGE_IDS.FLASH_LOAN], [manageCall1]);
379
+ return manageCall;
380
+ }
381
+
382
+ async getRebalanceCall(params: {
383
+ isLeg1toLeg2: boolean,
384
+ amount: Web3Number
385
+ }) {
386
+ let callSet1 = this.getVesuModifyPositionCalls({
387
+ isLeg1: true,
388
+ isDeposit: params.isLeg1toLeg2,
389
+ depositAmount: Web3Number.fromWei(0, 0),
390
+ debtAmount: params.amount
391
+ });
392
+
393
+ let callSet2 = this.getVesuModifyPositionCalls({
394
+ isLeg1: false,
395
+ isDeposit: params.isLeg1toLeg2,
396
+ depositAmount: params.amount,
397
+ debtAmount: Web3Number.fromWei(0, 0)
398
+ });
399
+
400
+ if (params.isLeg1toLeg2) {
401
+ const manageCall = this.getManageCall([
402
+ UNIVERSAL_MANAGE_IDS.VESU_LEG1,
403
+ UNIVERSAL_MANAGE_IDS.VESU_LEG2
404
+ ], [...callSet1.map(i => i.manageCall), ...callSet2.map(i => i.manageCall)]);
405
+ return manageCall;
406
+ } else {
407
+ const manageCall = this.getManageCall([
408
+ UNIVERSAL_MANAGE_IDS.VESU_LEG2,
409
+ UNIVERSAL_MANAGE_IDS.VESU_LEG1
410
+ ], [...callSet2.map(i => i.manageCall), ...callSet1.map(i => i.manageCall)]);
411
+ return manageCall;
412
+ }
413
+ }
414
+ }
415
+
416
+
417
+ export enum UNIVERSAL_MANAGE_IDS {
418
+ FLASH_LOAN = 'flash_loan_init',
419
+ VESU_LEG1 = 'vesu_leg1',
420
+ VESU_LEG2 = 'vesu_leg2',
421
+ APPROVE_TOKEN1 = 'approve_token1',
422
+ APPROVE_TOKEN2 = 'approve_token2'
423
+ }
424
+
425
+ export enum UNIVERSAL_ADAPTERS {
426
+ COMMON = 'common_adapter',
427
+ VESU_LEG1 = 'vesu_leg1_adapter',
428
+ VESU_LEG2 = 'vesu_leg2_adapter'
429
+ }
430
+
431
+ function getLooperSettings(
432
+ token1Symbol: string,
433
+ token2Symbol: string,
434
+ vaultSettings: UniversalStrategySettings,
435
+ pool1: ContractAddr,
436
+ pool2: ContractAddr
437
+ ) {
438
+ const USDCToken = Global.getDefaultTokens().find(token => token.symbol === token1Symbol)!;
439
+ const ETHToken = Global.getDefaultTokens().find(token => token.symbol === token2Symbol)!;
440
+
441
+ const commonAdapter = new CommonAdapter({
442
+ manager: vaultSettings.manager,
443
+ asset: USDCToken.address,
444
+ id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN
445
+ })
446
+ const vesuAdapterUSDCETH = new VesuAdapter({
447
+ poolId: pool1,
448
+ collateral: USDCToken,
449
+ debt: ETHToken,
450
+ vaultAllocator: vaultSettings.vaultAllocator,
451
+ id: UNIVERSAL_MANAGE_IDS.VESU_LEG1
452
+ })
453
+ const vesuAdapterETHUSDC = new VesuAdapter({
454
+ poolId: pool2,
455
+ collateral: ETHToken,
456
+ debt: USDCToken,
457
+ vaultAllocator: vaultSettings.vaultAllocator,
458
+ id: UNIVERSAL_MANAGE_IDS.VESU_LEG2
459
+ })
460
+ vaultSettings.adapters.push(...[{
461
+ id: UNIVERSAL_ADAPTERS.COMMON,
462
+ adapter: commonAdapter
463
+ }, {
464
+ id: UNIVERSAL_ADAPTERS.VESU_LEG1,
465
+ adapter: vesuAdapterUSDCETH
466
+ }, {
467
+ id: UNIVERSAL_ADAPTERS.VESU_LEG2,
468
+ adapter: vesuAdapterETHUSDC
469
+ }])
470
+ vaultSettings.leafAdapters.push(commonAdapter.getFlashloanAdapter.bind(commonAdapter));
471
+ vaultSettings.leafAdapters.push(vesuAdapterUSDCETH.getModifyPosition.bind(vesuAdapterUSDCETH));
472
+ vaultSettings.leafAdapters.push(vesuAdapterETHUSDC.getModifyPosition.bind(vesuAdapterETHUSDC));
473
+ vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(USDCToken.address, vesuAdapterUSDCETH.VESU_SINGLETON, UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1).bind(commonAdapter));
474
+ vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(ETHToken.address, vesuAdapterETHUSDC.VESU_SINGLETON, UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN2).bind(commonAdapter));
475
+ return vaultSettings;
476
+ }
477
+
478
+ const _riskFactor: RiskFactor[] = [
479
+ { type: RiskType.SMART_CONTRACT_RISK, value: 0.5, weight: 25, reason: "Audited by Zellic" },
480
+ { type: RiskType.LIQUIDATION_RISK, value: 1, weight: 50, reason: "Liquidation risk is mitigated btable price feed on Starknet" }
481
+ ];
482
+
483
+ const usdcVaultSettings: UniversalStrategySettings = {
484
+ manager: ContractAddr.from('0xf41a2b1f498a7f9629db0b8519259e66e964260a23d20003f3e42bb1997a07'),
485
+ vaultAllocator: ContractAddr.from('0x228cca1005d3f2b55cbaba27cb291dacf1b9a92d1d6b1638195fbd3d0c1e3ba'),
486
+ redeemRequestNFT: ContractAddr.from('0x906d03590010868cbf7590ad47043959d7af8e782089a605d9b22567b64fda'),
487
+ leafAdapters: [],
488
+ adapters: []
489
+ }
490
+
491
+ const wbtcVaultSettings: UniversalStrategySettings = {
492
+ manager: ContractAddr.from('0xef8a664ffcfe46a6af550766d27c28937bf1b77fb4ab54d8553e92bca5ba34'),
493
+ vaultAllocator: ContractAddr.from('0x1e01c25f0d9494570226ad28a7fa856c0640505e809c366a9fab4903320e735'),
494
+ redeemRequestNFT: ContractAddr.from('0x4fec59a12f8424281c1e65a80b5de51b4e754625c60cddfcd00d46941ec37b2'),
495
+ leafAdapters: [],
496
+ adapters: []
497
+ }
498
+
499
+ export const UniversalStrategies: IStrategyMetadata<UniversalStrategySettings>[] =
500
+ [
501
+ {
502
+ name: "USDC Evergreen",
503
+ description: "A universal strategy for managing USDC assets",
504
+ address: ContractAddr.from('0x7e6498cf6a1bfc7e6fc89f1831865e2dacb9756def4ec4b031a9138788a3b5e'),
505
+ launchBlock: 0,
506
+ type: 'ERC4626',
507
+ depositTokens: [Global.getDefaultTokens().find(token => token.symbol === 'USDC')!],
508
+ additionalInfo: getLooperSettings('USDC', 'ETH', usdcVaultSettings, VesuPools.Genesis, VesuPools.Genesis),
509
+ risk: {
510
+ riskFactor: _riskFactor,
511
+ netRisk:
512
+ _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
513
+ _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
514
+ notARisks: getNoRiskTags(_riskFactor)
515
+ },
516
+ protocols: [Protocols.VESU],
517
+ maxTVL: Web3Number.fromWei(0, 6),
518
+ contractDetails: [],
519
+ faqs: [],
520
+ investmentSteps: [],
521
+ },
522
+ {
523
+ name: "WBTC Evergreen",
524
+ description: "A universal strategy for managing WBTC assets",
525
+ address: ContractAddr.from('0x5a4c1651b913aa2ea7afd9024911603152a19058624c3e425405370d62bf80c'),
526
+ launchBlock: 0,
527
+ type: 'ERC4626',
528
+ depositTokens: [Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!],
529
+ additionalInfo: getLooperSettings('WBTC', 'ETH', wbtcVaultSettings, VesuPools.Genesis, VesuPools.Genesis),
530
+ risk: {
531
+ riskFactor: _riskFactor,
532
+ netRisk:
533
+ _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
534
+ _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
535
+ notARisks: getNoRiskTags(_riskFactor)
536
+ },
537
+ protocols: [Protocols.VESU],
538
+ maxTVL: Web3Number.fromWei(0, 8),
539
+ contractDetails: [],
540
+ faqs: [],
541
+ investmentSteps: [],
542
+ },
543
+ ]
@@ -1,4 +1,5 @@
1
1
  export * from '@/utils/logger';
2
+ export * from './oz-merkle';
2
3
 
3
4
  // Utility type to make all optional properties required
4
5
  export type RequiredFields<T> = {
@@ -0,0 +1,100 @@
1
+ import { BytesLike, HexString, toHex } from "@ericnordelo/strk-merkle-tree/dist/bytes";
2
+ import { MultiProof, processMultiProof, processProof } from "@ericnordelo/strk-merkle-tree/dist/core";
3
+ import { standardLeafHash } from "@ericnordelo/strk-merkle-tree/dist/hashes";
4
+ import { MerkleTreeData, MerkleTreeImpl } from "@ericnordelo/strk-merkle-tree/dist/merkletree";
5
+ import { MerkleTreeOptions } from "@ericnordelo/strk-merkle-tree/dist/options";
6
+ import { ValueType } from "@ericnordelo/strk-merkle-tree/dist/serde";
7
+ import { validateArgument } from "@ericnordelo/strk-merkle-tree/src/utils/errors";
8
+ import { num } from "starknet";
9
+ import * as starknet from "@scure/starknet";
10
+
11
+ export interface LeafData {
12
+ id: bigint,
13
+ readableId: string,
14
+ data: bigint[]
15
+ }
16
+
17
+ function hash_leaf(leaf: LeafData) {
18
+ if (leaf.data.length < 1) {
19
+ throw new Error("Invalid leaf data");
20
+ }
21
+ let firstElement = leaf.data[0];
22
+ let value = firstElement;
23
+ for (let i=1; i < leaf.data.length; i++) {
24
+ value = pedersen_hash(value, leaf.data[i]);
25
+ }
26
+ return `0x${num.toHexString(value).replace(/^0x/, '').padStart(64, '0')}`;
27
+ }
28
+
29
+ function pedersen_hash(a: bigint, b: bigint): bigint {
30
+ return BigInt(starknet.pedersen(a, b).toString());
31
+ }
32
+
33
+
34
+ export interface StandardMerkleTreeData<T extends any> extends MerkleTreeData<T> {
35
+ format: 'standard-v1';
36
+ leafEncoding: ValueType[];
37
+ }
38
+
39
+ export class StandardMerkleTree extends MerkleTreeImpl<LeafData> {
40
+ protected constructor(
41
+ protected readonly tree: HexString[],
42
+ protected readonly values: StandardMerkleTreeData<LeafData>['values'],
43
+ protected readonly leafEncoding: ValueType[],
44
+ ) {
45
+ super(tree, values, leaf => {
46
+ return hash_leaf(leaf)
47
+ });
48
+ }
49
+
50
+ static of(
51
+ values: LeafData[],
52
+ leafEncoding: ValueType[] = [],
53
+ options: MerkleTreeOptions = {},
54
+ ): StandardMerkleTree {
55
+ // use default nodeHash (standardNodeHash)
56
+ const [tree, indexedValues] = MerkleTreeImpl.prepare(values, options, leaf => {
57
+ return hash_leaf(leaf)
58
+ });
59
+ return new StandardMerkleTree(tree, indexedValues, leafEncoding);
60
+ }
61
+
62
+ static load(data: StandardMerkleTreeData<LeafData>): StandardMerkleTree {
63
+ validateArgument(data.format === 'standard-v1', `Unknown format '${data.format}'`);
64
+ validateArgument(data.leafEncoding !== undefined, 'Expected leaf encoding');
65
+
66
+ const tree = new StandardMerkleTree(data.tree, data.values, data.leafEncoding);
67
+ tree.validate();
68
+ return tree;
69
+ }
70
+
71
+ static verify<T extends any[]>(root: BytesLike, leafEncoding: ValueType[], leaf: T, proof: BytesLike[]): boolean {
72
+ // use default nodeHash (standardNodeHash) for processProof
73
+ return toHex(root) === processProof(standardLeafHash(leafEncoding, leaf), proof);
74
+ }
75
+
76
+ static verifyMultiProof<T extends any[]>(
77
+ root: BytesLike,
78
+ leafEncoding: ValueType[],
79
+ multiproof: MultiProof<BytesLike, T>,
80
+ ): boolean {
81
+ // use default nodeHash (standardNodeHash) for processMultiProof
82
+ return (
83
+ toHex(root) ===
84
+ processMultiProof({
85
+ leaves: multiproof.leaves.map(leaf => standardLeafHash(leafEncoding, leaf)),
86
+ proof: multiproof.proof,
87
+ proofFlags: multiproof.proofFlags,
88
+ })
89
+ );
90
+ }
91
+
92
+ dump(): StandardMerkleTreeData<LeafData> {
93
+ return {
94
+ format: 'standard-v1',
95
+ leafEncoding: this.leafEncoding,
96
+ tree: this.tree,
97
+ values: this.values,
98
+ };
99
+ }
100
+ }