@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.
- package/dist/cli.js +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.browser.global.js +1195 -74
- package/dist/index.browser.mjs +1350 -227
- package/dist/index.d.ts +168 -30
- package/dist/index.js +1366 -239
- package/dist/index.mjs +1351 -228
- package/package.json +1 -1
- package/src/data/vesu-multiple.abi.json +475 -0
- package/src/dataTypes/_bignumber.ts +13 -0
- package/src/modules/ekubo-quoter.ts +127 -0
- package/src/modules/index.ts +1 -0
- package/src/strategies/ekubo-cl-vault.tsx +25 -11
- package/src/strategies/index.ts +2 -1
- package/src/strategies/universal-adapters/adapter-utils.ts +3 -0
- package/src/strategies/universal-adapters/baseAdapter.ts +3 -3
- package/src/strategies/universal-adapters/vesu-adapter.ts +244 -3
- package/src/strategies/universal-lst-muliplier-strategy.tsx +457 -0
- package/src/strategies/universal-strategy.tsx +134 -60
- package/src/utils/index.ts +3 -1
- package/src/utils/logger.node.ts +1 -1
|
@@ -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
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
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
|
|
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
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
const
|
|
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
|
-
|
|
243
|
-
return weightedSum /
|
|
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
|
|
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
|
|
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
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
const
|
|
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
|
-
|
|
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
|
-
//
|
|
316
|
-
const
|
|
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
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
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
|
|
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" },
|
package/src/utils/index.ts
CHANGED
|
@@ -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
|
+
|
package/src/utils/logger.node.ts
CHANGED
|
@@ -12,7 +12,7 @@ const colors = {
|
|
|
12
12
|
winston.addColors(colors);
|
|
13
13
|
|
|
14
14
|
export const logger = winston.createLogger({
|
|
15
|
-
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
|