@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.
- package/dist/cli.js +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.browser.global.js +1241 -74
- package/dist/index.browser.mjs +1396 -227
- package/dist/index.d.ts +169 -30
- package/dist/index.js +1412 -239
- package/dist/index.mjs +1397 -228
- package/package.json +1 -1
- package/src/data/vesu-multiple.abi.json +475 -0
- package/src/dataTypes/_bignumber.ts +13 -0
- package/src/interfaces/common.tsx +2 -1
- 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 +497 -0
- package/src/strategies/universal-strategy.tsx +142 -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,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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
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
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
const
|
|
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
|
-
|
|
243
|
-
return weightedSum /
|
|
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
|
|
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
|
|
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
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
const
|
|
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
|
-
|
|
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
|
-
//
|
|
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}`);
|
|
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
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
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
|
|
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" },
|
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
|