@strkfarm/sdk 1.1.8 → 1.1.9

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,38 @@ 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
+ const prevAUM = await this.getPrevAUM();
207
+
208
+ // get Vesu pools, positions and APYs
209
+ const vesuAdapters = this.getVesuAdapters();
210
+ const allVesuPools = await VesuAdapter.getVesuPools();
211
+ const pools = vesuAdapters.map((vesuAdapter) => {
212
+ return allVesuPools.pools.find(p => vesuAdapter.config.poolId.eqString(num.getHexString(p.id)));
213
+ });
214
+ logger.verbose(`${this.metadata.name}::netAPY: vesu-pools: ${JSON.stringify(pools)}`);
215
+ if (pools.some(p => !p)) {
200
216
  throw new Error('Pool not found');
201
217
  };
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();
218
+ const positions = await this.getVesuPositions();
216
219
  logger.verbose(`${this.metadata.name}::netAPY: positions: ${JSON.stringify(positions)}`);
220
+ const baseAPYs: number[] = [];
221
+ const rewardAPYs: number[] = [];
222
+ for (const [index, pool] of pools.entries()) {
223
+ const vesuAdapter = vesuAdapters[index];
224
+ const collateralAsset = pool.assets.find((a: any) => a.symbol === vesuAdapter.config.collateral.symbol)?.stats!;
225
+ const debtAsset = pool.assets.find((a: any) => a.symbol === vesuAdapter.config.debt.symbol)?.stats!;
226
+ const supplyApy = Number(collateralAsset.supplyApy.value || 0) / 1e18;
227
+ const lstAPY = Number(collateralAsset.lstApr?.value || 0) / 1e18;
228
+ baseAPYs.push(...[supplyApy + lstAPY, Number(debtAsset.borrowApr.value) / 1e18]);
229
+ rewardAPYs.push(...[Number(collateralAsset.defiSpringSupplyApr.value || "0") / 1e18, 0]);
230
+ }
231
+ logger.verbose(`${this.metadata.name}::netAPY: baseAPYs: ${JSON.stringify(baseAPYs)}`);
232
+ logger.verbose(`${this.metadata.name}::netAPY: rewardAPYs: ${JSON.stringify(rewardAPYs)}`);
233
+
234
+ // Else further compute will fail
235
+ assert(baseAPYs.length == positions.length, 'APYs and positions length mismatch');
236
+
237
+ // If no positions, return 0
217
238
  if (positions.every(p => p.amount.isZero())) {
218
239
  return { net: 0, splits: [{
219
240
  apy: 0, id: 'base'
@@ -221,12 +242,13 @@ export class UniversalStrategy<
221
242
  apy: 0, id: 'defispring'
222
243
  }]};
223
244
  }
245
+
246
+ // Compute APy using weights
224
247
  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);
248
+ const price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
249
+ const prevAUMUSD = prevAUM.multipliedBy(price.price);
250
+ const baseAPY = this.computeAPY(baseAPYs, weights, prevAUMUSD);
251
+ const rewardAPY = this.computeAPY(rewardAPYs, weights, prevAUMUSD);
230
252
  const netAPY = baseAPY + rewardAPY;
231
253
  logger.verbose(`${this.metadata.name}::netAPY: net: ${netAPY}, baseAPY: ${baseAPY}, rewardAPY: ${rewardAPY}`);
232
254
  return { net: netAPY, splits: [{
@@ -236,11 +258,11 @@ export class UniversalStrategy<
236
258
  }] };
237
259
  }
238
260
 
