@strkfarm/sdk 1.1.8 → 1.1.10

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.
@@ -13,13 +13,24 @@ import { AvnuWrapper, ERC20 } from "@/modules";
13
13
  import { AVNU_MIDDLEWARE } from "./universal-adapters/adapter-utils";
14
14
  import { VesuHarvests } from "@/modules/harvests";
15
15
 
16
+ export interface UniversalManageCall {
17
+ proofs: string[];
18
+ manageCall: ManageCall;
19
+ step: UNIVERSAL_MANAGE_IDS;
20
+ }
21
+
16
22
  export interface UniversalStrategySettings {
17
23
  vaultAddress: ContractAddr,
18
24
  manager: ContractAddr,
19
25
  vaultAllocator: ContractAddr,
20
26
  redeemRequestNFT: ContractAddr,
21
27
  aumOracle: ContractAddr,
28
+
29
+ // Individual merkle tree leaves
22
30
  leafAdapters: LeafAdapterFn<any>[],
31
+
32
+ // Useful for returning adapter class objects that can compute
33
+ // certain things for us (e.g. positions, hfs)
23
34
  adapters: {id: string, adapter: BaseAdapter}[],
24
35
  targetHealthFactor: number,
25
36
  minHealthFactor: number
@@ -192,28 +203,46 @@ export class UniversalStrategy<
192
203
  * @returns {Promise<number>} The weighted average APY across all pools
193
204
  */
194
205
  async netAPY(): Promise<{ net: number, splits: { apy: number, id: string }[] }> {
195
- const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
196
- const pools = await VesuAdapter.getVesuPools();
197
- const pool1 = pools.pools.find(p => vesuAdapter1.config.poolId.eqString(num.getHexString(p.id)));
198
- const pool2 = pools.pools.find(p => vesuAdapter2.config.poolId.eqString(num.getHexString(p.id)));
199
- if (!pool1 || !pool2) {
206
+ if (this.metadata.isPreview) {
207
+ return { net: 0, splits: [{
208
+ apy: 0, id: 'base'
209
+ }, {
210
+ apy: 0, id: 'defispring'
211
+ }] };
212
+ }
213
+
214
+ const prevAUM = await this.getPrevAUM();
215
+
216
+ // get Vesu pools, positions and APYs
217
+ const vesuAdapters = this.getVesuAdapters();
218
+ const allVesuPools = await VesuAdapter.getVesuPools();
219
+ const pools = vesuAdapters.map((vesuAdapter) => {
220
+ return allVesuPools.pools.find(p => vesuAdapter.config.poolId.eqString(num.getHexString(p.id)));
221
+ });
222
+ logger.verbose(`${this.metadata.name}::netAPY: vesu-pools: ${JSON.stringify(pools)}`);
223
+ if (pools.some(p => !p)) {
200
224
  throw new Error('Pool not found');
201
225
  };
202
- const collateralAsset1 = pool1.assets.find((a: any) => a.symbol === vesuAdapter1.config.collateral.symbol)?.stats!;
203
- const debtAsset1 = pool1.assets.find((a: any) => a.symbol === vesuAdapter1.config.debt.symbol)?.stats!;
204
- const collateralAsset2 = pool2.assets.find((a: any) => a.symbol === vesuAdapter2.config.collateral.symbol)?.stats!;
205
- const debtAsset2 = pool2.assets.find((a: any) => a.symbol === vesuAdapter2.config.debt.symbol)?.stats!;
206
-
207
- // supplyApy: { value: '8057256029163289', decimals: 18 },
208
- // defiSpringSupplyApr: { value: '46856062629264560', decimals: 18 },
209
- // borrowApr: { value: '12167825982336000', decimals: 18 },
210
- const collateral1APY = Number(collateralAsset1.supplyApy.value) / 1e18;
211
- const debt1APY = Number(debtAsset1.borrowApr.value) / 1e18;
212
- const collateral2APY = Number(collateralAsset2.supplyApy.value) / 1e18;
213
- const debt2APY = Number(debtAsset2.borrowApr.value) / 1e18;
214
-
215
- const positions = await this.getVaultPositions();
226
+ const positions = await this.getVesuPositions();
216
227
  logger.verbose(`${this.metadata.name}::netAPY: positions: ${JSON.stringify(positions)}`);
228
+ const baseAPYs: number[] = [];
229
+ const rewardAPYs: number[] = [];
230
+ for (const [index, pool] of pools.entries()) {
231
+ const vesuAdapter = vesuAdapters[index];
232
+ const collateralAsset = pool.assets.find((a: any) => a.symbol === vesuAdapter.config.collateral.symbol)?.stats!;
233
+ const debtAsset = pool.assets.find((a: any) => a.symbol === vesuAdapter.config.debt.symbol)?.stats!;
234
+ const supplyApy = Number(collateralAsset.supplyApy.value || 0) / 1e18;
235
+ const lstAPY = Number(collateralAsset.lstApr?.value || 0) / 1e18;
236
+ baseAPYs.push(...[supplyApy + lstAPY, Number(debtAsset.borrowApr.value) / 1e18]);
237
+ rewardAPYs.push(...[Number(collateralAsset.defiSpringSupplyApr.value || "0") / 1e18, 0]);
238
+ }
239
+ logger.verbose(`${this.metadata.name}::netAPY: baseAPYs: ${JSON.stringify(baseAPYs)}`);
240
+ logger.verbose(`${this.metadata.name}::netAPY: rewardAPYs: ${JSON.stringify(rewardAPYs)}`);
241
+
242
+ // Else further compute will fail
243
+ assert(baseAPYs.length == positions.length, 'APYs and positions length mismatch');
244
+
245
+ // If no positions, return 0
217
246
  if (positions.every(p => p.amount.isZero())) {
218
247
  return { net: 0, splits: [{
219
248
  apy: 0, id: 'base'
@@ -221,12 +250,13 @@ export class UniversalStrategy<
221
250
  apy: 0, id: 'defispring'
222
251
  }]};
223
252
  }
253
+
254
+ // Compute APy using weights
224
255
  const weights = positions.map((p, index) => p.usdValue * (index % 2 == 0 ? 1 : -1));
225
- const baseAPYs = [collateral1APY, debt1APY, collateral2APY, debt2APY];
226
- assert(positions.length == baseAPYs.length, "Positions and APYs length mismatch");
227
- const rewardAPYs = [Number(collateralAsset1.defiSpringSupplyApr.value) / 1e18, 0, Number(collateralAsset2.defiSpringSupplyApr.value) / 1e18, 0];
228
- const baseAPY = this.computeAPY(baseAPYs, weights);
229
- const rewardAPY = this.computeAPY(rewardAPYs, weights);
256
+ const price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
257
+ const prevAUMUSD = prevAUM.multipliedBy(price.price);
258
+ const baseAPY = this.computeAPY(baseAPYs, weights, prevAUMUSD);
259
+ const rewardAPY = this.computeAPY(rewardAPYs, weights, prevAUMUSD);
230
260
  const netAPY = baseAPY + rewardAPY;
231
261
  logger.verbose(`${this.metadata.name}::netAPY: net: ${netAPY}, baseAPY: ${baseAPY}, rewardAPY: ${rewardAPY}`);
232
262
  return { net: netAPY, splits: [{
@@ -236,11 +266,11 @@ export class UniversalStrategy<
236
266
  }] };
237
267
  }
238
268
 
239
- private computeAPY(apys: number[], weights: number[]) {
269
+ private computeAPY(apys: number[], weights: number[], currentAUM: Web3Number) {
240
270
  assert(apys.length === weights.length, "APYs and weights length mismatch");
241
271
  const weightedSum = apys.reduce((acc, apy, i) => acc + apy * weights[i], 0);
242
- const totalWeight = weights.reduce((acc, weight) => acc + weight, 0);
243
- return weightedSum / totalWeight;
272
+ logger.verbose(`${this.getTag()} computeAPY: apys: ${JSON.stringify(apys)}, weights: ${JSON.stringify(weights)}, weightedSum: ${weightedSum}, currentAUM: ${currentAUM}`);
273
+ return weightedSum / currentAUM.toNumber();
244
274
  }
245
275
 
246
276
  /**
@@ -275,33 +305,61 @@ export class UniversalStrategy<
275
305
  };
276
306
  }
277
307
 
278
- async getAUM(): Promise<{net: SingleTokenInfo, prevAum: Web3Number, splits: {id: string, aum: Web3Number}[]}> {
308
+ protected async getVesuAUM(adapter: VesuAdapter) {
309
+ const legAUM = await adapter.getPositions(this.config);
310
+ const underlying = this.asset();
311
+ let vesuAum = Web3Number.fromWei("0", underlying.decimals);
312
+
313
+ // handle collateral
314
+ if (legAUM[0].token.address.eq(underlying.address)) {
315
+ vesuAum = vesuAum.plus(legAUM[0].amount);
316
+ } else {
317
+ const tokenPrice = await this.pricer.getPrice(legAUM[1].token.symbol);
318
+ vesuAum = vesuAum.plus(legAUM[1].usdValue / tokenPrice.price);
319
+ }
320
+
321
+ // handle debt
322
+ if (legAUM[1].token.address.eq(underlying.address)) {
323
+ vesuAum = vesuAum.minus(legAUM[1].amount);
324
+ } else {
325
+ const tokenPrice = await this.pricer.getPrice(legAUM[1].token.symbol);
326
+ vesuAum = vesuAum.minus(legAUM[1].usdValue / tokenPrice.price);
327
+ };
328
+
329
+ logger.verbose(`${this.getTag()} Vesu AUM: ${vesuAum}, legCollateral: ${legAUM[0].amount.toNumber()}, legDebt: ${legAUM[1].amount.toNumber()}`);
330
+ return vesuAum;
331
+ }
332
+
333
+ async getPrevAUM() {
279
334
  const currentAUM: bigint = await this.contract.call('aum', []) as bigint;
280
- const lastReportTime = await this.contract.call('last_report_timestamp', []);
335
+ const prevAum = Web3Number.fromWei(currentAUM.toString(), this.asset().decimals);
336
+ logger.verbose(`${this.getTag()} Prev AUM: ${prevAum}`);
337
+ return prevAum;
338
+ }
339
+
340
+ async getAUM(): Promise<{net: SingleTokenInfo, prevAum: Web3Number, splits: {id: string, aum: Web3Number}[]}> {
341
+ const prevAum = await this.getPrevAUM();
281
342
 
282
343
  const token1Price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
283
344
 
284
- // calculate actual aum
285
- const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
286
- const leg1AUM = await vesuAdapter1.getPositions(this.config);
287
- const leg2AUM = await vesuAdapter2.getPositions(this.config);
345
+ // calculate vesu aum
346
+ const vesuAdapters = this.getVesuAdapters();
347
+ let vesuAum = Web3Number.fromWei("0", this.asset().decimals);
348
+ for (const adapter of vesuAdapters) {
349
+ vesuAum = vesuAum.plus(await this.getVesuAUM(adapter));
350
+ }
288
351
 
352
+ // account unused balance as aum as well (from vault allocator)
289
353
  const balance = await this.getUnusedBalance();
290
354
  logger.verbose(`${this.getTag()} unused balance: ${balance.amount.toNumber()}`);
291
355
 
292
- const vesuAum = leg1AUM[0].amount
293
- .plus(leg2AUM[0].usdValue / token1Price.price)
294
- .minus(leg1AUM[1].usdValue / token1Price.price)
295
- .minus(leg2AUM[1].amount);
296
- logger.verbose(`${this.getTag()} Vesu AUM: leg1: ${leg1AUM[0].amount.toNumber()}, ${leg1AUM[1].amount.toNumber()}, leg2: ${leg2AUM[0].amount.toNumber()}, ${leg2AUM[1].amount.toNumber()}`);
297
-
356
+ // Initiate return values
298
357
  const zeroAmt = Web3Number.fromWei('0', this.asset().decimals);
299
358
  const net = {
300
359
  tokenInfo: this.asset(),
301
360
  amount: zeroAmt,
302
361
  usdValue: 0
303
362
  };
304
- const prevAum = Web3Number.fromWei(currentAUM.toString(), this.asset().decimals);
305
363
  if (vesuAum.isZero()) {
306
364
  return { net, splits: [{
307
365
  aum: zeroAmt, id: AUMTypes.FINALISED
@@ -312,20 +370,10 @@ export class UniversalStrategy<
312
370
  const aumToken = vesuAum.plus(balance.amount);
313
371
  logger.verbose(`${this.getTag()} Actual AUM: ${aumToken}`);
314
372
 
315
- // calculate estimated growth from strk rewards
316
- const netAPY = await this.netAPY();
317
- // account only 80% of value
318
- const defispringAPY = (netAPY.splits.find(s => s.id === 'defispring')?.apy || 0) * 0.8;
319
- if (!defispringAPY) throw new Error('DefiSpring APY not found');
320
-
321
- const timeDiff = (Math.round(Date.now() / 1000) - Number(lastReportTime));
322
- const growthRate = timeDiff * defispringAPY / (365 * 24 * 60 * 60);
323
- const rewardAssets = prevAum.multipliedBy(growthRate);
324
- logger.verbose(`${this.getTag()} DefiSpring AUM time difference: ${timeDiff}`);
325
- logger.verbose(`${this.getTag()} Current AUM: ${currentAUM}`);
326
- logger.verbose(`${this.getTag()} Net APY: ${JSON.stringify(netAPY)}`);
327
- logger.verbose(`${this.getTag()} rewards AUM: ${rewardAssets}`);
373
+ // compute rewards contribution to AUM
374
+ const rewardAssets = await this.getRewardsAUM(prevAum);
328
375
 
376
+ // Sum up and return
329
377
  const newAUM = aumToken.plus(rewardAssets);
330
378
  logger.verbose(`${this.getTag()} New AUM: ${newAUM}`);
331
379
 
@@ -341,6 +389,27 @@ export class UniversalStrategy<
341
389
  return { net, splits, prevAum };
342
390
  }
343
391
 
392
+ // account for future rewards (e.g. defispring rewards)
393
+ protected async getRewardsAUM(prevAum: Web3Number) {
394
+ const lastReportTime = await this.contract.call('last_report_timestamp', []);
395
+ // - calculate estimated growth from strk rewards
396
+ const netAPY = await this.netAPY();
397
+ // account only 80% of value
398
+ const defispringAPY = (netAPY.splits.find(s => s.id === 'defispring')?.apy || 0) * 0.8;
399
+ if (!defispringAPY) throw new Error('DefiSpring APY not found');
400
+
401
+ // compute rewards contribution to AUM
402
+ const timeDiff = (Math.round(Date.now() / 1000) - Number(lastReportTime));
403
+ const growthRate = timeDiff * defispringAPY / (365 * 24 * 60 * 60);
404
+ const rewardAssets = prevAum.multipliedBy(growthRate);
405
+ logger.verbose(`${this.getTag()} DefiSpring AUM time difference: ${timeDiff}`);
406
+ logger.verbose(`${this.getTag()} Current AUM: ${prevAum.toString()}`);
407
+ logger.verbose(`${this.getTag()} Net APY: ${JSON.stringify(netAPY)}`);
408
+ logger.verbose(`${this.getTag()} rewards AUM: ${rewardAssets}`);
409
+
410
+ return rewardAssets;
411
+ }
412
+
344
413
  getVesuAdapters() {
345
414
  const vesuAdapter1 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG1) as VesuAdapter;
346
415
  const vesuAdapter2 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG2) as VesuAdapter;
@@ -352,11 +421,24 @@ export class UniversalStrategy<
352
421
  return [vesuAdapter1, vesuAdapter2];
353
422
  }
354
423
 
424
+ async getVesuPositions(): Promise<VaultPosition[]> {
425
+ const adapters = this.getVesuAdapters();
426
+ const positions: VaultPosition[] = [];
427
+ for (const adapter of adapters) {
428
+ positions.push(...await adapter.getPositions(this.config));
429
+ }
430
+ return positions;
431
+ }
432
+
355
433
  async getVaultPositions(): Promise<VaultPosition[]> {
356
- const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
357
- const leg1Positions = await vesuAdapter1.getPositions(this.config);
358
- const leg2Positions = await vesuAdapter2.getPositions(this.config);
359
- return [...leg1Positions, ...leg2Positions];
434
+ const vesuPositions = await this.getVesuPositions();
435
+ const unusedBalance = await this.getUnusedBalance();
436
+ return [...vesuPositions, {
437
+ amount: unusedBalance.amount,
438
+ usdValue: unusedBalance.usdValue,
439
+ token: this.asset(),
440
+ remarks: "Unused Balance"
441
+ }];
360
442
  }
361
443
 
362
444
  getSetManagerCall(strategist: ContractAddr, root = this.getMerkleRoot()) {
@@ -379,7 +461,7 @@ export class UniversalStrategy<
379
461
  isDeposit: boolean,
380
462
  depositAmount: Web3Number,
381
463
  debtAmount: Web3Number
382
- }) {
464
+ }): UniversalManageCall[] {
383
465
  assert(params.depositAmount.gt(0) || params.debtAmount.gt(0), 'Either deposit or debt amount must be greater than 0');
384
466
  // approve token
385
467
  const isToken1 = params.isLeg1 == params.isDeposit; // XOR
@@ -522,7 +604,7 @@ export class UniversalStrategy<
522
604
  }
523
605
  }
524
606
 
525
- async getVesuMultiplyCall(params: {
607
+ async getVesuModifyPositionCall(params: {
526
608
  isDeposit: boolean,
527
609
  leg1DepositAmount: Web3Number
528
610
  }) {
@@ -994,7 +1076,7 @@ function getFAQs(): FAQ[] {
994
1076
  ];
995
1077
  }
996
1078
 
997
- function getContractDetails(settings: UniversalStrategySettings): {address: ContractAddr, name: string}[] {
1079
+ export function getContractDetails(settings: UniversalStrategySettings): {address: ContractAddr, name: string}[] {
998
1080
  return [
999
1081
  { address: settings.vaultAddress, name: "Vault" },
1000
1082
  { address: settings.manager, name: "Vault Manager" },
@@ -1,3 +1,4 @@
1
+
1
2
  export * from '@/utils/logger';
2
3
  export * from './oz-merkle';
3
4
 
@@ -19,4 +20,5 @@ export function assert(condition: boolean, message: string) {
19
20
 
20
21
  export function getTrovesEndpoint(): string {
21
22
  return process.env.TROVES_ENDPOINT || 'https://app.troves.fi';
22
- }
23
+ }
24
+
@@ -12,7 +12,7 @@ const colors = {
12
12
  winston.addColors(colors);
13
13
 
14
14
  export const logger = winston.createLogger({
15
- level: "verbose", // Set the minimum logging level
15
+ level: "debug", // Set the minimum logging level
16
16
  format: format.combine(
17
17
  format.colorize({ all: true }), // Apply custom colors
18
18
  format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), // Add timestamp to log messages