239
- private computeAPY(apys: number[], weights: number[]) {
261
+ private computeAPY(apys: number[], weights: number[], currentAUM: Web3Number) {
240
262
  assert(apys.length === weights.length, "APYs and weights length mismatch");
241
263
  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;
264
+ logger.verbose(`${this.getTag()} computeAPY: apys: ${JSON.stringify(apys)}, weights: ${JSON.stringify(weights)}, weightedSum: ${weightedSum}, currentAUM: ${currentAUM}`);
265
+ return weightedSum / currentAUM.toNumber();
244
266
  }
245
267
 
246
268
  /**
@@ -275,33 +297,61 @@ export class UniversalStrategy<
275
297
  };
276
298
  }
277
299
 
278
- async getAUM(): Promise<{net: SingleTokenInfo, prevAum: Web3Number, splits: {id: string, aum: Web3Number}[]}> {
300
+ protected async getVesuAUM(adapter: VesuAdapter) {
301
+ const legAUM = await adapter.getPositions(this.config);
302
+ const underlying = this.asset();
303
+ let vesuAum = Web3Number.fromWei("0", underlying.decimals);
304
+
305
+ // handle collateral
306
+ if (legAUM[0].token.address.eq(underlying.address)) {
307
+ vesuAum = vesuAum.plus(legAUM[0].amount);
308
+ } else {
309
+ const tokenPrice = await this.pricer.getPrice(legAUM[1].token.symbol);
310
+ vesuAum = vesuAum.plus(legAUM[1].usdValue / tokenPrice.price);
311
+ }
312
+
313
+ // handle debt
314
+ if (legAUM[1].token.address.eq(underlying.address)) {
315
+ vesuAum = vesuAum.minus(legAUM[1].amount);
316
+ } else {
317
+ const tokenPrice = await this.pricer.getPrice(legAUM[1].token.symbol);
318
+ vesuAum = vesuAum.minus(legAUM[1].usdValue / tokenPrice.price);
319
+ };
320
+
321
+ logger.verbose(`${this.getTag()} Vesu AUM: ${vesuAum}, legCollateral: ${legAUM[0].amount.toNumber()}, legDebt: ${legAUM[1].amount.toNumber()}`);
322
+ return vesuAum;
323
+ }
324
+
325
+ async getPrevAUM() {
279
326
  const currentAUM: bigint = await this.contract.call('aum', []) as bigint;
280
- const lastReportTime = await this.contract.call('last_report_timestamp', []);
327
+ const prevAum = Web3Number.fromWei(currentAUM.toString(), this.asset().decimals);
328
+ logger.verbose(`${this.getTag()} Prev AUM: ${prevAum}`);
329
+ return prevAum;
330
+ }
331
+
332
+ async getAUM(): Promise<{net: SingleTokenInfo, prevAum: Web3Number, splits: {id: string, aum: Web3Number}[]}> {
333
+ const prevAum = await this.getPrevAUM();
281
334
 
282
335
  const token1Price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
283
336
 
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);
337
+ // calculate vesu aum
338
+ const vesuAdapters = this.getVesuAdapters();
339
+ let vesuAum = Web3Number.fromWei("0", this.asset().decimals);
340
+ for (const adapter of vesuAdapters) {
341
+ vesuAum = vesuAum.plus(await this.getVesuAUM(adapter));
342
+ }
288
343
 
344
+ // account unused balance as aum as well (from vault allocator)
289
345
  const balance = await this.getUnusedBalance();
290
346
  logger.verbose(`${this.getTag()} unused balance: ${balance.amount.toNumber()}`);
291
347
 
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
-
348
+ // Initiate return values
298
349
  const zeroAmt = Web3Number.fromWei('0', this.asset().decimals);
299
350
  const net = {
300
351
  tokenInfo: this.asset(),
301
352
  amount: zeroAmt,
302
353
  usdValue: 0
303
354
  };
304
- const prevAum = Web3Number.fromWei(currentAUM.toString(), this.asset().decimals);
305
355
  if (vesuAum.isZero()) {
306
356
  return { net, splits: [{
307
357
  aum: zeroAmt, id: AUMTypes.FINALISED
@@ -312,20 +362,10 @@ export class UniversalStrategy<
312
362
  const aumToken = vesuAum.plus(balance.amount);
313
363
  logger.verbose(`${this.getTag()} Actual AUM: ${aumToken}`);
314
364
 
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}`);
365
+ // compute rewards contribution to AUM
366
+ const rewardAssets = await this.getRewardsAUM(prevAum);
328
367
 
368
+ // Sum up and return
329
369
  const newAUM = aumToken.plus(rewardAssets);
330
370
  logger.verbose(`${this.getTag()} New AUM: ${newAUM}`);
331
371
 
@@ -341,6 +381,27 @@ export class UniversalStrategy<
341
381
  return { net, splits, prevAum };
342
382
  }
343
383
 
384
+ // account for future rewards (e.g. defispring rewards)
385
+ protected async getRewardsAUM(prevAum: Web3Number) {
386
+ const lastReportTime = await this.contract.call('last_report_timestamp', []);
387
+ // - calculate estimated growth from strk rewards
388
+ const netAPY = await this.netAPY();
389
+ // account only 80% of value
390
+ const defispringAPY = (netAPY.splits.find(s => s.id === 'defispring')?.apy || 0) * 0.8;
391
+ if (!defispringAPY) throw new Error('DefiSpring APY not found');
392
+
393
+ // compute rewards contribution to AUM
394
+ const timeDiff = (Math.round(Date.now() / 1000) - Number(lastReportTime));
395
+ const growthRate = timeDiff * defispringAPY / (365 * 24 * 60 * 60);
396
+ const rewardAssets = prevAum.multipliedBy(growthRate);
397
+ logger.verbose(`${this.getTag()} DefiSpring AUM time difference: ${timeDiff}`);
398
+ logger.verbose(`${this.getTag()} Current AUM: ${prevAum.toString()}`);
399
+ logger.verbose(`${this.getTag()} Net APY: ${JSON.stringify(netAPY)}`);
400
+ logger.verbose(`${this.getTag()} rewards AUM: ${rewardAssets}`);
401
+
402
+ return rewardAssets;
403
+ }
404
+
344
405
  getVesuAdapters() {
345
406
  const vesuAdapter1 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG1) as VesuAdapter;
346
407
  const vesuAdapter2 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG2) as VesuAdapter;
@@ -352,11 +413,24 @@ export class UniversalStrategy<
352
413
  return [vesuAdapter1, vesuAdapter2];
353
414
  }
354
415
 
416
+ async getVesuPositions(): Promise<VaultPosition[]> {
417
+ const adapters = this.getVesuAdapters();
418
+ const positions: VaultPosition[] = [];
419
+ for (const adapter of adapters) {
420
+ positions.push(...await adapter.getPositions(this.config));
421
+ }
422
+ return positions;
423
+ }
424
+
355
425
  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];
426
+ const vesuPositions = await this.getVesuPositions();
427
+ const unusedBalance = await this.getUnusedBalance();
428
+ return [...vesuPositions, {
429
+ amount: unusedBalance.amount,
430
+ usdValue: unusedBalance.usdValue,
431
+ token: this.asset(),
432
+ remarks: "Unused Balance"
433
+ }];
360
434
  }
361
435
 
362
436
  getSetManagerCall(strategist: ContractAddr, root = this.getMerkleRoot()) {
@@ -379,7 +453,7 @@ export class UniversalStrategy<
379
453
  isDeposit: boolean,
380
454
  depositAmount: Web3Number,
381
455
  debtAmount: Web3Number
382
- }) {
456
+ }): UniversalManageCall[] {
383
457
  assert(params.depositAmount.gt(0) || params.debtAmount.gt(0), 'Either deposit or debt amount must be greater than 0');
384
458
  // approve token
385
459
  const isToken1 = params.isLeg1 == params.isDeposit; // XOR
@@ -522,7 +596,7 @@ export class UniversalStrategy<
522
596
  }
523
597
  }
524
598
 
525
- async getVesuMultiplyCall(params: {
599
+ async getVesuModifyPositionCall(params: {
526
600
  isDeposit: boolean,
527
601
  leg1DepositAmount: Web3Number
528
602
  }) {
@@ -994,7 +1068,7 @@ function getFAQs(): FAQ[] {
994
1068
  ];
995
1069
  }
996
1070
 
997
- function getContractDetails(settings: UniversalStrategySettings): {address: ContractAddr, name: string}[] {
1071
+ export function getContractDetails(settings: UniversalStrategySettings): {address: ContractAddr, name: string}[] {
998
1072
  return [
999
1073
  { address: settings.vaultAddress, name: "Vault" },
1000
1074
  { 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